summaryrefslogtreecommitdiffstats
path: root/toolkit/mozapps/update
diff options
context:
space:
mode:
Diffstat (limited to 'toolkit/mozapps/update')
-rw-r--r--toolkit/mozapps/update/UpdateTelemetry.jsm488
-rw-r--r--toolkit/mozapps/update/common-standalone/moz.build12
-rw-r--r--toolkit/mozapps/update/common/certificatecheck.cpp250
-rw-r--r--toolkit/mozapps/update/common/certificatecheck.h22
-rw-r--r--toolkit/mozapps/update/common/errors.h110
-rw-r--r--toolkit/mozapps/update/common/moz.build32
-rw-r--r--toolkit/mozapps/update/common/pathhash.cpp139
-rw-r--r--toolkit/mozapps/update/common/pathhash.h19
-rw-r--r--toolkit/mozapps/update/common/readstrings.cpp236
-rw-r--r--toolkit/mozapps/update/common/readstrings.h43
-rw-r--r--toolkit/mozapps/update/common/registrycertificates.cpp154
-rw-r--r--toolkit/mozapps/update/common/registrycertificates.h14
-rw-r--r--toolkit/mozapps/update/common/sources.mozbuild28
-rw-r--r--toolkit/mozapps/update/common/uachelper.cpp222
-rw-r--r--toolkit/mozapps/update/common/uachelper.h23
-rw-r--r--toolkit/mozapps/update/common/updatecommon.cpp213
-rw-r--r--toolkit/mozapps/update/common/updatecommon.h47
-rw-r--r--toolkit/mozapps/update/common/updatedefines.h142
-rw-r--r--toolkit/mozapps/update/common/updatehelper.cpp609
-rw-r--r--toolkit/mozapps/update/common/updatehelper.h29
-rw-r--r--toolkit/mozapps/update/common/win_dirent.h32
-rw-r--r--toolkit/mozapps/update/content/history.js70
-rw-r--r--toolkit/mozapps/update/content/history.xul39
-rw-r--r--toolkit/mozapps/update/content/updates.css33
-rw-r--r--toolkit/mozapps/update/content/updates.js1399
-rw-r--r--toolkit/mozapps/update/content/updates.xml83
-rw-r--r--toolkit/mozapps/update/content/updates.xul206
-rw-r--r--toolkit/mozapps/update/jar.mn12
-rw-r--r--toolkit/mozapps/update/moz.build33
-rw-r--r--toolkit/mozapps/update/nsIUpdateService.idl575
-rw-r--r--toolkit/mozapps/update/nsUpdateService.js4567
-rw-r--r--toolkit/mozapps/update/nsUpdateService.manifest12
-rw-r--r--toolkit/mozapps/update/nsUpdateServiceStub.js45
-rw-r--r--toolkit/mozapps/update/tests/Makefile.in39
-rw-r--r--toolkit/mozapps/update/tests/TestAUSHelper.cpp423
-rw-r--r--toolkit/mozapps/update/tests/TestAUSReadStrings.cpp172
-rw-r--r--toolkit/mozapps/update/tests/TestAUSReadStrings1.ini47
-rw-r--r--toolkit/mozapps/update/tests/TestAUSReadStrings2.ini39
-rw-r--r--toolkit/mozapps/update/tests/TestAUSReadStrings3.ini39
-rw-r--r--toolkit/mozapps/update/tests/chrome/.eslintrc.js7
-rw-r--r--toolkit/mozapps/update/tests/chrome/chrome.ini64
-rw-r--r--toolkit/mozapps/update/tests/chrome/test_0010_background_basic.xul50
-rw-r--r--toolkit/mozapps/update/tests/chrome/test_0011_check_basic.xul51
-rw-r--r--toolkit/mozapps/update/tests/chrome/test_0012_check_basic_staging.xul55
-rw-r--r--toolkit/mozapps/update/tests/chrome/test_0013_check_no_updates.xul46
-rw-r--r--toolkit/mozapps/update/tests/chrome/test_0014_check_error_xml_malformed.xul46
-rw-r--r--toolkit/mozapps/update/tests/chrome/test_0061_check_verifyFailPartial_noComplete.xul52
-rw-r--r--toolkit/mozapps/update/tests/chrome/test_0062_check_verifyFailComplete_noPartial.xul52
-rw-r--r--toolkit/mozapps/update/tests/chrome/test_0063_check_verifyFailPartialComplete.xul52
-rw-r--r--toolkit/mozapps/update/tests/chrome/test_0064_check_verifyFailPartial_successComplete.xul52
-rw-r--r--toolkit/mozapps/update/tests/chrome/test_0071_notify_verifyFailPartial_noComplete.xul53
-rw-r--r--toolkit/mozapps/update/tests/chrome/test_0072_notify_verifyFailComplete_noPartial.xul52
-rw-r--r--toolkit/mozapps/update/tests/chrome/test_0073_notify_verifyFailPartialComplete.xul55
-rw-r--r--toolkit/mozapps/update/tests/chrome/test_0074_notify_verifyFailPartial_successComplete.xul55
-rw-r--r--toolkit/mozapps/update/tests/chrome/test_0081_error_patchApplyFailure_partial_only.xul53
-rw-r--r--toolkit/mozapps/update/tests/chrome/test_0082_error_patchApplyFailure_complete_only.xul52
-rw-r--r--toolkit/mozapps/update/tests/chrome/test_0083_error_patchApplyFailure_partial_complete.xul67
-rw-r--r--toolkit/mozapps/update/tests/chrome/test_0084_error_patchApplyFailure_verify_failed.xul68
-rw-r--r--toolkit/mozapps/update/tests/chrome/test_0085_error_patchApplyFailure_partial_complete_staging.xul94
-rw-r--r--toolkit/mozapps/update/tests/chrome/test_0092_finishedBackground.xul55
-rw-r--r--toolkit/mozapps/update/tests/chrome/test_0093_restartNotification.xul60
-rw-r--r--toolkit/mozapps/update/tests/chrome/test_0094_restartNotification_remote.xul60
-rw-r--r--toolkit/mozapps/update/tests/chrome/test_0095_restartNotification_remoteInvalidNumber.xul66
-rw-r--r--toolkit/mozapps/update/tests/chrome/test_0096_restartNotification_stagedBackground.xul65
-rw-r--r--toolkit/mozapps/update/tests/chrome/test_0097_restartNotification_stagedServiceBackground.xul65
-rw-r--r--toolkit/mozapps/update/tests/chrome/test_0101_background_restartNotification.xul46
-rw-r--r--toolkit/mozapps/update/tests/chrome/test_0102_background_restartNotification_staging.xul49
-rw-r--r--toolkit/mozapps/update/tests/chrome/test_0103_background_restartNotification_stagingService.xul50
-rw-r--r--toolkit/mozapps/update/tests/chrome/test_0111_neverButton_basic.xul61
-rw-r--r--toolkit/mozapps/update/tests/chrome/test_0113_showNeverForVersionRemovedWithPref.xul58
-rw-r--r--toolkit/mozapps/update/tests/chrome/test_0151_notify_backgroundCheckError.xul50
-rw-r--r--toolkit/mozapps/update/tests/chrome/test_0152_notify_backgroundCheckOfflineRetry.xul96
-rw-r--r--toolkit/mozapps/update/tests/chrome/test_0161_check_unsupported.xul50
-rw-r--r--toolkit/mozapps/update/tests/chrome/test_0162_notify_unsupported.xul44
-rw-r--r--toolkit/mozapps/update/tests/chrome/test_0171_check_noPerms_manual.xul64
-rw-r--r--toolkit/mozapps/update/tests/chrome/test_0172_notify_noPerms_manual.xul63
-rw-r--r--toolkit/mozapps/update/tests/chrome/test_9999_cleanup.xul112
-rw-r--r--toolkit/mozapps/update/tests/chrome/update.sjs194
-rw-r--r--toolkit/mozapps/update/tests/chrome/utils.js1011
-rw-r--r--toolkit/mozapps/update/tests/data/complete.exebin0 -> 79872 bytes
-rw-r--r--toolkit/mozapps/update/tests/data/complete.marbin0 -> 97888 bytes
-rw-r--r--toolkit/mozapps/update/tests/data/complete.pngbin0 -> 878 bytes
-rw-r--r--toolkit/mozapps/update/tests/data/complete_log_success_mac332
-rw-r--r--toolkit/mozapps/update/tests/data/complete_log_success_win320
-rw-r--r--toolkit/mozapps/update/tests/data/complete_mac.marbin0 -> 98454 bytes
-rw-r--r--toolkit/mozapps/update/tests/data/complete_precomplete18
-rw-r--r--toolkit/mozapps/update/tests/data/complete_precomplete_mac21
-rw-r--r--toolkit/mozapps/update/tests/data/complete_removed-files41
-rw-r--r--toolkit/mozapps/update/tests/data/complete_removed-files_mac41
-rw-r--r--toolkit/mozapps/update/tests/data/complete_update_manifest59
-rw-r--r--toolkit/mozapps/update/tests/data/old_version.marbin0 -> 421 bytes
-rw-r--r--toolkit/mozapps/update/tests/data/partial.exebin0 -> 79872 bytes
-rw-r--r--toolkit/mozapps/update/tests/data/partial.marbin0 -> 10645 bytes
-rw-r--r--toolkit/mozapps/update/tests/data/partial.pngbin0 -> 776 bytes
-rw-r--r--toolkit/mozapps/update/tests/data/partial_log_failure_mac192
-rw-r--r--toolkit/mozapps/update/tests/data/partial_log_failure_win192
-rw-r--r--toolkit/mozapps/update/tests/data/partial_log_success_mac279
-rw-r--r--toolkit/mozapps/update/tests/data/partial_log_success_win279
-rw-r--r--toolkit/mozapps/update/tests/data/partial_mac.marbin0 -> 11172 bytes
-rw-r--r--toolkit/mozapps/update/tests/data/partial_precomplete19
-rw-r--r--toolkit/mozapps/update/tests/data/partial_precomplete_mac22
-rw-r--r--toolkit/mozapps/update/tests/data/partial_removed-files41
-rw-r--r--toolkit/mozapps/update/tests/data/partial_removed-files_mac41
-rw-r--r--toolkit/mozapps/update/tests/data/partial_update_manifest63
-rw-r--r--toolkit/mozapps/update/tests/data/replace_log_success6
-rw-r--r--toolkit/mozapps/update/tests/data/shared.js632
-rw-r--r--toolkit/mozapps/update/tests/data/sharedUpdateXML.js364
-rw-r--r--toolkit/mozapps/update/tests/data/simple.marbin0 -> 1031 bytes
-rw-r--r--toolkit/mozapps/update/tests/data/wrong_product_channel.marbin0 -> 421 bytes
-rw-r--r--toolkit/mozapps/update/tests/data/xpcshellConstantsPP.js53
-rw-r--r--toolkit/mozapps/update/tests/data/xpcshellUtilsAUS.js4047
-rw-r--r--toolkit/mozapps/update/tests/moz.build101
-rw-r--r--toolkit/mozapps/update/tests/unit_aus_update/.eslintrc.js7
-rw-r--r--toolkit/mozapps/update/tests/unit_aus_update/canCheckForAndCanApplyUpdates.js138
-rw-r--r--toolkit/mozapps/update/tests/unit_aus_update/cleanupDownloadingForDifferentChannel.js46
-rw-r--r--toolkit/mozapps/update/tests/unit_aus_update/cleanupDownloadingForOlderAppVersion.js29
-rw-r--r--toolkit/mozapps/update/tests/unit_aus_update/cleanupDownloadingForSameVersionAndBuildID.js30
-rw-r--r--toolkit/mozapps/update/tests/unit_aus_update/cleanupDownloadingIncorrectStatus.js35
-rw-r--r--toolkit/mozapps/update/tests/unit_aus_update/cleanupPendingVersionFileIncorrectStatus.js37
-rw-r--r--toolkit/mozapps/update/tests/unit_aus_update/cleanupSuccessLogMove.js37
-rw-r--r--toolkit/mozapps/update/tests/unit_aus_update/cleanupSuccessLogsFIFO.js45
-rw-r--r--toolkit/mozapps/update/tests/unit_aus_update/downloadAndHashCheckMar.js161
-rw-r--r--toolkit/mozapps/update/tests/unit_aus_update/downloadCompleteAfterPartialFailure.js66
-rw-r--r--toolkit/mozapps/update/tests/unit_aus_update/downloadInterruptedRecovery.js225
-rw-r--r--toolkit/mozapps/update/tests/unit_aus_update/downloadResumeForSameAppVersion.js37
-rw-r--r--toolkit/mozapps/update/tests/unit_aus_update/head_update.js8
-rw-r--r--toolkit/mozapps/update/tests/unit_aus_update/remoteUpdateXML.js285
-rw-r--r--toolkit/mozapps/update/tests/unit_aus_update/uiAutoPref.js75
-rw-r--r--toolkit/mozapps/update/tests/unit_aus_update/uiSilentPref.js76
-rw-r--r--toolkit/mozapps/update/tests/unit_aus_update/uiUnsupportedAlreadyNotified.js74
-rw-r--r--toolkit/mozapps/update/tests/unit_aus_update/updateManagerXML.js177
-rw-r--r--toolkit/mozapps/update/tests/unit_aus_update/urlConstruction.js305
-rw-r--r--toolkit/mozapps/update/tests/unit_aus_update/xpcshell.ini27
-rw-r--r--toolkit/mozapps/update/tests/unit_base_updater/.eslintrc.js7
-rw-r--r--toolkit/mozapps/update/tests/unit_base_updater/head_update.js8
-rw-r--r--toolkit/mozapps/update/tests/unit_base_updater/invalidArgCallbackFileNotInInstallDirFailure.js37
-rw-r--r--toolkit/mozapps/update/tests/unit_base_updater/invalidArgCallbackFilePathTooLongFailure.js45
-rw-r--r--toolkit/mozapps/update/tests/unit_base_updater/invalidArgInstallDirPathTooLongFailure.js47
-rw-r--r--toolkit/mozapps/update/tests/unit_base_updater/invalidArgInstallDirPathTraversalFailure.js44
-rw-r--r--toolkit/mozapps/update/tests/unit_base_updater/invalidArgInstallWorkingDirPathNotSameFailure_win.js38
-rw-r--r--toolkit/mozapps/update/tests/unit_base_updater/invalidArgPatchDirPathTraversalFailure.js43
-rw-r--r--toolkit/mozapps/update/tests/unit_base_updater/invalidArgStageDirNotInInstallDirFailure_win.js38
-rw-r--r--toolkit/mozapps/update/tests/unit_base_updater/invalidArgWorkingDirPathLocalUNCFailure_win.js38
-rw-r--r--toolkit/mozapps/update/tests/unit_base_updater/invalidArgWorkingDirPathRelativeFailure.js37
-rw-r--r--toolkit/mozapps/update/tests/unit_base_updater/marAppApplyDirLockedStageFailure_win.js41
-rw-r--r--toolkit/mozapps/update/tests/unit_base_updater/marAppApplyUpdateAppBinInUseStageSuccess_win.js83
-rw-r--r--toolkit/mozapps/update/tests/unit_base_updater/marAppApplyUpdateStageOldVersionFailure.js88
-rw-r--r--toolkit/mozapps/update/tests/unit_base_updater/marAppApplyUpdateStageSuccess.js82
-rw-r--r--toolkit/mozapps/update/tests/unit_base_updater/marAppApplyUpdateSuccess.js65
-rw-r--r--toolkit/mozapps/update/tests/unit_base_updater/marAppInUseStageFailureComplete_win.js70
-rw-r--r--toolkit/mozapps/update/tests/unit_base_updater/marAppInUseStageSuccessComplete_unix.js113
-rw-r--r--toolkit/mozapps/update/tests/unit_base_updater/marAppInUseSuccessComplete.js62
-rw-r--r--toolkit/mozapps/update/tests/unit_base_updater/marCallbackAppStageSuccessComplete_win.js61
-rw-r--r--toolkit/mozapps/update/tests/unit_base_updater/marCallbackAppStageSuccessPartial_win.js61
-rw-r--r--toolkit/mozapps/update/tests/unit_base_updater/marCallbackAppSuccessComplete_win.js48
-rw-r--r--toolkit/mozapps/update/tests/unit_base_updater/marCallbackAppSuccessPartial_win.js48
-rw-r--r--toolkit/mozapps/update/tests/unit_base_updater/marFailurePartial.js47
-rw-r--r--toolkit/mozapps/update/tests/unit_base_updater/marFileInUseStageFailureComplete_win.js67
-rw-r--r--toolkit/mozapps/update/tests/unit_base_updater/marFileInUseStageFailurePartial_win.js67
-rw-r--r--toolkit/mozapps/update/tests/unit_base_updater/marFileInUseSuccessComplete_win.js63
-rw-r--r--toolkit/mozapps/update/tests/unit_base_updater/marFileInUseSuccessPartial_win.js63
-rw-r--r--toolkit/mozapps/update/tests/unit_base_updater/marFileLockedFailureComplete_win.js57
-rw-r--r--toolkit/mozapps/update/tests/unit_base_updater/marFileLockedFailurePartial_win.js56
-rw-r--r--toolkit/mozapps/update/tests/unit_base_updater/marFileLockedStageFailureComplete_win.js71
-rw-r--r--toolkit/mozapps/update/tests/unit_base_updater/marFileLockedStageFailurePartial_win.js70
-rw-r--r--toolkit/mozapps/update/tests/unit_base_updater/marRMRFDirFileInUseStageFailureComplete_win.js72
-rw-r--r--toolkit/mozapps/update/tests/unit_base_updater/marRMRFDirFileInUseStageFailurePartial_win.js71
-rw-r--r--toolkit/mozapps/update/tests/unit_base_updater/marRMRFDirFileInUseSuccessComplete_win.js63
-rw-r--r--toolkit/mozapps/update/tests/unit_base_updater/marRMRFDirFileInUseSuccessPartial_win.js62
-rw-r--r--toolkit/mozapps/update/tests/unit_base_updater/marStageFailurePartial.js41
-rw-r--r--toolkit/mozapps/update/tests/unit_base_updater/marStageSuccessComplete.js132
-rw-r--r--toolkit/mozapps/update/tests/unit_base_updater/marStageSuccessPartial.js112
-rw-r--r--toolkit/mozapps/update/tests/unit_base_updater/marSuccessComplete.js96
-rw-r--r--toolkit/mozapps/update/tests/unit_base_updater/marSuccessPartial.js79
-rw-r--r--toolkit/mozapps/update/tests/unit_base_updater/marVersionDowngrade.js51
-rw-r--r--toolkit/mozapps/update/tests/unit_base_updater/marWrongChannel.js51
-rw-r--r--toolkit/mozapps/update/tests/unit_base_updater/xpcshell.ini136
-rw-r--r--toolkit/mozapps/update/tests/unit_service_updater/.eslintrc.js7
-rw-r--r--toolkit/mozapps/update/tests/unit_service_updater/bootstrapSvc.js35
-rw-r--r--toolkit/mozapps/update/tests/unit_service_updater/checkUpdaterSigSvc.js39
-rw-r--r--toolkit/mozapps/update/tests/unit_service_updater/head_update.js8
-rw-r--r--toolkit/mozapps/update/tests/unit_service_updater/invalidArgInstallDirPathTooLongFailureSvc.js47
-rw-r--r--toolkit/mozapps/update/tests/unit_service_updater/invalidArgInstallDirPathTraversalFailureSvc.js44
-rw-r--r--toolkit/mozapps/update/tests/unit_service_updater/invalidArgInstallWorkingDirPathNotSameFailureSvc_win.js38
-rw-r--r--toolkit/mozapps/update/tests/unit_service_updater/invalidArgPatchDirPathTraversalFailureSvc.js43
-rw-r--r--toolkit/mozapps/update/tests/unit_service_updater/invalidArgStageDirNotInInstallDirFailureSvc_win.js38
-rw-r--r--toolkit/mozapps/update/tests/unit_service_updater/invalidArgWorkingDirPathLocalUNCFailureSvc_win.js38
-rw-r--r--toolkit/mozapps/update/tests/unit_service_updater/invalidArgWorkingDirPathRelativeFailureSvc.js37
-rw-r--r--toolkit/mozapps/update/tests/unit_service_updater/marAppApplyDirLockedStageFailureSvc_win.js41
-rw-r--r--toolkit/mozapps/update/tests/unit_service_updater/marAppApplyUpdateAppBinInUseStageSuccessSvc_win.js83
-rw-r--r--toolkit/mozapps/update/tests/unit_service_updater/marAppApplyUpdateStageSuccessSvc.js82
-rw-r--r--toolkit/mozapps/update/tests/unit_service_updater/marAppApplyUpdateSuccessSvc.js65
-rw-r--r--toolkit/mozapps/update/tests/unit_service_updater/marAppInUseStageFailureCompleteSvc_win.js70
-rw-r--r--toolkit/mozapps/update/tests/unit_service_updater/marAppInUseSuccessCompleteSvc.js62
-rw-r--r--toolkit/mozapps/update/tests/unit_service_updater/marCallbackAppStageSuccessCompleteSvc_win.js61
-rw-r--r--toolkit/mozapps/update/tests/unit_service_updater/marCallbackAppStageSuccessPartialSvc_win.js61
-rw-r--r--toolkit/mozapps/update/tests/unit_service_updater/marCallbackAppSuccessCompleteSvc_win.js48
-rw-r--r--toolkit/mozapps/update/tests/unit_service_updater/marCallbackAppSuccessPartialSvc_win.js48
-rw-r--r--toolkit/mozapps/update/tests/unit_service_updater/marFailurePartialSvc.js47
-rw-r--r--toolkit/mozapps/update/tests/unit_service_updater/marFileInUseStageFailureCompleteSvc_win.js67
-rw-r--r--toolkit/mozapps/update/tests/unit_service_updater/marFileInUseStageFailurePartialSvc_win.js67
-rw-r--r--toolkit/mozapps/update/tests/unit_service_updater/marFileInUseSuccessCompleteSvc_win.js63
-rw-r--r--toolkit/mozapps/update/tests/unit_service_updater/marFileInUseSuccessPartialSvc_win.js63
-rw-r--r--toolkit/mozapps/update/tests/unit_service_updater/marFileLockedFailureCompleteSvc_win.js57
-rw-r--r--toolkit/mozapps/update/tests/unit_service_updater/marFileLockedFailurePartialSvc_win.js56
-rw-r--r--toolkit/mozapps/update/tests/unit_service_updater/marFileLockedStageFailureCompleteSvc_win.js71
-rw-r--r--toolkit/mozapps/update/tests/unit_service_updater/marFileLockedStageFailurePartialSvc_win.js70
-rw-r--r--toolkit/mozapps/update/tests/unit_service_updater/marRMRFDirFileInUseStageFailureCompleteSvc_win.js72
-rw-r--r--toolkit/mozapps/update/tests/unit_service_updater/marRMRFDirFileInUseStageFailurePartialSvc_win.js71
-rw-r--r--toolkit/mozapps/update/tests/unit_service_updater/marRMRFDirFileInUseSuccessCompleteSvc_win.js63
-rw-r--r--toolkit/mozapps/update/tests/unit_service_updater/marRMRFDirFileInUseSuccessPartialSvc_win.js62
-rw-r--r--toolkit/mozapps/update/tests/unit_service_updater/marStageFailurePartialSvc.js41
-rw-r--r--toolkit/mozapps/update/tests/unit_service_updater/marStageSuccessCompleteSvc.js132
-rw-r--r--toolkit/mozapps/update/tests/unit_service_updater/marStageSuccessPartialSvc.js112
-rw-r--r--toolkit/mozapps/update/tests/unit_service_updater/marSuccessCompleteSvc.js96
-rw-r--r--toolkit/mozapps/update/tests/unit_service_updater/marSuccessPartialSvc.js79
-rw-r--r--toolkit/mozapps/update/tests/unit_service_updater/xpcshell.ini156
-rw-r--r--toolkit/mozapps/update/updater/Launchd.plist10
-rw-r--r--toolkit/mozapps/update/updater/Makefile.in29
-rw-r--r--toolkit/mozapps/update/updater/archivereader.cpp324
-rw-r--r--toolkit/mozapps/update/updater/archivereader.h41
-rw-r--r--toolkit/mozapps/update/updater/automounter_gonk.cpp251
-rw-r--r--toolkit/mozapps/update/updater/automounter_gonk.h48
-rw-r--r--toolkit/mozapps/update/updater/bspatch.cpp187
-rw-r--r--toolkit/mozapps/update/updater/bspatch.h93
-rw-r--r--toolkit/mozapps/update/updater/dep1.derbin0 -> 671 bytes
-rw-r--r--toolkit/mozapps/update/updater/dep2.derbin0 -> 671 bytes
-rw-r--r--toolkit/mozapps/update/updater/gen_cert_header.py22
-rw-r--r--toolkit/mozapps/update/updater/launchchild_osx.mm384
-rw-r--r--toolkit/mozapps/update/updater/loaddlls.cpp103
-rw-r--r--toolkit/mozapps/update/updater/macbuild/Contents/Info.plist.in39
-rw-r--r--toolkit/mozapps/update/updater/macbuild/Contents/PkgInfo1
-rw-r--r--toolkit/mozapps/update/updater/macbuild/Contents/Resources/English.lproj/InfoPlist.strings.in7
-rw-r--r--toolkit/mozapps/update/updater/macbuild/Contents/Resources/English.lproj/MainMenu.nib/classes.nib19
-rw-r--r--toolkit/mozapps/update/updater/macbuild/Contents/Resources/English.lproj/MainMenu.nib/info.nib22
-rw-r--r--toolkit/mozapps/update/updater/macbuild/Contents/Resources/English.lproj/MainMenu.nib/keyedobjects.nibbin0 -> 5567 bytes
-rw-r--r--toolkit/mozapps/update/updater/macbuild/Contents/Resources/updater.icnsbin0 -> 55969 bytes
-rw-r--r--toolkit/mozapps/update/updater/module.ver1
-rw-r--r--toolkit/mozapps/update/updater/moz.build62
-rw-r--r--toolkit/mozapps/update/updater/nightly_aurora_level3_primary.derbin0 -> 679 bytes
-rw-r--r--toolkit/mozapps/update/updater/nightly_aurora_level3_secondary.derbin0 -> 679 bytes
-rw-r--r--toolkit/mozapps/update/updater/progressui.h40
-rw-r--r--toolkit/mozapps/update/updater/progressui_gonk.cpp53
-rw-r--r--toolkit/mozapps/update/updater/progressui_gtk.cpp132
-rw-r--r--toolkit/mozapps/update/updater/progressui_null.cpp25
-rw-r--r--toolkit/mozapps/update/updater/progressui_osx.mm144
-rw-r--r--toolkit/mozapps/update/updater/progressui_win.cpp319
-rw-r--r--toolkit/mozapps/update/updater/release_primary.derbin0 -> 709 bytes
-rw-r--r--toolkit/mozapps/update/updater/release_secondary.derbin0 -> 713 bytes
-rw-r--r--toolkit/mozapps/update/updater/resource.h29
-rw-r--r--toolkit/mozapps/update/updater/updater-common.build136
-rw-r--r--toolkit/mozapps/update/updater/updater-xpcshell/Makefile.in41
-rw-r--r--toolkit/mozapps/update/updater/updater-xpcshell/moz.build14
-rw-r--r--toolkit/mozapps/update/updater/updater.cpp4454
-rw-r--r--toolkit/mozapps/update/updater/updater.exe.comctl32.manifest38
-rw-r--r--toolkit/mozapps/update/updater/updater.exe.manifest26
-rw-r--r--toolkit/mozapps/update/updater/updater.icobin0 -> 92854 bytes
-rw-r--r--toolkit/mozapps/update/updater/updater.pngbin0 -> 4030 bytes
-rw-r--r--toolkit/mozapps/update/updater/updater.rc137
-rw-r--r--toolkit/mozapps/update/updater/win_dirent.cpp78
-rw-r--r--toolkit/mozapps/update/updater/xpcshellCertificate.derbin0 -> 677 bytes
261 files changed, 35655 insertions, 0 deletions
diff --git a/toolkit/mozapps/update/UpdateTelemetry.jsm b/toolkit/mozapps/update/UpdateTelemetry.jsm
new file mode 100644
index 000000000..d64085143
--- /dev/null
+++ b/toolkit/mozapps/update/UpdateTelemetry.jsm
@@ -0,0 +1,488 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+this.EXPORTED_SYMBOLS = [
+ "AUSTLMY"
+];
+
+const {classes: Cc, interfaces: Ci, results: Cr, utils: Cu} = Components;
+
+Cu.import("resource://gre/modules/Services.jsm", this);
+
+this.AUSTLMY = {
+ // Telemetry for the application update background update check occurs when
+ // the background update timer fires after the update interval which is
+ // determined by the app.update.interval preference and its telemetry
+ // histogram IDs have the suffix '_NOTIFY'.
+ // Telemetry for the externally initiated background update check occurs when
+ // a call is made to |checkForBackgroundUpdates| which is typically initiated
+ // by an application when it has determined that the application should have
+ // received an update. This has separate telemetry so it is possible to
+ // analyze using the telemetry data systems that have not been updating when
+ // they should have.
+
+ // The update check was performed by the call to checkForBackgroundUpdates in
+ // nsUpdateService.js.
+ EXTERNAL: "EXTERNAL",
+ // The update check was performed by the call to notify in nsUpdateService.js.
+ NOTIFY: "NOTIFY",
+
+ /**
+ * Values for the UPDATE_CHECK_CODE_NOTIFY and UPDATE_CHECK_CODE_EXTERNAL
+ * Telemetry histograms.
+ */
+ // No update found (no notification)
+ CHK_NO_UPDATE_FOUND: 0,
+ // Update will be downloaded in the background (background download)
+ CHK_DOWNLOAD_UPDATE: 1,
+ // Showing prompt due to the update.xml specifying showPrompt
+ // (update notification)
+ CHK_SHOWPROMPT_SNIPPET: 2,
+ // Showing prompt due to preference (update notification)
+ CHK_SHOWPROMPT_PREF: 3,
+ // Already has an active update in progress (no notification)
+ CHK_HAS_ACTIVEUPDATE: 8,
+ // A background download is already in progress (no notification)
+ CHK_IS_DOWNLOADING: 9,
+ // An update is already staged (no notification)
+ CHK_IS_STAGED: 10,
+ // An update is already downloaded (no notification)
+ CHK_IS_DOWNLOADED: 11,
+ // Background checks disabled by preference (no notification)
+ CHK_PREF_DISABLED: 12,
+ // Update checks disabled by admin locked preference (no notification)
+ CHK_ADMIN_DISABLED: 13,
+ // Unable to check for updates per hasUpdateMutex() (no notification)
+ CHK_NO_MUTEX: 14,
+ // Unable to check for updates per gCanCheckForUpdates (no notification). This
+ // should be covered by other codes and is recorded just in case.
+ CHK_UNABLE_TO_CHECK: 15,
+ // Background checks disabled for the current session (no notification)
+ CHK_DISABLED_FOR_SESSION: 16,
+ // Unable to perform a background check while offline (no notification)
+ CHK_OFFLINE: 17,
+ // Note: codes 18 - 21 were removed along with the certificate checking code.
+ // General update check failure and threshold reached
+ // (check failure notification)
+ CHK_GENERAL_ERROR_PROMPT: 22,
+ // General update check failure and threshold not reached (no notification)
+ CHK_GENERAL_ERROR_SILENT: 23,
+ // No compatible update found though there were updates (no notification)
+ CHK_NO_COMPAT_UPDATE_FOUND: 24,
+ // Update found for a previous version (no notification)
+ CHK_UPDATE_PREVIOUS_VERSION: 25,
+ // Update found for a version with the never preference set (no notification)
+ CHK_UPDATE_NEVER_PREF: 26,
+ // Update found without a type attribute (no notification)
+ CHK_UPDATE_INVALID_TYPE: 27,
+ // The system is no longer supported (system unsupported notification)
+ CHK_UNSUPPORTED: 28,
+ // Unable to apply updates (manual install to update notification)
+ CHK_UNABLE_TO_APPLY: 29,
+ // Unable to check for updates due to no OS version (no notification)
+ CHK_NO_OS_VERSION: 30,
+ // Unable to check for updates due to no OS ABI (no notification)
+ CHK_NO_OS_ABI: 31,
+ // Invalid url for app.update.url default preference (no notification)
+ CHK_INVALID_DEFAULT_URL: 32,
+ // Update elevation failures or cancelations threshold reached for this
+ // version, OSX only (no notification)
+ CHK_ELEVATION_DISABLED_FOR_VERSION: 35,
+ // User opted out of elevated updates for the available update version, OSX
+ // only (no notification)
+ CHK_ELEVATION_OPTOUT_FOR_VERSION: 36,
+
+ /**
+ * Submit a telemetry ping for the update check result code or a telemetry
+ * ping for a count type histogram count when no update was found. The no
+ * update found ping is separate since it is the typical result, is less
+ * interesting than the other result codes, and it is easier to analyze the
+ * other codes without including it.
+ *
+ * @param aSuffix
+ * The histogram id suffix for histogram IDs:
+ * UPDATE_CHECK_CODE_EXTERNAL
+ * UPDATE_CHECK_CODE_NOTIFY
+ * UPDATE_CHECK_NO_UPDATE_EXTERNAL
+ * UPDATE_CHECK_NO_UPDATE_NOTIFY
+ * @param aCode
+ * An integer value as defined by the values that start with CHK_ in
+ * the above section.
+ */
+ pingCheckCode: function UT_pingCheckCode(aSuffix, aCode) {
+ try {
+ if (aCode == this.CHK_NO_UPDATE_FOUND) {
+ let id = "UPDATE_CHECK_NO_UPDATE_" + aSuffix;
+ // count type histogram
+ Services.telemetry.getHistogramById(id).add();
+ } else {
+ let id = "UPDATE_CHECK_CODE_" + aSuffix;
+ // enumerated type histogram
+ Services.telemetry.getHistogramById(id).add(aCode);
+ }
+ } catch (e) {
+ Cu.reportError(e);
+ }
+ },
+
+ /**
+ * Submit a telemetry ping for a failed update check's unhandled error code
+ * when the pingCheckCode is CHK_GENERAL_ERROR_SILENT. The histogram is a
+ * keyed count type with key names that are prefixed with 'AUS_CHECK_EX_ERR_'.
+ *
+ * @param aSuffix
+ * The histogram id suffix for histogram IDs:
+ * UPDATE_CHK_EXTENDED_ERROR_EXTERNAL
+ * UPDATE_CHK_EXTENDED_ERROR_NOTIFY
+ * @param aCode
+ * The extended error value return by a failed update check.
+ */
+ pingCheckExError: function UT_pingCheckExError(aSuffix, aCode) {
+ try {
+ let id = "UPDATE_CHECK_EXTENDED_ERROR_" + aSuffix;
+ let val = "AUS_CHECK_EX_ERR_" + aCode;
+ // keyed count type histogram
+ Services.telemetry.getKeyedHistogramById(id).add(val);
+ } catch (e) {
+ Cu.reportError(e);
+ }
+ },
+
+ // The state code and if present the status error code were read on startup.
+ STARTUP: "STARTUP",
+ // The state code and status error code if present were read after staging.
+ STAGE: "STAGE",
+
+ // Patch type Complete
+ PATCH_COMPLETE: "COMPLETE",
+ // Patch type partial
+ PATCH_PARTIAL: "PARTIAL",
+ // Patch type unknown
+ PATCH_UNKNOWN: "UNKNOWN",
+
+ /**
+ * Values for the UPDATE_DOWNLOAD_CODE_COMPLETE and
+ * UPDATE_DOWNLOAD_CODE_PARTIAL Telemetry histograms.
+ */
+ DWNLD_SUCCESS: 0,
+ DWNLD_RETRY_OFFLINE: 1,
+ DWNLD_RETRY_NET_TIMEOUT: 2,
+ DWNLD_RETRY_CONNECTION_REFUSED: 3,
+ DWNLD_RETRY_NET_RESET: 4,
+ DWNLD_ERR_NO_UPDATE: 5,
+ DWNLD_ERR_NO_UPDATE_PATCH: 6,
+ DWNLD_ERR_NO_PATCH_FILE: 7,
+ DWNLD_ERR_PATCH_SIZE_LARGER: 8,
+ DWNLD_ERR_PATCH_SIZE_NOT_EQUAL: 9,
+ DWNLD_ERR_BINDING_ABORTED: 10,
+ DWNLD_ERR_ABORT: 11,
+ DWNLD_ERR_DOCUMENT_NOT_CACHED: 12,
+ DWNLD_ERR_VERIFY_NO_REQUEST: 13,
+ DWNLD_ERR_VERIFY_PATCH_SIZE_NOT_EQUAL: 14,
+ DWNLD_ERR_VERIFY_NO_HASH_MATCH: 15,
+
+ /**
+ * Submit a telemetry ping for the update download result code.
+ *
+ * @param aIsComplete
+ * If true the histogram is for a patch type complete, if false the
+ * histogram is for a patch type partial, and when undefined the
+ * histogram is for an unknown patch type. This is used to determine
+ * the histogram ID out of the following histogram IDs:
+ * UPDATE_DOWNLOAD_CODE_COMPLETE
+ * UPDATE_DOWNLOAD_CODE_PARTIAL
+ * @param aCode
+ * An integer value as defined by the values that start with DWNLD_ in
+ * the above section.
+ */
+ pingDownloadCode: function UT_pingDownloadCode(aIsComplete, aCode) {
+ let patchType = this.PATCH_UNKNOWN;
+ if (aIsComplete === true) {
+ patchType = this.PATCH_COMPLETE;
+ } else if (aIsComplete === false) {
+ patchType = this.PATCH_PARTIAL;
+ }
+ try {
+ let id = "UPDATE_DOWNLOAD_CODE_" + patchType;
+ // enumerated type histogram
+ Services.telemetry.getHistogramById(id).add(aCode);
+ } catch (e) {
+ Cu.reportError(e);
+ }
+ },
+
+ /**
+ * Submit a telemetry ping for the update status state code.
+ *
+ * @param aSuffix
+ * The histogram id suffix for histogram IDs:
+ * UPDATE_STATE_CODE_COMPLETE_STARTUP
+ * UPDATE_STATE_CODE_PARTIAL_STARTUP
+ * UPDATE_STATE_CODE_UNKNOWN_STARTUP
+ * UPDATE_STATE_CODE_COMPLETE_STAGE
+ * UPDATE_STATE_CODE_PARTIAL_STAGE
+ * UPDATE_STATE_CODE_UNKNOWN_STAGE
+ * @param aCode
+ * An integer value as defined by the values that start with STATE_ in
+ * the above section for the update state from the update.status file.
+ */
+ pingStateCode: function UT_pingStateCode(aSuffix, aCode) {
+ try {
+ let id = "UPDATE_STATE_CODE_" + aSuffix;
+ // enumerated type histogram
+ Services.telemetry.getHistogramById(id).add(aCode);
+ } catch (e) {
+ Cu.reportError(e);
+ }
+ },
+
+ /**
+ * Submit a telemetry ping for the update status error code. This does not
+ * submit a success value which can be determined from the state code.
+ *
+ * @param aSuffix
+ * The histogram id suffix for histogram IDs:
+ * UPDATE_STATUS_ERROR_CODE_COMPLETE_STARTUP
+ * UPDATE_STATUS_ERROR_CODE_PARTIAL_STARTUP
+ * UPDATE_STATUS_ERROR_CODE_UNKNOWN_STARTUP
+ * UPDATE_STATUS_ERROR_CODE_COMPLETE_STAGE
+ * UPDATE_STATUS_ERROR_CODE_PARTIAL_STAGE
+ * UPDATE_STATUS_ERROR_CODE_UNKNOWN_STAGE
+ * @param aCode
+ * An integer value for the error code from the update.status file.
+ */
+ pingStatusErrorCode: function UT_pingStatusErrorCode(aSuffix, aCode) {
+ try {
+ let id = "UPDATE_STATUS_ERROR_CODE_" + aSuffix;
+ // enumerated type histogram
+ Services.telemetry.getHistogramById(id).add(aCode);
+ } catch (e) {
+ Cu.reportError(e);
+ }
+ },
+
+ /**
+ * Submit the interval in days since the last notification for this background
+ * update check or a boolean if the last notification is in the future.
+ *
+ * @param aSuffix
+ * The histogram id suffix for histogram IDs:
+ * UPDATE_INVALID_LASTUPDATETIME_EXTERNAL
+ * UPDATE_INVALID_LASTUPDATETIME_NOTIFY
+ * UPDATE_LAST_NOTIFY_INTERVAL_DAYS_EXTERNAL
+ * UPDATE_LAST_NOTIFY_INTERVAL_DAYS_NOTIFY
+ */
+ pingLastUpdateTime: function UT_pingLastUpdateTime(aSuffix) {
+ const PREF_APP_UPDATE_LASTUPDATETIME = "app.update.lastUpdateTime.background-update-timer";
+ if (Services.prefs.prefHasUserValue(PREF_APP_UPDATE_LASTUPDATETIME)) {
+ let lastUpdateTimeSeconds = Services.prefs.getIntPref(PREF_APP_UPDATE_LASTUPDATETIME);
+ if (lastUpdateTimeSeconds) {
+ let currentTimeSeconds = Math.round(Date.now() / 1000);
+ if (lastUpdateTimeSeconds > currentTimeSeconds) {
+ try {
+ let id = "UPDATE_INVALID_LASTUPDATETIME_" + aSuffix;
+ // count type histogram
+ Services.telemetry.getHistogramById(id).add();
+ } catch (e) {
+ Cu.reportError(e);
+ }
+ } else {
+ let intervalDays = (currentTimeSeconds - lastUpdateTimeSeconds) /
+ (60 * 60 * 24);
+ try {
+ let id = "UPDATE_LAST_NOTIFY_INTERVAL_DAYS_" + aSuffix;
+ // exponential type histogram
+ Services.telemetry.getHistogramById(id).add(intervalDays);
+ } catch (e) {
+ Cu.reportError(e);
+ }
+ }
+ }
+ }
+ },
+
+ /**
+ * Submit a telemetry ping for the last page displayed by the update wizard.
+ *
+ * @param aPageID
+ * The page id for the last page displayed.
+ */
+ pingWizLastPageCode: function UT_pingWizLastPageCode(aPageID) {
+ let pageMap = { invalid: 0,
+ dummy: 1,
+ checking: 2,
+ pluginupdatesfound: 3,
+ noupdatesfound: 4,
+ manualUpdate: 5,
+ unsupported: 6,
+ updatesfoundbasic: 8,
+ updatesfoundbillboard: 9,
+ downloading: 12,
+ errors: 13,
+ errorextra: 14,
+ errorpatching: 15,
+ finished: 16,
+ finishedBackground: 17,
+ installed: 18 };
+ try {
+ let id = "UPDATE_WIZ_LAST_PAGE_CODE";
+ // enumerated type histogram
+ Services.telemetry.getHistogramById(id).add(pageMap[aPageID] ||
+ pageMap.invalid);
+ } catch (e) {
+ Cu.reportError(e);
+ }
+ },
+
+ /**
+ * Submit a telemetry ping for a boolean type histogram that indicates if the
+ * service is installed and a telemetry ping for a boolean type histogram that
+ * indicates if the service was at some point installed and is now
+ * uninstalled.
+ *
+ * @param aSuffix
+ * The histogram id suffix for histogram IDs:
+ * UPDATE_SERVICE_INSTALLED_EXTERNAL
+ * UPDATE_SERVICE_INSTALLED_NOTIFY
+ * UPDATE_SERVICE_MANUALLY_UNINSTALLED_EXTERNAL
+ * UPDATE_SERVICE_MANUALLY_UNINSTALLED_NOTIFY
+ * @param aInstalled
+ * Whether the service is installed.
+ */
+ pingServiceInstallStatus: function UT_PSIS(aSuffix, aInstalled) {
+ // Report the error but don't throw since it is more important to
+ // successfully update than to throw.
+ if (!("@mozilla.org/windows-registry-key;1" in Cc)) {
+ Cu.reportError(Cr.NS_ERROR_NOT_AVAILABLE);
+ return;
+ }
+
+ try {
+ let id = "UPDATE_SERVICE_INSTALLED_" + aSuffix;
+ // boolean type histogram
+ Services.telemetry.getHistogramById(id).add(aInstalled);
+ } catch (e) {
+ Cu.reportError(e);
+ }
+
+ let attempted = 0;
+ try {
+ let wrk = Cc["@mozilla.org/windows-registry-key;1"].
+ createInstance(Ci.nsIWindowsRegKey);
+ wrk.open(wrk.ROOT_KEY_LOCAL_MACHINE,
+ "SOFTWARE\\Mozilla\\MaintenanceService",
+ wrk.ACCESS_READ | wrk.WOW64_64);
+ // Was the service at some point installed, but is now uninstalled?
+ attempted = wrk.readIntValue("Attempted");
+ wrk.close();
+ } catch (e) {
+ // Since this will throw if the registry key doesn't exist (e.g. the
+ // service has never been installed) don't report an error.
+ }
+
+ try {
+ let id = "UPDATE_SERVICE_MANUALLY_UNINSTALLED_" + aSuffix;
+ if (!aInstalled && attempted) {
+ // count type histogram
+ Services.telemetry.getHistogramById(id).add();
+ }
+ } catch (e) {
+ Cu.reportError(e);
+ }
+ },
+
+ /**
+ * Submit a telemetry ping for a count type histogram when the expected value
+ * does not equal the boolean value of a pref or if the pref isn't present
+ * when the expected value does not equal default value. This lessens the
+ * amount of data submitted to telemetry.
+ *
+ * @param aID
+ * The histogram ID to report to.
+ * @param aPref
+ * The preference to check.
+ * @param aDefault
+ * The default value when the preference isn't present.
+ * @param aExpected (optional)
+ * If specified and the value is the same as the value that will be
+ * added the value won't be added to telemetry.
+ */
+ pingBoolPref: function UT_pingBoolPref(aID, aPref, aDefault, aExpected) {
+ try {
+ let val = aDefault;
+ if (Services.prefs.getPrefType(aPref) != Ci.nsIPrefBranch.PREF_INVALID) {
+ val = Services.prefs.getBoolPref(aPref);
+ }
+ if (val != aExpected) {
+ // count type histogram
+ Services.telemetry.getHistogramById(aID).add();
+ }
+ } catch (e) {
+ Cu.reportError(e);
+ }
+ },
+
+ /**
+ * Submit a telemetry ping for a histogram with the integer value of a
+ * preference when it is not the expected value or the default value when it
+ * is not the expected value. This lessens the amount of data submitted to
+ * telemetry.
+ *
+ * @param aID
+ * The histogram ID to report to.
+ * @param aPref
+ * The preference to check.
+ * @param aDefault
+ * The default value when the pref is not set.
+ * @param aExpected (optional)
+ * If specified and the value is the same as the value that will be
+ * added the value won't be added to telemetry.
+ */
+ pingIntPref: function UT_pingIntPref(aID, aPref, aDefault, aExpected) {
+ try {
+ let val = aDefault;
+ if (Services.prefs.getPrefType(aPref) != Ci.nsIPrefBranch.PREF_INVALID) {
+ val = Services.prefs.getIntPref(aPref);
+ }
+ if (aExpected === undefined || val != aExpected) {
+ // enumerated or exponential type histogram
+ Services.telemetry.getHistogramById(aID).add(val);
+ }
+ } catch (e) {
+ Cu.reportError(e);
+ }
+ },
+
+ /**
+ * Submit a telemetry ping for all histogram types that take a single
+ * parameter to the telemetry add function and the count type histogram when
+ * the aExpected parameter is specified. If the aExpected parameter is
+ * specified and it equals the value specified by the aValue
+ * parameter the telemetry submission will be skipped.
+ *
+ * @param aID
+ * The histogram ID to report to.
+ * @param aValue
+ * The value to add when aExpected is not defined or the value to
+ * check if it is equal to when aExpected is defined.
+ * @param aExpected (optional)
+ * If specified and the value is the same as the value specified by
+ * aValue parameter the submission will be skipped.
+ */
+ pingGeneric: function UT_pingGeneric(aID, aValue, aExpected) {
+ try {
+ if (aExpected === undefined) {
+ Services.telemetry.getHistogramById(aID).add(aValue);
+ } else if (aValue != aExpected) {
+ // count type histogram
+ Services.telemetry.getHistogramById(aID).add();
+ }
+ } catch (e) {
+ Cu.reportError(e);
+ }
+ }
+};
+Object.freeze(AUSTLMY);
diff --git a/toolkit/mozapps/update/common-standalone/moz.build b/toolkit/mozapps/update/common-standalone/moz.build
new file mode 100644
index 000000000..dbc787b43
--- /dev/null
+++ b/toolkit/mozapps/update/common-standalone/moz.build
@@ -0,0 +1,12 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+Library('updatecommon-standalone')
+
+srcdir = '../common'
+
+include('../common/sources.mozbuild')
+
+if CONFIG['OS_ARCH'] == 'WINNT':
+ USE_STATIC_LIBS = True
diff --git a/toolkit/mozapps/update/common/certificatecheck.cpp b/toolkit/mozapps/update/common/certificatecheck.cpp
new file mode 100644
index 000000000..afb2e7ee3
--- /dev/null
+++ b/toolkit/mozapps/update/common/certificatecheck.cpp
@@ -0,0 +1,250 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <windows.h>
+#include <softpub.h>
+#include <wintrust.h>
+
+#include "certificatecheck.h"
+#include "updatecommon.h"
+
+static const int ENCODING = X509_ASN_ENCODING | PKCS_7_ASN_ENCODING;
+
+/**
+ * Checks to see if a file stored at filePath matches the specified info.
+ *
+ * @param filePath The PE file path to check
+ * @param infoToMatch The acceptable information to match
+ * @return ERROR_SUCCESS if successful, ERROR_NOT_FOUND if the info
+ * does not match, or the last error otherwise.
+ */
+DWORD
+CheckCertificateForPEFile(LPCWSTR filePath,
+ CertificateCheckInfo &infoToMatch)
+{
+ HCERTSTORE certStore = nullptr;
+ HCRYPTMSG cryptMsg = nullptr;
+ PCCERT_CONTEXT certContext = nullptr;
+ PCMSG_SIGNER_INFO signerInfo = nullptr;
+ DWORD lastError = ERROR_SUCCESS;
+
+ // Get the HCERTSTORE and HCRYPTMSG from the signed file.
+ DWORD encoding, contentType, formatType;
+ BOOL result = CryptQueryObject(CERT_QUERY_OBJECT_FILE,
+ filePath,
+ CERT_QUERY_CONTENT_FLAG_PKCS7_SIGNED_EMBED,
+ CERT_QUERY_CONTENT_FLAG_ALL,
+ 0, &encoding, &contentType,
+ &formatType, &certStore, &cryptMsg, nullptr);
+ if (!result) {
+ lastError = GetLastError();
+ LOG_WARN(("CryptQueryObject failed. (%d)", lastError));
+ goto cleanup;
+ }
+
+ // Pass in nullptr to get the needed signer information size.
+ DWORD signerInfoSize;
+ result = CryptMsgGetParam(cryptMsg, CMSG_SIGNER_INFO_PARAM, 0,
+ nullptr, &signerInfoSize);
+ if (!result) {
+ lastError = GetLastError();
+ LOG_WARN(("CryptMsgGetParam failed. (%d)", lastError));
+ goto cleanup;
+ }
+
+ // Allocate the needed size for the signer information.
+ signerInfo = (PCMSG_SIGNER_INFO)LocalAlloc(LPTR, signerInfoSize);
+ if (!signerInfo) {
+ lastError = GetLastError();
+ LOG_WARN(("Unable to allocate memory for Signer Info. (%d)", lastError));
+ goto cleanup;
+ }
+
+ // Get the signer information (PCMSG_SIGNER_INFO).
+ // In particular we want the issuer and serial number.
+ result = CryptMsgGetParam(cryptMsg, CMSG_SIGNER_INFO_PARAM, 0,
+ (PVOID)signerInfo, &signerInfoSize);
+ if (!result) {
+ lastError = GetLastError();
+ LOG_WARN(("CryptMsgGetParam failed. (%d)", lastError));
+ goto cleanup;
+ }
+
+ // Search for the signer certificate in the certificate store.
+ CERT_INFO certInfo;
+ certInfo.Issuer = signerInfo->Issuer;
+ certInfo.SerialNumber = signerInfo->SerialNumber;
+ certContext = CertFindCertificateInStore(certStore, ENCODING, 0,
+ CERT_FIND_SUBJECT_CERT,
+ (PVOID)&certInfo, nullptr);
+ if (!certContext) {
+ lastError = GetLastError();
+ LOG_WARN(("CertFindCertificateInStore failed. (%d)", lastError));
+ goto cleanup;
+ }
+
+ if (!DoCertificateAttributesMatch(certContext, infoToMatch)) {
+ lastError = ERROR_NOT_FOUND;
+ LOG_WARN(("Certificate did not match issuer or name. (%d)", lastError));
+ goto cleanup;
+ }
+
+cleanup:
+ if (signerInfo) {
+ LocalFree(signerInfo);
+ }
+ if (certContext) {
+ CertFreeCertificateContext(certContext);
+ }
+ if (certStore) {
+ CertCloseStore(certStore, 0);
+ }
+ if (cryptMsg) {
+ CryptMsgClose(cryptMsg);
+ }
+ return lastError;
+}
+
+/**
+ * Checks to see if a file stored at filePath matches the specified info.
+ *
+ * @param certContext The certificate context of the file
+ * @param infoToMatch The acceptable information to match
+ * @return FALSE if the info does not match or if any error occurs in the check
+ */
+BOOL
+DoCertificateAttributesMatch(PCCERT_CONTEXT certContext,
+ CertificateCheckInfo &infoToMatch)
+{
+ DWORD dwData;
+ LPWSTR szName = nullptr;
+
+ if (infoToMatch.issuer) {
+ // Pass in nullptr to get the needed size of the issuer buffer.
+ dwData = CertGetNameString(certContext,
+ CERT_NAME_SIMPLE_DISPLAY_TYPE,
+ CERT_NAME_ISSUER_FLAG, nullptr,
+ nullptr, 0);
+
+ if (!dwData) {
+ LOG_WARN(("CertGetNameString failed. (%d)", GetLastError()));
+ return FALSE;
+ }
+
+ // Allocate memory for Issuer name buffer.
+ szName = (LPWSTR)LocalAlloc(LPTR, dwData * sizeof(WCHAR));
+ if (!szName) {
+ LOG_WARN(("Unable to allocate memory for issuer name. (%d)",
+ GetLastError()));
+ return FALSE;
+ }
+
+ // Get Issuer name.
+ if (!CertGetNameStringW(certContext, CERT_NAME_SIMPLE_DISPLAY_TYPE,
+ CERT_NAME_ISSUER_FLAG, nullptr, szName, dwData)) {
+ LOG_WARN(("CertGetNameString failed. (%d)", GetLastError()));
+ LocalFree(szName);
+ return FALSE;
+ }
+
+ // If the issuer does not match, return a failure.
+ if (!infoToMatch.issuer ||
+ wcscmp(szName, infoToMatch.issuer)) {
+ LocalFree(szName);
+ return FALSE;
+ }
+
+ LocalFree(szName);
+ szName = nullptr;
+ }
+
+ if (infoToMatch.name) {
+ // Pass in nullptr to get the needed size of the name buffer.
+ dwData = CertGetNameString(certContext, CERT_NAME_SIMPLE_DISPLAY_TYPE,
+ 0, nullptr, nullptr, 0);
+ if (!dwData) {
+ LOG_WARN(("CertGetNameString failed. (%d)", GetLastError()));
+ return FALSE;
+ }
+
+ // Allocate memory for the name buffer.
+ szName = (LPWSTR)LocalAlloc(LPTR, dwData * sizeof(WCHAR));
+ if (!szName) {
+ LOG_WARN(("Unable to allocate memory for subject name. (%d)",
+ GetLastError()));
+ return FALSE;
+ }
+
+ // Obtain the name.
+ if (!(CertGetNameStringW(certContext, CERT_NAME_SIMPLE_DISPLAY_TYPE, 0,
+ nullptr, szName, dwData))) {
+ LOG_WARN(("CertGetNameString failed. (%d)", GetLastError()));
+ LocalFree(szName);
+ return FALSE;
+ }
+
+ // If the issuer does not match, return a failure.
+ if (!infoToMatch.name ||
+ wcscmp(szName, infoToMatch.name)) {
+ LocalFree(szName);
+ return FALSE;
+ }
+
+ // We have a match!
+ LocalFree(szName);
+ }
+
+ // If there were any errors we would have aborted by now.
+ return TRUE;
+}
+
+/**
+ * Verifies the trust of the specified file path.
+ *
+ * @param filePath The file path to check.
+ * @return ERROR_SUCCESS if successful, or the last error code otherwise.
+ */
+DWORD
+VerifyCertificateTrustForFile(LPCWSTR filePath)
+{
+ // Setup the file to check.
+ WINTRUST_FILE_INFO fileToCheck;
+ ZeroMemory(&fileToCheck, sizeof(fileToCheck));
+ fileToCheck.cbStruct = sizeof(WINTRUST_FILE_INFO);
+ fileToCheck.pcwszFilePath = filePath;
+
+ // Setup what to check, we want to check it is signed and trusted.
+ WINTRUST_DATA trustData;
+ ZeroMemory(&trustData, sizeof(trustData));
+ trustData.cbStruct = sizeof(trustData);
+ trustData.pPolicyCallbackData = nullptr;
+ trustData.pSIPClientData = nullptr;
+ trustData.dwUIChoice = WTD_UI_NONE;
+ trustData.fdwRevocationChecks = WTD_REVOKE_NONE;
+ trustData.dwUnionChoice = WTD_CHOICE_FILE;
+ trustData.dwStateAction = 0;
+ trustData.hWVTStateData = nullptr;
+ trustData.pwszURLReference = nullptr;
+ // no UI
+ trustData.dwUIContext = 0;
+ trustData.pFile = &fileToCheck;
+
+ GUID policyGUID = WINTRUST_ACTION_GENERIC_VERIFY_V2;
+ // Check if the file is signed by something that is trusted.
+ LONG ret = WinVerifyTrust(nullptr, &policyGUID, &trustData);
+ if (ERROR_SUCCESS == ret) {
+ // The hash that represents the subject is trusted and there were no
+ // verification errors. No publisher nor time stamp chain errors.
+ LOG(("The file \"%ls\" is signed and the signature was verified.",
+ filePath));
+ return ERROR_SUCCESS;
+ }
+
+ DWORD lastError = GetLastError();
+ LOG_WARN(("There was an error validating trust of the certificate for file"
+ " \"%ls\". Returned: %d. (%d)", filePath, ret, lastError));
+ return ret;
+}
diff --git a/toolkit/mozapps/update/common/certificatecheck.h b/toolkit/mozapps/update/common/certificatecheck.h
new file mode 100644
index 000000000..43a7c85b6
--- /dev/null
+++ b/toolkit/mozapps/update/common/certificatecheck.h
@@ -0,0 +1,22 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef _CERTIFICATECHECK_H_
+#define _CERTIFICATECHECK_H_
+
+#include <wincrypt.h>
+
+struct CertificateCheckInfo
+{
+ LPCWSTR name;
+ LPCWSTR issuer;
+};
+
+BOOL DoCertificateAttributesMatch(PCCERT_CONTEXT pCertContext,
+ CertificateCheckInfo &infoToMatch);
+DWORD VerifyCertificateTrustForFile(LPCWSTR filePath);
+DWORD CheckCertificateForPEFile(LPCWSTR filePath,
+ CertificateCheckInfo &infoToMatch);
+
+#endif
diff --git a/toolkit/mozapps/update/common/errors.h b/toolkit/mozapps/update/common/errors.h
new file mode 100644
index 000000000..aac029175
--- /dev/null
+++ b/toolkit/mozapps/update/common/errors.h
@@ -0,0 +1,110 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef Errors_h__
+#define Errors_h__
+
+#define OK 0
+
+// Error codes that are no longer used should not be used again unless they
+// aren't used in client code (e.g. nsUpdateService.js, updates.js,
+// UpdatePrompt.js, etc.).
+
+#define MAR_ERROR_EMPTY_ACTION_LIST 1
+#define LOADSOURCE_ERROR_WRONG_SIZE 2
+
+// Error codes 3-16 are for general update problems.
+#define USAGE_ERROR 3
+#define CRC_ERROR 4
+#define PARSE_ERROR 5
+#define READ_ERROR 6
+#define WRITE_ERROR 7
+// #define UNEXPECTED_ERROR 8 // Replaced with errors 38-42
+#define ELEVATION_CANCELED 9
+#define READ_STRINGS_MEM_ERROR 10
+#define ARCHIVE_READER_MEM_ERROR 11
+#define BSPATCH_MEM_ERROR 12
+#define UPDATER_MEM_ERROR 13
+#define UPDATER_QUOTED_PATH_MEM_ERROR 14
+#define BAD_ACTION_ERROR 15
+#define STRING_CONVERSION_ERROR 16
+
+// Error codes 17-23 are related to security tasks for MAR
+// signing and MAR protection.
+#define CERT_LOAD_ERROR 17
+#define CERT_HANDLING_ERROR 18
+#define CERT_VERIFY_ERROR 19
+#define ARCHIVE_NOT_OPEN 20
+#define COULD_NOT_READ_PRODUCT_INFO_BLOCK_ERROR 21
+#define MAR_CHANNEL_MISMATCH_ERROR 22
+#define VERSION_DOWNGRADE_ERROR 23
+
+// Error codes 24-33 and 49-57 are for the Windows maintenance service.
+#define SERVICE_UPDATER_COULD_NOT_BE_STARTED 24
+#define SERVICE_NOT_ENOUGH_COMMAND_LINE_ARGS 25
+#define SERVICE_UPDATER_SIGN_ERROR 26
+#define SERVICE_UPDATER_COMPARE_ERROR 27
+#define SERVICE_UPDATER_IDENTITY_ERROR 28
+#define SERVICE_STILL_APPLYING_ON_SUCCESS 29
+#define SERVICE_STILL_APPLYING_ON_FAILURE 30
+#define SERVICE_UPDATER_NOT_FIXED_DRIVE 31
+#define SERVICE_COULD_NOT_LOCK_UPDATER 32
+#define SERVICE_INSTALLDIR_ERROR 33
+
+#define NO_INSTALLDIR_ERROR 34
+#define WRITE_ERROR_ACCESS_DENIED 35
+// #define WRITE_ERROR_SHARING_VIOLATION 36 // Replaced with errors 46-48
+#define WRITE_ERROR_CALLBACK_APP 37
+#define UNEXPECTED_BZIP_ERROR 39
+#define UNEXPECTED_MAR_ERROR 40
+#define UNEXPECTED_BSPATCH_ERROR 41
+#define UNEXPECTED_FILE_OPERATION_ERROR 42
+#define FILESYSTEM_MOUNT_READWRITE_ERROR 43
+#define DELETE_ERROR_EXPECTED_DIR 46
+#define DELETE_ERROR_EXPECTED_FILE 47
+#define RENAME_ERROR_EXPECTED_FILE 48
+
+// Error codes 24-33 and 49-57 are for the Windows maintenance service.
+#define SERVICE_COULD_NOT_COPY_UPDATER 49
+#define SERVICE_STILL_APPLYING_TERMINATED 50
+#define SERVICE_STILL_APPLYING_NO_EXIT_CODE 51
+#define SERVICE_INVALID_APPLYTO_DIR_STAGED_ERROR 52
+#define SERVICE_CALC_REG_PATH_ERROR 53
+#define SERVICE_INVALID_APPLYTO_DIR_ERROR 54
+#define SERVICE_INVALID_INSTALL_DIR_PATH_ERROR 55
+#define SERVICE_INVALID_WORKING_DIR_PATH_ERROR 56
+#define SERVICE_INSTALL_DIR_REG_ERROR 57
+
+#define WRITE_ERROR_FILE_COPY 61
+#define WRITE_ERROR_DELETE_FILE 62
+#define WRITE_ERROR_OPEN_PATCH_FILE 63
+#define WRITE_ERROR_PATCH_FILE 64
+#define WRITE_ERROR_APPLY_DIR_PATH 65
+#define WRITE_ERROR_CALLBACK_PATH 66
+#define WRITE_ERROR_FILE_ACCESS_DENIED 67
+#define WRITE_ERROR_DIR_ACCESS_DENIED 68
+#define WRITE_ERROR_DELETE_BACKUP 69
+#define WRITE_ERROR_EXTRACT 70
+#define REMOVE_FILE_SPEC_ERROR 71
+#define INVALID_APPLYTO_DIR_STAGED_ERROR 72
+#define LOCK_ERROR_PATCH_FILE 73
+#define INVALID_APPLYTO_DIR_ERROR 74
+#define INVALID_INSTALL_DIR_PATH_ERROR 75
+#define INVALID_WORKING_DIR_PATH_ERROR 76
+#define INVALID_CALLBACK_PATH_ERROR 77
+#define INVALID_CALLBACK_DIR_ERROR 78
+
+// Error codes 80 through 99 are reserved for nsUpdateService.js
+
+// The following error codes are only used by updater.exe
+// when a fallback key exists for tests.
+#define FALLBACKKEY_UNKNOWN_ERROR 100
+#define FALLBACKKEY_REGPATH_ERROR 101
+#define FALLBACKKEY_NOKEY_ERROR 102
+#define FALLBACKKEY_SERVICE_NO_STOP_ERROR 103
+#define FALLBACKKEY_LAUNCH_ERROR 104
+
+#endif // Errors_h__
diff --git a/toolkit/mozapps/update/common/moz.build b/toolkit/mozapps/update/common/moz.build
new file mode 100644
index 000000000..cacb0bad2
--- /dev/null
+++ b/toolkit/mozapps/update/common/moz.build
@@ -0,0 +1,32 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+EXPORTS += [
+ 'readstrings.h',
+ 'updatecommon.h',
+ 'updatedefines.h',
+]
+
+if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'windows':
+ EXPORTS += [
+ 'pathhash.h',
+ 'uachelper.h',
+ 'updatehelper.cpp',
+ 'updatehelper.h',
+ ]
+ if CONFIG['MOZ_MAINTENANCE_SERVICE']:
+ EXPORTS += [
+ 'certificatecheck.h',
+ 'registrycertificates.h',
+ ]
+
+Library('updatecommon')
+
+DEFINES['NS_NO_XPCOM'] = True
+
+srcdir = '.'
+
+include('sources.mozbuild')
diff --git a/toolkit/mozapps/update/common/pathhash.cpp b/toolkit/mozapps/update/common/pathhash.cpp
new file mode 100644
index 000000000..89a004cde
--- /dev/null
+++ b/toolkit/mozapps/update/common/pathhash.cpp
@@ -0,0 +1,139 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include <windows.h>
+#include <wincrypt.h>
+#include "pathhash.h"
+
+
+/**
+ * Converts a binary sequence into a hex string
+ *
+ * @param hash The binary data sequence
+ * @param hashSize The size of the binary data sequence
+ * @param hexString A buffer to store the hex string, must be of
+ * size 2 * @hashSize
+*/
+static void
+BinaryDataToHexString(const BYTE *hash, DWORD &hashSize,
+ LPWSTR hexString)
+{
+ WCHAR *p = hexString;
+ for (DWORD i = 0; i < hashSize; ++i) {
+ wsprintfW(p, L"%.2x", hash[i]);
+ p += 2;
+ }
+}
+
+/**
+ * Calculates an MD5 hash for the given input binary data
+ *
+ * @param data Any sequence of bytes
+ * @param dataSize The number of bytes inside @data
+ * @param hash Output buffer to store hash, must be freed by the caller
+ * @param hashSize The number of bytes in the output buffer
+ * @return TRUE on success
+*/
+static BOOL
+CalculateMD5(const char *data, DWORD dataSize,
+ BYTE **hash, DWORD &hashSize)
+{
+ HCRYPTPROV hProv = 0;
+ HCRYPTHASH hHash = 0;
+
+ if (!CryptAcquireContext(&hProv, nullptr, nullptr, PROV_RSA_FULL,
+ CRYPT_VERIFYCONTEXT)) {
+ if (NTE_BAD_KEYSET != GetLastError()) {
+ return FALSE;
+ }
+
+ // Maybe it doesn't exist, try to create it.
+ if (!CryptAcquireContext(&hProv, nullptr, nullptr, PROV_RSA_FULL,
+ CRYPT_VERIFYCONTEXT | CRYPT_NEWKEYSET)) {
+ return FALSE;
+ }
+ }
+
+ if (!CryptCreateHash(hProv, CALG_MD5, 0, 0, &hHash)) {
+ return FALSE;
+ }
+
+ if (!CryptHashData(hHash, reinterpret_cast<const BYTE*>(data),
+ dataSize, 0)) {
+ return FALSE;
+ }
+
+ DWORD dwCount = sizeof(DWORD);
+ if (!CryptGetHashParam(hHash, HP_HASHSIZE, (BYTE *)&hashSize,
+ &dwCount, 0)) {
+ return FALSE;
+ }
+
+ *hash = new BYTE[hashSize];
+ ZeroMemory(*hash, hashSize);
+ if (!CryptGetHashParam(hHash, HP_HASHVAL, *hash, &hashSize, 0)) {
+ return FALSE;
+ }
+
+ if (hHash) {
+ CryptDestroyHash(hHash);
+ }
+
+ if (hProv) {
+ CryptReleaseContext(hProv,0);
+ }
+
+ return TRUE;
+}
+
+/**
+ * Converts a file path into a unique registry location for cert storage
+ *
+ * @param filePath The input file path to get a registry path from
+ * @param registryPath A buffer to write the registry path to, must
+ * be of size in WCHARs MAX_PATH + 1
+ * @return TRUE if successful
+*/
+BOOL
+CalculateRegistryPathFromFilePath(const LPCWSTR filePath,
+ LPWSTR registryPath)
+{
+ size_t filePathLen = wcslen(filePath);
+ if (!filePathLen) {
+ return FALSE;
+ }
+
+ // If the file path ends in a slash, ignore that character
+ if (filePath[filePathLen -1] == L'\\' ||
+ filePath[filePathLen - 1] == L'/') {
+ filePathLen--;
+ }
+
+ // Copy in the full path into our own buffer.
+ // Copying in the extra slash is OK because we calculate the hash
+ // based on the filePathLen which excludes the slash.
+ // +2 to account for the possibly trailing slash and the null terminator.
+ WCHAR *lowercasePath = new WCHAR[filePathLen + 2];
+ memset(lowercasePath, 0, (filePathLen + 2) * sizeof(WCHAR));
+ wcsncpy(lowercasePath, filePath, filePathLen + 1);
+ _wcslwr(lowercasePath);
+
+ BYTE *hash;
+ DWORD hashSize = 0;
+ if (!CalculateMD5(reinterpret_cast<const char*>(lowercasePath),
+ filePathLen * 2,
+ &hash, hashSize)) {
+ delete[] lowercasePath;
+ return FALSE;
+ }
+ delete[] lowercasePath;
+
+ LPCWSTR baseRegPath = L"SOFTWARE\\Mozilla\\"
+ L"MaintenanceService\\";
+ wcsncpy(registryPath, baseRegPath, MAX_PATH);
+ BinaryDataToHexString(hash, hashSize,
+ registryPath + wcslen(baseRegPath));
+ delete[] hash;
+ return TRUE;
+}
diff --git a/toolkit/mozapps/update/common/pathhash.h b/toolkit/mozapps/update/common/pathhash.h
new file mode 100644
index 000000000..a238317e1
--- /dev/null
+++ b/toolkit/mozapps/update/common/pathhash.h
@@ -0,0 +1,19 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef _PATHHASH_H_
+#define _PATHHASH_H_
+
+/**
+ * Converts a file path into a unique registry location for cert storage
+ *
+ * @param filePath The input file path to get a registry path from
+ * @param registryPath A buffer to write the registry path to, must
+ * be of size in WCHARs MAX_PATH + 1
+ * @return TRUE if successful
+*/
+BOOL CalculateRegistryPathFromFilePath(const LPCWSTR filePath,
+ LPWSTR registryPath);
+
+#endif
diff --git a/toolkit/mozapps/update/common/readstrings.cpp b/toolkit/mozapps/update/common/readstrings.cpp
new file mode 100644
index 000000000..428c91c0b
--- /dev/null
+++ b/toolkit/mozapps/update/common/readstrings.cpp
@@ -0,0 +1,236 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include <limits.h>
+#include <string.h>
+#include <stdio.h>
+#include "readstrings.h"
+#include "errors.h"
+
+#ifdef XP_WIN
+# define NS_tfopen _wfopen
+# define OPEN_MODE L"rb"
+#else
+# define NS_tfopen fopen
+# define OPEN_MODE "r"
+#endif
+
+// stack based FILE wrapper to ensure that fclose is called.
+class AutoFILE {
+public:
+ explicit AutoFILE(FILE *fp) : fp_(fp) {}
+ ~AutoFILE() { if (fp_) fclose(fp_); }
+ operator FILE *() { return fp_; }
+private:
+ FILE *fp_;
+};
+
+class AutoCharArray {
+public:
+ explicit AutoCharArray(size_t len) { ptr_ = new char[len]; }
+ ~AutoCharArray() { delete[] ptr_; }
+ operator char *() { return ptr_; }
+private:
+ char *ptr_;
+};
+
+static const char kNL[] = "\r\n";
+static const char kEquals[] = "=";
+static const char kWhitespace[] = " \t";
+static const char kRBracket[] = "]";
+
+static const char*
+NS_strspnp(const char *delims, const char *str)
+{
+ const char *d;
+ do {
+ for (d = delims; *d != '\0'; ++d) {
+ if (*str == *d) {
+ ++str;
+ break;
+ }
+ }
+ } while (*d);
+
+ return str;
+}
+
+static char*
+NS_strtok(const char *delims, char **str)
+{
+ if (!*str)
+ return nullptr;
+
+ char *ret = (char*) NS_strspnp(delims, *str);
+
+ if (!*ret) {
+ *str = ret;
+ return nullptr;
+ }
+
+ char *i = ret;
+ do {
+ for (const char *d = delims; *d != '\0'; ++d) {
+ if (*i == *d) {
+ *i = '\0';
+ *str = ++i;
+ return ret;
+ }
+ }
+ ++i;
+ } while (*i);
+
+ *str = nullptr;
+ return ret;
+}
+
+/**
+ * Find a key in a keyList containing zero-delimited keys ending with "\0\0".
+ * Returns a zero-based index of the key in the list, or -1 if the key is not found.
+ */
+static int
+find_key(const char *keyList, char* key)
+{
+ if (!keyList)
+ return -1;
+
+ int index = 0;
+ const char *p = keyList;
+ while (*p)
+ {
+ if (strcmp(key, p) == 0)
+ return index;
+
+ p += strlen(p) + 1;
+ index++;
+ }
+
+ // The key was not found if we came here
+ return -1;
+}
+
+/**
+ * A very basic parser for updater.ini taken mostly from nsINIParser.cpp
+ * that can be used by standalone apps.
+ *
+ * @param path Path to the .ini file to read
+ * @param keyList List of zero-delimited keys ending with two zero characters
+ * @param numStrings Number of strings to read into results buffer - must be equal to the number of keys
+ * @param results Two-dimensional array of strings to be filled in the same order as the keys provided
+ * @param section Optional name of the section to read; defaults to "Strings"
+ */
+int
+ReadStrings(const NS_tchar *path,
+ const char *keyList,
+ unsigned int numStrings,
+ char results[][MAX_TEXT_LEN],
+ const char *section)
+{
+ AutoFILE fp(NS_tfopen(path, OPEN_MODE));
+
+ if (!fp)
+ return READ_ERROR;
+
+ /* get file size */
+ if (fseek(fp, 0, SEEK_END) != 0)
+ return READ_ERROR;
+
+ long len = ftell(fp);
+ if (len <= 0)
+ return READ_ERROR;
+
+ size_t flen = size_t(len);
+ AutoCharArray fileContents(flen + 1);
+ if (!fileContents)
+ return READ_STRINGS_MEM_ERROR;
+
+ /* read the file in one swoop */
+ if (fseek(fp, 0, SEEK_SET) != 0)
+ return READ_ERROR;
+
+ size_t rd = fread(fileContents, sizeof(char), flen, fp);
+ if (rd != flen)
+ return READ_ERROR;
+
+ fileContents[flen] = '\0';
+
+ char *buffer = fileContents;
+ bool inStringsSection = false;
+
+ unsigned int read = 0;
+
+ while (char *token = NS_strtok(kNL, &buffer)) {
+ if (token[0] == '#' || token[0] == ';') // it's a comment
+ continue;
+
+ token = (char*) NS_strspnp(kWhitespace, token);
+ if (!*token) // empty line
+ continue;
+
+ if (token[0] == '[') { // section header!
+ ++token;
+ char const * currSection = token;
+
+ char *rb = NS_strtok(kRBracket, &token);
+ if (!rb || NS_strtok(kWhitespace, &token)) {
+ // there's either an unclosed [Section or a [Section]Moretext!
+ // we could frankly decide that this INI file is malformed right
+ // here and stop, but we won't... keep going, looking for
+ // a well-formed [section] to continue working with
+ inStringsSection = false;
+ }
+ else {
+ if (section)
+ inStringsSection = strcmp(currSection, section) == 0;
+ else
+ inStringsSection = strcmp(currSection, "Strings") == 0;
+ }
+
+ continue;
+ }
+
+ if (!inStringsSection) {
+ // If we haven't found a section header (or we found a malformed
+ // section header), or this isn't the [Strings] section don't bother
+ // parsing this line.
+ continue;
+ }
+
+ char *key = token;
+ char *e = NS_strtok(kEquals, &token);
+ if (!e)
+ continue;
+
+ int keyIndex = find_key(keyList, key);
+ if (keyIndex >= 0 && (unsigned int)keyIndex < numStrings)
+ {
+ strncpy(results[keyIndex], token, MAX_TEXT_LEN - 1);
+ results[keyIndex][MAX_TEXT_LEN - 1] = '\0';
+ read++;
+ }
+ }
+
+ return (read == numStrings) ? OK : PARSE_ERROR;
+}
+
+// A wrapper function to read strings for the updater.
+// Added for compatibility with the original code.
+int
+ReadStrings(const NS_tchar *path, StringTable *results)
+{
+ const unsigned int kNumStrings = 2;
+ const char *kUpdaterKeys = "Title\0Info\0";
+ char updater_strings[kNumStrings][MAX_TEXT_LEN];
+
+ int result = ReadStrings(path, kUpdaterKeys, kNumStrings, updater_strings);
+
+ strncpy(results->title, updater_strings[0], MAX_TEXT_LEN - 1);
+ results->title[MAX_TEXT_LEN - 1] = '\0';
+ strncpy(results->info, updater_strings[1], MAX_TEXT_LEN - 1);
+ results->info[MAX_TEXT_LEN - 1] = '\0';
+
+ return result;
+}
diff --git a/toolkit/mozapps/update/common/readstrings.h b/toolkit/mozapps/update/common/readstrings.h
new file mode 100644
index 000000000..cd1909fc9
--- /dev/null
+++ b/toolkit/mozapps/update/common/readstrings.h
@@ -0,0 +1,43 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef READSTRINGS_H__
+#define READSTRINGS_H__
+
+#define MAX_TEXT_LEN 600
+
+#ifdef XP_WIN
+# include <windows.h>
+ typedef WCHAR NS_tchar;
+#else
+ typedef char NS_tchar;
+#endif
+
+#ifndef NULL
+#define NULL 0
+#endif
+
+struct StringTable
+{
+ char title[MAX_TEXT_LEN];
+ char info[MAX_TEXT_LEN];
+};
+
+/**
+ * This function reads in localized strings from updater.ini
+ */
+int ReadStrings(const NS_tchar *path, StringTable *results);
+
+/**
+ * This function reads in localized strings corresponding to the keys from a given .ini
+ */
+int ReadStrings(const NS_tchar *path,
+ const char *keyList,
+ unsigned int numStrings,
+ char results[][MAX_TEXT_LEN],
+ const char *section = nullptr);
+
+#endif // READSTRINGS_H__
diff --git a/toolkit/mozapps/update/common/registrycertificates.cpp b/toolkit/mozapps/update/common/registrycertificates.cpp
new file mode 100644
index 000000000..1c9c44619
--- /dev/null
+++ b/toolkit/mozapps/update/common/registrycertificates.cpp
@@ -0,0 +1,154 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <windows.h>
+
+#include "registrycertificates.h"
+#include "pathhash.h"
+#include "updatecommon.h"
+#include "updatehelper.h"
+#define MAX_KEY_LENGTH 255
+
+/**
+ * Verifies if the file path matches any certificate stored in the registry.
+ *
+ * @param filePath The file path of the application to check if allowed.
+ * @param allowFallbackKeySkip when this is TRUE the fallback registry key will
+ * be used to skip the certificate check. This is the default since the
+ * fallback registry key is located under HKEY_LOCAL_MACHINE which can't be
+ * written to by a low integrity process.
+ * Note: the maintenance service binary can be used to perform this check for
+ * testing or troubleshooting.
+ * @return TRUE if the binary matches any of the allowed certificates.
+ */
+BOOL
+DoesBinaryMatchAllowedCertificates(LPCWSTR basePathForUpdate, LPCWSTR filePath,
+ BOOL allowFallbackKeySkip)
+{
+ WCHAR maintenanceServiceKey[MAX_PATH + 1];
+ if (!CalculateRegistryPathFromFilePath(basePathForUpdate,
+ maintenanceServiceKey)) {
+ return FALSE;
+ }
+
+ // We use KEY_WOW64_64KEY to always force 64-bit view.
+ // The user may have both x86 and x64 applications installed
+ // which each register information. We need a consistent place
+ // to put those certificate attributes in and hence why we always
+ // force the non redirected registry under Wow6432Node.
+ // This flag is ignored on 32bit systems.
+ HKEY baseKey;
+ LONG retCode = RegOpenKeyExW(HKEY_LOCAL_MACHINE,
+ maintenanceServiceKey, 0,
+ KEY_READ | KEY_WOW64_64KEY, &baseKey);
+ if (retCode != ERROR_SUCCESS) {
+ LOG_WARN(("Could not open key. (%d)", retCode));
+ // Our tests run with a different apply directory for each test.
+ // We use this registry key on our test slaves to store the
+ // allowed name/issuers.
+ retCode = RegOpenKeyExW(HKEY_LOCAL_MACHINE,
+ TEST_ONLY_FALLBACK_KEY_PATH, 0,
+ KEY_READ | KEY_WOW64_64KEY, &baseKey);
+ if (retCode != ERROR_SUCCESS) {
+ LOG_WARN(("Could not open fallback key. (%d)", retCode));
+ return FALSE;
+ } else if (allowFallbackKeySkip) {
+ LOG_WARN(("Fallback key present, skipping VerifyCertificateTrustForFile "
+ "check and the certificate attribute registry matching "
+ "check."));
+ RegCloseKey(baseKey);
+ return TRUE;
+ }
+ }
+
+ // Get the number of subkeys.
+ DWORD subkeyCount = 0;
+ retCode = RegQueryInfoKeyW(baseKey, nullptr, nullptr, nullptr, &subkeyCount,
+ nullptr, nullptr, nullptr, nullptr, nullptr,
+ nullptr, nullptr);
+ if (retCode != ERROR_SUCCESS) {
+ LOG_WARN(("Could not query info key. (%d)", retCode));
+ RegCloseKey(baseKey);
+ return FALSE;
+ }
+
+ // Enumerate the subkeys, each subkey represents an allowed certificate.
+ for (DWORD i = 0; i < subkeyCount; i++) {
+ WCHAR subkeyBuffer[MAX_KEY_LENGTH];
+ DWORD subkeyBufferCount = MAX_KEY_LENGTH;
+ retCode = RegEnumKeyExW(baseKey, i, subkeyBuffer,
+ &subkeyBufferCount, nullptr,
+ nullptr, nullptr, nullptr);
+ if (retCode != ERROR_SUCCESS) {
+ LOG_WARN(("Could not enum certs. (%d)", retCode));
+ RegCloseKey(baseKey);
+ return FALSE;
+ }
+
+ // Open the subkey for the current certificate
+ HKEY subKey;
+ retCode = RegOpenKeyExW(baseKey,
+ subkeyBuffer,
+ 0,
+ KEY_READ | KEY_WOW64_64KEY,
+ &subKey);
+ if (retCode != ERROR_SUCCESS) {
+ LOG_WARN(("Could not open subkey. (%d)", retCode));
+ continue; // Try the next subkey
+ }
+
+ const int MAX_CHAR_COUNT = 256;
+ DWORD valueBufSize = MAX_CHAR_COUNT * sizeof(WCHAR);
+ WCHAR name[MAX_CHAR_COUNT] = { L'\0' };
+ WCHAR issuer[MAX_CHAR_COUNT] = { L'\0' };
+
+ // Get the name from the registry
+ retCode = RegQueryValueExW(subKey, L"name", 0, nullptr,
+ (LPBYTE)name, &valueBufSize);
+ if (retCode != ERROR_SUCCESS) {
+ LOG_WARN(("Could not obtain name from registry. (%d)", retCode));
+ RegCloseKey(subKey);
+ continue; // Try the next subkey
+ }
+
+ // Get the issuer from the registry
+ valueBufSize = MAX_CHAR_COUNT * sizeof(WCHAR);
+ retCode = RegQueryValueExW(subKey, L"issuer", 0, nullptr,
+ (LPBYTE)issuer, &valueBufSize);
+ if (retCode != ERROR_SUCCESS) {
+ LOG_WARN(("Could not obtain issuer from registry. (%d)", retCode));
+ RegCloseKey(subKey);
+ continue; // Try the next subkey
+ }
+
+ CertificateCheckInfo allowedCertificate = {
+ name,
+ issuer,
+ };
+
+ retCode = CheckCertificateForPEFile(filePath, allowedCertificate);
+ if (retCode != ERROR_SUCCESS) {
+ LOG_WARN(("Error on certificate check. (%d)", retCode));
+ RegCloseKey(subKey);
+ continue; // Try the next subkey
+ }
+
+ retCode = VerifyCertificateTrustForFile(filePath);
+ if (retCode != ERROR_SUCCESS) {
+ LOG_WARN(("Error on certificate trust check. (%d)", retCode));
+ RegCloseKey(subKey);
+ continue; // Try the next subkey
+ }
+
+ RegCloseKey(baseKey);
+ // Raise the roof, we found a match!
+ return TRUE;
+ }
+
+ RegCloseKey(baseKey);
+ // No certificates match, :'(
+ return FALSE;
+}
diff --git a/toolkit/mozapps/update/common/registrycertificates.h b/toolkit/mozapps/update/common/registrycertificates.h
new file mode 100644
index 000000000..9f68d1a8d
--- /dev/null
+++ b/toolkit/mozapps/update/common/registrycertificates.h
@@ -0,0 +1,14 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef _REGISTRYCERTIFICATES_H_
+#define _REGISTRYCERTIFICATES_H_
+
+#include "certificatecheck.h"
+
+BOOL DoesBinaryMatchAllowedCertificates(LPCWSTR basePathForUpdate,
+ LPCWSTR filePath,
+ BOOL allowFallbackKeySkip = TRUE);
+
+#endif
diff --git a/toolkit/mozapps/update/common/sources.mozbuild b/toolkit/mozapps/update/common/sources.mozbuild
new file mode 100644
index 000000000..cde51e09b
--- /dev/null
+++ b/toolkit/mozapps/update/common/sources.mozbuild
@@ -0,0 +1,28 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+sources = []
+
+if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'windows':
+ sources += [
+ 'pathhash.cpp',
+ 'uachelper.cpp',
+ 'updatehelper.cpp',
+ ]
+ if CONFIG['MOZ_MAINTENANCE_SERVICE']:
+ sources += [
+ 'certificatecheck.cpp',
+ 'registrycertificates.cpp',
+ ]
+ OS_LIBS += [
+ 'crypt32',
+ 'wintrust',
+ ]
+
+sources += [
+ 'readstrings.cpp',
+ 'updatecommon.cpp',
+]
+
+SOURCES += sorted(['%s/%s' % (srcdir, s) for s in sources])
diff --git a/toolkit/mozapps/update/common/uachelper.cpp b/toolkit/mozapps/update/common/uachelper.cpp
new file mode 100644
index 000000000..11647aa72
--- /dev/null
+++ b/toolkit/mozapps/update/common/uachelper.cpp
@@ -0,0 +1,222 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include <windows.h>
+#include <wtsapi32.h>
+#include "uachelper.h"
+#include "updatecommon.h"
+
+// See the MSDN documentation with title: Privilege Constants
+// At the time of this writing, this documentation is located at:
+// http://msdn.microsoft.com/en-us/library/windows/desktop/bb530716%28v=vs.85%29.aspx
+LPCTSTR UACHelper::PrivsToDisable[] = {
+ SE_ASSIGNPRIMARYTOKEN_NAME,
+ SE_AUDIT_NAME,
+ SE_BACKUP_NAME,
+ // CreateProcess will succeed but the app will fail to launch on some WinXP
+ // machines if SE_CHANGE_NOTIFY_NAME is disabled. In particular this happens
+ // for limited user accounts on those machines. The define is kept here as a
+ // reminder that it should never be re-added.
+ // This permission is for directory watching but also from MSDN: "This
+ // privilege also causes the system to skip all traversal access checks."
+ // SE_CHANGE_NOTIFY_NAME,
+ SE_CREATE_GLOBAL_NAME,
+ SE_CREATE_PAGEFILE_NAME,
+ SE_CREATE_PERMANENT_NAME,
+ SE_CREATE_SYMBOLIC_LINK_NAME,
+ SE_CREATE_TOKEN_NAME,
+ SE_DEBUG_NAME,
+ SE_ENABLE_DELEGATION_NAME,
+ SE_IMPERSONATE_NAME,
+ SE_INC_BASE_PRIORITY_NAME,
+ SE_INCREASE_QUOTA_NAME,
+ SE_INC_WORKING_SET_NAME,
+ SE_LOAD_DRIVER_NAME,
+ SE_LOCK_MEMORY_NAME,
+ SE_MACHINE_ACCOUNT_NAME,
+ SE_MANAGE_VOLUME_NAME,
+ SE_PROF_SINGLE_PROCESS_NAME,
+ SE_RELABEL_NAME,
+ SE_REMOTE_SHUTDOWN_NAME,
+ SE_RESTORE_NAME,
+ SE_SECURITY_NAME,
+ SE_SHUTDOWN_NAME,
+ SE_SYNC_AGENT_NAME,
+ SE_SYSTEM_ENVIRONMENT_NAME,
+ SE_SYSTEM_PROFILE_NAME,
+ SE_SYSTEMTIME_NAME,
+ SE_TAKE_OWNERSHIP_NAME,
+ SE_TCB_NAME,
+ SE_TIME_ZONE_NAME,
+ SE_TRUSTED_CREDMAN_ACCESS_NAME,
+ SE_UNDOCK_NAME,
+ SE_UNSOLICITED_INPUT_NAME
+};
+
+/**
+ * Opens a user token for the given session ID
+ *
+ * @param sessionID The session ID for the token to obtain
+ * @return A handle to the token to obtain which will be primary if enough
+ * permissions exist. Caller should close the handle.
+ */
+HANDLE
+UACHelper::OpenUserToken(DWORD sessionID)
+{
+ HMODULE module = LoadLibraryW(L"wtsapi32.dll");
+ HANDLE token = nullptr;
+ decltype(WTSQueryUserToken)* wtsQueryUserToken =
+ (decltype(WTSQueryUserToken)*) GetProcAddress(module, "WTSQueryUserToken");
+ if (wtsQueryUserToken) {
+ wtsQueryUserToken(sessionID, &token);
+ }
+ FreeLibrary(module);
+ return token;
+}
+
+/**
+ * Opens a linked token for the specified token.
+ *
+ * @param token The token to get the linked token from
+ * @return A linked token or nullptr if one does not exist.
+ * Caller should close the handle.
+ */
+HANDLE
+UACHelper::OpenLinkedToken(HANDLE token)
+{
+ // Magic below...
+ // UAC creates 2 tokens. One is the restricted token which we have.
+ // the other is the UAC elevated one. Since we are running as a service
+ // as the system account we have access to both.
+ TOKEN_LINKED_TOKEN tlt;
+ HANDLE hNewLinkedToken = nullptr;
+ DWORD len;
+ if (GetTokenInformation(token, (TOKEN_INFORMATION_CLASS)TokenLinkedToken,
+ &tlt, sizeof(TOKEN_LINKED_TOKEN), &len)) {
+ token = tlt.LinkedToken;
+ hNewLinkedToken = token;
+ }
+ return hNewLinkedToken;
+}
+
+
+/**
+ * Enables or disables a privilege for the specified token.
+ *
+ * @param token The token to adjust the privilege on.
+ * @param priv The privilege to adjust.
+ * @param enable Whether to enable or disable it
+ * @return TRUE if the token was adjusted to the specified value.
+ */
+BOOL
+UACHelper::SetPrivilege(HANDLE token, LPCTSTR priv, BOOL enable)
+{
+ LUID luidOfPriv;
+ if (!LookupPrivilegeValue(nullptr, priv, &luidOfPriv)) {
+ return FALSE;
+ }
+
+ TOKEN_PRIVILEGES tokenPriv;
+ tokenPriv.PrivilegeCount = 1;
+ tokenPriv.Privileges[0].Luid = luidOfPriv;
+ tokenPriv.Privileges[0].Attributes = enable ? SE_PRIVILEGE_ENABLED : 0;
+
+ SetLastError(ERROR_SUCCESS);
+ if (!AdjustTokenPrivileges(token, false, &tokenPriv,
+ sizeof(tokenPriv), nullptr, nullptr)) {
+ return FALSE;
+ }
+
+ return GetLastError() == ERROR_SUCCESS;
+}
+
+/**
+ * For each privilege that is specified, an attempt will be made to
+ * drop the privilege.
+ *
+ * @param token The token to adjust the privilege on.
+ * Pass nullptr for current token.
+ * @param unneededPrivs An array of unneeded privileges.
+ * @param count The size of the array
+ * @return TRUE if there were no errors
+ */
+BOOL
+UACHelper::DisableUnneededPrivileges(HANDLE token,
+ LPCTSTR *unneededPrivs,
+ size_t count)
+{
+ HANDLE obtainedToken = nullptr;
+ if (!token) {
+ // Note: This handle is a pseudo-handle and need not be closed
+ HANDLE process = GetCurrentProcess();
+ if (!OpenProcessToken(process, TOKEN_ALL_ACCESS_P, &obtainedToken)) {
+ LOG_WARN(("Could not obtain token for current process, no "
+ "privileges changed. (%d)", GetLastError()));
+ return FALSE;
+ }
+ token = obtainedToken;
+ }
+
+ BOOL result = TRUE;
+ for (size_t i = 0; i < count; i++) {
+ if (SetPrivilege(token, unneededPrivs[i], FALSE)) {
+ LOG(("Disabled unneeded token privilege: %s.",
+ unneededPrivs[i]));
+ } else {
+ LOG(("Could not disable token privilege value: %s. (%d)",
+ unneededPrivs[i], GetLastError()));
+ result = FALSE;
+ }
+ }
+
+ if (obtainedToken) {
+ CloseHandle(obtainedToken);
+ }
+ return result;
+}
+
+/**
+ * Disables privileges for the specified token.
+ * The privileges to disable are in PrivsToDisable.
+ * In the future there could be new privs and we are not sure if we should
+ * explicitly disable these or not.
+ *
+ * @param token The token to drop the privilege on.
+ * Pass nullptr for current token.
+ * @return TRUE if there were no errors
+ */
+BOOL
+UACHelper::DisablePrivileges(HANDLE token)
+{
+ static const size_t PrivsToDisableSize =
+ sizeof(UACHelper::PrivsToDisable) / sizeof(UACHelper::PrivsToDisable[0]);
+
+ return DisableUnneededPrivileges(token, UACHelper::PrivsToDisable,
+ PrivsToDisableSize);
+}
+
+/**
+ * Check if the current user can elevate.
+ *
+ * @return true if the user can elevate.
+ * false otherwise.
+ */
+bool
+UACHelper::CanUserElevate()
+{
+ HANDLE token;
+ if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &token)) {
+ return false;
+ }
+
+ TOKEN_ELEVATION_TYPE elevationType;
+ DWORD len;
+ bool canElevate = GetTokenInformation(token, TokenElevationType,
+ &elevationType,
+ sizeof(elevationType), &len) &&
+ (elevationType == TokenElevationTypeLimited);
+ CloseHandle(token);
+
+ return canElevate;
+}
diff --git a/toolkit/mozapps/update/common/uachelper.h b/toolkit/mozapps/update/common/uachelper.h
new file mode 100644
index 000000000..6481ff5b5
--- /dev/null
+++ b/toolkit/mozapps/update/common/uachelper.h
@@ -0,0 +1,23 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef _UACHELPER_H_
+#define _UACHELPER_H_
+
+class UACHelper
+{
+public:
+ static HANDLE OpenUserToken(DWORD sessionID);
+ static HANDLE OpenLinkedToken(HANDLE token);
+ static BOOL DisablePrivileges(HANDLE token);
+ static bool CanUserElevate();
+
+private:
+ static BOOL SetPrivilege(HANDLE token, LPCTSTR privs, BOOL enable);
+ static BOOL DisableUnneededPrivileges(HANDLE token,
+ LPCTSTR *unneededPrivs, size_t count);
+ static LPCTSTR PrivsToDisable[];
+};
+
+#endif
diff --git a/toolkit/mozapps/update/common/updatecommon.cpp b/toolkit/mozapps/update/common/updatecommon.cpp
new file mode 100644
index 000000000..aff7d7260
--- /dev/null
+++ b/toolkit/mozapps/update/common/updatecommon.cpp
@@ -0,0 +1,213 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#if defined(XP_WIN)
+#include <windows.h>
+#endif
+
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <stdarg.h>
+
+#include "updatecommon.h"
+
+UpdateLog::UpdateLog() : logFP(nullptr)
+{
+}
+
+void UpdateLog::Init(NS_tchar* sourcePath,
+ const NS_tchar* fileName)
+{
+ if (logFP) {
+ return;
+ }
+
+ int dstFilePathLen = NS_tsnprintf(mDstFilePath,
+ sizeof(mDstFilePath)/sizeof(mDstFilePath[0]),
+ NS_T("%s/%s"), sourcePath, fileName);
+ // If the destination path was over the length limit,
+ // disable logging by skipping opening the file and setting logFP.
+ if ((dstFilePathLen > 0) &&
+ (dstFilePathLen <
+ static_cast<int>(sizeof(mDstFilePath)/sizeof(mDstFilePath[0])))) {
+#ifdef XP_WIN
+ if (GetTempFileNameW(sourcePath, L"log", 0, mTmpFilePath) != 0) {
+ logFP = NS_tfopen(mTmpFilePath, NS_T("w"));
+
+ // Delete this file now so it is possible to tell from the unelevated
+ // updater process if the elevated updater process has written the log.
+ DeleteFileW(mDstFilePath);
+ }
+#elif XP_MACOSX
+ logFP = NS_tfopen(mDstFilePath, NS_T("w"));
+#else
+ // On platforms that have an updates directory in the installation directory
+ // (e.g. platforms other than Windows and Mac) the update log is written to
+ // a temporary file and then to the update log file. This is needed since
+ // the installation directory is moved during a replace request. This can be
+ // removed when the platform's updates directory is located outside of the
+ // installation directory.
+ logFP = tmpfile();
+#endif
+ }
+}
+
+void UpdateLog::Finish()
+{
+ if (!logFP) {
+ return;
+ }
+
+#if !defined(XP_WIN) && !defined(XP_MACOSX)
+ const int blockSize = 1024;
+ char buffer[blockSize];
+ fflush(logFP);
+ rewind(logFP);
+
+ FILE *updateLogFP = NS_tfopen(mDstFilePath, NS_T("wb+"));
+ while (!feof(logFP)) {
+ size_t read = fread(buffer, 1, blockSize, logFP);
+ if (ferror(logFP)) {
+ fclose(logFP);
+ logFP = nullptr;
+ fclose(updateLogFP);
+ updateLogFP = nullptr;
+ return;
+ }
+
+ size_t written = 0;
+
+ while (written < read) {
+ size_t chunkWritten = fwrite(buffer, 1, read - written, updateLogFP);
+ if (chunkWritten <= 0) {
+ fclose(logFP);
+ logFP = nullptr;
+ fclose(updateLogFP);
+ updateLogFP = nullptr;
+ return;
+ }
+
+ written += chunkWritten;
+ }
+ }
+ fclose(updateLogFP);
+ updateLogFP = nullptr;
+#endif
+
+ fclose(logFP);
+ logFP = nullptr;
+
+#ifdef XP_WIN
+ // When the log file already exists then the elevated updater has already
+ // written the log file and the temp file for the log should be discarded.
+ if (!NS_taccess(mDstFilePath, F_OK)) {
+ DeleteFileW(mTmpFilePath);
+ } else {
+ MoveFileW(mTmpFilePath, mDstFilePath);
+ }
+#endif
+}
+
+void UpdateLog::Flush()
+{
+ if (!logFP) {
+ return;
+ }
+
+ fflush(logFP);
+}
+
+void UpdateLog::Printf(const char *fmt, ... )
+{
+ if (!logFP) {
+ return;
+ }
+
+ va_list ap;
+ va_start(ap, fmt);
+ vfprintf(logFP, fmt, ap);
+ fprintf(logFP, "\n");
+ va_end(ap);
+}
+
+void UpdateLog::WarnPrintf(const char *fmt, ... )
+{
+ if (!logFP) {
+ return;
+ }
+
+ va_list ap;
+ va_start(ap, fmt);
+ fprintf(logFP, "*** Warning: ");
+ vfprintf(logFP, fmt, ap);
+ fprintf(logFP, "***\n");
+ va_end(ap);
+}
+
+/**
+ * Performs checks of a full path for validity for this application.
+ *
+ * @param origFullPath
+ * The full path to check.
+ * @return true if the path is valid for this application and false otherwise.
+ */
+bool
+IsValidFullPath(NS_tchar* origFullPath)
+{
+ // Subtract 1 from MAXPATHLEN for null termination.
+ if (NS_tstrlen(origFullPath) > MAXPATHLEN - 1) {
+ // The path is longer than acceptable for this application.
+ return false;
+ }
+
+#ifdef XP_WIN
+ NS_tchar testPath[MAXPATHLEN] = {NS_T('\0')};
+ // GetFullPathNameW will replace / with \ which PathCanonicalizeW requires.
+ if (GetFullPathNameW(origFullPath, MAXPATHLEN, testPath, nullptr) == 0) {
+ // Unable to get the full name for the path (e.g. invalid path).
+ return false;
+ }
+
+ NS_tchar canonicalPath[MAXPATHLEN] = {NS_T('\0')};
+ if (!PathCanonicalizeW(canonicalPath, testPath)) {
+ // Path could not be canonicalized (e.g. invalid path).
+ return false;
+ }
+
+ // Check if the path passed in resolves to a differerent path.
+ if (NS_tstricmp(origFullPath, canonicalPath) != 0) {
+ // Case insensitive string comparison between the supplied path and the
+ // canonical path are not equal. This will prevent directory traversal and
+ // the use of / in paths since they are converted to \.
+ return false;
+ }
+
+ NS_tstrncpy(testPath, origFullPath, MAXPATHLEN);
+ if (!PathStripToRootW(testPath)) {
+ // It should always be possible to strip a valid path to its root.
+ return false;
+ }
+
+ if (origFullPath[0] == NS_T('\\')) {
+ // Only allow UNC server share paths.
+ if (!PathIsUNCServerShareW(testPath)) {
+ return false;
+ }
+ }
+#else
+ // Only allow full paths.
+ if (origFullPath[0] != NS_T('/')) {
+ return false;
+ }
+
+ // The path must not traverse directories
+ if (NS_tstrstr(origFullPath, NS_T("..")) != nullptr ||
+ NS_tstrstr(origFullPath, NS_T("./")) != nullptr) {
+ return false;
+ }
+#endif
+ return true;
+}
diff --git a/toolkit/mozapps/update/common/updatecommon.h b/toolkit/mozapps/update/common/updatecommon.h
new file mode 100644
index 000000000..3055851d8
--- /dev/null
+++ b/toolkit/mozapps/update/common/updatecommon.h
@@ -0,0 +1,47 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef UPDATECOMMON_H
+#define UPDATECOMMON_H
+
+#include "updatedefines.h"
+#include <stdio.h>
+
+class UpdateLog
+{
+public:
+ static UpdateLog & GetPrimaryLog()
+ {
+ static UpdateLog primaryLog;
+ return primaryLog;
+ }
+
+ void Init(NS_tchar* sourcePath, const NS_tchar* fileName);
+ void Finish();
+ void Flush();
+ void Printf(const char *fmt, ... );
+ void WarnPrintf(const char *fmt, ... );
+
+ ~UpdateLog()
+ {
+ Finish();
+ }
+
+protected:
+ UpdateLog();
+ FILE *logFP;
+ NS_tchar mTmpFilePath[MAXPATHLEN];
+ NS_tchar mDstFilePath[MAXPATHLEN];
+};
+
+bool IsValidFullPath(NS_tchar* fullPath);
+
+#define LOG_WARN(args) UpdateLog::GetPrimaryLog().WarnPrintf args
+#define LOG(args) UpdateLog::GetPrimaryLog().Printf args
+#define LogInit(PATHNAME_, FILENAME_) \
+ UpdateLog::GetPrimaryLog().Init(PATHNAME_, FILENAME_)
+#define LogFinish() UpdateLog::GetPrimaryLog().Finish()
+#define LogFlush() UpdateLog::GetPrimaryLog().Flush()
+
+#endif
diff --git a/toolkit/mozapps/update/common/updatedefines.h b/toolkit/mozapps/update/common/updatedefines.h
new file mode 100644
index 000000000..760d2c4c4
--- /dev/null
+++ b/toolkit/mozapps/update/common/updatedefines.h
@@ -0,0 +1,142 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef UPDATEDEFINES_H
+#define UPDATEDEFINES_H
+
+#include "readstrings.h"
+
+#ifndef MAXPATHLEN
+# ifdef PATH_MAX
+# define MAXPATHLEN PATH_MAX
+# elif defined(MAX_PATH)
+# define MAXPATHLEN MAX_PATH
+# elif defined(_MAX_PATH)
+# define MAXPATHLEN _MAX_PATH
+# elif defined(CCHMAXPATH)
+# define MAXPATHLEN CCHMAXPATH
+# else
+# define MAXPATHLEN 1024
+# endif
+#endif
+
+#if defined(XP_WIN)
+# include <windows.h>
+# include <shlwapi.h>
+# include <direct.h>
+# include <io.h>
+# include <stdio.h>
+# include <stdarg.h>
+
+# ifndef F_OK
+# define F_OK 00
+# endif
+# ifndef W_OK
+# define W_OK 02
+# endif
+# ifndef R_OK
+# define R_OK 04
+# endif
+# define S_ISDIR(s) (((s) & _S_IFMT) == _S_IFDIR)
+# define S_ISREG(s) (((s) & _S_IFMT) == _S_IFREG)
+
+# define access _access
+
+# define putenv _putenv
+# if defined(_MSC_VER) && _MSC_VER < 1900
+# define stat _stat
+# endif
+# define DELETE_DIR L"tobedeleted"
+# define CALLBACK_BACKUP_EXT L".moz-callback"
+
+# define LOG_S "%S"
+# define NS_T(str) L ## str
+# define NS_SLASH NS_T('\\')
+
+static inline int mywcsprintf(WCHAR* dest, size_t count, const WCHAR* fmt, ...)
+{
+ size_t _count = count - 1;
+ va_list varargs;
+ va_start(varargs, fmt);
+ int result = _vsnwprintf(dest, count - 1, fmt, varargs);
+ va_end(varargs);
+ dest[_count] = L'\0';
+ return result;
+}
+#define NS_tsnprintf mywcsprintf
+# define NS_taccess _waccess
+# define NS_tchdir _wchdir
+# define NS_tchmod _wchmod
+# define NS_tfopen _wfopen
+# define NS_tmkdir(path, perms) _wmkdir(path)
+# define NS_tremove _wremove
+// _wrename is used to avoid the link tracking service.
+# define NS_trename _wrename
+# define NS_trmdir _wrmdir
+# define NS_tstat _wstat
+# define NS_tlstat _wstat // No symlinks on Windows
+# define NS_tstat_t _stat
+# define NS_tstrcat wcscat
+# define NS_tstrcmp wcscmp
+# define NS_tstricmp wcsicmp
+# define NS_tstrcpy wcscpy
+# define NS_tstrncpy wcsncpy
+# define NS_tstrlen wcslen
+# define NS_tstrchr wcschr
+# define NS_tstrrchr wcsrchr
+# define NS_tstrstr wcsstr
+# include "win_dirent.h"
+# define NS_tDIR DIR
+# define NS_tdirent dirent
+# define NS_topendir opendir
+# define NS_tclosedir closedir
+# define NS_treaddir readdir
+#else
+# include <sys/wait.h>
+# include <unistd.h>
+
+#ifdef SOLARIS
+# include <sys/stat.h>
+#else
+# include <fts.h>
+#endif
+# include <dirent.h>
+
+#ifdef XP_MACOSX
+# include <sys/time.h>
+#endif
+
+# define LOG_S "%s"
+# define NS_T(str) str
+# define NS_SLASH NS_T('/')
+# define NS_tsnprintf snprintf
+# define NS_taccess access
+# define NS_tchdir chdir
+# define NS_tchmod chmod
+# define NS_tfopen fopen
+# define NS_tmkdir mkdir
+# define NS_tremove remove
+# define NS_trename rename
+# define NS_trmdir rmdir
+# define NS_tstat stat
+# define NS_tstat_t stat
+# define NS_tlstat lstat
+# define NS_tstrcat strcat
+# define NS_tstrcmp strcmp
+# define NS_tstricmp strcasecmp
+# define NS_tstrcpy strcpy
+# define NS_tstrncpy strncpy
+# define NS_tstrlen strlen
+# define NS_tstrrchr strrchr
+# define NS_tstrstr strstr
+# define NS_tDIR DIR
+# define NS_tdirent dirent
+# define NS_topendir opendir
+# define NS_tclosedir closedir
+# define NS_treaddir readdir
+#endif
+
+#define BACKUP_EXT NS_T(".moz-backup")
+
+#endif
diff --git a/toolkit/mozapps/update/common/updatehelper.cpp b/toolkit/mozapps/update/common/updatehelper.cpp
new file mode 100644
index 000000000..afb89bacf
--- /dev/null
+++ b/toolkit/mozapps/update/common/updatehelper.cpp
@@ -0,0 +1,609 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include <windows.h>
+
+// Needed for CreateToolhelp32Snapshot
+#include <tlhelp32.h>
+#ifndef ONLY_SERVICE_LAUNCHING
+
+#include <stdio.h>
+#include "shlobj.h"
+#include "updatehelper.h"
+#include "uachelper.h"
+#include "pathhash.h"
+#include "mozilla/UniquePtr.h"
+
+// Needed for PathAppendW
+#include <shlwapi.h>
+
+using mozilla::MakeUnique;
+using mozilla::UniquePtr;
+
+BOOL PathAppendSafe(LPWSTR base, LPCWSTR extra);
+BOOL PathGetSiblingFilePath(LPWSTR destinationBuffer,
+ LPCWSTR siblingFilePath,
+ LPCWSTR newFileName);
+
+/**
+ * Obtains the path of a file in the same directory as the specified file.
+ *
+ * @param destinationBuffer A buffer of size MAX_PATH + 1 to store the result.
+ * @param siblingFIlePath The path of another file in the same directory
+ * @param newFileName The filename of another file in the same directory
+ * @return TRUE if successful
+ */
+BOOL
+PathGetSiblingFilePath(LPWSTR destinationBuffer,
+ LPCWSTR siblingFilePath,
+ LPCWSTR newFileName)
+{
+ if (wcslen(siblingFilePath) >= MAX_PATH) {
+ return FALSE;
+ }
+
+ wcsncpy(destinationBuffer, siblingFilePath, MAX_PATH);
+ if (!PathRemoveFileSpecW(destinationBuffer)) {
+ return FALSE;
+ }
+
+ if (wcslen(destinationBuffer) + wcslen(newFileName) >= MAX_PATH) {
+ return FALSE;
+ }
+
+ return PathAppendSafe(destinationBuffer, newFileName);
+}
+
+/**
+ * Starts the upgrade process for update of the service if it is
+ * already installed.
+ *
+ * @param installDir the installation directory where
+ * maintenanceservice_installer.exe is located.
+ * @return TRUE if successful
+ */
+BOOL
+StartServiceUpdate(LPCWSTR installDir)
+{
+ // Get a handle to the local computer SCM database
+ SC_HANDLE manager = OpenSCManager(nullptr, nullptr,
+ SC_MANAGER_ALL_ACCESS);
+ if (!manager) {
+ return FALSE;
+ }
+
+ // Open the service
+ SC_HANDLE svc = OpenServiceW(manager, SVC_NAME,
+ SERVICE_ALL_ACCESS);
+ if (!svc) {
+ CloseServiceHandle(manager);
+ return FALSE;
+ }
+
+ // If we reach here, then the service is installed, so
+ // proceed with upgrading it.
+
+ CloseServiceHandle(manager);
+
+ // The service exists and we opened it, get the config bytes needed
+ DWORD bytesNeeded;
+ if (!QueryServiceConfigW(svc, nullptr, 0, &bytesNeeded) &&
+ GetLastError() != ERROR_INSUFFICIENT_BUFFER) {
+ CloseServiceHandle(svc);
+ return FALSE;
+ }
+
+ // Get the service config information, in particular we want the binary
+ // path of the service.
+ UniquePtr<char[]> serviceConfigBuffer = MakeUnique<char[]>(bytesNeeded);
+ if (!QueryServiceConfigW(svc,
+ reinterpret_cast<QUERY_SERVICE_CONFIGW*>(serviceConfigBuffer.get()),
+ bytesNeeded, &bytesNeeded)) {
+ CloseServiceHandle(svc);
+ return FALSE;
+ }
+
+ CloseServiceHandle(svc);
+
+ QUERY_SERVICE_CONFIGW &serviceConfig =
+ *reinterpret_cast<QUERY_SERVICE_CONFIGW*>(serviceConfigBuffer.get());
+
+ PathUnquoteSpacesW(serviceConfig.lpBinaryPathName);
+
+ // Obtain the temp path of the maintenance service binary
+ WCHAR tmpService[MAX_PATH + 1] = { L'\0' };
+ if (!PathGetSiblingFilePath(tmpService, serviceConfig.lpBinaryPathName,
+ L"maintenanceservice_tmp.exe")) {
+ return FALSE;
+ }
+
+ // Get the new maintenance service path from the install dir
+ WCHAR newMaintServicePath[MAX_PATH + 1] = { L'\0' };
+ wcsncpy(newMaintServicePath, installDir, MAX_PATH);
+ PathAppendSafe(newMaintServicePath,
+ L"maintenanceservice.exe");
+
+ // Copy the temp file in alongside the maintenace service.
+ // This is a requirement for maintenance service upgrades.
+ if (!CopyFileW(newMaintServicePath, tmpService, FALSE)) {
+ return FALSE;
+ }
+
+ // Start the upgrade comparison process
+ STARTUPINFOW si = {0};
+ si.cb = sizeof(STARTUPINFOW);
+ // No particular desktop because no UI
+ si.lpDesktop = L"";
+ PROCESS_INFORMATION pi = {0};
+ WCHAR cmdLine[64] = { '\0' };
+ wcsncpy(cmdLine, L"dummyparam.exe upgrade",
+ sizeof(cmdLine) / sizeof(cmdLine[0]) - 1);
+ BOOL svcUpdateProcessStarted = CreateProcessW(tmpService,
+ cmdLine,
+ nullptr, nullptr, FALSE,
+ 0,
+ nullptr, installDir, &si, &pi);
+ if (svcUpdateProcessStarted) {
+ CloseHandle(pi.hProcess);
+ CloseHandle(pi.hThread);
+ }
+ return svcUpdateProcessStarted;
+}
+
+#endif
+
+/**
+ * Executes a maintenance service command
+ *
+ * @param argc The total number of arguments in argv
+ * @param argv An array of null terminated strings to pass to the service,
+ * @return ERROR_SUCCESS if the service command was started.
+ * Less than 16000, a windows system error code from StartServiceW
+ * More than 20000, 20000 + the last state of the service constant if
+ * the last state is something other than stopped.
+ * 17001 if the SCM could not be opened
+ * 17002 if the service could not be opened
+*/
+DWORD
+StartServiceCommand(int argc, LPCWSTR* argv)
+{
+ DWORD lastState = WaitForServiceStop(SVC_NAME, 5);
+ if (lastState != SERVICE_STOPPED) {
+ return 20000 + lastState;
+ }
+
+ // Get a handle to the SCM database.
+ SC_HANDLE serviceManager = OpenSCManager(nullptr, nullptr,
+ SC_MANAGER_CONNECT |
+ SC_MANAGER_ENUMERATE_SERVICE);
+ if (!serviceManager) {
+ return 17001;
+ }
+
+ // Get a handle to the service.
+ SC_HANDLE service = OpenServiceW(serviceManager,
+ SVC_NAME,
+ SERVICE_START);
+ if (!service) {
+ CloseServiceHandle(serviceManager);
+ return 17002;
+ }
+
+ // Wait at most 5 seconds trying to start the service in case of errors
+ // like ERROR_SERVICE_DATABASE_LOCKED or ERROR_SERVICE_REQUEST_TIMEOUT.
+ const DWORD maxWaitMS = 5000;
+ DWORD currentWaitMS = 0;
+ DWORD lastError = ERROR_SUCCESS;
+ while (currentWaitMS < maxWaitMS) {
+ BOOL result = StartServiceW(service, argc, argv);
+ if (result) {
+ lastError = ERROR_SUCCESS;
+ break;
+ } else {
+ lastError = GetLastError();
+ }
+ Sleep(100);
+ currentWaitMS += 100;
+ }
+ CloseServiceHandle(service);
+ CloseServiceHandle(serviceManager);
+ return lastError;
+}
+
+#ifndef ONLY_SERVICE_LAUNCHING
+
+/**
+ * Launch a service initiated action for a software update with the
+ * specified arguments.
+ *
+ * @param exePath The path of the executable to run
+ * @param argc The total number of arguments in argv
+ * @param argv An array of null terminated strings to pass to the exePath,
+ * argv[0] must be the path to the updater.exe
+ * @return ERROR_SUCCESS if successful
+ */
+DWORD
+LaunchServiceSoftwareUpdateCommand(int argc, LPCWSTR* argv)
+{
+ // The service command is the same as the updater.exe command line except
+ // it has 2 extra args: 1) The Path to udpater.exe, and 2) the command
+ // being executed which is "software-update"
+ LPCWSTR *updaterServiceArgv = new LPCWSTR[argc + 2];
+ updaterServiceArgv[0] = L"MozillaMaintenance";
+ updaterServiceArgv[1] = L"software-update";
+
+ for (int i = 0; i < argc; ++i) {
+ updaterServiceArgv[i + 2] = argv[i];
+ }
+
+ // Execute the service command by starting the service with
+ // the passed in arguments.
+ DWORD ret = StartServiceCommand(argc + 2, updaterServiceArgv);
+ delete[] updaterServiceArgv;
+ return ret;
+}
+
+/**
+ * Joins a base directory path with a filename.
+ *
+ * @param base The base directory path of size MAX_PATH + 1
+ * @param extra The filename to append
+ * @return TRUE if the file name was successful appended to base
+ */
+BOOL
+PathAppendSafe(LPWSTR base, LPCWSTR extra)
+{
+ if (wcslen(base) + wcslen(extra) >= MAX_PATH) {
+ return FALSE;
+ }
+
+ return PathAppendW(base, extra);
+}
+
+/**
+ * Sets update.status to a specific failure code
+ *
+ * @param updateDirPath The path of the update directory
+ * @return TRUE if successful
+ */
+BOOL
+WriteStatusFailure(LPCWSTR updateDirPath, int errorCode)
+{
+ // The temp file is not removed on failure since there is client code that
+ // will remove it.
+ WCHAR tmpUpdateStatusFilePath[MAX_PATH + 1] = { L'\0' };
+ if (GetTempFileNameW(updateDirPath, L"svc", 0, tmpUpdateStatusFilePath) == 0) {
+ return FALSE;
+ }
+
+ HANDLE tmpStatusFile = CreateFileW(tmpUpdateStatusFilePath, GENERIC_WRITE, 0,
+ nullptr, CREATE_ALWAYS, 0, nullptr);
+ if (tmpStatusFile == INVALID_HANDLE_VALUE) {
+ return FALSE;
+ }
+
+ char failure[32];
+ sprintf(failure, "failed: %d", errorCode);
+ DWORD toWrite = strlen(failure);
+ DWORD wrote;
+ BOOL ok = WriteFile(tmpStatusFile, failure,
+ toWrite, &wrote, nullptr);
+ CloseHandle(tmpStatusFile);
+
+ if (!ok || wrote != toWrite) {
+ return FALSE;
+ }
+
+ WCHAR updateStatusFilePath[MAX_PATH + 1] = { L'\0' };
+ wcsncpy(updateStatusFilePath, updateDirPath, MAX_PATH);
+ if (!PathAppendSafe(updateStatusFilePath, L"update.status")) {
+ return FALSE;
+ }
+
+ if (MoveFileExW(tmpUpdateStatusFilePath, updateStatusFilePath,
+ MOVEFILE_REPLACE_EXISTING) == 0) {
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+#endif
+
+/**
+ * Waits for a service to enter a stopped state.
+ * This function does not stop the service, it just blocks until the service
+ * is stopped.
+ *
+ * @param serviceName The service to wait for.
+ * @param maxWaitSeconds The maximum number of seconds to wait
+ * @return state of the service after a timeout or when stopped.
+ * A value of 255 is returned for an error. Typical values are:
+ * SERVICE_STOPPED 0x00000001
+ * SERVICE_START_PENDING 0x00000002
+ * SERVICE_STOP_PENDING 0x00000003
+ * SERVICE_RUNNING 0x00000004
+ * SERVICE_CONTINUE_PENDING 0x00000005
+ * SERVICE_PAUSE_PENDING 0x00000006
+ * SERVICE_PAUSED 0x00000007
+ * last status not set 0x000000CF
+ * Could no query status 0x000000DF
+ * Could not open service, access denied 0x000000EB
+ * Could not open service, invalid handle 0x000000EC
+ * Could not open service, invalid name 0x000000ED
+ * Could not open service, does not exist 0x000000EE
+ * Could not open service, other error 0x000000EF
+ * Could not open SCM, access denied 0x000000FD
+ * Could not open SCM, database does not exist 0x000000FE;
+ * Could not open SCM, other error 0x000000FF;
+ * Note: The strange choice of error codes above SERVICE_PAUSED are chosen
+ * in case Windows comes out with other service stats higher than 7, they
+ * would likely call it 8 and above. JS code that uses this in TestAUSHelper
+ * only handles values up to 255 so that's why we don't use GetLastError
+ * directly.
+ */
+DWORD
+WaitForServiceStop(LPCWSTR serviceName, DWORD maxWaitSeconds)
+{
+ // 0x000000CF is defined above to be not set
+ DWORD lastServiceState = 0x000000CF;
+
+ // Get a handle to the SCM database.
+ SC_HANDLE serviceManager = OpenSCManager(nullptr, nullptr,
+ SC_MANAGER_CONNECT |
+ SC_MANAGER_ENUMERATE_SERVICE);
+ if (!serviceManager) {
+ DWORD lastError = GetLastError();
+ switch(lastError) {
+ case ERROR_ACCESS_DENIED:
+ return 0x000000FD;
+ case ERROR_DATABASE_DOES_NOT_EXIST:
+ return 0x000000FE;
+ default:
+ return 0x000000FF;
+ }
+ }
+
+ // Get a handle to the service.
+ SC_HANDLE service = OpenServiceW(serviceManager,
+ serviceName,
+ SERVICE_QUERY_STATUS);
+ if (!service) {
+ DWORD lastError = GetLastError();
+ CloseServiceHandle(serviceManager);
+ switch(lastError) {
+ case ERROR_ACCESS_DENIED:
+ return 0x000000EB;
+ case ERROR_INVALID_HANDLE:
+ return 0x000000EC;
+ case ERROR_INVALID_NAME:
+ return 0x000000ED;
+ case ERROR_SERVICE_DOES_NOT_EXIST:
+ return 0x000000EE;
+ default:
+ return 0x000000EF;
+ }
+ }
+
+ DWORD currentWaitMS = 0;
+ SERVICE_STATUS_PROCESS ssp;
+ ssp.dwCurrentState = lastServiceState;
+ while (currentWaitMS < maxWaitSeconds * 1000) {
+ DWORD bytesNeeded;
+ if (!QueryServiceStatusEx(service, SC_STATUS_PROCESS_INFO, (LPBYTE)&ssp,
+ sizeof(SERVICE_STATUS_PROCESS), &bytesNeeded)) {
+ DWORD lastError = GetLastError();
+ switch (lastError) {
+ case ERROR_INVALID_HANDLE:
+ ssp.dwCurrentState = 0x000000D9;
+ break;
+ case ERROR_ACCESS_DENIED:
+ ssp.dwCurrentState = 0x000000DA;
+ break;
+ case ERROR_INSUFFICIENT_BUFFER:
+ ssp.dwCurrentState = 0x000000DB;
+ break;
+ case ERROR_INVALID_PARAMETER:
+ ssp.dwCurrentState = 0x000000DC;
+ break;
+ case ERROR_INVALID_LEVEL:
+ ssp.dwCurrentState = 0x000000DD;
+ break;
+ case ERROR_SHUTDOWN_IN_PROGRESS:
+ ssp.dwCurrentState = 0x000000DE;
+ break;
+ // These 3 errors can occur when the service is not yet stopped but
+ // it is stopping.
+ case ERROR_INVALID_SERVICE_CONTROL:
+ case ERROR_SERVICE_CANNOT_ACCEPT_CTRL:
+ case ERROR_SERVICE_NOT_ACTIVE:
+ currentWaitMS += 50;
+ Sleep(50);
+ continue;
+ default:
+ ssp.dwCurrentState = 0x000000DF;
+ }
+
+ // We couldn't query the status so just break out
+ break;
+ }
+
+ // The service is already in use.
+ if (ssp.dwCurrentState == SERVICE_STOPPED) {
+ break;
+ }
+ currentWaitMS += 50;
+ Sleep(50);
+ }
+
+ lastServiceState = ssp.dwCurrentState;
+ CloseServiceHandle(service);
+ CloseServiceHandle(serviceManager);
+ return lastServiceState;
+}
+
+#ifndef ONLY_SERVICE_LAUNCHING
+
+/**
+ * Determines if there is at least one process running for the specified
+ * application. A match will be found across any session for any user.
+ *
+ * @param process The process to check for existance
+ * @return ERROR_NOT_FOUND if the process was not found
+ * ERROR_SUCCESS if the process was found and there were no errors
+ * Other Win32 system error code for other errors
+**/
+DWORD
+IsProcessRunning(LPCWSTR filename)
+{
+ // Take a snapshot of all processes in the system.
+ HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
+ if (INVALID_HANDLE_VALUE == snapshot) {
+ return GetLastError();
+ }
+
+ PROCESSENTRY32W processEntry;
+ processEntry.dwSize = sizeof(PROCESSENTRY32W);
+ if (!Process32FirstW(snapshot, &processEntry)) {
+ DWORD lastError = GetLastError();
+ CloseHandle(snapshot);
+ return lastError;
+ }
+
+ do {
+ if (wcsicmp(filename, processEntry.szExeFile) == 0) {
+ CloseHandle(snapshot);
+ return ERROR_SUCCESS;
+ }
+ } while (Process32NextW(snapshot, &processEntry));
+ CloseHandle(snapshot);
+ return ERROR_NOT_FOUND;
+}
+
+/**
+ * Waits for the specified applicaiton to exit.
+ *
+ * @param filename The application to wait for.
+ * @param maxSeconds The maximum amount of seconds to wait for all
+ * instances of the application to exit.
+ * @return ERROR_SUCCESS if no instances of the application exist
+ * WAIT_TIMEOUT if the process is still running after maxSeconds.
+ * Any other Win32 system error code.
+*/
+DWORD
+WaitForProcessExit(LPCWSTR filename, DWORD maxSeconds)
+{
+ DWORD applicationRunningError = WAIT_TIMEOUT;
+ for(DWORD i = 0; i < maxSeconds; i++) {
+ DWORD applicationRunningError = IsProcessRunning(filename);
+ if (ERROR_NOT_FOUND == applicationRunningError) {
+ return ERROR_SUCCESS;
+ }
+ Sleep(1000);
+ }
+
+ if (ERROR_SUCCESS == applicationRunningError) {
+ return WAIT_TIMEOUT;
+ }
+
+ return applicationRunningError;
+}
+
+/**
+ * Determines if the fallback key exists or not
+ *
+ * @return TRUE if the fallback key exists and there was no error checking
+*/
+BOOL
+DoesFallbackKeyExist()
+{
+ HKEY testOnlyFallbackKey;
+ if (RegOpenKeyExW(HKEY_LOCAL_MACHINE,
+ TEST_ONLY_FALLBACK_KEY_PATH, 0,
+ KEY_READ | KEY_WOW64_64KEY,
+ &testOnlyFallbackKey) != ERROR_SUCCESS) {
+ return FALSE;
+ }
+
+ RegCloseKey(testOnlyFallbackKey);
+ return TRUE;
+}
+
+#endif
+
+/**
+ * Determines if the file system for the specified file handle is local
+ * @param file path to check the filesystem type for, must be at most MAX_PATH
+ * @param isLocal out parameter which will hold TRUE if the drive is local
+ * @return TRUE if the call succeeded
+*/
+BOOL
+IsLocalFile(LPCWSTR file, BOOL &isLocal)
+{
+ WCHAR rootPath[MAX_PATH + 1] = { L'\0' };
+ if (wcslen(file) > MAX_PATH) {
+ return FALSE;
+ }
+
+ wcsncpy(rootPath, file, MAX_PATH);
+ PathStripToRootW(rootPath);
+ isLocal = GetDriveTypeW(rootPath) == DRIVE_FIXED;
+ return TRUE;
+}
+
+
+/**
+ * Determines the DWORD value of a registry key value
+ *
+ * @param key The base key to where the value name exists
+ * @param valueName The name of the value
+ * @param retValue Out parameter which will hold the value
+ * @return TRUE on success
+*/
+static BOOL
+GetDWORDValue(HKEY key, LPCWSTR valueName, DWORD &retValue)
+{
+ DWORD regDWORDValueSize = sizeof(DWORD);
+ LONG retCode = RegQueryValueExW(key, valueName, 0, nullptr,
+ reinterpret_cast<LPBYTE>(&retValue),
+ &regDWORDValueSize);
+ return ERROR_SUCCESS == retCode;
+}
+
+/**
+ * Determines if the the system's elevation type allows
+ * unprmopted elevation.
+ *
+ * @param isUnpromptedElevation Out parameter which specifies if unprompted
+ * elevation is allowed.
+ * @return TRUE if the user can actually elevate and the value was obtained
+ * successfully.
+*/
+BOOL
+IsUnpromptedElevation(BOOL &isUnpromptedElevation)
+{
+ if (!UACHelper::CanUserElevate()) {
+ return FALSE;
+ }
+
+ LPCWSTR UACBaseRegKey =
+ L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Policies\\System";
+ HKEY baseKey;
+ LONG retCode = RegOpenKeyExW(HKEY_LOCAL_MACHINE,
+ UACBaseRegKey, 0,
+ KEY_READ, &baseKey);
+ if (retCode != ERROR_SUCCESS) {
+ return FALSE;
+ }
+
+ DWORD consent, secureDesktop;
+ BOOL success = GetDWORDValue(baseKey, L"ConsentPromptBehaviorAdmin",
+ consent);
+ success = success &&
+ GetDWORDValue(baseKey, L"PromptOnSecureDesktop", secureDesktop);
+ isUnpromptedElevation = !consent && !secureDesktop;
+
+ RegCloseKey(baseKey);
+ return success;
+}
diff --git a/toolkit/mozapps/update/common/updatehelper.h b/toolkit/mozapps/update/common/updatehelper.h
new file mode 100644
index 000000000..3f010df49
--- /dev/null
+++ b/toolkit/mozapps/update/common/updatehelper.h
@@ -0,0 +1,29 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+BOOL StartServiceUpdate(LPCWSTR installDir);
+DWORD LaunchServiceSoftwareUpdateCommand(int argc, LPCWSTR *argv);
+BOOL WriteStatusFailure(LPCWSTR updateDirPath, int errorCode);
+DWORD WaitForServiceStop(LPCWSTR serviceName, DWORD maxWaitSeconds);
+DWORD WaitForProcessExit(LPCWSTR filename, DWORD maxSeconds);
+DWORD IsProcessRunning(LPCWSTR filename);
+BOOL DoesFallbackKeyExist();
+BOOL IsLocalFile(LPCWSTR file, BOOL &isLocal);
+DWORD StartServiceCommand(int argc, LPCWSTR* argv);
+BOOL IsUnpromptedElevation(BOOL &isUnpromptedElevation);
+
+#define SVC_NAME L"MozillaMaintenance"
+
+#define BASE_SERVICE_REG_KEY \
+ L"SOFTWARE\\Mozilla\\MaintenanceService"
+
+// The test only fallback key, as its name implies, is only present on machines
+// that will use automated tests. Since automated tests always run from a
+// different directory for each test, the presence of this key bypasses the
+// "This is a valid installation directory" check. This key also stores
+// the allowed name and issuer for cert checks so that the cert check
+// code can still be run unchanged.
+#define TEST_ONLY_FALLBACK_KEY_PATH \
+ BASE_SERVICE_REG_KEY L"\\3932ecacee736d366d6436db0f55bce4"
+
diff --git a/toolkit/mozapps/update/common/win_dirent.h b/toolkit/mozapps/update/common/win_dirent.h
new file mode 100644
index 000000000..28f5317ff
--- /dev/null
+++ b/toolkit/mozapps/update/common/win_dirent.h
@@ -0,0 +1,32 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef WINDIRENT_H__
+#define WINDIRENT_H__
+
+#ifndef XP_WIN
+#error This library should only be used on Windows
+#endif
+
+#include <windows.h>
+
+struct DIR {
+ explicit DIR(const WCHAR* path);
+ ~DIR();
+ HANDLE findHandle;
+ WCHAR name[MAX_PATH];
+};
+
+struct dirent {
+ dirent();
+ WCHAR d_name[MAX_PATH];
+};
+
+DIR* opendir(const WCHAR* path);
+int closedir(DIR* dir);
+dirent* readdir(DIR* dir);
+
+#endif // WINDIRENT_H__
diff --git a/toolkit/mozapps/update/content/history.js b/toolkit/mozapps/update/content/history.js
new file mode 100644
index 000000000..32a098de5
--- /dev/null
+++ b/toolkit/mozapps/update/content/history.js
@@ -0,0 +1,70 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+const NS_XUL = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
+
+var gUpdateHistory = {
+ _view: null,
+
+ /**
+ * Initialize the User Interface
+ */
+ onLoad: function() {
+ this._view = document.getElementById("historyItems");
+
+ var um =
+ Components.classes["@mozilla.org/updates/update-manager;1"].
+ getService(Components.interfaces.nsIUpdateManager);
+ var uc = um.updateCount;
+ if (uc) {
+ while (this._view.hasChildNodes())
+ this._view.removeChild(this._view.firstChild);
+
+ var bundle = document.getElementById("updateBundle");
+
+ for (var i = 0; i < uc; ++i) {
+ var update = um.getUpdateAt(i);
+ if (!update || !update.name)
+ continue;
+
+ // Don't display updates that are downloading since they don't have
+ // valid statusText for the UI (bug 485493).
+ if (!update.statusText)
+ continue;
+
+ var element = document.createElementNS(NS_XUL, "update");
+ this._view.appendChild(element);
+ element.name = bundle.getFormattedString("updateFullName",
+ [update.name, update.buildID]);
+ element.type = bundle.getString("updateType_" + update.type);
+ element.installDate = this._formatDate(update.installDate);
+ if (update.detailsURL)
+ element.detailsURL = update.detailsURL;
+ else
+ element.hideDetailsURL = true;
+ element.status = update.statusText;
+ }
+ }
+ var cancelbutton = document.documentElement.getButton("cancel");
+ cancelbutton.focus();
+ },
+
+ /**
+ * Formats a date into human readable form
+ * @param seconds
+ * A date in seconds since 1970 epoch
+ * @returns A human readable date string
+ */
+ _formatDate: function(seconds) {
+ var date = new Date(seconds);
+ const locale = Components.classes["@mozilla.org/chrome/chrome-registry;1"]
+ .getService(Components.interfaces.nsIXULChromeRegistry)
+ .getSelectedLocale("global", true);
+ const dtOptions = { year: 'numeric', month: 'long', day: 'numeric',
+ hour: 'numeric', minute: 'numeric', second: 'numeric' };
+ return date.toLocaleString(locale, dtOptions);
+ }
+};
+
diff --git a/toolkit/mozapps/update/content/history.xul b/toolkit/mozapps/update/content/history.xul
new file mode 100644
index 000000000..92a32b6ef
--- /dev/null
+++ b/toolkit/mozapps/update/content/history.xul
@@ -0,0 +1,39 @@
+<?xml version="1.0"?>
+
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<!DOCTYPE dialog [
+<!ENTITY % historyDTD SYSTEM "chrome://mozapps/locale/update/history.dtd">
+<!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd">
+%historyDTD;
+%brandDTD;
+]>
+
+<?xml-stylesheet href="chrome://global/skin/"?>
+<?xml-stylesheet href="chrome://mozapps/content/update/updates.css"?>
+<?xml-stylesheet href="chrome://mozapps/skin/update/updates.css"?>
+
+<dialog id="history" windowtype="Update:History"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ style="width: 35em;"
+ buttons="cancel"
+ defaultButton="cancel"
+ buttonlabelcancel="&closebutton.label;"
+ title="&history.title;"
+ onload="gUpdateHistory.onLoad();">
+
+ <script type="application/javascript"
+ src="chrome://mozapps/content/update/history.js"/>
+
+ <stringbundle id="updateBundle"
+ src="chrome://mozapps/locale/update/updates.properties"/>
+
+ <label>&history.intro;</label>
+ <separator class="thin"/>
+ <richlistbox id="historyItems" flex="1">
+ <label>&noupdates.label;</label>
+ </richlistbox>
+ <separator class="thin"/>
+</dialog>
diff --git a/toolkit/mozapps/update/content/updates.css b/toolkit/mozapps/update/content/updates.css
new file mode 100644
index 000000000..e83c3be03
--- /dev/null
+++ b/toolkit/mozapps/update/content/updates.css
@@ -0,0 +1,33 @@
+/* Stop animations on pages that aren't on the current page (bug 341749). */
+#updates:not([currentpageid="checking"]) #checkingProgress,
+#updates:not([currentpageid="downloading"]) #downloadProgress {
+ display: none;
+}
+
+/* Hide the wizard's header so the size of the billboard can size the window
+ on creation. A custom header will be used in its place when a header is
+ needed. */
+.wizard-header {
+ display: none;
+}
+
+/* Display the custom header */
+.update-header {
+ display: -moz-box !important;
+}
+
+/* Custom header implementation based on the Wizard's header. This allows the
+ size of the billboard's remotecontent to size the window since it does not
+ have an updateheader on the billboard page. */
+updateheader {
+ -moz-binding: url("chrome://mozapps/content/update/updates.xml#updateheader");
+ display: -moz-box;
+ -moz-box-orient: horizontal;
+}
+
+/* Update History Window */
+update {
+ -moz-binding: url("chrome://mozapps/content/update/updates.xml#update");
+ display: -moz-box;
+ -moz-box-orient: vertical;
+}
diff --git a/toolkit/mozapps/update/content/updates.js b/toolkit/mozapps/update/content/updates.js
new file mode 100644
index 000000000..6e8de7275
--- /dev/null
+++ b/toolkit/mozapps/update/content/updates.js
@@ -0,0 +1,1399 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+'use strict';
+
+/* import-globals-from ../../../content/contentAreaUtils.js */
+
+// Firefox's macBrowserOverlay.xul includes scripts that define Cc, Ci, and Cr
+// so we have to use different names.
+const {classes: CoC, interfaces: CoI, results: CoR, utils: CoU} = Components;
+
+/* globals DownloadUtils, Services, AUSTLMY */
+CoU.import("resource://gre/modules/DownloadUtils.jsm", this);
+CoU.import("resource://gre/modules/Services.jsm", this);
+CoU.import("resource://gre/modules/UpdateTelemetry.jsm", this);
+
+const XMLNS_XUL = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
+
+const PREF_APP_UPDATE_BACKGROUNDERRORS = "app.update.backgroundErrors";
+const PREF_APP_UPDATE_CERT_ERRORS = "app.update.cert.errors";
+const PREF_APP_UPDATE_ELEVATE_NEVER = "app.update.elevate.never";
+const PREF_APP_UPDATE_ENABLED = "app.update.enabled";
+const PREF_APP_UPDATE_LOG = "app.update.log";
+const PREF_APP_UPDATE_NOTIFIEDUNSUPPORTED = "app.update.notifiedUnsupported";
+const PREF_APP_UPDATE_TEST_LOOP = "app.update.test.loop";
+const PREF_APP_UPDATE_URL_MANUAL = "app.update.url.manual";
+
+const PREFBRANCH_APP_UPDATE_NEVER = "app.update.never.";
+
+const UPDATE_TEST_LOOP_INTERVAL = 2000;
+
+const URI_UPDATES_PROPERTIES = "chrome://mozapps/locale/update/updates.properties";
+
+const STATE_DOWNLOADING = "downloading";
+const STATE_PENDING = "pending";
+const STATE_PENDING_SERVICE = "pending-service";
+const STATE_PENDING_ELEVATE = "pending-elevate";
+const STATE_APPLYING = "applying";
+const STATE_APPLIED = "applied";
+const STATE_APPLIED_SERVICE = "applied-service";
+const STATE_SUCCEEDED = "succeeded";
+const STATE_DOWNLOAD_FAILED = "download-failed";
+const STATE_FAILED = "failed";
+
+const SRCEVT_FOREGROUND = 1;
+const SRCEVT_BACKGROUND = 2;
+
+const BACKGROUNDCHECK_MULTIPLE_FAILURES = 110;
+
+var gLogEnabled = false;
+var gUpdatesFoundPageId;
+
+// Notes:
+// 1. use the wizard's goTo method whenever possible to change the wizard
+// page since it is simpler than most other methods and behaves nicely with
+// mochitests.
+// 2. using a page's onPageShow method to then change to a different page will
+// of course call that page's onPageShow method which can make mochitests
+// overly complicated and fragile so avoid doing this if at all possible.
+// This is why a page's next attribute is set prior to the page being shown
+// whenever possible.
+
+/**
+ * Logs a string to the error console.
+ * @param string
+ * The string to write to the error console..
+ */
+function LOG(module, string) {
+ if (gLogEnabled) {
+ dump("*** AUS:UI " + module + ":" + string + "\n");
+ Services.console.logStringMessage("AUS:UI " + module + ":" + string);
+ }
+}
+
+/**
+ * Opens a URL using the event target's url attribute for the URL. This is a
+ * workaround for Bug 263433 which prevents respecting tab browser preferences
+ * for where to open a URL.
+ */
+function openUpdateURL(event) {
+ if (event.button == 0)
+ openURL(event.target.getAttribute("url"));
+}
+
+/**
+ * Gets a preference value, handling the case where there is no default.
+ * @param func
+ * The name of the preference function to call, on nsIPrefBranch
+ * @param preference
+ * The name of the preference
+ * @param defaultValue
+ * The default value to return in the event the preference has
+ * no setting
+ * @returns The value of the preference, or undefined if there was no
+ * user or default value.
+ */
+function getPref(func, preference, defaultValue) {
+ try {
+ return Services.prefs[func](preference);
+ }
+ catch (e) {
+ LOG("General", "getPref - failed to get preference: " + preference);
+ }
+ return defaultValue;
+}
+
+/**
+ * A set of shared data and control functions for the wizard as a whole.
+ */
+var gUpdates = {
+ /**
+ * The nsIUpdate object being used by this window (either for downloading,
+ * notification or both).
+ */
+ update: null,
+
+ /**
+ * The updates.properties <stringbundle> element.
+ */
+ strings: null,
+
+ /**
+ * The Application brandShortName (e.g. "Firefox")
+ */
+ brandName: null,
+
+ /**
+ * The <wizard> element
+ */
+ wiz: null,
+
+ /**
+ * Whether to run the unload handler. This will be set to false when the user
+ * exits the wizard via onWizardCancel or onWizardFinish.
+ */
+ _runUnload: true,
+
+ /**
+ * Submit on close telemtry values for the update wizard.
+ * @param pageID
+ * The page id for the last page displayed.
+ */
+ _submitTelemetry: function(aPageID) {
+ AUSTLMY.pingWizLastPageCode(aPageID);
+ },
+
+ /**
+ * Helper function for setButtons
+ * Resets button to original label & accesskey if string is null.
+ */
+ _setButton: function(button, string) {
+ if (string) {
+ var label = this.getAUSString(string);
+ if (label.indexOf("%S") != -1)
+ label = label.replace(/%S/, this.brandName);
+ button.label = label;
+ button.setAttribute("accesskey",
+ this.getAUSString(string + ".accesskey"));
+ } else {
+ button.label = button.defaultLabel;
+ button.setAttribute("accesskey", button.defaultAccesskey);
+ }
+ },
+
+ /**
+ * Sets the attributes needed for this Wizard's control buttons (labels,
+ * disabled, hidden, etc.)
+ * @param extra1ButtonString
+ * The property in the stringbundle containing the label to put on
+ * the first extra button, or null to hide the first extra button.
+ * @param extra2ButtonString
+ * The property in the stringbundle containing the label to put on
+ * the second extra button, or null to hide the second extra button.
+ * @param nextFinishButtonString
+ * The property in the stringbundle containing the label to put on
+ * the Next / Finish button, or null to hide the button. The Next and
+ * Finish buttons are never displayed at the same time in a wizard
+ * with the the Finish button only being displayed when there are no
+ * additional pages to display in the wizard.
+ * @param canAdvance
+ * true if the wizard can be advanced (e.g. the next / finish button
+ * should be enabled), false otherwise.
+ * @param showCancel
+ * true if the wizard's cancel button should be shown, false
+ * otherwise. If not specified this will default to false.
+ *
+ * Note:
+ * Per Bug 324121 the wizard should not look like a wizard and to accomplish
+ * this the back button is never displayed. This causes the wizard buttons to
+ * be arranged as follows on Windows with the next and finish buttons never
+ * being displayed at the same time.
+ * +--------------------------------------------------------------+
+ * | [ extra1 ] [ extra2 ] [ next or finish ] |
+ * +--------------------------------------------------------------+
+ */
+ setButtons: function(extra1ButtonString, extra2ButtonString,
+ nextFinishButtonString, canAdvance, showCancel) {
+ this.wiz.canAdvance = canAdvance;
+
+ var bnf = this.wiz.getButton(this.wiz.onLastPage ? "finish" : "next");
+ var be1 = this.wiz.getButton("extra1");
+ var be2 = this.wiz.getButton("extra2");
+ var bc = this.wiz.getButton("cancel");
+
+ // Set the labels for the next / finish, extra1, and extra2 buttons
+ this._setButton(bnf, nextFinishButtonString);
+ this._setButton(be1, extra1ButtonString);
+ this._setButton(be2, extra2ButtonString);
+
+ bnf.hidden = bnf.disabled = !nextFinishButtonString;
+ be1.hidden = be1.disabled = !extra1ButtonString;
+ be2.hidden = be2.disabled = !extra2ButtonString;
+ bc.hidden = bc.disabled = !showCancel;
+
+ // Hide and disable the back button each time setButtons is called
+ // (see bug 464765).
+ var btn = this.wiz.getButton("back");
+ btn.hidden = btn.disabled = true;
+
+ // Hide and disable the finish button if not on the last page or the next
+ // button if on the last page each time setButtons is called.
+ btn = this.wiz.getButton(this.wiz.onLastPage ? "next" : "finish");
+ btn.hidden = btn.disabled = true;
+ },
+
+ getAUSString: function(key, strings) {
+ if (strings)
+ return this.strings.getFormattedString(key, strings);
+ return this.strings.getString(key);
+ },
+
+ never: function () {
+ // If the user clicks "No Thanks", we should not prompt them to update to
+ // this version again unless they manually select "Check for Updates..."
+ // which will clear all of the "never" prefs. There are currently two
+ // "never" prefs: the older PREFBRANCH_APP_UPDATE_NEVER as well as the
+ // OSX-only PREF_APP_UPDATE_ELEVATE_NEVER. We set both of these prefs (if
+ // applicable) to ensure that we don't prompt the user regardless of which
+ // pref is checked.
+ let neverPrefName = PREFBRANCH_APP_UPDATE_NEVER + this.update.appVersion;
+ Services.prefs.setBoolPref(neverPrefName, true);
+ let aus = CoC["@mozilla.org/updates/update-service;1"].
+ getService(CoI.nsIApplicationUpdateService);
+ if (aus.elevationRequired) {
+ Services.prefs.setCharPref(PREF_APP_UPDATE_ELEVATE_NEVER,
+ this.update.appVersion);
+ }
+ },
+
+ /**
+ * A hash of |pageid| attribute to page object. Can be used to dispatch
+ * function calls to the appropriate page.
+ */
+ _pages: { },
+
+ /**
+ * Called when the user presses the "Finish" button on the wizard, dispatches
+ * the function call to the selected page.
+ */
+ onWizardFinish: function() {
+ this._runUnload = false;
+ var pageid = document.documentElement.currentPage.pageid;
+ if ("onWizardFinish" in this._pages[pageid])
+ this._pages[pageid].onWizardFinish();
+ this._submitTelemetry(pageid);
+ },
+
+ /**
+ * Called when the user presses the "Cancel" button on the wizard, dispatches
+ * the function call to the selected page.
+ */
+ onWizardCancel: function() {
+ this._runUnload = false;
+ var pageid = document.documentElement.currentPage.pageid;
+ if ("onWizardCancel" in this._pages[pageid])
+ this._pages[pageid].onWizardCancel();
+ this._submitTelemetry(pageid);
+ },
+
+ /**
+ * Called when the user presses the "Next" button on the wizard, dispatches
+ * the function call to the selected page.
+ */
+ onWizardNext: function() {
+ var cp = document.documentElement.currentPage;
+ if (!cp)
+ return;
+ var pageid = cp.pageid;
+ if ("onWizardNext" in this._pages[pageid])
+ this._pages[pageid].onWizardNext();
+ },
+
+ /**
+ * The checking process that spawned this update UI. There are two types:
+ * SRCEVT_FOREGROUND:
+ * Some user-generated event caused this UI to appear, e.g. the Help
+ * menu item or the button in preferences. When in this mode, the UI
+ * should remain active for the duration of the download.
+ * SRCEVT_BACKGROUND:
+ * A background update check caused this UI to appear, probably because
+ * the user has the app.update.auto preference set to false.
+ */
+ sourceEvent: SRCEVT_FOREGROUND,
+
+ /**
+ * Helper function for onLoad
+ * Saves default button label & accesskey for use by _setButton
+ */
+ _cacheButtonStrings: function (buttonName) {
+ var button = this.wiz.getButton(buttonName);
+ button.defaultLabel = button.label;
+ button.defaultAccesskey = button.getAttribute("accesskey");
+ },
+
+ /**
+ * Called when the wizard UI is loaded.
+ */
+ onLoad: function() {
+ this.wiz = document.documentElement;
+
+ gLogEnabled = getPref("getBoolPref", PREF_APP_UPDATE_LOG, false);
+
+ this.strings = document.getElementById("updateStrings");
+ var brandStrings = document.getElementById("brandStrings");
+ this.brandName = brandStrings.getString("brandShortName");
+
+ var pages = this.wiz.childNodes;
+ for (var i = 0; i < pages.length; ++i) {
+ var page = pages[i];
+ if (page.localName == "wizardpage")
+ this._pages[page.pageid] = eval(page.getAttribute("object"));
+ }
+
+ // Cache the standard button labels in case we need to restore them
+ this._cacheButtonStrings("next");
+ this._cacheButtonStrings("finish");
+ this._cacheButtonStrings("extra1");
+ this._cacheButtonStrings("extra2");
+
+ // Advance to the Start page.
+ this.getStartPageID(function(startPageID) {
+ LOG("gUpdates", "onLoad - setting current page to startpage " + startPageID);
+ gUpdates.wiz.currentPage = document.getElementById(startPageID);
+ });
+ },
+
+ /**
+ * Called when the wizard UI is unloaded.
+ */
+ onUnload: function() {
+ if (this._runUnload) {
+ var cp = this.wiz.currentPage;
+ if (cp.pageid != "finished" && cp.pageid != "finishedBackground")
+ this.onWizardCancel();
+ }
+ },
+
+ /**
+ * Gets the ID of the <wizardpage> object that should be displayed first. This
+ * is an asynchronous method that passes the resulting object to a callback
+ * function.
+ *
+ * This is determined by how we were called by the update prompt:
+ *
+ * Prompt Method: Arg0: Update State: Src Event: Failed: Result:
+ * showUpdateAvailable nsIUpdate obj -- background -- updatesfoundbasic
+ * showUpdateDownloaded nsIUpdate obj pending background -- finishedBackground
+ * showUpdateError nsIUpdate obj failed either partial errorpatching
+ * showUpdateError nsIUpdate obj failed either complete errors
+ * checkForUpdates null -- foreground -- checking
+ * checkForUpdates null downloading foreground -- downloading
+ *
+ * @param aCallback
+ * A callback to pass the <wizardpage> object to be displayed first to.
+ */
+ getStartPageID: function(aCallback) {
+ if ("arguments" in window && window.arguments[0]) {
+ var arg0 = window.arguments[0];
+ if (arg0 instanceof CoI.nsIUpdate) {
+ // If the first argument is a nsIUpdate object, we are notifying the
+ // user that the background checking found an update that requires
+ // their permission to install, and it's ready for download.
+ this.setUpdate(arg0);
+ if (this.update.errorCode == BACKGROUNDCHECK_MULTIPLE_FAILURES) {
+ aCallback("errorextra");
+ return;
+ }
+
+ if (this.update.unsupported) {
+ aCallback("unsupported");
+ return;
+ }
+
+ var p = this.update.selectedPatch;
+ if (p) {
+ let state = p.state;
+ let patchFailed = this.update.getProperty("patchingFailed");
+ if (patchFailed) {
+ if (patchFailed != "partial" || this.update.patchCount != 2) {
+ // If the complete patch failed, which is far less likely, show
+ // the error text held by the update object in the generic errors
+ // page, triggered by the |STATE_DOWNLOAD_FAILED| state. This also
+ // handles the case when an elevation was cancelled on Mac OS X.
+ state = STATE_DOWNLOAD_FAILED;
+ } else {
+ // If the system failed to apply the partial patch, show the
+ // screen which best describes this condition, which is triggered
+ // by the |STATE_FAILED| state.
+ state = STATE_FAILED;
+ }
+ }
+
+ // Now select the best page to start with, given the current state of
+ // the Update.
+ switch (state) {
+ case STATE_PENDING:
+ case STATE_PENDING_SERVICE:
+ case STATE_PENDING_ELEVATE:
+ case STATE_APPLIED:
+ case STATE_APPLIED_SERVICE:
+ this.sourceEvent = SRCEVT_BACKGROUND;
+ aCallback("finishedBackground");
+ return;
+ case STATE_DOWNLOADING:
+ aCallback("downloading");
+ return;
+ case STATE_FAILED:
+ window.getAttention();
+ aCallback("errorpatching");
+ return;
+ case STATE_DOWNLOAD_FAILED:
+ case STATE_APPLYING:
+ aCallback("errors");
+ return;
+ }
+ }
+
+ let aus = CoC["@mozilla.org/updates/update-service;1"].
+ getService(CoI.nsIApplicationUpdateService);
+ if (!aus.canApplyUpdates) {
+ aCallback("manualUpdate");
+ return;
+ }
+
+ aCallback(this.updatesFoundPageId);
+ return;
+ }
+ }
+ else {
+ var um = CoC["@mozilla.org/updates/update-manager;1"].
+ getService(CoI.nsIUpdateManager);
+ if (um.activeUpdate) {
+ this.setUpdate(um.activeUpdate);
+ aCallback("downloading");
+ return;
+ }
+ }
+ aCallback("checking");
+ },
+
+ /**
+ * Returns the string page ID for the appropriate updates found page based
+ * on the update's metadata.
+ */
+ get updatesFoundPageId() {
+ if (gUpdatesFoundPageId)
+ return gUpdatesFoundPageId;
+ return gUpdatesFoundPageId = "updatesfoundbasic";
+ },
+
+ /**
+ * Sets the Update object for this wizard
+ * @param update
+ * The update object
+ */
+ setUpdate: function(update) {
+ this.update = update;
+ if (this.update)
+ this.update.QueryInterface(CoI.nsIWritablePropertyBag);
+ }
+};
+
+/**
+ * The "Checking for Updates" page. Provides feedback on the update checking
+ * process.
+ */
+var gCheckingPage = {
+ /**
+ * The nsIUpdateChecker that is currently checking for updates. We hold onto
+ * this so we can cancel the update check if the user closes the window.
+ */
+ _checker: null,
+
+ /**
+ * Initialize
+ */
+ onPageShow: function() {
+ gUpdates.setButtons(null, null, null, false, true);
+ gUpdates.wiz.getButton("cancel").focus();
+
+ // Clear all of the "never" prefs to handle the scenario where the user
+ // clicked "never" for an update, selected "Check for Updates...", and
+ // then canceled. If we don't clear the "never" prefs future
+ // notifications will never happen.
+ Services.prefs.deleteBranch(PREFBRANCH_APP_UPDATE_NEVER);
+ if (Services.prefs.prefHasUserValue(PREF_APP_UPDATE_ELEVATE_NEVER)) {
+ Services.prefs.clearUserPref(PREF_APP_UPDATE_ELEVATE_NEVER);
+ }
+
+ // The user will be notified if there is an error so clear the background
+ // check error count.
+ if (Services.prefs.prefHasUserValue(PREF_APP_UPDATE_BACKGROUNDERRORS)) {
+ Services.prefs.clearUserPref(PREF_APP_UPDATE_BACKGROUNDERRORS);
+ }
+
+ // The preference will be set back to true if the system is still
+ // unsupported.
+ if (Services.prefs.prefHasUserValue(PREF_APP_UPDATE_NOTIFIEDUNSUPPORTED)) {
+ Services.prefs.clearUserPref(PREF_APP_UPDATE_NOTIFIEDUNSUPPORTED);
+ }
+
+ this._checker = CoC["@mozilla.org/updates/update-checker;1"].
+ createInstance(CoI.nsIUpdateChecker);
+ this._checker.checkForUpdates(this.updateListener, true);
+ },
+
+ /**
+ * The user has closed the window, either by pressing cancel or using a Window
+ * Manager control, so stop checking for updates.
+ */
+ onWizardCancel: function() {
+ this._checker.stopChecking(CoI.nsIUpdateChecker.CURRENT_CHECK);
+ },
+
+ /**
+ * An object implementing nsIUpdateCheckListener that is notified as the
+ * update check commences.
+ */
+ updateListener: {
+ /**
+ * See nsIUpdateCheckListener
+ */
+ onCheckComplete: function(request, updates, updateCount) {
+ var aus = CoC["@mozilla.org/updates/update-service;1"].
+ getService(CoI.nsIApplicationUpdateService);
+ gUpdates.setUpdate(aus.selectUpdate(updates, updates.length));
+ if (gUpdates.update) {
+ LOG("gCheckingPage", "onCheckComplete - update found");
+ if (gUpdates.update.unsupported) {
+ gUpdates.wiz.goTo("unsupported");
+ return;
+ }
+
+ if (!aus.canApplyUpdates || gUpdates.update.elevationFailure) {
+ // Prevent multiple notifications for the same update when the user is
+ // unable to apply updates.
+ gUpdates.never();
+ gUpdates.wiz.goTo("manualUpdate");
+ return;
+ }
+
+ gUpdates.wiz.goTo(gUpdates.updatesFoundPageId);
+ return;
+ }
+
+ LOG("gCheckingPage", "onCheckComplete - no update found");
+ gUpdates.wiz.goTo("noupdatesfound");
+ },
+
+ /**
+ * See nsIUpdateCheckListener
+ */
+ onError: function(request, update) {
+ LOG("gCheckingPage", "onError - proceeding to error page");
+ gUpdates.setUpdate(update);
+ gUpdates.wiz.goTo("errors");
+ },
+
+ /**
+ * See nsISupports.idl
+ */
+ QueryInterface: function(aIID) {
+ if (!aIID.equals(CoI.nsIUpdateCheckListener) &&
+ !aIID.equals(CoI.nsISupports))
+ throw CoR.NS_ERROR_NO_INTERFACE;
+ return this;
+ }
+ }
+};
+
+/**
+ * The "No Updates Are Available" page
+ */
+var gNoUpdatesPage = {
+ /**
+ * Initialize
+ */
+ onPageShow: function() {
+ LOG("gNoUpdatesPage", "onPageShow - could not select an appropriate " +
+ "update. Either there were no updates or |selectUpdate| failed");
+
+ if (getPref("getBoolPref", PREF_APP_UPDATE_ENABLED, true))
+ document.getElementById("noUpdatesAutoEnabled").hidden = false;
+ else
+ document.getElementById("noUpdatesAutoDisabled").hidden = false;
+
+ gUpdates.setButtons(null, null, "okButton", true);
+ gUpdates.wiz.getButton("finish").focus();
+ }
+};
+
+/**
+ * The "Unable to Update" page. Provides the user information about why they
+ * were unable to update and a manual download url.
+ */
+var gManualUpdatePage = {
+ onPageShow: function() {
+ var manualURL = Services.urlFormatter.formatURLPref(PREF_APP_UPDATE_URL_MANUAL);
+ var manualUpdateLinkLabel = document.getElementById("manualUpdateLinkLabel");
+ manualUpdateLinkLabel.value = manualURL;
+ manualUpdateLinkLabel.setAttribute("url", manualURL);
+
+ gUpdates.setButtons(null, null, "okButton", true);
+ gUpdates.wiz.getButton("finish").focus();
+ }
+};
+
+/**
+ * The "System Unsupported" page. Provides the user with information about their
+ * system no longer being supported and an url for more information.
+ */
+var gUnsupportedPage = {
+ onPageShow: function() {
+ Services.prefs.setBoolPref(PREF_APP_UPDATE_NOTIFIEDUNSUPPORTED, true);
+ if (gUpdates.update.detailsURL) {
+ let unsupportedLinkLabel = document.getElementById("unsupportedLinkLabel");
+ unsupportedLinkLabel.setAttribute("url", gUpdates.update.detailsURL);
+ }
+
+ gUpdates.setButtons(null, null, "okButton", true);
+ gUpdates.wiz.getButton("finish").focus();
+ }
+};
+
+/**
+ * The "Updates Are Available" page. Provides the user information about the
+ * available update.
+ */
+var gUpdatesFoundBasicPage = {
+ /**
+ * Initialize
+ */
+ onPageShow: function() {
+ gUpdates.wiz.canRewind = false;
+ var update = gUpdates.update;
+ gUpdates.setButtons("askLaterButton",
+ update.showNeverForVersion ? "noThanksButton" : null,
+ "updateButton_" + update.type, true);
+ var btn = gUpdates.wiz.getButton("next");
+ btn.focus();
+
+ var updateName = update.name;
+ if (update.channel == "nightly") {
+ updateName = gUpdates.getAUSString("updateNightlyName",
+ [gUpdates.brandName,
+ update.displayVersion,
+ update.buildID]);
+ }
+ var updateNameElement = document.getElementById("updateName");
+ updateNameElement.value = updateName;
+
+ var introText = gUpdates.getAUSString("intro_" + update.type,
+ [gUpdates.brandName, update.displayVersion]);
+ var introElem = document.getElementById("updatesFoundInto");
+ introElem.setAttribute("severity", update.type);
+ introElem.textContent = introText;
+
+ var updateMoreInfoURL = document.getElementById("updateMoreInfoURL");
+ if (update.detailsURL)
+ updateMoreInfoURL.setAttribute("url", update.detailsURL);
+ else
+ updateMoreInfoURL.hidden = true;
+
+ var updateTitle = gUpdates.getAUSString("updatesfound_" + update.type +
+ ".title");
+ document.getElementById("updatesFoundBasicHeader").setAttribute("label", updateTitle);
+ },
+
+ onExtra1: function() {
+ gUpdates.wiz.cancel();
+ },
+
+ onExtra2: function() {
+ gUpdates.never();
+ gUpdates.wiz.cancel();
+ }
+};
+
+/**
+ * The "Update is Downloading" page - provides feedback for the download
+ * process plus a pause/resume UI
+ */
+var gDownloadingPage = {
+ /**
+ * DOM Elements
+ */
+ _downloadStatus: null,
+ _downloadProgress: null,
+ _pauseButton: null,
+
+ /**
+ * Whether or not we are currently paused
+ */
+ _paused: false,
+
+ /**
+ * Label cache to hold the 'Connecting' string
+ */
+ _label_downloadStatus: null,
+
+ /**
+ * Member variables for updating download status
+ */
+ _lastSec: Infinity,
+ _startTime: null,
+ _pausedStatus: "",
+
+ _hiding: false,
+
+ /**
+ * Have we registered an observer for a background update being staged
+ */
+ _updateApplyingObserver: false,
+
+ /**
+ * Initialize
+ */
+ onPageShow: function() {
+ this._downloadStatus = document.getElementById("downloadStatus");
+ this._downloadProgress = document.getElementById("downloadProgress");
+ this._pauseButton = document.getElementById("pauseButton");
+ this._label_downloadStatus = this._downloadStatus.textContent;
+
+ this._pauseButton.setAttribute("tooltiptext",
+ gUpdates.getAUSString("pauseButtonPause"));
+
+ // move focus to the pause/resume button and then disable it (bug #353177)
+ this._pauseButton.focus();
+ this._pauseButton.disabled = true;
+
+ var aus = CoC["@mozilla.org/updates/update-service;1"].
+ getService(CoI.nsIApplicationUpdateService);
+
+ var um = CoC["@mozilla.org/updates/update-manager;1"].
+ getService(CoI.nsIUpdateManager);
+ var activeUpdate = um.activeUpdate;
+ if (activeUpdate) {
+ gUpdates.setUpdate(activeUpdate);
+
+ // It's possible the update has already been downloaded and is being
+ // applied by the time this page is shown, depending on how fast the
+ // download goes and how quickly the 'next' button is clicked to get here.
+ if (activeUpdate.state == STATE_PENDING ||
+ activeUpdate.state == STATE_PENDING_ELEVATE ||
+ activeUpdate.state == STATE_PENDING_SERVICE) {
+ if (!activeUpdate.getProperty("stagingFailed")) {
+ gUpdates.setButtons("hideButton", null, null, false);
+ gUpdates.wiz.getButton("extra1").focus();
+
+ this._setUpdateApplying();
+ return;
+ }
+
+ gUpdates.wiz.goTo("finished");
+ return;
+ }
+ }
+
+ if (!gUpdates.update) {
+ LOG("gDownloadingPage", "onPageShow - no valid update to download?!");
+ return;
+ }
+
+ this._startTime = Date.now();
+
+ try {
+ // Say that this was a foreground download, not a background download,
+ // since the user cared enough to look in on this process.
+ gUpdates.update.QueryInterface(CoI.nsIWritablePropertyBag);
+ gUpdates.update.setProperty("foregroundDownload", "true");
+
+ // Pause any active background download and restart it as a foreground
+ // download.
+ aus.pauseDownload();
+ var state = aus.downloadUpdate(gUpdates.update, false);
+ if (state == "failed") {
+ // We've tried as hard as we could to download a valid update -
+ // we fell back from a partial patch to a complete patch and even
+ // then we couldn't validate. Show a validation error with instructions
+ // on how to manually update.
+ this.cleanUp();
+ gUpdates.wiz.goTo("errors");
+ return;
+ }
+ // Add this UI as a listener for active downloads
+ aus.addDownloadListener(this);
+
+ if (activeUpdate)
+ this._setUIState(!aus.isDownloading);
+ }
+ catch (e) {
+ LOG("gDownloadingPage", "onPageShow - error: " + e);
+ }
+
+ gUpdates.setButtons("hideButton", null, null, false);
+ gUpdates.wiz.getButton("extra1").focus();
+ },
+
+ /**
+ * Updates the text status message
+ */
+ _setStatus: function(status) {
+ // Don't bother setting the same text more than once. This can happen
+ // due to the asynchronous behavior of the downloader.
+ if (this._downloadStatus.textContent == status)
+ return;
+ while (this._downloadStatus.hasChildNodes())
+ this._downloadStatus.removeChild(this._downloadStatus.firstChild);
+ this._downloadStatus.appendChild(document.createTextNode(status));
+ },
+
+ /**
+ * Update download progress status to show time left, speed, and progress.
+ * Also updates the status needed for pausing the download.
+ *
+ * @param aCurr
+ * Current number of bytes transferred
+ * @param aMax
+ * Total file size of the download
+ * @return Current active download status
+ */
+ _updateDownloadStatus: function(aCurr, aMax) {
+ let status;
+
+ // Get the download time left and progress
+ let rate = aCurr / (Date.now() - this._startTime) * 1000;
+ [status, this._lastSec] =
+ DownloadUtils.getDownloadStatus(aCurr, aMax, rate, this._lastSec);
+
+ // Get the download progress for pausing
+ this._pausedStatus = DownloadUtils.getTransferTotal(aCurr, aMax);
+
+ return status;
+ },
+
+ /**
+ * Adjust UI to suit a certain state of paused-ness
+ * @param paused
+ * Whether or not the download is paused
+ */
+ _setUIState: function(paused) {
+ var u = gUpdates.update;
+ if (paused) {
+ if (this._downloadProgress.mode != "normal")
+ this._downloadProgress.mode = "normal";
+ this._pauseButton.setAttribute("tooltiptext",
+ gUpdates.getAUSString("pauseButtonResume"));
+ this._pauseButton.setAttribute("paused", "true");
+ var p = u.selectedPatch.QueryInterface(CoI.nsIPropertyBag);
+ var status = p.getProperty("status");
+ if (status) {
+ let pausedStatus = gUpdates.getAUSString("downloadPausedStatus", [status]);
+ this._setStatus(pausedStatus);
+ }
+ }
+ else {
+ if (this._downloadProgress.mode != "undetermined")
+ this._downloadProgress.mode = "undetermined";
+ this._pauseButton.setAttribute("paused", "false");
+ this._pauseButton.setAttribute("tooltiptext",
+ gUpdates.getAUSString("pauseButtonPause"));
+ this._setStatus(this._label_downloadStatus);
+ }
+ },
+
+ /**
+ * Wait for an update being staged in the background.
+ */
+ _setUpdateApplying: function() {
+ this._downloadProgress.mode = "undetermined";
+ this._pauseButton.hidden = true;
+ let applyingStatus = gUpdates.getAUSString("applyingUpdate");
+ this._setStatus(applyingStatus);
+
+ Services.obs.addObserver(this, "update-staged", false);
+ this._updateApplyingObserver = true;
+ },
+
+ /**
+ * Clean up the listener and observer registered for the wizard.
+ */
+ cleanUp: function() {
+ var aus = CoC["@mozilla.org/updates/update-service;1"].
+ getService(CoI.nsIApplicationUpdateService);
+ aus.removeDownloadListener(this);
+
+ if (this._updateApplyingObserver) {
+ Services.obs.removeObserver(this, "update-staged");
+ this._updateApplyingObserver = false;
+ }
+ },
+
+ /**
+ * When the user clicks the Pause/Resume button
+ */
+ onPause: function() {
+ var aus = CoC["@mozilla.org/updates/update-service;1"].
+ getService(CoI.nsIApplicationUpdateService);
+ if (this._paused)
+ aus.downloadUpdate(gUpdates.update, false);
+ else {
+ var patch = gUpdates.update.selectedPatch;
+ patch.QueryInterface(CoI.nsIWritablePropertyBag);
+ patch.setProperty("status", this._pausedStatus);
+ aus.pauseDownload();
+ }
+ this._paused = !this._paused;
+
+ // Update the UI
+ this._setUIState(this._paused);
+ },
+
+ /**
+ * When the user has closed the window using a Window Manager control (this
+ * page doesn't have a cancel button) cancel the update in progress.
+ */
+ onWizardCancel: function() {
+ if (this._hiding)
+ return;
+
+ this.cleanUp();
+ },
+
+ /**
+ * When the user closes the Wizard UI by clicking the Hide button
+ */
+ onHide: function() {
+ // Set _hiding to true to prevent onWizardCancel from cancelling the update
+ // that is in progress.
+ this._hiding = true;
+
+ // Remove ourself as a download listener so that we don't continue to be
+ // fed progress and state notifications after the UI we're updating has
+ // gone away.
+ this.cleanUp();
+
+ var aus = CoC["@mozilla.org/updates/update-service;1"].
+ getService(CoI.nsIApplicationUpdateService);
+ var um = CoC["@mozilla.org/updates/update-manager;1"].
+ getService(CoI.nsIUpdateManager);
+ um.activeUpdate = gUpdates.update;
+
+ // If the download was paused by the user, ask the user if they want to
+ // have the update resume in the background.
+ var downloadInBackground = true;
+ if (this._paused) {
+ var title = gUpdates.getAUSString("resumePausedAfterCloseTitle");
+ var message = gUpdates.getAUSString("resumePausedAfterCloseMsg",
+ [gUpdates.brandName]);
+ var ps = Services.prompt;
+ var flags = ps.STD_YES_NO_BUTTONS;
+ // Focus the software update wizard before prompting. This will raise
+ // the software update wizard if it is minimized making it more obvious
+ // what the prompt is for and will solve the problem of windows
+ // obscuring the prompt. See bug #350299 for more details.
+ window.focus();
+ var rv = ps.confirmEx(window, title, message, flags, null, null, null,
+ null, { });
+ if (rv == CoI.nsIPromptService.BUTTON_POS_0)
+ downloadInBackground = false;
+ }
+ if (downloadInBackground) {
+ // Continue download in the background at full speed.
+ LOG("gDownloadingPage", "onHide - continuing download in background " +
+ "at full speed");
+ aus.downloadUpdate(gUpdates.update, false);
+ }
+ gUpdates.wiz.cancel();
+ },
+
+ /**
+ * When the data transfer begins
+ * @param request
+ * The nsIRequest object for the transfer
+ * @param context
+ * Additional data
+ */
+ onStartRequest: function(request, context) {
+ // This !paused test is necessary because onStartRequest may fire after
+ // the download was paused (for those speedy clickers...)
+ if (this._paused)
+ return;
+
+ if (this._downloadProgress.mode != "undetermined")
+ this._downloadProgress.mode = "undetermined";
+ this._setStatus(this._label_downloadStatus);
+ },
+
+ /**
+ * When new data has been downloaded
+ * @param request
+ * The nsIRequest object for the transfer
+ * @param context
+ * Additional data
+ * @param progress
+ * The current number of bytes transferred
+ * @param maxProgress
+ * The total number of bytes that must be transferred
+ */
+ onProgress: function(request, context, progress, maxProgress) {
+ let status = this._updateDownloadStatus(progress, maxProgress);
+ var currentProgress = Math.round(100 * (progress / maxProgress));
+
+ var p = gUpdates.update.selectedPatch;
+ p.QueryInterface(CoI.nsIWritablePropertyBag);
+ p.setProperty("progress", currentProgress);
+ p.setProperty("status", status);
+
+ // This !paused test is necessary because onProgress may fire after
+ // the download was paused (for those speedy clickers...)
+ if (this._paused)
+ return;
+
+ if (this._downloadProgress.mode != "normal")
+ this._downloadProgress.mode = "normal";
+ if (this._downloadProgress.value != currentProgress)
+ this._downloadProgress.value = currentProgress;
+ if (this._pauseButton.disabled)
+ this._pauseButton.disabled = false;
+
+ // If the update has completed downloading and the download status contains
+ // the original text return early to avoid an assertion in debug builds.
+ // Since the page will advance immmediately due to the update completing the
+ // download updating the status is not important.
+ // nsTextFrame::GetTrimmedOffsets 'Can only call this on frames that have
+ // been reflowed'.
+ if (progress == maxProgress &&
+ this._downloadStatus.textContent == this._label_downloadStatus)
+ return;
+
+ this._setStatus(status);
+ },
+
+ /**
+ * When we have new status text
+ * @param request
+ * The nsIRequest object for the transfer
+ * @param context
+ * Additional data
+ * @param status
+ * A status code
+ * @param statusText
+ * Human readable version of |status|
+ */
+ onStatus: function(request, context, status, statusText) {
+ this._setStatus(statusText);
+ },
+
+ /**
+ * When data transfer ceases
+ * @param request
+ * The nsIRequest object for the transfer
+ * @param context
+ * Additional data
+ * @param status
+ * Status code containing the reason for the cessation.
+ */
+ onStopRequest: function(request, context, status) {
+ if (this._downloadProgress.mode != "normal")
+ this._downloadProgress.mode = "normal";
+
+ var u = gUpdates.update;
+ switch (status) {
+ case CoR.NS_ERROR_CORRUPTED_CONTENT:
+ case CoR.NS_ERROR_UNEXPECTED:
+ if (u.selectedPatch.state == STATE_DOWNLOAD_FAILED &&
+ (u.isCompleteUpdate || u.patchCount != 2)) {
+ // Verification error of complete patch, informational text is held in
+ // the update object.
+ this.cleanUp();
+ gUpdates.wiz.goTo("errors");
+ break;
+ }
+ // Verification failed for a partial patch, complete patch is now
+ // downloading so return early and do NOT remove the download listener!
+
+ // Reset the progress meter to "undertermined" mode so that we don't
+ // show old progress for the new download of the "complete" patch.
+ this._downloadProgress.mode = "undetermined";
+ this._pauseButton.disabled = true;
+ document.getElementById("verificationFailed").hidden = false;
+ break;
+ case CoR.NS_BINDING_ABORTED:
+ LOG("gDownloadingPage", "onStopRequest - pausing download");
+ // Do not remove UI listener since the user may resume downloading again.
+ break;
+ case CoR.NS_OK:
+ LOG("gDownloadingPage", "onStopRequest - patch verification succeeded");
+ // If the background update pref is set, we should wait until the update
+ // is actually staged in the background.
+ let aus = CoC["@mozilla.org/updates/update-service;1"].
+ getService(CoI.nsIApplicationUpdateService);
+ if (aus.canStageUpdates) {
+ this._setUpdateApplying();
+ } else {
+ this.cleanUp();
+ gUpdates.wiz.goTo("finished");
+ }
+ break;
+ default:
+ LOG("gDownloadingPage", "onStopRequest - transfer failed");
+ // Some kind of transfer error, die.
+ this.cleanUp();
+ gUpdates.wiz.goTo("errors");
+ break;
+ }
+ },
+
+ /**
+ * See nsIObserver.idl
+ */
+ observe: function(aSubject, aTopic, aData) {
+ if (aTopic == "update-staged") {
+ if (aData == STATE_DOWNLOADING) {
+ // We've fallen back to downloding the full update because the
+ // partial update failed to get staged in the background.
+ this._setStatus("downloading");
+ return;
+ }
+ this.cleanUp();
+ if (aData == STATE_APPLIED ||
+ aData == STATE_APPLIED_SERVICE ||
+ aData == STATE_PENDING ||
+ aData == STATE_PENDING_SERVICE ||
+ aData == STATE_PENDING_ELEVATE) {
+ // If the update is successfully applied, or if the updater has
+ // fallen back to non-staged updates, go to the finish page.
+ gUpdates.wiz.goTo("finished");
+ } else {
+ gUpdates.wiz.goTo("errors");
+ }
+ }
+ },
+
+ /**
+ * See nsISupports.idl
+ */
+ QueryInterface: function(iid) {
+ if (!iid.equals(CoI.nsIRequestObserver) &&
+ !iid.equals(CoI.nsIProgressEventSink) &&
+ !iid.equals(CoI.nsIObserver) &&
+ !iid.equals(CoI.nsISupports))
+ throw CoR.NS_ERROR_NO_INTERFACE;
+ return this;
+ }
+};
+
+/**
+ * The "There was an error during the update" page.
+ */
+var gErrorsPage = {
+ /**
+ * Initialize
+ */
+ onPageShow: function() {
+ gUpdates.setButtons(null, null, "okButton", true);
+ gUpdates.wiz.getButton("finish").focus();
+
+ var statusText = gUpdates.update.statusText;
+ LOG("gErrorsPage", "onPageShow - update.statusText: " + statusText);
+
+ var errorReason = document.getElementById("errorReason");
+ errorReason.value = statusText;
+ var manualURL = Services.urlFormatter.formatURLPref(PREF_APP_UPDATE_URL_MANUAL);
+ var errorLinkLabel = document.getElementById("errorLinkLabel");
+ errorLinkLabel.value = manualURL;
+ errorLinkLabel.setAttribute("url", manualURL);
+ }
+};
+
+/**
+ * The page shown when there is a background check or a certificate attribute
+ * error.
+ */
+var gErrorExtraPage = {
+ /**
+ * Initialize
+ */
+ onPageShow: function() {
+ gUpdates.setButtons(null, null, "okButton", true);
+ gUpdates.wiz.getButton("finish").focus();
+
+ if (Services.prefs.prefHasUserValue(PREF_APP_UPDATE_BACKGROUNDERRORS)) {
+ Services.prefs.clearUserPref(PREF_APP_UPDATE_BACKGROUNDERRORS);
+ }
+
+ document.getElementById("genericBackgroundErrorLabel").hidden = false;
+ let manualURL = Services.urlFormatter.formatURLPref(PREF_APP_UPDATE_URL_MANUAL);
+ let errorLinkLabel = document.getElementById("errorExtraLinkLabel");
+ errorLinkLabel.value = manualURL;
+ errorLinkLabel.setAttribute("url", manualURL);
+ }
+};
+
+/**
+ * The "There was an error applying a partial patch" page.
+ */
+var gErrorPatchingPage = {
+ /**
+ * Initialize
+ */
+ onPageShow: function() {
+ gUpdates.setButtons(null, null, "okButton", true);
+ },
+
+ onWizardNext: function() {
+ switch (gUpdates.update.selectedPatch.state) {
+ case STATE_APPLIED:
+ case STATE_APPLIED_SERVICE:
+ gUpdates.wiz.goTo("finished");
+ break;
+ case STATE_PENDING:
+ case STATE_PENDING_SERVICE:
+ let aus = CoC["@mozilla.org/updates/update-service;1"].
+ getService(CoI.nsIApplicationUpdateService);
+ if (!aus.canStageUpdates) {
+ gUpdates.wiz.goTo("finished");
+ break;
+ }
+ // intentional fallthrough
+ case STATE_DOWNLOADING:
+ gUpdates.wiz.goTo("downloading");
+ break;
+ case STATE_DOWNLOAD_FAILED:
+ gUpdates.wiz.goTo("errors");
+ break;
+ }
+ }
+};
+
+/**
+ * The "Update has been downloaded" page. Shows information about what
+ * was downloaded.
+ */
+var gFinishedPage = {
+ /**
+ * Initialize
+ */
+ onPageShow: function() {
+ let aus = CoC["@mozilla.org/updates/update-service;1"].
+ getService(CoI.nsIApplicationUpdateService);
+ if (aus.elevationRequired) {
+ LOG("gFinishedPage", "elevationRequired");
+ gUpdates.setButtons("restartLaterButton", "noThanksButton",
+ "restartNowButton", true);
+ } else {
+ LOG("gFinishedPage", "not elevationRequired");
+ gUpdates.setButtons("restartLaterButton", null, "restartNowButton",
+ true);
+ }
+ gUpdates.wiz.getButton("finish").focus();
+ },
+
+ /**
+ * Initialize the Wizard Page for a Background Source Event
+ */
+ onPageShowBackground: function() {
+ this.onPageShow();
+ let updateFinishedName = document.getElementById("updateFinishedName");
+ updateFinishedName.value = gUpdates.update.name;
+
+ let link = document.getElementById("finishedBackgroundLink");
+ if (gUpdates.update.detailsURL) {
+ link.setAttribute("url", gUpdates.update.detailsURL);
+ // The details link is stealing focus so it is disabled by default and
+ // should only be enabled after onPageShow has been called.
+ link.disabled = false;
+ } else {
+ link.hidden = true;
+ }
+ let aus = CoC["@mozilla.org/updates/update-service;1"].
+ getService(CoI.nsIApplicationUpdateService);
+ if (aus.elevationRequired) {
+ let more = document.getElementById("finishedBackgroundMore");
+ more.setAttribute("hidden", "true");
+ let moreElevated =
+ document.getElementById("finishedBackgroundMoreElevated");
+ moreElevated.setAttribute("hidden", "false");
+ let moreElevatedLink =
+ document.getElementById("finishedBackgroundMoreElevatedLink");
+ moreElevatedLink.setAttribute("hidden", "false");
+ let moreElevatedLinkLabel =
+ document.getElementById("finishedBackgroundMoreElevatedLinkLabel");
+ let manualURL = Services.urlFormatter.formatURLPref(PREF_APP_UPDATE_URL_MANUAL);
+ moreElevatedLinkLabel.value = manualURL;
+ moreElevatedLinkLabel.setAttribute("url", manualURL);
+ moreElevatedLinkLabel.setAttribute("hidden", "false");
+ }
+
+ if (getPref("getBoolPref", PREF_APP_UPDATE_TEST_LOOP, false)) {
+ setTimeout(function () { gUpdates.wiz.getButton("finish").click(); },
+ UPDATE_TEST_LOOP_INTERVAL);
+ }
+ },
+
+ /**
+ * Called when the wizard finishes, i.e. the "Restart Now" button is
+ * clicked.
+ */
+ onWizardFinish: function() {
+ // Do the restart
+ LOG("gFinishedPage", "onWizardFinish - restarting the application");
+
+ let aus = CoC["@mozilla.org/updates/update-service;1"].
+ getService(CoI.nsIApplicationUpdateService);
+ if (aus.elevationRequired) {
+ let um = CoC["@mozilla.org/updates/update-manager;1"].
+ getService(CoI.nsIUpdateManager);
+ if (um) {
+ um.elevationOptedIn();
+ }
+ }
+
+ // disable the "finish" (Restart) and "extra1" (Later) buttons
+ // because the Software Update wizard is still up at the point,
+ // and will remain up until we return and we close the
+ // window with a |window.close()| in wizard.xml
+ // (it was the firing the "wizardfinish" event that got us here.)
+ // This prevents the user from switching back
+ // to the Software Update dialog and clicking "Restart" or "Later"
+ // when dealing with the "confirm close" prompts.
+ // See bug #350299 for more details.
+ gUpdates.wiz.getButton("finish").disabled = true;
+ gUpdates.wiz.getButton("extra1").disabled = true;
+
+ // Notify all windows that an application quit has been requested.
+ var cancelQuit = CoC["@mozilla.org/supports-PRBool;1"].
+ createInstance(CoI.nsISupportsPRBool);
+ Services.obs.notifyObservers(cancelQuit, "quit-application-requested",
+ "restart");
+
+ // Something aborted the quit process.
+ if (cancelQuit.data)
+ return;
+
+ // If already in safe mode restart in safe mode (bug 327119)
+ if (Services.appinfo.inSafeMode) {
+ let env = CoC["@mozilla.org/process/environment;1"].
+ getService(CoI.nsIEnvironment);
+ env.set("MOZ_SAFE_MODE_RESTART", "1");
+ }
+
+ // Restart the application
+ CoC["@mozilla.org/toolkit/app-startup;1"].getService(CoI.nsIAppStartup).
+ quit(CoI.nsIAppStartup.eAttemptQuit | CoI.nsIAppStartup.eRestart);
+ },
+
+ /**
+ * When the user clicks the "Restart Later" instead of the Restart Now" button
+ * in the wizard after an update has been downloaded.
+ */
+ onExtra1: function() {
+ gUpdates.wiz.cancel();
+ },
+
+ /**
+ * When elevation is required and the user clicks "No Thanks" in the wizard.
+ */
+ onExtra2: Task.async(function*() {
+ Services.obs.notifyObservers(null, "update-canceled", null);
+ let um = CoC["@mozilla.org/updates/update-manager;1"].
+ getService(CoI.nsIUpdateManager);
+ um.cleanupActiveUpdate();
+ gUpdates.never();
+ gUpdates.wiz.cancel();
+ }),
+};
+
+/**
+ * Callback for the Update Prompt to set the current page if an Update Wizard
+ * window is already found to be open.
+ * @param pageid
+ * The ID of the page to switch to
+ */
+function setCurrentPage(pageid) {
+ gUpdates.wiz.currentPage = document.getElementById(pageid);
+}
diff --git a/toolkit/mozapps/update/content/updates.xml b/toolkit/mozapps/update/content/updates.xml
new file mode 100644
index 000000000..9e8d4e936
--- /dev/null
+++ b/toolkit/mozapps/update/content/updates.xml
@@ -0,0 +1,83 @@
+<?xml version="1.0"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+
+<!DOCTYPE bindings SYSTEM "chrome://mozapps/locale/update/updates.dtd">
+
+<bindings id="updatesBindings"
+ xmlns="http://www.mozilla.org/xbl"
+ xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns:xbl="http://www.mozilla.org/xbl">
+
+ <binding id="updateheader" extends="chrome://global/content/bindings/wizard.xml#wizard-header">
+ <resources>
+ <stylesheet src="chrome://global/skin/wizard.css"/>
+ </resources>
+ <content>
+ <xul:hbox class="wizard-header update-header" flex="1">
+ <xul:vbox class="wizard-header-box-1">
+ <xul:vbox class="wizard-header-box-text">
+ <xul:label class="wizard-header-label" xbl:inherits="value=label"/>
+ </xul:vbox>
+ </xul:vbox>
+ </xul:hbox>
+ </content>
+ </binding>
+
+ <binding id="update" extends="chrome://global/content/bindings/richlistbox.xml#richlistitem">
+ <content>
+ <xul:hbox>
+ <xul:label class="update-name" xbl:inherits="value=name" flex="1" crop="right"/>
+ <xul:label xbl:inherits="href=detailsURL,hidden=hideDetailsURL" class="text-link"
+ value="&update.details.label;"/>
+ </xul:hbox>
+ <xul:label class="update-type" xbl:inherits="value=type"/>
+ <xul:grid>
+ <xul:columns>
+ <xul:column class="update-label-column"/>
+ <xul:column flex="1"/>
+ </xul:columns>
+ <xul:rows>
+ <xul:row>
+ <xul:label class="update-installedOn-label">&update.installedOn.label;</xul:label>
+ <xul:label class="update-installedOn-value" xbl:inherits="value=installDate" flex="1" crop="right"/>
+ </xul:row>
+ <xul:row>
+ <xul:label class="update-status-label">&update.status.label;</xul:label>
+ <xul:description class="update-status-value" flex="1"/>
+ </xul:row>
+ </xul:rows>
+ </xul:grid>
+ </content>
+ <implementation>
+ <property name="name"
+ onget="return this.getAttribute('name');"
+ onset="this.setAttribute('name', val); return val;"/>
+ <property name="detailsURL"
+ onget="return this.getAttribute('detailsURL');"
+ onset="this.setAttribute('detailsURL', val); return val;"/>
+ <property name="installDate"
+ onget="return this.getAttribute('installDate');"
+ onset="this.setAttribute('installDate', val); return val;"/>
+ <property name="type"
+ onget="return this.getAttribute('type');"
+ onset="this.setAttribute('type', val); return val;"/>
+ <property name="hideDetailsURL"
+ onget="return this.getAttribute('hideDetailsURL');"
+ onset="this.setAttribute('hideDetailsURL', val); return val;"/>
+ <property name="status"
+ onget="return this.getAttribute('status');">
+ <setter><![CDATA[
+ this.setAttribute("status", val);
+ var field = document.getAnonymousElementByAttribute(this, "class", "update-status-value");
+ while (field.hasChildNodes())
+ field.removeChild(field.firstChild);
+ field.appendChild(document.createTextNode(val));
+ return val;
+ ]]></setter>
+ </property>
+ </implementation>
+ </binding>
+</bindings>
diff --git a/toolkit/mozapps/update/content/updates.xul b/toolkit/mozapps/update/content/updates.xul
new file mode 100644
index 000000000..e4cdc7a49
--- /dev/null
+++ b/toolkit/mozapps/update/content/updates.xul
@@ -0,0 +1,206 @@
+<?xml version="1.0"?>
+
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
+<?xml-stylesheet href="chrome://mozapps/content/update/updates.css"?>
+<?xml-stylesheet href="chrome://mozapps/skin/update/updates.css"?>
+
+<!DOCTYPE wizard [
+<!ENTITY % updateDTD SYSTEM "chrome://mozapps/locale/update/updates.dtd">
+<!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd">
+%updateDTD;
+%brandDTD;
+]>
+
+<wizard id="updates"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="&updateWizard.title;"
+ windowtype="Update:Wizard"
+ style="width: auto; height: auto"
+ onwizardfinish="gUpdates.onWizardFinish();"
+ onwizardcancel="gUpdates.onWizardCancel();"
+ onwizardnext="gUpdates.onWizardNext();"
+ onload="gUpdates.onLoad();"
+ onunload="gUpdates.onUnload();">
+
+ <script type="application/javascript" src="chrome://global/content/contentAreaUtils.js"/>
+ <script type="application/javascript" src="chrome://mozapps/content/update/updates.js"/>
+
+ <stringbundleset id="updateSet">
+ <stringbundle id="brandStrings" src="chrome://branding/locale/brand.properties"/>
+ <stringbundle id="updateStrings" src="chrome://mozapps/locale/update/updates.properties"/>
+ </stringbundleset>
+
+ <wizardpage id="dummy" pageid="dummy" firstpage="true"/>
+
+ <wizardpage id="checking" pageid="checking" next="noupdatesfound"
+ object="gCheckingPage" onpageshow="gCheckingPage.onPageShow();">
+ <updateheader label="&checking.title;"/>
+ <vbox class="update-content" flex="1">
+ <label>&updateCheck.label;</label>
+ <separator class="thin"/>
+ <progressmeter id="checkingProgress" mode="undetermined"/>
+ </vbox>
+ </wizardpage>
+
+ <wizardpage id="noupdatesfound" pageid="noupdatesfound"
+ object="gNoUpdatesPage" onpageshow="gNoUpdatesPage.onPageShow();">
+ <updateheader label="&noupdatesfound.title;"/>
+ <vbox class="update-content" flex="1">
+ <label id="noUpdatesAutoEnabled" hidden="true">&noupdatesautoenabled.intro;</label>
+ <label id="noUpdatesAutoDisabled" hidden="true">&noupdatesautodisabled.intro;</label>
+ </vbox>
+ </wizardpage>
+
+ <wizardpage id="manualUpdate" pageid="manualUpdate" object="gManualUpdatePage"
+ onpageshow="gManualUpdatePage.onPageShow();">
+ <updateheader label="&manualUpdate.title;"/>
+ <vbox class="update-content" flex="1">
+ <label id="manualUpdateDesc">&manualUpdate.desc;</label>
+ <label id="manualUpdateSpaceDesc"
+ hidden="true">&manualUpdate.space.desc;</label>
+ <separator class="thin"/>
+ <label>&manualUpdateGetMsg.label;</label>
+ <hbox>
+ <label class="text-link" id="manualUpdateLinkLabel" value=""
+ onclick="openUpdateURL(event);"/>
+ </hbox>
+ </vbox>
+ </wizardpage>
+
+ <wizardpage id="unsupported" pageid="unsupported"
+ object="gUnsupportedPage"
+ onpageshow="gUnsupportedPage.onPageShow();">
+ <updateheader label="&unsupported.title;"/>
+ <vbox class="update-content" flex="1">
+ <description flex="1">&unsupported.label;
+ <label id="unsupportedLinkLabel" class="text-link inline-link" onclick="openUpdateURL(event);">
+ &unsupportedLink.label;
+ </label>
+ </description>
+ </vbox>
+ </wizardpage>
+
+ <wizardpage id="updatesfoundbasic" pageid="updatesfoundbasic"
+ object="gUpdatesFoundBasicPage" next="downloading"
+ onpageshow="gUpdatesFoundBasicPage.onPageShow();"
+ onextra1="gUpdatesFoundBasicPage.onExtra1();"
+ onextra2="gUpdatesFoundBasicPage.onExtra2();">
+ <updateheader id="updatesFoundBasicHeader" label=""/>
+ <vbox class="update-content" flex="1">
+ <label id="updatesFoundInto"/>
+ <separator class="thin"/>
+ <label id="updateName" crop="right" value=""/>
+ <separator id="updateNameSep" class="thin"/>
+ <label id="upgradeEvangelism">&evangelism.desc;</label>
+ <separator id="upgradeEvangelismSep" flex="1"/>
+ <vbox flex="1">
+ <hbox id="moreInfoURL">
+ <label class="text-link" id="updateMoreInfoURL"
+ value="&clickHere.label;" onclick="openUpdateURL(event);"/>
+ </hbox>
+ </vbox>
+ </vbox>
+ </wizardpage>
+
+ <wizardpage id="downloading" pageid="downloading"
+ object="gDownloadingPage" onextra1="gDownloadingPage.onHide();"
+ onpageshow="gDownloadingPage.onPageShow();">
+ <updateheader label="&downloadPage.title;"/>
+ <vbox class="update-content" flex="1">
+ <hbox id="downloadStatusProgress">
+ <progressmeter id="downloadProgress" mode="undetermined" flex="1"/>
+ <button id="pauseButton" oncommand="gDownloadingPage.onPause();"
+ paused="false"/>
+ </hbox>
+ <separator class="thin"/>
+ <hbox id="downloadStatusLine">
+ <label id="downloadStatus" flex="1">&connecting.label;</label>
+ </hbox>
+ <separator/>
+ <hbox id="verificationFailed" align="start" hidden="true">
+ <image id="verificationFailedIcon"/>
+ <label flex="1">&verificationFailedText.label;</label>
+ </hbox>
+ </vbox>
+ </wizardpage>
+
+ <wizardpage id="errors" pageid="errors" object="gErrorsPage"
+ onpageshow="gErrorsPage.onPageShow();">
+ <updateheader label="&error.title;"/>
+ <vbox class="update-content" flex="1">
+ <label id="errorIntro">&error.label;</label>
+ <separator/>
+ <textbox class="plain" readonly="true" id="errorReason" multiline="true"
+ rows="3"/>
+ <separator/>
+ <label id="errorManual">&errorManual.label;</label>
+ <hbox>
+ <label class="text-link" id="errorLinkLabel" value=""
+ onclick="openUpdateURL(event);"/>
+ </hbox>
+ </vbox>
+ </wizardpage>
+
+ <wizardpage id="errorextra" pageid="errorextra"
+ object="gErrorExtraPage"
+ onpageshow="gErrorExtraPage.onPageShow();">
+ <updateheader label="&error.title;"/>
+ <vbox class="update-content" flex="1">
+ <label id="bgErrorLabel">&genericBackgroundError.label;</label>
+ <hbox>
+ <label id="errorExtraLinkLabel" class="text-link"
+ value="" onclick="openUpdateURL(event);"/>
+ </hbox>
+ </vbox>
+ </wizardpage>
+
+ <wizardpage id="errorpatching" pageid="errorpatching" next="downloading"
+ object="gErrorPatchingPage"
+ onpageshow="gErrorPatchingPage.onPageShow();">
+ <updateheader label="&error.title;"/>
+ <vbox class="update-content" flex="1">
+ <label>&errorpatching.intro;</label>
+ </vbox>
+ </wizardpage>
+
+ <wizardpage id="finished" pageid="finished" object="gFinishedPage"
+ onpageshow="gFinishedPage.onPageShow();"
+ onextra1="gFinishedPage.onExtra1()">
+ <updateheader label="&finishedPage.title;"/>
+ <vbox class="update-content" flex="1">
+ <label>&finishedPage.text;</label>
+ </vbox>
+ </wizardpage>
+
+ <wizardpage id="finishedBackground" pageid="finishedBackground"
+ object="gFinishedPage" onextra1="gFinishedPage.onExtra1()"
+ onextra2="gFinishedPage.onExtra2()"
+ onpageshow="gFinishedPage.onPageShowBackground();">
+ <updateheader label="&finishedPage.title;"/>
+ <vbox class="update-content" flex="1">
+ <label>&finishedBackgroundPage.text;</label>
+ <separator/>
+ <hbox align="center">
+ <label>&finishedBackground.name;</label>
+ <label id="updateFinishedName" flex="1" crop="right" value=""/>
+ <label id="finishedBackgroundLink" class="text-link" disabled="true"
+ value="&details.link;" onclick="openUpdateURL(event);"/>
+ </hbox>
+ <spacer flex="1"/>
+ <label id="finishedBackgroundMore">&finishedBackground.more;</label>
+ <label id="finishedBackgroundMoreElevated"
+ hidden="true">&finishedBackground.moreElevated;</label>
+ <label id="finishedBackgroundMoreElevatedLink"
+ hidden="true">&errorManual.label;</label>
+ <hbox>
+ <label class="text-link" id="finishedBackgroundMoreElevatedLinkLabel"
+ value="" onclick="openUpdateURL(event);" hidden="true"/>
+ </hbox>
+ </vbox>
+ </wizardpage>
+
+</wizard>
diff --git a/toolkit/mozapps/update/jar.mn b/toolkit/mozapps/update/jar.mn
new file mode 100644
index 000000000..9bb5d2d05
--- /dev/null
+++ b/toolkit/mozapps/update/jar.mn
@@ -0,0 +1,12 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+toolkit.jar:
+% content mozapps %content/mozapps/
+ content/mozapps/update/history.xul (content/history.xul)
+ content/mozapps/update/history.js (content/history.js)
+ content/mozapps/update/updates.css (content/updates.css)
+ content/mozapps/update/updates.js (content/updates.js)
+ content/mozapps/update/updates.xml (content/updates.xml)
+ content/mozapps/update/updates.xul (content/updates.xul)
diff --git a/toolkit/mozapps/update/moz.build b/toolkit/mozapps/update/moz.build
new file mode 100644
index 000000000..78a6996b7
--- /dev/null
+++ b/toolkit/mozapps/update/moz.build
@@ -0,0 +1,33 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+XPIDL_MODULE = 'update'
+
+DIRS += [
+ 'common',
+ 'updater',
+]
+
+XPIDL_SOURCES += [
+ 'nsIUpdateService.idl',
+]
+
+TEST_DIRS += ['tests']
+
+EXTRA_COMPONENTS += [
+ 'nsUpdateService.js',
+ 'nsUpdateService.manifest',
+ 'nsUpdateServiceStub.js',
+]
+
+EXTRA_JS_MODULES += [
+ 'UpdateTelemetry.jsm',
+]
+
+JAR_MANIFESTS += ['jar.mn']
+
+with Files('**'):
+ BUG_COMPONENT = ('Toolkit', 'Application Update')
diff --git a/toolkit/mozapps/update/nsIUpdateService.idl b/toolkit/mozapps/update/nsIUpdateService.idl
new file mode 100644
index 000000000..817df5a67
--- /dev/null
+++ b/toolkit/mozapps/update/nsIUpdateService.idl
@@ -0,0 +1,575 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIDOMDocument;
+interface nsIDOMElement;
+interface nsIDOMWindow;
+interface nsIRequest;
+interface nsIRequestObserver;
+interface nsISimpleEnumerator;
+interface nsIFile;
+
+/**
+ * An interface that describes an object representing a patch file that can
+ * be downloaded and applied to a version of this application so that it
+ * can be updated.
+ */
+[scriptable, uuid(dc8fb8a9-3a53-4031-9469-2a5197ea30e7)]
+interface nsIUpdatePatch : nsISupports
+{
+ /**
+ * The type of this patch:
+ * "partial" A binary difference between two application versions
+ * "complete" A complete patch containing all of the replacement files
+ * to update to the new version
+ */
+ attribute AString type;
+
+ /**
+ * The URL this patch was being downloaded from
+ */
+ attribute AString URL;
+
+ /**
+ * The final URL this patch was being downloaded from
+ */
+ attribute AString finalURL;
+
+ /**
+ * The hash function to use when determining this file's integrity
+ */
+ attribute AString hashFunction;
+
+ /**
+ * The value of the hash function named above that should be computed if
+ * this file is not corrupt.
+ */
+ attribute AString hashValue;
+
+ /**
+ * The size of this file, in bytes.
+ */
+ attribute unsigned long size;
+
+ /**
+ * The state of this patch
+ */
+ attribute AString state;
+
+ /**
+ * true if this patch is currently selected as the patch to be downloaded and
+ * installed for this update transaction, false if another patch from this
+ * update has been selected.
+ */
+ attribute boolean selected;
+
+ /**
+ * Serializes this patch object into a DOM Element
+ * @param updates
+ * The document to serialize into
+ * @returns The DOM Element created by the serialization process
+ */
+ nsIDOMElement serialize(in nsIDOMDocument updates);
+};
+
+/**
+ * An interface that describes an object representing an available update to
+ * the current application - this update may have several available patches
+ * from which one must be selected to download and install, for example we
+ * might select a binary difference patch first and attempt to apply that,
+ * then if the application process fails fall back to downloading a complete
+ * file-replace patch. This object also contains information about the update
+ * that the front end and other application services can use to learn more
+ * about what is going on.
+ */
+[scriptable, uuid(e094c045-f4ff-41fd-92da-cd2effd2c7c9)]
+interface nsIUpdate : nsISupports
+{
+ /**
+ * The type of update:
+ * "major" A major new version of the Application
+ * "minor" A minor update to the Application (e.g. security update)
+ */
+ attribute AString type;
+
+ /**
+ * The name of the update, or "<Application Name> <Update Version>"
+ */
+ attribute AString name;
+
+ /**
+ * The string to display in the user interface for the version. If you want
+ * a real version number use appVersion.
+ */
+ attribute AString displayVersion;
+
+ /**
+ * The Application version of this update.
+ */
+ attribute AString appVersion;
+
+ /**
+ * The Application version prior to the application being updated.
+ */
+ attribute AString previousAppVersion;
+
+ /**
+ * The Build ID of this update. Used to determine a particular build, down
+ * to the hour, minute and second of its creation. This allows the system
+ * to differentiate between several nightly builds with the same |version|
+ * for example.
+ */
+ attribute AString buildID;
+
+ /**
+ * The URL to a page which offers details about the content of this
+ * update. Ideally, this page is not the release notes but some other page
+ * that summarizes the differences between this update and the previous,
+ * which also links to the release notes.
+ */
+ attribute AString detailsURL;
+
+ /**
+ * The URL to the Update Service that supplied this update.
+ */
+ attribute AString serviceURL;
+
+ /**
+ * The channel used to retrieve this update from the Update Service.
+ */
+ attribute AString channel;
+
+ /**
+ * Whether to show the update prompt which requires user confirmation when an
+ * update is found during a background update check. This overrides the
+ * default setting to download the update in the background.
+ */
+ attribute boolean showPrompt;
+
+ /**
+ * Whether to show the "No Thanks" button in the update prompt. This allows
+ * the user to never receive a notification for that specific update version
+ * again.
+ */
+ attribute boolean showNeverForVersion;
+
+ /**
+ * Whether the update is no longer supported on this system.
+ */
+ attribute boolean unsupported;
+
+ /**
+ * Allows overriding the default amount of time in seconds before prompting the
+ * user to apply an update. If not specified, the value of
+ * app.update.promptWaitTime will be used.
+ */
+ attribute long long promptWaitTime;
+
+ /**
+ * Whether or not the update being downloaded is a complete replacement of
+ * the user's existing installation or a patch representing the difference
+ * between the new version and the previous version.
+ */
+ attribute boolean isCompleteUpdate;
+
+ /**
+ * Whether or not the update is a security update or not. If this is true,
+ * then we present more serious sounding user interface messages to the
+ * user.
+ */
+ attribute boolean isSecurityUpdate;
+
+ /**
+ * Whether or not the update being downloaded is an OS update. This is
+ * generally only possible in Gonk right now.
+ */
+ attribute boolean isOSUpdate;
+
+ /**
+ * When the update was installed.
+ */
+ attribute long long installDate;
+
+ /**
+ * A message associated with this update, if any.
+ */
+ attribute AString statusText;
+
+ /**
+ * The currently selected patch for this update.
+ */
+ readonly attribute nsIUpdatePatch selectedPatch;
+
+ /**
+ * The state of the selected patch:
+ * "downloading" The update is being downloaded.
+ * "pending" The update is ready to be applied.
+ * "pending-service" The update is ready to be applied with the service.
+ * "pending-elevate" The update is ready to be applied but requires elevation.
+ * "applying" The update is being applied.
+ * "applied" The update is ready to be switched to.
+ * "applied-os" The update is OS update and to be installed.
+ * "applied-service" The update is ready to be switched to with the service.
+ * "succeeded" The update was successfully applied.
+ * "download-failed" The update failed to be downloaded.
+ * "failed" The update failed to be applied.
+ */
+ attribute AString state;
+
+ /**
+ * A numeric error code that conveys additional information about the state
+ * of a failed update or failed certificate attribute check during an update
+ * check. If the update is not in the "failed" state or the certificate
+ * attribute check has not failed the value is zero.
+ *
+ * TODO: Define typical error codes (for now, see updater/errors.h and the
+ * CERT_ATTR_CHECK_FAILED_* values in nsUpdateService.js)
+ */
+ attribute long errorCode;
+
+ /**
+ * Whether an elevation failure has been encountered for this update.
+ */
+ attribute boolean elevationFailure;
+
+ /**
+ * The number of patches supplied by this update.
+ */
+ readonly attribute unsigned long patchCount;
+
+ /**
+ * Retrieves a patch.
+ * @param index
+ * The index of the patch to retrieve.
+ * @returns The nsIUpdatePatch at the specified index.
+ */
+ nsIUpdatePatch getPatchAt(in unsigned long index);
+
+ /**
+ * Serializes this update object into a DOM Element
+ * @param updates
+ * The document to serialize into
+ * @returns The DOM Element created by the serialization process
+ */
+ nsIDOMElement serialize(in nsIDOMDocument updates);
+};
+
+/**
+ * An interface describing an object that listens to the progress of an update
+ * check operation. This object is notified as the check continues, finishes
+ * and if it has an error.
+ */
+[scriptable, uuid(4aa2b4bb-39ea-407b-98ff-89f19134d4c0)]
+interface nsIUpdateCheckListener : nsISupports
+{
+ /**
+ * The update check was completed.
+ * @param request
+ * The XMLHttpRequest handling the update check.
+ * @param updates
+ * An array of nsIUpdate objects listing available updates.
+ * @param updateCount
+ * The size of the |updates| array.
+ */
+ void onCheckComplete(in jsval request,
+ [array, size_is(updateCount)] in nsIUpdate updates,
+ in unsigned long updateCount);
+
+ /**
+ * An error occurred while loading the remote update service file.
+ * @param request
+ * The XMLHttpRequest handling the update check.
+ * @param update
+ * A nsIUpdate object that contains details about the
+ * error in its |statusText| property.
+ */
+ void onError(in jsval request,
+ in nsIUpdate update);
+};
+
+/**
+ * An interface describing an object that knows how to check for updates.
+ */
+[scriptable, uuid(877ace25-8bc5-452a-8586-9c1cf2871994)]
+interface nsIUpdateChecker : nsISupports
+{
+ /**
+ * Checks for available updates, notifying a listener of the results.
+ * @param listener
+ * An object implementing nsIUpdateCheckListener which is notified
+ * of the results of an update check.
+ * @param force
+ * Forces the checker to check for updates, regardless of the
+ * current value of the user's update settings. This is used by
+ * any piece of UI that offers the user the imperative option to
+ * check for updates now, regardless of their update settings.
+ * force will not work if the system administrator has locked
+ * the app.update.enabled preference.
+ */
+ void checkForUpdates(in nsIUpdateCheckListener listener, in boolean force);
+
+ /**
+ * Constants for the |stopChecking| function that tell the Checker how long
+ * to stop checking:
+ *
+ * CURRENT_CHECK: Stops the current (active) check only
+ * CURRENT_SESSION: Stops all checking for the current session
+ * ANY_CHECKS: Stops all checking, any session from now on
+ * (disables update checking preferences)
+ */
+ const unsigned short CURRENT_CHECK = 1;
+ const unsigned short CURRENT_SESSION = 2;
+ const unsigned short ANY_CHECKS = 3;
+
+ /**
+ * Ends any pending update check.
+ * @param duration
+ * A value representing the set of checks to stop doing.
+ */
+ void stopChecking(in unsigned short duration);
+};
+
+/**
+ * An interface describing a global application service that handles performing
+ * background update checks and provides utilities for selecting and
+ * downloading update patches.
+ */
+[scriptable, uuid(1107d207-a263-403a-b268-05772ec10757)]
+interface nsIApplicationUpdateService : nsISupports
+{
+ /**
+ * Checks for available updates in the background using the listener provided
+ * by the application update service for background checks.
+ */
+ void checkForBackgroundUpdates();
+
+ /**
+ * The Update Checker used for background update checking.
+ */
+ readonly attribute nsIUpdateChecker backgroundChecker;
+
+ /**
+ * Selects the best update to install from a list of available updates.
+ * @param updates
+ * An array of updates that are available
+ * @param updateCount
+ * The length of the |updates| array
+ */
+ nsIUpdate selectUpdate([array, size_is(updateCount)] in nsIUpdate updates,
+ in unsigned long updateCount);
+
+ /**
+ * Adds a listener that receives progress and state information about the
+ * update that is currently being downloaded, e.g. to update a user
+ * interface.
+ * @param listener
+ * An object implementing nsIRequestObserver and optionally
+ * nsIProgressEventSink that is to be notified of state and
+ * progress information as the update is downloaded.
+ */
+ void addDownloadListener(in nsIRequestObserver listener);
+
+ /**
+ * Removes a listener that is receiving progress and state information
+ * about the update that is currently being downloaded.
+ * @param listener
+ * The listener object to remove.
+ */
+ void removeDownloadListener(in nsIRequestObserver listener);
+
+ /**
+ *
+ */
+ AString downloadUpdate(in nsIUpdate update, in boolean background);
+
+ /**
+ * Apply the OS update which has been downloaded and staged as applied.
+ * @param update
+ * The update has been downloaded and staged as applied.
+ * @throws if the update object is not an OS update.
+ */
+ void applyOsUpdate(in nsIUpdate update);
+
+ /**
+ * Get the Active Updates directory
+ * @returns An nsIFile for the active updates directory.
+ */
+ nsIFile getUpdatesDirectory();
+
+ /**
+ * Pauses the active update download process
+ */
+ void pauseDownload();
+
+ /**
+ * Whether or not there is an download happening at the moment.
+ */
+ readonly attribute boolean isDownloading;
+
+ /**
+ * Whether or not the Update Service can check for updates. This is a function
+ * of whether or not application update is disabled by the application and the
+ * platform the application is running on.
+ */
+ readonly attribute boolean canCheckForUpdates;
+
+ /**
+ * Whether or not the installation requires elevation. Currently only
+ * implemented on OSX, returns false on other platforms.
+ */
+ readonly attribute boolean elevationRequired;
+
+ /**
+ * Whether or not the Update Service can download and install updates.
+ * On Windows, this is a function of whether or not the maintenance service
+ * is installed and enabled. On other systems, and as a fallback on Windows,
+ * this depends on whether the current user has write access to the install
+ * directory.
+ */
+ readonly attribute boolean canApplyUpdates;
+
+ /**
+ * Whether or not a different instance is handling updates of this
+ * installation. This currently only ever returns true on Windows
+ * when 2 instances of an application are open. Only one of the instances
+ * will actually handle updates for the installation.
+ */
+ readonly attribute boolean isOtherInstanceHandlingUpdates;
+
+ /**
+ * Whether the Update Service is able to stage updates.
+ */
+ readonly attribute boolean canStageUpdates;
+};
+
+/**
+ * An interface describing a component which handles the job of processing
+ * an update after it's been downloaded.
+ */
+[scriptable, uuid(74439497-d796-4915-8cef-3dfe43027e4d)]
+interface nsIUpdateProcessor : nsISupports
+{
+ /**
+ * Processes the update which has been downloaded.
+ * This happens without restarting the application.
+ * On Windows, this can also be used for switching to an applied
+ * update request.
+ * @param update The update being applied, or null if this is a switch
+ * to updated application request. Must be non-null on GONK.
+ */
+ void processUpdate(in nsIUpdate update);
+};
+
+/**
+ * An interface describing a global application service that maintains a list
+ * of updates previously performed as well as the current active update.
+ */
+[scriptable, uuid(0f1098e9-a447-4af9-b030-6f8f35c85f89)]
+interface nsIUpdateManager : nsISupports
+{
+ /**
+ * Gets the update at the specified index
+ * @param index
+ * The index within the updates array
+ * @returns The nsIUpdate object at the specified index
+ */
+ nsIUpdate getUpdateAt(in long index);
+
+ /**
+ * Gets the total number of updates in the history list.
+ */
+ readonly attribute long updateCount;
+
+ /**
+ * The active (current) update. The active update is not in the history list.
+ */
+ attribute nsIUpdate activeUpdate;
+
+ /**
+ * Saves all updates to disk.
+ */
+ void saveUpdates();
+
+ /**
+ * Refresh the update status based on the information in update.status.
+ */
+ void refreshUpdateStatus();
+
+ /**
+ * The user agreed to proceed with an elevated update and we are now
+ * permitted to show an elevation prompt.
+ */
+ void elevationOptedIn();
+
+ /**
+ * Clean up and remove the active update without applying it.
+ */
+ void cleanupActiveUpdate();
+};
+
+/**
+ * An interface describing an object that can show various kinds of Update
+ * notification UI to the user.
+ */
+[scriptable, uuid(cee3bd60-c564-42ff-a2bf-d442cb15f75c)]
+interface nsIUpdatePrompt : nsISupports
+{
+ /**
+ * Shows the application update checking user interface and checks if there
+ * is an update available.
+ */
+ void checkForUpdates();
+
+ /**
+ * Shows the application update available user interface advising that an
+ * update is available for download and install. If the app.update.silent
+ * preference is true or the user interface is already displayed the call will
+ * be a no-op.
+ * @param update
+ * The nsIUpdate object to be downloaded and installed
+ */
+ void showUpdateAvailable(in nsIUpdate update);
+
+ /**
+ * Shows the application update downloaded user interface advising that an
+ * update has now been downloaded and a restart is necessary to complete the
+ * update. If background is true (e.g. the download was not user initiated)
+ * and the app.update.silent preference is true the call will be a no-op.
+ * @param update
+ * The nsIUpdate object that was downloaded
+ * @param background
+ * Less obtrusive UI, starting with a non-modal notification alert
+ */
+ void showUpdateDownloaded(in nsIUpdate update,
+ [optional] in boolean background);
+
+ /**
+ * Shows the application update error user interface advising that an error
+ * occurred while checking for or applying an update. If the app.update.silent
+ * preference is true the call will be a no-op.
+ * @param update
+ * An nsIUpdate object representing the update that could not be
+ * installed. The nsIUpdate object will not be the actual update when
+ * the error occurred during an update check and will instead be an
+ * nsIUpdate object with the error information for the update check.
+ */
+ void showUpdateError(in nsIUpdate update);
+
+ /**
+ * Shows a list of all updates installed to date.
+ * @param parent
+ * An nsIDOMWindow to set as the parent for this window. Can be null.
+ */
+ void showUpdateHistory(in nsIDOMWindow parent);
+
+ /**
+ * Shows the application update downloaded user interface advising that an
+ * update, which requires elevation, has now been downloaded and a restart is
+ * necessary to complete the update.
+ */
+ void showUpdateElevationRequired();
+};
diff --git a/toolkit/mozapps/update/nsUpdateService.js b/toolkit/mozapps/update/nsUpdateService.js
new file mode 100644
index 000000000..10eb1d100
--- /dev/null
+++ b/toolkit/mozapps/update/nsUpdateService.js
@@ -0,0 +1,4567 @@
+/* -*- Mode: javascript; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+'use strict';
+
+const { classes: Cc, interfaces: Ci, results: Cr, utils: Cu } = Components;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm", this);
+Cu.import("resource://gre/modules/FileUtils.jsm", this);
+Cu.import("resource://gre/modules/Services.jsm", this);
+Cu.import("resource://gre/modules/ctypes.jsm", this);
+Cu.import("resource://gre/modules/UpdateTelemetry.jsm", this);
+Cu.import("resource://gre/modules/AppConstants.jsm", this);
+Cu.importGlobalProperties(["XMLHttpRequest"]);
+
+const UPDATESERVICE_CID = Components.ID("{B3C290A6-3943-4B89-8BBE-C01EB7B3B311}");
+const UPDATESERVICE_CONTRACTID = "@mozilla.org/updates/update-service;1";
+
+const PREF_APP_UPDATE_ALTWINDOWTYPE = "app.update.altwindowtype";
+const PREF_APP_UPDATE_AUTO = "app.update.auto";
+const PREF_APP_UPDATE_BACKGROUNDINTERVAL = "app.update.download.backgroundInterval";
+const PREF_APP_UPDATE_BACKGROUNDERRORS = "app.update.backgroundErrors";
+const PREF_APP_UPDATE_BACKGROUNDMAXERRORS = "app.update.backgroundMaxErrors";
+const PREF_APP_UPDATE_CANCELATIONS = "app.update.cancelations";
+const PREF_APP_UPDATE_CANCELATIONS_OSX = "app.update.cancelations.osx";
+const PREF_APP_UPDATE_CANCELATIONS_OSX_MAX = "app.update.cancelations.osx.max";
+const PREF_APP_UPDATE_ELEVATE_NEVER = "app.update.elevate.never";
+const PREF_APP_UPDATE_ELEVATE_VERSION = "app.update.elevate.version";
+const PREF_APP_UPDATE_ENABLED = "app.update.enabled";
+const PREF_APP_UPDATE_IDLETIME = "app.update.idletime";
+const PREF_APP_UPDATE_LOG = "app.update.log";
+const PREF_APP_UPDATE_NOTIFIEDUNSUPPORTED = "app.update.notifiedUnsupported";
+const PREF_APP_UPDATE_POSTUPDATE = "app.update.postupdate";
+const PREF_APP_UPDATE_PROMPTWAITTIME = "app.update.promptWaitTime";
+const PREF_APP_UPDATE_SERVICE_ENABLED = "app.update.service.enabled";
+const PREF_APP_UPDATE_SERVICE_ERRORS = "app.update.service.errors";
+const PREF_APP_UPDATE_SERVICE_MAXERRORS = "app.update.service.maxErrors";
+const PREF_APP_UPDATE_SILENT = "app.update.silent";
+const PREF_APP_UPDATE_SOCKET_MAXERRORS = "app.update.socket.maxErrors";
+const PREF_APP_UPDATE_SOCKET_RETRYTIMEOUT = "app.update.socket.retryTimeout";
+const PREF_APP_UPDATE_STAGING_ENABLED = "app.update.staging.enabled";
+const PREF_APP_UPDATE_URL = "app.update.url";
+const PREF_APP_UPDATE_URL_DETAILS = "app.update.url.details";
+
+const PREFBRANCH_APP_UPDATE_NEVER = "app.update.never.";
+
+const URI_BRAND_PROPERTIES = "chrome://branding/locale/brand.properties";
+const URI_UPDATE_HISTORY_DIALOG = "chrome://mozapps/content/update/history.xul";
+const URI_UPDATE_NS = "http://www.mozilla.org/2005/app-update";
+const URI_UPDATE_PROMPT_DIALOG = "chrome://mozapps/content/update/updates.xul";
+const URI_UPDATES_PROPERTIES = "chrome://mozapps/locale/update/updates.properties";
+
+const KEY_UPDROOT = "UpdRootD";
+const KEY_EXECUTABLE = "XREExeF";
+// Gonk only
+const KEY_UPDATE_ARCHIVE_DIR = "UpdArchD";
+
+const DIR_UPDATES = "updates";
+
+const FILE_ACTIVE_UPDATE_XML = "active-update.xml";
+const FILE_BACKUP_UPDATE_LOG = "backup-update.log";
+const FILE_LAST_UPDATE_LOG = "last-update.log";
+const FILE_UPDATES_XML = "updates.xml";
+const FILE_UPDATE_LINK = "update.link";
+const FILE_UPDATE_LOG = "update.log";
+const FILE_UPDATE_MAR = "update.mar";
+const FILE_UPDATE_STATUS = "update.status";
+const FILE_UPDATE_TEST = "update.test";
+const FILE_UPDATE_VERSION = "update.version";
+
+const STATE_NONE = "null";
+const STATE_DOWNLOADING = "downloading";
+const STATE_PENDING = "pending";
+const STATE_PENDING_SERVICE = "pending-service";
+const STATE_PENDING_ELEVATE = "pending-elevate";
+const STATE_APPLYING = "applying";
+const STATE_APPLIED = "applied";
+const STATE_APPLIED_OS = "applied-os";
+const STATE_APPLIED_SERVICE = "applied-service";
+const STATE_SUCCEEDED = "succeeded";
+const STATE_DOWNLOAD_FAILED = "download-failed";
+const STATE_FAILED = "failed";
+
+// The values below used by this code are from common/errors.h
+const WRITE_ERROR = 7;
+const ELEVATION_CANCELED = 9;
+const SERVICE_UPDATER_COULD_NOT_BE_STARTED = 24;
+const SERVICE_NOT_ENOUGH_COMMAND_LINE_ARGS = 25;
+const SERVICE_UPDATER_SIGN_ERROR = 26;
+const SERVICE_UPDATER_COMPARE_ERROR = 27;
+const SERVICE_UPDATER_IDENTITY_ERROR = 28;
+const SERVICE_STILL_APPLYING_ON_SUCCESS = 29;
+const SERVICE_STILL_APPLYING_ON_FAILURE = 30;
+const SERVICE_UPDATER_NOT_FIXED_DRIVE = 31;
+const SERVICE_COULD_NOT_LOCK_UPDATER = 32;
+const SERVICE_INSTALLDIR_ERROR = 33;
+const WRITE_ERROR_ACCESS_DENIED = 35;
+const WRITE_ERROR_CALLBACK_APP = 37;
+const FILESYSTEM_MOUNT_READWRITE_ERROR = 43;
+const SERVICE_COULD_NOT_COPY_UPDATER = 49;
+const SERVICE_STILL_APPLYING_TERMINATED = 50;
+const SERVICE_STILL_APPLYING_NO_EXIT_CODE = 51;
+const WRITE_ERROR_FILE_COPY = 61;
+const WRITE_ERROR_DELETE_FILE = 62;
+const WRITE_ERROR_OPEN_PATCH_FILE = 63;
+const WRITE_ERROR_PATCH_FILE = 64;
+const WRITE_ERROR_APPLY_DIR_PATH = 65;
+const WRITE_ERROR_CALLBACK_PATH = 66;
+const WRITE_ERROR_FILE_ACCESS_DENIED = 67;
+const WRITE_ERROR_DIR_ACCESS_DENIED = 68;
+const WRITE_ERROR_DELETE_BACKUP = 69;
+const WRITE_ERROR_EXTRACT = 70;
+
+// Array of write errors to simplify checks for write errors
+const WRITE_ERRORS = [WRITE_ERROR,
+ WRITE_ERROR_ACCESS_DENIED,
+ WRITE_ERROR_CALLBACK_APP,
+ WRITE_ERROR_FILE_COPY,
+ WRITE_ERROR_DELETE_FILE,
+ WRITE_ERROR_OPEN_PATCH_FILE,
+ WRITE_ERROR_PATCH_FILE,
+ WRITE_ERROR_APPLY_DIR_PATH,
+ WRITE_ERROR_CALLBACK_PATH,
+ WRITE_ERROR_FILE_ACCESS_DENIED,
+ WRITE_ERROR_DIR_ACCESS_DENIED,
+ WRITE_ERROR_DELETE_BACKUP,
+ WRITE_ERROR_EXTRACT];
+
+// Array of write errors to simplify checks for service errors
+const SERVICE_ERRORS = [SERVICE_UPDATER_COULD_NOT_BE_STARTED,
+ SERVICE_NOT_ENOUGH_COMMAND_LINE_ARGS,
+ SERVICE_UPDATER_SIGN_ERROR,
+ SERVICE_UPDATER_COMPARE_ERROR,
+ SERVICE_UPDATER_IDENTITY_ERROR,
+ SERVICE_STILL_APPLYING_ON_SUCCESS,
+ SERVICE_STILL_APPLYING_ON_FAILURE,
+ SERVICE_UPDATER_NOT_FIXED_DRIVE,
+ SERVICE_COULD_NOT_LOCK_UPDATER,
+ SERVICE_INSTALLDIR_ERROR,
+ SERVICE_COULD_NOT_COPY_UPDATER,
+ SERVICE_STILL_APPLYING_TERMINATED,
+ SERVICE_STILL_APPLYING_NO_EXIT_CODE];
+
+// Error codes 80 through 99 are reserved for nsUpdateService.js and are not
+// defined in common/errors.h
+const FOTA_GENERAL_ERROR = 80;
+const FOTA_UNKNOWN_ERROR = 81;
+const FOTA_FILE_OPERATION_ERROR = 82;
+const FOTA_RECOVERY_ERROR = 83;
+const INVALID_UPDATER_STATE_CODE = 98;
+const INVALID_UPDATER_STATUS_CODE = 99;
+
+// Custom update error codes
+const BACKGROUNDCHECK_MULTIPLE_FAILURES = 110;
+const NETWORK_ERROR_OFFLINE = 111;
+
+// Error codes should be < 1000. Errors above 1000 represent http status codes
+const HTTP_ERROR_OFFSET = 1000;
+
+const DOWNLOAD_CHUNK_SIZE = 300000; // bytes
+const DOWNLOAD_BACKGROUND_INTERVAL = 600; // seconds
+const DOWNLOAD_FOREGROUND_INTERVAL = 0;
+
+const UPDATE_WINDOW_NAME = "Update:Wizard";
+
+// The number of consecutive failures when updating using the service before
+// setting the app.update.service.enabled preference to false.
+const DEFAULT_SERVICE_MAX_ERRORS = 10;
+
+// The number of consecutive socket errors to allow before falling back to
+// downloading a different MAR file or failing if already downloading the full.
+const DEFAULT_SOCKET_MAX_ERRORS = 10;
+
+// The number of milliseconds to wait before retrying a connection error.
+const DEFAULT_SOCKET_RETRYTIMEOUT = 2000;
+
+// Default maximum number of elevation cancelations per update version before
+// giving up.
+const DEFAULT_CANCELATIONS_OSX_MAX = 3;
+
+// This maps app IDs to their respective notification topic which signals when
+// the application's user interface has been displayed.
+const APPID_TO_TOPIC = {
+ // Firefox
+ "{ec8030f7-c20a-464f-9b0e-13a3a9e97384}": "sessionstore-windows-restored",
+ // SeaMonkey
+ "{92650c4d-4b8e-4d2a-b7eb-24ecf4f6b63a}": "sessionstore-windows-restored",
+ // Fennec
+ "{aa3c5121-dab2-40e2-81ca-7ea25febc110}": "sessionstore-windows-restored",
+ // Thunderbird
+ "{3550f703-e582-4d05-9a08-453d09bdfdc6}": "mail-startup-done",
+ // Instantbird
+ "{33cb9019-c295-46dd-be21-8c4936574bee}": "xul-window-visible",
+};
+
+var gUpdateMutexHandle = null;
+
+// Gonk only
+var gSDCardMountLock = null;
+
+// Gonk only
+XPCOMUtils.defineLazyGetter(this, "gExtStorage", function aus_gExtStorage() {
+ if (AppConstants.platform != "gonk") {
+ throw Cr.NS_ERROR_NOT_IMPLEMENTED;
+ }
+ return Services.env.get("EXTERNAL_STORAGE");
+});
+
+XPCOMUtils.defineLazyModuleGetter(this, "UpdateUtils",
+ "resource://gre/modules/UpdateUtils.jsm");
+
+XPCOMUtils.defineLazyGetter(this, "gLogEnabled", function aus_gLogEnabled() {
+ return getPref("getBoolPref", PREF_APP_UPDATE_LOG, false);
+});
+
+XPCOMUtils.defineLazyGetter(this, "gUpdateBundle", function aus_gUpdateBundle() {
+ return Services.strings.createBundle(URI_UPDATES_PROPERTIES);
+});
+
+// shared code for suppressing bad cert dialogs
+XPCOMUtils.defineLazyGetter(this, "gCertUtils", function aus_gCertUtils() {
+ let temp = { };
+ Cu.import("resource://gre/modules/CertUtils.jsm", temp);
+ return temp;
+});
+
+/**
+ * Tests to make sure that we can write to a given directory.
+ *
+ * @param updateTestFile a test file in the directory that needs to be tested.
+ * @param createDirectory whether a test directory should be created.
+ * @throws if we don't have right access to the directory.
+ */
+function testWriteAccess(updateTestFile, createDirectory) {
+ const NORMAL_FILE_TYPE = Ci.nsILocalFile.NORMAL_FILE_TYPE;
+ const DIRECTORY_TYPE = Ci.nsILocalFile.DIRECTORY_TYPE;
+ if (updateTestFile.exists())
+ updateTestFile.remove(false);
+ updateTestFile.create(createDirectory ? DIRECTORY_TYPE : NORMAL_FILE_TYPE,
+ createDirectory ? FileUtils.PERMS_DIRECTORY : FileUtils.PERMS_FILE);
+ updateTestFile.remove(false);
+}
+
+/**
+ * Windows only function that closes a Win32 handle.
+ *
+ * @param handle The handle to close
+ */
+function closeHandle(handle) {
+ let lib = ctypes.open("kernel32.dll");
+ let CloseHandle = lib.declare("CloseHandle",
+ ctypes.winapi_abi,
+ ctypes.int32_t, /* success */
+ ctypes.void_t.ptr); /* handle */
+ CloseHandle(handle);
+ lib.close();
+}
+
+/**
+ * Windows only function that creates a mutex.
+ *
+ * @param aName
+ * The name for the mutex.
+ * @param aAllowExisting
+ * If false the function will close the handle and return null.
+ * @return The Win32 handle to the mutex.
+ */
+function createMutex(aName, aAllowExisting = true) {
+ if (AppConstants.platform != "win") {
+ throw Cr.NS_ERROR_NOT_IMPLEMENTED;
+ }
+
+ const INITIAL_OWN = 1;
+ const ERROR_ALREADY_EXISTS = 0xB7;
+ let lib = ctypes.open("kernel32.dll");
+ let CreateMutexW = lib.declare("CreateMutexW",
+ ctypes.winapi_abi,
+ ctypes.void_t.ptr, /* return handle */
+ ctypes.void_t.ptr, /* security attributes */
+ ctypes.int32_t, /* initial owner */
+ ctypes.char16_t.ptr); /* name */
+
+ let handle = CreateMutexW(null, INITIAL_OWN, aName);
+ let alreadyExists = ctypes.winLastError == ERROR_ALREADY_EXISTS;
+ if (handle && !handle.isNull() && !aAllowExisting && alreadyExists) {
+ closeHandle(handle);
+ handle = null;
+ }
+ lib.close();
+
+ if (handle && handle.isNull()) {
+ handle = null;
+ }
+
+ return handle;
+}
+
+/**
+ * Windows only function that determines a unique mutex name for the
+ * installation.
+ *
+ * @param aGlobal true if the function should return a global mutex. A global
+ * mutex is valid across different sessions
+ * @return Global mutex path
+ */
+function getPerInstallationMutexName(aGlobal = true) {
+ if (AppConstants.platform != "win") {
+ throw Cr.NS_ERROR_NOT_IMPLEMENTED;
+ }
+
+ let hasher = Cc["@mozilla.org/security/hash;1"].
+ createInstance(Ci.nsICryptoHash);
+ hasher.init(hasher.SHA1);
+
+ let exeFile = Services.dirsvc.get(KEY_EXECUTABLE, Ci.nsILocalFile);
+
+ let converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"].
+ createInstance(Ci.nsIScriptableUnicodeConverter);
+ converter.charset = "UTF-8";
+ var data = converter.convertToByteArray(exeFile.path.toLowerCase());
+
+ hasher.update(data, data.length);
+ return (aGlobal ? "Global\\" : "") + "MozillaUpdateMutex-" + hasher.finish(true);
+}
+
+/**
+ * Whether or not the current instance has the update mutex. The update mutex
+ * gives protection against 2 applications from the same installation updating:
+ * 1) Running multiple profiles from the same installation path
+ * 2) Two applications running in 2 different user sessions from the same path
+ *
+ * @return true if this instance holds the update mutex
+ */
+function hasUpdateMutex() {
+ if (AppConstants.platform != "win") {
+ return true;
+ }
+ if (!gUpdateMutexHandle) {
+ gUpdateMutexHandle = createMutex(getPerInstallationMutexName(true), false);
+ }
+ return !!gUpdateMutexHandle;
+}
+
+/**
+ * Determines whether or not all descendants of a directory are writeable.
+ * Note: Does not check the root directory itself for writeability.
+ *
+ * @return true if all descendants are writeable, false otherwise
+ */
+function areDirectoryEntriesWriteable(aDir) {
+ let items = aDir.directoryEntries;
+ while (items.hasMoreElements()) {
+ let item = items.getNext().QueryInterface(Ci.nsIFile);
+ if (!item.isWritable()) {
+ LOG("areDirectoryEntriesWriteable - unable to write to " + item.path);
+ return false;
+ }
+ if (item.isDirectory() && !areDirectoryEntriesWriteable(item)) {
+ return false;
+ }
+ }
+ return true;
+}
+
+/**
+ * OSX only function to determine if the user requires elevation to be able to
+ * write to the application bundle.
+ *
+ * @return true if elevation is required, false otherwise
+ */
+function getElevationRequired() {
+ if (AppConstants.platform != "macosx") {
+ return false;
+ }
+
+ try {
+ // Recursively check that the application bundle (and its descendants) can
+ // be written to.
+ LOG("getElevationRequired - recursively testing write access on " +
+ getInstallDirRoot().path);
+ if (!getInstallDirRoot().isWritable() ||
+ !areDirectoryEntriesWriteable(getInstallDirRoot())) {
+ LOG("getElevationRequired - unable to write to application bundle, " +
+ "elevation required");
+ return true;
+ }
+ } catch (ex) {
+ LOG("getElevationRequired - unable to write to application bundle, " +
+ "elevation required. Exception: " + ex);
+ return true;
+ }
+ LOG("getElevationRequired - able to write to application bundle, elevation " +
+ "not required");
+ return false;
+}
+
+/**
+ * Determines whether or not an update can be applied. This is always true on
+ * Windows when the service is used. Also, this is always true on OSX because we
+ * offer users the option to perform an elevated update when necessary.
+ *
+ * @return true if an update can be applied, false otherwise
+ */
+function getCanApplyUpdates() {
+ let useService = false;
+ if (shouldUseService()) {
+ // No need to perform directory write checks, the maintenance service will
+ // be able to write to all directories.
+ LOG("getCanApplyUpdates - bypass the write checks because we'll use the service");
+ useService = true;
+ }
+
+ if (!useService && AppConstants.platform != "macosx") {
+ try {
+ let updateTestFile = getUpdateFile([FILE_UPDATE_TEST]);
+ LOG("getCanApplyUpdates - testing write access " + updateTestFile.path);
+ testWriteAccess(updateTestFile, false);
+ if (AppConstants.platform == "win") {
+ // Example windowsVersion: Windows XP == 5.1
+ let windowsVersion = Services.sysinfo.getProperty("version");
+ LOG("getCanApplyUpdates - windowsVersion = " + windowsVersion);
+
+ /**
+ * For Vista, updates can be performed to a location requiring admin
+ * privileges by requesting elevation via the UAC prompt when launching
+ * updater.exe if the appDir is under the Program Files directory
+ * (e.g. C:\Program Files\) and UAC is turned on and we can elevate
+ * (e.g. user has a split token).
+ *
+ * Note: this does note attempt to handle the case where UAC is turned on
+ * and the installation directory is in a restricted location that
+ * requires admin privileges to update other than Program Files.
+ */
+ let userCanElevate = false;
+
+ if (parseFloat(windowsVersion) >= 6) {
+ try {
+ // KEY_UPDROOT will fail and throw an exception if
+ // appDir is not under the Program Files, so we rely on that
+ let dir = Services.dirsvc.get(KEY_UPDROOT, Ci.nsIFile);
+ // appDir is under Program Files, so check if the user can elevate
+ userCanElevate = Services.appinfo.QueryInterface(Ci.nsIWinAppHelper).
+ userCanElevate;
+ LOG("getCanApplyUpdates - on Vista, userCanElevate: " + userCanElevate);
+ }
+ catch (ex) {
+ // When the installation directory is not under Program Files,
+ // fall through to checking if write access to the
+ // installation directory is available.
+ LOG("getCanApplyUpdates - on Vista, appDir is not under Program Files");
+ }
+ }
+
+ /**
+ * On Windows, we no longer store the update under the app dir.
+ *
+ * If we are on Windows (including Vista, if we can't elevate) we need to
+ * to check that we can create and remove files from the actual app
+ * directory (like C:\Program Files\Mozilla Firefox). If we can't
+ * (because this user is not an adminstrator, for example) canUpdate()
+ * should return false.
+ *
+ * For Vista, we perform this check to enable updating the application
+ * when the user has write access to the installation directory under the
+ * following scenarios:
+ * 1) the installation directory is not under Program Files
+ * (e.g. C:\Program Files)
+ * 2) UAC is turned off
+ * 3) UAC is turned on and the user is not an admin
+ * (e.g. the user does not have a split token)
+ * 4) UAC is turned on and the user is already elevated, so they can't be
+ * elevated again
+ */
+ if (!userCanElevate) {
+ // if we're unable to create the test file this will throw an exception.
+ let appDirTestFile = getAppBaseDir();
+ appDirTestFile.append(FILE_UPDATE_TEST);
+ LOG("getCanApplyUpdates - testing write access " + appDirTestFile.path);
+ if (appDirTestFile.exists()) {
+ appDirTestFile.remove(false);
+ }
+ appDirTestFile.create(Ci.nsILocalFile.NORMAL_FILE_TYPE, FileUtils.PERMS_FILE);
+ appDirTestFile.remove(false);
+ }
+ }
+ } catch (e) {
+ LOG("getCanApplyUpdates - unable to apply updates. Exception: " + e);
+ // No write privileges to install directory
+ return false;
+ }
+ } // if (!useService)
+
+ LOG("getCanApplyUpdates - able to apply updates");
+ return true;
+}
+
+/**
+ * Whether or not the application can stage an update for the current session.
+ * These checks are only performed once per session due to using a lazy getter.
+ *
+ * @return true if updates can be staged for this session.
+ */
+XPCOMUtils.defineLazyGetter(this, "gCanStageUpdatesSession", function aus_gCSUS() {
+ if (getElevationRequired()) {
+ LOG("gCanStageUpdatesSession - unable to stage updates because elevation " +
+ "is required.");
+ return false;
+ }
+
+ try {
+ let updateTestFile;
+ if (AppConstants.platform == "macosx") {
+ updateTestFile = getUpdateFile([FILE_UPDATE_TEST]);
+ } else {
+ updateTestFile = getInstallDirRoot();
+ updateTestFile.append(FILE_UPDATE_TEST);
+ }
+ LOG("gCanStageUpdatesSession - testing write access " +
+ updateTestFile.path);
+ testWriteAccess(updateTestFile, true);
+ if (AppConstants.platform != "macosx") {
+ // On all platforms except Mac, we need to test the parent directory as
+ // well, as we need to be able to move files in that directory during the
+ // replacing step.
+ updateTestFile = getInstallDirRoot().parent;
+ updateTestFile.append(FILE_UPDATE_TEST);
+ LOG("gCanStageUpdatesSession - testing write access " +
+ updateTestFile.path);
+ updateTestFile.createUnique(Ci.nsILocalFile.DIRECTORY_TYPE,
+ FileUtils.PERMS_DIRECTORY);
+ updateTestFile.remove(false);
+ }
+ } catch (e) {
+ LOG("gCanStageUpdatesSession - unable to stage updates. Exception: " +
+ e);
+ // No write privileges
+ return false;
+ }
+
+ LOG("gCanStageUpdatesSession - able to stage updates");
+ return true;
+});
+
+/**
+ * Whether or not the application can stage an update.
+ *
+ * @return true if updates can be staged.
+ */
+function getCanStageUpdates() {
+ // If staging updates are disabled, then just bail out!
+ if (!getPref("getBoolPref", PREF_APP_UPDATE_STAGING_ENABLED, false)) {
+ LOG("getCanStageUpdates - staging updates is disabled by preference " +
+ PREF_APP_UPDATE_STAGING_ENABLED);
+ return false;
+ }
+
+ if (AppConstants.platform == "win" && shouldUseService()) {
+ // No need to perform directory write checks, the maintenance service will
+ // be able to write to all directories.
+ LOG("getCanStageUpdates - able to stage updates using the service");
+ return true;
+ }
+
+ // For Gonk, the updater will remount the /system partition to move staged
+ // files into place.
+ if (AppConstants.platform == "gonk") {
+ LOG("getCanStageUpdates - able to stage updates because this is gonk");
+ return true;
+ }
+
+ if (!hasUpdateMutex()) {
+ LOG("getCanStageUpdates - unable to apply updates because another " +
+ "instance of the application is already handling updates for this " +
+ "installation.");
+ return false;
+ }
+
+ return gCanStageUpdatesSession;
+}
+
+XPCOMUtils.defineLazyGetter(this, "gCanCheckForUpdates", function aus_gCanCheckForUpdates() {
+ // If the administrator has disabled app update and locked the preference so
+ // users can't check for updates. This preference check is ok in this lazy
+ // getter since locked prefs don't change until the application is restarted.
+ var enabled = getPref("getBoolPref", PREF_APP_UPDATE_ENABLED, true);
+ if (!enabled && Services.prefs.prefIsLocked(PREF_APP_UPDATE_ENABLED)) {
+ LOG("gCanCheckForUpdates - unable to automatically check for updates, " +
+ "the preference is disabled and admistratively locked.");
+ return false;
+ }
+
+ // If we don't know the binary platform we're updating, we can't update.
+ if (!UpdateUtils.ABI) {
+ LOG("gCanCheckForUpdates - unable to check for updates, unknown ABI");
+ return false;
+ }
+
+ // If we don't know the OS version we're updating, we can't update.
+ if (!UpdateUtils.OSVersion) {
+ LOG("gCanCheckForUpdates - unable to check for updates, unknown OS " +
+ "version");
+ return false;
+ }
+
+ LOG("gCanCheckForUpdates - able to check for updates");
+ return true;
+});
+
+/**
+ * Logs a string to the error console.
+ * @param string
+ * The string to write to the error console.
+ */
+function LOG(string) {
+ if (gLogEnabled) {
+ dump("*** AUS:SVC " + string + "\n");
+ Services.console.logStringMessage("AUS:SVC " + string);
+ }
+}
+
+/**
+ * Gets a preference value, handling the case where there is no default.
+ * @param func
+ * The name of the preference function to call, on nsIPrefBranch
+ * @param preference
+ * The name of the preference
+ * @param defaultValue
+ * The default value to return in the event the preference has
+ * no setting
+ * @return The value of the preference, or undefined if there was no
+ * user or default value.
+ */
+function getPref(func, preference, defaultValue) {
+ try {
+ return Services.prefs[func](preference);
+ }
+ catch (e) {
+ }
+ return defaultValue;
+}
+
+/**
+ * Convert a string containing binary values to hex.
+ */
+function binaryToHex(input) {
+ var result = "";
+ for (var i = 0; i < input.length; ++i) {
+ var hex = input.charCodeAt(i).toString(16);
+ if (hex.length == 1)
+ hex = "0" + hex;
+ result += hex;
+ }
+ return result;
+}
+
+/**
+ * Gets the specified directory at the specified hierarchy under the
+ * update root directory and creates it if it doesn't exist.
+ * @param pathArray
+ * An array of path components to locate beneath the directory
+ * specified by |key|
+ * @return nsIFile object for the location specified.
+ */
+function getUpdateDirCreate(pathArray) {
+ return FileUtils.getDir(KEY_UPDROOT, pathArray, true);
+}
+
+/**
+ * Gets the application base directory.
+ *
+ * @return nsIFile object for the application base directory.
+ */
+function getAppBaseDir() {
+ return Services.dirsvc.get(KEY_EXECUTABLE, Ci.nsIFile).parent;
+}
+
+/**
+ * Gets the root of the installation directory which is the application
+ * bundle directory on Mac OS X and the location of the application binary
+ * on all other platforms.
+ *
+ * @return nsIFile object for the directory
+ */
+function getInstallDirRoot() {
+ let dir = getAppBaseDir();
+ if (AppConstants.platform == "macosx") {
+ // On Mac, we store the Updated.app directory inside the bundle directory.
+ dir = dir.parent.parent;
+ }
+ return dir;
+}
+
+/**
+ * Gets the file at the specified hierarchy under the update root directory.
+ * @param pathArray
+ * An array of path components to locate beneath the directory
+ * specified by |key|. The last item in this array must be the
+ * leaf name of a file.
+ * @return nsIFile object for the file specified. The file is NOT created
+ * if it does not exist, however all required directories along
+ * the way are.
+ */
+function getUpdateFile(pathArray) {
+ let file = getUpdateDirCreate(pathArray.slice(0, -1));
+ file.append(pathArray[pathArray.length - 1]);
+ return file;
+}
+
+/**
+ * Returns human readable status text from the updates.properties bundle
+ * based on an error code
+ * @param code
+ * The error code to look up human readable status text for
+ * @param defaultCode
+ * The default code to look up should human readable status text
+ * not exist for |code|
+ * @return A human readable status text string
+ */
+function getStatusTextFromCode(code, defaultCode) {
+ let reason;
+ try {
+ reason = gUpdateBundle.GetStringFromName("check_error-" + code);
+ LOG("getStatusTextFromCode - transfer error: " + reason + ", code: " +
+ code);
+ }
+ catch (e) {
+ // Use the default reason
+ reason = gUpdateBundle.GetStringFromName("check_error-" + defaultCode);
+ LOG("getStatusTextFromCode - transfer error: " + reason +
+ ", default code: " + defaultCode);
+ }
+ return reason;
+}
+
+/**
+ * Get the Active Updates directory
+ * @return The active updates directory, as a nsIFile object
+ */
+function getUpdatesDir() {
+ // Right now, we only support downloading one patch at a time, so we always
+ // use the same target directory.
+ return getUpdateDirCreate([DIR_UPDATES, "0"]);
+}
+
+/**
+ * Reads the update state from the update.status file in the specified
+ * directory.
+ * @param dir
+ * The dir to look for an update.status file in
+ * @return The status value of the update.
+ */
+function readStatusFile(dir) {
+ let statusFile = dir.clone();
+ statusFile.append(FILE_UPDATE_STATUS);
+ let status = readStringFromFile(statusFile) || STATE_NONE;
+ LOG("readStatusFile - status: " + status + ", path: " + statusFile.path);
+ return status;
+}
+
+/**
+ * Writes the current update operation/state to a file in the patch
+ * directory, indicating to the patching system that operations need
+ * to be performed.
+ * @param dir
+ * The patch directory where the update.status file should be
+ * written.
+ * @param state
+ * The state value to write.
+ */
+function writeStatusFile(dir, state) {
+ let statusFile = dir.clone();
+ statusFile.append(FILE_UPDATE_STATUS);
+ writeStringToFile(statusFile, state);
+}
+
+/**
+ * Writes the update's application version to a file in the patch directory. If
+ * the update doesn't provide application version information via the
+ * appVersion attribute the string "null" will be written to the file.
+ * This value is compared during startup (in nsUpdateDriver.cpp) to determine if
+ * the update should be applied. Note that this won't provide protection from
+ * downgrade of the application for the nightly user case where the application
+ * version doesn't change.
+ * @param dir
+ * The patch directory where the update.version file should be
+ * written.
+ * @param version
+ * The version value to write. Will be the string "null" when the
+ * update doesn't provide the appVersion attribute in the update xml.
+ */
+function writeVersionFile(dir, version) {
+ let versionFile = dir.clone();
+ versionFile.append(FILE_UPDATE_VERSION);
+ writeStringToFile(versionFile, version);
+}
+
+/**
+ * Gonk only function that reads the link file specified in the update.link file
+ * in the specified directory and returns the nsIFile for the corresponding
+ * file.
+ * @param dir
+ * The dir to look for an update.link file in
+ * @return A nsIFile for the file path specified in the
+ * update.link file or null if the update.link file
+ * doesn't exist.
+ */
+function getFileFromUpdateLink(dir) {
+ if (AppConstants.platform != "gonk") {
+ throw Cr.NS_ERROR_NOT_IMPLEMENTED;
+ }
+ let linkFile = dir.clone();
+ linkFile.append(FILE_UPDATE_LINK);
+ let link = readStringFromFile(linkFile);
+ LOG("getFileFromUpdateLink linkFile.path: " + linkFile.path + ", link: " + link);
+ if (!link) {
+ return null;
+ }
+ let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
+ file.initWithPath(link);
+ return file;
+}
+
+/**
+ * Gonk only function to create a link file. This allows the actual patch to
+ * live in a directory different from the update directory.
+ * @param dir
+ * The patch directory where the update.link file
+ * should be written.
+ * @param patchFile
+ * The fully qualified filename of the patchfile.
+ */
+function writeLinkFile(dir, patchFile) {
+ if (AppConstants.platform != "gonk") {
+ throw Cr.NS_ERROR_NOT_IMPLEMENTED;
+ }
+ let linkFile = dir.clone();
+ linkFile.append(FILE_UPDATE_LINK);
+ writeStringToFile(linkFile, patchFile.path);
+ if (patchFile.path.indexOf(gExtStorage) == 0) {
+ // The patchfile is being stored on external storage. Try to lock it
+ // so that it doesn't get shared with the PC while we're downloading
+ // to it.
+ acquireSDCardMountLock();
+ }
+}
+
+/**
+ * Gonk only function to acquire a VolumeMountLock for the sdcard volume.
+ *
+ * This prevents the SDCard from being shared with the PC while
+ * we're downloading the update.
+ */
+function acquireSDCardMountLock() {
+ if (AppConstants.platform != "gonk") {
+ throw Cr.NS_ERROR_NOT_IMPLEMENTED;
+ }
+ let volsvc = Cc["@mozilla.org/telephony/volume-service;1"].
+ getService(Ci.nsIVolumeService);
+ if (volsvc) {
+ gSDCardMountLock = volsvc.createMountLock("sdcard");
+ }
+}
+
+/**
+ * Gonk only function that determines if the state corresponds to an
+ * interrupted update. This could either be because the download was
+ * interrupted, or because staging the update was interrupted.
+ *
+ * @return true if the state corresponds to an interrupted
+ * update.
+ */
+function isInterruptedUpdate(status) {
+ if (AppConstants.platform != "gonk") {
+ throw Cr.NS_ERROR_NOT_IMPLEMENTED;
+ }
+ return (status == STATE_DOWNLOADING) ||
+ (status == STATE_PENDING) ||
+ (status == STATE_APPLYING);
+}
+
+/**
+ * Releases any SDCard mount lock that we might have.
+ *
+ * This once again allows the SDCard to be shared with the PC.
+ */
+function releaseSDCardMountLock() {
+ if (AppConstants.platform != "gonk") {
+ throw Cr.NS_ERROR_UNEXPECTED;
+ }
+ if (gSDCardMountLock) {
+ gSDCardMountLock.unlock();
+ gSDCardMountLock = null;
+ }
+}
+
+/**
+ * Determines if the service should be used to attempt an update
+ * or not.
+ *
+ * @return true if the service should be used for updates.
+ */
+function shouldUseService() {
+ // This function will return true if the mantenance service should be used if
+ // all of the following conditions are met:
+ // 1) This build was done with the maintenance service enabled
+ // 2) The maintenance service is installed
+ // 3) The pref for using the service is enabled
+ // 4) The Windows version is XP Service Pack 3 or above (for SHA-2 support)
+ // The maintenance service requires SHA-2 support because we sign our binaries
+ // with a SHA-2 certificate and the certificate is verified before the binary
+ // is launched.
+ if (!AppConstants.MOZ_MAINTENANCE_SERVICE || !isServiceInstalled() ||
+ !getPref("getBoolPref", PREF_APP_UPDATE_SERVICE_ENABLED, false) ||
+ !AppConstants.isPlatformAndVersionAtLeast("win", "5.1") /* WinXP */) {
+ return false;
+ }
+
+ // If it's newer than XP, then the service pack doesn't matter.
+ if (Services.sysinfo.getProperty("version") != "5.1") {
+ return true;
+ }
+
+ // If the Windows version is XP, we also need to check the service pack.
+ // We'll return false if only < SP3 is installed, or if we can't tell.
+ // Check the service pack level by calling GetVersionEx via ctypes.
+ const BYTE = ctypes.uint8_t;
+ const WORD = ctypes.uint16_t;
+ const DWORD = ctypes.uint32_t;
+ const WCHAR = ctypes.char16_t;
+ const BOOL = ctypes.int;
+ // This structure is described at:
+ // http://msdn.microsoft.com/en-us/library/ms724833%28v=vs.85%29.aspx
+ const SZCSDVERSIONLENGTH = 128;
+ const OSVERSIONINFOEXW = new ctypes.StructType('OSVERSIONINFOEXW',
+ [
+ {dwOSVersionInfoSize: DWORD},
+ {dwMajorVersion: DWORD},
+ {dwMinorVersion: DWORD},
+ {dwBuildNumber: DWORD},
+ {dwPlatformId: DWORD},
+ {szCSDVersion: ctypes.ArrayType(WCHAR, SZCSDVERSIONLENGTH)},
+ {wServicePackMajor: WORD},
+ {wServicePackMinor: WORD},
+ {wSuiteMask: WORD},
+ {wProductType: BYTE},
+ {wReserved: BYTE}
+ ]);
+
+ let kernel32 = false;
+ try {
+ kernel32 = ctypes.open("Kernel32");
+ } catch (e) {
+ Cu.reportError("Unable to open kernel32! " + e);
+ return false;
+ }
+
+ if (kernel32) {
+ try {
+ try {
+ let GetVersionEx = kernel32.declare("GetVersionExW",
+ ctypes.default_abi,
+ BOOL,
+ OSVERSIONINFOEXW.ptr);
+ let winVer = OSVERSIONINFOEXW();
+ winVer.dwOSVersionInfoSize = OSVERSIONINFOEXW.size;
+
+ if (0 !== GetVersionEx(winVer.address())) {
+ return winVer.wServicePackMajor >= 3;
+ }
+ Cu.reportError("Unknown failure in GetVersionEX (returned 0)");
+ return false;
+ } catch (e) {
+ Cu.reportError("Error getting service pack information. Exception: " + e);
+ return false;
+ }
+ } finally {
+ kernel32.close();
+ }
+ }
+
+ // If the service pack check couldn't be done, assume we can't use the service.
+ return false;
+}
+
+/**
+ * Determines if the service is is installed.
+ *
+ * @return true if the service is installed.
+ */
+function isServiceInstalled() {
+ if (AppConstants.MOZ_MAINTENANCE_SERVICE && AppConstants.platform == "win") {
+ let installed = 0;
+ try {
+ let wrk = Cc["@mozilla.org/windows-registry-key;1"].
+ createInstance(Ci.nsIWindowsRegKey);
+ wrk.open(wrk.ROOT_KEY_LOCAL_MACHINE,
+ "SOFTWARE\\Mozilla\\MaintenanceService",
+ wrk.ACCESS_READ | wrk.WOW64_64);
+ installed = wrk.readIntValue("Installed");
+ wrk.close();
+ } catch (e) {
+ }
+ installed = installed == 1; // convert to bool
+ LOG("isServiceInstalled = " + installed);
+ return installed;
+ }
+ return false;
+}
+
+/**
+ * Removes the contents of the updates patch directory and rotates the update
+ * logs when present. If the update.log exists in the patch directory this will
+ * move the last-update.log if it exists to backup-update.log in the parent
+ * directory of the patch directory and then move the update.log in the patch
+ * directory to last-update.log in the parent directory of the patch directory.
+ *
+ * @param aRemovePatchFiles (optional, defaults to true)
+ * When true the update's patch directory contents are removed.
+ */
+function cleanUpUpdatesDir(aRemovePatchFiles = true) {
+ let updateDir;
+ try {
+ updateDir = getUpdatesDir();
+ } catch (e) {
+ LOG("cleanUpUpdatesDir - unable to get the updates patch directory. " +
+ "Exception: " + e);
+ return;
+ }
+
+ // Preserve the last update log file for debugging purposes.
+ let updateLogFile = updateDir.clone();
+ updateLogFile.append(FILE_UPDATE_LOG);
+ if (updateLogFile.exists()) {
+ let dir = updateDir.parent;
+ let logFile = dir.clone();
+ logFile.append(FILE_LAST_UPDATE_LOG);
+ if (logFile.exists()) {
+ try {
+ logFile.moveTo(dir, FILE_BACKUP_UPDATE_LOG);
+ } catch (e) {
+ LOG("cleanUpUpdatesDir - failed to rename file " + logFile.path +
+ " to " + FILE_BACKUP_UPDATE_LOG);
+ }
+ }
+
+ try {
+ updateLogFile.moveTo(dir, FILE_LAST_UPDATE_LOG);
+ } catch (e) {
+ LOG("cleanUpUpdatesDir - failed to rename file " + updateLogFile.path +
+ " to " + FILE_LAST_UPDATE_LOG);
+ }
+ }
+
+ if (aRemovePatchFiles) {
+ let dirEntries = updateDir.directoryEntries;
+ while (dirEntries.hasMoreElements()) {
+ let file = dirEntries.getNext().QueryInterface(Ci.nsIFile);
+ if (AppConstants.platform == "gonk") {
+ if (file.leafName == FILE_UPDATE_LINK) {
+ let linkedFile = getFileFromUpdateLink(updateDir);
+ if (linkedFile && linkedFile.exists()) {
+ linkedFile.remove(false);
+ }
+ }
+ }
+
+ // Now, recursively remove this file. The recursive removal is needed for
+ // Mac OSX because this directory will contain a copy of updater.app,
+ // which is itself a directory and the MozUpdater directory on platforms
+ // other than Windows.
+ try {
+ file.remove(true);
+ } catch (e) {
+ LOG("cleanUpUpdatesDir - failed to remove file " + file.path);
+ }
+ }
+ }
+ if (AppConstants.platform == "gonk") {
+ releaseSDCardMountLock();
+ }
+}
+
+/**
+ * Clean up updates list and the updates directory.
+ */
+function cleanupActiveUpdate() {
+ // Move the update from the Active Update list into the Past Updates list.
+ var um = Cc["@mozilla.org/updates/update-manager;1"].
+ getService(Ci.nsIUpdateManager);
+ um.activeUpdate = null;
+ um.saveUpdates();
+
+ // Now trash the updates directory, since we're done with it
+ cleanUpUpdatesDir();
+}
+
+/**
+ * An enumeration of items in a JS array.
+ * @constructor
+ */
+function ArrayEnumerator(aItems) {
+ this._index = 0;
+ if (aItems) {
+ for (var i = 0; i < aItems.length; ++i) {
+ if (!aItems[i])
+ aItems.splice(i, 1);
+ }
+ }
+ this._contents = aItems;
+}
+
+ArrayEnumerator.prototype = {
+ _index: 0,
+ _contents: [],
+
+ hasMoreElements: function ArrayEnumerator_hasMoreElements() {
+ return this._index < this._contents.length;
+ },
+
+ getNext: function ArrayEnumerator_getNext() {
+ return this._contents[this._index++];
+ }
+};
+
+/**
+ * Writes a string of text to a file. A newline will be appended to the data
+ * written to the file. This function only works with ASCII text.
+ */
+function writeStringToFile(file, text) {
+ let fos = FileUtils.openSafeFileOutputStream(file);
+ text += "\n";
+ fos.write(text, text.length);
+ FileUtils.closeSafeFileOutputStream(fos);
+}
+
+function readStringFromInputStream(inputStream) {
+ var sis = Cc["@mozilla.org/scriptableinputstream;1"].
+ createInstance(Ci.nsIScriptableInputStream);
+ sis.init(inputStream);
+ var text = sis.read(sis.available());
+ sis.close();
+ if (text && text[text.length - 1] == "\n") {
+ text = text.slice(0, -1);
+ }
+ return text;
+}
+
+/**
+ * Reads a string of text from a file. A trailing newline will be removed
+ * before the result is returned. This function only works with ASCII text.
+ */
+function readStringFromFile(file) {
+ if (!file.exists()) {
+ LOG("readStringFromFile - file doesn't exist: " + file.path);
+ return null;
+ }
+ var fis = Cc["@mozilla.org/network/file-input-stream;1"].
+ createInstance(Ci.nsIFileInputStream);
+ fis.init(file, FileUtils.MODE_RDONLY, FileUtils.PERMS_FILE, 0);
+ return readStringFromInputStream(fis);
+}
+
+function handleUpdateFailure(update, errorCode) {
+ update.errorCode = parseInt(errorCode);
+ if (update.errorCode == FOTA_GENERAL_ERROR ||
+ update.errorCode == FOTA_FILE_OPERATION_ERROR ||
+ update.errorCode == FOTA_RECOVERY_ERROR ||
+ update.errorCode == FOTA_UNKNOWN_ERROR) {
+ // In the case of FOTA update errors, don't reset the state to pending. This
+ // causes the FOTA update path to try again, which is not necessarily what
+ // we want.
+ update.statusText = gUpdateBundle.GetStringFromName("statusFailed");
+
+ Cc["@mozilla.org/updates/update-prompt;1"].
+ createInstance(Ci.nsIUpdatePrompt).
+ showUpdateError(update);
+ writeStatusFile(getUpdatesDir(), STATE_FAILED + ": " + errorCode);
+ cleanupActiveUpdate();
+ return true;
+ }
+
+ // Replace with Array.prototype.includes when it has stabilized.
+ if (WRITE_ERRORS.indexOf(update.errorCode) != -1 ||
+ update.errorCode == FILESYSTEM_MOUNT_READWRITE_ERROR) {
+ Cc["@mozilla.org/updates/update-prompt;1"].
+ createInstance(Ci.nsIUpdatePrompt).
+ showUpdateError(update);
+ writeStatusFile(getUpdatesDir(), update.state = STATE_PENDING);
+ return true;
+ }
+
+ if (update.errorCode == ELEVATION_CANCELED) {
+ let cancelations = getPref("getIntPref", PREF_APP_UPDATE_CANCELATIONS, 0);
+ cancelations++;
+ Services.prefs.setIntPref(PREF_APP_UPDATE_CANCELATIONS, cancelations);
+ if (AppConstants.platform == "macosx") {
+ let osxCancelations = getPref("getIntPref",
+ PREF_APP_UPDATE_CANCELATIONS_OSX, 0);
+ osxCancelations++;
+ Services.prefs.setIntPref(PREF_APP_UPDATE_CANCELATIONS_OSX,
+ osxCancelations);
+ let maxCancels = getPref("getIntPref",
+ PREF_APP_UPDATE_CANCELATIONS_OSX_MAX,
+ DEFAULT_CANCELATIONS_OSX_MAX);
+ // Prevent the preference from setting a value greater than 5.
+ maxCancels = Math.min(maxCancels, 5);
+ if (osxCancelations >= maxCancels) {
+ cleanupActiveUpdate();
+ } else {
+ writeStatusFile(getUpdatesDir(),
+ update.state = STATE_PENDING_ELEVATE);
+ }
+ update.statusText = gUpdateBundle.GetStringFromName("elevationFailure");
+ update.QueryInterface(Ci.nsIWritablePropertyBag);
+ update.setProperty("patchingFailed", "elevationFailure");
+ let prompter = Cc["@mozilla.org/updates/update-prompt;1"].
+ createInstance(Ci.nsIUpdatePrompt);
+ prompter.showUpdateError(update);
+ } else {
+ writeStatusFile(getUpdatesDir(), update.state = STATE_PENDING);
+ }
+ return true;
+ }
+
+ if (Services.prefs.prefHasUserValue(PREF_APP_UPDATE_CANCELATIONS)) {
+ Services.prefs.clearUserPref(PREF_APP_UPDATE_CANCELATIONS);
+ }
+ if (Services.prefs.prefHasUserValue(PREF_APP_UPDATE_CANCELATIONS_OSX)) {
+ Services.prefs.clearUserPref(PREF_APP_UPDATE_CANCELATIONS_OSX);
+ }
+
+ // Replace with Array.prototype.includes when it has stabilized.
+ if (SERVICE_ERRORS.indexOf(update.errorCode) != -1) {
+ var failCount = getPref("getIntPref",
+ PREF_APP_UPDATE_SERVICE_ERRORS, 0);
+ var maxFail = getPref("getIntPref",
+ PREF_APP_UPDATE_SERVICE_MAXERRORS,
+ DEFAULT_SERVICE_MAX_ERRORS);
+ // Prevent the preference from setting a value greater than 10.
+ maxFail = Math.min(maxFail, 10);
+ // As a safety, when the service reaches maximum failures, it will
+ // disable itself and fallback to using the normal update mechanism
+ // without the service.
+ if (failCount >= maxFail) {
+ Services.prefs.setBoolPref(PREF_APP_UPDATE_SERVICE_ENABLED, false);
+ Services.prefs.clearUserPref(PREF_APP_UPDATE_SERVICE_ERRORS);
+ } else {
+ failCount++;
+ Services.prefs.setIntPref(PREF_APP_UPDATE_SERVICE_ERRORS,
+ failCount);
+ }
+
+ writeStatusFile(getUpdatesDir(), update.state = STATE_PENDING);
+ return true;
+ }
+
+ if (Services.prefs.prefHasUserValue(PREF_APP_UPDATE_SERVICE_ERRORS)) {
+ Services.prefs.clearUserPref(PREF_APP_UPDATE_SERVICE_ERRORS);
+ }
+
+ return false;
+}
+
+/**
+ * Fall back to downloading a complete update in case an update has failed.
+ *
+ * @param update the update object that has failed to apply.
+ * @param postStaging true if we have just attempted to stage an update.
+ */
+function handleFallbackToCompleteUpdate(update, postStaging) {
+ cleanupActiveUpdate();
+
+ update.statusText = gUpdateBundle.GetStringFromName("patchApplyFailure");
+ var oldType = update.selectedPatch ? update.selectedPatch.type
+ : "complete";
+ if (update.selectedPatch && oldType == "partial" && update.patchCount == 2) {
+ // Partial patch application failed, try downloading the complete
+ // update in the background instead.
+ LOG("handleFallbackToCompleteUpdate - install of partial patch " +
+ "failed, downloading complete patch");
+ var status = Cc["@mozilla.org/updates/update-service;1"].
+ getService(Ci.nsIApplicationUpdateService).
+ downloadUpdate(update, !postStaging);
+ if (status == STATE_NONE)
+ cleanupActiveUpdate();
+ }
+ else {
+ LOG("handleFallbackToCompleteUpdate - install of complete or " +
+ "only one patch offered failed.");
+ }
+ update.QueryInterface(Ci.nsIWritablePropertyBag);
+ update.setProperty("patchingFailed", oldType);
+}
+
+function pingStateAndStatusCodes(aUpdate, aStartup, aStatus) {
+ let patchType = AUSTLMY.PATCH_UNKNOWN;
+ if (aUpdate && aUpdate.selectedPatch && aUpdate.selectedPatch.type) {
+ if (aUpdate.selectedPatch.type == "complete") {
+ patchType = AUSTLMY.PATCH_COMPLETE;
+ } else if (aUpdate.selectedPatch.type == "partial") {
+ patchType = AUSTLMY.PATCH_PARTIAL;
+ }
+ }
+
+ let suffix = patchType + "_" + (aStartup ? AUSTLMY.STARTUP : AUSTLMY.STAGE);
+ let stateCode = 0;
+ let parts = aStatus.split(":");
+ if (parts.length > 0) {
+ switch (parts[0]) {
+ case STATE_NONE:
+ stateCode = 2;
+ break;
+ case STATE_DOWNLOADING:
+ stateCode = 3;
+ break;
+ case STATE_PENDING:
+ stateCode = 4;
+ break;
+ case STATE_PENDING_SERVICE:
+ stateCode = 5;
+ break;
+ case STATE_APPLYING:
+ stateCode = 6;
+ break;
+ case STATE_APPLIED:
+ stateCode = 7;
+ break;
+ case STATE_APPLIED_OS:
+ stateCode = 8;
+ break;
+ case STATE_APPLIED_SERVICE:
+ stateCode = 9;
+ break;
+ case STATE_SUCCEEDED:
+ stateCode = 10;
+ break;
+ case STATE_DOWNLOAD_FAILED:
+ stateCode = 11;
+ break;
+ case STATE_FAILED:
+ stateCode = 12;
+ break;
+ case STATE_PENDING_ELEVATE:
+ stateCode = 13;
+ break;
+ default:
+ stateCode = 1;
+ }
+
+ if (parts.length > 1) {
+ let statusErrorCode = INVALID_UPDATER_STATE_CODE;
+ if (parts[0] == STATE_FAILED) {
+ statusErrorCode = parseInt(parts[1]) || INVALID_UPDATER_STATUS_CODE;
+ }
+ AUSTLMY.pingStatusErrorCode(suffix, statusErrorCode);
+ }
+ }
+ AUSTLMY.pingStateCode(suffix, stateCode);
+}
+
+/**
+ * Update Patch
+ * @param patch
+ * A <patch> element to initialize this object with
+ * @throws if patch has a size of 0
+ * @constructor
+ */
+function UpdatePatch(patch) {
+ this._properties = {};
+ for (var i = 0; i < patch.attributes.length; ++i) {
+ var attr = patch.attributes.item(i);
+ attr.QueryInterface(Ci.nsIDOMAttr);
+ switch (attr.name) {
+ case "selected":
+ this.selected = attr.value == "true";
+ break;
+ case "size":
+ if (0 == parseInt(attr.value)) {
+ LOG("UpdatePatch:init - 0-sized patch!");
+ throw Cr.NS_ERROR_ILLEGAL_VALUE;
+ }
+ // fall through
+ default:
+ this[attr.name] = attr.value;
+ break;
+ }
+ }
+}
+UpdatePatch.prototype = {
+ /**
+ * See nsIUpdateService.idl
+ */
+ serialize: function UpdatePatch_serialize(updates) {
+ var patch = updates.createElementNS(URI_UPDATE_NS, "patch");
+ patch.setAttribute("type", this.type);
+ patch.setAttribute("URL", this.URL);
+ // finalURL is not available until after the download has started
+ if (this.finalURL) {
+ patch.setAttribute("finalURL", this.finalURL);
+ }
+ patch.setAttribute("hashFunction", this.hashFunction);
+ patch.setAttribute("hashValue", this.hashValue);
+ patch.setAttribute("size", this.size);
+ if (this.selected) {
+ patch.setAttribute("selected", this.selected);
+ }
+ patch.setAttribute("state", this.state);
+
+ for (let p in this._properties) {
+ if (this._properties[p].present) {
+ patch.setAttribute(p, this._properties[p].data);
+ }
+ }
+
+ return patch;
+ },
+
+ /**
+ * A hash of custom properties
+ */
+ _properties: null,
+
+ /**
+ * See nsIWritablePropertyBag.idl
+ */
+ setProperty: function UpdatePatch_setProperty(name, value) {
+ this._properties[name] = { data: value, present: true };
+ },
+
+ /**
+ * See nsIWritablePropertyBag.idl
+ */
+ deleteProperty: function UpdatePatch_deleteProperty(name) {
+ if (name in this._properties)
+ this._properties[name].present = false;
+ else
+ throw Cr.NS_ERROR_FAILURE;
+ },
+
+ /**
+ * See nsIPropertyBag.idl
+ */
+ get enumerator() {
+ var properties = [];
+ for (var p in this._properties)
+ properties.push(this._properties[p].data);
+ return new ArrayEnumerator(properties);
+ },
+
+ /**
+ * See nsIPropertyBag.idl
+ * Note: returns null instead of throwing when the property doesn't exist to
+ * simplify code and to silence warnings in debug builds.
+ */
+ getProperty: function UpdatePatch_getProperty(name) {
+ if (name in this._properties &&
+ this._properties[name].present) {
+ return this._properties[name].data;
+ }
+ return null;
+ },
+
+ /**
+ * Returns whether or not the update.status file for this patch exists at the
+ * appropriate location.
+ */
+ get statusFileExists() {
+ var statusFile = getUpdatesDir();
+ statusFile.append(FILE_UPDATE_STATUS);
+ return statusFile.exists();
+ },
+
+ /**
+ * See nsIUpdateService.idl
+ */
+ get state() {
+ if (this._properties.state)
+ return this._properties.state;
+ return STATE_NONE;
+ },
+ set state(val) {
+ this._properties.state = val;
+ },
+
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIUpdatePatch,
+ Ci.nsIPropertyBag,
+ Ci.nsIWritablePropertyBag])
+};
+
+/**
+ * Update
+ * Implements nsIUpdate
+ * @param update
+ * An <update> element to initialize this object with
+ * @throws if the update contains no patches
+ * @constructor
+ */
+function Update(update) {
+ this._properties = {};
+ this._patches = [];
+ this.isCompleteUpdate = false;
+ this.isOSUpdate = false;
+ this.showPrompt = false;
+ this.showNeverForVersion = false;
+ this.unsupported = false;
+ this.channel = "default";
+ this.promptWaitTime = getPref("getIntPref", PREF_APP_UPDATE_PROMPTWAITTIME, 43200);
+ this.backgroundInterval = getPref("getIntPref", PREF_APP_UPDATE_BACKGROUNDINTERVAL,
+ DOWNLOAD_BACKGROUND_INTERVAL);
+
+ // Null <update>, assume this is a message container and do no
+ // further initialization
+ if (!update) {
+ return;
+ }
+
+ const ELEMENT_NODE = Ci.nsIDOMNode.ELEMENT_NODE;
+ let patch;
+ for (let i = 0; i < update.childNodes.length; ++i) {
+ let patchElement = update.childNodes.item(i);
+ if (patchElement.nodeType != ELEMENT_NODE ||
+ patchElement.localName != "patch") {
+ continue;
+ }
+
+ patchElement.QueryInterface(Ci.nsIDOMElement);
+ try {
+ patch = new UpdatePatch(patchElement);
+ } catch (e) {
+ continue;
+ }
+ this._patches.push(patch);
+ }
+
+ if (this._patches.length == 0 && !update.hasAttribute("unsupported")) {
+ throw Cr.NS_ERROR_ILLEGAL_VALUE;
+ }
+
+ // Set the installDate value with the current time. If the update has an
+ // installDate attribute this will be replaced with that value if it doesn't
+ // equal 0.
+ this.installDate = (new Date()).getTime();
+
+ for (let i = 0; i < update.attributes.length; ++i) {
+ var attr = update.attributes.item(i);
+ attr.QueryInterface(Ci.nsIDOMAttr);
+ if (attr.value == "undefined") {
+ continue;
+ } else if (attr.name == "detailsURL") {
+ this._detailsURL = attr.value;
+ } else if (attr.name == "installDate" && attr.value) {
+ let val = parseInt(attr.value);
+ if (val) {
+ this.installDate = val;
+ }
+ } else if (attr.name == "isCompleteUpdate") {
+ this.isCompleteUpdate = attr.value == "true";
+ } else if (attr.name == "isSecurityUpdate") {
+ this.isSecurityUpdate = attr.value == "true";
+ } else if (attr.name == "isOSUpdate") {
+ this.isOSUpdate = attr.value == "true";
+ } else if (attr.name == "showNeverForVersion") {
+ this.showNeverForVersion = attr.value == "true";
+ } else if (attr.name == "showPrompt") {
+ this.showPrompt = attr.value == "true";
+ } else if (attr.name == "promptWaitTime") {
+ if (!isNaN(attr.value)) {
+ this.promptWaitTime = parseInt(attr.value);
+ }
+ } else if (attr.name == "backgroundInterval") {
+ if (!isNaN(attr.value)) {
+ this.backgroundInterval = parseInt(attr.value);
+ }
+ } else if (attr.name == "unsupported") {
+ this.unsupported = attr.value == "true";
+ } else {
+ this[attr.name] = attr.value;
+
+ switch (attr.name) {
+ case "appVersion":
+ case "buildID":
+ case "channel":
+ case "displayVersion":
+ case "name":
+ case "previousAppVersion":
+ case "serviceURL":
+ case "statusText":
+ case "type":
+ break;
+ default:
+ // Save custom attributes when serializing to the local xml file but
+ // don't use this method for the expected attributes which are already
+ // handled in serialize.
+ this.setProperty(attr.name, attr.value);
+ break;
+ }
+ }
+ }
+
+ if (!this.displayVersion) {
+ this.displayVersion = this.appVersion;
+ }
+
+ // Don't allow the background download interval to be greater than 10 minutes.
+ this.backgroundInterval = Math.min(this.backgroundInterval, 600);
+
+ // The Update Name is either the string provided by the <update> element, or
+ // the string: "<App Name> <Update App Version>"
+ var name = "";
+ if (update.hasAttribute("name")) {
+ name = update.getAttribute("name");
+ } else {
+ var brandBundle = Services.strings.createBundle(URI_BRAND_PROPERTIES);
+ var appName = brandBundle.GetStringFromName("brandShortName");
+ name = gUpdateBundle.formatStringFromName("updateName",
+ [appName, this.displayVersion], 2);
+ }
+ this.name = name;
+}
+Update.prototype = {
+ /**
+ * See nsIUpdateService.idl
+ */
+ get patchCount() {
+ return this._patches.length;
+ },
+
+ /**
+ * See nsIUpdateService.idl
+ */
+ getPatchAt: function Update_getPatchAt(index) {
+ return this._patches[index];
+ },
+
+ /**
+ * See nsIUpdateService.idl
+ *
+ * We use a copy of the state cached on this object in |_state| only when
+ * there is no selected patch, i.e. in the case when we could not load
+ * |.activeUpdate| from the update manager for some reason but still have
+ * the update.status file to work with.
+ */
+ _state: "",
+ set state(state) {
+ if (this.selectedPatch)
+ this.selectedPatch.state = state;
+ this._state = state;
+ return state;
+ },
+ get state() {
+ if (this.selectedPatch)
+ return this.selectedPatch.state;
+ return this._state;
+ },
+
+ /**
+ * See nsIUpdateService.idl
+ */
+ errorCode: 0,
+
+ /**
+ * See nsIUpdateService.idl
+ */
+ get selectedPatch() {
+ for (var i = 0; i < this.patchCount; ++i) {
+ if (this._patches[i].selected)
+ return this._patches[i];
+ }
+ return null;
+ },
+
+ /**
+ * See nsIUpdateService.idl
+ */
+ get detailsURL() {
+ if (!this._detailsURL) {
+ try {
+ // Try using a default details URL supplied by the distribution
+ // if the update XML does not supply one.
+ return Services.urlFormatter.formatURLPref(PREF_APP_UPDATE_URL_DETAILS);
+ }
+ catch (e) {
+ }
+ }
+ return this._detailsURL || "";
+ },
+
+ /**
+ * See nsIUpdateService.idl
+ */
+ serialize: function Update_serialize(updates) {
+ // If appVersion isn't defined just return null. This happens when cleaning
+ // up invalid updates (e.g. incorrect channel).
+ if (!this.appVersion) {
+ return null;
+ }
+ var update = updates.createElementNS(URI_UPDATE_NS, "update");
+ update.setAttribute("appVersion", this.appVersion);
+ update.setAttribute("buildID", this.buildID);
+ update.setAttribute("channel", this.channel);
+ update.setAttribute("displayVersion", this.displayVersion);
+ update.setAttribute("installDate", this.installDate);
+ update.setAttribute("isCompleteUpdate", this.isCompleteUpdate);
+ update.setAttribute("isOSUpdate", this.isOSUpdate);
+ update.setAttribute("name", this.name);
+ update.setAttribute("serviceURL", this.serviceURL);
+ update.setAttribute("showNeverForVersion", this.showNeverForVersion);
+ update.setAttribute("showPrompt", this.showPrompt);
+ update.setAttribute("promptWaitTime", this.promptWaitTime);
+ update.setAttribute("backgroundInterval", this.backgroundInterval);
+ update.setAttribute("type", this.type);
+
+ if (this.detailsURL) {
+ update.setAttribute("detailsURL", this.detailsURL);
+ }
+ if (this.previousAppVersion) {
+ update.setAttribute("previousAppVersion", this.previousAppVersion);
+ }
+ if (this.statusText) {
+ update.setAttribute("statusText", this.statusText);
+ }
+ if (this.unsupported) {
+ update.setAttribute("unsupported", this.unsupported);
+ }
+ updates.documentElement.appendChild(update);
+
+ for (let p in this._properties) {
+ if (this._properties[p].present) {
+ update.setAttribute(p, this._properties[p].data);
+ }
+ }
+
+ for (let i = 0; i < this.patchCount; ++i) {
+ update.appendChild(this.getPatchAt(i).serialize(updates));
+ }
+
+ return update;
+ },
+
+ /**
+ * A hash of custom properties
+ */
+ _properties: null,
+
+ /**
+ * See nsIWritablePropertyBag.idl
+ */
+ setProperty: function Update_setProperty(name, value) {
+ this._properties[name] = { data: value, present: true };
+ },
+
+ /**
+ * See nsIWritablePropertyBag.idl
+ */
+ deleteProperty: function Update_deleteProperty(name) {
+ if (name in this._properties)
+ this._properties[name].present = false;
+ else
+ throw Cr.NS_ERROR_FAILURE;
+ },
+
+ /**
+ * See nsIPropertyBag.idl
+ */
+ get enumerator() {
+ var properties = [];
+ for (let p in this._properties) {
+ properties.push(this._properties[p].data);
+ }
+ return new ArrayEnumerator(properties);
+ },
+
+ /**
+ * See nsIPropertyBag.idl
+ * Note: returns null instead of throwing when the property doesn't exist to
+ * simplify code and to silence warnings in debug builds.
+ */
+ getProperty: function Update_getProperty(name) {
+ if (name in this._properties && this._properties[name].present) {
+ return this._properties[name].data;
+ }
+ return null;
+ },
+
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIUpdate,
+ Ci.nsIPropertyBag,
+ Ci.nsIWritablePropertyBag])
+};
+
+const UpdateServiceFactory = {
+ _instance: null,
+ createInstance: function (outer, iid) {
+ if (outer != null)
+ throw Cr.NS_ERROR_NO_AGGREGATION;
+ return this._instance == null ? this._instance = new UpdateService() :
+ this._instance;
+ }
+};
+
+/**
+ * UpdateService
+ * A Service for managing the discovery and installation of software updates.
+ * @constructor
+ */
+function UpdateService() {
+ LOG("Creating UpdateService");
+ Services.obs.addObserver(this, "xpcom-shutdown", false);
+ Services.prefs.addObserver(PREF_APP_UPDATE_LOG, this, false);
+ if (AppConstants.platform == "gonk") {
+ // PowerManagerService::SyncProfile (which is called for Reboot, PowerOff
+ // and Restart) sends the profile-change-net-teardown event. We can then
+ // pause the download in a similar manner to xpcom-shutdown.
+ Services.obs.addObserver(this, "profile-change-net-teardown", false);
+ }
+}
+
+UpdateService.prototype = {
+ /**
+ * The downloader we are using to download updates. There is only ever one of
+ * these.
+ */
+ _downloader: null,
+
+ /**
+ * Whether or not the service registered the "online" observer.
+ */
+ _registeredOnlineObserver: false,
+
+ /**
+ * The current number of consecutive socket errors
+ */
+ _consecutiveSocketErrors: 0,
+
+ /**
+ * A timer used to retry socket errors
+ */
+ _retryTimer: null,
+
+ /**
+ * Whether or not a background update check was initiated by the
+ * application update timer notification.
+ */
+ _isNotify: true,
+
+ /**
+ * Handle Observer Service notifications
+ * @param subject
+ * The subject of the notification
+ * @param topic
+ * The notification name
+ * @param data
+ * Additional data
+ */
+ observe: function AUS_observe(subject, topic, data) {
+ switch (topic) {
+ case "post-update-processing":
+ if (readStatusFile(getUpdatesDir()) == STATE_SUCCEEDED) {
+ // The active update needs to be copied to the first update in the
+ // updates.xml early during startup to support post update actions
+ // (bug 1301288).
+ let um = Cc["@mozilla.org/updates/update-manager;1"].
+ getService(Ci.nsIUpdateManager);
+ um.activeUpdate.state = STATE_SUCCEEDED;
+ um.saveUpdates();
+ Services.prefs.setBoolPref(PREF_APP_UPDATE_POSTUPDATE, true);
+ }
+
+ if (Services.appinfo.ID in APPID_TO_TOPIC) {
+ // Delay post-update processing to ensure that possible update
+ // dialogs are shown in front of the app window, if possible.
+ // See bug 311614.
+ Services.obs.addObserver(this, APPID_TO_TOPIC[Services.appinfo.ID],
+ false);
+ break;
+ }
+ // intentional fallthrough
+ case "sessionstore-windows-restored":
+ case "mail-startup-done":
+ case "xul-window-visible":
+ if (Services.appinfo.ID in APPID_TO_TOPIC) {
+ Services.obs.removeObserver(this,
+ APPID_TO_TOPIC[Services.appinfo.ID]);
+ }
+ // intentional fallthrough
+ case "test-post-update-processing":
+ // Clean up any extant updates
+ this._postUpdateProcessing();
+ break;
+ case "network:offline-status-changed":
+ this._offlineStatusChanged(data);
+ break;
+ case "nsPref:changed":
+ if (data == PREF_APP_UPDATE_LOG) {
+ gLogEnabled = getPref("getBoolPref", PREF_APP_UPDATE_LOG, false);
+ }
+ break;
+ case "profile-change-net-teardown": // fall thru
+ case "xpcom-shutdown":
+ Services.obs.removeObserver(this, topic);
+ Services.prefs.removeObserver(PREF_APP_UPDATE_LOG, this);
+
+ if (AppConstants.platform == "win" && gUpdateMutexHandle) {
+ // If we hold the update mutex, let it go!
+ // The OS would clean this up sometime after shutdown,
+ // but that would have no guarantee on timing.
+ closeHandle(gUpdateMutexHandle);
+ }
+ if (this._retryTimer) {
+ this._retryTimer.cancel();
+ }
+
+ this.pauseDownload();
+ // Prevent leaking the downloader (bug 454964)
+ this._downloader = null;
+ break;
+ }
+ },
+
+ /**
+ * The following needs to happen during the post-update-processing
+ * notification from nsUpdateServiceStub.js:
+ * 1. post update processing
+ * 2. resume of a download that was in progress during a previous session
+ * 3. start of a complete update download after the failure to apply a partial
+ * update
+ */
+
+ /**
+ * Perform post-processing on updates lingering in the updates directory
+ * from a previous application session - either report install failures (and
+ * optionally attempt to fetch a different version if appropriate) or
+ * notify the user of install success.
+ */
+ _postUpdateProcessing: function AUS__postUpdateProcessing() {
+ if (!this.canCheckForUpdates) {
+ LOG("UpdateService:_postUpdateProcessing - unable to check for " +
+ "updates... returning early");
+ return;
+ }
+
+ if (!this.canApplyUpdates) {
+ LOG("UpdateService:_postUpdateProcessing - unable to apply " +
+ "updates... returning early");
+ // If the update is present in the update directly somehow,
+ // it would prevent us from notifying the user of futher updates.
+ cleanupActiveUpdate();
+ return;
+ }
+
+ var um = Cc["@mozilla.org/updates/update-manager;1"].
+ getService(Ci.nsIUpdateManager);
+ var update = um.activeUpdate;
+ var status = readStatusFile(getUpdatesDir());
+ pingStateAndStatusCodes(update, true, status);
+ // STATE_NONE status typically means that the update.status file is present
+ // but a background download error occurred.
+ if (status == STATE_NONE) {
+ LOG("UpdateService:_postUpdateProcessing - no status, no update");
+ cleanupActiveUpdate();
+ return;
+ }
+
+ // Handle the case when the update is the same or older than the current
+ // version and nsUpdateDriver.cpp skipped updating due to the version being
+ // older than the current version.
+ if (update && update.appVersion &&
+ (status == STATE_PENDING || status == STATE_PENDING_SERVICE ||
+ status == STATE_APPLIED || status == STATE_APPLIED_SERVICE ||
+ status == STATE_PENDING_ELEVATE)) {
+ if (Services.vc.compare(update.appVersion, Services.appinfo.version) < 0 ||
+ Services.vc.compare(update.appVersion, Services.appinfo.version) == 0 &&
+ update.buildID == Services.appinfo.appBuildID) {
+ LOG("UpdateService:_postUpdateProcessing - removing update for older " +
+ "or same application version");
+ cleanupActiveUpdate();
+ return;
+ }
+ }
+
+ if (AppConstants.platform == "gonk") {
+ // This code is called very early in the boot process, before we've even
+ // had a chance to setup the UI so we can give feedback to the user.
+ //
+ // Since the download may be occuring over a link which has associated
+ // cost, we want to require user-consent before resuming the download.
+ // Also, applying an already downloaded update now is undesireable,
+ // since the phone will look dead while the update is being applied.
+ // Applying the update can take several minutes. Instead we wait until
+ // the UI is initialized so it is possible to give feedback to and get
+ // consent to update from the user.
+ if (isInterruptedUpdate(status)) {
+ LOG("UpdateService:_postUpdateProcessing - interrupted update detected - wait for user consent");
+ return;
+ }
+ }
+
+ if (status == STATE_DOWNLOADING) {
+ LOG("UpdateService:_postUpdateProcessing - patch found in downloading " +
+ "state");
+ if (update && update.state != STATE_SUCCEEDED) {
+ // Resume download
+ status = this.downloadUpdate(update, true);
+ if (status == STATE_NONE)
+ cleanupActiveUpdate();
+ }
+ return;
+ }
+
+ if (status == STATE_APPLYING) {
+ // This indicates that the background updater service is in either of the
+ // following two states:
+ // 1. It is in the process of applying an update in the background, and
+ // we just happen to be racing against that.
+ // 2. It has failed to apply an update for some reason, and we hit this
+ // case because the updater process has set the update status to
+ // applying, but has never finished.
+ // In order to differentiate between these two states, we look at the
+ // state field of the update object. If it's "pending", then we know
+ // that this is the first time we're hitting this case, so we switch
+ // that state to "applying" and we just wait and hope for the best.
+ // If it's "applying", we know that we've already been here once, so
+ // we really want to start from a clean state.
+ if (update &&
+ (update.state == STATE_PENDING ||
+ update.state == STATE_PENDING_SERVICE)) {
+ LOG("UpdateService:_postUpdateProcessing - patch found in applying " +
+ "state for the first time");
+ update.state = STATE_APPLYING;
+ um.saveUpdates();
+ } else { // We get here even if we don't have an update object
+ LOG("UpdateService:_postUpdateProcessing - patch found in applying " +
+ "state for the second time");
+ cleanupActiveUpdate();
+ }
+ return;
+ }
+
+ if (AppConstants.platform == "gonk") {
+ // The update is only applied but not selected to be installed
+ if (status == STATE_APPLIED && update && update.isOSUpdate) {
+ LOG("UpdateService:_postUpdateProcessing - update staged as applied found");
+ return;
+ }
+
+ if (status == STATE_APPLIED_OS && update && update.isOSUpdate) {
+ // In gonk, we need to check for OS update status after startup, since
+ // the recovery partition won't write to update.status for us
+ let recoveryService = Cc["@mozilla.org/recovery-service;1"].
+ getService(Ci.nsIRecoveryService);
+ let fotaStatus = recoveryService.getFotaUpdateStatus();
+ switch (fotaStatus) {
+ case Ci.nsIRecoveryService.FOTA_UPDATE_SUCCESS:
+ status = STATE_SUCCEEDED;
+ break;
+ case Ci.nsIRecoveryService.FOTA_UPDATE_FAIL:
+ status = STATE_FAILED + ": " + FOTA_GENERAL_ERROR;
+ break;
+ case Ci.nsIRecoveryService.FOTA_UPDATE_UNKNOWN:
+ default:
+ status = STATE_FAILED + ": " + FOTA_UNKNOWN_ERROR;
+ break;
+ }
+ }
+ }
+
+ if (!update) {
+ if (status != STATE_SUCCEEDED) {
+ LOG("UpdateService:_postUpdateProcessing - previous patch failed " +
+ "and no patch available");
+ cleanupActiveUpdate();
+ return;
+ }
+ update = new Update(null);
+ }
+
+ let parts = status.split(":");
+ update.state = parts[0];
+ if (update.state == STATE_FAILED && parts[1]) {
+ update.errorCode = parseInt(parts[1]);
+ }
+
+
+ if (status != STATE_SUCCEEDED) {
+ // Since the update didn't succeed save a copy of the active update's
+ // current state to the updates.xml so it is possible to track failures.
+ um.saveUpdates();
+ // Rotate the update logs so the update log isn't removed. By passing
+ // false the patch directory won't be removed.
+ cleanUpUpdatesDir(false);
+ }
+
+ if (status == STATE_SUCCEEDED) {
+ update.statusText = gUpdateBundle.GetStringFromName("installSuccess");
+
+ // Update the patch's metadata.
+ um.activeUpdate = update;
+
+ // Done with this update. Clean it up.
+ cleanupActiveUpdate();
+ } else if (status == STATE_PENDING_ELEVATE) {
+ let prompter = Cc["@mozilla.org/updates/update-prompt;1"].
+ createInstance(Ci.nsIUpdatePrompt);
+ prompter.showUpdateElevationRequired();
+ return;
+ } else {
+ // If there was an I/O error it is assumed that the patch is not invalid
+ // and it is set to pending so an attempt to apply it again will happen
+ // when the application is restarted.
+ if (update.state == STATE_FAILED && update.errorCode) {
+ if (handleUpdateFailure(update, update.errorCode)) {
+ return;
+ }
+ }
+
+ // Something went wrong with the patch application process.
+ handleFallbackToCompleteUpdate(update, false);
+ let prompter = Cc["@mozilla.org/updates/update-prompt;1"].
+ createInstance(Ci.nsIUpdatePrompt);
+ prompter.showUpdateError(update);
+ }
+ },
+
+ /**
+ * Register an observer when the network comes online, so we can short-circuit
+ * the app.update.interval when there isn't connectivity
+ */
+ _registerOnlineObserver: function AUS__registerOnlineObserver() {
+ if (this._registeredOnlineObserver) {
+ LOG("UpdateService:_registerOnlineObserver - observer already registered");
+ return;
+ }
+
+ LOG("UpdateService:_registerOnlineObserver - waiting for the network to " +
+ "be online, then forcing another check");
+
+ Services.obs.addObserver(this, "network:offline-status-changed", false);
+ this._registeredOnlineObserver = true;
+ },
+
+ /**
+ * Called from the network:offline-status-changed observer.
+ */
+ _offlineStatusChanged: function AUS__offlineStatusChanged(status) {
+ if (status !== "online") {
+ return;
+ }
+
+ Services.obs.removeObserver(this, "network:offline-status-changed");
+ this._registeredOnlineObserver = false;
+
+ LOG("UpdateService:_offlineStatusChanged - network is online, forcing " +
+ "another background check");
+
+ // the background checker is contained in notify
+ this._attemptResume();
+ },
+
+ onCheckComplete: function AUS_onCheckComplete(request, updates, updateCount) {
+ this._selectAndInstallUpdate(updates);
+ },
+
+ onError: function AUS_onError(request, update) {
+ LOG("UpdateService:onError - error during background update. error code: " +
+ update.errorCode + ", status text: " + update.statusText);
+
+ if (update.errorCode == NETWORK_ERROR_OFFLINE) {
+ // Register an online observer to try again
+ this._registerOnlineObserver();
+ if (this._pingSuffix) {
+ AUSTLMY.pingCheckCode(this._pingSuffix, AUSTLMY.CHK_OFFLINE);
+ }
+ return;
+ }
+
+ // Send the error code to telemetry
+ AUSTLMY.pingCheckExError(this._pingSuffix, update.errorCode);
+ update.errorCode = BACKGROUNDCHECK_MULTIPLE_FAILURES;
+ let errCount = getPref("getIntPref", PREF_APP_UPDATE_BACKGROUNDERRORS, 0);
+ errCount++;
+ Services.prefs.setIntPref(PREF_APP_UPDATE_BACKGROUNDERRORS, errCount);
+ // Don't allow the preference to set a value greater than 20 for max errors.
+ let maxErrors = Math.min(getPref("getIntPref", PREF_APP_UPDATE_BACKGROUNDMAXERRORS, 10), 20);
+
+ if (errCount >= maxErrors) {
+ let prompter = Cc["@mozilla.org/updates/update-prompt;1"].
+ createInstance(Ci.nsIUpdatePrompt);
+ prompter.showUpdateError(update);
+ AUSTLMY.pingCheckCode(this._pingSuffix, AUSTLMY.CHK_GENERAL_ERROR_PROMPT);
+ } else {
+ AUSTLMY.pingCheckCode(this._pingSuffix, AUSTLMY.CHK_GENERAL_ERROR_SILENT);
+ }
+ },
+
+ /**
+ * Called when a connection should be resumed
+ */
+ _attemptResume: function AUS_attemptResume() {
+ LOG("UpdateService:_attemptResume");
+ // If a download is in progress, then resume it.
+ if (this._downloader && this._downloader._patch &&
+ this._downloader._patch.state == STATE_DOWNLOADING &&
+ this._downloader._update) {
+ LOG("UpdateService:_attemptResume - _patch.state: " +
+ this._downloader._patch.state);
+ // Make sure downloading is the state for selectPatch to work correctly
+ writeStatusFile(getUpdatesDir(), STATE_DOWNLOADING);
+ var status = this.downloadUpdate(this._downloader._update,
+ this._downloader.background);
+ LOG("UpdateService:_attemptResume - downloadUpdate status: " + status);
+ if (status == STATE_NONE) {
+ cleanupActiveUpdate();
+ }
+ return;
+ }
+
+ this.backgroundChecker.checkForUpdates(this, false);
+ },
+
+ /**
+ * Notified when a timer fires
+ * @param timer
+ * The timer that fired
+ */
+ notify: function AUS_notify(timer) {
+ this._checkForBackgroundUpdates(true);
+ },
+
+ /**
+ * See nsIUpdateService.idl
+ */
+ checkForBackgroundUpdates: function AUS_checkForBackgroundUpdates() {
+ this._checkForBackgroundUpdates(false);
+ },
+
+ // The suffix used for background update check telemetry histogram ID's.
+ get _pingSuffix() {
+ return this._isNotify ? AUSTLMY.NOTIFY : AUSTLMY.EXTERNAL;
+ },
+
+ /**
+ * Checks for updates in the background.
+ * @param isNotify
+ * Whether or not a background update check was initiated by the
+ * application update timer notification.
+ */
+ _checkForBackgroundUpdates: function AUS__checkForBackgroundUpdates(isNotify) {
+ this._isNotify = isNotify;
+
+ // Histogram IDs:
+ // UPDATE_PING_COUNT_EXTERNAL
+ // UPDATE_PING_COUNT_NOTIFY
+ AUSTLMY.pingGeneric("UPDATE_PING_COUNT_" + this._pingSuffix,
+ true, false);
+
+ // Histogram IDs:
+ // UPDATE_UNABLE_TO_APPLY_EXTERNAL
+ // UPDATE_UNABLE_TO_APPLY_NOTIFY
+ AUSTLMY.pingGeneric("UPDATE_UNABLE_TO_APPLY_" + this._pingSuffix,
+ getCanApplyUpdates(), true);
+ // Histogram IDs:
+ // UPDATE_CANNOT_STAGE_EXTERNAL
+ // UPDATE_CANNOT_STAGE_NOTIFY
+ AUSTLMY.pingGeneric("UPDATE_CANNOT_STAGE_" + this._pingSuffix,
+ getCanStageUpdates(), true);
+ // Histogram IDs:
+ // UPDATE_INVALID_LASTUPDATETIME_EXTERNAL
+ // UPDATE_INVALID_LASTUPDATETIME_NOTIFY
+ // UPDATE_LAST_NOTIFY_INTERVAL_DAYS_EXTERNAL
+ // UPDATE_LAST_NOTIFY_INTERVAL_DAYS_NOTIFY
+ AUSTLMY.pingLastUpdateTime(this._pingSuffix);
+ // Histogram IDs:
+ // UPDATE_NOT_PREF_UPDATE_ENABLED_EXTERNAL
+ // UPDATE_NOT_PREF_UPDATE_ENABLED_NOTIFY
+ AUSTLMY.pingBoolPref("UPDATE_NOT_PREF_UPDATE_ENABLED_" + this._pingSuffix,
+ PREF_APP_UPDATE_ENABLED, true, true);
+ // Histogram IDs:
+ // UPDATE_NOT_PREF_UPDATE_AUTO_EXTERNAL
+ // UPDATE_NOT_PREF_UPDATE_AUTO_NOTIFY
+ AUSTLMY.pingBoolPref("UPDATE_NOT_PREF_UPDATE_AUTO_" + this._pingSuffix,
+ PREF_APP_UPDATE_AUTO, true, true);
+ // Histogram IDs:
+ // UPDATE_NOT_PREF_UPDATE_STAGING_ENABLED_EXTERNAL
+ // UPDATE_NOT_PREF_UPDATE_STAGING_ENABLED_NOTIFY
+ AUSTLMY.pingBoolPref("UPDATE_NOT_PREF_UPDATE_STAGING_ENABLED_" +
+ this._pingSuffix,
+ PREF_APP_UPDATE_STAGING_ENABLED, true, true);
+ if (AppConstants.platform == "win" || AppConstants.platform == "macosx") {
+ // Histogram IDs:
+ // UPDATE_PREF_UPDATE_CANCELATIONS_EXTERNAL
+ // UPDATE_PREF_UPDATE_CANCELATIONS_NOTIFY
+ AUSTLMY.pingIntPref("UPDATE_PREF_UPDATE_CANCELATIONS_" + this._pingSuffix,
+ PREF_APP_UPDATE_CANCELATIONS, 0, 0);
+ }
+ if (AppConstants.platform == "macosx") {
+ // Histogram IDs:
+ // UPDATE_PREF_UPDATE_CANCELATIONS_OSX_EXTERNAL
+ // UPDATE_PREF_UPDATE_CANCELATIONS_OSX_NOTIFY
+ AUSTLMY.pingIntPref("UPDATE_PREF_UPDATE_CANCELATIONS_OSX_" +
+ this._pingSuffix,
+ PREF_APP_UPDATE_CANCELATIONS_OSX, 0, 0);
+ }
+ if (AppConstants.MOZ_MAINTENANCE_SERVICE) {
+ // Histogram IDs:
+ // UPDATE_NOT_PREF_UPDATE_SERVICE_ENABLED_EXTERNAL
+ // UPDATE_NOT_PREF_UPDATE_SERVICE_ENABLED_NOTIFY
+ AUSTLMY.pingBoolPref("UPDATE_NOT_PREF_UPDATE_SERVICE_ENABLED_" +
+ this._pingSuffix,
+ PREF_APP_UPDATE_SERVICE_ENABLED, true);
+ // Histogram IDs:
+ // UPDATE_PREF_SERVICE_ERRORS_EXTERNAL
+ // UPDATE_PREF_SERVICE_ERRORS_NOTIFY
+ AUSTLMY.pingIntPref("UPDATE_PREF_SERVICE_ERRORS_" + this._pingSuffix,
+ PREF_APP_UPDATE_SERVICE_ERRORS, 0, 0);
+ if (AppConstants.platform == "win") {
+ // Histogram IDs:
+ // UPDATE_SERVICE_INSTALLED_EXTERNAL
+ // UPDATE_SERVICE_INSTALLED_NOTIFY
+ // UPDATE_SERVICE_MANUALLY_UNINSTALLED_EXTERNAL
+ // UPDATE_SERVICE_MANUALLY_UNINSTALLED_NOTIFY
+ AUSTLMY.pingServiceInstallStatus(this._pingSuffix, isServiceInstalled());
+ }
+ }
+
+ // If a download is in progress or the patch has been staged do nothing.
+ if (this.isDownloading) {
+ AUSTLMY.pingCheckCode(this._pingSuffix, AUSTLMY.CHK_IS_DOWNLOADING);
+ return;
+ }
+
+ if (this._downloader && this._downloader.patchIsStaged) {
+ let readState = readStatusFile(getUpdatesDir());
+ if (readState == STATE_PENDING || readState == STATE_PENDING_SERVICE ||
+ readState == STATE_PENDING_ELEVATE) {
+ AUSTLMY.pingCheckCode(this._pingSuffix, AUSTLMY.CHK_IS_DOWNLOADED);
+ } else {
+ AUSTLMY.pingCheckCode(this._pingSuffix, AUSTLMY.CHK_IS_STAGED);
+ }
+ return;
+ }
+
+ let validUpdateURL = true;
+ try {
+ this.backgroundChecker.getUpdateURL(false);
+ } catch (e) {
+ validUpdateURL = false;
+ }
+ // The following checks are done here so they can be differentiated from
+ // foreground checks.
+ if (!UpdateUtils.OSVersion) {
+ AUSTLMY.pingCheckCode(this._pingSuffix, AUSTLMY.CHK_NO_OS_VERSION);
+ } else if (!UpdateUtils.ABI) {
+ AUSTLMY.pingCheckCode(this._pingSuffix, AUSTLMY.CHK_NO_OS_ABI);
+ } else if (!validUpdateURL) {
+ AUSTLMY.pingCheckCode(this._pingSuffix,
+ AUSTLMY.CHK_INVALID_DEFAULT_URL);
+ } else if (!getPref("getBoolPref", PREF_APP_UPDATE_ENABLED, true)) {
+ AUSTLMY.pingCheckCode(this._pingSuffix, AUSTLMY.CHK_PREF_DISABLED);
+ } else if (!hasUpdateMutex()) {
+ AUSTLMY.pingCheckCode(this._pingSuffix, AUSTLMY.CHK_NO_MUTEX);
+ } else if (!gCanCheckForUpdates) {
+ AUSTLMY.pingCheckCode(this._pingSuffix, AUSTLMY.CHK_UNABLE_TO_CHECK);
+ } else if (!this.backgroundChecker._enabled) {
+ AUSTLMY.pingCheckCode(this._pingSuffix, AUSTLMY.CHK_DISABLED_FOR_SESSION);
+ }
+
+ this.backgroundChecker.checkForUpdates(this, false);
+ },
+
+ /**
+ * Determine the update from the specified updates that should be offered.
+ * If both valid major and minor updates are available the minor update will
+ * be offered.
+ * @param updates
+ * An array of available nsIUpdate items
+ * @return The nsIUpdate to offer.
+ */
+ selectUpdate: function AUS_selectUpdate(updates) {
+ if (updates.length == 0) {
+ AUSTLMY.pingCheckCode(this._pingSuffix, AUSTLMY.CHK_NO_UPDATE_FOUND);
+ return null;
+ }
+
+ // The ping for unsupported is sent after the call to showPrompt.
+ if (updates.length == 1 && updates[0].unsupported) {
+ return updates[0];
+ }
+
+ // Choose the newest of the available minor and major updates.
+ var majorUpdate = null;
+ var minorUpdate = null;
+ var vc = Services.vc;
+ let lastCheckCode = AUSTLMY.CHK_NO_COMPAT_UPDATE_FOUND;
+
+ updates.forEach(function(aUpdate) {
+ // Ignore updates for older versions of the application and updates for
+ // the same version of the application with the same build ID.
+ if (vc.compare(aUpdate.appVersion, Services.appinfo.version) < 0 ||
+ vc.compare(aUpdate.appVersion, Services.appinfo.version) == 0 &&
+ aUpdate.buildID == Services.appinfo.appBuildID) {
+ LOG("UpdateService:selectUpdate - skipping update because the " +
+ "update's application version is less than the current " +
+ "application version");
+ lastCheckCode = AUSTLMY.CHK_UPDATE_PREVIOUS_VERSION;
+ return;
+ }
+
+ // Skip the update if the user responded with "never" to this update's
+ // application version and the update specifies showNeverForVersion
+ // (see bug 350636).
+ let neverPrefName = PREFBRANCH_APP_UPDATE_NEVER + aUpdate.appVersion;
+ if (aUpdate.showNeverForVersion &&
+ getPref("getBoolPref", neverPrefName, false)) {
+ LOG("UpdateService:selectUpdate - skipping update because the " +
+ "preference " + neverPrefName + " is true");
+ lastCheckCode = AUSTLMY.CHK_UPDATE_NEVER_PREF;
+ return;
+ }
+
+ switch (aUpdate.type) {
+ case "major":
+ if (!majorUpdate)
+ majorUpdate = aUpdate;
+ else if (vc.compare(majorUpdate.appVersion, aUpdate.appVersion) <= 0)
+ majorUpdate = aUpdate;
+ break;
+ case "minor":
+ if (!minorUpdate)
+ minorUpdate = aUpdate;
+ else if (vc.compare(minorUpdate.appVersion, aUpdate.appVersion) <= 0)
+ minorUpdate = aUpdate;
+ break;
+ default:
+ LOG("UpdateService:selectUpdate - skipping unknown update type: " +
+ aUpdate.type);
+ lastCheckCode = AUSTLMY.CHK_UPDATE_INVALID_TYPE;
+ break;
+ }
+ });
+
+ let update = minorUpdate || majorUpdate;
+ if (AppConstants.platform == "macosx" && update) {
+ if (getElevationRequired()) {
+ let installAttemptVersion = getPref("getCharPref",
+ PREF_APP_UPDATE_ELEVATE_VERSION,
+ null);
+ if (vc.compare(installAttemptVersion, update.appVersion) != 0) {
+ Services.prefs.setCharPref(PREF_APP_UPDATE_ELEVATE_VERSION,
+ update.appVersion);
+ if (Services.prefs.prefHasUserValue(
+ PREF_APP_UPDATE_CANCELATIONS_OSX)) {
+ Services.prefs.clearUserPref(PREF_APP_UPDATE_CANCELATIONS_OSX);
+ }
+ if (Services.prefs.prefHasUserValue(PREF_APP_UPDATE_ELEVATE_NEVER)) {
+ Services.prefs.clearUserPref(PREF_APP_UPDATE_ELEVATE_NEVER);
+ }
+ } else {
+ let numCancels = getPref("getIntPref",
+ PREF_APP_UPDATE_CANCELATIONS_OSX, 0);
+ let rejectedVersion = getPref("getCharPref",
+ PREF_APP_UPDATE_ELEVATE_NEVER, "");
+ let maxCancels = getPref("getIntPref",
+ PREF_APP_UPDATE_CANCELATIONS_OSX_MAX,
+ DEFAULT_CANCELATIONS_OSX_MAX);
+ if (numCancels >= maxCancels) {
+ LOG("UpdateService:selectUpdate - the user requires elevation to " +
+ "install this update, but the user has exceeded the max " +
+ "number of elevation attempts.");
+ update.elevationFailure = true;
+ AUSTLMY.pingCheckCode(
+ this._pingSuffix,
+ AUSTLMY.CHK_ELEVATION_DISABLED_FOR_VERSION);
+ } else if (vc.compare(rejectedVersion, update.appVersion) == 0) {
+ LOG("UpdateService:selectUpdate - the user requires elevation to " +
+ "install this update, but elevation is disabled for this " +
+ "version.");
+ update.elevationFailure = true;
+ AUSTLMY.pingCheckCode(this._pingSuffix,
+ AUSTLMY.CHK_ELEVATION_OPTOUT_FOR_VERSION);
+ } else {
+ LOG("UpdateService:selectUpdate - the user requires elevation to " +
+ "install the update.");
+ }
+ }
+ } else {
+ // Clear elevation-related prefs since they no longer apply (the user
+ // may have gained write access to the Firefox directory or an update
+ // was executed with a different profile).
+ if (Services.prefs.prefHasUserValue(PREF_APP_UPDATE_ELEVATE_VERSION)) {
+ Services.prefs.clearUserPref(PREF_APP_UPDATE_ELEVATE_VERSION);
+ }
+ if (Services.prefs.prefHasUserValue(PREF_APP_UPDATE_CANCELATIONS_OSX)) {
+ Services.prefs.clearUserPref(PREF_APP_UPDATE_CANCELATIONS_OSX);
+ }
+ if (Services.prefs.prefHasUserValue(PREF_APP_UPDATE_ELEVATE_NEVER)) {
+ Services.prefs.clearUserPref(PREF_APP_UPDATE_ELEVATE_NEVER);
+ }
+ }
+ } else if (!update) {
+ AUSTLMY.pingCheckCode(this._pingSuffix, lastCheckCode);
+ }
+
+ return update;
+ },
+
+ /**
+ * Determine which of the specified updates should be installed and begin the
+ * download/installation process or notify the user about the update.
+ * @param updates
+ * An array of available updates
+ */
+ _selectAndInstallUpdate: function AUS__selectAndInstallUpdate(updates) {
+ // Return early if there's an active update. The user is already aware and
+ // is downloading or performed some user action to prevent notification.
+ var um = Cc["@mozilla.org/updates/update-manager;1"].
+ getService(Ci.nsIUpdateManager);
+ if (um.activeUpdate) {
+ if (AppConstants.platform == "gonk") {
+ // For gonk, the user isn't necessarily aware of the update, so we need
+ // to show the prompt to make sure.
+ this._showPrompt(um.activeUpdate);
+ }
+ AUSTLMY.pingCheckCode(this._pingSuffix, AUSTLMY.CHK_HAS_ACTIVEUPDATE);
+ return;
+ }
+
+ var updateEnabled = getPref("getBoolPref", PREF_APP_UPDATE_ENABLED, true);
+ if (!updateEnabled) {
+ AUSTLMY.pingCheckCode(this._pingSuffix, AUSTLMY.CHK_PREF_DISABLED);
+ LOG("UpdateService:_selectAndInstallUpdate - not prompting because " +
+ "update is disabled");
+ return;
+ }
+
+ var update = this.selectUpdate(updates, updates.length);
+ if (!update || update.elevationFailure) {
+ return;
+ }
+
+ if (update.unsupported) {
+ LOG("UpdateService:_selectAndInstallUpdate - update not supported for " +
+ "this system");
+ if (!getPref("getBoolPref", PREF_APP_UPDATE_NOTIFIEDUNSUPPORTED, false)) {
+ LOG("UpdateService:_selectAndInstallUpdate - notifying that the " +
+ "update is not supported for this system");
+ this._showPrompt(update);
+ }
+ AUSTLMY.pingCheckCode(this._pingSuffix, AUSTLMY.CHK_UNSUPPORTED);
+ return;
+ }
+
+ if (!getCanApplyUpdates()) {
+ LOG("UpdateService:_selectAndInstallUpdate - the user is unable to " +
+ "apply updates... prompting");
+ this._showPrompt(update);
+ AUSTLMY.pingCheckCode(this._pingSuffix, AUSTLMY.CHK_UNABLE_TO_APPLY);
+ return;
+ }
+
+ /**
+ * From this point on there are two possible outcomes:
+ * 1. download and install the update automatically
+ * 2. notify the user about the availability of an update
+ *
+ * Notes:
+ * a) if the app.update.auto preference is false then automatic download and
+ * install is disabled and the user will be notified.
+ * b) if the update has a showPrompt attribute the user will be notified.
+ *
+ * If the update when it is first read does not have an appVersion attribute
+ * the following deprecated behavior will occur:
+ * Update Type Outcome
+ * Major Notify
+ * Minor Auto Install
+ */
+ if (update.showPrompt) {
+ LOG("UpdateService:_selectAndInstallUpdate - prompting because the " +
+ "update snippet specified showPrompt");
+ AUSTLMY.pingCheckCode(this._pingSuffix, AUSTLMY.CHK_SHOWPROMPT_SNIPPET);
+ this._showPrompt(update);
+ return;
+ }
+
+ if (!getPref("getBoolPref", PREF_APP_UPDATE_AUTO, true)) {
+ LOG("UpdateService:_selectAndInstallUpdate - prompting because silent " +
+ "install is disabled");
+ AUSTLMY.pingCheckCode(this._pingSuffix, AUSTLMY.CHK_SHOWPROMPT_PREF);
+ this._showPrompt(update);
+ return;
+ }
+
+ LOG("UpdateService:_selectAndInstallUpdate - download the update");
+ let status = this.downloadUpdate(update, true);
+ if (status == STATE_NONE) {
+ cleanupActiveUpdate();
+ }
+ AUSTLMY.pingCheckCode(this._pingSuffix, AUSTLMY.CHK_DOWNLOAD_UPDATE);
+ },
+
+ _showPrompt: function AUS__showPrompt(update) {
+ let prompter = Cc["@mozilla.org/updates/update-prompt;1"].
+ createInstance(Ci.nsIUpdatePrompt);
+ prompter.showUpdateAvailable(update);
+ },
+
+ /**
+ * The Checker used for background update checks.
+ */
+ _backgroundChecker: null,
+
+ /**
+ * See nsIUpdateService.idl
+ */
+ get backgroundChecker() {
+ if (!this._backgroundChecker)
+ this._backgroundChecker = new Checker();
+ return this._backgroundChecker;
+ },
+
+ /**
+ * See nsIUpdateService.idl
+ */
+ get canCheckForUpdates() {
+ return gCanCheckForUpdates && hasUpdateMutex();
+ },
+
+ /**
+ * See nsIUpdateService.idl
+ */
+ get elevationRequired() {
+ return getElevationRequired();
+ },
+
+ /**
+ * See nsIUpdateService.idl
+ */
+ get canApplyUpdates() {
+ return getCanApplyUpdates() && hasUpdateMutex();
+ },
+
+ /**
+ * See nsIUpdateService.idl
+ */
+ get canStageUpdates() {
+ return getCanStageUpdates();
+ },
+
+ /**
+ * See nsIUpdateService.idl
+ */
+ get isOtherInstanceHandlingUpdates() {
+ return !hasUpdateMutex();
+ },
+
+
+ /**
+ * See nsIUpdateService.idl
+ */
+ addDownloadListener: function AUS_addDownloadListener(listener) {
+ if (!this._downloader) {
+ LOG("UpdateService:addDownloadListener - no downloader!");
+ return;
+ }
+ this._downloader.addDownloadListener(listener);
+ },
+
+ /**
+ * See nsIUpdateService.idl
+ */
+ removeDownloadListener: function AUS_removeDownloadListener(listener) {
+ if (!this._downloader) {
+ LOG("UpdateService:removeDownloadListener - no downloader!");
+ return;
+ }
+ this._downloader.removeDownloadListener(listener);
+ },
+
+ /**
+ * See nsIUpdateService.idl
+ */
+ downloadUpdate: function AUS_downloadUpdate(update, background) {
+ if (!update)
+ throw Cr.NS_ERROR_NULL_POINTER;
+
+ // Don't download the update if the update's version is less than the
+ // current application's version or the update's version is the same as the
+ // application's version and the build ID is the same as the application's
+ // build ID.
+ if (update.appVersion &&
+ (Services.vc.compare(update.appVersion, Services.appinfo.version) < 0 ||
+ update.buildID && update.buildID == Services.appinfo.appBuildID &&
+ update.appVersion == Services.appinfo.version)) {
+ LOG("UpdateService:downloadUpdate - canceling download of update since " +
+ "it is for an earlier or same application version and build ID.\n" +
+ "current application version: " + Services.appinfo.version + "\n" +
+ "update application version : " + update.appVersion + "\n" +
+ "current build ID: " + Services.appinfo.appBuildID + "\n" +
+ "update build ID : " + update.buildID);
+ cleanupActiveUpdate();
+ return STATE_NONE;
+ }
+
+ // If a download request is in progress vs. a download ready to resume
+ if (this.isDownloading) {
+ if (update.isCompleteUpdate == this._downloader.isCompleteUpdate &&
+ background == this._downloader.background) {
+ LOG("UpdateService:downloadUpdate - no support for downloading more " +
+ "than one update at a time");
+ return readStatusFile(getUpdatesDir());
+ }
+ this._downloader.cancel();
+ }
+ if (AppConstants.platform == "gonk") {
+ let um = Cc["@mozilla.org/updates/update-manager;1"].
+ getService(Ci.nsIUpdateManager);
+ let activeUpdate = um.activeUpdate;
+ if (activeUpdate &&
+ (activeUpdate.appVersion != update.appVersion ||
+ activeUpdate.buildID != update.buildID)) {
+ // We have an activeUpdate (which presumably was interrupted), and are
+ // about start downloading a new one. Make sure we remove all traces
+ // of the active one (otherwise we'll start appending the new update.mar
+ // the the one that's been partially downloaded).
+ LOG("UpdateService:downloadUpdate - removing stale active update.");
+ cleanupActiveUpdate();
+ }
+ }
+ // Set the previous application version prior to downloading the update.
+ update.previousAppVersion = Services.appinfo.version;
+ this._downloader = new Downloader(background, this);
+ return this._downloader.downloadUpdate(update);
+ },
+
+ /**
+ * See nsIUpdateService.idl
+ */
+ pauseDownload: function AUS_pauseDownload() {
+ if (this.isDownloading) {
+ this._downloader.cancel();
+ } else if (this._retryTimer) {
+ // Download status is still consider as 'downloading' during retry.
+ // We need to cancel both retry and download at this stage.
+ this._retryTimer.cancel();
+ this._retryTimer = null;
+ this._downloader.cancel();
+ }
+ },
+
+ /**
+ * See nsIUpdateService.idl
+ */
+ getUpdatesDirectory: getUpdatesDir,
+
+ /**
+ * See nsIUpdateService.idl
+ */
+ get isDownloading() {
+ return this._downloader && this._downloader.isBusy;
+ },
+
+ /**
+ * See nsIUpdateService.idl
+ */
+ applyOsUpdate: function AUS_applyOsUpdate(aUpdate) {
+ if (!aUpdate.isOSUpdate || aUpdate.state != STATE_APPLIED) {
+ aUpdate.statusText = "fota-state-error";
+ throw Cr.NS_ERROR_FAILURE;
+ }
+
+ aUpdate.QueryInterface(Ci.nsIWritablePropertyBag);
+ let osApplyToDir = aUpdate.getProperty("osApplyToDir");
+
+ if (!osApplyToDir) {
+ LOG("UpdateService:applyOsUpdate - Error: osApplyToDir is not defined" +
+ "in the nsIUpdate!");
+ pingStateAndStatusCodes(aUpdate, false,
+ STATE_FAILED + ": " + FOTA_FILE_OPERATION_ERROR);
+ handleUpdateFailure(aUpdate, FOTA_FILE_OPERATION_ERROR);
+ return;
+ }
+
+ let updateFile = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
+ updateFile.initWithPath(osApplyToDir + "/update.zip");
+ if (!updateFile.exists()) {
+ LOG("UpdateService:applyOsUpdate - Error: OS update is not found at " +
+ updateFile.path);
+ pingStateAndStatusCodes(aUpdate, false,
+ STATE_FAILED + ": " + FOTA_FILE_OPERATION_ERROR);
+ handleUpdateFailure(aUpdate, FOTA_FILE_OPERATION_ERROR);
+ return;
+ }
+
+ writeStatusFile(getUpdatesDir(), aUpdate.state = STATE_APPLIED_OS);
+ LOG("UpdateService:applyOsUpdate - Rebooting into recovery to apply " +
+ "FOTA update: " + updateFile.path);
+ try {
+ let recoveryService = Cc["@mozilla.org/recovery-service;1"]
+ .getService(Ci.nsIRecoveryService);
+ recoveryService.installFotaUpdate(updateFile.path);
+ } catch (e) {
+ LOG("UpdateService:applyOsUpdate - Error: Couldn't reboot into recovery" +
+ " to apply FOTA update " + updateFile.path);
+ pingStateAndStatusCodes(aUpdate, false,
+ STATE_FAILED + ": " + FOTA_RECOVERY_ERROR);
+ writeStatusFile(getUpdatesDir(), aUpdate.state = STATE_APPLIED);
+ handleUpdateFailure(aUpdate, FOTA_RECOVERY_ERROR);
+ }
+ },
+
+ classID: UPDATESERVICE_CID,
+ classInfo: XPCOMUtils.generateCI({classID: UPDATESERVICE_CID,
+ contractID: UPDATESERVICE_CONTRACTID,
+ interfaces: [Ci.nsIApplicationUpdateService,
+ Ci.nsITimerCallback,
+ Ci.nsIObserver],
+ flags: Ci.nsIClassInfo.SINGLETON}),
+
+ _xpcom_factory: UpdateServiceFactory,
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIApplicationUpdateService,
+ Ci.nsIUpdateCheckListener,
+ Ci.nsITimerCallback,
+ Ci.nsIObserver])
+};
+
+/**
+ * A service to manage active and past updates.
+ * @constructor
+ */
+function UpdateManager() {
+ // Ensure the Active Update file is loaded
+ var updates = this._loadXMLFileIntoArray(getUpdateFile(
+ [FILE_ACTIVE_UPDATE_XML]));
+ if (updates.length > 0) {
+ // Under some edgecases such as Windows system restore the active-update.xml
+ // will contain a pending update without the status file which will return
+ // STATE_NONE. To recover from this situation clean the updates dir and
+ // rewrite the active-update.xml file without the broken update.
+ if (readStatusFile(getUpdatesDir()) == STATE_NONE) {
+ cleanUpUpdatesDir();
+ this._writeUpdatesToXMLFile([], getUpdateFile([FILE_ACTIVE_UPDATE_XML]));
+ }
+ else
+ this._activeUpdate = updates[0];
+ }
+}
+UpdateManager.prototype = {
+ /**
+ * All previously downloaded and installed updates, as an array of nsIUpdate
+ * objects.
+ */
+ _updates: null,
+
+ /**
+ * The current actively downloading/installing update, as a nsIUpdate object.
+ */
+ _activeUpdate: null,
+
+ /**
+ * Handle Observer Service notifications
+ * @param subject
+ * The subject of the notification
+ * @param topic
+ * The notification name
+ * @param data
+ * Additional data
+ */
+ observe: function UM_observe(subject, topic, data) {
+ // Hack to be able to run and cleanup tests by reloading the update data.
+ if (topic == "um-reload-update-data") {
+ this._updates = this._loadXMLFileIntoArray(getUpdateFile(
+ [FILE_UPDATES_XML]));
+ this._activeUpdate = null;
+ var updates = this._loadXMLFileIntoArray(getUpdateFile(
+ [FILE_ACTIVE_UPDATE_XML]));
+ if (updates.length > 0)
+ this._activeUpdate = updates[0];
+ }
+ },
+
+ /**
+ * Loads an updates.xml formatted file into an array of nsIUpdate items.
+ * @param file
+ * A nsIFile for the updates.xml file
+ * @return The array of nsIUpdate items held in the file.
+ */
+ _loadXMLFileIntoArray: function UM__loadXMLFileIntoArray(file) {
+ if (!file.exists()) {
+ LOG("UpdateManager:_loadXMLFileIntoArray: XML file does not exist");
+ return [];
+ }
+
+ var result = [];
+ var fileStream = Cc["@mozilla.org/network/file-input-stream;1"].
+ createInstance(Ci.nsIFileInputStream);
+ fileStream.init(file, FileUtils.MODE_RDONLY, FileUtils.PERMS_FILE, 0);
+ try {
+ var parser = Cc["@mozilla.org/xmlextras/domparser;1"].
+ createInstance(Ci.nsIDOMParser);
+ var doc = parser.parseFromStream(fileStream, "UTF-8",
+ fileStream.available(), "text/xml");
+
+ const ELEMENT_NODE = Ci.nsIDOMNode.ELEMENT_NODE;
+ var updateCount = doc.documentElement.childNodes.length;
+ for (var i = 0; i < updateCount; ++i) {
+ var updateElement = doc.documentElement.childNodes.item(i);
+ if (updateElement.nodeType != ELEMENT_NODE ||
+ updateElement.localName != "update")
+ continue;
+
+ updateElement.QueryInterface(Ci.nsIDOMElement);
+ let update;
+ try {
+ update = new Update(updateElement);
+ } catch (e) {
+ LOG("UpdateManager:_loadXMLFileIntoArray - invalid update");
+ continue;
+ }
+ result.push(update);
+ }
+ }
+ catch (e) {
+ LOG("UpdateManager:_loadXMLFileIntoArray - error constructing update " +
+ "list. Exception: " + e);
+ }
+ fileStream.close();
+ return result;
+ },
+
+ /**
+ * Load the update manager, initializing state from state files.
+ */
+ _ensureUpdates: function UM__ensureUpdates() {
+ if (!this._updates) {
+ this._updates = this._loadXMLFileIntoArray(getUpdateFile(
+ [FILE_UPDATES_XML]));
+ var activeUpdates = this._loadXMLFileIntoArray(getUpdateFile(
+ [FILE_ACTIVE_UPDATE_XML]));
+ if (activeUpdates.length > 0)
+ this._activeUpdate = activeUpdates[0];
+ }
+ },
+
+ /**
+ * See nsIUpdateService.idl
+ */
+ getUpdateAt: function UM_getUpdateAt(index) {
+ this._ensureUpdates();
+ return this._updates[index];
+ },
+
+ /**
+ * See nsIUpdateService.idl
+ */
+ get updateCount() {
+ this._ensureUpdates();
+ return this._updates.length;
+ },
+
+ /**
+ * See nsIUpdateService.idl
+ */
+ get activeUpdate() {
+ if (this._activeUpdate &&
+ this._activeUpdate.channel != UpdateUtils.UpdateChannel) {
+ LOG("UpdateManager:get activeUpdate - channel has changed, " +
+ "reloading default preferences to workaround bug 802022");
+ // Workaround to get distribution preferences loaded (Bug 774618). This
+ // can be removed after bug 802022 is fixed.
+ let prefSvc = Services.prefs.QueryInterface(Ci.nsIObserver);
+ prefSvc.observe(null, "reload-default-prefs", null);
+ if (this._activeUpdate.channel != UpdateUtils.UpdateChannel) {
+ // User switched channels, clear out any old active updates and remove
+ // partial downloads
+ this._activeUpdate = null;
+ this.saveUpdates();
+
+ // Destroy the updates directory, since we're done with it.
+ cleanUpUpdatesDir();
+ }
+ }
+ return this._activeUpdate;
+ },
+ set activeUpdate(activeUpdate) {
+ this._addUpdate(activeUpdate);
+ this._activeUpdate = activeUpdate;
+ if (!activeUpdate) {
+ // If |activeUpdate| is null, we have updated both lists - the active list
+ // and the history list, so we want to write both files.
+ this.saveUpdates();
+ }
+ else
+ this._writeUpdatesToXMLFile([this._activeUpdate],
+ getUpdateFile([FILE_ACTIVE_UPDATE_XML]));
+ return activeUpdate;
+ },
+
+ /**
+ * Add an update to the Updates list. If the item already exists in the list,
+ * replace the existing value with the new value.
+ * @param update
+ * The nsIUpdate object to add.
+ */
+ _addUpdate: function UM__addUpdate(update) {
+ if (!update)
+ return;
+ this._ensureUpdates();
+ if (this._updates) {
+ for (var i = 0; i < this._updates.length; ++i) {
+ // Keep all update entries with a state of STATE_FAILED and replace the
+ // first update entry that has the same application version and build ID
+ // if it exists. This allows the update history to only have one success
+ // entry for an update and entries for all failed updates.
+ if (update.state != STATE_FAILED &&
+ this._updates[i] &&
+ this._updates[i].state != STATE_FAILED &&
+ this._updates[i].appVersion == update.appVersion &&
+ this._updates[i].buildID == update.buildID) {
+ // Replace the existing entry with the new value, updating
+ // all metadata.
+ this._updates[i] = update;
+ return;
+ }
+ }
+ }
+ // Otherwise add it to the front of the list.
+ this._updates.unshift(update);
+ },
+
+ /**
+ * Serializes an array of updates to an XML file
+ * @param updates
+ * An array of nsIUpdate objects
+ * @param file
+ * The nsIFile object to serialize to
+ */
+ _writeUpdatesToXMLFile: function UM__writeUpdatesToXMLFile(updates, file) {
+ var fos = Cc["@mozilla.org/network/safe-file-output-stream;1"].
+ createInstance(Ci.nsIFileOutputStream);
+ var modeFlags = FileUtils.MODE_WRONLY | FileUtils.MODE_CREATE |
+ FileUtils.MODE_TRUNCATE;
+ if (!file.exists()) {
+ file.create(Ci.nsILocalFile.NORMAL_FILE_TYPE, FileUtils.PERMS_FILE);
+ }
+ fos.init(file, modeFlags, FileUtils.PERMS_FILE, 0);
+
+ try {
+ var parser = Cc["@mozilla.org/xmlextras/domparser;1"].
+ createInstance(Ci.nsIDOMParser);
+ const EMPTY_UPDATES_DOCUMENT = "<?xml version=\"1.0\"?><updates xmlns=\"http://www.mozilla.org/2005/app-update\"></updates>";
+ var doc = parser.parseFromString(EMPTY_UPDATES_DOCUMENT, "text/xml");
+
+ for (var i = 0; i < updates.length; ++i) {
+ // If appVersion isn't defined don't add the update. This happens when
+ // cleaning up invalid updates (e.g. incorrect channel).
+ if (updates[i] && updates[i].appVersion) {
+ doc.documentElement.appendChild(updates[i].serialize(doc));
+ }
+ }
+
+ var serializer = Cc["@mozilla.org/xmlextras/xmlserializer;1"].
+ createInstance(Ci.nsIDOMSerializer);
+ serializer.serializeToStream(doc.documentElement, fos, null);
+ } catch (e) {
+ }
+
+ FileUtils.closeSafeFileOutputStream(fos);
+ },
+
+ /**
+ * See nsIUpdateService.idl
+ */
+ saveUpdates: function UM_saveUpdates() {
+ this._writeUpdatesToXMLFile([this._activeUpdate],
+ getUpdateFile([FILE_ACTIVE_UPDATE_XML]));
+ if (this._activeUpdate)
+ this._addUpdate(this._activeUpdate);
+
+ this._ensureUpdates();
+ // Don't write updates that have a temporary state to the updates.xml file.
+ if (this._updates) {
+ let updates = this._updates.slice();
+ for (let i = updates.length - 1; i >= 0; --i) {
+ let state = updates[i].state;
+ if (state == STATE_NONE || state == STATE_DOWNLOADING ||
+ state == STATE_APPLIED || state == STATE_APPLIED_SERVICE ||
+ state == STATE_PENDING || state == STATE_PENDING_SERVICE ||
+ state == STATE_PENDING_ELEVATE) {
+ updates.splice(i, 1);
+ }
+ }
+
+ this._writeUpdatesToXMLFile(updates.slice(0, 20),
+ getUpdateFile([FILE_UPDATES_XML]));
+ }
+ },
+
+ /**
+ * See nsIUpdateService.idl
+ */
+ refreshUpdateStatus: function UM_refreshUpdateStatus() {
+ var update = this._activeUpdate;
+ if (!update) {
+ return;
+ }
+ var status = readStatusFile(getUpdatesDir());
+ pingStateAndStatusCodes(update, false, status);
+ var parts = status.split(":");
+ update.state = parts[0];
+ if (update.state == STATE_FAILED && parts[1]) {
+ update.errorCode = parseInt(parts[1]);
+ }
+ let um = Cc["@mozilla.org/updates/update-manager;1"].
+ getService(Ci.nsIUpdateManager);
+ // Save a copy of the active update's current state to the updates.xml so
+ // it is possible to track failures.
+ um.saveUpdates();
+
+ // Rotate the update logs so the update log isn't removed if a complete
+ // update is downloaded. By passing false the patch directory won't be
+ // removed.
+ cleanUpUpdatesDir(false);
+
+ if (update.state == STATE_FAILED && parts[1]) {
+ if (!handleUpdateFailure(update, parts[1])) {
+ handleFallbackToCompleteUpdate(update, true);
+ }
+
+ update.QueryInterface(Ci.nsIWritablePropertyBag);
+ update.setProperty("stagingFailed", "true");
+ }
+ if (update.state == STATE_APPLIED && shouldUseService()) {
+ writeStatusFile(getUpdatesDir(), update.state = STATE_APPLIED_SERVICE);
+ }
+
+ // Send an observer notification which the update wizard uses in
+ // order to update its UI.
+ LOG("UpdateManager:refreshUpdateStatus - Notifying observers that " +
+ "the update was staged. state: " + update.state + ", status: " + status);
+ Services.obs.notifyObservers(null, "update-staged", update.state);
+
+ if (AppConstants.platform == "gonk") {
+ // Do this after everything else, since it will likely cause the app to
+ // shut down.
+ if (update.state == STATE_APPLIED) {
+ // Notify the user that an update has been staged and is ready for
+ // installation (i.e. that they should restart the application). We do
+ // not notify on failed update attempts.
+ let prompter = Cc["@mozilla.org/updates/update-prompt;1"].
+ createInstance(Ci.nsIUpdatePrompt);
+ prompter.showUpdateDownloaded(update, true);
+ } else {
+ releaseSDCardMountLock();
+ }
+ return;
+ }
+ // Only prompt when the UI isn't already open.
+ let windowType = getPref("getCharPref", PREF_APP_UPDATE_ALTWINDOWTYPE, null);
+ if (Services.wm.getMostRecentWindow(UPDATE_WINDOW_NAME) ||
+ windowType && Services.wm.getMostRecentWindow(windowType)) {
+ return;
+ }
+
+ if (update.state == STATE_APPLIED ||
+ update.state == STATE_APPLIED_SERVICE ||
+ update.state == STATE_PENDING ||
+ update.state == STATE_PENDING_SERVICE ||
+ update.state == STATE_PENDING_ELEVATE) {
+ // Notify the user that an update has been staged and is ready for
+ // installation (i.e. that they should restart the application).
+ let prompter = Cc["@mozilla.org/updates/update-prompt;1"].
+ createInstance(Ci.nsIUpdatePrompt);
+ prompter.showUpdateDownloaded(update, true);
+ }
+ },
+
+ /**
+ * See nsIUpdateService.idl
+ */
+ elevationOptedIn: function UM_elevationOptedIn() {
+ // The user has been been made aware that the update requires elevation.
+ let update = this._activeUpdate;
+ if (!update) {
+ return;
+ }
+ let status = readStatusFile(getUpdatesDir());
+ let parts = status.split(":");
+ update.state = parts[0];
+ if (update.state == STATE_PENDING_ELEVATE) {
+ // Proceed with the pending update.
+ // Note: STATE_PENDING_ELEVATE stands for "pending user's approval to
+ // proceed with an elevated update". As long as we see this state, we will
+ // notify the user of the availability of an update that requires
+ // elevation. |elevationOptedIn| (this function) is called when the user
+ // gives us approval to proceed, so we want to switch to STATE_PENDING.
+ // The updater then detects whether or not elevation is required and
+ // displays the elevation prompt if necessary. This last step does not
+ // depend on the state in the status file.
+ writeStatusFile(getUpdatesDir(), STATE_PENDING);
+ }
+ },
+
+ /**
+ * See nsIUpdateService.idl
+ */
+ cleanupActiveUpdate: function UM_cleanupActiveUpdate() {
+ cleanupActiveUpdate();
+ },
+
+ classID: Components.ID("{093C2356-4843-4C65-8709-D7DBCBBE7DFB}"),
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIUpdateManager, Ci.nsIObserver])
+};
+
+/**
+ * Checker
+ * Checks for new Updates
+ * @constructor
+ */
+function Checker() {
+}
+Checker.prototype = {
+ /**
+ * The XMLHttpRequest object that performs the connection.
+ */
+ _request: null,
+
+ /**
+ * The nsIUpdateCheckListener callback
+ */
+ _callback: null,
+
+ /**
+ * The URL of the update service XML file to connect to that contains details
+ * about available updates.
+ */
+ getUpdateURL: function UC_getUpdateURL(force) {
+ this._forced = force;
+
+ let url;
+ try {
+ url = Services.prefs.getDefaultBranch(null).
+ getCharPref(PREF_APP_UPDATE_URL);
+ } catch (e) {
+ }
+
+ if (!url || url == "") {
+ LOG("Checker:getUpdateURL - update URL not defined");
+ return null;
+ }
+
+ url = UpdateUtils.formatUpdateURL(url);
+
+ if (force) {
+ url += (url.indexOf("?") != -1 ? "&" : "?") + "force=1";
+ }
+
+ LOG("Checker:getUpdateURL - update URL: " + url);
+ return url;
+ },
+
+ /**
+ * See nsIUpdateService.idl
+ */
+ checkForUpdates: function UC_checkForUpdates(listener, force) {
+ LOG("Checker: checkForUpdates, force: " + force);
+ if (!listener)
+ throw Cr.NS_ERROR_NULL_POINTER;
+
+ Services.obs.notifyObservers(null, "update-check-start", null);
+
+ var url = this.getUpdateURL(force);
+ if (!url || (!this.enabled && !force))
+ return;
+
+ this._request = new XMLHttpRequest();
+ this._request.open("GET", url, true);
+ this._request.channel.notificationCallbacks = new gCertUtils.BadCertHandler(false);
+ // Prevent the request from reading from the cache.
+ this._request.channel.loadFlags |= Ci.nsIRequest.LOAD_BYPASS_CACHE;
+ // Prevent the request from writing to the cache.
+ this._request.channel.loadFlags |= Ci.nsIRequest.INHIBIT_CACHING;
+ // Disable cutting edge features, like TLS 1.3, where middleboxes might brick us
+ this._request.channel.QueryInterface(Ci.nsIHttpChannelInternal).beConservative = true;
+
+ this._request.overrideMimeType("text/xml");
+ // The Cache-Control header is only interpreted by proxies and the
+ // final destination. It does not help if a resource is already
+ // cached locally.
+ this._request.setRequestHeader("Cache-Control", "no-cache");
+ // HTTP/1.0 servers might not implement Cache-Control and
+ // might only implement Pragma: no-cache
+ this._request.setRequestHeader("Pragma", "no-cache");
+
+ var self = this;
+ this._request.addEventListener("error", function(event) { self.onError(event); }, false);
+ this._request.addEventListener("load", function(event) { self.onLoad(event); }, false);
+
+ LOG("Checker:checkForUpdates - sending request to: " + url);
+ this._request.send(null);
+
+ this._callback = listener;
+ },
+
+ /**
+ * Returns an array of nsIUpdate objects discovered by the update check.
+ * @throws if the XML document element node name is not updates.
+ */
+ get _updates() {
+ var updatesElement = this._request.responseXML.documentElement;
+ if (!updatesElement) {
+ LOG("Checker:_updates get - empty updates document?!");
+ return [];
+ }
+
+ if (updatesElement.nodeName != "updates") {
+ LOG("Checker:_updates get - unexpected node name!");
+ throw new Error("Unexpected node name, expected: updates, got: " +
+ updatesElement.nodeName);
+ }
+
+ const ELEMENT_NODE = Ci.nsIDOMNode.ELEMENT_NODE;
+ var updates = [];
+ for (var i = 0; i < updatesElement.childNodes.length; ++i) {
+ var updateElement = updatesElement.childNodes.item(i);
+ if (updateElement.nodeType != ELEMENT_NODE ||
+ updateElement.localName != "update")
+ continue;
+
+ updateElement.QueryInterface(Ci.nsIDOMElement);
+ let update;
+ try {
+ update = new Update(updateElement);
+ } catch (e) {
+ LOG("Checker:_updates get - invalid <update/>, ignoring...");
+ continue;
+ }
+ update.serviceURL = this.getUpdateURL(this._forced);
+ update.channel = UpdateUtils.UpdateChannel;
+ updates.push(update);
+ }
+
+ return updates;
+ },
+
+ /**
+ * Returns the status code for the XMLHttpRequest
+ */
+ _getChannelStatus: function UC__getChannelStatus(request) {
+ var status = 0;
+ try {
+ status = request.status;
+ }
+ catch (e) {
+ }
+
+ if (status == 0)
+ status = request.channel.QueryInterface(Ci.nsIRequest).status;
+ return status;
+ },
+
+ _isHttpStatusCode: function UC__isHttpStatusCode(status) {
+ return status >= 100 && status <= 599;
+ },
+
+ /**
+ * The XMLHttpRequest succeeded and the document was loaded.
+ * @param event
+ * The nsIDOMEvent for the load
+ */
+ onLoad: function UC_onLoad(event) {
+ LOG("Checker:onLoad - request completed downloading document");
+
+ try {
+ // Analyze the resulting DOM and determine the set of updates.
+ var updates = this._updates;
+ LOG("Checker:onLoad - number of updates available: " + updates.length);
+
+ if (Services.prefs.prefHasUserValue(PREF_APP_UPDATE_BACKGROUNDERRORS)) {
+ Services.prefs.clearUserPref(PREF_APP_UPDATE_BACKGROUNDERRORS);
+ }
+
+ // Tell the callback about the updates
+ this._callback.onCheckComplete(event.target, updates, updates.length);
+ } catch (e) {
+ LOG("Checker:onLoad - there was a problem checking for updates. " +
+ "Exception: " + e);
+ var request = event.target;
+ var status = this._getChannelStatus(request);
+ LOG("Checker:onLoad - request.status: " + status);
+ var update = new Update(null);
+ update.errorCode = status;
+ update.statusText = getStatusTextFromCode(status, 404);
+
+ if (this._isHttpStatusCode(status)) {
+ update.errorCode = HTTP_ERROR_OFFSET + status;
+ }
+
+ this._callback.onError(request, update);
+ }
+
+ this._callback = null;
+ this._request = null;
+ },
+
+ /**
+ * There was an error of some kind during the XMLHttpRequest
+ * @param event
+ * The nsIDOMEvent for the error
+ */
+ onError: function UC_onError(event) {
+ var request = event.target;
+ var status = this._getChannelStatus(request);
+ LOG("Checker:onError - request.status: " + status);
+
+ // If we can't find an error string specific to this status code,
+ // just use the 200 message from above, which means everything
+ // "looks" fine but there was probably an XML error or a bogus file.
+ var update = new Update(null);
+ update.errorCode = status;
+ update.statusText = getStatusTextFromCode(status, 200);
+
+ if (status == Cr.NS_ERROR_OFFLINE) {
+ // We use a separate constant here because nsIUpdate.errorCode is signed
+ update.errorCode = NETWORK_ERROR_OFFLINE;
+ } else if (this._isHttpStatusCode(status)) {
+ update.errorCode = HTTP_ERROR_OFFSET + status;
+ }
+
+ this._callback.onError(request, update);
+ this._request = null;
+ },
+
+ /**
+ * Whether or not we are allowed to do update checking.
+ */
+ _enabled: true,
+ get enabled() {
+ return getPref("getBoolPref", PREF_APP_UPDATE_ENABLED, true) &&
+ gCanCheckForUpdates && hasUpdateMutex() && this._enabled;
+ },
+
+ /**
+ * See nsIUpdateService.idl
+ */
+ stopChecking: function UC_stopChecking(duration) {
+ // Always stop the current check
+ if (this._request)
+ this._request.abort();
+
+ switch (duration) {
+ case Ci.nsIUpdateChecker.CURRENT_SESSION:
+ this._enabled = false;
+ break;
+ case Ci.nsIUpdateChecker.ANY_CHECKS:
+ this._enabled = false;
+ Services.prefs.setBoolPref(PREF_APP_UPDATE_ENABLED, this._enabled);
+ break;
+ }
+
+ this._callback = null;
+ },
+
+ classID: Components.ID("{898CDC9B-E43F-422F-9CC4-2F6291B415A3}"),
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIUpdateChecker])
+};
+
+/**
+ * Manages the download of updates
+ * @param background
+ * Whether or not this downloader is operating in background
+ * update mode.
+ * @param updateService
+ * The update service that created this downloader.
+ * @constructor
+ */
+function Downloader(background, updateService) {
+ LOG("Creating Downloader");
+ this.background = background;
+ this.updateService = updateService;
+}
+Downloader.prototype = {
+ /**
+ * The nsIUpdatePatch that we are downloading
+ */
+ _patch: null,
+
+ /**
+ * The nsIUpdate that we are downloading
+ */
+ _update: null,
+
+ /**
+ * The nsIIncrementalDownload object handling the download
+ */
+ _request: null,
+
+ /**
+ * Whether or not the update being downloaded is a complete replacement of
+ * the user's existing installation or a patch representing the difference
+ * between the new version and the previous version.
+ */
+ isCompleteUpdate: null,
+
+ /**
+ * Cancels the active download.
+ */
+ cancel: function Downloader_cancel(cancelError) {
+ LOG("Downloader: cancel");
+ if (cancelError === undefined) {
+ cancelError = Cr.NS_BINDING_ABORTED;
+ }
+ if (this._request && this._request instanceof Ci.nsIRequest) {
+ this._request.cancel(cancelError);
+ }
+ if (AppConstants.platform == "gonk") {
+ releaseSDCardMountLock();
+ }
+ },
+
+ /**
+ * Whether or not a patch has been downloaded and staged for installation.
+ */
+ get patchIsStaged() {
+ var readState = readStatusFile(getUpdatesDir());
+ // Note that if we decide to download and apply new updates after another
+ // update has been successfully applied in the background, we need to stop
+ // checking for the APPLIED state here.
+ return readState == STATE_PENDING || readState == STATE_PENDING_SERVICE ||
+ readState == STATE_PENDING_ELEVATE ||
+ readState == STATE_APPLIED || readState == STATE_APPLIED_SERVICE;
+ },
+
+ /**
+ * Verify the downloaded file. We assume that the download is complete at
+ * this point.
+ */
+ _verifyDownload: function Downloader__verifyDownload() {
+ LOG("Downloader:_verifyDownload called");
+ if (!this._request) {
+ AUSTLMY.pingDownloadCode(this.isCompleteUpdate,
+ AUSTLMY.DWNLD_ERR_VERIFY_NO_REQUEST);
+ return false;
+ }
+
+ let destination = this._request.destination;
+
+ // Ensure that the file size matches the expected file size.
+ if (destination.fileSize != this._patch.size) {
+ LOG("Downloader:_verifyDownload downloaded size != expected size.");
+ AUSTLMY.pingDownloadCode(this.isCompleteUpdate,
+ AUSTLMY.DWNLD_ERR_VERIFY_PATCH_SIZE_NOT_EQUAL);
+ return false;
+ }
+
+ LOG("Downloader:_verifyDownload downloaded size == expected size.");
+
+ // The hash check is not necessary when mar signatures are used to verify
+ // the downloaded mar file.
+ if (AppConstants.MOZ_VERIFY_MAR_SIGNATURE) {
+ return true;
+ }
+
+ let fileStream = Cc["@mozilla.org/network/file-input-stream;1"].
+ createInstance(Ci.nsIFileInputStream);
+ fileStream.init(destination, FileUtils.MODE_RDONLY, FileUtils.PERMS_FILE, 0);
+
+ let digest;
+ try {
+ let hash = Cc["@mozilla.org/security/hash;1"].
+ createInstance(Ci.nsICryptoHash);
+ var hashFunction = Ci.nsICryptoHash[this._patch.hashFunction.toUpperCase()];
+ if (hashFunction == undefined) {
+ throw Cr.NS_ERROR_UNEXPECTED;
+ }
+ hash.init(hashFunction);
+ hash.updateFromStream(fileStream, -1);
+ // NOTE: For now, we assume that the format of _patch.hashValue is hex
+ // encoded binary (such as what is typically output by programs like
+ // sha1sum). In the future, this may change to base64 depending on how
+ // we choose to compute these hashes.
+ digest = binaryToHex(hash.finish(false));
+ } catch (e) {
+ LOG("Downloader:_verifyDownload - failed to compute hash of the " +
+ "downloaded update archive");
+ digest = "";
+ }
+
+ fileStream.close();
+
+ if (digest == this._patch.hashValue.toLowerCase()) {
+ LOG("Downloader:_verifyDownload hashes match.");
+ return true;
+ }
+
+ LOG("Downloader:_verifyDownload hashes do not match. ");
+ AUSTLMY.pingDownloadCode(this.isCompleteUpdate,
+ AUSTLMY.DWNLD_ERR_VERIFY_NO_HASH_MATCH);
+ return false;
+ },
+
+ /**
+ * Select the patch to use given the current state of updateDir and the given
+ * set of update patches.
+ * @param update
+ * A nsIUpdate object to select a patch from
+ * @param updateDir
+ * A nsIFile representing the update directory
+ * @return A nsIUpdatePatch object to download
+ */
+ _selectPatch: function Downloader__selectPatch(update, updateDir) {
+ // Given an update to download, we will always try to download the patch
+ // for a partial update over the patch for a full update.
+
+ /**
+ * Return the first UpdatePatch with the given type.
+ * @param type
+ * The type of the patch ("complete" or "partial")
+ * @return A nsIUpdatePatch object matching the type specified
+ */
+ function getPatchOfType(type) {
+ for (var i = 0; i < update.patchCount; ++i) {
+ var patch = update.getPatchAt(i);
+ if (patch && patch.type == type)
+ return patch;
+ }
+ return null;
+ }
+
+ // Look to see if any of the patches in the Update object has been
+ // pre-selected for download, otherwise we must figure out which one
+ // to select ourselves.
+ var selectedPatch = update.selectedPatch;
+
+ var state = readStatusFile(updateDir);
+
+ // If this is a patch that we know about, then select it. If it is a patch
+ // that we do not know about, then remove it and use our default logic.
+ var useComplete = false;
+ if (selectedPatch) {
+ LOG("Downloader:_selectPatch - found existing patch with state: " +
+ state);
+ if (state == STATE_DOWNLOADING) {
+ LOG("Downloader:_selectPatch - resuming download");
+ return selectedPatch;
+ }
+
+ if (AppConstants.platform == "gonk") {
+ if (state == STATE_PENDING || state == STATE_APPLYING) {
+ LOG("Downloader:_selectPatch - resuming interrupted apply");
+ return selectedPatch;
+ }
+ if (state == STATE_APPLIED) {
+ LOG("Downloader:_selectPatch - already downloaded and staged");
+ return null;
+ }
+ } else if (state == STATE_PENDING || state == STATE_PENDING_SERVICE ||
+ state == STATE_PENDING_ELEVATE) {
+ LOG("Downloader:_selectPatch - already downloaded and staged");
+ return null;
+ }
+
+ if (update && selectedPatch.type == "complete") {
+ // This is a pretty fatal error. Just bail.
+ LOG("Downloader:_selectPatch - failed to apply complete patch!");
+ writeStatusFile(updateDir, STATE_NONE);
+ writeVersionFile(getUpdatesDir(), null);
+ return null;
+ }
+
+ // Something went wrong when we tried to apply the previous patch.
+ // Try the complete patch next time.
+ useComplete = true;
+ selectedPatch = null;
+ }
+
+ // If we were not able to discover an update from a previous download, we
+ // select the best patch from the given set.
+ var partialPatch = getPatchOfType("partial");
+ if (!useComplete)
+ selectedPatch = partialPatch;
+ if (!selectedPatch) {
+ if (partialPatch)
+ partialPatch.selected = false;
+ selectedPatch = getPatchOfType("complete");
+ }
+
+ // Restore the updateDir since we may have deleted it.
+ updateDir = getUpdatesDir();
+
+ // if update only contains a partial patch, selectedPatch == null here if
+ // the partial patch has been attempted and fails and we're trying to get a
+ // complete patch
+ if (selectedPatch)
+ selectedPatch.selected = true;
+
+ update.isCompleteUpdate = useComplete;
+
+ // Reset the Active Update object on the Update Manager and flush the
+ // Active Update DB.
+ var um = Cc["@mozilla.org/updates/update-manager;1"].
+ getService(Ci.nsIUpdateManager);
+ um.activeUpdate = update;
+
+ return selectedPatch;
+ },
+
+ /**
+ * Whether or not we are currently downloading something.
+ */
+ get isBusy() {
+ return this._request != null;
+ },
+
+ /**
+ * Get the nsIFile to use for downloading the active update's selected patch
+ */
+ _getUpdateArchiveFile: function Downloader__getUpdateArchiveFile() {
+ var updateArchive;
+ if (AppConstants.platform == "gonk") {
+ try {
+ updateArchive = FileUtils.getDir(KEY_UPDATE_ARCHIVE_DIR, [], true);
+ } catch (e) {
+ return null;
+ }
+ } else {
+ updateArchive = getUpdatesDir().clone();
+ }
+
+ updateArchive.append(FILE_UPDATE_MAR);
+ return updateArchive;
+ },
+
+ /**
+ * Download and stage the given update.
+ * @param update
+ * A nsIUpdate object to download a patch for. Cannot be null.
+ */
+ downloadUpdate: function Downloader_downloadUpdate(update) {
+ LOG("UpdateService:_downloadUpdate");
+ if (!update) {
+ AUSTLMY.pingDownloadCode(undefined, AUSTLMY.DWNLD_ERR_NO_UPDATE);
+ throw Cr.NS_ERROR_NULL_POINTER;
+ }
+
+ var updateDir = getUpdatesDir();
+
+ this._update = update;
+
+ // This function may return null, which indicates that there are no patches
+ // to download.
+ this._patch = this._selectPatch(update, updateDir);
+ if (!this._patch) {
+ LOG("Downloader:downloadUpdate - no patch to download");
+ AUSTLMY.pingDownloadCode(undefined, AUSTLMY.DWNLD_ERR_NO_UPDATE_PATCH);
+ return readStatusFile(updateDir);
+ }
+ this.isCompleteUpdate = this._patch.type == "complete";
+
+ let patchFile = null;
+
+ // Only used by gonk
+ let status = STATE_NONE;
+ if (AppConstants.platform == "gonk") {
+ status = readStatusFile(updateDir);
+ if (isInterruptedUpdate(status)) {
+ LOG("Downloader:downloadUpdate - interruptted update");
+ // The update was interrupted. Try to locate the existing patch file.
+ // For an interrupted download, this allows a resume rather than a
+ // re-download.
+ patchFile = getFileFromUpdateLink(updateDir);
+ if (!patchFile) {
+ // No link file. We'll just assume that the update.mar is in the
+ // update directory.
+ patchFile = updateDir.clone();
+ patchFile.append(FILE_UPDATE_MAR);
+ }
+ if (patchFile.exists()) {
+ LOG("Downloader:downloadUpdate - resuming with patchFile " + patchFile.path);
+ if (patchFile.fileSize == this._patch.size) {
+ LOG("Downloader:downloadUpdate - patchFile appears to be fully downloaded");
+ // Bump the status along so that we don't try to redownload again.
+ if (getElevationRequired()) {
+ status = STATE_PENDING_ELEVATE;
+ } else {
+ status = STATE_PENDING;
+ }
+ }
+ } else {
+ LOG("Downloader:downloadUpdate - patchFile " + patchFile.path +
+ " doesn't exist - performing full download");
+ // The patchfile doesn't exist, we might as well treat this like
+ // a new download.
+ patchFile = null;
+ }
+ if (patchFile && status != STATE_DOWNLOADING) {
+ // It looks like the patch was downloaded, but got interrupted while it
+ // was being verified or applied. So we'll fake the downloading portion.
+
+ if (getElevationRequired()) {
+ writeStatusFile(updateDir, STATE_PENDING_ELEVATE);
+ } else {
+ writeStatusFile(updateDir, STATE_PENDING);
+ }
+
+ // Since the code expects the onStopRequest callback to happen
+ // asynchronously (And you have to call AUS_addDownloadListener
+ // after calling AUS_downloadUpdate) we need to defer this.
+
+ this._downloadTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
+ this._downloadTimer.initWithCallback(function() {
+ this._downloadTimer = null;
+ // Send a fake onStopRequest. Filling in the destination allows
+ // _verifyDownload to work, and then the update will be applied.
+ this._request = {destination: patchFile};
+ this.onStopRequest(this._request, null, Cr.NS_OK);
+ }.bind(this), 0, Ci.nsITimer.TYPE_ONE_SHOT);
+
+ // Returning STATE_DOWNLOADING makes UpdatePrompt think we're
+ // downloading. The onStopRequest that we spoofed above will make it
+ // look like the download finished.
+ return STATE_DOWNLOADING;
+ }
+ }
+ }
+
+ if (!patchFile) {
+ // Find a place to put the patchfile that we're going to download.
+ patchFile = this._getUpdateArchiveFile();
+ }
+ if (!patchFile) {
+ AUSTLMY.pingDownloadCode(this.isCompleteUpdate,
+ AUSTLMY.DWNLD_ERR_NO_PATCH_FILE);
+ return STATE_NONE;
+ }
+
+ if (AppConstants.platform == "gonk") {
+ if (patchFile.path.indexOf(updateDir.path) != 0) {
+ // The patchFile is in a directory which is different from the
+ // updateDir, create a link file.
+ writeLinkFile(updateDir, patchFile);
+
+ if (!isInterruptedUpdate(status) && patchFile.exists()) {
+ // Remove stale patchFile
+ patchFile.remove(false);
+ }
+ }
+ }
+
+ update.QueryInterface(Ci.nsIPropertyBag);
+ let interval = this.background ? update.getProperty("backgroundInterval")
+ : DOWNLOAD_FOREGROUND_INTERVAL;
+
+ var uri = Services.io.newURI(this._patch.URL, null, null);
+ LOG("Downloader:downloadUpdate - url: " + uri.spec + ", path: " +
+ patchFile.path + ", interval: " + interval);
+
+ this._request = Cc["@mozilla.org/network/incremental-download;1"].
+ createInstance(Ci.nsIIncrementalDownload);
+ this._request.init(uri, patchFile, DOWNLOAD_CHUNK_SIZE, interval);
+ this._request.start(this, null);
+
+ writeStatusFile(updateDir, STATE_DOWNLOADING);
+ this._patch.QueryInterface(Ci.nsIWritablePropertyBag);
+ this._patch.state = STATE_DOWNLOADING;
+ var um = Cc["@mozilla.org/updates/update-manager;1"].
+ getService(Ci.nsIUpdateManager);
+ um.saveUpdates();
+ return STATE_DOWNLOADING;
+ },
+
+ /**
+ * An array of download listeners to notify when we receive
+ * nsIRequestObserver or nsIProgressEventSink method calls.
+ */
+ _listeners: [],
+
+ /**
+ * Adds a listener to the download process
+ * @param listener
+ * A download listener, implementing nsIRequestObserver and
+ * nsIProgressEventSink
+ */
+ addDownloadListener: function Downloader_addDownloadListener(listener) {
+ for (var i = 0; i < this._listeners.length; ++i) {
+ if (this._listeners[i] == listener)
+ return;
+ }
+ this._listeners.push(listener);
+ },
+
+ /**
+ * Removes a download listener
+ * @param listener
+ * The listener to remove.
+ */
+ removeDownloadListener: function Downloader_removeDownloadListener(listener) {
+ for (var i = 0; i < this._listeners.length; ++i) {
+ if (this._listeners[i] == listener) {
+ this._listeners.splice(i, 1);
+ return;
+ }
+ }
+ },
+
+ /**
+ * When the async request begins
+ * @param request
+ * The nsIRequest object for the transfer
+ * @param context
+ * Additional data
+ */
+ onStartRequest: function Downloader_onStartRequest(request, context) {
+ if (request instanceof Ci.nsIIncrementalDownload)
+ LOG("Downloader:onStartRequest - original URI spec: " + request.URI.spec +
+ ", final URI spec: " + request.finalURI.spec);
+ // Always set finalURL in onStartRequest since it can change.
+ this._patch.finalURL = request.finalURI.spec;
+ var um = Cc["@mozilla.org/updates/update-manager;1"].
+ getService(Ci.nsIUpdateManager);
+ um.saveUpdates();
+
+ var listeners = this._listeners.concat();
+ var listenerCount = listeners.length;
+ for (var i = 0; i < listenerCount; ++i)
+ listeners[i].onStartRequest(request, context);
+ },
+
+ /**
+ * When new data has been downloaded
+ * @param request
+ * The nsIRequest object for the transfer
+ * @param context
+ * Additional data
+ * @param progress
+ * The current number of bytes transferred
+ * @param maxProgress
+ * The total number of bytes that must be transferred
+ */
+ onProgress: function Downloader_onProgress(request, context, progress,
+ maxProgress) {
+ LOG("Downloader:onProgress - progress: " + progress + "/" + maxProgress);
+
+ if (progress > this._patch.size) {
+ LOG("Downloader:onProgress - progress: " + progress +
+ " is higher than patch size: " + this._patch.size);
+ // It's important that we use a different code than
+ // NS_ERROR_CORRUPTED_CONTENT so that tests can verify the difference
+ // between a hash error and a wrong download error.
+ AUSTLMY.pingDownloadCode(this.isCompleteUpdate,
+ AUSTLMY.DWNLD_ERR_PATCH_SIZE_LARGER);
+ this.cancel(Cr.NS_ERROR_UNEXPECTED);
+ return;
+ }
+
+ if (maxProgress != this._patch.size) {
+ LOG("Downloader:onProgress - maxProgress: " + maxProgress +
+ " is not equal to expected patch size: " + this._patch.size);
+ // It's important that we use a different code than
+ // NS_ERROR_CORRUPTED_CONTENT so that tests can verify the difference
+ // between a hash error and a wrong download error.
+ AUSTLMY.pingDownloadCode(this.isCompleteUpdate,
+ AUSTLMY.DWNLD_ERR_PATCH_SIZE_NOT_EQUAL);
+ this.cancel(Cr.NS_ERROR_UNEXPECTED);
+ return;
+ }
+
+ var listeners = this._listeners.concat();
+ var listenerCount = listeners.length;
+ for (var i = 0; i < listenerCount; ++i) {
+ var listener = listeners[i];
+ if (listener instanceof Ci.nsIProgressEventSink)
+ listener.onProgress(request, context, progress, maxProgress);
+ }
+ this.updateService._consecutiveSocketErrors = 0;
+ },
+
+ /**
+ * When we have new status text
+ * @param request
+ * The nsIRequest object for the transfer
+ * @param context
+ * Additional data
+ * @param status
+ * A status code
+ * @param statusText
+ * Human readable version of |status|
+ */
+ onStatus: function Downloader_onStatus(request, context, status, statusText) {
+ LOG("Downloader:onStatus - status: " + status + ", statusText: " +
+ statusText);
+
+ var listeners = this._listeners.concat();
+ var listenerCount = listeners.length;
+ for (var i = 0; i < listenerCount; ++i) {
+ var listener = listeners[i];
+ if (listener instanceof Ci.nsIProgressEventSink)
+ listener.onStatus(request, context, status, statusText);
+ }
+ },
+
+ /**
+ * When data transfer ceases
+ * @param request
+ * The nsIRequest object for the transfer
+ * @param context
+ * Additional data
+ * @param status
+ * Status code containing the reason for the cessation.
+ */
+ onStopRequest: function Downloader_onStopRequest(request, context, status) {
+ if (request instanceof Ci.nsIIncrementalDownload)
+ LOG("Downloader:onStopRequest - original URI spec: " + request.URI.spec +
+ ", final URI spec: " + request.finalURI.spec + ", status: " + status);
+
+ // XXX ehsan shouldShowPrompt should always be false here.
+ // But what happens when there is already a UI showing?
+ var state = this._patch.state;
+ var shouldShowPrompt = false;
+ var shouldRegisterOnlineObserver = false;
+ var shouldRetrySoon = false;
+ var deleteActiveUpdate = false;
+ var retryTimeout = getPref("getIntPref", PREF_APP_UPDATE_SOCKET_RETRYTIMEOUT,
+ DEFAULT_SOCKET_RETRYTIMEOUT);
+ // Prevent the preference from setting a value greater than 10000.
+ retryTimeout = Math.min(retryTimeout, 10000);
+ var maxFail = getPref("getIntPref", PREF_APP_UPDATE_SOCKET_MAXERRORS,
+ DEFAULT_SOCKET_MAX_ERRORS);
+ // Prevent the preference from setting a value greater than 20.
+ maxFail = Math.min(maxFail, 20);
+ LOG("Downloader:onStopRequest - status: " + status + ", " +
+ "current fail: " + this.updateService._consecutiveSocketErrors + ", " +
+ "max fail: " + maxFail + ", " + "retryTimeout: " + retryTimeout);
+ if (Components.isSuccessCode(status)) {
+ if (this._verifyDownload()) {
+ if (shouldUseService()) {
+ state = STATE_PENDING_SERVICE;
+ } else if (getElevationRequired()) {
+ state = STATE_PENDING_ELEVATE;
+ } else {
+ state = STATE_PENDING;
+ }
+ if (this.background) {
+ shouldShowPrompt = !getCanStageUpdates();
+ }
+ AUSTLMY.pingDownloadCode(this.isCompleteUpdate, AUSTLMY.DWNLD_SUCCESS);
+
+ // Tell the updater.exe we're ready to apply.
+ writeStatusFile(getUpdatesDir(), state);
+ writeVersionFile(getUpdatesDir(), this._update.appVersion);
+ this._update.installDate = (new Date()).getTime();
+ this._update.statusText = gUpdateBundle.GetStringFromName("installPending");
+ }
+ else {
+ LOG("Downloader:onStopRequest - download verification failed");
+ state = STATE_DOWNLOAD_FAILED;
+ status = Cr.NS_ERROR_CORRUPTED_CONTENT;
+
+ // Yes, this code is a string.
+ const vfCode = "verification_failed";
+ var message = getStatusTextFromCode(vfCode, vfCode);
+ this._update.statusText = message;
+
+ if (this._update.isCompleteUpdate || this._update.patchCount != 2)
+ deleteActiveUpdate = true;
+
+ // Destroy the updates directory, since we're done with it.
+ cleanUpUpdatesDir();
+ }
+ } else if (status == Cr.NS_ERROR_OFFLINE) {
+ // Register an online observer to try again.
+ // The online observer will continue the incremental download by
+ // calling downloadUpdate on the active update which continues
+ // downloading the file from where it was.
+ LOG("Downloader:onStopRequest - offline, register online observer: true");
+ AUSTLMY.pingDownloadCode(this.isCompleteUpdate,
+ AUSTLMY.DWNLD_RETRY_OFFLINE);
+ shouldRegisterOnlineObserver = true;
+ deleteActiveUpdate = false;
+ // Each of NS_ERROR_NET_TIMEOUT, ERROR_CONNECTION_REFUSED,
+ // NS_ERROR_NET_RESET and NS_ERROR_DOCUMENT_NOT_CACHED can be returned
+ // when disconnecting the internet while a download of a MAR is in
+ // progress. There may be others but I have not encountered them during
+ // testing.
+ } else if ((status == Cr.NS_ERROR_NET_TIMEOUT ||
+ status == Cr.NS_ERROR_CONNECTION_REFUSED ||
+ status == Cr.NS_ERROR_NET_RESET ||
+ status == Cr.NS_ERROR_DOCUMENT_NOT_CACHED) &&
+ this.updateService._consecutiveSocketErrors < maxFail) {
+ LOG("Downloader:onStopRequest - socket error, shouldRetrySoon: true");
+ let dwnldCode = AUSTLMY.DWNLD_RETRY_CONNECTION_REFUSED;
+ if (status == Cr.NS_ERROR_NET_TIMEOUT) {
+ dwnldCode = AUSTLMY.DWNLD_RETRY_NET_TIMEOUT;
+ } else if (status == Cr.NS_ERROR_NET_RESET) {
+ dwnldCode = AUSTLMY.DWNLD_RETRY_NET_RESET;
+ } else if (status == Cr.NS_ERROR_DOCUMENT_NOT_CACHED) {
+ dwnldCode = AUSTLMY.DWNLD_ERR_DOCUMENT_NOT_CACHED;
+ }
+ AUSTLMY.pingDownloadCode(this.isCompleteUpdate, dwnldCode);
+ shouldRetrySoon = true;
+ deleteActiveUpdate = false;
+ } else if (status != Cr.NS_BINDING_ABORTED &&
+ status != Cr.NS_ERROR_ABORT) {
+ LOG("Downloader:onStopRequest - non-verification failure");
+ let dwnldCode = AUSTLMY.DWNLD_ERR_BINDING_ABORTED;
+ if (status == Cr.NS_ERROR_ABORT) {
+ dwnldCode = AUSTLMY.DWNLD_ERR_ABORT;
+ }
+ AUSTLMY.pingDownloadCode(this.isCompleteUpdate, dwnldCode);
+
+ // Some sort of other failure, log this in the |statusText| property
+ state = STATE_DOWNLOAD_FAILED;
+
+ // XXXben - if |request| (The Incremental Download) provided a means
+ // for accessing the http channel we could do more here.
+
+ this._update.statusText = getStatusTextFromCode(status,
+ Cr.NS_BINDING_FAILED);
+
+ if (AppConstants.platform == "gonk") {
+ // bug891009: On FirefoxOS, manaully retry OTA download will reuse
+ // the Update object. We need to remove selected patch so that download
+ // can be triggered again successfully.
+ this._update.selectedPatch.selected = false;
+ }
+
+ // Destroy the updates directory, since we're done with it.
+ cleanUpUpdatesDir();
+
+ deleteActiveUpdate = true;
+ }
+ LOG("Downloader:onStopRequest - setting state to: " + state);
+ this._patch.state = state;
+ var um = Cc["@mozilla.org/updates/update-manager;1"].
+ getService(Ci.nsIUpdateManager);
+ if (deleteActiveUpdate) {
+ this._update.installDate = (new Date()).getTime();
+ um.activeUpdate = null;
+ }
+ else if (um.activeUpdate) {
+ um.activeUpdate.state = state;
+ }
+ um.saveUpdates();
+
+ // Only notify listeners about the stopped state if we
+ // aren't handling an internal retry.
+ if (!shouldRetrySoon && !shouldRegisterOnlineObserver) {
+ var listeners = this._listeners.concat();
+ var listenerCount = listeners.length;
+ for (var i = 0; i < listenerCount; ++i) {
+ listeners[i].onStopRequest(request, context, status);
+ }
+ }
+
+ this._request = null;
+
+ if (state == STATE_DOWNLOAD_FAILED) {
+ var allFailed = true;
+ // Check if there is a complete update patch that can be downloaded.
+ if (!this._update.isCompleteUpdate && this._update.patchCount == 2) {
+ LOG("Downloader:onStopRequest - verification of patch failed, " +
+ "downloading complete update patch");
+ this._update.isCompleteUpdate = true;
+ let updateStatus = this.downloadUpdate(this._update);
+
+ if (updateStatus == STATE_NONE) {
+ cleanupActiveUpdate();
+ } else {
+ allFailed = false;
+ }
+ }
+
+ if (allFailed) {
+ LOG("Downloader:onStopRequest - all update patch downloads failed");
+ // If the update UI is not open (e.g. the user closed the window while
+ // downloading) and if at any point this was a foreground download
+ // notify the user about the error. If the update was a background
+ // update there is no notification since the user won't be expecting it.
+ if (!Services.wm.getMostRecentWindow(UPDATE_WINDOW_NAME)) {
+ this._update.QueryInterface(Ci.nsIWritablePropertyBag);
+ if (this._update.getProperty("foregroundDownload") == "true") {
+ let prompter = Cc["@mozilla.org/updates/update-prompt;1"].
+ createInstance(Ci.nsIUpdatePrompt);
+ prompter.showUpdateError(this._update);
+ }
+ }
+
+ if (AppConstants.platform == "gonk") {
+ // We always forward errors in B2G, since Gaia controls the update UI
+ let prompter = Cc["@mozilla.org/updates/update-prompt;1"].
+ createInstance(Ci.nsIUpdatePrompt);
+ prompter.showUpdateError(this._update);
+ }
+
+ // Prevent leaking the update object (bug 454964).
+ this._update = null;
+ }
+ // A complete download has been initiated or the failure was handled.
+ return;
+ }
+
+ if (state == STATE_PENDING || state == STATE_PENDING_SERVICE ||
+ state == STATE_PENDING_ELEVATE) {
+ if (getCanStageUpdates()) {
+ LOG("Downloader:onStopRequest - attempting to stage update: " +
+ this._update.name);
+
+ // Initiate the update in the background
+ try {
+ Cc["@mozilla.org/updates/update-processor;1"].
+ createInstance(Ci.nsIUpdateProcessor).
+ processUpdate(this._update);
+ } catch (e) {
+ // Fail gracefully in case the application does not support the update
+ // processor service.
+ LOG("Downloader:onStopRequest - failed to stage update. Exception: " +
+ e);
+ if (this.background) {
+ shouldShowPrompt = true;
+ }
+ }
+ }
+ }
+
+ // Do this after *everything* else, since it will likely cause the app
+ // to shut down.
+ if (shouldShowPrompt) {
+ // Notify the user that an update has been downloaded and is ready for
+ // installation (i.e. that they should restart the application). We do
+ // not notify on failed update attempts.
+ let prompter = Cc["@mozilla.org/updates/update-prompt;1"].
+ createInstance(Ci.nsIUpdatePrompt);
+ prompter.showUpdateDownloaded(this._update, true);
+ }
+
+ if (shouldRegisterOnlineObserver) {
+ LOG("Downloader:onStopRequest - Registering online observer");
+ this.updateService._registerOnlineObserver();
+ } else if (shouldRetrySoon) {
+ LOG("Downloader:onStopRequest - Retrying soon");
+ this.updateService._consecutiveSocketErrors++;
+ if (this.updateService._retryTimer) {
+ this.updateService._retryTimer.cancel();
+ }
+ this.updateService._retryTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
+ this.updateService._retryTimer.initWithCallback(function() {
+ this._attemptResume();
+ }.bind(this.updateService), retryTimeout, Ci.nsITimer.TYPE_ONE_SHOT);
+ } else {
+ // Prevent leaking the update object (bug 454964)
+ this._update = null;
+ }
+ },
+
+ /**
+ * See nsIInterfaceRequestor.idl
+ */
+ getInterface: function Downloader_getInterface(iid) {
+ // The network request may require proxy authentication, so provide the
+ // default nsIAuthPrompt if requested.
+ if (iid.equals(Ci.nsIAuthPrompt)) {
+ var prompt = Cc["@mozilla.org/network/default-auth-prompt;1"].
+ createInstance();
+ return prompt.QueryInterface(iid);
+ }
+ throw Cr.NS_NOINTERFACE;
+ },
+
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIRequestObserver,
+ Ci.nsIProgressEventSink,
+ Ci.nsIInterfaceRequestor])
+};
+
+/**
+ * UpdatePrompt
+ * An object which can prompt the user with information about updates, request
+ * action, etc. Embedding clients can override this component with one that
+ * invokes a native front end.
+ * @constructor
+ */
+function UpdatePrompt() {
+}
+UpdatePrompt.prototype = {
+ /**
+ * See nsIUpdateService.idl
+ */
+ checkForUpdates: function UP_checkForUpdates() {
+ if (this._getAltUpdateWindow())
+ return;
+
+ this._showUI(null, URI_UPDATE_PROMPT_DIALOG, null, UPDATE_WINDOW_NAME,
+ null, null);
+ },
+
+ /**
+ * See nsIUpdateService.idl
+ */
+ showUpdateAvailable: function UP_showUpdateAvailable(update) {
+ if (getPref("getBoolPref", PREF_APP_UPDATE_SILENT, false) ||
+ this._getUpdateWindow() || this._getAltUpdateWindow()) {
+ return;
+ }
+
+ this._showUnobtrusiveUI(null, URI_UPDATE_PROMPT_DIALOG, null,
+ UPDATE_WINDOW_NAME, "updatesavailable", update);
+ },
+
+ /**
+ * See nsIUpdateService.idl
+ */
+ showUpdateDownloaded: function UP_showUpdateDownloaded(update, background) {
+ if (background && getPref("getBoolPref", PREF_APP_UPDATE_SILENT, false)) {
+ return;
+ }
+ // Trigger the display of the hamburger menu badge.
+ Services.obs.notifyObservers(null, "update-downloaded", update.state);
+
+ if (this._getAltUpdateWindow())
+ return;
+
+ if (background) {
+ this._showUnobtrusiveUI(null, URI_UPDATE_PROMPT_DIALOG, null,
+ UPDATE_WINDOW_NAME, "finishedBackground", update);
+ } else {
+ this._showUI(null, URI_UPDATE_PROMPT_DIALOG, null,
+ UPDATE_WINDOW_NAME, "finishedBackground", update);
+ }
+ },
+
+ /**
+ * See nsIUpdateService.idl
+ */
+ showUpdateError: function UP_showUpdateError(update) {
+ if (getPref("getBoolPref", PREF_APP_UPDATE_SILENT, false) ||
+ this._getAltUpdateWindow())
+ return;
+
+ // In some cases, we want to just show a simple alert dialog.
+ // Replace with Array.prototype.includes when it has stabilized.
+ if (update.state == STATE_FAILED &&
+ (WRITE_ERRORS.indexOf(update.errorCode) != -1 ||
+ update.errorCode == FILESYSTEM_MOUNT_READWRITE_ERROR ||
+ update.errorCode == FOTA_GENERAL_ERROR ||
+ update.errorCode == FOTA_FILE_OPERATION_ERROR ||
+ update.errorCode == FOTA_RECOVERY_ERROR ||
+ update.errorCode == FOTA_UNKNOWN_ERROR)) {
+ var title = gUpdateBundle.GetStringFromName("updaterIOErrorTitle");
+ var text = gUpdateBundle.formatStringFromName("updaterIOErrorMsg",
+ [Services.appinfo.name,
+ Services.appinfo.name], 2);
+ Services.ww.getNewPrompter(null).alert(title, text);
+ return;
+ }
+
+ if (update.errorCode == BACKGROUNDCHECK_MULTIPLE_FAILURES) {
+ this._showUIWhenIdle(null, URI_UPDATE_PROMPT_DIALOG, null,
+ UPDATE_WINDOW_NAME, null, update);
+ return;
+ }
+
+ this._showUI(null, URI_UPDATE_PROMPT_DIALOG, null, UPDATE_WINDOW_NAME,
+ "errors", update);
+ },
+
+ /**
+ * See nsIUpdateService.idl
+ */
+ showUpdateHistory: function UP_showUpdateHistory(parent) {
+ this._showUI(parent, URI_UPDATE_HISTORY_DIALOG, "modal,dialog=yes",
+ "Update:History", null, null);
+ },
+
+ /**
+ * See nsIUpdateService.idl
+ */
+ showUpdateElevationRequired: function UP_showUpdateElevationRequired() {
+ if (getPref("getBoolPref", PREF_APP_UPDATE_SILENT, false) ||
+ this._getAltUpdateWindow()) {
+ return;
+ }
+
+ let um = Cc["@mozilla.org/updates/update-manager;1"].
+ getService(Ci.nsIUpdateManager);
+ this._showUI(null, URI_UPDATE_PROMPT_DIALOG, null,
+ UPDATE_WINDOW_NAME, "finishedBackground", um.activeUpdate);
+ },
+
+ /**
+ * Returns the update window if present.
+ */
+ _getUpdateWindow: function UP__getUpdateWindow() {
+ return Services.wm.getMostRecentWindow(UPDATE_WINDOW_NAME);
+ },
+
+ /**
+ * Returns an alternative update window if present. When a window with this
+ * windowtype is open the application update service won't open the normal
+ * application update user interface window.
+ */
+ _getAltUpdateWindow: function UP__getAltUpdateWindow() {
+ let windowType = getPref("getCharPref", PREF_APP_UPDATE_ALTWINDOWTYPE, null);
+ if (!windowType)
+ return null;
+ return Services.wm.getMostRecentWindow(windowType);
+ },
+
+ /**
+ * Display the update UI after the prompt wait time has elapsed.
+ * @param parent
+ * A parent window, can be null
+ * @param uri
+ * The URI string of the dialog to show
+ * @param name
+ * The Window Name of the dialog to show, in case it is already open
+ * and can merely be focused
+ * @param page
+ * The page of the wizard to be displayed, if one is already open.
+ * @param update
+ * An update to pass to the UI in the window arguments.
+ * Can be null
+ */
+ _showUnobtrusiveUI: function UP__showUnobUI(parent, uri, features, name, page,
+ update) {
+ var observer = {
+ updatePrompt: this,
+ service: null,
+ timer: null,
+ notify: function () {
+ // the user hasn't restarted yet => prompt when idle
+ this.service.removeObserver(this, "quit-application");
+ // If the update window is already open skip showing the UI
+ if (this.updatePrompt._getUpdateWindow())
+ return;
+ this.updatePrompt._showUIWhenIdle(parent, uri, features, name, page, update);
+ },
+ observe: function (aSubject, aTopic, aData) {
+ switch (aTopic) {
+ case "quit-application":
+ if (this.timer)
+ this.timer.cancel();
+ this.service.removeObserver(this, "quit-application");
+ break;
+ }
+ }
+ };
+
+ // bug 534090 - show the UI for update available notifications when the
+ // the system has been idle for at least IDLE_TIME.
+ if (page == "updatesavailable") {
+ var idleService = Cc["@mozilla.org/widget/idleservice;1"].
+ getService(Ci.nsIIdleService);
+ // Don't allow the preference to set a value greater than 600 seconds for the idle time.
+ const IDLE_TIME = Math.min(getPref("getIntPref", PREF_APP_UPDATE_IDLETIME, 60), 600);
+ if (idleService.idleTime / 1000 >= IDLE_TIME) {
+ this._showUI(parent, uri, features, name, page, update);
+ return;
+ }
+ }
+
+ observer.service = Services.obs;
+ observer.service.addObserver(observer, "quit-application", false);
+
+ // bug 534090 - show the UI when idle for update available notifications.
+ if (page == "updatesavailable") {
+ this._showUIWhenIdle(parent, uri, features, name, page, update);
+ return;
+ }
+
+ // Give the user x seconds to react before prompting as defined by
+ // promptWaitTime
+ observer.timer = Cc["@mozilla.org/timer;1"].
+ createInstance(Ci.nsITimer);
+ observer.timer.initWithCallback(observer, update.promptWaitTime * 1000,
+ observer.timer.TYPE_ONE_SHOT);
+ },
+
+ /**
+ * Show the UI when the user was idle
+ * @param parent
+ * A parent window, can be null
+ * @param uri
+ * The URI string of the dialog to show
+ * @param name
+ * The Window Name of the dialog to show, in case it is already open
+ * and can merely be focused
+ * @param page
+ * The page of the wizard to be displayed, if one is already open.
+ * @param update
+ * An update to pass to the UI in the window arguments.
+ * Can be null
+ */
+ _showUIWhenIdle: function UP__showUIWhenIdle(parent, uri, features, name,
+ page, update) {
+ var idleService = Cc["@mozilla.org/widget/idleservice;1"].
+ getService(Ci.nsIIdleService);
+
+ // Don't allow the preference to set a value greater than 600 seconds for the idle time.
+ const IDLE_TIME = Math.min(getPref("getIntPref", PREF_APP_UPDATE_IDLETIME, 60), 600);
+ if (idleService.idleTime / 1000 >= IDLE_TIME) {
+ this._showUI(parent, uri, features, name, page, update);
+ } else {
+ var observer = {
+ updatePrompt: this,
+ observe: function (aSubject, aTopic, aData) {
+ switch (aTopic) {
+ case "idle":
+ // If the update window is already open skip showing the UI
+ if (!this.updatePrompt._getUpdateWindow())
+ this.updatePrompt._showUI(parent, uri, features, name, page, update);
+ // fall thru
+ case "quit-application":
+ idleService.removeIdleObserver(this, IDLE_TIME);
+ Services.obs.removeObserver(this, "quit-application");
+ break;
+ }
+ }
+ };
+ idleService.addIdleObserver(observer, IDLE_TIME);
+ Services.obs.addObserver(observer, "quit-application", false);
+ }
+ },
+
+ /**
+ * Show the Update Checking UI
+ * @param parent
+ * A parent window, can be null
+ * @param uri
+ * The URI string of the dialog to show
+ * @param name
+ * The Window Name of the dialog to show, in case it is already open
+ * and can merely be focused
+ * @param page
+ * The page of the wizard to be displayed, if one is already open.
+ * @param update
+ * An update to pass to the UI in the window arguments.
+ * Can be null
+ */
+ _showUI: function UP__showUI(parent, uri, features, name, page, update) {
+ var ary = null;
+ if (update) {
+ ary = Cc["@mozilla.org/array;1"].
+ createInstance(Ci.nsIMutableArray);
+ ary.appendElement(update, /* weak =*/ false);
+ }
+
+ var win = this._getUpdateWindow();
+ if (win) {
+ if (page && "setCurrentPage" in win)
+ win.setCurrentPage(page);
+ win.focus();
+ }
+ else {
+ var openFeatures = "chrome,centerscreen,dialog=no,resizable=no,titlebar,toolbar=no";
+ if (features)
+ openFeatures += "," + features;
+ Services.ww.openWindow(parent, uri, "", openFeatures, ary);
+ }
+ },
+
+ classDescription: "Update Prompt",
+ contractID: "@mozilla.org/updates/update-prompt;1",
+ classID: Components.ID("{27ABA825-35B5-4018-9FDD-F99250A0E722}"),
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIUpdatePrompt])
+};
+
+var components = [UpdateService, Checker, UpdatePrompt, UpdateManager];
+this.NSGetFactory = XPCOMUtils.generateNSGetFactory(components);
diff --git a/toolkit/mozapps/update/nsUpdateService.manifest b/toolkit/mozapps/update/nsUpdateService.manifest
new file mode 100644
index 000000000..59dbf0b5a
--- /dev/null
+++ b/toolkit/mozapps/update/nsUpdateService.manifest
@@ -0,0 +1,12 @@
+component {B3C290A6-3943-4B89-8BBE-C01EB7B3B311} nsUpdateService.js
+contract @mozilla.org/updates/update-service;1 {B3C290A6-3943-4B89-8BBE-C01EB7B3B311}
+category update-timer nsUpdateService @mozilla.org/updates/update-service;1,getService,background-update-timer,app.update.interval,43200,86400
+component {093C2356-4843-4C65-8709-D7DBCBBE7DFB} nsUpdateService.js
+contract @mozilla.org/updates/update-manager;1 {093C2356-4843-4C65-8709-D7DBCBBE7DFB}
+component {898CDC9B-E43F-422F-9CC4-2F6291B415A3} nsUpdateService.js
+contract @mozilla.org/updates/update-checker;1 {898CDC9B-E43F-422F-9CC4-2F6291B415A3}
+component {27ABA825-35B5-4018-9FDD-F99250A0E722} nsUpdateService.js
+contract @mozilla.org/updates/update-prompt;1 {27ABA825-35B5-4018-9FDD-F99250A0E722}
+component {e43b0010-04ba-4da6-b523-1f92580bc150} nsUpdateServiceStub.js
+contract @mozilla.org/updates/update-service-stub;1 {e43b0010-04ba-4da6-b523-1f92580bc150}
+category profile-after-change nsUpdateServiceStub @mozilla.org/updates/update-service-stub;1
diff --git a/toolkit/mozapps/update/nsUpdateServiceStub.js b/toolkit/mozapps/update/nsUpdateServiceStub.js
new file mode 100644
index 000000000..dbb841b84
--- /dev/null
+++ b/toolkit/mozapps/update/nsUpdateServiceStub.js
@@ -0,0 +1,45 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm", this);
+Cu.import("resource://gre/modules/FileUtils.jsm", this);
+
+const DIR_UPDATES = "updates";
+const FILE_UPDATE_STATUS = "update.status";
+
+const KEY_UPDROOT = "UpdRootD";
+
+/**
+ * Gets the specified directory at the specified hierarchy under the update root
+ * directory without creating it if it doesn't exist.
+ * @param pathArray
+ * An array of path components to locate beneath the directory
+ * specified by |key|
+ * @return nsIFile object for the location specified.
+ */
+function getUpdateDirNoCreate(pathArray) {
+ return FileUtils.getDir(KEY_UPDROOT, pathArray, false);
+}
+
+function UpdateServiceStub() {
+ let statusFile = getUpdateDirNoCreate([DIR_UPDATES, "0"]);
+ statusFile.append(FILE_UPDATE_STATUS);
+ // If the update.status file exists then initiate post update processing.
+ if (statusFile.exists()) {
+ let aus = Cc["@mozilla.org/updates/update-service;1"].
+ getService(Ci.nsIApplicationUpdateService).
+ QueryInterface(Ci.nsIObserver);
+ aus.observe(null, "post-update-processing", "");
+ }
+}
+UpdateServiceStub.prototype = {
+ observe: function() {},
+ classID: Components.ID("{e43b0010-04ba-4da6-b523-1f92580bc150}"),
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver])
+};
+
+this.NSGetFactory = XPCOMUtils.generateNSGetFactory([UpdateServiceStub]);
diff --git a/toolkit/mozapps/update/tests/Makefile.in b/toolkit/mozapps/update/tests/Makefile.in
new file mode 100644
index 000000000..0b8d19aa2
--- /dev/null
+++ b/toolkit/mozapps/update/tests/Makefile.in
@@ -0,0 +1,39 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+XPCSHELLTESTROOT = $(topobjdir)/_tests/xpcshell/$(relativesrcdir)
+
+pp_const_file = $(srcdir)/data/xpcshellConstantsPP.js
+
+PP_TARGETS += aus-test-const
+aus-test-const := $(pp_const_file)
+aus-test-const_PATH := $(XPCSHELLTESTROOT)/data
+aus-test-const_FLAGS := -Fsubstitution $(DEFINES) $(ACDEFINES)
+aus-test-const_TARGET := misc
+
+INI_TEST_FILES = \
+ TestAUSReadStrings1.ini \
+ TestAUSReadStrings2.ini \
+ TestAUSReadStrings3.ini \
+ $(NULL)
+
+MOZ_WINCONSOLE = 1
+
+include $(topsrcdir)/config/rules.mk
+
+# TestAUSReadStrings runs during check in the following directory with a Unicode
+# char in order to test bug 473417 on Windows.
+ifeq ($(OS_ARCH),WINNT)
+bug473417dir = test_bug473417-ó
+else
+bug473417dir = test_bug473417
+endif
+
+check::
+ $(RM) -rf $(DEPTH)/_tests/updater/ && $(NSINSTALL) -D $(DEPTH)/_tests/updater/$(bug473417dir)/
+ for i in $(INI_TEST_FILES); do \
+ $(INSTALL) $(srcdir)/$$i $(DEPTH)/_tests/updater/$(bug473417dir)/; \
+ done
+ $(INSTALL) $(FINAL_TARGET)/TestAUSReadStrings$(BIN_SUFFIX) $(DEPTH)/_tests/updater/$(bug473417dir)/
+ @$(RUN_TEST_PROGRAM) $(DEPTH)/_tests/updater/$(bug473417dir)/TestAUSReadStrings$(BIN_SUFFIX)
diff --git a/toolkit/mozapps/update/tests/TestAUSHelper.cpp b/toolkit/mozapps/update/tests/TestAUSHelper.cpp
new file mode 100644
index 000000000..f71103b7a
--- /dev/null
+++ b/toolkit/mozapps/update/tests/TestAUSHelper.cpp
@@ -0,0 +1,423 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+#ifdef XP_WIN
+# include <windows.h>
+# include <wintrust.h>
+# include <tlhelp32.h>
+# include <softpub.h>
+# include <direct.h>
+# include <io.h>
+ typedef WCHAR NS_tchar;
+# define NS_main wmain
+# ifndef F_OK
+# define F_OK 00
+# endif
+# ifndef W_OK
+# define W_OK 02
+# endif
+# ifndef R_OK
+# define R_OK 04
+# endif
+# if defined(_MSC_VER) && _MSC_VER < 1900
+# define stat _stat
+# endif
+# define NS_T(str) L ## str
+# define NS_tsnprintf(dest, count, fmt, ...) \
+ { \
+ int _count = count - 1; \
+ _snwprintf(dest, _count, fmt, ##__VA_ARGS__); \
+ dest[_count] = L'\0'; \
+ }
+# define NS_taccess _waccess
+# define NS_tchdir _wchdir
+# define NS_tfopen _wfopen
+# define NS_tstrcmp wcscmp
+# define NS_ttoi _wtoi
+# define NS_tstat _wstat
+# define NS_tgetcwd _wgetcwd
+# define LOG_S "%S"
+
+#include "../common/updatehelper.h"
+#include "../common/certificatecheck.h"
+
+#else
+# include <unistd.h>
+# define NS_main main
+ typedef char NS_tchar;
+# define NS_T(str) str
+# define NS_tsnprintf snprintf
+# define NS_taccess access
+# define NS_tchdir chdir
+# define NS_tfopen fopen
+# define NS_tstrcmp strcmp
+# define NS_ttoi atoi
+# define NS_tstat stat
+# define NS_tgetcwd getcwd
+# define NS_tfputs fputs
+# define LOG_S "%s"
+#endif
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#ifndef MAXPATHLEN
+# ifdef PATH_MAX
+# define MAXPATHLEN PATH_MAX
+# elif defined(MAX_PATH)
+# define MAXPATHLEN MAX_PATH
+# elif defined(_MAX_PATH)
+# define MAXPATHLEN _MAX_PATH
+# elif defined(CCHMAXPATH)
+# define MAXPATHLEN CCHMAXPATH
+# else
+# define MAXPATHLEN 1024
+# endif
+#endif
+
+static void
+WriteMsg(const NS_tchar *path, const char *status)
+{
+ FILE* outFP = NS_tfopen(path, NS_T("wb"));
+ if (!outFP) {
+ return;
+ }
+
+ fprintf(outFP, "%s\n", status);
+ fclose(outFP);
+ outFP = nullptr;
+}
+
+static bool
+CheckMsg(const NS_tchar *path, const char *expected)
+{
+ if (NS_taccess(path, F_OK)) {
+ return false;
+ }
+
+ FILE *inFP = NS_tfopen(path, NS_T("rb"));
+ if (!inFP) {
+ return false;
+ }
+
+ struct stat ms;
+ if (fstat(fileno(inFP), &ms)) {
+ fclose(inFP);
+ inFP = nullptr;
+ return false;
+ }
+
+ char *mbuf = (char *) malloc(ms.st_size + 1);
+ if (!mbuf) {
+ fclose(inFP);
+ inFP = nullptr;
+ return false;
+ }
+
+ size_t r = ms.st_size;
+ char *rb = mbuf;
+ size_t c = fread(rb, sizeof(char), 50, inFP);
+ r -= c;
+ rb += c;
+ if (c == 0 && r) {
+ free(mbuf);
+ fclose(inFP);
+ inFP = nullptr;
+ return false;
+ }
+ mbuf[ms.st_size] = '\0';
+ rb = mbuf;
+
+ bool isMatch = strcmp(rb, expected) == 0;
+ free(mbuf);
+ fclose(inFP);
+ inFP = nullptr;
+ return isMatch;
+}
+
+int NS_main(int argc, NS_tchar **argv)
+{
+ if (argc == 2) {
+ if (!NS_tstrcmp(argv[1], NS_T("post-update-async")) ||
+ !NS_tstrcmp(argv[1], NS_T("post-update-sync"))) {
+ NS_tchar exePath[MAXPATHLEN];
+#ifdef XP_WIN
+ if (!::GetModuleFileNameW(0, exePath, MAXPATHLEN)) {
+ return 1;
+ }
+#else
+ strcpy(exePath, argv[0]);
+#endif
+ NS_tchar runFilePath[MAXPATHLEN];
+ NS_tsnprintf(runFilePath, sizeof(runFilePath)/sizeof(runFilePath[0]),
+ NS_T("%s.running"), exePath);
+#ifdef XP_WIN
+ if (!NS_taccess(runFilePath, F_OK)) {
+ // This makes it possible to check if the post update process was
+ // launched twice which happens when the service performs an update.
+ NS_tchar runFilePathBak[MAXPATHLEN];
+ NS_tsnprintf(runFilePathBak, sizeof(runFilePathBak)/sizeof(runFilePathBak[0]),
+ NS_T("%s.bak"), runFilePath);
+ MoveFileExW(runFilePath, runFilePathBak, MOVEFILE_REPLACE_EXISTING);
+ }
+#endif
+ WriteMsg(runFilePath, "running");
+
+ if (!NS_tstrcmp(argv[1], NS_T("post-update-sync"))) {
+#ifdef XP_WIN
+ Sleep(2000);
+#else
+ sleep(2);
+#endif
+ }
+
+ NS_tchar logFilePath[MAXPATHLEN];
+ NS_tsnprintf(logFilePath, sizeof(logFilePath)/sizeof(logFilePath[0]),
+ NS_T("%s.log"), exePath);
+ WriteMsg(logFilePath, "post-update");
+ return 0;
+ }
+ }
+
+ if (argc < 3) {
+ fprintf(stderr, \
+ "\n" \
+ "Application Update Service Test Helper\n" \
+ "\n" \
+ "Usage: WORKINGDIR INFILE OUTFILE -s SECONDS [FILETOLOCK]\n" \
+ " or: WORKINGDIR LOGFILE [ARG2 ARG3...]\n" \
+ " or: signature-check filepath\n" \
+ " or: setup-symlink dir1 dir2 file symlink\n" \
+ " or: remove-symlink dir1 dir2 file symlink\n" \
+ " or: check-symlink symlink\n" \
+ " or: post-update\n" \
+ "\n" \
+ " WORKINGDIR \tThe relative path to the working directory to use.\n" \
+ " INFILE \tThe relative path from the working directory for the file to\n" \
+ " \tread actions to perform such as finish.\n" \
+ " OUTFILE \tThe relative path from the working directory for the file to\n" \
+ " \twrite status information.\n" \
+ " SECONDS \tThe number of seconds to sleep.\n" \
+ " FILETOLOCK \tThe relative path from the working directory to an existing\n" \
+ " \tfile to open exlusively.\n" \
+ " \tOnly available on Windows platforms and silently ignored on\n" \
+ " \tother platforms.\n" \
+ " LOGFILE \tThe relative path from the working directory to log the\n" \
+ " \tcommand line arguments.\n" \
+ " ARG2 ARG3...\tArguments to write to the LOGFILE after the preceding command\n" \
+ " \tline arguments.\n" \
+ "\n" \
+ "Note: All paths must be relative.\n" \
+ "\n");
+ return 1;
+ }
+
+ if (!NS_tstrcmp(argv[1], NS_T("check-signature"))) {
+#if defined(XP_WIN) && defined(MOZ_MAINTENANCE_SERVICE)
+ if (ERROR_SUCCESS == VerifyCertificateTrustForFile(argv[2])) {
+ return 0;
+ } else {
+ return 1;
+ }
+#else
+ // Not implemented on non-Windows platforms
+ return 1;
+#endif
+ }
+
+ if (!NS_tstrcmp(argv[1], NS_T("setup-symlink"))) {
+#ifdef XP_UNIX
+ NS_tchar path[MAXPATHLEN];
+ NS_tsnprintf(path, sizeof(path)/sizeof(path[0]),
+ NS_T("%s/%s"), NS_T("/tmp"), argv[2]);
+ mkdir(path, 0755);
+ NS_tsnprintf(path, sizeof(path)/sizeof(path[0]),
+ NS_T("%s/%s/%s"), NS_T("/tmp"), argv[2], argv[3]);
+ mkdir(path, 0755);
+ NS_tsnprintf(path, sizeof(path)/sizeof(path[0]),
+ NS_T("%s/%s/%s/%s"), NS_T("/tmp"), argv[2], argv[3], argv[4]);
+ FILE * file = NS_tfopen(path, NS_T("w"));
+ if (file) {
+ NS_tfputs(NS_T("test"), file);
+ fclose(file);
+ }
+ if (symlink(path, argv[5]) != 0) {
+ return 1;
+ }
+ NS_tsnprintf(path, sizeof(path)/sizeof(path[0]),
+ NS_T("%s/%s"), NS_T("/tmp"), argv[2]);
+ if (argc > 6 && !NS_tstrcmp(argv[6], NS_T("change-perm"))) {
+ chmod(path, 0644);
+ }
+ return 0;
+#else
+ // Not implemented on non-Unix platforms
+ return 1;
+#endif
+ }
+
+ if (!NS_tstrcmp(argv[1], NS_T("remove-symlink"))) {
+#ifdef XP_UNIX
+ NS_tchar path[MAXPATHLEN];
+ NS_tsnprintf(path, sizeof(path)/sizeof(path[0]),
+ NS_T("%s/%s"), NS_T("/tmp"), argv[2]);
+ chmod(path, 0755);
+ NS_tsnprintf(path, sizeof(path)/sizeof(path[0]),
+ NS_T("%s/%s/%s/%s"), NS_T("/tmp"), argv[2], argv[3], argv[4]);
+ unlink(path);
+ NS_tsnprintf(path, sizeof(path)/sizeof(path[0]),
+ NS_T("%s/%s/%s"), NS_T("/tmp"), argv[2], argv[3]);
+ rmdir(path);
+ NS_tsnprintf(path, sizeof(path)/sizeof(path[0]),
+ NS_T("%s/%s"), NS_T("/tmp"), argv[2]);
+ rmdir(path);
+ return 0;
+#else
+ // Not implemented on non-Unix platforms
+ return 1;
+#endif
+ }
+
+ if (!NS_tstrcmp(argv[1], NS_T("check-symlink"))) {
+#ifdef XP_UNIX
+ struct stat ss;
+ lstat(argv[2], &ss);
+ return S_ISLNK(ss.st_mode) ? 0 : 1;
+#else
+ // Not implemented on non-Unix platforms
+ return 1;
+#endif
+ }
+
+ if (!NS_tstrcmp(argv[1], NS_T("wait-for-service-stop"))) {
+#ifdef XP_WIN
+ const int maxWaitSeconds = NS_ttoi(argv[3]);
+ LPCWSTR serviceName = argv[2];
+ DWORD serviceState = WaitForServiceStop(serviceName, maxWaitSeconds);
+ if (SERVICE_STOPPED == serviceState) {
+ return 0;
+ } else {
+ return serviceState;
+ }
+#else
+ // Not implemented on non-Windows platforms
+ return 1;
+#endif
+ }
+
+ if (!NS_tstrcmp(argv[1], NS_T("wait-for-application-exit"))) {
+#ifdef XP_WIN
+ const int maxWaitSeconds = NS_ttoi(argv[3]);
+ LPCWSTR application = argv[2];
+ DWORD ret = WaitForProcessExit(application, maxWaitSeconds);
+ if (ERROR_SUCCESS == ret) {
+ return 0;
+ } else if (WAIT_TIMEOUT == ret) {
+ return 1;
+ } else {
+ return 2;
+ }
+#else
+ // Not implemented on non-Windows platforms
+ return 1;
+#endif
+ }
+
+ if (!NS_tstrcmp(argv[1], NS_T("is-process-running"))) {
+#ifdef XP_WIN
+ LPCWSTR application = argv[2];
+ return (ERROR_NOT_FOUND == IsProcessRunning(application)) ? 0 : 1;
+#else
+ // Not implemented on non-Windows platforms
+ return 1;
+#endif
+ }
+
+ if (!NS_tstrcmp(argv[1], NS_T("launch-service"))) {
+#ifdef XP_WIN
+ DWORD ret = LaunchServiceSoftwareUpdateCommand(argc - 2, (LPCWSTR *)argv + 2);
+ if (ret != ERROR_SUCCESS) {
+ // 192 is used to avoid reusing a possible return value from the call to
+ // WaitForServiceStop
+ return 0x000000C0;
+ }
+ // Wait a maximum of 120 seconds.
+ DWORD lastState = WaitForServiceStop(SVC_NAME, 120);
+ if (SERVICE_STOPPED == lastState) {
+ return 0;
+ }
+ return lastState;
+#else
+ // Not implemented on non-Windows platforms
+ return 1;
+#endif
+ }
+
+ if (NS_tchdir(argv[1]) != 0) {
+ return 1;
+ }
+
+ // File in use test helper section
+ if (!NS_tstrcmp(argv[4], NS_T("-s"))) {
+ NS_tchar *cwd = NS_tgetcwd(nullptr, 0);
+ NS_tchar inFilePath[MAXPATHLEN];
+ NS_tsnprintf(inFilePath, sizeof(inFilePath)/sizeof(inFilePath[0]),
+ NS_T("%s/%s"), cwd, argv[2]);
+ NS_tchar outFilePath[MAXPATHLEN];
+ NS_tsnprintf(outFilePath, sizeof(outFilePath)/sizeof(outFilePath[0]),
+ NS_T("%s/%s"), cwd, argv[3]);
+
+ int seconds = NS_ttoi(argv[5]);
+#ifdef XP_WIN
+ HANDLE hFile = INVALID_HANDLE_VALUE;
+ if (argc == 7) {
+ hFile = CreateFileW(argv[6],
+ DELETE | GENERIC_WRITE, 0,
+ nullptr, OPEN_EXISTING, 0, nullptr);
+ if (hFile == INVALID_HANDLE_VALUE) {
+ WriteMsg(outFilePath, "error_locking");
+ return 1;
+ }
+ }
+
+ WriteMsg(outFilePath, "sleeping");
+ int i = 0;
+ while (!CheckMsg(inFilePath, "finish\n") && i++ <= seconds) {
+ Sleep(1000);
+ }
+
+ if (argc == 7) {
+ CloseHandle(hFile);
+ }
+#else
+ WriteMsg(outFilePath, "sleeping");
+ int i = 0;
+ while (!CheckMsg(inFilePath, "finish\n") && i++ <= seconds) {
+ sleep(1);
+ }
+#endif
+ WriteMsg(outFilePath, "finished");
+ return 0;
+ }
+
+ {
+ // Command line argument test helper section
+ NS_tchar logFilePath[MAXPATHLEN];
+ NS_tsnprintf(logFilePath, sizeof(logFilePath)/sizeof(logFilePath[0]),
+ NS_T("%s"), argv[2]);
+
+ FILE* logFP = NS_tfopen(logFilePath, NS_T("wb"));
+ for (int i = 1; i < argc; ++i) {
+ fprintf(logFP, LOG_S "\n", argv[i]);
+ }
+
+ fclose(logFP);
+ logFP = nullptr;
+ }
+
+ return 0;
+}
diff --git a/toolkit/mozapps/update/tests/TestAUSReadStrings.cpp b/toolkit/mozapps/update/tests/TestAUSReadStrings.cpp
new file mode 100644
index 000000000..c1de44f8e
--- /dev/null
+++ b/toolkit/mozapps/update/tests/TestAUSReadStrings.cpp
@@ -0,0 +1,172 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * This binary tests the updater's ReadStrings ini parser and should run in a
+ * directory with a Unicode character to test bug 473417.
+ */
+#ifdef XP_WIN
+ #include <windows.h>
+ #define NS_main wmain
+ #define NS_tstrrchr wcsrchr
+ #define NS_T(str) L ## str
+ #define PATH_SEPARATOR_CHAR L'\\'
+ // On Windows, argv[0] can also have forward slashes instead
+ #define ALT_PATH_SEPARATOR_CHAR L'/'
+#else
+ #include <unistd.h>
+ #define NS_main main
+ #define NS_tstrrchr strrchr
+ #define NS_T(str) str
+ #define PATH_SEPARATOR_CHAR '/'
+#endif
+
+#include <stdio.h>
+#include <stdarg.h>
+#include <string.h>
+
+#include "updater/resource.h"
+#include "updater/progressui.h"
+#include "common/readstrings.h"
+#include "common/errors.h"
+#include "mozilla/ArrayUtils.h"
+
+#ifndef MAXPATHLEN
+# ifdef PATH_MAX
+# define MAXPATHLEN PATH_MAX
+# elif defined(MAX_PATH)
+# define MAXPATHLEN MAX_PATH
+# elif defined(_MAX_PATH)
+# define MAXPATHLEN _MAX_PATH
+# elif defined(CCHMAXPATH)
+# define MAXPATHLEN CCHMAXPATH
+# else
+# define MAXPATHLEN 1024
+# endif
+#endif
+
+#define TEST_NAME "Updater ReadStrings"
+
+using namespace mozilla;
+
+static int gFailCount = 0;
+
+/**
+ * Prints the given failure message and arguments using printf, prepending
+ * "TEST-UNEXPECTED-FAIL " for the benefit of the test harness and
+ * appending "\n" to eliminate having to type it at each call site.
+ */
+void fail(const char* msg, ...)
+{
+ va_list ap;
+
+ printf("TEST-UNEXPECTED-FAIL | ");
+
+ va_start(ap, msg);
+ vprintf(msg, ap);
+ va_end(ap);
+
+ putchar('\n');
+ ++gFailCount;
+}
+
+int NS_main(int argc, NS_tchar **argv)
+{
+ printf("Running TestAUSReadStrings tests\n");
+
+ int rv = 0;
+ int retval;
+ NS_tchar inifile[MAXPATHLEN];
+ StringTable testStrings;
+
+ NS_tchar *slash = NS_tstrrchr(argv[0], PATH_SEPARATOR_CHAR);
+#ifdef ALT_PATH_SEPARATOR_CHAR
+ NS_tchar *altslash = NS_tstrrchr(argv[0], ALT_PATH_SEPARATOR_CHAR);
+ slash = (slash > altslash) ? slash : altslash;
+#endif // ALT_PATH_SEPARATOR_CHAR
+
+ if (!slash) {
+ fail("%s | unable to find platform specific path separator (check 1)", TEST_NAME);
+ return 20;
+ }
+
+ *(++slash) = '\0';
+ // Test success when the ini file exists with both Title and Info in the
+ // Strings section and the values for Title and Info.
+ NS_tsnprintf(inifile, ArrayLength(inifile), NS_T("%sTestAUSReadStrings1.ini"), argv[0]);
+ retval = ReadStrings(inifile, &testStrings);
+ if (retval == OK) {
+ if (strcmp(testStrings.title, "Title Test - \xD0\x98\xD1\x81\xD0\xBF\xD1\x8B" \
+ "\xD1\x82\xD0\xB0\xD0\xBD\xD0\xB8\xD0\xB5 " \
+ "\xCE\x94\xCE\xBF\xCE\xBA\xCE\xB9\xCE\xBC\xCE\xAE " \
+ "\xE3\x83\x86\xE3\x82\xB9\xE3\x83\x88 " \
+ "\xE6\xB8\xAC\xE8\xA9\xA6 " \
+ "\xE6\xB5\x8B\xE8\xAF\x95") != 0) {
+ rv = 21;
+ fail("%s | Title ini value incorrect (check 3)", TEST_NAME);
+ }
+
+ if (strcmp(testStrings.info, "Info Test - \xD0\x98\xD1\x81\xD0\xBF\xD1\x8B" \
+ "\xD1\x82\xD0\xB0\xD0\xBD\xD0\xB8\xD0\xB5 " \
+ "\xCE\x94\xCE\xBF\xCE\xBA\xCE\xB9\xCE\xBC\xCE\xAE " \
+ "\xE3\x83\x86\xE3\x82\xB9\xE3\x83\x88 " \
+ "\xE6\xB8\xAC\xE8\xA9\xA6 " \
+ "\xE6\xB5\x8B\xE8\xAF\x95\xE2\x80\xA6") != 0) {
+ rv = 22;
+ fail("%s | Info ini value incorrect (check 4)", TEST_NAME);
+ }
+ } else {
+ fail("%s | ReadStrings returned %i (check 2)", TEST_NAME, retval);
+ rv = 23;
+ }
+
+ // Test failure when the ini file exists without Title and with Info in the
+ // Strings section.
+ NS_tsnprintf(inifile, ArrayLength(inifile), NS_T("%sTestAUSReadStrings2.ini"), argv[0]);
+ retval = ReadStrings(inifile, &testStrings);
+ if (retval != PARSE_ERROR) {
+ rv = 24;
+ fail("%s | ReadStrings returned %i (check 5)", TEST_NAME, retval);
+ }
+
+ // Test failure when the ini file exists with Title and without Info in the
+ // Strings section.
+ NS_tsnprintf(inifile, ArrayLength(inifile), NS_T("%sTestAUSReadStrings3.ini"), argv[0]);
+ retval = ReadStrings(inifile, &testStrings);
+ if (retval != PARSE_ERROR) {
+ rv = 25;
+ fail("%s | ReadStrings returned %i (check 6)", TEST_NAME, retval);
+ }
+
+ // Test failure when the ini file doesn't exist
+ NS_tsnprintf(inifile, ArrayLength(inifile), NS_T("%sTestAUSReadStringsBogus.ini"), argv[0]);
+ retval = ReadStrings(inifile, &testStrings);
+ if (retval != READ_ERROR) {
+ rv = 26;
+ fail("%s | ini file doesn't exist (check 7)", TEST_NAME);
+ }
+
+ // Test reading a non-default section name
+ NS_tsnprintf(inifile, ArrayLength(inifile), NS_T("%sTestAUSReadStrings3.ini"), argv[0]);
+ retval = ReadStrings(inifile, "Title\0", 1, &testStrings.title, "BogusSection2");
+ if (retval == OK) {
+ if (strcmp(testStrings.title, "Bogus Title") != 0) {
+ rv = 27;
+ fail("%s | Title ini value incorrect (check 9)", TEST_NAME);
+ }
+ } else {
+ fail("%s | ReadStrings returned %i (check 8)", TEST_NAME, retval);
+ rv = 28;
+ }
+
+
+ if (rv == 0) {
+ printf("TEST-PASS | %s | all checks passed\n", TEST_NAME);
+ } else {
+ fail("%s | %i out of 9 checks failed", TEST_NAME, gFailCount);
+ }
+
+ return rv;
+}
diff --git a/toolkit/mozapps/update/tests/TestAUSReadStrings1.ini b/toolkit/mozapps/update/tests/TestAUSReadStrings1.ini
new file mode 100644
index 000000000..5ab13c185
--- /dev/null
+++ b/toolkit/mozapps/update/tests/TestAUSReadStrings1.ini
@@ -0,0 +1,47 @@
+; This file is in the UTF-8 encoding
+
+[BogusSection1]
+
+; Comment
+
+Title=Bogus Title
+
+; Comment
+
+Info=Bogus Info
+
+; Comment
+
+[Strings]
+
+Bogus1=Bogus1
+
+; Comment
+
+Title=Title Test - ИÑпытание Δοκιμή テスト 測試 测试
+
+; Comment
+
+Bogus2=Bogus2
+
+; Comment
+
+Info=Info Test - ИÑпытание Δοκιμή テスト 測試 测试…
+
+; Comment
+
+Bogus3=Bogus3
+
+; Comment
+
+[BogusSection2]
+
+; Comment
+
+Title=Bogus Title
+
+; Comment
+
+Info=Bogus Info
+
+; Comment
diff --git a/toolkit/mozapps/update/tests/TestAUSReadStrings2.ini b/toolkit/mozapps/update/tests/TestAUSReadStrings2.ini
new file mode 100644
index 000000000..8291a7c94
--- /dev/null
+++ b/toolkit/mozapps/update/tests/TestAUSReadStrings2.ini
@@ -0,0 +1,39 @@
+; This file is in the UTF-8 encoding
+
+[BogusSection1]
+
+; Comment
+
+Title=Bogus Title
+
+; Comment
+
+Info=Bogus Info
+
+; Comment
+
+[Strings]
+
+Bogus1=Bogus1
+
+; Comment
+
+Info=Info
+
+; Comment
+
+Bogus2=Bogus2
+
+; Comment
+
+[BogusSection2]
+
+; Comment
+
+Title=Bogus Title
+
+; Comment
+
+Info=Bogus Info
+
+; Comment
diff --git a/toolkit/mozapps/update/tests/TestAUSReadStrings3.ini b/toolkit/mozapps/update/tests/TestAUSReadStrings3.ini
new file mode 100644
index 000000000..a64d1232e
--- /dev/null
+++ b/toolkit/mozapps/update/tests/TestAUSReadStrings3.ini
@@ -0,0 +1,39 @@
+; This file is in the UTF-8 encoding
+
+[BogusSection1]
+
+; Comment
+
+Title=Bogus Title
+
+; Comment
+
+Info=Bogus Info
+
+; Comment
+
+[Strings]
+
+Bogus1=Bogus1
+
+; Comment
+
+Title=Title
+
+; Comment
+
+Bogus2=Bogus2
+
+; Comment
+
+[BogusSection2]
+
+; Comment
+
+Title=Bogus Title
+
+; Comment
+
+Info=Bogus Info
+
+; Comment
diff --git a/toolkit/mozapps/update/tests/chrome/.eslintrc.js b/toolkit/mozapps/update/tests/chrome/.eslintrc.js
new file mode 100644
index 000000000..8c0f4f574
--- /dev/null
+++ b/toolkit/mozapps/update/tests/chrome/.eslintrc.js
@@ -0,0 +1,7 @@
+"use strict";
+
+module.exports = {
+ "extends": [
+ "../../../../../testing/mochitest/chrome.eslintrc.js"
+ ]
+};
diff --git a/toolkit/mozapps/update/tests/chrome/chrome.ini b/toolkit/mozapps/update/tests/chrome/chrome.ini
new file mode 100644
index 000000000..88e3dd4e8
--- /dev/null
+++ b/toolkit/mozapps/update/tests/chrome/chrome.ini
@@ -0,0 +1,64 @@
+; This Source Code Form is subject to the terms of the Mozilla Public
+; License, v. 2.0. If a copy of the MPL was not distributed with this
+; file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+[DEFAULT]
+tags = appupdate
+support-files =
+ utils.js
+ update.sjs
+
+; mochitest-chrome tests must start with "test_" and are executed in sorted
+; order and not in the order specified in the manifest.
+[test_0010_background_basic.xul]
+[test_0011_check_basic.xul]
+[test_0012_check_basic_staging.xul]
+skip-if = asan
+reason = Bug 1168003
+[test_0013_check_no_updates.xul]
+[test_0014_check_error_xml_malformed.xul]
+[test_0061_check_verifyFailPartial_noComplete.xul]
+[test_0062_check_verifyFailComplete_noPartial.xul]
+[test_0063_check_verifyFailPartialComplete.xul]
+[test_0064_check_verifyFailPartial_successComplete.xul]
+[test_0071_notify_verifyFailPartial_noComplete.xul]
+[test_0072_notify_verifyFailComplete_noPartial.xul]
+[test_0073_notify_verifyFailPartialComplete.xul]
+[test_0074_notify_verifyFailPartial_successComplete.xul]
+[test_0081_error_patchApplyFailure_partial_only.xul]
+[test_0082_error_patchApplyFailure_complete_only.xul]
+[test_0083_error_patchApplyFailure_partial_complete.xul]
+[test_0084_error_patchApplyFailure_verify_failed.xul]
+[test_0085_error_patchApplyFailure_partial_complete_staging.xul]
+skip-if = asan
+reason = Bug 1168003
+[test_0092_finishedBackground.xul]
+[test_0093_restartNotification.xul]
+[test_0094_restartNotification_remote.xul]
+[test_0095_restartNotification_remoteInvalidNumber.xul]
+[test_0096_restartNotification_stagedBackground.xul]
+skip-if = asan
+reason = Bug 1168003
+[test_0097_restartNotification_stagedServiceBackground.xul]
+skip-if = os != 'win'
+reason = only Windows has the maintenance service.
+[test_0101_background_restartNotification.xul]
+[test_0102_background_restartNotification_staging.xul]
+skip-if = asan
+reason = Bug 1168003
+[test_0103_background_restartNotification_stagingService.xul]
+skip-if = os != 'win'
+reason = only Windows has the maintenance service.
+[test_0111_neverButton_basic.xul]
+[test_0113_showNeverForVersionRemovedWithPref.xul]
+[test_0151_notify_backgroundCheckError.xul]
+[test_0152_notify_backgroundCheckOfflineRetry.xul]
+[test_0161_check_unsupported.xul]
+[test_0162_notify_unsupported.xul]
+[test_0171_check_noPerms_manual.xul]
+skip-if = os != 'win'
+reason = test must be able to prevent file deletion.
+[test_0172_notify_noPerms_manual.xul]
+skip-if = os != 'win'
+reason = test must be able to prevent file deletion.
+[test_9999_cleanup.xul]
diff --git a/toolkit/mozapps/update/tests/chrome/test_0010_background_basic.xul b/toolkit/mozapps/update/tests/chrome/test_0010_background_basic.xul
new file mode 100644
index 000000000..8d088cc8a
--- /dev/null
+++ b/toolkit/mozapps/update/tests/chrome/test_0010_background_basic.xul
@@ -0,0 +1,50 @@
+<?xml version="1.0"?>
+<!--
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+-->
+
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?>
+
+<window title="Update Wizard pages: basic, download, and finished"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ onload="runTestDefault();">
+<script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
+<script type="application/javascript"
+ src="utils.js"/>
+
+<script type="application/javascript">
+<![CDATA[
+
+const TESTS = [ {
+ pageid: PAGEID_FOUND_BASIC,
+ buttonClick: "next"
+}, {
+ pageid: PAGEID_DOWNLOADING
+}, {
+ pageid: PAGEID_FINISHED,
+ buttonClick: "extra1"
+} ];
+
+function runTest() {
+ debugDump("entering");
+
+ let url = URL_HTTP_UPDATE_XML + "?showDetails=1&showPrompt=1" +
+ getVersionParams();
+ setUpdateURL(url);
+
+ gAUS.checkForBackgroundUpdates();
+}
+
+]]>
+</script>
+
+<body xmlns="http://www.w3.org/1999/xhtml">
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test"></pre>
+</body>
+</window>
diff --git a/toolkit/mozapps/update/tests/chrome/test_0011_check_basic.xul b/toolkit/mozapps/update/tests/chrome/test_0011_check_basic.xul
new file mode 100644
index 000000000..12b5302a4
--- /dev/null
+++ b/toolkit/mozapps/update/tests/chrome/test_0011_check_basic.xul
@@ -0,0 +1,51 @@
+<?xml version="1.0"?>
+<!--
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+-->
+
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?>
+
+<window title="Update Wizard pages: update check, basic, download, and finished"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ onload="runTestDefault();">
+<script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
+<script type="application/javascript"
+ src="utils.js"/>
+
+<script type="application/javascript">
+<![CDATA[
+
+const TESTS = [ {
+ pageid: PAGEID_CHECKING
+}, {
+ pageid: PAGEID_FOUND_BASIC,
+ buttonClick: "next"
+}, {
+ pageid: PAGEID_DOWNLOADING
+}, {
+ pageid: PAGEID_FINISHED,
+ buttonClick: "extra1"
+} ];
+
+function runTest() {
+ debugDump("entering");
+
+ let url = URL_HTTP_UPDATE_XML + "?showDetails=1" + getVersionParams();
+ setUpdateURL(url);
+
+ gUP.checkForUpdates();
+}
+
+]]>
+</script>
+
+<body xmlns="http://www.w3.org/1999/xhtml">
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test"></pre>
+</body>
+</window>
diff --git a/toolkit/mozapps/update/tests/chrome/test_0012_check_basic_staging.xul b/toolkit/mozapps/update/tests/chrome/test_0012_check_basic_staging.xul
new file mode 100644
index 000000000..d910adc08
--- /dev/null
+++ b/toolkit/mozapps/update/tests/chrome/test_0012_check_basic_staging.xul
@@ -0,0 +1,55 @@
+<?xml version="1.0"?>
+<!--
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+-->
+
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?>
+
+<window title="Update Wizard pages: update check, basic, download with staging, and finished"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ onload="runTestDefault();">
+<script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
+<script type="application/javascript"
+ src="utils.js"/>
+
+<script type="application/javascript">
+<![CDATA[
+
+const TESTS = [ {
+ pageid: PAGEID_CHECKING
+}, {
+ pageid: PAGEID_FOUND_BASIC,
+ buttonClick: "next"
+}, {
+ pageid: PAGEID_DOWNLOADING
+}, {
+ pageid: PAGEID_FINISHED,
+ buttonClick: "extra1"
+} ];
+
+gUseTestUpdater = true;
+
+function runTest() {
+ debugDump("entering");
+
+ Services.prefs.setBoolPref(PREF_APP_UPDATE_STAGING_ENABLED, true);
+
+ let url = URL_HTTP_UPDATE_XML + "?showDetails=1" + getVersionParams();
+ setUpdateURL(url);
+
+ gUP.checkForUpdates();
+}
+
+]]>
+</script>
+
+<body xmlns="http://www.w3.org/1999/xhtml">
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test"></pre>
+</body>
+</window>
diff --git a/toolkit/mozapps/update/tests/chrome/test_0013_check_no_updates.xul b/toolkit/mozapps/update/tests/chrome/test_0013_check_no_updates.xul
new file mode 100644
index 000000000..c3f024c73
--- /dev/null
+++ b/toolkit/mozapps/update/tests/chrome/test_0013_check_no_updates.xul
@@ -0,0 +1,46 @@
+<?xml version="1.0"?>
+<!--
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+-->
+
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?>
+
+<window title="Update Wizard pages: update check and no updates found"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ onload="runTestDefault();">
+<script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
+<script type="application/javascript"
+ src="utils.js"/>
+
+<script type="application/javascript">
+<![CDATA[
+
+const TESTS = [ {
+ pageid: PAGEID_CHECKING
+}, {
+ pageid: PAGEID_NO_UPDATES_FOUND,
+ buttonClick: "finish"
+} ];
+
+function runTest() {
+ debugDump("entering");
+
+ let url = URL_HTTP_UPDATE_XML + "?noUpdates=1";
+ setUpdateURL(url);
+
+ gUP.checkForUpdates();
+}
+
+]]>
+</script>
+
+<body xmlns="http://www.w3.org/1999/xhtml">
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test"></pre>
+</body>
+</window>
diff --git a/toolkit/mozapps/update/tests/chrome/test_0014_check_error_xml_malformed.xul b/toolkit/mozapps/update/tests/chrome/test_0014_check_error_xml_malformed.xul
new file mode 100644
index 000000000..f399a0096
--- /dev/null
+++ b/toolkit/mozapps/update/tests/chrome/test_0014_check_error_xml_malformed.xul
@@ -0,0 +1,46 @@
+<?xml version="1.0"?>
+<!--
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+-->
+
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?>
+
+<window title="Update Wizard pages: update check and error (xml malformed)"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ onload="runTestDefault();">
+<script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
+<script type="application/javascript"
+ src="utils.js"/>
+
+<script type="application/javascript">
+<![CDATA[
+
+const TESTS = [ {
+ pageid: PAGEID_CHECKING
+}, {
+ pageid: PAGEID_ERRORS,
+ buttonClick: "finish"
+} ];
+
+function runTest() {
+ debugDump("entering");
+
+ let url = URL_HTTP_UPDATE_XML + "?xmlMalformed=1";
+ setUpdateURL(url);
+
+ gUP.checkForUpdates();
+}
+
+]]>
+</script>
+
+<body xmlns="http://www.w3.org/1999/xhtml">
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test"></pre>
+</body>
+</window>
diff --git a/toolkit/mozapps/update/tests/chrome/test_0061_check_verifyFailPartial_noComplete.xul b/toolkit/mozapps/update/tests/chrome/test_0061_check_verifyFailPartial_noComplete.xul
new file mode 100644
index 000000000..1040c19e3
--- /dev/null
+++ b/toolkit/mozapps/update/tests/chrome/test_0061_check_verifyFailPartial_noComplete.xul
@@ -0,0 +1,52 @@
+<?xml version="1.0"?>
+<!--
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+-->
+
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?>
+
+<window title="Update Wizard pages: update check, basic, download, and errors (partial patch with an invalid size)"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ onload="runTestDefault();">
+<script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
+<script type="application/javascript"
+ src="utils.js"/>
+
+<script type="application/javascript">
+<![CDATA[
+
+const TESTS = [ {
+ pageid: PAGEID_CHECKING
+}, {
+ pageid: PAGEID_FOUND_BASIC,
+ buttonClick: "next"
+}, {
+ pageid: PAGEID_DOWNLOADING
+}, {
+ pageid: PAGEID_ERRORS,
+ buttonClick: "finish"
+} ];
+
+function runTest() {
+ debugDump("entering");
+
+ let url = URL_HTTP_UPDATE_XML + "?showDetails=1&partialPatchOnly=1" +
+ "&invalidPartialSize=1" + getVersionParams();
+ setUpdateURL(url);
+
+ gUP.checkForUpdates();
+}
+
+]]>
+</script>
+
+<body xmlns="http://www.w3.org/1999/xhtml">
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test"></pre>
+</body>
+</window>
diff --git a/toolkit/mozapps/update/tests/chrome/test_0062_check_verifyFailComplete_noPartial.xul b/toolkit/mozapps/update/tests/chrome/test_0062_check_verifyFailComplete_noPartial.xul
new file mode 100644
index 000000000..9221a4b98
--- /dev/null
+++ b/toolkit/mozapps/update/tests/chrome/test_0062_check_verifyFailComplete_noPartial.xul
@@ -0,0 +1,52 @@
+<?xml version="1.0"?>
+<!--
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+-->
+
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?>
+
+<window title="Update Wizard pages: update check, basic, download, and errors (complete patch with an invalid size)"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ onload="runTestDefault();">
+<script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
+<script type="application/javascript"
+ src="utils.js"/>
+
+<script type="application/javascript">
+<![CDATA[
+
+const TESTS = [ {
+ pageid: PAGEID_CHECKING
+}, {
+ pageid: PAGEID_FOUND_BASIC,
+ buttonClick: "next"
+}, {
+ pageid: PAGEID_DOWNLOADING
+}, {
+ pageid: PAGEID_ERRORS,
+ buttonClick: "finish"
+} ];
+
+function runTest() {
+ debugDump("entering");
+
+ let url = URL_HTTP_UPDATE_XML + "?showDetails=1&completePatchOnly=1" +
+ "&invalidCompleteSize=1" + getVersionParams();
+ setUpdateURL(url);
+
+ gUP.checkForUpdates();
+}
+
+]]>
+</script>
+
+<body xmlns="http://www.w3.org/1999/xhtml">
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test"></pre>
+</body>
+</window>
diff --git a/toolkit/mozapps/update/tests/chrome/test_0063_check_verifyFailPartialComplete.xul b/toolkit/mozapps/update/tests/chrome/test_0063_check_verifyFailPartialComplete.xul
new file mode 100644
index 000000000..8da5c7e97
--- /dev/null
+++ b/toolkit/mozapps/update/tests/chrome/test_0063_check_verifyFailPartialComplete.xul
@@ -0,0 +1,52 @@
+<?xml version="1.0"?>
+<!--
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+-->
+
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?>
+
+<window title="Update Wizard pages: update check, basic, download, and errors (partial and complete patches with invalid sizes)"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ onload="runTestDefault();">
+<script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
+<script type="application/javascript"
+ src="utils.js"/>
+
+<script type="application/javascript">
+<![CDATA[
+
+const TESTS = [ {
+ pageid: PAGEID_CHECKING
+}, {
+ pageid: PAGEID_FOUND_BASIC,
+ buttonClick: "next"
+}, {
+ pageid: PAGEID_DOWNLOADING
+}, {
+ pageid: PAGEID_ERRORS,
+ buttonClick: "finish"
+} ];
+
+function runTest() {
+ debugDump("entering");
+
+ let url = URL_HTTP_UPDATE_XML + "?showDetails=1&invalidPartialSize=1" +
+ "&invalidCompleteSize=1" + getVersionParams();
+ setUpdateURL(url);
+
+ gUP.checkForUpdates();
+}
+
+]]>
+</script>
+
+<body xmlns="http://www.w3.org/1999/xhtml">
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test"></pre>
+</body>
+</window>
diff --git a/toolkit/mozapps/update/tests/chrome/test_0064_check_verifyFailPartial_successComplete.xul b/toolkit/mozapps/update/tests/chrome/test_0064_check_verifyFailPartial_successComplete.xul
new file mode 100644
index 000000000..db0f33d2f
--- /dev/null
+++ b/toolkit/mozapps/update/tests/chrome/test_0064_check_verifyFailPartial_successComplete.xul
@@ -0,0 +1,52 @@
+<?xml version="1.0"?>
+<!--
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+-->
+
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?>
+
+<window title="Update Wizard pages: update check, basic, download, and finished (partial patch with an invalid size and successful complete patch)"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ onload="runTestDefault();">
+<script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
+<script type="application/javascript"
+ src="utils.js"/>
+
+<script type="application/javascript">
+<![CDATA[
+
+const TESTS = [ {
+ pageid: PAGEID_CHECKING
+}, {
+ pageid: PAGEID_FOUND_BASIC,
+ buttonClick: "next"
+}, {
+ pageid: PAGEID_DOWNLOADING
+}, {
+ pageid: PAGEID_FINISHED,
+ buttonClick: "extra1"
+} ];
+
+function runTest() {
+ debugDump("entering");
+
+ let url = URL_HTTP_UPDATE_XML + "?showDetails=1&invalidPartialSize=1" +
+ getVersionParams();
+ setUpdateURL(url);
+
+ gUP.checkForUpdates();
+}
+
+]]>
+</script>
+
+<body xmlns="http://www.w3.org/1999/xhtml">
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test"></pre>
+</body>
+</window>
diff --git a/toolkit/mozapps/update/tests/chrome/test_0071_notify_verifyFailPartial_noComplete.xul b/toolkit/mozapps/update/tests/chrome/test_0071_notify_verifyFailPartial_noComplete.xul
new file mode 100644
index 000000000..736df13a3
--- /dev/null
+++ b/toolkit/mozapps/update/tests/chrome/test_0071_notify_verifyFailPartial_noComplete.xul
@@ -0,0 +1,53 @@
+<?xml version="1.0"?>
+<!--
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+-->
+
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?>
+
+<window title="Update Wizard pages: errors (partial patch with an invalid size)"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ onload="runTestDefault();">
+<script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
+<script type="application/javascript"
+ src="utils.js"/>
+
+<script type="application/javascript">
+<![CDATA[
+
+const TESTS = [ {
+ pageid: PAGEID_ERRORS,
+ buttonClick: "finish"
+} ];
+
+function runTest() {
+ debugDump("entering");
+
+ let patches = getLocalPatchString("partial", null, null, null, "1234", null,
+ STATE_DOWNLOADING);
+ let updates = getLocalUpdateString(patches, null, null, null,
+ Services.appinfo.version,
+ Services.appinfo.platformVersion);
+
+ writeUpdatesToXMLFile(getLocalUpdatesXMLString(updates), true);
+
+ writeUpdatesToXMLFile(getLocalUpdatesXMLString(""), false);
+ writeStatusFile(STATE_DOWNLOADING);
+ reloadUpdateManagerData();
+
+ testPostUpdateProcessing();
+}
+
+]]>
+</script>
+
+<body xmlns="http://www.w3.org/1999/xhtml">
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test"></pre>
+</body>
+</window>
diff --git a/toolkit/mozapps/update/tests/chrome/test_0072_notify_verifyFailComplete_noPartial.xul b/toolkit/mozapps/update/tests/chrome/test_0072_notify_verifyFailComplete_noPartial.xul
new file mode 100644
index 000000000..cafab4d27
--- /dev/null
+++ b/toolkit/mozapps/update/tests/chrome/test_0072_notify_verifyFailComplete_noPartial.xul
@@ -0,0 +1,52 @@
+<?xml version="1.0"?>
+<!--
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+-->
+
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?>
+
+<window title="Update Wizard pages: errors (complete patch with an invalid size)"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ onload="runTestDefault();">
+<script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
+<script type="application/javascript"
+ src="utils.js"/>
+
+<script type="application/javascript">
+<![CDATA[
+
+const TESTS = [ {
+ pageid: PAGEID_ERRORS,
+ buttonClick: "finish"
+} ];
+
+function runTest() {
+ debugDump("entering");
+
+ let patches = getLocalPatchString("complete", null, null, null, "1234", null,
+ STATE_DOWNLOADING);
+ let updates = getLocalUpdateString(patches, null, null, null,
+ Services.appinfo.version);
+
+ writeUpdatesToXMLFile(getLocalUpdatesXMLString(updates), true);
+
+ writeUpdatesToXMLFile(getLocalUpdatesXMLString(""), false);
+ writeStatusFile(STATE_DOWNLOADING);
+ reloadUpdateManagerData();
+
+ testPostUpdateProcessing();
+}
+
+]]>
+</script>
+
+<body xmlns="http://www.w3.org/1999/xhtml">
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test"></pre>
+</body>
+</window>
diff --git a/toolkit/mozapps/update/tests/chrome/test_0073_notify_verifyFailPartialComplete.xul b/toolkit/mozapps/update/tests/chrome/test_0073_notify_verifyFailPartialComplete.xul
new file mode 100644
index 000000000..c1db983a3
--- /dev/null
+++ b/toolkit/mozapps/update/tests/chrome/test_0073_notify_verifyFailPartialComplete.xul
@@ -0,0 +1,55 @@
+<?xml version="1.0"?>
+<!--
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+-->
+
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?>
+
+<window title="Update Wizard pages: errors (partial and complete patches with invalid sizes)"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ onload="runTestDefault();">
+<script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
+<script type="application/javascript"
+ src="utils.js"/>
+
+<script type="application/javascript">
+<![CDATA[
+
+const TESTS = [ {
+ pageid: PAGEID_ERRORS,
+ buttonClick: "finish"
+} ];
+
+function runTest() {
+ debugDump("entering");
+
+ let patches = getLocalPatchString("partial", null, null, null, "1234", null,
+ STATE_DOWNLOADING) +
+ getLocalPatchString("complete", null, null, null, "1234",
+ "false");
+ let updates = getLocalUpdateString(patches, null, null, null,
+ Services.appinfo.version, null,
+ null, null, null, null, "false");
+
+ writeUpdatesToXMLFile(getLocalUpdatesXMLString(updates), true);
+
+ writeUpdatesToXMLFile(getLocalUpdatesXMLString(""), false);
+ writeStatusFile(STATE_DOWNLOADING);
+ reloadUpdateManagerData();
+
+ testPostUpdateProcessing();
+}
+
+]]>
+</script>
+
+<body xmlns="http://www.w3.org/1999/xhtml">
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test"></pre>
+</body>
+</window>
diff --git a/toolkit/mozapps/update/tests/chrome/test_0074_notify_verifyFailPartial_successComplete.xul b/toolkit/mozapps/update/tests/chrome/test_0074_notify_verifyFailPartial_successComplete.xul
new file mode 100644
index 000000000..2c28da768
--- /dev/null
+++ b/toolkit/mozapps/update/tests/chrome/test_0074_notify_verifyFailPartial_successComplete.xul
@@ -0,0 +1,55 @@
+<?xml version="1.0"?>
+<!--
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+-->
+
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?>
+
+<window title="Update Wizard pages: finishedBackground (partial patch with an invalid size and successful complete patch)"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ onload="runTestDefault();">
+<script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
+<script type="application/javascript"
+ src="utils.js"/>
+
+<script type="application/javascript">
+<![CDATA[
+
+const TESTS = [ {
+ pageid: PAGEID_FINISHED_BKGRD,
+ buttonClick: "extra1"
+} ];
+
+function runTest() {
+ debugDump("entering");
+
+ let patches = getLocalPatchString("partial", null, null, null, "1234", null,
+ STATE_DOWNLOADING) +
+ getLocalPatchString("complete", null, null, null, null,
+ "false");
+ let updates = getLocalUpdateString(patches, null, null, null,
+ Services.appinfo.version, null,
+ null, null, null, null, "false");
+
+ writeUpdatesToXMLFile(getLocalUpdatesXMLString(updates), true);
+
+ writeUpdatesToXMLFile(getLocalUpdatesXMLString(""), false);
+ writeStatusFile(STATE_DOWNLOADING);
+ reloadUpdateManagerData();
+
+ testPostUpdateProcessing();
+}
+
+]]>
+</script>
+
+<body xmlns="http://www.w3.org/1999/xhtml">
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test"></pre>
+</body>
+</window>
diff --git a/toolkit/mozapps/update/tests/chrome/test_0081_error_patchApplyFailure_partial_only.xul b/toolkit/mozapps/update/tests/chrome/test_0081_error_patchApplyFailure_partial_only.xul
new file mode 100644
index 000000000..10c34f63b
--- /dev/null
+++ b/toolkit/mozapps/update/tests/chrome/test_0081_error_patchApplyFailure_partial_only.xul
@@ -0,0 +1,53 @@
+<?xml version="1.0"?>
+<!--
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+-->
+
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?>
+
+<window title="Update Wizard pages: errors (partial only patch apply failure)"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ onload="runTestDefault();">
+<script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
+<script type="application/javascript"
+ src="utils.js"/>
+
+<script type="application/javascript">
+<![CDATA[
+
+const TESTS = [ {
+ pageid: PAGEID_ERRORS,
+ buttonClick: "finish"
+} ];
+
+function runTest() {
+ debugDump("entering");
+
+ let patches = getLocalPatchString("partial", null, null, null, null, null,
+ STATE_PENDING);
+ let updates = getLocalUpdateString(patches, null, null, null,
+ Services.appinfo.version, null,
+ null, null, null, null, "false");
+
+ writeUpdatesToXMLFile(getLocalUpdatesXMLString(updates), true);
+
+ writeUpdatesToXMLFile(getLocalUpdatesXMLString(""), false);
+ writeStatusFile(STATE_FAILED_CRC_ERROR);
+ reloadUpdateManagerData();
+
+ testPostUpdateProcessing();
+}
+
+]]>
+</script>
+
+<body xmlns="http://www.w3.org/1999/xhtml">
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test"></pre>
+</body>
+</window>
diff --git a/toolkit/mozapps/update/tests/chrome/test_0082_error_patchApplyFailure_complete_only.xul b/toolkit/mozapps/update/tests/chrome/test_0082_error_patchApplyFailure_complete_only.xul
new file mode 100644
index 000000000..2c4b389f4
--- /dev/null
+++ b/toolkit/mozapps/update/tests/chrome/test_0082_error_patchApplyFailure_complete_only.xul
@@ -0,0 +1,52 @@
+<?xml version="1.0"?>
+<!--
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+-->
+
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?>
+
+<window title="Update Wizard pages: errors (complete only patch apply failure)"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ onload="runTestDefault();">
+<script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
+<script type="application/javascript"
+ src="utils.js"/>
+
+<script type="application/javascript">
+<![CDATA[
+
+const TESTS = [ {
+ pageid: PAGEID_ERRORS,
+ buttonClick: "finish"
+} ];
+
+function runTest() {
+ debugDump("entering");
+
+ let patches = getLocalPatchString("complete", null, null, null, null, null,
+ STATE_PENDING);
+ let updates = getLocalUpdateString(patches, null, null, null,
+ Services.appinfo.version);
+
+ writeUpdatesToXMLFile(getLocalUpdatesXMLString(updates), true);
+
+ writeUpdatesToXMLFile(getLocalUpdatesXMLString(""), false);
+ writeStatusFile(STATE_FAILED_CRC_ERROR);
+ reloadUpdateManagerData();
+
+ testPostUpdateProcessing();
+}
+
+]]>
+</script>
+
+<body xmlns="http://www.w3.org/1999/xhtml">
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test"></pre>
+</body>
+</window>
diff --git a/toolkit/mozapps/update/tests/chrome/test_0083_error_patchApplyFailure_partial_complete.xul b/toolkit/mozapps/update/tests/chrome/test_0083_error_patchApplyFailure_partial_complete.xul
new file mode 100644
index 000000000..01adb1c3d
--- /dev/null
+++ b/toolkit/mozapps/update/tests/chrome/test_0083_error_patchApplyFailure_partial_complete.xul
@@ -0,0 +1,67 @@
+<?xml version="1.0"?>
+<!--
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+-->
+
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?>
+
+<window title="Update Wizard pages: error patching, download, and finished (partial failed and download complete)"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ onload="runTestDefault();">
+<script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
+<script type="application/javascript"
+ src="utils.js"/>
+
+<script type="application/javascript">
+<![CDATA[
+
+const TESTS = [ {
+ pageid: PAGEID_ERROR_PATCHING,
+ buttonClick: "next"
+}, {
+ pageid: PAGEID_DOWNLOADING,
+ extraStartFunction: createContinueFile
+}, {
+ pageid: PAGEID_FINISHED,
+ buttonClick: "extra1",
+ extraStartFunction: removeContinueFile
+} ];
+
+function runTest() {
+ debugDump("entering");
+
+ removeContinueFile();
+
+ // Specify the url to update.sjs with a slowDownloadMar param so the ui can
+ // load before the download completes.
+ let slowDownloadURL = URL_HTTP_UPDATE_XML + "?slowDownloadMar=1";
+ let patches = getLocalPatchString("partial", null, null, null, null, null,
+ STATE_PENDING) +
+ getLocalPatchString("complete", slowDownloadURL, null, null,
+ null, "false");
+ let updates = getLocalUpdateString(patches, null, null, null,
+ Services.appinfo.version, null,
+ null, null, null, null, "false");
+
+ writeUpdatesToXMLFile(getLocalUpdatesXMLString(updates), true);
+
+ writeUpdatesToXMLFile(getLocalUpdatesXMLString(""), false);
+ writeStatusFile(STATE_FAILED_CRC_ERROR);
+ reloadUpdateManagerData();
+
+ testPostUpdateProcessing();
+}
+
+]]>
+</script>
+
+<body xmlns="http://www.w3.org/1999/xhtml">
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test"></pre>
+</body>
+</window>
diff --git a/toolkit/mozapps/update/tests/chrome/test_0084_error_patchApplyFailure_verify_failed.xul b/toolkit/mozapps/update/tests/chrome/test_0084_error_patchApplyFailure_verify_failed.xul
new file mode 100644
index 000000000..2e0c2b41e
--- /dev/null
+++ b/toolkit/mozapps/update/tests/chrome/test_0084_error_patchApplyFailure_verify_failed.xul
@@ -0,0 +1,68 @@
+<?xml version="1.0"?>
+<!--
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+-->
+
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?>
+
+<window title="Update Wizard pages: error patching, download, and errors (partial failed and download complete verification failure)"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ onload="runTestDefault();">
+<script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
+<script type="application/javascript"
+ src="utils.js"/>
+
+<script type="application/javascript">
+<![CDATA[
+
+const TESTS = [ {
+ pageid: PAGEID_ERROR_PATCHING,
+ buttonClick: "next"
+}, {
+ pageid: PAGEID_DOWNLOADING,
+ extraStartFunction: createContinueFile
+}, {
+ pageid: PAGEID_ERRORS,
+ buttonClick: "finish",
+ extraStartFunction: removeContinueFile
+} ];
+
+function runTest() {
+ debugDump("entering");
+
+ removeContinueFile();
+
+ // Specify the url to update.sjs with a slowDownloadMar param so the ui can
+ // load before the download completes.
+ let slowDownloadURL = URL_HTTP_UPDATE_XML + "?slowDownloadMar=1";
+ let patches = getLocalPatchString("partial", null, null, null, null, null,
+ STATE_PENDING) +
+ getLocalPatchString("complete", slowDownloadURL, "MD5",
+ null, "1234",
+ "false");
+ let updates = getLocalUpdateString(patches, null, null, null,
+ Services.appinfo.version, null,
+ null, null, null, null, "false");
+
+ writeUpdatesToXMLFile(getLocalUpdatesXMLString(updates), true);
+
+ writeUpdatesToXMLFile(getLocalUpdatesXMLString(""), false);
+ writeStatusFile(STATE_FAILED_CRC_ERROR);
+ reloadUpdateManagerData();
+
+ testPostUpdateProcessing();
+}
+
+]]>
+</script>
+
+<body xmlns="http://www.w3.org/1999/xhtml">
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test"></pre>
+</body>
+</window>
diff --git a/toolkit/mozapps/update/tests/chrome/test_0085_error_patchApplyFailure_partial_complete_staging.xul b/toolkit/mozapps/update/tests/chrome/test_0085_error_patchApplyFailure_partial_complete_staging.xul
new file mode 100644
index 000000000..fc83505f9
--- /dev/null
+++ b/toolkit/mozapps/update/tests/chrome/test_0085_error_patchApplyFailure_partial_complete_staging.xul
@@ -0,0 +1,94 @@
+<?xml version="1.0"?>
+<!--
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+-->
+
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?>
+
+<window title="Update Wizard pages: error patching, download with staging, and finished (partial failed and download complete), with fast MAR download"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ onload="runTestDefault();">
+<script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
+<script type="application/javascript"
+ src="utils.js"/>
+
+<script type="application/javascript">
+<![CDATA[
+
+// This test forces the download to complete before the "next" button on the
+// errorpatching wizard page is clicked. This is done by creating the continue
+// file when the wizard loads to start the download, then clicking the "next"
+// button in the download's onStopRequest event listener.
+
+const testDownloadListener = {
+ onStartRequest(aRequest, aContext) { },
+
+ onProgress(aRequest, aContext, aProgress, aMaxProgress) { },
+
+ onStatus(aRequest, aContext, aStatus, aStatusText) { },
+
+ onStopRequest(aRequest, aContext, aStatus) {
+ debugDump("clicking errorpatching page next button");
+ gDocElem.getButton("next").click();
+ gAUS.removeDownloadListener(this);
+ },
+
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIRequestObserver,
+ Ci.nsIProgressEventSink])
+};
+
+let TESTS = [ {
+ pageid: PAGEID_ERROR_PATCHING,
+ extraCheckFunction: createContinueFile
+}, {
+ pageid: PAGEID_DOWNLOADING
+}, {
+ pageid: PAGEID_FINISHED,
+ buttonClick: "extra1",
+ extraStartFunction: removeContinueFile
+} ];
+
+gUseTestUpdater = true;
+
+function runTest() {
+ debugDump("entering");
+
+ removeContinueFile();
+
+ // Specify the url to update.sjs with a slowDownloadMar param so the ui can
+ // load before the download completes.
+ let slowDownloadURL = URL_HTTP_UPDATE_XML + "?slowDownloadMar=1";
+ let patches = getLocalPatchString("partial", null, null, null, null, null,
+ STATE_PENDING) +
+ getLocalPatchString("complete", slowDownloadURL, null, null,
+ null, "false");
+ let updates = getLocalUpdateString(patches, null, null, null,
+ Services.appinfo.version, null,
+ null, null, null, null, "false");
+
+ writeUpdatesToXMLFile(getLocalUpdatesXMLString(updates), true);
+ writeUpdatesToXMLFile(getLocalUpdatesXMLString(""), false);
+ writeStatusFile(STATE_FAILED_READ_ERROR);
+
+ Services.prefs.setBoolPref(PREF_APP_UPDATE_STAGING_ENABLED, true);
+
+ reloadUpdateManagerData();
+
+ testPostUpdateProcessing();
+
+ gAUS.addDownloadListener(testDownloadListener);
+}
+
+]]>
+</script>
+
+<body xmlns="http://www.w3.org/1999/xhtml">
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test"></pre>
+</body>
+</window>
diff --git a/toolkit/mozapps/update/tests/chrome/test_0092_finishedBackground.xul b/toolkit/mozapps/update/tests/chrome/test_0092_finishedBackground.xul
new file mode 100644
index 000000000..a0b29ddea
--- /dev/null
+++ b/toolkit/mozapps/update/tests/chrome/test_0092_finishedBackground.xul
@@ -0,0 +1,55 @@
+<?xml version="1.0"?>
+<!--
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+-->
+
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?>
+
+<window title="Update Wizard pages: finished background"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ onload="runTestDefault();">
+<script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
+<script type="application/javascript"
+ src="utils.js"/>
+
+<script type="application/javascript">
+<![CDATA[
+
+const TESTS = [ {
+ pageid: PAGEID_FINISHED_BKGRD,
+ buttonClick: "extra1"
+} ];
+
+function runTest() {
+ debugDump("entering");
+
+ let patches = getLocalPatchString("complete", null, null, null, null, null,
+ STATE_PENDING);
+ let updates = getLocalUpdateString(patches, null, null, null,
+ Services.appinfo.version,
+ Services.appinfo.platformVersion);
+ writeUpdatesToXMLFile(getLocalUpdatesXMLString(updates), true);
+ writeUpdatesToXMLFile(getLocalUpdatesXMLString(""), false);
+ writeStatusFile(STATE_SUCCEEDED);
+
+ reloadUpdateManagerData();
+
+ is(gUpdateManager.activeUpdate.state, "pending",
+ "The active update should have a state of pending");
+
+ gUP.showUpdateDownloaded(gUpdateManager.activeUpdate);
+}
+
+]]>
+</script>
+
+<body xmlns="http://www.w3.org/1999/xhtml">
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test"></pre>
+</body>
+</window>
diff --git a/toolkit/mozapps/update/tests/chrome/test_0093_restartNotification.xul b/toolkit/mozapps/update/tests/chrome/test_0093_restartNotification.xul
new file mode 100644
index 000000000..6db5b9897
--- /dev/null
+++ b/toolkit/mozapps/update/tests/chrome/test_0093_restartNotification.xul
@@ -0,0 +1,60 @@
+<?xml version="1.0"?>
+<!--
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+-->
+
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?>
+
+<window title="Update Wizard pages: restart notification pref promptWaitTime"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ onload="runTestDefault();">
+<script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
+<script type="application/javascript"
+ src="utils.js"/>
+
+<script type="application/javascript">
+<![CDATA[
+
+const TESTS = [ {
+ pageid: PAGEID_FINISHED_BKGRD,
+ buttonClick: "extra1"
+} ];
+
+function runTest() {
+ debugDump("entering");
+
+ let patches = getLocalPatchString("complete", null, null, null, null, null,
+ STATE_PENDING);
+ let updates = getLocalUpdateString(patches, null, null, null,
+ Services.appinfo.version);
+ writeUpdatesToXMLFile(getLocalUpdatesXMLString(updates), true);
+ writeUpdatesToXMLFile(getLocalUpdatesXMLString(""), false);
+ writeStatusFile(STATE_SUCCEEDED);
+
+ Services.prefs.setIntPref(PREF_APP_UPDATE_PROMPTWAITTIME, 1);
+
+ reloadUpdateManagerData();
+
+ is(gUpdateManager.activeUpdate.state, STATE_PENDING,
+ "The active update should have a state of " + STATE_PENDING);
+
+ ok(gUpdateManager.activeUpdate.promptWaitTime == 1, "Checking that the " +
+ "update's promptWaitTime attribute value was set from the " +
+ PREF_APP_UPDATE_PROMPTWAITTIME + " preference");
+
+ gUP.showUpdateDownloaded(gUpdateManager.activeUpdate, true);
+}
+
+]]>
+</script>
+
+<body xmlns="http://www.w3.org/1999/xhtml">
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test"></pre>
+</body>
+</window>
diff --git a/toolkit/mozapps/update/tests/chrome/test_0094_restartNotification_remote.xul b/toolkit/mozapps/update/tests/chrome/test_0094_restartNotification_remote.xul
new file mode 100644
index 000000000..6e72a42c1
--- /dev/null
+++ b/toolkit/mozapps/update/tests/chrome/test_0094_restartNotification_remote.xul
@@ -0,0 +1,60 @@
+<?xml version="1.0"?>
+<!--
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+-->
+
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?>
+
+<window title="Update Wizard pages: restart notification xml promptWaitTime"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ onload="runTestDefault();">
+<script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
+<script type="application/javascript"
+ src="utils.js"/>
+
+<script type="application/javascript">
+<![CDATA[
+
+const TESTS = [ {
+ pageid: PAGEID_FINISHED_BKGRD,
+ buttonClick: "extra1"
+} ];
+
+function runTest() {
+ debugDump("entering");
+
+ let patches = getLocalPatchString("complete", null, null, null, null, null,
+ STATE_PENDING);
+ let updates = getLocalUpdateString(patches, null, null, null,
+ Services.appinfo.version, null,
+ null, null, null, null, false,
+ null, false, false, false, 1);
+ writeUpdatesToXMLFile(getLocalUpdatesXMLString(updates), true);
+ writeUpdatesToXMLFile(getLocalUpdatesXMLString(""), false);
+ writeStatusFile(STATE_SUCCEEDED);
+
+ reloadUpdateManagerData();
+
+ is(gUpdateManager.activeUpdate.state, STATE_PENDING,
+ "The active update should have a state of " + STATE_PENDING);
+
+ ok(gUpdateManager.activeUpdate.promptWaitTime == 1, "Checking that the " +
+ "update's promptWaitTime attribute value was set by the XML");
+
+ gUP.showUpdateDownloaded(gUpdateManager.activeUpdate, true);
+
+}
+
+]]>
+</script>
+
+<body xmlns="http://www.w3.org/1999/xhtml">
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test"></pre>
+</body>
+</window>
diff --git a/toolkit/mozapps/update/tests/chrome/test_0095_restartNotification_remoteInvalidNumber.xul b/toolkit/mozapps/update/tests/chrome/test_0095_restartNotification_remoteInvalidNumber.xul
new file mode 100644
index 000000000..5b1b826a5
--- /dev/null
+++ b/toolkit/mozapps/update/tests/chrome/test_0095_restartNotification_remoteInvalidNumber.xul
@@ -0,0 +1,66 @@
+<?xml version="1.0"?>
+<!--
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+-->
+
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?>
+
+<window title="Update Wizard pages: restart notification xml promptWaitTime with invalid number"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ onload="runTestDefault();">
+<script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
+<script type="application/javascript"
+ src="utils.js"/>
+
+<script type="application/javascript">
+<![CDATA[
+
+const TESTS = [ {
+ pageid: PAGEID_FINISHED_BKGRD,
+ buttonClick: "extra1"
+} ];
+
+function runTest() {
+ debugDump("entering");
+
+ let patches = getLocalPatchString("complete", null, null, null, null, null,
+ STATE_PENDING);
+ let updates = getLocalUpdateString(patches, null, null, null,
+ Services.appinfo.version,
+ null, null,
+ null, null, null,
+ null, false, null,
+ false, false,
+ false, "invalidNumber");
+ writeUpdatesToXMLFile(getLocalUpdatesXMLString(updates), true);
+ writeUpdatesToXMLFile(getLocalUpdatesXMLString(""), false);
+ writeStatusFile(STATE_SUCCEEDED);
+
+ Services.prefs.setIntPref(PREF_APP_UPDATE_PROMPTWAITTIME, 1);
+
+ reloadUpdateManagerData();
+
+ is(gUpdateManager.activeUpdate.state, STATE_PENDING,
+ "The active update should have a state of " + STATE_PENDING);
+
+ ok(gUpdateManager.activeUpdate.promptWaitTime == 1, "Checking that the " +
+ "update's promptWaitTime attribute value was set from the " +
+ PREF_APP_UPDATE_PROMPTWAITTIME + " preference");
+
+ gUP.showUpdateDownloaded(gUpdateManager.activeUpdate, true);
+
+}
+
+]]>
+</script>
+
+<body xmlns="http://www.w3.org/1999/xhtml">
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test"></pre>
+</body>
+</window>
diff --git a/toolkit/mozapps/update/tests/chrome/test_0096_restartNotification_stagedBackground.xul b/toolkit/mozapps/update/tests/chrome/test_0096_restartNotification_stagedBackground.xul
new file mode 100644
index 000000000..b86861012
--- /dev/null
+++ b/toolkit/mozapps/update/tests/chrome/test_0096_restartNotification_stagedBackground.xul
@@ -0,0 +1,65 @@
+<?xml version="1.0"?>
+<!--
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+-->
+
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?>
+
+<window title="Update Wizard pages: restart notification staged w/o service"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ onload="runTestDefault();">
+<script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
+<script type="application/javascript"
+ src="utils.js"/>
+
+<script type="application/javascript">
+<![CDATA[
+
+const TESTS = [ {
+ pageid: PAGEID_FINISHED_BKGRD,
+ buttonClick: "extra1"
+} ];
+
+gUseTestUpdater = true;
+
+function runTest() {
+ debugDump("entering");
+
+ let patches = getLocalPatchString("complete", null, null, null, null, null,
+ STATE_APPLIED);
+ let updates = getLocalUpdateString(patches, null, null, null,
+ Services.appinfo.version,
+ Services.appinfo.platformVersion);
+ writeUpdatesToXMLFile(getLocalUpdatesXMLString(updates), true);
+ writeUpdatesToXMLFile(getLocalUpdatesXMLString(""), false);
+ writeStatusFile(STATE_APPLIED);
+
+ Services.prefs.setIntPref(PREF_APP_UPDATE_PROMPTWAITTIME, 1);
+ Services.prefs.setBoolPref(PREF_APP_UPDATE_STAGING_ENABLED, true);
+ Services.prefs.setBoolPref(PREF_APP_UPDATE_SERVICE_ENABLED, false);
+
+ reloadUpdateManagerData();
+
+ is(gUpdateManager.activeUpdate.state, STATE_APPLIED,
+ "The active update should have a state of " + STATE_APPLIED);
+
+ ok(gUpdateManager.activeUpdate.promptWaitTime == 1, "Checking that the " +
+ "update's promptWaitTime attribute value was set from the " +
+ PREF_APP_UPDATE_PROMPTWAITTIME + " preference");
+
+ gUpdateManager.refreshUpdateStatus();
+}
+
+]]>
+</script>
+
+<body xmlns="http://www.w3.org/1999/xhtml">
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test"></pre>
+</body>
+</window>
diff --git a/toolkit/mozapps/update/tests/chrome/test_0097_restartNotification_stagedServiceBackground.xul b/toolkit/mozapps/update/tests/chrome/test_0097_restartNotification_stagedServiceBackground.xul
new file mode 100644
index 000000000..9f7a602c4
--- /dev/null
+++ b/toolkit/mozapps/update/tests/chrome/test_0097_restartNotification_stagedServiceBackground.xul
@@ -0,0 +1,65 @@
+<?xml version="1.0"?>
+<!--
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+-->
+
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?>
+
+<window title="Update Wizard pages: restart notification staged with service"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ onload="runTestDefault();">
+<script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
+<script type="application/javascript"
+ src="utils.js"/>
+
+<script type="application/javascript">
+<![CDATA[
+
+const TESTS = [ {
+ pageid: PAGEID_FINISHED_BKGRD,
+ buttonClick: "extra1"
+} ];
+
+gUseTestUpdater = true;
+
+function runTest() {
+ debugDump("entering");
+
+ let patches = getLocalPatchString("complete", null, null, null, null, null,
+ STATE_APPLIED_SVC);
+ let updates = getLocalUpdateString(patches, null, null, null,
+ Services.appinfo.version,
+ Services.appinfo.platformVersion);
+ writeUpdatesToXMLFile(getLocalUpdatesXMLString(updates), true);
+ writeUpdatesToXMLFile(getLocalUpdatesXMLString(""), false);
+ writeStatusFile(STATE_APPLIED_SVC);
+
+ Services.prefs.setIntPref(PREF_APP_UPDATE_PROMPTWAITTIME, 1);
+ Services.prefs.setBoolPref(PREF_APP_UPDATE_STAGING_ENABLED, true);
+ Services.prefs.setBoolPref(PREF_APP_UPDATE_SERVICE_ENABLED, true);
+
+ reloadUpdateManagerData();
+
+ is(gUpdateManager.activeUpdate.state, STATE_APPLIED_SVC,
+ "The active update should have a state of " + STATE_APPLIED_SVC);
+
+ ok(gUpdateManager.activeUpdate.promptWaitTime == 1, "Checking that the " +
+ "update's promptWaitTime attribute value was set from the " +
+ PREF_APP_UPDATE_PROMPTWAITTIME + " preference");
+
+ gUpdateManager.refreshUpdateStatus();
+}
+
+]]>
+</script>
+
+<body xmlns="http://www.w3.org/1999/xhtml">
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test"></pre>
+</body>
+</window>
diff --git a/toolkit/mozapps/update/tests/chrome/test_0101_background_restartNotification.xul b/toolkit/mozapps/update/tests/chrome/test_0101_background_restartNotification.xul
new file mode 100644
index 000000000..faa60c08b
--- /dev/null
+++ b/toolkit/mozapps/update/tests/chrome/test_0101_background_restartNotification.xul
@@ -0,0 +1,46 @@
+<?xml version="1.0"?>
+<!--
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+-->
+
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?>
+
+<window title="Update Wizard pages: background finish with a background download"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ onload="runTestDefault();">
+<script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
+<script type="application/javascript"
+ src="utils.js"/>
+
+<script type="application/javascript">
+<![CDATA[
+
+const TESTS = [ {
+ pageid: PAGEID_FINISHED_BKGRD,
+ buttonClick: "extra1"
+} ];
+
+function runTest() {
+ debugDump("entering");
+
+ Services.prefs.setIntPref(PREF_APP_UPDATE_PROMPTWAITTIME, 1);
+
+ let url = URL_HTTP_UPDATE_XML + "?showDetails=1" + getVersionParams();
+ setUpdateURL(url);
+
+ gAUS.notify(null);
+}
+
+]]>
+</script>
+
+<body xmlns="http://www.w3.org/1999/xhtml">
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test"></pre>
+</body>
+</window>
diff --git a/toolkit/mozapps/update/tests/chrome/test_0102_background_restartNotification_staging.xul b/toolkit/mozapps/update/tests/chrome/test_0102_background_restartNotification_staging.xul
new file mode 100644
index 000000000..3e6f0fec8
--- /dev/null
+++ b/toolkit/mozapps/update/tests/chrome/test_0102_background_restartNotification_staging.xul
@@ -0,0 +1,49 @@
+<?xml version="1.0"?>
+<!--
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+-->
+
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?>
+
+<window title="Update Wizard pages: background finish with a background download and update staging"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ onload="runTestDefault();">
+<script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
+<script type="application/javascript"
+ src="utils.js"/>
+
+<script type="application/javascript">
+<![CDATA[
+
+const TESTS = [ {
+ pageid: PAGEID_FINISHED_BKGRD,
+ buttonClick: "extra1"
+} ];
+
+gUseTestUpdater = true;
+
+function runTest() {
+ debugDump("entering");
+
+ Services.prefs.setBoolPref(PREF_APP_UPDATE_STAGING_ENABLED, true);
+ Services.prefs.setIntPref(PREF_APP_UPDATE_PROMPTWAITTIME, 1);
+
+ let url = URL_HTTP_UPDATE_XML + "?showDetails=1" + getVersionParams();
+ setUpdateURL(url);
+
+ gAUS.notify(null);
+}
+
+]]>
+</script>
+
+<body xmlns="http://www.w3.org/1999/xhtml">
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test"></pre>
+</body>
+</window>
diff --git a/toolkit/mozapps/update/tests/chrome/test_0103_background_restartNotification_stagingService.xul b/toolkit/mozapps/update/tests/chrome/test_0103_background_restartNotification_stagingService.xul
new file mode 100644
index 000000000..c60a9fe49
--- /dev/null
+++ b/toolkit/mozapps/update/tests/chrome/test_0103_background_restartNotification_stagingService.xul
@@ -0,0 +1,50 @@
+<?xml version="1.0"?>
+<!--
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+-->
+
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?>
+
+<window title="Update Wizard pages: background finish with a background download and update staging and servicefs"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ onload="runTestDefault();">
+<script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
+<script type="application/javascript"
+ src="utils.js"/>
+
+<script type="application/javascript">
+<![CDATA[
+
+const TESTS = [ {
+ pageid: PAGEID_FINISHED_BKGRD,
+ buttonClick: "extra1"
+} ];
+
+gUseTestUpdater = true;
+
+function runTest() {
+ debugDump("entering");
+
+ Services.prefs.setBoolPref(PREF_APP_UPDATE_STAGING_ENABLED, true);
+ Services.prefs.setBoolPref(PREF_APP_UPDATE_SERVICE_ENABLED, true);
+ Services.prefs.setIntPref(PREF_APP_UPDATE_PROMPTWAITTIME, 1);
+
+ let url = URL_HTTP_UPDATE_XML + "?showDetails=1" + getVersionParams();
+ setUpdateURL(url);
+
+ gAUS.notify(null);
+}
+
+]]>
+</script>
+
+<body xmlns="http://www.w3.org/1999/xhtml">
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test"></pre>
+</body>
+</window>
diff --git a/toolkit/mozapps/update/tests/chrome/test_0111_neverButton_basic.xul b/toolkit/mozapps/update/tests/chrome/test_0111_neverButton_basic.xul
new file mode 100644
index 000000000..adca621d9
--- /dev/null
+++ b/toolkit/mozapps/update/tests/chrome/test_0111_neverButton_basic.xul
@@ -0,0 +1,61 @@
+<?xml version="1.0"?>
+<!--
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+-->
+
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?>
+
+<window title="Update Wizard pages: update check and basic (never button test)"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ onload="runTestDefault();">
+<script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
+<script type="application/javascript"
+ src="utils.js"/>
+
+<script type="application/javascript">
+<![CDATA[
+
+gPrefToCheck = PREFBRANCH_APP_UPDATE_NEVER + Services.appinfo.version;
+
+const TESTS = [ {
+ pageid: PAGEID_CHECKING
+}, {
+ pageid: PAGEID_FOUND_BASIC,
+ extraDelayedCheckFunction: checkPrefHasUserValue,
+ prefHasUserValue: false,
+ neverButton: true,
+ buttonClick: "extra2"
+} ];
+
+function runTest() {
+ debugDump("entering");
+
+ let url = URL_HTTP_UPDATE_XML + "?showNever=1&showDetails=1" +
+ getVersionParams();
+ setUpdateURL(url);
+
+ // add the never preference for this version to verify that checking for
+ // updates clears the preference.
+ Services.prefs.setBoolPref(gPrefToCheck, true)
+
+ gUP.checkForUpdates();
+}
+
+function finishTest() {
+ checkPrefHasUserValue(true);
+ finishTestDefault();
+}
+
+]]>
+</script>
+
+<body xmlns="http://www.w3.org/1999/xhtml">
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test"></pre>
+</body>
+</window>
diff --git a/toolkit/mozapps/update/tests/chrome/test_0113_showNeverForVersionRemovedWithPref.xul b/toolkit/mozapps/update/tests/chrome/test_0113_showNeverForVersionRemovedWithPref.xul
new file mode 100644
index 000000000..89dd55ea1
--- /dev/null
+++ b/toolkit/mozapps/update/tests/chrome/test_0113_showNeverForVersionRemovedWithPref.xul
@@ -0,0 +1,58 @@
+<?xml version="1.0"?>
+<!--
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+-->
+
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?>
+
+<window title="Update Wizard pages: update available with never pref and without showNeverForVersion"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ onload="runTestDefault();">
+<script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
+<script type="application/javascript"
+ src="utils.js"/>
+
+<script type="application/javascript">
+<![CDATA[
+
+gPrefToCheck = PREFBRANCH_APP_UPDATE_NEVER + Services.appinfo.version;
+
+const TESTS = [ {
+ pageid: PAGEID_FOUND_BASIC,
+ extraDelayedCheckFunction: checkPrefHasUserValue,
+ prefHasUserValue: true,
+ buttonClick: "extra1"
+} ];
+
+function runTest() {
+ debugDump("entering");
+
+ let url = URL_HTTP_UPDATE_XML + "?showDetails=1&showPrompt=1" +
+ getVersionParams();
+ setUpdateURL(url);
+
+ // add the never preference for this version to verify that checking for
+ // updates clears the preference.
+ Services.prefs.setBoolPref(gPrefToCheck, true)
+
+ gAUS.notify(null);
+}
+
+function finishTest() {
+ Services.prefs.clearUserPref(gPrefToCheck)
+ finishTestDefault();
+}
+
+]]>
+</script>
+
+<body xmlns="http://www.w3.org/1999/xhtml">
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test"></pre>
+</body>
+</window>
diff --git a/toolkit/mozapps/update/tests/chrome/test_0151_notify_backgroundCheckError.xul b/toolkit/mozapps/update/tests/chrome/test_0151_notify_backgroundCheckError.xul
new file mode 100644
index 000000000..13798e5c9
--- /dev/null
+++ b/toolkit/mozapps/update/tests/chrome/test_0151_notify_backgroundCheckError.xul
@@ -0,0 +1,50 @@
+<?xml version="1.0"?>
+<!--
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+-->
+
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?>
+
+<window title="Test notification when multiple background check errors occur (bug 595455)"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ onload="runTestDefault();">
+<script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
+<script type="application/javascript"
+ src="utils.js"/>
+
+<script type="application/javascript">
+<![CDATA[
+
+const TESTS = [ {
+ pageid: PAGEID_ERROR_EXTRA,
+ extraDelayedCheckFunction: checkErrorExtraPage,
+ shouldBeHidden: false,
+ displayedTextElem: "bgErrorLabel",
+ buttonClick: "finish"
+} ];
+
+function runTest() {
+ debugDump("entering");
+
+ let url = URL_HTTP_UPDATE_XML + "?xmlMalformed=1";
+ setUpdateURL(url);
+
+ errorsPrefObserver.init(PREF_APP_UPDATE_BACKGROUNDERRORS,
+ PREF_APP_UPDATE_BACKGROUNDMAXERRORS);
+
+ gAUS.notify(null);
+}
+
+]]>
+</script>
+
+<body xmlns="http://www.w3.org/1999/xhtml">
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test"></pre>
+</body>
+</window>
diff --git a/toolkit/mozapps/update/tests/chrome/test_0152_notify_backgroundCheckOfflineRetry.xul b/toolkit/mozapps/update/tests/chrome/test_0152_notify_backgroundCheckOfflineRetry.xul
new file mode 100644
index 000000000..04e613418
--- /dev/null
+++ b/toolkit/mozapps/update/tests/chrome/test_0152_notify_backgroundCheckOfflineRetry.xul
@@ -0,0 +1,96 @@
+<?xml version="1.0"?>
+<!--
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+-->
+
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?>
+
+<window title="Test that an update check that fails due to being offline is performed after going online"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ onload="runTestDefault();">
+<script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
+<script type="application/javascript"
+ src="utils.js"/>
+
+<script type="application/javascript">
+<![CDATA[
+
+const TESTS = [ {
+ pageid: PAGEID_FOUND_BASIC,
+ buttonClick: "extra1"
+} ];
+
+const NETWORK_ERROR_OFFLINE = 111;
+var gProxyPrefValue;
+
+function runTest() {
+ debugDump("entering");
+
+ let url = URL_HTTP_UPDATE_XML + "?showDetails=1" + getVersionParams();
+ setUpdateURL(url);
+
+ Services.prefs.setBoolPref(PREF_APP_UPDATE_ENABLED, true);
+ Services.prefs.setBoolPref(PREF_APP_UPDATE_AUTO, false);
+
+ Services.io.offline = true;
+ gProxyPrefValue = Services.prefs.getIntPref("network.proxy.type");
+ Services.prefs.setIntPref("network.proxy.type", 0);
+
+ gUpdateChecker.checkForUpdates(updateCheckListener, true);
+}
+
+function resetOffline() {
+ Services.prefs.setIntPref("network.proxy.type", gProxyPrefValue);
+ Services.io.offline = false;
+}
+
+/* Update check listener */
+const updateCheckListener = {
+ onProgress: function UCL_onProgress(aRequest, aPosition, aTotalSize) {
+ },
+
+ onCheckComplete: function UCL_onCheckComplete(aRequest, aUpdates, aUpdateCount) {
+ let status = aRequest.status;
+ if (status == 0) {
+ status = aRequest.channel.QueryInterface(Ci.nsIRequest).status;
+ }
+ debugDump("url = " + aRequest.channel.originalURI.spec + ", " +
+ "request.status = " + status + ", " +
+ "updateCount = " + aUpdateCount);
+ ok(false, "Unexpected updateCheckListener::onCheckComplete called");
+ },
+
+ onError: function UCL_onError(aRequest, aUpdate) {
+ let status = aRequest.status;
+ if (status == 0) {
+ status = aRequest.channel.QueryInterface(Ci.nsIRequest).status;
+ }
+ is(status, Cr.NS_ERROR_OFFLINE,
+ "checking the request status value");
+ is(aUpdate.errorCode, NETWORK_ERROR_OFFLINE,
+ "checking the update error code");
+ debugDump("url = " + aRequest.channel.originalURI.spec + ", " +
+ "request.status = " + status + ", " +
+ "update.statusText = " +
+ (aUpdate.statusText ? aUpdate.statusText : "null"));
+ gAUS.onError(aRequest, aUpdate);
+ // Use a timeout to allow the XHR to complete
+ SimpleTest.executeSoon(resetOffline);
+ },
+
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIUpdateCheckListener])
+};
+
+]]>
+</script>
+
+<body xmlns="http://www.w3.org/1999/xhtml">
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test"></pre>
+</body>
+</window>
diff --git a/toolkit/mozapps/update/tests/chrome/test_0161_check_unsupported.xul b/toolkit/mozapps/update/tests/chrome/test_0161_check_unsupported.xul
new file mode 100644
index 000000000..c8e8d837b
--- /dev/null
+++ b/toolkit/mozapps/update/tests/chrome/test_0161_check_unsupported.xul
@@ -0,0 +1,50 @@
+<?xml version="1.0"?>
+<!--
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+-->
+
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?>
+
+<window title="Test checking for updates when system is no longer supported (bug 843497)"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ onload="runTestDefault();">
+<script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
+<script type="application/javascript"
+ src="utils.js"/>
+
+<script type="application/javascript">
+<![CDATA[
+
+const TESTS = [ {
+ pageid: PAGEID_CHECKING
+}, {
+ pageid: PAGEID_UNSUPPORTED,
+ buttonClick: "finish"
+} ];
+
+function runTest() {
+ debugDump("entering");
+
+ // When checking manually the unsupported page should still be shown even if
+ // it was shown previously.
+ Services.prefs.setBoolPref(PREF_APP_UPDATE_NOTIFIEDUNSUPPORTED, true);
+
+ let url = URL_HTTP_UPDATE_XML + "?unsupported=1";
+ setUpdateURL(url);
+
+ gUP.checkForUpdates();
+}
+
+]]>
+</script>
+
+<body xmlns="http://www.w3.org/1999/xhtml">
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test"></pre>
+</body>
+</window>
diff --git a/toolkit/mozapps/update/tests/chrome/test_0162_notify_unsupported.xul b/toolkit/mozapps/update/tests/chrome/test_0162_notify_unsupported.xul
new file mode 100644
index 000000000..d88d2092d
--- /dev/null
+++ b/toolkit/mozapps/update/tests/chrome/test_0162_notify_unsupported.xul
@@ -0,0 +1,44 @@
+<?xml version="1.0"?>
+<!--
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+-->
+
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?>
+
+<window title="Test notification of updates when system is no longer supported (bug 843497)"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ onload="runTestDefault();">
+<script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
+<script type="application/javascript"
+ src="utils.js"/>
+
+<script type="application/javascript">
+<![CDATA[
+
+const TESTS = [ {
+ pageid: PAGEID_UNSUPPORTED,
+ buttonClick: "finish"
+} ];
+
+function runTest() {
+ debugDump("entering");
+
+ let url = URL_HTTP_UPDATE_XML + "?unsupported=1";
+ setUpdateURL(url);
+
+ gAUS.notify(null);
+}
+
+]]>
+</script>
+
+<body xmlns="http://www.w3.org/1999/xhtml">
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test"></pre>
+</body>
+</window>
diff --git a/toolkit/mozapps/update/tests/chrome/test_0171_check_noPerms_manual.xul b/toolkit/mozapps/update/tests/chrome/test_0171_check_noPerms_manual.xul
new file mode 100644
index 000000000..142c02baa
--- /dev/null
+++ b/toolkit/mozapps/update/tests/chrome/test_0171_check_noPerms_manual.xul
@@ -0,0 +1,64 @@
+<?xml version="1.0"?>
+<!--
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+-->
+
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?>
+
+<window title="Update Wizard pages: manual"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ onload="runTestDefault();">
+<script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
+<script type="application/javascript"
+ src="utils.js"/>
+
+<script type="application/javascript">
+<![CDATA[
+
+const TESTS = [ {
+ pageid: PAGEID_CHECKING
+}, {
+ pageid: PAGEID_MANUAL_UPDATE,
+ buttonClick: "finish",
+ extraCheckFunction: getWriteTestFile
+} ];
+
+function runTest() {
+ debugDump("entering");
+
+ let file = getWriteTestFile();
+ file.create(file.NORMAL_FILE_TYPE, 0o444);
+ file.fileAttributesWin |= file.WFA_READONLY;
+ file.fileAttributesWin &= ~file.WFA_READWRITE;
+
+ let url = URL_HTTP_UPDATE_XML + "?showDetails=1" + getVersionParams();
+ setUpdateURL(url);
+
+ gUP.checkForUpdates();
+}
+
+function getWriteTestFile() {
+ let file = getAppBaseDir();
+ file.append(FILE_UPDATE_TEST);
+ file.QueryInterface(Ci.nsILocalFileWin);
+ if (file.exists()) {
+ file.fileAttributesWin |= file.WFA_READWRITE;
+ file.fileAttributesWin &= ~file.WFA_READONLY;
+ file.remove(true);
+ }
+ return file;
+}
+
+]]>
+</script>
+
+<body xmlns="http://www.w3.org/1999/xhtml">
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test"></pre>
+</body>
+</window>
diff --git a/toolkit/mozapps/update/tests/chrome/test_0172_notify_noPerms_manual.xul b/toolkit/mozapps/update/tests/chrome/test_0172_notify_noPerms_manual.xul
new file mode 100644
index 000000000..6784b9c90
--- /dev/null
+++ b/toolkit/mozapps/update/tests/chrome/test_0172_notify_noPerms_manual.xul
@@ -0,0 +1,63 @@
+<?xml version="1.0"?>
+<!--
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+-->
+
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?>
+
+<window title="Update Wizard pages: manual"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ onload="runTestDefault();">
+<script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
+<script type="application/javascript"
+ src="utils.js"/>
+
+<script type="application/javascript">
+<![CDATA[
+
+const TESTS = [ {
+ pageid: PAGEID_MANUAL_UPDATE,
+ buttonClick: "finish",
+ extraCheckFunction: getWriteTestFile
+} ];
+
+function runTest() {
+ debugDump("entering");
+
+ let file = getWriteTestFile();
+ file.create(file.NORMAL_FILE_TYPE, 0o444);
+ file.fileAttributesWin |= file.WFA_READONLY;
+ file.fileAttributesWin &= ~file.WFA_READWRITE;
+
+ let url = URL_HTTP_UPDATE_XML + "?showDetails=1&showPrompt=1" +
+ getVersionParams();
+ setUpdateURL(url);
+
+ gAUS.checkForBackgroundUpdates();
+}
+
+function getWriteTestFile() {
+ let file = getAppBaseDir();
+ file.append(FILE_UPDATE_TEST);
+ file.QueryInterface(Ci.nsILocalFileWin);
+ if (file.exists()) {
+ file.fileAttributesWin |= file.WFA_READWRITE;
+ file.fileAttributesWin &= ~file.WFA_READONLY;
+ file.remove(true);
+ }
+ return file;
+}
+
+]]>
+</script>
+
+<body xmlns="http://www.w3.org/1999/xhtml">
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test"></pre>
+</body>
+</window>
diff --git a/toolkit/mozapps/update/tests/chrome/test_9999_cleanup.xul b/toolkit/mozapps/update/tests/chrome/test_9999_cleanup.xul
new file mode 100644
index 000000000..a55263eb2
--- /dev/null
+++ b/toolkit/mozapps/update/tests/chrome/test_9999_cleanup.xul
@@ -0,0 +1,112 @@
+<?xml version="1.0"?>
+<!--
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+/**
+ * Removes files and preferences for previous application update tests in case
+ * any of them had a fatal error. The test name ensures that it will run after
+ * all other tests as long as the test naming uses the same format as the
+ * existing tests.
+ */
+-->
+
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?>
+
+<window title="Application Update test cleanup"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ onload="runTest();">
+<script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
+<script type="application/javascript"
+ src="utils.js"/>
+
+<script type="application/javascript">
+<![CDATA[
+
+/**
+ * If the application update tests left behind any of the files it uses it could
+ * be a very bad thing. The purpose of this test is to prevent that from
+ * happening.
+ */
+function runTest() {
+ debugDump("entering");
+
+ SimpleTest.waitForExplicitFinish();
+
+ if (DEBUG_AUS_TEST) {
+ Services.prefs.setBoolPref(PREF_APP_UPDATE_LOG, true);
+ }
+
+ closeUpdateWindow();
+
+ // Always leave the app.update.enabled and app.update.staging.enabled
+ // preferences set to false when cleaning up.
+ Services.prefs.setBoolPref(PREF_APP_UPDATE_ENABLED, false);
+ Services.prefs.setBoolPref(PREF_APP_UPDATE_STAGING_ENABLED, false);
+
+ resetFiles();
+ removeUpdateDirsAndFiles();
+ reloadUpdateManagerData();
+
+ let file = getUpdatesXMLFile(true);
+ ok(!file.exists(), file.path + " should not exist");
+
+ file = getUpdatesXMLFile(false);
+ ok(!file.exists(), file.path + " should not exist");
+
+ let dir = getUpdatesDir();
+
+ file = dir.clone();
+ file.append(FILE_UPDATE_STATUS);
+ ok(!file.exists(), file.path + " should not exist");
+
+ file = dir.clone();
+ file.append(FILE_UPDATE_MAR);
+ ok(!file.exists(), file.path + " should not exist");
+
+ cleanupRestoreUpdaterBackup();
+}
+
+/**
+ * After all tests finish this will repeatedly attempt to restore the real
+ * updater if it exists and then call finishTest after the restore is
+ * successful.
+ */
+function cleanupRestoreUpdaterBackup() {
+ debugDump("entering");
+
+ try {
+ // Windows debug builds keep the updater file in use for a short period of
+ // time after the updater process exits.
+ restoreUpdaterBackup();
+ } catch (e) {
+ logTestInfo("Attempt to restore the backed up updater failed... " +
+ "will try again, Exception: " + e);
+ SimpleTest.executeSoon(cleanupRestoreUpdaterBackup);
+ return;
+ }
+
+ SimpleTest.executeSoon(finishTest);
+}
+
+function finishTest() {
+ debugDump("entering");
+
+ if (Services.prefs.prefHasUserValue(PREF_APP_UPDATE_LOG)) {
+ Services.prefs.clearUserPref(PREF_APP_UPDATE_LOG);
+ }
+ SimpleTest.finish();
+}
+
+]]>
+</script>
+
+<body xmlns="http://www.w3.org/1999/xhtml">
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test"></pre>
+</body>
+</window>
diff --git a/toolkit/mozapps/update/tests/chrome/update.sjs b/toolkit/mozapps/update/tests/chrome/update.sjs
new file mode 100644
index 000000000..78bb1b93f
--- /dev/null
+++ b/toolkit/mozapps/update/tests/chrome/update.sjs
@@ -0,0 +1,194 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+/**
+ * Server side http server script for application update tests.
+ */
+
+const { classes: Cc, interfaces: Ci } = Components;
+
+const REL_PATH_DATA = "chrome/toolkit/mozapps/update/tests/data/";
+
+function getTestDataFile(aFilename) {
+ let file = Cc["@mozilla.org/file/directory_service;1"].
+ getService(Ci.nsIProperties).get("CurWorkD", Ci.nsILocalFile);
+ let pathParts = REL_PATH_DATA.split("/");
+ for (let i = 0; i < pathParts.length; ++i) {
+ file.append(pathParts[i]);
+ }
+ if (aFilename) {
+ file.append(aFilename);
+ }
+ return file;
+}
+
+function loadHelperScript() {
+ let scriptFile = getTestDataFile("sharedUpdateXML.js");
+ let io = Cc["@mozilla.org/network/io-service;1"].getService(Ci.nsIIOService2);
+ let scriptSpec = io.newFileURI(scriptFile).spec;
+ let scriptloader = Cc["@mozilla.org/moz/jssubscript-loader;1"].
+ getService(Ci.mozIJSSubScriptLoader);
+ scriptloader.loadSubScript(scriptSpec, this);
+}
+loadHelperScript();
+
+const URL_HOST = "http://example.com";
+const URL_PATH_UPDATE_XML = "/chrome/toolkit/mozapps/update/tests/chrome/update.sjs";
+const URL_HTTP_UPDATE_SJS = URL_HOST + URL_PATH_UPDATE_XML;
+const SERVICE_URL = URL_HOST + "/" + REL_PATH_DATA + FILE_SIMPLE_MAR;
+
+const SLOW_MAR_DOWNLOAD_INTERVAL = 100;
+var gTimer;
+
+function handleRequest(aRequest, aResponse) {
+ let params = { };
+ if (aRequest.queryString) {
+ params = parseQueryString(aRequest.queryString);
+ }
+
+ let statusCode = params.statusCode ? parseInt(params.statusCode) : 200;
+ let statusReason = params.statusReason ? params.statusReason : "OK";
+ aResponse.setStatusLine(aRequest.httpVersion, statusCode, statusReason);
+ aResponse.setHeader("Cache-Control", "no-cache", false);
+
+ // When a mar download is started by the update service it can finish
+ // downloading before the ui has loaded. By specifying a serviceURL for the
+ // update patch that points to this file and has a slowDownloadMar param the
+ // mar will be downloaded asynchronously which will allow the ui to load
+ // before the download completes.
+ if (params.slowDownloadMar) {
+ aResponse.processAsync();
+ aResponse.setHeader("Content-Type", "binary/octet-stream");
+ aResponse.setHeader("Content-Length", SIZE_SIMPLE_MAR);
+ var continueFile = getTestDataFile("continue");
+ var contents = readFileBytes(getTestDataFile(FILE_SIMPLE_MAR));
+ gTimer = Cc["@mozilla.org/timer;1"].
+ createInstance(Ci.nsITimer);
+ gTimer.initWithCallback(function(aTimer) {
+ if (continueFile.exists()) {
+ gTimer.cancel();
+ aResponse.write(contents);
+ aResponse.finish();
+ }
+ }, SLOW_MAR_DOWNLOAD_INTERVAL, Ci.nsITimer.TYPE_REPEATING_SLACK);
+ return;
+ }
+
+ if (params.uiURL) {
+ let remoteType = "";
+ if (!params.remoteNoTypeAttr && params.uiURL == "BILLBOARD") {
+ remoteType = " " + params.uiURL.toLowerCase() + "=\"1\"";
+ }
+ aResponse.write("<html><head><meta http-equiv=\"content-type\" content=" +
+ "\"text/html; charset=utf-8\"></head><body" +
+ remoteType + ">" + params.uiURL +
+ "<br><br>this is a test mar that will not affect your " +
+ "build.</body></html>");
+ return;
+ }
+
+ if (params.xmlMalformed) {
+ aResponse.write("xml error");
+ return;
+ }
+
+ if (params.noUpdates) {
+ aResponse.write(getRemoteUpdatesXMLString(""));
+ return;
+ }
+
+ if (params.unsupported) {
+ aResponse.write(getRemoteUpdatesXMLString(" <update type=\"major\" " +
+ "unsupported=\"true\" " +
+ "detailsURL=\"" + URL_HOST +
+ "\"></update>\n"));
+ return;
+ }
+
+ let size;
+ let patches = "";
+ if (!params.partialPatchOnly) {
+ size = SIZE_SIMPLE_MAR + (params.invalidCompleteSize ? "1" : "");
+ patches += getRemotePatchString("complete", SERVICE_URL, "SHA512",
+ SHA512_HASH_SIMPLE_MAR, size);
+ }
+
+ if (!params.completePatchOnly) {
+ size = SIZE_SIMPLE_MAR + (params.invalidPartialSize ? "1" : "");
+ patches += getRemotePatchString("partial", SERVICE_URL, "SHA512",
+ SHA512_HASH_SIMPLE_MAR, size);
+ }
+
+ let type = params.type ? params.type : "major";
+ let name = params.name ? params.name : "App Update Test";
+ let appVersion = params.appVersion ? params.appVersion : "999999.9";
+ let displayVersion = params.displayVersion ? params.displayVersion
+ : "version " + appVersion;
+ let buildID = params.buildID ? params.buildID : "01234567890123";
+ // XXXrstrong - not specifying a detailsURL will cause a leak due to bug 470244
+// let detailsURL = params.showDetails ? URL_HTTP_UPDATE_SJS + "?uiURL=DETAILS" : null;
+ let detailsURL = URL_HTTP_UPDATE_SJS + "?uiURL=DETAILS";
+ let showPrompt = params.showPrompt ? "true" : null;
+ let showNever = params.showNever ? "true" : null;
+ let promptWaitTime = params.promptWaitTime ? params.promptWaitTime : null;
+
+ let updates = getRemoteUpdateString(patches, type, "App Update Test",
+ displayVersion, appVersion, buildID,
+ detailsURL, showPrompt, showNever,
+ promptWaitTime);
+ aResponse.write(getRemoteUpdatesXMLString(updates));
+}
+
+/**
+ * Helper function to create a JS object representing the url parameters from
+ * the request's queryString.
+ *
+ * @param aQueryString
+ * The request's query string.
+ * @return A JS object representing the url parameters from the request's
+ * queryString.
+ */
+function parseQueryString(aQueryString) {
+ let paramArray = aQueryString.split("&");
+ let regex = /^([^=]+)=(.*)$/;
+ let params = {};
+ for (let i = 0, sz = paramArray.length; i < sz; i++) {
+ let match = regex.exec(paramArray[i]);
+ if (!match) {
+ throw "Bad parameter in queryString! '" + paramArray[i] + "'";
+ }
+ params[decodeURIComponent(match[1])] = decodeURIComponent(match[2]);
+ }
+
+ return params;
+}
+
+/**
+ * Reads the binary contents of a file and returns it as a string.
+ *
+ * @param aFile
+ * The file to read from.
+ * @return The contents of the file as a string.
+ */
+function readFileBytes(aFile) {
+ let fis = Cc["@mozilla.org/network/file-input-stream;1"].
+ createInstance(Ci.nsIFileInputStream);
+ fis.init(aFile, -1, -1, false);
+ let bis = Cc["@mozilla.org/binaryinputstream;1"].
+ createInstance(Ci.nsIBinaryInputStream);
+ bis.setInputStream(fis);
+ let data = [];
+ let count = fis.available();
+ while (count > 0) {
+ let bytes = bis.readByteArray(Math.min(65535, count));
+ data.push(String.fromCharCode.apply(null, bytes));
+ count -= bytes.length;
+ if (bytes.length == 0) {
+ throw "Nothing read from input stream!";
+ }
+ }
+ data.join('');
+ fis.close();
+ return data.toString();
+}
diff --git a/toolkit/mozapps/update/tests/chrome/utils.js b/toolkit/mozapps/update/tests/chrome/utils.js
new file mode 100644
index 000000000..31d0d2e5a
--- /dev/null
+++ b/toolkit/mozapps/update/tests/chrome/utils.js
@@ -0,0 +1,1011 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+/**
+ * Test Definition
+ *
+ * Most tests can use an array named TESTS that will perform most if not all of
+ * the necessary checks. Each element in the array must be an object with the
+ * following possible properties. Additional properties besides the ones listed
+ * below can be added as needed.
+ *
+ * overrideCallback (optional)
+ * The function to call for the next test. This is typically called when the
+ * wizard page changes but can also be called for other events by the previous
+ * test. If this property isn't defined then the defaultCallback function will
+ * be called. If this property is defined then all other properties are
+ * optional.
+ *
+ * pageid (required unless overrideCallback is specified)
+ * The expected pageid for the wizard. This property is required unless the
+ * overrideCallback property is defined.
+ *
+ * extraStartFunction (optional)
+ * The function to call at the beginning of the defaultCallback function. If
+ * the function returns true the defaultCallback function will return early
+ * which allows waiting for a specific condition to be evaluated in the
+ * function specified in the extraStartFunction property before continuing
+ * with the test.
+ *
+ * extraCheckFunction (optional)
+ * The function to call to perform extra checks in the defaultCallback
+ * function.
+ *
+ * extraDelayedCheckFunction (optional)
+ * The function to call to perform extra checks in the delayedDefaultCallback
+ * function.
+ *
+ * buttonStates (optional)
+ * A javascript object representing the expected hidden and disabled attribute
+ * values for the buttons of the current wizard page. The values are checked
+ * in the delayedDefaultCallback function. For information about the structure
+ * of this object refer to the getExpectedButtonStates and checkButtonStates
+ * functions.
+ *
+ * buttonClick (optional)
+ * The current wizard page button to click at the end of the
+ * delayedDefaultCallback function. If the buttonClick property is defined
+ * then the extraDelayedFinishFunction property can't be specified due to race
+ * conditions in some of the tests and if both of them are specified the test
+ * will intentionally throw.
+ *
+ * extraDelayedFinishFunction (optional)
+ * The function to call at the end of the delayedDefaultCallback function.
+ * If the extraDelayedFinishFunction property is defined then the buttonClick
+ * property can't be specified due to race conditions in some of the tests and
+ * if both of them are specified the test will intentionally throw.
+ *
+ * ranTest (should not be specified)
+ * When delayedDefaultCallback is called a property named ranTest is added to
+ * the current test so it is possible to verify that each test in the TESTS
+ * array has ran.
+ *
+ * prefHasUserValue (optional)
+ * For comparing the expected value defined by this property with the return
+ * value of prefHasUserValue using gPrefToCheck for the preference name in the
+ * checkPrefHasUserValue function.
+ */
+
+'use strict';
+
+/* globals TESTS, runTest, finishTest */
+
+const { classes: Cc, interfaces: Ci, manager: Cm, results: Cr,
+ utils: Cu } = Components;
+
+Cu.import("resource://gre/modules/Services.jsm", this);
+
+const IS_MACOSX = ("nsILocalFileMac" in Ci);
+const IS_WIN = ("@mozilla.org/windows-registry-key;1" in Cc);
+
+// The tests have to use the pageid instead of the pageIndex due to the
+// app update wizard's access method being random.
+const PAGEID_DUMMY = "dummy"; // Done
+const PAGEID_CHECKING = "checking"; // Done
+const PAGEID_NO_UPDATES_FOUND = "noupdatesfound"; // Done
+const PAGEID_MANUAL_UPDATE = "manualUpdate"; // Done
+const PAGEID_UNSUPPORTED = "unsupported"; // Done
+const PAGEID_FOUND_BASIC = "updatesfoundbasic"; // Done
+const PAGEID_DOWNLOADING = "downloading"; // Done
+const PAGEID_ERRORS = "errors"; // Done
+const PAGEID_ERROR_EXTRA = "errorextra"; // Done
+const PAGEID_ERROR_PATCHING = "errorpatching"; // Done
+const PAGEID_FINISHED = "finished"; // Done
+const PAGEID_FINISHED_BKGRD = "finishedBackground"; // Done
+
+const UPDATE_WINDOW_NAME = "Update:Wizard";
+
+const URL_HOST = "http://example.com";
+const URL_PATH_UPDATE_XML = "/chrome/toolkit/mozapps/update/tests/chrome/update.sjs";
+const REL_PATH_DATA = "chrome/toolkit/mozapps/update/tests/data";
+
+// These two URLs must not contain parameters since tests add their own
+// test specific parameters.
+const URL_HTTP_UPDATE_XML = URL_HOST + URL_PATH_UPDATE_XML;
+const URL_HTTPS_UPDATE_XML = "https://example.com" + URL_PATH_UPDATE_XML;
+
+const URI_UPDATE_PROMPT_DIALOG = "chrome://mozapps/content/update/updates.xul";
+
+const PREF_APP_UPDATE_INTERVAL = "app.update.interval";
+const PREF_APP_UPDATE_LASTUPDATETIME = "app.update.lastUpdateTime.background-update-timer";
+
+const LOG_FUNCTION = info;
+
+const BIN_SUFFIX = (IS_WIN ? ".exe" : "");
+const FILE_UPDATER_BIN = "updater" + (IS_MACOSX ? ".app" : BIN_SUFFIX);
+const FILE_UPDATER_BIN_BAK = FILE_UPDATER_BIN + ".bak";
+
+var gURLData = URL_HOST + "/" + REL_PATH_DATA + "/";
+
+var gTestTimeout = 240000; // 4 minutes
+var gTimeoutTimer;
+
+// The number of SimpleTest.executeSoon calls to perform when waiting on an
+// update window to close before giving up.
+const CLOSE_WINDOW_TIMEOUT_MAXCOUNT = 10;
+// Counter for the SimpleTest.executeSoon when waiting on an update window to
+// close before giving up.
+var gCloseWindowTimeoutCounter = 0;
+
+// The following vars are for restoring previous preference values (if present)
+// when the test finishes.
+var gAppUpdateEnabled; // app.update.enabled
+var gAppUpdateServiceEnabled; // app.update.service.enabled
+var gAppUpdateStagingEnabled; // app.update.staging.enabled
+var gAppUpdateURLDefault; // app.update.url (default prefbranch)
+
+var gTestCounter = -1;
+var gWin;
+var gDocElem;
+var gPrefToCheck;
+var gUseTestUpdater = false;
+
+// Set to true to log additional information for debugging. To log additional
+// information for an individual test set DEBUG_AUS_TEST to true in the test's
+// onload function.
+var DEBUG_AUS_TEST = true;
+
+const DATA_URI_SPEC = "chrome://mochitests/content/chrome/toolkit/mozapps/update/tests/data/";
+/* import-globals-from ../data/shared.js */
+Services.scriptloader.loadSubScript(DATA_URI_SPEC + "shared.js", this);
+
+/**
+ * The current test in TESTS array.
+ */
+this.__defineGetter__("gTest", function() {
+ return TESTS[gTestCounter];
+});
+
+/**
+ * The current test's callback. This will either return the callback defined in
+ * the test's overrideCallback property or defaultCallback if the
+ * overrideCallback property is undefined.
+ */
+this.__defineGetter__("gCallback", function() {
+ return gTest.overrideCallback ? gTest.overrideCallback
+ : defaultCallback;
+});
+
+/**
+ * nsIObserver for receiving window open and close notifications.
+ */
+const gWindowObserver = {
+ observe: function WO_observe(aSubject, aTopic, aData) {
+ let win = aSubject.QueryInterface(Ci.nsIDOMEventTarget);
+
+ if (aTopic == "domwindowclosed") {
+ if (win.location != URI_UPDATE_PROMPT_DIALOG) {
+ debugDump("domwindowclosed event for window not being tested - " +
+ "location: " + win.location + "... returning early");
+ return;
+ }
+ // Allow tests the ability to provide their own function (it must be
+ // named finishTest) for finishing the test.
+ try {
+ finishTest();
+ }
+ catch (e) {
+ finishTestDefault();
+ }
+ return;
+ }
+
+ win.addEventListener("load", function WO_observe_onLoad() {
+ win.removeEventListener("load", WO_observe_onLoad, false);
+ // Ignore windows other than the update UI window.
+ if (win.location != URI_UPDATE_PROMPT_DIALOG) {
+ debugDump("load event for window not being tested - location: " +
+ win.location + "... returning early");
+ return;
+ }
+
+ // The first wizard page should always be the dummy page.
+ let pageid = win.document.documentElement.currentPage.pageid;
+ if (pageid != PAGEID_DUMMY) {
+ // This should never happen but if it does this will provide a clue
+ // for diagnosing the cause.
+ ok(false, "Unexpected load event - pageid got: " + pageid +
+ ", expected: " + PAGEID_DUMMY + "... returning early");
+ return;
+ }
+
+ gWin = win;
+ gDocElem = gWin.document.documentElement;
+ gDocElem.addEventListener("pageshow", onPageShowDefault, false);
+ }, false);
+ }
+};
+
+/**
+ * Default test run function that can be used by most tests. This function uses
+ * protective measures to prevent the test from failing provided by
+ * |runTestDefaultWaitForWindowClosed| helper functions to prevent failure due
+ * to a previous test failure.
+ */
+function runTestDefault() {
+ debugDump("entering");
+
+ if (!("@mozilla.org/zipwriter;1" in Cc)) {
+ ok(false, "nsIZipWriter is required to run these tests");
+ return;
+ }
+
+ SimpleTest.waitForExplicitFinish();
+
+ runTestDefaultWaitForWindowClosed();
+}
+
+/**
+ * If an update window is found SimpleTest.executeSoon can callback before the
+ * update window is fully closed especially with debug builds. If an update
+ * window is found this function will call itself using SimpleTest.executeSoon
+ * up to the amount declared in CLOSE_WINDOW_TIMEOUT_MAXCOUNT until the update
+ * window has closed before continuing the test.
+ */
+function runTestDefaultWaitForWindowClosed() {
+ gCloseWindowTimeoutCounter++;
+ if (gCloseWindowTimeoutCounter > CLOSE_WINDOW_TIMEOUT_MAXCOUNT) {
+ try {
+ finishTest();
+ }
+ catch (e) {
+ finishTestDefault();
+ }
+ return;
+ }
+
+ // The update window should not be open at this time. If it is the call to
+ // |closeUpdateWindow| will close it and cause the test to fail.
+ if (closeUpdateWindow()) {
+ SimpleTest.executeSoon(runTestDefaultWaitForWindowClosed);
+ } else {
+ Services.ww.registerNotification(gWindowObserver);
+
+ gCloseWindowTimeoutCounter = 0;
+
+ setupFiles();
+ setupPrefs();
+ gEnv.set("MOZ_TEST_SKIP_UPDATE_STAGE", "1");
+ removeUpdateDirsAndFiles();
+ reloadUpdateManagerData();
+ setupTimer(gTestTimeout);
+ SimpleTest.executeSoon(setupTestUpdater);
+ }
+}
+
+/**
+ * Default test finish function that can be used by most tests. This function
+ * uses protective measures to prevent the next test from failing provided by
+ * |finishTestDefaultWaitForWindowClosed| helper functions to prevent failure
+ * due to an update window being left open.
+ */
+function finishTestDefault() {
+ debugDump("entering");
+ if (gTimeoutTimer) {
+ gTimeoutTimer.cancel();
+ gTimeoutTimer = null;
+ }
+
+ if (gChannel) {
+ debugDump("channel = " + gChannel);
+ gChannel = null;
+ gPrefRoot.removeObserver(PREF_APP_UPDATE_CHANNEL, observer);
+ }
+
+ verifyTestsRan();
+
+ resetPrefs();
+ gEnv.set("MOZ_TEST_SKIP_UPDATE_STAGE", "");
+ resetFiles();
+ removeUpdateDirsAndFiles();
+ reloadUpdateManagerData();
+
+ Services.ww.unregisterNotification(gWindowObserver);
+ if (gDocElem) {
+ gDocElem.removeEventListener("pageshow", onPageShowDefault, false);
+ }
+
+ finishTestRestoreUpdaterBackup();
+}
+
+/**
+ * nsITimerCallback for the timeout timer to cleanly finish a test if the Update
+ * Window doesn't close for a test. This allows the next test to run properly if
+ * a previous test fails.
+ *
+ * @param aTimer
+ * The nsITimer that fired.
+ */
+function finishTestTimeout(aTimer) {
+ ok(false, "Test timed out. Maximum time allowed is " + (gTestTimeout / 1000) +
+ " seconds");
+
+ try {
+ finishTest();
+ }
+ catch (e) {
+ finishTestDefault();
+ }
+}
+
+/**
+ * When a test finishes this will repeatedly attempt to restore the real updater
+ * for tests that use the test updater and then call
+ * finishTestDefaultWaitForWindowClosed after the restore is successful.
+ */
+function finishTestRestoreUpdaterBackup() {
+ if (gUseTestUpdater) {
+ try {
+ // Windows debug builds keep the updater file in use for a short period of
+ // time after the updater process exits.
+ restoreUpdaterBackup();
+ } catch (e) {
+ logTestInfo("Attempt to restore the backed up updater failed... " +
+ "will try again, Exception: " + e);
+ SimpleTest.executeSoon(finishTestRestoreUpdaterBackup);
+ return;
+ }
+ }
+
+ finishTestDefaultWaitForWindowClosed();
+}
+
+/**
+ * If an update window is found SimpleTest.executeSoon can callback before the
+ * update window is fully closed especially with debug builds. If an update
+ * window is found this function will call itself using SimpleTest.executeSoon
+ * up to the amount declared in CLOSE_WINDOW_TIMEOUT_MAXCOUNT until the update
+ * window has closed before finishing the test.
+ */
+function finishTestDefaultWaitForWindowClosed() {
+ gCloseWindowTimeoutCounter++;
+ if (gCloseWindowTimeoutCounter > CLOSE_WINDOW_TIMEOUT_MAXCOUNT) {
+ SimpleTest.requestCompleteLog();
+ SimpleTest.finish();
+ return;
+ }
+
+ // The update window should not be open at this time. If it is the call to
+ // |closeUpdateWindow| will close it and cause the test to fail.
+ if (closeUpdateWindow()) {
+ SimpleTest.executeSoon(finishTestDefaultWaitForWindowClosed);
+ } else {
+ SimpleTest.finish();
+ }
+}
+
+/**
+ * Default callback for the wizard's documentElement pageshow listener. This
+ * will return early for event's where the originalTarget's nodeName is not
+ * wizardpage.
+ */
+function onPageShowDefault(aEvent) {
+ if (!gTimeoutTimer) {
+ debugDump("gTimeoutTimer is null... returning early");
+ return;
+ }
+
+ // Return early if the event's original target isn't for a wizardpage element.
+ // This check is necessary due to the remotecontent element firing pageshow.
+ if (aEvent.originalTarget.nodeName != "wizardpage") {
+ debugDump("only handles events with an originalTarget nodeName of " +
+ "|wizardpage|. aEvent.originalTarget.nodeName = " +
+ aEvent.originalTarget.nodeName + "... returning early");
+ return;
+ }
+
+ gTestCounter++;
+ gCallback(aEvent);
+}
+
+/**
+ * Default callback that can be used by most tests.
+ */
+function defaultCallback(aEvent) {
+ if (!gTimeoutTimer) {
+ debugDump("gTimeoutTimer is null... returning early");
+ return;
+ }
+
+ debugDump("entering - TESTS[" + gTestCounter + "], pageid: " + gTest.pageid +
+ ", aEvent.originalTarget.nodeName: " +
+ aEvent.originalTarget.nodeName);
+
+ if (gTest && gTest.extraStartFunction) {
+ debugDump("calling extraStartFunction " + gTest.extraStartFunction.name);
+ if (gTest.extraStartFunction(aEvent)) {
+ debugDump("extraStartFunction early return");
+ return;
+ }
+ }
+
+ is(gDocElem.currentPage.pageid, gTest.pageid,
+ "Checking currentPage.pageid equals " + gTest.pageid + " in pageshow");
+
+ // Perform extra checks if specified by the test
+ if (gTest.extraCheckFunction) {
+ debugDump("calling extraCheckFunction " + gTest.extraCheckFunction.name);
+ gTest.extraCheckFunction();
+ }
+
+ // The wizard page buttons' disabled and hidden attributes are set after the
+ // pageshow event so use executeSoon to allow them to be set so their disabled
+ // and hidden attribute values can be checked.
+ SimpleTest.executeSoon(delayedDefaultCallback);
+}
+
+/**
+ * Delayed default callback called using executeSoon in defaultCallback which
+ * allows the wizard page buttons' disabled and hidden attributes to be set
+ * before checking their values.
+ */
+function delayedDefaultCallback() {
+ if (!gTimeoutTimer) {
+ debugDump("gTimeoutTimer is null... returning early");
+ return;
+ }
+
+ if (!gTest) {
+ debugDump("gTest is null... returning early");
+ return;
+ }
+
+ debugDump("entering - TESTS[" + gTestCounter + "], pageid: " + gTest.pageid);
+
+ // Verify the pageid hasn't changed after executeSoon was called.
+ is(gDocElem.currentPage.pageid, gTest.pageid,
+ "Checking currentPage.pageid equals " + gTest.pageid + " after " +
+ "executeSoon");
+
+ checkButtonStates();
+
+ // Perform delayed extra checks if specified by the test
+ if (gTest.extraDelayedCheckFunction) {
+ debugDump("calling extraDelayedCheckFunction " +
+ gTest.extraDelayedCheckFunction.name);
+ gTest.extraDelayedCheckFunction();
+ }
+
+ // Used to verify that this test has been performed
+ gTest.ranTest = true;
+
+ if (gTest.buttonClick) {
+ debugDump("clicking " + gTest.buttonClick + " button");
+ if (gTest.extraDelayedFinishFunction) {
+ throw ("Tests cannot have a buttonClick and an extraDelayedFinishFunction property");
+ }
+ gDocElem.getButton(gTest.buttonClick).click();
+ } else if (gTest.extraDelayedFinishFunction) {
+ debugDump("calling extraDelayedFinishFunction " +
+ gTest.extraDelayedFinishFunction.name);
+ gTest.extraDelayedFinishFunction();
+ }
+}
+
+/**
+ * Gets the continue file used to signal the mock http server to continue
+ * downloading for slow download mar file tests without creating it.
+ *
+ * @return nsILocalFile for the continue file.
+ */
+function getContinueFile() {
+ let continueFile = Cc["@mozilla.org/file/directory_service;1"].
+ getService(Ci.nsIProperties).
+ get("CurWorkD", Ci.nsILocalFile);
+ let continuePath = REL_PATH_DATA + "/continue";
+ let continuePathParts = continuePath.split("/");
+ for (let i = 0; i < continuePathParts.length; ++i) {
+ continueFile.append(continuePathParts[i]);
+ }
+ return continueFile;
+}
+
+/**
+ * Creates the continue file used to signal the mock http server to continue
+ * downloading for slow download mar file tests.
+ */
+function createContinueFile() {
+ debugDump("creating 'continue' file for slow mar downloads");
+ writeFile(getContinueFile(), "");
+}
+
+/**
+ * Removes the continue file used to signal the mock http server to continue
+ * downloading for slow download mar file tests.
+ */
+function removeContinueFile() {
+ let continueFile = getContinueFile();
+ if (continueFile.exists()) {
+ debugDump("removing 'continue' file for slow mar downloads");
+ continueFile.remove(false);
+ }
+}
+
+/**
+ * Checks the wizard page buttons' disabled and hidden attributes values are
+ * correct. If an expected button id is not specified then the expected disabled
+ * and hidden attribute value is true.
+ */
+function checkButtonStates() {
+ debugDump("entering - TESTS[" + gTestCounter + "], pageid: " + gTest.pageid);
+
+ const buttonNames = ["extra1", "extra2", "back", "next", "finish", "cancel"];
+ let buttonStates = getExpectedButtonStates();
+ buttonNames.forEach(function(aButtonName) {
+ let button = gDocElem.getButton(aButtonName);
+ let hasHidden = aButtonName in buttonStates &&
+ "hidden" in buttonStates[aButtonName];
+ let hidden = hasHidden ? buttonStates[aButtonName].hidden : true;
+ let hasDisabled = aButtonName in buttonStates &&
+ "disabled" in buttonStates[aButtonName];
+ let disabled = hasDisabled ? buttonStates[aButtonName].disabled : true;
+ is(button.hidden, hidden, "Checking " + aButtonName + " button " +
+ "hidden attribute value equals " + (hidden ? "true" : "false"));
+ is(button.disabled, disabled, "Checking " + aButtonName + " button " +
+ "disabled attribute value equals " + (disabled ? "true" : "false"));
+ });
+}
+
+/**
+ * Returns the expected disabled and hidden attribute values for the buttons of
+ * the current wizard page.
+ */
+function getExpectedButtonStates() {
+ // Allow individual tests to override the expected button states.
+ if (gTest.buttonStates) {
+ return gTest.buttonStates;
+ }
+
+ switch (gTest.pageid) {
+ case PAGEID_CHECKING:
+ return {cancel: {disabled: false, hidden: false}};
+ case PAGEID_FOUND_BASIC:
+ if (gTest.neverButton) {
+ return {extra1: {disabled: false, hidden: false},
+ extra2: {disabled: false, hidden: false},
+ next: {disabled: false, hidden: false}};
+ }
+ return {extra1: {disabled: false, hidden: false},
+ next: {disabled: false, hidden: false}};
+ case PAGEID_DOWNLOADING:
+ return {extra1: {disabled: false, hidden: false}};
+ case PAGEID_NO_UPDATES_FOUND:
+ case PAGEID_MANUAL_UPDATE:
+ case PAGEID_UNSUPPORTED:
+ case PAGEID_ERRORS:
+ case PAGEID_ERROR_EXTRA:
+ return {finish: {disabled: false, hidden: false}};
+ case PAGEID_ERROR_PATCHING:
+ return {next: { disabled: false, hidden: false}};
+ case PAGEID_FINISHED:
+ case PAGEID_FINISHED_BKGRD:
+ return {extra1: { disabled: false, hidden: false},
+ finish: { disabled: false, hidden: false}};
+ }
+ return null;
+}
+
+/**
+ * Compares the return value of prefHasUserValue for the preference specified in
+ * gPrefToCheck with the value passed in the aPrefHasValue parameter or the
+ * value specified in the current test's prefHasUserValue property if
+ * aPrefHasValue is undefined.
+ *
+ * @param aPrefHasValue (optional)
+ * The expected value returned from prefHasUserValue for the preference
+ * specified in gPrefToCheck. If aPrefHasValue is undefined the value
+ * of the current test's prefHasUserValue property will be used.
+ */
+function checkPrefHasUserValue(aPrefHasValue) {
+ let prefHasUserValue = aPrefHasValue === undefined ? gTest.prefHasUserValue
+ : aPrefHasValue;
+ is(Services.prefs.prefHasUserValue(gPrefToCheck), prefHasUserValue,
+ "Checking prefHasUserValue for preference " + gPrefToCheck + " equals " +
+ (prefHasUserValue ? "true" : "false"));
+}
+
+/**
+ * Checks whether the link is hidden for a general background update check error
+ * or not on the errorextra page and that the app.update.backgroundErrors
+ * preference does not have a user value.
+ *
+ * @param aShouldBeHidden (optional)
+ * The expected value for the label's hidden attribute for the link. If
+ * aShouldBeHidden is undefined the value of the current test's
+ * shouldBeHidden property will be used.
+ */
+function checkErrorExtraPage(aShouldBeHidden) {
+ let shouldBeHidden = aShouldBeHidden === undefined ? gTest.shouldBeHidden
+ : aShouldBeHidden;
+ is(gWin.document.getElementById("errorExtraLinkLabel").hidden, shouldBeHidden,
+ "Checking errorExtraLinkLabel hidden attribute equals " +
+ (shouldBeHidden ? "true" : "false"));
+
+ is(gWin.document.getElementById(gTest.displayedTextElem).hidden, false,
+ "Checking " + gTest.displayedTextElem + " should not be hidden");
+
+ ok(!Services.prefs.prefHasUserValue(PREF_APP_UPDATE_BACKGROUNDERRORS),
+ "Preference " + PREF_APP_UPDATE_BACKGROUNDERRORS + " should not have a " +
+ "user value");
+}
+
+/**
+ * Gets the update version info for the update url parameters to send to
+ * update.sjs.
+ *
+ * @param aAppVersion (optional)
+ * The application version for the update snippet. If not specified the
+ * current application version will be used.
+ * @return The url parameters for the application and platform version to send
+ * to update.sjs.
+ */
+function getVersionParams(aAppVersion) {
+ let appInfo = Services.appinfo;
+ return "&appVersion=" + (aAppVersion ? aAppVersion : appInfo.version);
+}
+
+/**
+ * Verifies that all tests ran.
+ */
+function verifyTestsRan() {
+ debugDump("entering");
+
+ // Return early if there are no tests defined.
+ if (!TESTS) {
+ return;
+ }
+
+ gTestCounter = -1;
+ for (let i = 0; i < TESTS.length; ++i) {
+ gTestCounter++;
+ let test = TESTS[i];
+ let msg = "Checking if TESTS[" + i + "] test was performed... " +
+ "callback function name = " + gCallback.name + ", " +
+ "pageid = " + test.pageid;
+ ok(test.ranTest, msg);
+ }
+}
+
+/**
+ * Creates a backup of files the tests need to modify so they can be restored to
+ * the original file when the test has finished and then modifies the files.
+ */
+function setupFiles() {
+ // Backup the updater-settings.ini file if it exists by moving it.
+ let baseAppDir = getGREDir();
+ let updateSettingsIni = baseAppDir.clone();
+ updateSettingsIni.append(FILE_UPDATE_SETTINGS_INI);
+ if (updateSettingsIni.exists()) {
+ updateSettingsIni.moveTo(baseAppDir, FILE_UPDATE_SETTINGS_INI_BAK);
+ }
+ updateSettingsIni = baseAppDir.clone();
+ updateSettingsIni.append(FILE_UPDATE_SETTINGS_INI);
+ writeFile(updateSettingsIni, UPDATE_SETTINGS_CONTENTS);
+}
+
+/**
+ * For tests that use the test updater restores the backed up real updater if
+ * it exists and tries again on failure since Windows debug builds at times
+ * leave the file in use. After success moveRealUpdater is called to continue
+ * the setup of the test updater. For tests that don't use the test updater
+ * runTest will be called.
+ */
+function setupTestUpdater() {
+ if (!gUseTestUpdater) {
+ runTest();
+ return;
+ }
+
+ try {
+ restoreUpdaterBackup();
+ } catch (e) {
+ logTestInfo("Attempt to restore the backed up updater failed... " +
+ "will try again, Exception: " + e);
+ SimpleTest.executeSoon(setupTestUpdater);
+ return;
+ }
+ moveRealUpdater();
+}
+
+/**
+ * Backs up the real updater and tries again on failure since Windows debug
+ * builds at times leave the file in use. After success it will call
+ * copyTestUpdater to continue the setup of the test updater.
+ */
+function moveRealUpdater() {
+ try {
+ // Move away the real updater
+ let baseAppDir = getAppBaseDir();
+ let updater = baseAppDir.clone();
+ updater.append(FILE_UPDATER_BIN);
+ updater.moveTo(baseAppDir, FILE_UPDATER_BIN_BAK);
+ } catch (e) {
+ logTestInfo("Attempt to move the real updater out of the way failed... " +
+ "will try again, Exception: " + e);
+ SimpleTest.executeSoon(moveRealUpdater);
+ return;
+ }
+
+ copyTestUpdater();
+}
+
+/**
+ * Copies the test updater so it can be used by tests and tries again on failure
+ * since Windows debug builds at times leave the file in use. After success it
+ * will call runTest to continue the test.
+ */
+function copyTestUpdater() {
+ try {
+ // Copy the test updater
+ let baseAppDir = getAppBaseDir();
+ let testUpdaterDir = Services.dirsvc.get("CurWorkD", Ci.nsILocalFile);
+ let relPath = REL_PATH_DATA;
+ let pathParts = relPath.split("/");
+ for (let i = 0; i < pathParts.length; ++i) {
+ testUpdaterDir.append(pathParts[i]);
+ }
+
+ let testUpdater = testUpdaterDir.clone();
+ testUpdater.append(FILE_UPDATER_BIN);
+ testUpdater.copyToFollowingLinks(baseAppDir, FILE_UPDATER_BIN);
+ } catch (e) {
+ logTestInfo("Attempt to copy the test updater failed... " +
+ "will try again, Exception: " + e);
+ SimpleTest.executeSoon(copyTestUpdater);
+ return;
+ }
+
+ runTest();
+}
+
+/**
+ * Restores the updater that was backed up. This is called in setupTestUpdater
+ * before the backup of the real updater is done in case the previous test
+ * failed to restore the updater, in finishTestDefaultWaitForWindowClosed when
+ * the test has finished, and in test_9999_cleanup.xul after all tests have
+ * finished.
+ */
+function restoreUpdaterBackup() {
+ let baseAppDir = getAppBaseDir();
+ let updater = baseAppDir.clone();
+ let updaterBackup = baseAppDir.clone();
+ updater.append(FILE_UPDATER_BIN);
+ updaterBackup.append(FILE_UPDATER_BIN_BAK);
+ if (updaterBackup.exists()) {
+ if (updater.exists()) {
+ updater.remove(true);
+ }
+ updaterBackup.moveTo(baseAppDir, FILE_UPDATER_BIN);
+ }
+}
+
+/**
+ * Sets the most common preferences used by tests to values used by the majority
+ * of the tests and when necessary saves the preference's original values if
+ * present so they can be set back to the original values when the test has
+ * finished.
+ */
+function setupPrefs() {
+ if (DEBUG_AUS_TEST) {
+ Services.prefs.setBoolPref(PREF_APP_UPDATE_LOG, true);
+ }
+
+ // Prevent nsIUpdateTimerManager from notifying nsIApplicationUpdateService
+ // to check for updates by setting the app update last update time to the
+ // current time minus one minute in seconds and the interval time to 12 hours
+ // in seconds.
+ let now = Math.round(Date.now() / 1000) - 60;
+ Services.prefs.setIntPref(PREF_APP_UPDATE_LASTUPDATETIME, now);
+ Services.prefs.setIntPref(PREF_APP_UPDATE_INTERVAL, 43200);
+
+ if (Services.prefs.prefHasUserValue(PREF_APP_UPDATE_ENABLED)) {
+ gAppUpdateEnabled = Services.prefs.getBoolPref(PREF_APP_UPDATE_ENABLED);
+ }
+ Services.prefs.setBoolPref(PREF_APP_UPDATE_ENABLED, true);
+
+ if (Services.prefs.prefHasUserValue(PREF_APP_UPDATE_SERVICE_ENABLED)) {
+ gAppUpdateServiceEnabled = Services.prefs.getBoolPref(PREF_APP_UPDATE_SERVICE_ENABLED);
+ }
+ Services.prefs.setBoolPref(PREF_APP_UPDATE_SERVICE_ENABLED, false);
+
+ if (Services.prefs.prefHasUserValue(PREF_APP_UPDATE_STAGING_ENABLED)) {
+ gAppUpdateStagingEnabled = Services.prefs.getBoolPref(PREF_APP_UPDATE_STAGING_ENABLED);
+ }
+ Services.prefs.setBoolPref(PREF_APP_UPDATE_STAGING_ENABLED, false);
+
+ Services.prefs.setIntPref(PREF_APP_UPDATE_IDLETIME, 0);
+ Services.prefs.setIntPref(PREF_APP_UPDATE_PROMPTWAITTIME, 0);
+ Services.prefs.setBoolPref(PREF_APP_UPDATE_SILENT, false);
+ Services.prefs.setIntPref(PREF_APP_UPDATE_DOWNLOADBACKGROUNDINTERVAL, 0);
+}
+
+/**
+ * Restores files that were backed up for the tests and general file cleanup.
+ */
+function resetFiles() {
+ // Restore the backed up updater-settings.ini if it exists.
+ let baseAppDir = getGREDir();
+ let updateSettingsIni = baseAppDir.clone();
+ updateSettingsIni.append(FILE_UPDATE_SETTINGS_INI_BAK);
+ if (updateSettingsIni.exists()) {
+ updateSettingsIni.moveTo(baseAppDir, FILE_UPDATE_SETTINGS_INI);
+ }
+
+ // Not being able to remove the "updated" directory will not adversely affect
+ // subsequent tests so wrap it in a try block and don't test whether its
+ // removal was successful.
+ let updatedDir;
+ if (IS_MACOSX) {
+ updatedDir = getUpdatesDir();
+ updatedDir.append(DIR_PATCH);
+ } else {
+ updatedDir = getAppBaseDir();
+ }
+ updatedDir.append(DIR_UPDATED);
+ if (updatedDir.exists()) {
+ try {
+ removeDirRecursive(updatedDir);
+ }
+ catch (e) {
+ logTestInfo("Unable to remove directory. Path: " + updatedDir.path +
+ ", Exception: " + e);
+ }
+ }
+}
+
+/**
+ * Resets the most common preferences used by tests to their original values.
+ */
+function resetPrefs() {
+ if (gAppUpdateURLDefault) {
+ gDefaultPrefBranch.setCharPref(PREF_APP_UPDATE_URL, gAppUpdateURLDefault);
+ }
+
+ if (gAppUpdateEnabled !== undefined) {
+ Services.prefs.setBoolPref(PREF_APP_UPDATE_ENABLED, gAppUpdateEnabled);
+ } else if (Services.prefs.prefHasUserValue(PREF_APP_UPDATE_ENABLED)) {
+ Services.prefs.clearUserPref(PREF_APP_UPDATE_ENABLED);
+ }
+
+ if (gAppUpdateServiceEnabled !== undefined) {
+ Services.prefs.setBoolPref(PREF_APP_UPDATE_SERVICE_ENABLED, gAppUpdateServiceEnabled);
+ } else if (Services.prefs.prefHasUserValue(PREF_APP_UPDATE_SERVICE_ENABLED)) {
+ Services.prefs.clearUserPref(PREF_APP_UPDATE_SERVICE_ENABLED);
+ }
+
+ if (gAppUpdateStagingEnabled !== undefined) {
+ Services.prefs.setBoolPref(PREF_APP_UPDATE_STAGING_ENABLED, gAppUpdateStagingEnabled);
+ } else if (Services.prefs.prefHasUserValue(PREF_APP_UPDATE_STAGING_ENABLED)) {
+ Services.prefs.clearUserPref(PREF_APP_UPDATE_STAGING_ENABLED);
+ }
+
+ if (Services.prefs.prefHasUserValue(PREF_APP_UPDATE_IDLETIME)) {
+ Services.prefs.clearUserPref(PREF_APP_UPDATE_IDLETIME);
+ }
+
+ if (Services.prefs.prefHasUserValue(PREF_APP_UPDATE_PROMPTWAITTIME)) {
+ Services.prefs.clearUserPref(PREF_APP_UPDATE_PROMPTWAITTIME);
+ }
+
+ if (Services.prefs.prefHasUserValue(PREF_APP_UPDATE_URL_DETAILS)) {
+ Services.prefs.clearUserPref(PREF_APP_UPDATE_URL_DETAILS);
+ }
+
+ if (Services.prefs.prefHasUserValue(PREF_APP_UPDATE_NOTIFIEDUNSUPPORTED)) {
+ Services.prefs.clearUserPref(PREF_APP_UPDATE_NOTIFIEDUNSUPPORTED);
+ }
+
+ if (Services.prefs.prefHasUserValue(PREF_APP_UPDATE_LOG)) {
+ Services.prefs.clearUserPref(PREF_APP_UPDATE_LOG);
+ }
+
+ if (Services.prefs.prefHasUserValue(PREF_APP_UPDATE_SILENT)) {
+ Services.prefs.clearUserPref(PREF_APP_UPDATE_SILENT);
+ }
+
+ if (Services.prefs.prefHasUserValue(PREF_APP_UPDATE_BACKGROUNDERRORS)) {
+ Services.prefs.clearUserPref(PREF_APP_UPDATE_BACKGROUNDERRORS);
+ }
+
+ if (Services.prefs.prefHasUserValue(PREF_APP_UPDATE_BACKGROUNDMAXERRORS)) {
+ Services.prefs.clearUserPref(PREF_APP_UPDATE_BACKGROUNDMAXERRORS);
+ }
+
+ if (Services.prefs.prefHasUserValue(PREF_APP_UPDATE_DOWNLOADBACKGROUNDINTERVAL)) {
+ Services.prefs.clearUserPref(PREF_APP_UPDATE_DOWNLOADBACKGROUNDINTERVAL);
+ }
+
+ try {
+ Services.prefs.deleteBranch(PREFBRANCH_APP_UPDATE_NEVER);
+ }
+ catch (e) {
+ }
+}
+
+function setupTimer(aTestTimeout) {
+ gTestTimeout = aTestTimeout;
+ if (gTimeoutTimer) {
+ gTimeoutTimer.cancel();
+ gTimeoutTimer = null;
+ }
+ gTimeoutTimer = Cc["@mozilla.org/timer;1"].
+ createInstance(Ci.nsITimer);
+ gTimeoutTimer.initWithCallback(finishTestTimeout, gTestTimeout,
+ Ci.nsITimer.TYPE_ONE_SHOT);
+}
+
+/**
+ * Closes the update window if it is open and causes the test to fail if an
+ * update window is found.
+ *
+ * @return true if an update window was found, otherwise false.
+ */
+function closeUpdateWindow() {
+ let updateWindow = getUpdateWindow();
+ if (!updateWindow) {
+ return false;
+ }
+
+ ok(false, "Found an existing Update Window from the current or a previous " +
+ "test... attempting to close it.");
+ updateWindow.close();
+ return true;
+}
+
+/**
+ * Gets the update window.
+ *
+ * @return The nsIDOMWindow for the Update Window if it is open and null
+ * if it isn't.
+ */
+function getUpdateWindow() {
+ return Services.wm.getMostRecentWindow(UPDATE_WINDOW_NAME);
+}
+
+/**
+ * Helper for background check errors.
+ */
+const errorsPrefObserver = {
+ observedPref: null,
+ maxErrorPref: null,
+
+ /**
+ * Sets up a preference observer and sets the associated maximum errors
+ * preference used for background notification.
+ *
+ * @param aObservePref
+ * The preference to observe.
+ * @param aMaxErrorPref
+ * The maximum errors preference.
+ * @param aMaxErrorCount
+ * The value to set the maximum errors preference to.
+ */
+ init: function(aObservePref, aMaxErrorPref, aMaxErrorCount) {
+ this.observedPref = aObservePref;
+ this.maxErrorPref = aMaxErrorPref;
+
+ let maxErrors = aMaxErrorCount ? aMaxErrorCount : 2;
+ Services.prefs.setIntPref(aMaxErrorPref, maxErrors);
+ Services.prefs.addObserver(aObservePref, this, false);
+ },
+
+ /**
+ * Preference observer for the preference specified in |this.observedPref|.
+ */
+ observe: function XPI_observe(aSubject, aTopic, aData) {
+ if (aData == this.observedPref) {
+ let errCount = Services.prefs.getIntPref(this.observedPref);
+ let errMax = Services.prefs.getIntPref(this.maxErrorPref);
+ if (errCount >= errMax) {
+ debugDump("removing pref observer");
+ Services.prefs.removeObserver(this.observedPref, this);
+ } else {
+ debugDump("notifying AUS");
+ SimpleTest.executeSoon(function() {
+ gAUS.notify(null);
+ });
+ }
+ }
+ }
+};
diff --git a/toolkit/mozapps/update/tests/data/complete.exe b/toolkit/mozapps/update/tests/data/complete.exe
new file mode 100644
index 000000000..da9cdf0cc
--- /dev/null
+++ b/toolkit/mozapps/update/tests/data/complete.exe
Binary files differ
diff --git a/toolkit/mozapps/update/tests/data/complete.mar b/toolkit/mozapps/update/tests/data/complete.mar
new file mode 100644
index 000000000..52306e0a2
--- /dev/null
+++ b/toolkit/mozapps/update/tests/data/complete.mar
Binary files differ
diff --git a/toolkit/mozapps/update/tests/data/complete.png b/toolkit/mozapps/update/tests/data/complete.png
new file mode 100644
index 000000000..2990a539f
--- /dev/null
+++ b/toolkit/mozapps/update/tests/data/complete.png
Binary files differ
diff --git a/toolkit/mozapps/update/tests/data/complete_log_success_mac b/toolkit/mozapps/update/tests/data/complete_log_success_mac
new file mode 100644
index 000000000..4f992a137
--- /dev/null
+++ b/toolkit/mozapps/update/tests/data/complete_log_success_mac
@@ -0,0 +1,332 @@
+UPDATE TYPE complete
+PREPARE REMOVEFILE Contents/Resources/searchplugins/searchpluginstext0
+PREPARE REMOVEFILE Contents/Resources/searchplugins/searchpluginspng0.png
+PREPARE REMOVEFILE Contents/Resources/removed-files
+PREPARE REMOVEFILE Contents/Resources/precomplete
+PREPARE REMOVEFILE Contents/Resources/2/20/20text0
+PREPARE REMOVEFILE Contents/Resources/2/20/20png0.png
+PREPARE REMOVEFILE Contents/Resources/0/0exe0.exe
+PREPARE REMOVEFILE Contents/Resources/0/00/00text0
+PREPARE REMOVEFILE Contents/MacOS/exe0.exe
+PREPARE REMOVEDIR Contents/Resources/searchplugins/
+PREPARE REMOVEDIR Contents/Resources/defaults/pref/
+PREPARE REMOVEDIR Contents/Resources/defaults/
+PREPARE REMOVEDIR Contents/Resources/2/20/
+PREPARE REMOVEDIR Contents/Resources/2/
+PREPARE REMOVEDIR Contents/Resources/0/00/
+PREPARE REMOVEDIR Contents/Resources/0/
+PREPARE REMOVEDIR Contents/Resources/
+PREPARE REMOVEDIR Contents/MacOS/
+PREPARE REMOVEDIR Contents/
+PREPARE ADD Contents/Resources/searchplugins/searchpluginstext0
+PREPARE ADD Contents/Resources/searchplugins/searchpluginspng1.png
+PREPARE ADD Contents/Resources/searchplugins/searchpluginspng0.png
+PREPARE ADD Contents/Resources/removed-files
+PREPARE ADD Contents/Resources/precomplete
+PREPARE ADD Contents/Resources/distribution/extensions/extensions1/extensions1text0
+PREPARE ADD Contents/Resources/distribution/extensions/extensions1/extensions1png1.png
+PREPARE ADD Contents/Resources/distribution/extensions/extensions1/extensions1png0.png
+PREPARE ADD Contents/Resources/distribution/extensions/extensions0/extensions0text0
+PREPARE ADD Contents/Resources/distribution/extensions/extensions0/extensions0png1.png
+PREPARE ADD Contents/Resources/distribution/extensions/extensions0/extensions0png0.png
+PREPARE ADD Contents/Resources/1/10/10text0
+PREPARE ADD Contents/Resources/0/0exe0.exe
+PREPARE ADD Contents/Resources/0/00/00text1
+PREPARE ADD Contents/Resources/0/00/00text0
+PREPARE ADD Contents/Resources/0/00/00png0.png
+PREPARE ADD Contents/MacOS/exe0.exe
+PREPARE REMOVEDIR Contents/Resources/9/99/
+PREPARE REMOVEDIR Contents/Resources/9/99/
+PREPARE REMOVEDIR Contents/Resources/9/98/
+PREPARE REMOVEFILE Contents/Resources/9/97/970/97xtext0
+PREPARE REMOVEFILE Contents/Resources/9/97/970/97xtext1
+PREPARE REMOVEDIR Contents/Resources/9/97/970/
+PREPARE REMOVEFILE Contents/Resources/9/97/971/97xtext0
+PREPARE REMOVEFILE Contents/Resources/9/97/971/97xtext1
+PREPARE REMOVEDIR Contents/Resources/9/97/971/
+PREPARE REMOVEDIR Contents/Resources/9/97/
+PREPARE REMOVEFILE Contents/Resources/9/96/96text0
+PREPARE REMOVEFILE Contents/Resources/9/96/96text1
+PREPARE REMOVEDIR Contents/Resources/9/96/
+PREPARE REMOVEDIR Contents/Resources/9/95/
+PREPARE REMOVEDIR Contents/Resources/9/95/
+PREPARE REMOVEDIR Contents/Resources/9/94/
+PREPARE REMOVEDIR Contents/Resources/9/94/
+PREPARE REMOVEDIR Contents/Resources/9/93/
+PREPARE REMOVEDIR Contents/Resources/9/92/
+PREPARE REMOVEDIR Contents/Resources/9/91/
+PREPARE REMOVEDIR Contents/Resources/9/90/
+PREPARE REMOVEDIR Contents/Resources/9/90/
+PREPARE REMOVEDIR Contents/Resources/8/89/
+PREPARE REMOVEDIR Contents/Resources/8/89/
+PREPARE REMOVEDIR Contents/Resources/8/88/
+PREPARE REMOVEFILE Contents/Resources/8/87/870/87xtext0
+PREPARE REMOVEFILE Contents/Resources/8/87/870/87xtext1
+PREPARE REMOVEDIR Contents/Resources/8/87/870/
+PREPARE REMOVEFILE Contents/Resources/8/87/871/87xtext0
+PREPARE REMOVEFILE Contents/Resources/8/87/871/87xtext1
+PREPARE REMOVEDIR Contents/Resources/8/87/871/
+PREPARE REMOVEDIR Contents/Resources/8/87/
+PREPARE REMOVEFILE Contents/Resources/8/86/86text0
+PREPARE REMOVEFILE Contents/Resources/8/86/86text1
+PREPARE REMOVEDIR Contents/Resources/8/86/
+PREPARE REMOVEDIR Contents/Resources/8/85/
+PREPARE REMOVEDIR Contents/Resources/8/85/
+PREPARE REMOVEDIR Contents/Resources/8/84/
+PREPARE REMOVEDIR Contents/Resources/8/84/
+PREPARE REMOVEDIR Contents/Resources/8/83/
+PREPARE REMOVEDIR Contents/Resources/8/82/
+PREPARE REMOVEDIR Contents/Resources/8/81/
+PREPARE REMOVEDIR Contents/Resources/8/80/
+PREPARE REMOVEDIR Contents/Resources/8/80/
+PREPARE REMOVEFILE Contents/Resources/7/70/7xtest.exe
+PREPARE REMOVEFILE Contents/Resources/7/70/7xtext0
+PREPARE REMOVEFILE Contents/Resources/7/70/7xtext1
+PREPARE REMOVEDIR Contents/Resources/7/70/
+PREPARE REMOVEFILE Contents/Resources/7/71/7xtest.exe
+PREPARE REMOVEFILE Contents/Resources/7/71/7xtext0
+PREPARE REMOVEFILE Contents/Resources/7/71/7xtext1
+PREPARE REMOVEDIR Contents/Resources/7/71/
+PREPARE REMOVEFILE Contents/Resources/7/7text0
+PREPARE REMOVEFILE Contents/Resources/7/7text1
+PREPARE REMOVEDIR Contents/Resources/7/
+PREPARE REMOVEDIR Contents/Resources/6/
+PREPARE REMOVEFILE Contents/Resources/5/5text1
+PREPARE REMOVEFILE Contents/Resources/5/5text0
+PREPARE REMOVEFILE Contents/Resources/5/5test.exe
+PREPARE REMOVEFILE Contents/Resources/5/5text0
+PREPARE REMOVEFILE Contents/Resources/5/5text1
+PREPARE REMOVEDIR Contents/Resources/5/
+PREPARE REMOVEFILE Contents/Resources/4/4text1
+PREPARE REMOVEFILE Contents/Resources/4/4text0
+PREPARE REMOVEDIR Contents/Resources/4/
+PREPARE REMOVEFILE Contents/Resources/3/3text1
+PREPARE REMOVEFILE Contents/Resources/3/3text0
+EXECUTE REMOVEFILE Contents/Resources/searchplugins/searchpluginstext0
+EXECUTE REMOVEFILE Contents/Resources/searchplugins/searchpluginspng0.png
+EXECUTE REMOVEFILE Contents/Resources/removed-files
+EXECUTE REMOVEFILE Contents/Resources/precomplete
+EXECUTE REMOVEFILE Contents/Resources/2/20/20text0
+EXECUTE REMOVEFILE Contents/Resources/2/20/20png0.png
+EXECUTE REMOVEFILE Contents/Resources/0/0exe0.exe
+EXECUTE REMOVEFILE Contents/Resources/0/00/00text0
+EXECUTE REMOVEFILE Contents/MacOS/exe0.exe
+EXECUTE REMOVEDIR Contents/Resources/searchplugins/
+EXECUTE REMOVEDIR Contents/Resources/defaults/pref/
+EXECUTE REMOVEDIR Contents/Resources/defaults/
+EXECUTE REMOVEDIR Contents/Resources/2/20/
+EXECUTE REMOVEDIR Contents/Resources/2/
+EXECUTE REMOVEDIR Contents/Resources/0/00/
+EXECUTE REMOVEDIR Contents/Resources/0/
+EXECUTE REMOVEDIR Contents/Resources/
+EXECUTE REMOVEDIR Contents/MacOS/
+EXECUTE REMOVEDIR Contents/
+EXECUTE ADD Contents/Resources/searchplugins/searchpluginstext0
+EXECUTE ADD Contents/Resources/searchplugins/searchpluginspng1.png
+EXECUTE ADD Contents/Resources/searchplugins/searchpluginspng0.png
+EXECUTE ADD Contents/Resources/removed-files
+EXECUTE ADD Contents/Resources/precomplete
+EXECUTE ADD Contents/Resources/distribution/extensions/extensions1/extensions1text0
+EXECUTE ADD Contents/Resources/distribution/extensions/extensions1/extensions1png1.png
+EXECUTE ADD Contents/Resources/distribution/extensions/extensions1/extensions1png0.png
+EXECUTE ADD Contents/Resources/distribution/extensions/extensions0/extensions0text0
+EXECUTE ADD Contents/Resources/distribution/extensions/extensions0/extensions0png1.png
+EXECUTE ADD Contents/Resources/distribution/extensions/extensions0/extensions0png0.png
+EXECUTE ADD Contents/Resources/1/10/10text0
+EXECUTE ADD Contents/Resources/0/0exe0.exe
+EXECUTE ADD Contents/Resources/0/00/00text1
+EXECUTE ADD Contents/Resources/0/00/00text0
+EXECUTE ADD Contents/Resources/0/00/00png0.png
+EXECUTE ADD Contents/MacOS/exe0.exe
+EXECUTE REMOVEDIR Contents/Resources/9/99/
+EXECUTE REMOVEDIR Contents/Resources/9/99/
+EXECUTE REMOVEDIR Contents/Resources/9/98/
+EXECUTE REMOVEFILE Contents/Resources/9/97/970/97xtext0
+EXECUTE REMOVEFILE Contents/Resources/9/97/970/97xtext1
+EXECUTE REMOVEDIR Contents/Resources/9/97/970/
+EXECUTE REMOVEFILE Contents/Resources/9/97/971/97xtext0
+EXECUTE REMOVEFILE Contents/Resources/9/97/971/97xtext1
+EXECUTE REMOVEDIR Contents/Resources/9/97/971/
+EXECUTE REMOVEDIR Contents/Resources/9/97/
+EXECUTE REMOVEFILE Contents/Resources/9/96/96text0
+EXECUTE REMOVEFILE Contents/Resources/9/96/96text1
+EXECUTE REMOVEDIR Contents/Resources/9/96/
+EXECUTE REMOVEDIR Contents/Resources/9/95/
+EXECUTE REMOVEDIR Contents/Resources/9/95/
+EXECUTE REMOVEDIR Contents/Resources/9/94/
+EXECUTE REMOVEDIR Contents/Resources/9/94/
+EXECUTE REMOVEDIR Contents/Resources/9/93/
+EXECUTE REMOVEDIR Contents/Resources/9/92/
+EXECUTE REMOVEDIR Contents/Resources/9/91/
+EXECUTE REMOVEDIR Contents/Resources/9/90/
+EXECUTE REMOVEDIR Contents/Resources/9/90/
+EXECUTE REMOVEDIR Contents/Resources/8/89/
+EXECUTE REMOVEDIR Contents/Resources/8/89/
+EXECUTE REMOVEDIR Contents/Resources/8/88/
+EXECUTE REMOVEFILE Contents/Resources/8/87/870/87xtext0
+EXECUTE REMOVEFILE Contents/Resources/8/87/870/87xtext1
+EXECUTE REMOVEDIR Contents/Resources/8/87/870/
+EXECUTE REMOVEFILE Contents/Resources/8/87/871/87xtext0
+EXECUTE REMOVEFILE Contents/Resources/8/87/871/87xtext1
+EXECUTE REMOVEDIR Contents/Resources/8/87/871/
+EXECUTE REMOVEDIR Contents/Resources/8/87/
+EXECUTE REMOVEFILE Contents/Resources/8/86/86text0
+EXECUTE REMOVEFILE Contents/Resources/8/86/86text1
+EXECUTE REMOVEDIR Contents/Resources/8/86/
+EXECUTE REMOVEDIR Contents/Resources/8/85/
+EXECUTE REMOVEDIR Contents/Resources/8/85/
+EXECUTE REMOVEDIR Contents/Resources/8/84/
+EXECUTE REMOVEDIR Contents/Resources/8/84/
+EXECUTE REMOVEDIR Contents/Resources/8/83/
+EXECUTE REMOVEDIR Contents/Resources/8/82/
+EXECUTE REMOVEDIR Contents/Resources/8/81/
+EXECUTE REMOVEDIR Contents/Resources/8/80/
+EXECUTE REMOVEDIR Contents/Resources/8/80/
+EXECUTE REMOVEFILE Contents/Resources/7/70/7xtest.exe
+EXECUTE REMOVEFILE Contents/Resources/7/70/7xtext0
+EXECUTE REMOVEFILE Contents/Resources/7/70/7xtext1
+EXECUTE REMOVEDIR Contents/Resources/7/70/
+EXECUTE REMOVEFILE Contents/Resources/7/71/7xtest.exe
+EXECUTE REMOVEFILE Contents/Resources/7/71/7xtext0
+EXECUTE REMOVEFILE Contents/Resources/7/71/7xtext1
+EXECUTE REMOVEDIR Contents/Resources/7/71/
+EXECUTE REMOVEFILE Contents/Resources/7/7text0
+EXECUTE REMOVEFILE Contents/Resources/7/7text1
+EXECUTE REMOVEDIR Contents/Resources/7/
+EXECUTE REMOVEDIR Contents/Resources/6/
+EXECUTE REMOVEFILE Contents/Resources/5/5text1
+EXECUTE REMOVEFILE Contents/Resources/5/5text0
+EXECUTE REMOVEFILE Contents/Resources/5/5test.exe
+EXECUTE REMOVEFILE Contents/Resources/5/5text0
+file cannot be removed because it does not exist; skipping
+EXECUTE REMOVEFILE Contents/Resources/5/5text1
+file cannot be removed because it does not exist; skipping
+EXECUTE REMOVEDIR Contents/Resources/5/
+EXECUTE REMOVEFILE Contents/Resources/4/4text1
+EXECUTE REMOVEFILE Contents/Resources/4/4text0
+EXECUTE REMOVEDIR Contents/Resources/4/
+EXECUTE REMOVEFILE Contents/Resources/3/3text1
+EXECUTE REMOVEFILE Contents/Resources/3/3text0
+FINISH REMOVEFILE Contents/Resources/searchplugins/searchpluginstext0
+FINISH REMOVEFILE Contents/Resources/searchplugins/searchpluginspng0.png
+FINISH REMOVEFILE Contents/Resources/removed-files
+FINISH REMOVEFILE Contents/Resources/precomplete
+FINISH REMOVEFILE Contents/Resources/2/20/20text0
+FINISH REMOVEFILE Contents/Resources/2/20/20png0.png
+FINISH REMOVEFILE Contents/Resources/0/0exe0.exe
+FINISH REMOVEFILE Contents/Resources/0/00/00text0
+FINISH REMOVEFILE Contents/MacOS/exe0.exe
+FINISH REMOVEDIR Contents/Resources/searchplugins/
+removing directory: Contents/Resources/searchplugins/, rv: 0
+FINISH REMOVEDIR Contents/Resources/defaults/pref/
+removing directory: Contents/Resources/defaults/pref/, rv: 0
+FINISH REMOVEDIR Contents/Resources/defaults/
+removing directory: Contents/Resources/defaults/, rv: 0
+FINISH REMOVEDIR Contents/Resources/2/20/
+FINISH REMOVEDIR Contents/Resources/2/
+FINISH REMOVEDIR Contents/Resources/0/00/
+removing directory: Contents/Resources/0/00/, rv: 0
+FINISH REMOVEDIR Contents/Resources/0/
+removing directory: Contents/Resources/0/, rv: 0
+FINISH REMOVEDIR Contents/Resources/
+removing directory: Contents/Resources/, rv: 0
+FINISH REMOVEDIR Contents/MacOS/
+removing directory: Contents/MacOS/, rv: 0
+FINISH REMOVEDIR Contents/
+removing directory: Contents/, rv: 0
+FINISH ADD Contents/Resources/searchplugins/searchpluginstext0
+FINISH ADD Contents/Resources/searchplugins/searchpluginspng1.png
+FINISH ADD Contents/Resources/searchplugins/searchpluginspng0.png
+FINISH ADD Contents/Resources/removed-files
+FINISH ADD Contents/Resources/precomplete
+FINISH ADD Contents/Resources/distribution/extensions/extensions1/extensions1text0
+FINISH ADD Contents/Resources/distribution/extensions/extensions1/extensions1png1.png
+FINISH ADD Contents/Resources/distribution/extensions/extensions1/extensions1png0.png
+FINISH ADD Contents/Resources/distribution/extensions/extensions0/extensions0text0
+FINISH ADD Contents/Resources/distribution/extensions/extensions0/extensions0png1.png
+FINISH ADD Contents/Resources/distribution/extensions/extensions0/extensions0png0.png
+FINISH ADD Contents/Resources/1/10/10text0
+FINISH ADD Contents/Resources/0/0exe0.exe
+FINISH ADD Contents/Resources/0/00/00text1
+FINISH ADD Contents/Resources/0/00/00text0
+FINISH ADD Contents/Resources/0/00/00png0.png
+FINISH ADD Contents/MacOS/exe0.exe
+FINISH REMOVEDIR Contents/Resources/9/99/
+FINISH REMOVEDIR Contents/Resources/9/99/
+directory no longer exists; skipping
+FINISH REMOVEDIR Contents/Resources/9/98/
+FINISH REMOVEFILE Contents/Resources/9/97/970/97xtext0
+FINISH REMOVEFILE Contents/Resources/9/97/970/97xtext1
+FINISH REMOVEDIR Contents/Resources/9/97/970/
+FINISH REMOVEFILE Contents/Resources/9/97/971/97xtext0
+FINISH REMOVEFILE Contents/Resources/9/97/971/97xtext1
+FINISH REMOVEDIR Contents/Resources/9/97/971/
+FINISH REMOVEDIR Contents/Resources/9/97/
+FINISH REMOVEFILE Contents/Resources/9/96/96text0
+FINISH REMOVEFILE Contents/Resources/9/96/96text1
+FINISH REMOVEDIR Contents/Resources/9/96/
+FINISH REMOVEDIR Contents/Resources/9/95/
+FINISH REMOVEDIR Contents/Resources/9/95/
+directory no longer exists; skipping
+FINISH REMOVEDIR Contents/Resources/9/94/
+FINISH REMOVEDIR Contents/Resources/9/94/
+directory no longer exists; skipping
+FINISH REMOVEDIR Contents/Resources/9/93/
+FINISH REMOVEDIR Contents/Resources/9/92/
+removing directory: Contents/Resources/9/92/, rv: 0
+FINISH REMOVEDIR Contents/Resources/9/91/
+removing directory: Contents/Resources/9/91/, rv: 0
+FINISH REMOVEDIR Contents/Resources/9/90/
+FINISH REMOVEDIR Contents/Resources/9/90/
+directory no longer exists; skipping
+FINISH REMOVEDIR Contents/Resources/8/89/
+FINISH REMOVEDIR Contents/Resources/8/89/
+directory no longer exists; skipping
+FINISH REMOVEDIR Contents/Resources/8/88/
+FINISH REMOVEFILE Contents/Resources/8/87/870/87xtext0
+FINISH REMOVEFILE Contents/Resources/8/87/870/87xtext1
+FINISH REMOVEDIR Contents/Resources/8/87/870/
+FINISH REMOVEFILE Contents/Resources/8/87/871/87xtext0
+FINISH REMOVEFILE Contents/Resources/8/87/871/87xtext1
+FINISH REMOVEDIR Contents/Resources/8/87/871/
+FINISH REMOVEDIR Contents/Resources/8/87/
+FINISH REMOVEFILE Contents/Resources/8/86/86text0
+FINISH REMOVEFILE Contents/Resources/8/86/86text1
+FINISH REMOVEDIR Contents/Resources/8/86/
+FINISH REMOVEDIR Contents/Resources/8/85/
+FINISH REMOVEDIR Contents/Resources/8/85/
+directory no longer exists; skipping
+FINISH REMOVEDIR Contents/Resources/8/84/
+FINISH REMOVEDIR Contents/Resources/8/84/
+directory no longer exists; skipping
+FINISH REMOVEDIR Contents/Resources/8/83/
+FINISH REMOVEDIR Contents/Resources/8/82/
+removing directory: Contents/Resources/8/82/, rv: 0
+FINISH REMOVEDIR Contents/Resources/8/81/
+removing directory: Contents/Resources/8/81/, rv: 0
+FINISH REMOVEDIR Contents/Resources/8/80/
+FINISH REMOVEDIR Contents/Resources/8/80/
+directory no longer exists; skipping
+FINISH REMOVEFILE Contents/Resources/7/70/7xtest.exe
+FINISH REMOVEFILE Contents/Resources/7/70/7xtext0
+FINISH REMOVEFILE Contents/Resources/7/70/7xtext1
+FINISH REMOVEDIR Contents/Resources/7/70/
+FINISH REMOVEFILE Contents/Resources/7/71/7xtest.exe
+FINISH REMOVEFILE Contents/Resources/7/71/7xtext0
+FINISH REMOVEFILE Contents/Resources/7/71/7xtext1
+FINISH REMOVEDIR Contents/Resources/7/71/
+FINISH REMOVEFILE Contents/Resources/7/7text0
+FINISH REMOVEFILE Contents/Resources/7/7text1
+FINISH REMOVEDIR Contents/Resources/7/
+FINISH REMOVEDIR Contents/Resources/6/
+FINISH REMOVEFILE Contents/Resources/5/5text1
+FINISH REMOVEFILE Contents/Resources/5/5text0
+FINISH REMOVEFILE Contents/Resources/5/5test.exe
+FINISH REMOVEDIR Contents/Resources/5/
+FINISH REMOVEFILE Contents/Resources/4/4text1
+FINISH REMOVEFILE Contents/Resources/4/4text0
+FINISH REMOVEDIR Contents/Resources/4/
+FINISH REMOVEFILE Contents/Resources/3/3text1
+FINISH REMOVEFILE Contents/Resources/3/3text0
+succeeded
+calling QuitProgressUI
diff --git a/toolkit/mozapps/update/tests/data/complete_log_success_win b/toolkit/mozapps/update/tests/data/complete_log_success_win
new file mode 100644
index 000000000..c5a03dc9d
--- /dev/null
+++ b/toolkit/mozapps/update/tests/data/complete_log_success_win
@@ -0,0 +1,320 @@
+UPDATE TYPE complete
+PREPARE REMOVEFILE searchplugins/searchpluginstext0
+PREPARE REMOVEFILE searchplugins/searchpluginspng0.png
+PREPARE REMOVEFILE removed-files
+PREPARE REMOVEFILE precomplete
+PREPARE REMOVEFILE exe0.exe
+PREPARE REMOVEFILE 2/20/20text0
+PREPARE REMOVEFILE 2/20/20png0.png
+PREPARE REMOVEFILE 0/0exe0.exe
+PREPARE REMOVEFILE 0/00/00text0
+PREPARE REMOVEDIR searchplugins/
+PREPARE REMOVEDIR defaults/pref/
+PREPARE REMOVEDIR defaults/
+PREPARE REMOVEDIR 2/20/
+PREPARE REMOVEDIR 2/
+PREPARE REMOVEDIR 0/00/
+PREPARE REMOVEDIR 0/
+PREPARE ADD searchplugins/searchpluginstext0
+PREPARE ADD searchplugins/searchpluginspng1.png
+PREPARE ADD searchplugins/searchpluginspng0.png
+PREPARE ADD removed-files
+PREPARE ADD precomplete
+PREPARE ADD exe0.exe
+PREPARE ADD distribution/extensions/extensions1/extensions1text0
+PREPARE ADD distribution/extensions/extensions1/extensions1png1.png
+PREPARE ADD distribution/extensions/extensions1/extensions1png0.png
+PREPARE ADD distribution/extensions/extensions0/extensions0text0
+PREPARE ADD distribution/extensions/extensions0/extensions0png1.png
+PREPARE ADD distribution/extensions/extensions0/extensions0png0.png
+PREPARE ADD 1/10/10text0
+PREPARE ADD 0/0exe0.exe
+PREPARE ADD 0/00/00text1
+PREPARE ADD 0/00/00text0
+PREPARE ADD 0/00/00png0.png
+PREPARE REMOVEDIR 9/99/
+PREPARE REMOVEDIR 9/99/
+PREPARE REMOVEDIR 9/98/
+PREPARE REMOVEFILE 9/97/970/97xtext0
+PREPARE REMOVEFILE 9/97/970/97xtext1
+PREPARE REMOVEDIR 9/97/970/
+PREPARE REMOVEFILE 9/97/971/97xtext0
+PREPARE REMOVEFILE 9/97/971/97xtext1
+PREPARE REMOVEDIR 9/97/971/
+PREPARE REMOVEDIR 9/97/
+PREPARE REMOVEFILE 9/96/96text0
+PREPARE REMOVEFILE 9/96/96text1
+PREPARE REMOVEDIR 9/96/
+PREPARE REMOVEDIR 9/95/
+PREPARE REMOVEDIR 9/95/
+PREPARE REMOVEDIR 9/94/
+PREPARE REMOVEDIR 9/94/
+PREPARE REMOVEDIR 9/93/
+PREPARE REMOVEDIR 9/92/
+PREPARE REMOVEDIR 9/91/
+PREPARE REMOVEDIR 9/90/
+PREPARE REMOVEDIR 9/90/
+PREPARE REMOVEDIR 8/89/
+PREPARE REMOVEDIR 8/89/
+PREPARE REMOVEDIR 8/88/
+PREPARE REMOVEFILE 8/87/870/87xtext0
+PREPARE REMOVEFILE 8/87/870/87xtext1
+PREPARE REMOVEDIR 8/87/870/
+PREPARE REMOVEFILE 8/87/871/87xtext0
+PREPARE REMOVEFILE 8/87/871/87xtext1
+PREPARE REMOVEDIR 8/87/871/
+PREPARE REMOVEDIR 8/87/
+PREPARE REMOVEFILE 8/86/86text0
+PREPARE REMOVEFILE 8/86/86text1
+PREPARE REMOVEDIR 8/86/
+PREPARE REMOVEDIR 8/85/
+PREPARE REMOVEDIR 8/85/
+PREPARE REMOVEDIR 8/84/
+PREPARE REMOVEDIR 8/84/
+PREPARE REMOVEDIR 8/83/
+PREPARE REMOVEDIR 8/82/
+PREPARE REMOVEDIR 8/81/
+PREPARE REMOVEDIR 8/80/
+PREPARE REMOVEDIR 8/80/
+PREPARE REMOVEFILE 7/70/7xtest.exe
+PREPARE REMOVEFILE 7/70/7xtext0
+PREPARE REMOVEFILE 7/70/7xtext1
+PREPARE REMOVEDIR 7/70/
+PREPARE REMOVEFILE 7/71/7xtest.exe
+PREPARE REMOVEFILE 7/71/7xtext0
+PREPARE REMOVEFILE 7/71/7xtext1
+PREPARE REMOVEDIR 7/71/
+PREPARE REMOVEFILE 7/7text0
+PREPARE REMOVEFILE 7/7text1
+PREPARE REMOVEDIR 7/
+PREPARE REMOVEDIR 6/
+PREPARE REMOVEFILE 5/5text1
+PREPARE REMOVEFILE 5/5text0
+PREPARE REMOVEFILE 5/5test.exe
+PREPARE REMOVEFILE 5/5text0
+PREPARE REMOVEFILE 5/5text1
+PREPARE REMOVEDIR 5/
+PREPARE REMOVEFILE 4/4text1
+PREPARE REMOVEFILE 4/4text0
+PREPARE REMOVEDIR 4/
+PREPARE REMOVEFILE 3/3text1
+PREPARE REMOVEFILE 3/3text0
+EXECUTE REMOVEFILE searchplugins/searchpluginstext0
+EXECUTE REMOVEFILE searchplugins/searchpluginspng0.png
+EXECUTE REMOVEFILE removed-files
+EXECUTE REMOVEFILE precomplete
+EXECUTE REMOVEFILE exe0.exe
+EXECUTE REMOVEFILE 2/20/20text0
+EXECUTE REMOVEFILE 2/20/20png0.png
+EXECUTE REMOVEFILE 0/0exe0.exe
+EXECUTE REMOVEFILE 0/00/00text0
+EXECUTE REMOVEDIR searchplugins/
+EXECUTE REMOVEDIR defaults/pref/
+EXECUTE REMOVEDIR defaults/
+EXECUTE REMOVEDIR 2/20/
+EXECUTE REMOVEDIR 2/
+EXECUTE REMOVEDIR 0/00/
+EXECUTE REMOVEDIR 0/
+EXECUTE ADD searchplugins/searchpluginstext0
+EXECUTE ADD searchplugins/searchpluginspng1.png
+EXECUTE ADD searchplugins/searchpluginspng0.png
+EXECUTE ADD removed-files
+EXECUTE ADD precomplete
+EXECUTE ADD exe0.exe
+EXECUTE ADD distribution/extensions/extensions1/extensions1text0
+EXECUTE ADD distribution/extensions/extensions1/extensions1png1.png
+EXECUTE ADD distribution/extensions/extensions1/extensions1png0.png
+EXECUTE ADD distribution/extensions/extensions0/extensions0text0
+EXECUTE ADD distribution/extensions/extensions0/extensions0png1.png
+EXECUTE ADD distribution/extensions/extensions0/extensions0png0.png
+EXECUTE ADD 1/10/10text0
+EXECUTE ADD 0/0exe0.exe
+EXECUTE ADD 0/00/00text1
+EXECUTE ADD 0/00/00text0
+EXECUTE ADD 0/00/00png0.png
+EXECUTE REMOVEDIR 9/99/
+EXECUTE REMOVEDIR 9/99/
+EXECUTE REMOVEDIR 9/98/
+EXECUTE REMOVEFILE 9/97/970/97xtext0
+EXECUTE REMOVEFILE 9/97/970/97xtext1
+EXECUTE REMOVEDIR 9/97/970/
+EXECUTE REMOVEFILE 9/97/971/97xtext0
+EXECUTE REMOVEFILE 9/97/971/97xtext1
+EXECUTE REMOVEDIR 9/97/971/
+EXECUTE REMOVEDIR 9/97/
+EXECUTE REMOVEFILE 9/96/96text0
+EXECUTE REMOVEFILE 9/96/96text1
+EXECUTE REMOVEDIR 9/96/
+EXECUTE REMOVEDIR 9/95/
+EXECUTE REMOVEDIR 9/95/
+EXECUTE REMOVEDIR 9/94/
+EXECUTE REMOVEDIR 9/94/
+EXECUTE REMOVEDIR 9/93/
+EXECUTE REMOVEDIR 9/92/
+EXECUTE REMOVEDIR 9/91/
+EXECUTE REMOVEDIR 9/90/
+EXECUTE REMOVEDIR 9/90/
+EXECUTE REMOVEDIR 8/89/
+EXECUTE REMOVEDIR 8/89/
+EXECUTE REMOVEDIR 8/88/
+EXECUTE REMOVEFILE 8/87/870/87xtext0
+EXECUTE REMOVEFILE 8/87/870/87xtext1
+EXECUTE REMOVEDIR 8/87/870/
+EXECUTE REMOVEFILE 8/87/871/87xtext0
+EXECUTE REMOVEFILE 8/87/871/87xtext1
+EXECUTE REMOVEDIR 8/87/871/
+EXECUTE REMOVEDIR 8/87/
+EXECUTE REMOVEFILE 8/86/86text0
+EXECUTE REMOVEFILE 8/86/86text1
+EXECUTE REMOVEDIR 8/86/
+EXECUTE REMOVEDIR 8/85/
+EXECUTE REMOVEDIR 8/85/
+EXECUTE REMOVEDIR 8/84/
+EXECUTE REMOVEDIR 8/84/
+EXECUTE REMOVEDIR 8/83/
+EXECUTE REMOVEDIR 8/82/
+EXECUTE REMOVEDIR 8/81/
+EXECUTE REMOVEDIR 8/80/
+EXECUTE REMOVEDIR 8/80/
+EXECUTE REMOVEFILE 7/70/7xtest.exe
+EXECUTE REMOVEFILE 7/70/7xtext0
+EXECUTE REMOVEFILE 7/70/7xtext1
+EXECUTE REMOVEDIR 7/70/
+EXECUTE REMOVEFILE 7/71/7xtest.exe
+EXECUTE REMOVEFILE 7/71/7xtext0
+EXECUTE REMOVEFILE 7/71/7xtext1
+EXECUTE REMOVEDIR 7/71/
+EXECUTE REMOVEFILE 7/7text0
+EXECUTE REMOVEFILE 7/7text1
+EXECUTE REMOVEDIR 7/
+EXECUTE REMOVEDIR 6/
+EXECUTE REMOVEFILE 5/5text1
+EXECUTE REMOVEFILE 5/5text0
+EXECUTE REMOVEFILE 5/5test.exe
+EXECUTE REMOVEFILE 5/5text0
+file cannot be removed because it does not exist; skipping
+EXECUTE REMOVEFILE 5/5text1
+file cannot be removed because it does not exist; skipping
+EXECUTE REMOVEDIR 5/
+EXECUTE REMOVEFILE 4/4text1
+EXECUTE REMOVEFILE 4/4text0
+EXECUTE REMOVEDIR 4/
+EXECUTE REMOVEFILE 3/3text1
+EXECUTE REMOVEFILE 3/3text0
+FINISH REMOVEFILE searchplugins/searchpluginstext0
+FINISH REMOVEFILE searchplugins/searchpluginspng0.png
+FINISH REMOVEFILE removed-files
+FINISH REMOVEFILE precomplete
+FINISH REMOVEFILE exe0.exe
+FINISH REMOVEFILE 2/20/20text0
+FINISH REMOVEFILE 2/20/20png0.png
+FINISH REMOVEFILE 0/0exe0.exe
+FINISH REMOVEFILE 0/00/00text0
+FINISH REMOVEDIR searchplugins/
+removing directory: searchplugins/, rv: 0
+FINISH REMOVEDIR defaults/pref/
+removing directory: defaults/pref/, rv: 0
+FINISH REMOVEDIR defaults/
+removing directory: defaults/, rv: 0
+FINISH REMOVEDIR 2/20/
+FINISH REMOVEDIR 2/
+FINISH REMOVEDIR 0/00/
+removing directory: 0/00/, rv: 0
+FINISH REMOVEDIR 0/
+removing directory: 0/, rv: 0
+FINISH ADD searchplugins/searchpluginstext0
+FINISH ADD searchplugins/searchpluginspng1.png
+FINISH ADD searchplugins/searchpluginspng0.png
+FINISH ADD removed-files
+FINISH ADD precomplete
+FINISH ADD exe0.exe
+FINISH ADD distribution/extensions/extensions1/extensions1text0
+FINISH ADD distribution/extensions/extensions1/extensions1png1.png
+FINISH ADD distribution/extensions/extensions1/extensions1png0.png
+FINISH ADD distribution/extensions/extensions0/extensions0text0
+FINISH ADD distribution/extensions/extensions0/extensions0png1.png
+FINISH ADD distribution/extensions/extensions0/extensions0png0.png
+FINISH ADD 1/10/10text0
+FINISH ADD 0/0exe0.exe
+FINISH ADD 0/00/00text1
+FINISH ADD 0/00/00text0
+FINISH ADD 0/00/00png0.png
+FINISH REMOVEDIR 9/99/
+FINISH REMOVEDIR 9/99/
+directory no longer exists; skipping
+FINISH REMOVEDIR 9/98/
+FINISH REMOVEFILE 9/97/970/97xtext0
+FINISH REMOVEFILE 9/97/970/97xtext1
+FINISH REMOVEDIR 9/97/970/
+FINISH REMOVEFILE 9/97/971/97xtext0
+FINISH REMOVEFILE 9/97/971/97xtext1
+FINISH REMOVEDIR 9/97/971/
+FINISH REMOVEDIR 9/97/
+FINISH REMOVEFILE 9/96/96text0
+FINISH REMOVEFILE 9/96/96text1
+FINISH REMOVEDIR 9/96/
+FINISH REMOVEDIR 9/95/
+FINISH REMOVEDIR 9/95/
+directory no longer exists; skipping
+FINISH REMOVEDIR 9/94/
+FINISH REMOVEDIR 9/94/
+directory no longer exists; skipping
+FINISH REMOVEDIR 9/93/
+FINISH REMOVEDIR 9/92/
+removing directory: 9/92/, rv: 0
+FINISH REMOVEDIR 9/91/
+removing directory: 9/91/, rv: 0
+FINISH REMOVEDIR 9/90/
+FINISH REMOVEDIR 9/90/
+directory no longer exists; skipping
+FINISH REMOVEDIR 8/89/
+FINISH REMOVEDIR 8/89/
+directory no longer exists; skipping
+FINISH REMOVEDIR 8/88/
+FINISH REMOVEFILE 8/87/870/87xtext0
+FINISH REMOVEFILE 8/87/870/87xtext1
+FINISH REMOVEDIR 8/87/870/
+FINISH REMOVEFILE 8/87/871/87xtext0
+FINISH REMOVEFILE 8/87/871/87xtext1
+FINISH REMOVEDIR 8/87/871/
+FINISH REMOVEDIR 8/87/
+FINISH REMOVEFILE 8/86/86text0
+FINISH REMOVEFILE 8/86/86text1
+FINISH REMOVEDIR 8/86/
+FINISH REMOVEDIR 8/85/
+FINISH REMOVEDIR 8/85/
+directory no longer exists; skipping
+FINISH REMOVEDIR 8/84/
+FINISH REMOVEDIR 8/84/
+directory no longer exists; skipping
+FINISH REMOVEDIR 8/83/
+FINISH REMOVEDIR 8/82/
+removing directory: 8/82/, rv: 0
+FINISH REMOVEDIR 8/81/
+removing directory: 8/81/, rv: 0
+FINISH REMOVEDIR 8/80/
+FINISH REMOVEDIR 8/80/
+directory no longer exists; skipping
+FINISH REMOVEFILE 7/70/7xtest.exe
+FINISH REMOVEFILE 7/70/7xtext0
+FINISH REMOVEFILE 7/70/7xtext1
+FINISH REMOVEDIR 7/70/
+FINISH REMOVEFILE 7/71/7xtest.exe
+FINISH REMOVEFILE 7/71/7xtext0
+FINISH REMOVEFILE 7/71/7xtext1
+FINISH REMOVEDIR 7/71/
+FINISH REMOVEFILE 7/7text0
+FINISH REMOVEFILE 7/7text1
+FINISH REMOVEDIR 7/
+FINISH REMOVEDIR 6/
+FINISH REMOVEFILE 5/5text1
+FINISH REMOVEFILE 5/5text0
+FINISH REMOVEFILE 5/5test.exe
+FINISH REMOVEDIR 5/
+FINISH REMOVEFILE 4/4text1
+FINISH REMOVEFILE 4/4text0
+FINISH REMOVEDIR 4/
+FINISH REMOVEFILE 3/3text1
+FINISH REMOVEFILE 3/3text0
+succeeded
+calling QuitProgressUI
diff --git a/toolkit/mozapps/update/tests/data/complete_mac.mar b/toolkit/mozapps/update/tests/data/complete_mac.mar
new file mode 100644
index 000000000..ca1497f4f
--- /dev/null
+++ b/toolkit/mozapps/update/tests/data/complete_mac.mar
Binary files differ
diff --git a/toolkit/mozapps/update/tests/data/complete_precomplete b/toolkit/mozapps/update/tests/data/complete_precomplete
new file mode 100644
index 000000000..ae7a0013f
--- /dev/null
+++ b/toolkit/mozapps/update/tests/data/complete_precomplete
@@ -0,0 +1,18 @@
+remove "searchplugins/searchpluginstext0"
+remove "searchplugins/searchpluginspng1.png"
+remove "searchplugins/searchpluginspng0.png"
+remove "removed-files"
+remove "precomplete"
+remove "exe0.exe"
+remove "1/10/10text0"
+remove "0/0exe0.exe"
+remove "0/00/00text1"
+remove "0/00/00text0"
+remove "0/00/00png0.png"
+rmdir "searchplugins/"
+rmdir "defaults/pref/"
+rmdir "defaults/"
+rmdir "1/10/"
+rmdir "1/"
+rmdir "0/00/"
+rmdir "0/"
diff --git a/toolkit/mozapps/update/tests/data/complete_precomplete_mac b/toolkit/mozapps/update/tests/data/complete_precomplete_mac
new file mode 100644
index 000000000..8d81a36d6
--- /dev/null
+++ b/toolkit/mozapps/update/tests/data/complete_precomplete_mac
@@ -0,0 +1,21 @@
+remove "Contents/Resources/searchplugins/searchpluginstext0"
+remove "Contents/Resources/searchplugins/searchpluginspng1.png"
+remove "Contents/Resources/searchplugins/searchpluginspng0.png"
+remove "Contents/Resources/removed-files"
+remove "Contents/Resources/precomplete"
+remove "Contents/Resources/1/10/10text0"
+remove "Contents/Resources/0/0exe0.exe"
+remove "Contents/Resources/0/00/00text1"
+remove "Contents/Resources/0/00/00text0"
+remove "Contents/Resources/0/00/00png0.png"
+remove "Contents/MacOS/exe0.exe"
+rmdir "Contents/Resources/searchplugins/"
+rmdir "Contents/Resources/defaults/pref/"
+rmdir "Contents/Resources/defaults/"
+rmdir "Contents/Resources/1/10/"
+rmdir "Contents/Resources/1/"
+rmdir "Contents/Resources/0/00/"
+rmdir "Contents/Resources/0/"
+rmdir "Contents/Resources/"
+rmdir "Contents/MacOS/"
+rmdir "Contents/"
diff --git a/toolkit/mozapps/update/tests/data/complete_removed-files b/toolkit/mozapps/update/tests/data/complete_removed-files
new file mode 100644
index 000000000..e45c43c1f
--- /dev/null
+++ b/toolkit/mozapps/update/tests/data/complete_removed-files
@@ -0,0 +1,41 @@
+text0
+text1
+3/3text0
+3/3text1
+4/exe0.exe
+4/4text0
+4/4text1
+4/
+5/5text0
+5/5text1
+5/*
+6/
+7/*
+8/80/
+8/81/
+8/82/
+8/83/
+8/84/
+8/85/*
+8/86/*
+8/87/*
+8/88/*
+8/89/*
+8/80/
+8/84/*
+8/85/*
+8/89/
+9/90/
+9/91/
+9/92/
+9/93/
+9/94/
+9/95/*
+9/96/*
+9/97/*
+9/98/*
+9/99/*
+9/90/
+9/94/*
+9/95/*
+9/99/
diff --git a/toolkit/mozapps/update/tests/data/complete_removed-files_mac b/toolkit/mozapps/update/tests/data/complete_removed-files_mac
new file mode 100644
index 000000000..955dc5b34
--- /dev/null
+++ b/toolkit/mozapps/update/tests/data/complete_removed-files_mac
@@ -0,0 +1,41 @@
+Contents/Resources/text0
+Contents/Resources/text1
+Contents/Resources/3/3text0
+Contents/Resources/3/3text1
+Contents/Resources/4/exe0.exe
+Contents/Resources/4/4text0
+Contents/Resources/4/4text1
+Contents/Resources/4/
+Contents/Resources/5/5text0
+Contents/Resources/5/5text1
+Contents/Resources/5/*
+Contents/Resources/6/
+Contents/Resources/7/*
+Contents/Resources/8/80/
+Contents/Resources/8/81/
+Contents/Resources/8/82/
+Contents/Resources/8/83/
+Contents/Resources/8/84/
+Contents/Resources/8/85/*
+Contents/Resources/8/86/*
+Contents/Resources/8/87/*
+Contents/Resources/8/88/*
+Contents/Resources/8/89/*
+Contents/Resources/8/80/
+Contents/Resources/8/84/*
+Contents/Resources/8/85/*
+Contents/Resources/8/89/
+Contents/Resources/9/90/
+Contents/Resources/9/91/
+Contents/Resources/9/92/
+Contents/Resources/9/93/
+Contents/Resources/9/94/
+Contents/Resources/9/95/*
+Contents/Resources/9/96/*
+Contents/Resources/9/97/*
+Contents/Resources/9/98/*
+Contents/Resources/9/99/*
+Contents/Resources/9/90/
+Contents/Resources/9/94/*
+Contents/Resources/9/95/*
+Contents/Resources/9/99/
diff --git a/toolkit/mozapps/update/tests/data/complete_update_manifest b/toolkit/mozapps/update/tests/data/complete_update_manifest
new file mode 100644
index 000000000..383a324f6
--- /dev/null
+++ b/toolkit/mozapps/update/tests/data/complete_update_manifest
@@ -0,0 +1,59 @@
+type "complete"
+add "precomplete"
+add "searchplugins/searchpluginstext0"
+add "searchplugins/searchpluginspng1.png"
+add "searchplugins/searchpluginspng0.png"
+add "removed-files"
+add-if "extensions/extensions1" "extensions/extensions1/extensions1text0"
+add-if "extensions/extensions1" "extensions/extensions1/extensions1png1.png"
+add-if "extensions/extensions1" "extensions/extensions1/extensions1png0.png"
+add-if "extensions/extensions0" "extensions/extensions0/extensions0text0"
+add-if "extensions/extensions0" "extensions/extensions0/extensions0png1.png"
+add-if "extensions/extensions0" "extensions/extensions0/extensions0png0.png"
+add "exe0.exe"
+add "1/10/10text0"
+add "0/0exe0.exe"
+add "0/00/00text1"
+add "0/00/00text0"
+add "0/00/00png0.png"
+remove "text1"
+remove "text0"
+rmrfdir "9/99/"
+rmdir "9/99/"
+rmrfdir "9/98/"
+rmrfdir "9/97/"
+rmrfdir "9/96/"
+rmrfdir "9/95/"
+rmrfdir "9/95/"
+rmrfdir "9/94/"
+rmdir "9/94/"
+rmdir "9/93/"
+rmdir "9/92/"
+rmdir "9/91/"
+rmdir "9/90/"
+rmdir "9/90/"
+rmrfdir "8/89/"
+rmdir "8/89/"
+rmrfdir "8/88/"
+rmrfdir "8/87/"
+rmrfdir "8/86/"
+rmrfdir "8/85/"
+rmrfdir "8/85/"
+rmrfdir "8/84/"
+rmdir "8/84/"
+rmdir "8/83/"
+rmdir "8/82/"
+rmdir "8/81/"
+rmdir "8/80/"
+rmdir "8/80/"
+rmrfdir "7/"
+rmdir "6/"
+remove "5/5text1"
+remove "5/5text0"
+rmrfdir "5/"
+remove "4/exe0.exe"
+remove "4/4text1"
+remove "4/4text0"
+rmdir "4/"
+remove "3/3text1"
+remove "3/3text0"
diff --git a/toolkit/mozapps/update/tests/data/old_version.mar b/toolkit/mozapps/update/tests/data/old_version.mar
new file mode 100644
index 000000000..31550698a
--- /dev/null
+++ b/toolkit/mozapps/update/tests/data/old_version.mar
Binary files differ
diff --git a/toolkit/mozapps/update/tests/data/partial.exe b/toolkit/mozapps/update/tests/data/partial.exe
new file mode 100644
index 000000000..3949fd2a0
--- /dev/null
+++ b/toolkit/mozapps/update/tests/data/partial.exe
Binary files differ
diff --git a/toolkit/mozapps/update/tests/data/partial.mar b/toolkit/mozapps/update/tests/data/partial.mar
new file mode 100644
index 000000000..789d3d98d
--- /dev/null
+++ b/toolkit/mozapps/update/tests/data/partial.mar
Binary files differ
diff --git a/toolkit/mozapps/update/tests/data/partial.png b/toolkit/mozapps/update/tests/data/partial.png
new file mode 100644
index 000000000..9246f586c
--- /dev/null
+++ b/toolkit/mozapps/update/tests/data/partial.png
Binary files differ
diff --git a/toolkit/mozapps/update/tests/data/partial_log_failure_mac b/toolkit/mozapps/update/tests/data/partial_log_failure_mac
new file mode 100644
index 000000000..3b2933ebd
--- /dev/null
+++ b/toolkit/mozapps/update/tests/data/partial_log_failure_mac
@@ -0,0 +1,192 @@
+UPDATE TYPE partial
+PREPARE ADD Contents/Resources/searchplugins/searchpluginstext0
+PREPARE PATCH Contents/Resources/searchplugins/searchpluginspng1.png
+PREPARE PATCH Contents/Resources/searchplugins/searchpluginspng0.png
+PREPARE ADD Contents/Resources/precomplete
+PREPARE ADD Contents/Resources/distribution/extensions/extensions1/extensions1text0
+PREPARE PATCH Contents/Resources/distribution/extensions/extensions1/extensions1png1.png
+PREPARE PATCH Contents/Resources/distribution/extensions/extensions1/extensions1png0.png
+PREPARE ADD Contents/Resources/distribution/extensions/extensions0/extensions0text0
+PREPARE PATCH Contents/Resources/distribution/extensions/extensions0/extensions0png1.png
+PREPARE PATCH Contents/Resources/distribution/extensions/extensions0/extensions0png0.png
+PREPARE PATCH Contents/Resources/0/0exe0.exe
+PREPARE ADD Contents/Resources/0/00/00text0
+PREPARE PATCH Contents/Resources/0/00/00png0.png
+PREPARE PATCH Contents/MacOS/exe0.exe
+PREPARE ADD Contents/Resources/2/20/20text0
+PREPARE ADD Contents/Resources/2/20/20png0.png
+PREPARE ADD Contents/Resources/0/00/00text2
+PREPARE REMOVEFILE Contents/Resources/1/10/10text0
+PREPARE REMOVEFILE Contents/Resources/0/00/00text1
+PREPARE REMOVEDIR Contents/Resources/9/99/
+PREPARE REMOVEDIR Contents/Resources/9/99/
+PREPARE REMOVEDIR Contents/Resources/9/98/
+PREPARE REMOVEFILE Contents/Resources/9/97/970/97xtext0
+PREPARE REMOVEFILE Contents/Resources/9/97/970/97xtext1
+PREPARE REMOVEDIR Contents/Resources/9/97/970/
+PREPARE REMOVEFILE Contents/Resources/9/97/971/97xtext0
+PREPARE REMOVEFILE Contents/Resources/9/97/971/97xtext1
+PREPARE REMOVEDIR Contents/Resources/9/97/971/
+PREPARE REMOVEDIR Contents/Resources/9/97/
+PREPARE REMOVEFILE Contents/Resources/9/96/96text0
+PREPARE REMOVEFILE Contents/Resources/9/96/96text1
+PREPARE REMOVEDIR Contents/Resources/9/96/
+PREPARE REMOVEDIR Contents/Resources/9/95/
+PREPARE REMOVEDIR Contents/Resources/9/95/
+PREPARE REMOVEDIR Contents/Resources/9/94/
+PREPARE REMOVEDIR Contents/Resources/9/94/
+PREPARE REMOVEDIR Contents/Resources/9/93/
+PREPARE REMOVEDIR Contents/Resources/9/92/
+PREPARE REMOVEDIR Contents/Resources/9/91/
+PREPARE REMOVEDIR Contents/Resources/9/90/
+PREPARE REMOVEDIR Contents/Resources/9/90/
+PREPARE REMOVEDIR Contents/Resources/8/89/
+PREPARE REMOVEDIR Contents/Resources/8/89/
+PREPARE REMOVEDIR Contents/Resources/8/88/
+PREPARE REMOVEFILE Contents/Resources/8/87/870/87xtext0
+PREPARE REMOVEFILE Contents/Resources/8/87/870/87xtext1
+PREPARE REMOVEDIR Contents/Resources/8/87/870/
+PREPARE REMOVEFILE Contents/Resources/8/87/871/87xtext0
+PREPARE REMOVEFILE Contents/Resources/8/87/871/87xtext1
+PREPARE REMOVEDIR Contents/Resources/8/87/871/
+PREPARE REMOVEDIR Contents/Resources/8/87/
+PREPARE REMOVEFILE Contents/Resources/8/86/86text0
+PREPARE REMOVEFILE Contents/Resources/8/86/86text1
+PREPARE REMOVEDIR Contents/Resources/8/86/
+PREPARE REMOVEDIR Contents/Resources/8/85/
+PREPARE REMOVEDIR Contents/Resources/8/85/
+PREPARE REMOVEDIR Contents/Resources/8/84/
+PREPARE REMOVEDIR Contents/Resources/8/84/
+PREPARE REMOVEDIR Contents/Resources/8/83/
+PREPARE REMOVEDIR Contents/Resources/8/82/
+PREPARE REMOVEDIR Contents/Resources/8/81/
+PREPARE REMOVEDIR Contents/Resources/8/80/
+PREPARE REMOVEDIR Contents/Resources/8/80/
+PREPARE REMOVEFILE Contents/Resources/7/70/7xtest.exe
+PREPARE REMOVEFILE Contents/Resources/7/70/7xtext0
+PREPARE REMOVEFILE Contents/Resources/7/70/7xtext1
+PREPARE REMOVEDIR Contents/Resources/7/70/
+PREPARE REMOVEFILE Contents/Resources/7/71/7xtest.exe
+PREPARE REMOVEFILE Contents/Resources/7/71/7xtext0
+PREPARE REMOVEFILE Contents/Resources/7/71/7xtext1
+PREPARE REMOVEDIR Contents/Resources/7/71/
+PREPARE REMOVEFILE Contents/Resources/7/7text0
+PREPARE REMOVEFILE Contents/Resources/7/7text1
+PREPARE REMOVEDIR Contents/Resources/7/
+PREPARE REMOVEDIR Contents/Resources/6/
+PREPARE REMOVEFILE Contents/Resources/5/5text1
+PREPARE REMOVEFILE Contents/Resources/5/5text0
+PREPARE REMOVEFILE Contents/Resources/5/5test.exe
+PREPARE REMOVEFILE Contents/Resources/5/5text0
+PREPARE REMOVEFILE Contents/Resources/5/5text1
+PREPARE REMOVEDIR Contents/Resources/5/
+PREPARE REMOVEFILE Contents/Resources/4/4text1
+PREPARE REMOVEFILE Contents/Resources/4/4text0
+PREPARE REMOVEDIR Contents/Resources/4/
+PREPARE REMOVEFILE Contents/Resources/3/3text1
+PREPARE REMOVEFILE Contents/Resources/3/3text0
+PREPARE REMOVEDIR Contents/Resources/1/10/
+PREPARE REMOVEDIR Contents/Resources/1/
+EXECUTE ADD Contents/Resources/searchplugins/searchpluginstext0
+EXECUTE PATCH Contents/Resources/searchplugins/searchpluginspng1.png
+EXECUTE PATCH Contents/Resources/searchplugins/searchpluginspng0.png
+EXECUTE ADD Contents/Resources/precomplete
+EXECUTE ADD Contents/Resources/distribution/extensions/extensions1/extensions1text0
+EXECUTE PATCH Contents/Resources/distribution/extensions/extensions1/extensions1png1.png
+EXECUTE PATCH Contents/Resources/distribution/extensions/extensions1/extensions1png0.png
+EXECUTE ADD Contents/Resources/distribution/extensions/extensions0/extensions0text0
+EXECUTE PATCH Contents/Resources/distribution/extensions/extensions0/extensions0png1.png
+EXECUTE PATCH Contents/Resources/distribution/extensions/extensions0/extensions0png0.png
+EXECUTE PATCH Contents/Resources/0/0exe0.exe
+LoadSourceFile: destination file size 776 does not match expected size 79872
+LoadSourceFile failed
+### execution failed
+FINISH ADD Contents/Resources/searchplugins/searchpluginstext0
+FINISH PATCH Contents/Resources/searchplugins/searchpluginspng1.png
+FINISH PATCH Contents/Resources/searchplugins/searchpluginspng0.png
+FINISH ADD Contents/Resources/precomplete
+FINISH ADD Contents/Resources/distribution/extensions/extensions1/extensions1text0
+backup_restore: backup file doesn't exist: Contents/Resources/distribution/extensions/extensions1/extensions1text0.moz-backup
+FINISH PATCH Contents/Resources/distribution/extensions/extensions1/extensions1png1.png
+FINISH PATCH Contents/Resources/distribution/extensions/extensions1/extensions1png0.png
+FINISH ADD Contents/Resources/distribution/extensions/extensions0/extensions0text0
+FINISH PATCH Contents/Resources/distribution/extensions/extensions0/extensions0png1.png
+FINISH PATCH Contents/Resources/distribution/extensions/extensions0/extensions0png0.png
+FINISH PATCH Contents/Resources/0/0exe0.exe
+backup_restore: backup file doesn't exist: Contents/Resources/0/0exe0.exe.moz-backup
+FINISH ADD Contents/Resources/0/00/00text0
+backup_restore: backup file doesn't exist: Contents/Resources/0/00/00text0.moz-backup
+FINISH PATCH Contents/Resources/0/00/00png0.png
+backup_restore: backup file doesn't exist: Contents/Resources/0/00/00png0.png.moz-backup
+FINISH PATCH Contents/MacOS/exe0.exe
+backup_restore: backup file doesn't exist: Contents/MacOS/exe0.exe.moz-backup
+FINISH ADD Contents/Resources/2/20/20text0
+backup_restore: backup file doesn't exist: Contents/Resources/2/20/20text0.moz-backup
+FINISH ADD Contents/Resources/2/20/20png0.png
+backup_restore: backup file doesn't exist: Contents/Resources/2/20/20png0.png.moz-backup
+FINISH ADD Contents/Resources/0/00/00text2
+backup_restore: backup file doesn't exist: Contents/Resources/0/00/00text2.moz-backup
+FINISH REMOVEFILE Contents/Resources/1/10/10text0
+backup_restore: backup file doesn't exist: Contents/Resources/1/10/10text0.moz-backup
+FINISH REMOVEFILE Contents/Resources/0/00/00text1
+backup_restore: backup file doesn't exist: Contents/Resources/0/00/00text1.moz-backup
+FINISH REMOVEFILE Contents/Resources/9/97/970/97xtext0
+backup_restore: backup file doesn't exist: Contents/Resources/9/97/970/97xtext0.moz-backup
+FINISH REMOVEFILE Contents/Resources/9/97/970/97xtext1
+backup_restore: backup file doesn't exist: Contents/Resources/9/97/970/97xtext1.moz-backup
+FINISH REMOVEFILE Contents/Resources/9/97/971/97xtext0
+backup_restore: backup file doesn't exist: Contents/Resources/9/97/971/97xtext0.moz-backup
+FINISH REMOVEFILE Contents/Resources/9/97/971/97xtext1
+backup_restore: backup file doesn't exist: Contents/Resources/9/97/971/97xtext1.moz-backup
+FINISH REMOVEFILE Contents/Resources/9/96/96text0
+backup_restore: backup file doesn't exist: Contents/Resources/9/96/96text0.moz-backup
+FINISH REMOVEFILE Contents/Resources/9/96/96text1
+backup_restore: backup file doesn't exist: Contents/Resources/9/96/96text1.moz-backup
+FINISH REMOVEFILE Contents/Resources/8/87/870/87xtext0
+backup_restore: backup file doesn't exist: Contents/Resources/8/87/870/87xtext0.moz-backup
+FINISH REMOVEFILE Contents/Resources/8/87/870/87xtext1
+backup_restore: backup file doesn't exist: Contents/Resources/8/87/870/87xtext1.moz-backup
+FINISH REMOVEFILE Contents/Resources/8/87/871/87xtext0
+backup_restore: backup file doesn't exist: Contents/Resources/8/87/871/87xtext0.moz-backup
+FINISH REMOVEFILE Contents/Resources/8/87/871/87xtext1
+backup_restore: backup file doesn't exist: Contents/Resources/8/87/871/87xtext1.moz-backup
+FINISH REMOVEFILE Contents/Resources/8/86/86text0
+backup_restore: backup file doesn't exist: Contents/Resources/8/86/86text0.moz-backup
+FINISH REMOVEFILE Contents/Resources/8/86/86text1
+backup_restore: backup file doesn't exist: Contents/Resources/8/86/86text1.moz-backup
+FINISH REMOVEFILE Contents/Resources/7/70/7xtest.exe
+backup_restore: backup file doesn't exist: Contents/Resources/7/70/7xtest.exe.moz-backup
+FINISH REMOVEFILE Contents/Resources/7/70/7xtext0
+backup_restore: backup file doesn't exist: Contents/Resources/7/70/7xtext0.moz-backup
+FINISH REMOVEFILE Contents/Resources/7/70/7xtext1
+backup_restore: backup file doesn't exist: Contents/Resources/7/70/7xtext1.moz-backup
+FINISH REMOVEFILE Contents/Resources/7/71/7xtest.exe
+backup_restore: backup file doesn't exist: Contents/Resources/7/71/7xtest.exe.moz-backup
+FINISH REMOVEFILE Contents/Resources/7/71/7xtext0
+backup_restore: backup file doesn't exist: Contents/Resources/7/71/7xtext0.moz-backup
+FINISH REMOVEFILE Contents/Resources/7/71/7xtext1
+backup_restore: backup file doesn't exist: Contents/Resources/7/71/7xtext1.moz-backup
+FINISH REMOVEFILE Contents/Resources/7/7text0
+backup_restore: backup file doesn't exist: Contents/Resources/7/7text0.moz-backup
+FINISH REMOVEFILE Contents/Resources/7/7text1
+backup_restore: backup file doesn't exist: Contents/Resources/7/7text1.moz-backup
+FINISH REMOVEFILE Contents/Resources/5/5text1
+backup_restore: backup file doesn't exist: Contents/Resources/5/5text1.moz-backup
+FINISH REMOVEFILE Contents/Resources/5/5text0
+backup_restore: backup file doesn't exist: Contents/Resources/5/5text0.moz-backup
+FINISH REMOVEFILE Contents/Resources/5/5test.exe
+backup_restore: backup file doesn't exist: Contents/Resources/5/5test.exe.moz-backup
+FINISH REMOVEFILE Contents/Resources/5/5text0
+backup_restore: backup file doesn't exist: Contents/Resources/5/5text0.moz-backup
+FINISH REMOVEFILE Contents/Resources/5/5text1
+backup_restore: backup file doesn't exist: Contents/Resources/5/5text1.moz-backup
+FINISH REMOVEFILE Contents/Resources/4/4text1
+backup_restore: backup file doesn't exist: Contents/Resources/4/4text1.moz-backup
+FINISH REMOVEFILE Contents/Resources/4/4text0
+backup_restore: backup file doesn't exist: Contents/Resources/4/4text0.moz-backup
+FINISH REMOVEFILE Contents/Resources/3/3text1
+backup_restore: backup file doesn't exist: Contents/Resources/3/3text1.moz-backup
+FINISH REMOVEFILE Contents/Resources/3/3text0
+backup_restore: backup file doesn't exist: Contents/Resources/3/3text0.moz-backup
+failed: 2
+calling QuitProgressUI
diff --git a/toolkit/mozapps/update/tests/data/partial_log_failure_win b/toolkit/mozapps/update/tests/data/partial_log_failure_win
new file mode 100644
index 000000000..e3d683dc1
--- /dev/null
+++ b/toolkit/mozapps/update/tests/data/partial_log_failure_win
@@ -0,0 +1,192 @@
+UPDATE TYPE partial
+PREPARE ADD searchplugins/searchpluginstext0
+PREPARE PATCH searchplugins/searchpluginspng1.png
+PREPARE PATCH searchplugins/searchpluginspng0.png
+PREPARE ADD precomplete
+PREPARE PATCH exe0.exe
+PREPARE ADD distribution/extensions/extensions1/extensions1text0
+PREPARE PATCH distribution/extensions/extensions1/extensions1png1.png
+PREPARE PATCH distribution/extensions/extensions1/extensions1png0.png
+PREPARE ADD distribution/extensions/extensions0/extensions0text0
+PREPARE PATCH distribution/extensions/extensions0/extensions0png1.png
+PREPARE PATCH distribution/extensions/extensions0/extensions0png0.png
+PREPARE PATCH 0/0exe0.exe
+PREPARE ADD 0/00/00text0
+PREPARE PATCH 0/00/00png0.png
+PREPARE ADD 2/20/20text0
+PREPARE ADD 2/20/20png0.png
+PREPARE ADD 0/00/00text2
+PREPARE REMOVEFILE 1/10/10text0
+PREPARE REMOVEFILE 0/00/00text1
+PREPARE REMOVEDIR 9/99/
+PREPARE REMOVEDIR 9/99/
+PREPARE REMOVEDIR 9/98/
+PREPARE REMOVEFILE 9/97/970/97xtext0
+PREPARE REMOVEFILE 9/97/970/97xtext1
+PREPARE REMOVEDIR 9/97/970/
+PREPARE REMOVEFILE 9/97/971/97xtext0
+PREPARE REMOVEFILE 9/97/971/97xtext1
+PREPARE REMOVEDIR 9/97/971/
+PREPARE REMOVEDIR 9/97/
+PREPARE REMOVEFILE 9/96/96text0
+PREPARE REMOVEFILE 9/96/96text1
+PREPARE REMOVEDIR 9/96/
+PREPARE REMOVEDIR 9/95/
+PREPARE REMOVEDIR 9/95/
+PREPARE REMOVEDIR 9/94/
+PREPARE REMOVEDIR 9/94/
+PREPARE REMOVEDIR 9/93/
+PREPARE REMOVEDIR 9/92/
+PREPARE REMOVEDIR 9/91/
+PREPARE REMOVEDIR 9/90/
+PREPARE REMOVEDIR 9/90/
+PREPARE REMOVEDIR 8/89/
+PREPARE REMOVEDIR 8/89/
+PREPARE REMOVEDIR 8/88/
+PREPARE REMOVEFILE 8/87/870/87xtext0
+PREPARE REMOVEFILE 8/87/870/87xtext1
+PREPARE REMOVEDIR 8/87/870/
+PREPARE REMOVEFILE 8/87/871/87xtext0
+PREPARE REMOVEFILE 8/87/871/87xtext1
+PREPARE REMOVEDIR 8/87/871/
+PREPARE REMOVEDIR 8/87/
+PREPARE REMOVEFILE 8/86/86text0
+PREPARE REMOVEFILE 8/86/86text1
+PREPARE REMOVEDIR 8/86/
+PREPARE REMOVEDIR 8/85/
+PREPARE REMOVEDIR 8/85/
+PREPARE REMOVEDIR 8/84/
+PREPARE REMOVEDIR 8/84/
+PREPARE REMOVEDIR 8/83/
+PREPARE REMOVEDIR 8/82/
+PREPARE REMOVEDIR 8/81/
+PREPARE REMOVEDIR 8/80/
+PREPARE REMOVEDIR 8/80/
+PREPARE REMOVEFILE 7/70/7xtest.exe
+PREPARE REMOVEFILE 7/70/7xtext0
+PREPARE REMOVEFILE 7/70/7xtext1
+PREPARE REMOVEDIR 7/70/
+PREPARE REMOVEFILE 7/71/7xtest.exe
+PREPARE REMOVEFILE 7/71/7xtext0
+PREPARE REMOVEFILE 7/71/7xtext1
+PREPARE REMOVEDIR 7/71/
+PREPARE REMOVEFILE 7/7text0
+PREPARE REMOVEFILE 7/7text1
+PREPARE REMOVEDIR 7/
+PREPARE REMOVEDIR 6/
+PREPARE REMOVEFILE 5/5text1
+PREPARE REMOVEFILE 5/5text0
+PREPARE REMOVEFILE 5/5test.exe
+PREPARE REMOVEFILE 5/5text0
+PREPARE REMOVEFILE 5/5text1
+PREPARE REMOVEDIR 5/
+PREPARE REMOVEFILE 4/4text1
+PREPARE REMOVEFILE 4/4text0
+PREPARE REMOVEDIR 4/
+PREPARE REMOVEFILE 3/3text1
+PREPARE REMOVEFILE 3/3text0
+PREPARE REMOVEDIR 1/10/
+PREPARE REMOVEDIR 1/
+EXECUTE ADD searchplugins/searchpluginstext0
+EXECUTE PATCH searchplugins/searchpluginspng1.png
+EXECUTE PATCH searchplugins/searchpluginspng0.png
+EXECUTE ADD precomplete
+EXECUTE PATCH exe0.exe
+EXECUTE ADD distribution/extensions/extensions1/extensions1text0
+EXECUTE PATCH distribution/extensions/extensions1/extensions1png1.png
+EXECUTE PATCH distribution/extensions/extensions1/extensions1png0.png
+EXECUTE ADD distribution/extensions/extensions0/extensions0text0
+EXECUTE PATCH distribution/extensions/extensions0/extensions0png1.png
+EXECUTE PATCH distribution/extensions/extensions0/extensions0png0.png
+EXECUTE PATCH 0/0exe0.exe
+LoadSourceFile: destination file size 776 does not match expected size 79872
+LoadSourceFile failed
+### execution failed
+FINISH ADD searchplugins/searchpluginstext0
+FINISH PATCH searchplugins/searchpluginspng1.png
+FINISH PATCH searchplugins/searchpluginspng0.png
+FINISH ADD precomplete
+FINISH PATCH exe0.exe
+FINISH ADD distribution/extensions/extensions1/extensions1text0
+backup_restore: backup file doesn't exist: distribution/extensions/extensions1/extensions1text0.moz-backup
+FINISH PATCH distribution/extensions/extensions1/extensions1png1.png
+FINISH PATCH distribution/extensions/extensions1/extensions1png0.png
+FINISH ADD distribution/extensions/extensions0/extensions0text0
+FINISH PATCH distribution/extensions/extensions0/extensions0png1.png
+FINISH PATCH distribution/extensions/extensions0/extensions0png0.png
+FINISH PATCH 0/0exe0.exe
+backup_restore: backup file doesn't exist: 0/0exe0.exe.moz-backup
+FINISH ADD 0/00/00text0
+backup_restore: backup file doesn't exist: 0/00/00text0.moz-backup
+FINISH PATCH 0/00/00png0.png
+backup_restore: backup file doesn't exist: 0/00/00png0.png.moz-backup
+FINISH ADD 2/20/20text0
+backup_restore: backup file doesn't exist: 2/20/20text0.moz-backup
+FINISH ADD 2/20/20png0.png
+backup_restore: backup file doesn't exist: 2/20/20png0.png.moz-backup
+FINISH ADD 0/00/00text2
+backup_restore: backup file doesn't exist: 0/00/00text2.moz-backup
+FINISH REMOVEFILE 1/10/10text0
+backup_restore: backup file doesn't exist: 1/10/10text0.moz-backup
+FINISH REMOVEFILE 0/00/00text1
+backup_restore: backup file doesn't exist: 0/00/00text1.moz-backup
+FINISH REMOVEFILE 9/97/970/97xtext0
+backup_restore: backup file doesn't exist: 9/97/970/97xtext0.moz-backup
+FINISH REMOVEFILE 9/97/970/97xtext1
+backup_restore: backup file doesn't exist: 9/97/970/97xtext1.moz-backup
+FINISH REMOVEFILE 9/97/971/97xtext0
+backup_restore: backup file doesn't exist: 9/97/971/97xtext0.moz-backup
+FINISH REMOVEFILE 9/97/971/97xtext1
+backup_restore: backup file doesn't exist: 9/97/971/97xtext1.moz-backup
+FINISH REMOVEFILE 9/96/96text0
+backup_restore: backup file doesn't exist: 9/96/96text0.moz-backup
+FINISH REMOVEFILE 9/96/96text1
+backup_restore: backup file doesn't exist: 9/96/96text1.moz-backup
+FINISH REMOVEFILE 8/87/870/87xtext0
+backup_restore: backup file doesn't exist: 8/87/870/87xtext0.moz-backup
+FINISH REMOVEFILE 8/87/870/87xtext1
+backup_restore: backup file doesn't exist: 8/87/870/87xtext1.moz-backup
+FINISH REMOVEFILE 8/87/871/87xtext0
+backup_restore: backup file doesn't exist: 8/87/871/87xtext0.moz-backup
+FINISH REMOVEFILE 8/87/871/87xtext1
+backup_restore: backup file doesn't exist: 8/87/871/87xtext1.moz-backup
+FINISH REMOVEFILE 8/86/86text0
+backup_restore: backup file doesn't exist: 8/86/86text0.moz-backup
+FINISH REMOVEFILE 8/86/86text1
+backup_restore: backup file doesn't exist: 8/86/86text1.moz-backup
+FINISH REMOVEFILE 7/70/7xtest.exe
+backup_restore: backup file doesn't exist: 7/70/7xtest.exe.moz-backup
+FINISH REMOVEFILE 7/70/7xtext0
+backup_restore: backup file doesn't exist: 7/70/7xtext0.moz-backup
+FINISH REMOVEFILE 7/70/7xtext1
+backup_restore: backup file doesn't exist: 7/70/7xtext1.moz-backup
+FINISH REMOVEFILE 7/71/7xtest.exe
+backup_restore: backup file doesn't exist: 7/71/7xtest.exe.moz-backup
+FINISH REMOVEFILE 7/71/7xtext0
+backup_restore: backup file doesn't exist: 7/71/7xtext0.moz-backup
+FINISH REMOVEFILE 7/71/7xtext1
+backup_restore: backup file doesn't exist: 7/71/7xtext1.moz-backup
+FINISH REMOVEFILE 7/7text0
+backup_restore: backup file doesn't exist: 7/7text0.moz-backup
+FINISH REMOVEFILE 7/7text1
+backup_restore: backup file doesn't exist: 7/7text1.moz-backup
+FINISH REMOVEFILE 5/5text1
+backup_restore: backup file doesn't exist: 5/5text1.moz-backup
+FINISH REMOVEFILE 5/5text0
+backup_restore: backup file doesn't exist: 5/5text0.moz-backup
+FINISH REMOVEFILE 5/5test.exe
+backup_restore: backup file doesn't exist: 5/5test.exe.moz-backup
+FINISH REMOVEFILE 5/5text0
+backup_restore: backup file doesn't exist: 5/5text0.moz-backup
+FINISH REMOVEFILE 5/5text1
+backup_restore: backup file doesn't exist: 5/5text1.moz-backup
+FINISH REMOVEFILE 4/4text1
+backup_restore: backup file doesn't exist: 4/4text1.moz-backup
+FINISH REMOVEFILE 4/4text0
+backup_restore: backup file doesn't exist: 4/4text0.moz-backup
+FINISH REMOVEFILE 3/3text1
+backup_restore: backup file doesn't exist: 3/3text1.moz-backup
+FINISH REMOVEFILE 3/3text0
+backup_restore: backup file doesn't exist: 3/3text0.moz-backup
+failed: 2
+calling QuitProgressUI
diff --git a/toolkit/mozapps/update/tests/data/partial_log_success_mac b/toolkit/mozapps/update/tests/data/partial_log_success_mac
new file mode 100644
index 000000000..fb5272ad2
--- /dev/null
+++ b/toolkit/mozapps/update/tests/data/partial_log_success_mac
@@ -0,0 +1,279 @@
+UPDATE TYPE partial
+PREPARE ADD Contents/Resources/searchplugins/searchpluginstext0
+PREPARE PATCH Contents/Resources/searchplugins/searchpluginspng1.png
+PREPARE PATCH Contents/Resources/searchplugins/searchpluginspng0.png
+PREPARE ADD Contents/Resources/precomplete
+PREPARE ADD Contents/Resources/distribution/extensions/extensions1/extensions1text0
+PREPARE PATCH Contents/Resources/distribution/extensions/extensions1/extensions1png1.png
+PREPARE PATCH Contents/Resources/distribution/extensions/extensions1/extensions1png0.png
+PREPARE ADD Contents/Resources/distribution/extensions/extensions0/extensions0text0
+PREPARE PATCH Contents/Resources/distribution/extensions/extensions0/extensions0png1.png
+PREPARE PATCH Contents/Resources/distribution/extensions/extensions0/extensions0png0.png
+PREPARE PATCH Contents/Resources/0/0exe0.exe
+PREPARE ADD Contents/Resources/0/00/00text0
+PREPARE PATCH Contents/Resources/0/00/00png0.png
+PREPARE PATCH Contents/MacOS/exe0.exe
+PREPARE ADD Contents/Resources/2/20/20text0
+PREPARE ADD Contents/Resources/2/20/20png0.png
+PREPARE ADD Contents/Resources/0/00/00text2
+PREPARE REMOVEFILE Contents/Resources/1/10/10text0
+PREPARE REMOVEFILE Contents/Resources/0/00/00text1
+PREPARE REMOVEDIR Contents/Resources/9/99/
+PREPARE REMOVEDIR Contents/Resources/9/99/
+PREPARE REMOVEDIR Contents/Resources/9/98/
+PREPARE REMOVEFILE Contents/Resources/9/97/970/97xtext0
+PREPARE REMOVEFILE Contents/Resources/9/97/970/97xtext1
+PREPARE REMOVEDIR Contents/Resources/9/97/970/
+PREPARE REMOVEFILE Contents/Resources/9/97/971/97xtext0
+PREPARE REMOVEFILE Contents/Resources/9/97/971/97xtext1
+PREPARE REMOVEDIR Contents/Resources/9/97/971/
+PREPARE REMOVEDIR Contents/Resources/9/97/
+PREPARE REMOVEFILE Contents/Resources/9/96/96text0
+PREPARE REMOVEFILE Contents/Resources/9/96/96text1
+PREPARE REMOVEDIR Contents/Resources/9/96/
+PREPARE REMOVEDIR Contents/Resources/9/95/
+PREPARE REMOVEDIR Contents/Resources/9/95/
+PREPARE REMOVEDIR Contents/Resources/9/94/
+PREPARE REMOVEDIR Contents/Resources/9/94/
+PREPARE REMOVEDIR Contents/Resources/9/93/
+PREPARE REMOVEDIR Contents/Resources/9/92/
+PREPARE REMOVEDIR Contents/Resources/9/91/
+PREPARE REMOVEDIR Contents/Resources/9/90/
+PREPARE REMOVEDIR Contents/Resources/9/90/
+PREPARE REMOVEDIR Contents/Resources/8/89/
+PREPARE REMOVEDIR Contents/Resources/8/89/
+PREPARE REMOVEDIR Contents/Resources/8/88/
+PREPARE REMOVEFILE Contents/Resources/8/87/870/87xtext0
+PREPARE REMOVEFILE Contents/Resources/8/87/870/87xtext1
+PREPARE REMOVEDIR Contents/Resources/8/87/870/
+PREPARE REMOVEFILE Contents/Resources/8/87/871/87xtext0
+PREPARE REMOVEFILE Contents/Resources/8/87/871/87xtext1
+PREPARE REMOVEDIR Contents/Resources/8/87/871/
+PREPARE REMOVEDIR Contents/Resources/8/87/
+PREPARE REMOVEFILE Contents/Resources/8/86/86text0
+PREPARE REMOVEFILE Contents/Resources/8/86/86text1
+PREPARE REMOVEDIR Contents/Resources/8/86/
+PREPARE REMOVEDIR Contents/Resources/8/85/
+PREPARE REMOVEDIR Contents/Resources/8/85/
+PREPARE REMOVEDIR Contents/Resources/8/84/
+PREPARE REMOVEDIR Contents/Resources/8/84/
+PREPARE REMOVEDIR Contents/Resources/8/83/
+PREPARE REMOVEDIR Contents/Resources/8/82/
+PREPARE REMOVEDIR Contents/Resources/8/81/
+PREPARE REMOVEDIR Contents/Resources/8/80/
+PREPARE REMOVEDIR Contents/Resources/8/80/
+PREPARE REMOVEFILE Contents/Resources/7/70/7xtest.exe
+PREPARE REMOVEFILE Contents/Resources/7/70/7xtext0
+PREPARE REMOVEFILE Contents/Resources/7/70/7xtext1
+PREPARE REMOVEDIR Contents/Resources/7/70/
+PREPARE REMOVEFILE Contents/Resources/7/71/7xtest.exe
+PREPARE REMOVEFILE Contents/Resources/7/71/7xtext0
+PREPARE REMOVEFILE Contents/Resources/7/71/7xtext1
+PREPARE REMOVEDIR Contents/Resources/7/71/
+PREPARE REMOVEFILE Contents/Resources/7/7text0
+PREPARE REMOVEFILE Contents/Resources/7/7text1
+PREPARE REMOVEDIR Contents/Resources/7/
+PREPARE REMOVEDIR Contents/Resources/6/
+PREPARE REMOVEFILE Contents/Resources/5/5text1
+PREPARE REMOVEFILE Contents/Resources/5/5text0
+PREPARE REMOVEFILE Contents/Resources/5/5test.exe
+PREPARE REMOVEFILE Contents/Resources/5/5text0
+PREPARE REMOVEFILE Contents/Resources/5/5text1
+PREPARE REMOVEDIR Contents/Resources/5/
+PREPARE REMOVEFILE Contents/Resources/4/4text1
+PREPARE REMOVEFILE Contents/Resources/4/4text0
+PREPARE REMOVEDIR Contents/Resources/4/
+PREPARE REMOVEFILE Contents/Resources/3/3text1
+PREPARE REMOVEFILE Contents/Resources/3/3text0
+PREPARE REMOVEDIR Contents/Resources/1/10/
+PREPARE REMOVEDIR Contents/Resources/1/
+EXECUTE ADD Contents/Resources/searchplugins/searchpluginstext0
+EXECUTE PATCH Contents/Resources/searchplugins/searchpluginspng1.png
+EXECUTE PATCH Contents/Resources/searchplugins/searchpluginspng0.png
+EXECUTE ADD Contents/Resources/precomplete
+EXECUTE ADD Contents/Resources/distribution/extensions/extensions1/extensions1text0
+EXECUTE PATCH Contents/Resources/distribution/extensions/extensions1/extensions1png1.png
+EXECUTE PATCH Contents/Resources/distribution/extensions/extensions1/extensions1png0.png
+EXECUTE ADD Contents/Resources/distribution/extensions/extensions0/extensions0text0
+EXECUTE PATCH Contents/Resources/distribution/extensions/extensions0/extensions0png1.png
+EXECUTE PATCH Contents/Resources/distribution/extensions/extensions0/extensions0png0.png
+EXECUTE PATCH Contents/Resources/0/0exe0.exe
+EXECUTE ADD Contents/Resources/0/00/00text0
+EXECUTE PATCH Contents/Resources/0/00/00png0.png
+EXECUTE PATCH Contents/MacOS/exe0.exe
+EXECUTE ADD Contents/Resources/2/20/20text0
+EXECUTE ADD Contents/Resources/2/20/20png0.png
+EXECUTE ADD Contents/Resources/0/00/00text2
+EXECUTE REMOVEFILE Contents/Resources/1/10/10text0
+EXECUTE REMOVEFILE Contents/Resources/0/00/00text1
+EXECUTE REMOVEDIR Contents/Resources/9/99/
+EXECUTE REMOVEDIR Contents/Resources/9/99/
+EXECUTE REMOVEDIR Contents/Resources/9/98/
+EXECUTE REMOVEFILE Contents/Resources/9/97/970/97xtext0
+EXECUTE REMOVEFILE Contents/Resources/9/97/970/97xtext1
+EXECUTE REMOVEDIR Contents/Resources/9/97/970/
+EXECUTE REMOVEFILE Contents/Resources/9/97/971/97xtext0
+EXECUTE REMOVEFILE Contents/Resources/9/97/971/97xtext1
+EXECUTE REMOVEDIR Contents/Resources/9/97/971/
+EXECUTE REMOVEDIR Contents/Resources/9/97/
+EXECUTE REMOVEFILE Contents/Resources/9/96/96text0
+EXECUTE REMOVEFILE Contents/Resources/9/96/96text1
+EXECUTE REMOVEDIR Contents/Resources/9/96/
+EXECUTE REMOVEDIR Contents/Resources/9/95/
+EXECUTE REMOVEDIR Contents/Resources/9/95/
+EXECUTE REMOVEDIR Contents/Resources/9/94/
+EXECUTE REMOVEDIR Contents/Resources/9/94/
+EXECUTE REMOVEDIR Contents/Resources/9/93/
+EXECUTE REMOVEDIR Contents/Resources/9/92/
+EXECUTE REMOVEDIR Contents/Resources/9/91/
+EXECUTE REMOVEDIR Contents/Resources/9/90/
+EXECUTE REMOVEDIR Contents/Resources/9/90/
+EXECUTE REMOVEDIR Contents/Resources/8/89/
+EXECUTE REMOVEDIR Contents/Resources/8/89/
+EXECUTE REMOVEDIR Contents/Resources/8/88/
+EXECUTE REMOVEFILE Contents/Resources/8/87/870/87xtext0
+EXECUTE REMOVEFILE Contents/Resources/8/87/870/87xtext1
+EXECUTE REMOVEDIR Contents/Resources/8/87/870/
+EXECUTE REMOVEFILE Contents/Resources/8/87/871/87xtext0
+EXECUTE REMOVEFILE Contents/Resources/8/87/871/87xtext1
+EXECUTE REMOVEDIR Contents/Resources/8/87/871/
+EXECUTE REMOVEDIR Contents/Resources/8/87/
+EXECUTE REMOVEFILE Contents/Resources/8/86/86text0
+EXECUTE REMOVEFILE Contents/Resources/8/86/86text1
+EXECUTE REMOVEDIR Contents/Resources/8/86/
+EXECUTE REMOVEDIR Contents/Resources/8/85/
+EXECUTE REMOVEDIR Contents/Resources/8/85/
+EXECUTE REMOVEDIR Contents/Resources/8/84/
+EXECUTE REMOVEDIR Contents/Resources/8/84/
+EXECUTE REMOVEDIR Contents/Resources/8/83/
+EXECUTE REMOVEDIR Contents/Resources/8/82/
+EXECUTE REMOVEDIR Contents/Resources/8/81/
+EXECUTE REMOVEDIR Contents/Resources/8/80/
+EXECUTE REMOVEDIR Contents/Resources/8/80/
+EXECUTE REMOVEFILE Contents/Resources/7/70/7xtest.exe
+EXECUTE REMOVEFILE Contents/Resources/7/70/7xtext0
+EXECUTE REMOVEFILE Contents/Resources/7/70/7xtext1
+EXECUTE REMOVEDIR Contents/Resources/7/70/
+EXECUTE REMOVEFILE Contents/Resources/7/71/7xtest.exe
+EXECUTE REMOVEFILE Contents/Resources/7/71/7xtext0
+EXECUTE REMOVEFILE Contents/Resources/7/71/7xtext1
+EXECUTE REMOVEDIR Contents/Resources/7/71/
+EXECUTE REMOVEFILE Contents/Resources/7/7text0
+EXECUTE REMOVEFILE Contents/Resources/7/7text1
+EXECUTE REMOVEDIR Contents/Resources/7/
+EXECUTE REMOVEDIR Contents/Resources/6/
+EXECUTE REMOVEFILE Contents/Resources/5/5text1
+EXECUTE REMOVEFILE Contents/Resources/5/5text0
+EXECUTE REMOVEFILE Contents/Resources/5/5test.exe
+EXECUTE REMOVEFILE Contents/Resources/5/5text0
+file cannot be removed because it does not exist; skipping
+EXECUTE REMOVEFILE Contents/Resources/5/5text1
+file cannot be removed because it does not exist; skipping
+EXECUTE REMOVEDIR Contents/Resources/5/
+EXECUTE REMOVEFILE Contents/Resources/4/4text1
+EXECUTE REMOVEFILE Contents/Resources/4/4text0
+EXECUTE REMOVEDIR Contents/Resources/4/
+EXECUTE REMOVEFILE Contents/Resources/3/3text1
+EXECUTE REMOVEFILE Contents/Resources/3/3text0
+EXECUTE REMOVEDIR Contents/Resources/1/10/
+EXECUTE REMOVEDIR Contents/Resources/1/
+FINISH ADD Contents/Resources/searchplugins/searchpluginstext0
+FINISH PATCH Contents/Resources/searchplugins/searchpluginspng1.png
+FINISH PATCH Contents/Resources/searchplugins/searchpluginspng0.png
+FINISH ADD Contents/Resources/precomplete
+FINISH ADD Contents/Resources/distribution/extensions/extensions1/extensions1text0
+FINISH PATCH Contents/Resources/distribution/extensions/extensions1/extensions1png1.png
+FINISH PATCH Contents/Resources/distribution/extensions/extensions1/extensions1png0.png
+FINISH ADD Contents/Resources/distribution/extensions/extensions0/extensions0text0
+FINISH PATCH Contents/Resources/distribution/extensions/extensions0/extensions0png1.png
+FINISH PATCH Contents/Resources/distribution/extensions/extensions0/extensions0png0.png
+FINISH PATCH Contents/Resources/0/0exe0.exe
+FINISH ADD Contents/Resources/0/00/00text0
+FINISH PATCH Contents/Resources/0/00/00png0.png
+FINISH PATCH Contents/MacOS/exe0.exe
+FINISH ADD Contents/Resources/2/20/20text0
+FINISH ADD Contents/Resources/2/20/20png0.png
+FINISH ADD Contents/Resources/0/00/00text2
+FINISH REMOVEFILE Contents/Resources/1/10/10text0
+FINISH REMOVEFILE Contents/Resources/0/00/00text1
+FINISH REMOVEDIR Contents/Resources/9/99/
+FINISH REMOVEDIR Contents/Resources/9/99/
+directory no longer exists; skipping
+FINISH REMOVEDIR Contents/Resources/9/98/
+FINISH REMOVEFILE Contents/Resources/9/97/970/97xtext0
+FINISH REMOVEFILE Contents/Resources/9/97/970/97xtext1
+FINISH REMOVEDIR Contents/Resources/9/97/970/
+FINISH REMOVEFILE Contents/Resources/9/97/971/97xtext0
+FINISH REMOVEFILE Contents/Resources/9/97/971/97xtext1
+FINISH REMOVEDIR Contents/Resources/9/97/971/
+FINISH REMOVEDIR Contents/Resources/9/97/
+FINISH REMOVEFILE Contents/Resources/9/96/96text0
+FINISH REMOVEFILE Contents/Resources/9/96/96text1
+FINISH REMOVEDIR Contents/Resources/9/96/
+FINISH REMOVEDIR Contents/Resources/9/95/
+FINISH REMOVEDIR Contents/Resources/9/95/
+directory no longer exists; skipping
+FINISH REMOVEDIR Contents/Resources/9/94/
+FINISH REMOVEDIR Contents/Resources/9/94/
+directory no longer exists; skipping
+FINISH REMOVEDIR Contents/Resources/9/93/
+FINISH REMOVEDIR Contents/Resources/9/92/
+removing directory: Contents/Resources/9/92/, rv: 0
+FINISH REMOVEDIR Contents/Resources/9/91/
+removing directory: Contents/Resources/9/91/, rv: 0
+FINISH REMOVEDIR Contents/Resources/9/90/
+FINISH REMOVEDIR Contents/Resources/9/90/
+directory no longer exists; skipping
+FINISH REMOVEDIR Contents/Resources/8/89/
+FINISH REMOVEDIR Contents/Resources/8/89/
+directory no longer exists; skipping
+FINISH REMOVEDIR Contents/Resources/8/88/
+FINISH REMOVEFILE Contents/Resources/8/87/870/87xtext0
+FINISH REMOVEFILE Contents/Resources/8/87/870/87xtext1
+FINISH REMOVEDIR Contents/Resources/8/87/870/
+FINISH REMOVEFILE Contents/Resources/8/87/871/87xtext0
+FINISH REMOVEFILE Contents/Resources/8/87/871/87xtext1
+FINISH REMOVEDIR Contents/Resources/8/87/871/
+FINISH REMOVEDIR Contents/Resources/8/87/
+FINISH REMOVEFILE Contents/Resources/8/86/86text0
+FINISH REMOVEFILE Contents/Resources/8/86/86text1
+FINISH REMOVEDIR Contents/Resources/8/86/
+FINISH REMOVEDIR Contents/Resources/8/85/
+FINISH REMOVEDIR Contents/Resources/8/85/
+directory no longer exists; skipping
+FINISH REMOVEDIR Contents/Resources/8/84/
+FINISH REMOVEDIR Contents/Resources/8/84/
+directory no longer exists; skipping
+FINISH REMOVEDIR Contents/Resources/8/83/
+FINISH REMOVEDIR Contents/Resources/8/82/
+removing directory: Contents/Resources/8/82/, rv: 0
+FINISH REMOVEDIR Contents/Resources/8/81/
+removing directory: Contents/Resources/8/81/, rv: 0
+FINISH REMOVEDIR Contents/Resources/8/80/
+FINISH REMOVEDIR Contents/Resources/8/80/
+directory no longer exists; skipping
+FINISH REMOVEFILE Contents/Resources/7/70/7xtest.exe
+FINISH REMOVEFILE Contents/Resources/7/70/7xtext0
+FINISH REMOVEFILE Contents/Resources/7/70/7xtext1
+FINISH REMOVEDIR Contents/Resources/7/70/
+FINISH REMOVEFILE Contents/Resources/7/71/7xtest.exe
+FINISH REMOVEFILE Contents/Resources/7/71/7xtext0
+FINISH REMOVEFILE Contents/Resources/7/71/7xtext1
+FINISH REMOVEDIR Contents/Resources/7/71/
+FINISH REMOVEFILE Contents/Resources/7/7text0
+FINISH REMOVEFILE Contents/Resources/7/7text1
+FINISH REMOVEDIR Contents/Resources/7/
+FINISH REMOVEDIR Contents/Resources/6/
+FINISH REMOVEFILE Contents/Resources/5/5text1
+FINISH REMOVEFILE Contents/Resources/5/5text0
+FINISH REMOVEFILE Contents/Resources/5/5test.exe
+FINISH REMOVEDIR Contents/Resources/5/
+FINISH REMOVEFILE Contents/Resources/4/4text1
+FINISH REMOVEFILE Contents/Resources/4/4text0
+FINISH REMOVEDIR Contents/Resources/4/
+FINISH REMOVEFILE Contents/Resources/3/3text1
+FINISH REMOVEFILE Contents/Resources/3/3text0
+FINISH REMOVEDIR Contents/Resources/1/10/
+FINISH REMOVEDIR Contents/Resources/1/
+succeeded
+calling QuitProgressUI
diff --git a/toolkit/mozapps/update/tests/data/partial_log_success_win b/toolkit/mozapps/update/tests/data/partial_log_success_win
new file mode 100644
index 000000000..1f5c4b3b4
--- /dev/null
+++ b/toolkit/mozapps/update/tests/data/partial_log_success_win
@@ -0,0 +1,279 @@
+UPDATE TYPE partial
+PREPARE ADD searchplugins/searchpluginstext0
+PREPARE PATCH searchplugins/searchpluginspng1.png
+PREPARE PATCH searchplugins/searchpluginspng0.png
+PREPARE ADD precomplete
+PREPARE PATCH exe0.exe
+PREPARE ADD distribution/extensions/extensions1/extensions1text0
+PREPARE PATCH distribution/extensions/extensions1/extensions1png1.png
+PREPARE PATCH distribution/extensions/extensions1/extensions1png0.png
+PREPARE ADD distribution/extensions/extensions0/extensions0text0
+PREPARE PATCH distribution/extensions/extensions0/extensions0png1.png
+PREPARE PATCH distribution/extensions/extensions0/extensions0png0.png
+PREPARE PATCH 0/0exe0.exe
+PREPARE ADD 0/00/00text0
+PREPARE PATCH 0/00/00png0.png
+PREPARE ADD 2/20/20text0
+PREPARE ADD 2/20/20png0.png
+PREPARE ADD 0/00/00text2
+PREPARE REMOVEFILE 1/10/10text0
+PREPARE REMOVEFILE 0/00/00text1
+PREPARE REMOVEDIR 9/99/
+PREPARE REMOVEDIR 9/99/
+PREPARE REMOVEDIR 9/98/
+PREPARE REMOVEFILE 9/97/970/97xtext0
+PREPARE REMOVEFILE 9/97/970/97xtext1
+PREPARE REMOVEDIR 9/97/970/
+PREPARE REMOVEFILE 9/97/971/97xtext0
+PREPARE REMOVEFILE 9/97/971/97xtext1
+PREPARE REMOVEDIR 9/97/971/
+PREPARE REMOVEDIR 9/97/
+PREPARE REMOVEFILE 9/96/96text0
+PREPARE REMOVEFILE 9/96/96text1
+PREPARE REMOVEDIR 9/96/
+PREPARE REMOVEDIR 9/95/
+PREPARE REMOVEDIR 9/95/
+PREPARE REMOVEDIR 9/94/
+PREPARE REMOVEDIR 9/94/
+PREPARE REMOVEDIR 9/93/
+PREPARE REMOVEDIR 9/92/
+PREPARE REMOVEDIR 9/91/
+PREPARE REMOVEDIR 9/90/
+PREPARE REMOVEDIR 9/90/
+PREPARE REMOVEDIR 8/89/
+PREPARE REMOVEDIR 8/89/
+PREPARE REMOVEDIR 8/88/
+PREPARE REMOVEFILE 8/87/870/87xtext0
+PREPARE REMOVEFILE 8/87/870/87xtext1
+PREPARE REMOVEDIR 8/87/870/
+PREPARE REMOVEFILE 8/87/871/87xtext0
+PREPARE REMOVEFILE 8/87/871/87xtext1
+PREPARE REMOVEDIR 8/87/871/
+PREPARE REMOVEDIR 8/87/
+PREPARE REMOVEFILE 8/86/86text0
+PREPARE REMOVEFILE 8/86/86text1
+PREPARE REMOVEDIR 8/86/
+PREPARE REMOVEDIR 8/85/
+PREPARE REMOVEDIR 8/85/
+PREPARE REMOVEDIR 8/84/
+PREPARE REMOVEDIR 8/84/
+PREPARE REMOVEDIR 8/83/
+PREPARE REMOVEDIR 8/82/
+PREPARE REMOVEDIR 8/81/
+PREPARE REMOVEDIR 8/80/
+PREPARE REMOVEDIR 8/80/
+PREPARE REMOVEFILE 7/70/7xtest.exe
+PREPARE REMOVEFILE 7/70/7xtext0
+PREPARE REMOVEFILE 7/70/7xtext1
+PREPARE REMOVEDIR 7/70/
+PREPARE REMOVEFILE 7/71/7xtest.exe
+PREPARE REMOVEFILE 7/71/7xtext0
+PREPARE REMOVEFILE 7/71/7xtext1
+PREPARE REMOVEDIR 7/71/
+PREPARE REMOVEFILE 7/7text0
+PREPARE REMOVEFILE 7/7text1
+PREPARE REMOVEDIR 7/
+PREPARE REMOVEDIR 6/
+PREPARE REMOVEFILE 5/5text1
+PREPARE REMOVEFILE 5/5text0
+PREPARE REMOVEFILE 5/5test.exe
+PREPARE REMOVEFILE 5/5text0
+PREPARE REMOVEFILE 5/5text1
+PREPARE REMOVEDIR 5/
+PREPARE REMOVEFILE 4/4text1
+PREPARE REMOVEFILE 4/4text0
+PREPARE REMOVEDIR 4/
+PREPARE REMOVEFILE 3/3text1
+PREPARE REMOVEFILE 3/3text0
+PREPARE REMOVEDIR 1/10/
+PREPARE REMOVEDIR 1/
+EXECUTE ADD searchplugins/searchpluginstext0
+EXECUTE PATCH searchplugins/searchpluginspng1.png
+EXECUTE PATCH searchplugins/searchpluginspng0.png
+EXECUTE ADD precomplete
+EXECUTE PATCH exe0.exe
+EXECUTE ADD distribution/extensions/extensions1/extensions1text0
+EXECUTE PATCH distribution/extensions/extensions1/extensions1png1.png
+EXECUTE PATCH distribution/extensions/extensions1/extensions1png0.png
+EXECUTE ADD distribution/extensions/extensions0/extensions0text0
+EXECUTE PATCH distribution/extensions/extensions0/extensions0png1.png
+EXECUTE PATCH distribution/extensions/extensions0/extensions0png0.png
+EXECUTE PATCH 0/0exe0.exe
+EXECUTE ADD 0/00/00text0
+EXECUTE PATCH 0/00/00png0.png
+EXECUTE ADD 2/20/20text0
+EXECUTE ADD 2/20/20png0.png
+EXECUTE ADD 0/00/00text2
+EXECUTE REMOVEFILE 1/10/10text0
+EXECUTE REMOVEFILE 0/00/00text1
+EXECUTE REMOVEDIR 9/99/
+EXECUTE REMOVEDIR 9/99/
+EXECUTE REMOVEDIR 9/98/
+EXECUTE REMOVEFILE 9/97/970/97xtext0
+EXECUTE REMOVEFILE 9/97/970/97xtext1
+EXECUTE REMOVEDIR 9/97/970/
+EXECUTE REMOVEFILE 9/97/971/97xtext0
+EXECUTE REMOVEFILE 9/97/971/97xtext1
+EXECUTE REMOVEDIR 9/97/971/
+EXECUTE REMOVEDIR 9/97/
+EXECUTE REMOVEFILE 9/96/96text0
+EXECUTE REMOVEFILE 9/96/96text1
+EXECUTE REMOVEDIR 9/96/
+EXECUTE REMOVEDIR 9/95/
+EXECUTE REMOVEDIR 9/95/
+EXECUTE REMOVEDIR 9/94/
+EXECUTE REMOVEDIR 9/94/
+EXECUTE REMOVEDIR 9/93/
+EXECUTE REMOVEDIR 9/92/
+EXECUTE REMOVEDIR 9/91/
+EXECUTE REMOVEDIR 9/90/
+EXECUTE REMOVEDIR 9/90/
+EXECUTE REMOVEDIR 8/89/
+EXECUTE REMOVEDIR 8/89/
+EXECUTE REMOVEDIR 8/88/
+EXECUTE REMOVEFILE 8/87/870/87xtext0
+EXECUTE REMOVEFILE 8/87/870/87xtext1
+EXECUTE REMOVEDIR 8/87/870/
+EXECUTE REMOVEFILE 8/87/871/87xtext0
+EXECUTE REMOVEFILE 8/87/871/87xtext1
+EXECUTE REMOVEDIR 8/87/871/
+EXECUTE REMOVEDIR 8/87/
+EXECUTE REMOVEFILE 8/86/86text0
+EXECUTE REMOVEFILE 8/86/86text1
+EXECUTE REMOVEDIR 8/86/
+EXECUTE REMOVEDIR 8/85/
+EXECUTE REMOVEDIR 8/85/
+EXECUTE REMOVEDIR 8/84/
+EXECUTE REMOVEDIR 8/84/
+EXECUTE REMOVEDIR 8/83/
+EXECUTE REMOVEDIR 8/82/
+EXECUTE REMOVEDIR 8/81/
+EXECUTE REMOVEDIR 8/80/
+EXECUTE REMOVEDIR 8/80/
+EXECUTE REMOVEFILE 7/70/7xtest.exe
+EXECUTE REMOVEFILE 7/70/7xtext0
+EXECUTE REMOVEFILE 7/70/7xtext1
+EXECUTE REMOVEDIR 7/70/
+EXECUTE REMOVEFILE 7/71/7xtest.exe
+EXECUTE REMOVEFILE 7/71/7xtext0
+EXECUTE REMOVEFILE 7/71/7xtext1
+EXECUTE REMOVEDIR 7/71/
+EXECUTE REMOVEFILE 7/7text0
+EXECUTE REMOVEFILE 7/7text1
+EXECUTE REMOVEDIR 7/
+EXECUTE REMOVEDIR 6/
+EXECUTE REMOVEFILE 5/5text1
+EXECUTE REMOVEFILE 5/5text0
+EXECUTE REMOVEFILE 5/5test.exe
+EXECUTE REMOVEFILE 5/5text0
+file cannot be removed because it does not exist; skipping
+EXECUTE REMOVEFILE 5/5text1
+file cannot be removed because it does not exist; skipping
+EXECUTE REMOVEDIR 5/
+EXECUTE REMOVEFILE 4/4text1
+EXECUTE REMOVEFILE 4/4text0
+EXECUTE REMOVEDIR 4/
+EXECUTE REMOVEFILE 3/3text1
+EXECUTE REMOVEFILE 3/3text0
+EXECUTE REMOVEDIR 1/10/
+EXECUTE REMOVEDIR 1/
+FINISH ADD searchplugins/searchpluginstext0
+FINISH PATCH searchplugins/searchpluginspng1.png
+FINISH PATCH searchplugins/searchpluginspng0.png
+FINISH ADD precomplete
+FINISH PATCH exe0.exe
+FINISH ADD distribution/extensions/extensions1/extensions1text0
+FINISH PATCH distribution/extensions/extensions1/extensions1png1.png
+FINISH PATCH distribution/extensions/extensions1/extensions1png0.png
+FINISH ADD distribution/extensions/extensions0/extensions0text0
+FINISH PATCH distribution/extensions/extensions0/extensions0png1.png
+FINISH PATCH distribution/extensions/extensions0/extensions0png0.png
+FINISH PATCH 0/0exe0.exe
+FINISH ADD 0/00/00text0
+FINISH PATCH 0/00/00png0.png
+FINISH ADD 2/20/20text0
+FINISH ADD 2/20/20png0.png
+FINISH ADD 0/00/00text2
+FINISH REMOVEFILE 1/10/10text0
+FINISH REMOVEFILE 0/00/00text1
+FINISH REMOVEDIR 9/99/
+FINISH REMOVEDIR 9/99/
+directory no longer exists; skipping
+FINISH REMOVEDIR 9/98/
+FINISH REMOVEFILE 9/97/970/97xtext0
+FINISH REMOVEFILE 9/97/970/97xtext1
+FINISH REMOVEDIR 9/97/970/
+FINISH REMOVEFILE 9/97/971/97xtext0
+FINISH REMOVEFILE 9/97/971/97xtext1
+FINISH REMOVEDIR 9/97/971/
+FINISH REMOVEDIR 9/97/
+FINISH REMOVEFILE 9/96/96text0
+FINISH REMOVEFILE 9/96/96text1
+FINISH REMOVEDIR 9/96/
+FINISH REMOVEDIR 9/95/
+FINISH REMOVEDIR 9/95/
+directory no longer exists; skipping
+FINISH REMOVEDIR 9/94/
+FINISH REMOVEDIR 9/94/
+directory no longer exists; skipping
+FINISH REMOVEDIR 9/93/
+FINISH REMOVEDIR 9/92/
+removing directory: 9/92/, rv: 0
+FINISH REMOVEDIR 9/91/
+removing directory: 9/91/, rv: 0
+FINISH REMOVEDIR 9/90/
+FINISH REMOVEDIR 9/90/
+directory no longer exists; skipping
+FINISH REMOVEDIR 8/89/
+FINISH REMOVEDIR 8/89/
+directory no longer exists; skipping
+FINISH REMOVEDIR 8/88/
+FINISH REMOVEFILE 8/87/870/87xtext0
+FINISH REMOVEFILE 8/87/870/87xtext1
+FINISH REMOVEDIR 8/87/870/
+FINISH REMOVEFILE 8/87/871/87xtext0
+FINISH REMOVEFILE 8/87/871/87xtext1
+FINISH REMOVEDIR 8/87/871/
+FINISH REMOVEDIR 8/87/
+FINISH REMOVEFILE 8/86/86text0
+FINISH REMOVEFILE 8/86/86text1
+FINISH REMOVEDIR 8/86/
+FINISH REMOVEDIR 8/85/
+FINISH REMOVEDIR 8/85/
+directory no longer exists; skipping
+FINISH REMOVEDIR 8/84/
+FINISH REMOVEDIR 8/84/
+directory no longer exists; skipping
+FINISH REMOVEDIR 8/83/
+FINISH REMOVEDIR 8/82/
+removing directory: 8/82/, rv: 0
+FINISH REMOVEDIR 8/81/
+removing directory: 8/81/, rv: 0
+FINISH REMOVEDIR 8/80/
+FINISH REMOVEDIR 8/80/
+directory no longer exists; skipping
+FINISH REMOVEFILE 7/70/7xtest.exe
+FINISH REMOVEFILE 7/70/7xtext0
+FINISH REMOVEFILE 7/70/7xtext1
+FINISH REMOVEDIR 7/70/
+FINISH REMOVEFILE 7/71/7xtest.exe
+FINISH REMOVEFILE 7/71/7xtext0
+FINISH REMOVEFILE 7/71/7xtext1
+FINISH REMOVEDIR 7/71/
+FINISH REMOVEFILE 7/7text0
+FINISH REMOVEFILE 7/7text1
+FINISH REMOVEDIR 7/
+FINISH REMOVEDIR 6/
+FINISH REMOVEFILE 5/5text1
+FINISH REMOVEFILE 5/5text0
+FINISH REMOVEFILE 5/5test.exe
+FINISH REMOVEDIR 5/
+FINISH REMOVEFILE 4/4text1
+FINISH REMOVEFILE 4/4text0
+FINISH REMOVEDIR 4/
+FINISH REMOVEFILE 3/3text1
+FINISH REMOVEFILE 3/3text0
+FINISH REMOVEDIR 1/10/
+FINISH REMOVEDIR 1/
+succeeded
+calling QuitProgressUI
diff --git a/toolkit/mozapps/update/tests/data/partial_mac.mar b/toolkit/mozapps/update/tests/data/partial_mac.mar
new file mode 100644
index 000000000..5a702ed4a
--- /dev/null
+++ b/toolkit/mozapps/update/tests/data/partial_mac.mar
Binary files differ
diff --git a/toolkit/mozapps/update/tests/data/partial_precomplete b/toolkit/mozapps/update/tests/data/partial_precomplete
new file mode 100644
index 000000000..3ec201463
--- /dev/null
+++ b/toolkit/mozapps/update/tests/data/partial_precomplete
@@ -0,0 +1,19 @@
+remove "searchplugins/searchpluginstext0"
+remove "searchplugins/searchpluginspng1.png"
+remove "searchplugins/searchpluginspng0.png"
+remove "removed-files"
+remove "precomplete"
+remove "exe0.exe"
+remove "2/20/20text0"
+remove "2/20/20png0.png"
+remove "0/0exe0.exe"
+remove "0/00/00text2"
+remove "0/00/00text0"
+remove "0/00/00png0.png"
+rmdir "searchplugins/"
+rmdir "defaults/pref/"
+rmdir "defaults/"
+rmdir "2/20/"
+rmdir "2/"
+rmdir "0/00/"
+rmdir "0/"
diff --git a/toolkit/mozapps/update/tests/data/partial_precomplete_mac b/toolkit/mozapps/update/tests/data/partial_precomplete_mac
new file mode 100644
index 000000000..c65b6e4e3
--- /dev/null
+++ b/toolkit/mozapps/update/tests/data/partial_precomplete_mac
@@ -0,0 +1,22 @@
+remove "Contents/Resources/searchplugins/searchpluginstext0"
+remove "Contents/Resources/searchplugins/searchpluginspng1.png"
+remove "Contents/Resources/searchplugins/searchpluginspng0.png"
+remove "Contents/Resources/removed-files"
+remove "Contents/Resources/precomplete"
+remove "Contents/Resources/2/20/20text0"
+remove "Contents/Resources/2/20/20png0.png"
+remove "Contents/Resources/0/0exe0.exe"
+remove "Contents/Resources/0/00/00text2"
+remove "Contents/Resources/0/00/00text0"
+remove "Contents/Resources/0/00/00png0.png"
+remove "Contents/MacOS/exe0.exe"
+rmdir "Contents/Resources/searchplugins/"
+rmdir "Contents/Resources/defaults/pref/"
+rmdir "Contents/Resources/defaults/"
+rmdir "Contents/Resources/2/20/"
+rmdir "Contents/Resources/2/"
+rmdir "Contents/Resources/0/00/"
+rmdir "Contents/Resources/0/"
+rmdir "Contents/Resources/"
+rmdir "Contents/MacOS/"
+rmdir "Contents/"
diff --git a/toolkit/mozapps/update/tests/data/partial_removed-files b/toolkit/mozapps/update/tests/data/partial_removed-files
new file mode 100644
index 000000000..881311b82
--- /dev/null
+++ b/toolkit/mozapps/update/tests/data/partial_removed-files
@@ -0,0 +1,41 @@
+a/b/text0
+a/b/text1
+a/b/3/3text0
+a/b/3/3text1
+a/b/4/4exe0.exe
+a/b/4/4text0
+a/b/4/4text1
+a/b/4/
+a/b/5/5text0
+a/b/5/5text1
+a/b/5/*
+a/b/6/
+a/b/7/*
+a/b/8/80/
+a/b/8/81/
+a/b/8/82/
+a/b/8/83/
+a/b/8/84/
+a/b/8/85/*
+a/b/8/86/*
+a/b/8/87/*
+a/b/8/88/*
+a/b/8/89/*
+a/b/8/80/
+a/b/8/84/*
+a/b/8/85/*
+a/b/8/89/
+a/b/9/90/
+a/b/9/91/
+a/b/9/92/
+a/b/9/93/
+a/b/9/94/
+a/b/9/95/*
+a/b/9/96/*
+a/b/9/97/*
+a/b/9/98/*
+a/b/9/99/*
+a/b/9/90/
+a/b/9/94/*
+a/b/9/95/*
+a/b/9/99/
diff --git a/toolkit/mozapps/update/tests/data/partial_removed-files_mac b/toolkit/mozapps/update/tests/data/partial_removed-files_mac
new file mode 100644
index 000000000..955dc5b34
--- /dev/null
+++ b/toolkit/mozapps/update/tests/data/partial_removed-files_mac
@@ -0,0 +1,41 @@
+Contents/Resources/text0
+Contents/Resources/text1
+Contents/Resources/3/3text0
+Contents/Resources/3/3text1
+Contents/Resources/4/exe0.exe
+Contents/Resources/4/4text0
+Contents/Resources/4/4text1
+Contents/Resources/4/
+Contents/Resources/5/5text0
+Contents/Resources/5/5text1
+Contents/Resources/5/*
+Contents/Resources/6/
+Contents/Resources/7/*
+Contents/Resources/8/80/
+Contents/Resources/8/81/
+Contents/Resources/8/82/
+Contents/Resources/8/83/
+Contents/Resources/8/84/
+Contents/Resources/8/85/*
+Contents/Resources/8/86/*
+Contents/Resources/8/87/*
+Contents/Resources/8/88/*
+Contents/Resources/8/89/*
+Contents/Resources/8/80/
+Contents/Resources/8/84/*
+Contents/Resources/8/85/*
+Contents/Resources/8/89/
+Contents/Resources/9/90/
+Contents/Resources/9/91/
+Contents/Resources/9/92/
+Contents/Resources/9/93/
+Contents/Resources/9/94/
+Contents/Resources/9/95/*
+Contents/Resources/9/96/*
+Contents/Resources/9/97/*
+Contents/Resources/9/98/*
+Contents/Resources/9/99/*
+Contents/Resources/9/90/
+Contents/Resources/9/94/*
+Contents/Resources/9/95/*
+Contents/Resources/9/99/
diff --git a/toolkit/mozapps/update/tests/data/partial_update_manifest b/toolkit/mozapps/update/tests/data/partial_update_manifest
new file mode 100644
index 000000000..8d4e60ed2
--- /dev/null
+++ b/toolkit/mozapps/update/tests/data/partial_update_manifest
@@ -0,0 +1,63 @@
+type "partial"
+add "precomplete"
+add "a/b/searchplugins/searchpluginstext0"
+patch-if "a/b/searchplugins/searchpluginspng1.png" "a/b/searchplugins/searchpluginspng1.png.patch" "a/b/searchplugins/searchpluginspng1.png"
+patch-if "a/b/searchplugins/searchpluginspng0.png" "a/b/searchplugins/searchpluginspng0.png.patch" "a/b/searchplugins/searchpluginspng0.png"
+add-if "a/b/extensions/extensions1" "a/b/extensions/extensions1/extensions1text0"
+patch-if "a/b/extensions/extensions1" "a/b/extensions/extensions1/extensions1png1.png.patch" "a/b/extensions/extensions1/extensions1png1.png"
+patch-if "a/b/extensions/extensions1" "a/b/extensions/extensions1/extensions1png0.png.patch" "a/b/extensions/extensions1/extensions1png0.png"
+add-if "a/b/extensions/extensions0" "a/b/extensions/extensions0/extensions0text0"
+patch-if "a/b/extensions/extensions0" "a/b/extensions/extensions0/extensions0png1.png.patch" "a/b/extensions/extensions0/extensions0png1.png"
+patch-if "a/b/extensions/extensions0" "a/b/extensions/extensions0/extensions0png0.png.patch" "a/b/extensions/extensions0/extensions0png0.png"
+patch "a/b/exe0.exe.patch" "a/b/exe0.exe"
+patch "a/b/0/0exe0.exe.patch" "a/b/0/0exe0.exe"
+add "a/b/0/00/00text0"
+patch "a/b/0/00/00png0.png.patch" "a/b/0/00/00png0.png"
+add "a/b/2/20/20text0"
+add "a/b/2/20/20png0.png"
+add "a/b/0/00/00text2"
+remove "a/b/1/10/10text0"
+remove "a/b/0/00/00text1"
+remove "a/b/text1"
+remove "a/b/text0"
+rmrfdir "a/b/9/99/"
+rmdir "a/b/9/99/"
+rmrfdir "a/b/9/98/"
+rmrfdir "a/b/9/97/"
+rmrfdir "a/b/9/96/"
+rmrfdir "a/b/9/95/"
+rmrfdir "a/b/9/95/"
+rmrfdir "a/b/9/94/"
+rmdir "a/b/9/94/"
+rmdir "a/b/9/93/"
+rmdir "a/b/9/92/"
+rmdir "a/b/9/91/"
+rmdir "a/b/9/90/"
+rmdir "a/b/9/90/"
+rmrfdir "a/b/8/89/"
+rmdir "a/b/8/89/"
+rmrfdir "a/b/8/88/"
+rmrfdir "a/b/8/87/"
+rmrfdir "a/b/8/86/"
+rmrfdir "a/b/8/85/"
+rmrfdir "a/b/8/85/"
+rmrfdir "a/b/8/84/"
+rmdir "a/b/8/84/"
+rmdir "a/b/8/83/"
+rmdir "a/b/8/82/"
+rmdir "a/b/8/81/"
+rmdir "a/b/8/80/"
+rmdir "a/b/8/80/"
+rmrfdir "a/b/7/"
+rmdir "a/b/6/"
+remove "a/b/5/5text1"
+remove "a/b/5/5text0"
+rmrfdir "a/b/5/"
+remove "a/b/4/4text1"
+remove "a/b/4/4text0"
+remove "a/b/4/4exe0.exe"
+rmdir "a/b/4/"
+remove "a/b/3/3text1"
+remove "a/b/3/3text0"
+rmdir "a/b/1/10/"
+rmdir "a/b/1/"
diff --git a/toolkit/mozapps/update/tests/data/replace_log_success b/toolkit/mozapps/update/tests/data/replace_log_success
new file mode 100644
index 000000000..323f1db41
--- /dev/null
+++ b/toolkit/mozapps/update/tests/data/replace_log_success
@@ -0,0 +1,6 @@
+Performing a replace request
+rename_file: proceeding to rename the directory
+rename_file: proceeding to rename the directory
+Now, remove the tmpDir
+succeeded
+calling QuitProgressUI
diff --git a/toolkit/mozapps/update/tests/data/shared.js b/toolkit/mozapps/update/tests/data/shared.js
new file mode 100644
index 000000000..e9a10da06
--- /dev/null
+++ b/toolkit/mozapps/update/tests/data/shared.js
@@ -0,0 +1,632 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* Shared code for xpcshell and mochitests-chrome */
+/* eslint-disable no-undef */
+
+Cu.import("resource://gre/modules/FileUtils.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+const PREF_APP_UPDATE_AUTO = "app.update.auto";
+const PREF_APP_UPDATE_BACKGROUNDERRORS = "app.update.backgroundErrors";
+const PREF_APP_UPDATE_BACKGROUNDMAXERRORS = "app.update.backgroundMaxErrors";
+const PREF_APP_UPDATE_CHANNEL = "app.update.channel";
+const PREF_APP_UPDATE_DOWNLOADBACKGROUNDINTERVAL = "app.update.download.backgroundInterval";
+const PREF_APP_UPDATE_ENABLED = "app.update.enabled";
+const PREF_APP_UPDATE_IDLETIME = "app.update.idletime";
+const PREF_APP_UPDATE_LOG = "app.update.log";
+const PREF_APP_UPDATE_NOTIFIEDUNSUPPORTED = "app.update.notifiedUnsupported";
+const PREF_APP_UPDATE_PROMPTWAITTIME = "app.update.promptWaitTime";
+const PREF_APP_UPDATE_RETRYTIMEOUT = "app.update.socket.retryTimeout";
+const PREF_APP_UPDATE_SERVICE_ENABLED = "app.update.service.enabled";
+const PREF_APP_UPDATE_SILENT = "app.update.silent";
+const PREF_APP_UPDATE_SOCKET_MAXERRORS = "app.update.socket.maxErrors";
+const PREF_APP_UPDATE_STAGING_ENABLED = "app.update.staging.enabled";
+const PREF_APP_UPDATE_URL = "app.update.url";
+const PREF_APP_UPDATE_URL_DETAILS = "app.update.url.details";
+
+const PREFBRANCH_APP_UPDATE_NEVER = "app.update.never.";
+
+const PREFBRANCH_APP_PARTNER = "app.partner.";
+const PREF_DISTRIBUTION_ID = "distribution.id";
+const PREF_DISTRIBUTION_VERSION = "distribution.version";
+const PREF_TOOLKIT_TELEMETRY_ENABLED = "toolkit.telemetry.enabled";
+
+const NS_APP_PROFILE_DIR_STARTUP = "ProfDS";
+const NS_APP_USER_PROFILE_50_DIR = "ProfD";
+const NS_GRE_DIR = "GreD";
+const NS_GRE_BIN_DIR = "GreBinD";
+const NS_XPCOM_CURRENT_PROCESS_DIR = "XCurProcD";
+const XRE_EXECUTABLE_FILE = "XREExeF";
+const XRE_UPDATE_ROOT_DIR = "UpdRootD";
+
+const DIR_PATCH = "0";
+const DIR_TOBEDELETED = "tobedeleted";
+const DIR_UPDATES = "updates";
+const DIR_UPDATED = IS_MACOSX ? "Updated.app" : "updated";
+
+const FILE_ACTIVE_UPDATE_XML = "active-update.xml";
+const FILE_APPLICATION_INI = "application.ini";
+const FILE_BACKUP_UPDATE_LOG = "backup-update.log";
+const FILE_LAST_UPDATE_LOG = "last-update.log";
+const FILE_UPDATE_SETTINGS_INI = "update-settings.ini";
+const FILE_UPDATE_SETTINGS_INI_BAK = "update-settings.ini.bak";
+const FILE_UPDATER_INI = "updater.ini";
+const FILE_UPDATES_XML = "updates.xml";
+const FILE_UPDATE_LOG = "update.log";
+const FILE_UPDATE_MAR = "update.mar";
+const FILE_UPDATE_STATUS = "update.status";
+const FILE_UPDATE_TEST = "update.test";
+const FILE_UPDATE_VERSION = "update.version";
+
+const UPDATE_SETTINGS_CONTENTS = "[Settings]\n" +
+ "ACCEPTED_MAR_CHANNEL_IDS=xpcshell-test\n";
+
+const PR_RDWR = 0x04;
+const PR_CREATE_FILE = 0x08;
+const PR_TRUNCATE = 0x20;
+
+const DEFAULT_UPDATE_VERSION = "999999.0";
+
+var gChannel;
+
+/* import-globals-from ../data/sharedUpdateXML.js */
+Services.scriptloader.loadSubScript(DATA_URI_SPEC + "sharedUpdateXML.js", this);
+
+const PERMS_FILE = FileUtils.PERMS_FILE;
+const PERMS_DIRECTORY = FileUtils.PERMS_DIRECTORY;
+
+const MODE_WRONLY = FileUtils.MODE_WRONLY;
+const MODE_CREATE = FileUtils.MODE_CREATE;
+const MODE_APPEND = FileUtils.MODE_APPEND;
+const MODE_TRUNCATE = FileUtils.MODE_TRUNCATE;
+
+const URI_UPDATES_PROPERTIES = "chrome://mozapps/locale/update/updates.properties";
+const gUpdateBundle = Services.strings.createBundle(URI_UPDATES_PROPERTIES);
+
+XPCOMUtils.defineLazyGetter(this, "gAUS", function test_gAUS() {
+ return Cc["@mozilla.org/updates/update-service;1"].
+ getService(Ci.nsIApplicationUpdateService).
+ QueryInterface(Ci.nsITimerCallback).
+ QueryInterface(Ci.nsIObserver).
+ QueryInterface(Ci.nsIUpdateCheckListener);
+});
+
+XPCOMUtils.defineLazyServiceGetter(this, "gUpdateManager",
+ "@mozilla.org/updates/update-manager;1",
+ "nsIUpdateManager");
+
+XPCOMUtils.defineLazyGetter(this, "gUpdateChecker", function test_gUC() {
+ return Cc["@mozilla.org/updates/update-checker;1"].
+ createInstance(Ci.nsIUpdateChecker);
+});
+
+XPCOMUtils.defineLazyGetter(this, "gUP", function test_gUP() {
+ return Cc["@mozilla.org/updates/update-prompt;1"].
+ createInstance(Ci.nsIUpdatePrompt);
+});
+
+XPCOMUtils.defineLazyGetter(this, "gDefaultPrefBranch", function test_gDPB() {
+ return Services.prefs.getDefaultBranch(null);
+});
+
+XPCOMUtils.defineLazyGetter(this, "gPrefRoot", function test_gPR() {
+ return Services.prefs.getBranch(null);
+});
+
+XPCOMUtils.defineLazyServiceGetter(this, "gEnv",
+ "@mozilla.org/process/environment;1",
+ "nsIEnvironment");
+
+XPCOMUtils.defineLazyGetter(this, "gZipW", function test_gZipW() {
+ return Cc["@mozilla.org/zipwriter;1"].
+ createInstance(Ci.nsIZipWriter);
+});
+
+/* Triggers post-update processing */
+function testPostUpdateProcessing() {
+ gAUS.observe(null, "test-post-update-processing", "");
+}
+
+/* Initializes the update service stub */
+function initUpdateServiceStub() {
+ Cc["@mozilla.org/updates/update-service-stub;1"].
+ createInstance(Ci.nsISupports);
+}
+
+/* Reloads the update metadata from disk */
+function reloadUpdateManagerData() {
+ gUpdateManager.QueryInterface(Ci.nsIObserver).
+ observe(null, "um-reload-update-data", "");
+}
+
+const observer = {
+ observe: function(aSubject, aTopic, aData) {
+ if (aTopic == "nsPref:changed" && aData == PREF_APP_UPDATE_CHANNEL) {
+ let channel = gDefaultPrefBranch.getCharPref(PREF_APP_UPDATE_CHANNEL);
+ if (channel != gChannel) {
+ debugDump("Changing channel from " + channel + " to " + gChannel);
+ gDefaultPrefBranch.setCharPref(PREF_APP_UPDATE_CHANNEL, gChannel);
+ }
+ }
+ },
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver])
+};
+
+/**
+ * Sets the app.update.channel preference.
+ *
+ * @param aChannel
+ * The update channel.
+ */
+function setUpdateChannel(aChannel) {
+ gChannel = aChannel;
+ debugDump("setting default pref " + PREF_APP_UPDATE_CHANNEL + " to " + gChannel);
+ gDefaultPrefBranch.setCharPref(PREF_APP_UPDATE_CHANNEL, gChannel);
+ gPrefRoot.addObserver(PREF_APP_UPDATE_CHANNEL, observer, false);
+}
+
+/**
+ * Sets the app.update.url default preference.
+ *
+ * @param aURL
+ * The update url. If not specified 'URL_HOST + "/update.xml"' will be
+ * used.
+ */
+function setUpdateURL(aURL) {
+ let url = aURL ? aURL : URL_HOST + "/update.xml";
+ debugDump("setting " + PREF_APP_UPDATE_URL + " to " + url);
+ gDefaultPrefBranch.setCharPref(PREF_APP_UPDATE_URL, url);
+}
+
+/**
+ * Returns either the active or regular update database XML file.
+ *
+ * @param isActiveUpdate
+ * If true this will return the active-update.xml otherwise it will
+ * return the updates.xml file.
+ */
+function getUpdatesXMLFile(aIsActiveUpdate) {
+ let file = getUpdatesRootDir();
+ file.append(aIsActiveUpdate ? FILE_ACTIVE_UPDATE_XML : FILE_UPDATES_XML);
+ return file;
+}
+
+/**
+ * Writes the updates specified to either the active-update.xml or the
+ * updates.xml.
+ *
+ * @param aContent
+ * The updates represented as a string to write to the XML file.
+ * @param isActiveUpdate
+ * If true this will write to the active-update.xml otherwise it will
+ * write to the updates.xml file.
+ */
+function writeUpdatesToXMLFile(aContent, aIsActiveUpdate) {
+ writeFile(getUpdatesXMLFile(aIsActiveUpdate), aContent);
+}
+
+/**
+ * Writes the current update operation/state to a file in the patch
+ * directory, indicating to the patching system that operations need
+ * to be performed.
+ *
+ * @param aStatus
+ * The status value to write.
+ */
+function writeStatusFile(aStatus) {
+ let file = getUpdatesPatchDir();
+ file.append(FILE_UPDATE_STATUS);
+ writeFile(file, aStatus + "\n");
+}
+
+/**
+ * Writes the current update version to a file in the patch directory,
+ * indicating to the patching system the version of the update.
+ *
+ * @param aVersion
+ * The version value to write.
+ */
+function writeVersionFile(aVersion) {
+ let file = getUpdatesPatchDir();
+ file.append(FILE_UPDATE_VERSION);
+ writeFile(file, aVersion + "\n");
+}
+
+/**
+ * Gets the root directory for the updates directory.
+ *
+ * @return nsIFile for the updates root directory.
+ */
+function getUpdatesRootDir() {
+ return Services.dirsvc.get(XRE_UPDATE_ROOT_DIR, Ci.nsIFile);
+}
+
+/**
+ * Gets the updates directory.
+ *
+ * @return nsIFile for the updates directory.
+ */
+function getUpdatesDir() {
+ let dir = getUpdatesRootDir();
+ dir.append(DIR_UPDATES);
+ return dir;
+}
+
+/**
+ * Gets the directory for update patches.
+ *
+ * @return nsIFile for the updates directory.
+ */
+function getUpdatesPatchDir() {
+ let dir = getUpdatesDir();
+ dir.append(DIR_PATCH);
+ return dir;
+}
+
+/**
+ * Writes text to a file. This will replace existing text if the file exists
+ * and create the file if it doesn't exist.
+ *
+ * @param aFile
+ * The file to write to. Will be created if it doesn't exist.
+ * @param aText
+ * The text to write to the file. If there is existing text it will be
+ * replaced.
+ */
+function writeFile(aFile, aText) {
+ let fos = Cc["@mozilla.org/network/file-output-stream;1"].
+ createInstance(Ci.nsIFileOutputStream);
+ if (!aFile.exists()) {
+ aFile.create(Ci.nsILocalFile.NORMAL_FILE_TYPE, PERMS_FILE);
+ }
+ fos.init(aFile, MODE_WRONLY | MODE_CREATE | MODE_TRUNCATE, PERMS_FILE, 0);
+ fos.write(aText, aText.length);
+ fos.close();
+}
+
+/**
+ * Reads the current update operation/state in the status file in the patch
+ * directory including the error code if it is present.
+ *
+ * @return The status value.
+ */
+function readStatusFile() {
+ let file = getUpdatesPatchDir();
+ file.append(FILE_UPDATE_STATUS);
+
+ if (!file.exists()) {
+ debugDump("update status file does not exists! Path: " + file.path);
+ return STATE_NONE;
+ }
+
+ return readFile(file).split("\n")[0];
+}
+
+/**
+ * Reads the current update operation/state in the status file in the patch
+ * directory without the error code if it is present.
+ *
+ * @return The state value.
+ */
+function readStatusState() {
+ return readStatusFile().split(": ")[0];
+}
+
+/**
+ * Reads the current update operation/state in the status file in the patch
+ * directory with the error code.
+ *
+ * @return The state value.
+ */
+function readStatusFailedCode() {
+ return readStatusFile().split(": ")[1];
+}
+
+/**
+ * Reads text from a file and returns the string.
+ *
+ * @param aFile
+ * The file to read from.
+ * @return The string of text read from the file.
+ */
+function readFile(aFile) {
+ let fis = Cc["@mozilla.org/network/file-input-stream;1"].
+ createInstance(Ci.nsIFileInputStream);
+ if (!aFile.exists()) {
+ return null;
+ }
+ // Specifying -1 for ioFlags will open the file with the default of PR_RDONLY.
+ // Specifying -1 for perm will open the file with the default of 0.
+ fis.init(aFile, -1, -1, Ci.nsIFileInputStream.CLOSE_ON_EOF);
+ let sis = Cc["@mozilla.org/scriptableinputstream;1"].
+ createInstance(Ci.nsIScriptableInputStream);
+ sis.init(fis);
+ let text = sis.read(sis.available());
+ sis.close();
+ return text;
+}
+
+/**
+ * Reads the binary contents of a file and returns it as a string.
+ *
+ * @param aFile
+ * The file to read from.
+ * @return The contents of the file as a string.
+ */
+function readFileBytes(aFile) {
+ debugDump("attempting to read file, path: " + aFile.path);
+ let fis = Cc["@mozilla.org/network/file-input-stream;1"].
+ createInstance(Ci.nsIFileInputStream);
+ // Specifying -1 for ioFlags will open the file with the default of PR_RDONLY.
+ // Specifying -1 for perm will open the file with the default of 0.
+ fis.init(aFile, -1, -1, Ci.nsIFileInputStream.CLOSE_ON_EOF);
+ let bis = Cc["@mozilla.org/binaryinputstream;1"].
+ createInstance(Ci.nsIBinaryInputStream);
+ bis.setInputStream(fis);
+ let data = [];
+ let count = fis.available();
+ while (count > 0) {
+ let bytes = bis.readByteArray(Math.min(65535, count));
+ data.push(String.fromCharCode.apply(null, bytes));
+ count -= bytes.length;
+ if (bytes.length == 0) {
+ throw "Nothing read from input stream!";
+ }
+ }
+ data.join('');
+ fis.close();
+ return data.toString();
+}
+
+/* Returns human readable status text from the updates.properties bundle */
+function getStatusText(aErrCode) {
+ return getString("check_error-" + aErrCode);
+}
+
+/* Returns a string from the updates.properties bundle */
+function getString(aName) {
+ try {
+ return gUpdateBundle.GetStringFromName(aName);
+ } catch (e) {
+ }
+ return null;
+}
+
+/**
+ * Gets the file extension for an nsIFile.
+ *
+ * @param aFile
+ * The file to get the file extension for.
+ * @return The file extension.
+ */
+function getFileExtension(aFile) {
+ return Services.io.newFileURI(aFile).QueryInterface(Ci.nsIURL).
+ fileExtension;
+}
+
+/**
+ * Removes the updates.xml file, active-update.xml file, and all files and
+ * sub-directories in the updates directory except for the "0" sub-directory.
+ * This prevents some tests from failing due to files being left behind when the
+ * tests are interrupted.
+ */
+function removeUpdateDirsAndFiles() {
+ let file = getUpdatesXMLFile(true);
+ try {
+ if (file.exists()) {
+ file.remove(false);
+ }
+ } catch (e) {
+ logTestInfo("Unable to remove file. Path: " + file.path +
+ ", Exception: " + e);
+ }
+
+ file = getUpdatesXMLFile(false);
+ try {
+ if (file.exists()) {
+ file.remove(false);
+ }
+ } catch (e) {
+ logTestInfo("Unable to remove file. Path: " + file.path +
+ ", Exception: " + e);
+ }
+
+ // This fails sporadically on Mac OS X so wrap it in a try catch
+ let updatesDir = getUpdatesDir();
+ try {
+ cleanUpdatesDir(updatesDir);
+ } catch (e) {
+ logTestInfo("Unable to remove files / directories from directory. Path: " +
+ updatesDir.path + ", Exception: " + e);
+ }
+}
+
+/**
+ * Removes all files and sub-directories in the updates directory except for
+ * the "0" sub-directory.
+ *
+ * @param aDir
+ * nsIFile for the directory to be deleted.
+ */
+function cleanUpdatesDir(aDir) {
+ if (!aDir.exists()) {
+ return;
+ }
+
+ let dirEntries = aDir.directoryEntries;
+ while (dirEntries.hasMoreElements()) {
+ let entry = dirEntries.getNext().QueryInterface(Ci.nsIFile);
+
+ if (entry.isDirectory()) {
+ if (entry.leafName == DIR_PATCH && entry.parent.leafName == DIR_UPDATES) {
+ cleanUpdatesDir(entry);
+ entry.permissions = PERMS_DIRECTORY;
+ } else {
+ try {
+ entry.remove(true);
+ return;
+ } catch (e) {
+ }
+ cleanUpdatesDir(entry);
+ entry.permissions = PERMS_DIRECTORY;
+ try {
+ entry.remove(true);
+ } catch (e) {
+ logTestInfo("cleanUpdatesDir: unable to remove directory. Path: " +
+ entry.path + ", Exception: " + e);
+ throw (e);
+ }
+ }
+ } else {
+ entry.permissions = PERMS_FILE;
+ try {
+ entry.remove(false);
+ } catch (e) {
+ logTestInfo("cleanUpdatesDir: unable to remove file. Path: " +
+ entry.path + ", Exception: " + e);
+ throw (e);
+ }
+ }
+ }
+}
+
+/**
+ * Deletes a directory and its children. First it tries nsIFile::Remove(true).
+ * If that fails it will fall back to recursing, setting the appropriate
+ * permissions, and deleting the current entry.
+ *
+ * @param aDir
+ * nsIFile for the directory to be deleted.
+ */
+function removeDirRecursive(aDir) {
+ if (!aDir.exists()) {
+ return;
+ }
+
+ try {
+ debugDump("attempting to remove directory. Path: " + aDir.path);
+ aDir.remove(true);
+ return;
+ } catch (e) {
+ logTestInfo("non-fatal error removing directory. Exception: " + e);
+ }
+
+ let dirEntries = aDir.directoryEntries;
+ while (dirEntries.hasMoreElements()) {
+ let entry = dirEntries.getNext().QueryInterface(Ci.nsIFile);
+
+ if (entry.isDirectory()) {
+ removeDirRecursive(entry);
+ } else {
+ entry.permissions = PERMS_FILE;
+ try {
+ debugDump("attempting to remove file. Path: " + entry.path);
+ entry.remove(false);
+ } catch (e) {
+ logTestInfo("error removing file. Exception: " + e);
+ throw (e);
+ }
+ }
+ }
+
+ aDir.permissions = PERMS_DIRECTORY;
+ try {
+ debugDump("attempting to remove directory. Path: " + aDir.path);
+ aDir.remove(true);
+ } catch (e) {
+ logTestInfo("error removing directory. Exception: " + e);
+ throw (e);
+ }
+}
+
+/**
+ * Returns the directory for the currently running process. This is used to
+ * clean up after the tests and to locate the active-update.xml and updates.xml
+ * files.
+ *
+ * @return nsIFile for the current process directory.
+ */
+function getCurrentProcessDir() {
+ return Services.dirsvc.get(NS_XPCOM_CURRENT_PROCESS_DIR, Ci.nsIFile);
+}
+
+/**
+ * Gets the application base directory.
+ *
+ * @return nsIFile object for the application base directory.
+ */
+function getAppBaseDir() {
+ return Services.dirsvc.get(XRE_EXECUTABLE_FILE, Ci.nsIFile).parent;
+}
+
+/**
+ * Returns the Gecko Runtime Engine directory where files other than executable
+ * binaries are located. On Mac OS X this will be <bundle>/Contents/Resources/
+ * and the installation directory on all other platforms.
+ *
+ * @return nsIFile for the Gecko Runtime Engine directory.
+ */
+function getGREDir() {
+ return Services.dirsvc.get(NS_GRE_DIR, Ci.nsIFile);
+}
+
+/**
+ * Returns the Gecko Runtime Engine Binary directory where the executable
+ * binaries are located such as the updater binary (Windows and Linux) or
+ * updater package (Mac OS X). On Mac OS X this will be
+ * <bundle>/Contents/MacOS/ and the installation directory on all other
+ * platforms.
+ *
+ * @return nsIFile for the Gecko Runtime Engine Binary directory.
+ */
+function getGREBinDir() {
+ return Services.dirsvc.get(NS_GRE_BIN_DIR, Ci.nsIFile);
+}
+
+/**
+ * Logs TEST-INFO messages.
+ *
+ * @param aText
+ * The text to log.
+ * @param aCaller (optional)
+ * An optional Components.stack.caller. If not specified
+ * Components.stack.caller will be used.
+ */
+function logTestInfo(aText, aCaller) {
+ let caller = aCaller ? aCaller : Components.stack.caller;
+ let now = new Date();
+ let hh = now.getHours();
+ let mm = now.getMinutes();
+ let ss = now.getSeconds();
+ let ms = now.getMilliseconds();
+ let time = (hh < 10 ? "0" + hh : hh) + ":" +
+ (mm < 10 ? "0" + mm : mm) + ":" +
+ (ss < 10 ? "0" + ss : ss) + ":";
+ if (ms < 10) {
+ time += "00";
+ } else if (ms < 100) {
+ time += "0";
+ }
+ time += ms;
+ let msg = time + " | TEST-INFO | " + caller.filename + " | [" + caller.name +
+ " : " + caller.lineNumber + "] " + aText;
+ LOG_FUNCTION(msg);
+}
+
+/**
+ * Logs TEST-INFO messages when DEBUG_AUS_TEST evaluates to true.
+ *
+ * @param aText
+ * The text to log.
+ * @param aCaller (optional)
+ * An optional Components.stack.caller. If not specified
+ * Components.stack.caller will be used.
+ */
+function debugDump(aText, aCaller) {
+ if (DEBUG_AUS_TEST) {
+ let caller = aCaller ? aCaller : Components.stack.caller;
+ logTestInfo(aText, caller);
+ }
+}
diff --git a/toolkit/mozapps/update/tests/data/sharedUpdateXML.js b/toolkit/mozapps/update/tests/data/sharedUpdateXML.js
new file mode 100644
index 000000000..3aa01eff4
--- /dev/null
+++ b/toolkit/mozapps/update/tests/data/sharedUpdateXML.js
@@ -0,0 +1,364 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+/**
+ * Helper functions for creating xml strings used by application update tests.
+ *
+ * !IMPORTANT - This file contains everything needed (along with dependencies)
+ * by the updates.sjs file used by the mochitest-chrome tests. Since xpcshell
+ * used by the http server is launched with -v 170 this file must not use
+ * features greater than JavaScript 1.7.
+ */
+
+/* eslint-disable no-undef */
+
+const FILE_SIMPLE_MAR = "simple.mar";
+const SIZE_SIMPLE_MAR = "1031";
+const MD5_HASH_SIMPLE_MAR = "1f8c038577bb6845d94ccec4999113ee";
+const SHA1_HASH_SIMPLE_MAR = "5d49a672c87f10f31d7e326349564a11272a028b";
+const SHA256_HASH_SIMPLE_MAR = "1aabbed5b1dd6e16e139afc5b43d479e254e0c26" +
+ "3c8fb9249c0a1bb93071c5fb";
+const SHA384_HASH_SIMPLE_MAR = "26615014ea034af32ef5651492d5f493f5a7a1a48522e" +
+ "d24c366442a5ec21d5ef02e23fb58d79729b8ca2f9541" +
+ "99dd53";
+const SHA512_HASH_SIMPLE_MAR = "922e5ae22081795f6e8d65a3c508715c9a314054179a8" +
+ "bbfe5f50dc23919ad89888291bc0a07586ab17dd0304a" +
+ "b5347473601127571c66f61f5080348e05c36b";
+
+const STATE_NONE = "null";
+const STATE_DOWNLOADING = "downloading";
+const STATE_PENDING = "pending";
+const STATE_PENDING_SVC = "pending-service";
+const STATE_APPLYING = "applying";
+const STATE_APPLIED = "applied";
+const STATE_APPLIED_SVC = "applied-service";
+const STATE_SUCCEEDED = "succeeded";
+const STATE_DOWNLOAD_FAILED = "download-failed";
+const STATE_FAILED = "failed";
+
+const LOADSOURCE_ERROR_WRONG_SIZE = 2;
+const CRC_ERROR = 4;
+const READ_ERROR = 6;
+const WRITE_ERROR = 7;
+const MAR_CHANNEL_MISMATCH_ERROR = 22;
+const VERSION_DOWNGRADE_ERROR = 23;
+const SERVICE_COULD_NOT_COPY_UPDATER = 49;
+const SERVICE_INVALID_APPLYTO_DIR_STAGED_ERROR = 52;
+const SERVICE_INVALID_APPLYTO_DIR_ERROR = 54;
+const SERVICE_INVALID_INSTALL_DIR_PATH_ERROR = 55;
+const SERVICE_INVALID_WORKING_DIR_PATH_ERROR = 56;
+const INVALID_APPLYTO_DIR_STAGED_ERROR = 72;
+const INVALID_APPLYTO_DIR_ERROR = 74;
+const INVALID_INSTALL_DIR_PATH_ERROR = 75;
+const INVALID_WORKING_DIR_PATH_ERROR = 76;
+const INVALID_CALLBACK_PATH_ERROR = 77;
+const INVALID_CALLBACK_DIR_ERROR = 78;
+
+const STATE_FAILED_DELIMETER = ": ";
+
+const STATE_FAILED_LOADSOURCE_ERROR_WRONG_SIZE =
+ STATE_FAILED + STATE_FAILED_DELIMETER + LOADSOURCE_ERROR_WRONG_SIZE;
+const STATE_FAILED_CRC_ERROR =
+ STATE_FAILED + STATE_FAILED_DELIMETER + CRC_ERROR;
+const STATE_FAILED_READ_ERROR =
+ STATE_FAILED + STATE_FAILED_DELIMETER + READ_ERROR;
+const STATE_FAILED_WRITE_ERROR =
+ STATE_FAILED + STATE_FAILED_DELIMETER + WRITE_ERROR;
+const STATE_FAILED_MAR_CHANNEL_MISMATCH_ERROR =
+ STATE_FAILED + STATE_FAILED_DELIMETER + MAR_CHANNEL_MISMATCH_ERROR;
+const STATE_FAILED_VERSION_DOWNGRADE_ERROR =
+ STATE_FAILED + STATE_FAILED_DELIMETER + VERSION_DOWNGRADE_ERROR;
+const STATE_FAILED_SERVICE_COULD_NOT_COPY_UPDATER =
+ STATE_FAILED + STATE_FAILED_DELIMETER + SERVICE_COULD_NOT_COPY_UPDATER
+const STATE_FAILED_SERVICE_INVALID_APPLYTO_DIR_STAGED_ERROR =
+ STATE_FAILED + STATE_FAILED_DELIMETER + SERVICE_INVALID_APPLYTO_DIR_STAGED_ERROR;
+const STATE_FAILED_SERVICE_INVALID_APPLYTO_DIR_ERROR =
+ STATE_FAILED + STATE_FAILED_DELIMETER + SERVICE_INVALID_APPLYTO_DIR_ERROR;
+const STATE_FAILED_SERVICE_INVALID_INSTALL_DIR_PATH_ERROR =
+ STATE_FAILED + STATE_FAILED_DELIMETER + SERVICE_INVALID_INSTALL_DIR_PATH_ERROR;
+const STATE_FAILED_SERVICE_INVALID_WORKING_DIR_PATH_ERROR =
+ STATE_FAILED + STATE_FAILED_DELIMETER + SERVICE_INVALID_WORKING_DIR_PATH_ERROR;
+const STATE_FAILED_INVALID_APPLYTO_DIR_STAGED_ERROR =
+ STATE_FAILED + STATE_FAILED_DELIMETER + INVALID_APPLYTO_DIR_STAGED_ERROR;
+const STATE_FAILED_INVALID_APPLYTO_DIR_ERROR =
+ STATE_FAILED + STATE_FAILED_DELIMETER + INVALID_APPLYTO_DIR_ERROR;
+const STATE_FAILED_INVALID_INSTALL_DIR_PATH_ERROR =
+ STATE_FAILED + STATE_FAILED_DELIMETER + INVALID_INSTALL_DIR_PATH_ERROR;
+const STATE_FAILED_INVALID_WORKING_DIR_PATH_ERROR =
+ STATE_FAILED + STATE_FAILED_DELIMETER + INVALID_WORKING_DIR_PATH_ERROR;
+const STATE_FAILED_INVALID_CALLBACK_PATH_ERROR =
+ STATE_FAILED + STATE_FAILED_DELIMETER + INVALID_CALLBACK_PATH_ERROR;
+const STATE_FAILED_INVALID_CALLBACK_DIR_ERROR =
+ STATE_FAILED + STATE_FAILED_DELIMETER + INVALID_CALLBACK_DIR_ERROR;
+
+/**
+ * Constructs a string representing a remote update xml file.
+ *
+ * @param aUpdates
+ * The string representing the update elements.
+ * @return The string representing a remote update xml file.
+ */
+function getRemoteUpdatesXMLString(aUpdates) {
+ return "<?xml version=\"1.0\"?>\n" +
+ "<updates>\n" +
+ aUpdates +
+ "</updates>\n";
+}
+
+/**
+ * Constructs a string representing an update element for a remote update xml
+ * file. See getUpdateString for parameter information not provided below.
+ *
+ * @param aPatches
+ * String representing the application update patches.
+ * @return The string representing an update element for an update xml file.
+ */
+function getRemoteUpdateString(aPatches, aType, aName, aDisplayVersion,
+ aAppVersion, aBuildID, aDetailsURL, aShowPrompt,
+ aShowNeverForVersion, aPromptWaitTime,
+ aBackgroundInterval, aCustom1, aCustom2) {
+ return getUpdateString(aType, aName, aDisplayVersion, aAppVersion,
+ aBuildID, aDetailsURL, aShowPrompt,
+ aShowNeverForVersion, aPromptWaitTime,
+ aBackgroundInterval, aCustom1, aCustom2) + ">\n" +
+ aPatches +
+ " </update>\n";
+}
+
+/**
+ * Constructs a string representing a patch element for a remote update xml
+ * file. See getPatchString for parameter information not provided below.
+ *
+ * @return The string representing a patch element for a remote update xml file.
+ */
+function getRemotePatchString(aType, aURL, aHashFunction, aHashValue, aSize) {
+ return getPatchString(aType, aURL, aHashFunction, aHashValue, aSize) +
+ "/>\n";
+}
+
+/**
+ * Constructs a string representing a local update xml file.
+ *
+ * @param aUpdates
+ * The string representing the update elements.
+ * @return The string representing a local update xml file.
+ */
+function getLocalUpdatesXMLString(aUpdates) {
+ if (!aUpdates || aUpdates == "") {
+ return "<updates xmlns=\"http://www.mozilla.org/2005/app-update\"/>";
+ }
+ return ("<updates xmlns=\"http://www.mozilla.org/2005/app-update\">" +
+ aUpdates +
+ "</updates>").replace(/>\s+\n*</g, '><');
+}
+
+/**
+ * Constructs a string representing an update element for a local update xml
+ * file. See getUpdateString for parameter information not provided below.
+ *
+ * @param aPatches
+ * String representing the application update patches.
+ * @param aServiceURL (optional)
+ * The update's xml url.
+ * If not specified it will default to 'http://test_service/'.
+ * @param aIsCompleteUpdate (optional)
+ * The string 'true' if this update was a complete update or the string
+ * 'false' if this update was a partial update.
+ * If not specified it will default to 'true'.
+ * @param aChannel (optional)
+ * The update channel name.
+ * If not specified it will default to the default preference value of
+ * app.update.channel.
+ * @param aForegroundDownload (optional)
+ * The string 'true' if this update was manually downloaded or the
+ * string 'false' if this update was automatically downloaded.
+ * If not specified it will default to 'true'.
+ * @param aPreviousAppVersion (optional)
+ * The application version prior to applying the update.
+ * If not specified it will not be present.
+ * @return The string representing an update element for an update xml file.
+ */
+function getLocalUpdateString(aPatches, aType, aName, aDisplayVersion,
+ aAppVersion, aBuildID, aDetailsURL, aServiceURL,
+ aInstallDate, aStatusText, aIsCompleteUpdate,
+ aChannel, aForegroundDownload, aShowPrompt,
+ aShowNeverForVersion, aPromptWaitTime,
+ aBackgroundInterval, aPreviousAppVersion,
+ aCustom1, aCustom2) {
+ let serviceURL = aServiceURL ? aServiceURL : "http://test_service/";
+ let installDate = aInstallDate ? aInstallDate : "1238441400314";
+ let statusText = aStatusText ? aStatusText : "Install Pending";
+ let isCompleteUpdate =
+ typeof aIsCompleteUpdate == "string" ? aIsCompleteUpdate : "true";
+ let channel = aChannel ? aChannel
+ : gDefaultPrefBranch.getCharPref(PREF_APP_UPDATE_CHANNEL);
+ let foregroundDownload =
+ typeof aForegroundDownload == "string" ? aForegroundDownload : "true";
+ let previousAppVersion = aPreviousAppVersion ? "previousAppVersion=\"" +
+ aPreviousAppVersion + "\" "
+ : "";
+ return getUpdateString(aType, aName, aDisplayVersion, aAppVersion, aBuildID,
+ aDetailsURL, aShowPrompt, aShowNeverForVersion,
+ aPromptWaitTime, aBackgroundInterval, aCustom1, aCustom2) +
+ " " +
+ previousAppVersion +
+ "serviceURL=\"" + serviceURL + "\" " +
+ "installDate=\"" + installDate + "\" " +
+ "statusText=\"" + statusText + "\" " +
+ "isCompleteUpdate=\"" + isCompleteUpdate + "\" " +
+ "channel=\"" + channel + "\" " +
+ "foregroundDownload=\"" + foregroundDownload + "\">" +
+ aPatches +
+ " </update>";
+}
+
+/**
+ * Constructs a string representing a patch element for a local update xml file.
+ * See getPatchString for parameter information not provided below.
+ *
+ * @param aSelected (optional)
+ * Whether this patch is selected represented or not. The string 'true'
+ * denotes selected and the string 'false' denotes not selected.
+ * If not specified it will default to the string 'true'.
+ * @param aState (optional)
+ * The patch's state.
+ * If not specified it will default to STATE_SUCCEEDED.
+ * @return The string representing a patch element for a local update xml file.
+ */
+function getLocalPatchString(aType, aURL, aHashFunction, aHashValue, aSize,
+ aSelected, aState) {
+ let selected = typeof aSelected == "string" ? aSelected : "true";
+ let state = aState ? aState : STATE_SUCCEEDED;
+ return getPatchString(aType, aURL, aHashFunction, aHashValue, aSize) + " " +
+ "selected=\"" + selected + "\" " +
+ "state=\"" + state + "\"/>\n";
+}
+
+/**
+ * Constructs a string representing an update element for a remote update xml
+ * file.
+ *
+ * @param aType (optional)
+ * The update's type which should be major or minor. If not specified it
+ * will default to 'major'.
+ * @param aName (optional)
+ * The update's name.
+ * If not specified it will default to 'App Update Test'.
+ * @param aDisplayVersion (optional)
+ * The update's display version.
+ * If not specified it will default to 'version #' where # is the value
+ * of DEFAULT_UPDATE_VERSION.
+ * @param aAppVersion (optional)
+ * The update's application version.
+ * If not specified it will default to the value of
+ * DEFAULT_UPDATE_VERSION.
+ * @param aBuildID (optional)
+ * The update's build id.
+ * If not specified it will default to '20080811053724'.
+ * @param aDetailsURL (optional)
+ * The update's details url.
+ * If not specified it will default to 'http://test_details/' due to due
+ * to bug 470244.
+ * @param aShowPrompt (optional)
+ * Whether to show the prompt for the update when auto update is
+ * enabled.
+ * If not specified it will not be present and the update service will
+ * default to false.
+ * @param aShowNeverForVersion (optional)
+ * Whether to show the 'No Thanks' button in the update prompt.
+ * If not specified it will not be present and the update service will
+ * default to false.
+ * @param aPromptWaitTime (optional)
+ * Override for the app.update.promptWaitTime preference.
+ * @param aBackgroundInterval (optional)
+ * Override for the app.update.download.backgroundInterval preference.
+ * @param aCustom1 (optional)
+ * A custom attribute name and attribute value to add to the xml.
+ * Example: custom1_attribute="custom1 value"
+ * If not specified it will not be present.
+ * @param aCustom2 (optional)
+ * A custom attribute name and attribute value to add to the xml.
+ * Example: custom2_attribute="custom2 value"
+ * If not specified it will not be present.
+ * @return The string representing an update element for an update xml file.
+ */
+function getUpdateString(aType, aName, aDisplayVersion, aAppVersion, aBuildID,
+ aDetailsURL, aShowPrompt, aShowNeverForVersion,
+ aPromptWaitTime, aBackgroundInterval, aCustom1,
+ aCustom2) {
+ let type = aType ? aType : "major";
+ let name = aName ? aName : "App Update Test";
+ let displayVersion = aDisplayVersion ? "displayVersion=\"" +
+ aDisplayVersion + "\" "
+ : "";
+ let appVersion = "appVersion=\"" +
+ (aAppVersion ? aAppVersion : DEFAULT_UPDATE_VERSION) +
+ "\" ";
+ let buildID = aBuildID ? aBuildID : "20080811053724";
+ // XXXrstrong - not specifying a detailsURL will cause a leak due to bug 470244
+// let detailsURL = aDetailsURL ? "detailsURL=\"" + aDetailsURL + "\" " : "";
+ let detailsURL = "detailsURL=\"" +
+ (aDetailsURL ? aDetailsURL
+ : "http://test_details/") + "\" ";
+ let showPrompt = aShowPrompt ? "showPrompt=\"" + aShowPrompt + "\" " : "";
+ let showNeverForVersion = aShowNeverForVersion ? "showNeverForVersion=\"" +
+ aShowNeverForVersion + "\" "
+ : "";
+ let promptWaitTime = aPromptWaitTime ? "promptWaitTime=\"" + aPromptWaitTime +
+ "\" "
+ : "";
+ let backgroundInterval = aBackgroundInterval ? "backgroundInterval=\"" +
+ aBackgroundInterval + "\" "
+ : "";
+ let custom1 = aCustom1 ? aCustom1 + " " : "";
+ let custom2 = aCustom2 ? aCustom2 + " " : "";
+ return " <update type=\"" + type + "\" " +
+ "name=\"" + name + "\" " +
+ displayVersion +
+ appVersion +
+ detailsURL +
+ showPrompt +
+ showNeverForVersion +
+ promptWaitTime +
+ backgroundInterval +
+ custom1 +
+ custom2 +
+ "buildID=\"" + buildID + "\"";
+}
+
+/**
+ * Constructs a string representing a patch element for an update xml file.
+ *
+ * @param aType (optional)
+ * The patch's type which should be complete or partial.
+ * If not specified it will default to 'complete'.
+ * @param aURL (optional)
+ * The patch's url to the mar file.
+ * If not specified it will default to the value of:
+ * gURLData + FILE_SIMPLE_MAR
+ * @param aHashFunction (optional)
+ * The patch's hash function used to verify the mar file.
+ * If not specified it will default to 'MD5'.
+ * @param aHashValue (optional)
+ * The patch's hash value used to verify the mar file.
+ * If not specified it will default to the value of MD5_HASH_SIMPLE_MAR
+ * which is the MD5 hash value for the file specified by FILE_SIMPLE_MAR.
+ * @param aSize (optional)
+ * The patch's file size for the mar file.
+ * If not specified it will default to the file size for FILE_SIMPLE_MAR
+ * specified by SIZE_SIMPLE_MAR.
+ * @return The string representing a patch element for an update xml file.
+ */
+function getPatchString(aType, aURL, aHashFunction, aHashValue, aSize) {
+ let type = aType ? aType : "complete";
+ let url = aURL ? aURL : gURLData + FILE_SIMPLE_MAR;
+ let hashFunction = aHashFunction ? aHashFunction : "MD5";
+ let hashValue = aHashValue ? aHashValue : MD5_HASH_SIMPLE_MAR;
+ let size = aSize ? aSize : SIZE_SIMPLE_MAR;
+ return " <patch type=\"" + type + "\" " +
+ "URL=\"" + url + "\" " +
+ "hashFunction=\"" + hashFunction + "\" " +
+ "hashValue=\"" + hashValue + "\" " +
+ "size=\"" + size + "\"";
+}
diff --git a/toolkit/mozapps/update/tests/data/simple.mar b/toolkit/mozapps/update/tests/data/simple.mar
new file mode 100644
index 000000000..b2ccbd8d2
--- /dev/null
+++ b/toolkit/mozapps/update/tests/data/simple.mar
Binary files differ
diff --git a/toolkit/mozapps/update/tests/data/wrong_product_channel.mar b/toolkit/mozapps/update/tests/data/wrong_product_channel.mar
new file mode 100644
index 000000000..1e39cc214
--- /dev/null
+++ b/toolkit/mozapps/update/tests/data/wrong_product_channel.mar
Binary files differ
diff --git a/toolkit/mozapps/update/tests/data/xpcshellConstantsPP.js b/toolkit/mozapps/update/tests/data/xpcshellConstantsPP.js
new file mode 100644
index 000000000..e5f0fce58
--- /dev/null
+++ b/toolkit/mozapps/update/tests/data/xpcshellConstantsPP.js
@@ -0,0 +1,53 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* Preprocessed constants used by xpcshell tests */
+
+const INSTALL_LOCALE = "@AB_CD@";
+const MOZ_APP_NAME = "@MOZ_APP_NAME@";
+const BIN_SUFFIX = "@BIN_SUFFIX@";
+
+// MOZ_APP_VENDOR is optional.
+#ifdef MOZ_APP_VENDOR
+const MOZ_APP_VENDOR = "@MOZ_APP_VENDOR@";
+#else
+const MOZ_APP_VENDOR = "";
+#endif
+
+// MOZ_APP_BASENAME is not optional for tests.
+const MOZ_APP_BASENAME = "@MOZ_APP_BASENAME@";
+const APP_BIN_SUFFIX = "@BIN_SUFFIX@";
+
+const APP_INFO_NAME = "XPCShell";
+const APP_INFO_VENDOR = "Mozilla";
+
+#ifdef XP_WIN
+const IS_WIN = true;
+#else
+const IS_WIN = false;
+#endif
+
+#ifdef XP_MACOSX
+const IS_MACOSX = true;
+#else
+const IS_MACOSX = false;
+#endif
+
+#ifdef XP_UNIX
+const IS_UNIX = true;
+#else
+const IS_UNIX = false;
+#endif
+
+#ifdef MOZ_VERIFY_MAR_SIGNATURE
+const MOZ_VERIFY_MAR_SIGNATURE = true;
+#else
+const MOZ_VERIFY_MAR_SIGNATURE = false;
+#endif
+
+#ifdef DISABLE_UPDATER_AUTHENTICODE_CHECK
+ const IS_AUTHENTICODE_CHECK_ENABLED = false;
+#else
+ const IS_AUTHENTICODE_CHECK_ENABLED = true;
+#endif
diff --git a/toolkit/mozapps/update/tests/data/xpcshellUtilsAUS.js b/toolkit/mozapps/update/tests/data/xpcshellUtilsAUS.js
new file mode 100644
index 000000000..ada08f0ae
--- /dev/null
+++ b/toolkit/mozapps/update/tests/data/xpcshellUtilsAUS.js
@@ -0,0 +1,4047 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * Test log warnings that happen before the test has started
+ * "Couldn't get the user appdata directory. Crash events may not be produced."
+ * in nsExceptionHandler.cpp (possibly bug 619104)
+ *
+ * Test log warnings that happen after the test has finished
+ * "OOPDeinit() without successful OOPInit()" in nsExceptionHandler.cpp
+ * (bug 619104)
+ * "XPCOM objects created/destroyed from static ctor/dtor" in nsTraceRefcnt.cpp
+ * (possibly bug 457479)
+ *
+ * Other warnings printed to the test logs
+ * "site security information will not be persisted" in
+ * nsSiteSecurityService.cpp and the error in nsSystemInfo.cpp preceding this
+ * error are due to not having a profile when running some of the xpcshell
+ * tests. Since most xpcshell tests also log these errors these tests don't
+ * call do_get_profile unless necessary for the test.
+ * The "This method is lossy. Use GetCanonicalPath !" warning on Windows in
+ * nsLocalFileWin.cpp is from the call to GetNSSProfilePath in
+ * nsNSSComponent.cpp due to it using GetNativeCanonicalPath.
+ * "!mMainThread" in nsThreadManager.cpp are due to using timers and it might be
+ * possible to fix some or all of these in the test itself.
+ * "NS_FAILED(rv)" in nsThreadUtils.cpp are due to using timers and it might be
+ * possible to fix some or all of these in the test itself.
+ */
+
+'use strict';
+/* eslint-disable no-undef */
+
+const { classes: Cc, interfaces: Ci, manager: Cm, results: Cr,
+ utils: Cu } = Components;
+
+/* global INSTALL_LOCALE, MOZ_APP_NAME, BIN_SUFFIX, MOZ_APP_VENDOR */
+/* global MOZ_APP_BASENAME, APP_BIN_SUFFIX, APP_INFO_NAME, APP_INFO_VENDOR */
+/* global IS_WIN, IS_MACOSX, IS_UNIX, MOZ_VERIFY_MAR_SIGNATURE */
+/* global IS_AUTHENTICODE_CHECK_ENABLED */
+load("../data/xpcshellConstantsPP.js");
+
+function getLogSuffix() {
+ if (IS_WIN) {
+ return "_win";
+ }
+ if (IS_MACOSX) {
+ return "_mac";
+ }
+ return "_linux";
+}
+
+Cu.import("resource://gre/modules/Services.jsm", this);
+Cu.import("resource://gre/modules/ctypes.jsm", this);
+
+const DIR_MACOS = IS_MACOSX ? "Contents/MacOS/" : "";
+const DIR_RESOURCES = IS_MACOSX ? "Contents/Resources/" : "";
+const TEST_FILE_SUFFIX = IS_MACOSX ? "_mac" : "";
+const FILE_COMPLETE_MAR = "complete" + TEST_FILE_SUFFIX + ".mar";
+const FILE_PARTIAL_MAR = "partial" + TEST_FILE_SUFFIX + ".mar";
+const FILE_COMPLETE_PRECOMPLETE = "complete_precomplete" + TEST_FILE_SUFFIX;
+const FILE_PARTIAL_PRECOMPLETE = "partial_precomplete" + TEST_FILE_SUFFIX;
+const FILE_COMPLETE_REMOVEDFILES = "complete_removed-files" + TEST_FILE_SUFFIX;
+const FILE_PARTIAL_REMOVEDFILES = "partial_removed-files" + TEST_FILE_SUFFIX;
+const FILE_UPDATE_IN_PROGRESS_LOCK = "updated.update_in_progress.lock";
+const COMPARE_LOG_SUFFIX = getLogSuffix();
+const LOG_COMPLETE_SUCCESS = "complete_log_success" + COMPARE_LOG_SUFFIX;
+const LOG_PARTIAL_SUCCESS = "partial_log_success" + COMPARE_LOG_SUFFIX;
+const LOG_PARTIAL_FAILURE = "partial_log_failure" + COMPARE_LOG_SUFFIX;
+const LOG_REPLACE_SUCCESS = "replace_log_success";
+
+const USE_EXECV = IS_UNIX && !IS_MACOSX;
+
+const URL_HOST = "http://localhost";
+
+const FILE_APP_BIN = MOZ_APP_NAME + APP_BIN_SUFFIX;
+const FILE_COMPLETE_EXE = "complete.exe";
+const FILE_HELPER_BIN = "TestAUSHelper" + BIN_SUFFIX;
+const FILE_MAINTENANCE_SERVICE_BIN = "maintenanceservice.exe";
+const FILE_MAINTENANCE_SERVICE_INSTALLER_BIN = "maintenanceservice_installer.exe";
+const FILE_OLD_VERSION_MAR = "old_version.mar";
+const FILE_PARTIAL_EXE = "partial.exe";
+const FILE_UPDATER_BIN = "updater" + BIN_SUFFIX;
+const FILE_WRONG_CHANNEL_MAR = "wrong_product_channel.mar";
+
+const PERFORMING_STAGED_UPDATE = "Performing a staged update";
+const CALL_QUIT = "calling QuitProgressUI";
+const REMOVE_OLD_DIST_DIR = "removing old distribution directory";
+const MOVE_OLD_DIST_DIR = "Moving old distribution directory to new location";
+const ERR_UPDATE_IN_PROGRESS = "Update already in progress! Exiting";
+const ERR_RENAME_FILE = "rename_file: failed to rename file";
+const ERR_ENSURE_COPY = "ensure_copy: failed to copy the file";
+const ERR_UNABLE_OPEN_DEST = "unable to open destination file";
+const ERR_BACKUP_DISCARD = "backup_discard: unable to remove";
+const ERR_MOVE_DESTDIR_7 = "Moving destDir to tmpDir failed, err: 7";
+const ERR_BACKUP_CREATE_7 = "backup_create failed: 7";
+const ERR_LOADSOURCEFILE_FAILED = "LoadSourceFile failed";
+
+const LOG_SVC_SUCCESSFUL_LAUNCH = "Process was started... waiting on result.";
+
+// Typical end of a message when calling assert
+const MSG_SHOULD_EQUAL = " should equal the expected value";
+const MSG_SHOULD_EXIST = "the file or directory should exist";
+const MSG_SHOULD_NOT_EXIST = "the file or directory should not exist";
+
+// All we care about is that the last modified time has changed so that Mac OS
+// X Launch Services invalidates its cache so the test allows up to one minute
+// difference in the last modified time.
+const MAC_MAX_TIME_DIFFERENCE = 60000;
+
+// How many of do_execute_soon calls to wait before the test is aborted.
+const MAX_TIMEOUT_RUNS = 20000;
+
+// Time in seconds the helper application should sleep before exiting. The
+// helper can also be made to exit by writing |finish| to its input file.
+const HELPER_SLEEP_TIMEOUT = 180;
+
+// Maximum number of milliseconds the process that is launched can run before
+// the test will try to kill it.
+const APP_TIMER_TIMEOUT = 120000;
+
+// How many of do_timeout calls using FILE_IN_USE_TIMEOUT_MS to wait before the
+// test is aborted.
+const FILE_IN_USE_MAX_TIMEOUT_RUNS = 60;
+const FILE_IN_USE_TIMEOUT_MS = 1000;
+
+const PIPE_TO_NULL = IS_WIN ? ">nul" : "> /dev/null 2>&1";
+
+const LOG_FUNCTION = do_print;
+
+const gHTTPHandlerPath = "updates.xml";
+
+// This default value will be overridden when using the http server.
+var gURLData = URL_HOST + "/";
+
+var gTestID;
+
+var gTestserver;
+
+var gRegisteredServiceCleanup;
+
+var gCheckFunc;
+var gResponseBody;
+var gResponseStatusCode = 200;
+var gRequestURL;
+var gUpdateCount;
+var gUpdates;
+var gStatusCode;
+var gStatusText;
+var gStatusResult;
+
+var gProcess;
+var gAppTimer;
+var gHandle;
+
+var gGREDirOrig;
+var gGREBinDirOrig;
+var gAppDirOrig;
+
+// Variables are used instead of contants so tests can override these values if
+// necessary.
+var gCallbackBinFile = "callback_app" + BIN_SUFFIX;
+var gCallbackArgs = ["./", "callback.log", "Test Arg 2", "Test Arg 3"];
+var gPostUpdateBinFile = "postup_app" + BIN_SUFFIX;
+var gSvcOriginalLogContents;
+var gUseTestAppDir = true;
+// Some update staging failures can remove the update. This allows tests to
+// specify that the status file and the active update should not be checked
+// after an update is staged.
+var gStagingRemovedUpdate = false;
+
+var gTimeoutRuns = 0;
+var gFileInUseTimeoutRuns = 0;
+
+// Environment related globals
+var gShouldResetEnv = undefined;
+var gAddedEnvXRENoWindowsCrashDialog = false;
+var gEnvXPCOMDebugBreak;
+var gEnvXPCOMMemLeakLog;
+var gEnvDyldLibraryPath;
+var gEnvLdLibraryPath;
+var gASanOptions;
+
+// Set to true to log additional information for debugging. To log additional
+// information for an individual test set DEBUG_AUS_TEST to true in the test's
+// run_test function.
+var DEBUG_AUS_TEST = true;
+
+const DATA_URI_SPEC = Services.io.newFileURI(do_get_file("../data", false)).spec;
+/* import-globals-from ../data/shared.js */
+Services.scriptloader.loadSubScript(DATA_URI_SPEC + "shared.js", this);
+
+var gTestFiles = [];
+var gTestDirs = [];
+
+// Common files for both successful and failed updates.
+var gTestFilesCommon = [
+ {
+ description: "Should never change",
+ fileName: FILE_UPDATE_SETTINGS_INI,
+ relPathDir: DIR_RESOURCES,
+ originalContents: UPDATE_SETTINGS_CONTENTS,
+ compareContents: UPDATE_SETTINGS_CONTENTS,
+ originalFile: null,
+ compareFile: null,
+ originalPerms: 0o767,
+ comparePerms: 0o767
+ }, {
+ description: "Should never change",
+ fileName: "channel-prefs.js",
+ relPathDir: DIR_RESOURCES + "defaults/pref/",
+ originalContents: "ShouldNotBeReplaced\n",
+ compareContents: "ShouldNotBeReplaced\n",
+ originalFile: null,
+ compareFile: null,
+ originalPerms: 0o767,
+ comparePerms: 0o767
+ }];
+
+ // Files for a complete successful update. This can be used for a complete
+ // failed update by calling setTestFilesAndDirsForFailure.
+var gTestFilesCompleteSuccess = [
+ {
+ description: "Added by update.manifest (add)",
+ fileName: "precomplete",
+ relPathDir: DIR_RESOURCES,
+ originalContents: null,
+ compareContents: null,
+ originalFile: FILE_PARTIAL_PRECOMPLETE,
+ compareFile: FILE_COMPLETE_PRECOMPLETE,
+ originalPerms: 0o666,
+ comparePerms: 0o644
+ }, {
+ description: "Added by update.manifest (add)",
+ fileName: "searchpluginstext0",
+ relPathDir: DIR_RESOURCES + "searchplugins/",
+ originalContents: "ToBeReplacedWithFromComplete\n",
+ compareContents: "FromComplete\n",
+ originalFile: null,
+ compareFile: null,
+ originalPerms: 0o775,
+ comparePerms: 0o644
+ }, {
+ description: "Added by update.manifest (add)",
+ fileName: "searchpluginspng1.png",
+ relPathDir: DIR_RESOURCES + "searchplugins/",
+ originalContents: null,
+ compareContents: null,
+ originalFile: null,
+ compareFile: "complete.png",
+ originalPerms: null,
+ comparePerms: 0o644
+ }, {
+ description: "Added by update.manifest (add)",
+ fileName: "searchpluginspng0.png",
+ relPathDir: DIR_RESOURCES + "searchplugins/",
+ originalContents: null,
+ compareContents: null,
+ originalFile: "partial.png",
+ compareFile: "complete.png",
+ originalPerms: 0o666,
+ comparePerms: 0o644
+ }, {
+ description: "Added by update.manifest (add)",
+ fileName: "removed-files",
+ relPathDir: DIR_RESOURCES,
+ originalContents: null,
+ compareContents: null,
+ originalFile: FILE_PARTIAL_REMOVEDFILES,
+ compareFile: FILE_COMPLETE_REMOVEDFILES,
+ originalPerms: 0o666,
+ comparePerms: 0o644
+ }, {
+ description: "Added by update.manifest if the parent directory exists (add-if)",
+ fileName: "extensions1text0",
+ relPathDir: DIR_RESOURCES + "distribution/extensions/extensions1/",
+ originalContents: null,
+ compareContents: "FromComplete\n",
+ originalFile: null,
+ compareFile: null,
+ originalPerms: null,
+ comparePerms: 0o644
+ }, {
+ description: "Added by update.manifest if the parent directory exists (add-if)",
+ fileName: "extensions1png1.png",
+ relPathDir: DIR_RESOURCES + "distribution/extensions/extensions1/",
+ originalContents: null,
+ compareContents: null,
+ originalFile: "partial.png",
+ compareFile: "complete.png",
+ originalPerms: 0o666,
+ comparePerms: 0o644
+ }, {
+ description: "Added by update.manifest if the parent directory exists (add-if)",
+ fileName: "extensions1png0.png",
+ relPathDir: DIR_RESOURCES + "distribution/extensions/extensions1/",
+ originalContents: null,
+ compareContents: null,
+ originalFile: null,
+ compareFile: "complete.png",
+ originalPerms: null,
+ comparePerms: 0o644
+ }, {
+ description: "Added by update.manifest if the parent directory exists (add-if)",
+ fileName: "extensions0text0",
+ relPathDir: DIR_RESOURCES + "distribution/extensions/extensions0/",
+ originalContents: "ToBeReplacedWithFromComplete\n",
+ compareContents: "FromComplete\n",
+ originalFile: null,
+ compareFile: null,
+ originalPerms: null,
+ comparePerms: 0o644
+ }, {
+ description: "Added by update.manifest if the parent directory exists (add-if)",
+ fileName: "extensions0png1.png",
+ relPathDir: DIR_RESOURCES + "distribution/extensions/extensions0/",
+ originalContents: null,
+ compareContents: null,
+ originalFile: null,
+ compareFile: "complete.png",
+ originalPerms: null,
+ comparePerms: 0o644
+ }, {
+ description: "Added by update.manifest if the parent directory exists (add-if)",
+ fileName: "extensions0png0.png",
+ relPathDir: DIR_RESOURCES + "distribution/extensions/extensions0/",
+ originalContents: null,
+ compareContents: null,
+ originalFile: null,
+ compareFile: "complete.png",
+ originalPerms: null,
+ comparePerms: 0o644
+ }, {
+ description: "Added by update.manifest (add)",
+ fileName: "exe0.exe",
+ relPathDir: DIR_MACOS,
+ originalContents: null,
+ compareContents: null,
+ originalFile: FILE_HELPER_BIN,
+ compareFile: FILE_COMPLETE_EXE,
+ originalPerms: 0o777,
+ comparePerms: 0o755
+ }, {
+ description: "Added by update.manifest (add)",
+ fileName: "10text0",
+ relPathDir: DIR_RESOURCES + "1/10/",
+ originalContents: "ToBeReplacedWithFromComplete\n",
+ compareContents: "FromComplete\n",
+ originalFile: null,
+ compareFile: null,
+ originalPerms: 0o767,
+ comparePerms: 0o644
+ }, {
+ description: "Added by update.manifest (add)",
+ fileName: "0exe0.exe",
+ relPathDir: DIR_RESOURCES + "0/",
+ originalContents: null,
+ compareContents: null,
+ originalFile: FILE_HELPER_BIN,
+ compareFile: FILE_COMPLETE_EXE,
+ originalPerms: 0o777,
+ comparePerms: 0o755
+ }, {
+ description: "Added by update.manifest (add)",
+ fileName: "00text1",
+ relPathDir: DIR_RESOURCES + "0/00/",
+ originalContents: "ToBeReplacedWithFromComplete\n",
+ compareContents: "FromComplete\n",
+ originalFile: null,
+ compareFile: null,
+ originalPerms: 0o677,
+ comparePerms: 0o644
+ }, {
+ description: "Added by update.manifest (add)",
+ fileName: "00text0",
+ relPathDir: DIR_RESOURCES + "0/00/",
+ originalContents: "ToBeReplacedWithFromComplete\n",
+ compareContents: "FromComplete\n",
+ originalFile: null,
+ compareFile: null,
+ originalPerms: 0o775,
+ comparePerms: 0o644
+ }, {
+ description: "Added by update.manifest (add)",
+ fileName: "00png0.png",
+ relPathDir: DIR_RESOURCES + "0/00/",
+ originalContents: null,
+ compareContents: null,
+ originalFile: null,
+ compareFile: "complete.png",
+ originalPerms: 0o776,
+ comparePerms: 0o644
+ }, {
+ description: "Removed by precomplete (remove)",
+ fileName: "20text0",
+ relPathDir: DIR_RESOURCES + "2/20/",
+ originalContents: "ToBeDeleted\n",
+ compareContents: null,
+ originalFile: null,
+ compareFile: null,
+ originalPerms: null,
+ comparePerms: null
+ }, {
+ description: "Removed by precomplete (remove)",
+ fileName: "20png0.png",
+ relPathDir: DIR_RESOURCES + "2/20/",
+ originalContents: "ToBeDeleted\n",
+ compareContents: null,
+ originalFile: null,
+ compareFile: null,
+ originalPerms: null,
+ comparePerms: null
+ }];
+
+// Concatenate the common files to the end of the array.
+gTestFilesCompleteSuccess = gTestFilesCompleteSuccess.concat(gTestFilesCommon);
+
+// Files for a partial successful update. This can be used for a partial failed
+// update by calling setTestFilesAndDirsForFailure.
+var gTestFilesPartialSuccess = [
+ {
+ description: "Added by update.manifest (add)",
+ fileName: "precomplete",
+ relPathDir: DIR_RESOURCES,
+ originalContents: null,
+ compareContents: null,
+ originalFile: FILE_COMPLETE_PRECOMPLETE,
+ compareFile: FILE_PARTIAL_PRECOMPLETE,
+ originalPerms: 0o666,
+ comparePerms: 0o644
+ }, {
+ description: "Added by update.manifest (add)",
+ fileName: "searchpluginstext0",
+ relPathDir: DIR_RESOURCES + "searchplugins/",
+ originalContents: "ToBeReplacedWithFromPartial\n",
+ compareContents: "FromPartial\n",
+ originalFile: null,
+ compareFile: null,
+ originalPerms: 0o775,
+ comparePerms: 0o644
+ }, {
+ description: "Patched by update.manifest if the file exists (patch-if)",
+ fileName: "searchpluginspng1.png",
+ relPathDir: DIR_RESOURCES + "searchplugins/",
+ originalContents: null,
+ compareContents: null,
+ originalFile: "complete.png",
+ compareFile: "partial.png",
+ originalPerms: 0o666,
+ comparePerms: 0o666
+ }, {
+ description: "Patched by update.manifest if the file exists (patch-if)",
+ fileName: "searchpluginspng0.png",
+ relPathDir: DIR_RESOURCES + "searchplugins/",
+ originalContents: null,
+ compareContents: null,
+ originalFile: "complete.png",
+ compareFile: "partial.png",
+ originalPerms: 0o666,
+ comparePerms: 0o666
+ }, {
+ description: "Added by update.manifest if the parent directory exists (add-if)",
+ fileName: "extensions1text0",
+ relPathDir: DIR_RESOURCES + "distribution/extensions/extensions1/",
+ originalContents: null,
+ compareContents: "FromPartial\n",
+ originalFile: null,
+ compareFile: null,
+ originalPerms: null,
+ comparePerms: 0o644
+ }, {
+ description: "Patched by update.manifest if the parent directory exists (patch-if)",
+ fileName: "extensions1png1.png",
+ relPathDir: DIR_RESOURCES + "distribution/extensions/extensions1/",
+ originalContents: null,
+ compareContents: null,
+ originalFile: "complete.png",
+ compareFile: "partial.png",
+ originalPerms: 0o666,
+ comparePerms: 0o666
+ }, {
+ description: "Patched by update.manifest if the parent directory exists (patch-if)",
+ fileName: "extensions1png0.png",
+ relPathDir: DIR_RESOURCES + "distribution/extensions/extensions1/",
+ originalContents: null,
+ compareContents: null,
+ originalFile: "complete.png",
+ compareFile: "partial.png",
+ originalPerms: 0o666,
+ comparePerms: 0o666
+ }, {
+ description: "Added by update.manifest if the parent directory exists (add-if)",
+ fileName: "extensions0text0",
+ relPathDir: DIR_RESOURCES + "distribution/extensions/extensions0/",
+ originalContents: "ToBeReplacedWithFromPartial\n",
+ compareContents: "FromPartial\n",
+ originalFile: null,
+ compareFile: null,
+ originalPerms: 0o644,
+ comparePerms: 0o644
+ }, {
+ description: "Patched by update.manifest if the parent directory exists (patch-if)",
+ fileName: "extensions0png1.png",
+ relPathDir: DIR_RESOURCES + "distribution/extensions/extensions0/",
+ originalContents: null,
+ compareContents: null,
+ originalFile: "complete.png",
+ compareFile: "partial.png",
+ originalPerms: 0o644,
+ comparePerms: 0o644
+ }, {
+ description: "Patched by update.manifest if the parent directory exists (patch-if)",
+ fileName: "extensions0png0.png",
+ relPathDir: DIR_RESOURCES + "distribution/extensions/extensions0/",
+ originalContents: null,
+ compareContents: null,
+ originalFile: "complete.png",
+ compareFile: "partial.png",
+ originalPerms: 0o644,
+ comparePerms: 0o644
+ }, {
+ description: "Patched by update.manifest (patch)",
+ fileName: "exe0.exe",
+ relPathDir: DIR_MACOS,
+ originalContents: null,
+ compareContents: null,
+ originalFile: FILE_COMPLETE_EXE,
+ compareFile: FILE_PARTIAL_EXE,
+ originalPerms: 0o755,
+ comparePerms: 0o755
+ }, {
+ description: "Patched by update.manifest (patch)",
+ fileName: "0exe0.exe",
+ relPathDir: DIR_RESOURCES + "0/",
+ originalContents: null,
+ compareContents: null,
+ originalFile: FILE_COMPLETE_EXE,
+ compareFile: FILE_PARTIAL_EXE,
+ originalPerms: 0o755,
+ comparePerms: 0o755
+ }, {
+ description: "Added by update.manifest (add)",
+ fileName: "00text0",
+ relPathDir: DIR_RESOURCES + "0/00/",
+ originalContents: "ToBeReplacedWithFromPartial\n",
+ compareContents: "FromPartial\n",
+ originalFile: null,
+ compareFile: null,
+ originalPerms: 0o644,
+ comparePerms: 0o644
+ }, {
+ description: "Patched by update.manifest (patch)",
+ fileName: "00png0.png",
+ relPathDir: DIR_RESOURCES + "0/00/",
+ originalContents: null,
+ compareContents: null,
+ originalFile: "complete.png",
+ compareFile: "partial.png",
+ originalPerms: 0o666,
+ comparePerms: 0o666
+ }, {
+ description: "Added by update.manifest (add)",
+ fileName: "20text0",
+ relPathDir: DIR_RESOURCES + "2/20/",
+ originalContents: null,
+ compareContents: "FromPartial\n",
+ originalFile: null,
+ compareFile: null,
+ originalPerms: null,
+ comparePerms: 0o644
+ }, {
+ description: "Added by update.manifest (add)",
+ fileName: "20png0.png",
+ relPathDir: DIR_RESOURCES + "2/20/",
+ originalContents: null,
+ compareContents: null,
+ originalFile: null,
+ compareFile: "partial.png",
+ originalPerms: null,
+ comparePerms: 0o644
+ }, {
+ description: "Added by update.manifest (add)",
+ fileName: "00text2",
+ relPathDir: DIR_RESOURCES + "0/00/",
+ originalContents: null,
+ compareContents: "FromPartial\n",
+ originalFile: null,
+ compareFile: null,
+ originalPerms: null,
+ comparePerms: 0o644
+ }, {
+ description: "Removed by update.manifest (remove)",
+ fileName: "10text0",
+ relPathDir: DIR_RESOURCES + "1/10/",
+ originalContents: "ToBeDeleted\n",
+ compareContents: null,
+ originalFile: null,
+ compareFile: null,
+ originalPerms: null,
+ comparePerms: null
+ }, {
+ description: "Removed by update.manifest (remove)",
+ fileName: "00text1",
+ relPathDir: DIR_RESOURCES + "0/00/",
+ originalContents: "ToBeDeleted\n",
+ compareContents: null,
+ originalFile: null,
+ compareFile: null,
+ originalPerms: null,
+ comparePerms: null
+ }];
+
+// Concatenate the common files to the end of the array.
+gTestFilesPartialSuccess = gTestFilesPartialSuccess.concat(gTestFilesCommon);
+
+var gTestDirsCommon = [
+ {
+ relPathDir: DIR_RESOURCES + "3/",
+ dirRemoved: false,
+ files: ["3text0", "3text1"],
+ filesRemoved: true
+ }, {
+ relPathDir: DIR_RESOURCES + "4/",
+ dirRemoved: true,
+ files: ["4text0", "4text1"],
+ filesRemoved: true
+ }, {
+ relPathDir: DIR_RESOURCES + "5/",
+ dirRemoved: true,
+ files: ["5test.exe", "5text0", "5text1"],
+ filesRemoved: true
+ }, {
+ relPathDir: DIR_RESOURCES + "6/",
+ dirRemoved: true
+ }, {
+ relPathDir: DIR_RESOURCES + "7/",
+ dirRemoved: true,
+ files: ["7text0", "7text1"],
+ subDirs: ["70/", "71/"],
+ subDirFiles: ["7xtest.exe", "7xtext0", "7xtext1"]
+ }, {
+ relPathDir: DIR_RESOURCES + "8/",
+ dirRemoved: false
+ }, {
+ relPathDir: DIR_RESOURCES + "8/80/",
+ dirRemoved: true
+ }, {
+ relPathDir: DIR_RESOURCES + "8/81/",
+ dirRemoved: false,
+ files: ["81text0", "81text1"]
+ }, {
+ relPathDir: DIR_RESOURCES + "8/82/",
+ dirRemoved: false,
+ subDirs: ["820/", "821/"]
+ }, {
+ relPathDir: DIR_RESOURCES + "8/83/",
+ dirRemoved: true
+ }, {
+ relPathDir: DIR_RESOURCES + "8/84/",
+ dirRemoved: true
+ }, {
+ relPathDir: DIR_RESOURCES + "8/85/",
+ dirRemoved: true
+ }, {
+ relPathDir: DIR_RESOURCES + "8/86/",
+ dirRemoved: true,
+ files: ["86text0", "86text1"]
+ }, {
+ relPathDir: DIR_RESOURCES + "8/87/",
+ dirRemoved: true,
+ subDirs: ["870/", "871/"],
+ subDirFiles: ["87xtext0", "87xtext1"]
+ }, {
+ relPathDir: DIR_RESOURCES + "8/88/",
+ dirRemoved: true
+ }, {
+ relPathDir: DIR_RESOURCES + "8/89/",
+ dirRemoved: true
+ }, {
+ relPathDir: DIR_RESOURCES + "9/90/",
+ dirRemoved: true
+ }, {
+ relPathDir: DIR_RESOURCES + "9/91/",
+ dirRemoved: false,
+ files: ["91text0", "91text1"]
+ }, {
+ relPathDir: DIR_RESOURCES + "9/92/",
+ dirRemoved: false,
+ subDirs: ["920/", "921/"]
+ }, {
+ relPathDir: DIR_RESOURCES + "9/93/",
+ dirRemoved: true
+ }, {
+ relPathDir: DIR_RESOURCES + "9/94/",
+ dirRemoved: true
+ }, {
+ relPathDir: DIR_RESOURCES + "9/95/",
+ dirRemoved: true
+ }, {
+ relPathDir: DIR_RESOURCES + "9/96/",
+ dirRemoved: true,
+ files: ["96text0", "96text1"]
+ }, {
+ relPathDir: DIR_RESOURCES + "9/97/",
+ dirRemoved: true,
+ subDirs: ["970/", "971/"],
+ subDirFiles: ["97xtext0", "97xtext1"]
+ }, {
+ relPathDir: DIR_RESOURCES + "9/98/",
+ dirRemoved: true
+ }, {
+ relPathDir: DIR_RESOURCES + "9/99/",
+ dirRemoved: true
+ }];
+
+// Directories for a complete successful update. This array can be used for a
+// complete failed update by calling setTestFilesAndDirsForFailure.
+var gTestDirsCompleteSuccess = [
+ {
+ description: "Removed by precomplete (rmdir)",
+ relPathDir: DIR_RESOURCES + "2/20/",
+ dirRemoved: true
+ }, {
+ description: "Removed by precomplete (rmdir)",
+ relPathDir: DIR_RESOURCES + "2/",
+ dirRemoved: true
+ }];
+
+// Concatenate the common files to the beginning of the array.
+gTestDirsCompleteSuccess = gTestDirsCommon.concat(gTestDirsCompleteSuccess);
+
+// Directories for a partial successful update. This array can be used for a
+// partial failed update by calling setTestFilesAndDirsForFailure.
+var gTestDirsPartialSuccess = [
+ {
+ description: "Removed by update.manifest (rmdir)",
+ relPathDir: DIR_RESOURCES + "1/10/",
+ dirRemoved: true
+ }, {
+ description: "Removed by update.manifest (rmdir)",
+ relPathDir: DIR_RESOURCES + "1/",
+ dirRemoved: true
+ }];
+
+// Concatenate the common files to the beginning of the array.
+gTestDirsPartialSuccess = gTestDirsCommon.concat(gTestDirsPartialSuccess);
+
+// This makes it possible to run most tests on xulrunner where the update
+// channel default preference is not set.
+if (MOZ_APP_NAME == "xulrunner") {
+ try {
+ gDefaultPrefBranch.getCharPref(PREF_APP_UPDATE_CHANNEL);
+ } catch (e) {
+ setUpdateChannel("test_channel");
+ }
+}
+
+/**
+ * Helper function for setting up the test environment.
+ */
+function setupTestCommon() {
+ debugDump("start - general test setup");
+
+ Assert.strictEqual(gTestID, undefined,
+ "gTestID should be 'undefined' (setupTestCommon should " +
+ "only be called once)");
+
+ let caller = Components.stack.caller;
+ gTestID = caller.filename.toString().split("/").pop().split(".")[0];
+
+ // Tests that don't work with XULRunner.
+ const XUL_RUNNER_INCOMPATIBLE = ["marAppApplyUpdateAppBinInUseStageSuccess_win",
+ "marAppApplyUpdateStageSuccess",
+ "marAppApplyUpdateSuccess",
+ "marAppApplyUpdateAppBinInUseStageSuccessSvc_win",
+ "marAppApplyUpdateStageSuccessSvc",
+ "marAppApplyUpdateSuccessSvc"];
+ // Replace with Array.prototype.includes when it has stabilized.
+ if (MOZ_APP_NAME == "xulrunner" &&
+ XUL_RUNNER_INCOMPATIBLE.indexOf(gTestID) != -1) {
+ logTestInfo("Unable to run this test on xulrunner");
+ return false;
+ }
+
+ if (IS_SERVICE_TEST && !shouldRunServiceTest()) {
+ return false;
+ }
+
+ do_test_pending();
+
+ setDefaultPrefs();
+
+ // Don't attempt to show a prompt when an update finishes.
+ Services.prefs.setBoolPref(PREF_APP_UPDATE_SILENT, true);
+
+ gGREDirOrig = getGREDir();
+ gGREBinDirOrig = getGREBinDir();
+ gAppDirOrig = getAppBaseDir();
+
+ let applyDir = getApplyDirFile(null, true).parent;
+
+ // Try to remove the directory used to apply updates and the updates directory
+ // on platforms other than Windows. Since the test hasn't ran yet and the
+ // directory shouldn't exist finished this is non-fatal for the test.
+ if (applyDir.exists()) {
+ debugDump("attempting to remove directory. Path: " + applyDir.path);
+ try {
+ removeDirRecursive(applyDir);
+ } catch (e) {
+ logTestInfo("non-fatal error removing directory. Path: " +
+ applyDir.path + ", Exception: " + e);
+ // When the application doesn't exit properly it can cause the test to
+ // fail again on the second run with an NS_ERROR_FILE_ACCESS_DENIED error
+ // along with no useful information in the test log. To prevent this use
+ // a different directory for the test when it isn't possible to remove the
+ // existing test directory (bug 1294196).
+ gTestID += "_new";
+ logTestInfo("using a new directory for the test by changing gTestID " +
+ "since there is an existing test directory that can't be " +
+ "removed, gTestID: " + gTestID);
+ }
+ }
+
+ if (IS_WIN) {
+ Services.prefs.setBoolPref(PREF_APP_UPDATE_SERVICE_ENABLED,
+ IS_SERVICE_TEST ? true : false);
+ }
+
+ // adjustGeneralPaths registers a cleanup function that calls end_test when
+ // it is defined as a function.
+ adjustGeneralPaths();
+ // Logged once here instead of in the mock directory provider to lessen test
+ // log spam.
+ debugDump("Updates Directory (UpdRootD) Path: " + getMockUpdRootD().path);
+
+ // This prevents a warning about not being able to find the greprefs.js file
+ // from being logged.
+ let grePrefsFile = getGREDir();
+ if (!grePrefsFile.exists()) {
+ grePrefsFile.create(Ci.nsIFile.DIRECTORY_TYPE, PERMS_DIRECTORY);
+ }
+ grePrefsFile.append("greprefs.js");
+ if (!grePrefsFile.exists()) {
+ grePrefsFile.create(Ci.nsILocalFile.NORMAL_FILE_TYPE, PERMS_FILE);
+ }
+
+ // Remove the updates directory on Windows and Mac OS X which is located
+ // outside of the application directory after the call to adjustGeneralPaths
+ // has set it up. Since the test hasn't ran yet and the directory shouldn't
+ // exist this is non-fatal for the test.
+ if (IS_WIN || IS_MACOSX) {
+ let updatesDir = getMockUpdRootD();
+ if (updatesDir.exists()) {
+ debugDump("attempting to remove directory. Path: " + updatesDir.path);
+ try {
+ removeDirRecursive(updatesDir);
+ } catch (e) {
+ logTestInfo("non-fatal error removing directory. Path: " +
+ updatesDir.path + ", Exception: " + e);
+ }
+ }
+ }
+
+ debugDump("finish - general test setup");
+ return true;
+}
+
+/**
+ * Nulls out the most commonly used global vars used by tests to prevent leaks
+ * as needed and attempts to restore the system to its original state.
+ */
+function cleanupTestCommon() {
+ debugDump("start - general test cleanup");
+
+ // Force the update manager to reload the update data to prevent it from
+ // writing the old data to the files that have just been removed.
+ reloadUpdateManagerData();
+
+ if (gChannel) {
+ gPrefRoot.removeObserver(PREF_APP_UPDATE_CHANNEL, observer);
+ }
+
+ // Call app update's observe method passing xpcom-shutdown to test that the
+ // shutdown of app update runs without throwing or leaking. The observer
+ // method is used directly instead of calling notifyObservers so components
+ // outside of the scope of this test don't assert and thereby cause app update
+ // tests to fail.
+ gAUS.observe(null, "xpcom-shutdown", "");
+
+ gTestserver = null;
+
+ if (IS_UNIX) {
+ // This will delete the launch script if it exists.
+ getLaunchScript();
+ }
+
+ if (IS_WIN && MOZ_APP_BASENAME) {
+ let appDir = getApplyDirFile(null, true);
+ let vendor = MOZ_APP_VENDOR ? MOZ_APP_VENDOR : "Mozilla";
+ const REG_PATH = "SOFTWARE\\" + vendor + "\\" + MOZ_APP_BASENAME +
+ "\\TaskBarIDs";
+ let key = Cc["@mozilla.org/windows-registry-key;1"].
+ createInstance(Ci.nsIWindowsRegKey);
+ try {
+ key.open(Ci.nsIWindowsRegKey.ROOT_KEY_LOCAL_MACHINE, REG_PATH,
+ Ci.nsIWindowsRegKey.ACCESS_ALL);
+ if (key.hasValue(appDir.path)) {
+ key.removeValue(appDir.path);
+ }
+ } catch (e) {
+ }
+ try {
+ key.open(Ci.nsIWindowsRegKey.ROOT_KEY_CURRENT_USER, REG_PATH,
+ Ci.nsIWindowsRegKey.ACCESS_ALL);
+ if (key.hasValue(appDir.path)) {
+ key.removeValue(appDir.path);
+ }
+ } catch (e) {
+ }
+ }
+
+ // The updates directory is located outside of the application directory and
+ // needs to be removed on Windows and Mac OS X.
+ if (IS_WIN || IS_MACOSX) {
+ let updatesDir = getMockUpdRootD();
+ // Try to remove the directory used to apply updates. Since the test has
+ // already finished this is non-fatal for the test.
+ if (updatesDir.exists()) {
+ debugDump("attempting to remove directory. Path: " + updatesDir.path);
+ try {
+ removeDirRecursive(updatesDir);
+ } catch (e) {
+ logTestInfo("non-fatal error removing directory. Path: " +
+ updatesDir.path + ", Exception: " + e);
+ }
+ if (IS_MACOSX) {
+ let updatesRootDir = gUpdatesRootDir.clone();
+ while (updatesRootDir.path != updatesDir.path) {
+ if (updatesDir.exists()) {
+ debugDump("attempting to remove directory. Path: " +
+ updatesDir.path);
+ try {
+ // Try to remove the directory without the recursive flag set
+ // since the top level directory has already had its contents
+ // removed and the parent directory might still be used by a
+ // different test.
+ updatesDir.remove(false);
+ } catch (e) {
+ logTestInfo("non-fatal error removing directory. Path: " +
+ updatesDir.path + ", Exception: " + e);
+ if (e == Cr.NS_ERROR_FILE_DIR_NOT_EMPTY) {
+ break;
+ }
+ }
+ }
+ updatesDir = updatesDir.parent;
+ }
+ }
+ }
+ }
+
+ let applyDir = getApplyDirFile(null, true).parent;
+
+ // Try to remove the directory used to apply updates. Since the test has
+ // already finished this is non-fatal for the test.
+ if (applyDir.exists()) {
+ debugDump("attempting to remove directory. Path: " + applyDir.path);
+ try {
+ removeDirRecursive(applyDir);
+ } catch (e) {
+ logTestInfo("non-fatal error removing directory. Path: " +
+ applyDir.path + ", Exception: " + e);
+ }
+ }
+
+ resetEnvironment();
+
+ debugDump("finish - general test cleanup");
+}
+
+/**
+ * Helper function that calls do_test_finished that tracks whether a parallel
+ * run of a test passed when it runs synchronously so the log output can be
+ * inspected.
+ */
+function doTestFinish() {
+ if (DEBUG_AUS_TEST) {
+ // This prevents do_print errors from being printed by the xpcshell test
+ // harness due to nsUpdateService.js logging to the console when the
+ // app.update.log preference is true.
+ Services.prefs.setBoolPref(PREF_APP_UPDATE_LOG, false);
+ gAUS.observe(null, "nsPref:changed", PREF_APP_UPDATE_LOG);
+ }
+ do_execute_soon(do_test_finished);
+}
+
+/**
+ * Sets the most commonly used preferences used by tests
+ */
+function setDefaultPrefs() {
+ Services.prefs.setBoolPref(PREF_APP_UPDATE_ENABLED, true);
+ if (DEBUG_AUS_TEST) {
+ // Enable Update logging
+ Services.prefs.setBoolPref(PREF_APP_UPDATE_LOG, true);
+ } else {
+ // Some apps set this preference to true by default
+ Services.prefs.setBoolPref(PREF_APP_UPDATE_LOG, false);
+ }
+ // In case telemetry is enabled for xpcshell tests.
+ Services.prefs.setBoolPref(PREF_TOOLKIT_TELEMETRY_ENABLED, false);
+}
+
+/**
+ * Helper function for updater binary tests that sets the appropriate values
+ * to check for update failures.
+ */
+function setTestFilesAndDirsForFailure() {
+ gTestFiles.forEach(function STFADFF_Files(aTestFile) {
+ aTestFile.compareContents = aTestFile.originalContents;
+ aTestFile.compareFile = aTestFile.originalFile;
+ aTestFile.comparePerms = aTestFile.originalPerms;
+ });
+
+ gTestDirs.forEach(function STFADFF_Dirs(aTestDir) {
+ aTestDir.dirRemoved = false;
+ if (aTestDir.filesRemoved) {
+ aTestDir.filesRemoved = false;
+ }
+ });
+}
+
+/**
+ * Helper function for updater binary tests that prevents the distribution
+ * directory files from being created.
+ */
+function preventDistributionFiles() {
+ gTestFiles = gTestFiles.filter(function(aTestFile) {
+ return aTestFile.relPathDir.indexOf("distribution/") == -1;
+ });
+
+ gTestDirs = gTestDirs.filter(function(aTestDir) {
+ return aTestDir.relPathDir.indexOf("distribution/") == -1;
+ });
+}
+
+/**
+ * On Mac OS X this sets the last modified time for the app bundle directory to
+ * a date in the past to test that the last modified time is updated when an
+ * update has been successfully applied (bug 600098).
+ */
+function setAppBundleModTime() {
+ if (!IS_MACOSX) {
+ return;
+ }
+ let now = Date.now();
+ let yesterday = now - (1000 * 60 * 60 * 24);
+ let applyToDir = getApplyDirFile();
+ applyToDir.lastModifiedTime = yesterday;
+}
+
+/**
+ * On Mac OS X this checks that the last modified time for the app bundle
+ * directory has been updated when an update has been successfully applied
+ * (bug 600098).
+ */
+function checkAppBundleModTime() {
+ if (!IS_MACOSX) {
+ return;
+ }
+ let now = Date.now();
+ let applyToDir = getApplyDirFile();
+ let timeDiff = Math.abs(applyToDir.lastModifiedTime - now);
+ Assert.ok(timeDiff < MAC_MAX_TIME_DIFFERENCE,
+ "the last modified time on the apply to directory should " +
+ "change after a successful update");
+}
+
+/**
+ * On Mac OS X and Windows this checks if the post update '.running' file exists
+ * to determine if the post update binary was launched.
+ *
+ * @param aShouldExist
+ * Whether the post update '.running' file should exist.
+ */
+function checkPostUpdateRunningFile(aShouldExist) {
+ if (!IS_WIN && !IS_MACOSX) {
+ return;
+ }
+ let postUpdateRunningFile = getPostUpdateFile(".running");
+ if (aShouldExist) {
+ Assert.ok(postUpdateRunningFile.exists(),
+ MSG_SHOULD_EXIST + getMsgPath(postUpdateRunningFile.path));
+ } else {
+ Assert.ok(!postUpdateRunningFile.exists(),
+ MSG_SHOULD_NOT_EXIST + getMsgPath(postUpdateRunningFile.path));
+ }
+}
+
+/**
+ * Initializes the most commonly used settings and creates an instance of the
+ * update service stub.
+ */
+function standardInit() {
+ createAppInfo("xpcshell@tests.mozilla.org", APP_INFO_NAME, "1.0", "2.0");
+ // Initialize the update service stub component
+ initUpdateServiceStub();
+}
+
+/**
+ * Helper function for getting the application version from the application.ini
+ * file. This will look in both the GRE and the application directories for the
+ * application.ini file.
+ *
+ * @return The version string from the application.ini file.
+ */
+function getAppVersion() {
+ // Read the application.ini and use its application version.
+ let iniFile = gGREDirOrig.clone();
+ iniFile.append(FILE_APPLICATION_INI);
+ if (!iniFile.exists()) {
+ iniFile = gGREBinDirOrig.clone();
+ iniFile.append(FILE_APPLICATION_INI);
+ }
+ Assert.ok(iniFile.exists(),
+ MSG_SHOULD_EXIST + getMsgPath(iniFile.path));
+ let iniParser = Cc["@mozilla.org/xpcom/ini-parser-factory;1"].
+ getService(Ci.nsIINIParserFactory).
+ createINIParser(iniFile);
+ return iniParser.getString("App", "Version");
+}
+
+/**
+ * Helper function for getting the relative path to the directory where the
+ * application binary is located (e.g. <test_file_leafname>/dir.app/).
+ *
+ * Note: The dir.app subdirectory under <test_file_leafname> is needed for
+ * platforms other than Mac OS X so the tests can run in parallel due to
+ * update staging creating a lock file named moz_update_in_progress.lock in
+ * the parent directory of the installation directory.
+ *
+ * @return The relative path to the directory where application binary is
+ * located.
+ */
+function getApplyDirPath() {
+ return gTestID + "/dir.app/";
+}
+
+/**
+ * Helper function for getting the nsIFile for a file in the directory where the
+ * update will be applied.
+ *
+ * The files for the update are located two directories below the apply to
+ * directory since Mac OS X sets the last modified time for the root directory
+ * to the current time and if the update changes any files in the root directory
+ * then it wouldn't be possible to test (bug 600098).
+ *
+ * @param aRelPath (optional)
+ * The relative path to the file or directory to get from the root of
+ * the test's directory. If not specified the test's directory will be
+ * returned.
+ * @param aAllowNonexistent (optional)
+ * Whether the file must exist. If false or not specified the file must
+ * exist or the function will throw.
+ * @return The nsIFile for the file in the directory where the update will be
+ * applied.
+ * @throws If aAllowNonexistent is not specified or is false and the file or
+ * directory does not exist.
+ */
+function getApplyDirFile(aRelPath, aAllowNonexistent) {
+ let relpath = getApplyDirPath() + (aRelPath ? aRelPath : "");
+ return do_get_file(relpath, aAllowNonexistent);
+}
+
+/**
+ * Helper function for getting the nsIFile for a file in the directory where the
+ * update will be staged.
+ *
+ * The files for the update are located two directories below the stage
+ * directory since Mac OS X sets the last modified time for the root directory
+ * to the current time and if the update changes any files in the root directory
+ * then it wouldn't be possible to test (bug 600098).
+ *
+ * @param aRelPath (optional)
+ * The relative path to the file or directory to get from the root of
+ * the stage directory. If not specified the stage directory will be
+ * returned.
+ * @param aAllowNonexistent (optional)
+ * Whether the file must exist. If false or not specified the file must
+ * exist or the function will throw.
+ * @return The nsIFile for the file in the directory where the update will be
+ * staged.
+ * @throws If aAllowNonexistent is not specified or is false and the file or
+ * directory does not exist.
+ */
+function getStageDirFile(aRelPath, aAllowNonexistent) {
+ if (IS_MACOSX) {
+ let file = getMockUpdRootD();
+ file.append(DIR_UPDATES);
+ file.append(DIR_PATCH);
+ file.append(DIR_UPDATED);
+ if (aRelPath) {
+ let pathParts = aRelPath.split("/");
+ for (let i = 0; i < pathParts.length; i++) {
+ if (pathParts[i]) {
+ file.append(pathParts[i]);
+ }
+ }
+ }
+ if (!aAllowNonexistent) {
+ Assert.ok(file.exists(),
+ MSG_SHOULD_EXIST + getMsgPath(file.path));
+ }
+ return file;
+ }
+
+ let relpath = getApplyDirPath() + DIR_UPDATED + "/" + (aRelPath ? aRelPath : "");
+ return do_get_file(relpath, aAllowNonexistent);
+}
+
+/**
+ * Helper function for getting the relative path to the directory where the
+ * test data files are located.
+ *
+ * @return The relative path to the directory where the test data files are
+ * located.
+ */
+function getTestDirPath() {
+ return "../data/";
+}
+
+/**
+ * Helper function for getting the nsIFile for a file in the test data
+ * directory.
+ *
+ * @param aRelPath (optional)
+ * The relative path to the file or directory to get from the root of
+ * the test's data directory. If not specified the test's data
+ * directory will be returned.
+ * @param aAllowNonExists (optional)
+ * Whether or not to throw an error if the path exists.
+ * If not specified, then false is used.
+ * @return The nsIFile for the file in the test data directory.
+ * @throws If the file or directory does not exist.
+ */
+function getTestDirFile(aRelPath, aAllowNonExists) {
+ let relpath = getTestDirPath() + (aRelPath ? aRelPath : "");
+ return do_get_file(relpath, !!aAllowNonExists);
+}
+
+/**
+ * Helper function for getting the nsIFile for the maintenance service
+ * directory on Windows.
+ *
+ * @return The nsIFile for the maintenance service directory.
+ */
+function getMaintSvcDir() {
+ if (!IS_WIN) {
+ do_throw("Windows only function called by a different platform!");
+ }
+
+ const CSIDL_PROGRAM_FILES = 0x26;
+ const CSIDL_PROGRAM_FILESX86 = 0x2A;
+ // This will return an empty string on our Win XP build systems.
+ let maintSvcDir = getSpecialFolderDir(CSIDL_PROGRAM_FILESX86);
+ if (maintSvcDir) {
+ maintSvcDir.append("Mozilla Maintenance Service");
+ debugDump("using CSIDL_PROGRAM_FILESX86 - maintenance service install " +
+ "directory path: " + maintSvcDir.path);
+ }
+ if (!maintSvcDir || !maintSvcDir.exists()) {
+ maintSvcDir = getSpecialFolderDir(CSIDL_PROGRAM_FILES);
+ if (maintSvcDir) {
+ maintSvcDir.append("Mozilla Maintenance Service");
+ debugDump("using CSIDL_PROGRAM_FILES - maintenance service install " +
+ "directory path: " + maintSvcDir.path);
+ }
+ }
+ if (!maintSvcDir) {
+ do_throw("Unable to find the maintenance service install directory");
+ }
+
+ return maintSvcDir;
+}
+
+/**
+ * Get the nsILocalFile for a Windows special folder determined by the CSIDL
+ * passed.
+ *
+ * @param aCSIDL
+ * The CSIDL for the Windows special folder.
+ * @return The nsILocalFile for the Windows special folder.
+ * @throws If called from a platform other than Windows.
+ */
+function getSpecialFolderDir(aCSIDL) {
+ if (!IS_WIN) {
+ do_throw("Windows only function called by a different platform!");
+ }
+
+ let lib = ctypes.open("shell32");
+ let SHGetSpecialFolderPath = lib.declare("SHGetSpecialFolderPathW",
+ ctypes.winapi_abi,
+ ctypes.bool, /* bool(return) */
+ ctypes.int32_t, /* HWND hwndOwner */
+ ctypes.char16_t.ptr, /* LPTSTR lpszPath */
+ ctypes.int32_t, /* int csidl */
+ ctypes.bool /* BOOL fCreate */);
+
+ let aryPath = ctypes.char16_t.array()(260);
+ let rv = SHGetSpecialFolderPath(0, aryPath, aCSIDL, false);
+ lib.close();
+
+ let path = aryPath.readString(); // Convert the c-string to js-string
+ if (!path) {
+ return null;
+ }
+ debugDump("SHGetSpecialFolderPath returned path: " + path);
+ let dir = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsILocalFile);
+ dir.initWithPath(path);
+ return dir;
+}
+
+XPCOMUtils.defineLazyGetter(this, "gInstallDirPathHash", function test_gIDPH() {
+ if (!IS_WIN) {
+ do_throw("Windows only function called by a different platform!");
+ }
+
+ // Figure out where we should check for a cached hash value
+ if (!MOZ_APP_BASENAME) {
+ return null;
+ }
+
+ let vendor = MOZ_APP_VENDOR ? MOZ_APP_VENDOR : "Mozilla";
+ let appDir = getApplyDirFile(null, true);
+
+ const REG_PATH = "SOFTWARE\\" + vendor + "\\" + MOZ_APP_BASENAME +
+ "\\TaskBarIDs";
+ let regKey = Cc["@mozilla.org/windows-registry-key;1"].
+ createInstance(Ci.nsIWindowsRegKey);
+ try {
+ regKey.open(Ci.nsIWindowsRegKey.ROOT_KEY_LOCAL_MACHINE, REG_PATH,
+ Ci.nsIWindowsRegKey.ACCESS_ALL);
+ regKey.writeStringValue(appDir.path, gTestID);
+ return gTestID;
+ } catch (e) {
+ }
+
+ try {
+ regKey.create(Ci.nsIWindowsRegKey.ROOT_KEY_CURRENT_USER, REG_PATH,
+ Ci.nsIWindowsRegKey.ACCESS_ALL);
+ regKey.writeStringValue(appDir.path, gTestID);
+ return gTestID;
+ } catch (e) {
+ logTestInfo("failed to create registry key. Registry Path: " + REG_PATH +
+ ", Key Name: " + appDir.path + ", Key Value: " + gTestID +
+ ", Exception " + e);
+ }
+ return null;
+});
+
+XPCOMUtils.defineLazyGetter(this, "gLocalAppDataDir", function test_gLADD() {
+ if (!IS_WIN) {
+ do_throw("Windows only function called by a different platform!");
+ }
+
+ const CSIDL_LOCAL_APPDATA = 0x1c;
+ return getSpecialFolderDir(CSIDL_LOCAL_APPDATA);
+});
+
+XPCOMUtils.defineLazyGetter(this, "gProgFilesDir", function test_gPFD() {
+ if (!IS_WIN) {
+ do_throw("Windows only function called by a different platform!");
+ }
+
+ const CSIDL_PROGRAM_FILES = 0x26;
+ return getSpecialFolderDir(CSIDL_PROGRAM_FILES);
+});
+
+/**
+ * Helper function for getting the update root directory used by the tests. This
+ * returns the same directory as returned by nsXREDirProvider::GetUpdateRootDir
+ * in nsXREDirProvider.cpp so an application will be able to find the update
+ * when running a test that launches the application.
+ */
+function getMockUpdRootD() {
+ if (IS_WIN) {
+ return getMockUpdRootDWin();
+ }
+
+ if (IS_MACOSX) {
+ return getMockUpdRootDMac();
+ }
+
+ return getApplyDirFile(DIR_MACOS, true);
+}
+
+/**
+ * Helper function for getting the update root directory used by the tests. This
+ * returns the same directory as returned by nsXREDirProvider::GetUpdateRootDir
+ * in nsXREDirProvider.cpp so an application will be able to find the update
+ * when running a test that launches the application.
+ */
+function getMockUpdRootDWin() {
+ if (!IS_WIN) {
+ do_throw("Windows only function called by a different platform!");
+ }
+
+ let localAppDataDir = gLocalAppDataDir.clone();
+ let progFilesDir = gProgFilesDir.clone();
+ let appDir = Services.dirsvc.get(XRE_EXECUTABLE_FILE, Ci.nsIFile).parent;
+
+ let appDirPath = appDir.path;
+ let relPathUpdates = "";
+ if (gInstallDirPathHash && (MOZ_APP_VENDOR || MOZ_APP_BASENAME)) {
+ relPathUpdates += (MOZ_APP_VENDOR ? MOZ_APP_VENDOR : MOZ_APP_BASENAME) +
+ "\\" + DIR_UPDATES + "\\" + gInstallDirPathHash;
+ }
+
+ if (!relPathUpdates && progFilesDir) {
+ if (appDirPath.length > progFilesDir.path.length) {
+ if (appDirPath.substr(0, progFilesDir.path.length) == progFilesDir.path) {
+ if (MOZ_APP_VENDOR && MOZ_APP_BASENAME) {
+ relPathUpdates += MOZ_APP_VENDOR + "\\" + MOZ_APP_BASENAME;
+ } else {
+ relPathUpdates += MOZ_APP_BASENAME;
+ }
+ relPathUpdates += appDirPath.substr(progFilesDir.path.length);
+ }
+ }
+ }
+
+ if (!relPathUpdates) {
+ if (MOZ_APP_VENDOR && MOZ_APP_BASENAME) {
+ relPathUpdates += MOZ_APP_VENDOR + "\\" + MOZ_APP_BASENAME;
+ } else {
+ relPathUpdates += MOZ_APP_BASENAME;
+ }
+ relPathUpdates += "\\" + MOZ_APP_NAME;
+ }
+
+ let updatesDir = Cc["@mozilla.org/file/local;1"].
+ createInstance(Ci.nsILocalFile);
+ updatesDir.initWithPath(localAppDataDir.path + "\\" + relPathUpdates);
+ return updatesDir;
+}
+
+XPCOMUtils.defineLazyGetter(this, "gUpdatesRootDir", function test_gURD() {
+ if (!IS_MACOSX) {
+ do_throw("Mac OS X only function called by a different platform!");
+ }
+
+ let dir = Services.dirsvc.get("ULibDir", Ci.nsILocalFile);
+ dir.append("Caches");
+ if (MOZ_APP_VENDOR || MOZ_APP_BASENAME) {
+ dir.append(MOZ_APP_VENDOR ? MOZ_APP_VENDOR : MOZ_APP_BASENAME);
+ } else {
+ dir.append("Mozilla");
+ }
+ dir.append(DIR_UPDATES);
+ return dir;
+});
+
+/**
+ * Helper function for getting the update root directory used by the tests. This
+ * returns the same directory as returned by nsXREDirProvider::GetUpdateRootDir
+ * in nsXREDirProvider.cpp so an application will be able to find the update
+ * when running a test that launches the application.
+ */
+function getMockUpdRootDMac() {
+ if (!IS_MACOSX) {
+ do_throw("Mac OS X only function called by a different platform!");
+ }
+
+ let appDir = Services.dirsvc.get(XRE_EXECUTABLE_FILE, Ci.nsIFile).
+ parent.parent.parent;
+ let appDirPath = appDir.path;
+ appDirPath = appDirPath.substr(0, appDirPath.length - 4);
+
+ let pathUpdates = gUpdatesRootDir.path + appDirPath;
+ let updatesDir = Cc["@mozilla.org/file/local;1"].
+ createInstance(Ci.nsILocalFile);
+ updatesDir.initWithPath(pathUpdates);
+ return updatesDir;
+}
+
+/**
+ * Creates an update in progress lock file in the specified directory on
+ * Windows.
+ *
+ * @param aDir
+ * The nsIFile for the directory where the lock file should be created.
+ */
+function createUpdateInProgressLockFile(aDir) {
+ if (!IS_WIN) {
+ do_throw("Windows only function called by a different platform!");
+ }
+
+ let file = aDir.clone();
+ file.append(FILE_UPDATE_IN_PROGRESS_LOCK);
+ file.create(file.NORMAL_FILE_TYPE, 0o444);
+ file.QueryInterface(Ci.nsILocalFileWin);
+ file.fileAttributesWin |= file.WFA_READONLY;
+ file.fileAttributesWin &= ~file.WFA_READWRITE;
+ Assert.ok(file.exists(),
+ MSG_SHOULD_EXIST + getMsgPath(file.path));
+ Assert.ok(!file.isWritable(),
+ "the lock file should not be writeable");
+}
+
+/**
+ * Removes an update in progress lock file in the specified directory on
+ * Windows.
+ *
+ * @param aDir
+ * The nsIFile for the directory where the lock file is located.
+ */
+function removeUpdateInProgressLockFile(aDir) {
+ if (!IS_WIN) {
+ do_throw("Windows only function called by a different platform!");
+ }
+
+ let file = aDir.clone();
+ file.append(FILE_UPDATE_IN_PROGRESS_LOCK);
+ file.QueryInterface(Ci.nsILocalFileWin);
+ file.fileAttributesWin |= file.WFA_READWRITE;
+ file.fileAttributesWin &= ~file.WFA_READONLY;
+ file.remove(false);
+ Assert.ok(!file.exists(),
+ MSG_SHOULD_NOT_EXIST + getMsgPath(file.path));
+}
+
+/**
+ * Gets the test updater from the test data direcory.
+ *
+ * @return nsIFIle for the test updater.
+ */
+function getTestUpdater() {
+ let updater = getTestDirFile("updater.app", true);
+ if (!updater.exists()) {
+ updater = getTestDirFile(FILE_UPDATER_BIN);
+ if (!updater.exists()) {
+ do_throw("Unable to find the updater binary!");
+ }
+ }
+ Assert.ok(updater.exists(),
+ MSG_SHOULD_EXIST + getMsgPath(updater.path));
+ return updater;
+}
+
+/**
+ * Copies the test updater to the GRE binary directory and returns the nsIFile
+ * for the copied test updater.
+ *
+ * @return nsIFIle for the copied test updater.
+ */
+function copyTestUpdaterToBinDir() {
+ let testUpdater = getTestUpdater();
+ let updater = getGREBinDir();
+ updater.append(testUpdater.leafName);
+ if (!updater.exists()) {
+ testUpdater.copyToFollowingLinks(updater.parent, updater.leafName);
+ }
+ return updater;
+}
+
+/**
+ * Copies the test updater to the location where it will be launched to apply an
+ * update and returns the nsIFile for the copied test updater.
+ *
+ * @return nsIFIle for the copied test updater.
+ */
+function copyTestUpdaterForRunUsingUpdater() {
+ if (IS_WIN) {
+ return copyTestUpdaterToBinDir();
+ }
+
+ let testUpdater = getTestUpdater();
+ let updater = getUpdatesPatchDir();
+ updater.append(testUpdater.leafName);
+ if (!updater.exists()) {
+ testUpdater.copyToFollowingLinks(updater.parent, updater.leafName);
+ }
+
+ if (IS_MACOSX) {
+ updater.append("Contents");
+ updater.append("MacOS");
+ updater.append("org.mozilla.updater");
+ }
+ return updater;
+}
+
+/**
+ * Logs the contents of an update log and for maintenance service tests this
+ * will log the contents of the latest maintenanceservice.log.
+ *
+ * @param aLogLeafName
+ * The leaf name of the update log.
+ */
+function logUpdateLog(aLogLeafName) {
+ let updateLog = getUpdateLog(aLogLeafName);
+ if (updateLog.exists()) {
+ // xpcshell tests won't display the entire contents so log each line.
+ let updateLogContents = readFileBytes(updateLog).replace(/\r\n/g, "\n");
+ updateLogContents = replaceLogPaths(updateLogContents);
+ let aryLogContents = updateLogContents.split("\n");
+ logTestInfo("contents of " + updateLog.path + ":");
+ aryLogContents.forEach(function RU_LC_FE(aLine) {
+ logTestInfo(aLine);
+ });
+ } else {
+ logTestInfo("update log doesn't exist, path: " + updateLog.path);
+ }
+
+ if (IS_SERVICE_TEST) {
+ let serviceLog = getMaintSvcDir();
+ serviceLog.append("logs");
+ serviceLog.append("maintenanceservice.log");
+ if (serviceLog.exists()) {
+ // xpcshell tests won't display the entire contents so log each line.
+ let serviceLogContents = readFileBytes(serviceLog).replace(/\r\n/g, "\n");
+ serviceLogContents = replaceLogPaths(serviceLogContents);
+ let aryLogContents = serviceLogContents.split("\n");
+ logTestInfo("contents of " + serviceLog.path + ":");
+ aryLogContents.forEach(function RU_LC_FE(aLine) {
+ logTestInfo(aLine);
+ });
+ } else {
+ logTestInfo("maintenance service log doesn't exist, path: " +
+ serviceLog.path);
+ }
+ }
+}
+
+/**
+ * Gets the maintenance service log contents.
+ */
+function readServiceLogFile() {
+ let file = getMaintSvcDir();
+ file.append("logs");
+ file.append("maintenanceservice.log");
+ return readFile(file);
+}
+
+/**
+ * Launches the updater binary to apply an update for updater tests.
+ *
+ * @param aExpectedStatus
+ * The expected value of update.status when the test finishes. For
+ * service tests passing STATE_PENDING or STATE_APPLIED will change the
+ * value to STATE_PENDING_SVC and STATE_APPLIED_SVC respectively.
+ * @param aSwitchApp
+ * If true the update should switch the application with an updated
+ * staged application and if false the update should be applied to the
+ * installed application.
+ * @param aExpectedExitValue
+ * The expected exit value from the updater binary for non-service
+ * tests.
+ * @param aCheckSvcLog
+ * Whether the service log should be checked for service tests.
+ * @param aPatchDirPath (optional)
+ * When specified the patch directory path to use for invalid argument
+ * tests otherwise the normal path will be used.
+ * @param aInstallDirPath (optional)
+ * When specified the install directory path to use for invalid
+ * argument tests otherwise the normal path will be used.
+ * @param aApplyToDirPath (optional)
+ * When specified the apply to / working directory path to use for
+ * invalid argument tests otherwise the normal path will be used.
+ * @param aCallbackPath (optional)
+ * When specified the callback path to use for invalid argument tests
+ * otherwise the normal path will be used.
+ */
+function runUpdate(aExpectedStatus, aSwitchApp, aExpectedExitValue, aCheckSvcLog,
+ aPatchDirPath, aInstallDirPath, aApplyToDirPath,
+ aCallbackPath) {
+ let isInvalidArgTest = !!aPatchDirPath || !!aInstallDirPath ||
+ !!aApplyToDirPath || aCallbackPath;
+
+ let svcOriginalLog;
+ if (IS_SERVICE_TEST) {
+ copyFileToTestAppDir(FILE_MAINTENANCE_SERVICE_BIN, false);
+ copyFileToTestAppDir(FILE_MAINTENANCE_SERVICE_INSTALLER_BIN, false);
+ if (aCheckSvcLog) {
+ svcOriginalLog = readServiceLogFile();
+ }
+ }
+
+ // Copy the updater binary to the directory where it will apply updates.
+ let updateBin = copyTestUpdaterForRunUsingUpdater();
+ Assert.ok(updateBin.exists(),
+ MSG_SHOULD_EXIST + getMsgPath(updateBin.path));
+
+ let updatesDirPath = aPatchDirPath || getUpdatesPatchDir().path;
+ let installDirPath = aInstallDirPath || getApplyDirFile(null, true).path;
+ let applyToDirPath = aApplyToDirPath || getApplyDirFile(null, true).path;
+ let stageDirPath = aApplyToDirPath || getStageDirFile(null, true).path;
+
+ let callbackApp = getApplyDirFile(DIR_RESOURCES + gCallbackBinFile);
+ callbackApp.permissions = PERMS_DIRECTORY;
+
+ setAppBundleModTime();
+
+ let args = [updatesDirPath, installDirPath];
+ if (aSwitchApp) {
+ args[2] = stageDirPath;
+ args[3] = "0/replace";
+ } else {
+ args[2] = applyToDirPath;
+ args[3] = "0";
+ }
+
+ let launchBin = IS_SERVICE_TEST && isInvalidArgTest ? callbackApp : updateBin;
+
+ if (!isInvalidArgTest) {
+ args = args.concat([callbackApp.parent.path, callbackApp.path]);
+ args = args.concat(gCallbackArgs);
+ } else if (IS_SERVICE_TEST) {
+ args = ["launch-service", updateBin.path].concat(args);
+ } else if (aCallbackPath) {
+ args = args.concat([callbackApp.parent.path, aCallbackPath]);
+ }
+
+ debugDump("launching the program: " + launchBin.path + " " + args.join(" "));
+
+ if (aSwitchApp && !isInvalidArgTest) {
+ // We want to set the env vars again
+ gShouldResetEnv = undefined;
+ }
+
+ setEnvironment();
+
+ let process = Cc["@mozilla.org/process/util;1"].
+ createInstance(Ci.nsIProcess);
+ process.init(launchBin);
+ process.run(true, args, args.length);
+
+ resetEnvironment();
+
+ let status = readStatusFile();
+ if ((!IS_SERVICE_TEST && process.exitValue != aExpectedExitValue) ||
+ status != aExpectedStatus) {
+ if (process.exitValue != aExpectedExitValue) {
+ logTestInfo("updater exited with unexpected value! Got: " +
+ process.exitValue + ", Expected: " + aExpectedExitValue);
+ }
+ if (status != aExpectedStatus) {
+ logTestInfo("update status is not the expected status! Got: " + status +
+ ", Expected: " + aExpectedStatus);
+ }
+ logUpdateLog(FILE_LAST_UPDATE_LOG);
+ }
+
+ if (!IS_SERVICE_TEST) {
+ Assert.equal(process.exitValue, aExpectedExitValue,
+ "the process exit value" + MSG_SHOULD_EQUAL);
+ }
+ Assert.equal(status, aExpectedStatus,
+ "the update status" + MSG_SHOULD_EQUAL);
+
+ if (IS_SERVICE_TEST && aCheckSvcLog) {
+ let contents = readServiceLogFile();
+ Assert.notEqual(contents, svcOriginalLog,
+ "the contents of the maintenanceservice.log should not " +
+ "be the same as the original contents");
+ if (!isInvalidArgTest) {
+ Assert.notEqual(contents.indexOf(LOG_SVC_SUCCESSFUL_LAUNCH), -1,
+ "the contents of the maintenanceservice.log should " +
+ "contain the successful launch string");
+ }
+ }
+
+ do_execute_soon(runUpdateFinished);
+}
+
+/**
+ * Launches the helper binary synchronously with the specified arguments for
+ * updater tests.
+ *
+ * @param aArgs
+ * The arguments to pass to the helper binary.
+ * @return the process exit value returned by the helper binary.
+ */
+function runTestHelperSync(aArgs) {
+ let helperBin = getTestDirFile(FILE_HELPER_BIN);
+ let process = Cc["@mozilla.org/process/util;1"].
+ createInstance(Ci.nsIProcess);
+ process.init(helperBin);
+ debugDump("Running " + helperBin.path + " " + aArgs.join(" "));
+ process.run(true, aArgs, aArgs.length);
+ return process.exitValue;
+}
+
+/**
+ * Creates a symlink for updater tests.
+ */
+function createSymlink() {
+ let args = ["setup-symlink", "moz-foo", "moz-bar", "target",
+ getApplyDirFile().path + "/" + DIR_RESOURCES + "link"];
+ let exitValue = runTestHelperSync(args);
+ Assert.equal(exitValue, 0,
+ "the helper process exit value should be 0");
+ getApplyDirFile(DIR_RESOURCES + "link", false).permissions = 0o666;
+ args = ["setup-symlink", "moz-foo2", "moz-bar2", "target2",
+ getApplyDirFile().path + "/" + DIR_RESOURCES + "link2", "change-perm"];
+ exitValue = runTestHelperSync(args);
+ Assert.equal(exitValue, 0,
+ "the helper process exit value should be 0");
+}
+
+/**
+ * Removes a symlink for updater tests.
+ */
+function removeSymlink() {
+ let args = ["remove-symlink", "moz-foo", "moz-bar", "target",
+ getApplyDirFile().path + "/" + DIR_RESOURCES + "link"];
+ let exitValue = runTestHelperSync(args);
+ Assert.equal(exitValue, 0,
+ "the helper process exit value should be 0");
+ args = ["remove-symlink", "moz-foo2", "moz-bar2", "target2",
+ getApplyDirFile().path + "/" + DIR_RESOURCES + "link2"];
+ exitValue = runTestHelperSync(args);
+ Assert.equal(exitValue, 0,
+ "the helper process exit value should be 0");
+}
+
+/**
+ * Checks a symlink for updater tests.
+ */
+function checkSymlink() {
+ let args = ["check-symlink",
+ getApplyDirFile().path + "/" + DIR_RESOURCES + "link"];
+ let exitValue = runTestHelperSync(args);
+ Assert.equal(exitValue, 0,
+ "the helper process exit value should be 0");
+}
+
+/**
+ * Sets the active update and related information for updater tests.
+ */
+function setupActiveUpdate() {
+ let state = IS_SERVICE_TEST ? STATE_PENDING_SVC : STATE_PENDING;
+ let channel = gDefaultPrefBranch.getCharPref(PREF_APP_UPDATE_CHANNEL);
+ let patches = getLocalPatchString(null, null, null, null, null, "true",
+ state);
+ let updates = getLocalUpdateString(patches, null, null, null, null, null,
+ null, null, null, null, "true", channel);
+ writeUpdatesToXMLFile(getLocalUpdatesXMLString(updates), true);
+ writeVersionFile(DEFAULT_UPDATE_VERSION);
+ writeStatusFile(state);
+ reloadUpdateManagerData();
+ Assert.ok(!!gUpdateManager.activeUpdate,
+ "the active update should be defined");
+}
+
+/**
+ * Gets the specified update log.
+ *
+ * @param aLogLeafName
+ * The leaf name of the log to get.
+ * @return nsIFile for the update log.
+ */
+function getUpdateLog(aLogLeafName) {
+ let updateLog = getUpdatesDir();
+ if (aLogLeafName == FILE_UPDATE_LOG) {
+ updateLog.append(DIR_PATCH);
+ }
+ updateLog.append(aLogLeafName);
+ return updateLog;
+}
+
+/**
+ * The update-staged observer for the call to nsIUpdateProcessor:processUpdate.
+ */
+const gUpdateStagedObserver = {
+ observe: function(aSubject, aTopic, aData) {
+ debugDump("observe called with topic: " + aTopic + ", data: " + aData);
+ if (aTopic == "update-staged") {
+ Services.obs.removeObserver(gUpdateStagedObserver, "update-staged");
+ // The environment is reset after the update-staged observer topic because
+ // processUpdate in nsIUpdateProcessor uses a new thread and clearing the
+ // environment immediately after calling processUpdate can clear the
+ // environment before the updater is launched.
+ resetEnvironment();
+ // Use do_execute_soon to prevent any failures from propagating to the
+ // update service.
+ do_execute_soon(checkUpdateStagedState.bind(null, aData));
+ }
+ },
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver])
+};
+
+/**
+ * Stages an update using nsIUpdateProcessor:processUpdate for updater tests.
+ *
+ * @param aCheckSvcLog
+ * Whether the service log should be checked for service tests.
+ */
+function stageUpdate(aCheckSvcLog) {
+ debugDump("start - attempting to stage update");
+
+ if (IS_SERVICE_TEST && aCheckSvcLog) {
+ gSvcOriginalLogContents = readServiceLogFile();
+ }
+
+ Services.obs.addObserver(gUpdateStagedObserver, "update-staged", false);
+
+ setAppBundleModTime();
+ setEnvironment();
+ // Stage the update.
+ Cc["@mozilla.org/updates/update-processor;1"].
+ createInstance(Ci.nsIUpdateProcessor).
+ processUpdate(gUpdateManager.activeUpdate);
+
+ // The environment is not reset here because processUpdate in
+ // nsIUpdateProcessor uses a new thread and clearing the environment
+ // immediately after calling processUpdate can clear the environment before
+ // the updater is launched. Instead it is reset after the update-staged
+ // observer topic.
+
+ debugDump("finish - attempting to stage update");
+}
+
+/**
+ * Checks that the update state is correct as well as the expected files are
+ * present after staging and update for updater tests and then calls
+ * stageUpdateFinished.
+ *
+ * @param aUpdateState
+ * The update state received by the observer notification.
+ */
+function checkUpdateStagedState(aUpdateState) {
+ if (IS_WIN) {
+ if (IS_SERVICE_TEST) {
+ waitForServiceStop(false);
+ } else {
+ let updater = getApplyDirFile(FILE_UPDATER_BIN, true);
+ if (isFileInUse(updater)) {
+ do_timeout(FILE_IN_USE_TIMEOUT_MS,
+ checkUpdateStagedState.bind(null, aUpdateState));
+ return;
+ }
+ }
+ }
+
+ Assert.equal(aUpdateState, STATE_AFTER_STAGE,
+ "the notified state" + MSG_SHOULD_EQUAL);
+
+ if (!gStagingRemovedUpdate) {
+ Assert.equal(readStatusState(), STATE_AFTER_STAGE,
+ "the status file state" + MSG_SHOULD_EQUAL);
+
+ Assert.equal(gUpdateManager.activeUpdate.state, STATE_AFTER_STAGE,
+ "the update state" + MSG_SHOULD_EQUAL);
+ }
+
+ Assert.equal(gUpdateManager.updateCount, 1,
+ "the update manager updateCount attribute" + MSG_SHOULD_EQUAL);
+ Assert.equal(gUpdateManager.getUpdateAt(0).state, STATE_AFTER_STAGE,
+ "the update state" + MSG_SHOULD_EQUAL);
+
+ let log = getUpdateLog(FILE_LAST_UPDATE_LOG);
+ Assert.ok(log.exists(),
+ MSG_SHOULD_EXIST + getMsgPath(log.path));
+
+ log = getUpdateLog(FILE_UPDATE_LOG);
+ Assert.ok(!log.exists(),
+ MSG_SHOULD_NOT_EXIST + getMsgPath(log.path));
+
+ log = getUpdateLog(FILE_BACKUP_UPDATE_LOG);
+ Assert.ok(!log.exists(),
+ MSG_SHOULD_NOT_EXIST + getMsgPath(log.path));
+
+ let stageDir = getStageDirFile(null, true);
+ if (STATE_AFTER_STAGE == STATE_APPLIED ||
+ STATE_AFTER_STAGE == STATE_APPLIED_SVC) {
+ Assert.ok(stageDir.exists(),
+ MSG_SHOULD_EXIST + getMsgPath(stageDir.path));
+ } else {
+ Assert.ok(!stageDir.exists(),
+ MSG_SHOULD_NOT_EXIST + getMsgPath(stageDir.path));
+ }
+
+ if (IS_SERVICE_TEST && gSvcOriginalLogContents !== undefined) {
+ let contents = readServiceLogFile();
+ Assert.notEqual(contents, gSvcOriginalLogContents,
+ "the contents of the maintenanceservice.log should not " +
+ "be the same as the original contents");
+ Assert.notEqual(contents.indexOf(LOG_SVC_SUCCESSFUL_LAUNCH), -1,
+ "the contents of the maintenanceservice.log should " +
+ "contain the successful launch string");
+ }
+
+ do_execute_soon(stageUpdateFinished);
+}
+
+/**
+ * Helper function to check whether the maintenance service updater tests should
+ * run. See bug 711660 for more details.
+ *
+ * @return true if the test should run and false if it shouldn't.
+ */
+function shouldRunServiceTest() {
+ if (!IS_WIN) {
+ do_throw("Windows only function called by a different platform!");
+ }
+
+ let binDir = getGREBinDir();
+ let updaterBin = binDir.clone();
+ updaterBin.append(FILE_UPDATER_BIN);
+ Assert.ok(updaterBin.exists(),
+ MSG_SHOULD_EXIST + ", leafName: " + updaterBin.leafName);
+
+ let updaterBinPath = updaterBin.path;
+ if (/ /.test(updaterBinPath)) {
+ updaterBinPath = '"' + updaterBinPath + '"';
+ }
+
+ let isBinSigned = isBinarySigned(updaterBinPath);
+
+ const REG_PATH = "SOFTWARE\\Mozilla\\MaintenanceService\\" +
+ "3932ecacee736d366d6436db0f55bce4";
+ let key = Cc["@mozilla.org/windows-registry-key;1"].
+ createInstance(Ci.nsIWindowsRegKey);
+ try {
+ key.open(Ci.nsIWindowsRegKey.ROOT_KEY_LOCAL_MACHINE, REG_PATH,
+ Ci.nsIWindowsRegKey.ACCESS_READ | key.WOW64_64);
+ } catch (e) {
+ // The build system could sign the files and not have the test registry key
+ // in which case we should fail the test if the updater binary is signed so
+ // the build system can be fixed by adding the registry key.
+ if (IS_AUTHENTICODE_CHECK_ENABLED) {
+ Assert.ok(!isBinSigned,
+ "the updater.exe binary should not be signed when the test " +
+ "registry key doesn't exist (if it is, build system " +
+ "configuration bug?)");
+ }
+
+ logTestInfo("this test can only run on the buildbot build system at this " +
+ "time");
+ return false;
+ }
+
+ // Check to make sure the service is installed
+ let args = ["wait-for-service-stop", "MozillaMaintenance", "10"];
+ let exitValue = runTestHelperSync(args);
+ Assert.notEqual(exitValue, 0xEE, "the maintenance service should be " +
+ "installed (if not, build system configuration bug?)");
+
+ if (IS_AUTHENTICODE_CHECK_ENABLED) {
+ // The test registry key exists and IS_AUTHENTICODE_CHECK_ENABLED is true
+ // so the binaries should be signed. To run the test locally
+ // DISABLE_UPDATER_AUTHENTICODE_CHECK can be defined.
+ Assert.ok(isBinSigned,
+ "the updater.exe binary should be signed (if not, build system " +
+ "configuration bug?)");
+ }
+
+ // In case the machine is running an old maintenance service or if it
+ // is not installed, and permissions exist to install it. Then install
+ // the newer bin that we have since all of the other checks passed.
+ return attemptServiceInstall();
+}
+
+/**
+ * Helper function to check whether the a binary is signed.
+ *
+ * @param aBinPath
+ * The path to the file to check if it is signed.
+ * @return true if the file is signed and false if it isn't.
+ */
+function isBinarySigned(aBinPath) {
+ let args = ["check-signature", aBinPath];
+ let exitValue = runTestHelperSync(args);
+ if (exitValue != 0) {
+ logTestInfo("binary is not signed. " + FILE_HELPER_BIN + " returned " +
+ exitValue + " for file " + aBinPath);
+ return false;
+ }
+ return true;
+}
+
+/**
+ * Helper function for asynchronously setting up the application files required
+ * to launch the application for the updater tests by either copying or creating
+ * symlinks for the files. This is needed for Windows debug builds which can
+ * lock a file that is being copied so that the tests can run in parallel. After
+ * the files have been copied the setupUpdaterTestFinished function will be
+ * called.
+ */
+function setupAppFilesAsync() {
+ gTimeoutRuns++;
+ try {
+ setupAppFiles();
+ } catch (e) {
+ if (gTimeoutRuns > MAX_TIMEOUT_RUNS) {
+ do_throw("Exceeded MAX_TIMEOUT_RUNS while trying to setup application " +
+ "files! Exception: " + e);
+ }
+ do_execute_soon(setupAppFilesAsync);
+ return;
+ }
+
+ do_execute_soon(setupUpdaterTestFinished);
+}
+
+/**
+ * Helper function for setting up the application files required to launch the
+ * application for the updater tests by either copying or creating symlinks to
+ * the files.
+ */
+function setupAppFiles() {
+ debugDump("start - copying or creating symlinks to application files " +
+ "for the test");
+
+ let destDir = getApplyDirFile(null, true);
+ if (!destDir.exists()) {
+ try {
+ destDir.create(Ci.nsIFile.DIRECTORY_TYPE, PERMS_DIRECTORY);
+ } catch (e) {
+ logTestInfo("unable to create directory! Path: " + destDir.path +
+ ", Exception: " + e);
+ do_throw(e);
+ }
+ }
+
+ // Required files for the application or the test that aren't listed in the
+ // dependentlibs.list file.
+ let appFiles = [{relPath: FILE_APP_BIN,
+ inGreDir: false},
+ {relPath: FILE_APPLICATION_INI,
+ inGreDir: true},
+ {relPath: "dependentlibs.list",
+ inGreDir: true}];
+
+ // On Linux the updater.png must also be copied
+ if (IS_UNIX && !IS_MACOSX) {
+ appFiles.push({relPath: "icons/updater.png",
+ inGreDir: true});
+ }
+
+ // Read the dependent libs file leafnames from the dependentlibs.list file
+ // into the array.
+ let deplibsFile = gGREDirOrig.clone();
+ deplibsFile.append("dependentlibs.list");
+ let fis = Cc["@mozilla.org/network/file-input-stream;1"].
+ createInstance(Ci.nsIFileInputStream);
+ fis.init(deplibsFile, 0x01, 0o444, Ci.nsIFileInputStream.CLOSE_ON_EOF);
+ fis.QueryInterface(Ci.nsILineInputStream);
+
+ let hasMore;
+ let line = {};
+ do {
+ hasMore = fis.readLine(line);
+ appFiles.push({relPath: line.value,
+ inGreDir: false});
+ } while (hasMore);
+
+ fis.close();
+
+ appFiles.forEach(function CMAF_FLN_FE(aAppFile) {
+ copyFileToTestAppDir(aAppFile.relPath, aAppFile.inGreDir);
+ });
+
+ copyTestUpdaterToBinDir();
+
+ debugDump("finish - copying or creating symlinks to application files " +
+ "for the test");
+}
+
+/**
+ * Copies the specified files from the dist/bin directory into the test's
+ * application directory.
+ *
+ * @param aFileRelPath
+ * The relative path to the source and the destination of the file to
+ * copy.
+ * @param aInGreDir
+ * Whether the file is located in the GRE directory which is
+ * <bundle>/Contents/Resources on Mac OS X and is the installation
+ * directory on all other platforms. If false the file must be in the
+ * GRE Binary directory which is <bundle>/Contents/MacOS on Mac OS X
+ * and is the installation directory on on all other platforms.
+ */
+function copyFileToTestAppDir(aFileRelPath, aInGreDir) {
+ // gGREDirOrig and gGREBinDirOrig must always be cloned when changing its
+ // properties
+ let srcFile = aInGreDir ? gGREDirOrig.clone() : gGREBinDirOrig.clone();
+ let destFile = aInGreDir ? getGREDir() : getGREBinDir();
+ let fileRelPath = aFileRelPath;
+ let pathParts = fileRelPath.split("/");
+ for (let i = 0; i < pathParts.length; i++) {
+ if (pathParts[i]) {
+ srcFile.append(pathParts[i]);
+ destFile.append(pathParts[i]);
+ }
+ }
+
+ if (IS_MACOSX && !srcFile.exists()) {
+ debugDump("unable to copy file since it doesn't exist! Checking if " +
+ fileRelPath + ".app exists. Path: " + srcFile.path);
+ // gGREDirOrig and gGREBinDirOrig must always be cloned when changing its
+ // properties
+ srcFile = aInGreDir ? gGREDirOrig.clone() : gGREBinDirOrig.clone();
+ destFile = aInGreDir ? getGREDir() : getGREBinDir();
+ for (let i = 0; i < pathParts.length; i++) {
+ if (pathParts[i]) {
+ srcFile.append(pathParts[i] + (pathParts.length - 1 == i ? ".app" : ""));
+ destFile.append(pathParts[i] + (pathParts.length - 1 == i ? ".app" : ""));
+ }
+ }
+ fileRelPath = fileRelPath + ".app";
+ }
+ Assert.ok(srcFile.exists(),
+ MSG_SHOULD_EXIST + ", leafName: " + srcFile.leafName);
+
+ // Symlink libraries. Note that the XUL library on Mac OS X doesn't have a
+ // file extension and shouldSymlink will always be false on Windows.
+ let shouldSymlink = (pathParts[pathParts.length - 1] == "XUL" ||
+ fileRelPath.substr(fileRelPath.length - 3) == ".so" ||
+ fileRelPath.substr(fileRelPath.length - 6) == ".dylib");
+ if (!shouldSymlink) {
+ if (!destFile.exists()) {
+ try {
+ srcFile.copyToFollowingLinks(destFile.parent, destFile.leafName);
+ } catch (e) {
+ // Just in case it is partially copied
+ if (destFile.exists()) {
+ try {
+ destFile.remove(true);
+ } catch (ex) {
+ logTestInfo("unable to remove file that failed to copy! Path: " +
+ destFile.path);
+ }
+ }
+ do_throw("Unable to copy file! Path: " + srcFile.path +
+ ", Exception: " + ex);
+ }
+ }
+ } else {
+ try {
+ if (destFile.exists()) {
+ destFile.remove(false);
+ }
+ let ln = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsILocalFile);
+ ln.initWithPath("/bin/ln");
+ let process = Cc["@mozilla.org/process/util;1"].createInstance(Ci.nsIProcess);
+ process.init(ln);
+ let args = ["-s", srcFile.path, destFile.path];
+ process.run(true, args, args.length);
+ Assert.ok(destFile.isSymlink(),
+ destFile.leafName + " should be a symlink");
+ } catch (e) {
+ do_throw("Unable to create symlink for file! Path: " + srcFile.path +
+ ", Exception: " + e);
+ }
+ }
+}
+
+/**
+ * Attempts to upgrade the maintenance service if permissions are allowed.
+ * This is useful for XP where we have permission to upgrade in case an
+ * older service installer exists. Also if the user manually installed into
+ * a unprivileged location.
+ *
+ * @return true if the installed service is from this build. If the installed
+ * service is not from this build the test will fail instead of
+ * returning false.
+ */
+function attemptServiceInstall() {
+ if (!IS_WIN) {
+ do_throw("Windows only function called by a different platform!");
+ }
+
+ let maintSvcDir = getMaintSvcDir();
+ Assert.ok(maintSvcDir.exists(),
+ MSG_SHOULD_EXIST + ", leafName: " + maintSvcDir.leafName);
+ let oldMaintSvcBin = maintSvcDir.clone();
+ oldMaintSvcBin.append(FILE_MAINTENANCE_SERVICE_BIN);
+ Assert.ok(oldMaintSvcBin.exists(),
+ MSG_SHOULD_EXIST + ", leafName: " + oldMaintSvcBin.leafName);
+ let buildMaintSvcBin = getGREBinDir();
+ buildMaintSvcBin.append(FILE_MAINTENANCE_SERVICE_BIN);
+ if (readFileBytes(oldMaintSvcBin) == readFileBytes(buildMaintSvcBin)) {
+ debugDump("installed maintenance service binary is the same as the " +
+ "build's maintenance service binary");
+ return true;
+ }
+ let backupMaintSvcBin = maintSvcDir.clone();
+ backupMaintSvcBin.append(FILE_MAINTENANCE_SERVICE_BIN + ".backup");
+ try {
+ if (backupMaintSvcBin.exists()) {
+ backupMaintSvcBin.remove(false);
+ }
+ oldMaintSvcBin.moveTo(maintSvcDir, FILE_MAINTENANCE_SERVICE_BIN + ".backup");
+ buildMaintSvcBin.copyTo(maintSvcDir, FILE_MAINTENANCE_SERVICE_BIN);
+ backupMaintSvcBin.remove(false);
+ } catch (e) {
+ // Restore the original file in case the moveTo was successful.
+ if (backupMaintSvcBin.exists()) {
+ oldMaintSvcBin = maintSvcDir.clone();
+ oldMaintSvcBin.append(FILE_MAINTENANCE_SERVICE_BIN);
+ if (!oldMaintSvcBin.exists()) {
+ backupMaintSvcBin.moveTo(maintSvcDir, FILE_MAINTENANCE_SERVICE_BIN);
+ }
+ }
+ Assert.ok(false, "should be able copy the test maintenance service to " +
+ "the maintenance service directory (if not, build system " +
+ "configuration bug?), path: " + maintSvcDir.path);
+ }
+
+ return true;
+}
+
+/**
+ * Waits for the applications that are launched by the maintenance service to
+ * stop.
+ */
+function waitServiceApps() {
+ if (!IS_WIN) {
+ do_throw("Windows only function called by a different platform!");
+ }
+
+ // maintenanceservice_installer.exe is started async during updates.
+ waitForApplicationStop("maintenanceservice_installer.exe");
+ // maintenanceservice_tmp.exe is started async from the service installer.
+ waitForApplicationStop("maintenanceservice_tmp.exe");
+ // In case the SCM thinks the service is stopped, but process still exists.
+ waitForApplicationStop("maintenanceservice.exe");
+}
+
+/**
+ * Waits for the maintenance service to stop.
+ */
+function waitForServiceStop(aFailTest) {
+ if (!IS_WIN) {
+ do_throw("Windows only function called by a different platform!");
+ }
+
+ waitServiceApps();
+ debugDump("waiting for the maintenance service to stop if necessary");
+ // Use the helper bin to ensure the service is stopped. If not stopped, then
+ // wait for the service to stop (at most 120 seconds).
+ let args = ["wait-for-service-stop", "MozillaMaintenance", "120"];
+ let exitValue = runTestHelperSync(args);
+ Assert.notEqual(exitValue, 0xEE,
+ "the maintenance service should exist");
+ if (exitValue != 0) {
+ if (aFailTest) {
+ Assert.ok(false, "the maintenance service should stop, process exit " +
+ "value: " + exitValue);
+ }
+ logTestInfo("maintenance service did not stop which may cause test " +
+ "failures later, process exit value: " + exitValue);
+ } else {
+ debugDump("service stopped");
+ }
+ waitServiceApps();
+}
+
+/**
+ * Waits for the specified application to stop.
+ *
+ * @param aApplication
+ * The application binary name to wait until it has stopped.
+ */
+function waitForApplicationStop(aApplication) {
+ if (!IS_WIN) {
+ do_throw("Windows only function called by a different platform!");
+ }
+
+ debugDump("waiting for " + aApplication + " to stop if necessary");
+ // Use the helper bin to ensure the application is stopped. If not stopped,
+ // then wait for it to stop (at most 120 seconds).
+ let args = ["wait-for-application-exit", aApplication, "120"];
+ let exitValue = runTestHelperSync(args);
+ Assert.equal(exitValue, 0,
+ "the process should have stopped, process name: " +
+ aApplication);
+}
+
+
+/**
+ * Gets the platform specific shell binary that is launched using nsIProcess and
+ * in turn launches a binary used for the test (e.g. application, updater,
+ * etc.). A shell is used so debug console output can be redirected to a file so
+ * it doesn't end up in the test log.
+ *
+ * @return nsIFile for the shell binary to launch using nsIProcess.
+ */
+function getLaunchBin() {
+ let launchBin;
+ if (IS_WIN) {
+ launchBin = Services.dirsvc.get("WinD", Ci.nsIFile);
+ launchBin.append("System32");
+ launchBin.append("cmd.exe");
+ } else {
+ launchBin = Cc["@mozilla.org/file/local;1"].
+ createInstance(Ci.nsILocalFile);
+ launchBin.initWithPath("/bin/sh");
+ }
+ Assert.ok(launchBin.exists(),
+ MSG_SHOULD_EXIST + getMsgPath(launchBin.path));
+
+ return launchBin;
+}
+
+
+/**
+ * Locks a Windows directory.
+ *
+ * @param aDirPath
+ * The test file object that describes the file to make in use.
+ */
+function lockDirectory(aDirPath) {
+ if (!IS_WIN) {
+ do_throw("Windows only function called by a different platform!");
+ }
+
+ debugDump("start - locking installation directory");
+ const LPCWSTR = ctypes.char16_t.ptr;
+ const DWORD = ctypes.uint32_t;
+ const LPVOID = ctypes.voidptr_t;
+ const GENERIC_READ = 0x80000000;
+ const FILE_SHARE_READ = 1;
+ const FILE_SHARE_WRITE = 2;
+ const OPEN_EXISTING = 3;
+ const FILE_FLAG_BACKUP_SEMANTICS = 0x02000000;
+ const INVALID_HANDLE_VALUE = LPVOID(0xffffffff);
+ let kernel32 = ctypes.open("kernel32");
+ let CreateFile = kernel32.declare("CreateFileW", ctypes.default_abi,
+ LPVOID, LPCWSTR, DWORD, DWORD,
+ LPVOID, DWORD, DWORD, LPVOID);
+ gHandle = CreateFile(aDirPath, GENERIC_READ,
+ FILE_SHARE_READ | FILE_SHARE_WRITE, LPVOID(0),
+ OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, LPVOID(0));
+ Assert.notEqual(gHandle.toString(), INVALID_HANDLE_VALUE.toString(),
+ "the handle should not equal INVALID_HANDLE_VALUE");
+ kernel32.close();
+ debugDump("finish - locking installation directory");
+}
+
+/**
+ * Launches the test helper binary to make it in use for updater tests and then
+ * calls waitForHelperSleep.
+ *
+ * @param aTestFile
+ * The test file object that describes the file to make in use.
+ */
+function runHelperFileInUse(aRelPath, aCopyTestHelper) {
+ logTestInfo("aRelPath: " + aRelPath);
+ // Launch an existing file so it is in use during the update.
+ let helperBin = getTestDirFile(FILE_HELPER_BIN);
+ let fileInUseBin = getApplyDirFile(aRelPath);
+ if (aCopyTestHelper) {
+ fileInUseBin.remove(false);
+ helperBin.copyTo(fileInUseBin.parent, fileInUseBin.leafName);
+ }
+ fileInUseBin.permissions = PERMS_DIRECTORY;
+ let args = [getApplyDirPath() + DIR_RESOURCES, "input", "output", "-s",
+ HELPER_SLEEP_TIMEOUT];
+ let fileInUseProcess = Cc["@mozilla.org/process/util;1"].
+ createInstance(Ci.nsIProcess);
+ fileInUseProcess.init(fileInUseBin);
+ fileInUseProcess.run(false, args, args.length);
+
+ do_execute_soon(waitForHelperSleep);
+}
+
+/**
+ * Launches the test helper binary and locks a file specified on the command
+ * line for updater tests and then calls waitForHelperSleep.
+ *
+ * @param aTestFile
+ * The test file object that describes the file to lock.
+ */
+function runHelperLockFile(aTestFile) {
+ // Exclusively lock an existing file so it is in use during the update.
+ let helperBin = getTestDirFile(FILE_HELPER_BIN);
+ let helperDestDir = getApplyDirFile(DIR_RESOURCES);
+ helperBin.copyTo(helperDestDir, FILE_HELPER_BIN);
+ helperBin = getApplyDirFile(DIR_RESOURCES + FILE_HELPER_BIN);
+ // Strip off the first two directories so the path has to be from the helper's
+ // working directory.
+ let lockFileRelPath = aTestFile.relPathDir.split("/");
+ if (IS_MACOSX) {
+ lockFileRelPath = lockFileRelPath.slice(2);
+ }
+ lockFileRelPath = lockFileRelPath.join("/") + "/" + aTestFile.fileName;
+ let args = [getApplyDirPath() + DIR_RESOURCES, "input", "output", "-s",
+ HELPER_SLEEP_TIMEOUT, lockFileRelPath];
+ let helperProcess = Cc["@mozilla.org/process/util;1"].
+ createInstance(Ci.nsIProcess);
+ helperProcess.init(helperBin);
+ helperProcess.run(false, args, args.length);
+
+ do_execute_soon(waitForHelperSleep);
+}
+
+/**
+ * Helper function that waits until the helper has completed its operations and
+ * calls waitForHelperSleepFinished when it is finished.
+ */
+function waitForHelperSleep() {
+ gTimeoutRuns++;
+ // Give the lock file process time to lock the file before updating otherwise
+ // this test can fail intermittently on Windows debug builds.
+ let output = getApplyDirFile(DIR_RESOURCES + "output", true);
+ if (readFile(output) != "sleeping\n") {
+ if (gTimeoutRuns > MAX_TIMEOUT_RUNS) {
+ do_throw("Exceeded MAX_TIMEOUT_RUNS while waiting for the helper to " +
+ "finish its operation. Path: " + output.path);
+ }
+ // Uses do_timeout instead of do_execute_soon to lessen log spew.
+ do_timeout(FILE_IN_USE_TIMEOUT_MS, waitForHelperSleep);
+ return;
+ }
+ try {
+ output.remove(false);
+ } catch (e) {
+ if (gTimeoutRuns > MAX_TIMEOUT_RUNS) {
+ do_throw("Exceeded MAX_TIMEOUT_RUNS while waiting for the helper " +
+ "message file to no longer be in use. Path: " + output.path);
+ }
+ debugDump("failed to remove file. Path: " + output.path);
+ // Uses do_timeout instead of do_execute_soon to lessen log spew.
+ do_timeout(FILE_IN_USE_TIMEOUT_MS, waitForHelperSleep);
+ return;
+ }
+ waitForHelperSleepFinished();
+}
+
+/**
+ * Helper function that waits until the helper has finished its operations
+ * before calling waitForHelperFinishFileUnlock to verify that the helper's
+ * input and output directories are no longer in use.
+ */
+function waitForHelperFinished() {
+ // Give the lock file process time to lock the file before updating otherwise
+ // this test can fail intermittently on Windows debug builds.
+ let output = getApplyDirFile(DIR_RESOURCES + "output", true);
+ if (readFile(output) != "finished\n") {
+ // Uses do_timeout instead of do_execute_soon to lessen log spew.
+ do_timeout(FILE_IN_USE_TIMEOUT_MS, waitForHelperFinished);
+ return;
+ }
+ // Give the lock file process time to unlock the file before deleting the
+ // input and output files.
+ waitForHelperFinishFileUnlock();
+}
+
+/**
+ * Helper function that waits until the helper's input and output files are no
+ * longer in use before calling waitForHelperExitFinished.
+ */
+function waitForHelperFinishFileUnlock() {
+ try {
+ let output = getApplyDirFile(DIR_RESOURCES + "output", true);
+ if (output.exists()) {
+ output.remove(false);
+ }
+ let input = getApplyDirFile(DIR_RESOURCES + "input", true);
+ if (input.exists()) {
+ input.remove(false);
+ }
+ } catch (e) {
+ // Give the lock file process time to unlock the file before deleting the
+ // input and output files.
+ do_execute_soon(waitForHelperFinishFileUnlock);
+ return;
+ }
+ do_execute_soon(waitForHelperExitFinished);
+}
+
+/**
+ * Helper function to tell the helper to finish and exit its sleep state.
+ */
+function waitForHelperExit() {
+ let input = getApplyDirFile(DIR_RESOURCES + "input", true);
+ writeFile(input, "finish\n");
+ waitForHelperFinished();
+}
+
+/**
+ * Helper function for updater binary tests that creates the files and
+ * directories used by the test.
+ *
+ * @param aMarFile
+ * The mar file for the update test.
+ * @param aPostUpdateAsync
+ * When null the updater.ini is not created otherwise this parameter
+ * is passed to createUpdaterINI.
+ * @param aPostUpdateExeRelPathPrefix
+ * When aPostUpdateAsync null this value is ignored otherwise it is
+ * passed to createUpdaterINI.
+ */
+function setupUpdaterTest(aMarFile, aPostUpdateAsync,
+ aPostUpdateExeRelPathPrefix = "") {
+ let updatesPatchDir = getUpdatesPatchDir();
+ if (!updatesPatchDir.exists()) {
+ updatesPatchDir.create(Ci.nsIFile.DIRECTORY_TYPE, PERMS_DIRECTORY);
+ }
+ // Copy the mar that will be applied
+ let mar = getTestDirFile(aMarFile);
+ mar.copyToFollowingLinks(updatesPatchDir, FILE_UPDATE_MAR);
+
+ let helperBin = getTestDirFile(FILE_HELPER_BIN);
+ helperBin.permissions = PERMS_DIRECTORY;
+ let afterApplyBinDir = getApplyDirFile(DIR_RESOURCES, true);
+ helperBin.copyToFollowingLinks(afterApplyBinDir, gCallbackBinFile);
+ helperBin.copyToFollowingLinks(afterApplyBinDir, gPostUpdateBinFile);
+
+ gTestFiles.forEach(function SUT_TF_FE(aTestFile) {
+ if (aTestFile.originalFile || aTestFile.originalContents) {
+ let testDir = getApplyDirFile(aTestFile.relPathDir, true);
+ if (!testDir.exists()) {
+ testDir.create(Ci.nsIFile.DIRECTORY_TYPE, PERMS_DIRECTORY);
+ }
+
+ let testFile;
+ if (aTestFile.originalFile) {
+ testFile = getTestDirFile(aTestFile.originalFile);
+ testFile.copyToFollowingLinks(testDir, aTestFile.fileName);
+ testFile = getApplyDirFile(aTestFile.relPathDir + aTestFile.fileName);
+ } else {
+ testFile = getApplyDirFile(aTestFile.relPathDir + aTestFile.fileName,
+ true);
+ writeFile(testFile, aTestFile.originalContents);
+ }
+
+ // Skip these tests on Windows since chmod doesn't really set permissions
+ // on Windows.
+ if (!IS_WIN && aTestFile.originalPerms) {
+ testFile.permissions = aTestFile.originalPerms;
+ // Store the actual permissions on the file for reference later after
+ // setting the permissions.
+ if (!aTestFile.comparePerms) {
+ aTestFile.comparePerms = testFile.permissions;
+ }
+ }
+ }
+ });
+
+ // Add the test directory that will be updated for a successful update or left
+ // in the initial state for a failed update.
+ gTestDirs.forEach(function SUT_TD_FE(aTestDir) {
+ let testDir = getApplyDirFile(aTestDir.relPathDir, true);
+ if (!testDir.exists()) {
+ testDir.create(Ci.nsIFile.DIRECTORY_TYPE, PERMS_DIRECTORY);
+ }
+
+ if (aTestDir.files) {
+ aTestDir.files.forEach(function SUT_TD_F_FE(aTestFile) {
+ let testFile = getApplyDirFile(aTestDir.relPathDir + aTestFile, true);
+ if (!testFile.exists()) {
+ testFile.create(Ci.nsIFile.NORMAL_FILE_TYPE, PERMS_FILE);
+ }
+ });
+ }
+
+ if (aTestDir.subDirs) {
+ aTestDir.subDirs.forEach(function SUT_TD_SD_FE(aSubDir) {
+ let testSubDir = getApplyDirFile(aTestDir.relPathDir + aSubDir, true);
+ if (!testSubDir.exists()) {
+ testSubDir.create(Ci.nsIFile.DIRECTORY_TYPE, PERMS_DIRECTORY);
+ }
+
+ if (aTestDir.subDirFiles) {
+ aTestDir.subDirFiles.forEach(function SUT_TD_SDF_FE(aTestFile) {
+ let testFile = getApplyDirFile(aTestDir.relPathDir + aSubDir + aTestFile, true);
+ if (!testFile.exists()) {
+ testFile.create(Ci.nsIFile.NORMAL_FILE_TYPE, PERMS_FILE);
+ }
+ });
+ }
+ });
+ }
+ });
+
+ setupActiveUpdate();
+
+ if (aPostUpdateAsync !== null) {
+ createUpdaterINI(aPostUpdateAsync, aPostUpdateExeRelPathPrefix);
+ }
+
+ setupAppFilesAsync();
+}
+
+/**
+ * Helper function for updater binary tests that creates the update-settings.ini
+ * file.
+ */
+function createUpdateSettingsINI() {
+ let ini = getApplyDirFile(DIR_RESOURCES + FILE_UPDATE_SETTINGS_INI, true);
+ writeFile(ini, UPDATE_SETTINGS_CONTENTS);
+}
+
+/**
+ * Helper function for updater binary tests that creates the updater.ini
+ * file.
+ *
+ * @param aIsExeAsync
+ * True or undefined if the post update process should be async. If
+ * undefined ExeAsync will not be added to the updater.ini file in
+ * order to test the default launch behavior which is async.
+ * @param aExeRelPathPrefix
+ * A string to prefix the ExeRelPath values in the updater.ini.
+ */
+function createUpdaterINI(aIsExeAsync, aExeRelPathPrefix) {
+ let exeArg = "ExeArg=post-update-async\n";
+ let exeAsync = "";
+ if (aIsExeAsync !== undefined) {
+ if (aIsExeAsync) {
+ exeAsync = "ExeAsync=true\n";
+ } else {
+ exeArg = "ExeArg=post-update-sync\n";
+ exeAsync = "ExeAsync=false\n";
+ }
+ }
+
+ if (aExeRelPathPrefix && IS_WIN) {
+ aExeRelPathPrefix = aExeRelPathPrefix.replace("/", "\\");
+ }
+
+ let exeRelPathMac = "ExeRelPath=" + aExeRelPathPrefix + DIR_RESOURCES +
+ gPostUpdateBinFile + "\n";
+ let exeRelPathWin = "ExeRelPath=" + aExeRelPathPrefix + gPostUpdateBinFile + "\n";
+ let updaterIniContents = "[Strings]\n" +
+ "Title=Update Test\n" +
+ "Info=Running update test " + gTestID + "\n\n" +
+ "[PostUpdateMac]\n" +
+ exeRelPathMac +
+ exeArg +
+ exeAsync +
+ "\n" +
+ "[PostUpdateWin]\n" +
+ exeRelPathWin +
+ exeArg +
+ exeAsync;
+ let updaterIni = getApplyDirFile(DIR_RESOURCES + FILE_UPDATER_INI, true);
+ writeFile(updaterIni, updaterIniContents);
+}
+
+/**
+ * Gets the message log path used for assert checks to lessen the length printed
+ * to the log file.
+ *
+ * @param aPath
+ * The path to shorten for the log file.
+ * @return the message including the shortened path for the log file.
+ */
+function getMsgPath(aPath) {
+ return ", path: " + replaceLogPaths(aPath);
+}
+
+/**
+ * Helper function that replaces the common part of paths in the update log's
+ * contents with <test_dir_path> for paths to the the test directory and
+ * <update_dir_path> for paths to the update directory. This is needed since
+ * Assert.equal will truncate what it prints to the xpcshell log file.
+ *
+ * @param aLogContents
+ * The update log file's contents.
+ * @return the log contents with the paths replaced.
+ */
+function replaceLogPaths(aLogContents) {
+ let logContents = aLogContents;
+ // Remove the majority of the path up to the test directory. This is needed
+ // since Assert.equal won't print long strings to the test logs.
+ let testDirPath = do_get_file(gTestID, false).path;
+ if (IS_WIN) {
+ // Replace \\ with \\\\ so the regexp works.
+ testDirPath = testDirPath.replace(/\\/g, "\\\\");
+ }
+ logContents = logContents.replace(new RegExp(testDirPath, "g"),
+ "<test_dir_path>/" + gTestID);
+ let updatesDirPath = getMockUpdRootD().path;
+ if (IS_WIN) {
+ // Replace \\ with \\\\ so the regexp works.
+ updatesDirPath = updatesDirPath.replace(/\\/g, "\\\\");
+ }
+ logContents = logContents.replace(new RegExp(updatesDirPath, "g"),
+ "<update_dir_path>/" + gTestID);
+ if (IS_WIN) {
+ // Replace \ with /
+ logContents = logContents.replace(/\\/g, "/");
+ }
+ return logContents;
+}
+
+/**
+ * Helper function for updater binary tests for verifying the contents of the
+ * update log after a successful update.
+ *
+ * @param aCompareLogFile
+ * The log file to compare the update log with.
+ * @param aStaged
+ * If the update log file is for a staged update.
+ * @param aReplace
+ * If the update log file is for a replace update.
+ * @param aExcludeDistDir
+ * Removes lines containing the distribution directory from the log
+ * file to compare the update log with.
+ */
+function checkUpdateLogContents(aCompareLogFile, aStaged = false,
+ aReplace = false, aExcludeDistDir = false) {
+ if (IS_UNIX && !IS_MACOSX) {
+ // The order that files are returned when enumerating the file system on
+ // Linux is not deterministic so skip checking the logs.
+ return;
+ }
+
+ let updateLog = getUpdateLog(FILE_LAST_UPDATE_LOG);
+ let updateLogContents = readFileBytes(updateLog);
+
+ // The channel-prefs.js is defined in gTestFilesCommon which will always be
+ // located to the end of gTestFiles when it is present.
+ if (gTestFiles.length > 1 &&
+ gTestFiles[gTestFiles.length - 1].fileName == "channel-prefs.js" &&
+ !gTestFiles[gTestFiles.length - 1].originalContents) {
+ updateLogContents = updateLogContents.replace(/.*defaults\/.*/g, "");
+ }
+
+ if (gTestFiles.length > 2 &&
+ gTestFiles[gTestFiles.length - 2].fileName == FILE_UPDATE_SETTINGS_INI &&
+ !gTestFiles[gTestFiles.length - 2].originalContents) {
+ updateLogContents = updateLogContents.replace(/.*update-settings.ini.*/g, "");
+ }
+
+ // Skip the source/destination lines since they contain absolute paths.
+ // These could be changed to relative paths using <test_dir_path> and
+ // <update_dir_path>
+ updateLogContents = updateLogContents.replace(/PATCH DIRECTORY.*/g, "");
+ updateLogContents = updateLogContents.replace(/INSTALLATION DIRECTORY.*/g, "");
+ updateLogContents = updateLogContents.replace(/WORKING DIRECTORY.*/g, "");
+ // Skip lines that log failed attempts to open the callback executable.
+ updateLogContents = updateLogContents.replace(/NS_main: callback app file .*/g, "");
+
+ if (IS_MACOSX) {
+ // Skip lines that log moving the distribution directory for Mac v2 signing.
+ updateLogContents = updateLogContents.replace(/Moving old [^\n]*\nrename_file: .*/g, "");
+ updateLogContents = updateLogContents.replace(/New distribution directory .*/g, "");
+ }
+
+ if (IS_WIN) {
+ // The FindFile results when enumerating the filesystem on Windows is not
+ // determistic so the results matching the following need to be fixed.
+ let re = new RegExp("([^\n]* 7\/7text1[^\n]*)\n" +
+ "([^\n]* 7\/7text0[^\n]*)\n", "g");
+ updateLogContents = updateLogContents.replace(re, "$2\n$1\n");
+ }
+
+ if (aReplace) {
+ // Remove the lines which contain absolute paths
+ updateLogContents = updateLogContents.replace(/^Begin moving.*$/mg, "");
+ updateLogContents = updateLogContents.replace(/^ensure_remove: failed to remove file: .*$/mg, "");
+ updateLogContents = updateLogContents.replace(/^ensure_remove_recursive: unable to remove directory: .*$/mg, "");
+ updateLogContents = updateLogContents.replace(/^Removing tmpDir failed, err: -1$/mg, "");
+ updateLogContents = updateLogContents.replace(/^remove_recursive_on_reboot: .*$/mg, "");
+ }
+
+ // Remove carriage returns.
+ updateLogContents = updateLogContents.replace(/\r/g, "");
+ // Replace error codes since they are different on each platform.
+ updateLogContents = updateLogContents.replace(/, err:.*\n/g, "\n");
+ // Replace to make the log parsing happy.
+ updateLogContents = updateLogContents.replace(/non-fatal error /g, "");
+ // Remove consecutive newlines
+ updateLogContents = updateLogContents.replace(/\n+/g, "\n");
+ // Remove leading and trailing newlines
+ updateLogContents = updateLogContents.replace(/^\n|\n$/g, "");
+ // Replace the log paths with <test_dir_path> and <update_dir_path>
+ updateLogContents = replaceLogPaths(updateLogContents);
+
+ let compareLogContents = "";
+ if (aCompareLogFile) {
+ compareLogContents = readFileBytes(getTestDirFile(aCompareLogFile));
+ }
+
+ if (aStaged) {
+ compareLogContents = PERFORMING_STAGED_UPDATE + "\n" + compareLogContents;
+ }
+
+ // The channel-prefs.js is defined in gTestFilesCommon which will always be
+ // located to the end of gTestFiles.
+ if (gTestFiles.length > 1 &&
+ gTestFiles[gTestFiles.length - 1].fileName == "channel-prefs.js" &&
+ !gTestFiles[gTestFiles.length - 1].originalContents) {
+ compareLogContents = compareLogContents.replace(/.*defaults\/.*/g, "");
+ }
+
+ if (gTestFiles.length > 2 &&
+ gTestFiles[gTestFiles.length - 2].fileName == FILE_UPDATE_SETTINGS_INI &&
+ !gTestFiles[gTestFiles.length - 2].originalContents) {
+ compareLogContents = compareLogContents.replace(/.*update-settings.ini.*/g, "");
+ }
+
+ if (aExcludeDistDir) {
+ compareLogContents = compareLogContents.replace(/.*distribution\/.*/g, "");
+ }
+
+ // Remove leading and trailing newlines
+ compareLogContents = compareLogContents.replace(/\n+/g, "\n");
+ // Remove leading and trailing newlines
+ compareLogContents = compareLogContents.replace(/^\n|\n$/g, "");
+
+ // Don't write the contents of the file to the log to reduce log spam
+ // unless there is a failure.
+ if (compareLogContents == updateLogContents) {
+ Assert.ok(true, "the update log contents" + MSG_SHOULD_EQUAL);
+ } else {
+ logTestInfo("the update log contents are not correct");
+ logUpdateLog(FILE_LAST_UPDATE_LOG);
+ let aryLog = updateLogContents.split("\n");
+ let aryCompare = compareLogContents.split("\n");
+ // Pushing an empty string to both arrays makes it so either array's length
+ // can be used in the for loop below without going out of bounds.
+ aryLog.push("");
+ aryCompare.push("");
+ // xpcshell tests won't display the entire contents so log the first
+ // incorrect line.
+ for (let i = 0; i < aryLog.length; ++i) {
+ if (aryLog[i] != aryCompare[i]) {
+ logTestInfo("the first incorrect line in the update log is: " +
+ aryLog[i]);
+ Assert.equal(aryLog[i], aryCompare[i],
+ "the update log contents" + MSG_SHOULD_EQUAL);
+ }
+ }
+ // This should never happen!
+ do_throw("Unable to find incorrect update log contents!");
+ }
+}
+
+/**
+ * Helper function to check if the update log contains a string.
+ *
+ * @param aCheckString
+ * The string to check if the update log contains.
+ */
+function checkUpdateLogContains(aCheckString) {
+ let updateLog = getUpdateLog(FILE_LAST_UPDATE_LOG);
+ let updateLogContents = readFileBytes(updateLog).replace(/\r\n/g, "\n");
+ updateLogContents = replaceLogPaths(updateLogContents);
+ Assert.notEqual(updateLogContents.indexOf(aCheckString), -1,
+ "the update log contents should contain value: " +
+ aCheckString);
+}
+
+/**
+ * Helper function for updater binary tests for verifying the state of files and
+ * directories after a successful update.
+ *
+ * @param aGetFileFunc
+ * The function used to get the files in the directory to be checked.
+ * @param aStageDirExists
+ * If true the staging directory will be tested for existence and if
+ * false the staging directory will be tested for non-existence.
+ * @param aToBeDeletedDirExists
+ * On Windows, if true the tobedeleted directory will be tested for
+ * existence and if false the tobedeleted directory will be tested for
+ * non-existence. On all othere platforms it will be tested for
+ * non-existence.
+ */
+function checkFilesAfterUpdateSuccess(aGetFileFunc, aStageDirExists = false,
+ aToBeDeletedDirExists = false) {
+ debugDump("testing contents of files after a successful update");
+ gTestFiles.forEach(function CFAUS_TF_FE(aTestFile) {
+ let testFile = aGetFileFunc(aTestFile.relPathDir + aTestFile.fileName, true);
+ debugDump("testing file: " + testFile.path);
+ if (aTestFile.compareFile || aTestFile.compareContents) {
+ Assert.ok(testFile.exists(),
+ MSG_SHOULD_EXIST + getMsgPath(testFile.path));
+
+ // Skip these tests on Windows since chmod doesn't really set permissions
+ // on Windows.
+ if (!IS_WIN && aTestFile.comparePerms) {
+ // Check if the permssions as set in the complete mar file are correct.
+ Assert.equal(testFile.permissions & 0xfff,
+ aTestFile.comparePerms & 0xfff,
+ "the file permissions" + MSG_SHOULD_EQUAL);
+ }
+
+ let fileContents1 = readFileBytes(testFile);
+ let fileContents2 = aTestFile.compareFile ?
+ readFileBytes(getTestDirFile(aTestFile.compareFile)) :
+ aTestFile.compareContents;
+ // Don't write the contents of the file to the log to reduce log spam
+ // unless there is a failure.
+ if (fileContents1 == fileContents2) {
+ Assert.ok(true, "the file contents" + MSG_SHOULD_EQUAL);
+ } else {
+ Assert.equal(fileContents1, fileContents2,
+ "the file contents" + MSG_SHOULD_EQUAL);
+ }
+ } else {
+ Assert.ok(!testFile.exists(),
+ MSG_SHOULD_NOT_EXIST + getMsgPath(testFile.path));
+ }
+ });
+
+ debugDump("testing operations specified in removed-files were performed " +
+ "after a successful update");
+ gTestDirs.forEach(function CFAUS_TD_FE(aTestDir) {
+ let testDir = aGetFileFunc(aTestDir.relPathDir, true);
+ debugDump("testing directory: " + testDir.path);
+ if (aTestDir.dirRemoved) {
+ Assert.ok(!testDir.exists(),
+ MSG_SHOULD_NOT_EXIST + getMsgPath(testDir.path));
+ } else {
+ Assert.ok(testDir.exists(),
+ MSG_SHOULD_EXIST + getMsgPath(testDir.path));
+
+ if (aTestDir.files) {
+ aTestDir.files.forEach(function CFAUS_TD_F_FE(aTestFile) {
+ let testFile = aGetFileFunc(aTestDir.relPathDir + aTestFile, true);
+ if (aTestDir.filesRemoved) {
+ Assert.ok(!testFile.exists(),
+ MSG_SHOULD_NOT_EXIST + getMsgPath(testFile.path));
+ } else {
+ Assert.ok(testFile.exists(),
+ MSG_SHOULD_EXIST + getMsgPath(testFile.path));
+ }
+ });
+ }
+
+ if (aTestDir.subDirs) {
+ aTestDir.subDirs.forEach(function CFAUS_TD_SD_FE(aSubDir) {
+ let testSubDir = aGetFileFunc(aTestDir.relPathDir + aSubDir, true);
+ Assert.ok(testSubDir.exists(),
+ MSG_SHOULD_EXIST + getMsgPath(testSubDir.path));
+ if (aTestDir.subDirFiles) {
+ aTestDir.subDirFiles.forEach(function CFAUS_TD_SDF_FE(aTestFile) {
+ let testFile = aGetFileFunc(aTestDir.relPathDir +
+ aSubDir + aTestFile, true);
+ Assert.ok(testFile.exists(),
+ MSG_SHOULD_EXIST + getMsgPath(testFile.path));
+ });
+ }
+ });
+ }
+ }
+ });
+
+ checkFilesAfterUpdateCommon(aGetFileFunc, aStageDirExists,
+ aToBeDeletedDirExists);
+}
+
+/**
+ * Helper function for updater binary tests for verifying the state of files and
+ * directories after a failed update.
+ *
+ * @param aGetFileFunc
+ * The function used to get the files in the directory to be checked.
+ * @param aStageDirExists
+ * If true the staging directory will be tested for existence and if
+ * false the staging directory will be tested for non-existence.
+ * @param aToBeDeletedDirExists
+ * On Windows, if true the tobedeleted directory will be tested for
+ * existence and if false the tobedeleted directory will be tested for
+ * non-existence. On all othere platforms it will be tested for
+ * non-existence.
+ */
+function checkFilesAfterUpdateFailure(aGetFileFunc, aStageDirExists = false,
+ aToBeDeletedDirExists = false) {
+ debugDump("testing contents of files after a failed update");
+ gTestFiles.forEach(function CFAUF_TF_FE(aTestFile) {
+ let testFile = aGetFileFunc(aTestFile.relPathDir + aTestFile.fileName, true);
+ debugDump("testing file: " + testFile.path);
+ if (aTestFile.compareFile || aTestFile.compareContents) {
+ Assert.ok(testFile.exists(),
+ MSG_SHOULD_EXIST + getMsgPath(testFile.path));
+
+ // Skip these tests on Windows since chmod doesn't really set permissions
+ // on Windows.
+ if (!IS_WIN && aTestFile.comparePerms) {
+ // Check the original permssions are retained on the file.
+ Assert.equal(testFile.permissions & 0xfff,
+ aTestFile.comparePerms & 0xfff,
+ "the file permissions" + MSG_SHOULD_EQUAL);
+ }
+
+ let fileContents1 = readFileBytes(testFile);
+ let fileContents2 = aTestFile.compareFile ?
+ readFileBytes(getTestDirFile(aTestFile.compareFile)) :
+ aTestFile.compareContents;
+ // Don't write the contents of the file to the log to reduce log spam
+ // unless there is a failure.
+ if (fileContents1 == fileContents2) {
+ Assert.ok(true, "the file contents" + MSG_SHOULD_EQUAL);
+ } else {
+ Assert.equal(fileContents1, fileContents2,
+ "the file contents" + MSG_SHOULD_EQUAL);
+ }
+ } else {
+ Assert.ok(!testFile.exists(),
+ MSG_SHOULD_NOT_EXIST + getMsgPath(testFile.path));
+ }
+ });
+
+ debugDump("testing operations specified in removed-files were not " +
+ "performed after a failed update");
+ gTestDirs.forEach(function CFAUF_TD_FE(aTestDir) {
+ let testDir = aGetFileFunc(aTestDir.relPathDir, true);
+ Assert.ok(testDir.exists(),
+ MSG_SHOULD_EXIST + getMsgPath(testDir.path));
+
+ if (aTestDir.files) {
+ aTestDir.files.forEach(function CFAUS_TD_F_FE(aTestFile) {
+ let testFile = aGetFileFunc(aTestDir.relPathDir + aTestFile, true);
+ Assert.ok(testFile.exists(),
+ MSG_SHOULD_EXIST + getMsgPath(testFile.path));
+ });
+ }
+
+ if (aTestDir.subDirs) {
+ aTestDir.subDirs.forEach(function CFAUS_TD_SD_FE(aSubDir) {
+ let testSubDir = aGetFileFunc(aTestDir.relPathDir + aSubDir, true);
+ Assert.ok(testSubDir.exists(),
+ MSG_SHOULD_EXIST + getMsgPath(testSubDir.path));
+ if (aTestDir.subDirFiles) {
+ aTestDir.subDirFiles.forEach(function CFAUS_TD_SDF_FE(aTestFile) {
+ let testFile = aGetFileFunc(aTestDir.relPathDir +
+ aSubDir + aTestFile, true);
+ Assert.ok(testFile.exists(),
+ MSG_SHOULD_EXIST + getMsgPath(testFile.path));
+ });
+ }
+ });
+ }
+ });
+
+ checkFilesAfterUpdateCommon(aGetFileFunc, aStageDirExists,
+ aToBeDeletedDirExists);
+}
+
+/**
+ * Helper function for updater binary tests for verifying the state of common
+ * files and directories after a successful or failed update.
+ *
+ * @param aGetFileFunc
+ * the function used to get the files in the directory to be checked.
+ * @param aStageDirExists
+ * If true the staging directory will be tested for existence and if
+ * false the staging directory will be tested for non-existence.
+ * @param aToBeDeletedDirExists
+ * On Windows, if true the tobedeleted directory will be tested for
+ * existence and if false the tobedeleted directory will be tested for
+ * non-existence. On all othere platforms it will be tested for
+ * non-existence.
+ */
+function checkFilesAfterUpdateCommon(aGetFileFunc, aStageDirExists,
+ aToBeDeletedDirExists) {
+ debugDump("testing extra directories");
+ let stageDir = getStageDirFile(null, true);
+ if (aStageDirExists) {
+ Assert.ok(stageDir.exists(),
+ MSG_SHOULD_EXIST + getMsgPath(stageDir.path));
+ } else {
+ Assert.ok(!stageDir.exists(),
+ MSG_SHOULD_NOT_EXIST + getMsgPath(stageDir.path));
+ }
+
+ let toBeDeletedDirExists = IS_WIN ? aToBeDeletedDirExists : false;
+ let toBeDeletedDir = getApplyDirFile(DIR_TOBEDELETED, true);
+ if (toBeDeletedDirExists) {
+ Assert.ok(toBeDeletedDir.exists(),
+ MSG_SHOULD_EXIST + getMsgPath(toBeDeletedDir.path));
+ } else {
+ Assert.ok(!toBeDeletedDir.exists(),
+ MSG_SHOULD_NOT_EXIST + getMsgPath(toBeDeletedDir.path));
+ }
+
+ let updatingDir = getApplyDirFile("updating", true);
+ Assert.ok(!updatingDir.exists(),
+ MSG_SHOULD_NOT_EXIST + getMsgPath(updatingDir.path));
+
+ if (stageDir.exists()) {
+ updatingDir = stageDir.clone();
+ updatingDir.append("updating");
+ Assert.ok(!updatingDir.exists(),
+ MSG_SHOULD_NOT_EXIST + getMsgPath(updatingDir.path));
+ }
+
+ debugDump("testing backup files should not be left behind in the " +
+ "application directory");
+ let applyToDir = getApplyDirFile(null, true);
+ checkFilesInDirRecursive(applyToDir, checkForBackupFiles);
+
+ if (stageDir.exists()) {
+ debugDump("testing backup files should not be left behind in the " +
+ "staging directory");
+ applyToDir = getApplyDirFile(null, true);
+ checkFilesInDirRecursive(stageDir, checkForBackupFiles);
+ }
+}
+
+/**
+ * Helper function for updater binary tests for verifying the contents of the
+ * updater callback application log which should contain the arguments passed to
+ * the callback application.
+ */
+function checkCallbackLog() {
+ let appLaunchLog = getApplyDirFile(DIR_RESOURCES + gCallbackArgs[1], true);
+ if (!appLaunchLog.exists()) {
+ // Uses do_timeout instead of do_execute_soon to lessen log spew.
+ do_timeout(FILE_IN_USE_TIMEOUT_MS, checkCallbackLog);
+ return;
+ }
+
+ let expectedLogContents = gCallbackArgs.join("\n") + "\n";
+ let logContents = readFile(appLaunchLog);
+ // It is possible for the log file contents check to occur before the log file
+ // contents are completely written so wait until the contents are the expected
+ // value. If the contents are never the expected value then the test will
+ // fail by timing out after gTimeoutRuns is greater than MAX_TIMEOUT_RUNS or
+ // the test harness times out the test.
+ if (logContents != expectedLogContents) {
+ gTimeoutRuns++;
+ if (gTimeoutRuns > MAX_TIMEOUT_RUNS) {
+ logTestInfo("callback log contents are not correct");
+ // This file doesn't contain full paths so there is no need to call
+ // replaceLogPaths.
+ let aryLog = logContents.split("\n");
+ let aryCompare = expectedLogContents.split("\n");
+ // Pushing an empty string to both arrays makes it so either array's length
+ // can be used in the for loop below without going out of bounds.
+ aryLog.push("");
+ aryCompare.push("");
+ // xpcshell tests won't display the entire contents so log the incorrect
+ // line.
+ for (let i = 0; i < aryLog.length; ++i) {
+ if (aryLog[i] != aryCompare[i]) {
+ logTestInfo("the first incorrect line in the callback log is: " +
+ aryLog[i]);
+ Assert.equal(aryLog[i], aryCompare[i],
+ "the callback log contents" + MSG_SHOULD_EQUAL);
+ }
+ }
+ // This should never happen!
+ do_throw("Unable to find incorrect callback log contents!");
+ }
+ // Uses do_timeout instead of do_execute_soon to lessen log spew.
+ do_timeout(FILE_IN_USE_TIMEOUT_MS, checkCallbackLog);
+ return;
+ }
+ Assert.ok(true, "the callback log contents" + MSG_SHOULD_EQUAL);
+
+ waitForFilesInUse();
+}
+
+/**
+ * Helper function for updater binary tests for getting the log and running
+ * files created by the test helper binary file when called with the post-update
+ * command line argument.
+ *
+ * @param aSuffix
+ * The string to append to the post update test helper binary path.
+ */
+function getPostUpdateFile(aSuffix) {
+ return getApplyDirFile(DIR_RESOURCES + gPostUpdateBinFile + aSuffix, true);
+}
+
+/**
+ * Checks the contents of the updater post update binary log. When completed
+ * checkPostUpdateAppLogFinished will be called.
+ */
+function checkPostUpdateAppLog() {
+ // Only Mac OS X and Windows support post update.
+ if (IS_MACOSX || IS_WIN) {
+ gTimeoutRuns++;
+ let postUpdateLog = getPostUpdateFile(".log");
+ if (!postUpdateLog.exists()) {
+ debugDump("postUpdateLog does not exist. Path: " + postUpdateLog.path);
+ if (gTimeoutRuns > MAX_TIMEOUT_RUNS) {
+ do_throw("Exceeded MAX_TIMEOUT_RUNS while waiting for the post update " +
+ "process to create the post update log. Path: " +
+ postUpdateLog.path);
+ }
+ do_execute_soon(checkPostUpdateAppLog);
+ return;
+ }
+
+ let logContents = readFile(postUpdateLog);
+ // It is possible for the log file contents check to occur before the log file
+ // contents are completely written so wait until the contents are the expected
+ // value. If the contents are never the expected value then the test will
+ // fail by timing out after gTimeoutRuns is greater than MAX_TIMEOUT_RUNS or
+ // the test harness times out the test.
+ if (logContents != "post-update\n") {
+ if (gTimeoutRuns > MAX_TIMEOUT_RUNS) {
+ do_throw("Exceeded MAX_TIMEOUT_RUNS while waiting for the post update " +
+ "process to create the expected contents in the post update log. Path: " +
+ postUpdateLog.path);
+ }
+ do_execute_soon(checkPostUpdateAppLog);
+ return;
+ }
+ Assert.ok(true, "the post update log contents" + MSG_SHOULD_EQUAL);
+ }
+
+ do_execute_soon(checkPostUpdateAppLogFinished);
+}
+
+/**
+ * Helper function to check if a file is in use on Windows by making a copy of
+ * a file and attempting to delete the original file. If the deletion is
+ * successful the copy of the original file is renamed to the original file's
+ * name and if the deletion is not successful the copy of the original file is
+ * deleted.
+ *
+ * @param aFile
+ * An nsIFile for the file to be checked if it is in use.
+ * @return true if the file can't be deleted and false otherwise.
+ */
+function isFileInUse(aFile) {
+ if (!IS_WIN) {
+ do_throw("Windows only function called by a different platform!");
+ }
+
+ if (!aFile.exists()) {
+ debugDump("file does not exist, path: " + aFile.path);
+ return false;
+ }
+
+ let fileBak = aFile.parent;
+ fileBak.append(aFile.leafName + ".bak");
+ try {
+ if (fileBak.exists()) {
+ fileBak.remove(false);
+ }
+ aFile.copyTo(aFile.parent, fileBak.leafName);
+ aFile.remove(false);
+ fileBak.moveTo(aFile.parent, aFile.leafName);
+ debugDump("file is not in use, path: " + aFile.path);
+ return false;
+ } catch (e) {
+ debugDump("file in use, path: " + aFile.path + ", exception: " + e);
+ try {
+ if (fileBak.exists()) {
+ fileBak.remove(false);
+ }
+ } catch (ex) {
+ logTestInfo("unable to remove backup file, path: " +
+ fileBak.path + ", exception: " + ex);
+ }
+ }
+ return true;
+}
+
+/**
+ * Waits until files that are in use that break tests are no longer in use and
+ * then calls doTestFinish to end the test.
+ */
+function waitForFilesInUse() {
+ if (IS_WIN) {
+ let fileNames = [FILE_APP_BIN, FILE_UPDATER_BIN,
+ FILE_MAINTENANCE_SERVICE_INSTALLER_BIN];
+ for (let i = 0; i < fileNames.length; ++i) {
+ let file = getApplyDirFile(fileNames[i], true);
+ if (isFileInUse(file)) {
+ do_timeout(FILE_IN_USE_TIMEOUT_MS, waitForFilesInUse);
+ return;
+ }
+ }
+ }
+
+ debugDump("calling doTestFinish");
+ doTestFinish();
+}
+
+/**
+ * Helper function for updater binary tests for verifying there are no update
+ * backup files left behind after an update.
+ *
+ * @param aFile
+ * An nsIFile to check if it has moz-backup for its extension.
+ */
+function checkForBackupFiles(aFile) {
+ Assert.notEqual(getFileExtension(aFile), "moz-backup",
+ "the file's extension should not equal moz-backup" +
+ getMsgPath(aFile.path));
+}
+
+/**
+ * Helper function for updater binary tests for recursively enumerating a
+ * directory and calling a callback function with the file as a parameter for
+ * each file found.
+ *
+ * @param aDir
+ * A nsIFile for the directory to be deleted
+ * @param aCallback
+ * A callback function that will be called with the file as a
+ * parameter for each file found.
+ */
+function checkFilesInDirRecursive(aDir, aCallback) {
+ if (!aDir.exists()) {
+ do_throw("Directory must exist!");
+ }
+
+ let dirEntries = aDir.directoryEntries;
+ while (dirEntries.hasMoreElements()) {
+ let entry = dirEntries.getNext().QueryInterface(Ci.nsIFile);
+
+ if (entry.exists()) {
+ if (entry.isDirectory()) {
+ checkFilesInDirRecursive(entry, aCallback);
+ } else {
+ aCallback(entry);
+ }
+ }
+ }
+}
+
+
+/**
+ * Helper function to override the update prompt component to verify whether it
+ * is called or not.
+ *
+ * @param aCallback
+ * The callback to call if the update prompt component is called.
+ */
+function overrideUpdatePrompt(aCallback) {
+ Cu.import("resource://testing-common/MockRegistrar.jsm");
+ MockRegistrar.register("@mozilla.org/updates/update-prompt;1", UpdatePrompt, [aCallback]);
+}
+
+function UpdatePrompt(aCallback) {
+ this._callback = aCallback;
+
+ let fns = ["checkForUpdates", "showUpdateAvailable", "showUpdateDownloaded",
+ "showUpdateError", "showUpdateHistory", "showUpdateInstalled"];
+
+ fns.forEach(function UP_fns(aPromptFn) {
+ UpdatePrompt.prototype[aPromptFn] = function() {
+ if (!this._callback) {
+ return;
+ }
+
+ let callback = this._callback[aPromptFn];
+ if (!callback) {
+ return;
+ }
+
+ callback.apply(this._callback,
+ Array.prototype.slice.call(arguments));
+ };
+ });
+}
+
+UpdatePrompt.prototype = {
+ flags: Ci.nsIClassInfo.SINGLETON,
+ getScriptableHelper: () => null,
+ getInterfaces: function(aCount) {
+ let interfaces = [Ci.nsISupports, Ci.nsIUpdatePrompt];
+ aCount.value = interfaces.length;
+ return interfaces;
+ },
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIClassInfo, Ci.nsIUpdatePrompt])
+};
+
+/* Update check listener */
+const updateCheckListener = {
+ onProgress: function UCL_onProgress(aRequest, aPosition, aTotalSize) {
+ },
+
+ onCheckComplete: function UCL_onCheckComplete(aRequest, aUpdates, aUpdateCount) {
+ gRequestURL = aRequest.channel.originalURI.spec;
+ gUpdateCount = aUpdateCount;
+ gUpdates = aUpdates;
+ debugDump("url = " + gRequestURL + ", " +
+ "request.status = " + aRequest.status + ", " +
+ "updateCount = " + aUpdateCount);
+ // Use a timeout to allow the XHR to complete
+ do_execute_soon(gCheckFunc);
+ },
+
+ onError: function UCL_onError(aRequest, aUpdate) {
+ gRequestURL = aRequest.channel.originalURI.spec;
+ gStatusCode = aRequest.status;
+ if (gStatusCode == 0) {
+ gStatusCode = aRequest.channel.QueryInterface(Ci.nsIRequest).status;
+ }
+ gStatusText = aUpdate.statusText ? aUpdate.statusText : null;
+ debugDump("url = " + gRequestURL + ", " +
+ "request.status = " + gStatusCode + ", " +
+ "update.statusText = " + gStatusText);
+ // Use a timeout to allow the XHR to complete
+ do_execute_soon(gCheckFunc.bind(null, aRequest, aUpdate));
+ },
+
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIUpdateCheckListener])
+};
+
+/* Update download listener - nsIRequestObserver */
+const downloadListener = {
+ onStartRequest: function DL_onStartRequest(aRequest, aContext) {
+ },
+
+ onProgress: function DL_onProgress(aRequest, aContext, aProgress, aMaxProgress) {
+ },
+
+ onStatus: function DL_onStatus(aRequest, aContext, aStatus, aStatusText) {
+ },
+
+ onStopRequest: function DL_onStopRequest(aRequest, aContext, aStatus) {
+ gStatusResult = aStatus;
+ // Use a timeout to allow the request to complete
+ do_execute_soon(gCheckFunc);
+ },
+
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIRequestObserver,
+ Ci.nsIProgressEventSink])
+};
+
+/**
+ * Helper for starting the http server used by the tests
+ */
+function start_httpserver() {
+ let dir = getTestDirFile();
+ debugDump("http server directory path: " + dir.path);
+
+ if (!dir.isDirectory()) {
+ do_throw("A file instead of a directory was specified for HttpServer " +
+ "registerDirectory! Path: " + dir.path);
+ }
+
+ let { HttpServer } = Cu.import("resource://testing-common/httpd.js", {});
+ gTestserver = new HttpServer();
+ gTestserver.registerDirectory("/", dir);
+ gTestserver.registerPathHandler("/" + gHTTPHandlerPath, pathHandler);
+ gTestserver.start(-1);
+ let testserverPort = gTestserver.identity.primaryPort;
+ gURLData = URL_HOST + ":" + testserverPort + "/";
+ debugDump("http server port = " + testserverPort);
+}
+
+/**
+ * Custom path handler for the http server
+ *
+ * @param aMetadata
+ * The http metadata for the request.
+ * @param aResponse
+ * The http response for the request.
+ */
+function pathHandler(aMetadata, aResponse) {
+ aResponse.setHeader("Content-Type", "text/xml", false);
+ aResponse.setStatusLine(aMetadata.httpVersion, gResponseStatusCode, "OK");
+ aResponse.bodyOutputStream.write(gResponseBody, gResponseBody.length);
+}
+
+/**
+ * Helper for stopping the http server used by the tests
+ *
+ * @param aCallback
+ * The callback to call after stopping the http server.
+ */
+function stop_httpserver(aCallback) {
+ Assert.ok(!!aCallback, "the aCallback parameter should be defined");
+ gTestserver.stop(aCallback);
+}
+
+/**
+ * Creates an nsIXULAppInfo
+ *
+ * @param aID
+ * The ID of the test application
+ * @param aName
+ * A name for the test application
+ * @param aVersion
+ * The version of the application
+ * @param aPlatformVersion
+ * The gecko version of the application
+ */
+function createAppInfo(aID, aName, aVersion, aPlatformVersion) {
+ const XULAPPINFO_CONTRACTID = "@mozilla.org/xre/app-info;1";
+ const XULAPPINFO_CID = Components.ID("{c763b610-9d49-455a-bbd2-ede71682a1ac}");
+ let ifaces = [Ci.nsIXULAppInfo, Ci.nsIXULRuntime];
+ if (IS_WIN) {
+ ifaces.push(Ci.nsIWinAppHelper);
+ }
+ const XULAppInfo = {
+ vendor: APP_INFO_VENDOR,
+ name: aName,
+ ID: aID,
+ version: aVersion,
+ appBuildID: "2007010101",
+ platformVersion: aPlatformVersion,
+ platformBuildID: "2007010101",
+ inSafeMode: false,
+ logConsoleErrors: true,
+ OS: "XPCShell",
+ XPCOMABI: "noarch-spidermonkey",
+
+ QueryInterface: XPCOMUtils.generateQI(ifaces)
+ };
+
+ const XULAppInfoFactory = {
+ createInstance: function(aOuter, aIID) {
+ if (aOuter == null) {
+ return XULAppInfo.QueryInterface(aIID);
+ }
+ throw Cr.NS_ERROR_NO_AGGREGATION;
+ }
+ };
+
+ let registrar = Cm.QueryInterface(Ci.nsIComponentRegistrar);
+ registrar.registerFactory(XULAPPINFO_CID, "XULAppInfo",
+ XULAPPINFO_CONTRACTID, XULAppInfoFactory);
+}
+
+/**
+ * Returns the platform specific arguments used by nsIProcess when launching
+ * the application.
+ *
+ * @param aExtraArgs (optional)
+ * An array of extra arguments to append to the default arguments.
+ * @return an array of arguments to be passed to nsIProcess.
+ *
+ * Note: a shell is necessary to pipe the application's console output which
+ * would otherwise pollute the xpcshell log.
+ *
+ * Command line arguments used when launching the application:
+ * -no-remote prevents shell integration from being affected by an existing
+ * application process.
+ * -test-process-updates makes the application exit after being relaunched by
+ * the updater.
+ * the platform specific string defined by PIPE_TO_NULL to output both stdout
+ * and stderr to null. This is needed to prevent output from the application
+ * from ending up in the xpchsell log.
+ */
+function getProcessArgs(aExtraArgs) {
+ if (!aExtraArgs) {
+ aExtraArgs = [];
+ }
+
+ let appBinPath = getApplyDirFile(DIR_MACOS + FILE_APP_BIN, false).path;
+ if (/ /.test(appBinPath)) {
+ appBinPath = '"' + appBinPath + '"';
+ }
+
+ let args;
+ if (IS_UNIX) {
+ let launchScript = getLaunchScript();
+ // Precreate the script with executable permissions
+ launchScript.create(Ci.nsILocalFile.NORMAL_FILE_TYPE, PERMS_DIRECTORY);
+
+ let scriptContents = "#! /bin/sh\n";
+ scriptContents += appBinPath + " -no-remote -test-process-updates " +
+ aExtraArgs.join(" ") + " " + PIPE_TO_NULL;
+ writeFile(launchScript, scriptContents);
+ debugDump("created " + launchScript.path + " containing:\n" +
+ scriptContents);
+ args = [launchScript.path];
+ } else {
+ args = ["/D", "/Q", "/C", appBinPath, "-no-remote", "-test-process-updates"].
+ concat(aExtraArgs).concat([PIPE_TO_NULL]);
+ }
+ return args;
+}
+
+/**
+ * Gets a file path for the application to dump its arguments into. This is used
+ * to verify that a callback application is launched.
+ *
+ * @return the file for the application to dump its arguments into.
+ */
+function getAppArgsLogPath() {
+ let appArgsLog = do_get_file("/" + gTestID + "_app_args_log", true);
+ if (appArgsLog.exists()) {
+ appArgsLog.remove(false);
+ }
+ let appArgsLogPath = appArgsLog.path;
+ if (/ /.test(appArgsLogPath)) {
+ appArgsLogPath = '"' + appArgsLogPath + '"';
+ }
+ return appArgsLogPath;
+}
+
+/**
+ * Gets the nsIFile reference for the shell script to launch the application. If
+ * the file exists it will be removed by this function.
+ *
+ * @return the nsIFile for the shell script to launch the application.
+ */
+function getLaunchScript() {
+ let launchScript = do_get_file("/" + gTestID + "_launch.sh", true);
+ if (launchScript.exists()) {
+ launchScript.remove(false);
+ }
+ return launchScript;
+}
+
+/**
+ * Makes GreD, XREExeF, and UpdRootD point to unique file system locations so
+ * xpcshell tests can run in parallel and to keep the environment clean.
+ */
+function adjustGeneralPaths() {
+ let dirProvider = {
+ getFile: function AGP_DP_getFile(aProp, aPersistent) {
+ aPersistent.value = true;
+ switch (aProp) {
+ case NS_GRE_DIR:
+ if (gUseTestAppDir) {
+ return getApplyDirFile(DIR_RESOURCES, true);
+ }
+ break;
+ case NS_GRE_BIN_DIR:
+ if (gUseTestAppDir) {
+ return getApplyDirFile(DIR_MACOS, true);
+ }
+ break;
+ case XRE_EXECUTABLE_FILE:
+ if (gUseTestAppDir) {
+ return getApplyDirFile(DIR_MACOS + FILE_APP_BIN, true);
+ }
+ break;
+ case XRE_UPDATE_ROOT_DIR:
+ return getMockUpdRootD();
+ }
+ return null;
+ },
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIDirectoryServiceProvider])
+ };
+ let ds = Services.dirsvc.QueryInterface(Ci.nsIDirectoryService);
+ ds.QueryInterface(Ci.nsIProperties).undefine(NS_GRE_DIR);
+ ds.QueryInterface(Ci.nsIProperties).undefine(NS_GRE_BIN_DIR);
+ ds.QueryInterface(Ci.nsIProperties).undefine(XRE_EXECUTABLE_FILE);
+ ds.registerProvider(dirProvider);
+ do_register_cleanup(function AGP_cleanup() {
+ debugDump("start - unregistering directory provider");
+
+ if (gAppTimer) {
+ debugDump("start - cancel app timer");
+ gAppTimer.cancel();
+ gAppTimer = null;
+ debugDump("finish - cancel app timer");
+ }
+
+ if (gProcess && gProcess.isRunning) {
+ debugDump("start - kill process");
+ try {
+ gProcess.kill();
+ } catch (e) {
+ debugDump("kill process failed. Exception: " + e);
+ }
+ gProcess = null;
+ debugDump("finish - kill process");
+ }
+
+ if (gHandle) {
+ try {
+ debugDump("start - closing handle");
+ let kernel32 = ctypes.open("kernel32");
+ let CloseHandle = kernel32.declare("CloseHandle", ctypes.default_abi,
+ ctypes.bool, /* return*/
+ ctypes.voidptr_t /* handle*/);
+ if (!CloseHandle(gHandle)) {
+ debugDump("call to CloseHandle failed");
+ }
+ kernel32.close();
+ gHandle = null;
+ debugDump("finish - closing handle");
+ } catch (e) {
+ debugDump("call to CloseHandle failed. Exception: " + e);
+ }
+ }
+
+ // Call end_test first before the directory provider is unregistered
+ if (typeof end_test == typeof Function) {
+ debugDump("calling end_test");
+ end_test();
+ }
+
+ ds.unregisterProvider(dirProvider);
+ cleanupTestCommon();
+
+ debugDump("finish - unregistering directory provider");
+ });
+}
+
+/**
+ * The timer callback to kill the process if it takes too long.
+ */
+const gAppTimerCallback = {
+ notify: function TC_notify(aTimer) {
+ gAppTimer = null;
+ if (gProcess.isRunning) {
+ logTestInfo("attempting to kill process");
+ gProcess.kill();
+ }
+ Assert.ok(false, "launch application timer expired");
+ },
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsITimerCallback])
+};
+
+/**
+ * Launches an application to apply an update.
+ */
+function runUpdateUsingApp(aExpectedStatus) {
+ /**
+ * The observer for the call to nsIProcess:runAsync. When completed
+ * runUpdateFinished will be called.
+ */
+ const processObserver = {
+ observe: function PO_observe(aSubject, aTopic, aData) {
+ debugDump("topic: " + aTopic + ", process exitValue: " +
+ gProcess.exitValue);
+ resetEnvironment();
+ if (gAppTimer) {
+ gAppTimer.cancel();
+ gAppTimer = null;
+ }
+ Assert.equal(gProcess.exitValue, 0,
+ "the application process exit value should be 0");
+ Assert.equal(aTopic, "process-finished",
+ "the application process observer topic should be " +
+ "process-finished");
+
+ if (IS_SERVICE_TEST) {
+ waitForServiceStop(false);
+ }
+
+ do_execute_soon(afterAppExits);
+ },
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver])
+ };
+
+ function afterAppExits() {
+ gTimeoutRuns++;
+
+ if (IS_WIN) {
+ waitForApplicationStop(FILE_UPDATER_BIN);
+ }
+
+ let status;
+ try {
+ status = readStatusFile();
+ } catch (e) {
+ logTestInfo("error reading status file, exception: " + e);
+ }
+ // Don't proceed until the update's status is the expected value.
+ if (status != aExpectedStatus) {
+ if (gTimeoutRuns > MAX_TIMEOUT_RUNS) {
+ logUpdateLog(FILE_UPDATE_LOG);
+ do_throw("Exceeded MAX_TIMEOUT_RUNS while waiting for the update " +
+ "status to equal: " +
+ aExpectedStatus +
+ ", current status: " + status);
+ } else {
+ do_timeout(FILE_IN_USE_TIMEOUT_MS, afterAppExits);
+ }
+ return;
+ }
+
+ // Don't check for an update log when the code in nsUpdateDriver.cpp skips
+ // updating.
+ if (aExpectedStatus != STATE_PENDING &&
+ aExpectedStatus != STATE_PENDING_SVC &&
+ aExpectedStatus != STATE_APPLIED &&
+ aExpectedStatus != STATE_APPLIED_SVC) {
+ // Don't proceed until the update log has been created.
+ let log = getUpdateLog(FILE_UPDATE_LOG);
+ if (!log.exists()) {
+ if (gTimeoutRuns > MAX_TIMEOUT_RUNS) {
+ do_throw("Exceeded MAX_TIMEOUT_RUNS while waiting for the update " +
+ "log to be created. Path: " + log.path);
+ }
+ do_timeout(FILE_IN_USE_TIMEOUT_MS, afterAppExits);
+ return;
+ }
+ }
+
+ do_execute_soon(runUpdateFinished);
+ }
+
+ debugDump("start - launching application to apply update");
+
+ let appBin = getApplyDirFile(DIR_MACOS + FILE_APP_BIN, false);
+
+ let launchBin = getLaunchBin();
+ let args = getProcessArgs();
+ debugDump("launching " + launchBin.path + " " + args.join(" "));
+
+ gProcess = Cc["@mozilla.org/process/util;1"].
+ createInstance(Ci.nsIProcess);
+ gProcess.init(launchBin);
+
+ gAppTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
+ gAppTimer.initWithCallback(gAppTimerCallback, APP_TIMER_TIMEOUT,
+ Ci.nsITimer.TYPE_ONE_SHOT);
+
+ setEnvironment();
+ debugDump("launching application");
+ gProcess.runAsync(args, args.length, processObserver);
+
+ debugDump("finish - launching application to apply update");
+}
+
+/**
+ * Sets the environment that will be used by the application process when it is
+ * launched.
+ */
+function setEnvironment() {
+ // Prevent setting the environment more than once.
+ if (gShouldResetEnv !== undefined) {
+ return;
+ }
+
+ gShouldResetEnv = true;
+
+ // See bug 1279108.
+ if (gEnv.exists("ASAN_OPTIONS")) {
+ gASanOptions = gEnv.get("ASAN_OPTIONS");
+ gEnv.set("ASAN_OPTIONS", gASanOptions + ":detect_leaks=0");
+ } else {
+ gEnv.set("ASAN_OPTIONS", "detect_leaks=0");
+ }
+
+ if (IS_WIN && !gEnv.exists("XRE_NO_WINDOWS_CRASH_DIALOG")) {
+ gAddedEnvXRENoWindowsCrashDialog = true;
+ debugDump("setting the XRE_NO_WINDOWS_CRASH_DIALOG environment " +
+ "variable to 1... previously it didn't exist");
+ gEnv.set("XRE_NO_WINDOWS_CRASH_DIALOG", "1");
+ }
+
+ if (IS_UNIX) {
+ let appGreBinDir = gGREBinDirOrig.clone();
+ let envGreBinDir = Cc["@mozilla.org/file/local;1"].
+ createInstance(Ci.nsILocalFile);
+ let shouldSetEnv = true;
+ if (IS_MACOSX) {
+ if (gEnv.exists("DYLD_LIBRARY_PATH")) {
+ gEnvDyldLibraryPath = gEnv.get("DYLD_LIBRARY_PATH");
+ envGreBinDir.initWithPath(gEnvDyldLibraryPath);
+ if (envGreBinDir.path == appGreBinDir.path) {
+ gEnvDyldLibraryPath = null;
+ shouldSetEnv = false;
+ }
+ }
+
+ if (shouldSetEnv) {
+ debugDump("setting DYLD_LIBRARY_PATH environment variable value to " +
+ appGreBinDir.path);
+ gEnv.set("DYLD_LIBRARY_PATH", appGreBinDir.path);
+ }
+ } else {
+ if (gEnv.exists("LD_LIBRARY_PATH")) {
+ gEnvLdLibraryPath = gEnv.get("LD_LIBRARY_PATH");
+ envGreBinDir.initWithPath(gEnvLdLibraryPath);
+ if (envGreBinDir.path == appGreBinDir.path) {
+ gEnvLdLibraryPath = null;
+ shouldSetEnv = false;
+ }
+ }
+
+ if (shouldSetEnv) {
+ debugDump("setting LD_LIBRARY_PATH environment variable value to " +
+ appGreBinDir.path);
+ gEnv.set("LD_LIBRARY_PATH", appGreBinDir.path);
+ }
+ }
+ }
+
+ if (gEnv.exists("XPCOM_MEM_LEAK_LOG")) {
+ gEnvXPCOMMemLeakLog = gEnv.get("XPCOM_MEM_LEAK_LOG");
+ debugDump("removing the XPCOM_MEM_LEAK_LOG environment variable... " +
+ "previous value " + gEnvXPCOMMemLeakLog);
+ gEnv.set("XPCOM_MEM_LEAK_LOG", "");
+ }
+
+ if (gEnv.exists("XPCOM_DEBUG_BREAK")) {
+ gEnvXPCOMDebugBreak = gEnv.get("XPCOM_DEBUG_BREAK");
+ debugDump("setting the XPCOM_DEBUG_BREAK environment variable to " +
+ "warn... previous value " + gEnvXPCOMDebugBreak);
+ } else {
+ debugDump("setting the XPCOM_DEBUG_BREAK environment variable to " +
+ "warn... previously it didn't exist");
+ }
+
+ gEnv.set("XPCOM_DEBUG_BREAK", "warn");
+
+ if (IS_SERVICE_TEST) {
+ debugDump("setting MOZ_NO_SERVICE_FALLBACK environment variable to 1");
+ gEnv.set("MOZ_NO_SERVICE_FALLBACK", "1");
+ }
+}
+
+/**
+ * Sets the environment back to the original values after launching the
+ * application.
+ */
+function resetEnvironment() {
+ // Prevent resetting the environment more than once.
+ if (gShouldResetEnv !== true) {
+ return;
+ }
+
+ gShouldResetEnv = false;
+
+ // Restore previous ASAN_OPTIONS if there were any.
+ gEnv.set("ASAN_OPTIONS", gASanOptions ? gASanOptions : "");
+
+ if (gEnvXPCOMMemLeakLog) {
+ debugDump("setting the XPCOM_MEM_LEAK_LOG environment variable back to " +
+ gEnvXPCOMMemLeakLog);
+ gEnv.set("XPCOM_MEM_LEAK_LOG", gEnvXPCOMMemLeakLog);
+ }
+
+ if (gEnvXPCOMDebugBreak) {
+ debugDump("setting the XPCOM_DEBUG_BREAK environment variable back to " +
+ gEnvXPCOMDebugBreak);
+ gEnv.set("XPCOM_DEBUG_BREAK", gEnvXPCOMDebugBreak);
+ } else if (gEnv.exists("XPCOM_DEBUG_BREAK")) {
+ debugDump("clearing the XPCOM_DEBUG_BREAK environment variable");
+ gEnv.set("XPCOM_DEBUG_BREAK", "");
+ }
+
+ if (IS_UNIX) {
+ if (IS_MACOSX) {
+ if (gEnvDyldLibraryPath) {
+ debugDump("setting DYLD_LIBRARY_PATH environment variable value " +
+ "back to " + gEnvDyldLibraryPath);
+ gEnv.set("DYLD_LIBRARY_PATH", gEnvDyldLibraryPath);
+ } else if (gEnvDyldLibraryPath !== null) {
+ debugDump("removing DYLD_LIBRARY_PATH environment variable");
+ gEnv.set("DYLD_LIBRARY_PATH", "");
+ }
+ } else if (gEnvLdLibraryPath) {
+ debugDump("setting LD_LIBRARY_PATH environment variable value back " +
+ "to " + gEnvLdLibraryPath);
+ gEnv.set("LD_LIBRARY_PATH", gEnvLdLibraryPath);
+ } else if (gEnvLdLibraryPath !== null) {
+ debugDump("removing LD_LIBRARY_PATH environment variable");
+ gEnv.set("LD_LIBRARY_PATH", "");
+ }
+ }
+
+ if (IS_WIN && gAddedEnvXRENoWindowsCrashDialog) {
+ debugDump("removing the XRE_NO_WINDOWS_CRASH_DIALOG environment " +
+ "variable");
+ gEnv.set("XRE_NO_WINDOWS_CRASH_DIALOG", "");
+ }
+
+ if (IS_SERVICE_TEST) {
+ debugDump("removing MOZ_NO_SERVICE_FALLBACK environment variable");
+ gEnv.set("MOZ_NO_SERVICE_FALLBACK", "");
+ }
+}
diff --git a/toolkit/mozapps/update/tests/moz.build b/toolkit/mozapps/update/tests/moz.build
new file mode 100644
index 000000000..842ec7f90
--- /dev/null
+++ b/toolkit/mozapps/update/tests/moz.build
@@ -0,0 +1,101 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+HAS_MISC_RULE = True
+
+FINAL_TARGET = '_tests/xpcshell/toolkit/mozapps/update/tests/data'
+
+MOCHITEST_CHROME_MANIFESTS += ['chrome/chrome.ini']
+
+XPCSHELL_TESTS_MANIFESTS += [
+ 'unit_aus_update/xpcshell.ini',
+ 'unit_base_updater/xpcshell.ini'
+]
+
+if CONFIG['MOZ_MAINTENANCE_SERVICE']:
+ XPCSHELL_TESTS_MANIFESTS += ['unit_service_updater/xpcshell.ini']
+
+SimplePrograms([
+ 'TestAUSHelper',
+ 'TestAUSReadStrings',
+])
+
+LOCAL_INCLUDES += [
+ '/toolkit/mozapps/update',
+ '/toolkit/mozapps/update/common',
+]
+
+if CONFIG['OS_ARCH'] == 'WINNT':
+ USE_LIBS += [
+ 'updatecommon-standalone',
+ ]
+
+ OS_LIBS += [
+ 'shlwapi',
+ ]
+else:
+ USE_LIBS += [
+ 'updatecommon',
+ ]
+
+for var in ('MOZ_APP_NAME', 'MOZ_APP_BASENAME', 'MOZ_APP_DISPLAYNAME',
+ 'MOZ_APP_VENDOR', 'BIN_SUFFIX', 'MOZ_DEBUG'):
+ DEFINES[var] = CONFIG[var]
+
+DEFINES['NS_NO_XPCOM'] = True
+
+if CONFIG['MOZ_MAINTENANCE_SERVICE']:
+ DEFINES['MOZ_MAINTENANCE_SERVICE'] = CONFIG['MOZ_MAINTENANCE_SERVICE']
+
+# For debugging purposes only
+#DEFINES['DISABLE_UPDATER_AUTHENTICODE_CHECK'] = True
+
+if CONFIG['OS_ARCH'] == 'WINNT':
+ DEFINES['UNICODE'] = True
+ DEFINES['_UNICODE'] = True
+ USE_STATIC_LIBS = True
+ if CONFIG['GNU_CC']:
+ WIN32_EXE_LDFLAGS += ['-municode']
+
+TEST_HARNESS_FILES.testing.mochitest.chrome.toolkit.mozapps.update.tests.data += [
+ 'data/shared.js',
+ 'data/sharedUpdateXML.js',
+ 'data/simple.mar',
+]
+
+FINAL_TARGET_FILES += [
+ 'data/complete.exe',
+ 'data/complete.mar',
+ 'data/complete.png',
+ 'data/complete_log_success_mac',
+ 'data/complete_log_success_win',
+ 'data/complete_mac.mar',
+ 'data/complete_precomplete',
+ 'data/complete_precomplete_mac',
+ 'data/complete_removed-files',
+ 'data/complete_removed-files_mac',
+ 'data/complete_update_manifest',
+ 'data/old_version.mar',
+ 'data/partial.exe',
+ 'data/partial.mar',
+ 'data/partial.png',
+ 'data/partial_log_failure_mac',
+ 'data/partial_log_failure_win',
+ 'data/partial_log_success_mac',
+ 'data/partial_log_success_win',
+ 'data/partial_mac.mar',
+ 'data/partial_precomplete',
+ 'data/partial_precomplete_mac',
+ 'data/partial_removed-files',
+ 'data/partial_removed-files_mac',
+ 'data/partial_update_manifest',
+ 'data/replace_log_success',
+ 'data/shared.js',
+ 'data/sharedUpdateXML.js',
+ 'data/simple.mar',
+ 'data/wrong_product_channel.mar',
+ 'data/xpcshellUtilsAUS.js',
+]
diff --git a/toolkit/mozapps/update/tests/unit_aus_update/.eslintrc.js b/toolkit/mozapps/update/tests/unit_aus_update/.eslintrc.js
new file mode 100644
index 000000000..d35787cd2
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_aus_update/.eslintrc.js
@@ -0,0 +1,7 @@
+"use strict";
+
+module.exports = {
+ "extends": [
+ "../../../../../testing/xpcshell/xpcshell.eslintrc.js"
+ ]
+};
diff --git a/toolkit/mozapps/update/tests/unit_aus_update/canCheckForAndCanApplyUpdates.js b/toolkit/mozapps/update/tests/unit_aus_update/canCheckForAndCanApplyUpdates.js
new file mode 100644
index 000000000..1985df959
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_aus_update/canCheckForAndCanApplyUpdates.js
@@ -0,0 +1,138 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+function run_test() {
+ setupTestCommon();
+
+ // Verify write access to the custom app dir
+ debugDump("testing write access to the application directory");
+ let testFile = getCurrentProcessDir();
+ testFile.append("update_write_access_test");
+ testFile.create(Ci.nsIFile.NORMAL_FILE_TYPE, PERMS_FILE);
+ Assert.ok(testFile.exists(), MSG_SHOULD_EXIST);
+ testFile.remove(false);
+ Assert.ok(!testFile.exists(), MSG_SHOULD_NOT_EXIST);
+
+ standardInit();
+
+ if (IS_WIN) {
+ // Create a mutex to prevent being able to check for or apply updates.
+ debugDump("attempting to create mutex");
+ let handle = createMutex(getPerInstallationMutexName());
+ Assert.ok(!!handle, "the update mutex should have been created");
+
+ // Check if available updates cannot be checked for when there is a mutex
+ // for this installation.
+ Assert.ok(!gAUS.canCheckForUpdates, "should not be able to check for " +
+ "updates when there is an update mutex");
+
+ // Check if updates cannot be applied when there is a mutex for this
+ // installation.
+ Assert.ok(!gAUS.canApplyUpdates, "should not be able to apply updates " +
+ "when there is an update mutex");
+
+ debugDump("destroying mutex");
+ closeHandle(handle);
+ }
+
+ // Check if available updates can be checked for
+ Assert.ok(gAUS.canCheckForUpdates, "should be able to check for updates");
+ // Check if updates can be applied
+ Assert.ok(gAUS.canApplyUpdates, "should be able to apply updates");
+
+ if (IS_WIN) {
+ // Attempt to create a mutex when application update has already created one
+ // with the same name.
+ debugDump("attempting to create mutex");
+ let handle = createMutex(getPerInstallationMutexName());
+
+ Assert.ok(!handle, "should not be able to create the update mutex when " +
+ "the application has created the update mutex");
+ }
+
+ doTestFinish();
+}
+
+/**
+ * Determines a unique mutex name for the installation.
+ *
+ * @return Global mutex path.
+ */
+function getPerInstallationMutexName() {
+ if (!IS_WIN) {
+ do_throw("Windows only function called by a different platform!");
+ }
+
+ let hasher = Cc["@mozilla.org/security/hash;1"].
+ createInstance(Ci.nsICryptoHash);
+ hasher.init(hasher.SHA1);
+
+ let exeFile = Services.dirsvc.get(XRE_EXECUTABLE_FILE, Ci.nsILocalFile);
+
+ let converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"].
+ createInstance(Ci.nsIScriptableUnicodeConverter);
+ converter.charset = "UTF-8";
+ let data = converter.convertToByteArray(exeFile.path.toLowerCase());
+
+ hasher.update(data, data.length);
+ return "Global\\MozillaUpdateMutex-" + hasher.finish(true);
+}
+
+/**
+ * Closes a Win32 handle.
+ *
+ * @param aHandle
+ * The handle to close.
+ */
+function closeHandle(aHandle) {
+ if (!IS_WIN) {
+ do_throw("Windows only function called by a different platform!");
+ }
+
+ let lib = ctypes.open("kernel32.dll");
+ let CloseHandle = lib.declare("CloseHandle",
+ ctypes.winapi_abi,
+ ctypes.int32_t, /* success */
+ ctypes.void_t.ptr); /* handle */
+ CloseHandle(aHandle);
+ lib.close();
+}
+
+/**
+ * Creates a mutex.
+ *
+ * @param aName
+ * The name for the mutex.
+ * @return The Win32 handle to the mutex.
+ */
+function createMutex(aName) {
+ if (!IS_WIN) {
+ do_throw("Windows only function called by a different platform!");
+ }
+
+ const INITIAL_OWN = 1;
+ const ERROR_ALREADY_EXISTS = 0xB7;
+ let lib = ctypes.open("kernel32.dll");
+ let CreateMutexW = lib.declare("CreateMutexW",
+ ctypes.winapi_abi,
+ ctypes.void_t.ptr, /* return handle */
+ ctypes.void_t.ptr, /* security attributes */
+ ctypes.int32_t, /* initial owner */
+ ctypes.char16_t.ptr); /* name */
+
+ let handle = CreateMutexW(null, INITIAL_OWN, aName);
+ lib.close();
+ let alreadyExists = ctypes.winLastError == ERROR_ALREADY_EXISTS;
+ if (handle && !handle.isNull() && alreadyExists) {
+ closeHandle(handle);
+ handle = null;
+ }
+
+ if (handle && handle.isNull()) {
+ handle = null;
+ }
+
+ return handle;
+}
diff --git a/toolkit/mozapps/update/tests/unit_aus_update/cleanupDownloadingForDifferentChannel.js b/toolkit/mozapps/update/tests/unit_aus_update/cleanupDownloadingForDifferentChannel.js
new file mode 100644
index 000000000..a0a95af1b
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_aus_update/cleanupDownloadingForDifferentChannel.js
@@ -0,0 +1,46 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+/* General Update Manager Tests */
+
+function run_test() {
+ setupTestCommon();
+
+ debugDump("testing removal of an active update for a channel that is not" +
+ "valid due to switching channels (Bug 486275).");
+
+ let patches = getLocalPatchString(null, null, null, null, null, null,
+ STATE_DOWNLOADING);
+ let updates = getLocalUpdateString(patches, null, null, "version 1.0", "1.0");
+ writeUpdatesToXMLFile(getLocalUpdatesXMLString(updates), true);
+ writeStatusFile(STATE_DOWNLOADING);
+
+ patches = getLocalPatchString(null, null, null, null, null, null,
+ STATE_FAILED);
+ updates = getLocalUpdateString(patches, null, "Existing", "version 3.0",
+ "3.0", "3.0", null, null, null, null,
+ getString("patchApplyFailure"));
+ writeUpdatesToXMLFile(getLocalUpdatesXMLString(updates), false);
+
+ setUpdateChannel("original_channel");
+
+ standardInit();
+
+ Assert.equal(gUpdateManager.updateCount, 1,
+ "the update manager update count" + MSG_SHOULD_EQUAL);
+ let update = gUpdateManager.getUpdateAt(0);
+ Assert.equal(update.name, "Existing",
+ "the update's name" + MSG_SHOULD_EQUAL);
+
+ Assert.ok(!gUpdateManager.activeUpdate,
+ "there should not be an active update");
+ // Verify that the active-update.xml file has had the update from the old
+ // channel removed.
+ let file = getUpdatesXMLFile(true);
+ Assert.equal(readFile(file), getLocalUpdatesXMLString(""),
+ "the contents of active-update.xml" + MSG_SHOULD_EQUAL);
+
+ doTestFinish();
+}
diff --git a/toolkit/mozapps/update/tests/unit_aus_update/cleanupDownloadingForOlderAppVersion.js b/toolkit/mozapps/update/tests/unit_aus_update/cleanupDownloadingForOlderAppVersion.js
new file mode 100644
index 000000000..fc4f09787
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_aus_update/cleanupDownloadingForOlderAppVersion.js
@@ -0,0 +1,29 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+
+function run_test() {
+ setupTestCommon();
+
+ debugDump("testing cleanup of an update download in progress for an " +
+ "older version of the application on startup (Bug 485624)");
+
+ let patches = getLocalPatchString(null, null, null, null, null, null,
+ STATE_DOWNLOADING);
+ let updates = getLocalUpdateString(patches, null, null, "version 0.9", "0.9");
+ writeUpdatesToXMLFile(getLocalUpdatesXMLString(updates), true);
+ writeStatusFile(STATE_DOWNLOADING);
+
+ writeUpdatesToXMLFile(getLocalUpdatesXMLString(""), false);
+
+ standardInit();
+
+ Assert.ok(!gUpdateManager.activeUpdate,
+ "there should not be an active update");
+ Assert.equal(gUpdateManager.updateCount, 0,
+ "the update manager update count" + MSG_SHOULD_EQUAL);
+
+ doTestFinish();
+}
diff --git a/toolkit/mozapps/update/tests/unit_aus_update/cleanupDownloadingForSameVersionAndBuildID.js b/toolkit/mozapps/update/tests/unit_aus_update/cleanupDownloadingForSameVersionAndBuildID.js
new file mode 100644
index 000000000..b2d8ecbc6
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_aus_update/cleanupDownloadingForSameVersionAndBuildID.js
@@ -0,0 +1,30 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+function run_test() {
+ setupTestCommon();
+
+ debugDump("testing removal of an update download in progress for the " +
+ "same version of the application with the same application " +
+ "build id on startup (Bug 536547)");
+
+ let patches = getLocalPatchString(null, null, null, null, null, null,
+ STATE_DOWNLOADING);
+ let updates = getLocalUpdateString(patches, null, null, "version 1.0", "1.0",
+ "2007010101");
+ writeUpdatesToXMLFile(getLocalUpdatesXMLString(updates), true);
+ writeStatusFile(STATE_DOWNLOADING);
+
+ writeUpdatesToXMLFile(getLocalUpdatesXMLString(""), false);
+
+ standardInit();
+
+ Assert.ok(!gUpdateManager.activeUpdate,
+ "there should not be an active update");
+ Assert.equal(gUpdateManager.updateCount, 0,
+ "the update manager update count" + MSG_SHOULD_EQUAL);
+
+ doTestFinish();
+}
diff --git a/toolkit/mozapps/update/tests/unit_aus_update/cleanupDownloadingIncorrectStatus.js b/toolkit/mozapps/update/tests/unit_aus_update/cleanupDownloadingIncorrectStatus.js
new file mode 100644
index 000000000..13e4aeaf6
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_aus_update/cleanupDownloadingIncorrectStatus.js
@@ -0,0 +1,35 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+function run_test() {
+ setupTestCommon();
+
+ debugDump("testing update cleanup when reading the status file returns " +
+ "STATUS_NONE and the update xml has an update with " +
+ "STATE_DOWNLOADING (Bug 539717).");
+
+ writeUpdatesToXMLFile(getLocalUpdatesXMLString(""), false);
+ let patches = getLocalPatchString(null, null, null, null, null, null,
+ STATE_DOWNLOADING);
+ let updates = getLocalUpdateString(patches);
+ writeUpdatesToXMLFile(getLocalUpdatesXMLString(updates), true);
+ writeStatusFile(STATE_NONE);
+
+ standardInit();
+
+ let dir = getUpdatesDir();
+ dir.append(DIR_PATCH);
+ Assert.ok(dir.exists(), MSG_SHOULD_EXIST);
+
+ let statusFile = dir.clone();
+ statusFile.append(FILE_UPDATE_STATUS);
+ Assert.ok(!statusFile.exists(), MSG_SHOULD_NOT_EXIST);
+
+ Assert.ok(!gUpdateManager.activeUpdate,
+ "there should not be an active update");
+ Assert.equal(gUpdateManager.updateCount, 0,
+ "the update manager update count" + MSG_SHOULD_EQUAL);
+
+ doTestFinish();
+}
diff --git a/toolkit/mozapps/update/tests/unit_aus_update/cleanupPendingVersionFileIncorrectStatus.js b/toolkit/mozapps/update/tests/unit_aus_update/cleanupPendingVersionFileIncorrectStatus.js
new file mode 100644
index 000000000..7661da82d
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_aus_update/cleanupPendingVersionFileIncorrectStatus.js
@@ -0,0 +1,37 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+function run_test() {
+ setupTestCommon();
+
+ debugDump("testing update cleanup when reading the status file returns " +
+ "STATUS_NONE, the version file is for a newer version, and the " +
+ "update xml has an update with STATE_PENDING (Bug 601701).");
+
+ writeUpdatesToXMLFile(getLocalUpdatesXMLString(""), false);
+ let patches = getLocalPatchString(null, null, null, null, null, null,
+ STATE_PENDING);
+ let updates = getLocalUpdateString(patches);
+ writeUpdatesToXMLFile(getLocalUpdatesXMLString(updates), true);
+ writeVersionFile("99.9");
+
+ standardInit();
+
+ // Check that there is no activeUpdate first so the updates directory is
+ // cleaned up by the UpdateManager before the remaining tests.
+ Assert.ok(!gUpdateManager.activeUpdate,
+ "there should not be an active update");
+ Assert.equal(gUpdateManager.updateCount, 0,
+ "the update manager update count" + MSG_SHOULD_EQUAL);
+
+ let dir = getUpdatesDir();
+ dir.append(DIR_PATCH);
+ Assert.ok(dir.exists(), MSG_SHOULD_EXIST);
+
+ let versionFile = dir.clone();
+ versionFile.append(FILE_UPDATE_VERSION);
+ Assert.ok(!versionFile.exists(), MSG_SHOULD_NOT_EXIST);
+
+ doTestFinish();
+}
diff --git a/toolkit/mozapps/update/tests/unit_aus_update/cleanupSuccessLogMove.js b/toolkit/mozapps/update/tests/unit_aus_update/cleanupSuccessLogMove.js
new file mode 100644
index 000000000..d683b9931
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_aus_update/cleanupSuccessLogMove.js
@@ -0,0 +1,37 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+function run_test() {
+ setupTestCommon();
+
+ debugDump("testing that the update.log is moved after a successful update");
+
+ writeUpdatesToXMLFile(getLocalUpdatesXMLString(""), false);
+ let patches = getLocalPatchString(null, null, null, null, null, null,
+ STATE_PENDING);
+ let updates = getLocalUpdateString(patches);
+ writeUpdatesToXMLFile(getLocalUpdatesXMLString(updates), true);
+ writeStatusFile(STATE_SUCCEEDED);
+
+ let log = getUpdateLog(FILE_UPDATE_LOG);
+ writeFile(log, "Last Update Log");
+
+ standardInit();
+
+ Assert.ok(!log.exists(), MSG_SHOULD_NOT_EXIST);
+
+ log = getUpdateLog(FILE_LAST_UPDATE_LOG);
+ Assert.ok(log.exists(), MSG_SHOULD_EXIST);
+ Assert.equal(readFile(log), "Last Update Log",
+ "the last update log contents" + MSG_SHOULD_EQUAL);
+
+ log = getUpdateLog(FILE_BACKUP_UPDATE_LOG);
+ Assert.ok(!log.exists(), MSG_SHOULD_NOT_EXIST);
+
+ let dir = getUpdatesPatchDir();
+ Assert.ok(dir.exists(), MSG_SHOULD_EXIST);
+
+ doTestFinish();
+}
diff --git a/toolkit/mozapps/update/tests/unit_aus_update/cleanupSuccessLogsFIFO.js b/toolkit/mozapps/update/tests/unit_aus_update/cleanupSuccessLogsFIFO.js
new file mode 100644
index 000000000..8be93d0ff
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_aus_update/cleanupSuccessLogsFIFO.js
@@ -0,0 +1,45 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+function run_test() {
+ setupTestCommon();
+
+ debugDump("testing update logs are first in first out deleted");
+
+ writeUpdatesToXMLFile(getLocalUpdatesXMLString(""), false);
+ let patches = getLocalPatchString(null, null, null, null, null, null,
+ STATE_PENDING);
+ let updates = getLocalUpdateString(patches);
+ writeUpdatesToXMLFile(getLocalUpdatesXMLString(updates), true);
+ writeStatusFile(STATE_SUCCEEDED);
+
+ let log = getUpdateLog(FILE_LAST_UPDATE_LOG);
+ writeFile(log, "Backup Update Log");
+
+ log = getUpdateLog(FILE_BACKUP_UPDATE_LOG);
+ writeFile(log, "To Be Deleted Backup Update Log");
+
+ log = getUpdateLog(FILE_UPDATE_LOG);
+ writeFile(log, "Last Update Log");
+
+ standardInit();
+
+ Assert.ok(!log.exists(), MSG_SHOULD_NOT_EXIST);
+
+ log = getUpdateLog(FILE_LAST_UPDATE_LOG);
+ Assert.ok(log.exists(), MSG_SHOULD_EXIST);
+ Assert.equal(readFile(log), "Last Update Log",
+ "the last update log contents" + MSG_SHOULD_EQUAL);
+
+ log = getUpdateLog(FILE_BACKUP_UPDATE_LOG);
+ Assert.ok(log.exists(), MSG_SHOULD_EXIST);
+ Assert.equal(readFile(log), "Backup Update Log",
+ "the backup update log contents" + MSG_SHOULD_EQUAL);
+
+ let dir = getUpdatesPatchDir();
+ Assert.ok(dir.exists(), MSG_SHOULD_EXIST);
+
+ doTestFinish();
+}
diff --git a/toolkit/mozapps/update/tests/unit_aus_update/downloadAndHashCheckMar.js b/toolkit/mozapps/update/tests/unit_aus_update/downloadAndHashCheckMar.js
new file mode 100644
index 000000000..b715fb56e
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_aus_update/downloadAndHashCheckMar.js
@@ -0,0 +1,161 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+var gNextRunFunc;
+var gExpectedStatusResult;
+
+function run_test() {
+ // The network code that downloads the mar file accesses the profile to cache
+ // the download, but the profile is only available after calling
+ // do_get_profile in xpcshell tests. This prevents an error from being logged.
+ do_get_profile();
+
+ setupTestCommon();
+
+ debugDump("testing mar download and mar hash verification");
+
+ Services.prefs.setBoolPref(PREF_APP_UPDATE_STAGING_ENABLED, false);
+ start_httpserver();
+ setUpdateURL(gURLData + gHTTPHandlerPath);
+ standardInit();
+ // Only perform the non hash check tests when mar signing is enabled since the
+ // update service doesn't perform hash checks when mar signing is enabled.
+ if (MOZ_VERIFY_MAR_SIGNATURE) {
+ do_execute_soon(run_test_pt11);
+ } else {
+ do_execute_soon(run_test_pt1);
+ }
+}
+
+// The HttpServer must be stopped before calling do_test_finished
+function finish_test() {
+ stop_httpserver(doTestFinish);
+}
+
+// Helper function for testing mar downloads that have the correct size
+// specified in the update xml.
+function run_test_helper_pt1(aMsg, aExpectedStatusResult, aNextRunFunc) {
+ gUpdates = null;
+ gUpdateCount = null;
+ gStatusResult = null;
+ gCheckFunc = check_test_helper_pt1_1;
+ gNextRunFunc = aNextRunFunc;
+ gExpectedStatusResult = aExpectedStatusResult;
+ debugDump(aMsg, Components.stack.caller);
+ gUpdateChecker.checkForUpdates(updateCheckListener, true);
+}
+
+function check_test_helper_pt1_1() {
+ Assert.equal(gUpdateCount, 1,
+ "the update count" + MSG_SHOULD_EQUAL);
+ gCheckFunc = check_test_helper_pt1_2;
+ let bestUpdate = gAUS.selectUpdate(gUpdates, gUpdateCount);
+ let state = gAUS.downloadUpdate(bestUpdate, false);
+ if (state == STATE_NONE || state == STATE_FAILED) {
+ do_throw("nsIApplicationUpdateService:downloadUpdate returned " + state);
+ }
+ gAUS.addDownloadListener(downloadListener);
+}
+
+function check_test_helper_pt1_2() {
+ Assert.equal(gStatusResult, gExpectedStatusResult,
+ "the download status result" + MSG_SHOULD_EQUAL);
+ gAUS.removeDownloadListener(downloadListener);
+ gNextRunFunc();
+}
+
+function setResponseBody(aHashFunction, aHashValue, aSize) {
+ let patches = getRemotePatchString(null, null,
+ aHashFunction, aHashValue, aSize);
+ let updates = getRemoteUpdateString(patches);
+ gResponseBody = getRemoteUpdatesXMLString(updates);
+}
+
+// mar download with a valid MD5 hash
+function run_test_pt1() {
+ setResponseBody("MD5", MD5_HASH_SIMPLE_MAR);
+ run_test_helper_pt1("mar download with a valid MD5 hash",
+ Cr.NS_OK, run_test_pt2);
+}
+
+// mar download with an invalid MD5 hash
+function run_test_pt2() {
+ setResponseBody("MD5", MD5_HASH_SIMPLE_MAR + "0");
+ run_test_helper_pt1("mar download with an invalid MD5 hash",
+ Cr.NS_ERROR_CORRUPTED_CONTENT, run_test_pt3);
+}
+
+// mar download with a valid SHA1 hash
+function run_test_pt3() {
+ setResponseBody("SHA1", SHA1_HASH_SIMPLE_MAR);
+ run_test_helper_pt1("mar download with a valid SHA1 hash",
+ Cr.NS_OK, run_test_pt4);
+}
+
+// mar download with an invalid SHA1 hash
+function run_test_pt4() {
+ setResponseBody("SHA1", SHA1_HASH_SIMPLE_MAR + "0");
+ run_test_helper_pt1("mar download with an invalid SHA1 hash",
+ Cr.NS_ERROR_CORRUPTED_CONTENT, run_test_pt5);
+}
+
+// mar download with a valid SHA256 hash
+function run_test_pt5() {
+ setResponseBody("SHA256", SHA256_HASH_SIMPLE_MAR);
+ run_test_helper_pt1("mar download with a valid SHA256 hash",
+ Cr.NS_OK, run_test_pt6);
+}
+
+// mar download with an invalid SHA256 hash
+function run_test_pt6() {
+ setResponseBody("SHA256", SHA256_HASH_SIMPLE_MAR + "0");
+ run_test_helper_pt1("mar download with an invalid SHA256 hash",
+ Cr.NS_ERROR_CORRUPTED_CONTENT, run_test_pt7);
+}
+
+// mar download with a valid SHA384 hash
+function run_test_pt7() {
+ setResponseBody("SHA384", SHA384_HASH_SIMPLE_MAR);
+ run_test_helper_pt1("mar download with a valid SHA384 hash",
+ Cr.NS_OK, run_test_pt8);
+}
+
+// mar download with an invalid SHA384 hash
+function run_test_pt8() {
+ setResponseBody("SHA384", SHA384_HASH_SIMPLE_MAR + "0");
+ run_test_helper_pt1("mar download with an invalid SHA384 hash",
+ Cr.NS_ERROR_CORRUPTED_CONTENT, run_test_pt9);
+}
+
+// mar download with a valid SHA512 hash
+function run_test_pt9() {
+ setResponseBody("SHA512", SHA512_HASH_SIMPLE_MAR);
+ run_test_helper_pt1("mar download with a valid SHA512 hash",
+ Cr.NS_OK, run_test_pt10);
+}
+
+// mar download with an invalid SHA512 hash
+function run_test_pt10() {
+ setResponseBody("SHA512", SHA512_HASH_SIMPLE_MAR + "0");
+ run_test_helper_pt1("mar download with an invalid SHA512 hash",
+ Cr.NS_ERROR_CORRUPTED_CONTENT, run_test_pt11);
+}
+
+// mar download with the mar not found
+function run_test_pt11() {
+ let patches = getRemotePatchString(null, gURLData + "missing.mar");
+ let updates = getRemoteUpdateString(patches);
+ gResponseBody = getRemoteUpdatesXMLString(updates);
+ run_test_helper_pt1("mar download with the mar not found",
+ Cr.NS_ERROR_UNEXPECTED, run_test_pt12);
+}
+
+// mar download with a valid MD5 hash but invalid file size
+function run_test_pt12() {
+ const arbitraryFileSize = 1024000;
+ setResponseBody("MD5", MD5_HASH_SIMPLE_MAR, arbitraryFileSize);
+ run_test_helper_pt1("mar download with a valid MD5 hash but invalid file size",
+ Cr.NS_ERROR_UNEXPECTED, finish_test);
+}
diff --git a/toolkit/mozapps/update/tests/unit_aus_update/downloadCompleteAfterPartialFailure.js b/toolkit/mozapps/update/tests/unit_aus_update/downloadCompleteAfterPartialFailure.js
new file mode 100644
index 000000000..159033792
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_aus_update/downloadCompleteAfterPartialFailure.js
@@ -0,0 +1,66 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+Components.utils.import("resource://testing-common/MockRegistrar.jsm");
+
+const WindowWatcher = {
+ getNewPrompter: function WW_getNewPrompter(aParent) {
+ Assert.ok(!aParent,
+ "the aParent parameter should not be defined");
+ return {
+ alert: function WW_GNP_alert(aTitle, aText) {
+ let title = getString("updaterIOErrorTitle");
+ Assert.equal(aTitle, title,
+ "the ui string for title" + MSG_SHOULD_EQUAL);
+ let text = gUpdateBundle.formatStringFromName("updaterIOErrorMsg",
+ [Services.appinfo.name,
+ Services.appinfo.name], 2);
+ Assert.equal(aText, text,
+ "the ui string for message" + MSG_SHOULD_EQUAL);
+
+ doTestFinish();
+ }
+ };
+ },
+
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIWindowWatcher])
+};
+
+function run_test() {
+ setupTestCommon();
+
+ debugDump("testing download a complete on partial failure. Calling " +
+ "nsIUpdatePrompt::showUpdateError should call getNewPrompter " +
+ "and alert on the object returned by getNewPrompter when the " +
+ "update.state == " + STATE_FAILED + " and the update.errorCode " +
+ "== " + WRITE_ERROR + " (Bug 595059).");
+
+ Services.prefs.setBoolPref(PREF_APP_UPDATE_SILENT, false);
+
+ let windowWatcherCID =
+ MockRegistrar.register("@mozilla.org/embedcomp/window-watcher;1",
+ WindowWatcher);
+ do_register_cleanup(() => {
+ MockRegistrar.unregister(windowWatcherCID);
+ });
+
+ standardInit();
+
+ writeUpdatesToXMLFile(getLocalUpdatesXMLString(""), false);
+ let url = URL_HOST + "/" + FILE_COMPLETE_MAR;
+ let patches = getLocalPatchString("complete", url, null, null, null, null,
+ STATE_FAILED);
+ let updates = getLocalUpdateString(patches, null, null, "version 1.0", "1.0",
+ null, null, null, null, url);
+ writeUpdatesToXMLFile(getLocalUpdatesXMLString(updates), true);
+ writeStatusFile(STATE_FAILED);
+
+ reloadUpdateManagerData();
+
+ let update = gUpdateManager.activeUpdate;
+ update.errorCode = WRITE_ERROR;
+ let prompter = Cc["@mozilla.org/updates/update-prompt;1"].
+ createInstance(Ci.nsIUpdatePrompt);
+ prompter.showUpdateError(update);
+}
diff --git a/toolkit/mozapps/update/tests/unit_aus_update/downloadInterruptedRecovery.js b/toolkit/mozapps/update/tests/unit_aus_update/downloadInterruptedRecovery.js
new file mode 100644
index 000000000..ef2da26af
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_aus_update/downloadInterruptedRecovery.js
@@ -0,0 +1,225 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+/* General MAR File Download Tests */
+
+Components.utils.import("resource://testing-common/MockRegistrar.jsm");
+const INC_CONTRACT_ID = "@mozilla.org/network/incremental-download;1";
+
+// gIncrementalDownloadErrorType is used to loop through each of the connection
+// error types in the Mock incremental downloader.
+var gIncrementalDownloadErrorType = 0;
+
+var gNextRunFunc;
+var gExpectedStatusResult;
+
+function run_test() {
+ setupTestCommon();
+
+ debugDump("testing mar downloads, mar hash verification, and " +
+ "mar download interrupted recovery");
+
+ Services.prefs.setBoolPref(PREF_APP_UPDATE_STAGING_ENABLED, false);
+ start_httpserver();
+ setUpdateURL(gURLData + gHTTPHandlerPath);
+ standardInit();
+ do_execute_soon(run_test_pt1);
+}
+
+// The HttpServer must be stopped before calling do_test_finished
+function finish_test() {
+ stop_httpserver(doTestFinish);
+}
+
+// Helper function for testing mar downloads that have the correct size
+// specified in the update xml.
+function run_test_helper_pt1(aMsg, aExpectedStatusResult, aNextRunFunc) {
+ gUpdates = null;
+ gUpdateCount = null;
+ gStatusResult = null;
+ gCheckFunc = check_test_helper_pt1_1;
+ gNextRunFunc = aNextRunFunc;
+ gExpectedStatusResult = aExpectedStatusResult;
+ debugDump(aMsg, Components.stack.caller);
+ gUpdateChecker.checkForUpdates(updateCheckListener, true);
+}
+
+function check_test_helper_pt1_1() {
+ Assert.equal(gUpdateCount, 1,
+ "the update count" + MSG_SHOULD_EQUAL);
+ gCheckFunc = check_test_helper_pt1_2;
+ let bestUpdate = gAUS.selectUpdate(gUpdates, gUpdateCount);
+ let state = gAUS.downloadUpdate(bestUpdate, false);
+ if (state == STATE_NONE || state == STATE_FAILED) {
+ do_throw("nsIApplicationUpdateService:downloadUpdate returned " + state);
+ }
+ gAUS.addDownloadListener(downloadListener);
+}
+
+function check_test_helper_pt1_2() {
+ Assert.equal(gStatusResult, gExpectedStatusResult,
+ "the download status result" + MSG_SHOULD_EQUAL);
+ gAUS.removeDownloadListener(downloadListener);
+ gNextRunFunc();
+}
+
+function setResponseBody(aHashFunction, aHashValue, aSize) {
+ let patches = getRemotePatchString(null, null,
+ aHashFunction, aHashValue, aSize);
+ let updates = getRemoteUpdateString(patches);
+ gResponseBody = getRemoteUpdatesXMLString(updates);
+}
+
+function initMockIncrementalDownload() {
+ let incrementalDownloadCID =
+ MockRegistrar.register(INC_CONTRACT_ID, IncrementalDownload);
+ do_register_cleanup(() => {
+ MockRegistrar.unregister(incrementalDownloadCID);
+ });
+}
+
+/* This Mock incremental downloader is used to verify that connection
+ * interrupts work correctly in updater code. The implementation of
+ * the mock incremental downloader is very simple, it simply copies
+ * the file to the destination location.
+ */
+
+function IncrementalDownload() {
+ this.wrappedJSObject = this;
+}
+
+IncrementalDownload.prototype = {
+ /* nsIIncrementalDownload */
+ init: function(uri, file, chunkSize, intervalInSeconds) {
+ this._destination = file;
+ this._URI = uri;
+ this._finalURI = uri;
+ },
+
+ start: function(observer, ctxt) {
+ let tm = Cc["@mozilla.org/thread-manager;1"].
+ getService(Ci.nsIThreadManager);
+ // Do the actual operation async to give a chance for observers
+ // to add themselves.
+ tm.mainThread.dispatch(function() {
+ this._observer = observer.QueryInterface(Ci.nsIRequestObserver);
+ this._ctxt = ctxt;
+ this._observer.onStartRequest(this, this._ctxt);
+ let mar = getTestDirFile(FILE_SIMPLE_MAR);
+ mar.copyTo(this._destination.parent, this._destination.leafName);
+ let status = Cr.NS_OK;
+ switch (gIncrementalDownloadErrorType++) {
+ case 0:
+ status = Cr.NS_ERROR_NET_RESET;
+ break;
+ case 1:
+ status = Cr.NS_ERROR_CONNECTION_REFUSED;
+ break;
+ case 2:
+ status = Cr.NS_ERROR_NET_RESET;
+ break;
+ case 3:
+ status = Cr.NS_OK;
+ break;
+ case 4:
+ status = Cr.NS_ERROR_OFFLINE;
+ // After we report offline, we want to eventually show offline
+ // status being changed to online.
+ let tm2 = Cc["@mozilla.org/thread-manager;1"].
+ getService(Ci.nsIThreadManager);
+ tm2.mainThread.dispatch(function() {
+ Services.obs.notifyObservers(gAUS,
+ "network:offline-status-changed",
+ "online");
+ }, Ci.nsIThread.DISPATCH_NORMAL);
+ break;
+ }
+ this._observer.onStopRequest(this, this._ctxt, status);
+ }.bind(this), Ci.nsIThread.DISPATCH_NORMAL);
+ },
+
+ get URI() {
+ return this._URI;
+ },
+
+ get currentSize() {
+ throw Cr.NS_ERROR_NOT_IMPLEMENTED;
+ },
+
+ get destination() {
+ return this._destination;
+ },
+
+ get finalURI() {
+ return this._finalURI;
+ },
+
+ get totalSize() {
+ throw Cr.NS_ERROR_NOT_IMPLEMENTED;
+ },
+
+ /* nsIRequest */
+ cancel: function(aStatus) {
+ throw Cr.NS_ERROR_NOT_IMPLEMENTED;
+ },
+ suspend: function() {
+ throw Cr.NS_ERROR_NOT_IMPLEMENTED;
+ },
+ isPending: function() {
+ throw Cr.NS_ERROR_NOT_IMPLEMENTED;
+ },
+ _loadFlags: 0,
+ get loadFlags() {
+ return this._loadFlags;
+ },
+ set loadFlags(val) {
+ this._loadFlags = val;
+ },
+
+ _loadGroup: null,
+ get loadGroup() {
+ return this._loadGroup;
+ },
+ set loadGroup(val) {
+ this._loadGroup = val;
+ },
+
+ _name: "",
+ get name() {
+ return this._name;
+ },
+
+ _status: 0,
+ get status() {
+ return this._status;
+ },
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIIncrementalDownload])
+};
+
+// Test disconnecting during an update
+function run_test_pt1() {
+ initMockIncrementalDownload();
+ setResponseBody("MD5", MD5_HASH_SIMPLE_MAR);
+ run_test_helper_pt1("mar download with connection interruption",
+ Cr.NS_OK, run_test_pt2);
+}
+
+// Test disconnecting during an update
+function run_test_pt2() {
+ gIncrementalDownloadErrorType = 0;
+ Services.prefs.setIntPref(PREF_APP_UPDATE_SOCKET_MAXERRORS, 2);
+ Services.prefs.setIntPref(PREF_APP_UPDATE_RETRYTIMEOUT, 0);
+ setResponseBody("MD5", MD5_HASH_SIMPLE_MAR);
+ run_test_helper_pt1("mar download with connection interruption without recovery",
+ Cr.NS_ERROR_NET_RESET, run_test_pt3);
+}
+
+// Test entering offline mode while downloading
+function run_test_pt3() {
+ gIncrementalDownloadErrorType = 4;
+ setResponseBody("MD5", MD5_HASH_SIMPLE_MAR);
+ run_test_helper_pt1("mar download with offline mode",
+ Cr.NS_OK, finish_test);
+}
diff --git a/toolkit/mozapps/update/tests/unit_aus_update/downloadResumeForSameAppVersion.js b/toolkit/mozapps/update/tests/unit_aus_update/downloadResumeForSameAppVersion.js
new file mode 100644
index 000000000..ca065f573
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_aus_update/downloadResumeForSameAppVersion.js
@@ -0,0 +1,37 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+function run_test() {
+ setupTestCommon();
+
+ debugDump("testing resuming an update download in progress for the same " +
+ "version of the application on startup (Bug 485624)");
+
+ let patches = getLocalPatchString(null, null, null, null, null, null,
+ STATE_DOWNLOADING);
+ let updates = getLocalUpdateString(patches, null, null, "1.0", "1.0");
+ writeUpdatesToXMLFile(getLocalUpdatesXMLString(updates), true);
+ writeStatusFile(STATE_DOWNLOADING);
+
+ writeUpdatesToXMLFile(getLocalUpdatesXMLString(""), false);
+
+ standardInit();
+
+ Assert.equal(gUpdateManager.updateCount, 1,
+ "the update manager updateCount attribute" + MSG_SHOULD_EQUAL);
+ Assert.equal(gUpdateManager.activeUpdate.state, STATE_DOWNLOADING,
+ "the update manager activeUpdate state attribute" +
+ MSG_SHOULD_EQUAL);
+
+ // Pause the download and reload the Update Manager with an empty update so
+ // the Application Update Service doesn't write the update xml files during
+ // xpcom-shutdown which will leave behind the test directory.
+ gAUS.pauseDownload();
+ writeUpdatesToXMLFile(getLocalUpdatesXMLString(""), true);
+ writeUpdatesToXMLFile(getLocalUpdatesXMLString(""), false);
+ reloadUpdateManagerData();
+
+ do_execute_soon(doTestFinish);
+}
diff --git a/toolkit/mozapps/update/tests/unit_aus_update/head_update.js b/toolkit/mozapps/update/tests/unit_aus_update/head_update.js
new file mode 100644
index 000000000..9715c5828
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_aus_update/head_update.js
@@ -0,0 +1,8 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+const IS_SERVICE_TEST = false;
+
+/* import-globals-from ../data/xpcshellUtilsAUS.js */
+load("../data/xpcshellUtilsAUS.js");
diff --git a/toolkit/mozapps/update/tests/unit_aus_update/remoteUpdateXML.js b/toolkit/mozapps/update/tests/unit_aus_update/remoteUpdateXML.js
new file mode 100644
index 000000000..831c87257
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_aus_update/remoteUpdateXML.js
@@ -0,0 +1,285 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+var gNextRunFunc;
+var gExpectedCount;
+
+function run_test() {
+ setupTestCommon();
+
+ debugDump("testing remote update xml attributes");
+
+ start_httpserver();
+ setUpdateURL(gURLData + gHTTPHandlerPath);
+ setUpdateChannel("test_channel");
+
+ // This test expects that the app.update.download.backgroundInterval
+ // preference doesn't already exist.
+ Services.prefs.deleteBranch("app.update.download.backgroundInterval");
+
+ standardInit();
+ do_execute_soon(run_test_pt01);
+}
+
+// Helper function for testing update counts returned from an update xml
+function run_test_helper_pt1(aMsg, aExpectedCount, aNextRunFunc) {
+ gUpdates = null;
+ gUpdateCount = null;
+ gCheckFunc = check_test_helper_pt1;
+ gNextRunFunc = aNextRunFunc;
+ gExpectedCount = aExpectedCount;
+ debugDump(aMsg, Components.stack.caller);
+ gUpdateChecker.checkForUpdates(updateCheckListener, true);
+}
+
+function check_test_helper_pt1() {
+ Assert.equal(gUpdateCount, gExpectedCount,
+ "the update count" + MSG_SHOULD_EQUAL);
+ gNextRunFunc();
+}
+
+// update xml not found
+function run_test_pt01() {
+ run_test_helper_pt1("testing update xml not available",
+ null, run_test_pt02);
+}
+
+// one update available and the update's property values
+function run_test_pt02() {
+ debugDump("testing one update available and the update's property values");
+ gUpdates = null;
+ gUpdateCount = null;
+ gCheckFunc = check_test_pt02;
+ let patches = getRemotePatchString("complete", "http://complete/", "SHA1",
+ "98db9dad8e1d80eda7e1170d0187d6f53e477059",
+ "9856459");
+ patches += getRemotePatchString("partial", "http://partial/", "SHA1",
+ "e6678ca40ae7582316acdeddf3c133c9c8577de4",
+ "1316138");
+ let updates = getRemoteUpdateString(patches, "minor", "Minor Test",
+ "version 2.1a1pre", "2.1a1pre",
+ "20080811053724",
+ "http://details/",
+ "true",
+ "true", "345600", "1200",
+ "custom1_attr=\"custom1 value\"",
+ "custom2_attr=\"custom2 value\"");
+ gResponseBody = getRemoteUpdatesXMLString(updates);
+ gUpdateChecker.checkForUpdates(updateCheckListener, true);
+}
+
+function check_test_pt02() {
+ // XXXrstrong - not specifying a detailsURL will cause a leak due to bug 470244
+ // and until this is fixed this will not test the value for detailsURL when it
+ // isn't specified in the update xml.
+// let defaultDetailsURL;
+// try {
+ // Try using a default details URL supplied by the distribution
+ // if the update XML does not supply one.
+// let formatter = Cc["@mozilla.org/toolkit/URLFormatterService;1"].
+// getService(Ci.nsIURLFormatter);
+// defaultDetailsURL = formatter.formatURLPref(PREF_APP_UPDATE_URL_DETAILS);
+// } catch (e) {
+// defaultDetailsURL = "";
+// }
+
+ Assert.equal(gUpdateCount, 1,
+ "the update count" + MSG_SHOULD_EQUAL);
+ let bestUpdate = gAUS.selectUpdate(gUpdates, gUpdateCount).QueryInterface(Ci.nsIPropertyBag);
+ Assert.equal(bestUpdate.type, "minor",
+ "the update type attribute" + MSG_SHOULD_EQUAL);
+ Assert.equal(bestUpdate.name, "Minor Test",
+ "the update name attribute" + MSG_SHOULD_EQUAL);
+ Assert.equal(bestUpdate.displayVersion, "version 2.1a1pre",
+ "the update displayVersion attribute" + MSG_SHOULD_EQUAL);
+ Assert.equal(bestUpdate.appVersion, "2.1a1pre",
+ "the update appVersion attribute" + MSG_SHOULD_EQUAL);
+ Assert.equal(bestUpdate.buildID, "20080811053724",
+ "the update buildID attribute" + MSG_SHOULD_EQUAL);
+ Assert.equal(bestUpdate.detailsURL, "http://details/",
+ "the update detailsURL attribute" + MSG_SHOULD_EQUAL);
+ Assert.ok(bestUpdate.showPrompt,
+ "the update showPrompt attribute" + MSG_SHOULD_EQUAL);
+ Assert.ok(bestUpdate.showNeverForVersion,
+ "the update showNeverForVersion attribute" + MSG_SHOULD_EQUAL);
+ Assert.equal(bestUpdate.promptWaitTime, "345600",
+ "the update promptWaitTime attribute" + MSG_SHOULD_EQUAL);
+ // The default and maximum value for backgroundInterval is 600
+ Assert.equal(bestUpdate.getProperty("backgroundInterval"), "600",
+ "the update backgroundInterval attribute" + MSG_SHOULD_EQUAL);
+ Assert.equal(bestUpdate.serviceURL, gURLData + gHTTPHandlerPath + "?force=1",
+ "the update serviceURL attribute" + MSG_SHOULD_EQUAL);
+ Assert.equal(bestUpdate.channel, "test_channel",
+ "the update channel attribute" + MSG_SHOULD_EQUAL);
+ Assert.ok(!bestUpdate.isCompleteUpdate,
+ "the update isCompleteUpdate attribute" + MSG_SHOULD_EQUAL);
+ Assert.ok(!bestUpdate.isSecurityUpdate,
+ "the update isSecurityUpdate attribute" + MSG_SHOULD_EQUAL);
+ // Check that installDate is within 10 seconds of the current date.
+ Assert.ok((Date.now() - bestUpdate.installDate) < 10000,
+ "the update installDate attribute should be within 10 seconds of " +
+ "the current time");
+ Assert.ok(!bestUpdate.statusText,
+ "the update statusText attribute" + MSG_SHOULD_EQUAL);
+ // nsIUpdate:state returns an empty string when no action has been performed
+ // on an available update
+ Assert.equal(bestUpdate.state, "",
+ "the update state attribute" + MSG_SHOULD_EQUAL);
+ Assert.equal(bestUpdate.errorCode, 0,
+ "the update errorCode attribute" + MSG_SHOULD_EQUAL);
+ Assert.equal(bestUpdate.patchCount, 2,
+ "the update patchCount attribute" + MSG_SHOULD_EQUAL);
+ // XXX TODO - test nsIUpdate:serialize
+
+ Assert.equal(bestUpdate.getProperty("custom1_attr"), "custom1 value",
+ "the update custom1_attr property" + MSG_SHOULD_EQUAL);
+ Assert.equal(bestUpdate.getProperty("custom2_attr"), "custom2 value",
+ "the update custom2_attr property" + MSG_SHOULD_EQUAL);
+
+ let patch = bestUpdate.getPatchAt(0);
+ Assert.equal(patch.type, "complete",
+ "the update patch type attribute" + MSG_SHOULD_EQUAL);
+ Assert.equal(patch.URL, "http://complete/",
+ "the update patch URL attribute" + MSG_SHOULD_EQUAL);
+ Assert.equal(patch.hashFunction, "SHA1",
+ "the update patch hashFunction attribute" + MSG_SHOULD_EQUAL);
+ Assert.equal(patch.hashValue, "98db9dad8e1d80eda7e1170d0187d6f53e477059",
+ "the update patch hashValue attribute" + MSG_SHOULD_EQUAL);
+ Assert.equal(patch.size, 9856459,
+ "the update patch size attribute" + MSG_SHOULD_EQUAL);
+ // The value for patch.state can be the string 'null' as a valid value. This
+ // is confusing if it returns null which is an invalid value since the test
+ // failure output will show a failure for null == null. To lessen the
+ // confusion first check that the typeof for patch.state is string.
+ Assert.equal(typeof patch.state, "string",
+ "the update patch state typeof value should equal |string|");
+ Assert.equal(patch.state, STATE_NONE,
+ "the update patch state attribute" + MSG_SHOULD_EQUAL);
+ Assert.ok(!patch.selected,
+ "the update patch selected attribute" + MSG_SHOULD_EQUAL);
+ // XXX TODO - test nsIUpdatePatch:serialize
+
+ patch = bestUpdate.getPatchAt(1);
+ Assert.equal(patch.type, "partial",
+ "the update patch type attribute" + MSG_SHOULD_EQUAL);
+ Assert.equal(patch.URL, "http://partial/",
+ "the update patch URL attribute" + MSG_SHOULD_EQUAL);
+ Assert.equal(patch.hashFunction, "SHA1",
+ "the update patch hashFunction attribute" + MSG_SHOULD_EQUAL);
+ Assert.equal(patch.hashValue, "e6678ca40ae7582316acdeddf3c133c9c8577de4",
+ "the update patch hashValue attribute" + MSG_SHOULD_EQUAL);
+ Assert.equal(patch.size, 1316138,
+ "the update patch size attribute" + MSG_SHOULD_EQUAL);
+ Assert.equal(patch.state, STATE_NONE,
+ "the update patch state attribute" + MSG_SHOULD_EQUAL);
+ Assert.ok(!patch.selected,
+ "the update patch selected attribute" + MSG_SHOULD_EQUAL);
+ // XXX TODO - test nsIUpdatePatch:serialize
+
+ run_test_pt03();
+}
+
+// Empty update xml (an empty xml file returns a root node name of parsererror)
+function run_test_pt03() {
+ gResponseBody = "<parsererror/>";
+ run_test_helper_pt1("testing empty update xml",
+ null, run_test_pt04);
+}
+
+// no updates available
+function run_test_pt04() {
+ gResponseBody = getRemoteUpdatesXMLString("");
+ run_test_helper_pt1("testing no updates available",
+ 0, run_test_pt05);
+}
+
+// one update available with two patches
+function run_test_pt05() {
+ let patches = getRemotePatchString("complete");
+ patches += getRemotePatchString("partial");
+ let updates = getRemoteUpdateString(patches);
+ gResponseBody = getRemoteUpdatesXMLString(updates);
+ run_test_helper_pt1("testing one update available",
+ 1, run_test_pt06);
+}
+
+// three updates available each with two patches
+function run_test_pt06() {
+ let patches = getRemotePatchString("complete");
+ patches += getRemotePatchString("partial");
+ let updates = getRemoteUpdateString(patches);
+ updates += getRemoteUpdateString(patches);
+ updates += getRemoteUpdateString(patches);
+ gResponseBody = getRemoteUpdatesXMLString(updates);
+ run_test_helper_pt1("testing three updates available",
+ 3, run_test_pt07);
+}
+
+// one update with complete and partial patches with size 0 specified in the
+// update xml
+function run_test_pt07() {
+ let patches = getRemotePatchString("complete", null, null, null, "0");
+ patches += getRemotePatchString("partial", null, null, null, "0");
+ let updates = getRemoteUpdateString(patches);
+ gResponseBody = getRemoteUpdatesXMLString(updates);
+ run_test_helper_pt1("testing one update with complete and partial " +
+ "patches with size 0", 0, run_test_pt08);
+}
+
+// one update with complete patch with size 0 specified in the update xml
+function run_test_pt08() {
+ let patches = getRemotePatchString("complete", null, null, null, "0");
+ let updates = getRemoteUpdateString(patches);
+ gResponseBody = getRemoteUpdatesXMLString(updates);
+ run_test_helper_pt1("testing one update with complete patch with size 0",
+ 0, run_test_pt9);
+}
+
+// one update with partial patch with size 0 specified in the update xml
+function run_test_pt9() {
+ let patches = getRemotePatchString("partial", null, null, null, "0");
+ let updates = getRemoteUpdateString(patches);
+ gResponseBody = getRemoteUpdatesXMLString(updates);
+ run_test_helper_pt1("testing one update with partial patch with size 0",
+ 0, run_test_pt10);
+}
+
+// check that updates for older versions of the application aren't selected
+function run_test_pt10() {
+ let patches = getRemotePatchString("complete");
+ patches += getRemotePatchString("partial");
+ let updates = getRemoteUpdateString(patches, "minor", null, null, "1.0pre");
+ updates += getRemoteUpdateString(patches, "minor", null, null, "1.0a");
+ gResponseBody = getRemoteUpdatesXMLString(updates);
+ run_test_helper_pt1("testing two updates older than the current version",
+ 2, check_test_pt10);
+}
+
+function check_test_pt10() {
+ let bestUpdate = gAUS.selectUpdate(gUpdates, gUpdateCount);
+ Assert.ok(!bestUpdate,
+ "there should be no update available");
+ run_test_pt11();
+}
+
+// check that updates for the current version of the application are selected
+function run_test_pt11() {
+ let patches = getRemotePatchString("complete");
+ patches += getRemotePatchString("partial");
+ let updates = getRemoteUpdateString(patches, "minor", null, "version 1.0");
+ gResponseBody = getRemoteUpdatesXMLString(updates);
+ run_test_helper_pt1("testing one update equal to the current version",
+ 1, check_test_pt11);
+}
+
+function check_test_pt11() {
+ let bestUpdate = gAUS.selectUpdate(gUpdates, gUpdateCount);
+ Assert.ok(!!bestUpdate,
+ "there should be one update available");
+ Assert.equal(bestUpdate.displayVersion, "version 1.0",
+ "the update displayVersion attribute" + MSG_SHOULD_EQUAL);
+
+ doTestFinish();
+}
diff --git a/toolkit/mozapps/update/tests/unit_aus_update/uiAutoPref.js b/toolkit/mozapps/update/tests/unit_aus_update/uiAutoPref.js
new file mode 100644
index 000000000..ee1c40bfd
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_aus_update/uiAutoPref.js
@@ -0,0 +1,75 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+Components.utils.import("resource://testing-common/MockRegistrar.jsm");
+
+const WindowWatcher = {
+ openWindow: function(aParent, aUrl, aName, aFeatures, aArgs) {
+ gCheckFunc();
+ },
+
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIWindowWatcher])
+};
+
+const WindowMediator = {
+ getMostRecentWindow: function(aWindowType) {
+ do_execute_soon(check_status);
+ return { getInterface: XPCOMUtils.generateQI([Ci.nsIDOMWindow]) };
+ },
+
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIWindowMediator])
+};
+
+function run_test() {
+ setupTestCommon();
+ // Calling do_get_profile prevents an error from being logged
+ do_get_profile();
+
+ debugDump("testing that an update download doesn't start when the " +
+ PREF_APP_UPDATE_AUTO + " preference is false");
+
+ Services.prefs.setBoolPref(PREF_APP_UPDATE_AUTO, false);
+ Services.prefs.setBoolPref(PREF_APP_UPDATE_SILENT, false);
+
+ start_httpserver();
+ setUpdateURL(gURLData + gHTTPHandlerPath);
+ standardInit();
+
+ let windowWatcherCID =
+ MockRegistrar.register("@mozilla.org/embedcomp/window-watcher;1",
+ WindowWatcher);
+ let windowMediatorCID =
+ MockRegistrar.register("@mozilla.org/appshell/window-mediator;1",
+ WindowMediator);
+ do_register_cleanup(() => {
+ MockRegistrar.unregister(windowWatcherCID);
+ MockRegistrar.unregister(windowMediatorCID);
+ });
+
+ gCheckFunc = check_showUpdateAvailable;
+ let patches = getRemotePatchString("complete");
+ let updates = getRemoteUpdateString(patches, "minor", null, null, "1.0");
+ gResponseBody = getRemoteUpdatesXMLString(updates);
+ gAUS.notify(null);
+}
+
+function check_status() {
+ let status = readStatusFile();
+ Assert.notEqual(status, STATE_DOWNLOADING,
+ "the update state" + MSG_SHOULD_EQUAL);
+
+ // Pause the download and reload the Update Manager with an empty update so
+ // the Application Update Service doesn't write the update xml files during
+ // xpcom-shutdown which will leave behind the test directory.
+ gAUS.pauseDownload();
+ writeUpdatesToXMLFile(getLocalUpdatesXMLString(""), true);
+ writeUpdatesToXMLFile(getLocalUpdatesXMLString(""), false);
+ reloadUpdateManagerData();
+
+ do_execute_soon(doTestFinish);
+}
+
+function check_showUpdateAvailable() {
+ do_throw("showUpdateAvailable should not have called openWindow!");
+}
diff --git a/toolkit/mozapps/update/tests/unit_aus_update/uiSilentPref.js b/toolkit/mozapps/update/tests/unit_aus_update/uiSilentPref.js
new file mode 100644
index 000000000..25110be8c
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_aus_update/uiSilentPref.js
@@ -0,0 +1,76 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+Components.utils.import("resource://testing-common/MockRegistrar.jsm");
+
+/**
+ * Test that nsIUpdatePrompt doesn't display UI for showUpdateAvailable and
+ * showUpdateError when the app.update.silent preference is true.
+ */
+
+const WindowWatcher = {
+ openWindow: function(aParent, aUrl, aName, aFeatures, aArgs) {
+ gCheckFunc();
+ },
+
+ getNewPrompter: function(aParent) {
+ gCheckFunc();
+ },
+
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIWindowWatcher])
+};
+
+function run_test() {
+ setupTestCommon();
+
+ debugDump("testing nsIUpdatePrompt notifications should not be seen " +
+ "when the " + PREF_APP_UPDATE_SILENT + " preference is true");
+
+ Services.prefs.setBoolPref(PREF_APP_UPDATE_SILENT, true);
+
+ let windowWatcherCID =
+ MockRegistrar.register("@mozilla.org/embedcomp/window-watcher;1",
+ WindowWatcher);
+ do_register_cleanup(() => {
+ MockRegistrar.unregister(windowWatcherCID);
+ });
+
+ standardInit();
+
+ debugDump("testing showUpdateAvailable should not call openWindow");
+ writeUpdatesToXMLFile(getLocalUpdatesXMLString(""), false);
+ let patches = getLocalPatchString(null, null, null, null, null, null,
+ STATE_FAILED);
+ let updates = getLocalUpdateString(patches);
+ writeUpdatesToXMLFile(getLocalUpdatesXMLString(updates), true);
+ writeStatusFile(STATE_FAILED);
+ reloadUpdateManagerData();
+
+ gCheckFunc = check_showUpdateAvailable;
+ let update = gUpdateManager.activeUpdate;
+ gUP.showUpdateAvailable(update);
+ // Report a successful check after the call to showUpdateAvailable since it
+ // didn't throw and otherwise it would report no tests run.
+ Assert.ok(true,
+ "calling showUpdateAvailable should not attempt to open a window");
+
+ debugDump("testing showUpdateError should not call getNewPrompter");
+ gCheckFunc = check_showUpdateError;
+ update.errorCode = WRITE_ERROR;
+ gUP.showUpdateError(update);
+ // Report a successful check after the call to showUpdateError since it
+ // didn't throw and otherwise it would report no tests run.
+ Assert.ok(true,
+ "calling showUpdateError should not attempt to open a window");
+
+ doTestFinish();
+}
+
+function check_showUpdateAvailable() {
+ do_throw("showUpdateAvailable should not have called openWindow!");
+}
+
+function check_showUpdateError() {
+ do_throw("showUpdateError should not have seen getNewPrompter!");
+}
diff --git a/toolkit/mozapps/update/tests/unit_aus_update/uiUnsupportedAlreadyNotified.js b/toolkit/mozapps/update/tests/unit_aus_update/uiUnsupportedAlreadyNotified.js
new file mode 100644
index 000000000..5b694ed30
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_aus_update/uiUnsupportedAlreadyNotified.js
@@ -0,0 +1,74 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+Cu.import("resource://testing-common/MockRegistrar.jsm");
+
+const WindowWatcher = {
+ openWindow: function(aParent, aUrl, aName, aFeatures, aArgs) {
+ check_showUpdateAvailable();
+ },
+
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIWindowWatcher])
+};
+
+const WindowMediator = {
+ getMostRecentWindow: function(aWindowType) {
+ return null;
+ },
+
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIWindowMediator])
+};
+
+function run_test() {
+ setupTestCommon();
+
+ debugDump("testing nsIUpdatePrompt notifications should not be displayed " +
+ "when showUpdateAvailable is called for an unsupported system " +
+ "update when the unsupported notification has already been " +
+ "shown (bug 843497)");
+
+ start_httpserver();
+ setUpdateURL(gURLData + gHTTPHandlerPath);
+ standardInit();
+
+ let windowWatcherCID =
+ MockRegistrar.register("@mozilla.org/embedcomp/window-watcher;1",
+ WindowWatcher);
+ let windowMediatorCID =
+ MockRegistrar.register("@mozilla.org/appshell/window-mediator;1",
+ WindowMediator);
+ do_register_cleanup(() => {
+ MockRegistrar.unregister(windowWatcherCID);
+ MockRegistrar.unregister(windowMediatorCID);
+ });
+
+ Services.prefs.setBoolPref(PREF_APP_UPDATE_SILENT, false);
+ Services.prefs.setBoolPref(PREF_APP_UPDATE_NOTIFIEDUNSUPPORTED, true);
+ // This preference is used to determine when the background update check has
+ // completed since a successful check will clear the preference.
+ Services.prefs.setIntPref(PREF_APP_UPDATE_BACKGROUNDERRORS, 1);
+
+ gResponseBody = getRemoteUpdatesXMLString(" <update type=\"major\" " +
+ "name=\"Unsupported Update\" " +
+ "unsupported=\"true\" " +
+ "detailsURL=\"" + URL_HOST +
+ "\"></update>\n");
+ gAUS.notify(null);
+ do_execute_soon(check_test);
+}
+
+function check_test() {
+ if (Services.prefs.prefHasUserValue(PREF_APP_UPDATE_BACKGROUNDERRORS)) {
+ do_execute_soon(check_test);
+ return;
+ }
+ Assert.ok(true,
+ PREF_APP_UPDATE_BACKGROUNDERRORS + " preference should not exist");
+
+ stop_httpserver(doTestFinish);
+}
+
+function check_showUpdateAvailable() {
+ do_throw("showUpdateAvailable should not have called openWindow!");
+}
diff --git a/toolkit/mozapps/update/tests/unit_aus_update/updateManagerXML.js b/toolkit/mozapps/update/tests/unit_aus_update/updateManagerXML.js
new file mode 100644
index 000000000..e46469455
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_aus_update/updateManagerXML.js
@@ -0,0 +1,177 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+function run_test() {
+ setupTestCommon();
+
+ debugDump("testing addition of a successful update to " + FILE_UPDATES_XML +
+ " and verification of update properties including the format " +
+ "prior to bug 530872");
+
+ setUpdateChannel("test_channel");
+
+ // This test expects that the app.update.download.backgroundInterval
+ // preference doesn't already exist.
+ Services.prefs.deleteBranch("app.update.download.backgroundInterval");
+
+ // XXXrstrong - not specifying a detailsURL will cause a leak due to bug 470244
+ // and until bug 470244 is fixed this will not test the value for detailsURL
+ // when it isn't specified in the update xml.
+ let patches = getLocalPatchString("partial", "http://partial/", "SHA256",
+ "cd43", "86", "true", STATE_PENDING);
+ let updates = getLocalUpdateString(patches, "major", "New", "version 4",
+ "4.0", "20070811053724",
+ "http://details1/",
+ "http://service1/", "1238441300314",
+ "test status text", "false",
+ "test_channel", "true", "true", "true",
+ "345600", "300", "3.0",
+ "custom1_attr=\"custom1 value\"",
+ "custom2_attr=\"custom2 value\"");
+ writeUpdatesToXMLFile(getLocalUpdatesXMLString(updates), true);
+ writeStatusFile(STATE_SUCCEEDED);
+
+ patches = getLocalPatchString("complete", "http://complete/", "SHA1", "6232",
+ "75", "true", STATE_FAILED);
+ updates = getLocalUpdateString(patches, "major", "Existing", null, "3.0",
+ null,
+ "http://details2/",
+ "http://service2/", null,
+ getString("patchApplyFailure"), "true",
+ "test_channel", "false", null, null, "691200",
+ null, null,
+ "custom3_attr=\"custom3 value\"",
+ "custom4_attr=\"custom4 value\"");
+ writeUpdatesToXMLFile(getLocalUpdatesXMLString(updates), false);
+
+ standardInit();
+
+ Assert.ok(!gUpdateManager.activeUpdate,
+ "the update manager activeUpdate attribute" + MSG_SHOULD_EQUAL);
+ Assert.equal(gUpdateManager.updateCount, 2,
+ "the update manager updateCount attribute" + MSG_SHOULD_EQUAL);
+
+ debugDump("checking the activeUpdate properties");
+ let update = gUpdateManager.getUpdateAt(0).QueryInterface(Ci.nsIPropertyBag);
+ Assert.equal(update.state, STATE_SUCCEEDED,
+ "the update state attribute" + MSG_SHOULD_EQUAL);
+ Assert.equal(update.type, "major",
+ "the update type attribute" + MSG_SHOULD_EQUAL);
+ Assert.equal(update.name, "New",
+ "the update name attribute" + MSG_SHOULD_EQUAL);
+ Assert.equal(update.displayVersion, "version 4",
+ "the update displayVersion attribute" + MSG_SHOULD_EQUAL);
+ Assert.equal(update.appVersion, "4.0",
+ "the update appVersion attribute" + MSG_SHOULD_EQUAL);
+ Assert.equal(update.buildID, "20070811053724",
+ "the update buildID attribute" + MSG_SHOULD_EQUAL);
+ Assert.equal(update.detailsURL, "http://details1/",
+ "the update detailsURL attribute" + MSG_SHOULD_EQUAL);
+ Assert.equal(update.serviceURL, "http://service1/",
+ "the update serviceURL attribute" + MSG_SHOULD_EQUAL);
+ Assert.equal(update.installDate, "1238441300314",
+ "the update installDate attribute" + MSG_SHOULD_EQUAL);
+ // statusText is updated
+ Assert.equal(update.statusText, getString("installSuccess"),
+ "the update statusText attribute" + MSG_SHOULD_EQUAL);
+ Assert.ok(!update.isCompleteUpdate,
+ "the update isCompleteUpdate attribute" + MSG_SHOULD_EQUAL);
+ Assert.equal(update.channel, "test_channel",
+ "the update channel attribute" + MSG_SHOULD_EQUAL);
+ Assert.ok(!!update.showPrompt,
+ "the update showPrompt attribute" + MSG_SHOULD_EQUAL);
+ Assert.ok(!!update.showNeverForVersion,
+ "the update showNeverForVersion attribute" + MSG_SHOULD_EQUAL);
+ Assert.equal(update.promptWaitTime, "345600",
+ "the update promptWaitTime attribute" + MSG_SHOULD_EQUAL);
+ Assert.equal(update.getProperty("backgroundInterval"), "300",
+ "the update backgroundInterval attribute" + MSG_SHOULD_EQUAL);
+ Assert.equal(update.previousAppVersion, "3.0",
+ "the update previousAppVersion attribute" + MSG_SHOULD_EQUAL);
+ // Custom attributes
+ Assert.equal(update.getProperty("custom1_attr"), "custom1 value",
+ "the update custom1_attr property" + MSG_SHOULD_EQUAL);
+ Assert.equal(update.getProperty("custom2_attr"), "custom2 value",
+ "the update custom2_attr property" + MSG_SHOULD_EQUAL);
+
+ debugDump("checking the activeUpdate patch properties");
+ let patch = update.selectedPatch;
+ Assert.equal(patch.type, "partial",
+ "the update patch type attribute" + MSG_SHOULD_EQUAL);
+ Assert.equal(patch.URL, "http://partial/",
+ "the update patch URL attribute" + MSG_SHOULD_EQUAL);
+ Assert.equal(patch.hashFunction, "SHA256",
+ "the update patch hashFunction attribute" + MSG_SHOULD_EQUAL);
+ Assert.equal(patch.hashValue, "cd43",
+ "the update patch hashValue attribute" + MSG_SHOULD_EQUAL);
+ Assert.equal(patch.size, "86",
+ "the update patch size attribute" + MSG_SHOULD_EQUAL);
+ Assert.ok(!!patch.selected,
+ "the update patch selected attribute" + MSG_SHOULD_EQUAL);
+ Assert.equal(patch.state, STATE_SUCCEEDED,
+ "the update patch state attribute" + MSG_SHOULD_EQUAL);
+
+ debugDump("checking the first update properties");
+ update = gUpdateManager.getUpdateAt(1).QueryInterface(Ci.nsIPropertyBag);
+ Assert.equal(update.state, STATE_FAILED,
+ "the update state attribute" + MSG_SHOULD_EQUAL);
+ Assert.equal(update.name, "Existing",
+ "the update name attribute" + MSG_SHOULD_EQUAL);
+ Assert.equal(update.type, "major",
+ "the update type attribute" + MSG_SHOULD_EQUAL);
+ Assert.equal(update.displayVersion, "3.0",
+ "the update displayVersion attribute" + MSG_SHOULD_EQUAL);
+ Assert.equal(update.appVersion, "3.0",
+ "the update appVersion attribute" + MSG_SHOULD_EQUAL);
+ Assert.equal(update.detailsURL, "http://details2/",
+ "the update detailsURL attribute" + MSG_SHOULD_EQUAL);
+ Assert.equal(update.serviceURL, "http://service2/",
+ "the update serviceURL attribute" + MSG_SHOULD_EQUAL);
+ Assert.equal(update.installDate, "1238441400314",
+ "the update installDate attribute" + MSG_SHOULD_EQUAL);
+ Assert.equal(update.statusText, getString("patchApplyFailure"),
+ "the update statusText attribute" + MSG_SHOULD_EQUAL);
+ Assert.equal(update.buildID, "20080811053724",
+ "the update buildID attribute" + MSG_SHOULD_EQUAL);
+ Assert.ok(!!update.isCompleteUpdate,
+ "the update isCompleteUpdate attribute" + MSG_SHOULD_EQUAL);
+ Assert.equal(update.channel, "test_channel",
+ "the update channel attribute" + MSG_SHOULD_EQUAL);
+ Assert.ok(!update.showPrompt,
+ "the update showPrompt attribute" + MSG_SHOULD_EQUAL);
+ Assert.ok(!update.showNeverForVersion,
+ "the update showNeverForVersion attribute" + MSG_SHOULD_EQUAL);
+ Assert.equal(update.promptWaitTime, "691200",
+ "the update promptWaitTime attribute" + MSG_SHOULD_EQUAL);
+ // The default and maximum value for backgroundInterval is 600
+ Assert.equal(update.getProperty("backgroundInterval"), "600",
+ "the update backgroundInterval attribute" + MSG_SHOULD_EQUAL);
+ Assert.equal(update.previousAppVersion, null,
+ "the update previousAppVersion attribute" + MSG_SHOULD_EQUAL);
+ // Custom attributes
+ Assert.equal(update.getProperty("custom3_attr"), "custom3 value",
+ "the update custom3_attr property" + MSG_SHOULD_EQUAL);
+ Assert.equal(update.getProperty("custom4_attr"), "custom4 value",
+ "the update custom4_attr property" + MSG_SHOULD_EQUAL);
+
+ debugDump("checking the first update patch properties");
+ patch = update.selectedPatch;
+ Assert.equal(patch.type, "complete",
+ "the update patch type attribute" + MSG_SHOULD_EQUAL);
+ Assert.equal(patch.URL, "http://complete/",
+ "the update patch URL attribute" + MSG_SHOULD_EQUAL);
+ Assert.equal(patch.hashFunction, "SHA1",
+ "the update patch hashFunction attribute" + MSG_SHOULD_EQUAL);
+ Assert.equal(patch.hashValue, "6232",
+ "the update patch hashValue attribute" + MSG_SHOULD_EQUAL);
+ Assert.equal(patch.size, "75",
+ "the update patch size attribute" + MSG_SHOULD_EQUAL);
+ Assert.ok(!!patch.selected,
+ "the update patch selected attribute" + MSG_SHOULD_EQUAL);
+ Assert.equal(patch.state, STATE_FAILED,
+ "the update patch state attribute" + MSG_SHOULD_EQUAL);
+
+ doTestFinish();
+}
diff --git a/toolkit/mozapps/update/tests/unit_aus_update/urlConstruction.js b/toolkit/mozapps/update/tests/unit_aus_update/urlConstruction.js
new file mode 100644
index 000000000..db7d90094
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_aus_update/urlConstruction.js
@@ -0,0 +1,305 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+/* General URL Construction Tests */
+
+const URL_PREFIX = URL_HOST + "/";
+
+var gAppInfo;
+
+// Since gUpdateChecker.checkForUpdates uses XMLHttpRequest and XMLHttpRequest
+// can be slow it combines the checks whenever possible.
+function run_test() {
+ // This test needs access to omni.ja to read the update.locale file so don't
+ // use a custom directory for the application directory.
+ gUseTestAppDir = false;
+ setupTestCommon();
+
+ standardInit();
+ gAppInfo = Cc["@mozilla.org/xre/app-info;1"].
+ getService(Ci.nsIXULAppInfo).
+ QueryInterface(Ci.nsIXULRuntime);
+ do_execute_soon(run_test_pt1);
+}
+
+
+// url constructed with:
+// %PRODUCT%
+// %VERSION%
+// %BUILD_ID%
+// %BUILD_TARGET%
+// %LOCALE%
+// %CHANNEL%
+// %PLATFORM_VERSION%
+// %OS_VERSION%
+// %SYSTEM_CAPABILITIES%
+// %DISTRIBUTION%
+// %DISTRIBUTION_VERSION%
+function run_test_pt1() {
+ gCheckFunc = check_test_pt1;
+ // The code that gets the locale accesses the profile which is only available
+ // after calling do_get_profile in xpcshell tests. This prevents an error from
+ // being logged.
+ do_get_profile();
+
+ setUpdateChannel("test_channel");
+ gDefaultPrefBranch.setCharPref(PREF_DISTRIBUTION_ID, "test_distro");
+ gDefaultPrefBranch.setCharPref(PREF_DISTRIBUTION_VERSION, "test_distro_version");
+
+ let url = URL_PREFIX + "%PRODUCT%/%VERSION%/%BUILD_ID%/%BUILD_TARGET%/" +
+ "%LOCALE%/%CHANNEL%/%PLATFORM_VERSION%/%OS_VERSION%/" +
+ "%SYSTEM_CAPABILITIES%/%DISTRIBUTION%/%DISTRIBUTION_VERSION%/" +
+ "updates.xml";
+ debugDump("testing url construction - url: " + url);
+ setUpdateURL(url);
+ try {
+ gUpdateChecker.checkForUpdates(updateCheckListener, true);
+ } catch (e) {
+ debugDump("The following error is most likely due to a missing " +
+ "update.locale file");
+ do_throw(e);
+ }
+}
+
+function check_test_pt1() {
+ let url = URL_PREFIX + gAppInfo.name + "/" + gAppInfo.version + "/" +
+ gAppInfo.appBuildID + "/" + gAppInfo.OS + "_" + getABI() + "/" +
+ INSTALL_LOCALE + "/test_channel/" + gAppInfo.platformVersion + "/" +
+ getOSVersion() + "/" + getSystemCapabilities() +
+ "/test_distro/test_distro_version/updates.xml?force=1";
+ // Log the urls since Assert.equal won't print the entire urls to the log.
+ if (gRequestURL != url) {
+ logTestInfo("expected url: " + url);
+ logTestInfo("returned url: " + gRequestURL);
+ }
+ Assert.equal(gRequestURL, url,
+ "the url" + MSG_SHOULD_EQUAL);
+ run_test_pt2();
+}
+
+// url constructed with:
+// %CHANNEL% with distribution partners
+// %CUSTOM% parameter
+// force param when there already is a param - bug 454357
+function run_test_pt2() {
+ gCheckFunc = check_test_pt2;
+ let url = URL_PREFIX + "%CHANNEL%/updates.xml?custom=%CUSTOM%";
+ debugDump("testing url constructed with %CHANNEL% - " + url);
+ setUpdateURL(url);
+ gDefaultPrefBranch.setCharPref(PREFBRANCH_APP_PARTNER + "test_partner1",
+ "test_partner1");
+ gDefaultPrefBranch.setCharPref(PREFBRANCH_APP_PARTNER + "test_partner2",
+ "test_partner2");
+ Services.prefs.setCharPref("app.update.custom", "custom");
+ gUpdateChecker.checkForUpdates(updateCheckListener, true);
+}
+
+function check_test_pt2() {
+ let url = URL_PREFIX + "test_channel-cck-test_partner1-test_partner2/" +
+ "updates.xml?custom=custom&force=1";
+ Assert.equal(gRequestURL, url,
+ "the url" + MSG_SHOULD_EQUAL);
+ doTestFinish();
+}
+
+function getABI() {
+ let abi;
+ try {
+ abi = gAppInfo.XPCOMABI;
+ } catch (e) {
+ do_throw("nsIXULAppInfo:XPCOMABI not defined\n");
+ }
+
+ if (IS_MACOSX) {
+ // Mac universal build should report a different ABI than either macppc
+ // or mactel. This is necessary since nsUpdateService.js will set the ABI to
+ // Universal-gcc3 for Mac universal builds.
+ let macutils = Cc["@mozilla.org/xpcom/mac-utils;1"].
+ getService(Ci.nsIMacUtils);
+
+ if (macutils.isUniversalBinary) {
+ abi += "-u-" + macutils.architecturesInBinary;
+ }
+ } else if (IS_WIN) {
+ // Windows build should report the CPU architecture that it's running on.
+ abi += "-" + getProcArchitecture();
+ }
+ return abi;
+}
+
+function getOSVersion() {
+ let sysInfo = Cc["@mozilla.org/system-info;1"].getService(Ci.nsIPropertyBag2);
+ let osVersion = sysInfo.getProperty("name") + " " + sysInfo.getProperty("version");
+
+ if (IS_WIN) {
+ try {
+ let servicePack = getServicePack();
+ osVersion += "." + servicePack;
+ } catch (e) {
+ do_throw("Failure obtaining service pack: " + e);
+ }
+
+ if ("5.0" === sysInfo.getProperty("version")) { // Win2K
+ osVersion += " (unknown)";
+ } else {
+ try {
+ osVersion += " (" + getProcArchitecture() + ")";
+ } catch (e) {
+ do_throw("Failed to obtain processor architecture: " + e);
+ }
+ }
+ }
+
+ if (osVersion) {
+ try {
+ osVersion += " (" + sysInfo.getProperty("secondaryLibrary") + ")";
+ } catch (e) {
+ // Not all platforms have a secondary widget library, so an error is
+ // nothing to worry about.
+ }
+ osVersion = encodeURIComponent(osVersion);
+ }
+ return osVersion;
+}
+
+function getServicePack() {
+ // NOTE: This function is a helper function and not a test. Thus,
+ // it uses throw() instead of do_throw(). Any tests that use this function
+ // should catch exceptions thrown in this function and deal with them
+ // appropriately (usually by calling do_throw).
+ const BYTE = ctypes.uint8_t;
+ const WORD = ctypes.uint16_t;
+ const DWORD = ctypes.uint32_t;
+ const WCHAR = ctypes.char16_t;
+ const BOOL = ctypes.int;
+
+ // This structure is described at:
+ // http://msdn.microsoft.com/en-us/library/ms724833%28v=vs.85%29.aspx
+ const SZCSDVERSIONLENGTH = 128;
+ const OSVERSIONINFOEXW = new ctypes.StructType('OSVERSIONINFOEXW',
+ [
+ {dwOSVersionInfoSize: DWORD},
+ {dwMajorVersion: DWORD},
+ {dwMinorVersion: DWORD},
+ {dwBuildNumber: DWORD},
+ {dwPlatformId: DWORD},
+ {szCSDVersion: ctypes.ArrayType(WCHAR, SZCSDVERSIONLENGTH)},
+ {wServicePackMajor: WORD},
+ {wServicePackMinor: WORD},
+ {wSuiteMask: WORD},
+ {wProductType: BYTE},
+ {wReserved: BYTE}
+ ]);
+
+ let kernel32 = ctypes.open("kernel32");
+ try {
+ let GetVersionEx = kernel32.declare("GetVersionExW",
+ ctypes.default_abi,
+ BOOL,
+ OSVERSIONINFOEXW.ptr);
+ let winVer = OSVERSIONINFOEXW();
+ winVer.dwOSVersionInfoSize = OSVERSIONINFOEXW.size;
+
+ if (0 === GetVersionEx(winVer.address())) {
+ // Using "throw" instead of "do_throw" (see NOTE above)
+ throw ("Failure in GetVersionEx (returned 0)");
+ }
+
+ return winVer.wServicePackMajor + "." + winVer.wServicePackMinor;
+ } finally {
+ kernel32.close();
+ }
+}
+
+function getProcArchitecture() {
+ // NOTE: This function is a helper function and not a test. Thus,
+ // it uses throw() instead of do_throw(). Any tests that use this function
+ // should catch exceptions thrown in this function and deal with them
+ // appropriately (usually by calling do_throw).
+ const WORD = ctypes.uint16_t;
+ const DWORD = ctypes.uint32_t;
+
+ // This structure is described at:
+ // http://msdn.microsoft.com/en-us/library/ms724958%28v=vs.85%29.aspx
+ const SYSTEM_INFO = new ctypes.StructType('SYSTEM_INFO',
+ [
+ {wProcessorArchitecture: WORD},
+ {wReserved: WORD},
+ {dwPageSize: DWORD},
+ {lpMinimumApplicationAddress: ctypes.voidptr_t},
+ {lpMaximumApplicationAddress: ctypes.voidptr_t},
+ {dwActiveProcessorMask: DWORD.ptr},
+ {dwNumberOfProcessors: DWORD},
+ {dwProcessorType: DWORD},
+ {dwAllocationGranularity: DWORD},
+ {wProcessorLevel: WORD},
+ {wProcessorRevision: WORD}
+ ]);
+
+ let kernel32 = ctypes.open("kernel32");
+ try {
+ let GetNativeSystemInfo = kernel32.declare("GetNativeSystemInfo",
+ ctypes.default_abi,
+ ctypes.void_t,
+ SYSTEM_INFO.ptr);
+ let sysInfo = SYSTEM_INFO();
+ // Default to unknown
+ sysInfo.wProcessorArchitecture = 0xffff;
+
+ GetNativeSystemInfo(sysInfo.address());
+ switch (sysInfo.wProcessorArchitecture) {
+ case 9:
+ return "x64";
+ case 6:
+ return "IA64";
+ case 0:
+ return "x86";
+ default:
+ // Using "throw" instead of "do_throw" (see NOTE above)
+ throw ("Unknown architecture returned from GetNativeSystemInfo: " + sysInfo.wProcessorArchitecture);
+ }
+ } finally {
+ kernel32.close();
+ }
+}
+
+/**
+ * Provides system capability information for application update though it may
+ * be used by other consumers.
+ */
+function getSystemCapabilities() {
+ if (IS_WIN) {
+ const PF_MMX_INSTRUCTIONS_AVAILABLE = 3; // MMX
+ const PF_XMMI_INSTRUCTIONS_AVAILABLE = 6; // SSE
+ const PF_XMMI64_INSTRUCTIONS_AVAILABLE = 10; // SSE2
+ const PF_SSE3_INSTRUCTIONS_AVAILABLE = 13; // SSE3
+
+ let lib = ctypes.open("kernel32.dll");
+ let IsProcessorFeaturePresent = lib.declare("IsProcessorFeaturePresent",
+ ctypes.winapi_abi,
+ ctypes.int32_t, /* success */
+ ctypes.uint32_t); /* DWORD */
+ let instructionSet = "unknown";
+ try {
+ if (IsProcessorFeaturePresent(PF_SSE3_INSTRUCTIONS_AVAILABLE)) {
+ instructionSet = "SSE3";
+ } else if (IsProcessorFeaturePresent(PF_XMMI64_INSTRUCTIONS_AVAILABLE)) {
+ instructionSet = "SSE2";
+ } else if (IsProcessorFeaturePresent(PF_XMMI_INSTRUCTIONS_AVAILABLE)) {
+ instructionSet = "SSE";
+ } else if (IsProcessorFeaturePresent(PF_MMX_INSTRUCTIONS_AVAILABLE)) {
+ instructionSet = "MMX";
+ }
+ } catch (e) {
+ Cu.reportError("Error getting processor instruction set. " +
+ "Exception: " + e);
+ }
+
+ lib.close();
+ return instructionSet;
+ }
+
+ return "NA";
+}
diff --git a/toolkit/mozapps/update/tests/unit_aus_update/xpcshell.ini b/toolkit/mozapps/update/tests/unit_aus_update/xpcshell.ini
new file mode 100644
index 000000000..0d2205046
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_aus_update/xpcshell.ini
@@ -0,0 +1,27 @@
+; This Source Code Form is subject to the terms of the Mozilla Public
+; License, v. 2.0. If a copy of the MPL was not distributed with this
+; file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+[DEFAULT]
+tags = appupdate
+head = head_update.js
+tail =
+
+[canCheckForAndCanApplyUpdates.js]
+[urlConstruction.js]
+[updateManagerXML.js]
+[remoteUpdateXML.js]
+[downloadAndHashCheckMar.js]
+[cleanupDownloadingForOlderAppVersion.js]
+[cleanupDownloadingForDifferentChannel.js]
+[cleanupDownloadingForSameVersionAndBuildID.js]
+[cleanupDownloadingIncorrectStatus.js]
+[cleanupPendingVersionFileIncorrectStatus.js]
+[cleanupSuccessLogMove.js]
+[cleanupSuccessLogsFIFO.js]
+[downloadInterruptedRecovery.js]
+[downloadResumeForSameAppVersion.js]
+[downloadCompleteAfterPartialFailure.js]
+[uiSilentPref.js]
+[uiUnsupportedAlreadyNotified.js]
+[uiAutoPref.js]
diff --git a/toolkit/mozapps/update/tests/unit_base_updater/.eslintrc.js b/toolkit/mozapps/update/tests/unit_base_updater/.eslintrc.js
new file mode 100644
index 000000000..d35787cd2
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_base_updater/.eslintrc.js
@@ -0,0 +1,7 @@
+"use strict";
+
+module.exports = {
+ "extends": [
+ "../../../../../testing/xpcshell/xpcshell.eslintrc.js"
+ ]
+};
diff --git a/toolkit/mozapps/update/tests/unit_base_updater/head_update.js b/toolkit/mozapps/update/tests/unit_base_updater/head_update.js
new file mode 100644
index 000000000..9715c5828
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_base_updater/head_update.js
@@ -0,0 +1,8 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+const IS_SERVICE_TEST = false;
+
+/* import-globals-from ../data/xpcshellUtilsAUS.js */
+load("../data/xpcshellUtilsAUS.js");
diff --git a/toolkit/mozapps/update/tests/unit_base_updater/invalidArgCallbackFileNotInInstallDirFailure.js b/toolkit/mozapps/update/tests/unit_base_updater/invalidArgCallbackFileNotInInstallDirFailure.js
new file mode 100644
index 000000000..f3f767394
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_base_updater/invalidArgCallbackFileNotInInstallDirFailure.js
@@ -0,0 +1,37 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+/* Callback file not in install directory or a sub-directory of the install
+ directory failure */
+
+const STATE_AFTER_RUNUPDATE = STATE_FAILED_INVALID_CALLBACK_DIR_ERROR;
+
+function run_test() {
+ if (!setupTestCommon()) {
+ return;
+ }
+ gTestFiles = gTestFilesCompleteSuccess;
+ gTestDirs = gTestDirsCompleteSuccess;
+ setTestFilesAndDirsForFailure();
+ setupUpdaterTest(FILE_COMPLETE_MAR, false);
+}
+
+/**
+ * Called after the call to setupUpdaterTest finishes.
+ */
+function setupUpdaterTestFinished() {
+ let path = getTestDirFile(FILE_HELPER_BIN).path;
+ runUpdate(STATE_AFTER_RUNUPDATE, false, 1, true, null, null, null, path);
+}
+
+/**
+ * Called after the call to runUpdateUsingUpdater finishes.
+ */
+function runUpdateFinished() {
+ standardInit();
+ checkPostUpdateRunningFile(false);
+ checkFilesAfterUpdateFailure(getApplyDirFile);
+ waitForFilesInUse();
+}
diff --git a/toolkit/mozapps/update/tests/unit_base_updater/invalidArgCallbackFilePathTooLongFailure.js b/toolkit/mozapps/update/tests/unit_base_updater/invalidArgCallbackFilePathTooLongFailure.js
new file mode 100644
index 000000000..969f84f9d
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_base_updater/invalidArgCallbackFilePathTooLongFailure.js
@@ -0,0 +1,45 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+/* Too long callback file path failure test */
+
+const STATE_AFTER_RUNUPDATE = STATE_FAILED_INVALID_CALLBACK_PATH_ERROR;
+
+function run_test() {
+ if (!setupTestCommon()) {
+ return;
+ }
+ gTestFiles = gTestFilesCompleteSuccess;
+ gTestDirs = gTestDirsCompleteSuccess;
+ setTestFilesAndDirsForFailure();
+ setupUpdaterTest(FILE_COMPLETE_MAR, false);
+}
+
+/**
+ * Called after the call to setupUpdaterTest finishes.
+ */
+function setupUpdaterTestFinished() {
+ let path = "123456789";
+ if (IS_WIN) {
+ path = "\\" + path;
+ path = path.repeat(30); // 300 characters
+ path = "C:" + path;
+ } else {
+ path = "/" + path;
+ path = path.repeat(1000); // 10000 characters
+ }
+
+ runUpdate(STATE_AFTER_RUNUPDATE, false, 1, true, null, null, null, path);
+}
+
+/**
+ * Called after the call to runUpdateUsingUpdater finishes.
+ */
+function runUpdateFinished() {
+ standardInit();
+ checkPostUpdateRunningFile(false);
+ checkFilesAfterUpdateFailure(getApplyDirFile);
+ waitForFilesInUse();
+}
diff --git a/toolkit/mozapps/update/tests/unit_base_updater/invalidArgInstallDirPathTooLongFailure.js b/toolkit/mozapps/update/tests/unit_base_updater/invalidArgInstallDirPathTooLongFailure.js
new file mode 100644
index 000000000..70e03646a
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_base_updater/invalidArgInstallDirPathTooLongFailure.js
@@ -0,0 +1,47 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+/* Too long install directory path failure test */
+
+const STATE_AFTER_RUNUPDATE =
+ IS_SERVICE_TEST ? STATE_FAILED_SERVICE_INVALID_INSTALL_DIR_PATH_ERROR
+ : STATE_FAILED_INVALID_INSTALL_DIR_PATH_ERROR;
+
+function run_test() {
+ if (!setupTestCommon()) {
+ return;
+ }
+ gTestFiles = gTestFilesCompleteSuccess;
+ gTestDirs = gTestDirsCompleteSuccess;
+ setTestFilesAndDirsForFailure();
+ setupUpdaterTest(FILE_COMPLETE_MAR, false);
+}
+
+/**
+ * Called after the call to setupUpdaterTest finishes.
+ */
+function setupUpdaterTestFinished() {
+ let path = "123456789";
+ if (IS_WIN) {
+ path = "\\" + path;
+ path = path.repeat(30); // 300 characters
+ path = "C:" + path;
+ } else {
+ path = "/" + path;
+ path = path.repeat(1000); // 10000 characters
+ }
+
+ runUpdate(STATE_AFTER_RUNUPDATE, false, 1, true, null, path, null, null);
+}
+
+/**
+ * Called after the call to runUpdateUsingUpdater finishes.
+ */
+function runUpdateFinished() {
+ standardInit();
+ checkPostUpdateRunningFile(false);
+ checkFilesAfterUpdateFailure(getApplyDirFile);
+ waitForFilesInUse();
+}
diff --git a/toolkit/mozapps/update/tests/unit_base_updater/invalidArgInstallDirPathTraversalFailure.js b/toolkit/mozapps/update/tests/unit_base_updater/invalidArgInstallDirPathTraversalFailure.js
new file mode 100644
index 000000000..330578de6
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_base_updater/invalidArgInstallDirPathTraversalFailure.js
@@ -0,0 +1,44 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+/* Install directory path traversal failure test */
+
+const STATE_AFTER_RUNUPDATE =
+ IS_SERVICE_TEST ? STATE_FAILED_SERVICE_INVALID_INSTALL_DIR_PATH_ERROR
+ : STATE_FAILED_INVALID_INSTALL_DIR_PATH_ERROR;
+
+function run_test() {
+ if (!setupTestCommon()) {
+ return;
+ }
+ gTestFiles = gTestFilesCompleteSuccess;
+ gTestDirs = gTestDirsCompleteSuccess;
+ setTestFilesAndDirsForFailure();
+ setupUpdaterTest(FILE_COMPLETE_MAR, false);
+}
+
+/**
+ * Called after the call to setupUpdaterTest finishes.
+ */
+function setupUpdaterTestFinished() {
+ let path = "123456789";
+ if (IS_WIN) {
+ path = "C:\\" + path + "\\..\\" + path;
+ } else {
+ path = "/" + path + "/../" + path;
+ }
+
+ runUpdate(STATE_AFTER_RUNUPDATE, false, 1, true, null, path, null, null);
+}
+
+/**
+ * Called after the call to runUpdateUsingUpdater finishes.
+ */
+function runUpdateFinished() {
+ standardInit();
+ checkPostUpdateRunningFile(false);
+ checkFilesAfterUpdateFailure(getApplyDirFile);
+ waitForFilesInUse();
+}
diff --git a/toolkit/mozapps/update/tests/unit_base_updater/invalidArgInstallWorkingDirPathNotSameFailure_win.js b/toolkit/mozapps/update/tests/unit_base_updater/invalidArgInstallWorkingDirPathNotSameFailure_win.js
new file mode 100644
index 000000000..8ddb34af0
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_base_updater/invalidArgInstallWorkingDirPathNotSameFailure_win.js
@@ -0,0 +1,38 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+/* Different install and working directories for a regular update failure */
+
+const STATE_AFTER_RUNUPDATE =
+ IS_SERVICE_TEST ? STATE_FAILED_SERVICE_INVALID_APPLYTO_DIR_ERROR
+ : STATE_FAILED_INVALID_APPLYTO_DIR_ERROR;
+
+function run_test() {
+ if (!setupTestCommon()) {
+ return;
+ }
+ gTestFiles = gTestFilesCompleteSuccess;
+ gTestDirs = gTestDirsCompleteSuccess;
+ setTestFilesAndDirsForFailure();
+ setupUpdaterTest(FILE_COMPLETE_MAR, false);
+}
+
+/**
+ * Called after the call to setupUpdaterTest finishes.
+ */
+function setupUpdaterTestFinished() {
+ let path = getApplyDirFile("..", false).path;
+ runUpdate(STATE_AFTER_RUNUPDATE, false, 1, true, null, null, path, null);
+}
+
+/**
+ * Called after the call to runUpdateUsingUpdater finishes.
+ */
+function runUpdateFinished() {
+ standardInit();
+ checkPostUpdateRunningFile(false);
+ checkFilesAfterUpdateFailure(getApplyDirFile);
+ waitForFilesInUse();
+}
diff --git a/toolkit/mozapps/update/tests/unit_base_updater/invalidArgPatchDirPathTraversalFailure.js b/toolkit/mozapps/update/tests/unit_base_updater/invalidArgPatchDirPathTraversalFailure.js
new file mode 100644
index 000000000..c8ae3f0c6
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_base_updater/invalidArgPatchDirPathTraversalFailure.js
@@ -0,0 +1,43 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+/* Patch directory path traversal failure test */
+
+const STATE_AFTER_RUNUPDATE =
+ IS_SERVICE_TEST ? STATE_PENDING_SVC : STATE_PENDING;
+
+function run_test() {
+ if (!setupTestCommon()) {
+ return;
+ }
+ gTestFiles = gTestFilesCompleteSuccess;
+ gTestDirs = gTestDirsCompleteSuccess;
+ setTestFilesAndDirsForFailure();
+ setupUpdaterTest(FILE_COMPLETE_MAR, false);
+}
+
+/**
+ * Called after the call to setupUpdaterTest finishes.
+ */
+function setupUpdaterTestFinished() {
+ let path = getUpdatesPatchDir();
+ if (IS_WIN) {
+ path = path + "\\..\\";
+ } else {
+ path = path + "/../";
+ }
+
+ runUpdate(STATE_AFTER_RUNUPDATE, false, 1, true, path, null, null, null);
+}
+
+/**
+ * Called after the call to runUpdateUsingUpdater finishes.
+ */
+function runUpdateFinished() {
+ standardInit();
+ checkPostUpdateRunningFile(false);
+ checkFilesAfterUpdateFailure(getApplyDirFile);
+ waitForFilesInUse();
+}
diff --git a/toolkit/mozapps/update/tests/unit_base_updater/invalidArgStageDirNotInInstallDirFailure_win.js b/toolkit/mozapps/update/tests/unit_base_updater/invalidArgStageDirNotInInstallDirFailure_win.js
new file mode 100644
index 000000000..e9b227657
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_base_updater/invalidArgStageDirNotInInstallDirFailure_win.js
@@ -0,0 +1,38 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+/* Different install and working directories for a regular update failure */
+
+const STATE_AFTER_RUNUPDATE =
+ IS_SERVICE_TEST ? STATE_FAILED_SERVICE_INVALID_APPLYTO_DIR_STAGED_ERROR
+ : STATE_FAILED_INVALID_APPLYTO_DIR_STAGED_ERROR;
+
+function run_test() {
+ if (!setupTestCommon()) {
+ return;
+ }
+ gTestFiles = gTestFilesCompleteSuccess;
+ gTestDirs = gTestDirsCompleteSuccess;
+ setTestFilesAndDirsForFailure();
+ setupUpdaterTest(FILE_COMPLETE_MAR, false);
+}
+
+/**
+ * Called after the call to setupUpdaterTest finishes.
+ */
+function setupUpdaterTestFinished() {
+ let path = getApplyDirFile("..", false).path;
+ runUpdate(STATE_AFTER_RUNUPDATE, true, 1, true, null, null, path, null);
+}
+
+/**
+ * Called after the call to runUpdateUsingUpdater finishes.
+ */
+function runUpdateFinished() {
+ standardInit();
+ checkPostUpdateRunningFile(false);
+ checkFilesAfterUpdateFailure(getApplyDirFile);
+ waitForFilesInUse();
+}
diff --git a/toolkit/mozapps/update/tests/unit_base_updater/invalidArgWorkingDirPathLocalUNCFailure_win.js b/toolkit/mozapps/update/tests/unit_base_updater/invalidArgWorkingDirPathLocalUNCFailure_win.js
new file mode 100644
index 000000000..87bbad4aa
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_base_updater/invalidArgWorkingDirPathLocalUNCFailure_win.js
@@ -0,0 +1,38 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+/* Working directory path local UNC failure test */
+
+const STATE_AFTER_RUNUPDATE =
+ IS_SERVICE_TEST ? STATE_FAILED_SERVICE_INVALID_WORKING_DIR_PATH_ERROR
+ : STATE_FAILED_INVALID_WORKING_DIR_PATH_ERROR;
+
+function run_test() {
+ if (!setupTestCommon()) {
+ return;
+ }
+ gTestFiles = gTestFilesCompleteSuccess;
+ gTestDirs = gTestDirsCompleteSuccess;
+ setTestFilesAndDirsForFailure();
+ setupUpdaterTest(FILE_COMPLETE_MAR, false);
+}
+
+/**
+ * Called after the call to setupUpdaterTest finishes.
+ */
+function setupUpdaterTestFinished() {
+ let path = "\\\\.\\" + getApplyDirFile(null, false).path;
+ runUpdate(STATE_AFTER_RUNUPDATE, false, 1, true, null, null, path, null);
+}
+
+/**
+ * Called after the call to runUpdateUsingUpdater finishes.
+ */
+function runUpdateFinished() {
+ standardInit();
+ checkPostUpdateRunningFile(false);
+ checkFilesAfterUpdateFailure(getApplyDirFile);
+ waitForFilesInUse();
+}
diff --git a/toolkit/mozapps/update/tests/unit_base_updater/invalidArgWorkingDirPathRelativeFailure.js b/toolkit/mozapps/update/tests/unit_base_updater/invalidArgWorkingDirPathRelativeFailure.js
new file mode 100644
index 000000000..a550909b2
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_base_updater/invalidArgWorkingDirPathRelativeFailure.js
@@ -0,0 +1,37 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+/* Relative working directory path failure test */
+
+const STATE_AFTER_RUNUPDATE =
+ IS_SERVICE_TEST ? STATE_FAILED_SERVICE_INVALID_WORKING_DIR_PATH_ERROR
+ : STATE_FAILED_INVALID_WORKING_DIR_PATH_ERROR;
+
+function run_test() {
+ if (!setupTestCommon()) {
+ return;
+ }
+ gTestFiles = gTestFilesCompleteSuccess;
+ gTestDirs = gTestDirsCompleteSuccess;
+ setTestFilesAndDirsForFailure();
+ setupUpdaterTest(FILE_COMPLETE_MAR, false);
+}
+
+/**
+ * Called after the call to setupUpdaterTest finishes.
+ */
+function setupUpdaterTestFinished() {
+ runUpdate(STATE_AFTER_RUNUPDATE, false, 1, true, null, null, "test", null);
+}
+
+/**
+ * Called after the call to runUpdateUsingUpdater finishes.
+ */
+function runUpdateFinished() {
+ standardInit();
+ checkPostUpdateRunningFile(false);
+ checkFilesAfterUpdateFailure(getApplyDirFile);
+ waitForFilesInUse();
+}
diff --git a/toolkit/mozapps/update/tests/unit_base_updater/marAppApplyDirLockedStageFailure_win.js b/toolkit/mozapps/update/tests/unit_base_updater/marAppApplyDirLockedStageFailure_win.js
new file mode 100644
index 000000000..b9f793236
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_base_updater/marAppApplyDirLockedStageFailure_win.js
@@ -0,0 +1,41 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+/**
+ * Test applying an update by staging an update and launching an application to
+ * apply it.
+ */
+
+const STATE_AFTER_STAGE = STATE_PENDING;
+
+function run_test() {
+ if (!setupTestCommon()) {
+ return;
+ }
+
+ gTestFiles = gTestFilesCompleteSuccess;
+ gTestDirs = gTestDirsCompleteSuccess;
+ setTestFilesAndDirsForFailure();
+ createUpdateInProgressLockFile(getAppBaseDir());
+ setupUpdaterTest(FILE_COMPLETE_MAR, false);
+}
+
+/**
+ * Called after the call to setupUpdaterTest finishes.
+ */
+function setupUpdaterTestFinished() {
+ stageUpdate(false);
+}
+
+/**
+ * Called after the call to stageUpdate finishes.
+ */
+function stageUpdateFinished() {
+ removeUpdateInProgressLockFile(getAppBaseDir());
+ checkPostUpdateRunningFile(false);
+ checkFilesAfterUpdateFailure(getApplyDirFile);
+ checkUpdateLogContains(PERFORMING_STAGED_UPDATE);
+ checkUpdateLogContains(ERR_UPDATE_IN_PROGRESS);
+ waitForFilesInUse();
+}
diff --git a/toolkit/mozapps/update/tests/unit_base_updater/marAppApplyUpdateAppBinInUseStageSuccess_win.js b/toolkit/mozapps/update/tests/unit_base_updater/marAppApplyUpdateAppBinInUseStageSuccess_win.js
new file mode 100644
index 000000000..a606720b7
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_base_updater/marAppApplyUpdateAppBinInUseStageSuccess_win.js
@@ -0,0 +1,83 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+/**
+ * Test applying an update by staging an update and launching an application to
+ * apply it.
+ */
+
+const STATE_AFTER_STAGE = IS_SERVICE_TEST ? STATE_APPLIED_SVC : STATE_APPLIED;
+
+function run_test() {
+ if (!setupTestCommon()) {
+ return;
+ }
+
+ gTestFiles = gTestFilesCompleteSuccess;
+ gTestDirs = gTestDirsCompleteSuccess;
+ setupUpdaterTest(FILE_COMPLETE_MAR, undefined);
+}
+
+/**
+ * Called after the call to setupUpdaterTest finishes.
+ */
+function setupUpdaterTestFinished() {
+ stageUpdate(true);
+}
+
+/**
+ * Called after the call to stageUpdate finishes.
+ */
+function stageUpdateFinished() {
+ checkPostUpdateRunningFile(false);
+ checkFilesAfterUpdateSuccess(getStageDirFile, true, false);
+ checkUpdateLogContents(LOG_COMPLETE_SUCCESS, true);
+ lockDirectory(getAppBaseDir().path);
+ // Switch the application to the staged application that was updated.
+ runUpdateUsingApp(STATE_SUCCEEDED);
+}
+
+/**
+ * Called after the call to runUpdateUsingApp finishes.
+ */
+function runUpdateFinished() {
+ checkPostUpdateAppLog();
+}
+
+/**
+ * Called after the call to checkPostUpdateAppLog finishes.
+ */
+function checkPostUpdateAppLogFinished() {
+ checkAppBundleModTime();
+ standardInit();
+ Assert.equal(readStatusState(), STATE_NONE,
+ "the status file state" + MSG_SHOULD_EQUAL);
+ Assert.ok(!gUpdateManager.activeUpdate,
+ "the active update should not be defined");
+ Assert.equal(gUpdateManager.updateCount, 1,
+ "the update manager updateCount attribute" + MSG_SHOULD_EQUAL);
+ Assert.equal(gUpdateManager.getUpdateAt(0).state, STATE_SUCCEEDED,
+ "the update state" + MSG_SHOULD_EQUAL);
+ checkPostUpdateRunningFile(true);
+ checkFilesAfterUpdateSuccess(getApplyDirFile);
+ checkUpdateLogContents(LOG_COMPLETE_SUCCESS);
+
+ let updatesDir = getUpdatesPatchDir();
+ Assert.ok(updatesDir.exists(),
+ MSG_SHOULD_EXIST + getMsgPath(updatesDir.path));
+
+ let log = getUpdateLog(FILE_UPDATE_LOG);
+ Assert.ok(!log.exists(),
+ MSG_SHOULD_NOT_EXIST + getMsgPath(log.path));
+
+ log = getUpdateLog(FILE_LAST_UPDATE_LOG);
+ Assert.ok(log.exists(),
+ MSG_SHOULD_EXIST + getMsgPath(log.path));
+
+ log = getUpdateLog(FILE_BACKUP_UPDATE_LOG);
+ Assert.ok(log.exists(),
+ MSG_SHOULD_EXIST + getMsgPath(log.path));
+
+ waitForFilesInUse();
+}
diff --git a/toolkit/mozapps/update/tests/unit_base_updater/marAppApplyUpdateStageOldVersionFailure.js b/toolkit/mozapps/update/tests/unit_base_updater/marAppApplyUpdateStageOldVersionFailure.js
new file mode 100644
index 000000000..00b38adc7
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_base_updater/marAppApplyUpdateStageOldVersionFailure.js
@@ -0,0 +1,88 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+/**
+ * Test a replace request for a staged update with a version file that specifies
+ * an older version failure. The same check is used in nsUpdateDriver.cpp for
+ * all update types which is why there aren't tests for the maintenance service
+ * as well as for other update types.
+ */
+
+const STATE_AFTER_STAGE = STATE_APPLIED;
+
+function run_test() {
+ if (!setupTestCommon()) {
+ return;
+ }
+
+ gTestFiles = gTestFilesCompleteSuccess;
+ gTestDirs = gTestDirsCompleteSuccess;
+ setupUpdaterTest(FILE_COMPLETE_MAR);
+}
+
+/**
+ * Called after the call to setupUpdaterTest finishes.
+ */
+function setupUpdaterTestFinished() {
+ stageUpdate(false);
+}
+
+/**
+ * Called after the call to stageUpdate finishes.
+ */
+function stageUpdateFinished() {
+ checkPostUpdateRunningFile(false);
+ checkFilesAfterUpdateSuccess(getStageDirFile, true);
+ checkUpdateLogContents(LOG_COMPLETE_SUCCESS, true);
+ // Change the active update to an older version to simulate installing a new
+ // version of the application while there is an update that has been staged.
+ let channel = gDefaultPrefBranch.getCharPref(PREF_APP_UPDATE_CHANNEL);
+ let patches = getLocalPatchString(null, null, null, null, null, "true",
+ STATE_AFTER_STAGE);
+ let updates = getLocalUpdateString(patches, null, null, null, "1.0", null,
+ null, null, null, null, "true", channel);
+ writeUpdatesToXMLFile(getLocalUpdatesXMLString(updates), true);
+ // Change the version file to an older version to simulate installing a new
+ // version of the application while there is an update that has been staged.
+ writeVersionFile("1.0");
+ reloadUpdateManagerData();
+ // Try to switch the application to the staged application that was updated.
+ runUpdateUsingApp(STATE_AFTER_STAGE);
+}
+
+/**
+ * Called after the call to runUpdateUsingApp finishes.
+ */
+function runUpdateFinished() {
+ standardInit();
+ Assert.equal(readStatusState(), STATE_NONE,
+ "the status file state" + MSG_SHOULD_EQUAL);
+ Assert.ok(!gUpdateManager.activeUpdate,
+ "the active update should not be defined");
+ Assert.equal(gUpdateManager.updateCount, 1,
+ "the update manager updateCount attribute" + MSG_SHOULD_EQUAL);
+ Assert.equal(gUpdateManager.getUpdateAt(0).state, STATE_AFTER_STAGE,
+ "the update state" + MSG_SHOULD_EQUAL);
+ checkPostUpdateRunningFile(false);
+ setTestFilesAndDirsForFailure();
+ checkFilesAfterUpdateFailure(getApplyDirFile, IS_MACOSX ? false : true, false);
+
+ let updatesDir = getUpdatesPatchDir();
+ Assert.ok(updatesDir.exists(),
+ MSG_SHOULD_EXIST + getMsgPath(updatesDir.path));
+
+ let log = getUpdateLog(FILE_UPDATE_LOG);
+ Assert.ok(!log.exists(),
+ MSG_SHOULD_NOT_EXIST + getMsgPath(log.path));
+
+ log = getUpdateLog(FILE_LAST_UPDATE_LOG);
+ Assert.ok(log.exists(),
+ MSG_SHOULD_EXIST + getMsgPath(log.path));
+
+ log = getUpdateLog(FILE_BACKUP_UPDATE_LOG);
+ Assert.ok(!log.exists(),
+ MSG_SHOULD_NOT_EXIST + getMsgPath(log.path));
+
+ waitForFilesInUse();
+}
diff --git a/toolkit/mozapps/update/tests/unit_base_updater/marAppApplyUpdateStageSuccess.js b/toolkit/mozapps/update/tests/unit_base_updater/marAppApplyUpdateStageSuccess.js
new file mode 100644
index 000000000..5b9b08156
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_base_updater/marAppApplyUpdateStageSuccess.js
@@ -0,0 +1,82 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+/**
+ * Test applying an update by staging an update and launching an application to
+ * apply it.
+ */
+
+const STATE_AFTER_STAGE = IS_SERVICE_TEST ? STATE_APPLIED_SVC : STATE_APPLIED;
+
+function run_test() {
+ if (!setupTestCommon()) {
+ return;
+ }
+
+ gTestFiles = gTestFilesCompleteSuccess;
+ gTestDirs = gTestDirsCompleteSuccess;
+ setupUpdaterTest(FILE_COMPLETE_MAR, true);
+}
+
+/**
+ * Called after the call to setupUpdaterTest finishes.
+ */
+function setupUpdaterTestFinished() {
+ stageUpdate(true);
+}
+
+/**
+ * Called after the call to stageUpdate finishes.
+ */
+function stageUpdateFinished() {
+ checkPostUpdateRunningFile(false);
+ checkFilesAfterUpdateSuccess(getStageDirFile, true);
+ checkUpdateLogContents(LOG_COMPLETE_SUCCESS, true);
+ // Switch the application to the staged application that was updated.
+ runUpdateUsingApp(STATE_SUCCEEDED);
+}
+
+/**
+ * Called after the call to runUpdateUsingApp finishes.
+ */
+function runUpdateFinished() {
+ checkPostUpdateAppLog();
+}
+
+/**
+ * Called after the call to checkPostUpdateAppLog finishes.
+ */
+function checkPostUpdateAppLogFinished() {
+ checkAppBundleModTime();
+ standardInit();
+ Assert.equal(readStatusState(), STATE_NONE,
+ "the status file state" + MSG_SHOULD_EQUAL);
+ Assert.ok(!gUpdateManager.activeUpdate,
+ "the active update should not be defined");
+ Assert.equal(gUpdateManager.updateCount, 1,
+ "the update manager updateCount attribute" + MSG_SHOULD_EQUAL);
+ Assert.equal(gUpdateManager.getUpdateAt(0).state, STATE_SUCCEEDED,
+ "the update state" + MSG_SHOULD_EQUAL);
+ checkPostUpdateRunningFile(true);
+ checkFilesAfterUpdateSuccess(getApplyDirFile, false, true);
+ checkUpdateLogContents(LOG_REPLACE_SUCCESS, false, true);
+
+ let updatesDir = getUpdatesPatchDir();
+ Assert.ok(updatesDir.exists(),
+ MSG_SHOULD_EXIST + getMsgPath(updatesDir.path));
+
+ let log = getUpdateLog(FILE_UPDATE_LOG);
+ Assert.ok(!log.exists(),
+ MSG_SHOULD_NOT_EXIST + getMsgPath(log.path));
+
+ log = getUpdateLog(FILE_LAST_UPDATE_LOG);
+ Assert.ok(log.exists(),
+ MSG_SHOULD_EXIST + getMsgPath(log.path));
+
+ log = getUpdateLog(FILE_BACKUP_UPDATE_LOG);
+ Assert.ok(log.exists(),
+ MSG_SHOULD_EXIST + getMsgPath(log.path));
+
+ waitForFilesInUse();
+}
diff --git a/toolkit/mozapps/update/tests/unit_base_updater/marAppApplyUpdateSuccess.js b/toolkit/mozapps/update/tests/unit_base_updater/marAppApplyUpdateSuccess.js
new file mode 100644
index 000000000..e76233fe6
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_base_updater/marAppApplyUpdateSuccess.js
@@ -0,0 +1,65 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+/**
+ * Test applying an update by staging an update and launching an application to
+ * apply it.
+ */
+
+function run_test() {
+ if (!setupTestCommon()) {
+ return;
+ }
+
+ gTestFiles = gTestFilesCompleteSuccess;
+ gTestDirs = gTestDirsCompleteSuccess;
+ // The third parameter will test that a full path to the post update binary
+ // doesn't execute.
+ setupUpdaterTest(FILE_COMPLETE_MAR, undefined,
+ getApplyDirFile(null, true).path + "/");
+}
+
+/**
+ * Called after the call to setupUpdaterTest finishes.
+ */
+function setupUpdaterTestFinished() {
+ runUpdateUsingApp(STATE_SUCCEEDED);
+}
+
+/**
+ * Called after the call to runUpdateUsingApp finishes.
+ */
+function runUpdateFinished() {
+ checkAppBundleModTime();
+ standardInit();
+ Assert.equal(readStatusState(), STATE_NONE,
+ "the status file state" + MSG_SHOULD_EQUAL);
+ Assert.ok(!gUpdateManager.activeUpdate,
+ "the active update should not be defined");
+ Assert.equal(gUpdateManager.updateCount, 1,
+ "the update manager updateCount attribute" + MSG_SHOULD_EQUAL);
+ Assert.equal(gUpdateManager.getUpdateAt(0).state, STATE_SUCCEEDED,
+ "the update state" + MSG_SHOULD_EQUAL);
+ checkPostUpdateRunningFile(false);
+ checkFilesAfterUpdateSuccess(getApplyDirFile);
+ checkUpdateLogContents(LOG_COMPLETE_SUCCESS);
+
+ let updatesDir = getUpdatesPatchDir();
+ Assert.ok(updatesDir.exists(),
+ MSG_SHOULD_EXIST + getMsgPath(updatesDir.path));
+
+ let log = getUpdateLog(FILE_UPDATE_LOG);
+ Assert.ok(!log.exists(),
+ MSG_SHOULD_NOT_EXIST + getMsgPath(log.path));
+
+ log = getUpdateLog(FILE_LAST_UPDATE_LOG);
+ Assert.ok(log.exists(),
+ MSG_SHOULD_EXIST + getMsgPath(log.path));
+
+ log = getUpdateLog(FILE_BACKUP_UPDATE_LOG);
+ Assert.ok(!log.exists(),
+ MSG_SHOULD_NOT_EXIST + getMsgPath(log.path));
+
+ waitForFilesInUse();
+}
diff --git a/toolkit/mozapps/update/tests/unit_base_updater/marAppInUseStageFailureComplete_win.js b/toolkit/mozapps/update/tests/unit_base_updater/marAppInUseStageFailureComplete_win.js
new file mode 100644
index 000000000..b1505d58e
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_base_updater/marAppInUseStageFailureComplete_win.js
@@ -0,0 +1,70 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+/* Application in use complete MAR file staged patch apply failure test */
+
+const STATE_AFTER_STAGE = IS_SERVICE_TEST ? STATE_APPLIED_SVC : STATE_APPLIED;
+const STATE_AFTER_RUNUPDATE = IS_SERVICE_TEST ? STATE_PENDING_SVC : STATE_PENDING;
+
+function run_test() {
+ if (!setupTestCommon()) {
+ return;
+ }
+ gTestFiles = gTestFilesCompleteSuccess;
+ gTestDirs = gTestDirsCompleteSuccess;
+ setupUpdaterTest(FILE_COMPLETE_MAR, false);
+}
+
+/**
+ * Called after the call to setupUpdaterTest finishes.
+ */
+function setupUpdaterTestFinished() {
+ runHelperFileInUse(DIR_RESOURCES + gCallbackBinFile, false);
+}
+
+/**
+ * Called after the call to waitForHelperSleep finishes.
+ */
+function waitForHelperSleepFinished() {
+ stageUpdate(true);
+}
+
+/**
+ * Called after the call to stageUpdate finishes.
+ */
+function stageUpdateFinished() {
+ checkPostUpdateRunningFile(false);
+ checkFilesAfterUpdateSuccess(getStageDirFile, true);
+ checkUpdateLogContents(LOG_COMPLETE_SUCCESS, true);
+ // Switch the application to the staged application that was updated.
+ runUpdate(STATE_AFTER_RUNUPDATE, true, 1, true);
+}
+
+/**
+ * Called after the call to runUpdate finishes.
+ */
+function runUpdateFinished() {
+ waitForHelperExit();
+}
+
+/**
+ * Called after the call to waitForHelperExit finishes.
+ */
+function waitForHelperExitFinished() {
+ standardInit();
+ Assert.equal(readStatusState(), STATE_NONE,
+ "the status file state" + MSG_SHOULD_EQUAL);
+ Assert.ok(!gUpdateManager.activeUpdate,
+ "the active update should not be defined");
+ Assert.equal(gUpdateManager.updateCount, 1,
+ "the update manager updateCount attribute" + MSG_SHOULD_EQUAL);
+ Assert.equal(gUpdateManager.getUpdateAt(0).state, STATE_AFTER_RUNUPDATE,
+ "the update state" + MSG_SHOULD_EQUAL);
+ checkPostUpdateRunningFile(false);
+ setTestFilesAndDirsForFailure();
+ checkFilesAfterUpdateFailure(getApplyDirFile);
+ checkUpdateLogContains(ERR_RENAME_FILE);
+ checkUpdateLogContains(ERR_MOVE_DESTDIR_7 + "\n" + CALL_QUIT);
+ checkCallbackLog();
+}
diff --git a/toolkit/mozapps/update/tests/unit_base_updater/marAppInUseStageSuccessComplete_unix.js b/toolkit/mozapps/update/tests/unit_base_updater/marAppInUseStageSuccessComplete_unix.js
new file mode 100644
index 000000000..a1cc7d043
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_base_updater/marAppInUseStageSuccessComplete_unix.js
@@ -0,0 +1,113 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+/* Application in use complete MAR file staged patch apply success test */
+
+const START_STATE = STATE_PENDING;
+const STATE_AFTER_STAGE = STATE_APPLIED;
+
+function run_test() {
+ if (!setupTestCommon()) {
+ return;
+ }
+ gTestFiles = gTestFilesCompleteSuccess;
+ gTestFiles[gTestFiles.length - 1].originalContents = null;
+ gTestFiles[gTestFiles.length - 1].compareContents = "FromComplete\n";
+ gTestFiles[gTestFiles.length - 1].comparePerms = 0o644;
+ gTestDirs = gTestDirsCompleteSuccess;
+ setupUpdaterTest(FILE_COMPLETE_MAR, true);
+}
+
+/**
+ * Called after the call to setupUpdaterTest finishes.
+ */
+function setupUpdaterTestFinished() {
+ setupSymLinks();
+ runHelperFileInUse(DIR_RESOURCES + gCallbackBinFile, false);
+}
+
+/**
+ * Called after the call to waitForHelperSleep finishes.
+ */
+function waitForHelperSleepFinished() {
+ stageUpdate(true);
+}
+
+/**
+ * Called after the call to stageUpdate finishes.
+ */
+function stageUpdateFinished() {
+ checkPostUpdateRunningFile(false);
+ checkFilesAfterUpdateSuccess(getStageDirFile, true);
+ checkUpdateLogContents(LOG_COMPLETE_SUCCESS, true);
+ // Switch the application to the staged application that was updated.
+ runUpdate(STATE_SUCCEEDED, true, 0, true);
+}
+
+/**
+ * Called after the call to runUpdate finishes.
+ */
+function runUpdateFinished() {
+ waitForHelperExit();
+}
+
+/**
+ * Called after the call to waitForHelperExit finishes.
+ */
+function waitForHelperExitFinished() {
+ checkPostUpdateAppLog();
+}
+
+/**
+ * Called after the call to checkPostUpdateAppLog finishes.
+ */
+function checkPostUpdateAppLogFinished() {
+ checkAppBundleModTime();
+ checkSymLinks();
+ standardInit();
+ Assert.equal(readStatusState(), STATE_NONE,
+ "the status file state" + MSG_SHOULD_EQUAL);
+ Assert.ok(!gUpdateManager.activeUpdate,
+ "the active update should not be defined");
+ Assert.equal(gUpdateManager.updateCount, 1,
+ "the update manager updateCount attribute" + MSG_SHOULD_EQUAL);
+ Assert.equal(gUpdateManager.getUpdateAt(0).state, STATE_SUCCEEDED,
+ "the update state" + MSG_SHOULD_EQUAL);
+ checkPostUpdateRunningFile(true);
+ checkFilesAfterUpdateSuccess(getApplyDirFile);
+ checkUpdateLogContents(LOG_REPLACE_SUCCESS, false, true);
+ checkCallbackLog();
+}
+
+/**
+ * Setup symlinks for the test.
+ */
+function setupSymLinks() {
+ if (IS_UNIX) {
+ removeSymlink();
+ createSymlink();
+ do_register_cleanup(removeSymlink);
+ gTestFiles.splice(gTestFiles.length - 3, 0,
+ {
+ description: "Readable symlink",
+ fileName: "link",
+ relPathDir: DIR_RESOURCES,
+ originalContents: "test",
+ compareContents: "test",
+ originalFile: null,
+ compareFile: null,
+ originalPerms: 0o666,
+ comparePerms: 0o666
+ });
+ }
+}
+
+/**
+ * Checks the state of the symlinks for the test.
+ */
+function checkSymLinks() {
+ if (IS_UNIX) {
+ checkSymlink();
+ }
+}
diff --git a/toolkit/mozapps/update/tests/unit_base_updater/marAppInUseSuccessComplete.js b/toolkit/mozapps/update/tests/unit_base_updater/marAppInUseSuccessComplete.js
new file mode 100644
index 000000000..93333cade
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_base_updater/marAppInUseSuccessComplete.js
@@ -0,0 +1,62 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+/* Application in use complete MAR file patch apply success test */
+
+function run_test() {
+ if (!setupTestCommon()) {
+ return;
+ }
+ gTestFiles = gTestFilesCompleteSuccess;
+ gTestDirs = gTestDirsCompleteSuccess;
+ setupUpdaterTest(FILE_COMPLETE_MAR, false);
+}
+
+/**
+ * Called after the call to setupUpdaterTest finishes.
+ */
+function setupUpdaterTestFinished() {
+ runHelperFileInUse(DIR_RESOURCES + gCallbackBinFile, false);
+}
+
+/**
+ * Called after the call to waitForHelperSleep finishes.
+ */
+function waitForHelperSleepFinished() {
+ runUpdate(STATE_SUCCEEDED, false, 0, true);
+}
+
+/**
+ * Called after the call to runUpdate finishes.
+ */
+function runUpdateFinished() {
+ waitForHelperExit();
+}
+
+/**
+ * Called after the call to waitForHelperExit finishes.
+ */
+function waitForHelperExitFinished() {
+ checkPostUpdateAppLog();
+}
+
+/**
+ * Called after the call to checkPostUpdateAppLog finishes.
+ */
+function checkPostUpdateAppLogFinished() {
+ checkAppBundleModTime();
+ standardInit();
+ Assert.equal(readStatusState(), STATE_NONE,
+ "the status file state" + MSG_SHOULD_EQUAL);
+ Assert.ok(!gUpdateManager.activeUpdate,
+ "the active update should not be defined");
+ Assert.equal(gUpdateManager.updateCount, 1,
+ "the update manager updateCount attribute" + MSG_SHOULD_EQUAL);
+ Assert.equal(gUpdateManager.getUpdateAt(0).state, STATE_SUCCEEDED,
+ "the update state" + MSG_SHOULD_EQUAL);
+ checkPostUpdateRunningFile(true);
+ checkFilesAfterUpdateSuccess(getApplyDirFile);
+ checkUpdateLogContents(LOG_COMPLETE_SUCCESS);
+ checkCallbackLog();
+}
diff --git a/toolkit/mozapps/update/tests/unit_base_updater/marCallbackAppStageSuccessComplete_win.js b/toolkit/mozapps/update/tests/unit_base_updater/marCallbackAppStageSuccessComplete_win.js
new file mode 100644
index 000000000..79e54c182
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_base_updater/marCallbackAppStageSuccessComplete_win.js
@@ -0,0 +1,61 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+/* Replace app binary complete MAR file staged patch apply success test */
+
+const STATE_AFTER_STAGE = IS_SERVICE_TEST ? STATE_APPLIED_SVC : STATE_APPLIED;
+
+function run_test() {
+ if (!setupTestCommon()) {
+ return;
+ }
+ gTestFiles = gTestFilesCompleteSuccess;
+ gTestDirs = gTestDirsCompleteSuccess;
+ gCallbackBinFile = "exe0.exe";
+ setupUpdaterTest(FILE_COMPLETE_MAR, false);
+}
+
+/**
+ * Called after the call to setupUpdaterTest finishes.
+ */
+function setupUpdaterTestFinished() {
+ stageUpdate(true);
+}
+
+/**
+ * Called after the call to stageUpdate finishes.
+ */
+function stageUpdateFinished() {
+ checkPostUpdateRunningFile(false);
+ checkFilesAfterUpdateSuccess(getStageDirFile, true);
+ checkUpdateLogContents(LOG_COMPLETE_SUCCESS, true);
+ // Switch the application to the staged application that was updated.
+ runUpdate(STATE_SUCCEEDED, true, 0, true);
+}
+
+/**
+ * Called after the call to runUpdate finishes.
+ */
+function runUpdateFinished() {
+ checkPostUpdateAppLog();
+}
+
+/**
+ * Called after the call to checkPostUpdateAppLog finishes.
+ */
+function checkPostUpdateAppLogFinished() {
+ standardInit();
+ Assert.equal(readStatusState(), STATE_NONE,
+ "the status file state" + MSG_SHOULD_EQUAL);
+ Assert.ok(!gUpdateManager.activeUpdate,
+ "the active update should not be defined");
+ Assert.equal(gUpdateManager.updateCount, 1,
+ "the update manager updateCount attribute" + MSG_SHOULD_EQUAL);
+ Assert.equal(gUpdateManager.getUpdateAt(0).state, STATE_SUCCEEDED,
+ "the update state" + MSG_SHOULD_EQUAL);
+ checkPostUpdateRunningFile(true);
+ checkFilesAfterUpdateSuccess(getApplyDirFile, false, true);
+ checkUpdateLogContents(LOG_REPLACE_SUCCESS, false, true);
+ checkCallbackLog();
+}
diff --git a/toolkit/mozapps/update/tests/unit_base_updater/marCallbackAppStageSuccessPartial_win.js b/toolkit/mozapps/update/tests/unit_base_updater/marCallbackAppStageSuccessPartial_win.js
new file mode 100644
index 000000000..b1f84715f
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_base_updater/marCallbackAppStageSuccessPartial_win.js
@@ -0,0 +1,61 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+/* Patch app binary partial MAR file staged patch apply success test */
+
+const STATE_AFTER_STAGE = IS_SERVICE_TEST ? STATE_APPLIED_SVC : STATE_APPLIED;
+
+function run_test() {
+ if (!setupTestCommon()) {
+ return;
+ }
+ gTestFiles = gTestFilesPartialSuccess;
+ gTestDirs = gTestDirsPartialSuccess;
+ gCallbackBinFile = "exe0.exe";
+ setupUpdaterTest(FILE_PARTIAL_MAR, false);
+}
+
+/**
+ * Called after the call to setupUpdaterTest finishes.
+ */
+function setupUpdaterTestFinished() {
+ stageUpdate(true);
+}
+
+/**
+ * Called after the call to stageUpdate finishes.
+ */
+function stageUpdateFinished() {
+ checkPostUpdateRunningFile(false);
+ checkFilesAfterUpdateSuccess(getStageDirFile, true);
+ checkUpdateLogContents(LOG_PARTIAL_SUCCESS, true);
+ // Switch the application to the staged application that was updated.
+ runUpdate(STATE_SUCCEEDED, true, 0, true);
+}
+
+/**
+ * Called after the call to runUpdate finishes.
+ */
+function runUpdateFinished() {
+ checkPostUpdateAppLog();
+}
+
+/**
+ * Called after the call to checkPostUpdateAppLog finishes.
+ */
+function checkPostUpdateAppLogFinished() {
+ standardInit();
+ Assert.equal(readStatusState(), STATE_NONE,
+ "the status file state" + MSG_SHOULD_EQUAL);
+ Assert.ok(!gUpdateManager.activeUpdate,
+ "the active update should not be defined");
+ Assert.equal(gUpdateManager.updateCount, 1,
+ "the update manager updateCount attribute" + MSG_SHOULD_EQUAL);
+ Assert.equal(gUpdateManager.getUpdateAt(0).state, STATE_SUCCEEDED,
+ "the update state" + MSG_SHOULD_EQUAL);
+ checkPostUpdateRunningFile(true);
+ checkFilesAfterUpdateSuccess(getApplyDirFile, false, true);
+ checkUpdateLogContents(LOG_REPLACE_SUCCESS, false, true);
+ checkCallbackLog();
+}
diff --git a/toolkit/mozapps/update/tests/unit_base_updater/marCallbackAppSuccessComplete_win.js b/toolkit/mozapps/update/tests/unit_base_updater/marCallbackAppSuccessComplete_win.js
new file mode 100644
index 000000000..85e92d290
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_base_updater/marCallbackAppSuccessComplete_win.js
@@ -0,0 +1,48 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+/* Replace app binary complete MAR file patch apply success test */
+
+function run_test() {
+ if (!setupTestCommon()) {
+ return;
+ }
+ gTestFiles = gTestFilesCompleteSuccess;
+ gTestDirs = gTestDirsCompleteSuccess;
+ gCallbackBinFile = "exe0.exe";
+ setupUpdaterTest(FILE_COMPLETE_MAR, false);
+}
+
+/**
+ * Called after the call to setupUpdaterTest finishes.
+ */
+function setupUpdaterTestFinished() {
+ runUpdate(STATE_SUCCEEDED, false, 0, true);
+}
+
+/**
+ * Called after the call to runUpdate finishes.
+ */
+function runUpdateFinished() {
+ checkPostUpdateAppLog();
+}
+
+/**
+ * Called after the call to checkPostUpdateAppLog finishes.
+ */
+function checkPostUpdateAppLogFinished() {
+ standardInit();
+ Assert.equal(readStatusState(), STATE_NONE,
+ "the status file state" + MSG_SHOULD_EQUAL);
+ Assert.ok(!gUpdateManager.activeUpdate,
+ "the active update should not be defined");
+ Assert.equal(gUpdateManager.updateCount, 1,
+ "the update manager updateCount attribute" + MSG_SHOULD_EQUAL);
+ Assert.equal(gUpdateManager.getUpdateAt(0).state, STATE_SUCCEEDED,
+ "the update state" + MSG_SHOULD_EQUAL);
+ checkPostUpdateRunningFile(true);
+ checkFilesAfterUpdateSuccess(getApplyDirFile);
+ checkUpdateLogContents(LOG_COMPLETE_SUCCESS);
+ checkCallbackLog();
+}
diff --git a/toolkit/mozapps/update/tests/unit_base_updater/marCallbackAppSuccessPartial_win.js b/toolkit/mozapps/update/tests/unit_base_updater/marCallbackAppSuccessPartial_win.js
new file mode 100644
index 000000000..1212c9ba2
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_base_updater/marCallbackAppSuccessPartial_win.js
@@ -0,0 +1,48 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+/* Patch app binary partial MAR file patch apply success test */
+
+function run_test() {
+ if (!setupTestCommon()) {
+ return;
+ }
+ gTestFiles = gTestFilesPartialSuccess;
+ gTestDirs = gTestDirsPartialSuccess;
+ gCallbackBinFile = "exe0.exe";
+ setupUpdaterTest(FILE_PARTIAL_MAR, false);
+}
+
+/**
+ * Called after the call to setupUpdaterTest finishes.
+ */
+function setupUpdaterTestFinished() {
+ runUpdate(STATE_SUCCEEDED, false, 0, true);
+}
+
+/**
+ * Called after the call to runUpdate finishes.
+ */
+function runUpdateFinished() {
+ checkPostUpdateAppLog();
+}
+
+/**
+ * Called after the call to checkPostUpdateAppLog finishes.
+ */
+function checkPostUpdateAppLogFinished() {
+ standardInit();
+ Assert.equal(readStatusState(), STATE_NONE,
+ "the status file state" + MSG_SHOULD_EQUAL);
+ Assert.ok(!gUpdateManager.activeUpdate,
+ "the active update should not be defined");
+ Assert.equal(gUpdateManager.updateCount, 1,
+ "the update manager updateCount attribute" + MSG_SHOULD_EQUAL);
+ Assert.equal(gUpdateManager.getUpdateAt(0).state, STATE_SUCCEEDED,
+ "the update state" + MSG_SHOULD_EQUAL);
+ checkPostUpdateRunningFile(true);
+ checkFilesAfterUpdateSuccess(getApplyDirFile);
+ checkUpdateLogContents(LOG_PARTIAL_SUCCESS);
+ checkCallbackLog();
+}
diff --git a/toolkit/mozapps/update/tests/unit_base_updater/marFailurePartial.js b/toolkit/mozapps/update/tests/unit_base_updater/marFailurePartial.js
new file mode 100644
index 000000000..960c96f7b
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_base_updater/marFailurePartial.js
@@ -0,0 +1,47 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+/* General Partial MAR File Patch Apply Failure Test */
+
+function run_test() {
+ if (!setupTestCommon()) {
+ return;
+ }
+ gTestFiles = gTestFilesPartialSuccess;
+ gTestFiles[11].originalFile = "partial.png";
+ gTestDirs = gTestDirsPartialSuccess;
+ setTestFilesAndDirsForFailure();
+ setupUpdaterTest(FILE_PARTIAL_MAR, false);
+}
+
+/**
+ * Called after the call to setupUpdaterTest finishes.
+ */
+function setupUpdaterTestFinished() {
+ // If execv is used the updater process will turn into the callback process
+ // and the updater's return code will be that of the callback process.
+ runUpdate(STATE_FAILED_LOADSOURCE_ERROR_WRONG_SIZE, false, (USE_EXECV ? 0 : 1),
+ true);
+}
+
+/**
+ * Called after the call to runUpdate finishes.
+ */
+function runUpdateFinished() {
+ checkAppBundleModTime();
+ standardInit();
+ Assert.equal(readStatusFile(), STATE_NONE,
+ "the status file failure code" + MSG_SHOULD_EQUAL);
+ Assert.equal(gUpdateManager.updateCount, 1,
+ "the update manager updateCount attribute" + MSG_SHOULD_EQUAL);
+ Assert.equal(gUpdateManager.getUpdateAt(0).state, STATE_FAILED,
+ "the update state" + MSG_SHOULD_EQUAL);
+ Assert.equal(gUpdateManager.getUpdateAt(0).errorCode, LOADSOURCE_ERROR_WRONG_SIZE,
+ "the update errorCode" + MSG_SHOULD_EQUAL);
+ checkPostUpdateRunningFile(false);
+ checkFilesAfterUpdateFailure(getApplyDirFile);
+ checkUpdateLogContents(LOG_PARTIAL_FAILURE);
+ checkCallbackLog();
+}
diff --git a/toolkit/mozapps/update/tests/unit_base_updater/marFileInUseStageFailureComplete_win.js b/toolkit/mozapps/update/tests/unit_base_updater/marFileInUseStageFailureComplete_win.js
new file mode 100644
index 000000000..b39595f92
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_base_updater/marFileInUseStageFailureComplete_win.js
@@ -0,0 +1,67 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+/* File in use complete MAR file staged patch apply failure test */
+
+const STATE_AFTER_STAGE = IS_SERVICE_TEST ? STATE_APPLIED_SVC : STATE_APPLIED;
+const STATE_AFTER_RUNUPDATE = IS_SERVICE_TEST ? STATE_PENDING_SVC : STATE_PENDING;
+
+function run_test() {
+ if (!setupTestCommon()) {
+ return;
+ }
+ gTestFiles = gTestFilesCompleteSuccess;
+ gTestDirs = gTestDirsCompleteSuccess;
+ setupUpdaterTest(FILE_COMPLETE_MAR, false);
+}
+
+/**
+ * Called after the call to setupUpdaterTest finishes.
+ */
+function setupUpdaterTestFinished() {
+ runHelperFileInUse(gTestFiles[13].relPathDir + gTestFiles[13].fileName,
+ false);
+}
+
+/**
+ * Called after the call to waitForHelperSleep finishes.
+ */
+function waitForHelperSleepFinished() {
+ stageUpdate(true);
+}
+
+/**
+ * Called after the call to stageUpdate finishes.
+ */
+function stageUpdateFinished() {
+ checkPostUpdateRunningFile(false);
+ checkFilesAfterUpdateSuccess(getStageDirFile, true);
+ checkUpdateLogContents(LOG_COMPLETE_SUCCESS, true);
+ // Switch the application to the staged application that was updated.
+ runUpdate(STATE_AFTER_RUNUPDATE, true, 1, true);
+}
+
+/**
+ * Called after the call to runUpdate finishes.
+ */
+function runUpdateFinished() {
+ waitForHelperExit();
+}
+
+/**
+ * Called after the call to waitForHelperExit finishes.
+ */
+function waitForHelperExitFinished() {
+ standardInit();
+ Assert.equal(gUpdateManager.updateCount, 1,
+ "the update manager updateCount attribute" + MSG_SHOULD_EQUAL);
+ Assert.equal(gUpdateManager.getUpdateAt(0).state, STATE_AFTER_RUNUPDATE,
+ "the update state" + MSG_SHOULD_EQUAL);
+ checkPostUpdateRunningFile(false);
+ setTestFilesAndDirsForFailure();
+ checkFilesAfterUpdateFailure(getApplyDirFile);
+ checkUpdateLogContains(ERR_RENAME_FILE);
+ checkUpdateLogContains(ERR_MOVE_DESTDIR_7 + "\n" + CALL_QUIT);
+ checkCallbackLog();
+}
diff --git a/toolkit/mozapps/update/tests/unit_base_updater/marFileInUseStageFailurePartial_win.js b/toolkit/mozapps/update/tests/unit_base_updater/marFileInUseStageFailurePartial_win.js
new file mode 100644
index 000000000..06d386ad6
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_base_updater/marFileInUseStageFailurePartial_win.js
@@ -0,0 +1,67 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+/* File in use partial MAR file staged patch apply failure test */
+
+const STATE_AFTER_STAGE = IS_SERVICE_TEST ? STATE_APPLIED_SVC : STATE_APPLIED;
+const STATE_AFTER_RUNUPDATE = IS_SERVICE_TEST ? STATE_PENDING_SVC : STATE_PENDING;
+
+function run_test() {
+ if (!setupTestCommon()) {
+ return;
+ }
+ gTestFiles = gTestFilesPartialSuccess;
+ gTestDirs = gTestDirsPartialSuccess;
+ setupUpdaterTest(FILE_PARTIAL_MAR, false);
+}
+
+/**
+ * Called after the call to setupUpdaterTest finishes.
+ */
+function setupUpdaterTestFinished() {
+ runHelperFileInUse(gTestFiles[11].relPathDir + gTestFiles[11].fileName,
+ false);
+}
+
+/**
+ * Called after the call to waitForHelperSleep finishes.
+ */
+function waitForHelperSleepFinished() {
+ stageUpdate(true);
+}
+
+/**
+ * Called after the call to stageUpdate finishes.
+ */
+function stageUpdateFinished() {
+ checkPostUpdateRunningFile(false);
+ checkFilesAfterUpdateSuccess(getStageDirFile, true);
+ checkUpdateLogContents(LOG_PARTIAL_SUCCESS, true);
+ // Switch the application to the staged application that was updated.
+ runUpdate(STATE_AFTER_RUNUPDATE, true, 1, true);
+}
+
+/**
+ * Called after the call to runUpdate finishes.
+ */
+function runUpdateFinished() {
+ waitForHelperExit();
+}
+
+/**
+ * Called after the call to waitForHelperExit finishes.
+ */
+function waitForHelperExitFinished() {
+ standardInit();
+ Assert.equal(gUpdateManager.updateCount, 1,
+ "the update manager updateCount attribute" + MSG_SHOULD_EQUAL);
+ Assert.equal(gUpdateManager.getUpdateAt(0).state, STATE_AFTER_RUNUPDATE,
+ "the update state" + MSG_SHOULD_EQUAL);
+ checkPostUpdateRunningFile(false);
+ setTestFilesAndDirsForFailure();
+ checkFilesAfterUpdateFailure(getApplyDirFile);
+ checkUpdateLogContains(ERR_RENAME_FILE);
+ checkUpdateLogContains(ERR_MOVE_DESTDIR_7 + "\n" + CALL_QUIT);
+ checkCallbackLog();
+}
diff --git a/toolkit/mozapps/update/tests/unit_base_updater/marFileInUseSuccessComplete_win.js b/toolkit/mozapps/update/tests/unit_base_updater/marFileInUseSuccessComplete_win.js
new file mode 100644
index 000000000..89a2fff5e
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_base_updater/marFileInUseSuccessComplete_win.js
@@ -0,0 +1,63 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+/* File in use complete MAR file patch apply success test */
+
+function run_test() {
+ if (!setupTestCommon()) {
+ return;
+ }
+ gTestFiles = gTestFilesCompleteSuccess;
+ gTestDirs = gTestDirsCompleteSuccess;
+ setupUpdaterTest(FILE_COMPLETE_MAR, false);
+}
+
+/**
+ * Called after the call to setupUpdaterTest finishes.
+ */
+function setupUpdaterTestFinished() {
+ runHelperFileInUse(gTestFiles[13].relPathDir + gTestFiles[13].fileName,
+ false);
+}
+
+/**
+ * Called after the call to waitForHelperSleep finishes.
+ */
+function waitForHelperSleepFinished() {
+ runUpdate(STATE_SUCCEEDED, false, 0, true);
+}
+
+/**
+ * Called after the call to runUpdate finishes.
+ */
+function runUpdateFinished() {
+ waitForHelperExit();
+}
+
+/**
+ * Called after the call to waitForHelperExit finishes.
+ */
+function waitForHelperExitFinished() {
+ checkPostUpdateAppLog();
+}
+
+/**
+ * Called after the call to checkPostUpdateAppLog finishes.
+ */
+function checkPostUpdateAppLogFinished() {
+ standardInit();
+ Assert.equal(readStatusState(), STATE_NONE,
+ "the status file state" + MSG_SHOULD_EQUAL);
+ Assert.ok(!gUpdateManager.activeUpdate,
+ "the active update should not be defined");
+ Assert.equal(gUpdateManager.updateCount, 1,
+ "the update manager updateCount attribute" + MSG_SHOULD_EQUAL);
+ Assert.equal(gUpdateManager.getUpdateAt(0).state, STATE_SUCCEEDED,
+ "the update state" + MSG_SHOULD_EQUAL);
+ checkPostUpdateRunningFile(true);
+ checkFilesAfterUpdateSuccess(getApplyDirFile, false, true);
+ checkUpdateLogContains(ERR_BACKUP_DISCARD);
+ checkUpdateLogContains(STATE_SUCCEEDED + "\n" + CALL_QUIT);
+ checkCallbackLog();
+}
diff --git a/toolkit/mozapps/update/tests/unit_base_updater/marFileInUseSuccessPartial_win.js b/toolkit/mozapps/update/tests/unit_base_updater/marFileInUseSuccessPartial_win.js
new file mode 100644
index 000000000..ea85ddccc
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_base_updater/marFileInUseSuccessPartial_win.js
@@ -0,0 +1,63 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+/* File in use partial MAR file patch apply success test */
+
+function run_test() {
+ if (!setupTestCommon()) {
+ return;
+ }
+ gTestFiles = gTestFilesPartialSuccess;
+ gTestDirs = gTestDirsPartialSuccess;
+ setupUpdaterTest(FILE_PARTIAL_MAR, false);
+}
+
+/**
+ * Called after the call to setupUpdaterTest finishes.
+ */
+function setupUpdaterTestFinished() {
+ runHelperFileInUse(gTestFiles[11].relPathDir + gTestFiles[11].fileName,
+ false);
+}
+
+/**
+ * Called after the call to waitForHelperSleep finishes.
+ */
+function waitForHelperSleepFinished() {
+ runUpdate(STATE_SUCCEEDED, false, 0, true);
+}
+
+/**
+ * Called after the call to runUpdate finishes.
+ */
+function runUpdateFinished() {
+ waitForHelperExit();
+}
+
+/**
+ * Called after the call to waitForHelperExit finishes.
+ */
+function waitForHelperExitFinished() {
+ checkPostUpdateAppLog();
+}
+
+/**
+ * Called after the call to checkPostUpdateAppLog finishes.
+ */
+function checkPostUpdateAppLogFinished() {
+ standardInit();
+ Assert.equal(readStatusState(), STATE_NONE,
+ "the status file state" + MSG_SHOULD_EQUAL);
+ Assert.ok(!gUpdateManager.activeUpdate,
+ "the active update should not be defined");
+ Assert.equal(gUpdateManager.updateCount, 1,
+ "the update manager updateCount attribute" + MSG_SHOULD_EQUAL);
+ Assert.equal(gUpdateManager.getUpdateAt(0).state, STATE_SUCCEEDED,
+ "the update state" + MSG_SHOULD_EQUAL);
+ checkPostUpdateRunningFile(true);
+ checkFilesAfterUpdateSuccess(getApplyDirFile, false, true);
+ checkUpdateLogContains(ERR_BACKUP_DISCARD);
+ checkUpdateLogContains(STATE_SUCCEEDED + "\n" + CALL_QUIT);
+ checkCallbackLog();
+}
diff --git a/toolkit/mozapps/update/tests/unit_base_updater/marFileLockedFailureComplete_win.js b/toolkit/mozapps/update/tests/unit_base_updater/marFileLockedFailureComplete_win.js
new file mode 100644
index 000000000..c5efaa8c0
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_base_updater/marFileLockedFailureComplete_win.js
@@ -0,0 +1,57 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+/* File locked complete MAR file patch apply failure test */
+
+function run_test() {
+ if (!setupTestCommon()) {
+ return;
+ }
+ gTestFiles = gTestFilesCompleteSuccess;
+ gTestDirs = gTestDirsCompleteSuccess;
+ setTestFilesAndDirsForFailure();
+ setupUpdaterTest(FILE_COMPLETE_MAR, false);
+}
+
+/**
+ * Called after the call to setupUpdaterTest finishes.
+ */
+function setupUpdaterTestFinished() {
+ runHelperLockFile(gTestFiles[3]);
+}
+
+/**
+ * Called after the call to waitForHelperSleep finishes.
+ */
+function waitForHelperSleepFinished() {
+ runUpdate(STATE_FAILED_WRITE_ERROR, false, 1, true);
+}
+
+/**
+ * Called after the call to runUpdate finishes.
+ */
+function runUpdateFinished() {
+ waitForHelperExit();
+}
+
+/**
+ * Called after the call to waitForHelperExit finishes.
+ */
+function waitForHelperExitFinished() {
+ standardInit();
+ Assert.equal(readStatusFile(), STATE_PENDING,
+ "the status file failure code" + MSG_SHOULD_EQUAL);
+ Assert.equal(gUpdateManager.updateCount, 1,
+ "the update manager updateCount attribute" + MSG_SHOULD_EQUAL);
+ Assert.equal(gUpdateManager.getUpdateAt(0).state, STATE_PENDING,
+ "the update state" + MSG_SHOULD_EQUAL);
+ Assert.equal(gUpdateManager.getUpdateAt(0).errorCode, WRITE_ERROR,
+ "the update errorCode" + MSG_SHOULD_EQUAL);
+ checkPostUpdateRunningFile(false);
+ checkFilesAfterUpdateFailure(getApplyDirFile);
+ checkUpdateLogContains(ERR_RENAME_FILE);
+ checkUpdateLogContains(ERR_BACKUP_CREATE_7);
+ checkUpdateLogContains(STATE_FAILED_WRITE_ERROR + "\n" + CALL_QUIT);
+ checkCallbackLog();
+}
diff --git a/toolkit/mozapps/update/tests/unit_base_updater/marFileLockedFailurePartial_win.js b/toolkit/mozapps/update/tests/unit_base_updater/marFileLockedFailurePartial_win.js
new file mode 100644
index 000000000..4fdbadb5b
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_base_updater/marFileLockedFailurePartial_win.js
@@ -0,0 +1,56 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+/* File locked partial MAR file patch apply failure test */
+
+function run_test() {
+ if (!setupTestCommon()) {
+ return;
+ }
+ gTestFiles = gTestFilesPartialSuccess;
+ gTestDirs = gTestDirsPartialSuccess;
+ setTestFilesAndDirsForFailure();
+ setupUpdaterTest(FILE_PARTIAL_MAR, false);
+}
+
+/**
+ * Called after the call to setupUpdaterTest finishes.
+ */
+function setupUpdaterTestFinished() {
+ runHelperLockFile(gTestFiles[2]);
+}
+
+/**
+ * Called after the call to waitForHelperSleep finishes.
+ */
+function waitForHelperSleepFinished() {
+ runUpdate(STATE_FAILED_READ_ERROR, false, 1, true);
+}
+
+/**
+ * Called after the call to runUpdate finishes.
+ */
+function runUpdateFinished() {
+ waitForHelperExit();
+}
+
+/**
+ * Called after the call to waitForHelperExit finishes.
+ */
+function waitForHelperExitFinished() {
+ standardInit();
+ Assert.equal(readStatusFile(), STATE_NONE,
+ "the status file failure code" + MSG_SHOULD_EQUAL);
+ Assert.equal(gUpdateManager.updateCount, 1,
+ "the update manager updateCount attribute" + MSG_SHOULD_EQUAL);
+ Assert.equal(gUpdateManager.getUpdateAt(0).state, STATE_FAILED,
+ "the update state" + MSG_SHOULD_EQUAL);
+ Assert.equal(gUpdateManager.getUpdateAt(0).errorCode, READ_ERROR,
+ "the update errorCode" + MSG_SHOULD_EQUAL);
+ checkPostUpdateRunningFile(false);
+ checkFilesAfterUpdateFailure(getApplyDirFile);
+ checkUpdateLogContains(ERR_UNABLE_OPEN_DEST);
+ checkUpdateLogContains(STATE_FAILED_READ_ERROR + "\n" + CALL_QUIT);
+ checkCallbackLog();
+}
diff --git a/toolkit/mozapps/update/tests/unit_base_updater/marFileLockedStageFailureComplete_win.js b/toolkit/mozapps/update/tests/unit_base_updater/marFileLockedStageFailureComplete_win.js
new file mode 100644
index 000000000..4d12f4e42
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_base_updater/marFileLockedStageFailureComplete_win.js
@@ -0,0 +1,71 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+/* File locked complete MAR file staged patch apply failure test */
+
+const STATE_AFTER_STAGE = STATE_PENDING;
+
+function run_test() {
+ if (!setupTestCommon()) {
+ return;
+ }
+ gTestFiles = gTestFilesCompleteSuccess;
+ gTestDirs = gTestDirsCompleteSuccess;
+ setTestFilesAndDirsForFailure();
+ setupUpdaterTest(FILE_COMPLETE_MAR, false);
+}
+
+/**
+ * Called after the call to setupUpdaterTest finishes.
+ */
+function setupUpdaterTestFinished() {
+ runHelperLockFile(gTestFiles[3]);
+}
+
+/**
+ * Called after the call to waitForHelperSleep finishes.
+ */
+function waitForHelperSleepFinished() {
+ stageUpdate(true);
+}
+
+/**
+ * Called after the call to stageUpdate finishes.
+ */
+function stageUpdateFinished() {
+ checkPostUpdateRunningFile(false);
+ // Files aren't checked after staging since this test locks a file which
+ // prevents reading the file.
+ checkUpdateLogContains(ERR_ENSURE_COPY);
+ // Switch the application to the staged application that was updated.
+ runUpdate(STATE_FAILED_WRITE_ERROR, false, 1, false);
+}
+
+/**
+ * Called after the call to runUpdate finishes.
+ */
+function runUpdateFinished() {
+ waitForHelperExit();
+}
+
+/**
+ * Called after the call to waitForHelperExit finishes.
+ */
+function waitForHelperExitFinished() {
+ standardInit();
+ Assert.equal(readStatusFile(), STATE_PENDING,
+ "the status file failure code" + MSG_SHOULD_EQUAL);
+ Assert.equal(gUpdateManager.updateCount, 2,
+ "the update manager updateCount attribute" + MSG_SHOULD_EQUAL);
+ Assert.equal(gUpdateManager.getUpdateAt(0).state, STATE_PENDING,
+ "the update state" + MSG_SHOULD_EQUAL);
+ Assert.equal(gUpdateManager.getUpdateAt(0).errorCode, WRITE_ERROR,
+ "the update errorCode" + MSG_SHOULD_EQUAL);
+ checkPostUpdateRunningFile(false);
+ checkFilesAfterUpdateFailure(getApplyDirFile);
+ checkUpdateLogContains(ERR_RENAME_FILE);
+ checkUpdateLogContains(ERR_BACKUP_CREATE_7);
+ checkUpdateLogContains(STATE_FAILED_WRITE_ERROR + "\n" + CALL_QUIT);
+ checkCallbackLog();
+}
diff --git a/toolkit/mozapps/update/tests/unit_base_updater/marFileLockedStageFailurePartial_win.js b/toolkit/mozapps/update/tests/unit_base_updater/marFileLockedStageFailurePartial_win.js
new file mode 100644
index 000000000..5f64df34c
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_base_updater/marFileLockedStageFailurePartial_win.js
@@ -0,0 +1,70 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+/* File locked partial MAR file staged patch apply failure test */
+
+const STATE_AFTER_STAGE = STATE_PENDING;
+
+function run_test() {
+ if (!setupTestCommon()) {
+ return;
+ }
+ gTestFiles = gTestFilesPartialSuccess;
+ gTestDirs = gTestDirsPartialSuccess;
+ setTestFilesAndDirsForFailure();
+ setupUpdaterTest(FILE_PARTIAL_MAR, false);
+}
+
+/**
+ * Called after the call to setupUpdaterTest finishes.
+ */
+function setupUpdaterTestFinished() {
+ runHelperLockFile(gTestFiles[2]);
+}
+
+/**
+ * Called after the call to waitForHelperSleep finishes.
+ */
+function waitForHelperSleepFinished() {
+ stageUpdate(true);
+}
+
+/**
+ * Called after the call to stageUpdate finishes.
+ */
+function stageUpdateFinished() {
+ checkPostUpdateRunningFile(false);
+ // Files aren't checked after staging since this test locks a file which
+ // prevents reading the file.
+ checkUpdateLogContains(ERR_ENSURE_COPY);
+ // Switch the application to the staged application that was updated.
+ runUpdate(STATE_FAILED_READ_ERROR, false, 1, false);
+}
+
+/**
+ * Called after the call to runUpdate finishes.
+ */
+function runUpdateFinished() {
+ waitForHelperExit();
+}
+
+/**
+ * Called after the call to waitForHelperExit finishes.
+ */
+function waitForHelperExitFinished() {
+ standardInit();
+ Assert.equal(readStatusFile(), STATE_NONE,
+ "the status file failure code" + MSG_SHOULD_EQUAL);
+ Assert.equal(gUpdateManager.updateCount, 2,
+ "the update manager updateCount attribute" + MSG_SHOULD_EQUAL);
+ Assert.equal(gUpdateManager.getUpdateAt(0).state, STATE_FAILED,
+ "the update state" + MSG_SHOULD_EQUAL);
+ Assert.equal(gUpdateManager.getUpdateAt(0).errorCode, READ_ERROR,
+ "the update errorCode" + MSG_SHOULD_EQUAL);
+ checkPostUpdateRunningFile(false);
+ checkFilesAfterUpdateFailure(getApplyDirFile);
+ checkUpdateLogContains(ERR_UNABLE_OPEN_DEST);
+ checkUpdateLogContains(STATE_FAILED_READ_ERROR + "\n" + CALL_QUIT);
+ checkCallbackLog();
+}
diff --git a/toolkit/mozapps/update/tests/unit_base_updater/marRMRFDirFileInUseStageFailureComplete_win.js b/toolkit/mozapps/update/tests/unit_base_updater/marRMRFDirFileInUseStageFailureComplete_win.js
new file mode 100644
index 000000000..b83bafccc
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_base_updater/marRMRFDirFileInUseStageFailureComplete_win.js
@@ -0,0 +1,72 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+/* File in use inside removed dir complete MAR file staged patch apply failure
+ test */
+
+const STATE_AFTER_STAGE = IS_SERVICE_TEST ? STATE_APPLIED_SVC : STATE_APPLIED;
+const STATE_AFTER_RUNUPDATE = IS_SERVICE_TEST ? STATE_PENDING_SVC : STATE_PENDING;
+
+function run_test() {
+ if (!setupTestCommon()) {
+ return;
+ }
+ gTestFiles = gTestFilesCompleteSuccess;
+ gTestDirs = gTestDirsCompleteSuccess;
+ setupUpdaterTest(FILE_COMPLETE_MAR, false);
+}
+
+/**
+ * Called after the call to setupUpdaterTest finishes.
+ */
+function setupUpdaterTestFinished() {
+ runHelperFileInUse(gTestDirs[4].relPathDir + gTestDirs[4].subDirs[0] +
+ gTestDirs[4].subDirFiles[0], true);
+}
+
+/**
+ * Called after the call to waitForHelperSleep finishes.
+ */
+function waitForHelperSleepFinished() {
+ stageUpdate(true);
+}
+
+/**
+ * Called after the call to stageUpdate finishes.
+ */
+function stageUpdateFinished() {
+ checkPostUpdateRunningFile(false);
+ checkFilesAfterUpdateSuccess(getStageDirFile, true);
+ checkUpdateLogContents(LOG_COMPLETE_SUCCESS, true);
+ // Switch the application to the staged application that was updated.
+ runUpdate(STATE_AFTER_RUNUPDATE, true, 1, true);
+}
+
+/**
+ * Called after the call to runUpdate finishes.
+ */
+function runUpdateFinished() {
+ waitForHelperExit();
+}
+
+/**
+ * Called after the call to waitForHelperExit finishes.
+ */
+function waitForHelperExitFinished() {
+ standardInit();
+ Assert.equal(readStatusState(), STATE_NONE,
+ "the status file state" + MSG_SHOULD_EQUAL);
+ Assert.ok(!gUpdateManager.activeUpdate,
+ "the active update should not be defined");
+ Assert.equal(gUpdateManager.updateCount, 1,
+ "the update manager updateCount attribute" + MSG_SHOULD_EQUAL);
+ Assert.equal(gUpdateManager.getUpdateAt(0).state, STATE_AFTER_RUNUPDATE,
+ "the update state" + MSG_SHOULD_EQUAL);
+ checkPostUpdateRunningFile(false);
+ setTestFilesAndDirsForFailure();
+ checkFilesAfterUpdateFailure(getApplyDirFile);
+ checkUpdateLogContains(ERR_RENAME_FILE);
+ checkUpdateLogContains(ERR_MOVE_DESTDIR_7 + "\n" + CALL_QUIT);
+ checkCallbackLog();
+}
diff --git a/toolkit/mozapps/update/tests/unit_base_updater/marRMRFDirFileInUseStageFailurePartial_win.js b/toolkit/mozapps/update/tests/unit_base_updater/marRMRFDirFileInUseStageFailurePartial_win.js
new file mode 100644
index 000000000..39ea485cd
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_base_updater/marRMRFDirFileInUseStageFailurePartial_win.js
@@ -0,0 +1,71 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+/* File in use inside removed dir partial MAR file staged patch apply failure
+ test */
+
+const STATE_AFTER_STAGE = IS_SERVICE_TEST ? STATE_APPLIED_SVC : STATE_APPLIED;
+const STATE_AFTER_RUNUPDATE = IS_SERVICE_TEST ? STATE_PENDING_SVC : STATE_PENDING;
+
+function run_test() {
+ if (!setupTestCommon()) {
+ return;
+ }
+ gTestFiles = gTestFilesPartialSuccess;
+ gTestDirs = gTestDirsPartialSuccess;
+ setupUpdaterTest(FILE_PARTIAL_MAR, false);
+}
+
+/**
+ * Called after the call to setupUpdaterTest finishes.
+ */
+function setupUpdaterTestFinished() {
+ runHelperFileInUse(gTestDirs[2].relPathDir + gTestDirs[2].files[0], true);
+}
+
+/**
+ * Called after the call to waitForHelperSleep finishes.
+ */
+function waitForHelperSleepFinished() {
+ stageUpdate(true);
+}
+
+/**
+ * Called after the call to stageUpdate finishes.
+ */
+function stageUpdateFinished() {
+ checkPostUpdateRunningFile(false);
+ checkFilesAfterUpdateSuccess(getStageDirFile, true);
+ checkUpdateLogContents(LOG_PARTIAL_SUCCESS, true);
+ // Switch the application to the staged application that was updated.
+ runUpdate(STATE_AFTER_RUNUPDATE, true, 1, true);
+}
+
+/**
+ * Called after the call to runUpdate finishes.
+ */
+function runUpdateFinished() {
+ waitForHelperExit();
+}
+
+/**
+ * Called after the call to waitForHelperExit finishes.
+ */
+function waitForHelperExitFinished() {
+ standardInit();
+ Assert.equal(readStatusState(), STATE_NONE,
+ "the status file state" + MSG_SHOULD_EQUAL);
+ Assert.ok(!gUpdateManager.activeUpdate,
+ "the active update should not be defined");
+ Assert.equal(gUpdateManager.updateCount, 1,
+ "the update manager updateCount attribute" + MSG_SHOULD_EQUAL);
+ Assert.equal(gUpdateManager.getUpdateAt(0).state, STATE_AFTER_RUNUPDATE,
+ "the update state" + MSG_SHOULD_EQUAL);
+ checkPostUpdateRunningFile(false);
+ setTestFilesAndDirsForFailure();
+ checkFilesAfterUpdateFailure(getApplyDirFile);
+ checkUpdateLogContains(ERR_RENAME_FILE);
+ checkUpdateLogContains(ERR_MOVE_DESTDIR_7 + "\n" + CALL_QUIT);
+ checkCallbackLog();
+}
diff --git a/toolkit/mozapps/update/tests/unit_base_updater/marRMRFDirFileInUseSuccessComplete_win.js b/toolkit/mozapps/update/tests/unit_base_updater/marRMRFDirFileInUseSuccessComplete_win.js
new file mode 100644
index 000000000..a71bb8d49
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_base_updater/marRMRFDirFileInUseSuccessComplete_win.js
@@ -0,0 +1,63 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+/* File in use inside removed dir complete MAR file patch apply success test */
+
+function run_test() {
+ if (!setupTestCommon()) {
+ return;
+ }
+ gTestFiles = gTestFilesCompleteSuccess;
+ gTestDirs = gTestDirsCompleteSuccess;
+ setupUpdaterTest(FILE_COMPLETE_MAR, false);
+}
+
+/**
+ * Called after the call to setupUpdaterTest finishes.
+ */
+function setupUpdaterTestFinished() {
+ runHelperFileInUse(gTestDirs[4].relPathDir + gTestDirs[4].subDirs[0] +
+ gTestDirs[4].subDirFiles[0], true);
+}
+
+/**
+ * Called after the call to waitForHelperSleep finishes.
+ */
+function waitForHelperSleepFinished() {
+ runUpdate(STATE_SUCCEEDED, false, 0, true);
+}
+
+/**
+ * Called after the call to runUpdate finishes.
+ */
+function runUpdateFinished() {
+ waitForHelperExit();
+}
+
+/**
+ * Called after the call to waitForHelperExit finishes.
+ */
+function waitForHelperExitFinished() {
+ checkPostUpdateAppLog();
+}
+
+/**
+ * Called after the call to checkPostUpdateAppLog finishes.
+ */
+function checkPostUpdateAppLogFinished() {
+ standardInit();
+ Assert.equal(readStatusState(), STATE_NONE,
+ "the status file state" + MSG_SHOULD_EQUAL);
+ Assert.ok(!gUpdateManager.activeUpdate,
+ "the active update should not be defined");
+ Assert.equal(gUpdateManager.updateCount, 1,
+ "the update manager updateCount attribute" + MSG_SHOULD_EQUAL);
+ Assert.equal(gUpdateManager.getUpdateAt(0).state, STATE_SUCCEEDED,
+ "the update state" + MSG_SHOULD_EQUAL);
+ checkPostUpdateRunningFile(true);
+ checkFilesAfterUpdateSuccess(getApplyDirFile, false, true);
+ checkUpdateLogContains(ERR_BACKUP_DISCARD);
+ checkUpdateLogContains(STATE_SUCCEEDED + "\n" + CALL_QUIT);
+ checkCallbackLog();
+}
diff --git a/toolkit/mozapps/update/tests/unit_base_updater/marRMRFDirFileInUseSuccessPartial_win.js b/toolkit/mozapps/update/tests/unit_base_updater/marRMRFDirFileInUseSuccessPartial_win.js
new file mode 100644
index 000000000..2cbe70ed8
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_base_updater/marRMRFDirFileInUseSuccessPartial_win.js
@@ -0,0 +1,62 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+/* File in use inside removed dir partial MAR file patch apply success test */
+
+function run_test() {
+ if (!setupTestCommon()) {
+ return;
+ }
+ gTestFiles = gTestFilesPartialSuccess;
+ gTestDirs = gTestDirsPartialSuccess;
+ setupUpdaterTest(FILE_PARTIAL_MAR, false);
+}
+
+/**
+ * Called after the call to setupUpdaterTest finishes.
+ */
+function setupUpdaterTestFinished() {
+ runHelperFileInUse(gTestDirs[2].relPathDir + gTestDirs[2].files[0], true);
+}
+
+/**
+ * Called after the call to waitForHelperSleep finishes.
+ */
+function waitForHelperSleepFinished() {
+ runUpdate(STATE_SUCCEEDED, false, 0, true);
+}
+
+/**
+ * Called after the call to runUpdate finishes.
+ */
+function runUpdateFinished() {
+ waitForHelperExit();
+}
+
+/**
+ * Called after the call to waitForHelperExit finishes.
+ */
+function waitForHelperExitFinished() {
+ checkPostUpdateAppLog();
+}
+
+/**
+ * Called after the call to checkPostUpdateAppLog finishes.
+ */
+function checkPostUpdateAppLogFinished() {
+ standardInit();
+ Assert.equal(readStatusState(), STATE_NONE,
+ "the status file state" + MSG_SHOULD_EQUAL);
+ Assert.ok(!gUpdateManager.activeUpdate,
+ "the active update should not be defined");
+ Assert.equal(gUpdateManager.updateCount, 1,
+ "the update manager updateCount attribute" + MSG_SHOULD_EQUAL);
+ Assert.equal(gUpdateManager.getUpdateAt(0).state, STATE_SUCCEEDED,
+ "the update state" + MSG_SHOULD_EQUAL);
+ checkPostUpdateRunningFile(true);
+ checkFilesAfterUpdateSuccess(getApplyDirFile, false, true);
+ checkUpdateLogContains(ERR_BACKUP_DISCARD);
+ checkUpdateLogContains(STATE_SUCCEEDED + "\n" + CALL_QUIT);
+ checkCallbackLog();
+}
diff --git a/toolkit/mozapps/update/tests/unit_base_updater/marStageFailurePartial.js b/toolkit/mozapps/update/tests/unit_base_updater/marStageFailurePartial.js
new file mode 100644
index 000000000..a9ce23420
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_base_updater/marStageFailurePartial.js
@@ -0,0 +1,41 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+/* General Partial MAR File Staged Patch Apply Failure Test */
+
+const STATE_AFTER_STAGE = STATE_FAILED;
+gStagingRemovedUpdate = true;
+
+function run_test() {
+ if (!setupTestCommon()) {
+ return;
+ }
+ gTestFiles = gTestFilesPartialSuccess;
+ gTestFiles[11].originalFile = "partial.png";
+ gTestDirs = gTestDirsPartialSuccess;
+ setTestFilesAndDirsForFailure();
+ setupUpdaterTest(FILE_PARTIAL_MAR, false);
+}
+
+/**
+ * Called after the call to setupUpdaterTest finishes.
+ */
+function setupUpdaterTestFinished() {
+ stageUpdate(true);
+}
+
+/**
+ * Called after the call to stageUpdate finishes.
+ */
+function stageUpdateFinished() {
+ Assert.equal(readStatusState(), STATE_NONE,
+ "the status file state" + MSG_SHOULD_EQUAL);
+ Assert.equal(gUpdateManager.getUpdateAt(0).errorCode, LOADSOURCE_ERROR_WRONG_SIZE,
+ "the update errorCode" + MSG_SHOULD_EQUAL);
+ checkPostUpdateRunningFile(false);
+ checkFilesAfterUpdateFailure(getApplyDirFile);
+ checkUpdateLogContains(ERR_LOADSOURCEFILE_FAILED);
+ waitForFilesInUse();
+}
diff --git a/toolkit/mozapps/update/tests/unit_base_updater/marStageSuccessComplete.js b/toolkit/mozapps/update/tests/unit_base_updater/marStageSuccessComplete.js
new file mode 100644
index 000000000..f7745f68f
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_base_updater/marStageSuccessComplete.js
@@ -0,0 +1,132 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+/* General Complete MAR File Staged Patch Apply Test */
+
+const STATE_AFTER_STAGE = IS_SERVICE_TEST ? STATE_APPLIED_SVC : STATE_APPLIED;
+
+function run_test() {
+ if (!setupTestCommon()) {
+ return;
+ }
+ gTestFiles = gTestFilesCompleteSuccess;
+ gTestFiles[gTestFiles.length - 1].originalContents = null;
+ gTestFiles[gTestFiles.length - 1].compareContents = "FromComplete\n";
+ gTestFiles[gTestFiles.length - 1].comparePerms = 0o644;
+ gTestDirs = gTestDirsCompleteSuccess;
+ setupDistributionDir();
+ setupSymLinks();
+ setupUpdaterTest(FILE_COMPLETE_MAR, false);
+}
+
+/**
+ * Called after the call to setupUpdaterTest finishes.
+ */
+function setupUpdaterTestFinished() {
+ stageUpdate(true);
+}
+
+/**
+ * Called after the call to stageUpdate finishes.
+ */
+function stageUpdateFinished() {
+ checkPostUpdateRunningFile(false);
+ checkFilesAfterUpdateSuccess(getStageDirFile, true);
+ checkUpdateLogContents(LOG_COMPLETE_SUCCESS, true);
+ // Switch the application to the staged application that was updated.
+ runUpdate(STATE_SUCCEEDED, true, 0, true);
+}
+
+/**
+ * Called after the call to runUpdate finishes.
+ */
+function runUpdateFinished() {
+ checkPostUpdateAppLog();
+}
+
+/**
+ * Called after the call to checkPostUpdateAppLog finishes.
+ */
+function checkPostUpdateAppLogFinished() {
+ checkAppBundleModTime();
+ checkSymLinks();
+ standardInit();
+ Assert.equal(readStatusState(), STATE_NONE,
+ "the status file state" + MSG_SHOULD_EQUAL);
+ Assert.ok(!gUpdateManager.activeUpdate,
+ "the active update should not be defined");
+ Assert.equal(gUpdateManager.updateCount, 1,
+ "the update manager updateCount attribute" + MSG_SHOULD_EQUAL);
+ Assert.equal(gUpdateManager.getUpdateAt(0).state, STATE_SUCCEEDED,
+ "the update state" + MSG_SHOULD_EQUAL);
+ checkPostUpdateRunningFile(true);
+ checkFilesAfterUpdateSuccess(getApplyDirFile, false, true);
+ checkUpdateLogContents(LOG_REPLACE_SUCCESS, false, true);
+ checkDistributionDir();
+ checkCallbackLog();
+}
+
+/**
+ * Setup the state of the distribution directory for the test.
+ */
+function setupDistributionDir() {
+ if (IS_MACOSX) {
+ // Create files in the old distribution directory location to verify that
+ // the directory and its contents are removed when there is a distribution
+ // directory in the new location.
+ let testFile = getApplyDirFile(DIR_MACOS + "distribution/testFile", true);
+ writeFile(testFile, "test\n");
+ testFile = getApplyDirFile(DIR_MACOS + "distribution/test1/testFile", true);
+ writeFile(testFile, "test\n");
+ }
+}
+
+/**
+ * Checks the state of the distribution directory for the test.
+ */
+function checkDistributionDir() {
+ if (IS_MACOSX) {
+ let distributionDir = getApplyDirFile(DIR_MACOS + "distribution", true);
+ Assert.ok(!distributionDir.exists(),
+ MSG_SHOULD_NOT_EXIST + getMsgPath(distributionDir.path));
+ checkUpdateLogContains(REMOVE_OLD_DIST_DIR);
+ }
+}
+
+/**
+ * Setup symlinks for the test.
+ */
+function setupSymLinks() {
+ // Don't test symlinks on Mac OS X in this test since it tends to timeout.
+ // It is tested on Mac OS X in marAppInUseStageSuccessComplete_unix.js
+ if (IS_UNIX && !IS_MACOSX) {
+ removeSymlink();
+ createSymlink();
+ do_register_cleanup(removeSymlink);
+ gTestFiles.splice(gTestFiles.length - 3, 0,
+ {
+ description: "Readable symlink",
+ fileName: "link",
+ relPathDir: DIR_RESOURCES,
+ originalContents: "test",
+ compareContents: "test",
+ originalFile: null,
+ compareFile: null,
+ originalPerms: 0o666,
+ comparePerms: 0o666
+ });
+ }
+}
+
+/**
+ * Checks the state of the symlinks for the test.
+ */
+function checkSymLinks() {
+ // Don't test symlinks on Mac OS X in this test since it tends to timeout.
+ // It is tested on Mac OS X in marAppInUseStageSuccessComplete_unix.js
+ if (IS_UNIX && !IS_MACOSX) {
+ checkSymlink();
+ }
+}
diff --git a/toolkit/mozapps/update/tests/unit_base_updater/marStageSuccessPartial.js b/toolkit/mozapps/update/tests/unit_base_updater/marStageSuccessPartial.js
new file mode 100644
index 000000000..ef15326de
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_base_updater/marStageSuccessPartial.js
@@ -0,0 +1,112 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+/* General Partial MAR File Staged Patch Apply Test */
+
+const STATE_AFTER_STAGE = IS_SERVICE_TEST ? STATE_APPLIED_SVC : STATE_APPLIED;
+
+function run_test() {
+ if (!setupTestCommon()) {
+ return;
+ }
+ gTestFiles = gTestFilesPartialSuccess;
+ gTestFiles[gTestFiles.length - 2].originalContents = null;
+ gTestFiles[gTestFiles.length - 2].compareContents = "FromPartial\n";
+ gTestFiles[gTestFiles.length - 2].comparePerms = 0o644;
+ gTestDirs = gTestDirsPartialSuccess;
+ preventDistributionFiles();
+ setupDistributionDir();
+ setupUpdaterTest(FILE_PARTIAL_MAR, true);
+}
+
+/**
+ * Called after the call to setupUpdaterTest finishes.
+ */
+function setupUpdaterTestFinished() {
+ stageUpdate(true);
+}
+
+/**
+ * Called after the call to stageUpdate finishes.
+ */
+function stageUpdateFinished() {
+ checkPostUpdateRunningFile(false);
+ checkFilesAfterUpdateSuccess(getStageDirFile, true);
+ checkUpdateLogContents(LOG_PARTIAL_SUCCESS, true, false, true);
+ // Switch the application to the staged application that was updated.
+ runUpdate(STATE_SUCCEEDED, true, 0, true);
+}
+
+/**
+ * Called after the call to runUpdate finishes.
+ */
+function runUpdateFinished() {
+ checkPostUpdateAppLog();
+}
+
+/**
+ * Called after the call to checkPostUpdateAppLog finishes.
+ */
+function checkPostUpdateAppLogFinished() {
+ checkAppBundleModTime();
+ standardInit();
+ Assert.equal(readStatusState(), STATE_NONE,
+ "the status file state" + MSG_SHOULD_EQUAL);
+ Assert.ok(!gUpdateManager.activeUpdate,
+ "the active update should not be defined");
+ Assert.equal(gUpdateManager.updateCount, 1,
+ "the update manager updateCount attribute" + MSG_SHOULD_EQUAL);
+ Assert.equal(gUpdateManager.getUpdateAt(0).state, STATE_SUCCEEDED,
+ "the update state" + MSG_SHOULD_EQUAL);
+ checkPostUpdateRunningFile(true);
+ checkFilesAfterUpdateSuccess(getApplyDirFile, false, true);
+ checkUpdateLogContents(LOG_REPLACE_SUCCESS, false, true, true);
+ checkDistributionDir();
+ checkCallbackLog();
+}
+
+/**
+ * Setup the state of the distribution directory for the test.
+ */
+function setupDistributionDir() {
+ if (IS_MACOSX) {
+ // Create files in the old distribution directory location to verify that
+ // the directory and its contents are moved to the new location on update.
+ let testFile = getApplyDirFile(DIR_MACOS + "distribution/testFile", true);
+ writeFile(testFile, "test\n");
+ testFile = getApplyDirFile(DIR_MACOS + "distribution/test/testFile", true);
+ writeFile(testFile, "test\n");
+ }
+}
+
+/**
+ * Checks the state of the distribution directory.
+ */
+function checkDistributionDir() {
+ let distributionDir = getApplyDirFile(DIR_RESOURCES + "distribution", true);
+ if (IS_MACOSX) {
+ Assert.ok(distributionDir.exists(),
+ MSG_SHOULD_EXIST + getMsgPath(distributionDir.path));
+
+ let testFile = getApplyDirFile(DIR_RESOURCES + "distribution/testFile", true);
+ Assert.ok(testFile.exists(),
+ MSG_SHOULD_EXIST + getMsgPath(testFile.path));
+
+ testFile = getApplyDirFile(DIR_RESOURCES + "distribution/test/testFile", true);
+ Assert.ok(testFile.exists(),
+ MSG_SHOULD_EXIST + getMsgPath(testFile.path));
+
+ distributionDir = getApplyDirFile(DIR_MACOS + "distribution", true);
+ Assert.ok(!distributionDir.exists(),
+ MSG_SHOULD_NOT_EXIST + getMsgPath(distributionDir.path));
+
+ checkUpdateLogContains(MOVE_OLD_DIST_DIR);
+ } else {
+ debugDump("testing that files aren't added with an add-if instruction " +
+ "when the file's destination directory doesn't exist");
+ Assert.ok(!distributionDir.exists(),
+ MSG_SHOULD_NOT_EXIST + getMsgPath(distributionDir.path));
+ }
+}
diff --git a/toolkit/mozapps/update/tests/unit_base_updater/marSuccessComplete.js b/toolkit/mozapps/update/tests/unit_base_updater/marSuccessComplete.js
new file mode 100644
index 000000000..1008e867f
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_base_updater/marSuccessComplete.js
@@ -0,0 +1,96 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+/* General Complete MAR File Patch Apply Test */
+
+function run_test() {
+ if (!setupTestCommon()) {
+ return;
+ }
+ gTestFiles = gTestFilesCompleteSuccess;
+ gTestDirs = gTestDirsCompleteSuccess;
+ preventDistributionFiles();
+ setupDistributionDir();
+ setupUpdaterTest(FILE_COMPLETE_MAR, true);
+}
+
+/**
+ * Called after the call to setupUpdaterTest finishes.
+ */
+function setupUpdaterTestFinished() {
+ runUpdate(STATE_SUCCEEDED, false, 0, true);
+}
+
+/**
+ * Called after the call to runUpdate finishes.
+ */
+function runUpdateFinished() {
+ checkPostUpdateAppLog();
+}
+
+/**
+ * Called after the call to checkPostUpdateAppLog finishes.
+ */
+function checkPostUpdateAppLogFinished() {
+ checkAppBundleModTime();
+ standardInit();
+ Assert.equal(readStatusState(), STATE_NONE,
+ "the status file state" + MSG_SHOULD_EQUAL);
+ Assert.ok(!gUpdateManager.activeUpdate,
+ "the active update should not be defined");
+ Assert.equal(gUpdateManager.updateCount, 1,
+ "the update manager updateCount attribute" + MSG_SHOULD_EQUAL);
+ Assert.equal(gUpdateManager.getUpdateAt(0).state, STATE_SUCCEEDED,
+ "the update state" + MSG_SHOULD_EQUAL);
+ checkPostUpdateRunningFile(true);
+ checkFilesAfterUpdateSuccess(getApplyDirFile);
+ checkUpdateLogContents(LOG_COMPLETE_SUCCESS, false, false, true);
+ checkDistributionDir();
+ checkCallbackLog();
+}
+
+/**
+ * Setup the state of the distribution directory for the test.
+ */
+function setupDistributionDir() {
+ if (IS_MACOSX) {
+ // Create files in the old distribution directory location to verify that
+ // the directory and its contents are moved to the new location on update.
+ let testFile = getApplyDirFile(DIR_MACOS + "distribution/testFile", true);
+ writeFile(testFile, "test\n");
+ testFile = getApplyDirFile(DIR_MACOS + "distribution/test/testFile", true);
+ writeFile(testFile, "test\n");
+ }
+}
+
+/**
+ * Checks the state of the distribution directory.
+ */
+function checkDistributionDir() {
+ let distributionDir = getApplyDirFile(DIR_RESOURCES + "distribution", true);
+ if (IS_MACOSX) {
+ Assert.ok(distributionDir.exists(),
+ MSG_SHOULD_EXIST + getMsgPath(distributionDir.path));
+
+ let testFile = getApplyDirFile(DIR_RESOURCES + "distribution/testFile", true);
+ Assert.ok(testFile.exists(),
+ MSG_SHOULD_EXIST + getMsgPath(testFile.path));
+
+ testFile = getApplyDirFile(DIR_RESOURCES + "distribution/test/testFile", true);
+ Assert.ok(testFile.exists(),
+ MSG_SHOULD_EXIST + getMsgPath(testFile.path));
+
+ distributionDir = getApplyDirFile(DIR_MACOS + "distribution", true);
+ Assert.ok(!distributionDir.exists(),
+ MSG_SHOULD_NOT_EXIST + getMsgPath(distributionDir.path));
+
+ checkUpdateLogContains(MOVE_OLD_DIST_DIR);
+ } else {
+ debugDump("testing that files aren't added with an add-if instruction " +
+ "when the file's destination directory doesn't exist");
+ Assert.ok(!distributionDir.exists(),
+ MSG_SHOULD_NOT_EXIST + getMsgPath(distributionDir.path));
+ }
+}
diff --git a/toolkit/mozapps/update/tests/unit_base_updater/marSuccessPartial.js b/toolkit/mozapps/update/tests/unit_base_updater/marSuccessPartial.js
new file mode 100644
index 000000000..616390f55
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_base_updater/marSuccessPartial.js
@@ -0,0 +1,79 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+/* General Partial MAR File Patch Apply Test */
+
+function run_test() {
+ if (!setupTestCommon()) {
+ return;
+ }
+ gTestFiles = gTestFilesPartialSuccess;
+ gTestFiles[gTestFiles.length - 1].originalContents = null;
+ gTestFiles[gTestFiles.length - 1].compareContents = "FromPartial\n";
+ gTestFiles[gTestFiles.length - 1].comparePerms = 0o644;
+ gTestFiles[gTestFiles.length - 2].originalContents = null;
+ gTestFiles[gTestFiles.length - 2].compareContents = "FromPartial\n";
+ gTestFiles[gTestFiles.length - 2].comparePerms = 0o644;
+ gTestDirs = gTestDirsPartialSuccess;
+ setupDistributionDir();
+ // The third parameter will test that a relative path that contains a
+ // directory traversal to the post update binary doesn't execute.
+ setupUpdaterTest(FILE_PARTIAL_MAR, false, "test/../");
+}
+
+/**
+ * Called after the call to setupUpdaterTest finishes.
+ */
+function setupUpdaterTestFinished() {
+ runUpdate(STATE_SUCCEEDED, false, 0, true);
+}
+
+/**
+ * Called after the call to runUpdate finishes.
+ */
+function runUpdateFinished() {
+ checkAppBundleModTime();
+ standardInit();
+ Assert.equal(readStatusState(), STATE_NONE,
+ "the status file state" + MSG_SHOULD_EQUAL);
+ Assert.ok(!gUpdateManager.activeUpdate,
+ "the active update should not be defined");
+ Assert.equal(gUpdateManager.updateCount, 1,
+ "the update manager updateCount attribute" + MSG_SHOULD_EQUAL);
+ Assert.equal(gUpdateManager.getUpdateAt(0).state, STATE_SUCCEEDED,
+ "the update state" + MSG_SHOULD_EQUAL);
+ checkPostUpdateRunningFile(false);
+ checkFilesAfterUpdateSuccess(getApplyDirFile);
+ checkUpdateLogContents(LOG_PARTIAL_SUCCESS);
+ checkDistributionDir();
+ checkCallbackLog();
+}
+
+/**
+ * Setup the state of the distribution directory for the test.
+ */
+function setupDistributionDir() {
+ if (IS_MACOSX) {
+ // Create files in the old distribution directory location to verify that
+ // the directory and its contents are removed when there is a distribution
+ // directory in the new location.
+ let testFile = getApplyDirFile(DIR_MACOS + "distribution/testFile", true);
+ writeFile(testFile, "test\n");
+ testFile = getApplyDirFile(DIR_MACOS + "distribution/test/testFile", true);
+ writeFile(testFile, "test\n");
+ }
+}
+
+/**
+ * Checks the state of the distribution directory.
+ */
+function checkDistributionDir() {
+ if (IS_MACOSX) {
+ let distributionDir = getApplyDirFile(DIR_MACOS + "distribution", true);
+ Assert.ok(!distributionDir.exists(),
+ MSG_SHOULD_NOT_EXIST + getMsgPath(distributionDir.path));
+ checkUpdateLogContains(REMOVE_OLD_DIST_DIR);
+ }
+}
diff --git a/toolkit/mozapps/update/tests/unit_base_updater/marVersionDowngrade.js b/toolkit/mozapps/update/tests/unit_base_updater/marVersionDowngrade.js
new file mode 100644
index 000000000..86a2eb821
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_base_updater/marVersionDowngrade.js
@@ -0,0 +1,51 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+/* Test version downgrade MAR security check */
+
+function run_test() {
+ if (!MOZ_VERIFY_MAR_SIGNATURE) {
+ return;
+ }
+
+ if (!setupTestCommon()) {
+ return;
+ }
+ gTestFiles = gTestFilesCompleteSuccess;
+ gTestDirs = gTestDirsCompleteSuccess;
+ setTestFilesAndDirsForFailure();
+ setupUpdaterTest(FILE_OLD_VERSION_MAR, false);
+}
+
+/**
+ * Called after the call to setupUpdaterTest finishes.
+ */
+function setupUpdaterTestFinished() {
+ // If execv is used the updater process will turn into the callback process
+ // and the updater's return code will be that of the callback process.
+ runUpdate(STATE_FAILED_VERSION_DOWNGRADE_ERROR, false, (USE_EXECV ? 0 : 1),
+ false);
+}
+
+/**
+ * Called after the call to runUpdateUsingUpdater finishes.
+ */
+function runUpdateFinished() {
+ standardInit();
+ Assert.equal(readStatusState(), STATE_NONE,
+ "the status file state" + MSG_SHOULD_EQUAL);
+ Assert.ok(!gUpdateManager.activeUpdate,
+ "the active update should not be defined");
+ Assert.equal(gUpdateManager.updateCount, 1,
+ "the update manager updateCount attribute" + MSG_SHOULD_EQUAL);
+ Assert.equal(gUpdateManager.getUpdateAt(0).state, STATE_FAILED,
+ "the update state" + MSG_SHOULD_EQUAL);
+ Assert.equal(gUpdateManager.getUpdateAt(0).errorCode, VERSION_DOWNGRADE_ERROR,
+ "the update errorCode" + MSG_SHOULD_EQUAL);
+ checkPostUpdateRunningFile(false);
+ checkFilesAfterUpdateFailure(getApplyDirFile);
+ checkUpdateLogContains(STATE_FAILED_VERSION_DOWNGRADE_ERROR);
+ waitForFilesInUse();
+}
diff --git a/toolkit/mozapps/update/tests/unit_base_updater/marWrongChannel.js b/toolkit/mozapps/update/tests/unit_base_updater/marWrongChannel.js
new file mode 100644
index 000000000..6db906fbc
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_base_updater/marWrongChannel.js
@@ -0,0 +1,51 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+/* Test product/channel MAR security check */
+
+function run_test() {
+ if (!MOZ_VERIFY_MAR_SIGNATURE) {
+ return;
+ }
+
+ if (!setupTestCommon()) {
+ return;
+ }
+ gTestFiles = gTestFilesCompleteSuccess;
+ gTestDirs = gTestDirsCompleteSuccess;
+ setTestFilesAndDirsForFailure();
+ setupUpdaterTest(FILE_WRONG_CHANNEL_MAR, false);
+}
+
+/**
+ * Called after the call to setupUpdaterTest finishes.
+ */
+function setupUpdaterTestFinished() {
+ // If execv is used the updater process will turn into the callback process
+ // and the updater's return code will be that of the callback process.
+ runUpdate(STATE_FAILED_MAR_CHANNEL_MISMATCH_ERROR, false, (USE_EXECV ? 0 : 1),
+ false);
+}
+
+/**
+ * Called after the call to runUpdateUsingUpdater finishes.
+ */
+function runUpdateFinished() {
+ standardInit();
+ Assert.equal(readStatusState(), STATE_NONE,
+ "the status file state" + MSG_SHOULD_EQUAL);
+ Assert.ok(!gUpdateManager.activeUpdate,
+ "the active update should not be defined");
+ Assert.equal(gUpdateManager.updateCount, 1,
+ "the update manager updateCount attribute" + MSG_SHOULD_EQUAL);
+ Assert.equal(gUpdateManager.getUpdateAt(0).state, STATE_FAILED,
+ "the update state" + MSG_SHOULD_EQUAL);
+ Assert.equal(gUpdateManager.getUpdateAt(0).errorCode, MAR_CHANNEL_MISMATCH_ERROR,
+ "the update errorCode" + MSG_SHOULD_EQUAL);
+ checkPostUpdateRunningFile(false);
+ checkFilesAfterUpdateFailure(getApplyDirFile);
+ checkUpdateLogContains(STATE_FAILED_MAR_CHANNEL_MISMATCH_ERROR);
+ waitForFilesInUse();
+}
diff --git a/toolkit/mozapps/update/tests/unit_base_updater/xpcshell.ini b/toolkit/mozapps/update/tests/unit_base_updater/xpcshell.ini
new file mode 100644
index 000000000..2b77bee7a
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_base_updater/xpcshell.ini
@@ -0,0 +1,136 @@
+; This Source Code Form is subject to the terms of the Mozilla Public
+; License, v. 2.0. If a copy of the MPL was not distributed with this
+; file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+; Tests that require the updater binary. These tests should never run on Android
+; which doesn't use the updater binary as other applications do and are excluded
+; from running the tests in the moz.build file.
+
+[DEFAULT]
+tags = appupdate
+head = head_update.js
+tail =
+
+[invalidArgCallbackFileNotInInstallDirFailure.js]
+skip-if = os == 'win' && debug && (os_version == '5.1' || os_version == '5.2')
+reason = bug 1291985
+[invalidArgCallbackFilePathTooLongFailure.js]
+skip-if = os == 'win' && debug && (os_version == '5.1' || os_version == '5.2')
+reason = bug 1291985
+[invalidArgInstallDirPathTooLongFailure.js]
+skip-if = os == 'win' && debug && (os_version == '5.1' || os_version == '5.2')
+reason = bug 1291985
+[invalidArgInstallDirPathTraversalFailure.js]
+skip-if = os == 'win' && debug && (os_version == '5.1' || os_version == '5.2')
+reason = bug 1291985
+[invalidArgInstallWorkingDirPathNotSameFailure_win.js]
+skip-if = os != 'win' || debug && (os_version == '5.1' || os_version == '5.2')
+reason = Windows only test and bug 1291985
+[invalidArgPatchDirPathTraversalFailure.js]
+skip-if = os == 'win' && debug && (os_version == '5.1' || os_version == '5.2')
+reason = bug 1291985
+[invalidArgStageDirNotInInstallDirFailure_win.js]
+skip-if = os != 'win' || debug && (os_version == '5.1' || os_version == '5.2')
+reason = Windows only test and bug 1291985
+[invalidArgWorkingDirPathLocalUNCFailure_win.js]
+skip-if = os != 'win' || debug && (os_version == '5.1' || os_version == '5.2')
+reason = Windows only test and bug 1291985
+[invalidArgWorkingDirPathRelativeFailure.js]
+skip-if = os == 'win' && debug && (os_version == '5.1' || os_version == '5.2')
+reason = bug 1291985
+[marSuccessComplete.js]
+skip-if = os == 'win' && debug && (os_version == '5.1' || os_version == '5.2')
+reason = bug 1291985
+[marSuccessPartial.js]
+skip-if = os == 'win' && debug && (os_version == '5.1' || os_version == '5.2')
+reason = bug 1291985
+[marFailurePartial.js]
+skip-if = os == 'win' && debug && (os_version == '5.1' || os_version == '5.2')
+reason = bug 1291985
+[marStageSuccessComplete.js]
+skip-if = os == 'win' && debug && (os_version == '5.1' || os_version == '5.2')
+reason = bug 1291985
+[marStageSuccessPartial.js]
+skip-if = os == 'win' && debug && (os_version == '5.1' || os_version == '5.2')
+reason = bug 1291985
+[marVersionDowngrade.js]
+skip-if = os == 'win' && debug && (os_version == '5.1' || os_version == '5.2')
+reason = bug 1291985 and mar signing
+[marWrongChannel.js]
+skip-if = os == 'win' && debug && (os_version == '5.1' || os_version == '5.2')
+reason = bug 1291985 and mar signing
+[marStageFailurePartial.js]
+skip-if = os == 'win' && debug && (os_version == '5.1' || os_version == '5.2')
+reason = bug 1291985
+[marCallbackAppSuccessComplete_win.js]
+skip-if = os != 'win' || debug && (os_version == '5.1' || os_version == '5.2')
+reason = Windows only test and bug 1291985
+[marCallbackAppSuccessPartial_win.js]
+skip-if = os != 'win' || debug && (os_version == '5.1' || os_version == '5.2')
+reason = Windows only test and bug 1291985
+[marCallbackAppStageSuccessComplete_win.js]
+skip-if = os != 'win' || debug && (os_version == '5.1' || os_version == '5.2')
+reason = Windows only test and bug 1291985
+[marCallbackAppStageSuccessPartial_win.js]
+skip-if = os != 'win' || debug && (os_version == '5.1' || os_version == '5.2')
+reason = Windows only test and bug 1291985
+[marAppInUseSuccessComplete.js]
+skip-if = os == 'win' && debug && (os_version == '5.1' || os_version == '5.2')
+reason = bug 1291985
+[marAppInUseStageSuccessComplete_unix.js]
+skip-if = os == 'win'
+reason = not a Windows test
+[marAppInUseStageFailureComplete_win.js]
+skip-if = os != 'win' || debug && (os_version == '5.1' || os_version == '5.2')
+reason = Windows only test and bug 1291985
+[marFileLockedFailureComplete_win.js]
+skip-if = os != 'win' || debug && (os_version == '5.1' || os_version == '5.2')
+reason = Windows only test and bug 1291985
+[marFileLockedFailurePartial_win.js]
+skip-if = os != 'win' || debug && (os_version == '5.1' || os_version == '5.2')
+reason = Windows only test and bug 1291985
+[marFileLockedStageFailureComplete_win.js]
+skip-if = os != 'win' || debug && (os_version == '5.1' || os_version == '5.2')
+reason = Windows only test and bug 1291985
+[marFileLockedStageFailurePartial_win.js]
+skip-if = os != 'win' || debug && (os_version == '5.1' || os_version == '5.2')
+reason = Windows only test and bug 1291985
+[marFileInUseSuccessComplete_win.js]
+skip-if = os != 'win' || debug && (os_version == '5.1' || os_version == '5.2')
+reason = Windows only test and bug 1291985
+[marFileInUseSuccessPartial_win.js]
+skip-if = os != 'win' || debug && (os_version == '5.1' || os_version == '5.2')
+reason = Windows only test and bug 1291985
+[marRMRFDirFileInUseSuccessComplete_win.js]
+skip-if = os != 'win' || debug && (os_version == '5.1' || os_version == '5.2')
+reason = Windows only test and bug 1291985
+[marRMRFDirFileInUseSuccessPartial_win.js]
+skip-if = os != 'win' || debug && (os_version == '5.1' || os_version == '5.2')
+reason = Windows only test and bug 1291985
+[marFileInUseStageFailureComplete_win.js]
+skip-if = os != 'win' || debug && (os_version == '5.1' || os_version == '5.2')
+reason = Windows only test and bug 1291985
+[marFileInUseStageFailurePartial_win.js]
+skip-if = os != 'win' || debug && (os_version == '5.1' || os_version == '5.2')
+reason = Windows only test and bug 1291985
+[marRMRFDirFileInUseStageFailureComplete_win.js]
+skip-if = os != 'win' || debug && (os_version == '5.1' || os_version == '5.2')
+reason = Windows only test and bug 1291985
+[marRMRFDirFileInUseStageFailurePartial_win.js]
+skip-if = os != 'win' || debug && (os_version == '5.1' || os_version == '5.2')
+reason = Windows only test and bug 1291985
+[marAppApplyDirLockedStageFailure_win.js]
+skip-if = os != 'win' || debug && (os_version == '5.1' || os_version == '5.2')
+reason = Windows only test and bug 1291985
+[marAppApplyUpdateAppBinInUseStageSuccess_win.js]
+skip-if = os != 'win' || debug && (os_version == '5.1' || os_version == '5.2')
+reason = Windows only test and bug 1291985
+[marAppApplyUpdateSuccess.js]
+skip-if = os == 'win' && debug && (os_version == '5.1' || os_version == '5.2')
+reason = bug 1291985
+[marAppApplyUpdateStageSuccess.js]
+skip-if = os == 'win' && debug && (os_version == '5.1' || os_version == '5.2')
+reason = bug 1291985
+[marAppApplyUpdateStageOldVersionFailure.js]
+skip-if = os == 'win' && debug && (os_version == '5.1' || os_version == '5.2')
+reason = bug 1291985
diff --git a/toolkit/mozapps/update/tests/unit_service_updater/.eslintrc.js b/toolkit/mozapps/update/tests/unit_service_updater/.eslintrc.js
new file mode 100644
index 000000000..d35787cd2
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_service_updater/.eslintrc.js
@@ -0,0 +1,7 @@
+"use strict";
+
+module.exports = {
+ "extends": [
+ "../../../../../testing/xpcshell/xpcshell.eslintrc.js"
+ ]
+};
diff --git a/toolkit/mozapps/update/tests/unit_service_updater/bootstrapSvc.js b/toolkit/mozapps/update/tests/unit_service_updater/bootstrapSvc.js
new file mode 100644
index 000000000..015fbd0cb
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_service_updater/bootstrapSvc.js
@@ -0,0 +1,35 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+/* Bootstrap the tests using the service by installing our own version of the service */
+
+function run_test() {
+ if (!setupTestCommon()) {
+ return;
+ }
+ // We don't actually care if the MAR has any data, we only care about the
+ // application return code and update.status result.
+ gTestFiles = gTestFilesCommon;
+ gTestDirs = [];
+ setupUpdaterTest(FILE_COMPLETE_MAR, null);
+}
+
+/**
+ * Called after the call to setupUpdaterTest finishes.
+ */
+function setupUpdaterTestFinished() {
+ runUpdate(STATE_SUCCEEDED, false, 0, true);
+}
+
+/**
+ * Called after the call to runUpdateUsingService finishes.
+ */
+function runUpdateFinished() {
+ checkFilesAfterUpdateSuccess(getApplyDirFile, false, false);
+
+ // We need to check the service log even though this is a bootstrap
+ // because the app bin could be in use by this test by the time the next
+ // test runs.
+ checkCallbackLog();
+}
diff --git a/toolkit/mozapps/update/tests/unit_service_updater/checkUpdaterSigSvc.js b/toolkit/mozapps/update/tests/unit_service_updater/checkUpdaterSigSvc.js
new file mode 100644
index 000000000..bf765ee78
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_service_updater/checkUpdaterSigSvc.js
@@ -0,0 +1,39 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+/**
+ * We skip authenticode cert checks from the service udpates
+ * so that we can use updater-xpcshell with the wrong certs for testing.
+ * This tests that code path. */
+
+function run_test() {
+ if (!IS_AUTHENTICODE_CHECK_ENABLED) {
+ return;
+ }
+
+ let binDir = getGREBinDir();
+ let maintenanceServiceBin = binDir.clone();
+ maintenanceServiceBin.append(FILE_MAINTENANCE_SERVICE_BIN);
+
+ let updaterBin = binDir.clone();
+ updaterBin.append(FILE_UPDATER_BIN);
+
+ debugDump("Launching maintenance service bin: " +
+ maintenanceServiceBin.path + " to check updater: " +
+ updaterBin.path + " signature.");
+
+ // Bypass the manifest and run as invoker
+ gEnv.set("__COMPAT_LAYER", "RunAsInvoker");
+
+ let dummyInstallPath = "---";
+ let maintenanceServiceBinArgs = ["check-cert", dummyInstallPath,
+ updaterBin.path];
+ let maintenanceServiceBinProcess = Cc["@mozilla.org/process/util;1"].
+ createInstance(Ci.nsIProcess);
+ maintenanceServiceBinProcess.init(maintenanceServiceBin);
+ maintenanceServiceBinProcess.run(true, maintenanceServiceBinArgs,
+ maintenanceServiceBinArgs.length);
+ Assert.equal(maintenanceServiceBinProcess.exitValue, 0,
+ "the maintenance service exit value should be 0");
+}
diff --git a/toolkit/mozapps/update/tests/unit_service_updater/head_update.js b/toolkit/mozapps/update/tests/unit_service_updater/head_update.js
new file mode 100644
index 000000000..38be4ee39
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_service_updater/head_update.js
@@ -0,0 +1,8 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+const IS_SERVICE_TEST = true;
+
+/* import-globals-from ../data/xpcshellUtilsAUS.js */
+load("../data/xpcshellUtilsAUS.js");
diff --git a/toolkit/mozapps/update/tests/unit_service_updater/invalidArgInstallDirPathTooLongFailureSvc.js b/toolkit/mozapps/update/tests/unit_service_updater/invalidArgInstallDirPathTooLongFailureSvc.js
new file mode 100644
index 000000000..70e03646a
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_service_updater/invalidArgInstallDirPathTooLongFailureSvc.js
@@ -0,0 +1,47 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+/* Too long install directory path failure test */
+
+const STATE_AFTER_RUNUPDATE =
+ IS_SERVICE_TEST ? STATE_FAILED_SERVICE_INVALID_INSTALL_DIR_PATH_ERROR
+ : STATE_FAILED_INVALID_INSTALL_DIR_PATH_ERROR;
+
+function run_test() {
+ if (!setupTestCommon()) {
+ return;
+ }
+ gTestFiles = gTestFilesCompleteSuccess;
+ gTestDirs = gTestDirsCompleteSuccess;
+ setTestFilesAndDirsForFailure();
+ setupUpdaterTest(FILE_COMPLETE_MAR, false);
+}
+
+/**
+ * Called after the call to setupUpdaterTest finishes.
+ */
+function setupUpdaterTestFinished() {
+ let path = "123456789";
+ if (IS_WIN) {
+ path = "\\" + path;
+ path = path.repeat(30); // 300 characters
+ path = "C:" + path;
+ } else {
+ path = "/" + path;
+ path = path.repeat(1000); // 10000 characters
+ }
+
+ runUpdate(STATE_AFTER_RUNUPDATE, false, 1, true, null, path, null, null);
+}
+
+/**
+ * Called after the call to runUpdateUsingUpdater finishes.
+ */
+function runUpdateFinished() {
+ standardInit();
+ checkPostUpdateRunningFile(false);
+ checkFilesAfterUpdateFailure(getApplyDirFile);
+ waitForFilesInUse();
+}
diff --git a/toolkit/mozapps/update/tests/unit_service_updater/invalidArgInstallDirPathTraversalFailureSvc.js b/toolkit/mozapps/update/tests/unit_service_updater/invalidArgInstallDirPathTraversalFailureSvc.js
new file mode 100644
index 000000000..330578de6
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_service_updater/invalidArgInstallDirPathTraversalFailureSvc.js
@@ -0,0 +1,44 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+/* Install directory path traversal failure test */
+
+const STATE_AFTER_RUNUPDATE =
+ IS_SERVICE_TEST ? STATE_FAILED_SERVICE_INVALID_INSTALL_DIR_PATH_ERROR
+ : STATE_FAILED_INVALID_INSTALL_DIR_PATH_ERROR;
+
+function run_test() {
+ if (!setupTestCommon()) {
+ return;
+ }
+ gTestFiles = gTestFilesCompleteSuccess;
+ gTestDirs = gTestDirsCompleteSuccess;
+ setTestFilesAndDirsForFailure();
+ setupUpdaterTest(FILE_COMPLETE_MAR, false);
+}
+
+/**
+ * Called after the call to setupUpdaterTest finishes.
+ */
+function setupUpdaterTestFinished() {
+ let path = "123456789";
+ if (IS_WIN) {
+ path = "C:\\" + path + "\\..\\" + path;
+ } else {
+ path = "/" + path + "/../" + path;
+ }
+
+ runUpdate(STATE_AFTER_RUNUPDATE, false, 1, true, null, path, null, null);
+}
+
+/**
+ * Called after the call to runUpdateUsingUpdater finishes.
+ */
+function runUpdateFinished() {
+ standardInit();
+ checkPostUpdateRunningFile(false);
+ checkFilesAfterUpdateFailure(getApplyDirFile);
+ waitForFilesInUse();
+}
diff --git a/toolkit/mozapps/update/tests/unit_service_updater/invalidArgInstallWorkingDirPathNotSameFailureSvc_win.js b/toolkit/mozapps/update/tests/unit_service_updater/invalidArgInstallWorkingDirPathNotSameFailureSvc_win.js
new file mode 100644
index 000000000..8ddb34af0
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_service_updater/invalidArgInstallWorkingDirPathNotSameFailureSvc_win.js
@@ -0,0 +1,38 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+/* Different install and working directories for a regular update failure */
+
+const STATE_AFTER_RUNUPDATE =
+ IS_SERVICE_TEST ? STATE_FAILED_SERVICE_INVALID_APPLYTO_DIR_ERROR
+ : STATE_FAILED_INVALID_APPLYTO_DIR_ERROR;
+
+function run_test() {
+ if (!setupTestCommon()) {
+ return;
+ }
+ gTestFiles = gTestFilesCompleteSuccess;
+ gTestDirs = gTestDirsCompleteSuccess;
+ setTestFilesAndDirsForFailure();
+ setupUpdaterTest(FILE_COMPLETE_MAR, false);
+}
+
+/**
+ * Called after the call to setupUpdaterTest finishes.
+ */
+function setupUpdaterTestFinished() {
+ let path = getApplyDirFile("..", false).path;
+ runUpdate(STATE_AFTER_RUNUPDATE, false, 1, true, null, null, path, null);
+}
+
+/**
+ * Called after the call to runUpdateUsingUpdater finishes.
+ */
+function runUpdateFinished() {
+ standardInit();
+ checkPostUpdateRunningFile(false);
+ checkFilesAfterUpdateFailure(getApplyDirFile);
+ waitForFilesInUse();
+}
diff --git a/toolkit/mozapps/update/tests/unit_service_updater/invalidArgPatchDirPathTraversalFailureSvc.js b/toolkit/mozapps/update/tests/unit_service_updater/invalidArgPatchDirPathTraversalFailureSvc.js
new file mode 100644
index 000000000..c8ae3f0c6
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_service_updater/invalidArgPatchDirPathTraversalFailureSvc.js
@@ -0,0 +1,43 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+/* Patch directory path traversal failure test */
+
+const STATE_AFTER_RUNUPDATE =
+ IS_SERVICE_TEST ? STATE_PENDING_SVC : STATE_PENDING;
+
+function run_test() {
+ if (!setupTestCommon()) {
+ return;
+ }
+ gTestFiles = gTestFilesCompleteSuccess;
+ gTestDirs = gTestDirsCompleteSuccess;
+ setTestFilesAndDirsForFailure();
+ setupUpdaterTest(FILE_COMPLETE_MAR, false);
+}
+
+/**
+ * Called after the call to setupUpdaterTest finishes.
+ */
+function setupUpdaterTestFinished() {
+ let path = getUpdatesPatchDir();
+ if (IS_WIN) {
+ path = path + "\\..\\";
+ } else {
+ path = path + "/../";
+ }
+
+ runUpdate(STATE_AFTER_RUNUPDATE, false, 1, true, path, null, null, null);
+}
+
+/**
+ * Called after the call to runUpdateUsingUpdater finishes.
+ */
+function runUpdateFinished() {
+ standardInit();
+ checkPostUpdateRunningFile(false);
+ checkFilesAfterUpdateFailure(getApplyDirFile);
+ waitForFilesInUse();
+}
diff --git a/toolkit/mozapps/update/tests/unit_service_updater/invalidArgStageDirNotInInstallDirFailureSvc_win.js b/toolkit/mozapps/update/tests/unit_service_updater/invalidArgStageDirNotInInstallDirFailureSvc_win.js
new file mode 100644
index 000000000..e9b227657
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_service_updater/invalidArgStageDirNotInInstallDirFailureSvc_win.js
@@ -0,0 +1,38 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+/* Different install and working directories for a regular update failure */
+
+const STATE_AFTER_RUNUPDATE =
+ IS_SERVICE_TEST ? STATE_FAILED_SERVICE_INVALID_APPLYTO_DIR_STAGED_ERROR
+ : STATE_FAILED_INVALID_APPLYTO_DIR_STAGED_ERROR;
+
+function run_test() {
+ if (!setupTestCommon()) {
+ return;
+ }
+ gTestFiles = gTestFilesCompleteSuccess;
+ gTestDirs = gTestDirsCompleteSuccess;
+ setTestFilesAndDirsForFailure();
+ setupUpdaterTest(FILE_COMPLETE_MAR, false);
+}
+
+/**
+ * Called after the call to setupUpdaterTest finishes.
+ */
+function setupUpdaterTestFinished() {
+ let path = getApplyDirFile("..", false).path;
+ runUpdate(STATE_AFTER_RUNUPDATE, true, 1, true, null, null, path, null);
+}
+
+/**
+ * Called after the call to runUpdateUsingUpdater finishes.
+ */
+function runUpdateFinished() {
+ standardInit();
+ checkPostUpdateRunningFile(false);
+ checkFilesAfterUpdateFailure(getApplyDirFile);
+ waitForFilesInUse();
+}
diff --git a/toolkit/mozapps/update/tests/unit_service_updater/invalidArgWorkingDirPathLocalUNCFailureSvc_win.js b/toolkit/mozapps/update/tests/unit_service_updater/invalidArgWorkingDirPathLocalUNCFailureSvc_win.js
new file mode 100644
index 000000000..87bbad4aa
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_service_updater/invalidArgWorkingDirPathLocalUNCFailureSvc_win.js
@@ -0,0 +1,38 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+/* Working directory path local UNC failure test */
+
+const STATE_AFTER_RUNUPDATE =
+ IS_SERVICE_TEST ? STATE_FAILED_SERVICE_INVALID_WORKING_DIR_PATH_ERROR
+ : STATE_FAILED_INVALID_WORKING_DIR_PATH_ERROR;
+
+function run_test() {
+ if (!setupTestCommon()) {
+ return;
+ }
+ gTestFiles = gTestFilesCompleteSuccess;
+ gTestDirs = gTestDirsCompleteSuccess;
+ setTestFilesAndDirsForFailure();
+ setupUpdaterTest(FILE_COMPLETE_MAR, false);
+}
+
+/**
+ * Called after the call to setupUpdaterTest finishes.
+ */
+function setupUpdaterTestFinished() {
+ let path = "\\\\.\\" + getApplyDirFile(null, false).path;
+ runUpdate(STATE_AFTER_RUNUPDATE, false, 1, true, null, null, path, null);
+}
+
+/**
+ * Called after the call to runUpdateUsingUpdater finishes.
+ */
+function runUpdateFinished() {
+ standardInit();
+ checkPostUpdateRunningFile(false);
+ checkFilesAfterUpdateFailure(getApplyDirFile);
+ waitForFilesInUse();
+}
diff --git a/toolkit/mozapps/update/tests/unit_service_updater/invalidArgWorkingDirPathRelativeFailureSvc.js b/toolkit/mozapps/update/tests/unit_service_updater/invalidArgWorkingDirPathRelativeFailureSvc.js
new file mode 100644
index 000000000..a550909b2
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_service_updater/invalidArgWorkingDirPathRelativeFailureSvc.js
@@ -0,0 +1,37 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+/* Relative working directory path failure test */
+
+const STATE_AFTER_RUNUPDATE =
+ IS_SERVICE_TEST ? STATE_FAILED_SERVICE_INVALID_WORKING_DIR_PATH_ERROR
+ : STATE_FAILED_INVALID_WORKING_DIR_PATH_ERROR;
+
+function run_test() {
+ if (!setupTestCommon()) {
+ return;
+ }
+ gTestFiles = gTestFilesCompleteSuccess;
+ gTestDirs = gTestDirsCompleteSuccess;
+ setTestFilesAndDirsForFailure();
+ setupUpdaterTest(FILE_COMPLETE_MAR, false);
+}
+
+/**
+ * Called after the call to setupUpdaterTest finishes.
+ */
+function setupUpdaterTestFinished() {
+ runUpdate(STATE_AFTER_RUNUPDATE, false, 1, true, null, null, "test", null);
+}
+
+/**
+ * Called after the call to runUpdateUsingUpdater finishes.
+ */
+function runUpdateFinished() {
+ standardInit();
+ checkPostUpdateRunningFile(false);
+ checkFilesAfterUpdateFailure(getApplyDirFile);
+ waitForFilesInUse();
+}
diff --git a/toolkit/mozapps/update/tests/unit_service_updater/marAppApplyDirLockedStageFailureSvc_win.js b/toolkit/mozapps/update/tests/unit_service_updater/marAppApplyDirLockedStageFailureSvc_win.js
new file mode 100644
index 000000000..b9f793236
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_service_updater/marAppApplyDirLockedStageFailureSvc_win.js
@@ -0,0 +1,41 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+/**
+ * Test applying an update by staging an update and launching an application to
+ * apply it.
+ */
+
+const STATE_AFTER_STAGE = STATE_PENDING;
+
+function run_test() {
+ if (!setupTestCommon()) {
+ return;
+ }
+
+ gTestFiles = gTestFilesCompleteSuccess;
+ gTestDirs = gTestDirsCompleteSuccess;
+ setTestFilesAndDirsForFailure();
+ createUpdateInProgressLockFile(getAppBaseDir());
+ setupUpdaterTest(FILE_COMPLETE_MAR, false);
+}
+
+/**
+ * Called after the call to setupUpdaterTest finishes.
+ */
+function setupUpdaterTestFinished() {
+ stageUpdate(false);
+}
+
+/**
+ * Called after the call to stageUpdate finishes.
+ */
+function stageUpdateFinished() {
+ removeUpdateInProgressLockFile(getAppBaseDir());
+ checkPostUpdateRunningFile(false);
+ checkFilesAfterUpdateFailure(getApplyDirFile);
+ checkUpdateLogContains(PERFORMING_STAGED_UPDATE);
+ checkUpdateLogContains(ERR_UPDATE_IN_PROGRESS);
+ waitForFilesInUse();
+}
diff --git a/toolkit/mozapps/update/tests/unit_service_updater/marAppApplyUpdateAppBinInUseStageSuccessSvc_win.js b/toolkit/mozapps/update/tests/unit_service_updater/marAppApplyUpdateAppBinInUseStageSuccessSvc_win.js
new file mode 100644
index 000000000..a606720b7
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_service_updater/marAppApplyUpdateAppBinInUseStageSuccessSvc_win.js
@@ -0,0 +1,83 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+/**
+ * Test applying an update by staging an update and launching an application to
+ * apply it.
+ */
+
+const STATE_AFTER_STAGE = IS_SERVICE_TEST ? STATE_APPLIED_SVC : STATE_APPLIED;
+
+function run_test() {
+ if (!setupTestCommon()) {
+ return;
+ }
+
+ gTestFiles = gTestFilesCompleteSuccess;
+ gTestDirs = gTestDirsCompleteSuccess;
+ setupUpdaterTest(FILE_COMPLETE_MAR, undefined);
+}
+
+/**
+ * Called after the call to setupUpdaterTest finishes.
+ */
+function setupUpdaterTestFinished() {
+ stageUpdate(true);
+}
+
+/**
+ * Called after the call to stageUpdate finishes.
+ */
+function stageUpdateFinished() {
+ checkPostUpdateRunningFile(false);
+ checkFilesAfterUpdateSuccess(getStageDirFile, true, false);
+ checkUpdateLogContents(LOG_COMPLETE_SUCCESS, true);
+ lockDirectory(getAppBaseDir().path);
+ // Switch the application to the staged application that was updated.
+ runUpdateUsingApp(STATE_SUCCEEDED);
+}
+
+/**
+ * Called after the call to runUpdateUsingApp finishes.
+ */
+function runUpdateFinished() {
+ checkPostUpdateAppLog();
+}
+
+/**
+ * Called after the call to checkPostUpdateAppLog finishes.
+ */
+function checkPostUpdateAppLogFinished() {
+ checkAppBundleModTime();
+ standardInit();
+ Assert.equal(readStatusState(), STATE_NONE,
+ "the status file state" + MSG_SHOULD_EQUAL);
+ Assert.ok(!gUpdateManager.activeUpdate,
+ "the active update should not be defined");
+ Assert.equal(gUpdateManager.updateCount, 1,
+ "the update manager updateCount attribute" + MSG_SHOULD_EQUAL);
+ Assert.equal(gUpdateManager.getUpdateAt(0).state, STATE_SUCCEEDED,
+ "the update state" + MSG_SHOULD_EQUAL);
+ checkPostUpdateRunningFile(true);
+ checkFilesAfterUpdateSuccess(getApplyDirFile);
+ checkUpdateLogContents(LOG_COMPLETE_SUCCESS);
+
+ let updatesDir = getUpdatesPatchDir();
+ Assert.ok(updatesDir.exists(),
+ MSG_SHOULD_EXIST + getMsgPath(updatesDir.path));
+
+ let log = getUpdateLog(FILE_UPDATE_LOG);
+ Assert.ok(!log.exists(),
+ MSG_SHOULD_NOT_EXIST + getMsgPath(log.path));
+
+ log = getUpdateLog(FILE_LAST_UPDATE_LOG);
+ Assert.ok(log.exists(),
+ MSG_SHOULD_EXIST + getMsgPath(log.path));
+
+ log = getUpdateLog(FILE_BACKUP_UPDATE_LOG);
+ Assert.ok(log.exists(),
+ MSG_SHOULD_EXIST + getMsgPath(log.path));
+
+ waitForFilesInUse();
+}
diff --git a/toolkit/mozapps/update/tests/unit_service_updater/marAppApplyUpdateStageSuccessSvc.js b/toolkit/mozapps/update/tests/unit_service_updater/marAppApplyUpdateStageSuccessSvc.js
new file mode 100644
index 000000000..5b9b08156
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_service_updater/marAppApplyUpdateStageSuccessSvc.js
@@ -0,0 +1,82 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+/**
+ * Test applying an update by staging an update and launching an application to
+ * apply it.
+ */
+
+const STATE_AFTER_STAGE = IS_SERVICE_TEST ? STATE_APPLIED_SVC : STATE_APPLIED;
+
+function run_test() {
+ if (!setupTestCommon()) {
+ return;
+ }
+
+ gTestFiles = gTestFilesCompleteSuccess;
+ gTestDirs = gTestDirsCompleteSuccess;
+ setupUpdaterTest(FILE_COMPLETE_MAR, true);
+}
+
+/**
+ * Called after the call to setupUpdaterTest finishes.
+ */
+function setupUpdaterTestFinished() {
+ stageUpdate(true);
+}
+
+/**
+ * Called after the call to stageUpdate finishes.
+ */
+function stageUpdateFinished() {
+ checkPostUpdateRunningFile(false);
+ checkFilesAfterUpdateSuccess(getStageDirFile, true);
+ checkUpdateLogContents(LOG_COMPLETE_SUCCESS, true);
+ // Switch the application to the staged application that was updated.
+ runUpdateUsingApp(STATE_SUCCEEDED);
+}
+
+/**
+ * Called after the call to runUpdateUsingApp finishes.
+ */
+function runUpdateFinished() {
+ checkPostUpdateAppLog();
+}
+
+/**
+ * Called after the call to checkPostUpdateAppLog finishes.
+ */
+function checkPostUpdateAppLogFinished() {
+ checkAppBundleModTime();
+ standardInit();
+ Assert.equal(readStatusState(), STATE_NONE,
+ "the status file state" + MSG_SHOULD_EQUAL);
+ Assert.ok(!gUpdateManager.activeUpdate,
+ "the active update should not be defined");
+ Assert.equal(gUpdateManager.updateCount, 1,
+ "the update manager updateCount attribute" + MSG_SHOULD_EQUAL);
+ Assert.equal(gUpdateManager.getUpdateAt(0).state, STATE_SUCCEEDED,
+ "the update state" + MSG_SHOULD_EQUAL);
+ checkPostUpdateRunningFile(true);
+ checkFilesAfterUpdateSuccess(getApplyDirFile, false, true);
+ checkUpdateLogContents(LOG_REPLACE_SUCCESS, false, true);
+
+ let updatesDir = getUpdatesPatchDir();
+ Assert.ok(updatesDir.exists(),
+ MSG_SHOULD_EXIST + getMsgPath(updatesDir.path));
+
+ let log = getUpdateLog(FILE_UPDATE_LOG);
+ Assert.ok(!log.exists(),
+ MSG_SHOULD_NOT_EXIST + getMsgPath(log.path));
+
+ log = getUpdateLog(FILE_LAST_UPDATE_LOG);
+ Assert.ok(log.exists(),
+ MSG_SHOULD_EXIST + getMsgPath(log.path));
+
+ log = getUpdateLog(FILE_BACKUP_UPDATE_LOG);
+ Assert.ok(log.exists(),
+ MSG_SHOULD_EXIST + getMsgPath(log.path));
+
+ waitForFilesInUse();
+}
diff --git a/toolkit/mozapps/update/tests/unit_service_updater/marAppApplyUpdateSuccessSvc.js b/toolkit/mozapps/update/tests/unit_service_updater/marAppApplyUpdateSuccessSvc.js
new file mode 100644
index 000000000..e76233fe6
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_service_updater/marAppApplyUpdateSuccessSvc.js
@@ -0,0 +1,65 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+/**
+ * Test applying an update by staging an update and launching an application to
+ * apply it.
+ */
+
+function run_test() {
+ if (!setupTestCommon()) {
+ return;
+ }
+
+ gTestFiles = gTestFilesCompleteSuccess;
+ gTestDirs = gTestDirsCompleteSuccess;
+ // The third parameter will test that a full path to the post update binary
+ // doesn't execute.
+ setupUpdaterTest(FILE_COMPLETE_MAR, undefined,
+ getApplyDirFile(null, true).path + "/");
+}
+
+/**
+ * Called after the call to setupUpdaterTest finishes.
+ */
+function setupUpdaterTestFinished() {
+ runUpdateUsingApp(STATE_SUCCEEDED);
+}
+
+/**
+ * Called after the call to runUpdateUsingApp finishes.
+ */
+function runUpdateFinished() {
+ checkAppBundleModTime();
+ standardInit();
+ Assert.equal(readStatusState(), STATE_NONE,
+ "the status file state" + MSG_SHOULD_EQUAL);
+ Assert.ok(!gUpdateManager.activeUpdate,
+ "the active update should not be defined");
+ Assert.equal(gUpdateManager.updateCount, 1,
+ "the update manager updateCount attribute" + MSG_SHOULD_EQUAL);
+ Assert.equal(gUpdateManager.getUpdateAt(0).state, STATE_SUCCEEDED,
+ "the update state" + MSG_SHOULD_EQUAL);
+ checkPostUpdateRunningFile(false);
+ checkFilesAfterUpdateSuccess(getApplyDirFile);
+ checkUpdateLogContents(LOG_COMPLETE_SUCCESS);
+
+ let updatesDir = getUpdatesPatchDir();
+ Assert.ok(updatesDir.exists(),
+ MSG_SHOULD_EXIST + getMsgPath(updatesDir.path));
+
+ let log = getUpdateLog(FILE_UPDATE_LOG);
+ Assert.ok(!log.exists(),
+ MSG_SHOULD_NOT_EXIST + getMsgPath(log.path));
+
+ log = getUpdateLog(FILE_LAST_UPDATE_LOG);
+ Assert.ok(log.exists(),
+ MSG_SHOULD_EXIST + getMsgPath(log.path));
+
+ log = getUpdateLog(FILE_BACKUP_UPDATE_LOG);
+ Assert.ok(!log.exists(),
+ MSG_SHOULD_NOT_EXIST + getMsgPath(log.path));
+
+ waitForFilesInUse();
+}
diff --git a/toolkit/mozapps/update/tests/unit_service_updater/marAppInUseStageFailureCompleteSvc_win.js b/toolkit/mozapps/update/tests/unit_service_updater/marAppInUseStageFailureCompleteSvc_win.js
new file mode 100644
index 000000000..b1505d58e
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_service_updater/marAppInUseStageFailureCompleteSvc_win.js
@@ -0,0 +1,70 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+/* Application in use complete MAR file staged patch apply failure test */
+
+const STATE_AFTER_STAGE = IS_SERVICE_TEST ? STATE_APPLIED_SVC : STATE_APPLIED;
+const STATE_AFTER_RUNUPDATE = IS_SERVICE_TEST ? STATE_PENDING_SVC : STATE_PENDING;
+
+function run_test() {
+ if (!setupTestCommon()) {
+ return;
+ }
+ gTestFiles = gTestFilesCompleteSuccess;
+ gTestDirs = gTestDirsCompleteSuccess;
+ setupUpdaterTest(FILE_COMPLETE_MAR, false);
+}
+
+/**
+ * Called after the call to setupUpdaterTest finishes.
+ */
+function setupUpdaterTestFinished() {
+ runHelperFileInUse(DIR_RESOURCES + gCallbackBinFile, false);
+}
+
+/**
+ * Called after the call to waitForHelperSleep finishes.
+ */
+function waitForHelperSleepFinished() {
+ stageUpdate(true);
+}
+
+/**
+ * Called after the call to stageUpdate finishes.
+ */
+function stageUpdateFinished() {
+ checkPostUpdateRunningFile(false);
+ checkFilesAfterUpdateSuccess(getStageDirFile, true);
+ checkUpdateLogContents(LOG_COMPLETE_SUCCESS, true);
+ // Switch the application to the staged application that was updated.
+ runUpdate(STATE_AFTER_RUNUPDATE, true, 1, true);
+}
+
+/**
+ * Called after the call to runUpdate finishes.
+ */
+function runUpdateFinished() {
+ waitForHelperExit();
+}
+
+/**
+ * Called after the call to waitForHelperExit finishes.
+ */
+function waitForHelperExitFinished() {
+ standardInit();
+ Assert.equal(readStatusState(), STATE_NONE,
+ "the status file state" + MSG_SHOULD_EQUAL);
+ Assert.ok(!gUpdateManager.activeUpdate,
+ "the active update should not be defined");
+ Assert.equal(gUpdateManager.updateCount, 1,
+ "the update manager updateCount attribute" + MSG_SHOULD_EQUAL);
+ Assert.equal(gUpdateManager.getUpdateAt(0).state, STATE_AFTER_RUNUPDATE,
+ "the update state" + MSG_SHOULD_EQUAL);
+ checkPostUpdateRunningFile(false);
+ setTestFilesAndDirsForFailure();
+ checkFilesAfterUpdateFailure(getApplyDirFile);
+ checkUpdateLogContains(ERR_RENAME_FILE);
+ checkUpdateLogContains(ERR_MOVE_DESTDIR_7 + "\n" + CALL_QUIT);
+ checkCallbackLog();
+}
diff --git a/toolkit/mozapps/update/tests/unit_service_updater/marAppInUseSuccessCompleteSvc.js b/toolkit/mozapps/update/tests/unit_service_updater/marAppInUseSuccessCompleteSvc.js
new file mode 100644
index 000000000..93333cade
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_service_updater/marAppInUseSuccessCompleteSvc.js
@@ -0,0 +1,62 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+/* Application in use complete MAR file patch apply success test */
+
+function run_test() {
+ if (!setupTestCommon()) {
+ return;
+ }
+ gTestFiles = gTestFilesCompleteSuccess;
+ gTestDirs = gTestDirsCompleteSuccess;
+ setupUpdaterTest(FILE_COMPLETE_MAR, false);
+}
+
+/**
+ * Called after the call to setupUpdaterTest finishes.
+ */
+function setupUpdaterTestFinished() {
+ runHelperFileInUse(DIR_RESOURCES + gCallbackBinFile, false);
+}
+
+/**
+ * Called after the call to waitForHelperSleep finishes.
+ */
+function waitForHelperSleepFinished() {
+ runUpdate(STATE_SUCCEEDED, false, 0, true);
+}
+
+/**
+ * Called after the call to runUpdate finishes.
+ */
+function runUpdateFinished() {
+ waitForHelperExit();
+}
+
+/**
+ * Called after the call to waitForHelperExit finishes.
+ */
+function waitForHelperExitFinished() {
+ checkPostUpdateAppLog();
+}
+
+/**
+ * Called after the call to checkPostUpdateAppLog finishes.
+ */
+function checkPostUpdateAppLogFinished() {
+ checkAppBundleModTime();
+ standardInit();
+ Assert.equal(readStatusState(), STATE_NONE,
+ "the status file state" + MSG_SHOULD_EQUAL);
+ Assert.ok(!gUpdateManager.activeUpdate,
+ "the active update should not be defined");
+ Assert.equal(gUpdateManager.updateCount, 1,
+ "the update manager updateCount attribute" + MSG_SHOULD_EQUAL);
+ Assert.equal(gUpdateManager.getUpdateAt(0).state, STATE_SUCCEEDED,
+ "the update state" + MSG_SHOULD_EQUAL);
+ checkPostUpdateRunningFile(true);
+ checkFilesAfterUpdateSuccess(getApplyDirFile);
+ checkUpdateLogContents(LOG_COMPLETE_SUCCESS);
+ checkCallbackLog();
+}
diff --git a/toolkit/mozapps/update/tests/unit_service_updater/marCallbackAppStageSuccessCompleteSvc_win.js b/toolkit/mozapps/update/tests/unit_service_updater/marCallbackAppStageSuccessCompleteSvc_win.js
new file mode 100644
index 000000000..79e54c182
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_service_updater/marCallbackAppStageSuccessCompleteSvc_win.js
@@ -0,0 +1,61 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+/* Replace app binary complete MAR file staged patch apply success test */
+
+const STATE_AFTER_STAGE = IS_SERVICE_TEST ? STATE_APPLIED_SVC : STATE_APPLIED;
+
+function run_test() {
+ if (!setupTestCommon()) {
+ return;
+ }
+ gTestFiles = gTestFilesCompleteSuccess;
+ gTestDirs = gTestDirsCompleteSuccess;
+ gCallbackBinFile = "exe0.exe";
+ setupUpdaterTest(FILE_COMPLETE_MAR, false);
+}
+
+/**
+ * Called after the call to setupUpdaterTest finishes.
+ */
+function setupUpdaterTestFinished() {
+ stageUpdate(true);
+}
+
+/**
+ * Called after the call to stageUpdate finishes.
+ */
+function stageUpdateFinished() {
+ checkPostUpdateRunningFile(false);
+ checkFilesAfterUpdateSuccess(getStageDirFile, true);
+ checkUpdateLogContents(LOG_COMPLETE_SUCCESS, true);
+ // Switch the application to the staged application that was updated.
+ runUpdate(STATE_SUCCEEDED, true, 0, true);
+}
+
+/**
+ * Called after the call to runUpdate finishes.
+ */
+function runUpdateFinished() {
+ checkPostUpdateAppLog();
+}
+
+/**
+ * Called after the call to checkPostUpdateAppLog finishes.
+ */
+function checkPostUpdateAppLogFinished() {
+ standardInit();
+ Assert.equal(readStatusState(), STATE_NONE,
+ "the status file state" + MSG_SHOULD_EQUAL);
+ Assert.ok(!gUpdateManager.activeUpdate,
+ "the active update should not be defined");
+ Assert.equal(gUpdateManager.updateCount, 1,
+ "the update manager updateCount attribute" + MSG_SHOULD_EQUAL);
+ Assert.equal(gUpdateManager.getUpdateAt(0).state, STATE_SUCCEEDED,
+ "the update state" + MSG_SHOULD_EQUAL);
+ checkPostUpdateRunningFile(true);
+ checkFilesAfterUpdateSuccess(getApplyDirFile, false, true);
+ checkUpdateLogContents(LOG_REPLACE_SUCCESS, false, true);
+ checkCallbackLog();
+}
diff --git a/toolkit/mozapps/update/tests/unit_service_updater/marCallbackAppStageSuccessPartialSvc_win.js b/toolkit/mozapps/update/tests/unit_service_updater/marCallbackAppStageSuccessPartialSvc_win.js
new file mode 100644
index 000000000..b1f84715f
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_service_updater/marCallbackAppStageSuccessPartialSvc_win.js
@@ -0,0 +1,61 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+/* Patch app binary partial MAR file staged patch apply success test */
+
+const STATE_AFTER_STAGE = IS_SERVICE_TEST ? STATE_APPLIED_SVC : STATE_APPLIED;
+
+function run_test() {
+ if (!setupTestCommon()) {
+ return;
+ }
+ gTestFiles = gTestFilesPartialSuccess;
+ gTestDirs = gTestDirsPartialSuccess;
+ gCallbackBinFile = "exe0.exe";
+ setupUpdaterTest(FILE_PARTIAL_MAR, false);
+}
+
+/**
+ * Called after the call to setupUpdaterTest finishes.
+ */
+function setupUpdaterTestFinished() {
+ stageUpdate(true);
+}
+
+/**
+ * Called after the call to stageUpdate finishes.
+ */
+function stageUpdateFinished() {
+ checkPostUpdateRunningFile(false);
+ checkFilesAfterUpdateSuccess(getStageDirFile, true);
+ checkUpdateLogContents(LOG_PARTIAL_SUCCESS, true);
+ // Switch the application to the staged application that was updated.
+ runUpdate(STATE_SUCCEEDED, true, 0, true);
+}
+
+/**
+ * Called after the call to runUpdate finishes.
+ */
+function runUpdateFinished() {
+ checkPostUpdateAppLog();
+}
+
+/**
+ * Called after the call to checkPostUpdateAppLog finishes.
+ */
+function checkPostUpdateAppLogFinished() {
+ standardInit();
+ Assert.equal(readStatusState(), STATE_NONE,
+ "the status file state" + MSG_SHOULD_EQUAL);
+ Assert.ok(!gUpdateManager.activeUpdate,
+ "the active update should not be defined");
+ Assert.equal(gUpdateManager.updateCount, 1,
+ "the update manager updateCount attribute" + MSG_SHOULD_EQUAL);
+ Assert.equal(gUpdateManager.getUpdateAt(0).state, STATE_SUCCEEDED,
+ "the update state" + MSG_SHOULD_EQUAL);
+ checkPostUpdateRunningFile(true);
+ checkFilesAfterUpdateSuccess(getApplyDirFile, false, true);
+ checkUpdateLogContents(LOG_REPLACE_SUCCESS, false, true);
+ checkCallbackLog();
+}
diff --git a/toolkit/mozapps/update/tests/unit_service_updater/marCallbackAppSuccessCompleteSvc_win.js b/toolkit/mozapps/update/tests/unit_service_updater/marCallbackAppSuccessCompleteSvc_win.js
new file mode 100644
index 000000000..85e92d290
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_service_updater/marCallbackAppSuccessCompleteSvc_win.js
@@ -0,0 +1,48 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+/* Replace app binary complete MAR file patch apply success test */
+
+function run_test() {
+ if (!setupTestCommon()) {
+ return;
+ }
+ gTestFiles = gTestFilesCompleteSuccess;
+ gTestDirs = gTestDirsCompleteSuccess;
+ gCallbackBinFile = "exe0.exe";
+ setupUpdaterTest(FILE_COMPLETE_MAR, false);
+}
+
+/**
+ * Called after the call to setupUpdaterTest finishes.
+ */
+function setupUpdaterTestFinished() {
+ runUpdate(STATE_SUCCEEDED, false, 0, true);
+}
+
+/**
+ * Called after the call to runUpdate finishes.
+ */
+function runUpdateFinished() {
+ checkPostUpdateAppLog();
+}
+
+/**
+ * Called after the call to checkPostUpdateAppLog finishes.
+ */
+function checkPostUpdateAppLogFinished() {
+ standardInit();
+ Assert.equal(readStatusState(), STATE_NONE,
+ "the status file state" + MSG_SHOULD_EQUAL);
+ Assert.ok(!gUpdateManager.activeUpdate,
+ "the active update should not be defined");
+ Assert.equal(gUpdateManager.updateCount, 1,
+ "the update manager updateCount attribute" + MSG_SHOULD_EQUAL);
+ Assert.equal(gUpdateManager.getUpdateAt(0).state, STATE_SUCCEEDED,
+ "the update state" + MSG_SHOULD_EQUAL);
+ checkPostUpdateRunningFile(true);
+ checkFilesAfterUpdateSuccess(getApplyDirFile);
+ checkUpdateLogContents(LOG_COMPLETE_SUCCESS);
+ checkCallbackLog();
+}
diff --git a/toolkit/mozapps/update/tests/unit_service_updater/marCallbackAppSuccessPartialSvc_win.js b/toolkit/mozapps/update/tests/unit_service_updater/marCallbackAppSuccessPartialSvc_win.js
new file mode 100644
index 000000000..1212c9ba2
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_service_updater/marCallbackAppSuccessPartialSvc_win.js
@@ -0,0 +1,48 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+/* Patch app binary partial MAR file patch apply success test */
+
+function run_test() {
+ if (!setupTestCommon()) {
+ return;
+ }
+ gTestFiles = gTestFilesPartialSuccess;
+ gTestDirs = gTestDirsPartialSuccess;
+ gCallbackBinFile = "exe0.exe";
+ setupUpdaterTest(FILE_PARTIAL_MAR, false);
+}
+
+/**
+ * Called after the call to setupUpdaterTest finishes.
+ */
+function setupUpdaterTestFinished() {
+ runUpdate(STATE_SUCCEEDED, false, 0, true);
+}
+
+/**
+ * Called after the call to runUpdate finishes.
+ */
+function runUpdateFinished() {
+ checkPostUpdateAppLog();
+}
+
+/**
+ * Called after the call to checkPostUpdateAppLog finishes.
+ */
+function checkPostUpdateAppLogFinished() {
+ standardInit();
+ Assert.equal(readStatusState(), STATE_NONE,
+ "the status file state" + MSG_SHOULD_EQUAL);
+ Assert.ok(!gUpdateManager.activeUpdate,
+ "the active update should not be defined");
+ Assert.equal(gUpdateManager.updateCount, 1,
+ "the update manager updateCount attribute" + MSG_SHOULD_EQUAL);
+ Assert.equal(gUpdateManager.getUpdateAt(0).state, STATE_SUCCEEDED,
+ "the update state" + MSG_SHOULD_EQUAL);
+ checkPostUpdateRunningFile(true);
+ checkFilesAfterUpdateSuccess(getApplyDirFile);
+ checkUpdateLogContents(LOG_PARTIAL_SUCCESS);
+ checkCallbackLog();
+}
diff --git a/toolkit/mozapps/update/tests/unit_service_updater/marFailurePartialSvc.js b/toolkit/mozapps/update/tests/unit_service_updater/marFailurePartialSvc.js
new file mode 100644
index 000000000..960c96f7b
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_service_updater/marFailurePartialSvc.js
@@ -0,0 +1,47 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+/* General Partial MAR File Patch Apply Failure Test */
+
+function run_test() {
+ if (!setupTestCommon()) {
+ return;
+ }
+ gTestFiles = gTestFilesPartialSuccess;
+ gTestFiles[11].originalFile = "partial.png";
+ gTestDirs = gTestDirsPartialSuccess;
+ setTestFilesAndDirsForFailure();
+ setupUpdaterTest(FILE_PARTIAL_MAR, false);
+}
+
+/**
+ * Called after the call to setupUpdaterTest finishes.
+ */
+function setupUpdaterTestFinished() {
+ // If execv is used the updater process will turn into the callback process
+ // and the updater's return code will be that of the callback process.
+ runUpdate(STATE_FAILED_LOADSOURCE_ERROR_WRONG_SIZE, false, (USE_EXECV ? 0 : 1),
+ true);
+}
+
+/**
+ * Called after the call to runUpdate finishes.
+ */
+function runUpdateFinished() {
+ checkAppBundleModTime();
+ standardInit();
+ Assert.equal(readStatusFile(), STATE_NONE,
+ "the status file failure code" + MSG_SHOULD_EQUAL);
+ Assert.equal(gUpdateManager.updateCount, 1,
+ "the update manager updateCount attribute" + MSG_SHOULD_EQUAL);
+ Assert.equal(gUpdateManager.getUpdateAt(0).state, STATE_FAILED,
+ "the update state" + MSG_SHOULD_EQUAL);
+ Assert.equal(gUpdateManager.getUpdateAt(0).errorCode, LOADSOURCE_ERROR_WRONG_SIZE,
+ "the update errorCode" + MSG_SHOULD_EQUAL);
+ checkPostUpdateRunningFile(false);
+ checkFilesAfterUpdateFailure(getApplyDirFile);
+ checkUpdateLogContents(LOG_PARTIAL_FAILURE);
+ checkCallbackLog();
+}
diff --git a/toolkit/mozapps/update/tests/unit_service_updater/marFileInUseStageFailureCompleteSvc_win.js b/toolkit/mozapps/update/tests/unit_service_updater/marFileInUseStageFailureCompleteSvc_win.js
new file mode 100644
index 000000000..b39595f92
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_service_updater/marFileInUseStageFailureCompleteSvc_win.js
@@ -0,0 +1,67 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+/* File in use complete MAR file staged patch apply failure test */
+
+const STATE_AFTER_STAGE = IS_SERVICE_TEST ? STATE_APPLIED_SVC : STATE_APPLIED;
+const STATE_AFTER_RUNUPDATE = IS_SERVICE_TEST ? STATE_PENDING_SVC : STATE_PENDING;
+
+function run_test() {
+ if (!setupTestCommon()) {
+ return;
+ }
+ gTestFiles = gTestFilesCompleteSuccess;
+ gTestDirs = gTestDirsCompleteSuccess;
+ setupUpdaterTest(FILE_COMPLETE_MAR, false);
+}
+
+/**
+ * Called after the call to setupUpdaterTest finishes.
+ */
+function setupUpdaterTestFinished() {
+ runHelperFileInUse(gTestFiles[13].relPathDir + gTestFiles[13].fileName,
+ false);
+}
+
+/**
+ * Called after the call to waitForHelperSleep finishes.
+ */
+function waitForHelperSleepFinished() {
+ stageUpdate(true);
+}
+
+/**
+ * Called after the call to stageUpdate finishes.
+ */
+function stageUpdateFinished() {
+ checkPostUpdateRunningFile(false);
+ checkFilesAfterUpdateSuccess(getStageDirFile, true);
+ checkUpdateLogContents(LOG_COMPLETE_SUCCESS, true);
+ // Switch the application to the staged application that was updated.
+ runUpdate(STATE_AFTER_RUNUPDATE, true, 1, true);
+}
+
+/**
+ * Called after the call to runUpdate finishes.
+ */
+function runUpdateFinished() {
+ waitForHelperExit();
+}
+
+/**
+ * Called after the call to waitForHelperExit finishes.
+ */
+function waitForHelperExitFinished() {
+ standardInit();
+ Assert.equal(gUpdateManager.updateCount, 1,
+ "the update manager updateCount attribute" + MSG_SHOULD_EQUAL);
+ Assert.equal(gUpdateManager.getUpdateAt(0).state, STATE_AFTER_RUNUPDATE,
+ "the update state" + MSG_SHOULD_EQUAL);
+ checkPostUpdateRunningFile(false);
+ setTestFilesAndDirsForFailure();
+ checkFilesAfterUpdateFailure(getApplyDirFile);
+ checkUpdateLogContains(ERR_RENAME_FILE);
+ checkUpdateLogContains(ERR_MOVE_DESTDIR_7 + "\n" + CALL_QUIT);
+ checkCallbackLog();
+}
diff --git a/toolkit/mozapps/update/tests/unit_service_updater/marFileInUseStageFailurePartialSvc_win.js b/toolkit/mozapps/update/tests/unit_service_updater/marFileInUseStageFailurePartialSvc_win.js
new file mode 100644
index 000000000..06d386ad6
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_service_updater/marFileInUseStageFailurePartialSvc_win.js
@@ -0,0 +1,67 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+/* File in use partial MAR file staged patch apply failure test */
+
+const STATE_AFTER_STAGE = IS_SERVICE_TEST ? STATE_APPLIED_SVC : STATE_APPLIED;
+const STATE_AFTER_RUNUPDATE = IS_SERVICE_TEST ? STATE_PENDING_SVC : STATE_PENDING;
+
+function run_test() {
+ if (!setupTestCommon()) {
+ return;
+ }
+ gTestFiles = gTestFilesPartialSuccess;
+ gTestDirs = gTestDirsPartialSuccess;
+ setupUpdaterTest(FILE_PARTIAL_MAR, false);
+}
+
+/**
+ * Called after the call to setupUpdaterTest finishes.
+ */
+function setupUpdaterTestFinished() {
+ runHelperFileInUse(gTestFiles[11].relPathDir + gTestFiles[11].fileName,
+ false);
+}
+
+/**
+ * Called after the call to waitForHelperSleep finishes.
+ */
+function waitForHelperSleepFinished() {
+ stageUpdate(true);
+}
+
+/**
+ * Called after the call to stageUpdate finishes.
+ */
+function stageUpdateFinished() {
+ checkPostUpdateRunningFile(false);
+ checkFilesAfterUpdateSuccess(getStageDirFile, true);
+ checkUpdateLogContents(LOG_PARTIAL_SUCCESS, true);
+ // Switch the application to the staged application that was updated.
+ runUpdate(STATE_AFTER_RUNUPDATE, true, 1, true);
+}
+
+/**
+ * Called after the call to runUpdate finishes.
+ */
+function runUpdateFinished() {
+ waitForHelperExit();
+}
+
+/**
+ * Called after the call to waitForHelperExit finishes.
+ */
+function waitForHelperExitFinished() {
+ standardInit();
+ Assert.equal(gUpdateManager.updateCount, 1,
+ "the update manager updateCount attribute" + MSG_SHOULD_EQUAL);
+ Assert.equal(gUpdateManager.getUpdateAt(0).state, STATE_AFTER_RUNUPDATE,
+ "the update state" + MSG_SHOULD_EQUAL);
+ checkPostUpdateRunningFile(false);
+ setTestFilesAndDirsForFailure();
+ checkFilesAfterUpdateFailure(getApplyDirFile);
+ checkUpdateLogContains(ERR_RENAME_FILE);
+ checkUpdateLogContains(ERR_MOVE_DESTDIR_7 + "\n" + CALL_QUIT);
+ checkCallbackLog();
+}
diff --git a/toolkit/mozapps/update/tests/unit_service_updater/marFileInUseSuccessCompleteSvc_win.js b/toolkit/mozapps/update/tests/unit_service_updater/marFileInUseSuccessCompleteSvc_win.js
new file mode 100644
index 000000000..89a2fff5e
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_service_updater/marFileInUseSuccessCompleteSvc_win.js
@@ -0,0 +1,63 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+/* File in use complete MAR file patch apply success test */
+
+function run_test() {
+ if (!setupTestCommon()) {
+ return;
+ }
+ gTestFiles = gTestFilesCompleteSuccess;
+ gTestDirs = gTestDirsCompleteSuccess;
+ setupUpdaterTest(FILE_COMPLETE_MAR, false);
+}
+
+/**
+ * Called after the call to setupUpdaterTest finishes.
+ */
+function setupUpdaterTestFinished() {
+ runHelperFileInUse(gTestFiles[13].relPathDir + gTestFiles[13].fileName,
+ false);
+}
+
+/**
+ * Called after the call to waitForHelperSleep finishes.
+ */
+function waitForHelperSleepFinished() {
+ runUpdate(STATE_SUCCEEDED, false, 0, true);
+}
+
+/**
+ * Called after the call to runUpdate finishes.
+ */
+function runUpdateFinished() {
+ waitForHelperExit();
+}
+
+/**
+ * Called after the call to waitForHelperExit finishes.
+ */
+function waitForHelperExitFinished() {
+ checkPostUpdateAppLog();
+}
+
+/**
+ * Called after the call to checkPostUpdateAppLog finishes.
+ */
+function checkPostUpdateAppLogFinished() {
+ standardInit();
+ Assert.equal(readStatusState(), STATE_NONE,
+ "the status file state" + MSG_SHOULD_EQUAL);
+ Assert.ok(!gUpdateManager.activeUpdate,
+ "the active update should not be defined");
+ Assert.equal(gUpdateManager.updateCount, 1,
+ "the update manager updateCount attribute" + MSG_SHOULD_EQUAL);
+ Assert.equal(gUpdateManager.getUpdateAt(0).state, STATE_SUCCEEDED,
+ "the update state" + MSG_SHOULD_EQUAL);
+ checkPostUpdateRunningFile(true);
+ checkFilesAfterUpdateSuccess(getApplyDirFile, false, true);
+ checkUpdateLogContains(ERR_BACKUP_DISCARD);
+ checkUpdateLogContains(STATE_SUCCEEDED + "\n" + CALL_QUIT);
+ checkCallbackLog();
+}
diff --git a/toolkit/mozapps/update/tests/unit_service_updater/marFileInUseSuccessPartialSvc_win.js b/toolkit/mozapps/update/tests/unit_service_updater/marFileInUseSuccessPartialSvc_win.js
new file mode 100644
index 000000000..ea85ddccc
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_service_updater/marFileInUseSuccessPartialSvc_win.js
@@ -0,0 +1,63 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+/* File in use partial MAR file patch apply success test */
+
+function run_test() {
+ if (!setupTestCommon()) {
+ return;
+ }
+ gTestFiles = gTestFilesPartialSuccess;
+ gTestDirs = gTestDirsPartialSuccess;
+ setupUpdaterTest(FILE_PARTIAL_MAR, false);
+}
+
+/**
+ * Called after the call to setupUpdaterTest finishes.
+ */
+function setupUpdaterTestFinished() {
+ runHelperFileInUse(gTestFiles[11].relPathDir + gTestFiles[11].fileName,
+ false);
+}
+
+/**
+ * Called after the call to waitForHelperSleep finishes.
+ */
+function waitForHelperSleepFinished() {
+ runUpdate(STATE_SUCCEEDED, false, 0, true);
+}
+
+/**
+ * Called after the call to runUpdate finishes.
+ */
+function runUpdateFinished() {
+ waitForHelperExit();
+}
+
+/**
+ * Called after the call to waitForHelperExit finishes.
+ */
+function waitForHelperExitFinished() {
+ checkPostUpdateAppLog();
+}
+
+/**
+ * Called after the call to checkPostUpdateAppLog finishes.
+ */
+function checkPostUpdateAppLogFinished() {
+ standardInit();
+ Assert.equal(readStatusState(), STATE_NONE,
+ "the status file state" + MSG_SHOULD_EQUAL);
+ Assert.ok(!gUpdateManager.activeUpdate,
+ "the active update should not be defined");
+ Assert.equal(gUpdateManager.updateCount, 1,
+ "the update manager updateCount attribute" + MSG_SHOULD_EQUAL);
+ Assert.equal(gUpdateManager.getUpdateAt(0).state, STATE_SUCCEEDED,
+ "the update state" + MSG_SHOULD_EQUAL);
+ checkPostUpdateRunningFile(true);
+ checkFilesAfterUpdateSuccess(getApplyDirFile, false, true);
+ checkUpdateLogContains(ERR_BACKUP_DISCARD);
+ checkUpdateLogContains(STATE_SUCCEEDED + "\n" + CALL_QUIT);
+ checkCallbackLog();
+}
diff --git a/toolkit/mozapps/update/tests/unit_service_updater/marFileLockedFailureCompleteSvc_win.js b/toolkit/mozapps/update/tests/unit_service_updater/marFileLockedFailureCompleteSvc_win.js
new file mode 100644
index 000000000..c5efaa8c0
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_service_updater/marFileLockedFailureCompleteSvc_win.js
@@ -0,0 +1,57 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+/* File locked complete MAR file patch apply failure test */
+
+function run_test() {
+ if (!setupTestCommon()) {
+ return;
+ }
+ gTestFiles = gTestFilesCompleteSuccess;
+ gTestDirs = gTestDirsCompleteSuccess;
+ setTestFilesAndDirsForFailure();
+ setupUpdaterTest(FILE_COMPLETE_MAR, false);
+}
+
+/**
+ * Called after the call to setupUpdaterTest finishes.
+ */
+function setupUpdaterTestFinished() {
+ runHelperLockFile(gTestFiles[3]);
+}
+
+/**
+ * Called after the call to waitForHelperSleep finishes.
+ */
+function waitForHelperSleepFinished() {
+ runUpdate(STATE_FAILED_WRITE_ERROR, false, 1, true);
+}
+
+/**
+ * Called after the call to runUpdate finishes.
+ */
+function runUpdateFinished() {
+ waitForHelperExit();
+}
+
+/**
+ * Called after the call to waitForHelperExit finishes.
+ */
+function waitForHelperExitFinished() {
+ standardInit();
+ Assert.equal(readStatusFile(), STATE_PENDING,
+ "the status file failure code" + MSG_SHOULD_EQUAL);
+ Assert.equal(gUpdateManager.updateCount, 1,
+ "the update manager updateCount attribute" + MSG_SHOULD_EQUAL);
+ Assert.equal(gUpdateManager.getUpdateAt(0).state, STATE_PENDING,
+ "the update state" + MSG_SHOULD_EQUAL);
+ Assert.equal(gUpdateManager.getUpdateAt(0).errorCode, WRITE_ERROR,
+ "the update errorCode" + MSG_SHOULD_EQUAL);
+ checkPostUpdateRunningFile(false);
+ checkFilesAfterUpdateFailure(getApplyDirFile);
+ checkUpdateLogContains(ERR_RENAME_FILE);
+ checkUpdateLogContains(ERR_BACKUP_CREATE_7);
+ checkUpdateLogContains(STATE_FAILED_WRITE_ERROR + "\n" + CALL_QUIT);
+ checkCallbackLog();
+}
diff --git a/toolkit/mozapps/update/tests/unit_service_updater/marFileLockedFailurePartialSvc_win.js b/toolkit/mozapps/update/tests/unit_service_updater/marFileLockedFailurePartialSvc_win.js
new file mode 100644
index 000000000..4fdbadb5b
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_service_updater/marFileLockedFailurePartialSvc_win.js
@@ -0,0 +1,56 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+/* File locked partial MAR file patch apply failure test */
+
+function run_test() {
+ if (!setupTestCommon()) {
+ return;
+ }
+ gTestFiles = gTestFilesPartialSuccess;
+ gTestDirs = gTestDirsPartialSuccess;
+ setTestFilesAndDirsForFailure();
+ setupUpdaterTest(FILE_PARTIAL_MAR, false);
+}
+
+/**
+ * Called after the call to setupUpdaterTest finishes.
+ */
+function setupUpdaterTestFinished() {
+ runHelperLockFile(gTestFiles[2]);
+}
+
+/**
+ * Called after the call to waitForHelperSleep finishes.
+ */
+function waitForHelperSleepFinished() {
+ runUpdate(STATE_FAILED_READ_ERROR, false, 1, true);
+}
+
+/**
+ * Called after the call to runUpdate finishes.
+ */
+function runUpdateFinished() {
+ waitForHelperExit();
+}
+
+/**
+ * Called after the call to waitForHelperExit finishes.
+ */
+function waitForHelperExitFinished() {
+ standardInit();
+ Assert.equal(readStatusFile(), STATE_NONE,
+ "the status file failure code" + MSG_SHOULD_EQUAL);
+ Assert.equal(gUpdateManager.updateCount, 1,
+ "the update manager updateCount attribute" + MSG_SHOULD_EQUAL);
+ Assert.equal(gUpdateManager.getUpdateAt(0).state, STATE_FAILED,
+ "the update state" + MSG_SHOULD_EQUAL);
+ Assert.equal(gUpdateManager.getUpdateAt(0).errorCode, READ_ERROR,
+ "the update errorCode" + MSG_SHOULD_EQUAL);
+ checkPostUpdateRunningFile(false);
+ checkFilesAfterUpdateFailure(getApplyDirFile);
+ checkUpdateLogContains(ERR_UNABLE_OPEN_DEST);
+ checkUpdateLogContains(STATE_FAILED_READ_ERROR + "\n" + CALL_QUIT);
+ checkCallbackLog();
+}
diff --git a/toolkit/mozapps/update/tests/unit_service_updater/marFileLockedStageFailureCompleteSvc_win.js b/toolkit/mozapps/update/tests/unit_service_updater/marFileLockedStageFailureCompleteSvc_win.js
new file mode 100644
index 000000000..4d12f4e42
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_service_updater/marFileLockedStageFailureCompleteSvc_win.js
@@ -0,0 +1,71 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+/* File locked complete MAR file staged patch apply failure test */
+
+const STATE_AFTER_STAGE = STATE_PENDING;
+
+function run_test() {
+ if (!setupTestCommon()) {
+ return;
+ }
+ gTestFiles = gTestFilesCompleteSuccess;
+ gTestDirs = gTestDirsCompleteSuccess;
+ setTestFilesAndDirsForFailure();
+ setupUpdaterTest(FILE_COMPLETE_MAR, false);
+}
+
+/**
+ * Called after the call to setupUpdaterTest finishes.
+ */
+function setupUpdaterTestFinished() {
+ runHelperLockFile(gTestFiles[3]);
+}
+
+/**
+ * Called after the call to waitForHelperSleep finishes.
+ */
+function waitForHelperSleepFinished() {
+ stageUpdate(true);
+}
+
+/**
+ * Called after the call to stageUpdate finishes.
+ */
+function stageUpdateFinished() {
+ checkPostUpdateRunningFile(false);
+ // Files aren't checked after staging since this test locks a file which
+ // prevents reading the file.
+ checkUpdateLogContains(ERR_ENSURE_COPY);
+ // Switch the application to the staged application that was updated.
+ runUpdate(STATE_FAILED_WRITE_ERROR, false, 1, false);
+}
+
+/**
+ * Called after the call to runUpdate finishes.
+ */
+function runUpdateFinished() {
+ waitForHelperExit();
+}
+
+/**
+ * Called after the call to waitForHelperExit finishes.
+ */
+function waitForHelperExitFinished() {
+ standardInit();
+ Assert.equal(readStatusFile(), STATE_PENDING,
+ "the status file failure code" + MSG_SHOULD_EQUAL);
+ Assert.equal(gUpdateManager.updateCount, 2,
+ "the update manager updateCount attribute" + MSG_SHOULD_EQUAL);
+ Assert.equal(gUpdateManager.getUpdateAt(0).state, STATE_PENDING,
+ "the update state" + MSG_SHOULD_EQUAL);
+ Assert.equal(gUpdateManager.getUpdateAt(0).errorCode, WRITE_ERROR,
+ "the update errorCode" + MSG_SHOULD_EQUAL);
+ checkPostUpdateRunningFile(false);
+ checkFilesAfterUpdateFailure(getApplyDirFile);
+ checkUpdateLogContains(ERR_RENAME_FILE);
+ checkUpdateLogContains(ERR_BACKUP_CREATE_7);
+ checkUpdateLogContains(STATE_FAILED_WRITE_ERROR + "\n" + CALL_QUIT);
+ checkCallbackLog();
+}
diff --git a/toolkit/mozapps/update/tests/unit_service_updater/marFileLockedStageFailurePartialSvc_win.js b/toolkit/mozapps/update/tests/unit_service_updater/marFileLockedStageFailurePartialSvc_win.js
new file mode 100644
index 000000000..5f64df34c
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_service_updater/marFileLockedStageFailurePartialSvc_win.js
@@ -0,0 +1,70 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+/* File locked partial MAR file staged patch apply failure test */
+
+const STATE_AFTER_STAGE = STATE_PENDING;
+
+function run_test() {
+ if (!setupTestCommon()) {
+ return;
+ }
+ gTestFiles = gTestFilesPartialSuccess;
+ gTestDirs = gTestDirsPartialSuccess;
+ setTestFilesAndDirsForFailure();
+ setupUpdaterTest(FILE_PARTIAL_MAR, false);
+}
+
+/**
+ * Called after the call to setupUpdaterTest finishes.
+ */
+function setupUpdaterTestFinished() {
+ runHelperLockFile(gTestFiles[2]);
+}
+
+/**
+ * Called after the call to waitForHelperSleep finishes.
+ */
+function waitForHelperSleepFinished() {
+ stageUpdate(true);
+}
+
+/**
+ * Called after the call to stageUpdate finishes.
+ */
+function stageUpdateFinished() {
+ checkPostUpdateRunningFile(false);
+ // Files aren't checked after staging since this test locks a file which
+ // prevents reading the file.
+ checkUpdateLogContains(ERR_ENSURE_COPY);
+ // Switch the application to the staged application that was updated.
+ runUpdate(STATE_FAILED_READ_ERROR, false, 1, false);
+}
+
+/**
+ * Called after the call to runUpdate finishes.
+ */
+function runUpdateFinished() {
+ waitForHelperExit();
+}
+
+/**
+ * Called after the call to waitForHelperExit finishes.
+ */
+function waitForHelperExitFinished() {
+ standardInit();
+ Assert.equal(readStatusFile(), STATE_NONE,
+ "the status file failure code" + MSG_SHOULD_EQUAL);
+ Assert.equal(gUpdateManager.updateCount, 2,
+ "the update manager updateCount attribute" + MSG_SHOULD_EQUAL);
+ Assert.equal(gUpdateManager.getUpdateAt(0).state, STATE_FAILED,
+ "the update state" + MSG_SHOULD_EQUAL);
+ Assert.equal(gUpdateManager.getUpdateAt(0).errorCode, READ_ERROR,
+ "the update errorCode" + MSG_SHOULD_EQUAL);
+ checkPostUpdateRunningFile(false);
+ checkFilesAfterUpdateFailure(getApplyDirFile);
+ checkUpdateLogContains(ERR_UNABLE_OPEN_DEST);
+ checkUpdateLogContains(STATE_FAILED_READ_ERROR + "\n" + CALL_QUIT);
+ checkCallbackLog();
+}
diff --git a/toolkit/mozapps/update/tests/unit_service_updater/marRMRFDirFileInUseStageFailureCompleteSvc_win.js b/toolkit/mozapps/update/tests/unit_service_updater/marRMRFDirFileInUseStageFailureCompleteSvc_win.js
new file mode 100644
index 000000000..b83bafccc
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_service_updater/marRMRFDirFileInUseStageFailureCompleteSvc_win.js
@@ -0,0 +1,72 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+/* File in use inside removed dir complete MAR file staged patch apply failure
+ test */
+
+const STATE_AFTER_STAGE = IS_SERVICE_TEST ? STATE_APPLIED_SVC : STATE_APPLIED;
+const STATE_AFTER_RUNUPDATE = IS_SERVICE_TEST ? STATE_PENDING_SVC : STATE_PENDING;
+
+function run_test() {
+ if (!setupTestCommon()) {
+ return;
+ }
+ gTestFiles = gTestFilesCompleteSuccess;
+ gTestDirs = gTestDirsCompleteSuccess;
+ setupUpdaterTest(FILE_COMPLETE_MAR, false);
+}
+
+/**
+ * Called after the call to setupUpdaterTest finishes.
+ */
+function setupUpdaterTestFinished() {
+ runHelperFileInUse(gTestDirs[4].relPathDir + gTestDirs[4].subDirs[0] +
+ gTestDirs[4].subDirFiles[0], true);
+}
+
+/**
+ * Called after the call to waitForHelperSleep finishes.
+ */
+function waitForHelperSleepFinished() {
+ stageUpdate(true);
+}
+
+/**
+ * Called after the call to stageUpdate finishes.
+ */
+function stageUpdateFinished() {
+ checkPostUpdateRunningFile(false);
+ checkFilesAfterUpdateSuccess(getStageDirFile, true);
+ checkUpdateLogContents(LOG_COMPLETE_SUCCESS, true);
+ // Switch the application to the staged application that was updated.
+ runUpdate(STATE_AFTER_RUNUPDATE, true, 1, true);
+}
+
+/**
+ * Called after the call to runUpdate finishes.
+ */
+function runUpdateFinished() {
+ waitForHelperExit();
+}
+
+/**
+ * Called after the call to waitForHelperExit finishes.
+ */
+function waitForHelperExitFinished() {
+ standardInit();
+ Assert.equal(readStatusState(), STATE_NONE,
+ "the status file state" + MSG_SHOULD_EQUAL);
+ Assert.ok(!gUpdateManager.activeUpdate,
+ "the active update should not be defined");
+ Assert.equal(gUpdateManager.updateCount, 1,
+ "the update manager updateCount attribute" + MSG_SHOULD_EQUAL);
+ Assert.equal(gUpdateManager.getUpdateAt(0).state, STATE_AFTER_RUNUPDATE,
+ "the update state" + MSG_SHOULD_EQUAL);
+ checkPostUpdateRunningFile(false);
+ setTestFilesAndDirsForFailure();
+ checkFilesAfterUpdateFailure(getApplyDirFile);
+ checkUpdateLogContains(ERR_RENAME_FILE);
+ checkUpdateLogContains(ERR_MOVE_DESTDIR_7 + "\n" + CALL_QUIT);
+ checkCallbackLog();
+}
diff --git a/toolkit/mozapps/update/tests/unit_service_updater/marRMRFDirFileInUseStageFailurePartialSvc_win.js b/toolkit/mozapps/update/tests/unit_service_updater/marRMRFDirFileInUseStageFailurePartialSvc_win.js
new file mode 100644
index 000000000..39ea485cd
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_service_updater/marRMRFDirFileInUseStageFailurePartialSvc_win.js
@@ -0,0 +1,71 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+/* File in use inside removed dir partial MAR file staged patch apply failure
+ test */
+
+const STATE_AFTER_STAGE = IS_SERVICE_TEST ? STATE_APPLIED_SVC : STATE_APPLIED;
+const STATE_AFTER_RUNUPDATE = IS_SERVICE_TEST ? STATE_PENDING_SVC : STATE_PENDING;
+
+function run_test() {
+ if (!setupTestCommon()) {
+ return;
+ }
+ gTestFiles = gTestFilesPartialSuccess;
+ gTestDirs = gTestDirsPartialSuccess;
+ setupUpdaterTest(FILE_PARTIAL_MAR, false);
+}
+
+/**
+ * Called after the call to setupUpdaterTest finishes.
+ */
+function setupUpdaterTestFinished() {
+ runHelperFileInUse(gTestDirs[2].relPathDir + gTestDirs[2].files[0], true);
+}
+
+/**
+ * Called after the call to waitForHelperSleep finishes.
+ */
+function waitForHelperSleepFinished() {
+ stageUpdate(true);
+}
+
+/**
+ * Called after the call to stageUpdate finishes.
+ */
+function stageUpdateFinished() {
+ checkPostUpdateRunningFile(false);
+ checkFilesAfterUpdateSuccess(getStageDirFile, true);
+ checkUpdateLogContents(LOG_PARTIAL_SUCCESS, true);
+ // Switch the application to the staged application that was updated.
+ runUpdate(STATE_AFTER_RUNUPDATE, true, 1, true);
+}
+
+/**
+ * Called after the call to runUpdate finishes.
+ */
+function runUpdateFinished() {
+ waitForHelperExit();
+}
+
+/**
+ * Called after the call to waitForHelperExit finishes.
+ */
+function waitForHelperExitFinished() {
+ standardInit();
+ Assert.equal(readStatusState(), STATE_NONE,
+ "the status file state" + MSG_SHOULD_EQUAL);
+ Assert.ok(!gUpdateManager.activeUpdate,
+ "the active update should not be defined");
+ Assert.equal(gUpdateManager.updateCount, 1,
+ "the update manager updateCount attribute" + MSG_SHOULD_EQUAL);
+ Assert.equal(gUpdateManager.getUpdateAt(0).state, STATE_AFTER_RUNUPDATE,
+ "the update state" + MSG_SHOULD_EQUAL);
+ checkPostUpdateRunningFile(false);
+ setTestFilesAndDirsForFailure();
+ checkFilesAfterUpdateFailure(getApplyDirFile);
+ checkUpdateLogContains(ERR_RENAME_FILE);
+ checkUpdateLogContains(ERR_MOVE_DESTDIR_7 + "\n" + CALL_QUIT);
+ checkCallbackLog();
+}
diff --git a/toolkit/mozapps/update/tests/unit_service_updater/marRMRFDirFileInUseSuccessCompleteSvc_win.js b/toolkit/mozapps/update/tests/unit_service_updater/marRMRFDirFileInUseSuccessCompleteSvc_win.js
new file mode 100644
index 000000000..a71bb8d49
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_service_updater/marRMRFDirFileInUseSuccessCompleteSvc_win.js
@@ -0,0 +1,63 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+/* File in use inside removed dir complete MAR file patch apply success test */
+
+function run_test() {
+ if (!setupTestCommon()) {
+ return;
+ }
+ gTestFiles = gTestFilesCompleteSuccess;
+ gTestDirs = gTestDirsCompleteSuccess;
+ setupUpdaterTest(FILE_COMPLETE_MAR, false);
+}
+
+/**
+ * Called after the call to setupUpdaterTest finishes.
+ */
+function setupUpdaterTestFinished() {
+ runHelperFileInUse(gTestDirs[4].relPathDir + gTestDirs[4].subDirs[0] +
+ gTestDirs[4].subDirFiles[0], true);
+}
+
+/**
+ * Called after the call to waitForHelperSleep finishes.
+ */
+function waitForHelperSleepFinished() {
+ runUpdate(STATE_SUCCEEDED, false, 0, true);
+}
+
+/**
+ * Called after the call to runUpdate finishes.
+ */
+function runUpdateFinished() {
+ waitForHelperExit();
+}
+
+/**
+ * Called after the call to waitForHelperExit finishes.
+ */
+function waitForHelperExitFinished() {
+ checkPostUpdateAppLog();
+}
+
+/**
+ * Called after the call to checkPostUpdateAppLog finishes.
+ */
+function checkPostUpdateAppLogFinished() {
+ standardInit();
+ Assert.equal(readStatusState(), STATE_NONE,
+ "the status file state" + MSG_SHOULD_EQUAL);
+ Assert.ok(!gUpdateManager.activeUpdate,
+ "the active update should not be defined");
+ Assert.equal(gUpdateManager.updateCount, 1,
+ "the update manager updateCount attribute" + MSG_SHOULD_EQUAL);
+ Assert.equal(gUpdateManager.getUpdateAt(0).state, STATE_SUCCEEDED,
+ "the update state" + MSG_SHOULD_EQUAL);
+ checkPostUpdateRunningFile(true);
+ checkFilesAfterUpdateSuccess(getApplyDirFile, false, true);
+ checkUpdateLogContains(ERR_BACKUP_DISCARD);
+ checkUpdateLogContains(STATE_SUCCEEDED + "\n" + CALL_QUIT);
+ checkCallbackLog();
+}
diff --git a/toolkit/mozapps/update/tests/unit_service_updater/marRMRFDirFileInUseSuccessPartialSvc_win.js b/toolkit/mozapps/update/tests/unit_service_updater/marRMRFDirFileInUseSuccessPartialSvc_win.js
new file mode 100644
index 000000000..2cbe70ed8
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_service_updater/marRMRFDirFileInUseSuccessPartialSvc_win.js
@@ -0,0 +1,62 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+/* File in use inside removed dir partial MAR file patch apply success test */
+
+function run_test() {
+ if (!setupTestCommon()) {
+ return;
+ }
+ gTestFiles = gTestFilesPartialSuccess;
+ gTestDirs = gTestDirsPartialSuccess;
+ setupUpdaterTest(FILE_PARTIAL_MAR, false);
+}
+
+/**
+ * Called after the call to setupUpdaterTest finishes.
+ */
+function setupUpdaterTestFinished() {
+ runHelperFileInUse(gTestDirs[2].relPathDir + gTestDirs[2].files[0], true);
+}
+
+/**
+ * Called after the call to waitForHelperSleep finishes.
+ */
+function waitForHelperSleepFinished() {
+ runUpdate(STATE_SUCCEEDED, false, 0, true);
+}
+
+/**
+ * Called after the call to runUpdate finishes.
+ */
+function runUpdateFinished() {
+ waitForHelperExit();
+}
+
+/**
+ * Called after the call to waitForHelperExit finishes.
+ */
+function waitForHelperExitFinished() {
+ checkPostUpdateAppLog();
+}
+
+/**
+ * Called after the call to checkPostUpdateAppLog finishes.
+ */
+function checkPostUpdateAppLogFinished() {
+ standardInit();
+ Assert.equal(readStatusState(), STATE_NONE,
+ "the status file state" + MSG_SHOULD_EQUAL);
+ Assert.ok(!gUpdateManager.activeUpdate,
+ "the active update should not be defined");
+ Assert.equal(gUpdateManager.updateCount, 1,
+ "the update manager updateCount attribute" + MSG_SHOULD_EQUAL);
+ Assert.equal(gUpdateManager.getUpdateAt(0).state, STATE_SUCCEEDED,
+ "the update state" + MSG_SHOULD_EQUAL);
+ checkPostUpdateRunningFile(true);
+ checkFilesAfterUpdateSuccess(getApplyDirFile, false, true);
+ checkUpdateLogContains(ERR_BACKUP_DISCARD);
+ checkUpdateLogContains(STATE_SUCCEEDED + "\n" + CALL_QUIT);
+ checkCallbackLog();
+}
diff --git a/toolkit/mozapps/update/tests/unit_service_updater/marStageFailurePartialSvc.js b/toolkit/mozapps/update/tests/unit_service_updater/marStageFailurePartialSvc.js
new file mode 100644
index 000000000..a9ce23420
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_service_updater/marStageFailurePartialSvc.js
@@ -0,0 +1,41 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+/* General Partial MAR File Staged Patch Apply Failure Test */
+
+const STATE_AFTER_STAGE = STATE_FAILED;
+gStagingRemovedUpdate = true;
+
+function run_test() {
+ if (!setupTestCommon()) {
+ return;
+ }
+ gTestFiles = gTestFilesPartialSuccess;
+ gTestFiles[11].originalFile = "partial.png";
+ gTestDirs = gTestDirsPartialSuccess;
+ setTestFilesAndDirsForFailure();
+ setupUpdaterTest(FILE_PARTIAL_MAR, false);
+}
+
+/**
+ * Called after the call to setupUpdaterTest finishes.
+ */
+function setupUpdaterTestFinished() {
+ stageUpdate(true);
+}
+
+/**
+ * Called after the call to stageUpdate finishes.
+ */
+function stageUpdateFinished() {
+ Assert.equal(readStatusState(), STATE_NONE,
+ "the status file state" + MSG_SHOULD_EQUAL);
+ Assert.equal(gUpdateManager.getUpdateAt(0).errorCode, LOADSOURCE_ERROR_WRONG_SIZE,
+ "the update errorCode" + MSG_SHOULD_EQUAL);
+ checkPostUpdateRunningFile(false);
+ checkFilesAfterUpdateFailure(getApplyDirFile);
+ checkUpdateLogContains(ERR_LOADSOURCEFILE_FAILED);
+ waitForFilesInUse();
+}
diff --git a/toolkit/mozapps/update/tests/unit_service_updater/marStageSuccessCompleteSvc.js b/toolkit/mozapps/update/tests/unit_service_updater/marStageSuccessCompleteSvc.js
new file mode 100644
index 000000000..f7745f68f
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_service_updater/marStageSuccessCompleteSvc.js
@@ -0,0 +1,132 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+/* General Complete MAR File Staged Patch Apply Test */
+
+const STATE_AFTER_STAGE = IS_SERVICE_TEST ? STATE_APPLIED_SVC : STATE_APPLIED;
+
+function run_test() {
+ if (!setupTestCommon()) {
+ return;
+ }
+ gTestFiles = gTestFilesCompleteSuccess;
+ gTestFiles[gTestFiles.length - 1].originalContents = null;
+ gTestFiles[gTestFiles.length - 1].compareContents = "FromComplete\n";
+ gTestFiles[gTestFiles.length - 1].comparePerms = 0o644;
+ gTestDirs = gTestDirsCompleteSuccess;
+ setupDistributionDir();
+ setupSymLinks();
+ setupUpdaterTest(FILE_COMPLETE_MAR, false);
+}
+
+/**
+ * Called after the call to setupUpdaterTest finishes.
+ */
+function setupUpdaterTestFinished() {
+ stageUpdate(true);
+}
+
+/**
+ * Called after the call to stageUpdate finishes.
+ */
+function stageUpdateFinished() {
+ checkPostUpdateRunningFile(false);
+ checkFilesAfterUpdateSuccess(getStageDirFile, true);
+ checkUpdateLogContents(LOG_COMPLETE_SUCCESS, true);
+ // Switch the application to the staged application that was updated.
+ runUpdate(STATE_SUCCEEDED, true, 0, true);
+}
+
+/**
+ * Called after the call to runUpdate finishes.
+ */
+function runUpdateFinished() {
+ checkPostUpdateAppLog();
+}
+
+/**
+ * Called after the call to checkPostUpdateAppLog finishes.
+ */
+function checkPostUpdateAppLogFinished() {
+ checkAppBundleModTime();
+ checkSymLinks();
+ standardInit();
+ Assert.equal(readStatusState(), STATE_NONE,
+ "the status file state" + MSG_SHOULD_EQUAL);
+ Assert.ok(!gUpdateManager.activeUpdate,
+ "the active update should not be defined");
+ Assert.equal(gUpdateManager.updateCount, 1,
+ "the update manager updateCount attribute" + MSG_SHOULD_EQUAL);
+ Assert.equal(gUpdateManager.getUpdateAt(0).state, STATE_SUCCEEDED,
+ "the update state" + MSG_SHOULD_EQUAL);
+ checkPostUpdateRunningFile(true);
+ checkFilesAfterUpdateSuccess(getApplyDirFile, false, true);
+ checkUpdateLogContents(LOG_REPLACE_SUCCESS, false, true);
+ checkDistributionDir();
+ checkCallbackLog();
+}
+
+/**
+ * Setup the state of the distribution directory for the test.
+ */
+function setupDistributionDir() {
+ if (IS_MACOSX) {
+ // Create files in the old distribution directory location to verify that
+ // the directory and its contents are removed when there is a distribution
+ // directory in the new location.
+ let testFile = getApplyDirFile(DIR_MACOS + "distribution/testFile", true);
+ writeFile(testFile, "test\n");
+ testFile = getApplyDirFile(DIR_MACOS + "distribution/test1/testFile", true);
+ writeFile(testFile, "test\n");
+ }
+}
+
+/**
+ * Checks the state of the distribution directory for the test.
+ */
+function checkDistributionDir() {
+ if (IS_MACOSX) {
+ let distributionDir = getApplyDirFile(DIR_MACOS + "distribution", true);
+ Assert.ok(!distributionDir.exists(),
+ MSG_SHOULD_NOT_EXIST + getMsgPath(distributionDir.path));
+ checkUpdateLogContains(REMOVE_OLD_DIST_DIR);
+ }
+}
+
+/**
+ * Setup symlinks for the test.
+ */
+function setupSymLinks() {
+ // Don't test symlinks on Mac OS X in this test since it tends to timeout.
+ // It is tested on Mac OS X in marAppInUseStageSuccessComplete_unix.js
+ if (IS_UNIX && !IS_MACOSX) {
+ removeSymlink();
+ createSymlink();
+ do_register_cleanup(removeSymlink);
+ gTestFiles.splice(gTestFiles.length - 3, 0,
+ {
+ description: "Readable symlink",
+ fileName: "link",
+ relPathDir: DIR_RESOURCES,
+ originalContents: "test",
+ compareContents: "test",
+ originalFile: null,
+ compareFile: null,
+ originalPerms: 0o666,
+ comparePerms: 0o666
+ });
+ }
+}
+
+/**
+ * Checks the state of the symlinks for the test.
+ */
+function checkSymLinks() {
+ // Don't test symlinks on Mac OS X in this test since it tends to timeout.
+ // It is tested on Mac OS X in marAppInUseStageSuccessComplete_unix.js
+ if (IS_UNIX && !IS_MACOSX) {
+ checkSymlink();
+ }
+}
diff --git a/toolkit/mozapps/update/tests/unit_service_updater/marStageSuccessPartialSvc.js b/toolkit/mozapps/update/tests/unit_service_updater/marStageSuccessPartialSvc.js
new file mode 100644
index 000000000..ef15326de
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_service_updater/marStageSuccessPartialSvc.js
@@ -0,0 +1,112 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+/* General Partial MAR File Staged Patch Apply Test */
+
+const STATE_AFTER_STAGE = IS_SERVICE_TEST ? STATE_APPLIED_SVC : STATE_APPLIED;
+
+function run_test() {
+ if (!setupTestCommon()) {
+ return;
+ }
+ gTestFiles = gTestFilesPartialSuccess;
+ gTestFiles[gTestFiles.length - 2].originalContents = null;
+ gTestFiles[gTestFiles.length - 2].compareContents = "FromPartial\n";
+ gTestFiles[gTestFiles.length - 2].comparePerms = 0o644;
+ gTestDirs = gTestDirsPartialSuccess;
+ preventDistributionFiles();
+ setupDistributionDir();
+ setupUpdaterTest(FILE_PARTIAL_MAR, true);
+}
+
+/**
+ * Called after the call to setupUpdaterTest finishes.
+ */
+function setupUpdaterTestFinished() {
+ stageUpdate(true);
+}
+
+/**
+ * Called after the call to stageUpdate finishes.
+ */
+function stageUpdateFinished() {
+ checkPostUpdateRunningFile(false);
+ checkFilesAfterUpdateSuccess(getStageDirFile, true);
+ checkUpdateLogContents(LOG_PARTIAL_SUCCESS, true, false, true);
+ // Switch the application to the staged application that was updated.
+ runUpdate(STATE_SUCCEEDED, true, 0, true);
+}
+
+/**
+ * Called after the call to runUpdate finishes.
+ */
+function runUpdateFinished() {
+ checkPostUpdateAppLog();
+}
+
+/**
+ * Called after the call to checkPostUpdateAppLog finishes.
+ */
+function checkPostUpdateAppLogFinished() {
+ checkAppBundleModTime();
+ standardInit();
+ Assert.equal(readStatusState(), STATE_NONE,
+ "the status file state" + MSG_SHOULD_EQUAL);
+ Assert.ok(!gUpdateManager.activeUpdate,
+ "the active update should not be defined");
+ Assert.equal(gUpdateManager.updateCount, 1,
+ "the update manager updateCount attribute" + MSG_SHOULD_EQUAL);
+ Assert.equal(gUpdateManager.getUpdateAt(0).state, STATE_SUCCEEDED,
+ "the update state" + MSG_SHOULD_EQUAL);
+ checkPostUpdateRunningFile(true);
+ checkFilesAfterUpdateSuccess(getApplyDirFile, false, true);
+ checkUpdateLogContents(LOG_REPLACE_SUCCESS, false, true, true);
+ checkDistributionDir();
+ checkCallbackLog();
+}
+
+/**
+ * Setup the state of the distribution directory for the test.
+ */
+function setupDistributionDir() {
+ if (IS_MACOSX) {
+ // Create files in the old distribution directory location to verify that
+ // the directory and its contents are moved to the new location on update.
+ let testFile = getApplyDirFile(DIR_MACOS + "distribution/testFile", true);
+ writeFile(testFile, "test\n");
+ testFile = getApplyDirFile(DIR_MACOS + "distribution/test/testFile", true);
+ writeFile(testFile, "test\n");
+ }
+}
+
+/**
+ * Checks the state of the distribution directory.
+ */
+function checkDistributionDir() {
+ let distributionDir = getApplyDirFile(DIR_RESOURCES + "distribution", true);
+ if (IS_MACOSX) {
+ Assert.ok(distributionDir.exists(),
+ MSG_SHOULD_EXIST + getMsgPath(distributionDir.path));
+
+ let testFile = getApplyDirFile(DIR_RESOURCES + "distribution/testFile", true);
+ Assert.ok(testFile.exists(),
+ MSG_SHOULD_EXIST + getMsgPath(testFile.path));
+
+ testFile = getApplyDirFile(DIR_RESOURCES + "distribution/test/testFile", true);
+ Assert.ok(testFile.exists(),
+ MSG_SHOULD_EXIST + getMsgPath(testFile.path));
+
+ distributionDir = getApplyDirFile(DIR_MACOS + "distribution", true);
+ Assert.ok(!distributionDir.exists(),
+ MSG_SHOULD_NOT_EXIST + getMsgPath(distributionDir.path));
+
+ checkUpdateLogContains(MOVE_OLD_DIST_DIR);
+ } else {
+ debugDump("testing that files aren't added with an add-if instruction " +
+ "when the file's destination directory doesn't exist");
+ Assert.ok(!distributionDir.exists(),
+ MSG_SHOULD_NOT_EXIST + getMsgPath(distributionDir.path));
+ }
+}
diff --git a/toolkit/mozapps/update/tests/unit_service_updater/marSuccessCompleteSvc.js b/toolkit/mozapps/update/tests/unit_service_updater/marSuccessCompleteSvc.js
new file mode 100644
index 000000000..1008e867f
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_service_updater/marSuccessCompleteSvc.js
@@ -0,0 +1,96 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+/* General Complete MAR File Patch Apply Test */
+
+function run_test() {
+ if (!setupTestCommon()) {
+ return;
+ }
+ gTestFiles = gTestFilesCompleteSuccess;
+ gTestDirs = gTestDirsCompleteSuccess;
+ preventDistributionFiles();
+ setupDistributionDir();
+ setupUpdaterTest(FILE_COMPLETE_MAR, true);
+}
+
+/**
+ * Called after the call to setupUpdaterTest finishes.
+ */
+function setupUpdaterTestFinished() {
+ runUpdate(STATE_SUCCEEDED, false, 0, true);
+}
+
+/**
+ * Called after the call to runUpdate finishes.
+ */
+function runUpdateFinished() {
+ checkPostUpdateAppLog();
+}
+
+/**
+ * Called after the call to checkPostUpdateAppLog finishes.
+ */
+function checkPostUpdateAppLogFinished() {
+ checkAppBundleModTime();
+ standardInit();
+ Assert.equal(readStatusState(), STATE_NONE,
+ "the status file state" + MSG_SHOULD_EQUAL);
+ Assert.ok(!gUpdateManager.activeUpdate,
+ "the active update should not be defined");
+ Assert.equal(gUpdateManager.updateCount, 1,
+ "the update manager updateCount attribute" + MSG_SHOULD_EQUAL);
+ Assert.equal(gUpdateManager.getUpdateAt(0).state, STATE_SUCCEEDED,
+ "the update state" + MSG_SHOULD_EQUAL);
+ checkPostUpdateRunningFile(true);
+ checkFilesAfterUpdateSuccess(getApplyDirFile);
+ checkUpdateLogContents(LOG_COMPLETE_SUCCESS, false, false, true);
+ checkDistributionDir();
+ checkCallbackLog();
+}
+
+/**
+ * Setup the state of the distribution directory for the test.
+ */
+function setupDistributionDir() {
+ if (IS_MACOSX) {
+ // Create files in the old distribution directory location to verify that
+ // the directory and its contents are moved to the new location on update.
+ let testFile = getApplyDirFile(DIR_MACOS + "distribution/testFile", true);
+ writeFile(testFile, "test\n");
+ testFile = getApplyDirFile(DIR_MACOS + "distribution/test/testFile", true);
+ writeFile(testFile, "test\n");
+ }
+}
+
+/**
+ * Checks the state of the distribution directory.
+ */
+function checkDistributionDir() {
+ let distributionDir = getApplyDirFile(DIR_RESOURCES + "distribution", true);
+ if (IS_MACOSX) {
+ Assert.ok(distributionDir.exists(),
+ MSG_SHOULD_EXIST + getMsgPath(distributionDir.path));
+
+ let testFile = getApplyDirFile(DIR_RESOURCES + "distribution/testFile", true);
+ Assert.ok(testFile.exists(),
+ MSG_SHOULD_EXIST + getMsgPath(testFile.path));
+
+ testFile = getApplyDirFile(DIR_RESOURCES + "distribution/test/testFile", true);
+ Assert.ok(testFile.exists(),
+ MSG_SHOULD_EXIST + getMsgPath(testFile.path));
+
+ distributionDir = getApplyDirFile(DIR_MACOS + "distribution", true);
+ Assert.ok(!distributionDir.exists(),
+ MSG_SHOULD_NOT_EXIST + getMsgPath(distributionDir.path));
+
+ checkUpdateLogContains(MOVE_OLD_DIST_DIR);
+ } else {
+ debugDump("testing that files aren't added with an add-if instruction " +
+ "when the file's destination directory doesn't exist");
+ Assert.ok(!distributionDir.exists(),
+ MSG_SHOULD_NOT_EXIST + getMsgPath(distributionDir.path));
+ }
+}
diff --git a/toolkit/mozapps/update/tests/unit_service_updater/marSuccessPartialSvc.js b/toolkit/mozapps/update/tests/unit_service_updater/marSuccessPartialSvc.js
new file mode 100644
index 000000000..616390f55
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_service_updater/marSuccessPartialSvc.js
@@ -0,0 +1,79 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+/* General Partial MAR File Patch Apply Test */
+
+function run_test() {
+ if (!setupTestCommon()) {
+ return;
+ }
+ gTestFiles = gTestFilesPartialSuccess;
+ gTestFiles[gTestFiles.length - 1].originalContents = null;
+ gTestFiles[gTestFiles.length - 1].compareContents = "FromPartial\n";
+ gTestFiles[gTestFiles.length - 1].comparePerms = 0o644;
+ gTestFiles[gTestFiles.length - 2].originalContents = null;
+ gTestFiles[gTestFiles.length - 2].compareContents = "FromPartial\n";
+ gTestFiles[gTestFiles.length - 2].comparePerms = 0o644;
+ gTestDirs = gTestDirsPartialSuccess;
+ setupDistributionDir();
+ // The third parameter will test that a relative path that contains a
+ // directory traversal to the post update binary doesn't execute.
+ setupUpdaterTest(FILE_PARTIAL_MAR, false, "test/../");
+}
+
+/**
+ * Called after the call to setupUpdaterTest finishes.
+ */
+function setupUpdaterTestFinished() {
+ runUpdate(STATE_SUCCEEDED, false, 0, true);
+}
+
+/**
+ * Called after the call to runUpdate finishes.
+ */
+function runUpdateFinished() {
+ checkAppBundleModTime();
+ standardInit();
+ Assert.equal(readStatusState(), STATE_NONE,
+ "the status file state" + MSG_SHOULD_EQUAL);
+ Assert.ok(!gUpdateManager.activeUpdate,
+ "the active update should not be defined");
+ Assert.equal(gUpdateManager.updateCount, 1,
+ "the update manager updateCount attribute" + MSG_SHOULD_EQUAL);
+ Assert.equal(gUpdateManager.getUpdateAt(0).state, STATE_SUCCEEDED,
+ "the update state" + MSG_SHOULD_EQUAL);
+ checkPostUpdateRunningFile(false);
+ checkFilesAfterUpdateSuccess(getApplyDirFile);
+ checkUpdateLogContents(LOG_PARTIAL_SUCCESS);
+ checkDistributionDir();
+ checkCallbackLog();
+}
+
+/**
+ * Setup the state of the distribution directory for the test.
+ */
+function setupDistributionDir() {
+ if (IS_MACOSX) {
+ // Create files in the old distribution directory location to verify that
+ // the directory and its contents are removed when there is a distribution
+ // directory in the new location.
+ let testFile = getApplyDirFile(DIR_MACOS + "distribution/testFile", true);
+ writeFile(testFile, "test\n");
+ testFile = getApplyDirFile(DIR_MACOS + "distribution/test/testFile", true);
+ writeFile(testFile, "test\n");
+ }
+}
+
+/**
+ * Checks the state of the distribution directory.
+ */
+function checkDistributionDir() {
+ if (IS_MACOSX) {
+ let distributionDir = getApplyDirFile(DIR_MACOS + "distribution", true);
+ Assert.ok(!distributionDir.exists(),
+ MSG_SHOULD_NOT_EXIST + getMsgPath(distributionDir.path));
+ checkUpdateLogContains(REMOVE_OLD_DIST_DIR);
+ }
+}
diff --git a/toolkit/mozapps/update/tests/unit_service_updater/xpcshell.ini b/toolkit/mozapps/update/tests/unit_service_updater/xpcshell.ini
new file mode 100644
index 000000000..1d63f8583
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_service_updater/xpcshell.ini
@@ -0,0 +1,156 @@
+; This Source Code Form is subject to the terms of the Mozilla Public
+; License, v. 2.0. If a copy of the MPL was not distributed with this
+; file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+; Tests that require the updater binary and the maintenance service.
+
+[DEFAULT]
+tags = appupdate
+head = head_update.js
+tail =
+
+[bootstrapSvc.js]
+skip-if = os == 'win' && debug && (os_version == '5.1' || os_version == '5.2')
+reason = bug 1291985
+run-sequentially = Uses the Mozilla Maintenance Service.
+[invalidArgInstallDirPathTooLongFailureSvc.js]
+skip-if = os == 'win' && debug && (os_version == '5.1' || os_version == '5.2')
+reason = bug 1291985
+run-sequentially = Uses the Mozilla Maintenance Service.
+[invalidArgInstallDirPathTraversalFailureSvc.js]
+skip-if = os == 'win' && debug && (os_version == '5.1' || os_version == '5.2')
+reason = bug 1291985
+run-sequentially = Uses the Mozilla Maintenance Service.
+[invalidArgInstallWorkingDirPathNotSameFailureSvc_win.js]
+skip-if = os == 'win' && debug && (os_version == '5.1' || os_version == '5.2')
+reason = bug 1291985
+run-sequentially = Uses the Mozilla Maintenance Service.
+[invalidArgPatchDirPathTraversalFailureSvc.js]
+skip-if = os == 'win' && debug && (os_version == '5.1' || os_version == '5.2')
+reason = bug 1291985
+run-sequentially = Uses the Mozilla Maintenance Service.
+[invalidArgStageDirNotInInstallDirFailureSvc_win.js]
+skip-if = os == 'win' && debug && (os_version == '5.1' || os_version == '5.2')
+reason = bug 1291985
+run-sequentially = Uses the Mozilla Maintenance Service.
+[invalidArgWorkingDirPathLocalUNCFailureSvc_win.js]
+skip-if = os == 'win' && debug && (os_version == '5.1' || os_version == '5.2')
+reason = bug 1291985
+run-sequentially = Uses the Mozilla Maintenance Service.
+[invalidArgWorkingDirPathRelativeFailureSvc.js]
+skip-if = os == 'win' && debug && (os_version == '5.1' || os_version == '5.2')
+reason = bug 1291985
+run-sequentially = Uses the Mozilla Maintenance Service.
+[marSuccessCompleteSvc.js]
+skip-if = os == 'win' && debug && (os_version == '5.1' || os_version == '5.2')
+reason = bug 1291985
+run-sequentially = Uses the Mozilla Maintenance Service.
+[marSuccessPartialSvc.js]
+skip-if = os == 'win' && debug && (os_version == '5.1' || os_version == '5.2')
+reason = bug 1291985
+run-sequentially = Uses the Mozilla Maintenance Service.
+[marFailurePartialSvc.js]
+skip-if = os == 'win' && debug && (os_version == '5.1' || os_version == '5.2')
+reason = bug 1291985
+run-sequentially = Uses the Mozilla Maintenance Service.
+[marStageSuccessCompleteSvc.js]
+skip-if = os == 'win' && debug && (os_version == '5.1' || os_version == '5.2')
+reason = bug 1291985
+run-sequentially = Uses the Mozilla Maintenance Service.
+[marStageSuccessPartialSvc.js]
+skip-if = os == 'win' && debug && (os_version == '5.1' || os_version == '5.2')
+reason = bug 1291985
+run-sequentially = Uses the Mozilla Maintenance Service.
+[marStageFailurePartialSvc.js]
+skip-if = os == 'win' && debug && (os_version == '5.1' || os_version == '5.2')
+reason = bug 1291985
+run-sequentially = Uses the Mozilla Maintenance Service.
+[marCallbackAppSuccessCompleteSvc_win.js]
+skip-if = os == 'win' && debug && (os_version == '5.1' || os_version == '5.2')
+reason = bug 1291985
+run-sequentially = Uses the Mozilla Maintenance Service.
+[marCallbackAppSuccessPartialSvc_win.js]
+skip-if = os == 'win' && debug && (os_version == '5.1' || os_version == '5.2')
+reason = bug 1291985
+run-sequentially = Uses the Mozilla Maintenance Service.
+[marCallbackAppStageSuccessCompleteSvc_win.js]
+skip-if = os == 'win' && debug && (os_version == '5.1' || os_version == '5.2')
+reason = bug 1291985
+run-sequentially = Uses the Mozilla Maintenance Service.
+[marCallbackAppStageSuccessPartialSvc_win.js]
+skip-if = os == 'win' && debug && (os_version == '5.1' || os_version == '5.2')
+reason = bug 1291985
+run-sequentially = Uses the Mozilla Maintenance Service.
+[marAppInUseSuccessCompleteSvc.js]
+skip-if = os == 'win' && debug && (os_version == '5.1' || os_version == '5.2')
+reason = bug 1291985
+run-sequentially = Uses the Mozilla Maintenance Service.
+[marAppInUseStageFailureCompleteSvc_win.js]
+skip-if = os == 'win' && debug && (os_version == '5.1' || os_version == '5.2')
+reason = bug 1291985
+run-sequentially = Uses the Mozilla Maintenance Service.
+[marFileLockedFailureCompleteSvc_win.js]
+skip-if = os == 'win' && debug && (os_version == '5.1' || os_version == '5.2')
+reason = bug 1291985
+run-sequentially = Uses the Mozilla Maintenance Service.
+[marFileLockedFailurePartialSvc_win.js]
+skip-if = os == 'win' && debug && (os_version == '5.1' || os_version == '5.2')
+reason = bug 1291985
+run-sequentially = Uses the Mozilla Maintenance Service.
+[marFileLockedStageFailureCompleteSvc_win.js]
+skip-if = os == 'win' && debug && (os_version == '5.1' || os_version == '5.2')
+reason = bug 1291985
+run-sequentially = Uses the Mozilla Maintenance Service.
+[marFileLockedStageFailurePartialSvc_win.js]
+skip-if = os == 'win' && debug && (os_version == '5.1' || os_version == '5.2')
+reason = bug 1291985
+run-sequentially = Uses the Mozilla Maintenance Service.
+[marFileInUseSuccessCompleteSvc_win.js]
+skip-if = os == 'win' && debug && (os_version == '5.1' || os_version == '5.2')
+reason = bug 1291985
+run-sequentially = Uses the Mozilla Maintenance Service.
+[marFileInUseSuccessPartialSvc_win.js]
+skip-if = os == 'win' && debug && (os_version == '5.1' || os_version == '5.2')
+reason = bug 1291985
+run-sequentially = Uses the Mozilla Maintenance Service.
+[marRMRFDirFileInUseSuccessCompleteSvc_win.js]
+skip-if = os == 'win' && debug && (os_version == '5.1' || os_version == '5.2')
+reason = bug 1291985
+run-sequentially = Uses the Mozilla Maintenance Service.
+[marRMRFDirFileInUseSuccessPartialSvc_win.js]
+skip-if = os == 'win' && debug && (os_version == '5.1' || os_version == '5.2')
+reason = bug 1291985
+run-sequentially = Uses the Mozilla Maintenance Service.
+[marFileInUseStageFailureCompleteSvc_win.js]
+skip-if = os == 'win' && debug && (os_version == '5.1' || os_version == '5.2')
+reason = bug 1291985
+run-sequentially = Uses the Mozilla Maintenance Service.
+[marFileInUseStageFailurePartialSvc_win.js]
+skip-if = os == 'win' && debug && (os_version == '5.1' || os_version == '5.2')
+reason = bug 1291985
+run-sequentially = Uses the Mozilla Maintenance Service.
+[marRMRFDirFileInUseStageFailureCompleteSvc_win.js]
+skip-if = os == 'win' && debug && (os_version == '5.1' || os_version == '5.2')
+reason = bug 1291985
+run-sequentially = Uses the Mozilla Maintenance Service.
+[marRMRFDirFileInUseStageFailurePartialSvc_win.js]
+skip-if = os == 'win' && debug && (os_version == '5.1' || os_version == '5.2')
+reason = bug 1291985
+run-sequentially = Uses the Mozilla Maintenance Service.
+[marAppApplyDirLockedStageFailureSvc_win.js]
+skip-if = os == 'win' && debug && (os_version == '5.1' || os_version == '5.2')
+reason = bug 1291985
+run-sequentially = Uses the Mozilla Maintenance Service.
+[marAppApplyUpdateAppBinInUseStageSuccessSvc_win.js]
+skip-if = os == 'win' && debug && (os_version == '5.1' || os_version == '5.2')
+reason = bug 1291985
+run-sequentially = Uses the Mozilla Maintenance Service.
+[marAppApplyUpdateSuccessSvc.js]
+skip-if = os == 'win' && debug && (os_version == '5.1' || os_version == '5.2')
+reason = bug 1291985
+run-sequentially = Uses the Mozilla Maintenance Service.
+[marAppApplyUpdateStageSuccessSvc.js]
+skip-if = os == 'win' && debug && (os_version == '5.1' || os_version == '5.2')
+reason = bug 1291985
+run-sequentially = Uses the Mozilla Maintenance Service.
+[checkUpdaterSigSvc.js]
diff --git a/toolkit/mozapps/update/updater/Launchd.plist b/toolkit/mozapps/update/updater/Launchd.plist
new file mode 100644
index 000000000..f0b5cef08
--- /dev/null
+++ b/toolkit/mozapps/update/updater/Launchd.plist
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+ <key>Label</key>
+ <string>org.mozilla.updater</string>
+ <key>RunAtLoad</key>
+ <true/>
+</dict>
+</plist>
diff --git a/toolkit/mozapps/update/updater/Makefile.in b/toolkit/mozapps/update/updater/Makefile.in
new file mode 100644
index 000000000..84a843d18
--- /dev/null
+++ b/toolkit/mozapps/update/updater/Makefile.in
@@ -0,0 +1,29 @@
+# vim:set ts=8 sw=8 sts=8 noet:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+# For changes here, also consider ./updater-xpcshell/Makefile.in
+
+ifndef MOZ_WINCONSOLE
+ifdef MOZ_DEBUG
+MOZ_WINCONSOLE = 1
+else
+MOZ_WINCONSOLE = 0
+endif
+endif
+
+include $(topsrcdir)/config/rules.mk
+
+ifeq (cocoa,$(MOZ_WIDGET_TOOLKIT))
+export::
+ sed -e 's/%MOZ_MACBUNDLE_ID%/$(MOZ_MACBUNDLE_ID)/' $(srcdir)/macbuild/Contents/Info.plist.in > $(DIST)/bin/Info.plist
+libs::
+ $(NSINSTALL) -D $(DIST)/bin/updater.app
+ rsync -a -C --exclude '*.in' $(srcdir)/macbuild/Contents $(DIST)/bin/updater.app
+ rsync -a -C $(DIST)/bin/Info.plist $(DIST)/bin/updater.app/Contents
+ sed -e 's/%APP_NAME%/$(MOZ_APP_DISPLAYNAME)/' $(srcdir)/macbuild/Contents/Resources/English.lproj/InfoPlist.strings.in | \
+ iconv -f UTF-8 -t UTF-16 > $(DIST)/bin/updater.app/Contents/Resources/English.lproj/InfoPlist.strings
+ $(NSINSTALL) -D $(DIST)/bin/updater.app/Contents/MacOS
+ $(NSINSTALL) $(DIST)/bin/org.mozilla.updater $(DIST)/bin/updater.app/Contents/MacOS
+endif
diff --git a/toolkit/mozapps/update/updater/archivereader.cpp b/toolkit/mozapps/update/updater/archivereader.cpp
new file mode 100644
index 000000000..90cf45c3d
--- /dev/null
+++ b/toolkit/mozapps/update/updater/archivereader.cpp
@@ -0,0 +1,324 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include <string.h>
+#include <stdlib.h>
+#include <fcntl.h>
+#include "bzlib.h"
+#include "archivereader.h"
+#include "errors.h"
+#ifdef XP_WIN
+#include "nsAlgorithm.h" // Needed by nsVersionComparator.cpp
+#include "updatehelper.h"
+#endif
+
+// These are generated at compile time based on the DER file for the channel
+// being used
+#ifdef MOZ_VERIFY_MAR_SIGNATURE
+#ifdef TEST_UPDATER
+#include "../xpcshellCert.h"
+#else
+#include "primaryCert.h"
+#include "secondaryCert.h"
+#endif
+#endif
+
+#define UPDATER_NO_STRING_GLUE_STL
+#include "nsVersionComparator.cpp"
+#undef UPDATER_NO_STRING_GLUE_STL
+
+#if defined(XP_UNIX)
+# include <sys/types.h>
+#elif defined(XP_WIN)
+# include <io.h>
+#endif
+
+static int inbuf_size = 262144;
+static int outbuf_size = 262144;
+static char *inbuf = nullptr;
+static char *outbuf = nullptr;
+
+/**
+ * Performs a verification on the opened MAR file with the passed in
+ * certificate name ID and type ID.
+ *
+ * @param archive The MAR file to verify the signature on.
+ * @param certData The certificate data.
+ * @return OK on success, CERT_VERIFY_ERROR on failure.
+*/
+template<uint32_t SIZE>
+int
+VerifyLoadedCert(MarFile *archive, const uint8_t (&certData)[SIZE])
+{
+ (void)archive;
+ (void)certData;
+
+#ifdef MOZ_VERIFY_MAR_SIGNATURE
+ const uint32_t size = SIZE;
+ const uint8_t* const data = &certData[0];
+ if (mar_verify_signatures(archive, &data, &size, 1)) {
+ return CERT_VERIFY_ERROR;
+ }
+#endif
+
+ return OK;
+}
+
+/**
+ * Performs a verification on the opened MAR file. Both the primary and backup
+ * keys stored are stored in the current process and at least the primary key
+ * will be tried. Success will be returned as long as one of the two
+ * signatures verify.
+ *
+ * @return OK on success
+*/
+int
+ArchiveReader::VerifySignature()
+{
+ if (!mArchive) {
+ return ARCHIVE_NOT_OPEN;
+ }
+
+#ifndef MOZ_VERIFY_MAR_SIGNATURE
+ return OK;
+#else
+#ifdef TEST_UPDATER
+ int rv = VerifyLoadedCert(mArchive, xpcshellCertData);
+#else
+ int rv = VerifyLoadedCert(mArchive, primaryCertData);
+ if (rv != OK) {
+ rv = VerifyLoadedCert(mArchive, secondaryCertData);
+ }
+#endif
+ return rv;
+#endif
+}
+
+/**
+ * Verifies that the MAR file matches the current product, channel, and version
+ *
+ * @param MARChannelID The MAR channel name to use, only updates from MARs
+ * with a matching MAR channel name will succeed.
+ * If an empty string is passed, no check will be done
+ * for the channel name in the product information block.
+ * If a comma separated list of values is passed then
+ * one value must match.
+ * @param appVersion The application version to use, only MARs with an
+ * application version >= to appVersion will be applied.
+ * @return OK on success
+ * COULD_NOT_READ_PRODUCT_INFO_BLOCK if the product info block
+ * could not be read.
+ * MARCHANNEL_MISMATCH_ERROR if update-settings.ini's MAR
+ * channel ID doesn't match the MAR
+ * file's MAR channel ID.
+ * VERSION_DOWNGRADE_ERROR if the application version for
+ * this updater is newer than the
+ * one in the MAR.
+ */
+int
+ArchiveReader::VerifyProductInformation(const char *MARChannelID,
+ const char *appVersion)
+{
+ if (!mArchive) {
+ return ARCHIVE_NOT_OPEN;
+ }
+
+ ProductInformationBlock productInfoBlock;
+ int rv = mar_read_product_info_block(mArchive,
+ &productInfoBlock);
+ if (rv != OK) {
+ return COULD_NOT_READ_PRODUCT_INFO_BLOCK_ERROR;
+ }
+
+ // Only check the MAR channel name if specified, it should be passed in from
+ // the update-settings.ini file.
+ if (MARChannelID && strlen(MARChannelID)) {
+ // Check for at least one match in the comma separated list of values.
+ const char *delimiter = " ,\t";
+ // Make a copy of the string in case a read only memory buffer
+ // was specified. strtok modifies the input buffer.
+ char channelCopy[512] = { 0 };
+ strncpy(channelCopy, MARChannelID, sizeof(channelCopy) - 1);
+ char *channel = strtok(channelCopy, delimiter);
+ rv = MAR_CHANNEL_MISMATCH_ERROR;
+ while(channel) {
+ if (!strcmp(channel, productInfoBlock.MARChannelID)) {
+ rv = OK;
+ break;
+ }
+ channel = strtok(nullptr, delimiter);
+ }
+ }
+
+ if (rv == OK) {
+ /* Compare both versions to ensure we don't have a downgrade
+ -1 if appVersion is older than productInfoBlock.productVersion
+ 1 if appVersion is newer than productInfoBlock.productVersion
+ 0 if appVersion is the same as productInfoBlock.productVersion
+ This even works with strings like:
+ - 12.0a1 being older than 12.0a2
+ - 12.0a2 being older than 12.0b1
+ - 12.0a1 being older than 12.0
+ - 12.0 being older than 12.1a1 */
+ int versionCompareResult =
+ mozilla::CompareVersions(appVersion, productInfoBlock.productVersion);
+ if (1 == versionCompareResult) {
+ rv = VERSION_DOWNGRADE_ERROR;
+ }
+ }
+
+ free((void *)productInfoBlock.MARChannelID);
+ free((void *)productInfoBlock.productVersion);
+ return rv;
+}
+
+int
+ArchiveReader::Open(const NS_tchar *path)
+{
+ if (mArchive)
+ Close();
+
+ if (!inbuf) {
+ inbuf = (char *)malloc(inbuf_size);
+ if (!inbuf) {
+ // Try again with a smaller buffer.
+ inbuf_size = 1024;
+ inbuf = (char *)malloc(inbuf_size);
+ if (!inbuf)
+ return ARCHIVE_READER_MEM_ERROR;
+ }
+ }
+
+ if (!outbuf) {
+ outbuf = (char *)malloc(outbuf_size);
+ if (!outbuf) {
+ // Try again with a smaller buffer.
+ outbuf_size = 1024;
+ outbuf = (char *)malloc(outbuf_size);
+ if (!outbuf)
+ return ARCHIVE_READER_MEM_ERROR;
+ }
+ }
+
+#ifdef XP_WIN
+ mArchive = mar_wopen(path);
+#else
+ mArchive = mar_open(path);
+#endif
+ if (!mArchive)
+ return READ_ERROR;
+
+ return OK;
+}
+
+void
+ArchiveReader::Close()
+{
+ if (mArchive) {
+ mar_close(mArchive);
+ mArchive = nullptr;
+ }
+
+ if (inbuf) {
+ free(inbuf);
+ inbuf = nullptr;
+ }
+
+ if (outbuf) {
+ free(outbuf);
+ outbuf = nullptr;
+ }
+}
+
+int
+ArchiveReader::ExtractFile(const char *name, const NS_tchar *dest)
+{
+ const MarItem *item = mar_find_item(mArchive, name);
+ if (!item)
+ return READ_ERROR;
+
+#ifdef XP_WIN
+ FILE* fp = _wfopen(dest, L"wb+");
+#else
+ int fd = creat(dest, item->flags);
+ if (fd == -1)
+ return WRITE_ERROR;
+
+ FILE *fp = fdopen(fd, "wb");
+#endif
+ if (!fp)
+ return WRITE_ERROR;
+
+ int rv = ExtractItemToStream(item, fp);
+
+ fclose(fp);
+ return rv;
+}
+
+int
+ArchiveReader::ExtractFileToStream(const char *name, FILE *fp)
+{
+ const MarItem *item = mar_find_item(mArchive, name);
+ if (!item)
+ return READ_ERROR;
+
+ return ExtractItemToStream(item, fp);
+}
+
+int
+ArchiveReader::ExtractItemToStream(const MarItem *item, FILE *fp)
+{
+ /* decompress the data chunk by chunk */
+
+ bz_stream strm;
+ int offset, inlen, outlen, ret = OK;
+
+ memset(&strm, 0, sizeof(strm));
+ if (BZ2_bzDecompressInit(&strm, 0, 0) != BZ_OK)
+ return UNEXPECTED_BZIP_ERROR;
+
+ offset = 0;
+ for (;;) {
+ if (!item->length) {
+ ret = UNEXPECTED_MAR_ERROR;
+ break;
+ }
+
+ if (offset < (int) item->length && strm.avail_in == 0) {
+ inlen = mar_read(mArchive, item, offset, inbuf, inbuf_size);
+ if (inlen <= 0)
+ return READ_ERROR;
+ offset += inlen;
+ strm.next_in = inbuf;
+ strm.avail_in = inlen;
+ }
+
+ strm.next_out = outbuf;
+ strm.avail_out = outbuf_size;
+
+ ret = BZ2_bzDecompress(&strm);
+ if (ret != BZ_OK && ret != BZ_STREAM_END) {
+ ret = UNEXPECTED_BZIP_ERROR;
+ break;
+ }
+
+ outlen = outbuf_size - strm.avail_out;
+ if (outlen) {
+ if (fwrite(outbuf, outlen, 1, fp) != 1) {
+ ret = WRITE_ERROR_EXTRACT;
+ break;
+ }
+ }
+
+ if (ret == BZ_STREAM_END) {
+ ret = OK;
+ break;
+ }
+ }
+
+ BZ2_bzDecompressEnd(&strm);
+ return ret;
+}
diff --git a/toolkit/mozapps/update/updater/archivereader.h b/toolkit/mozapps/update/updater/archivereader.h
new file mode 100644
index 000000000..a9d78aab1
--- /dev/null
+++ b/toolkit/mozapps/update/updater/archivereader.h
@@ -0,0 +1,41 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef ArchiveReader_h__
+#define ArchiveReader_h__
+
+#include <stdio.h>
+#include "mar.h"
+
+#ifdef XP_WIN
+ typedef WCHAR NS_tchar;
+#else
+ typedef char NS_tchar;
+#endif
+
+// This class provides an API to extract files from an update archive.
+class ArchiveReader
+{
+public:
+ ArchiveReader() : mArchive(nullptr) {}
+ ~ArchiveReader() { Close(); }
+
+ int Open(const NS_tchar *path);
+ int VerifySignature();
+ int VerifyProductInformation(const char *MARChannelID,
+ const char *appVersion);
+ void Close();
+
+ int ExtractFile(const char *item, const NS_tchar *destination);
+ int ExtractFileToStream(const char *item, FILE *fp);
+
+private:
+ int ExtractItemToStream(const MarItem *item, FILE *fp);
+
+ MarFile *mArchive;
+};
+
+#endif // ArchiveReader_h__
diff --git a/toolkit/mozapps/update/updater/automounter_gonk.cpp b/toolkit/mozapps/update/updater/automounter_gonk.cpp
new file mode 100644
index 000000000..3dff2a133
--- /dev/null
+++ b/toolkit/mozapps/update/updater/automounter_gonk.cpp
@@ -0,0 +1,251 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include <android/log.h>
+#include <cutils/android_reboot.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <sys/mount.h>
+#include <sys/reboot.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+
+#include "automounter_gonk.h"
+#include "updatedefines.h"
+#include "updatelogging.h"
+
+#define LOG_TAG "GonkAutoMounter"
+
+#define GONK_LOG(level, format, ...) \
+ LOG((LOG_TAG ": " format "\n", ##__VA_ARGS__)); \
+ __android_log_print(level, LOG_TAG, format, ##__VA_ARGS__)
+
+#define LOGI(format, ...) GONK_LOG(ANDROID_LOG_INFO, format, ##__VA_ARGS__)
+#define LOGE(format, ...) GONK_LOG(ANDROID_LOG_ERROR, format, ##__VA_ARGS__)
+
+const char *kGonkMountsPath = "/proc/mounts";
+const char *kGonkSystemPath = "/system";
+
+GonkAutoMounter::GonkAutoMounter() : mDevice(nullptr), mAccess(Unknown)
+{
+ if (!RemountSystem(ReadWrite)) {
+ LOGE("Could not remount %s as read-write.", kGonkSystemPath);
+ }
+}
+
+GonkAutoMounter::~GonkAutoMounter()
+{
+ bool result = RemountSystem(ReadOnly);
+ free(mDevice);
+
+ if (!result) {
+ // Don't take any chances when remounting as read-only fails, just reboot.
+ Reboot();
+ }
+}
+
+void
+GonkAutoMounter::Reboot()
+{
+ // The android_reboot wrapper provides more safety, doing fancier read-only
+ // remounting and attempting to sync() the filesystem first. If this fails
+ // our only hope is to force a reboot directly without these protections.
+ // For more, see system/core/libcutils/android_reboot.c
+ LOGE("Could not remount %s as read-only, forcing a system reboot.",
+ kGonkSystemPath);
+ LogFlush();
+
+ if (android_reboot(ANDROID_RB_RESTART, 0, nullptr) != 0) {
+ LOGE("Safe system reboot failed, attempting to force");
+ LogFlush();
+
+ if (reboot(RB_AUTOBOOT) != 0) {
+ LOGE("CRITICAL: Failed to force restart");
+ }
+ }
+}
+
+static const char *
+MountAccessToString(MountAccess access)
+{
+ switch (access) {
+ case ReadOnly: return "read-only";
+ case ReadWrite: return "read-write";
+ default: return "unknown";
+ }
+}
+
+bool
+GonkAutoMounter::RemountSystem(MountAccess access)
+{
+ if (!UpdateMountStatus()) {
+ return false;
+ }
+
+ if (mAccess == access) {
+ return true;
+ }
+
+ unsigned long flags = MS_REMOUNT;
+ if (access == ReadOnly) {
+ flags |= MS_RDONLY;
+ // Give the system a chance to flush file buffers
+ sync();
+ }
+
+ if (!MountSystem(flags)) {
+ return false;
+ }
+
+ // Check status again to verify /system has been properly remounted
+ if (!UpdateMountStatus()) {
+ return false;
+ }
+
+ if (mAccess != access) {
+ LOGE("Updated mount status %s should be %s",
+ MountAccessToString(mAccess),
+ MountAccessToString(access));
+ return false;
+ }
+
+ return true;
+}
+
+bool
+GonkAutoMounter::UpdateMountStatus()
+{
+ FILE *mountsFile = NS_tfopen(kGonkMountsPath, "r");
+
+ if (mountsFile == nullptr) {
+ LOGE("Error opening %s: %s", kGonkMountsPath, strerror(errno));
+ return false;
+ }
+
+ // /proc/mounts returns a 0 size from fstat, so we use the same
+ // pre-allocated buffer size that ADB does here
+ const int mountsMaxSize = 4096;
+ char mountData[mountsMaxSize];
+ size_t read = fread(mountData, 1, mountsMaxSize - 1, mountsFile);
+ mountData[read + 1] = '\0';
+
+ if (ferror(mountsFile)) {
+ LOGE("Error reading %s, %s", kGonkMountsPath, strerror(errno));
+ fclose(mountsFile);
+ return false;
+ }
+
+ char *token, *tokenContext;
+ bool foundSystem = false;
+
+ for (token = strtok_r(mountData, "\n", &tokenContext);
+ token;
+ token = strtok_r(nullptr, "\n", &tokenContext))
+ {
+ if (ProcessMount(token)) {
+ foundSystem = true;
+ break;
+ }
+ }
+
+ fclose(mountsFile);
+
+ if (!foundSystem) {
+ LOGE("Couldn't find %s mount in %s", kGonkSystemPath, kGonkMountsPath);
+ }
+ return foundSystem;
+}
+
+bool
+GonkAutoMounter::ProcessMount(const char *mount)
+{
+ const int strSize = 256;
+ char mountDev[strSize];
+ char mountDir[strSize];
+ char mountAccess[strSize];
+
+ int rv = sscanf(mount, "%255s %255s %*s %255s %*d %*d\n",
+ mountDev, mountDir, mountAccess);
+ mountDev[strSize - 1] = '\0';
+ mountDir[strSize - 1] = '\0';
+ mountAccess[strSize - 1] = '\0';
+
+ if (rv != 3) {
+ return false;
+ }
+
+ if (strcmp(kGonkSystemPath, mountDir) != 0) {
+ return false;
+ }
+
+ free(mDevice);
+ mDevice = strdup(mountDev);
+ mAccess = Unknown;
+
+ char *option, *optionContext;
+ for (option = strtok_r(mountAccess, ",", &optionContext);
+ option;
+ option = strtok_r(nullptr, ",", &optionContext))
+ {
+ if (strcmp("ro", option) == 0) {
+ mAccess = ReadOnly;
+ break;
+ } else if (strcmp("rw", option) == 0) {
+ mAccess = ReadWrite;
+ break;
+ }
+ }
+
+ return true;
+}
+
+/*
+ * Mark the given block device as read-write or read-only, using the BLKROSET
+ * ioctl.
+ */
+static void SetBlockReadWriteStatus(const char *blockdev, bool setReadOnly) {
+ int fd;
+ int roMode = setReadOnly ? 1 : 0;
+
+ fd = open(blockdev, O_RDONLY);
+ if (fd < 0) {
+ return;
+ }
+
+ if (ioctl(fd, BLKROSET, &roMode) == -1) {
+ LOGE("Error setting read-only mode on %s to %s: %s", blockdev,
+ setReadOnly ? "true": "false", strerror(errno));
+ }
+ close(fd);
+}
+
+
+bool
+GonkAutoMounter::MountSystem(unsigned long flags)
+{
+ if (!mDevice) {
+ LOGE("No device was found for %s", kGonkSystemPath);
+ return false;
+ }
+
+ // Without setting the block device ro mode to false, we get a permission
+ // denied error while trying to remount it in read-write.
+ SetBlockReadWriteStatus(mDevice, (flags & MS_RDONLY));
+
+ const char *readOnly = flags & MS_RDONLY ? "read-only" : "read-write";
+ int result = mount(mDevice, kGonkSystemPath, "none", flags, nullptr);
+
+ if (result != 0) {
+ LOGE("Error mounting %s as %s: %s", kGonkSystemPath, readOnly,
+ strerror(errno));
+ return false;
+ }
+
+ LOGI("Mounted %s partition as %s", kGonkSystemPath, readOnly);
+ return true;
+}
diff --git a/toolkit/mozapps/update/updater/automounter_gonk.h b/toolkit/mozapps/update/updater/automounter_gonk.h
new file mode 100644
index 000000000..e40cacbc2
--- /dev/null
+++ b/toolkit/mozapps/update/updater/automounter_gonk.h
@@ -0,0 +1,48 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef AUTOMOUNTER_GONK_H__
+#define AUTOMOUNTER_GONK_H__
+
+typedef enum {
+ ReadOnly,
+ ReadWrite,
+ Unknown
+} MountAccess;
+
+/**
+ * This class will remount the /system partition as read-write in Gonk to allow
+ * the updater write access. Upon destruction, /system will be remounted back to
+ * read-only. If something causes /system to remain read-write, this class will
+ * reboot the device and allow the system to mount as read-only.
+ *
+ * Code inspired from AOSP system/core/adb/remount_service.c
+ */
+class GonkAutoMounter
+{
+public:
+ GonkAutoMounter();
+ ~GonkAutoMounter();
+
+ MountAccess GetAccess() const
+ {
+ return mAccess;
+ }
+
+private:
+ bool RemountSystem(MountAccess access);
+ bool ForceRemountReadOnly();
+ bool UpdateMountStatus();
+ bool ProcessMount(const char *mount);
+ bool MountSystem(unsigned long flags);
+ void Reboot();
+
+private:
+ char *mDevice;
+ MountAccess mAccess;
+};
+
+#endif // AUTOMOUNTER_GONK_H__
diff --git a/toolkit/mozapps/update/updater/bspatch.cpp b/toolkit/mozapps/update/updater/bspatch.cpp
new file mode 100644
index 000000000..e632fe3d3
--- /dev/null
+++ b/toolkit/mozapps/update/updater/bspatch.cpp
@@ -0,0 +1,187 @@
+/*-
+ * Copyright 2003,2004 Colin Percival
+ * All rights reserved
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted providing that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
+ * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ *
+ * Changelog:
+ * 2005-04-26 - Define the header as a C structure, add a CRC32 checksum to
+ * the header, and make all the types 32-bit.
+ * --Benjamin Smedberg <benjamin@smedbergs.us>
+ */
+
+#include "bspatch.h"
+#include "errors.h"
+
+#include <sys/stat.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <fcntl.h>
+#include <string.h>
+#include <limits.h>
+
+#if defined(XP_WIN)
+# include <io.h>
+#else
+# include <unistd.h>
+#endif
+
+#ifdef XP_WIN
+# include <winsock2.h>
+#else
+# include <arpa/inet.h>
+#endif
+
+#ifndef SSIZE_MAX
+# define SSIZE_MAX LONG_MAX
+#endif
+
+int
+MBS_ReadHeader(FILE* file, MBSPatchHeader *header)
+{
+ size_t s = fread(header, 1, sizeof(MBSPatchHeader), file);
+ if (s != sizeof(MBSPatchHeader))
+ return READ_ERROR;
+
+ header->slen = ntohl(header->slen);
+ header->scrc32 = ntohl(header->scrc32);
+ header->dlen = ntohl(header->dlen);
+ header->cblen = ntohl(header->cblen);
+ header->difflen = ntohl(header->difflen);
+ header->extralen = ntohl(header->extralen);
+
+ struct stat hs;
+ s = fstat(fileno(file), &hs);
+ if (s)
+ return READ_ERROR;
+
+ if (memcmp(header->tag, "MBDIFF10", 8) != 0)
+ return UNEXPECTED_BSPATCH_ERROR;
+
+ if (sizeof(MBSPatchHeader) +
+ header->cblen +
+ header->difflen +
+ header->extralen != uint32_t(hs.st_size))
+ return UNEXPECTED_BSPATCH_ERROR;
+
+ return OK;
+}
+
+int
+MBS_ApplyPatch(const MBSPatchHeader *header, FILE* patchFile,
+ unsigned char *fbuffer, FILE* file)
+{
+ unsigned char *fbufend = fbuffer + header->slen;
+
+ unsigned char *buf = (unsigned char*) malloc(header->cblen +
+ header->difflen +
+ header->extralen);
+ if (!buf)
+ return BSPATCH_MEM_ERROR;
+
+ int rv = OK;
+
+ size_t r = header->cblen + header->difflen + header->extralen;
+ unsigned char *wb = buf;
+ while (r) {
+ const size_t count = (r > SSIZE_MAX) ? SSIZE_MAX : r;
+ size_t c = fread(wb, 1, count, patchFile);
+ if (c != count) {
+ rv = READ_ERROR;
+ goto end;
+ }
+
+ r -= c;
+ wb += c;
+ }
+
+ {
+ MBSPatchTriple *ctrlsrc = (MBSPatchTriple*) buf;
+ unsigned char *diffsrc = buf + header->cblen;
+ unsigned char *extrasrc = diffsrc + header->difflen;
+
+ MBSPatchTriple *ctrlend = (MBSPatchTriple*) diffsrc;
+ unsigned char *diffend = extrasrc;
+ unsigned char *extraend = extrasrc + header->extralen;
+
+ do {
+ ctrlsrc->x = ntohl(ctrlsrc->x);
+ ctrlsrc->y = ntohl(ctrlsrc->y);
+ ctrlsrc->z = ntohl(ctrlsrc->z);
+
+#ifdef DEBUG_bsmedberg
+ printf("Applying block:\n"
+ " x: %u\n"
+ " y: %u\n"
+ " z: %i\n",
+ ctrlsrc->x,
+ ctrlsrc->y,
+ ctrlsrc->z);
+#endif
+
+ /* Add x bytes from oldfile to x bytes from the diff block */
+
+ if (fbuffer + ctrlsrc->x > fbufend ||
+ diffsrc + ctrlsrc->x > diffend) {
+ rv = UNEXPECTED_BSPATCH_ERROR;
+ goto end;
+ }
+ for (uint32_t i = 0; i < ctrlsrc->x; ++i) {
+ diffsrc[i] += fbuffer[i];
+ }
+ if ((uint32_t) fwrite(diffsrc, 1, ctrlsrc->x, file) != ctrlsrc->x) {
+ rv = WRITE_ERROR_PATCH_FILE;
+ goto end;
+ }
+ fbuffer += ctrlsrc->x;
+ diffsrc += ctrlsrc->x;
+
+ /* Copy y bytes from the extra block */
+
+ if (extrasrc + ctrlsrc->y > extraend) {
+ rv = UNEXPECTED_BSPATCH_ERROR;
+ goto end;
+ }
+ if ((uint32_t) fwrite(extrasrc, 1, ctrlsrc->y, file) != ctrlsrc->y) {
+ rv = WRITE_ERROR_PATCH_FILE;
+ goto end;
+ }
+ extrasrc += ctrlsrc->y;
+
+ /* "seek" forwards in oldfile by z bytes */
+
+ if (fbuffer + ctrlsrc->z > fbufend) {
+ rv = UNEXPECTED_BSPATCH_ERROR;
+ goto end;
+ }
+ fbuffer += ctrlsrc->z;
+
+ /* and on to the next control block */
+
+ ++ctrlsrc;
+ } while (ctrlsrc < ctrlend);
+ }
+
+end:
+ free(buf);
+ return rv;
+}
diff --git a/toolkit/mozapps/update/updater/bspatch.h b/toolkit/mozapps/update/updater/bspatch.h
new file mode 100644
index 000000000..c24c001cc
--- /dev/null
+++ b/toolkit/mozapps/update/updater/bspatch.h
@@ -0,0 +1,93 @@
+/*-
+ * Copyright 2003,2004 Colin Percival
+ * All rights reserved
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted providing that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
+ * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ *
+ * Changelog:
+ * 2005-04-26 - Define the header as a C structure, add a CRC32 checksum to
+ * the header, and make all the types 32-bit.
+ * --Benjamin Smedberg <benjamin@smedbergs.us>
+ */
+
+#ifndef bspatch_h__
+#define bspatch_h__
+
+#include <stdint.h>
+#include <stdio.h>
+
+typedef struct MBSPatchHeader_ {
+ /* "MBDIFF10" */
+ char tag[8];
+
+ /* Length of the file to be patched */
+ uint32_t slen;
+
+ /* CRC32 of the file to be patched */
+ uint32_t scrc32;
+
+ /* Length of the result file */
+ uint32_t dlen;
+
+ /* Length of the control block in bytes */
+ uint32_t cblen;
+
+ /* Length of the diff block in bytes */
+ uint32_t difflen;
+
+ /* Length of the extra block in bytes */
+ uint32_t extralen;
+
+ /* Control block (MBSPatchTriple[]) */
+ /* Diff block (binary data) */
+ /* Extra block (binary data) */
+} MBSPatchHeader;
+
+/**
+ * Read the header of a patch file into the MBSPatchHeader structure.
+ *
+ * @param fd Must have been opened for reading, and be at the beginning
+ * of the file.
+ */
+int MBS_ReadHeader(FILE* file, MBSPatchHeader *header);
+
+/**
+ * Apply a patch. This method does not validate the checksum of the original
+ * file: client code should validate the checksum before calling this method.
+ *
+ * @param patchfd Must have been processed by MBS_ReadHeader
+ * @param fbuffer The original file read into a memory buffer of length
+ * header->slen.
+ * @param filefd Must have been opened for writing. Should be truncated
+ * to header->dlen if it is an existing file. The offset
+ * should be at the beginning of the file.
+ */
+int MBS_ApplyPatch(const MBSPatchHeader *header, FILE* patchFile,
+ unsigned char *fbuffer, FILE* file);
+
+typedef struct MBSPatchTriple_ {
+ uint32_t x; /* add x bytes from oldfile to x bytes from the diff block */
+ uint32_t y; /* copy y bytes from the extra block */
+ int32_t z; /* seek forwards in oldfile by z bytes */
+} MBSPatchTriple;
+
+#endif // bspatch_h__
diff --git a/toolkit/mozapps/update/updater/dep1.der b/toolkit/mozapps/update/updater/dep1.der
new file mode 100644
index 000000000..95b4ef38c
--- /dev/null
+++ b/toolkit/mozapps/update/updater/dep1.der
Binary files differ
diff --git a/toolkit/mozapps/update/updater/dep2.der b/toolkit/mozapps/update/updater/dep2.der
new file mode 100644
index 000000000..a460d6a16
--- /dev/null
+++ b/toolkit/mozapps/update/updater/dep2.der
Binary files differ
diff --git a/toolkit/mozapps/update/updater/gen_cert_header.py b/toolkit/mozapps/update/updater/gen_cert_header.py
new file mode 100644
index 000000000..7ecb15619
--- /dev/null
+++ b/toolkit/mozapps/update/updater/gen_cert_header.py
@@ -0,0 +1,22 @@
+from __future__ import print_function
+
+import binascii
+
+def file_byte_generator(filename, block_size = 512):
+ with open(filename, "rb") as f:
+ while True:
+ block = f.read(block_size)
+ if block:
+ for byte in block:
+ yield byte
+ else:
+ break
+
+def create_header(out_fh, in_filename):
+ assert out_fh.name.endswith('.h')
+ array_name = out_fh.name[:-2] + 'Data'
+ hexified = ["0x" + binascii.hexlify(byte) for byte in file_byte_generator(in_filename)]
+ print("const uint8_t " + array_name + "[] = {", file=out_fh)
+ print(", ".join(hexified), file=out_fh)
+ print("};", file=out_fh)
+ return 0
diff --git a/toolkit/mozapps/update/updater/launchchild_osx.mm b/toolkit/mozapps/update/updater/launchchild_osx.mm
new file mode 100644
index 000000000..5a36ae623
--- /dev/null
+++ b/toolkit/mozapps/update/updater/launchchild_osx.mm
@@ -0,0 +1,384 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include <Cocoa/Cocoa.h>
+#include <CoreServices/CoreServices.h>
+#include <crt_externs.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <spawn.h>
+#include <SystemConfiguration/SystemConfiguration.h>
+#include "readstrings.h"
+
+class MacAutoreleasePool {
+public:
+ MacAutoreleasePool()
+ {
+ mPool = [[NSAutoreleasePool alloc] init];
+ }
+ ~MacAutoreleasePool()
+ {
+ [mPool release];
+ }
+
+private:
+ NSAutoreleasePool* mPool;
+};
+
+void LaunchChild(int argc, const char** argv)
+{
+ MacAutoreleasePool pool;
+
+ @try {
+ NSString* launchPath = [NSString stringWithUTF8String:argv[0]];
+ NSMutableArray* arguments = [NSMutableArray arrayWithCapacity:argc - 1];
+ for (int i = 1; i < argc; i++) {
+ [arguments addObject:[NSString stringWithUTF8String:argv[i]]];
+ }
+ [NSTask launchedTaskWithLaunchPath:launchPath
+ arguments:arguments];
+ } @catch (NSException* e) {
+ NSLog(@"%@: %@", e.name, e.reason);
+ }
+}
+
+void
+LaunchMacPostProcess(const char* aAppBundle)
+{
+ MacAutoreleasePool pool;
+
+ // Launch helper to perform post processing for the update; this is the Mac
+ // analogue of LaunchWinPostProcess (PostUpdateWin).
+ NSString* iniPath = [NSString stringWithUTF8String:aAppBundle];
+ iniPath =
+ [iniPath stringByAppendingPathComponent:@"Contents/Resources/updater.ini"];
+
+ NSFileManager* fileManager = [NSFileManager defaultManager];
+ if (![fileManager fileExistsAtPath:iniPath]) {
+ // the file does not exist; there is nothing to run
+ return;
+ }
+
+ int readResult;
+ char values[2][MAX_TEXT_LEN];
+ readResult = ReadStrings([iniPath UTF8String],
+ "ExeRelPath\0ExeArg\0",
+ 2,
+ values,
+ "PostUpdateMac");
+ if (readResult) {
+ return;
+ }
+
+ NSString *exeRelPath = [NSString stringWithUTF8String:values[0]];
+ NSString *exeArg = [NSString stringWithUTF8String:values[1]];
+ if (!exeArg || !exeRelPath) {
+ return;
+ }
+
+ // The path must not traverse directories and it must be a relative path.
+ if ([exeRelPath rangeOfString:@".."].location != NSNotFound ||
+ [exeRelPath rangeOfString:@"./"].location != NSNotFound ||
+ [exeRelPath rangeOfString:@"/"].location == 0) {
+ return;
+ }
+
+ NSString* exeFullPath = [NSString stringWithUTF8String:aAppBundle];
+ exeFullPath = [exeFullPath stringByAppendingPathComponent:exeRelPath];
+
+ char optVals[1][MAX_TEXT_LEN];
+ readResult = ReadStrings([iniPath UTF8String],
+ "ExeAsync\0",
+ 1,
+ optVals,
+ "PostUpdateMac");
+
+ NSTask *task = [[NSTask alloc] init];
+ [task setLaunchPath:exeFullPath];
+ [task setArguments:[NSArray arrayWithObject:exeArg]];
+ [task launch];
+ if (!readResult) {
+ NSString *exeAsync = [NSString stringWithUTF8String:optVals[0]];
+ if ([exeAsync isEqualToString:@"false"]) {
+ [task waitUntilExit];
+ }
+ }
+ // ignore the return value of the task, there's nothing we can do with it
+ [task release];
+}
+
+id ConnectToUpdateServer()
+{
+ MacAutoreleasePool pool;
+
+ id updateServer = nil;
+ BOOL isConnected = NO;
+ int currTry = 0;
+ const int numRetries = 10; // Number of IPC connection retries before
+ // giving up.
+ while (!isConnected && currTry < numRetries) {
+ @try {
+ updateServer = (id)[NSConnection
+ rootProxyForConnectionWithRegisteredName:
+ @"org.mozilla.updater.server"
+ host:nil
+ usingNameServer:[NSSocketPortNameServer sharedInstance]];
+ if (!updateServer ||
+ ![updateServer respondsToSelector:@selector(abort)] ||
+ ![updateServer respondsToSelector:@selector(getArguments)] ||
+ ![updateServer respondsToSelector:@selector(shutdown)]) {
+ NSLog(@"Server doesn't exist or doesn't provide correct selectors.");
+ sleep(1); // Wait 1 second.
+ currTry++;
+ } else {
+ isConnected = YES;
+ }
+ } @catch (NSException* e) {
+ NSLog(@"Encountered exception, retrying: %@: %@", e.name, e.reason);
+ sleep(1); // Wait 1 second.
+ currTry++;
+ }
+ }
+ if (!isConnected) {
+ NSLog(@"Failed to connect to update server after several retries.");
+ return nil;
+ }
+ return updateServer;
+}
+
+void CleanupElevatedMacUpdate(bool aFailureOccurred)
+{
+ MacAutoreleasePool pool;
+
+ id updateServer = ConnectToUpdateServer();
+ if (updateServer) {
+ @try {
+ if (aFailureOccurred) {
+ [updateServer performSelector:@selector(abort)];
+ } else {
+ [updateServer performSelector:@selector(shutdown)];
+ }
+ } @catch (NSException* e) { }
+ }
+
+ NSFileManager* manager = [NSFileManager defaultManager];
+ [manager removeItemAtPath:@"/Library/PrivilegedHelperTools/org.mozilla.updater"
+ error:nil];
+ [manager removeItemAtPath:@"/Library/LaunchDaemons/org.mozilla.updater.plist"
+ error:nil];
+ const char* launchctlArgs[] = {"/bin/launchctl",
+ "remove",
+ "org.mozilla.updater"};
+ // The following call will terminate the current process due to the "remove"
+ // argument in launchctlArgs.
+ LaunchChild(3, launchctlArgs);
+}
+
+// Note: Caller is responsible for freeing argv.
+bool ObtainUpdaterArguments(int* argc, char*** argv)
+{
+ MacAutoreleasePool pool;
+
+ id updateServer = ConnectToUpdateServer();
+ if (!updateServer) {
+ // Let's try our best and clean up.
+ CleanupElevatedMacUpdate(true);
+ return false; // Won't actually get here due to CleanupElevatedMacUpdate.
+ }
+
+ @try {
+ NSArray* updaterArguments =
+ [updateServer performSelector:@selector(getArguments)];
+ *argc = [updaterArguments count];
+ char** tempArgv = (char**)malloc(sizeof(char*) * (*argc));
+ for (int i = 0; i < *argc; i++) {
+ int argLen = [[updaterArguments objectAtIndex:i] length] + 1;
+ tempArgv[i] = (char*)malloc(argLen);
+ strncpy(tempArgv[i], [[updaterArguments objectAtIndex:i] UTF8String],
+ argLen);
+ }
+ *argv = tempArgv;
+ } @catch (NSException* e) {
+ // Let's try our best and clean up.
+ CleanupElevatedMacUpdate(true);
+ return false; // Won't actually get here due to CleanupElevatedMacUpdate.
+ }
+ return true;
+}
+
+/**
+ * The ElevatedUpdateServer is launched from a non-elevated updater process.
+ * It allows an elevated updater process (usually a privileged helper tool) to
+ * connect to it and receive all the necessary arguments to complete a
+ * successful update.
+ */
+@interface ElevatedUpdateServer : NSObject
+{
+ NSArray* mUpdaterArguments;
+ BOOL mShouldKeepRunning;
+ BOOL mAborted;
+}
+- (id)initWithArgs:(NSArray*)args;
+- (BOOL)runServer;
+- (NSArray*)getArguments;
+- (void)abort;
+- (BOOL)wasAborted;
+- (void)shutdown;
+- (BOOL)shouldKeepRunning;
+@end
+
+@implementation ElevatedUpdateServer
+
+- (id)initWithArgs:(NSArray*)args
+{
+ self = [super init];
+ if (!self) {
+ return nil;
+ }
+ mUpdaterArguments = args;
+ mShouldKeepRunning = YES;
+ mAborted = NO;
+ return self;
+}
+
+- (BOOL)runServer
+{
+ NSPort* serverPort = [NSSocketPort port];
+ NSConnection* server = [NSConnection connectionWithReceivePort:serverPort
+ sendPort:serverPort];
+ [server setRootObject:self];
+ if ([server registerName:@"org.mozilla.updater.server"
+ withNameServer:[NSSocketPortNameServer sharedInstance]] == NO) {
+ NSLog(@"Unable to register as DirectoryServer.");
+ NSLog(@"Is another copy running?");
+ return NO;
+ }
+
+ while ([self shouldKeepRunning] &&
+ [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
+ beforeDate:[NSDate distantFuture]]);
+ return ![self wasAborted];
+}
+
+- (NSArray*)getArguments
+{
+ return mUpdaterArguments;
+}
+
+- (void)abort
+{
+ mAborted = YES;
+ [self shutdown];
+}
+
+- (BOOL)wasAborted
+{
+ return mAborted;
+}
+
+- (void)shutdown
+{
+ mShouldKeepRunning = NO;
+}
+
+- (BOOL)shouldKeepRunning
+{
+ return mShouldKeepRunning;
+}
+
+@end
+
+bool ServeElevatedUpdate(int argc, const char** argv)
+{
+ MacAutoreleasePool pool;
+
+ NSMutableArray* updaterArguments = [NSMutableArray arrayWithCapacity:argc];
+ for (int i = 0; i < argc; i++) {
+ [updaterArguments addObject:[NSString stringWithUTF8String:argv[i]]];
+ }
+
+ ElevatedUpdateServer* updater =
+ [[ElevatedUpdateServer alloc] initWithArgs:[updaterArguments copy]];
+ bool didSucceed = [updater runServer];
+
+ [updater release];
+ return didSucceed;
+}
+
+bool IsOwnedByGroupAdmin(const char* aAppBundle)
+{
+ MacAutoreleasePool pool;
+
+ NSString* appDir = [NSString stringWithUTF8String:aAppBundle];
+ NSFileManager* fileManager = [NSFileManager defaultManager];
+
+ NSDictionary* attributes = [fileManager attributesOfItemAtPath:appDir
+ error:nil];
+ bool isOwnedByAdmin = false;
+ if (attributes &&
+ [[attributes valueForKey:NSFileGroupOwnerAccountID] intValue] == 80) {
+ isOwnedByAdmin = true;
+ }
+ return isOwnedByAdmin;
+}
+
+void SetGroupOwnershipAndPermissions(const char* aAppBundle)
+{
+ MacAutoreleasePool pool;
+
+ NSString* appDir = [NSString stringWithUTF8String:aAppBundle];
+ NSFileManager* fileManager = [NSFileManager defaultManager];
+ NSError* error = nil;
+ NSArray* paths =
+ [fileManager subpathsOfDirectoryAtPath:appDir
+ error:&error];
+ if (error) {
+ return;
+ }
+
+ // Set group ownership of Firefox.app to 80 ("admin") and permissions to
+ // 0775.
+ if (![fileManager setAttributes:@{ NSFileGroupOwnerAccountID: @(80),
+ NSFilePosixPermissions: @(0775) }
+ ofItemAtPath:appDir
+ error:&error] || error) {
+ return;
+ }
+
+ NSArray* permKeys = [NSArray arrayWithObjects:NSFileGroupOwnerAccountID,
+ NSFilePosixPermissions,
+ nil];
+ // For all descendants of Firefox.app, set group ownership to 80 ("admin") and
+ // ensure write permission for the group.
+ for (NSString* currPath in paths) {
+ NSString* child = [appDir stringByAppendingPathComponent:currPath];
+ NSDictionary* oldAttributes =
+ [fileManager attributesOfItemAtPath:child
+ error:&error];
+ if (error) {
+ return;
+ }
+ // Skip symlinks, since they could be pointing to files outside of the .app
+ // bundle.
+ if ([oldAttributes fileType] == NSFileTypeSymbolicLink) {
+ continue;
+ }
+ NSNumber* oldPerms =
+ (NSNumber*)[oldAttributes valueForKey:NSFilePosixPermissions];
+ NSArray* permObjects =
+ [NSArray arrayWithObjects:
+ [NSNumber numberWithUnsignedLong:80],
+ [NSNumber numberWithUnsignedLong:[oldPerms shortValue] | 020],
+ nil];
+ NSDictionary* attributes = [NSDictionary dictionaryWithObjects:permObjects
+ forKeys:permKeys];
+ if (![fileManager setAttributes:attributes
+ ofItemAtPath:child
+ error:&error] || error) {
+ return;
+ }
+ }
+}
diff --git a/toolkit/mozapps/update/updater/loaddlls.cpp b/toolkit/mozapps/update/updater/loaddlls.cpp
new file mode 100644
index 000000000..b4291a5df
--- /dev/null
+++ b/toolkit/mozapps/update/updater/loaddlls.cpp
@@ -0,0 +1,103 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include <windows.h>
+
+// Delayed load libraries are loaded when the first symbol is used.
+// The following ensures that we load the delayed loaded libraries from the
+// system directory.
+struct AutoLoadSystemDependencies
+{
+ AutoLoadSystemDependencies()
+ {
+ // Remove the current directory from the search path for dynamically loaded
+ // DLLs as a precaution. This call has no effect for delay load DLLs.
+ SetDllDirectory(L"");
+
+ HMODULE module = ::GetModuleHandleW(L"kernel32.dll");
+ if (module) {
+ // SetDefaultDllDirectories is always available on Windows 8 and above. It
+ // is also available on Windows Vista, Windows Server 2008, and
+ // Windows 7 when MS KB2533623 has been applied.
+ decltype(SetDefaultDllDirectories)* setDefaultDllDirectories =
+ (decltype(SetDefaultDllDirectories)*) GetProcAddress(module, "SetDefaultDllDirectories");
+ if (setDefaultDllDirectories) {
+ setDefaultDllDirectories(LOAD_LIBRARY_SEARCH_SYSTEM32);
+ return;
+ }
+ }
+
+ // When SetDefaultDllDirectories is not available, fallback to preloading
+ // dlls. The order that these are loaded does not matter since they are
+ // loaded using the LOAD_WITH_ALTERED_SEARCH_PATH flag.
+#ifdef HAVE_64BIT_BUILD
+ // DLLs for Firefox x64 on Windows 7 (x64).
+ // Note: dwmapi.dll is preloaded since a crash will try to load it from the
+ // application's directory.
+ static LPCWSTR delayDLLs[] = { L"apphelp.dll",
+ L"cryptbase.dll",
+ L"cryptsp.dll",
+ L"dwmapi.dll",
+ L"mpr.dll",
+ L"ntmarta.dll",
+ L"profapi.dll",
+ L"propsys.dll",
+ L"sspicli.dll",
+ L"wsock32.dll" };
+
+#else
+ // DLLs for Firefox x86 on Windows XP through Windows 7 (x86 and x64).
+ // Note: dwmapi.dll is preloaded since a crash will try to load it from the
+ // application's directory.
+ static LPCWSTR delayDLLs[] = { L"apphelp.dll",
+ L"crypt32.dll",
+ L"cryptbase.dll",
+ L"cryptsp.dll",
+ L"dwmapi.dll",
+ L"mpr.dll",
+ L"msasn1.dll",
+ L"ntmarta.dll",
+ L"profapi.dll",
+ L"propsys.dll",
+ L"psapi.dll",
+ L"secur32.dll",
+ L"sspicli.dll",
+ L"userenv.dll",
+ L"uxtheme.dll",
+ L"ws2_32.dll",
+ L"ws2help.dll",
+ L"wsock32.dll" };
+#endif
+
+ WCHAR systemDirectory[MAX_PATH + 1] = { L'\0' };
+ // If GetSystemDirectory fails we accept that we'll load the DLLs from the
+ // normal search path.
+ GetSystemDirectoryW(systemDirectory, MAX_PATH + 1);
+ size_t systemDirLen = wcslen(systemDirectory);
+
+ // Make the system directory path terminate with a slash
+ if (systemDirectory[systemDirLen - 1] != L'\\' && systemDirLen) {
+ systemDirectory[systemDirLen] = L'\\';
+ ++systemDirLen;
+ // No need to re-null terminate
+ }
+
+ // For each known DLL ensure it is loaded from the system32 directory
+ for (size_t i = 0; i < sizeof(delayDLLs) / sizeof(delayDLLs[0]); ++i) {
+ size_t fileLen = wcslen(delayDLLs[i]);
+ wcsncpy(systemDirectory + systemDirLen, delayDLLs[i],
+ MAX_PATH - systemDirLen);
+ if (systemDirLen + fileLen <= MAX_PATH) {
+ systemDirectory[systemDirLen + fileLen] = L'\0';
+ } else {
+ systemDirectory[MAX_PATH] = L'\0';
+ }
+ LPCWSTR fullModulePath = systemDirectory; // just for code readability
+ // LOAD_WITH_ALTERED_SEARCH_PATH makes a dll look in its own directory for
+ // dependencies and is only available on Win 7 and below.
+ LoadLibraryExW(fullModulePath, nullptr, LOAD_WITH_ALTERED_SEARCH_PATH);
+ }
+ }
+} loadDLLs;
diff --git a/toolkit/mozapps/update/updater/macbuild/Contents/Info.plist.in b/toolkit/mozapps/update/updater/macbuild/Contents/Info.plist.in
new file mode 100644
index 000000000..a9b9fcba9
--- /dev/null
+++ b/toolkit/mozapps/update/updater/macbuild/Contents/Info.plist.in
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+ <key>CFBundleDevelopmentRegion</key>
+ <string>English</string>
+ <key>CFBundleExecutable</key>
+ <string>org.mozilla.updater</string>
+ <key>CFBundleIconFile</key>
+ <string>updater.icns</string>
+ <key>CFBundleIdentifier</key>
+ <string>org.mozilla.updater</string>
+ <key>CFBundleInfoDictionaryVersion</key>
+ <string>6.0</string>
+ <key>CFBundlePackageType</key>
+ <string>APPL</string>
+ <key>CFBundleSignature</key>
+ <string>????</string>
+ <key>CFBundleVersion</key>
+ <string>1.0</string>
+ <key>NSMainNibFile</key>
+ <string>MainMenu</string>
+ <key>NSPrincipalClass</key>
+ <string>NSApplication</string>
+ <key>LSMinimumSystemVersion</key>
+ <string>10.5</string>
+ <key>LSMinimumSystemVersionByArchitecture</key>
+ <dict>
+ <key>i386</key>
+ <string>10.5.0</string>
+ <key>x86_64</key>
+ <string>10.6.0</string>
+ </dict>
+ <key>SMAuthorizedClients</key>
+ <array>
+ <string>identifier "%MOZ_MACBUNDLE_ID%" and ((anchor apple generic and certificate leaf[field.1.2.840.113635.100.6.1.9]) or (anchor apple generic and certificate 1[field.1.2.840.113635.100.6.2.6] and certificate leaf[field.1.2.840.113635.100.6.1.13] and certificate leaf[subject.OU] = "43AQ936H96"))</string>
+ </array>
+</dict>
+</plist>
diff --git a/toolkit/mozapps/update/updater/macbuild/Contents/PkgInfo b/toolkit/mozapps/update/updater/macbuild/Contents/PkgInfo
new file mode 100644
index 000000000..bd04210fb
--- /dev/null
+++ b/toolkit/mozapps/update/updater/macbuild/Contents/PkgInfo
@@ -0,0 +1 @@
+APPL???? \ No newline at end of file
diff --git a/toolkit/mozapps/update/updater/macbuild/Contents/Resources/English.lproj/InfoPlist.strings.in b/toolkit/mozapps/update/updater/macbuild/Contents/Resources/English.lproj/InfoPlist.strings.in
new file mode 100644
index 000000000..bca4022e7
--- /dev/null
+++ b/toolkit/mozapps/update/updater/macbuild/Contents/Resources/English.lproj/InfoPlist.strings.in
@@ -0,0 +1,7 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* Localized versions of Info.plist keys */
+
+CFBundleName = "%APP_NAME% Software Update";
diff --git a/toolkit/mozapps/update/updater/macbuild/Contents/Resources/English.lproj/MainMenu.nib/classes.nib b/toolkit/mozapps/update/updater/macbuild/Contents/Resources/English.lproj/MainMenu.nib/classes.nib
new file mode 100644
index 000000000..6cfb50406
--- /dev/null
+++ b/toolkit/mozapps/update/updater/macbuild/Contents/Resources/English.lproj/MainMenu.nib/classes.nib
@@ -0,0 +1,19 @@
+{
+ IBClasses = (
+ {
+ CLASS = FirstResponder;
+ LANGUAGE = ObjC;
+ SUPERCLASS = NSObject;
+},
+ {
+ CLASS = UpdaterUI;
+ LANGUAGE = ObjC;
+ OUTLETS = {
+ progressBar = NSProgressIndicator;
+ progressTextField = NSTextField;
+ };
+ SUPERCLASS = NSObject;
+}
+ );
+ IBVersion = 1;
+} \ No newline at end of file
diff --git a/toolkit/mozapps/update/updater/macbuild/Contents/Resources/English.lproj/MainMenu.nib/info.nib b/toolkit/mozapps/update/updater/macbuild/Contents/Resources/English.lproj/MainMenu.nib/info.nib
new file mode 100644
index 000000000..150917837
--- /dev/null
+++ b/toolkit/mozapps/update/updater/macbuild/Contents/Resources/English.lproj/MainMenu.nib/info.nib
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+ <key>IBDocumentLocation</key>
+ <string>111 162 356 240 0 0 1440 878 </string>
+ <key>IBEditorPositions</key>
+ <dict>
+ <key>29</key>
+ <string>106 299 84 44 0 0 1440 878 </string>
+ </dict>
+ <key>IBFramework Version</key>
+ <string>489.0</string>
+ <key>IBOpenObjects</key>
+ <array>
+ <integer>21</integer>
+ <integer>29</integer>
+ </array>
+ <key>IBSystem Version</key>
+ <string>10J567</string>
+</dict>
+</plist>
diff --git a/toolkit/mozapps/update/updater/macbuild/Contents/Resources/English.lproj/MainMenu.nib/keyedobjects.nib b/toolkit/mozapps/update/updater/macbuild/Contents/Resources/English.lproj/MainMenu.nib/keyedobjects.nib
new file mode 100644
index 000000000..61ff02600
--- /dev/null
+++ b/toolkit/mozapps/update/updater/macbuild/Contents/Resources/English.lproj/MainMenu.nib/keyedobjects.nib
Binary files differ
diff --git a/toolkit/mozapps/update/updater/macbuild/Contents/Resources/updater.icns b/toolkit/mozapps/update/updater/macbuild/Contents/Resources/updater.icns
new file mode 100644
index 000000000..d7499c669
--- /dev/null
+++ b/toolkit/mozapps/update/updater/macbuild/Contents/Resources/updater.icns
Binary files differ
diff --git a/toolkit/mozapps/update/updater/module.ver b/toolkit/mozapps/update/updater/module.ver
new file mode 100644
index 000000000..771416bb1
--- /dev/null
+++ b/toolkit/mozapps/update/updater/module.ver
@@ -0,0 +1 @@
+WIN32_MODULE_DESCRIPTION=@MOZ_APP_DISPLAYNAME@ Software Updater
diff --git a/toolkit/mozapps/update/updater/moz.build b/toolkit/mozapps/update/updater/moz.build
new file mode 100644
index 000000000..1cca83b5b
--- /dev/null
+++ b/toolkit/mozapps/update/updater/moz.build
@@ -0,0 +1,62 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'cocoa':
+ Program('org.mozilla.updater')
+else:
+ Program('updater')
+
+updater_rel_path = ''
+include('updater-common.build')
+if CONFIG['ENABLE_TESTS']:
+ DIRS += ['updater-xpcshell']
+
+CXXFLAGS += CONFIG['MOZ_BZ2_CFLAGS']
+
+if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'cocoa':
+ LDFLAGS += ['-sectcreate',
+ '__TEXT',
+ '__info_plist',
+ TOPOBJDIR + '/dist/bin/Info.plist',
+ '-sectcreate',
+ '__TEXT',
+ '__launchd_plist',
+ SRCDIR + '/Launchd.plist']
+
+GENERATED_FILES = [
+ 'primaryCert.h',
+ 'secondaryCert.h',
+ 'xpcshellCert.h',
+]
+
+primary_cert = GENERATED_FILES['primaryCert.h']
+secondary_cert = GENERATED_FILES['secondaryCert.h']
+
+# This is how the xpcshellCertificate.der file is generated, in case we ever
+# have to regenerate it.
+# ./certutil -L -d modules/libmar/tests/unit/data -n mycert -r > xpcshellCertificate.der
+xpcshell_cert = GENERATED_FILES['xpcshellCert.h']
+
+primary_cert.script = 'gen_cert_header.py:create_header'
+secondary_cert.script = 'gen_cert_header.py:create_header'
+xpcshell_cert.script = 'gen_cert_header.py:create_header'
+
+if CONFIG['MOZ_UPDATE_CHANNEL'] in ('beta', 'release', 'esr'):
+ primary_cert.inputs += ['release_primary.der']
+ secondary_cert.inputs += ['release_secondary.der']
+elif CONFIG['MOZ_UPDATE_CHANNEL'] in ('nightly', 'aurora', 'nightly-elm',
+ 'nightly-profiling', 'nightly-oak',
+ 'nightly-ux'):
+ primary_cert.inputs += ['nightly_aurora_level3_primary.der']
+ secondary_cert.inputs += ['nightly_aurora_level3_secondary.der']
+else:
+ primary_cert.inputs += ['dep1.der']
+ secondary_cert.inputs += ['dep2.der']
+
+xpcshell_cert.inputs += ['xpcshellCertificate.der']
+
+if 'gtk' in CONFIG['MOZ_WIDGET_TOOLKIT']:
+ FINAL_TARGET_FILES.icons += ['updater.png']
diff --git a/toolkit/mozapps/update/updater/nightly_aurora_level3_primary.der b/toolkit/mozapps/update/updater/nightly_aurora_level3_primary.der
new file mode 100644
index 000000000..b22124798
--- /dev/null
+++ b/toolkit/mozapps/update/updater/nightly_aurora_level3_primary.der
Binary files differ
diff --git a/toolkit/mozapps/update/updater/nightly_aurora_level3_secondary.der b/toolkit/mozapps/update/updater/nightly_aurora_level3_secondary.der
new file mode 100644
index 000000000..2dffbd02d
--- /dev/null
+++ b/toolkit/mozapps/update/updater/nightly_aurora_level3_secondary.der
Binary files differ
diff --git a/toolkit/mozapps/update/updater/progressui.h b/toolkit/mozapps/update/updater/progressui.h
new file mode 100644
index 000000000..5462815de
--- /dev/null
+++ b/toolkit/mozapps/update/updater/progressui.h
@@ -0,0 +1,40 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef PROGRESSUI_H__
+#define PROGRESSUI_H__
+
+#include "updatedefines.h"
+
+#if defined(XP_WIN)
+ typedef WCHAR NS_tchar;
+ #define NS_main wmain
+#else
+ typedef char NS_tchar;
+ #define NS_main main
+#endif
+
+// Called to perform any initialization of the widget toolkit
+int InitProgressUI(int *argc, NS_tchar ***argv);
+
+#if defined(XP_WIN)
+ // Called on the main thread at startup
+ int ShowProgressUI(bool indeterminate = false, bool initUIStrings = true);
+ int InitProgressUIStrings();
+#elif defined(XP_MACOSX)
+ // Called on the main thread at startup
+ int ShowProgressUI(bool indeterminate = false);
+#else
+ // Called on the main thread at startup
+ int ShowProgressUI();
+#endif
+// May be called from any thread
+void QuitProgressUI();
+
+// May be called from any thread: progress is a number between 0 and 100
+void UpdateProgressUI(float progress);
+
+#endif // PROGRESSUI_H__
diff --git a/toolkit/mozapps/update/updater/progressui_gonk.cpp b/toolkit/mozapps/update/updater/progressui_gonk.cpp
new file mode 100644
index 000000000..f77d0af63
--- /dev/null
+++ b/toolkit/mozapps/update/updater/progressui_gonk.cpp
@@ -0,0 +1,53 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: sw=2 ts=8 et :
+ */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include <assert.h>
+#include <stdio.h>
+
+#include <string>
+
+#include "android/log.h"
+
+#include "progressui.h"
+
+#define LOG(args...) __android_log_print(ANDROID_LOG_INFO, "GeckoUpdater" , ## args)
+
+using namespace std;
+
+int InitProgressUI(int *argc, char ***argv)
+{
+ return 0;
+}
+
+int ShowProgressUI()
+{
+ LOG("Starting to apply update ...\n");
+ return 0;
+}
+
+void QuitProgressUI()
+{
+ LOG("Finished applying update\n");
+}
+
+void UpdateProgressUI(float progress)
+{
+ assert(0.0f <= progress && progress <= 100.0f);
+
+ static const size_t kProgressBarLength = 50;
+ static size_t sLastNumBars;
+ size_t numBars = size_t(float(kProgressBarLength) * progress / 100.0f);
+ if (numBars == sLastNumBars) {
+ return;
+ }
+ sLastNumBars = numBars;
+
+ size_t numSpaces = kProgressBarLength - numBars;
+ string bars(numBars, '=');
+ string spaces(numSpaces, ' ');
+ LOG("Progress [ %s%s ]\n", bars.c_str(), spaces.c_str());
+}
diff --git a/toolkit/mozapps/update/updater/progressui_gtk.cpp b/toolkit/mozapps/update/updater/progressui_gtk.cpp
new file mode 100644
index 000000000..902bc5ac8
--- /dev/null
+++ b/toolkit/mozapps/update/updater/progressui_gtk.cpp
@@ -0,0 +1,132 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include <stdio.h>
+#include <gtk/gtk.h>
+#include <unistd.h>
+#include "mozilla/Sprintf.h"
+#include "progressui.h"
+#include "readstrings.h"
+#include "errors.h"
+
+#define TIMER_INTERVAL 100
+
+static float sProgressVal; // between 0 and 100
+static gboolean sQuit = FALSE;
+static gboolean sEnableUI;
+static guint sTimerID;
+
+static GtkWidget *sWin;
+static GtkWidget *sLabel;
+static GtkWidget *sProgressBar;
+
+static const char *sProgramPath;
+
+static gboolean
+UpdateDialog(gpointer data)
+{
+ if (sQuit)
+ {
+ gtk_widget_hide(sWin);
+ gtk_main_quit();
+ }
+
+ float progress = sProgressVal;
+
+ gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(sProgressBar),
+ progress / 100.0);
+
+ return TRUE;
+}
+
+static gboolean
+OnDeleteEvent(GtkWidget *widget, GdkEvent *event, gpointer user_data)
+{
+ return TRUE;
+}
+
+int
+InitProgressUI(int *pargc, char ***pargv)
+{
+ sProgramPath = (*pargv)[0];
+
+ sEnableUI = gtk_init_check(pargc, pargv);
+ return 0;
+}
+
+int
+ShowProgressUI()
+{
+ if (!sEnableUI)
+ return -1;
+
+ // Only show the Progress UI if the process is taking a significant amount of
+ // time where a significant amount of time is defined as .5 seconds after
+ // ShowProgressUI is called sProgress is less than 70.
+ usleep(500000);
+
+ if (sQuit || sProgressVal > 70.0f)
+ return 0;
+
+ char ini_path[PATH_MAX];
+ SprintfLiteral(ini_path, "%s.ini", sProgramPath);
+
+ StringTable strings;
+ if (ReadStrings(ini_path, &strings) != OK)
+ return -1;
+
+ sWin = gtk_window_new(GTK_WINDOW_TOPLEVEL);
+ if (!sWin)
+ return -1;
+
+ static GdkPixbuf *pixbuf;
+ char icon_path[PATH_MAX];
+ SprintfLiteral(icon_path, "%s.png", sProgramPath);
+
+ g_signal_connect(G_OBJECT(sWin), "delete_event",
+ G_CALLBACK(OnDeleteEvent), nullptr);
+
+ gtk_window_set_title(GTK_WINDOW(sWin), strings.title);
+ gtk_window_set_type_hint(GTK_WINDOW(sWin), GDK_WINDOW_TYPE_HINT_DIALOG);
+ gtk_window_set_position(GTK_WINDOW(sWin), GTK_WIN_POS_CENTER_ALWAYS);
+ gtk_window_set_resizable(GTK_WINDOW(sWin), FALSE);
+ gtk_window_set_decorated(GTK_WINDOW(sWin), TRUE);
+ gtk_window_set_deletable(GTK_WINDOW(sWin),FALSE);
+ pixbuf = gdk_pixbuf_new_from_file (icon_path, nullptr);
+ gtk_window_set_icon(GTK_WINDOW(sWin), pixbuf);
+ g_object_unref(pixbuf);
+
+ GtkWidget *vbox = gtk_vbox_new(TRUE, 6);
+ sLabel = gtk_label_new(strings.info);
+ gtk_misc_set_alignment(GTK_MISC(sLabel), 0.0f, 0.0f);
+ sProgressBar = gtk_progress_bar_new();
+
+ gtk_box_pack_start(GTK_BOX(vbox), sLabel, FALSE, FALSE, 0);
+ gtk_box_pack_start(GTK_BOX(vbox), sProgressBar, TRUE, TRUE, 0);
+
+ sTimerID = g_timeout_add(TIMER_INTERVAL, UpdateDialog, nullptr);
+
+ gtk_container_set_border_width(GTK_CONTAINER(sWin), 10);
+ gtk_container_add(GTK_CONTAINER(sWin), vbox);
+ gtk_widget_show_all(sWin);
+
+ gtk_main();
+ return 0;
+}
+
+// Called on a background thread
+void
+QuitProgressUI()
+{
+ sQuit = TRUE;
+}
+
+// Called on a background thread
+void
+UpdateProgressUI(float progress)
+{
+ sProgressVal = progress; // 32-bit writes are atomic
+}
diff --git a/toolkit/mozapps/update/updater/progressui_null.cpp b/toolkit/mozapps/update/updater/progressui_null.cpp
new file mode 100644
index 000000000..cb3ac6369
--- /dev/null
+++ b/toolkit/mozapps/update/updater/progressui_null.cpp
@@ -0,0 +1,25 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "progressui.h"
+
+int InitProgressUI(int *argc, char ***argv)
+{
+ return 0;
+}
+
+int ShowProgressUI()
+{
+ return 0;
+}
+
+void QuitProgressUI()
+{
+}
+
+void UpdateProgressUI(float progress)
+{
+}
diff --git a/toolkit/mozapps/update/updater/progressui_osx.mm b/toolkit/mozapps/update/updater/progressui_osx.mm
new file mode 100644
index 000000000..54c9c41b7
--- /dev/null
+++ b/toolkit/mozapps/update/updater/progressui_osx.mm
@@ -0,0 +1,144 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#import <Cocoa/Cocoa.h>
+#include <stdio.h>
+#include <unistd.h>
+#include "mozilla/Sprintf.h"
+#include "progressui.h"
+#include "readstrings.h"
+#include "errors.h"
+
+#define TIMER_INTERVAL 0.2
+
+static float sProgressVal; // between 0 and 100
+static BOOL sQuit = NO;
+static BOOL sIndeterminate = NO;
+static StringTable sLabels;
+static const char *sUpdatePath;
+
+@interface UpdaterUI : NSObject
+{
+ IBOutlet NSProgressIndicator *progressBar;
+ IBOutlet NSTextField *progressTextField;
+}
+@end
+
+@implementation UpdaterUI
+
+-(void)awakeFromNib
+{
+ NSWindow *w = [progressBar window];
+
+ [w setTitle:[NSString stringWithUTF8String:sLabels.title]];
+ [progressTextField setStringValue:[NSString stringWithUTF8String:sLabels.info]];
+
+ NSRect origTextFrame = [progressTextField frame];
+ [progressTextField sizeToFit];
+
+ int widthAdjust = progressTextField.frame.size.width - origTextFrame.size.width;
+
+ if (widthAdjust > 0) {
+ NSRect f;
+ f.size.width = w.frame.size.width + widthAdjust;
+ f.size.height = w.frame.size.height;
+ [w setFrame:f display:YES];
+ }
+
+ [w center];
+
+ [progressBar setIndeterminate:sIndeterminate];
+ [progressBar setDoubleValue:0.0];
+
+ [[NSTimer scheduledTimerWithTimeInterval:TIMER_INTERVAL target:self
+ selector:@selector(updateProgressUI:)
+ userInfo:nil repeats:YES] retain];
+
+ // Make sure we are on top initially
+ [NSApp activateIgnoringOtherApps:YES];
+}
+
+// called when the timer goes off
+-(void)updateProgressUI:(NSTimer *)aTimer
+{
+ if (sQuit) {
+ [aTimer invalidate];
+ [aTimer release];
+
+ // It seems to be necessary to activate and hide ourselves before we stop,
+ // otherwise the "run" method will not return until the user focuses some
+ // other app. The activate step is necessary if we are not the active app.
+ // This is a big hack, but it seems to do the trick.
+ [NSApp activateIgnoringOtherApps:YES];
+ [NSApp hide:self];
+ [NSApp stop:self];
+ }
+
+ float progress = sProgressVal;
+
+ [progressBar setDoubleValue:(double)progress];
+}
+
+// leave this as returning a BOOL instead of NSApplicationTerminateReply
+// for backward compatibility
+- (BOOL)applicationShouldTerminate:(NSApplication *)sender
+{
+ return sQuit;
+}
+
+@end
+
+int
+InitProgressUI(int *pargc, char ***pargv)
+{
+ sUpdatePath = (*pargv)[1];
+
+ return 0;
+}
+
+int
+ShowProgressUI(bool indeterminate)
+{
+ // Only show the Progress UI if the process is taking a significant amount of
+ // time where a significant amount of time is defined as .5 seconds after
+ // ShowProgressUI is called sProgress is less than 70.
+ usleep(500000);
+
+ if (sQuit || sProgressVal > 70.0f)
+ return 0;
+
+ char path[PATH_MAX];
+ SprintfLiteral(path, "%s/updater.ini", sUpdatePath);
+ if (ReadStrings(path, &sLabels) != OK)
+ return -1;
+
+ // Continue the update without showing the Progress UI if any of the supplied
+ // strings are larger than MAX_TEXT_LEN (Bug 628829).
+ if (!(strlen(sLabels.title) < MAX_TEXT_LEN - 1 &&
+ strlen(sLabels.info) < MAX_TEXT_LEN - 1))
+ return -1;
+
+ sIndeterminate = indeterminate;
+ [NSApplication sharedApplication];
+ [NSBundle loadNibNamed:@"MainMenu" owner:NSApp];
+ [NSApp run];
+
+ return 0;
+}
+
+// Called on a background thread
+void
+QuitProgressUI()
+{
+ sQuit = YES;
+}
+
+// Called on a background thread
+void
+UpdateProgressUI(float progress)
+{
+ sProgressVal = progress; // 32-bit writes are atomic
+}
diff --git a/toolkit/mozapps/update/updater/progressui_win.cpp b/toolkit/mozapps/update/updater/progressui_win.cpp
new file mode 100644
index 000000000..89bd71e85
--- /dev/null
+++ b/toolkit/mozapps/update/updater/progressui_win.cpp
@@ -0,0 +1,319 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include <stdio.h>
+#include <windows.h>
+#include <commctrl.h>
+#include <process.h>
+#include <io.h>
+
+#include "resource.h"
+#include "progressui.h"
+#include "readstrings.h"
+#include "errors.h"
+
+#define TIMER_ID 1
+#define TIMER_INTERVAL 100
+
+#define RESIZE_WINDOW(hwnd, extrax, extray) \
+ { \
+ RECT windowSize; \
+ GetWindowRect(hwnd, &windowSize); \
+ SetWindowPos(hwnd, 0, 0, 0, windowSize.right - windowSize.left + extrax, \
+ windowSize.bottom - windowSize.top + extray, \
+ SWP_NOMOVE | SWP_NOZORDER); \
+ }
+
+#define MOVE_WINDOW(hwnd, dx, dy) \
+ { \
+ RECT rc; \
+ POINT pt; \
+ GetWindowRect(hwnd, &rc); \
+ pt.x = rc.left; \
+ pt.y = rc.top; \
+ ScreenToClient(GetParent(hwnd), &pt); \
+ SetWindowPos(hwnd, 0, pt.x + dx, pt.y + dy, 0, 0, \
+ SWP_NOSIZE | SWP_NOZORDER); \
+ }
+
+static float sProgress; // between 0 and 100
+static BOOL sQuit = FALSE;
+static BOOL sIndeterminate = FALSE;
+static StringTable sUIStrings;
+
+static BOOL
+GetStringsFile(WCHAR filename[MAX_PATH])
+{
+ if (!GetModuleFileNameW(nullptr, filename, MAX_PATH))
+ return FALSE;
+
+ WCHAR *dot = wcsrchr(filename, '.');
+ if (!dot || wcsicmp(dot + 1, L"exe"))
+ return FALSE;
+
+ wcscpy(dot + 1, L"ini");
+ return TRUE;
+}
+
+static void
+UpdateDialog(HWND hDlg)
+{
+ int pos = int(sProgress + 0.5f);
+ HWND hWndPro = GetDlgItem(hDlg, IDC_PROGRESS);
+ SendMessage(hWndPro, PBM_SETPOS, pos, 0L);
+}
+
+// The code in this function is from MSDN:
+// http://msdn.microsoft.com/library/default.asp?url=/library/en-us/winui/winui/windowsuserinterface/windowing/dialogboxes/usingdialogboxes.asp
+static void
+CenterDialog(HWND hDlg)
+{
+ RECT rc, rcOwner, rcDlg;
+
+ // Get the owner window and dialog box rectangles.
+ HWND desktop = GetDesktopWindow();
+
+ GetWindowRect(desktop, &rcOwner);
+ GetWindowRect(hDlg, &rcDlg);
+ CopyRect(&rc, &rcOwner);
+
+ // Offset the owner and dialog box rectangles so that
+ // right and bottom values represent the width and
+ // height, and then offset the owner again to discard
+ // space taken up by the dialog box.
+
+ OffsetRect(&rcDlg, -rcDlg.left, -rcDlg.top);
+ OffsetRect(&rc, -rc.left, -rc.top);
+ OffsetRect(&rc, -rcDlg.right, -rcDlg.bottom);
+
+ // The new position is the sum of half the remaining
+ // space and the owner's original position.
+
+ SetWindowPos(hDlg,
+ HWND_TOP,
+ rcOwner.left + (rc.right / 2),
+ rcOwner.top + (rc.bottom / 2),
+ 0, 0, // ignores size arguments
+ SWP_NOSIZE);
+}
+
+static void
+InitDialog(HWND hDlg)
+{
+ WCHAR szwTitle[MAX_TEXT_LEN];
+ WCHAR szwInfo[MAX_TEXT_LEN];
+
+ MultiByteToWideChar(CP_UTF8, 0, sUIStrings.title, -1, szwTitle,
+ sizeof(szwTitle)/sizeof(szwTitle[0]));
+ MultiByteToWideChar(CP_UTF8, 0, sUIStrings.info, -1, szwInfo,
+ sizeof(szwInfo)/sizeof(szwInfo[0]));
+
+ SetWindowTextW(hDlg, szwTitle);
+ SetWindowTextW(GetDlgItem(hDlg, IDC_INFO), szwInfo);
+
+ // Set dialog icon
+ HICON hIcon = LoadIcon(GetModuleHandle(nullptr),
+ MAKEINTRESOURCE(IDI_DIALOG));
+ if (hIcon)
+ SendMessage(hDlg, WM_SETICON, ICON_BIG, (LPARAM) hIcon);
+
+ HWND hWndPro = GetDlgItem(hDlg, IDC_PROGRESS);
+ SendMessage(hWndPro, PBM_SETRANGE, 0, MAKELPARAM(0, 100));
+ if (sIndeterminate) {
+ LONG_PTR val = GetWindowLongPtr(hWndPro, GWL_STYLE);
+ SetWindowLongPtr(hWndPro, GWL_STYLE, val|PBS_MARQUEE);
+ SendMessage(hWndPro,(UINT) PBM_SETMARQUEE,(WPARAM) TRUE,(LPARAM)50 );
+ }
+
+ // Resize the dialog to fit all of the text if necessary.
+ RECT infoSize, textSize;
+ HWND hWndInfo = GetDlgItem(hDlg, IDC_INFO);
+
+ // Get the control's font for calculating the new size for the control
+ HDC hDCInfo = GetDC(hWndInfo);
+ HFONT hInfoFont, hOldFont = NULL;
+ hInfoFont = (HFONT)SendMessage(hWndInfo, WM_GETFONT, 0, 0);
+
+ if (hInfoFont)
+ hOldFont = (HFONT)SelectObject(hDCInfo, hInfoFont);
+
+ // Measure the space needed for the text on a single line. DT_CALCRECT means
+ // nothing is drawn.
+ if (DrawText(hDCInfo, szwInfo, -1, &textSize,
+ DT_CALCRECT | DT_NOCLIP | DT_SINGLELINE)) {
+ GetClientRect(hWndInfo, &infoSize);
+ SIZE extra;
+ // Calculate the additional space needed for the text by subtracting from
+ // the rectangle returned by DrawText the existing client rectangle's width
+ // and height.
+ extra.cx = (textSize.right - textSize.left) - \
+ (infoSize.right - infoSize.left);
+ extra.cy = (textSize.bottom - textSize.top) - \
+ (infoSize.bottom - infoSize.top);
+ if (extra.cx < 0)
+ extra.cx = 0;
+ if (extra.cy < 0)
+ extra.cy = 0;
+ if ((extra.cx > 0) || (extra.cy > 0)) {
+ RESIZE_WINDOW(hDlg, extra.cx, extra.cy);
+ RESIZE_WINDOW(hWndInfo, extra.cx, extra.cy);
+ RESIZE_WINDOW(hWndPro, extra.cx, 0);
+ MOVE_WINDOW(hWndPro, 0, extra.cy);
+ }
+ }
+
+ if (hOldFont)
+ SelectObject(hDCInfo, hOldFont);
+
+ ReleaseDC(hWndInfo, hDCInfo);
+
+ CenterDialog(hDlg); // make dialog appear in the center of the screen
+
+ SetTimer(hDlg, TIMER_ID, TIMER_INTERVAL, nullptr);
+}
+
+// Message handler for update dialog.
+static LRESULT CALLBACK
+DialogProc(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
+{
+ switch (message)
+ {
+ case WM_INITDIALOG:
+ InitDialog(hDlg);
+ return TRUE;
+
+ case WM_TIMER:
+ if (sQuit) {
+ EndDialog(hDlg, 0);
+ } else {
+ UpdateDialog(hDlg);
+ }
+ return TRUE;
+
+ case WM_COMMAND:
+ return TRUE;
+ }
+ return FALSE;
+}
+
+int
+InitProgressUI(int *argc, WCHAR ***argv)
+{
+ return 0;
+}
+
+/**
+ * Initializes the progress UI strings
+ *
+ * @return 0 on success, -1 on error
+*/
+int
+InitProgressUIStrings() {
+ // If we do not have updater.ini, then we should not bother showing UI.
+ WCHAR filename[MAX_PATH];
+ if (!GetStringsFile(filename)) {
+ return -1;
+ }
+
+ if (_waccess(filename, 04)) {
+ return -1;
+ }
+
+ // If the updater.ini doesn't have the required strings, then we should not
+ // bother showing UI.
+ if (ReadStrings(filename, &sUIStrings) != OK) {
+ return -1;
+ }
+
+ return 0;
+}
+
+int
+ShowProgressUI(bool indeterminate, bool initUIStrings)
+{
+ sIndeterminate = indeterminate;
+ if (!indeterminate) {
+ // Only show the Progress UI if the process is taking a significant amount of
+ // time where a significant amount of time is defined as .5 seconds after
+ // ShowProgressUI is called sProgress is less than 70.
+ Sleep(500);
+
+ if (sQuit || sProgress > 70.0f)
+ return 0;
+ }
+
+ // Don't load the UI if there's an <exe_name>.Local directory for redirection.
+ WCHAR appPath[MAX_PATH + 1] = { L'\0' };
+ if (!GetModuleFileNameW(nullptr, appPath, MAX_PATH)) {
+ return -1;
+ }
+
+ if (wcslen(appPath) + wcslen(L".Local") >= MAX_PATH) {
+ return -1;
+ }
+
+ wcscat(appPath, L".Local");
+
+ if (!_waccess(appPath, 04)) {
+ return -1;
+ }
+
+ // Don't load the UI if the strings for the UI are not provided.
+ if (initUIStrings && InitProgressUIStrings() == -1) {
+ return -1;
+ }
+
+ if (!GetModuleFileNameW(nullptr, appPath, MAX_PATH)) {
+ return -1;
+ }
+
+ // Use an activation context that supports visual styles for the controls.
+ ACTCTXW actx = {0};
+ actx.cbSize = sizeof(ACTCTXW);
+ actx.dwFlags = ACTCTX_FLAG_RESOURCE_NAME_VALID | ACTCTX_FLAG_HMODULE_VALID;
+ actx.hModule = GetModuleHandle(NULL); // Use the embedded manifest
+ // This is needed only for Win XP but doesn't cause a problem with other
+ // versions of Windows.
+ actx.lpSource = appPath;
+ actx.lpResourceName = MAKEINTRESOURCE(IDR_COMCTL32_MANIFEST);
+
+ HANDLE hactx = INVALID_HANDLE_VALUE;
+ hactx = CreateActCtxW(&actx);
+ ULONG_PTR actxCookie = NULL;
+ if (hactx != INVALID_HANDLE_VALUE) {
+ // Push the specified activation context to the top of the activation stack.
+ ActivateActCtx(hactx, &actxCookie);
+ }
+
+ INITCOMMONCONTROLSEX icc = {
+ sizeof(INITCOMMONCONTROLSEX),
+ ICC_PROGRESS_CLASS
+ };
+ InitCommonControlsEx(&icc);
+
+ DialogBox(GetModuleHandle(nullptr),
+ MAKEINTRESOURCE(IDD_DIALOG), nullptr,
+ (DLGPROC) DialogProc);
+
+ if (hactx != INVALID_HANDLE_VALUE) {
+ // Deactivate the context now that the comctl32.dll is loaded.
+ DeactivateActCtx(0, actxCookie);
+ }
+
+ return 0;
+}
+
+void
+QuitProgressUI()
+{
+ sQuit = TRUE;
+}
+
+void
+UpdateProgressUI(float progress)
+{
+ sProgress = progress; // 32-bit writes are atomic
+}
diff --git a/toolkit/mozapps/update/updater/release_primary.der b/toolkit/mozapps/update/updater/release_primary.der
new file mode 100644
index 000000000..11417c35e
--- /dev/null
+++ b/toolkit/mozapps/update/updater/release_primary.der
Binary files differ
diff --git a/toolkit/mozapps/update/updater/release_secondary.der b/toolkit/mozapps/update/updater/release_secondary.der
new file mode 100644
index 000000000..16a7ef6d9
--- /dev/null
+++ b/toolkit/mozapps/update/updater/release_secondary.der
Binary files differ
diff --git a/toolkit/mozapps/update/updater/resource.h b/toolkit/mozapps/update/updater/resource.h
new file mode 100644
index 000000000..3cfa4efda
--- /dev/null
+++ b/toolkit/mozapps/update/updater/resource.h
@@ -0,0 +1,29 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+//{{NO_DEPENDENCIES}}
+// Microsoft Visual C++ generated include file.
+// Used by updater.rc
+//
+#define IDD_DIALOG 101
+#define IDC_PROGRESS 1000
+#define IDC_INFO 1002
+#define IDI_DIALOG 1003
+#define TYPE_CERT 512
+#define IDR_PRIMARY_CERT 1004
+#define IDR_BACKUP_CERT 1005
+#define IDS_UPDATER_IDENTITY 1006
+#define IDR_XPCSHELL_CERT 1007
+#define IDR_COMCTL32_MANIFEST 17
+
+// Next default values for new objects
+//
+#ifdef APSTUDIO_INVOKED
+#ifndef APSTUDIO_READONLY_SYMBOLS
+#define _APS_NEXT_RESOURCE_VALUE 102
+#define _APS_NEXT_COMMAND_VALUE 40001
+#define _APS_NEXT_CONTROL_VALUE 1008
+#define _APS_NEXT_SYMED_VALUE 101
+#endif
+#endif
diff --git a/toolkit/mozapps/update/updater/updater-common.build b/toolkit/mozapps/update/updater/updater-common.build
new file mode 100644
index 000000000..02b7338bc
--- /dev/null
+++ b/toolkit/mozapps/update/updater/updater-common.build
@@ -0,0 +1,136 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+srcs = [
+ 'archivereader.cpp',
+ 'bspatch.cpp',
+ 'updater.cpp',
+]
+
+have_progressui = 0
+
+if CONFIG['MOZ_VERIFY_MAR_SIGNATURE']:
+ USE_LIBS += [
+ 'verifymar',
+ ]
+
+if CONFIG['OS_ARCH'] == 'WINNT':
+ have_progressui = 1
+ srcs += [
+ 'loaddlls.cpp',
+ 'progressui_win.cpp',
+ 'win_dirent.cpp',
+ ]
+ RCINCLUDE = '%supdater.rc' % updater_rel_path
+ DEFINES['UNICODE'] = True
+ DEFINES['_UNICODE'] = True
+ USE_STATIC_LIBS = True
+
+ # Pick up nsWindowsRestart.cpp
+ LOCAL_INCLUDES += [
+ '/toolkit/xre',
+ ]
+ USE_LIBS += [
+ 'updatecommon-standalone',
+ ]
+ OS_LIBS += [
+ 'comctl32',
+ 'ws2_32',
+ 'shell32',
+ 'shlwapi',
+ 'crypt32',
+ 'advapi32',
+ ]
+elif CONFIG['OS_ARCH'] == 'Linux' and CONFIG['MOZ_VERIFY_MAR_SIGNATURE']:
+ USE_LIBS += [
+ 'nss',
+ 'signmar',
+ 'updatecommon',
+ ]
+ OS_LIBS += CONFIG['NSPR_LIBS']
+else:
+ USE_LIBS += [
+ 'updatecommon',
+ ]
+
+USE_LIBS += [
+ 'mar',
+]
+
+if CONFIG['MOZ_SYSTEM_BZ2']:
+ OS_LIBS += CONFIG['MOZ_BZ2_LIBS']
+else:
+ USE_LIBS += [
+ 'bz2',
+ ]
+
+if 'gtk' in CONFIG['MOZ_WIDGET_TOOLKIT']:
+ have_progressui = 1
+ srcs += [
+ 'progressui_gtk.cpp',
+ ]
+
+if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'cocoa':
+ have_progressui = 1
+ srcs += [
+ 'launchchild_osx.mm',
+ 'progressui_osx.mm',
+ ]
+ OS_LIBS += [
+ '-framework Cocoa',
+ '-framework Security',
+ '-framework SystemConfiguration',
+ ]
+ UNIFIED_SOURCES += [
+ '/toolkit/xre/updaterfileutils_osx.mm',
+ ]
+ LOCAL_INCLUDES += [
+ '/toolkit/xre',
+ ]
+elif CONFIG['MOZ_WIDGET_TOOLKIT'] == 'gonk':
+ have_progressui = 1
+ srcs += [
+ 'automounter_gonk.cpp',
+ 'progressui_gonk.cpp',
+ ]
+ DISABLE_STL_WRAPPING = True
+ OS_LIBS += [
+ 'cutils',
+ 'sysutils',
+ ]
+
+if have_progressui == 0:
+ srcs += [
+ 'progressui_null.cpp',
+ ]
+
+SOURCES += sorted(srcs)
+
+DEFINES['NS_NO_XPCOM'] = True
+DISABLE_STL_WRAPPING = True
+for var in ('MAR_CHANNEL_ID', 'MOZ_APP_VERSION'):
+ DEFINES[var] = '"%s"' % CONFIG[var]
+
+LOCAL_INCLUDES += [
+ '/toolkit/mozapps/update/common',
+ '/xpcom/glue',
+]
+
+DELAYLOAD_DLLS += [
+ 'crypt32.dll',
+ 'comctl32.dll',
+ 'userenv.dll',
+ 'wsock32.dll',
+]
+
+if CONFIG['_MSC_VER']:
+ WIN32_EXE_LDFLAGS += ['-ENTRY:wmainCRTStartup']
+elif CONFIG['OS_ARCH'] == 'WINNT':
+ WIN32_EXE_LDFLAGS += ['-municode']
+
+if 'gtk' in CONFIG['MOZ_WIDGET_TOOLKIT']:
+ CXXFLAGS += CONFIG['TK_CFLAGS']
+ OS_LIBS += CONFIG['TK_LIBS']
diff --git a/toolkit/mozapps/update/updater/updater-xpcshell/Makefile.in b/toolkit/mozapps/update/updater/updater-xpcshell/Makefile.in
new file mode 100644
index 000000000..01822b186
--- /dev/null
+++ b/toolkit/mozapps/update/updater/updater-xpcshell/Makefile.in
@@ -0,0 +1,41 @@
+# vim:set ts=8 sw=8 sts=8 noet:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+# For changes here, also consider ../Makefile.in
+
+XPCSHELLTESTROOT = $(topobjdir)/_tests/xpcshell/toolkit/mozapps/update/tests
+MOCHITESTROOT = $(topobjdir)/_tests/testing/mochitest/chrome/toolkit/mozapps/update/tests
+
+include $(topsrcdir)/config/rules.mk
+
+ifndef MOZ_WINCONSOLE
+ifdef MOZ_DEBUG
+MOZ_WINCONSOLE = 1
+else
+MOZ_WINCONSOLE = 0
+endif
+endif
+
+ifdef COMPILE_ENVIRONMENT
+tools::
+ifeq (cocoa,$(MOZ_WIDGET_TOOLKIT))
+ # Copy for xpcshell tests
+ $(NSINSTALL) -D $(XPCSHELLTESTROOT)/data/updater-xpcshell.app
+ rsync -a -C --exclude '*.in' $(srcdir)/../macbuild/Contents $(XPCSHELLTESTROOT)/data/updater-xpcshell.app
+ sed -e 's/%APP_NAME%/$(MOZ_APP_DISPLAYNAME)/' $(srcdir)/../macbuild/Contents/Resources/English.lproj/InfoPlist.strings.in | \
+ iconv -f UTF-8 -t UTF-16 > $(XPCSHELLTESTROOT)/data/updater-xpcshell.app/Contents/Resources/English.lproj/InfoPlist.strings
+ $(NSINSTALL) -D $(XPCSHELLTESTROOT)/data/updater-xpcshell.app/Contents/MacOS/updater-xpcshell
+ $(NSINSTALL) updater-xpcshell $(XPCSHELLTESTROOT)/data/updater-xpcshell.app/Contents/MacOS
+ rm -Rf $(XPCSHELLTESTROOT)/data/updater.app
+ mv $(XPCSHELLTESTROOT)/data/updater-xpcshell.app $(XPCSHELLTESTROOT)/data/updater.app
+ mv $(XPCSHELLTESTROOT)/data/updater.app/Contents/MacOS/updater-xpcshell $(XPCSHELLTESTROOT)/data/updater.app/Contents/MacOS/org.mozilla.updater
+
+ # Copy for mochitest chrome tests
+ rsync -a -C $(XPCSHELLTESTROOT)/data/updater.app $(MOCHITESTROOT)/data/
+else
+ cp $(PROGRAM) $(XPCSHELLTESTROOT)/data/updater$(BIN_SUFFIX)
+ cp $(PROGRAM) $(MOCHITESTROOT)/data/updater$(BIN_SUFFIX)
+endif
+endif # COMPILE_ENVIRONMENT
diff --git a/toolkit/mozapps/update/updater/updater-xpcshell/moz.build b/toolkit/mozapps/update/updater/updater-xpcshell/moz.build
new file mode 100644
index 000000000..710b7e1de
--- /dev/null
+++ b/toolkit/mozapps/update/updater/updater-xpcshell/moz.build
@@ -0,0 +1,14 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+Program('updater-xpcshell')
+
+updater_rel_path = '../'
+DIST_INSTALL = False
+DEFINES['TEST_UPDATER'] = True
+include('../updater-common.build')
+
+CXXFLAGS += CONFIG['MOZ_BZ2_CFLAGS']
diff --git a/toolkit/mozapps/update/updater/updater.cpp b/toolkit/mozapps/update/updater/updater.cpp
new file mode 100644
index 000000000..63a92c084
--- /dev/null
+++ b/toolkit/mozapps/update/updater/updater.cpp
@@ -0,0 +1,4454 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * Manifest Format
+ * ---------------
+ *
+ * contents = 1*( line )
+ * line = method LWS *( param LWS ) CRLF
+ * CRLF = "\r\n"
+ * LWS = 1*( " " | "\t" )
+ *
+ * Available methods for the manifest file:
+ *
+ * updatev2.manifest
+ * -----------------
+ * method = "add" | "add-if" | "patch" | "patch-if" | "remove" |
+ * "rmdir" | "rmrfdir" | type
+ *
+ * 'type' is the update type (e.g. complete or partial) and when present MUST
+ * be the first entry in the update manifest. The type is used to support
+ * downgrades by causing the actions defined in precomplete to be performed.
+ *
+ * updatev3.manifest
+ * -----------------
+ * method = "add" | "add-if" | "add-if-not" | "patch" | "patch-if" |
+ * "remove" | "rmdir" | "rmrfdir" | type
+ *
+ * 'add-if-not' adds a file if it doesn't exist.
+ *
+ * precomplete
+ * -----------
+ * method = "remove" | "rmdir"
+ */
+#include "bspatch.h"
+#include "progressui.h"
+#include "archivereader.h"
+#include "readstrings.h"
+#include "errors.h"
+#include "bzlib.h"
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <stdarg.h>
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <errno.h>
+#include <algorithm>
+
+#include "updatecommon.h"
+#ifdef XP_MACOSX
+#include "updaterfileutils_osx.h"
+#endif // XP_MACOSX
+
+#include "mozilla/Compiler.h"
+#include "mozilla/Types.h"
+#include "mozilla/UniquePtr.h"
+
+// Amount of the progress bar to use in each of the 3 update stages,
+// should total 100.0.
+#define PROGRESS_PREPARE_SIZE 20.0f
+#define PROGRESS_EXECUTE_SIZE 75.0f
+#define PROGRESS_FINISH_SIZE 5.0f
+
+// Amount of time in ms to wait for the parent process to close
+#ifdef DEBUG
+// Use a large value for debug builds since the xpcshell tests take a long time.
+#define PARENT_WAIT 30000
+#else
+#define PARENT_WAIT 10000
+#endif
+
+#if defined(XP_MACOSX)
+// These functions are defined in launchchild_osx.mm
+void CleanupElevatedMacUpdate(bool aFailureOccurred);
+bool IsOwnedByGroupAdmin(const char* aAppBundle);
+bool IsRecursivelyWritable(const char* aPath);
+void LaunchChild(int argc, const char** argv);
+void LaunchMacPostProcess(const char* aAppBundle);
+bool ObtainUpdaterArguments(int* argc, char*** argv);
+bool ServeElevatedUpdate(int argc, const char** argv);
+void SetGroupOwnershipAndPermissions(const char* aAppBundle);
+struct UpdateServerThreadArgs
+{
+ int argc;
+ const NS_tchar** argv;
+};
+#endif
+
+#ifndef _O_BINARY
+# define _O_BINARY 0
+#endif
+
+#ifndef NULL
+# define NULL (0)
+#endif
+
+#ifndef SSIZE_MAX
+# define SSIZE_MAX LONG_MAX
+#endif
+
+// We want to use execv to invoke the callback executable on platforms where
+// we were launched using execv. See nsUpdateDriver.cpp.
+#if defined(XP_UNIX) && !defined(XP_MACOSX)
+#define USE_EXECV
+#endif
+
+#if defined(MOZ_WIDGET_GONK)
+# include "automounter_gonk.h"
+# include <unistd.h>
+# include <android/log.h>
+# include <linux/ioprio.h>
+# include <sys/resource.h>
+
+#if ANDROID_VERSION < 21
+// The only header file in bionic which has a function prototype for ioprio_set
+// is libc/include/sys/linux-unistd.h. However, linux-unistd.h conflicts
+// badly with unistd.h, so we declare the prototype for ioprio_set directly.
+extern "C" MOZ_EXPORT int ioprio_set(int which, int who, int ioprio);
+#else
+# include <sys/syscall.h>
+static int ioprio_set(int which, int who, int ioprio) {
+ return syscall(__NR_ioprio_set, which, who, ioprio);
+}
+#endif
+
+# define MAYBE_USE_HARD_LINKS 1
+static bool sUseHardLinks = true;
+#else
+# define MAYBE_USE_HARD_LINKS 0
+#endif
+
+#if defined(MOZ_VERIFY_MAR_SIGNATURE) && !defined(XP_WIN) && \
+ !defined(XP_MACOSX) && !defined(MOZ_WIDGET_GONK)
+#include "nss.h"
+#include "prerror.h"
+#endif
+
+#ifdef XP_WIN
+#ifdef MOZ_MAINTENANCE_SERVICE
+#include "registrycertificates.h"
+#endif
+BOOL PathAppendSafe(LPWSTR base, LPCWSTR extra);
+BOOL PathGetSiblingFilePath(LPWSTR destinationBuffer,
+ LPCWSTR siblingFilePath,
+ LPCWSTR newFileName);
+#include "updatehelper.h"
+
+// Closes the handle if valid and if the updater is elevated returns with the
+// return code specified. This prevents multiple launches of the callback
+// application by preventing the elevated process from launching the callback.
+#define EXIT_WHEN_ELEVATED(path, handle, retCode) \
+ { \
+ if (handle != INVALID_HANDLE_VALUE) { \
+ CloseHandle(handle); \
+ } \
+ if (_waccess(path, F_OK) == 0 && NS_tremove(path) != 0) { \
+ LogFinish(); \
+ return retCode; \
+ } \
+ }
+#endif
+
+//-----------------------------------------------------------------------------
+
+// This variable lives in libbz2. It's declared in bzlib_private.h, so we just
+// declare it here to avoid including that entire header file.
+#define BZ2_CRC32TABLE_UNDECLARED
+
+#if MOZ_IS_GCC || defined(__clang__)
+extern "C" __attribute__((visibility("default"))) unsigned int BZ2_crc32Table[256];
+#undef BZ2_CRC32TABLE_UNDECLARED
+#elif defined(__SUNPRO_C) || defined(__SUNPRO_CC)
+extern "C" __global unsigned int BZ2_crc32Table[256];
+#undef BZ2_CRC32TABLE_UNDECLARED
+#endif
+#if defined(BZ2_CRC32TABLE_UNDECLARED)
+extern "C" unsigned int BZ2_crc32Table[256];
+#undef BZ2_CRC32TABLE_UNDECLARED
+#endif
+
+static unsigned int
+crc32(const unsigned char *buf, unsigned int len)
+{
+ unsigned int crc = 0xffffffffL;
+
+ const unsigned char *end = buf + len;
+ for (; buf != end; ++buf)
+ crc = (crc << 8) ^ BZ2_crc32Table[(crc >> 24) ^ *buf];
+
+ crc = ~crc;
+ return crc;
+}
+
+//-----------------------------------------------------------------------------
+
+// A simple stack based container for a FILE struct that closes the
+// file descriptor from its destructor.
+class AutoFile
+{
+public:
+ explicit AutoFile(FILE* file = nullptr)
+ : mFile(file) {
+ }
+
+ ~AutoFile() {
+ if (mFile != nullptr)
+ fclose(mFile);
+ }
+
+ AutoFile &operator=(FILE* file) {
+ if (mFile != 0)
+ fclose(mFile);
+ mFile = file;
+ return *this;
+ }
+
+ operator FILE*() {
+ return mFile;
+ }
+
+ FILE* get() {
+ return mFile;
+ }
+
+private:
+ FILE* mFile;
+};
+
+struct MARChannelStringTable {
+ MARChannelStringTable()
+ {
+ MARChannelID[0] = '\0';
+ }
+
+ char MARChannelID[MAX_TEXT_LEN];
+};
+
+//-----------------------------------------------------------------------------
+
+typedef void (* ThreadFunc)(void *param);
+
+#ifdef XP_WIN
+#include <process.h>
+
+class Thread
+{
+public:
+ int Run(ThreadFunc func, void *param)
+ {
+ mThreadFunc = func;
+ mThreadParam = param;
+
+ unsigned int threadID;
+
+ mThread = (HANDLE) _beginthreadex(nullptr, 0, ThreadMain, this, 0,
+ &threadID);
+
+ return mThread ? 0 : -1;
+ }
+ int Join()
+ {
+ WaitForSingleObject(mThread, INFINITE);
+ CloseHandle(mThread);
+ return 0;
+ }
+private:
+ static unsigned __stdcall ThreadMain(void *p)
+ {
+ Thread *self = (Thread *) p;
+ self->mThreadFunc(self->mThreadParam);
+ return 0;
+ }
+ HANDLE mThread;
+ ThreadFunc mThreadFunc;
+ void *mThreadParam;
+};
+
+#elif defined(XP_UNIX)
+#include <pthread.h>
+
+class Thread
+{
+public:
+ int Run(ThreadFunc func, void *param)
+ {
+ return pthread_create(&thr, nullptr, (void* (*)(void *)) func, param);
+ }
+ int Join()
+ {
+ void *result;
+ return pthread_join(thr, &result);
+ }
+private:
+ pthread_t thr;
+};
+
+#else
+#error "Unsupported platform"
+#endif
+
+//-----------------------------------------------------------------------------
+
+static NS_tchar gPatchDirPath[MAXPATHLEN];
+static NS_tchar gInstallDirPath[MAXPATHLEN];
+static NS_tchar gWorkingDirPath[MAXPATHLEN];
+static ArchiveReader gArchiveReader;
+static bool gSucceeded = false;
+static bool sStagedUpdate = false;
+static bool sReplaceRequest = false;
+static bool sUsingService = false;
+static bool sIsOSUpdate = false;
+
+#ifdef XP_WIN
+// The current working directory specified in the command line.
+static NS_tchar* gDestPath;
+static NS_tchar gCallbackRelPath[MAXPATHLEN];
+static NS_tchar gCallbackBackupPath[MAXPATHLEN];
+static NS_tchar gDeleteDirPath[MAXPATHLEN];
+#endif
+
+static const NS_tchar kWhitespace[] = NS_T(" \t");
+static const NS_tchar kNL[] = NS_T("\r\n");
+static const NS_tchar kQuote[] = NS_T("\"");
+
+static inline size_t
+mmin(size_t a, size_t b)
+{
+ return (a > b) ? b : a;
+}
+
+static NS_tchar*
+mstrtok(const NS_tchar *delims, NS_tchar **str)
+{
+ if (!*str || !**str) {
+ *str = nullptr;
+ return nullptr;
+ }
+
+ // skip leading "whitespace"
+ NS_tchar *ret = *str;
+ const NS_tchar *d;
+ do {
+ for (d = delims; *d != NS_T('\0'); ++d) {
+ if (*ret == *d) {
+ ++ret;
+ break;
+ }
+ }
+ } while (*d);
+
+ if (!*ret) {
+ *str = ret;
+ return nullptr;
+ }
+
+ NS_tchar *i = ret;
+ do {
+ for (d = delims; *d != NS_T('\0'); ++d) {
+ if (*i == *d) {
+ *i = NS_T('\0');
+ *str = ++i;
+ return ret;
+ }
+ }
+ ++i;
+ } while (*i);
+
+ *str = nullptr;
+ return ret;
+}
+
+static bool
+EnvHasValue(const char *name)
+{
+ const char *val = getenv(name);
+ return (val && *val);
+}
+
+/**
+ * Coverts a relative update path to a full path.
+ *
+ * @param relpath
+ * The relative path to convert to a full path.
+ * @return valid filesystem full path or nullptr if memory allocation fails.
+ */
+static NS_tchar*
+get_full_path(const NS_tchar *relpath)
+{
+ NS_tchar *destpath = sStagedUpdate ? gWorkingDirPath : gInstallDirPath;
+ size_t lendestpath = NS_tstrlen(destpath);
+ size_t lenrelpath = NS_tstrlen(relpath);
+ NS_tchar *s = new NS_tchar[lendestpath + lenrelpath + 2];
+ if (!s) {
+ return nullptr;
+ }
+
+ NS_tchar *c = s;
+
+ NS_tstrcpy(c, destpath);
+ c += lendestpath;
+ NS_tstrcat(c, NS_T("/"));
+ c++;
+
+ NS_tstrcat(c, relpath);
+ c += lenrelpath;
+ *c = NS_T('\0');
+ return s;
+}
+
+/**
+ * Converts a full update path into a relative path; reverses get_full_path.
+ *
+ * @param fullpath
+ * The absolute path to convert into a relative path.
+ * return pointer to the location within fullpath where the relative path starts
+ * or fullpath itself if it already looks relative.
+ */
+static const NS_tchar*
+get_relative_path(const NS_tchar *fullpath)
+{
+ // If the path isn't absolute, just return it as-is.
+#ifdef XP_WIN
+ if (fullpath[1] != ':' && fullpath[2] != '\\') {
+#else
+ if (fullpath[0] != '/') {
+#endif
+ return fullpath;
+ }
+
+ NS_tchar *prefix = sStagedUpdate ? gWorkingDirPath : gInstallDirPath;
+
+ // If the path isn't long enough to be absolute, return it as-is.
+ if (NS_tstrlen(fullpath) <= NS_tstrlen(prefix)) {
+ return fullpath;
+ }
+
+ return fullpath + NS_tstrlen(prefix) + 1;
+}
+
+/**
+ * Gets the platform specific path and performs simple checks to the path. If
+ * the path checks don't pass nullptr will be returned.
+ *
+ * @param line
+ * The line from the manifest that contains the path.
+ * @param isdir
+ * Whether the path is a directory path. Defaults to false.
+ * @return valid filesystem path or nullptr if the path checks fail.
+ */
+static NS_tchar*
+get_valid_path(NS_tchar **line, bool isdir = false)
+{
+ NS_tchar *path = mstrtok(kQuote, line);
+ if (!path) {
+ LOG(("get_valid_path: unable to determine path: " LOG_S, line));
+ return nullptr;
+ }
+
+ // All paths must be relative from the current working directory
+ if (path[0] == NS_T('/')) {
+ LOG(("get_valid_path: path must be relative: " LOG_S, path));
+ return nullptr;
+ }
+
+#ifdef XP_WIN
+ // All paths must be relative from the current working directory
+ if (path[0] == NS_T('\\') || path[1] == NS_T(':')) {
+ LOG(("get_valid_path: path must be relative: " LOG_S, path));
+ return nullptr;
+ }
+#endif
+
+ if (isdir) {
+ // Directory paths must have a trailing forward slash.
+ if (path[NS_tstrlen(path) - 1] != NS_T('/')) {
+ LOG(("get_valid_path: directory paths must have a trailing forward " \
+ "slash: " LOG_S, path));
+ return nullptr;
+ }
+
+ // Remove the trailing forward slash because stat on Windows will return
+ // ENOENT if the path has a trailing slash.
+ path[NS_tstrlen(path) - 1] = NS_T('\0');
+ }
+
+ // Don't allow relative paths that resolve to a parent directory.
+ if (NS_tstrstr(path, NS_T("..")) != nullptr) {
+ LOG(("get_valid_path: paths must not contain '..': " LOG_S, path));
+ return nullptr;
+ }
+
+ return path;
+}
+
+static NS_tchar*
+get_quoted_path(const NS_tchar *path)
+{
+ size_t lenQuote = NS_tstrlen(kQuote);
+ size_t lenPath = NS_tstrlen(path);
+ size_t len = lenQuote + lenPath + lenQuote + 1;
+
+ NS_tchar *s = (NS_tchar *) malloc(len * sizeof(NS_tchar));
+ if (!s)
+ return nullptr;
+
+ NS_tchar *c = s;
+ NS_tstrcpy(c, kQuote);
+ c += lenQuote;
+ NS_tstrcat(c, path);
+ c += lenPath;
+ NS_tstrcat(c, kQuote);
+ c += lenQuote;
+ *c = NS_T('\0');
+ c++;
+ return s;
+}
+
+static void ensure_write_permissions(const NS_tchar *path)
+{
+#ifdef XP_WIN
+ (void) _wchmod(path, _S_IREAD | _S_IWRITE);
+#else
+ struct stat fs;
+ if (!stat(path, &fs) && !(fs.st_mode & S_IWUSR)) {
+ (void)chmod(path, fs.st_mode | S_IWUSR);
+ }
+#endif
+}
+
+static int ensure_remove(const NS_tchar *path)
+{
+ ensure_write_permissions(path);
+ int rv = NS_tremove(path);
+ if (rv)
+ LOG(("ensure_remove: failed to remove file: " LOG_S ", rv: %d, err: %d",
+ path, rv, errno));
+ return rv;
+}
+
+// Remove the directory pointed to by path and all of its files and sub-directories.
+static int ensure_remove_recursive(const NS_tchar *path,
+ bool continueEnumOnFailure = false)
+{
+ // We use lstat rather than stat here so that we can successfully remove
+ // symlinks.
+ struct NS_tstat_t sInfo;
+ int rv = NS_tlstat(path, &sInfo);
+ if (rv) {
+ // This error is benign
+ return rv;
+ }
+ if (!S_ISDIR(sInfo.st_mode)) {
+ return ensure_remove(path);
+ }
+
+ NS_tDIR *dir;
+ NS_tdirent *entry;
+
+ dir = NS_topendir(path);
+ if (!dir) {
+ LOG(("ensure_remove_recursive: unable to open directory: " LOG_S
+ ", rv: %d, err: %d", path, rv, errno));
+ return rv;
+ }
+
+ while ((entry = NS_treaddir(dir)) != 0) {
+ if (NS_tstrcmp(entry->d_name, NS_T(".")) &&
+ NS_tstrcmp(entry->d_name, NS_T(".."))) {
+ NS_tchar childPath[MAXPATHLEN];
+ NS_tsnprintf(childPath, sizeof(childPath)/sizeof(childPath[0]),
+ NS_T("%s/%s"), path, entry->d_name);
+ rv = ensure_remove_recursive(childPath);
+ if (rv && !continueEnumOnFailure) {
+ break;
+ }
+ }
+ }
+
+ NS_tclosedir(dir);
+
+ if (rv == OK) {
+ ensure_write_permissions(path);
+ rv = NS_trmdir(path);
+ if (rv) {
+ LOG(("ensure_remove_recursive: unable to remove directory: " LOG_S
+ ", rv: %d, err: %d", path, rv, errno));
+ }
+ }
+ return rv;
+}
+
+static bool is_read_only(const NS_tchar *flags)
+{
+ size_t length = NS_tstrlen(flags);
+ if (length == 0)
+ return false;
+
+ // Make sure the string begins with "r"
+ if (flags[0] != NS_T('r'))
+ return false;
+
+ // Look for "r+" or "r+b"
+ if (length > 1 && flags[1] == NS_T('+'))
+ return false;
+
+ // Look for "rb+"
+ if (NS_tstrcmp(flags, NS_T("rb+")) == 0)
+ return false;
+
+ return true;
+}
+
+static FILE* ensure_open(const NS_tchar *path, const NS_tchar *flags, unsigned int options)
+{
+ ensure_write_permissions(path);
+ FILE* f = NS_tfopen(path, flags);
+ if (is_read_only(flags)) {
+ // Don't attempt to modify the file permissions if the file is being opened
+ // in read-only mode.
+ return f;
+ }
+ if (NS_tchmod(path, options) != 0) {
+ if (f != nullptr) {
+ fclose(f);
+ }
+ return nullptr;
+ }
+ struct NS_tstat_t ss;
+ if (NS_tstat(path, &ss) != 0 || ss.st_mode != options) {
+ if (f != nullptr) {
+ fclose(f);
+ }
+ return nullptr;
+ }
+ return f;
+}
+
+// Ensure that the directory containing this file exists.
+static int ensure_parent_dir(const NS_tchar *path)
+{
+ int rv = OK;
+
+ NS_tchar *slash = (NS_tchar *) NS_tstrrchr(path, NS_T('/'));
+ if (slash) {
+ *slash = NS_T('\0');
+ rv = ensure_parent_dir(path);
+ // Only attempt to create the directory if we're not at the root
+ if (rv == OK && *path) {
+ rv = NS_tmkdir(path, 0755);
+ // If the directory already exists, then ignore the error.
+ if (rv < 0 && errno != EEXIST) {
+ LOG(("ensure_parent_dir: failed to create directory: " LOG_S ", " \
+ "err: %d", path, errno));
+ rv = WRITE_ERROR;
+ } else {
+ rv = OK;
+ }
+ }
+ *slash = NS_T('/');
+ }
+ return rv;
+}
+
+#ifdef XP_UNIX
+static int ensure_copy_symlink(const NS_tchar *path, const NS_tchar *dest)
+{
+ // Copy symlinks by creating a new symlink to the same target
+ NS_tchar target[MAXPATHLEN + 1] = {NS_T('\0')};
+ int rv = readlink(path, target, MAXPATHLEN);
+ if (rv == -1) {
+ LOG(("ensure_copy_symlink: failed to read the link: " LOG_S ", err: %d",
+ path, errno));
+ return READ_ERROR;
+ }
+ rv = symlink(target, dest);
+ if (rv == -1) {
+ LOG(("ensure_copy_symlink: failed to create the new link: " LOG_S ", target: " LOG_S " err: %d",
+ dest, target, errno));
+ return READ_ERROR;
+ }
+ return 0;
+}
+#endif
+
+#if MAYBE_USE_HARD_LINKS
+/*
+ * Creates a hardlink (destFilename) which points to the existing file
+ * (srcFilename).
+ *
+ * @return 0 if successful, an error otherwise
+ */
+
+static int
+create_hard_link(const NS_tchar *srcFilename, const NS_tchar *destFilename)
+{
+ if (link(srcFilename, destFilename) < 0) {
+ LOG(("link(%s, %s) failed errno = %d", srcFilename, destFilename, errno));
+ return WRITE_ERROR;
+ }
+ return OK;
+}
+#endif
+
+// Copy the file named path onto a new file named dest.
+static int ensure_copy(const NS_tchar *path, const NS_tchar *dest)
+{
+#ifdef XP_WIN
+ // Fast path for Windows
+ bool result = CopyFileW(path, dest, false);
+ if (!result) {
+ LOG(("ensure_copy: failed to copy the file " LOG_S " over to " LOG_S ", lasterr: %x",
+ path, dest, GetLastError()));
+ return WRITE_ERROR_FILE_COPY;
+ }
+ return OK;
+#else
+ struct NS_tstat_t ss;
+ int rv = NS_tlstat(path, &ss);
+ if (rv) {
+ LOG(("ensure_copy: failed to read file status info: " LOG_S ", err: %d",
+ path, errno));
+ return READ_ERROR;
+ }
+
+#ifdef XP_UNIX
+ if (S_ISLNK(ss.st_mode)) {
+ return ensure_copy_symlink(path, dest);
+ }
+#endif
+
+#if MAYBE_USE_HARD_LINKS
+ if (sUseHardLinks) {
+ if (!create_hard_link(path, dest)) {
+ return OK;
+ }
+ // Since we failed to create the hard link, fall through and copy the file.
+ sUseHardLinks = false;
+ }
+#endif
+
+ AutoFile infile(ensure_open(path, NS_T("rb"), ss.st_mode));
+ if (!infile) {
+ LOG(("ensure_copy: failed to open the file for reading: " LOG_S ", err: %d",
+ path, errno));
+ return READ_ERROR;
+ }
+ AutoFile outfile(ensure_open(dest, NS_T("wb"), ss.st_mode));
+ if (!outfile) {
+ LOG(("ensure_copy: failed to open the file for writing: " LOG_S ", err: %d",
+ dest, errno));
+ return WRITE_ERROR;
+ }
+
+ // This block size was chosen pretty arbitrarily but seems like a reasonable
+ // compromise. For example, the optimal block size on a modern OS X machine
+ // is 100k */
+ const int blockSize = 32 * 1024;
+ void* buffer = malloc(blockSize);
+ if (!buffer)
+ return UPDATER_MEM_ERROR;
+
+ while (!feof(infile.get())) {
+ size_t read = fread(buffer, 1, blockSize, infile);
+ if (ferror(infile.get())) {
+ LOG(("ensure_copy: failed to read the file: " LOG_S ", err: %d",
+ path, errno));
+ free(buffer);
+ return READ_ERROR;
+ }
+
+ size_t written = 0;
+
+ while (written < read) {
+ size_t chunkWritten = fwrite(buffer, 1, read - written, outfile);
+ if (chunkWritten <= 0) {
+ LOG(("ensure_copy: failed to write the file: " LOG_S ", err: %d",
+ dest, errno));
+ free(buffer);
+ return WRITE_ERROR_FILE_COPY;
+ }
+
+ written += chunkWritten;
+ }
+ }
+
+ rv = NS_tchmod(dest, ss.st_mode);
+
+ free(buffer);
+ return rv;
+#endif
+}
+
+template <unsigned N>
+struct copy_recursive_skiplist {
+ NS_tchar paths[N][MAXPATHLEN];
+
+ void append(unsigned index, const NS_tchar *path, const NS_tchar *suffix) {
+ NS_tsnprintf(paths[index], MAXPATHLEN, NS_T("%s/%s"), path, suffix);
+ }
+
+ bool find(const NS_tchar *path) {
+ for (int i = 0; i < static_cast<int>(N); ++i) {
+ if (!NS_tstricmp(paths[i], path)) {
+ return true;
+ }
+ }
+ return false;
+ }
+};
+
+// Copy all of the files and subdirectories under path to a new directory named dest.
+// The path names in the skiplist will be skipped and will not be copied.
+template <unsigned N>
+static int ensure_copy_recursive(const NS_tchar *path, const NS_tchar *dest,
+ copy_recursive_skiplist<N>& skiplist)
+{
+ struct NS_tstat_t sInfo;
+ int rv = NS_tlstat(path, &sInfo);
+ if (rv) {
+ LOG(("ensure_copy_recursive: path doesn't exist: " LOG_S ", rv: %d, err: %d",
+ path, rv, errno));
+ return READ_ERROR;
+ }
+
+#ifdef XP_UNIX
+ if (S_ISLNK(sInfo.st_mode)) {
+ return ensure_copy_symlink(path, dest);
+ }
+#endif
+
+ if (!S_ISDIR(sInfo.st_mode)) {
+ return ensure_copy(path, dest);
+ }
+
+ rv = NS_tmkdir(dest, sInfo.st_mode);
+ if (rv < 0 && errno != EEXIST) {
+ LOG(("ensure_copy_recursive: could not create destination directory: " LOG_S ", rv: %d, err: %d",
+ path, rv, errno));
+ return WRITE_ERROR;
+ }
+
+ NS_tDIR *dir;
+ NS_tdirent *entry;
+
+ dir = NS_topendir(path);
+ if (!dir) {
+ LOG(("ensure_copy_recursive: path is not a directory: " LOG_S ", rv: %d, err: %d",
+ path, rv, errno));
+ return READ_ERROR;
+ }
+
+ while ((entry = NS_treaddir(dir)) != 0) {
+ if (NS_tstrcmp(entry->d_name, NS_T(".")) &&
+ NS_tstrcmp(entry->d_name, NS_T(".."))) {
+ NS_tchar childPath[MAXPATHLEN];
+ NS_tsnprintf(childPath, sizeof(childPath)/sizeof(childPath[0]),
+ NS_T("%s/%s"), path, entry->d_name);
+ if (skiplist.find(childPath)) {
+ continue;
+ }
+ NS_tchar childPathDest[MAXPATHLEN];
+ NS_tsnprintf(childPathDest, sizeof(childPathDest)/sizeof(childPathDest[0]),
+ NS_T("%s/%s"), dest, entry->d_name);
+ rv = ensure_copy_recursive(childPath, childPathDest, skiplist);
+ if (rv) {
+ break;
+ }
+ }
+ }
+ NS_tclosedir(dir);
+ return rv;
+}
+
+// Renames the specified file to the new file specified. If the destination file
+// exists it is removed.
+static int rename_file(const NS_tchar *spath, const NS_tchar *dpath,
+ bool allowDirs = false)
+{
+ int rv = ensure_parent_dir(dpath);
+ if (rv)
+ return rv;
+
+ struct NS_tstat_t spathInfo;
+ rv = NS_tstat(spath, &spathInfo);
+ if (rv) {
+ LOG(("rename_file: failed to read file status info: " LOG_S ", " \
+ "err: %d", spath, errno));
+ return READ_ERROR;
+ }
+
+ if (!S_ISREG(spathInfo.st_mode)) {
+ if (allowDirs && !S_ISDIR(spathInfo.st_mode)) {
+ LOG(("rename_file: path present, but not a file: " LOG_S ", err: %d",
+ spath, errno));
+ return RENAME_ERROR_EXPECTED_FILE;
+ } else {
+ LOG(("rename_file: proceeding to rename the directory"));
+ }
+ }
+
+ if (!NS_taccess(dpath, F_OK)) {
+ if (ensure_remove(dpath)) {
+ LOG(("rename_file: destination file exists and could not be " \
+ "removed: " LOG_S, dpath));
+ return WRITE_ERROR_DELETE_FILE;
+ }
+ }
+
+ if (NS_trename(spath, dpath) != 0) {
+ LOG(("rename_file: failed to rename file - src: " LOG_S ", " \
+ "dst:" LOG_S ", err: %d", spath, dpath, errno));
+ return WRITE_ERROR;
+ }
+
+ return OK;
+}
+
+#ifdef XP_WIN
+// Remove the directory pointed to by path and all of its files and
+// sub-directories. If a file is in use move it to the tobedeleted directory
+// and attempt to schedule removal of the file on reboot
+static int remove_recursive_on_reboot(const NS_tchar *path, const NS_tchar *deleteDir)
+{
+ struct NS_tstat_t sInfo;
+ int rv = NS_tlstat(path, &sInfo);
+ if (rv) {
+ // This error is benign
+ return rv;
+ }
+
+ if (!S_ISDIR(sInfo.st_mode)) {
+ NS_tchar tmpDeleteFile[MAXPATHLEN];
+ GetTempFileNameW(deleteDir, L"rep", 0, tmpDeleteFile);
+ NS_tremove(tmpDeleteFile);
+ rv = rename_file(path, tmpDeleteFile, false);
+ if (MoveFileEx(rv ? path : tmpDeleteFile, nullptr, MOVEFILE_DELAY_UNTIL_REBOOT)) {
+ LOG(("remove_recursive_on_reboot: file will be removed on OS reboot: "
+ LOG_S, rv ? path : tmpDeleteFile));
+ } else {
+ LOG(("remove_recursive_on_reboot: failed to schedule OS reboot removal of "
+ "file: " LOG_S, rv ? path : tmpDeleteFile));
+ }
+ return rv;
+ }
+
+ NS_tDIR *dir;
+ NS_tdirent *entry;
+
+ dir = NS_topendir(path);
+ if (!dir) {
+ LOG(("remove_recursive_on_reboot: unable to open directory: " LOG_S
+ ", rv: %d, err: %d",
+ path, rv, errno));
+ return rv;
+ }
+
+ while ((entry = NS_treaddir(dir)) != 0) {
+ if (NS_tstrcmp(entry->d_name, NS_T(".")) &&
+ NS_tstrcmp(entry->d_name, NS_T(".."))) {
+ NS_tchar childPath[MAXPATHLEN];
+ NS_tsnprintf(childPath, sizeof(childPath)/sizeof(childPath[0]),
+ NS_T("%s/%s"), path, entry->d_name);
+ // There is no need to check the return value of this call since this
+ // function is only called after an update is successful and there is not
+ // much that can be done to recover if it isn't successful. There is also
+ // no need to log the value since it will have already been logged.
+ remove_recursive_on_reboot(childPath, deleteDir);
+ }
+ }
+
+ NS_tclosedir(dir);
+
+ if (rv == OK) {
+ ensure_write_permissions(path);
+ rv = NS_trmdir(path);
+ if (rv) {
+ LOG(("remove_recursive_on_reboot: unable to remove directory: " LOG_S
+ ", rv: %d, err: %d", path, rv, errno));
+ }
+ }
+ return rv;
+}
+#endif
+
+//-----------------------------------------------------------------------------
+
+// Create a backup of the specified file by renaming it.
+static int backup_create(const NS_tchar *path)
+{
+ NS_tchar backup[MAXPATHLEN];
+ NS_tsnprintf(backup, sizeof(backup)/sizeof(backup[0]),
+ NS_T("%s") BACKUP_EXT, path);
+
+ return rename_file(path, backup);
+}
+
+// Rename the backup of the specified file that was created by renaming it back
+// to the original file.
+static int backup_restore(const NS_tchar *path, const NS_tchar *relPath)
+{
+ NS_tchar backup[MAXPATHLEN];
+ NS_tsnprintf(backup, sizeof(backup)/sizeof(backup[0]),
+ NS_T("%s") BACKUP_EXT, path);
+
+ NS_tchar relBackup[MAXPATHLEN];
+ NS_tsnprintf(relBackup, sizeof(relBackup) / sizeof(relBackup[0]),
+ NS_T("%s") BACKUP_EXT, relPath);
+
+ if (NS_taccess(backup, F_OK)) {
+ LOG(("backup_restore: backup file doesn't exist: " LOG_S, relBackup));
+ return OK;
+ }
+
+ return rename_file(backup, path);
+}
+
+// Discard the backup of the specified file that was created by renaming it.
+static int backup_discard(const NS_tchar *path, const NS_tchar *relPath)
+{
+ NS_tchar backup[MAXPATHLEN];
+ NS_tsnprintf(backup, sizeof(backup)/sizeof(backup[0]),
+ NS_T("%s") BACKUP_EXT, path);
+
+ NS_tchar relBackup[MAXPATHLEN];
+ NS_tsnprintf(relBackup, sizeof(relBackup) / sizeof(relBackup[0]),
+ NS_T("%s") BACKUP_EXT, relPath);
+
+ // Nothing to discard
+ if (NS_taccess(backup, F_OK)) {
+ return OK;
+ }
+
+ int rv = ensure_remove(backup);
+#if defined(XP_WIN)
+ if (rv && !sStagedUpdate && !sReplaceRequest) {
+ LOG(("backup_discard: unable to remove: " LOG_S, relBackup));
+ NS_tchar path[MAXPATHLEN];
+ GetTempFileNameW(gDeleteDirPath, L"moz", 0, path);
+ if (rename_file(backup, path)) {
+ LOG(("backup_discard: failed to rename file:" LOG_S ", dst:" LOG_S,
+ relBackup, relPath));
+ return WRITE_ERROR_DELETE_BACKUP;
+ }
+ // The MoveFileEx call to remove the file on OS reboot will fail if the
+ // process doesn't have write access to the HKEY_LOCAL_MACHINE registry key
+ // but this is ok since the installer / uninstaller will delete the
+ // directory containing the file along with its contents after an update is
+ // applied, on reinstall, and on uninstall.
+ if (MoveFileEx(path, nullptr, MOVEFILE_DELAY_UNTIL_REBOOT)) {
+ LOG(("backup_discard: file renamed and will be removed on OS " \
+ "reboot: " LOG_S, relPath));
+ } else {
+ LOG(("backup_discard: failed to schedule OS reboot removal of " \
+ "file: " LOG_S, relPath));
+ }
+ }
+#else
+ if (rv)
+ return WRITE_ERROR_DELETE_BACKUP;
+#endif
+
+ return OK;
+}
+
+// Helper function for post-processing a temporary backup.
+static void backup_finish(const NS_tchar *path, const NS_tchar *relPath,
+ int status)
+{
+ if (status == OK)
+ backup_discard(path, relPath);
+ else
+ backup_restore(path, relPath);
+}
+
+//-----------------------------------------------------------------------------
+
+static int DoUpdate();
+
+class Action
+{
+public:
+ Action() : mProgressCost(1), mNext(nullptr) { }
+ virtual ~Action() { }
+
+ virtual int Parse(NS_tchar *line) = 0;
+
+ // Do any preprocessing to ensure that the action can be performed. Execute
+ // will be called if this Action and all others return OK from this method.
+ virtual int Prepare() = 0;
+
+ // Perform the operation. Return OK to indicate success. After all actions
+ // have been executed, Finish will be called. A requirement of Execute is
+ // that its operation be reversable from Finish.
+ virtual int Execute() = 0;
+
+ // Finish is called after execution of all actions. If status is OK, then
+ // all actions were successfully executed. Otherwise, some action failed.
+ virtual void Finish(int status) = 0;
+
+ int mProgressCost;
+private:
+ Action* mNext;
+
+ friend class ActionList;
+};
+
+class RemoveFile : public Action
+{
+public:
+ RemoveFile() : mSkip(0) { }
+
+ int Parse(NS_tchar *line);
+ int Prepare();
+ int Execute();
+ void Finish(int status);
+
+private:
+ mozilla::UniquePtr<NS_tchar[]> mFile;
+ mozilla::UniquePtr<NS_tchar[]> mRelPath;
+ int mSkip;
+};
+
+int
+RemoveFile::Parse(NS_tchar *line)
+{
+ // format "<deadfile>"
+
+ NS_tchar * validPath = get_valid_path(&line);
+ if (!validPath) {
+ return PARSE_ERROR;
+ }
+
+ mRelPath = mozilla::MakeUnique<NS_tchar[]>(MAXPATHLEN);
+ NS_tstrcpy(mRelPath.get(), validPath);
+
+ mFile.reset(get_full_path(validPath));
+ if (!mFile) {
+ return PARSE_ERROR;
+ }
+
+ return OK;
+}
+
+int
+RemoveFile::Prepare()
+{
+ // Skip the file if it already doesn't exist.
+ int rv = NS_taccess(mFile.get(), F_OK);
+ if (rv) {
+ mSkip = 1;
+ mProgressCost = 0;
+ return OK;
+ }
+
+ LOG(("PREPARE REMOVEFILE " LOG_S, mRelPath.get()));
+
+ // Make sure that we're actually a file...
+ struct NS_tstat_t fileInfo;
+ rv = NS_tstat(mFile.get(), &fileInfo);
+ if (rv) {
+ LOG(("failed to read file status info: " LOG_S ", err: %d", mFile.get(),
+ errno));
+ return READ_ERROR;
+ }
+
+ if (!S_ISREG(fileInfo.st_mode)) {
+ LOG(("path present, but not a file: " LOG_S, mFile.get()));
+ return DELETE_ERROR_EXPECTED_FILE;
+ }
+
+ NS_tchar *slash = (NS_tchar *) NS_tstrrchr(mFile.get(), NS_T('/'));
+ if (slash) {
+ *slash = NS_T('\0');
+ rv = NS_taccess(mFile.get(), W_OK);
+ *slash = NS_T('/');
+ } else {
+ rv = NS_taccess(NS_T("."), W_OK);
+ }
+
+ if (rv) {
+ LOG(("access failed: %d", errno));
+ return WRITE_ERROR_FILE_ACCESS_DENIED;
+ }
+
+ return OK;
+}
+
+int
+RemoveFile::Execute()
+{
+ if (mSkip)
+ return OK;
+
+ LOG(("EXECUTE REMOVEFILE " LOG_S, mRelPath.get()));
+
+ // The file is checked for existence here and in Prepare since it might have
+ // been removed by a separate instruction: bug 311099.
+ int rv = NS_taccess(mFile.get(), F_OK);
+ if (rv) {
+ LOG(("file cannot be removed because it does not exist; skipping"));
+ mSkip = 1;
+ return OK;
+ }
+
+ if (sStagedUpdate) {
+ // Staged updates don't need backup files so just remove it.
+ rv = ensure_remove(mFile.get());
+ if (rv) {
+ return rv;
+ }
+ } else {
+ // Rename the old file. It will be removed in Finish.
+ rv = backup_create(mFile.get());
+ if (rv) {
+ LOG(("backup_create failed: %d", rv));
+ return rv;
+ }
+ }
+
+ return OK;
+}
+
+void
+RemoveFile::Finish(int status)
+{
+ if (mSkip) {
+ return;
+ }
+
+ LOG(("FINISH REMOVEFILE " LOG_S, mRelPath.get()));
+
+ // Staged updates don't create backup files.
+ if (!sStagedUpdate) {
+ backup_finish(mFile.get(), mRelPath.get(), status);
+ }
+}
+
+class RemoveDir : public Action
+{
+public:
+ RemoveDir() : mSkip(0) { }
+
+ virtual int Parse(NS_tchar *line);
+ virtual int Prepare(); // check that the source dir exists
+ virtual int Execute();
+ virtual void Finish(int status);
+
+private:
+ mozilla::UniquePtr<NS_tchar[]> mDir;
+ mozilla::UniquePtr<NS_tchar[]> mRelPath;
+ int mSkip;
+};
+
+int
+RemoveDir::Parse(NS_tchar *line)
+{
+ // format "<deaddir>/"
+
+ NS_tchar * validPath = get_valid_path(&line, true);
+ if (!validPath) {
+ return PARSE_ERROR;
+ }
+
+ mRelPath = mozilla::MakeUnique<NS_tchar[]>(MAXPATHLEN);
+ NS_tstrcpy(mRelPath.get(), validPath);
+
+ mDir.reset(get_full_path(validPath));
+ if (!mDir) {
+ return PARSE_ERROR;
+ }
+
+ return OK;
+}
+
+int
+RemoveDir::Prepare()
+{
+ // We expect the directory to exist if we are to remove it.
+ int rv = NS_taccess(mDir.get(), F_OK);
+ if (rv) {
+ mSkip = 1;
+ mProgressCost = 0;
+ return OK;
+ }
+
+ LOG(("PREPARE REMOVEDIR " LOG_S "/", mRelPath.get()));
+
+ // Make sure that we're actually a dir.
+ struct NS_tstat_t dirInfo;
+ rv = NS_tstat(mDir.get(), &dirInfo);
+ if (rv) {
+ LOG(("failed to read directory status info: " LOG_S ", err: %d", mRelPath.get(),
+ errno));
+ return READ_ERROR;
+ }
+
+ if (!S_ISDIR(dirInfo.st_mode)) {
+ LOG(("path present, but not a directory: " LOG_S, mRelPath.get()));
+ return DELETE_ERROR_EXPECTED_DIR;
+ }
+
+ rv = NS_taccess(mDir.get(), W_OK);
+ if (rv) {
+ LOG(("access failed: %d, %d", rv, errno));
+ return WRITE_ERROR_DIR_ACCESS_DENIED;
+ }
+
+ return OK;
+}
+
+int
+RemoveDir::Execute()
+{
+ if (mSkip)
+ return OK;
+
+ LOG(("EXECUTE REMOVEDIR " LOG_S "/", mRelPath.get()));
+
+ // The directory is checked for existence at every step since it might have
+ // been removed by a separate instruction: bug 311099.
+ int rv = NS_taccess(mDir.get(), F_OK);
+ if (rv) {
+ LOG(("directory no longer exists; skipping"));
+ mSkip = 1;
+ }
+
+ return OK;
+}
+
+void
+RemoveDir::Finish(int status)
+{
+ if (mSkip || status != OK)
+ return;
+
+ LOG(("FINISH REMOVEDIR " LOG_S "/", mRelPath.get()));
+
+ // The directory is checked for existence at every step since it might have
+ // been removed by a separate instruction: bug 311099.
+ int rv = NS_taccess(mDir.get(), F_OK);
+ if (rv) {
+ LOG(("directory no longer exists; skipping"));
+ return;
+ }
+
+
+ if (status == OK) {
+ if (NS_trmdir(mDir.get())) {
+ LOG(("non-fatal error removing directory: " LOG_S "/, rv: %d, err: %d",
+ mRelPath.get(), rv, errno));
+ }
+ }
+}
+
+class AddFile : public Action
+{
+public:
+ AddFile() : mAdded(false) { }
+
+ virtual int Parse(NS_tchar *line);
+ virtual int Prepare();
+ virtual int Execute();
+ virtual void Finish(int status);
+
+private:
+ mozilla::UniquePtr<NS_tchar[]> mFile;
+ mozilla::UniquePtr<NS_tchar[]> mRelPath;
+ bool mAdded;
+};
+
+int
+AddFile::Parse(NS_tchar *line)
+{
+ // format "<newfile>"
+
+ NS_tchar * validPath = get_valid_path(&line);
+ if (!validPath) {
+ return PARSE_ERROR;
+ }
+
+ mRelPath = mozilla::MakeUnique<NS_tchar[]>(MAXPATHLEN);
+ NS_tstrcpy(mRelPath.get(), validPath);
+
+ mFile.reset(get_full_path(validPath));
+ if (!mFile) {
+ return PARSE_ERROR;
+ }
+
+ return OK;
+}
+
+int
+AddFile::Prepare()
+{
+ LOG(("PREPARE ADD " LOG_S, mRelPath.get()));
+
+ return OK;
+}
+
+int
+AddFile::Execute()
+{
+ LOG(("EXECUTE ADD " LOG_S, mRelPath.get()));
+
+ int rv;
+
+ // First make sure that we can actually get rid of any existing file.
+ rv = NS_taccess(mFile.get(), F_OK);
+ if (rv == 0) {
+ if (sStagedUpdate) {
+ // Staged updates don't need backup files so just remove it.
+ rv = ensure_remove(mFile.get());
+ } else {
+ rv = backup_create(mFile.get());
+ }
+ if (rv) {
+ return rv;
+ }
+ } else {
+ rv = ensure_parent_dir(mFile.get());
+ if (rv)
+ return rv;
+ }
+
+#ifdef XP_WIN
+ char sourcefile[MAXPATHLEN];
+ if (!WideCharToMultiByte(CP_UTF8, 0, mRelPath.get(), -1, sourcefile,
+ MAXPATHLEN, nullptr, nullptr)) {
+ LOG(("error converting wchar to utf8: %d", GetLastError()));
+ return STRING_CONVERSION_ERROR;
+ }
+
+ rv = gArchiveReader.ExtractFile(sourcefile, mFile.get());
+#else
+ rv = gArchiveReader.ExtractFile(mRelPath.get(), mFile.get());
+#endif
+ if (!rv) {
+ mAdded = true;
+ }
+ return rv;
+}
+
+void
+AddFile::Finish(int status)
+{
+ LOG(("FINISH ADD " LOG_S, mRelPath.get()));
+ // Staged updates don't create backup files.
+ if (!sStagedUpdate) {
+ // When there is an update failure and a file has been added it is removed
+ // here since there might not be a backup to replace it.
+ if (status && mAdded) {
+ NS_tremove(mFile.get());
+ }
+ backup_finish(mFile.get(), mRelPath.get(), status);
+ }
+}
+
+class PatchFile : public Action
+{
+public:
+ PatchFile() : mPatchFile(nullptr), mPatchIndex(-1), buf(nullptr) { }
+
+ virtual ~PatchFile();
+
+ virtual int Parse(NS_tchar *line);
+ virtual int Prepare(); // should check for patch file and for checksum here
+ virtual int Execute();
+ virtual void Finish(int status);
+
+private:
+ int LoadSourceFile(FILE* ofile);
+
+ static int sPatchIndex;
+
+ const NS_tchar *mPatchFile;
+ mozilla::UniquePtr<NS_tchar[]> mFile;
+ mozilla::UniquePtr<NS_tchar[]> mFileRelPath;
+ int mPatchIndex;
+ MBSPatchHeader header;
+ unsigned char *buf;
+ NS_tchar spath[MAXPATHLEN];
+ AutoFile mPatchStream;
+};
+
+int PatchFile::sPatchIndex = 0;
+
+PatchFile::~PatchFile()
+{
+ // Make sure mPatchStream gets unlocked on Windows; the system will do that,
+ // but not until some indeterminate future time, and we want determinism.
+ // Normally this happens at the end of Execute, when we close the stream;
+ // this call is here in case Execute errors out.
+#ifdef XP_WIN
+ if (mPatchStream) {
+ UnlockFile((HANDLE)_get_osfhandle(fileno(mPatchStream)), 0, 0, -1, -1);
+ }
+#endif
+
+ // delete the temporary patch file
+ if (spath[0]) {
+ NS_tremove(spath);
+ }
+
+ if (buf) {
+ free(buf);
+ }
+}
+
+int
+PatchFile::LoadSourceFile(FILE* ofile)
+{
+ struct stat os;
+ int rv = fstat(fileno((FILE *)ofile), &os);
+ if (rv) {
+ LOG(("LoadSourceFile: unable to stat destination file: " LOG_S ", " \
+ "err: %d", mFileRelPath.get(), errno));
+ return READ_ERROR;
+ }
+
+ if (uint32_t(os.st_size) != header.slen) {
+ LOG(("LoadSourceFile: destination file size %d does not match expected size %d",
+ uint32_t(os.st_size), header.slen));
+ return LOADSOURCE_ERROR_WRONG_SIZE;
+ }
+
+ buf = (unsigned char *) malloc(header.slen);
+ if (!buf) {
+ return UPDATER_MEM_ERROR;
+ }
+
+ size_t r = header.slen;
+ unsigned char *rb = buf;
+ while (r) {
+ const size_t count = mmin(SSIZE_MAX, r);
+ size_t c = fread(rb, 1, count, ofile);
+ if (c != count) {
+ LOG(("LoadSourceFile: error reading destination file: " LOG_S,
+ mFileRelPath.get()));
+ return READ_ERROR;
+ }
+
+ r -= c;
+ rb += c;
+ }
+
+ // Verify that the contents of the source file correspond to what we expect.
+
+ unsigned int crc = crc32(buf, header.slen);
+
+ if (crc != header.scrc32) {
+ LOG(("LoadSourceFile: destination file crc %d does not match expected " \
+ "crc %d", crc, header.scrc32));
+ return CRC_ERROR;
+ }
+
+ return OK;
+}
+
+int
+PatchFile::Parse(NS_tchar *line)
+{
+ // format "<patchfile>" "<filetopatch>"
+
+ // Get the path to the patch file inside of the mar
+ mPatchFile = mstrtok(kQuote, &line);
+ if (!mPatchFile) {
+ return PARSE_ERROR;
+ }
+
+ // consume whitespace between args
+ NS_tchar *q = mstrtok(kQuote, &line);
+ if (!q) {
+ return PARSE_ERROR;
+ }
+
+ NS_tchar * validPath = get_valid_path(&line);
+ if (!validPath) {
+ return PARSE_ERROR;
+ }
+
+ mFileRelPath = mozilla::MakeUnique<NS_tchar[]>(MAXPATHLEN);
+ NS_tstrcpy(mFileRelPath.get(), validPath);
+
+ mFile.reset(get_full_path(validPath));
+ if (!mFile) {
+ return PARSE_ERROR;
+ }
+
+ return OK;
+}
+
+int
+PatchFile::Prepare()
+{
+ LOG(("PREPARE PATCH " LOG_S, mFileRelPath.get()));
+
+ // extract the patch to a temporary file
+ mPatchIndex = sPatchIndex++;
+
+ NS_tsnprintf(spath, sizeof(spath)/sizeof(spath[0]),
+ NS_T("%s/updating/%d.patch"), gWorkingDirPath, mPatchIndex);
+
+ NS_tremove(spath);
+
+ mPatchStream = NS_tfopen(spath, NS_T("wb+"));
+ if (!mPatchStream) {
+ return WRITE_ERROR;
+ }
+
+#ifdef XP_WIN
+ // Lock the patch file, so it can't be messed with between
+ // when we're done creating it and when we go to apply it.
+ if (!LockFile((HANDLE)_get_osfhandle(fileno(mPatchStream)), 0, 0, -1, -1)) {
+ LOG(("Couldn't lock patch file: %d", GetLastError()));
+ return LOCK_ERROR_PATCH_FILE;
+ }
+
+ char sourcefile[MAXPATHLEN];
+ if (!WideCharToMultiByte(CP_UTF8, 0, mPatchFile, -1, sourcefile, MAXPATHLEN,
+ nullptr, nullptr)) {
+ LOG(("error converting wchar to utf8: %d", GetLastError()));
+ return STRING_CONVERSION_ERROR;
+ }
+
+ int rv = gArchiveReader.ExtractFileToStream(sourcefile, mPatchStream);
+#else
+ int rv = gArchiveReader.ExtractFileToStream(mPatchFile, mPatchStream);
+#endif
+
+ return rv;
+}
+
+int
+PatchFile::Execute()
+{
+ LOG(("EXECUTE PATCH " LOG_S, mFileRelPath.get()));
+
+ fseek(mPatchStream, 0, SEEK_SET);
+
+ int rv = MBS_ReadHeader(mPatchStream, &header);
+ if (rv) {
+ return rv;
+ }
+
+ FILE *origfile = nullptr;
+#ifdef XP_WIN
+ if (NS_tstrcmp(mFileRelPath.get(), gCallbackRelPath) == 0) {
+ // Read from the copy of the callback when patching since the callback can't
+ // be opened for reading to prevent the application from being launched.
+ origfile = NS_tfopen(gCallbackBackupPath, NS_T("rb"));
+ } else {
+ origfile = NS_tfopen(mFile.get(), NS_T("rb"));
+ }
+#else
+ origfile = NS_tfopen(mFile.get(), NS_T("rb"));
+#endif
+
+ if (!origfile) {
+ LOG(("unable to open destination file: " LOG_S ", err: %d",
+ mFileRelPath.get(), errno));
+ return READ_ERROR;
+ }
+
+ rv = LoadSourceFile(origfile);
+ fclose(origfile);
+ if (rv) {
+ LOG(("LoadSourceFile failed"));
+ return rv;
+ }
+
+ // Rename the destination file if it exists before proceeding so it can be
+ // used to restore the file to its original state if there is an error.
+ struct NS_tstat_t ss;
+ rv = NS_tstat(mFile.get(), &ss);
+ if (rv) {
+ LOG(("failed to read file status info: " LOG_S ", err: %d",
+ mFileRelPath.get(), errno));
+ return READ_ERROR;
+ }
+
+ // Staged updates don't need backup files.
+ if (!sStagedUpdate) {
+ rv = backup_create(mFile.get());
+ if (rv) {
+ return rv;
+ }
+ }
+
+#if defined(HAVE_POSIX_FALLOCATE)
+ AutoFile ofile(ensure_open(mFile.get(), NS_T("wb+"), ss.st_mode));
+ posix_fallocate(fileno((FILE *)ofile), 0, header.dlen);
+#elif defined(XP_WIN)
+ bool shouldTruncate = true;
+ // Creating the file, setting the size, and then closing the file handle
+ // lessens fragmentation more than any other method tested. Other methods that
+ // have been tested are:
+ // 1. _chsize / _chsize_s reduced fragmentation though not completely.
+ // 2. _get_osfhandle and then setting the size reduced fragmentation though
+ // not completely. There are also reports of _get_osfhandle failing on
+ // mingw.
+ HANDLE hfile = CreateFileW(mFile.get(),
+ GENERIC_WRITE,
+ 0,
+ nullptr,
+ CREATE_ALWAYS,
+ FILE_ATTRIBUTE_NORMAL,
+ nullptr);
+
+ if (hfile != INVALID_HANDLE_VALUE) {
+ if (SetFilePointer(hfile, header.dlen,
+ nullptr, FILE_BEGIN) != INVALID_SET_FILE_POINTER &&
+ SetEndOfFile(hfile) != 0) {
+ shouldTruncate = false;
+ }
+ CloseHandle(hfile);
+ }
+
+ AutoFile ofile(ensure_open(mFile.get(), shouldTruncate ? NS_T("wb+") : NS_T("rb+"),
+ ss.st_mode));
+#elif defined(XP_MACOSX)
+ AutoFile ofile(ensure_open(mFile.get(), NS_T("wb+"), ss.st_mode));
+ // Modified code from FileUtils.cpp
+ fstore_t store = {F_ALLOCATECONTIG, F_PEOFPOSMODE, 0, header.dlen};
+ // Try to get a continous chunk of disk space
+ rv = fcntl(fileno((FILE *)ofile), F_PREALLOCATE, &store);
+ if (rv == -1) {
+ // OK, perhaps we are too fragmented, allocate non-continuous
+ store.fst_flags = F_ALLOCATEALL;
+ rv = fcntl(fileno((FILE *)ofile), F_PREALLOCATE, &store);
+ }
+
+ if (rv != -1) {
+ ftruncate(fileno((FILE *)ofile), header.dlen);
+ }
+#else
+ AutoFile ofile(ensure_open(mFile.get(), NS_T("wb+"), ss.st_mode));
+#endif
+
+ if (ofile == nullptr) {
+ LOG(("unable to create new file: " LOG_S ", err: %d", mFileRelPath.get(),
+ errno));
+ return WRITE_ERROR_OPEN_PATCH_FILE;
+ }
+
+#ifdef XP_WIN
+ if (!shouldTruncate) {
+ fseek(ofile, 0, SEEK_SET);
+ }
+#endif
+
+ rv = MBS_ApplyPatch(&header, mPatchStream, buf, ofile);
+
+ // Go ahead and do a bit of cleanup now to minimize runtime overhead.
+ // Make sure mPatchStream gets unlocked on Windows; the system will do that,
+ // but not until some indeterminate future time, and we want determinism.
+#ifdef XP_WIN
+ UnlockFile((HANDLE)_get_osfhandle(fileno(mPatchStream)), 0, 0, -1, -1);
+#endif
+ // Set mPatchStream to nullptr to make AutoFile close the file,
+ // so it can be deleted on Windows.
+ mPatchStream = nullptr;
+ NS_tremove(spath);
+ spath[0] = NS_T('\0');
+ free(buf);
+ buf = nullptr;
+
+ return rv;
+}
+
+void
+PatchFile::Finish(int status)
+{
+ LOG(("FINISH PATCH " LOG_S, mFileRelPath.get()));
+
+ // Staged updates don't create backup files.
+ if (!sStagedUpdate) {
+ backup_finish(mFile.get(), mFileRelPath.get(), status);
+ }
+}
+
+class AddIfFile : public AddFile
+{
+public:
+ virtual int Parse(NS_tchar *line);
+ virtual int Prepare();
+ virtual int Execute();
+ virtual void Finish(int status);
+
+protected:
+ mozilla::UniquePtr<NS_tchar[]> mTestFile;
+};
+
+int
+AddIfFile::Parse(NS_tchar *line)
+{
+ // format "<testfile>" "<newfile>"
+
+ mTestFile.reset(get_full_path(get_valid_path(&line)));
+ if (!mTestFile) {
+ return PARSE_ERROR;
+ }
+
+ // consume whitespace between args
+ NS_tchar *q = mstrtok(kQuote, &line);
+ if (!q) {
+ return PARSE_ERROR;
+ }
+
+ return AddFile::Parse(line);
+}
+
+int
+AddIfFile::Prepare()
+{
+ // If the test file does not exist, then skip this action.
+ if (NS_taccess(mTestFile.get(), F_OK)) {
+ mTestFile = nullptr;
+ return OK;
+ }
+
+ return AddFile::Prepare();
+}
+
+int
+AddIfFile::Execute()
+{
+ if (!mTestFile)
+ return OK;
+
+ return AddFile::Execute();
+}
+
+void
+AddIfFile::Finish(int status)
+{
+ if (!mTestFile)
+ return;
+
+ AddFile::Finish(status);
+}
+
+class AddIfNotFile : public AddFile
+{
+public:
+ virtual int Parse(NS_tchar *line);
+ virtual int Prepare();
+ virtual int Execute();
+ virtual void Finish(int status);
+
+protected:
+ mozilla::UniquePtr<NS_tchar[]> mTestFile;
+};
+
+int
+AddIfNotFile::Parse(NS_tchar *line)
+{
+ // format "<testfile>" "<newfile>"
+
+ mTestFile.reset(get_full_path(get_valid_path(&line)));
+ if (!mTestFile) {
+ return PARSE_ERROR;
+ }
+
+ // consume whitespace between args
+ NS_tchar *q = mstrtok(kQuote, &line);
+ if (!q) {
+ return PARSE_ERROR;
+ }
+
+ return AddFile::Parse(line);
+}
+
+int
+AddIfNotFile::Prepare()
+{
+ // If the test file exists, then skip this action.
+ if (!NS_taccess(mTestFile.get(), F_OK)) {
+ mTestFile = NULL;
+ return OK;
+ }
+
+ return AddFile::Prepare();
+}
+
+int
+AddIfNotFile::Execute()
+{
+ if (!mTestFile)
+ return OK;
+
+ return AddFile::Execute();
+}
+
+void
+AddIfNotFile::Finish(int status)
+{
+ if (!mTestFile)
+ return;
+
+ AddFile::Finish(status);
+}
+
+class PatchIfFile : public PatchFile
+{
+public:
+ virtual int Parse(NS_tchar *line);
+ virtual int Prepare(); // should check for patch file and for checksum here
+ virtual int Execute();
+ virtual void Finish(int status);
+
+private:
+ mozilla::UniquePtr<NS_tchar[]> mTestFile;
+};
+
+int
+PatchIfFile::Parse(NS_tchar *line)
+{
+ // format "<testfile>" "<patchfile>" "<filetopatch>"
+
+ mTestFile.reset(get_full_path(get_valid_path(&line)));
+ if (!mTestFile) {
+ return PARSE_ERROR;
+ }
+
+ // consume whitespace between args
+ NS_tchar *q = mstrtok(kQuote, &line);
+ if (!q) {
+ return PARSE_ERROR;
+ }
+
+ return PatchFile::Parse(line);
+}
+
+int
+PatchIfFile::Prepare()
+{
+ // If the test file does not exist, then skip this action.
+ if (NS_taccess(mTestFile.get(), F_OK)) {
+ mTestFile = nullptr;
+ return OK;
+ }
+
+ return PatchFile::Prepare();
+}
+
+int
+PatchIfFile::Execute()
+{
+ if (!mTestFile)
+ return OK;
+
+ return PatchFile::Execute();
+}
+
+void
+PatchIfFile::Finish(int status)
+{
+ if (!mTestFile)
+ return;
+
+ PatchFile::Finish(status);
+}
+
+//-----------------------------------------------------------------------------
+
+#ifdef XP_WIN
+#include "nsWindowsRestart.cpp"
+#include "nsWindowsHelpers.h"
+#include "uachelper.h"
+#include "pathhash.h"
+
+/**
+ * Launch the post update application (helper.exe). It takes in the path of the
+ * callback application to calculate the path of helper.exe. For service updates
+ * this is called from both the system account and the current user account.
+ *
+ * @param installationDir The path to the callback application binary.
+ * @param updateInfoDir The directory where update info is stored.
+ * @return true if there was no error starting the process.
+ */
+bool
+LaunchWinPostProcess(const WCHAR *installationDir,
+ const WCHAR *updateInfoDir)
+{
+ WCHAR workingDirectory[MAX_PATH + 1] = { L'\0' };
+ wcsncpy(workingDirectory, installationDir, MAX_PATH);
+
+ // Launch helper.exe to perform post processing (e.g. registry and log file
+ // modifications) for the update.
+ WCHAR inifile[MAX_PATH + 1] = { L'\0' };
+ wcsncpy(inifile, installationDir, MAX_PATH);
+ if (!PathAppendSafe(inifile, L"updater.ini")) {
+ return false;
+ }
+
+ WCHAR exefile[MAX_PATH + 1];
+ WCHAR exearg[MAX_PATH + 1];
+ WCHAR exeasync[10];
+ bool async = true;
+ if (!GetPrivateProfileStringW(L"PostUpdateWin", L"ExeRelPath", nullptr,
+ exefile, MAX_PATH + 1, inifile)) {
+ return false;
+ }
+
+ if (!GetPrivateProfileStringW(L"PostUpdateWin", L"ExeArg", nullptr, exearg,
+ MAX_PATH + 1, inifile)) {
+ return false;
+ }
+
+ if (!GetPrivateProfileStringW(L"PostUpdateWin", L"ExeAsync", L"TRUE",
+ exeasync,
+ sizeof(exeasync)/sizeof(exeasync[0]),
+ inifile)) {
+ return false;
+ }
+
+ // The relative path must not contain directory traversals, current directory,
+ // or colons.
+ if (wcsstr(exefile, L"..") != nullptr ||
+ wcsstr(exefile, L"./") != nullptr ||
+ wcsstr(exefile, L".\\") != nullptr ||
+ wcsstr(exefile, L":") != nullptr) {
+ return false;
+ }
+
+ // The relative path must not start with a decimal point, backslash, or
+ // forward slash.
+ if (exefile[0] == L'.' ||
+ exefile[0] == L'\\' ||
+ exefile[0] == L'/') {
+ return false;
+ }
+
+ WCHAR exefullpath[MAX_PATH + 1] = { L'\0' };
+ wcsncpy(exefullpath, installationDir, MAX_PATH);
+ if (!PathAppendSafe(exefullpath, exefile)) {
+ return false;
+ }
+
+ if (!IsValidFullPath(exefullpath)) {
+ return false;
+ }
+
+#if !defined(TEST_UPDATER) && defined(MOZ_MAINTENANCE_SERVICE)
+ if (sUsingService &&
+ !DoesBinaryMatchAllowedCertificates(installationDir, exefullpath)) {
+ return false;
+ }
+#endif
+
+ WCHAR dlogFile[MAX_PATH + 1];
+ if (!PathGetSiblingFilePath(dlogFile, exefullpath, L"uninstall.update")) {
+ return false;
+ }
+
+ WCHAR slogFile[MAX_PATH + 1] = { L'\0' };
+ wcsncpy(slogFile, updateInfoDir, MAX_PATH);
+ if (!PathAppendSafe(slogFile, L"update.log")) {
+ return false;
+ }
+
+ WCHAR dummyArg[14] = { L'\0' };
+ wcsncpy(dummyArg, L"argv0ignored ", sizeof(dummyArg) / sizeof(dummyArg[0]) - 1);
+
+ size_t len = wcslen(exearg) + wcslen(dummyArg);
+ WCHAR *cmdline = (WCHAR *) malloc((len + 1) * sizeof(WCHAR));
+ if (!cmdline) {
+ return false;
+ }
+
+ wcsncpy(cmdline, dummyArg, len);
+ wcscat(cmdline, exearg);
+
+ if (sUsingService ||
+ !_wcsnicmp(exeasync, L"false", 6) ||
+ !_wcsnicmp(exeasync, L"0", 2)) {
+ async = false;
+ }
+
+ // We want to launch the post update helper app to update the Windows
+ // registry even if there is a failure with removing the uninstall.update
+ // file or copying the update.log file.
+ CopyFileW(slogFile, dlogFile, false);
+
+ STARTUPINFOW si = {sizeof(si), 0};
+ si.lpDesktop = L"";
+ PROCESS_INFORMATION pi = {0};
+
+ bool ok = CreateProcessW(exefullpath,
+ cmdline,
+ nullptr, // no special security attributes
+ nullptr, // no special thread attributes
+ false, // don't inherit filehandles
+ 0, // No special process creation flags
+ nullptr, // inherit my environment
+ workingDirectory,
+ &si,
+ &pi);
+ free(cmdline);
+ if (ok) {
+ if (!async) {
+ WaitForSingleObject(pi.hProcess, INFINITE);
+ }
+ CloseHandle(pi.hProcess);
+ CloseHandle(pi.hThread);
+ }
+ return ok;
+}
+
+#endif
+
+static void
+LaunchCallbackApp(const NS_tchar *workingDir,
+ int argc,
+ NS_tchar **argv,
+ bool usingService)
+{
+ putenv(const_cast<char*>("NO_EM_RESTART="));
+ putenv(const_cast<char*>("MOZ_LAUNCHED_CHILD=1"));
+
+ // Run from the specified working directory (see bug 312360). This is not
+ // necessary on Windows CE since the application that launches the updater
+ // passes the working directory as an --environ: command line argument.
+ if (NS_tchdir(workingDir) != 0) {
+ LOG(("Warning: chdir failed"));
+ }
+
+#if defined(USE_EXECV)
+ execv(argv[0], argv);
+#elif defined(XP_MACOSX)
+ LaunchChild(argc, (const char**)argv);
+#elif defined(XP_WIN)
+ // Do not allow the callback to run when running an update through the
+ // service as session 0. The unelevated updater.exe will do the launching.
+ if (!usingService) {
+ WinLaunchChild(argv[0], argc, argv, nullptr);
+ }
+#else
+# warning "Need implementaton of LaunchCallbackApp"
+#endif
+}
+
+static bool
+WriteStatusFile(const char* aStatus)
+{
+ NS_tchar filename[MAXPATHLEN] = {NS_T('\0')};
+#if defined(XP_WIN)
+ // The temp file is not removed on failure since there is client code that
+ // will remove it.
+ if (GetTempFileNameW(gPatchDirPath, L"sta", 0, filename) == 0) {
+ return false;
+ }
+#else
+ NS_tsnprintf(filename, sizeof(filename)/sizeof(filename[0]),
+ NS_T("%s/update.status"), gPatchDirPath);
+#endif
+
+ // Make sure that the directory for the update status file exists
+ if (ensure_parent_dir(filename)) {
+ return false;
+ }
+
+ // This is scoped to make the AutoFile close the file so it is possible to
+ // move the temp file to the update.status file on Windows.
+ {
+ AutoFile file(NS_tfopen(filename, NS_T("wb+")));
+ if (file == nullptr) {
+ return false;
+ }
+
+ if (fwrite(aStatus, strlen(aStatus), 1, file) != 1) {
+ return false;
+ }
+ }
+
+#if defined(XP_WIN)
+ NS_tchar dstfilename[MAXPATHLEN] = {NS_T('\0')};
+ NS_tsnprintf(dstfilename, sizeof(dstfilename)/sizeof(dstfilename[0]),
+ NS_T("%s\\update.status"), gPatchDirPath);
+ if (MoveFileExW(filename, dstfilename, MOVEFILE_REPLACE_EXISTING) == 0) {
+ return false;
+ }
+#endif
+
+ return true;
+}
+
+static void
+WriteStatusFile(int status)
+{
+ const char *text;
+
+ char buf[32];
+ if (status == OK) {
+ if (sStagedUpdate) {
+ text = "applied\n";
+ } else {
+ text = "succeeded\n";
+ }
+ } else {
+ snprintf(buf, sizeof(buf)/sizeof(buf[0]), "failed: %d\n", status);
+ text = buf;
+ }
+
+ WriteStatusFile(text);
+}
+
+#ifdef MOZ_MAINTENANCE_SERVICE
+/*
+ * Read the update.status file and sets isPendingService to true if
+ * the status is set to pending-service.
+ *
+ * @param isPendingService Out parameter for specifying if the status
+ * is set to pending-service or not.
+ * @return true if the information was retrieved and it is pending
+ * or pending-service.
+*/
+static bool
+IsUpdateStatusPendingService()
+{
+ NS_tchar filename[MAXPATHLEN];
+ NS_tsnprintf(filename, sizeof(filename)/sizeof(filename[0]),
+ NS_T("%s/update.status"), gPatchDirPath);
+
+ AutoFile file(NS_tfopen(filename, NS_T("rb")));
+ if (file == nullptr)
+ return false;
+
+ char buf[32] = { 0 };
+ fread(buf, sizeof(buf), 1, file);
+
+ const char kPendingService[] = "pending-service";
+ const char kAppliedService[] = "applied-service";
+
+ return (strncmp(buf, kPendingService,
+ sizeof(kPendingService) - 1) == 0) ||
+ (strncmp(buf, kAppliedService,
+ sizeof(kAppliedService) - 1) == 0);
+}
+#endif
+
+#ifdef XP_WIN
+/*
+ * Read the update.status file and sets isSuccess to true if
+ * the status is set to succeeded.
+ *
+ * @param isSucceeded Out parameter for specifying if the status
+ * is set to succeeded or not.
+ * @return true if the information was retrieved and it is succeeded.
+*/
+static bool
+IsUpdateStatusSucceeded(bool &isSucceeded)
+{
+ isSucceeded = false;
+ NS_tchar filename[MAXPATHLEN];
+ NS_tsnprintf(filename, sizeof(filename)/sizeof(filename[0]),
+ NS_T("%s/update.status"), gPatchDirPath);
+
+ AutoFile file(NS_tfopen(filename, NS_T("rb")));
+ if (file == nullptr)
+ return false;
+
+ char buf[32] = { 0 };
+ fread(buf, sizeof(buf), 1, file);
+
+ const char kSucceeded[] = "succeeded";
+ isSucceeded = strncmp(buf, kSucceeded,
+ sizeof(kSucceeded) - 1) == 0;
+ return true;
+}
+#endif
+
+/*
+ * Copy the entire contents of the application installation directory to the
+ * destination directory for the update process.
+ *
+ * @return 0 if successful, an error code otherwise.
+ */
+static int
+CopyInstallDirToDestDir()
+{
+ // These files should not be copied over to the updated app
+#ifdef XP_WIN
+#define SKIPLIST_COUNT 3
+#elif XP_MACOSX
+#define SKIPLIST_COUNT 0
+#else
+#define SKIPLIST_COUNT 2
+#endif
+ copy_recursive_skiplist<SKIPLIST_COUNT> skiplist;
+#ifndef XP_MACOSX
+ skiplist.append(0, gInstallDirPath, NS_T("updated"));
+ skiplist.append(1, gInstallDirPath, NS_T("updates/0"));
+#ifdef XP_WIN
+ skiplist.append(2, gInstallDirPath, NS_T("updated.update_in_progress.lock"));
+#endif
+#endif
+
+ return ensure_copy_recursive(gInstallDirPath, gWorkingDirPath, skiplist);
+}
+
+/*
+ * Replace the application installation directory with the destination
+ * directory in order to finish a staged update task
+ *
+ * @return 0 if successful, an error code otherwise.
+ */
+static int
+ProcessReplaceRequest()
+{
+ // The replacement algorithm is like this:
+ // 1. Move destDir to tmpDir. In case of failure, abort.
+ // 2. Move newDir to destDir. In case of failure, revert step 1 and abort.
+ // 3. Delete tmpDir (or defer it to the next reboot).
+
+#ifdef XP_MACOSX
+ NS_tchar destDir[MAXPATHLEN];
+ NS_tsnprintf(destDir, sizeof(destDir)/sizeof(destDir[0]),
+ NS_T("%s/Contents"), gInstallDirPath);
+#elif XP_WIN
+ // Windows preserves the case of the file/directory names. We use the
+ // GetLongPathName API in order to get the correct case for the directory
+ // name, so that if the user has used a different case when launching the
+ // application, the installation directory's name does not change.
+ NS_tchar destDir[MAXPATHLEN];
+ if (!GetLongPathNameW(gInstallDirPath, destDir,
+ sizeof(destDir)/sizeof(destDir[0]))) {
+ return NO_INSTALLDIR_ERROR;
+ }
+#else
+ NS_tchar* destDir = gInstallDirPath;
+#endif
+
+ NS_tchar tmpDir[MAXPATHLEN];
+ NS_tsnprintf(tmpDir, sizeof(tmpDir)/sizeof(tmpDir[0]),
+ NS_T("%s.bak"), destDir);
+
+ NS_tchar newDir[MAXPATHLEN];
+ NS_tsnprintf(newDir, sizeof(newDir)/sizeof(newDir[0]),
+#ifdef XP_MACOSX
+ NS_T("%s/Contents"),
+ gWorkingDirPath);
+#else
+ NS_T("%s.bak/updated"),
+ gInstallDirPath);
+#endif
+
+ // First try to remove the possibly existing temp directory, because if this
+ // directory exists, we will fail to rename destDir.
+ // No need to error check here because if this fails, we will fail in the
+ // next step anyways.
+ ensure_remove_recursive(tmpDir);
+
+ LOG(("Begin moving destDir (" LOG_S ") to tmpDir (" LOG_S ")",
+ destDir, tmpDir));
+ int rv = rename_file(destDir, tmpDir, true);
+#ifdef XP_WIN
+ // On Windows, if Firefox is launched using the shortcut, it will hold a handle
+ // to its installation directory open, which might not get released in time.
+ // Therefore we wait a little bit here to see if the handle is released.
+ // If it's not released, we just fail to perform the replace request.
+ const int max_retries = 10;
+ int retries = 0;
+ while (rv == WRITE_ERROR && (retries++ < max_retries)) {
+ LOG(("PerformReplaceRequest: destDir rename attempt %d failed. " \
+ "File: " LOG_S ". Last error: %d, err: %d", retries,
+ destDir, GetLastError(), rv));
+
+ Sleep(100);
+
+ rv = rename_file(destDir, tmpDir, true);
+ }
+#endif
+ if (rv) {
+ // The status file will have 'pending' written to it so there is no value in
+ // returning an error specific for this failure.
+ LOG(("Moving destDir to tmpDir failed, err: %d", rv));
+ return rv;
+ }
+
+ LOG(("Begin moving newDir (" LOG_S ") to destDir (" LOG_S ")",
+ newDir, destDir));
+ rv = rename_file(newDir, destDir, true);
+#ifdef XP_MACOSX
+ if (rv) {
+ LOG(("Moving failed. Begin copying newDir (" LOG_S ") to destDir (" LOG_S ")",
+ newDir, destDir));
+ copy_recursive_skiplist<0> skiplist;
+ rv = ensure_copy_recursive(newDir, destDir, skiplist);
+ }
+#endif
+ if (rv) {
+ LOG(("Moving newDir to destDir failed, err: %d", rv));
+ LOG(("Now, try to move tmpDir back to destDir"));
+ ensure_remove_recursive(destDir);
+ int rv2 = rename_file(tmpDir, destDir, true);
+ if (rv2) {
+ LOG(("Moving tmpDir back to destDir failed, err: %d", rv2));
+ }
+ // The status file will be have 'pending' written to it so there is no value
+ // in returning an error specific for this failure.
+ return rv;
+ }
+
+#if !defined(XP_WIN) && !defined(XP_MACOSX)
+ // Platforms that have their updates directory in the installation directory
+ // need to have the last-update.log and backup-update.log files moved from the
+ // old installation directory to the new installation directory.
+ NS_tchar tmpLog[MAXPATHLEN];
+ NS_tsnprintf(tmpLog, sizeof(tmpLog)/sizeof(tmpLog[0]),
+ NS_T("%s/updates/last-update.log"), tmpDir);
+ if (!NS_taccess(tmpLog, F_OK)) {
+ NS_tchar destLog[MAXPATHLEN];
+ NS_tsnprintf(destLog, sizeof(destLog)/sizeof(destLog[0]),
+ NS_T("%s/updates/last-update.log"), destDir);
+ NS_tremove(destLog);
+ NS_trename(tmpLog, destLog);
+ }
+#endif
+
+ LOG(("Now, remove the tmpDir"));
+ rv = ensure_remove_recursive(tmpDir, true);
+ if (rv) {
+ LOG(("Removing tmpDir failed, err: %d", rv));
+#ifdef XP_WIN
+ NS_tchar deleteDir[MAXPATHLEN];
+ NS_tsnprintf(deleteDir, sizeof(deleteDir)/sizeof(deleteDir[0]),
+ NS_T("%s\\%s"), destDir, DELETE_DIR);
+ // Attempt to remove the tobedeleted directory and then recreate it if it
+ // was successfully removed.
+ _wrmdir(deleteDir);
+ if (NS_taccess(deleteDir, F_OK)) {
+ NS_tmkdir(deleteDir, 0755);
+ }
+ remove_recursive_on_reboot(tmpDir, deleteDir);
+#endif
+ }
+
+#ifdef XP_MACOSX
+ // On OS X, we we need to remove the staging directory after its Contents
+ // directory has been moved.
+ NS_tchar updatedAppDir[MAXPATHLEN];
+ NS_tsnprintf(updatedAppDir, sizeof(updatedAppDir)/sizeof(updatedAppDir[0]),
+ NS_T("%s/Updated.app"), gPatchDirPath);
+ ensure_remove_recursive(updatedAppDir);
+#endif
+
+ gSucceeded = true;
+
+ return 0;
+}
+
+#ifdef XP_WIN
+static void
+WaitForServiceFinishThread(void *param)
+{
+ // We wait at most 10 minutes, we already waited 5 seconds previously
+ // before deciding to show this UI.
+ WaitForServiceStop(SVC_NAME, 595);
+ QuitProgressUI();
+}
+#endif
+
+#ifdef MOZ_VERIFY_MAR_SIGNATURE
+/**
+ * This function reads in the ACCEPTED_MAR_CHANNEL_IDS from update-settings.ini
+ *
+ * @param path The path to the ini file that is to be read
+ * @param results A pointer to the location to store the read strings
+ * @return OK on success
+ */
+static int
+ReadMARChannelIDs(const NS_tchar *path, MARChannelStringTable *results)
+{
+ const unsigned int kNumStrings = 1;
+ const char *kUpdaterKeys = "ACCEPTED_MAR_CHANNEL_IDS\0";
+ char updater_strings[kNumStrings][MAX_TEXT_LEN];
+
+ int result = ReadStrings(path, kUpdaterKeys, kNumStrings,
+ updater_strings, "Settings");
+
+ strncpy(results->MARChannelID, updater_strings[0], MAX_TEXT_LEN - 1);
+ results->MARChannelID[MAX_TEXT_LEN - 1] = 0;
+
+ return result;
+}
+#endif
+
+static int
+GetUpdateFileName(NS_tchar *fileName, int maxChars)
+{
+#if defined(MOZ_WIDGET_GONK)
+ // If an update.link file exists, then it will contain the name
+ // of the update file (terminated by a newline).
+
+ NS_tchar linkFileName[MAXPATHLEN];
+ NS_tsnprintf(linkFileName, sizeof(linkFileName)/sizeof(linkFileName[0]),
+ NS_T("%s/update.link"), gPatchDirPath);
+ AutoFile linkFile(NS_tfopen(linkFileName, NS_T("rb")));
+ if (linkFile == nullptr) {
+ NS_tsnprintf(fileName, maxChars,
+ NS_T("%s/update.mar"), gPatchDirPath);
+ return OK;
+ }
+
+ char dataFileName[MAXPATHLEN];
+ size_t bytesRead;
+
+ if ((bytesRead = fread(dataFileName, 1, sizeof(dataFileName)-1, linkFile)) <= 0) {
+ *fileName = NS_T('\0');
+ return READ_ERROR;
+ }
+ if (dataFileName[bytesRead-1] == '\n') {
+ // Strip trailing newline (for \n and \r\n)
+ bytesRead--;
+ }
+ if (dataFileName[bytesRead-1] == '\r') {
+ // Strip trailing CR (for \r, \r\n)
+ bytesRead--;
+ }
+ dataFileName[bytesRead] = '\0';
+
+ strncpy(fileName, dataFileName, maxChars-1);
+ fileName[maxChars-1] = '\0';
+#else
+ // We currently only support update.link files under GONK
+ NS_tsnprintf(fileName, maxChars,
+ NS_T("%s/update.mar"), gPatchDirPath);
+#endif
+ return OK;
+}
+
+static void
+UpdateThreadFunc(void *param)
+{
+ // open ZIP archive and process...
+ int rv;
+ if (sReplaceRequest) {
+ rv = ProcessReplaceRequest();
+ } else {
+ NS_tchar dataFile[MAXPATHLEN];
+ rv = GetUpdateFileName(dataFile, sizeof(dataFile)/sizeof(dataFile[0]));
+ if (rv == OK) {
+ rv = gArchiveReader.Open(dataFile);
+ }
+
+#ifdef MOZ_VERIFY_MAR_SIGNATURE
+ if (rv == OK) {
+#ifdef XP_WIN
+ HKEY baseKey = nullptr;
+ wchar_t valueName[] = L"Image Path";
+ wchar_t rasenh[] = L"rsaenh.dll";
+ bool reset = false;
+ if (RegOpenKeyExW(HKEY_LOCAL_MACHINE,
+ L"SOFTWARE\\Microsoft\\Cryptography\\Defaults\\Provider\\Microsoft Enhanced Cryptographic Provider v1.0",
+ 0, KEY_READ | KEY_WRITE,
+ &baseKey) == ERROR_SUCCESS) {
+ wchar_t path[MAX_PATH + 1];
+ DWORD size = sizeof(path);
+ DWORD type;
+ if (RegQueryValueExW(baseKey, valueName, 0, &type,
+ (LPBYTE)path, &size) == ERROR_SUCCESS) {
+ if (type == REG_SZ && wcscmp(path, rasenh) == 0) {
+ wchar_t rasenhFullPath[] = L"%SystemRoot%\\System32\\rsaenh.dll";
+ if (RegSetValueExW(baseKey, valueName, 0, REG_SZ,
+ (const BYTE*)rasenhFullPath,
+ sizeof(rasenhFullPath)) == ERROR_SUCCESS) {
+ reset = true;
+ }
+ }
+ }
+ }
+#endif
+ rv = gArchiveReader.VerifySignature();
+#ifdef XP_WIN
+ if (baseKey) {
+ if (reset) {
+ RegSetValueExW(baseKey, valueName, 0, REG_SZ,
+ (const BYTE*)rasenh,
+ sizeof(rasenh));
+ }
+ RegCloseKey(baseKey);
+ }
+#endif
+ }
+
+ if (rv == OK) {
+ if (rv == OK) {
+ NS_tchar updateSettingsPath[MAX_TEXT_LEN];
+ NS_tsnprintf(updateSettingsPath,
+ sizeof(updateSettingsPath) / sizeof(updateSettingsPath[0]),
+#ifdef XP_MACOSX
+ NS_T("%s/Contents/Resources/update-settings.ini"),
+#else
+ NS_T("%s/update-settings.ini"),
+#endif
+ gWorkingDirPath);
+ MARChannelStringTable MARStrings;
+ if (ReadMARChannelIDs(updateSettingsPath, &MARStrings) != OK) {
+ // If we can't read from update-settings.ini then we shouldn't impose
+ // a MAR restriction. Some installations won't even include this file.
+ MARStrings.MARChannelID[0] = '\0';
+ }
+
+ rv = gArchiveReader.VerifyProductInformation(MARStrings.MARChannelID,
+ MOZ_APP_VERSION);
+ }
+ }
+#endif
+
+ if (rv == OK && sStagedUpdate && !sIsOSUpdate) {
+#ifdef TEST_UPDATER
+ // The MOZ_TEST_SKIP_UPDATE_STAGE environment variable prevents copying
+ // the files in dist/bin in the test updater when staging an update since
+ // this can cause tests to timeout.
+ if (EnvHasValue("MOZ_TEST_SKIP_UPDATE_STAGE")) {
+ rv = OK;
+ } else {
+ rv = CopyInstallDirToDestDir();
+ }
+#else
+ rv = CopyInstallDirToDestDir();
+#endif
+ }
+
+ if (rv == OK) {
+ rv = DoUpdate();
+ gArchiveReader.Close();
+ NS_tchar updatingDir[MAXPATHLEN];
+ NS_tsnprintf(updatingDir, sizeof(updatingDir)/sizeof(updatingDir[0]),
+ NS_T("%s/updating"), gWorkingDirPath);
+ ensure_remove_recursive(updatingDir);
+ }
+ }
+
+ if (rv && (sReplaceRequest || sStagedUpdate)) {
+#ifdef XP_WIN
+ // On Windows, the current working directory of the process should be changed
+ // so that it's not locked.
+ if (sStagedUpdate) {
+ NS_tchar sysDir[MAX_PATH + 1] = { L'\0' };
+ if (GetSystemDirectoryW(sysDir, MAX_PATH + 1)) {
+ NS_tchdir(sysDir);
+ }
+ }
+#endif
+ ensure_remove_recursive(gWorkingDirPath);
+ // When attempting to replace the application, we should fall back
+ // to non-staged updates in case of a failure. We do this by
+ // setting the status to pending, exiting the updater, and
+ // launching the callback application. The callback application's
+ // startup path will see the pending status, and will start the
+ // updater application again in order to apply the update without
+ // staging.
+ if (sReplaceRequest) {
+ WriteStatusFile(sUsingService ? "pending-service" : "pending");
+ } else {
+ WriteStatusFile(rv);
+ }
+#ifdef TEST_UPDATER
+ // Some tests need to use --test-process-updates again.
+ putenv(const_cast<char*>("MOZ_TEST_PROCESS_UPDATES="));
+#endif
+ } else {
+ if (rv) {
+ LOG(("failed: %d", rv));
+ } else {
+#ifdef XP_MACOSX
+ // If the update was successful we need to update the timestamp on the
+ // top-level Mac OS X bundle directory so that Mac OS X's Launch Services
+ // picks up any major changes when the bundle is updated.
+ if (!sStagedUpdate && utimes(gInstallDirPath, nullptr) != 0) {
+ LOG(("Couldn't set access/modification time on application bundle."));
+ }
+#endif
+
+ LOG(("succeeded"));
+ }
+ WriteStatusFile(rv);
+ }
+
+ LOG(("calling QuitProgressUI"));
+ QuitProgressUI();
+}
+
+#ifdef XP_MACOSX
+static void
+ServeElevatedUpdateThreadFunc(void* param)
+{
+ UpdateServerThreadArgs* threadArgs = (UpdateServerThreadArgs*)param;
+ gSucceeded = ServeElevatedUpdate(threadArgs->argc, threadArgs->argv);
+ if (!gSucceeded) {
+ WriteStatusFile(ELEVATION_CANCELED);
+ }
+ QuitProgressUI();
+}
+
+void freeArguments(int argc, char** argv)
+{
+ for (int i = 0; i < argc; i++) {
+ free(argv[i]);
+ }
+ free(argv);
+}
+#endif
+
+int LaunchCallbackAndPostProcessApps(int argc, NS_tchar** argv,
+ int callbackIndex
+#ifdef XP_WIN
+ , const WCHAR* elevatedLockFilePath
+ , HANDLE updateLockFileHandle
+#elif XP_MACOSX
+ , bool isElevated
+#endif
+ )
+{
+ if (argc > callbackIndex) {
+#if defined(XP_WIN)
+ if (gSucceeded) {
+ if (!LaunchWinPostProcess(gInstallDirPath, gPatchDirPath)) {
+ fprintf(stderr, "The post update process was not launched");
+ }
+
+ // The service update will only be executed if it is already installed.
+ // For first time installs of the service, the install will happen from
+ // the PostUpdate process. We do the service update process here
+ // because it's possible we are updating with updater.exe without the
+ // service if the service failed to apply the update. We want to update
+ // the service to a newer version in that case. If we are not running
+ // through the service, then MOZ_USING_SERVICE will not exist.
+ if (!sUsingService) {
+ StartServiceUpdate(gInstallDirPath);
+ }
+ }
+ EXIT_WHEN_ELEVATED(elevatedLockFilePath, updateLockFileHandle, 0);
+#elif XP_MACOSX
+ if (!isElevated) {
+ if (gSucceeded) {
+ LaunchMacPostProcess(gInstallDirPath);
+ }
+#endif
+
+ LaunchCallbackApp(argv[5],
+ argc - callbackIndex,
+ argv + callbackIndex,
+ sUsingService);
+#ifdef XP_MACOSX
+ } // if (!isElevated)
+#endif /* XP_MACOSX */
+ }
+ return 0;
+}
+
+int NS_main(int argc, NS_tchar **argv)
+{
+ // The callback is the remaining arguments starting at callbackIndex.
+ // The argument specified by callbackIndex is the callback executable and the
+ // argument prior to callbackIndex is the working directory.
+ const int callbackIndex = 6;
+
+#ifdef XP_MACOSX
+ bool isElevated =
+ strstr(argv[0], "/Library/PrivilegedHelperTools/org.mozilla.updater") != 0;
+ if (isElevated) {
+ if (!ObtainUpdaterArguments(&argc, &argv)) {
+ // Won't actually get here because ObtainUpdaterArguments will terminate
+ // the current process on failure.
+ return 1;
+ }
+ }
+#endif
+
+#if defined(MOZ_WIDGET_GONK)
+ if (EnvHasValue("LD_PRELOAD")) {
+ // If the updater is launched with LD_PRELOAD set, then we wind up
+ // preloading libmozglue.so. Under some circumstances, this can cause
+ // the remount of /system to fail when going from rw to ro, so if we
+ // detect LD_PRELOAD we unsetenv it and relaunch ourselves without it.
+ // This will cause the offending preloaded library to be closed.
+ //
+ // For a variety of reasons, this is really hard to do in a safe manner
+ // in the parent process, so we do it here.
+ unsetenv("LD_PRELOAD");
+ execv(argv[0], argv);
+ __android_log_print(ANDROID_LOG_INFO, "updater",
+ "execve failed: errno: %d. Exiting...", errno);
+ _exit(1);
+ }
+#endif
+
+#if defined(MOZ_VERIFY_MAR_SIGNATURE) && !defined(XP_WIN) && \
+ !defined(XP_MACOSX) && !defined(MOZ_WIDGET_GONK)
+ // On Windows and Mac we rely on native APIs to do verifications so we don't
+ // need to initialize NSS at all there.
+ // Otherwise, minimize the amount of NSS we depend on by avoiding all the NSS
+ // databases.
+ if (NSS_NoDB_Init(NULL) != SECSuccess) {
+ PRErrorCode error = PR_GetError();
+ fprintf(stderr, "Could not initialize NSS: %s (%d)",
+ PR_ErrorToName(error), (int) error);
+ _exit(1);
+ }
+#endif
+
+#ifdef XP_MACOSX
+ if (!isElevated) {
+#endif
+ InitProgressUI(&argc, &argv);
+#ifdef XP_MACOSX
+ }
+#endif
+
+ // To process an update the updater command line must at a minimum have the
+ // directory path containing the updater.mar file to process as the first
+ // argument, the install directory as the second argument, and the directory
+ // to apply the update to as the third argument. When the updater is launched
+ // by another process the PID of the parent process should be provided in the
+ // optional fourth argument and the updater will wait on the parent process to
+ // exit if the value is non-zero and the process is present. This is necessary
+ // due to not being able to update files that are in use on Windows. The
+ // optional fifth argument is the callback's working directory and the
+ // optional sixth argument is the callback path. The callback is the
+ // application to launch after updating and it will be launched when these
+ // arguments are provided whether the update was successful or not. All
+ // remaining arguments are optional and are passed to the callback when it is
+ // launched.
+ if (argc < 4) {
+ fprintf(stderr, "Usage: updater patch-dir install-dir apply-to-dir [wait-pid [callback-working-dir callback-path args...]]\n");
+#ifdef XP_MACOSX
+ if (isElevated) {
+ freeArguments(argc, argv);
+ CleanupElevatedMacUpdate(true);
+ }
+#endif
+ return 1;
+ }
+
+ // This check is also performed in workmonitor.cpp since the maintenance
+ // service can be called directly.
+ if (!IsValidFullPath(argv[1])) {
+ // Since the status file is written to the patch directory and the patch
+ // directory is invalid don't write the status file.
+ fprintf(stderr, "The patch directory path is not valid for this " \
+ "application (" LOG_S ")\n", argv[1]);
+#ifdef XP_MACOSX
+ if (isElevated) {
+ freeArguments(argc, argv);
+ CleanupElevatedMacUpdate(true);
+ }
+#endif
+ return 1;
+ }
+ // The directory containing the update information.
+ NS_tstrncpy(gPatchDirPath, argv[1], MAXPATHLEN);
+
+ // This check is also performed in workmonitor.cpp since the maintenance
+ // service can be called directly.
+ if (!IsValidFullPath(argv[2])) {
+ WriteStatusFile(INVALID_INSTALL_DIR_PATH_ERROR);
+ fprintf(stderr, "The install directory path is not valid for this " \
+ "application (" LOG_S ")\n", argv[2]);
+#ifdef XP_MACOSX
+ if (isElevated) {
+ freeArguments(argc, argv);
+ CleanupElevatedMacUpdate(true);
+ }
+#endif
+ return 1;
+ }
+ // The directory we're going to update to.
+ // We copy this string because we need to remove trailing slashes. The C++
+ // standard says that it's always safe to write to strings pointed to by argv
+ // elements, but I don't necessarily believe it.
+ NS_tstrncpy(gInstallDirPath, argv[2], MAXPATHLEN);
+ gInstallDirPath[MAXPATHLEN - 1] = NS_T('\0');
+ NS_tchar *slash = NS_tstrrchr(gInstallDirPath, NS_SLASH);
+ if (slash && !slash[1]) {
+ *slash = NS_T('\0');
+ }
+
+#ifdef XP_WIN
+ bool useService = false;
+ bool testOnlyFallbackKeyExists = false;
+ bool noServiceFallback = false;
+
+ // We never want the service to be used unless we build with
+ // the maintenance service.
+#ifdef MOZ_MAINTENANCE_SERVICE
+ useService = IsUpdateStatusPendingService();
+#ifdef TEST_UPDATER
+ noServiceFallback = EnvHasValue("MOZ_NO_SERVICE_FALLBACK");
+ putenv(const_cast<char*>("MOZ_NO_SERVICE_FALLBACK="));
+ // Our tests run with a different apply directory for each test.
+ // We use this registry key on our test slaves to store the
+ // allowed name/issuers.
+ testOnlyFallbackKeyExists = DoesFallbackKeyExist();
+#endif
+#endif
+
+ // Remove everything except close window from the context menu
+ {
+ HKEY hkApp = nullptr;
+ RegCreateKeyExW(HKEY_CURRENT_USER, L"Software\\Classes\\Applications",
+ 0, nullptr, REG_OPTION_NON_VOLATILE, KEY_SET_VALUE, nullptr,
+ &hkApp, nullptr);
+ RegCloseKey(hkApp);
+ if (RegCreateKeyExW(HKEY_CURRENT_USER,
+ L"Software\\Classes\\Applications\\updater.exe",
+ 0, nullptr, REG_OPTION_VOLATILE, KEY_SET_VALUE, nullptr,
+ &hkApp, nullptr) == ERROR_SUCCESS) {
+ RegSetValueExW(hkApp, L"IsHostApp", 0, REG_NONE, 0, 0);
+ RegSetValueExW(hkApp, L"NoOpenWith", 0, REG_NONE, 0, 0);
+ RegSetValueExW(hkApp, L"NoStartPage", 0, REG_NONE, 0, 0);
+ RegCloseKey(hkApp);
+ }
+ }
+#endif
+
+ // If there is a PID specified and it is not '0' then wait for the process to exit.
+#ifdef XP_WIN
+ __int64 pid = 0;
+#else
+ int pid = 0;
+#endif
+ if (argc > 4) {
+#ifdef XP_WIN
+ pid = _wtoi64(argv[4]);
+#else
+ pid = atoi(argv[4]);
+#endif
+ if (pid == -1) {
+ // This is a signal from the parent process that the updater should stage
+ // the update.
+ sStagedUpdate = true;
+ } else if (NS_tstrstr(argv[4], NS_T("/replace"))) {
+ // We're processing a request to replace the application with a staged
+ // update.
+ sReplaceRequest = true;
+ }
+ }
+
+ // This check is also performed in workmonitor.cpp since the maintenance
+ // service can be called directly.
+ if (!IsValidFullPath(argv[3])) {
+ WriteStatusFile(INVALID_WORKING_DIR_PATH_ERROR);
+ fprintf(stderr, "The working directory path is not valid for this " \
+ "application (" LOG_S ")\n", argv[3]);
+#ifdef XP_MACOSX
+ if (isElevated) {
+ freeArguments(argc, argv);
+ CleanupElevatedMacUpdate(true);
+ }
+#endif
+ return 1;
+ }
+ // The directory we're going to update to.
+ // We copy this string because we need to remove trailing slashes. The C++
+ // standard says that it's always safe to write to strings pointed to by argv
+ // elements, but I don't necessarily believe it.
+ NS_tstrncpy(gWorkingDirPath, argv[3], MAXPATHLEN);
+ gWorkingDirPath[MAXPATHLEN - 1] = NS_T('\0');
+ slash = NS_tstrrchr(gWorkingDirPath, NS_SLASH);
+ if (slash && !slash[1]) {
+ *slash = NS_T('\0');
+ }
+
+ // These checks are also performed in workmonitor.cpp since the maintenance
+ // service can be called directly.
+ if (argc > callbackIndex) {
+ if (!IsValidFullPath(argv[callbackIndex])) {
+ WriteStatusFile(INVALID_CALLBACK_PATH_ERROR);
+ fprintf(stderr, "The callback file path is not valid for this " \
+ "application (" LOG_S ")\n", argv[callbackIndex]);
+#ifdef XP_MACOSX
+ if (isElevated) {
+ freeArguments(argc, argv);
+ CleanupElevatedMacUpdate(true);
+ }
+#endif
+ return 1;
+ }
+
+ size_t len = NS_tstrlen(gInstallDirPath);
+ NS_tchar callbackInstallDir[MAXPATHLEN] = { NS_T('\0') };
+ NS_tstrncpy(callbackInstallDir, argv[callbackIndex], len);
+ if (NS_tstrcmp(gInstallDirPath, callbackInstallDir) != 0) {
+ WriteStatusFile(INVALID_CALLBACK_DIR_ERROR);
+ fprintf(stderr, "The callback file must be located in the " \
+ "installation directory (" LOG_S ")\n", argv[callbackIndex]);
+#ifdef XP_MACOSX
+ if (isElevated) {
+ freeArguments(argc, argv);
+ CleanupElevatedMacUpdate(true);
+ }
+#endif
+ return 1;
+ }
+ }
+
+#ifdef XP_MACOSX
+ if (!isElevated && !IsRecursivelyWritable(argv[2])) {
+ // If the app directory isn't recursively writeable, an elevated update is
+ // required.
+ UpdateServerThreadArgs threadArgs;
+ threadArgs.argc = argc;
+ threadArgs.argv = const_cast<const NS_tchar**>(argv);
+
+ Thread t1;
+ if (t1.Run(ServeElevatedUpdateThreadFunc, &threadArgs) == 0) {
+ // Show an indeterminate progress bar while an elevated update is in
+ // progress.
+ ShowProgressUI(true);
+ }
+ t1.Join();
+
+ LaunchCallbackAndPostProcessApps(argc, argv, callbackIndex, false);
+ return gSucceeded ? 0 : 1;
+ }
+#endif
+
+ if (EnvHasValue("MOZ_OS_UPDATE")) {
+ sIsOSUpdate = true;
+ putenv(const_cast<char*>("MOZ_OS_UPDATE="));
+ }
+
+ LogInit(gPatchDirPath, NS_T("update.log"));
+
+ if (!WriteStatusFile("applying")) {
+ LOG(("failed setting status to 'applying'"));
+#ifdef XP_MACOSX
+ if (isElevated) {
+ freeArguments(argc, argv);
+ CleanupElevatedMacUpdate(true);
+ }
+#endif
+ return 1;
+ }
+
+ if (sStagedUpdate) {
+ LOG(("Performing a staged update"));
+ } else if (sReplaceRequest) {
+ LOG(("Performing a replace request"));
+ }
+
+ LOG(("PATCH DIRECTORY " LOG_S, gPatchDirPath));
+ LOG(("INSTALLATION DIRECTORY " LOG_S, gInstallDirPath));
+ LOG(("WORKING DIRECTORY " LOG_S, gWorkingDirPath));
+
+#if defined(XP_WIN)
+ // These checks are also performed in workmonitor.cpp since the maintenance
+ // service can be called directly.
+ if (_wcsnicmp(gWorkingDirPath, gInstallDirPath, MAX_PATH) != 0) {
+ if (!sStagedUpdate && !sReplaceRequest) {
+ WriteStatusFile(INVALID_APPLYTO_DIR_ERROR);
+ LOG(("Installation directory and working directory must be the same "
+ "for non-staged updates. Exiting."));
+ LogFinish();
+ return 1;
+ }
+
+ NS_tchar workingDirParent[MAX_PATH];
+ NS_tsnprintf(workingDirParent,
+ sizeof(workingDirParent) / sizeof(workingDirParent[0]),
+ NS_T("%s"), gWorkingDirPath);
+ if (!PathRemoveFileSpecW(workingDirParent)) {
+ WriteStatusFile(REMOVE_FILE_SPEC_ERROR);
+ LOG(("Error calling PathRemoveFileSpecW: %d", GetLastError()));
+ LogFinish();
+ return 1;
+ }
+
+ if (_wcsnicmp(workingDirParent, gInstallDirPath, MAX_PATH) != 0) {
+ WriteStatusFile(INVALID_APPLYTO_DIR_STAGED_ERROR);
+ LOG(("The apply-to directory must be the same as or "
+ "a child of the installation directory! Exiting."));
+ LogFinish();
+ return 1;
+ }
+ }
+#endif
+
+#ifdef MOZ_WIDGET_GONK
+ const char *prioEnv = getenv("MOZ_UPDATER_PRIO");
+ if (prioEnv) {
+ int32_t prioVal;
+ int32_t oomScoreAdj;
+ int32_t ioprioClass;
+ int32_t ioprioLevel;
+ if (sscanf(prioEnv, "%d/%d/%d/%d",
+ &prioVal, &oomScoreAdj, &ioprioClass, &ioprioLevel) == 4) {
+ LOG(("MOZ_UPDATER_PRIO=%s", prioEnv));
+ if (setpriority(PRIO_PROCESS, 0, prioVal)) {
+ LOG(("setpriority(%d) failed, errno = %d", prioVal, errno));
+ }
+ if (ioprio_set(IOPRIO_WHO_PROCESS, 0,
+ IOPRIO_PRIO_VALUE(ioprioClass, ioprioLevel))) {
+ LOG(("ioprio_set(%d,%d) failed: errno = %d",
+ ioprioClass, ioprioLevel, errno));
+ }
+ FILE *fs = fopen("/proc/self/oom_score_adj", "w");
+ if (fs) {
+ fprintf(fs, "%d", oomScoreAdj);
+ fclose(fs);
+ } else {
+ LOG(("Unable to open /proc/self/oom_score_adj for writing, errno = %d",
+ errno));
+ }
+ }
+ }
+#endif
+
+#ifdef XP_WIN
+ if (pid > 0) {
+ HANDLE parent = OpenProcess(SYNCHRONIZE, false, (DWORD) pid);
+ // May return nullptr if the parent process has already gone away.
+ // Otherwise, wait for the parent process to exit before starting the
+ // update.
+ if (parent) {
+ DWORD waitTime = PARENT_WAIT;
+ DWORD result = WaitForSingleObject(parent, waitTime);
+ CloseHandle(parent);
+ if (result != WAIT_OBJECT_0) {
+ return 1;
+ }
+ }
+ }
+#else
+ if (pid > 0)
+ waitpid(pid, nullptr, 0);
+#endif
+
+#ifdef XP_WIN
+#ifdef MOZ_MAINTENANCE_SERVICE
+ sUsingService = EnvHasValue("MOZ_USING_SERVICE");
+ putenv(const_cast<char*>("MOZ_USING_SERVICE="));
+#endif
+ // lastFallbackError keeps track of the last error for the service not being
+ // used, in case of an error when fallback is not enabled we write the
+ // error to the update.status file.
+ // When fallback is disabled (MOZ_NO_SERVICE_FALLBACK does not exist) then
+ // we will instead fallback to not using the service and display a UAC prompt.
+ int lastFallbackError = FALLBACKKEY_UNKNOWN_ERROR;
+
+ // Launch a second instance of the updater with the runas verb on Windows
+ // when write access is denied to the installation directory.
+ HANDLE updateLockFileHandle = INVALID_HANDLE_VALUE;
+ NS_tchar elevatedLockFilePath[MAXPATHLEN] = {NS_T('\0')};
+ if (!sUsingService &&
+ (argc > callbackIndex || sStagedUpdate || sReplaceRequest)) {
+ NS_tchar updateLockFilePath[MAXPATHLEN];
+ if (sStagedUpdate) {
+ // When staging an update, the lock file is:
+ // <install_dir>\updated.update_in_progress.lock
+ NS_tsnprintf(updateLockFilePath,
+ sizeof(updateLockFilePath)/sizeof(updateLockFilePath[0]),
+ NS_T("%s/updated.update_in_progress.lock"), gInstallDirPath);
+ } else if (sReplaceRequest) {
+ // When processing a replace request, the lock file is:
+ // <install_dir>\..\moz_update_in_progress.lock
+ NS_tchar installDir[MAXPATHLEN];
+ NS_tstrcpy(installDir, gInstallDirPath);
+ NS_tchar *slash = (NS_tchar *) NS_tstrrchr(installDir, NS_SLASH);
+ *slash = NS_T('\0');
+ NS_tsnprintf(updateLockFilePath,
+ sizeof(updateLockFilePath)/sizeof(updateLockFilePath[0]),
+ NS_T("%s\\moz_update_in_progress.lock"), installDir);
+ } else {
+ // In the non-staging update case, the lock file is:
+ // <install_dir>\<app_name>.exe.update_in_progress.lock
+ NS_tsnprintf(updateLockFilePath,
+ sizeof(updateLockFilePath)/sizeof(updateLockFilePath[0]),
+ NS_T("%s.update_in_progress.lock"), argv[callbackIndex]);
+ }
+
+ // The update_in_progress.lock file should only exist during an update. In
+ // case it exists attempt to remove it and exit if that fails to prevent
+ // simultaneous updates occurring.
+ if (!_waccess(updateLockFilePath, F_OK) &&
+ NS_tremove(updateLockFilePath) != 0) {
+ // Try to fall back to the old way of doing updates if a staged
+ // update fails.
+ if (sStagedUpdate || sReplaceRequest) {
+ // Note that this could fail, but if it does, there isn't too much we
+ // can do in order to recover anyways.
+ WriteStatusFile("pending");
+ }
+ LOG(("Update already in progress! Exiting"));
+ return 1;
+ }
+
+ updateLockFileHandle = CreateFileW(updateLockFilePath,
+ GENERIC_READ | GENERIC_WRITE,
+ 0,
+ nullptr,
+ OPEN_ALWAYS,
+ FILE_FLAG_DELETE_ON_CLOSE,
+ nullptr);
+
+ NS_tsnprintf(elevatedLockFilePath,
+ sizeof(elevatedLockFilePath)/sizeof(elevatedLockFilePath[0]),
+ NS_T("%s/update_elevated.lock"), gPatchDirPath);
+
+ // Even if a file has no sharing access, you can still get its attributes
+ bool startedFromUnelevatedUpdater =
+ GetFileAttributesW(elevatedLockFilePath) != INVALID_FILE_ATTRIBUTES;
+
+ // If we're running from the service, then we were started with the same
+ // token as the service so the permissions are already dropped. If we're
+ // running from an elevated updater that was started from an unelevated
+ // updater, then we drop the permissions here. We do not drop the
+ // permissions on the originally called updater because we use its token
+ // to start the callback application.
+ if (startedFromUnelevatedUpdater) {
+ // Disable every privilege we don't need. Processes started using
+ // CreateProcess will use the same token as this process.
+ UACHelper::DisablePrivileges(nullptr);
+ }
+
+ if (updateLockFileHandle == INVALID_HANDLE_VALUE ||
+ (useService && testOnlyFallbackKeyExists && noServiceFallback)) {
+ if (!_waccess(elevatedLockFilePath, F_OK) &&
+ NS_tremove(elevatedLockFilePath) != 0) {
+ fprintf(stderr, "Unable to create elevated lock file! Exiting\n");
+ return 1;
+ }
+
+ HANDLE elevatedFileHandle;
+ elevatedFileHandle = CreateFileW(elevatedLockFilePath,
+ GENERIC_READ | GENERIC_WRITE,
+ 0,
+ nullptr,
+ OPEN_ALWAYS,
+ FILE_FLAG_DELETE_ON_CLOSE,
+ nullptr);
+
+ if (elevatedFileHandle == INVALID_HANDLE_VALUE) {
+ LOG(("Unable to create elevated lock file! Exiting"));
+ return 1;
+ }
+
+ wchar_t *cmdLine = MakeCommandLine(argc - 1, argv + 1);
+ if (!cmdLine) {
+ CloseHandle(elevatedFileHandle);
+ return 1;
+ }
+
+ // Make sure the path to the updater to use for the update is on local.
+ // We do this check to make sure that file locking is available for
+ // race condition security checks.
+ if (useService) {
+ BOOL isLocal = FALSE;
+ useService = IsLocalFile(argv[0], isLocal) && isLocal;
+ }
+
+ // If we have unprompted elevation we should NOT use the service
+ // for the update. Service updates happen with the SYSTEM account
+ // which has more privs than we need to update with.
+ // Windows 8 provides a user interface so users can configure this
+ // behavior and it can be configured in the registry in all Windows
+ // versions that support UAC.
+ if (useService) {
+ BOOL unpromptedElevation;
+ if (IsUnpromptedElevation(unpromptedElevation)) {
+ useService = !unpromptedElevation;
+ }
+ }
+
+ // Make sure the service registry entries for the instsallation path
+ // are available. If not don't use the service.
+ if (useService) {
+ WCHAR maintenanceServiceKey[MAX_PATH + 1];
+ if (CalculateRegistryPathFromFilePath(gInstallDirPath,
+ maintenanceServiceKey)) {
+ HKEY baseKey = nullptr;
+ if (RegOpenKeyExW(HKEY_LOCAL_MACHINE,
+ maintenanceServiceKey, 0,
+ KEY_READ | KEY_WOW64_64KEY,
+ &baseKey) == ERROR_SUCCESS) {
+ RegCloseKey(baseKey);
+ } else {
+#ifdef TEST_UPDATER
+ useService = testOnlyFallbackKeyExists;
+#endif
+ if (!useService) {
+ lastFallbackError = FALLBACKKEY_NOKEY_ERROR;
+ }
+ }
+ } else {
+ useService = false;
+ lastFallbackError = FALLBACKKEY_REGPATH_ERROR;
+ }
+ }
+
+ // Originally we used to write "pending" to update.status before
+ // launching the service command. This is no longer needed now
+ // since the service command is launched from updater.exe. If anything
+ // fails in between, we can fall back to using the normal update process
+ // on our own.
+
+ // If we still want to use the service try to launch the service
+ // comamnd for the update.
+ if (useService) {
+ // If the update couldn't be started, then set useService to false so
+ // we do the update the old way.
+ DWORD ret = LaunchServiceSoftwareUpdateCommand(argc, (LPCWSTR *)argv);
+ useService = (ret == ERROR_SUCCESS);
+ // If the command was launched then wait for the service to be done.
+ if (useService) {
+ bool showProgressUI = false;
+ // Never show the progress UI when staging updates.
+ if (!sStagedUpdate) {
+ // We need to call this separately instead of allowing ShowProgressUI
+ // to initialize the strings because the service will move the
+ // ini file out of the way when running updater.
+ showProgressUI = !InitProgressUIStrings();
+ }
+
+ // Wait for the service to stop for 5 seconds. If the service
+ // has still not stopped then show an indeterminate progress bar.
+ DWORD lastState = WaitForServiceStop(SVC_NAME, 5);
+ if (lastState != SERVICE_STOPPED) {
+ Thread t1;
+ if (t1.Run(WaitForServiceFinishThread, nullptr) == 0 &&
+ showProgressUI) {
+ ShowProgressUI(true, false);
+ }
+ t1.Join();
+ }
+
+ lastState = WaitForServiceStop(SVC_NAME, 1);
+ if (lastState != SERVICE_STOPPED) {
+ // If the service doesn't stop after 10 minutes there is
+ // something seriously wrong.
+ lastFallbackError = FALLBACKKEY_SERVICE_NO_STOP_ERROR;
+ useService = false;
+ }
+ } else {
+ lastFallbackError = FALLBACKKEY_LAUNCH_ERROR;
+ }
+ }
+
+ // If the service can't be used when staging an update, make sure that
+ // the UAC prompt is not shown! In this case, just set the status to
+ // pending and the update will be applied during the next startup.
+ if (!useService && sStagedUpdate) {
+ if (updateLockFileHandle != INVALID_HANDLE_VALUE) {
+ CloseHandle(updateLockFileHandle);
+ }
+ WriteStatusFile("pending");
+ return 0;
+ }
+
+ // If we started the service command, and it finished, check the
+ // update.status file to make sure it succeeded, and if it did
+ // we need to manually start the PostUpdate process from the
+ // current user's session of this unelevated updater.exe the
+ // current process is running as.
+ // Note that we don't need to do this if we're just staging the update,
+ // as the PostUpdate step runs when performing the replacing in that case.
+ if (useService && !sStagedUpdate) {
+ bool updateStatusSucceeded = false;
+ if (IsUpdateStatusSucceeded(updateStatusSucceeded) &&
+ updateStatusSucceeded) {
+ if (!LaunchWinPostProcess(gInstallDirPath, gPatchDirPath)) {
+ fprintf(stderr, "The post update process which runs as the user"
+ " for service update could not be launched.");
+ }
+ }
+ }
+
+ // If we didn't want to use the service at all, or if an update was
+ // already happening, or launching the service command failed, then
+ // launch the elevated updater.exe as we do without the service.
+ // We don't launch the elevated updater in the case that we did have
+ // write access all along because in that case the only reason we're
+ // using the service is because we are testing.
+ if (!useService && !noServiceFallback &&
+ updateLockFileHandle == INVALID_HANDLE_VALUE) {
+ SHELLEXECUTEINFO sinfo;
+ memset(&sinfo, 0, sizeof(SHELLEXECUTEINFO));
+ sinfo.cbSize = sizeof(SHELLEXECUTEINFO);
+ sinfo.fMask = SEE_MASK_FLAG_NO_UI |
+ SEE_MASK_FLAG_DDEWAIT |
+ SEE_MASK_NOCLOSEPROCESS;
+ sinfo.hwnd = nullptr;
+ sinfo.lpFile = argv[0];
+ sinfo.lpParameters = cmdLine;
+ sinfo.lpVerb = L"runas";
+ sinfo.nShow = SW_SHOWNORMAL;
+
+ bool result = ShellExecuteEx(&sinfo);
+ free(cmdLine);
+
+ if (result) {
+ WaitForSingleObject(sinfo.hProcess, INFINITE);
+ CloseHandle(sinfo.hProcess);
+ } else {
+ WriteStatusFile(ELEVATION_CANCELED);
+ }
+ }
+
+ if (argc > callbackIndex) {
+ LaunchCallbackApp(argv[5], argc - callbackIndex,
+ argv + callbackIndex, sUsingService);
+ }
+
+ CloseHandle(elevatedFileHandle);
+
+ if (!useService && !noServiceFallback &&
+ INVALID_HANDLE_VALUE == updateLockFileHandle) {
+ // We didn't use the service and we did run the elevated updater.exe.
+ // The elevated updater.exe is responsible for writing out the
+ // update.status file.
+ return 0;
+ } else if (useService) {
+ // The service command was launched. The service is responsible for
+ // writing out the update.status file.
+ if (updateLockFileHandle != INVALID_HANDLE_VALUE) {
+ CloseHandle(updateLockFileHandle);
+ }
+ return 0;
+ } else {
+ // Otherwise the service command was not launched at all.
+ // We are only reaching this code path because we had write access
+ // all along to the directory and a fallback key existed, and we
+ // have fallback disabled (MOZ_NO_SERVICE_FALLBACK env var exists).
+ // We only currently use this env var from XPCShell tests.
+ CloseHandle(updateLockFileHandle);
+ WriteStatusFile(lastFallbackError);
+ return 0;
+ }
+ }
+ }
+#endif
+
+#if defined(MOZ_WIDGET_GONK)
+ // In gonk, the master b2g process sets its umask to 0027 because
+ // there's no reason for it to ever create world-readable files.
+ // The updater binary, however, needs to do this, and it inherits
+ // the master process's cautious umask. So we drop down a bit here.
+ umask(0022);
+
+ // Remount the /system partition as read-write for gonk. The destructor will
+ // remount /system as read-only. We add an extra level of scope here to avoid
+ // calling LogFinish() before the GonkAutoMounter destructor has a chance
+ // to be called
+ {
+#if !defined(TEST_UPDATER)
+ GonkAutoMounter mounter;
+ if (mounter.GetAccess() != MountAccess::ReadWrite) {
+ WriteStatusFile(FILESYSTEM_MOUNT_READWRITE_ERROR);
+ return 1;
+ }
+#endif
+#endif
+
+ if (sStagedUpdate) {
+ // When staging updates, blow away the old installation directory and create
+ // it from scratch.
+ ensure_remove_recursive(gWorkingDirPath);
+ }
+ if (!sReplaceRequest) {
+ // Try to create the destination directory if it doesn't exist
+ int rv = NS_tmkdir(gWorkingDirPath, 0755);
+ if (rv != OK && errno != EEXIST) {
+#ifdef XP_MACOSX
+ if (isElevated) {
+ freeArguments(argc, argv);
+ CleanupElevatedMacUpdate(true);
+ }
+#endif
+ return 1;
+ }
+ }
+
+#ifdef XP_WIN
+ // For replace requests, we don't need to do any real updates, so this is not
+ // necessary.
+ if (!sReplaceRequest) {
+ // Allocate enough space for the length of the path an optional additional
+ // trailing slash and null termination.
+ NS_tchar *destpath = (NS_tchar *) malloc((NS_tstrlen(gWorkingDirPath) + 2) * sizeof(NS_tchar));
+ if (!destpath) {
+ return 1;
+ }
+
+ NS_tchar *c = destpath;
+ NS_tstrcpy(c, gWorkingDirPath);
+ c += NS_tstrlen(gWorkingDirPath);
+ if (gWorkingDirPath[NS_tstrlen(gWorkingDirPath) - 1] != NS_T('/') &&
+ gWorkingDirPath[NS_tstrlen(gWorkingDirPath) - 1] != NS_T('\\')) {
+ NS_tstrcat(c, NS_T("/"));
+ c += NS_tstrlen(NS_T("/"));
+ }
+ *c = NS_T('\0');
+ c++;
+
+ gDestPath = destpath;
+ }
+
+ NS_tchar applyDirLongPath[MAXPATHLEN];
+ if (!GetLongPathNameW(gWorkingDirPath, applyDirLongPath,
+ sizeof(applyDirLongPath) / sizeof(applyDirLongPath[0]))) {
+ LOG(("NS_main: unable to find apply to dir: " LOG_S, gWorkingDirPath));
+ LogFinish();
+ WriteStatusFile(WRITE_ERROR_APPLY_DIR_PATH);
+ EXIT_WHEN_ELEVATED(elevatedLockFilePath, updateLockFileHandle, 1);
+ if (argc > callbackIndex) {
+ LaunchCallbackApp(argv[5], argc - callbackIndex,
+ argv + callbackIndex, sUsingService);
+ }
+ return 1;
+ }
+
+ HANDLE callbackFile = INVALID_HANDLE_VALUE;
+ if (argc > callbackIndex) {
+ // If the callback executable is specified it must exist for a successful
+ // update. It is important we null out the whole buffer here because later
+ // we make the assumption that the callback application is inside the
+ // apply-to dir. If we don't have a fully null'ed out buffer it can lead
+ // to stack corruption which causes crashes and other problems.
+ NS_tchar callbackLongPath[MAXPATHLEN];
+ ZeroMemory(callbackLongPath, sizeof(callbackLongPath));
+ NS_tchar *targetPath = argv[callbackIndex];
+ NS_tchar buffer[MAXPATHLEN * 2] = { NS_T('\0') };
+ size_t bufferLeft = MAXPATHLEN * 2;
+ if (sReplaceRequest) {
+ // In case of replace requests, we should look for the callback file in
+ // the destination directory.
+ size_t commonPrefixLength = PathCommonPrefixW(argv[callbackIndex],
+ gInstallDirPath,
+ nullptr);
+ NS_tchar *p = buffer;
+ NS_tstrncpy(p, argv[callbackIndex], commonPrefixLength);
+ p += commonPrefixLength;
+ bufferLeft -= commonPrefixLength;
+ NS_tstrncpy(p, gInstallDirPath + commonPrefixLength, bufferLeft);
+
+ size_t len = NS_tstrlen(gInstallDirPath + commonPrefixLength);
+ p += len;
+ bufferLeft -= len;
+ *p = NS_T('\\');
+ ++p;
+ bufferLeft--;
+ *p = NS_T('\0');
+ NS_tchar installDir[MAXPATHLEN];
+ NS_tstrcpy(installDir, gInstallDirPath);
+ size_t callbackPrefixLength = PathCommonPrefixW(argv[callbackIndex],
+ installDir,
+ nullptr);
+ NS_tstrncpy(p, argv[callbackIndex] + std::max(callbackPrefixLength,
+ commonPrefixLength), bufferLeft);
+ targetPath = buffer;
+ }
+ if (!GetLongPathNameW(targetPath, callbackLongPath,
+ sizeof(callbackLongPath)/sizeof(callbackLongPath[0]))) {
+ LOG(("NS_main: unable to find callback file: " LOG_S, targetPath));
+ LogFinish();
+ WriteStatusFile(WRITE_ERROR_CALLBACK_PATH);
+ EXIT_WHEN_ELEVATED(elevatedLockFilePath, updateLockFileHandle, 1);
+ if (argc > callbackIndex) {
+ LaunchCallbackApp(argv[5],
+ argc - callbackIndex,
+ argv + callbackIndex,
+ sUsingService);
+ }
+ return 1;
+ }
+
+ // Doing this is only necessary when we're actually applying a patch.
+ if (!sReplaceRequest) {
+ int len = NS_tstrlen(applyDirLongPath);
+ NS_tchar *s = callbackLongPath;
+ NS_tchar *d = gCallbackRelPath;
+ // advance to the apply to directory and advance past the trailing backslash
+ // if present.
+ s += len;
+ if (*s == NS_T('\\'))
+ ++s;
+
+ // Copy the string and replace backslashes with forward slashes along the
+ // way.
+ do {
+ if (*s == NS_T('\\'))
+ *d = NS_T('/');
+ else
+ *d = *s;
+ ++s;
+ ++d;
+ } while (*s);
+ *d = NS_T('\0');
+ ++d;
+
+ const size_t callbackBackupPathBufSize =
+ sizeof(gCallbackBackupPath)/sizeof(gCallbackBackupPath[0]);
+ const int callbackBackupPathLen =
+ NS_tsnprintf(gCallbackBackupPath, callbackBackupPathBufSize,
+ NS_T("%s" CALLBACK_BACKUP_EXT), argv[callbackIndex]);
+
+ if (callbackBackupPathLen < 0 ||
+ callbackBackupPathLen >= static_cast<int>(callbackBackupPathBufSize)) {
+ LOG(("NS_main: callback backup path truncated"));
+ LogFinish();
+ WriteStatusFile(USAGE_ERROR);
+
+ // Don't attempt to launch the callback when the callback path is
+ // longer than expected.
+ EXIT_WHEN_ELEVATED(elevatedLockFilePath, updateLockFileHandle, 1);
+ return 1;
+ }
+
+ // Make a copy of the callback executable so it can be read when patching.
+ NS_tremove(gCallbackBackupPath);
+ if(!CopyFileW(argv[callbackIndex], gCallbackBackupPath, true)) {
+ DWORD copyFileError = GetLastError();
+ LOG(("NS_main: failed to copy callback file " LOG_S
+ " into place at " LOG_S, argv[callbackIndex], gCallbackBackupPath));
+ LogFinish();
+ if (copyFileError == ERROR_ACCESS_DENIED) {
+ WriteStatusFile(WRITE_ERROR_ACCESS_DENIED);
+ } else {
+ WriteStatusFile(WRITE_ERROR_CALLBACK_APP);
+ }
+
+ EXIT_WHEN_ELEVATED(elevatedLockFilePath, updateLockFileHandle, 1);
+ LaunchCallbackApp(argv[callbackIndex],
+ argc - callbackIndex,
+ argv + callbackIndex,
+ sUsingService);
+ return 1;
+ }
+
+ // Since the process may be signaled as exited by WaitForSingleObject before
+ // the release of the executable image try to lock the main executable file
+ // multiple times before giving up. If we end up giving up, we won't
+ // fail the update.
+ const int max_retries = 10;
+ int retries = 1;
+ DWORD lastWriteError = 0;
+ do {
+ // By opening a file handle wihout FILE_SHARE_READ to the callback
+ // executable, the OS will prevent launching the process while it is
+ // being updated.
+ callbackFile = CreateFileW(targetPath,
+ DELETE | GENERIC_WRITE,
+ // allow delete, rename, and write
+ FILE_SHARE_DELETE | FILE_SHARE_WRITE,
+ nullptr, OPEN_EXISTING, 0, nullptr);
+ if (callbackFile != INVALID_HANDLE_VALUE)
+ break;
+
+ lastWriteError = GetLastError();
+ LOG(("NS_main: callback app file open attempt %d failed. " \
+ "File: " LOG_S ". Last error: %d", retries,
+ targetPath, lastWriteError));
+
+ Sleep(100);
+ } while (++retries <= max_retries);
+
+ // CreateFileW will fail if the callback executable is already in use.
+ if (callbackFile == INVALID_HANDLE_VALUE) {
+ // Only fail the update if the last error was not a sharing violation.
+ if (lastWriteError != ERROR_SHARING_VIOLATION) {
+ LOG(("NS_main: callback app file in use, failed to exclusively open " \
+ "executable file: " LOG_S, argv[callbackIndex]));
+ LogFinish();
+ if (lastWriteError == ERROR_ACCESS_DENIED) {
+ WriteStatusFile(WRITE_ERROR_ACCESS_DENIED);
+ } else {
+ WriteStatusFile(WRITE_ERROR_CALLBACK_APP);
+ }
+
+ NS_tremove(gCallbackBackupPath);
+ EXIT_WHEN_ELEVATED(elevatedLockFilePath, updateLockFileHandle, 1);
+ LaunchCallbackApp(argv[5],
+ argc - callbackIndex,
+ argv + callbackIndex,
+ sUsingService);
+ return 1;
+ }
+ LOG(("NS_main: callback app file in use, continuing without " \
+ "exclusive access for executable file: " LOG_S,
+ argv[callbackIndex]));
+ }
+ }
+ }
+
+ // DELETE_DIR is not required when performing a staged update or replace
+ // request; it can be used during a replace request but then it doesn't
+ // use gDeleteDirPath.
+ if (!sStagedUpdate && !sReplaceRequest) {
+ // The directory to move files that are in use to on Windows. This directory
+ // will be deleted after the update is finished, on OS reboot using
+ // MoveFileEx if it contains files that are in use, or by the post update
+ // process after the update finishes. On Windows when performing a normal
+ // update (e.g. the update is not a staged update and is not a replace
+ // request) gWorkingDirPath is the same as gInstallDirPath and
+ // gWorkingDirPath is used because it is the destination directory.
+ NS_tsnprintf(gDeleteDirPath,
+ sizeof(gDeleteDirPath) / sizeof(gDeleteDirPath[0]),
+ NS_T("%s/%s"), gWorkingDirPath, DELETE_DIR);
+
+ if (NS_taccess(gDeleteDirPath, F_OK)) {
+ NS_tmkdir(gDeleteDirPath, 0755);
+ }
+ }
+#endif /* XP_WIN */
+
+ // Run update process on a background thread. ShowProgressUI may return
+ // before QuitProgressUI has been called, so wait for UpdateThreadFunc to
+ // terminate. Avoid showing the progress UI when staging an update, or if this
+ // is an elevated process on OSX.
+ Thread t;
+ if (t.Run(UpdateThreadFunc, nullptr) == 0) {
+ if (!sStagedUpdate && !sReplaceRequest
+#ifdef XP_MACOSX
+ && !isElevated
+#endif
+ ) {
+ ShowProgressUI();
+ }
+ }
+ t.Join();
+
+#ifdef XP_WIN
+ if (argc > callbackIndex && !sReplaceRequest) {
+ if (callbackFile != INVALID_HANDLE_VALUE) {
+ CloseHandle(callbackFile);
+ }
+ // Remove the copy of the callback executable.
+ NS_tremove(gCallbackBackupPath);
+ }
+
+ if (!sStagedUpdate && !sReplaceRequest && _wrmdir(gDeleteDirPath)) {
+ LOG(("NS_main: unable to remove directory: " LOG_S ", err: %d",
+ DELETE_DIR, errno));
+ // The directory probably couldn't be removed due to it containing files
+ // that are in use and will be removed on OS reboot. The call to remove the
+ // directory on OS reboot is done after the calls to remove the files so the
+ // files are removed first on OS reboot since the directory must be empty
+ // for the directory removal to be successful. The MoveFileEx call to remove
+ // the directory on OS reboot will fail if the process doesn't have write
+ // access to the HKEY_LOCAL_MACHINE registry key but this is ok since the
+ // installer / uninstaller will delete the directory along with its contents
+ // after an update is applied, on reinstall, and on uninstall.
+ if (MoveFileEx(gDeleteDirPath, nullptr, MOVEFILE_DELAY_UNTIL_REBOOT)) {
+ LOG(("NS_main: directory will be removed on OS reboot: " LOG_S,
+ DELETE_DIR));
+ } else {
+ LOG(("NS_main: failed to schedule OS reboot removal of " \
+ "directory: " LOG_S, DELETE_DIR));
+ }
+ }
+#endif /* XP_WIN */
+
+#if defined(MOZ_WIDGET_GONK)
+ } // end the extra level of scope for the GonkAutoMounter
+#endif
+
+#ifdef XP_MACOSX
+ // When the update is successful remove the precomplete file in the root of
+ // the application bundle and move the distribution directory from
+ // Contents/MacOS to Contents/Resources and if both exist delete the
+ // directory under Contents/MacOS (see Bug 1068439).
+ if (gSucceeded && !sStagedUpdate) {
+ NS_tchar oldPrecomplete[MAXPATHLEN];
+ NS_tsnprintf(oldPrecomplete, sizeof(oldPrecomplete)/sizeof(oldPrecomplete[0]),
+ NS_T("%s/precomplete"), gInstallDirPath);
+ NS_tremove(oldPrecomplete);
+
+ NS_tchar oldDistDir[MAXPATHLEN];
+ NS_tsnprintf(oldDistDir, sizeof(oldDistDir)/sizeof(oldDistDir[0]),
+ NS_T("%s/Contents/MacOS/distribution"), gInstallDirPath);
+ int rv = NS_taccess(oldDistDir, F_OK);
+ if (!rv) {
+ NS_tchar newDistDir[MAXPATHLEN];
+ NS_tsnprintf(newDistDir, sizeof(newDistDir)/sizeof(newDistDir[0]),
+ NS_T("%s/Contents/Resources/distribution"), gInstallDirPath);
+ rv = NS_taccess(newDistDir, F_OK);
+ if (!rv) {
+ LOG(("New distribution directory already exists... removing old " \
+ "distribution directory: " LOG_S, oldDistDir));
+ rv = ensure_remove_recursive(oldDistDir);
+ if (rv) {
+ LOG(("Removing old distribution directory failed - err: %d", rv));
+ }
+ } else {
+ LOG(("Moving old distribution directory to new location. src: " LOG_S \
+ ", dst:" LOG_S, oldDistDir, newDistDir));
+ rv = rename_file(oldDistDir, newDistDir, true);
+ if (rv) {
+ LOG(("Moving old distribution directory to new location failed - " \
+ "err: %d", rv));
+ }
+ }
+ }
+ }
+
+ if (isElevated) {
+ SetGroupOwnershipAndPermissions(gInstallDirPath);
+ freeArguments(argc, argv);
+ CleanupElevatedMacUpdate(false);
+ } else if (IsOwnedByGroupAdmin(gInstallDirPath)) {
+ // If the group ownership of the Firefox .app bundle was set to the "admin"
+ // group during a previous elevated update, we need to ensure that all files
+ // in the bundle have group ownership of "admin" as well as write permission
+ // for the group to not break updates in the future.
+ SetGroupOwnershipAndPermissions(gInstallDirPath);
+ }
+#endif /* XP_MACOSX */
+
+ LogFinish();
+
+ int retVal = LaunchCallbackAndPostProcessApps(argc, argv, callbackIndex
+#ifdef XP_WIN
+ , elevatedLockFilePath
+ , updateLockFileHandle
+#elif XP_MACOSX
+ , isElevated
+#endif
+ );
+
+ return retVal ? retVal : (gSucceeded ? 0 : 1);
+}
+
+class ActionList
+{
+public:
+ ActionList() : mFirst(nullptr), mLast(nullptr), mCount(0) { }
+ ~ActionList();
+
+ void Append(Action* action);
+ int Prepare();
+ int Execute();
+ void Finish(int status);
+
+private:
+ Action *mFirst;
+ Action *mLast;
+ int mCount;
+};
+
+ActionList::~ActionList()
+{
+ Action* a = mFirst;
+ while (a) {
+ Action *b = a;
+ a = a->mNext;
+ delete b;
+ }
+}
+
+void
+ActionList::Append(Action *action)
+{
+ if (mLast)
+ mLast->mNext = action;
+ else
+ mFirst = action;
+
+ mLast = action;
+ mCount++;
+}
+
+int
+ActionList::Prepare()
+{
+ // If the action list is empty then we should fail in order to signal that
+ // something has gone wrong. Otherwise we report success when nothing is
+ // actually done. See bug 327140.
+ if (mCount == 0) {
+ LOG(("empty action list"));
+ return MAR_ERROR_EMPTY_ACTION_LIST;
+ }
+
+ Action *a = mFirst;
+ int i = 0;
+ while (a) {
+ int rv = a->Prepare();
+ if (rv)
+ return rv;
+
+ float percent = float(++i) / float(mCount);
+ UpdateProgressUI(PROGRESS_PREPARE_SIZE * percent);
+
+ a = a->mNext;
+ }
+
+ return OK;
+}
+
+int
+ActionList::Execute()
+{
+ int currentProgress = 0, maxProgress = 0;
+ Action *a = mFirst;
+ while (a) {
+ maxProgress += a->mProgressCost;
+ a = a->mNext;
+ }
+
+ a = mFirst;
+ while (a) {
+ int rv = a->Execute();
+ if (rv) {
+ LOG(("### execution failed"));
+ return rv;
+ }
+
+ currentProgress += a->mProgressCost;
+ float percent = float(currentProgress) / float(maxProgress);
+ UpdateProgressUI(PROGRESS_PREPARE_SIZE +
+ PROGRESS_EXECUTE_SIZE * percent);
+
+ a = a->mNext;
+ }
+
+ return OK;
+}
+
+void
+ActionList::Finish(int status)
+{
+ Action *a = mFirst;
+ int i = 0;
+ while (a) {
+ a->Finish(status);
+
+ float percent = float(++i) / float(mCount);
+ UpdateProgressUI(PROGRESS_PREPARE_SIZE +
+ PROGRESS_EXECUTE_SIZE +
+ PROGRESS_FINISH_SIZE * percent);
+
+ a = a->mNext;
+ }
+
+ if (status == OK)
+ gSucceeded = true;
+}
+
+
+#ifdef XP_WIN
+int add_dir_entries(const NS_tchar *dirpath, ActionList *list)
+{
+ int rv = OK;
+ WIN32_FIND_DATAW finddata;
+ HANDLE hFindFile;
+ NS_tchar searchspec[MAXPATHLEN];
+ NS_tchar foundpath[MAXPATHLEN];
+
+ NS_tsnprintf(searchspec, sizeof(searchspec)/sizeof(searchspec[0]),
+ NS_T("%s*"), dirpath);
+ mozilla::UniquePtr<const NS_tchar> pszSpec(get_full_path(searchspec));
+
+ hFindFile = FindFirstFileW(pszSpec.get(), &finddata);
+ if (hFindFile != INVALID_HANDLE_VALUE) {
+ do {
+ // Don't process the current or parent directory.
+ if (NS_tstrcmp(finddata.cFileName, NS_T(".")) == 0 ||
+ NS_tstrcmp(finddata.cFileName, NS_T("..")) == 0)
+ continue;
+
+ NS_tsnprintf(foundpath, sizeof(foundpath)/sizeof(foundpath[0]),
+ NS_T("%s%s"), dirpath, finddata.cFileName);
+ if (finddata.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
+ NS_tsnprintf(foundpath, sizeof(foundpath)/sizeof(foundpath[0]),
+ NS_T("%s/"), foundpath);
+ // Recurse into the directory.
+ rv = add_dir_entries(foundpath, list);
+ if (rv) {
+ LOG(("add_dir_entries error: " LOG_S ", err: %d", foundpath, rv));
+ return rv;
+ }
+ } else {
+ // Add the file to be removed to the ActionList.
+ NS_tchar *quotedpath = get_quoted_path(foundpath);
+ if (!quotedpath)
+ return PARSE_ERROR;
+
+ Action *action = new RemoveFile();
+ rv = action->Parse(quotedpath);
+ if (rv) {
+ LOG(("add_dir_entries Parse error on recurse: " LOG_S ", err: %d",
+ quotedpath, rv));
+ return rv;
+ }
+ free(quotedpath);
+
+ list->Append(action);
+ }
+ } while (FindNextFileW(hFindFile, &finddata) != 0);
+
+ FindClose(hFindFile);
+ {
+ // Add the directory to be removed to the ActionList.
+ NS_tchar *quotedpath = get_quoted_path(dirpath);
+ if (!quotedpath)
+ return PARSE_ERROR;
+
+ Action *action = new RemoveDir();
+ rv = action->Parse(quotedpath);
+ if (rv) {
+ LOG(("add_dir_entries Parse error on close: " LOG_S ", err: %d",
+ quotedpath, rv));
+ } else {
+ list->Append(action);
+ }
+ free(quotedpath);
+ }
+ }
+
+ return rv;
+}
+
+#elif defined(SOLARIS)
+int add_dir_entries(const NS_tchar *dirpath, ActionList *list)
+{
+ int rv = OK;
+ NS_tchar foundpath[MAXPATHLEN];
+ struct {
+ dirent dent_buffer;
+ char chars[MAXNAMLEN];
+ } ent_buf;
+ struct dirent* ent;
+ mozilla::UniquePtr<NS_tchar[]> searchpath(get_full_path(dirpath));
+
+ DIR* dir = opendir(searchpath.get());
+ if (!dir) {
+ LOG(("add_dir_entries error on opendir: " LOG_S ", err: %d", searchpath.get(),
+ errno));
+ return UNEXPECTED_FILE_OPERATION_ERROR;
+ }
+
+ while (readdir_r(dir, (dirent *)&ent_buf, &ent) == 0 && ent) {
+ if ((strcmp(ent->d_name, ".") == 0) ||
+ (strcmp(ent->d_name, "..") == 0))
+ continue;
+
+ NS_tsnprintf(foundpath, sizeof(foundpath)/sizeof(foundpath[0]),
+ NS_T("%s%s"), searchpath.get(), ent->d_name);
+ struct stat64 st_buf;
+ int test = stat64(foundpath, &st_buf);
+ if (test) {
+ closedir(dir);
+ return UNEXPECTED_FILE_OPERATION_ERROR;
+ }
+ if (S_ISDIR(st_buf.st_mode)) {
+ NS_tsnprintf(foundpath, sizeof(foundpath)/sizeof(foundpath[0]),
+ NS_T("%s/"), foundpath);
+ // Recurse into the directory.
+ rv = add_dir_entries(foundpath, list);
+ if (rv) {
+ LOG(("add_dir_entries error: " LOG_S ", err: %d", foundpath, rv));
+ closedir(dir);
+ return rv;
+ }
+ } else {
+ // Add the file to be removed to the ActionList.
+ NS_tchar *quotedpath = get_quoted_path(get_relative_path(foundpath));
+ if (!quotedpath) {
+ closedir(dir);
+ return PARSE_ERROR;
+ }
+
+ Action *action = new RemoveFile();
+ rv = action->Parse(quotedpath);
+ if (rv) {
+ LOG(("add_dir_entries Parse error on recurse: " LOG_S ", err: %d",
+ quotedpath, rv));
+ closedir(dir);
+ return rv;
+ }
+
+ list->Append(action);
+ }
+ }
+ closedir(dir);
+
+ // Add the directory to be removed to the ActionList.
+ NS_tchar *quotedpath = get_quoted_path(get_relative_path(dirpath));
+ if (!quotedpath)
+ return PARSE_ERROR;
+
+ Action *action = new RemoveDir();
+ rv = action->Parse(quotedpath);
+ if (rv) {
+ LOG(("add_dir_entries Parse error on close: " LOG_S ", err: %d",
+ quotedpath, rv));
+ }
+ else {
+ list->Append(action);
+ }
+
+ return rv;
+}
+
+#else
+
+int add_dir_entries(const NS_tchar *dirpath, ActionList *list)
+{
+ int rv = OK;
+ FTS *ftsdir;
+ FTSENT *ftsdirEntry;
+ mozilla::UniquePtr<NS_tchar[]> searchpath(get_full_path(dirpath));
+
+ // Remove the trailing slash so the paths don't contain double slashes. The
+ // existence of the slash has already been checked in DoUpdate.
+ searchpath[NS_tstrlen(searchpath.get()) - 1] = NS_T('\0');
+ char* const pathargv[] = {searchpath.get(), nullptr};
+
+ // FTS_NOCHDIR is used so relative paths from the destination directory are
+ // returned.
+ if (!(ftsdir = fts_open(pathargv,
+ FTS_PHYSICAL | FTS_NOSTAT | FTS_XDEV | FTS_NOCHDIR,
+ nullptr)))
+ return UNEXPECTED_FILE_OPERATION_ERROR;
+
+ while ((ftsdirEntry = fts_read(ftsdir)) != nullptr) {
+ NS_tchar foundpath[MAXPATHLEN];
+ NS_tchar *quotedpath = nullptr;
+ Action *action = nullptr;
+
+ switch (ftsdirEntry->fts_info) {
+ // Filesystem objects that shouldn't be in the application's directories
+ case FTS_SL:
+ case FTS_SLNONE:
+ case FTS_DEFAULT:
+ LOG(("add_dir_entries: found a non-standard file: " LOG_S,
+ ftsdirEntry->fts_path));
+ // Fall through and try to remove as a file
+ MOZ_FALLTHROUGH;
+
+ // Files
+ case FTS_F:
+ case FTS_NSOK:
+ // Add the file to be removed to the ActionList.
+ NS_tsnprintf(foundpath, sizeof(foundpath)/sizeof(foundpath[0]),
+ NS_T("%s"), ftsdirEntry->fts_accpath);
+ quotedpath = get_quoted_path(get_relative_path(foundpath));
+ if (!quotedpath) {
+ rv = UPDATER_QUOTED_PATH_MEM_ERROR;
+ break;
+ }
+ action = new RemoveFile();
+ rv = action->Parse(quotedpath);
+ free(quotedpath);
+ if (!rv)
+ list->Append(action);
+ break;
+
+ // Directories
+ case FTS_DP:
+ rv = OK;
+ // Add the directory to be removed to the ActionList.
+ NS_tsnprintf(foundpath, sizeof(foundpath)/sizeof(foundpath[0]),
+ NS_T("%s/"), ftsdirEntry->fts_accpath);
+ quotedpath = get_quoted_path(get_relative_path(foundpath));
+ if (!quotedpath) {
+ rv = UPDATER_QUOTED_PATH_MEM_ERROR;
+ break;
+ }
+
+ action = new RemoveDir();
+ rv = action->Parse(quotedpath);
+ free(quotedpath);
+ if (!rv)
+ list->Append(action);
+ break;
+
+ // Errors
+ case FTS_DNR:
+ case FTS_NS:
+ // ENOENT is an acceptable error for FTS_DNR and FTS_NS and means that
+ // we're racing with ourselves. Though strange, the entry will be
+ // removed anyway.
+ if (ENOENT == ftsdirEntry->fts_errno) {
+ rv = OK;
+ break;
+ }
+ MOZ_FALLTHROUGH;
+
+ case FTS_ERR:
+ rv = UNEXPECTED_FILE_OPERATION_ERROR;
+ LOG(("add_dir_entries: fts_read() error: " LOG_S ", err: %d",
+ ftsdirEntry->fts_path, ftsdirEntry->fts_errno));
+ break;
+
+ case FTS_DC:
+ rv = UNEXPECTED_FILE_OPERATION_ERROR;
+ LOG(("add_dir_entries: fts_read() returned FT_DC: " LOG_S,
+ ftsdirEntry->fts_path));
+ break;
+
+ default:
+ // FTS_D is ignored and FTS_DP is used instead (post-order).
+ rv = OK;
+ break;
+ }
+
+ if (rv != OK)
+ break;
+ }
+
+ fts_close(ftsdir);
+
+ return rv;
+}
+#endif
+
+static NS_tchar*
+GetManifestContents(const NS_tchar *manifest)
+{
+ AutoFile mfile(NS_tfopen(manifest, NS_T("rb")));
+ if (mfile == nullptr) {
+ LOG(("GetManifestContents: error opening manifest file: " LOG_S, manifest));
+ return nullptr;
+ }
+
+ struct stat ms;
+ int rv = fstat(fileno((FILE *)mfile), &ms);
+ if (rv) {
+ LOG(("GetManifestContents: error stating manifest file: " LOG_S, manifest));
+ return nullptr;
+ }
+
+ char *mbuf = (char *) malloc(ms.st_size + 1);
+ if (!mbuf)
+ return nullptr;
+
+ size_t r = ms.st_size;
+ char *rb = mbuf;
+ while (r) {
+ const size_t count = mmin(SSIZE_MAX, r);
+ size_t c = fread(rb, 1, count, mfile);
+ if (c != count) {
+ LOG(("GetManifestContents: error reading manifest file: " LOG_S, manifest));
+ free(mbuf);
+ return nullptr;
+ }
+
+ r -= c;
+ rb += c;
+ }
+ mbuf[ms.st_size] = '\0';
+ rb = mbuf;
+
+#ifndef XP_WIN
+ return rb;
+#else
+ NS_tchar *wrb = (NS_tchar *) malloc((ms.st_size + 1) * sizeof(NS_tchar));
+ if (!wrb) {
+ free(mbuf);
+ return nullptr;
+ }
+
+ if (!MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, rb, -1, wrb,
+ ms.st_size + 1)) {
+ LOG(("GetManifestContents: error converting utf8 to utf16le: %d", GetLastError()));
+ free(mbuf);
+ free(wrb);
+ return nullptr;
+ }
+ free(mbuf);
+
+ return wrb;
+#endif
+}
+
+int AddPreCompleteActions(ActionList *list)
+{
+ if (sIsOSUpdate) {
+ return OK;
+ }
+
+#ifdef XP_MACOSX
+ mozilla::UniquePtr<NS_tchar[]> manifestPath(get_full_path(
+ NS_T("Contents/Resources/precomplete")));
+#else
+ mozilla::UniquePtr<NS_tchar[]> manifestPath(get_full_path(
+ NS_T("precomplete")));
+#endif
+
+ NS_tchar *rb = GetManifestContents(manifestPath.get());
+ if (rb == nullptr) {
+ LOG(("AddPreCompleteActions: error getting contents of precomplete " \
+ "manifest"));
+ // Applications aren't required to have a precomplete manifest. The mar
+ // generation scripts enforce the presence of a precomplete manifest.
+ return OK;
+ }
+
+ int rv;
+ NS_tchar *line;
+ while((line = mstrtok(kNL, &rb)) != 0) {
+ // skip comments
+ if (*line == NS_T('#'))
+ continue;
+
+ NS_tchar *token = mstrtok(kWhitespace, &line);
+ if (!token) {
+ LOG(("AddPreCompleteActions: token not found in manifest"));
+ return PARSE_ERROR;
+ }
+
+ Action *action = nullptr;
+ if (NS_tstrcmp(token, NS_T("remove")) == 0) { // rm file
+ action = new RemoveFile();
+ }
+ else if (NS_tstrcmp(token, NS_T("remove-cc")) == 0) { // no longer supported
+ continue;
+ }
+ else if (NS_tstrcmp(token, NS_T("rmdir")) == 0) { // rmdir if empty
+ action = new RemoveDir();
+ }
+ else {
+ LOG(("AddPreCompleteActions: unknown token: " LOG_S, token));
+ return PARSE_ERROR;
+ }
+
+ if (!action)
+ return BAD_ACTION_ERROR;
+
+ rv = action->Parse(line);
+ if (rv)
+ return rv;
+
+ list->Append(action);
+ }
+
+ return OK;
+}
+
+int DoUpdate()
+{
+ NS_tchar manifest[MAXPATHLEN];
+ NS_tsnprintf(manifest, sizeof(manifest)/sizeof(manifest[0]),
+ NS_T("%s/updating/update.manifest"), gWorkingDirPath);
+ ensure_parent_dir(manifest);
+
+ // extract the manifest
+ int rv = gArchiveReader.ExtractFile("updatev3.manifest", manifest);
+ if (rv) {
+ rv = gArchiveReader.ExtractFile("updatev2.manifest", manifest);
+ if (rv) {
+ LOG(("DoUpdate: error extracting manifest file"));
+ return rv;
+ }
+ }
+
+ NS_tchar *rb = GetManifestContents(manifest);
+ NS_tremove(manifest);
+ if (rb == nullptr) {
+ LOG(("DoUpdate: error opening manifest file: " LOG_S, manifest));
+ return READ_ERROR;
+ }
+
+
+ ActionList list;
+ NS_tchar *line;
+ bool isFirstAction = true;
+
+ while((line = mstrtok(kNL, &rb)) != 0) {
+ // skip comments
+ if (*line == NS_T('#'))
+ continue;
+
+ NS_tchar *token = mstrtok(kWhitespace, &line);
+ if (!token) {
+ LOG(("DoUpdate: token not found in manifest"));
+ return PARSE_ERROR;
+ }
+
+ if (isFirstAction) {
+ isFirstAction = false;
+ // The update manifest isn't required to have a type declaration. The mar
+ // generation scripts enforce the presence of the type declaration.
+ if (NS_tstrcmp(token, NS_T("type")) == 0) {
+ const NS_tchar *type = mstrtok(kQuote, &line);
+ LOG(("UPDATE TYPE " LOG_S, type));
+ if (NS_tstrcmp(type, NS_T("complete")) == 0) {
+ rv = AddPreCompleteActions(&list);
+ if (rv)
+ return rv;
+ }
+ continue;
+ }
+ }
+
+ Action *action = nullptr;
+ if (NS_tstrcmp(token, NS_T("remove")) == 0) { // rm file
+ action = new RemoveFile();
+ }
+ else if (NS_tstrcmp(token, NS_T("rmdir")) == 0) { // rmdir if empty
+ action = new RemoveDir();
+ }
+ else if (NS_tstrcmp(token, NS_T("rmrfdir")) == 0) { // rmdir recursive
+ const NS_tchar *reldirpath = mstrtok(kQuote, &line);
+ if (!reldirpath)
+ return PARSE_ERROR;
+
+ if (reldirpath[NS_tstrlen(reldirpath) - 1] != NS_T('/'))
+ return PARSE_ERROR;
+
+ rv = add_dir_entries(reldirpath, &list);
+ if (rv)
+ return rv;
+
+ continue;
+ }
+ else if (NS_tstrcmp(token, NS_T("add")) == 0) {
+ action = new AddFile();
+ }
+ else if (NS_tstrcmp(token, NS_T("patch")) == 0) {
+ action = new PatchFile();
+ }
+ else if (NS_tstrcmp(token, NS_T("add-if")) == 0) { // Add if exists
+ action = new AddIfFile();
+ }
+ else if (NS_tstrcmp(token, NS_T("add-if-not")) == 0) { // Add if not exists
+ action = new AddIfNotFile();
+ }
+ else if (NS_tstrcmp(token, NS_T("patch-if")) == 0) { // Patch if exists
+ action = new PatchIfFile();
+ }
+ else {
+ LOG(("DoUpdate: unknown token: " LOG_S, token));
+ return PARSE_ERROR;
+ }
+
+ if (!action)
+ return BAD_ACTION_ERROR;
+
+ rv = action->Parse(line);
+ if (rv)
+ return rv;
+
+ list.Append(action);
+ }
+
+ rv = list.Prepare();
+ if (rv)
+ return rv;
+
+ rv = list.Execute();
+
+ list.Finish(rv);
+ return rv;
+}
diff --git a/toolkit/mozapps/update/updater/updater.exe.comctl32.manifest b/toolkit/mozapps/update/updater/updater.exe.comctl32.manifest
new file mode 100644
index 000000000..9a6cdb565
--- /dev/null
+++ b/toolkit/mozapps/update/updater/updater.exe.comctl32.manifest
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
+<assemblyIdentity
+ version="1.0.0.0"
+ processorArchitecture="*"
+ name="Updater"
+ type="win32"
+/>
+<description>Updater</description>
+<dependency>
+ <dependentAssembly>
+ <assemblyIdentity
+ type="win32"
+ name="Microsoft.Windows.Common-Controls"
+ version="6.0.0.0"
+ processorArchitecture="*"
+ publicKeyToken="6595b64144ccf1df"
+ language="*"
+ />
+ </dependentAssembly>
+</dependency>
+<ms_asmv3:trustInfo xmlns:ms_asmv3="urn:schemas-microsoft-com:asm.v3">
+ <ms_asmv3:security>
+ <ms_asmv3:requestedPrivileges>
+ <ms_asmv3:requestedExecutionLevel level="asInvoker" uiAccess="false" />
+ </ms_asmv3:requestedPrivileges>
+ </ms_asmv3:security>
+</ms_asmv3:trustInfo>
+ <compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
+ <application>
+ <supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"/>
+ <supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}"/>
+ <supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}"/>
+ <supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}"/>
+ <supportedOS Id="{e2011457-1546-43c5-a5fe-008deee3d3f0}"/>
+ </application>
+ </compatibility>
+</assembly>
diff --git a/toolkit/mozapps/update/updater/updater.exe.manifest b/toolkit/mozapps/update/updater/updater.exe.manifest
new file mode 100644
index 000000000..cd229c954
--- /dev/null
+++ b/toolkit/mozapps/update/updater/updater.exe.manifest
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
+<assemblyIdentity
+ version="1.0.0.0"
+ processorArchitecture="*"
+ name="Updater"
+ type="win32"
+/>
+<description>Updater</description>
+<ms_asmv3:trustInfo xmlns:ms_asmv3="urn:schemas-microsoft-com:asm.v3">
+ <ms_asmv3:security>
+ <ms_asmv3:requestedPrivileges>
+ <ms_asmv3:requestedExecutionLevel level="asInvoker" uiAccess="false" />
+ </ms_asmv3:requestedPrivileges>
+ </ms_asmv3:security>
+</ms_asmv3:trustInfo>
+ <compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
+ <application>
+ <supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"/>
+ <supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}"/>
+ <supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}"/>
+ <supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}"/>
+ <supportedOS Id="{e2011457-1546-43c5-a5fe-008deee3d3f0}"/>
+ </application>
+ </compatibility>
+</assembly>
diff --git a/toolkit/mozapps/update/updater/updater.ico b/toolkit/mozapps/update/updater/updater.ico
new file mode 100644
index 000000000..48457029d
--- /dev/null
+++ b/toolkit/mozapps/update/updater/updater.ico
Binary files differ
diff --git a/toolkit/mozapps/update/updater/updater.png b/toolkit/mozapps/update/updater/updater.png
new file mode 100644
index 000000000..7b5e78907
--- /dev/null
+++ b/toolkit/mozapps/update/updater/updater.png
Binary files differ
diff --git a/toolkit/mozapps/update/updater/updater.rc b/toolkit/mozapps/update/updater/updater.rc
new file mode 100644
index 000000000..7603eecb6
--- /dev/null
+++ b/toolkit/mozapps/update/updater/updater.rc
@@ -0,0 +1,137 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// Microsoft Visual C++ generated resource script.
+//
+#ifdef TEST_UPDATER
+#include "../resource.h"
+#define MANIFEST_PATH "../updater.exe.manifest"
+#define COMCTL32_MANIFEST_PATH "../updater.exe.comctl32.manifest"
+#define ICON_PATH "../updater.ico"
+#else
+#include "resource.h"
+#define MANIFEST_PATH "updater.exe.manifest"
+#define COMCTL32_MANIFEST_PATH "updater.exe.comctl32.manifest"
+#define ICON_PATH "updater.ico"
+#endif
+
+#define APSTUDIO_READONLY_SYMBOLS
+/////////////////////////////////////////////////////////////////////////////
+//
+// Generated from the TEXTINCLUDE 2 resource.
+//
+#include "winresrc.h"
+
+/////////////////////////////////////////////////////////////////////////////
+#undef APSTUDIO_READONLY_SYMBOLS
+
+/////////////////////////////////////////////////////////////////////////////
+// English (U.S.) resources
+
+#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU)
+#ifdef _WIN32
+LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
+#pragma code_page(1252)
+#endif //_WIN32
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// RT_MANIFEST
+//
+
+1 RT_MANIFEST MANIFEST_PATH
+IDR_COMCTL32_MANIFEST RT_MANIFEST COMCTL32_MANIFEST_PATH
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Icon
+//
+
+IDI_DIALOG ICON ICON_PATH
+
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Embedded an identifier to uniquely identiy this as a Mozilla updater.
+//
+
+STRINGTABLE
+{
+ IDS_UPDATER_IDENTITY, "moz-updater.exe-4cdccec4-5ee0-4a06-9817-4cd899a9db49"
+}
+
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Dialog
+//
+
+IDD_DIALOG DIALOGEX 0, 0, 253, 41
+STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION
+FONT 8, "MS Shell Dlg", 400, 0, 0x1
+BEGIN
+ CONTROL "",IDC_PROGRESS,"msctls_progress32",WS_BORDER,7,24,239,10
+ LTEXT "",IDC_INFO,7,8,239,13,SS_NOPREFIX
+END
+
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// DESIGNINFO
+//
+
+#ifdef APSTUDIO_INVOKED
+GUIDELINES DESIGNINFO
+BEGIN
+ IDD_DIALOG, DIALOG
+ BEGIN
+ LEFTMARGIN, 7
+ RIGHTMARGIN, 246
+ TOPMARGIN, 7
+ BOTTOMMARGIN, 39
+ END
+END
+#endif // APSTUDIO_INVOKED
+
+
+#ifdef APSTUDIO_INVOKED
+/////////////////////////////////////////////////////////////////////////////
+//
+// TEXTINCLUDE
+//
+
+1 TEXTINCLUDE
+BEGIN
+ "resource.h\0"
+END
+
+2 TEXTINCLUDE
+BEGIN
+ "#include ""winresrc.h""\r\n"
+ "\0"
+END
+
+3 TEXTINCLUDE
+BEGIN
+ "\r\n"
+ "\0"
+END
+
+#endif // APSTUDIO_INVOKED
+
+#endif // English (U.S.) resources
+/////////////////////////////////////////////////////////////////////////////
+
+
+
+#ifndef APSTUDIO_INVOKED
+/////////////////////////////////////////////////////////////////////////////
+//
+// Generated from the TEXTINCLUDE 3 resource.
+//
+
+
+/////////////////////////////////////////////////////////////////////////////
+#endif // not APSTUDIO_INVOKED
+
diff --git a/toolkit/mozapps/update/updater/win_dirent.cpp b/toolkit/mozapps/update/updater/win_dirent.cpp
new file mode 100644
index 000000000..b0807ba5e
--- /dev/null
+++ b/toolkit/mozapps/update/updater/win_dirent.cpp
@@ -0,0 +1,78 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "win_dirent.h"
+#include <errno.h>
+#include <string.h>
+
+// This file implements the minimum set of dirent APIs used by updater.cpp on
+// Windows. If updater.cpp is modified to use more of this API, we need to
+// implement those parts here too.
+
+static dirent gDirEnt;
+
+DIR::DIR(const WCHAR* path)
+ : findHandle(INVALID_HANDLE_VALUE)
+{
+ memset(name, 0, sizeof(name));
+ wcsncpy(name, path, sizeof(name)/sizeof(name[0]));
+ wcsncat(name, L"\\*", sizeof(name)/sizeof(name[0]) - wcslen(name) - 1);
+}
+
+DIR::~DIR()
+{
+ if (findHandle != INVALID_HANDLE_VALUE) {
+ FindClose(findHandle);
+ }
+}
+
+dirent::dirent()
+{
+ d_name[0] = L'\0';
+}
+
+DIR*
+opendir(const WCHAR* path)
+{
+ return new DIR(path);
+}
+
+int
+closedir(DIR* dir)
+{
+ delete dir;
+ return 0;
+}
+
+dirent* readdir(DIR* dir)
+{
+ WIN32_FIND_DATAW data;
+ if (dir->findHandle != INVALID_HANDLE_VALUE) {
+ BOOL result = FindNextFileW(dir->findHandle, &data);
+ if (!result) {
+ if (GetLastError() != ERROR_FILE_NOT_FOUND) {
+ errno = ENOENT;
+ }
+ return 0;
+ }
+ } else {
+ // Reading the first directory entry
+ dir->findHandle = FindFirstFileW(dir->name, &data);
+ if (dir->findHandle == INVALID_HANDLE_VALUE) {
+ if (GetLastError() == ERROR_FILE_NOT_FOUND) {
+ errno = ENOENT;
+ } else {
+ errno = EBADF;
+ }
+ return 0;
+ }
+ }
+ memset(gDirEnt.d_name, 0, sizeof(gDirEnt.d_name));
+ wcsncpy(gDirEnt.d_name, data.cFileName,
+ sizeof(gDirEnt.d_name)/sizeof(gDirEnt.d_name[0]));
+ return &gDirEnt;
+}
+
diff --git a/toolkit/mozapps/update/updater/xpcshellCertificate.der b/toolkit/mozapps/update/updater/xpcshellCertificate.der
new file mode 100644
index 000000000..185b2dff4
--- /dev/null
+++ b/toolkit/mozapps/update/updater/xpcshellCertificate.der
Binary files differ