Bug 1519486 - convert autocomplete-richlistitem XBL binding to CE, r=MattN
authorAlexander Surkov <surkov.alexander@gmail.com>
Thu, 24 Jan 2019 08:40:36 -0500
changeset 456525 e454c9f140445c9655907f54a5c7f760d4e0d430
parent 456524 3a0bd44bd411d8823aa062bb1f8fbb43ea53c87c
child 456526 0f6e106e3efde0812c69233ec8ae9a2d824a5135
push id35488
push userdvarga@mozilla.com
push dateSat, 02 Feb 2019 09:44:51 +0000
treeherdermozilla-central@d8cebb3b46cf [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersMattN
bugs1519486
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 1519486 - convert autocomplete-richlistitem XBL binding to CE, r=MattN
browser/base/content/test/performance/browser_urlbar_keyed_search.js
browser/base/content/test/performance/browser_urlbar_search.js
browser/extensions/formautofill/test/mochitest/mochitest.ini
browser/extensions/formautofill/test/mochitest/test_autofill_and_ordinal_forms.html
toolkit/components/extensions/test/browser/browser_ext_themes_autocomplete_popup.js
toolkit/components/satchel/test/parent_utils.js
toolkit/components/satchel/test/satchel_common.js
toolkit/content/widgets/autocomplete-richlistitem.js
toolkit/content/widgets/autocomplete.xml
toolkit/content/xul.css
--- a/browser/base/content/test/performance/browser_urlbar_keyed_search.js
+++ b/browser/base/content/test/performance/browser_urlbar_keyed_search.js
@@ -32,30 +32,30 @@ if (AppConstants.DEBUG ||
       "openPopup@chrome://global/content/bindings/autocomplete.xml",
       "set_popupOpen@chrome://global/content/bindings/autocomplete.xml",
     ],
   });
 }
 EXPECTED_REFLOWS_FIRST_OPEN.push(
   {
     stack: [
-      "_handleOverflow@chrome://global/content/bindings/autocomplete.xml",
-      "handleOverUnderflow@chrome://global/content/bindings/autocomplete.xml",
-      "_reuseAcItem@chrome://global/content/bindings/autocomplete.xml",
+      "_handleOverflow@chrome://global/content/elements/autocomplete-richlistitem.js",
+      "handleOverUnderflow@chrome://global/content/elements/autocomplete-richlistitem.js",
+      "_reuseAcItem@chrome://global/content/elements/autocomplete-richlistitem.js",
       "_appendCurrentResult@chrome://global/content/bindings/autocomplete.xml",
       "_invalidate@chrome://global/content/bindings/autocomplete.xml",
       "invalidate@chrome://global/content/bindings/autocomplete.xml",
     ],
     maxCount: 60, // This number should only ever go down - never up.
   },
 
   {
     stack: [
-      "_handleOverflow@chrome://global/content/bindings/autocomplete.xml",
-      "handleOverUnderflow@chrome://global/content/bindings/autocomplete.xml",
+      "_handleOverflow@chrome://global/content/elements/autocomplete-richlistitem.js",
+      "handleOverUnderflow@chrome://global/content/elements/autocomplete-richlistitem.js",
       "_openAutocompletePopup@chrome://browser/content/urlbarBindings.xml",
       "openAutocompletePopup@chrome://browser/content/urlbarBindings.xml",
       "openPopup@chrome://global/content/bindings/autocomplete.xml",
       "set_popupOpen@chrome://global/content/bindings/autocomplete.xml",
     ],
     maxCount: 6, // This number should only ever go down - never up.
   },
 
@@ -86,18 +86,18 @@ if (AppConstants.RELEASE_OR_BETA) {
       "_handleOverflow@chrome://global/content/bindings/autocomplete.xml",
       "_onOverflow@chrome://global/content/bindings/autocomplete.xml",
       "onoverflow@chrome://browser/content/browser.xul",
     ],
     maxCount: 6,
   },
   {
     stack: [
-      "_handleOverflow@chrome://global/content/bindings/autocomplete.xml",
-      "_adjustAcItem@chrome://global/content/bindings/autocomplete.xml",
+      "_handleOverflow@chrome://global/content/elements/autocomplete-richlistitem.js",
+      "_adjustAcItem@chrome://global/content/elements/autocomplete-richlistitem.js",
       "_appendCurrentResult@chrome://global/content/bindings/autocomplete.xml",
       "_invalidate@chrome://global/content/bindings/autocomplete.xml",
       "invalidate@chrome://global/content/bindings/autocomplete.xml",
     ],
     maxCount: 12,
   });
 }
 
