Bug 1577835 - Reformat chat/ code with eslint and Prettier. r=mkmelin
authorPaul Morris <paul@thunderbird.net>
Fri, 30 Aug 2019 13:43:03 -0400
changeset 35696 e5737872508567abf8b0444f43eb4ee4ce211c75
parent 35695 d62d63b2f73ccbbb09eafb7c9ac345c1a3467a5d
child 35697 8e3167f4cea7e33c9b7948a3ce7981774d1f2a2d
push id2486
push userclokep@gmail.com
push dateMon, 02 Sep 2019 20:24:43 +0000
treeherdercomm-beta@1b30a9a6e7f6 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmkmelin
bugs1577835
Bug 1577835 - Reformat chat/ code with eslint and Prettier. r=mkmelin # ignore-this-changeset These changes were achieved by: 1. Using eslint to add curly brackets to control statements that did not have them (if, else, etc.), thanks to the eslint rule: "curly": ["error", "all"] Done by running |mach eslint chat/ --fix| 2. Reformatting the code using Prettier. Done by deleting the chat/ line from the .prettierignore file and running: |mach eslint chat/ --fix|
.prettierignore
chat/components/src/imAccounts.js
chat/components/src/imCommands.js
chat/components/src/imContacts.js
chat/components/src/imConversations.js
chat/components/src/imCore.js
chat/components/src/logger.js
chat/components/src/smileProtocolHandler.js
chat/components/src/test/test_accounts.js
chat/components/src/test/test_commands.js
chat/components/src/test/test_conversations.js
chat/components/src/test/test_logger.js
chat/content/browserRequest.js
chat/content/chat-account-richlistitem.js
chat/content/chat-tooltip.js
chat/content/conversation-browser.js
chat/content/imAccountOptionsHelper.js
chat/content/otr-add-fingerprint.js
chat/content/otr-auth.js
chat/content/otr-finger.js
chat/content/otrWorker.js
chat/modules/ArrayBufferUtils.jsm
chat/modules/CLib.jsm
chat/modules/NormalizedMap.jsm
chat/modules/OTR.jsm
chat/modules/OTRHelpers.jsm
chat/modules/OTRLib.jsm
chat/modules/OTRUI.jsm
chat/modules/ToLocaleFormat.jsm
chat/modules/hiddenWindow.jsm
chat/modules/imContentSink.jsm
chat/modules/imServices.jsm
chat/modules/imSmileys.jsm
chat/modules/imStatusUtils.jsm
chat/modules/imTextboxUtils.jsm
chat/modules/imThemes.jsm
chat/modules/imXPCOMUtils.jsm
chat/modules/jsProtoHelper.jsm
chat/modules/socket.jsm
chat/modules/test/test_ArrayBufferUtils.js
chat/modules/test/test_NormalizedMap.js
chat/modules/test/test_filtering.js
chat/protocols/facebook/facebook.js
chat/protocols/gtalk/gtalk.js
chat/protocols/irc/irc.js
chat/protocols/irc/ircBase.jsm
chat/protocols/irc/ircCAP.jsm
chat/protocols/irc/ircCTCP.jsm
chat/protocols/irc/ircCommands.jsm
chat/protocols/irc/ircDCC.jsm
chat/protocols/irc/ircHandlers.jsm
chat/protocols/irc/ircISUPPORT.jsm
chat/protocols/irc/ircMultiPrefix.jsm
chat/protocols/irc/ircNonStandard.jsm
chat/protocols/irc/ircSASL.jsm
chat/protocols/irc/ircServerTime.jsm
chat/protocols/irc/ircServices.jsm
chat/protocols/irc/ircUtils.jsm
chat/protocols/irc/ircWatchMonitor.jsm
chat/protocols/irc/test/test_ctcpColoring.js
chat/protocols/irc/test/test_ctcpDequote.js
chat/protocols/irc/test/test_ctcpFormatting.js
chat/protocols/irc/test/test_ctcpQuote.js
chat/protocols/irc/test/test_ircCAP.js
chat/protocols/irc/test/test_ircCommands.js
chat/protocols/irc/test/test_ircMessage.js
chat/protocols/irc/test/test_ircNonStandard.js
chat/protocols/irc/test/test_ircServerTime.js
chat/protocols/irc/test/test_sendBufferedCommand.js
chat/protocols/irc/test/test_setMode.js
chat/protocols/irc/test/test_splitLongMessages.js
chat/protocols/irc/test/test_tryNewNick.js
chat/protocols/jsTest/jsTestProtocol.js
chat/protocols/matrix/matrix-sdk.jsm
chat/protocols/matrix/matrix.js
chat/protocols/odnoklassniki/odnoklassniki.js
chat/protocols/skype/skype.js
chat/protocols/skype/test/test_MagicSha256.js
chat/protocols/skype/test/test_contactUrlToName.js
chat/protocols/twitter/twitter.js
chat/protocols/xmpp/test/test_authmechs.js
chat/protocols/xmpp/test/test_dnsSrv.js
chat/protocols/xmpp/test/test_parseJidAndNormalization.js
chat/protocols/xmpp/test/test_parseVCard.js
chat/protocols/xmpp/test/test_saslPrep.js
chat/protocols/xmpp/test/test_xmppParser.js
chat/protocols/xmpp/test/test_xmppXml.js
chat/protocols/xmpp/xmpp-authmechs.jsm
chat/protocols/xmpp/xmpp-commands.jsm
chat/protocols/xmpp/xmpp-session.jsm
chat/protocols/xmpp/xmpp-xml.jsm
chat/protocols/xmpp/xmpp.js
chat/protocols/xmpp/xmpp.jsm
chat/protocols/yahoo/yahoo.js
--- a/.prettierignore
+++ b/.prettierignore
@@ -6,15 +6,14 @@
 *.xhtml
 *.xul
 *.xml
 
 # Ignore .eslintrc.js for now.
 .eslintrc.js
 
 # Ignore all top-level directories that contain JS files (for now).
-chat/**
 common/**
 editor/**
 ldap/**
 mail/**
 mailnews/**
 suite/**
--- a/chat/components/src/imAccounts.js
+++ b/chat/components/src/imAccounts.js
@@ -6,18 +6,21 @@ var {
   EmptyEnumerator,
   nsSimpleEnumerator,
   XPCOMUtils,
   setTimeout,
   clearTimeout,
   executeSoon,
   l10nHelper,
 } = ChromeUtils.import("resource:///modules/imXPCOMUtils.jsm");
-var {Services} = ChromeUtils.import("resource:///modules/imServices.jsm");
-var {GenericAccountPrototype, GenericAccountBuddyPrototype} = ChromeUtils.import("resource:///modules/jsProtoHelper.jsm");
+var { Services } = ChromeUtils.import("resource:///modules/imServices.jsm");
+var {
+  GenericAccountPrototype,
+  GenericAccountBuddyPrototype,
+} = ChromeUtils.import("resource:///modules/jsProtoHelper.jsm");
 
 var kPrefAutologinPending = "messenger.accounts.autoLoginPending";
 var kPrefMessengerAccounts = "messenger.accounts";
 var kPrefAccountPrefix = "messenger.account.";
 var kAccountKeyPrefix = "account";
 var kAccountOptionPrefPrefix = "options.";
 var kPrefAccountName = "name";
 var kPrefAccountPrpl = "prpl";
@@ -32,128 +35,171 @@ var kPrefAccountPassword = "password";
 XPCOMUtils.defineLazyGetter(this, "_", () =>
   l10nHelper("chrome://chat/locale/accounts.properties")
 );
 
 XPCOMUtils.defineLazyGetter(this, "_maxDebugMessages", () =>
   Services.prefs.getIntPref("messenger.accounts.maxDebugMessages")
 );
 
-XPCOMUtils.defineLazyServiceGetter(this, "HttpProtocolHandler",
-  "@mozilla.org/network/protocol;1?name=http", "nsIHttpProtocolHandler");
+XPCOMUtils.defineLazyServiceGetter(
+  this,
+  "HttpProtocolHandler",
+  "@mozilla.org/network/protocol;1?name=http",
+  "nsIHttpProtocolHandler"
+);
 
 var gUserCanceledMasterPasswordPrompt = false;
 var gConvertingOldPasswords = false;
 
 var SavePrefTimer = {
   saveNow() {
     if (this._timer) {
       clearTimeout(this._timer);
       this._timer = null;
     }
     Services.prefs.savePrefFile(null);
   },
   _timer: null,
   unInitTimer() {
-    if (this._timer)
+    if (this._timer) {
       this.saveNow();
+    }
   },
   initTimer() {
-    if (!this._timer)
+    if (!this._timer) {
       this._timer = setTimeout(this.saveNow.bind(this), 5000);
+    }
   },
 };
 
 var AutoLoginCounter = {
   _count: 0,
   startAutoLogin() {
     ++this._count;
-    if (this._count != 1)
+    if (this._count != 1) {
       return;
+    }
     Services.prefs.setIntPref(kPrefAutologinPending, Date.now() / 1000);
     SavePrefTimer.saveNow();
   },
   finishedAutoLogin() {
     --this._count;
-    if (this._count != 0)
+    if (this._count != 0) {
       return;
+    }
     Services.prefs.deleteBranch(kPrefAutologinPending);
     SavePrefTimer.initTimer();
   },
 };
 
-function UnknownProtocol(aPrplId)
-{
+function UnknownProtocol(aPrplId) {
   this.id = aPrplId;
 }
 UnknownProtocol.prototype = {
   __proto__: ClassInfo("prplIProtocol", "Unknown protocol"),
-  get name() { return ""; },
-  get normalizedName() { return this.name; },
-  get iconBaseURI() { return "chrome://chat/skin/prpl-unknown/"; },
-  getOptions() { return EmptyEnumerator; },
-  getUsernameSplit() { return EmptyEnumerator; },
-  get usernameEmptyText() { return ""; },
+  get name() {
+    return "";
+  },
+  get normalizedName() {
+    return this.name;
+  },
+  get iconBaseURI() {
+    return "chrome://chat/skin/prpl-unknown/";
+  },
+  getOptions() {
+    return EmptyEnumerator;
+  },
+  getUsernameSplit() {
+    return EmptyEnumerator;
+  },
+  get usernameEmptyText() {
+    return "";
+  },
 
-  getAccount(aKey, aName) { throw Cr.NS_ERROR_NOT_IMPLEMENTED; },
-  accountExists() { throw Cr.NS_ERROR_NOT_IMPLEMENTED; },
+  getAccount(aKey, aName) {
+    throw Cr.NS_ERROR_NOT_IMPLEMENTED;
+  },
+  accountExists() {
+    throw Cr.NS_ERROR_NOT_IMPLEMENTED;
+  },
 
   // false seems an acceptable default for all options
   // (they should never be called anyway).
-  get uniqueChatName() { return false; },
-  get chatHasTopic() { return false; },
-  get noPassword() { return false; },
-  get newMailNotification() { return false; },
-  get imagesInIM() { return false; },
-  get passwordOptional() { return true; },
-  get usePointSize() { return true; },
-  get registerNoScreenName() { return false; },
-  get slashCommandsNative() { return false; },
-  get usePurpleProxy() { return false; },
+  get uniqueChatName() {
+    return false;
+  },
+  get chatHasTopic() {
+    return false;
+  },
+  get noPassword() {
+    return false;
+  },
+  get newMailNotification() {
+    return false;
+  },
+  get imagesInIM() {
+    return false;
+  },
+  get passwordOptional() {
+    return true;
+  },
+  get usePointSize() {
+    return true;
+  },
+  get registerNoScreenName() {
+    return false;
+  },
+  get slashCommandsNative() {
+    return false;
+  },
+  get usePurpleProxy() {
+    return false;
+  },
 };
 
 // An unknown prplIAccount.
 function UnknownAccount(aAccount) {
   this._init(aAccount.protocol, aAccount);
 }
 UnknownAccount.prototype = GenericAccountPrototype;
 
 function UnknownAccountBuddy(aAccount, aBuddy, aTag) {
   this._init(new UnknownAccount(aAccount), aBuddy, aTag);
 }
 UnknownAccountBuddy.prototype = GenericAccountBuddyPrototype;
 
 // aName and aPrplId are provided as parameter only if this is a new
 // account that doesn't exist in the preferences. In this case, these
 // 2 values should be stored.
-function imAccount(aKey, aName, aPrplId)
-{
-  if (!aKey.startsWith(kAccountKeyPrefix))
+function imAccount(aKey, aName, aPrplId) {
+  if (!aKey.startsWith(kAccountKeyPrefix)) {
     throw Cr.NS_ERROR_INVALID_ARG;
+  }
 
   this.id = aKey;
   this.numericId = parseInt(aKey.substr(kAccountKeyPrefix.length));
   gAccountsService._keepAccount(this);
   this.prefBranch = Services.prefs.getBranch(kPrefAccountPrefix + aKey + ".");
 
   if (aName) {
     this.name = aName;
     this.prefBranch.setStringPref(kPrefAccountName, aName);
 
     this.firstConnectionState = Ci.imIAccount.FIRST_CONNECTION_UNKNOWN;
-  }
-  else {
+  } else {
     this.name = this.prefBranch.getStringPref(kPrefAccountName);
   }
 
   let prplId = aPrplId;
-  if (prplId)
+  if (prplId) {
     this.prefBranch.setCharPref(kPrefAccountPrpl, prplId);
-  else
+  } else {
     prplId = this.prefBranch.getCharPref(kPrefAccountPrpl);
+  }
 
   // Get the protocol plugin, or fallback to an UnknownProtocol instance.
   this.protocol = Services.core.getProtocolById(prplId);
   if (!this.protocol) {
     this.protocol = new UnknownProtocol(prplId);
     this._connectionErrorReason = Ci.imIAccount.ERROR_UNKNOWN_PRPL;
     return;
   }
@@ -164,238 +210,280 @@ function imAccount(aKey, aName, aPrplId)
   // Get the prplIAccount from the protocol plugin.
   this.prplAccount = this.protocol.getAccount(this);
 
   // 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)
+  if (this.firstConnectionState == Ci.imIAccount.FIRST_CONNECTION_PENDING) {
     this.firstConnectionState = Ci.imIAccount.FIRST_CONNECTION_CRASHED;
+  }
 
   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.getStringPref(kPrefAccountPassword);
-        if (password && !this.password)
+        if (password && !this.password) {
           this.password = password;
-      } catch (e) { /* No password saved in the prefs for this account. */ }
+        }
+      } catch (e) {
+        /* No password saved in the prefs for this account. */
+      }
     }
 
     // Check for errors that should prevent connection attempts.
-    if (this._passwordRequired && !this.password)
+    if (this._passwordRequired && !this.password) {
       this._connectionErrorReason = Ci.imIAccount.ERROR_MISSING_PASSWORD;
-    else if (this.firstConnectionState == Ci.imIAccount.FIRST_CONNECTION_CRASHED)
+    } 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,
   protocol: null,
   prplAccount: null,
   connectionState: Ci.imIAccount.STATE_DISCONNECTED,
   connectionStateMsg: "",
   connectionErrorMessage: "",
   _connectionErrorReason: Ci.prplIAccount.NO_ERROR,
   get connectionErrorReason() {
-    if (this._connectionErrorReason != Ci.prplIAccount.NO_ERROR &&
-        (this._connectionErrorReason != Ci.imIAccount.ERROR_MISSING_PASSWORD ||
-         !this._password))
+    if (
+      this._connectionErrorReason != Ci.prplIAccount.NO_ERROR &&
+      (this._connectionErrorReason != Ci.imIAccount.ERROR_MISSING_PASSWORD ||
+        !this._password)
+    ) {
       return this._connectionErrorReason;
+    }
     return this.prplAccount.connectionErrorReason;
   },
 
   observe(aSubject, aTopic, aData) {
-    if (aTopic == "account-connect-progress")
+    if (aTopic == "account-connect-progress") {
       this.connectionStateMsg = aData;
-    else if (aTopic == "account-connecting") {
+    } else if (aTopic == "account-connecting") {
       if (this.prplAccount.connectionErrorReason != Ci.prplIAccount.NO_ERROR) {
         delete this.connectionErrorMessage;
         if (this.timeOfNextReconnect - Date.now() > 1000) {
           // This is a manual reconnection, reset the auto-reconnect stuff
           this.timeOfLastConnect = 0;
           this._cancelReconnection();
         }
       }
-      if (this.firstConnectionState != Ci.imIAccount.FIRST_CONNECTION_OK)
+      if (this.firstConnectionState != Ci.imIAccount.FIRST_CONNECTION_OK) {
         this.firstConnectionState = Ci.imIAccount.FIRST_CONNECTION_PENDING;
+      }
       this.connectionState = Ci.imIAccount.STATE_CONNECTING;
-    }
-    else if (aTopic == "account-connected") {
+    } else if (aTopic == "account-connected") {
       this.connectionState = Ci.imIAccount.STATE_CONNECTED;
       this._finishedAutoLogin();
       this.timeOfLastConnect = Date.now();
-      if (this.firstConnectionState != Ci.imIAccount.FIRST_CONNECTION_OK)
+      if (this.firstConnectionState != Ci.imIAccount.FIRST_CONNECTION_OK) {
         this.firstConnectionState = Ci.imIAccount.FIRST_CONNECTION_OK;
+      }
       delete this.connectionStateMsg;
 
-      if (this.canJoinChat &&
-          this.prefBranch.prefHasUserValue(kPrefAccountAutoJoin)) {
+      if (
+        this.canJoinChat &&
+        this.prefBranch.prefHasUserValue(kPrefAccountAutoJoin)
+      ) {
         let autojoin = this.prefBranch.getStringPref(kPrefAccountAutoJoin);
         if (autojoin) {
           for (let room of autojoin.trim().split(/,\s*/)) {
-            if (room)
+            if (room) {
               this.joinChat(this.getChatRoomDefaultFieldValues(room));
+            }
           }
         }
       }
-    }
-    else if (aTopic == "account-disconnecting") {
+    } else if (aTopic == "account-disconnecting") {
       this.connectionState = Ci.imIAccount.STATE_DISCONNECTING;
       this.connectionErrorMessage = aData;
       delete this.connectionStateMsg;
       this._finishedAutoLogin();
 
       let firstConnectionState = this.firstConnectionState;
-      if (firstConnectionState != Ci.imIAccount.FIRST_CONNECTION_OK &&
-          firstConnectionState != Ci.imIAccount.FIRST_CONNECTION_CRASHED)
+      if (
+        firstConnectionState != Ci.imIAccount.FIRST_CONNECTION_OK &&
+        firstConnectionState != Ci.imIAccount.FIRST_CONNECTION_CRASHED
+      ) {
         this.firstConnectionState = Ci.imIAccount.FIRST_CONNECTION_UNKNOWN;
+      }
 
       let connectionErrorReason = this.prplAccount.connectionErrorReason;
       if (connectionErrorReason != Ci.prplIAccount.NO_ERROR) {
-        if (connectionErrorReason == Ci.prplIAccount.ERROR_NETWORK_ERROR ||
-            connectionErrorReason == Ci.prplIAccount.ERROR_ENCRYPTION_ERROR)
+        if (
+          connectionErrorReason == Ci.prplIAccount.ERROR_NETWORK_ERROR ||
+          connectionErrorReason == Ci.prplIAccount.ERROR_ENCRYPTION_ERROR
+        ) {
           this._startReconnectTimer();
+        }
         this._sendNotification("account-connect-error");
       }
-    }
-    else if (aTopic == "account-disconnected") {
+    } else if (aTopic == "account-disconnected") {
       this.connectionState = Ci.imIAccount.STATE_DISCONNECTED;
       let connectionErrorReason = this.prplAccount.connectionErrorReason;
       if (connectionErrorReason != Ci.prplIAccount.NO_ERROR) {
         // If the account was disconnected with an error, save the debug messages.
         this._omittedDebugMessagesBeforeError += this._omittedDebugMessages;
-        if (this._debugMessagesBeforeError)
+        if (this._debugMessagesBeforeError) {
           this._omittedDebugMessagesBeforeError += this._debugMessagesBeforeError.length;
+        }
         this._debugMessagesBeforeError = this._debugMessages;
-      }
-      else {
+      } else {
         // After a clean disconnection, drop the debug messages that
         // could have been left by a previous error.
         delete this._omittedDebugMessagesBeforeError;
         delete this._debugMessagesBeforeError;
       }
       delete this._omittedDebugMessages;
       delete this._debugMessages;
-      if (this._statusObserver &&
-          connectionErrorReason == Ci.prplIAccount.NO_ERROR &&
-          this.statusInfo.statusType > Ci.imIStatusInfo.STATUS_OFFLINE) {
+      if (
+        this._statusObserver &&
+        connectionErrorReason == Ci.prplIAccount.NO_ERROR &&
+        this.statusInfo.statusType > Ci.imIStatusInfo.STATUS_OFFLINE
+      ) {
         // If the status changed back to online while an account was still
         // disconnecting, it was not reconnected automatically at that point,
         // so we must do it now. (This happens for protocols like IRC where
         // disconnection is not immediate.)
         this._sendNotification(aTopic, aData);
         this.connect();
         return;
       }
+    } else {
+      throw Cr.NS_ERROR_UNEXPECTED;
     }
-    else
-      throw Cr.NS_ERROR_UNEXPECTED;
     this._sendNotification(aTopic, aData);
   },
 
   _debugMessages: null,
   _omittedDebugMessages: 0,
   _debugMessagesBeforeError: null,
   _omittedDebugMessagesBeforeError: 0,
   logDebugMessage(aMessage, aLevel) {
-    if (!this._debugMessages)
+    if (!this._debugMessages) {
       this._debugMessages = [];
-    if (_maxDebugMessages &&
-        this._debugMessages.length >= _maxDebugMessages) {
+    }
+    if (_maxDebugMessages && this._debugMessages.length >= _maxDebugMessages) {
       this._debugMessages.shift();
       ++this._omittedDebugMessages;
     }
-    this._debugMessages.push({logLevel: aLevel, message: aMessage});
+    this._debugMessages.push({ logLevel: aLevel, message: aMessage });
   },
   _createDebugMessage(aMessage) {
-    let scriptError =
-      Cc["@mozilla.org/scripterror;1"].createInstance(Ci.nsIScriptError);
-    scriptError.init(aMessage, "", "", 0, null, Ci.nsIScriptError.warningFlag,
-                     "component javascript");
-    return {logLevel: 0, message: scriptError};
+    let scriptError = Cc["@mozilla.org/scripterror;1"].createInstance(
+      Ci.nsIScriptError
+    );
+    scriptError.init(
+      aMessage,
+      "",
+      "",
+      0,
+      null,
+      Ci.nsIScriptError.warningFlag,
+      "component javascript"
+    );
+    return { logLevel: 0, message: scriptError };
   },
   getDebugMessages(aCount) {
     let messages = [];
     if (this._omittedDebugMessagesBeforeError) {
       let text = this._omittedDebugMessagesBeforeError + " messages omitted";
       messages.push(this._createDebugMessage(text));
     }
-    if (this._debugMessagesBeforeError)
+    if (this._debugMessagesBeforeError) {
       messages = messages.concat(this._debugMessagesBeforeError);
+    }
     if (this._omittedDebugMessages) {
       let text = this._omittedDebugMessages + " messages omitted";
       messages.push(this._createDebugMessage(text));
     }
-    if (this._debugMessages)
+    if (this._debugMessages) {
       messages = messages.concat(this._debugMessages);
+    }
     if (messages.length) {
       let appInfo = Services.appinfo;
       let header =
         `${appInfo.name} ${appInfo.version} (${appInfo.appBuildID}), ` +
         `Gecko ${appInfo.platformVersion} (${appInfo.platformBuildID}) ` +
         `on ${HttpProtocolHandler.oscpu}`;
       messages.unshift(this._createDebugMessage(header));
     }
 
-    if (aCount)
+    if (aCount) {
       aCount.value = messages.length;
+    }
     return messages;
   },
 
   _observedStatusInfo: null,
-  get observedStatusInfo() { return this._observedStatusInfo; },
+  get observedStatusInfo() {
+    return this._observedStatusInfo;
+  },
   _statusObserver: null,
   set observedStatusInfo(aUserStatusInfo) {
-    if (!this.prplAccount)
+    if (!this.prplAccount) {
       return;
-    if (this._statusObserver)
+    }
+    if (this._statusObserver) {
       this.statusInfo.removeObserver(this._statusObserver);
+    }
     this._observedStatusInfo = aUserStatusInfo;
-    if (this._statusObserver)
+    if (this._statusObserver) {
       this.statusInfo.addObserver(this._statusObserver);
+    }
   },
   _removeStatusObserver() {
     if (this._statusObserver) {
       this.statusInfo.removeObserver(this._statusObserver);
       delete this._statusObserver;
     }
   },
-  get statusInfo() { return this._observedStatusInfo || Services.core.globalUserStatus; },
+  get statusInfo() {
+    return this._observedStatusInfo || Services.core.globalUserStatus;
+  },
 
   reconnectAttempt: 0,
   timeOfLastConnect: 0,
   timeOfNextReconnect: 0,
   _reconnectTimer: null,
   _startReconnectTimer() {
     if (Services.io.offline) {
       Cu.reportError("_startReconnectTimer called while offline");
       return;
     }
 
     /* If the last successful connection is older than 10 seconds, reset the
        number of reconnection attempts. */
     const kTimeBeforeSuccessfulConnection = 10;
-    if (this.timeOfLastConnect &&
-        this.timeOfLastConnect + kTimeBeforeSuccessfulConnection * 1000 < Date.now()) {
+    if (
+      this.timeOfLastConnect &&
+      this.timeOfLastConnect + kTimeBeforeSuccessfulConnection * 1000 <
+        Date.now()
+    ) {
       delete this.reconnectAttempt;
       delete this.timeOfLastConnect;
     }
 
-    let timers =
-      Services.prefs.getCharPref("messenger.accounts.reconnectTimer").split(",");
+    let timers = Services.prefs
+      .getCharPref("messenger.accounts.reconnectTimer")
+      .split(",");
     let delay = timers[Math.min(this.reconnectAttempt, timers.length - 1)];
     let msDelay = parseInt(delay) * 1000;
     ++this.reconnectAttempt;
     this.timeOfNextReconnect = Date.now() + msDelay;
     this._reconnectTimer = setTimeout(this.connect.bind(this), msDelay);
   },
 
   _sendNotification(aTopic, aData) {
@@ -405,68 +493,77 @@ imAccount.prototype = {
   get firstConnectionState() {
     try {
       return this.prefBranch.getIntPref(kPrefAccountFirstConnectionState);
     } catch (e) {
       return Ci.imIAccount.FIRST_CONNECTION_OK;
     }
   },
   set firstConnectionState(aState) {
-    if (aState == Ci.imIAccount.FIRST_CONNECTION_OK)
+    if (aState == Ci.imIAccount.FIRST_CONNECTION_OK) {
       this.prefBranch.deleteBranch(kPrefAccountFirstConnectionState);
-    else {
+    } else {
       this.prefBranch.setIntPref(kPrefAccountFirstConnectionState, aState);
       // We want to save this pref immediately when trying to connect.
-      if (aState == Ci.imIAccount.FIRST_CONNECTION_PENDING)
+      if (aState == Ci.imIAccount.FIRST_CONNECTION_PENDING) {
         SavePrefTimer.saveNow();
-      else
+      } else {
         SavePrefTimer.initTimer();
+      }
     }
   },
 
   _pendingReconnectForConnectionInfoChange: false,
   _connectionInfoChanged() {
     // The next connection will be the first connection with these parameters.
     this.firstConnectionState = Ci.imIAccount.FIRST_CONNECTION_UNKNOWN;
 
     // We want to attempt to reconnect with the new settings only if a
     // previous attempt failed or a connection attempt is currently
     // pending (so we can return early if the account is currently
     // connected or disconnected without error).
     // The code doing the reconnection attempt is wrapped within an
     // executeSoon call so that when multiple settings are changed at
     // once we don't attempt to reconnect until they are all saved.
     // If a reconnect attempt is already scheduled, we can also return early.
-    if (this._pendingReconnectForConnectionInfoChange || this.connected ||
-        (this.disconnected &&
-         this.connectionErrorReason == Ci.prplIAccount.NO_ERROR))
+    if (
+      this._pendingReconnectForConnectionInfoChange ||
+      this.connected ||
+      (this.disconnected &&
+        this.connectionErrorReason == Ci.prplIAccount.NO_ERROR)
+    ) {
       return;
+    }
 
     this._pendingReconnectForConnectionInfoChange = true;
-    executeSoon(function() {
-      delete this._pendingReconnectForConnectionInfoChange;
-      // If the connection parameters have changed while we were
-      // trying to connect, cancel the ongoing connection attempt and
-      // try again with the new parameters.
-      if (this.connecting) {
-        this.disconnect();
-        this.connect();
-        return;
-      }
-      // If the account was disconnected because of a non-fatal
-      // connection error, retry now that we have new parameters.
-      let errorReason = this.connectionErrorReason;
-      if (this.disconnected &&
+    executeSoon(
+      function() {
+        delete this._pendingReconnectForConnectionInfoChange;
+        // If the connection parameters have changed while we were
+        // trying to connect, cancel the ongoing connection attempt and
+        // try again with the new parameters.
+        if (this.connecting) {
+          this.disconnect();
+          this.connect();
+          return;
+        }
+        // If the account was disconnected because of a non-fatal
+        // connection error, retry now that we have new parameters.
+        let errorReason = this.connectionErrorReason;
+        if (
+          this.disconnected &&
           errorReason != Ci.prplIAccount.NO_ERROR &&
           errorReason != Ci.imIAccount.ERROR_MISSING_PASSWORD &&
           errorReason != Ci.imIAccount.ERROR_CRASHED &&
-          errorReason != Ci.imIAccount.ERROR_UNKNOWN_PRPL) {
-        this.connect();
-      }
-    }.bind(this));
+          errorReason != Ci.imIAccount.ERROR_UNKNOWN_PRPL
+        ) {
+          this.connect();
+        }
+      }.bind(this)
+    );
   },
 
   // If the protocol plugin is missing, we can't access the normalizedName,
   // but in lots of cases this.name is equivalent.
   get normalizedName() {
     return this.prplAccount ? this.prplAccount.normalizedName : this.name;
   },
   normalize(aName) {
@@ -475,464 +572,574 @@ imAccount.prototype = {
 
   _sendUpdateNotification() {
     this._sendNotification("account-updated");
   },
 
   set alias(val) {
     if (val) {
       this.prefBranch.setStringPref(kPrefAccountAlias, val);
+    } else {
+      this.prefBranch.deleteBranch(kPrefAccountAlias);
     }
-    else
-      this.prefBranch.deleteBranch(kPrefAccountAlias);
     this._sendUpdateNotification();
   },
   get alias() {
     try {
       return this.prefBranch.getStringPref(kPrefAccountAlias);
     } catch (e) {
       return "";
     }
   },
 
   _password: "",
   get password() {
-    if (this._password)
+    if (this._password) {
       return this._password;
+    }
 
     // Avoid prompting the user for the master password more than once at startup.
-    if (gUserCanceledMasterPasswordPrompt)
+    if (gUserCanceledMasterPasswordPrompt) {
       return "";
+    }
 
     let passwordURI = "im://" + this.protocol.id;
     let logins;
     try {
       logins = Services.logins.findLogins(passwordURI, null, passwordURI);
     } catch (e) {
       this._handleMasterPasswordException(e);
       return "";
     }
     let normalizedName = this.normalizedName;
     for (let login of logins) {
       if (login.username == normalizedName) {
         this._password = login.password;
-        if (this._connectionErrorReason == Ci.imIAccount.ERROR_MISSING_PASSWORD) {
+        if (
+          this._connectionErrorReason == Ci.imIAccount.ERROR_MISSING_PASSWORD
+        ) {
           // We have found a password for an account marked as missing password,
           // re-check all others accounts missing a password. But first,
           // remove the error on our own account to avoid re-checking it.
           delete this._connectionErrorReason;
           gAccountsService._checkIfPasswordStillMissing();
         }
         return this._password;
       }
     }
     return "";
   },
   _checkIfPasswordStillMissing() {
-    if (this._connectionErrorReason != Ci.imIAccount.ERROR_MISSING_PASSWORD ||
-        !this.password)
+    if (
+      this._connectionErrorReason != Ci.imIAccount.ERROR_MISSING_PASSWORD ||
+      !this.password
+    ) {
       return;
+    }
 
     delete this._connectionErrorReason;
     this._sendUpdateNotification();
   },
   get _passwordRequired() {
     return !this.protocol.noPassword && !this.protocol.passwordOptional;
   },
   set password(aPassword) {
     this._password = aPassword;
-    if (gUserCanceledMasterPasswordPrompt)
+    if (gUserCanceledMasterPasswordPrompt) {
       return;
-    let newLogin = Cc["@mozilla.org/login-manager/loginInfo;1"]
-                   .createInstance(Ci.nsILoginInfo);
+    }
+    let newLogin = Cc["@mozilla.org/login-manager/loginInfo;1"].createInstance(
+      Ci.nsILoginInfo
+    );
     let passwordURI = "im://" + this.protocol.id;
-    newLogin.init(passwordURI, null, passwordURI, this.normalizedName,
-                  aPassword, "", "");
+    newLogin.init(
+      passwordURI,
+      null,
+      passwordURI,
+      this.normalizedName,
+      aPassword,
+      "",
+      ""
+    );
     try {
       let logins = Services.logins.findLogins(passwordURI, null, passwordURI);
       let saved = false;
       for (let login of logins) {
         if (newLogin.matches(login, true)) {
-          if (aPassword)
+          if (aPassword) {
             Services.logins.modifyLogin(login, newLogin);
-          else
+          } else {
             Services.logins.removeLogin(login);
+          }
           saved = true;
           break;
         }
       }
-      if (!saved && aPassword)
+      if (!saved && aPassword) {
         Services.logins.addLogin(newLogin);
+      }
     } catch (e) {
       this._handleMasterPasswordException(e);
     }
 
     this._connectionInfoChanged();
-    if (aPassword &&
-        this._connectionErrorReason == Ci.imIAccount.ERROR_MISSING_PASSWORD)
+    if (
+      aPassword &&
+      this._connectionErrorReason == Ci.imIAccount.ERROR_MISSING_PASSWORD
+    ) {
       this._connectionErrorReason = Ci.imIAccount.NO_ERROR;
-    else if (!aPassword && this._passwordRequired)
+    } else if (!aPassword && this._passwordRequired) {
       this._connectionErrorReason = Ci.imIAccount.ERROR_MISSING_PASSWORD;
+    }
     this._sendUpdateNotification();
   },
   _handleMasterPasswordException(aException) {
-    if (aException.result != Cr.NS_ERROR_ABORT)
+    if (aException.result != Cr.NS_ERROR_ABORT) {
       throw aException;
+    }
 
     gUserCanceledMasterPasswordPrompt = true;
-    executeSoon(function() { gUserCanceledMasterPasswordPrompt = false; });
+    executeSoon(function() {
+      gUserCanceledMasterPasswordPrompt = false;
+    });
   },
 
   get autoLogin() {
     return this.prefBranch.getBoolPref(kPrefAccountAutoLogin, true);
   },
   set autoLogin(val) {
     this.prefBranch.setBoolPref(kPrefAccountAutoLogin, val);
     SavePrefTimer.initTimer();
     this._sendUpdateNotification();
   },
   _autoLoginPending: false,
   checkAutoLogin() {
     // No auto-login if: the account has an error at the imIAccount level
     // (unknown protocol, missing password, first connection crashed),
     // the account is already connected or connecting, or autoLogin is off.
-    if (this._connectionErrorReason != Ci.prplIAccount.NO_ERROR ||
-        this.connecting || this.connected || !this.autoLogin)
+    if (
+      this._connectionErrorReason != Ci.prplIAccount.NO_ERROR ||
+      this.connecting ||
+      this.connected ||
+      !this.autoLogin
+    ) {
       return;
+    }
 
     this._autoLoginPending = true;
     AutoLoginCounter.startAutoLogin();
     try {
       this.connect();
     } catch (e) {
       Cu.reportError(e);
       this._finishedAutoLogin();
     }
   },
   _finishedAutoLogin() {
-    if (!this.hasOwnProperty("_autoLoginPending"))
+    if (!this.hasOwnProperty("_autoLoginPending")) {
       return;
+    }
     delete this._autoLoginPending;
     AutoLoginCounter.finishedAutoLogin();
   },
 
   // Delete the account (from the preferences, mozStorage, and call unInit).
   remove() {
-    let login = Cc["@mozilla.org/login-manager/loginInfo;1"]
-                .createInstance(Ci.nsILoginInfo);
+    let login = Cc["@mozilla.org/login-manager/loginInfo;1"].createInstance(
+      Ci.nsILoginInfo
+    );
     let passwordURI = "im://" + this.protocol.id;
     // Note: the normalizedName may not be exactly right if the
     // protocol plugin is missing.
     login.init(passwordURI, null, passwordURI, this.normalizedName, "", "", "");
     let logins = Services.logins.findLogins(passwordURI, null, passwordURI);
     for (let l of logins) {
       if (login.matches(l, true)) {
         Services.logins.removeLogin(l);
         break;
       }
     }
-    if (this.connected || this.connecting)
+    if (this.connected || this.connecting) {
       this.disconnect();
-    if (this.prplAccount)
+    }
+    if (this.prplAccount) {
       this.prplAccount.remove();
+    }
     this.unInit();
     Services.contacts.forgetAccount(this.numericId);
     this.prefBranch.deleteBranch("");
   },
   unInit() {
     // remove any pending reconnection timer.
     this._cancelReconnection();
 
     // Keeping a status observer could cause an immediate reconnection.
     this._removeStatusObserver();
 
     // remove any pending autologin preference used for crash detection.
     this._finishedAutoLogin();
 
     // If the first connection was pending on quit, we set it back to unknown.
-    if (this.firstConnectionState == Ci.imIAccount.FIRST_CONNECTION_PENDING)
+    if (this.firstConnectionState == Ci.imIAccount.FIRST_CONNECTION_PENDING) {
       this.firstConnectionState = Ci.imIAccount.FIRST_CONNECTION_UNKNOWN;
+    }
 
     // and make sure we cleanup the save pref timer.
     SavePrefTimer.unInitTimer();
 
-    if (this.prplAccount)
+    if (this.prplAccount) {
       this.prplAccount.unInit();
+    }
 
     delete this.protocol;
     delete this.prplAccount;
   },
 
   get _ensurePrplAccount() {
-    if (this.prplAccount)
+    if (this.prplAccount) {
       return this.prplAccount;
+    }
     throw Cr.NS_ERROR_NOT_IMPLEMENTED;
   },
   connect() {
-    if (!this.prplAccount)
+    if (!this.prplAccount) {
       throw Cr.NS_ERROR_NOT_IMPLEMENTED;
+    }
 
     if (this._passwordRequired) {
       // If the previous connection attempt failed because we have a wrong password,
       // clear the passwor cache so that if there's no password in the password
       // manager the user gets prompted again.
-      if (this.connectionErrorReason == Ci.prplIAccount.ERROR_AUTHENTICATION_FAILED)
+      if (
+        this.connectionErrorReason ==
+        Ci.prplIAccount.ERROR_AUTHENTICATION_FAILED
+      ) {
         delete this._password;
+      }
 
       let password = this.password;
       if (!password) {
         let prompts = Services.prompt;
-        let shouldSave = {value: false};
-        password = {value: ""};
-        if (!prompts.promptPassword(null, _("passwordPromptTitle", this.name),
-                                    _("passwordPromptText", this.name),
-                                    password, _("passwordPromptSaveCheckbox"),
-                                    shouldSave))
+        let shouldSave = { value: false };
+        password = { value: "" };
+        if (
+          !prompts.promptPassword(
+            null,
+            _("passwordPromptTitle", this.name),
+            _("passwordPromptText", this.name),
+            password,
+            _("passwordPromptSaveCheckbox"),
+            shouldSave
+          )
+        ) {
           return;
+        }
 
-        if (shouldSave.value)
+        if (shouldSave.value) {
           this.password = password.value;
-        else
+        } else {
           this._password = password.value;
+        }
       }
     }
 
     if (!this._statusObserver) {
       this._statusObserver = {
-        observe: (function(aSubject, aTopic, aData) {
+        observe: function(aSubject, aTopic, aData) {
           // Disconnect or reconnect the account automatically, otherwise notify
           // the prplAccount instance.
           let statusType = aSubject.statusType;
           let connectionErrorReason = this.connectionErrorReason;
           if (statusType == Ci.imIStatusInfo.STATUS_OFFLINE) {
-            if (this.connected || this.connecting)
+            if (this.connected || this.connecting) {
               this.prplAccount.disconnect();
+            }
             this._cancelReconnection();
+          } else if (
+            statusType > Ci.imIStatusInfo.STATUS_OFFLINE &&
+            this.disconnected &&
+            (connectionErrorReason == Ci.prplIAccount.NO_ERROR ||
+              connectionErrorReason == Ci.prplIAccount.ERROR_NETWORK_ERROR ||
+              connectionErrorReason == Ci.prplIAccount.ERROR_ENCRYPTION_ERROR)
+          ) {
+            this.prplAccount.connect();
+          } else if (this.connected) {
+            this.prplAccount.observe(aSubject, aTopic, aData);
           }
-          else if (statusType > Ci.imIStatusInfo.STATUS_OFFLINE &&
-                   this.disconnected &&
-                   (connectionErrorReason == Ci.prplIAccount.NO_ERROR ||
-                    connectionErrorReason == Ci.prplIAccount.ERROR_NETWORK_ERROR ||
-                    connectionErrorReason == Ci.prplIAccount.ERROR_ENCRYPTION_ERROR))
-            this.prplAccount.connect();
-          else if (this.connected)
-            this.prplAccount.observe(aSubject, aTopic, aData);
-        }).bind(this),
+        }.bind(this),
       };
 
       this.statusInfo.addObserver(this._statusObserver);
     }
 
-    if (!Services.io.offline &&
-        this.statusInfo.statusType > Ci.imIStatusInfo.STATUS_OFFLINE &&
-        this.disconnected)
+    if (
+      !Services.io.offline &&
+      this.statusInfo.statusType > Ci.imIStatusInfo.STATUS_OFFLINE &&
+      this.disconnected
+    ) {
       this.prplAccount.connect();
+    }
   },
   disconnect() {
     this._removeStatusObserver();
-    if (!this.disconnected)
+    if (!this.disconnected) {
       this._ensurePrplAccount.disconnect();
+    }
   },
 
-  get disconnected() { return this.connectionState == Ci.imIAccount.STATE_DISCONNECTED; },
-  get connected() { return this.connectionState == Ci.imIAccount.STATE_CONNECTED; },
-  get connecting() { return this.connectionState == Ci.imIAccount.STATE_CONNECTING; },
-  get disconnecting() { return this.connectionState == Ci.imIAccount.STATE_DISCONNECTING; },
+  get disconnected() {
+    return this.connectionState == Ci.imIAccount.STATE_DISCONNECTED;
+  },
+  get connected() {
+    return this.connectionState == Ci.imIAccount.STATE_CONNECTED;
+  },
+  get connecting() {
+    return this.connectionState == Ci.imIAccount.STATE_CONNECTING;
+  },
+  get disconnecting() {
+    return this.connectionState == Ci.imIAccount.STATE_DISCONNECTING;
+  },
 
   _cancelReconnection() {
     if (this._reconnectTimer) {
       clearTimeout(this._reconnectTimer);
       delete this._reconnectTimer;
     }
     delete this.reconnectAttempt;
     delete this.timeOfNextReconnect;
   },
   cancelReconnection() {
-    if (!this.disconnected)
+    if (!this.disconnected) {
       throw Cr.NS_ERROR_UNEXPECTED;
+    }
 
     // Ensure we don't keep a status observer that could re-enable the
     // auto-reconnect timers.
     this.disconnect();
 
     this._cancelReconnection();
   },
   createConversation(aName) {
     return this._ensurePrplAccount.createConversation(aName);
   },
   addBuddy(aTag, aName) {
     this._ensurePrplAccount.addBuddy(aTag, aName);
   },
   loadBuddy(aBuddy, aTag) {
-    if (this.prplAccount)
+    if (this.prplAccount) {
       return this.prplAccount.loadBuddy(aBuddy, aTag);
+    }
     // Generate dummy account buddies for unknown protocols.
     return new UnknownAccountBuddy(this, aBuddy, aTag);
   },
   requestBuddyInfo(aBuddyName) {
     this._ensurePrplAccount.requestBuddyInfo(aBuddyName);
   },
-  getChatRoomFields() { return this._ensurePrplAccount.getChatRoomFields(); },
+  getChatRoomFields() {
+    return this._ensurePrplAccount.getChatRoomFields();
+  },
   getChatRoomDefaultFieldValues(aDefaultChatName) {
-    return this._ensurePrplAccount.getChatRoomDefaultFieldValues(aDefaultChatName);
+    return this._ensurePrplAccount.getChatRoomDefaultFieldValues(
+      aDefaultChatName
+    );
   },
-  get canJoinChat() { return this.prplAccount ? this.prplAccount.canJoinChat : false; },
+  get canJoinChat() {
+    return this.prplAccount ? this.prplAccount.canJoinChat : false;
+  },
   joinChat(aComponents) {
     this._ensurePrplAccount.joinChat(aComponents);
   },
   setBool(aName, aVal) {
     this.prefBranch.setBoolPref(kAccountOptionPrefPrefix + aName, aVal);
     this._connectionInfoChanged();
-    if (this.prplAccount)
+    if (this.prplAccount) {
       this.prplAccount.setBool(aName, aVal);
+    }
     SavePrefTimer.initTimer();
   },
   setInt(aName, aVal) {
     this.prefBranch.setIntPref(kAccountOptionPrefPrefix + aName, aVal);
     this._connectionInfoChanged();
-    if (this.prplAccount)
+    if (this.prplAccount) {
       this.prplAccount.setInt(aName, aVal);
+    }
     SavePrefTimer.initTimer();
   },
   setString(aName, aVal) {
     this.prefBranch.setStringPref(kAccountOptionPrefPrefix + aName, aVal);
     this._connectionInfoChanged();
-    if (this.prplAccount)
+    if (this.prplAccount) {
       this.prplAccount.setString(aName, aVal);
+    }
     SavePrefTimer.initTimer();
   },
-  save() { SavePrefTimer.saveNow(); },
+  save() {
+    SavePrefTimer.saveNow();
+  },
 
-  get HTMLEnabled() { return this._ensurePrplAccount.HTMLEnabled; },
-  get HTMLEscapePlainText() { return this._ensurePrplAccount.HTMLEscapePlainText; },
-  get noBackgroundColors() { return this._ensurePrplAccount.noBackgroundColors; },
-  get autoResponses() { return this._ensurePrplAccount.autoResponses; },
-  get singleFormatting() { return this._ensurePrplAccount.singleFormatting; },
-  get noFontSizes() { return this._ensurePrplAccount.noFontSizes; },
-  get noUrlDesc() { return this._ensurePrplAccount.noUrlDesc; },
-  get noImages() { return this._ensurePrplAccount.noImages; },
+  get HTMLEnabled() {
+    return this._ensurePrplAccount.HTMLEnabled;
+  },
+  get HTMLEscapePlainText() {
+    return this._ensurePrplAccount.HTMLEscapePlainText;
+  },
+  get noBackgroundColors() {
+    return this._ensurePrplAccount.noBackgroundColors;
+  },
+  get autoResponses() {
+    return this._ensurePrplAccount.autoResponses;
+  },
+  get singleFormatting() {
+    return this._ensurePrplAccount.singleFormatting;
+  },
+  get noFontSizes() {
+    return this._ensurePrplAccount.noFontSizes;
+  },
+  get noUrlDesc() {
+    return this._ensurePrplAccount.noUrlDesc;
+  },
+  get noImages() {
+    return this._ensurePrplAccount.noImages;
+  },
 };
 
 var gAccountsService = null;
 
-function AccountsService() { }
+function AccountsService() {}
 AccountsService.prototype = {
   initAccounts() {
     this._initAutoLoginStatus();
     this._accounts = [];
     this._accountsById = {};
     gAccountsService = this;
-    gConvertingOldPasswords =
-      Services.prefs.getBoolPref(kPrefConvertOldPasswords);
+    gConvertingOldPasswords = Services.prefs.getBoolPref(
+      kPrefConvertOldPasswords
+    );
     let accountList = this._accountList;
-    for (let account of (accountList ? accountList.split(",") : [])) {
+    for (let account of accountList ? accountList.split(",") : []) {
       try {
         account.trim();
-        if (!account)
+        if (!account) {
           throw Cr.NS_ERROR_INVALID_ARG;
+        }
         new imAccount(account);
       } catch (e) {
         Cu.reportError(e);
         dump(e + " " + e.toSource() + "\n");
       }
     }
     // If the user has canceled a master password prompt, we haven't
     // been able to save any password, so the old password conversion
     // still needs to happen.
-    if (gConvertingOldPasswords && !gUserCanceledMasterPasswordPrompt)
+    if (gConvertingOldPasswords && !gUserCanceledMasterPasswordPrompt) {
       Services.prefs.setBoolPref(kPrefConvertOldPasswords, false);
+    }
 
     this._prefObserver = this.observe.bind(this);
     Services.prefs.addObserver(kPrefMessengerAccounts, this._prefObserver);
   },
 
   _observingAccountListChange: true,
   _prefObserver: null,
   observe(aSubject, aTopic, aData) {
-    if (aTopic != "nsPref:changed" || aData != kPrefMessengerAccounts ||
-       !this._observingAccountListChange)
+    if (
+      aTopic != "nsPref:changed" ||
+      aData != kPrefMessengerAccounts ||
+      !this._observingAccountListChange
+    ) {
       return;
+    }
 
-    this._accounts =
-      this._accountList.split(",").map(account => account.trim())
-          .filter(k => k.startsWith(kAccountKeyPrefix))
-          .map(k => parseInt(k.substr(kAccountKeyPrefix.length)))
-          .map(this.getAccountByNumericId, this)
-          .filter(a => a);
+    this._accounts = this._accountList
+      .split(",")
+      .map(account => account.trim())
+      .filter(k => k.startsWith(kAccountKeyPrefix))
+      .map(k => parseInt(k.substr(kAccountKeyPrefix.length)))
+      .map(this.getAccountByNumericId, this)
+      .filter(a => a);
 
     Services.obs.notifyObservers(this, "account-list-updated");
   },
 
-  get _accountList() { return Services.prefs.getCharPref(kPrefMessengerAccounts); },
+  get _accountList() {
+    return Services.prefs.getCharPref(kPrefMessengerAccounts);
+  },
   set _accountList(aNewList) {
     this._observingAccountListChange = false;
     Services.prefs.setCharPref(kPrefMessengerAccounts, aNewList);
     delete this._observingAccountListChange;
   },
 
   unInitAccounts() {
-    for (let account of this._accounts)
+    for (let account of this._accounts) {
       account.unInit();
+    }
     gAccountsService = null;
     delete this._accounts;
     delete this._accountsById;
     Services.prefs.removeObserver(kPrefMessengerAccounts, this._prefObserver);
     delete this._prefObserver;
   },
 
   autoLoginStatus: Ci.imIAccountsService.AUTOLOGIN_ENABLED,
   _initAutoLoginStatus() {
     /* If auto-login is already disabled, do nothing */
-    if (this.autoLoginStatus != Ci.imIAccountsService.AUTOLOGIN_ENABLED)
+    if (this.autoLoginStatus != Ci.imIAccountsService.AUTOLOGIN_ENABLED) {
       return;
+    }
 
     let prefs = Services.prefs;
     if (!prefs.getIntPref("messenger.startup.action")) {
       // the value 0 means that we start without connecting the accounts
       this.autoLoginStatus = Ci.imIAccountsService.AUTOLOGIN_USER_DISABLED;
       return;
     }
 
     /* Disable auto-login if we are running in safe mode */
     if (Services.appinfo.inSafeMode) {
       this.autoLoginStatus = Ci.imIAccountsService.AUTOLOGIN_SAFE_MODE;
       return;
     }
 
     /* Check if we crashed at the last startup during autologin */
     let autoLoginPending;
-    if (prefs.getPrefType(kPrefAutologinPending) == prefs.PREF_INVALID ||
-        !(autoLoginPending = prefs.getIntPref(kPrefAutologinPending))) {
+    if (
+      prefs.getPrefType(kPrefAutologinPending) == prefs.PREF_INVALID ||
+      !(autoLoginPending = prefs.getIntPref(kPrefAutologinPending))
+    ) {
       // if the pref isn't set, then we haven't crashed: keep autologin enabled
       return;
     }
 
     // Last autologin hasn't finished properly.
     // For now, assume it's because of a crash.
     this.autoLoginStatus = Ci.imIAccountsService.AUTOLOGIN_CRASH;
     prefs.deleteBranch(kPrefAutologinPending);
 
     // If the crash reporter isn't built, we can't know anything more.
-    if (!("nsICrashReporter" in Ci))
+    if (!("nsICrashReporter" in Ci)) {
       return;
+    }
 
     try {
       // Try to get more info with breakpad
       let lastCrashTime = 0;
 
       /* Locate the LastCrash file */
       let lastCrash = Services.dirsvc.get("UAppData", Ci.nsIFile);
       lastCrash.append("Crash Reports");
       lastCrash.append("LastCrash");
       if (lastCrash.exists()) {
         /* Ok, the file exists, now let's try to read it */
-        let is = Cc["@mozilla.org/network/file-input-stream;1"]
-                 .createInstance(Ci.nsIFileInputStream);
-        let sis = Cc["@mozilla.org/scriptableinputstream;1"]
-                  .createInstance(Ci.nsIScriptableInputStream);
+        let is = Cc["@mozilla.org/network/file-input-stream;1"].createInstance(
+          Ci.nsIFileInputStream
+        );
+        let sis = Cc["@mozilla.org/scriptableinputstream;1"].createInstance(
+          Ci.nsIScriptableInputStream
+        );
         is.init(lastCrash, -1, 0, 0);
         sis.init(sis);
 
         lastCrashTime = parseInt(sis.read(lastCrash.fileSize));
 
         sis.close();
       }
       // The file not existing is totally acceptable, it just means that
@@ -945,131 +1152,153 @@ AccountsService.prototype = {
 
       if (lastCrashTime < autoLoginPending) {
         // the last crash caught by breakpad is older than our last autologin
         // attempt.
         // If breakpad is currently enabled, we can be confident that
         // autologin was interrupted for an exterior reason
         // (application killed by the user, power outage, ...)
         try {
-          Services.appinfo.QueryInterface(Ci.nsICrashReporter)
-                  .annotateCrashReport("=", "");
+          Services.appinfo
+            .QueryInterface(Ci.nsICrashReporter)
+            .annotateCrashReport("=", "");
         } catch (e) {
           // This should fail with NS_ERROR_INVALID_ARG if breakpad is enabled,
           // and NS_ERROR_NOT_INITIALIZED if it is not.
-          if (e.result != Cr.NS_ERROR_NOT_INITIALIZED)
+          if (e.result != Cr.NS_ERROR_NOT_INITIALIZED) {
             this.autoLoginStatus = Ci.imIAccountsService.AUTOLOGIN_ENABLED;
+          }
         }
       }
     } catch (e) {
       // if we failed to get the last crash time, then keep the
       // AUTOLOGIN_CRASH value in mAutoLoginStatus and return.
-
     }
   },
 
   processAutoLogin() {
-    if (!this._accounts)  // if we're already shutting down
+    if (!this._accounts) {
+      // if we're already shutting down
       return;
+    }
 
-    for (let account of this._accounts)
+    for (let account of this._accounts) {
       account.checkAutoLogin();
+    }
 
     // Make sure autologin is now enabled, so that we don't display a
     // message stating that it is disabled and asking the user if it
     // should be processed now.
     this.autoLoginStatus = Ci.imIAccountsService.AUTOLOGIN_ENABLED;
 
     // Notify observers so that any message stating that autologin is
     // disabled can be removed
     Services.obs.notifyObservers(this, "autologin-processed");
   },
 
   _checkingIfPasswordStillMissing: false,
   _checkIfPasswordStillMissing() {
     // Avoid recursion.
-    if (this._checkingIfPasswordStillMissing)
+    if (this._checkingIfPasswordStillMissing) {
       return;
+    }
 
     this._checkingIfPasswordStillMissing = true;
-    for (let account of this._accounts)
+    for (let account of this._accounts) {
       account._checkIfPasswordStillMissing();
+    }
     delete this._checkingIfPasswordStillMissing;
   },
 
   getAccountById(aAccountId) {
-    if (!aAccountId.startsWith(kAccountKeyPrefix))
+    if (!aAccountId.startsWith(kAccountKeyPrefix)) {
       throw Cr.NS_ERROR_INVALID_ARG;
+    }
 
     let id = parseInt(aAccountId.substr(kAccountKeyPrefix.length));
     return this.getAccountByNumericId(id);
   },
 
   _keepAccount(aAccount) {
     this._accounts.push(aAccount);
     this._accountsById[aAccount.numericId] = aAccount;
   },
-  getAccountByNumericId(aAccountId) { return this._accountsById[aAccountId]; },
-  getAccounts() { return new nsSimpleEnumerator(this._accounts); },
+  getAccountByNumericId(aAccountId) {
+    return this._accountsById[aAccountId];
+  },
+  getAccounts() {
+    return new nsSimpleEnumerator(this._accounts);
+  },
 
   createAccount(aName, aPrpl) {
     // Ensure an account with the same name and protocol doesn't already exist.
     let prpl = Services.core.getProtocolById(aPrpl);
-    if (!prpl)
+    if (!prpl) {
       throw Cr.NS_ERROR_UNEXPECTED;
+    }
     if (prpl.accountExists(aName)) {
       Cu.reportError("Attempted to create a duplicate account!");
       throw Cr.NS_ERROR_ALREADY_INITIALIZED;
     }
 
     /* First get a unique id for the new account. */
     let id;
     for (id = 1; ; ++id) {
-      if (this._accountsById.hasOwnProperty(id))
+      if (this._accountsById.hasOwnProperty(id)) {
         continue;
+      }
 
       /* id isn't used by a known account, double check it isn't
        already used in the sqlite database. This should never
        happen, except if we have a corrupted profile. */
-      if (!Services.contacts.accountIdExists(id))
+      if (!Services.contacts.accountIdExists(id)) {
         break;
-      Services.console.logStringMessage("No account " + id + " but there is some data in the buddy list for an account with this number. Your profile may be corrupted.");
+      }
+      Services.console.logStringMessage(
+        "No account " +
+          id +
+          " but there is some data in the buddy list for an account with this number. Your profile may be corrupted."
+      );
     }
 
     /* Actually create the new account. */
     let key = kAccountKeyPrefix + id;
     let account = new imAccount(key, aName, aPrpl);
 
     /* Save the account list pref. */
     let list = this._accountList;
     this._accountList = list ? list + "," + key : key;
 
     Services.obs.notifyObservers(account, "account-added");
     return account;
   },
 
   deleteAccount(aAccountId) {
     let account = this.getAccountById(aAccountId);
-    if (!account)
+    if (!account) {
       throw Cr.NS_ERROR_INVALID_ARG;
+    }
 
     let index = this._accounts.indexOf(account);
-    if (index == -1)
+    if (index == -1) {
       throw Cr.NS_ERROR_UNEXPECTED;
+    }
 
     let id = account.numericId;
     account.remove();
     this._accounts.splice(index, 1);
     delete this._accountsById[id];
     Services.obs.notifyObservers(account, "account-removed");
 
     /* Update the account list pref. */
     let list = this._accountList;
-    this._accountList =
-      list.split(",").filter(k => k.trim() != aAccountId).join(",");
+    this._accountList = list
+      .split(",")
+      .filter(k => k.trim() != aAccountId)
+      .join(",");
   },
 
   QueryInterface: ChromeUtils.generateQI([Ci.imIAccountsService]),
   classDescription: "Accounts",
   classID: Components.ID("{a94b5427-cd8d-40cf-b47e-b67671953e70}"),
   contractID: "@mozilla.org/chat/accounts-service;1",
 };
 
--- a/chat/components/src/imCommands.js
+++ b/chat/components/src/imCommands.js
@@ -1,84 +1,96 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-var {Services} = ChromeUtils.import("resource:///modules/imServices.jsm");
+var { Services } = ChromeUtils.import("resource:///modules/imServices.jsm");
 var {
   XPCOMUtils,
   setTimeout,
   clearTimeout,
   executeSoon,
   nsSimpleEnumerator,
   EmptyEnumerator,
   ClassInfo,
   l10nHelper,
   initLogModule,
 } = ChromeUtils.import("resource:///modules/imXPCOMUtils.jsm");
 
 XPCOMUtils.defineLazyGetter(this, "_", () =>
   l10nHelper("chrome://chat/locale/commands.properties")
 );
 
-function CommandsService() { }
+function CommandsService() {}
 CommandsService.prototype = {
   initCommands() {
     this._commands = {};
     // The say command is directly implemented in the UI layer, but has a
     // dummy command registered here so it shows up as a command (e.g. when
     // using the /help command).
     this.registerCommand({
       name: "say",
-      get helpString() { return _("sayHelpString"); },
+      get helpString() {
+        return _("sayHelpString");
+      },
       usageContext: Ci.imICommand.CMD_CONTEXT_ALL,
       priority: Ci.imICommand.CMD_PRIORITY_HIGH,
       run(aMsg, aConv) {
         throw Cr.NS_ERROR_NOT_IMPLEMENTED;
       },
     });
 
     this.registerCommand({
       name: "raw",
-      get helpString() { return _("rawHelpString"); },
+      get helpString() {
+        return _("rawHelpString");
+      },
       usageContext: Ci.imICommand.CMD_CONTEXT_ALL,
       priority: Ci.imICommand.CMD_PRIORITY_DEFAULT,
       run(aMsg, aConv) {
         let conv = Services.conversations.getUIConversation(aConv);
-        if (!conv)
+        if (!conv) {
           return false;
+        }
         conv.sendMsg(aMsg);
         return true;
       },
     });
 
     this.registerCommand({
       // Reference the command service so we can use the internal properties
       // directly.
       cmdSrv: this,
 
       name: "help",
-      get helpString() { return _("helpHelpString"); },
+      get helpString() {
+        return _("helpHelpString");
+      },
       usageContext: Ci.imICommand.CMD_CONTEXT_ALL,
       priority: Ci.imICommand.CMD_PRIORITY_DEFAULT,
       run(aMsg, aConv) {
         aMsg = aMsg.trim();
         let conv = Services.conversations.getUIConversation(aConv);
-        if (!conv)
+        if (!conv) {
           return false;
+        }
 
         // Handle when no command is given, list all possible commands that are
         // available for this conversation (alphabetically).
         if (!aMsg) {
           let commands = this.cmdSrv.listCommandsForConversation(aConv, {});
-          if (!commands.length)
+          if (!commands.length) {
             return false;
+          }
 
           // Concatenate the command names (separated by a comma and space).
-          let cmds = commands.map(aCmd => aCmd.name).sort().join(", ");
+          let cmds = commands
+            .map(aCmd => aCmd.name)
+            .sort()
+            .join(", ");
           let message = _("commands", cmds);
 
           // Display the message
           conv.systemMessage(message);
           return true;
         }
 
         // A command name was given, find the commands that match.
@@ -90,18 +102,19 @@ CommandsService.prototype = {
           conv.systemMessage(message);
           return true;
         }
 
         // Only show the help for the one of the highest priority.
         let cmd = cmdArray[0];
 
         let text = cmd.helpString;
-        if (!text)
+        if (!text) {
           text = _("noHelp", cmd.name);
+        }
 
         // Display the message.
         conv.systemMessage(text);
         return true;
       },
     });
 
     // Status commands
@@ -111,147 +124,171 @@ CommandsService.prototype = {
       busy: "UNAVAILABLE",
       dnd: "UNAVAILABLE",
       offline: "OFFLINE",
     };
     for (let cmd in status) {
       let statusValue = Ci.imIStatusInfo["STATUS_" + status[cmd]];
       this.registerCommand({
         name: cmd,
-        get helpString() { return _("statusCommand", this.name, _(this.name)); },
+        get helpString() {
+          return _("statusCommand", this.name, _(this.name));
+        },
         usageContext: Ci.imICommand.CMD_CONTEXT_ALL,
         priority: Ci.imICommand.CMD_PRIORITY_HIGH,
         run(aMsg) {
           Services.core.globalUserStatus.setStatus(statusValue, aMsg);
           return true;
         },
       });
     }
   },
   unInitCommands() {
     delete this._commands;
   },
 
   registerCommand(aCommand, aPrplId) {
     let name = aCommand.name;
-    if (!name)
+    if (!name) {
       throw Cr.NS_ERROR_INVALID_ARG;
+    }
 
-    if (!(this._commands.hasOwnProperty(name)))
+    if (!this._commands.hasOwnProperty(name)) {
       this._commands[name] = {};
+    }
     this._commands[name][aPrplId || ""] = aCommand;
   },
   unregisterCommand(aCommandName, aPrplId) {
     if (this._commands.hasOwnProperty(aCommandName)) {
       let prplId = aPrplId || "";
       let commands = this._commands[aCommandName];
-      if (commands.hasOwnProperty(prplId))
+      if (commands.hasOwnProperty(prplId)) {
         delete commands[prplId];
-      if (!Object.keys(commands).length)
+      }
+      if (!Object.keys(commands).length) {
         delete this._commands[aCommandName];
+      }
     }
   },
   listCommandsForConversation(aConversation, commandCount) {
     let result = [];
     let prplId = aConversation && aConversation.account.protocol.id;
     for (let name in this._commands) {
       let commands = this._commands[name];
-      if (commands.hasOwnProperty(""))
+      if (commands.hasOwnProperty("")) {
         result.push(commands[""]);
-      if (prplId && commands.hasOwnProperty(prplId))
+      }
+      if (prplId && commands.hasOwnProperty(prplId)) {
         result.push(commands[prplId]);
+      }
     }
-    if (aConversation)
+    if (aConversation) {
       result = result.filter(this._usageContextFilter(aConversation));
+    }
     commandCount.value = result.length;
     return result;
   },
   // List only the commands for a protocol (excluding the global commands).
   listCommandsForProtocol(aPrplId, commandCount) {
-    if (!aPrplId)
+    if (!aPrplId) {
       throw new Error("You must provide a prpl ID.");
+    }
 
     let result = [];
     for (let name in this._commands) {
       let commands = this._commands[name];
-      if (commands.hasOwnProperty(aPrplId))
+      if (commands.hasOwnProperty(aPrplId)) {
         result.push(commands[aPrplId]);
+      }
     }
     commandCount.value = result.length;
     return result;
   },
   _usageContextFilter(aConversation) {
     let usageContext =
       Ci.imICommand["CMD_CONTEXT_" + (aConversation.isChat ? "CHAT" : "IM")];
     return c => c.usageContext & usageContext;
   },
   _findCommands(aConversation, aName) {
     let prplId = null;
     if (aConversation) {
       let account = aConversation.account;
-      if (account.connected)
+      if (account.connected) {
         prplId = account.protocol.id;
+      }
     }
 
     let commandNames;
     // If there is an exact match for the given command name,
     // don't look at any other commands.
-    if (this._commands.hasOwnProperty(aName))
+    if (this._commands.hasOwnProperty(aName)) {
       commandNames = [aName];
+    }
     // Otherwise, check if there is a partial match.
     else {
-      commandNames = Object.keys(this._commands)
-                           .filter(command => command.startsWith(aName));
+      commandNames = Object.keys(this._commands).filter(command =>
+        command.startsWith(aName)
+      );
     }
 
     // If a single full command name matches the given (partial)
     // command name, return the results for that command name. Otherwise,
     // return an empty array (don't assume a certain command).
     let cmdArray = [];
     for (let commandName of commandNames) {
       let matches = [];
 
       // Get the 2 possible commands (the global and the proto specific).
       let commands = this._commands[commandName];
-      if (commands.hasOwnProperty(""))
+      if (commands.hasOwnProperty("")) {
         matches.push(commands[""]);
-      if (prplId && commands.hasOwnProperty(prplId))
+      }
+      if (prplId && commands.hasOwnProperty(prplId)) {
         matches.push(commands[prplId]);
+      }
 
       // Remove the commands that can't apply in this context.
-      if (aConversation)
+      if (aConversation) {
         matches = matches.filter(this._usageContextFilter(aConversation));
+      }
 
-      if (!matches.length)
+      if (!matches.length) {
         continue;
+      }
 
       // If we have found a second matching command name, return the empty array.
-      if (cmdArray.length)
+      if (cmdArray.length) {
         return [];
+      }
 
       cmdArray = matches;
     }
 
     // Sort the matching commands by priority before returning the array.
     return cmdArray.sort((a, b) => b.priority - a.priority);
   },
   executeCommand(aMessage, aConversation, aReturnedConv) {
-    if (!aMessage)
+    if (!aMessage) {
       throw Cr.NS_ERROR_INVALID_ARG;
+    }
 
     let matchResult;
-    if (aMessage[0] != "/" ||
-        !(matchResult = /^\/([a-z0-9]+)(?: |$)([\s\S]*)/.exec(aMessage)))
+    if (
+      aMessage[0] != "/" ||
+      !(matchResult = /^\/([a-z0-9]+)(?: |$)([\s\S]*)/.exec(aMessage))
+    ) {
       return false;
+    }
 
     let [, name, args] = matchResult;
 
     let cmdArray = this._findCommands(aConversation, name);
-    if (!cmdArray.length)
+    if (!cmdArray.length) {
       return false;
+    }
 
     // cmdArray contains commands sorted by priority, attempt to apply
     // them in order until one succeeds.
     if (!cmdArray.some(aCmd => aCmd.run(args, aConversation, aReturnedConv))) {
       // If they all failed, print help message.
       this.executeCommand("/help " + name, aConversation);
     }
     return true;
--- a/chat/components/src/imContacts.js
+++ b/chat/components/src/imContacts.js
@@ -1,13 +1,13 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-var {Services} = ChromeUtils.import("resource:///modules/imServices.jsm");
+var { Services } = ChromeUtils.import("resource:///modules/imServices.jsm");
 var {
   XPCOMUtils,
   setTimeout,
   clearTimeout,
   executeSoon,
   nsSimpleEnumerator,
   EmptyEnumerator,
   ClassInfo,
@@ -16,40 +16,41 @@ var {
 } = ChromeUtils.import("resource:///modules/imXPCOMUtils.jsm");
 
 XPCOMUtils.defineLazyGetter(this, "_", () =>
   l10nHelper("chrome://chat/locale/contacts.properties")
 );
 
 var gDBConnection = null;
 
-function executeAsyncThenFinalize(statement)
-{
+function executeAsyncThenFinalize(statement) {
   statement.executeAsync();
   statement.finalize();
 }
 
-function getDBConnection()
-{
+function getDBConnection() {
   const NS_APP_USER_PROFILE_50_DIR = "ProfD";
   let dbFile = Services.dirsvc.get(NS_APP_USER_PROFILE_50_DIR, Ci.nsIFile);
   dbFile.append("blist.sqlite");
 
   let conn = Services.storage.openDatabase(dbFile);
-  if (!conn.connectionReady)
+  if (!conn.connectionReady) {
     throw Cr.NS_ERROR_UNEXPECTED;
+  }
 
   // Grow blist db in 512KB increments.
   try {
     conn.setGrowthIncrement(512 * 1024, "");
   } catch (e) {
     if (e.result == Cr.NS_ERROR_FILE_TOO_BIG) {
-      Services.console.logStringMessage("Not setting growth increment on " +
-                                        "blist.sqlite because the available " +
-                                        "disk space is limited");
+      Services.console.logStringMessage(
+        "Not setting growth increment on " +
+          "blist.sqlite because the available " +
+          "disk space is limited"
+      );
     } else {
       throw e;
     }
   }
 
   // Create tables and indexes.
   [
     "CREATE TABLE IF NOT EXISTS accounts (" +
@@ -105,18 +106,19 @@ function getDBConnection()
 // committed automatically at the end of the event loop spin so that
 // we flush buddy list data to disk only once per event loop spin.
 var gDBConnWithPendingTransaction = null;
 Object.defineProperty(this, "DBConn", {
   configurable: true,
   enumerable: true,
 
   get() {
-    if (gDBConnWithPendingTransaction)
+    if (gDBConnWithPendingTransaction) {
       return gDBConnWithPendingTransaction;
+    }
 
     if (!gDBConnection) {
       gDBConnection = getDBConnection();
       Services.obs.addObserver(function dbClose(aSubject, aTopic, aData) {
         Services.obs.removeObserver(dbClose, aTopic);
         if (gDBConnection) {
           gDBConnection.asyncClose();
           gDBConnection = null;
@@ -128,207 +130,253 @@ Object.defineProperty(this, "DBConn", {
     executeSoon(function() {
       gDBConnWithPendingTransaction.commitTransaction();
       gDBConnWithPendingTransaction = null;
     });
     return gDBConnection;
   },
 });
 
-function TagsService() { }
+function TagsService() {}
 TagsService.prototype = {
-  get wrappedJSObject() { return this; },
-  get defaultTag() { return this.createTag(_("defaultGroup")); },
+  get wrappedJSObject() {
+    return this;
+  },
+  get defaultTag() {
+    return this.createTag(_("defaultGroup"));
+  },
   createTag(aName) {
     // If the tag already exists, we don't want to create a duplicate.
     let tag = this.getTagByName(aName);
-    if (tag)
+    if (tag) {
       return tag;
+    }
 
-    let statement = DBConn.createStatement("INSERT INTO tags (name, position) VALUES(:name, 0)");
+    let statement = DBConn.createStatement(
+      "INSERT INTO tags (name, position) VALUES(:name, 0)"
+    );
     try {
       statement.params.name = aName;
       statement.executeStep();
     } finally {
       statement.finalize();
     }
 
     tag = new Tag(DBConn.lastInsertRowID, aName);
     Tags.push(tag);
     return tag;
   },
   // Get an existing tag by (numeric) id. Returns null if not found.
   getTagById: aId => TagsById[aId],
   // Get an existing tag by name (will do an SQL query). Returns null
   // if not found.
   getTagByName(aName) {
-    let statement = DBConn.createStatement("SELECT id FROM tags where name = :name");
+    let statement = DBConn.createStatement(
+      "SELECT id FROM tags where name = :name"
+    );
     statement.params.name = aName;
     try {
-      if (!statement.executeStep())
+      if (!statement.executeStep()) {
         return null;
+      }
       return this.getTagById(statement.row.id);
     } finally {
       statement.finalize();
     }
   },
   // Get an array of all existing tags.
   getTags(aTagCount) {
-    if (Tags.length)
-      Tags.sort((a, b) => a.name.toLowerCase().localeCompare(b.name.toLowerCase()));
-    else
+    if (Tags.length) {
+      Tags.sort((a, b) =>
+        a.name.toLowerCase().localeCompare(b.name.toLowerCase())
+      );
+    } else {
       this.defaultTag;
+    }
 
-    if (aTagCount)
+    if (aTagCount) {
       aTagCount.value = Tags.length;
+    }
     return Tags;
   },
 
   isTagHidden: aTag => aTag.id in otherContactsTag._hiddenTags,
-  hideTag(aTag) { otherContactsTag.hideTag(aTag); },
-  showTag(aTag) { otherContactsTag.showTag(aTag); },
+  hideTag(aTag) {
+    otherContactsTag.hideTag(aTag);
+  },
+  showTag(aTag) {
+    otherContactsTag.showTag(aTag);
+  },
   get otherContactsTag() {
     otherContactsTag._initContacts();
     return otherContactsTag;
   },
 
   QueryInterface: ChromeUtils.generateQI([Ci.imITagsService]),
   classDescription: "Tags",
   classID: Components.ID("{1fa92237-4303-4384-b8ac-4e65b50810a5}"),
   contractID: "@mozilla.org/chat/tags-service;1",
 };
 
 // TODO move into the tagsService
 var Tags = [];
-var TagsById = { };
+var TagsById = {};
 
 function Tag(aId, aName) {
   this._id = aId;
   this._name = aName;
   this._contacts = [];
   this._observers = [];
 
   TagsById[this.id] = this;
 }
 Tag.prototype = {
   __proto__: ClassInfo("imITag", "Tag"),
-  get id() { return this._id; },
-  get name() { return this._name; },
+  get id() {
+    return this._id;
+  },
+  get name() {
+    return this._name;
+  },
   set name(aNewName) {
-    let statement = DBConn.createStatement("UPDATE tags SET name = :name WHERE id = :id");
+    let statement = DBConn.createStatement(
+      "UPDATE tags SET name = :name WHERE id = :id"
+    );
     try {
       statement.params.name = aNewName;
       statement.params.id = this._id;
       statement.execute();
     } finally {
       statement.finalize();
     }
 
     // FIXME move the account buddies if some use this tag as their group
     return aNewName;
   },
   getContacts(aContactCount) {
     let contacts = this._contacts.filter(c => !c._empty);
-    if (aContactCount)
+    if (aContactCount) {
       aContactCount.value = contacts.length;
+    }
     return contacts;
   },
   _addContact(aContact) {
     this._contacts.push(aContact);
   },
   _removeContact(aContact) {
     let index = this._contacts.indexOf(aContact);
-    if (index != -1)
+    if (index != -1) {
       this._contacts.splice(index, 1);
+    }
   },
 
   addObserver(aObserver) {
-    if (!this._observers.includes(aObserver))
+    if (!this._observers.includes(aObserver)) {
       this._observers.push(aObserver);
+    }
   },
   removeObserver(aObserver) {
     this._observers = this._observers.filter(o => o !== aObserver);
   },
   notifyObservers(aSubject, aTopic, aData) {
-    for (let observer of this._observers)
+    for (let observer of this._observers) {
       observer.observe(aSubject, aTopic, aData);
+    }
   },
 };
 
-
 var otherContactsTag = {
   __proto__: ClassInfo(["nsIObserver", "imITag"], "Other Contacts Tag"),
   hiddenTagsPref: "messenger.buddies.hiddenTags",
   _hiddenTags: {},
   _contactsInitialized: false,
   _saveHiddenTagsPref() {
-    Services.prefs.setCharPref(this.hiddenTagsPref,
-                               Object.keys(this._hiddenTags).join(","));
+    Services.prefs.setCharPref(
+      this.hiddenTagsPref,
+      Object.keys(this._hiddenTags).join(",")
+    );
   },
   showTag(aTag) {
     let id = aTag.id;
     delete this._hiddenTags[id];
     let contacts = Object.keys(this._contacts).map(id => this._contacts[id]);
-    for (let contact of contacts)
-      if (contact.getTags().some(t => t.id == id))
+    for (let contact of contacts) {
+      if (contact.getTags().some(t => t.id == id)) {
         this._removeContact(contact);
+      }
+    }
 
     aTag.notifyObservers(aTag, "tag-shown");
     Services.obs.notifyObservers(aTag, "tag-shown");
     this._saveHiddenTagsPref();
   },
   hideTag(aTag) {
-    if (aTag.id < 0 || aTag.id in otherContactsTag._hiddenTags)
+    if (aTag.id < 0 || aTag.id in otherContactsTag._hiddenTags) {
       return;
+    }
 
     this._hiddenTags[aTag.id] = aTag;
-    if (this._contactsInitialized)
+    if (this._contactsInitialized) {
       this._hideTag(aTag);
+    }
 
     aTag.notifyObservers(aTag, "tag-hidden");
     Services.obs.notifyObservers(aTag, "tag-hidden");
     this._saveHiddenTagsPref();
   },
   _hideTag(aTag) {
-    for (let contact of aTag.getContacts())
-      if (!(contact.id in this._contacts) &&
-          contact.getTags().every(t => t.id in this._hiddenTags))
+    for (let contact of aTag.getContacts()) {
+      if (
+        !(contact.id in this._contacts) &&
+        contact.getTags().every(t => t.id in this._hiddenTags)
+      ) {
         this._addContact(contact);
+      }
+    }
   },
   observe(aSubject, aTopic, aData) {
     aSubject.QueryInterface(Ci.imIContact);
     if (aTopic == "contact-tag-removed" || aTopic == "contact-added") {
-      if (!(aSubject.id in this._contacts) &&
-          !(parseInt(aData) in this._hiddenTags) &&
-          aSubject.getTags().every(t => t.id in this._hiddenTags))
+      if (
+        !(aSubject.id in this._contacts) &&
+        !(parseInt(aData) in this._hiddenTags) &&
+        aSubject.getTags().every(t => t.id in this._hiddenTags)
+      ) {
         this._addContact(aSubject);
+      }
+    } else if (
+      aSubject.id in this._contacts &&
+      (aTopic == "contact-removed" ||
+        (aTopic == "contact-tag-added" &&
+          !(parseInt(aData) in this._hiddenTags)))
+    ) {
+      this._removeContact(aSubject);
     }
-    else if (aSubject.id in this._contacts &&
-             (aTopic == "contact-removed" ||
-              (aTopic == "contact-tag-added" &&
-              !(parseInt(aData) in this._hiddenTags))))
-      this._removeContact(aSubject);
   },
 
   _initHiddenTags() {
     let pref = Services.prefs.getCharPref(this.hiddenTagsPref);
-    if (!pref)
+    if (!pref) {
       return;
-    for (let tagId of pref.split(","))
+    }
+    for (let tagId of pref.split(",")) {
       this._hiddenTags[tagId] = TagsById[tagId];
+    }
   },
   _initContacts() {
-    if (this._contactsInitialized)
+    if (this._contactsInitialized) {
       return;
+    }
     this._observers = [];
     this._observer = {
       self: this,
       observe(aSubject, aTopic, aData) {
-        if (aTopic == "contact-moved-in" && !(aSubject instanceof Contact))
+        if (aTopic == "contact-moved-in" && !(aSubject instanceof Contact)) {
           return;
+        }
 
         this.self.notifyObservers(aSubject, aTopic, aData);
       },
     };
     this._contacts = {};
     this._contactsInitialized = true;
     for (let id in this._hiddenTags) {
       let tag = this._hiddenTags[id];
@@ -336,539 +384,662 @@ var otherContactsTag = {
     }
     Services.obs.addObserver(this, "contact-tag-added");
     Services.obs.addObserver(this, "contact-tag-removed");
     Services.obs.addObserver(this, "contact-added");
     Services.obs.addObserver(this, "contact-removed");
   },
 
   // imITag implementation
-  get id() { return -1; },
-  get name() { return "__others__"; },
-  set name(aNewName) { throw Cr.NS_ERROR_NOT_AVAILABLE; },
+  get id() {
+    return -1;
+  },
+  get name() {
+    return "__others__";
+  },
+  set name(aNewName) {
+    throw Cr.NS_ERROR_NOT_AVAILABLE;
+  },
   getContacts(aContactCount) {
     let contacts = Object.keys(this._contacts).map(id => this._contacts[id]);
-    if (aContactCount)
+    if (aContactCount) {
       aContactCount.value = contacts.length;
+    }
     return contacts;
   },
   _addContact(aContact) {
     this._contacts[aContact.id] = aContact;
     this.notifyObservers(aContact, "contact-moved-in");
-    for (let observer of ContactsById[aContact.id]._observers)
+    for (let observer of ContactsById[aContact.id]._observers) {
       observer.observe(this, "contact-moved-in", null);
+    }
     aContact.addObserver(this._observer);
   },
   _removeContact(aContact) {
     delete this._contacts[aContact.id];
     aContact.removeObserver(this._observer);
     this.notifyObservers(aContact, "contact-moved-out");
-    for (let observer of ContactsById[aContact.id]._observers)
+    for (let observer of ContactsById[aContact.id]._observers) {
       observer.observe(this, "contact-moved-out", null);
+    }
   },
 
   addObserver(aObserver) {
-    if (!this._observers.includes(aObserver))
+    if (!this._observers.includes(aObserver)) {
       this._observers.push(aObserver);
+    }
   },
   removeObserver(aObserver) {
     this._observers = this._observers.filter(o => o !== aObserver);
   },
   notifyObservers(aSubject, aTopic, aData) {
-    for (let observer of this._observers)
+    for (let observer of this._observers) {
       observer.observe(aSubject, aTopic, aData);
+    }
   },
 };
 
-
-var ContactsById = { };
+var ContactsById = {};
 var LastDummyContactId = 0;
 function Contact(aId, aAlias) {
   // Assign a negative id to dummy contacts that have a single buddy
   this._id = aId || --LastDummyContactId;
   this._alias = aAlias;
   this._tags = [];
   this._buddies = [];
   this._observers = [];
 
   ContactsById[this._id] = this;
 }
 Contact.prototype = {
   __proto__: ClassInfo("imIContact", "Contact"),
   _id: 0,
-  get id() { return this._id; },
-  get alias() { return this._alias; },
+  get id() {
+    return this._id;
+  },
+  get alias() {
+    return this._alias;
+  },
   set alias(aNewAlias) {
     this._ensureNotDummy();
 
-    let statement = DBConn.createStatement("UPDATE contacts SET alias = :alias WHERE id = :id");
+    let statement = DBConn.createStatement(
+      "UPDATE contacts SET alias = :alias WHERE id = :id"
+    );
     statement.params.alias = aNewAlias;
     statement.params.id = this._id;
     executeAsyncThenFinalize(statement);
 
     let oldDisplayName = this.displayName;
     this._alias = aNewAlias;
     this._notifyObservers("display-name-changed", oldDisplayName);
     for (let buddy of this._buddies) {
-      for (let accountBuddy of buddy._accounts)
+      for (let accountBuddy of buddy._accounts) {
         accountBuddy.serverAlias = aNewAlias;
+      }
     }
     return aNewAlias;
   },
   _ensureNotDummy() {
-    if (this._id >= 0)
+    if (this._id >= 0) {
       return;
+    }
 
     // Create a real contact for this dummy contact
-    let statement = DBConn.createStatement("INSERT INTO contacts DEFAULT VALUES");
+    let statement = DBConn.createStatement(
+      "INSERT INTO contacts DEFAULT VALUES"
+    );
     try {
       statement.execute();
     } finally {
       statement.finalize();
     }
     delete ContactsById[this._id];
     let oldId = this._id;
     this._id = DBConn.lastInsertRowID;
     ContactsById[this._id] = this;
     this._notifyObservers("no-longer-dummy", oldId.toString());
     // Update the contact_id for the single existing buddy of this contact
-    statement = DBConn.createStatement("UPDATE buddies SET contact_id = :id WHERE id = :buddy_id");
+    statement = DBConn.createStatement(
+      "UPDATE buddies SET contact_id = :id WHERE id = :buddy_id"
+    );
     statement.params.id = this._id;
     statement.params.buddy_id = this._buddies[0].id;
     executeAsyncThenFinalize(statement);
   },
 
   getTags(aTagCount) {
-    if (aTagCount)
+    if (aTagCount) {
       aTagCount.value = this._tags.length;
+    }
     return this._tags;
   },
   addTag(aTag, aInherited) {
-    if (this.hasTag(aTag))
+    if (this.hasTag(aTag)) {
       return;
+    }
 
     if (!aInherited) {
       this._ensureNotDummy();
-      let statement =
-        DBConn.createStatement("INSERT INTO contact_tag (contact_id, tag_id) " +
-                               "VALUES(:contactId, :tagId)");
+      let statement = DBConn.createStatement(
+        "INSERT INTO contact_tag (contact_id, tag_id) " +
+          "VALUES(:contactId, :tagId)"
+      );
       statement.params.contactId = this.id;
       statement.params.tagId = aTag.id;
       executeAsyncThenFinalize(statement);
     }
 
     aTag = TagsById[aTag.id];
     this._tags.push(aTag);
     aTag._addContact(this);
 
     aTag.notifyObservers(this, "contact-moved-in");
-    for (let observer of this._observers)
+    for (let observer of this._observers) {
       observer.observe(aTag, "contact-moved-in", null);
+    }
     Services.obs.notifyObservers(this, "contact-tag-added", aTag.id);
   },
   /* Remove a tag from the local tags of the contact. */
   _removeTag(aTag) {
-    if (!this.hasTag(aTag) || this._isTagInherited(aTag))
+    if (!this.hasTag(aTag) || this._isTagInherited(aTag)) {
       return;
+    }
 
     this._removeContactTagRow(aTag);
 
     this._tags = this._tags.filter(tag => tag.id != aTag.id);
     aTag = TagsById[aTag.id];
     aTag._removeContact(this);
 
     aTag.notifyObservers(this, "contact-moved-out");
-    for (let observer of this._observers)
+    for (let observer of this._observers) {
       observer.observe(aTag, "contact-moved-out", null);
+    }
     Services.obs.notifyObservers(this, "contact-tag-removed", aTag.id);
   },
   _removeContactTagRow(aTag) {
-    let statement = DBConn.createStatement("DELETE FROM contact_tag " +
-                                           "WHERE contact_id = :contactId " +
-                                           "AND tag_id = :tagId");
+    let statement = DBConn.createStatement(
+      "DELETE FROM contact_tag " +
+        "WHERE contact_id = :contactId " +
+        "AND tag_id = :tagId"
+    );
     statement.params.contactId = this.id;
     statement.params.tagId = aTag.id;
     executeAsyncThenFinalize(statement);
   },
-  hasTag(aTag) { return this._tags.some((t => t.id == aTag.id)); },
+  hasTag(aTag) {
+    return this._tags.some(t => t.id == aTag.id);
+  },
   _massMove: false,
   removeTag(aTag) {
-    if (!this.hasTag(aTag))
-      throw new Error("Attempting to remove a tag that the contact doesn't have");
-    if (this._tags.length == 1)
+    if (!this.hasTag(aTag)) {
+      throw new Error(
+        "Attempting to remove a tag that the contact doesn't have"
+      );
+    }
+    if (this._tags.length == 1) {
       throw new Error("Attempting to remove the last tag of a contact");
+    }
 
     this._massMove = true;
     let hasTag = this.hasTag.bind(this);
     let newTag = this._tags[this._tags[0].id != aTag.id ? 0 : 1];
     let moved = false;
     this._buddies.forEach(function(aBuddy) {
       aBuddy._accounts.forEach(function(aAccountBuddy) {
         if (aAccountBuddy.tag.id == aTag.id) {
-          if (aBuddy._accounts.some(ab =>
-               ab.account.numericId == aAccountBuddy.account.numericId &&
-               ab.tag.id != aTag.id && hasTag(ab.tag))) {
+          if (
+            aBuddy._accounts.some(
+              ab =>
+                ab.account.numericId == aAccountBuddy.account.numericId &&
+                ab.tag.id != aTag.id &&
+                hasTag(ab.tag)
+            )
+          ) {
             // A buddy that already has an accountBuddy of the same
             // account with another tag of the contact shouldn't be
             // moved to newTag, just remove the accountBuddy
             // associated to the tag we are removing.
             aAccountBuddy.remove();
             moved = true;
-          }
-          else {
+          } else {
             try {
               aAccountBuddy.tag = newTag;
               moved = true;
             } catch (e) {
               // Ignore failures. Some protocol plugins may not implement this.
             }
           }
         }
       });
     });
     this._massMove = false;
-    if (moved)
+    if (moved) {
       this._moved(aTag, newTag);
-    else {
+    } else {
       // If we are here, the old tag is not inherited from a buddy, so
       // just remove the local tag.
       this._removeTag(aTag);
     }
   },
   _isTagInherited(aTag) {
     for (let buddy of this._buddies) {
-      for (let accountBuddy of buddy._accounts)
-        if (accountBuddy.tag.id == aTag.id)
+      for (let accountBuddy of buddy._accounts) {
+        if (accountBuddy.tag.id == aTag.id) {
           return true;
+        }
+      }
     }
     return false;
   },
   _moved(aOldTag, aNewTag) {
-    if (this._massMove)
+    if (this._massMove) {
       return;
+    }
 
     // Avoid xpconnect wrappers.
     aNewTag = aNewTag && TagsById[aNewTag.id];
     aOldTag = aOldTag && TagsById[aOldTag.id];
 
     // Decide what we need to do. Return early if nothing to do.
     let shouldRemove =
       aOldTag && this.hasTag(aOldTag) && !this._isTagInherited(aOldTag);
     let shouldAdd =
       aNewTag && !this.hasTag(aNewTag) && this._isTagInherited(aNewTag);
-    if (!shouldRemove && !shouldAdd)
+    if (!shouldRemove && !shouldAdd) {
       return;
+    }
 
     // Apply the changes.
     let tags = this._tags;
     if (shouldRemove) {
       tags = tags.filter(aTag => aTag.id != aOldTag.id);
       aOldTag._removeContact(this);
     }
     if (shouldAdd) {
       tags.push(aNewTag);
       aNewTag._addContact(this);
     }
     this._tags = tags;
 
     // Finally, notify of the changes.
     if (shouldRemove) {
       aOldTag.notifyObservers(this, "contact-moved-out");
-      for (let observer of this._observers)
+      for (let observer of this._observers) {
         observer.observe(aOldTag, "contact-moved-out", null);
+      }
       Services.obs.notifyObservers(this, "contact-tag-removed", aOldTag.id);
     }
     if (shouldAdd) {
       aNewTag.notifyObservers(this, "contact-moved-in");
-      for (let observer of this._observers)
+      for (let observer of this._observers) {
         observer.observe(aNewTag, "contact-moved-in", null);
+      }
       Services.obs.notifyObservers(this, "contact-tag-added", aNewTag.id);
     }
     Services.obs.notifyObservers(this, "contact-moved");
   },
 
   getBuddies(aBuddyCount) {
-    if (aBuddyCount)
+    if (aBuddyCount) {
       aBuddyCount.value = this._buddies.length;
+    }
     return this._buddies;
   },
   get _empty() {
-    return this._buddies.length == 0 ||
-           this._buddies.every(b => b._empty);
+    return this._buddies.length == 0 || this._buddies.every(b => b._empty);
   },
 
   mergeContact(aContact) {
     // Avoid merging the contact with itself or merging into an
     // already removed contact.
-    if (aContact.id == this.id || !(this.id in ContactsById))
+    if (aContact.id == this.id || !(this.id in ContactsById)) {
       throw Cr.NS_ERROR_INVALID_ARG;
+    }
 
     this._ensureNotDummy();
     let contact = ContactsById[aContact.id]; // remove XPConnect wrapper
 
     // Copy all the contact-only tags first, otherwise they would be lost.
-    for (let tag of contact.getTags())
-      if (!contact._isTagInherited(tag))
+    for (let tag of contact.getTags()) {
+      if (!contact._isTagInherited(tag)) {
         this.addTag(tag);
+      }
+    }
 
     // Adopt each buddy. Removing the last one will delete the contact.
-    for (let buddy of contact.getBuddies())
+    for (let buddy of contact.getBuddies()) {
       buddy.contact = this;
+    }
     this._updatePreferredBuddy();
   },
   moveBuddyBefore(aBuddy, aBeforeBuddy) {
     let buddy = BuddiesById[aBuddy.id]; // remove XPConnect wrapper
     let oldPosition = this._buddies.indexOf(buddy);
-    if (oldPosition == -1)
+    if (oldPosition == -1) {
       throw new Error("aBuddy isn't attached to this contact");
+    }
 
     let newPosition = -1;
-    if (aBeforeBuddy)
+    if (aBeforeBuddy) {
       newPosition = this._buddies.indexOf(BuddiesById[aBeforeBuddy.id]);
-    if (newPosition == -1)
+    }
+    if (newPosition == -1) {
       newPosition = this._buddies.length - 1;
+    }
 
-    if (oldPosition == newPosition)
+    if (oldPosition == newPosition) {
       return;
+    }
 
     this._buddies.splice(oldPosition, 1);
     this._buddies.splice(newPosition, 0, buddy);
-    this._updatePositions(Math.min(oldPosition, newPosition),
-                          Math.max(oldPosition, newPosition));
+    this._updatePositions(
+      Math.min(oldPosition, newPosition),
+      Math.max(oldPosition, newPosition)
+    );
     buddy._notifyObservers("position-changed", String(newPosition));
     this._updatePreferredBuddy(buddy);
   },
   adoptBuddy(aBuddy) {
-    if (aBuddy.contact.id == this.id)
+    if (aBuddy.contact.id == this.id) {
       throw Cr.NS_ERROR_INVALID_ARG;
+    }
 
     let buddy = BuddiesById[aBuddy.id]; // remove XPConnect wrapper
     buddy.contact = this;
     this._updatePreferredBuddy(buddy);
   },
   _massRemove: false,
   _removeBuddy(aBuddy) {
     if (this._buddies.length == 1) {
       if (this._id > 0) {
-        let statement =
-          DBConn.createStatement("DELETE FROM contacts WHERE id = :id");
+        let statement = DBConn.createStatement(
+          "DELETE FROM contacts WHERE id = :id"
+        );
         statement.params.id = this._id;
         executeAsyncThenFinalize(statement);
       }
       this._notifyObservers("removed");
       delete ContactsById[this._id];
 
-      for (let tag of this._tags)
+      for (let tag of this._tags) {
         tag._removeContact(this);
-      let statement =
-        DBConn.createStatement("DELETE FROM contact_tag WHERE contact_id = :id");
+      }
+      let statement = DBConn.createStatement(
+        "DELETE FROM contact_tag WHERE contact_id = :id"
+      );
       statement.params.id = this._id;
       executeAsyncThenFinalize(statement);
 
       delete this._tags;
       delete this._buddies;
       delete this._observers;
-    }
-    else {
+    } else {
       let index = this._buddies.indexOf(aBuddy);
-      if (index == -1)
+      if (index == -1) {
         throw new Error("Removing an unknown buddy from contact " + this._id);
+      }
 
       this._buddies = this._buddies.filter(b => b !== aBuddy);
 
       // If we are actually removing the whole contact, don't bother updating
       // the positions or the preferred buddy.
-      if (this._massRemove)
+      if (this._massRemove) {
         return;
+      }
 
       // No position to update if the removed buddy is at the last position.
-      if (index < this._buddies.length)
+      if (index < this._buddies.length) {
         this._updatePositions(index);
+      }
 
-      if (this._preferredBuddy.id == aBuddy.id)
+      if (this._preferredBuddy.id == aBuddy.id) {
         this._updatePreferredBuddy();
+      }
     }
   },
   _updatePositions(aIndexBegin, aIndexEnd) {
-    if (aIndexEnd === undefined)
+    if (aIndexEnd === undefined) {
       aIndexEnd = this._buddies.length - 1;
-    if (aIndexBegin > aIndexEnd)
+    }
+    if (aIndexBegin > aIndexEnd) {
       throw new Error("_updatePositions: Invalid indexes");
+    }
 
-    let statement =
-      DBConn.createStatement("UPDATE buddies SET position = :position " +
-                             "WHERE id = :buddyId");
+    let statement = DBConn.createStatement(
+      "UPDATE buddies SET position = :position " + "WHERE id = :buddyId"
+    );
     for (let i = aIndexBegin; i <= aIndexEnd; ++i) {
       statement.params.position = i;
       statement.params.buddyId = this._buddies[i].id;
       statement.executeAsync();
     }
     statement.finalize();
   },
 
   detachBuddy(aBuddy) {
     // Should return a new contact with the same list of tags.
     let buddy = BuddiesById[aBuddy.id];
-    if (buddy.contact.id != this.id)
+    if (buddy.contact.id != this.id) {
       throw Cr.NS_ERROR_INVALID_ARG;
-    if (buddy.contact._buddies.length == 1)
+    }
+    if (buddy.contact._buddies.length == 1) {
       throw Cr.NS_ERROR_UNEXPECTED;
+    }
 
     // Save the list of tags, it may be destoyed if the buddy was the last one.
     let tags = buddy.contact.getTags();
 
     // Create a new dummy contact and use it for the detached buddy.
     buddy.contact = new Contact();
     buddy.contact._notifyObservers("added");
 
     // The first tag was inherited during the contact setter.
     // This will copy the remaining tags.
-    for (let tag of tags)
+    for (let tag of tags) {
       buddy.contact.addTag(tag);
+    }
 
     return buddy.contact;
   },
   remove() {
     this._massRemove = true;
-    for (let buddy of this._buddies)
+    for (let buddy of this._buddies) {
       buddy.remove();
+    }
   },
 
   // imIStatusInfo implementation
   _preferredBuddy: null,
   get preferredBuddy() {
-    if (!this._preferredBuddy)
+    if (!this._preferredBuddy) {
       this._updatePreferredBuddy();
+    }
     return this._preferredBuddy;
   },
   set preferredBuddy(aBuddy) {
     let shouldNotify = this._preferredBuddy != null;
     let oldDisplayName =
       this._preferredBuddy && this._preferredBuddy.displayName;
     this._preferredBuddy = aBuddy;
-    if (shouldNotify)
+    if (shouldNotify) {
       this._notifyObservers("preferred-buddy-changed");
-    if (oldDisplayName && this._preferredBuddy.displayName != oldDisplayName)
+    }
+    if (oldDisplayName && this._preferredBuddy.displayName != oldDisplayName) {
       this._notifyObservers("display-name-changed", oldDisplayName);
+    }
     this._updateStatus();
   },
   // aBuddy indicate which buddy's availability has changed.
   _updatePreferredBuddy(aBuddy) {
     if (aBuddy) {
       aBuddy = BuddiesById[aBuddy.id]; // remove potential XPConnect wrapper
 
       if (!this._preferredBuddy) {
         this.preferredBuddy = aBuddy;
         return;
       }
 
       if (aBuddy.id == this._preferredBuddy.id) {
         // The suggested buddy is already preferred, check if its
         // availability has changed.
-        if (aBuddy.statusType > this._statusType ||
-            (aBuddy.statusType == this._statusType &&
-             aBuddy.availabilityDetails >= this._availabilityDetails)) {
+        if (
+          aBuddy.statusType > this._statusType ||
+          (aBuddy.statusType == this._statusType &&
+            aBuddy.availabilityDetails >= this._availabilityDetails)
+        ) {
           // keep the currently preferred buddy, only update the status.
           this._updateStatus();
           return;
         }
         // We aren't sure that the currently preferred buddy should
         // still be preferred. Let's go through the list!
-      }
-      else {
+      } else {
         // The suggested buddy is not currently preferred. If it is
         // more available or at a better position, prefer it!
-        if (aBuddy.statusType > this._statusType ||
-            (aBuddy.statusType == this._statusType &&
-             (aBuddy.availabilityDetails > this._availabilityDetails ||
+        if (
+          aBuddy.statusType > this._statusType ||
+          (aBuddy.statusType == this._statusType &&
+            (aBuddy.availabilityDetails > this._availabilityDetails ||
               (aBuddy.availabilityDetails == this._availabilityDetails &&
-               this._buddies.indexOf(aBuddy) < this._buddies.indexOf(this.preferredBuddy)))))
+                this._buddies.indexOf(aBuddy) <
+                  this._buddies.indexOf(this.preferredBuddy))))
+        ) {
           this.preferredBuddy = aBuddy;
+        }
         return;
       }
     }
 
     let preferred;
     // |this._buddies| is ordered by user preference, so in case of
     // equal availability, keep the current value of |preferred|.
     for (let buddy of this._buddies) {
-      if (!preferred || preferred.statusType < buddy.statusType ||
-          (preferred.statusType == buddy.statusType &&
-           preferred.availabilityDetails < buddy.availabilityDetails))
+      if (
+        !preferred ||
+        preferred.statusType < buddy.statusType ||
+        (preferred.statusType == buddy.statusType &&
+          preferred.availabilityDetails < buddy.availabilityDetails)
+      ) {
         preferred = buddy;
+      }
     }
-    if (preferred && (!this._preferredBuddy ||
-                      preferred.id != this._preferredBuddy.id))
+    if (
+      preferred &&
+      (!this._preferredBuddy || preferred.id != this._preferredBuddy.id)
+    ) {
       this.preferredBuddy = preferred;
+    }
   },
   _updateStatus() {
     let buddy = this._preferredBuddy; // for convenience
 
     // Decide which notifications should be fired.
     let notifications = [];
-    if (this._statusType != buddy.statusType ||
-        this._availabilityDetails != buddy.availabilityDetails)
+    if (
+      this._statusType != buddy.statusType ||
+      this._availabilityDetails != buddy.availabilityDetails
+    ) {
       notifications.push("availability-changed");
-    if (this._statusType != buddy.statusType ||
-        this._statusText != buddy.statusText) {
+    }
+    if (
+      this._statusType != buddy.statusType ||
+      this._statusText != buddy.statusText
+    ) {
       notifications.push("status-changed");
-      if (this.online && buddy.statusType <= Ci.imIStatusInfo.STATUS_OFFLINE)
+      if (this.online && buddy.statusType <= Ci.imIStatusInfo.STATUS_OFFLINE) {
         notifications.push("signed-off");
-      if (!this.online && buddy.statusType > Ci.imIStatusInfo.STATUS_OFFLINE)
+      }
+      if (!this.online && buddy.statusType > Ci.imIStatusInfo.STATUS_OFFLINE) {
         notifications.push("signed-on");
+      }
     }
 
     // Actually change the stored status.
-    [this._statusType, this._statusText, this._availabilityDetails] =
-      [buddy.statusType, buddy.statusText, buddy.availabilityDetails];
+    [this._statusType, this._statusText, this._availabilityDetails] = [
+      buddy.statusType,
+      buddy.statusText,
+      buddy.availabilityDetails,
+    ];
 
     // Fire the notifications.
     notifications.forEach(function(aTopic) {
       this._notifyObservers(aTopic);
     }, this);
   },
-  get displayName() { return this._alias || this.preferredBuddy.displayName; },
-  get buddyIconFilename() { return this.preferredBuddy.buddyIconFilename; },
+  get displayName() {
+    return this._alias || this.preferredBuddy.displayName;
+  },
+  get buddyIconFilename() {
+    return this.preferredBuddy.buddyIconFilename;
+  },
   _statusType: 0,
-  get statusType() { return this._statusType; },
-  get online() { return this.statusType > Ci.imIStatusInfo.STATUS_OFFLINE; },
-  get available() { return this.statusType == Ci.imIStatusInfo.STATUS_AVAILABLE; },
-  get idle() { return this.statusType == Ci.imIStatusInfo.STATUS_IDLE; },
-  get mobile() { return this.statusType == Ci.imIStatusInfo.STATUS_MOBILE; },
+  get statusType() {
+    return this._statusType;
+  },
+  get online() {
+    return this.statusType > Ci.imIStatusInfo.STATUS_OFFLINE;
+  },
+  get available() {
+    return this.statusType == Ci.imIStatusInfo.STATUS_AVAILABLE;
+  },
+  get idle() {
+    return this.statusType == Ci.imIStatusInfo.STATUS_IDLE;
+  },
+  get mobile() {
+    return this.statusType == Ci.imIStatusInfo.STATUS_MOBILE;
+  },
   _statusText: "",
-  get statusText() { return this._statusText; },
+  get statusText() {
+    return this._statusText;
+  },
   _availabilityDetails: 0,
-  get availabilityDetails() { return this._availabilityDetails; },
-  get canSendMessage() { return this.preferredBuddy.canSendMessage; },
+  get availabilityDetails() {
+    return this._availabilityDetails;
+  },
+  get canSendMessage() {
+    return this.preferredBuddy.canSendMessage;
+  },
   // XXX should we list the buddies in the tooltip?
-  getTooltipInfo() { return this.preferredBuddy.getTooltipInfo(); },
+  getTooltipInfo() {
+    return this.preferredBuddy.getTooltipInfo();
+  },
   createConversation() {
     let uiConv = Services.conversations.getUIConversationByContactId(this.id);
-    if (uiConv)
+    if (uiConv) {
       return uiConv.target;
+    }
     return this.preferredBuddy.createConversation();
   },
 
   addObserver(aObserver) {
-    if (!this._observers.includes(aObserver))
+    if (!this._observers.includes(aObserver)) {
       this._observers.push(aObserver);
+    }
   },
   removeObserver(aObserver) {
-    if (!this.hasOwnProperty("_observers"))
+    if (!this.hasOwnProperty("_observers")) {
       return;
+    }
 
     this._observers = this._observers.filter(o => o !== aObserver);
   },
   // internal calls + calls from add-ons
   notifyObservers(aSubject, aTopic, aData) {
-    for (let observer of this._observers)
-      if ("observe" in observer) // avoid failing on destructed XBL bindings...
+    for (let observer of this._observers) {
+      if ("observe" in observer) {
+        // avoid failing on destructed XBL bindings...
         observer.observe(aSubject, aTopic, aData);
-    for (let tag of this._tags)
+      }
+    }
+    for (let tag of this._tags) {
       tag.notifyObservers(aSubject, aTopic, aData);
+    }
     Services.obs.notifyObservers(aSubject, aTopic, aData);
   },
   _notifyObservers(aTopic, aData) {
     this.notifyObservers(this, "contact-" + aTopic, aData);
   },
 
   // This is called by the imIBuddy implementations.
   _observe(aSubject, aTopic, aData) {
@@ -877,261 +1048,355 @@ Contact.prototype = {
 
     let isPreferredBuddy =
       aSubject instanceof Buddy && aSubject.id == this.preferredBuddy.id;
     switch (aTopic) {
       case "buddy-availability-changed":
         this._updatePreferredBuddy(aSubject);
         break;
       case "buddy-status-changed":
-        if (isPreferredBuddy)
+        if (isPreferredBuddy) {
           this._updateStatus();
+        }
         break;
       case "buddy-display-name-changed":
-        if (isPreferredBuddy && !this._alias)
+        if (isPreferredBuddy && !this._alias) {
           this._notifyObservers("display-name-changed", aData);
+        }
         break;
       case "buddy-icon-changed":
-        if (isPreferredBuddy)
+        if (isPreferredBuddy) {
           this._notifyObservers("icon-changed");
+        }
         break;
       case "buddy-added":
         // Currently buddies are always added in dummy empty contacts,
         // later we may want to check this._buddies.length == 1.
         this._notifyObservers("added");
         break;
       case "buddy-removed":
         this._removeBuddy(aSubject);
     }
   },
 };
 
-var BuddiesById = { };
+var BuddiesById = {};
 function Buddy(aId, aKey, aName, aSrvAlias, aContactId) {
   this._id = aId;
   this._key = aKey;
   this._name = aName;
-  if (aSrvAlias)
+  if (aSrvAlias) {
     this._srvAlias = aSrvAlias;
+  }
   this._accounts = [];
   this._observers = [];
 
-  if (aContactId)
+  if (aContactId) {
     this._contact = ContactsById[aContactId];
+  }
   // Avoid failure if aContactId was invalid.
-  if (!this._contact)
+  if (!this._contact) {
     this._contact = new Contact(null, null);
+  }
 
   this._contact._buddies.push(this);
 
   BuddiesById[this._id] = this;
 }
 Buddy.prototype = {
   __proto__: ClassInfo("imIBuddy", "Buddy"),
-  get id() { return this._id; },
+  get id() {
+    return this._id;
+  },
   destroy() {
-    for (let ab of this._accounts)
+    for (let ab of this._accounts) {
       ab.unInit();
+    }
     delete this._accounts;
     delete this._observers;
     delete this._preferredAccount;
   },
-  get protocol() { return this._accounts[0].account.protocol; },
-  get userName() { return this._name; },
-  get normalizedName() { return this._key; },
+  get protocol() {
+    return this._accounts[0].account.protocol;
+  },
+  get userName() {
+    return this._name;
+  },
+  get normalizedName() {
+    return this._key;
+  },
   _srvAlias: "",
   _contact: null,
-  get contact() { return this._contact; },
+  get contact() {
+    return this._contact;
+  },
   set contact(aContact) /* not in imIBuddy */ {
-    if (aContact.id == this._contact.id)
+    if (aContact.id == this._contact.id) {
       throw Cr.NS_ERROR_INVALID_ARG;
+    }
 
     this._notifyObservers("moved-out-of-contact");
     this._contact._removeBuddy(this);
 
     this._contact = aContact;
     this._contact._buddies.push(this);
 
     // Ensure all the inherited tags are in the new contact.
-    for (let accountBuddy of this._accounts)
+    for (let accountBuddy of this._accounts) {
       this._contact.addTag(TagsById[accountBuddy.tag.id], true);
+    }
 
-    let statement =
-      DBConn.createStatement("UPDATE buddies SET contact_id = :contactId, " +
-                             "position = :position " +
-                             "WHERE id = :buddyId");
+    let statement = DBConn.createStatement(
+      "UPDATE buddies SET contact_id = :contactId, " +
+        "position = :position " +
+        "WHERE id = :buddyId"
+    );
     statement.params.contactId = aContact.id > 0 ? aContact.id : 0;
     statement.params.position = aContact._buddies.length - 1;
     statement.params.buddyId = this.id;
     executeAsyncThenFinalize(statement);
 
     this._notifyObservers("moved-into-contact");
     return aContact;
   },
   _hasAccountBuddy(aAccountId, aTagId) {
     for (let ab of this._accounts) {
-      if (ab.account.numericId == aAccountId && ab.tag.id == aTagId)
+      if (ab.account.numericId == aAccountId && ab.tag.id == aTagId) {
         return true;
+      }
     }
     return false;
   },
   getAccountBuddies(aAccountBuddyCount) {
-    if (aAccountBuddyCount)
+    if (aAccountBuddyCount) {
       aAccountBuddyCount.value = this._accounts.length;
+    }
     return this._accounts;
   },
 
   _addAccount(aAccountBuddy, aTag) {
     this._accounts.push(aAccountBuddy);
     let contact = this._contact;
     if (!this._contact._tags.includes(aTag)) {
       this._contact._tags.push(aTag);
       aTag._addContact(contact);
     }
 
-    if (!this._preferredAccount)
+    if (!this._preferredAccount) {
       this._preferredAccount = aAccountBuddy;
+    }
   },
-  get _empty() { return this._accounts.length == 0; },
+  get _empty() {
+    return this._accounts.length == 0;
+  },
 
   remove() {
-    for (let account of this._accounts)
+    for (let account of this._accounts) {
       account.remove();
+    }
   },
 
   // imIStatusInfo implementation
   _preferredAccount: null,
-  get preferredAccountBuddy() { return this._preferredAccount; },
+  get preferredAccountBuddy() {
+    return this._preferredAccount;
+  },
   _isPreferredAccount(aAccountBuddy) {
-    if (aAccountBuddy.account.numericId != this._preferredAccount.account.numericId)
+    if (
+      aAccountBuddy.account.numericId !=
+      this._preferredAccount.account.numericId
+    ) {
       return false;
+    }
 
     // In case we have more than one accountBuddy for the same buddy
     // and account (possible if the buddy is in several groups on the
     // server), the protocol plugin may be broken and not update all
     // instances, so ensure we handle the notifications on the instance
     // that is currently being notified of a change:
     this._preferredAccount = aAccountBuddy;
 
     return true;
   },
   set preferredAccount(aAccount) {
     let oldDisplayName =
       this._preferredAccount && this._preferredAccount.displayName;
     this._preferredAccount = aAccount;
     this._notifyObservers("preferred-account-changed");
-    if (oldDisplayName && this._preferredAccount.displayName != oldDisplayName)
+    if (
+      oldDisplayName &&
+      this._preferredAccount.displayName != oldDisplayName
+    ) {
       this._notifyObservers("display-name-changed", oldDisplayName);
+    }
     this._updateStatus();
   },
   // aAccount indicate which account's availability has changed.
   _updatePreferredAccount(aAccount) {
     if (aAccount) {
-      if (aAccount.account.numericId == this._preferredAccount.account.numericId) {
+      if (
+        aAccount.account.numericId == this._preferredAccount.account.numericId
+      ) {
         // The suggested account is already preferred, check if its
         // availability has changed.
-        if (aAccount.statusType > this._statusType ||
-            (aAccount.statusType == this._statusType &&
-             aAccount.availabilityDetails >= this._availabilityDetails)) {
+        if (
+          aAccount.statusType > this._statusType ||
+          (aAccount.statusType == this._statusType &&
+            aAccount.availabilityDetails >= this._availabilityDetails)
+        ) {
           // keep the currently preferred account, only update the status.
           this._updateStatus();
           return;
         }
         // We aren't sure that the currently preferred account should
         // still be preferred. Let's go through the list!
-      }
-      else {
+      } else {
         // The suggested account is not currently preferred. If it is
         // more available, prefer it!
-        if (aAccount.statusType > this._statusType ||
-            (aAccount.statusType == this._statusType &&
-             aAccount.availabilityDetails > this._availabilityDetails))
+        if (
+          aAccount.statusType > this._statusType ||
+          (aAccount.statusType == this._statusType &&
+            aAccount.availabilityDetails > this._availabilityDetails)
+        ) {
           this.preferredAccount = aAccount;
+        }
         return;
       }
     }
 
     let preferred;
     // TODO take into account the order of the account-manager list.
     for (let account of this._accounts) {
-      if (!preferred || preferred.statusType < account.statusType ||
-          (preferred.statusType == account.statusType &&
-           preferred.availabilityDetails < account.availabilityDetails))
+      if (
+        !preferred ||
+        preferred.statusType < account.statusType ||
+        (preferred.statusType == account.statusType &&
+          preferred.availabilityDetails < account.availabilityDetails)
+      ) {
         preferred = account;
+      }
     }
     if (!this._preferredAccount) {
-      if (preferred)
+      if (preferred) {
         this.preferredAccount = preferred;
+      }
       return;
     }
-    if (preferred.account.numericId != this._preferredAccount.account.numericId)
+    if (
+      preferred.account.numericId != this._preferredAccount.account.numericId
+    ) {
       this.preferredAccount = preferred;
-    else
+    } else {
       this._updateStatus();
+    }
   },
   _updateStatus() {
     let account = this._preferredAccount; // for convenience
 
     // Decide which notifications should be fired.
     let notifications = [];
-    if (this._statusType != account.statusType ||
-        this._availabilityDetails != account.availabilityDetails)
+    if (
+      this._statusType != account.statusType ||
+      this._availabilityDetails != account.availabilityDetails
+    ) {
       notifications.push("availability-changed");
-    if (this._statusType != account.statusType ||
-        this._statusText != account.statusText) {
+    }
+    if (
+      this._statusType != account.statusType ||
+      this._statusText != account.statusText
+    ) {
       notifications.push("status-changed");
-      if (this.online && account.statusType <= Ci.imIStatusInfo.STATUS_OFFLINE)
+      if (
+        this.online &&
+        account.statusType <= Ci.imIStatusInfo.STATUS_OFFLINE
+      ) {
         notifications.push("signed-off");
-      if (!this.online && account.statusType > Ci.imIStatusInfo.STATUS_OFFLINE)
+      }
+      if (
+        !this.online &&
+        account.statusType > Ci.imIStatusInfo.STATUS_OFFLINE
+      ) {
         notifications.push("signed-on");
+      }
     }
 
     // Actually change the stored status.
-    [this._statusType, this._statusText, this._availabilityDetails] =
-      [account.statusType, account.statusText, account.availabilityDetails];
+    [this._statusType, this._statusText, this._availabilityDetails] = [
+      account.statusType,
+      account.statusText,
+      account.availabilityDetails,
+    ];
 
     // Fire the notifications.
     notifications.forEach(function(aTopic) {
       this._notifyObservers(aTopic);
     }, this);
   },
   get displayName() {
-    return this._preferredAccount && this._preferredAccount.displayName ||
-           this._srvAlias || this._name;
+    return (
+      (this._preferredAccount && this._preferredAccount.displayName) ||
+      this._srvAlias ||
+      this._name
+    );
   },
-  get buddyIconFilename() { return this._preferredAccount.buddyIconFilename; },
+  get buddyIconFilename() {
+    return this._preferredAccount.buddyIconFilename;
+  },
   _statusType: 0,
-  get statusType() { return this._statusType; },
-  get online() { return this.statusType > Ci.imIStatusInfo.STATUS_OFFLINE; },
-  get available() { return this.statusType == Ci.imIStatusInfo.STATUS_AVAILABLE; },
-  get idle() { return this.statusType == Ci.imIStatusInfo.STATUS_IDLE; },
-  get mobile() { return this.statusType == Ci.imIStatusInfo.STATUS_MOBILE; },
+  get statusType() {
+    return this._statusType;
+  },
+  get online() {
+    return this.statusType > Ci.imIStatusInfo.STATUS_OFFLINE;
+  },
+  get available() {
+    return this.statusType == Ci.imIStatusInfo.STATUS_AVAILABLE;
+  },
+  get idle() {
+    return this.statusType == Ci.imIStatusInfo.STATUS_IDLE;
+  },
+  get mobile() {
+    return this.statusType == Ci.imIStatusInfo.STATUS_MOBILE;
+  },
   _statusText: "",
-  get statusText() { return this._statusText; },
+  get statusText() {
+    return this._statusText;
+  },
   _availabilityDetails: 0,
-  get availabilityDetails() { return this._availabilityDetails; },
-  get canSendMessage() { return this._preferredAccount.canSendMessage; },
+  get availabilityDetails() {
+    return this._availabilityDetails;
+  },
+  get canSendMessage() {
+    return this._preferredAccount.canSendMessage;
+  },
   // XXX should we list the accounts in the tooltip?
-  getTooltipInfo() { return this._preferredAccount.getTooltipInfo(); },
-  createConversation() { return this._preferredAccount.createConversation(); },
+  getTooltipInfo() {
+    return this._preferredAccount.getTooltipInfo();
+  },
+  createConversation() {
+    return this._preferredAccount.createConversation();
+  },
 
   addObserver(aObserver) {
-    if (!this._observers.includes(aObserver))
+    if (!this._observers.includes(aObserver)) {
       this._observers.push(aObserver);
+    }
   },
   removeObserver(aObserver) {
-    if (!this._observers)
+    if (!this._observers) {
       return;
+    }
     this._observers = this._observers.filter(o => o !== aObserver);
   },
   // internal calls + calls from add-ons
   notifyObservers(aSubject, aTopic, aData) {
     try {
-      for (let observer of this._observers)
+      for (let observer of this._observers) {
         observer.observe(aSubject, aTopic, aData);
+      }
       this._contact._observe(aSubject, aTopic, aData);
     } catch (e) {
       Cu.reportError(e);
     }
   },
   _notifyObservers(aTopic, aData) {
     this.notifyObservers(this, "buddy-" + aTopic, aData);
   },
@@ -1141,146 +1406,173 @@ Buddy.prototype = {
     // Forward the notification.
     this.notifyObservers(aSubject, aTopic, aData);
 
     switch (aTopic) {
       case "account-buddy-availability-changed":
         this._updatePreferredAccount(aSubject);
         break;
       case "account-buddy-status-changed":
-        if (this._isPreferredAccount(aSubject))
+        if (this._isPreferredAccount(aSubject)) {
           this._updateStatus();
+        }
         break;
       case "account-buddy-display-name-changed":
         if (this._isPreferredAccount(aSubject)) {
           this._srvAlias =
             this.displayName != this.userName ? this.displayName : "";
-          let statement =
-            DBConn.createStatement("UPDATE buddies SET srv_alias = :srvAlias " +
-                                   "WHERE id = :buddyId");
+          let statement = DBConn.createStatement(
+            "UPDATE buddies SET srv_alias = :srvAlias " + "WHERE id = :buddyId"
+          );
           statement.params.buddyId = this.id;
           statement.params.srvAlias = this._srvAlias;
           executeAsyncThenFinalize(statement);
           this._notifyObservers("display-name-changed", aData);
         }
         break;
       case "account-buddy-icon-changed":
-        if (this._isPreferredAccount(aSubject))
+        if (this._isPreferredAccount(aSubject)) {
           this._notifyObservers("icon-changed");
+        }
         break;
       case "account-buddy-added":
         if (this._accounts.length == 0) {
           // Add the new account in the empty buddy instance.
           // The TagsById hack is to bypass the xpconnect wrapper.
           this._addAccount(aSubject, TagsById[aSubject.tag.id]);
           this._updateStatus();
           this._notifyObservers("added");
-        }
-        else {
+        } else {
           this._accounts.push(aSubject);
           this.contact._moved(null, aSubject.tag);
           this._updatePreferredAccount(aSubject);
         }
         break;
       case "account-buddy-removed":
         if (this._accounts.length == 1) {
-          let statement =
-            DBConn.createStatement("DELETE FROM buddies WHERE id = :id");
+          let statement = DBConn.createStatement(
+            "DELETE FROM buddies WHERE id = :id"
+          );
           try {
             statement.params.id = this.id;
             statement.execute();
           } finally {
             statement.finalize();
           }
           this._notifyObservers("removed");
 
           delete BuddiesById[this._id];
           this.destroy();
-        }
-        else {
+        } else {
           this._accounts = this._accounts.filter(function(ab) {
-            return (ab.account.numericId != aSubject.account.numericId ||
-                    ab.tag.id != aSubject.tag.id);
+            return (
+              ab.account.numericId != aSubject.account.numericId ||
+              ab.tag.id != aSubject.tag.id
+            );
           });
-          if (this._preferredAccount.account.numericId == aSubject.account.numericId &&
-              this._preferredAccount.tag.id == aSubject.tag.id) {
+          if (
+            this._preferredAccount.account.numericId ==
+              aSubject.account.numericId &&
+            this._preferredAccount.tag.id == aSubject.tag.id
+          ) {
             this._preferredAccount = null;
             this._updatePreferredAccount();
           }
           this.contact._moved(aSubject.tag);
         }
         break;
     }
   },
 };
 
-
-function ContactsService() { }
+function ContactsService() {}
 ContactsService.prototype = {
   initContacts() {
     let statement = DBConn.createStatement("SELECT id, name FROM tags");
     try {
-      while (statement.executeStep())
+      while (statement.executeStep()) {
         Tags.push(new Tag(statement.getInt32(0), statement.getUTF8String(1)));
+      }
     } finally {
       statement.finalize();
     }
 
     statement = DBConn.createStatement("SELECT id, alias FROM contacts");
     try {
-      while (statement.executeStep())
+      while (statement.executeStep()) {
         new Contact(statement.getInt32(0), statement.getUTF8String(1));
+      }
     } finally {
       statement.finalize();
     }
 
-    statement =
-      DBConn.createStatement("SELECT contact_id, tag_id FROM contact_tag");
+    statement = DBConn.createStatement(
+      "SELECT contact_id, tag_id FROM contact_tag"
+    );
     try {
       while (statement.executeStep()) {
         let contact = ContactsById[statement.getInt32(0)];
         let tag = TagsById[statement.getInt32(1)];
         contact._tags.push(tag);
         tag._addContact(contact);
       }
     } finally {
       statement.finalize();
     }
 
-    statement = DBConn.createStatement("SELECT id, key, name, srv_alias, contact_id FROM buddies ORDER BY position");
+    statement = DBConn.createStatement(
+      "SELECT id, key, name, srv_alias, contact_id FROM buddies ORDER BY position"
+    );
     try {
       while (statement.executeStep()) {
-        new Buddy(statement.getInt32(0), statement.getUTF8String(1),
-                statement.getUTF8String(2), statement.getUTF8String(3),
-                statement.getInt32(4));
+        new Buddy(
+          statement.getInt32(0),
+          statement.getUTF8String(1),
+          statement.getUTF8String(2),
+          statement.getUTF8String(3),
+          statement.getInt32(4)
+        );
         // FIXME is there a way to enforce that all AccountBuddies of a Buddy have the same protocol?
       }
     } finally {
       statement.finalize();
     }
 
-    statement = DBConn.createStatement("SELECT account_id, buddy_id, tag_id FROM account_buddy");
+    statement = DBConn.createStatement(
+      "SELECT account_id, buddy_id, tag_id FROM account_buddy"
+    );
     try {
       while (statement.executeStep()) {
         let accountId = statement.getInt32(0);
         let buddyId = statement.getInt32(1);
         let tagId = statement.getInt32(2);
 
         if (!BuddiesById.hasOwnProperty(buddyId)) {
-          Cu.reportError("Corrupted database: account_buddy entry for account " +
-                         accountId + " and tag " + tagId +
-                         " references unknown buddy with id " + buddyId);
+          Cu.reportError(
+            "Corrupted database: account_buddy entry for account " +
+              accountId +
+              " and tag " +
+              tagId +
+              " references unknown buddy with id " +
+              buddyId
+          );
           continue;
         }
 
         let buddy = BuddiesById[buddyId];
         if (buddy._hasAccountBuddy(accountId, tagId)) {
-          Cu.reportError("Corrupted database: duplicated account_buddy entry: " +
-                         "account_id = " + accountId + ", buddy_id = " + buddyId +
-                         ", tag_id = " + tagId);
+          Cu.reportError(
+            "Corrupted database: duplicated account_buddy entry: " +
+              "account_id = " +
+              accountId +
+              ", buddy_id = " +
+              buddyId +
+              ", tag_id = " +
+              tagId
+          );
           continue;
         }
 
         let account = Services.accounts.getAccountByNumericId(accountId);
         let tag = TagsById[tagId];
         try {
           buddy._addAccount(account.loadBuddy(buddy, tag), tag);
         } catch (e) {
@@ -1290,147 +1582,170 @@ ContactsService.prototype = {
       }
     } finally {
       statement.finalize();
     }
     otherContactsTag._initHiddenTags();
   },
   unInitContacts() {
     Tags = [];
-    TagsById = { };
+    TagsById = {};
     // Avoid shutdown leaks caused by references to native components
     // implementing prplIAccountBuddy.
     for (let buddyId in BuddiesById) {
       let buddy = BuddiesById[buddyId];
       buddy.destroy();
     }
-    BuddiesById = { };
-    ContactsById = { };
+    BuddiesById = {};
+    ContactsById = {};
   },
 
   getContactById: aId => ContactsById[aId],
   // Get an array of all existing contacts.
   getContacts(aContactCount) {
     let contacts = Object.keys(ContactsById)
-                         .filter(id => !ContactsById[id]._empty)
-                         .map(id => ContactsById[id]);
-    if (aContactCount)
+      .filter(id => !ContactsById[id]._empty)
+      .map(id => ContactsById[id]);
+    if (aContactCount) {
       aContactCount.value = contacts.length;
+    }
     return contacts;
   },
   getBuddyById: aId => BuddiesById[aId],
   getBuddyByNameAndProtocol(aNormalizedName, aPrpl) {
-    let statement =
-      DBConn.createStatement("SELECT b.id FROM buddies b " +
-                             "JOIN account_buddy ab ON buddy_id = b.id " +
-                             "JOIN accounts a ON account_id = a.id " +
-                             "WHERE b.key = :buddyName and a.prpl = :prplId");
+    let statement = DBConn.createStatement(
+      "SELECT b.id FROM buddies b " +
+        "JOIN account_buddy ab ON buddy_id = b.id " +
+        "JOIN accounts a ON account_id = a.id " +
+        "WHERE b.key = :buddyName and a.prpl = :prplId"
+    );
     statement.params.buddyName = aNormalizedName;
     statement.params.prplId = aPrpl.id;
     try {
-      if (!statement.executeStep())
+      if (!statement.executeStep()) {
         return null;
+      }
       return BuddiesById[statement.row.id];
     } finally {
       statement.finalize();
     }
   },
   getAccountBuddyByNameAndAccount(aNormalizedName, aAccount) {
-    let buddy = this.getBuddyByNameAndProtocol(aNormalizedName,
-                                               aAccount.protocol);
+    let buddy = this.getBuddyByNameAndProtocol(
+      aNormalizedName,
+      aAccount.protocol
+    );
     if (buddy) {
       let id = aAccount.id;
       for (let accountBuddy of buddy.getAccountBuddies()) {
-        if (accountBuddy.account.id == id)
+        if (accountBuddy.account.id == id) {
           return accountBuddy;
+        }
       }
     }
     return null;
   },
 
   accountBuddyAdded(aAccountBuddy) {
     let account = aAccountBuddy.account;
     let normalizedName = aAccountBuddy.normalizedName;
-    let buddy = this.getBuddyByNameAndProtocol(normalizedName, account.protocol);
+    let buddy = this.getBuddyByNameAndProtocol(
+      normalizedName,
+      account.protocol
+    );
     if (!buddy) {
-      let statement =
-        DBConn.createStatement("INSERT INTO buddies " +
-                               "(key, name, srv_alias, position) " +
-                               "VALUES(:key, :name, :srvAlias, 0)");
+      let statement = DBConn.createStatement(
+        "INSERT INTO buddies " +
+          "(key, name, srv_alias, position) " +
+          "VALUES(:key, :name, :srvAlias, 0)"
+      );
       try {
         let name = aAccountBuddy.userName;
         let srvAlias = aAccountBuddy.serverAlias;
         statement.params.key = normalizedName;
         statement.params.name = name;
         statement.params.srvAlias = srvAlias;
         statement.execute();
-        buddy =
-          new Buddy(DBConn.lastInsertRowID, normalizedName, name, srvAlias, 0);
+        buddy = new Buddy(
+          DBConn.lastInsertRowID,
+          normalizedName,
+          name,
+          srvAlias,
+          0
+        );
       } finally {
         statement.finalize();
       }
     }
 
     // Initialize the 'buddy' field of the prplIAccountBuddy instance.
     aAccountBuddy.buddy = buddy;
 
     // Ensure we aren't storing a duplicate entry.
     let accountId = account.numericId;
     let tagId = aAccountBuddy.tag.id;
     if (buddy._hasAccountBuddy(accountId, tagId)) {
-      Cu.reportError("Attempting to store a duplicate account buddy " +
-                     normalizedName + ", account id = " + accountId +
-                     ", tag id = " + tagId);
+      Cu.reportError(
+        "Attempting to store a duplicate account buddy " +
+          normalizedName +
+          ", account id = " +
+          accountId +
+          ", tag id = " +
+          tagId
+      );
       return;
     }
 
     // Store the new account buddy.
-    let statement =
-      DBConn.createStatement("INSERT INTO account_buddy " +
-                             "(account_id, buddy_id, tag_id) " +
-                             "VALUES(:accountId, :buddyId, :tagId)");
+    let statement = DBConn.createStatement(
+      "INSERT INTO account_buddy " +
+        "(account_id, buddy_id, tag_id) " +
+        "VALUES(:accountId, :buddyId, :tagId)"
+    );
     try {
       statement.params.accountId = accountId;
       statement.params.buddyId = buddy.id;
       statement.params.tagId = tagId;
       statement.execute();
     } finally {
       statement.finalize();
     }
 
     // Fire the notifications.
     buddy.observe(aAccountBuddy, "account-buddy-added");
   },
   accountBuddyRemoved(aAccountBuddy) {
     let buddy = aAccountBuddy.buddy;
-    let statement =
-      DBConn.createStatement("DELETE FROM account_buddy " +
-                                    "WHERE account_id = :accountId AND " +
-                                          "buddy_id = :buddyId AND " +
-                                          "tag_id = :tagId");
+    let statement = DBConn.createStatement(
+      "DELETE FROM account_buddy " +
+        "WHERE account_id = :accountId AND " +
+        "buddy_id = :buddyId AND " +
+        "tag_id = :tagId"
+    );
     try {
       statement.params.accountId = aAccountBuddy.account.numericId;
       statement.params.buddyId = buddy.id;
       statement.params.tagId = aAccountBuddy.tag.id;
       statement.execute();
     } finally {
       statement.finalize();
     }
 
     buddy.observe(aAccountBuddy, "account-buddy-removed");
   },
 
   accountBuddyMoved(aAccountBuddy, aOldTag, aNewTag) {
     let buddy = aAccountBuddy.buddy;
-    let statement =
-      DBConn.createStatement("UPDATE account_buddy " +
-                             "SET tag_id = :newTagId " +
-                             "WHERE account_id = :accountId AND " +
-                                   "buddy_id = :buddyId AND " +
-                                   "tag_id = :oldTagId");
+    let statement = DBConn.createStatement(
+      "UPDATE account_buddy " +
+        "SET tag_id = :newTagId " +
+        "WHERE account_id = :accountId AND " +
+        "buddy_id = :buddyId AND " +
+        "tag_id = :oldTagId"
+    );
     try {
       statement.params.accountId = aAccountBuddy.account.numericId;
       statement.params.buddyId = buddy.id;
       statement.params.oldTagId = aOldTag.id;
       statement.params.newTagId = aNewTag.id;
       statement.execute();
     } finally {
       statement.finalize();
@@ -1442,74 +1757,85 @@ ContactsService.prototype = {
     // keeping direct tag <-> contact links in the contact_tag table.
     contact._removeContactTagRow(aNewTag);
 
     buddy.observe(aAccountBuddy, "account-buddy-moved");
     contact._moved(aOldTag, aNewTag);
   },
 
   storeAccount(aId, aUserName, aPrplId) {
-    let statement =
-      DBConn.createStatement("SELECT name, prpl FROM accounts WHERE id = :id");
+    let statement = DBConn.createStatement(
+      "SELECT name, prpl FROM accounts WHERE id = :id"
+    );
     statement.params.id = aId;
     try {
       if (statement.executeStep()) {
-        if (statement.getUTF8String(0) == aUserName &&
-            statement.getUTF8String(1) == aPrplId)
-          return; // The account is already stored correctly.
+        if (
+          statement.getUTF8String(0) == aUserName &&
+          statement.getUTF8String(1) == aPrplId
+        ) {
+          return;
+        } // The account is already stored correctly.
         throw Cr.NS_ERROR_UNEXPECTED; // Corrupted database?!?
       }
     } finally {
       statement.finalize();
     }
 
     // Actually store the account.
-    statement = DBConn.createStatement("INSERT INTO accounts (id, name, prpl) " +
-                                       "VALUES(:id, :userName, :prplId)");
+    statement = DBConn.createStatement(
+      "INSERT INTO accounts (id, name, prpl) " +
+        "VALUES(:id, :userName, :prplId)"
+    );
     try {
       statement.params.id = aId;
       statement.params.userName = aUserName;
       statement.params.prplId = aPrplId;
       statement.execute();
     } finally {
       statement.finalize();
     }
   },
   accountIdExists(aId) {
-    let statement =
-      DBConn.createStatement("SELECT id FROM accounts WHERE id = :id");
+    let statement = DBConn.createStatement(
+      "SELECT id FROM accounts WHERE id = :id"
+    );
     try {
       statement.params.id = aId;
       return statement.executeStep();
     } finally {
       statement.finalize();
     }
   },
   forgetAccount(aId) {
-    let statement =
-      DBConn.createStatement("DELETE FROM accounts WHERE id = :accountId");
+    let statement = DBConn.createStatement(
+      "DELETE FROM accounts WHERE id = :accountId"
+    );
     try {
       statement.params.accountId = aId;
       statement.execute();
     } finally {
       statement.finalize();
     }
 
     // removing the account from the accounts table is not enough,
     // we need to remove all the associated account_buddy entries too
-    statement = DBConn.createStatement("DELETE FROM account_buddy " +
-                                       "WHERE account_id = :accountId");
+    statement = DBConn.createStatement(
+      "DELETE FROM account_buddy " + "WHERE account_id = :accountId"
+    );
     try {
       statement.params.accountId = aId;
       statement.execute();
     } finally {
       statement.finalize();
     }
   },
 
   QueryInterface: ChromeUtils.generateQI([Ci.imIContactsService]),
   classDescription: "Contacts",
   classID: Components.ID("{8c3725dd-ee26-489d-8135-736015af8c7f}"),
   contractID: "@mozilla.org/chat/contacts-service;1",
 };
 
-var NSGetFactory = XPCOMUtils.generateNSGetFactory([ContactsService,
-                                                      TagsService]);
+var NSGetFactory = XPCOMUtils.generateNSGetFactory([
+  ContactsService,
+  TagsService,
+]);
--- a/chat/components/src/imConversations.js
+++ b/chat/components/src/imConversations.js
@@ -1,14 +1,14 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-var {Services} = ChromeUtils.import("resource:///modules/imServices.jsm");
-var {Status} = ChromeUtils.import("resource:///modules/imStatusUtils.jsm");
+var { Services } = ChromeUtils.import("resource:///modules/imServices.jsm");
+var { Status } = ChromeUtils.import("resource:///modules/imStatusUtils.jsm");
 var {
   XPCOMUtils,
   setTimeout,
   clearTimeout,
   executeSoon,
   nsSimpleEnumerator,
   EmptyEnumerator,
   ClassInfo,
@@ -53,355 +53,470 @@ imMessage.prototype = {
   cancelled: false,
   color: "",
   _displayMessage: null,
   encrypted: false,
 
   get displayMessage() {
     // Explicitly test for null so that blank messages don't fall back to
     // the original. Especially problematic in encryption extensions like OTR.
-    return this._displayMessage !== null ?
-      this._displayMessage : this.prplMessage.originalMessage;
+    return this._displayMessage !== null
+      ? this._displayMessage
+      : this.prplMessage.originalMessage;
+  },
+  set displayMessage(aMsg) {
+    this._displayMessage = aMsg;
   },
-  set displayMessage(aMsg) { this._displayMessage = aMsg; },
 
-  get message() { return this.prplMessage.message; },
-  set message(aMsg) { this.prplMessage.message = aMsg; },
+  get message() {
+    return this.prplMessage.message;
+  },
+  set message(aMsg) {
+    this.prplMessage.message = aMsg;
+  },
 
   // from prplIMessage
-  get who() { return this.prplMessage.who; },
-  get time() { return this.prplMessage.time; },
-  get id() { return this.prplMessage.id; },
-  get alias() { return this.prplMessage.alias; },
-  get iconURL() { return this.prplMessage.iconURL; },
-  get conversation() { return this.prplMessage.conversation; },
-  set conversation(aConv) { this.prplMessage.conversation = aConv; },
-  get outgoing() { return this.prplMessage.outgoing; },
-  get incoming() { return this.prplMessage.incoming; },
-  get system() { return this.prplMessage.system; },
-  get autoResponse() { return this.prplMessage.autoResponse; },
-  get containsNick() { return this.prplMessage.containsNick; },
-  get noLog() { return this.prplMessage.noLog; },
-  get error() { return this.prplMessage.error; },
-  get delayed() { return this.prplMessage.delayed; },
-  get noFormat() { return this.prplMessage.noFormat; },
-  get containsImages() { return this.prplMessage.containsImages; },
-  get notification() { return this.prplMessage.notification; },
-  get noLinkification() { return this.prplMessage.noLinkification; },
-  get originalMessage() { return this.prplMessage.originalMessage; },
-  getActions(aCount) { return this.prplMessage.getActions(aCount || {}); },
+  get who() {
+    return this.prplMessage.who;
+  },
+  get time() {
+    return this.prplMessage.time;
+  },
+  get id() {
+    return this.prplMessage.id;
+  },
+  get alias() {
+    return this.prplMessage.alias;
+  },
+  get iconURL() {
+    return this.prplMessage.iconURL;
+  },
+  get conversation() {
+    return this.prplMessage.conversation;
+  },
+  set conversation(aConv) {
+    this.prplMessage.conversation = aConv;
+  },
+  get outgoing() {
+    return this.prplMessage.outgoing;
+  },
+  get incoming() {
+    return this.prplMessage.incoming;
+  },
+  get system() {
+    return this.prplMessage.system;
+  },
+  get autoResponse() {
+    return this.prplMessage.autoResponse;
+  },
+  get containsNick() {
+    return this.prplMessage.containsNick;
+  },
+  get noLog() {
+    return this.prplMessage.noLog;
+  },
+  get error() {
+    return this.prplMessage.error;
+  },
+  get delayed() {
+    return this.prplMessage.delayed;
+  },
+  get noFormat() {
+    return this.prplMessage.noFormat;
+  },
+  get containsImages() {
+    return this.prplMessage.containsImages;
+  },
+  get notification() {
+    return this.prplMessage.notification;
+  },
+  get noLinkification() {
+    return this.prplMessage.noLinkification;
+  },
+  get originalMessage() {
+    return this.prplMessage.originalMessage;
+  },
+  getActions(aCount) {
+    return this.prplMessage.getActions(aCount || {});
+  },
 };
 
-function UIConversation(aPrplConversation)
-{
+function UIConversation(aPrplConversation) {
   this._prplConv = {};
   this.id = ++gLastUIConvId;
   this._observers = [];
   this._messages = [];
   this.changeTargetTo(aPrplConversation);
   let iface = Ci["prplIConv" + (aPrplConversation.isChat ? "Chat" : "IM")];
   this._interfaces = this._interfaces.concat(iface);
   // XPConnect will create a wrapper around 'this' after here,
   // so the list of exposed interfaces shouldn't change anymore.
   this.updateContactObserver();
   Services.obs.notifyObservers(this, "new-ui-conversation");
 }
 
 UIConversation.prototype = {
-  __proto__: ClassInfo(["imIConversation", "prplIConversation", "nsIObserver"],
-                       "UI conversation"),
+  __proto__: ClassInfo(
+    ["imIConversation", "prplIConversation", "nsIObserver"],
+    "UI conversation"
+  ),
   _observedContact: null,
   get contact() {
     let target = this.target;
-    if (!target.isChat && target.buddy)
+    if (!target.isChat && target.buddy) {
       return target.buddy.buddy.contact;
+    }
     return null;
   },
   updateContactObserver() {
     let contact = this.contact;
     if (contact && !this._observedContact) {
       contact.addObserver(this);
       this._observedContact = contact;
-    }
-    else if (!contact && this.observedContact) {
+    } else if (!contact && this.observedContact) {
       this._observedContact.removeObserver(this);
       delete this._observedContact;
     }
   },
-  get target() { return this._prplConv[this._currentTargetId]; },
+  get target() {
+    return this._prplConv[this._currentTargetId];
+  },
   set target(aPrplConversation) {
     this.changeTargetTo(aPrplConversation);
   },
-  get hasMultipleTargets() { return Object.keys(this._prplConv).length > 1; },
+  get hasMultipleTargets() {
+    return Object.keys(this._prplConv).length > 1;
+  },
   getTargetByAccount(aAccount) {
     let accountId = aAccount.id;
     for (let id in this._prplConv) {
       let prplConv = this._prplConv[id];
-      if (prplConv.account.id == accountId)
+      if (prplConv.account.id == accountId) {
         return prplConv;
+      }
     }
     return null;
   },
   _currentTargetId: 0,
   changeTargetTo(aPrplConversation) {
     let id = aPrplConversation.id;
-    if (this._currentTargetId == id)
+    if (this._currentTargetId == id) {
       return;
+    }
 
     if (!(id in this._prplConv)) {
       this._prplConv[id] = aPrplConversation;
       aPrplConversation.addObserver(this.observeConv.bind(this, id));
     }
 
     let shouldNotify = this._currentTargetId;
     this._currentTargetId = id;
     if (!this.isChat) {
       let buddy = this.buddy;
-      if (buddy)
-        ({statusType: this.statusType, statusText: this.statusText} = buddy);
+      if (buddy) {
+        ({ statusType: this.statusType, statusText: this.statusText } = buddy);
+      }
     }
     if (shouldNotify) {
       this.notifyObservers(this, "target-prpl-conversation-changed");
       let target = this.target;
       let params = [target.title, target.account.protocol.name];
       this.systemMessage(bundle.formatStringFromName("targetChanged", params));
     }
   },
   // Returns a boolean indicating if the ui-conversation was closed.
   // If the conversation was closed, aContactId.value is set to the contact id
   // or 0 if no contact was associated with the conversation.
   removeTarget(aPrplConversation, aContactId) {
     let id = aPrplConversation.id;
-    if (!(id in this._prplConv))
+    if (!(id in this._prplConv)) {
       throw new Error("unknown prpl conversation");
+    }
 
     delete this._prplConv[id];
-    if (this._currentTargetId != id)
+    if (this._currentTargetId != id) {
       return false;
+    }
 
     for (let newId in this._prplConv) {
       this.changeTargetTo(this._prplConv[newId]);
       return false;
     }
 
     if (this._observedContact) {
       this._observedContact.removeObserver(this);
       aContactId.value = this._observedContact.id;
       delete this._observedContact;
+    } else {
+      aContactId.value = 0;
     }
-    else
-      aContactId.value = 0;
 
     delete this._currentTargetId;
     this.notifyObservers(this, "ui-conversation-closed");
     return true;
   },
 
   _unreadMessageCount: 0,
-  get unreadMessageCount() { return this._unreadMessageCount; },
+  get unreadMessageCount() {
+    return this._unreadMessageCount;
+  },
   _unreadTargetedMessageCount: 0,
-  get unreadTargetedMessageCount() { return this._unreadTargetedMessageCount; },
+  get unreadTargetedMessageCount() {
+    return this._unreadTargetedMessageCount;
+  },
   _unreadIncomingMessageCount: 0,
-  get unreadIncomingMessageCount() { return this._unreadIncomingMessageCount; },
+  get unreadIncomingMessageCount() {
+    return this._unreadIncomingMessageCount;
+  },
   markAsRead() {
     delete this._unreadMessageCount;
     delete this._unreadTargetedMessageCount;
     delete this._unreadIncomingMessageCount;
     this._notifyUnreadCountChanged();
   },
   _lastNotifiedUnreadCount: 0,
   _notifyUnreadCountChanged() {
-    if (this._unreadIncomingMessageCount == this._lastNotifiedUnreadCount)
+    if (this._unreadIncomingMessageCount == this._lastNotifiedUnreadCount) {
       return;
+    }
 
     this._lastNotifiedUnreadCount = this._unreadIncomingMessageCount;
-    for (let observer of this._observers)
-      observer.observe(this, "unread-message-count-changed",
-                       this._unreadIncomingMessageCount.toString());
+    for (let observer of this._observers) {
+      observer.observe(
+        this,
+        "unread-message-count-changed",
+        this._unreadIncomingMessageCount.toString()
+      );
+    }
   },
   getMessages(aMessageCount) {
-    if (aMessageCount)
+    if (aMessageCount) {
       aMessageCount.value = this._messages.length;
+    }
     return this._messages;
   },
   checkClose() {
-    if (!this._currentTargetId)
-      return true; // already closed.
+    if (!this._currentTargetId) {
+      return true;
+    } // already closed.
 
-    if (!Services.prefs.getBoolPref("messenger.conversations.alwaysClose") &&
-        (this.isChat && !this.left ||
-         !this.isChat && (this.unreadIncomingMessageCount != 0 ||
-                          Services.prefs.getBoolPref("messenger.conversations.holdByDefault"))))
+    if (
+      !Services.prefs.getBoolPref("messenger.conversations.alwaysClose") &&
+      ((this.isChat && !this.left) ||
+        (!this.isChat &&
+          (this.unreadIncomingMessageCount != 0 ||
+            Services.prefs.getBoolPref(
+              "messenger.conversations.holdByDefault"
+            ))))
+    ) {
       return false;
+    }
 
     this.close();
     return true;
   },
 
   observe(aSubject, aTopic, aData) {
     if (aTopic == "contact-no-longer-dummy") {
       let oldId = parseInt(aData);
       // gConversationsService is ugly... :(
       delete gConversationsService._uiConvByContactId[oldId];
       gConversationsService._uiConvByContactId[aSubject.id] = this;
-    }
-    else if (aTopic == "account-buddy-status-changed") {
-      if (!this._statusUpdatePending &&
-          aSubject.account.id == this.account.id &&
-          aSubject.buddy.id == this.buddy.buddy.id) {
+    } else if (aTopic == "account-buddy-status-changed") {
+      if (
+        !this._statusUpdatePending &&
+        aSubject.account.id == this.account.id &&
+        aSubject.buddy.id == this.buddy.buddy.id
+      ) {
         this._statusUpdatePending = true;
-        Services.tm.mainThread.dispatch(this.updateBuddyStatus.bind(this),
-                                        Ci.nsIEventTarget.DISPATCH_NORMAL);
+        Services.tm.mainThread.dispatch(
+          this.updateBuddyStatus.bind(this),
+          Ci.nsIEventTarget.DISPATCH_NORMAL
+        );
       }
+    } else if (aTopic == "account-buddy-icon-changed") {
+      if (
+        !this._statusUpdatePending &&
+        aSubject.account.id == this.account.id &&
+        aSubject.buddy.id == this.buddy.buddy.id
+      ) {
+        this._iconUpdatePending = true;
+        Services.tm.mainThread.dispatch(
+          this.updateIcon.bind(this),
+          Ci.nsIEventTarget.DISPATCH_NORMAL
+        );
+      }
+    } else if (
+      aTopic == "account-buddy-display-name-changed" &&
+      aSubject.account.id == this.account.id &&
+      aSubject.buddy.id == this.buddy.buddy.id
+    ) {
+      this.notifyObservers(this, "update-buddy-display-name");
     }
-    else if (aTopic == "account-buddy-icon-changed") {
-      if (!this._statusUpdatePending &&
-          aSubject.account.id == this.account.id &&
-          aSubject.buddy.id == this.buddy.buddy.id) {
-        this._iconUpdatePending = true;
-        Services.tm.mainThread.dispatch(this.updateIcon.bind(this),
-                                        Ci.nsIEventTarget.DISPATCH_NORMAL);
-      }
-    }
-    else if (aTopic == "account-buddy-display-name-changed" &&
-             aSubject.account.id == this.account.id &&
-             aSubject.buddy.id == this.buddy.buddy.id)
-      this.notifyObservers(this, "update-buddy-display-name");
   },
 
   _iconUpdatePending: false,
   updateIcon() {
     delete this._iconUpdatePending;
     this.notifyObservers(this, "update-buddy-icon");
   },
 
   _statusUpdatePending: false,
   updateBuddyStatus() {
     delete this._statusUpdatePending;
-    let {statusType: statusType, statusText: statusText} = this.buddy;
+    let { statusType: statusType, statusText: statusText } = this.buddy;
 
-    if (("statusType" in this) && this.statusType == statusType &&
-        this.statusText == statusText)
+    if (
+      "statusType" in this &&
+      this.statusType == statusType &&
+      this.statusText == statusText
+    ) {
       return;
+    }
 
     let wasUnknown = this.statusType == Ci.imIStatusInfo.STATUS_UNKNOWN;
     this.statusType = statusType;
     this.statusText = statusText;
 
     this.notifyObservers(this, "update-buddy-status");
 
     let msg;
-    if (statusType == Ci.imIStatusInfo.STATUS_UNKNOWN)
+    if (statusType == Ci.imIStatusInfo.STATUS_UNKNOWN) {
       msg = bundle.formatStringFromName("statusUnknown", [this.title]);
-    else {
+    } else {
       let status = Status.toLabel(statusType);
       let stringId = wasUnknown ? "statusChangedFromUnknown" : "statusChanged";
       if (this._justReconnected) {
         stringId = "statusKnown";
         delete this._justReconnected;
       }
       if (statusText) {
-        msg = bundle.formatStringFromName(stringId + "WithStatusText",
-                                          [this.title, status, statusText]);
+        msg = bundle.formatStringFromName(stringId + "WithStatusText", [
+          this.title,
+          status,
+          statusText,
+        ]);
+      } else {
+        msg = bundle.formatStringFromName(stringId, [this.title, status]);
       }
-      else
-        msg = bundle.formatStringFromName(stringId, [this.title, status]);
     }
     this.systemMessage(msg);
   },
 
   _disconnected: false,
   disconnecting() {
-    if (this._disconnected)
+    if (this._disconnected) {
       return;
+    }
 
     this._disconnected = true;
-    if (this.contact)
-      return; // handled by the contact observer.
+    if (this.contact) {
+      return;
+    } // handled by the contact observer.
 
-    if (this.isChat && this.left)
+    if (this.isChat && this.left) {
       this._wasLeft = true;
-    else
+    } else {
       this.systemMessage(bundle.GetStringFromName("accountDisconnected"));
+    }
     this.notifyObservers(this, "update-buddy-status");
   },
   connected() {
     if (this._disconnected) {
       delete this._disconnected;
       let msg = bundle.GetStringFromName("accountReconnected");
       if (this.isChat) {
         if (!this._wasLeft) {
           this.systemMessage(msg);
           // Reconnect chat if possible.
           let chatRoomFields = this.target.chatRoomFields;
-          if (chatRoomFields)
+          if (chatRoomFields) {
             this.account.joinChat(chatRoomFields);
+          }
         }
         delete this._wasLeft;
-      }
-      else {
+      } else {
         this._justReconnected = true;
         // Exclude convs with contacts, these receive presence info updates
         // (and therefore a reconnected message).
-        if (!this.contact)
+        if (!this.contact) {
           this.systemMessage(msg);
+        }
       }
     }
     this.notifyObservers(this, "update-buddy-status");
   },
 
   observeConv(aTargetId, aSubject, aTopic, aData) {
-    if (aTargetId != this._currentTargetId &&
-        (aTopic == "new-text" ||
-         (aTopic == "update-typing" &&
-          this._prplConv[aTargetId].typingState == Ci.prplIConvIM.TYPING)))
+    if (
+      aTargetId != this._currentTargetId &&
+      (aTopic == "new-text" ||
+        (aTopic == "update-typing" &&
+          this._prplConv[aTargetId].typingState == Ci.prplIConvIM.TYPING))
+    ) {
       this.target = this._prplConv[aTargetId];
+    }
 
     this.notifyObservers(aSubject, aTopic, aData);
   },
 
   systemMessage(aText, aIsError) {
-    let flags = {system: true, noLog: true, error: !!aIsError};
-    (new Message("system", aText, flags)).conversation = this;
+    let flags = { system: true, noLog: true, error: !!aIsError };
+    new Message("system", aText, flags).conversation = this;
   },
 
   // prplIConversation
-  get isChat() { return this.target.isChat; },
-  get account() { return this.target.account; },
-  get name() { return this.target.name; },
-  get normalizedName() { return this.target.normalizedName; },
-  get title() { return this.target.title; },
-  get startDate() { return this.target.startDate; },
+  get isChat() {
+    return this.target.isChat;
+  },
+  get account() {
+    return this.target.account;
+  },
+  get name() {
+    return this.target.name;
+  },
+  get normalizedName() {
+    return this.target.normalizedName;
+  },
+  get title() {
+    return this.target.title;
+  },
+  get startDate() {
+    return this.target.startDate;
+  },
   sendMsg(aMsg) {
     // Add-ons (eg. pastebin) have an opportunity to cancel the message at this
     // point, or change the text content of the message.
     // If an add-on wants to split a message, it should truncate the first
     // message, and insert new messages using the conversation's sendMsg method.
     let om = new OutgoingMessage(aMsg, this);
     this.notifyObservers(om, "preparing-message");
-    if (om.cancelled)
+    if (om.cancelled) {
       return;
+    }
 
     // Protocols have an opportunity here to preprocess messages before they are
     // sent (eg. split long messages). If a message is split here, the split
     // will be visible in the UI.
     let messages = this.target.prepareForSending(om);
 
     // Protocols can return null if they don't need to make any changes.
     // (nb. passing null with retval array results in an empty array)
-    if (!messages || !messages.length)
+    if (!messages || !messages.length) {
       messages = [om.message];
+    }
 
     for (let msg of messages) {
       // Add-ons (eg. OTR) have an opportunity to tweak or cancel the message
       // at this point.
       om = new OutgoingMessage(msg, this.target);
       this.notifyObservers(om, "sending-message");
-      if (om.cancelled)
+      if (om.cancelled) {
         continue;
+      }
       this.target.sendMsg(om.message);
     }
   },
   unInit() {
     for (let id in this._prplConv) {
       let conv = this._prplConv[id];
       gConversationsService.forgetConversation(conv);
     }
@@ -413,171 +528,218 @@ UIConversation.prototype = {
     delete this._currentTargetId;
     this.notifyObservers(this, "ui-conversation-destroyed");
   },
   close() {
     for (let id in this._prplConv) {
       let conv = this._prplConv[id];
       conv.close();
     }
-    if (!this.hasOwnProperty("_currentTargetId"))
+    if (!this.hasOwnProperty("_currentTargetId")) {
       return;
+    }
     delete this._currentTargetId;
     this.notifyObservers(this, "ui-conversation-closed");
     Services.obs.notifyObservers(this, "ui-conversation-closed");
   },
   addObserver(aObserver) {
-    if (!this._observers.includes(aObserver))
+    if (!this._observers.includes(aObserver)) {
       this._observers.push(aObserver);
+    }
   },
   removeObserver(aObserver) {
     this._observers = this._observers.filter(o => o !== aObserver);
   },
   notifyObservers(aSubject, aTopic, aData) {
     if (aTopic == "new-text") {
       aSubject = new imMessage(aSubject);
       this.notifyObservers(aSubject, "received-message");
-      if (aSubject.cancelled)
+      if (aSubject.cancelled) {
         return;
-      if (!aSubject.system)
+      }
+      if (!aSubject.system) {
         aSubject.conversation.prepareForDisplaying(aSubject);
+      }
 
       this._messages.push(aSubject);
       ++this._unreadMessageCount;
       if (aSubject.incoming && !aSubject.system) {
         ++this._unreadIncomingMessageCount;
-        if (!this.isChat || aSubject.containsNick)
+        if (!this.isChat || aSubject.containsNick) {
           ++this._unreadTargetedMessageCount;
+        }
       }
     }
 
     for (let observer of this._observers) {
-      if (!observer.observe && !this._observers.includes(observer))
-        continue; // observer removed by a previous call to another observer.
+      if (!observer.observe && !this._observers.includes(observer)) {
+        continue;
+      } // observer removed by a previous call to another observer.
       observer.observe(aSubject, aTopic, aData);
     }
     this._notifyUnreadCountChanged();
 
     if (aTopic == "new-text") {
       Services.obs.notifyObservers(aSubject, aTopic, aData);
-      if (aSubject.incoming && !aSubject.system &&
-          (!this.isChat || aSubject.containsNick)) {
+      if (
+        aSubject.incoming &&
+        !aSubject.system &&
+        (!this.isChat || aSubject.containsNick)
+      ) {
         this.notifyObservers(aSubject, "new-directed-incoming-message", aData);
-        Services.obs.notifyObservers(aSubject, "new-directed-incoming-message", aData);
+        Services.obs.notifyObservers(
+          aSubject,
+          "new-directed-incoming-message",
+          aData
+        );
       }
     }
   },
 
   // Used above when notifying of new-texts originating in the
   // UIConversation. This happens when this.systemMessage() is called. The
   // conversation for the message is set as the UIConversation.
   prepareForDisplaying(aMsg) {},
 
   // prplIConvIM
-  get buddy() { return this.target.buddy; },
-  get typingState() { return this.target.typingState; },
-  sendTyping(aString) { return this.target.sendTyping(aString); },
+  get buddy() {
+    return this.target.buddy;
+  },
+  get typingState() {
+    return this.target.typingState;
+  },
+  sendTyping(aString) {
+    return this.target.sendTyping(aString);
+  },
 
   // Chat only
-  getParticipants() { return this.target.getParticipants(); },
-  get topic() { return this.target.topic; },
-  set topic(aTopic) { this.target.topic = aTopic; },
-  get topicSetter() { return this.target.topicSetter; },
-  get topicSettable() { return this.target.topicSettable; },
-  get noTopicString() { return bundle.GetStringFromName("noTopic"); },
-  get nick() { return this.target.nick; },
-  get left() { return this.target.left; },
-  get joining() { return this.target.joining; },
+  getParticipants() {
+    return this.target.getParticipants();
+  },
+  get topic() {
+    return this.target.topic;
+  },
+  set topic(aTopic) {
+    this.target.topic = aTopic;
+  },
+  get topicSetter() {
+    return this.target.topicSetter;
+  },
+  get topicSettable() {
+    return this.target.topicSettable;
+  },
+  get noTopicString() {
+    return bundle.GetStringFromName("noTopic");
+  },
+  get nick() {
+    return this.target.nick;
+  },
+  get left() {
+    return this.target.left;
+  },
+  get joining() {
+    return this.target.joining;
+  },
 };
 
 var gConversationsService;
-function ConversationsService() { gConversationsService = this; }
+function ConversationsService() {
+  gConversationsService = this;
+}
 ConversationsService.prototype = {
-  get wrappedJSObject() { return this; },
+  get wrappedJSObject() {
+    return this;
+  },
 
   initConversations() {
     this._uiConv = {};
     this._uiConvByContactId = {};
     this._prplConversations = [];
     Services.obs.addObserver(this, "account-disconnecting");
     Services.obs.addObserver(this, "account-connected");
     Services.obs.addObserver(this, "account-buddy-added");
     Services.obs.addObserver(this, "account-buddy-removed");
   },
 
   unInitConversations() {
     let UIConvs = this.getUIConversations();
-    for (let UIConv of UIConvs)
+    for (let UIConv of UIConvs) {
       UIConv.unInit();
+    }
     delete this._uiConv;
     delete this._uiConvByContactId;
     // This should already be empty, but just to be sure...
-    for (let prplConv of this._prplConversations)
+    for (let prplConv of this._prplConversations) {
       prplConv.unInit();
+    }
     delete this._prplConversations;
     Services.obs.removeObserver(this, "account-disconnecting");
     Services.obs.removeObserver(this, "account-connected");
     Services.obs.removeObserver(this, "account-buddy-added");
     Services.obs.removeObserver(this, "account-buddy-removed");
   },
 
   observe(aSubject, aTopic, aData) {
     if (aTopic == "account-connected") {
       for (let id in this._uiConv) {
         let conv = this._uiConv[id];
-        if (conv.account.id == aSubject.id)
+        if (conv.account.id == aSubject.id) {
           conv.connected();
+        }
       }
-    }
-    else if (aTopic == "account-disconnecting") {
+    } else if (aTopic == "account-disconnecting") {
       for (let id in this._uiConv) {
         let conv = this._uiConv[id];
-        if (conv.account.id == aSubject.id)
+        if (conv.account.id == aSubject.id) {
           conv.disconnecting();
+        }
       }
-    }
-    else if (aTopic == "account-buddy-added") {
+    } else if (aTopic == "account-buddy-added") {
       let accountBuddy = aSubject;
-      let prplConversation =
-        this.getConversationByNameAndAccount(accountBuddy.normalizedName,
-                                             accountBuddy.account, false);
-      if (!prplConversation)
+      let prplConversation = this.getConversationByNameAndAccount(
+        accountBuddy.normalizedName,
+        accountBuddy.account,
+        false
+      );
+      if (!prplConversation) {
         return;
+      }
 
       let uiConv = this.getUIConversation(prplConversation);
       let contactId = accountBuddy.buddy.contact.id;
       if (contactId in this._uiConvByContactId) {
         // Trouble! There is an existing uiConv for this contact.
         // We should avoid having two uiConvs with the same contact.
         // This is ugly UX, but at least can only happen if there is
         // already an accountBuddy with the same name for the same
         // protocol on a different account, which should be rare.
         this.removeConversation(prplConversation);
         return;
       }
       // Link the existing uiConv to the contact.
       this._uiConvByContactId[contactId] = uiConv;
       uiConv.updateContactObserver();
       uiConv.notifyObservers(uiConv, "update-conv-buddy");
-    }
-    else if (aTopic == "account-buddy-removed") {
+    } else if (aTopic == "account-buddy-removed") {
       let accountBuddy = aSubject;
       let contactId = accountBuddy.buddy.contact.id;
-      if (!(contactId in this._uiConvByContactId))
+      if (!(contactId in this._uiConvByContactId)) {
         return;
+      }
       let uiConv = this._uiConvByContactId[contactId];
 
       // If there is more than one target on the uiConv, close the
       // prplConv as we can't dissociate the uiConv from the contact.
       // The conversation with the contact will continue with a different
       // target.
       if (uiConv.hasMultipleTargets) {
         let prplConversation = uiConv.getTargetByAccount(accountBuddy.account);
-        if (prplConversation)
+        if (prplConversation) {
           this.removeConversation(prplConversation);
+        }
         return;
       }
 
       delete this._uiConvByContactId[contactId];
       uiConv.updateContactObserver();
       uiConv.notifyObservers(uiConv, "update-conv-buddy");
     }
   },
@@ -589,94 +751,108 @@ ConversationsService.prototype = {
 
     // Notify observers.
     Services.obs.notifyObservers(aPrplConversation, "new-conversation");
 
     // Update or create the corresponding UI conversation.
     let contactId;
     if (!aPrplConversation.isChat) {
       let accountBuddy = aPrplConversation.buddy;
-      if (accountBuddy)
+      if (accountBuddy) {
         contactId = accountBuddy.buddy.contact.id;
+      }
     }
 
     if (contactId) {
       if (contactId in this._uiConvByContactId) {
         let uiConv = this._uiConvByContactId[contactId];
         uiConv.target = aPrplConversation;
         this._uiConv[aPrplConversation.id] = uiConv;
         return;
       }
     }
 
     let newUIConv = new UIConversation(aPrplConversation);
     this._uiConv[aPrplConversation.id] = newUIConv;
-    if (contactId)
+    if (contactId) {
       this._uiConvByContactId[contactId] = newUIConv;
+    }
   },
   removeConversation(aPrplConversation) {
     Services.obs.notifyObservers(aPrplConversation, "conversation-closed");
 
     let uiConv = this.getUIConversation(aPrplConversation);
     delete this._uiConv[aPrplConversation.id];
     let contactId = {};
     if (uiConv.removeTarget(aPrplConversation, contactId)) {
-      if (contactId.value)
+      if (contactId.value) {
         delete this._uiConvByContactId[contactId.value];
+      }
       Services.obs.notifyObservers(uiConv, "ui-conversation-closed");
     }
     this.forgetConversation(aPrplConversation);
   },
   forgetConversation(aPrplConversation) {
     aPrplConversation.unInit();
 
-    this._prplConversations =
-      this._prplConversations.filter(c => c !== aPrplConversation);
+    this._prplConversations = this._prplConversations.filter(
+      c => c !== aPrplConversation
+    );
   },
 
   getUIConversations(aConvCount) {
     let rv = [];
     if (this._uiConv) {
       for (let prplConvId in this._uiConv) {
         // Since an UIConversation may be linked to multiple prplConversations,
         // we must ensure we don't return the same UIConversation twice,
         // by checking the id matches that of the active prplConversation.
         let uiConv = this._uiConv[prplConvId];
-        if (prplConvId == uiConv.target.id)
+        if (prplConvId == uiConv.target.id) {
           rv.push(uiConv);
+        }
       }
     }
-    if (aConvCount)
+    if (aConvCount) {
       aConvCount.value = rv.length;
+    }
     return rv;
   },
   getUIConversation(aPrplConversation) {
     let id = aPrplConversation.id;
-    if (this._uiConv && id in this._uiConv)
+    if (this._uiConv && id in this._uiConv) {
       return this._uiConv[id];
+    }
     throw new Error("Unknown conversation");
   },
   getUIConversationByContactId(aId) {
-    return (aId in this._uiConvByContactId) ? this._uiConvByContactId[aId] : null;
+    return aId in this._uiConvByContactId ? this._uiConvByContactId[aId] : null;
   },
 
-  getConversations() { return new nsSimpleEnumerator(this._prplConversations); },
+  getConversations() {
+    return new nsSimpleEnumerator(this._prplConversations);
+  },
   getConversationById(aId) {
-    for (let conv of this._prplConversations)
-      if (conv.id == aId)
+    for (let conv of this._prplConversations) {
+      if (conv.id == aId) {
         return conv;
+      }
+    }
     return null;
   },
   getConversationByNameAndAccount(aName, aAccount, aIsChat) {
     let normalizedName = aAccount.normalize(aName);
     for (let conv of this._prplConversations) {
-      if (aAccount.normalize(conv.name) == normalizedName &&
-          aAccount.numericId == conv.account.numericId &&
-          conv.isChat == aIsChat)
+      if (
+        aAccount.normalize(conv.name) == normalizedName &&
+        aAccount.numericId == conv.account.numericId &&
+        conv.isChat == aIsChat
+      ) {
         return conv;
+      }
     }
     return null;
   },
 
   QueryInterface: ChromeUtils.generateQI([Ci.imIConversationsService]),
   classDescription: "Conversations",
   classID: Components.ID("{b2397cd5-c76d-4618-8410-f344c7c6443a}"),
   contractID: "@mozilla.org/chat/conversations-service;1",
--- a/chat/components/src/imCore.js
+++ b/chat/components/src/imCore.js
@@ -1,174 +1,204 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-var {Services} = ChromeUtils.import("resource:///modules/imServices.jsm");
+var { Services } = ChromeUtils.import("resource:///modules/imServices.jsm");
 var {
   XPCOMUtils,
   ClassInfo,
   initLogModule,
   nsSimpleEnumerator,
 } = ChromeUtils.import("resource:///modules/imXPCOMUtils.jsm");
 
-XPCOMUtils.defineLazyServiceGetter(this, "categoryManager",
-                                   "@mozilla.org/categorymanager;1",
-                                   "nsICategoryManager");
+XPCOMUtils.defineLazyServiceGetter(
+  this,
+  "categoryManager",
+  "@mozilla.org/categorymanager;1",
+  "nsICategoryManager"
+);
 
 var kQuitApplicationGranted = "quit-application-granted";
 var kProtocolPluginCategory = "im-protocol-plugin";
 
-var kPrefReportIdle =        "messenger.status.reportIdle";
-var kPrefUserIconFilename =  "messenger.status.userIconFileName";
-var kPrefUserDisplayname =   "messenger.status.userDisplayName";
-var kPrefTimeBeforeIdle =    "messenger.status.timeBeforeIdle";
-var kPrefAwayWhenIdle =      "messenger.status.awayWhenIdle";
-var kPrefDefaultMessage =    "messenger.status.defaultIdleAwayMessage";
+var kPrefReportIdle = "messenger.status.reportIdle";
+var kPrefUserIconFilename = "messenger.status.userIconFileName";
+var kPrefUserDisplayname = "messenger.status.userDisplayName";
+var kPrefTimeBeforeIdle = "messenger.status.timeBeforeIdle";
+var kPrefAwayWhenIdle = "messenger.status.awayWhenIdle";
+var kPrefDefaultMessage = "messenger.status.defaultIdleAwayMessage";
 
 var NS_IOSERVICE_GOING_OFFLINE_TOPIC = "network:offline-about-to-go-offline";
 var NS_IOSERVICE_OFFLINE_STATUS_TOPIC = "network:offline-status-changed";
 
-function UserStatus()
-{
+function UserStatus() {
   this._observers = [];
 
-  if (Services.prefs.getBoolPref(kPrefReportIdle))
+  if (Services.prefs.getBoolPref(kPrefReportIdle)) {
     this._addIdleObserver();
+  }
   Services.prefs.addObserver(kPrefReportIdle, this);
 
-  if (Services.io.offline)
+  if (Services.io.offline) {
     this._offlineStatusType = Ci.imIStatusInfo.STATUS_OFFLINE;
+  }
   Services.obs.addObserver(this, NS_IOSERVICE_GOING_OFFLINE_TOPIC);
   Services.obs.addObserver(this, NS_IOSERVICE_OFFLINE_STATUS_TOPIC);
 }
 UserStatus.prototype = {
   __proto__: ClassInfo("imIUserStatusInfo", "User status info"),
 
   unInit() {
     this._observers = [];
     Services.prefs.removeObserver(kPrefReportIdle, this);
-    if (this._observingIdleness)
+    if (this._observingIdleness) {
       this._removeIdleObserver();
+    }
     Services.obs.removeObserver(this, NS_IOSERVICE_GOING_OFFLINE_TOPIC);
     Services.obs.removeObserver(this, NS_IOSERVICE_OFFLINE_STATUS_TOPIC);
   },
   _observingIdleness: false,
   _addIdleObserver() {
     this._observingIdleness = true;
-    this._idleService =
-      Cc["@mozilla.org/widget/idleservice;1"].getService(Ci.nsIIdleService);
+    this._idleService = Cc["@mozilla.org/widget/idleservice;1"].getService(
+      Ci.nsIIdleService
+    );
     Services.obs.addObserver(this, "im-sent");
 
     this._timeBeforeIdle = Services.prefs.getIntPref(kPrefTimeBeforeIdle);
-    if (this._timeBeforeIdle < 0)
+    if (this._timeBeforeIdle < 0) {
       this._timeBeforeIdle = 0;
+    }
     Services.prefs.addObserver(kPrefTimeBeforeIdle, this);
-    if (this._timeBeforeIdle)
+    if (this._timeBeforeIdle) {
       this._idleService.addIdleObserver(this, this._timeBeforeIdle);
+    }
   },
   _removeIdleObserver() {
-    if (this._timeBeforeIdle)
+    if (this._timeBeforeIdle) {
       this._idleService.removeIdleObserver(this, this._timeBeforeIdle);
+    }
 
     Services.prefs.removeObserver(kPrefTimeBeforeIdle, this);
     delete this._timeBeforeIdle;
 
     Services.obs.removeObserver(this, "im-sent");
     delete this._idleService;
     delete this._observingIdleness;
   },
 
   observe(aSubject, aTopic, aData) {
     if (aTopic == "nsPref:changed") {
       if (aData == kPrefReportIdle) {
         let reportIdle = Services.prefs.getBoolPref(kPrefReportIdle);
-        if (reportIdle && !this._observingIdleness)
+        if (reportIdle && !this._observingIdleness) {
           this._addIdleObserver();
-        else if (!reportIdle && this._observingIdleness)
-        this._removeIdleObserver();
-      }
-      else if (aData == kPrefTimeBeforeIdle) {
+        } else if (!reportIdle && this._observingIdleness) {
+          this._removeIdleObserver();
+        }
+      } else if (aData == kPrefTimeBeforeIdle) {
         let timeBeforeIdle = Services.prefs.getIntPref(kPrefTimeBeforeIdle);
         if (timeBeforeIdle != this._timeBeforeIdle) {
-          if (this._timeBeforeIdle)
+          if (this._timeBeforeIdle) {
             this._idleService.removeIdleObserver(this, this._timeBeforeIdle);
+          }
           this._timeBeforeIdle = timeBeforeIdle;
-          if (this._timeBeforeIdle)
+          if (this._timeBeforeIdle) {
             this._idleService.addIdleObserver(this, this._timeBeforeIdle);
+          }
         }
+      } else {
+        throw Cr.NS_ERROR_UNEXPECTED;
       }
-      else
-        throw Cr.NS_ERROR_UNEXPECTED;
-    }
-    else if (aTopic == NS_IOSERVICE_GOING_OFFLINE_TOPIC)
+    } else if (aTopic == NS_IOSERVICE_GOING_OFFLINE_TOPIC) {
       this.offline = true;
-    else if (aTopic == NS_IOSERVICE_OFFLINE_STATUS_TOPIC && aData == "online")
+    } else if (
+      aTopic == NS_IOSERVICE_OFFLINE_STATUS_TOPIC &&
+      aData == "online"
+    ) {
       this.offline = false;
-    else
+    } else {
       this._checkIdle();
+    }
   },
 
   _offlineStatusType: Ci.imIStatusInfo.STATUS_AVAILABLE,
   set offline(aOffline) {
     let statusType = this.statusType;
     let statusText = this.statusText;
-    if (aOffline)
+    if (aOffline) {
       this._offlineStatusType = Ci.imIStatusInfo.STATUS_OFFLINE;
-    else
+    } else {
       delete this._offlineStatusType;
-    if (this.statusType != statusType || this.statusText != statusText)
+    }
+    if (this.statusType != statusType || this.statusText != statusText) {
       this._notifyObservers("status-changed", this.statusText);
+    }
   },
 
   _idleTime: 0,
-  get idleTime() { return this._idleTime; },
+  get idleTime() {
+    return this._idleTime;
+  },
   set idleTime(aIdleTime) {
     this._idleTime = aIdleTime;
     this._notifyObservers("idle-time-changed", aIdleTime);
   },
   _idle: false,
   _idleStatusText: "",
   _idleStatusType: Ci.imIStatusInfo.STATUS_AVAILABLE,
   _checkIdle() {
     let idleTime = Math.floor(this._idleService.idleTime / 1000);
     let idle = this._timeBeforeIdle && idleTime >= this._timeBeforeIdle;
-    if (idle == this._idle)
+    if (idle == this._idle) {
       return;
+    }
 
     let statusType = this.statusType;
     let statusText = this.statusText;
     this._idle = idle;
     if (idle) {
       this.idleTime = idleTime;
       if (Services.prefs.getBoolPref(kPrefAwayWhenIdle)) {
         this._idleStatusType = Ci.imIStatusInfo.STATUS_AWAY;
-        this._idleStatusText =
-          Services.prefs.getComplexValue(kPrefDefaultMessage,
-                                         Ci.nsIPrefLocalizedString).data;
+        this._idleStatusText = Services.prefs.getComplexValue(
+          kPrefDefaultMessage,
+          Ci.nsIPrefLocalizedString
+        ).data;
       }
-    }
-    else {
+    } else {
       this.idleTime = 0;
       delete this._idleStatusType;
       delete this._idleStatusText;
     }
-    if (this.statusType != statusType || this.statusText != statusText)
+    if (this.statusType != statusType || this.statusText != statusText) {
       this._notifyObservers("status-changed", this.statusText);
+    }
   },
 
   _statusText: "",
-  get statusText() { return this._statusText || this._idleStatusText; },
+  get statusText() {
+    return this._statusText || this._idleStatusText;
+  },
   _statusType: Ci.imIStatusInfo.STATUS_AVAILABLE,
-  get statusType() { return Math.min(this._statusType, this._idleStatusType, this._offlineStatusType); },
+  get statusType() {
+    return Math.min(
+      this._statusType,
+      this._idleStatusType,
+      this._offlineStatusType
+    );
+  },
   setStatus(aStatus, aMessage) {
-    if (aStatus != Ci.imIStatusInfo.STATUS_UNKNOWN)
+    if (aStatus != Ci.imIStatusInfo.STATUS_UNKNOWN) {
       this._statusType = aStatus;
-    if (aStatus != Ci.imIStatusInfo.STATUS_OFFLINE)
+    }
+    if (aStatus != Ci.imIStatusInfo.STATUS_OFFLINE) {
       this._statusText = aMessage;
+    }
     this._notifyObservers("status-changed", aMessage);
   },
 
   _getProfileDir: () => Services.dirsvc.get("ProfD", Ci.nsIFile),
   setUserIcon(aIconFile) {
     let folder = this._getProfileDir();
 
     let newName = "";
@@ -187,29 +217,31 @@ UserStatus.prototype = {
     Services.prefs.setCharPref(kPrefUserIconFilename, newName);
 
     // Now that the new icon has been copied to the profile directory
     // and the pref value changed, we can remove the old icon. Ignore
     // failures so that we always fire the user-icon-changed notification.
     try {
       if (oldFileName) {
         folder.append(oldFileName);
-        if (folder.exists())
+        if (folder.exists()) {
           folder.remove(false);
+        }
       }
     } catch (e) {
       Cu.reportError(e);
     }
 
     this._notifyObservers("user-icon-changed", newName);
   },
   getUserIcon() {
     let filename = Services.prefs.getCharPref(kPrefUserIconFilename);
-    if (!filename)
-      return null; // No icon has been set.
+    if (!filename) {
+      return null;
+    } // No icon has been set.
 
     let file = this._getProfileDir();
     file.append(filename);
 
     if (!file.exists()) {
       Services.console.logStringMessage("Invalid userIconFileName preference");
       return null;
     }
@@ -221,38 +253,45 @@ UserStatus.prototype = {
     return Services.prefs.getStringPref(kPrefUserDisplayname);
   },
   set displayName(aDisplayName) {
     Services.prefs.setStringPref(kPrefUserDisplayname, aDisplayName);
     this._notifyObservers("user-display-name-changed", aDisplayName);
   },
 
   addObserver(aObserver) {
-    if (!this._observers.includes(aObserver))
+    if (!this._observers.includes(aObserver)) {
       this._observers.push(aObserver);
+    }
   },
   removeObserver(aObserver) {
     this._observers = this._observers.filter(o => o !== aObserver);
   },
   _notifyObservers(aTopic, aData) {
-    for (let observer of this._observers)
+    for (let observer of this._observers) {
       observer.observe(this, aTopic, aData);
+    }
   },
 };
 
 var gCoreService;
-function CoreService() { gCoreService = this; }
+function CoreService() {
+  gCoreService = this;
+}
 CoreService.prototype = {
   globalUserStatus: null,
 
   _initialized: false,
-  get initialized() { return this._initialized; },
+  get initialized() {
+    return this._initialized;
+  },
   init() {
-    if (this._initialized)
+    if (this._initialized) {
       return;
+    }
 
     initLogModule("core", this);
 
     Services.obs.addObserver(this, kQuitApplicationGranted);
     this._initialized = true;
 
     Services.cmd.initCommands();
     this._protos = {};
@@ -274,68 +313,76 @@ CoreService.prototype = {
     // is available.
     if (accounts.autoLoginStatus == Ci.imIAccountsService.AUTOLOGIN_ENABLED) {
       Services.logins.initializationPromise.then(() => {
         Services.accounts.processAutoLogin();
       });
     }
   },
   observe(aObject, aTopic, aData) {
-    if (aTopic == kQuitApplicationGranted)
+    if (aTopic == kQuitApplicationGranted) {
       this.quit();
+    }
   },
   quit() {
-    if (!this._initialized)
+    if (!this._initialized) {
       throw Cr.NS_ERROR_NOT_INITIALIZED;
+    }
 
     Services.obs.removeObserver(this, kQuitApplicationGranted);
     Services.obs.notifyObservers(this, "prpl-quit");
 
     Services.conversations.unInitConversations();
     Services.accounts.unInitAccounts();
     Services.contacts.unInitContacts();
     Services.cmd.unInitCommands();
 
     this.globalUserStatus.unInit();
     delete this.globalUserStatus;
     delete this._protos;
     delete this._initialized;
   },
 
   getProtocols() {
-    if (!this._initialized)
+    if (!this._initialized) {
       throw Cr.NS_ERROR_NOT_INITIALIZED;
+    }
 
     let protocols = [];
     let entries = categoryManager.enumerateCategory(kProtocolPluginCategory);
     while (entries.hasMoreElements()) {
       let id = entries.getNext().QueryInterface(Ci.nsISupportsCString).data;
 
       // If the preference is set to disable this prpl, don't show it in the
       // full list of protocols.
       let pref = "chat.prpls." + id + ".disable";
-      if (Services.prefs.getPrefType(pref) == Services.prefs.PREF_BOOL &&
-          Services.prefs.getBoolPref(pref)) {
+      if (
+        Services.prefs.getPrefType(pref) == Services.prefs.PREF_BOOL &&
+        Services.prefs.getBoolPref(pref)
+      ) {
         this.LOG("Disabling prpl: " + id);
         continue;
       }
 
       let proto = this.getProtocolById(id);
-      if (proto)
+      if (proto) {
         protocols.push(proto);
+      }
     }
     return new nsSimpleEnumerator(protocols);
   },
 
   getProtocolById(aPrplId) {
-    if (!this._initialized)
+    if (!this._initialized) {
       throw Cr.NS_ERROR_NOT_INITIALIZED;
+    }
 
-    if (this._protos.hasOwnProperty(aPrplId))
+    if (this._protos.hasOwnProperty(aPrplId)) {
       return this._protos[aPrplId];
+    }
 
     let cid;
     try {
       cid = categoryManager.getCategoryEntry(kProtocolPluginCategory, aPrplId);
     } catch (e) {
       return null; // no protocol registered for this id.
     }
 
@@ -343,18 +390,19 @@ CoreService.prototype = {
     try {
       proto = Cc[cid].createInstance(Ci.prplIProtocol);
     } catch (e) {
       // This is a real error, the protocol is registered and failed to init.
       let error = "failed to create an instance of " + cid + ": " + e;
       dump(error + "\n");
       Cu.reportError(error);
     }
-    if (!proto)
+    if (!proto) {
       return null;
+    }
 
     try {
       proto.init(aPrplId);
     } catch (e) {
       Cu.reportError("Could not initialize protocol " + aPrplId + ": " + e);
       return null;
     }
 
--- a/chat/components/src/logger.js
+++ b/chat/components/src/logger.js
@@ -1,25 +1,29 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 var CC = Components.Constructor;
 
-var {Services} = ChromeUtils.import("resource:///modules/imServices.jsm");
-var {
-  EmptyEnumerator,
-  l10nHelper,
-  XPCOMUtils,
-} = ChromeUtils.import("resource:///modules/imXPCOMUtils.jsm");
-var {GenericMessagePrototype} = ChromeUtils.import("resource:///modules/jsProtoHelper.jsm");
-var {ClassInfo} = ChromeUtils.import("resource:///modules/imXPCOMUtils.jsm");
-var {ToLocaleFormat} = ChromeUtils.import("resource:///modules/ToLocaleFormat.jsm");
-var {OS} = ChromeUtils.import("resource://gre/modules/osfile.jsm");
-var {getHiddenHTMLWindow} = ChromeUtils.import("resource:///modules/hiddenWindow.jsm");
+var { Services } = ChromeUtils.import("resource:///modules/imServices.jsm");
+var { EmptyEnumerator, l10nHelper, XPCOMUtils } = ChromeUtils.import(
+  "resource:///modules/imXPCOMUtils.jsm"
+);
+var { GenericMessagePrototype } = ChromeUtils.import(
+  "resource:///modules/jsProtoHelper.jsm"
+);
+var { ClassInfo } = ChromeUtils.import("resource:///modules/imXPCOMUtils.jsm");
+var { ToLocaleFormat } = ChromeUtils.import(
+  "resource:///modules/ToLocaleFormat.jsm"
+);
+var { OS } = ChromeUtils.import("resource://gre/modules/osfile.jsm");
+var { getHiddenHTMLWindow } = ChromeUtils.import(
+  "resource:///modules/hiddenWindow.jsm"
+);
 
 XPCOMUtils.defineLazyGetter(this, "_", () =>
   l10nHelper("chrome://chat/locale/logger.properties")
 );
 
 var kLineBreak = "@mozilla.org/windows-registry-key;1" in Cc ? "\r\n" : "\n";
 
 /*
@@ -30,24 +34,27 @@ var kLineBreak = "@mozilla.org/windows-r
 var gFilePromises = new Map();
 
 // Uses above map to queue operations on a file.
 function queueFileOperation(aPath, aOperation) {
   // Ensure the operation is queued regardless of whether the last one succeeded.
   // This is safe since the promise is returned and consumers are expected to
   // handle any errors. If there's no promise existing for the given path already,
   // queue the operation on a dummy pre-resolved promise.
-  let promise =
-    (gFilePromises.get(aPath) || Promise.resolve()).then(aOperation, aOperation);
+  let promise = (gFilePromises.get(aPath) || Promise.resolve()).then(
+    aOperation,
+    aOperation
+  );
   gFilePromises.set(aPath, promise);
 
   let cleanup = () => {
     // If no further operations have been queued, remove the reference from the map.
-    if (gFilePromises.get(aPath) === promise)
+    if (gFilePromises.get(aPath) === promise) {
       gFilePromises.delete(aPath);
+    }
   };
   // Ensure we clear unused promises whether they resolved or rejected.
   promise.then(cleanup, cleanup);
 
   return promise;
 }
 
 /*
@@ -56,23 +63,24 @@ function queueFileOperation(aPath, aOper
  * We open the file, append, and close it immediately. The alternative is to keep
  * it open and append as required, but we want to make sure we don't open a file
  * for reading while it's already open for writing, so we close it every time
  * (opening a file multiple times concurrently may fail on Windows).
  * Note: This function creates parent directories if required.
  */
 function appendToFile(aPath, aEncodedString, aCreate) {
   return queueFileOperation(aPath, async function() {
-    await OS.File.makeDir(OS.Path.dirname(aPath),
-                          {ignoreExisting: true, from: OS.Constants.Path.profileDir});
-    let file = await OS.File.open(aPath, {write: true, create: aCreate});
+    await OS.File.makeDir(OS.Path.dirname(aPath), {
+      ignoreExisting: true,
+      from: OS.Constants.Path.profileDir,
+    });
+    let file = await OS.File.open(aPath, { write: true, create: aCreate });
     try {
       await file.write(aEncodedString);
-    }
-    finally {
+    } finally {
       /*
        * If both the write() above and the close() below throw, and we don't
        * handle the close error here, the promise will be rejected with the close
        * error and the write error will be dropped. To avoid this, we log any
        * close error here so that any write error will be propagated.
        */
       await file.close().catch(Cu.reportError);
     }
@@ -80,97 +88,111 @@ function appendToFile(aPath, aEncodedStr
 }
 
 OS.File.profileBeforeChange.addBlocker(
   "Chat logger: writing all pending messages",
   async function() {
     for (let promise of gFilePromises.values()) {
       try {
         await promise;
-      }
-      catch (aError) {
+      } catch (aError) {
         // Ignore the error, whatever queued the operation will take care of it.
       }
     }
   }
 );
 
-
 // This function checks names against OS naming conventions and alters them
 // accordingly so that they can be used as file/folder names.
 function encodeName(aName) {
   // Reserved device names by Windows (prefixing "%").
   let reservedNames = /^(CON|PRN|AUX|NUL|COM\d|LPT\d)$/i;
-  if (reservedNames.test(aName))
+  if (reservedNames.test(aName)) {
     return "%" + aName;
+  }
 
   // "." and " " must not be at the end of a file or folder name (appending "_").
-  if (/[\. _]/.test(aName.slice(-1)))
+  if (/[\. _]/.test(aName.slice(-1))) {
     aName += "_";
+  }
 
   // Reserved characters are replaced by %[hex value]. encodeURIComponent() is
   // not sufficient, nevertheless decodeURIComponent() can be used to decode.
-  function encodeReservedChars(match) { return "%" + match.charCodeAt(0).toString(16); }
+  function encodeReservedChars(match) {
+    return "%" + match.charCodeAt(0).toString(16);
+  }
   return aName.replace(/[<>:"\/\\|?*&%]/g, encodeReservedChars);
 }
 
 function getLogFolderPathForAccount(aAccount) {
-  return OS.Path.join(OS.Constants.Path.profileDir,
-                      "logs", aAccount.protocol.normalizedName,
-                      encodeName(aAccount.normalizedName));
+  return OS.Path.join(
+    OS.Constants.Path.profileDir,
+    "logs",
+    aAccount.protocol.normalizedName,
+    encodeName(aAccount.normalizedName)
+  );
 }
 
 function getLogFilePathForConversation(aConv, aFormat, aStartTime) {
-  if (!aStartTime)
+  if (!aStartTime) {
     aStartTime = aConv.startDate / 1000;
+  }
   let path = getLogFolderPathForAccount(aConv.account);
   let name = aConv.normalizedName;
-  if (convIsRealMUC(aConv))
+  if (convIsRealMUC(aConv)) {
     name += ".chat";
-  return OS.Path.join(path, encodeName(name),
-                      getNewLogFileName(aFormat, aStartTime));
+  }
+  return OS.Path.join(
+    path,
+    encodeName(name),
+    getNewLogFileName(aFormat, aStartTime)
+  );
 }
 
 function getNewLogFileName(aFormat, aStartTime) {
   let date = aStartTime ? new Date(aStartTime) : new Date();
   let dateTime = ToLocaleFormat("%Y-%m-%d.%H%M%S", date);
   let offset = date.getTimezoneOffset();
   if (offset < 0) {
     dateTime += "+";
     offset *= -1;
+  } else {
+    dateTime += "-";
   }
-  else
-    dateTime += "-";
   let minutes = offset % 60;
   offset = (offset - minutes) / 60;
   function twoDigits(aNumber) {
-    if (aNumber == 0)
+    if (aNumber == 0) {
       return "00";
+    }
     return aNumber < 10 ? "0" + aNumber : aNumber;
   }
-  if (!aFormat)
+  if (!aFormat) {
     aFormat = "txt";
+  }
   return dateTime + twoDigits(offset) + twoDigits(minutes) + "." + aFormat;
 }
 
-
 // One of these is maintained for every conversation being logged. It initializes
 // a log file and appends to it as required.
 function LogWriter(aConversation) {
   this._conv = aConversation;
-  if (Services.prefs.getCharPref("purple.logging.format") == "json")
+  if (Services.prefs.getCharPref("purple.logging.format") == "json") {
     this.format = "json";
+  }
   this.paths = [];
   this.startNewFile(this._conv.startDate / 1000);
 }
 LogWriter.prototype = {
   // All log file paths used by this LogWriter.
   paths: [],
   // Path of the log file that is currently being written to.
-  get currentPath() { return this.paths[this.paths.length - 1]; },
+  get currentPath() {
+    return this.paths[this.paths.length - 1];
+  },
   // Constructor sets this to a promise that will resolve when the log header
   // has been written.
   _initialized: null,
   _startTime: null,
   _lastMessageTime: null,
   _messageCount: 0,
   format: "txt",
   encoder: new TextEncoder(),
@@ -179,225 +201,277 @@ LogWriter.prototype = {
     // log file is the time of the next message. Since message times are in seconds,
     // if we receive 1000 messages within a second after starting the new file,
     // we will create another file, using the same start time - and so the same
     // file name. To avoid this, ensure the new start time is at least one second
     // greater than the current one. This is ugly, but should rarely be needed.
     aStartTime = Math.max(aStartTime, this._startTime + 1000);
     this._startTime = this._lastMessageTime = aStartTime;
     this._messageCount = 0;
-    this.paths.push(getLogFilePathForConversation(this._conv, this.format, aStartTime));
+    this.paths.push(
+      getLogFilePathForConversation(this._conv, this.format, aStartTime)
+    );
     let account = this._conv.account;
     let header;
     if (this.format == "json") {
       header = {
         date: new Date(this._startTime),
         name: this._conv.name,
         title: this._conv.title,
         account: account.normalizedName,
         protocol: account.protocol.normalizedName,
         isChat: this._conv.isChat,
         normalizedName: this._conv.normalizedName,
       };
-      if (aContinuedSession)
+      if (aContinuedSession) {
         header.continuedSession = true;
+      }
       header = JSON.stringify(header) + "\n";
-    }
-    else {
+    } else {
       const dateTimeFormatter = new Services.intl.DateTimeFormat("en-US", {
-        dateStyle: "full", timeStyle: "long",
+        dateStyle: "full",
+        timeStyle: "long",
       });
-      header = "Conversation with " + this._conv.name +
-               " at " + dateTimeFormatter.format(new Date(this._conv.startDate / 1000)) +
-               " on " + account.name +
-               " (" + account.protocol.normalizedName + ")" + kLineBreak;
+      header =
+        "Conversation with " +
+        this._conv.name +
+        " at " +
+        dateTimeFormatter.format(new Date(this._conv.startDate / 1000)) +
+        " on " +
+        account.name +
+        " (" +
+        account.protocol.normalizedName +
+        ")" +
+        kLineBreak;
     }
-    this._initialized =
-      appendToFile(this.currentPath, this.encoder.encode(header), true);
+    this._initialized = appendToFile(
+      this.currentPath,
+      this.encoder.encode(header),
+      true
+    );
     // Catch the error separately so that _initialized will stay rejected if
     // writing the header failed.
     this._initialized.catch(aError =>
-                            Cu.reportError("Failed to initialize log file:\n" + aError));
+      Cu.reportError("Failed to initialize log file:\n" + aError)
+    );
   },
   _serialize(aString) {
     // TODO cleanup once bug 102699 is fixed
     let doc = getHiddenHTMLWindow().document;
     let div = doc.createElementNS("http://www.w3.org/1999/xhtml", "div");
     // eslint-disable-next-line no-unsanitized/property
-    div.innerHTML = aString.replace(/\r?\n/g, "<br/>").replace(/<br>/gi, "<br/>");
+    div.innerHTML = aString
+      .replace(/\r?\n/g, "<br/>")
+      .replace(/<br>/gi, "<br/>");
     const type = "text/plain";
     let encoder = Cu.createDocumentEncoder(type);
     encoder.init(doc, type, 0);
     encoder.setContainerNode(div);
-    encoder.setNodeFixup({fixupNode(aNode, aSerializeKids) {
-      if (aNode.localName == "a" && aNode.hasAttribute("href")) {
-        let url = aNode.getAttribute("href");
-        let content = aNode.textContent;
-        if (url != content)
-          aNode.textContent = content + " (" + url + ")";
-      }
-      return null;
-    }});
+    encoder.setNodeFixup({
+      fixupNode(aNode, aSerializeKids) {
+        if (aNode.localName == "a" && aNode.hasAttribute("href")) {
+          let url = aNode.getAttribute("href");
+          let content = aNode.textContent;
+          if (url != content) {
+            aNode.textContent = content + " (" + url + ")";
+          }
+        }
+        return null;
+      },
+    });
     return encoder.encodeToString();
   },
   // We start a new log file in the following cases:
   // - If it has been 30 minutes since the last message.
   kInactivityLimit: 30 * 60 * 1000,
   // - If at midnight, it's been longer than 3 hours since we started the file.
   kDayOverlapLimit: 3 * 60 * 60 * 1000,
   // - After every 1000 messages.
   kMessageCountLimit: 1000,
   logMessage(aMessage) {
     // aMessage.time is in seconds, we need it in milliseconds.
     let messageTime = aMessage.time * 1000;
     let messageMidnight = new Date(messageTime).setHours(0, 0, 0, 0);
 
     let inactivityLimitExceeded =
-      !aMessage.delayed && messageTime - this._lastMessageTime > this.kInactivityLimit;
+      !aMessage.delayed &&
+      messageTime - this._lastMessageTime > this.kInactivityLimit;
     let dayOverlapLimitExceeded =
-      !aMessage.delayed && messageMidnight - this._startTime > this.kDayOverlapLimit;
+      !aMessage.delayed &&
+      messageMidnight - this._startTime > this.kDayOverlapLimit;
 
-    if (inactivityLimitExceeded || dayOverlapLimitExceeded ||
-        this._messageCount == this.kMessageCountLimit) {
+    if (
+      inactivityLimitExceeded ||
+      dayOverlapLimitExceeded ||
+      this._messageCount == this.kMessageCountLimit
+    ) {
       // We start a new session if the inactivity limit was exceeded.
       this.startNewFile(messageTime, !inactivityLimitExceeded);
     }
     ++this._messageCount;
 
-    if (!aMessage.delayed)
+    if (!aMessage.delayed) {
       this._lastMessageTime = messageTime;
+    }
 
     let lineToWrite;
     if (this.format == "json") {
       let msg = {
         date: new Date(messageTime),
         who: aMessage.who,
         text: aMessage.displayMessage,
-        flags: ["outgoing", "incoming", "system", "autoResponse",
-                "containsNick", "error", "delayed",
-                "noFormat", "containsImages", "notification",
-                "noLinkification"].filter(f => aMessage[f]),
+        flags: [
+          "outgoing",
+          "incoming",
+          "system",
+          "autoResponse",
+          "containsNick",
+          "error",
+          "delayed",
+          "noFormat",
+          "containsImages",
+          "notification",
+          "noLinkification",
+        ].filter(f => aMessage[f]),
       };
       let alias = aMessage.alias;
-      if (alias && alias != msg.who)
+      if (alias && alias != msg.who) {
         msg.alias = alias;
+      }
       lineToWrite = JSON.stringify(msg) + "\n";
-    }
-    else {
+    } else {
       // Text log.
       let date = new Date(messageTime);
       let line = "(" + date.toLocaleTimeString() + ") ";
       let msg = this._serialize(aMessage.displayMessage);
-      if (aMessage.system)
+      if (aMessage.system) {
         line += msg;
-      else {
+      } else {
         let sender = aMessage.alias || aMessage.who;
-        if (aMessage.autoResponse)
+        if (aMessage.autoResponse) {
           line += sender + " <AUTO-REPLY>: " + msg;
-        else if (msg.startsWith("/me "))
+        } else if (msg.startsWith("/me ")) {
           line += "***" + sender + " " + msg.substr(4);
-        else
+        } else {
           line += sender + ": " + msg;
+        }
       }
       lineToWrite = line + kLineBreak;
     }
     lineToWrite = this.encoder.encode(lineToWrite);
     this._initialized.then(() => {
-      appendToFile(this.currentPath, lineToWrite)
-        .catch(aError => Cu.reportError("Failed to log message:\n" + aError));
+      appendToFile(this.currentPath, lineToWrite).catch(aError =>
+        Cu.reportError("Failed to log message:\n" + aError)
+      );
     });
   },
 };
 
 var dummyLogWriter = {
   paths: null,
   currentPath: null,
   logMessage() {},
 };
 
-
 var gLogWritersById = new Map();
 function getLogWriter(aConversation) {
   let id = aConversation.id;
   if (!gLogWritersById.has(id)) {
     let prefName =
       "purple.logging.log_" + (aConversation.isChat ? "chats" : "ims");
-    if (Services.prefs.getBoolPref(prefName))
+    if (Services.prefs.getBoolPref(prefName)) {
       gLogWritersById.set(id, new LogWriter(aConversation));
-    else
+    } else {
       gLogWritersById.set(id, dummyLogWriter);
+    }
   }
   return gLogWritersById.get(id);
 }
 
 function closeLogWriter(aConversation) {
   gLogWritersById.delete(aConversation.id);
 }
 
 // LogWriter for system logs.
 function SystemLogWriter(aAccount) {
   this._account = aAccount;
-  this.path = OS.Path.join(getLogFolderPathForAccount(aAccount), ".system",
-                           getNewLogFileName());
+  this.path = OS.Path.join(
+    getLogFolderPathForAccount(aAccount),
+    ".system",
+    getNewLogFileName()
+  );
   const dateTimeFormatter = new Services.intl.DateTimeFormat("en-US", {
-    dateStyle: "full", timeStyle: "long",
+    dateStyle: "full",
+    timeStyle: "long",
   });
-  let header = "System log for account " + aAccount.name +
-               " (" + aAccount.protocol.normalizedName +
-               ") connected at " +
-               dateTimeFormatter.format(new Date()) + kLineBreak;
-  this._initialized = appendToFile(this.path, this.encoder.encode(header), true);
+  let header =
+    "System log for account " +
+    aAccount.name +
+    " (" +
+    aAccount.protocol.normalizedName +
+    ") connected at " +
+    dateTimeFormatter.format(new Date()) +
+    kLineBreak;
+  this._initialized = appendToFile(
+    this.path,
+    this.encoder.encode(header),
+    true
+  );
   // Catch the error separately so that _initialized will stay rejected if
   // writing the header failed.
   this._initialized.catch(aError =>
-                          Cu.reportError("Error initializing system log:\n" + aError));
+    Cu.reportError("Error initializing system log:\n" + aError)
+  );
 }
 SystemLogWriter.prototype = {
   encoder: new TextEncoder(),
   // Constructor sets this to a promise that will resolve when the log header
   // has been written.
   _initialized: null,
   path: null,
   logEvent(aString) {
     let date = ToLocaleFormat("%x %X", new Date());
-    let lineToWrite =
-      this.encoder.encode("---- " + aString + " @ " + date + " ----" + kLineBreak);
+    let lineToWrite = this.encoder.encode(
+      "---- " + aString + " @ " + date + " ----" + kLineBreak
+    );
     this._initialized.then(() => {
-      appendToFile(this.path, lineToWrite)
-        .catch(aError => Cu.reportError("Failed to log event:\n" + aError));
+      appendToFile(this.path, lineToWrite).catch(aError =>
+        Cu.reportError("Failed to log event:\n" + aError)
+      );
     });
   },
 };
 
 var dummySystemLogWriter = {
   path: null,
   logEvent() {},
 };
 
-
 var gSystemLogWritersById = new Map();
 function getSystemLogWriter(aAccount, aCreate) {
   let id = aAccount.id;
   if (aCreate) {
-    if (!Services.prefs.getBoolPref("purple.logging.log_system"))
+    if (!Services.prefs.getBoolPref("purple.logging.log_system")) {
       return dummySystemLogWriter;
+    }
     let writer = new SystemLogWriter(aAccount);
     gSystemLogWritersById.set(id, writer);
     return writer;
   }
 
-  return gSystemLogWritersById.has(id) && gSystemLogWritersById.get(id) ||
-    dummySystemLogWriter;
+  return (
+    (gSystemLogWritersById.has(id) && gSystemLogWritersById.get(id)) ||
+    dummySystemLogWriter
+  );
 }
 
 function closeSystemLogWriter(aAccount) {
   gSystemLogWritersById.delete(aAccount.id);
 }
 
-
 /**
  * Takes a properly formatted log file name and extracts the date information
  * and filetype, returning the results as an Array.
  *
  * Filenames are expected to be formatted as:
  *
  * YYYY-MM-DD.HHmmSS+ZZzz.format
  *
@@ -405,89 +479,111 @@ function closeSystemLogWriter(aAccount) 
  * @returns an Array, where the first element is a Date object for the date
  *          that the log file represents, and the file type as a string.
  */
 function getDateFromFilename(aFilename) {
   const kRegExp = /([\d]{4})-([\d]{2})-([\d]{2}).([\d]{2})([\d]{2})([\d]{2})([+-])([\d]{2})([\d]{2}).*\.([A-Za-z]+)$/;
 
   let r = aFilename.match(kRegExp);
   if (!r) {
-    Cu.reportError("Found log file with name not maching YYYY-MM-DD.HHmmSS+ZZzz.format: " + aFilename);
+    Cu.reportError(
+      "Found log file with name not maching YYYY-MM-DD.HHmmSS+ZZzz.format: " +
+        aFilename
+    );
     return [];
   }
 
   // We ignore the timezone offset for now (FIXME)
   return [new Date(r[1], r[2] - 1, r[3], r[4], r[5], r[6]), r[10]];
 }
 
 /**
  * Returns true if a Conversation is both a chat conversation, and not
  * a Twitter conversation.
  */
 function convIsRealMUC(aConversation) {
-  return (aConversation.isChat &&
-          aConversation.account.protocol.id != "prpl-twitter");
+  return (
+    aConversation.isChat && aConversation.account.protocol.id != "prpl-twitter"
+  );
 }
 
-
 function LogMessage(aData, aConversation) {
   this._init(aData.who, aData.text);
   this._conversation = aConversation;
   this.time = Math.round(new Date(aData.date) / 1000);
-  if ("alias" in aData)
+  if ("alias" in aData) {
     this._alias = aData.alias;
-  for (let flag of aData.flags)
+  }
+  for (let flag of aData.flags) {
     this[flag] = true;
+  }
 }
 
 LogMessage.prototype = {
   __proto__: GenericMessagePrototype,
   _interfaces: [Ci.imIMessage, Ci.prplIMessage],
-  get displayMessage() { return this.originalMessage; },
+  get displayMessage() {
+    return this.originalMessage;
+  },
 };
 
-
 function LogConversation(aMessages, aProperties) {
   this._messages = aMessages;
-  for (let property in aProperties)
+  for (let property in aProperties) {
     this[property] = aProperties[property];
+  }
 }
 LogConversation.prototype = {
   __proto__: ClassInfo("imILogConversation", "Log conversation object"),
-  get isChat() { return this._isChat; },
-  get buddy() { return null; },
-  get account() { return {
-    alias: "",
-    name: this._accountName,
-    normalizedName: this._accountName,
-    protocol: {name: this._protocolName},
-    statusInfo: Services.core.globalUserStatus,
-  }; },
+  get isChat() {
+    return this._isChat;
+  },
+  get buddy() {
+    return null;
+  },
+  get account() {
+    return {
+      alias: "",
+      name: this._accountName,
+      normalizedName: this._accountName,
+      protocol: { name: this._protocolName },
+      statusInfo: Services.core.globalUserStatus,
+    };
+  },
   getMessages(aMessageCount) {
-    if (aMessageCount)
+    if (aMessageCount) {
       aMessageCount.value = this._messages.length;
+    }
     return this._messages.map(m => new LogMessage(m, this));
   },
   getMessagesEnumerator(aMessageCount) {
-    if (aMessageCount)
+    if (aMessageCount) {
       aMessageCount.value = this._messages.length;
+    }
     let enumerator = {
       _index: 0,
       _conv: this,
       _messages: this._messages,
-      hasMoreElements() { return this._index < this._messages.length; },
-      getNext() { return new LogMessage(this._messages[this._index++], this._conv); },
+      hasMoreElements() {
+        return this._index < this._messages.length;
+      },
+      getNext() {
+        return new LogMessage(this._messages[this._index++], this._conv);
+      },
       QueryInterface: ChromeUtils.generateQI([Ci.nsISimpleEnumerator]),
-      * [Symbol.iterator]() { while (this.hasMoreElements()) yield this.getNext(); },
+      *[Symbol.iterator]() {
+        while (this.hasMoreElements()) {
+          yield this.getNext();
+        }
+      },
     };
     return enumerator;
   },
 };
 
-
 /**
  * A Log object represents one or more log files. The constructor expects one
  * argument, which is either a single path to a (json or txt) log file or an
  * array of objects each having two properties:
  *   path: The full path of the (json only) log file it represents.
  *   time: The Date object extracted from the filename of the logfile.
  *
  * The returned Log object's time property will be:
@@ -508,18 +604,20 @@ function Log(aEntries) {
     this.time = date.valueOf() / 1000;
     this.format = format;
     // Wrap the path in an array
     this._entryPaths = [path];
     return;
   }
 
   if (!aEntries.length) {
-    throw new Error("Log was passed an invalid argument, " +
-                    "expected a non-empty array or a string.");
+    throw new Error(
+      "Log was passed an invalid argument, " +
+        "expected a non-empty array or a string."
+    );
   }
 
   // Assume aEntries is an array of objects.
   // Sort our list of entries for this day in increasing order.
   aEntries.sort((aLeft, aRight) => aLeft.time - aRight.time);
 
   this._entryPaths = aEntries.map(entry => entry.path);
   // Calculate the timestamp for the first entry down to the day.
@@ -540,29 +638,30 @@ Log.prototype = {
   async getConversation() {
     /*
      * Read the set of log files asynchronously and return a promise that
      * resolves to a LogConversation instance. Even if a file contains some
      * junk (invalid JSON), messages that are valid will be read. If the first
      * line of metadata is corrupt however, the data isn't useful and the
      * promise will resolve to null.
      */
-    if (this.format != "json")
+    if (this.format != "json") {
       return null;
+    }
     let messages = [];
     let properties = {};
     let firstFile = true;
     let decoder = new TextDecoder();
     for (let path of this._entryPaths) {
       let lines;
       try {
         let contents = await queueFileOperation(path, () => OS.File.read(path));
         lines = decoder.decode(contents).split("\n");
       } catch (aError) {
-        Cu.reportError("Error reading log file \"" + path + "\":\n" + aError);
+        Cu.reportError('Error reading log file "' + path + '":\n' + aError);
         continue;
       }
       let nextLine = lines.shift();
       let filename = OS.Path.basename(path);
 
       let data;
       try {
         // This will fail if either nextLine is undefined, or not valid JSON.
@@ -594,35 +693,37 @@ Log.prototype = {
         properties._protocolName = data.protocol;
         properties._isChat = data.isChat;
         properties.normalizedName = data.normalizedName;
         firstFile = false;
       }
 
       while (lines.length) {
         nextLine = lines.shift();
-        if (!nextLine)
+        if (!nextLine) {
           break;
+        }
         try {
           messages.push(JSON.parse(nextLine));
         } catch (e) {
           // If a message line contains junk, just ignore the error and
           // continue reading the conversation.
         }
       }
     }
 
-    if (firstFile) // All selected log files are invalid.
+    if (firstFile) {
+      // All selected log files are invalid.
       return null;
+    }
 
     return new LogConversation(messages, properties);
   },
 };
 
-
 /**
  * Log enumerators provide lists of log files ("entries"). aEntries is an array
  * of the OS.File.DirectoryIterator.Entry instances which represent the log
  * files to be parsed.
  *
  * DailyLogEnumerator organizes entries by date, and enumerates them in order.
  * LogEnumerator enumerates logs in the same order as the input array.
  */
@@ -645,320 +746,393 @@ function DailyLogEnumerator(aEntries) {
       // into the same Arrays. We clone the date for the log, reset it to
       // the 0th hour/minute/second, and use that to construct an ID for the
       // Array we'll put the log in.
       dateForID.setHours(0);
       dateForID.setMinutes(0);
       dateForID.setSeconds(0);
       dayID = dateForID.toISOString();
 
-      if (!(dayID in this._entries))
+      if (!(dayID in this._entries)) {
         this._entries[dayID] = [];
+      }
 
       this._entries[dayID].push({
         path,
         time: logDate,
       });
-    }
-    else {
+    } else {
       // Add legacy text logs as individual paths.
       dayID = dateForID.toISOString() + "txt";
       this._entries[dayID] = path;
     }
   }
 
   this._days = Object.keys(this._entries).sort();
   this._index = 0;
 }
 DailyLogEnumerator.prototype = {
   _entries: {},
   _days: [],
   _index: 0,
-  hasMoreElements() { return this._index < this._days.length; },
+  hasMoreElements() {
+    return this._index < this._days.length;
+  },
   getNext() {
     let dayID = this._days[this._index++];
     return new Log(this._entries[dayID]);
   },
   QueryInterface: ChromeUtils.generateQI([Ci.nsISimpleEnumerator]),
-  * [Symbol.iterator]() { while (this.hasMoreElements()) yield this.getNext(); },
+  *[Symbol.iterator]() {
+    while (this.hasMoreElements()) {
+      yield this.getNext();
+    }
+  },
 };
 
 function LogEnumerator(aEntries) {
   this._entries = aEntries;
   this._entries.sort((a, b) => a.name > b.name);
 }
 LogEnumerator.prototype = {
   _entries: [],
   hasMoreElements() {
     return this._entries.length > 0;
   },
   getNext() {
     // Create and return a log from the first entry.
     return new Log(this._entries.shift().path);
   },
   QueryInterface: ChromeUtils.generateQI([Ci.nsISimpleEnumerator]),
-  * [Symbol.iterator]() { while (this.hasMoreElements()) yield this.getNext(); },
+  *[Symbol.iterator]() {
+    while (this.hasMoreElements()) {
+      yield this.getNext();
+    }
+  },
 };
 
-
-function Logger() { }
+function Logger() {}
 Logger.prototype = {
   // Returned Promise resolves to an array of entries for the
   // log folder if it exists, otherwise null.
   async _getLogArray(aAccount, aNormalizedName) {
     let iterator, path;
     try {
-      path = OS.Path.join(getLogFolderPathForAccount(aAccount),
-                          encodeName(aNormalizedName));
+      path = OS.Path.join(
+        getLogFolderPathForAccount(aAccount),
+        encodeName(aNormalizedName)
+      );
       if (await queueFileOperation(path, () => OS.File.exists(path))) {
         iterator = new OS.File.DirectoryIterator(path);
         let entries = await iterator.nextBatch();
         iterator.close();
         return entries;
       }
     } catch (aError) {
-      if (iterator)
+      if (iterator) {
         iterator.close();
-      Cu.reportError("Error getting directory entries for \"" +
-                     path + "\":\n" + aError);
+      }
+      Cu.reportError(
+        'Error getting directory entries for "' + path + '":\n' + aError
+      );
     }
     return [];
   },
   getLogFromFile(aFilePath, aGroupByDay) {
-    if (!aGroupByDay)
+    if (!aGroupByDay) {
       return Promise.resolve(new Log(aFilePath));
+    }
     let [targetDate] = getDateFromFilename(OS.Path.basename(aFilePath));
-    if (!targetDate)
+    if (!targetDate) {
       return null;
+    }
 
     targetDate = targetDate.toDateString();
 
     // We'll assume that the files relevant to our interests are
     // in the same folder as the one provided.
     let iterator = new OS.File.DirectoryIterator(OS.Path.dirname(aFilePath));
     let relevantEntries = [];
-    return iterator.forEach(function(aEntry) {
-      if (aEntry.isDir)
-        return;
-      let path = aEntry.path;
-      let [logTime] = getDateFromFilename(OS.Path.basename(path));
+    return iterator
+      .forEach(function(aEntry) {
+        if (aEntry.isDir) {
+          return;
+        }
+        let path = aEntry.path;
+        let [logTime] = getDateFromFilename(OS.Path.basename(path));
 
-      // If someone placed a 'foreign' file into the logs directory,
-      // pattern matching fails and getDateFromFilename() returns [].
-      if (logTime && targetDate == logTime.toDateString()) {
-        relevantEntries.push({
-          path,
-          time: logTime,
-        });
-      }
-    }).then(() => {
-      iterator.close();
-      return new Log(relevantEntries);
-    }, aError => {
-      iterator.close();
-      throw aError;
-    });
+        // If someone placed a 'foreign' file into the logs directory,
+        // pattern matching fails and getDateFromFilename() returns [].
+        if (logTime && targetDate == logTime.toDateString()) {
+          relevantEntries.push({
+            path,
+            time: logTime,
+          });
+        }
+      })
+      .then(
+        () => {
+          iterator.close();
+          return new Log(relevantEntries);
+        },
+        aError => {
+          iterator.close();
+          throw aError;
+        }
+      );
   },
   // Creates and returns the appropriate LogEnumerator for the given log array
   // depending on aGroupByDay, or an EmptyEnumerator if the input array is empty.
   _getEnumerator(aLogArray, aGroupByDay) {
     let enumerator = aGroupByDay ? DailyLogEnumerator : LogEnumerator;
     return aLogArray.length ? new enumerator(aLogArray) : EmptyEnumerator;
   },
   async getLogPathsForConversation(aConversation) {
     let writer = gLogWritersById.get(aConversation.id);
     // Resolve to null if we haven't created a LogWriter yet for this conv, or
     // if logging is disabled (paths will be null).
-    if (!writer || !writer.paths)
+    if (!writer || !writer.paths) {
       return null;
+    }
     let paths = writer.paths;
     // Wait for any pending file operations to finish, then resolve to the paths
     // regardless of whether these operations succeeded.
-    for (let path of paths)
+    for (let path of paths) {
       await gFilePromises.get(path);
+    }
     return paths;
   },
   getLogsForAccountAndName(aAccount, aNormalizedName, aGroupByDay) {
-    return this._getLogArray(aAccount, aNormalizedName)
-               .then(aEntries => this._getEnumerator(aEntries, aGroupByDay));
+    return this._getLogArray(aAccount, aNormalizedName).then(aEntries =>
+      this._getEnumerator(aEntries, aGroupByDay)
+    );
   },
   getLogsForAccountBuddy(aAccountBuddy, aGroupByDay) {
-    return this.getLogsForAccountAndName(aAccountBuddy.account,
-                                         aAccountBuddy.normalizedName, aGroupByDay);
+    return this.getLogsForAccountAndName(
+      aAccountBuddy.account,
+      aAccountBuddy.normalizedName,
+      aGroupByDay
+    );
   },
   async getLogsForBuddy(aBuddy, aGroupByDay) {
     let entries = [];
     for (let accountBuddy of aBuddy.getAccountBuddies()) {
-      entries = entries.concat(await this._getLogArray(accountBuddy.account,
-                                                       accountBuddy.normalizedName));
+      entries = entries.concat(
+        await this._getLogArray(
+          accountBuddy.account,
+          accountBuddy.normalizedName
+        )
+      );
     }
     return this._getEnumerator(entries, aGroupByDay);
   },
   async getLogsForContact(aContact, aGroupByDay) {
     let entries = [];
     for (let buddy of aContact.getBuddies()) {
       for (let accountBuddy of buddy.getAccountBuddies()) {
-        entries = entries.concat(await this._getLogArray(accountBuddy.account,
-                                                         accountBuddy.normalizedName));
+        entries = entries.concat(
+          await this._getLogArray(
+            accountBuddy.account,
+            accountBuddy.normalizedName
+          )
+        );
       }
     }
     return this._getEnumerator(entries, aGroupByDay);
   },
   getLogsForConversation(aConversation, aGroupByDay) {
     let name = aConversation.normalizedName;
-    if (convIsRealMUC(aConversation))
+    if (convIsRealMUC(aConversation)) {
       name += ".chat";
-    return this.getLogsForAccountAndName(aConversation.account, name, aGroupByDay);
+    }
+    return this.getLogsForAccountAndName(
+      aConversation.account,
+      name,
+      aGroupByDay
+    );
   },
   getSystemLogsForAccount(aAccount) {
     return this.getLogsForAccountAndName(aAccount, ".system");
   },
   async getSimilarLogs(aLog, aGroupByDay) {
     let iterator = new OS.File.DirectoryIterator(OS.Path.dirname(aLog.path));
     let entries;
     try {
       entries = await iterator.nextBatch();
     } catch (aError) {
-      Cu.reportError("Error getting similar logs for \"" +
-                     aLog.path + "\":\n" + aError);
+      Cu.reportError(
+        'Error getting similar logs for "' + aLog.path + '":\n' + aError
+      );
     }
     // If there was an error, this will return an EmptyEnumerator.
     return this._getEnumerator(entries, aGroupByDay);
   },
 
   getLogFolderPathForAccount(aAccount) {
     return getLogFolderPathForAccount(aAccount);
   },
 
   deleteLogFolderForAccount(aAccount) {
-    if (!aAccount.disconnecting && !aAccount.disconnected)
-      throw new Error("Account must be disconnected first before deleting logs.");
+    if (!aAccount.disconnecting && !aAccount.disconnected) {
+      throw new Error(
+        "Account must be disconnected first before deleting logs."
+      );
+    }
 
-    if (aAccount.disconnecting)
-      Cu.reportError("Account is still disconnecting while we attempt to remove logs.");
+    if (aAccount.disconnecting) {
+      Cu.reportError(
+        "Account is still disconnecting while we attempt to remove logs."
+      );
+    }
 
     let logPath = this.getLogFolderPathForAccount(aAccount);
     // Find all operations on files inside the log folder.
     let pendingPromises = [];
     function checkLogFiles(promiseOperation, filePath) {
-      if (filePath.startsWith(logPath))
+      if (filePath.startsWith(logPath)) {
         pendingPromises.push(promiseOperation);
+      }
     }
     gFilePromises.forEach(checkLogFiles);
     // After all operations finish, remove the whole log folder.
     return Promise.all(pendingPromises)
-                  .then(values => { OS.File.removeDir(logPath, { ignoreAbsent: true }); })
-                  .catch(aError => Cu.reportError("Failed to remove log folders:\n" + aError));
+      .then(values => {
+        OS.File.removeDir(logPath, { ignoreAbsent: true });
+      })
+      .catch(aError =>
+        Cu.reportError("Failed to remove log folders:\n" + aError)
+      );
   },
 
   async forEach(aCallback) {
     let getAllSubdirs = async function(aPaths, aErrorMsg) {
       let entries = [];
       for (let path of aPaths) {
         let iterator = new OS.File.DirectoryIterator(path);
         try {
           entries = entries.concat(await iterator.nextBatch());
         } catch (aError) {
-          if (aErrorMsg)
+          if (aErrorMsg) {
             Cu.reportError(aErrorMsg + "\n" + aError);
+          }
         } finally {
           iterator.close();
         }
       }
-      entries = entries.filter(aEntry => aEntry.isDir)
-                       .map(aEntry => aEntry.path);
+      entries = entries
+        .filter(aEntry => aEntry.isDir)
+        .map(aEntry => aEntry.path);
       return entries;
     };
 
     let logsPath = OS.Path.join(OS.Constants.Path.profileDir, "logs");
     let prpls = await getAllSubdirs([logsPath]);
-    let accounts =
-      await getAllSubdirs(prpls, "Error while sweeping prpl folder:");
-    let logFolders =
-      await getAllSubdirs(accounts, "Error while sweeping account folder:");
+    let accounts = await getAllSubdirs(
+      prpls,
+      "Error while sweeping prpl folder:"
+    );
+    let logFolders = await getAllSubdirs(
+      accounts,
+      "Error while sweeping account folder:"
+    );
     for (let folder of logFolders) {
       let iterator = new OS.File.DirectoryIterator(folder);
       try {
         await iterator.forEach(aEntry => {
-          if (aEntry.isDir || !aEntry.name.endsWith(".json"))
+          if (aEntry.isDir || !aEntry.name.endsWith(".json")) {
             return null;
+          }
           return aCallback.processLog(aEntry.path);
         });
       } catch (aError) {
         // If the callback threw, reject the promise and let the caller handle it.
-        if (!(aError instanceof OS.File.Error))
+        if (!(aError instanceof OS.File.Error)) {
           throw aError;
+        }
         Cu.reportError("Error sweeping log folder:\n" + aError);
       } finally {
         iterator.close();
       }
     }
   },
 
   observe(aSubject, aTopic, aData) {
     switch (aTopic) {
-    case "profile-after-change":
-      Services.obs.addObserver(this, "final-ui-startup");
-      break;
-    case "final-ui-startup":
-      Services.obs.removeObserver(this, "final-ui-startup");
-      ["new-text", "conversation-closed", "conversation-left-chat",
-       "account-connected", "account-disconnected",
-       "account-buddy-status-changed"].forEach(function(aEvent) {
-        Services.obs.addObserver(this, aEvent);
-      }, this);
-      break;
-    case "new-text":
-      let excludeBecauseEncrypted = false;
-      if (aSubject.encrypted) {
-        excludeBecauseEncrypted = !Services.prefs.getBoolPref(
-          "messenger.account." + aSubject.conversation.account.id +
-          ".options.otrAllowMsgLog",
-          Services.prefs.getBoolPref("chat.otr.default.allowMsgLog"));
-      }
-      if (!aSubject.noLog && !excludeBecauseEncrypted) {
-        let log = getLogWriter(aSubject.conversation);
-        log.logMessage(aSubject);
-      }
-      break;
-    case "conversation-closed":
-    case "conversation-left-chat":
-      closeLogWriter(aSubject);
-      break;
-    case "account-connected":
-      getSystemLogWriter(aSubject, true).logEvent("+++ " + aSubject.name +
-                                                " signed on");
-      break;
-    case "account-disconnected":
-      getSystemLogWriter(aSubject).logEvent("+++ " + aSubject.name +
-                                          " signed off");
-      closeSystemLogWriter(aSubject);
-      break;
-    case "account-buddy-status-changed":
-      let status;
-      if (!aSubject.online)
-        status = "Offline";
-      else if (aSubject.mobile)
-        status = "Mobile";
-      else if (aSubject.idle)
-        status = "Idle";
-      else if (aSubject.available)
-        status = "Available";
-      else
-        status = "Unavailable";
+      case "profile-after-change":
+        Services.obs.addObserver(this, "final-ui-startup");
+        break;
+      case "final-ui-startup":
+        Services.obs.removeObserver(this, "final-ui-startup");
+        [
+          "new-text",
+          "conversation-closed",
+          "conversation-left-chat",
+          "account-connected",
+          "account-disconnected",
+          "account-buddy-status-changed",
+        ].forEach(function(aEvent) {
+          Services.obs.addObserver(this, aEvent);
+        }, this);
+        break;
+      case "new-text":
+        let excludeBecauseEncrypted = false;
+        if (aSubject.encrypted) {
+          excludeBecauseEncrypted = !Services.prefs.getBoolPref(
+            "messenger.account." +
+              aSubject.conversation.account.id +
+              ".options.otrAllowMsgLog",
+            Services.prefs.getBoolPref("chat.otr.default.allowMsgLog")
+          );
+        }
+        if (!aSubject.noLog && !excludeBecauseEncrypted) {
+          let log = getLogWriter(aSubject.conversation);
+          log.logMessage(aSubject);
+        }
+        break;
+      case "conversation-closed":
+      case "conversation-left-chat":
+        closeLogWriter(aSubject);
+        break;
+      case "account-connected":
+        getSystemLogWriter(aSubject, true).logEvent(
+          "+++ " + aSubject.name + " signed on"
+        );
+        break;
+      case "account-disconnected":
+        getSystemLogWriter(aSubject).logEvent(
+          "+++ " + aSubject.name + " signed off"
+        );
+        closeSystemLogWriter(aSubject);
+        break;
+      case "account-buddy-status-changed":
+        let status;
+        if (!aSubject.online) {
+          status = "Offline";
+        } else if (aSubject.mobile) {
+          status = "Mobile";
+        } else if (aSubject.idle) {
+          status = "Idle";
+        } else if (aSubject.available) {
+          status = "Available";
+        } else {
+          status = "Unavailable";
+        }
 
-      let statusText = aSubject.statusText;
-      if (statusText)
-        status += " (\"" + statusText + "\")";
+        let statusText = aSubject.statusText;
+        if (statusText) {
+          status += ' ("' + statusText + '")';
+        }
 
-      let nameText = aSubject.displayName + " (" + aSubject.userName + ")";
-      getSystemLogWriter(aSubject.account).logEvent(nameText + " is now " + status);
-      break;
-    default:
-      throw new Error("Unexpected notification " + aTopic);
+        let nameText = aSubject.displayName + " (" + aSubject.userName + ")";
+        getSystemLogWriter(aSubject.account).logEvent(
+          nameText + " is now " + status
+        );
+        break;
+      default:
+        throw new Error("Unexpected notification " + aTopic);
     }
   },
 
   QueryInterface: ChromeUtils.generateQI([Ci.nsIObserver, Ci.imILogger]),
   classDescription: "Logger",
   classID: Components.ID("{fb0dc220-2c7a-4216-9f19-6b8f3480eae9}"),
   contractID: "@mozilla.org/chat/logger;1",
 };
--- a/chat/components/src/smileProtocolHandler.js
+++ b/chat/components/src/smileProtocolHandler.js
@@ -1,41 +1,46 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-var {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm");
-var {XPCOMUtils} = ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
+var { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
+var { XPCOMUtils } = ChromeUtils.import(
+  "resource://gre/modules/XPCOMUtils.jsm"
+);
 var {
   smileImMarkup,
   smileTextNode,
   smileString,
   getSmileRealURI,
   getSmileyList,
 } = ChromeUtils.import("resource:///modules/imSmileys.jsm");
 
 var kSmileRegexp = /^smile:\/\//;
 
-function smileProtocolHandler() { }
+function smileProtocolHandler() {}
 
 smileProtocolHandler.prototype = {
   scheme: "smile",
   defaultPort: -1,
-  protocolFlags: Ci.nsIProtocolHandler.URI_NORELATIVE |
-                 Ci.nsIProtocolHandler.URI_NOAUTH |
-                 Ci.nsIProtocolHandler.URI_IS_UI_RESOURCE |
-                 Ci.nsIProtocolHandler.URI_IS_LOCAL_RESOURCE,
+  protocolFlags:
+    Ci.nsIProtocolHandler.URI_NORELATIVE |
+    Ci.nsIProtocolHandler.URI_NOAUTH |
+    Ci.nsIProtocolHandler.URI_IS_UI_RESOURCE |
+    Ci.nsIProtocolHandler.URI_IS_LOCAL_RESOURCE,
   newChannel(aURI, aLoadInfo) {
     let smile = aURI.spec.replace(kSmileRegexp, "");
     let uri = Services.io.newURI(getSmileRealURI(smile));
     let channel = Services.io.newChannelFromURIWithLoadInfo(uri, aLoadInfo);
     channel.originalURI = aURI;
     return channel;
   },
-  allowPort(aPort, aScheme) { return false; },
+  allowPort(aPort, aScheme) {
+    return false;
+  },
 
   classDescription: "Smile Protocol Handler",
   classID: Components.ID("{04e58eae-dfbc-4c9e-8130-6d9ef19cbff4}"),
   contractID: "@mozilla.org/network/protocol;1?name=smile",
   QueryInterface: ChromeUtils.generateQI([Ci.nsIProtocolHandler]),
 };
 
 var NSGetFactory = XPCOMUtils.generateNSGetFactory([smileProtocolHandler]);
--- a/chat/components/src/test/test_accounts.js
+++ b/chat/components/src/test/test_accounts.js
@@ -1,14 +1,16 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-var {Services} = ChromeUtils.import("resource:///modules/imServices.jsm");
-const {updateAppInfo} = ChromeUtils.import("resource://testing-common/AppInfo.jsm");
+var { Services } = ChromeUtils.import("resource:///modules/imServices.jsm");
+const { updateAppInfo } = ChromeUtils.import(
+  "resource://testing-common/AppInfo.jsm"
+);
 
 function run_test() {
   do_get_profile();
 
   // Test the handling of accounts for unknown protocols.
   const kAccountName = "Unknown";
   const kPrplId = "prpl-unknown";
 
@@ -23,15 +25,18 @@ function run_test() {
     updateAppInfo();
     Services.core.init();
 
     let account = Services.accounts.getAccountByNumericId(1);
     Assert.ok(account instanceof Ci.imIAccount);
     Assert.equal(account.name, kAccountName);
     Assert.equal(account.normalizedName, kAccountName);
     Assert.equal(account.protocol.id, kPrplId);
-    Assert.equal(account.connectionErrorReason, Ci.imIAccount.ERROR_UNKNOWN_PRPL);
+    Assert.equal(
+      account.connectionErrorReason,
+      Ci.imIAccount.ERROR_UNKNOWN_PRPL
+    );
   } finally {
     Services.core.quit();
 
     prefs.deleteBranch("messenger");
   }
 }
--- a/chat/components/src/test/test_commands.js
+++ b/chat/components/src/test/test_commands.js
@@ -1,153 +1,240 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-var {Services} = ChromeUtils.import("resource:///modules/imServices.jsm");
+var { Services } = ChromeUtils.import("resource:///modules/imServices.jsm");
 // We don't load the command service via Services as we want to access
 // _findCommands in order to avoid having to intercept command execution.
 var imCommands = {};
-Services.scriptloader.loadSubScript("resource:///components/imCommands.js", imCommands);
+Services.scriptloader.loadSubScript(
+  "resource:///components/imCommands.js",
+  imCommands
+);
 
 var kPrplId = "green";
 var kPrplId2 = "red";
 
 var fakeAccount = {
   connected: true,
-  protocol: {id: kPrplId},
+  protocol: { id: kPrplId },
 };
 var fakeDisconnectedAccount = {
   connected: false,
-  protocol: {id: kPrplId},
+  protocol: { id: kPrplId },
 };
 var fakeAccount2 = {
   connected: true,
-  protocol: {id: kPrplId2},
+  protocol: { id: kPrplId2 },
 };
 
 var fakeConversation = {
   account: fakeAccount,
   isChat: true,
 };
 
 function fakeCommand(aName, aUsageContext) {
   this.name = aName;
-  if (aUsageContext)
+  if (aUsageContext) {
     this.usageContext = aUsageContext;
+  }
 }
 fakeCommand.prototype = {
-  get helpString() { return ""; },
+  get helpString() {
+    return "";
+  },
   usageContext: Ci.imICommand.CMD_CONTEXT_ALL,
   priority: Ci.imICommand.CMD_PRIORITY_PRPL,
   run: (aMsg, aConv) => true,
 };
 
 function run_test() {
   let cmdserv = new imCommands.CommandsService();
   cmdserv.initCommands();
 
   // Some commands providing multiple possible completions.
   cmdserv.registerCommand(new fakeCommand("banana"), kPrplId2);
   cmdserv.registerCommand(new fakeCommand("baloney"), kPrplId2);
 
   // MUC-only command.
-  cmdserv.registerCommand(new fakeCommand("balderdash",
-                                          Ci.imICommand.CMD_CONTEXT_CHAT),
-                          kPrplId);
+  cmdserv.registerCommand(
+    new fakeCommand("balderdash", Ci.imICommand.CMD_CONTEXT_CHAT),
+    kPrplId
+  );
 
   // Name clashes with global command.
   cmdserv.registerCommand(new fakeCommand("offline"), kPrplId);
 
   // Name starts with another command name.
   cmdserv.registerCommand(new fakeCommand("helpme"), kPrplId);
 
   // Command name contains numbers.
   cmdserv.registerCommand(new fakeCommand("r9kbeta"), kPrplId);
 
   // Array of (possibly partial) command names as entered by the user.
-  let testCmds = ["x", "b", "ba", "bal", "back", "hel", "help", "off", "offline"];
+  let testCmds = [
+    "x",
+    "b",
+    "ba",
+    "bal",
+    "back",
+    "hel",
+    "help",
+    "off",
+    "offline",
+  ];
 
   // We test an array of different possible conversations.
   // cmdlist lists all the available commands for the given conversation.
   // results is an array which for each testCmd provides an array containing
   // data with which the return value of _findCommands can be checked. In
   // particular, the name of the command and whether the first (i.e. preferred)
   // entry in the returned array of commands is a prpl command. (If the latter
   // boolean is not given, false is assumed, if the name is not given, that
   // corresponds to no commands being returned.)
   let testData = [
     {
       desc: "No conversation argument.",
       cmdlist: "away, back, busy, dnd, help, offline, raw, say",
-      results: [[], [], ["back"], [], ["back"], ["help"], ["help"], ["offline"], ["offline"]],
+      results: [
+        [],
+        [],
+        ["back"],
+        [],
+        ["back"],
+        ["help"],
+        ["help"],
+        ["offline"],
+        ["offline"],
+      ],
     },
     {
       desc: "Disconnected conversation with fakeAccount.",
       conv: {
         account: fakeDisconnectedAccount,
       },
-      cmdlist: "away, back, busy, dnd, help, helpme, offline, offline, r9kbeta, raw, say",
-      results: [[], [], ["back"], [], ["back"], ["help"], ["help"], ["offline"], ["offline"]],
+      cmdlist:
+        "away, back, busy, dnd, help, helpme, offline, offline, r9kbeta, raw, say",
+      results: [
+        [],
+        [],
+        ["back"],
+        [],
+        ["back"],
+        ["help"],
+        ["help"],
+        ["offline"],
+        ["offline"],
+      ],
     },
     {
       desc: "Conversation with fakeAccount.",
       conv: {
         account: fakeAccount,
       },
-      cmdlist: "away, back, busy, dnd, help, helpme, offline, offline, r9kbeta, raw, say",
-      results: [[], [], ["back"], [], ["back"], [], ["help"], ["offline"], ["offline"]],
+      cmdlist:
+        "away, back, busy, dnd, help, helpme, offline, offline, r9kbeta, raw, say",
+      results: [
+        [],
+        [],
+        ["back"],
+        [],
+        ["back"],
+        [],
+        ["help"],
+        ["offline"],
+        ["offline"],
+      ],
     },
     {
       desc: "MUC with fakeAccount.",
       conv: {
         account: fakeAccount,
         isChat: true,
       },
-      cmdlist: "away, back, balderdash, busy, dnd, help, helpme, offline, offline, r9kbeta, raw, say",
-      results: [[], [], [], ["balderdash", true], ["back"], [], ["help"], ["offline"], ["offline"]],
+      cmdlist:
+        "away, back, balderdash, busy, dnd, help, helpme, offline, offline, r9kbeta, raw, say",
+      results: [
+        [],
+        [],
+        [],
+        ["balderdash", true],
+        ["back"],
+        [],
+        ["help"],
+        ["offline"],
+        ["offline"],
+      ],
     },
     {
       desc: "Conversation with fakeAccount2.",
       conv: {
         account: fakeAccount2,
       },
-      cmdlist: "away, back, baloney, banana, busy, dnd, help, offline, raw, say",
-      results: [[], [], [], ["baloney", true], ["back"], ["help"], ["help"], ["offline"], ["offline"]],
+      cmdlist:
+        "away, back, baloney, banana, busy, dnd, help, offline, raw, say",
+      results: [
+        [],
+        [],
+        [],
+        ["baloney", true],
+        ["back"],
+        ["help"],
+        ["help"],
+        ["offline"],
+        ["offline"],
+      ],
     },
     {
       desc: "MUC with fakeAccount2.",
       conv: {
         account: fakeAccount2,
         isChat: true,
       },
-      cmdlist: "away, back, baloney, banana, busy, dnd, help, offline, raw, say",
-      results: [[], [], [], ["baloney", true], ["back"], ["help"], ["help"], ["offline"], ["offline"]],
+      cmdlist:
+        "away, back, baloney, banana, busy, dnd, help, offline, raw, say",
+      results: [
+        [],
+        [],
+        [],
+        ["baloney", true],
+        ["back"],
+        ["help"],
+        ["help"],
+        ["offline"],
+        ["offline"],
+      ],
     },
   ];
 
   for (let test of testData) {
     info("The following tests are with: " + test.desc);
 
     // Check which commands are available in which context.
-    let cmdlist = cmdserv.listCommandsForConversation(test.conv, {})
-                         .map(aCmd => aCmd.name).sort().join(", ");
+    let cmdlist = cmdserv
+      .listCommandsForConversation(test.conv, {})
+      .map(aCmd => aCmd.name)
+      .sort()
+      .join(", ");
     Assert.equal(cmdlist, test.cmdlist);
 
     for (let testCmd of testCmds) {
       info("Testing command found for '" + testCmd + "'");
       let expectedResult = test.results.shift();
       let cmdArray = cmdserv._findCommands(test.conv, testCmd);
       // Check whether commands are only returned when appropriate.
       Assert.equal(cmdArray.length > 0, expectedResult.length > 0);
       if (cmdArray.length) {
         // Check if the right command was returned.
         Assert.equal(cmdArray[0].name, expectedResult[0]);
-        Assert.equal(cmdArray[0].priority == Ci.imICommand.CMD_PRIORITY_PRPL,
-                     !!expectedResult[1]);
+        Assert.equal(
+          cmdArray[0].priority == Ci.imICommand.CMD_PRIORITY_PRPL,
+          !!expectedResult[1]
+        );
       }
     }
   }
 
   // Array of messages to test command execution of.
   let testMessages = [
     {
       message: "/r9kbeta",
@@ -169,13 +256,16 @@ function run_test() {
       message: "/notregistered",
       result: false,
     },
   ];
 
   // Test command execution.
   for (let executionTest of testMessages) {
     info("Testing command execution for '" + executionTest.message + "'");
-    Assert.equal(cmdserv.executeCommand(executionTest.message, fakeConversation), executionTest.result);
+    Assert.equal(
+      cmdserv.executeCommand(executionTest.message, fakeConversation),
+      executionTest.result
+    );
   }
 
   cmdserv.unInitCommands();
 }
--- a/chat/components/src/test/test_conversations.js
+++ b/chat/components/src/test/test_conversations.js
@@ -1,68 +1,83 @@
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
-var {Services} = ChromeUtils.import("resource:///modules/imServices.jsm");
-var {
-  GenericConvIMPrototype,
-  Message,
-} = ChromeUtils.import("resource:///modules/jsProtoHelper.jsm");
+var { Services } = ChromeUtils.import("resource:///modules/imServices.jsm");
+var { GenericConvIMPrototype, Message } = ChromeUtils.import(
+  "resource:///modules/jsProtoHelper.jsm"
+);
 
 var imConversations = {};
 Services.scriptloader.loadSubScript(
-  "resource:///components/imConversations.js", imConversations
+  "resource:///components/imConversations.js",
+  imConversations
 );
 
 // Fake prplConversation
 var _id = 0;
 function Conversation(aName) {
   this._name = aName;
   this._observers = [];
   this._date = Date.now() * 1000;
   this.id = ++_id;
 }
 Conversation.prototype = {
   __proto__: GenericConvIMPrototype,
   _account: {
     imAccount: {
-      protocol: {name: "Fake Protocol"},
+      protocol: { name: "Fake Protocol" },
       alias: "",
       name: "Fake Account",
     },
-    ERROR(e) { throw e; },
+    ERROR(e) {
+      throw e;
+    },
     DEBUG() {},
   },
   addObserver(aObserver) {
-    if (!(aObserver instanceof Ci.nsIObserver))
-      aObserver = {observe: aObserver};
+    if (!(aObserver instanceof Ci.nsIObserver)) {
+      aObserver = { observe: aObserver };
+    }
     GenericConvIMPrototype.addObserver.call(this, aObserver);
   },
 };
 
 // Ensure that when iMsg.message is set to a message (including the empty
 // string), it returns that message. If not, it should return the original
 // message. This prevents regressions due to JS coercions.
 var test_null_message = function() {
   let originalMessage = "Hi!";
   let pMsg = new Message("buddy", originalMessage, {
-    outgoing: true, _alias: "buddy", time: Date.now(),
+    outgoing: true,
+    _alias: "buddy",
+    time: Date.now(),
   });
   let iMsg = new imConversations.imMessage(pMsg);
   equal(iMsg.message, originalMessage, "Expected the original message.");
   // Setting the message should prevent a fallback to the original.
   iMsg.message = "";
-  equal(iMsg.message, "", "Expected an empty string; not the original message.");
-  equal(iMsg.originalMessage, originalMessage, "Expected the original message.");
+  equal(
+    iMsg.message,
+    "",
+    "Expected an empty string; not the original message."
+  );
+  equal(
+    iMsg.originalMessage,
+    originalMessage,
+    "Expected the original message."
+  );
 };
 
 // ROT13, used as an example transformation.
 function rot13(aString) {
   return aString.replace(/[a-zA-Z]/g, function(c) {
-    return String.fromCharCode(c.charCodeAt(0) + (c.toLowerCase() < "n" ? 1 : -1) * 13);
+    return String.fromCharCode(
+      c.charCodeAt(0) + (c.toLowerCase() < "n" ? 1 : -1) * 13
+    );
   });
 }
 
 // A test that exercises the message transformation pipeline.
 //
 // From the sending users perspective, this looks like:
 //   -> UIConv sendMsg
 //   -> UIConv notifyObservers `preparing-message`
@@ -81,90 +96,117 @@ function rot13(aString) {
 //   -> UIConv notifyObservers `received-message`
 //   -> protocol prepareForDisplaying
 //   -> UIConv notifyObservers `new-text`
 //
 // The test walks the sending path, which covers both.
 var test_message_transformation = function() {
   let conv = new Conversation();
   conv.sendMsg = function(aMsg) {
-    this.writeMessage("user", aMsg, {outgoing: true});
+    this.writeMessage("user", aMsg, { outgoing: true });
   };
 
   let uiConv = new imConversations.UIConversation(conv);
   let message = "Hello!";
-  let receivedMsg = false, newTxt = false;
+  let receivedMsg = false,
+    newTxt = false;
 
   uiConv.addObserver({
     observe(aObject, aTopic, aMsg) {
       switch (aTopic) {
         case "sending-message":
           ok(!newTxt, "sending-message should fire before new-text.");
-          ok(!receivedMsg, "sending-message should fire before received-message.");
-          ok(aObject.QueryInterface(Ci.imIOutgoingMessage), "Wrong message type.");
+          ok(
+            !receivedMsg,
+            "sending-message should fire before received-message."
+          );
+          ok(
+            aObject.QueryInterface(Ci.imIOutgoingMessage),
+            "Wrong message type."
+          );
           aObject.message = rot13(aObject.message);
           break;
         case "received-message":
           ok(!newTxt, "received-message should fire before new-text.");
-          ok(!receivedMsg, "Sanity check that receive-message hasn't fired yet.");
+          ok(
+            !receivedMsg,
+            "Sanity check that receive-message hasn't fired yet."
+          );
           ok(aObject.outgoing, "Expected an outgoing message.");
           ok(aObject.QueryInterface(Ci.imIMessage), "Wrong message type.");
-          equal(aObject.displayMessage, rot13(message), "Expected to have been rotated while sending-message.");
+          equal(
+            aObject.displayMessage,
+            rot13(message),
+            "Expected to have been rotated while sending-message."
+          );
           aObject.displayMessage = rot13(aObject.displayMessage);
           receivedMsg = true;
           break;
         case "new-text":
           ok(!newTxt, "Sanity check that new-text hasn't fired yet.");
           ok(receivedMsg, "Expected received-message to have fired.");
           ok(aObject.outgoing, "Expected an outgoing message.");
           ok(aObject.QueryInterface(Ci.imIMessage), "Wrong message type.");
-          equal(aObject.displayMessage, message, "Expected to have been rotated back to msg in received-message.");
+          equal(
+            aObject.displayMessage,
+            message,
+            "Expected to have been rotated back to msg in received-message."
+          );
           newTxt = true;
           break;
       }
     },
   });
 
   uiConv.sendMsg(message);
   ok(newTxt, "Expected new-text to have fired.");
 };
 
 // A test that cancels a message before it can be sent.
 var test_cancel_send_message = function() {
   let conv = new Conversation();
   conv.sendMsg = function(aMsg) {
-    ok(false, "The message should have been halted in the conversation service.");
+    ok(
+      false,
+      "The message should have been halted in the conversation service."
+    );
   };
 
   let sending = false;
   let uiConv = new imConversations.UIConversation(conv);
   uiConv.addObserver({
     observe(aObject, aTopic, aMsg) {
       switch (aTopic) {
         case "sending-message":
-          ok(aObject.QueryInterface(Ci.imIOutgoingMessage), "Wrong message type.");
+          ok(
+            aObject.QueryInterface(Ci.imIOutgoingMessage),
+            "Wrong message type."
+          );
           aObject.cancelled = true;
           sending = true;
           break;
         case "received-message":
         case "new-text":
-          ok(false, "No other notification should be fired for a cancelled message.");
+          ok(
+            false,
+            "No other notification should be fired for a cancelled message."
+          );
           break;
       }
     },
   });
   uiConv.sendMsg("Hi!");
   ok(sending, "The sending-message notification was never fired.");
 };
 
 // A test that cancels a message before it gets displayed.
 var test_cancel_display_message = function() {
   let conv = new Conversation();
   conv.sendMsg = function(aMsg) {
-    this.writeMessage("user", aMsg, {outgoing: true});
+    this.writeMessage("user", aMsg, { outgoing: true });
   };
 
   let received = false;
   let uiConv = new imConversations.UIConversation(conv);
   uiConv.addObserver({
     observe(aObject, aTopic, aMsg) {
       switch (aTopic) {
         case "received-message":
@@ -183,17 +225,17 @@ var test_cancel_display_message = functi
   ok(received, "The received-message notification was never fired.");
 };
 
 // A test that ensures protocols get a chance to prepare a message before
 // sending and displaying.
 var test_prpl_message_prep = function() {
   let conv = new Conversation();
   conv.sendMsg = function(aMsg) {
-    this.writeMessage("user", aMsg, {outgoing: true});
+    this.writeMessage("user", aMsg, { outgoing: true });
   };
 
   let msg = "Hi!";
   let prefix = "test> ";
 
   let prepared = false;
   conv.prepareForSending = function(aMsg) {
     ok(aMsg.QueryInterface(Ci.imIOutgoingMessage), "Wrong message type.");
--- a/chat/components/src/test/test_logger.js
+++ b/chat/components/src/test/test_logger.js
@@ -1,19 +1,22 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 do_get_profile();
 
-var {Services} = ChromeUtils.import("resource:///modules/imServices.jsm");
-const {OS} = ChromeUtils.import("resource://gre/modules/osfile.jsm");
+var { Services } = ChromeUtils.import("resource:///modules/imServices.jsm");
+const { OS } = ChromeUtils.import("resource://gre/modules/osfile.jsm");
 
 var gLogger = {};
-Services.scriptloader.loadSubScript("resource:///components/logger.js", gLogger);
+Services.scriptloader.loadSubScript(
+  "resource:///components/logger.js",
+  gLogger
+);
 
 var logDirPath = OS.Path.join(OS.Constants.Path.profileDir, "logs");
 
 var dummyAccount = {
   name: "dummy-account",
   normalizedName: "dummyaccount",
   protocol: {
     normalizedName: "dummy",
@@ -30,48 +33,60 @@ var dummyTwitterAccount = {
   },
 };
 
 var dummyConv = {
   account: dummyAccount,
   id: 0,
   title: "dummy conv",
   normalizedName: "dummyconv",
-  get name() { return this.normalizedName; },
-  get startDate() { return new Date(2011, 5, 28).valueOf() * 1000; },
+  get name() {
+    return this.normalizedName;
+  },
+  get startDate() {
+    return new Date(2011, 5, 28).valueOf() * 1000;
+  },
   isChat: false,
 };
 
 // A day after the first one.
 var dummyConv2 = {
   account: dummyAccount,
   id: 0,
   title: "dummy conv",
   normalizedName: "dummyconv",
-  get name() { return this.normalizedName; },
-  get startDate() { return new Date(2011, 5, 29).valueOf() * 1000; },
+  get name() {
+    return this.normalizedName;
+  },
+  get startDate() {
+    return new Date(2011, 5, 29).valueOf() * 1000;
+  },
   isChat: false,
 };
 
 var dummyMUC = {
   account: dummyAccount,
   id: 1,
   title: "Dummy MUC",
   normalizedName: "dummymuc",
-  get name() { return this.normalizedName; },
+  get name() {
+    return this.normalizedName;
+  },
   startDate: new Date(2011, 5, 28).valueOf() * 1000,
   isChat: true,
 };
 
 var dummyTwitterConv = {
   account: dummyTwitterAccount,
   id: 2,
   title: "Dummy Twitter Conv",
   normalizedName: "dummytwitterconv",
-  get name() { return this.normalizedName; },
+  get name() {
+    return this.normalizedName;
+  },
   startDate: new Date(2011, 5, 28).valueOf() * 1000,
   isChat: true,
 };
 
 var encodeName_input = [
   "CON",
   "PRN",
   "AUX",
@@ -80,39 +95,39 @@ var encodeName_input = [
   "LPT5",
   "file",
   "file.",
   "file ",
   "file_",
   "file<",
   "file>",
   "file:",
-  "file\"",
+  'file"',
   "file/",
   "file\\",
   "file|",
   "file?",
   "file*",
   "file&",
   "file%",
   "fi<le",
   "fi>le",
   "fi:le",
-  "fi\"le",
+  'fi"le',
   "fi/le",
   "fi\\le",
   "fi|le",
   "fi?le",
   "fi*le",
   "fi&le",
   "fi%le",
   "<file",
   ">file",
   ":file",
-  "\"file",
+  '"file',
   "/file",
   "\\file",
   "|file",
   "?file",
   "*file",
   "&file",
   "%file",
   "\\fi?*&%le<>",
@@ -184,81 +199,107 @@ var test_queueFileOperation = async func
   equal(gFP.get("path2"), p2);
   // This should throw since p2 rejected. Drop the error.
   await p2.then(() => do_throw(), () => {});
   ok(!gFP.has("path2"));
 
   let onPromiseComplete = (aPromise, aHandler) => {
     return aPromise.then(aHandler, aHandler);
   };
-  let test_queueOrder = (aOperation) => {
+  let test_queueOrder = aOperation => {
     let promise = qFO("queueOrderPath", aOperation);
     let firstOperationComplete = false;
-    onPromiseComplete(promise, () => firstOperationComplete = true);
+    onPromiseComplete(promise, () => (firstOperationComplete = true));
     return qFO("queueOrderPath", () => {
       ok(firstOperationComplete);
     });
   };
   // Test the queue order for rejected and resolved promises.
   await test_queueOrder(dummyResolvedOperation);
   await test_queueOrder(dummyRejectedOperation);
 };
 
 var test_getLogFolderPathForAccount = async function() {
   let path = gLogger.getLogFolderPathForAccount(dummyAccount);
-  equal(OS.Path.join(logDirPath, dummyAccount.protocol.normalizedName,
-                     gLogger.encodeName(dummyAccount.normalizedName)), path);
+  equal(
+    OS.Path.join(
+      logDirPath,
+      dummyAccount.protocol.normalizedName,
+      gLogger.encodeName(dummyAccount.normalizedName)
+    ),
+    path
+  );
 };
 
 // Tests the global function getLogFilePathForConversation in logger.js.
 var test_getLogFilePathForConversation = async function() {
   let path = gLogger.getLogFilePathForConversation(dummyConv, "format");
-  let expectedPath = OS.Path.join(logDirPath, dummyAccount.protocol.normalizedName,
-                                  gLogger.encodeName(dummyAccount.normalizedName));
+  let expectedPath = OS.Path.join(
+    logDirPath,
+    dummyAccount.protocol.normalizedName,
+    gLogger.encodeName(dummyAccount.normalizedName)
+  );
   expectedPath = OS.Path.join(
-    expectedPath, gLogger.encodeName(dummyConv.normalizedName));
+    expectedPath,
+    gLogger.encodeName(dummyConv.normalizedName)
+  );
   expectedPath = OS.Path.join(
-    expectedPath, gLogger.getNewLogFileName("format", dummyConv.startDate / 1000));
+    expectedPath,
+    gLogger.getNewLogFileName("format", dummyConv.startDate / 1000)
+  );
   equal(path, expectedPath);
 };
 
 var test_getLogFilePathForMUC = async function() {
   let path = gLogger.getLogFilePathForConversation(dummyMUC, "format");
-  let expectedPath = OS.Path.join(logDirPath, dummyAccount.protocol.normalizedName,
-                                  gLogger.encodeName(dummyAccount.normalizedName));
+  let expectedPath = OS.Path.join(
+    logDirPath,
+    dummyAccount.protocol.normalizedName,
+    gLogger.encodeName(dummyAccount.normalizedName)
+  );
   expectedPath = OS.Path.join(
-    expectedPath, gLogger.encodeName(dummyMUC.normalizedName + ".chat"));
+    expectedPath,
+    gLogger.encodeName(dummyMUC.normalizedName + ".chat")
+  );
   expectedPath = OS.Path.join(
-    expectedPath, gLogger.getNewLogFileName("format", dummyMUC.startDate / 1000));
+    expectedPath,
+    gLogger.getNewLogFileName("format", dummyMUC.startDate / 1000)
+  );
   equal(path, expectedPath);
 };
 
 var test_getLogFilePathForTwitterConv = async function() {
   let path = gLogger.getLogFilePathForConversation(dummyTwitterConv, "format");
-  let expectedPath =
-    OS.Path.join(logDirPath, dummyTwitterAccount.protocol.normalizedName,
-                 gLogger.encodeName(dummyTwitterAccount.normalizedName));
+  let expectedPath = OS.Path.join(
+    logDirPath,
+    dummyTwitterAccount.protocol.normalizedName,
+    gLogger.encodeName(dummyTwitterAccount.normalizedName)
+  );
   expectedPath = OS.Path.join(
-    expectedPath, gLogger.encodeName(dummyTwitterConv.normalizedName));
+    expectedPath,
+    gLogger.encodeName(dummyTwitterConv.normalizedName)
+  );
   expectedPath = OS.Path.join(
-    expectedPath, gLogger.getNewLogFileName("format",
-                                            dummyTwitterConv.startDate / 1000));
+    expectedPath,
+    gLogger.getNewLogFileName("format", dummyTwitterConv.startDate / 1000)
+  );
   equal(path, expectedPath);
 };
 
 var test_appendToFile = async function() {
   const kStringToWrite = "Hello, world!";
   let path = OS.Path.join(OS.Constants.Path.profileDir, "testFile.txt");
   let encoder = new TextEncoder();
   let encodedString = encoder.encode(kStringToWrite);
   gLogger.appendToFile(path, encodedString);
   encodedString = encoder.encode(kStringToWrite);
   gLogger.appendToFile(path, encodedString);
-  let text = (new TextDecoder()).decode(
-    await gLogger.queueFileOperation(path, () => OS.File.read(path)));
+  let text = new TextDecoder().decode(
+    await gLogger.queueFileOperation(path, () => OS.File.read(path))
+  );
   // The read text should be equal to kStringToWrite repeated twice.
   equal(text, kStringToWrite + kStringToWrite);
   await OS.File.remove(path);
 };
 
 // Tests the getLogPathsForConversation API defined in the imILogger interface.
 var test_getLogPathsForConversation = async function() {
   let logger = new gLogger.Logger();
@@ -310,72 +351,82 @@ var test_logging = async function() {
       },
     ];
   };
   let firstDayMsgs = getMsgsForConv(dummyConv);
   let secondDayMsgs = getMsgsForConv(dummyConv2);
 
   let logMessagesForConv = async function(aConv, aMessages) {
     let logWriter = gLogger.getLogWriter(aConv);
-    for (let message of aMessages)
+    for (let message of aMessages) {
       logWriter.logMessage(message);
+    }
     // If we don't wait for the messages to get written, we have no guarantee
     // later in the test that the log files were created, and getConversation
     // will return an EmptyEnumerator. Logging the messages is queued on the
     // _initialized promise, so we need to await on that first.
     await logWriter._initialized;
     await gLogger.gFilePromises.get(logWriter.currentPath);
     // Ensure two different files for the different dates.
     gLogger.closeLogWriter(aConv);
   };
   await logMessagesForConv(dummyConv, firstDayMsgs);
   await logMessagesForConv(dummyConv2, secondDayMsgs);
 
   // Write a zero-length file and a file with incorrect JSON for each day
   // to ensure they are handled correctly.
   let logDir = OS.Path.dirname(
-    gLogger.getLogFilePathForConversation(dummyConv, "json"));
+    gLogger.getLogFilePathForConversation(dummyConv, "json")
+  );
   let createBadFiles = async function(aConv) {
-    let blankFile = OS.Path.join(logDir,
-      gLogger.getNewLogFileName("json", (aConv.startDate + oneSec) / 1000));
-    let invalidJSONFile = OS.Path.join(logDir,
-      gLogger.getNewLogFileName("json", (aConv.startDate + (2 * oneSec)) / 1000));
-    let file = await OS.File.open(blankFile, {truncate: true});
+    let blankFile = OS.Path.join(
+      logDir,
+      gLogger.getNewLogFileName("json", (aConv.startDate + oneSec) / 1000)
+    );
+    let invalidJSONFile = OS.Path.join(
+      logDir,
+      gLogger.getNewLogFileName("json", (aConv.startDate + 2 * oneSec) / 1000)
+    );
+    let file = await OS.File.open(blankFile, { truncate: true });
     await file.close();
-    await OS.File.writeAtomic(invalidJSONFile,
-                              new TextEncoder().encode("This isn't JSON!"));
+    await OS.File.writeAtomic(
+      invalidJSONFile,
+      new TextEncoder().encode("This isn't JSON!")
+    );
   };
   await createBadFiles(dummyConv);
   await createBadFiles(dummyConv2);
 
   let testMsgs = function(aMsgs, aExpectedMsgs, aExpectedSessions) {
     // Ensure the number of session messages is correct.
     let sessions = aMsgs.filter(aMsg => aMsg.who == "sessionstart").length;
     equal(sessions, aExpectedSessions);
 
     // Discard session messages, etc.
     aMsgs = aMsgs.filter(aMsg => !aMsg.noLog);
 
     equal(aMsgs.length, aExpectedMsgs.length);
 
     for (let i = 0; i < aMsgs.length; ++i) {
-      let message = aMsgs[i], expectedMessage = aExpectedMsgs[i];
+      let message = aMsgs[i],
+        expectedMessage = aExpectedMsgs[i];
       for (let prop in expectedMessage) {
         ok(prop in message);
         equal(expectedMessage[prop], message[prop]);
       }
     }
   };
 
   let logs = await logger.getLogsForConversation(dummyConv);
   let allLogMsgs = [];
   while (logs.hasMoreElements()) {
     let conv = await logs.getNext().getConversation();
-    if (!conv)
+    if (!conv) {
       continue;
+    }
     allLogMsgs = allLogMsgs.concat(conv.getMessages());
   }
   // Two session messages, one for each valid log file.
   testMsgs(allLogMsgs, firstDayMsgs.concat(secondDayMsgs), 2);
 
   // Accepts time in seconds, reduces it to a date, and returns the value in millis.
   let reduceTimeToDate = function(aTime) {
     let date = new Date(aTime * 1000);
@@ -403,25 +454,27 @@ var test_logging = async function() {
   await logger.forEach({
     async processLog(aLog) {
       let info = await OS.File.stat(aLog);
       ok(!info.isDir);
       ok(aLog.endsWith(".json"));
       await OS.File.remove(aLog);
     },
   });
-  let logFolder = OS.Path.dirname(gLogger.getLogFilePathForConversation(dummyConv));
+  let logFolder = OS.Path.dirname(
+    gLogger.getLogFilePathForConversation(dummyConv)
+  );
   // The folder should now be empty - this will throw if it isn't.
-  await OS.File.removeEmptyDir(logFolder, {ignoreAbsent: false});
+  await OS.File.removeEmptyDir(logFolder, { ignoreAbsent: false });
 };
 
 var test_logFileSplitting = async function() {
   // Start clean, remove the log directory.
   let logFolderPath = OS.Path.join(OS.Constants.Path.profileDir, "logs");
-  await OS.File.removeDir(logFolderPath, {ignoreAbsent: true});
+  await OS.File.removeDir(logFolderPath, { ignoreAbsent: true });
   let logWriter = gLogger.getLogWriter(dummyConv);
   let startTime = logWriter._startTime / 1000; // Message times are in seconds.
   let oldPath = logWriter.currentPath;
   let message = {
     time: startTime,
     who: "John Doe",
     originalMessage: "Hello, world!",
     outgoing: true,
@@ -429,60 +482,68 @@ var test_logFileSplitting = async functi
 
   let logMessage = async function(aMessage) {
     logWriter.logMessage(aMessage);
     await logWriter._initialized;
     await gLogger.gFilePromises.get(logWriter.currentPath);
   };
 
   await logMessage(message);
-  message.time += (logWriter.kInactivityLimit / 1000) + 1;
+  message.time += logWriter.kInactivityLimit / 1000 + 1;
   // This should go in a new log file.
   await logMessage(message);
   notEqual(logWriter.currentPath, oldPath);
   // The log writer's new start time should be the time of the message.
   equal(message.time * 1000, logWriter._startTime);
 
   let getCurrentHeader = async function() {
-    return JSON.parse(new TextDecoder()
-                      .decode(await OS.File.read(logWriter.currentPath))
-                      .split("\n")[0]);
+    return JSON.parse(
+      new TextDecoder()
+        .decode(await OS.File.read(logWriter.currentPath))
+        .split("\n")[0]
+    );
   };
 
   // The header of the new log file should not have the continuedSession flag set.
   ok(!(await getCurrentHeader()).continuedSession);
 
   // Set the start time sufficiently before midnight, and the last message time
   // to just before midnight. A new log file should be created at midnight.
-  logWriter._startTime = new Date(logWriter._startTime)
-    .setHours(24, 0, 0, -(logWriter.kDayOverlapLimit + 1));
+  logWriter._startTime = new Date(logWriter._startTime).setHours(
+    24,
+    0,
+    0,
+    -(logWriter.kDayOverlapLimit + 1)
+  );
   let nearlyMidnight = new Date(logWriter._startTime).setHours(24, 0, 0, -1);
   oldPath = logWriter.currentPath;
   logWriter._lastMessageTime = nearlyMidnight;
   message.time = new Date(nearlyMidnight).setHours(24, 0, 0, 1) / 1000;
   await logMessage(message);
   // The message should have gone in a new file.
   notEqual(oldPath, logWriter.currentPath);
   // The header should have the continuedSession flag set this time.
   ok((await getCurrentHeader()).continuedSession);
 
   // Ensure a new file is created every kMessageCountLimit messages.
   oldPath = logWriter.currentPath;
   let messageCountLimit = logWriter.kMessageCountLimit;
-  for (let i = 0; i < messageCountLimit; ++i)
+  for (let i = 0; i < messageCountLimit; ++i) {
     logMessage(message);
+  }
   await logMessage(message);
   notEqual(oldPath, logWriter.currentPath);
   // The header should have the continuedSession flag set this time too.
   ok((await getCurrentHeader()).continuedSession);
   // Again, to make sure it still works correctly after splitting it once already.
   oldPath = logWriter.currentPath;
   // We already logged one message to ensure it went into a new file, so i = 1.
-  for (let i = 1; i < messageCountLimit; ++i)
+  for (let i = 1; i < messageCountLimit; ++i) {
     logMessage(message);
+  }
   await logMessage(message);
   notEqual(oldPath, logWriter.currentPath);
   ok((await getCurrentHeader()).continuedSession);
 
   // The new start time is the time of the message. If we log sufficiently more
   // messages with the same time property, ensure that the start time of the next
   // log file is greater than the previous one, and that a new path is being used.
   let oldStartTime = logWriter._startTime;
@@ -501,18 +562,19 @@ var test_logFileSplitting = async functi
   ok(logWriter._startTime > oldStartTime);
 
   // Clean up.
   await OS.File.removeDir(logFolderPath);
 };
 
 function run_test() {
   // Test encodeName().
-  for (let i = 0; i < encodeName_input.length; ++i)
+  for (let i = 0; i < encodeName_input.length; ++i) {
     equal(gLogger.encodeName(encodeName_input[i]), encodeName_output[i]);
+  }
 
   // Test convIsRealMUC().
   ok(!gLogger.convIsRealMUC(dummyConv));
   ok(!gLogger.convIsRealMUC(dummyTwitterConv));
   ok(gLogger.convIsRealMUC(dummyMUC));
 
   add_task(test_getLogFolderPathForAccount);
 
--- a/chat/content/browserRequest.js
+++ b/chat/content/browserRequest.js
@@ -1,85 +1,95 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-var {Services} = ChromeUtils.import("resource:///modules/imServices.jsm");
+var { Services } = ChromeUtils.import("resource:///modules/imServices.jsm");
 
 var wpl = Ci.nsIWebProgressListener;
 
 var reporterListener = {
   _isBusy: false,
   get statusMeter() {
     delete this.statusMeter;
-    return this.statusMeter = document.getElementById("statusbar-icon");
+    return (this.statusMeter = document.getElementById("statusbar-icon"));
   },
   get securityButton() {
     delete this.securityButton;
-    return this.securityButton = document.getElementById("security-button");
+    return (this.securityButton = document.getElementById("security-button"));
   },
   get securityLabel() {
     delete this.securityLabel;
-    return this.securityLabel = document.getElementById("security-status");
+    return (this.securityLabel = document.getElementById("security-status"));
   },
   get securityDisplay() {
     delete this.securityDisplay;
-    return this.securityDisplay = document.getElementById("security-display");
+    return (this.securityDisplay = document.getElementById("security-display"));
   },
 
-  QueryInterface: ChromeUtils.generateQI(["nsIWebProgressListener",
-                                          "nsISupportsWeakReference"]),
+  QueryInterface: ChromeUtils.generateQI([
+    "nsIWebProgressListener",
+    "nsISupportsWeakReference",
+  ]),
 
-  onStateChange(/* in nsIWebProgress */ aWebProgress,
-                /* in nsIRequest */ aRequest,
-                /* in unsigned long */ aStateFlags,
-                /* in nsresult */ aStatus) {
-    if (aStateFlags & wpl.STATE_START &&
-        aStateFlags & wpl.STATE_IS_NETWORK) {
+  onStateChange(
+    /* in nsIWebProgress */ aWebProgress,
+    /* in nsIRequest */ aRequest,
+    /* in unsigned long */ aStateFlags,
+    /* in nsresult */ aStatus
+  ) {
+    if (aStateFlags & wpl.STATE_START && aStateFlags & wpl.STATE_IS_NETWORK) {
       this.statusMeter.value = 0;
       this.statusMeter.parentNode.collapsed = false;
       this.securityLabel.collapsed = true;
-    }
-    else if (aStateFlags & wpl.STATE_STOP &&
-             aStateFlags & wpl.STATE_IS_NETWORK) {
+    } else if (
+      aStateFlags & wpl.STATE_STOP &&
+      aStateFlags & wpl.STATE_IS_NETWORK
+    ) {
       this.statusMeter.parentNode.collapsed = true;
       this.securityLabel.collapsed = false;
     }
   },
 
-  onProgressChange(/* in nsIWebProgress */ aWebProgress,
-                   /* in nsIRequest */ aRequest,
-                   /* in long */ aCurSelfProgress,
-                   /* in long */ aMaxSelfProgress,
-                   /* in long */ aCurTotalProgress,
-                   /* in long */ aMaxTotalProgress) {
+  onProgressChange(
+    /* in nsIWebProgress */ aWebProgress,
+    /* in nsIRequest */ aRequest,
+    /* in long */ aCurSelfProgress,
+    /* in long */ aMaxSelfProgress,
+    /* in long */ aCurTotalProgress,
+    /* in long */ aMaxTotalProgress
+  ) {
     if (aMaxTotalProgress > 0) {
       let percentage = (aCurTotalProgress * 100) / aMaxTotalProgress;
       this.statusMeter.value = percentage;
     }
   },
 
-  onLocationChange(/* in nsIWebProgress */ aWebProgress,
-                   /* in nsIRequest */ aRequest,
-                   /* in nsIURI */ aLocation) {
+  onLocationChange(
+    /* in nsIWebProgress */ aWebProgress,
+    /* in nsIRequest */ aRequest,
+    /* in nsIURI */ aLocation
+  ) {
     this.securityDisplay.setAttribute("value", aLocation.host);
   },
 
-  onStatusChange(/* in nsIWebProgress */ aWebProgress,
-                 /* in nsIRequest */ aRequest,
-                 /* in nsresult */ aStatus,
-                 /* in wstring */ aMessage) {
-  },
+  onStatusChange(
+    /* in nsIWebProgress */ aWebProgress,
+    /* in nsIRequest */ aRequest,
+    /* in nsresult */ aStatus,
+    /* in wstring */ aMessage
+  ) {},
 
-  onSecurityChange(/* in nsIWebProgress */ aWebProgress,
-                   /* in nsIRequest */ aRequest,
-                   /* in unsigned long */ aState) {
-    const wpl_security_bits = wpl.STATE_IS_SECURE |
-                              wpl.STATE_IS_BROKEN |
-                              wpl.STATE_IS_INSECURE;
+  onSecurityChange(
+    /* in nsIWebProgress */ aWebProgress,
+    /* in nsIRequest */ aRequest,
+    /* in unsigned long */ aState
+  ) {
+    const wpl_security_bits =
+      wpl.STATE_IS_SECURE | wpl.STATE_IS_BROKEN | wpl.STATE_IS_INSECURE;
     let browser = document.getElementById("requestFrame");
     let level;
 
     switch (aState & wpl_security_bits) {
       case wpl.STATE_IS_SECURE:
         level = "high";
         break;
       case wpl.STATE_IS_BROKEN:
@@ -89,55 +99,56 @@ var reporterListener = {
     if (level) {
       this.securityButton.setAttribute("level", level);
       this.securityButton.hidden = false;
       this.securityLabel.setAttribute("value", browser.securityUI.tooltipText);
     } else {
       this.securityButton.hidden = true;
       this.securityButton.removeAttribute("level");
     }
-    this.securityButton.setAttribute("tooltiptext",
-                                     browser.securityUI.tooltipText);
+    this.securityButton.setAttribute(
+      "tooltiptext",
+      browser.securityUI.tooltipText
+    );
   },
 
-  onContentBlockingEvent(/* in nsIWebProgress */ aWebProgress,
-                         /* in nsIRequest */ aRequest,
-                         /* in unsigned long */ aEvent) {
-  },
+  onContentBlockingEvent(
+    /* in nsIWebProgress */ aWebProgress,
+    /* in nsIRequest */ aRequest,
+    /* in unsigned long */ aEvent
+  ) {},
 };
 
-function cancelRequest()
-{
+function cancelRequest() {
   reportUserClosed();
   window.close();
 }
 
-function reportUserClosed()
-{
+function reportUserClosed() {
   let request = window.arguments[0];
   request.QueryInterface(Ci.prplIRequestBrowser);
   request.cancelled();
 }
 
-function loadRequestedUrl()
-{
+function loadRequestedUrl() {
   let request = window.arguments[0];
   request.QueryInterface(Ci.prplIRequestBrowser);
   document.getElementById("headerMessage").textContent = request.promptText;
   let account = request.account;
   document.getElementById("headerLabel").value =
     account.protocol.name + " - " + account.name;
   document.getElementById("headerImage").src =
     account.protocol.iconBaseURI + "icon48.png";
 
   let browser = document.getElementById("requestFrame");
   browser.docShell.allowPlugins = false;
 
-  if (Services.prefs.getBoolPref("chat.browserRequest.disableJavascript"))
+  if (Services.prefs.getBoolPref("chat.browserRequest.disableJavascript")) {
     browser.docShell.allowJavascript = false;
+  }
 
-  browser.addProgressListener(reporterListener,
-                              Ci.nsIWebProgress.NOTIFY_ALL);
+  browser.addProgressListener(reporterListener, Ci.nsIWebProgress.NOTIFY_ALL);
   let url = request.url;
-  if (url != "")
+  if (url != "") {
     browser.setAttribute("src", url);
+  }
   request.loaded(window, browser.webProgress);
 }
--- a/chat/content/chat-account-richlistitem.js
+++ b/chat/content/chat-account-richlistitem.js
@@ -1,58 +1,64 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
-  * License, v. 2.0. If a copy of the MPL was not distributed with this
-  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 /* global MozElements, MozXULElement, gAccountManager */
 
 // Wrap in a block to prevent leaking to window scope.
 {
-  const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
-  const { DownloadUtils } = ChromeUtils.import("resource://gre/modules/DownloadUtils.jsm");
+  const { Services } = ChromeUtils.import(
+    "resource://gre/modules/Services.jsm"
+  );
+  const { DownloadUtils } = ChromeUtils.import(
+    "resource://gre/modules/DownloadUtils.jsm"
+  );
 
   /**
-  * The MozChatAccountRichlistitem widget displays the information about the
-  * configured account: i.e. icon, state, name, error, checkbox for
-  * auto sign in and buttons for disconnect and properties.
-  *
-  * @extends {MozElements.MozRichlistitem}
-  */
+   * The MozChatAccountRichlistitem widget displays the information about the
+   * configured account: i.e. icon, state, name, error, checkbox for
+   * auto sign in and buttons for disconnect and properties.
+   *
+   * @extends {MozElements.MozRichlistitem}
+   */
   class MozChatAccountRichlistitem extends MozElements.MozRichlistitem {
     static get inheritedAttributes() {
       return {
-        "stack": "tooltiptext=protocol",
+        stack: "tooltiptext=protocol",
         ".accountIcon": "src=prplicon",
         ".accountName": "value=name",
         ".autoSignOn": "checked=autologin",
         ".account-buttons": "autologin,name",
       };
     }
 
     connectedCallback() {
       if (this.delayConnectedCallback() || this.hasChildNodes()) {
         return;
       }
 
-      this.addEventListener("dblclick", (event) => {
+      this.addEventListener("dblclick", event => {
         if (event.button == 0) {
           // If we double clicked on a widget that has already done
           // something with the first click, we should ignore the event
           let localName = event.originalTarget.localName;
           if (localName != "button" && localName != "checkbox") {
             this.buttons.proceedDefaultAction();
           }
         }
         // Prevent from loading an account wizzard
         event.stopPropagation();
       });
 
-      this.appendChild(MozXULElement.parseXULToFragment(`
+      this.appendChild(
+        MozXULElement.parseXULToFragment(
+          `
         <vbox flex="1">
           <hbox flex="1" align="top">
             <vbox>
               <stack>
                 <image class="accountIcon"></image>
                 <image class="accountStateIcon"></image>
               </stack>
               <spacer flex="1"></spacer>
@@ -74,17 +80,20 @@
           </hbox>
           <hbox flex="1" class="account-buttons">
             <button class="disconnectButton" command="cmd_disconnect"></button>
             <button class="connectButton" command="cmd_connect"></button>
             <spacer flex="1"></spacer>
             <button command="cmd_edit"></button>
           </hbox>
         </vbox>
-    `, ["chrome://chat/locale/accounts.dtd"]));
+    `,
+          ["chrome://chat/locale/accounts.dtd"]
+        )
+      );
       this.initializeAttributeInheritance();
     }
 
     set autoLogin(val) {
       if (val) {
         this.setAttribute("autologin", "true");
       } else {
         this.removeAttribute("autologin");
@@ -149,94 +158,119 @@
       } else if (this._account.disconnecting) {
         state = "connected";
       }
       this.setAttribute("state", state);
       this.autoLogin = aAccount.autoLogin;
     }
 
     updateConnectionState() {
-      let bundle = Services.strings.createBundle("chrome://messenger/locale/imAccounts.properties");
+      let bundle = Services.strings.createBundle(
+        "chrome://messenger/locale/imAccounts.properties"
+      );
       const key = "account.connection.progress";
       let text = this._account.connectionStateMsg;
-      text = text ? bundle.formatStringFromName(key, [text]) :
-        bundle.GetStringFromName("account.connecting");
+      text = text
+        ? bundle.formatStringFromName(key, [text])
+        : bundle.GetStringFromName("account.connecting");
 
       let progress = this.querySelector(".connecting");
       progress.setAttribute("value", text);
       if (this.reconnectUpdateInterval) {
         this._cancelReconnectTimer();
       }
 
       this.removeAttribute("certError");
     }
 
     updateConnectionError() {
-      let bundle = Services.strings.createBundle("chrome://messenger/locale/imAccounts.properties");
+      let bundle = Services.strings.createBundle(
+        "chrome://messenger/locale/imAccounts.properties"
+      );
       const key = "account.connection.error";
       let account = this._account;
       let text;
       let errorReason = account.connectionErrorReason;
       if (errorReason == Ci.imIAccount.ERROR_UNKNOWN_PRPL) {
-        text = bundle.formatStringFromName(key + "UnknownPrpl", [account.protocol.id]);
+        text = bundle.formatStringFromName(key + "UnknownPrpl", [
+          account.protocol.id,
+        ]);
       } else if (errorReason == Ci.imIAccount.ERROR_MISSING_PASSWORD) {
         text = bundle.GetStringFromName(key + "EnteringPasswordRequired");
       } else if (errorReason == Ci.imIAccount.ERROR_CRASHED) {
         text = bundle.GetStringFromName(key + "CrashedAccount");
       } else {
         text = account.connectionErrorMessage;
       }
 
       if (errorReason != Ci.imIAccount.ERROR_MISSING_PASSWORD) {
         text = bundle.formatStringFromName(key, [text]);
       }
 
       this.setAttribute("error", "true");
-      if ((Ci.imIAccount.ERROR_CERT_NOT_PROVIDED <= errorReason &&
-        errorReason <= Ci.imIAccount.ERROR_CERT_OTHER_ERROR) &&
-        account.prplAccount.connectionTarget) {
+      if (
+        Ci.imIAccount.ERROR_CERT_NOT_PROVIDED <= errorReason &&
+        errorReason <= Ci.imIAccount.ERROR_CERT_OTHER_ERROR &&
+        account.prplAccount.connectionTarget
+      ) {
         this.setAttribute("certError", "true");
       }
       let error = this.querySelector(".error-description");
       error.textContent = text;
 
       let updateReconnect = () => {
-        let date = Math.round((account.timeOfNextReconnect - Date.now()) / 1000);
+        let date = Math.round(
+          (account.timeOfNextReconnect - Date.now()) / 1000
+        );
         let reconnect = "";
         if (date > 0) {
           let [val1, unit1, val2, unit2] = DownloadUtils.convertTimeUnits(date);
-          if (!val2)
-            reconnect = bundle.formatStringFromName("account.reconnectInSingle",
-              [val1, unit1]);
-          else
-            reconnect = bundle.formatStringFromName("account.reconnectInDouble",
-              [val1, unit1, val2, unit2]);
+          if (!val2) {
+            reconnect = bundle.formatStringFromName(
+              "account.reconnectInSingle",
+              [val1, unit1]
+            );
+          } else {
+            reconnect = bundle.formatStringFromName(
+              "account.reconnectInDouble",
+              [val1, unit1, val2, unit2]
+            );
+          }
         }
         this.querySelector(".error-reconnect").textContent = reconnect;
         return reconnect;
       };
       if (updateReconnect() && !this.reconnectUpdateInterval) {
         this.setAttribute("reconnectPending", "true");
         this.reconnectUpdateInterval = setInterval(updateReconnect, 1000);
         gAccountManager.disableCommandItems();
       }
     }
 
     refreshConnectedLabel() {
-      let bundle = Services.strings.createBundle("chrome://messenger/locale/imAccounts.properties");
-      let date = 60 * Math.floor((Date.now() - this._account.timeOfLastConnect) / 60000);
+      let bundle = Services.strings.createBundle(
+        "chrome://messenger/locale/imAccounts.properties"
+      );
+      let date =
+        60 * Math.floor((Date.now() - this._account.timeOfLastConnect) / 60000);
       let value;
       if (date > 0) {
         let [val1, unit1, val2, unit2] = DownloadUtils.convertTimeUnits(date);
         if (!val2) {
-          value = bundle.formatStringFromName("account.connectedForSingle",
-            [val1, unit1]);
+          value = bundle.formatStringFromName("account.connectedForSingle", [
+            val1,
+            unit1,
+          ]);
         } else {
-          value = bundle.formatStringFromName("account.connectedForDouble",
-            [val1, unit1, val2, unit2]);
+          value = bundle.formatStringFromName("account.connectedForDouble", [
+            val1,
+            unit1,
+            val2,
+            unit2,
+          ]);
         }
       } else {
         value = bundle.GetStringFromName("account.connectedForSeconds");
       }
       this.connectedLabel.value = value;
     }
 
     _cancelReconnectTimer() {
@@ -256,45 +290,56 @@
     restoreItems() {
       // Called after a removal and reinsertion of the binding
       this._buttons = null;
       this._connectedLabel = null;
       if (this._account.connected) {
         this.refreshConnectedLabel();
       } else if (this._account.connecting) {
         this.updateConnectionState();
-      } else if (this._account.connectionErrorReason != Ci.prplIAccount.NO_ERROR) {
+      } else if (
+        this._account.connectionErrorReason != Ci.prplIAccount.NO_ERROR
+      ) {
         this.updateConnectionError();
       }
     }
 
     destroy() {
       // If we have a reconnect timer, stop it:
       // it will throw errors otherwise (see bug 480).
       if (!this.reconnectUpdateInterval) {
         return;
       }
       clearInterval(this.reconnectUpdateInterval);
       delete this.reconnectUpdateInterval;
     }
 
     get activeButton() {
-      let action = this.account.disconnected ? ".connectButton" : ".disconnectButton";
+      let action = this.account.disconnected
+        ? ".connectButton"
+        : ".disconnectButton";
       return this.querySelector(action);
     }
 
     setFocus() {
       let focusTarget = this.activeButton;
       let accountName = this.getAttribute("name");
-      focusTarget.setAttribute("aria-label", focusTarget.label + " " + accountName);
+      focusTarget.setAttribute(
+        "aria-label",
+        focusTarget.label + " " + accountName
+      );
       if (focusTarget.disabled) {
         focusTarget = document.getElementById("accountlist");
       }
       focusTarget.focus();
     }
 
     proceedDefaultAction() {
       this.activeButton.click();
     }
   }
 
-  customElements.define("chat-account-richlistitem", MozChatAccountRichlistitem, { extends: "richlistitem" });
+  customElements.define(
+    "chat-account-richlistitem",
+    MozChatAccountRichlistitem,
+    { extends: "richlistitem" }
+  );
 }
--- a/chat/content/chat-tooltip.js
+++ b/chat/content/chat-tooltip.js
@@ -4,25 +4,25 @@
 
 "use strict";
 
 /* global MozXULElement */
 /* global getBrowser */
 
 // Wrap in a block to prevent leaking to window scope.
 {
-  let {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm");
-  let {Status} = ChromeUtils.import("resource:///modules/imStatusUtils.jsm");
+  let { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
+  let { Status } = ChromeUtils.import("resource:///modules/imStatusUtils.jsm");
 
   /**
-  * The MozChatTooltip widget implements a custom tooltip for chat. This tooltip
-  * is used to display a rich tooltip when you mouse over contacts, channels
-  * etc. in the chat view.
-  * @extends {MozXULElement}
-  */
+   * The MozChatTooltip widget implements a custom tooltip for chat. This tooltip
+   * is used to display a rich tooltip when you mouse over contacts, channels
+   * etc. in the chat view.
+   * @extends {MozXULElement}
+   */
   class MozChatTooltip extends MozXULElement {
     static get inheritedAttributes() {
       return {
         ".userIconHolder": "userIcon",
         ".userIcon": "src=userIcon",
         ".statusTypeIcon": "status,left",
         ".tooltipDisplayName": "value=displayname",
         ".tooltipProtoIcon": "src=iconPrpl,status",
@@ -32,37 +32,46 @@
 
     constructor() {
       super();
       this._buddy = null;
 
       this.observer = {
         // @see {nsIObserver}
         observe: (subject, topic, data) => {
-          if (subject == this.buddy &&
-              (topic == "account-buddy-status-changed" ||
-               topic == "account-buddy-status-detail-changed" ||
-               topic == "account-buddy-display-name-changed" ||
-               topic == "account-buddy-icon-changed")) {
+          if (
+            subject == this.buddy &&
+            (topic == "account-buddy-status-changed" ||
+              topic == "account-buddy-status-detail-changed" ||
+              topic == "account-buddy-display-name-changed" ||
+              topic == "account-buddy-icon-changed")
+          ) {
             this.updateTooltipFromBuddy(this.buddy);
-          } else if (topic == "user-info-received" &&
-                     data == this.observedUserInfo) {
-            this.updateTooltipInfo(subject.QueryInterface(Ci.nsISimpleEnumerator));
+          } else if (
+            topic == "user-info-received" &&
+            data == this.observedUserInfo
+          ) {
+            this.updateTooltipInfo(
+              subject.QueryInterface(Ci.nsISimpleEnumerator)
+            );
           }
         },
-        QueryInterface: ChromeUtils.generateQI([Ci.nsIObserver, Ci.nsISupportsWeakReference]),
+        QueryInterface: ChromeUtils.generateQI([
+          Ci.nsIObserver,
+          Ci.nsISupportsWeakReference,
+        ]),
       };
 
-      this.addEventListener("popupshowing", (event) => {
+      this.addEventListener("popupshowing", event => {
         if (!this._onPopupShowing()) {
           event.preventDefault();
         }
       });
 
-      this.addEventListener("popuphiding", (event) => {
+      this.addEventListener("popuphiding", event => {
         this.buddy = null;
         this.removeAttribute("noTopic");
         this.removeAttribute("left");
         if ("observedUserInfo" in this && this.observedUserInfo) {
           Services.obs.removeObserver(this.observer, "user-info-received");
           delete this.observedUserInfo;
         }
       });
@@ -82,40 +91,56 @@
 
       // Reset tooltip.
       let largeTooltip = this.querySelector(".largeTooltip");
       largeTooltip.hidden = false;
       this.removeAttribute("label");
 
       let localName = elt.localName;
 
-      if (localName == "richlistitem" && elt.getAttribute("is") == "chat-group") {
+      if (
+        localName == "richlistitem" &&
+        elt.getAttribute("is") == "chat-group"
+      ) {
         return false;
       }
 
-      if (localName == "richlistitem" &&
-          elt.getAttribute("is") == "chat-imconv" && elt.conv) {
+      if (
+        localName == "richlistitem" &&
+        elt.getAttribute("is") == "chat-imconv" &&
+        elt.conv
+      ) {
         return this.updateTooltipFromConversation(elt.conv);
       }
 
-      if (localName == "richlistitem" && elt.getAttribute("is") == "chat-contact") {
-        return this.updateTooltipFromBuddy(elt.contact.preferredBuddy.preferredAccountBuddy);
+      if (
+        localName == "richlistitem" &&
+        elt.getAttribute("is") == "chat-contact"
+      ) {
+        return this.updateTooltipFromBuddy(
+          elt.contact.preferredBuddy.preferredAccountBuddy
+        );
       }
 
       if (localName == "richlistitem") {
         let contactlistbox = document.getElementById("contactlistbox");
         let conv = contactlistbox.selectedItem.conv;
-        return this.updateTooltipFromParticipant(elt.chatBuddy.name, conv,
-          elt.chatBuddy);
+        return this.updateTooltipFromParticipant(
+          elt.chatBuddy.name,
+          conv,
+          elt.chatBuddy
+        );
       }
 
       let classList = elt.classList;
-      if (classList.contains("ib-nick") ||
-          classList.contains("ib-sender") ||
-          classList.contains("ib-person")) {
+      if (
+        classList.contains("ib-nick") ||
+        classList.contains("ib-sender") ||
+        classList.contains("ib-person")
+      ) {
         let conv = getBrowser()._conv;
         if (conv.isChat) {
           return this.updateTooltipFromParticipant(elt.textContent, conv);
         }
         if (elt.textContent == conv.name) {
           return this.updateTooltipFromConversation(conv);
         }
       }
@@ -123,26 +148,32 @@
       // Are we over a message?
       for (let node = elt; node; node = node.parentNode) {
         if (!node._originalMsg) {
           continue;
         }
         // It's a message, so add a date/time tooltip.
         let date = new Date(node._originalMsg.time * 1000);
         let text;
-        if ((new Date()).toDateString() == date.toDateString()) {
-          const dateTimeFormatter = new Services.intl.DateTimeFormat(undefined, {
-            timeStyle: "medium",
-          });
+        if (new Date().toDateString() == date.toDateString()) {
+          const dateTimeFormatter = new Services.intl.DateTimeFormat(
+            undefined,
+            {
+              timeStyle: "medium",
+            }
+          );
           text = dateTimeFormatter.format(date);
         } else {
-          const dateTimeFormatter = new Services.intl.DateTimeFormat(undefined, {
-            dateStyle: "short",
-            timeStyle: "medium",
-          });
+          const dateTimeFormatter = new Services.intl.DateTimeFormat(
+            undefined,
+            {
+              dateStyle: "short",
+              timeStyle: "medium",
+            }
+          );
           text = dateTimeFormatter.format(date);
         }
         // Setting the attribute on this node means that if the element
         // we are pointing at carries a title set by the prpl,
         // that title won't be overridden.
         node.setAttribute("title", text);
         break;
       }
@@ -152,17 +183,18 @@
       return false;
     }
 
     connectedCallback() {
       if (this.delayConnectedCallback()) {
         return;
       }
       this.textContent = "";
-      this.appendChild(MozXULElement.parseXULToFragment(`
+      this.appendChild(
+        MozXULElement.parseXULToFragment(`
         <vbox class="largeTooltip">
           <hbox align="start" crop="end" flex="1">
             <vbox flex="1">
               <stack>
                 <!-- The box around the user icon is a workaround for bug 955673. -->
                 <box class="userIconHolder">
                   <image class="userIcon"></image>
                 </box>
@@ -184,24 +216,26 @@
           <grid>
             <columns>
               <column></column>
               <column flex="1"></column>
             </columns>
             <rows class="tooltipRows"></rows>
           </grid>
         </vbox>
-      `));
+      `)
+      );
       this.initializeAttributeInheritance();
     }
 
     get bundle() {
       if (!this._bundle) {
-        this._bundle =
-          Services.strings.createBundle("chrome://chat/locale/imtooltip.properties");
+        this._bundle = Services.strings.createBundle(
+          "chrome://chat/locale/imtooltip.properties"
+        );
       }
       return this._bundle;
     }
 
     set buddy(val) {
       if (val == this._buddy) {
         return val;
       }
@@ -388,19 +422,20 @@
       }
 
       let account = aConv.account;
       let normalizedNick = aConv.target.getNormalizedChatBuddyName(aNick);
       // To try to ensure that we aren't misidentifying a nick with a
       // contact, we require at least that the normalizedChatBuddyName of
       // the nick is normalized like a normalizedName for contacts.
       if (normalizedNick == account.normalize(normalizedNick)) {
-        let accountBuddy =
-          Services.contacts.getAccountBuddyByNameAndAccount(normalizedNick,
-            account);
+        let accountBuddy = Services.contacts.getAccountBuddyByNameAndAccount(
+          normalizedNick,
+          account
+        );
         if (accountBuddy) {
           return this.updateTooltipFromBuddy(accountBuddy);
         }
       }
 
       this.reset();
       this.setAttribute("displayname", aNick);
       this.setAttribute("iconPrpl", account.protocol.iconBaseURI + "icon.png");
--- a/chat/content/conversation-browser.js
+++ b/chat/content/conversation-browser.js
@@ -1,760 +1,853 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
-  * License, v. 2.0. If a copy of the MPL was not distributed with this
-  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 /* global MozXULElement */
 
 // Wrap in a block to prevent leaking to window scope.
 {
-  const {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm");
+  const { Services } = ChromeUtils.import(
+    "resource://gre/modules/Services.jsm"
+  );
   const {
-  initHTMLDocument,
-  serializeSelection,
-  getCurrentTheme,
-  isNextMessage,
-  getHTMLForMessage,
-  insertHTMLForMessage,
-} = ChromeUtils.import("resource:///modules/imThemes.jsm");
-  const {smileTextNode} = ChromeUtils.import("resource:///modules/imSmileys.jsm");
-  const {cleanupImMarkup} = ChromeUtils.import("resource:///modules/imContentSink.jsm");
+    initHTMLDocument,
+    serializeSelection,
+    getCurrentTheme,
+    isNextMessage,
+    getHTMLForMessage,
+    insertHTMLForMessage,
+  } = ChromeUtils.import("resource:///modules/imThemes.jsm");
+  const { smileTextNode } = ChromeUtils.import(
+    "resource:///modules/imSmileys.jsm"
+  );
+  const { cleanupImMarkup } = ChromeUtils.import(
+    "resource:///modules/imContentSink.jsm"
+  );
+
+  (function() {
+    // <browser> is lazily set up through setElementCreationCallback,
+    // i.e. put into customElements the first time it's really seen.
+    // Create a fake to ensure browser exists in customElements, since otherwise
+    // we can't extend it. Then make sure this fake doesn't stay around.
+    if (!customElements.get("browser")) {
+      delete document.createXULElement("browser");
+    }
+  })();
+
+  /**
+   * The chat conversation browser, i.e. the main content on the chat tab.
+   * @augments {MozBrowser}
+   */
+  class MozConversationBrowser extends customElements.get("browser") {
+    constructor() {
+      super();
+
+      this._conv = null;
 
-(function() {
-  // <browser> is lazily set up through setElementCreationCallback,
-  // i.e. put into customElements the first time it's really seen.
-  // Create a fake to ensure browser exists in customElements, since otherwise
-  // we can't extend it. Then make sure this fake doesn't stay around.
-  if (!customElements.get("browser")) {
-    delete document.createXULElement("browser");
-  }
-})();
+      // @implements {nsIWebProgressListener}
+      this.progressListener = {
+        onStateChange: (progress, request, stateFlags, status) => {
+          if (
+            !(stateFlags & Ci.nsIWebProgressListener.STATE_STOP) ||
+            !(
+              stateFlags & Ci.nsIWebProgressListener.STATE_IS_DOCUMENT ||
+              stateFlags & Ci.nsIWebProgressListener.STATE_IS_WINDOW
+            )
+          ) {
+            return;
+          }
+          if (!this._loadState) {
+            initHTMLDocument(this._conv, this.theme, this.contentDocument);
+            this._loadState = 1;
+            this._exposeMethodsToContent();
+            return;
+          }
+          this.removeProgressListener(this.progressListener);
+
+          this.initMagicCopy();
 
-/**
- * The chat conversation browser, i.e. the main content on the chat tab.
- * @augments {MozBrowser}
- */
-class MozConversationBrowser extends customElements.get("browser") {
-  constructor() {
-    super();
+          // We need to reset these variables here to avoid a race
+          // condition if we are starting to display a new conversation
+          // but the display of the previous conversation wasn't finished.
+          // This can happen if the user quickly changes the selected
+          // conversation in the log viewer.
+          this._lastMessage = null;
+          this._lastMessageIsContext = true;
+          this._firstNonContextElt = null;
+          this._messageDisplayPending = false;
+          this._pendingMessages = [];
+          this._nextPendingMessageIndex = 0;
+          this._pendingMessagesDisplayed = 0;
+          this._displayPendingMessagesCalls = 0;
+          this._sessions = [];
+          if (this.progressBar) {
+            this.progressBar.hidden = true;
+          }
+
+          this.onChatNodeContentLoad = this.onContentElementLoad.bind(this);
+          this.contentChatNode.addEventListener(
+            "load",
+            this.onChatNodeContentLoad,
+            true
+          );
 
-    this._conv = null;
+          // Notify observers to get the conversation shown.
+          Services.obs.notifyObservers(this, "conversation-loaded");
+        },
+        onProgressChange(
+          progress,
+          request,
+          curSelf,
+          maxSelf,
+          curTotal,
+          maxTotal
+        ) {},
+        onLocationChange(aprogress, request, location) {},
+        onStatusChange(progress, request, status, message) {},
+        onSecurityChange(progress, request, state) {},
+        onContentBlockingEvent(progress, request, event) {},
+        QueryInterface: ChromeUtils.generateQI([
+          Ci.nsIWebProgressListener,
+          Ci.nsISupportsWeakReference,
+        ]),
+      };
 
-    // @implements {nsIWebProgressListener}
-    this.progressListener = {
-      onStateChange: (progress, request, stateFlags, status) => {
-        if (!(stateFlags & Ci.nsIWebProgressListener.STATE_STOP) ||
-            !((stateFlags & Ci.nsIWebProgressListener.STATE_IS_DOCUMENT) ||
-              (stateFlags & Ci.nsIWebProgressListener.STATE_IS_WINDOW))) {
+      // Make sure to load URLs externally.
+      this.addEventListener("click", event => {
+        // Right click should open the context menu.
+        if (event.button == 2) {
           return;
         }
-        if (!this._loadState) {
-          initHTMLDocument(this._conv, this.theme, this.contentDocument);
-          this._loadState = 1;
-          this._exposeMethodsToContent();
+
+        // The 'click' event is fired even when the link is
+        // activated with the keyboard.
+
+        // The event target may be a descendant of the actual link.
+        let url;
+        for (let elem = event.target; elem; elem = elem.parentNode) {
+          if (elem instanceof HTMLAnchorElement) {
+            url = elem.href;
+            if (url) {
+              break;
+            }
+          }
+        }
+        if (!url) {
+          return;
+        }
+
+        let uri = Services.io.newURI(url);
+
+        // http and https are the only schemes that are both
+        // allowed by our IM filters and exposed.
+        if (!uri.schemeIs("http") && !uri.schemeIs("https")) {
           return;
         }
-        this.removeProgressListener(this.progressListener);
+
+        event.preventDefault();
+        event.stopPropagation();
+
+        // loadURI can throw if the default browser is misconfigured.
+        Cc["@mozilla.org/uriloader/external-protocol-service;1"]
+          .getService(Ci.nsIExternalProtocolService)
+          .loadURI(uri);
+      });
+
+      this.addEventListener("keypress", event => {
+        switch (event.keyCode) {
+          case KeyEvent.DOM_VK_PAGE_UP: {
+            if (event.shiftKey) {
+              this.contentWindow.scrollByPages(-1);
+            } else if (event.altKey) {
+              this.scrollToPreviousSection();
+            }
+            break;
+          }
+          case KeyEvent.DOM_VK_PAGE_DOWN: {
+            if (event.shiftKey) {
+              this.contentWindow.scrollByPages(1);
+            } else if (event.altKey) {
+              this.scrollToNextSection();
+            }
+            break;
+          }
+          case KeyEvent.DOM_VK_HOME: {
+            this.scrollToPreviousSection();
+            event.preventDefault();
+            break;
+          }
+          case KeyEvent.DOM_VK_END: {
+            this.scrollToNextSection();
+            event.preventDefault();
+            break;
+          }
+        }
+      });
+    }
+
+    connectedCallback() {
+      super.connectedCallback();
+
+      this._theme = null;
+
+      this._loadState = 0;
+
+      this.autoCopyEnabled = false;
+
+      this.magicCopyPref =
+        "messenger.conversations.selections.magicCopyEnabled";
+
+      this.magicCopyInitialized = false;
+
+      this._destroyed = false;
+
+      // Makes the chat browser scroll to the bottom automatically when we append
+      // a new message. This behavior gets disabled when the user scrolls up to
+      // look at the history, and we re-enable it when the user scrolls to
+      // (within 10px) of the bottom.
+      this._convScrollEnabled = true;
+
+      this._textModifiers = [smileTextNode];
+
+      // These variables are reset in onStateChange:
+      this._lastMessage = null;
+      this._lastMessageIsContext = true;
+      this._firstNonContextElt = null;
+      this._messageDisplayPending = false;
+      this._pendingMessages = [];
+      this._nextPendingMessageIndex = 0;
+      this._pendingMessagesDisplayed = 0;
+      this._displayPendingMessagesCalls = 0;
+      this._sessions = [];
+
+      this.progressBar = null;
 
-        this.initMagicCopy();
+      this.addEventListener("scroll", this.browserScroll);
+      this.addEventListener("resize", this.browserResize);
+
+      // @implements {nsIObserver}
+      this.prefObserver = (subject, topic, data) => {
+        if (this.magicCopyEnabled) {
+          this.enableMagicCopy();
+        } else {
+          this.disableMagicCopy();
+        }
+      };
+
+      // @implements {nsIController}
+      this.copyController = {
+        supportsCommand(command) {
+          return command == "cmd_copy" || command == "cmd_cut";
+        },
+        isCommandEnabled: command => {
+          return (
+            command == "cmd_copy" &&
+            !this.contentWindow.getSelection().isCollapsed
+          );
+        },
+        doCommand: command => {
+          let selection = this.contentWindow.getSelection();
+          if (selection.isCollapsed) {
+            return;
+          }
+
+          Cc["@mozilla.org/widget/clipboardhelper;1"]
+            .getService(Ci.nsIClipboardHelper)
+            .copyString(serializeSelection(selection));
+        },
+        onEvent(command) {},
+        QueryInterface: ChromeUtils.generateQI([Ci.nsIController]),
+      };
+
+      // @implements {nsISelectionListener}
+      this.chatSelectionListener = {
+        notifySelectionChanged(document, selection, reason) {
+          if (
+            !(
+              reason & Ci.nsISelectionListener.MOUSEUP_REASON ||
+              reason & Ci.nsISelectionListener.SELECTALL_REASON ||
+              reason & Ci.nsISelectionListener.KEYPRESS_REASON
+            )
+          ) {
+            return;
+          } // we are still dragging, don't bother with the selection
+
+          Cc["@mozilla.org/widget/clipboardhelper;1"]
+            .getService(Ci.nsIClipboardHelper)
+            .copyStringToClipboard(
+              serializeSelection(selection),
+              Ci.nsIClipboard.kSelectionClipboard
+            );
+        },
+        QueryInterface: ChromeUtils.generateQI([Ci.nsISelectionListener]),
+      };
+    }
+
+    init(conversation) {
+      // Magic Copy may be initialized if the convbrowser is already
+      // displaying a conversation.
+      this.uninitMagicCopy();
+
+      this._conv = conversation;
+
+      // init is called when the message style preview is
+      // reloaded so we need to reset _theme.
+      this._theme = null;
+
+      // Prevent ongoing asynchronous message display from continuing.
+      this._messageDisplayPending = false;
+
+      // _loadState is 0 while loading conv.html and 1 while
+      // loading the real conversation HTML.
+      this._loadState = 0;
 
-        // We need to reset these variables here to avoid a race
-        // condition if we are starting to display a new conversation
-        // but the display of the previous conversation wasn't finished.
-        // This can happen if the user quickly changes the selected
-        // conversation in the log viewer.
-        this._lastMessage = null;
-        this._lastMessageIsContext = true;
-        this._firstNonContextElt = null;
-        this._messageDisplayPending = false;
+      this.docShell.charset = "UTF-8";
+      const URI = "chrome://chat/content/conv.html";
+      const loadURIOptions = {
+        triggeringPrincipal: Services.scriptSecurityManager.getSystemPrincipal(),
+      };
+      this.webNavigation.loadURI(URI, loadURIOptions);
+      this.addProgressListener(this.progressListener);
+    }
+
+    get theme() {
+      return this._theme || (this._theme = getCurrentTheme());
+    }
+
+    get contentDocument() {
+      return this.webNavigation.document;
+    }
+
+    get contentChatNode() {
+      return this.contentDocument.getElementById("Chat");
+    }
+
+    get magicCopyEnabled() {
+      return Services.prefs.getBoolPref(this.magicCopyPref);
+    }
+
+    enableMagicCopy() {
+      this.contentWindow.controllers.insertControllerAt(0, this.copyController);
+      this.autoCopyEnabled =
+        Services.clipboard.supportsSelectionClipboard() &&
+        Services.prefs.getBoolPref("clipboard.autocopy");
+      if (this.autoCopyEnabled) {
+        this.contentWindow
+          .getSelection()
+          .addSelectionListener(this.chatSelectionListener);
+      }
+    }
+
+    disableMagicCopy() {
+      this.contentWindow.controllers.removeController(this.copyController);
+      if (this.autoCopyEnabled) {
+        this.contentWindow
+          .getSelection()
+          .removeSelectionListener(this.chatSelectionListener);
+      }
+    }
+
+    initMagicCopy() {
+      if (this.magicCopyInitialized) {
+        return;
+      }
+      Services.prefs.addObserver(this.magicCopyPref, this.prefObserver);
+      this.magicCopyInitialized = true;
+      if (this.magicCopyEnabled) {
+        this.enableMagicCopy();
+      }
+    }
+
+    uninitMagicCopy() {
+      if (!this.magicCopyInitialized) {
+        return;
+      }
+      Services.prefs.removeObserver(this.magicCopyPref, this.prefObserver);
+      if (this.magicCopyEnabled) {
+        this.disableMagicCopy();
+      }
+      this.magicCopyInitialized = false;
+    }
+
+    destroy() {
+      super.destroy();
+      if (this._destroyed) {
+        return;
+      }
+      this._destroyed = true;
+      this._messageDisplayPending = false;
+
+      this.uninitMagicCopy();
+
+      if (this.contentChatNode) {
+        // Remove the listener only if the conversation was initialized.
+        this.contentChatNode.removeEventListener(
+          "load",
+          this.onChatNodeContentLoad,
+          true
+        );
+      }
+    }
+
+    _updateConvScrollEnabled() {
+      // Enable auto-scroll if the scrollbar is at the bottom.
+      let body = this.contentDocument.querySelector("body");
+      this._convScrollEnabled =
+        body.scrollHeight <= body.scrollTop + body.clientHeight + 10;
+      return this._convScrollEnabled;
+    }
+
+    convScrollEnabled() {
+      return this._convScrollEnabled || this._updateConvScrollEnabled();
+    }
+
+    _scrollToElement(aElt) {
+      aElt.scrollIntoView(true);
+      this._scrollingIntoView = true;
+    }
+
+    _exposeMethodsToContent() {
+      // Expose scrollToElement and convScrollEnabled to the message styles.
+      this.contentWindow.scrollToElement = this._scrollToElement.bind(this);
+      this.contentWindow.convScrollEnabled = this.convScrollEnabled.bind(this);
+    }
+
+    addTextModifier(aModifier) {
+      if (!this._textModifiers.includes(aModifier)) {
+        this._textModifiers.push(aModifier);
+      }
+    }
+
+    set isActive(value) {
+      this.docShell.isActive = value;
+      if (value && this._pendingMessages.length) {
+        this.startDisplayingPendingMessages(false);
+      }
+    }
+
+    appendMessage(aMsg, aContext, aFirstUnread) {
+      this._pendingMessages.push({
+        msg: aMsg,
+        context: aContext,
+        firstUnread: aFirstUnread,
+      });
+      if (this.docShell.isActive) {
+        this.startDisplayingPendingMessages(true);
+      }
+    }
+
+    startDisplayingPendingMessages(delayed) {
+      if (this._messageDisplayPending) {
+        return;
+      }
+      this._messageDisplayPending = true;
+      this.contentWindow.messageInsertPending = true;
+      if (delayed) {
+        requestIdleCallback(this.displayPendingMessages.bind(this));
+      } else {
+        // 200ms here is a generous amount of time. The conversation switch
+        // should take no more than 100ms to feel 'immediate', but the perceived
+        // performance if we flicker is likely even worse than having a barely
+        // perceptible delay.
+        let deadline = Cu.now() + 200;
+        this.displayPendingMessages({
+          timeRemaining() {
+            return deadline - Cu.now();
+          },
+        });
+      }
+    }
+
+    // getNextPendingMessage and getPendingMessagesCount are the
+    // only 2 methods accessing the this._pendingMessages array
+    // directly during the chunked display of messages. It is
+    // possible to override these 2 methods to replace the array
+    // with something else. The log viewer for example uses an
+    // enumerator that creates message objects lazily to avoid
+    // jank when displaying lots of messages.
+    getNextPendingMessage() {
+      let length = this._pendingMessages.length;
+      if (this._nextPendingMessageIndex == length) {
+        return null;
+      }
+
+      let result = this._pendingMessages[this._nextPendingMessageIndex++];
+
+      if (this._nextPendingMessageIndex == length) {
         this._pendingMessages = [];
         this._nextPendingMessageIndex = 0;
-        this._pendingMessagesDisplayed = 0;
-        this._displayPendingMessagesCalls = 0;
-        this._sessions = [];
-        if (this.progressBar)
-          this.progressBar.hidden = true;
+      }
 
-        this.onChatNodeContentLoad = this.onContentElementLoad.bind(this);
-        this.contentChatNode.addEventListener("load", this.onChatNodeContentLoad, true);
+      return result;
+    }
 
-        // Notify observers to get the conversation shown.
-        Services.obs.notifyObservers(this, "conversation-loaded");
-      },
-      onProgressChange(progress, request, curSelf, maxSelf, curTotal, maxTotal) { },
-      onLocationChange(aprogress, request, location) { },
-      onStatusChange(progress, request, status, message) { },
-      onSecurityChange(progress, request, state) { },
-      onContentBlockingEvent(progress, request, event) { },
-      QueryInterface: ChromeUtils.generateQI([
-        Ci.nsIWebProgressListener,
-        Ci.nsISupportsWeakReference],
-      ),
-    };
+    getPendingMessagesCount() {
+      return this._pendingMessages.length;
+    }
 
-    // Make sure to load URLs externally.
-    this.addEventListener("click", (event) => {
-      // Right click should open the context menu.
-      if (event.button == 2)
-        return;
-
-      // The 'click' event is fired even when the link is
-      // activated with the keyboard.
-
-      // The event target may be a descendant of the actual link.
-      let url;
-      for (let elem = event.target; elem; elem = elem.parentNode) {
-        if (elem instanceof HTMLAnchorElement) {
-          url = elem.href;
-          if (url)
-            break;
-        }
-      }
-      if (!url) {
+    displayPendingMessages(timing) {
+      if (!this._messageDisplayPending) {
         return;
       }
 
-      let uri = Services.io.newURI(url);
-
-      // http and https are the only schemes that are both
-      // allowed by our IM filters and exposed.
-      if (!uri.schemeIs("http") && !uri.schemeIs("https"))
-        return;
-
-      event.preventDefault();
-      event.stopPropagation();
-
-      // loadURI can throw if the default browser is misconfigured.
-      Cc["@mozilla.org/uriloader/external-protocol-service;1"]
-        .getService(Ci.nsIExternalProtocolService)
-        .loadURI(uri);
-    });
-
-    this.addEventListener("keypress", (event) => {
-      switch (event.keyCode) {
-        case KeyEvent.DOM_VK_PAGE_UP: {
-          if (event.shiftKey) {
-            this.contentWindow.scrollByPages(-1);
-          } else if (event.altKey) {
-            this.scrollToPreviousSection();
-          }
+      let max = this.getPendingMessagesCount();
+      do {
+        // One message takes less than 2ms on average.
+        let msg = this.getNextPendingMessage();
+        if (!msg) {
           break;
         }
-        case KeyEvent.DOM_VK_PAGE_DOWN: {
-          if (event.shiftKey) {
-            this.contentWindow.scrollByPages(1);
-          } else if (event.altKey) {
-            this.scrollToNextSection();
+        this.displayMessage(
+          msg.msg,
+          msg.context,
+          ++this._pendingMessagesDisplayed < max,
+          msg.firstUnread
+        );
+      } while (timing.timeRemaining() > 2);
+
+      let event = document.createEvent("UIEvents");
+      event.initUIEvent("MessagesDisplayed", false, false, window, 0);
+      if (this._pendingMessagesDisplayed < max) {
+        if (this.progressBar) {
+          // Show progress bar if after the third call (ca. 120ms)
+          // less than half the messages have been displayed.
+          if (
+            ++this._displayPendingMessagesCalls > 2 &&
+            max > 2 * this._pendingMessagesDisplayed
+          ) {
+            this.progressBar.hidden = false;
           }
-          break;
+          this.progressBar.max = max;
+          this.progressBar.value = this._pendingMessagesDisplayed;
+        }
+        requestIdleCallback(this.displayPendingMessages.bind(this));
+        this.dispatchEvent(event);
+        return;
+      }
+      this.contentWindow.messageInsertPending = false;
+      this._messageDisplayPending = false;
+      this._pendingMessagesDisplayed = 0;
+      this._displayPendingMessagesCalls = 0;
+      if (this.progressBar) {
+        this.progressBar.hidden = true;
+      }
+      this.dispatchEvent(event);
+    }
+
+    displayMessage(aMsg, aContext, aNoAutoScroll, aFirstUnread) {
+      let doc = this.contentDocument;
+
+      if (aMsg.noLog && aMsg.notification && aMsg.who == "sessionstart") {
+        // New session log.
+        if (this._lastMessage) {
+          let ruler = doc.createElement("hr");
+          ruler.className = "sessionstart-ruler";
+          this.contentChatNode.appendChild(ruler);
+          this._sessions.push(ruler);
+          // Close any open bubble.
+          this._lastMessage = null;
+        }
+        // Suppress this message unless it was an error message.
+        if (!aMsg.error) {
+          return;
+        }
+      }
+
+      let cs = Cc["@mozilla.org/txttohtmlconv;1"].getService(
+        Ci.mozITXTToHTMLConv
+      );
+
+      // kStructPhrase creates tags for plaintext-markup like *bold*,
+      // /italics/, etc. We always use this; the content filter will
+      // filter it out if the user does not want styling.
+      let csFlags = cs.kStructPhrase;
+      // Automatically find and link freetext URLs
+      if (!aMsg.noLinkification) {
+        csFlags |= cs.kURLs;
+      }
+
+      if (aFirstUnread) {
+        this.setUnreadRuler();
+      }
+
+      // Right trim before displaying. This removes any OTR related
+      // whitespace when the extension isn't enabled.
+      let msg = aMsg.displayMessage.trimRight();
+
+      // The slash of a leading '/me' should not be used to
+      // format as italic, so we remove the '/me' text before
+      // scanning the HTML, and we add it back later.
+      let meRegExp = /^((<[^>]+>)*)\/me /;
+      let me = false;
+      if (meRegExp.test(msg)) {
+        me = true;
+        msg = msg.replace(meRegExp, "$1");
+      }
+
+      msg = cs
+        .scanHTML(msg.replace(/&/g, "FROM-DTD-amp"), csFlags)
+        .replace(/FROM-DTD-amp/g, "&");
+
+      if (me) {
+        msg = msg.replace(/^((<[^>]+>)*)/, "$1/me ");
+      }
+
+      aMsg.message = cleanupImMarkup(
+        msg.replace(/\r?\n/g, "<br/>"),
+        null,
+        this._textModifiers
+      );
+
+      let next =
+        (aContext == this._lastMessageIsContext || aMsg.system) &&
+        isNextMessage(this.theme, aMsg, this._lastMessage);
+      let newElt;
+      if (next && aFirstUnread) {
+        // If there wasn't an unread ruler, this would be a Next message.
+        // Therefore, save that version for later.
+        let html = getHTMLForMessage(aMsg, this.theme, next, aContext);
+        let ruler = doc.getElementById("unread-ruler");
+        ruler.nextMsgHtml = html;
+        ruler._originalMsg = aMsg;
+
+        // Remember where the Next message(s) would have gone.
+        let insert = doc.getElementById("insert");
+        if (!insert) {
+          insert = doc.createElement("div");
+          ruler.parentNode.insertBefore(insert, ruler);
         }
-        case KeyEvent.DOM_VK_HOME: {
-          this.scrollToPreviousSection();
-          event.preventDefault();
-          break;
+        insert.id = "insert-before";
+
+        next = false;
+        html = getHTMLForMessage(aMsg, this.theme, next, aContext);
+        newElt = insertHTMLForMessage(aMsg, html, doc, next);
+        let marker = doc.createElement("div");
+        marker.id = "end-of-split-block";
+        newElt.parentNode.appendChild(marker);
+
+        // Bracket the place where additional Next messages will be added,
+        // if that's not after the end-of-split-block element.
+        insert = doc.getElementById("insert");
+        if (insert) {
+          marker = doc.createElement("div");
+          marker.id = "next-messages-start";
+          insert.parentNode.insertBefore(marker, insert);
+          marker = doc.createElement("div");
+          marker.id = "next-messages-end";
+          insert.parentNode.insertBefore(marker, insert.nextSibling);
+        }
+      } else {
+        let html = getHTMLForMessage(aMsg, this.theme, next, aContext);
+        newElt = insertHTMLForMessage(aMsg, html, doc, next);
+      }
+
+      if (!aNoAutoScroll) {
+        newElt.getBoundingClientRect(); // avoid ireflow bugs
+        if (this.convScrollEnabled()) {
+          this._scrollToElement(newElt);
+        }
+      }
+      this._lastElement = newElt;
+      this._lastMessage = aMsg;
+      if (!aContext && !this._firstNonContextElt && !aMsg.system) {
+        this._firstNonContextElt = newElt;
+      }
+      this._lastMessageIsContext = aContext;
+    }
+
+    setUnreadRuler() {
+      // Remove any existing ruler (occurs when the window has lost focus).
+      this.removeUnreadRuler();
+
+      let ruler = this.contentDocument.createElement("hr");
+      ruler.id = "unread-ruler";
+      this.contentChatNode.appendChild(ruler);
+    }
+
+    removeUnreadRuler() {
+      let doc = this.contentDocument;
+      let ruler = doc.getElementById("unread-ruler");
+      if (!ruler) {
+        return;
+      }
+
+      // If a message block was split by the ruler, rejoin it.
+      let moveTo = doc.getElementById("insert-before");
+      if (moveTo) {
+        // Protect an existing insert node.
+        let actualInsert = doc.getElementById("insert");
+        if (actualInsert) {
+          actualInsert.id = "actual-insert";
         }
-        case KeyEvent.DOM_VK_END: {
-          this.scrollToNextSection();
-          event.preventDefault();
+
+        // Add first message following the ruler as a Next type message.
+        // Replicates the relevant parts of insertHTMLForMessage().
+        let range = doc.createRange();
+        let moveToParent = moveTo.parentNode;
+        range.selectNode(moveToParent);
+        // eslint-disable-next-line no-unsanitized/method
+        let documentFragment = range.createContextualFragment(
+          ruler.nextMsgHtml
+        );
+        for (
+          let root = documentFragment.firstChild;
+          root;
+          root = root.nextSibling
+        ) {
+          root._originalMsg = ruler._originalMsg;
+        }
+        moveToParent.insertBefore(documentFragment, moveTo);
+
+        // If this added an insert node, insert the next messages there.
+        let insert = doc.getElementById("insert");
+        if (insert) {
+          moveTo.remove();
+          moveTo = insert;
+          moveToParent = moveTo.parentNode;
+        }
+
+        // Move remaining messages from the message block following the ruler.
+        let nextMessagesStart = doc.getElementById("next-messages-start");
+        if (nextMessagesStart) {
+          range = doc.createRange();
+          range.setStartAfter(nextMessagesStart);
+          range.setEndBefore(doc.getElementById("next-messages-end"));
+          moveToParent.insertBefore(range.extractContents(), moveTo);
+        }
+        moveTo.remove();
+
+        // Restore existing insert node.
+        if (actualInsert) {
+          actualInsert.id = "insert";
+        }
+
+        // Delete surplus message block.
+        range = doc.createRange();
+        range.setStartAfter(ruler);
+        range.setEndAfter(doc.getElementById("end-of-split-block"));
+        range.deleteContents();
+      }
+      ruler.remove();
+    }
+
+    _getSections() {
+      // If a section is displayed below this point, we assume not enough of
+      // it is visible, so we must scroll to it.
+      // The 3/4 constant is arbitrary, but it has to be greater than 1/2.
+      this._maximalSectionOffset = Math.round((this.clientHeight * 3) / 4);
+
+      // Get list of current section elements.
+      let sectionElements = [];
+      if (this._firstNonContextElt) {
+        sectionElements.push(this._firstNonContextElt);
+      }
+      let ruler = this.contentDocument.getElementById("unread-ruler");
+      if (ruler) {
+        sectionElements.push(ruler);
+      }
+      sectionElements = sectionElements.concat(this._sessions);
+
+      // Return ordered array of sections with entries
+      // [Y, scrollY such that Y is centered]
+      let sections = [];
+      let maxY = this.contentWindow.scrollMaxY;
+      for (let i = 0; i < sectionElements.length; ++i) {
+        let y = sectionElements[i].offsetTop;
+        // The section is unnecessary if close to top/bottom of conversation.
+        if (y < this._maximalSectionOffset || maxY < y) {
+          continue;
+        }
+        sections.push([y, y - Math.round(this.clientHeight / 2)]);
+      }
+      sections.sort((a, b) => a[0] - b[0]);
+      return sections;
+    }
+
+    scrollToPreviousSection() {
+      let sections = this._getSections();
+      let y = this.contentWindow.scrollY;
+      let newY = 0;
+      for (let i = sections.length - 1; i >= 0; --i) {
+        let section = sections[i];
+        if (y > section[0]) {
+          newY = section[1];
           break;
         }
       }
-    });
-  }
-
-  connectedCallback() {
-    super.connectedCallback();
-
-    this._theme = null;
-
-    this._loadState = 0;
-
-    this.autoCopyEnabled = false;
-
-    this.magicCopyPref = "messenger.conversations.selections.magicCopyEnabled";
-
-    this.magicCopyInitialized = false;
-
-    this._destroyed = false;
-
-    // Makes the chat browser scroll to the bottom automatically when we append
-    // a new message. This behavior gets disabled when the user scrolls up to
-    // look at the history, and we re-enable it when the user scrolls to
-    // (within 10px) of the bottom.
-    this._convScrollEnabled = true;
-
-    this._textModifiers = [smileTextNode];
-
-    // These variables are reset in onStateChange:
-    this._lastMessage = null;
-    this._lastMessageIsContext = true;
-    this._firstNonContextElt = null;
-    this._messageDisplayPending = false;
-    this._pendingMessages = [];
-    this._nextPendingMessageIndex = 0;
-    this._pendingMessagesDisplayed = 0;
-    this._displayPendingMessagesCalls = 0;
-    this._sessions = [];
-
-    this.progressBar = null;
-
-    this.addEventListener("scroll", this.browserScroll);
-    this.addEventListener("resize", this.browserResize);
-
-
-    // @implements {nsIObserver}
-    this.prefObserver = (subject, topic, data) => {
-      if (this.magicCopyEnabled) {
-        this.enableMagicCopy();
-      } else {
-        this.disableMagicCopy();
-      }
-    };
-
-    // @implements {nsIController}
-    this.copyController = {
-      supportsCommand(command) {
-        return command == "cmd_copy" || command == "cmd_cut";
-      },
-      isCommandEnabled: command => {
-        return command == "cmd_copy" &&
-          !this.contentWindow.getSelection().isCollapsed;
-      },
-      doCommand: command => {
-        let selection = this.contentWindow.getSelection();
-        if (selection.isCollapsed)
-          return;
-
-        Cc["@mozilla.org/widget/clipboardhelper;1"]
-          .getService(Ci.nsIClipboardHelper)
-          .copyString(serializeSelection(selection));
-      },
-      onEvent(command) { },
-      QueryInterface: ChromeUtils.generateQI([Ci.nsIController]),
-    };
-
-    // @implements {nsISelectionListener}
-    this.chatSelectionListener = {
-      notifySelectionChanged(document, selection, reason) {
-        if (!(reason & Ci.nsISelectionListener.MOUSEUP_REASON ||
-              reason & Ci.nsISelectionListener.SELECTALL_REASON ||
-              reason & Ci.nsISelectionListener.KEYPRESS_REASON))
-          return; // we are still dragging, don't bother with the selection
-
-        Cc["@mozilla.org/widget/clipboardhelper;1"]
-          .getService(Ci.nsIClipboardHelper)
-          .copyStringToClipboard(serializeSelection(selection),
-                                 Ci.nsIClipboard.kSelectionClipboard);
-      },
-      QueryInterface: ChromeUtils.generateQI([Ci.nsISelectionListener]),
-    };
-  }
-
-  init(conversation) {
-    // Magic Copy may be initialized if the convbrowser is already
-    // displaying a conversation.
-    this.uninitMagicCopy();
-
-    this._conv = conversation;
-
-    // init is called when the message style preview is
-    // reloaded so we need to reset _theme.
-    this._theme = null;
-
-    // Prevent ongoing asynchronous message display from continuing.
-    this._messageDisplayPending = false;
-
-    // _loadState is 0 while loading conv.html and 1 while
-    // loading the real conversation HTML.
-    this._loadState = 0;
-
-    this.docShell.charset = "UTF-8";
-    const URI = "chrome://chat/content/conv.html";
-    const loadURIOptions = {
-      triggeringPrincipal: Services.scriptSecurityManager.getSystemPrincipal(),
-    };
-    this.webNavigation.loadURI(URI, loadURIOptions);
-    this.addProgressListener(this.progressListener);
-  }
-
-  get theme() {
-    return this._theme || (this._theme = getCurrentTheme());
-  }
-
-  get contentDocument() {
-    return this.webNavigation.document;
-  }
-
-  get contentChatNode() {
-    return this.contentDocument.getElementById("Chat");
-  }
+      this.contentWindow.scrollTo(0, newY);
+    }
 
-  get magicCopyEnabled() {
-    return Services.prefs.getBoolPref(this.magicCopyPref);
-  }
-
-  enableMagicCopy() {
-    this.contentWindow.controllers.insertControllerAt(0, this.copyController);
-    this.autoCopyEnabled = Services.clipboard.supportsSelectionClipboard() &&
-      Services.prefs.getBoolPref("clipboard.autocopy");
-    if (this.autoCopyEnabled) {
-      this.contentWindow.getSelection().addSelectionListener(this.chatSelectionListener);
-    }
-  }
-
-  disableMagicCopy() {
-    this.contentWindow.controllers.removeController(this.copyController);
-    if (this.autoCopyEnabled) {
-      this.contentWindow.getSelection().removeSelectionListener(this.chatSelectionListener);
-    }
-  }
-
-  initMagicCopy() {
-    if (this.magicCopyInitialized)
-      return;
-    Services.prefs.addObserver(this.magicCopyPref, this.prefObserver);
-    this.magicCopyInitialized = true;
-    if (this.magicCopyEnabled)
-      this.enableMagicCopy();
-  }
-
-  uninitMagicCopy() {
-    if (!this.magicCopyInitialized)
-      return;
-    Services.prefs.removeObserver(this.magicCopyPref, this.prefObserver);
-    if (this.magicCopyEnabled)
-      this.disableMagicCopy();
-    this.magicCopyInitialized = false;
-  }
-
-  destroy() {
-    super.destroy();
-    if (this._destroyed)
-      return;
-    this._destroyed = true;
-    this._messageDisplayPending = false;
-
-    this.uninitMagicCopy();
-
-    if (this.contentChatNode) {
-      // Remove the listener only if the conversation was initialized.
-      this.contentChatNode
-          .removeEventListener("load", this.onChatNodeContentLoad, true);
-    }
-  }
-
-  _updateConvScrollEnabled() {
-    // Enable auto-scroll if the scrollbar is at the bottom.
-    let body = this.contentDocument.querySelector("body");
-    this._convScrollEnabled =
-      body.scrollHeight <= body.scrollTop + body.clientHeight + 10;
-    return this._convScrollEnabled;
-  }
-
-  convScrollEnabled() {
-    return this._convScrollEnabled || this._updateConvScrollEnabled();
-  }
-
-  _scrollToElement(aElt) {
-    aElt.scrollIntoView(true);
-    this._scrollingIntoView = true;
-  }
-
-  _exposeMethodsToContent() {
-    // Expose scrollToElement and convScrollEnabled to the message styles.
-    this.contentWindow.scrollToElement = this._scrollToElement.bind(this);
-    this.contentWindow.convScrollEnabled = this.convScrollEnabled.bind(this);
-  }
-
-  addTextModifier(aModifier) {
-    if (!this._textModifiers.includes(aModifier))
-      this._textModifiers.push(aModifier);
-  }
-
-  set isActive(value) {
-    this.docShell.isActive = value;
-    if (value && this._pendingMessages.length)
-      this.startDisplayingPendingMessages(false);
-  }
-
-  appendMessage(aMsg, aContext, aFirstUnread) {
-    this._pendingMessages.push({
-      msg: aMsg,
-      context: aContext,
-      firstUnread: aFirstUnread,
-    });
-    if (this.docShell.isActive)
-      this.startDisplayingPendingMessages(true);
-  }
-
-  startDisplayingPendingMessages(delayed) {
-    if (this._messageDisplayPending)
-      return;
-    this._messageDisplayPending = true;
-    this.contentWindow.messageInsertPending = true;
-    if (delayed) {
-      requestIdleCallback(this.displayPendingMessages.bind(this));
-    } else {
-      // 200ms here is a generous amount of time. The conversation switch
-      // should take no more than 100ms to feel 'immediate', but the perceived
-      // performance if we flicker is likely even worse than having a barely
-      // perceptible delay.
-      let deadline = Cu.now() + 200;
-      this.displayPendingMessages({
-        timeRemaining() {
-          return deadline - Cu.now();
-        },
-      });
-    }
-  }
-
-  // getNextPendingMessage and getPendingMessagesCount are the
-  // only 2 methods accessing the this._pendingMessages array
-  // directly during the chunked display of messages. It is
-  // possible to override these 2 methods to replace the array
-  // with something else. The log viewer for example uses an
-  // enumerator that creates message objects lazily to avoid
-  // jank when displaying lots of messages.
-  getNextPendingMessage() {
-    let length = this._pendingMessages.length;
-    if (this._nextPendingMessageIndex == length)
-      return null;
-
-    let result = this._pendingMessages[this._nextPendingMessageIndex++];
-
-    if (this._nextPendingMessageIndex == length) {
-      this._pendingMessages = [];
-      this._nextPendingMessageIndex = 0;
+    scrollToNextSection() {
+      let sections = this._getSections();
+      let y = this.contentWindow.scrollY;
+      let newY = this.contentWindow.scrollMaxY;
+      for (let i = 0; i < sections.length; ++i) {
+        let section = sections[i];
+        if (y + this._maximalSectionOffset < section[0]) {
+          newY = section[1];
+          break;
+        }
+      }
+      this.contentWindow.scrollTo(0, newY);
     }
 
-    return result;
-  }
-
-  getPendingMessagesCount() {
-    return this._pendingMessages.length;
-  }
-
-  displayPendingMessages(timing) {
-    if (!this._messageDisplayPending)
-      return;
-
-    let max = this.getPendingMessagesCount();
-    do {
-      // One message takes less than 2ms on average.
-      let msg = this.getNextPendingMessage();
-      if (!msg)
-        break;
-      this.displayMessage(msg.msg, msg.context,
-        ++this._pendingMessagesDisplayed < max,
-        msg.firstUnread);
-    } while (timing.timeRemaining() > 2);
-
-    let event = document.createEvent("UIEvents");
-    event.initUIEvent("MessagesDisplayed", false, false, window, 0);
-    if (this._pendingMessagesDisplayed < max) {
-      if (this.progressBar) {
-        // Show progress bar if after the third call (ca. 120ms)
-        // less than half the messages have been displayed.
-        if (++this._displayPendingMessagesCalls > 2 &&
-          max > 2 * this._pendingMessagesDisplayed)
-          this.progressBar.hidden = false;
-        this.progressBar.max = max;
-        this.progressBar.value = this._pendingMessagesDisplayed;
+    browserScroll(event) {
+      if (this._scrollingIntoView) {
+        // We have explicitly requested a scrollIntoView, ignore the event.
+        this._scrollingIntoView = false;
+        this._lastScrollHeight = this.scrollHeight;
+        this._lastScrollWidth = this.scrollWidth;
+        return;
       }
-      requestIdleCallback(this.displayPendingMessages.bind(this));
-      this.dispatchEvent(event);
-      return;
-    }
-    this.contentWindow.messageInsertPending = false;
-    this._messageDisplayPending = false;
-    this._pendingMessagesDisplayed = 0;
-    this._displayPendingMessagesCalls = 0;
-    if (this.progressBar)
-      this.progressBar.hidden = true;
-    this.dispatchEvent(event);
-  }
-
-  displayMessage(aMsg, aContext, aNoAutoScroll, aFirstUnread) {
-    let doc = this.contentDocument;
-
-    if (aMsg.noLog && aMsg.notification &&
-        aMsg.who == "sessionstart") {
-      // New session log.
-      if (this._lastMessage) {
-        let ruler = doc.createElement("hr");
-        ruler.className = "sessionstart-ruler";
-        this.contentChatNode.appendChild(ruler);
-        this._sessions.push(ruler);
-        // Close any open bubble.
-        this._lastMessage = null;
-      }
-      // Suppress this message unless it was an error message.
-      if (!aMsg.error)
-        return;
-    }
-
-    let cs = Cc["@mozilla.org/txttohtmlconv;1"]
-      .getService(Ci.mozITXTToHTMLConv);
 
-     // kStructPhrase creates tags for plaintext-markup like *bold*,
-     // /italics/, etc. We always use this; the content filter will
-     // filter it out if the user does not want styling.
-    let csFlags = cs.kStructPhrase;
-    // Automatically find and link freetext URLs
-    if (!aMsg.noLinkification)
-      csFlags |= cs.kURLs;
-
-    if (aFirstUnread)
-      this.setUnreadRuler();
-
-    // Right trim before displaying. This removes any OTR related
-    // whitespace when the extension isn't enabled.
-    let msg = aMsg.displayMessage.trimRight();
-
-    // The slash of a leading '/me' should not be used to
-    // format as italic, so we remove the '/me' text before
-    // scanning the HTML, and we add it back later.
-    let meRegExp = /^((<[^>]+>)*)\/me /;
-    let me = false;
-    if (meRegExp.test(msg)) {
-      me = true;
-      msg = msg.replace(meRegExp, "$1");
-    }
-
-    msg = cs.scanHTML(msg.replace(/&/g, "FROM-DTD-amp"), csFlags)
-      .replace(/FROM-DTD-amp/g, "&");
-
-    if (me)
-      msg = msg.replace(/^((<[^>]+>)*)/, "$1/me ");
-
-    aMsg.message = cleanupImMarkup(msg.replace(/\r?\n/g, "<br/>"),
-      null, this._textModifiers);
+      if (
+        !("_lastScrollHeight" in this) ||
+        this._lastScrollHeight != this.scrollHeight ||
+        this._lastScrollWidth != this.scrollWidth
+      ) {
+        // Ensure scroll events triggered by a change of the
+        // content area size (eg. resizing the window or moving the
+        // textbox splitter) don't affect the auto-scroll behavior.
+        this._lastScrollHeight = this.scrollHeight;
+        this._lastScrollWidth = this.scrollWidth;
+      }
 
-    let next = (aContext == this._lastMessageIsContext || aMsg.system) &&
-      isNextMessage(this.theme, aMsg, this._lastMessage);
-    let newElt;
-    if (next && aFirstUnread) {
-      // If there wasn't an unread ruler, this would be a Next message.
-      // Therefore, save that version for later.
-      let html = getHTMLForMessage(aMsg, this.theme, next, aContext);
-      let ruler = doc.getElementById("unread-ruler");
-      ruler.nextMsgHtml = html;
-      ruler._originalMsg = aMsg;
-
-      // Remember where the Next message(s) would have gone.
-      let insert = doc.getElementById("insert");
-      if (!insert) {
-        insert = doc.createElement("div");
-        ruler.parentNode.insertBefore(insert, ruler);
+      // If images higher than one line of text load they will trigger a
+      // scroll event, which shouldn't disable auto-scroll while messages
+      // are being appended without being scrolled.
+      if (this._messageDisplayPending) {
+        return;
       }
-      insert.id = "insert-before";
 
-      next = false;
-      html = getHTMLForMessage(aMsg, this.theme, next, aContext);
-      newElt = insertHTMLForMessage(aMsg, html, doc, next);
-      let marker = doc.createElement("div");
-      marker.id = "end-of-split-block";
-      newElt.parentNode.appendChild(marker);
-
-      // Bracket the place where additional Next messages will be added,
-      // if that's not after the end-of-split-block element.
-      insert = doc.getElementById("insert");
-      if (insert) {
-        marker = doc.createElement("div");
-        marker.id = "next-messages-start";
-        insert.parentNode.insertBefore(marker, insert);
-        marker = doc.createElement("div");
-        marker.id = "next-messages-end";
-        insert.parentNode.insertBefore(marker, insert.nextSibling);
-      }
-    } else {
-      let html = getHTMLForMessage(aMsg, this.theme, next, aContext);
-      newElt = insertHTMLForMessage(aMsg, html, doc, next);
+      // Enable or disable auto-scroll based on the scrollbar position.
+      this._updateConvScrollEnabled();
     }
 
-    if (!aNoAutoScroll) {
-      newElt.getBoundingClientRect(); // avoid ireflow bugs
-      if (this.convScrollEnabled())
-        this._scrollToElement(newElt);
-    }
-    this._lastElement = newElt;
-    this._lastMessage = aMsg;
-    if (!aContext && !this._firstNonContextElt && !aMsg.system)
-      this._firstNonContextElt = newElt;
-    this._lastMessageIsContext = aContext;
-  }
-
-  setUnreadRuler() {
-    // Remove any existing ruler (occurs when the window has lost focus).
-    this.removeUnreadRuler();
-
-    let ruler = this.contentDocument.createElement("hr");
-    ruler.id = "unread-ruler";
-    this.contentChatNode.appendChild(ruler);
-  }
-
-  removeUnreadRuler() {
-    let doc = this.contentDocument;
-    let ruler = doc.getElementById("unread-ruler");
-    if (!ruler)
-      return;
-
-    // If a message block was split by the ruler, rejoin it.
-    let moveTo = doc.getElementById("insert-before");
-    if (moveTo) {
-      // Protect an existing insert node.
-      let actualInsert = doc.getElementById("insert");
-      if (actualInsert)
-        actualInsert.id = "actual-insert";
-
-      // Add first message following the ruler as a Next type message.
-      // Replicates the relevant parts of insertHTMLForMessage().
-      let range = doc.createRange();
-      let moveToParent = moveTo.parentNode;
-      range.selectNode(moveToParent);
-      // eslint-disable-next-line no-unsanitized/method
-      let documentFragment = range.createContextualFragment(ruler.nextMsgHtml);
-      for (let root = documentFragment.firstChild; root; root = root.nextSibling)
-        root._originalMsg = ruler._originalMsg;
-      moveToParent.insertBefore(documentFragment, moveTo);
-
-      // If this added an insert node, insert the next messages there.
-      let insert = doc.getElementById("insert");
-      if (insert) {
-        moveTo.remove();
-        moveTo = insert;
-        moveToParent = moveTo.parentNode;
-      }
-
-      // Move remaining messages from the message block following the ruler.
-      let nextMessagesStart = doc.getElementById("next-messages-start");
-      if (nextMessagesStart) {
-        range = doc.createRange();
-        range.setStartAfter(nextMessagesStart);
-        range.setEndBefore(doc.getElementById("next-messages-end"));
-        moveToParent.insertBefore(range.extractContents(), moveTo);
-      }
-      moveTo.remove();
-
-      // Restore existing insert node.
-      if (actualInsert)
-        actualInsert.id = "insert";
-
-      // Delete surplus message block.
-      range = doc.createRange();
-      range.setStartAfter(ruler);
-      range.setEndAfter(doc.getElementById("end-of-split-block"));
-      range.deleteContents();
-    }
-    ruler.remove();
-  }
-
-  _getSections() {
-    // If a section is displayed below this point, we assume not enough of
-    // it is visible, so we must scroll to it.
-    // The 3/4 constant is arbitrary, but it has to be greater than 1/2.
-    this._maximalSectionOffset = Math.round(this.clientHeight * 3 / 4);
-
-    // Get list of current section elements.
-    let sectionElements = [];
-    if (this._firstNonContextElt)
-      sectionElements.push(this._firstNonContextElt);
-    let ruler = this.contentDocument.getElementById("unread-ruler");
-    if (ruler)
-      sectionElements.push(ruler);
-    sectionElements = sectionElements.concat(this._sessions);
-
-    // Return ordered array of sections with entries
-    // [Y, scrollY such that Y is centered]
-    let sections = [];
-    let maxY = this.contentWindow.scrollMaxY;
-    for (let i = 0; i < sectionElements.length; ++i) {
-      let y = sectionElements[i].offsetTop;