Wed, 31 Dec 2014 13:27:57 +0100
Ignore runtime configuration files generated during quality assurance.
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 // Services = object with smart getters for common XPCOM services
6 Components.utils.import("resource://gre/modules/Services.jsm");
8 const PREF_EM_HOTFIX_ID = "extensions.hotfix.id";
10 #ifdef TOR_BROWSER_VERSION
11 # Add double-quotes back on (stripped by JarMaker.py).
12 #expand const TOR_BROWSER_VERSION = "__TOR_BROWSER_VERSION__";
13 #endif
15 function init(aEvent)
16 {
17 if (aEvent.target != document)
18 return;
20 try {
21 var distroId = Services.prefs.getCharPref("distribution.id");
22 if (distroId) {
23 var distroVersion = Services.prefs.getCharPref("distribution.version");
25 var distroIdField = document.getElementById("distributionId");
26 distroIdField.value = distroId + " - " + distroVersion;
27 distroIdField.style.display = "block";
29 try {
30 // This is in its own try catch due to bug 895473 and bug 900925.
31 var distroAbout = Services.prefs.getComplexValue("distribution.about",
32 Components.interfaces.nsISupportsString);
33 var distroField = document.getElementById("distribution");
34 distroField.value = distroAbout;
35 distroField.style.display = "block";
36 }
37 catch (ex) {
38 // Pref is unset
39 Components.utils.reportError(ex);
40 }
41 }
42 }
43 catch (e) {
44 // Pref is unset
45 }
47 // Include the build ID and display warning if this is an "a#" (nightly or aurora) build
48 let version = Services.appinfo.version;
49 if (/a\d+$/.test(version)) {
50 document.getElementById("experimental").hidden = false;
51 document.getElementById("communityDesc").hidden = true;
52 }
54 #ifdef TOR_BROWSER_VERSION
55 let versionElem = document.getElementById("version");
56 if (versionElem)
57 versionElem.textContent += " (Tor Browser " + TOR_BROWSER_VERSION + ")";
58 #endif
60 #ifdef MOZ_UPDATER
61 gAppUpdater = new appUpdater();
63 #if MOZ_UPDATE_CHANNEL != release
64 let defaults = Services.prefs.getDefaultBranch("");
65 let channelLabel = document.getElementById("currentChannel");
66 channelLabel.value = defaults.getCharPref("app.update.channel");
67 #endif
68 #endif
70 #ifdef XP_MACOSX
71 // it may not be sized at this point, and we need its width to calculate its position
72 window.sizeToContent();
73 window.moveTo((screen.availWidth / 2) - (window.outerWidth / 2), screen.availHeight / 5);
74 #endif
75 }
77 #ifdef MOZ_UPDATER
78 Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
79 Components.utils.import("resource://gre/modules/DownloadUtils.jsm");
80 Components.utils.import("resource://gre/modules/AddonManager.jsm");
82 var gAppUpdater;
84 function onUnload(aEvent) {
85 if (gAppUpdater.isChecking)
86 gAppUpdater.checker.stopChecking(Components.interfaces.nsIUpdateChecker.CURRENT_CHECK);
87 // Safe to call even when there isn't a download in progress.
88 gAppUpdater.removeDownloadListener();
89 gAppUpdater = null;
90 }
93 function appUpdater()
94 {
95 this.updateDeck = document.getElementById("updateDeck");
97 // Hide the update deck when there is already an update window open to avoid
98 // syncing issues between them.
99 if (Services.wm.getMostRecentWindow("Update:Wizard")) {
100 this.updateDeck.hidden = true;
101 return;
102 }
104 XPCOMUtils.defineLazyServiceGetter(this, "aus",
105 "@mozilla.org/updates/update-service;1",
106 "nsIApplicationUpdateService");
107 XPCOMUtils.defineLazyServiceGetter(this, "checker",
108 "@mozilla.org/updates/update-checker;1",
109 "nsIUpdateChecker");
110 XPCOMUtils.defineLazyServiceGetter(this, "um",
111 "@mozilla.org/updates/update-manager;1",
112 "nsIUpdateManager");
114 this.bundle = Services.strings.
115 createBundle("chrome://browser/locale/browser.properties");
117 let manualURL = Services.urlFormatter.formatURLPref("app.update.url.manual");
118 let manualLink = document.getElementById("manualLink");
119 manualLink.value = manualURL;
120 manualLink.href = manualURL;
121 document.getElementById("failedLink").href = manualURL;
123 if (this.updateDisabledAndLocked) {
124 this.selectPanel("adminDisabled");
125 return;
126 }
128 if (this.isPending || this.isApplied) {
129 this.selectPanel("apply");
130 return;
131 }
133 if (this.aus.isOtherInstanceHandlingUpdates) {
134 this.selectPanel("otherInstanceHandlingUpdates");
135 return;
136 }
138 if (this.isDownloading) {
139 this.startDownload();
140 // selectPanel("downloading") is called from setupDownloadingUI().
141 return;
142 }
144 // Honor the "Never check for updates" option by not only disabling background
145 // update checks, but also in the About dialog, by presenting a
146 // "Check for updates" button.
147 // If updates are found, the user is then asked if he wants to "Update to <version>".
148 if (!this.updateEnabled) {
149 this.selectPanel("checkForUpdates");
150 return;
151 }
153 // That leaves the options
154 // "Check for updates, but let me choose whether to install them", and
155 // "Automatically install updates".
156 // In both cases, we check for updates without asking.
157 // In the "let me choose" case, we ask before downloading though, in onCheckComplete.
158 this.checkForUpdates();
159 }
161 appUpdater.prototype =
162 {
163 // true when there is an update check in progress.
164 isChecking: false,
166 // true when there is an update already staged / ready to be applied.
167 get isPending() {
168 if (this.update) {
169 return this.update.state == "pending" ||
170 this.update.state == "pending-service";
171 }
172 return this.um.activeUpdate &&
173 (this.um.activeUpdate.state == "pending" ||
174 this.um.activeUpdate.state == "pending-service");
175 },
177 // true when there is an update already installed in the background.
178 get isApplied() {
179 if (this.update)
180 return this.update.state == "applied" ||
181 this.update.state == "applied-service";
182 return this.um.activeUpdate &&
183 (this.um.activeUpdate.state == "applied" ||
184 this.um.activeUpdate.state == "applied-service");
185 },
187 // true when there is an update download in progress.
188 get isDownloading() {
189 if (this.update)
190 return this.update.state == "downloading";
191 return this.um.activeUpdate &&
192 this.um.activeUpdate.state == "downloading";
193 },
195 // true when updating is disabled by an administrator.
196 get updateDisabledAndLocked() {
197 return !this.updateEnabled &&
198 Services.prefs.prefIsLocked("app.update.enabled");
199 },
201 // true when updating is enabled.
202 get updateEnabled() {
203 try {
204 return Services.prefs.getBoolPref("app.update.enabled");
205 }
206 catch (e) { }
207 return true; // Firefox default is true
208 },
210 // true when updating in background is enabled.
211 get backgroundUpdateEnabled() {
212 return this.updateEnabled &&
213 gAppUpdater.aus.canStageUpdates;
214 },
216 // true when updating is automatic.
217 get updateAuto() {
218 try {
219 return Services.prefs.getBoolPref("app.update.auto");
220 }
221 catch (e) { }
222 return true; // Firefox default is true
223 },
225 /**
226 * Sets the panel of the updateDeck.
227 *
228 * @param aChildID
229 * The id of the deck's child to select, e.g. "apply".
230 */
231 selectPanel: function(aChildID) {
232 let panel = document.getElementById(aChildID);
234 let button = panel.querySelector("button");
235 if (button) {
236 if (aChildID == "downloadAndInstall") {
237 let updateVersion = gAppUpdater.update.displayVersion;
238 button.label = this.bundle.formatStringFromName("update.downloadAndInstallButton.label", [updateVersion], 1);
239 button.accessKey = this.bundle.GetStringFromName("update.downloadAndInstallButton.accesskey");
240 }
241 this.updateDeck.selectedPanel = panel;
242 if (!document.commandDispatcher.focusedElement || // don't steal the focus
243 document.commandDispatcher.focusedElement.localName == "button") // except from the other buttons
244 button.focus();
246 } else {
247 this.updateDeck.selectedPanel = panel;
248 }
249 },
251 /**
252 * Check for updates
253 */
254 checkForUpdates: function() {
255 this.selectPanel("checkingForUpdates");
256 this.isChecking = true;
257 this.checker.checkForUpdates(this.updateCheckListener, true);
258 // after checking, onCheckComplete() is called
259 },
261 /**
262 * Check for addon compat, or start the download right away
263 */
264 doUpdate: function() {
265 // skip the compatibility check if the update doesn't provide appVersion,
266 // or the appVersion is unchanged, e.g. nightly update
267 #ifdef TOR_BROWSER_UPDATE
268 let pkgVersion = TOR_BROWSER_VERSION;
269 #else
270 let pkgVersion = Services.appinfo.version;
271 #endif
272 if (!this.update.appVersion ||
273 Services.vc.compare(gAppUpdater.update.appVersion, pkgVersion) == 0) {
274 this.startDownload();
275 } else {
276 this.checkAddonCompatibility();
277 }
278 },
280 /**
281 * Handles oncommand for the "Restart to Update" button
282 * which is presented after the download has been downloaded.
283 */
284 buttonRestartAfterDownload: function() {
285 if (!this.isPending && !this.isApplied)
286 return;
288 // Notify all windows that an application quit has been requested.
289 let cancelQuit = Components.classes["@mozilla.org/supports-PRBool;1"].
290 createInstance(Components.interfaces.nsISupportsPRBool);
291 Services.obs.notifyObservers(cancelQuit, "quit-application-requested", "restart");
293 // Something aborted the quit process.
294 if (cancelQuit.data)
295 return;
297 let appStartup = Components.classes["@mozilla.org/toolkit/app-startup;1"].
298 getService(Components.interfaces.nsIAppStartup);
300 // If already in safe mode restart in safe mode (bug 327119)
301 if (Services.appinfo.inSafeMode) {
302 appStartup.restartInSafeMode(Components.interfaces.nsIAppStartup.eAttemptQuit);
303 return;
304 }
306 appStartup.quit(Components.interfaces.nsIAppStartup.eAttemptQuit |
307 Components.interfaces.nsIAppStartup.eRestart);
308 },
310 /**
311 * Handles oncommand for the "Apply Update…" button
312 * which is presented if we need to show the billboard or license.
313 */
314 buttonApplyBillboard: function() {
315 const URI_UPDATE_PROMPT_DIALOG = "chrome://mozapps/content/update/updates.xul";
316 var ary = null;
317 ary = Components.classes["@mozilla.org/supports-array;1"].
318 createInstance(Components.interfaces.nsISupportsArray);
319 ary.AppendElement(this.update);
320 var openFeatures = "chrome,centerscreen,dialog=no,resizable=no,titlebar,toolbar=no";
321 Services.ww.openWindow(null, URI_UPDATE_PROMPT_DIALOG, "", openFeatures, ary);
322 window.close(); // close the "About" window; updates.xul takes over.
323 },
325 /**
326 * Implements nsIUpdateCheckListener. The methods implemented by
327 * nsIUpdateCheckListener are in a different scope from nsIIncrementalDownload
328 * to make it clear which are used by each interface.
329 */
330 updateCheckListener: {
331 /**
332 * See nsIUpdateService.idl
333 */
334 onCheckComplete: function(aRequest, aUpdates, aUpdateCount) {
335 gAppUpdater.isChecking = false;
336 gAppUpdater.update = gAppUpdater.aus.
337 selectUpdate(aUpdates, aUpdates.length);
338 if (!gAppUpdater.update) {
339 gAppUpdater.selectPanel("noUpdatesFound");
340 return;
341 }
343 if (gAppUpdater.update.unsupported) {
344 if (gAppUpdater.update.detailsURL) {
345 let unsupportedLink = document.getElementById("unsupportedLink");
346 unsupportedLink.href = gAppUpdater.update.detailsURL;
347 }
348 gAppUpdater.selectPanel("unsupportedSystem");
349 return;
350 }
352 if (!gAppUpdater.aus.canApplyUpdates) {
353 gAppUpdater.selectPanel("manualUpdate");
354 return;
355 }
357 // Firefox no longer displays a license for updates and the licenseURL
358 // check is just in case a distibution does.
359 if (gAppUpdater.update.billboardURL || gAppUpdater.update.licenseURL) {
360 gAppUpdater.selectPanel("applyBillboard");
361 return;
362 }
364 if (gAppUpdater.updateAuto) // automatically download and install
365 gAppUpdater.doUpdate();
366 else // ask
367 gAppUpdater.selectPanel("downloadAndInstall");
368 },
370 /**
371 * See nsIUpdateService.idl
372 */
373 onError: function(aRequest, aUpdate) {
374 // Errors in the update check are treated as no updates found. If the
375 // update check fails repeatedly without a success the user will be
376 // notified with the normal app update user interface so this is safe.
377 gAppUpdater.isChecking = false;
378 gAppUpdater.selectPanel("noUpdatesFound");
379 },
381 /**
382 * See nsISupports.idl
383 */
384 QueryInterface: function(aIID) {
385 if (!aIID.equals(Components.interfaces.nsIUpdateCheckListener) &&
386 !aIID.equals(Components.interfaces.nsISupports))
387 throw Components.results.NS_ERROR_NO_INTERFACE;
388 return this;
389 }
390 },
392 /**
393 * Checks the compatibility of add-ons for the application update.
394 */
395 checkAddonCompatibility: function() {
396 try {
397 var hotfixID = Services.prefs.getCharPref(PREF_EM_HOTFIX_ID);
398 }
399 catch (e) { }
401 var self = this;
402 AddonManager.getAllAddons(function(aAddons) {
403 self.addons = [];
404 self.addonsCheckedCount = 0;
405 aAddons.forEach(function(aAddon) {
406 // Protect against code that overrides the add-ons manager and doesn't
407 // implement the isCompatibleWith or the findUpdates method.
408 if (!("isCompatibleWith" in aAddon) || !("findUpdates" in aAddon)) {
409 let errMsg = "Add-on doesn't implement either the isCompatibleWith " +
410 "or the findUpdates method!";
411 if (aAddon.id)
412 errMsg += " Add-on ID: " + aAddon.id;
413 Components.utils.reportError(errMsg);
414 return;
415 }
417 // If an add-on isn't appDisabled and isn't userDisabled then it is
418 // either active now or the user expects it to be active after the
419 // restart. If that is the case and the add-on is not installed by the
420 // application and is not compatible with the new application version
421 // then the user should be warned that the add-on will become
422 // incompatible. If an addon's type equals plugin it is skipped since
423 // checking plugins compatibility information isn't supported and
424 // getting the scope property of a plugin breaks in some environments
425 // (see bug 566787). The hotfix add-on is also ignored as it shouldn't
426 // block the user from upgrading.
427 try {
428 #ifdef TOR_BROWSER_UPDATE
429 let compatVersion = self.update.platformVersion;
430 #else
431 let compatVersion = self.update.appVersion;
432 #endif
433 if (aAddon.type != "plugin" && aAddon.id != hotfixID &&
434 !aAddon.appDisabled && !aAddon.userDisabled &&
435 aAddon.scope != AddonManager.SCOPE_APPLICATION &&
436 aAddon.isCompatible &&
437 !aAddon.isCompatibleWith(compatVersion,
438 self.update.platformVersion))
439 self.addons.push(aAddon);
440 }
441 catch (e) {
442 Components.utils.reportError(e);
443 }
444 });
445 self.addonsTotalCount = self.addons.length;
446 if (self.addonsTotalCount == 0) {
447 self.startDownload();
448 return;
449 }
451 self.checkAddonsForUpdates();
452 });
453 },
455 /**
456 * Checks if there are updates for add-ons that are incompatible with the
457 * application update.
458 */
459 checkAddonsForUpdates: function() {
460 this.addons.forEach(function(aAddon) {
461 #ifdef TOR_BROWSER_UPDATE
462 let compatVersion = this.update.platformVersion;
463 #else
464 let compatVersion = this.update.appVersion;
465 #endif
466 aAddon.findUpdates(this, AddonManager.UPDATE_WHEN_NEW_APP_DETECTED,
467 compatVersion,
468 this.update.platformVersion);
469 }, this);
470 },
472 /**
473 * See XPIProvider.jsm
474 */
475 onCompatibilityUpdateAvailable: function(aAddon) {
476 for (var i = 0; i < this.addons.length; ++i) {
477 if (this.addons[i].id == aAddon.id) {
478 this.addons.splice(i, 1);
479 break;
480 }
481 }
482 },
484 /**
485 * See XPIProvider.jsm
486 */
487 onUpdateAvailable: function(aAddon, aInstall) {
488 #ifdef TOR_BROWSER_UPDATE
489 let compatVersion = this.update.platformVersion;
490 #else
491 let compatVersion = this.update.appVersion;
492 #endif
493 if (!Services.blocklist.isAddonBlocklisted(aAddon,
494 compatVersion,
495 this.update.platformVersion)) {
496 // Compatibility or new version updates mean the same thing here.
497 this.onCompatibilityUpdateAvailable(aAddon);
498 }
499 },
501 /**
502 * See XPIProvider.jsm
503 */
504 onUpdateFinished: function(aAddon) {
505 ++this.addonsCheckedCount;
507 if (this.addonsCheckedCount < this.addonsTotalCount)
508 return;
510 if (this.addons.length == 0) {
511 // Compatibility updates or new version updates were found for all add-ons
512 this.startDownload();
513 return;
514 }
516 this.selectPanel("apply");
517 },
519 /**
520 * Starts the download of an update mar.
521 */
522 startDownload: function() {
523 if (!this.update)
524 this.update = this.um.activeUpdate;
525 this.update.QueryInterface(Components.interfaces.nsIWritablePropertyBag);
526 this.update.setProperty("foregroundDownload", "true");
528 this.aus.pauseDownload();
529 let state = this.aus.downloadUpdate(this.update, false);
530 if (state == "failed") {
531 this.selectPanel("downloadFailed");
532 return;
533 }
535 this.setupDownloadingUI();
536 },
538 /**
539 * Switches to the UI responsible for tracking the download.
540 */
541 setupDownloadingUI: function() {
542 this.downloadStatus = document.getElementById("downloadStatus");
543 this.downloadStatus.value =
544 DownloadUtils.getTransferTotal(0, this.update.selectedPatch.size);
545 this.selectPanel("downloading");
546 this.aus.addDownloadListener(this);
547 },
549 removeDownloadListener: function() {
550 if (this.aus) {
551 this.aus.removeDownloadListener(this);
552 }
553 },
555 /**
556 * See nsIRequestObserver.idl
557 */
558 onStartRequest: function(aRequest, aContext) {
559 },
561 /**
562 * See nsIRequestObserver.idl
563 */
564 onStopRequest: function(aRequest, aContext, aStatusCode) {
565 switch (aStatusCode) {
566 case Components.results.NS_ERROR_UNEXPECTED:
567 if (this.update.selectedPatch.state == "download-failed" &&
568 (this.update.isCompleteUpdate || this.update.patchCount != 2)) {
569 // Verification error of complete patch, informational text is held in
570 // the update object.
571 this.removeDownloadListener();
572 this.selectPanel("downloadFailed");
573 break;
574 }
575 // Verification failed for a partial patch, complete patch is now
576 // downloading so return early and do NOT remove the download listener!
577 break;
578 case Components.results.NS_BINDING_ABORTED:
579 // Do not remove UI listener since the user may resume downloading again.
580 break;
581 case Components.results.NS_OK:
582 this.removeDownloadListener();
583 if (this.backgroundUpdateEnabled) {
584 this.selectPanel("applying");
585 let update = this.um.activeUpdate;
586 let self = this;
587 Services.obs.addObserver(function (aSubject, aTopic, aData) {
588 // Update the UI when the background updater is finished
589 let status = aData;
590 if (status == "applied" || status == "applied-service" ||
591 status == "pending" || status == "pending-service") {
592 // If the update is successfully applied, or if the updater has
593 // fallen back to non-staged updates, show the "Restart to Update"
594 // button.
595 self.selectPanel("apply");
596 } else if (status == "failed") {
597 // Background update has failed, let's show the UI responsible for
598 // prompting the user to update manually.
599 self.selectPanel("downloadFailed");
600 } else if (status == "downloading") {
601 // We've fallen back to downloading the full update because the
602 // partial update failed to get staged in the background.
603 // Therefore we need to keep our observer.
604 self.setupDownloadingUI();
605 return;
606 }
607 Services.obs.removeObserver(arguments.callee, "update-staged");
608 }, "update-staged", false);
609 } else {
610 this.selectPanel("apply");
611 }
612 break;
613 default:
614 this.removeDownloadListener();
615 this.selectPanel("downloadFailed");
616 break;
617 }
618 },
620 /**
621 * See nsIProgressEventSink.idl
622 */
623 onStatus: function(aRequest, aContext, aStatus, aStatusArg) {
624 },
626 /**
627 * See nsIProgressEventSink.idl
628 */
629 onProgress: function(aRequest, aContext, aProgress, aProgressMax) {
630 this.downloadStatus.value =
631 DownloadUtils.getTransferTotal(aProgress, aProgressMax);
632 },
634 /**
635 * See nsISupports.idl
636 */
637 QueryInterface: function(aIID) {
638 if (!aIID.equals(Components.interfaces.nsIProgressEventSink) &&
639 !aIID.equals(Components.interfaces.nsIRequestObserver) &&
640 !aIID.equals(Components.interfaces.nsISupports))
641 throw Components.results.NS_ERROR_NO_INTERFACE;
642 return this;
643 }
644 };
645 #endif