Bug 883390 - Part 1 - Move autocomplete into overlay, centralize urlbar code. r=sfoster
authorJonathan Wilde <jwilde@jwilde.me>
Wed, 10 Jul 2013 12:14:00 -0700
changeset 138550 e9bb67dba984f79763d783ff66933547dc58a5ff
parent 138549 bc9ec6aff994fe4f4d78b42d2747ba9fd90df086
child 138551 47a9f741253c252a24061ff8c9a69f1c33579c6b
push id24961
push useremorley@mozilla.com
push dateTue, 16 Jul 2013 08:58:21 +0000
treeherdermozilla-central@41ed26826acb [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssfoster
bugs883390
milestone25.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 883390 - Part 1 - Move autocomplete into overlay, centralize urlbar code. r=sfoster
browser/metro/base/content/appbar.js
browser/metro/base/content/bindings/autocomplete.xml
browser/metro/base/content/bindings/grid.xml
browser/metro/base/content/bindings/urlbar.xml
browser/metro/base/content/browser-ui.js
browser/metro/base/content/browser.css
browser/metro/base/content/browser.xul
browser/metro/base/jar.mn
browser/metro/base/tests/mochitest/browser_canonizeURL.js
browser/metro/theme/browser.css
--- a/browser/metro/base/content/appbar.js
+++ b/browser/metro/base/content/appbar.js
@@ -110,17 +110,17 @@ var Appbar = {
                                      null, null);
         if (uri.schemeIs('http') || uri.schemeIs('https')) {
           typesArray.push("view-on-desktop");
         }
       } catch(ex) {
       }
 
       var x = this.menuButton.getBoundingClientRect().left;
-      var y = Elements.navbar.getBoundingClientRect().top;
+      var y = Elements.toolbar.getBoundingClientRect().top;
       ContextMenuUI.showContextMenu({
         json: {
           types: typesArray,
           string: '',
           xPos: x,
           yPos: y,
           leftAligned: true,
           bottomAligned: true
--- a/browser/metro/base/content/bindings/grid.xml
+++ b/browser/metro/base/content/bindings/grid.xml
@@ -81,22 +81,23 @@
               this._fireOnSelectionChange();
             }
           ]]>
         </body>
       </method>
 
       <method name="handleItemClick">
         <parameter name="aItem"/>
+        <parameter name="aEvent"/>
         <body>
           <![CDATA[
             if(!this.isBound)
               return;
             if (this.controller)
-              this.controller.handleItemClick(aItem);
+              this.controller.handleItemClick(aItem, aEvent);
           ]]>
         </body>
       </method>
 
       <method name="handleItemContextMenu">
         <parameter name="aItem"/>
         <parameter name="aEvent"/>
         <body>
rename from browser/metro/base/content/bindings/autocomplete.xml
rename to browser/metro/base/content/bindings/urlbar.xml
--- a/browser/metro/base/content/bindings/autocomplete.xml
+++ b/browser/metro/base/content/bindings/urlbar.xml
@@ -9,288 +9,606 @@
 %browserDTD;
 ]>
 
 <bindings
     xmlns="http://www.mozilla.org/xbl"
     xmlns:xbl="http://www.mozilla.org/xbl"
     xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
 
-  <binding id="autocomplete" extends="chrome://global/content/bindings/autocomplete.xml#autocomplete">
-    <implementation>
+  <binding id="urlbar" extends="chrome://global/content/bindings/autocomplete.xml#autocomplete">
+    <implementation implements="nsIObserver">
       <constructor>
-          <![CDATA[
-            this.minResultsForPopup = 0;
-            this.popup._input = this;
-          ]]>
+        <![CDATA[
+          this._mayFormat = Services.prefs.getBoolPref("browser.urlbar.formatting.enabled");
+          this._mayTrimURLs = Services.prefs.getBoolPref("browser.urlbar.trimURLs");
+          this._maySelectAll = Services.prefs.getBoolPref("browser.urlbar.doubleClickSelectsAll");
+
+          Services.prefs.addObserver("browser.urlbar.formatting.enabled", this, false);
+          Services.prefs.addObserver("browser.urlbar.trimURLs", this, false);
+          Services.prefs.addObserver("browser.urlbar.doubleClickSelectsAll", this, false);
+
+          this.inputField.controllers.insertControllerAt(0, this._copyCutValueController);
+
+          this.minResultsForPopup = 0;
+          this.popup._input = this;
+        ]]>
       </constructor>
 
+      <destructor>
+        <![CDATA[
+          Services.prefs.removeObserver("browser.urlbar.formatting.enabled", this);
+          Services.prefs.removeObserver("browser.urlbar.trimURLs", this);
+          Services.prefs.removeObserver("browser.urlbar.doubleClickSelectsAll", this);
+        ]]>
+      </destructor>
+
+      <field name="_mayFormat"/>
+      <field name="_mayTrimURLs"/>
+      <field name="_maySelectAll"/>
+      <field name="_lastKnownGoodURL"/>
+
       <method name="openPopup">
         <body>
           <![CDATA[
             this.popup.openAutocompletePopup(this, null);
           ]]>
         </body>
       </method>
 
       <method name="closePopup">
         <body>
           <![CDATA[
             this.popup.closePopup(this, null);
           ]]>
         </body>
       </method>
 
