forked from mirrors/gecko-dev
		
	
		
			
				
	
	
		
			636 lines
		
	
	
	
		
			20 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			636 lines
		
	
	
	
		
			20 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| /* 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";
 | |
| 
 | |
| // This is loaded into all XUL windows. Wrap in a block to prevent
 | |
| // leaking to window scope.
 | |
| {
 | |
|   const MozPopupElement = MozElements.MozElementMixin(XULPopupElement);
 | |
|   MozElements.MozAutocompleteRichlistboxPopup = class MozAutocompleteRichlistboxPopup extends (
 | |
|     MozPopupElement
 | |
|   ) {
 | |
|     constructor() {
 | |
|       super();
 | |
| 
 | |
|       this.attachShadow({ mode: "open" });
 | |
| 
 | |
|       {
 | |
|         let slot = document.createElement("slot");
 | |
|         slot.part = "content";
 | |
|         this.shadowRoot.appendChild(slot);
 | |
|       }
 | |
| 
 | |
|       this.mInput = null;
 | |
|       this.mPopupOpen = false;
 | |
|       this._currentIndex = 0;
 | |
|       this._disabledItemClicked = false;
 | |
| 
 | |
|       this.setListeners();
 | |
|     }
 | |
| 
 | |
|     initialize() {
 | |
|       this.setAttribute("ignorekeys", "true");
 | |
|       this.setAttribute("level", "top");
 | |
|       this.setAttribute("consumeoutsideclicks", "never");
 | |
| 
 | |
|       this.textContent = "";
 | |
|       this.appendChild(this.constructor.fragment);
 | |
| 
 | |
|       /**
 | |
|        * This is the default number of rows that we give the autocomplete
 | |
|        * popup when the textbox doesn't have a "maxrows" attribute
 | |
|        * for us to use.
 | |
|        */
 | |
|       this.defaultMaxRows = 6;
 | |
| 
 | |
|       /**
 | |
|        * In some cases (e.g. when the input's dropmarker button is clicked),
 | |
|        * the input wants to display a popup with more rows. In that case, it
 | |
|        * should increase its maxRows property and store the "normal" maxRows
 | |
|        * in this field. When the popup is hidden, we restore the input's
 | |
|        * maxRows to the value stored in this field.
 | |
|        *
 | |
|        * This field is set to -1 between uses so that we can tell when it's
 | |
|        * been set by the input and when we need to set it in the popupshowing
 | |
|        * handler.
 | |
|        */
 | |
|       this._normalMaxRows = -1;
 | |
|       this._previousSelectedIndex = -1;
 | |
|       this.mLastMoveTime = Date.now();
 | |
|       this.mousedOverIndex = -1;
 | |
|       this._richlistbox = this.querySelector(".autocomplete-richlistbox");
 | |
| 
 | |
|       if (!this.listEvents) {
 | |
|         this.listEvents = {
 | |
|           handleEvent: event => {
 | |
|             if (!this.parentNode) {
 | |
|               return;
 | |
|             }
 | |
| 
 | |
|             switch (event.type) {
 | |
|               case "mousedown":
 | |
|                 this._disabledItemClicked =
 | |
|                   !!event.target.closest("richlistitem")?.disabled;
 | |
|                 break;
 | |
|               case "mouseup":
 | |
|                 // Don't call onPopupClick for the scrollbar buttons, thumb,
 | |
|                 // slider, etc. If we hit the richlistbox and not a
 | |
|                 // richlistitem, we ignore the event.
 | |
|                 if (
 | |
|                   event.target.closest("richlistbox,richlistitem").localName ==
 | |
|                     "richlistitem" &&
 | |
|                   !this._disabledItemClicked
 | |
|                 ) {
 | |
|                   this.onPopupClick(event);
 | |
|                 }
 | |
|                 this._disabledItemClicked = false;
 | |
|                 break;
 | |
|               case "mousemove":
 | |
|                 if (Date.now() - this.mLastMoveTime <= 30) {
 | |
|                   return;
 | |
|                 }
 | |
| 
 | |
|                 let item = event.target.closest("richlistbox,richlistitem");
 | |
| 
 | |
|                 // If we hit the richlistbox and not a richlistitem, we ignore
 | |
|                 // the event.
 | |
|                 if (item.localName == "richlistbox") {
 | |
|                   return;
 | |
|                 }
 | |
| 
 | |
|                 let index = this.richlistbox.getIndexOfItem(item);
 | |
| 
 | |
|                 this.mousedOverIndex = index;
 | |
| 
 | |
|                 if (item.selectedByMouseOver) {
 | |
|                   this.richlistbox.selectedIndex = index;
 | |
|                 }
 | |
| 
 | |
|                 this.mLastMoveTime = Date.now();
 | |
|                 break;
 | |
|             }
 | |
|           },
 | |
|         };
 | |
|       }
 | |
