Bug 1519533 - convert formautofill autocomplete-profile-listitem bindings to CE, r=MattN
authorAlexander Surkov <surkov.alexander@gmail.com>
Fri, 08 Feb 2019 22:57:03 -0500
changeset 458959 d09c7dc93f20
parent 458958 6ad8d2be5c2c
child 458960 e96e61bd282f
push id111907
push usersurkov.alexander@gmail.com
push dateThu, 14 Feb 2019 00:41:21 +0000
treeherdermozilla-inbound@d09c7dc93f20 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersMattN
bugs1519533
milestone67.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1519533 - convert formautofill autocomplete-profile-listitem bindings to CE, r=MattN Differential Revision: https://phabricator.services.mozilla.com/D19257
browser/extensions/formautofill/FormAutofillParent.jsm
browser/extensions/formautofill/content/customElements.js
browser/extensions/formautofill/content/formautofill.css
browser/extensions/formautofill/content/formautofill.xml
toolkit/content/widgets/autocomplete.xml
--- a/browser/extensions/formautofill/FormAutofillParent.jsm
+++ b/browser/extensions/formautofill/FormAutofillParent.jsm
@@ -120,18 +120,39 @@ FormAutofillParent.prototype = {
 
     // Only listen to credit card related messages if it is available
     if (FormAutofill.isAutofillCreditCardsAvailable) {
       Services.ppmm.addMessageListener("FormAutofill:SaveCreditCard", this);
       Services.ppmm.addMessageListener("FormAutofill:RemoveCreditCards", this);
       Services.ppmm.addMessageListener("FormAutofill:GetDecryptedString", this);
       Services.prefs.addObserver(ENABLED_AUTOFILL_CREDITCARDS_PREF, this);
     }
+
+    for (let win of Services.wm.getEnumerator("navigator:browser")) {
+      this.injectElements(win.document);
+    }
+    Services.wm.addListener(this);
   },
 
