Bug 1478572 - Turn on ESLint in mail/components/im; r=florian
authorGeoff Lankow <geoff@darktrojan.net>
Tue, 02 Oct 2018 17:17:08 +1300
changeset 33283 826d121902902c7bed5b17bde052be850cbbb739
parent 33282 1bc7b3e7b9dbf36520251860b7bee9a3ee8653a9
child 33284 cc2807a09902b25cf0a964faf4e422397ecf05c7
push id387
push userclokep@gmail.com
push dateMon, 10 Dec 2018 21:30:47 +0000
reviewersflorian
bugs1478572
Bug 1478572 - Turn on ESLint in mail/components/im; r=florian
.eslintignore
chat/modules/imSmileys.jsm
mail/base/content/search.xml
mail/components/im/content/.eslintrc.js
mail/components/im/content/addbuddy.js
mail/components/im/content/am-im.js
mail/components/im/content/chat-messenger.js
mail/components/im/content/imAccountWizard.js
mail/components/im/content/imAccounts.js
mail/components/im/content/imContextMenu.js
mail/components/im/content/imStatusSelector.js
mail/components/im/content/imcontact.xml
mail/components/im/content/imconv.xml
mail/components/im/content/imconversation.xml
mail/components/im/content/imgroup.xml
mail/components/im/content/joinchat.js
mail/components/im/imIncomingServer.js
mail/components/im/imProtocolInfo.js
mail/components/im/jar.mn
mail/components/im/messages/bubbles/Footer.html
mail/components/im/messages/dark/Footer.html
mail/components/im/messages/mail/Footer.html
mail/components/im/messages/papersheets/Footer.html
mail/components/im/modules/chatHandler.jsm
mail/components/im/modules/chatNotifications.jsm
mail/components/im/modules/index_im.js
mail/components/im/modules/index_im.jsm
mail/components/im/modules/search_im.js
mail/components/im/modules/search_im.jsm
mail/components/im/moz.build
mail/components/im/smileys/theme.js
mail/components/im/smileys/theme.json
--- a/.eslintignore
+++ b/.eslintignore
@@ -47,25 +47,27 @@ mailnews/test/*
 mailnews/extensions/*
 !mailnews/extensions/newsblog
 
 # mail exclusions
 mail/app/**
 mail/base/**
 mail/branding/**
 mail/components/cloudfile/**
-mail/components/im/**
 mail/components/newmailaccount/**
 mail/components/search/**
 mail/config/**
 mail/extensions/**
 mail/installer/**
 mail/locales/**
 mail/test/**
 mail/themes/**
 
+# prefs files
+mail/components/im/all-im.js
+
 # calendar/ exclusions
 
 # prefs files
 calendar/lightning/content/lightning.js
 
 # third party library
 calendar/base/modules/ical.js
--- a/chat/modules/imSmileys.jsm
+++ b/chat/modules/imSmileys.jsm
@@ -16,17 +16,17 @@ this.EXPORTED_SYMBOLS = [
   "smileImMarkup", // used to add smile:// img tags into IM markup.
   "smileTextNode", // used to add smile:// img tags to the content of a textnode
   "smileString", // used to add smile:// img tags into a string without parsing it as HTML. Be sure the string doesn't contain HTML tags.
   "getSmileRealURI", // used to retrieve the chrome URI for a smile:// URI
   "getSmileyList" // used to display a list of smileys in the UI
 ];
 
 var kEmoticonsThemePref = "messenger.options.emoticonsTheme";
-var kThemeFile = "theme.js";
+var kThemeFile = "theme.json";
 
 Object.defineProperty(this, "gTheme", {
   configurable: true,
   enumerable: true,
 
   get() {
     delete this.gTheme;
     gPrefObserver.init();
--- a/mail/base/content/search.xml
+++ b/mail/base/content/search.xml
@@ -228,17 +228,17 @@
                 tabmail.closeTab();
               }
               this.value = ''; // clear our value, to avoid persistence
               let args = {
                 searcher: new GlodaMsgSearcher(null, searchString)
               };
               if (Services.prefs.getBoolPref("mail.chat.enabled")) {
                 if (!("GlodaIMSearcher" in window))
-                  ChromeUtils.import("resource:///modules/search_im.js");
+                  ChromeUtils.import("resource:///modules/search_im.jsm");
                 args.IMSearcher = new GlodaIMSearcher(null, searchString);
               }
               tabmail.openTab("glodaFacet", args);
             }
           } catch (e) {
             logException(e);
           }
         ]]>
new file mode 100644
--- /dev/null
+++ b/mail/components/im/content/.eslintrc.js
@@ -0,0 +1,27 @@
+"use strict";
+/* eslint-env node */
+
+module.exports = {
+  overrides: [{
+    files: [
+      "imcontact.xml",
+      "imconv.xml",
+      "imconversation.xml",
+    ],
+    globals: {
+      AppConstants: true,
+      chatHandler: true,
+      fixIterator: true,
+      gChatTab: true,
+      Services: true,
+
+      // chat/modules/imStatusUtils.jsm
+      Status: true,
+
+      // chat/modules/imTextboxUtils.jsm
+      MessageFormat: true,
+      TextboxSize: true,
+      TextboxSpellChecker: true,
+    },
+  }],
+};
--- a/mail/components/im/content/addbuddy.js
+++ b/mail/components/im/content/addbuddy.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/. */
 