|       this.richlistbox.addEventListener("mousedown", this.listEvents);
 | |
|       this.richlistbox.addEventListener("mouseup", this.listEvents);
 | |
|       this.richlistbox.addEventListener("mousemove", this.listEvents);
 | |
|     }
 | |
| 
 | |
|     get richlistbox() {
 | |
|       if (!this._richlistbox) {
 | |
|         this.initialize();
 | |
|       }
 | |
|       return this._richlistbox;
 | |
|     }
 | |
| 
 | |
|     static get markup() {
 | |
|       return `
 | |
|       <richlistbox class="autocomplete-richlistbox"/>
 | |
|     `;
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * nsIAutoCompletePopup
 | |
|      */
 | |
|     get input() {
 | |
|       return this.mInput;
 | |
|     }
 | |
| 
 | |
|     get overrideValue() {
 | |
|       return null;
 | |
|     }
 | |
| 
 | |
|     get popupOpen() {
 | |
|       return this.mPopupOpen;
 | |
|     }
 | |
| 
 | |
|     get maxRows() {
 | |
|       return (this.mInput && this.mInput.maxRows) || this.defaultMaxRows;
 | |
|     }
 | |
| 
 | |
|     set selectedIndex(val) {
 | |
|       if (val != this.richlistbox.selectedIndex) {
 | |
|         this._previousSelectedIndex = this.richlistbox.selectedIndex;
 | |
|       }
 | |
|       this.richlistbox.selectedIndex = val;
 | |
|       // Since ensureElementIsVisible may cause an expensive Layout flush,
 | |
|       // invoke it only if there may be a scrollbar, so if we could fetch
 | |
|       // more results than we can show at once.
 | |
|       // maxResults is the maximum number of fetched results, maxRows is the
 | |
|       // maximum number of rows we show at once, without a scrollbar.
 | |
|       if (this.mPopupOpen && this.maxResults > this.maxRows) {
 | |
|         // when clearing the selection (val == -1, so selectedItem will be
 | |
|         // null), we want to scroll back to the top.  see bug #406194
 | |
|         this.richlistbox.ensureElementIsVisible(
 | |
|           this.richlistbox.selectedItem || this.richlistbox.firstElementChild
 | |
|         );
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     get selectedIndex() {
 | |
|       return this.richlistbox.selectedIndex;
 | |
|     }
 | |
| 
 | |
|     get maxResults() {
 | |
|       // This is how many richlistitems will be kept around.
 | |
|       // Note, this getter may be overridden, or instances
 | |
|       // can have the nomaxresults attribute set to have no
 | |
|       // limit.
 | |
|       if (this.getAttribute("nomaxresults") == "true") {
 | |
|         return Infinity;
 | |
|       }
 | |
|       return 20;
 | |
|     }
 | |
| 
 | |
|     get matchCount() {
 | |
|       return Math.min(this.mInput.controller.matchCount, this.maxResults);
 | |
|     }
 | |
| 
 | |
|     get overflowPadding() {
 | |
|       return Number(this.getAttribute("overflowpadding"));
 | |
|     }
 | |
| 
 | |
|     set view(val) {}
 | |
| 
 | |
|     get view() {
 | |
|       return this.mInput.controller;
 | |
|     }
 | |
| 
 | |
|     closePopup() {
 | |
|       if (this.mPopupOpen) {
 | |
|         this.hidePopup();
 | |
|         this.style.removeProperty("--panel-width");
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     getNextIndex(aReverse, aAmount, aIndex, aMaxRow) {
 | |
|       if (aMaxRow < 0) {
 | |
|         return -1;
 | |
|       }
 | |
| 
 | |
|       do {
 | |
|         var newIdx = aIndex + (aReverse ? -1 : 1) * aAmount;
 | |
|         if (
 | |
|           (aReverse && aIndex == -1) ||
 | |
|           (newIdx > aMaxRow && aIndex != aMaxRow)
 | |
|         ) {
 | |
|           newIdx = aMaxRow;
 | |
|         } else if ((!aReverse && aIndex == -1) || (newIdx < 0 && aIndex != 0)) {
 | |
|           newIdx = 0;
 | |
|         }
 | |
| 
 | |
|         if (
 | |
|           (newIdx < 0 && aIndex == 0) ||
 | |
|           (newIdx > aMaxRow && aIndex == aMaxRow)
 | |
|         ) {
 | |
|           aIndex = -1;
 | |
|         } else {
 | |
|           aIndex = newIdx;
 | |
|         }
 | |
| 
 | |
|         if (aIndex == -1) {
 | |
|           return -1;
 | |
|         }
 | |
|       } while (
 | |
|         !this.richlistbox.canUserSelect(this.richlistbox.getItemAtIndex(aIndex))
 | |
|       );
 | |
| 
 | |
|       return aIndex;
 | |
|     }
 | |
| 
 | |
|     onPopupClick(aEvent) {
 | |
|       this.input.controller.handleEnter(true, aEvent);
 | |
|     }
 | |
| 
 | |
|     onSearchBegin() {
 | |
|       this.mousedOverIndex = -1;
 | |
| 
 | |
|       if (typeof this._onSearchBegin == "function") {
 | |
|         this._onSearchBegin();
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     openAutocompletePopup(aInput, aElement) {
 | |
|       // until we have "baseBinding", (see bug #373652) this allows
 | |
|       // us to override openAutocompletePopup(), but still call
 | |
|       // the method on the base class
 | |
|       this._openAutocompletePopup(aInput, aElement);
 | |
|     }
 | |
| 
 | |
|     _openAutocompletePopup(aInput, aElement) {
 | |
|       if (!this._richlistbox) {
 | |
|         this.initialize();
 | |
|       }
 | |
| 
 | |
|       if (!this.mPopupOpen) {
 | |
|         // It's possible that the panel is hidden initially
 | |
|         // to avoid impacting startup / new window performance
 | |
|         aInput.popup.hidden = false;
 | |
| 
 | |
|         this.mInput = aInput;
 | |
|         // clear any previous selection, see bugs 400671 and 488357
 | |
|         this.selectedIndex = -1;
 | |
| 
 | |
|         var width = aElement.getBoundingClientRect().width;
 | |
|         this.style.setProperty("--panel-width", Math.max(width, 100) + "px");
 | |
|         // invalidate() depends on the width attribute
 | |
|         this._invalidate();
 | |
| 
 | |
|         this.openPopup(aElement, "after_start", 0, 0, false, false);
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     invalidate(reason) {
 | |
|       // Don't bother doing work if we're not even showing
 | |
|       if (!this.mPopupOpen) {
 | |
|         return;
 | |
|       }
 | |
| 
 | |
|       this._invalidate(reason);
 | |
|     }
 | |
| 
 | |
|     _invalidate(reason) {
 | |
|       // collapsed if no matches
 | |
|       this.richlistbox.collapsed = this.matchCount == 0;
 | |
| 
 | |
|       // Update the richlistbox height.
 | |
|       if (this._adjustHeightRAFToken) {
 | |
|         cancelAnimationFrame(this._adjustHeightRAFToken);
 | |
|         this._adjustHeightRAFToken = null;
 | |
|       }
 | |
| 
 | |
|       if (this.mPopupOpen) {
 | |
|         this._adjustHeightOnPopupShown = false;
 | |
|         this._adjustHeightRAFToken = requestAnimationFrame(() =>
 | |
|           this.adjustHeight()
 | |
|         );
 | |
|       } else {
 | |
|         this._adjustHeightOnPopupShown = true;
 | |
|       }
 | |
| 
 | |
|       this._currentIndex = 0;
 | |
|       if (this._appendResultTimeout) {
 | |
|         clearTimeout(this._appendResultTimeout);
 | |
|       }
 | |
|       this._appendCurrentResult(reason);
 | |
|     }
 | |
| 
 | |
|     _collapseUnusedItems() {
 | |
|       let existingItemsCount = this.richlistbox.children.length;
 | |
|       for (let i = this.matchCount; i < existingItemsCount; ++i) {
 | |
|         this.richlistbox.children[i].collapsed = true;
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     adjustHeight() {
 | |
|       // Figure out how many rows to show
 | |
|       let rows = this.richlistbox.children;
 | |
|       let numRows = Math.min(this.matchCount, this.maxRows, rows.length);
 | |
| 
 | |
|       // Default the height to 0 if we have no rows to show
 | |
|       let height = 0;
 | |
|       if (numRows) {
 | |
|         let firstRowRect = rows[0].getBoundingClientRect();
 | |
|         if (this._rlbPadding == undefined) {
 | |
|           let style = window.getComputedStyle(this.richlistbox);
 | |
|           let paddingTop = parseInt(style.paddingTop) || 0;
 | |
|           let paddingBottom = parseInt(style.paddingBottom) || 0;
 | |
|           this._rlbPadding = paddingTop + paddingBottom;
 | |
|         }
 | |
| 
 | |
|         // The class `forceHandleUnderflow` is for the item might need to
 | |
|         // handle OverUnderflow or Overflow when the height of an item will
 | |
|         // be changed dynamically.
 | |
|         for (let i = 0; i < numRows; i++) {
 | |
|           if (rows[i].classList.contains("forceHandleUnderflow")) {
 | |
|             rows[i].handleOverUnderflow();
 | |
|           }
 | |
|         }
 | |
| 
 | |
|         let lastRowRect = rows[numRows - 1].getBoundingClientRect();
 | |
|         // Calculate the height to have the first row to last row shown
 | |
|         height = lastRowRect.bottom - firstRowRect.top + this._rlbPadding;
 | |
|       }
 | |
| 
 | |
|       this._collapseUnusedItems();
 | |
| 
 | |
|       // We need to get the ceiling of the calculated value to ensure that the
 | |
|       // box fully contains all of its contents and doesn't cause a scrollbar.
 | |
|       this.richlistbox.style.height = Math.ceil(height) + "px";
 | |
|     }
 | |
| 
 | |
|     _appendCurrentResult(invalidateReason) {
 | |
|       var controller = this.mInput.controller;
 | |
|       var matchCount = this.matchCount;
 | |
|       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 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+$/, "");
 | |
| 
 | |
|         // Generic items can pack their details as JSON inside label
 | |
|         try {
 | |
|           const details = JSON.parse(label);
 | |
|           if (details.title) {
 | |
|             value = details.title;
 | |
|             label = details.subtitle ?? "";
 | |
|           }
 | |
|         } catch {}
 | |
| 
 | |
|         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");
 | |
| 
 | |
|           // The styles on the list which have different <content> structure and overrided
 | |
|           // _adjustAcItem() are unreusable.
 | |
|           const UNREUSEABLE_STYLES = [
 | |
|             "autofill",
 | |
|             "action",
 | |
|             "status",
 | |
|             "generatedPassword",
 | |
|             "generic",
 | |
|             "importableLearnMore",
 | |
|             "importableLogins",
 | |
|             "insecureWarning",
 | |
|             "loginsFooter",
 | |
|             "loginWithOrigin",
 | |
|           ];
 | |
|           // 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)
 | |
|             );
 | |
|         }
 | |
| 
 | |
|         // If no reusable item available, then create a new item.
 | |
|         if (!reusable) {
 | |
|           let options = null;
 | |
|           switch (style) {
 | |
|             case "autofill":
 | |
|               options = { is: "autocomplete-autofill-richlistitem" };
 | |
|               break;
 | |
|             case "action":
 | |
|               options = { is: "autocomplete-action-richlistitem" };
 | |
|               break;
 | |
|             case "status":
 | |
|               options = { is: "autocomplete-status-richlistitem" };
 | |
|               break;
 | |
|             case "generic":
 | |
|               options = { is: "autocomplete-two-line-richlistitem" };
 | |
|               break;
 | |
|             case "importableLearnMore":
 | |
|               options = {
 | |
|                 is: "autocomplete-importable-learn-more-richlistitem",
 | |
|               };
 | |
|               break;
 | |
|             case "importableLogins":
 | |
|               options = { is: "autocomplete-importable-logins-richlistitem" };
 | |
|               break;
 | |
|             case "generatedPassword":
 | |
|               options = { is: "autocomplete-generated-password-richlistitem" };
 | |
|               break;
 | |
|             case "insecureWarning":
 | |
|               options = { is: "autocomplete-richlistitem-insecure-warning" };
 | |
|               break;
 | |
|             case "loginsFooter":
 | |
|               options = { is: "autocomplete-richlistitem-logins-footer" };
 | |
|               break;
 | |
|             case "loginWithOrigin":
 | |
|               options = { is: "autocomplete-login-richlistitem" };
 | |
|               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);
 | |
| 
 | |
|         // Completely reuse the existing richlistitem for invalidation
 | |
|         // due to new results, but only when: the item is the same, *OR*
 | |
|         // we are about to replace the currently moused-over item, to
 | |
|         // avoid surprising the user.
 | |
|         let iface = Ci.nsIAutoCompletePopup;
 | |
|         if (
 | |
|           reusable &&
 | |
|           originalText == trimmedSearchString &&
 | |
|           invalidateReason == iface.INVALIDATE_REASON_NEW_RESULT &&
 | |
|           (originalValue == value ||
 | |
|             this.mousedOverIndex === this._currentIndex)
 | |
|         ) {
 | |
|           // try to re-use the existing item
 | |
|           item._reuseAcItem();
 | |
|           this._currentIndex++;
 | |
|           continue;
 | |
|         } else {
 | |
|           if (typeof item._cleanup == "function") {
 | |
|             item._cleanup();
 | |
|           }
 | |
|           item.setAttribute("originaltype", style);
 | |
|         }
 | |
| 
 | |
|         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.
 | |
|           item._adjustAcItem();
 | |
|           item.collapsed = false;
 | |
|         } else if (itemExists) {
 | |
|           let oldItem = this.richlistbox.children[this._currentIndex];
 | |
|           this.richlistbox.replaceChild(item, oldItem);
 | |
|         } else {
 | |
|           this.richlistbox.appendChild(item);
 | |
|         }
 | |
| 
 | |
|         this._currentIndex++;
 | |
|       }
 | |
| 
 | |
|       if (typeof this.onResultsAdded == "function") {
 | |
|         // The items bindings may not be attached yet, so we must delay this
 | |
|         // before we can properly handle items properly without breaking
 | |
|         // the richlistbox.
 | |
|         Services.tm.dispatchToMainThread(() => this.onResultsAdded());
 | |
|       }
 | |
| 
 | |
|       if (this._currentIndex < matchCount) {
 | |
|         // yield after each batch of items so that typing the url bar is
 | |
|         // responsive
 | |
|         this._appendResultTimeout = setTimeout(
 | |
|           () => this._appendCurrentResult(),
 | |
|           0
 | |
|         );
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     selectBy(aReverse, aPage) {
 | |
|       try {
 | |
|         var amount = aPage ? 5 : 1;
 | |
| 
 | |
|         // because we collapsed unused items, we can't use this.richlistbox.getRowCount(), we need to use the matchCount
 | |
|         this.selectedIndex = this.getNextIndex(
 | |
|           aReverse,
 | |
|           amount,
 | |
|           this.selectedIndex,
 | |
|           this.matchCount - 1
 | |
|         );
 | |
|         if (this.selectedIndex == -1) {
 | |
|           this.input._focus();
 | |
|         }
 | |
|       } catch (ex) {
 | |
|         // do nothing - occasionally timer-related js errors happen here
 | |
|         // e.g. "this.selectedIndex has no properties", when you type fast and hit a
 | |
|         // navigation key before this popup has opened
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     disconnectedCallback() {
 | |
|       if (this.listEvents) {
 | |
|         this.richlistbox.removeEventListener("mousedown", this.listEvents);
 | |
|         this.richlistbox.removeEventListener("mouseup", this.listEvents);
 | |
|         this.richlistbox.removeEventListener("mousemove", this.listEvents);
 | |
|         delete this.listEvents;
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     setListeners() {
 | |
|       this.addEventListener("popupshowing", () => {
 | |
|         // If normalMaxRows wasn't already set by the input, then set it here
 | |
|         // so that we restore the correct number when the popup is hidden.
 | |
| 
 | |
|         // Null-check this.mInput; see bug 1017914
 | |
|         if (this._normalMaxRows < 0 && this.mInput) {
 | |
|           this._normalMaxRows = this.mInput.maxRows;
 | |
|         }
 | |
| 
 | |
|         this.mPopupOpen = true;
 | |
|       });
 | |
| 
 | |
|       this.addEventListener("popupshown", () => {
 | |
|         if (this._adjustHeightOnPopupShown) {
 | |
|           this._adjustHeightOnPopupShown = false;
 | |
|           this.adjustHeight();
 | |
|         }
 | |
|       });
 | |
| 
 | |
|       this.addEventListener("popuphiding", () => {
 | |
|         var isListActive = true;
 | |
|         if (this.selectedIndex == -1) {
 | |
|           isListActive = false;
 | |
|         }
 | |
|         this.input.controller.stopSearch();
 | |
| 
 | |
|         this.mPopupOpen = false;
 | |
| 
 | |
|         // Reset the maxRows property to the cached "normal" value (if there's
 | |
|         // any), and reset normalMaxRows so that we can detect whether it was set
 | |
|         // by the input when the popupshowing handler runs.
 | |
| 
 | |
|         // Null-check this.mInput; see bug 1017914
 | |
|         if (this.mInput && this._normalMaxRows > 0) {
 | |
|           this.mInput.maxRows = this._normalMaxRows;
 | |
|         }
 | |
|         this._normalMaxRows = -1;
 | |
|         // If the list was being navigated and then closed, make sure
 | |
|         // we fire accessible focus event back to textbox
 | |
| 
 | |
|         // Null-check this.mInput; see bug 1017914
 | |
|         if (isListActive && this.mInput) {
 | |
|           this.mInput.mIgnoreFocus = true;
 | |
|           this.mInput._focus();
 | |
|           this.mInput.mIgnoreFocus = false;
 | |
|         }
 | |
|       });
 | |
|     }
 | |
|   };
 | |
| 
 | |
|   MozPopupElement.implementCustomInterface(
 | |
|     MozElements.MozAutocompleteRichlistboxPopup,
 | |
|     [Ci.nsIAutoCompletePopup]
 | |
|   );
 | |
| 
 | |
|   customElements.define(
 | |
|     "autocomplete-richlistbox-popup",
 | |
|     MozElements.MozAutocompleteRichlistboxPopup,
 | |
|     {
 | |
|       extends: "panel",
 | |
|     }
 | |
|   );
 | |
| }
 | 
