toolkit/mozapps/update/content/updates.js

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

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();
  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;
  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);
  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();
  1110     catch (e) {
  1111       LOG("gUpdatesFoundBillboardPage", "onWizardCancel - " +
  1112           "moreInfoContent.stopDownloading() failed: " + e);
  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;
  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;
  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();
  1215     catch (e) {
  1216       LOG("gLicensePage", "onWizardNext - nsIUpdateManager:saveUpdates() " +
  1217           "failed: " + e);
  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();
  1231     catch (e) {
  1232       LOG("gLicensePage", "onWizardCancel - " +
  1233           "licenseContent.stopDownloading() failed: " + e);
  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);
  1266   },
  1268   /**
  1269    * The non-standard "Back" button.
  1270    */
  1271   onExtra1: function() {
  1272     gUpdates.wiz.goTo(gUpdates.update.licenseURL ? "license"
  1273                                                  : gUpdates.updatesFoundPageId);
  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;
  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;
  1364       else {
  1365         // Add this UI as a listener for active downloads
  1366         aus.addDownloadListener(this);
  1369       if (activeUpdate)
  1370         this._setUIState(!aus.isDownloading);
  1372     catch(e) {
  1373       LOG("gDownloadingPage", "onPageShow - error: " + e);
  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.
  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);
  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);
  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;
  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();
  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;
  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);
  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;
  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");
  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;
  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;
  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");
  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;
  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);
  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);
  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);
  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;
  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;
  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;
  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);
  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");
  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();
  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;
  1925     catch (e) {
  1928     gUpdates.setButtons(null, null, "okButton", true);
  1929     gUpdates.wiz.getButton("finish").focus();
  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);

mercurial