PageRenderTime 80ms CodeModel.GetById 17ms app.highlight 51ms RepoModel.GetById 1ms app.codeStats 0ms

/toolkit/mozapps/update/content/updates.js

http://github.com/zpao/v8monkey
JavaScript | 1821 lines | 1016 code | 199 blank | 606 comment | 171 complexity | 4bc21227c4e448fbd230eeef46083a26 MD5 | raw file

Large files files are truncated, but you can click here to view the full file

   1/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
   2/* ***** BEGIN LICENSE BLOCK *****
   3 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
   4 *
   5 * The contents of this file are subject to the Mozilla Public License Version
   6 * 1.1 (the "License"); you may not use this file except in compliance with
   7 * the License. You may obtain a copy of the License at
   8 * http://www.mozilla.org/MPL/
   9 *
  10 * Software distributed under the License is distributed on an "AS IS" basis,
  11 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
  12 * for the specific language governing rights and limitations under the
  13 * License.
  14 *
  15 * The Original Code is the Update Service.
  16 *
  17 * The Initial Developer of the Original Code is Google Inc.
  18 * Portions created by the Initial Developer are Copyright (C) 2005
  19 * the Initial Developer. All Rights Reserved.
  20 *
  21 * Contributor(s):
  22 *  Ben Goodger <ben@mozilla.org> (Original Author)
  23 *  Asaf Romano <mozilla.mano@sent.com>
  24 *  Jeff Walden <jwalden+code@mit.edu>
  25 *  Robert Strong <robert.bugzilla@gmail.com>
  26 *
  27 * Alternatively, the contents of this file may be used under the terms of
  28 * either the GNU General Public License Version 2 or later (the "GPL"), or
  29 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  30 * in which case the provisions of the GPL or the LGPL are applicable instead
  31 * of those above. If you wish to allow use of your version of this file only
  32 * under the terms of either the GPL or the LGPL, and not to allow others to
  33 * use your version of this file under the terms of the MPL, indicate your
  34 * decision by deleting the provisions above and replace them with the notice
  35 * and other provisions required by the GPL or the LGPL. If you do not delete
  36 * the provisions above, a recipient may use your version of this file under
  37 * the terms of any one of the MPL, the GPL or the LGPL.
  38 *
  39 * ***** END LICENSE BLOCK ***** */
  40
  41Components.utils.import("resource://gre/modules/DownloadUtils.jsm");
  42Components.utils.import("resource://gre/modules/AddonManager.jsm");
  43Components.utils.import("resource://gre/modules/Services.jsm");
  44
  45// Firefox's macBrowserOverlay.xul includes scripts that define Cc, Ci, and Cr
  46// so we have to use different names.
  47const CoC = Components.classes;
  48const CoI = Components.interfaces;
  49const CoR = Components.results;
  50
  51const XMLNS_XUL               = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
  52
  53const PREF_APP_UPDATE_BACKGROUNDERRORS   = "app.update.backgroundErrors";
  54const PREF_APP_UPDATE_BILLBOARD_TEST_URL = "app.update.billboard.test_url";
  55const PREF_APP_UPDATE_CERT_ERRORS        = "app.update.cert.errors";
  56const PREF_APP_UPDATE_ENABLED            = "app.update.enabled";
  57const PREF_APP_UPDATE_LOG                = "app.update.log";
  58const PREF_APP_UPDATE_MANUAL_URL         = "app.update.url.manual";
  59const PREF_APP_UPDATE_NEVER_BRANCH       = "app.update.never.";
  60const PREF_APP_UPDATE_TEST_LOOP          = "app.update.test.loop";
  61const PREF_PLUGINS_UPDATEURL             = "plugins.update.url";
  62
  63const PREF_EM_HOTFIX_ID                  = "extensions.hotfix.id";
  64
  65const UPDATE_TEST_LOOP_INTERVAL     = 2000;
  66
  67const URI_UPDATES_PROPERTIES  = "chrome://mozapps/locale/update/updates.properties";
  68
  69const STATE_DOWNLOADING       = "downloading";
  70const STATE_PENDING           = "pending";
  71const STATE_PENDING_SVC       = "pending-service";
  72const STATE_APPLYING          = "applying";
  73const STATE_SUCCEEDED         = "succeeded";
  74const STATE_DOWNLOAD_FAILED   = "download-failed";
  75const STATE_FAILED            = "failed";
  76
  77const SRCEVT_FOREGROUND       = 1;
  78const SRCEVT_BACKGROUND       = 2;
  79
  80const CERT_ATTR_CHECK_FAILED_NO_UPDATE  = 100;
  81const CERT_ATTR_CHECK_FAILED_HAS_UPDATE = 101;
  82const BACKGROUNDCHECK_MULTIPLE_FAILURES = 110;
  83
  84var gLogEnabled = false;
  85var gUpdatesFoundPageId;
  86
  87// Notes:
  88// 1. use the wizard's goTo method whenever possible to change the wizard
  89//    page since it is simpler than most other methods and behaves nicely with
  90//    mochitests.
  91// 2. using a page's onPageShow method to then change to a different page will
  92//    of course call that page's onPageShow method which can make mochitests
  93//    overly complicated and fragile so avoid doing this if at all possible.
  94//    This is why a page's next attribute is set prior to the page being shown
  95//    whenever possible.
  96
  97/**
  98 * Logs a string to the error console.
  99 * @param   string
 100 *          The string to write to the error console..
 101 */
 102function LOG(module, string) {
 103  if (gLogEnabled) {
 104    dump("*** AUS:UI " + module + ":" + string + "\n");
 105    Services.console.logStringMessage("AUS:UI " + module + ":" + string);
 106  }
 107}
 108
 109/**
 110 * Opens a URL using the event target's url attribute for the URL. This is a
 111 * workaround for Bug 263433 which prevents respecting tab browser preferences
 112 * for where to open a URL.
 113 */
 114function openUpdateURL(event) {
 115  if (event.button == 0)
 116    openURL(event.target.getAttribute("url"));
 117}
 118
 119/**
 120 * Gets a preference value, handling the case where there is no default.
 121 * @param   func
 122 *          The name of the preference function to call, on nsIPrefBranch
 123 * @param   preference
 124 *          The name of the preference
 125 * @param   defaultValue
 126 *          The default value to return in the event the preference has
 127 *          no setting
 128 * @returns The value of the preference, or undefined if there was no
 129 *          user or default value.
 130 */
 131function getPref(func, preference, defaultValue) {
 132  try {
 133    return Services.prefs[func](preference);
 134  }
 135  catch (e) {
 136    LOG("General", "getPref - failed to get preference: " + preference);
 137  }
 138  return defaultValue;
 139}
 140
 141/**
 142 * A set of shared data and control functions for the wizard as a whole.
 143 */
 144var gUpdates = {
 145  /**
 146   * The nsIUpdate object being used by this window (either for downloading,
 147   * notification or both).
 148   */
 149  update: null,
 150
 151  /**
 152   * List of incompatible add-ons
 153   */
 154  addons: [],
 155
 156  /**
 157   * The updates.properties <stringbundle> element.
 158   */
 159  strings: null,
 160
 161  /**
 162   * The Application brandShortName (e.g. "Firefox")
 163   */
 164  brandName: null,
 165
 166  /**
 167   * The <wizard> element
 168   */
 169  wiz: null,
 170
 171  /**
 172   * Whether to run the unload handler. This will be set to false when the user
 173   * exits the wizard via onWizardCancel or onWizardFinish.
 174   */
 175  _runUnload: true,
 176
 177  /**
 178   * Helper function for setButtons
 179   * Resets button to original label & accesskey if string is null.
 180   */
 181  _setButton: function(button, string) {
 182    if (string) {
 183      var label = this.getAUSString(string);
 184      if (label.indexOf("%S") != -1)
 185        label = label.replace(/%S/, this.brandName);
 186      button.label = label;
 187      button.setAttribute("accesskey",
 188                          this.getAUSString(string + ".accesskey"));
 189    } else {
 190      button.label = button.defaultLabel;
 191      button.setAttribute("accesskey", button.defaultAccesskey);
 192    }
 193  },
 194
 195  /**
 196   * Sets the attributes needed for this Wizard's control buttons (labels,
 197   * disabled, hidden, etc.)
 198   * @param   extra1ButtonString
 199   *          The property in the stringbundle containing the label to put on
 200   *          the first extra button, or null to hide the first extra button.
 201   * @param   extra2ButtonString
 202   *          The property in the stringbundle containing the label to put on
 203   *          the second extra button, or null to hide the second extra button.
 204   * @param   nextFinishButtonString
 205   *          The property in the stringbundle containing the label to put on
 206   *          the Next / Finish button, or null to hide the button. The Next and
 207   *          Finish buttons are never displayed at the same time in a wizard
 208   *          with the the Finish button only being displayed when there are no
 209   *          additional pages to display in the wizard.
 210   * @param   canAdvance
 211   *          true if the wizard can be advanced (e.g. the next / finish button
 212   *          should be enabled), false otherwise.
 213   * @param   showCancel
 214   *          true if the wizard's cancel button should be shown, false
 215   *          otherwise. If not specified this will default to false.
 216   *
 217   * Note:
 218   * Per Bug 324121 the wizard should not look like a wizard and to accomplish
 219   * this the back button is never displayed and the cancel button is only
 220   * displayed for the checking and the incompatibleCheck pages. This causes the
 221   * wizard buttons to be arranged as follows on Windows with the next and
 222   * finish buttons never being displayed at the same time.
 223   * +--------------------------------------------------------------+
 224   * | [ extra1 ] [ extra2 ]                     [ next or finish ] |
 225   * +--------------------------------------------------------------+
 226   */
 227  setButtons: function(extra1ButtonString, extra2ButtonString,
 228                       nextFinishButtonString, canAdvance, showCancel) {
 229    this.wiz.canAdvance = canAdvance;
 230
 231    var bnf = this.wiz.getButton(this.wiz.onLastPage ? "finish" : "next");
 232    var be1 = this.wiz.getButton("extra1");
 233    var be2 = this.wiz.getButton("extra2");
 234    var bc = this.wiz.getButton("cancel");
 235
 236    // Set the labels for the next / finish, extra1, and extra2 buttons
 237    this._setButton(bnf, nextFinishButtonString);
 238    this._setButton(be1, extra1ButtonString);
 239    this._setButton(be2, extra2ButtonString);
 240
 241    bnf.hidden = bnf.disabled = !nextFinishButtonString;
 242    be1.hidden = be1.disabled = !extra1ButtonString;
 243    be2.hidden = be2.disabled = !extra2ButtonString;
 244    bc.hidden = bc.disabled = !showCancel;
 245
 246    // Hide and disable the back button each time setButtons is called
 247    // (see bug 464765).
 248    var btn = this.wiz.getButton("back");
 249    btn.hidden = btn.disabled = true;
 250
 251    // Hide and disable the finish button if not on the last page or the next
 252    // button if on the last page each time setButtons is called.
 253    btn = this.wiz.getButton(this.wiz.onLastPage ? "next" : "finish");
 254    btn.hidden = btn.disabled = true;
 255  },
 256
 257  getAUSString: function(key, strings) {
 258    if (strings)
 259      return this.strings.getFormattedString(key, strings);
 260    return this.strings.getString(key);
 261  },
 262
 263  never: function () {
 264    // If the user clicks "No Thanks", we should not prompt them to update to
 265    // this version again unless they manually select "Check for Updates..."
 266    // which will clear all of the "never" prefs.
 267    var neverPrefName = PREF_APP_UPDATE_NEVER_BRANCH + this.update.appVersion;
 268    Services.prefs.setBoolPref(neverPrefName, true);
 269  },
 270
 271  /**
 272   * A hash of |pageid| attribute to page object. Can be used to dispatch
 273   * function calls to the appropriate page.
 274   */
 275  _pages: { },
 276
 277  /**
 278   * Called when the user presses the "Finish" button on the wizard, dispatches
 279   * the function call to the selected page.
 280   */
 281  onWizardFinish: function() {
 282    this._runUnload = false;
 283    var pageid = document.documentElement.currentPage.pageid;
 284    if ("onWizardFinish" in this._pages[pageid])
 285      this._pages[pageid].onWizardFinish();
 286  },
 287
 288  /**
 289   * Called when the user presses the "Cancel" button on the wizard, dispatches
 290   * the function call to the selected page.
 291   */
 292  onWizardCancel: function() {
 293    this._runUnload = false;
 294    var pageid = document.documentElement.currentPage.pageid;
 295    if ("onWizardCancel" in this._pages[pageid])
 296      this._pages[pageid].onWizardCancel();
 297  },
 298
 299  /**
 300   * Called when the user presses the "Next" button on the wizard, dispatches
 301   * the function call to the selected page.
 302   */
 303  onWizardNext: function() {
 304    var cp = document.documentElement.currentPage;
 305    if (!cp)
 306      return;
 307    var pageid = cp.pageid;
 308    if ("onWizardNext" in this._pages[pageid])
 309      this._pages[pageid].onWizardNext();
 310  },
 311
 312  /**
 313   * The checking process that spawned this update UI. There are two types:
 314   * SRCEVT_FOREGROUND:
 315   *   Some user-generated event caused this UI to appear, e.g. the Help
 316   *   menu item or the button in preferences. When in this mode, the UI
 317   *   should remain active for the duration of the download.
 318   * SRCEVT_BACKGROUND:
 319   *   A background update check caused this UI to appear, probably because
 320   *   incompatibilities in Extensions or other addons were discovered and
 321   *   the user's consent to continue was required. When in this mode, the
 322   *   UI will disappear after the user's consent is obtained.
 323   */
 324  sourceEvent: SRCEVT_FOREGROUND,
 325
 326  /**
 327   * Helper function for onLoad
 328   * Saves default button label & accesskey for use by _setButton
 329   */
 330  _cacheButtonStrings: function (buttonName) {
 331    var button = this.wiz.getButton(buttonName);
 332    button.defaultLabel = button.label;
 333    button.defaultAccesskey = button.getAttribute("accesskey");
 334  },
 335
 336  /**
 337   * Called when the wizard UI is loaded.
 338   */
 339  onLoad: function() {
 340    this.wiz = document.documentElement;
 341
 342    gLogEnabled = getPref("getBoolPref", PREF_APP_UPDATE_LOG, false)
 343
 344    this.strings = document.getElementById("updateStrings");
 345    var brandStrings = document.getElementById("brandStrings");
 346    this.brandName = brandStrings.getString("brandShortName");
 347
 348    var pages = this.wiz.childNodes;
 349    for (var i = 0; i < pages.length; ++i) {
 350      var page = pages[i];
 351      if (page.localName == "wizardpage")
 352        this._pages[page.pageid] = eval(page.getAttribute("object"));
 353    }
 354
 355    // Cache the standard button labels in case we need to restore them
 356    this._cacheButtonStrings("next");
 357    this._cacheButtonStrings("finish");
 358    this._cacheButtonStrings("extra1");
 359    this._cacheButtonStrings("extra2");
 360
 361    // Advance to the Start page.
 362    this.getStartPageID(function(startPageID) {
 363      LOG("gUpdates", "onLoad - setting current page to startpage " + startPageID);
 364      gUpdates.wiz.currentPage = document.getElementById(startPageID);
 365    });
 366  },
 367
 368  /**
 369   * Called when the wizard UI is unloaded.
 370   */
 371  onUnload: function() {
 372    if (this._runUnload) {
 373      var cp = this.wiz.currentPage;
 374      if (cp.pageid != "finished" && cp.pageid != "finishedBackground")
 375        this.onWizardCancel();
 376    }
 377  },
 378
 379  /**
 380   * Gets the ID of the <wizardpage> object that should be displayed first. This
 381   * is an asynchronous method that passes the resulting object to a callback
 382   * function.
 383   *
 384   * This is determined by how we were called by the update prompt:
 385   *
 386   * Prompt Method:       Arg0:         Update State: Src Event:  Failed:   Result:
 387   * showUpdateAvailable  nsIUpdate obj --            background  --        see Note below
 388   * showUpdateDownloaded nsIUpdate obj pending       background  --        finishedBackground
 389   * showUpdateInstalled  "installed"   --            --          --        installed
 390   * showUpdateError      nsIUpdate obj failed        either      partial   errorpatching
 391   * showUpdateError      nsIUpdate obj failed        either      complete  errors
 392   * checkForUpdates      null          --            foreground  --        checking
 393   * checkForUpdates      null          downloading   foreground  --        downloading
 394   *
 395   * Note: the page returned (e.g. Result) for showUpdateAvaulable is as follows:
 396   * New enabled incompatible add-ons   : incompatibleCheck page
 397   * No new enabled incompatible add-ons: either updatesfoundbasic or
 398   *                                      updatesfoundbillboard as determined by
 399   *                                      updatesFoundPageId
 400   * @param   aCallback
 401   *          A callback to pass the <wizardpage> object to be displayed first to.
 402   */
 403  getStartPageID: function(aCallback) {
 404    if ("arguments" in window && window.arguments[0]) {
 405      var arg0 = window.arguments[0];
 406      if (arg0 instanceof CoI.nsIUpdate) {
 407        // If the first argument is a nsIUpdate object, we are notifying the
 408        // user that the background checking found an update that requires
 409        // their permission to install, and it's ready for download.
 410        this.setUpdate(arg0);
 411        if (this.update.errorCode == CERT_ATTR_CHECK_FAILED_NO_UPDATE ||
 412            this.update.errorCode == CERT_ATTR_CHECK_FAILED_HAS_UPDATE ||
 413            this.update.errorCode == BACKGROUNDCHECK_MULTIPLE_FAILURES) {
 414          aCallback("errorextra");
 415          return;
 416        }
 417
 418        var p = this.update.selectedPatch;
 419        if (p) {
 420          var state = p.state;
 421          var patchFailed;
 422          try {
 423            patchFailed = this.update.getProperty("patchingFailed");
 424          }
 425          catch (e) {
 426          }
 427          if (patchFailed) {
 428            if (patchFailed == "partial" && this.update.patchCount == 2) {
 429              // If the system failed to apply the partial patch, show the
 430              // screen which best describes this condition, which is triggered
 431              // by the |STATE_FAILED| state.
 432              state = STATE_FAILED;
 433            }
 434            else {
 435              // Otherwise, if the complete patch failed, which is far less
 436              // likely, show the error text held by the update object in the
 437              // generic errors page, triggered by the |STATE_DOWNLOAD_FAILED|
 438              // state.
 439              state = STATE_DOWNLOAD_FAILED;
 440            }
 441          }
 442
 443          // Now select the best page to start with, given the current state of
 444          // the Update.
 445          switch (state) {
 446          case STATE_PENDING:
 447          case STATE_PENDING_SVC:
 448            this.sourceEvent = SRCEVT_BACKGROUND;
 449            aCallback("finishedBackground");
 450            return;
 451          case STATE_DOWNLOADING:
 452            aCallback("downloading");
 453            return;
 454          case STATE_FAILED:
 455            window.getAttention();
 456            aCallback("errorpatching");
 457            return;
 458          case STATE_DOWNLOAD_FAILED:
 459          case STATE_APPLYING:
 460            aCallback("errors");
 461            return;
 462          }
 463        }
 464        if (this.update.licenseURL)
 465          this.wiz.getPageById(this.updatesFoundPageId).setAttribute("next", "license");
 466
 467        var self = this;
 468        this.getShouldCheckAddonCompatibility(function(shouldCheck) {
 469          if (shouldCheck) {
 470            var incompatCheckPage = document.getElementById("incompatibleCheck");
 471            incompatCheckPage.setAttribute("next", self.updatesFoundPageId);
 472            aCallback(incompatCheckPage.id);
 473          }
 474          else {
 475            aCallback(self.updatesFoundPageId);
 476          }
 477        });
 478        return;
 479      }
 480      else if (arg0 == "installed") {
 481        aCallback("installed");
 482        return;
 483      }
 484    }
 485    else {
 486      var um = CoC["@mozilla.org/updates/update-manager;1"].
 487               getService(CoI.nsIUpdateManager);
 488      if (um.activeUpdate) {
 489        this.setUpdate(um.activeUpdate);
 490        aCallback("downloading");
 491        return;
 492      }
 493    }
 494
 495    // Provide the ability to test the billboard html
 496    var billboardTestURL = getPref("getCharPref", PREF_APP_UPDATE_BILLBOARD_TEST_URL, null);
 497    if (billboardTestURL) {
 498      var updatesFoundBillboardPage = document.getElementById("updatesfoundbillboard");
 499      updatesFoundBillboardPage.setAttribute("next", "dummy");
 500      gUpdatesFoundBillboardPage.onExtra1 = function(){ gUpdates.wiz.cancel(); };
 501      gUpdatesFoundBillboardPage.onExtra2 = function(){ gUpdates.wiz.cancel(); };
 502      this.onWizardNext = function() { gUpdates.wiz.cancel(); };
 503      this.update = { billboardURL        : billboardTestURL,
 504                      brandName           : this.brandName,
 505                      displayVersion      : "Billboard Test 1.0",
 506                      showNeverForVersion : true,
 507                      type                : "major" };
 508      aCallback(updatesFoundBillboardPage.id);
 509    }
 510    else {
 511      aCallback("checking");
 512    }
 513  },
 514
 515  getShouldCheckAddonCompatibility: function(aCallback) {
 516    // this early return should never happen
 517    if (!this.update) {
 518      aCallback(false);
 519      return;
 520    }
 521
 522    if (!this.update.appVersion ||
 523        Services.vc.compare(this.update.appVersion, Services.appinfo.version) == 0) {
 524      aCallback(false);
 525      return;
 526    }
 527
 528    try {
 529      var hotfixID = Services.prefs.getCharPref(PREF_EM_HOTFIX_ID);
 530    }
 531    catch (e) { }
 532
 533    var self = this;
 534    AddonManager.getAllAddons(function(addons) {
 535      self.addons = [];
 536      addons.forEach(function(addon) {
 537        // Protect against code that overrides the add-ons manager and doesn't
 538        // implement the isCompatibleWith or the findUpdates method.
 539        if (!("isCompatibleWith" in addon) || !("findUpdates" in addon)) {
 540          let errMsg = "Add-on doesn't implement either the isCompatibleWith " +
 541                       "or the findUpdates method!";
 542          if (addon.id)
 543            errMsg += " Add-on ID: " + addon.id;
 544          Components.utils.reportError(errMsg);
 545          return;
 546        }
 547
 548        // If an add-on isn't appDisabled and isn't userDisabled then it is
 549        // either active now or the user expects it to be active after the
 550        // restart. If that is the case and the add-on is not installed by the
 551        // application and is not compatible with the new application version
 552        // then the user should be warned that the add-on will become
 553        // incompatible. If an addon's type equals plugin it is skipped since
 554        // checking plugins compatibility information isn't supported and
 555        // getting the scope property of a plugin breaks in some environments
 556        // (see bug 566787). The hotfix add-on is also ignored as it shouldn't
 557        // block the user from upgrading.
 558        try {
 559          if (addon.type != "plugin" && addon.id != hotfixID &&
 560              !addon.appDisabled && !addon.userDisabled &&
 561              addon.scope != AddonManager.SCOPE_APPLICATION &&
 562              addon.isCompatible &&
 563              !addon.isCompatibleWith(self.update.appVersion,
 564                                      self.update.platformVersion))
 565            self.addons.push(addon);
 566        }
 567        catch (e) {
 568          Components.utils.reportError(e);
 569        }
 570      });
 571
 572      aCallback(self.addons.length != 0);
 573    });
 574  },
 575
 576  /**
 577   * Returns the string page ID for the appropriate updates found page based
 578   * on the update's metadata.
 579   */
 580  get updatesFoundPageId() {
 581    if (gUpdatesFoundPageId)
 582      return gUpdatesFoundPageId;
 583    return gUpdatesFoundPageId = this.update.billboardURL ? "updatesfoundbillboard"
 584                                                          : "updatesfoundbasic";
 585  },
 586
 587  /**
 588   * Sets the Update object for this wizard
 589   * @param   update
 590   *          The update object
 591   */
 592  setUpdate: function(update) {
 593    this.update = update;
 594    if (this.update)
 595      this.update.QueryInterface(CoI.nsIWritablePropertyBag);
 596  }
 597}
 598
 599/**
 600 * The "Checking for Updates" page. Provides feedback on the update checking
 601 * process.
 602 */
 603var gCheckingPage = {
 604  /**
 605   * The nsIUpdateChecker that is currently checking for updates. We hold onto
 606   * this so we can cancel the update check if the user closes the window.
 607   */
 608  _checker: null,
 609
 610  /**
 611   * Initialize
 612   */
 613  onPageShow: function() {
 614    gUpdates.setButtons(null, null, null, false, true);
 615    gUpdates.wiz.getButton("cancel").focus();
 616
 617    // Clear all of the "never" prefs to handle the scenario where the user
 618    // clicked "never" for an update, selected "Check for Updates...", and
 619    // then canceled.  If we don't clear the "never" prefs future
 620    // notifications will never happen.
 621    Services.prefs.deleteBranch(PREF_APP_UPDATE_NEVER_BRANCH);
 622
 623    // The user will be notified if there is an error so clear the background
 624    // check error count.
 625    if (Services.prefs.prefHasUserValue(PREF_APP_UPDATE_BACKGROUNDERRORS))
 626      Services.prefs.clearUserPref(PREF_APP_UPDATE_BACKGROUNDERRORS);
 627
 628    this._checker = CoC["@mozilla.org/updates/update-checker;1"].
 629                    createInstance(CoI.nsIUpdateChecker);
 630    this._checker.checkForUpdates(this.updateListener, true);
 631  },
 632
 633  /**
 634   * The user has closed the window, either by pressing cancel or using a Window
 635   * Manager control, so stop checking for updates.
 636   */
 637  onWizardCancel: function() {
 638    this._checker.stopChecking(CoI.nsIUpdateChecker.CURRENT_CHECK);
 639  },
 640
 641  /**
 642   * An object implementing nsIUpdateCheckListener that is notified as the
 643   * update check commences.
 644   */
 645  updateListener: {
 646    /**
 647     * See nsIUpdateCheckListener
 648     */
 649    onProgress: function(request, position, totalSize) {
 650      var pm = document.getElementById("checkingProgress");
 651      pm.mode = "normal";
 652      pm.value = Math.floor(100 * (position / totalSize));
 653    },
 654
 655    /**
 656     * See nsIUpdateCheckListener
 657     */
 658    onCheckComplete: function(request, updates, updateCount) {
 659      var aus = CoC["@mozilla.org/updates/update-service;1"].
 660                getService(CoI.nsIApplicationUpdateService);
 661      gUpdates.setUpdate(aus.selectUpdate(updates, updates.length));
 662      if (gUpdates.update) {
 663        LOG("gCheckingPage", "onCheckComplete - update found");
 664        if (!aus.canApplyUpdates) {
 665          // Prevent multiple notifications for the same update when the user is
 666          // unable to apply updates.
 667          gUpdates.never();
 668          gUpdates.wiz.goTo("manualUpdate");
 669          return;
 670        }
 671
 672        if (gUpdates.update.licenseURL) {
 673          // gUpdates.updatesFoundPageId returns the pageid and not the
 674          // element's id so use the wizard's getPageById method.
 675          gUpdates.wiz.getPageById(gUpdates.updatesFoundPageId).setAttribute("next", "license");
 676        }
 677
 678        gUpdates.getShouldCheckAddonCompatibility(function(shouldCheck) {
 679          if (shouldCheck) {
 680            var incompatCheckPage = document.getElementById("incompatibleCheck");
 681            incompatCheckPage.setAttribute("next", gUpdates.updatesFoundPageId);
 682            gUpdates.wiz.goTo("incompatibleCheck");
 683          }
 684          else {
 685            gUpdates.wiz.goTo(gUpdates.updatesFoundPageId);
 686          }
 687        });
 688        return;
 689      }
 690
 691      LOG("gCheckingPage", "onCheckComplete - no update found");
 692      gUpdates.wiz.goTo("noupdatesfound");
 693    },
 694
 695    /**
 696     * See nsIUpdateCheckListener
 697     */
 698    onError: function(request, update) {
 699      LOG("gCheckingPage", "onError - proceeding to error page");
 700      gUpdates.setUpdate(update);
 701      if (update.errorCode &&
 702          (update.errorCode == CERT_ATTR_CHECK_FAILED_NO_UPDATE ||
 703           update.errorCode == CERT_ATTR_CHECK_FAILED_HAS_UPDATE)) {
 704        gUpdates.wiz.goTo("errorextra");
 705      }
 706      else {
 707        gUpdates.wiz.goTo("errors");
 708      }
 709    },
 710
 711    /**
 712     * See nsISupports.idl
 713     */
 714    QueryInterface: function(aIID) {
 715      if (!aIID.equals(CoI.nsIUpdateCheckListener) &&
 716          !aIID.equals(CoI.nsISupports))
 717        throw CoR.NS_ERROR_NO_INTERFACE;
 718      return this;
 719    }
 720  }
 721};
 722
 723/**
 724 * The "You have outdated plugins" page
 725 */
 726var gPluginsPage = {
 727  /**
 728   * URL of the plugin updates page
 729   */
 730  _url: null,
 731  
 732  /**
 733   * Initialize
 734   */
 735  onPageShow: function() {
 736    var prefs = Services.prefs;
 737    if (prefs.getPrefType(PREF_PLUGINS_UPDATEURL) == prefs.PREF_INVALID) {
 738      gUpdates.wiz.goTo("noupdatesfound");
 739      return;
 740    }
 741    
 742    this._url = Services.urlFormatter.formatURLPref(PREF_PLUGINS_UPDATEURL);
 743    var link = document.getElementById("pluginupdateslink");
 744    link.setAttribute("href", this._url);
 745
 746
 747    var phs = CoC["@mozilla.org/plugin/host;1"].
 748                 getService(CoI.nsIPluginHost);
 749    var plugins = phs.getPluginTags();
 750    var blocklist = CoC["@mozilla.org/extensions/blocklist;1"].
 751                      getService(CoI.nsIBlocklistService);
 752
 753    var hasOutdated = false;
 754    for (let i = 0; i < plugins.length; i++) {
 755      let pluginState = blocklist.getPluginBlocklistState(plugins[i]);
 756      if (pluginState == CoI.nsIBlocklistService.STATE_OUTDATED) {
 757        hasOutdated = true;
 758        break;
 759      }
 760    }
 761    if (!hasOutdated) {
 762      gUpdates.wiz.goTo("noupdatesfound");
 763      return;
 764    }
 765
 766    gUpdates.setButtons(null, null, "okButton", true);
 767    gUpdates.wiz.getButton("finish").focus();
 768  },
 769  
 770  /**
 771   * Finish button clicked.
 772   */
 773  onWizardFinish: function() {
 774    openURL(this._url);
 775  }
 776};
 777
 778/**
 779 * The "No Updates Are Available" page
 780 */
 781var gNoUpdatesPage = {
 782  /**
 783   * Initialize
 784   */
 785  onPageShow: function() {
 786    LOG("gNoUpdatesPage", "onPageShow - could not select an appropriate " +
 787        "update. Either there were no updates or |selectUpdate| failed");
 788
 789    if (getPref("getBoolPref", PREF_APP_UPDATE_ENABLED, true))
 790      document.getElementById("noUpdatesAutoEnabled").hidden = false;
 791    else
 792      document.getElementById("noUpdatesAutoDisabled").hidden = false;
 793
 794    gUpdates.setButtons(null, null, "okButton", true);
 795    gUpdates.wiz.getButton("finish").focus();
 796  }
 797};
 798
 799
 800/**
 801 * The page that checks if there are any incompatible add-ons.
 802 */
 803var gIncompatibleCheckPage = {
 804  /**
 805   * Count of incompatible add-ons to check for updates
 806   */
 807  _totalCount: 0,
 808
 809  /**
 810   * Count of incompatible add-ons that have beend checked for updates
 811   */
 812  _completedCount: 0,
 813
 814  /**
 815   * The progress bar for this page
 816   */
 817  _pBar: null,
 818
 819  /**
 820   * Initialize
 821   */
 822  onPageShow: function() {
 823    LOG("gIncompatibleCheckPage", "onPageShow - checking for updates to " +
 824        "incompatible add-ons");
 825
 826    gUpdates.setButtons(null, null, null, false, true);
 827    gUpdates.wiz.getButton("cancel").focus();
 828    this._pBar = document.getElementById("incompatibleCheckProgress");
 829    this._totalCount = gUpdates.addons.length;
 830
 831    this._pBar.mode = "normal";
 832    gUpdates.addons.forEach(function(addon) {
 833      addon.findUpdates(this, AddonManager.UPDATE_WHEN_NEW_APP_DETECTED,
 834                        gUpdates.update.appVersion,
 835                        gUpdates.update.platformVersion);
 836    }, this);
 837  },
 838
 839  // Addon UpdateListener
 840  onCompatibilityUpdateAvailable: function(addon) {
 841    // Remove the add-on from the list of add-ons that will become incompatible
 842    // with the new version of the application.
 843    for (var i = 0; i < gUpdates.addons.length; ++i) {
 844      if (gUpdates.addons[i].id == addon.id) {
 845        LOG("gIncompatibleCheckPage", "onCompatibilityUpdateAvailable - " +
 846            "found update for add-on ID: " + addon.id);
 847        gUpdates.addons.splice(i, 1);
 848        break;
 849      }
 850    }
 851  },
 852
 853  onUpdateAvailable: function(addon, install) {
 854    // If the new version of this add-on is blocklisted for the new application
 855    // then it isn't a valid update and the user should still be warned that
 856    // the add-on will become incompatible.
 857    let bs = CoC["@mozilla.org/extensions/blocklist;1"].
 858             getService(CoI.nsIBlocklistService);
 859    if (bs.isAddonBlocklisted(addon.id, install.version,
 860                              gUpdates.update.appVersion,
 861                              gUpdates.update.platformVersion))
 862      return;
 863
 864    // Compatibility or new version updates mean the same thing here.
 865    this.onCompatibilityUpdateAvailable(addon);
 866  },
 867
 868  onUpdateFinished: function(addon) {
 869    ++this._completedCount;
 870    this._pBar.value = Math.ceil((this._completedCount / this._totalCount) * 100);
 871
 872    if (this._completedCount < this._totalCount)
 873      return;
 874
 875    if (gUpdates.addons.length == 0) {
 876      LOG("gIncompatibleCheckPage", "onUpdateFinished - updates were found " +
 877          "for all incompatible add-ons");
 878    }
 879    else {
 880      LOG("gIncompatibleCheckPage", "onUpdateFinished - there are still " +
 881          "incompatible add-ons");
 882      if (gUpdates.update.licenseURL) {
 883        document.getElementById("license").setAttribute("next", "incompatibleList");
 884      }
 885      else {
 886        // gUpdates.updatesFoundPageId returns the pageid and not the element's
 887        // id so use the wizard's getPageById method.
 888        gUpdates.wiz.getPageById(gUpdates.updatesFoundPageId).setAttribute("next", "incompatibleList");
 889      }
 890    }
 891    gUpdates.wiz.goTo(gUpdates.updatesFoundPageId);
 892  }
 893};
 894
 895/**
 896 * The "Unable to Update" page. Provides the user information about why they
 897 * were unable to update and a manual download url.
 898 */
 899var gManualUpdatePage = {
 900  onPageShow: function() {
 901    var manualURL = Services.urlFormatter.formatURLPref(PREF_APP_UPDATE_MANUAL_URL);
 902    var manualUpdateLinkLabel = document.getElementById("manualUpdateLinkLabel");
 903    manualUpdateLinkLabel.value = manualURL;
 904    manualUpdateLinkLabel.setAttribute("url", manualURL);
 905
 906    gUpdates.setButtons(null, null, "okButton", true);
 907    gUpdates.wiz.getButton("finish").focus();
 908  }
 909};
 910
 911/**
 912 * The "Updates Are Available" page. Provides the user information about the
 913 * available update.
 914 */
 915var gUpdatesFoundBasicPage = {
 916  /**
 917   * Initialize
 918   */
 919  onPageShow: function() {
 920    gUpdates.wiz.canRewind = false;
 921    var update = gUpdates.update;
 922    gUpdates.setButtons("askLaterButton",
 923                        update.showNeverForVersion ? "noThanksButton" : null,
 924                        "updateButton_" + update.type, true);
 925    var btn = gUpdates.wiz.getButton("next");
 926    btn.focus();
 927
 928    var updateName = update.name;
 929    if (update.channel == "nightly") {
 930      updateName = gUpdates.getAUSString("updateNightlyName",
 931                                         [gUpdates.brandName,
 932                                          update.displayVersion,
 933                                          update.buildID]);
 934    }
 935    var updateNameElement = document.getElementById("updateName");
 936    updateNameElement.value = updateName;
 937
 938    var introText = gUpdates.getAUSString("intro_" + update.type,
 939                                          [gUpdates.brandName, update.displayVersion]);
 940    var introElem = document.getElementById("updatesFoundInto");
 941    introElem.setAttribute("severity", update.type);
 942    introElem.textContent = introText;
 943
 944    var updateMoreInfoURL = document.getElementById("updateMoreInfoURL");
 945    if (update.detailsURL)
 946      updateMoreInfoURL.setAttribute("url", update.detailsURL);
 947    else
 948      updateMoreInfoURL.hidden = true;
 949
 950    var updateTitle = gUpdates.getAUSString("updatesfound_" + update.type +
 951                                            ".title");
 952    document.getElementById("updatesFoundBasicHeader").setAttribute("label", updateTitle);
 953  },
 954
 955  onExtra1: function() {
 956    gUpdates.wiz.cancel();
 957  },
 958
 959  onExtra2: function() {
 960    gUpdates.never();
 961    gUpdates.wiz.cancel();
 962  }
 963};
 964
 965/**
 966 * The "Updates Are Available" page with a billboard. Provides the user
 967 * information about the available update.
 968 */
 969var gUpdatesFoundBillboardPage = {
 970  /**
 971   * If this page has been previously loaded
 972   */
 973  _billboardLoaded: false,
 974
 975  /**
 976   * Initialize
 977   */
 978  onPageShow: function() {
 979    var update = gUpdates.update;
 980    gUpdates.setButtons("askLaterButton",
 981                        update.showNeverForVersion ? "noThanksButton" : null,
 982                        "updateButton_" + update.type, true);
 983    gUpdates.wiz.getButton("next").focus();
 984
 985    if (this._billboardLoaded)
 986      return;
 987
 988    var remoteContent = document.getElementById("updateMoreInfoContent");
 989    remoteContent.addEventListener("load",
 990                                   gUpdatesFoundBillboardPage.onBillboardLoad,
 991                                   false);
 992    // update_name and update_version need to be set before url
 993    // so that when attempting to download the url, we can show
 994    // the formatted "Download..." string
 995    remoteContent.update_name = gUpdates.brandName;
 996    remoteContent.update_version = update.displayVersion;
 997
 998    var billboardTestURL = getPref("getCharPref", PREF_APP_UPDATE_BILLBOARD_TEST_URL, null);
 999    if (billboardTestURL) {
1000      // Allow file urls when testing the billboard and fallback to the
1001      // normal method if the URL isn't a file.
1002      var scheme = Services.io.newURI(billboardTestURL, null, null).scheme;
1003      if (scheme == "file")
1004        remoteContent.testFileUrl = update.billboardURL;
1005      else
1006        remoteContent.url = update.billboardURL;
1007    }
1008    else
1009      remoteContent.url = update.billboardURL;
1010
1011    this._billboardLoaded = true;
1012  },
1013
1014  /**
1015   * When the billboard document has loaded
1016   */
1017  onBillboardLoad: function(aEvent) {
1018    var remoteContent = document.getElementById("updateMoreInfoContent");
1019    // Note: may be called multiple times due to multiple onLoad events.
1020    var state = remoteContent.getAttribute("state");
1021    if (state == "loading" || aEvent.originalTarget != remoteContent)
1022      return;
1023
1024    remoteContent.removeEventListener("load", gUpdatesFoundBillboardPage.onBillboardLoad, false);
1025    if (state == "error") {
1026      gUpdatesFoundPageId = "updatesfoundbasic";
1027      var next = gUpdates.wiz.getPageById("updatesfoundbillboard").getAttribute("next");
1028      gUpdates.wiz.getPageById(gUpdates.updatesFoundPageId).setAttribute("next", next);
1029      gUpdates.wiz.goTo(gUpdates.updatesFoundPageId);
1030    }
1031  },
1032
1033  onExtra1: function() {
1034    this.onWizardCancel();
1035    gUpdates.wiz.cancel();
1036  },
1037
1038  onExtra2: function() {
1039    this.onWizardCancel();
1040    gUpdates.never();
1041    gUpdates.wiz.cancel();
1042  },
1043
1044  /**
1045   * When the user cancels the wizard
1046   */
1047  onWizardCancel: function() {
1048    try {
1049      var remoteContent = document.getElementById("updateMoreInfoContent");
1050      if (remoteContent)
1051        remoteContent.stopDownloading();
1052    }
1053    catch (e) {
1054      LOG("gUpdatesFoundBillboardPage", "onWizardCancel - " +
1055          "moreInfoContent.stopDownloading() failed: " + e);
1056    }
1057  }
1058};
1059
1060/**
1061 * The page which shows the user a license associated with an update. The
1062 * user must agree to the terms of the license before continuing to install
1063 * the update.
1064 */
1065var gLicensePage = {
1066  /**
1067   * If the license url has been previously loaded
1068   */
1069  _licenseLoaded: false,
1070
1071  /**
1072   * Initialize
1073   */
1074  onPageShow: function() {
1075    gUpdates.setButtons("backButton", null, "acceptTermsButton", false);
1076
1077    var licenseContent = document.getElementById("licenseContent");
1078    if (this._licenseLoaded || licenseContent.getAttribute("state") == "error") {
1079      this.onAcceptDeclineRadio();
1080      var licenseGroup = document.getElementById("acceptDeclineLicense");
1081      licenseGroup.focus();
1082      return;
1083    }
1084
1085    gUpdates.wiz.canAdvance = false;
1086
1087    // Disable the license radiogroup until the EULA has been downloaded
1088    document.getElementById("acceptDeclineLicense").disabled = true;
1089    gUpdates.update.setProperty("licenseAccepted", "false");
1090
1091    licenseContent.addEventListener("load", gLicensePage.onLicenseLoad, false);
1092    // The update_name and update_version need to be set before url so the ui
1093    // can display the formatted "Download..." string when attempting to
1094    // download the url.
1095    licenseContent.update_name = gUpdates.brandName;
1096    licenseContent.update_version = gUpdates.update.displayVersion;
1097    licenseContent.url = gUpdates.update.licenseURL;
1098  },
1099
1100  /**
1101   * When the license document has loaded
1102   */
1103  onLicenseLoad: function(aEvent) {
1104    var licenseContent = document.getElementById("licenseContent");
1105    // Disable or enable the radiogroup based on the state attribute of
1106    // licenseContent.
1107    // Note: may be called multiple times due to multiple onLoad events.
1108    var state = licenseContent.getAttribute("state");
1109    if (state == "loading" || aEvent.originalTarget != licenseContent)
1110      return;
1111
1112    licenseContent.removeEventListener("load", gLicensePage.onLicenseLoad, false);
1113
1114    if (state == "error") {
1115      gUpdates.wiz.goTo("manualUpdate");
1116      return;
1117    }
1118
1119    gLicensePage._licenseLoaded = true;
1120    document.getElementById("acceptDeclineLicense").disabled = false;
1121    gUpdates.wiz.getButton("extra1").disabled = false;
1122  },
1123
1124  /**
1125   * When the user changes the state of the accept / decline radio group
1126   */
1127  onAcceptDeclineRadio: function() {
1128    // Return early if this page hasn't been loaded (bug 405257). This event is
1129    // fired during the construction of the wizard before gUpdates has received
1130    // its onload event (bug 452389).
1131    if (!this._licenseLoaded)
1132      return;
1133
1134    var selectedIndex = document.getElementById("acceptDeclineLicense")
1135                                .selectedIndex;
1136    // 0 == Accept, 1 == Decline
1137    var licenseAccepted = (selectedIndex == 0);
1138    gUpdates.wiz.canAdvance = licenseAccepted;
1139  },
1140
1141  /**
1142   * The non-standard "Back" button.
1143   */
1144  onExtra1: function() {
1145    gUpdates.wiz.goTo(gUpdates.updatesFoundPageId);
1146  },
1147
1148  /**
1149   * When the user clicks next after accepting the license
1150   */
1151  onWizardNext: function() {
1152    try {
1153      gUpdates.update.setProperty("licenseAccepted", "true");
1154      var um = CoC["@mozilla.org/updates/update-manager;1"].
1155               getService(CoI.nsIUpdateManager);
1156      um.saveUpdates();
1157    }
1158    catch (e) {
1159      LOG("gLicensePage", "onWizardNext - nsIUpdateManager:saveUpdates() " +
1160          "failed: " + e);
1161    }
1162  },
1163
1164  /**
1165   * When the user cancels the wizard
1166   */
1167  onWizardCancel: function() {
1168    try {
1169      var licenseContent = document.getElementById("licenseContent");
1170      // If the license was downloading, stop it.
1171      if (licenseContent)
1172        licenseContent.stopDownloading();
1173    }
1174    catch (e) {
1175      LOG("gLicensePage", "onWizardCancel - " +
1176          "licenseContent.stopDownloading() failed: " + e);
1177    }
1178  }
1179};
1180
1181/**
1182 * The page which shows add-ons that are incompatible and do not have updated
1183 * compatibility information or a version update available to make them
1184 * compatible.
1185 */
1186var gIncompatibleListPage = {
1187  /**
1188   * Initialize
1189   */
1190  onPageShow: function() {
1191    gUpdates.setButtons("backButton", null, "okButton", true);
1192    var listbox = document.getElementById("incompatibleListbox");
1193    if (listbox.children.length > 0)
1194      return;
1195
1196    var intro = gUpdates.getAUSString("incompatAddons_" + gUpdates.update.type,
1197                                      [gUpdates.brandName,
1198                                       gUpdates.update.displayVersion]);
1199    document.getElementById("incompatibleListDesc").textContent = intro;
1200
1201    var addons = gUpdates.addons;
1202    for (var i = 0; i < addons.length; ++i) {
1203      var listitem = document.createElement("listitem");
1204      var addonLabel = gUpdates.getAUSString("addonLabel", [addons[i].name,
1205                                                            addons[i].version]);
1206      listitem.setAttribute("label", addonLabel);
1207      listbox.appendChild(listitem);
1208    }
1209  },
1210
1211  /**
1212   * The non-standard "Back" button.
1213   */
1214  onExtra1: function() {
1215    gUpdates.wiz.goTo(gUpdates.update.licenseURL ? "license"
1216                                                 : gUpdates.updatesFoundPageId);
1217  }
1218};
1219
1220/**
1221 * The "Update is Downloading" page - provides feedback for the download
1222 * process plus a pause/resume UI
1223 */
1224var gDownloadingPage = {
1225  /**
1226   * DOM Elements
1227   */
1228  _downloadStatus: null,
1229  _downloadProgress: null,
1230  _pauseButton: null,
1231
1232  /**
1233   * Whether or not we are currently paused
1234   */
1235  _paused: false,
1236
1237  /**
1238   * Label cache to hold the 'Connecting' string
1239   */
1240  _label_downloadStatus: null,
1241
1242  /**
1243   * Member variables for updating download status
1244   */
1245  _lastSec: Infinity,
1246  _startTime: null,
1247  _pausedStatus: "",
1248
1249  _hiding: false,
1250
1251  /**
1252   * Initialize
1253   */
1254  onPageShow: function() {
1255    this._downloadStatus = document.getElementById("downloadStatus");
1256    this._downloadProgress = document.getElementById("downloadProgress");
1257    this._pauseButton = document.getElementById("pauseButton");
1258    this._label_downloadStatus = this._downloadStatus.textContent;
1259
1260    this._pauseButton.setAttribute("tooltiptext",
1261                                   gUpdates.getAUSString("pauseButtonPause"));
1262
1263    // move focus to the pause/resume button and then disable it (bug #353177)
1264    this._pauseButton.focus();
1265    this._pauseButton.disabled = true;
1266
1267    var aus = CoC["@mozilla.org/updates/update-service;1"].
1268              getService(CoI.nsIApplicationUpdateService);
1269
1270    var um = CoC["@mozilla.org/updates/update-manager;1"].
1271             getService(CoI.nsIUpdateManager);
1272    var activeUpdate = um.activeUpdate;
1273    if (activeUpdate)
1274      gUpdates.setUpdate(activeUpdate);
1275
1276    if (!gUpdates.update) {
1277      LOG("gDownloadingPage", "onPageShow - no valid update to download?!");
1278      return;
1279    }
1280
1281    this._startTime = Date.now();
1282
1283    try {
1284      // Say that this was a foreground download, not a background download,
1285      // since the user cared enough to look in on this process.
1286      gUpdates.update.QueryInterface(CoI.nsIWritablePropertyBag);
1287      gUpdates.update.setProperty("foregroundDownload", "true");
1288
1289      // Pause any active background download and restart it as a foreground
1290      // download.
1291      aus.pauseDownload();
1292      var state = aus.downloadUpdate(gUpdates.update, false);
1293      if (state == "failed") {
1294        // We've tried as hard as we could to download a valid update -
1295        // we fell back from a partial patch to a complete patch and even
1296        // then we couldn't validate. Show a validation error with instructions
1297        // on how to manually update.
1298        this.removeDownloadListener();
1299        gUpdates.wiz.goTo("errors");
1300        return;
1301      }
1302      else {
1303        // Add this UI as a listener for active downloads
1304        aus.addDownloadListener(this);
1305      }
1306
1307      if (activeUpdate)
1308        this._setUIState(!aus.isDownloading);
1309    }
1310    catch(e) {
1311      LOG("gDownloadingPage", "onPageShow - error: " + e);
1312    }
1313
1314    gUpdates.setButtons("hideButton", null, null, false);
1315    gUpdates.wiz.getButton("extra1").focus();
1316  },
1317
1318  /**
1319   * Updates the text status message
1320   */
1321  _setStatus: function(status) {
1322    // Don't bother setting the same text more than once. This can happen
1323    // due to the asynchronous behavior of the downloader.
1324    if (this._downloadStatus.textContent == status)
1325      return;
1326    while (this._downloadStatus.hasChildNodes())
1327      this._downloadStatus.removeChild(this._downloadStatus.firstChild);
1328    this._downloadStatus.appendChild(document.createTextNode(status));
1329  },
1330
1331  /**
1332   * Update download progress status to show time left, speed, and progress.
1333   * Also updates the status needed for pausing the download.
1334   *
1335   * @param aCurr
1336   *        Current number of bytes transferred
1337   * @param aMax
1338   *        Total file size of the download
1339   * @return Current active download status
1340   */
1341  _updateDownloadStatus: function(aCurr, aMax) {
1342    let status;
1343
1344    // Get the download time left and progress
1345    let rate = aCurr / (Date.now() - this._startTime) * 1000;
1346    [status, this._lastSec] =
1347      DownloadUtils.getDownloadStatus(aCurr, aMax, rate, this._lastSec);
1348
1349    // Get the download progress for pausing
1350    this._pausedStatus = DownloadUtils.getTransferTotal(aCurr, aMax);
1351
1352    return status;
1353  },
1354
1355  /**
1356   * Adjust UI to suit a certain state of paused-ness
1357   * @param   paused
1358   *          Whether or not the download is paused
1359   */
1360  _setUIState: function(paused) {
1361    var u = gUpdates.update;
1362    if (paused) {
1363      if (this._downloadProgress.mode != "normal")
1364        this._downloadProgress.mode = "normal";
1365      this._pauseButton.setAttribute("tooltiptext",
1366                                     gUpdates.getAUSString("pauseButtonResume"));
1367      this._pauseButton.setAttribute("paused", "true");
1368      var p = u.selectedPatch.QueryInterface(CoI.nsIPropertyBag);
1369      var status = p.getProperty("status");
1370      if (status) {
1371        let pausedStatus = gUpdates.getAUSString("downloadPausedStatus", [status]);
1372        this._setStatus(pausedStatus);
1373      }
1374    }
1375    else {
1376      if (this._downloadProgress.mode != "undetermined")
1377        this._downloadProgress.mode = "undetermined";
1378      this._pauseButton.setAttribute("paused", "false");
1379      this._pauseButton.setAttribute("tooltiptext",
1380                                     gUpdates.getAUSString("pauseButtonPause"));
1381      this._setStatus(this._label_downloadStatus);
1382    }
1383  },
1384
1385  /**
1386   * Removes the download listener.
1387   */
1388  removeDownloadListener: function() {
1389    var aus = CoC["@mozilla.org/updates/update-service;1"].
1390              getService(CoI.nsIApplicationUpdateService);
1391    aus.removeDownloadListener(this);
1392  },
1393
1394  /**
1395   * When the user clicks the Pause/Resume button
1396   */
1397  onPause: function() {
1398    var aus = CoC["@mozilla.org/updates/update-service;1"].
1399              getService(CoI.nsIApplicationUpdateService);
1400    if (this._paused)
1401      aus.downloadUpdate(gUpdates.update, false);
1402    else {
1403      var patch = gUpdates.update.selectedPatch;
1404      patch.QueryInterface(CoI.nsIWritablePropertyBag);
1405      patch.setProperty("status", this._pausedStatus);
1406      aus.pauseDownload();
1407    }
1408    this._paused = !this._paused;
1409
1410    // Update the UI
1411    this._setUIState(this._paused);
1412  },
1413
1414  /**
1415   * When the user has closed the window using a Window Manager control (this
1416   * page doesn't have a cancel button) cancel the update in progress.
1417   */
1418  onWizardCancel: function() {
1419    if (this._hiding)
1420      return;
1421
1422    this.removeDownloadListener();
1423  },
1424
1425  /**
1426   * When the user closes the Wizard UI by clicking the Hide button
1427   */
1428  onHide: function() {
1429    // Set _hiding to true to prevent onWizardCancel from cancelling the update
1430    // that is in progress.
1431    this._hiding = true;
1432
1433    // Remove ourself as a download listener so that we don't continue to be
1434    // fed progress and state notifications after the UI we're updating has
1435    // gone away.
1436    this.removeDownloadListener();
1437
1438    var aus = CoC["@mozilla.org/updates/update-service;1"].
1439              getService(CoI.nsIApplicationUpdateService);
1440    var um = CoC["@mozilla.org/updates/update-manager;1"].
1441             getService(CoI.nsIUpdateManager);
1442    um.activeUpdate = gUpdates.update;
1443
1444    // If the download was paused by the user, ask the user if they want to
1445    // have the update resume in the background.
1446    var downloadInBackground = true;
1447    if (this._paused) {
1448      var title = gUpdates.getAUSString("resumePausedAfterCloseTitle")

Large files files are truncated, but you can click here to view the full file