+      <!-- URI Display: Domain Highlighting -->
+
+      <method name="_clearFormatting">
+        <body>
+          <![CDATA[
+            if (!this._mayFormat)
+              return;
+
+            let controller = this.editor.selectionController;
+            let selection = controller.getSelection(controller.SELECTION_URLSECONDARY);
+            selection.removeAllRanges();
+          ]]>
+        </body>
+      </method>
+
       <method name="formatValue">
         <body>
           <![CDATA[
-            BrowserUI.formatURI();
+            if (!this._mayFormat || this.isEditing)
+              return;
+
+            let controller = this.editor.selectionController;
+            let selection = controller.getSelection(controller.SELECTION_URLSECONDARY);
+            selection.removeAllRanges();
+
+            let textNode = this.editor.rootElement.firstChild;
+            let value = textNode.textContent;
+
+            let protocol = value.match(/^[a-z\d.+\-]+:(?=[^\d])/);
+            if (protocol &&
+                ["http:", "https:", "ftp:"].indexOf(protocol[0]) == -1)
+              return;
+            let matchedURL = value.match(/^((?:[a-z]+:\/\/)?(?:[^\/]+@)?)(.+?)(?::\d+)?(?:\/|$)/);
+            if (!matchedURL)
+              return;
+
+            let [, preDomain, domain] = matchedURL;
+            let baseDomain = domain;
+            let subDomain = "";
+            // getBaseDomainFromHost doesn't recognize IPv6 literals in brackets as IPs (bug 667159)
+            if (domain[0] != "[") {
+              try {
+                baseDomain = Services.eTLD.getBaseDomainFromHost(domain);
+                if (!domain.endsWith(baseDomain)) {
+                  // getBaseDomainFromHost converts its resultant to ACE.
+                  let IDNService = Cc["@mozilla.org/network/idn-service;1"]
+                                   .getService(Ci.nsIIDNService);
+                  baseDomain = IDNService.convertACEtoUTF8(baseDomain);
+                }
+              } catch (e) {}
+            }
+            if (baseDomain != domain) {
+              subDomain = domain.slice(0, -baseDomain.length);
+            }
+
+            let rangeLength = preDomain.length + subDomain.length;
+            if (rangeLength) {
+              let range = document.createRange();
+              range.setStart(textNode, 0);
+              range.setEnd(textNode, rangeLength);
+              selection.addRange(range);
+            }
+
+            let startRest = preDomain.length + domain.length;
+            if (startRest < value.length) {
+              let range = document.createRange();
+              range.setStart(textNode, startRest);
+              range.setEnd(textNode, value.length);
+              selection.addRange(range);
+            }
+          ]]>
+        </body>
+      </method>
+
+      <!-- URI Display: Scheme and Trailing Slash Triming -->
+
+      <method name="_trimURL">
+        <parameter name="aURL"/>
+        <body>
+          <![CDATA[
+            // This function must not modify the given URL such that calling
+            // nsIURIFixup::createFixupURI with the rfdesult will produce a different URI.
+            return aURL /* remove single trailing slash for http/https/ftp URLs */
+               .replace(/^((?:http|https|ftp):\/\/[^/]+)\/$/, "$1")
+                /* remove http:// unless the host starts with "ftp\d*\." or contains "@" */
+               .replace(/^http:\/\/((?!ftp\d*\.)[^\/@]+(?:\/|$))/, "$1");
+          ]]>
+        </body>
+      </method>
+
+      <method name="_getSelectedValueForClipboard">
+        <body>
+          <![CDATA[
+            // Grab the actual input field's value, not our value, which could include moz-action:
+            let inputVal = this.inputField.value;
+            let selectedVal = inputVal.substring(this.selectionStart, this.electionEnd);
+
+            // If the selection doesn't start at the beginning or doesn't span the full domain or
+            // the URL bar is modified, nothing else to do here.
+            if (this.selectionStart > 0 || this.valueIsTyped)
+              return selectedVal;
+
+            // The selection doesn't span the full domain if it doesn't contain a slash and is
+            // followed by some character other than a slash.
+            if (!selectedVal.contains("/")) {
+              let remainder = inputVal.replace(selectedVal, "");
+              if (remainder != "" && remainder[0] != "/")
+                return selectedVal;
+            }
+
+            let uriFixup = Cc["@mozilla.org/docshell/urifixup;1"].getService(Ci.nsIURIFixup);
+
+            let uri;
+            try {
+              uri = uriFixup.createFixupURI(inputVal, Ci.nsIURIFixup.FIXUP_FLAG_USE_UTF8);
+            } catch (e) {}
+            if (!uri)
+              return selectedVal;
+
+            // Only copy exposable URIs
+            try {
+              uri = uriFixup.createExposableURI(uri);
+            } catch (ex) {}
+
+            // If the entire URL is selected, just use the actual loaded URI.
+            if (inputVal == selectedVal) {
+              // ... but only if  isn't a javascript: or data: URI, since those
+              // are hard to read when encoded
+              if (!uri.schemeIs("javascript") && !uri.schemeIs("data")) {
+                // Parentheses are known to confuse third-party applications (bug 458565).
+                selectedVal = uri.spec.replace(/[()]/g, function (c) escape(c));
+              }
+
+              return selectedVal;
+            }
+
+            // Just the beginning of the URL is selected, check for a trimmed value
+            let spec = uri.spec;
+            let trimmedSpec = this._trimURL(spec);
+            if (spec != trimmedSpec) {
+              // Prepend the portion that trimURL removed from the beginning.
+              // This assumes trimURL will only truncate the URL at
+              // the beginning or end (or both).
+              let trimmedSegments = spec.split(trimmedSpec);
+              selectedVal = trimmedSegments[0] + selectedVal;
+            }
+
+            return selectedVal;
           ]]>
         </body>
       </method>
 
+      <field name="_copyCutValueController">
+        <![CDATA[
+          ({
+            urlbar: this,
+            doCommand: function(aCommand) {
+              let urlbar = this.urlbar;
+              let val = urlbar._getSelectedValueForClipboard();
+              if (!val)
+                return;
+
+              if (aCommand == "cmd_cut" && this.isCommandEnabled(aCommand)) {
+                let start = urlbar.selectionStart;
+                let end = urlbar.selectionEnd;
+                urlbar.inputField.value = urlbar.inputField.value.substring(0, start) +
+                                          urlbar.inputField.value.substring(end);
+                urlbar.selectionStart = urlbar.selectionEnd = start;
+              }
+
+              Cc["@mozilla.org/widget/clipboardhelper;1"]
+                .getService(Ci.nsIClipboardHelper)
+                .copyString(val, document);
+            },
+
+            supportsCommand: function(aCommand) {
+              switch (aCommand) {
+                case "cmd_copy":
+                case "cmd_cut":
+                  return true;
+              }
+              return false;
+            },
+
+            isCommandEnabled: function(aCommand) {
+              let urlbar = this.urlbar;
+              return this.supportsCommand(aCommand) &&
+                     (aCommand != "cmd_cut" || !urlbar.readOnly) &&
+                     urlbar.selectionStart < urlbar.selectionEnd;
+            },
+
+            onEvent: function(aEventName) {}
+          })
+        ]]>
+      </field>
+
       <method name="trimValue">
         <parameter name="aURL"/>
-        <body><![CDATA[
-          return BrowserUI.trimURL(aURL);
-        ]]></body>
+        <body>
+          <![CDATA[
+            return (this._mayTrimURLs) ? this._trimURL(aURL) : aURL;
+          ]]>
+        </body>
+      </method>
+
+      <!-- URI Editing -->
+
+      <property name="isEditing" readonly="true">
+        <getter>
+          <![CDATA[
+            return Elements.urlbarState.getAttribute("mode") == "edit";
+          ]]>
+        </getter>
+      </property>
+
+      <method name="beginEditing">
+        <parameter name="aShouldDismiss"/>
+        <body>
+          <![CDATA[
+            if (this.isEditing)
+              return;
+
+            Elements.urlbarState.setAttribute("mode", "edit");
+            this._lastKnownGoodURL = this.value;
+
+            if (!this.focused)
+              this.focus();
+
+            this._clearFormatting();
+            this.select();
+
+            if (aShouldDismiss)
+              ContextUI.dismissTabs();
+          ]]>
+        </body>
+      </method>
+
+      <method name="endEditing">
+        <parameter name="aShouldRevert"/>
+        <body>
+          <![CDATA[
+            if (!this.isEditing)
+              return;
+
+            Elements.urlbarState.setAttribute("mode", "view");
+            this.closePopup();
+            this.formatValue();
+
+            if (this.focused)
+              this.blur();
+
+            if (aShouldRevert)
+              this.value = this._lastKnownGoodURL;
+          ]]>
+        </body>
+      </method>
+
+      <!-- URI Submission -->
+
+      <method name="_canonizeURL">
+        <parameter name="aURL"/>
+        <parameter name="aTriggeringEvent"/>
+        <body>
+          <![CDATA[
+            if (!aURL)
+              return "";
+
+            // Only add the suffix when the URL bar value isn't already "URL-like",
+            // and only if we get a keyboard event, to match user expectations.
+            if (/^\s*[^.:\/\s]+(?:\/.*|\s*)$/i.test(aURL)) {
+              let accel = aTriggeringEvent.ctrlKey;
+              let shift = aTriggeringEvent.shiftKey;
+              let suffix = "";
+
+              switch (true) {
+                case (accel && shift):
+                  suffix = ".org/";
+                  break;
+                case (shift):
+                  suffix = ".net/";
+                  break;
+                case (accel):
+                  try {
+                    suffix = gPrefService.getCharPref("browser.fixup.alternate.suffix");
+                    if (suffix.charAt(suffix.length - 1) != "/")
+                      suffix += "/";
+                  } catch(e) {
+                    suffix = ".com/";
+                  }
+                  break;
+              }
+
+              if (suffix) {
+                // trim leading/trailing spaces (bug 233205)
+                aURL = aURL.trim();
+
+                // Tack www. and suffix on.  If user has appended directories, insert
+                // suffix before them (bug 279035).  Be careful not to get two slashes.
+                let firstSlash = aURL.indexOf("/");
+                if (firstSlash >= 0) {
+                  aURL = aURL.substring(0, firstSlash) + suffix + aURL.substring(firstSlash + 1);
+                } else {
+                  aURL = aURL + suffix;
+                }
+                aURL = "http://www." + aURL;
+              }
+            }
+
+            return aURL;
+          ]]>
+        </body>
+      </method>
+
+      <method name="submitURL">
+        <parameter name="aEvent"/>
+        <body>
+          <![CDATA[
+            // If the address was typed in by a user, tidy it up
+            if (aEvent instanceof KeyEvent)
+              this.value = this._canonizeURL(this.value, aEvent);
+
+            this.endEditing();
+            BrowserUI.goToURI(this.value);
+
+            return true;
+          ]]>
+        </body>
+      </method>
+
+      <method name="submitSearch">
+        <parameter name="anEngineName"/>
+        <body>
+          <![CDATA[
+            this.endEditing();
+            BrowserUI.doOpenSearch(anEngineName);
+
+            return true;
+          ]]>
+        </body>
+      </method>
+
+      <!-- nsIObserver -->
+
+      <method name="observe">
+        <parameter name="aSubject"/>
+        <parameter name="aTopic"/>
+        <parameter name="aData"/>
+        <body>
+          <![CDATA[
+            if (aTopic != "nsPref:changed")
+              return;
+
+            switch (aData) {
+              case "browser.urlbar.formatting.enabled":
+                this._mayFormat = Services.prefs.getBoolPref(aData);
+                break;
+              case "browser.urlbar.trimURLs":
+                this._mayTrimURLs = Services.prefs.getBoolPref(aData);
+                break;
+              case "browser.urlbar.doubleClickSelectsAll":
+                this._maySelectAll = Services.prefs.getBoolPref(aData);
+                break;
+            }
+          ]]>
+        </body>
       </method>
     </implementation>
 
     <handlers>
