Bug 961431 - Autocomplete suggestions in split console are sliding down. r=robcee
authorMihai Sucan <mihai.sucan@gmail.com>
Tue, 28 Jan 2014 15:55:19 +0200
changeset 181642 ea49291871ef8e22ef8ba632787779c5df834a92
parent 181641 afed99fd2c9f6ade5ff025723108cf4c2f301172
child 181643 24979a717e7d9eed81675a4e57a3e5ae62458b33
push id3343
push userffxbld
push dateMon, 17 Mar 2014 21:55:32 +0000
treeherdermozilla-beta@2f7d3415f79f [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersrobcee
bugs961431
milestone29.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 961431 - Autocomplete suggestions in split console are sliding down. r=robcee
browser/devtools/inspector/selector-search.js
browser/devtools/markupview/markup-view.js
browser/devtools/shared/autocomplete-popup.js
browser/devtools/styleinspector/rule-view.js
--- a/browser/devtools/inspector/selector-search.js
+++ b/browser/devtools/inspector/selector-search.js
@@ -45,17 +45,16 @@ function SelectorSearch(aInspector, aCon
   this._onHTMLSearch = this._onHTMLSearch.bind(this);
   this._onSearchKeypress = this._onSearchKeypress.bind(this);
   this._onListBoxKeypress = this._onListBoxKeypress.bind(this);
 
   // Options for the AutocompletePopup.
   let options = {
     panelId: "inspector-searchbox-panel",
     listBoxId: "searchbox-panel-listbox",
-    fixedWidth: true,
     autoSelect: true,
     position: "before_start",
     direction: "ltr",
     onClick: this._onListBoxKeypress,
     onKeypress: this._onListBoxKeypress,
   };
   this.searchPopup = new AutocompletePopup(this.panelDoc, options);
 
--- a/browser/devtools/markupview/markup-view.js
+++ b/browser/devtools/markupview/markup-view.js
@@ -71,17 +71,16 @@ function MarkupView(aInspector, aFrame, 
   try {
     this.maxChildren = Services.prefs.getIntPref("devtools.markup.pagesize");
   } catch(ex) {
     this.maxChildren = DEFAULT_MAX_CHILDREN;
   }
 
   // Creating the popup to be used to show CSS suggestions.
   let options = {
-    fixedWidth: true,
     autoSelect: true,
     theme: "auto"
   };
   this.popup = new AutocompletePopup(this.doc.defaultView.parent.document, options);
 
   this.undo = new UndoStack();
   this.undo.installController(aControllerWindow);
 
--- a/browser/devtools/shared/autocomplete-popup.js
+++ b/browser/devtools/shared/autocomplete-popup.js
@@ -20,27 +20,25 @@ loader.lazyImporter(this, "gDevTools", "
  * @param Object aOptions
  *        An object consiting any of the following options:
  *        - panelId {String} The id for the popup panel.
  *        - listBoxId {String} The id for the richlistbox inside the panel.
  *        - position {String} The position for the popup panel.
  *        - theme {String} String related to the theme of the popup.
  *        - autoSelect {Boolean} Boolean to allow the first entry of the popup
  *                     panel to be automatically selected when the popup shows.
- *        - fixedWidth {Boolean} Boolean to control dynamic width of the popup.
  *        - direction {String} The direction of the text in the panel. rtl or ltr
  *        - onSelect {String} The select event handler for the richlistbox
  *        - onClick {String} The click event handler for the richlistbox.
  *        - onKeypress {String} The keypress event handler for the richlistitems.
  */
 function AutocompletePopup(aDocument, aOptions = {})
 {
   this._document = aDocument;
 
-  this.fixedWidth = aOptions.fixedWidth || false;
   this.autoSelect = aOptions.autoSelect || false;
   this.position = aOptions.position || "after_start";
   this.direction = aOptions.direction || "ltr";
 
   this.onSelect = aOptions.onSelect;
   this.onClick = aOptions.onClick;
   this.onKeypress = aOptions.onKeypress;
 
@@ -70,17 +68,16 @@ function AutocompletePopup(aDocument, aO
 
     let mainPopupSet = this._document.getElementById("mainPopupSet");
     if (mainPopupSet) {
       mainPopupSet.appendChild(this._panel);
     }
     else {
       this._document.documentElement.appendChild(this._panel);
     }
-    this._list = null;
   }
   else {
     this._list = this._panel.firstChild;
   }
 
   if (!this._list) {
     this._list = this._document.createElementNS(XUL_NS, "richlistbox");
     this._panel.appendChild(this._list);
@@ -132,24 +129,23 @@ AutocompletePopup.prototype = {
    *        Horizontal offset in pixels from the left of the node to the left
    *        of the popup.
    * @param Number aYOffset
    *        Vertical offset in pixels from the top of the node to the starting
    *        of the popup.
    */
   openPopup: function AP_openPopup(aAnchor, aXOffset = 0, aYOffset = 0)
   {
+    this.__maxLabelLength = -1;
+    this._updateSize();
     this._panel.openPopup(aAnchor, this.position, aXOffset, aYOffset);
 
     if (this.autoSelect) {
       this.selectFirstItem();
     }
-    if (!this.fixedWidth) {
-      this._updateSize();
-    }
   },
 
   /**
    * Hide the autocomplete popup panel.
    */
   hidePopup: function AP_hidePopup()
   {
     this._panel.hidePopup();
@@ -236,90 +232,95 @@ AutocompletePopup.prototype = {
     this.clearItems();
     aItems.forEach(this.appendItem, this);
 
     // Make sure that the new content is properly fitted by the XUL richlistbox.
     if (this.isOpen) {
       if (this.autoSelect) {
         this.selectFirstItem();
       }
-      if (!this.fixedWidth) {
-        this._updateSize();
-      }
+      this._updateSize();
     }
   },
 
   /**
    * Selects the first item of the richlistbox. Note that first item here is the
    * item closes to the input element, which means that 0th index if position is
    * below, and last index if position is above.
    */
   selectFirstItem: function AP_selectFirstItem()
   {
     if (this.position.contains("before")) {
       this.selectedIndex = this.itemCount - 1;
     }
     else {
       this.selectedIndex = 0;
     }
+    this._list.ensureIndexIsVisible(this._list.selectedIndex);
+  },
+
+  __maxLabelLength: -1,
+
+  get _maxLabelLength() {
+    if (this.__maxLabelLength != -1) {
+      return this.__maxLabelLength;
+    }
+
+    let max = 0;
+    for (let i = 0; i < this._list.childNodes.length; i++) {
+      let item = this._list.childNodes[i]._autocompleteItem;
+      let str = item.label;
+      if (item.count) {
+        str += (item.count + "");
+      }
+      max = Math.max(str.length, max);
+    }
+
+    this.__maxLabelLength = max;
+    return this.__maxLabelLength;
   },
 
   /**
    * Update the panel size to fit the content.
    *
    * @private
    */
   _updateSize: function AP__updateSize()
   {
     if (!this._panel) {
       return;
     }
-    // Flush the layout so that we get the latest height.
-    this._panel.boxObject.height;
-    let height = {};
-    this._list.scrollBoxObject.getScrolledSize({}, height);
-    // Change the width of the popup only if the scrollbar is visible.
-    if (height.value > this._panel.clientHeight) {
-      this._list.width = this._panel.clientWidth + this._scrollbarWidth;
-    }
-    // Height change is required, otherwise the panel is drawn at an offset
-    // the first time.
-    this._list.height = this._list.clientHeight;
-    // This brings the panel back at right position.
-    this._list.top = 0;
-    // Move the panel to -1,-1 to realign the popup with its anchor node when
-    // decreasing the panel height.
-    this._panel.moveTo(-1, -1);
-    // Changing panel height might make the selected item out of view, so
-    // bring it back to view.
+
+    this._list.style.width = (this._maxLabelLength + 3) +"ch";
     this._list.ensureIndexIsVisible(this._list.selectedIndex);
   },
 
   /**
    * Clear all the items from the autocomplete list.
    */
   clearItems: function AP_clearItems()
   {
     // Reset the selectedIndex to -1 before clearing the list
     this.selectedIndex = -1;
 
     while (this._list.hasChildNodes()) {
       this._list.removeChild(this._list.firstChild);
     }
 
-    if (!this.fixedWidth) {
-      // Reset the panel and list dimensions. New dimensions are calculated when
-      // a new set of items is added to the autocomplete popup.
-      this._list.width = "";
-      this._list.height = "";
-      this._panel.width = "";
-      this._panel.height = "";
-      this._panel.top = "";
-      this._panel.left = "";
-    }
+    this.__maxLabelLength = -1;
+
+    // Reset the panel and list dimensions. New dimensions are calculated when
+    // a new set of items is added to the autocomplete popup.
+    this._list.width = "";
+    this._list.style.width = "";
+    this._list.height = "";
+    this._panel.width = "";
+    this._panel.height = "";
+    this._panel.top = "";
+    this._panel.left = "";
   },
 
   /**
    * Getter for the index of the selected item.
    *
    * @type number
    */
   get selectedIndex() {
@@ -492,48 +493,16 @@ AutocompletePopup.prototype = {
    * Focuses the richlistbox.
    */
   focus: function AP_focus()
   {
     this._list.focus();
   },
 
   /**
-   * Determine the scrollbar width in the current document.
-   *
-   * @private
-   */
-  get _scrollbarWidth()
-  {
-    if (this.__scrollbarWidth !== null) {
-      return this.__scrollbarWidth;
-    }
-
-    let doc = this._document;
-    if (doc.defaultView.matchMedia("(-moz-overlay-scrollbars)").matches) {
-      // This is for the Mac's floating scrollbar, which actually is drawn over
-      // the content, thus taking no extra width.
-      return (this.__scrollbarWidth = 0);
-    }
-
-    let hbox = doc.createElementNS(XUL_NS, "hbox");
-    hbox.setAttribute("style", "height: 0%; overflow: hidden");
-
-    let scrollbar = doc.createElementNS(XUL_NS, "scrollbar");
-    scrollbar.setAttribute("orient", "vertical");
-    hbox.appendChild(scrollbar);
-    doc.documentElement.appendChild(hbox);
-
-    this.__scrollbarWidth = scrollbar.clientWidth;
-    doc.documentElement.removeChild(hbox);
-
-    return this.__scrollbarWidth;
-  },
-
-  /**
    * Manages theme switching for the popup based on the devtools.theme pref.
    *
    * @private
    *
    * @param String aEvent
    *        The name of the event. In this case, "pref-changed".
    * @param Object aData
    *        An object passed by the emitter of the event. In this case, the
--- a/browser/devtools/styleinspector/rule-view.js
+++ b/browser/devtools/styleinspector/rule-view.js
@@ -1084,17 +1084,16 @@ function CssRuleView(aInspector, aDoc, a
   this._handlePrefChange = this._handlePrefChange.bind(this);
   gDevTools.on("pref-changed", this._handlePrefChange);
 
   this._onSourcePrefChanged = this._onSourcePrefChanged.bind(this);
   this._prefObserver = new PrefObserver("devtools.");
   this._prefObserver.on(PREF_ORIG_SOURCES, this._onSourcePrefChanged);
 
   let options = {
-    fixedWidth: true,
     autoSelect: true,
     theme: "auto"
   };
   this.popup = new AutocompletePopup(aDoc.defaultView.parent.document, options);
 
   // Create a tooltip for previewing things in the rule view (images for now)
   this.previewTooltip = new Tooltip(this.inspector.panelDoc);
   this.previewTooltip.startTogglingOnHover(this.element,