Bug 622578 - form helper navigation bar should not obscure web content when only the NEXT / PREV buttons are shown [r=mfinkle]
authorVivien Nicolas <21@vingtetun.org>
Thu, 24 Feb 2011 02:07:51 +0100
changeset 67424 d38090a940d8ab3318b4f9280aee5146c7a12bf3
parent 67423 6684aa6aa2d81af6df2e5e00a931a7db30f553a1
child 67425 0340e160f706c35eb7945bb317b7f3cf2224b402
push id1
push userroot
push dateTue, 26 Apr 2011 22:38:44 +0000
treeherdermozilla-beta@bfdb6e623a36 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmfinkle
bugs622578
Bug 622578 - form helper navigation bar should not obscure web content when only the NEXT / PREV buttons are shown [r=mfinkle]
mobile/chrome/content/MenuListHelperUI.js
mobile/chrome/content/SelectHelperUI.js
mobile/chrome/content/bindings.xml
mobile/chrome/content/browser-ui.js
mobile/chrome/content/browser.js
mobile/chrome/content/browser.xul
mobile/chrome/content/common-ui.js
mobile/chrome/content/forms.js
mobile/themes/core/browser.css
mobile/themes/core/jar.mn
mobile/themes/core/platform.css
--- a/mobile/chrome/content/MenuListHelperUI.js
+++ b/mobile/chrome/content/MenuListHelperUI.js
@@ -40,17 +40,17 @@ var MenuListHelperUI = {
       let item = document.createElement("richlistitem");
       if (child.disabled)
         item.setAttribute("disabled", "true");
       if (child.hidden)
         item.setAttribute("hidden", "true");
 
       // Add selected as a class name instead of an attribute to not being overidden
       // by the richlistbox behavior (it sets the "current" and "selected" attribute
-      item.setAttribute("class", "menulist-command prompt-button" + (child.selected ? " selected" : ""));
+      item.setAttribute("class", "option-command prompt-button" + (child.selected ? " selected" : ""));
 
       let image = document.createElement("image");
       image.setAttribute("src", child.image || "");
       item.appendChild(image);
 
       let label = document.createElement("label");
       label.setAttribute("value", child.label);
       item.appendChild(label);
--- a/mobile/chrome/content/SelectHelperUI.js
+++ b/mobile/chrome/content/SelectHelperUI.js
@@ -1,39 +1,63 @@
 /**
  * SelectHelperUI: Provides an interface for making a choice in a list.
  *   Supports simultaneous selection of choices and group headers.
  */
 var SelectHelperUI = {
+  _selectedIndexes: null,
   _list: null,
-  _selectedIndexes: null,
 
-  get _panel() {
-    delete this._panel;
-    return this._panel = document.getElementById("select-container");
+  get _container() {
+    delete this._container;
+    return this._container = document.getElementById("select-container");
+  },
+
+  get _listbox() {
+    delete this._listbox;
+    return this._listbox = document.getElementById("select-commands");
   },
 
-  show: function selectHelperShow(aList) {
+  get _title() {
+    delete this._title;
+    return this._title = document.getElementById("select-title");
+  },
+
+  show: function selectHelperShow(aList, aTitle) {
+    if (this._list)
+      this.reset();
+
     this._list = aList;
 
-    this._container = document.getElementById("select-list");
+    // The element label is used as a title to give more context
+    this._title.value = aTitle || "";
     this._container.setAttribute("multiple", aList.multiple ? "true" : "false");
 
+    // Save already selected indexes to detect what has changed when a a new
+    // element is selected
     this._selectedIndexes = this._getSelectedIndexes();
     let firstSelected = null;
 
     // Using a fragment prevent us to hang on huge list
     let fragment = document.createDocumentFragment();
     let choices = aList.choices;
     for (let i = 0; i < choices.length; i++) {
       let choice = choices[i];
-      let item = document.createElement("option");
-      item.className = "chrome-select-option";
+      let item = document.createElement("listitem");
+
+      item.setAttribute("class", "option-command listitem-iconic prompt-button");
+      item.setAttribute("image", "");
+      item.setAttribute("flex", "1");
+      item.setAttribute("crop", "center");
       item.setAttribute("label", choice.text);
-      choice.disabled ? item.setAttribute("disabled", choice.disabled)
+
+      choice.selected ? item.setAttribute("selected", "true")
+                      : item.removeAttribute("selected");
+
+      choice.disabled ? item.setAttribute("disabled", "true")
                       : item.removeAttribute("disabled");
       fragment.appendChild(item);
 
       if (choice.group) {
         item.classList.add("optgroup");
         continue;
       }
 
@@ -43,72 +67,56 @@ var SelectHelperUI = {
       if (choice.inGroup)
         item.classList.add("in-optgroup");
 
       if (choice.selected) {
         item.setAttribute("selected", "true");
         firstSelected = firstSelected || item;
       }
     }
-    this._container.appendChild(fragment);
-
-    this._panel.hidden = false;
-    this._panel.height = this._panel.getBoundingClientRect().height;
-
-    if (!this._docked)
-      BrowserUI.pushPopup(this, this._panel);
-
+    this._listbox.appendChild(fragment);
+    this._container.hidden = false;
+  
+    BrowserUI.pushPopup(this, this._container);
     this._scrollElementIntoView(firstSelected);
-
     this._container.addEventListener("click", this, false);
-    this._panel.addEventListener("overflow", this, true);
-  },
+    window.addEventListener("resize", this, true);
+    this.sizeToContent();
 
-  dock: function selectHelperDock(aContainer) {
-    aContainer.insertBefore(this._panel, aContainer.lastChild);
-    this.resize();
-    this._docked = true;
-  },
-
-  undock: function selectHelperUndock() {
-    let rootNode = Elements.stack;
-    rootNode.insertBefore(this._panel, rootNode.lastChild);
-    this._panel.style.maxHeight = "";
-    this._docked = false;
+    let evt = document.createEvent("UIEvents");
+    evt.initUIEvent("SelectUI", true, false, window, true);
+    window.dispatchEvent(evt);
   },
 
   reset: function selectHelperReset() {
     this._updateControl();
-    let empty = this._container.cloneNode(false);
-    this._container.parentNode.replaceChild(empty, this._container);
-    this._container = empty;
+    while (this._listbox.hasChildNodes())
+      this._listbox.removeChild(this._listbox.lastChild);
     this._list = null;
+    this._title.value = "";
     this._selectedIndexes = null;
-    this._panel.height = "";
+    BrowserUI.popPopup(this);
   },
 
-  resize: function selectHelperResize() {
-    this._panel.style.maxHeight = (window.innerHeight / 1.8) + "px";
+  sizeToContent: function selectHelperSizeToContent() {
+    this._container.firstChild.maxHeight = window.innerHeight * 0.75;
   },
 
-  hide: function selectHelperResize() {
+  hide: function selectHelperHide() {
     if (!this._list)
       return;
 
+    window.removeEventListener("resize", this, true);
     this._container.removeEventListener("click", this, false);
-    this._panel.removeEventListener("overflow", this, true);
-
-    this._panel.hidden = true;
+    this._container.hidden = true;
+    this.reset();
 
-    if (this._docked)
-      this.undock();
-    else
-      BrowserUI.popPopup(this);
-
-    this.reset();
+    let evt = document.createEvent("UIEvents");
+    evt.initUIEvent("SelectUI", true, false, window, true);
+    window.dispatchEvent(evt);
   },
 
   unselectAll: function selectHelperUnselectAll() {
     if (!this._list)
       return;
 
     let choices = this._list.choices;
     this._forEachOption(function(aItem, aIndex) {
@@ -117,22 +125,29 @@ var SelectHelperUI = {
     });
   },
 
   selectByIndex: function selectHelperSelectByIndex(aIndex) {
     if (!this._list)
       return;
 
     let choices = this._list.choices;
-    for (let i = 0; i < this._container.childNodes.length; i++) {
-      let option = this._container.childNodes[i];
+    let children = this._listbox.childNodes;
+    for (let i = 0; i < children.length; i++) {
+      let option = children[i];
       if (option.optionIndex == aIndex) {
-        option.selected = true;
-        this._choices[i].selected = true;
-        this._scrollElementIntoView(option);
+        let choice = choices[i];
+        if (this._list.multiple) {
+          choice.selected = !choice.selected;
+          option.setAttribute("selected", choice.selected);
+        } else {
+          option.setAttribute("selected", "true");
+          choice.selected = true;
+          this._scrollElementIntoView(option);
+        }
         break;
       }
     }
   },
 
   _getSelectedIndexes: function _selectHelperGetSelectedIndexes() {
     let indexes = [];
     if (!this._list)
@@ -150,38 +165,37 @@ var SelectHelperUI = {
 
   _scrollElementIntoView: function _selectHelperScrollElementIntoView(aElement) {
     if (!aElement)
       return;
 
     let index = -1;
     this._forEachOption(
       function(aItem, aIndex) {
-        if (aElement.optionIndex == aItem.optionIndex)
+        if (aItem.optionIndex == aElement.optionIndex)
           index = aIndex;
       }
     );
 
     if (index == -1)
       return;
 
-    let scrollBoxObject = this._container.boxObject.QueryInterface(Ci.nsIScrollBoxObject);
+    let scrollBoxObject = this._listbox.boxObject.QueryInterface(Ci.nsIScrollBoxObject);
     let itemHeight = aElement.getBoundingClientRect().height;
-    let visibleItemsCount = this._container.boxObject.height / itemHeight;
+    let visibleItemsCount = this._listbox.boxObject.height / itemHeight;
     if ((index + 1) > visibleItemsCount) {
       let delta = Math.ceil(visibleItemsCount / 2);
       scrollBoxObject.scrollTo(0, ((index + 1) - delta) * itemHeight);
-    }
-    else {
+    } else {
       scrollBoxObject.scrollTo(0, 0);
     }
   },
 
   _forEachOption: function _selectHelperForEachOption(aCallback) {
-    let children = this._container.children;
+    let children = this._listbox.childNodes;
     for (let i = 0; i < children.length; i++) {
       let item = children[i];
       if (!item.hasOwnProperty("optionIndex"))
         continue;
       aCallback(item, i);
     }
   },
 
@@ -205,41 +219,46 @@ var SelectHelperUI = {
   },
 
   handleEvent: function selectHelperHandleEvent(aEvent) {
     switch (aEvent.type) {
       case "click":
         let item = aEvent.target;
         if (item && item.hasOwnProperty("optionIndex")) {
           if (this._list.multiple) {
-            // Toggle the item state
-            item.selected = !item.selected;
-          }
-          else {
+            item.classList.toggle("selected");
+          } else {
             this.unselectAll();
 
             // Select the new one and update the control
-            item.selected = true;
+            item.classList.add("selected");
           }
-          this.onSelect(item.optionIndex, item.selected, !this._list.multiple);
+          this.onSelect(item.optionIndex, item.classList.contains("selected"), !this._list.multiple);
+        } else if (item == this._container) {
+          // The click is outside the listbox area, so we need to hide the list
+          // This is used instead of the popup mechanism otherwise the click
+          // will be dispatched while we want to inhibit it (I think)
+          this.hide();
         }
         break;
+      case "resize":
+        this.sizeToContent();
+        break;
     }
   },
 
   onSelect: function selectHelperOnSelect(aIndex, aSelected, aClearAll) {
     let json = {
       index: aIndex,
       selected: aSelected,
       clearAll: aClearAll
     };
     Browser.selectedBrowser.messageManager.sendAsyncMessage("FormAssist:ChoiceSelect", json);
 
     // http://www.whatwg.org/specs/web-apps/current-work/multipage/the-button-element.html#the-select-element
+    // The list will be closed as soon as the user click if it is not multiple,
+    // while list with multiple choices have a button to close it
     if (!this._list.multiple) {
       this._updateControl();
-      // Update the selectedIndex so the field will fire a new change event if
-      // needed
-      this._selectedIndexes = [aIndex];
+      this.hide();
     }
-
   }
 };
--- a/mobile/chrome/content/bindings.xml
+++ b/mobile/chrome/content/bindings.xml
@@ -1472,54 +1472,48 @@
         <![CDATA[
           event.stopPropagation();
         ]]>
       </handler>
     </handlers>
   </binding>
 
   <binding id="content-navigator">
-    <content pack="end">
-      <children includes="vbox"/>
-      <xul:hbox class="content-navigator-box panel-dark" pack="end">
-        <children includes="textbox|arrowscrollbox"/>
-        <xul:toolbarbutton anonid="previous-button" class="content-navigator-item previous-button" xbl:inherits="command=previous"/>
-        <xul:toolbarbutton anonid="next-button" class="content-navigator-item next-button" xbl:inherits="command=next"/>
-      </xul:hbox>
+    <content class="content-navigator-box" orient="horizontal" pack="end">
+      <children includes="textbox"/>
+      <xul:button anonid="previous-button" class="content-navigator-item previous-button" xbl:inherits="command=previous"/>
+      <xul:button anonid="next-button" class="content-navigator-item next-button" xbl:inherits="command=next"/>
     </content>
 
     <implementation>
       <field name="_previousButton">
         document.getAnonymousElementByAttribute(this, "anonid", "previous-button");
       </field>
 
       <field name="_nextButton">
         document.getAnonymousElementByAttribute(this, "anonid", "next-button");
       </field>
 
-      <field name="_spacer">
-        document.getElementById(this.getAttribute("spacer"));
-      </field>
-
       <method name="contentHasChanged">
         <body><![CDATA[
-          let height = Math.floor(this.getBoundingClientRect().height);
-          let oldHeight = parseInt(this._spacer.getAttribute("height"));
-          this.top = window.innerHeight - height;
-          this._spacer.setAttribute("height", height);
+          if (!this.isActive)
+            return;
 
-          if (height != oldHeight) {
-            let event = document.createEvent("Events");
-            event.initEvent("SizeChanged", true, false);
-            this.dispatchEvent(event);
-          }
+          // There is a race condition with getBoundingClientRect and when the
+          // box is displayed, the padding is ignored in the size calculation
+          this.getBoundingClientRect();
+
+          setTimeout(function(self) {
+            let height = Math.floor(self.getBoundingClientRect().height);
+            self.top = window.innerHeight - height;
+          }, 0, this);
         ]]></body>
       </method>
 
-      <property name="isActive" onget="return !this._spacer.hidden;"/>
+      <property name="isActive" onget="return !!this.model;"/>
 
       <field name="model">null</field>
       <method name="show">
         <parameter name="aModel" />
         <body><![CDATA[
           // call the hide callback of the current object if any
           if (this.model && this.model.type != aModel.type)
             this.model.hide();
@@ -1532,31 +1526,29 @@
           // buttons attributes sync with commands doesn not look updated when
           // we dynamically switch the "command" attribute so we need to ensure
           // the disabled state of the buttons is right when switching commands
           this._previousButton.disabled = document.getElementById(aModel.commands.previous).getAttribute("disabled") == "true";
           this._nextButton.disabled = document.getElementById(aModel.commands.next).getAttribute("disabled") == "true";
 
           this.model = aModel;
           this.contentHasChanged();
-          this._spacer.hidden = false;
         ]]></body>
       </method>
 
       <method name="hide">
         <parameter name="aModel" />
         <body><![CDATA[
           this.removeAttribute("next");
           this.removeAttribute("previous");
           this.removeAttribute("close");
           this.removeAttribute("type");
 
+          this.contentHasChanged();
           this.model = null;
-          this.contentHasChanged();
-          this._spacer.hidden = true;
         ]]></body>
       </method>
     </implementation>
   </binding>
 
   <binding id="menulist" display="xul:box" extends="chrome://global/content/bindings/menulist.xml#menulist">
     <handlers>
       <handler event="mousedown" phase="capturing">
--- a/mobile/chrome/content/browser-ui.js
+++ b/mobile/chrome/content/browser-ui.js
@@ -424,18 +424,17 @@ var BrowserUI = {
 
     // awesomebar and related panels
     let popup = document.getElementById("awesome-panels");
     popup.top = this.toolbarH;
     popup.height = windowH - this.toolbarH;
     popup.width = windowW;
 
     // content navigator helper
-    let contentHelper = document.getElementById("content-navigator");
-    contentHelper.top = windowH - contentHelper.getBoundingClientRect().height;
+    document.getElementById("content-navigator").contentHasChanged();
   },
 
   init: function() {
     this._edit = document.getElementById("urlbar-edit");
     this._title = document.getElementById("urlbar-title");
     this._throbber = document.getElementById("urlbar-throbber");
     this._favicon = document.getElementById("urlbar-favicon");
     this._favicon.addEventListener("error", this, false);
--- a/mobile/chrome/content/browser.js
+++ b/mobile/chrome/content/browser.js
@@ -167,17 +167,17 @@ var Browser = {
     // XXX change
 
     /* handles dispatching clicks on browser into clicks in content or zooms */
     Elements.browsers.customDragger = new Browser.MainDragger();
 
     /* handles web progress management for open browsers */
     Elements.browsers.webProgress = new Browser.WebProgress();
 
-    let keySender = new ContentCustomKeySender(Elements.browsers);
+    this.keySender = new ContentCustomKeySender(Elements.browsers);
     let mouseModule = new MouseModule();
     let gestureModule = new GestureModule(Elements.browsers);
     let scrollWheelModule = new ScrollwheelModule(Elements.browsers);
 
     ContentTouchHandler.init();
 
     // Warning, total hack ahead. All of the real-browser related scrolling code
     // lies in a pretend scrollbox here. Let's not land this as-is. Maybe it's time
@@ -313,19 +313,16 @@ var Browser = {
     os.addObserver(SessionHistoryObserver, "browser:purge-session-history", false);
     os.addObserver(ContentCrashObserver, "ipc:content-shutdown", false);
     os.addObserver(MemoryObserver, "memory-pressure", false);
 
     // Listens for change in the viewable area
 #if MOZ_PLATFORM_MAEMO == 6
     os.addObserver(ViewableAreaObserver, "softkb-change", false);
 #endif
-    Elements.contentNavigator.addEventListener("SizeChanged", function() {
-      ViewableAreaObserver.update();
-    }, true);
 
     window.QueryInterface(Ci.nsIDOMChromeWindow).browserDOMWindow = new nsBrowserAccess();
 
     Elements.browsers.addEventListener("DOMUpdatePageReport", PopupBlockerObserver.onUpdatePageReport, false);
 
     // Make sure we're online before attempting to load
     Util.forceOnline();
 
@@ -1108,17 +1105,16 @@ var Browser = {
 
 Browser.MainDragger = function MainDragger() {
   this._horizontalScrollbar = document.getElementById("horizontal-scroller");
   this._verticalScrollbar = document.getElementById("vertical-scroller");
   this._scrollScales = { x: 0, y: 0 };
 
   Elements.browsers.addEventListener("PanBegin", this, false);
   Elements.browsers.addEventListener("PanFinished", this, false);
-  Elements.contentNavigator.addEventListener("SizeChanged", this, false);
 };
 
 Browser.MainDragger.prototype = {
   isDraggable: function isDraggable(target, scroller) {
     return { x: true, y: true };
   },
 
   dragStart: function dragStart(clientX, clientY, target, scroller) {
@@ -1166,46 +1162,41 @@ Browser.MainDragger.prototype = {
     this._panScroller(Browser.controlsScrollboxScroller, doffset);
     this._panScroller(Browser.pageScrollboxScroller, doffset);
     this._updateScrollbars();
 
     return !doffset.equals(dx, dy);
   },
 
   handleEvent: function handleEvent(aEvent) {
+    let browser = getBrowser();
     switch (aEvent.type) {
       case "PanBegin": {
-        let browser = Browser.selectedBrowser;
         let width = ViewableAreaObserver.width, height = ViewableAreaObserver.height;
         let contentWidth = browser.contentDocumentWidth * browser.scale;
         let contentHeight = browser.contentDocumentHeight * browser.scale;
 
         // Allow a small margin on both sides to prevent adding scrollbars
         // on small viewport approximation
         const ALLOWED_MARGIN = 5;
         const SCROLL_CORNER_SIZE = 8;
         this._scrollScales = {
           x: (width + ALLOWED_MARGIN) < contentWidth ? (width - SCROLL_CORNER_SIZE) / contentWidth : 0,
           y: (height + ALLOWED_MARGIN) < contentHeight ? (height - SCROLL_CORNER_SIZE) / contentHeight : 0
         }
         this._showScrollbars();
         break;
       }
-      case "PanFinished": {
+      case "PanFinished":
         this._hideScrollbars();
 
         // Update the scroll position of the content
         let browser = getBrowser();
         browser._updateCSSViewport();
         break;
-      }
-      case "SizeChanged":
-        let height = Elements.contentNavigator.getBoundingClientRect().height;
-        this._horizontalScrollbar.setAttribute("bottom", 2 + height);
-        break;
     }
   },
 
   /** Return offset that pans controls away from screen. Updates doffset with leftovers. */
   _panControlsAwayOffset: function(doffset) {
     let x = 0, y = 0, rect;
 
     rect = Rect.fromRect(Browser.pageScrollbox.getBoundingClientRect()).map(Math.round);
@@ -2732,17 +2723,17 @@ function rendererFactory(aBrowser, aCanv
  * window but floats over it.
  */
 var ViewableAreaObserver = {
   get width() {
     return this._width || window.innerWidth;
   },
 
   get height() {
-    return (this._height || window.innerHeight) - Elements.contentNavigator.getBoundingClientRect().height;
+    return (this._height || window.innerHeight);
   },
 
   observe: function va_observe(aSubject, aTopic, aData) {
 #if MOZ_PLATFORM_MAEMO == 6
     let rect = Rect.fromRect(JSON.parse(aData));
     let height = rect.bottom - rect.top;
     let width = rect.right - rect.left;
     if (height == window.innerHeight && width == window.innerWidth) {
--- a/mobile/chrome/content/browser.xul
+++ b/mobile/chrome/content/browser.xul
@@ -36,16 +36,17 @@
    - and other provisions required by the LGPL or the GPL. If you do not delete
    - the provisions above, a recipient may use your version of this file under
    - the terms of any one of the MPL, the GPL or the LGPL.
    -
    - ***** END LICENSE BLOCK ***** -->
 
 <?xml-stylesheet href="chrome://browser/skin/platform.css" type="text/css"?>
 <?xml-stylesheet href="chrome://browser/skin/browser.css" type="text/css"?>
+<?xml-stylesheet href="chrome://browser/skin/forms.css" type="text/css"?>
 <?xml-stylesheet href="chrome://browser/content/browser.css" type="text/css"?>
 
 <!DOCTYPE window [
 <!ENTITY % globalDTD SYSTEM "chrome://global/locale/global.dtd">
 %globalDTD;
 <!ENTITY % browserDTD SYSTEM "chrome://browser/locale/browser.dtd">
 %browserDTD;
 <!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd">
@@ -269,26 +270,23 @@
             <!-- Content viewport -->
             <vbox id="content-viewport" class="window-width window-height">
               <!-- Content viewport -->
               <stack>
                 <deck id="browsers" flex="1"/>
                 <!-- vertical scrollbar -->
                 <box id="vertical-scroller" class="scroller" orient="vertical" right="2" top="0"/>
               </stack>
-              <box id="content-navigator-spacer" hidden="true"/>
             </vbox>
           </vbox>
         </scrollbox>
 
         <!-- popup for content navigator helper -->
-        <vbox id="content-navigator" class="window-width" top="0" spacer="content-navigator-spacer">
-          <arrowscrollbox id="form-helper-autofill" collapsed="true" align="center" flex="1" orient="horizontal"
-                          onclick="FormHelperUI.doAutoComplete(event.target);"/>
-          <textbox id="find-helper-textbox" class="search-bar content-navigator-item" oncommand="FindHelperUI.search(this.value)" oninput="FindHelperUI.updateCommands(this.value);" type="search" flex="1"/>
+        <vbox id="content-navigator" class="window-width" top="0">
+          <textbox id="find-helper-textbox" class="search-bar content-navigator-item" oncommand="FindHelperUI.search(this.value)" oninput="FindHelperUI.updateCommands(this.value);" type="search"/>
         </vbox>
 
         <!-- horizontal scrollbar -->
         <box id="horizontal-scroller" class="scroller" orient="horizontal" left="0" bottom="2"/>
       </stack>
 
       <!-- Right toolbar -->
       <vbox class="panel-dark">
@@ -344,16 +342,21 @@
           onclick="PageActions.forgetPassword(event);"/>
         <pageaction id="pageaction-reset" title="&pageactions.reset;"
           onclick="PageActions.clearPagePermissions(event);"/>
         <pageaction id="pageaction-search" title="&pageactions.search.addNew;"/>
         <pageaction id="pageaction-charset" title="&pageactions.charEncoding;" onclick="CharsetMenu.show();"/>
       </hbox>
     </arrowbox>
 
+    <!-- Form Helper suggestions helper popup -->
+    <arrowbox id="form-helper-suggestions-container" flex="1" hidden="true" offset="0" top="0" left="0">
+      <arrowscrollbox id="form-helper-suggestions" align="center" flex="1" orient="horizontal" onclick="FormHelperUI.doAutoComplete(event.target);"/>
+    </arrowbox>
+
     <arrowbox id="bookmark-popup" hidden="true" align="center" offset="12">
       <label value="&bookmarkPopup.label;"/>
       <separator class="thin"/>
       <vbox>
         <button id="bookmark-popup-edit" label="&bookmarkEdit.label;" oncommand="BookmarkHelper.edit();"/>
         <spacer/>
         <button id="bookmark-popup-remove" label="&bookmarkRemove.label;" oncommand="BookmarkPopup.hide(); BookmarkHelper.removeBookmarksForURI(getBrowser().currentURI);"/>
       </vbox>
@@ -574,25 +577,26 @@
       <hbox id="search-engines-list" class="prompt-buttons" flex="1"/>
     </arrowbox>
 
     <vbox id="newtab-popup" hidden="true" class="dialog-dark" onclick="NewTabPopup.selectTab()" align="center" left="21">
       <label/>
     </vbox>
 
     <!-- options dialog for select form field -->
-    <vbox id="select-container" hidden="true" pack="center">
-      <spacer id="select-spacer" flex="1000"/>
-      <vbox id="select-container-inner" class="dialog-dark" flex="1">
-        <scrollbox id="select-list" flex="1" orient="vertical"/>
-        <hbox id="select-buttons" pack="center">
-          <button id="select-buttons-done" label="&selectHelper.done;" oncommand="SelectHelperUI.hide();"/>
+    <vbox id="select-container" class="window-width window-height context-block" top="0" left="0" hidden="true" flex="1">
+      <vbox id="select-popup" class="dialog-dark">
+        <hbox pack="center">
+          <label id="select-title" class="options-title" align="center" crop="center" flex="1"/>
+        </hbox>
+        <scrollbox id="select-commands" onclick="if (event.target != this) SelectHelperUI.selectByIndex(event.target.optionIndex);" orient="vertical" flex="1"/>
+        <hbox pack="center">
+          <button label="&selectHelper.done;" oncommand="SelectHelperUI.hide();"/>
         </hbox>
       </vbox>
-      <spacer flex="1000"/>
     </vbox>
 
     <hbox id="context-container" class="window-width window-height context-block" top="0" left="0" hidden="true">
       <vbox id="context-popup" class="dialog-dark">
         <hbox id="context-header">
           <label id="context-hint" crop="center" flex="1"/>
         </hbox>
         <richlistbox id="context-commands" onclick="ContextHelper.hide();" flex="1">
@@ -651,16 +655,17 @@
 #endif
         </richlistbox>
       </vbox>
     </hbox>
 
     <hbox id="menulist-container" class="window-width window-height context-block" top="0" left="0" hidden="true" flex="1">
       <vbox id="menulist-popup" class="dialog-dark">
         <label id="menulist-title" crop="center" flex="1"/>
+        <label id="menulist-title" class="options-title" crop="center" flex="0"/>
         <richlistbox id="menulist-commands" class="prompt-buttons" onclick="if (event.target != this) MenuListHelperUI.selectByIndex(this.selectedIndex);" flex="1"/>
       </vbox>
     </hbox>
 
     <!-- alerts for content -->
     <hbox id="alerts-container" hidden="true" align="start" bottom="0" onclick="AlertsHelper.click(event);">
       <image id="alerts-image"/>
       <vbox flex="1">
--- a/mobile/chrome/content/common-ui.js
+++ b/mobile/chrome/content/common-ui.js
@@ -470,20 +470,24 @@ var FindHelperUI = {
 
   init: function findHelperInit() {
     this._textbox = document.getElementById("find-helper-textbox");
     this._container = document.getElementById("content-navigator");
 
     this._cmdPrevious = document.getElementById(this.commands.previous);
     this._cmdNext = document.getElementById(this.commands.next);
 
-    // Listen for form assistant messages from content
+    // Listen for find assistant messages from content
     messageManager.addMessageListener("FindAssist:Show", this);
     messageManager.addMessageListener("FindAssist:Hide", this);
 
+    // Listen for pan events happening on the browsers
+    Elements.browsers.addEventListener("PanBegin", this, false);
+    Elements.browsers.addEventListener("PanFinished", this, false);
+
     // Listen for events where form assistant should be closed
     document.getElementById("tabs").addEventListener("TabSelect", this, true);
     Elements.browsers.addEventListener("URLChanged", this, true);
   },
 
   receiveMessage: function findHelperReceiveMessage(aMessage) {
     let json = aMessage.json;
     switch(aMessage.name) {
@@ -505,16 +509,26 @@ var FindHelperUI = {
       case "TabSelect":
         this.hide();
         break;
 
       case "URLChanged":
         if (aEvent.detail && aEvent.target == getBrowser())
           this.hide();
         break;
+
+      case "PanBegin":
+        this._container.style.visibility = "hidden";
+        this._textbox.collapsed = true;
+        break;
+
+      case "PanFinished":
+        this._container.style.visibility = "visible";
+        this._textbox.collapsed = false;
+        break;
     }
   },
 
   show: function findHelperShow() {
     this._container.show(this);
     this.search(this._textbox.value);
     this._textbox.select();
     this._textbox.focus();
@@ -602,17 +616,17 @@ var FormHelperUI = {
   },
 
   get enabled() {
     return Services.prefs.getBoolPref("formhelper.enabled");
   },
 
   init: function formHelperInit() {
     this._container = document.getElementById("content-navigator");
-    this._autofillContainer = document.getElementById("form-helper-autofill");
+    this._suggestionsContainer = document.getElementById("form-helper-suggestions-container");
     this._cmdPrevious = document.getElementById(this.commands.previous);
     this._cmdNext = document.getElementById(this.commands.next);
 
     // Listen for form assistant messages from content
     messageManager.addMessageListener("FormAssist:Show", this);
     messageManager.addMessageListener("FormAssist:Hide", this);
     messageManager.addMessageListener("FormAssist:Update", this);
     messageManager.addMessageListener("FormAssist:Resize", this);
@@ -623,40 +637,55 @@ var FormHelperUI = {
     tabs.addEventListener("TabSelect", this, true);
     tabs.addEventListener("TabClose", this, true);
     Elements.browsers.addEventListener("URLChanged", this, true);
     Elements.browsers.addEventListener("SizeChanged", this, true);
 
     // Listen for modal dialog to show/hide the UI
     messageManager.addMessageListener("DOMWillOpenModalDialog", this);
     messageManager.addMessageListener("DOMModalDialogClosed", this);
+
+    // Listen key events for fields that are non-editable
+    window.addEventListener("keydown", this, true);
+    window.addEventListener("keyup", this, true);
+    window.addEventListener("keypress", this, true);
+
+    // Listen some events to show/hide the autocomplete box
+    Elements.browsers.addEventListener("PanBegin", this, false);
+    Elements.browsers.addEventListener("PanFinished", this, false);
+    window.addEventListener("AnimatedZoomBegin", this, false);
+    window.addEventListener("AnimatedZoomEnd", this, false);
   },
 
   _currentBrowser: null,
   show: function formHelperShow(aElement, aHasPrevious, aHasNext) {
     this._currentBrowser = Browser.selectedBrowser;
+    this._currentCaretRect = null;
 
     // Update the next/previous commands
     this._cmdPrevious.setAttribute("disabled", !aHasPrevious);
     this._cmdNext.setAttribute("disabled", !aHasNext);
+    this._hasSuggestions = false;
     this._open = true;
 
     let lastElement = this._currentElement || null;
     this._currentElement = {
       id: aElement.id,
       name: aElement.name,
+      title: aElement.title,
       value: aElement.value,
       maxLength: aElement.maxLength,
       type: aElement.type,
       isAutocomplete: aElement.isAutocomplete,
       list: aElement.choices
     }
 
-    this._updateContainer(lastElement, this._currentElement);
+    this._updateContainerForSelect(lastElement, this._currentElement);
     this._zoom(Rect.fromRect(aElement.rect), Rect.fromRect(aElement.caretRect));
+    this._updateSuggestionsFor(this._currentElement);
 
     // Prevent the view to scroll automatically while typing
     this._currentBrowser.scrollSync = false;
   },
 
   hide: function formHelperHide() {
     if (!this._open)
       return;
@@ -664,16 +693,17 @@ var FormHelperUI = {
     // Restore the scroll synchonisation
     this._currentBrowser.scrollSync = true;
 
     // reset current Element and Caret Rect
     this._currentElementRect = null;
     this._currentCaretRect = null;
 
     this._updateContainerForSelect(this._currentElement, null);
+    this._resetSuggestions();
 
     this._currentBrowser.messageManager.sendAsyncMessage("FormAssist:Closed", { });
     this._open = false;
   },
 
   // for VKB that does not resize the window
   _currentCaretRect: null,
   _currentElementRect: null,
@@ -682,26 +712,54 @@ var FormHelperUI = {
       return;
 
     switch (aEvent.type) {
       case "TabSelect":
       case "TabClose":
         this.hide();
         break;
 
+      case "PanBegin":
+        // The previous/next buttons should be hidden during a manual panning
+        // operation but not doing a zoom operation since this happen on both
+        // manual dblClick and navigation between the fields by clicking the
+        // buttons
+        this._container.style.visibility = "hidden";
+
+      case "AnimatedZoomBegin":
+        // Changing the hidden attribute here create bugs with the scrollbox
+        // arrows because the binding will miss some underflow events
+        if (this._hasSuggestions)
+          this._suggestionsContainer.style.visibility = "hidden";
+        break;
+
+      case "PanFinished":
+        this._container.style.visibility = "visible";
+
+      case "AnimatedZoomEnd":
+        if (this._hasSuggestions) {
+          this._suggestionsContainer.style.visibility = "visible";
+          this._ensureSuggestionsVisible();
+        }
+        break;
+
       case "URLChanged":
         if (aEvent.detail && aEvent.target == getBrowser())
           this.hide();
         break;
 
+      case "keydown":
+      case "keypress":
+      case "keyup":
+        Browser.keySender.handleEvent(aEvent);
+        break;
+
       case "SizeChanged":
         setTimeout(function(self) {
-          SelectHelperUI.resize();
-          self._container.contentHasChanged();
-
+          SelectHelperUI.sizeToContent();
           self._zoom(self._currentElementRect, self._currentCaretRect);
         }, 0, this);
         break;
     }
   },
 
   receiveMessage: function formHelperReceiveMessage(aMessage) {
     if (!this._open && aMessage.name != "FormAssist:Show" && aMessage.name != "FormAssist:Hide")
@@ -709,52 +767,47 @@ var FormHelperUI = {
 
     let json = aMessage.json;
     switch (aMessage.name) {
       case "FormAssist:Show":
         // if the user has manually disabled the Form Assistant UI we still
         // want to show a UI for <select /> element but not managed by
         // FormHelperUI
         this.enabled ? this.show(json.current, json.hasPrevious, json.hasNext)
-                     : SelectHelperUI.show(json.current.choices);
+                     : SelectHelperUI.show(json.current.choices, json.current.title);
         break;
 
       case "FormAssist:Hide":
         this.enabled ? this.hide()
                      : SelectHelperUI.hide();
         break;
 
       case "FormAssist:Resize":
         let element = json.current;
         this._zoom(Rect.fromRect(element.rect), Rect.fromRect(element.caretRect));
         break;
 
       case "FormAssist:AutoComplete":
-        this._updateAutocompleteFor(json.current);
-        this._container.contentHasChanged();
+        this._updateSuggestionsFor(json.current);
         break;
 
        case "FormAssist:Update":
         Browser.hideSidebars();
         Browser.hideTitlebar();
         this._zoom(null, Rect.fromRect(json.caretRect));
         break;
 
       case "DOMWillOpenModalDialog":
-        if (aMessage.target == Browser.selectedBrowser) {
+        if (aMessage.target == Browser.selectedBrowser && this._container.isActive)
           this._container.style.display = "none";
-          this._container._spacer.hidden = true;
-        }
         break;
 
       case "DOMModalDialogClosed":
-        if (aMessage.target == Browser.selectedBrowser) {
+        if (aMessage.target == Browser.selectedBrowser && this._container.isActive)
           this._container.style.display = "-moz-box";
-          this._container._spacer.hidden = false;
-        }
         break;
     }
   },
 
   goToPrevious: function formHelperGoToPrevious() {
     this._currentBrowser.messageManager.sendAsyncMessage("FormAssist:Previous", { });
   },
 
@@ -779,43 +832,66 @@ var FormHelperUI = {
     this._container.hidden = !aVal;
     if (aVal) {
       this._zoomStart();
       this._container.show(this);
     } else {
       this._zoomFinish();
       this._currentElement = null;
       this._container.hide(this);
+
+      // Since the style is overrided when a popup is shown, it needs to be
+      // resetted here to let the default CSS works
+      this._container.style.display = "";
     }
 
     let evt = document.createEvent("UIEvents");
     evt.initUIEvent("FormUI", true, true, window, aVal);
     this._container.dispatchEvent(evt);
   },
 
-  _updateAutocompleteFor: function _formHelperUpdateAutocompleteFor(aElement) {
+  _hasSuggestions: false,
+  _updateSuggestionsFor: function _formHelperUpdateAutocompleteFor(aElement) {
     let suggestions = this._getAutocompleteSuggestions(aElement);
-    this._displaySuggestions(suggestions);
-  },
+    if (!suggestions.length) {
+      this._resetSuggestions();
+      return;
+    }
+    // Keeps the suggestions element hidden while is it not positionned to the
+    // correct place
+    this._suggestionsContainer.style.visibility = "hidden";
+    this._suggestionsContainer.hidden = false;
 
-  _displaySuggestions: function _formHelperDisplaySuggestions(aSuggestions) {
-    let autofill = this._autofillContainer;
-    while (autofill.hasChildNodes())
-      autofill.removeChild(autofill.lastChild);
+    // the scrollX/scrollY position can change because of the animated zoom so
+    // delay the suggestions positioning
+    if (AnimatedZoom.isZooming()) {
+      let self = this;
+      window.addEventListener("AnimatedZoomEnd", function() {
+        window.removeEventListener("AnimatedZoomEnd", arguments.callee, true);
+          self._updateSuggestionsFor(aElement);
+      }, true);
+      return;
+    }
+
+    let container = this._suggestionsContainer.firstChild;
+    while (container.hasChildNodes())
+      container.removeChild(container.lastChild);
 
     let fragment = document.createDocumentFragment();
-    for (let i = 0; i < aSuggestions.length; i++) {
-      let value = aSuggestions[i];
+    for (let i = 0; i < suggestions.length; i++) {
+      let value = suggestions[i];
       let button = document.createElement("label");
       button.setAttribute("value", value);
-      button.className = "form-helper-autofill-label";
+      button.className = "form-helper-suggestions-label";
       fragment.appendChild(button);
     }
-    autofill.appendChild(fragment);
-    autofill.collapsed = !aSuggestions.length;
+    container.appendChild(fragment);
+
+    this._hasSuggestions = true;
+    this._ensureSuggestionsVisible();
   },
 
   /** Retrieve the autocomplete list from the autocomplete service for an element */
   _getAutocompleteSuggestions: function _formHelperGetAutocompleteSuggestions(aElement) {
     if (!aElement.isAutocomplete)
       return [];
 
     let suggestions = [];
@@ -827,39 +903,107 @@ var FormHelperUI = {
         let value = results.getValueAt(i);
         suggestions.push(value);
       }
     }
 
     return suggestions;
   },
 
+  _resetSuggestions: function _formHelperResetAutocomplete() {
+    this._suggestionsContainer.hidden = true;
+    this._hasSuggestions = false;
+  },
+
+  /** 
+   * This method positionned the list of suggestions on the screen using
+   * a 'virtual' element as referrer that match the real content element
+   * This method called element.getBoundingClientRect() many times and can be
+   * expensive, do not called it too many times.
+   */
+  _ensureSuggestionsVisible: function _formHelperEnsureSuggestionsVisible() {
+    let container = this._suggestionsContainer;
+    container.firstChild.style.maxWidth = (window.innerWidth * 0.75) + "px";
+
+    let browser = getBrowser();
+    let rect = this._currentElementRect.clone().scale(browser.scale, browser.scale);
+    let scroll = browser.getRootView().getPosition();
+
+    // The sidebars scroll needs to be taken into account, otherwise the arrows
+    // can be misplaced if the sidebars are open
+    let [leftVis, rightVis, leftW, rightW] = Browser.computeSidebarVisibility();
+    let leftOffset = leftVis * leftW;
+    let rightOffset = rightVis * rightW;
+    let topOffset = (BrowserUI.toolbarH - Browser.getScrollboxPosition(Browser.pageScrollboxScroller).y);
+    let virtualContentRect = {
+      width: rect.width,
+      height: rect.height,
+      left: Math.ceil(rect.left - scroll.x + leftOffset - rightOffset),
+      right: Math.floor(rect.left + rect.width - scroll.x + leftOffset - rightOffset),
+      top: Math.ceil(rect.top - scroll.y + topOffset),
+      bottom: Math.floor(rect.top + rect.height - scroll.y + topOffset)
+    };
+
+    // If the suggestions are out of view there is no need to display it
+    let browserRect = Rect.fromRect(browser.getBoundingClientRect());
+    if (BrowserUI.isToolbarLocked()) {
+      // If the toolbar is locked, it can appear over the field in such a way
+      // that the field is hidden
+      browserRect = new Rect(browserRect.left + leftOffset - rightOffset, browserRect.top + BrowserUI.toolbarH,
+                             browserRect.width + leftOffset - rightOffset, browserRect.height - BrowserUI.toolbarH);
+    }
+
+    if (browserRect.intersect(Rect.fromRect(virtualContentRect)).isEmpty()) {
+      container.style.visibility = "hidden";
+      return;
+    }
+
+    // Adding rect.height to the top moves the arrowbox below the virtual field
+    let left = rect.left - scroll.x + leftOffset - rightOffset;
+    let top = rect.top - scroll.y + topOffset + (rect.height);
+
+    // Ensure parts of the arrowbox are not outside the window
+    // XXX do we want to correct when it is out of view on the left side too?
+    let arrowboxRect = Rect.fromRect(container.getBoundingClientRect());
+    if (left + arrowboxRect.width > window.innerWidth)
+      left -= (left + arrowboxRect.width - window.innerWidth);
+    container.left = left;
+
+    // Do not position the suggestions over the navigation buttons
+    let buttonsHeight = this._container.getBoundingClientRect().height;
+    if (top + arrowboxRect.height >= window.innerHeight - buttonsHeight)
+      top -= (rect.height + arrowboxRect.height);
+    container.top = top;
+  
+    // Create a virtual element to point to
+    let virtualContentElement = {
+      getBoundingClientRect: function() {
+        return virtualContentRect;
+      }
+    };
+    container.anchorTo(virtualContentElement);
+    container.style.visibility = "visible";
+  },
+
   /** Update the form helper container to reflect new element user is editing. */
   _updateContainer: function _formHelperUpdateContainer(aLastElement, aCurrentElement) {
     this._updateContainerForSelect(aLastElement, aCurrentElement);
 
-    // Setup autofill UI
-    this._updateAutocompleteFor(aCurrentElement);
     this._container.contentHasChanged();
   },
 
   /** Helper for _updateContainer that handles the case where the new element is a select. */
   _updateContainerForSelect: function _formHelperUpdateContainerForSelect(aLastElement, aCurrentElement) {
     let lastHasChoices = aLastElement && (aLastElement.list != null);
     let currentHasChoices = aCurrentElement && (aCurrentElement.list != null);
 
-    if (!lastHasChoices && currentHasChoices) {
-      SelectHelperUI.dock(this._container);
-      SelectHelperUI.show(aCurrentElement.list);
-    } else if (lastHasChoices && currentHasChoices) {
-      SelectHelperUI.reset();
-      SelectHelperUI.show(aCurrentElement.list);
-    } else if (lastHasChoices && !currentHasChoices) {
+    if (currentHasChoices)
+      SelectHelperUI.show(aCurrentElement.list, aCurrentElement.title);
+    else if (lastHasChoices)
       SelectHelperUI.hide();
-    }
   },
 
   /** Zoom and move viewport so that element is legible and touchable. */
   _zoom: function _formHelperZoom(aElementRect, aCaretRect) {
     let browser = getBrowser();
     let zoomRect = Rect.fromRect(browser.getBoundingClientRect());
 
     // Zoom to a specified Rect
--- a/mobile/chrome/content/forms.js
+++ b/mobile/chrome/content/forms.js
@@ -64,16 +64,17 @@ function FormAssistant() {
   addMessageListener("FormAssist:Closed", this);
   addMessageListener("FormAssist:Previous", this);
   addMessageListener("FormAssist:Next", this);
   addMessageListener("FormAssist:ChoiceSelect", this);
   addMessageListener("FormAssist:ChoiceChange", this);
   addMessageListener("FormAssist:AutoComplete", this);
   addMessageListener("Content:SetWindowSize", this);
 
+  addEventListener("keypress", this, true);
   addEventListener("keyup", this, false);
   addEventListener("focus", this, true);
   addEventListener("pageshow", this, false);
   addEventListener("pagehide", this, false);
 
   this._enabled = Services.prefs.getBoolPref("formhelper.enabled");
 };
 
@@ -277,25 +278,27 @@ FormAssistant.prototype = {
   focusSync: false,
   handleEvent: function formHelperHandleEvent(aEvent) {
     // focus changes should be taken into account only if the user has done a
     // manual operation like manually clicking
     let shouldIgnoreFocus = (aEvent.type == "focus" && !this._open && !this.focusSync);
     if ((!this._open && aEvent.type != "focus") || shouldIgnoreFocus)
       return;
 
+    let currentElement = this.currentElement;
     switch (aEvent.type) {
       case "pagehide":
       case "pageshow":
         // When reacting to a page show/hide, if the focus is different this
         // could mean the web page has dramatically changed because of
         // an Ajax change based on fragment identifier
-        if (gFocusManager.focusedElement != this.currentElement)
+        if (gFocusManager.focusedElement != currentElement)
           this.close();
         break;
+
       case "focus":
         let focusedElement = gFocusManager.getFocusedElementForWindow(content, true, {}) || aEvent.target;
 
         // If a body element is editable and the body is the child of an
         // iframe we can assume this is an advanced HTML editor, so let's
         // redirect the form helper selection to the iframe element
         if (focusedElement && this._isEditable(focusedElement)) {
           let editableElement = this._getTopLevelEditable(focusedElement);
@@ -304,69 +307,101 @@ FormAssistant.prototype = {
               self.open(editableElement);
             });
           }
           return;
         }
 
         // if an element is focused while we're closed but the element can be handle
         // by the assistant, try to activate it (only during mouseup)
-        if (!this.currentElement) {
+        if (!currentElement) {
           if (focusedElement && this._isValidElement(focusedElement)) {
             this._executeDelayed(function(self) {
               self.open(focusedElement);
             });
           }
           return;
         }
 
         let focusedIndex = this._getIndexForElement(focusedElement);
         if (focusedIndex != -1 && this.currentIndex != focusedIndex)
           this.currentIndex = focusedIndex;
         break;
 
+      // key processing inside a select element are done during the keypress
+      // handler, preventing this one to be fired cancel the selection change
+      case "keypress":
+        let formExceptions = { button: true, checkbox: true, file: true, image: true, radio: true, reset: true, submit: true };
+        if (this._isSelectElement(currentElement) || formExceptions[currentElement.type] ||
+            currentElement instanceof HTMLButtonElement || (currentElement.getAttribute("role") == "button" && currentElement.hasAttribute("tabindex"))) {
+          switch (aEvent.keyCode) {
+            case aEvent.DOM_VK_RIGHT:
+              this._executeDelayed(function(self) {
+                self.currentIndex++;
+              });
+              aEvent.stopPropagation();
+              aEvent.preventDefault();
+              break;
+
+            case aEvent.DOM_VK_LEFT:
+              this._executeDelayed(function(self) {
+                self.currentIndex--;
+              });
+              aEvent.stopPropagation();
+              aEvent.preventDefault();
+              break;
+          }
+        }
+        break;
+
       case "keyup":
-        let currentElement = this.currentElement;
         switch (aEvent.keyCode) {
           case aEvent.DOM_VK_DOWN:
             if (currentElement instanceof HTMLInputElement && !this._isAutocomplete(currentElement)) {
               if (this._hasKeyListener(currentElement))
                 return;
-            }
-            else if (currentElement instanceof HTMLTextAreaElement) {
+            } else if (currentElement instanceof HTMLTextAreaElement) {
               let existSelection = currentElement.selectionEnd - currentElement.selectionStart;
               let isEnd = (currentElement.textLength == currentElement.selectionEnd);
               if (!isEnd || existSelection)
                 return;
+            } else if (getListForElement(currentElement)) {
+              this.currentIndex = this.currentIndex;
+              return;
             }
 
             this.currentIndex++;
             break;
 
           case aEvent.DOM_VK_UP:
             if (currentElement instanceof HTMLInputElement && !this._isAutocomplete(currentElement)) {
               if (this._hasKeyListener(currentElement))
                 return;
-            }
-            else if (currentElement instanceof HTMLTextAreaElement) {
+            } else if (currentElement instanceof HTMLTextAreaElement) {
               let existSelection = currentElement.selectionEnd - currentElement.selectionStart;
               let isStart = (currentElement.selectionEnd == 0);
               if (!isStart || existSelection)
                 return;
+            } else if (this._isSelectElement(currentElement)) {
+              this.currentIndex = this.currentIndex;
+              return;
             }
 
             this.currentIndex--;
             break;
 
           case aEvent.DOM_VK_RETURN:
+          case aEvent.DOM_VK_ESCAPE:
             break;
 
           default:
             if (this._isAutocomplete(aEvent.target))
               sendAsyncMessage("FormAssist:AutoComplete", this._getJSON());
+            else if (this._isSelectElement(currentElement))
+              this.currentIndex = this.currentIndex;
             break;
         }
 
         let caretRect = this._getCaretRect();
         if (!caretRect.isEmpty())
           sendAsyncMessage("FormAssist:Update", { caretRect: caretRect });
     }
   },
@@ -489,19 +524,18 @@ FormAssistant.prototype = {
   _isSelectElement: function formHelperIsSelectElement(aElement) {
     return (aElement instanceof HTMLSelectElement || aElement instanceof XULMenuListElement);
   },
 
   /** Caret is used to input text for this element. */
   _getCaretRect: function _formHelperGetCaretRect() {
     let element = this.currentElement;
     let focusedElement = gFocusManager.getFocusedElementForWindow(content, true, {});
-    if ((element instanceof HTMLTextAreaElement ||
-        (element instanceof HTMLInputElement && element.type == "text")) &&
-        focusedElement == element) {
+    if ((element.mozIsTextField && element.mozIsTextField(false) ||
+        element instanceof HTMLTextAreaElement) && focusedElement == element) {
       let utils = Util.getWindowUtils(element.ownerDocument.defaultView);
       let rect = utils.sendQueryContentEvent(utils.QUERY_CARET_RECT, element.selectionEnd, 0, 0, 0);
       if (rect) {
         let scroll = ContentScroll.getScrollOffset(element.ownerDocument.defaultView);
         return new Rect(scroll.x + rect.left, scroll.y + rect.top, rect.width, rect.height);
       }
     }
 
@@ -511,17 +545,17 @@ FormAssistant.prototype = {
   /** Gets a rect bounding important parts of the element that must be seen when assisting. */
   _getRect: function _formHelperGetRect() {
     const kDistanceMax = 100;
     let element = this.currentElement;
     let elRect = getBoundingContentRect(element);
 
     let labels = this._getLabels();
     for (let i=0; i<labels.length; i++) {
-      let labelRect = getBoundingContentRect(labels[i]);
+      let labelRect = labels[i].rect;
       if (labelRect.left < elRect.left) {
         let isClose = Math.abs(labelRect.left - elRect.left) - labelRect.width < kDistanceMax &&
                       Math.abs(labelRect.top - elRect.top) - labelRect.height < kDistanceMax;
         if (isClose) {
           let width = labelRect.width + elRect.width + (elRect.left - labelRect.left - labelRect.width);
           return new Rect(labelRect.left, labelRect.top, width, elRect.height).expandToIntegers();
         }
       }
@@ -530,21 +564,26 @@ FormAssistant.prototype = {
   },
 
   _getLabels: function formHelperGetLabels() {
     let associatedLabels = [];
 
     let element = this.currentElement;
     let labels = element.ownerDocument.getElementsByTagName("label");
     for (let i=0; i<labels.length; i++) {
-      if (labels[i].control == element)
-        associatedLabels.push(labels[i]);
+      let label = labels[i];
+      if ((label.control == element || label.getAttribute("for") == element.id) && this._isVisibleElement(label)) {
+        associatedLabels.push({
+          rect: getBoundingContentRect(label),
+          title: label.textContent
+        });
+      }
     }
 
-    return associatedLabels.filter(this._isVisibleElement);
+    return associatedLabels;
   },
 
   _getAllElements: function getAllElements(aElement) {
     // XXX good candidate for tracing if possible.
     // The tough ones are lenght and isVisibleElement.
     let document = aElement.ownerDocument;
     if (!document)
       return;
@@ -591,20 +630,22 @@ FormAssistant.prototype = {
         return i;
     }
     return -1;
   },
 
   _getJSON: function() {
     let element = this.currentElement;
     let list = getListForElement(element);
+    let labels = this._getLabels();
     return {
       current: {
         id: element.id,
         name: element.name,
+        title: labels.length ? labels[0].title : "",
         value: element.value,
         maxLength: element.maxLength,
         type: (element.getAttribute("type") || "").toLowerCase(),
         choices: list,
         isAutocomplete: this._isAutocomplete(this.currentElement),
         rect: this._getRect(),
         caretRect: this._getCaretRect()
       },
--- a/mobile/themes/core/browser.css
+++ b/mobile/themes/core/browser.css
@@ -1304,251 +1304,16 @@ pageaction:not([image]) > hbox >.pageact
 }
 %endif
 
 /* helperapp (save-as) popup ----------------------------------------------- */
 #helperapp-target {
   font-size: @font_small@ !important;
 }
 
-/* navigator popup -------------------------------------------------------------- */
-#content-navigator {
-  padding: 0;
-  background-color: #5e6166;
-}
-
-#content-navigator,
-#content-navigator > #select-container > #select-spacer,
-#content-navigator > #select-container > #select-container-inner > #select-buttons {
-  display: none;
-}
-
-#find-helper-textbox[status="1"] { /* Ci.nsITypeAheadFind.FIND_NOTFOUND */
-  background: rgb(255,200,200);
-}
-
-#content-navigator[type="find"],
-#content-navigator[type="form"] {
-  display: -moz-box;
-}
-
-#content-navigator:not([type="form"]) > #form-helper-autofill {
-  visibility: collapse;
-}
-
-#content-navigator:not([type="form"]) > #select-container,
-#content-navigator:not([type="find"]) > #find-helper-textbox {
-  display: none;
-}
-
-#content-navigator > #select-container > #select-container-inner {
-  border-width: 0;
-  border-radius: @border_radius_normal@ @border_radius_normal@ 0 0;
-  padding: @padding_normal@ @padding_small@ @padding_normal@ @padding_small@;
-  -moz-box-flex: 0;
-}
-
-#content-navigator > #select-container > #select-container-inner > #select-list {
-  min-height: @touch_row@;
-}
-
-#content-navigator > #select-container > spacer {
-  display: none;
-}
-
-#select-buttons {
-  padding: @padding_small@ @padding_normal@; /* row size & core spacing */
-}
-
-#select-buttons-done {
-  -moz-user-focus: ignore;
-  -moz-user-select: none;
-}
-
-.content-navigator-box {
-  padding: 0;
-}
-
-#content-navigator > hbox > .content-navigator-item {
-  margin: 0;
-}
-
-/* XXX this should go with the final Android theme */
-#content-navigator > hbox > toolbarbutton {
-  border-left: @border_width_tiny@ solid rgba(255,255,255,0.2);
-  border-right: @border_width_tiny@ solid rgba(0,0,0,0.2);
-  background-color: transparent;
-  -moz-box-align: center;
-  -moz-box-pack: center;
-}
-
-#content-navigator > hbox > .previous-button {
-  -moz-margin-end: 0;
-  list-style-image: url("chrome://browser/skin/images/previous-hdpi.png");
-}
-
-#content-navigator > hbox > .next-button {
-  -moz-margin-start: 0;
-  list-style-image: url("chrome://browser/skin/images/next-hdpi.png");
-}
-
-#content-navigator > hbox > toolbarbutton:not([disabled="true"]):hover:active {
-  background-color: #3d3f42;
-}
-
-#content-navigator > hbox > toolbarbutton[disabled="true"] {
-  opacity: 0.5;
-}
-
-#form-helper-autofill,
-#find-helper-textbox {
-  border: none;
-  margin: @margin_small@;
-  border-radius: @border_radius_normal@;
-}
-
-#form-helper-autofill {
-  padding: 0; /* half core spacing & none (autorepeat arrows compensate) */
-  color: black;
-  background-color: rgb(235,235,235);
-  background-image: url("chrome://browser/skin/images/button-bg.png");
-  background-size: auto 100%;
-  background-repeat: repeat-x;
-}
-
-#form-helper-autofill > .autorepeatbutton-down,
-#form-helper-autofill > .autorepeatbutton-up {
-  border: none;
-}
-
-#form-helper-autofill > .autorepeatbutton-down {
-  list-style-image: url("chrome://browser/skin/images/arrowright-16.png");
-}
-
-#form-helper-autofill > .autorepeatbutton-down:-moz-locale-dir(rtl) {
-  list-style-image: url("chrome://browser/skin/images/arrowleft-16.png");
-}
-
-#form-helper-autofill > .autorepeatbutton-up {
-  list-style-image: url("chrome://browser/skin/images/arrowleft-16.png");
-}
-
-#form-helper-autofill > .autorepeatbutton-up:-moz-locale-dir(rtl) {
-  list-style-image: url("chrome://browser/skin/images/arrowright-16.png");
-}
-
-/* force the autorepeat buttons to create a 'padding' when collapsed */
-#form-helper-autofill > autorepeatbutton[collapsed="true"],
-#form-helper-autofill > autorepeatbutton[disabled="true"] {
-  visibility: hidden;
-}
-
-.form-helper-autofill-label {
-  padding: @padding_xxnormal@ @padding_normal@; /* 12px helps get row size for the labels */
-  margin: 0;
-  border-color: transparent rgb(215,215,215) transparent rgb(255,255,255);
-  border-style: solid;
-  border-width: @border_width_tiny@;
-}
-
-.form-helper-autofill-label:first-child {
-  -moz-padding-start: -moz-initial; /* the arrowscrollbox creates enough left padding */
-  -moz-border-start: none;
-}
-
-.form-helper-autofill-label:last-child {
-  -moz-border-end: none;
-}
-
-.form-helper-autofill-label:hover:active {
-  background-color: #8db8d8;
-}
-
-#select-container:not([hidden=true]) + #form-buttons {
-  border-top: 0;
-}
-
-/* select popup ------------------------------------------------------------ */
-#stack > #select-container {
-  padding: 32px;
-}
-
-#select-list {
-  border: @border_width_tiny@ solid gray;
-  background-color: #fff;
-}
-
-.chrome-select-option {
-  color: #000;
-  background-color: #fff;
-  padding: @padding_small@;
-  border-bottom: @border_width_tiny@ solid rgb(207,207,207);
-  min-height: @touch_row@; /* row size */
-  max-height: @touch_row@; /* row size */
-  -moz-box-align: center;
-}
-
-
-.chrome-select-option[selected="true"] {
-  background-color: #8db8d8;
-}
-
-.chrome-select-option[disabled="true"] {
-  pointer-events: none;
-  color: #aaa !important;
-}
-
-.chrome-select-option.optgroup {
-  font-weight: bold;
-  font-style: italic;
-}
-
-.chrome-select-option.in-optgroup {
-  -moz-padding-start: -moz-calc(30px + @padding_large@);
-}
-
-.chrome-select-option-image {
-  min-width: 30px;
-}
-
-.chrome-select-option[selected="true"] {
-  list-style-image: url("chrome://browser/skin/images/check-30.png");
-}
-
-/* menulist popup ---------------------------------------------------------- */
-#menulist-commands {
-  display: -moz-box;
-}
-
-#menulist-title {
-  padding: @padding_xxnormal@ @padding_small@ @padding_small@ @padding_small@;
-  font-size: @font_small@;
-}
-
-#menulist-title[value=""] {
-  display: none;
-}
-
-.menulist-command > image {
-  width: 32px;
-  height: 32px;
-}
-
-.menulist-command.selected > image {
-  width: 30px;
-  height: 30px;
-  list-style-image: url("chrome://browser/skin/images/check-30.png");
-  margin-right: @margin_tiny@;
-  margin-top: @margin_tiny@;
-}
-
-.menulist-command:not(.selected) > image[src=""] {
-  visibility: hidden;
-}
-
 /* full-screen video ------------------------------------------------------- */
 .full-screen {
   position: absolute;
   z-index: 500;
 }
 
 /* Android menu ------------------------------------------------------------ */
 #appmenu {
--- a/mobile/themes/core/jar.mn
+++ b/mobile/themes/core/jar.mn
@@ -1,22 +1,23 @@
 #filter substitution
 
 chrome.jar:
 % skin browser classic/1.0 %skin/
   skin/aboutCertError.css                   (aboutCertError.css)
   skin/aboutPage.css                        (aboutPage.css)
   skin/about.css                            (about.css)
   skin/aboutHome.css                        (aboutHome.css)
+* skin/browser.css                          (browser.css)
   skin/config.css                           (config.css)
   skin/firstRun.css                         (firstRun.css)
+* skin/forms.css                            (forms.css)
   skin/header.css                           (header.css)
+* skin/notification.css                     (notification.css)
 * skin/platform.css                         (platform.css)
-* skin/browser.css                          (browser.css)
-* skin/notification.css                     (notification.css)
   skin/touchcontrols.css                    (touchcontrols.css)
 % override chrome://global/skin/about.css chrome://browser/skin/about.css
 % override chrome://global/skin/media/videocontrols.css chrome://browser/skin/touchcontrols.css
 
   skin/images/appmenu-addons-hdpi.png       (images/appmenu-addons-hdpi.png)
   skin/images/appmenu-active-hdpi.png       (images/appmenu-active-hdpi.png)
   skin/images/appmenu-downloads-hdpi.png    (images/appmenu-downloads-hdpi.png)
   skin/images/appmenu-preferences-hdpi.png  (images/appmenu-preferences-hdpi.png)
--- a/mobile/themes/core/platform.css
+++ b/mobile/themes/core/platform.css
@@ -86,16 +86,17 @@ textbox:not([type="number"]) {
 textbox[isempty="true"] {
   color: gray;
 }
 
 textbox.search-bar {
   border: @border_width_small@ solid rgba(0,0,0,0.4);
   background-color: #f9f9f9;
   background: url("chrome://browser/skin/images/textbox-bg.png") top left repeat-x;
+  background-size: 100% 100%; 
 }
 
 textbox[disabled="true"] {
   background-color: lightgray;
 }
 
 /* sidebars spacer --------------------------------------------------------- */
 .sidebar-spacer {