-      <handler event="click" phase="capturing">
+      <!-- Entering editing mode -->
+
+      <handler event="input" phase="capturing">
         <![CDATA[
-        // If the urlbar is not already focused, focus it and select the contents.
-        if (Elements.urlbarState.getAttribute("mode") != "edit") {
-          BrowserUI._editURI(true);
-        }
+          // Ensures that paste-and-go actually brings the URL bar into editing mode.
+          this.beginEditing();
         ]]>
       </handler>
 
+      <handler event="click" phase="capturing">
+        <![CDATA[
+          this.beginEditing(true);
+        ]]>
+      </handler>
+
+      <!-- Editing mode behaviors -->
+
       <handler event="dblclick" phase="capturing">
         <![CDATA[
-          let selectAll = Services.prefs.getBoolPref("browser.urlbar.doubleClickSelectsAll");
-          if (selectAll)
-            this.select();
+            if (this._maySelectAll) this.select();
         ]]>
       </handler>
 
       <handler event="contextmenu" phase="capturing">
         <![CDATA[
           let box = this.inputField.parentNode;
           box.showContextMenu(this, event, true);
         ]]>
       </handler>
 
+      <!-- Leaving editing mode -->
+
+      <handler event="blur" phase="capturing">
+        <![CDATA[
+          this.endEditing();
+        ]]>
+      </handler>
+
       <handler event="keypress" phase="capturing" keycode="VK_RETURN">
         <![CDATA[
-          event.preventDefault();
-          event.stopPropagation();
+          if (this.popup.submitSelected())
+            return;
 
-          this.popup.handleCompletion();
+          if (this.submitURL(event))
+            return;
         ]]>
       </handler>
     </handlers>
   </binding>
 
-  <binding id="autocomplete-popup">
+  <binding id="urlbar-autocomplete">
     <content orient="horizontal">
-      <xul:vbox id="results-vbox" class="meta-section viewable-height" flex="1">
+      <xul:vbox id="results-vbox" flex="1">
         <xul:label class="meta-section-title" value="&autocompleteResultsHeader.label;"/>
-        <richgrid id="results-richgrid" deferlayout="true" anonid="results" seltype="single" flex="1"/>
+        <richgrid id="results-richgrid" rows="3" deferlayout="true" anonid="results" seltype="single" flex="1"/>
       </xul:vbox>
 
-      <xul:vbox id="searches-vbox" class="meta-section viewable-height" flex="1">
+      <xul:vbox id="searches-vbox" flex="1">
         <xul:label class="meta-section-title" value="&autocompleteSearchesHeader.label;"/>
-        <richgrid id="searches-richgrid" deferlayout="true" anonid="searches" seltype="single" flex="1"/>
+        <richgrid id="searches-richgrid" rows="3" deferlayout="true" anonid="searches" seltype="single" flex="1"/>
       </xul:vbox>
     </content>
 
     <implementation implements="nsIAutoCompletePopup, nsIObserver">
       <constructor>
         <![CDATA[
+          this.hidden = true;
+
           Services.obs.addObserver(this, "browser-search-engine-modified", false);
-          this.updateSearchEngines();
+
           this._results.controller = this;
           this._searches.controller = this;
         ]]>
       </constructor>
 
       <destructor>
         <![CDATA[
           Services.obs.removeObserver(this, "browser-search-engine-modified");
         ]]>
       </destructor>
 
-      <method name="handleItemClick">
-        <parameter name="aItem"/>
-        <body>
-          <![CDATA[
-            if (aItem.control == this._searches) {
-              let engineName = aItem.getAttribute("value");
-              BrowserUI.doOpenSearch(engineName);
-            } else {
-              let url = aItem.getAttribute("value");
-              Browser.loadURI(url);
-            }
-          ]]>
-        </body>
-      </method>
-
-    <!-- nsIAutocompleteInput -->
+      <!-- nsIAutocompleteInput -->
 
       <field name="_input">null</field>
-      <field name="_popupOpen">false</field>
 
+      <property name="popupOpen" readonly="true" onget="return !this.hidden"/>
       <property name="overrideValue" readonly="true" onget="return null;"/>
 
       <property name="selectedItem">
         <getter>
           <![CDATA[
             return this._isGridBound(this._results) ? this._results.selectedItem : null;
           ]]>
         </getter>
         <setter>
           <![CDATA[
             return this._isGridBound(this._results) ? this._results.selectedItem : null;
           ]]>
         </setter>
       </property>
+
       <property name="selectedIndex">
         <getter>
           <![CDATA[
             return this._isGridBound(this._results) ? this._results.selectedIndex : -1;
           ]]>
         </getter>
         <setter>
           <![CDATA[
             return this._isGridBound(this._results) ? this._results.selectedIndex : -1;
           ]]>
         </setter>
       </property>
 
       <property name="input" readonly="true" onget="return this._input;"/>
-      <property name="popupOpen" readonly="true" onget="return this._popupOpen;"/>
       <property name="_matchCount" readonly="true" onget="return this.input.controller.matchCount;"/>
 
       <method name="openAutocompletePopup">
         <parameter name="aInput"/>
         <parameter name="aElement"/>
         <body>
           <![CDATA[
-            if (this._popupOpen)
+            if (this.popupOpen)
               return;
 
             ContextUI.dismissContextAppbar();
 
             this._input = aInput;
-            this._popupOpen = true;
             this._grid = this._results;
 
             this.clearSelection();
             this.invalidate();
 
             this._results.arrangeItemsNow();
             this._searches.arrangeItemsNow();
 
-            this._fire("autocompletestart");
-          ]]>
-        </body>
-      </method>
-
-      <method name="gridBoundCallback">
-        <body>
-          <![CDATA[
-            this.updateResults();
+            this.hidden = false;
           ]]>
         </body>
       </method>
 
       <method name="closePopup">
         <body>
           <![CDATA[
-            if (!this._popupOpen)
+            if (!this.popupOpen)
               return;
 
             this.input.controller.stopSearch();
-            this._popupOpen = false;
-            this._fire("autocompleteend");
+            this.hidden = true;
           ]]>
         </body>
       </method>
 
+      <!-- Updating the results on the grids -->
+
       <method name="invalidate">
         <body>
           <![CDATA[
-            if (!this._popupOpen)
+            if (!this.popupOpen)
               return;
 
             this.updateResults();
             this.updateSearchEngineSubtitles();
           ]]>
         </body>
       </method>
 
