Bug 1791785 - When dumping symbols never emit INLINE_ORIGIN directives with an empty...
[gecko.git] / browser / modules / AppUpdater.jsm
blob1a29bcb44e11e7adce61afc853081ba76854c388
1 /* This Source Code Form is subject to the terms of the Mozilla Public
2  * License, v. 2.0. If a copy of the MPL was not distributed with this
3  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 "use strict";
7 var EXPORTED_SYMBOLS = ["AppUpdater"];
9 var { XPCOMUtils } = ChromeUtils.importESModule(
10   "resource://gre/modules/XPCOMUtils.sys.mjs"
13 var gLogfileOutputStream;
15 const { AppConstants } = ChromeUtils.import(
16   "resource://gre/modules/AppConstants.jsm"
18 const { FileUtils } = ChromeUtils.import(
19   "resource://gre/modules/FileUtils.jsm"
21 const PREF_APP_UPDATE_LOG = "app.update.log";
22 const PREF_APP_UPDATE_LOG_FILE = "app.update.log.file";
23 const KEY_PROFILE_DIR = "ProfD";
24 const FILE_UPDATE_MESSAGES = "update_messages.log";
25 const lazy = {};
26 XPCOMUtils.defineLazyModuleGetters(lazy, {
27   UpdateUtils: "resource://gre/modules/UpdateUtils.jsm",
28 });
29 XPCOMUtils.defineLazyGetter(lazy, "gLogEnabled", function aus_gLogEnabled() {
30   return (
31     Services.prefs.getBoolPref(PREF_APP_UPDATE_LOG, false) ||
32     Services.prefs.getBoolPref(PREF_APP_UPDATE_LOG_FILE, false)
33   );
34 });
35 XPCOMUtils.defineLazyGetter(
36   lazy,
37   "gLogfileEnabled",
38   function aus_gLogfileEnabled() {
39     return Services.prefs.getBoolPref(PREF_APP_UPDATE_LOG_FILE, false);
40   }
43 const PREF_APP_UPDATE_CANCELATIONS_OSX = "app.update.cancelations.osx";
44 const PREF_APP_UPDATE_ELEVATE_NEVER = "app.update.elevate.never";
46 /**
47  * This class checks for app updates in the foreground.  It has several public
48  * methods for checking for updates, downloading updates, stopping the current
49  * update, and getting the current update status.  It can also register
50  * listeners that will be called back as different stages of updates occur.
51  */
52 class AppUpdater {
53   constructor() {
54     this._listeners = new Set();
55     XPCOMUtils.defineLazyServiceGetter(
56       this,
57       "aus",
58       "@mozilla.org/updates/update-service;1",
59       "nsIApplicationUpdateService"
60     );
61     XPCOMUtils.defineLazyServiceGetter(
62       this,
63       "checker",
64       "@mozilla.org/updates/update-checker;1",
65       "nsIUpdateChecker"
66     );
67     XPCOMUtils.defineLazyServiceGetter(
68       this,
69       "um",
70       "@mozilla.org/updates/update-manager;1",
71       "nsIUpdateManager"
72     );
73     this.QueryInterface = ChromeUtils.generateQI([
74       "nsIObserver",
75       "nsIProgressEventSink",
76       "nsIRequestObserver",
77       "nsISupportsWeakReference",
78     ]);
79     Services.obs.addObserver(this, "update-swap", /* ownsWeak */ true);
81     // This one call observes PREF_APP_UPDATE_LOG and PREF_APP_UPDATE_LOG_FILE
82     Services.prefs.addObserver(PREF_APP_UPDATE_LOG, this);
83   }
85   /**
86    * The main entry point for checking for updates.  As different stages of the
87    * check and possible subsequent update occur, the updater's status is set and
88    * listeners are called.
89    */
90   check() {
91     if (!AppConstants.MOZ_UPDATER || this.updateDisabledByPackage) {
92       LOG(
93         "AppUpdater:check -" +
94           "AppConstants.MOZ_UPDATER=" +
95           AppConstants.MOZ_UPDATER +
96           "this.updateDisabledByPackage: " +
97           this.updateDisabledByPackage
98       );
99       this._setStatus(AppUpdater.STATUS.NO_UPDATER);
100       return;
101     }
103     if (this.updateDisabledByPolicy) {
104       LOG("AppUpdater:check - this.updateDisabledByPolicy");
105       this._setStatus(AppUpdater.STATUS.UPDATE_DISABLED_BY_POLICY);
106       return;
107     }
109     if (this.isReadyForRestart) {
110       LOG("AppUpdater:check - this.isReadyForRestart");
111       this._setStatus(AppUpdater.STATUS.READY_FOR_RESTART);
112       return;
113     }
115     if (this.aus.isOtherInstanceHandlingUpdates) {
116       LOG("AppUpdater:check - this.aus.isOtherInstanceHandlingUpdates");
117       this._setStatus(AppUpdater.STATUS.OTHER_INSTANCE_HANDLING_UPDATES);
118       return;
119     }
121     if (this.isDownloading) {
122       LOG("AppUpdater:check - this.isDownloading");
123       this.startDownload();
124       return;
125     }
127     if (this.isStaging) {
128       LOG("AppUpdater:check - this.isStaging");
129       this._waitForUpdateToStage();
130       return;
131     }
133     // We might need this value later, so start loading it from the disk now.
134     this.promiseAutoUpdateSetting = lazy.UpdateUtils.getAppUpdateAutoEnabled();
136     // That leaves the options
137     // "Check for updates, but let me choose whether to install them", and
138     // "Automatically install updates".
139     // In both cases, we check for updates without asking.
140     // In the "let me choose" case, we ask before downloading though, in onCheckComplete.
141     this.checkForUpdates();
142   }
144   // true when there is an update ready to be applied on restart or staged.
145   get isPending() {
146     if (this.update) {
147       return (
148         this.update.state == "pending" ||
149         this.update.state == "pending-service" ||
150         this.update.state == "pending-elevate"
151       );
152     }
153     return (
154       this.um.readyUpdate &&
155       (this.um.readyUpdate.state == "pending" ||
156         this.um.readyUpdate.state == "pending-service" ||
157         this.um.readyUpdate.state == "pending-elevate")
158     );
159   }
161   // true when there is an update already staged.
162   get isApplied() {
163     if (this.update) {
164       return (
165         this.update.state == "applied" || this.update.state == "applied-service"
166       );
167     }
168     return (
169       this.um.readyUpdate &&
170       (this.um.readyUpdate.state == "applied" ||
171         this.um.readyUpdate.state == "applied-service")
172     );
173   }
175   get isStaging() {
176     if (!this.updateStagingEnabled) {
177       return false;
178     }
179     let errorCode;
180     if (this.update) {
181       errorCode = this.update.errorCode;
182     } else if (this.um.readyUpdate) {
183       errorCode = this.um.readyUpdate.errorCode;
184     }
185     // If the state is pending and the error code is not 0, staging must have
186     // failed.
187     return this.isPending && errorCode == 0;
188   }
190   // true when an update ready to restart to finish the update process.
191   get isReadyForRestart() {
192     if (this.updateStagingEnabled) {
193       let errorCode;
194       if (this.update) {
195         errorCode = this.update.errorCode;
196       } else if (this.um.readyUpdate) {
197         errorCode = this.um.readyUpdate.errorCode;
198       }
199       // If the state is pending and the error code is not 0, staging must have
200       // failed and Firefox should be restarted to try to apply the update
201       // without staging.
202       return this.isApplied || (this.isPending && errorCode != 0);
203     }
204     return this.isPending;
205   }
207   // true when there is an update download in progress.
208   get isDownloading() {
209     if (this.update) {
210       return this.update.state == "downloading";
211     }
212     return (
213       this.um.downloadingUpdate &&
214       this.um.downloadingUpdate.state == "downloading"
215     );
216   }
218   // true when updating has been disabled by enterprise policy
219   get updateDisabledByPolicy() {
220     return Services.policies && !Services.policies.isAllowed("appUpdate");
221   }
223   // true if updating is disabled because we're running in an app package.
224   // This is distinct from updateDisabledByPolicy because we need to avoid
225   // messages being shown to the user about an "administrator" handling
226   // updates; packaged apps may be getting updated by an administrator or they
227   // may not be, and we don't have a good way to tell the difference from here,
228   // so we err to the side of less confusion for unmanaged users.
229   get updateDisabledByPackage() {
230     try {
231       return Services.sysinfo.getProperty("hasWinPackageId");
232     } catch (_ex) {
233       // The hasWinPackageId property doesn't exist; assume it would be false.
234     }
235     return false;
236   }
238   // true when updating in background is enabled.
239   get updateStagingEnabled() {
240     LOG(
241       "AppUpdater:updateStagingEnabled" +
242         "canStageUpdates: " +
243         this.aus.canStageUpdates
244     );
245     return (
246       !this.updateDisabledByPolicy &&
247       !this.updateDisabledByPackage &&
248       this.aus.canStageUpdates
249     );
250   }
252   /**
253    * Check for updates
254    */
255   checkForUpdates() {
256     // Clear prefs that could prevent a user from discovering available updates.
257     if (Services.prefs.prefHasUserValue(PREF_APP_UPDATE_CANCELATIONS_OSX)) {
258       Services.prefs.clearUserPref(PREF_APP_UPDATE_CANCELATIONS_OSX);
259     }
260     if (Services.prefs.prefHasUserValue(PREF_APP_UPDATE_ELEVATE_NEVER)) {
261       Services.prefs.clearUserPref(PREF_APP_UPDATE_ELEVATE_NEVER);
262     }
263     this._setStatus(AppUpdater.STATUS.CHECKING);
264     this.checker.checkForUpdates(this._updateCheckListener, true);
265     // after checking, onCheckComplete() is called
266     LOG("AppUpdater:checkForUpdates - waiting for onCheckComplete()");
267   }
269   /**
270    * Implements nsIUpdateCheckListener. The methods implemented by
271    * nsIUpdateCheckListener are in a different scope from nsIIncrementalDownload
272    * to make it clear which are used by each interface.
273    */
274   get _updateCheckListener() {
275     if (!this.__updateCheckListener) {
276       this.__updateCheckListener = {
277         /**
278          * See nsIUpdateService.idl
279          */
280         onCheckComplete: async (aRequest, aUpdates) => {
281           LOG("AppUpdater:_updateCheckListener:onCheckComplete - reached.");
282           this.update = this.aus.selectUpdate(aUpdates);
283           if (!this.update) {
284             LOG(
285               "AppUpdater:_updateCheckListener:onCheckComplete - result: " +
286                 "NO_UPDATES_FOUND"
287             );
288             this._setStatus(AppUpdater.STATUS.NO_UPDATES_FOUND);
289             return;
290           }
292           if (this.update.unsupported) {
293             LOG(
294               "AppUpdater:_updateCheckListener:onCheckComplete - result: " +
295                 "UNSUPPORTED SYSTEM"
296             );
297             this._setStatus(AppUpdater.STATUS.UNSUPPORTED_SYSTEM);
298             return;
299           }
301           if (!this.aus.canApplyUpdates) {
302             LOG(
303               "AppUpdater:_updateCheckListener:onCheckComplete - result: " +
304                 "MANUAL_UPDATE"
305             );
306             this._setStatus(AppUpdater.STATUS.MANUAL_UPDATE);
307             return;
308           }
310           if (!this.promiseAutoUpdateSetting) {
311             this.promiseAutoUpdateSetting = lazy.UpdateUtils.getAppUpdateAutoEnabled();
312           }
313           this.promiseAutoUpdateSetting.then(updateAuto => {
314             if (updateAuto && !this.aus.manualUpdateOnly) {
315               LOG(
316                 "AppUpdater:_updateCheckListener:onCheckComplete - " +
317                   "updateAuto is active and " +
318                   "manualUpdateOnlydateOnly is inactive." +
319                   "start the download."
320               );
321               // automatically download and install
322               this.startDownload();
323             } else {
324               // ask
325               this._setStatus(AppUpdater.STATUS.DOWNLOAD_AND_INSTALL);
326             }
327           });
328         },
330         /**
331          * See nsIUpdateService.idl
332          */
333         onError: async (aRequest, aUpdate) => {
334           // Errors in the update check are treated as no updates found. If the
335           // update check fails repeatedly without a success the user will be
336           // notified with the normal app update user interface so this is safe.
337           LOG("AppUpdater:_updateCheckListener:onError: NO_UPDATES_FOUND");
338           this._setStatus(AppUpdater.STATUS.NO_UPDATES_FOUND);
339         },
341         /**
342          * See nsISupports.idl
343          */
344         QueryInterface: ChromeUtils.generateQI(["nsIUpdateCheckListener"]),
345       };
346     }
347     return this.__updateCheckListener;
348   }
350   /**
351    * Sets the status to STAGING.  The status will then be set again when the
352    * update finishes staging.
353    */
354   _waitForUpdateToStage() {
355     if (!this.update) {
356       this.update = this.um.readyUpdate;
357     }
358     this.update.QueryInterface(Ci.nsIWritablePropertyBag);
359     this.update.setProperty("foregroundDownload", "true");
360     this._setStatus(AppUpdater.STATUS.STAGING);
361     this._awaitStagingComplete();
362   }
364   /**
365    * Starts the download of an update mar.
366    */
367   startDownload() {
368     if (!this.update) {
369       this.update = this.um.downloadingUpdate;
370     }
371     this.update.QueryInterface(Ci.nsIWritablePropertyBag);
372     this.update.setProperty("foregroundDownload", "true");
374     let success = this.aus.downloadUpdate(this.update, false);
375     if (!success) {
376       LOG("AppUpdater:startDownload - downloadUpdate failed.");
377       this._setStatus(AppUpdater.STATUS.DOWNLOAD_FAILED);
378       return;
379     }
381     this._setupDownloadListener();
382   }
384   /**
385    * Starts tracking the download.
386    */
387   _setupDownloadListener() {
388     this._setStatus(AppUpdater.STATUS.DOWNLOADING);
389     this.aus.addDownloadListener(this);
390     LOG("AppUpdater:_setupDownloadListener - registered a download listener");
391   }
393   /**
394    * See nsIRequestObserver.idl
395    */
396   onStartRequest(aRequest) {
397     LOG("AppUpdater:onStartRequest - aRequest: " + aRequest);
398   }
400   /**
401    * See nsIRequestObserver.idl
402    */
403   onStopRequest(aRequest, aStatusCode) {
404     LOG(
405       "AppUpdater:onStopRequest " +
406         "- aRequest: " +
407         aRequest +
408         ", aStatusCode: " +
409         aStatusCode
410     );
411     switch (aStatusCode) {
412       case Cr.NS_ERROR_UNEXPECTED:
413         if (
414           this.update.selectedPatch.state == "download-failed" &&
415           (this.update.isCompleteUpdate || this.update.patchCount != 2)
416         ) {
417           // Verification error of complete patch, informational text is held in
418           // the update object.
419           this.aus.removeDownloadListener(this);
420           LOG(
421             "AppUpdater:onStopRequest " +
422               "- download failed with unexpected error" +
423               ", removed download listener"
424           );
425           this._setStatus(AppUpdater.STATUS.DOWNLOAD_FAILED);
426           break;
427         }
428         // Verification failed for a partial patch, complete patch is now
429         // downloading so return early and do NOT remove the download listener!
430         break;
431       case Cr.NS_BINDING_ABORTED:
432         // Do not remove UI listener since the user may resume downloading again.
433         break;
434       case Cr.NS_OK:
435         this.aus.removeDownloadListener(this);
436         LOG(
437           "AppUpdater:onStopRequest " +
438             "- download ok" +
439             ", removed download listener"
440         );
441         if (this.updateStagingEnabled) {
442           // It could be that another instance was started during the download,
443           // and if that happened, then we actually should not advance to the
444           // STAGING status because the staging process isn't really happening
445           // until that instance exits (or we time out waiting).
446           if (this.aus.isOtherInstanceHandlingUpdates) {
447             LOG(
448               "AppUpdater:onStopRequest " +
449                 "- aStatusCode=Cr.NS_OK" +
450                 ", another instance is handling updates"
451             );
452             this._setStatus(AppUpdater.OTHER_INSTANCE_HANDLING_UPDATES);
453           } else {
454             LOG(
455               "AppUpdater:onStopRequest " +
456                 "- aStatusCode=Cr.NS_OK" +
457                 ", no competitive instance found."
458             );
459             this._setStatus(AppUpdater.STATUS.STAGING);
460           }
461           // But we should register the staging observer in either case, because
462           // if we do time out waiting for the other instance to exit, then
463           // staging really will start at that point.
464           this._awaitStagingComplete();
465         } else {
466           this._awaitDownloadComplete();
467         }
468         break;
469       default:
470         this.aus.removeDownloadListener(this);
471         LOG(
472           "AppUpdater:onStopRequest " +
473             "- case default" +
474             ", removing download listener" +
475             ", because the download failed."
476         );
477         this._setStatus(AppUpdater.STATUS.DOWNLOAD_FAILED);
478         break;
479     }
480   }
482   /**
483    * See nsIProgressEventSink.idl
484    */
485   onStatus(aRequest, aStatus, aStatusArg) {
486     LOG(
487       "AppUpdater:onStatus " +
488         "- aRequest: " +
489         aRequest +
490         ", aStatus: " +
491         aStatus +
492         ", aStatusArg: " +
493         aStatusArg
494     );
495   }
497   /**
498    * See nsIProgressEventSink.idl
499    */
500   onProgress(aRequest, aProgress, aProgressMax) {
501     LOG(
502       "AppUpdater:onProgress " +
503         "- aRequest: " +
504         aRequest +
505         ", aProgress: " +
506         aProgress +
507         ", aProgressMax: " +
508         aProgressMax
509     );
510     this._setStatus(AppUpdater.STATUS.DOWNLOADING, aProgress, aProgressMax);
511   }
513   /**
514    * This function registers an observer that watches for the download
515    * to complete. Once it does, it updates the status accordingly.
516    */
517   _awaitDownloadComplete() {
518     let observer = (aSubject, aTopic, aData) => {
519       // Update the UI when the download is finished
520       LOG(
521         "AppUpdater:_awaitStagingComplete - observer reached" +
522           ", status changes to READY_FOR_RESTART"
523       );
524       this._setStatus(AppUpdater.STATUS.READY_FOR_RESTART);
525       Services.obs.removeObserver(observer, "update-downloaded");
526     };
527     Services.obs.addObserver(observer, "update-downloaded");
528   }
530   /**
531    * This function registers an observer that watches for the staging process
532    * to complete. Once it does, it sets the status to either request that the
533    * user restarts to install the update on success, request that the user
534    * manually download and install the newer version, or automatically download
535    * a complete update if applicable.
536    */
537   _awaitStagingComplete() {
538     let observer = (aSubject, aTopic, aData) => {
539       LOG(
540         "AppUpdater:_awaitStagingComplete:observer" +
541           "- aSubject: " +
542           aSubject +
543           "- aTopic: " +
544           aTopic +
545           "- aData (=status): " +
546           aData
547       );
548       // Update the UI when the background updater is finished
549       switch (aTopic) {
550         case "update-staged":
551           let status = aData;
552           if (
553             status == "applied" ||
554             status == "applied-service" ||
555             status == "pending" ||
556             status == "pending-service" ||
557             status == "pending-elevate"
558           ) {
559             // If the update is successfully applied, or if the updater has
560             // fallen back to non-staged updates, show the "Restart to Update"
561             // button.
562             this._setStatus(AppUpdater.STATUS.READY_FOR_RESTART);
563           } else if (status == "failed") {
564             // Background update has failed, let's show the UI responsible for
565             // prompting the user to update manually.
566             this._setStatus(AppUpdater.STATUS.DOWNLOAD_FAILED);
567           } else if (status == "downloading") {
568             // We've fallen back to downloading the complete update because the
569             // partial update failed to get staged in the background.
570             // Therefore we need to keep our observer.
571             this._setupDownloadListener();
572             return;
573           }
574           break;
575         case "update-error":
576           this._setStatus(AppUpdater.STATUS.DOWNLOAD_FAILED);
577           break;
578       }
579       Services.obs.removeObserver(observer, "update-staged");
580       Services.obs.removeObserver(observer, "update-error");
581     };
582     Services.obs.addObserver(observer, "update-staged");
583     Services.obs.addObserver(observer, "update-error");
584   }
586   /**
587    * Stops the current check for updates and any ongoing download.
588    */
589   stop() {
590     LOG("AppUpdater:stop called, remove download listener");
591     this.checker.stopCurrentCheck();
592     this.aus.removeDownloadListener(this);
593   }
595   /**
596    * {AppUpdater.STATUS} The status of the current check or update.
597    */
598   get status() {
599     if (!this._status) {
600       if (!AppConstants.MOZ_UPDATER || this.updateDisabledByPackage) {
601         LOG("AppUpdater:status - no updater or updates disabled by package.");
602         this._status = AppUpdater.STATUS.NO_UPDATER;
603       } else if (this.updateDisabledByPolicy) {
604         LOG("AppUpdater:status - updateDisabledByPolicy");
605         this._status = AppUpdater.STATUS.UPDATE_DISABLED_BY_POLICY;
606       } else if (this.isReadyForRestart) {
607         LOG("AppUpdater:status - isReadyForRestart");
608         this._status = AppUpdater.STATUS.READY_FOR_RESTART;
609       } else if (this.aus.isOtherInstanceHandlingUpdates) {
610         LOG("AppUpdater:status - another instance is handling updates");
611         this._status = AppUpdater.STATUS.OTHER_INSTANCE_HANDLING_UPDATES;
612       } else if (this.isDownloading) {
613         LOG("AppUpdater:status - isDownloading");
614         this._status = AppUpdater.STATUS.DOWNLOADING;
615       } else if (this.isStaging) {
616         LOG("AppUpdater:status - isStaging");
617         this._status = AppUpdater.STATUS.STAGING;
618       } else {
619         LOG("AppUpdater:status - NEVER_CHECKED");
620         this._status = AppUpdater.STATUS.NEVER_CHECKED;
621       }
622     }
623     return this._status;
624   }
626   /**
627    * Adds a listener function that will be called back on status changes as
628    * different stages of updates occur.  The function will be called without
629    * arguments for most status changes; see the comments around the STATUS value
630    * definitions below.  This is safe to call multiple times with the same
631    * function.  It will be added only once.
632    *
633    * @param {function} listener
634    *   The listener function to add.
635    */
636   addListener(listener) {
637     this._listeners.add(listener);
638   }
640   /**
641    * Removes a listener.  This is safe to call multiple times with the same
642    * function, or with a function that was never added.
643    *
644    * @param {function} listener
645    *   The listener function to remove.
646    */
647   removeListener(listener) {
648     this._listeners.delete(listener);
649   }
651   /**
652    * Sets the updater's current status and calls listeners.
653    *
654    * @param {AppUpdater.STATUS} status
655    *   The new updater status.
656    * @param {*} listenerArgs
657    *   Arguments to pass to listeners.
658    */
659   _setStatus(status, ...listenerArgs) {
660     this._status = status;
661     for (let listener of this._listeners) {
662       listener(status, ...listenerArgs);
663     }
664     return status;
665   }
667   observe(subject, topic, status) {
668     LOG(
669       "AppUpdater:observe " +
670         "- subject: " +
671         subject +
672         ", topic: " +
673         topic +
674         ", status: " +
675         status
676     );
677     switch (topic) {
678       case "update-swap":
679         this._handleUpdateSwap();
680         break;
681       case "nsPref:changed":
682         if (
683           status == PREF_APP_UPDATE_LOG ||
684           status == PREF_APP_UPDATE_LOG_FILE
685         ) {
686           lazy.gLogEnabled; // Assigning this before it is lazy-loaded is an error.
687           lazy.gLogEnabled =
688             Services.prefs.getBoolPref(PREF_APP_UPDATE_LOG, false) ||
689             Services.prefs.getBoolPref(PREF_APP_UPDATE_LOG_FILE, false);
690         }
691         break;
692       case "quit-application":
693         Services.prefs.removeObserver(PREF_APP_UPDATE_LOG, this);
694         Services.obs.removeObserver(this, topic);
695     }
696   }
698   _handleUpdateSwap() {
699     // This function exists to deal with the fact that we support handling 2
700     // updates at once: a ready update and a downloading update. But AppUpdater
701     // only ever really considers a single update at a time.
702     // We see an update swap just when the downloading update has finished
703     // downloading and is being swapped into UpdateManager.readyUpdate. At this
704     // point, we are in one of two states. Either:
705     //  a) The update that is being swapped in is the update that this
706     //     AppUpdater has already been tracking, or
707     //  b) We've been tracking the ready update. Now that the downloading
708     //     update is about to be swapped into the place of the ready update, we
709     //     need to switch over to tracking the new update.
710     if (
711       this._status == AppUpdater.STATUS.DOWNLOADING ||
712       this._status == AppUpdater.STATUS.STAGING
713     ) {
714       // We are already tracking the correct update.
715       return;
716     }
718     if (this.updateStagingEnabled) {
719       LOG("AppUpdater:_handleUpdateSwap - updateStagingEnabled");
720       this._setStatus(AppUpdater.STATUS.STAGING);
721       this._awaitStagingComplete();
722     } else {
723       LOG("AppUpdater:_handleUpdateSwap - updateStagingDisabled");
724       this._setStatus(AppUpdater.STATUS.DOWNLOADING);
725       this._awaitDownloadComplete();
726     }
727   }
730 AppUpdater.STATUS = {
731   // Updates are allowed and there's no downloaded or staged update, but the
732   // AppUpdater hasn't checked for updates yet, so it doesn't know more than
733   // that.
734   NEVER_CHECKED: 0,
736   // The updater isn't available (AppConstants.MOZ_UPDATER is falsey).
737   NO_UPDATER: 1,
739   // "appUpdate" is not allowed by policy.
740   UPDATE_DISABLED_BY_POLICY: 2,
742   // Another app instance is handling updates.
743   OTHER_INSTANCE_HANDLING_UPDATES: 3,
745   // There's an update, but it's not supported on this system.
746   UNSUPPORTED_SYSTEM: 4,
748   // The user must apply updates manually.
749   MANUAL_UPDATE: 5,
751   // The AppUpdater is checking for updates.
752   CHECKING: 6,
754   // The AppUpdater checked for updates and none were found.
755   NO_UPDATES_FOUND: 7,
757   // The AppUpdater is downloading an update.  Listeners are notified of this
758   // status as a download starts.  They are also notified on download progress,
759   // and in that case they are passed two arguments: the current download
760   // progress and the total download size.
761   DOWNLOADING: 8,
763   // The AppUpdater tried to download an update but it failed.
764   DOWNLOAD_FAILED: 9,
766   // There's an update available, but the user wants us to ask them to download
767   // and install it.
768   DOWNLOAD_AND_INSTALL: 10,
770   // An update is staging.
771   STAGING: 11,
773   // An update is downloaded and staged and will be applied on restart.
774   READY_FOR_RESTART: 12,
776   /**
777    * Is the given `status` a terminal state in the update state machine?
778    *
779    * A terminal state means that the `check()` method has completed.
780    *
781    * N.b.: `DOWNLOAD_AND_INSTALL` is not considered terminal because the normal
782    * flow is that Firefox will show UI prompting the user to install, and when
783    * the user interacts, the `check()` method will continue through the update
784    * state machine.
785    *
786    * @returns {boolean} `true` if `status` is terminal.
787    */
788   isTerminalStatus(status) {
789     return ![
790       AppUpdater.STATUS.CHECKING,
791       AppUpdater.STATUS.DOWNLOAD_AND_INSTALL,
792       AppUpdater.STATUS.DOWNLOADING,
793       AppUpdater.STATUS.NEVER_CHECKED,
794       AppUpdater.STATUS.STAGING,
795     ].includes(status);
796   },
798   /**
799    * Turn the given `status` into a string for debugging.
800    *
801    * @returns {?string} representation of given numerical `status`.
802    */
803   debugStringFor(status) {
804     for (let [k, v] of Object.entries(AppUpdater.STATUS)) {
805       if (v == status) {
806         return k;
807       }
808     }
809     return null;
810   },
814  * Logs a string to the error console. If enabled, also logs to the update
815  * messages file.
816  * @param   string
817  *          The string to write to the error console.
818  */
819 function LOG(string) {
820   if (lazy.gLogEnabled) {
821     dump("*** AUS:AUM " + string + "\n");
822     if (!Cu.isInAutomation) {
823       Services.console.logStringMessage("AUS:AUM " + string);
824     }
826     if (lazy.gLogfileEnabled) {
827       if (!gLogfileOutputStream) {
828         let logfile = Services.dirsvc.get(KEY_PROFILE_DIR, Ci.nsIFile);
829         logfile.append(FILE_UPDATE_MESSAGES);
830         gLogfileOutputStream = FileUtils.openAtomicFileOutputStream(logfile);
831       }
833       try {
834         let encoded = new TextEncoder().encode(string + "\n");
835         gLogfileOutputStream.write(encoded, encoded.length);
836         gLogfileOutputStream.flush();
837       } catch (e) {
838         dump("*** AUS:AUM Unable to write to messages file: " + e + "\n");
839         Services.console.logStringMessage(
840           "AUS:AUM Unable to write to messages file: " + e
841         );
842       }
843     }
844   }