--- a/browser/base/content/test/performance/browser_urlbar_search.js
+++ b/browser/base/content/test/performance/browser_urlbar_search.js
@@ -33,30 +33,30 @@ if (AppConstants.DEBUG ||
       "openPopup@chrome://global/content/bindings/autocomplete.xml",
       "set_popupOpen@chrome://global/content/bindings/autocomplete.xml",
     ],
   });
 }
 EXPECTED_REFLOWS_FIRST_OPEN.push(
   {
     stack: [
-      "_handleOverflow@chrome://global/content/bindings/autocomplete.xml",
-      "handleOverUnderflow@chrome://global/content/bindings/autocomplete.xml",
-      "_reuseAcItem@chrome://global/content/bindings/autocomplete.xml",
+      "_handleOverflow@chrome://global/content/elements/autocomplete-richlistitem.js",
+      "handleOverUnderflow@chrome://global/content/elements/autocomplete-richlistitem.js",
+      "_reuseAcItem@chrome://global/content/elements/autocomplete-richlistitem.js",
       "_appendCurrentResult@chrome://global/content/bindings/autocomplete.xml",
       "_invalidate@chrome://global/content/bindings/autocomplete.xml",
       "invalidate@chrome://global/content/bindings/autocomplete.xml",
     ],
     maxCount: 36, // This number should only ever go down - never up.
   },
 
   {
     stack: [
-      "_handleOverflow@chrome://global/content/bindings/autocomplete.xml",
-      "handleOverUnderflow@chrome://global/content/bindings/autocomplete.xml",
+      "_handleOverflow@chrome://global/content/elements/autocomplete-richlistitem.js",
+      "handleOverUnderflow@chrome://global/content/elements/autocomplete-richlistitem.js",
       "_openAutocompletePopup@chrome://browser/content/urlbarBindings.xml",
       "openAutocompletePopup@chrome://browser/content/urlbarBindings.xml",
       "openPopup@chrome://global/content/bindings/autocomplete.xml",
       "set_popupOpen@chrome://global/content/bindings/autocomplete.xml",
     ],
     maxCount: 6, // This number should only ever go down - never up.
   },
 
@@ -70,19 +70,19 @@ EXPECTED_REFLOWS_FIRST_OPEN.push(
     ],
   }
 );
 
 /* These reflows happen everytime the awesomebar panel opens. */
 const EXPECTED_REFLOWS_SECOND_OPEN = [
   {
     stack: [
-      "_handleOverflow@chrome://global/content/bindings/autocomplete.xml",
-      "handleOverUnderflow@chrome://global/content/bindings/autocomplete.xml",
-      "_reuseAcItem@chrome://global/content/bindings/autocomplete.xml",
+      "_handleOverflow@chrome://global/content/elements/autocomplete-richlistitem.js",
+      "handleOverUnderflow@chrome://global/content/elements/autocomplete-richlistitem.js",
+      "_reuseAcItem@chrome://global/content/elements/autocomplete-richlistitem.js",
       "_appendCurrentResult@chrome://global/content/bindings/autocomplete.xml",
       "_invalidate@chrome://global/content/bindings/autocomplete.xml",
       "invalidate@chrome://global/content/bindings/autocomplete.xml",
     ],
     maxCount: 24, // This number should only ever go down - never up.
   },
 
   // Bug 1359989
--- a/browser/extensions/formautofill/test/mochitest/mochitest.ini
+++ b/browser/extensions/formautofill/test/mochitest/mochitest.ini
@@ -1,16 +1,17 @@
 [DEFAULT]
 support-files =
   ../../../../../toolkit/components/satchel/test/satchel_common.js
   ../../../../../toolkit/components/satchel/test/parent_utils.js
   formautofill_common.js
   formautofill_parent_utils.js
 
 [test_address_level_1_submission.html]
+[test_autofill_and_ordinal_forms.html]
 [test_autofocus_form.html]
 skip-if = verify
 [test_basic_autocomplete_form.html]
 skip-if = verify
 [test_form_changes.html]
 [test_formautofill_preview_highlight.html]
 skip-if = verify
 [test_multi_locale_CA_address_form.html]
new file mode 100644
--- /dev/null
+++ b/browser/extensions/formautofill/test/mochitest/test_autofill_and_ordinal_forms.html
@@ -0,0 +1,115 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <meta charset="utf-8">
+  <title>Test autofill submit</title>
+  <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="text/javascript" src="/tests/SimpleTest/AddTask.js"></script>
+  <script type="text/javascript" src="formautofill_common.js"></script>
+  <script type="text/javascript" src="satchel_common.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+
+<script>
+/* import-globals-from ../../../../../toolkit/components/satchel/test/satchel_common.js */
+
+"use strict";
+
+let MOCK_STORAGE = [{
+  "given-name": "John",
+  "additional-name": "R",
+  "family-name": "Smith",
+  "organization": "Sesame Street",
+  "street-address": "123 Sesame Street.",
+  "tel": "+13453453456",
+  "country": "US",
+  "address-level1": "NY",
+}];
+
+initPopupListener();
+
+add_task(async function setupStorage() {
+  await addAddress(MOCK_STORAGE[0]);
+
+  await updateFormHistory([
+    {op: "add", fieldname: "username", value: "petya"},
+    {op: "add", fieldname: "current-password", value: "abrh#25_,K"},
+  ]);
+});
+
+add_task(async function check_switch_autofill_form_popup() {
+  await setInput("#tel", "");
+  synthesizeKey("KEY_ArrowDown");
+  await expectPopup();
+  checkMenuEntries(
+    [
+      `{"primary":"+13453453456","secondary":"123 Sesame Street."}`,
+      `{"primary":"","secondary":"","categories":["name","organization","address","tel"],"focusedCategory":"tel"}`,
+    ],
+    false
+  );
+
+  await testMenuEntry(0, "!(el instanceof MozElements.MozAutocompleteRichlistitem)");
+});
+
+add_task(async function check_switch_oridnal_form_popup() {
+  // We need an intentional wait here before switching form.
+  await sleep();
+  await setInput("#username", "");
+  synthesizeKey("KEY_ArrowDown");
+  await expectPopup();
+  checkMenuEntries(["petya"], false);
+
+  await testMenuEntry(0, "el instanceof MozElements.MozAutocompleteRichlistitem");
+});
+
+add_task(async function check_switch_autofill_form_popup_back() {
+  // We need an intentional wait here before switching form.
+  await sleep();
+  await setInput("#tel", "");
+  synthesizeKey("KEY_ArrowDown");
+  await expectPopup();
+  checkMenuEntries(
+    [
+      `{"primary":"+13453453456","secondary":"123 Sesame Street."}`,
+      `{"primary":"","secondary":"","categories":["name","organization","address","tel"],"focusedCategory":"tel"}`,
+    ],
+    false
+  );
+
+  await testMenuEntry(0, "!(el instanceof MozElements.MozAutocompleteRichlistitem)");
+});
+
+</script>
+
+<div>
+
+  <h2>Address form</h2>
+  <form class="alignedLabels">
+    <label>given-name: <input autocomplete="given-name" autofocus></label>
+    <label>additional-name: <input id="additional-name" autocomplete="additional-name"></label>
+    <label>family-name: <input autocomplete="family-name"></label>
+    <label>organization: <input autocomplete="organization"></label>
+    <label>street-address: <input autocomplete="street-address"></label>
+    <label>address-level1: <input autocomplete="address-level1"></label>
+    <label>postal-code: <input autocomplete="postal-code"></label>
+    <label>country: <input autocomplete="country"></label>
+    <label>country-name: <input autocomplete="country-name"></label>
+    <label>tel: <input id="tel" autocomplete="tel"></label>
+    <p>
+      <input type="submit" value="Submit">
+      <button type="reset">Reset</button>
+    </p>
+  </form>
+
+  <h2>Ordinal form</h2>
+  <form class="alignedLabels">
+    <label>username: <input id="username" autocomplete="username"></label>
+    <p><input type="submit" value="Username"></p>
+  </form>
+
+</div>
+</body>
+</html>
--- a/toolkit/components/extensions/test/browser/browser_ext_themes_autocomplete_popup.js
+++ b/toolkit/components/extensions/test/browser/browser_ext_themes_autocomplete_popup.js
@@ -145,22 +145,22 @@ add_task(async function test_popup_url()
                `Popup highlight background color should be set to ${POPUP_SELECTED_COLOR}`);
 
   Assert.equal(resultCS.color,
                `rgb(${hexToRGB(POPUP_SELECTED_TEXT_COLOR).join(", ")})`,
                `Popup highlight color should be set to ${POPUP_SELECTED_TEXT_COLOR}`);
 
   results[1].removeAttribute("selected");
 
-  let urlText = document.getAnonymousElementByAttribute(results[1], "anonid", "url-text");
+  let urlText = results[1]._urlText;
   Assert.equal(window.getComputedStyle(urlText).color,
                `rgb(${hexToRGB(POPUP_URL_COLOR_DARK).join(", ")})`,
                `Urlbar popup url color should be set to ${POPUP_URL_COLOR_DARK}`);
 
-  let actionText = document.getAnonymousElementByAttribute(results[1], "anonid", "action-text");
+  let actionText = results[1]._actionText;
   Assert.equal(window.getComputedStyle(actionText).color,
                `rgb(${hexToRGB(POPUP_ACTION_COLOR_DARK).join(", ")})`,
                `Urlbar popup action color should be set to ${POPUP_ACTION_COLOR_DARK}`);
 
   let root = document.documentElement;
   Assert.equal(root.getAttribute("lwt-popup-brighttext"),
                "",
                "brighttext should not be set!");
@@ -198,29 +198,29 @@ add_task(async function test_popup_url()
 
   await extension.startup();
 
   popupCS = window.getComputedStyle(popup);
   Assert.equal(popupCS.color,
                `rgb(${hexToRGB(POPUP_TEXT_COLOR_BRIGHT).join(", ")})`,
                `Popup color should be set to ${POPUP_TEXT_COLOR_BRIGHT}`);
 
-  urlText = document.getAnonymousElementByAttribute(results[1], "anonid", "url-text");
+  urlText = results[1]._urlText;
   Assert.equal(window.getComputedStyle(urlText).color,
                `rgb(${hexToRGB(POPUP_URL_COLOR_BRIGHT).join(", ")})`,
                `Urlbar popup url color should be set to ${POPUP_URL_COLOR_BRIGHT}`);
 
-  actionText = document.getAnonymousElementByAttribute(results[1], "anonid", "action-text");
+  actionText = results[1]._actionText;
   Assert.equal(window.getComputedStyle(actionText).color,
                `rgb(${hexToRGB(POPUP_ACTION_COLOR_BRIGHT).join(", ")})`,
                `Urlbar popup action color should be set to ${POPUP_ACTION_COLOR_BRIGHT}`);
 
   // Since brighttext is enabled, the seperator color should be
   // POPUP_TEXT_COLOR_BRIGHT with added alpha.
-  let separator = document.getAnonymousElementByAttribute(results[1], "anonid", "separator");
+  let separator = results[1]._separator;
   Assert.equal(window.getComputedStyle(separator).color,
                `rgba(${hexToRGB(POPUP_TEXT_COLOR_BRIGHT).join(", ")}, 0.5)`,
                `Urlbar popup separator color should be set to ${POPUP_TEXT_COLOR_BRIGHT} with alpha`);
 
   Assert.equal(root.getAttribute("lwt-popup-brighttext"),
                "true",
                "brighttext should be set to true!");
   Assert.equal(root.getAttribute("lwt-popup-darktext"),
@@ -240,13 +240,13 @@ add_task(async function test_popup_url()
 
   // Calculate what GrayText should be. May differ between platforms.
   let span = document.createXULElement("span");
   span.style.color = "GrayText";
   document.documentElement.appendChild(span);
   let GRAY_TEXT = window.getComputedStyle(span).color;
   span.remove();
 
-  separator = document.getAnonymousElementByAttribute(results[1], "anonid", "separator");
+  separator = results[1]._separator;
   Assert.equal(window.getComputedStyle(separator).color,
                GRAY_TEXT,
                `Urlbar popup separator color should be set to ${GRAY_TEXT}`);
 });
--- a/toolkit/components/satchel/test/parent_utils.js
+++ b/toolkit/components/satchel/test/parent_utils.js
@@ -96,16 +96,26 @@ var ParentUtils = {
     ContentTaskUtils.waitForCondition(() => {
       return gAutocompletePopup.popupOpen &&
              gAutocompletePopup.selectedIndex === expectedIndex;
     }, "Checking selected index").then(() => {
       sendAsyncMessage("gotSelectedIndex");
     });
   },
 
+  testMenuEntry(index, statement) {
+    ContentTaskUtils.waitForCondition(() => {
+      let el = gAutocompletePopup.richlistbox.getItemAtIndex(index);
+      let testFunc = new Services.ww.activeWindow.Function("el", `return ${statement}`);
+      return gAutocompletePopup.popupOpen && el && testFunc(el);
+    }, "Testing menu entry").then(() => {
+      sendAsyncMessage("menuEntryTested");
+    });
+  },
+
   getPopupState() {
     sendAsyncMessage("gotPopupState", {
       open: gAutocompletePopup.popupOpen,
       selectedIndex: gAutocompletePopup.selectedIndex,
       direction: gAutocompletePopup.style.direction,
     });
   },
 
@@ -137,16 +147,19 @@ addMessageListener("countEntries", ({ na
 
 addMessageListener("waitForMenuChange", ({ expectedCount, expectedFirstValue }) => {
   ParentUtils.checkRowCount(expectedCount, expectedFirstValue);
 });
 
 addMessageListener("waitForSelectedIndex", ({ expectedIndex }) => {
   ParentUtils.checkSelectedIndex(expectedIndex);
 });
+addMessageListener("waitForMenuEntryTest", ({ index, statement }) => {
+  ParentUtils.testMenuEntry(index, statement);
+});
 
 addMessageListener("getPopupState", () => {
   ParentUtils.getPopupState();
 });
 
 addMessageListener("addObserver", () => {
   Services.obs.addObserver(ParentUtils, "satchel-storage-changed");
 });
--- a/toolkit/components/satchel/test/satchel_common.js
+++ b/toolkit/components/satchel/test/satchel_common.js
@@ -201,16 +201,26 @@ function notifySelectedIndex(expectedInd
       if (then) {
         then();
       }
       resolve();
     });
   });
 }
 
+function testMenuEntry(index, statement) {
+  return new Promise(resolve => {
+    gChromeScript.sendAsyncMessage("waitForMenuEntryTest", { index, statement });
+    gChromeScript.addMessageListener("menuEntryTested", function changed() {
+      gChromeScript.removeMessageListener("menuEntryTested", changed);
+      resolve();
+    });
+  });
+}
+
 function getPopupState(then = null) {
   return new Promise(resolve => {
     gChromeScript.sendAsyncMessage("getPopupState");
     gChromeScript.addMessageListener("gotPopupState", function listener(state) {
       gChromeScript.removeMessageListener("gotPopupState", listener);
       if (then) {
         then(state);
       }
--- a/toolkit/content/widgets/autocomplete-richlistitem.js
+++ b/toolkit/content/widgets/autocomplete-richlistitem.js
@@ -4,17 +4,17 @@
 
 "use strict";
 
 // This is loaded into all XUL windows. Wrap in a block to prevent
 // leaking to window scope.
 {
 const {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm");
 
-class MozAutocompleteRichlistitem extends MozElements.MozRichlistitem {
+MozElements.MozAutocompleteRichlistitem = class MozAutocompleteRichlistitem extends MozElements.MozRichlistitem {
   constructor() {
     super();
 
     /**
      * This overrides listitem's mousedown handler because we want to set the
      * selected item even when the shift or accel keys are pressed.
      */
     this.addEventListener("mousedown", (event) => {
@@ -57,18 +57,16 @@ class MozAutocompleteRichlistitem extend
   connectedCallback() {
     if (this.delayConnectedCallback()) {
       return;
     }
 
     this.textContent = "";
     this.appendChild(MozXULElement.parseXULToFragment(this._markup));
 
-    this.setAttribute("align", "center");
-
     this._boundaryCutoff = null;
     this._inOverflow = false;
 
     this._updateAttributes();
     this._adjustAcItem();
   }
 
   static get observedAttributes() {
@@ -903,24 +901,24 @@ class MozAutocompleteRichlistitem extend
       // search components.
       action.params = {
         url: params,
       };
     }
 
     return action;
   }
-}
+};
 
 MozXULElement.implementCustomInterface(
-  MozAutocompleteRichlistitem,
+  MozElements.MozAutocompleteRichlistitem,
   [Ci.nsIDOMXULSelectControlItemElement]
 );
 
-class MozAutocompleteRichlistitemInsecureWarning extends MozAutocompleteRichlistitem {
+class MozAutocompleteRichlistitemInsecureWarning extends MozElements.MozAutocompleteRichlistitem {
   constructor() {
     super();
 
     this.addEventListener("click", (event) => {
       if (event.button != 0) {
         return;
       }
 
@@ -1014,12 +1012,16 @@ class MozAutocompleteRichlistitemInsecur
   /**
    * Override _getSearchTokens to have the Learn More text emphasized
    */
   _getSearchTokens(aSearch) {
     return [this._learnMoreString.toLowerCase()];
   }
 }
 
+customElements.define("autocomplete-richlistitem", MozElements.MozAutocompleteRichlistitem, {
+  extends: "richlistitem",
+});
+
 customElements.define("autocomplete-richlistitem-insecure-warning", MozAutocompleteRichlistitemInsecureWarning, {
   extends: "richlistitem",
 });
 }
--- a/toolkit/content/widgets/autocomplete.xml
+++ b/toolkit/content/widgets/autocomplete.xml
@@ -1009,31 +1009,31 @@
           var existingItemsCount = this.richlistbox.children.length;
 
           // Process maxRows per chunk to improve performance and user experience
           for (let i = 0; i < this.maxRows; i++) {
             if (this._currentIndex >= matchCount) {
               break;
             }
             let item;
-            let reusable = false;
             let itemExists = this._currentIndex < existingItemsCount;
 
             let originalValue, originalText, originalType;
             let style = controller.getStyleAt(this._currentIndex);
             let value =
               style && style.includes("autofill") ?
               controller.getFinalCompleteValueAt(this._currentIndex) :
               controller.getValueAt(this._currentIndex);
             let label = controller.getLabelAt(this._currentIndex);
             let comment = controller.getCommentAt(this._currentIndex);
             let image = controller.getImageAt(this._currentIndex);
             // trim the leading/trailing whitespace
             let trimmedSearchString = controller.searchString.replace(/^\s+/, "").replace(/\s+$/, "");
 
+            let reusable = false;
             if (itemExists) {
               item = this.richlistbox.children[this._currentIndex];
 
               // Url may be a modified version of value, see _adjustAcItem().
               originalValue = item.getAttribute("url") || item.getAttribute("ac-value");
               originalText = item.getAttribute("ac-text");
               originalType = item.getAttribute("originaltype");
 
@@ -1044,21 +1044,36 @@
                 "autofill-footer",
                 "autofill-clear-button",
                 "autofill-insecureWarning",
               ];
               // Reuse the item when its style is exactly equal to the previous style or
               // neither of their style are in the UNREUSEABLE_STYLES.
               reusable = originalType === style ||
                 !(UNREUSEABLE_STYLES.includes(style) || UNREUSEABLE_STYLES.includes(originalType));
-            } else {
-              // need to create a new item
-              let options = style == "insecureWarning" ?
-                { is: "autocomplete-richlistitem-insecure-warning" } : null;
+            }
+
+            // If no reusable item available, then create a new item.
+            if (!reusable) {
+              let options = null;
+              switch (style) {
+                case "autofill-profile":
+                case "autofill-footer":
+                case "autofill-clear-button":
+                case "autofill-insecureWarning":
+                  // implemented via XBL bindings, no CE for them
+                  break;
+                case "insecureWarning":
+                  options = { is: "autocomplete-richlistitem-insecure-warning" };
+                  break;
+                default:
+                  options = { is: "autocomplete-richlistitem" };
+              }
               item = document.createXULElement("richlistitem", options);
+              item.className = "autocomplete-richlistitem";
             }
 
             item.setAttribute("dir", this.style.direction);
             item.setAttribute("ac-image", image);
             item.setAttribute("ac-value", value);
             item.setAttribute("ac-label", label);
             item.setAttribute("ac-comment", comment);
             item.setAttribute("ac-text", trimmedSearchString);
@@ -1081,30 +1096,28 @@
               }
             } else {
               if (typeof item._cleanup == "function") {
                 item._cleanup();
               }
               item.setAttribute("originaltype", style);
             }
 
-            if (itemExists) {
+            if (reusable) {
               // Adjust only when the result's type is reusable for existing
               // item's. Otherwise, we might insensibly call old _adjustAcItem()
               // as new binding has not been attached yet.
               // We don't need to worry about switching to new binding, since
               // _adjustAcItem() will fired by its own constructor accordingly.
-              if (reusable) {
-                item._adjustAcItem();
-              }
+              item._adjustAcItem();
               item.collapsed = false;
+            } else if (itemExists) {
+              let oldItem = this.richlistbox.children[this._currentIndex];
+              this.richlistbox.replaceChild(item, oldItem);
             } else {
-              // set the class at the end so we can use the attributes
-              // in the xbl constructor
-              item.className = "autocomplete-richlistitem";
               this.richlistbox.appendChild(item);
             }
 
             this._currentIndex++;
           }
 
           if (typeof this.onResultsAdded == "function") {
             // The items bindings may not be attached yet, so we must delay this
--- a/toolkit/content/xul.css
+++ b/toolkit/content/xul.css
@@ -577,18 +577,19 @@ panel[type="autocomplete-richlistbox"] {
 }
 
 .autocomplete-richlistbox {
   -moz-user-focus: ignore;
   overflow-x: hidden !important;
 }
 
 .autocomplete-richlistitem {
-  -moz-binding: url("chrome://global/content/bindings/autocomplete.xml#autocomplete-richlistitem");
+  -moz-binding: none;
   -moz-box-orient: vertical;
+  -moz-box-align: center;
   overflow: -moz-hidden-unscrollable;
 }
 
 %endif
 
 /* The following rule is here to fix bug 96899 (and now 117952).
    Somehow trees create a situation
    in which a popupset flows itself as if its popup child is directly within it