Bug 1491245 - Introduce UrlbarValueFormatter and use it in both UrlbarInput and urlbarBindings.xml. r=Standard8
authorDão Gottwald <dao@mozilla.com>
Tue, 02 Oct 2018 19:27:42 +0000
changeset 439291 a4d2c14d8fa5ca3531d15c83fdb1616b3e7fcfc8
parent 439290 f2737c4b2abebdc89bd3eb464ef1906df6234eb8
child 439292 fa95314b2d87293cbc150662dc5eaadd73624cf0
push id34760
push userdvarga@mozilla.com
push dateWed, 03 Oct 2018 04:19:01 +0000
treeherdermozilla-central@9e0a27bf253e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersStandard8
bugs1491245
milestone64.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 1491245 - Introduce UrlbarValueFormatter and use it in both UrlbarInput and urlbarBindings.xml. r=Standard8 Differential Revision: https://phabricator.services.mozilla.com/D7492
browser/base/content/browser.js
browser/base/content/test/urlbar/browser_urlOverflow.js
browser/base/content/urlbarBindings.xml
browser/components/urlbar/UrlbarInput.jsm
browser/components/urlbar/UrlbarPrefs.jsm
browser/components/urlbar/UrlbarValueFormatter.jsm
browser/components/urlbar/moz.build
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -64,16 +64,17 @@ XPCOMUtils.defineLazyModuleGetters(this,
   SitePermissions: "resource:///modules/SitePermissions.jsm",
   TabCrashHandler: "resource:///modules/ContentCrashHandlers.jsm",
   TelemetryStopwatch: "resource://gre/modules/TelemetryStopwatch.jsm",
   Translation: "resource:///modules/translation/Translation.jsm",
   UITour: "resource:///modules/UITour.jsm",
   UpdateUtils: "resource://gre/modules/UpdateUtils.jsm",
   UrlbarInput: "resource:///modules/UrlbarInput.jsm",
   UrlbarPrefs: "resource:///modules/UrlbarPrefs.jsm",
+  UrlbarValueFormatter: "resource:///modules/UrlbarValueFormatter.jsm",
   Utils: "resource://gre/modules/sessionstore/Utils.jsm",
   Weave: "resource://services-sync/main.js",
   WebNavigationFrames: "resource://gre/modules/WebNavigationFrames.jsm",
   fxAccounts: "resource://gre/modules/FxAccounts.jsm",
   webrtcUI: "resource:///modules/webrtcUI.jsm",
   ZoomUI: "resource:///modules/ZoomUI.jsm",
 });
 
