Wed, 31 Dec 2014 06:09:35 +0100
Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.
1 #filter substitution
3 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
4 /* This Source Code Form is subject to the terms of the Mozilla Public
5 * License, v. 2.0. If a copy of the MPL was not distributed with this
6 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
8 Components.utils.import("resource://gre/modules/DownloadUtils.jsm");
9 Components.utils.import("resource://gre/modules/AddonManager.jsm");
10 Components.utils.import("resource://gre/modules/Services.jsm");
12 // Firefox's macBrowserOverlay.xul includes scripts that define Cc, Ci, and Cr
13 // so we have to use different names.
14 const CoC = Components.classes;
15 const CoI = Components.interfaces;
16 const CoR = Components.results;
18 const XMLNS_XUL = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
20 const PREF_APP_UPDATE_BACKGROUNDERRORS = "app.update.backgroundErrors";
21 const PREF_APP_UPDATE_BILLBOARD_TEST_URL = "app.update.billboard.test_url";
22 const PREF_APP_UPDATE_CERT_ERRORS = "app.update.cert.errors";
23 const PREF_APP_UPDATE_ENABLED = "app.update.enabled";
24 const PREF_APP_UPDATE_LOG = "app.update.log";
25 const PREF_APP_UPDATE_MANUAL_URL = "app.update.url.manual";
26 const PREF_APP_UPDATE_NEVER_BRANCH = "app.update.never.";
27 const PREF_APP_UPDATE_NOTIFIEDUNSUPPORTED = "app.update.notifiedUnsupported";
28 const PREF_APP_UPDATE_TEST_LOOP = "app.update.test.loop";
29 const PREF_PLUGINS_UPDATEURL = "plugins.update.url";
31 const PREF_EM_HOTFIX_ID = "extensions.hotfix.id";
33 const UPDATE_TEST_LOOP_INTERVAL = 2000;
35 const URI_UPDATES_PROPERTIES = "chrome://mozapps/locale/update/updates.properties";
37 const STATE_DOWNLOADING = "downloading";
38 const STATE_PENDING = "pending";
39 const STATE_PENDING_SVC = "pending-service";
40 const STATE_APPLYING = "applying";
41 const STATE_APPLIED = "applied";
42 const STATE_APPLIED_SVC = "applied-service";
43 const STATE_SUCCEEDED = "succeeded";
44 const STATE_DOWNLOAD_FAILED = "download-failed";
45 const STATE_FAILED = "failed";
47 const SRCEVT_FOREGROUND = 1;
48 const SRCEVT_BACKGROUND = 2;
50 const CERT_ATTR_CHECK_FAILED_NO_UPDATE = 100;
51 const CERT_ATTR_CHECK_FAILED_HAS_UPDATE = 101;
52 const BACKGROUNDCHECK_MULTIPLE_FAILURES = 110;
54 #ifdef TOR_BROWSER_VERSION
55 # Add double-quotes back on (stripped by JarMaker.py).
56 #expand const TOR_BROWSER_VERSION = "__TOR_BROWSER_VERSION__";
57 #endif
59 var gLogEnabled = false;
60 var gUpdatesFoundPageId;
62 // Notes:
63 // 1. use the wizard's goTo method whenever possible to change the wizard
64 // page since it is simpler than most other methods and behaves nicely with
65 // mochitests.
66 // 2. using a page's onPageShow method to then change to a different page will
67 // of course call that page's onPageShow method which can make mochitests
68 // overly complicated and fragile so avoid doing this if at all possible.
69 // This is why a page's next attribute is set prior to the page being shown
70 // whenever possible.
72 /**
73 * Logs a string to the error console.
74 * @param string
75 * The string to write to the error console..
76 */
77 function LOG(module, string) {
78 if (gLogEnabled) {
79 dump("*** AUS:UI " + module + ":" + string + "\n");
80 Services.console.logStringMessage("AUS:UI " + module + ":" + string);
81 }
82 }
84 /**
85 * Opens a URL using the event target's url attribute for the URL. This is a
86 * workaround for Bug 263433 which prevents respecting tab browser preferences
87 * for where to open a URL.
88 */
89 function openUpdateURL(event) {
90 if (event.button == 0)
91 openURL(event.target.getAttribute("url"));
92 }
94 /**
95 * Gets a preference value, handling the case where there is no default.
96 * @param func
97 * The name of the preference function to call, on nsIPrefBranch
98 * @param preference
99 * The name of the preference
100 * @param defaultValue
101 * The default value to return in the event the preference has
102 * no setting
103 * @returns The value of the preference, or undefined if there was no
104 * user or default value.
105 */
106 function getPref(func, preference, defaultValue) {
107 try {
108 return Services.prefs[func](preference);
109 }
110 catch (e) {
111 LOG("General", "getPref - failed to get preference: " + preference);
112 }
113 return defaultValue;
114 }
116 /**
117 * A set of shared data and control functions for the wizard as a whole.
118 */
119 var gUpdates = {
120 /**
121 * The nsIUpdate object being used by this window (either for downloading,
122 * notification or both).
123 */
124 update: null,
126 /**
127 * List of incompatible add-ons
128 */
129 addons: [],
131 /**
132 * The updates.properties <stringbundle> element.
133 */
134 strings: null,
136 /**
137 * The Application brandShortName (e.g. "Firefox")
138 */
139 brandName: null,
141 /**
142 * The <wizard> element
143 */
144 wiz: null,
146 /**
147 * Whether to run the unload handler. This will be set to false when the user
148 * exits the wizard via onWizardCancel or onWizardFinish.
149 */
150 _runUnload: true,
152 /**
153 * Submit the last page code when the wizard exited. The pageid is used to map
154 * to an integer instead of using the pageindex since pages can be added and
155 * removed which would change the page's pageindex.
156 * @param pageID
157 */
158 _sendLastPageCodePing: function(pageID) {
159 var pageMap = { invalid: 0,
160 dummy: 1,
161 checking: 2,
162 pluginupdatesfound: 3,
163 noupdatesfound: 4,
164 manualUpdate: 5,
165 unsupported: 6,
166 incompatibleCheck: 7,
167 updatesfoundbasic: 8,
168 updatesfoundbillboard: 9,
169 license: 10,
170 incompatibleList: 11,
171 downloading: 12,
172 errors: 13,
173 errorextra: 14,
174 errorpatching: 15,
175 finished: 16,
176 finishedBackground: 17,
177 installed: 18 };
178 try {
179 Services.telemetry.getHistogramById("UPDATER_WIZ_LAST_PAGE_CODE").
180 add(pageMap[pageID] || pageMap.invalid);
181 }
182 catch (e) {
183 Components.utils.reportError(e);
184 }
185 },
187 /**
188 * Helper function for setButtons
189 * Resets button to original label & accesskey if string is null.
190 */
191 _setButton: function(button, string) {
192 if (string) {
193 var label = this.getAUSString(string);
194 if (label.indexOf("%S") != -1)
195 label = label.replace(/%S/, this.brandName);
196 button.label = label;
197 button.setAttribute("accesskey",
198 this.getAUSString(string + ".accesskey"));
199 } else {
200 button.label = button.defaultLabel;
201 button.setAttribute("accesskey", button.defaultAccesskey);
202 }
203 },
205 /**
206 * Sets the attributes needed for this Wizard's control buttons (labels,
207 * disabled, hidden, etc.)
208 * @param extra1ButtonString
209 * The property in the stringbundle containing the label to put on
210 * the first extra button, or null to hide the first extra button.
211 * @param extra2ButtonString
212 * The property in the stringbundle containing the label to put on
213 * the second extra button, or null to hide the second extra button.
214 * @param nextFinishButtonString
215 * The property in the stringbundle containing the label to put on
216 * the Next / Finish button, or null to hide the button. The Next and
217 * Finish buttons are never displayed at the same time in a wizard
218 * with the the Finish button only being displayed when there are no
219 * additional pages to display in the wizard.
220 * @param canAdvance
221 * true if the wizard can be advanced (e.g. the next / finish button
222 * should be enabled), false otherwise.
223 * @param showCancel
224 * true if the wizard's cancel button should be shown, false
225 * otherwise. If not specified this will default to false.
226 *
227 * Note:
228 * Per Bug 324121 the wizard should not look like a wizard and to accomplish
229 * this the back button is never displayed and the cancel button is only
230 * displayed for the checking and the incompatibleCheck pages. This causes the
231 * wizard buttons to be arranged as follows on Windows with the next and
232 * finish buttons never being displayed at the same time.
233 * +--------------------------------------------------------------+
234 * | [ extra1 ] [ extra2 ] [ next or finish ] |
235 * +--------------------------------------------------------------+
236 */
237 setButtons: function(extra1ButtonString, extra2ButtonString,
238 nextFinishButtonString, canAdvance, showCancel) {
239 this.wiz.canAdvance = canAdvance;
241 var bnf = this.wiz.getButton(this.wiz.onLastPage ? "finish" : "next");
242 var be1 = this.wiz.getButton("extra1");
243 var be2 = this.wiz.getButton("extra2");
244 var bc = this.wiz.getButton("cancel");
246 // Set the labels for the next / finish, extra1, and extra2 buttons
247 this._setButton(bnf, nextFinishButtonString);
248 this._setButton(be1, extra1ButtonString);
249 this._setButton(be2, extra2ButtonString);
251 bnf.hidden = bnf.disabled = !nextFinishButtonString;
252 be1.hidden = be1.disabled = !extra1ButtonString;
253 be2.hidden = be2.disabled = !extra2ButtonString;
254 bc.hidden = bc.disabled = !showCancel;
256 // Hide and disable the back button each time setButtons is called
257 // (see bug 464765).
258 var btn = this.wiz.getButton("back");
259 btn.hidden = btn.disabled = true;
261 // Hide and disable the finish button if not on the last page or the next
262 // button if on the last page each time setButtons is called.
263 btn = this.wiz.getButton(this.wiz.onLastPage ? "next" : "finish");
264 btn.hidden = btn.disabled = true;
265 },
267 getAUSString: function(key, strings) {
268 if (strings)
269 return this.strings.getFormattedString(key, strings);
270 return this.strings.getString(key);
271 },
273 never: function () {
274 // If the user clicks "No Thanks", we should not prompt them to update to
275 // this version again unless they manually select "Check for Updates..."
276 // which will clear all of the "never" prefs.
277 var neverPrefName = PREF_APP_UPDATE_NEVER_BRANCH + this.update.appVersion;
278 Services.prefs.setBoolPref(neverPrefName, true);
279 },
281 /**
282 * A hash of |pageid| attribute to page object. Can be used to dispatch
283 * function calls to the appropriate page.
284 */
285 _pages: { },
287 /**
288 * Called when the user presses the "Finish" button on the wizard, dispatches
289 * the function call to the selected page.
290 */
291 onWizardFinish: function() {
292 this._runUnload = false;
293 var pageid = document.documentElement.currentPage.pageid;
294 if ("onWizardFinish" in this._pages[pageid])
295 this._pages[pageid].onWizardFinish();
296 this._sendLastPageCodePing(pageid);
297 },
299 /**
300 * Called when the user presses the "Cancel" button on the wizard, dispatches
301 * the function call to the selected page.
302 */
303 onWizardCancel: function() {
304 this._runUnload = false;
305 var pageid = document.documentElement.currentPage.pageid;
306 if ("onWizardCancel" in this._pages[pageid])
307 this._pages[pageid].onWizardCancel();
308 this._sendLastPageCodePing(pageid);
309 },
311 /**
312 * Called when the user presses the "Next" button on the wizard, dispatches
313 * the function call to the selected page.
314 */
315 onWizardNext: function() {
316 var cp = document.documentElement.currentPage;
317 if (!cp)
318 return;
319 var pageid = cp.pageid;
320 if ("onWizardNext" in this._pages[pageid])
321 this._pages[pageid].onWizardNext();
322 },
324 /**
325 * The checking process that spawned this update UI. There are two types:
326 * SRCEVT_FOREGROUND:
327 * Some user-generated event caused this UI to appear, e.g. the Help
328 * menu item or the button in preferences. When in this mode, the UI
329 * should remain active for the duration of the download.
330 * SRCEVT_BACKGROUND:
331 * A background update check caused this UI to appear, probably because
332 * incompatibilities in Extensions or other addons were discovered and
333 * the user's consent to continue was required. When in this mode, the
334 * UI will disappear after the user's consent is obtained.
335 */
336 sourceEvent: SRCEVT_FOREGROUND,
338 /**
339 * Helper function for onLoad
340 * Saves default button label & accesskey for use by _setButton
341 */
342 _cacheButtonStrings: function (buttonName) {
343 var button = this.wiz.getButton(buttonName);
344 button.defaultLabel = button.label;
345 button.defaultAccesskey = button.getAttribute("accesskey");
346 },
348 /**
349 * Called when the wizard UI is loaded.
350 */
351 onLoad: function() {
352 this.wiz = document.documentElement;
354 gLogEnabled = getPref("getBoolPref", PREF_APP_UPDATE_LOG, false)
356 this.strings = document.getElementById("updateStrings");
357 var brandStrings = document.getElementById("brandStrings");
358 this.brandName = brandStrings.getString("brandShortName");
360 var pages = this.wiz.childNodes;
361 for (var i = 0; i < pages.length; ++i) {
362 var page = pages[i];
363 if (page.localName == "wizardpage")
364 this._pages[page.pageid] = eval(page.getAttribute("object"));
365 }
367 // Cache the standard button labels in case we need to restore them
368 this._cacheButtonStrings("next");
369 this._cacheButtonStrings("finish");
370 this._cacheButtonStrings("extra1");
371 this._cacheButtonStrings("extra2");
373 // Advance to the Start page.
374 this.getStartPageID(function(startPageID) {
375 LOG("gUpdates", "onLoad - setting current page to startpage " + startPageID);
376 gUpdates.wiz.currentPage = document.getElementById(startPageID);
377 });
378 },
380 /**
381 * Called when the wizard UI is unloaded.
382 */
383 onUnload: function() {
384 if (this._runUnload) {
385 var cp = this.wiz.currentPage;
386 if (cp.pageid != "finished" && cp.pageid != "finishedBackground")
387 this.onWizardCancel();
388 }
389 },
391 /**
392 * Gets the ID of the <wizardpage> object that should be displayed first. This
393 * is an asynchronous method that passes the resulting object to a callback
394 * function.
395 *
396 * This is determined by how we were called by the update prompt:
397 *
398 * Prompt Method: Arg0: Update State: Src Event: Failed: Result:
399 * showUpdateAvailable nsIUpdate obj -- background -- see Note below
400 * showUpdateDownloaded nsIUpdate obj pending background -- finishedBackground
401 * showUpdateInstalled "installed" -- -- -- installed
402 * showUpdateError nsIUpdate obj failed either partial errorpatching
403 * showUpdateError nsIUpdate obj failed either complete errors
404 * checkForUpdates null -- foreground -- checking
405 * checkForUpdates null downloading foreground -- downloading
406 *
407 * Note: the page returned (e.g. Result) for showUpdateAvaulable is as follows:
408 * New enabled incompatible add-ons : incompatibleCheck page
409 * No new enabled incompatible add-ons: either updatesfoundbasic or
410 * updatesfoundbillboard as determined by
411 * updatesFoundPageId
412 * @param aCallback
413 * A callback to pass the <wizardpage> object to be displayed first to.
414 */
415 getStartPageID: function(aCallback) {
416 if ("arguments" in window && window.arguments[0]) {
417 var arg0 = window.arguments[0];
418 if (arg0 instanceof CoI.nsIUpdate) {
419 // If the first argument is a nsIUpdate object, we are notifying the
420 // user that the background checking found an update that requires
421 // their permission to install, and it's ready for download.
422 this.setUpdate(arg0);
423 if (this.update.errorCode == CERT_ATTR_CHECK_FAILED_NO_UPDATE ||
424 this.update.errorCode == CERT_ATTR_CHECK_FAILED_HAS_UPDATE ||
425 this.update.errorCode == BACKGROUNDCHECK_MULTIPLE_FAILURES) {
426 aCallback("errorextra");
427 return;
428 }
430 if (this.update.unsupported) {
431 aCallback("unsupported");
432 return;
433 }
435 var p = this.update.selectedPatch;
436 if (p) {
437 var state = p.state;
438 var patchFailed;
439 try {
440 patchFailed = this.update.getProperty("patchingFailed");
441 }
442 catch (e) {
443 }
444 if (patchFailed) {
445 if (patchFailed == "partial" && this.update.patchCount == 2) {
446 // If the system failed to apply the partial patch, show the
447 // screen which best describes this condition, which is triggered
448 // by the |STATE_FAILED| state.
449 state = STATE_FAILED;
450 }
451 else {
452 // Otherwise, if the complete patch failed, which is far less
453 // likely, show the error text held by the update object in the
454 // generic errors page, triggered by the |STATE_DOWNLOAD_FAILED|
455 // state.
456 state = STATE_DOWNLOAD_FAILED;
457 }
458 }
460 // Now select the best page to start with, given the current state of
461 // the Update.
462 switch (state) {
463 case STATE_PENDING:
464 case STATE_PENDING_SVC:
465 case STATE_APPLIED:
466 case STATE_APPLIED_SVC:
467 this.sourceEvent = SRCEVT_BACKGROUND;
468 aCallback("finishedBackground");
469 return;
470 case STATE_DOWNLOADING:
471 aCallback("downloading");
472 return;
473 case STATE_FAILED:
474 window.getAttention();
475 aCallback("errorpatching");
476 return;
477 case STATE_DOWNLOAD_FAILED:
478 case STATE_APPLYING:
479 aCallback("errors");
480 return;
481 }
482 }
483 if (this.update.licenseURL)
484 this.wiz.getPageById(this.updatesFoundPageId).setAttribute("next", "license");
486 var self = this;
487 this.getShouldCheckAddonCompatibility(function(shouldCheck) {
488 if (shouldCheck) {
489 var incompatCheckPage = document.getElementById("incompatibleCheck");
490 incompatCheckPage.setAttribute("next", self.updatesFoundPageId);
491 aCallback(incompatCheckPage.id);
492 }
493 else {
494 aCallback(self.updatesFoundPageId);
495 }
496 });
497 return;
498 }
499 else if (arg0 == "installed") {
500 aCallback("installed");
501 return;
502 }
503 }
504 else {
505 var um = CoC["@mozilla.org/updates/update-manager;1"].
506 getService(CoI.nsIUpdateManager);
507 if (um.activeUpdate) {
508 this.setUpdate(um.activeUpdate);
509 aCallback("downloading");
510 return;
511 }
512 }
514 // Provide the ability to test the billboard html
515 var billboardTestURL = getPref("getCharPref", PREF_APP_UPDATE_BILLBOARD_TEST_URL, null);
516 if (billboardTestURL) {
517 var updatesFoundBillboardPage = document.getElementById("updatesfoundbillboard");
518 updatesFoundBillboardPage.setAttribute("next", "dummy");
519 gUpdatesFoundBillboardPage.onExtra1 = function(){ gUpdates.wiz.cancel(); };
520 gUpdatesFoundBillboardPage.onExtra2 = function(){ gUpdates.wiz.cancel(); };
521 this.onWizardNext = function() { gUpdates.wiz.cancel(); };
522 this.update = { billboardURL : billboardTestURL,
523 brandName : this.brandName,
524 displayVersion : "Billboard Test 1.0",
525 showNeverForVersion : true,
526 type : "major" };
527 aCallback(updatesFoundBillboardPage.id);
528 }
529 else {
530 aCallback("checking");
531 }
532 },
534 getShouldCheckAddonCompatibility: function(aCallback) {
535 // this early return should never happen
536 if (!this.update) {
537 aCallback(false);
538 return;
539 }
541 #ifdef TOR_BROWSER_UPDATE
542 var appVersion = TOR_BROWSER_VERSION;
543 #else
544 var appVersion = Services.appinfo.version;
545 #endif
546 if (!this.update.appVersion ||
547 Services.vc.compare(this.update.appVersion, appVersion) == 0) {
548 aCallback(false);
549 return;
550 }
552 try {
553 var hotfixID = Services.prefs.getCharPref(PREF_EM_HOTFIX_ID);
554 }
555 catch (e) { }
557 var self = this;
558 AddonManager.getAllAddons(function(addons) {
559 #ifdef TOR_BROWSER_UPDATE
560 let compatVersion = self.update.platformVersion;
561 #else
562 let compatVersion = self.update.appVersion;
563 #endif
564 self.addons = [];
565 addons.forEach(function(addon) {
566 // Protect against code that overrides the add-ons manager and doesn't
567 // implement the isCompatibleWith or the findUpdates method.
568 if (!("isCompatibleWith" in addon) || !("findUpdates" in addon)) {
569 let errMsg = "Add-on doesn't implement either the isCompatibleWith " +
570 "or the findUpdates method!";
571 if (addon.id)
572 errMsg += " Add-on ID: " + addon.id;
573 Components.utils.reportError(errMsg);
574 return;
575 }
577 // If an add-on isn't appDisabled and isn't userDisabled then it is
578 // either active now or the user expects it to be active after the
579 // restart. If that is the case and the add-on is not installed by the
580 // application and is not compatible with the new application version
581 // then the user should be warned that the add-on will become
582 // incompatible. If an addon's type equals plugin it is skipped since
583 // checking plugins compatibility information isn't supported and
584 // getting the scope property of a plugin breaks in some environments
585 // (see bug 566787). The hotfix add-on is also ignored as it shouldn't
586 // block the user from upgrading.
587 try {
588 if (addon.type != "plugin" && addon.id != hotfixID &&
589 !addon.appDisabled && !addon.userDisabled &&
590 addon.scope != AddonManager.SCOPE_APPLICATION &&
591 addon.isCompatible &&
592 !addon.isCompatibleWith(compatVersion,
593 self.update.platformVersion))
594 self.addons.push(addon);
595 }
596 catch (e) {
597 Components.utils.reportError(e);
598 }
599 });
601 aCallback(self.addons.length != 0);
602 });
603 },
605 /**
606 * Returns the string page ID for the appropriate updates found page based
607 * on the update's metadata.
608 */
609 get updatesFoundPageId() {
610 if (gUpdatesFoundPageId)
611 return gUpdatesFoundPageId;
612 return gUpdatesFoundPageId = this.update.billboardURL ? "updatesfoundbillboard"
613 : "updatesfoundbasic";
614 },
616 /**
617 * Sets the Update object for this wizard
618 * @param update
619 * The update object
620 */
621 setUpdate: function(update) {
622 this.update = update;
623 if (this.update)
624 this.update.QueryInterface(CoI.nsIWritablePropertyBag);
625 }
626 }
628 /**
629 * The "Checking for Updates" page. Provides feedback on the update checking
630 * process.
631 */
632 var gCheckingPage = {
633 /**
634 * The nsIUpdateChecker that is currently checking for updates. We hold onto
635 * this so we can cancel the update check if the user closes the window.
636 */
637 _checker: null,
639 /**
640 * Initialize
641 */
642 onPageShow: function() {
643 gUpdates.setButtons(null, null, null, false, true);
644 gUpdates.wiz.getButton("cancel").focus();
646 // Clear all of the "never" prefs to handle the scenario where the user
647 // clicked "never" for an update, selected "Check for Updates...", and
648 // then canceled. If we don't clear the "never" prefs future
649 // notifications will never happen.
650 Services.prefs.deleteBranch(PREF_APP_UPDATE_NEVER_BRANCH);
652 // The user will be notified if there is an error so clear the background
653 // check error count.
654 if (Services.prefs.prefHasUserValue(PREF_APP_UPDATE_BACKGROUNDERRORS))
655 Services.prefs.clearUserPref(PREF_APP_UPDATE_BACKGROUNDERRORS);
657 // The preference will be set back to true if the system is still
658 // unsupported.
659 if (Services.prefs.prefHasUserValue(PREF_APP_UPDATE_NOTIFIEDUNSUPPORTED))
660 Services.prefs.clearUserPref(PREF_APP_UPDATE_NOTIFIEDUNSUPPORTED);
662 this._checker = CoC["@mozilla.org/updates/update-checker;1"].
663 createInstance(CoI.nsIUpdateChecker);
664 this._checker.checkForUpdates(this.updateListener, true);
665 },
667 /**
668 * The user has closed the window, either by pressing cancel or using a Window
669 * Manager control, so stop checking for updates.
670 */
671 onWizardCancel: function() {
672 this._checker.stopChecking(CoI.nsIUpdateChecker.CURRENT_CHECK);
673 },
675 /**
676 * An object implementing nsIUpdateCheckListener that is notified as the
677 * update check commences.
678 */
679 updateListener: {
680 /**
681 * See nsIUpdateCheckListener
682 */
683 onCheckComplete: function(request, updates, updateCount) {
684 var aus = CoC["@mozilla.org/updates/update-service;1"].
685 getService(CoI.nsIApplicationUpdateService);
686 gUpdates.setUpdate(aus.selectUpdate(updates, updates.length));
687 if (gUpdates.update) {
688 LOG("gCheckingPage", "onCheckComplete - update found");
689 if (gUpdates.update.unsupported) {
690 gUpdates.wiz.goTo("unsupported");
691 return;
692 }
694 if (!aus.canApplyUpdates) {
695 // Prevent multiple notifications for the same update when the user is
696 // unable to apply updates.
697 gUpdates.never();
698 gUpdates.wiz.goTo("manualUpdate");
699 return;
700 }
702 if (gUpdates.update.licenseURL) {
703 // gUpdates.updatesFoundPageId returns the pageid and not the
704 // element's id so use the wizard's getPageById method.
705 gUpdates.wiz.getPageById(gUpdates.updatesFoundPageId).setAttribute("next", "license");
706 }
708 gUpdates.getShouldCheckAddonCompatibility(function(shouldCheck) {
709 if (shouldCheck) {
710 var incompatCheckPage = document.getElementById("incompatibleCheck");
711 incompatCheckPage.setAttribute("next", gUpdates.updatesFoundPageId);
712 gUpdates.wiz.goTo("incompatibleCheck");
713 }
714 else {
715 gUpdates.wiz.goTo(gUpdates.updatesFoundPageId);
716 }
717 });
718 return;
719 }
721 LOG("gCheckingPage", "onCheckComplete - no update found");
722 gUpdates.wiz.goTo("noupdatesfound");
723 },
725 /**
726 * See nsIUpdateCheckListener
727 */
728 onError: function(request, update) {
729 LOG("gCheckingPage", "onError - proceeding to error page");
730 gUpdates.setUpdate(update);
731 if (update.errorCode &&
732 (update.errorCode == CERT_ATTR_CHECK_FAILED_NO_UPDATE ||
733 update.errorCode == CERT_ATTR_CHECK_FAILED_HAS_UPDATE)) {
734 gUpdates.wiz.goTo("errorextra");
735 }
736 else {
737 gUpdates.wiz.goTo("errors");
738 }
739 },
741 /**
742 * See nsISupports.idl
743 */
744 QueryInterface: function(aIID) {
745 if (!aIID.equals(CoI.nsIUpdateCheckListener) &&
746 !aIID.equals(CoI.nsISupports))
747 throw CoR.NS_ERROR_NO_INTERFACE;
748 return this;
749 }
750 }
751 };
753 /**
754 * The "You have outdated plugins" page
755 */
756 var gPluginsPage = {
757 /**
758 * URL of the plugin updates page
759 */
760 _url: null,
762 /**
763 * Initialize
764 */
765 onPageShow: function() {
766 var prefs = Services.prefs;
767 if (prefs.getPrefType(PREF_PLUGINS_UPDATEURL) == prefs.PREF_INVALID) {
768 gUpdates.wiz.goTo("noupdatesfound");
769 return;
770 }
772 this._url = Services.urlFormatter.formatURLPref(PREF_PLUGINS_UPDATEURL);
773 var link = document.getElementById("pluginupdateslink");
774 link.setAttribute("href", this._url);
777 var phs = CoC["@mozilla.org/plugin/host;1"].
778 getService(CoI.nsIPluginHost);
779 var plugins = phs.getPluginTags();
780 var blocklist = CoC["@mozilla.org/extensions/blocklist;1"].
781 getService(CoI.nsIBlocklistService);
783 var hasOutdated = false;
784 for (let i = 0; i < plugins.length; i++) {
785 let pluginState = blocklist.getPluginBlocklistState(plugins[i]);
786 if (pluginState == CoI.nsIBlocklistService.STATE_OUTDATED) {
787 hasOutdated = true;
788 break;
789 }
790 }
791 if (!hasOutdated) {
792 gUpdates.wiz.goTo("noupdatesfound");
793 return;
794 }
796 gUpdates.setButtons(null, null, "okButton", true);
797 gUpdates.wiz.getButton("finish").focus();
798 },
800 /**
801 * Finish button clicked.
802 */
803 onWizardFinish: function() {
804 openURL(this._url);
805 }
806 };
808 /**
809 * The "No Updates Are Available" page
810 */
811 var gNoUpdatesPage = {
812 /**
813 * Initialize
814 */
815 onPageShow: function() {
816 LOG("gNoUpdatesPage", "onPageShow - could not select an appropriate " +
817 "update. Either there were no updates or |selectUpdate| failed");
819 if (getPref("getBoolPref", PREF_APP_UPDATE_ENABLED, true))
820 document.getElementById("noUpdatesAutoEnabled").hidden = false;
821 else
822 document.getElementById("noUpdatesAutoDisabled").hidden = false;
824 gUpdates.setButtons(null, null, "okButton", true);
825 gUpdates.wiz.getButton("finish").focus();
826 }
827 };
830 /**
831 * The page that checks if there are any incompatible add-ons.
832 */
833 var gIncompatibleCheckPage = {
834 /**
835 * Count of incompatible add-ons to check for updates
836 */
837 _totalCount: 0,
839 /**
840 * Count of incompatible add-ons that have beend checked for updates
841 */
842 _completedCount: 0,
844 /**
845 * The progress bar for this page
846 */
847 _pBar: null,
849 /**
850 * Initialize
851 */
852 onPageShow: function() {
853 LOG("gIncompatibleCheckPage", "onPageShow - checking for updates to " +
854 "incompatible add-ons");
856 gUpdates.setButtons(null, null, null, false, true);
857 gUpdates.wiz.getButton("cancel").focus();
858 this._pBar = document.getElementById("incompatibleCheckProgress");
859 this._totalCount = gUpdates.addons.length;
861 this._pBar.mode = "normal";
862 #ifdef TOR_BROWSER_UPDATE
863 let compatVersion = gUpdates.update.platformVersion;
864 #else
865 let compatVersion = gUpdates.update.appVersion;
866 #endif
867 gUpdates.addons.forEach(function(addon) {
868 addon.findUpdates(this, AddonManager.UPDATE_WHEN_NEW_APP_DETECTED,
869 compatVersion,
870 gUpdates.update.platformVersion);
871 }, this);
872 },
874 // Addon UpdateListener
875 onCompatibilityUpdateAvailable: function(addon) {
876 // Remove the add-on from the list of add-ons that will become incompatible
877 // with the new version of the application.
878 for (var i = 0; i < gUpdates.addons.length; ++i) {
879 if (gUpdates.addons[i].id == addon.id) {
880 LOG("gIncompatibleCheckPage", "onCompatibilityUpdateAvailable - " +
881 "found update for add-on ID: " + addon.id);
882 gUpdates.addons.splice(i, 1);
883 break;
884 }
885 }
886 },
888 onUpdateAvailable: function(addon, install) {
889 // If the new version of this add-on is blocklisted for the new application
890 // then it isn't a valid update and the user should still be warned that
891 // the add-on will become incompatible.
892 let bs = CoC["@mozilla.org/extensions/blocklist;1"].
893 getService(CoI.nsIBlocklistService);
894 #ifdef TOR_BROWSER_UPDATE
895 let compatVersion = gUpdates.update.platformVersion;
896 #else
897 let compatVersion = gUpdates.update.appVersion;
898 #endif
899 if (bs.isAddonBlocklisted(addon,
900 compatVersion,
901 gUpdates.update.platformVersion))
902 return;
904 // Compatibility or new version updates mean the same thing here.
905 this.onCompatibilityUpdateAvailable(addon);
906 },
908 onUpdateFinished: function(addon) {
909 ++this._completedCount;
910 this._pBar.value = Math.ceil((this._completedCount / this._totalCount) * 100);
912 if (this._completedCount < this._totalCount)
913 return;
915 if (gUpdates.addons.length == 0) {
916 LOG("gIncompatibleCheckPage", "onUpdateFinished - updates were found " +
917 "for all incompatible add-ons");
918 }
919 else {
920 LOG("gIncompatibleCheckPage", "onUpdateFinished - there are still " +
921 "incompatible add-ons");
922 if (gUpdates.update.licenseURL) {
923 document.getElementById("license").setAttribute("next", "incompatibleList");
924 }
925 else {
926 // gUpdates.updatesFoundPageId returns the pageid and not the element's
927 // id so use the wizard's getPageById method.
928 gUpdates.wiz.getPageById(gUpdates.updatesFoundPageId).setAttribute("next", "incompatibleList");
929 }
930 }
931 gUpdates.wiz.goTo(gUpdates.updatesFoundPageId);
932 }
933 };
935 /**
936 * The "Unable to Update" page. Provides the user information about why they
937 * were unable to update and a manual download url.
938 */
939 var gManualUpdatePage = {
940 onPageShow: function() {
941 var manualURL = Services.urlFormatter.formatURLPref(PREF_APP_UPDATE_MANUAL_URL);
942 var manualUpdateLinkLabel = document.getElementById("manualUpdateLinkLabel");
943 manualUpdateLinkLabel.value = manualURL;
944 manualUpdateLinkLabel.setAttribute("url", manualURL);
946 gUpdates.setButtons(null, null, "okButton", true);
947 gUpdates.wiz.getButton("finish").focus();
948 }
949 };
951 /**
952 * The "System Unsupported" page. Provides the user with information about their
953 * system no longer being supported and an url for more information.
954 */
955 var gUnsupportedPage = {
956 onPageShow: function() {
957 Services.prefs.setBoolPref(PREF_APP_UPDATE_NOTIFIEDUNSUPPORTED, true);
958 if (gUpdates.update.detailsURL) {
959 let unsupportedLinkLabel = document.getElementById("unsupportedLinkLabel");
960 unsupportedLinkLabel.setAttribute("url", gUpdates.update.detailsURL);
961 }
963 gUpdates.setButtons(null, null, "okButton", true);
964 gUpdates.wiz.getButton("finish").focus();
965 }
966 };
968 /**
969 * The "Updates Are Available" page. Provides the user information about the
970 * available update.
971 */
972 var gUpdatesFoundBasicPage = {
973 /**
974 * Initialize
975 */
976 onPageShow: function() {
977 gUpdates.wiz.canRewind = false;
978 var update = gUpdates.update;
979 gUpdates.setButtons("askLaterButton",
980 update.showNeverForVersion ? "noThanksButton" : null,
981 "updateButton_" + update.type, true);
982 var btn = gUpdates.wiz.getButton("next");
983 btn.focus();
985 var updateName = update.name;
986 if (update.channel == "nightly") {
987 updateName = gUpdates.getAUSString("updateNightlyName",
988 [gUpdates.brandName,
989 update.displayVersion,
990 update.buildID]);
991 }
992 var updateNameElement = document.getElementById("updateName");
993 updateNameElement.value = updateName;
995 var introText = gUpdates.getAUSString("intro_" + update.type,
996 [gUpdates.brandName, update.displayVersion]);
997 var introElem = document.getElementById("updatesFoundInto");
998 introElem.setAttribute("severity", update.type);
999 introElem.textContent = introText;
1001 var updateMoreInfoURL = document.getElementById("updateMoreInfoURL");
1002 if (update.detailsURL)
1003 updateMoreInfoURL.setAttribute("url", update.detailsURL);
1004 else
1005 updateMoreInfoURL.hidden = true;
1007 var updateTitle = gUpdates.getAUSString("updatesfound_" + update.type +
1008 ".title");
1009 document.getElementById("updatesFoundBasicHeader").setAttribute("label", updateTitle);
1010 },
1012 onExtra1: function() {
1013 gUpdates.wiz.cancel();
1014 },
1016 onExtra2: function() {
1017 gUpdates.never();
1018 gUpdates.wiz.cancel();
1019 }
1020 };
1022 /**
1023 * The "Updates Are Available" page with a billboard. Provides the user
1024 * information about the available update.
1025 */
1026 var gUpdatesFoundBillboardPage = {
1027 /**
1028 * If this page has been previously loaded
1029 */
1030 _billboardLoaded: false,
1032 /**
1033 * Initialize
1034 */
1035 onPageShow: function() {
1036 var update = gUpdates.update;
1037 gUpdates.setButtons("askLaterButton",
1038 update.showNeverForVersion ? "noThanksButton" : null,
1039 "updateButton_" + update.type, true);
1040 gUpdates.wiz.getButton("next").focus();
1042 if (this._billboardLoaded)
1043 return;
1045 var remoteContent = document.getElementById("updateMoreInfoContent");
1046 remoteContent.addEventListener("load",
1047 gUpdatesFoundBillboardPage.onBillboardLoad,
1048 false);
1049 // update_name and update_version need to be set before url
1050 // so that when attempting to download the url, we can show
1051 // the formatted "Download..." string
1052 remoteContent.update_name = gUpdates.brandName;
1053 remoteContent.update_version = update.displayVersion;
1055 var billboardTestURL = getPref("getCharPref", PREF_APP_UPDATE_BILLBOARD_TEST_URL, null);
1056 if (billboardTestURL) {
1057 // Allow file urls when testing the billboard and fallback to the
1058 // normal method if the URL isn't a file.
1059 var scheme = Services.io.newURI(billboardTestURL, null, null).scheme;
1060 if (scheme == "file")
1061 remoteContent.testFileUrl = update.billboardURL;
1062 else
1063 remoteContent.url = update.billboardURL;
1064 }
1065 else
1066 remoteContent.url = update.billboardURL;
1068 this._billboardLoaded = true;
1069 },
1071 /**
1072 * When the billboard document has loaded
1073 */
1074 onBillboardLoad: function(aEvent) {
1075 var remoteContent = document.getElementById("updateMoreInfoContent");
1076 // Note: may be called multiple times due to multiple onLoad events.
1077 var state = remoteContent.getAttribute("state");
1078 if (state == "loading" || aEvent.originalTarget != remoteContent)
1079 return;
1081 remoteContent.removeEventListener("load", gUpdatesFoundBillboardPage.onBillboardLoad, false);
1082 if (state == "error") {
1083 gUpdatesFoundPageId = "updatesfoundbasic";
1084 var next = gUpdates.wiz.getPageById("updatesfoundbillboard").getAttribute("next");
1085 gUpdates.wiz.getPageById(gUpdates.updatesFoundPageId).setAttribute("next", next);
1086 gUpdates.wiz.goTo(gUpdates.updatesFoundPageId);
1087 }
1088 },
1090 onExtra1: function() {
1091 this.onWizardCancel();
1092 gUpdates.wiz.cancel();
1093 },
1095 onExtra2: function() {
1096 this.onWizardCancel();
1097 gUpdates.never();
1098 gUpdates.wiz.cancel();
1099 },
1101 /**
1102 * When the user cancels the wizard
1103 */
1104 onWizardCancel: function() {
1105 try {
1106 var remoteContent = document.getElementById("updateMoreInfoContent");
1107 if (remoteContent)
1108 remoteContent.stopDownloading();
1109 }
1110 catch (e) {
1111 LOG("gUpdatesFoundBillboardPage", "onWizardCancel - " +
1112 "moreInfoContent.stopDownloading() failed: " + e);
1113 }
1114 }
1115 };
1117 /**
1118 * The page which shows the user a license associated with an update. The
1119 * user must agree to the terms of the license before continuing to install
1120 * the update.
1121 */
1122 var gLicensePage = {
1123 /**
1124 * If the license url has been previously loaded
1125 */
1126 _licenseLoaded: false,
1128 /**
1129 * Initialize
1130 */
1131 onPageShow: function() {
1132 gUpdates.setButtons("backButton", null, "acceptTermsButton", false);
1134 var licenseContent = document.getElementById("licenseContent");
1135 if (this._licenseLoaded || licenseContent.getAttribute("state") == "error") {
1136 this.onAcceptDeclineRadio();
1137 var licenseGroup = document.getElementById("acceptDeclineLicense");
1138 licenseGroup.focus();
1139 return;
1140 }
1142 gUpdates.wiz.canAdvance = false;
1144 // Disable the license radiogroup until the EULA has been downloaded
1145 document.getElementById("acceptDeclineLicense").disabled = true;
1146 gUpdates.update.setProperty("licenseAccepted", "false");
1148 licenseContent.addEventListener("load", gLicensePage.onLicenseLoad, false);
1149 // The update_name and update_version need to be set before url so the ui
1150 // can display the formatted "Download..." string when attempting to
1151 // download the url.
1152 licenseContent.update_name = gUpdates.brandName;
1153 licenseContent.update_version = gUpdates.update.displayVersion;
1154 licenseContent.url = gUpdates.update.licenseURL;
1155 },
1157 /**
1158 * When the license document has loaded
1159 */
1160 onLicenseLoad: function(aEvent) {
1161 var licenseContent = document.getElementById("licenseContent");
1162 // Disable or enable the radiogroup based on the state attribute of
1163 // licenseContent.
1164 // Note: may be called multiple times due to multiple onLoad events.
1165 var state = licenseContent.getAttribute("state");
1166 if (state == "loading" || aEvent.originalTarget != licenseContent)
1167 return;
1169 licenseContent.removeEventListener("load", gLicensePage.onLicenseLoad, false);
1171 if (state == "error") {
1172 gUpdates.wiz.goTo("manualUpdate");
1173 return;
1174 }
1176 gLicensePage._licenseLoaded = true;
1177 document.getElementById("acceptDeclineLicense").disabled = false;
1178 gUpdates.wiz.getButton("extra1").disabled = false;
1179 },
1181 /**
1182 * When the user changes the state of the accept / decline radio group
1183 */
1184 onAcceptDeclineRadio: function() {
1185 // Return early if this page hasn't been loaded (bug 405257). This event is
1186 // fired during the construction of the wizard before gUpdates has received
1187 // its onload event (bug 452389).
1188 if (!this._licenseLoaded)
1189 return;
1191 var selectedIndex = document.getElementById("acceptDeclineLicense")
1192 .selectedIndex;
1193 // 0 == Accept, 1 == Decline
1194 var licenseAccepted = (selectedIndex == 0);
1195 gUpdates.wiz.canAdvance = licenseAccepted;
1196 },
1198 /**
1199 * The non-standard "Back" button.
1200 */
1201 onExtra1: function() {
1202 gUpdates.wiz.goTo(gUpdates.updatesFoundPageId);
1203 },
1205 /**
1206 * When the user clicks next after accepting the license
1207 */
1208 onWizardNext: function() {
1209 try {
1210 gUpdates.update.setProperty("licenseAccepted", "true");
1211 var um = CoC["@mozilla.org/updates/update-manager;1"].
1212 getService(CoI.nsIUpdateManager);
1213 um.saveUpdates();
1214 }
1215 catch (e) {
1216 LOG("gLicensePage", "onWizardNext - nsIUpdateManager:saveUpdates() " +
1217 "failed: " + e);
1218 }
1219 },
1221 /**
1222 * When the user cancels the wizard
1223 */
1224 onWizardCancel: function() {
1225 try {
1226 var licenseContent = document.getElementById("licenseContent");
1227 // If the license was downloading, stop it.
1228 if (licenseContent)
1229 licenseContent.stopDownloading();
1230 }
1231 catch (e) {
1232 LOG("gLicensePage", "onWizardCancel - " +
1233 "licenseContent.stopDownloading() failed: " + e);
1234 }
1235 }
1236 };
1238 /**
1239 * The page which shows add-ons that are incompatible and do not have updated
1240 * compatibility information or a version update available to make them
1241 * compatible.
1242 */
1243 var gIncompatibleListPage = {
1244 /**
1245 * Initialize
1246 */
1247 onPageShow: function() {
1248 gUpdates.setButtons("backButton", null, "okButton", true);
1249 var listbox = document.getElementById("incompatibleListbox");
1250 if (listbox.children.length > 0)
1251 return;
1253 var intro = gUpdates.getAUSString("incompatAddons_" + gUpdates.update.type,
1254 [gUpdates.brandName,
1255 gUpdates.update.displayVersion]);
1256 document.getElementById("incompatibleListDesc").textContent = intro;
1258 var addons = gUpdates.addons;
1259 for (var i = 0; i < addons.length; ++i) {
1260 var listitem = document.createElement("listitem");
1261 var addonLabel = gUpdates.getAUSString("addonLabel", [addons[i].name,
1262 addons[i].version]);
1263 listitem.setAttribute("label", addonLabel);
1264 listbox.appendChild(listitem);
1265 }
1266 },
1268 /**
1269 * The non-standard "Back" button.
1270 */
1271 onExtra1: function() {
1272 gUpdates.wiz.goTo(gUpdates.update.licenseURL ? "license"
1273 : gUpdates.updatesFoundPageId);
1274 }
1275 };
1277 /**
1278 * The "Update is Downloading" page - provides feedback for the download
1279 * process plus a pause/resume UI
1280 */
1281 var gDownloadingPage = {
1282 /**
1283 * DOM Elements
1284 */
1285 _downloadStatus: null,
1286 _downloadProgress: null,
1287 _pauseButton: null,
1289 /**
1290 * Whether or not we are currently paused
1291 */
1292 _paused: false,
1294 /**
1295 * Label cache to hold the 'Connecting' string
1296 */
1297 _label_downloadStatus: null,
1299 /**
1300 * Member variables for updating download status
1301 */
1302 _lastSec: Infinity,
1303 _startTime: null,
1304 _pausedStatus: "",
1306 _hiding: false,
1308 /**
1309 * Have we registered an observer for a background update being staged
1310 */
1311 _updateApplyingObserver: false,
1313 /**
1314 * Initialize
1315 */
1316 onPageShow: function() {
1317 this._downloadStatus = document.getElementById("downloadStatus");
1318 this._downloadProgress = document.getElementById("downloadProgress");
1319 this._pauseButton = document.getElementById("pauseButton");
1320 this._label_downloadStatus = this._downloadStatus.textContent;
1322 this._pauseButton.setAttribute("tooltiptext",
1323 gUpdates.getAUSString("pauseButtonPause"));
1325 // move focus to the pause/resume button and then disable it (bug #353177)
1326 this._pauseButton.focus();
1327 this._pauseButton.disabled = true;
1329 var aus = CoC["@mozilla.org/updates/update-service;1"].
1330 getService(CoI.nsIApplicationUpdateService);
1332 var um = CoC["@mozilla.org/updates/update-manager;1"].
1333 getService(CoI.nsIUpdateManager);
1334 var activeUpdate = um.activeUpdate;
1335 if (activeUpdate)
1336 gUpdates.setUpdate(activeUpdate);
1338 if (!gUpdates.update) {
1339 LOG("gDownloadingPage", "onPageShow - no valid update to download?!");
1340 return;
1341 }
1343 this._startTime = Date.now();
1345 try {
1346 // Say that this was a foreground download, not a background download,
1347 // since the user cared enough to look in on this process.
1348 gUpdates.update.QueryInterface(CoI.nsIWritablePropertyBag);
1349 gUpdates.update.setProperty("foregroundDownload", "true");
1351 // Pause any active background download and restart it as a foreground
1352 // download.
1353 aus.pauseDownload();
1354 var state = aus.downloadUpdate(gUpdates.update, false);
1355 if (state == "failed") {
1356 // We've tried as hard as we could to download a valid update -
1357 // we fell back from a partial patch to a complete patch and even
1358 // then we couldn't validate. Show a validation error with instructions
1359 // on how to manually update.
1360 this.cleanUp();
1361 gUpdates.wiz.goTo("errors");
1362 return;
1363 }
1364 else {
1365 // Add this UI as a listener for active downloads
1366 aus.addDownloadListener(this);
1367 }
1369 if (activeUpdate)
1370 this._setUIState(!aus.isDownloading);
1371 }
1372 catch(e) {
1373 LOG("gDownloadingPage", "onPageShow - error: " + e);
1374 }
1376 gUpdates.setButtons("hideButton", null, null, false);
1377 gUpdates.wiz.getButton("extra1").focus();
1378 },
1380 /**
1381 * Updates the text status message
1382 */
1383 _setStatus: function(status) {
1384 // Don't bother setting the same text more than once. This can happen
1385 // due to the asynchronous behavior of the downloader.
1386 if (this._downloadStatus.textContent == status)
1387 return;
1388 while (this._downloadStatus.hasChildNodes())
1389 this._downloadStatus.removeChild(this._downloadStatus.firstChild);
1390 this._downloadStatus.appendChild(document.createTextNode(status));
1391 },
1393 /**
1394 * Update download progress status to show time left, speed, and progress.
1395 * Also updates the status needed for pausing the download.
1396 *
1397 * @param aCurr
1398 * Current number of bytes transferred
1399 * @param aMax
1400 * Total file size of the download
1401 * @return Current active download status
1402 */
1403 _updateDownloadStatus: function(aCurr, aMax) {
1404 let status;
1406 // Get the download time left and progress
1407 let rate = aCurr / (Date.now() - this._startTime) * 1000;
1408 [status, this._lastSec] =
1409 DownloadUtils.getDownloadStatus(aCurr, aMax, rate, this._lastSec);
1411 // Get the download progress for pausing
1412 this._pausedStatus = DownloadUtils.getTransferTotal(aCurr, aMax);
1414 return status;
1415 },
1417 /**
1418 * Adjust UI to suit a certain state of paused-ness
1419 * @param paused
1420 * Whether or not the download is paused
1421 */
1422 _setUIState: function(paused) {
1423 var u = gUpdates.update;
1424 if (paused) {
1425 if (this._downloadProgress.mode != "normal")
1426 this._downloadProgress.mode = "normal";
1427 this._pauseButton.setAttribute("tooltiptext",
1428 gUpdates.getAUSString("pauseButtonResume"));
1429 this._pauseButton.setAttribute("paused", "true");
1430 var p = u.selectedPatch.QueryInterface(CoI.nsIPropertyBag);
1431 var status = p.getProperty("status");
1432 if (status) {
1433 let pausedStatus = gUpdates.getAUSString("downloadPausedStatus", [status]);
1434 this._setStatus(pausedStatus);
1435 }
1436 }
1437 else {
1438 if (this._downloadProgress.mode != "undetermined")
1439 this._downloadProgress.mode = "undetermined";
1440 this._pauseButton.setAttribute("paused", "false");
1441 this._pauseButton.setAttribute("tooltiptext",
1442 gUpdates.getAUSString("pauseButtonPause"));
1443 this._setStatus(this._label_downloadStatus);
1444 }
1445 },
1447 /**
1448 * Wait for an update being staged in the background.
1449 */
1450 _setUpdateApplying: function() {
1451 this._downloadProgress.mode = "undetermined";
1452 this._pauseButton.hidden = true;
1453 let applyingStatus = gUpdates.getAUSString("applyingUpdate");
1454 this._setStatus(applyingStatus);
1456 Services.obs.addObserver(this, "update-staged", false);
1457 this._updateApplyingObserver = true;
1458 },
1460 /**
1461 * Clean up the listener and observer registered for the wizard.
1462 */
1463 cleanUp: function() {
1464 var aus = CoC["@mozilla.org/updates/update-service;1"].
1465 getService(CoI.nsIApplicationUpdateService);
1466 aus.removeDownloadListener(this);
1468 if (this._updateApplyingObserver) {
1469 Services.obs.removeObserver(this, "update-staged");
1470 this._updateApplyingObserver = false;
1471 }
1472 },
1474 /**
1475 * When the user clicks the Pause/Resume button
1476 */
1477 onPause: function() {
1478 var aus = CoC["@mozilla.org/updates/update-service;1"].
1479 getService(CoI.nsIApplicationUpdateService);
1480 if (this._paused)
1481 aus.downloadUpdate(gUpdates.update, false);
1482 else {
1483 var patch = gUpdates.update.selectedPatch;
1484 patch.QueryInterface(CoI.nsIWritablePropertyBag);
1485 patch.setProperty("status", this._pausedStatus);
1486 aus.pauseDownload();
1487 }
1488 this._paused = !this._paused;
1490 // Update the UI
1491 this._setUIState(this._paused);
1492 },
1494 /**
1495 * When the user has closed the window using a Window Manager control (this
1496 * page doesn't have a cancel button) cancel the update in progress.
1497 */
1498 onWizardCancel: function() {
1499 if (this._hiding)
1500 return;
1502 this.cleanUp();
1503 },
1505 /**
1506 * When the user closes the Wizard UI by clicking the Hide button
1507 */
1508 onHide: function() {
1509 // Set _hiding to true to prevent onWizardCancel from cancelling the update
1510 // that is in progress.
1511 this._hiding = true;
1513 // Remove ourself as a download listener so that we don't continue to be
1514 // fed progress and state notifications after the UI we're updating has
1515 // gone away.
1516 this.cleanUp();
1518 var aus = CoC["@mozilla.org/updates/update-service;1"].
1519 getService(CoI.nsIApplicationUpdateService);
1520 var um = CoC["@mozilla.org/updates/update-manager;1"].
1521 getService(CoI.nsIUpdateManager);
1522 um.activeUpdate = gUpdates.update;
1524 // If the download was paused by the user, ask the user if they want to
1525 // have the update resume in the background.
1526 var downloadInBackground = true;
1527 if (this._paused) {
1528 var title = gUpdates.getAUSString("resumePausedAfterCloseTitle");
1529 var message = gUpdates.getAUSString("resumePausedAfterCloseMsg",
1530 [gUpdates.brandName]);
1531 var ps = Services.prompt;
1532 var flags = ps.STD_YES_NO_BUTTONS;
1533 // Focus the software update wizard before prompting. This will raise
1534 // the software update wizard if it is minimized making it more obvious
1535 // what the prompt is for and will solve the problem of windows
1536 // obscuring the prompt. See bug #350299 for more details.
1537 window.focus();
1538 var rv = ps.confirmEx(window, title, message, flags, null, null, null,
1539 null, { });
1540 if (rv == CoI.nsIPromptService.BUTTON_POS_0)
1541 downloadInBackground = false;
1542 }
1543 if (downloadInBackground) {
1544 // Continue download in the background at full speed.
1545 LOG("gDownloadingPage", "onHide - continuing download in background " +
1546 "at full speed");
1547 aus.downloadUpdate(gUpdates.update, false);
1548 }
1549 gUpdates.wiz.cancel();
1550 },
1552 /**
1553 * When the data transfer begins
1554 * @param request
1555 * The nsIRequest object for the transfer
1556 * @param context
1557 * Additional data
1558 */
1559 onStartRequest: function(request, context) {
1560 // This !paused test is necessary because onStartRequest may fire after
1561 // the download was paused (for those speedy clickers...)
1562 if (this._paused)
1563 return;
1565 if (this._downloadProgress.mode != "undetermined")
1566 this._downloadProgress.mode = "undetermined";
1567 this._setStatus(this._label_downloadStatus);
1568 },
1570 /**
1571 * When new data has been downloaded
1572 * @param request
1573 * The nsIRequest object for the transfer
1574 * @param context
1575 * Additional data
1576 * @param progress
1577 * The current number of bytes transferred
1578 * @param maxProgress
1579 * The total number of bytes that must be transferred
1580 */
1581 onProgress: function(request, context, progress, maxProgress) {
1582 let status = this._updateDownloadStatus(progress, maxProgress);
1583 var currentProgress = Math.round(100 * (progress / maxProgress));
1585 var p = gUpdates.update.selectedPatch;
1586 p.QueryInterface(CoI.nsIWritablePropertyBag);
1587 p.setProperty("progress", currentProgress);
1588 p.setProperty("status", status);
1590 // This !paused test is necessary because onProgress may fire after
1591 // the download was paused (for those speedy clickers...)
1592 if (this._paused)
1593 return;
1595 if (this._downloadProgress.mode != "normal")
1596 this._downloadProgress.mode = "normal";
1597 if (this._downloadProgress.value != currentProgress)
1598 this._downloadProgress.value = currentProgress;
1599 if (this._pauseButton.disabled)
1600 this._pauseButton.disabled = false;
1602 // If the update has completed downloading and the download status contains
1603 // the original text return early to avoid an assertion in debug builds.
1604 // Since the page will advance immmediately due to the update completing the
1605 // download updating the status is not important.
1606 // nsTextFrame::GetTrimmedOffsets 'Can only call this on frames that have
1607 // been reflowed'.
1608 if (progress == maxProgress &&
1609 this._downloadStatus.textContent == this._label_downloadStatus)
1610 return;
1612 this._setStatus(status);
1613 },
1615 /**
1616 * When we have new status text
1617 * @param request
1618 * The nsIRequest object for the transfer
1619 * @param context
1620 * Additional data
1621 * @param status
1622 * A status code
1623 * @param statusText
1624 * Human readable version of |status|
1625 */
1626 onStatus: function(request, context, status, statusText) {
1627 this._setStatus(statusText);
1628 },
1630 /**
1631 * When data transfer ceases
1632 * @param request
1633 * The nsIRequest object for the transfer
1634 * @param context
1635 * Additional data
1636 * @param status
1637 * Status code containing the reason for the cessation.
1638 */
1639 onStopRequest: function(request, context, status) {
1640 if (this._downloadProgress.mode != "normal")
1641 this._downloadProgress.mode = "normal";
1643 var u = gUpdates.update;
1644 switch (status) {
1645 case CoR.NS_ERROR_CORRUPTED_CONTENT:
1646 case CoR.NS_ERROR_UNEXPECTED:
1647 if (u.selectedPatch.state == STATE_DOWNLOAD_FAILED &&
1648 (u.isCompleteUpdate || u.patchCount != 2)) {
1649 // Verification error of complete patch, informational text is held in
1650 // the update object.
1651 this.cleanUp();
1652 gUpdates.wiz.goTo("errors");
1653 break;
1654 }
1655 // Verification failed for a partial patch, complete patch is now
1656 // downloading so return early and do NOT remove the download listener!
1658 // Reset the progress meter to "undertermined" mode so that we don't
1659 // show old progress for the new download of the "complete" patch.
1660 this._downloadProgress.mode = "undetermined";
1661 this._pauseButton.disabled = true;
1662 document.getElementById("verificationFailed").hidden = false;
1663 break;
1664 case CoR.NS_BINDING_ABORTED:
1665 LOG("gDownloadingPage", "onStopRequest - pausing download");
1666 // Do not remove UI listener since the user may resume downloading again.
1667 break;
1668 case CoR.NS_OK:
1669 LOG("gDownloadingPage", "onStopRequest - patch verification succeeded");
1670 // If the background update pref is set, we should wait until the update
1671 // is actually staged in the background.
1672 var aus = CoC["@mozilla.org/updates/update-service;1"].
1673 getService(CoI.nsIApplicationUpdateService);
1674 if (aus.canStageUpdates) {
1675 this._setUpdateApplying();
1676 } else {
1677 this.cleanUp();
1678 gUpdates.wiz.goTo("finished");
1679 }
1680 break;
1681 default:
1682 LOG("gDownloadingPage", "onStopRequest - transfer failed");
1683 // Some kind of transfer error, die.
1684 this.cleanUp();
1685 gUpdates.wiz.goTo("errors");
1686 break;
1687 }
1688 },
1690 /**
1691 * See nsIObserver.idl
1692 */
1693 observe: function(aSubject, aTopic, aData) {
1694 if (aTopic == "update-staged") {
1695 if (aData == STATE_DOWNLOADING) {
1696 // We've fallen back to downloding the full update because the
1697 // partial update failed to get staged in the background.
1698 this._setStatus("downloading");
1699 return;
1700 }
1701 this.cleanUp();
1702 if (aData == STATE_APPLIED ||
1703 aData == STATE_APPLIED_SVC ||
1704 aData == STATE_PENDING ||
1705 aData == STATE_PENDING_SVC) {
1706 // If the update is successfully applied, or if the updater has
1707 // fallen back to non-staged updates, go to the finish page.
1708 gUpdates.wiz.goTo("finished");
1709 } else {
1710 gUpdates.wiz.goTo("errors");
1711 }
1712 }
1713 },
1715 /**
1716 * See nsISupports.idl
1717 */
1718 QueryInterface: function(iid) {
1719 if (!iid.equals(CoI.nsIRequestObserver) &&
1720 !iid.equals(CoI.nsIProgressEventSink) &&
1721 !iid.equals(CoI.nsIObserver) &&
1722 !iid.equals(CoI.nsISupports))
1723 throw CoR.NS_ERROR_NO_INTERFACE;
1724 return this;
1725 }
1726 };
1728 /**
1729 * The "There was an error during the update" page.
1730 */
1731 var gErrorsPage = {
1732 /**
1733 * Initialize
1734 */
1735 onPageShow: function() {
1736 gUpdates.setButtons(null, null, "okButton", true);
1737 gUpdates.wiz.getButton("finish").focus();
1739 var statusText = gUpdates.update.statusText;
1740 LOG("gErrorsPage" , "onPageShow - update.statusText: " + statusText);
1742 var errorReason = document.getElementById("errorReason");
1743 errorReason.value = statusText;
1744 var manualURL = Services.urlFormatter.formatURLPref(PREF_APP_UPDATE_MANUAL_URL);
1745 var errorLinkLabel = document.getElementById("errorLinkLabel");
1746 errorLinkLabel.value = manualURL;
1747 errorLinkLabel.setAttribute("url", manualURL);
1748 }
1749 };
1751 /**
1752 * The page shown when there is a background check or a certificate attribute
1753 * error.
1754 */
1755 var gErrorExtraPage = {
1756 /**
1757 * Initialize
1758 */
1759 onPageShow: function() {
1760 gUpdates.setButtons(null, null, "okButton", true);
1761 gUpdates.wiz.getButton("finish").focus();
1762 let secHistogram = CoC["@mozilla.org/base/telemetry;1"].
1763 getService(CoI.nsITelemetry).
1764 getHistogramById("SECURITY_UI");
1766 if (Services.prefs.prefHasUserValue(PREF_APP_UPDATE_CERT_ERRORS))
1767 Services.prefs.clearUserPref(PREF_APP_UPDATE_CERT_ERRORS);
1769 if (Services.prefs.prefHasUserValue(PREF_APP_UPDATE_BACKGROUNDERRORS))
1770 Services.prefs.clearUserPref(PREF_APP_UPDATE_BACKGROUNDERRORS);
1772 if (gUpdates.update.errorCode == CERT_ATTR_CHECK_FAILED_HAS_UPDATE) {
1773 document.getElementById("errorCertAttrHasUpdateLabel").hidden = false;
1774 secHistogram.add(CoI.nsISecurityUITelemetry.WARNING_INSECURE_UPDATE);
1775 }
1776 else {
1777 if (gUpdates.update.errorCode == CERT_ATTR_CHECK_FAILED_NO_UPDATE){
1778 document.getElementById("errorCertCheckNoUpdateLabel").hidden = false;
1779 secHistogram.add(CoI.nsISecurityUITelemetry.WARNING_NO_SECURE_UPDATE);
1780 }
1781 else
1782 document.getElementById("genericBackgroundErrorLabel").hidden = false;
1783 var manualURL = Services.urlFormatter.formatURLPref(PREF_APP_UPDATE_MANUAL_URL);
1784 var errorLinkLabel = document.getElementById("errorExtraLinkLabel");
1785 errorLinkLabel.value = manualURL;
1786 errorLinkLabel.setAttribute("url", manualURL);
1787 errorLinkLabel.hidden = false;
1788 }
1789 }
1790 };
1792 /**
1793 * The "There was an error applying a partial patch" page.
1794 */
1795 var gErrorPatchingPage = {
1796 /**
1797 * Initialize
1798 */
1799 onPageShow: function() {
1800 gUpdates.setButtons(null, null, "okButton", true);
1801 },
1803 onWizardNext: function() {
1804 switch (gUpdates.update.selectedPatch.state) {
1805 case STATE_PENDING:
1806 case STATE_PENDING_SVC:
1807 gUpdates.wiz.goTo("finished");
1808 break;
1809 case STATE_DOWNLOADING:
1810 gUpdates.wiz.goTo("downloading");
1811 break;
1812 case STATE_DOWNLOAD_FAILED:
1813 gUpdates.wiz.goTo("errors");
1814 break;
1815 }
1816 }
1817 };
1819 /**
1820 * The "Update has been downloaded" page. Shows information about what
1821 * was downloaded.
1822 */
1823 var gFinishedPage = {
1824 /**
1825 * Initialize
1826 */
1827 onPageShow: function() {
1828 gUpdates.setButtons("restartLaterButton", null, "restartNowButton",
1829 true);
1830 gUpdates.wiz.getButton("finish").focus();
1831 },
1833 /**
1834 * Initialize the Wizard Page for a Background Source Event
1835 */
1836 onPageShowBackground: function() {
1837 this.onPageShow();
1838 var updateFinishedName = document.getElementById("updateFinishedName");
1839 updateFinishedName.value = gUpdates.update.name;
1841 var link = document.getElementById("finishedBackgroundLink");
1842 if (gUpdates.update.detailsURL) {
1843 link.setAttribute("url", gUpdates.update.detailsURL);
1844 // The details link is stealing focus so it is disabled by default and
1845 // should only be enabled after onPageShow has been called.
1846 link.disabled = false;
1847 }
1848 else
1849 link.hidden = true;
1851 if (getPref("getBoolPref", PREF_APP_UPDATE_TEST_LOOP, false)) {
1852 setTimeout(function () {
1853 gUpdates.wiz.getButton("finish").click();
1854 }, UPDATE_TEST_LOOP_INTERVAL);
1855 }
1856 },
1858 /**
1859 * Called when the wizard finishes, i.e. the "Restart Now" button is
1860 * clicked.
1861 */
1862 onWizardFinish: function() {
1863 // Do the restart
1864 LOG("gFinishedPage" , "onWizardFinish - restarting the application");
1866 // disable the "finish" (Restart) and "extra1" (Later) buttons
1867 // because the Software Update wizard is still up at the point,
1868 // and will remain up until we return and we close the
1869 // window with a |window.close()| in wizard.xml
1870 // (it was the firing the "wizardfinish" event that got us here.)
1871 // This prevents the user from switching back
1872 // to the Software Update dialog and clicking "Restart" or "Later"
1873 // when dealing with the "confirm close" prompts.
1874 // See bug #350299 for more details.
1875 gUpdates.wiz.getButton("finish").disabled = true;
1876 gUpdates.wiz.getButton("extra1").disabled = true;
1878 // Notify all windows that an application quit has been requested.
1879 var cancelQuit = CoC["@mozilla.org/supports-PRBool;1"].
1880 createInstance(CoI.nsISupportsPRBool);
1881 Services.obs.notifyObservers(cancelQuit, "quit-application-requested",
1882 "restart");
1884 // Something aborted the quit process.
1885 if (cancelQuit.data)
1886 return;
1888 // If already in safe mode restart in safe mode (bug 327119)
1889 if (Services.appinfo.inSafeMode) {
1890 let env = CoC["@mozilla.org/process/environment;1"].
1891 getService(CoI.nsIEnvironment);
1892 env.set("MOZ_SAFE_MODE_RESTART", "1");
1893 }
1895 // Restart the application
1896 CoC["@mozilla.org/toolkit/app-startup;1"].getService(CoI.nsIAppStartup).
1897 quit(CoI.nsIAppStartup.eAttemptQuit | CoI.nsIAppStartup.eRestart);
1898 },
1900 /**
1901 * When the user clicks the "Restart Later" instead of the Restart Now" button
1902 * in the wizard after an update has been downloaded.
1903 */
1904 onExtra1: function() {
1905 gUpdates.wiz.cancel();
1906 }
1907 };
1909 /**
1910 * The "Update was Installed Successfully" page.
1911 */
1912 var gInstalledPage = {
1913 /**
1914 * Initialize
1915 */
1916 onPageShow: function() {
1917 var branding = document.getElementById("brandStrings");
1918 try {
1919 // whatsNewURL should just be a pref (bug 546609).
1920 var url = branding.getFormattedString("whatsNewURL", [Services.appinfo.version]);
1921 var whatsnewLink = document.getElementById("whatsnewLink");
1922 whatsnewLink.setAttribute("url", url);
1923 whatsnewLink.hidden = false;
1924 }
1925 catch (e) {
1926 }
1928 gUpdates.setButtons(null, null, "okButton", true);
1929 gUpdates.wiz.getButton("finish").focus();
1930 }
1931 };
1933 /**
1934 * Callback for the Update Prompt to set the current page if an Update Wizard
1935 * window is already found to be open.
1936 * @param pageid
1937 * The ID of the page to switch to
1938 */
1939 function setCurrentPage(pageid) {
1940 gUpdates.wiz.currentPage = document.getElementById(pageid);
1941 }