Merge mozilla-central to inbound on a CLOSED TREE
authorTiberius Oros <toros@mozilla.com>
Wed, 03 Oct 2018 09:44:29 +0300
changeset 495108 f7fd7d2cbc2ef4737be60b0ec5d432ba4fccaee2
parent 495107 10b62fac52698573bba71b4622e3a35ba2edb181 (current diff)
parent 495073 d770ea2a1b257febbbbe07a38163d66bdc47e1fd (diff)
child 495109 82a9e5de506a02333fcbf95ebf62ad73b5d130b9
push id9984
push userffxbld-merge
push dateMon, 15 Oct 2018 21:07:35 +0000
treeherdermozilla-beta@183d27ea8570 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
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
Merge mozilla-central to inbound on a CLOSED TREE
browser/base/content/test/siteIdentity/browser_tls_handshake_failure.js
dom/webidl/moz.build
taskcluster/ci/build/linux.yml
testing/mozharness/configs/selfserve/production.py
testing/mozharness/configs/selfserve/staging.py
testing/mozharness/mozharness/base/signing.py
testing/mozharness/mozharness/base/vcs/vcssync.py
testing/mozharness/mozharness/mozilla/building/buildbase.py
testing/mozharness/mozharness/mozilla/release.py
testing/mozharness/mozharness/mozilla/repo_manifest.py
testing/mozharness/mozharness/mozilla/selfserve.py
testing/mozharness/mozharness/mozilla/signed_certificate_timestamp.py
testing/mozharness/mozharness/mozilla/updates/__init__.py
testing/mozharness/scripts/release/submit-to-ct.py
testing/mozharness/scripts/release/updates.py
testing/mozharness/test/test_base_transfer.py
testing/mozharness/test/test_mozilla_release.py
testing/mozharness/test/test_mozilla_signed_certificate_timestamp.py
testing/web-platform/meta/css/css-multicol/multicol-span-none-001.xht.ini
--- a/accessible/android/SessionAccessibility.cpp
+++ b/accessible/android/SessionAccessibility.cpp
@@ -19,18 +19,25 @@
 
 template<>
 const char nsWindow::NativePtr<mozilla::a11y::SessionAccessibility>::sName[] =
   "SessionAccessibility";
 
 using namespace mozilla::a11y;
 
 void
-SessionAccessibility::SetAttached(bool aAttached)
+SessionAccessibility::SetAttached(bool aAttached,
+                                  already_AddRefed<Runnable> aRunnable)
 {
   if (RefPtr<nsThread> uiThread = GetAndroidUiThread()) {
     uiThread->Dispatch(NS_NewRunnableFunction(
       "SessionAccessibility::Attach",
       [aAttached,
        sa = java::SessionAccessibility::NativeProvider::GlobalRef(
-         mSessionAccessibility)] { sa->SetAttached(aAttached); }));
+           mSessionAccessibility),
+       runnable = RefPtr<Runnable>(aRunnable)] {
+        sa->SetAttached(aAttached);
+        if (runnable) {
+          runnable->Run();
+        }
+      }));
   }
 }
--- a/accessible/android/SessionAccessibility.h
+++ b/accessible/android/SessionAccessibility.h
@@ -20,31 +20,39 @@ public:
 
   SessionAccessibility(
     nsWindow::NativePtr<SessionAccessibility>* aPtr,
     nsWindow* aWindow,
     java::SessionAccessibility::NativeProvider::Param aSessionAccessibility)
     : mWindow(aPtr, aWindow)
     , mSessionAccessibility(aSessionAccessibility)
   {
-    SetAttached(true);
+    SetAttached(true, nullptr);
   }
 
-  void OnDetach() { SetAttached(false); }
+  void OnDetach(already_AddRefed<Runnable> aDisposer)
+  {
+    SetAttached(false, std::move(aDisposer));
+  }
+
+  const java::SessionAccessibility::NativeProvider::Ref& GetJavaAccessibility()
+  {
+    return mSessionAccessibility;
+  }
 
   // Native implementations
   using Base::AttachNative;
   using Base::DisposeNative;
 
   NS_INLINE_DECL_REFCOUNTING(SessionAccessibility)
 
 private:
   ~SessionAccessibility() {}
 
-  void SetAttached(bool aAttached);
+  void SetAttached(bool aAttached, already_AddRefed<Runnable> aRunnable);
 
   nsWindow::WindowPtr<SessionAccessibility> mWindow; // Parent only
   java::SessionAccessibility::NativeProvider::GlobalRef mSessionAccessibility;
 };
 
 } // namespace a11y
 } // namespace mozilla
 
--- a/browser/base/content/browser-pageActions.js
+++ b/browser/base/content/browser-pageActions.js
@@ -53,32 +53,21 @@ var BrowserPageActions = {
     this.panelNode.addEventListener("popuphiding", () => {
       this.mainButtonNode.removeAttribute("open");
     });
   },
 
   _onPanelShowing() {
     this.placeLazyActionsInPanel();
     for (let action of PageActions.actionsInPanel(window)) {
-      if (action.id == "sendToDevice") {
-        this.panelNode.removeAttribute(action.getTitle());
-        action.setTitle(this.getSendToDeviceString(), window);
-      }
       let buttonNode = this.panelButtonNodeForActionID(action.id);
       action.onShowingInPanel(buttonNode);
     }
   },
 
-  getSendToDeviceString() {
-    let tabCount = gBrowser.multiSelectedTabsCount || 1;
-    return PluralForm.get(tabCount,
-                          gNavigatorBundle.getString("pageAction.sendTabsToDevice.label"))
-                     .replace("#1", tabCount.toLocaleString());
-  },
-
   placeLazyActionsInPanel() {
     let actions = this._actionsToLazilyPlaceInPanel;
     this._actionsToLazilyPlaceInPanel = [];
     for (let action of actions) {
       this._placeActionInPanelNow(action);
     }
   },
 
