Bug 1030059 - Passwords gone in newest nightly - fix bustage from bug 853549 (async password storage initialization). r=clokep
authoraleth <aleth@instantbird.org>
Fri, 21 Nov 2014 15:38:13 +0100
changeset 17108 c5a41348fbaa96b7c4e8a7d08465e47fbda1d8eb
parent 17107 b51e8eaa3b4c1187712b7d9b9aba4c359da7f955
child 17109 97d2ec8ebddeff268dc6c400d72c4d4bdbe44570
push id10596
push useraleth@instantbird.org
push dateFri, 21 Nov 2014 14:43:24 +0000
treeherdercomm-central@ae1c9811a808 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersclokep
bugs1030059, 853549
Bug 1030059 - Passwords gone in newest nightly - fix bustage from bug 853549 (async password storage initialization). r=clokep
chat/components/public/imIAccount.idl
chat/components/src/imAccounts.js
chat/components/src/imCore.js
im/content/accounts.js
im/locales/en-US/chrome/instantbird/core.properties
im/modules/ibCore.jsm
--- a/chat/components/public/imIAccount.idl
+++ b/chat/components/public/imIAccount.idl
@@ -254,17 +254,20 @@ interface imIAccount: prplIAccount {
   /* Set when the account is trying to connect for the first time
      with the current parameters (removed after a successsful connection) */
   const short FIRST_CONNECTION_PENDING = 2;
   /* Set at startup when the previous state was pending */
   const short FIRST_CONNECTION_CRASHED = 4;
 
   attribute short firstConnectionState;
 
-  // Passwords are stored in the toolkit Password Manager.
+  /* Passwords are stored in the toolkit Password Manager.
+   * Warning: Don't attempt to access passwords during startup before
+   * Services.login.initializationPromise has resolved.
+   */
   attribute AUTF8String password;
 
   attribute AUTF8String alias;
 
   /* While an account is connecting, this attribute contains a message
      indicating the current step of the connection */
   readonly attribute AUTF8String connectionStateMsg;
 
--- a/chat/components/src/imAccounts.js
+++ b/chat/components/src/imAccounts.js
@@ -156,33 +156,35 @@ function imAccount(aKey, aName, aPrplId)
   // Send status change notifications to the account.
   this.observedStatusInfo = null; // (To execute the setter).
 
   // If we have never finished the first connection attempt for this account,
   // mark the account as having caused a crash.
   if (this.firstConnectionState == Ci.imIAccount.FIRST_CONNECTION_PENDING)
     this.firstConnectionState = Ci.imIAccount.FIRST_CONNECTION_CRASHED;
 
-  // Try to convert old passwords stored in the preferences.
-  // Don't try too hard if the user has canceled a master password prompt:
-  // we don't want to display several of theses prompts at startup.
-  if (gConvertingOldPasswords && !this.protocol.noPassword) {
-    try {
-      let password = this.prefBranch.getComplexValue(kPrefAccountPassword,
-                                                     Ci.nsISupportsString).data;
-      if (password && !this.password)
-        this.password = password;
-    } catch (e) { /* No password saved in the prefs for this account. */ }
-  }
+  Services.logins.initializationPromise.then(() => {
+    // Try to convert old passwords stored in the preferences.
+    // Don't try too hard if the user has canceled a master password prompt:
+    // we don't want to display several of theses prompts at startup.
+    if (gConvertingOldPasswords && !this.protocol.noPassword) {
+      try {
+        let password = this.prefBranch.getComplexValue(kPrefAccountPassword,
+                                                       Ci.nsISupportsString).data;
+        if (password && !this.password)
+          this.password = password;
+      } catch (e) { /* No password saved in the prefs for this account. */ }
+    }
 
-  // Check for errors that should prevent connection attempts.
-  if (this._passwordRequired && !this.password)
-    this._connectionErrorReason = Ci.imIAccount.ERROR_MISSING_PASSWORD;
-  else if (this.firstConnectionState == Ci.imIAccount.FIRST_CONNECTION_CRASHED)
-    this._connectionErrorReason = Ci.imIAccount.ERROR_CRASHED;
+    // Check for errors that should prevent connection attempts.
+    if (this._passwordRequired && !this.password)
+      this._connectionErrorReason = Ci.imIAccount.ERROR_MISSING_PASSWORD;
+    else if (this.firstConnectionState == Ci.imIAccount.FIRST_CONNECTION_CRASHED)
+      this._connectionErrorReason = Ci.imIAccount.ERROR_CRASHED;
+  });
 }
 
 imAccount.prototype = {
   __proto__: ClassInfo(["imIAccount", "prplIAccount"], "im account object"),
 
   name: "",
   id: "",
   numericId: 0,
--- a/chat/components/src/imCore.js
+++ b/chat/components/src/imCore.js
@@ -263,18 +263,23 @@ CoreService.prototype = {
     });
 
     let accounts = Services.accounts;
     accounts.initAccounts();
     Services.contacts.initContacts();
     Services.conversations.initConversations();
     Services.obs.notifyObservers(this, "prpl-init", null);
 
