Merge mozilla-central to autoland. a=merge CLOSED TREE
[gecko.git] / browser / base / content / aboutDialog-appUpdater.js
blob5a8cc0561b99f33c4ebb76690a1d5dd9d8108c21
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 // Note: this file is included in aboutDialog.xhtml and preferences/advanced.xhtml
6 // if MOZ_UPDATER is defined.
8 /* import-globals-from aboutDialog.js */
10 var { XPCOMUtils } = ChromeUtils.importESModule(
11   "resource://gre/modules/XPCOMUtils.sys.mjs"
14 ChromeUtils.defineESModuleGetters(this, {
15   AppUpdater: "resource://gre/modules/AppUpdater.sys.mjs",
16   DownloadUtils: "resource://gre/modules/DownloadUtils.sys.mjs",
17   UpdateUtils: "resource://gre/modules/UpdateUtils.sys.mjs",
18 });
20 XPCOMUtils.defineLazyServiceGetter(
21   this,
22   "AUS",
23   "@mozilla.org/updates/update-service;1",
24   "nsIApplicationUpdateService"
27 var UPDATING_MIN_DISPLAY_TIME_MS = 1500;
29 var gAppUpdater;
31 function onUnload(_aEvent) {
32   if (gAppUpdater) {
33     gAppUpdater.destroy();
34     gAppUpdater = null;
35   }
38 function appUpdater(options = {}) {
39   this._appUpdater = new AppUpdater();
41   this._appUpdateListener = (status, ...args) => {
42     this._onAppUpdateStatus(status, ...args);
43   };
44   this._appUpdater.addListener(this._appUpdateListener);
46   this.options = options;
47   this.updatingMinDisplayTimerId = null;
48   this.updateDeck = document.getElementById("updateDeck");
50   this.bundle = Services.strings.createBundle(
51     "chrome://browser/locale/browser.properties"
52   );
54   try {
55     let manualURL = new URL(
56       Services.urlFormatter.formatURLPref("app.update.url.manual")
57     );
59     for (const manualLink of document.querySelectorAll(".manualLink")) {
60       // Strip hash and search parameters for display text.
61       let displayUrl = manualURL.origin + manualURL.pathname;
62       manualLink.href = manualURL.href;
63       document.l10n.setArgs(manualLink.closest("[data-l10n-id]"), {
64         displayUrl,
65       });
66     }
68     document.getElementById("failedLink").href = manualURL.href;
69   } catch (e) {
70     console.error("Invalid manual update url.", e);
71   }
73   this._appUpdater.check();
76 appUpdater.prototype = {
77   destroy() {
78     this.stopCurrentCheck();
79     if (this.updatingMinDisplayTimerId) {
80       clearTimeout(this.updatingMinDisplayTimerId);
81     }
82   },
84   stopCurrentCheck() {
85     this._appUpdater.removeListener(this._appUpdateListener);
86     this._appUpdater.stop();
87   },
89   get update() {
90     return this._appUpdater.update;
91   },
93   get selectedPanel() {
94     return this.updateDeck.selectedPanel;
95   },
97   _onAppUpdateStatus(status, ...args) {
98     switch (status) {
99       case AppUpdater.STATUS.UPDATE_DISABLED_BY_POLICY:
100         this.selectPanel("policyDisabled");
101         break;
102       case AppUpdater.STATUS.READY_FOR_RESTART:
103         this.selectPanel("apply");
104         break;
105       case AppUpdater.STATUS.OTHER_INSTANCE_HANDLING_UPDATES:
106         this.selectPanel("otherInstanceHandlingUpdates");
107         break;
108       case AppUpdater.STATUS.DOWNLOADING: {
109         const downloadStatus = document.getElementById("downloading");
110         if (!args.length) {
111           // Very early in the DOWNLOADING state, `selectedPatch` may not be
112           // available yet. But this function will be called again when it is
113           // available. A `maxSize < 0` indicates that the max size is not yet
114           // available.
115           let maxSize = -1;
116           if (this.update.selectedPatch) {
117             maxSize = this.update.selectedPatch.size;
118           }
119           const transfer = DownloadUtils.getTransferTotal(0, maxSize);
120           document.l10n.setArgs(downloadStatus, { transfer });
121           this.selectPanel("downloading");
122         } else {
123           let [progress, max] = args;
124           const transfer = DownloadUtils.getTransferTotal(progress, max);
125           document.l10n.setArgs(downloadStatus, { transfer });
126         }
127         break;
128       }
129       case AppUpdater.STATUS.STAGING:
130         this.selectPanel("applying");
131         break;
132       case AppUpdater.STATUS.CHECKING: {
133         this.checkingForUpdatesDelayPromise = new Promise(resolve => {
134           this.updatingMinDisplayTimerId = setTimeout(
135             resolve,
136             UPDATING_MIN_DISPLAY_TIME_MS
137           );
138         });
139         if (Services.policies.isAllowed("appUpdate")) {
140           this.selectPanel("checkingForUpdates");
141         } else {
142           this.selectPanel("policyDisabled");
143         }
144         break;
145       }
146       case AppUpdater.STATUS.CHECKING_FAILED:
147         this.selectPanel("checkingFailed");
148         break;
149       case AppUpdater.STATUS.NO_UPDATES_FOUND:
150         this.checkingForUpdatesDelayPromise.then(() => {
151           if (Services.policies.isAllowed("appUpdate")) {
152             this.selectPanel("noUpdatesFound");
153           } else {
154             this.selectPanel("policyDisabled");
155           }
156         });
157         break;
158       case AppUpdater.STATUS.UNSUPPORTED_SYSTEM:
159         if (this.update.detailsURL) {
160           let unsupportedLink = document.getElementById("unsupportedLink");
161           unsupportedLink.href = this.update.detailsURL;
162         }
163         this.selectPanel("unsupportedSystem");
164         break;
165       case AppUpdater.STATUS.MANUAL_UPDATE:
166         this.selectPanel("manualUpdate");
167         break;
168       case AppUpdater.STATUS.DOWNLOAD_AND_INSTALL:
169         this.selectPanel("downloadAndInstall");
170         break;
171       case AppUpdater.STATUS.DOWNLOAD_FAILED:
172         this.selectPanel("downloadFailed");
173         break;
174       case AppUpdater.STATUS.INTERNAL_ERROR:
175         this.selectPanel("internalError");
176         break;
177       case AppUpdater.STATUS.NEVER_CHECKED:
178         this.selectPanel("checkForUpdates");
179         break;
180       case AppUpdater.STATUS.NO_UPDATER:
181       default:
182         this.selectPanel("noUpdater");
183         break;
184     }
185   },
187   /**
188    * Sets the panel of the updateDeck and the visibility of icons
189    * in the #icons element.
190    *
191    * @param  aChildID
192    *         The id of the deck's child to select, e.g. "apply".
193    */
194   selectPanel(aChildID) {
195     let panel = document.getElementById(aChildID);
196     let icons = document.getElementById("icons");
197     if (icons) {
198       icons.className = aChildID;
199     }
201     // Make sure to select the panel before potentially auto-focusing the button.
202     this.updateDeck.selectedPanel = panel;
204     let button = panel.querySelector("button");
205     if (button) {
206       if (aChildID == "downloadAndInstall") {
207         let updateVersion = gAppUpdater.update.displayVersion;
208         // Include the build ID if this is an "a#" (nightly or aurora) build
209         if (/a\d+$/.test(updateVersion)) {
210           let buildID = gAppUpdater.update.buildID;
211           let year = buildID.slice(0, 4);
212           let month = buildID.slice(4, 6);
213           let day = buildID.slice(6, 8);
214           updateVersion += ` (${year}-${month}-${day})`;
215         }
216         button.label = this.bundle.formatStringFromName(
217           "update.downloadAndInstallButton.label",
218           [updateVersion]
219         );
220         button.accessKey = this.bundle.GetStringFromName(
221           "update.downloadAndInstallButton.accesskey"
222         );
223       }
224       if (this.options.buttonAutoFocus) {
225         let promise = Promise.resolve();
226         if (document.readyState != "complete") {
227           promise = new Promise(resolve =>
228             window.addEventListener("load", resolve, { once: true })
229           );
230         }
231         promise.then(() => {
232           if (
233             !document.commandDispatcher.focusedElement || // don't steal the focus
234             // except from the other buttons
235             document.commandDispatcher.focusedElement.localName == "button"
236           ) {
237             button.focus();
238           }
239         });
240       }
241     }
242   },
244   /**
245    * Check for updates
246    */
247   checkForUpdates() {
248     this._appUpdater.check();
249   },
251   /**
252    * Handles oncommand for the "Restart to Update" button
253    * which is presented after the download has been downloaded.
254    */
255   buttonRestartAfterDownload() {
256     if (AUS.currentState != Ci.nsIApplicationUpdateService.STATE_PENDING) {
257       return;
258     }
260     gAppUpdater.selectPanel("restarting");
262     // Notify all windows that an application quit has been requested.
263     let cancelQuit = Cc["@mozilla.org/supports-PRBool;1"].createInstance(
264       Ci.nsISupportsPRBool
265     );
266     Services.obs.notifyObservers(
267       cancelQuit,
268       "quit-application-requested",
269       "restart"
270     );
272     // Something aborted the quit process.
273     if (cancelQuit.data) {
274       gAppUpdater.selectPanel("apply");
275       return;
276     }
278     // If already in safe mode restart in safe mode (bug 327119)
279     if (Services.appinfo.inSafeMode) {
280       Services.startup.restartInSafeMode(Ci.nsIAppStartup.eAttemptQuit);
281       return;
282     }
284     if (
285       !Services.startup.quit(
286         Ci.nsIAppStartup.eAttemptQuit | Ci.nsIAppStartup.eRestart
287       )
288     ) {
289       // Either the user or the hidden window aborted the quit process.
290       gAppUpdater.selectPanel("apply");
291     }
292   },
294   /**
295    * Starts the download of an update mar.
296    */
297   startDownload() {
298     this._appUpdater.allowUpdateDownload();
299   },