-      <method name="selectBy">
-        <parameter name="aReverse"/>
-        <parameter name="aPage"/>
-        <body>
-          <![CDATA[
-            let handleOnSelect = this._handleOnSelect;
-            this._handleOnSelect = false;
-
-            // TODO <jwilde>: Pressing page up/down should jump between rows,
-            //                not just items in the grid
-
-            // Move between grids if we're at the edge of one
-            if ((this._grid.isSelectionAtEnd && !aReverse) ||
-                (this._grid.isSelectionAtStart && aReverse)) {
-              let index = aReverse ? this._otherGrid.itemCount - 1 : 0;
-              this._otherGrid.selectedIndex = index;
-            } else {
-              this._grid.offsetSelection(aReverse ? -1 : 1);
-            }
-
-            this._handleOnSelect = handleOnSelect;
-          ]]>
-        </body>
-      </method>
-
-    <!-- nsIObserver -->
-
-      <method name="observe">
-        <parameter name="aSubject"/>
-        <parameter name="aTopic"/>
-        <parameter name="aData"/>
-        <body>
-          <![CDATA[
-            if (aTopic != "browser-search-engine-modified")
-              return;
-
-            switch (aData) {
-              case "engine-added":
-              case "engine-removed":
-              case "engine-changed":
-                this.updateSearchEngines();
-                break;
-              case "engine-current":
-                // Not relevant
-                break;
-            }
-          ]]>
-        </body>
-      </method>
-
-    <!-- Interface for updating various components of the popup. -->
-
       <method name="updateResults">
         <body>
           <![CDATA[
             if (!this._isGridBound(this._results))
               return;
+
             if (!this.input)
               return;
 
             let controller = this.input.controller;
             let lastMatch = this._matchCount - 1;
             let iterCount = Math.max(this._results.itemCount, this._matchCount);
 
             // Swap out existing items for new search hit results
@@ -318,19 +636,21 @@
             }
 
             this._results.arrangeItems();
           ]]>
         </body>
       </method>
 
       <method name="updateSearchEngines">
-        <body><![CDATA[
-          Services.search.init(this._onSearchServiceInit.bind(this));
-        ]]></body>
+        <body>
+          <![CDATA[
+            Services.search.init(this._onSearchServiceInit.bind(this));
+          ]]>
+        </body>
       </method>
 
       <method name="_onSearchServiceInit">
         <body>
           <![CDATA[
             if (!this._isGridBound(this._searches))
               return;
 
@@ -366,31 +686,32 @@
               let item = this._searches.getItemAtIndex(i);
               item.setAttribute("label", label);
               item.refresh && item.refresh();
             }
           ]]>
         </body>
       </method>
 
-    <!-- Interface for handling actions across grids -->
+      <!-- Selecting search results -->
 