--- a/browser/base/content/test/urlbar/browser_urlOverflow.js
+++ b/browser/base/content/test/urlbar/browser_urlOverflow.js
@@ -6,40 +6,40 @@ async function testVal(aExpected, overfl
   info(`Testing ${aExpected}`);
   URLBarSetURI(makeURI(aExpected));
 
   Assert.equal(gURLBar.selectionStart, gURLBar.selectionEnd,
     "Selection sanity check");
 
   gURLBar.focus();
   Assert.equal(document.activeElement, gURLBar.inputField, "URL Bar should be focused");
-  Assert.equal(gURLBar.scheme.value, "", "Check the scheme value");
-  Assert.equal(getComputedStyle(gURLBar.scheme).visibility, "hidden",
+  Assert.equal(gURLBar.valueFormatter.scheme.value, "", "Check the scheme value");
+  Assert.equal(getComputedStyle(gURLBar.valueFormatter.scheme).visibility, "hidden",
                "Check the scheme box visibility");
 
   gURLBar.blur();
   await window.promiseDocumentFlushed(() => {});
   // The attribute doesn't always change, so we can't use waitForAttribute.
   await TestUtils.waitForCondition(
     () => gURLBar.getAttribute("textoverflow") === overflowSide);
 
   let scheme = aExpected.match(/^([a-z]+:\/{0,2})/)[1];
   // We strip http, so we should not show the scheme for it.
   if (scheme == "http://" && Services.prefs.getBoolPref("browser.urlbar.trimURLs", true))
     scheme = "";
 
-  Assert.equal(gURLBar.scheme.value, scheme, "Check the scheme value");
+  Assert.equal(gURLBar.valueFormatter.scheme.value, scheme, "Check the scheme value");
   let isOverflowed = gURLBar.inputField.scrollWidth > gURLBar.inputField.clientWidth;
   Assert.equal(isOverflowed, !!overflowSide, "Check The input field overflow");
   Assert.equal(gURLBar.getAttribute("textoverflow"), overflowSide,
                "Check the textoverflow attribute");
   if (overflowSide) {
     let side = gURLBar.inputField.scrollLeft == 0 ? "end" : "start";
     Assert.equal(side, overflowSide, "Check the overflow side");
-    Assert.equal(getComputedStyle(gURLBar.scheme).visibility,
+    Assert.equal(getComputedStyle(gURLBar.valueFormatter.scheme).visibility,
                  scheme && isOverflowed && overflowSide == "start" ? "visible" : "hidden",
                  "Check the scheme box visibility");
   }
 }
 
 add_task(async function() {
   // We use a new tab for the test to be sure all the tab switching and loading
   // is complete before starting, otherwise onLocationChange for this tab could
--- a/browser/base/content/urlbarBindings.xml
+++ b/browser/base/content/urlbarBindings.xml
@@ -94,16 +94,21 @@ file, You can obtain one at http://mozil
     </content>
 
     <implementation implements="nsIObserver">
       <field name="ExtensionSearchHandler" readonly="true">
         (ChromeUtils.import("resource://gre/modules/ExtensionSearchHandler.jsm", {})).ExtensionSearchHandler;
       </field>
 
       <constructor><![CDATA[
+        // UrlbarInput compatibility shims
+        this.document = document;
+        this.window = window;
+        this.textbox = this;
+
         this._prefs = Cc["@mozilla.org/preferences-service;1"]
                         .getService(Ci.nsIPrefService)
                         .getBranch("browser.urlbar.");
         this._prefs.addObserver("", this);
 
         this._defaultPrefs = Cc["@mozilla.org/preferences-service;1"]
                                .getService(Ci.nsIPrefService)
                                .getDefaultBranch("browser.urlbar.");
@@ -113,17 +118,16 @@ file, You can obtain one at http://mozil
 
         this.openInTab = this._prefs.getBoolPref("openintab");
         this.clickSelectsAll = this._prefs.getBoolPref("clickSelectsAll");
         this.doubleClickSelectsAll = this._prefs.getBoolPref("doubleClickSelectsAll");
         this.completeDefaultIndex = this._prefs.getBoolPref("autoFill");
         this.speculativeConnectEnabled = this._prefs.getBoolPref("speculativeConnect.enabled");
         this.urlbarSearchSuggestEnabled = this._prefs.getBoolPref("suggest.searches");
         this.timeout = this._prefs.getIntPref("delay");
-        this._formattingEnabled = this._prefs.getBoolPref("formatting.enabled");
         this._mayTrimURLs = this._prefs.getBoolPref("trimURLs");
         this._adoptIntoActiveWindow = this._prefs.getBoolPref("switchTabs.adoptIntoActiveWindow");
         this._ctrlCanonizesURLs = this._prefs.getBoolPref("ctrlCanonizesURLs");
         this.inputField.controllers.insertControllerAt(0, this._copyCutController);
         this.inputField.addEventListener("paste", this);
         this.inputField.addEventListener("mousedown", this);
         this.inputField.addEventListener("mousemove", this);
         this.inputField.addEventListener("mouseout", this);
@@ -205,18 +209,18 @@ file, You can obtain one at http://mozil
 
         // Null out the one-offs' popup and textbox so that it cleans up its
         // internal state for both.  Most importantly, it removes the event
         // listeners that it added to both.
         this.popup.oneOffSearchButtons.popup = null;
         this.popup.oneOffSearchButtons.textbox = null;
       ]]></destructor>
 
-      <field name="scheme" readonly="true">
-        document.getAnonymousElementByAttribute(this, "anonid", "scheme");
+      <field name="valueFormatter" readonly="true">
+        new UrlbarValueFormatter(this);
       </field>
 
       <field name="goButton">
         document.getAnonymousElementByAttribute(this, "anonid", "urlbar-go-button");
       </field>
 
       <field name="_value">""</field>
       <field name="gotResultForCurrentQuery">false</field>
@@ -550,318 +554,23 @@ file, You can obtain one at http://mozil
         <parameter name="aURL"/>
         <body><![CDATA[
           // This method must not modify the given URL such that calling
           // nsIURIFixup::createFixupURI with the result will produce a different URI.
           return this._mayTrimURLs ? trimURL(aURL) : aURL;
         ]]></body>
       </method>
 
-      <field name="_formattingEnabled">true</field>
-
-      <!--
-        This is used only as an optimization to avoid removing formatting in
-        the _remove* format methods when no formatting is actually applied.
-      -->
-      <field name="_formattingApplied">false</field>
-
       <!--
         This method tries to apply styling to the text in the input, depending
         on the text.  See the _format* methods.
       -->
       <method name="formatValue">
         <body><![CDATA[
-          if (!this.editor || !this.editor.rootElement.firstChild.textContent) {
-            return;
-          }
-
-          // Remove the current formatting.
-          this._removeURLFormat();
-          this._removeSearchAliasFormat();
-          this._formattingApplied = false;
-
-          // Apply new formatting.  Formatter methods should return true if they
-          // successfully formatted the value and false if not.  We apply only
-          // one formatter at a time, so we stop at the first successful one.
-          let formatterMethods = [
-            "_formatURL",
-            "_formatSearchAlias",
-          ];
-          this._formattingApplied = formatterMethods.some(m => this[m]());
-        ]]></body>
-      </method>
-
-      <method name="_removeURLFormat">
-        <body><![CDATA[
-          this.scheme.value = "";
-          if (!this._formattingApplied) {
-            return;
-          }
-          let controller = this.editor.selectionController;
-          let strikeOut =
-            controller.getSelection(controller.SELECTION_URLSTRIKEOUT);
-          strikeOut.removeAllRanges();
-          let selection =
-            controller.getSelection(controller.SELECTION_URLSECONDARY);
-          selection.removeAllRanges();
-          this._formatScheme(controller.SELECTION_URLSTRIKEOUT, true);
-          this._formatScheme(controller.SELECTION_URLSECONDARY, true);
-          this.inputField.style.setProperty("--urlbar-scheme-size", "0px");
-        ]]></body>
-      </method>
-
-      <!--
-        If the input value is a URL and the input is not focused, this
-        formatter method highlights the domain, and if mixed content is present,
-        it crosses out the https scheme.  It also ensures that the host is
-        visible (not scrolled out of sight).
-
-        @param  onlyEnsureFormattedHostVisible
-                Pass true to skip formatting and instead only ensure that the
-                host is visible.
-        @return True if formatting was applied and false if not.
-      -->
-      <method name="_formatURL">
-        <parameter name="onlyEnsureFormattedHostVisible"/>
-        <body><![CDATA[
-          if (this.focused) {
-            return false;
-          }
-
-          let textNode = this.editor.rootElement.firstChild;
-          let value = textNode.textContent;
-
-          // Get the URL from the fixup service:
-          let flags = Services.uriFixup.FIXUP_FLAG_FIX_SCHEME_TYPOS |
-                      Services.uriFixup.FIXUP_FLAG_ALLOW_KEYWORD_LOOKUP;
-          let uriInfo;
-          try {
-            uriInfo = Services.uriFixup.getFixupURIInfo(value, flags);
-          } catch (ex) {}
-          // Ignore if we couldn't make a URI out of this, the URI resulted in a search,
-          // or the URI has a non-http(s)/ftp protocol.
-          if (!uriInfo ||
-              !uriInfo.fixedURI ||
-              uriInfo.keywordProviderName ||
-              !["http", "https", "ftp"].includes(uriInfo.fixedURI.scheme)) {
-            return false;
-          }
-
-          // If we trimmed off the http scheme, ensure we stick it back on before
-          // trying to figure out what domain we're accessing, so we don't get
-          // confused by user:pass@host http URLs. We later use
-          // trimmedLength to ensure we don't count the length of a trimmed protocol
-          // when determining which parts of the URL to highlight as "preDomain".
-          let trimmedLength = 0;
-          if (uriInfo.fixedURI.scheme == "http" && !value.startsWith("http://")) {
-            value = "http://" + value;
-            trimmedLength = "http://".length;
-          }
-
-          let matchedURL = value.match(/^(([a-z]+:\/\/)(?:[^\/#?]+@)?)(\S+?)(?::\d+)?\s*(?:[\/#?]|$)/);
-          if (!matchedURL) {
-            return false;
-          }
-
-          let [, preDomain, schemeWSlashes, domain] = matchedURL;
-          // We strip http, so we should not show the scheme box for it.
-          if (!this._mayTrimURLs || schemeWSlashes != "http://") {
-            this.scheme.value = schemeWSlashes;
-            this.inputField.style.setProperty("--urlbar-scheme-size",
-                                              schemeWSlashes.length + "ch");
-          }
-
-          // Used to avoid re-entrance in the requestAnimationFrame callback.
-          let instance = this._formatURLInstance = {};
-
-          // Make sure the host is always visible. Since it is aligned on
-          // the first strong directional character, we set scrollLeft
-          // appropriately to ensure the domain stays visible in case of an
-          // overflow.
-          window.requestAnimationFrame(() => {
-            // Check for re-entrance. On focus change this formatting code is
-            // invoked regardless, thus this should be enough.
-            if (this._formatURLInstance != instance) {
-              return;
-            }
-            let directionality = window.windowUtils.getDirectionFromText(domain);
-            // In the future, for example in bug 525831, we may add a forceRTL
-            // char just after the domain, and in such a case we should not
-            // scroll to the left.
-            if (directionality == window.windowUtils.DIRECTION_RTL &&
-                value[preDomain.length + domain.length] != "\u200E") {
-              this.inputField.scrollLeft = this.inputField.scrollLeftMax;
-            }
-          });
-
-          if (onlyEnsureFormattedHostVisible || !this._formattingEnabled) {
-            return false;
-          }
-
-          let controller = this.editor.selectionController;
-
-          this._formatScheme(controller.SELECTION_URLSECONDARY);
-
-          // Strike out the "https" part if mixed active content is loaded.
-          if (this.getAttribute("pageproxystate") == "valid" &&
-              value.startsWith("https:") &&
-              gBrowser.securityUI.state &
-                Ci.nsIWebProgressListener.STATE_LOADED_MIXED_ACTIVE_CONTENT) {
-            let range = document.createRange();
-            range.setStart(textNode, 0);
-            range.setEnd(textNode, 5);
-            let strikeOut =
-              controller.getSelection(controller.SELECTION_URLSTRIKEOUT);
-            strikeOut.addRange(range);
-            this._formatScheme(controller.SELECTION_URLSTRIKEOUT);
-          }
-
-          let baseDomain = domain;
-          let subDomain = "";
-          try {
-            baseDomain = Services.eTLD.getBaseDomainFromHost(uriInfo.fixedURI.host);
-            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 selection =
-            controller.getSelection(controller.SELECTION_URLSECONDARY);
-
-          let rangeLength = preDomain.length + subDomain.length - trimmedLength;
-          if (rangeLength) {
-            let range = document.createRange();
-            range.setStart(textNode, 0);
-            range.setEnd(textNode, rangeLength);
-            selection.addRange(range);
-          }
-
-          let startRest = preDomain.length + domain.length - trimmedLength;
-          if (startRest < value.length - trimmedLength) {
-            let range = document.createRange();
-            range.setStart(textNode, startRest);
-            range.setEnd(textNode, value.length - trimmedLength);
-            selection.addRange(range);
-          }
-
-          return true;
-        ]]></body>
-      </method>
-
-      <method name="_formatScheme">
-        <parameter name="selectionType"/>
-        <parameter name="clear"/>
-        <body><![CDATA[
-          let editor = this.scheme.editor;
-          let controller = editor.selectionController;
-          let textNode = editor.rootElement.firstChild;
-          let selection = controller.getSelection(selectionType);
-          if (clear) {
-            selection.removeAllRanges();
-          } else {
-            let r = document.createRange();
-            r.setStart(textNode, 0);
-            r.setEnd(textNode, textNode.textContent.length);
-            selection.addRange(r);
-          }
-        ]]></body>
-      </method>
-
-      <method name="_removeSearchAliasFormat">
-        <body><![CDATA[
-          if (!this._formattingApplied) {
-            return;
-          }
-          let selection = this.editor.selectionController.getSelection(
-            Ci.nsISelectionController.SELECTION_FIND
-          );
-          selection.removeAllRanges();
-        ]]></body>
-      </method>
-
-      <!--
-        If the input value starts with a search alias, this formatter method
-        highlights it.
-
-        @return True if formatting was applied and false if not.
-      -->
-      <method name="_formatSearchAlias">
-        <body><![CDATA[
-          if (!this._formattingEnabled) {
-            return false;
-          }
-
-          // There can only be an alias to highlight if the heuristic result is
-          // an alias searchengine result and it's either currently selected or
-          // was selected when the popup was closed.  We also need to check
-          // whether a one-off search button is selected because in that case
-          // there won't be a selection but the alias should not be highlighted.
-          let heuristicItem = this.popup.richlistbox.children[0] || null;
-          let alias =
-            (this.popup.selectedIndex == 0 ||
-             (this.popup.selectedIndex < 0 &&
-              this.popup._previousSelectedIndex == 0)) &&
-            !this.popup.oneOffSearchButtons.selectedButton &&
-            heuristicItem &&
-            heuristicItem.getAttribute("actiontype") == "searchengine" &&
-            this._parseActionUrl(heuristicItem.getAttribute("url")).params.alias;
-          if (!alias) {
-            return false;
-          }
-
-          let textNode = this.editor.rootElement.firstChild;
-          let value = textNode.textContent;
-
-          let index = value.indexOf(alias);
-          if (index < 0) {
-            return false;
-          }
-
-          // We abuse the SELECTION_FIND selection type to do our highlighting.
-          // It's the only type that works with Selection.setColors().
-          let selection = this.editor.selectionController.getSelection(
-            Ci.nsISelectionController.SELECTION_FIND
-          );
-
-          let range = document.createRange();
-          range.setStart(textNode, index);
-          range.setEnd(textNode, index + alias.length);
-          selection.addRange(range);
-
-          let fg = "#2362d7";
-          let bg = "#d2e6fd";
-
-          // Selection.setColors() will swap the given foreground and background
-          // colors if it detects that the contrast between the background
-          // color and the frame color is too low.  Normally we don't want that
-          // to happen; we want it to use our colors as given (even if setColors
-          // thinks the contrast is too low).  But it's a nice feature for non-
-          // default themes, where the contrast between our background color and
-          // the input's frame color might actually be too low.  We can
-          // (hackily) force setColors to use our colors as given by passing
-          // them as the alternate colors.  Otherwise, allow setColors to swap
-          // them, which we can do by passing "currentColor".  See
-          // nsTextPaintStyle::GetHighlightColors for details.
-          if (this.querySelector(":-moz-lwtheme") ||
-              (AppConstants.platform == "win" &&
-               window.matchMedia("(-moz-windows-default-theme: 0)").matches)) {
-            // non-default theme(s)
-            selection.setColors(fg, bg, "currentColor", "currentColor");
-          } else {
-            // default themes
-            selection.setColors(fg, bg, fg, bg);
-          }
-
-          return true;
+          this.valueFormatter.update();
         ]]></body>
       </method>
 
       <method name="handleRevert">
         <body><![CDATA[
           var isScrolling = this.popupOpen;
 
           gBrowser.userTypedValue = null;
@@ -1482,19 +1191,16 @@ file, You can obtain one at http://mozil
                 this[aData] = this._prefs.getBoolPref(aData);
                 break;
               case "autoFill":
                 this.completeDefaultIndex = this._prefs.getBoolPref(aData);
                 break;
               case "delay":
                 this.timeout = this._prefs.getIntPref(aData);
                 break;
-              case "formatting.enabled":
-                this._formattingEnabled = this._prefs.getBoolPref(aData);
-                break;
               case "ctrlCanonizesURLs":
                 this._ctrlCanonizesURLs = this._prefs.getBoolPref(aData);
                 break;
               case "speculativeConnect.enabled":
                 this.speculativeConnectEnabled = this._prefs.getBoolPref(aData);
                 break;
               case "openintab":
                 this.openInTab = this._prefs.getBoolPref(aData);
@@ -1620,26 +1326,26 @@ file, You can obtain one at http://mozil
               break;
             case "resize":
               if (aEvent.target == window) {
                 // Close the popup since it would be wrongly sized, we'll
                 // recalculate a proper size on reopening. For example, this may
                 // happen when using special OS resize functions like Win+Arrow.
                 this.closePopup();
 
-                // Make sure the host remains visible in the input field (via
-                // _formatURL) when the window is resized.  We don't want to
+                // Make sure the host remains visible in the input field
+                // when the window is resized.  We don't want to
                 // hurt resize performance though, so do this only after resize
                 // events have stopped and a small timeout has elapsed.
                 if (this._resizeThrottleTimeout) {
                   clearTimeout(this._resizeThrottleTimeout);
                 }
                 this._resizeThrottleTimeout = setTimeout(() => {
                   this._resizeThrottleTimeout = null;
-                  this._formatURL(true);
+                  this.valueFormatter.ensureFormattedHostVisible();
                 }, 100);
               }
               break;
           }
         ]]></body>
       </method>
 
       <method name="updateTextOverflow">
--- a/browser/components/urlbar/UrlbarInput.jsm
+++ b/browser/components/urlbar/UrlbarInput.jsm
@@ -9,16 +9,17 @@ var EXPORTED_SYMBOLS = ["UrlbarInput"];
 ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
 
 XPCOMUtils.defineLazyModuleGetters(this, {
   PrivateBrowsingUtils: "resource://gre/modules/PrivateBrowsingUtils.jsm",
   QueryContext: "resource:///modules/UrlbarController.jsm",
   Services: "resource://gre/modules/Services.jsm",
   UrlbarController: "resource:///modules/UrlbarController.jsm",
   UrlbarPrefs: "resource:///modules/UrlbarPrefs.jsm",
+  UrlbarValueFormatter: "resource:///modules/UrlbarValueFormatter.jsm",
   UrlbarView: "resource:///modules/UrlbarView.jsm",
 });
 
 XPCOMUtils.defineLazyServiceGetter(this, "ClipboardHelper",
                                    "@mozilla.org/widget/clipboardhelper;1",
                                    "nsIClipboardHelper");
 
 /**
@@ -90,17 +91,23 @@ class UrlbarInput {
           if (setter in this) {
             return this[setter](val);
           }
           return this.textbox[property] = val;
         },
       });
     }
 
+    XPCOMUtils.defineLazyGetter(this, "valueFormatter", () => {
+      return new UrlbarValueFormatter(this);
+    });
+
     this.addEventListener("input", this);
+    this.inputField.addEventListener("blur", this);
+    this.inputField.addEventListener("focus", this);
     this.inputField.addEventListener("mousedown", this);
     this.inputField.addEventListener("overflow", this);
     this.inputField.addEventListener("underflow", this);
     this.inputField.addEventListener("scrollend", this);
     this.inputField.addEventListener("select", this);
 
     this.inputField.controllers.insertControllerAt(0, new CopyCutController(this));
   }
@@ -111,17 +118,21 @@ class UrlbarInput {
    *
    * @param {string} val
    *   The string to be trimmed if it appears to be URI
    */
   trimValue(val) {
     return UrlbarPrefs.get("trimURLs") ? this.window.trimURL(val) : val;
   }
 
+  /**
+   * Applies styling to the text in the urlbar input, depending on the text.
+   */
   formatValue() {
+    this.valueFormatter.update();
   }
 
   closePopup() {
     this.view.close();
   }
 
   openResults() {
     this.view.open();
@@ -303,16 +314,24 @@ class UrlbarInput {
       }
     }
 
     return action;
   }
 
   // Event handlers below.
 
+  _onblur(event) {
+    this.formatValue();
+  }
+
+  _onfocus(event) {
+    this.formatValue();
+  }
+
   _onmousedown(event) {
     if (event.button == 0 &&
         event.detail == 2 &&
         UrlbarPrefs.get("doubleClickSelectsAll")) {
       this.editor.selectAll();
       event.preventDefault();
     }
   }
--- a/browser/components/urlbar/UrlbarPrefs.jsm
+++ b/browser/components/urlbar/UrlbarPrefs.jsm
@@ -56,16 +56,19 @@ const PREF_URLBAR_DEFAULTS = new Map([
   // If true, this optimizes for replacing the full URL rather than selecting a
   // portion of it. This also copies the urlbar value to the selection
   // clipboard on systems that support it.
   ["doubleClickSelectsAll", false],
 
   // When true, `javascript:` URLs are not included in search results.
   ["filter.javascript", true],
 
+  // Applies URL highlighting and other styling to the text in the urlbar input.
+  ["formatting.enabled", false],
+
   // Allows results from one search to be reused in the next search.  One of the
   // INSERTMETHOD values.
   ["insertMethod", UrlbarUtils.INSERTMETHOD.MERGE_RELATED],
 
   // Controls how URLs are matched against the user's search string.  See
   // mozIPlacesAutoComplete.
   ["matchBehavior", Ci.mozIPlacesAutoComplete.MATCH_BOUNDARY_ANYWHERE],
 
copy from browser/base/content/urlbarBindings.xml
copy to browser/components/urlbar/UrlbarValueFormatter.jsm
--- a/browser/base/content/urlbarBindings.xml
+++ b/browser/components/urlbar/UrlbarValueFormatter.jsm
@@ -1,3165 +1,337 @@
-<?xml version="1.0"?>
-
-<!--
--*- Mode: HTML -*-
-This Source Code Form is subject to the terms of the Mozilla Public
-License, v. 2.0. If a copy of the MPL was not distributed with this
-file, You can obtain one at http://mozilla.org/MPL/2.0/.
--->
-
-<!DOCTYPE bindings [
-<!ENTITY % notificationDTD SYSTEM "chrome://global/locale/notification.dtd">
-%notificationDTD;
-<!ENTITY % browserDTD SYSTEM "chrome://browser/locale/browser.dtd">
-%browserDTD;
-<!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd">
-%brandDTD;
-]>
-
-<bindings id="urlbarBindings" xmlns="http://www.mozilla.org/xbl"
-          xmlns:html="http://www.w3.org/1999/xhtml"
-          xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
-          xmlns:xbl="http://www.mozilla.org/xbl">
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-  <binding id="urlbar" extends="chrome://global/content/bindings/textbox.xml#textbox">
-    <content sizetopopup="pref">
-      <xul:hbox flex="1" class="urlbar-textbox-container" tooltip="aHTMLTooltip">
-        <children includes="image|deck|stack|box"/>
-        <xul:moz-input-box anonid="moz-input-box"
-                  class="urlbar-input-box"
-                  flex="1">
-          <children/>
-          <html:input anonid="scheme"
-                      class="urlbar-scheme textbox-input"
-                      required="required"
-                      xbl:inherits="textoverflow,focused"/>
-          <html:input anonid="input"
-                      class="urlbar-input textbox-input"
-                      allowevents="true"
-                      inputmode="mozAwesomebar"
-                      xbl:inherits="value,maxlength,disabled,size,readonly,placeholder,tabindex,accesskey,focused,textoverflow"/>
-        </xul:moz-input-box>
-        <xul:image anonid="urlbar-go-button"
-                   class="urlbar-go-button urlbar-icon"
-                   onclick="gURLBar.handleCommand(event);"
-                   tooltiptext="&goEndCap.tooltip;"
-                   xbl:inherits="pageproxystate,parentfocused=focused,usertyping"/>
-        <xul:dropmarker anonid="historydropmarker"
-                        class="urlbar-history-dropmarker urlbar-icon chromeclass-toolbar-additional"
-                        tooltiptext="&urlbar.openHistoryPopup.tooltip;"
-                        allowevents="true"
-                        xbl:inherits="open,parentfocused=focused,usertyping"/>
-        <children includes="hbox"/>
-      </xul:hbox>
-      <xul:popupset anonid="popupset"
-                    class="autocomplete-result-popupset"/>
-      <children includes="toolbarbutton"/>
-    </content>
-  </binding>
+"use strict";
 
-  <binding id="legacy-urlbar" extends="chrome://global/content/bindings/autocomplete.xml#autocomplete">
+var EXPORTED_SYMBOLS = ["UrlbarValueFormatter"];
 
-    <content sizetopopup="pref">
-      <xul:hbox flex="1" class="urlbar-textbox-container" tooltip="aHTMLTooltip">
-        <children includes="image|deck|stack|box"/>
-        <xul:moz-input-box anonid="moz-input-box"
-                  class="urlbar-input-box"
-                  flex="1">
-          <children/>
-          <html:input anonid="scheme"
-                      class="urlbar-scheme textbox-input"
-                      required="required"
-                      xbl:inherits="textoverflow,focused"/>
-          <html:input anonid="input"
-                      class="urlbar-input textbox-input"
-                      allowevents="true"
-                      inputmode="mozAwesomebar"
-                      xbl:inherits="value,maxlength,disabled,size,readonly,placeholder,tabindex,accesskey,focused,textoverflow"/>
-        </xul:moz-input-box>
-        <xul:image anonid="urlbar-go-button"
-                   class="urlbar-go-button urlbar-icon"
-                   onclick="gURLBar.handleCommand(event);"
-                   tooltiptext="&goEndCap.tooltip;"
-                   xbl:inherits="pageproxystate,parentfocused=focused,usertyping"/>
-        <xul:dropmarker anonid="historydropmarker"
-                        class="urlbar-history-dropmarker urlbar-icon chromeclass-toolbar-additional"
-                        tooltiptext="&urlbar.openHistoryPopup.tooltip;"
-                        allowevents="true"
-                        xbl:inherits="open,parentfocused=focused,usertyping"/>
-        <children includes="hbox"/>
-      </xul:hbox>
-      <xul:popupset anonid="popupset"
-                    class="autocomplete-result-popupset"/>
-      <children includes="toolbarbutton"/>
-    </content>
-
-    <implementation implements="nsIObserver">
-      <field name="ExtensionSearchHandler" readonly="true">
-        (ChromeUtils.import("resource://gre/modules/ExtensionSearchHandler.jsm", {})).ExtensionSearchHandler;
-      </field>
-
-      <constructor><![CDATA[
-        this._prefs = Cc["@mozilla.org/preferences-service;1"]
-                        .getService(Ci.nsIPrefService)
-                        .getBranch("browser.urlbar.");
-        this._prefs.addObserver("", this);
-
-        this._defaultPrefs = Cc["@mozilla.org/preferences-service;1"]
-                               .getService(Ci.nsIPrefService)
-                               .getDefaultBranch("browser.urlbar.");
-
-        Services.prefs.addObserver("browser.search.suggest.enabled", this);
-        this.browserSearchSuggestEnabled = Services.prefs.getBoolPref("browser.search.suggest.enabled");
+ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
 
-        this.openInTab = this._prefs.getBoolPref("openintab");
-        this.clickSelectsAll = this._prefs.getBoolPref("clickSelectsAll");
-        this.doubleClickSelectsAll = this._prefs.getBoolPref("doubleClickSelectsAll");
-        this.completeDefaultIndex = this._prefs.getBoolPref("autoFill");
-        this.speculativeConnectEnabled = this._prefs.getBoolPref("speculativeConnect.enabled");
-        this.urlbarSearchSuggestEnabled = this._prefs.getBoolPref("suggest.searches");
-        this.timeout = this._prefs.getIntPref("delay");
-        this._formattingEnabled = this._prefs.getBoolPref("formatting.enabled");
-        this._mayTrimURLs = this._prefs.getBoolPref("trimURLs");
-        this._adoptIntoActiveWindow = this._prefs.getBoolPref("switchTabs.adoptIntoActiveWindow");
-        this._ctrlCanonizesURLs = this._prefs.getBoolPref("ctrlCanonizesURLs");
-        this.inputField.controllers.insertControllerAt(0, this._copyCutController);
-        this.inputField.addEventListener("paste", this);
-        this.inputField.addEventListener("mousedown", this);
-        this.inputField.addEventListener("mousemove", this);
-        this.inputField.addEventListener("mouseout", this);
-        this.inputField.addEventListener("overflow", this);
-        this.inputField.addEventListener("underflow", this);
-        this.inputField.addEventListener("scrollend", this);
-        window.addEventListener("resize", this);
-
-        var textBox = document.getAnonymousElementByAttribute(this,
-                                                "anonid", "moz-input-box");
-        // Force the Custom Element to upgrade until Bug 1470242 handles this:
-        customElements.upgrade(textBox);
-        var cxmenu = textBox.menupopup;
-        var pasteAndGo;
-        cxmenu.addEventListener("popupshowing", function() {
-          if (!pasteAndGo)
-            return;
-          var controller = document.commandDispatcher.getControllerForCommand("cmd_paste");
-          var enabled = controller.isCommandEnabled("cmd_paste");
-          if (enabled)
-            pasteAndGo.removeAttribute("disabled");
-          else
-            pasteAndGo.setAttribute("disabled", "true");
-        });
-
-        var insertLocation = cxmenu.firstElementChild;
-        while (insertLocation.nextElementSibling &&
-               insertLocation.getAttribute("cmd") != "cmd_paste")
-          insertLocation = insertLocation.nextElementSibling;
-        if (insertLocation) {
-          pasteAndGo = document.createXULElement("menuitem");
-          let label = Services.strings.createBundle("chrome://browser/locale/browser.properties").
-                                   GetStringFromName("pasteAndGo.label");
-          pasteAndGo.setAttribute("label", label);
-          pasteAndGo.setAttribute("anonid", "paste-and-go");
-          pasteAndGo.setAttribute("oncommand",
-              "gURLBar.select(); goDoCommand('cmd_paste'); gURLBar.handleCommand();");
-          cxmenu.insertBefore(pasteAndGo, insertLocation.nextElementSibling);
-        }
-
-        this.popup.addEventListener("popupshowing", () => {
-          this._enableOrDisableOneOffSearches();
-        }, {capture: true, once: true});
-
-        // history dropmarker open state
-        this.popup.addEventListener("popupshowing", () => {
-          this.setAttribute("open", "true");
-        });
-        this.popup.addEventListener("popuphidden", () => {
-          requestAnimationFrame(() => {
-            this.removeAttribute("open");
-          });
-        });
-      ]]></constructor>
+XPCOMUtils.defineLazyModuleGetters(this, {
+  AppConstants: "resource://gre/modules/AppConstants.jsm",
+  Services: "resource://gre/modules/Services.jsm",
+  UrlbarPrefs: "resource:///modules/UrlbarPrefs.jsm",
+});
 
-      <destructor><![CDATA[
-        // Somehow, it's possible for the XBL destructor to fire without the
-        // constructor ever having fired. Fix:
-        if (!this._prefs) {
-          return;
-        }
-        this._prefs.removeObserver("", this);
-        this._prefs = null;
-        Services.prefs.removeObserver("browser.search.suggest.enabled", this);
-        this.inputField.controllers.removeController(this._copyCutController);
-        this.inputField.removeEventListener("paste", this);
-        this.inputField.removeEventListener("mousedown", this);
-        this.inputField.removeEventListener("mousemove", this);
-        this.inputField.removeEventListener("mouseout", this);
-        this.inputField.removeEventListener("overflow", this);
-        this.inputField.removeEventListener("underflow", this);
-        this.inputField.removeEventListener("scrollend", this);
-        window.removeEventListener("resize", this);
-
-        if (this._deferredKeyEventTimeout) {
-          clearTimeout(this._deferredKeyEventTimeout);
-          this._deferredKeyEventTimeout = null;
-        }
-
-        // Null out the one-offs' popup and textbox so that it cleans up its
-        // internal state for both.  Most importantly, it removes the event
-        // listeners that it added to both.
-        this.popup.oneOffSearchButtons.popup = null;
-        this.popup.oneOffSearchButtons.textbox = null;
-      ]]></destructor>
-
-      <field name="scheme" readonly="true">
-        document.getAnonymousElementByAttribute(this, "anonid", "scheme");
-      </field>
-
-      <field name="goButton">
-        document.getAnonymousElementByAttribute(this, "anonid", "urlbar-go-button");
-      </field>
-
-      <field name="_value">""</field>
-      <field name="gotResultForCurrentQuery">false</field>
-
-      <!--
-        This is set around HandleHenter so it can be used in handleCommand.
-        It is also used to track whether we must handle a delayed handleEnter,
-        by checking if it has been cleared.
-      -->
-      <field name="handleEnterInstance">null</field>
-
-      <!--
-        Since we never want scrollbars, we always use the maxResults value.
-      -->
-      <property name="maxRows"
-                onget="return this.popup.maxResults;"/>
-
-      <!--
-        Set by focusAndSelectUrlBar to indicate whether the next focus event was
-        initiated by an explicit user action. See the "focus" handler below.
-      -->
-      <field name="userInitiatedFocus">false</field>
-
-      <!--
-        onBeforeValueGet is called by the base-binding's .value getter.
-        It can return an object with a "value" property, to override the
-        return value of the getter.
-      -->
-      <method name="onBeforeValueGet">
-        <body><![CDATA[
-          return { value: this._value };
-        ]]></body>
-      </method>
-
-      <!--
-        onBeforeValueSet is called by the base-binding's .value setter.
-        It should return the value that the setter should use.
-      -->
-      <method name="onBeforeValueSet">
-        <parameter name="aValue"/>
-        <body><![CDATA[
-          this._value = aValue;
-          var returnValue = aValue;
-          var action = this._parseActionUrl(aValue);
-
-          if (action) {
-            switch (action.type) {
-              case "switchtab": // Fall through.
-              case "remotetab": // Fall through.
-              case "visiturl": {
-                returnValue = action.params.displayUrl;
-                break;
-              }
-              case "keyword": // Fall through.
-              case "searchengine": {
-                returnValue = action.params.input;
-                break;
-              }
-              case "extension": {
-                returnValue = action.params.content;
-                break;
-              }
-            }
-          } else {
-            let originalUrl = ReaderMode.getOriginalUrlObjectForDisplay(aValue);
-            if (originalUrl) {
-              returnValue = originalUrl.displaySpec;
-            }
-          }
-
-          // Set the actiontype only if the user is not overriding actions.
-          if (action && this._pressedNoActionKeys.size == 0) {
-            this.setAttribute("actiontype", action.type);
-          } else {
-            this.removeAttribute("actiontype");
-          }
-          return returnValue;
-        ]]></body>
-      </method>
-
-      <method name="onKeyPress">
-        <parameter name="aEvent"/>
-        <parameter name="aNoDefer"/>
-        <body><![CDATA[
-          switch (aEvent.keyCode) {
-            case KeyEvent.DOM_VK_LEFT:
-            case KeyEvent.DOM_VK_RIGHT:
-            case KeyEvent.DOM_VK_HOME:
-              // Reset the selected index so that nsAutoCompleteController
-              // simply closes the popup without trying to fill anything.
-              this.popup.selectedIndex = -1;
-              break;
-            case KeyEvent.DOM_VK_TAB:
-              this.userSelectionBehavior = "tab";
-              // The user is explicitly making a selection, so the popup
-              // should get accessibility focus.
-              this.popup.richlistbox.suppressMenuItemEvent = false;
-              break;
-            case KeyEvent.DOM_VK_UP:
-            case KeyEvent.DOM_VK_DOWN:
-            case KeyEvent.DOM_VK_PAGE_UP:
-            case KeyEvent.DOM_VK_PAGE_DOWN:
-              if (this.userSelectionBehavior != "tab")
-                this.userSelectionBehavior = "arrow";
-              // The user is explicitly making a selection, so the popup
-              // should get accessibility focus.
-              this.popup.richlistbox.suppressMenuItemEvent = false;
-              break;
-          }
-          if (!this.popup.disableKeyNavigation) {
-            if (!aNoDefer && this._shouldDeferKeyEvent(aEvent)) {
-              this._deferKeyEvent(aEvent, "onKeyPress");
-              return false;
-            }
-            if (this.popup.popupOpen && this.popup.handleKeyPress(aEvent)) {
-              return true;
-            }
-          }
-          return this.handleKeyPress(aEvent);
-        ]]></body>
-      </method>
-
-      <!--
-        Search results arrive asynchronously, which means that keypresses may
-        arrive before results do and therefore not have the effect the user
-        intends.  That's especially likely to happen with the down arrow and
-        enter keys due to the one-off search buttons: if the user very quickly
-        pastes something in the input, presses the down arrow key, and then hits
-        enter, they are probably expecting to visit the first result.  But if
-        there are no results, then pressing down and enter will trigger the
-        first one-off button.  To prevent that undesirable behavior, certain
-        keys are buffered and deferred until more results arrive, at which time
-        they're replayed.
-
-        @param  event
-                The key event that should maybe be deferred.
-        @return True if the event should be deferred, false if not.
-       -->
-      <method name="_shouldDeferKeyEvent">
-        <parameter name="event"/>
-        <body><![CDATA[
-          // If any event has been deferred for this search, then defer all
-          // subsequent events so that the user does not experience any
-          // keypresses out of order.  All events will be replayed when
-          // _deferredKeyEventTimeout fires.
-          if (this._deferredKeyEventQueue.length) {
-            return true;
-          }
-
-          // At this point, no events have been deferred for this search, and we
-          // need to decide whether `event` is the first one that should be.
-
-          if (!this._keyCodesToDefer.has(event.keyCode)) {
-            // Not a key that should trigger deferring.
-            return false;
-          }
-
-          let waitedLongEnough =
-            this._searchStartDate + this._deferredKeyEventTimeoutMs <= Cu.now();
-          if (waitedLongEnough) {
-            // This is a key that we would defer, but enough time has passed
-            // since the start of the search that we don't want to block the
-            // user's keypresses anymore.
-            return false;
-          }
+/**
+ * Applies URL highlighting and other styling to the text in the urlbar input,
+ * depending on the text.
+ */
+class UrlbarValueFormatter {
+  /**
+   * @param {UrlbarInput} urlbarInput
+   */
+  constructor(urlbarInput) {
+    this.urlbarInput = urlbarInput;
+    this.window = this.urlbarInput.window;
+    this.document = this.window.document;
+    this.editor = this.urlbarInput.editor;
+    this.inputField = this.urlbarInput.inputField;
+    this.scheme =
+      this.document.getAnonymousElementByAttribute(this.urlbarInput.textbox, "anonid", "scheme");
 
-          if (event.keyCode == KeyEvent.DOM_VK_TAB && !this.popupOpen) {
-            // The popup is closed and the user pressed the Tab key.  The
-            // focus should move out of the urlbar immediately.
-            return false;
-          }
-
-          return !this._safeToPlayDeferredKeyEvent(event);
-        ]]></body>
-      </method>
-
-      <!--
-        Returns true if the given deferred key event can be played now without
-        possibly surprising the user.  This depends on the state of the popup,
-        its results, and the type of keypress.  Use this method only after
-        determining that the event should be deferred, or after it's already
-        been deferred and you want to know if it can be played now.
-
-        @param  event
-                The key event.
-        @return True if the event can be played, false if not.
-      -->
-      <method name="_safeToPlayDeferredKeyEvent">
-        <parameter name="event"/>
-        <body><![CDATA[
-          if (event.keyCode == KeyEvent.DOM_VK_RETURN) {
-            return this.popup.selectedIndex != 0 ||
-                   this.gotResultForCurrentQuery;
-          }
-
-          if (!this.gotResultForCurrentQuery || !this.popupOpen) {
-            // We're still waiting on the first result, or the popup hasn't
-            // opened yet, so not safe.
-            return false;
-          }
-
-          let maxResultsRemaining =
-            this.popup.maxResults - this.popup.matchCount;
-          if (maxResultsRemaining == 0) {
-            // The popup can't possibly have any more results, so there's no
-            // need to defer any event now.
-            return true;
-          }
-
-          if (event.keyCode == KeyEvent.DOM_VK_DOWN) {
-            // Don't play the event if the last result is selected so that the
-            // user doesn't accidentally arrow down into the one-off buttons
-            // when they didn't mean to.
-            let lastResultSelected =
-              this.popup.selectedIndex + 1 == this.popup.matchCount;
-            return !lastResultSelected;
-          }
-
-          return true;
-        ]]></body>
-      </method>
-
-      <!--
-        Adds a key event to the deferred event queue.
-
-        @param event
-               The key event to defer.
-        @param methodName
-               The name of the method on `this` to call.  It's expected to take
-               two arguments: the event, and a noDefer bool.  If the bool is
-               true, then the event is being replayed and it should not be
-               deferred.
-      -->
-      <method name="_deferKeyEvent">
-        <parameter name="event"/>
-        <parameter name="methodName"/>
-        <body><![CDATA[
-          // Somehow event.defaultPrevented ends up true for deferred events.
-          // autocomplete ignores defaultPrevented events, which means it would
-          // ignore replayed deferred events if we didn't tell it to bypass
-          // defaultPrevented.  That's the purpose of this expando.  If we could
-          // figure out what's setting defaultPrevented and prevent it, then we
-          // could get rid of this.
-          if (event.urlbarDeferred) {
-            throw new Error("Key event already deferred!");
-          }
-          event.urlbarDeferred = true;
-
-          this._deferredKeyEventQueue.push({
-            methodName,
-            event,
-            searchString: this.mController.searchString,
-          });
+    // This is used only as an optimization to avoid removing formatting in
+    // the _remove* format methods when no formatting is actually applied.
+    this._formattingApplied = false;
+  }
 
-          if (!this._deferredKeyEventTimeout) {
-            // Start the timeout that will unconditionally replay all deferred
-            // events when it fires so that, after a certain point, we don't
-            // keep blocking the user's keypresses when nothing else has caused
-            // the events to be replayed.  Do not check whether it's safe to
-            // replay the events because otherwise it may look like we ignored
-            // the user's input.
-            let elapsed = Cu.now() - this._searchStartDate;
-            let remaining = this._deferredKeyEventTimeoutMs - elapsed;
-            this._deferredKeyEventTimeout = setTimeout(() => {
-              this.replayAllDeferredKeyEvents();
-              this._deferredKeyEventTimeout = null;
-            }, Math.max(0, remaining));
-          }
-        ]]></body>
-      </method>
-
-      <!-- The enter key is always deferred, so it's not included here. -->
-      <field name="_keyCodesToDefer">new Set([
-        KeyboardEvent.DOM_VK_RETURN,
-        KeyboardEvent.DOM_VK_DOWN,
-        KeyboardEvent.DOM_VK_TAB,
-      ])</field>
-      <field name="_deferredKeyEventQueue">[]</field>
-      <field name="_deferredKeyEventTimeout">null</field>
-      <field name="_deferredKeyEventTimeoutMs">200</field>
-      <field name="_searchStartDate">0</field>
-
-      <method name="replaySafeDeferredKeyEvents">
-        <body><![CDATA[
-          if (!this._deferredKeyEventQueue.length) {
-            return;
-          }
-          let instance = this._deferredKeyEventQueue[0];
-          if (!this._safeToPlayDeferredKeyEvent(instance.event)) {
-            return;
-          }
-          this._deferredKeyEventQueue.shift();
-          this._replayKeyEventInstance(instance);
-          Services.tm.dispatchToMainThread(() => {
-            this.replaySafeDeferredKeyEvents();
-          });
-        ]]></body>
-      </method>
+  update() {
+    if (!this.editor || !this.editor.rootElement.firstChild.textContent) {
+      return;
+    }
 
-      <!--
-        Unconditionally replays all deferred key events.  This does not check
-        whether it's safe to replay the events; use replaySafeDeferredKeyEvents
-        for that.  Use this method when you must replay all events so that it
-        does not appear that we ignored the user's input.
-      -->
-      <method name="replayAllDeferredKeyEvents">
-        <body><![CDATA[
-          let instance = this._deferredKeyEventQueue.shift();
-          if (!instance) {
-            return;
-          }
-          this._replayKeyEventInstance(instance);
-          Services.tm.dispatchToMainThread(() => {
-            this.replayAllDeferredKeyEvents();
-          });
-        ]]></body>
-      </method>
-
-      <method name="_replayKeyEventInstance">
-        <parameter name="instance"/>
-        <body><![CDATA[
-          // Safety check: handle only if the search string didn't change.
-          if (this.mController.searchString == instance.searchString) {
-            this[instance.methodName](instance.event, true);
-          }
-        ]]></body>
-      </method>
-
-      <field name="_mayTrimURLs">true</field>
-      <method name="trimValue">
-        <parameter name="aURL"/>
-        <body><![CDATA[
-          // This method must not modify the given URL such that calling
-          // nsIURIFixup::createFixupURI with the result will produce a different URI.
-          return this._mayTrimURLs ? trimURL(aURL) : aURL;
-        ]]></body>
-      </method>
-
-      <field name="_formattingEnabled">true</field>
-
-      <!--
-        This is used only as an optimization to avoid removing formatting in
-        the _remove* format methods when no formatting is actually applied.
-      -->
-      <field name="_formattingApplied">false</field>
-
-      <!--
-        This method tries to apply styling to the text in the input, depending
-        on the text.  See the _format* methods.
-      -->
-      <method name="formatValue">
-        <body><![CDATA[
-          if (!this.editor || !this.editor.rootElement.firstChild.textContent) {
-            return;
-          }
+    // Remove the current formatting.
+    this._removeURLFormat();
+    this._removeSearchAliasFormat();
 
-          // Remove the current formatting.
-          this._removeURLFormat();
-          this._removeSearchAliasFormat();
-          this._formattingApplied = false;
-
-          // Apply new formatting.  Formatter methods should return true if they
-          // successfully formatted the value and false if not.  We apply only
-          // one formatter at a time, so we stop at the first successful one.
-          let formatterMethods = [
-            "_formatURL",
-            "_formatSearchAlias",
-          ];
-          this._formattingApplied = formatterMethods.some(m => this[m]());
-        ]]></body>
-      </method>
-
-      <method name="_removeURLFormat">
-        <body><![CDATA[
-          this.scheme.value = "";
-          if (!this._formattingApplied) {
-            return;
-          }
-          let controller = this.editor.selectionController;
-          let strikeOut =
-            controller.getSelection(controller.SELECTION_URLSTRIKEOUT);
-          strikeOut.removeAllRanges();
-          let selection =
-            controller.getSelection(controller.SELECTION_URLSECONDARY);
-          selection.removeAllRanges();
-          this._formatScheme(controller.SELECTION_URLSTRIKEOUT, true);
-          this._formatScheme(controller.SELECTION_URLSECONDARY, true);
-          this.inputField.style.setProperty("--urlbar-scheme-size", "0px");
-        ]]></body>
-      </method>
-
-      <!--
-        If the input value is a URL and the input is not focused, this
-        formatter method highlights the domain, and if mixed content is present,
-        it crosses out the https scheme.  It also ensures that the host is
-        visible (not scrolled out of sight).
-
-        @param  onlyEnsureFormattedHostVisible
-                Pass true to skip formatting and instead only ensure that the
-                host is visible.
-        @return True if formatting was applied and false if not.
-      -->
-      <method name="_formatURL">
-        <parameter name="onlyEnsureFormattedHostVisible"/>
-        <body><![CDATA[
-          if (this.focused) {
-            return false;
-          }
-
-          let textNode = this.editor.rootElement.firstChild;
-          let value = textNode.textContent;
-
-          // Get the URL from the fixup service:
-          let flags = Services.uriFixup.FIXUP_FLAG_FIX_SCHEME_TYPOS |
-                      Services.uriFixup.FIXUP_FLAG_ALLOW_KEYWORD_LOOKUP;
-          let uriInfo;
-          try {
-            uriInfo = Services.uriFixup.getFixupURIInfo(value, flags);
-          } catch (ex) {}
-          // Ignore if we couldn't make a URI out of this, the URI resulted in a search,
-          // or the URI has a non-http(s)/ftp protocol.
-          if (!uriInfo ||
-              !uriInfo.fixedURI ||
-              uriInfo.keywordProviderName ||
-              !["http", "https", "ftp"].includes(uriInfo.fixedURI.scheme)) {
-            return false;
-          }
-
-          // If we trimmed off the http scheme, ensure we stick it back on before
-          // trying to figure out what domain we're accessing, so we don't get
-          // confused by user:pass@host http URLs. We later use
-          // trimmedLength to ensure we don't count the length of a trimmed protocol
-          // when determining which parts of the URL to highlight as "preDomain".
-          let trimmedLength = 0;
-          if (uriInfo.fixedURI.scheme == "http" && !value.startsWith("http://")) {
-            value = "http://" + value;
-            trimmedLength = "http://".length;
-          }
-
-          let matchedURL = value.match(/^(([a-z]+:\/\/)(?:[^\/#?]+@)?)(\S+?)(?::\d+)?\s*(?:[\/#?]|$)/);
-          if (!matchedURL) {
-            return false;
-          }
-
-          let [, preDomain, schemeWSlashes, domain] = matchedURL;
-          // We strip http, so we should not show the scheme box for it.
-          if (!this._mayTrimURLs || schemeWSlashes != "http://") {
-            this.scheme.value = schemeWSlashes;
-            this.inputField.style.setProperty("--urlbar-scheme-size",
-                                              schemeWSlashes.length + "ch");
-          }
-
-          // Used to avoid re-entrance in the requestAnimationFrame callback.
-          let instance = this._formatURLInstance = {};
+    // Apply new formatting.  Formatter methods should return true if they
+    // successfully formatted the value and false if not.  We apply only
+    // one formatter at a time, so we stop at the first successful one.
+    this._formattingApplied =
+      this._formatURL() ||
+      this._formatSearchAlias();
+  }
 
-          // Make sure the host is always visible. Since it is aligned on
-          // the first strong directional character, we set scrollLeft
-          // appropriately to ensure the domain stays visible in case of an
-          // overflow.
-          window.requestAnimationFrame(() => {
-            // Check for re-entrance. On focus change this formatting code is
-            // invoked regardless, thus this should be enough.
-            if (this._formatURLInstance != instance) {
-              return;
-            }
-            let directionality = window.windowUtils.getDirectionFromText(domain);
-            // In the future, for example in bug 525831, we may add a forceRTL
-            // char just after the domain, and in such a case we should not
-            // scroll to the left.
-            if (directionality == window.windowUtils.DIRECTION_RTL &&
-                value[preDomain.length + domain.length] != "\u200E") {
-              this.inputField.scrollLeft = this.inputField.scrollLeftMax;
-            }
-          });
-
-          if (onlyEnsureFormattedHostVisible || !this._formattingEnabled) {
-            return false;
-          }
-
-          let controller = this.editor.selectionController;
-
-          this._formatScheme(controller.SELECTION_URLSECONDARY);
-
-          // Strike out the "https" part if mixed active content is loaded.
-          if (this.getAttribute("pageproxystate") == "valid" &&
-              value.startsWith("https:") &&
-              gBrowser.securityUI.state &
-                Ci.nsIWebProgressListener.STATE_LOADED_MIXED_ACTIVE_CONTENT) {
-            let range = document.createRange();
-            range.setStart(textNode, 0);
-            range.setEnd(textNode, 5);
-            let strikeOut =
-              controller.getSelection(controller.SELECTION_URLSTRIKEOUT);
-            strikeOut.addRange(range);
-            this._formatScheme(controller.SELECTION_URLSTRIKEOUT);
-          }
+  ensureFormattedHostVisible(urlMetaData) {
+    // Used to avoid re-entrance in the requestAnimationFrame callback.
+    let instance = this._formatURLInstance = {};
 
-          let baseDomain = domain;
-          let subDomain = "";
-          try {
-            baseDomain = Services.eTLD.getBaseDomainFromHost(uriInfo.fixedURI.host);
-            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 selection =
-            controller.getSelection(controller.SELECTION_URLSECONDARY);
-
-          let rangeLength = preDomain.length + subDomain.length - trimmedLength;
-          if (rangeLength) {
-            let range = document.createRange();
-            range.setStart(textNode, 0);
-            range.setEnd(textNode, rangeLength);
-            selection.addRange(range);
-          }
-
-          let startRest = preDomain.length + domain.length - trimmedLength;
-          if (startRest < value.length - trimmedLength) {
-            let range = document.createRange();
-            range.setStart(textNode, startRest);
-            range.setEnd(textNode, value.length - trimmedLength);
-            selection.addRange(range);
-          }
-
-          return true;
-        ]]></body>
-      </method>
-
-      <method name="_formatScheme">
-        <parameter name="selectionType"/>
-        <parameter name="clear"/>
-        <body><![CDATA[
-          let editor = this.scheme.editor;
-          let controller = editor.selectionController;
-          let textNode = editor.rootElement.firstChild;
-          let selection = controller.getSelection(selectionType);
-          if (clear) {
-            selection.removeAllRanges();
-          } else {
-            let r = document.createRange();
-            r.setStart(textNode, 0);
-            r.setEnd(textNode, textNode.textContent.length);
-            selection.addRange(r);
-          }
-        ]]></body>
-      </method>
+    // Make sure the host is always visible. Since it is aligned on
+    // the first strong directional character, we set scrollLeft
+    // appropriately to ensure the domain stays visible in case of an
+    // overflow.
+    this.window.requestAnimationFrame(() => {
+      // Check for re-entrance. On focus change this formatting code is
+      // invoked regardless, thus this should be enough.
+      if (this._formatURLInstance != instance) {
+        return;
+      }
 
-      <method name="_removeSearchAliasFormat">
-        <body><![CDATA[
-          if (!this._formattingApplied) {
-            return;
-          }
-          let selection = this.editor.selectionController.getSelection(
-            Ci.nsISelectionController.SELECTION_FIND
-          );
-          selection.removeAllRanges();
-        ]]></body>
-      </method>
-
-      <!--
-        If the input value starts with a search alias, this formatter method
-        highlights it.
-
-        @return True if formatting was applied and false if not.
-      -->
-      <method name="_formatSearchAlias">
-        <body><![CDATA[
-          if (!this._formattingEnabled) {
-            return false;
-          }
-
-          // There can only be an alias to highlight if the heuristic result is
-          // an alias searchengine result and it's either currently selected or
-          // was selected when the popup was closed.  We also need to check
-          // whether a one-off search button is selected because in that case
-          // there won't be a selection but the alias should not be highlighted.
-          let heuristicItem = this.popup.richlistbox.children[0] || null;
-          let alias =
-            (this.popup.selectedIndex == 0 ||
-             (this.popup.selectedIndex < 0 &&
-              this.popup._previousSelectedIndex == 0)) &&
-            !this.popup.oneOffSearchButtons.selectedButton &&
-            heuristicItem &&
-            heuristicItem.getAttribute("actiontype") == "searchengine" &&
-            this._parseActionUrl(heuristicItem.getAttribute("url")).params.alias;
-          if (!alias) {
-            return false;
-          }
-
-          let textNode = this.editor.rootElement.firstChild;
-          let value = textNode.textContent;
-
-          let index = value.indexOf(alias);
-          if (index < 0) {
-            return false;
-          }
-
-          // We abuse the SELECTION_FIND selection type to do our highlighting.
-          // It's the only type that works with Selection.setColors().
-          let selection = this.editor.selectionController.getSelection(
-            Ci.nsISelectionController.SELECTION_FIND
-          );
-
-          let range = document.createRange();
-          range.setStart(textNode, index);
-          range.setEnd(textNode, index + alias.length);
-          selection.addRange(range);
-
-          let fg = "#2362d7";
-          let bg = "#d2e6fd";
-
-          // Selection.setColors() will swap the given foreground and background
-          // colors if it detects that the contrast between the background
-          // color and the frame color is too low.  Normally we don't want that
-          // to happen; we want it to use our colors as given (even if setColors
-          // thinks the contrast is too low).  But it's a nice feature for non-
-          // default themes, where the contrast between our background color and
-          // the input's frame color might actually be too low.  We can
-          // (hackily) force setColors to use our colors as given by passing
-          // them as the alternate colors.  Otherwise, allow setColors to swap
-          // them, which we can do by passing "currentColor".  See
-          // nsTextPaintStyle::GetHighlightColors for details.
-          if (this.querySelector(":-moz-lwtheme") ||
-              (AppConstants.platform == "win" &&
-               window.matchMedia("(-moz-windows-default-theme: 0)").matches)) {
-            // non-default theme(s)
-            selection.setColors(fg, bg, "currentColor", "currentColor");
-          } else {
-            // default themes
-            selection.setColors(fg, bg, fg, bg);
-          }
-
-          return true;
-        ]]></body>
-      </method>
-
-      <method name="handleRevert">
-        <body><![CDATA[
-          var isScrolling = this.popupOpen;
-
-          gBrowser.userTypedValue = null;
-
-          // don't revert to last valid url unless page is NOT loading
-          // and user is NOT key-scrolling through autocomplete list
-          if (!XULBrowserWindow.isBusy && !isScrolling) {
-            URLBarSetURI(null, true);
-
-            // If the value isn't empty and the urlbar has focus, select the value.
-            if (this.value && this.hasAttribute("focused"))
-              this.select();
-          }
-
-          // tell widget to revert to last typed text only if the user
-          // was scrolling when they hit escape
-          return !isScrolling;
-        ]]></body>
-      </method>
-
-      <method name="_whereToOpen">
-        <parameter name="event"/>
-        <body><![CDATA[
-          let isMouseEvent = event instanceof MouseEvent;
-          let reuseEmpty = !isMouseEvent;
-          let where = undefined;
-          if (!isMouseEvent && event && event.altKey) {
-            // We support using 'alt' to open in a tab, because ctrl/shift
-            // might be used for canonizing URLs:
-            where = event.shiftKey ? "tabshifted" : "tab";
-          } else if (!isMouseEvent && this._ctrlCanonizesURLs && event && event.ctrlKey) {
-            // If we're allowing canonization, and this is a key event with ctrl
-            // pressed, open in current tab to allow ctrl-enter to canonize URL.
-            where = "current";
-          } else {
-            where = whereToOpenLink(event, false, false);
-          }
-          if (this.openInTab) {
-            if (where == "current") {
-              where = "tab";
-            } else if (where == "tab") {
-              where = "current";
-            }
-            reuseEmpty = true;
-          }
-          if (where == "tab" && reuseEmpty && isTabEmpty(gBrowser.selectedTab)) {
-            where = "current";
-          }
-          return where;
-        ]]></body>
-      </method>
-
-      <!--
-        This is ultimately called by the autocomplete controller as the result
-        of handleEnter when the Return key is pressed in the textbox.  Since
-        onPopupClick also calls handleEnter, this is also called as a result in
-        that case.
-
-        @param event
-               The event that triggered the command.
-        @param openUILinkWhere
-               Optional.  The "where" to pass to openTrustedLinkIn.  This method
-               computes the appropriate "where" given the event, but you can
-               use this to override it.
-        @param openUILinkParams
-               Optional.  The parameters to pass to openTrustedLinkIn.  As with
-               "where", this method computes the appropriate parameters, but
-               any parameters you supply here will override those.
-      -->
-      <method name="handleCommand">
-        <parameter name="event"/>
-        <parameter name="openUILinkWhere"/>
-        <parameter name="openUILinkParams"/>
-        <parameter name="triggeringPrincipal"/>
-        <body><![CDATA[
-          let isMouseEvent = event instanceof MouseEvent;
-          if (isMouseEvent && event.button == 2) {
-            // Do nothing for right clicks.
-            return;
-          }
-
-          BrowserUsageTelemetry.recordUrlbarSelectedResultMethod(
-            event, this.userSelectionBehavior);
-
-          // Determine whether to use the selected one-off search button.  In
-          // one-off search buttons parlance, "selected" means that the button
-          // has been navigated to via the keyboard.  So we want to use it if
-          // the triggering event is not a mouse click -- i.e., it's a Return
-          // key -- or if the one-off was mouse-clicked.
-          let selectedOneOff = this.popup.oneOffSearchButtons.selectedButton;
-          if (selectedOneOff &&
-              isMouseEvent &&
-              event.originalTarget != selectedOneOff) {
-            selectedOneOff = null;
-          }
-
-          // Do the command of the selected one-off if it's not an engine.
-          if (selectedOneOff && !selectedOneOff.engine) {
-            selectedOneOff.doCommand();
-            return;
-          }
-
-          let where = openUILinkWhere || this._whereToOpen(event);
+      // In the future, for example in bug 525831, we may add a forceRTL
+      // char just after the domain, and in such a case we should not
+      // scroll to the left.
+      urlMetaData = urlMetaData || this._getUrlMetaData();
+      if (!urlMetaData) {
+        return;
+      }
+      let { url, preDomain, domain } = urlMetaData;
+      let directionality = this.window.windowUtils.getDirectionFromText(domain);
+      if (directionality == this.window.windowUtils.DIRECTION_RTL &&
+          url[preDomain.length + domain.length] != "\u200E") {
+        this.inputField.scrollLeft = this.inputField.scrollLeftMax;
+      }
+    });
+  }
 
-          let url = this.value;
-          if (!url) {
-            return;
-          }
-
-          let mayInheritPrincipal = false;
-          let postData = null;
-          let browser = gBrowser.selectedBrowser;
-          let action = this._parseActionUrl(url);
+  _getUrlMetaData() {
+    if (this.urlbarInput.focused) {
+      return null;
+    }
 
-          if (selectedOneOff && selectedOneOff.engine) {
-            // If there's a selected one-off button then load a search using
-            // the one-off's engine.
-            [url, postData] =
-              this._parseAndRecordSearchEngineLoad(selectedOneOff.engine,
-                                                   this.oneOffSearchQuery,
-                                                   event, where,
-                                                   openUILinkParams);
-          } else if (action) {
-            switch (action.type) {
-              case "visiturl":
-                // Unifiedcomplete uses fixupURI to tell if something is a visit
-                // or a search, and passes out the fixedURI as the url param.
-                // By using that uri we would end up passing a different string
-                // to the docshell that may run a different not-found heuristic.
-                // For example, "mozilla/run" would be fixed by unifiedcomplete
-                // to "http://mozilla/run". The docshell, once it can't resolve
-                // mozilla, would note the string has a scheme, and try to load
-                // http://mozilla.com/run instead of searching "mozilla/run".
-                // So, if we have the original input at hand, we pass it through
-                // and let the docshell handle it.
-                if (action.params.input) {
-                  url = action.params.input;
-                  break;
-                }
-                url = action.params.url;
-                break;
-              case "remotetab":
-                url = action.params.url;
-                break;
-              case "keyword":
-                if (action.params.postData) {
-                  postData = getPostDataStream(action.params.postData);
-                }
-                mayInheritPrincipal = true;
-                url = action.params.url;
-                break;
-              case "switchtab":
-                url = action.params.url;
-                if (this.hasAttribute("actiontype")) {
-                  this.handleRevert();
-                  let prevTab = gBrowser.selectedTab;
-                  let loadOpts = {
-                    adoptIntoActiveWindow: this._adoptIntoActiveWindow,
-                  };
-
-                  if (switchToTabHavingURI(url, false, loadOpts) &&
-                      isTabEmpty(prevTab)) {
-                    gBrowser.removeTab(prevTab);
-                  }
-                  return;
-                }
+    let url = this.inputField.value;
 
-                // Once we get here, we got a switchtab action but the user
-                // bypassed it by pressing shift/meta/ctrl. Those modifiers
-                // might otherwise affect where we open - we always want to
-                // open in the current tab.
-                where = "current";
-                break;
-              case "searchengine":
-                if (selectedOneOff && selectedOneOff.engine) {
-                  // Replace the engine with the selected one-off engine.
-                  action.params.engineName = selectedOneOff.engine.name;
-                }
-                const actionDetails = {
-                  isSuggestion: !!action.params.searchSuggestion,
-                  isAlias: !!action.params.alias,
-                };
-                [url, postData] = this._parseAndRecordSearchEngineLoad(
-                  action.params.engineName,
-                  action.params.searchSuggestion || action.params.searchQuery,
-                  event,
-                  where,
-                  openUILinkParams,
-                  actionDetails
-                );
-                break;
-              case "extension":
-                this.handleRevert();
-                // Give the extension control of handling the command.
-                let searchString = action.params.content;
-                let keyword = action.params.keyword;
-                this.ExtensionSearchHandler.handleInputEntered(keyword, searchString, where);
-                return;
-            }
-          } else {
-            // This is a fallback for add-ons and old testing code that directly
-            // set value and try to confirm it. UnifiedComplete should always
-            // resolve to a valid url.
-            try {
-              url = url.trim();
-              new URL(url);
-            } catch (ex) {
-              let lastLocationChange = browser.lastLocationChange;
-              getShortcutOrURIAndPostData(url).then(data => {
-                if (where != "current" ||
-                    browser.lastLocationChange == lastLocationChange) {
-                  this._loadURL(data.url, browser, data.postData, where,
-                                openUILinkParams, data.mayInheritPrincipal,
-                                triggeringPrincipal);
-                }
-              });
-              return;
-            }
-          }
-
-          this._loadURL(url, browser, postData, where, openUILinkParams,
-                        mayInheritPrincipal, triggeringPrincipal);
-        ]]></body>
-      </method>
-
-      <property name="oneOffSearchQuery">
-        <getter><![CDATA[
-          // If the user has selected a search suggestion, chances are they
-          // want to use the one off search engine to search for that suggestion,
-          // not the string that they manually entered into the location bar.
-          let action = this._parseActionUrl(this.value);
-          if (action && action.type == "searchengine") {
-            return action.params.input;
-          }
-          // this.textValue may be an autofilled string.  Search only with the
-          // portion that the user typed, if any, by preferring the autocomplete
-          // controller's searchString (including handleEnterInstance.searchString).
-          return this.handleEnterSearchString ||
-                 this.mController.searchString ||
-                 this.textValue;
-        ]]></getter>
-      </property>
-
-      <method name="_loadURL">
-        <parameter name="url"/>
-        <parameter name="browser"/>
-        <parameter name="postData"/>
-        <parameter name="openUILinkWhere"/>
-        <parameter name="openUILinkParams"/>
-        <parameter name="mayInheritPrincipal"/>
-        <parameter name="triggeringPrincipal"/>
-        <body><![CDATA[
-          this.value = url;
-          browser.userTypedValue = url;
-          if (gInitialPages.includes(url)) {
-            browser.initialPageLoadedFromURLBar = url;
-          }
-          try {
-            addToUrlbarHistory(url);
-          } catch (ex) {
-            // Things may go wrong when adding url to session history,
-            // but don't let that interfere with the loading of the url.
-            Cu.reportError(ex);
-          }
-
-          let params = {
-            postData,
-            allowThirdPartyFixup: true,
-            triggeringPrincipal,
-          };
-          if (openUILinkWhere == "current") {
-            params.targetBrowser = browser;
-            params.indicateErrorPageLoad = true;
-            params.allowPinnedTabHostChange = true;
-            params.allowInheritPrincipal = mayInheritPrincipal;
-            params.allowPopups = url.startsWith("javascript:");
-          } else {
-            params.initiatingDoc = document;
-          }
-
-          if (openUILinkParams) {
-            for (let key in openUILinkParams) {
-              params[key] = openUILinkParams[key];
-            }
-          }
-
-          // Focus the content area before triggering loads, since if the load
-          // occurs in a new tab, we want focus to be restored to the content
-          // area when the current tab is re-selected.
-          browser.focus();
-
-          if (openUILinkWhere != "current") {
-            this.handleRevert();
-          }
-
-          try {
-            openTrustedLinkIn(url, openUILinkWhere, params);
-          } catch (ex) {
-            // This load can throw an exception in certain cases, which means
-            // we'll want to replace the URL with the loaded URL:
-            if (ex.result != Cr.NS_ERROR_LOAD_SHOWED_ERRORPAGE) {
-              this.handleRevert();
-            }
-          }
+    // Get the URL from the fixup service:
+    let flags = Services.uriFixup.FIXUP_FLAG_FIX_SCHEME_TYPOS |
+                Services.uriFixup.FIXUP_FLAG_ALLOW_KEYWORD_LOOKUP;
+    let uriInfo;
+    try {
+      uriInfo = Services.uriFixup.getFixupURIInfo(url, flags);
+    } catch (ex) {}
+    // Ignore if we couldn't make a URI out of this, the URI resulted in a search,
+    // or the URI has a non-http(s)/ftp protocol.
+    if (!uriInfo ||
+        !uriInfo.fixedURI ||
+        uriInfo.keywordProviderName ||
+        !["http", "https", "ftp"].includes(uriInfo.fixedURI.scheme)) {
+      return null;
+    }
 
-          // Ensure the start of the URL is visible for usability reasons.
-          this.selectionStart = this.selectionEnd = 0;
-        ]]></body>
-      </method>
-
-      <method name="_parseAndRecordSearchEngineLoad">
-        <parameter name="engineOrEngineName"/>
-        <parameter name="query"/>
-        <parameter name="event"/>
-        <parameter name="openUILinkWhere"/>
-        <parameter name="openUILinkParams"/>
-        <parameter name="searchActionDetails"/>
-        <body><![CDATA[
-          let engine =
-            typeof(engineOrEngineName) == "string" ?
-              Services.search.getEngineByName(engineOrEngineName) :
-              engineOrEngineName;
-          let isOneOff = this.popup.oneOffSearchButtons
-              .maybeRecordTelemetry(event, openUILinkWhere, openUILinkParams);
-          // Infer the type of the event which triggered the search.
-          let eventType = "unknown";
-          if (event instanceof KeyboardEvent) {
-            eventType = "key";
-          } else if (event instanceof MouseEvent) {
-            eventType = "mouse";
-          }
-          // Augment the search action details object.
-          let details = searchActionDetails || {};
-          details.isOneOff = isOneOff;
-          details.type = eventType;
-
-          BrowserSearch.recordSearchInTelemetry(engine, "urlbar", details);
-          let submission = engine.getSubmission(query, null, "keyword");
-          return [submission.uri.spec, submission.postData];
-        ]]></body>
-      </method>
-
-      <method name="maybeCanonizeURL">
-        <parameter name="aTriggeringEvent"/>
-        <parameter name="aUrl"/>
-        <body><![CDATA[
-          // 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) ||
-              !this._ctrlCanonizesURLs ||
-              !(aTriggeringEvent instanceof KeyboardEvent) ||
-              !aTriggeringEvent.ctrlKey) {
-            return;
-          }
-
-          let suffix = Services.prefs.getCharPref("browser.fixup.alternate.suffix", ".com/");
-          if (!suffix.endsWith("/")) {
-            suffix += "/";
-          }
-
-          // trim leading/trailing spaces (bug 233205)
-          let url = 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 = url.indexOf("/");
-          if (firstSlash >= 0) {
-            url = url.substring(0, firstSlash) + suffix +
-                  url.substring(firstSlash + 1);
-          } else {
-            url = url + suffix;
-          }
-
-          this.popup.overrideValue = "http://www." + url;
-        ]]></body>
-      </method>
-
-      <method name="_initURLTooltip">
-        <body><![CDATA[
-          if (this.focused || !this._inOverflow)
-            return;
-          this.inputField.setAttribute("title", this.value);
-        ]]></body>
-      </method>
-
-      <method name="_hideURLTooltip">
-        <body><![CDATA[
-          this.inputField.removeAttribute("title");
-        ]]></body>
-      </method>
+    // If we trimmed off the http scheme, ensure we stick it back on before
+    // trying to figure out what domain we're accessing, so we don't get
+    // confused by user:pass@host http URLs. We later use
+    // trimmedLength to ensure we don't count the length of a trimmed protocol
+    // when determining which parts of the URL to highlight as "preDomain".
+    let trimmedLength = 0;
+    if (uriInfo.fixedURI.scheme == "http" && !url.startsWith("http://")) {
+      url = "http://" + url;
+      trimmedLength = "http://".length;
+    }
 
-      <!-- Returns:
-           null if there's a security issue and we should do nothing.
-           a URL object if there is one that we're OK with loading,
-           a text value otherwise.
-           -->
-      <method name="_getDroppableItem">
-        <parameter name="aEvent"/>
-        <body><![CDATA[
-          let links;
-          try {
-            links = browserDragAndDrop.dropLinks(aEvent);
-          } catch (ex) {
-            // this is possibly a security exception, in which case we should return
-            // null. Always return null because we can't *know* what exception is
-            // being returned.
-            return null;
-          }
-          // The URL bar automatically handles inputs with newline characters,
-          // so we can get away with treating text/x-moz-url flavours as text/plain.
-          if (links.length > 0 && links[0].url) {
-            let triggeringPrincipal = browserDragAndDrop.getTriggeringPrincipal(aEvent);
-            aEvent.preventDefault();
-            let url = links[0].url;
-            let strippedURL = stripUnsafeProtocolOnPaste(url);
-            if (strippedURL != url) {
-              aEvent.stopImmediatePropagation();
-              return null;
-            }
-            let urlObj;
-            try {
-              // If this throws, urlSecurityCheck would also throw, as that's what it
-              // does with things that don't pass the IO service's newURI constructor
-              // without fixup. It's conceivable we may want to relax this check in
-              // the future (so e.g. www.foo.com gets fixed up), but not right now.
-              urlObj = new URL(url);
-              // If we succeed, try to pass security checks. If this works, return the
-              // URL object. If the *security checks* fail, return null.
-              try {
-                urlSecurityCheck(url,
-                                 triggeringPrincipal,
-                                 Ci.nsIScriptSecurityManager.DISALLOW_INHERIT_PRINCIPAL);
-                return urlObj;
-              } catch (ex) {
-                return null;
-              }
-            } catch (ex) {
-              // We couldn't make a URL out of this. Continue on, and return text below.
-            }
-          }
-          return aEvent.dataTransfer.getData("text/unicode");
-        ]]></body>
-      </method>
+    let matchedURL = url.match(/^(([a-z]+:\/\/)(?:[^\/#?]+@)?)(\S+?)(?::\d+)?\s*(?:[\/#?]|$)/);
+    if (!matchedURL) {
+      return null;
+    }
 
-      <method name="onDragOver">
-        <parameter name="aEvent"/>
-        <body><![CDATA[
-          if (!this._getDroppableItem(aEvent)) {
-            aEvent.dataTransfer.dropEffect = "none";
-          }
-        ]]></body>
-      </method>
-
-      <method name="onDrop">
-        <parameter name="aEvent"/>
-        <body><![CDATA[
-          let droppedItem = this._getDroppableItem(aEvent);
-          if (droppedItem) {
-            let triggeringPrincipal = browserDragAndDrop.getTriggeringPrincipal(aEvent);
-            this.value = droppedItem instanceof URL ? droppedItem.href : droppedItem;
-            SetPageProxyState("invalid");
-            this.focus();
-            this.handleCommand(null, undefined, undefined, triggeringPrincipal);
-            // Force not showing the dropped URI immediately.
-            gBrowser.userTypedValue = null;
-            URLBarSetURI(null, true);
-          }
-        ]]></body>
-      </method>
-
-      <method name="makeURIReadable">
-        <parameter name="aURI"/>
-        <body>
-          <![CDATA[
-            // Avoid copying 'about:reader?url=', and always provide the original URI:
-            // Reader mode ensures we call createExposableURI itself.
-            let readerStrippedURI = ReaderMode.getOriginalUrlObjectForDisplay(aURI.displaySpec);
-            if (readerStrippedURI) {
-              aURI = readerStrippedURI;
-            } else {
-              // Only copy exposable URIs
-              try {
-                aURI = Services.uriFixup.createExposableURI(aURI);
-              } catch (ex) {}
-            }
-            return aURI;
-          ]]>
-        </body>
-      </method>
-
-      <method name="_getSelectedValueForClipboard">
-        <body><![CDATA[
-          // Grab the actual input field's value, not our value, which could
-          // include "moz-action:".
-          var inputVal = this.inputField.value;
-          let selection = this.editor.selection;
-          const flags = Ci.nsIDocumentEncoder.OutputPreformatted |
-                        Ci.nsIDocumentEncoder.OutputRaw;
-          let selectedVal = selection.toStringWithFormat("text/plain", flags, 0);
+    let [, preDomain, schemeWSlashes, domain] = matchedURL;
+    return { preDomain, schemeWSlashes, domain, url, uriInfo, trimmedLength };
+  }
 
-          // Handle multiple-range selection as a string for simplicity.
-          if (selection.rangeCount > 1) {
-             return selectedVal;
-          }
-
-          // If the selection doesn't start at the beginning or doesn't span the
-          // full domain or the URL bar is modified or there is no text at all,
-          // nothing else to do here.
-          if (this.selectionStart > 0 || this.valueIsTyped || selectedVal == "")
-            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.includes("/")) {
-            let remainder = inputVal.replace(selectedVal, "");
-            if (remainder != "" && remainder[0] != "/")
-              return selectedVal;
-          }
-
-          // If the value was filled by a search suggestion, just return it.
-          let action = this._parseActionUrl(this.value);
-          if (action && action.type == "searchengine")
-            return selectedVal;
-
-          let uriFixup = Cc["@mozilla.org/docshell/urifixup;1"].getService(Ci.nsIURIFixup);
-
-          let uri;
-          if (this.getAttribute("pageproxystate") == "valid") {
-            uri = gBrowser.currentURI;
-          } else {
-            // We're dealing with an autocompleted value, create a new URI from that.
-            try {
-              uri = uriFixup.createFixupURI(inputVal, Ci.nsIURIFixup.FIXUP_FLAG_NONE);
-            } catch (e) {}
-            if (!uri)
-              return selectedVal;
-          }
-
-          uri = this.makeURIReadable(uri);
-
-          // If the entire URL is selected, just use the actual loaded URI,
-          // unless we want a decoded URI, or it's a data: or javascript: URI,
-          // since those are hard to read when encoded.
-          if (inputVal == selectedVal &&
-              !uri.schemeIs("javascript") && !uri.schemeIs("data") &&
-              !Services.prefs.getBoolPref("browser.urlbar.decodeURLsOnCopy")) {
-            return uri.displaySpec;
-          }
-
-          // Just the beginning of the URL is selected, or we want a decoded
-          // url. First check for a trimmed value.
-          let spec = uri.displaySpec;
-          let trimmedSpec = this.trimValue(spec);
-          if (spec != trimmedSpec) {
-            // Prepend the portion that trimValue removed from the beginning.
-            // This assumes trimValue 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="_copyCutController"><![CDATA[
-        ({
-          urlbar: this,
-          doCommand(aCommand) {
-            var urlbar = this.urlbar;
-            var 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;
-
-              let event = document.createEvent("UIEvents");
-              event.initUIEvent("input", true, false, window, 0);
-              urlbar.dispatchEvent(event);
-
-              SetPageProxyState("invalid");
-            }
+  _removeURLFormat() {
+    this.scheme.value = "";
+    if (!this._formattingApplied) {
+      return;
+    }
+    let controller = this.editor.selectionController;
+    let strikeOut =
+      controller.getSelection(controller.SELECTION_URLSTRIKEOUT);
+    strikeOut.removeAllRanges();
+    let selection =
+      controller.getSelection(controller.SELECTION_URLSECONDARY);
+    selection.removeAllRanges();
+    this._formatScheme(controller.SELECTION_URLSTRIKEOUT, true);
+    this._formatScheme(controller.SELECTION_URLSECONDARY, true);
+    this.inputField.style.setProperty("--urlbar-scheme-size", "0px");
+  }
 
-            Cc["@mozilla.org/widget/clipboardhelper;1"]
-              .getService(Ci.nsIClipboardHelper)
-              .copyString(val);
-          },
-          supportsCommand(aCommand) {
-            switch (aCommand) {
-              case "cmd_copy":
-              case "cmd_cut":
-                return true;
-            }
-            return false;
-          },
-          isCommandEnabled(aCommand) {
-            return this.supportsCommand(aCommand) &&
-                   (aCommand != "cmd_cut" || !this.urlbar.readOnly) &&
-                   this.urlbar.selectionStart < this.urlbar.selectionEnd;
-          },
-          onEvent(aEventName) {},
-        })
-      ]]></field>
+  /**
+   * If the input value is a URL and the input is not focused, this
+   * formatter method highlights the domain, and if mixed content is present,
+   * it crosses out the https scheme.  It also ensures that the host is
+   * visible (not scrolled out of sight).
+   *
+   * @returns {boolean}
+   *   True if formatting was applied and false if not.
+   */
+  _formatURL() {
+    let urlMetaData = this._getUrlMetaData();
+    if (!urlMetaData) {
+      return false;
+    }
 
-      <method name="observe">
-        <parameter name="aSubject"/>
-        <parameter name="aTopic"/>
-        <parameter name="aData"/>
-        <body><![CDATA[
-          if (aTopic == "nsPref:changed") {
-            switch (aData) {
-              case "clickSelectsAll":
-              case "doubleClickSelectsAll":
-                this[aData] = this._prefs.getBoolPref(aData);
-                break;
-              case "autoFill":
-                this.completeDefaultIndex = this._prefs.getBoolPref(aData);
-                break;
-              case "delay":
-                this.timeout = this._prefs.getIntPref(aData);
-                break;
-              case "formatting.enabled":
-                this._formattingEnabled = this._prefs.getBoolPref(aData);
-                break;
-              case "ctrlCanonizesURLs":
-                this._ctrlCanonizesURLs = this._prefs.getBoolPref(aData);
-                break;
-              case "speculativeConnect.enabled":
-                this.speculativeConnectEnabled = this._prefs.getBoolPref(aData);
-                break;
-              case "openintab":
-                this.openInTab = this._prefs.getBoolPref(aData);
-                break;
-              case "browser.search.suggest.enabled":
-                this.browserSearchSuggestEnabled = Services.prefs.getBoolPref(aData);
-                break;
-              case "suggest.searches":
-                this.urlbarSearchSuggestEnabled = this._prefs.getBoolPref(aData);
-              case "userMadeSearchSuggestionsChoice":
-                // Mirror the value for future use, see the comment in the
-                // binding's constructor.
-                this._prefs.setBoolPref("searchSuggestionsChoice",
-                  this.urlbarSearchSuggestEnabled);
-                // Clear the cached value to allow changing conditions in tests.
-                delete this._whichSearchSuggestionsNotification;
-                break;
-              case "trimURLs":
-                this._mayTrimURLs = this._prefs.getBoolPref(aData);
-                break;
-              case "oneOffSearches":
-                this._enableOrDisableOneOffSearches();
-                break;
-              case "maxRichResults":
-                this.popup.maxResults = this._prefs.getIntPref(aData);
-                break;
-              case "switchTabs.adoptIntoActiveWindow":
-                this._adoptIntoActiveWindow =
-                  this._prefs.getBoolPref("switchTabs.adoptIntoActiveWindow");
-                break;
-            }
-          }
-        ]]></body>
-      </method>
-
-      <method name="_enableOrDisableOneOffSearches">
-        <body><![CDATA[
-          this.popup.oneOffSearchesEnabled =
-            this._prefs.getBoolPref("oneOffSearches");
-        ]]></body>
-      </method>
-
-      <method name="handleEvent">
-        <parameter name="aEvent"/>
-        <body><![CDATA[
-          switch (aEvent.type) {
-            case "paste":
-              let originalPasteData = aEvent.clipboardData.getData("text/plain");
-              if (!originalPasteData) {
-                return;
-              }
-
-              let oldValue = this.inputField.value;
-              let oldStart = oldValue.substring(0, this.inputField.selectionStart);
-              // If there is already non-whitespace content in the URL bar
-              // preceding the pasted content, it's not necessary to check
-              // protocols used by the pasted content:
-              if (oldStart.trim()) {
-                return;
-              }
-              let oldEnd = oldValue.substring(this.inputField.selectionEnd);
-
-              let pasteData = stripUnsafeProtocolOnPaste(originalPasteData);
-              if (originalPasteData != pasteData) {
-                // Unfortunately we're not allowed to set the bits being pasted
-                // so cancel this event:
-                aEvent.preventDefault();
-                aEvent.stopImmediatePropagation();
+    let { url, uriInfo, preDomain, schemeWSlashes, domain, trimmedLength } = urlMetaData;
+    // We strip http, so we should not show the scheme box for it.
+    if (!UrlbarPrefs.get("trimURLs") || schemeWSlashes != "http://") {
+      this.scheme.value = schemeWSlashes;
+      this.inputField.style.setProperty("--urlbar-scheme-size",
+                                        schemeWSlashes.length + "ch");
+    }
 
-                this.inputField.value = oldStart + pasteData + oldEnd;
-                // Fix up cursor/selection:
-                let newCursorPos = oldStart.length + pasteData.length;
-                this.inputField.selectionStart = newCursorPos;
-                this.inputField.selectionEnd = newCursorPos;
-              }
-              break;
-            case "mousedown":
-              if (this.doubleClickSelectsAll &&
-                  aEvent.button == 0 && aEvent.detail == 2) {
-                this.editor.selectAll();
-                aEvent.preventDefault();
-              }
-              break;
-            case "mousemove":
-              this._initURLTooltip();
-              break;
-            case "mouseout":
-              this._hideURLTooltip();
-              break;
-            case "overflow": {
-              const targetIsPlaceholder =
-                !aEvent.originalTarget.classList.contains("anonymous-div");
-              // We only care about the non-placeholder text.
-              // This shouldn't be needed, see bug 1487036.
-              if (targetIsPlaceholder) {
-                break;
-              }
-              this._inOverflow = true;
-              this.updateTextOverflow();
-              break;
-            }
-            case "underflow": {
-              const targetIsPlaceholder =
-                !aEvent.originalTarget.classList.contains("anonymous-div");
-              // We only care about the non-placeholder text.
-              // This shouldn't be needed, see bug 1487036.
-              if (targetIsPlaceholder) {
-                break;
-              }
-              this._inOverflow = false;
-              this.updateTextOverflow();
-              this._hideURLTooltip();
-              break;
-            }
-            case "scrollend":
-              this.updateTextOverflow();
-              break;
-            case "TabSelect":
-              // The autocomplete controller uses heuristic on some internal caches
-              // to handle cases like backspace, autofill or repeated searches.
-              // Ensure to clear those internal caches when switching tabs.
-              this.controller.resetInternalState();
-              break;
-            case "resize":
-              if (aEvent.target == window) {
-                // Close the popup since it would be wrongly sized, we'll
-                // recalculate a proper size on reopening. For example, this may
-                // happen when using special OS resize functions like Win+Arrow.
-                this.closePopup();
+    this.ensureFormattedHostVisible(urlMetaData);
 
-                // Make sure the host remains visible in the input field (via
-                // _formatURL) when the window is resized.  We don't want to
-                // hurt resize performance though, so do this only after resize
-                // events have stopped and a small timeout has elapsed.
-                if (this._resizeThrottleTimeout) {
-                  clearTimeout(this._resizeThrottleTimeout);
-                }
-                this._resizeThrottleTimeout = setTimeout(() => {
-                  this._resizeThrottleTimeout = null;
-                  this._formatURL(true);
-                }, 100);
-              }
-              break;
-          }
-        ]]></body>
-      </method>
+    if (!UrlbarPrefs.get("formatting.enabled")) {
+      return false;
+    }
 
-      <method name="updateTextOverflow">
-        <body><![CDATA[
-          if (this._inOverflow) {
-            window.promiseDocumentFlushed(() => {
-              // Check overflow again to ensure it didn't change in the meanwhile.
-              let input = this.inputField;
-              if (input && this._inOverflow) {
-                let side = input.scrollLeft &&
-                           input.scrollLeft == input.scrollLeftMax ? "start" : "end";
-                this.setAttribute("textoverflow", side);
-              }
-            });
-          } else {
-            this.removeAttribute("textoverflow");
-          }
-        ]]></body>
-      </method>
-
-      <!--
-        onBeforeTextValueGet is called by the base-binding's .textValue getter.
-        It should return the value that the getter should use.
-      -->
-      <method name="onBeforeTextValueGet">
-        <body><![CDATA[
-          return { value: this.inputField.value };
-        ]]></body>
-      </method>
-
-      <!--
-        onBeforeTextValueSet is called by the base-binding's .textValue setter.
-        It should return the value that the setter should use.
-      -->
-      <method name="onBeforeTextValueSet">
-        <parameter name="aValue"/>
-        <body><![CDATA[
-          let val = aValue;
-          let uri;
-          try {
-            uri = makeURI(val);
-          } catch (ex) {}
-
-          if (uri) {
-            // Do not touch moz-action URIs at all.  They depend on being
-            // properly encoded and decoded and will break if decoded
-            // unexpectedly.
-            if (!this._parseActionUrl(val)) {
-              val = losslessDecodeURI(uri);
-            }
-          }
-
-          return val;
-        ]]></body>
-      </method>
+    let controller = this.editor.selectionController;
 
-      <method name="_parseActionUrl">
-        <parameter name="aUrl"/>
-        <body><![CDATA[
-          const MOZ_ACTION_REGEX = /^moz-action:([^,]+),(.*)$/;
-          if (!MOZ_ACTION_REGEX.test(aUrl))
-            return null;
-
-          // URL is in the format moz-action:ACTION,PARAMS
-          // Where PARAMS is a JSON encoded object.
-          let [, type, params] = aUrl.match(MOZ_ACTION_REGEX);
-
-          let action = {
-            type,
-          };
-
-          action.params = JSON.parse(params);
-          for (let key in action.params) {
-            action.params[key] = decodeURIComponent(action.params[key]);
-          }
+    this._formatScheme(controller.SELECTION_URLSECONDARY);
 
-          if ("url" in action.params) {
-            let uri;
-            try {
-              uri = makeURI(action.params.url);
-              action.params.displayUrl = losslessDecodeURI(uri);
-            } catch (e) {
-              action.params.displayUrl = action.params.url;
-            }
-          }
-
-          return action;
-        ]]></body>
-      </method>
-
-      <property name="_noActionKeys" readonly="true">
-        <getter><![CDATA[
-          if (!this.__noActionKeys) {
-            this.__noActionKeys = new Set([
-              KeyEvent.DOM_VK_ALT,
-              KeyEvent.DOM_VK_SHIFT,
-            ]);
-            let modifier = AppConstants.platform == "macosx" ?
-                           KeyEvent.DOM_VK_META :
-                           KeyEvent.DOM_VK_CONTROL;
-            this.__noActionKeys.add(modifier);
-          }
-          return this.__noActionKeys;
-        ]]></getter>
-      </property>
-
-      <field name="_pressedNoActionKeys"><![CDATA[
-        new Set()
-      ]]></field>
+    let textNode = this.editor.rootElement.firstChild;
 
-      <method name="_clearNoActions">
-        <parameter name="aURL"/>
-        <body><![CDATA[
-          this._pressedNoActionKeys.clear();
-          this.popup.removeAttribute("noactions");
-          let action = this._parseActionUrl(this._value);
-          if (action)
-            this.setAttribute("actiontype", action.type);
-        ]]></body>
-      </method>
-
-      <method name="onInput">
-        <parameter name="aEvent"/>
-        <body><![CDATA[
-          if (!this.mIgnoreInput && this.mController.input == this) {
-            this._value = this.inputField.value;
-            gBrowser.userTypedValue = this.value;
-            this.valueIsTyped = true;
-            if (this.inputField.value) {
-              this.setAttribute("usertyping", "true");
-            } else {
-              this.removeAttribute("usertyping");
-            }
-            // If the popup already had accessibility focus, bring it back to
-            // the input, since the user is editing.
-            if (!this.popup.richlistbox.suppressMenuItemEvent &&
-                this.popup.richlistbox.currentItem) {
-              this.popup.richlistbox.currentItem._fireEvent("DOMMenuItemInactive");
-            }
-            // The user is typing, so don't give accessibility focus to the
-            // popup, even if an item gets automatically selected.
-            this.popup.richlistbox.suppressMenuItemEvent = true;
-            // Only wait for a result when we are sure to get one.  In some
-            // cases, like when pasting the same exact text, we may not fire
-            // a new search and we won't get a result.
-            this._onInputHandledText = this.mController.handleText();
-            if (this._onInputHandledText) {
-              this.gotResultForCurrentQuery = false;
-              this._searchStartDate = Cu.now();
-              this._deferredKeyEventQueue = [];
-              if (this._deferredKeyEventTimeout) {
-                clearTimeout(this._deferredKeyEventTimeout);
-                this._deferredKeyEventTimeout = null;
-              }
-            }
-          }
-          this.resetActionType();
-        ]]></body>
-      </method>
-
-      <method name="handleEnter">
-        <parameter name="event"/>
-        <parameter name="noDefer"/>
-        <body><![CDATA[
-          // We need to ensure we're using a selected autocomplete result.
-          // A result should automatically be selected by default,
-          // however autocomplete is async and therefore we may not
-          // have a result set relating to the current input yet. If that
-          // happens, we need to mark that when the first result does get added,
-          // it needs to be handled as if enter was pressed with that first
-          // result selected.
-          // If anything other than the default (first) result is selected, then
-          // it must have been manually selected by the human. We let this
-          // explicit choice be used, even if it may be related to a previous
-          // input.
-          // However, if the default result is automatically selected, we
-          // ensure that it corresponds to the current input.
-
-          // Store the current search string so it can be used in handleCommand,
-          // which will be called as a result of mController.handleEnter().
-          this.handleEnterSearchString = this.mController.searchString;
-
-          if (!noDefer && this._shouldDeferKeyEvent(event)) {
-            // Defer the event until the first non-heuristic result comes in.
-            this._deferKeyEvent(event, "handleEnter");
-            return false;
-          }
-
-          let canonizeValue = this.value;
-          if (event.ctrlKey) {
-            let action = this._parseActionUrl(canonizeValue);
-            if (action && "searchSuggestion" in action.params) {
-              canonizeValue = action.params.searchSuggestion;
-            } else if (this.popup.selectedIndex === 0 &&
-                       this.mController.getStyleAt(0).includes("autofill")) {
-              canonizeValue = this.handleEnterSearchString;
-            }
-          }
-          this.maybeCanonizeURL(event, canonizeValue);
-          let handled = this.mController.handleEnter(false, event);
-          this.handleEnterSearchString = null;
-          this.popup.overrideValue = null;
-          return handled;
-        ]]></body>
-      </method>
-
-      <method name="handleDelete">
-        <body><![CDATA[
-          // If the heuristic result is selected, then the autocomplete
-          // controller's handleDelete implementation will remove it, which is
-          // not what we want.  So in that case, call handleText so it acts as
-          // a backspace on the text value instead of removing the result.
-          if (this.popup.selectedIndex == 0 &&
-              this.popup._isFirstResultHeuristic) {
-            this.mController.handleText();
-            return false;
-          }
-          return this.mController.handleDelete();
-        ]]></body>
-      </method>
-
-      <property name="_userMadeSearchSuggestionsChoice" readonly="true">
-        <getter><![CDATA[
-          return this._prefs.getBoolPref("userMadeSearchSuggestionsChoice") ||
-                 this._defaultPrefs.getBoolPref("suggest.searches") != this._prefs.getBoolPref("suggest.searches");
-        ]]></getter>
-      </property>
-
-      <property name="whichSearchSuggestionsNotification" readonly="true">
-        <getter><![CDATA[
-          // Once we return "none" once, we'll always return "none".
-          // If available, use the cached value, rather than running all of the
-          // checks again at every locationbar focus.
-          if (this._whichSearchSuggestionsNotification) {
-            return this._whichSearchSuggestionsNotification;
-          }
-
-          if (this.browserSearchSuggestEnabled && !this.inPrivateContext &&
-              // In any case, if the user made a choice we should not nag him.
-              !this._userMadeSearchSuggestionsChoice) {
-            if (this._defaultPrefs.getBoolPref("suggest.searches") &&
-                this.urlbarSearchSuggestEnabled && // Has not been switched off.
-                this._prefs.getIntPref("timesBeforeHidingSuggestionsHint")) {
-              return "opt-out";
-            }
-          }
-          return this._whichSearchSuggestionsNotification = "none";
-        ]]></getter>
-      </property>
-
-      <method name="updateSearchSuggestionsNotificationImpressions">
-        <parameter name="whichNotification"/>
-        <body><![CDATA[
-          if (whichNotification == "none") {
-            throw new Error("Unexpected notification type");
-          }
-
-          let remaining = this._prefs.getIntPref("timesBeforeHidingSuggestionsHint");
-          if (remaining > 0) {
-            this._prefs.setIntPref("timesBeforeHidingSuggestionsHint", remaining - 1);
-          }
-        ]]></body>
-      </method>
-
-      <method name="maybeShowSearchSuggestionsNotificationOnFocus">
-        <parameter name="mouseFocused"/>
-        <body><![CDATA[
-          let whichNotification = this.whichSearchSuggestionsNotification;
-          if (this._showSearchSuggestionNotificationOnMouseFocus &&
-              mouseFocused) {
-            // Force showing the opt-out notification.
-            this._whichSearchSuggestionsNotification = whichNotification = "opt-out";
-          }
-          if (whichNotification == "opt-out") {
-            try {
-              this.popup.openAutocompletePopup(this, this);
-            } finally {
-              if (mouseFocused) {
-                delete this._whichSearchSuggestionsNotification;
-                this._showSearchSuggestionNotificationOnMouseFocus = false;
-              }
-            }
-          }
-        ]]></body>
-      </method>
-
-      <!--
-        Sets the input's value, starts a search, and opens the popup.
-
-        @param  value
-                The input's value will be set to this value, and the search will
-                use it as its query.
-        @param  options
-                An optional object with the following optional properties:
-                * disableOneOffButtons: Set to true to hide the one-off search
-                  buttons.
-                * disableSearchSuggestionsNotification: Set to true to hide the
-                  onboarding opt-out search suggestions notification.
-      -->
-      <method name="search">
-        <parameter name="value"/>
-        <parameter name="options"/>
-        <body><![CDATA[
-          options = options || {};
-
-          if (options.disableOneOffButtons) {
-            this.popup.addEventListener("popupshowing", () => {
-              if (this.popup.oneOffSearchesEnabled) {
-                this.popup.oneOffSearchesEnabled = false;
-                this.popup.addEventListener("popuphidden", () => {
-                  this.popup.oneOffSearchesEnabled = true;
-                }, {once: true});
-              }
-            }, {once: true});
-          }
+    // Strike out the "https" part if mixed active content is loaded.
+    if (this.urlbarInput.getAttribute("pageproxystate") == "valid" &&
+        url.startsWith("https:") &&
+        this.window.gBrowser.securityUI.state &
+          Ci.nsIWebProgressListener.STATE_LOADED_MIXED_ACTIVE_CONTENT) {
+      let range = this.document.createRange();
+      range.setStart(textNode, 0);
+      range.setEnd(textNode, 5);
+      let strikeOut =
+        controller.getSelection(controller.SELECTION_URLSTRIKEOUT);
+      strikeOut.addRange(range);
+      this._formatScheme(controller.SELECTION_URLSTRIKEOUT);
+    }
 
-          if (options.disableSearchSuggestionsNotification &&
-              this.whichSearchSuggestionsNotification != "none") {
-            let which = this.whichSearchSuggestionsNotification;
-            this._whichSearchSuggestionsNotification = "none";
-            this.popup.addEventListener("popuphidden", () => {
-              this._whichSearchSuggestionsNotification = which;
-            }, {once: true});
-          }
-
-          // We want the value to be treated as text that the user typed.  It
-          // should go through the controller.handleText() path in onInput() so
-          // that gBrowser.userTypedValue, this.valueIsTyped, etc. are set and
-          // nsAutoCompleteController::HandleText() is called.  Set this.value
-          // and fire an input event to do that.  (If we set this.textValue we'd
-          // get an input event for free, but it would also set mIgnoreInput,
-          // skipping all of the above requirements.)
-          this.focus();
-          this.value = value;
-
-          // Avoid selecting the text if this method is called twice in a row.
-          this.selectionStart = -1;
-
-          let event = document.createEvent("Events");
-          event.initEvent("input", true, true);
-          this.dispatchEvent(event);
-
-          // handleText() ignores the value if it's the same as the previous
-          // value, but we want consecutive searches with the same value to be
-          // possible.  If handleText() returned false, then manually start a
-          // new search here.
-          if (!this._onInputHandledText) {
-            this.gotResultForCurrentQuery = false;
-            this.controller.startSearch(value);
-          }
-        ]]></body>
-      </method>
-    </implementation>
-
-    <handlers>
-      <handler event="keydown"><![CDATA[
-        if (this._noActionKeys.has(event.keyCode) &&
-            this.popup.selectedIndex >= 0 &&
-            !this._pressedNoActionKeys.has(event.keyCode)) {
-          if (this._pressedNoActionKeys.size == 0) {
-            this.popup.setAttribute("noactions", "true");
-            this.removeAttribute("actiontype");
-          }
-          this._pressedNoActionKeys.add(event.keyCode);
-        }
-      ]]></handler>
-
-      <handler event="keyup"><![CDATA[
-        if (this._noActionKeys.has(event.keyCode) &&
-            this._pressedNoActionKeys.has(event.keyCode)) {
-          this._pressedNoActionKeys.delete(event.keyCode);
-          if (this._pressedNoActionKeys.size == 0)
-            this._clearNoActions();
-        }
-      ]]></handler>
-
-      <handler event="mousedown"><![CDATA[
-        if (event.button == 0) {
-          if (event.originalTarget.getAttribute("anonid") == "historydropmarker") {
-            this.toggleHistoryPopup();
-          }
-
-          // Eventually show the opt-out notification even if the location bar is
-          // empty, focused, and the user clicks on it.
-          if (this.focused && this.textValue == "") {
-            this.maybeShowSearchSuggestionsNotificationOnFocus(true);
-          }
-        }
-      ]]></handler>
-
-      <handler event="focus"><![CDATA[
-        if (event.originalTarget == this.inputField) {
-          this._hideURLTooltip();
-          this.formatValue();
-          if (this.getAttribute("pageproxystate") != "valid") {
-            UpdatePopupNotificationsVisibility();
-          }
+    let baseDomain = domain;
+    let subDomain = "";
+    try {
+      baseDomain = Services.eTLD.getBaseDomainFromHost(uriInfo.fixedURI.host);
+      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);
+    }
 
-          // We show the opt-out notification when the mouse/keyboard focus the
-          // urlbar, but in any case we want to enforce at least one
-          // notification when the user focuses it with the mouse.
-          let whichNotification = this.whichSearchSuggestionsNotification;
-          if (whichNotification == "opt-out" &&
-              this._showSearchSuggestionNotificationOnMouseFocus === undefined) {
-            this._showSearchSuggestionNotificationOnMouseFocus = true;
-          }
-
-          // Check whether the focus change came from a keyboard/mouse action.
-          let focusMethod = Services.focus.getLastFocusMethod(window);
-          // If it's a focus started by code and the primary user intention was
-          // not to go to the location bar, don't show a notification.
-          if (!focusMethod && !this.userInitiatedFocus) {
-            return;
-          }
-
-          let mouseFocused = !!(focusMethod & Services.focus.FLAG_BYMOUSE);
-          this.maybeShowSearchSuggestionsNotificationOnFocus(mouseFocused);
-        }
-      ]]></handler>
-
-      <handler event="blur"><![CDATA[
-        if (event.originalTarget == this.inputField) {
-          this._clearNoActions();
-          this.formatValue();
-          if (this.getAttribute("pageproxystate") != "valid") {
-            UpdatePopupNotificationsVisibility();
-          }
-        }
-        if (this.ExtensionSearchHandler.hasActiveInputSession()) {
-          this.ExtensionSearchHandler.handleInputCancelled();
-        }
-        if (this._deferredKeyEventTimeout) {
-          clearTimeout(this._deferredKeyEventTimeout);
-          this._deferredKeyEventTimeout = null;
-        }
-        this._deferredKeyEventQueue = [];
-      ]]></handler>
+    let selection = controller.getSelection(controller.SELECTION_URLSECONDARY);
 
-      <handler event="dragstart" phase="capturing"><![CDATA[
-        // Drag only if the gesture starts from the input field.
-        if (this.inputField != event.originalTarget &&
-            !(this.inputField.compareDocumentPosition(event.originalTarget) &
-              Node.DOCUMENT_POSITION_CONTAINED_BY))
-          return;
-
-        // Drag only if the entire value is selected and it's a valid URI.
-        var isFullSelection = this.selectionStart == 0 &&
-                              this.selectionEnd == this.textLength;
-        if (!isFullSelection ||
-            this.getAttribute("pageproxystate") != "valid")
-          return;
-
-        var urlString = gBrowser.selectedBrowser.currentURI.displaySpec;
-        var title = gBrowser.selectedBrowser.contentTitle || urlString;
-        var htmlString = "<a href=\"" + urlString + "\">" + urlString + "</a>";
-
-        var dt = event.dataTransfer;
-        dt.setData("text/x-moz-url", urlString + "\n" + title);
-        dt.setData("text/unicode", urlString);
-        dt.setData("text/html", htmlString);
-
-        dt.effectAllowed = "copyLink";
-        event.stopPropagation();
-      ]]></handler>
-
-      <handler event="dragover" phase="capturing" action="this.onDragOver(event, this);"/>
-      <handler event="drop" phase="capturing" action="this.onDrop(event, this);"/>
-      <handler event="select"><![CDATA[
-        if (!Cc["@mozilla.org/widget/clipboard;1"]
-               .getService(Ci.nsIClipboard)
-               .supportsSelectionClipboard())
-          return;
-
-        if (!window.windowUtils.isHandlingUserInput)
-          return;
-
-        var val = this._getSelectedValueForClipboard();
-        if (!val)
-          return;
-
-        Cc["@mozilla.org/widget/clipboardhelper;1"]
-          .getService(Ci.nsIClipboardHelper)
-          .copyStringToClipboard(val, Ci.nsIClipboard.kSelectionClipboard);
-      ]]></handler>
-    </handlers>
-
-  </binding>
-
-  <binding id="urlbar-rich-result-popup" extends="chrome://global/content/bindings/autocomplete.xml#autocomplete-rich-result-popup">
+    let rangeLength = preDomain.length + subDomain.length - trimmedLength;
+    if (rangeLength) {
+      let range = this.document.createRange();
+      range.setStart(textNode, 0);
+      range.setEnd(textNode, rangeLength);
+      selection.addRange(range);
+    }
 
-    <content ignorekeys="true" level="top" consumeoutsideclicks="never"
-             aria-owns="richlistbox">
-      <xul:deck anonid="search-suggestions-notification"
-                align="center"
-                role="alert"
-                selectedIndex="0">
-        <!-- OPT-OUT -->
-        <xul:hbox flex="1" align="center" anonid="search-suggestions-opt-out">
-          <xul:image class="ac-site-icon" type="searchengine"/>
-          <xul:hbox anonid="search-suggestions-hint-typing">
-            <xul:description class="ac-title-text">&brandShortName;</xul:description>
-          </xul:hbox>
-          <xul:hbox anonid="search-suggestions-hint-box" flex="1">
-            <xul:description id="search-suggestions-hint">
-              <html:span class="prefix">&#x1f4a1; &urlbar.searchSuggestionsNotification.hintPrefix;</html:span>
-              <html:span>&urlbar.searchSuggestionsNotification.hint;</html:span>
-            </xul:description>
-          </xul:hbox>
-          <xul:label id="search-suggestions-change-settings"
-                     class="text-link"
-                     role="link"
-#ifdef XP_WIN
-                     value="&urlbar.searchSuggestionsNotification.changeSettingsWin;"
-                     accesskey="&urlbar.searchSuggestionsNotification.changeSettingsWin.accesskey;"
-#else
-                     value="&urlbar.searchSuggestionsNotification.changeSettingsUnix;"
-                     accesskey="&urlbar.searchSuggestionsNotification.changeSettingsUnix.accesskey;"
-#endif
-                     onclick="openPreferences('paneSearch', {origin: 'searchChangeSettings'});"
-                     control="search-suggestions-change-settings"/>
-        </xul:hbox>
-      </xul:deck>
-      <xul:richlistbox anonid="richlistbox" class="autocomplete-richlistbox"
-                       flex="1"/>
-      <xul:hbox anonid="footer">
-        <children/>
-        <xul:vbox anonid="one-off-search-buttons"
-                  class="search-one-offs"
-                  compact="true"
-                  includecurrentengine="true"
-                  disabletab="true"
-                  flex="1"/>
-      </xul:hbox>
-    </content>
+    let startRest = preDomain.length + domain.length - trimmedLength;
+    if (startRest < url.length - trimmedLength) {
+      let range = this.document.createRange();
+      range.setStart(textNode, startRest);
+      range.setEnd(textNode, url.length - trimmedLength);
+      selection.addRange(range);
+    }
 
-    <implementation>
-      <!--
-        For performance reasons we want to limit the size of the text runs we
-        build and show to the user.
-      -->
-      <field name="textRunsMaxLen">255</field>
-
-      <field name="_maxResults">0</field>
-
-      <field name="_bundle" readonly="true">
-        Cc["@mozilla.org/intl/stringbundle;1"].
-          getService(Ci.nsIStringBundleService).
-          createBundle("chrome://browser/locale/places/places.properties");
-      </field>
-
-      <field name="searchSuggestionsNotification" readonly="true">
-        document.getAnonymousElementByAttribute(
-          this, "anonid", "search-suggestions-notification"
-        );
-      </field>
-
-      <field name="footer" readonly="true">
-        document.getAnonymousElementByAttribute(this, "anonid", "footer");
-      </field>
-
-      <field name="shrinkDelay" readonly="true">
-        250
-      </field>
-
-      <field name="oneOffSearchButtons" readonly="true">
-        document.getAnonymousElementByAttribute(this, "anonid",
-                                                "one-off-search-buttons");
-      </field>
-
-      <field name="_oneOffSearchesEnabled">false</field>
-
-      <field name="_overrideValue">null</field>
-      <property name="overrideValue"
-                onget="return this._overrideValue;"
-                onset="this._overrideValue = val; return val;"/>
-
-      <method name="onPopupClick">
-        <parameter name="aEvent"/>
-        <body><![CDATA[
-          if (aEvent.button == 2) {
-            // Ignore right-clicks.
-            return;
-          }
-          // Otherwise "call super" -- do what autocomplete-base-popup does.
-          this.input.controller.handleEnter(true, aEvent);
-        ]]></body>
-      </method>
+    return true;
+  }
 
-      <property name="oneOffSearchesEnabled">
-        <getter><![CDATA[
-          return this._oneOffSearchesEnabled;
-        ]]></getter>
-        <setter><![CDATA[
-          this._oneOffSearchesEnabled = !!val;
-          if (val) {
-            this.oneOffSearchButtons.telemetryOrigin = "urlbar";
-            this.oneOffSearchButtons.style.display = "-moz-box";
-            // Set .textbox first, since the popup setter will cause
-            // a _rebuild call that uses it.
-            this.oneOffSearchButtons.textbox = this.input;
-            this.oneOffSearchButtons.popup = this;
-          } else {
-            this.oneOffSearchButtons.telemetryOrigin = null;
-            this.oneOffSearchButtons.style.display = "none";
-            this.oneOffSearchButtons.textbox = null;
-            this.oneOffSearchButtons.popup = null;
-          }
-          return this._oneOffSearchesEnabled;
-        ]]></setter>
-      </property>
-
-      <!-- Override this so that navigating between items results in an item
-           always being selected. -->
-      <method name="getNextIndex">
-        <parameter name="reverse"/>
-        <parameter name="amount"/>
-        <parameter name="index"/>
-        <parameter name="maxRow"/>
-        <body><![CDATA[
-          if (maxRow < 0)
-            return -1;
-
-          let newIndex = index + (reverse ? -1 : 1) * amount;
-
-          // We only want to wrap if navigation is in any direction by one item,
-          // otherwise we clamp to one end of the list.
-          // ie, hitting page-down will only cause is to wrap if we're already
-          // at one end of the list.
-
-          // Allow the selection to be removed if the first result is not a
-          // heuristic result.
-          if (!this._isFirstResultHeuristic) {
-            if (reverse && index == -1 || newIndex > maxRow && index != maxRow)
-              newIndex = maxRow;
-            else if (!reverse && index == -1 || newIndex < 0 && index != 0)
-              newIndex = 0;
-
-            if (newIndex < 0 && index == 0 || newIndex > maxRow && index == maxRow)
-              newIndex = -1;
-
-            return newIndex;
-          }
-
-          // Otherwise do not allow the selection to be removed.
-          if (newIndex < 0) {
-            newIndex = index > 0 ? 0 : maxRow;
-          } else if (newIndex > maxRow) {
-            newIndex = index < maxRow ? maxRow : 0;
-          }
-          return newIndex;
-        ]]></body>
-      </method>
-
-      <property name="_isFirstResultHeuristic" readonly="true">
-        <getter>
-          <![CDATA[
-            // The popup usually has a special "heuristic" first result (added
-            // by UnifiedComplete.js) that is automatically selected when the
-            // popup opens.
-            return this.input.mController.matchCount > 0 &&
-                   this.input.mController
-                             .getStyleAt(0)
-                             .split(/\s+/).indexOf("heuristic") > 0;
-          ]]>
-        </getter>
-      </property>
-
-      <property name="maxResults">
-        <getter>
-          <![CDATA[
-            if (!this._maxResults) {
-              this._maxResults = Services.prefs.getIntPref("browser.urlbar.maxRichResults");
-            }
-            return this._maxResults;
-          ]]>
-        </getter>
-        <setter>
-          <![CDATA[
-            return this._maxResults = parseInt(val);
-          ]]>
-        </setter>
-      </property>
-
-      <!-- This is set either to undefined or to a new object containing
-           { start, end } margin values in pixels. These are used to align the
-           results to the input field. -->
-      <property name="margins"
-                onget="return this._margins;">
-        <setter>
-          <![CDATA[
-          this._margins = val;
-
-          if (val) {
-            /* eslint-disable no-multi-spaces */
-            let paddingInCSS =
-                3   // .autocomplete-richlistbox padding-left/right
-              + 6   // .ac-site-icon margin-inline-start
-              + 16  // .ac-site-icon width
-              + 6;  // .ac-site-icon margin-inline-end
-            /* eslint-enable no-multi-spaces */
-            let actualVal = Math.round(val.start) - paddingInCSS;
-            let actualValEnd = Math.round(val.end);
-            this.style.setProperty("--item-padding-start", actualVal + "px");
-            this.style.setProperty("--item-padding-end", actualValEnd + "px");
-          } else {
-            this.style.removeProperty("--item-padding-start");
-            this.style.removeProperty("--item-padding-end");
-          }
-
-          return val;
-          ]]>
-        </setter>
-      </property>
-
-      <method name="openAutocompletePopup">
-        <parameter name="aInput"/>
-        <parameter name="aElement"/>
-        <body>
-          <![CDATA[
-          // initially the panel is hidden
-          // to avoid impacting startup / new window performance
-          aInput.popup.hidden = false;
+  _formatScheme(selectionType, clear) {
+    let editor = this.scheme.editor;
+    let controller = editor.selectionController;
+    let textNode = editor.rootElement.firstChild;
+    let selection = controller.getSelection(selectionType);
+    if (clear) {
+      selection.removeAllRanges();
+    } else {
+      let r = this.document.createRange();
+      r.setStart(textNode, 0);
+      r.setEnd(textNode, textNode.textContent.length);
+      selection.addRange(r);
+    }
+  }
 
-          this._openAutocompletePopup(aInput, aElement);
-          ]]>
-        </body>
-      </method>
-
-      <method name="_openAutocompletePopup">
-        <parameter name="aInput"/>
-        <parameter name="aElement"/>
-        <body><![CDATA[
-          if (this.mPopupOpen) {
-            return;
-          }
-
-          // Set the direction of the popup based on the textbox (bug 649840).
-          // getComputedStyle causes a layout flush, so avoid calling it if a
-          // direction has already been set.
-          if (!this.style.direction) {
-            this.style.direction =
-              aElement.ownerGlobal.getComputedStyle(aElement).direction;
-          }
-          let popupDirection = this.style.direction;
-
-          // Make the popup span the width of the window.  First, set its width.
-          let documentRect =
-            window.windowUtils
-                .getBoundsWithoutFlushing(window.document.documentElement);
-          let width = documentRect.right - documentRect.left;
-          this.setAttribute("width", width);
-
-          // Now make its starting margin negative so that its leading edge
-          // aligns with the window border.
-          let elementRect =
-            window.windowUtils.getBoundsWithoutFlushing(aElement);
-          if (popupDirection == "rtl") {
-            let offset = elementRect.right - documentRect.right;
-            this.style.marginRight = offset + "px";
-          } else {
-            let offset = documentRect.left - elementRect.left;
-            this.style.marginLeft = offset + "px";
-          }
-
-          // Keep the popup items' site icons aligned with the urlbar's identity
-          // icon if it's not too far from the edge of the window.  We define
-          // "too far" as "more than 30% of the window's width AND more than
-          // 250px".  Do this *before* adding any items because when the new
-          // value of the margins are different from the previous value, over-
-          // and underflow must be handled for each item already in the popup.
-          let needsHandleOverUnderflow = false;
-          let boundToCheck = popupDirection == "rtl" ? "right" : "left";
-          let inputRect = window.windowUtils.getBoundsWithoutFlushing(aInput);
-          let startOffset = Math.abs(inputRect[boundToCheck] - documentRect[boundToCheck]);
-          let alignSiteIcons = startOffset / width <= 0.3 || startOffset <= 250;
-          if (alignSiteIcons) {
-            // Calculate the end margin if we have a start margin.
-            let boundToCheckEnd = popupDirection == "rtl" ? "left" : "right";
-            let endOffset = Math.abs(inputRect[boundToCheckEnd] -
-                                     documentRect[boundToCheckEnd]);
-            if (endOffset > startOffset * 2) {
-              // Provide more space when aligning would result in an unbalanced
-              // margin. This allows the location bar to be moved to the start
-              // of the navigation toolbar to reclaim space for results.
-              endOffset = startOffset;
-            }
-            let identityIcon = document.getElementById("identity-icon");
-            let identityRect =
-              window.windowUtils.getBoundsWithoutFlushing(identityIcon);
-            let start = popupDirection == "rtl" ?
-                        documentRect.right - identityRect.right :
-                        identityRect.left;
-            if (!this.margins || start != this.margins.start ||
-                                 endOffset != this.margins.end ||
-                                 width != this.margins.width) {
-              this.margins = { start, end: endOffset, width };
-              needsHandleOverUnderflow = true;
-            }
-          } else if (this.margins) {
-            // Reset the alignment so that the site icons are positioned
-            // according to whatever's in the CSS.
-            this.margins = undefined;
-            needsHandleOverUnderflow = true;
-          }
-
-          // Now that the margins have been set, start adding items (via
-          // _invalidate).
-          this.mInput = aInput;
-          this.input.controller.setInitiallySelectedIndex(this._isFirstResultHeuristic ? 0 : -1);
-          this.input.userSelectionBehavior = "none";
-          this._invalidate();
+  _removeSearchAliasFormat() {
+    if (!this._formattingApplied) {
+      return;
+    }
+    let selection = this.editor.selectionController.getSelection(
+      Ci.nsISelectionController.SELECTION_FIND
+    );
+    selection.removeAllRanges();
+  }
 
-          try {
-            let whichNotification = aInput.whichSearchSuggestionsNotification;
-            if (whichNotification != "none") {
-              // Update the impressions count on real popupshown, since there's
-              // no guarantee openPopup will be respected by the platform.
-              // Though, we must ensure the handled event is the expected one.
-              let impressionId = this._searchSuggestionsImpressionId = {};
-              this.addEventListener("popupshown", () => {
-                if (this._searchSuggestionsImpressionId == impressionId)
-                  aInput.updateSearchSuggestionsNotificationImpressions(whichNotification);
-              }, {once: true});
-              this._showSearchSuggestionsNotification(whichNotification, popupDirection);
-            } else if (this.classList.contains("showSearchSuggestionsNotification")) {
-              this._hideSearchSuggestionsNotification();
-            }
-          } catch (ex) {
-            // Not critical for the urlbar functionality, just report the error.
-            Cu.reportError(ex);
-          }
-
-          // Position the popup below the navbar.  To get the y-coordinate,
-          // which is an offset from the bottom of the input, subtract the
-          // bottom of the navbar from the buttom of the input.
-          let yOffset = Math.round(
-            window.windowUtils.getBoundsWithoutFlushing(document.getElementById("nav-bar")).bottom -
-            window.windowUtils.getBoundsWithoutFlushing(aInput).bottom);
-
-          if (!this.richlistbox.suppressMenuItemEvent && this.richlistbox.currentItem) {
-            // The richlistbox fired a DOMMenuItemActive for accessibility,
-            // but because the popup isn't open yet, accessibility will ignore
-            // it. Therefore, fire it again once the popup opens.
-            this.addEventListener("popupshown", () => {
-              this.richlistbox.currentItem._fireEvent("DOMMenuItemActive");
-            }, {once: true});
-          }
-
-          this.openPopup(aElement, "after_start", 0, yOffset, false, false);
-
-          // Do this immediately after we've requested the popup to open. This
-          // will cause sync reflows but prevents flickering.
-          if (needsHandleOverUnderflow) {
-            for (let item of this.richlistbox.children) {
-              item.handleOverUnderflow();
-            }
-          }
-        ]]></body>
-      </method>
-
-      <method name="adjustHeight">
-        <body>
-          <![CDATA[
-          // If we were going to shrink later, cancel that for now:
-          if (this._shrinkTimeout) {
-            clearTimeout(this._shrinkTimeout);
-            this._shrinkTimeout = null;
-          }
-          let lastRowCount = this._lastRowCount;
-          // Figure out how many rows to show
-          let rows = this.richlistbox.children;
-          this._lastRowCount = rows.length;
-          let numRows = Math.min(this.matchCount, this.maxRows, rows.length);
-
-          // If we're going from 0 to non-0 rows, we might need to remove
-          // the height attribute to allow the popup to size. The attribute
-          // is set from XUL popup management code.
-          if (!lastRowCount && rows.length) {
-            this.removeAttribute("height");
-          }
-
-          // Default the height to 0 if we have no rows to show
-          let height = 0;
-          if (numRows) {
-            if (!this._rowHeight) {
-              window.promiseDocumentFlushed(() => {
-                if (window.closed) {
-                  return;
-                }
-                this._rowHeight = rows[0].getBoundingClientRect().height;
-                let style = window.getComputedStyle(this.richlistbox);
-
-                let paddingTop = parseInt(style.paddingTop) || 0;
-                let paddingBottom = parseInt(style.paddingBottom) || 0;
-                this._rlbPadding = paddingTop + paddingBottom;
-                // Then re-run - but don't dirty layout from inside this callback.
-                window.requestAnimationFrame(() => this.adjustHeight());
-              });
-              return;
-            }
-
-            // Calculate the height to have the first row to last row shown
-            height = (this._rowHeight * numRows) + this._rlbPadding;
-          }
+  /**
+   * If the input value starts with a search alias, this formatter method
+   * highlights it.
+   *
+   * @returns {boolean}
+   *   True if formatting was applied and false if not.
+   */
+  _formatSearchAlias() {
+    if (!UrlbarPrefs.get("formatting.enabled")) {
+      return false;
+    }
 
-          let animate = this.getAttribute("dontanimate") != "true";
-          let currentHeight =
-            parseFloat(this.richlistbox.getAttribute("height"), 10) ||
-            parseFloat(this.richlistbox.style.height, 10) ||
-            0; // It's possible we get here when we haven't set height on the richlistbox
-               // yet, which means parseFloat will return NaN. It should return 0 instead.
-          if (height > currentHeight) {
-            // Grow immediately.
-            if (animate) {
-              this.richlistbox.removeAttribute("height");
-              this.richlistbox.style.height = height + "px";
-            } else {
-              this.richlistbox.style.removeProperty("height");
-              this.richlistbox.height = height;
-            }
-          } else if (height < currentHeight) { // Don't shrink if height matches exactly
-            // Delay shrinking to avoid flicker.
-            this._shrinkTimeout = setTimeout(() => {
-              this._collapseUnusedItems();
-              if (animate) {
-                this.richlistbox.removeAttribute("height");
-                this.richlistbox.style.height = height + "px";
-              } else {
-                this.richlistbox.style.removeProperty("height");
-                this.richlistbox.height = height;
-              }
-            }, this.shrinkDelay);
-          }
-          ]]>
-        </body>
-      </method>
-
-      <method name="_showSearchSuggestionsNotification">
-        <parameter name="whichNotification"/>
-        <parameter name="popupDirection"/>
-        <body>
-          <![CDATA[
-          if (whichNotification == "opt-out") {
-            if (this.margins) {
-              this.searchSuggestionsNotification.style.paddingInlineStart =
-                this.margins.start + "px";
-            } else {
-              this.searchSuggestionsNotification.style.removeProperty("padding-inline-start");
-            }
-
-            // We want to animate the opt-out hint only once.
-            if (!this._firstSearchSuggestionsNotification) {
-              this._firstSearchSuggestionsNotification = true;
-              this.searchSuggestionsNotification.setAttribute("animate", "true");
-            }
-          }
-
-          this.searchSuggestionsNotification.setAttribute("aria-describedby",
-                                                          "search-suggestions-hint");
-
-          // With the notification shown, the listbox's height can sometimes be
-          // too small when it's flexed, as it normally is.  Also, it can start
-          // out slightly scrolled down.  Both problems appear together, most
-          // often when the popup is very narrow and the notification's text
-          // must wrap.  Work around them by removing the flex.
-          //
-          // But without flexing the listbox, the listbox's height animation
-          // sometimes fails to complete, leaving the popup too tall.  Work
-          // around that problem by disabling the listbox animation.
-          this.richlistbox.flex = 0;
-          this.setAttribute("dontanimate", "true");
-
-          this.classList.add("showSearchSuggestionsNotification");
-          // Don't show the one-off buttons if we are showing onboarding and
-          // there's no result, since it would be ugly and pointless.
-          this.footer.collapsed = this.matchCount == 0;
-          this.input.tabScrolling = this.matchCount != 0;
-
-          // This event allows accessibility APIs to see the notification.
-          if (!this.popupOpen) {
-            let event = document.createEvent("Events");
-            event.initEvent("AlertActive", true, true);
-            this.searchSuggestionsNotification.dispatchEvent(event);
-          }
-          ]]>
-        </body>
-      </method>
-
-      <method name="_hideSearchSuggestionsNotification">
-        <body>
-          <![CDATA[
-          this.classList.remove("showSearchSuggestionsNotification");
-          this.richlistbox.flex = 1;
-          this.removeAttribute("dontanimate");
-          this.searchSuggestionsNotification.removeAttribute("animate");
-          if (this.matchCount) {
-            // Update popup height.
-            this._invalidate();
-          } else {
-            this.closePopup();
-          }
-          ]]>
-        </body>
-      </method>
-
-      <!-- This handles keypress changes to the selection among the one-off
-           search buttons and between the one-offs and the listbox.  It returns
-           true if the keypress was consumed and false if not. -->
-      <method name="handleKeyPress">
-        <parameter name="aEvent"/>
-        <body><![CDATA[
-          this.oneOffSearchButtons.handleKeyPress(aEvent, this.matchCount,
-                                                  !this._isFirstResultHeuristic,
-                                                  gBrowser.userTypedValue);
-          return aEvent.defaultPrevented && !aEvent.urlbarDeferred;
-        ]]></body>
-      </method>
-
-      <!-- This is called when a one-off is clicked and when "search in new tab"
-           is selected from a one-off context menu. -->
-      <method name="handleOneOffSearch">
-        <parameter name="event"/>
-        <parameter name="engine"/>
-        <parameter name="where"/>
-        <parameter name="params"/>
-        <body><![CDATA[
-          this.input.handleCommand(event, where, params);
-        ]]></body>
-      </method>
-
-      <!-- Result listitems call this to determine which search engine they
-           should show in their labels and include in their url attributes. -->
-      <property name="overrideSearchEngineName" readonly="true">
-        <getter><![CDATA[
-          let button = this.oneOffSearchButtons.selectedButton;
-          return button && button.engine && button.engine.name;
-        ]]></getter>
-      </property>
-
-      <method name="createResultLabel">
-        <parameter name="item"/>
-        <parameter name="proposedLabel"/>
-        <body>
-          <![CDATA[
-            let parts = [proposedLabel];
-
-            let action = this.input._parseActionUrl(item.getAttribute("url"));
-            if (action) {
-              switch (action.type) {
-              case "searchengine":
-                parts = [
-                  action.params.searchSuggestion || action.params.searchQuery,
-                  action.params.engineName,
-                ];
-                break;
-              case "switchtab":
-              case "remotetab":
-                parts = [
-                  item.getAttribute("title"),
-                  item.getAttribute("displayurl"),
-                ];
-                break;
-              }
-            }
-
-            let types = item.getAttribute("type").split(/\s+/);
-            let type = types.find(t => t != "action" && t != "heuristic");
-            try {
-              // Some types intentionally do not map to strings, which is not
-              // an error.
-              parts.push(this._bundle.GetStringFromName(type + "ResultLabel"));
-            } catch (e) {}
-
-            return parts.filter(str => str).join(" ");
-          ]]>
-        </body>
-      </method>
-
-      <method name="maybeSetupSpeculativeConnect">
-        <parameter name="aUriString"/>
-        <body><![CDATA[
-          try {
-            let uri = makeURI(aUriString);
-            Services.io.speculativeConnect2(uri, gBrowser.contentPrincipal, null);
-          } catch (ex) {
-            // Can't setup speculative connection for this uri string for some
-            // reason, just ignore it.
-          }
-        ]]></body>
-      </method>
-
-      <method name="onResultsAdded">
-        <body>
-          <![CDATA[
-            // If nothing is selected yet, select the first result if it is a
-            // pre-selected "heuristic" result.  (See UnifiedComplete.js.)
-            let selectHeuristic =
-              this.selectedIndex == -1 && this._isFirstResultHeuristic;
-            if (selectHeuristic) {
-              this.input.controller.setInitiallySelectedIndex(0);
-            }
-
-            // If this is the heuristic result of a new search, format its
-            // search alias in the input or remove the formatting of the
-            // previous alias, as necessary.  We need to check selectHeuristic
-            // because the result may have already been added but only now is
-            // being selected, and we need to check gotResultForCurrentQuery
-            // because the result may be from the previous search and already
-            // selected and is now being reused.
-            if (selectHeuristic || !this.input.gotResultForCurrentQuery) {
-              this.input.formatValue();
-            }
+    // There can only be an alias to highlight if the heuristic result is
+    // an alias searchengine result and it's either currently selected or
+    // was selected when the popup was closed.  We also need to check
+    // whether a one-off search button is selected because in that case
+    // there won't be a selection but the alias should not be highlighted.
+    let popup = this.urlbarInput.popup;
+    if (!popup) {
+      // TODO: make this work with UrlbarView
+      return false;
+    }
+    let heuristicItem = popup.richlistbox.children[0] || null;
+    let alias =
+      (popup.selectedIndex == 0 ||
+       (popup.selectedIndex < 0 &&
+        popup._previousSelectedIndex == 0)) &&
+      !popup.oneOffSearchButtons.selectedButton &&
+      heuristicItem &&
+      heuristicItem.getAttribute("actiontype") == "searchengine" &&
+      this.urlbarInput._parseActionUrl(heuristicItem.getAttribute("url")).params.alias;
+    if (!alias) {
+      return false;
+    }
 
-            // If this is the first time we get the result from the current
-            // search and we are not in the private context, we can speculatively
-            // connect to the intended site as a performance optimization.
-            if (!this.input.gotResultForCurrentQuery &&
-                this.input.speculativeConnectEnabled &&
-                !this.input.inPrivateContext &&
-                this.input.mController.matchCount > 0) {
-              let firstStyle = this.input.mController.getStyleAt(0);
-              if (firstStyle.includes("autofill")) {
-                let uri = this.input.mController.getFinalCompleteValueAt(0);
-                this.maybeSetupSpeculativeConnect(uri);
-              } else if (firstStyle.includes("searchengine") &&
-                         this.input.browserSearchSuggestEnabled &&
-                         this.input.urlbarSearchSuggestEnabled) {
-                // Preconnect to the current search engine only if the search
-                // suggestions are enabled.
-                let engine = Services.search.currentEngine;
-                engine.speculativeConnect({window,
-                                           originAttributes: gBrowser.contentPrincipal.originAttributes});
-              }
-            }
-
-            // When a result is present the footer should always be visible.
-            this.footer.collapsed = false;
-            this.input.tabScrolling = true;
-
-            this.input.gotResultForCurrentQuery = true;
-            this.input.replaySafeDeferredKeyEvents();
-          ]]>
-        </body>
-      </method>
+    let textNode = this.editor.rootElement.firstChild;
+    let value = textNode.textContent;
 
-      <method name="_onSearchBegin">
-        <body><![CDATA[
-          // Set the selected index to 0 (heuristic) until a result comes back
-          // and we can evaluate it better.
-          //
-          // This is required to properly manage delayed handleEnter:
-          // 1. if a search starts we set selectedIndex to 0 here, and it will
-          //    be updated by onResultsAdded. Since selectedIndex is 0,
-          //    handleEnter will delay the action if a result didn't arrive yet.
-          // 2. if a search doesn't start (for example if autocomplete is
-          //    disabled), this won't be called, and the selectedIndex will be
-          //    the default -1 value. Then handleEnter will know it should not
-          //    delay the action, cause a result wont't ever arrive.
-          this.input.controller.setInitiallySelectedIndex(0);
-
-          // Since we are starting a new search, reset the currently selected
-          // one-off button, to cover those cases where the oneoff buttons
-          // binding won't receive an actual DOM event. For example, a search
-          // could be started without an actual input event, and the popup may
-          // not have been closed from the previous search.
-          this.oneOffSearchButtons.selectedButton = null;
-        ]]></body>
-      </method>
-
-      <field name="_addonIframe">null</field>
-      <field name="_addonIframeOwner">null</field>
-      <field name="_addonIframeOverriddenFunctionsByName">{}</field>
-
-      <!-- These methods must be overridden and properly handled by the API
-           runtime so that it doesn't break the popup.  If any of these methods
-           is not overridden, then initAddonIframe should throw. -->
-      <field name="_addonIframeOverrideFunctionNames">[
-        "_invalidate",
-      ]</field>
-
-      <field name="_addonIframeHiddenAnonids">[
-        "search-suggestions-notification",
-        "richlistbox",
-        "one-off-search-buttons",
-      ]</field>
-      <field name="_addonIframeHiddenDisplaysByAnonid">{}</field>
+    let index = value.indexOf(alias);
+    if (index < 0) {
+      return false;
+    }
 
-      <method name="initAddonIframe">
-        <parameter name="owner"/>
-        <parameter name="overrides"/>
-        <body><![CDATA[
-          if (this._addonIframeOwner) {
-            // Another add-on has already requested the iframe.  Return null to
-            // signal to the calling add-on that it should not take over the
-            // popup.  First add-on wins for now.
-            return null;
-          }
-          // Make sure all overrides are provided before doing anything.
-          for (let name of this._addonIframeOverrideFunctionNames) {
-            if (typeof(overrides[name]) != "function") {
-              throw new Error(
-                "Override for method '" + name + "' must be given"
-              );
-            }
-          }
-          // OK, insert the iframe.
-          this._addonIframeOwner = owner;
-          this._addonIframe = this._makeAddonIframe();
-          this._addonIframeOverriddenFunctionsByName = {};
-          for (let name of this._addonIframeOverrideFunctionNames) {
-            this._addonIframeOverriddenFunctionsByName[name] = this[name];
-            this[name] = overrides[name];
-          }
-          return this._addonIframe;
-        ]]></body>
-      </method>
-
-      <method name="destroyAddonIframe">
-        <parameter name="owner"/>
-        <body><![CDATA[
-          if (this._addonIframeOwner != owner) {
-            throw new Error("You're not the iframe owner");
-          }
-          this._addonIframeOwner = null;
-          this._addonIframe.remove();
-          this._addonIframe = null;
-          for (let anonid of this._addonIframeHiddenAnonids) {
-            let child = document.getAnonymousElementByAttribute(
-              this, "anonid", anonid
-            );
-            child.style.display =
-              this._addonIframeHiddenDisplaysByAnonid[anonid];
-          }
-          for (let name in this._addonIframeOverriddenFunctionsByName) {
-            this[name] = this._addonIframeOverriddenFunctionsByName[name];
-          }
-          this._addonIframeOverriddenFunctionsByName = {};
-        ]]></body>
-      </method>
+    // We abuse the SELECTION_FIND selection type to do our highlighting.
+    // It's the only type that works with Selection.setColors().
+    let selection = this.editor.selectionController.getSelection(
+      Ci.nsISelectionController.SELECTION_FIND
+    );
 
-      <method name="_makeAddonIframe">
-        <body><![CDATA[
-          this._addonIframeHiddenDisplaysByAnonid = {};
-          for (let anonid of this._addonIframeHiddenAnonids) {
-            let child = document.getAnonymousElementByAttribute(
-              this, "anonid", anonid
-            );
-            this._addonIframeHiddenDisplaysByAnonid[anonid] =
-              child.style.display;
-            child.style.display = "none";
-          }
-          let iframe = document.createXULElement("iframe");
-          iframe.setAttribute("type", "content");
-          iframe.setAttribute("flex", "1");
-          iframe.style.transition = "height 100ms";
-          this.appendChild(iframe);
-          return iframe;
-        ]]></body>
-      </method>
-
-    </implementation>
-    <handlers>
+    let range = this.document.createRange();
+    range.setStart(textNode, index);
+    range.setEnd(textNode, index + alias.length);
+    selection.addRange(range);
 
-      <handler event="SelectedOneOffButtonChanged"><![CDATA[
-        // Update all searchengine result items to use the newly selected
-        // engine.
-        for (let item of this.richlistbox.children) {
-          if (item.collapsed) {
-            break;
-          }
-          let url = item.getAttribute("url");
-          if (url) {
-            let action = item._parseActionUrl(url);
-            if (action && action.type == "searchengine") {
-              item._adjustAcItem();
-            }
-          }
-        }
-
-        // If the selection moved from the results to the one-off settings
-        // button, then call formatValue to remove the formatting of the search
-        // alias in the input, if any.  In all other cases the alias formatting
-        // is removed when the input's value setter calls formatValue, but in
-        // this specific case, at the time that formatValue is called,
-        // oneOffSearchButtons.selectedButton is still null, so the formatting
-        // is not removed.  The settings button is selected right after that.
-        if (this.oneOffSearchButtons.selectedButton ==
-              this.oneOffSearchButtons.settingsButtonCompact &&
-            (!event.detail || !event.detail.previousSelectedButton)) {
-          this.input.formatValue();
-        }
-      ]]></handler>
-
-      <handler event="mousedown"><![CDATA[
-        // Required to make the xul:label.text-link elements in the search
-        // suggestions notification work correctly when clicked on Linux.
-        // This is copied from the mousedown handler in
-        // browser-search-autocomplete-result-popup, which apparently had a
-        // similar problem.
-        event.preventDefault();
-
-        if (event.button == 2) {
-          // Right mouse button currently allows to select.
-          this.input.userSelectionBehavior = "rightClick";
-          // Ignore right-clicks.
-          return;
-        }
-
-        if (!this.input.speculativeConnectEnabled) {
-          return;
-        }
+    let fg = "#2362d7";
+    let bg = "#d2e6fd";
 
-        // Ensure the user is clicking on an url instead of other buttons
-        // on the popup.
-        let elt = event.originalTarget;
-        while (elt && elt.localName != "richlistitem" && elt != this) {
-          elt = elt.parentNode;
-        }
-        if (!elt || elt.localName != "richlistitem") {
-          return;
-        }
-        // The user might click on a ghost entry which was removed because of
-        // the coming new results.
-        if (this.input.controller.matchCount <= this.selectedIndex) {
-          return;
-        }
-
-        let url = this.input.controller.getFinalCompleteValueAt(this.selectedIndex);
-
-        // Whitelist the cases that we want to speculative connect, and ignore
-        // other moz-action uris or fancy protocols.
-        // Note that it's likely we've speculatively connected to the first
-        // url because it is a heuristic "autofill" result (see bug 1348275).
-        // "moz-action:searchengine" is also the same case. (see bug 1355443)
-        // So we won't duplicate the effort here.
-        if (url.startsWith("http") && this.selectedIndex > 0) {
-          this.maybeSetupSpeculativeConnect(url);
-        } else if (url.startsWith("moz-action:remotetab")) {
-          // URL is in the format moz-action:ACTION,PARAMS
-          // Where PARAMS is a JSON encoded object.
-          const MOZ_ACTION_REGEX = /^moz-action:([^,]+),(.*)$/;
-          if (!MOZ_ACTION_REGEX.test(url))
-            return;
-
-          let params = JSON.parse(url.match(MOZ_ACTION_REGEX)[2]);
-          if (params.url) {
-            this.maybeSetupSpeculativeConnect(decodeURIComponent(params.url));
-          }
-        }
-
-      ]]></handler>
-
-    </handlers>
-  </binding>
-
-  <binding id="addon-progress-notification" extends="chrome://global/content/bindings/notification.xml#popup-notification">
-    <implementation>
-      <constructor><![CDATA[
-        if (!this.notification)
-          return;
-
-        this.notification.options.installs.forEach(function(aInstall) {
-          aInstall.addListener(this);
-        }, this);
-
-        // Calling updateProgress can sometimes cause this notification to be
-        // removed in the middle of refreshing the notification panel which
-        // makes the panel get refreshed again. Just initialise to the
-        // undetermined state and then schedule a proper check at the next
-        // opportunity
-        this.setProgress(0, -1);
-        this._updateProgressTimeout = setTimeout(this.updateProgress.bind(this), 0);
-      ]]></constructor>
-
-      <destructor><![CDATA[
-        this.destroy();
-      ]]></destructor>
-
-      <field name="progressmeter" readonly="true">
-        document.getElementById("addon-progress-notification-progressmeter");
-      </field>
-      <field name="progresstext" readonly="true">
-        document.getElementById("addon-progress-notification-progresstext");
-      </field>
-      <property name="DownloadUtils" readonly="true">
-        <getter><![CDATA[
-          let module = {};
-          ChromeUtils.import("resource://gre/modules/DownloadUtils.jsm", module);
-          Object.defineProperty(this, "DownloadUtils", {
-            configurable: true,
-            enumerable: true,
-            writable: true,
-            value: module.DownloadUtils,
-          });
-          return module.DownloadUtils;
-        ]]></getter>
-      </property>
-
-      <method name="destroy">
-        <body><![CDATA[
-          if (!this.notification)
-            return;
-
-          this.notification.options.installs.forEach(function(aInstall) {
-            aInstall.removeListener(this);
-          }, this);
-          clearTimeout(this._updateProgressTimeout);
-        ]]></body>
-      </method>
+    // Selection.setColors() will swap the given foreground and background
+    // colors if it detects that the contrast between the background
+    // color and the frame color is too low.  Normally we don't want that
+    // to happen; we want it to use our colors as given (even if setColors
+    // thinks the contrast is too low).  But it's a nice feature for non-
+    // default themes, where the contrast between our background color and
+    // the input's frame color might actually be too low.  We can
+    // (hackily) force setColors to use our colors as given by passing
+    // them as the alternate colors.  Otherwise, allow setColors to swap
+    // them, which we can do by passing "currentColor".  See
+    // nsTextPaintStyle::GetHighlightColors for details.
+    if (this.document.documentElement.querySelector(":-moz-lwtheme") ||
+        (AppConstants.platform == "win" &&
+         this.window.matchMedia("(-moz-windows-default-theme: 0)").matches)) {
+      // non-default theme(s)
+      selection.setColors(fg, bg, "currentColor", "currentColor");
+    } else {
+      // default themes
+      selection.setColors(fg, bg, fg, bg);
+    }
 
-      <method name="setProgress">
-        <parameter name="aProgress"/>
-        <parameter name="aMaxProgress"/>
-        <body><![CDATA[
-          if (aMaxProgress == -1) {
-            this.progressmeter.setAttribute("mode", "undetermined");
-          } else {
-            this.progressmeter.setAttribute("mode", "determined");
-            this.progressmeter.setAttribute("value", (aProgress * 100) / aMaxProgress);
-          }
-
-          let now = Date.now();
-
-          if (!this.notification.lastUpdate) {
-            this.notification.lastUpdate = now;
-            this.notification.lastProgress = aProgress;
-            return;
-          }
-
-          let delta = now - this.notification.lastUpdate;
-          if ((delta < 400) && (aProgress < aMaxProgress))
-            return;
-
-          delta /= 1000;
-
-          // This algorithm is the same used by the downloads code.
-          let speed = (aProgress - this.notification.lastProgress) / delta;
-          if (this.notification.speed)
-            speed = speed * 0.9 + this.notification.speed * 0.1;
-
-          this.notification.lastUpdate = now;
-          this.notification.lastProgress = aProgress;
-          this.notification.speed = speed;
-
-          let status = null;
-          [status, this.notification.last] = this.DownloadUtils.getDownloadStatus(aProgress, aMaxProgress, speed, this.notification.last);
-          this.progresstext.setAttribute("value", status);
-          this.progresstext.setAttribute("tooltiptext", status);
-        ]]></body>
-      </method>
-
-      <method name="cancel">
-        <body><![CDATA[
-          let installs = this.notification.options.installs;
-          installs.forEach(function(aInstall) {
-            try {
-              aInstall.cancel();
-            } catch (e) {
-              // Cancel will throw if the download has already failed
-            }
-          }, this);
-
-          PopupNotifications.remove(this.notification);
-        ]]></body>
-      </method>
-
-      <method name="updateProgress">
-        <body><![CDATA[
-          if (!this.notification)
-            return;
-
-          let downloadingCount = 0;
-          let progress = 0;
-          let maxProgress = 0;
-
-          this.notification.options.installs.forEach(function(aInstall) {
-            if (aInstall.maxProgress == -1)
-              maxProgress = -1;
-            progress += aInstall.progress;
-            if (maxProgress >= 0)
-              maxProgress += aInstall.maxProgress;
-            if (aInstall.state < AddonManager.STATE_DOWNLOADED)
-              downloadingCount++;
-          });
-
-          if (downloadingCount == 0) {
-            this.destroy();
-            this.progressmeter.setAttribute("mode", "undetermined");
-            let status = gNavigatorBundle.getString("addonDownloadVerifying");
-            this.progresstext.setAttribute("value", status);
-            this.progresstext.setAttribute("tooltiptext", status);
-          } else {
-            this.setProgress(progress, maxProgress);
-          }
-        ]]></body>
-      </method>
-
-      <method name="onDownloadProgress">
-        <body><![CDATA[
-          this.updateProgress();
-        ]]></body>
-      </method>
-
-      <method name="onDownloadFailed">
-        <body><![CDATA[
-          this.updateProgress();
-        ]]></body>
-      </method>
-
-      <method name="onDownloadCancelled">
-        <body><![CDATA[
-          this.updateProgress();
-        ]]></body>
-      </method>
-
-      <method name="onDownloadEnded">
-        <body><![CDATA[
-          this.updateProgress();
-        ]]></body>
-      </method>
-    </implementation>
-  </binding>
-</bindings>
+    return true;
+  }
+}
--- a/browser/components/urlbar/moz.build
+++ b/browser/components/urlbar/moz.build
@@ -9,13 +9,14 @@ EXTRA_JS_MODULES += [
     'UrlbarController.jsm',
     'UrlbarInput.jsm',
     'UrlbarMatch.jsm',
     'UrlbarPrefs.jsm',
     'UrlbarProviderOpenTabs.jsm',
     'UrlbarProvidersManager.jsm',
     'UrlbarTokenizer.jsm',
     'UrlbarUtils.jsm',
+    'UrlbarValueFormatter.jsm',
     'UrlbarView.jsm',
 ]
 
 BROWSER_CHROME_MANIFESTS += ['tests/browser/browser.ini']
 XPCSHELL_TESTS_MANIFESTS += ['tests/unit/xpcshell.ini']