-    if (accounts.autoLoginStatus == Ci.imIAccountsService.AUTOLOGIN_ENABLED)
-      accounts.processAutoLogin();
+    // Wait with automatic connections until the password service
+    // is available.
+    if (accounts.autoLoginStatus == Ci.imIAccountsService.AUTOLOGIN_ENABLED) {
+      Services.logins.initializationPromise.then(() => {
+        Services.accounts.processAutoLogin();
+      });
+    }
   },
   observe: function(aObject, aTopic, aData) {
     if (aTopic == kQuitApplicationGranted)
       this.quit();
   },
   quit: function() {
     if (!this._initialized)
       throw Cr.NS_ERROR_NOT_INITIALIZED;
--- a/im/content/accounts.js
+++ b/im/content/accounts.js
@@ -26,47 +26,52 @@ const events = [
 
 var gAccountManager = {
   // Sets the delay after connect() or disconnect() during which
   // it is impossible to perform disconnect() and connect()
   _disabledDelay: 500,
   disableTimerID: 0,
   _connectedLabelInterval: 0,
   load: function am_load() {
-    this.accountList = document.getElementById("accountlist");
-    let defaultID;
-    for (let acc in this.getAccounts()) {
-      var elt = document.createElement("richlistitem");
-      this.accountList.appendChild(elt);
-      elt.build(acc);
-      if (!defaultID && acc.firstConnectionState == acc.FIRST_CONNECTION_CRASHED)
-        defaultID = acc.id;
-    }
-    for each (let event in events)
-      Services.obs.addObserver(this, event, false);
-    if (!this.accountList.getRowCount())
-      // This is horrible, but it works. Otherwise (at least on mac)
-      // the wizard is not centered relatively to the account manager
-      setTimeout(function() { gAccountManager.new(); }, 0);
-    else {
-      // we have accounts, show the list
-      document.getElementById("accountsDesk").selectedIndex = 1;
+    // Wait until the password service is ready before offering anything.
+    Services.logins.initializationPromise.then(() => {
+      this.accountList = document.getElementById("accountlist");
+      let defaultID;
+      for (let acc in this.getAccounts()) {
+        var elt = document.createElement("richlistitem");
+        this.accountList.appendChild(elt);
+        elt.build(acc);
+        if (!defaultID && acc.firstConnectionState == acc.FIRST_CONNECTION_CRASHED)
+          defaultID = acc.id;
+      }
+      for each (let event in events)
+        Services.obs.addObserver(this, event, false);
+      if (!this.accountList.getRowCount())
+        // This is horrible, but it works. Otherwise (at least on mac)
+        // the wizard is not centered relatively to the account manager
+        setTimeout(function() { gAccountManager.new(); }, 0);
+      else {
+        // we have accounts, show the list
+        document.getElementById("accountsDesk").selectedIndex = 1;
 
-      // ensure an account is selected
-      if (defaultID)
-        this.selectAccount(defaultID);
-      else
-        this.accountList.selectedIndex = 0;
-    }
+        // ensure an account is selected
+        if (defaultID)
+          this.selectAccount(defaultID);
+        else
+          this.accountList.selectedIndex = 0;
+      }
 
-    this.setAutoLoginNotification();
+      this.setAutoLoginNotification();
 
-    this.accountList.addEventListener("keypress", this.onKeyPress, true);
-    window.addEventListener("unload", this.unload.bind(this));
-    this._connectedLabelInterval = setInterval(this.updateConnectedLabels, 60000);
+      this.accountList.addEventListener("keypress", this.onKeyPress, true);
+      window.addEventListener("unload", this.unload.bind(this));
+      this._connectedLabelInterval = setInterval(this.updateConnectedLabels, 60000);
+    }, () => {
+      this.close();
+    });
   },
   unload: function am_unload() {
     clearInterval(this._connectedLabelInterval);
     for each (let event in events)
       Services.obs.removeObserver(this, event);
   },
   _updateAccountList: function am__updateAccountList() {
     let accountList = this.accountList;
--- a/im/locales/en-US/chrome/instantbird/core.properties
+++ b/im/locales/en-US/chrome/instantbird/core.properties
@@ -7,11 +7,12 @@ aboutCommand.invalidPageMessage="%S" is 
 
 startupFailure.title=Instantbird - Start up failure
 startupFailure.apologize=Instantbird encountered a serious error and cannot start, we apologize for the inconvenience.
 startupFailure.update=An updated version will probably be available shortly to fix the problem.
 
 startupFailure.purplexpcomFileError=Description: The file "instantbird.xpt" is missing or corrupted.
 startupFailure.xpcomRegistrationError=Description: XPCOM registration of the core component failed.
 startupFailure.purplexpcomInitError=An exception occurred while initializing the core component: %S
+startupFailure.passwordServiceError=Password service initialization failed.
 
 startupFailure.buttonUpdate=Check for Updates
 startupFailure.buttonClose=Close Instantbird
--- a/im/modules/ibCore.jsm
+++ b/im/modules/ibCore.jsm
@@ -56,16 +56,21 @@ var Core = {
       return false;
     }
 
     if (!Components.classes["@mozilla.org/chat/core-service;1"]) {
       this._promptError("startupFailure.xpcomRegistrationError");
       return false;
     }
 
+    // Trigger asynchronous initialization of the password service.
+    Services.logins.initializationPromise.catch(() => {
+      this._promptError("startupFailure.passwordServiceError");
+    });
+
     this.initLibpurpleOverrides();
 
     try {
       Services.core.init();
     }
     catch (e) {
       this._promptError("startupFailure.purplexpcomInitError", e);
       return false;
@@ -121,17 +126,19 @@ var Core = {
       usageContext: Ci.imICommand.CMD_CONTEXT_ALL,
       priority: Ci.imICommand.CMD_PRIORITY_DEFAULT,
       run: (aMsg, aConv) => {
         this.showDebugLog(aConv.account.id);
         return true;
       }
     });
 
-    this._showAccountManagerIfNeeded(true);
+    Services.logins.initializationPromise.then(() => {
+      this._showAccountManagerIfNeeded(true));
+    });
     return true;
   },
 
   showDebugLog: function(aAccountId) {
     this.showTab("debugLogPanel", aPanel => {
       aPanel.browser.addEventListener("DOMContentLoaded", () => {
         aPanel.initAccountList(aAccountId);
         aPanel.showDebugLog();