-      <method name="handleCompletion">
+      <method name="selectBy">
+        <parameter name="aReverse"/>
+        <parameter name="aPage"/>
         <body>
           <![CDATA[
-            if (this._grid == this._results) {
-              this.input.controller.handleEnter(false);
-              return;
-            }
-
-            if (this._grid == this._searches) {
-              let engine = this._engines[this._grid.selectedIndex];
-              BrowserUI.doOpenSearch(engine.name);
-              this.closePopup();
-              return;
+            // Move between grids if we're at the edge of one.
+            if ((this._grid.isSelectionAtEnd && !aReverse) ||
+                (this._grid.isSelectionAtStart && aReverse)) {
+              let index = !aReverse ? 0 : this._otherGrid.itemCount - 1;
+              this._grid.clearSelection();
+              this._otherGrid.selectedIndex = index;
+              this._grid = this._otherGrid;
+            } else {
+              this._grid.offsetSelection(aReverse ? -1 : 1);
             }
           ]]>
         </body>
       </method>
 
       <method name="clearSelection">
         <body>
           <![CDATA[
@@ -398,76 +719,104 @@
               this._results.clearSelection();
 
             if (this._isGridBound(this._searches))
               this._searches.clearSelection();
           ]]>
         </body>
       </method>
 
-    <!-- Helpers -->
+      <!-- Submitting selected search results -->
+
+      <method name="submitSelected">
+        <body>
+          <![CDATA[
+            if (this._results.selectedIndex >= 0) {
+              let url = this.input.controller.getValueAt(this._results.selectedIndex);
+              this.input.value = url;
+              return this.input.submitURL();
+            }
+
+            if (this._searches.selectedIndex >= 0) {
+              let engine = this._engines[this._searches.selectedIndex];
+              return this.input.submitSearch(engine.name);
+            }
+
+            return false;
+          ]]>
+        </body>
+      </method>
+
+      <method name="handleItemClick">
+        <parameter name="aItem"/>
+        <body>
+          <![CDATA[
+            // XXX this should probably be supported in grid.xml
+            // somehow via some single-selection option.
+            if (this._grid) this._grid.clearSelection();
+            this._grid = aItem.control;
+            this._grid.selectedIndex = this._grid.getIndexOfItem(aItem);
+
+            this.submitSelected();
+          ]]>
+        </body>
+      </method>
+
+      <!-- nsIObserver -->
+
+      <method name="observe">
+        <parameter name="aSubject"/>
+        <parameter name="aTopic"/>
+        <parameter name="aData"/>
+        <body>
+          <![CDATA[
+            if (aTopic != "browser-search-engine-modified" ||
+                aData != "engine-changed")
+              return;
+
+            this.updateSearchEngines();
+          ]]>
+        </body>
+      </method>
+
+      <!-- Helpers -->
 
       <field name="_engines">[]</field>
-      <field name="_handleOnSelect">true</field>
       <field name="_grid">null</field>
-
-      <property name="_results" readonly="true" onget="return document.getAnonymousElementByAttribute(this, 'anonid', 'results');"/>
-      <property name="_searches" readonly="true" onget="return document.getAnonymousElementByAttribute(this, 'anonid', 'searches');"/>
+      <field name="_results" readonly="true">document.getAnonymousElementByAttribute(this, 'anonid', 'results');</field>
+      <field name="_searches" readonly="true">document.getAnonymousElementByAttribute(this, 'anonid', 'searches');</field>
 
       <property name="_otherGrid" readonly="true">
         <getter>
           <![CDATA[
+            if (this._grid == null)
+              return null;
+
             return (this._grid == this._results) ? this._searches : this._results;
           ]]>
         </getter>
       </property>
 
       <method name="_isGridBound">
         <parameter name="aGrid"/>
         <body>
           <![CDATA[
             return aGrid && aGrid.itemCount != undefined;
           ]]>
         </body>
       </method>
-
-      <method name="_fire">
-        <parameter name="aName"/>
-        <body>
-          <![CDATA[
-            let event = document.createEvent("Events");
-            event.initEvent(aName, true, false);
-            this.dispatchEvent(event);
-          ]]>
-        </body>
-      </method>
     </implementation>
 
     <handlers>
-      <handler event="select">
-        <![CDATA[
-          let grid = event.originalTarget;
-          if (grid != this._grid) {
-            if (this._grid.clearSelection)
-              this._grid.clearSelection();
-            this._grid = grid;
-          }
-
-          if (this._handleOnSelect && this._results.selectedItem) {
-            BrowserUI.goToURI(
-              this._results.selectedItem.getAttribute("value"));
-            this.closePopup();
-          } else if (this._handleOnSelect) {
-            this.handleCompletion();
-          }
-        ]]>
-      </handler>
-
       <handler event="contentgenerated">
         <![CDATA[
           let grid = event.originalTarget;
+
           if (grid == this._searches)
             this.updateSearchEngines();
+
+          if (grid == this._results)
+            this.updateResults();
         ]]>
       </handler>
     </handlers>
   </binding>
 </bindings>
--- a/browser/metro/base/content/browser-ui.js
+++ b/browser/metro/base/content/browser-ui.js
@@ -102,21 +102,17 @@ var BrowserUI = {
 
     // listening escape to dismiss dialog on VK_ESCAPE
     window.addEventListener("keypress", this, true);
 
     window.addEventListener("MozPrecisePointer", this, true);
     window.addEventListener("MozImprecisePointer", this, true);
 
     Services.prefs.addObserver("browser.cache.disk_cache_ssl", this, false);
-    Services.prefs.addObserver("browser.urlbar.formatting.enabled", this, false);
-    Services.prefs.addObserver("browser.urlbar.trimURLs", this, false);
     Services.obs.addObserver(this, "metro_viewstate_changed", false);
-    
-    this._edit.inputField.controllers.insertControllerAt(0, this._copyCutURIController);
 
     // Init core UI modules
     ContextUI.init();
     StartUI.init();
     PanelUI.init();
     FlyoutPanelsUI.init();
     PageThumbs.init();
     SettingsCharm.init();
@@ -352,117 +348,67 @@ var BrowserUI = {
   },
 
   /* Set the location to the current content */
   updateURI: function(aOptions) {
     aOptions = aOptions || {};
 
     let uri = this.getDisplayURI(Browser.selectedBrowser);
     let cleanURI = Util.isURLEmpty(uri) ? "" : uri;
-    this._setURI(cleanURI);
+
+    this._edit.value = cleanURI;
 
     if ("captionOnly" in aOptions && aOptions.captionOnly)
       return;
 
     StartUI.update(uri);
     this._updateButtons();
     this._updateToolbar();
   },
 
   goToURI: function(aURI) {
     aURI = aURI || this._edit.value;
     if (!aURI)
       return;
 
+    this._edit.value = aURI;
+
     // Make sure we're online before attempting to load
     Util.forceOnline();
 
     BrowserUI.showContent(aURI);
     content.focus();
-    this._setURI(aURI);
 
     Task.spawn(function() {
       let postData = {};
       aURI = yield Browser.getShortcutOrURI(aURI, postData);
       Browser.loadURI(aURI, { flags: Ci.nsIWebNavigation.LOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUP, postData: postData });
 
       // Delay doing the fixup so the raw URI is passed to loadURIWithFlags
       // and the proper third-party fixup can be done
       let fixupFlags = Ci.nsIURIFixup.FIXUP_FLAG_ALLOW_KEYWORD_LOOKUP;
       let uri = gURIFixup.createFixupURI(aURI, fixupFlags);
       gHistSvc.markPageAsTyped(uri);
 
       BrowserUI._titleChanged(Browser.selectedBrowser);
     });
   },
 
-  handleUrlbarEnter: function handleUrlbarEnter(aEvent) {
-    let url = this._edit.value;
-    if (aEvent instanceof KeyEvent)
-      url = this._canonizeURL(url, aEvent);
-    this.goToURI(url);
-  },
-
-  _canonizeURL: function _canonizeURL(aUrl, aTriggeringEvent) {
-    if (!aUrl)
-      return "";
-
-    // Only add the suffix when the URL bar value isn't already "URL-like",
-    // and only if we get a keyboard event, to match user expectations.
-    if (/^\s*[^.:\/\s]+(?:\/.*|\s*)$/i.test(aUrl)) {
-      let accel = aTriggeringEvent.ctrlKey;
-      let shift = aTriggeringEvent.shiftKey;
-      let suffix = "";
-
-      switch (true) {
-        case (accel && shift):
-          suffix = ".org/";
-          break;
-        case (shift):
-          suffix = ".net/";
-          break;
-        case (accel):
-          try {
-            suffix = gPrefService.getCharPref("browser.fixup.alternate.suffix");
-            if (suffix.charAt(suffix.length - 1) != "/")
-              suffix += "/";
-          } catch(e) {
-            suffix = ".com/";
-          }
-          break;
-      }
-
-      if (suffix) {
-        // trim leading/trailing spaces (bug 233205)
-        aUrl = aUrl.trim();
-
-        // Tack www. and suffix on.  If user has appended directories, insert
-        // suffix before them (bug 279035).  Be careful not to get two slashes.
-        let firstSlash = aUrl.indexOf("/");
-        if (firstSlash >= 0) {
-          aUrl = aUrl.substring(0, firstSlash) + suffix + aUrl.substring(firstSlash + 1);
-        } else {
-          aUrl = aUrl + suffix;
-        }
-        aUrl = "http://www." + aUrl;
-      }
-    }
-    return aUrl;
-  },
-
   doOpenSearch: function doOpenSearch(aName) {
     // save the current value of the urlbar
     let searchValue = this._edit.value;
+    let engine = Services.search.getEngineByName(aName);
+    let submission = engine.getSubmission(searchValue, null);
+
+    this._edit.value = submission.uri.spec;
 
     // Make sure we're online before attempting to load
     Util.forceOnline();
     BrowserUI.showContent();
 
-    let engine = Services.search.getEngineByName(aName);
-    let submission = engine.getSubmission(searchValue, null);
     Browser.loadURI(submission.uri.spec, { postData: submission.postData });
 
     // loadURI may open a new tab, so get the selectedBrowser afterward.
     Browser.selectedBrowser.userTypedValue = submission.uri.spec;
     this._titleChanged(Browser.selectedBrowser);
   },
 
   /*********************************
@@ -611,37 +557,31 @@ var BrowserUI = {
 
   observe: function BrowserUI_observe(aSubject, aTopic, aData) {
     switch (aTopic) {
       case "nsPref:changed":
         switch (aData) {
           case "browser.cache.disk_cache_ssl":
             this._sslDiskCacheEnabled = Services.prefs.getBoolPref(aData);
             break;
-          case "browser.urlbar.formatting.enabled":
-            this._formattingEnabled = Services.prefs.getBoolPref(aData);
-            break;
-          case "browser.urlbar.trimURLs":
-            this._mayTrimURLs = Services.prefs.getBoolPref(aData);
-            break;
           case debugServerStateChanged:
             if (Services.prefs.getBoolPref(aData)) {
               this.runDebugServer();
             } else {
               this.stopDebugServer();
             }
             break;
           case debugServerPortChanged:
             this.changeDebugPort(Services.prefs.getIntPref(aData));
             break;
         }
         break;
       case "metro_viewstate_changed":
         this._adjustDOMforViewState();
-        let autocomplete = document.getElementById("start-autocomplete");
+        let autocomplete = document.getElementById("urlbar-autocomplete");
         if (aData == "snapped") {
           FlyoutPanelsUI.hide();
           // Order matters (need grids to get dimensions, etc), now
           // let snapped grid know to refresh/redraw
           Services.obs.notifyObservers(null, "metro_viewstate_dom_snapped", null);
           autocomplete.setAttribute("orient", "vertical");
         }
         else {
@@ -717,214 +657,16 @@ var BrowserUI = {
     let isLoading = Browser.selectedTab.isLoading();
 
     if (isLoading && mode != "loading")
       Elements.urlbarState.setAttribute("mode", "loading");
     else if (!isLoading && mode != "edit")
       Elements.urlbarState.setAttribute("mode", "view");
   },
 
-  _trimURL: function _trimURL(aURL) {
-    // This function must not modify the given URL such that calling
-    // nsIURIFixup::createFixupURI with the result will produce a different URI.
-    return aURL /* remove single trailing slash for http/https/ftp URLs */
-               .replace(/^((?:http|https|ftp):\/\/[^/]+)\/$/, "$1")
-                /* remove http:// unless the host starts with "ftp\d*\." or contains "@" */
-               .replace(/^http:\/\/((?!ftp\d*\.)[^\/@]+(?:\/|$))/, "$1");
-  },
-
-  trimURL: function trimURL(aURL) {
-    return this.mayTrimURLs ? this._trimURL(aURL) : aURL;
-  },
-
-  _setURI: function _setURI(aURL) {
-    this._edit.value = aURL;
-    this.lastKnownGoodURL = aURL;
-  },
-
-  _getSelectedURIForClipboard: function _getSelectedURIForClipboard() {
-    // Grab the actual input field's value, not our value, which could include moz-action:
-    let inputVal = this._edit.inputField.value;
-    let selectedVal = inputVal.substring(this._edit.selectionStart, this._edit.electionEnd);
-
-    // If the selection doesn't start at the beginning or doesn't span the full domain or
-    // the URL bar is modified, nothing else to do here.
-    if (this._edit.selectionStart > 0 || this._edit.valueIsTyped)
-      return selectedVal;
-    // The selection doesn't span the full domain if it doesn't contain a slash and is
-    // followed by some character other than a slash.
-    if (!selectedVal.contains("/")) {
-      let remainder = inputVal.replace(selectedVal, "");
-      if (remainder != "" && remainder[0] != "/")
-        return selectedVal;
-    }
-
-    let uriFixup = Cc["@mozilla.org/docshell/urifixup;1"].getService(Ci.nsIURIFixup);
-
-    let uri;
-    try {
-      uri = uriFixup.createFixupURI(inputVal, Ci.nsIURIFixup.FIXUP_FLAG_USE_UTF8);
-    } catch (e) {}
-    if (!uri)
-      return selectedVal;
-
-    // Only copy exposable URIs
-    try {
-      uri = uriFixup.createExposableURI(uri);
-    } catch (ex) {}
-
-    // If the entire URL is selected, just use the actual loaded URI.
-    if (inputVal == selectedVal) {
-      // ... but only if  isn't a javascript: or data: URI, since those
-      // are hard to read when encoded
-      if (!uri.schemeIs("javascript") && !uri.schemeIs("data")) {
-        // Parentheses are known to confuse third-party applications (bug 458565).
-        selectedVal = uri.spec.replace(/[()]/g, function (c) escape(c));
-      }
-
-      return selectedVal;
-    }
-
-    // Just the beginning of the URL is selected, check for a trimmed value
-    let spec = uri.spec;
-    let trimmedSpec = this.trimURL(spec);
-    if (spec != trimmedSpec) {
-      // Prepend the portion that trimURL removed from the beginning.
-      // This assumes trimURL will only truncate the URL at
-      // the beginning or end (or both).
-      let trimmedSegments = spec.split(trimmedSpec);
-      selectedVal = trimmedSegments[0] + selectedVal;
-    }
-
-    return selectedVal;
-  },
-
-  _copyCutURIController: {
-    doCommand: function(aCommand) {
-      let urlbar = BrowserUI._edit;
-      let val = BrowserUI._getSelectedURIForClipboard();
-      if (!val)
-        return;
-
-      if (aCommand == "cmd_cut" && this.isCommandEnabled(aCommand)) {
-        let start = urlbar.selectionStart;
-        let end = urlbar.selectionEnd;
-        urlbar.inputField.value = urlbar.inputField.value.substring(0, start) +
-                                  urlbar.inputField.value.substring(end);
-        urlbar.selectionStart = urlbar.selectionEnd = start;
-      }
-
-      Cc["@mozilla.org/widget/clipboardhelper;1"]
-        .getService(Ci.nsIClipboardHelper)
-        .copyString(val, document);
-    },
-
-    supportsCommand: function(aCommand) {
-      switch (aCommand) {
-        case "cmd_copy":
-        case "cmd_cut":
-          return true;
-      }
-      return false;
-    },
-
-    isCommandEnabled: function(aCommand) {
-      let urlbar = BrowserUI._edit;
-      return this.supportsCommand(aCommand) &&
-             (aCommand != "cmd_cut" || !urlbar.readOnly) &&
-             urlbar.selectionStart < urlbar.selectionEnd;
-    },
-
-    onEvent: function(aEventName) {}
-  },
-
-  _editURI: function _editURI(aShouldDismiss) {
-    this._edit.focus();
-    this._edit.select();
-
-    Elements.urlbarState.setAttribute("mode", "edit");
-    StartUI.show();
-    if (aShouldDismiss) {
-      ContextUI.dismissTabs();
-    }
-  },
-
-  formatURI: function formatURI() {
-    if (!this.formattingEnabled ||
-        Elements.urlbarState.getAttribute("mode") == "edit")
-      return;
-
-    let controller = this._edit.editor.selectionController;
-    let selection = controller.getSelection(controller.SELECTION_URLSECONDARY);
-    selection.removeAllRanges();
-
-    let textNode = this._edit.editor.rootElement.firstChild;
-    let value = textNode.textContent;
-
-    let protocol = value.match(/^[a-z\d.+\-]+:(?=[^\d])/);
-    if (protocol &&
-        ["http:", "https:", "ftp:"].indexOf(protocol[0]) == -1)
-      return;
-    let matchedURL = value.match(/^((?:[a-z]+:\/\/)?(?:[^\/]+@)?)(.+?)(?::\d+)?(?:\/|$)/);
-    if (!matchedURL)
-      return;
-
-    let [, preDomain, domain] = matchedURL;
-    let baseDomain = domain;
-    let subDomain = "";
-    // getBaseDomainFromHost doesn't recognize IPv6 literals in brackets as IPs (bug 667159)
-    if (domain[0] != "[") {
-      try {
-        baseDomain = Services.eTLD.getBaseDomainFromHost(domain);
-        if (!domain.endsWith(baseDomain)) {
-          // getBaseDomainFromHost converts its resultant to ACE.
-          let IDNService = Cc["@mozilla.org/network/idn-service;1"]
-                           .getService(Ci.nsIIDNService);
-          baseDomain = IDNService.convertACEtoUTF8(baseDomain);
-        }
-      } catch (e) {}
-    }
-    if (baseDomain != domain) {
-      subDomain = domain.slice(0, -baseDomain.length);
-    }
-
-    let rangeLength = preDomain.length + subDomain.length;
-    if (rangeLength) {
-      let range = document.createRange();
-      range.setStart(textNode, 0);
-      range.setEnd(textNode, rangeLength);
-      selection.addRange(range);
-    }
-
-    let startRest = preDomain.length + domain.length;
-    if (startRest < value.length) {
-      let range = document.createRange();
-      range.setStart(textNode, startRest);
-      range.setEnd(textNode, value.length);
-      selection.addRange(range);
-    }
-  },
-
-  _clearURIFormatting: function _clearURIFormatting() {
-    if (!this.formattingEnabled)
-      return;
-
-    let controller = this._edit.editor.selectionController;
-    let selection = controller.getSelection(controller.SELECTION_URLSECONDARY);
-    selection.removeAllRanges();
-  },
-
-  _urlbarBlurred: function _urlbarBlurred() {
-    let state = Elements.urlbarState;
-    if (state.getAttribute("mode") == "edit")
-      state.removeAttribute("mode");
-    this._updateToolbar();
-    this.formatURI();
-  },
-
   _closeOrQuit: function _closeOrQuit() {
     // Close active dialog, if we have one. If not then close the application.
     if (!BrowserUI.isContentShowing()) {
       BrowserUI.showContent();
     } else {
       // Check to see if we should really close the window
       if (Browser.closing()) {
         window.close();
@@ -975,20 +717,17 @@ var BrowserUI = {
 
   // Checks if various different parts of the UI is visible and closes
   // them one at a time.
   handleEscape: function (aEvent) {
     aEvent.stopPropagation();
     aEvent.preventDefault();
 
     if (this._edit.popupOpen) {
-      this._edit.value = this.lastKnownGoodURL;
-      this._edit.closePopup();
-      StartUI.hide();
-      ContextUI.dismiss();
+      this._edit.endEditing(true);
       return;
     }
 
     // Check open popups
     if (DialogUI._popup) {
       DialogUI._hidePopup();
       return;
     }
@@ -1195,34 +934,16 @@ var BrowserUI = {
 
   get sslDiskCacheEnabled() {
     if (this._sslDiskCacheEnabled === null) {
       this._sslDiskCacheEnabled = Services.prefs.getBoolPref("browser.cache.disk_cache_ssl");
     }
     return this._sslDiskCacheEnabled;
   },
 
-  _formattingEnabled: null,
-
-  get formattingEnabled() {
-    if (this._formattingEnabled === null) {
-      this._formattingEnabled = Services.prefs.getBoolPref("browser.urlbar.formatting.enabled");
-    }
-    return this._formattingEnabled;
-  },
-
-  _mayTrimURLs: null,
-
-  get mayTrimURLs() {
-    if (this._mayTrimURLs === null) {
-      this._mayTrimURLs = Services.prefs.getBoolPref("browser.urlbar.trimURLs");
-    }
-    return this._mayTrimURLs;
-  },
-
   supportsCommand : function(cmd) {
     var isSupported = false;
     switch (cmd) {
       case "cmd_back":
       case "cmd_forward":
       case "cmd_reload":
       case "cmd_forceReload":
       case "cmd_stop":
@@ -1292,17 +1013,17 @@ var BrowserUI = {
       case "cmd_go":
         this.goToURI();
         break;
       case "cmd_home":
         this.goToURI(Browser.getHomePage());
         break;
       case "cmd_openLocation":
         ContextUI.displayNavbar();
-        this._editURI(true);
+        this._edit.beginEditing(true);
         break;
       case "cmd_addBookmark":
         ContextUI.displayNavbar();
         Appbar.onStarButton(true);
         break;
       case "cmd_bookmarks":
         PanelUI.show("bookmarks-container");
         break;
@@ -1322,17 +1043,17 @@ var BrowserUI = {
         // Only close one window
         this._closeOrQuit();
         break;
       case "cmd_close":
         this._closeOrQuit();
         break;
       case "cmd_newTab":
         this.newTab();
-        this._editURI(false);
+        this._edit.beginEditing(false);
         ContextUI.peekTabs(kNewTabAnimationDelayMsec);
         break;
       case "cmd_closeTab":
         this.closeTab();
         break;
       case "cmd_undoCloseTab":
         this.undoCloseTab();
         break;
@@ -1363,35 +1084,32 @@ var BrowserUI = {
   },
 
   crashReportingPrefChanged: function crashReportingPrefChanged(aState) {
     CrashReporter.submitReports = aState;
   }
 };
 
 var StartUI = {
-  get isVisible() { return this.isStartPageVisible || this.isFiltering; },
+  get isVisible() { return this.isStartPageVisible; },
   get isStartPageVisible() { return Elements.windowState.hasAttribute("startpage"); },
-  get isFiltering() { return Elements.windowState.hasAttribute("filtering"); },
 
   get maxResultsPerSection() {
     return Services.prefs.getIntPref("browser.display.startUI.maxresults");
   },
 
   sections: [
     "TopSitesStartView",
     "TopSitesSnappedView",
     "BookmarksStartView",
     "HistoryStartView",
     "RemoteTabsStartView"
   ],
 
   init: function init() {
-    Elements.startUI.addEventListener("autocompletestart", this, false);
-    Elements.startUI.addEventListener("autocompleteend", this, false);
     Elements.startUI.addEventListener("contextmenu", this, false);
     Elements.startUI.addEventListener("click", this, false);
     Elements.startUI.addEventListener("MozMousePixelScroll", this, false);
 
     this.sections.forEach(function (sectionName) {
       let section = window[sectionName];
       if (section.init)
         section.init();
@@ -1420,44 +1138,24 @@ var StartUI = {
     this.sections.forEach(function (sectionName) {
       let section = window[sectionName];
       if (section.show)
         section.show();
     });
     return true;
   },
 
-  /** Show the autocomplete popup */
-  filter: function filter() {
-    if (this.isFiltering)
-      return;
-
-    BrowserUI._edit.openPopup();
-    Elements.windowState.setAttribute("filtering", "true");
-  },
-
-  /** Hide the autocomplete popup */
-  unfilter: function unfilter() {
-    if (!this.isFiltering)
-      return;
-
-    BrowserUI._edit.closePopup();
-    Elements.windowState.removeAttribute("filtering");
-  },
-
   /** Hide the Firefox start page */
   hide: function hide(aURI) {
     aURI = aURI || Browser.selectedBrowser.currentURI.spec;
     if (!this.isStartPageVisible || this.isStartURI(aURI))
       return false;
 
     Elements.contentShowing.removeAttribute("disabled");
     Elements.windowState.removeAttribute("startpage");
-
-    this.unfilter();
     return true;
   },
 
   /** Is the current tab supposed to show the Firefox start page? */
   isStartURI: function isStartURI(aURI) {
     aURI = aURI || Browser.selectedBrowser.currentURI.spec;
     return aURI == kStartOverlayURI || aURI == "about:home";
   },
@@ -1479,22 +1177,16 @@ var StartUI = {
       // Advanced notice to CAO, so we can shuffle the nav bar in advance
       // of the keyboard transition.
       ContentAreaObserver.navBarWillBlur();
     }
   },
 
   handleEvent: function handleEvent(aEvent) {
     switch (aEvent.type) {
-      case "autocompletestart":
-        this.filter();
-        break;
-      case "autocompleteend":
-        this.unfilter();
-        break;
       case "contextmenu":
         let event = document.createEvent("Events");
         event.initEvent("MozEdgeUICompleted", true, false);
         window.dispatchEvent(event);
         break;
       case "click":
         this.onClick(aEvent);
         break;
--- a/browser/metro/base/content/browser.css
+++ b/browser/metro/base/content/browser.css
@@ -90,21 +90,21 @@ setting[type="menulist"] {
 }
 
 #chrome-selection-overlay,
 #content-selection-overlay {
   -moz-binding: url("chrome://browser/content/bindings/selectionoverlay.xml#selection-binding");
 }
 
 #urlbar-edit {
-  -moz-binding: url("chrome://browser/content/bindings/autocomplete.xml#autocomplete");
+  -moz-binding: url("chrome://browser/content/bindings/urlbar.xml#urlbar");
 }
 
-#start-autocomplete {
-  -moz-binding: url("chrome://browser/content/bindings/autocomplete.xml#autocomplete-popup");
+#urlbar-autocomplete {
+  -moz-binding: url("chrome://browser/content/bindings/urlbar.xml#urlbar-autocomplete");
 }
 
 richgrid {
   -moz-binding: url("chrome://browser/content/bindings/grid.xml#richgrid");
 }
 
 richgriditem {
   -moz-binding: url("chrome://browser/content/bindings/grid.xml#richgrid-item");
--- a/browser/metro/base/content/browser.xul
+++ b/browser/metro/base/content/browser.xul
@@ -224,18 +224,16 @@
               <label class="meta-section-title" value="&snappedBookmarksHeader.label;"
                 onclick="PanelUI.show('bookmarks-container');"/>
               <label class="meta-section-title" value="&snappedHistoryHeader.label;"
                 onclick="PanelUI.show('history-container');" inputProcessing="true"/>
               <label id="snappedRemoteTabsLabel" class="meta-section-title" value="&snappedRemoteTabsHeader.label;"
                 onclick="PanelUI.show('remotetabs-container');" inputProcessing="true"/>
             </scrollbox>
           </hbox>
-          <!-- Autocompletion interface -->
-          <box id="start-autocomplete" observes="bcast_windowState"/>
         </hbox>
       </vbox> <!-- end tray -->
 
       <!-- Content viewport -->
       <stack id="content-viewport">
         <deck id="browsers" flex="1" observes="bcast_preciseInput"/>
         <box id="vertical-scroller" class="scroller" orient="vertical" end="0" top="0"/>
         <box id="horizontal-scroller" class="scroller" orient="horizontal" left="0" bottom="0"/>
@@ -252,54 +250,58 @@
               observes="cmd_back"></html:div>
 
     <!-- Navbar -->
     <appbar id="navbar" mousethrough="never" observes="bcast_windowState">
       <hbox id="progress-container" layer="true">
         <hbox id="progress-control" />
       </hbox>
 
-      <!-- Main Toolbar -->
-      <toolbar id="toolbar" observes="bcast_windowState" flex="1">
-        <observes element="bcast_windowState" attribute="*"/>
-        <observes element="bcast_urlbarState" attribute="*"/>
+      <vbox id="toolbar-autocomplete" flex="1">
+        <!-- Autocomplete -->
+        <scrollbox flex="1">
+          <hbox id="urlbar-autocomplete" observes="bcast_windowState"/>
+        </scrollbox>
 
-        <toolbarbutton id="back-button" class="appbar-primary" command="cmd_back"/>
-        <toolbarbutton id="forward-button" class="appbar-primary" command="cmd_forward"/>
+        <!-- Main Toolbar -->
+        <toolbar id="toolbar" observes="bcast_windowState" flex="1">
+          <observes element="bcast_windowState" attribute="*"/>
+          <observes element="bcast_urlbarState" attribute="*"/>
+
+          <toolbarbutton id="back-button" class="appbar-primary" command="cmd_back"/>
+          <toolbarbutton id="forward-button" class="appbar-primary" command="cmd_forward"/>
 
-        <hbox id="urlbar-container" flex="1" observes="bcast_urlbarState">
-          <hbox id="urlbar" flex="1">
-            <box id="identity-box" role="button">
-              <hbox id="identity-box-inner" align="center" mousethrough="always">
-                <image id="identity-icon"/>
-              </hbox>
-            </box>
+          <hbox id="urlbar-container" flex="1" observes="bcast_urlbarState">
+            <hbox id="urlbar" flex="1">
+              <box id="identity-box" role="button">
+                <hbox id="identity-box-inner" align="center" mousethrough="always">
+                  <image id="identity-icon"/>
+                </hbox>
+              </box>
 
-            <textbox id="urlbar-edit"
-                     type="url"
-                     class="uri-element"
-                     autocompletesearch="history"
-                     autocompletepopup="start-autocomplete"
-                     completeselectedindex="true"
-                     placeholder="&urlbar.emptytext;"
-                     flex="1"
-                     onpaste="this.focus();"
-                     ontextentered="BrowserUI.handleUrlbarEnter(param);"
-                     onblur="BrowserUI._urlbarBlurred();"/>
+              <textbox id="urlbar-edit"
+                       type="url"
+                       class="uri-element"
+                       autocompletesearch="history"
+                       autocompletepopup="urlbar-autocomplete"
+                       completeselectedindex="true"
+                       placeholder="&urlbar.emptytext;"
+                       flex="1"/>
+            </hbox>
+
+            <toolbarbutton id="reload-button" oncommand="CommandUpdater.doCommand(event.shiftKey ? 'cmd_forceReload' : 'cmd_reload');"/>
+            <toolbarbutton id="stop-button" command="cmd_stop"/>
           </hbox>
 
-          <toolbarbutton id="reload-button" oncommand="CommandUpdater.doCommand(event.shiftKey ? 'cmd_forceReload' : 'cmd_reload');"/>
-          <toolbarbutton id="stop-button" command="cmd_stop"/>
-        </hbox>
-
-        <toolbarbutton id="download-button" oncommand="Appbar.onDownloadButton()"/>
-        <toolbarbutton id="star-button" class="appbar-primary" type="checkbox" oncommand="Appbar.onStarButton()"/>
-        <toolbarbutton id="pin-button" class="appbar-primary" type="checkbox" oncommand="Appbar.onPinButton()"/>
-        <toolbarbutton id="menu-button" class="appbar-primary" oncommand="Appbar.onMenuButton(event)"/>
-       </toolbar>
+          <toolbarbutton id="download-button" oncommand="Appbar.onDownloadButton()"/>
+          <toolbarbutton id="star-button" class="appbar-primary" type="checkbox" oncommand="Appbar.onStarButton()"/>
+          <toolbarbutton id="pin-button" class="appbar-primary" type="checkbox" oncommand="Appbar.onPinButton()"/>
+          <toolbarbutton id="menu-button" class="appbar-primary" oncommand="Appbar.onMenuButton(event)"/>
+        </toolbar>
+      </vbox>
     </appbar>
 
     <vbox id="panel-container" hidden="true" class="window-width window-height meta" observes="bcast_windowState">
       <hbox id="panel-header">
         <toolbarbutton id="panel-close-button" command="cmd_panel"/>
 
         <menulist id="panel-view-switcher" oncommand="PanelUI.switchPane(this.value);">
           <menupopup>
--- a/browser/metro/base/jar.mn
+++ b/browser/metro/base/jar.mn
@@ -16,17 +16,17 @@ chrome.jar:
   content/bindings/toggleswitch.xml            (content/bindings/toggleswitch.xml)
   content/bindings/browser.xml                 (content/bindings/browser.xml)
   content/bindings/browser.js                  (content/bindings/browser.js)
   content/bindings/downloads.xml               (content/bindings/downloads.xml)
   content/bindings/console.xml                 (content/bindings/console.xml)
   content/bindings/dialog.xml                  (content/bindings/dialog.xml)
   content/bindings/arrowbox.xml                (content/bindings/arrowbox.xml)
   content/bindings/grid.xml                    (content/bindings/grid.xml)
-  content/bindings/autocomplete.xml            (content/bindings/autocomplete.xml)
+  content/bindings/urlbar.xml                  (content/bindings/urlbar.xml)
   content/bindings/appbar.xml                  (content/bindings/appbar.xml)
   content/bindings/flyoutpanel.xml             (content/bindings/flyoutpanel.xml)
   content/bindings/selectionoverlay.xml        (content/bindings/selectionoverlay.xml)
   content/bindings/cssthrobber.xml             (content/bindings/cssthrobber.xml)
   content/bindings/popup.xml                   (content/bindings/popup.xml)
 
 * content/flyouts/flyoutUI.js                  (content/flyouts/flyoutUI.js)
 * content/flyouts/aboutFlyout.js               (content/flyouts/aboutFlyout.js)
--- a/browser/metro/base/tests/mochitest/browser_canonizeURL.js
+++ b/browser/metro/base/tests/mochitest/browser_canonizeURL.js
@@ -11,11 +11,11 @@ function test() {
     ["example", {ctrlKey: true}, "http://www.example.com/"],
     ["example.org", {ctrlKey: true}, "example.org"],
     ["example", {shiftKey: true}, "http://www.example.net/"],
     ["example", {shiftKey: true, ctrlKey: true}, "http://www.example.org/"],
     ["  example  ", {ctrlKey: true}, "http://www.example.com/"],
     [" example/a ", {ctrlKey: true}, "http://www.example.com/a"]
   ];
   for (let [input, modifiers, result] of testcases) {
-    is(BrowserUI._canonizeURL(input, modifiers), result, input + " -> " + result);
+    is(BrowserUI._edit._canonizeURL(input, modifiers), result, input + " -> " + result);
   }
 }
--- a/browser/metro/theme/browser.css
+++ b/browser/metro/theme/browser.css
@@ -243,17 +243,17 @@ documenttab[selected] .documenttab-selec
   padding: 0px;
   margin-top: -30px;
   margin-left: -18px;
   pointer-events: auto;
 }
 
 /* Toolbar ------------------------------------------------------------------ */
 
-#toolbar {
+#toolbar-autocomplete {
   background-color: @panel_light_color@;
 }
 
 #toolbar > #back-button {
   list-style-image: url(chrome://browser/skin/images/appbar-back.png);
   position: relative;
   z-index: 1;
   transition: opacity @forward_transition_length@ ease-out;
@@ -359,26 +359,26 @@ appbar {
   position: fixed;
   bottom: 0;
   width: 100%;
   transform: translateY(100%);
   transition: transform @metro_animation_duration@ @metro_animation_easing@;
   font-size: 0;
 }
 
-appbar > toolbar {
+appbar toolbar {
   -moz-appearance: none;
   -moz-box-align: center;
   border: 0;
   width: 100%;
   min-height: @toolbar_height@;
   font-size: 1rem;
 }
 
-appbar > toolbar > toolbarbutton {
+appbar toolbar > toolbarbutton {
   border: 0;
   margin: 0 @toolbar_horizontal_spacing@;
   padding: 0;
   /* Don't inherit background-color from toolbarbutton[checked="true"] */
   background-color: transparent;
 }
 
 appbar > toolbar > toolbarbutton[disabled] {