-ChromeUtils.import("resource:///modules/iteratorUtils.jsm");
-ChromeUtils.import("resource:///modules/imServices.jsm");
+var { fixIterator } = ChromeUtils.import("resource:///modules/iteratorUtils.jsm", null);
+var { Services } = ChromeUtils.import("resource:///modules/imServices.jsm", null);
 
 var addBuddy = {
   onload: function ab_onload() {
     let accountList = document.getElementById("accountlist");
     for (let acc of fixIterator(Services.accounts.getAccounts())) {
       if (!acc.connected)
         continue;
       let proto = acc.protocol;
@@ -29,10 +29,10 @@ var addBuddy = {
   },
 
   getValue: function ab_getValue(aId) { return document.getElementById(aId).value; },
 
   create: function ab_create() {
     let account = Services.accounts.getAccountById(this.getValue("accountlist"));
     let group = document.getElementById("chatBundle").getString("defaultGroup");
     account.addBuddy(Services.tags.createTag(group), this.getValue("name"));
-  }
+  },
 };
--- a/mail/components/im/content/am-im.js
+++ b/mail/components/im/content/am-im.js
@@ -1,18 +1,20 @@
 /* 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/. */
 
+// chat/content/imAccountOptionsHelper.js
+/* globals accountOptionsHelper */
+
 ChromeUtils.import("resource://gre/modules/Services.jsm");
 
 var autoJoinPref = "autoJoin";
 
-function onPreInit(aAccount, aAccountValue)
-{
+function onPreInit(aAccount, aAccountValue) {
   account.init(aAccount.incomingServer.wrappedJSObject.imAccount);
 }
 
 var account = {
   init: function account_init(aAccount) {
     this.account = aAccount;
     this.proto = this.account.protocol;
     document.getElementById("accountName").value = this.account.name;
@@ -20,18 +22,17 @@ var account = {
     document.getElementById("protocolIcon").src =
       this.proto.iconBaseURI + "icon48.png";
 
     let password = document.getElementById("server.password");
     let passwordBox = document.getElementById("passwordBox");
     if (this.proto.noPassword) {
       passwordBox.hidden = true;
       password.removeAttribute("wsm_persist");
-    }
-    else {
+    } else {
       passwordBox.hidden = false;
       try {
         // Should we force layout here to ensure password.value works?
         // Will throw if we don't have a protocol plugin for the account.
         password.value = this.account.password;
         password.setAttribute("wsm_persist", "true");
       } catch (e) {
         passwordBox.hidden = true;
@@ -63,35 +64,35 @@ var account = {
       yield options.getNext();
   },
 
   populateProtoSpecificBox: function account_populate() {
     let attributes = {};
     attributes[Ci.prplIPref.typeBool] = [
       {name: "wsm_persist", value: "true"},
       {name: "preftype", value: "bool"},
-      {name: "genericattr", value: "true"}
+      {name: "genericattr", value: "true"},
     ];
     attributes[Ci.prplIPref.typeInt] = [
       {name: "wsm_persist", value: "true"},
       {name: "preftype", value: "int"},
-      {name: "genericattr", value: "true"}
+      {name: "genericattr", value: "true"},
     ];
     attributes[Ci.prplIPref.typeString] = attributes[Ci.prplIPref.typeList] = [
       {name: "wsm_persist", value: "true"},
       {name: "preftype", value: "wstring"},
-      {name: "genericattr", value: "true"}
+      {name: "genericattr", value: "true"},
     ];
     let haveOptions =
       accountOptionsHelper.addOptions("server.", this.getProtoOptions(),
                                       attributes);
     let advanced = document.getElementById("advanced");
     if (advanced.hidden && haveOptions) {
       advanced.hidden = false;
       // Force textbox XBL binding attachment by forcing layout,
       // otherwise setFormElementValue from AccountManager.js sets
       // properties that don't exist when restoring values.
       document.getElementById("protoSpecific").getBoundingClientRect();
-    }
-    else if (!haveOptions)
+    } else if (!haveOptions) {
       advanced.hidden = true;
-  }
+    }
+  },
 };
--- a/mail/components/im/content/chat-messenger.js
+++ b/mail/components/im/content/chat-messenger.js
@@ -1,21 +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/. */
 
-var imServices = {};
+// This file is loaded in messenger.xul.
+/* globals fixIterator, MailToolboxCustomizeDone, Notifications, openIMAccountMgr,
+   PROTO_TREE_VIEW, Services, Status, statusSelector, ZoomManager */
+
 ChromeUtils.import("resource:///modules/chatNotifications.jsm");
-ChromeUtils.import("resource:///modules/imServices.jsm", imServices);
+var { Services: imServices } = ChromeUtils.import("resource:///modules/imServices.jsm", null);
 ChromeUtils.import("resource://gre/modules/AppConstants.jsm");
 
 ChromeUtils.defineModuleGetter(this, "OS", "resource://gre/modules/osfile.jsm");
 
-imServices = imServices.Services;
-
 var gBuddyListContextMenu = null;
 
 function buddyListContextMenu(aXulMenu) {
   this.target = aXulMenu.triggerNode;
   this.menu = aXulMenu;
   let localName = this.target.localName;
   this.onContact = localName == "imcontact";
   this.onConv = localName == "imconv";
@@ -66,49 +67,49 @@ buddyListContextMenu.prototype = {
     let flags = prompts.BUTTON_TITLE_IS_STRING * prompts.BUTTON_POS_0 +
                 prompts.BUTTON_TITLE_CANCEL * prompts.BUTTON_POS_1 +
                 prompts.BUTTON_POS_1_DEFAULT;
     if (prompts.confirmEx(window, promptTitle, promptMessage, flags,
                           deleteButton, null, null, null, {}))
       return;
 
     this.target.deleteContact();
-  }
+  },
 };
 
 var gChatTab = null;
 
 var chatTabType = {
   name: "chat",
   panelId: "chatTabPanel",
   hasBeenOpened: false,
   modes: {
     chat: {
-      type: "chat"
-    }
+      type: "chat",
+    },
   },
 
   tabMonitor: {
     monitorName: "chattab",
 
     // Unused, but needed functions
-    onTabTitleChanged: function() {},
-    onTabOpened: function(aTab) {},
-    onTabClosing: function() {},
-    onTabPersist: function() {},
-    onTabRestored: function() {},
+    onTabTitleChanged() {},
+    onTabOpened(aTab) {},
+    onTabClosing() {},
+    onTabPersist() {},
+    onTabRestored() {},
 
     onTabSwitched(aNewTab, aOldTab) {
       // aNewTab == chat is handled earlier by showTab() below.
       if (aOldTab.mode.name == "chat")
         chatHandler._onTabDeactivated();
-    }
+    },
   },
 
-  _handleArgs: function(aArgs) {
+  _handleArgs(aArgs) {
     if (!aArgs || !("convType" in aArgs) ||
         (aArgs.convType != "log" && aArgs.convType != "focus"))
       return;
 
     if (aArgs.convType == "focus") {
       chatHandler.focusConversation(aArgs.conv);
       return;
     }
@@ -130,17 +131,17 @@ var chatTabType = {
     if (tabmail.currentTabInfo.mode.name == "chat")
       chatHandler._onTabActivated();
   },
   _onWindowDeactivated() {
     let tabmail = document.getElementById("tabmail");
     if (tabmail.currentTabInfo.mode.name == "chat")
       chatHandler._onTabDeactivated();
   },
-  openTab: function(aTab, aArgs) {
+  openTab(aTab, aArgs) {
     if (!this.hasBeenOpened) {
       if (chatHandler.ChatCore && chatHandler.ChatCore.initialized) {
         let convs = imServices.conversations.getUIConversations();
         if (convs.length != 0) {
           convs.sort((a, b) =>
                      a.title.toLowerCase().localeCompare(b.title.toLowerCase()));
           for (let conv of convs)
             chatHandler._addConversation(conv);
@@ -156,41 +157,41 @@ var chatTabType = {
 
     gChatTab = aTab;
     aTab.tabNode.setAttribute("type", "chat");
     this._handleArgs(aArgs);
     this.showTab(aTab);
     chatHandler.updateTitle();
     this.hasBeenOpened = true;
   },
-  shouldSwitchTo: function(aArgs) {
+  shouldSwitchTo(aArgs) {
     if (!gChatTab)
       return -1;
     this._handleArgs(aArgs);
     return document.getElementById("tabmail").tabInfo.indexOf(gChatTab);
   },
-  showTab: function(aTab) {
+  showTab(aTab) {
     gChatTab = aTab;
     chatHandler._onTabActivated();
     // The next call may change the selected conversation, but that
     // will be handled by the selected mutation observer of the imconv.
     chatHandler._updateSelectedConversation();
     chatHandler._updateFocus();
   },
-  closeTab: function(aTab) {
+  closeTab(aTab) {
     gChatTab = null;
     let tabmail = document.getElementById("tabmail");
     tabmail.unregisterTabMonitor(this.tabMonitor);
     window.removeEventListener("deactivate", chatTabType._onWindowDeactivated);
     window.removeEventListener("activate", chatTabType._onWindowActivated);
   },
-  persistTab: function(aTab) {
+  persistTab(aTab) {
     return {};
   },
-  restoreTab: function(aTabmail, aPersistedState) {
+  restoreTab(aTabmail, aPersistedState) {
     aTabmail.openTab("chat", {});
   },
 
   supportsCommand: function ct_supportsCommand(aCommand, aTab) {
     switch (aCommand) {
       case "cmd_fullZoomReduce":
       case "cmd_fullZoomEnlarge":
       case "cmd_fullZoomReset":
@@ -238,47 +239,45 @@ var chatTabType = {
       case "cmd_findAgain":
         this.getFindbar().onFindAgainCommand(false);
         break;
       case "cmd_findPrevious":
         this.getFindbar().onFindAgainCommand(true);
         break;
     }
   },
-  onEvent: function(aEvent, aTab) { },
+  onEvent(aEvent, aTab) {},
   getBrowser: function ct_getBrowser(aTab) {
     let panel = document.getElementById("conversationsDeck").selectedPanel;
     if (panel == document.getElementById("logDisplay")) {
       if (document.getElementById("logDisplayDeck").selectedPanel ==
           document.getElementById("logDisplayBrowserBox"))
         return document.getElementById("conv-log-browser");
-    }
-    else if (panel && panel.localName == "imconversation") {
+    } else if (panel && panel.localName == "imconversation") {
       return panel.browser;
     }
     return null;
   },
   getFindbar: function ct_getFindbar(aTab) {
     let panel = document.getElementById("conversationsDeck").selectedPanel;
     if (panel == document.getElementById("logDisplay")) {
       if (document.getElementById("logDisplayDeck").selectedPanel ==
           document.getElementById("logDisplayBrowserBox"))
         return document.getElementById("log-findbar");
-    }
-    else if (panel && panel.localName == "imconversation") {
+    } else if (panel && panel.localName == "imconversation") {
       return panel.findbar;
     }
     return null;
   },
 
-  saveTabState: function(aTab) { }
+  saveTabState(aTab) {},
 };
 
 var chatHandler = {
-  _addConversation: function(aConv) {
+  _addConversation(aConv) {
     let list = document.getElementById("contactlistbox");
     let convs = document.getElementById("conversationsGroup");
     let selectedItem = list.selectedItem;
     let shouldSelect =
       gChatTab && gChatTab.tabNode.selected &&
       (!selectedItem || (selectedItem == convs &&
                         convs.nextSibling.localName != "imconv"));
     let elt = convs.addContact(aConv, "imconv");
@@ -291,35 +290,35 @@ var chatHandler = {
     let contact = aConv.buddy.buddy.contact;
     elt.imContact = contact;
     let groupName = (contact.online ? "on" : "off") + "linecontactsGroup";
     let item = document.getElementById(groupName).removeContact(contact);
     if (list.selectedItem == item)
       list.selectedItem = elt;
   },
 
-  _hasConversationForContact: function(aContact) {
+  _hasConversationForContact(aContact) {
     let convs = document.getElementById("conversationsGroup").contacts;
     return convs.some(aConversation =>
       aConversation.hasOwnProperty("imContact") &&
       aConversation.imContact.id == aContact.id);
   },
 
   _chatButtonUpdatePending: false,
-  updateChatButtonState: function() {
+  updateChatButtonState() {
     if (this._chatButtonUpdatePending)
       return;
     this._chatButtonUpdatePending = true;
     Services.tm.mainThread.dispatch(this._updateChatButtonState.bind(this),
                                     Ci.nsIEventTarget.DISPATCH_NORMAL);
   },
   // This is the unread count that was part of the latest
   // unread-im-count-changed notification.
   _notifiedUnreadCount: 0,
-  _updateChatButtonState: function() {
+  _updateChatButtonState() {
     delete this._chatButtonUpdatePending;
     let chatButton = document.getElementById("button-chat");
     if (!chatButton)
       return;
 
     let [unreadTargettedCount, unreadTotalCount] = this.countUnreadMessages();
     chatButton.badgeCount = unreadTargettedCount;
 
@@ -332,28 +331,28 @@ var chatHandler = {
       let unreadInt = Cc["@mozilla.org/supports-PRInt32;1"]
                         .createInstance(Ci.nsISupportsPRInt32);
       unreadInt.data = unreadTargettedCount;
       Services.obs.notifyObservers(unreadInt, "unread-im-count-changed", unreadTargettedCount);
       this._notifiedUnreadCount = unreadTargettedCount;
     }
   },
 
-  countUnreadMessages: function() {
+  countUnreadMessages() {
     let convs = imServices.conversations.getUIConversations();
     let unreadTargettedCount = 0;
     let unreadTotalCount = 0;
     for (let conv of convs) {
       unreadTargettedCount += conv.unreadTargetedMessageCount;
       unreadTotalCount += conv.unreadIncomingMessageCount;
     }
     return [unreadTargettedCount, unreadTotalCount];
   },
 
-  updateTitle: function() {
+  updateTitle() {
     if (!gChatTab)
       return;
 
     let title =
       document.getElementById("chatBundle").getString("chatTabTitle");
     let [unreadTargettedCount] = this.countUnreadMessages();
     if (unreadTargettedCount) {
       title += " (" + unreadTargettedCount + ")";
@@ -362,47 +361,47 @@ var chatHandler = {
       if (selectedItem && selectedItem.localName == "imconv" &&
           !selectedItem.hidden)
         title += " - " + selectedItem.getAttribute("displayname");
     }
     gChatTab.title = title;
     document.getElementById("tabmail").setTabTitle(gChatTab);
   },
 
-  onConvResize: function() {
+  onConvResize() {
     let convDeck = document.getElementById("conversationsDeck");
     let panel = convDeck.selectedPanel;
     if (panel && panel.localName == "imconversation")
       panel.onConvResize();
   },
 
-  setStatusMenupopupCommand: function(aEvent) {
+  setStatusMenupopupCommand(aEvent) {
     let target = aEvent.originalTarget;
     if (target.getAttribute("id") == "imStatusShowAccounts" ||
         target.getAttribute("id") == "appmenu_imStatusShowAccounts") {
       openIMAccountMgr();
       return;
     }
 
     let status = target.getAttribute("status");
     if (!status)
       return; // Can status really be null? Maybe because of an add-on...
 
     let us = imServices.core.globalUserStatus;
     us.setStatus(Status.toFlag(status), us.statusText);
   },
 
   _pendingLogBrowserLoad: false,
-  _showLogPanel: function() {
+  _showLogPanel() {
     document.getElementById("conversationsDeck").selectedPanel =
       document.getElementById("logDisplay");
     document.getElementById("logDisplayDeck").selectedPanel =
       document.getElementById("logDisplayBrowserBox");
   },
-  _showLog: function(aConversation, aSearchTerm) {
+  _showLog(aConversation, aSearchTerm) {
     if (!aConversation)
       return;
     this._showLogPanel();
     let browser = document.getElementById("conv-log-browser");
     browser._autoScrollEnabled = false;
     if (this._pendingLogBrowserLoad) {
       browser._conv = aConversation;
       return;
@@ -454,17 +453,17 @@ var chatHandler = {
    * Display a list of logs into a tree, and optionally handle a default selection.
    *
    * @param aLogs An nsISimpleEnumerator of imILog.
    * @param aShouldSelect Either a boolean (true means select the first log
    * of the list, false or undefined means don't mess with the selection) or a log
    * item that needs to be selected.
    * @returns true if there's at least one log in the list, false if empty.
    */
-  _showLogList: function(aLogs, aShouldSelect) {
+  _showLogList(aLogs, aShouldSelect) {
     let logTree = document.getElementById("logTree");
     let treeView = this._treeView = new chatLogTreeView(logTree, aLogs);
     if (!treeView._rowMap.length)
       return false;
     if (!aShouldSelect)
       return true;
     if (aShouldSelect === true) {
       // Select the first line.
@@ -494,20 +493,20 @@ var chatHandler = {
              treeView._rowMap[index].log.time != logTime)
         ++index;
       if (treeView._rowMap[index].log.time == logTime) {
         logTree.view.selection.select(index);
         logTree.treeBoxObject.ensureRowIsVisible(index);
       }
       return true;
     }
-    throw("Couldn't find the log to select among the set of logs passed.");
+    throw "Couldn't find the log to select among the set of logs passed.";
   },
 
-  onLogSelect: function() {
+  onLogSelect() {
     let selection = this._treeView.selection;
     let currentIndex = selection.currentIndex;
     // The current (focused) row may not be actually selected...
     if (!selection.isSelected(currentIndex))
       return;
 
     let log = this._treeView._rowMap[currentIndex].log;
     if (!log)
@@ -517,57 +516,57 @@ var chatHandler = {
     if (list.selectedItem.getAttribute("id") != "searchResultConv")
       document.getElementById("goToConversation").hidden = false;
     log.getConversation().then(aLogConv => {
       this._showLog(aLogConv);
     });
   },
 
   _contactObserver: {
-    observe: function(aSubject, aTopic, aData) {
+    observe(aSubject, aTopic, aData) {
       if (aTopic == "contact-status-changed" ||
           aTopic == "contact-display-name-changed" ||
           aTopic == "contact-icon-changed")
         chatHandler.showContactInfo(aSubject);
-    }
+    },
   },
   _observedContact: null,
   get observedContact() { return this._observedContact; },
   set observedContact(aContact) {
     if (aContact == this._observedContact)
       return aContact;
     if (this._observedContact) {
       this._observedContact.removeObserver(this._contactObserver);
       delete this._observedContact;
     }
     this._observedContact = aContact;
     if (aContact)
       aContact.addObserver(this._contactObserver);
     return aContact;
   },
-  showCurrentConversation: function() {
+  showCurrentConversation() {
     let item = document.getElementById("contactlistbox").selectedItem;
     if (!item)
       return;
     if (item.localName == "imconv") {
       document.getElementById("conversationsDeck").selectedPanel = item.convView;
       document.getElementById("logTree").view.selection.clearSelection();
       item.convView.focus();
-    }
-    else if (item.localName == "imcontact")
+    } else if (item.localName == "imcontact") {
       item.openConversation();
+    }
   },
-  focusConversation: function(aUIConv) {
+  focusConversation(aUIConv) {
     let conv =
       document.getElementById("conversationsGroup").contactsById[aUIConv.id];
     document.getElementById("contactlistbox").selectedItem = conv;
     if (conv.convView)
       conv.convView.focus();
   },
-  showContactInfo: function(aContact) {
+  showContactInfo(aContact) {
     let cti = document.getElementById("conv-top-info");
     cti.setAttribute("userIcon", aContact.buddyIconFilename);
     cti.setAttribute("displayName", aContact.displayName);
     let proto = aContact.preferredBuddy.protocol;
     cti.setAttribute("prplIcon", proto.iconBaseURI + "icon.png");
     let statusText = aContact.statusText;
     let statusType = aContact.statusType;
     if (statusText)
@@ -584,29 +583,29 @@ var chatHandler = {
     cti.removeAttribute("noTopic");
 
     let bundle = document.getElementById("chatBundle");
     let button = document.getElementById("goToConversation");
     button.label = bundle.getFormattedString("startAConversationWith.button",
                                              [aContact.displayName]);
     button.disabled = !aContact.canSendMessage;
   },
-  _hideContextPane: function(aHide) {
+  _hideContextPane(aHide) {
     document.getElementById("contextSplitter").hidden = aHide;
     document.getElementById("contextPane").hidden = aHide;
   },
-  onListItemClick: function(aEvent) {
+  onListItemClick(aEvent) {
     // We only care about single clicks of the left button.
     if (aEvent.button != 0 || aEvent.detail != 1)
       return;
     let item = document.getElementById("contactlistbox").selectedItem;
     if (item.localName == "imconv" && item.convView)
       item.convView.focus();
   },
-  onListItemSelected: function() {
+  onListItemSelected() {
     let contactlistbox = document.getElementById("contactlistbox");
     let item = contactlistbox.selectedItem;
     if (!item || item.hidden || item.localName == "imgroup") {
       this._hideContextPane(true);
       document.getElementById("conversationsDeck").selectedPanel =
         document.getElementById("noConvScreen");
       this.updateTitle();
       this.observedContact = null;
@@ -635,65 +634,64 @@ var chatHandler = {
       imServices.logs.getLogFromFile(path, true).then(aLog => {
         imServices.logs.getSimilarLogs(aLog, true).then(aSimilarLogs => {
           if (contactlistbox.selectedItem != item)
             return;
           this._pendingSearchTerm = item.searchTerm || undefined;
           this._showLogList(aSimilarLogs, aLog);
         });
       });
-    }
-    else if (item.localName == "imconv") {
+    } else if (item.localName == "imconv") {
       let convDeck = document.getElementById("conversationsDeck");
       if (!item.convView) {
         // Create new conversation binding.
         let conv = document.createElement("imconversation");
         convDeck.appendChild(conv);
         conv.conv = item.conv;
         conv.tab = item;
         conv.setAttribute("contentcontextmenu", "chatConversationContextMenu");
         conv.setAttribute("contenttooltip", "imTooltip");
         item.convView = conv;
         document.getElementById("contextSplitter").hidden = false;
         document.getElementById("contextPane").hidden = false;
+      } else {
+        item.convView.onConvResize();
       }
-      else
-        item.convView.onConvResize();
 
       convDeck.selectedPanel = item.convView;
       item.convView.updateConvStatus();
       item.update();
 
       imServices.logs.getLogsForConversation(item.conv, true).then(aLogs => {
         if (contactlistbox.selectedItem != item)
           return;
         this._showLogList(aLogs);
       });
 
       let contextPane = document.getElementById("contextPane");
       if (item.conv.isChat) {
         contextPane.setAttribute("chat", "true");
         item.convView.showParticipants();
+      } else {
+        contextPane.removeAttribute("chat");
       }
-      else
-        contextPane.removeAttribute("chat");
 
       let button = document.getElementById("goToConversation");
       let bundle = document.getElementById("chatBundle");
       button.label = bundle.getString("goBackToCurrentConversation.button");
       button.disabled = false;
       this.observedContact = null;
-    }
-    else if (item.localName == "imcontact") {
+    } else if (item.localName == "imcontact") {
       let contact = item.contact;
       if (this.observedContact && contact &&
-          this.observedContact.id == contact.id)
+          this.observedContact.id == contact.id) {
         return; // onselect has just been fired again because a status
                 // change caused the imcontact to move.
                 // Return early to avoid flickering and changing the selected log.
+      }
 
       this.showContactInfo(contact);
       this.observedContact = contact;
 
       document.getElementById("contextPane").removeAttribute("chat");
 
       imServices.logs.getLogsForContact(contact, true).then(aLogs => {
         if (contactlistbox.selectedItem != item)
@@ -704,31 +702,31 @@ var chatHandler = {
           document.getElementById("logDisplayDeck").selectedPanel =
             document.getElementById("noPreviousConvScreen");
         }
       });
     }
     this.updateTitle();
   },
 
-  onNickClick: function(aEvent) {
+  onNickClick(aEvent) {
     // Open a private conversation only for a middle or double click.
     if (aEvent.button != 1 && (aEvent.button != 0 || aEvent.detail != 2))
       return;
 
     let conv = document.getElementById("contactlistbox").selectedItem.conv;
     let nick = aEvent.originalTarget.chatBuddy.name;
     let name = conv.target.getNormalizedChatBuddyName(nick);
     try {
       let newconv = conv.account.createConversation(name);
       this.focusConversation(newconv);
     } catch (e) {}
   },
 
-  onNicklistKeyPress: function(aEvent) {
+  onNicklistKeyPress(aEvent) {
     if (aEvent.keyCode != aEvent.DOM_VK_RETURN)
       return;
 
     let listbox = aEvent.originalTarget;
     if (listbox.selectedCount == 0)
       return;
 
     let conv = document.getElementById("contactlistbox").selectedItem.conv;
@@ -740,31 +738,31 @@ var chatHandler = {
         newconv = conv.account.createConversation(name);
       } catch (e) {}
     }
     // Only focus last of the opened conversations.
     if (newconv)
       this.focusConversation(newconv);
   },
 
-  _openDialog: function(aType) {
+  _openDialog(aType) {
     let features = "chrome,modal,titlebar,centerscreen";
     window.openDialog("chrome://messenger/content/chat/" + aType + ".xul", "",
                       features);
   },
-  addBuddy: function() {
+  addBuddy() {
      this._openDialog("addbuddy");
   },
-  joinChat: function() {
+  joinChat() {
     this._openDialog("joinchat");
   },
 
   _colorCache: {},
   // Duplicated code from imconversation.xml :-(
-  _computeColor: function(aName) {
+  _computeColor(aName) {
     if (Object.prototype.hasOwnProperty.call(this._colorCache, aName))
       return this._colorCache[aName];
 
     // Compute the color based on the nick
     var nick = aName.match(/[a-zA-Z0-9]+/);
     nick = nick ? nick[0].toLowerCase() : nick = aName;
     // We compute a hue value (between 0 and 359) based on the
     // characters of the nick.
@@ -782,17 +780,17 @@ var chatHandler = {
       // now char contains a value between 1 and 36
       res += char * weight;
       weight *= kWeightReductionPerChar;
     }
     return (this._colorCache[aName] = Math.round(res) % 360);
   },
 
   _placeHolderButtonId: "",
-  _updateNoConvPlaceHolder: function() {
+  _updateNoConvPlaceHolder() {
     let connected = false;
     let hasAccount = false;
     let canJoinChat = false;
     for (let account of fixIterator(imServices.accounts.getAccounts())) {
       hasAccount = true;
       if (account.connected) {
         connected = true;
         if (account.canJoinChat) {
@@ -802,18 +800,17 @@ var chatHandler = {
       }
     }
     document.getElementById("noConvInnerBox").hidden = !connected;
     document.getElementById("noAccountInnerBox").hidden = hasAccount;
     document.getElementById("noConnectedAccountInnerBox").hidden =
       connected || !hasAccount;
     if (connected) {
       delete this._placeHolderButtonId;
-    }
-    else {
+    } else {
       this._placeHolderButtonId =
         hasAccount ? "openIMAccountManagerButton" : "openIMAccountWizardButton";
     }
     for (let id of ["statusTypeIcon", "statusMessage", "button-chat-accounts"]) {
       let elt = document.getElementById(id);
       if (elt)
         elt.disabled = !hasAccount;
     }
@@ -829,23 +826,22 @@ var chatHandler = {
       if (elt)
         elt.disabled = !canJoinChat;
     }
     let groupIds = ["conversations", "onlinecontacts", "offlinecontacts"];
     let contactlist = document.getElementById("contactlistbox");
     if (!hasAccount || !connected && groupIds.every(id =>
         document.getElementById(id + "Group").contacts.length)) {
       contactlist.disabled = true;
-    }
-    else {
+    } else {
       contactlist.disabled = false;
       this._updateSelectedConversation();
     }
   },
-  _updateSelectedConversation: function() {
+  _updateSelectedConversation() {
     let list = document.getElementById("contactlistbox");
     // We can't select anything if there's no account.
     if (list.disabled)
       return;
 
     // If the selection is already a conversation with unread messages, keep it.
     let selectedItem = list.selectedItem;
     if (selectedItem && selectedItem.localName == "imconv" &&
@@ -885,17 +881,17 @@ var chatHandler = {
     for (let id of groupIds) {
       let item = document.getElementById(id + "Group");
       if (item.collapsed)
         continue;
       list.selectedItem = item;
       return;
     }
   },
-  _updateFocus: function() {
+  _updateFocus() {
     let focusId = this._placeHolderButtonId || "contactlistbox";
     document.getElementById(focusId).focus();
   },
   _getActiveConvView() {
     let list = document.getElementById("contactlistbox");
     if (list.disabled)
       return null;
     let selectedItem = list.selectedItem;
@@ -911,17 +907,17 @@ var chatHandler = {
     if (convView)
       convView.switchingToPanel();
   },
   _onTabDeactivated() {
     let convView = chatHandler._getActiveConvView();
     if (convView)
       convView.switchingAwayFromPanel();
   },
-  observe: function(aSubject, aTopic, aData) {
+  observe(aSubject, aTopic, aData) {
     if (aTopic == "chat-core-initialized") {
       this.initAfterChatCore();
       return;
     }
 
     if (aTopic == "conversation-loaded") {
       let browser = document.getElementById("conv-log-browser");
       if (aSubject != browser)
@@ -1019,22 +1015,22 @@ var chatHandler = {
       let bundle = document.getElementById("chatBundle");
       let label = bundle.getFormattedString("buddy.authRequest.label",
                                             [aSubject.userName]);
       let value =
         "buddy-auth-request-" + aSubject.account.id + aSubject.userName;
       let acceptButton = {
         accessKey: bundle.getString("buddy.authRequest.allow.accesskey"),
         label: bundle.getString("buddy.authRequest.allow.label"),
-        callback: function() { aSubject.grant(); }
+        callback() { aSubject.grant(); },
       };
       let denyButton = {
         accessKey: bundle.getString("buddy.authRequest.deny.accesskey"),
         label: bundle.getString("buddy.authRequest.deny.label"),
-        callback: function() { aSubject.deny(); }
+        callback() { aSubject.deny(); },
       };
       let box = document.getElementById("chatTabPanel");
       box.appendNotification(label, value, null, box.PRIORITY_INFO_HIGH,
                             [acceptButton, denyButton]);
       if (!gChatTab) {
         let tabmail = document.getElementById("tabmail");
         tabmail.openTab("chat", {background: true});
       }
@@ -1044,56 +1040,55 @@ var chatHandler = {
       aSubject.QueryInterface(Ci.prplIBuddyRequest);
       let value =
         "buddy-auth-request-" + aSubject.account.id + aSubject.userName;
       let notification =
         document.getElementById("chatTabPanel")
                 .getNotificationWithValue(value);
       if (notification)
         notification.close();
-      return;
     }
   },
-  initAfterChatCore: function() {
+  initAfterChatCore() {
     let onGroup = document.getElementById("onlinecontactsGroup");
     let offGroup = document.getElementById("offlinecontactsGroup");
 
     for (let name in chatHandler.allContacts) {
       let contact = chatHandler.allContacts[name];
       let group = contact.online ? onGroup : offGroup;
       group.addContact(contact);
     }
 
     onGroup._updateGroupLabel();
     offGroup._updateGroupLabel();
 
     ["new-text", "new-ui-conversation", "ui-conversation-closed",
      "contact-signed-on", "contact-signed-off",
      "contact-added", "contact-removed", "contact-no-longer-dummy",
      "account-connected", "account-disconnected",
-     "account-added","account-removed"
+     "account-added", "account-removed",
     ].forEach(chatHandler._addObserver);
 
     chatHandler._updateNoConvPlaceHolder();
     statusSelector.init();
   },
   _observedTopics: [],
-  _addObserver: function(aTopic) {
+  _addObserver(aTopic) {
     imServices.obs.addObserver(chatHandler, aTopic);
     chatHandler._observedTopics.push(aTopic);
   },
-  _removeObservers: function() {
+  _removeObservers() {
     for (let topic of this._observedTopics)
       imServices.obs.removeObserver(this, topic);
   },
   // TODO move this function away from here and test it.
   _getNextUnreadConversation(aConversations, aCurrent, aReverse) {
     let convCount = aConversations.length;
     if (!convCount)
-      return;
+      return -1;
 
     let direction = aReverse ? -1 : 1;
     let next = (i) => {
       i += direction;
       if (i < 0)
         return i + convCount;
       if (i >= convCount)
         return i - convCount;
@@ -1125,17 +1120,17 @@ var chatHandler = {
     let rawConversations = conversations.map((c) => c.conv);
     let current;
     if (aList.selectedItem.localName === "imconv")
       current = aList.selectedIndex - aList.getIndexOfItem(conversations[0]);
     let newIndex = this._getNextUnreadConversation(rawConversations, current, aReverse);
     if (newIndex !== -1)
       aList.selectedItem = conversations[newIndex];
   },
-  init: function() {
+  init() {
     Notifications.init();
     if (!Services.prefs.getBoolPref("mail.chat.enabled")) {
       ["button-chat", "menu_goChat", "goChatSeparator",
        "imAccountsStatus", "joinChatMenuItem", "newIMAccountMenuItem",
        "newIMContactMenuItem", "appmenu_joinChatMenuItem", "appmenu_afterChatSeparator",
        "appmenu_goChat", "appmenu_imAccountsStatus", "appmenu_goChatSeparator",
        "appmenu_newIMAccountMenuItem", "appmenu_newIMContactMenuItem"].forEach(function(aId) {
          let elt = document.getElementById(aId);
@@ -1191,48 +1186,48 @@ var chatHandler = {
 
     ChromeUtils.import("resource:///modules/chatHandler.jsm", this);
     if (this.ChatCore.initialized)
       this.initAfterChatCore();
     else {
       this.ChatCore.init();
       this._addObserver("chat-core-initialized");
     }
-  }
+  },
 };
 
 function chatLogTreeGroupItem(aTitle, aLogItems) {
   this._title = aTitle;
   this._children = aLogItems;
   for (let child of this._children)
     child._parent = this;
   this._open = false;
 }
 chatLogTreeGroupItem.prototype = {
-  getText: function() { return this._title; },
+  getText() { return this._title; },
   get id() { return this._title; },
   get open() { return this._open; },
   get level() { return 0; },
   get _parent() { return null; },
   get children() { return this._children; },
-  getProperties: function() { return ""; }
+  getProperties() { return ""; },
 };
 
 function chatLogTreeLogItem(aLog, aText, aLevel) {
   this.log = aLog;
   this._text = aText;
   this._level = aLevel;
 }
 chatLogTreeLogItem.prototype = {
-  getText: function() { return this._text; },
+  getText() { return this._text; },
   get id() { return this.log.title; },
   get open() { return false; },
   get level() { return this._level; },
   get children() { return []; },
-  getProperties: function() { return ""; }
+  getProperties() { return ""; },
 };
 
 function chatLogTreeView(aTree, aLogs) {
   this._tree = aTree;
   this._logs = aLogs;
   this._tree.view = this;
   this._rebuild();
 }
@@ -1251,23 +1246,23 @@ chatLogTreeView.prototype = {
     this._rowMap = [];
 
     // Used to show the dates in the log list in the locale of the application.
     let chatBundle = document.getElementById("chatBundle");
     let dateFormatBundle = document.getElementById("bundle_dateformat");
     let placesBundle = document.getElementById("bundle_places");
     let formatDate = function(aDate) {
       const dateFormatter = new Services.intl.DateTimeFormat(undefined, {
-        dateStyle: "short"
+        dateStyle: "short",
       });
       return dateFormatter.format(aDate);
     };
     let formatDateTime = function(aDate) {
       const dateTimeFormatter = new Services.intl.DateTimeFormat(undefined, {
-        dateStyle: "short", timeStyle: "short"
+        dateStyle: "short", timeStyle: "short",
       });
       return dateTimeFormatter.format(aDate);
     };
     let formatMonthYear = function(aDate) {
       let month = formatMonth(aDate);
       return dateFormatBundle.getFormattedString("finduri-MonthYear",
                                                  [month, aDate.getFullYear()]);
     };
@@ -1282,17 +1277,17 @@ chatLogTreeView.prototype = {
 
     // The keys used in the 'firstgroups' object should match string ids.
     // The order is the reverse of that in which they will appear
     // in the logTree.
     let firstgroups = {
       previousWeek: [],
       currentWeek: [],
       yesterday: [],
-      today: []
+      today: [],
     };
 
     // today and yesterday are treated differently, because for JSON logs they
     // represent individual logs, and are not "groups".
     let today = null, yesterday = null;
 
     // Build a chatLogTreeLogItem for each log, and put it in the right group.
     let groups = {};
@@ -1305,51 +1300,48 @@ chatLogTreeView.prototype = {
       let title = (isJSON ? formatDate : formatDateTime)(logDate);
       let group;
       if (timeFromToday <= 0) {
         if (isJSON) {
           today = new chatLogTreeLogItem(log, chatBundle.getString("log.today"), 0);
           continue;
         }
         group = firstgroups.today;
-      }
-      else if (timeFromToday <= kDayInMsecs) {
+      } else if (timeFromToday <= kDayInMsecs) {
         if (isJSON) {
           yesterday = new chatLogTreeLogItem(log, chatBundle.getString("log.yesterday"), 0);
           continue;
         }
         group = firstgroups.yesterday;
-      }
-      // Note that the 7 days of the current week include today.
-      else if (timeFromToday <= kWeekInMsecs - kDayInMsecs) {
+      } else if (timeFromToday <= kWeekInMsecs - kDayInMsecs) {
+        // Note that the 7 days of the current week include today.
         group = firstgroups.currentWeek;
         if (isJSON)
           title = formatWeekday(logDate);
-      }
-      else if (timeFromToday <= kTwoWeeksInMsecs - kDayInMsecs)
+      } else if (timeFromToday <= kTwoWeeksInMsecs - kDayInMsecs) {
         group = firstgroups.previousWeek;
-      else {
+      } else {
         logDate.setHours(0);
         logDate.setMinutes(0);
         logDate.setSeconds(0);
         logDate.setDate(1);
         let groupID = logDate.toISOString();
         if (!(groupID in groups)) {
           let groupname;
           if (logDate.getFullYear() == nowDate.getFullYear()) {
             if (logDate.getMonth() == nowDate.getMonth())
               groupname = placesBundle.getString("finduri-AgeInMonths-is-0");
             else
               groupname = formatMonth(logDate);
+          } else {
+            groupname = formatMonthYear(logDate);
           }
-          else
-            groupname = formatMonthYear(logDate);
           groups[groupID] = {
             entries: [],
-            name: groupname
+            name: groupname,
           };
         }
         group = groups[groupID].entries;
       }
       group.push(new chatLogTreeLogItem(log, title, 1));
     }
 
     let groupIDs = Object.keys(groups).sort().reverse();
@@ -1357,30 +1349,30 @@ chatLogTreeView.prototype = {
     // Add firstgroups to groups and groupIDs.
     for (let groupID in firstgroups) {
       let group = firstgroups[groupID];
       if (!group.length)
         continue;
       groupIDs.unshift(groupID);
       groups[groupID] = {
         entries: firstgroups[groupID],
-        name: chatBundle.getString("log." + groupID)
+        name: chatBundle.getString("log." + groupID),
       };
     }
 
     // Build tree.
     if (today)
       this._rowMap.push(today);
     if (yesterday)
       this._rowMap.push(yesterday);
-    groupIDs.forEach(function (aGroupID) {
+    groupIDs.forEach(function(aGroupID) {
       let group = groups[aGroupID];
       group.entries.sort((l1, l2) => l2.log.time - l1.log.time);
       this._rowMap.push(new chatLogTreeGroupItem(group.name, group.entries));
     }, this);
 
     // Finally, notify the tree.
     if (this._tree)
       this._tree.rowCountChanged(0, this._rowMap.length);
-  }
+  },
 };
 
 window.addEventListener("load", chatHandler.init.bind(chatHandler));
--- a/mail/components/im/content/imAccountWizard.js
+++ b/mail/components/im/content/imAccountWizard.js
@@ -1,29 +1,36 @@
 /* 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/. */
 
-ChromeUtils.import("resource:///modules/imServices.jsm");
+// chat/content/imAccountOptionsHelper.js
+/* globals accountOptionsHelper */
+
+var { Services } = ChromeUtils.import("resource:///modules/imServices.jsm", null);
 ChromeUtils.import("resource:///modules/MailServices.jsm");
 
 var PREF_EXTENSIONS_GETMOREPROTOCOLSURL = "extensions.getMoreProtocolsURL";
 
 var accountWizard = {
   onload: function aw_onload() {
     // Ensure the im core is initialized before we get a list of protocols.
     Services.core.init();
 
     accountWizard.setGetMoreProtocols();
 
     var protoList = document.getElementById("protolist");
     var protos = [];
     for (let proto of this.getProtocols())
       protos.push(proto);
-    protos.sort((a, b) => a.name < b.name ? -1 : a.name > b.name ? 1 : 0);
+    protos.sort((a, b) => {
+      if (a.name < b.name)
+        return -1;
+      return a.name > b.name ? 1 : 0;
+    });
     protos.forEach(function(proto) {
       let image = document.createElement("image");
       image.setAttribute("src", proto.iconBaseURI + "icon.png");
 
       let label = document.createElement("label");
       label.setAttribute("value", proto.name);
 
       let item = document.createElement("richlistitem");
@@ -119,18 +126,17 @@ var accountWizard = {
 
     var bundle = document.getElementById("accountsBundle");
     var usernameInfo;
     var emptyText = this.proto.usernameEmptyText;
     if (emptyText) {
       usernameInfo =
         bundle.getFormattedString("accountUsernameInfoWithDescription",
                                   [emptyText, this.proto.name]);
-    }
-    else {
+    } else {
       usernameInfo =
         bundle.getFormattedString("accountUsernameInfo", [this.proto.name]);
     }
     document.getElementById("usernameInfo").textContent = usernameInfo;
 
     var vbox = document.getElementById("userNameBox");
     // remove anything that may be there for another protocol
     while (vbox.hasChildNodes())
@@ -250,29 +256,29 @@ var accountWizard = {
       let eltName = id + "-" + name;
       let val = this.getValue(eltName);
       // The value will be undefined if the proto specific groupbox has never been opened
       if (val === undefined)
         continue;
       switch (opt.type) {
       case opt.typeBool:
         if (val != opt.getBool())
-          this.prefs.push({opt: opt, name: name, value: !!val});
+          this.prefs.push({opt, name, value: !!val});
         break;
       case opt.typeInt:
         if (val != opt.getInt())
-          this.prefs.push({opt: opt, name: name, value: val});
+          this.prefs.push({opt, name, value: val});
         break;
       case opt.typeString:
         if (val != opt.getString())
-          this.prefs.push({opt: opt, name: name, value: val});
+          this.prefs.push({opt, name, value: val});
         break;
       case opt.typeList:
         if (val != opt.getListDefault())
-          this.prefs.push({opt: opt, name: name, value: val});
+          this.prefs.push({opt, name, value: val});
         break;
       default:
         throw "unknown preference type " + opt.type;
       }
     }
 
     for (let i = 0; i < this.prefs.length; ++i) {
       let opt = this.prefs[i];
@@ -286,17 +292,17 @@ var accountWizard = {
     if (!this.proto.noPassword && this.password)
       acc.password = this.password;
     if (this.alias)
       acc.alias = this.alias;
 
     for (let i = 0; i < this.prefs.length; ++i) {
       let option = this.prefs[i];
       let opt = option.opt;
-      switch(opt.type) {
+      switch (opt.type) {
       case opt.typeBool:
         acc.setBool(option.name, option.value);
         break;
       case opt.typeInt:
         acc.setInt(option.name, option.value);
         break;
       case opt.typeString:
       case opt.typeList:
@@ -350,17 +356,17 @@ var accountWizard = {
     if ("value" in elt)
       return elt.value;
     // If the groupbox has never been opened, the binding isn't attached
     // so the attributes don't exist. The calling code in showSummary
     // has a special handling of the undefined value for this case.
     return undefined;
   },
 
-  getIter: function*(aEnumerator) {
+  * getIter(aEnumerator) {
     while (aEnumerator.hasMoreElements())
       yield aEnumerator.getNext();
   },
   getProtocols: function aw_getProtocols() {
     return this.getIter(Services.core.getProtocols());
   },
   getProtoOptions: function aw_getProtoOptions() {
     return this.getIter(this.proto.getOptions());
@@ -379,44 +385,42 @@ var accountWizard = {
   },
 
   toggleGroupbox: function aw_toggleGroupbox(id) {
     var elt = document.getElementById(id);
     if (elt.hasAttribute("closed")) {
       elt.removeAttribute("closed");
       if (elt.flexWhenOpened)
         elt.flex = elt.flexWhenOpened;
-    }
-    else {
+    } else {
       elt.setAttribute("closed", "true");
       if (elt.flex) {
         elt.flexWhenOpened = elt.flex;
         elt.flex = 0;
       }
     }
   },
 
   /* Check for correctness and set URL for the "Get more protocols..."-link
    *  Stripped down code from preferences/themes.js
    */
-  setGetMoreProtocols: function (){
+  setGetMoreProtocols() {
     let prefURL = PREF_EXTENSIONS_GETMOREPROTOCOLSURL;
     var getMore = document.getElementById("getMoreProtocols");
     var showGetMore = false;
     const nsIPrefBranch = Ci.nsIPrefBranch;
 
     if (Services.prefs.getPrefType(prefURL) != nsIPrefBranch.PREF_INVALID) {
       try {
         var getMoreURL = Services.urlFormatter.formatURLPref(prefURL);
         getMore.setAttribute("getMoreURL", getMoreURL);
         showGetMore = getMoreURL != "about:blank";
-      }
-      catch (e) { }
+      } catch (e) {}
     }
     getMore.hidden = !showGetMore;
   },
 
-  openURL: function (aURL) {
+  openURL(aURL) {
     Cc["@mozilla.org/uriloader/external-protocol-service;1"]
       .getService(Ci.nsIExternalProtocolService)
       .loadURI(Services.io.newURI(aURL));
-  }
+  },
 };
--- a/mail/components/im/content/imAccounts.js
+++ b/mail/components/im/content/imAccounts.js
@@ -1,14 +1,19 @@
 /* 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/. */
 
-ChromeUtils.import("resource:///modules/imServices.jsm");
-ChromeUtils.import("resource:///modules/iteratorUtils.jsm");
+// mail/base/content/nsDragAndDrop.js
+/* globals FlavourSet, TransferData */
+// imStatusSelector.js
+/* globals statusSelector */
+
+var { Services } = ChromeUtils.import("resource:///modules/imServices.jsm", null);
+var { fixIterator } = ChromeUtils.import("resource:///modules/iteratorUtils.jsm", null);
 ChromeUtils.import("resource:///modules/MailServices.jsm");
 ChromeUtils.import("resource://gre/modules/DownloadUtils.jsm");
 
 // This is the list of notifications that the account manager window observes
 var events = [
   "prpl-quit",
   "account-list-updated",
   "account-added",
@@ -17,17 +22,17 @@ var events = [
   "account-connected",
   "account-connecting",
   "account-disconnected",
   "account-disconnecting",
   "account-connect-progress",
   "account-connect-error",
   "autologin-processed",
   "status-changed",
-  "network:offline-status-changed"
+  "network:offline-status-changed",
 ];
 
 var gAccountManager = {
   // Sets the delay after connect() or disconnect() during which
   // it is impossible to perform disconnect() and connect()
   _disabledDelay: 500,
   disableTimerID: 0,
   _connectedLabelInterval: 0,
@@ -105,50 +110,45 @@ var gAccountManager = {
     this.disableCommandItems();
   },
   observe: function am_observe(aObject, aTopic, aData) {
     if (aTopic == "prpl-quit") {
       // libpurple is being uninitialized. We don't need the account
       // manager window anymore, close it.
       this.close();
       return;
-    }
-    else if (aTopic == "autologin-processed") {
+    } else if (aTopic == "autologin-processed") {
       var notification = document.getElementById("accountsNotificationBox")
                                  .getNotificationWithValue("autoLoginStatus");
       if (notification)
         notification.close();
       return;
-    }
-    else if (aTopic == "network:offline-status-changed") {
+    } else if (aTopic == "network:offline-status-changed") {
       this.setOffline(aData == "offline");
       return;
-    }
-    else if (aTopic == "status-changed") {
+    } else if (aTopic == "status-changed") {
       this.setOffline(aObject.statusType == Ci.imIStatusInfo.STATUS_OFFLINE);
       return;
-    }
-    else if (aTopic == "account-list-updated") {
+    } else if (aTopic == "account-list-updated") {
       this._updateAccountList();
       return;
     }
 
     // The following notification handlers need an account.
     aObject.QueryInterface(Ci.imIAccount);
 
     if (aTopic == "account-added") {
       document.getElementById("accountsDesk").selectedIndex = 1;
-      var elt = document.createElement("richlistitem");
+      let elt = document.createElement("richlistitem");
       this.accountList.appendChild(elt);
       elt.build(aObject);
       if (this.accountList.getRowCount() == 1)
         this.accountList.selectedIndex = 0;
-    }
-    else if (aTopic == "account-removed") {
-      var elt = document.getElementById(aObject.id);
+    } else if (aTopic == "account-removed") {
+      let elt = document.getElementById(aObject.id);
       elt.destroy();
       if (!elt.selected) {
         elt.remove();
         return;
       }
       // The currently selected element is removed,
       // ensure another element gets selected (if the list is not empty)
       var selectedIndex = this.accountList.selectedIndex;
@@ -159,44 +159,40 @@ var gAccountManager = {
       var count = this.accountList.getRowCount();
       if (!count) {
         document.getElementById("accountsDesk").selectedIndex = 0;
         return;
       }
       if (selectedIndex == count)
         --selectedIndex;
       this.accountList.selectedIndex = selectedIndex;
-    }
-    else if (aTopic == "account-updated") {
+    } else if (aTopic == "account-updated") {
       document.getElementById(aObject.id).build(aObject);
       this.disableCommandItems();
-    }
-    else if (aTopic == "account-connect-progress")
+    } else if (aTopic == "account-connect-progress") {
       document.getElementById(aObject.id).updateConnectionState();
-    else if (aTopic == "account-connect-error")
+    } else if (aTopic == "account-connect-error") {
       document.getElementById(aObject.id).updateConnectionError();
-    else {
+    } else {
       const stateEvents = {
         "account-connected": "connected",
         "account-connecting": "connecting",
         "account-disconnected": "disconnected",
-        "account-disconnecting": "disconnecting"
+        "account-disconnecting": "disconnecting",
       };
       if (aTopic in stateEvents) {
         let elt = document.getElementById(aObject.id);
         if (!elt)
           return; // probably disconnecting a removed account.
 
         if (aTopic == "account-connecting") {
           elt.removeAttribute("error");
           elt.updateConnectionState();
-        }
-        else {
-          if (aTopic == "account-connected")
-            elt.refreshConnectedLabel();
+        } else if (aTopic == "account-connected") {
+          elt.refreshConnectedLabel();
         }
 
         elt.setAttribute("state", stateEvents[aTopic]);
       }
     }
   },
   cancelReconnection: function am_cancelReconnection() {
     this.accountList.selectedItem.cancelReconnection();
@@ -221,44 +217,44 @@ var gAccountManager = {
     if (!account.disconnected || !prplAccount.connectionTarget)
       return;
 
     // Open the Gecko SSL exception dialog.
     let params = {
       exceptionAdded: false,
       securityInfo: prplAccount.secInfo,
       prefetchCert: true,
-      location: prplAccount.connectionTarget
+      location: prplAccount.connectionTarget,
     };
     window.openDialog("chrome://pippki/content/exceptionDialog.xul", "",
                       "chrome,centerscreen,modal", params);
     // Reconnect the account if an exception was added.
     if (params.exceptionAdded)
       account.connect();
   },
   copyDebugLog: function am_copyDebugLog() {
     let account = this.accountList.selectedItem.account;
     let text = account.getDebugMessages().map(function(dbgMsg) {
       let m = dbgMsg.message;
       let time = new Date(m.timeStamp);
       const dateTimeFormatter = new Services.intl.DateTimeFormat(undefined, {
-        dateStyle: "short", timeStyle: "long"
+        dateStyle: "short", timeStyle: "long",
       });
       time = dateTimeFormatter.format(time);
       let level = dbgMsg.logLevel;
       if (!level)
         return "(" + m.errorMessage + ")";
       if (level == dbgMsg.LEVEL_ERROR)
         level = "ERROR";
       else if (level == dbgMsg.LEVEL_WARNING)
         level = "WARN.";
       else if (level == dbgMsg.LEVEL_LOG)
         level = "LOG  ";
       else
-        level = "DEBUG"
+        level = "DEBUG";
       return "[" + time + "] " + level + " (@ " + m.sourceLine +
              " " + m.sourceName + ":" + m.lineNumber + ")\n" +
              m.errorMessage;
     }).join("\n");
     Cc["@mozilla.org/widget/clipboardhelper;1"]
       .getService(Ci.nsIClipboardHelper).copyString(text);
   },
   updateConnectedLabels: function am_updateConnectedLabels() {
@@ -301,32 +297,31 @@ var gAccountManager = {
         break;
       }
     }
 
     let win = Services.wm.getMostRecentWindow("mailnews:accountmanager");
     if (win) {
       win.focus();
       win.selectServer(server);
-    }
-    else {
+    } else {
       window.openDialog("chrome://messenger/content/AccountManager.xul",
                         "AccountManager",
                         "chrome,centerscreen,modal,titlebar,resizable",
-                        { server: server, selectPage: null });
+                        { server, selectPage: null });
     }
   },
   autologin: function am_autologin() {
     var elt = this.accountList.selectedItem;
     elt.autoLogin = !elt.autoLogin;
   },
   close: function am_close() {
     // If a modal dialog is opened, we can't close this window now
     if (this.modalDialog)
-      setTimeout(function() { window.close();}, 0);
+      setTimeout(function() { window.close(); }, 0);
     else
       window.close();
   },
 
   /* This function disables or enables the currently selected button and
      the corresponding context menu item */
   disableCommandItems: function am_disableCommandItems() {
     let accountList = this.accountList;
@@ -367,17 +362,17 @@ var gAccountManager = {
     let isAccount = targetElt instanceof Ci.nsIDOMXULSelectControlItemElement;
     document.getElementById("contextAccountsItems").hidden = !isAccount;
     if (isAccount) {
       let account = targetElt.account;
       let hiddenItems = {
         connect: !account.disconnected,
         disconnect: account.disconnected || account.disconnecting,
         cancelReconnection: !targetElt.hasAttribute("reconnectPending"),
-        accountsItemsSeparator: account.disconnecting
+        accountsItemsSeparator: account.disconnecting,
       };
       for (let name in hiddenItems)
         document.getElementById("context_" + name).hidden = hiddenItems[name];
     }
   },
 
   selectAccount: function am_selectAccount(aAccountId) {
     this.accountList.selectedItem = document.getElementById(aAccountId);
@@ -428,17 +423,16 @@ var gAccountManager = {
     }
 
     if (event.keyCode == event.DOM_VK_RETURN) {
       let target = event.originalTarget;
       if (target.localName != "checkbox" &&
           (target.localName != "button" ||
            /^(dis)?connect$/.test(target.getAttribute("anonid"))))
         this.selectedItem.buttons.proceedDefaultAction();
-      return;
     }
   },
 
   moveCurrentItem: function am_moveCurrentItem(aOffset) {
     let accountList = this.accountList;
     if (!aOffset || !accountList.selectedItem)
       return;
 
@@ -487,17 +481,17 @@ var gAccountManager = {
     }
 
     var bundle = document.getElementById("accountsBundle");
     var box = document.getElementById("accountsNotificationBox");
     var priority = box.PRIORITY_INFO_HIGH;
     var connectNowButton = {
       accessKey: bundle.getString("accountsManager.notification.button.accessKey"),
       callback: this.processAutoLogin,
-      label: bundle.getString("accountsManager.notification.button.label")
+      label: bundle.getString("accountsManager.notification.button.label"),
     };
     var label;
 
     switch (autoLoginStatus) {
       case as.AUTOLOGIN_USER_DISABLED:
         label = bundle.getString("accountsManager.notification.userDisabled.label");
         break;
 
@@ -560,35 +554,29 @@ var gAccountManager = {
   },
   setOffline: function am_setOffline(aState) {
     this.isOffline = aState;
     if (aState)
       this.accountList.setAttribute("offline", "true");
     else
       this.accountList.removeAttribute("offline");
     this.disableCommandItems();
-  }
+  },
 };
 
 
 var gAMDragAndDrop = {
   ACCOUNT_MIME_TYPE: "application/x-moz-richlistitem",
   // Size of the scroll zone on the top and on the bottom of the account list
   MAGIC_SCROLL_HEIGHT: 20,
 
   // A preference already exists to define scroll speed, let's use it.
   get SCROLL_SPEED() {
     delete this.SCROLL_SPEED;
-    try {
-      this.SCROLL_SPEED =
-        Services.prefs.getIntPref("toolkit.scrollbox.scrollIncrement");
-    }
-    catch (e) {
-      this.SCROLL_SPEED = 20;
-    }
+    this.SCROLL_SPEED = Services.prefs.getIntPref("toolkit.scrollbox.scrollIncrement", 20);
     return this.SCROLL_SPEED;
   },
 
   onDragStart: function amdnd_onDragStart(aEvent, aTransferData, aAction) {
     let accountElement = aEvent.explicitOriginalTarget;
     // This stops the dragging session.
     if (!(accountElement instanceof Ci.nsIDOMXULSelectControlItemElement))
       throw "Element is not draggable!";
@@ -622,18 +610,17 @@ var gAMDragAndDrop = {
 
     if (aEvent.clientY < accountElement.getBoundingClientRect().top +
                          accountElement.clientHeight / 2) {
       // we don't want the previous item to show its default bottom-border
       let previousItem = accountElement.previousSibling;
       if (previousItem)
         previousItem.style.borderBottom = "none";
       accountElement.setAttribute("dragover", "up");
-    }
-    else {
+    } else {
       if (("_accountElement" in this) &&
           this._accountElement == accountElement &&
           accountElement.getAttribute("dragover") == "up")
         this.cleanBorders();
       accountElement.setAttribute("dragover", "down");
     }
 
     this._accountElement = accountElement;
@@ -698,12 +685,12 @@ var gAMDragAndDrop = {
     gAccountManager.moveCurrentItem(offset);
   },
 
   getSupportedFlavours: function amdnd_getSupportedFlavours() {
     var flavours = new FlavourSet();
     flavours.appendFlavour(this.ACCOUNT_MIME_TYPE,
                            "nsIDOMXULSelectControlItemElement");
     return flavours;
-  }
+  },
 };
 
 window.addEventListener("DOMContentLoaded", () => { gAccountManager.load(); });
--- a/mail/components/im/content/imContextMenu.js
+++ b/mail/components/im/content/imContextMenu.js
@@ -1,12 +1,15 @@
 /* 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/. */
 
+// This file is loaded in messenger.xul.
+/* globals gatherTextUnder, goUpdateGlobalEditMenuItems, makeURLAbsolute, Services */
+
 var gChatContextMenu = null;
 
 function imContextMenu(aXulMenu) {
   this.target            = null;
   this.menu              = null;
   this.onLink            = false;
   this.onMailtoLink      = false;
   this.onSaveableLink    = false;
@@ -18,25 +21,25 @@ function imContextMenu(aXulMenu) {
   this.isContentSelected = false;
   this.shouldDisplay     = true;
   this.ellipsis = "\u2026";
 
   try {
     this.ellipsis =
       Services.prefs.getComplexValue("intl.ellipsis",
                                      Ci.nsIPrefLocalizedString).data;
-  } catch (e) { }
+  } catch (e) {}
 
   // Initialize new menu.
   this.initMenu(aXulMenu);
 }
 
 // Prototype for nsContextMenu "class."
 imContextMenu.prototype = {
-  cleanup: function() {
+  cleanup() {
     let elt = document.getElementById("context-sep-messageactions").nextSibling;
     // remove the action menuitems added last time we opened the popup
     while (elt && elt.localName != "menuseparator") {
       let tmp = elt.nextSibling;
       elt.remove();
       elt = tmp;
     }
   },
@@ -92,31 +95,30 @@ imContextMenu.prototype = {
       menuitem.setAttribute("label", action.label);
       menuitem.setAttribute("oncommand", "this.action.run();");
       menuitem.action = action;
       sep.parentNode.appendChild(menuitem);
     }
   },
 
   // Set various context menu attributes based on the state of the world.
-  setTarget: function (aNode) {
+  setTarget(aNode) {
 
     // Initialize contextual info.
     this.onLink            = false;
     this.linkURL           = "";
     this.linkURI           = null;
     this.linkProtocol      = "";
 
     // Remember the node that was clicked.
     this.target = aNode;
 
     // First, do checks for nodes that never have children.
     // Second, bubble out, looking for items of interest that can have children.
     // Always pick the innermost link, background image, etc.
-    const XMLNS = "http://www.w3.org/XML/1998/namespace";
     var elem = this.target;
     while (elem) {
       if (elem.nodeType == Node.ELEMENT_NODE) {
         // Link?
         if (!this.onLink &&
              ((elem instanceof HTMLAnchorElement && elem.href) ||
               (elem instanceof HTMLAreaElement && elem.href) ||
               elem instanceof HTMLLinkElement ||
@@ -133,17 +135,17 @@ imContextMenu.prototype = {
           while ((parent = parent.parentNode) &&
                  (parent.nodeType == Node.ELEMENT_NODE)) {
             try {
               if ((parent instanceof HTMLAnchorElement && parent.href) ||
                   (parent instanceof HTMLAreaElement && parent.href) ||
                   parent instanceof HTMLLinkElement ||
                   parent.getAttributeNS("http://www.w3.org/1999/xlink", "type") == "simple")
                 realLink = parent;
-            } catch (e) { }
+            } catch (e) {}
           }
 
           // Remember corresponding element.
           this.link = realLink;
           this.linkURL = this.getLinkURL();
           this.linkURI = this.getLinkURI();
           this.linkProtocol = this.getLinkProtocol();
           this.onMailtoLink = (this.linkProtocol == "mailto");
@@ -151,158 +153,150 @@ imContextMenu.prototype = {
         }
       }
 
       elem = elem.parentNode;
     }
   },
 
   // Returns true if clicked-on link targets a resource that can be saved.
-  isLinkSaveable: function(aLink) {
-    return this.linkProtocol && !(
-             this.linkProtocol == "mailto"     ||
-             this.linkProtocol == "javascript" ||
-             this.linkProtocol == "news"       ||
-             this.linkProtocol == "snews"      );
+  isLinkSaveable(aLink) {
+    return this.linkProtocol &&
+      !["mailto", "javascript", "news", "snews"].includes(this.linkProtocol);
   },
 
   // Open linked-to URL in a new window.
-  openLink: function (aURI) {
+  openLink(aURI) {
     Cc["@mozilla.org/uriloader/external-protocol-service;1"].
     getService(Ci.nsIExternalProtocolService).
     loadURI(aURI || this.linkURI, window);
   },
 
   // Generate email address and put it on clipboard.
-  copyEmail: function() {
+  copyEmail() {
     // Copy the comma-separated list of email addresses only.
     // There are other ways of embedding email addresses in a mailto:
     // link, but such complex parsing is beyond us.
     var url = this.linkURL;
     var qmark = url.indexOf("?");
     var addresses;
 
     // 7 == length of "mailto:"
     addresses = qmark > 7 ? url.substring(7, qmark) : url.substr(7);
 
     // Let's try to unescape it using a character set
     // in case the address is not ASCII.
     try {
       var characterSet = this.target.ownerDocument.characterSet;
-      const textToSubURI = Cc["@mozilla.org/intl/texttosuburi;1"].
-                           getService(Ci.nsITextToSubURI);
-      addresses = textToSubURI.unEscapeURIForUI(characterSet, addresses);
-    }
-    catch(ex) {
+      addresses = Services.textToSubURI.unEscapeURIForUI(characterSet, addresses);
+    } catch (ex) {
       // Do nothing.
     }
 
     var clipboard = Cc["@mozilla.org/widget/clipboardhelper;1"].
                     getService(Ci.nsIClipboardHelper);
     clipboard.copyString(addresses);
   },
 
-  ///////////////
-  // Utilities //
-  ///////////////
+  // ---------
+  // Utilities
 
   // Show/hide one item (specified via name or the item element itself).
-  showItem: function(aItemOrId, aShow) {
+  showItem(aItemOrId, aShow) {
     var item = aItemOrId.constructor == String ?
       document.getElementById(aItemOrId) : aItemOrId;
     if (item)
       item.hidden = !aShow;
   },
 
   // Temporary workaround for DOM api not yet implemented by XUL nodes.
-  cloneNode: function(aItem) {
+  cloneNode(aItem) {
     // Create another element like the one we're cloning.
     var node = document.createElement(aItem.tagName);
 
     // Copy attributes from argument item to the new one.
     var attrs = aItem.attributes;
     for (var i = 0; i < attrs.length; i++) {
       var attr = attrs.item(i);
       node.setAttribute(attr.nodeName, attr.nodeValue);
     }
 
     // Voila!
     return node;
   },
 
   // Generate fully qualified URL for clicked-on link.
-  getLinkURL: function() {
+  getLinkURL() {
     var href = this.link.href;
     if (href)
       return href;
 
     href = this.link.getAttributeNS("http://www.w3.org/1999/xlink",
                                     "href");
 
     if (!href || (href.trim() == "")) {
       // Without this we try to save as the current doc,
       // for example, HTML case also throws if empty
       throw "Empty href";
     }
 
     return makeURLAbsolute(this.link.baseURI, href);
   },
 
-  getLinkURI: function() {
+  getLinkURI() {
     try {
       return Services.io.newURI(this.linkURL);
-    }
-    catch (ex) {
-     // e.g. empty URL string
+    } catch (ex) {
+      // e.g. empty URL string
     }
 
     return null;
   },
 
-  getLinkProtocol: function() {
+  getLinkProtocol() {
     if (this.linkURI)
       return this.linkURI.scheme; // can be |undefined|
 
     return null;
   },
 
   // Get text of link.
-  linkText: function() {
+  linkText() {
     var text = gatherTextUnder(this.link);
     if (text == "") {
       text = this.link.getAttribute("title");
       if (!text || (text.trim() == "")) {
         text = this.link.getAttribute("alt");
         if (!text || (text.trim() == ""))
           text = this.linkURL;
       }
     }
 
     return text;
   },
 
   // Get selected text. Only display the first 15 chars.
-  isTextSelection: function() {
+  isTextSelection() {
     // Get 16 characters, so that we can trim the selection if it's greater
     // than 15 chars
     var selectedText = getBrowserSelection(16);
 
     if (!selectedText)
       return false;
 
     if (selectedText.length > 15)
-      selectedText = selectedText.substr(0,15) + this.ellipsis;
+      selectedText = selectedText.substr(0, 15) + this.ellipsis;
 
     return true;
   },
 
   // Returns true if anything is selected.
-  isContentSelection: function() {
+  isContentSelection() {
     return !document.commandDispatcher.focusedWindow.getSelection().isCollapsed;
-  }
+  },
 };
 
 /**
  * Gets the selected text in the active browser. Leading and trailing
  * whitespace is removed, and consecutive whitespace is replaced by a single
  * space. A maximum of 150 characters will be returned, regardless of the value
  * of aCharLen.
  *
--- a/mail/components/im/content/imStatusSelector.js
+++ b/mail/components/im/content/imStatusSelector.js
@@ -1,13 +1,17 @@
 /* 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/. */
 
-ChromeUtils.import("resource:///modules/imStatusUtils.jsm");
+// chat/modules/imTextboxUtils.jsm
+/* globals TextboxSpellChecker */
+
+var { Status } = ChromeUtils.import("resource:///modules/imStatusUtils.jsm", null);
+var { Services } = ChromeUtils.import("resource:///modules/imServices.jsm", null);
 
 var statusSelector = {
   observe: function ss_observe(aSubject, aTopic, aMsg) {
     if (aTopic == "status-changed")
       this.displayCurrentStatus();
     else if (aTopic == "user-icon-changed")
       this.displayUserIcon();
     else if (aTopic == "user-display-name-changed")
@@ -159,18 +163,17 @@ var statusSelector = {
           newStatus = Ci.imIStatusInfo.STATUS_OFFLINE;
         delete this._statusTypeBeforeEditing;
         delete this._statusTypeEditing;
       }
       // apply the new status only if it is different from the current one
       if (newStatus != Ci.imIStatusInfo.STATUS_UNKNOWN ||
           elt.value != elt.getAttribute("value"))
         Services.core.globalUserStatus.setStatus(newStatus, elt.value);
-    }
-    else if ("_statusTypeBeforeEditing" in this) {
+    } else if ("_statusTypeBeforeEditing" in this) {
       this.displayStatusType(this._statusTypeBeforeEditing);
       delete this._statusTypeBeforeEditing;
       delete this._statusTypeEditing;
     }
 
     if (elt.hasAttribute("usingDefault"))
       elt.setAttribute("value", elt.getAttribute("usingDefault"));
     TextboxSpellChecker.unregisterTextbox(elt);
@@ -282,10 +285,10 @@ var statusSelector = {
     statusSelector._events = events;
 
     window.addEventListener("unload", statusSelector.unload);
   },
 
   unload: function ss_unload() {
     for (let event of statusSelector._events)
       Services.obs.removeObserver(statusSelector, event);
-   }
+   },
 };
--- a/mail/components/im/content/imcontact.xml
+++ b/mail/components/im/content/imcontact.xml
@@ -215,33 +215,33 @@
     </implementation>
     <handlers>
      <handler event="blur">
        <![CDATA[
          if (!this.hasAttribute("aliasing"))
            return;
 
          if (Services.focus.activeWindow == document.defaultView)
-           finishAliasing(true);
+           this.finishAliasing(true);
        ]]>
      </handler>
 
      <handler event="mousedown">
        <![CDATA[
-         if (!this.hasAttribute("aliasing") && canOpenConversation() &&
+         if (!this.hasAttribute("aliasing") && this.canOpenConversation() &&
              event.originalTarget.getAttribute("anonid") == "startChatBubble") {
-           openConversation();
+           this.openConversation();
            event.preventDefault();
          }
        ]]>
      </handler>
 
      <handler event="click">
        <![CDATA[
-         if (!this.hasAttribute("aliasing") && canOpenConversation() &&
+         if (!this.hasAttribute("aliasing") && this.canOpenConversation() &&
              event.detail == 2 &&
              event.originalTarget.getAttribute("anonid") != "expander")
-           openConversation();
+           this.openConversation();
        ]]>
      </handler>
     </handlers>
   </binding>
 </bindings>
--- a/mail/components/im/content/imconv.xml
+++ b/mail/components/im/content/imconv.xml
@@ -127,18 +127,17 @@
             this.directedUnreadCount = 0;
             chatHandler.updateTitle();
             chatHandler.updateChatButtonState();
           }
           this.setAttribute("unreadCount", "0");
           this.setAttribute("unreadTargetedCount", "0");
           this.removeAttribute("unread");
           this.removeAttribute("attention");
-        }
-        else {
+        } else {
           let unreadCount = this.conv.unreadIncomingMessageCount;
           let directedMessages = unreadCount;
           if (unreadCount) {
             this.setAttribute("unread", "true");
             if (this.conv.isChat) {
               directedMessages = this.conv.unreadTargetedMessageCount;
               if (directedMessages)
                 this.setAttribute("attention", "true");
@@ -159,18 +158,17 @@
 
         if (this.conv.isChat) {
           if (this.conv.joining)
             this.setAttribute("status", "joining");
           else if (!this.conv.account.connected || this.conv.left)
             this.setAttribute("status", "left");
           else
             this.removeAttribute("status");
-        }
-        else {
+        } else {
           let statusType = Ci.imIStatusInfo.STATUS_UNKNOWN;
           let buddy = this.conv.buddy;
           if (buddy && buddy.account.connected)
             statusType = buddy.statusType;
           if (!("Status" in window))
             ChromeUtils.import("resource:///modules/imStatusUtils.jsm");
           this.setAttribute("status", Status.toAttribute(statusType));
         }
@@ -201,18 +199,17 @@
         let newSelectedItem;
         let list = this.parentNode;
         if (list.selectedItem == this)
           newSelectedItem = this.previousSibling;
 
         if (this.log) {
           this.hidden = true;
           delete this.log;
-        }
-        else {
+        } else {
           this.remove();
           delete this.conv;
         }
         if (newSelectedItem)
           list.selectedItem = newSelectedItem;
       ]]>
       </body>
      </method>
@@ -238,34 +235,34 @@
           return;
         }
 
         let isMac = "nsILocalFileMac" in Ci;
         let accelKeyPressed = isMac ? aEvent.metaKey : aEvent.ctrlKey;
         // If a character was typed or the accel+v copy shortcut was used,
         // focus the input box and resend the key event.
         if (aEvent.charCode != 0 && !aEvent.altKey &&
-            (accelKeyPressed && aEvent.charCode == 'v'.charCodeAt(0) ||
+            (accelKeyPressed && aEvent.charCode == "v".charCodeAt(0) ||
              !aEvent.ctrlKey && !aEvent.metaKey)) {
           this.convView.focus();
 
           let clonedEvent = new KeyboardEvent("keypress", aEvent);
           this.convView.editor.inputField.dispatchEvent(clonedEvent);
           aEvent.preventDefault();
         }
       ]]>
       </body>
      </method>
     </implementation>
     <handlers>
      <handler event="mousedown" phase="capturing">
        <![CDATA[
          let anonid = event.originalTarget.getAttribute("anonid");
          if (anonid == "closeConversationButton") {
-           closeConversation();
+           this.closeConversation();
            event.stopPropagation();
            event.preventDefault();
          }
        ]]>
      </handler>
     </handlers>
   </binding>
 </bindings>
--- a/mail/components/im/content/imconversation.xml
+++ b/mail/components/im/content/imconversation.xml
@@ -159,18 +159,17 @@
         if (!aMsg.system && conv.isChat) {
           let name = aMsg.who;
           let color;
           if (this.buddies.has(name)) {
             let buddy = this.buddies.get(name);
             color = buddy.color;
             buddy.removeAttribute("inactive");
             this._activeBuddies[name] = true;
-          }
-          else {
+          } else {
             // Buddy no longer in the room
             color = this._computeColor(name);
           }
           aMsg.color = "color: hsl(" + color + ", 100%, 40%);";
         }
 
         // Porting note: In TB, this.tab points at the imconv richlistitem element.
         let read = this._readCount > 0;
@@ -235,28 +234,26 @@
           let convToFocus = {};
 
           // The /say command is used to bypass command processing
           // (/say can be shortened to just /).
           // "/say" or "/say " should be ignored, as should "/" and "/ ".
           if (aMsg.match(/^\/(?:say)? ?$/)) {
             this.resetInput();
             return;
-          }
-          else if (aMsg.match(/^\/(?:say)? .*/))
+          } else if (aMsg.match(/^\/(?:say)? .*/)) {
             aMsg = aMsg.slice(aMsg.indexOf(" ") + 1);
-          else if (Services.cmd.executeCommand(aMsg, this._conv.target,
-                                               convToFocus)) {
+          } else if (Services.cmd.executeCommand(aMsg, this._conv.target,
+                                                 convToFocus)) {
             this._conv.sendTyping("");
             this.resetInput();
             if (convToFocus.value)
               chatHandler.focusConversation(convToFocus.value);
             return;
-          }
-          else if (account.protocol.slashCommandsNative && account.connected) {
+          } else if (account.protocol.slashCommandsNative && account.connected) {
             let cmd = aMsg.match(/^\/[^ ]+/);
             if (cmd && cmd != "/me") {
               this._conv.systemMessage(
                 this.bundle.formatStringFromName("unknownCommand", [cmd], 1),
                 true);
               return;
             }
           }
@@ -272,70 +269,67 @@
             let style = MessageFormat.getMessageStyle();
             let proto = this._conv.account.protocol.id;
             if (proto == "prpl-msn") {
               if ("color" in style)
                 msg = "<font color=\"" + style.color + "\">" + msg + "</font>";
               if ("fontFamily" in style)
                 msg = "<font face=\"" + style.fontFamily + "\">" + msg + "</font>";
               // MSN doesn't support font size info in messages...
-            }
-            else if (proto == "prpl-aim" || proto == "prpl-icq") {
-              let styleAttributes = ""
+            } else if (proto == "prpl-aim" || proto == "prpl-icq") {
+              let styleAttributes = "";
               if ("color" in style)
                 styleAttributes += " color=\"" + style.color + "\"";
               if ("fontFamily" in style)
                 styleAttributes += " face=\"" + style.fontFamily + "\"";
               if ("fontSize" in style) {
                 let size = style.fontSize - style.defaultFontSize;
                 if (size < -4)
                   size = 1;
                 else if (size < 0)
                   size = 2;
                 else if (size < 3)
-                  size = 3
+                  size = 3;
                 else if (size < 7)
                   size = 4;
                 else if (size < 15)
                   size = 5;
                 else if (size < 25)
                   size = 6;
                 else
                   size = 7;
                 styleAttributes += " size=\"" + size + "\""
                                  + " style=\"font-size: " + style.fontSize + "px;\"";
               }
               if (styleAttributes)
                 msg = "<font" + styleAttributes + ">" + msg + "</font>";
-            }
-            else {
+            } else {
               let styleProperties = [];
               if ("color" in style)
                 styleProperties.push("color: " + style.color);
               if ("fontFamily" in style)
                 styleProperties.push("font-family: " + style.fontFamily);
               if ("fontSize" in style)
                 styleProperties.push("font-size: " + style.fontSize + "px");
               style = styleProperties.join("; ");
               if (style)
                 msg = "<span style=\"" + style + "\">" + msg + "</span>";
             }
           }
           this._conv.sendMsg(msg);
-        }
-        else {
+        } else {
           msg = account.HTMLEscapePlainText ? msg : aMsg;
 
           if (account.noNewlines) {
             // 'Illegal operation on WrappedNative prototype object' if the this
             // object is not specified (since this._conv implements nsIClassInfo)
             msg.split("\n").forEach(this._conv.sendMsg, this._conv);
+          } else {
+            this._conv.sendMsg(msg);
           }
-          else
-            this._conv.sendMsg(msg);
         }
         // reset the textbox to its original size
         this.resetInput();
       ]]>
       </body>
      </method>
 
      <method name="_onSplitterChange">
@@ -360,20 +354,20 @@
        0.1 means that the textbox's height is 10% of the conversation's height.
      -->
      <field name="_TEXTBOX_RATIO" readonly="true">0.1</field>
 
 
      <method name="calculateTextboxDefaultHeight">
       <body>
       <![CDATA[
-        let totalSpace = parseInt(window.getComputedStyle(this, null)
+        let totalSpace = parseInt(window.getComputedStyle(this)
                                         .getPropertyValue("height"));
         let textbox = this.editor;
-        let textboxStyle = window.getComputedStyle(textbox, null);
+        let textboxStyle = window.getComputedStyle(textbox);
         let lineHeight = parseInt(textboxStyle.getPropertyValue("line-height"));
 
         // Compute the overhead size.
         let textboxHeight = textbox.inputField.clientHeight;
         let deckHeight = textbox.parentNode.boxObject.height;
         this._TEXTBOX_VERTICAL_OVERHEAD = deckHeight - textboxHeight;
 
         // Calculate the number of lines to display.
@@ -469,18 +463,17 @@
 
         // Undo tab complete.
         if (noSelection && this._completions &&
             event.keyCode == KeyEvent.DOM_VK_BACK_SPACE &&
             !event.altKey && !event.ctrlKey && !event.metaKey && !event.shiftKey) {
           if (text == this._beforeTabComplete) {
             // Nothing to undo, so let backspace act normally.
             delete this._completions;
-          }
-          else {
+          } else {
             event.preventDefault();
 
             // First undo the comma separating multiple nicks or the suffix.
             // More than one nick:
             //   "nick1, nick2: " -> "nick1: nick2"
             // Single nick: remove the suffix
             //   "nick1: " -> "nick1"
             let pos = inputBox.selectionStart;
@@ -564,18 +557,17 @@
           if (completingCommand) {
             for (let cmd of Services.cmd.listCommandsForConversation(this._conv)) {
               // It's possible to have a global and a protocol specific command
               // with the same name. Avoid duplicates in the |completions| array.
               let name = "/" + cmd.name;
               if (!completions.includes(name))
                 completions.push(name);
             }
-          }
-          else {
+          } else {
             // If it's not a command, the only thing we can complete is a nick.
             if (!this._conv.isChat)
               return;
 
             firstWordSuffix = ": ";
 
             completions = Array.from(this.buddies.keys());
 
@@ -691,40 +683,39 @@
           delete this._shouldListCompletionsLater;
           if (this._completions.length > 1) {
             let completionsList = this._completions.join(" ");
             if (preferredNick) {
               // If we have a preferred nick (which is completed as a whole
               // even if there are alternatives), only show the list of
               // completions on the next <tab> press.
               this._shouldListCompletionsLater = completionsList;
+            } else {
+              this._conv.systemMessage(completionsList);
             }
-            else
-              this._conv.systemMessage(completionsList);
           }
 
           let suffix = (isFirstWord ? firstWordSuffix : "");
           this._completions = this._completions.map(c => c + suffix);
 
           let completion;
           if (this._completions.length == 1 || preferredNick) {
             // Only one possible completion? Apply it! :-)
             completion = this._completions[this._completionsIndex++];
             this._completionsIndex %= this._completions.length;
-          }
-          else {
+          } else {
             // We have several possible completions, attempt to find a common prefix.
             let maxLength = Math.min(firstCompletion.length, lastCompletion.length);
             let i = 0;
             while (i < maxLength && firstCompletion[i] == lastCompletion[i])
               ++i;
 
-            if (i)
+            if (i) {
               completion = firstCompletion.substring(0, i);
-            else {
+            } else {
               // Include this case so that secondNick is applied anyway,
               // in case a completion is added by another tab press.
               completion = word;
             }
           }
 
           // Always replace what the user typed as its upper/lowercase may
           // not be correct.
@@ -733,31 +724,31 @@
 
           if (secondNick) {
             // Replace the trailing colon with a comma before the completed nick.
             inputBox.selectionStart -= 2;
             completion = ", " + completion;
           }
 
           this.addString(completion);
+        } else if (this._completions) {
+          delete this._completions;
         }
-        else if (this._completions)
-          delete this._completions;
 
         if (event.keyCode != 13)
           return;
 
         if (!event.ctrlKey && !event.shiftKey && !event.altKey) {
           // Prevent the default action before calling sendMsg to avoid having
           // a line break inserted in the textbox if sendMsg throws.
           event.preventDefault();
           this.sendMsg(text);
+        } else if (!event.shiftKey) {
+          this.addString("\n");
         }
-        else if (!event.shiftKey)
-          this.addString("\n");
       ]]>
       </body>
      </method>
 
      <field name="_pendingValueChangedCall">false</field>
      <method name="inputValueChanged">
        <body>
        <![CDATA[
@@ -799,18 +790,17 @@
            left = this._conv.sendTyping(text.slice(4));
 
          // When the input box is cleared or there is no character limit,
          // don't show the character limit.
          let charCounter = this.getElt("charCounter");
          if (left == Ci.prplIConversation.NO_TYPING_LIMIT || !text.length) {
            charCounter.setAttribute("value", "");
            inputBox.removeAttribute("invalidInput");
-         }
-         else {
+         } else {
            // 200 is a 'magic' constant to avoid showing big numbers.
            charCounter.setAttribute("value", (left < 200 ? left : ""));
 
            if (left >= 0)
              inputBox.removeAttribute("invalidInput");
            else if (left < 0)
              inputBox.setAttribute("invalidInput", "true");
          }
@@ -857,30 +847,29 @@
             this.getElt("splitter-bottom").getAttribute("state") == "dragging") {
           input.style.overflowY = "";
           return;
         }
 
         // Check whether we can increase the height without hiding the status bar
         // (ensure the min-height property on the top part of this dialog)
         let topBox = this.getElt("conv-top");
-        let topBoxStyle = window.getComputedStyle(topBox, null);
+        let topBoxStyle = window.getComputedStyle(topBox);
         let topMinSize = parseInt(topBoxStyle.getPropertyValue("min-height"));
         let topSize = parseInt(topBoxStyle.getPropertyValue("height"));
         let deck = textbox.parentNode;
         let oldDeckHeight = parseInt(deck.height);
         let newDeckHeight =
           parseInt(input.scrollHeight) + this._TEXTBOX_VERTICAL_OVERHEAD;
 
         if (!topMinSize || topSize - topMinSize > newDeckHeight - oldDeckHeight) {
           // Hide a possible vertical scrollbar.
           input.style.overflowY = "hidden";
           deck.height = newDeckHeight;
-        }
-        else {
+        } else {
           input.style.overflowY = "";
           // Set it to the maximum possible value.
           deck.height = oldDeckHeight + (topSize - topMinSize);
         }
       ]]>
       </body>
      </method>
 
@@ -889,22 +878,21 @@
       <![CDATA[
         let splitter = this.getElt("splitter-bottom");
         let textbox = this.editor;
 
         if (!splitter.hasAttribute("state")) {
           this.calculateTextboxDefaultHeight();
           textbox.parentNode.height = textbox.defaultHeight +
                                       this._TEXTBOX_VERTICAL_OVERHEAD;
-        }
-        else {
+        } else {
           // Used in case the browser is already on its min-height, resize the
           // textbox to avoid hiding the status bar.
           let convTop = this.getElt("conv-top");
-          let convTopStyle = window.getComputedStyle(convTop, null);
+          let convTopStyle = window.getComputedStyle(convTop);
           let convTopHeight = parseInt(convTopStyle.getPropertyValue("height"));
           let convTopMinHeight =
             parseInt(convTopStyle.getPropertyValue("min-height"));
 
           if (convTopHeight == convTopMinHeight) {
             textbox.parentNode.height = parseInt(textbox.parentNode.minHeight);
             convTopHeight = parseInt(convTopStyle.getPropertyValue("height"));
             textbox.parentNode.height = parseInt(textbox.parentNode.minHeight) +
@@ -927,21 +915,18 @@
       ]]>
       </body>
      </method>
 
      <method name="browserKeyPress">
      <parameter name="event"/>
       <body>
       <![CDATA[
-#ifndef XP_MACOSX
-        var accelKeyPressed = event.ctrlKey;
-#else
-        var accelKeyPressed = event.metaKey;
-#endif
+        var accelKeyPressed = AppConstants.platform == "macosx" ? event.metaKey : event.ctrlKey;
+
         // 118 is the decimal code for "v" character, 13 keyCode for "return" key
         if (((accelKeyPressed && event.charCode != 118) || event.altKey) &&
             event.keyCode != 13)
           return;
 
         if (event.charCode == 0 &&  // it's not a character, it's a command key
             (event.keyCode != 13 && // Return
              event.keyCode != 8 &&  // Backspace
@@ -1238,35 +1223,34 @@
          }
        ]]>
        </body>
      </method>
 
      <method name="getShowNickModifier">
        <body>
        <![CDATA[
-         return (function (aNode) {
+         return (function(aNode) {
            if (!("_showNickRegExp" in this)) {
              if (!("_showNickList" in this)) {
                this._showNickList = {};
                for (let n of this.buddies.keys())
                  this._showNickList[n.replace(this._nickEscape, "\\$&")] = true;
              }
 
              // The reverse sort ensures that if we have "foo" and "foobar",
              // "foobar" will be matched first by the regexp.
              let nicks = Object.keys(this._showNickList).sort().reverse().join("|");
              if (nicks) {
                // We use \W to match for word-boundaries, as \b will not match the
                // nick if it starts/ends with \W characters.
                // XXX Ideally we would use unicode word boundaries:
                // http://www.unicode.org/reports/tr29/#Word_Boundaries
                this._showNickRegExp = new RegExp("\\W(?:" + nicks + ")\\W");
-             }
-             else {
+             } else {
                // nobody, disable...
                this._showNickRegExp = {exec: () => null};
                return 0;
              }
            }
            let exp = this._showNickRegExp;
            let result = 0;
            let match;
@@ -1284,45 +1268,43 @@
              // the nick. The text in aNode hasn't been processed yet.
              let nick = nickNode.data;
              let elt = aNode.ownerDocument.createElement("span");
              elt.setAttribute("class", "ib-nick");
              if (this.buddies.has(nick)) {
                let buddy = this.buddies.get(nick);
                elt.setAttribute("style", buddy.colorStyle);
                elt.setAttribute("data-nickColor", buddy.color);
+             } else {
+               elt.setAttribute("data-left", "true");
              }
-             else
-               elt.setAttribute("data-left", "true");
              nickNode.parentNode.replaceChild(elt, nickNode);
              elt.textContent = nick;
              result += 2;
            }
            return result;
          }).bind(this);
        ]]>
        </body>
      </method>
 
      <method name="updateTopic">
        <body>
        <![CDATA[
           let cti = document.getElementById("conv-top-info");
-          let statusMessage = this.getElt("statusMessage");
           if (this._conv.topicSettable)
             cti.setAttribute("topicEditable", "true");
           else
             cti.removeAttribute("topicEditable");
 
           var topic = this._conv.topic;
           if (topic) {
             cti.setAttribute("statusTooltiptext", topic);
             cti.removeAttribute("noTopic");
-          }
-          else {
+          } else {
             topic = this._conv.noTopicString;
             cti.setAttribute("noTopic", "true");
             cti.setAttribute("statusTooltiptext", topic);
           }
           cti.setAttribute("statusMessage", topic);
           cti.setAttribute("statusMessageWithDash", " - " + topic);
           cti.removeAttribute("userIcon");
        ]]>
@@ -1352,27 +1334,26 @@
        <![CDATA[
           let typingState = this._conv.typingState;
           let cti = document.getElementById("conv-top-info");
           cti.removeAttribute("typing");
           cti.removeAttribute("typed");
 
           let name = this._currentTypingName;
           if (!this._currentTypingName)
-            name = this._conv.title;//.replace(/^([a-zA-Z0-9.]+)[@\s].*/, "$1");
+            name = this._conv.title; // .replace(/^([a-zA-Z0-9.]+)[@\s].*/, "$1");
           if (typingState == Ci.prplIConvIM.TYPING) {
             cti.setAttribute("typing", "true");
             let typingMsg = this.bundle.formatStringFromName("chat.contactIsTyping",
                                                              [name], 1);
             cti.setAttribute("statusTypeTooltiptext", typingMsg);
             cti.setAttribute("statusTooltiptext", typingMsg);
             cti.setAttribute("statusMessage",
                              this.bundle.GetStringFromName("chat.isTyping"));
-          }
-          else if (typingState == Ci.prplIConvIM.TYPED) {
+          } else if (typingState == Ci.prplIConvIM.TYPED) {
             cti.setAttribute("typed", "true");
 
             let typedMsg = this.bundle.formatStringFromName("chat.contactHasStoppedTyping",
                                                             [name], 1);
             cti.setAttribute("statusTypeTooltiptext", typedMsg);
             cti.setAttribute("statusTooltiptext", typedMsg);
             cti.setAttribute("statusMessage",
                              this.bundle.GetStringFromName("chat.hasStoppedTyping"));
@@ -1433,26 +1414,25 @@
           let cti = document.getElementById("conv-top-info");
           cti.setAttribute("prplIcon",
                            this._conv.account.protocol.iconBaseURI + "icon.png");
 
           if (this._conv.isChat) {
             this.updateTopic();
             cti.setAttribute("status", "chat");
             cti.setAttribute("displayName", this._conv.title);
-          }
-          else {
+          } else {
             let displayName = this._conv.title;
             let statusText = "";
             let statusType = Ci.imIStatusInfo.STATUS_UNKNOWN;
 
             let buddy = this._conv.buddy;
-            if (!buddy || !buddy.account.connected)
+            if (!buddy || !buddy.account.connected) {
               cti.removeAttribute("userIcon");
-            else {
+            } else {
               displayName = buddy.displayName;
               statusText = buddy.statusText;
               statusType = buddy.statusType;
               cti.setAttribute("userIcon", buddy.buddyIconFilename);
             }
 
             cti.setAttribute("displayName", displayName);
             if (statusText)
@@ -1536,17 +1516,17 @@
            // modifiers can be added with observers for this notification.
            if (!this.loaded)
              setTimeout(this._showFirstMessages.bind(this), 0);
 
            Services.obs.removeObserver(this, "conversation-loaded");
            return;
          }
 
-         switch(aTopic) {
+         switch (aTopic) {
          case "new-text":
            if (this.loaded)
              this.addMsg(aSubject);
            break;
 
          case "status-text-changed":
            this._statusText = aData || "";
            this.displayStatusText();
@@ -1639,17 +1619,17 @@
        <getter>
          <![CDATA[
            return this._conv;
          ]]>
        </getter>
        <setter>
          <![CDATA[
            if (this._conv && val)
-             throw("Already initialized");
+             throw "Already initialized";
            if (!val) {
              // this conversation has probably been moved to another
              // tab. Forget the prplConversation so that it isn't
              // closed when destroying this binding.
              this._forgetConv();
              return val;
            }
            this._conv = val;
--- a/mail/components/im/content/imgroup.xml
+++ b/mail/components/im/content/imgroup.xml
@@ -69,17 +69,17 @@
             if (this.sortComparator(aContact, this.contacts[middle].contact) < 0)
               end = middle;
             else
               start = middle + 1;
           }
         }
         let last = end == 0 ? this : this.contacts[end - 1];
         this.parentNode.insertBefore(contactElt, last.nextSibling);
-        contactElt.build(aContact);//, this);
+        contactElt.build(aContact);
         contactElt.group = this;
         this.contacts.splice(end, 0, contactElt);
         this.contactsById[aContact.id] = contactElt;
         this.removeAttribute("collapsed");
         this._updateGroupLabel();
         return contactElt;
       ]]>
       </body>
@@ -148,18 +148,17 @@
 
      <method name="close">
       <body>
       <![CDATA[
         if (this.hasAttribute("closed")) {
           this.removeAttribute("closed");
           this.setAttribute("aria-expanded", "true");
           this._updateClosedState(false);
-        }
-        else {
+        } else {
           this.setAttribute("closed", "true");
           this.setAttribute("aria-expanded", "false");
           this._updateClosedState(true);
         }
 
         this._updateGroupLabel();
       ]]>
       </body>
--- a/mail/components/im/content/joinchat.js
+++ b/mail/components/im/content/joinchat.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/. */
 
-ChromeUtils.import("resource:///modules/iteratorUtils.jsm");
-ChromeUtils.import("resource:///modules/imServices.jsm");
+var { fixIterator } = ChromeUtils.import("resource:///modules/iteratorUtils.jsm", null);
+var { Services } = ChromeUtils.import("resource:///modules/imServices.jsm", null);
 
 var autoJoinPref = "autoJoin";
 
 var joinChat = {
   onload: function jc_onload() {
     var accountList = document.getElementById("accountlist");
     for (let acc of fixIterator(Services.accounts.getAccounts())) {
       if (!acc.connected || !acc.canJoinChat)
@@ -76,31 +76,31 @@ var joinChat = {
         text = document.getElementById("optionalcolumn")
                        .getAttribute("labeltxt");
         label.setAttribute("value", text);
         row.appendChild(label);
       }
 
       row.setAttribute("align", "baseline");
       sep.parentNode.insertBefore(row, sep);
-      joinChat._fields.push({field: field, textbox: textbox});
+      joinChat._fields.push({field, textbox});
     }
 
     window.sizeToContent();
   },
 
   join: function jc_join() {
     let values = joinChat._values;
     for (let field of joinChat._fields) {
       let val = field.textbox.value.trim();
       if (!val && field.field.required) {
         field.textbox.focus();
-        //FIXME: why isn't the return false enough?
+        // FIXME: why isn't the return false enough?
         throw "Some required fields are empty!";
-        return false;
+        // return false;
       }
       if (val)
         values.setValue(field.field.identifier, val);
     }
     let account = joinChat._account;
     account.joinChat(values);
 
     let protoId = account.protocol.id;
@@ -117,17 +117,17 @@ var joinChat = {
     let conv = Services.conversations.getConversationByNameAndAccount(name,
                                                                       account,
                                                                       true);
     if (conv) {
       let mailWindow = Services.wm.getMostRecentWindow("mail:3pane");
       if (mailWindow) {
         mailWindow.focus();
         let tabmail = mailWindow.document.getElementById("tabmail");
-        tabmail.openTab("chat", {convType: "focus", conv: conv});
+        tabmail.openTab("chat", {convType: "focus", conv});
       }
     }
 
     if (document.getElementById("autojoin").checked) {
       // "nick" for JS-XMPP, "handle" for libpurple prpls.
       let nick = values.getValue("nick") || values.getValue("handle");
       if (nick)
         name += "/" + nick;
@@ -144,10 +144,10 @@ var joinChat = {
 
       if (!autojoin.includes(name)) {
         autojoin.push(name);
         prefBranch.setStringPref(autoJoinPref, autojoin.join(","));
       }
     }
 
     return true;
-  }
+  },
 };
--- a/mail/components/im/imIncomingServer.js
+++ b/mail/components/im/imIncomingServer.js
@@ -1,16 +1,19 @@
 /* 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/. */
 
-ChromeUtils.import("resource:///modules/imXPCOMUtils.jsm");
-ChromeUtils.import("resource:///modules/imServices.jsm");
+const {
+  EmptyEnumerator,
+  XPCOMUtils,
+} = ChromeUtils.import("resource:///modules/imXPCOMUtils.jsm", null);
+const { Services } = ChromeUtils.import("resource:///modules/imServices.jsm", null);
 
-function imIncomingServer() { }
+function imIncomingServer() {}
 
 imIncomingServer.prototype = {
   get wrappedJSObject() { return this; },
   _imAccount: null,
   get imAccount() {
     if (this._imAccount)
       return this._imAccount;
 
@@ -30,21 +33,21 @@ imIncomingServer.prototype = {
   get offlineSupportLevel() { return 0; },
   get supportsDiskSpace() { return false; },
   _key: "",
   get key() { return this._key; },
   set key(aKey) {
     this._key = aKey;
     this._prefBranch = Services.prefs.getBranch("mail.server." + aKey + ".");
   },
-  equals: function (aServer) {
+  equals(aServer) {
     return "wrappedJSObject" in aServer && aServer.wrappedJSObject == this;
   },
 
-  clearAllValues: function() {
+  clearAllValues() {
     Services.accounts.deleteAccount(this.imAccount.id);
     this._prefBranch.deleteBranch("");
     delete this._prefBranch;
     delete this._imAccount;
   },
 
   // Returns the directory where the account would have its data stored.
   // There are currently conversation logs only.
@@ -54,45 +57,45 @@ imIncomingServer.prototype = {
   get localPath() {
     let logPath = Services.logs.getLogFolderPathForAccount(this.imAccount);
     let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
     file.initWithPath(logPath);
     return file;
   },
 
   // Removes files created by this account.
-  removeFiles: function() {
+  removeFiles() {
     Services.logs.deleteLogFolderForAccount(this.imAccount);
   },
 
   // called by nsMsgAccountManager while deleting an account:
-  forgetSessionPassword: function() { },
+  forgetSessionPassword() {},
 
-  forgetPassword: function() {
+  forgetPassword() {
     // Password is cleared in imAccount.remove()
     // TODO: this may need to be implemented here as a separate function
     // once IM accounts support changing username/hostname.
   },
 
   // Shown in the "Remove Account" confirm prompt.
   get prettyName() {
     let protocol = this.imAccount.protocol.name || this.imAccount.protocol.id;
     return protocol + " - " + this.imAccount.name;
   },
 
-  //XXX Flo: I don't think constructedPrettyName is visible in the UI
+  // XXX Flo: I don't think constructedPrettyName is visible in the UI
   get constructedPrettyName() { return "constructedPrettyName FIXME"; },
   get realHostName() { return this.hostName; },
   set realHostName(aValue) {},
 
   port: -1,
   accountManagerChrome: "am-im.xul",
 
 
-  //FIXME need a new imIIncomingService iface + classinfo for these 3 properties :(
+  // FIXME need a new imIIncomingService iface + classinfo for these 3 properties :(
   get password() { return this.imAccount.password; },
   set password(aPassword) {
     this.imAccount.password = aPassword;
   },
   get alias() { return this.imAccount.alias; },
   set alias(aAlias) {
     this.imAccount.alias = aAlias;
   },
@@ -105,68 +108,68 @@ imIncomingServer.prototype = {
     }
   },
   set autojoin(aAutoJoin) {
     let prefName = "messenger.account." + this.imAccount.id + ".autoJoin";
     Services.prefs.setStringPref(prefName, aAutoJoin);
   },
 
   // This is used for user-visible advanced preferences.
-  setUnicharValue: function(aPrefName, aValue) {
+  setUnicharValue(aPrefName, aValue) {
     if (aPrefName == "autojoin")
       this.autojoin = aValue;
     else if (aPrefName == "alias")
       this.alias = aValue;
     else if (aPrefName == "password")
       this.password = aValue;
     else
       this.imAccount.setString(aPrefName, aValue);
   },
-  getUnicharValue: function(aPrefName) {
+  getUnicharValue(aPrefName) {
     if (aPrefName == "autojoin")
       return this.autojoin;
     if (aPrefName == "alias")
       return this.alias;
     if (aPrefName == "password")
       return this.password;
 
     try {
       let prefName =
         "messenger.account." + this.imAccount.id + ".options." + aPrefName;
       return Services.prefs.getStringPref(prefName);
     } catch (x) {
       return this._getDefault(aPrefName);
     }
   },
-  setBoolValue: function(aPrefName, aValue) {
+  setBoolValue(aPrefName, aValue) {
     this.imAccount.setBool(aPrefName, aValue);
   },
-  getBoolValue: function(aPrefName) {
+  getBoolValue(aPrefName) {
     try {
       let prefName =
         "messenger.account." + this.imAccount.id + ".options." + aPrefName;
       return Services.prefs.getBoolPref(prefName);
     } catch (x) {
       return this._getDefault(aPrefName);
     }
   },
-  setIntValue: function(aPrefName, aValue) {
+  setIntValue(aPrefName, aValue) {
     this.imAccount.setInt(aPrefName, aValue);
   },
-  getIntValue: function(aPrefName) {
+  getIntValue(aPrefName) {
     try {
       let prefName =
         "messenger.account." + this.imAccount.id + ".options." + aPrefName;
       return Services.prefs.getIntPref(prefName);
     } catch (x) {
       return this._getDefault(aPrefName);
     }
   },
   _defaultOptionValues: null,
-  _getDefault: function(aPrefName) {
+  _getDefault(aPrefName) {
     if (this._defaultOptionValues)
       return this._defaultOptionValues[aPrefName];
 
     this._defaultOptionValues = {};
     let options = this.imAccount.protocol.getOptions();
     while (options.hasMoreElements()) {
       let opt = options.getNext();
       let type = opt.type;
@@ -178,20 +181,20 @@ imIncomingServer.prototype = {
         this._defaultOptionValues[opt.name] = opt.getString();
       else if (type == opt.typeList)
         this._defaultOptionValues[opt.name] = opt.getListDefault();
     }
     return this._defaultOptionValues[aPrefName];
   },
 
   // the "Char" type will be used only for "imAccount" and internally.
-  setCharValue: function(aPrefName, aValue) {
+  setCharValue(aPrefName, aValue) {
     this._prefBranch.setCharPref(aPrefName, aValue);
   },
-  getCharValue: function(aPrefName) {
+  getCharValue(aPrefName) {
     try {
       return this._prefBranch.getCharPref(aPrefName);
     } catch (x) {
       return "";
     }
   },
 
   get type() { return this._prefBranch.getCharPref("type"); },
@@ -210,46 +213,46 @@ imIncomingServer.prototype = {
     this._prefBranch.setCharPref("userName", aUsername);
   },
 
   get hostName() { return this._prefBranch.getCharPref("hostname"); },
   set hostName(aHostName) {
     this._prefBranch.setCharPref("hostname", aHostName);
   },
 
-  writeToFolderCache: function() { },
-  closeCachedConnections: function() { },
+  writeToFolderCache() {},
+  closeCachedConnections() {},
 
   // Shutdown the server instance so at least disconnect from the server.
-  shutdown: function() {
+  shutdown() {
     // Ensure this account has not been destroyed already.
     if (this.imAccount.prplAccount)
       this.imAccount.disconnect();
   },
 
-  setFilterList: function() { },
+  setFilterList() {},
 
   get canBeDefaultServer() { return false; },
 
   // AccountManager.js verifies that spamSettings is non-null before
   // using the initialize method, but we can't just use a null value
   // because that would crash nsMsgPurgeService::PerformPurge which
   // only verifies the nsresult return value of the spamSettings
   // getter before accessing the level property.
   get spamSettings() {
     return {
       level: 0,
-      initialize: function(aServer) {},
-      QueryInterface: ChromeUtils.generateQI([Ci.nsISpamSettings])
+      initialize(aServer) {},
+      QueryInterface: ChromeUtils.generateQI([Ci.nsISpamSettings]),
     };
   },
 
   // nsMsgDBFolder.cpp crashes in HandleAutoCompactEvent if this doesn't exist:
   msgStore: {
-    supportsCompaction: false
+    supportsCompaction: false,
   },
 
   get serverURI() { return "im://" + this.imAccount.protocol.id + "/" + this.imAccount.name; },
   _rootFolder: null,
   get rootMsgFolder() { return this.rootFolder; },
   get rootFolder() {
     if (this._rootFolder)
       return this._rootFolder;
@@ -257,40 +260,40 @@ imIncomingServer.prototype = {
     return (this._rootFolder = {
       isServer: true,
       server: this,
       get URI() { return this.server.serverURI; },
       get prettyName() { return this.server.prettyName; }, // used in the account manager tree
       get name() { return this.server.prettyName + " name"; }, // never displayed?
       // used in the folder pane tree, if we don't hide the IM accounts:
       get abbreviatedName() { return this.server.prettyName + "abbreviatedName"; },
-      AddFolderListener: function() {},
-      RemoveFolderListener: function() {},
+      AddFolderListener() {},
+      RemoveFolderListener() {},
       descendants: Cc["@mozilla.org/array;1"]
                   .createInstance(Ci.nsIArray),
-      ListDescendants: function(descendants) {},
+      ListDescendants(descendants) {},
       getFlag: () => false,
       getFolderWithFlags: aFlags => null,
       getFoldersWithFlags: aFlags =>
         Cc["@mozilla.org/array;1"]
           .createInstance(Ci.nsIArray),
       get subFolders() { return EmptyEnumerator; },
       getStringProperty: aPropertyName => "",
       getNumUnread: aDeep => 0,
-      Shutdown: function() {},
-      QueryInterface: ChromeUtils.generateQI([Ci.nsIMsgFolder])
+      Shutdown() {},
+      QueryInterface: ChromeUtils.generateQI([Ci.nsIMsgFolder]),
     });
   },
 
   get sortOrder() { return 300000000; },
 
   get protocolInfo() {
     return Cc["@mozilla.org/messenger/protocol/info;1?type=im"]
              .getService(Ci.nsIMsgProtocolInfo);
   },
 
   classDescription: "IM Msg Incoming Server implementation",
   classID: Components.ID("{9dd7f36b-5960-4f0a-8789-f5f516bd083d}"),
   contractID: "@mozilla.org/messenger/server;1?type=im",
-  QueryInterface: ChromeUtils.generateQI([Ci.nsIMsgIncomingServer])
+  QueryInterface: ChromeUtils.generateQI([Ci.nsIMsgIncomingServer]),
 };
 
 var NSGetFactory = XPCOMUtils.generateNSGetFactory([imIncomingServer]);
--- a/mail/components/im/imProtocolInfo.js
+++ b/mail/components/im/imProtocolInfo.js
@@ -1,35 +1,35 @@
 /* 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/. */
 
 ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
 
-function imProtocolInfo() { }
+function imProtocolInfo() {}
 
 imProtocolInfo.prototype = {
 
   defaultLocalPath: null,
   get serverIID() { return null; },
   get requiresUsername() { return true; },
   get preflightPrettyNameWithEmailAddress() { return false; },
   get canDelete() { return true; },
   // Even though IM accounts can login at startup, canLoginAtStartUp
   // should be false as it's used to decide if new messages should be
   // fetched at startup and that concept of message doesn't apply to
   // IM accounts.
   get canLoginAtStartUp() { return false; },
   get canDuplicate() { return false; },
-  getDefaultServerPort: () =>  0,
+  getDefaultServerPort: () => 0,
   get canGetMessages() { return false; },
   get canGetIncomingMessages() { return false; },
   get defaultDoBiff() { return false; },
   get showComposeMsgLink() { return false; },
   get foldersCreatedAsync() { return false; },
 
   classDescription: "IM Msg Protocol Info implementation",
   classID: Components.ID("{13118758-dad2-418c-a03d-1acbfed0cd01}"),
   contractID: "@mozilla.org/messenger/protocol/info;1?type=im",
-  QueryInterface: ChromeUtils.generateQI([Ci.nsIMsgProtocolInfo])
+  QueryInterface: ChromeUtils.generateQI([Ci.nsIMsgProtocolInfo]),
 };
 
 var NSGetFactory = XPCOMUtils.generateNSGetFactory([imProtocolInfo]);
--- a/mail/components/im/jar.mn
+++ b/mail/components/im/jar.mn
@@ -15,17 +15,17 @@ messenger.jar:
     content/messenger/chat/imAccounts.css                (content/imAccounts.css)
     content/messenger/chat/imAccounts.js                 (content/imAccounts.js)
     content/messenger/chat/imAccounts.xul                (content/imAccounts.xul)
     content/messenger/chat/imAccountWizard.xul           (content/imAccountWizard.xul)
     content/messenger/chat/imAccountWizard.js            (content/imAccountWizard.js)
     content/messenger/chat/imContextMenu.js              (content/imContextMenu.js)
     content/messenger/chat/imStatusSelector.js           (content/imStatusSelector.js)
     content/messenger/chat/imcontact.xml                 (content/imcontact.xml)
-*   content/messenger/chat/imconversation.xml            (content/imconversation.xml)
+    content/messenger/chat/imconversation.xml            (content/imconversation.xml)
     content/messenger/chat/imconv.xml                    (content/imconv.xml)
     content/messenger/chat/imgroup.xml                   (content/imgroup.xml)
 % skin messenger-messagestyles classic/1.0 %skin/classic/messenger/messages/
 	skin/classic/messenger/messages/mail/Bitmaps/minus-hover.png          (messages/mail/Bitmaps/minus-hover.png)
 	skin/classic/messenger/messages/mail/Bitmaps/minus.png                (messages/mail/Bitmaps/minus.png)
 	skin/classic/messenger/messages/mail/Bitmaps/plus-hover.png           (messages/mail/Bitmaps/plus-hover.png)
 	skin/classic/messenger/messages/mail/Bitmaps/plus.png                 (messages/mail/Bitmaps/plus.png)
 	skin/classic/messenger/messages/mail/Footer.html                      (messages/mail/Footer.html)
@@ -184,17 +184,17 @@ messenger.jar:
 	skin/classic/messenger/messages/simple/Incoming/Context.html (messages/simple/Incoming/Context.html)
 	skin/classic/messenger/messages/simple/Incoming/NextContext.html (messages/simple/Incoming/NextContext.html)
 	skin/classic/messenger/messages/simple/Info.plist            (messages/simple/Info.plist)
 	skin/classic/messenger/messages/simple/main.css              (messages/simple/main.css)
 	skin/classic/messenger/messages/simple/Status.html           (messages/simple/Status.html)
 	skin/classic/messenger/messages/simple/Variants/Normal.css   (messages/simple/Variants/Normal.css)
 	skin/classic/messenger/messages/simple/Variants/Dark.css     (messages/simple/Variants/Dark.css)
 % skin messenger-emoticons classic/1.0 %skin/classic/messenger/smileys/
-	skin/classic/messenger/smileys/theme.js              (smileys/theme.js)
+	skin/classic/messenger/smileys/theme.json            (smileys/theme.json)
 	skin/classic/messenger/smileys/angry.png             (smileys/angry.png)
 	skin/classic/messenger/smileys/confused.png          (smileys/confused.png)
 	skin/classic/messenger/smileys/cool.png              (smileys/cool.png)
 	skin/classic/messenger/smileys/cry.png               (smileys/cry.png)
 	skin/classic/messenger/smileys/embarrassed.png       (smileys/embarrassed.png)
 	skin/classic/messenger/smileys/grin.png              (smileys/grin.png)
 	skin/classic/messenger/smileys/heart.png             (smileys/heart.png)
 	skin/classic/messenger/smileys/manga_annoyed.png     (smileys/manga_annoyed.png)
--- a/mail/components/im/messages/bubbles/Footer.html
+++ b/mail/components/im/messages/bubbles/Footer.html
@@ -1,38 +1,40 @@
 <!-- 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/. -->
 
 <script type="application/javascript">
+// chat/content/convbrowser.xml
+/* globals autoScrollEnabled, scrollToElement */
+
 /* [pseudo_color, pseudo_background, bubble_borders] */
 const elements_lightness = [[75, 94, 80], [75, 94, 80], [70, 93, 75], [65, 92, 70], [55, 90, 65], [48, 90, 60], [44, 86, 50], [44, 88, 60], [45, 88, 70], [45, 90, 70], [45, 92, 70], [45, 92, 70], [45, 92, 70], [45, 92, 70], [45, 92, 70], [45, 92, 70], [45, 92, 70], [45, 92, 70], [45, 92, 70], [60, 92, 70], [70, 93, 75], [75, 94, 80], [75, 94, 80], [75, 94, 80], [75, 94, 80], [75, 94, 80], [75, 94, 80], [75, 94, 80], [75, 94, 80], [75, 94, 80], [75, 94, 80], [75, 94, 80], [75, 94, 80], [75, 94, 80], [75, 94, 80], [75, 94, 80]];
 
 const bubble_background = "hsl(#, 100%, 97%)";
 const bubble_borders = "hsl(#, 100%, #%)";
 const pseudo_color = "hsl(#, 100%, #%)";
 const pseudo_background = "hsl(#, 100%, #%)";
 
 var alternating = null;
 
-function setColors(target)
-{
+function setColors(target) {
   var senderColor = target.getAttribute("data-senderColor");
 
   if (!senderColor)
     return;
 
   var regexp = /color:\s*hsl\(\s*(\d{1,3})\s*,\s*\d{1,3}\%\s*,\s*\d{1,3}\%\s*\)/;
   var parsed = regexp.exec(senderColor);
 
   if (!parsed)
     return;
 
-  var senderHue = ((Math.round(parsed[1]/10))*10)%360;
-  var lightness = elements_lightness[senderHue/10];
+  var senderHue = ((Math.round(parsed[1] / 10)) * 10) % 360;
+  var lightness = elements_lightness[senderHue / 10];
 
   target.style.backgroundColor = bubble_background.replace("#", senderHue);
   target.style.borderColor = bubble_borders.replace("#", senderHue)
                                            .replace("#", lightness[2]);
 
   var pseudo = target.getElementsByClassName("pseudo")[0];
   pseudo.style.color = pseudo_color.replace("#", senderHue)
                                    .replace("#", lightness[0]);
@@ -40,17 +42,17 @@ function setColors(target)
                                                   .replace("#", lightness[1]);
 
   var div_indicator = target.getElementsByClassName("indicator")[0];
   var imageURL = "url('Bitmaps/indicator_" + senderHue;
   if (target.classList.contains("incoming")) {
     // getComputedStyle is prohibitively expensive, and we need it only to
     // know if we are using an alternating variant, so we cache the result.
     if (alternating === null) {
-      alternating = document.defaultView.getComputedStyle(div_indicator, null)
+      alternating = document.defaultView.getComputedStyle(div_indicator)
                             .backgroundImage.endsWith('_alt.png")') ? "_alt" : "";
     }
     imageURL += alternating;
   }
   div_indicator.style.backgroundImage = imageURL + ".png')";
 }
 
 
@@ -92,18 +94,17 @@ var lastMessageTimeoutTime = -1;
  * When the last message is more than timebeforetextdisplay old, we display
  * the time in text. To avoid blinking Mac scrollbar and visual distractions
  * for some very sensitive users, we update the whitespace only when a new
  * message is displayed or when the user switches between tabs. While the
  * conversation is visible, this function is called by timers, but we will
  * only update the time displayed in text (this behavior is obtained by
  * setting the aUpdateTextOnly parameter to true; otherwise it is omitted).
  */
-function handleLastMessage(aUpdateTextOnly)
-{
+function handleLastMessage(aUpdateTextOnly) {
   if (window.messageInsertPending)
     return;
 
   var intervalInMs = Date.now() - lastMsgTime * 1000;
   var interval = Math.round(intervalInMs / 1000);
   var p = document.getElementById("lastMessage");
   var margin;
   if (!aUpdateTextOnly) {
@@ -137,40 +138,37 @@ function handleLastMessage(aUpdateTextOn
   // The setTimeout callbacks are frequently called a few ms early,
   // but our code prefers being called a little late, so add 20ms.
   lastMessageTimeoutTime = next + 20;
   lastMessageTimeout =
     setTimeout(handleLastMessage, lastMessageTimeoutTime, aUpdateTextOnly);
 }
 
 var lastMsgTime = 0;
-function updateLastMsgTime(aMsgTime)
-{
+function updateLastMsgTime(aMsgTime) {
   if (aMsgTime > lastMsgTime)
     lastMsgTime = aMsgTime;
 
   if (lastMsgTime && lastMessageTimeoutTime != 0 && !document.hidden) {
     clearTimeout(lastMessageTimeout);
     setTimeout(handleLastMessage, 0);
     lastMessageTimeoutTime = 0;
   }
 }
 
-function visibilityChanged()
-{
+function visibilityChanged() {
   if (document.hidden) {
     clearTimeout(lastMessageTimeout);
     lastMessageTimeoutTime = -1;
+  } else if (lastMsgTime) {
+    handleLastMessage();
   }
-  else if (lastMsgTime)
-    handleLastMessage();
 }
 
-function checkNewText(target)
-{
+function checkNewText(target) {
   var nicks = target.getElementsByClassName("ib-nick");
   for (var i = 0; i < nicks.length; ++i) {
     var nick = nicks[i];
     if (nick.hasAttribute("data-left"))
       continue;
     var hue = nick.getAttribute("data-nickColor");
     var senderHue = ((Math.round(hue / 10)) * 10) % 360;
     var lightness = elements_lightness[senderHue / 10];
@@ -192,17 +190,17 @@ function checkNewText(target)
     // to be of equal size, since the preceding bubble will have a shadow.
     var rulerMarginBottom = kRulerMarginTop - 1;
 
     if (lastMsgTime && msgTime >= lastMsgTime) {
       var interval = msgTime - lastMsgTime;
       var margin = computeSpace(interval);
       let isTimetext = interval >= timebeforetextdisplay;
       if (isTimetext) {
-        var p = document.createElement("p");
+        let p = document.createElement("p");
         p.className = "interval";
         if (shouldSetSessionRuler) {
           // Hide the hr and style the time text accordingly instead.
           prev.classList.remove("sessionstart-ruler");
           prev.style.border = "none";
           p.classList.add("sessionstart-ruler");
           margin += 6;
           prev = p;
@@ -226,23 +224,22 @@ function checkNewText(target)
           rulerMarginBottom = shadow - 1;
         }
       }
     }
     if (shouldSetUnreadRuler || shouldSetSessionRuler) {
       prev.style.marginBottom = rulerMarginBottom + "px";
       prev.style.marginTop = kRulerMarginTop + "px";
     }
-  }
-  else if (target.tagName == "P" && target.className == "event") {
+  } else if (target.tagName == "P" && target.className == "event") {
     let parent = target.parentNode;
     // We need to start a group with this element if there are at least 4
     // system messages and they aren't already grouped.
     if (!parent.grouped && parent.querySelector("p.event:nth-of-type(4)")) {
-      var p = document.createElement("p");
+      let p = document.createElement("p");
       p.className = "eventToggle";
       p.addEventListener("click", event =>
         event.target.parentNode.classList.toggle("hide-children"));
       parent.insertBefore(p, parent.querySelector("p.event:nth-of-type(2)"));
       parent.classList.add("hide-children");
       parent.grouped = true;
     }
   }
--- a/mail/components/im/messages/dark/Footer.html
+++ b/mail/components/im/messages/dark/Footer.html
@@ -2,18 +2,17 @@
    - 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/. -->
 
 <script type="application/javascript">
 const p_border_top = "1px solid hsla(#, 100%, 80%, 0.4)";
 const p_background = "-moz-linear-gradient(top, hsla(#, 100%, 80%, 0.3), hsla(#, 100%, 80%, 0.1) 30px)";
 const nick_background = "-moz-linear-gradient(top, hsla(#, 100%, 80%, 0.3), hsla(#, 100%, 80%, 0.1) 1em)";
 
-function setColors(target)
-{
+function setColors(target) {
   var senderColor = target.getAttribute("data-senderColor");
 
   if (!senderColor)
     return;
 
   var regexp = /color:\s*hsl\(\s*(\d{1,3})\s*,\s*\d{1,3}\%\s*,\s*\d{1,3}\%\s*\)/;
   var parsed = regexp.exec(senderColor);
 
@@ -21,18 +20,17 @@ function setColors(target)
     return;
 
   var senderHue = parsed[1];
 
   target.style.borderTop = p_border_top.replace("#", senderHue);
   target.style.background = p_background.replace(/#/g, senderHue);
 }
 
-function checkNewText(target)
-{
+function checkNewText(target) {
   if (target.tagName == "P" && target.className != "event-messages")
     setColors(target);
 
   var nicks = target.getElementsByClassName("ib-nick");
   for (var i = 0; i < nicks.length; ++i) {
     var nick = nicks[i];
     if (!nick.hasAttribute("data-left"))
       nick.style.background = nick_background.replace(/#/g, nick.getAttribute("data-nickColor"));
--- a/mail/components/im/messages/mail/Footer.html
+++ b/mail/components/im/messages/mail/Footer.html
@@ -1,16 +1,15 @@
 <!-- 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/. -->
 
 <script type="application/javascript">
 
-function checkNewText(target)
-{
+function checkNewText(target) {
   if (target.tagName == "P" && target.className == "event") {
     let parent = target.parentNode;
     // We need to start a group with this element if there are at least 4
     // system messages and they aren't already grouped.
     if (!parent.grouped && parent.querySelector("p.event:nth-of-type(4)")) {
       var p = document.createElement("p");
       p.className = "eventToggle";
       p.addEventListener("click", event =>
--- a/mail/components/im/messages/papersheets/Footer.html
+++ b/mail/components/im/messages/papersheets/Footer.html
@@ -6,18 +6,17 @@
 
 const bg_gradient = "background: -moz-linear-gradient(top, rgba(0, 0, 0, 0), rgba(0, 0, 0, 0.1) 15px, hsla(#, 100%, 98%, 1) 15px, hsla(#, 100%, 98%, 1));";
 const bg_context_gradient = "background: -moz-linear-gradient(top, rgba(0, 0, 0, 0), rgba(0, 0, 0, 0.05) 15px, hsla(#, 20%, 98%, 1) 15px, hsla(#, 20%, 98%, 1));";
 const bg_color = "background-color: hsl(#, 100%, 98%);";
 
 var body = document.getElementById("ibcontent");
 
 
-function setColors(target)
-{
+function setColors(target) {
   var senderColor = target.getAttribute("data-senderColor");
 
   if (senderColor) {
     var regexp = /color:\s*hsl\(\s*(\d{1,3})\s*,\s*\d{1,3}\%\s*,\s*\d{1,3}\%\s*\)/;
     var parsed = regexp.exec(senderColor);
 
     if (parsed) {
       var senderHue = parsed[1];
@@ -26,35 +25,31 @@ function setColors(target)
       else
         target.setAttribute("style", bg_gradient.replace(/#/g, senderHue));
     }
   }
 
   if (body.scrollHeight <= screen.height) {
     if (senderHue) {
       body.setAttribute("style", bg_color.replace("#", senderHue));
-    }
-    else if (target.classList.contains("outgoing")) {
+    } else if (target.classList.contains("outgoing")) {
       body.className = "outgoing-color";
       body.removeAttribute("style");
-    }
-    else if (target.classList.contains("incoming")) {
+    } else if (target.classList.contains("incoming")) {
       body.className = "incoming-color";
       body.removeAttribute("style");
-    }
-    else if (target.classList.contains("event")) {
+    } else if (target.classList.contains("event")) {
       body.className = "event-color";
       body.removeAttribute("style");
     }
   }
 }
 
 
-function checkNewText(target)
-{
+function checkNewText(target) {
   if (target.tagName == "DIV")
     setColors(target);
   else if (target.tagName == "P" && target.className == "event") {
     let parent = target.parentNode;
     // We need to start a group with this element if there are at least 3
     // system messages and they aren't already grouped.
     if (!parent.grouped && parent.querySelector("p.event:nth-of-type(3)")) {
       var div = document.createElement("div");
--- a/mail/components/im/modules/chatHandler.jsm
+++ b/mail/components/im/modules/chatHandler.jsm
@@ -1,47 +1,47 @@
 /* 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/. */
 
 this.EXPORTED_SYMBOLS = ["allContacts", "onlineContacts", "ChatCore"];
 
-ChromeUtils.import("resource:///modules/imServices.jsm");
-ChromeUtils.import("resource:///modules/iteratorUtils.jsm");
+const { Services } = ChromeUtils.import("resource:///modules/imServices.jsm", null);
+const { fixIterator } = ChromeUtils.import("resource:///modules/iteratorUtils.jsm", null);
 ChromeUtils.import("resource:///modules/MailServices.jsm");
 
 var allContacts = {};
 var onlineContacts = {};
 
 var ChatCore = {
   initialized: false,
   _initializing: false,
-  init: function() {
+  init() {
     if (this._initializing)
       return;
     this._initializing = true;
 
-    ChromeUtils.import("resource:///modules/index_im.js");
+    ChromeUtils.import("resource:///modules/index_im.jsm", null);
 
     Services.obs.addObserver(this, "browser-request");
     Services.obs.addObserver(this, "contact-signed-on");
     Services.obs.addObserver(this, "contact-signed-off");
     Services.obs.addObserver(this, "contact-added");
     Services.obs.addObserver(this, "contact-removed");
 
     // The initialization of the im core may trigger a master password prompt,
     // so wrap it with the async prompter service. Note this service already
     // waits for the asynchronous initialization of the password service.
     Cc["@mozilla.org/messenger/msgAsyncPrompter;1"]
       .getService(Ci.nsIMsgAsyncPrompter)
       .queueAsyncAuthPrompt("im", false, {
-      onPromptStartAsync: function(callback) {
+      onPromptStartAsync(callback) {
         callback.onAuthResult(this.onPromptStart());
       },
-      onPromptStart: function() {
+      onPromptStart() {
         Services.core.init();
 
         // Find the accounts that exist in the im account service but
         // not in nsMsgAccountManager. They have probably been lost if
         // the user has used an older version of Thunderbird on a
         // profile with IM accounts. See bug 736035.
         let accountsById = {};
         for (let account of fixIterator(Services.accounts.getAccounts()))
@@ -63,33 +63,33 @@ var ChatCore = {
           let acc = mgr.createAccount();
           // Avoid new folder notifications.
           inServer.valid = false;
           acc.incomingServer = inServer;
           inServer.valid = true;
           mgr.notifyServerLoaded(inServer);
         }
 
-        Services.tags.getTags().forEach(function (aTag) {
+        Services.tags.getTags().forEach(function(aTag) {
           aTag.getContacts().forEach(function(aContact) {
             let name = aContact.preferredBuddy.normalizedName;
             allContacts[name] = aContact;
           });
         });
 
         ChatCore.initialized = true;
         Services.obs.notifyObservers(null, "chat-core-initialized");
         ChatCore._initializing = false;
         return true;
       },
-      onPromptAuthAvailable: function() { },
-      onPromptCanceled: function() { }
+      onPromptAuthAvailable() {},
+      onPromptCanceled() {},
     });
   },
-  observe: function(aSubject, aTopic, aData) {
+  observe(aSubject, aTopic, aData) {
     if (aTopic == "browser-request") {
       Services.ww.openWindow(null,
                              "chrome://chat/content/browserRequest.xul",
                              null, "chrome", aSubject);
       return;
     }
 
     if (aTopic == "contact-signed-on") {
@@ -104,12 +104,11 @@ var ChatCore = {
 
     if (aTopic == "contact-added") {
       allContacts[aSubject.preferredBuddy.normalizedName] = aSubject;
       return;
     }
 
     if (aTopic == "contact-removed") {
       delete allContacts[aSubject.preferredBuddy.normalizedName];
-      return;
     }
-  }
+  },
 };
--- a/mail/components/im/modules/chatNotifications.jsm
+++ b/mail/components/im/modules/chatNotifications.jsm
@@ -1,52 +1,51 @@
 /* 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/. */
 
 this.EXPORTED_SYMBOLS = ["Notifications"];
 
-ChromeUtils.import("resource:///modules/imServices.jsm");
-ChromeUtils.import("resource:///modules/hiddenWindow.jsm");
+const { Services } = ChromeUtils.import("resource:///modules/imServices.jsm", null);
 ChromeUtils.import("resource:///modules/StringBundle.js");
 ChromeUtils.import("resource://gre/modules/AppConstants.jsm");
 ChromeUtils.import("resource://gre/modules/PluralForm.jsm");
 ChromeUtils.import("resource://gre/modules/Timer.jsm");
 Cu.importGlobalProperties(["DOMParser"]);
 
 // Time in seconds: it is the minimum time of inactivity
 // needed to show the bundled notification.
 var kTimeToWaitForMoreMsgs = 3;
 
 var Notifications = {
-  get ellipsis () {
+  get ellipsis() {
     let ellipsis = "[\u2026]";
 
     try {
       ellipsis =
         Services.prefs.getComplexValue("intl.ellipsis",
                                        Ci.nsIPrefLocalizedString).data;
-    } catch (e) { }
+    } catch (e) {}
     return ellipsis;
   },
 
   // Holds the first direct message of a bundle while we wait for further
   // messages from the same sender to arrive.
   _heldMessage: null,
   // Number of messages to be bundled in the notification (excluding
   // _heldMessage).
   _msgCounter: 0,
   // Time the last message was received.
   _lastMessageTime: 0,
   // Sender of the last message.
   _lastMessageSender: null,
   // timeout Id for the set timeout for showing notification.
   _timeoutId: null,
 
-  _showMessageNotification: function(aMessage, aCounter = 0) {
+  _showMessageNotification(aMessage, aCounter = 0) {
     // We are about to show the notification, so let's play the notification sound.
     // We play the sound if the user is away from TB window or even away from chat tab.
     let win = Services.wm.getMostRecentWindow("mail:3pane");
     if (!Services.focus.activeWindow ||
         win.document.getElementById("tabmail").currentTabInfo.mode.name != "chat")
       Services.obs.notifyObservers(aMessage, "play-chat-notification-sound");
 
     // If TB window has focus, there's no need to show the notification..
@@ -143,23 +142,23 @@ var Notifications = {
           .activateApplication(true);
       }
     });
 
     this._heldMessage = null;
     this._msgCounter = 0;
   },
 
-  init: function() {
+  init() {
     Services.obs.addObserver(Notifications, "new-directed-incoming-message");
     Services.obs.addObserver(Notifications, "alertclickcallback");
   },
 
   _notificationPrefName: "mail.chat.show_desktop_notifications",
-  observe: function(aSubject, aTopic, aData) {
+  observe(aSubject, aTopic, aData) {
     if (aTopic == "new-directed-incoming-message" &&
         Services.prefs.getBoolPref(this._notificationPrefName)) {
       // If this is the first message, we show the notification and
       // store the sender's name.
       let sender = aSubject.who || aSubject.alias;
       if (this._lastMessageSender == null) {
         this._lastMessageSender = sender;
         this._lastMessageTime = aSubject.time;
@@ -188,14 +187,14 @@ var Notifications = {
         if (!this._heldMessage)
           this._heldMessage = aSubject;
         else
           this._msgCounter++;
 
         clearTimeout(this._timeoutId);
         this._timeoutId = setTimeout(function() {
             Notifications._showMessageNotification(Notifications._heldMessage,
-                                                   Notifications._msgCounter)
+                                                   Notifications._msgCounter);
           }, kTimeToWaitForMoreMsgs * 1000);
       }
     }
-  }
+  },
 };
rename from mail/components/im/modules/index_im.js
rename to mail/components/im/modules/index_im.jsm
--- a/mail/components/im/modules/index_im.js
+++ b/mail/components/im/modules/index_im.jsm
@@ -1,26 +1,31 @@
 /* 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/. */
 
 this.EXPORTED_SYMBOLS = [];
 
 var CC = Components.Constructor;
 
-ChromeUtils.import("resource:///modules/gloda/public.js");
-ChromeUtils.import("resource:///modules/gloda/datamodel.js");
-ChromeUtils.import("resource:///modules/gloda/indexer.js");
-ChromeUtils.import("resource:///modules/imServices.jsm");
-ChromeUtils.import("resource:///modules/imXPCOMUtils.jsm");
-ChromeUtils.import("resource:///modules/iteratorUtils.jsm");
+const { Gloda } = ChromeUtils.import("resource:///modules/gloda/public.js", null);
+const { GlodaAccount } = ChromeUtils.import("resource:///modules/gloda/datamodel.js", null);
+const {
+  GlodaIndexer,
+  IndexingJob,
+} = ChromeUtils.import("resource:///modules/gloda/indexer.js", null);
+const { fixIterator } = ChromeUtils.import("resource:///modules/iteratorUtils.jsm", null);
+const { Services } = ChromeUtils.import("resource:///modules/imServices.jsm", null);
 ChromeUtils.import("resource:///modules/MailServices.jsm");
 ChromeUtils.import("resource://gre/modules/FileUtils.jsm");
-ChromeUtils.import("resource://gre/modules/NetUtil.jsm");
-ChromeUtils.import("resource://gre/modules/Services.jsm");
+ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
+const {
+  clearTimeout,
+  setTimeout,
+} = ChromeUtils.import("resource://gre/modules/Timer.jsm", null);
 
 ChromeUtils.defineModuleGetter(this, "OS", "resource://gre/modules/osfile.jsm");
 ChromeUtils.defineModuleGetter(this, "Task", "resource://gre/modules/Task.jsm");
 ChromeUtils.defineModuleGetter(this, "AsyncShutdown",
                                "resource://gre/modules/AsyncShutdown.jsm");
 ChromeUtils.defineModuleGetter(this, "GlodaDatastore",
                                "resource:///modules/gloda/datastore.js");
 
@@ -38,18 +43,17 @@ var ScriptableInputStream = CC("@mozilla
 var kIndexingDelay = 5000; // in milliseconds
 
 XPCOMUtils.defineLazyGetter(this, "MailFolder", () =>
   Cc["@mozilla.org/rdf/resource-factory;1?name=mailbox"].createInstance(Ci.nsIMsgFolder)
 );
 
 var gIMAccounts = {};
 
-function GlodaIMConversation(aTitle, aTime, aPath, aContent)
-{
+function GlodaIMConversation(aTitle, aTime, aPath, aContent) {
   // grokNounItem from gloda.js puts automatically the values of all
   // JS properties in the jsonAttributes magic attribute, except if
   // they start with _, so we put the values in _-prefixed properties,
   // and have getters in the prototype.
   this._title = aTitle;
   this._time = aTime;
   this._path = aPath;
   this._content = aContent;
@@ -105,41 +109,41 @@ GlodaIMConversation.prototype = {
   get tags() { return []; },
   get starred() { return false; },
   get attachmentNames() { return null; },
   get indexedBodyText() { return this._content; },
   get read() { return true; },
   get folder() { return Gloda.IGNORE_FACET; },
 
   // for glodaFacetView.js _removeDupes
-  get headerMessageID() { return this.id; }
+  get headerMessageID() { return this.id; },
 };
 
 // FIXME
 var WidgetProvider = {
   providerName: "widget",
-  process: function*() {
-    //XXX What is this supposed to do?
+  * process() {
+    // XXX What is this supposed to do?
     yield Gloda.kWorkDone;
-  }
+  },
 };
 
 var IMConversationNoun = {
   name: "im-conversation",
   clazz: GlodaIMConversation,
   allowsArbitraryAttrs: true,
   tableName: "imConversations",
   schema: {
-    columns: [['id', 'INTEGER PRIMARY KEY'],
-              ['title', 'STRING'],
-              ['time', 'NUMBER'],
-              ['path', 'STRING']
+    columns: [["id", "INTEGER PRIMARY KEY"],
+              ["title", "STRING"],
+              ["time", "NUMBER"],
+              ["path", "STRING"],
              ],
-    fulltextColumns: [['content', 'STRING']]
-  }
+    fulltextColumns: [["content", "STRING"]],
+  },
 };
 Gloda.defineNoun(IMConversationNoun);
 
 // Needs to be set after calling defineNoun, otherwise it's replaced
 // by databind.js' implementation.
 IMConversationNoun.objFromRow = function(aRow) {
   // Row columns are:
   // 0 id
@@ -163,100 +167,100 @@ Gloda.defineAttribute({
   provider: WidgetProvider, extensionName: EXT_NAME,
   attributeType: Gloda.kAttrFundamental,
   attributeName: "time",
   singular: true,
   special: Gloda.kSpecialColumn,
   specialColumnName: "time",
   subjectNouns: [IMConversationNoun.id],
   objectNoun: Gloda.NOUN_NUMBER,
-  canQuery: true
+  canQuery: true,
 });
 Gloda.defineAttribute({
   provider: WidgetProvider, extensionName: EXT_NAME,
   attributeType: Gloda.kAttrFundamental,
   attributeName: "title",
   singular: true,
   special: Gloda.kSpecialString,
   specialColumnName: "title",
   subjectNouns: [IMConversationNoun.id],
   objectNoun: Gloda.NOUN_STRING,
-  canQuery: true
+  canQuery: true,
 });
 Gloda.defineAttribute({
   provider: WidgetProvider, extensionName: EXT_NAME,
   attributeType: Gloda.kAttrFundamental,
   attributeName: "path",
   singular: true,
   special: Gloda.kSpecialString,
   specialColumnName: "path",
   subjectNouns: [IMConversationNoun.id],
   objectNoun: Gloda.NOUN_STRING,
-  canQuery: true
+  canQuery: true,
 });
 
 // --- fulltext attributes
 Gloda.defineAttribute({
   provider: WidgetProvider, extensionName: EXT_NAME,
   attributeType: Gloda.kAttrFundamental,
   attributeName: "content",
   singular: true,
   special: Gloda.kSpecialFulltext,
   specialColumnName: "content",
   subjectNouns: [IMConversationNoun.id],
   objectNoun: Gloda.NOUN_FULLTEXT,
-  canQuery: true
+  canQuery: true,
 });
 
 // -- fulltext search helper
 // fulltextMatches.  Match over message subject, body, and attachments
 // @testpoint gloda.noun.message.attr.fulltextMatches
 this._attrFulltext = Gloda.defineAttribute({
   provider: WidgetProvider,
   extensionName: EXT_NAME,
   attributeType: Gloda.kAttrDerived,
   attributeName: "fulltextMatches",
   singular: true,
   special: Gloda.kSpecialFulltext,
   specialColumnName: "imConversationsText",
   subjectNouns: [IMConversationNoun.id],
-  objectNoun: Gloda.NOUN_FULLTEXT
+  objectNoun: Gloda.NOUN_FULLTEXT,
 });
 // For facet.js DateFaceter
 Gloda.defineAttribute({
   provider: WidgetProvider, extensionName: EXT_NAME,
   attributeType: Gloda.kAttrDerived,
   attributeName: "date",
   singular: true,
   special: Gloda.kSpecialColumn,
   subjectNouns: [IMConversationNoun.id],
   objectNoun: Gloda.NOUN_NUMBER,
   facet: {
-    type: "date"
+    type: "date",
   },
-  canQuery: true
+  canQuery: true,
 });
 
 var GlodaIMIndexer = {
   name: "index_im",
   cacheVersion: 1,
-  enable: function() {
+  enable() {
     Services.obs.addObserver(this, "conversation-closed");
     Services.obs.addObserver(this, "new-ui-conversation");
     Services.obs.addObserver(this, "ui-conversation-closed");
 
     // The shutdown blocker ensures pending saves happen even if the app
     // gets shut down before the timer fires.
     if (this._shutdownBlockerAdded)
       return;
     this._shutdownBlockerAdded = true;
     AsyncShutdown.profileBeforeChange.addBlocker("GlodaIMIndexer cache save",
       () => {
         if (!this._cacheSaveTimer)
-          return;
+          return Promise.resolve();
         clearTimeout(this._cacheSaveTimer);
         return this._saveCacheNow();
       });
 
     this._knownFiles = {};
 
     let dir = FileUtils.getFile("ProfD", ["logs"]);
     if (!dir.exists() || !dir.isDirectory())
@@ -289,17 +293,17 @@ var GlodaIMIndexer = {
 
     this.cacheVersion = data.version;
 
     // If there was no version set on the cache, there is a chance that the index
     // is affected by bug 1069845. fixEntriesWithAbsolutePaths() sets the version to 1.
     if (!this.cacheVersion)
       this.fixEntriesWithAbsolutePaths();
   },
-  disable: function() {
+  disable() {
     Services.obs.removeObserver(this, "conversation-closed");
     Services.obs.removeObserver(this, "new-ui-conversation");
     Services.obs.removeObserver(this, "ui-conversation-closed");
   },
 
   /* _knownFiles is a tree whose leaves are the last modified times of
    * log files when they were last indexed.
    * Each level of the tree is stored as an object. The root node is an
@@ -310,28 +314,28 @@ var GlodaIMIndexer = {
    * protocol names -> account names -> conv names  -> file names -> last modified time
    * convObj maps ALL previously indexed log files of a chat buddy or MUC to
    * their last modified times. Note that gloda knows nothing about log grouping
    * done by logger.js.
    */
   _knownFiles: {},
   _cacheSaveTimer: null,
   _shutdownBlockerAdded: false,
-  _scheduleCacheSave: function() {
+  _scheduleCacheSave() {
     if (this._cacheSaveTimer)
       return;
     this._cacheSaveTimer = setTimeout(this._saveCacheNow, 5000);
   },
-  _saveCacheNow: function() {
+  _saveCacheNow() {
     GlodaIMIndexer._cacheSaveTimer = null;
 
     let data = {
       knownFiles: GlodaIMIndexer._knownFiles,
       datastoreID: Gloda.datastoreID,
-      version: GlodaIMIndexer.cacheVersion
+      version: GlodaIMIndexer.cacheVersion,
     };
 
     // Asynchronously copy the data to the file.
     let path = OS.Path.join(OS.Constants.Path.profileDir, "logs", kCacheFileName);
     return OS.File.writeAtomic(path, JSON.stringify(data),
                                {encoding: "utf-8", tmpPath: path + ".tmp"})
              .catch(aError => Cu.reportError("Failed to write cache file: " + aError));
   },
@@ -340,51 +344,51 @@ var GlodaIMIndexer = {
   // Promise queue for indexing jobs. The next indexing job is queued using this
   // promise's then() to ensure we only load logs for one conv at a time.
   _indexingJobPromise: null,
   // Maps a conv id to the function that resolves the promise representing the
   // ongoing indexing job on it. This is called from indexIMConversation when it
   // finishes and will trigger the next queued indexing job.
   _indexingJobCallbacks: new Map(),
 
-  _scheduleIndexingJob: function(aConversation) {
+  _scheduleIndexingJob(aConversation) {
     let convId = aConversation.id;
 
     // If we've already scheduled this conversation to be indexed, let's
     // not repeat.
     if (!(convId in this._knownConversations)) {
       this._knownConversations[convId] = {
         id: convId,
         scheduledIndex: null,
         logFileCount: null,
-        convObj: {}
+        convObj: {},
       };
     }
 
     if (!this._knownConversations[convId].scheduledIndex) {
       // Ok, let's schedule the job.
       this._knownConversations[convId].scheduledIndex = setTimeout(
         this._beginIndexingJob.bind(this, aConversation),
         kIndexingDelay);
     }
   },
 
-  _beginIndexingJob: function(aConversation) {
+  _beginIndexingJob(aConversation) {
     let convId = aConversation.id;
 
     // In the event that we're triggering this indexing job manually, without
     // bothering to schedule it (for example, when a conversation is closed),
     // we give the conversation an entry in _knownConversations, which would
     // normally have been done in _scheduleIndexingJob.
     if (!(convId in this._knownConversations)) {
       this._knownConversations[convId] = {
         id: convId,
         scheduledIndex: null,
         logFileCount: null,
-        convObj: {}
+        convObj: {},
       };
     }
 
     let conv = this._knownConversations[convId];
     Task.spawn(function* () {
       // We need to get the log files every time, because a new log file might
       // have been started since we last got them.
       let logFiles =
@@ -505,52 +509,50 @@ var GlodaIMIndexer = {
 
       // We only want to schedule an indexing job if this message is
       // immediately visible to the user. We figure this out by finding
       // the unread message count on the associated UIConversation for this
       // message. If the unread count is 0, we know that the message has been
       // displayed to the user.
       if (uiConv.unreadIncomingMessageCount == 0)
         this._scheduleIndexingJob(conv);
-
-      return;
     }
   },
 
 
   /* If there is an existing gloda conversation for the given path,
    * find its id.
    */
-  _getIdFromPath: function(aPath) {
+  _getIdFromPath(aPath) {
     let selectStatement = GlodaDatastore._createAsyncStatement(
       "SELECT id FROM imConversations WHERE path = ?1");
     selectStatement.bindByIndex(0, aPath);
     let id;
     return new Promise((resolve, reject) => {
       selectStatement.executeAsync({
         handleResult: aResultSet => {
           let row = aResultSet.getNextRow();
           if (!row)
             return;
           if (id || aResultSet.getNextRow()) {
             Cu.reportError("Warning: found more than one gloda conv id for " +
-              aPath  + "\n");
+              aPath + "\n");
           }
           id = id || row.getInt64(0); // We use the first found id.
         },
         handleError: aError =>
           Cu.reportError("Error finding gloda id from path:\n" + aError),
-        handleCompletion: () => {resolve(id);}
+        handleCompletion: () => { resolve(id); },
       });
     });
   },
 
   // Get the path of a log file relative to the logs directory - the last 4
   // components of the path.
-  _getRelativePath: function(aLogPath) {
+  _getRelativePath(aLogPath) {
     return OS.Path.split(aLogPath).components.slice(-4).join("/");
   },
 
   /* aGlodaConv is an optional inout param that lets the caller save and reuse
    * the GlodaIMConversation instance created when the conversation is indexed
    * the first time. After a conversation is indexed for the first time,
    * the GlodaIMConversation instance has its id property set to the row id of
    * the conversation in the database. This id is required to later update the
@@ -582,46 +584,45 @@ var GlodaIMIndexer = {
                            let prefix = who ? who + ": " : "";
                            return prefix + MailFolder.convertMsgSnippetToPlainText(m.message);
                          })
                          .join("\n\n");
     let glodaConv;
     if (aGlodaConv && aGlodaConv.value) {
       glodaConv = aGlodaConv.value;
       glodaConv._content = content;
-    }
-    else {
+    } else {
       let relativePath = this._getRelativePath(aLogPath);
       glodaConv = new GlodaIMConversation(logConv.title, log.time, relativePath, content);
       // If we've indexed this file before, we need the id of the existing
       // gloda conversation so that the existing entry gets updated. This can
       // happen if the log sweep detects that the last messages in an open
       // chat were not in fact indexed before that session was shut down.
       let id = yield this._getIdFromPath(relativePath);
       if (id)
         glodaConv.id = id;
       if (aGlodaConv)
         aGlodaConv.value = glodaConv;
     }
 
     if (!aCache)
-      throw("indexIMConversation called without aCache parameter.");
+      throw "indexIMConversation called without aCache parameter.";
     let isNew = !Object.prototype.hasOwnProperty.call(aCache, fileName) && !glodaConv.id;
     let rv = aCallbackHandle.pushAndGo(
       Gloda.grokNounItem(glodaConv, {}, true, isNew, aCallbackHandle));
 
     if (!aLastModifiedTime)
       Cu.reportError("indexIMConversation called without lastModifiedTime parameter.");
     aCache[fileName] = aLastModifiedTime || 1;
     this._scheduleCacheSave();
 
     return rv;
   }),
 
-  _worker_indexIMConversation: function*(aJob, aCallbackHandle) {
+  * _worker_indexIMConversation(aJob, aCallbackHandle) {
     let glodaConv = {};
     let existingGlodaConv = aJob.conversation.glodaConv;
     if (existingGlodaConv &&
         existingGlodaConv.path == this._getRelativePath(aJob.path))
       glodaConv.value = aJob.conversation.glodaConv;
 
     // indexIMConversation may initiate an async grokNounItem sub-job.
     this.indexIMConversation(aCallbackHandle, aJob.path, aJob.lastModifiedTime,
@@ -635,17 +636,17 @@ var GlodaIMIndexer = {
     this._indexingJobCallbacks.get(aJob.conversation.id)();
     this._indexingJobCallbacks.delete(aJob.conversation.id);
     this._indexingJobPromise = null;
     aJob.conversation.indexPending = false;
     aJob.conversation.glodaConv = glodaConv.value;
     yield Gloda.kWorkDone;
   },
 
-  _worker_logsFolderSweep: function*(aJob) {
+  * _worker_logsFolderSweep(aJob) {
     let dir = FileUtils.getFile("ProfD", ["logs"]);
     if (!dir.exists() || !dir.isDirectory()) {
       // If the folder does not exist, then we are done.
       yield GlodaIndexer.kWorkDone;
     }
 
     // Sweep the logs directory for log files, adding any new entries to the
     // _knownFiles tree as we traverse.
@@ -681,17 +682,17 @@ var GlodaIMIndexer = {
           GlodaIndexer.indexJob(job);
         }
       }
     }
 
     yield GlodaIndexer.kWorkDone;
   },
 
-  _worker_convFolderSweep: function*(aJob, aCallbackHandle) {
+  * _worker_convFolderSweep(aJob, aCallbackHandle) {
     let folder = aJob.folder;
 
     let sessions = folder.directoryEntries;
     while (sessions.hasMoreElements()) {
       let file = sessions.getNext().QueryInterface(Ci.nsIFile);
       let fileName = file.leafName;
       if (!file.isFile() || !file.isReadable() || !fileName.endsWith(".json") ||
           (Object.prototype.hasOwnProperty.call(aJob.convObj, fileName) &&
@@ -705,37 +706,31 @@ var GlodaIMIndexer = {
       // until callbackDriver() is called above.
       yield Gloda.kWorkAsync;
     }
     yield Gloda.kWorkDone;
   },
 
   get workers() {
     return [
-      ["indexIMConversation", {
-         worker: this._worker_indexIMConversation
-       }],
-      ["logsFolderSweep", {
-         worker: this._worker_logsFolderSweep
-       }],
-      ["convFolderSweep", {
-         worker: this._worker_convFolderSweep
-       }]
+      ["indexIMConversation", { worker: this._worker_indexIMConversation }],
+      ["logsFolderSweep", { worker: this._worker_logsFolderSweep }],
+      ["convFolderSweep", { worker: this._worker_convFolderSweep }],
     ];
   },
 
-  initialSweep: function() {
+  initialSweep() {
     let job = new IndexingJob("logsFolderSweep", null);
     GlodaIndexer.indexJob(job);
   },
 
   // Due to bug 1069845, some logs were indexed against their full paths instead
   // of their path relative to the logs directory. These entries are updated to
   // use relative paths below.
-  fixEntriesWithAbsolutePaths: function() {
+  fixEntriesWithAbsolutePaths() {
     let store = GlodaDatastore;
     let selectStatement = store._createAsyncStatement(
       "SELECT id, path FROM imConversations");
     let updateStatement = store._createAsyncStatement(
       "UPDATE imConversations SET path = ?1 WHERE id = ?2");
 
     store._beginTransaction();
     selectStatement.executeAsync({
@@ -752,29 +747,29 @@ var GlodaIMIndexer = {
           if (pathComponents.length > 4) {
             updateStatement.bindByIndex(1, row.getInt64(0)); // id
             updateStatement.bindByIndex(0,
               pathComponents.slice(-4).join("/")); // Last 4 path components
             updateStatement.executeAsync({
               handleResult: () => {},
               handleError: aError =>
                 Cu.reportError("Error updating bad entry:\n" + aError),
-              handleCompletion: () => {}
+              handleCompletion: () => {},
             });
           }
         }
       },
 
       handleError: aError =>
         Cu.reportError("Error looking for bad entries:\n" + aError),
 
       handleCompletion: () => {
         store.runPostCommit(() => {
           this.cacheVersion = 1;
           this._scheduleCacheSave();
         });
         store._commitTransaction();
-      }
+      },
     });
-  }
+  },
 };
 
 GlodaIndexer.registerIndexer(GlodaIMIndexer);
rename from mail/components/im/modules/search_im.js
rename to mail/components/im/modules/search_im.jsm
--- a/mail/components/im/modules/search_im.js
+++ b/mail/components/im/modules/search_im.jsm
@@ -1,28 +1,28 @@
 /* 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/. */
 
 this.EXPORTED_SYMBOLS = ["GlodaIMSearcher"];
 
 ChromeUtils.import("resource://gre/modules/Services.jsm");
-ChromeUtils.import("resource:///modules/gloda/public.js");
+const { Gloda } = ChromeUtils.import("resource:///modules/gloda/public.js", null);
 
 /**
  * How much time boost should a 'score point' amount to?  The authoritative,
  *  incontrivertible answer, across all time and space, is a week.
  *  Note that gloda stores conversation timestamps in seconds.
  */
-var FUZZSCORE_TIMESTAMP_FACTOR = 60 * 60 * 24 * 7;
+// var FUZZSCORE_TIMESTAMP_FACTOR = 60 * 60 * 24 * 7;
 
-var RANK_USAGE =
-  "glodaRank(matchinfo(imConversationsText), 1.0, 2.0, 2.0, 1.5, 1.5)";
+// var RANK_USAGE =
+//   "glodaRank(matchinfo(imConversationsText), 1.0, 2.0, 2.0, 1.5, 1.5)";
 
-var DASCORE ="imConversations.time";
+var DASCORE = "imConversations.time";
 //  "(((" + RANK_USAGE + ") * " +
 //    FUZZSCORE_TIMESTAMP_FACTOR +
 //   ") + imConversations.time)";
 
 /**
  * A new optimization decision we are making is that we do not want to carry
  *  around any data in our ephemeral tables that is not used for whittling the
  *  result set.  The idea is that the btree page cache or OS cache is going to
@@ -83,18 +83,17 @@ var NUEVO_FULLTEXT_SQL =
 
 function identityFunc(x) {
   return x;
 }
 
 function oneLessMaxZero(x) {
   if (x <= 1)
     return 0;
-  else
-    return x - 1;
+  return x - 1;
 }
 
 function reduceSum(accum, curValue) {
   return accum + curValue;
 }
 
 /*
  * Columns are: body, subject, attachment names, author, recipients
@@ -141,19 +140,19 @@ function scoreOffsets(aMessage, aContext
                              termTemplate.concat(),
                              termTemplate.concat(),
                              termTemplate.concat()];
 
   // we need a friendlyParseInt because otherwise the radix stuff happens
   //  because of the extra arguments map parses.  curse you, map!
   let offsetNums =
     aContext.stashedColumns[aMessage.id][0].split(" ").map(x => parseInt(x));
-  for (let i=0; i < offsetNums.length; i += 4) {
+  for (let i = 0; i < offsetNums.length; i += 4) {
     let columnIndex = offsetNums[i];
-    let termIndex = offsetNums[i+1];
+    let termIndex = offsetNums[i + 1];
     columnTermIncidence[columnIndex][termIndex]++;
   }
 
   for (let iColumn = 0; iColumn < COLUMN_ALL_MATCH_SCORES.length; iColumn++) {
     let termIncidence = columnTermIncidence[iColumn];
     // bestow all match credit
     if (termIncidence.every(identityFunc))
       score += COLUMN_ALL_MATCH_SCORES[iColumn];
@@ -236,30 +235,30 @@ GlodaIMSearcher.prototype = {
 
       let spaceIndex = aSearchString.indexOf(" ");
       if (spaceIndex == -1) {
         addTerm(aSearchString);
         break;
       }
 
       addTerm(aSearchString.substring(0, spaceIndex));
-      aSearchString = aSearchString.substring(spaceIndex+1);
+      aSearchString = aSearchString.substring(spaceIndex + 1);
     }
 
     return terms;
   },
 
   buildFulltextQuery: function GlodaIMSearcher_buildFulltextQuery() {
     let query = Gloda.newQuery(Gloda.lookupNoun("im-conversation"), {
       noMagic: true,
       explicitSQL: NUEVO_FULLTEXT_SQL,
       limitClauseAlreadyIncluded: true,
       // osets is 0-based column number 4 (volatile to column changes)
       // save the offset column for extra analysis
-      stashColumns: [6]
+      stashColumns: [6],
     });
 
     let fulltextQueryString = "";
 
     for (let [iTerm, term] of this.fulltextTerms.entries()) {
       if (iTerm)
         fulltextQueryString += this.andTerms ? " " : " OR ";
 
@@ -296,24 +295,24 @@ GlodaIMSearcher.prototype = {
 
     this.query = this.buildFulltextQuery();
     this.collection = this.query.getCollection(this, aData);
     this.completed = false;
 
     return this.collection;
   },
 
-  sortBy: '-dascore',
+  sortBy: "-dascore",
 
   onItemsAdded: function GlodaIMSearcher_onItemsAdded(aItems, aCollection) {
     let newScores = Gloda.scoreNounItems(
       aItems,
       {
         terms: this.fulltextTerms,
-        stashedColumns: aCollection.stashedColumns
+        stashedColumns: aCollection.stashedColumns,
       },
       [scoreOffsets]);
     if (this.scores)
       this.scores = this.scores.concat(newScores);
     else
       this.scores = newScores;
 
     if (this.listener)
@@ -328,10 +327,10 @@ GlodaIMSearcher.prototype = {
                                                           aCollection) {
     if (this.listener)
       this.listener.onItemsRemoved(aItems, aCollection);
   },
   onQueryCompleted: function GlodaIMSearcher_onQueryCompleted(aCollection) {
     this.completed = true;
     if (this.listener)
       this.listener.onQueryCompleted(aCollection);
-  }
+  },
 };
--- a/mail/components/im/moz.build
+++ b/mail/components/im/moz.build
@@ -7,18 +7,18 @@ EXTRA_COMPONENTS += [
     'im.manifest',
     'imIncomingServer.js',
     'imProtocolInfo.js',
 ]
 
 EXTRA_JS_MODULES += [
     'modules/chatHandler.jsm',
     'modules/chatNotifications.jsm',
-    'modules/index_im.js',
-    'modules/search_im.js',
+    'modules/index_im.jsm',
+    'modules/search_im.jsm',
 ]
 
 JAR_MANIFESTS += ['jar.mn']
 
 JS_PREFERENCE_FILES += [
     'all-im.js',
 ]
 
rename from mail/components/im/smileys/theme.js
rename to mail/components/im/smileys/theme.json