Bug 1531312 - [de-xbl] convert account and buttons bindings to custom element. r=mkmelin
authorKhushil Mistry <khushil324@gmail.com>
Fri, 22 Mar 2019 06:43:00 +0100
changeset 26151 34cf7d8564ad
parent 26150 fc8015e62158
child 26152 223c952fa567
push id15696
push usermozilla@jorgk.com
push dateSat, 23 Mar 2019 08:18:55 +0000
treeherdercomm-central@34cf7d8564ad [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmkmelin
bugs1531312
Bug 1531312 - [de-xbl] convert account and buttons bindings to custom element. r=mkmelin
chat/content/account.xml
chat/content/accounts.css
chat/content/chat-account-richlistitem.js
chat/content/jar.mn
mail/components/im/content/imAccounts.js
mail/components/im/content/imAccounts.xul
deleted file mode 100644
--- a/chat/content/account.xml
+++ /dev/null
@@ -1,345 +0,0 @@
-<?xml version="1.0"?>
-<!-- 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/. -->
-
-<!-- import-globals-from ../../mail/components/im/content/imAccounts.js -->
-
-<!DOCTYPE bindings [
-  <!ENTITY % accountsDTD SYSTEM "chrome://chat/locale/accounts.dtd">
-  %accountsDTD;
-]>
-
-<bindings id="accountBindings"
-          xmlns="http://www.mozilla.org/xbl"
-          xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
-          xmlns:xbl="http://www.mozilla.org/xbl">
-
-  <binding id="account" extends="chrome://global/content/bindings/richlistbox.xml#richlistitem">
-    <content>
-      <xul:vbox flex="1">
-        <xul:hbox flex="1" align="top">
-          <xul:vbox>
-            <xul:stack xbl:inherits="tooltiptext=protocol">
-              <xul:image xbl:inherits="src=prplicon" class="accountIcon"/>
-              <xul:image class="accountStateIcon"/>
-            </xul:stack>
-            <xul:spacer flex="1"/>
-          </xul:vbox>
-          <xul:vbox flex="1" align="start">
-            <xul:label xbl:inherits="value=name" crop="end" class="accountName"/>
-            <xul:label class="connecting" crop="end" anonid="connecting" value="&account.connecting;"/>
-            <xul:label class="connected" crop="end" anonid="connected"/>
-            <xul:label class="disconnecting" crop="end" value="&account.disconnecting;"/>
-            <xul:label class="disconnected" crop="end" value="&account.disconnected;"/>
-            <xul:description class="error" anonid="error"/>
-            <xul:description class="error" anonid="reconnect"/>
-            <xul:label class="addException text-link"
-                       onclick="gAccountManager.addException()"
-                       data-l10n-id="certmgr-add-exception"/>
-            <xul:spacer flex="1"/>
-          </xul:vbox>
-          <xul:checkbox label="&account.autoSignOn.label;" dir="reverse"
-                        xbl:inherits="checked=autologin" class="autoSignOn"
-                        accesskey="&account.autoSignOn.accesskey;"
-                        oncommand="gAccountManager.autologin()"/>
-        </xul:hbox>
-        <xul:hbox flex="1" class="account-buttons" anonid="buttons"
-                  xbl:inherits="autologin,name"/>
-      </xul:vbox>
-    </content>
-    <implementation>
-      <method name="build">
-        <parameter name="aAccount"/>
-        <body>
-        <![CDATA[
-          this._account = aAccount;
-          this.setAttribute("name", aAccount.name);
-          this.setAttribute("id", aAccount.id);
-          var proto = aAccount.protocol;
-          this.setAttribute("protocol", proto.name);
-          this.setAttribute("prplicon", proto.iconBaseURI + "icon32.png");
-          var state = "Unknown";
-          if (this._account.connected) {
-            state = "connected";
-            this.refreshConnectedLabel();
-          } else if (this._account.disconnected) {
-            state = "disconnected";
-            if (this._account.connectionErrorReason != Ci.prplIAccount.NO_ERROR)
-              this.updateConnectionError();
-            else {
-              this.removeAttribute("error");
-              this.removeAttribute("certError");
-            }
-          } else if (this._account.connecting) {
-            state = "connecting";
-            this.updateConnectionState();
-          } else if (this._account.disconnecting) {
-            state = "connected";
-          }
-          this.setAttribute("state", state);
-          this.autoLogin = aAccount.autoLogin;
-        ]]>
-        </body>
-      </method>
-
-      <method name="updateConnectionState">
-        <body>
-        <![CDATA[
-          var bundle = document.getElementById("accountsBundle");
-          const key = "account.connection.progress";
-          var text = this._account.connectionStateMsg;
-          text = text ? bundle.getFormattedString(key, [text])
-                      : bundle.getString("account.connecting");
-
-          var progress = document.getAnonymousElementByAttribute(this, "anonid",
-                                                                 "connecting");
-          progress.setAttribute("value", text);
-          if (this.reconnectUpdateInterval)
-            this._cancelReconnectTimer();
-
-          this.removeAttribute("certError");
-        ]]>
-        </body>
-      </method>
-
-      <method name="updateConnectionError">
-        <body>
-        <![CDATA[
-          var bundle = document.getElementById("accountsBundle");
-          const key = "account.connection.error";
-          var account = this._account;
-          var text;
-          let errorReason = account.connectionErrorReason;
-          if (errorReason == Ci.imIAccount.ERROR_UNKNOWN_PRPL)
-            text = bundle.getFormattedString(key + "UnknownPrpl",
-                                             [account.protocol.id]);
-          else if (errorReason == Ci.imIAccount.ERROR_MISSING_PASSWORD)
-            text = bundle.getString(key + "EnteringPasswordRequired");
-          else if (errorReason == Ci.imIAccount.ERROR_CRASHED)
-            text = bundle.getString(key + "CrashedAccount");
-          else
-            text = account.connectionErrorMessage;
-
-          if (errorReason != Ci.imIAccount.ERROR_MISSING_PASSWORD)
-            text = bundle.getFormattedString(key, [text]);
-
-          this.setAttribute("error", "true");
-          if ((Ci.imIAccount.ERROR_CERT_NOT_PROVIDED <= errorReason &&
-               errorReason <= Ci.imIAccount.ERROR_CERT_OTHER_ERROR) &&
-              account.prplAccount.connectionTarget)
-            this.setAttribute("certError", "true");
-          var error = document.getAnonymousElementByAttribute(this, "anonid",
-                                                              "error");
-          error.textContent = text;
-
-          var updateReconnect = (function() {
-            var date = Math.round((account.timeOfNextReconnect - Date.now()) / 1000);
-            let reconnect = "";
-            if (date > 0) {
-              let [val1, unit1, val2, unit2] = DownloadUtils.convertTimeUnits(date);
-              if (!val2)
-                reconnect = bundle.getFormattedString("account.reconnectInSingle",
-                                                      [val1, unit1]);
-              else
-                reconnect = bundle.getFormattedString("account.reconnectInDouble",
-                                                      [val1, unit1, val2, unit2]);
-            }
-            document.getAnonymousElementByAttribute(this, "anonid", "reconnect")
-                    .textContent = reconnect;
-            return reconnect;
-          }).bind(this);
-          if (updateReconnect() && !this.reconnectUpdateInterval) {
-            this.setAttribute("reconnectPending", "true");
-            this.reconnectUpdateInterval = setInterval(updateReconnect, 1000);
-            gAccountManager.disableCommandItems();
-          }
-        ]]>
-        </body>
-      </method>
-
-      <method name="refreshConnectedLabel">
-        <body>
-        <![CDATA[
-          var bundle = document.getElementById("accountsBundle");
-          var date =
-            60 * Math.floor((Date.now() - this._account.timeOfLastConnect) / 60000);
-          let value;
-          if (date > 0) {
-            let [val1, unit1, val2, unit2] = DownloadUtils.convertTimeUnits(date);
-            if (!val2)
-              value = bundle.getFormattedString("account.connectedForSingle",
-                                                [val1, unit1]);
-            else
-              value = bundle.getFormattedString("account.connectedForDouble",
-                                                [val1, unit1, val2, unit2]);
-          }
-          else
-            value = bundle.getString("account.connectedForSeconds");
-          this.connectedLabel.value = value;
-        ]]>
-        </body>
-      </method>
-
-      <method name="_cancelReconnectTimer">
-        <body>
-        <![CDATA[
-          this.removeAttribute("reconnectPending");
-          clearInterval(this.reconnectUpdateInterval);
-          delete this.reconnectUpdateInterval;
-          gAccountManager.disableCommandItems();
-        ]]>
-        </body>
-      </method>
-
-      <method name="cancelReconnection">
-        <body>
-        <![CDATA[
-          if (this.reconnectUpdateInterval) {
-            this._cancelReconnectTimer();
-            this._account.cancelReconnection();
-          }
-        ]]>
-        </body>
-      </method>
-
-      <method name="restoreItems">
-        <body>
-        <![CDATA[
-          // Called after a removal and reinsertion of the binding
-          this._buttons = null;
-          this._connectedLabel = null;
-          if (this._account.connected)
-            this.refreshConnectedLabel();
-          else if (this._account.connecting)
-            this.updateConnectionState();
-          else if (this._account.connectionErrorReason != Ci.prplIAccount.NO_ERROR)
-            this.updateConnectionError();
-        ]]>
-        </body>
-      </method>
-
-      <method name="destroy">
-        <body>
-        <![CDATA[
-          // If we have a reconnect timer, stop it:
-          // it will throw errors otherwise (see bug 480).
-          if (!this.reconnectUpdateInterval)
-            return;
-          clearInterval(this.reconnectUpdateInterval);
-          delete this.reconnectUpdateInterval;
-        ]]>
-        </body>
-      </method>
-
-      <property name="autoLogin">
-        <getter>
-        <![CDATA[
-          return this.hasAttribute("autologin");
-        ]]>
-        </getter>
-        <setter>
-        <![CDATA[
-          if (val)
-            this.setAttribute("autologin", "true");
-          else
-            this.removeAttribute("autologin");
-          if (this._account.autoLogin != val)
-            this._account.autoLogin = val;
-          return val;
-        ]]>
-        </setter>
-      </property>
-
-      <!-- override the default accessible name -->
-      <property name="label" onget="return this.getAttribute('name');"/>
-
-      <property name="account" onget="return this._account;"/>
-
-      <property name="connectedLabel">
-        <getter>
-        <![CDATA[
-          if (!this._connectedLabel)
-            this._connectedLabel =
-              document.getAnonymousElementByAttribute(this, "anonid", "connected");
-          return this._connectedLabel;
-        ]]>
-        </getter>
-      </property>
-
-      <property name="buttons">
-        <getter>
-        <![CDATA[
-          if (!this._buttons)
-            this._buttons =
-              document.getAnonymousElementByAttribute(this, "anonid", "buttons");
-          return this._buttons;
-        ]]>
-        </getter>
-      </property>
-    </implementation>
-    <handlers>
-      <handler event="dblclick">
-      <![CDATA[
-        if (event.button == 0) {
-          // If we double clicked on a widget that has already done
-          // something with the first click, we should ignore the event
-          var localName = event.originalTarget.localName;
-          if (localName != "button" && localName != "checkbox")
-            this.buttons.proceedDefaultAction();
-        }
-
-        // Prevent from loading an account wizzard
-        event.stopPropagation();
-      ]]>
-      </handler>
-    </handlers>
-  </binding>
-
-  <binding id="buttons">
-    <content>
-      <xul:button class="disconnectButton"
-                  command="cmd_disconnect"
-                  anonid="disconnect"/>
-      <xul:button class="connectButton"
-                  command="cmd_connect"
-                  anonid="connect"/>
-      <xul:spacer flex="1"/>
-      <xul:button command="cmd_edit"/>
-    </content>
-    <implementation>
-      <property name="activeButton" readonly="true">
-        <getter>
-        <![CDATA[
-          let action =
-            document.getBindingParent(this).account.disconnected ? "connect"
-                                                                 : "disconnect";
-          return document.getAnonymousElementByAttribute(this, "anonid", action);
-        ]]>
-        </getter>
-      </property>
-
-      <method name="setFocus">
-        <body>
-        <![CDATA[
-          let focusTarget = this.activeButton;
-          let accountName = this.getAttribute("name");
-          focusTarget.setAttribute("aria-label",
-                                   focusTarget.label + " " + accountName);
-          if (focusTarget.disabled)
-            focusTarget = document.getElementById("accountlist");
-          focusTarget.focus();
-        ]]>
-        </body>
-      </method>
-
-      <method name="proceedDefaultAction">
-        <body>
-        <![CDATA[
-          this.activeButton.click();
-        ]]>
-        </body>
-      </method>
-    </implementation>
-  </binding>
-</bindings>
--- a/chat/content/accounts.css
+++ b/chat/content/accounts.css
@@ -1,20 +1,12 @@
 /* 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/. */
 
-richlistitem {
-  -moz-binding: url("chrome://chat/content/account.xml#account");
-}
-
-richlistitem[selected="true"] .account-buttons {
-  -moz-binding: url("chrome://chat/content/account.xml#buttons");
-}
-
 richlistitem:not([selected="true"]) .account-buttons {
   display: none;
 }
 
 richlistitem:not([state="connected"]) .connected,
 richlistitem:not([state="connecting"]) .connecting,
 richlistitem:not([state="disconnected"]) .disconnected,
 richlistitem:not([state="disconnecting"]) .disconnecting,
new file mode 100644
--- /dev/null
+++ b/chat/content/chat-account-richlistitem.js
@@ -0,0 +1,298 @@
+/* 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/. */
+
+"use strict";
+
+/* global MozElements, MozXULElement, gAccountManager */
+
+{
+  let { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
+  let { DownloadUtils } = ChromeUtils.import("resource://gre/modules/DownloadUtils.jsm");
+  /**
+  * The MozChatAccountRichlistitem widget displays the information about the
+  * configured account: i.e. icon, state, name, error, checkbox for
+  * auto sign in and buttons for disconnect and properties.
+  *
+  * @extends {MozElements.MozRichlistitem}
+  */
+  class MozChatAccountRichlistitem extends MozElements.MozRichlistitem {
+    static get inheritedAttributes() {
+      return {
+        "stack": "tooltiptext=protocol",
+        ".accountIcon": "src=prplicon",
+        ".accountName": "value=name",
+        ".autoSignOn": "checked=autologin",
+        ".account-buttons": "autologin,name",
+      };
+    }
+
+    connectedCallback() {
+      if (this.delayConnectedCallback() || this.hasChildNodes()) {
+        return;
+      }
+
+      this.addEventListener("dblclick", (event) => {
+        if (event.button == 0) {
+          // If we double clicked on a widget that has already done
+          // something with the first click, we should ignore the event
+          let localName = event.originalTarget.localName;
+          if (localName != "button" && localName != "checkbox") {
+            this.buttons.proceedDefaultAction();
+          }
+        }
+        // Prevent from loading an account wizzard
+        event.stopPropagation();
+      });
+
+      this.appendChild(MozXULElement.parseXULToFragment(`
+        <vbox flex="1">
+          <hbox flex="1" align="top">
+            <vbox>
+              <stack>
+                <image class="accountIcon"></image>
+                <image class="accountStateIcon"></image>
+              </stack>
+              <spacer flex="1"></spacer>
+            </vbox>
+            <vbox flex="1" align="start">
+              <label crop="end" class="accountName"></label>
+              <label class="connecting" crop="end" value="&account.connecting;"></label>
+              <label class="connected" crop="end"></label>
+              <label class="disconnecting" crop="end" value="&account.disconnecting;"></label>
+              <label class="disconnected" crop="end" value="&account.disconnected;"></label>
+              <description class="error error-description"></description>
+              <description class="error error-reconnect"></description>
+              <label class="addException text-link" onclick="gAccountManager.addException()"
+                data-l10n-id="certmgr-add-exception"></label>
+              <spacer flex="1"></spacer>
+            </vbox>
+            <checkbox label="&account.autoSignOn.label;" dir="reverse" class="autoSignOn"
+              accesskey="&account.autoSignOn.accesskey;" oncommand="gAccountManager.autologin()"></checkbox>
+          </hbox>
+          <hbox flex="1" class="account-buttons">
+            <button class="disconnectButton" command="cmd_disconnect"></button>
+            <button class="connectButton" command="cmd_connect"></button>
+            <spacer flex="1"></spacer>
+            <button command="cmd_edit"></button>
+          </hbox>
+        </vbox>
+    `, ["chrome://chat/locale/accounts.dtd"]));
+      this.initializeAttributeInheritance();
+    }
+
+    set autoLogin(val) {
+      if (val) {
+        this.setAttribute("autologin", "true");
+      } else {
+        this.removeAttribute("autologin");
+      }
+      if (this._account.autoLogin != val) {
+        this._account.autoLogin = val;
+      }
+      return val;
+    }
+
+    get autoLogin() {
+      return this.hasAttribute("autologin");
+    }
+
+    /**
+     * override the default accessible name
+     */
+    get label() {
+      return this.getAttribute("name");
+    }
+
+    get account() {
+      return this._account;
+    }
+
+    get connectedLabel() {
+      if (!this._connectedLabel) {
+        this._connectedLabel = this.querySelector(".connected");
+      }
+      return this._connectedLabel;
+    }
+
+    get buttons() {
+      if (!this._buttons) {
+        this._buttons = this.querySelector(".account-buttons");
+      }
+      return this._buttons;
+    }
+
+    build(aAccount) {
+      this._account = aAccount;
+      this.setAttribute("name", aAccount.name);
+      this.setAttribute("id", aAccount.id);
+      let proto = aAccount.protocol;
+      this.setAttribute("protocol", proto.name);
+      this.setAttribute("prplicon", proto.iconBaseURI + "icon32.png");
+      let state = "Unknown";
+      if (this._account.connected) {
+        state = "connected";
+        this.refreshConnectedLabel();
+      } else if (this._account.disconnected) {
+        state = "disconnected";
+        if (this._account.connectionErrorReason != Ci.prplIAccount.NO_ERROR) {
+          this.updateConnectionError();
+        } else {
+          this.removeAttribute("error");
+          this.removeAttribute("certError");
+        }
+      } else if (this._account.connecting) {
+        state = "connecting";
+        this.updateConnectionState();
+      } else if (this._account.disconnecting) {
+        state = "connected";
+      }
+      this.setAttribute("state", state);
+      this.autoLogin = aAccount.autoLogin;
+    }
+
+    updateConnectionState() {
+      let bundle = Services.strings.createBundle("chrome://messenger/locale/imAccounts.properties");
+      const key = "account.connection.progress";
+      let text = this._account.connectionStateMsg;
+      text = text ? bundle.formatStringFromName(key, [text], 1) :
+        bundle.GetStringFromName("account.connecting");
+
+      let progress = this.querySelector(".connecting");
+      progress.setAttribute("value", text);
+      if (this.reconnectUpdateInterval) {
+        this._cancelReconnectTimer();
+      }
+
+      this.removeAttribute("certError");
+    }
+
+    updateConnectionError() {
+      let bundle = Services.strings.createBundle("chrome://messenger/locale/imAccounts.properties");
+      const key = "account.connection.error";
+      let account = this._account;
+      let text;
+      let errorReason = account.connectionErrorReason;
+      if (errorReason == Ci.imIAccount.ERROR_UNKNOWN_PRPL) {
+        text = bundle.formatStringFromName(key + "UnknownPrpl", [account.protocol.id], 1);
+      } else if (errorReason == Ci.imIAccount.ERROR_MISSING_PASSWORD) {
+        text = bundle.GetStringFromName(key + "EnteringPasswordRequired");
+      } else if (errorReason == Ci.imIAccount.ERROR_CRASHED) {
+        text = bundle.GetStringFromName(key + "CrashedAccount");
+      } else {
+        text = account.connectionErrorMessage;
+      }
+
+      if (errorReason != Ci.imIAccount.ERROR_MISSING_PASSWORD) {
+        text = bundle.formatStringFromName(key, [text], 1);
+      }
+
+      this.setAttribute("error", "true");
+      if ((Ci.imIAccount.ERROR_CERT_NOT_PROVIDED <= errorReason &&
+        errorReason <= Ci.imIAccount.ERROR_CERT_OTHER_ERROR) &&
+        account.prplAccount.connectionTarget) {
+        this.setAttribute("certError", "true");
+      }
+      let error = this.querySelector(".error-description");
+      error.textContent = text;
+
+      let updateReconnect = () => {
+        let date = Math.round((account.timeOfNextReconnect - Date.now()) / 1000);
+        let reconnect = "";
+        if (date > 0) {
+          let [val1, unit1, val2, unit2] = DownloadUtils.convertTimeUnits(date);
+          if (!val2)
+            reconnect = bundle.formatStringFromName("account.reconnectInSingle",
+              [val1, unit1], 2);
+          else
+            reconnect = bundle.formatStringFromName("account.reconnectInDouble",
+              [val1, unit1, val2, unit2], 4);
+        }
+        this.querySelector(".error-reconnect").textContent = reconnect;
+        return reconnect;
+      };
+      if (updateReconnect() && !this.reconnectUpdateInterval) {
+        this.setAttribute("reconnectPending", "true");
+        this.reconnectUpdateInterval = setInterval(updateReconnect, 1000);
+        gAccountManager.disableCommandItems();
+      }
+    }
+
+    refreshConnectedLabel() {
+      let bundle = Services.strings.createBundle("chrome://messenger/locale/imAccounts.properties");
+      let date = 60 * Math.floor((Date.now() - this._account.timeOfLastConnect) / 60000);
+      let value;
+      if (date > 0) {
+        let [val1, unit1, val2, unit2] = DownloadUtils.convertTimeUnits(date);
+        if (!val2) {
+          value = bundle.formatStringFromName("account.connectedForSingle",
+            [val1, unit1], 2);
+        } else {
+          value = bundle.formatStringFromName("account.connectedForDouble",
+            [val1, unit1, val2, unit2], 4);
+        }
+      } else {
+        value = bundle.GetStringFromName("account.connectedForSeconds");
+      }
+      this.connectedLabel.value = value;
+    }
+
+    _cancelReconnectTimer() {
+      this.removeAttribute("reconnectPending");
+      clearInterval(this.reconnectUpdateInterval);
+      delete this.reconnectUpdateInterval;
+      gAccountManager.disableCommandItems();
+    }
+
+    cancelReconnection() {
+      if (this.reconnectUpdateInterval) {
+        this._cancelReconnectTimer();
+        this._account.cancelReconnection();
+      }
+    }
+
+    restoreItems() {
+      // Called after a removal and reinsertion of the binding
+      this._buttons = null;
+      this._connectedLabel = null;
+      if (this._account.connected) {
+        this.refreshConnectedLabel();
+      } else if (this._account.connecting) {
+        this.updateConnectionState();
+      } else if (this._account.connectionErrorReason != Ci.prplIAccount.NO_ERROR) {
+        this.updateConnectionError();
+      }
+    }
+
+    destroy() {
+      // If we have a reconnect timer, stop it:
+      // it will throw errors otherwise (see bug 480).
+      if (!this.reconnectUpdateInterval) {
+        return;
+      }
+      clearInterval(this.reconnectUpdateInterval);
+      delete this.reconnectUpdateInterval;
+    }
+
+    get activeButton() {
+      let action = this.account.disconnected ? ".connectButton" : ".disconnectButton";
+      return this.querySelector(action);
+    }
+
+    setFocus() {
+      let focusTarget = this.activeButton;
+      let accountName = this.getAttribute("name");
+      focusTarget.setAttribute("aria-label", focusTarget.label + " " + accountName);
+      if (focusTarget.disabled) {
+        focusTarget = document.getElementById("accountlist");
+      }
+      focusTarget.focus();
+    }
+
+    proceedDefaultAction() {
+      this.activeButton.click();
+    }
+  }
+
+  customElements.define("chat-account-richlistitem", MozChatAccountRichlistitem, { extends: "richlistitem" });
+}
--- a/chat/content/jar.mn
+++ b/chat/content/jar.mn
@@ -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/.
 
 chat.jar:
 % content chat %content/chat/
 	content/chat/accounts.css
-	content/chat/account.xml
 	content/chat/browserRequest.js
 	content/chat/browserRequest.xul
 	content/chat/imAccountOptionsHelper.js
+	content/chat/chat-account-richlistitem.js
 *	content/chat/imtooltip.xml
 	content/chat/conversation-browser.js
 	content/chat/conv.html
--- a/mail/components/im/content/imAccounts.js
+++ b/mail/components/im/content/imAccounts.js
@@ -39,17 +39,17 @@ var gAccountManager = {
   _connectedLabelInterval: 0,
   load() {
     // Wait until the password service is ready before offering anything.
     Services.logins.initializationPromise.then(() => {
       this.accountList = document.getElementById("accountlist");
       let defaultID;
       Services.core.init(); // ensure the imCore is initialized.
       for (let acc of this.getAccounts()) {
-        var elt = document.createElement("richlistitem");
+        let elt = document.createElement("richlistitem", { is: "chat-account-richlistitem" });
         this.accountList.appendChild(elt);
         elt.build(acc);
         if (!defaultID && acc.firstConnectionState == acc.FIRST_CONNECTION_CRASHED)
           defaultID = acc.id;
       }
       for (let event of events)
         Services.obs.addObserver(this, event);
       if (!this.accountList.getRowCount()) {
@@ -133,17 +133,17 @@ var gAccountManager = {
       return;
     }
 
     // The following notification handlers need an account.
     aObject.QueryInterface(Ci.imIAccount);
 
     if (aTopic == "account-added") {
       document.getElementById("accountsDesk").selectedIndex = 1;
-      let elt = document.createElement("richlistitem");
+      let elt = document.createElement("richlistitem", { is: "chat-account-richlistitem" });
       this.accountList.appendChild(elt);
       elt.build(aObject);
       if (this.accountList.getRowCount() == 1)
         this.accountList.selectedIndex = 0;
     } else if (aTopic == "account-removed") {
       let elt = document.getElementById(aObject.id);
       elt.destroy();
       if (!elt.selected) {
@@ -272,17 +272,17 @@ var gAccountManager = {
   temporarilyDisableButtons() {
     document.getElementById("cmd_disconnect").setAttribute("disabled", "true");
     document.getElementById("cmd_connect").setAttribute("disabled", "true");
     clearTimeout(this.disableTimerID);
     this.accountList.focus();
     this.disableTimerID = setTimeout(function(aItem) {
       gAccountManager.disableTimerID = 0;
       gAccountManager.disableCommandItems();
-      aItem.buttons.setFocus();
+      aItem.setFocus();
     }, this._disabledDelay, this.accountList.selectedItem);
   },
 
   new() {
     this.openDialog("chrome://messenger/content/chat/imAccountWizard.xul");
   },
   edit() {
     // Find the nsIIncomingServer for the current imIAccount.
--- a/mail/components/im/content/imAccounts.xul
+++ b/mail/components/im/content/imAccounts.xul
@@ -24,22 +24,22 @@
         windowtype="Messenger:Accounts"
         title="&accountsWindow.title;"
         style="&accountsWindow2.style;"
         persist="width height screenX screenY">
  <script type="application/javascript" src="chrome://messenger/content/chat/imAccounts.js"/>
  <script type="application/javascript" src="chrome://messenger/content/chat/imStatusSelector.js"/>
  <script type="application/javascript" src="chrome://global/content/nsDragAndDrop.js" />
  <script type="application/javascript" src="chrome://global/content/nsTransferable.js" />
+ <script type="application/javascript" src="chrome://chat/content/chat-account-richlistitem.js" />
 
  <linkset>
    <html:link rel="localization" href="security/certificates/certManager.ftl"/>
  </linkset>
 
- <stringbundle id="accountsBundle" src="chrome://messenger/locale/imAccounts.properties"/>
  <stringbundle id="chatBundle" src="chrome://messenger/locale/chat.properties"/>
 
  <commandset id="accountsCommands">
    <command id="cmd_connect"
             accesskey="&account.connect.accesskey;"
             label="&account.connect.label;"
             oncommand="gAccountManager.connect()"/>
    <command id="cmd_disconnect"