@@ -1000,18 +989,31 @@ BrowserPageActions.emailLink = {
     PanelMultiView.hidePopup(BrowserPageActions.panelNode);
     MailIntegration.sendLinkForBrowser(gBrowser.selectedBrowser);
   },
 };
 
 // send to device
 BrowserPageActions.sendToDevice = {
   onBeforePlacedInWindow(browserWindow) {
+    this._updateTitle();
+    gBrowser.addEventListener("TabMultiSelect", event => {
+      this._updateTitle();
+    });
+  },
+
+  // The action's title in this window depends on the number of tabs that are
+  // selected.
+  _updateTitle() {
     let action = PageActions.actionForID("sendToDevice");
-    BrowserPageActions.takeActionTitleFromPanel(action);
+    let string =
+      gBrowserBundle.GetStringFromName("pageAction.sendTabsToDevice.label");
+    let tabCount = gBrowser.selectedTabs.length;
+    let title = PluralForm.get(tabCount, string).replace("#1", tabCount);
+    action.setTitle(title, window);
   },
 
   onSubviewPlaced(panelViewNode) {
     let bodyNode = panelViewNode.querySelector(".panel-subview-body");
     let notReady = document.createXULElement("toolbarbutton");
     notReady.classList.add(
       "subviewbutton",
       "subviewbutton-iconic",
--- a/browser/base/content/browser.css
+++ b/browser/base/content/browser.css
@@ -211,21 +211,26 @@ panelview[mainview] > .panel-header {
 
 #tabbrowser-tabs[movingtab] > .tabbrowser-tab[selected],
 #tabbrowser-tabs[movingtab] > .tabbrowser-tab[multiselected] {
   position: relative;
   z-index: 2;
   pointer-events: none; /* avoid blocking dragover events on scroll buttons */
 }
 
+.tabbrowser-tab[tab-grouping],
 .tabbrowser-tab[tabdrop-samewindow],
 #tabbrowser-tabs[movingtab] > .tabbrowser-tab[fadein]:not([selected]):not([multiselected]) {
   transition: transform 200ms var(--animation-easing-function);
 }
 
+.tabbrowser-tab[tab-grouping][multiselected]:not([selected]) {
+  z-index: 2;
+}
+
 /* The next 3 rules allow dragging tabs slightly outside of the tabstrip
  * to make it easier to drag tabs. */
 #TabsToolbar[movingtab] {
   padding-bottom: 15px;
 }
 
 #TabsToolbar[movingtab] > #tabbrowser-tabs {
   padding-bottom: 15px;
--- 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/browser.xul
+++ b/browser/base/content/browser.xul
@@ -143,35 +143,59 @@ xmlns="http://www.w3.org/1999/xhtml"
                 oncommand="duplicateTabIn(TabContextMenu.contextTab, 'tab');"/>
       <menu id="context_reopenInContainer"
             label="&reopenInContainer.label;"
             accesskey="&reopenInContainer.accesskey;"
             hidden="true">
         <menupopup oncommand="TabContextMenu.reopenInContainer(event);"
                    onpopupshowing="TabContextMenu.createReopenInContainerMenu(event);"/>
       </menu>
-      <menuitem id="context_openTabInWindow" label="&moveToNewWindow.label;"
-                accesskey="&moveToNewWindow.accesskey;"
-                tbattr="tabbrowser-multiple"
-                oncommand="gBrowser.replaceTabsWithWindow(TabContextMenu.contextTab);"/>
+      <menu id="context_moveTabOptions"
+            multiselectcontextlabel="&moveSelectedTabOptions.label;"
+            multiselectcontextaccesskey="&moveSelectedTabOptions.accesskey;"
+            nonmultiselectcontextlabel="&moveTabOptions.label;"
+            nonmultiselectcontextaccesskey="&moveTabOptions.accesskey;">
+        <menupopup id="moveTabOptionsMenu">
+          <menuitem id="context_moveToStart"
+                    label="&moveToStart.label;"
+                    accesskey="&moveToStart.accesskey;"
+                    tbattr="tabbrowser-multiple"
+                    oncommand="gBrowser.moveTabsToStart(TabContextMenu.contextTab);"/>
+          <menuitem id="context_moveToEnd"
+                    label="&moveToEnd.label;"
+                    accesskey="&moveToEnd.accesskey;"
+                    tbattr="tabbrowser-multiple"
+                    oncommand="gBrowser.moveTabsToEnd(TabContextMenu.contextTab);"/>
+          <menuitem id="context_openTabInWindow" label="&moveToNewWindow.label;"
+                    accesskey="&moveToNewWindow.accesskey;"
+                    tbattr="tabbrowser-multiple"
+                    oncommand="gBrowser.replaceTabsWithWindow(TabContextMenu.contextTab);"/>
+        </menupopup>
+      </menu>
       <menuseparator id="context_sendTabToDevice_separator" class="sync-ui-item"/>
       <menu id="context_sendTabToDevice"
             class="sync-ui-item">
         <menupopup id="context_sendTabToDevicePopupMenu"
                    onpopupshowing="gSync.populateSendTabToDevicesMenu(event.target, TabContextMenu.contextTab);"/>
       </menu>
       <menuseparator/>
       <menuitem id="context_reloadAllTabs" label="&reloadAllTabs.label;" accesskey="&reloadAllTabs.accesskey;"
                 tbattr="tabbrowser-multiple-visible"
                 oncommand="gBrowser.reloadAllTabs();"/>
-      <menuitem id="context_closeTabsToTheEnd" label="&closeTabsToTheEnd.label;" accesskey="&closeTabsToTheEnd.accesskey;"
-                oncommand="gBrowser.removeTabsToTheEndFrom(TabContextMenu.contextTab, {animate: true});"/>
-      <menuitem id="context_closeOtherTabs" label="&closeOtherTabs.label;" accesskey="&closeOtherTabs.accesskey;"
-                oncommand="gBrowser.removeAllTabsBut(TabContextMenu.contextTab);"/>
       <menuseparator/>
+      <menu id="context_closeTabOptions"
+            label="&closeTabOptions.label;"
+            accesskey="&closeTabOptions.accesskey;">
+        <menupopup id="closeOtherTabsMenu">
+          <menuitem id="context_closeTabsToTheEnd" label="&closeTabsToTheEnd.label;" accesskey="&closeTabsToTheEnd.accesskey;"
+                    oncommand="gBrowser.removeTabsToTheEndFrom(TabContextMenu.contextTab, {animate: true});"/>
+          <menuitem id="context_closeOtherTabs" label="&closeOtherTabs.label;" accesskey="&closeOtherTabs.accesskey;"
+                    oncommand="gBrowser.removeAllTabsBut(TabContextMenu.contextTab);"/>
+        </menupopup>
+      </menu>
       <menuitem id="context_undoCloseTab"
                 label="&undoCloseTab.label;"
                 accesskey="&undoCloseTab.accesskey;"
                 observes="History:UndoCloseTab"/>
       <menuitem id="context_closeTab" label="&closeTab.label;" accesskey="&closeTab.accesskey;"
                 oncommand="gBrowser.removeTab(TabContextMenu.contextTab, { animate: true });"/>
       <menuitem id="context_closeSelectedTabs" label="&closeSelectedTabs.label;"
                 hidden="true" accesskey="&closeSelectedTabs.accesskey;"
@@ -511,17 +535,16 @@ xmlns="http://www.w3.org/1999/xhtml"
            hidden="true"
            flip="slide"
            photon="true"
            position="bottomcenter topright"
            tabspecific="true"
            noautofocus="true"
            copyURL-title="&pageAction.copyLink.label;"
            emailLink-title="&emailPageCmd.label;"
-           sendToDevice-title=""
            sendToDevice-notReadyTitle="&sendToDevice.syncNotReady.label;"
            shareURL-title="&pageAction.shareUrl.label;"
            shareMore-label="&pageAction.shareMore.label;">
       <panelmultiview id="pageActionPanelMultiView"
                       mainViewId="pageActionPanelMainView"
                       viewCacheId="appMenu-viewCache">
         <panelview id="pageActionPanelMainView"
                    context="pageActionContextMenu"
--- a/browser/base/content/tabbrowser.js
+++ b/browser/base/content/tabbrowser.js
@@ -2549,16 +2549,40 @@ window._gBrowser = {
     // Additionally send pinned tab events
     if (pinned) {
       this._notifyPinnedStatus(t);
     }
 
     return t;
   },
 
+  moveTabsToStart(contextTab) {
+    let tabs = contextTab.multiselected ?
+      gBrowser.selectedTabs :
+      [contextTab];
+    // Walk the array in reverse order so the tabs are kept in order.
+    for (let i = tabs.length - 1; i >= 0; i--) {
+      let tab = tabs[i];
+      if (tab._tPos > 0) {
+        this.moveTabTo(tab, 0);
+      }
+    }
+  },
+
+  moveTabsToEnd(contextTab) {
+    let tabs = contextTab.multiselected ?
+      gBrowser.selectedTabs :
+      [contextTab];
+    for (let tab of tabs) {
+      if (tab._tPos < this.tabs.length - 1) {
+        this.moveTabTo(tab, this.tabs.length - 1);
+      }
+    }
+  },
+
   warnAboutClosingTabs(tabsToClose, aCloseTabs, aOptionalMessage) {
     if (tabsToClose <= 1)
       return true;
 
     const pref = aCloseTabs == this.closingTabsEnum.ALL ?
       "browser.tabs.warnOnClose" : "browser.tabs.warnOnCloseOtherTabs";
     var shouldPrompt = Services.prefs.getBoolPref(pref);
     if (!shouldPrompt)
@@ -3836,17 +3860,18 @@ window._gBrowser = {
 
   selectAllTabs() {
     let visibleTabs = this.visibleTabs;
     gBrowser.addRangeToMultiSelectedTabs(visibleTabs[0],
                                          visibleTabs[visibleTabs.length - 1]);
   },
 
   allTabsSelected() {
-    return this.visibleTabs.every(t => t.multiselected);
+    return this.visibleTabs.length == 1 ||
+           this.visibleTabs.every(t => t.multiselected);
   },
 
   lockClearMultiSelectionOnce() {
     this._clearMultiSelectionLockedOnce = true;
     this._clearMultiSelectionLocked = true;
   },
 
   unlockClearMultiSelection() {
@@ -5288,16 +5313,33 @@ var TabContextMenu = {
     contextPinTab.hidden = this.contextTab.pinned || multiselectionContext;
     let contextUnpinTab = document.getElementById("context_unpinTab");
     contextUnpinTab.hidden = !this.contextTab.pinned || multiselectionContext;
     let contextPinSelectedTabs = document.getElementById("context_pinSelectedTabs");
     contextPinSelectedTabs.hidden = this.contextTab.pinned || !multiselectionContext;
     let contextUnpinSelectedTabs = document.getElementById("context_unpinSelectedTabs");
     contextUnpinSelectedTabs.hidden = !this.contextTab.pinned || !multiselectionContext;
 
+    let contextMoveTabOptions = document.getElementById("context_moveTabOptions");
+    contextMoveTabOptions.disabled = gBrowser.allTabsSelected();
+    let moveTabOptionsStringPrefix = multiselectionContext ? "multiselectcontext" : "nonmultiselectcontext";
+    let moveTabOptionsLabel = contextMoveTabOptions.getAttribute(moveTabOptionsStringPrefix + "label");
+    let moveTabOptionsAccessKey = contextMoveTabOptions.getAttribute(moveTabOptionsStringPrefix + "accesskey");
+    contextMoveTabOptions.setAttribute("label", moveTabOptionsLabel);
+    contextMoveTabOptions.setAttribute("accesskey", moveTabOptionsAccessKey);
+    let selectedTabs = gBrowser.selectedTabs;
+    let contextMoveTabToEnd = document.getElementById("context_moveToEnd");
+    let allSelectedTabsAdjacent = selectedTabs.every((element, index, array) => {
+      return array.length > index + 1 ? element._tPos + 1 == array[index + 1]._tPos : true;
+    });
+    contextMoveTabToEnd.disabled = selectedTabs[selectedTabs.length - 1]._tPos == gBrowser.visibleTabs.length - 1 &&
+                                   allSelectedTabsAdjacent;
+    let contextMoveTabToStart = document.getElementById("context_moveToStart");
+    contextMoveTabToStart.disabled = selectedTabs[0]._tPos == 0 && allSelectedTabsAdjacent;
+
     // Hide the "Duplicate Tab" if there is a selection present
     let contextDuplicateTab = document.getElementById("context_duplicateTab");
     contextDuplicateTab.hidden = multiselectionContext;
 
     // Disable "Close Tabs to the Right" if there are no tabs
     // following it.
     document.getElementById("context_closeTabsToTheEnd").disabled =
       gBrowser.getTabsToTheEndFrom(this.contextTab).length == 0;
@@ -5307,16 +5349,19 @@ var TabContextMenu = {
       gBrowser.visibleTabs.filter(t => !t.multiselected && !t.pinned).length :
       gBrowser.visibleTabs.filter(t => t != this.contextTab && !t.pinned).length;
     document.getElementById("context_closeOtherTabs").disabled = unpinnedTabsToClose < 1;
 
     // Only one of close_tab/close_selected_tabs should be visible
     document.getElementById("context_closeTab").hidden = multiselectionContext;
     document.getElementById("context_closeSelectedTabs").hidden = !multiselectionContext;
 
+    // Hide "Close Tab Options" if all tabs are selected
+    document.getElementById("context_closeTabOptions").hidden = gBrowser.allTabsSelected();
+
     // Hide "Bookmark Tab" for multiselection.
     // Update its state if visible.
     let bookmarkTab = document.getElementById("context_bookmarkTab");
     bookmarkTab.hidden = multiselectionContext;
 
     // Show "Bookmark Selected Tabs" in a multiselect context and hide it otherwise.
     let bookmarkMultiSelectedTabs = document.getElementById("context_bookmarkSelectedTabs");
     bookmarkMultiSelectedTabs.hidden = !multiselectionContext;
--- a/browser/base/content/tabbrowser.xml
+++ b/browser/base/content/tabbrowser.xml
@@ -581,16 +581,17 @@
           }
         ]]></body>
       </method>
 
       <method name="_animateTabMove">
         <parameter name="event"/>
         <body><![CDATA[
           let draggedTab = event.dataTransfer.mozGetDataAt(TAB_DROP_TYPE, 0);
+          let movingTabs = draggedTab._dragData.movingTabs;
 
           if (this.getAttribute("movingtab") != "true") {
             this.setAttribute("movingtab", "true");
             this.parentNode.setAttribute("movingtab", "true");
             if (!draggedTab.multiselected)
               this.selectedItem = draggedTab;
           }
 
@@ -607,17 +608,17 @@
           draggedTab._dragData.animLastScreenX = screenX;
 
           let rtl = (window.getComputedStyle(this).direction == "rtl");
           let pinned = draggedTab.pinned;
           let numPinned = gBrowser._numPinnedTabs;
           let tabs = this._getVisibleTabs()
                          .slice(pinned ? 0 : numPinned,
                                 pinned ? numPinned : undefined);
-          let movingTabs = draggedTab._dragData.movingTabs;
+
           if (rtl) {
             tabs.reverse();
             // Copy moving tabs array to avoid infinite reversing.
             movingTabs = [...movingTabs].reverse();
           }
           let tabWidth = draggedTab.getBoundingClientRect().width;
           let shiftWidth = tabWidth * movingTabs.length;
           draggedTab._dragData.tabWidth = tabWidth;
@@ -717,16 +718,190 @@
 
           this.removeAttribute("movingtab");
           this.parentNode.removeAttribute("movingtab");
 
           this._handleTabSelect();
         ]]></body>
       </method>
 
+      <!--  Regroup all selected tabs around the
+            tab in param  -->
+      <method name="_groupSelectedTabs">
+        <parameter name="tab"/>
+        <body><![CDATA[
+          let draggedTabPos = tab._tPos;
+          let selectedTabs = gBrowser.selectedTabs;
+          let animate = gBrowser.animationsEnabled;
+
+          tab.groupingTabsData = {
+            finished: !animate,
+          };
+
+
+          // Animate left selected tabs
+
+          let insertAtPos = draggedTabPos - 1;
+          for (let i = selectedTabs.indexOf(tab) - 1; i > -1; i--) {
+            let movingTab = selectedTabs[i];
+            insertAtPos = newIndex(movingTab, insertAtPos);
+
+            if (animate) {
+              movingTab.groupingTabsData = {};
+              addAnimationData(movingTab, insertAtPos, "left");
+            } else {
+              gBrowser.moveTabTo(movingTab, insertAtPos);
+            }
+            insertAtPos--;
+          }
+
+          // Animate right selected tabs
+
+          insertAtPos = draggedTabPos + 1;
+          for (let i = selectedTabs.indexOf(tab) + 1; i < selectedTabs.length; i++) {
+            let movingTab = selectedTabs[i];
+            insertAtPos = newIndex(movingTab, insertAtPos);
+
+            if (animate) {
+              movingTab.groupingTabsData = {};
+              addAnimationData(movingTab, insertAtPos, "right");
+            } else {
+              gBrowser.moveTabTo(movingTab, insertAtPos);
+            }
+            insertAtPos++;
+          }
+
+          // Slide the relevant tabs to their new position.
+          let rtl = Services.locale.isAppLocaleRTL ? -1 : 1;
+          for (let t of this._getVisibleTabs()) {
+            if (t.groupingTabsData && t.groupingTabsData.translateX) {
+              let translateX = rtl * t.groupingTabsData.translateX;
+              t.style.transform = "translateX(" + translateX + "px)";
+            }
+          }
+
+          function newIndex(aTab, index) {
+            // Don't allow mixing pinned and unpinned tabs.
+            if (aTab.pinned) {
+              return Math.min(index, gBrowser._numPinnedTabs - 1);
+            }
+            return Math.max(index, gBrowser._numPinnedTabs);
+          }
+
+          function addAnimationData(movingTab, movingTabNewIndex, side) {
+            let movingTabOldIndex = movingTab._tPos;
+
+            if (movingTabOldIndex == movingTabNewIndex) {
+              // movingTab is already at the right position
+              // and thus don't need to be animated.
+              return;
+            }
+
+            let movingTabWidth = movingTab.boxObject.width;
+            let shift = (movingTabNewIndex - movingTabOldIndex) * movingTabWidth;
+
+            movingTab.groupingTabsData.animate = true;
+            movingTab.setAttribute("tab-grouping", "true");
+
+            movingTab.groupingTabsData.translateX = shift;
+
+            let onTransitionEnd = transitionendEvent => {
+              if (transitionendEvent.propertyName != "transform" ||
+                  transitionendEvent.originalTarget != movingTab) {
+                return;
+              }
+              movingTab.removeEventListener("transitionend", onTransitionEnd);
+              movingTab.groupingTabsData.newIndex = movingTabNewIndex;
+              movingTab.groupingTabsData.animate = false;
+            };
+
+            movingTab.addEventListener("transitionend", onTransitionEnd);
+
+            // Add animation data for tabs between movingTab (selected
+            // tab moving towards the dragged tab) and draggedTab.
+            // Those tabs in the middle should move in
+            // the opposite direction of movingTab.
+
+            let lowerIndex = Math.min(movingTabOldIndex, draggedTabPos);
+            let higherIndex = Math.max(movingTabOldIndex, draggedTabPos);
+
+            for (let i = lowerIndex + 1; i < higherIndex; i++) {
+              let middleTab = gBrowser.visibleTabs[i];
+
+              if (middleTab.pinned != movingTab.pinned) {
+                // Don't mix pinned and unpinned tabs
+                break;
+              }
+
+              if (middleTab.multiselected) {
+                // Skip because this selected tab should
+                // be shifted towards the dragged Tab.
+                continue;
+              }
+
+              if (!middleTab.groupingTabsData || !middleTab.groupingTabsData.translateX) {
+                middleTab.groupingTabsData = { translateX: 0};
+              }
+              if (side == "left") {
+                middleTab.groupingTabsData.translateX -= movingTabWidth;
+              } else {
+                middleTab.groupingTabsData.translateX += movingTabWidth;
+              }
+
+              middleTab.setAttribute("tab-grouping", "true");
+            }
+          }
+        ]]></body>
+      </method>
+
+      <method name="_finishGroupSelectedTabs">
+        <parameter name="tab"/>
+        <body><![CDATA[
+          if (!tab.groupingTabsData || tab.groupingTabsData.finished)
+            return;
+
+          tab.groupingTabsData.finished = true;
+
+          let selectedTabs = gBrowser.selectedTabs;
+          let tabIndex = selectedTabs.indexOf(tab);
+
+          // Moving left tabs
+          for (let i = tabIndex - 1; i > -1; i--) {
+            let movingTab = selectedTabs[i];
+            if (movingTab.groupingTabsData.newIndex) {
+              gBrowser.moveTabTo(movingTab, movingTab.groupingTabsData.newIndex);
+            }
+          }
+
+          // Moving right tabs
+          for (let i = tabIndex + 1; i < selectedTabs.length; i++) {
+            let movingTab = selectedTabs[i];
+            if (movingTab.groupingTabsData.newIndex) {
+              gBrowser.moveTabTo(movingTab, movingTab.groupingTabsData.newIndex);
+            }
+          }
+
+          for (let t of this._getVisibleTabs()) {
+            t.style.transform = "";
+            t.removeAttribute("tab-grouping");
+            delete t.groupingTabsData;
+          }
+        ]]></body>
+      </method>
+
+      <method name="_isGroupTabsAnimationOver">
+        <body><![CDATA[
+          for (let tab of gBrowser.selectedTabs) {
+            if (tab.groupingTabsData && tab.groupingTabsData.animate)
+              return false;
+          }
+          return true;
+        ]]></body>
+      </method>
+
       <method name="handleEvent">
         <parameter name="aEvent"/>
         <body><![CDATA[
           switch (aEvent.type) {
             case "resize":
               if (aEvent.target != window)
                 break;
 
@@ -1284,35 +1459,17 @@
         // Set the cursor to an arrow during tab drags.
         dt.mozCursor = "default";
 
         // Set the tab as the source of the drag, which ensures we have a stable
         // node to deliver the `dragend` event.  See bug 1345473.
         dt.addElement(tab);
 
         if (tab.multiselected) {
-          // Regroup all selected tabs around the dragged tab
-          // for multiple tabs dragging
-          let draggedTabPos = tab._tPos;
-
-          // Move left selected tabs
-          let insertAtPos = draggedTabPos - 1;
-          for (let i = selectedTabs.indexOf(tab) - 1; i > -1; i--) {
-            let movingTab = selectedTabs[i];
-            gBrowser.moveTabTo(movingTab, insertAtPos);
-            insertAtPos--;
-          }
-
-          // Move right selected tabs
-          insertAtPos = draggedTabPos + 1;
-          for (let i = selectedTabs.indexOf(tab) + 1; i < selectedTabs.length; i++) {
-            let movingTab = selectedTabs[i];
-            gBrowser.moveTabTo(movingTab, insertAtPos);
-            insertAtPos++;
-          }
+          this._groupSelectedTabs(tab);
         }
 
         // Create a canvas to which we capture the current tab.
         // Until canvas is HiDPI-aware (bug 780362), we need to scale the desired
         // canvas size (in CSS pixels) to the window's backing resolution in order
         // to get a full-resolution drag image for use on HiDPI displays.
         let windowUtils = window.windowUtils;
         let scale = windowUtils.screenPixelsPerCSSPixel / windowUtils.fullZoom;
@@ -1419,21 +1576,31 @@
             case "scrollbutton-down":
               pixelsToScroll = arrowScrollbox.scrollIncrement;
               break;
           }
           if (pixelsToScroll)
             arrowScrollbox.scrollByPixels((ltr ? 1 : -1) * pixelsToScroll, true);
         }
 
-        if (effects == "move" &&
-            this == event.dataTransfer.mozGetDataAt(TAB_DROP_TYPE, 0).parentNode) {
+        let draggedTab = event.dataTransfer.mozGetDataAt(TAB_DROP_TYPE, 0);
+        if ((effects == "move" || effects == "copy") &&
+            this == draggedTab.parentNode) {
           ind.collapsed = true;
-          this._animateTabMove(event);
-          return;
+
+          if (!this._isGroupTabsAnimationOver()) {
+            // Wait for grouping tabs animation to finish
+            return;
+          }
+          this._finishGroupSelectedTabs(draggedTab);
+
+          if (effects == "move") {
+            this._animateTabMove(event);
+            return;
+          }
         }
 
         this._finishAnimateTabMove();
 
         if (effects == "link") {
           let tab = this._getDragTargetTab(event, true);
           if (tab) {
             if (!this._dragTime)
@@ -1491,16 +1658,17 @@
         var draggedTab;
         let movingTabs;
         if (dt.mozTypesAt(0)[0] == TAB_DROP_TYPE) { // tab copy or move
           draggedTab = dt.mozGetDataAt(TAB_DROP_TYPE, 0);
           // not our drop then
           if (!draggedTab)
             return;
           movingTabs = draggedTab._dragData.movingTabs;
+          draggedTab.parentNode._finishGroupSelectedTabs(draggedTab);
         }
 
         this._tabDropIndicator.collapsed = true;
         event.stopPropagation();
         if (draggedTab && dropEffect == "copy") {
           // copy the dropped tab (wherever it's from)
           let newIndex = this._getDropIndex(event, false);
           let draggedTabCopy;
@@ -1631,16 +1799,17 @@
         var draggedTab = dt.mozGetDataAt(TAB_DROP_TYPE, 0);
 
         // Prevent this code from running if a tabdrop animation is
         // running since calling _finishAnimateTabMove would clear
         // any CSS transition that is running.
         if (draggedTab.hasAttribute("tabdrop-samewindow"))
           return;
 
+        this._finishGroupSelectedTabs(draggedTab);
         this._finishAnimateTabMove();
 
         if (dt.mozUserCancelled || dt.dropEffect != "none" || this._isCustomizing) {
           delete draggedTab._dragData;
           return;
         }
 
         // Disable detach within the browser toolbox
--- a/browser/base/content/test/pageinfo/browser.ini
+++ b/browser/base/content/test/pageinfo/browser.ini
@@ -5,16 +5,15 @@ support-files =
   image.html
   ../general/audio.ogg
   ../general/moz.png
   ../general/video.ogg
 [browser_pageinfo_images.js]
 support-files =
   all_images.html
 [browser_pageinfo_image_info.js]
-uses-unsafe-cpows = true
 skip-if = (os == 'linux' && e10s) # bug 1161699
 [browser_pageinfo_permissions.js]
 [browser_pageinfo_security.js]
 [browser_pageinfo_svg_image.js]
 support-files =
   svg_image.html
   ../general/title_test.svg
--- a/browser/base/content/test/siteIdentity/browser.ini
+++ b/browser/base/content/test/siteIdentity/browser.ini
@@ -19,17 +19,16 @@ support-files =
 [browser_bug902156.js]
 tags = mcb
 support-files =
   file_bug902156.js
   file_bug902156_1.html
   file_bug902156_2.html
   file_bug902156_3.html
 [browser_bug906190.js]
-uses-unsafe-cpows = true
 tags = mcb
 support-files =
   file_bug906190_1.html
   file_bug906190_2.html
   file_bug906190_3_4.html
   file_bug906190_redirected.html
   file_bug906190.js
   file_bug906190.sjs
@@ -103,9 +102,9 @@ support-files =
 [browser_no_mcb_for_onions.js]
 tags = mcb
 support-files =
   test_no_mcb_for_onions.html
 [browser_check_identity_state.js]
 [browser_iframe_navigation.js]
 support-files =
   iframe_navigation.html
-[browser_tls_handshake_failure.js]
+[browser_navigation_failures.js]
rename from browser/base/content/test/siteIdentity/browser_tls_handshake_failure.js
rename to browser/base/content/test/siteIdentity/browser_navigation_failures.js
--- a/browser/base/content/test/siteIdentity/browser_tls_handshake_failure.js
+++ b/browser/base/content/test/siteIdentity/browser_navigation_failures.js
@@ -1,25 +1,41 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
-// Tests that the site identity indicator is properly updated for connections
-// where the TLS handshake fails.
-// See bug 1492424.
+// Tests that the site identity indicator is properly updated for navigations
+// that fail for various reasons. In particular, we currently test TLS handshake
+// failures and about: pages that don't actually exist.
+// See bug 1492424 and bug 1493427.
 
+const kSecureURI = getRootDirectory(gTestPath).replace("chrome://mochitests/content",
+                                                       "https://example.com") + "dummy_page.html";
 add_task(async function() {
-  let rootURI = getRootDirectory(gTestPath).replace("chrome://mochitests/content",
-                                                    "https://example.com");
-  await BrowserTestUtils.withNewTab(rootURI + "dummy_page.html", async (browser) => {
+  await BrowserTestUtils.withNewTab(kSecureURI, async (browser) => {
     let identityMode = window.document.getElementById("identity-box").className;
     is(identityMode, "verifiedDomain", "identity should be secure before");
 
     const TLS_HANDSHAKE_FAILURE_URI = "https://ssl3.example.com/";
     // Try to connect to a server where the TLS handshake will fail.
     BrowserTestUtils.loadURI(browser, TLS_HANDSHAKE_FAILURE_URI);
     await BrowserTestUtils.browserLoaded(browser, false, TLS_HANDSHAKE_FAILURE_URI, true);
 
     let newIdentityMode = window.document.getElementById("identity-box").className;
     is(newIdentityMode, "unknownIdentity", "identity should be unknown (not secure) after");
   });
 });
+
+add_task(async function() {
+  await BrowserTestUtils.withNewTab(kSecureURI, async (browser) => {
+    let identityMode = window.document.getElementById("identity-box").className;
+    is(identityMode, "verifiedDomain", "identity should be secure before");
+
+    const BAD_ABOUT_PAGE_URI = "about:somethingthatdoesnotexist";
+    // Try to load an about: page that doesn't exist
+    BrowserTestUtils.loadURI(browser, BAD_ABOUT_PAGE_URI);
+    await BrowserTestUtils.browserLoaded(browser, false, BAD_ABOUT_PAGE_URI, true);
+
+    let newIdentityMode = window.document.getElementById("identity-box").className;
+    is(newIdentityMode, "unknownIdentity", "identity should be unknown (not secure) after");
+  });
+});
--- a/browser/base/content/test/tabs/browser_multiselect_tabs_reorder.js
+++ b/browser/base/content/test/tabs/browser_multiselect_tabs_reorder.js
@@ -1,16 +1,20 @@
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
 const PREF_MULTISELECT_TABS = "browser.tabs.multiselect";
+const PREF_ANIMATION = "toolkit.cosmeticAnimations.enabled";
 
 add_task(async function setPref() {
   await SpecialPowers.pushPrefEnv({
-    set: [[PREF_MULTISELECT_TABS, true]],
+    set: [
+      [PREF_MULTISELECT_TABS, true],
+      [PREF_ANIMATION, false],
+    ],
   });
 });
 
 add_task(async function() {
   let tab0 = gBrowser.selectedTab;
   let tab1 = await addTab();
   let tab2 = await addTab();
   let tab3 = await addTab();
--- a/browser/base/content/test/urlbar/browser.ini
+++ b/browser/base/content/test/urlbar/browser.ini
@@ -84,20 +84,18 @@ support-files =
 [browser_urlbarDecode.js]
 [browser_urlbarDelete.js]
 [browser_urlbarEnter.js]
 [browser_urlbar_whereToOpen.js]
 [browser_urlbarEnterAfterMouseOver.js]
 skip-if = os == "linux" # Bug 1073339 - Investigate autocomplete test unreliability on Linux/e10s
 [browser_urlbarFocusedCmdK.js]
 [browser_urlbarHashChangeProxyState.js]
-uses-unsafe-cpows = true
 [browser_urlbarHighlightSearchAlias.js]
 [browser_urlbarKeepStateAcrossTabSwitches.js]
-uses-unsafe-cpows = true
 [browser_urlbarOneOffs.js]
 support-files =
   searchSuggestionEngine.xml
   searchSuggestionEngine.sjs
 [browser_urlbarOneOffs_searchSuggestions.js]
 support-files =
   searchSuggestionEngine.xml
   searchSuggestionEngine.sjs
--- a/browser/base/content/test/urlbar/browser_page_action_menu.js
+++ b/browser/base/content/test/urlbar/browser_page_action_menu.js
@@ -522,16 +522,74 @@ add_task(async function sendToDevice_dev
     let hiddenPromise = promisePageActionPanelHidden();
     BrowserPageActions.panelNode.hidePopup();
     await hiddenPromise;
 
     cleanUp();
   });
 });
 
+add_task(async function sendToDevice_title() {
+  // Open two tabs that are sendable.
+  await BrowserTestUtils.withNewTab("http://example.com/a", async otherBrowser => {
+    await BrowserTestUtils.withNewTab("http://example.com/b", async () => {
+      await promiseSyncReady();
+      const sandbox = sinon.sandbox.create();
+      sandbox.stub(gSync, "syncReady").get(() => true);
+      sandbox.stub(Weave.Service.clientsEngine, "isFirstSync").get(() => false);
+      sandbox.stub(UIState, "get").returns({ status: UIState.STATUS_SIGNED_IN });
+      sandbox.stub(gSync, "isSendableURI").returns(true);
+      sandbox.stub(gSync, "remoteClients").get(() => []);
+      sandbox.stub(Weave.Service.clientsEngine, "getClientType").callsFake(id => mockRemoteClients.find(c => c.id == id).type);
+
+      let cleanUp = () => {
+        sandbox.restore();
+      };
+      registerCleanupFunction(cleanUp);
+
+      // Open the panel.  Only one tab is selected, so the action's title should
+      // be "Send Tab to Device".
+      await promisePageActionPanelOpen();
+      let sendToDeviceButton =
+        document.getElementById("pageAction-panel-sendToDevice");
+      Assert.ok(!sendToDeviceButton.disabled);
+
+      Assert.equal(sendToDeviceButton.label, "Send Tab to Device");
+      Assert.equal(PageActions.actionForID("sendToDevice").getTitle(window),
+                   "Send Tab to Device");
+
+      // Hide the panel.
+      let hiddenPromise = promisePageActionPanelHidden();
+      BrowserPageActions.panelNode.hidePopup();
+      await hiddenPromise;
+
+      // Add the other tab to the selection.
+      gBrowser.addToMultiSelectedTabs(gBrowser.getTabForBrowser(otherBrowser),
+                                      false);
+
+      // Open the panel again.  Now the action's title should be "Send 2 Tabs to
+      // Device".
+      await promisePageActionPanelOpen();
+      Assert.ok(!sendToDeviceButton.disabled);
+      Assert.equal(sendToDeviceButton.label, "Send 2 Tabs to Device");
+      Assert.equal(PageActions.actionForID("sendToDevice").getTitle(window),
+                   "Send 2 Tabs to Device");
+
+      // Hide the panel.
+      hiddenPromise = promisePageActionPanelHidden();
+      BrowserPageActions.panelNode.hidePopup();
+      await hiddenPromise;
+
+      cleanUp();
+
+      await UIState.reset();
+    });
+  });
+});
+
 add_task(async function sendToDevice_inUrlbar() {
   // Open a tab that's sendable.
   await BrowserTestUtils.withNewTab("http://example.com/", async () => {
     await promiseSyncReady();
     const sandbox = sinon.sandbox.create();
     sandbox.stub(gSync, "syncReady").get(() => true);
     sandbox.stub(Weave.Service.clientsEngine, "isFirstSync").get(() => false);
     sandbox.stub(UIState, "get").returns({ status: UIState.STATUS_SIGNED_IN });
--- 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/contextualidentity/test/browser/browser.ini
+++ b/browser/components/contextualidentity/test/browser/browser.ini
@@ -13,17 +13,16 @@ support-files =
 [browser_forgetaboutsite.js]
 [browser_forgetAPI_cookie_getCookiesWithOriginAttributes.js]
 [browser_restore_getCookiesWithOriginAttributes.js]
 [browser_forgetAPI_EME_forgetThisSite.js]
 [browser_forgetAPI_quota_clearStoragesForPrincipal.js]
 skip-if = verify
 [browser_newtabButton.js]
 [browser_usercontext.js]
-uses-unsafe-cpows = true
 [browser_usercontextid_tabdrop.js]
 skip-if = os == "mac" || os == "win" # Intermittent failure - bug 1268276
 [browser_windowName.js]
 tags = openwindow
 [browser_windowOpen.js]
 tags = openwindow
 [browser_serviceworkers.js]
 [browser_broadcastchannel.js]
--- 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']
--- a/browser/extensions/onboarding/test/browser/browser.ini
+++ b/browser/extensions/onboarding/test/browser/browser.ini
@@ -8,13 +8,11 @@ skip-if = debug || os == "mac" # Full ke
 [browser_onboarding_notification.js]
 [browser_onboarding_notification_2.js]
 [browser_onboarding_notification_3.js]
 [browser_onboarding_notification_4.js]
 [browser_onboarding_notification_5.js]
 [browser_onboarding_notification_click_auto_complete_tour.js]
 [browser_onboarding_select_default_tour.js]
 [browser_onboarding_skip_tour.js]
-uses-unsafe-cpows = true
 [browser_onboarding_tours.js]
 [browser_onboarding_tourset.js]
-uses-unsafe-cpows = true
 [browser_onboarding_uitour.js]
--- a/browser/extensions/screenshots/webextension/manifest.json
+++ b/browser/extensions/screenshots/webextension/manifest.json
@@ -31,17 +31,18 @@
     }
   ],
   "page_action": {
     "browser_style": true,
     "default_icon" : {
       "32": "icons/icon-v2.svg"
     },
     "default_title": "__MSG_contextMenuLabel__",
-    "show_matches": ["<all_urls>"]
+    "show_matches": ["<all_urls>"],
+    "pinned": false
   },
   "icons": {
     "32": "icons/icon-v2.svg"
   },
   "web_accessible_resources": [
     "blank.html",
     "icons/cancel.svg",
     "icons/download.svg",
--- a/browser/locales/en-US/chrome/browser/browser.dtd
+++ b/browser/locales/en-US/chrome/browser/browser.dtd
@@ -24,16 +24,18 @@
 <!ENTITY  reloadAllTabs.label                "Reload All Tabs">
 <!ENTITY  reloadAllTabs.accesskey            "A">
 <!ENTITY  selectAllTabs.label                "Select All Tabs">
 <!ENTITY  selectAllTabs.accesskey            "S">
 <!-- LOCALIZATION NOTE (duplicateTab.label): This is a command to duplicate
 a tab (i.e. it is a verb, not adjective). -->
 <!ENTITY  duplicateTab.label                 "Duplicate Tab">
 <!ENTITY  duplicateTab.accesskey             "D">
+<!ENTITY  closeTabOptions.label              "Close Tab Options">
+<!ENTITY  closeTabOptions.accesskey          "O">
 <!-- LOCALIZATION NOTE (closeTabsToTheEnd.label): This should indicate the
 direction in which tabs are closed, i.e. locales that use RTL mode should say
 left instead of right. -->
 <!ENTITY  closeTabsToTheEnd.label            "Close Tabs to the Right">
 <!ENTITY  closeTabsToTheEnd.accesskey        "i">
 <!ENTITY  closeOtherTabs.label               "Close Other Tabs">
 <!ENTITY  closeOtherTabs.accesskey           "o">
 
@@ -62,16 +64,26 @@ can reach it easily. -->
 <!ENTITY  pinTab.label                       "Pin Tab">
 <!ENTITY  pinTab.accesskey                   "P">
 <!ENTITY  unpinTab.label                     "Unpin Tab">
 <!ENTITY  unpinTab.accesskey                 "b">
 <!ENTITY  sendPageToDevice.label             "Send Page to Device">
 <!ENTITY  sendPageToDevice.accesskey         "n">
 <!ENTITY  sendLinkToDevice.label             "Send Link to Device">
 <!ENTITY  sendLinkToDevice.accesskey         "n">
+<!-- LOCALIZATION NOTE (moveTabOptions.label and moveSelectedTabOptions.label):
+These two items have the same accesskey but will never be visible at the same time. -->
+<!ENTITY  moveTabOptions.label               "Move Tab">
+<!ENTITY  moveTabOptions.accesskey           "v">
+<!ENTITY  moveSelectedTabOptions.label       "Move Tabs">
+<!ENTITY  moveSelectedTabOptions.accesskey   "v">
+<!ENTITY  moveToStart.label                  "Move to Start">
+<!ENTITY  moveToStart.accesskey              "S">
+<!ENTITY  moveToEnd.label                    "Move to End">
+<!ENTITY  moveToEnd.accesskey                "E">
 <!ENTITY  moveToNewWindow.label              "Move to New Window">
 <!ENTITY  moveToNewWindow.accesskey          "W">
 <!ENTITY  reopenInContainer.label            "Reopen in Container">
 <!ENTITY  reopenInContainer.accesskey        "e">
 <!ENTITY  bookmarkTab.label                  "Bookmark Tab">
 <!ENTITY  bookmarkTab.accesskey              "T">
 <!ENTITY  undoCloseTab.label                 "Undo Close Tab">
 <!ENTITY  undoCloseTab.accesskey             "U">
--- a/browser/modules/PageActions.jsm
+++ b/browser/modules/PageActions.jsm
@@ -1132,22 +1132,24 @@ var gBuiltInActions = [
     },
     onSubviewShowing(panelViewNode) {
       browserPageActions(panelViewNode).addSearchEngine
         .onSubviewShowing(panelViewNode);
     },
   },
 ];
 
+// send to device
 if (Services.prefs.getBoolPref("identity.fxaccounts.enabled")) {
   gBuiltInActions.push(
-  // send to device
   {
     id: "sendToDevice",
-    title: "sendToDevice-title",
+    // The actual title is set by each window, per window, and depends on the
+    // number of tabs that are selected.
+    title: "sendToDevice",
     onBeforePlacedInWindow(browserWindow) {
       browserPageActions(browserWindow).sendToDevice
         .onBeforePlacedInWindow(browserWindow);
     },
     onLocationChange(browserWindow) {
       browserPageActions(browserWindow).sendToDevice.onLocationChange();
     },
     wantsSubview: true,
@@ -1157,19 +1159,19 @@ if (Services.prefs.getBoolPref("identity
     },
     onSubviewShowing(panelViewNode) {
       browserPageActions(panelViewNode).sendToDevice
         .onShowingSubview(panelViewNode);
     },
   });
 }
 
+// share URL
 if (AppConstants.platform == "macosx") {
   gBuiltInActions.push(
-  // Share URL
   {
     id: "shareURL",
     title: "shareURL-title",
     onShowingInPanel(buttonNode) {
       browserPageActions(buttonNode).shareURL.onShowingInPanel(buttonNode);
     },
     onBeforePlacedInWindow(browserWindow) {
       browserPageActions(browserWindow).shareURL
--- a/devtools/client/aboutdebugging/test/browser_addons_debug_webextension_popup.js
+++ b/devtools/client/aboutdebugging/test/browser_addons_debug_webextension_popup.js
@@ -40,16 +40,19 @@ function makeWidgetId(id) {
   id = id.toLowerCase();
   return id.replace(/[^a-z0-9_-]/g, "_");
 }
 
 add_task(async function testWebExtensionsToolboxSwitchToPopup() {
   let onReadyForOpenPopup;
   let onPopupCustomMessage;
 
+  is(Services.prefs.getBoolPref("ui.popup.disable_autohide"), false,
+     "disable_autohide shoult be initially false");
+
   Management.on("startup", function listener(event, extension) {
     if (extension.name != ADDON_NAME) {
       return;
     }
 
     Management.off("startup", listener);
 
     function waitForExtensionTestMessage(expectedMessage) {
@@ -201,13 +204,16 @@ add_task(async function testWebExtension
   const args = await onPopupCustomMessage;
   ok(true, "Received console message from the popup page function as expected");
   is(args[0], "popupPageFunctionCalled", "Got the expected console message");
   is(args[1] && args[1].name, ADDON_NAME,
      "Got the expected manifest from WebExtension API");
 
   await onToolboxClose;
 
-  ok(true, "Addon toolbox closed");
+  info("Addon toolbox closed");
+
+  is(Services.prefs.getBoolPref("ui.popup.disable_autohide"), false,
+     "disable_autohide should be reset to false when the toolbox is closed");
 
   await uninstallAddon({document, id: ADDON_ID, name: ADDON_NAME});
   await closeAboutDebugging(tab);
 });
--- a/devtools/client/debugger/test/mochitest/browser.ini
+++ b/devtools/client/debugger/test/mochitest/browser.ini
@@ -114,130 +114,101 @@ support-files =
   head.js
   sjs_post-page.sjs
   sjs_random-javascript.sjs
   testactors.js
   !/devtools/client/shared/test/shared-head.js
   !/devtools/client/shared/test/telemetry-test-helpers.js
 
 [browser_dbg_aaa_run_first_leaktest.js]
-uses-unsafe-cpows = true
 skip-if = e10s && debug
 [browser_dbg_addon-modules.js]
 skip-if = e10s # TODO
 tags = addons
 [browser_dbg_addon-modules-unpacked.js]
 skip-if = e10s # TODO
 tags = addons
 [browser_dbg_addon-console.js]
 skip-if = e10s && debug || os == 'win' || verify # bug 1005274
 tags = addons
 [browser_dbg_bfcache.js]
 skip-if = e10s || true # bug 1113935
 [browser_dbg_break-in-anon.js]
-uses-unsafe-cpows = true
 [browser_dbg_break-on-next.js]
 skip-if = true # Bug 1437712
 [browser_dbg_break-on-next-console.js]
 uses-unsafe-cpows = true
 [browser_dbg_break-unselected.js]
 [browser_dbg_breakpoints-editor.js]
-uses-unsafe-cpows = true
 skip-if = e10s && debug
 [browser_dbg_breakpoints-eval.js]
-uses-unsafe-cpows = true
 skip-if = e10s && debug
 [browser_dbg_breakpoints-new-script.js]
-uses-unsafe-cpows = true
 skip-if = e10s && debug
 [browser_dbg_breakpoints-other-tabs.js]
-uses-unsafe-cpows = true
 skip-if = e10s && debug
 [browser_dbg_bug-896139.js]
-uses-unsafe-cpows = true
 skip-if = e10s && debug
 [browser_dbg_chrome-create.js]
 skip-if = (e10s && debug) || (verify && os == "linux") # Exit code mismatch with verify
 [browser_dbg_chrome-debugging.js]
 skip-if = e10s && debug
 [browser_dbg_clean-exit.js]
 skip-if = true # Bug 1044985 (racy test)
 [browser_dbg_closure-inspection.js]
-uses-unsafe-cpows = true
 skip-if = e10s && debug
 [browser_dbg_conditional-breakpoints-01.js]
-uses-unsafe-cpows = true
 skip-if = e10s && debug
 [browser_dbg_conditional-breakpoints-02.js]
-uses-unsafe-cpows = true
 skip-if = e10s && debug
 [browser_dbg_conditional-breakpoints-03.js]
-uses-unsafe-cpows = true
 skip-if = e10s && debug
 [browser_dbg_conditional-breakpoints-04.js]
-uses-unsafe-cpows = true
 skip-if = e10s && debug
 [browser_dbg_conditional-breakpoints-05.js]
-uses-unsafe-cpows = true
 skip-if = e10s && debug
 [browser_dbg_console-eval.js]
-uses-unsafe-cpows = true
 skip-if = e10s && debug
 [browser_dbg_console-named-eval.js]
-uses-unsafe-cpows = true
 skip-if = e10s && debug
 [browser_dbg_server-conditional-bp-01.js]
-uses-unsafe-cpows = true
 skip-if = e10s && debug || (os == "linux") # Bug 1468669
 [browser_dbg_server-conditional-bp-02.js]
-uses-unsafe-cpows = true
 skip-if = e10s && debug
 [browser_dbg_server-conditional-bp-03.js]
-uses-unsafe-cpows = true
 skip-if = e10s && debug
 [browser_dbg_server-conditional-bp-04.js]
-uses-unsafe-cpows = true
 skip-if = e10s && debug
 [browser_dbg_server-conditional-bp-05.js]
-uses-unsafe-cpows = true
 skip-if = e10s && debug
 [browser_dbg_controller-evaluate-01.js]
-uses-unsafe-cpows = true
 skip-if = e10s && debug
 [browser_dbg_controller-evaluate-02.js]
-uses-unsafe-cpows = true
 skip-if = e10s && debug
 [browser_dbg_debugger-statement.js]
 skip-if = e10s && debug
 [browser_dbg_event-listeners-01.js]
-uses-unsafe-cpows = true
 skip-if = e10s && debug
 [browser_dbg_event-listeners-02.js]
-uses-unsafe-cpows = true
 skip-if = e10s && debug
 [browser_dbg_event-listeners-03.js]
 skip-if = e10s && debug
 [browser_dbg_file-reload.js]
-uses-unsafe-cpows = true
 skip-if = e10s && debug
 [browser_dbg_global-method-override.js]
-uses-unsafe-cpows = true
 skip-if = e10s && debug
 [browser_dbg_globalactor.js]
 skip-if = e10s
 [browser_dbg_host-layout.js]
-uses-unsafe-cpows = true
 skip-if = e10s && debug
 [browser_dbg_jump-to-function-definition.js]
-uses-unsafe-cpows = true
 skip-if = e10s && debug
 [browser_dbg_iframes.js]
 skip-if = e10s # TODO
 [browser_dbg_interrupts.js]
-uses-unsafe-cpows = true
 skip-if = e10s && debug
 [browser_dbg_listaddons.js]
 skip-if = e10s && debug
 tags = addons
 [browser_dbg_listtabs-01.js]
 [browser_dbg_listtabs-02.js]
 skip-if = true # Never worked for remote frames, needs a mock DebuggerServerConnection
 [browser_dbg_listtabs-03.js]
--- a/devtools/client/framework/toolbox.js
+++ b/devtools/client/framework/toolbox.js
@@ -2260,26 +2260,34 @@ Toolbox.prototype = {
   },
 
   /**
    * Returns an instance of the preference actor. This is a lazily initialized root
    * actor that persists preferences to the debuggee, instead of just to the DevTools
    * client. See the definition of the preference actor for more information.
    */
   get preferenceFront() {
-    return this.target.client.mainRoot.getFront("preference");
+    const frontPromise = this.target.client.mainRoot.getFront("preference");
+    frontPromise.then(front => {
+      // Set the _preferenceFront property to allow the resetPreferences toolbox method
+      // to cleanup the preference set when the toolbox is closed.
+      this._preferenceFront = front;
+    });
+
+    return frontPromise;
   },
 
   // Is the disable auto-hide of pop-ups feature available in this context?
   get disableAutohideAvailable() {
     return this._target.chrome;
   },
 
   async toggleNoAutohide() {
     const front = await this.preferenceFront;
+
     const toggledValue = !(await this._isDisableAutohideEnabled());
 
     front.setBoolPref(DISABLE_AUTOHIDE_PREF, toggledValue);
 
     if (this.disableAutohideAvailable) {
       this.component.setDisableAutohide(toggledValue);
     }
     this._autohideHasBeenToggled = true;
--- a/dom/base/DOMPrefsInternal.h
+++ b/dom/base/DOMPrefsInternal.h
@@ -15,16 +15,17 @@
 //     This is allows the use of DOMPrefs in WebIDL files.
 
 DOM_WEBIDL_PREF(canvas_imagebitmap_extensions_enabled)
 DOM_WEBIDL_PREF(dom_caches_enabled)
 DOM_WEBIDL_PREF(dom_webnotifications_serviceworker_enabled)
 DOM_WEBIDL_PREF(dom_webnotifications_requireinteraction_enabled)
 DOM_WEBIDL_PREF(dom_serviceWorkers_enabled)
 DOM_WEBIDL_PREF(dom_storageManager_enabled)
+DOM_WEBIDL_PREF(dom_testing_structuredclonetester_enabled)
 DOM_WEBIDL_PREF(dom_promise_rejection_events_enabled)
 DOM_WEBIDL_PREF(dom_push_enabled)
 DOM_WEBIDL_PREF(gfx_offscreencanvas_enabled)
 DOM_WEBIDL_PREF(dom_webkitBlink_dirPicker_enabled)
 DOM_WEBIDL_PREF(dom_netinfo_enabled)
 DOM_WEBIDL_PREF(dom_fetchObserver_enabled)
 DOM_WEBIDL_PREF(dom_enable_performance_observer)
 DOM_WEBIDL_PREF(dom_performance_enable_scheduler_timing)
--- a/dom/base/StructuredCloneHolder.cpp
+++ b/dom/base/StructuredCloneHolder.cpp
@@ -8,31 +8,34 @@
 
 #include "ImageContainer.h"
 #include "mozilla/AutoRestore.h"
 #include "mozilla/dom/BlobBinding.h"
 #include "mozilla/dom/CryptoKey.h"
 #include "mozilla/dom/StructuredCloneBlob.h"
 #include "mozilla/dom/Directory.h"
 #include "mozilla/dom/DirectoryBinding.h"
+#include "mozilla/dom/DOMPrefs.h"
 #include "mozilla/dom/File.h"
 #include "mozilla/dom/FileList.h"
 #include "mozilla/dom/FileListBinding.h"
 #include "mozilla/dom/FormData.h"
 #include "mozilla/dom/ImageBitmap.h"
 #include "mozilla/dom/ImageBitmapBinding.h"
 #include "mozilla/dom/ImageData.h"
 #include "mozilla/dom/ImageDataBinding.h"
 #include "mozilla/dom/StructuredClone.h"
 #include "mozilla/dom/MessagePort.h"
 #include "mozilla/dom/MessagePortBinding.h"
 #include "mozilla/dom/OffscreenCanvas.h"
 #include "mozilla/dom/OffscreenCanvasBinding.h"
 #include "mozilla/dom/PMessagePort.h"
 #include "mozilla/dom/StructuredCloneTags.h"
+#include "mozilla/dom/StructuredCloneTester.h"
+#include "mozilla/dom/StructuredCloneTesterBinding.h"
 #include "mozilla/dom/SubtleCryptoBinding.h"
 #include "mozilla/dom/ToJSValue.h"
 #include "mozilla/dom/URLSearchParams.h"
 #include "mozilla/dom/URLSearchParamsBinding.h"
 #include "mozilla/dom/WebCryptoCommon.h"
 #include "mozilla/gfx/2D.h"
 #include "mozilla/ipc/BackgroundChild.h"
 #include "mozilla/ipc/BackgroundUtils.h"
@@ -450,16 +453,20 @@ StructuredCloneHolder::ReadFullySerializ
       } else {
         result = cert->WrapObject(aCx, nullptr);
       }
     }
     return result;
   }
 #endif
 
+  if (aTag == SCTAG_DOM_STRUCTURED_CLONE_TESTER) {
+    return StructuredCloneTester::ReadStructuredClone(aCx, aReader);
+  }
+
   // Don't know what this is. Bail.
   xpc::Throw(aCx, NS_ERROR_DOM_DATA_CLONE_ERR);
   return nullptr;
 }
 
 /* static */ bool
 StructuredCloneHolder::WriteFullySerializableObjects(JSContext* aCx,
                                                      JSStructuredCloneWriter* aWriter,
@@ -500,16 +507,32 @@ StructuredCloneHolder::WriteFullySeriali
     if (NS_SUCCEEDED(UNWRAP_OBJECT(RTCCertificate, &obj, cert))) {
       MOZ_ASSERT(NS_IsMainThread());
       return JS_WriteUint32Pair(aWriter, SCTAG_DOM_RTC_CERTIFICATE, 0) &&
              cert->WriteStructuredClone(aWriter);
     }
   }
 #endif
 
+  // StructuredCloneTester - testing only
+  {
+    StructuredCloneTester* sct = nullptr;
+    if (NS_SUCCEEDED(UNWRAP_OBJECT(StructuredCloneTester, &obj, sct))) {
+      MOZ_ASSERT(StaticPrefs::dom_testing_structuredclonetester_enabled());
+
+      // "Fail" serialization
+      if (!sct->Serializable()) {
+        xpc::Throw(aCx, NS_ERROR_DOM_DATA_CLONE_ERR);
+        return false;
+      }
+
+      return sct->WriteStructuredClone(aWriter);
+    }
+  }
+
   if (NS_IsMainThread() && xpc::IsReflector(obj)) {
     nsCOMPtr<nsISupports> base = xpc::UnwrapReflectorToISupports(obj);
     nsCOMPtr<nsIPrincipal> principal = do_QueryInterface(base);
     if (principal) {
       auto nsjsprincipals = nsJSPrincipals::get(principal);
       return nsjsprincipals->write(aCx, aWriter);
     }
   }
--- a/dom/base/StructuredCloneTags.h
+++ b/dom/base/StructuredCloneTags.h
@@ -65,15 +65,17 @@ enum StructuredCloneTags {
   SCTAG_DOM_STRUCTURED_CLONE_HOLDER,
 
   // When adding a new tag for IDB, please don't add it to the end of the list!
   // Tags that are supported by IDB must not ever change. See the static assert
   // in IDBObjectStore.cpp, method CommonStructuredCloneReadCallback.
   // Adding to the end of the list would make removing of other tags harder in
   // future.
 
-  SCTAG_DOM_MAX
+  SCTAG_DOM_MAX,
+
+  SCTAG_DOM_STRUCTURED_CLONE_TESTER
 };
 
 } // namespace dom
 } // namespace mozilla
 
 #endif // StructuredCloneTags_h__
new file mode 100644
--- /dev/null
+++ b/dom/base/StructuredCloneTester.cpp
@@ -0,0 +1,121 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/. */
+
+#include "StructuredCloneTester.h"
+
+#include "js/StructuredClone.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/dom/StructuredCloneTags.h"
+#include "mozilla/dom/StructuredCloneTesterBinding.h"
+#include "xpcpublic.h"
+
+namespace mozilla {
+
+class ErrorResult;
+
+namespace dom {
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_0(StructuredCloneTester)
+NS_IMPL_CYCLE_COLLECTING_ADDREF(StructuredCloneTester)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(StructuredCloneTester)
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(StructuredCloneTester)
+  NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+  NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+StructuredCloneTester::StructuredCloneTester(nsISupports* aParent,
+                                             const bool aSerializable,
+                                             const bool aDeserializable)
+  : mParent(aParent),
+    mSerializable(aSerializable),
+    mDeserializable(aDeserializable)
+{
+}
+
+/* static */ already_AddRefed<StructuredCloneTester>
+StructuredCloneTester::Constructor(const GlobalObject& aGlobal,
+                                   const bool aSerializable,
+                                   const bool aDeserializable,
+                                   ErrorResult& aRv)
+{
+  RefPtr<StructuredCloneTester> sct = new StructuredCloneTester(aGlobal.GetAsSupports(),
+                                                                aSerializable,
+                                                                aDeserializable);
+  return sct.forget();
+}
+
+bool
+StructuredCloneTester::Serializable() const
+{
+  return mSerializable;
+}
+
+bool
+StructuredCloneTester::Deserializable() const
+{
+  return mDeserializable;
+}
+
+/* static */ JSObject*
+StructuredCloneTester::ReadStructuredClone(JSContext* aCx,
+                                           JSStructuredCloneReader* aReader)
+{
+  uint32_t serializable = 0;
+  uint32_t deserializable = 0;
+
+  if (!JS_ReadUint32Pair(aReader, &serializable, &deserializable)) {
+    return nullptr;
+  }
+
+  nsIGlobalObject* global = xpc::CurrentNativeGlobal(aCx);
+
+  if (NS_WARN_IF(!global)) {
+    return nullptr;
+  }
+
+  // Prevent the return value from being trashed by a GC during ~nsRefPtr
+  JS::RootedObject result(aCx);
+
+  RefPtr<StructuredCloneTester> sct = new StructuredCloneTester(
+    global,
+    static_cast<bool>(serializable),
+    static_cast<bool>(deserializable)
+  );
+
+  // "Fail" deserialization
+  if (!sct->Deserializable()) {
+    xpc::Throw(aCx, NS_ERROR_DOM_DATA_CLONE_ERR);
+    return nullptr;
+  }
+
+  result = sct->WrapObject(aCx, nullptr);
+
+  return result;
+}
+
+bool
+StructuredCloneTester::WriteStructuredClone(JSStructuredCloneWriter* aWriter) const
+{
+  return JS_WriteUint32Pair(aWriter, SCTAG_DOM_STRUCTURED_CLONE_TESTER, 0) &&
+         JS_WriteUint32Pair(aWriter, static_cast<uint32_t>(Serializable()),
+                            static_cast<uint32_t>(Deserializable()));
+}
+
+nsISupports*
+StructuredCloneTester::GetParentObject() const
+{
+  return mParent;
+}
+
+JSObject*
+StructuredCloneTester::WrapObject(JSContext* aCx,
+                                  JS::Handle<JSObject*> aGivenProto)
+{
+  return StructuredCloneTester_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+} // namespace dom
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/base/StructuredCloneTester.h
@@ -0,0 +1,70 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/. */
+
+#ifndef mozilla_dom_StructuredCloneTester_h
+#define mozilla_dom_StructuredCloneTester_h
+
+#include "mozilla/AlreadyAddRefed.h"
+#include "nsCOMPtr.h"
+#include "nsISupports.h"
+#include "nsWrapperCache.h"
+
+struct JSStructuredCloneReader;
+struct JSStructuredCloneWriter;
+
+namespace mozilla {
+
+class ErrorResult;
+
+namespace dom {
+
+class GlobalObject;
+
+class StructuredCloneTester final : public nsISupports
+                                  , public nsWrapperCache
+{
+public:
+  NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+  NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(StructuredCloneTester)
+
+  static already_AddRefed<StructuredCloneTester>
+  Constructor(const GlobalObject& aGlobal, const bool aSerializable,
+              const bool aDeserializable, ErrorResult& aRv);
+
+  bool
+  Serializable() const;
+
+  bool
+  Deserializable() const;
+
+  static JSObject*
+  ReadStructuredClone(JSContext* aCx, JSStructuredCloneReader* aReader);
+
+  bool
+  WriteStructuredClone(JSStructuredCloneWriter* aWriter) const;
+
+  nsISupports*
+  GetParentObject() const;
+
+  // nsWrapperCache interface
+  JSObject*
+  WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
+
+private:
+  StructuredCloneTester(nsISupports* aParent,
+                        const bool aSerializable,
+                        const bool aDeserializable);
+  ~StructuredCloneTester() = default;
+
+  nsCOMPtr<nsISupports> mParent;
+  bool mSerializable;
+  bool mDeserializable;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_StructuredCloneTester_h
--- a/dom/base/moz.build
+++ b/dom/base/moz.build
@@ -215,16 +215,17 @@ EXPORTS.mozilla.dom += [
     'SameProcessMessageQueue.h',
     'ScreenLuminance.h',
     'ScreenOrientation.h',
     'Selection.h',
     'ShadowRoot.h',
     'StructuredCloneBlob.h',
     'StructuredCloneHolder.h',
     'StructuredCloneTags.h',
+    'StructuredCloneTester.h',
     'StyleSheetList.h',
     'SubtleCrypto.h',
     'SyncMessageSender.h',
     'TabGroup.h',
     'Text.h',
     'Timeout.h',
     'TimeoutHandler.h',
     'TimeoutManager.h',
@@ -370,16 +371,17 @@ UNIFIED_SOURCES += [
     'SameProcessMessageQueue.cpp',
     'ScreenLuminance.cpp',
     'ScreenOrientation.cpp',
     'Selection.cpp',
     'SelectionChangeEventDispatcher.cpp',
     'ShadowRoot.cpp',
     'StructuredCloneBlob.cpp',
     'StructuredCloneHolder.cpp',
+    'StructuredCloneTester.cpp',
     'StyleSheetList.cpp',
     'SubtleCrypto.cpp',
     'TabGroup.cpp',
     'Text.cpp',
     'TextInputProcessor.cpp',
     'ThirdPartyUtil.cpp',
     'Timeout.cpp',
     'TimeoutBudgetManager.cpp',
new file mode 100644
--- /dev/null
+++ b/dom/webidl/StructuredCloneTester.webidl
@@ -0,0 +1,15 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/.
+ */
+
+// The WebIDL compiler does not accept a Pref-ed interface exposed to any scopes
+// other than *only* `Window`, so the Func is Pref-ed instead.
+[Constructor(boolean serializable, boolean deserializable),
+ Exposed=(Window,Worker),
+ Func="mozilla::dom::DOMPrefs::dom_testing_structuredclonetester_enabled"]
+interface StructuredCloneTester {
+  readonly attribute boolean serializable;
+  readonly attribute boolean deserializable;
+};
--- a/dom/webidl/moz.build
+++ b/dom/webidl/moz.build
@@ -769,16 +769,17 @@ WEBIDL_FILES = [
     'SourceBufferList.webidl',
     'StereoPannerNode.webidl',
     'Storage.webidl',
     'StorageEvent.webidl',
     'StorageManager.webidl',
     'StorageType.webidl',
     'StreamFilter.webidl',
     'StreamFilterDataEvent.webidl',
+    'StructuredCloneTester.webidl',
     'StyleSheet.webidl',
     'StyleSheetList.webidl',
     'SubtleCrypto.webidl',
     'SVGAElement.webidl',
     'SVGAngle.webidl',
     'SVGAnimatedAngle.webidl',
     'SVGAnimatedBoolean.webidl',
     'SVGAnimatedEnumeration.webidl',
--- a/gfx/ipc/GPUParent.cpp
+++ b/gfx/ipc/GPUParent.cpp
@@ -41,17 +41,16 @@
 #include "nsDebugImpl.h"
 #include "nsIGfxInfo.h"
 #include "nsThreadManager.h"
 #include "prenv.h"
 #include "ProcessUtils.h"
 #include "VRGPUChild.h"
 #include "VRManager.h"
 #include "VRManagerParent.h"
-#include "VRThread.h"
 #include "VsyncBridgeParent.h"
 #if defined(XP_WIN)
 # include "mozilla/gfx/DeviceManagerDx.h"
 # include <process.h>
 # include <dwrite.h>
 #endif
 #ifdef MOZ_WIDGET_GTK
 # include <gtk/gtk.h>
@@ -129,18 +128,16 @@ GPUParent::Init(base::ProcessId aParentP
   DeviceManagerDx::Init();
 #endif
 
   if (NS_FAILED(NS_InitMinimalXPCOM())) {
     return false;
   }
 
   CompositorThreadHolder::Start();
-  // TODO: Bug 1406327, Start VRListenerThreadHolder when loading VR content.
-  VRListenerThreadHolder::Start();
   APZThreadUtils::SetControllerThread(MessageLoop::current());
   apz::InitializeGlobalState();
   LayerTreeOwnerTracker::Initialize();
   mozilla::ipc::SetThisProcessName("GPU Process");
 #ifdef XP_WIN
   wmf::MFStartup();
 #endif
   return true;
@@ -528,17 +525,16 @@ GPUParent::ActorDestroy(ActorDestroyReas
 #endif
 
   if (mVsyncBridge) {
     mVsyncBridge->Shutdown();
     mVsyncBridge = nullptr;
   }
   dom::VideoDecoderManagerParent::ShutdownVideoBridge();
   CompositorThreadHolder::Shutdown();
-  VRListenerThreadHolder::Shutdown();
   // There is a case that RenderThread exists when gfxVars::UseWebRender() is false.
   // This could happen when WebRender was fallbacked to compositor.
   if (wr::RenderThread::Get()) {
     wr::RenderThread::ShutDown();
   }
 
   image::ImageMemoryReporter::ShutdownForWebRender();
 
--- a/gfx/layers/ipc/CompositorVsyncScheduler.cpp
+++ b/gfx/layers/ipc/CompositorVsyncScheduler.cpp
@@ -30,17 +30,16 @@
 #include "nsThreadUtils.h"              // for NS_IsMainThread
 #include "mozilla/Telemetry.h"
 #include "mozilla/VsyncDispatcher.h"
 #if defined(XP_WIN) || defined(MOZ_WIDGET_GTK)
 #include "VsyncSource.h"
 #endif
 #include "mozilla/widget/CompositorWidget.h"
 #include "VRManager.h"
-#include "VRThread.h"
 
 namespace mozilla {
 
 namespace layers {
 
 using namespace mozilla::gfx;
 using namespace std;
 
@@ -77,18 +76,18 @@ CompositorVsyncScheduler::CompositorVsyn
   : mVsyncSchedulerOwner(aVsyncSchedulerOwner)
   , mLastCompose(TimeStamp::Now())
   , mIsObservingVsync(false)
   , mNeedsComposite(0)
   , mVsyncNotificationsSkipped(0)
   , mWidget(aWidget)
   , mCurrentCompositeTaskMonitor("CurrentCompositeTaskMonitor")
   , mCurrentCompositeTask(nullptr)
-  , mCurrentVRListenerTaskMonitor("CurrentVRTaskMonitor")
-  , mCurrentVRListenerTask(nullptr)
+  , mCurrentVRTaskMonitor("CurrentVRTaskMonitor")
+  , mCurrentVRTask(nullptr)
 {
   mVsyncObserver = new Observer(this);
 
   // mAsapScheduling is set on the main thread during init,
   // but is only accessed after on the compositor thread.
   mAsapScheduling = gfxPrefs::LayersCompositionFrameRate() == 0 ||
                     gfxPlatform::IsInLayoutAsapMode() ||
                     recordreplay::IsRecordingOrReplaying();
@@ -132,25 +131,25 @@ CompositorVsyncScheduler::PostCompositeT
     mCurrentCompositeTask = task;
     ScheduleTask(task.forget());
   }
 }
 
 void
 CompositorVsyncScheduler::PostVRTask(TimeStamp aTimestamp)
 {
-  MonitorAutoLock lockVR(mCurrentVRListenerTaskMonitor);
-  if (mCurrentVRListenerTask == nullptr && VRListenerThreadHolder::Loop()) {
+  MonitorAutoLock lockVR(mCurrentVRTaskMonitor);
+  if (mCurrentVRTask == nullptr && CompositorThreadHolder::Loop()) {
     RefPtr<Runnable> task = NewRunnableMethod<TimeStamp>(
       "layers::CompositorVsyncScheduler::DispatchVREvents",
       this,
       &CompositorVsyncScheduler::DispatchVREvents,
       aTimestamp);
-    mCurrentVRListenerTask = task;
-    VRListenerThreadHolder::Loop()->PostDelayedTask(task.forget(), 0);
+    mCurrentVRTask = task;
+    CompositorThreadHolder::Loop()->PostDelayedTask(task.forget(), 0);
   }
 }
 
 void
 CompositorVsyncScheduler::ScheduleComposition()
 {
   MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread());
   if (!mVsyncObserver) {
@@ -315,23 +314,23 @@ CompositorVsyncScheduler::UnobserveVsync
   mWidget->ObserveVsync(nullptr);
   mIsObservingVsync = false;
 }
 
 void
 CompositorVsyncScheduler::DispatchVREvents(TimeStamp aVsyncTimestamp)
 {
   {
-    MonitorAutoLock lock(mCurrentVRListenerTaskMonitor);
-    mCurrentVRListenerTask = nullptr;
+    MonitorAutoLock lock(mCurrentVRTaskMonitor);
+    mCurrentVRTask = nullptr;
   }
   // This only allows to be called by CompositorVsyncScheduler::PostVRTask()
   // When the process is going to shutdown, the runnable has chance to be executed
-  // by other threads, we only want it to be run at VRListenerThread.
-  if (!VRListenerThreadHolder::IsInVRListenerThread()) {
+  // by other threads, we only want it to be run in the compositor thread.
+  if (!CompositorThreadHolder::IsInCompositorThread()) {
     return;
   }
 
   VRManager* vm = VRManager::Get();
   vm->NotifyVsync(aVsyncTimestamp);
 }
 
 void
--- a/gfx/layers/ipc/CompositorVsyncScheduler.h
+++ b/gfx/layers/ipc/CompositorVsyncScheduler.h
@@ -144,16 +144,16 @@ private:
   uint32_t mNeedsComposite;
   int32_t mVsyncNotificationsSkipped;
   widget::CompositorWidget* mWidget;
   RefPtr<CompositorVsyncScheduler::Observer> mVsyncObserver;
 
   mozilla::Monitor mCurrentCompositeTaskMonitor;
   RefPtr<CancelableRunnable> mCurrentCompositeTask;
 
-  mozilla::Monitor mCurrentVRListenerTaskMonitor;
-  RefPtr<Runnable> mCurrentVRListenerTask;
+  mozilla::Monitor mCurrentVRTaskMonitor;
+  RefPtr<Runnable> mCurrentVRTask;
 };
 
 } // namespace layers
 } // namespace mozilla
 
 #endif // mozilla_layers_CompositorVsyncScheduler_h
--- a/gfx/thebes/gfxPlatform.cpp
+++ b/gfx/thebes/gfxPlatform.cpp
@@ -1165,17 +1165,16 @@ gfxPlatform::InitLayersIPC()
 
   if (XRE_IsParentProcess() || recordreplay::IsRecordingOrReplaying()) {
     if (!gfxConfig::IsEnabled(Feature::GPU_PROCESS) && gfxVars::UseWebRender()) {
       wr::RenderThread::Start();
       image::ImageMemoryReporter::InitForWebRender();
     }
 
     layers::CompositorThreadHolder::Start();
-    gfx::VRListenerThreadHolder::Start();
   }
 }
 
 /* static */ void
 gfxPlatform::ShutdownLayersIPC()
 {
     if (!sLayersIPCIsUp) {
       return;
@@ -1194,17 +1193,16 @@ gfxPlatform::ShutdownLayersIPC()
           layers::PaintThread::Shutdown();
         }
     } else if (XRE_IsParentProcess()) {
         gfx::VRManagerChild::ShutDown();
         layers::CompositorManagerChild::Shutdown();
         layers::ImageBridgeChild::ShutDown();
         // This has to happen after shutting down the child protocols.
         layers::CompositorThreadHolder::Shutdown();
-        gfx::VRListenerThreadHolder::Shutdown();
         image::ImageMemoryReporter::ShutdownForWebRender();
         // There is a case that RenderThread exists when gfxVars::UseWebRender() is false.
         // This could happen when WebRender was fallbacked to compositor.
         if (wr::RenderThread::Get()) {
           wr::RenderThread::ShutDown();
 
           Preferences::UnregisterCallback(WebRenderDebugPrefChangeCallback, WR_DEBUG_PREF);
         }
--- a/gfx/vr/VRDisplayHost.cpp
+++ b/gfx/vr/VRDisplayHost.cpp
@@ -3,16 +3,17 @@
 /* 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/. */
 
 #include "VRDisplayHost.h"
 #include "gfxPrefs.h"
 #include "gfxVR.h"
 #include "ipc/VRLayerParent.h"
+#include "mozilla/layers/CompositorThread.h" // for CompositorThreadHolder
 #include "mozilla/layers/TextureHost.h"
 #include "mozilla/dom/GamepadBinding.h" // For GamepadMappingType
 #include "VRThread.h"
 
 #if defined(XP_WIN)
 
 #include <d3d11.h>
 #include "gfxWindowsPlatform.h"
@@ -337,17 +338,17 @@ VRDisplayHost::SubmitFrameInternal(const
    * the render loop, if we don't successfully call it, we shouldn't trigger
    * NotifyVRVsync immediately, as it will run unbounded.
    * If NotifyVRVsync is not called here due to SubmitFrame failing, the
    * fallback "watchdog" code in VRDisplayHost::NotifyVSync() will cause
    * frames to continue at a lower refresh rate until frame submission
    * succeeds again.
    */
   VRManager* vm = VRManager::Get();
-  MessageLoop* loop = VRListenerThreadHolder::Loop();
+  MessageLoop* loop = CompositorThreadHolder::Loop();
 
   loop->PostTask(NewRunnableMethod<const uint32_t>(
     "gfx::VRManager::NotifyVRVsync",
     vm, &VRManager::NotifyVRVsync, mDisplayInfo.mDisplayID
   ));
 #endif
 }
 
--- a/gfx/vr/VRManager.cpp
+++ b/gfx/vr/VRManager.cpp
@@ -258,82 +258,63 @@ VRManager::UpdateRequestedDevices()
  * VRManager::NotifyVsync must be called on every 2d vsync (usually at 60hz).
  * This must be called even when no WebVR site is active.
  * If we don't have a 2d display attached to the system, we can call this
  * at the VR display's native refresh rate.
  **/
 void
 VRManager::NotifyVsync(const TimeStamp& aVsyncTimestamp)
 {
-  MOZ_ASSERT(VRListenerThreadHolder::IsInVRListenerThread());
-
   for (const auto& manager : mManagers) {
     manager->NotifyVSync();
   }
 }
 
 void
 VRManager::StartTasks()
 {
-  MOZ_ASSERT(VRListenerThread());
   if (!mTaskTimer) {
     mTaskInterval = GetOptimalTaskInterval();
     mTaskTimer = NS_NewTimer();
-    mTaskTimer->SetTarget(VRListenerThreadHolder::Loop()->SerialEventTarget());
+    mTaskTimer->SetTarget(CompositorThreadHolder::Loop()->SerialEventTarget());
     mTaskTimer->InitWithNamedFuncCallback(
       TaskTimerCallback,
       this,
       mTaskInterval,
       nsITimer::TYPE_REPEATING_PRECISE_CAN_SKIP,
       "VRManager::TaskTimerCallback");
   }
 }
 
 void
 VRManager::StopTasks()
 {
   if (mTaskTimer) {
-    MOZ_ASSERT(VRListenerThread());
     mTaskTimer->Cancel();
     mTaskTimer = nullptr;
   }
 }
 
 /*static*/ void
-VRManager::StopVRListenerThreadTasks()
-{
-  if (sVRManagerSingleton) {
-    sVRManagerSingleton->StopTasks();
-  }
-}
-
-/*static*/ void
 VRManager::TaskTimerCallback(nsITimer* aTimer, void* aClosure)
 {
   /**
    * It is safe to use the pointer passed in aClosure to reference the
    * VRManager object as the timer is canceled in VRManager::Destroy.
    * VRManager::Destroy set mInitialized to false, which is asserted
    * in the VRManager destructor, guaranteeing that this functions
    * runs if and only if the VRManager object is valid.
    */
   VRManager* self = static_cast<VRManager*>(aClosure);
   self->RunTasks();
 }
 
 void
 VRManager::RunTasks()
 {
-  // During shutdown, this might be called outside of the
-  // VR Listener Thread; however, we no longer need to
-  // execute any tasks during this time.
-  if (!VRListenerThreadHolder::IsInVRListenerThread()) {
-    return;
-  }
-
   // Will be called once every 1ms when a VR presentation
   // is active or once per vsync when a VR presentation is
   // not active.
 
   TimeStamp now = TimeStamp::Now();
   double lastTickMs = mAccumulator100ms;
   double deltaTime = 0.0f;
   if (!mLastTickTime.IsNull()) {
@@ -392,18 +373,16 @@ VRManager::GetOptimalTaskInterval()
  * called more than once within 1ms.
  * When VR is not active, this will be
  * called once per VSync if it wasn't
  * called within the last 1ms.
  */
 void
 VRManager::Run1msTasks(double aDeltaTime)
 {
-  MOZ_ASSERT(VRListenerThreadHolder::IsInVRListenerThread());
-
   for (const auto& manager : mManagers) {
     manager->Run1msTasks(aDeltaTime);
   }
 
   for (auto iter = mVRDisplays.Iter(); !iter.Done(); iter.Next()) {
     gfx::VRDisplayHost* display = iter.UserData();
     display->Run1msTasks(aDeltaTime);
   }
@@ -414,18 +393,16 @@ VRManager::Run1msTasks(double aDeltaTime
  * called more than once within 10ms.
  * When VR is not active, this will be
  * called once per VSync if it wasn't
  * called within the last 10ms.
  */
 void
 VRManager::Run10msTasks()
 {
-  MOZ_ASSERT(VRListenerThreadHolder::IsInVRListenerThread());
-
   UpdateRequestedDevices();
 
   for (const auto& manager : mManagers) {
     manager->Run10msTasks();
   }
 
   for (auto iter = mVRDisplays.Iter(); !iter.Done(); iter.Next()) {
     gfx::VRDisplayHost* display = iter.UserData();
@@ -438,18 +415,16 @@ VRManager::Run10msTasks()
  * called more than once within 100ms.
  * When VR is not active, this will be
  * called once per VSync if it wasn't
  * called within the last 100ms.
  */
 void
 VRManager::Run100msTasks()
 {
-  MOZ_ASSERT(VRListenerThreadHolder::IsInVRListenerThread());
-
   // We must continually refresh the VR display enumeration to check
   // for events that we must fire such as Window.onvrdisplayconnect
   // Note that enumeration itself may activate display hardware, such
   // as Oculus, so we only do this when we know we are displaying content
   // that is looking for VR displays.
   RefreshVRDisplays();
 
   // Update state and enumeration of VR controllers
@@ -490,17 +465,16 @@ VRManager::CheckForInactiveTimeout()
       mLastDisplayEnumerationTime = TimeStamp();
     }
   }
 }
 
 void
 VRManager::NotifyVRVsync(const uint32_t& aDisplayID)
 {
-  MOZ_ASSERT(VRListenerThreadHolder::IsInVRListenerThread());
   for (const auto& manager: mManagers) {
     if (manager->GetIsPresenting()) {
       manager->HandleInput();
     }
   }
 
   RefPtr<VRDisplayHost> display = GetDisplay(aDisplayID);
   if (display) {
--- a/gfx/vr/VRManager.h
+++ b/gfx/vr/VRManager.h
@@ -58,17 +58,16 @@ public:
 
   void VibrateHaptic(uint32_t aControllerIdx, uint32_t aHapticIndex,
                      double aIntensity, double aDuration, const VRManagerPromise& aPromise);
   void StopVibrateHaptic(uint32_t aControllerIdx);
   void NotifyVibrateHapticCompleted(const VRManagerPromise& aPromise);
   void DispatchSubmitFrameResult(uint32_t aDisplayID, const VRSubmitFrameResultInfo& aResult);
   void StartVRNavigation(const uint32_t& aDisplayID);
   void StopVRNavigation(const uint32_t& aDisplayID, const TimeDuration& aTimeout);
-  static void StopVRListenerThreadTasks();
 
 protected:
   VRManager();
   ~VRManager();
 
 private:
 
   void Init();
--- a/gfx/vr/VRThread.cpp
+++ b/gfx/vr/VRThread.cpp
@@ -9,151 +9,35 @@
 #include "nsThreadManager.h"
 #include "nsThreadUtils.h"
 #include "VRManager.h"
 
 namespace mozilla {
 
 namespace gfx {
 
-static StaticRefPtr<VRListenerThreadHolder> sVRListenerThreadHolder;
-static bool sFinishedVRListenerShutDown = true;
 static const uint32_t kDefaultThreadLifeTime = 60; // in 60 seconds.
 static const uint32_t kDelayPostTaskTime = 20000; // in 20000 ms.
 
-VRListenerThreadHolder* GetVRListenerThreadHolder()
-{
-  return sVRListenerThreadHolder;
-}
-
-base::Thread*
-VRListenerThread()
-{
-  return sVRListenerThreadHolder
-         ? sVRListenerThreadHolder->GetThread()
-         : nullptr;
-}
-
-/* static */ MessageLoop*
-VRListenerThreadHolder::Loop()
-{
-  return VRListenerThread() ? VRListenerThread()->message_loop() : nullptr;
-}
-
-VRListenerThreadHolder*
-VRListenerThreadHolder::GetSingleton()
-{
-  return sVRListenerThreadHolder;
-}
-
-VRListenerThreadHolder::VRListenerThreadHolder()
- : mThread(CreateThread())
-{
-  MOZ_ASSERT(NS_IsMainThread());
-}
-
-VRListenerThreadHolder::~VRListenerThreadHolder()
-{
-  MOZ_ASSERT(NS_IsMainThread());
-
-  if (mThread) {
-    DestroyThread(mThread);
-  }
-}
-
-/* static */ void
-VRListenerThreadHolder::DestroyThread(base::Thread* aThread)
-{
-  MOZ_ASSERT(NS_IsMainThread());
-  MOZ_ASSERT(!sVRListenerThreadHolder,
-             "We shouldn't be destroying the VR listener thread yet.");
-  delete aThread;
-  sFinishedVRListenerShutDown = true;
-}
-
-/* static */ base::Thread*
-VRListenerThreadHolder::CreateThread()
-{
-  MOZ_ASSERT(NS_IsMainThread());
-  MOZ_ASSERT(!sVRListenerThreadHolder, "The VR listener thread has already been started!");
-
-  base::Thread* vrThread = new base::Thread("VRListener");
-  base::Thread::Options options;
-  /* Timeout values are powers-of-two to enable us get better data.
-     128ms is chosen for transient hangs because 8Hz should be the minimally
-     acceptable goal for Compositor responsiveness (normal goal is 60Hz). */
-  options.transient_hang_timeout = 128; // milliseconds
-  /* 2048ms is chosen for permanent hangs because it's longer than most
-   * Compositor hangs seen in the wild, but is short enough to not miss getting
-   * native hang stacks. */
-  options.permanent_hang_timeout = 2048; // milliseconds
-
-  if (!vrThread->StartWithOptions(options)) {
-    delete vrThread;
-    return nullptr;
-  }
-
-  return vrThread;
-}
-
-void
-VRListenerThreadHolder::Start()
-{
-  MOZ_ASSERT(NS_IsMainThread(), "Should be on the main thread!");
-  MOZ_ASSERT(!sVRListenerThreadHolder, "The VR listener thread has already been started!");
-  sFinishedVRListenerShutDown = false;
-  sVRListenerThreadHolder = new VRListenerThreadHolder();
-
-  if (!sVRListenerThreadHolder->GetThread()) {
-    MOZ_ASSERT(false, "VR listener thread not started.");
-    sVRListenerThreadHolder = nullptr;
-  }
-}
-
-void
-VRListenerThreadHolder::Shutdown()
-{
-  MOZ_ASSERT(NS_IsMainThread(), "Should be on the main thread!");
-  VRManager::StopVRListenerThreadTasks();
-
-  if (!sVRListenerThreadHolder) {
-    // We've already shutdown or never started.
-    return;
-  }
-
-  sVRListenerThreadHolder = nullptr;
-
-  SpinEventLoopUntil([&]() { return sFinishedVRListenerShutDown; });
-}
-
-/* static */ bool
-VRListenerThreadHolder::IsInVRListenerThread()
-{
-  return VRListenerThread() &&
-		 VRListenerThread()->thread_id() == PlatformThread::CurrentId();
-}
-
 VRThread::VRThread(const nsCString& aName)
  : mThread(nullptr)
  , mLifeTime(kDefaultThreadLifeTime)
  , mStarted(false)
 {
   mName = aName;
 }
 
 VRThread::~VRThread()
 {
   Shutdown();
 }
 
 void
 VRThread::Start()
 {
-  MOZ_ASSERT(VRListenerThreadHolder::IsInVRListenerThread());
-
   if (!mThread) {
     nsresult rv = NS_NewNamedThread(mName, getter_AddRefs(mThread));
     MOZ_ASSERT(mThread);
 
     if (NS_FAILED(rv)) {
       MOZ_ASSERT(false, "Failed to create a vr thread.");
     }
     RefPtr<Runnable> runnable =
--- a/gfx/vr/VRThread.h
+++ b/gfx/vr/VRThread.h
@@ -8,49 +8,16 @@
  #define GFX_VR_THREAD_H
 
 #include "ThreadSafeRefcountingWithMainThreadDestruction.h"
 #include "base/thread.h"                // for Thread
 
 namespace mozilla {
 namespace gfx {
 
-class VRListenerThreadHolder final
-{
-  NS_INLINE_DECL_THREADSAFE_REFCOUNTING_WITH_MAIN_THREAD_DESTRUCTION(VRListenerThreadHolder)
-
-public:
-  VRListenerThreadHolder();
-
-  base::Thread* GetThread() const {
-    return mThread;
-  }
-
-  static VRListenerThreadHolder* GetSingleton();
-
-  static bool IsActive() {
-    return GetSingleton() && Loop();
-  }
-
-  static void Start();
-  static void Shutdown();
-  static MessageLoop* Loop();
-  static bool IsInVRListenerThread();
-
-private:
-  ~VRListenerThreadHolder();
-
-  base::Thread* const mThread;
-
-  static base::Thread* CreateThread();
-  static void DestroyThread(base::Thread* aThread);
-};
-
-base::Thread* VRListenerThread();
-
 class VRThread final
 {
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(VRThread)
 
 public:
   explicit VRThread(const nsCString& aName);
 
   void Start();
--- a/gfx/vr/gfxVROculus.cpp
+++ b/gfx/vr/gfxVROculus.cpp
@@ -1487,17 +1487,17 @@ VRControllerOculus::VibrateHapticComplet
       printf_stderr("%s Haptics skipped.\n",
                     GamepadHandValues::strings[uint32_t(GetHand())].value);
     }
   }
 
   VRManager *vm = VRManager::Get();
   MOZ_ASSERT(vm);
 
-  VRListenerThreadHolder::Loop()->PostTask(
+  CompositorThreadHolder::Loop()->PostTask(
     NewRunnableMethod<StoreCopyPassByConstLRef<VRManagerPromise>>(
       "VRManager::NotifyVibrateHapticCompleted",
       vm, &VRManager::NotifyVibrateHapticCompleted, aPromise));
 }
 
 void
 VRControllerOculus::VibrateHaptic(ovrSession aSession,
                                   uint32_t aHapticIndex,
--- a/gfx/vr/gfxVROculus.h
+++ b/gfx/vr/gfxVROculus.h
@@ -67,17 +67,16 @@ private:
   RefPtr<VRThread> mSubmitThread;
   // The timestamp of the last time Oculus set ShouldQuit to true.
   TimeStamp mLastShouldQuit;
   // The timestamp of the last ending presentation
   TimeStamp mLastPresentationEnd;
   VRTelemetry mTelemetry;
   bool mRequestPresentation;
   bool mRequestTracking;
-  bool mTracking;
   bool mDrawBlack;
   bool mIsConnected;
   bool mIsMounted;
 
   ~VROculusSession();
   void Uninitialize();
   bool Initialize(ovrInitFlags aFlags);
   bool LoadOvrLib();
--- a/gfx/vr/gfxVROpenVR.cpp
+++ b/gfx/vr/gfxVROpenVR.cpp
@@ -541,17 +541,17 @@ VRControllerOpenVR::UpdateVibrateHaptic(
     VibrateHapticComplete(aPromise);
   }
 }
 
 void
 VRControllerOpenVR::VibrateHapticComplete(const VRManagerPromise& aPromise)
 {
   VRManager *vm = VRManager::Get();
-  VRListenerThreadHolder::Loop()->PostTask(
+  CompositorThreadHolder::Loop()->PostTask(
     NewRunnableMethod<StoreCopyPassByConstLRef<VRManagerPromise>>(
       "VRManager::NotifyVibrateHapticCompleted",
       vm, &VRManager::NotifyVibrateHapticCompleted, aPromise));
 }
 
 void
 VRControllerOpenVR::VibrateHaptic(::vr::IVRSystem* aVRSystem,
                                   uint32_t aHapticIndex,
--- a/gfx/vr/gfxVRPuppet.cpp
+++ b/gfx/vr/gfxVRPuppet.cpp
@@ -9,16 +9,17 @@
 #include "TextureD3D11.h"
 #include "mozilla/gfx/DeviceManagerDx.h"
 #elif defined(XP_MACOSX)
 #include "mozilla/gfx/MacIOSurface.h"
 #endif
 
 #include "mozilla/Base64.h"
 #include "mozilla/gfx/DataSurfaceHelpers.h"
+#include "mozilla/layers/CompositorThread.h" // for CompositorThreadHolder
 #include "gfxPrefs.h"
 #include "gfxUtils.h"
 #include "gfxVRPuppet.h"
 #include "VRManager.h"
 #include "VRThread.h"
 
 #include "mozilla/dom/GamepadEventTypes.h"
 #include "mozilla/dom/GamepadBinding.h"
@@ -389,17 +390,17 @@ VRDisplayPuppet::SubmitFrame(ID3D11Textu
       }
       mContext->Unmap(mappedTexture, 0);
 
       if (Base64Encode(rawString, result.mBase64Image) != NS_OK) {
         MOZ_ASSERT(false, "Failed to encode base64 images.");
       }
       // Dispatch the base64 encoded string to the DOM side. Then, it will be decoded
       // and convert to a PNG image there.
-      MessageLoop* loop = VRListenerThreadHolder::Loop();
+      MessageLoop* loop = CompositorThreadHolder::Loop();
       loop->PostTask(NewRunnableMethod<const uint32_t, VRSubmitFrameResultInfo>(
         "VRManager::DispatchSubmitFrameResult",
         vm, &VRManager::DispatchSubmitFrameResult, mDisplayInfo.mDisplayID, result
       ));
       break;
     }
     case 2:
     {
@@ -535,17 +536,17 @@ VRDisplayPuppet::SubmitFrame(MacIOSurfac
         }
         dataSurf->Unmap();
 
         if (Base64Encode(rawString, result.mBase64Image) != NS_OK) {
           MOZ_ASSERT(false, "Failed to encode base64 images.");
         }
         // Dispatch the base64 encoded string to the DOM side. Then, it will be decoded
         // and convert to a PNG image there.
-        MessageLoop* loop = VRListenerThreadHolder::Loop();
+        MessageLoop* loop = CompositorThreadHolder::Loop();
         loop->PostTask(NewRunnableMethod<const uint32_t, VRSubmitFrameResultInfo>(
           "VRManager::DispatchSubmitFrameResult",
           vm, &VRManager::DispatchSubmitFrameResult, mDisplayInfo.mDisplayID, result
         ));
       }
       break;
     }
     case 2:
--- a/gfx/vr/ipc/VRManagerChild.cpp
+++ b/gfx/vr/ipc/VRManagerChild.cpp
@@ -113,17 +113,17 @@ VRManagerChild::ReinitForContent(Endpoin
 VRManagerChild::InitSameProcess()
 {
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(!sVRManagerChildSingleton);
 
   sVRManagerChildSingleton = new VRManagerChild();
   sVRManagerParentSingleton = VRManagerParent::CreateSameProcess();
   sVRManagerChildSingleton->Open(sVRManagerParentSingleton->GetIPCChannel(),
-                                 VRListenerThreadHolder::Loop(),
+                                 CompositorThreadHolder::Loop(),
                                  mozilla::ipc::ChildSide);
 }
 
 /* static */ void
 VRManagerChild::InitWithGPUProcess(Endpoint<PVRManagerChild>&& aEndpoint)
 {
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(!sVRManagerChildSingleton);
--- a/gfx/vr/ipc/VRManagerParent.cpp
+++ b/gfx/vr/ipc/VRManagerParent.cpp
@@ -80,17 +80,17 @@ VRManagerParent::UnregisterFromManager()
   VRManager* vm = VRManager::Get();
   vm->RemoveVRManagerParent(this);
   mVRManagerHolder = nullptr;
 }
 
 /* static */ bool
 VRManagerParent::CreateForContent(Endpoint<PVRManagerParent>&& aEndpoint)
 {
-  MessageLoop* loop = VRListenerThreadHolder::Loop();
+  MessageLoop* loop = CompositorThreadHolder::Loop();
 
   RefPtr<VRManagerParent> vmp = new VRManagerParent(aEndpoint.OtherPid(), true);
   loop->PostTask(NewRunnableMethod<Endpoint<PVRManagerParent>&&>(
     "gfx::VRManagerParent::Bind",
     vmp,
     &VRManagerParent::Bind,
     std::move(aEndpoint)));
 
@@ -104,78 +104,75 @@ VRManagerParent::Bind(Endpoint<PVRManage
     return;
   }
   mSelfRef = this;
 
   RegisterWithManager();
 }
 
 /*static*/ void
-VRManagerParent::RegisterVRManagerInVRListenerThread(VRManagerParent* aVRManager)
+VRManagerParent::RegisterVRManagerInCompositorThread(VRManagerParent* aVRManager)
 {
   aVRManager->RegisterWithManager();
 }
 
 /*static*/ VRManagerParent*
 VRManagerParent::CreateSameProcess()
 {
-  MessageLoop* loop = VRListenerThreadHolder::Loop();
+  MessageLoop* loop = CompositorThreadHolder::Loop();
   RefPtr<VRManagerParent> vmp = new VRManagerParent(base::GetCurrentProcId(), false);
-  vmp->mVRListenerThreadHolder = VRListenerThreadHolder::GetSingleton();
+  vmp->mCompositorThreadHolder = CompositorThreadHolder::GetSingleton();
   vmp->mSelfRef = vmp;
-  loop->PostTask(NewRunnableFunction("RegisterVRManagerInVRListenerThreadRunnable",
-                                     RegisterVRManagerInVRListenerThread, vmp.get()));
+  loop->PostTask(NewRunnableFunction("RegisterVRManagerIncompositorThreadRunnable",
+                                     RegisterVRManagerInCompositorThread, vmp.get()));
   return vmp.get();
 }
 
 bool
 VRManagerParent::CreateForGPUProcess(Endpoint<PVRManagerParent>&& aEndpoint)
 {
-  MessageLoop* loop = VRListenerThreadHolder::Loop();
+  MessageLoop* loop = CompositorThreadHolder::Loop();
 
   RefPtr<VRManagerParent> vmp = new VRManagerParent(aEndpoint.OtherPid(), false);
-  vmp->mVRListenerThreadHolder = VRListenerThreadHolder::GetSingleton();
+  vmp->mCompositorThreadHolder = CompositorThreadHolder::GetSingleton();
   vmp->mSelfRef = vmp;
   loop->PostTask(NewRunnableMethod<Endpoint<PVRManagerParent>&&>(
     "gfx::VRManagerParent::Bind",
     vmp,
     &VRManagerParent::Bind,
     std::move(aEndpoint)));
   return true;
 }
 
 void
 VRManagerParent::DeferredDestroy()
 {
-  mVRListenerThreadHolder = nullptr;
+  mCompositorThreadHolder = nullptr;
   mSelfRef = nullptr;
 }
 
 void
 VRManagerParent::ActorDestroy(ActorDestroyReason why)
 {
   UnregisterFromManager();
   MessageLoop::current()->PostTask(
     NewRunnableMethod("gfx::VRManagerParent::DeferredDestroy",
                       this,
                       &VRManagerParent::DeferredDestroy));
 }
 
 void
 VRManagerParent::OnChannelConnected(int32_t aPid)
 {
-  mVRListenerThreadHolder = VRListenerThreadHolder::GetSingleton();
+  mCompositorThreadHolder = CompositorThreadHolder::GetSingleton();
 }
 
 mozilla::ipc::IPCResult
 VRManagerParent::RecvRefreshDisplays()
 {
-  // TODO: Bug 1406327, Launch VR listener thread here.
-  MOZ_ASSERT(VRListenerThreadHolder::IsInVRListenerThread());
-
   // This is called to refresh the VR Displays for Navigator.GetVRDevices().
   // We must pass "true" to VRManager::RefreshVRDisplays()
   // to ensure that the promise returned by Navigator.GetVRDevices
   // can resolve even if there are no changes to the VR Displays.
   VRManager* vm = VRManager::Get();
   vm->RefreshVRDisplays(true);
 
   return IPC_OK();
@@ -271,17 +268,17 @@ mozilla::ipc::IPCResult
 VRManagerParent::RecvCreateVRServiceTestController(const nsCString& aID, const uint32_t& aPromiseID)
 {
   uint32_t controllerIdx = 1; // ID's are 1 based
   nsTArray<VRControllerInfo> controllerInfoArray;
   impl::VRControllerPuppet* controllerPuppet = nullptr;
   VRManager* vm = VRManager::Get();
 
   /**
-   * The controller is created asynchronously in the VRListener thread.
+   * The controller is created asynchronously.
    * We will wait up to kMaxControllerCreationTime milliseconds before
    * assuming that the controller will never be created.
    */
   const int kMaxControllerCreationTime = 1000;
   /**
    * min(100ms, kVRIdleTaskInterval) * 10 as a very
    * pessimistic estimation of the maximum duration possible.
    * It's possible that the IPC message queues could be so busy
--- a/gfx/vr/ipc/VRManagerParent.h
+++ b/gfx/vr/ipc/VRManagerParent.h
@@ -2,23 +2,23 @@
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* 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/. */
 
 #ifndef MOZILLA_GFX_VR_VRMANAGERPARENT_H
 #define MOZILLA_GFX_VR_VRMANAGERPARENT_H
 
+#include "mozilla/layers/CompositorThread.h" // for CompositorThreadHolder
 #include "mozilla/layers/CompositableTransactionParent.h"  // need?
 #include "mozilla/gfx/PVRManagerParent.h" // for PVRManagerParent
 #include "mozilla/gfx/PVRLayerParent.h"   // for PVRLayerParent
 #include "mozilla/ipc/ProtocolUtils.h"    // for IToplevelProtocol
 #include "mozilla/TimeStamp.h"            // for TimeStamp
 #include "gfxVR.h"                        // for VRFieldOfView
-#include "VRThread.h"                     // for VRListenerThreadHolder
 
 namespace mozilla {
 using namespace layers;
 namespace gfx {
 
 class VRManager;
 
 namespace impl {
@@ -76,25 +76,26 @@ protected:
   virtual mozilla::ipc::IPCResult RecvStartVRNavigation(const uint32_t& aDeviceID) override;
   virtual mozilla::ipc::IPCResult RecvStopVRNavigation(const uint32_t& aDeviceID, const TimeDuration& aTimeout) override;
 private:
   void RegisterWithManager();
   void UnregisterFromManager();
 
   void Bind(Endpoint<PVRManagerParent>&& aEndpoint);
 
-  static void RegisterVRManagerInVRListenerThread(VRManagerParent* aVRManager);
+  static void RegisterVRManagerInCompositorThread(VRManagerParent* aVRManager);
 
   void DeferredDestroy();
   already_AddRefed<impl::VRControllerPuppet> GetControllerPuppet(uint32_t aDeviceID);
 
   // This keeps us alive until ActorDestroy(), at which point we do a
   // deferred destruction of ourselves.
   RefPtr<VRManagerParent> mSelfRef;
-  RefPtr<VRListenerThreadHolder> mVRListenerThreadHolder;
+  // Keep the compositor thread alive, until we have destroyed ourselves.
+  RefPtr<CompositorThreadHolder> mCompositorThreadHolder;
 
   // Keep the VRManager alive, until we have destroyed ourselves.
   RefPtr<VRManager> mVRManagerHolder;
   nsRefPtrHashtable<nsUint32HashKey, impl::VRControllerPuppet> mVRControllerTests;
   uint32_t mControllerTestID;
   bool mHaveEventListener;
   bool mHaveControllerListener;
   bool mIsContentChild;
--- a/js/xpconnect/loader/XPCOMUtils.jsm
+++ b/js/xpconnect/loader/XPCOMUtils.jsm
@@ -54,16 +54,27 @@
 
 
 var EXPORTED_SYMBOLS = [ "XPCOMUtils" ];
 
 let global = Cu.getGlobalForObject({});
 
 const nsIFactoryQI = ChromeUtils.generateQI([Ci.nsIFactory]);
 
+// Some global imports expose additional symbols; for example,
+// `Cu.importGlobalProperties(["MessageChannel"])` imports `MessageChannel`
+// and `MessagePort`. This table maps those extra symbols to the main
+// import name.
+const EXTRA_GLOBAL_NAME_TO_IMPORT_NAME = {
+  Headers: "fetch",
+  MessagePort: "MessageChannel",
+  Request: "fetch",
+  Response: "fetch",
+};
+
 /**
  * Redefines the given property on the given object with the given
  * value. This can be used to redefine getter properties which do not
  * implement setters.
  */
 function redefine(object, prop, value) {
   Object.defineProperty(object, prop, {
     configurable: true,
@@ -169,17 +180,18 @@ var XPCOMUtils = {
    *        The object on which to define the properties.
    * @param {string[]} aNames
    *        The list of global properties to define.
    */
   defineLazyGlobalGetters(aObject, aNames) {
     for (let name of aNames) {
       this.defineLazyGetter(aObject, name, () => {
         if (!(name in global)) {
-          Cu.importGlobalProperties([name]);
+          let importName = EXTRA_GLOBAL_NAME_TO_IMPORT_NAME[name] || name;
+          Cu.importGlobalProperties([importName]);
         }
         return global[name];
       });
     }
   },
 
   /**
    * Defines a getter on a specified object for a service.  The service will not
--- a/mobile/android/annotations/src/main/java/org/mozilla/gecko/annotationProcessors/CodeGenerator.java
+++ b/mobile/android/annotations/src/main/java/org/mozilla/gecko/annotationProcessors/CodeGenerator.java
@@ -561,49 +561,53 @@ public class CodeGenerator {
      * @return The bytes to be written to the wrappers file.
      */
     public String getWrapperFileContents() {
         cpp.append(
                 Utils.getIfdefFooter(options.ifdef));
         return cpp.toString();
     }
 
+    private boolean haveNatives() {
+        return nativesInits.length() > 0 || Utils.isJNIObject(cls);
+    }
+
     /**
      * Get the finalised bytes to go into the generated header file.
      *
      * @return The bytes to be written to the header file.
      */
     public String getHeaderFileContents() {
         if (this.callingThread == null) {
             this.callingThread = AnnotationInfo.CallingThread.ANY;
         }
 
         header.append(
                 "    static const mozilla::jni::CallingThread callingThread =\n" +
                 "            " + this.callingThread.nativeValue() + ";\n" +
                 "\n");
 
-        if (nativesInits.length() > 0) {
+        if (haveNatives()) {
             header.append(
                     "    template<class Impl> class Natives;\n");
         }
         header.append(
                 "};\n" +
                 "\n" +
                 Utils.getIfdefFooter(options.ifdef));
         return header.toString();
     }
 
     /**
      * Get the finalised bytes to go into the generated natives header file.
      *
      * @return The bytes to be written to the header file.
      */
     public String getNativesFileContents() {
-        if (nativesInits.length() == 0) {
+        if (!haveNatives()) {
             return "";
         }
         natives.append(
                 "    static const JNINativeMethod methods[" + numNativesInits + "];\n" +
                 "};\n" +
                 "\n" +
                 "template<class Impl>\n" +
                 "const JNINativeMethod " + clsName + "::Natives<Impl>::methods[] = {" + nativesInits + '\n' +
--- a/mobile/android/annotations/src/main/java/org/mozilla/gecko/annotationProcessors/utils/Utils.java
+++ b/mobile/android/annotations/src/main/java/org/mozilla/gecko/annotationProcessors/utils/Utils.java
@@ -338,9 +338,18 @@ public class Utils {
     }
 
     public static String getIfdefFooter(String ifdef) {
         if (ifdef.isEmpty()) {
             return "";
         }
         return "#endif // " + ifdef + "\n";
     }
+
+    public static boolean isJNIObject(Class<?> cls) {
+        for (; cls != null; cls = cls.getSuperclass()) {
+            if (cls.getName().equals("org.mozilla.gecko.mozglue.JNIObject")) {
+                return true;
+            }
+        }
+        return false;
+    }
 }
--- a/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/ContentDelegateTest.kt
+++ b/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/ContentDelegateTest.kt
@@ -101,16 +101,43 @@ class ContentDelegateTest : BaseSessionT
             override fun onPageStop(session: GeckoSession, success: Boolean) {
                 assertThat("Page should load successfully", success, equalTo(true))
             }
         })
     }
 
     @IgnoreCrash
     @ReuseSession(false)
+    @WithDisplay(width = 10, height = 10)
+    @Test fun crashContent_tapAfterCrash() {
+        // This test doesn't make sense without multiprocess
+        assumeThat(sessionRule.env.isMultiprocess, equalTo(true))
+        // Cannot test x86 debug builds due to Gecko's "ah_crap_handler"
+        // that waits for debugger to attach during a SIGSEGV.
+        assumeThat(sessionRule.env.isDebugBuild && sessionRule.env.cpuArch == "x86",
+                   equalTo(false))
+
+        mainSession.delegateUntilTestEnd(object : Callbacks.ContentDelegate {
+            override fun onCrash(session: GeckoSession) {
+                mainSession.open()
+                mainSession.loadTestPath(HELLO_HTML_PATH)
+            }
+        })
+
+        mainSession.synthesizeTap(5, 5)
+        mainSession.loadUri(CONTENT_CRASH_URL)
+        mainSession.waitForPageStop()
+
+        mainSession.synthesizeTap(5, 5)
+        mainSession.reload()
+        mainSession.waitForPageStop()
+    }
+
+    @IgnoreCrash
+    @ReuseSession(false)
     @Test fun crashContentMultipleSessions() {
         // This test doesn't make sense without multiprocess
         assumeThat(sessionRule.env.isMultiprocess, equalTo(true))
         // Cannot test x86 debug builds due to Gecko's "ah_crap_handler"
         // that waits for debugger to attach during a SIGSEGV.
         assumeThat(sessionRule.env.isDebugBuild && sessionRule.env.cpuArch == "x86",
                    equalTo(false))
 
--- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/gfx/LayerSession.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/gfx/LayerSession.java
@@ -65,21 +65,22 @@ public class LayerSession {
         private void onCompositorAttached() {
             LayerSession.this.onCompositorAttached();
         }
 
         @WrapForJNI(calledFrom = "ui")
         private void onCompositorDetached() {
             // Clear out any pending calls on the UI thread.
             LayerSession.this.onCompositorDetached();
-            disposeNative();
         }
 
-        @WrapForJNI(calledFrom = "ui", dispatchTo = "gecko")
-        @Override protected native void disposeNative();
+        @Override protected void disposeNative() {
+            // Disposal happens in native code.
+            throw new UnsupportedOperationException();
+        }
 
         @WrapForJNI(calledFrom = "ui", dispatchTo = "gecko")
         public native void attachNPZC(PanZoomController npzc);
 
         @WrapForJNI(calledFrom = "ui", dispatchTo = "gecko")
         public native void onBoundsChanged(int left, int top, int width, int height);
 
         // Gecko thread pauses compositor; blocks UI thread.
--- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/gfx/PanZoomController.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/gfx/PanZoomController.java
@@ -265,23 +265,25 @@ public class PanZoomController extends J
 
     @WrapForJNI(calledFrom = "ui")
     private void setAttached(final boolean attached) {
         if (attached) {
             mAttached = true;
             flushEventQueue();
         } else if (mAttached) {
             mAttached = false;
-            disposeNative();
             enableEventQueue();
         }
     }
 
-    @WrapForJNI(calledFrom = "ui", dispatchTo = "gecko") @Override // JNIObject
-    protected native void disposeNative();
+    @Override // JNIObject
+    protected void disposeNative() {
+        // Disposal happens in native code.
+        throw new UnsupportedOperationException();
+    }
 
     @WrapForJNI(stubName = "SetIsLongpressEnabled") // Called from test thread.
     private native void nativeSetIsLongpressEnabled(boolean isLongpressEnabled);
 
     /**
      * Set whether Gecko should generate long-press events.
      *
      * @param isLongpressEnabled True if Gecko should generate long-press events.
--- a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/SessionAccessibility.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/SessionAccessibility.java
@@ -808,21 +808,18 @@ public class SessionAccessibility {
                     AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED, View.NO_ID);
             ((ViewParent) mView).requestSendAccessibilityEvent(mView, event);
         }
     }
 
     /* package */ final class NativeProvider extends JNIObject {
         @WrapForJNI(calledFrom = "ui")
         private void setAttached(final boolean attached) {
-            if (attached) {
-                mAttached = true;
-            } else if (mAttached) {
-                mAttached = false;
-                disposeNative();
-            }
+            mAttached = attached;
         }
 
-        @WrapForJNI(calledFrom = "ui", dispatchTo = "gecko")
-        @Override
-        protected native void disposeNative();
+        @Override // JNIObject
+        protected void disposeNative() {
+            // Disposal happens in native code.
+            throw new UnsupportedOperationException();
+        }
     }
 }
--- a/modules/libpref/init/StaticPrefList.h
+++ b/modules/libpref/init/StaticPrefList.h
@@ -281,16 +281,22 @@ VARCACHE_PREF(
 )
 
 VARCACHE_PREF(
   "dom.serviceWorkers.testing.enabled",
    dom_serviceWorkers_testing_enabled,
   RelaxedAtomicBool, false
 )
 
+VARCACHE_PREF(
+  "dom.testing.structuredclonetester.enabled",
+  dom_testing_structuredclonetester_enabled,
+  RelaxedAtomicBool, false
+)
+
 // Enable Storage API for all platforms except Android.
 #if !defined(MOZ_WIDGET_ANDROID)
 # define PREF_VALUE true
 #else
 # define PREF_VALUE false
 #endif
 VARCACHE_PREF(
   "dom.storageManager.enabled",
--- a/security/manager/ssl/nsSecureBrowserUIImpl.cpp
+++ b/security/manager/ssl/nsSecureBrowserUIImpl.cpp
@@ -283,17 +283,18 @@ nsSecureBrowserUIImpl::UpdateStateAndSec
 // window or whatever corresponds to an <iframe mozbrowser> element). In some
 // cases, we also receive it from nsIWebProgress instances that are children of
 // that nsIWebProgress. We ignore notifications from children because they don't
 // change the top-level state (if children load mixed or tracking content, the
 // docShell will know and will tell us in GetState when we call
 // CheckForBlockedContent).
 // When we receive a notification from the top-level nsIWebProgress, we extract
 // any relevant security information and set our state accordingly. We then call
-// OnSecurityChange to notify any downstream listeners of the security state.
+// OnSecurityChange on the docShell corresponding to the nsIWebProgress we were
+// initialized with to notify any downstream listeners of the security state.
 NS_IMETHODIMP
 nsSecureBrowserUIImpl::OnLocationChange(nsIWebProgress* aWebProgress,
                                         nsIRequest* aRequest,
                                         nsIURI* aLocation,
                                         uint32_t aFlags)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
@@ -326,34 +327,36 @@ nsSecureBrowserUIImpl::OnLocationChange(
   nsCOMPtr<nsIChannel> channel(do_QueryInterface(aRequest));
   if (channel) {
     MOZ_LOG(gSecureBrowserUILog, LogLevel::Debug,
             ("  we have a channel %p", channel.get()));
     nsresult rv = UpdateStateAndSecurityInfo(channel, aLocation);
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return rv;
     }
+  }
 
-    nsCOMPtr<nsISecurityEventSink> eventSink;
-    NS_QueryNotificationCallbacks(channel, eventSink);
-    if (NS_WARN_IF(!eventSink)) {
-      return NS_ERROR_INVALID_ARG;
+  mozilla::dom::ContentBlockingLog* contentBlockingLog = nullptr;
+  nsCOMPtr<nsIDocShell> docShell = do_QueryReferent(mDocShell);
+  if (docShell) {
+    nsIDocument* doc = docShell->GetDocument();
+    if (doc) {
+      contentBlockingLog = doc->GetContentBlockingLog();
     }
-    mozilla::dom::ContentBlockingLog* contentBlockingLog = nullptr;
-    nsCOMPtr<nsIDocShell> docShell = do_QueryReferent(mDocShell);
-    if (docShell) {
-      nsIDocument* doc = docShell->GetDocument();
-      if (doc) {
-        contentBlockingLog = doc->GetContentBlockingLog();
-      }
-    }
+  }
+
+  nsCOMPtr<nsISecurityEventSink> eventSink = do_QueryInterface(docShell);
+  if (eventSink) {
     MOZ_LOG(gSecureBrowserUILog, LogLevel::Debug,
-            ("  calling OnSecurityChange %p %x\n", aRequest, mState));
+            ("  calling OnSecurityChange %p %x", aRequest, mState));
     Unused << eventSink->OnSecurityChange(aRequest, mOldState, mState,
                                           contentBlockingLog);
+  } else {
+    MOZ_LOG(gSecureBrowserUILog, LogLevel::Debug,
+            ("  no docShell or couldn't QI it to nsISecurityEventSink?"));
   }
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsSecureBrowserUIImpl::OnStateChange(nsIWebProgress*,
                                      nsIRequest*,
--- a/services/sync/modules/resource.js
+++ b/services/sync/modules/resource.js
@@ -5,17 +5,17 @@
 var EXPORTED_SYMBOLS = ["Resource"];
 
 ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
 ChromeUtils.import("resource://gre/modules/Log.jsm");
 ChromeUtils.import("resource://services-common/observers.js");
 ChromeUtils.import("resource://services-common/utils.js");
 ChromeUtils.import("resource://services-sync/util.js");
 const {setTimeout, clearTimeout} = ChromeUtils.import("resource://gre/modules/Timer.jsm", {});
-XPCOMUtils.defineLazyGlobalGetters(this, ["fetch"]);
+XPCOMUtils.defineLazyGlobalGetters(this, ["fetch", "Headers", "Request"]);
 /* global AbortController */
 
 /*
  * Resource represents a remote network resource, identified by a URI.
  * Create an instance like so:
  *
  *   let resource = new Resource("http://foobar.com/path/to/resource");
  *
--- a/taskcluster/ci/build/linux.yml
+++ b/taskcluster/ci/build/linux.yml
@@ -896,17 +896,16 @@ linux64-rusttests/debug:
 linux64-tup/opt:
     description: "Linux64 Tup"
     index:
         product: firefox
         job-name: linux64-tup-opt
     treeherder:
         platform: linux64/opt
         symbol: Btup
-        tier: 2
     worker-type: aws-provisioner-v1/gecko-{level}-b-linux
     worker:
         max-run-time: 3600
         env:
             PERFHERDER_EXTRA_OPTIONS: tup
     run:
         using: mozharness
         actions: [get-secrets build]
--- a/taskcluster/ci/test/raptor.yml
+++ b/taskcluster/ci/test/raptor.yml
@@ -41,17 +41,21 @@ raptor-tp6-firefox:
     mozharness:
         extra-options:
             - --test=raptor-tp6
 
 raptor-tp6-chrome:
     description: "Raptor tp6 on Chrome"
     try-name: raptor-tp6-chrome
     treeherder-symbol: Rap-C(tp6)
-    run-on-projects: ['try']
+    run-on-projects: ['try', 'mozilla-central']
+    tier:
+        by-test-platform:
+            linux64.*: 3
+            default: 2
     max-run-time: 1200
     mozharness:
         extra-options:
             - --test=raptor-tp6
             - --app=chrome
 
 raptor-speedometer-firefox:
     description: "Raptor Speedometer on Firefox"
--- a/taskcluster/ci/toolchain/linux.yml
+++ b/taskcluster/ci/toolchain/linux.yml
@@ -710,17 +710,17 @@ linux64-gn:
         - linux64-gcc-4.9
 
 linux64-tup:
     description: "tup toolchain build"
     treeherder:
         kind: build
         platform: toolchains/opt
         symbol: TL(tup)
-        tier: 2
+        tier: 1
     worker-type: aws-provisioner-v1/gecko-{level}-b-linux
     worker:
         max-run-time: 3600
     run:
         using: toolchain-script
         script: build-tup-linux.sh
         resources:
             - 'taskcluster/scripts/misc/tooltool-download.sh'
--- a/taskcluster/taskgraph/config.py
+++ b/taskcluster/taskgraph/config.py
@@ -34,17 +34,17 @@ graph_config_schema = Schema({
     Required('index'): {
         Required('products'): [basestring]
     },
     Required('try'): {
         # We have a few platforms for which we want to do some "extra" builds, or at
         # least build-ish things.  Sort of.  Anyway, these other things are implemented
         # as different "platforms".  These do *not* automatically ride along with "-p
         # all"
-        Required('ridealong-builds', default={}): {basestring: [basestring]},
+        Required('ridealong-builds'): {basestring: [basestring]},
     },
     Required('release-promotion'): {
         Required('products'): [basestring],
         Required('flavors'): {basestring: {
             Required('product'): basestring,
             Required('target-tasks-method'): basestring,
             Optional('is-rc'): bool,
             Optional('rebuild-kinds'): [basestring],
@@ -84,17 +84,17 @@ class GraphConfig(object):
             )
         return os.path.join(
             os.path.dirname(os.path.dirname(self.root_dir)),
             ".taskcluster.yml",
         )
 
 
 def validate_graph_config(config):
-    return validate_schema(graph_config_schema, config, "Invalid graph configuration:")
+    validate_schema(graph_config_schema, config, "Invalid graph configuration:")
 
 
 def load_graph_config(root_dir):
     config_yml = os.path.join(root_dir, "config.yml")
     if not os.path.exists(config_yml):
         raise Exception("Couldn't find taskgraph configuration: {}".format(config_yml))
 
     logger.debug("loading config from `{}`".format(config_yml))
deleted file mode 100644
--- a/testing/mozharness/configs/selfserve/production.py
+++ /dev/null
@@ -1,3 +0,0 @@
-config = {
-    "selfserve_url": "https://secure.pub.build.mozilla.org/buildapi/self-serve",
-}
deleted file mode 100644
--- a/testing/mozharness/configs/selfserve/staging.py
+++ /dev/null
@@ -1,3 +0,0 @@
-config = {
-    "selfserve_url": "https://secure-pub-build.allizom.org/buildapi/self-serve",
-}
--- a/testing/mozharness/docs/mozharness.base.rst
+++ b/testing/mozharness/docs/mozharness.base.rst
@@ -62,24 +62,16 @@ mozharness.base.python module
 mozharness.base.script module
 -----------------------------
 
 .. automodule:: mozharness.base.script
     :members:
     :undoc-members:
     :show-inheritance:
 
-mozharness.base.signing module
-------------------------------
-
-.. automodule:: mozharness.base.signing
-    :members:
-    :undoc-members:
-    :show-inheritance:
-
 mozharness.base.transfer module
 -------------------------------
 
 .. automodule:: mozharness.base.transfer
     :members:
     :undoc-members:
     :show-inheritance:
 
--- a/testing/mozharness/docs/mozharness.base.vcs.rst
+++ b/testing/mozharness/docs/mozharness.base.vcs.rst
@@ -23,24 +23,15 @@ mozharness.base.vcs.mercurial module
 mozharness.base.vcs.vcsbase module
 ----------------------------------
 
 .. automodule:: mozharness.base.vcs.vcsbase
     :members:
     :undoc-members:
     :show-inheritance:
 
-mozharness.base.vcs.vcssync module
-----------------------------------
-
-.. automodule:: mozharness.base.vcs.vcssync
-    :members:
-    :undoc-members:
-    :show-inheritance:
-
-
 Module contents
 ---------------
 
 .. automodule:: mozharness.base.vcs
     :members:
     :undoc-members:
     :show-inheritance:
--- a/testing/mozharness/docs/mozharness.mozilla.rst
+++ b/testing/mozharness/docs/mozharness.mozilla.rst
@@ -40,40 +40,16 @@ mozharness.mozilla.mozbase module
 mozharness.mozilla.purge module
 -------------------------------
 
 .. automodule:: mozharness.mozilla.purge
     :members:
     :undoc-members:
     :show-inheritance:
 
-mozharness.mozilla.release module
----------------------------------
-
-.. automodule:: mozharness.mozilla.release
-    :members:
-    :undoc-members:
-    :show-inheritance:
-
-mozharness.mozilla.repo_manifest module
----------------------------------------
-
-.. automodule:: mozharness.mozilla.repo_manifest
-    :members:
-    :undoc-members:
-    :show-inheritance:
-
-mozharness.mozilla.signing module
----------------------------------
-
-.. automodule:: mozharness.mozilla.signing
-    :members:
-    :undoc-members:
-    :show-inheritance:
-
 mozharness.mozilla.tooltool module
 ----------------------------------
 
 .. automodule:: mozharness.mozilla.tooltool
     :members:
     :undoc-members:
     :show-inheritance:
 
--- a/testing/mozharness/mozharness/base/config.py
+++ b/testing/mozharness/mozharness/base/config.py
@@ -73,16 +73,17 @@ def make_immutable(item):
     else:
         result = item
     return result
 
 
 class LockedTuple(tuple):
     def __new__(cls, items):
         return tuple.__new__(cls, (make_immutable(x) for x in items))
+
     def __deepcopy__(self, memo):
         return [deepcopy(elem, memo) for elem in self]
 
 
 # ReadOnlyDict {{{1
 class ReadOnlyDict(dict):
     def __init__(self, dictionary):
         self._lock = False
@@ -130,17 +131,22 @@ class ReadOnlyDict(dict):
         memo[id(self)] = result
         for k, v in self.__dict__.items():
             setattr(result, k, deepcopy(v, memo))
         result._lock = False
         for k, v in self.items():
             result[k] = deepcopy(v, memo)
         return result
 
-DEFAULT_CONFIG_PATH = os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(__file__))), "configs")
+
+DEFAULT_CONFIG_PATH = os.path.join(
+    os.path.dirname(os.path.dirname(os.path.dirname(__file__))),
+    "configs",
+)
+
 
 # parse_config_file {{{1
 def parse_config_file(file_name, quiet=False, search_path=None,
                       config_dict_name="config"):
     """Read a config file and return a dictionary.
     """
     file_path = None
     if os.path.exists(file_name):
@@ -161,17 +167,18 @@ def parse_config_file(file_name, quiet=F
         config = local_dict[config_dict_name]
     elif file_name.endswith('.json'):
         fh = open(file_path)
         config = {}
         json_config = json.load(fh)
         config = dict(json_config)
         fh.close()
     else:
-        raise RuntimeError("Unknown config file type %s! (config files must end in .json or .py)" % file_name)
+        raise RuntimeError(
+            "Unknown config file type %s! (config files must end in .json or .py)" % file_name)
     # TODO return file_path
     return config
 
 
 def download_config_file(url, file_name):
     n = 0
     attempts = 5
     sleeptime = 60
@@ -253,17 +260,17 @@ class BaseConfig(object):
             # parse sys.argv which in this case would be the command line
             # options specified to run the tests, e.g. nosetests -v. Clearly,
             # the options passed to nosetests (such as -v) should not be
             # interpreted by mozharness as mozharness options, so we specify
             # a dummy command line with no options, so that the parser does
             # not add anything from the test invocation command line
             # arguments to the mozharness options.
             if option_args is None:
-                option_args=['dummy_mozharness_script_with_no_command_line_options.py']
+                option_args = ['dummy_mozharness_script_with_no_command_line_options.py']
         if config_options is None:
             config_options = []
         self._create_config_parser(config_options, usage)
         # we allow manually passing of option args for things like nosetests
         self.parse_args(args=option_args)
 
     def get_read_only_config(self):
         return ReadOnlyDict(self._config)
@@ -403,17 +410,17 @@ class BaseConfig(object):
                 if not quiet:
                     print("Invalid action %s not in %s!" % (action,
                                                             self.all_actions))
                 raise SystemExit(-1)
         return action_list
 
     def verify_actions_order(self, action_list):
         try:
-            indexes = [ self.all_actions.index(elt) for elt in action_list ]
+            indexes = [self.all_actions.index(elt) for elt in action_list]
             sorted_indexes = sorted(indexes)
             for i in range(len(indexes)):
                 if indexes[i] != sorted_indexes[i]:
                     print(("Action %s comes in different order in %s\n" +
                            "than in %s") % (action_list[i], action_list, self.all_actions))
                     raise SystemExit(-1)
         except ValueError as e:
             print("Invalid action found: " + str(e))
@@ -451,17 +458,20 @@ class BaseConfig(object):
                     file_name = os.path.basename(cf)
                     file_path = os.path.join(os.getcwd(), file_name)
                     download_config_file(cf, file_path)
                     all_cfg_files_and_dicts.append(
                         (file_path, parse_config_file(file_path, search_path=["."]))
                     )
                 else:
                     all_cfg_files_and_dicts.append(
-                        (cf, parse_config_file(cf, search_path=config_paths + [DEFAULT_CONFIG_PATH]))
+                        (cf, parse_config_file(
+                            cf,
+                            search_path=config_paths + [DEFAULT_CONFIG_PATH]
+                        ))
                     )
             except Exception:
                 if cf in options.opt_config_files:
                     print(
                         "WARNING: optional config file not found %s" % cf
                     )
                 else:
                     raise
--- a/testing/mozharness/mozharness/base/errors.py
+++ b/testing/mozharness/mozharness/base/errors.py
@@ -23,16 +23,17 @@ import re
 
 from mozharness.base.log import DEBUG, WARNING, ERROR, CRITICAL, FATAL
 
 
 # Exceptions
 class VCSException(Exception):
     pass
 
+
 # ErrorLists {{{1
 BaseErrorList = [{
     'substr': r'''command not found''',
     'level': ERROR
 }]
 
 # For ssh, scp, rsync over ssh
 SSHErrorList = BaseErrorList + [{
@@ -123,17 +124,18 @@ PythonErrorList = BaseErrorList + [
     {'regex': re.compile(r'''raise \w*Exception: '''), 'level': CRITICAL},
     {'regex': re.compile(r'''raise \w*Error: '''), 'level': CRITICAL},
 ]
 
 VirtualenvErrorList = [
     {'substr': r'''not found or a compiler error:''', 'level': WARNING},
     {'regex': re.compile('''\d+: error: '''), 'level': ERROR},
     {'regex': re.compile('''\d+: warning: '''), 'level': WARNING},
-    {'regex': re.compile(r'''Downloading .* \(.*\): *([0-9]+%)? *[0-9\.]+[kmKM]b'''), 'level': DEBUG},
+    {'regex': re.compile(
+        r'''Downloading .* \(.*\): *([0-9]+%)? *[0-9\.]+[kmKM]b'''), 'level': DEBUG},
 ] + PythonErrorList
 
 
 # We may need to have various MakefileErrorLists for differing amounts of
 # warning-ignoring-ness.
 MakefileErrorList = BaseErrorList + PythonErrorList + [
     {'substr': r'''No rule to make target ''', 'level': ERROR},
     {'regex': re.compile(r'''akefile.*was not found\.'''), 'level': ERROR},
@@ -154,25 +156,27 @@ TarErrorList = BaseErrorList + [
     {'substr': r'''Cannot exec: No such file or directory''', 'level': ERROR},
     {'substr': r''': Error is not recoverable: exiting now''', 'level': ERROR},
 ]
 
 JarsignerErrorList = [{
     'substr': r'''command not found''',
     'level': FATAL
 }, {
-    'substr': r'''jarsigner error: java.lang.RuntimeException: keystore load: Keystore was tampered with, or password was incorrect''',
+    'substr': r'''jarsigner error: java.lang.RuntimeException: keystore load: '''
+              r'''Keystore was tampered with, or password was incorrect''',
     'level': FATAL,
     'explanation': r'''The store passphrase is probably incorrect!''',
 }, {
     'regex': re.compile(r'''jarsigner: key associated with .* not a private key'''),
     'level': FATAL,
     'explanation': r'''The key passphrase is probably incorrect!''',
 }, {
-    'regex': re.compile(r'''jarsigner error: java.lang.RuntimeException: keystore load: .* .No such file or directory'''),
+    'regex': re.compile(r'''jarsigner error: java.lang.RuntimeException: '''
+                        r'''keystore load: .* .No such file or directory'''),
     'level': FATAL,
     'explanation': r'''The keystore doesn't exist!''',
 }, {
     'substr': r'''jarsigner: unable to open jar file:''',
     'level': FATAL,
     'explanation': r'''The apk is missing!''',
 }]
 
--- a/testing/mozharness/mozharness/base/log.py
+++ b/testing/mozharness/mozharness/base/log.py
@@ -687,12 +687,13 @@ def numeric_log_level(level):
     Args:
         level (str): log level name to convert.
 
     Returns:
         int: numeric value of the log level name.
     """
     return LOG_LEVELS[level]
 
+
 # __main__ {{{1
 if __name__ == '__main__':
     """ Useless comparison, due to the `pass` keyword on its body"""
     pass
--- a/testing/mozharness/mozharness/base/python.py
+++ b/testing/mozharness/mozharness/base/python.py
@@ -26,16 +26,17 @@ from mozharness.base.script import (
 from mozharness.base.errors import VirtualenvErrorList
 from mozharness.base.log import WARNING, FATAL
 
 external_tools_path = os.path.join(
     os.path.abspath(os.path.dirname(os.path.dirname(mozharness.__file__))),
     'external_tools',
 )
 
+
 def get_tlsv1_post():
     # Monkeypatch to work around SSL errors in non-bleeding-edge Python.
     # Taken from https://lukasa.co.uk/2013/01/Choosing_SSL_Version_In_Requests/
     import requests
     from requests.packages.urllib3.poolmanager import PoolManager
     import ssl
 
     class TLSV1Adapter(requests.adapters.HTTPAdapter):
@@ -43,16 +44,17 @@ def get_tlsv1_post():
             self.poolmanager = PoolManager(num_pools=connections,
                                            maxsize=maxsize,
                                            block=block,
                                            ssl_version=ssl.PROTOCOL_TLSv1)
     s = requests.Session()
     s.mount('https://', TLSV1Adapter())
     return s.post
 
+
 # Virtualenv {{{1
 virtualenv_config_options = [
     [["--virtualenv-path"], {
         "action": "store",
         "dest": "virtualenv_path",
         "default": "venv",
         "help": "Specify the path to the virtualenv top level directory"
     }],
@@ -129,17 +131,18 @@ class VirtualenvMixin(object):
         c['virtualenv_path'] is set; otherwise return the binary name.
         Otherwise return None
         """
         if binary not in self.python_paths:
             bin_dir = 'bin'
             if self._is_windows():
                 bin_dir = 'Scripts'
             virtualenv_path = self.query_virtualenv_path()
-            self.python_paths[binary] = os.path.abspath(os.path.join(virtualenv_path, bin_dir, binary))
+            self.python_paths[binary] = os.path.abspath(
+                os.path.join(virtualenv_path, bin_dir, binary))
 
         return self.python_paths[binary]
 
     def query_python_site_packages_path(self):
         if self.site_packages_path:
             return self.site_packages_path
         python = self.query_python_path()
         self.site_packages_path = self.get_output_from_command(
@@ -156,19 +159,22 @@ class VirtualenvMixin(object):
         packages = {}
 
         if pip_freeze_output is None:
             # get the output from `pip freeze`
             pip = self.query_python_path("pip")
             if not pip:
                 self.log("package_versions: Program pip not in path", level=error_level)
                 return {}
-            pip_freeze_output = self.get_output_from_command([pip, "freeze"], silent=True, ignore_errors=True)
+            pip_freeze_output = self.get_output_from_command(
+                [pip, "freeze"], silent=True, ignore_errors=True)
             if not isinstance(pip_freeze_output, basestring):
-                self.fatal("package_versions: Error encountered running `pip freeze`: %s" % pip_freeze_output)
+                self.fatal(
+                    "package_versions: Error encountered running `pip freeze`: "
+                    + pip_freeze_output)
 
         for line in pip_freeze_output.splitlines():
             # parse the output into package, version
             line = line.strip()
             if not line:
                 # whitespace
                 continue
             if line.startswith('-'):
@@ -239,17 +245,19 @@ class VirtualenvMixin(object):
                 self.fatal("module parameter required with install_method='easy_install'")
             if requirements:
                 # Install pip requirements files separately, since they're
                 # not understood by easy_install.
                 self.install_module(requirements=requirements,
                                     install_method='pip')
             command = [self.query_python_path(), '-m', 'easy_install']
         else:
-            self.fatal("install_module() doesn't understand an install_method of %s!" % install_method)
+            self.fatal(
+                "install_module() doesn't understand an install_method of %s!"
+                % install_method)
 
         for link in c.get('find_links', []):
             parsed = urlparse.urlparse(link)
 
             try:
                 socket.gethostbyname(parsed.hostname)
             except socket.gaierror as e:
                 self.info('error resolving %s (ignoring): %s' %
@@ -259,17 +267,19 @@ class VirtualenvMixin(object):
             command.extend(["--find-links", link])
 
         # module_url can be None if only specifying requirements files
         if module_url:
             if editable:
                 if install_method in (None, 'pip'):
                     command += ['-e']
                 else:
-                    self.fatal("editable installs not supported for install_method %s" % install_method)
+                    self.fatal(
+                        "editable installs not supported for install_method %s"
+                        % install_method)
             command += [module_url]
 
         # If we're only installing a single requirements file, use
         # the file's directory as cwd, so relative paths work correctly.
         cwd = dirs['abs_work_dir']
         if not module and len(requirements) == 1:
             cwd = os.path.dirname(requirements[0])
 
@@ -277,17 +287,20 @@ class VirtualenvMixin(object):
         # Allow for errors while building modules, but require a
         # return status of 0.
         self.retry(
             self.run_command,
             # None will cause default value to be used
             attempts=1 if optional else None,
             good_statuses=(0,),
             error_level=WARNING if optional else FATAL,
-            error_message='Could not install python package: ' + quoted_command + ' failed after %(attempts)d tries!',
+            error_message=(
+                'Could not install python package: '
+                + quoted_command + ' failed after %(attempts)d tries!'
+            ),
             args=[command, ],
             kwargs={
                 'error_list': VirtualenvErrorList,
                 'cwd': cwd,
                 'env': env,
                 # WARNING only since retry will raise final FATAL if all
                 # retry attempts are unsuccessful - and we only want
                 # an ERROR of FATAL if *no* retry attempt works
@@ -357,17 +370,19 @@ class VirtualenvMixin(object):
         # only use --alway-copy when not using Redhat.
         if self._is_redhat():
             self.warning("creating virtualenv without --always-copy "
                          "due to issues on Redhat derived distros")
         else:
             virtualenv_options.append('--always-copy')
 
         if os.path.exists(self.query_python_path()):
-            self.info("Virtualenv %s appears to already exist; skipping virtualenv creation." % self.query_python_path())
+            self.info(
+                "Virtualenv %s appears to already exist; "
+                "skipping virtualenv creation." % self.query_python_path())
         else:
             self.mkdir_p(dirs['abs_work_dir'])
             self.run_command(virtualenv + virtualenv_options + [venv_path],
                              cwd=dirs['abs_work_dir'],
                              error_list=VirtualenvErrorList,
                              partial_env={'VIRTUALENV_NO_DOWNLOAD': "1"},
                              halt_on_failure=True)
 
@@ -583,17 +598,20 @@ class ResourceMonitoringMixin(Perfherder
             message = '{prefix} - Wall time: {duration:.0f}s; ' \
                 'CPU: {cpu_percent}; ' \
                 'Read bytes: {io_read_bytes}; Write bytes: {io_write_bytes}; ' \
                 'Read time: {io_read_time}; Write time: {io_write_time}'
 
             # XXX Some test harnesses are complaining about a string being
             # being fed into a 'f' formatter. This will help diagnose the
             # issue.
-            cpu_percent_str = str(round(cpu_percent)) + '%' if cpu_percent else "Can't collect data"
+            if cpu_percent:
+                cpu_percent_str = str(round(cpu_percent)) + '%'
+            else:
+                cpu_percent_str = "Can't collect data"
 
             try:
                 self.info(
                     message.format(
                         prefix=prefix, duration=duration,
                         cpu_percent=cpu_percent_str, io_read_bytes=io.read_bytes,
                         io_write_bytes=io.write_bytes, io_read_time=io.read_time,
                         io_write_time=io.write_time
--- a/testing/mozharness/mozharness/base/script.py
+++ b/testing/mozharness/mozharness/base/script.py
@@ -1,10 +1,8 @@
-
-#!/usr/bin/env python
 # ***** BEGIN LICENSE BLOCK *****
 # 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/.
 # ***** END LICENSE BLOCK *****
 """Generic script objects.
 
 script.py, along with config.py and log.py, represents the core of
@@ -325,19 +323,20 @@ class ScriptMixin(PlatformMixin):
             if name == '.' or name == '..':
                 continue
             full_name = os.path.join(path, name)
 
             if file_attr & win32file.FILE_ATTRIBUTE_DIRECTORY:
                 self._rmtree_windows(full_name)
             else:
                 try:
-                    win32file.SetFileAttributesW('\\\\?\\' + full_name, win32file.FILE_ATTRIBUTE_NORMAL)
+                    win32file.SetFileAttributesW(
+                        '\\\\?\\' + full_name, win32file.FILE_ATTRIBUTE_NORMAL)
                     win32file.DeleteFile('\\\\?\\' + full_name)
-                except:
+                except Exception:
                     # DeleteFile fails on long paths, del /f /q works just fine
                     self.run_command('del /F /Q "%s"' % full_name)
 
         win32file.RemoveDirectory('\\\\?\\' + path)
 
     def get_filename_from_url(self, url):
         """ parse a filename base on an url.
 
@@ -373,18 +372,16 @@ class ScriptMixin(PlatformMixin):
 
         .. _urllib2.urlopen:
         https://docs.python.org/2/library/urllib2.html#urllib2.urlopen
         """
         # http://bugs.python.org/issue13359 - urllib2 does not automatically quote the URL
         url_quoted = urllib2.quote(url, safe='%/:=&?~#+!$,;\'@()*[]|')
         return urllib2.urlopen(url_quoted, **kwargs)
 
-
-
     def fetch_url_into_memory(self, url):
         ''' Downloads a file from a url into memory instead of disk.
 
         Args:
             url (str): URL path where the file to be downloaded is located.
 
         Raises:
             IOError: When the url points to a file on disk and cannot be found
@@ -406,17 +403,18 @@ class ScriptMixin(PlatformMixin):
             content_length = os.stat(path).st_size
 
             # In case we're referrencing a file without file://
             if parsed_url.scheme == '':
                 url = 'file://%s' % os.path.abspath(url)
                 parsed_url = urlparse.urlparse(url)
 
         request = urllib2.Request(url)
-        # When calling fetch_url_into_memory() you should retry when we raise one of these exceptions:
+        # When calling fetch_url_into_memory() you should retry when we raise
+        # one of these exceptions:
         # * Bug 1300663 - HTTPError: HTTP Error 404: Not Found
         # * Bug 1300413 - HTTPError: HTTP Error 500: Internal Server Error
         # * Bug 1300943 - HTTPError: HTTP Error 503: Service Unavailable
         # * Bug 1300953 - URLError: <urlopen error [Errno -2] Name or service not known>
         # * Bug 1301594 - URLError: <urlopen error [Errno 10054] An existing connection was ...
         # * Bug 1301597 - URLError: <urlopen error [Errno 8] _ssl.c:504: EOF occurred in ...
         # * Bug 1301855 - URLError: <urlopen error [Errno 60] Operation timed out>
         # * Bug 1302237 - URLError: <urlopen error [Errno 104] Connection reset by peer>
@@ -431,37 +429,37 @@ class ScriptMixin(PlatformMixin):
         response_body = response.read()
         response_body_size = len(response_body)
 
         self.info('Content-Length response header: {}'.format(content_length))
         self.info('Bytes received: {}'.format(response_body_size))
 
         if response_body_size != content_length:
             raise ContentLengthMismatch(
-                'The retrieved Content-Length header declares a body length of {} bytes, while we actually retrieved {} bytes'.format(
+                'The retrieved Content-Length header declares a body length '
+                'of {} bytes, while we actually retrieved {} bytes'.format(
                     content_length, response_body_size)
             )
 
         if response.info().get('Content-Encoding') == 'gzip':
             self.info('Content-Encoding is "gzip", so decompressing response body')
             # See http://www.zlib.net/manual.html#Advanced
             # section "ZEXTERN int ZEXPORT inflateInit2 OF....":
             #   Add 32 to windowBits to enable zlib and gzip decoding with automatic
             #   header detection, or add 16 to decode only the gzip format (the zlib
             #   format will return a Z_DATA_ERROR).
             # Adding 16 since we only wish to support gzip encoding.
-            file_contents = zlib.decompress(response_body, zlib.MAX_WBITS|16)
+            file_contents = zlib.decompress(response_body, zlib.MAX_WBITS | 16)
         else:
             file_contents = response_body
 
         # Use BytesIO instead of StringIO
         # http://stackoverflow.com/questions/34162017/unzip-buffer-with-python/34162395#34162395
         return BytesIO(file_contents)
 
-
     def _download_file(self, url, file_name):
         """ Helper function for download_file()
         Additionaly this function logs all exceptions as warnings before
         re-raising them
 
         Args:
             url (str): string containing the URL with the file location
             file_name (str): name of the file where the downloaded file
@@ -497,17 +495,19 @@ class ScriptMixin(PlatformMixin):
                 # file, and delete the compressed version.
                 local_file = open(file_name + '.gz', 'wb')
             else:
                 local_file = open(file_name, 'wb')
             while True:
                 block = f.read(1024 ** 2)
                 if not block:
                     if f_length is not None and got_length != f_length:
-                        raise urllib2.URLError("Download incomplete; content-length was %d, but only received %d" % (f_length, got_length))
+                        raise urllib2.URLError(
+                            "Download incomplete; content-length was %d, "
+                            "but only received %d" % (f_length, got_length))
                     break
                 local_file.write(block)
                 if f_length is not None:
                     got_length += len(block)
             local_file.close()
             if f.info().get('Content-Encoding') == 'gzip':
                 # Decompress file into target location, then remove compressed version
                 with open(file_name, 'wb') as f_out:
@@ -579,26 +579,24 @@ class ScriptMixin(PlatformMixin):
             kwargs = {"url": url, "file_name": file_name}
 
         return self.retry(
             download_func,
             kwargs=kwargs,
             **retry_args
         )
 
-
     def _filter_entries(self, namelist, extract_dirs):
         """Filter entries of the archive based on the specified list of to extract dirs."""
         filter_partial = functools.partial(fnmatch.filter, namelist)
         entries = itertools.chain(*map(filter_partial, extract_dirs or ['*']))
 
         for entry in entries:
             yield entry
 
-
     def unzip(self, compressed_file, extract_to, extract_dirs='*', verbose=False):
         """This method allows to extract a zip file without writing to disk first.
 
         Args:
             compressed_file (object): File-like object with the contents of a compressed zip file.
             extract_to (str): where to extract the compressed file.
             extract_dirs (list, optional): directories inside the archive file to extract.
                                            Defaults to '*'.
@@ -630,29 +628,27 @@ class ScriptMixin(PlatformMixin):
                     # Only set permissions if attributes are available. Otherwise all
                     # permissions will be removed eg. on Windows.
                     if mode:
                         os.chmod(fname, mode)
 
                 except KeyError:
                     self.warning('{} was not found in the zip file'.format(entry))
 
-
     def deflate(self, compressed_file, mode, extract_to='.', *args, **kwargs):
         """This method allows to extract a compressed file from a tar, tar.bz2 and tar.gz files.
 
         Args:
             compressed_file (object): File-like object with the contents of a compressed file.
             mode (str): string of the form 'filemode[:compression]' (e.g. 'r:gz' or 'r:bz2')
             extract_to (str, optional): where to extract the compressed file.
         """
         t = tarfile.open(fileobj=compressed_file, mode=mode)
         t.extractall(path=extract_to)
 
-
     def download_unpack(self, url, extract_to='.', extract_dirs='*', verbose=False):
         """Generic method to download and extract a compressed file without writing it to disk first.
 
         Args:
             url (str): URL where the file to be downloaded is located.
             extract_to (str, optional): directory where the downloaded file will
                                         be extracted to.
             extract_dirs (list, optional): directories inside the archive to extract.
@@ -739,17 +735,16 @@ class ScriptMixin(PlatformMixin):
         #    Let's unpack the file
         function, kwargs = _determine_extraction_method_and_kwargs(url)
         try:
             function(**kwargs)
         except zipfile.BadZipfile:
             # Dump the exception and exit
             self.exception(level=FATAL)
 
-
     def load_json_url(self, url, error_level=None, *args, **kwargs):
         """ Returns a json object from a url (it retries). """
         contents = self._retry_download(
             url=url, error_level=error_level, *args, **kwargs
         )
         return json.loads(contents.read())
 
     # http://www.techniqal.com/blog/2008/07/31/python-file-read-write-with-urllib2/
@@ -839,17 +834,19 @@ class ScriptMixin(PlatformMixin):
 
         .. _stat:
         https://docs.python.org/2/library/os.html#os.chmod
         """
 
         self.info("Chmoding %s to %s" % (path, str(oct(mode))))
         os.chmod(path, mode)
 
-    def copyfile(self, src, dest, log_level=INFO, error_level=ERROR, copystat=False, compress=False):
+    def copyfile(
+        self, src, dest, log_level=INFO, error_level=ERROR, copystat=False, compress=False
+    ):
         """ copy or compress `src` into `dest`.
 
         Args:
             src (str): filepath to copy.
             dest (str): filepath where to move the content to.
             log_level (str, optional): log level to use for normal operation. Defaults to
                                       `INFO`
             error_level (str, optional): log level to use on error. Defaults to `ERROR`
@@ -1303,35 +1300,38 @@ class ScriptMixin(PlatformMixin):
                         found = True
                 else:
                     self.log("a exes %s dict's value is not a string, list, or tuple. Got key "
                              "%s and value %s" % (exe_name, name, str(path)), level=error_level)
                 if found:
                     exe = path
                     break
             else:
-                self.log("query_exe was a searchable dict but an existing path could not be "
-                         "determined. Tried searching in paths: %s" % (str(exe)), level=error_level)
+                self.log("query_exe was a searchable dict but an existing "
+                         "path could not be determined. Tried searching in "
+                         "paths: %s" % (str(exe)), level=error_level)
                 return None
         elif isinstance(exe, list) or isinstance(exe, tuple):
             exe = [x % repl_dict for x in exe]
         elif isinstance(exe, str):
             exe = exe % repl_dict
         else:
             self.log("query_exe: %s is not a list, tuple, dict, or string: "
                      "%s!" % (exe_name, str(exe)), level=error_level)
             return exe
         if return_type == "list":
             if isinstance(exe, str):
                 exe = [exe]
         elif return_type == "string":
             if isinstance(exe, list):
                 exe = subprocess.list2cmdline(exe)
         elif return_type is not None:
-            self.log("Unknown return_type type %s requested in query_exe!" % return_type, level=error_level)
+            self.log(
+                "Unknown return_type type %s requested in query_exe!"
+                % return_type, level=error_level)
         return exe
 
     def run_command(self, command, cwd=None, error_list=None,
                     halt_on_failure=False, success_codes=None,
                     env=None, partial_env=None, return_type='status',
                     throw_exception=False, output_parser=None,
                     output_timeout=None, fatal_exit_code=2,
                     error_level=ERROR, **kwargs):
@@ -1416,17 +1416,19 @@ class ScriptMixin(PlatformMixin):
             parser = output_parser
 
         try:
             if output_timeout:
                 def processOutput(line):
                     parser.add_lines(line)
 
                 def onTimeout():
-                    self.info("Automation Error: mozprocess timed out after %s seconds running %s" % (str(output_timeout), str(command)))
+                    self.info(
+                        "Automation Error: mozprocess timed out after "
+                        "%s seconds running %s" % (str(output_timeout), str(command)))
 
                 p = ProcessHandler(command,
                                    shell=shell,
                                    env=env,
                                    cwd=cwd,
                                    storeOutput=False,
                                    onTimeout=(onTimeout,),
                                    processOutputLine=[processOutput])
@@ -1591,17 +1593,19 @@ class ScriptMixin(PlatformMixin):
         shell = True
         if isinstance(command, list):
             shell = False
 
         p = subprocess.Popen(command, shell=shell, stdout=tmp_stdout,
                              cwd=cwd, stderr=tmp_stderr, env=env, bufsize=0)
         # XXX: changed from self.debug to self.log due to this error:
         #      TypeError: debug() takes exactly 1 argument (2 given)
-        self.log("Temporary files: %s and %s" % (tmp_stdout_filename, tmp_stderr_filename), level=DEBUG)
+        self.log(
+            "Temporary files: %s and %s"
+            % (tmp_stdout_filename, tmp_stderr_filename), level=DEBUG)
         p.wait()
         tmp_stdout.close()
         tmp_stderr.close()
         return_level = DEBUG
         output = None
         if return_type == 'output' or not silent:
             if os.path.exists(tmp_stdout_filename) and os.path.getsize(tmp_stdout_filename):
                 output = self.read_from_file(tmp_stdout_filename,
@@ -2021,18 +2025,18 @@ class BaseScript(ScriptMixin, LogMixin, 
                         continue
 
                     try:
                         self.info("Running post-action listener: %s" % fn)
                         method = getattr(self, fn)
                         method(action, success=False)
                     except Exception:
                         self.error("An additional exception occurred during "
-                                   "post-action for %s: %s" % (action,
-                                   traceback.format_exc()))
+                                   "post-action for %s: %s"
+                                   % (action, traceback.format_exc()))
 
                 self.fatal("Aborting due to exception in pre-action listener.")
 
         # We always run post action listeners, even if the main routine failed.
         success = False
         try:
             self.info("Running main action method: %s" % method_name)
             self._possibly_run_method("preflight_%s" % method_name)
@@ -2316,13 +2320,8 @@ class BaseScript(ScriptMixin, LogMixin, 
     def return_code(self):
         return self._return_code
 
     @return_code.setter
     def return_code(self, code):
         old_return_code, self._return_code = self._return_code, code
         if old_return_code != code:
             self.warning("setting return code to %d" % code)
-
-# __main__ {{{1
-if __name__ == '__main__':
-    """ Useless comparison, due to the `pass` keyword on its body"""
-    pass
deleted file mode 100755
--- a/testing/mozharness/mozharness/base/signing.py
+++ /dev/null
@@ -1,165 +0,0 @@
-#!/usr/bin/env python
-# ***** BEGIN LICENSE BLOCK *****
-# 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/.
-# ***** END LICENSE BLOCK *****
-"""Generic signing methods.
-"""
-
-import getpass
-import hashlib
-import os
-import re
-import subprocess
-
-from mozharness.base.errors import JarsignerErrorList, ZipErrorList, ZipalignErrorList
-from mozharness.base.log import OutputParser, IGNORE, DEBUG, INFO, ERROR, FATAL
-
-UnsignApkErrorList = [{
-    'regex': re.compile(r'''zip warning: name not matched: '?META-INF/'''),
-    'level': INFO,
-    'explanation': r'''This apk is already unsigned.''',
-}, {
-    'substr': r'''zip error: Nothing to do!''',
-    'level': IGNORE,
-}] + ZipErrorList
-
-TestJarsignerErrorList = [{
-    "substr": "jarsigner: unable to open jar file:",
-    "level": IGNORE,
-}] + JarsignerErrorList
-
-
-# BaseSigningMixin {{{1
-class BaseSigningMixin(object):
-    """Generic signing helper methods.
-    """
-    def query_filesize(self, file_path):
-        self.info("Determining filesize for %s" % file_path)
-        length = os.path.getsize(file_path)
-        self.info(" %s" % str(length))
-        return length
-
-    # TODO this should be parallelized with the to-be-written BaseHelper!
-    def query_sha512sum(self, file_path):
-        self.info("Determining sha512sum for %s" % file_path)
-        m = hashlib.sha512()
-        contents = self.read_from_file(file_path, verbose=False,
-                                       open_mode='rb')
-        m.update(contents)
-        sha512 = m.hexdigest()
-        self.info(" %s" % sha512)
-        return sha512
-
-
-# AndroidSigningMixin {{{1
-class AndroidSigningMixin(object):
-    """
-    Generic Android apk signing methods.
-
-    Dependent on BaseScript.
-    """
-    # TODO port build/tools/release/signing/verify-android-signature.sh here
-
-    key_passphrase = os.environ.get('android_keypass')
-    store_passphrase = os.environ.get('android_storepass')
-
-    def passphrase(self):
-        if not self.store_passphrase:
-            self.store_passphrase = getpass.getpass("Store passphrase: ")
-        if not self.key_passphrase:
-            self.key_passphrase = getpass.getpass("Key passphrase: ")
-
-    def _verify_passphrases(self, keystore, key_alias, error_level=FATAL):
-        self.info("Verifying passphrases...")
-        status = self.sign_apk("NOTAREALAPK", keystore,
-                               self.store_passphrase, self.key_passphrase,
-                               key_alias, remove_signature=False,
-                               log_level=DEBUG, error_level=DEBUG,
-                               error_list=TestJarsignerErrorList)
-        if status == 0:
-            self.info("Passphrases are good.")
-        elif status < 0:
-            self.log("Encountered errors while trying to sign!",
-                     level=error_level)
-        else:
-            self.log("Unable to verify passphrases!",
-                     level=error_level)
-        return status
-
-    def verify_passphrases(self):
-        c = self.config
-        self._verify_passphrases(c['keystore'], c['key_alias'])
-
-    def postflight_passphrase(self):
-        self.verify_passphrases()
-
-    def sign_apk(self, apk, keystore, storepass, keypass, key_alias,
-                 remove_signature=True, error_list=None,
-                 log_level=INFO, error_level=ERROR):
-        """
-        Signs an apk with jarsigner.
-        """
-        jarsigner = self.query_exe('jarsigner')
-        if remove_signature:
-            status = self.unsign_apk(apk)
-            if status:
-                self.error("Can't remove signature in %s!" % apk)
-                return -1
-        if error_list is None:
-            error_list = JarsignerErrorList[:]
-        # This needs to run silently, so no run_command() or
-        # get_output_from_command() (though I could add a
-        # suppress_command_echo=True or something?)
-        self.log("(signing %s)" % apk, level=log_level)
-        try:
-            p = subprocess.Popen([jarsigner, "-keystore", keystore,
-                                 "-storepass", storepass,
-                                 "-keypass", keypass,
-                                 apk, key_alias],
-                                 stdout=subprocess.PIPE,
-                                 stderr=subprocess.STDOUT,
-                                 bufsize=0)
-        except OSError:
-            self.exception("Error while signing %s (missing %s?):" % (apk, jarsigner))
-            return -2
-        except ValueError:
-            self.exception("Popen called with invalid arguments during signing?")
-            return -3
-        parser = OutputParser(config=self.config, log_obj=self.log_obj,
-                              error_list=error_list)
-        loop = True
-        while loop:
-            if p.poll() is not None:
-                """Avoid losing the final lines of the log?"""
-                loop = False
-            for line in p.stdout:
-                parser.add_lines(line)
-        if parser.num_errors:
-            self.log("(failure)", level=error_level)
-        else:
-            self.log("(success)", level=log_level)
-        return parser.num_errors
-
-    def unsign_apk(self, apk, **kwargs):
-        zip_bin = self.query_exe("zip")
-        return self.run_command([zip_bin, apk, '-d', 'META-INF/*'],
-                                error_list=UnsignApkErrorList,
-                                success_codes=[0, 12],
-                                return_type='num_errors', **kwargs)
-
-    def align_apk(self, unaligned_apk, aligned_apk, error_level=ERROR):
-        """
-        Zipalign apk.
-        Returns None on success, not None on failure.
-        """
-        dirs = self.query_abs_dirs()
-        zipalign = self.query_exe("zipalign")
-        if self.run_command([zipalign, '-f', '4',
-                             unaligned_apk, aligned_apk],
-                            return_type='num_errors',
-                            cwd=dirs['abs_work_dir'],
-                            error_list=ZipalignErrorList):
-            self.log("Unable to zipalign %s to %s!" % (unaligned_apk, aligned_apk), level=error_level)
-            return -1
--- a/testing/mozharness/mozharness/base/transfer.py
+++ b/testing/mozharness/mozharness/base/transfer.py
@@ -22,117 +22,34 @@ from mozharness.base.log import DEBUG, E
 
 # TransferMixin {{{1
 class TransferMixin(object):
     """
     Generic transfer methods.
 
     Dependent on BaseScript.
     """
-    def rsync_upload_directory(self, local_path, ssh_key, ssh_user,
-                               remote_host, remote_path,
-                               rsync_options=None,
-                               error_level=ERROR,
-                               create_remote_directory=True,
-                               ):
-        """
-        Create a remote directory and upload the contents of
-        a local directory to it via rsync+ssh.
-
-        Returns:
-            None: on success
-              -1: if local_path is not a directory
-              -2: if the remote_directory cannot be created
-                  (it only makes sense if create_remote_directory is True)
-              -3: rsync fails to copy to the remote directory
-        """
-        dirs = self.query_abs_dirs()
-        self.info("Uploading the contents of %s to %s:%s" % (local_path, remote_host, remote_path))
-        rsync = self.query_exe("rsync")
-        ssh = self.query_exe("ssh")
-        if rsync_options is None:
-            rsync_options = ['-azv']
-        if not os.path.isdir(local_path):
-            self.log("%s isn't a directory!" % local_path,
-                     level=ERROR)
-            return -1
-        if create_remote_directory:
-            mkdir_error_list = [{
-                'substr': r'''exists but is not a directory''',
-                'level': ERROR
-            }] + SSHErrorList
-            if self.run_command([ssh, '-oIdentityFile=%s' % ssh_key,
-                                 '%s@%s' % (ssh_user, remote_host),
-                                 'mkdir', '-p', remote_path],
-                                cwd=dirs['abs_work_dir'],
-                                return_type='num_errors',
-                                error_list=mkdir_error_list):
-                self.log("Unable to create remote directory %s:%s!" % (remote_host, remote_path), level=error_level)
-                return -2
-        if self.run_command([rsync, '-e',
-                             '%s -oIdentityFile=%s' % (ssh, ssh_key)
-                             ] + rsync_options + ['.',
-                            '%s@%s:%s/' % (ssh_user, remote_host, remote_path)],
-                            cwd=local_path,
-                            return_type='num_errors',
-                            error_list=SSHErrorList):
-            self.log("Unable to rsync %s to %s:%s!" % (local_path, remote_host, remote_path), level=error_level)
-            return -3
-
-    def rsync_download_directory(self, ssh_key, ssh_user, remote_host,
-                                 remote_path, local_path,
-                                 rsync_options=None,
-                                 error_level=ERROR,
-                                 ):
-        """
-        rsync+ssh the content of a remote directory to local_path
-
-        Returns:
-            None: on success
-              -1: if local_path is not a directory
-              -3: rsync fails to download from the remote directory
-        """
-        self.info("Downloading the contents of %s:%s to %s" % (remote_host, remote_path, local_path))
-        rsync = self.query_exe("rsync")
-        ssh = self.query_exe("ssh")
-        if rsync_options is None:
-            rsync_options = ['-azv']
-        if not os.path.isdir(local_path):
-            self.log("%s isn't a directory!" % local_path,
-                     level=error_level)
-            return -1
-        if self.run_command([rsync, '-e',
-                             '%s -oIdentityFile=%s' % (ssh, ssh_key)
-                             ] + rsync_options + [
-                            '%s@%s:%s/' % (ssh_user, remote_host, remote_path),
-                            '.'],
-                            cwd=local_path,
-                            return_type='num_errors',
-                            error_list=SSHErrorList):
-            self.log("Unable to rsync %s:%s to %s!" % (remote_host, remote_path, local_path), level=error_level)
-            return -3
-
     def load_json_from_url(self, url, timeout=30, log_level=DEBUG):
         self.log("Attempting to download %s; timeout=%i" % (url, timeout),
                  level=log_level)
         try:
             r = urllib2.urlopen(url, timeout=timeout)
             j = json.load(r)
             self.log(pprint.pformat(j), level=log_level)
-        except:
+        except BaseException:
             self.exception(message="Unable to download %s!" % url)
             raise
         return j
 
     def scp_upload_directory(self, local_path, ssh_key, ssh_user,
                              remote_host, remote_path,
                              scp_options=None,
                              error_level=ERROR,
                              create_remote_directory=True,
-                            ):
+                             ):
         """
         Create a remote directory and upload the contents of
         a local directory to it via scp only
 
         Returns:
             None: on success
               -1: if local_path is not a directory
               -2: if the remote_directory cannot be created
@@ -155,18 +72,22 @@ class TransferMixin(object):
                 'level': ERROR
             }] + SSHErrorList
             if self.run_command([ssh, '-oIdentityFile=%s' % ssh_key,
                                  '%s@%s' % (ssh_user, remote_host),
                                  'mkdir', '-p', remote_path],
                                 cwd=dirs['abs_work_dir'],
                                 return_type='num_errors',
                                 error_list=mkdir_error_list):
-                self.log("Unable to create remote directory %s:%s!" % (remote_host, remote_path), level=error_level)
+                self.log(
+                    "Unable to create remote directory %s:%s!"
+                    % (remote_host, remote_path), level=error_level)
                 return -2
         if self.run_command([scp, '-oIdentityFile=%s' % ssh_key,
                              scp_options, '.',
                              '%s@%s:%s/' % (ssh_user, remote_host, remote_path)],
                             cwd=local_path,
                             return_type='num_errors',
                             error_list=SSHErrorList):
-            self.log("Unable to scp %s to %s:%s!" % (local_path, remote_host, remote_path), level=error_level)
-            return -3
\ No newline at end of file
+            self.log(
+                "Unable to scp %s to %s:%s!"
+                % (local_path, remote_host, remote_path), level=error_level)
+            return -3
--- a/testing/mozharness/mozharness/base/vcs/gittool.py
+++ b/testing/mozharness/mozharness/base/vcs/gittool.py
@@ -74,17 +74,18 @@ class GittoolVCS(ScriptMixin, LogMixin):
         cmd = self.gittool[:]
         if branch:
             cmd.extend(['-b', branch])
         if revision:
             cmd.extend(['-r', revision])
         if clean:
             cmd.append('--clean')
 
-        for base_mirror_url in self.config.get('gittool_base_mirror_urls', self.config.get('vcs_base_mirror_urls', [])):
+        for base_mirror_url in self.config.get(
+                'gittool_base_mirror_urls', self.config.get('vcs_base_mirror_urls', [])):
             bits = urlparse.urlparse(repo)
             mirror_url = urlparse.urljoin(base_mirror_url, bits.path)
             cmd.extend(['--mirror', mirror_url])
 
         cmd.extend([repo, dest])
         parser = GittoolParser(config=self.config, log_obj=self.log_obj,
                                error_list=GitErrorList)
         retval = self.run_command(cmd, error_list=GitErrorList, env=env, output_parser=parser)
--- a/testing/mozharness/mozharness/base/vcs/mercurial.py
+++ b/testing/mozharness/mozharness/base/vcs/mercurial.py
@@ -284,17 +284,18 @@ class MercurialVCS(ScriptMixin, LogMixin
         """Check for outgoing changesets present in a repo"""
         self.info("Checking for outgoing changesets from %s to %s." % (src, remote))
         cmd = self.hg + ['-q', 'out', '--template', '{node} {branches}\n']
         cmd.extend(self.common_args(**kwargs))
         cmd.append(remote)
         if os.path.exists(src):
             try:
                 revs = []
-                for line in self.get_output_from_command(cmd, cwd=src, throw_exception=True).rstrip().split("\n"):
+                for line in self.get_output_from_command(
+                        cmd, cwd=src, throw_exception=True).rstrip().split("\n"):
                     try:
                         rev, branch = line.split()
                     # Mercurial displays no branch at all if the revision
                     # is on "default"
                     except ValueError:
                         rev = line.rstrip()
                         branch = "default"
                     revs.append((rev, branch))
deleted file mode 100644
--- a/testing/mozharness/mozharness/base/vcs/vcssync.py
+++ /dev/null
@@ -1,101 +0,0 @@
-#!/usr/bin/env python
-# ***** BEGIN LICENSE BLOCK *****
-# 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/.
-# ***** END LICENSE BLOCK *****
-"""Generic VCS support.
-"""
-
-import os
-import smtplib
-import sys
-import time
-
-sys.path.insert(1, os.path.dirname(os.path.dirname(os.path.dirname(sys.path[0]))))
-
-from mozharness.base.log import ERROR, INFO
-from mozharness.base.vcs.vcsbase import VCSScript
-
-
-# VCSSyncScript {{{1
-class VCSSyncScript(VCSScript):
-    start_time = time.time()
-
-    def __init__(self, **kwargs):
-        super(VCSSyncScript, self).__init__(**kwargs)
-
-    def notify(self, message=None, fatal=False):
-        """ Email people in the notify_config (depending on status and failure_only)
-            """
-        c = self.config
-        dirs = self.query_abs_dirs()
-        job_name = c.get('job_name', c.get('conversion_dir', os.getcwd()))
-        end_time = time.time()
-        seconds = int(end_time - self.start_time)
-        self.info("Job took %d seconds." % seconds)
-        subject = "[vcs2vcs] Successful conversion for %s" % job_name
-        text = ''
-        error_contents = ''
-        max_log_sample_size = c.get('email_max_log_sample_size') # default defined in vcs_sync.py
-        error_log = os.path.join(dirs['abs_log_dir'], self.log_obj.log_files[ERROR])
-        info_log = os.path.join(dirs['abs_log_dir'], self.log_obj.log_files[INFO])
-        if os.path.exists(error_log) and os.path.getsize(error_log) > 0:
-            error_contents = self.get_output_from_command(
-                ["egrep", "-C5", "^[0-9:]+ +(ERROR|CRITICAL|FATAL) -", info_log],
-                silent=True,
-            )
-        if fatal:
-            subject = "[vcs2vcs] Failed conversion for %s" % job_name
-            text = ''
-            if len(message) > max_log_sample_size:
-                text += '*** Message below has been truncated: it was %s characters, and has been reduced to %s characters:\n\n' % (len(message), max_log_sample_size)
-            text += message[0:max_log_sample_size] + '\n\n' # limit message to max_log_sample_size in size (large emails fail to send)
-        if not self.successful_repos:
-            subject = "[vcs2vcs] Successful no-op conversion for %s" % job_name
-        if error_contents and not fatal:
-            subject += " with warnings"
-        if self.successful_repos:
-            if len(self.successful_repos) <= 5:
-                subject += ' (' + ','.join(self.successful_repos) + ')'
-            else:
-                text += "Successful repos: %s\n\n" % ', '.join(self.successful_repos)
-        subject += ' (%ds)' % seconds
-        if self.summary_list:
-            text += 'Summary is non-zero:\n\n'
-            for item in self.summary_list:
-                text += '%s - %s\n' % (item['level'], item['message'])
-        if not fatal and error_contents and not self.summary_list:
-            text += 'Summary is empty; the below errors have probably been auto-corrected.\n\n'
-        if error_contents:
-            if len(error_contents) > max_log_sample_size:
-                text += '\n*** Message below has been truncated: it was %s characters, and has been reduced to %s characters:\n' % (len(error_contents), max_log_sample_size)
-            text += '\n%s\n\n' % error_contents[0:max_log_sample_size] # limit message to 100KB in size (large emails fail to send)
-        if not text:
-            subject += " <EOM>"
-        for notify_config in c.get('notify_config', []):
-            if not fatal:
-                if notify_config.get('failure_only'):
-                    self.info("Skipping notification for %s (failure_only)" % notify_config['to'])
-                    continue
-                if not text and notify_config.get('skip_empty_messages'):
-                    self.info("Skipping notification for %s (skip_empty_messages)" % notify_config['to'])
-                    continue
-            fromaddr = notify_config.get('from', c['default_notify_from'])
-            message = '\r\n'.join((
-                "From: %s" % fromaddr,
-                "To: %s" % notify_config['to'],
-                "CC: %s" % ','.join(notify_config.get('cc', [])),
-                "Subject: %s" % subject,
-                "",
-                text
-            ))
-            toaddrs = [notify_config['to']] + notify_config.get('cc', [])
-            # TODO allow for a different smtp server
-            # TODO deal with failures
-            server = smtplib.SMTP('localhost')
-            self.retry(
-                server.sendmail,
-                args=(fromaddr, toaddrs, message),
-            )
-            server.quit()
--- a/testing/mozharness/mozharness/lib/python/authentication.py
+++ b/testing/mozharness/mozharness/lib/python/authentication.py
@@ -7,16 +7,17 @@
 """module for http authentication operations"""
 import getpass
 import os
 
 CREDENTIALS_PATH = os.path.expanduser("~/.mozilla/credentials.cfg")
 DIRNAME = os.path.dirname(CREDENTIALS_PATH)
 LDAP_PASSWORD = None
 
+
 def get_credentials():
     """ Returns http credentials.
 
     The user's email address is stored on disk (for convenience in the future)
     while the password is requested from the user on first invocation.
     """
     global LDAP_PASSWORD
     if not os.path.exists(DIRNAME):
@@ -41,13 +42,14 @@ def get_credentials():
         os.chmod(CREDENTIALS_PATH, 0600)
 
     if not LDAP_PASSWORD:
         print "Please enter your LDAP password (we won't store it):"
         LDAP_PASSWORD = getpass.getpass()
 
     return https_username, LDAP_PASSWORD
 
+
 def get_credentials_path():
     if os.path.isfile(CREDENTIALS_PATH):
         get_credentials()
 
     return CREDENTIALS_PATH
--- a/testing/mozharness/mozharness/mozilla/building/buildbase.py
+++ b/testing/mozharness/mozharness/mozilla/building/buildbase.py
@@ -18,25 +18,26 @@ import time
 import uuid
 import copy
 import glob
 
 # import the power of mozharness ;)
 import sys
 from datetime import datetime
 import re
-from mozharness.base.config import BaseConfig, parse_config_file, DEFAULT_CONFIG_PATH
+from mozharness.base.config import (
+    BaseConfig, parse_config_file, DEFAULT_CONFIG_PATH,
+)
 from mozharness.base.log import ERROR, OutputParser, FATAL
 from mozharness.base.script import PostScriptRun
 from mozharness.base.vcs.vcsbase import MercurialScript
 from mozharness.mozilla.automation import (
     AutomationMixin,
     EXIT_STATUS_DICT,
     TBPL_STATUS_DICT,
-    TBPL_EXCEPTION,
     TBPL_FAILURE,
     TBPL_RETRY,
     TBPL_WARNING,
     TBPL_SUCCESS,
     TBPL_WORST_LEVEL_TUPLE,
 )
 from mozharness.mozilla.secrets import SecretsMixin
 from mozharness.mozilla.testing.errors import TinderBoxPrintRe
@@ -57,17 +58,17 @@ ERROR_MSGS = {
 Please make sure that "repo" is in your config.',
     'comments_undetermined': '"comments" could not be determined. This may be \
 because it was a forced build.',
     'tooltool_manifest_undetermined': '"tooltool_manifest_src" not set, \
 Skipping run_tooltool...',
 }
 
 
-### Output Parsers
+# Output Parsers
 
 TBPL_UPLOAD_ERRORS = [
     {
         'regex': re.compile("Connection timed out"),
         'level': TBPL_RETRY,
     },
     {
         'regex': re.compile("Connection reset by peer"),
@@ -159,21 +160,23 @@ class CheckTestCompleteParser(OutputPars
         # Print the summary.
         summary = tbox_print_summary(self.pass_count,
                                      self.fail_count,
                                      self.leaked)
         self.info("TinderboxPrint: check<br/>%s\n" % summary)
 
         return self.tbpl_status
 
+
 class MozconfigPathError(Exception):
     """
     There was an error getting a mozconfig path from a mozharness config.
     """
 
+
 def get_mozconfig_path(script, config, dirs):
     """
     Get the path to the mozconfig file to use from a mozharness config.
 
     :param script: The object to interact with the filesystem through.
     :type script: ScriptMixin:
 
     :param config: The mozharness config to inspect.
@@ -253,31 +256,29 @@ class BuildingConfig(BaseConfig):
         all_config_dicts = []
         # important config files
         variant_cfg_file = branch_cfg_file = pool_cfg_file = ''
 
         # we want to make the order in which the options were given
         # not matter. ie: you can supply --branch before --build-pool
         # or vice versa and the hierarchy will not be different
 
-        #### The order from highest precedence to lowest is:
-        ## There can only be one of these...
+        # ### The order from highest precedence to lowest is:
+        # # There can only be one of these...
         # 1) build_pool: this can be either staging, pre-prod, and prod cfgs
         # 2) branch: eg: mozilla-central, cedar, cypress, etc
         # 3) build_variant: these could be known like asan and debug
         #                   or a custom config
-        ##
-        ## There can be many of these
+        #
+        # # There can be many of these
         # 4) all other configs: these are any configs that are passed with
         #                       --cfg and --opt-cfg. There order is kept in
         #                       which they were passed on the cmd line. This
         #                       behaviour is maintains what happens by default
         #                       in mozharness
-        ##
-        ####
 
         # so, let's first assign the configs that hold a known position of
         # importance (1 through 3)
         for i, cf in enumerate(all_config_files):
             if options.build_pool:
                 if cf == BuildOptionParser.build_pool_cfg_file:
                     pool_cfg_file = all_config_files[i]
 
@@ -315,18 +316,19 @@ class BuildingConfig(BaseConfig):
                                                search_path=config_paths + [DEFAULT_CONFIG_PATH])
             if branch_configs.get(options.branch or ""):
                 all_config_dicts.append(
                     (branch_cfg_file, branch_configs[options.branch])
                 )
         if pool_cfg_file:
             # take only the specific pool. If we are here, the pool
             # must be present
-            build_pool_configs = parse_config_file(pool_cfg_file,
-                                                   search_path=config_paths + [DEFAULT_CONFIG_PATH])
+            build_pool_configs = parse_config_file(
+                pool_cfg_file,
+                search_path=config_paths + [DEFAULT_CONFIG_PATH])
             all_config_dicts.append(
                 (pool_cfg_file, build_pool_configs[options.build_pool])
             )
         return all_config_dicts
 
 
 # noinspection PyUnusedLocal
 class BuildOptionParser(object):
@@ -357,41 +359,43 @@ class BuildOptionParser(object):
         'fuzzing-debug': 'builds/releng_sub_%s_configs/%s_fuzzing_debug.py',
         'asan-and-debug': 'builds/releng_sub_%s_configs/%s_asan_and_debug.py',
         'asan-tc-and-debug': 'builds/releng_sub_%s_configs/%s_asan_tc_and_debug.py',
         'stat-and-debug': 'builds/releng_sub_%s_configs/%s_stat_and_debug.py',
         'code-coverage-debug': 'builds/releng_sub_%s_configs/%s_code_coverage_debug.py',
         'code-coverage-opt': 'builds/releng_sub_%s_configs/%s_code_coverage_opt.py',
         'source': 'builds/releng_sub_%s_configs/%s_source.py',
         'noopt-debug': 'builds/releng_sub_%s_configs/%s_noopt_debug.py',
-        'api-16-gradle-dependencies': 'builds/releng_sub_%s_configs/%s_api_16_gradle_dependencies.py',
+        'api-16-gradle-dependencies':
+            'builds/releng_sub_%s_configs/%s_api_16_gradle_dependencies.py',
         'api-16': 'builds/releng_sub_%s_configs/%s_api_16.py',
         'api-16-artifact': 'builds/releng_sub_%s_configs/%s_api_16_artifact.py',
         'api-16-debug': 'builds/releng_sub_%s_configs/%s_api_16_debug.py',
         'api-16-debug-ccov': 'builds/releng_sub_%s_configs/%s_api_16_debug_ccov.py',
         'api-16-debug-artifact': 'builds/releng_sub_%s_configs/%s_api_16_debug_artifact.py',
         'api-16-gradle': 'builds/releng_sub_%s_configs/%s_api_16_gradle.py',
         'api-16-gradle-artifact': 'builds/releng_sub_%s_configs/%s_api_16_gradle_artifact.py',
-        'api-16-without-google-play-services': 'builds/releng_sub_%s_configs/%s_api_16_without_google_play_services.py',
+        'api-16-without-google-play-services':
+            'builds/releng_sub_%s_configs/%s_api_16_without_google_play_services.py',
         'rusttests': 'builds/releng_sub_%s_configs/%s_rusttests.py',
         'rusttests-debug': 'builds/releng_sub_%s_configs/%s_rusttests_debug.py',
         'x86': 'builds/releng_sub_%s_configs/%s_x86.py',
         'x86-artifact': 'builds/releng_sub_%s_configs/%s_x86_artifact.py',
         'x86-fuzzing-debug': 'builds/releng_sub_%s_configs/%s_x86_fuzzing_debug.py',
         'x86_64': 'builds/releng_sub_%s_configs/%s_x86_64.py',
         'x86_64-artifact': 'builds/releng_sub_%s_configs/%s_x86_64_artifact.py',
         'api-16-partner-sample1': 'builds/releng_sub_%s_configs/%s_api_16_partner_sample1.py',
         'aarch64': 'builds/releng_sub_%s_configs/%s_aarch64.py',
         'android-test': 'builds/releng_sub_%s_configs/%s_test.py',
         'android-test-ccov': 'builds/releng_sub_%s_configs/%s_test_ccov.py',
         'android-checkstyle': 'builds/releng_sub_%s_configs/%s_checkstyle.py',
         'android-lint': 'builds/releng_sub_%s_configs/%s_lint.py',
         'android-findbugs': 'builds/releng_sub_%s_configs/%s_findbugs.py',
         'android-geckoview-docs': 'builds/releng_sub_%s_configs/%s_geckoview_docs.py',
-        'valgrind' : 'builds/releng_sub_%s_configs/%s_valgrind.py',
+        'valgrind': 'builds/releng_sub_%s_configs/%s_valgrind.py',
         'artifact': 'builds/releng_sub_%s_configs/%s_artifact.py',
         'debug-artifact': 'builds/releng_sub_%s_configs/%s_debug_artifact.py',
         'devedition': 'builds/releng_sub_%s_configs/%s_devedition.py',
         'tup': 'builds/releng_sub_%s_configs/%s_tup.py',
     }
     build_pool_cfg_file = 'builds/build_pool_specifics.py'
     branch_cfg_file = 'builds/branch_specifics.py'
 
@@ -760,23 +764,22 @@ or run without that action (ie: --no-{ac
                 sys.executable, os.path.join(dirs['abs_src_dir'], 'mach'), 'python',
                 print_conf_setting_path, app_ini_path,
                 'App', prop
             ]
             env = self.query_build_env()
             # dirs['abs_obj_dir'] can be different from env['MOZ_OBJDIR'] on
             # mac, and that confuses mach.
             del env['MOZ_OBJDIR']
-            return self.get_output_from_command(cmd,
-                cwd=dirs['abs_obj_dir'], env=env)
+            return self.get_output_from_command(
+                cmd, cwd=dirs['abs_obj_dir'], env=env)
         else:
             return None
 
     def query_buildid(self):
-        c = self.config
         if self.buildid:
             return self.buildid
 
         # for taskcluster, we pass MOZ_BUILD_DATE into mozharness as an
         # environment variable, only to have it pass the same value out with
         # the same name.
         buildid = os.environ.get('MOZ_BUILD_DATE')
 
@@ -1120,17 +1123,18 @@ or run without that action (ie: --no-{ac
     def _run_mach_command_in_build_env(self, args):
         """Run a mach command in a build context."""
         env = self.query_build_env()
         env.update(self.query_mach_build_env())
 
         dirs = self.query_abs_dirs()
 
         if 'MOZILLABUILD' in os.environ:
-            # We found many issues with intermittent build failures when not invoking mach via bash.
+            # We found many issues with intermittent build failures when not
+            # invoking mach via bash.
             # See bug 1364651 before considering changing.
             mach = [
                 os.path.join(os.environ['MOZILLABUILD'], 'msys', 'bin', 'bash.exe'),
                 os.path.join(dirs['abs_src_dir'], 'mach')
             ]
         else:
             mach = [sys.executable, 'mach']
 
@@ -1193,34 +1197,36 @@ or run without that action (ie: --no-{ac
             'echo-variable-PACKAGE',
             'AB_CD=multi',
         ]
         package_filename = self.get_output_from_command(
             package_cmd,
             cwd=objdir,
         )
         if not package_filename:
-            self.fatal("Unable to determine the package filename for the multi-l10n build. Was trying to run: %s" % package_cmd)
+            self.fatal(
+                "Unable to determine the package filename for the multi-l10n build. "
+                "Was trying to run: %s" % package_cmd)
 
         self.info('Multi-l10n package filename is: %s' % package_filename)
 
         parser = MakeUploadOutputParser(config=self.config,
                                         log_obj=self.log_obj,
                                         )
         upload_cmd = ['make', 'upload', 'AB_CD=multi']
         self.run_command(upload_cmd,
                          env=self.query_mach_build_env(multiLocale=False),
                          cwd=objdir, halt_on_failure=True,
                          output_parser=parser)
         upload_files_cmd = [
             'make',
             'echo-variable-UPLOAD_FILES',
             'AB_CD=multi',
         ]
-        output = self.get_output_from_command(
+        self.get_output_from_command(
             upload_files_cmd,
             cwd=objdir,
         )
 
     def postflight_build(self):
         """grabs properties from post build and calls ccache -s"""
         # A list of argument lists.  Better names gratefully accepted!
         mach_commands = self.config.get('postflight_build_mach_commands', [])
@@ -1356,17 +1362,16 @@ or run without that action (ie: --no-{ac
                 continue
             data['subtests'].append({
                 'name': phase['name'],
                 'value': phase['duration'],
             })
 
         return data
 
-
     def _load_sccache_stats(self):
         stats_file = os.path.join(
             self.query_abs_dirs()['abs_obj_dir'], 'sccache-stats.json'
         )
         if not os.path.exists(stats_file):
             self.info('%s does not exist; not loading sccache stats' % stats_file)
             return
 
@@ -1451,33 +1456,33 @@ or run without that action (ie: --no-{ac
                             self.fatal('should not see %s (%s) multiple times!'
                                        % (name, path))
                         subtests[name] = size
                 for name in subtests:
                     self.info('Size of %s: %s bytes' % (name,
                                                         subtests[name]))
                     size_measurements.append(
                         {'name': name, 'value': subtests[name]})
-            except:
+            except Exception:
                 self.info('Unable to search %s for component sizes.' % installer)
                 size_measurements = []
 
         if not installer_size and not size_measurements:
             return
 
         # We want to always collect metrics. But alerts for installer size are
         # only use for builds with ship. So nix the alerts for builds we don't
         # ship.
         def filter_alert(alert):
             if not self._is_configuration_shipped():
                 alert['shouldAlert'] = False
 
             return alert
 
-        if installer.endswith('.apk'): # Android
+        if installer.endswith('.apk'):  # Android
             yield filter_alert({
                 "name": "installer size",
                 "value": installer_size,
                 "alertChangeType": "absolute",
                 "alertThreshold": (200 * 1024),
                 "subtests": size_measurements
             })
         else:
@@ -1488,18 +1493,16 @@ or run without that action (ie: --no-{ac
                 "alertThreshold": (100 * 1024),
                 "subtests": size_measurements
             })
 
     def _get_sections(self, file, filter=None):
         """
         Returns a dictionary of sections and their sizes.
         """
-        from StringIO import StringIO
-
         # Check for `rust_size`, our cross platform version of size. It should
         # be installed by tooltool in $abs_src_dir/rust-size/rust-size
         rust_size = os.path.join(self.query_abs_dirs()['abs_src_dir'],
                                  'rust-size', 'rust-size')
         size_prog = self.which(rust_size)
         if not size_prog:
             self.info("Couldn't find `rust-size` program")
             return {}
--- a/testing/mozharness/mozharness/mozilla/checksums.py
+++ b/testing/mozharness/mozharness/mozilla/checksums.py
@@ -1,21 +1,30 @@
 def parse_checksums_file(checksums):
-    """Parses checksums files that the build system generates and uploads:
-        https://hg.mozilla.org/mozilla-central/file/default/build/checksums.py"""
+    """
+    Parses checksums files that the build system generates and uploads:
+    https://hg.mozilla.org/mozilla-central/file/default/build/checksums.py
+    """
     fileInfo = {}
     for line in checksums.splitlines():
         hash_, type_, size, file_ = line.split(None, 3)
         size = int(size)
         if size < 0:
             raise ValueError("Found negative value (%d) for size." % size)
         if file_ not in fileInfo:
             fileInfo[file_] = {"hashes": {}}
         # If the file already exists, make sure that the size matches the
         # previous entry.
         elif fileInfo[file_]['size'] != size:
-            raise ValueError("Found different sizes for same file %s (%s and %s)" % (file_, fileInfo[file_]['size'], size))
+            raise ValueError(
+                "Found different sizes for same file %s (%s and %s)"
+                % (file_, fileInfo[file_]['size'], size))
         # Same goes for the hash.
-        elif type_ in fileInfo[file_]['hashes'] and fileInfo[file_]['hashes'][type_] != hash_:
-            raise ValueError("Found different %s hashes for same file %s (%s and %s)" % (type_, file_, fileInfo[file_]['hashes'][type_], hash_))
+        elif (
+            type_ in fileInfo[file_]['hashes']
+            and fileInfo[file_]['hashes'][type_] != hash_
+        ):
+            raise ValueError(
+                "Found different %s hashes for same file %s (%s and %s)"
+                % (type_, file_, fileInfo[file_]['hashes'][type_], hash_))
         fileInfo[file_]['size'] = size
         fileInfo[file_]['hashes'][type_] = hash_
     return fileInfo
--- a/testing/mozharness/mozharness/mozilla/fetches.py
+++ b/testing/mozharness/mozharness/mozilla/fetches.py
@@ -17,17 +17,18 @@ class FetchesMixin(object):
 
     @property
     def fetch_script(self):
         if getattr(self, '_fetch_script', None):
             return self._fetch_script
 
         self._fetch_script = find_executable('fetch-content')
         if not self._fetch_script and 'GECKO_PATH' in os.environ:
-            self._fetch_script = os.path.join(os.environ['GECKO_PATH'],
+            self._fetch_script = os.path.join(
+                os.environ['GECKO_PATH'],
                 'taskcluster', 'script', 'misc', 'fetch-content')
         return self._fetch_script
 
     def fetch_content(self):
         if not os.environ.get('MOZ_FETCHES'):
             self.warning('no fetches to download')
             return
 
--- a/testing/mozharness/mozharness/mozilla/l10n/multi_locale_build.py
+++ b/testing/mozharness/mozharness/mozilla/l10n/multi_locale_build.py
@@ -152,11 +152,12 @@ class MultiLocaleBuild(LocalesMixin, Mer
 
     def _process_command(self, **kwargs):
         """Stub wrapper function that allows us to call scratchbox in
            MaemoMultiLocaleBuild.
 
         """
         return self.run_command(**kwargs)
 
+
 # __main__ {{{1
 if __name__ == '__main__':
     pass
--- a/testing/mozharness/mozharness/mozilla/merkle.py
+++ b/testing/mozharness/mozharness/mozilla/merkle.py
@@ -1,24 +1,26 @@
-#!/usr/bin/env python
+import struct
 
-import struct
 
 def _round2(n):
     k = 1
     while k < n:
         k <<= 1
     return k >> 1
 
+
 def _leaf_hash(hash_fn, leaf):
     return hash_fn(b'\x00' + leaf).digest()
 
+
 def _pair_hash(hash_fn, left, right):
     return hash_fn(b'\x01' + left + right).digest()
 
+
 class InclusionProof:
     """
     Represents a Merkle inclusion proof for purposes of serialization,
     deserialization, and verification of the proof.  The format for inclusion
     proofs in RFC 6962-bis is as follows:
 
         opaque LogID<2..127>;
         opaque NodeHash<32..2^8-1>;
@@ -51,17 +53,17 @@ class InclusionProof:
     @staticmethod
     def from_rfc6962_bis(serialized):
         start = 0
         read = 1
         if len(serialized) < start + read:
             raise Exception('Inclusion proof too short for log ID header')
         log_id_len, = struct.unpack('B', serialized[start:start+read])
         start += read
-        start += log_id_len # Ignore the log ID itself
+        start += log_id_len  # Ignore the log ID itself
 
         read = 8 + 8 + 2
         if len(serialized) < start + read:
             raise Exception('Inclusion proof too short for middle section')
         tree_size, leaf_index, path_len = struct.unpack('!QQH', serialized[start:start+read])
         start += read
 
         path_elements = []
@@ -113,20 +115,20 @@ class InclusionProof:
         for i, elem in enumerate(self.path_elements):
             if lr[i]:
                 node = _pair_hash(hash_fn, node, elem)
             else:
                 node = _pair_hash(hash_fn, elem, node)
 
         return node
 
-
     def verify(self, hash_fn, leaf, leaf_index, tree_size, tree_head):
         return self._expected_head(hash_fn, leaf, leaf_index, tree_size) == tree_head
 
+
 class MerkleTree:
     """
     Implements a Merkle tree on a set of data items following the
     structure defined in RFC 6962-bis.  This allows us to create a
     single hash value that summarizes the data (the 'head'), and an
     'inclusion proof' for each element that connects it to the head.
 
     https://tools.ietf.org/html/draft-ietf-trans-rfc6962-bis-24
deleted file mode 100755
--- a/testing/mozharness/mozharness/mozilla/release.py
+++ /dev/null
@@ -1,48 +0,0 @@
-#!/usr/bin/env python
-# ***** BEGIN LICENSE BLOCK *****
-# 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/.
-# ***** END LICENSE BLOCK *****
-"""release.py
-
-"""
-
-import os
-from distutils.version import LooseVersion, StrictVersion
-
-
-# ReleaseMixin {{{1
-class ReleaseMixin():
-    release_config = {}
-
-    def query_release_config(self):
-        if self.release_config:
-            return self.release_config
-        self.info("No release config file; using default config.")
-        for key in ('version', 'buildnum',
-                    'ftp_server', 'ftp_user', 'ftp_ssh_key'):
-            self.release_config[key] = c[key]
-        self.info("Release config:\n%s" % self.release_config)
-        return self.release_config
-
-
-def get_previous_version(version, partial_versions):
-    """ The patcher config bumper needs to know the exact previous version
-    We use LooseVersion for ESR because StrictVersion can't parse the trailing
-    'esr', but StrictVersion otherwise because it can sort X.0bN lower than X.0.
-    The current version is excluded to avoid an error if build1 is aborted
-    before running the updates builder and now we're doing build2
-    """
-    if version.endswith('esr'):
-        return str(max(LooseVersion(v) for v in partial_versions if
-                       v != version))
-    else:
-        # StrictVersion truncates trailing zero in versions with more than 1
-        # dot. Compose a structure that will be sorted by StrictVersion and
-        # return untouched version
-        composed = sorted([(v, StrictVersion(v)) for v in partial_versions if
-                           v != version], key=lambda x: x[1], reverse=True)
-        return composed[0][0]
-
-
deleted file mode 100644
--- a/testing/mozharness/mozharness/mozilla/repo_manifest.py
+++ /dev/null
@@ -1,226 +0,0 @@
-"""
-Module for handling repo style XML manifests
-"""
-import xml.dom.minidom
-import os
-import re
-
-
-def load_manifest(filename):
-    """
-    Loads manifest from `filename` and returns a single flattened manifest
-    Processes any <include name="..." /> nodes recursively
-    Removes projects referenced by <remove-project name="..." /> nodes
-    Abort on unsupported manifest tags
-    Returns the root node of the resulting DOM
-    """
-    doc = xml.dom.minidom.parse(filename)
-
-    # Check that we don't have any unsupported tags
-    to_visit = list(doc.childNodes)
-    while to_visit:
-        node = to_visit.pop()
-        # Skip text nodes
-        if node.nodeType in (node.TEXT_NODE, node.COMMENT_NODE):
-            continue
-
-        if node.tagName not in ('include', 'project', 'remote', 'default', 'manifest', 'copyfile', 'remove-project'):
-            raise ValueError("Unsupported tag: %s" % node.tagName)
-        to_visit.extend(node.childNodes)
-
-    # Find all <include> nodes
-    for i in doc.getElementsByTagName('include'):
-        p = i.parentNode
-
-        # The name attribute is relative to where the original manifest lives
-        inc_filename = i.getAttribute('name')
-        inc_filename = os.path.join(os.path.dirname(filename), inc_filename)
-
-        # Parse the included file
-        inc_doc = load_manifest(inc_filename).documentElement
-        # For all the child nodes in the included manifest, insert into our
-        # manifest just before the include node
-        # We operate on a copy of childNodes because when we reparent `c`, the
-        # list of childNodes is modified.
-        for c in inc_doc.childNodes[:]:
-            p.insertBefore(c, i)
-        # Now we can remove the include node
-        p.removeChild(i)
-
-    # Remove all projects referenced by <remove-project>
-    projects = {}
-    manifest = doc.documentElement
-    to_remove = []
-    for node in manifest.childNodes:
-        # Skip text nodes
-        if node.nodeType in (node.TEXT_NODE, node.COMMENT_NODE):
-            continue
-
-        if node.tagName == 'project':
-            projects[node.getAttribute('name')] = node
-
-        elif node.tagName == 'remove-project':
-            project_node = projects[node.getAttribute('name')]
-            to_remove.append(project_node)
-            to_remove.append(node)
-
-    for r in to_remove:
-        r.parentNode.removeChild(r)
-
-    return doc
-
-
-def rewrite_remotes(manifest, mapping_func, force_all=True):
-    """
-    Rewrite manifest remotes in place
-    Returns the same manifest, with the remotes transformed by mapping_func
-    mapping_func should return a modified remote node, or None if no changes
-    are required
-    If force_all is True, then it is an error for mapping_func to return None;
-    a ValueError is raised in this case
-    """
-    for r in manifest.getElementsByTagName('remote'):
-        m = mapping_func(r)
-        if not m:
-            if force_all:
-                raise ValueError("Wasn't able to map %s" % r.toxml())
-            continue
-
-        r.parentNode.replaceChild(m, r)
-
-
-def add_project(manifest, name, path, remote=None, revision=None):
-    """
-    Adds a project to the manifest in place
-    """
-
-    project = manifest.createElement("project")
-    project.setAttribute('name', name)
-    project.setAttribute('path', path)
-    if remote:
-        project.setAttribute('remote', remote)
-    if revision:
-        project.setAttribute('revision', revision)
-
-    manifest.documentElement.appendChild(project)
-
-
-def remove_project(manifest, name=None, path=None):
-    """
-    Removes a project from manifest.
-    One of name or path must be set. If path is specified, then the project
-    with the given path is removed, otherwise the project with the given name
-    is removed.
-    """
-    assert name or path
-    node = get_project(manifest, name, path)
-    if node:
-        node.parentNode.removeChild(node)
-    return node
-
-
-def get_project(manifest, name=None, path=None):
-    """
-    Gets a project node from the manifest.
-    One of name or path must be set. If path is specified, then the project
-    with the given path is returned, otherwise the project with the given name
-    is returned.
-    """
-    assert name or path
-    for node in manifest.getElementsByTagName('project'):
-        if path is not None and node.getAttribute('path') == path:
-            return node
-        if node.getAttribute('name') == name:
-            return node
-
-
-def get_remote(manifest, name):
-    for node in manifest.getElementsByTagName('remote'):
-        if node.getAttribute('name') == name:
-            return node
-
-
-def get_default(manifest):
-    default = manifest.getElementsByTagName('default')[0]
-    return default
-
-
-def get_project_remote_url(manifest, project):
-    """
-    Gets the remote URL for the given project node. Will return the default
-    remote if the project doesn't explicitly specify one.
-    """
-    if project.hasAttribute('remote'):
-        remote = get_remote(manifest, project.getAttribute('remote'))
-    else:
-        default = get_default(manifest)
-        remote = get_remote(manifest, default.getAttribute('remote'))
-    fetch = remote.getAttribute('fetch')
-    if not fetch.endswith('/'):
-        fetch += '/'
-    return "%s%s" % (fetch, project.getAttribute('name'))
-
-
-def get_project_revision(manifest, project):
-    """
-    Gets the revision for the given project node. Will return the default
-    revision if the project doesn't explicitly specify one.
-    """
-    if project.hasAttribute('revision'):
-        return project.getAttribute('revision')
-    else:
-        default = get_default(manifest)
-        return default.getAttribute('revision')
-
-
-def remove_group(manifest, group):
-    """
-    Removes all projects with groups=`group`
-    """
-    retval = []
-    for node in manifest.getElementsByTagName('project'):
-        if group in node.getAttribute('groups').split(","):
-            node.parentNode.removeChild(node)
-            retval.append(node)
-    return retval
-
-
-def map_remote(r, mappings):
-    """
-    Helper function for mapping git remotes
-    """
-    remote = r.getAttribute('fetch')
-    if remote in mappings:
-        r.setAttribute('fetch', mappings[remote])
-        # Add a comment about where our original remote was
-        comment = r.ownerDocument.createComment("original fetch url was %s" % remote)
-        line = r.ownerDocument.createTextNode("\n")
-        r.parentNode.insertBefore(comment, r)
-        r.parentNode.insertBefore(line, r)
-        return r
-    return None
-
-
-COMMIT_PATTERN = re.compile("[0-9a-f]{40}")
-
-
-def is_commitid(revision):
-    """
-    Returns True if revision looks like a commit id
-    i.e. 40 character string made up of 0-9a-f
-    """
-    return bool(re.match(COMMIT_PATTERN, revision))
-
-
-def cleanup(manifest, depth=0):
-    """
-    Remove any empty text nodes
-    """
-    for n in manifest.childNodes[:]:
-        if n.childNodes:
-            n.normalize()
-        if n.nodeType == n.TEXT_NODE and not n.data.strip():
-            if not n.nextSibling:
-                depth -= 2
-            n.data = "\n" + (" " * depth)
-        cleanup(n, depth + 2)
--- a/testing/mozharness/mozharness/mozilla/secrets.py
+++ b/testing/mozharness/mozharness/mozilla/secrets.py
@@ -3,20 +3,18 @@
 # 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/.
 # ***** END LICENSE BLOCK *****
 """Support for fetching secrets from the secrets API
 """
 
 import os
-import mozharness
 import urllib2
 import json
-from mozharness.base.log import ERROR
 
 
 class SecretsMixin(object):
 
     def _fetch_secret(self, secret_name):
         self.info("fetching secret {} from API".format(secret_name))
         # fetch from http://taskcluster, which points to the taskcluster proxy
         # within a taskcluster task.  Outside of that environment, do not
deleted file mode 100644
--- a/testing/mozharness/mozharness/mozilla/selfserve.py
+++ /dev/null
@@ -1,47 +0,0 @@
-import json
-import site
-
-# SelfServeMixin {{{1
-class SelfServeMixin(object):
-    def _get_session(self):
-        site_packages_path = self.query_python_site_packages_path()
-        site.addsitedir(site_packages_path)
-        import requests
-        session = requests.Session()
-        adapter = requests.adapters.HTTPAdapter(max_retries=5)
-        session.mount("http://", adapter)
-        session.mount("https://", adapter)
-        return session
-
-    def _get_base_url(self):
-        return self.config["selfserve_url"].rstrip("/")
-
-    def trigger_nightly_builds(self, branch, revision, auth):
-        session = self._get_session()
-
-        selfserve_base = self._get_base_url()
-        url = "%s/%s/rev/%s/nightly" % (selfserve_base, branch, revision)
-
-        data = {
-            "revision": revision,
-        }
-        self.info("Triggering nightly builds via %s" % url)
-        return session.post(url, data=data, auth=auth).raise_for_status()
-
-    def trigger_arbitrary_job(self, builder, branch, revision, auth, files=None):
-        session = self._get_session()
-
-        selfserve_base = self._get_base_url()
-        url = "%s/%s/builders/%s/%s" % (selfserve_base, branch, builder, revision)
-
-        data = {
-            "properties": json.dumps({
-                "branch": branch,
-                "revision": revision
-            }),
-        }
-        if files:
-            data["files"] = json.dumps(files)
-
-        self.info("Triggering arbritrary job at %s" % url)
-        return session.post(url, data=data, auth=auth).raise_for_status()
deleted file mode 100644
--- a/testing/mozharness/mozharness/mozilla/signed_certificate_timestamp.py
+++ /dev/null
@@ -1,130 +0,0 @@
-#!/usr/bin/env python
-
-import struct
-import base64
-
-class SignedCertificateTimestamp:
-    """
-    Represents a Signed Certificate Timestamp from a Certificate Transparency
-    log, which is how the log indicates that it has seen and logged a
-    certificate.  The format for SCTs in RFC 6962 is as follows:
-
-        struct {
-            Version sct_version;
-            LogID id;
-            uint64 timestamp;
-            CtExtensions extensions;
-            digitally-signed struct {
-                Version sct_version;
-                SignatureType signature_type = certificate_timestamp;
-                uint64 timestamp;
-                LogEntryType entry_type;
-                select(entry_type) {
-                    case x509_entry: ASN.1Cert;
-                    case precert_entry: PreCert;
-                } signed_entry;
-               CtExtensions extensions;
-            };
-        } SignedCertificateTimestamp;
-
-    Here, the "digitally-signed" is just a fixed struct encoding the algorithm
-    and signature:
-
-        struct {
-            SignatureAndHashAlgorithm algorithm;
-            opaque signature<0..2^16-1>;
-        } DigitallySigned;
-
-    In other words the whole serialized SCT comprises:
-
-      - 1 octet of version = v1 = resp["sct_version"]
-      - 32 octets of LogID = resp["id"]
-      - 8 octets of timestamp = resp["timestamp"]
-      - 2 octets of extensions length + resp["extensions"]
-      - 2+2+N octets of signature
-
-    These are built from RFC 6962 API responses, which are encoded in JSON
-    object of the following form:
-
-        {
-            "sct_version": 0,
-            "id": "...",
-            "timestamp": ...,
-            "extensions": "",
-            "signature": "...",
-        }
-
-    The "signature" field contains the whole DigitallySigned struct.
-    """
-
-    # We only support SCTs from RFC 6962 logs
-    SCT_VERSION = 0
-
-    def __init__(self, response_json=None):
-        self.version = SignedCertificateTimestamp.SCT_VERSION
-
-        if response_json is not None:
-            if response_json['sct_version'] is not SignedCertificateTimestamp.SCT_VERSION:
-                raise Exception('Incorrect version for SCT')
-
-            self.id = base64.b64decode(response_json['id'])
-            self.timestamp = response_json['timestamp']
-            self.signature = base64.b64decode(response_json['signature'])
-
-            self.extensions = b''
-            if 'extensions' in response_json:
-                self.extensions = base64.b64decode(response_json['extensions'])
-
-
-    @staticmethod
-    def from_rfc6962(serialized):
-        start = 0
-        read = 1 + 32 + 8
-        if len(serialized) < start + read:
-            raise Exception('SCT too short for version, log ID, and timestamp')
-        version, = struct.unpack('B', serialized[0])
-        log_id = serialized[1:1+32]
-        timestamp, = struct.unpack('!Q', serialized[1+32:1+32+8])
-        start += read
-
-        if version is not SignedCertificateTimestamp.SCT_VERSION:
-            raise Exception('Incorrect version for SCT')
-
-        read = 2
-        if len(serialized) < start + read:
-            raise Exception('SCT too short for extension length')
-        ext_len, = struct.unpack('!H', serialized[start:start+read])
-        start += read
-
-        read = ext_len
-        if len(serialized) < start + read:
-            raise Exception('SCT too short for extensions')
-        extensions = serialized[start:read]
-        start += read
-
-        read = 4
-        if len(serialized) < start + read:
-            raise Exception('SCT too short for signature header')
-        alg, sig_len, = struct.unpack('!HH', serialized[start:start+read])
-        start += read
-
-        read = sig_len
-        if len(serialized) < start + read:
-            raise Exception('SCT too short for signature')
-        sig = serialized[start:start+read]
-
-        sct = SignedCertificateTimestamp()
-        sct.id = log_id
-        sct.timestamp = timestamp
-        sct.extensions = extensions
-        sct.signature = struct.pack('!HH', alg, sig_len) + sig
-        return sct
-
-
-    def to_rfc6962(self):
-        version = struct.pack("B", self.version)
-        timestamp = struct.pack("!Q", self.timestamp)
-        ext_len = struct.pack("!H", len(self.extensions))
-
-        return version + self.id + timestamp + \
-               ext_len + self.extensions + self.signature
deleted file mode 100644
--- a/testing/mozharness/scripts/desktop_l10n.py
+++ b/testing/mozharness/scripts/desktop_l10n.py
@@ -118,17 +118,16 @@ class DesktopSingleLocale(LocalesMixin, 
             config_options=self.config_options,
             require_config_file=require_config_file,
             **buildscript_kwargs
         )
 
         self.bootstrap_env = None
         self.upload_env = None
         self.revision = None
-        self.version = None
         self.upload_urls = {}
         self.pushdate = None
         # upload_files is a dictionary of files to upload, keyed by locale.
         self.upload_files = {}
 
     def _pre_config_lock(self, rw_config):
         """replaces 'configuration_tokens' with their values, before the
            configuration gets locked. If some of the configuration_tokens
@@ -318,24 +317,16 @@ class DesktopSingleLocale(LocalesMixin, 
         # we want to log all the messages from make
         output = []
         for line in raw_output.split("\n"):
             output.append(line.strip())
         output = " ".join(output).strip()
         self.info('echo-variable-%s: %s' % (variable, output))
         return output
 
-    def query_version(self):
-        """Gets the version from the objdir.
-        Only valid after setup is run."""
-        if self.version:
-            return self.version
-        self.version = self._query_make_variable("MOZ_APP_VERSION")
-        return self.version
-
     def _map(self, func, items):
         """runs func for any item in items, calls the add_failure() for each
            error. It assumes that function returns 0 when successful.
            returns a two element tuple with (success_count, total_count)"""
         success_count = 0
         total_count = len(items)
         name = func.__name__
         for item in items:
--- a/testing/mozharness/scripts/desktop_partner_repacks.py
+++ b/testing/mozharness/scripts/desktop_partner_repacks.py
@@ -11,25 +11,23 @@ This script manages Desktop partner repa
 import os
 import sys
 
 # load modules from parent dir
 sys.path.insert(1, os.path.dirname(sys.path[0]))
 
 from mozharness.base.script import BaseScript
 from mozharness.mozilla.automation import AutomationMixin
-from mozharness.mozilla.release import ReleaseMixin
 from mozharness.mozilla.secrets import SecretsMixin
 from mozharness.base.python import VirtualenvMixin
 from mozharness.base.log import FATAL
 
 
 # DesktopPartnerRepacks {{{1
-class DesktopPartnerRepacks(ReleaseMixin, AutomationMixin,
-                            BaseScript, VirtualenvMixin, SecretsMixin):
+class DesktopPartnerRepacks(AutomationMixin, BaseScript, VirtualenvMixin, SecretsMixin):
     """Manages desktop partner repacks"""
     actions = [
                 "clobber",
                 "get-secrets",
                 "setup",
                 "repack",
                 "summary",
               ]
--- a/testing/mozharness/scripts/mobile_l10n.py
+++ b/testing/mozharness/scripts/mobile_l10n.py
@@ -88,17