+  injectElements(doc) {
+    Services.scriptloader.loadSubScript("chrome://formautofill/content/customElements.js",
+                                        doc.ownerGlobal);
+  },
+
+  onOpenWindow(xulWindow) {
+    const win = xulWindow.docShell.domWindow;
+    win.addEventListener("load", () => {
+      if (win.document.documentElement.getAttribute("windowtype") == "navigator:browser") {
+        this.injectElements(win.document);
+      }
+    }, {once: true});
+  },
+
+  onCloseWindow() {},
+
   observe(subject, topic, data) {
     log.debug("observe:", topic, "with data:", data);
     switch (topic) {
       case "privacy-pane-loaded": {
         let formAutofillPreferences = new FormAutofillPreferences();
         let document = subject.document;
         let prefFragment = formAutofillPreferences.init(document);
         let formAutofillGroupBox = document.getElementById("formAutofillGroupBox");
@@ -275,16 +296,17 @@ FormAutofillParent.prototype = {
     this.formAutofillStorage._saveImmediately();
 
     Services.ppmm.removeMessageListener("FormAutofill:InitStorage", this);
     Services.ppmm.removeMessageListener("FormAutofill:GetRecords", this);
     Services.ppmm.removeMessageListener("FormAutofill:SaveAddress", this);
     Services.ppmm.removeMessageListener("FormAutofill:RemoveAddresses", this);
     Services.obs.removeObserver(this, "privacy-pane-loaded");
     Services.prefs.removeObserver(ENABLED_AUTOFILL_ADDRESSES_PREF, this);
+    Services.wm.removeListener(this);
 
     if (FormAutofill.isAutofillCreditCardsAvailable) {
       Services.ppmm.removeMessageListener("FormAutofill:SaveCreditCard", this);
       Services.ppmm.removeMessageListener("FormAutofill:RemoveCreditCards", this);
       Services.ppmm.removeMessageListener("FormAutofill:GetDecryptedString", this);
       Services.prefs.removeObserver(ENABLED_AUTOFILL_CREDITCARDS_PREF, this);
     }
   },
rename from browser/extensions/formautofill/content/formautofill.xml
rename to browser/extensions/formautofill/content/customElements.js
--- a/browser/extensions/formautofill/content/formautofill.xml
+++ b/browser/extensions/formautofill/content/customElements.js
@@ -1,354 +1,357 @@
-<?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/. -->
+/* 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 into the browser window scope.
+/* eslint-env mozilla/browser-window */
+/* eslint-disable mozilla/balanced-listeners */ // Not relevant since the document gets unloaded.
 
-<bindings id="formautofillBindings"
-          xmlns="http://www.mozilla.org/xbl"
-          xmlns:html="http://www.w3.org/1999/xhtml"
-          xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
-          xmlns:xbl="http://www.mozilla.org/xbl">
+"use strict";
+
+// Wrap in a block to prevent leaking to window scope.
+(() => {
+  const {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm");
+
+  class MozAutocompleteProfileListitemBase extends MozElements.MozRichlistitem {
+    constructor() {
+      super();
 
-  <binding id="autocomplete-profile-listitem-base" extends="chrome://global/content/bindings/richlistbox.xml#richlistitem">
-    <implementation implements="nsIDOMXULSelectControlItemElement">
-      <constructor>
-      </constructor>
-      <!-- For form autofill, we want to unify the selection no matter by
-      keyboard navigation or mouseover in order not to confuse user which
-      profile preview is being shown. This field is set to true to indicate
-      that selectedIndex of popup should be changed while mouseover item -->
-      <field name="selectedByMouseOver">true</field>
+      /**
+       * For form autofill, we want to unify the selection no matter by
+       * keyboard navigation or mouseover in order not to confuse user which
+       * profile preview is being shown. This field is set to true to indicate
+       * that selectedIndex of popup should be changed while mouseover item
+       */
+      this.selectedByMouseOver = true;
+    }
 
-      <property name="_stringBundle">
-        <getter><![CDATA[
-          /* global Services */
-          if (!this.__stringBundle) {
-            this.__stringBundle = Services.strings.createBundle("chrome://formautofill/locale/formautofill.properties");
-          }
-          return this.__stringBundle;
-        ]]></getter>
-      </property>
+    get _stringBundle() {
+      if (!this.__stringBundle) {
+        this.__stringBundle = Services.strings.createBundle(
+        "chrome://formautofill/locale/formautofill.properties"
+      );
+      }
+      return this.__stringBundle;
+    }
 
-      <method name="_cleanup">
-        <body>
-        <![CDATA[
-          this.removeAttribute("formautofillattached");
-          if (this._itemBox) {
-            this._itemBox.removeAttribute("size");
-          }
-        ]]>
-        </body>
-      </method>
+    _cleanup() {
+      this.removeAttribute("formautofillattached");
+      if (this._itemBox) {
+        this._itemBox.removeAttribute("size");
+      }
+    }
 
-      <method name="_onOverflow">
-        <body></body>
-      </method>
+    _onOverflow() {}
+
+    _onUnderflow() {}
 
-      <method name="_onUnderflow">
-        <body></body>
-      </method>
+    handleOverUnderflow() {}
 
-      <method name="handleOverUnderflow">
-        <body></body>
-      </method>
+    _adjustAutofillItemLayout() {
+      let outerBoxRect = this.parentNode.getBoundingClientRect();
 
-      <method name="_adjustAutofillItemLayout">
-        <body>
-        <![CDATA[
-          let outerBoxRect = this.parentNode.getBoundingClientRect();
+    // Make item fit in popup as XUL box could not constrain
+    // item's width
+      this._itemBox.style.width = outerBoxRect.width + "px";
+    // Use two-lines layout when width is smaller than 150px or
+    // 185px if an image precedes the label.
+      let oneLineMinRequiredWidth = this.getAttribute("ac-image") ? 185 : 150;
 
-          // Make item fit in popup as XUL box could not constrain
-          // item's width
-          this._itemBox.style.width = outerBoxRect.width + "px";
-          // Use two-lines layout when width is smaller than 150px or
-          // 185px if an image precedes the label.
-          let oneLineMinRequiredWidth = this.getAttribute("ac-image") ? 185 : 150;
+      if (outerBoxRect.width <= oneLineMinRequiredWidth) {
+        this._itemBox.setAttribute("size", "small");
+      } else {
+        this._itemBox.removeAttribute("size");
+      }
+    }
+  }
 
-          if (outerBoxRect.width <= oneLineMinRequiredWidth) {
-            this._itemBox.setAttribute("size", "small");
-          } else {
-            this._itemBox.removeAttribute("size");
-          }
-        ]]>
-        </body>
-      </method>
-    </implementation>
-  </binding>
+  MozElements.MozAutocompleteProfileListitem = class MozAutocompleteProfileListitem extends MozAutocompleteProfileListitemBase {
+    connectedCallback() {
+      if (this.delayConnectedCallback()) {
+        return;
+      }
+
+      this.textContent = "";
 
-  <binding id="autocomplete-profile-listitem" extends="chrome://formautofill/content/formautofill.xml#autocomplete-profile-listitem-base">
-    <xbl:content xmlns="http://www.w3.org/1999/xhtml">
-      <div anonid="autofill-item-box" class="autofill-item-box" xbl:inherits="ac-image">
-        <div class="profile-label-col profile-item-col">
-          <span anonid="profile-label-affix" class="profile-label-affix"></span>
-          <span anonid="profile-label" class="profile-label"></span>
+      this.appendChild(MozXULElement.parseXULToFragment(`
+        <div xmlns="http://www.w3.org/1999/xhtml" class="autofill-item-box">
+          <div class="profile-label-col profile-item-col">
+            <span class="profile-label-affix"></span>
+            <span class="profile-label"></span>
+          </div>
+          <div class="profile-comment-col profile-item-col">
+            <span class="profile-comment"></span>
+          </div>
         </div>
-        <div class="profile-comment-col profile-item-col">
-          <span anonid="profile-comment" class="profile-comment"></span>
-        </div>
-      </div>
-    </xbl:content>
+      `));
+
+      this._itemBox = this.querySelector(".autofill-item-box");
+      this._labelAffix = this.querySelector(".profile-label-affix");
+      this._label = this.querySelector(".profile-label");
+      this._comment = this.querySelector(".profile-comment");
 
-    <implementation implements="nsIDOMXULSelectControlItemElement">
-      <constructor>
-        <![CDATA[
-          this._itemBox = document.getAnonymousElementByAttribute(
-            this, "anonid", "autofill-item-box"
-          );
-          this._labelAffix = document.getAnonymousElementByAttribute(
-            this, "anonid", "profile-label-affix"
-          );
-          this._label = document.getAnonymousElementByAttribute(
-            this, "anonid", "profile-label"
-          );
-          this._comment = document.getAnonymousElementByAttribute(
-            this, "anonid", "profile-comment"
-          );
+      this._updateAttributes();
+      this._adjustAcItem();
+    }
+
+    static get observedAttributes() {
+      return [
+        "ac-image",
+      ];
+    }
 
-          this._adjustAcItem();
-        ]]>
-      </constructor>
+    attributeChangedCallback(name, oldValue, newValue) {
+      if (this.isConnectedAndReady && name == "ac-image" && oldValue != newValue) {
+        this._updateAttributes();
+      }
+    }
+
+    _updateAttributes() {
+      this.inheritAttribute(this._itemBox, "ac-image");
+    }
 
-      <property name="selected" onget="return this.getAttribute('selected') == 'true';">
-        <setter><![CDATA[
-          /* global Cu */
-          if (val) {
-            this.setAttribute("selected", "true");
-          } else {
-            this.removeAttribute("selected");
-          }
+    set selected(val) {
+      if (val) {
+        this.setAttribute("selected", "true");
+      } else {
+        this.removeAttribute("selected");
+      }
 
-          let {AutoCompletePopup} = ChromeUtils.import("resource://gre/modules/AutoCompletePopup.jsm");
+      let {AutoCompletePopup} =
+        ChromeUtils.import("resource://gre/modules/AutoCompletePopup.jsm");
+      AutoCompletePopup.sendMessageToBrowser("FormAutofill:PreviewProfile");
 
-          AutoCompletePopup.sendMessageToBrowser("FormAutofill:PreviewProfile");
-
-          return val;
-        ]]></setter>
-      </property>
+      return val;
+    }
 
-      <method name="_adjustAcItem">
-        <body>
-        <![CDATA[
-          this._adjustAutofillItemLayout();
-          this.setAttribute("formautofillattached", "true");
-          this._itemBox.style.setProperty("--primary-icon", `url(${this.getAttribute("ac-image")})`);
+    get selected() {
+      return this.getAttribute("selected") == "true";
+    }
+
+    _adjustAcItem() {
+      this._adjustAutofillItemLayout();
+      this.setAttribute("formautofillattached", "true");
+      this._itemBox.style.setProperty("--primary-icon", `url(${this.getAttribute("ac-image")})`);
 
-          let {primaryAffix, primary, secondary} = JSON.parse(this.getAttribute("ac-value"));
+      let {primaryAffix, primary, secondary} = JSON.parse(this.getAttribute("ac-value"));
 
-          this._labelAffix.textContent = primaryAffix;
-          this._label.textContent = primary;
-          this._comment.textContent = secondary;
-        ]]>
-        </body>
-      </method>
-    </implementation>
-  </binding>
+      this._labelAffix.textContent = primaryAffix;
+      this._label.textContent = primary;
+      this._comment.textContent = secondary;
+    }
+  };
 
-  <binding id="autocomplete-profile-listitem-footer" extends="chrome://formautofill/content/formautofill.xml#autocomplete-profile-listitem-base">
-    <xbl:content xmlns="http://www.w3.org/1999/xhtml">
-      <div anonid="autofill-footer" class="autofill-item-box autofill-footer">
-        <div anonid="autofill-warning" class="autofill-footer-row autofill-warning">
-        </div>
-        <div anonid="autofill-option-button" class="autofill-footer-row autofill-button">
-        </div>
-      </div>
-    </xbl:content>
+  customElements.define(
+    "autocomplete-profile-listitem",
+    MozElements.MozAutocompleteProfileListitem,
+    {extends: "richlistitem"}
+  );
 
-    <handlers>
-      <handler event="click" button="0"><![CDATA[
+  class MozAutocompleteProfileListitemFooter extends MozAutocompleteProfileListitemBase {
+    constructor() {
+      super();
+
+      this.addEventListener("click", (event) => {
+        if (event.button != 0) {
+          return;
+        }
+
         if (this._warningTextBox.contains(event.originalTarget)) {
           return;
         }
 
         window.openPreferences("privacy-form-autofill", {origin: "autofillFooter"});
-      ]]></handler>
-    </handlers>
+      });
+    }
 
-    <implementation implements="nsIDOMXULSelectControlItemElement">
-      <constructor>
-        <![CDATA[
-          this._itemBox = document.getAnonymousElementByAttribute(
-            this, "anonid", "autofill-footer"
-          );
-          this._optionButton = document.getAnonymousElementByAttribute(
-            this, "anonid", "autofill-option-button"
-          );
-          this._warningTextBox = document.getAnonymousElementByAttribute(
-            this, "anonid", "autofill-warning"
-          );
+    connectedCallback() {
+      if (this.delayConnectedCallback()) {
+        return;
+      }
 
-          /**
-           * A handler for updating warning message once selectedIndex has been changed.
-           *
-           * There're three different states of warning message:
-           * 1. None of addresses were selected: We show all the categories intersection of fields in the
-           *    form and fields in the results.
-           * 2. An address was selested: Show the additional categories that will also be filled.
-           * 3. An address was selected, but the focused category is the same as the only one category: Only show
-           * the exact category that we're going to fill in.
-           *
-           * @private
-           * @param {string[]} data.categories
-           *        The categories of all the fields contained in the selected address.
-           */
-          this._updateWarningNote = ({data} = {}) => {
-            let categories = (data && data.categories) ? data.categories : this._allFieldCategories;
-            // If the length of categories is 1, that means all the fillable fields are in the same
-            // category. We will change the way to inform user according to this flag. When the value
-            // is true, we show "Also autofills ...", otherwise, show "Autofills ..." only.
-            let hasExtraCategories = categories.length > 1;
-            // Show the categories in certain order to conform with the spec.
-            let orderedCategoryList = [{id: "address", l10nId: "category.address"},
-                                       {id: "name", l10nId: "category.name"},
-                                       {id: "organization", l10nId: "category.organization2"},
-                                       {id: "tel", l10nId: "category.tel"},
-                                       {id: "email", l10nId: "category.email"}];
-            let showCategories = hasExtraCategories ?
-              orderedCategoryList.filter(category => categories.includes(category.id) && category.id != this._focusedCategory) :
-              [orderedCategoryList.find(category => category.id == this._focusedCategory)];
+      this.textContent = "";
+      this.appendChild(MozXULElement.parseXULToFragment(`
+        <div xmlns="http://www.w3.org/1999/xhtml" class="autofill-item-box autofill-footer">
+          <div class="autofill-footer-row autofill-warning"></div>
+          <div class="autofill-footer-row autofill-button"></div>
+        </div>
+      `));
+
+      this._itemBox = this.querySelector(".autofill-footer");
+      this._optionButton = this.querySelector(".autofill-button");
+      this._warningTextBox = this.querySelector(".autofill-warning");
 
-            let separator = this._stringBundle.GetStringFromName("fieldNameSeparator");
-            let warningTextTmplKey = hasExtraCategories ? "phishingWarningMessage" : "phishingWarningMessage2";
-            let categoriesText = showCategories.map(category => this._stringBundle.GetStringFromName(category.l10nId)).join(separator);
-
-            this._warningTextBox.textContent = this._stringBundle.formatStringFromName(warningTextTmplKey,
-              [categoriesText], 1);
-            this.parentNode.parentNode.adjustHeight();
-          };
+      /**
+       * A handler for updating warning message once selectedIndex has been changed.
+       *
+       * There're three different states of warning message:
+       * 1. None of addresses were selected: We show all the categories intersection of fields in the
+       *    form and fields in the results.
+       * 2. An address was selested: Show the additional categories that will also be filled.
+       * 3. An address was selected, but the focused category is the same as the only one category: Only show
+       * the exact category that we're going to fill in.
+       *
+       * @private
+       * @param {string[]} data.categories
+       *        The categories of all the fields contained in the selected address.
+       */
+      this._updateWarningNote = ({data} = {}) => {
+        let categories = (data && data.categories) ? data.categories : this._allFieldCategories;
+        // If the length of categories is 1, that means all the fillable fields are in the same
+        // category. We will change the way to inform user according to this flag. When the value
+        // is true, we show "Also autofills ...", otherwise, show "Autofills ..." only.
+        let hasExtraCategories = categories.length > 1;
+        // Show the categories in certain order to conform with the spec.
+        let orderedCategoryList = [{id: "address", l10nId: "category.address"},
+          {id: "name", l10nId: "category.name"},
+          {id: "organization", l10nId: "category.organization2"},
+          {id: "tel", l10nId: "category.tel"},
+          {id: "email", l10nId: "category.email"},
+        ];
+        let showCategories = hasExtraCategories ?
+            orderedCategoryList.filter(category => categories.includes(category.id) && category.id != this._focusedCategory) :
+            [orderedCategoryList.find(category => category.id == this._focusedCategory)];
 
-          this._adjustAcItem();
-        ]]>
-      </constructor>
-
-      <method name="_onCollapse">
-        <body>
-        <![CDATA[
-          /* global messageManager */
-
-          if (this.showWarningText) {
-            messageManager.removeMessageListener("FormAutofill:UpdateWarningMessage", this._updateWarningNote);
-          }
+        let separator = this._stringBundle.GetStringFromName("fieldNameSeparator");
+        let warningTextTmplKey = hasExtraCategories ? "phishingWarningMessage" : "phishingWarningMessage2";
+        let categoriesText = showCategories.map(category => this._stringBundle.GetStringFromName(category.l10nId)).join(separator);
 
-          this._itemBox.removeAttribute("no-warning");
-        ]]>
-        </body>
-      </method>
+        this._warningTextBox.textContent = this._stringBundle.formatStringFromName(warningTextTmplKey, [categoriesText], 1);
+        this.parentNode.parentNode.adjustHeight();
+      };
+
+      this._adjustAcItem();
+    }
 
-      <method name="_adjustAcItem">
-        <body>
-        <![CDATA[
-          /* global Cu */
-          this._adjustAutofillItemLayout();
-          this.setAttribute("formautofillattached", "true");
+    _onCollapse() {
+      /* global messageManager */
+      if (this.showWarningText) {
+        messageManager.removeMessageListener(
+          "FormAutofill:UpdateWarningMessage", this._updateWarningNote
+        );
+      }
+      this._itemBox.removeAttribute("no-warning");
+    }
 
-          let {AppConstants} = ChromeUtils.import("resource://gre/modules/AppConstants.jsm");
-          // TODO: The "Short" suffix is pointless now as normal version string is no longer needed,
-          // we should consider removing the suffix if possible when the next time locale change.
-          let buttonTextBundleKey = AppConstants.platform == "macosx" ?
-            "autocompleteFooterOptionOSXShort" : "autocompleteFooterOptionShort";
-          let buttonText = this._stringBundle.GetStringFromName(buttonTextBundleKey);
-          this._optionButton.textContent = buttonText;
+    _adjustAcItem() {
+      /* global Cu */
+      this._adjustAutofillItemLayout();
+      this.setAttribute("formautofillattached", "true");
 
-          let value = JSON.parse(this.getAttribute("ac-value"));
+      let {AppConstants} = ChromeUtils.import("resource://gre/modules/AppConstants.jsm", {});
+      // TODO: The "Short" suffix is pointless now as normal version string is no longer needed,
+      // we should consider removing the suffix if possible when the next time locale change.
+      let buttonTextBundleKey = AppConstants.platform == "macosx" ?
+      "autocompleteFooterOptionOSXShort" : "autocompleteFooterOptionShort";
+      let buttonText = this._stringBundle.GetStringFromName(buttonTextBundleKey);
+      this._optionButton.textContent = buttonText;
 
-          this._allFieldCategories = value.categories;
-          this._focusedCategory = value.focusedCategory;
-          this.showWarningText = this._allFieldCategories && this._focusedCategory;
+      let value = JSON.parse(this.getAttribute("ac-value"));
+
+      this._allFieldCategories = value.categories;
+      this._focusedCategory = value.focusedCategory;
+      this.showWarningText = this._allFieldCategories && this._focusedCategory;
 
-          if (this.showWarningText) {
-            messageManager.addMessageListener("FormAutofill:UpdateWarningMessage", this._updateWarningNote);
+      if (this.showWarningText) {
+        messageManager.addMessageListener("FormAutofill:UpdateWarningMessage", this._updateWarningNote);
+        this._updateWarningNote();
+      } else {
+        this._itemBox.setAttribute("no-warning", "true");
+      }
+    }
+  }
 
-            this._updateWarningNote();
-          } else {
-            this._itemBox.setAttribute("no-warning", "true");
-          }
-        ]]>
-        </body>
-      </method>
-    </implementation>
-  </binding>
-
-  <binding id="autocomplete-creditcard-insecure-field" extends="chrome://formautofill/content/formautofill.xml#autocomplete-profile-listitem-base">
-    <xbl:content xmlns="http://www.w3.org/1999/xhtml">
-      <div anonid="autofill-item-box" class="autofill-insecure-item">
-      </div>
-    </xbl:content>
+  customElements.define(
+    "autocomplete-profile-listitem-footer",
+    MozAutocompleteProfileListitemFooter,
+    {extends: "richlistitem"}
+  );
 
-    <implementation implements="nsIDOMXULSelectControlItemElement">
-      <constructor>
-      <![CDATA[
-        this._itemBox = document.getAnonymousElementByAttribute(
-          this, "anonid", "autofill-item-box"
-        );
+  class MozAutocompleteCreditcardInsecureField extends MozAutocompleteProfileListitemBase {
+    connectedCallback() {
+      if (this.delayConnectedCallback()) {
+        return;
+      }
+      this.textContent = "";
+      this.appendChild(MozXULElement.parseXULToFragment(`
+        <div xmlns="http://www.w3.org/1999/xhtml" class="autofill-insecure-item"></div>
+      `));
 
-        this._adjustAcItem();
-      ]]>
-      </constructor>
+      this._itemBox = this.querySelector(".autofill-insecure-item");
+
+      this._adjustAcItem();
+    }
 
-      <property name="selected" onget="return this.getAttribute('selected') == 'true';">
-        <setter><![CDATA[
-          // Make this item unselectable since we see this item as a pure message.
-          return false;
-        ]]></setter>
-      </property>
+    set selected(val) {
+    // Make this item unselectable since we see this item as a pure message.
+      return false;
+    }
+
+    get selected() {
+      return this.getAttribute("selected") == "true";
+    }
 
-      <method name="_adjustAcItem">
-        <body>
-        <![CDATA[
-          this._adjustAutofillItemLayout();
-          this.setAttribute("formautofillattached", "true");
+    _adjustAcItem() {
+      this._adjustAutofillItemLayout();
+      this.setAttribute("formautofillattached", "true");
 
-          let value = this.getAttribute("ac-value");
-          this._itemBox.textContent = value;
-        ]]>
-        </body>
-      </method>
+      let value = this.getAttribute("ac-value");
+      this._itemBox.textContent = value;
+    }
+  }
 
-    </implementation>
-  </binding>
+  customElements.define(
+    "autocomplete-creditcard-insecure-field",
+    MozAutocompleteCreditcardInsecureField,
+    {extends: "richlistitem"}
+  );
+
+  class MozAutocompleteProfileListitemClearButton extends MozAutocompleteProfileListitemBase {
+    constructor() {
+      super();
 
-  <binding id="autocomplete-profile-listitem-clear-button" extends="chrome://formautofill/content/formautofill.xml#autocomplete-profile-listitem-base">
-    <xbl:content xmlns="http://www.w3.org/1999/xhtml">
-      <div anonid="autofill-item-box" class="autofill-item-box autofill-footer">
-        <div anonid="autofill-clear-button" class="autofill-footer-row autofill-button"></div>
-      </div>
-    </xbl:content>
+      this.addEventListener("click", (event) => {
+        if (event.button != 0) {
+          return;
+        }
 
-    <handlers>
-      <handler event="click" button="0"><![CDATA[
-        /* global Cu */
-        let {AutoCompletePopup} = ChromeUtils.import("resource://gre/modules/AutoCompletePopup.jsm");
+        let {AutoCompletePopup} =
+        ChromeUtils.import("resource://gre/modules/AutoCompletePopup.jsm");
+        AutoCompletePopup.sendMessageToBrowser("FormAutofill:ClearForm");
+      });
+    }
 
-        AutoCompletePopup.sendMessageToBrowser("FormAutofill:ClearForm");
-      ]]></handler>
-    </handlers>
+    connectedCallback() {
+      if (this.delayConnectedCallback()) {
+        return;
+      }
 
-    <implementation implements="nsIDOMXULSelectControlItemElement">
-      <constructor>
-      <![CDATA[
-        this._itemBox = document.getAnonymousElementByAttribute(
-          this, "anonid", "autofill-item-box"
-        );
-        this._clearBtn = document.getAnonymousElementByAttribute(
-          this, "anonid", "autofill-clear-button"
-        );
+      this.textContent = "";
+      this.appendChild(MozXULElement.parseXULToFragment(`
+        <div xmlns="http://www.w3.org/1999/xhtml" class="autofill-item-box autofill-footer">
+          <div class="autofill-footer-row autofill-button"></div>
+        </div>
+      `));
 
-        this._adjustAcItem();
-      ]]>
-      </constructor>
+      this._itemBox = this.querySelector(".autofill-item-box");
+      this._clearBtn = this.querySelector(".autofill-button");
+
+      this._adjustAcItem();
+    }
 
-      <method name="_adjustAcItem">
-        <body>
-        <![CDATA[
-          this._adjustAutofillItemLayout();
-          this.setAttribute("formautofillattached", "true");
+    _adjustAcItem() {
+      this._adjustAutofillItemLayout();
+      this.setAttribute("formautofillattached", "true");
 
-          let clearFormBtnLabel = this._stringBundle.GetStringFromName("clearFormBtnLabel2");
-          this._clearBtn.textContent = clearFormBtnLabel;
-        ]]>
-        </body>
-      </method>
+      let clearFormBtnLabel =
+      this._stringBundle.GetStringFromName("clearFormBtnLabel2");
+      this._clearBtn.textContent = clearFormBtnLabel;
+    }
+  }
 
-    </implementation>
-  </binding>
-
-</bindings>
+  customElements.define(
+    "autocomplete-profile-listitem-clear-button",
+    MozAutocompleteProfileListitemClearButton,
+    {extends: "richlistitem"}
+  );
+})();
--- a/browser/extensions/formautofill/content/formautofill.css
+++ b/browser/extensions/formautofill/content/formautofill.css
@@ -6,33 +6,19 @@
 #PopupAutoComplete > richlistbox > richlistitem[originaltype="autofill-footer"],
 #PopupAutoComplete > richlistbox > richlistitem[originaltype="autofill-insecureWarning"],
 #PopupAutoComplete > richlistbox > richlistitem[originaltype="autofill-clear-button"] {
   display: block;
   margin: 0;
   padding: 0;
   height: auto;
   min-height: auto;
-}
-
-#PopupAutoComplete > richlistbox > richlistitem[originaltype="autofill-profile"] {
-  -moz-binding: url("chrome://formautofill/content/formautofill.xml#autocomplete-profile-listitem");
+  -moz-binding: none;
 }
 
-#PopupAutoComplete > richlistbox > richlistitem[originaltype="autofill-footer"] {
-  -moz-binding: url("chrome://formautofill/content/formautofill.xml#autocomplete-profile-listitem-footer");
-}
-
-#PopupAutoComplete > richlistbox > richlistitem[originaltype="autofill-insecureWarning"] {
-  -moz-binding: url("chrome://formautofill/content/formautofill.xml#autocomplete-creditcard-insecure-field");
-}
-
-#PopupAutoComplete > richlistbox > richlistitem[originaltype="autofill-clear-button"] {
-  -moz-binding: url("chrome://formautofill/content/formautofill.xml#autocomplete-profile-listitem-clear-button");
-}
 /* Treat @collpased="true" as display: none similar to how it is for XUL elements.
  * https://developer.mozilla.org/en-US/docs/Web/CSS/visibility#Values */
 #PopupAutoComplete > richlistbox > richlistitem[originaltype="autofill-profile"][collapsed="true"],
 #PopupAutoComplete > richlistbox > richlistitem[originaltype="autofill-footer"][collapsed="true"],
 #PopupAutoComplete > richlistbox > richlistitem[originaltype="autofill-insecureWarning"][collapsed="true"],
 #PopupAutoComplete > richlistbox > richlistitem[originaltype="autofill-clear-button"][collapsed="true"] {
   display: none;
 }
--- a/toolkit/content/widgets/autocomplete.xml
+++ b/toolkit/content/widgets/autocomplete.xml
@@ -1055,20 +1055,26 @@
                 !(UNREUSEABLE_STYLES.includes(style) || UNREUSEABLE_STYLES.includes(originalType));
             }
 
             // If no reusable item available, then create a new item.
             if (!reusable) {
               let options = null;
               switch (style) {
                 case "autofill-profile":
+                  options = { is: "autocomplete-profile-listitem" };
+                  break;
                 case "autofill-footer":
+                  options = { is: "autocomplete-profile-listitem-footer" };
+                  break;
                 case "autofill-clear-button":
+                  options = { is: "autocomplete-profile-listitem-clear-button" };
+                  break;
                 case "autofill-insecureWarning":
-                  // implemented via XBL bindings, no CE for them
+                  options = { is: "autocomplete-creditcard-insecure-field" };
                   break;
                 case "insecureWarning":
                   options = { is: "autocomplete-richlistitem-insecure-warning" };
                   break;
                 default:
                   options = { is: "autocomplete-richlistitem" };
               }
               item = document.createXULElement("richlistitem", options);