Merge mozilla-central to inbound a=merge
authorCoroiu Cristina <ccoroiu@mozilla.com>
Sat, 17 Nov 2018 23:43:13 +0200
changeset 446933 572f525f1afe
parent 446932 3a3c453432c1 (current diff)
parent 446922 77223bb2fac2 (diff)
child 446934 d32110a492f5
push id35059
push usercbrindusan@mozilla.com
push dateSun, 18 Nov 2018 11:17:46 +0000
treeherdermozilla-central@b3ceae83e290 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone65.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 a=merge
taskcluster/ci/mar-signing-l10n/kind.yml
taskcluster/ci/mar-signing/kind.yml
taskcluster/taskgraph/transforms/mar_signing.py
--- a/browser/base/content/browser.xul
+++ b/browser/base/content/browser.xul
@@ -84,16 +84,17 @@
 
 <script type="application/javascript"
 #ifdef BROWSER_XHTML
 xmlns="http://www.w3.org/1999/xhtml"
 #endif
 >
   Services.scriptloader.loadSubScript("chrome://global/content/contentAreaUtils.js", this);
   Services.scriptloader.loadSubScript("chrome://browser/content/tabbrowser.js", this);
+  Services.scriptloader.loadSubScript("chrome://browser/content/search/search-one-offs.js", this);
 
   window.onload = gBrowserInit.onLoad.bind(gBrowserInit);
   window.onunload = gBrowserInit.onUnload.bind(gBrowserInit);
   window.onclose = WindowIsClosing;
 
 #ifdef BROWSER_XHTML
   window.addEventListener("readystatechange", () => {
     // We initially hide the window to prevent layouts during parse. This lets us
@@ -255,24 +256,24 @@ xmlns="http://www.w3.org/1999/xhtml"
            flip="none"
            level="parent">
       <html:div class="urlbarView-body-outer">
         <html:div class="urlbarView-body-inner">
           <!-- TODO: add search suggestions notification -->
           <html:div class="urlbarView-results"/>
         </html:div>
       </html:div>
-      <search-one-offs class="search-one-offs"
-                       compact="true"
-                       includecurrentengine="true"
-                       disabletab="true"/>
+      <hbox class="search-one-offs"
+            compact="true"
+            includecurrentengine="true"
+            disabletab="true"/>
     </panel>
 
-   <!-- for date/time picker. consumeoutsideclicks is set to never, so that
-        clicks on the anchored input box are never consumed. -->
+    <!-- for date/time picker. consumeoutsideclicks is set to never, so that
+         clicks on the anchored input box are never consumed. -->
     <panel id="DateTimePickerPanel"
            type="arrow"
            hidden="true"
            orient="vertical"
            noautofocus="true"
            norolluponanchor="true"
            consumeoutsideclicks="never"
            level="parent"
--- a/browser/base/content/global-scripts.inc
+++ b/browser/base/content/global-scripts.inc
@@ -12,17 +12,16 @@
 xmlns="http://www.w3.org/1999/xhtml"
 #endif
 >
 Components.utils.import("resource://gre/modules/Services.jsm");
 
 for (let script of [
   "chrome://browser/content/browser.js",
   "chrome://browser/content/search/searchbar.js",
-  "chrome://browser/content/search/search-one-offs.js",
 
   "chrome://browser/content/browser-captivePortal.js",
   "chrome://browser/content/browser-compacttheme.js",
   "chrome://browser/content/browser-contentblocking.js",
   "chrome://browser/content/browser-media.js",
   "chrome://browser/content/browser-pageActions.js",
   "chrome://browser/content/browser-places.js",
   "chrome://browser/content/browser-plugins.js",
--- a/browser/base/content/test/urlbar/browser_urlbarOneOffs.js
+++ b/browser/base/content/test/urlbar/browser_urlbarOneOffs.js
@@ -255,27 +255,27 @@ add_task(async function collapsedOneOffs
 // The one-offs should be hidden when searching with an "@engine" search engine
 // alias.
 add_task(async function hiddenWhenUsingSearchAlias() {
   let typedValue = "@example";
   await promiseAutocompleteResultPopup(typedValue, window, true);
   await waitForAutocompleteResultAt(0);
   Assert.equal(gURLBar.popup.oneOffSearchesEnabled, false);
   Assert.equal(
-    window.getComputedStyle(gURLBar.popup.oneOffSearchButtons).display,
+    window.getComputedStyle(gURLBar.popup.oneOffSearchButtons.container).display,
     "none"
   );
   await hidePopup();
 
   typedValue = "not an engine alias";
   await promiseAutocompleteResultPopup(typedValue, window, true);
   await waitForAutocompleteResultAt(0);
   Assert.equal(gURLBar.popup.oneOffSearchesEnabled, true);
   Assert.equal(
-    window.getComputedStyle(gURLBar.popup.oneOffSearchButtons).display,
+    window.getComputedStyle(gURLBar.popup.oneOffSearchButtons.container).display,
     "-moz-box"
   );
   await hidePopup();
 });
 
 
 function assertState(result, oneOff, textValue = undefined) {
   Assert.equal(gURLBar.popup.selectedIndex, result,
--- a/browser/base/content/test/urlbar/browser_urlbarSearchFunction.js
+++ b/browser/base/content/test/urlbar/browser_urlbarSearchFunction.js
@@ -122,17 +122,17 @@ function assertSearchSuggestionsNotifica
  * Asserts that the one-off search buttons are or aren't visible.
  *
  * @param visible
  *        True if they should be visible, false if not.
  */
 function assertOneOffButtonsVisible(visible) {
   Assert.equal(gURLBar.popup.oneOffSearchesEnabled, visible);
   Assert.equal(
-    window.getComputedStyle(gURLBar.popup.oneOffSearchButtons).display,
+    window.getComputedStyle(gURLBar.popup.oneOffSearchButtons.container).display,
     visible ? "-moz-box" : "none"
   );
 }
 
 /**
  * Asserts that the urlbar's input value is the given value.  Also asserts that
  * the first (heuristic) result in the popup is a search suggestion whose search
  * query is the given value.
--- a/browser/base/content/urlbarBindings.xml
+++ b/browser/base/content/urlbarBindings.xml
@@ -1868,22 +1868,22 @@ file, You can obtain one at http://mozil
                      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:search-one-offs anonid="one-off-search-buttons"
-                             class="search-one-offs"
-                             compact="true"
-                             includecurrentengine="true"
-                             disabletab="true"
-                             flex="1"/>
+        <xul:hbox anonid="one-off-search-buttons"
+                  class="search-one-offs"
+                  compact="true"
+                  includecurrentengine="true"
+                  disabletab="true"
+                  flex="1"/>
       </xul:hbox>
     </content>
 
     <implementation>
       <!--
         For performance reasons we want to limit the size of the text runs we
         build and show to the user.
       -->
@@ -1907,18 +1907,19 @@ file, You can obtain one at http://mozil
         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");
+        new window.SearchOneOffs(
+          document.getAnonymousElementByAttribute(this, "anonid",
+                                                  "one-off-search-buttons"));
       </field>
 
       <field name="_overrideValue">null</field>
       <property name="overrideValue"
                 onget="return this._overrideValue;"
                 onset="this._overrideValue = val; return val;"/>
 
       <method name="onPopupClick">
--- a/browser/components/search/content/search-one-offs.js
+++ b/browser/components/search/content/search-one-offs.js
@@ -3,228 +3,44 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 /* eslint-env mozilla/browser-window */
 
 {
 
-let sharedFragment;
-function getFragment() {
-  if (!sharedFragment) {
-    sharedFragment = MozXULElement.parseXULToFragment(`
-    <deck class="search-panel-one-offs-header search-panel-header search-panel-current-input">
-      <label class="searchbar-oneoffheader-search" value="&searchWithHeader.label;"></label>
-      <hbox class="search-panel-searchforwith search-panel-current-input">
-        <label value="&searchFor.label;"></label>
-        <label class="searchbar-oneoffheader-searchtext search-panel-input-value" flex="1" crop="end"></label>
-        <label flex="10000" value="&searchWith.label;"></label>
-      </hbox>
-      <hbox class="search-panel-searchonengine search-panel-current-input">
-        <label value="&search.label;"></label>
-        <label class="searchbar-oneoffheader-engine search-panel-input-value" flex="1" crop="end"></label>
-        <label flex="10000" value="&searchAfter.label;"></label>
-      </hbox>
-    </deck>
-    <description role="group" class="search-panel-one-offs">
-      <button oncommand="showSettings();" class="searchbar-engine-one-off-item search-setting-button-compact" tooltiptext="&changeSearchSettings.tooltip;"></button>
-    </description>
-    <vbox class="search-add-engines"></vbox>
-    <button oncommand="showSettings();" class="search-setting-button search-panel-header" label="&changeSearchSettings.button;"></button>
-    <menupopup class="search-one-offs-context-menu">
-      <menuitem class="search-one-offs-context-open-in-new-tab" label="&searchInNewTab.label;" accesskey="&searchInNewTab.accesskey;"></menuitem>
-      <menuitem class="search-one-offs-context-set-default" label="&searchSetAsDefault.label;" accesskey="&searchSetAsDefault.accesskey;"></menuitem>
-    </menupopup>
-    `, ["chrome://browser/locale/browser.dtd"]);
-  }
-
-  return document.importNode(sharedFragment, true);
-}
-
-class MozSearchOneOffs extends MozXULElement {
-  constructor() {
-    super();
-
-    this.addEventListener("mousedown", event => {
-      let target = event.originalTarget;
-      if (target.classList.contains("addengine-menu-button")) {
-        return;
-      }
-      // Required to receive click events from the buttons on Linux.
-      event.preventDefault();
-    });
-
-    this.addEventListener("mousemove", event => {
-      let target = event.originalTarget;
-
-      // Handle mouseover on the add-engine menu button and its popup items.
-      if ((target.localName == "menuitem" && target.classList.contains("addengine-item")) ||
-          target.classList.contains("addengine-menu-button")) {
-        let menuButton = this.querySelector(".addengine-menu-button");
-        this._updateStateForButton(menuButton);
-        this._addEngineMenuShouldBeOpen = true;
-        this._resetAddEngineMenuTimeout();
-        return;
-      }
-
-      if (target.localName != "button") {
-        return;
-      }
-
-      // Ignore mouse events when the context menu is open.
-      if (this._ignoreMouseEvents) {
-        return;
-      }
-
-      let isOneOff =
-        target.classList.contains("searchbar-engine-one-off-item") &&
-        !target.classList.contains("dummy");
-      if (isOneOff ||
-          target.classList.contains("addengine-item") ||
-          target.classList.contains("search-setting-button")) {
-        this._updateStateForButton(target);
-      }
-    });
-
-    this.addEventListener("mouseout", event => {
-      let target = event.originalTarget;
-
-      // Handle mouseout on the add-engine menu button and its popup items.
-      if ((target.localName == "menuitem" && target.classList.contains("addengine-item")) ||
-          target.classList.contains("addengine-menu-button")) {
-        this._updateStateForButton(null);
-        this._addEngineMenuShouldBeOpen = false;
-        this._resetAddEngineMenuTimeout();
-        return;
-      }
-
-      if (target.localName != "button") {
-        return;
-      }
-
-      // Don't update the mouseover state if the context menu is open.
-      if (this._ignoreMouseEvents) {
-        return;
-      }
-
-      this._updateStateForButton(null);
-    });
-
-    this.addEventListener("click", event => {
-      if (event.button == 2) {
-        return; // ignore right clicks.
-      }
+class SearchOneOffs {
+  constructor(container) {
+    this.container = container;
 
-      let button = event.originalTarget;
-      let engine = button.engine;
-
-      if (!engine) {
-        return;
-      }
-
-      // Select the clicked button so that consumers can easily tell which
-      // button was acted on.
-      this.selectedButton = button;
-      this.handleSearchCommand(event, engine);
-    });
-
-    this.addEventListener("command", event => {
-      let target = event.originalTarget;
-      if (target.classList.contains("addengine-item")) {
-        // On success, hide the panel and tell event listeners to reshow it to
-        // show the new engine.
-        let installCallback = {
-          onSuccess: engine => {
-            this._rebuild();
-          },
-          onError(errorCode) {
-            if (errorCode != Ci.nsISearchInstallCallback.ERROR_DUPLICATE_ENGINE) {
-              // Download error is shown by the search service
-              return;
-            }
-            const kSearchBundleURI =
-              "chrome://global/locale/search/search.properties";
-            let searchBundle = Services.strings.createBundle(kSearchBundleURI);
-            let brandBundle = document.getElementById("bundle_brand");
-            let brandName = brandBundle.getString("brandShortName");
-            let title = searchBundle.GetStringFromName(
-              "error_invalid_engine_title"
-            );
-            let text = searchBundle.formatStringFromName(
-              "error_duplicate_engine_msg",
-              [brandName, target.getAttribute("uri")],
-              2
-            );
-            Services.prompt.QueryInterface(Ci.nsIPromptFactory);
-            let prompt = Services.prompt.getPrompt(
-              gBrowser.contentWindow,
-              Ci.nsIPrompt
-            );
-            prompt.QueryInterface(Ci.nsIWritablePropertyBag2);
-            prompt.setPropertyAsBool("allowTabModal", true);
-            prompt.alert(title, text);
-          },
-        };
-        Services.search.addEngine(target.getAttribute("uri"),
-                                  target.getAttribute("image"), false,
-                                  installCallback);
-      }
-      if (target.classList.contains("search-one-offs-context-open-in-new-tab")) {
-        // Select the context-clicked button so that consumers can easily
-        // tell which button was acted on.
-        this.selectedButton = this._buttonForEngine(this._contextEngine);
-        this.handleSearchCommand(event, this._contextEngine, true);
-      }
-      if (target.classList.contains("search-one-offs-context-set-default")) {
-        let currentEngine = Services.search.defaultEngine;
-
-        if (!this.getAttribute("includecurrentengine")) {
-          // Make the target button of the context menu reflect the current
-          // search engine first. Doing this as opposed to rebuilding all the
-          // one-off buttons avoids flicker.
-          let button = this._buttonForEngine(this._contextEngine);
-          button.id = this._buttonIDForEngine(currentEngine);
-          let uri = "chrome://browser/skin/search-engine-placeholder.png";
-          if (currentEngine.iconURI) {
-            uri = currentEngine.iconURI.spec;
-          }
-          button.setAttribute("image", uri);
-          button.setAttribute("tooltiptext", currentEngine.name);
-          button.engine = currentEngine;
-        }
-
-        Services.search.defaultEngine = this._contextEngine;
-      }
-    });
-
-    this.addEventListener("contextmenu", event => {
-      let target = event.originalTarget;
-      // Prevent the context menu from appearing except on the one off buttons.
-      if (!target.classList.contains("searchbar-engine-one-off-item") ||
-          target.classList.contains("dummy")) {
-        event.preventDefault();
-        return;
-      }
-      this.querySelector(".search-one-offs-context-set-default")
-          .setAttribute("disabled", target.engine == Services.search.defaultEngine);
-
-      this.contextMenuPopup.openPopupAtScreen(event.screenX, event.screenY, true);
-      event.preventDefault();
-
-      this._contextEngine = target.engine;
-    });
-  }
-
-  connectedCallback() {
-    if (this.delayConnectedCallback()) {
-      return;
-    }
-
-    this.appendChild(getFragment());
+    this.container.appendChild(MozXULElement.parseXULToFragment(`
+      <deck class="search-panel-one-offs-header search-panel-header search-panel-current-input">
+        <label class="searchbar-oneoffheader-search" value="&searchWithHeader.label;"/>
+        <hbox class="search-panel-searchforwith search-panel-current-input">
+          <label value="&searchFor.label;"/>
+          <label class="searchbar-oneoffheader-searchtext search-panel-input-value" flex="1" crop="end"/>
+          <label flex="10000" value="&searchWith.label;"/>
+        </hbox>
+        <hbox class="search-panel-searchonengine search-panel-current-input">
+          <label value="&search.label;"/>
+          <label class="searchbar-oneoffheader-engine search-panel-input-value" flex="1" crop="end"/>
+          <label flex="10000" value="&searchAfter.label;"/>
+        </hbox>
+      </deck>
+      <description role="group" class="search-panel-one-offs">
+        <button class="searchbar-engine-one-off-item search-setting-button-compact" tooltiptext="&changeSearchSettings.tooltip;"/>
+      </description>
+      <vbox class="search-add-engines"/>
+      <button class="search-setting-button search-panel-header" label="&changeSearchSettings.button;"/>
+      <menupopup class="search-one-offs-context-menu">
+        <menuitem class="search-one-offs-context-open-in-new-tab" label="&searchInNewTab.label;" accesskey="&searchInNewTab.accesskey;"/>
+        <menuitem class="search-one-offs-context-set-default" label="&searchSetAsDefault.label;" accesskey="&searchSetAsDefault.accesskey;"/>
+      </menupopup>
+      `, ["chrome://browser/locale/browser.dtd"]));
 
     this._popup = null;
 
     this._textbox = null;
 
     this._textboxWidth = 0;
 
     /**
@@ -272,41 +88,82 @@ class MozSearchOneOffs extends MozXULEle
      * many engines offered by the current site.
      */
     this._addEngineMenuTimeoutMs = 200;
 
     this._addEngineMenuTimeout = null;
 
     this._addEngineMenuShouldBeOpen = false;
 
+    this.addEventListener("mousedown", this);
+    this.addEventListener("mousemove", this);
+    this.addEventListener("mouseout", this);
+    this.addEventListener("click", this);
+    this.addEventListener("command", this);
+    this.addEventListener("contextmenu", this);
+
     // Prevent popup events from the context menu from reaching the autocomplete
     // binding (or other listeners).
     let listener = aEvent => aEvent.stopPropagation();
     this.contextMenuPopup.addEventListener("popupshowing", listener);
     this.contextMenuPopup.addEventListener("popuphiding", listener);
     this.contextMenuPopup.addEventListener("popupshown", aEvent => {
       this._ignoreMouseEvents = true;
       aEvent.stopPropagation();
     });
     this.contextMenuPopup.addEventListener("popuphidden", aEvent => {
       this._ignoreMouseEvents = false;
       aEvent.stopPropagation();
     });
 
     // Add weak referenced observers to invalidate our cached list of engines.
+    this.QueryInterface = ChromeUtils.generateQI([Ci.nsIObserver, Ci.nsISupportsWeakReference]);
     Services.prefs.addObserver("browser.search.hiddenOneOffs", this, true);
     Services.obs.addObserver(this, "browser-search-engine-modified", true);
     Services.obs.addObserver(this, "browser-search-service", true);
 
     // Rebuild the buttons when the theme changes.  See bug 1357800 for
     // details.  Summary: On Linux, switching between themes can cause a row
     // of buttons to disappear.
     Services.obs.addObserver(this, "lightweight-theme-changed", true);
   }
 
+  addEventListener(...args) {
+    this.container.addEventListener(...args);
+  }
+
+  removeEventListener(...args) {
+    this.container.removeEventListener(...args);
+  }
+
+  dispatchEvent(...args) {
+    this.container.dispatchEvent(...args);
+  }
+
+  getAttribute(...args) {
+    return this.container.getAttribute(...args);
+  }
+
+  setAttribute(...args) {
+    this.container.setAttribute(...args);
+  }
+
+  querySelector(...args) {
+    return this.container.querySelector(...args);
+  }
+
+  handleEvent(event) {
+    let methodName = "_on_" + event.type;
+    if (methodName in this) {
+      this[methodName](event);
+    } else {
+      throw new Error("Unrecognized search-one-offs event: " + event.type);
+    }
+  }
+
   /**
    * Width in pixels of the one-off buttons.  49px is the min-width of
    * each search engine button, adapt this const when changing the css.
    * It's actually 48px + 1px of right border.
    */
   get buttonWidth() {
     return 49;
   }
@@ -333,41 +190,48 @@ class MozSearchOneOffs extends MozXULEle
     this._popup = val;
 
     // If the popup is already open, rebuild the one-offs now.  The
     // popup may be opening, so check that the state is not closed
     // instead of checking popupOpen.
     if (val && val.state != "closed") {
       this._rebuild();
     }
+
     return val;
   }
 
   get popup() {
     return this._popup;
   }
+
   /**
    * The textbox associated with the one-offs.  Set this to a textbox to
    * automatically keep the related one-offs UI up to date.  Otherwise you
    * can leave it null/undefined, and in that case you should update the
    * query property manually.
    */
   set textbox(val) {
     if (this._textbox) {
       this._textbox.removeEventListener("input", this);
     }
     if (val) {
       val.addEventListener("input", this);
     }
     return this._textbox = val;
   }
 
+  get style() {
+    return this.container.style;
+  }
+
   get textbox() {
     return this._textbox;
   }
+
   /**
    * The query string currently shown in the one-offs.  If the textbox
    * property is non-null, then this is automatically updated on
    * input.
    */
   set query(val) {
     this._query = val;
     if (this.popup && this.popup.popupOpen) {
@@ -461,54 +325,21 @@ class MozSearchOneOffs extends MozXULEle
       return (!currentEngineNameToIgnore ||
               name != currentEngineNameToIgnore) &&
              !hiddenList.includes(name);
     });
 
     return this._engines;
   }
 
-  /**
-   * This handles events outside the one-off buttons, like on the popup
-   * and textbox.
-   */
-  handleEvent(event) {
-    switch (event.type) {
-      case "input":
-        // Allow the consumer's input to override its value property with
-        // a oneOffSearchQuery property.  That way if the value is not
-        // actually what the user typed (e.g., it's autofilled, or it's a
-        // mozaction URI), the consumer has some way of providing it.
-        this.query = event.target.oneOffSearchQuery || event.target.value;
-        break;
-      case "popupshowing":
-        this._rebuild();
-        break;
-      case "popuphidden":
-        Services.tm.dispatchToMainThread(() => {
-          this.selectedButton = null;
-          this._contextEngine = null;
-        });
-        break;
-    }
-  }
-
   observe(aEngine, aTopic, aData) {
     // Make sure the engine list is refetched next time it's needed.
     this._engines = null;
   }
 
-  showSettings() {
-    openPreferences("paneSearch", { origin: "contentSearch" });
-
-    // If the preference tab was already selected, the panel doesn't
-    // close itself automatically.
-    this.popup.hidePopup();
-  }
-
   /**
    * Updates the parts of the UI that show the query string.
    */
   _updateAfterQueryChanged() {
     let headerSearchText = this.querySelector(".searchbar-oneoffheader-searchtext");
     headerSearchText.setAttribute("value", this.query);
     let groupText;
     let isOneOffSelected =
@@ -1192,14 +1023,217 @@ class MozSearchOneOffs extends MozXULEle
       clearTimeout(this._addEngineMenuTimeout);
     }
     this._addEngineMenuTimeout = setTimeout(() => {
       delete this._addEngineMenuTimeout;
       let button = this.querySelector(".addengine-menu-button");
       button.open = this._addEngineMenuShouldBeOpen;
     }, this._addEngineMenuTimeoutMs);
   }
+
+  // Event handlers below.
+
+  _on_mousedown(event) {
+    let target = event.originalTarget;
+    if (target.classList.contains("addengine-menu-button")) {
+      return;
+    }
+    // Required to receive click events from the buttons on Linux.
+    event.preventDefault();
+  }
+
+  _on_mousemove(event) {
+    let target = event.originalTarget;
+
+    // Handle mouseover on the add-engine menu button and its popup items.
+    if ((target.localName == "menuitem" && target.classList.contains("addengine-item")) ||
+        target.classList.contains("addengine-menu-button")) {
+      let menuButton = this.querySelector(".addengine-menu-button");
+      this._updateStateForButton(menuButton);
+      this._addEngineMenuShouldBeOpen = true;
+      this._resetAddEngineMenuTimeout();
+      return;
+    }
+
+    if (target.localName != "button") {
+      return;
+    }
+
+    // Ignore mouse events when the context menu is open.
+    if (this._ignoreMouseEvents) {
+      return;
+    }
+
+    let isOneOff =
+      target.classList.contains("searchbar-engine-one-off-item") &&
+      !target.classList.contains("dummy");
+    if (isOneOff ||
+        target.classList.contains("addengine-item") ||
+        target.classList.contains("search-setting-button")) {
+      this._updateStateForButton(target);
+    }
+  }
+
+  _on_mouseout(event) {
+    let target = event.originalTarget;
+
+    // Handle mouseout on the add-engine menu button and its popup items.
+    if ((target.localName == "menuitem" && target.classList.contains("addengine-item")) ||
+        target.classList.contains("addengine-menu-button")) {
+      this._updateStateForButton(null);
+      this._addEngineMenuShouldBeOpen = false;
+      this._resetAddEngineMenuTimeout();
+      return;
+    }
+
+    if (target.localName != "button") {
+      return;
+    }
+
+    // Don't update the mouseover state if the context menu is open.
+    if (this._ignoreMouseEvents) {
+      return;
+    }
+
+    this._updateStateForButton(null);
+  }
+
+  _on_click(event) {
+    if (event.button == 2) {
+      return; // ignore right clicks.
+    }
+
+    let button = event.originalTarget;
+    let engine = button.engine;
+
+    if (!engine) {
+      return;
+    }
+
+    // Select the clicked button so that consumers can easily tell which
+    // button was acted on.
+    this.selectedButton = button;
+    this.handleSearchCommand(event, engine);
+  }
+
+  _on_command(event) {
+    let target = event.target;
+
+    if (target == this.settingsButton ||
+        target == this.settingsButtonCompact) {
+      openPreferences("paneSearch", { origin: "contentSearch" });
+
+      // If the preference tab was already selected, the panel doesn't
+      // close itself automatically.
+      this.popup.hidePopup();
+      return;
+    }
+
+    if (target.classList.contains("addengine-item")) {
+      // On success, hide the panel and tell event listeners to reshow it to
+      // show the new engine.
+      let installCallback = {
+        onSuccess: engine => {
+          this._rebuild();
+        },
+        onError(errorCode) {
+          if (errorCode != Ci.nsISearchInstallCallback.ERROR_DUPLICATE_ENGINE) {
+            // Download error is shown by the search service
+            return;
+          }
+          const kSearchBundleURI =
+            "chrome://global/locale/search/search.properties";
+          let searchBundle = Services.strings.createBundle(kSearchBundleURI);
+          let brandBundle = document.getElementById("bundle_brand");
+          let brandName = brandBundle.getString("brandShortName");
+          let title = searchBundle.GetStringFromName(
+            "error_invalid_engine_title"
+          );
+          let text = searchBundle.formatStringFromName(
+            "error_duplicate_engine_msg",
+            [brandName, target.getAttribute("uri")],
+            2
+          );
+          Services.prompt.QueryInterface(Ci.nsIPromptFactory);
+          let prompt = Services.prompt.getPrompt(
+            gBrowser.contentWindow,
+            Ci.nsIPrompt
+          );
+          prompt.QueryInterface(Ci.nsIWritablePropertyBag2);
+          prompt.setPropertyAsBool("allowTabModal", true);
+          prompt.alert(title, text);
+        },
+      };
+      Services.search.addEngine(target.getAttribute("uri"),
+                                target.getAttribute("image"), false,
+                                installCallback);
+    }
+
+    if (target.classList.contains("search-one-offs-context-open-in-new-tab")) {
+      // Select the context-clicked button so that consumers can easily
+      // tell which button was acted on.
+      this.selectedButton = this._buttonForEngine(this._contextEngine);
+      this.handleSearchCommand(event, this._contextEngine, true);
+    }
+
+    if (target.classList.contains("search-one-offs-context-set-default")) {
+      let currentEngine = Services.search.defaultEngine;
+
+      if (!this.getAttribute("includecurrentengine")) {
+        // Make the target button of the context menu reflect the current
+        // search engine first. Doing this as opposed to rebuilding all the
+        // one-off buttons avoids flicker.
+        let button = this._buttonForEngine(this._contextEngine);
+        button.id = this._buttonIDForEngine(currentEngine);
+        let uri = "chrome://browser/skin/search-engine-placeholder.png";
+        if (currentEngine.iconURI) {
+          uri = currentEngine.iconURI.spec;
+        }
+        button.setAttribute("image", uri);
+        button.setAttribute("tooltiptext", currentEngine.name);
+        button.engine = currentEngine;
+      }
+
+      Services.search.defaultEngine = this._contextEngine;
+    }
+  }
+
+  _on_contextmenu(event) {
+    let target = event.originalTarget;
+    // Prevent the context menu from appearing except on the one off buttons.
+    if (!target.classList.contains("searchbar-engine-one-off-item") ||
+        target.classList.contains("dummy")) {
+      event.preventDefault();
+      return;
+    }
+    this.querySelector(".search-one-offs-context-set-default")
+        .setAttribute("disabled", target.engine == Services.search.defaultEngine);
+
+    this.contextMenuPopup.openPopupAtScreen(event.screenX, event.screenY, true);
+    event.preventDefault();
+
+    this._contextEngine = target.engine;
+  }
+
+  _on_input(event) {
+    // Allow the consumer's input to override its value property with
+    // a oneOffSearchQuery property.  That way if the value is not
+    // actually what the user typed (e.g., it's autofilled, or it's a
+    // mozaction URI), the consumer has some way of providing it.
+    this.query = event.target.oneOffSearchQuery || event.target.value;
+  }
+
+  _on_popupshowing() {
+    this._rebuild();
+  }
+
+  _on_popuphidden() {
+    Services.tm.dispatchToMainThread(() => {
+      this.selectedButton = null;
+      this._contextEngine = null;
+    });
+  }
 }
 
-MozXULElement.implementCustomInterface(MozSearchOneOffs, [Ci.nsIObserver, Ci.nsIWeakReference]);
-customElements.define("search-one-offs", MozSearchOneOffs);
+window.SearchOneOffs = SearchOneOffs;
 
 }
+
--- a/browser/components/search/content/search.xml
+++ b/browser/components/search/content/search.xml
@@ -372,17 +372,17 @@
     <content ignorekeys="true" level="top" consumeoutsideclicks="never">
       <xul:hbox anonid="searchbar-engine" xbl:inherits="showonlysettings"
                 class="search-panel-header search-panel-current-engine">
         <xul:image class="searchbar-engine-image" xbl:inherits="src"/>
         <xul:label anonid="searchbar-engine-name" flex="1" crop="end"
                    role="presentation"/>
       </xul:hbox>
       <xul:richlistbox anonid="richlistbox" class="autocomplete-richlistbox search-panel-tree" flex="1"/>
-      <xul:search-one-offs anonid="search-one-off-buttons" class="search-one-offs"/>
+      <xul:hbox anonid="search-one-off-buttons" class="search-one-offs"/>
     </content>
     <implementation>
       <method name="openAutocompletePopup">
         <parameter name="aInput"/>
         <parameter name="aElement"/>
         <body><![CDATA[
           // initially the panel is hidden
           // to avoid impacting startup / new window performance
@@ -468,18 +468,19 @@
               this._bundle = Services.strings.createBundle(kBundleURI);
             }
             return this._bundle;
           ]]>
         </getter>
       </property>
 
       <field name="oneOffButtons" readonly="true">
-        document.getAnonymousElementByAttribute(this, "anonid",
-                                                "search-one-off-buttons");
+        new window.SearchOneOffs(
+          document.getAnonymousElementByAttribute(this, "anonid",
+                                                  "search-one-off-buttons"));
       </field>
 
       <method name="updateHeader">
         <body><![CDATA[
           let currentEngine = Services.search.defaultEngine;
           let uri = currentEngine.iconURI;
           if (uri) {
             this.setAttribute("src", uri.spec);
--- a/browser/components/search/test/browser/browser_oneOffContextMenu.js
+++ b/browser/components/search/test/browser/browser_oneOffContextMenu.js
@@ -1,20 +1,18 @@
 "use strict";
 
 const TEST_ENGINE_NAME = "Foo";
 const TEST_ENGINE_BASENAME = "testEngine.xml";
 
 const searchPopup = document.getElementById("PopupSearchAutoComplete");
-const oneOffElement = document.getAnonymousElementByAttribute(
-  searchPopup, "anonid", "search-one-off-buttons"
-);
-const contextMenu = oneOffElement.querySelector(".search-one-offs-context-menu");
-const oneOffButtons = oneOffElement.buttons;
-const searchInNewTabMenuItem = oneOffElement.querySelector(".search-one-offs-context-open-in-new-tab");
+const oneOffInstance = searchPopup.oneOffButtons;
+const contextMenu = oneOffInstance.querySelector(".search-one-offs-context-menu");
+const oneOffButtons = oneOffInstance.buttons;
+const searchInNewTabMenuItem = oneOffInstance.querySelector(".search-one-offs-context-open-in-new-tab");
 
 let searchbar;
 let searchIcon;
 
 add_task(async function init() {
   searchbar = await gCUITestUtils.addSearchBar();
   registerCleanupFunction(() => {
     gCUITestUtils.removeSearchBar();
--- a/browser/components/search/test/browser/browser_oneOffContextMenu_setDefault.js
+++ b/browser/components/search/test/browser/browser_oneOffContextMenu_setDefault.js
@@ -4,22 +4,18 @@ const TEST_ENGINE_NAME = "Foo";
 const TEST_ENGINE_BASENAME = "testEngine.xml";
 const SEARCHBAR_BASE_ID = "searchbar-engine-one-off-item-";
 const URLBAR_BASE_ID = "urlbar-engine-one-off-item-";
 const ONEOFF_URLBAR_PREF = "browser.urlbar.oneOffSearches";
 
 const urlbar = document.getElementById("urlbar");
 const searchPopup = document.getElementById("PopupSearchAutoComplete");
 const urlbarPopup = document.getElementById("PopupAutoCompleteRichResult");
-const searchOneOffElement = document.getAnonymousElementByAttribute(
-  searchPopup, "anonid", "search-one-off-buttons"
-);
-const urlBarOneOffElement = document.getAnonymousElementByAttribute(
-  urlbarPopup, "anonid", "one-off-search-buttons"
-);
+const searchOneOff = searchPopup.oneOffButtons;
+const urlBarOneOff = urlbarPopup.oneOffSearchButtons;
 
 let originalEngine = Services.search.defaultEngine;
 
 function resetEngine() {
   Services.search.defaultEngine = originalEngine;
 }
 
 registerCleanupFunction(resetEngine);
@@ -35,20 +31,20 @@ add_task(async function init() {
 
   await promiseNewEngine(TEST_ENGINE_BASENAME, {
     setAsCurrent: false,
   });
 });
 
 add_task(async function test_searchBarChangeEngine() {
   let oneOffButton = await openPopupAndGetEngineButton(true, searchPopup,
-                                                       searchOneOffElement,
+                                                       searchOneOff,
                                                        SEARCHBAR_BASE_ID);
 
-  const setDefaultEngineMenuItem = searchOneOffElement.querySelector(
+  const setDefaultEngineMenuItem = searchOneOff.querySelector(
     ".search-one-offs-context-set-default"
   );
 
   // Click the set default engine menu item.
   let promise = promiseCurrentEngineChanged();
   EventUtils.synthesizeMouseAtCenter(setDefaultEngineMenuItem, {});
 
   // This also checks the engine correctly changed.
@@ -69,20 +65,20 @@ add_task(async function test_urlBarChang
   registerCleanupFunction(function() {
     Services.prefs.clearUserPref(ONEOFF_URLBAR_PREF);
   });
 
   // Ensure the engine is reset.
   resetEngine();
 
   let oneOffButton = await openPopupAndGetEngineButton(false, urlbarPopup,
-                                                       urlBarOneOffElement,
+                                                       urlBarOneOff,
                                                        URLBAR_BASE_ID);
 
-  const setDefaultEngineMenuItem = urlBarOneOffElement.querySelector(
+  const setDefaultEngineMenuItem = urlBarOneOff.querySelector(
     ".search-one-offs-context-set-default"
   );
 
   // Click the set default engine menu item.
   let promise = promiseCurrentEngineChanged();
   EventUtils.synthesizeMouseAtCenter(setDefaultEngineMenuItem, {});
 
   // This also checks the engine correctly changed.
@@ -123,40 +119,40 @@ function promiseCurrentEngineChanged() {
 
 /**
  * Opens the specified urlbar/search popup and gets the test engine from the
  * one-off buttons.
  *
  * @param {Boolean} isSearch true if the search popup should be opened; false
  *                           for the urlbar popup.
  * @param {Object} popup The expected popup.
- * @param {Object} oneOffElement The expected one-off-element for the popup.
+ * @param {Object} oneOffInstance The expected one-off instance for the popup.
  * @param {String} baseId The expected string for the id of the current
  *                        engine button, without the engine name.
  * @return {Object} Returns an object that represents the one off button for the
  *                          test engine.
  */
-async function openPopupAndGetEngineButton(isSearch, popup, oneOffElement, baseId) {
+async function openPopupAndGetEngineButton(isSearch, popup, oneOffInstance, baseId) {
   // Open the popup.
   let promise = promiseEvent(popup, "popupshown");
   info("Opening panel");
 
   // We have to open the popups in differnt ways.
   if (isSearch) {
     // Use the search icon to avoid hitting the network.
     EventUtils.synthesizeMouseAtCenter(searchIcon, {});
   } else {
     // There's no history at this stage, so we need to press a key.
     urlbar.focus();
     EventUtils.sendString("a");
   }
   await promise;
 
-  const contextMenu = oneOffElement.contextMenuPopup;
-  const oneOffButtons = oneOffElement.buttons;
+  const contextMenu = oneOffInstance.contextMenuPopup;
+  const oneOffButtons = oneOffInstance.buttons;
 
   // Get the one-off button for the test engine.
   let oneOffButton;
   for (let node of oneOffButtons.children) {
     if (node.engine && node.engine.name == TEST_ENGINE_NAME) {
       oneOffButton = node;
       break;
     }
--- a/browser/components/urlbar/UrlbarView.jsm
+++ b/browser/components/urlbar/UrlbarView.jsm
@@ -40,16 +40,22 @@ class UrlbarView {
       if (event.target.classList.contains("urlbarView-row-inner")) {
         event.target.toggleAttribute("overflow", false);
       }
     });
 
     this.controller.addQueryListener(this);
   }
 
+  get oneOffSearchButtons() {
+    return this._oneOffSearchButtons ||
+      (this._oneOffSearchButtons =
+         new this.window.SearchOneOffs(this.panel.querySelector(".search-one-offs")));
+  }
+
   /**
    * Opens the autocomplete results popup.
    */
   open() {
     this.panel.removeAttribute("hidden");
 
     let panelDirection = this.panel.style.direction;
     if (!panelDirection) {
@@ -61,16 +67,20 @@ class UrlbarView {
     let documentRect =
       this._getBoundsWithoutFlushing(this.document.documentElement);
     let width = documentRect.right - documentRect.left;
     this.panel.setAttribute("width", width);
 
     // Subtract two pixels for left and right borders on the panel.
     this._mainContainer.style.maxWidth = (width - 2) + "px";
 
+    // TODO: Search one off buttons are a stub right now.
+    //       We'll need to set them up properly.
+    this.oneOffSearchButtons;
+
     this.panel.openPopup(this.urlbar.textbox.closest("toolbar"), "after_end", 0, -1);
 
     this._rows.firstElementChild.toggleAttribute("selected", true);
   }
 
   /**
    * Closes the autocomplete results popup.
    */
--- a/browser/locales/l10n-changesets.json
+++ b/browser/locales/l10n-changesets.json
@@ -1424,16 +1424,31 @@
             "macosx64-devedition",
             "win32",
             "win32-devedition",
             "win64",
             "win64-devedition"
         ],
         "revision": "default"
     },
+    "trs": {
+        "platforms": [
+            "linux",
+            "linux-devedition",
+            "linux64",
+            "linux64-devedition",
+            "macosx64",
+            "macosx64-devedition",
+            "win32",
+            "win32-devedition",
+            "win64",
+            "win64-devedition"
+        ],
+        "revision": "default"
+    },
     "uk": {
         "platforms": [
             "linux",
             "linux-devedition",
             "linux64",
             "linux64-devedition",
             "macosx64",
             "macosx64-devedition",
--- a/dom/base/nsFocusManager.cpp
+++ b/dom/base/nsFocusManager.cpp
@@ -3095,17 +3095,17 @@ private:
 void
 ScopedContentTraversal::Next()
 {
   MOZ_ASSERT(mCurrent);
 
   // Get mCurrent's first child if it's in the same scope.
   if (!(mCurrent->GetShadowRoot() || mCurrent->IsHTMLElement(nsGkAtoms::slot)) ||
       mCurrent == mOwner) {
-    FlattenedChildIterator iter(mCurrent);
+    StyleChildrenIterator iter(mCurrent);
     nsIContent* child = iter.GetNextChild();
     if (child) {
       SetCurrent(child);
       return;
     }
   }
 
   // If mOwner has no children, END traversal
@@ -3113,22 +3113,21 @@ ScopedContentTraversal::Next()
     SetCurrent(nullptr);
     return;
   }
 
   nsIContent* current = mCurrent;
   while (1) {
     // Create parent's iterator and move to current
     nsIContent* parent = current->GetFlattenedTreeParent();
-    FlattenedChildIterator parentIter(parent);
+    StyleChildrenIterator parentIter(parent);
     parentIter.Seek(current);
 
     // Get next sibling of current
-    nsIContent* next = parentIter.GetNextChild();
-    if (next) {
+    if (nsIContent* next = parentIter.GetNextChild()) {
       SetCurrent(next);
       return;
     }
 
     // If no next sibling and parent is mOwner, END traversal
     if (parent == mOwner) {
       SetCurrent(nullptr);
       return;
@@ -3142,40 +3141,40 @@ void
 ScopedContentTraversal::Prev()
 {
   MOZ_ASSERT(mCurrent);
 
   nsIContent* parent;
   nsIContent* last;
   if (mCurrent == mOwner) {
     // Get last child of mOwner
-    FlattenedChildIterator ownerIter(mOwner, false /* aStartAtBeginning */);
+    StyleChildrenIterator ownerIter(mOwner, false /* aStartAtBeginning */);
     last = ownerIter.GetPreviousChild();
 
     parent = last;
   } else {
     // Create parent's iterator and move to mCurrent
     parent = mCurrent->GetFlattenedTreeParent();
-    FlattenedChildIterator parentIter(parent);
+    StyleChildrenIterator parentIter(parent);
     parentIter.Seek(mCurrent);
 
     // Get previous sibling
     last = parentIter.GetPreviousChild();
   }
 
   while (last) {
     parent = last;
     if (parent->GetShadowRoot() ||
         parent->IsHTMLElement(nsGkAtoms::slot)) {
       // Skip contents in other scopes
       break;
     }
 
     // Find last child
-    FlattenedChildIterator iter(parent, false /* aStartAtBeginning */);
+    StyleChildrenIterator iter(parent, false /* aStartAtBeginning */);
     last = iter.GetPreviousChild();
   }
 
   // If parent is mOwner and no previous sibling remains, END traversal
   SetCurrent(parent == mOwner ? nullptr : parent);
 }
 
 nsIContent*
@@ -3303,82 +3302,16 @@ nsFocusManager::GetNextTabbableContentIn
       } else {
         nsIFrame* frame = iterContent->GetPrimaryFrame();
         if (!frame) {
           continue;
         }
         frame->IsFocusable(&tabIndex, 0);
       }
       if (tabIndex < 0 || !(aIgnoreTabIndex || tabIndex == aCurrentTabIndex)) {
-        // If the element has native anonymous content, we may need to
-        // focus some NAC element, even if the element itself isn't focusable.
-        // This happens for example with <input type="date">.
-        // So, try to find NAC and then traverse the frame tree to find elements
-        // to focus.
-        // Yet, even if the frame is a nsIAnonymousContentCreator, don't
-        // traverse into the element again when the element is in a UA Widget,
-        // because there isn't any NAC to focus.
-        nsIFrame* possibleAnonOwnerFrame = iterContent->GetPrimaryFrame();
-        nsIAnonymousContentCreator* anonCreator =
-          do_QueryFrame(possibleAnonOwnerFrame);
-        bool isIterContentInUAWidgetShadow =
-          iterContent->GetContainingShadow() &&
-          iterContent->GetContainingShadow()->IsUAWidget();
-        if (anonCreator &&
-            !isIterContentInUAWidgetShadow &&
-            !iterContent->IsInNativeAnonymousSubtree()) {
-          nsIFrame* frame = nullptr;
-          // Find the first or last frame in tree order so that
-          // we can scope frame traversing to NAC.
-          if (aForward) {
-            frame = possibleAnonOwnerFrame->PrincipalChildList().FirstChild();
-          } else {
-            frame = possibleAnonOwnerFrame->PrincipalChildList().LastChild();
-            nsIFrame* last = frame;
-            while (last) {
-              frame = last;
-              last = frame->PrincipalChildList().LastChild();
-            }
-          };
-
-          nsCOMPtr<nsIFrameEnumerator> frameTraversal;
-          nsresult rv = NS_NewFrameTraversal(getter_AddRefs(frameTraversal),
-                                             iterContent->OwnerDoc()->
-                                               GetShell()->GetPresContext(),
-                                             frame,
-                                             ePreOrder,
-                                             false, // aVisual
-                                             false, // aLockInScrollView
-                                             true, // aFollowOOFs
-                                             true  // aSkipPopupChecks
-                                             );
-          if (NS_SUCCEEDED(rv)) {
-            nsIFrame* frame =
-              static_cast<nsIFrame*>(frameTraversal->CurrentItem());
-            while (frame) {
-              int32_t tabIndex;
-              frame->IsFocusable(&tabIndex, 0);
-              if (tabIndex >= 0 &&
-                  (aIgnoreTabIndex || aCurrentTabIndex == tabIndex)) {
-                return frame->GetContent();
-              }
-
-              if (aForward) {
-                frameTraversal->Next();
-              } else {
-                frameTraversal->Prev();
-              }
-              frame = static_cast<nsIFrame*>(frameTraversal->CurrentItem());
-              if (frame == possibleAnonOwnerFrame) {
-                break;
-              }
-            }
-          }
-        }
-
         continue;
       }
 
       if (!IsHostOrSlot(iterContent)) {
         nsCOMPtr<nsIContent> elementInFrame;
         bool checkSubDocument = true;
         if (aForDocumentNavigation &&
             TryDocumentNavigation(iterContent, &checkSubDocument,
@@ -3976,17 +3909,17 @@ nsFocusManager::GetNextTabbableMapArea(b
 }
 
 int32_t
 nsFocusManager::GetNextTabIndex(nsIContent* aParent,
                                 int32_t aCurrentTabIndex,
                                 bool aForward)
 {
   int32_t tabIndex, childTabIndex;
-  FlattenedChildIterator iter(aParent);
+  StyleChildrenIterator iter(aParent);
 
   if (aForward) {
     tabIndex = 0;
     for (nsIContent* child = iter.GetNextChild();
          child;
          child = iter.GetNextChild()) {
       // Skip child's descendants if child is a shadow host or slot, as they are
       // in the focus navigation scope owned by child's shadow root
--- a/dom/base/test/file_bug1453693.html
+++ b/dom/base/test/file_bug1453693.html
@@ -375,15 +375,17 @@
 
       function init() {
         SimpleTest.waitForFocus(runTest);
       }
     </script>
     <style>
     </style>
     <template id="template">
-      <p tabindex="0" id="p1">component</p>
-      <p tabindex="0" id="p2">/component</p>
+      <div style="overflow: hidden">
+        <p tabindex="0" id="p1">component</p>
+        <p tabindex="0" id="p2">/component</p>
+      </div>
     </template>
   </head>
   <body onload="init()">
   </body>
 </html>
--- a/taskcluster/ci/beetmover-repackage/kind.yml
+++ b/taskcluster/ci/beetmover-repackage/kind.yml
@@ -19,22 +19,20 @@ kind-dependencies:
     - repackage-signing
     - nightly-l10n
     - nightly-l10n-signing
     - repackage-l10n
     - repackage-signing-l10n
     - partials
     - partials-signing
     - repackage-signing-msi
-    - mar-signing
-    - mar-signing-l10n
 
 primary-dependency:
-    - repackage
-    - repackage-l10n
+    - repackage-signing-l10n
+    - repackage-signing
 
 only-for-build-platforms:
     - linux-nightly/opt
     - linux64-nightly/opt
     - macosx64-nightly/opt
     - win32-nightly/opt
     - win64-nightly/opt
     - linux-devedition-nightly/opt
--- a/taskcluster/ci/config.yml
+++ b/taskcluster/ci/config.yml
@@ -72,17 +72,16 @@ treeherder:
         'TW64': 'Toolchain builds for Windows 64-bits'
         'WMC32': 'MinGW-Clang builds for Windows 32-bits'
         'WMC64': 'MinGW-Clang builds for Windows 64-bits'
         'Searchfox': 'Searchfox builds'
         'SM': 'Spidermonkey builds'
         'pub': 'APK publishing'
         'p': 'Partial generation'
         'ps': 'Partials signing'
-        'ms': 'Complete MAR signing'
         'Rel': 'Release promotion'
         'Snap': 'Snap image generation'
         'langpack': 'Langpack sigatures and uploads'
         'TPS': 'Sync tests'
         'UV': 'Update verify'
         'pipfu': 'pipfile update'
 
 index:
deleted file mode 100644
--- a/taskcluster/ci/mar-signing-l10n/kind.yml
+++ /dev/null
@@ -1,33 +0,0 @@
-# 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/.
-
-loader: taskgraph.loader.single_dep:loader
-
-transforms:
-    - taskgraph.transforms.name_sanity:transforms
-    - taskgraph.transforms.mar_signing:transforms
-    - taskgraph.transforms.task:transforms
-
-kind-dependencies:
-    - repackage-l10n
-
-only-for-build-platforms:
-    - linux-nightly/opt
-    - linux-devedition-nightly/opt
-    - linux64-nightly/opt
-    - linux64-devedition-nightly/opt
-    - linux64-asan-reporter-nightly/opt
-    - macosx64-nightly/opt
-    - macosx64-devedition-nightly/opt
-    - win32-nightly/opt
-    - win32-devedition-nightly/opt
-    - win64-nightly/opt
-    - win64-devedition-nightly/opt
-
-job-template:
-    shipping-phase: promote
-    treeherder-group: ms
-    description-suffix: 'mar signing'
-    required_signoffs:
-        - mar-signing
deleted file mode 100644
--- a/taskcluster/ci/mar-signing/kind.yml
+++ /dev/null
@@ -1,35 +0,0 @@
-# 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/.
-
-loader: taskgraph.loader.single_dep:loader
-
-transforms:
-    - taskgraph.transforms.name_sanity:transforms
-    - taskgraph.transforms.mar_signing:transforms
-    - taskgraph.transforms.task:transforms
-
-kind-dependencies:
-    - repackage
-
-only-for-build-platforms:
-    - linux-nightly/opt
-    - linux-devedition-nightly/opt
-    - linux64-nightly/opt
-    - linux64-devedition-nightly/opt
-    - linux64-asan-reporter-nightly/opt
-    - macosx64-nightly/opt
-    - macosx64-devedition-nightly/opt
-    - win32-nightly/opt
-    - win32-devedition-nightly/opt
-    - win64-nightly/opt
-    - win64-devedition-nightly/opt
-    - linux64-asan-reporter-nightly/opt
-    - win64-asan-reporter-nightly/opt
-
-job-template:
-    shipping-phase: promote
-    treeherder-group: ms
-    description-suffix: 'mar signing'
-    required_signoffs:
-        - mar-signing
--- a/taskcluster/ci/partials-signing/kind.yml
+++ b/taskcluster/ci/partials-signing/kind.yml
@@ -1,20 +1,16 @@
 # 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/.
 
 loader: taskgraph.loader.single_dep:loader
 
 transforms:
   - taskgraph.transforms.name_sanity:transforms
-  - taskgraph.transforms.mar_signing:transforms
+  - taskgraph.transforms.partials_signing:transforms
   - taskgraph.transforms.task:transforms
 
 kind-dependencies:
   - partials
 
 job-template:
   shipping-phase: promote
-  treeherder-group: ps
-  description-suffix: 'partial signing'
-  required_signoffs:
-    - mar-signing
--- a/taskcluster/ci/partials/kind.yml
+++ b/taskcluster/ci/partials/kind.yml
@@ -5,18 +5,18 @@
 loader: taskgraph.loader.single_dep:loader
 
 transforms:
    - taskgraph.transforms.name_sanity:transforms
    - taskgraph.transforms.partials:transforms
    - taskgraph.transforms.task:transforms
 
 kind-dependencies:
-   - repackage
-   - repackage-l10n
+   - repackage-signing
+   - repackage-signing-l10n
 
 only-for-attributes:
    - nightly
 
 only-for-build-platforms:
    - macosx64-nightly/opt
    - macosx64-devedition-nightly/opt
    - win32-nightly/opt
--- a/taskcluster/ci/repackage-signing-l10n/kind.yml
+++ b/taskcluster/ci/repackage-signing-l10n/kind.yml
@@ -9,15 +9,22 @@ transforms:
    - taskgraph.transforms.repackage_signing:transforms
    - taskgraph.transforms.repackage_routes:transforms
    - taskgraph.transforms.task:transforms
 
 kind-dependencies:
    - repackage-l10n
 
 only-for-build-platforms:
+   - linux-nightly/opt
+   - linux-devedition-nightly/opt
+   - linux64-nightly/opt
+   - linux64-devedition-nightly/opt
+   - linux64-asan-reporter-nightly/opt
+   - macosx64-nightly/opt
+   - macosx64-devedition-nightly/opt
    - win32-nightly/opt
    - win32-devedition-nightly/opt
    - win32/opt
    - win64-nightly/opt
    - win64-devedition-nightly/opt
    - win64/opt
    - win64-asan-reporter-nightly/opt
--- a/taskcluster/ci/repackage-signing/kind.yml
+++ b/taskcluster/ci/repackage-signing/kind.yml
@@ -9,15 +9,22 @@ transforms:
    - taskgraph.transforms.repackage_signing:transforms
    - taskgraph.transforms.repackage_routes:transforms
    - taskgraph.transforms.task:transforms
 
 kind-dependencies:
    - repackage
 
 only-for-build-platforms:
+   - linux-nightly/opt
+   - linux-devedition-nightly/opt
+   - linux64-nightly/opt
+   - linux64-devedition-nightly/opt
+   - linux64-asan-reporter-nightly/opt
+   - macosx64-nightly/opt
+   - macosx64-devedition-nightly/opt
    - win32-nightly/opt
    - win32-devedition-nightly/opt
    - win32/opt
    - win64-nightly/opt
    - win64-devedition-nightly/opt
    - win64/opt
    - win64-asan-reporter-nightly/opt
--- a/taskcluster/docs/attributes.rst
+++ b/taskcluster/docs/attributes.rst
@@ -261,15 +261,8 @@ cache_digest
 Some tasks generate artifacts that are cached between pushes. This is the unique string used
 to identify the current version of the artifacts. See :py:mod:`taskgraph.util.cached_task`.
 
 cache_type
 ==========
 Some tasks generate artifacts that are cached between pushes. This is the type of cache that is
 used for the this task. See :py:mod:`taskgraph.util.cached_task`.
 
-required_signoffs
-=================
-A list of release signoffs that this kind requires, should the release also
-require these signoffs. For example, ``mar-signing`` signoffs may be required
-by some releases in the future; for any releases that require ``mar-signing``
-signoffs, the kinds that also require that signoff are marked with this
-attribute.
--- a/taskcluster/docs/kinds.rst
+++ b/taskcluster/docs/kinds.rst
@@ -443,29 +443,23 @@ and this task would package that up as a
 
 repackage-l10n
 --------------
 Repackage-L10n is a ```Repackage``` task split up to be suitable for use after l10n repacks.
 
 
 repackage-signing
 -----------------
-Repackage-signing take the repackaged installers (windows) and signs them.
+Repackage-signing take the repackaged installers (windows) and update packaging (with
+the signed internal bits) and signs them.
 
 repackage-signing-l10n
 ----------------------
-Repackage-signing-l10n take the repackaged installers (windows) and signs them for localized versions.
-
-mar-signing
------------
-Mar-signing takes the complete update MARs and signs them.
-
-mar-signing-l10n
-----------------
-Mar-signing-l10n takes the complete update MARs and signs them for localized versions.
+Repackage-signing take the repackaged installers (windows) and update packaging (with
+the signed internal bits) and signs them for localized versions.
 
 repackage-msi
 -------------
 Repackage-msi takes the signed full installer and produces an msi installer (that wraps the full installer)
 Using the ```./mach repackage``` command
 
 repackage-signing-msi
 ---------------------
--- a/taskcluster/docs/parameters.rst
+++ b/taskcluster/docs/parameters.rst
@@ -171,22 +171,16 @@ Release Promotion
    The build number for partner repacks. We sometimes have multiple partner build numbers per release build number; this parameter lets us bump them independently. Defaults to 1.
 
 ``release_enable_emefree``
    Boolean which controls repacking vanilla Firefox builds into EME-free builds.
 
 ``release_product``
    The product that is being released.
 
-``required_signoffs``
-   A list of signoffs that are required for this release promotion flavor. If specified, and if the corresponding `signoff_urls` url isn't specified, tasks that require these signoffs will not be scheduled.
-
-``signoff_urls``
-   A dictionary of signoff keys to url values. These are the urls marking the corresponding ``required_signoffs`` as signed off.
-
 Comm Push Information
 ---------------------
 
 These parameters correspond to the repository and revision of the comm-central
 repository to checkout. Their meaning is the same as the corresponding
 parameters for the gecko repository above. They are optional, but if any of
 them are specified, they must all be specified.
 
--- a/taskcluster/taskgraph/actions/release_promotion.py
+++ b/taskcluster/taskgraph/actions/release_promotion.py
@@ -24,51 +24,27 @@ from taskgraph.util.partners import (
     get_token
 )
 from taskgraph.taskgraph import TaskGraph
 from taskgraph.decision import taskgraph_decision
 from taskgraph.parameters import Parameters
 from taskgraph.util.attributes import RELEASE_PROMOTION_PROJECTS
 
 
-RELEASE_PROMOTION_SIGNOFFS = ('mar-signing', )
-
-
 def is_release_promotion_available(parameters):
     return parameters['project'] in RELEASE_PROMOTION_PROJECTS
 
 
 def get_partner_config(partner_url_config, github_token):
     partner_config = {}
     for kind, url in partner_url_config.items():
         partner_config[kind] = get_partner_config_by_url(url, kind, github_token)
     return partner_config
 
 
-def get_signoff_properties():
-    props = {}
-    for signoff in RELEASE_PROMOTION_SIGNOFFS:
-        props[signoff] = {
-            'type': 'string',
-        }
-    return props
-
-
-def get_required_signoffs(input, parameters):
-    input_signoffs = set(input.get('required_signoffs', []))
-    params_signoffs = set(parameters['required_signoffs'] or [])
-    return sorted(list(input_signoffs | params_signoffs))
-
-
-def get_signoff_urls(input, parameters):
-    signoff_urls = parameters['signoff_urls']
-    signoff_urls.update(input.get('signoff_urls', {}))
-    return signoff_urls
-
-
 def get_flavors(graph_config, param):
     """
     Get all flavors with the given parameter enabled.
     """
     promotion_flavors = graph_config['release-promotion']['flavors']
     return sorted([
         flavor for (flavor, config) in promotion_flavors.items()
         if config.get(param, False)
@@ -216,29 +192,16 @@ def get_flavors(graph_config, param):
                 'properties': {},
                 'additionalProperties': True,
             },
             'release_enable_emefree': {
                 'type': 'boolean',
                 'default': False,
                 'description': ('Toggle for creating EME-free repacks'),
             },
-            'required_signoffs': {
-                'type': 'array',
-                'description': ('The flavor of release promotion to perform.'),
-                'items': {
-                    'enum': RELEASE_PROMOTION_SIGNOFFS,
-                }
-            },
-            'signoff_urls': {
-                'type': 'object',
-                'default': {},
-                'additionalProperties': False,
-                'properties': get_signoff_properties(),
-            },
         },
         "required": ['release_promotion_flavor', 'build_number'],
     }
 )
 def release_promotion_action(parameters, graph_config, input, task_group_id, task_id, task):
     release_promotion_flavor = input['release_promotion_flavor']
     promotion_config = graph_config['release-promotion']['flavors'][release_promotion_flavor]
     release_history = {}
@@ -340,15 +303,12 @@ def release_promotion_action(parameters,
         parameters['release_partner_build_number'] = input['release_partner_build_number']
 
     if partner_config:
         parameters['release_partner_config'] = fix_partner_config(partner_config)
 
     if input['version']:
         parameters['version'] = input['version']
 
-    parameters['required_signoffs'] = get_required_signoffs(input, parameters)
-    parameters['signoff_urls'] = get_signoff_urls(input, parameters)
-
     # make parameters read-only
     parameters = Parameters(**parameters)
 
     taskgraph_decision({'root': graph_config.root_dir}, parameters=parameters)
--- a/taskcluster/taskgraph/decision.py
+++ b/taskcluster/taskgraph/decision.py
@@ -233,18 +233,16 @@ def get_decision_parameters(config, opti
     parameters['release_type'] = ''
     parameters['release_eta'] = ''
     parameters['release_enable_partners'] = False
     parameters['release_partners'] = []
     parameters['release_partner_config'] = {}
     parameters['release_partner_build_number'] = 1
     parameters['release_enable_emefree'] = False
     parameters['release_product'] = None
-    parameters['required_signoffs'] = []
-    parameters['signoff_urls'] = {}
     parameters['try_mode'] = None
     parameters['try_task_config'] = None
     parameters['try_options'] = None
 
     # owner must be an email, but sometimes (e.g., for ffxbld) it is not, in which
     # case, fake it
     if '@' not in parameters['owner']:
         parameters['owner'] += '@noreply.mozilla.org'
--- a/taskcluster/taskgraph/loader/multi_dep.py
+++ b/taskcluster/taskgraph/loader/multi_dep.py
@@ -4,17 +4,16 @@
 
 from __future__ import absolute_import, print_function, unicode_literals
 
 import copy
 
 from voluptuous import Required
 
 from ..task import Task
-from ..util.attributes import sorted_unique_list
 from ..util.schema import Schema
 
 schema = Schema({
     Required('primary-dependency', 'primary dependency task'): Task,
     Required(
         'dependent-tasks',
         'dictionary of dependent tasks, keyed by kind',
     ): {basestring: Task},
@@ -62,21 +61,16 @@ def loader(kind, path, config, params, l
             job.update(copy.deepcopy(job_template))
         # copy shipping_product from upstream
         product = job['primary-dependency'].attributes.get(
             'shipping_product',
             job['primary-dependency'].task.get('shipping-product')
         )
         if product:
             job.setdefault('shipping-product', product)
-        job.setdefault('attributes', {})['required_signoffs'] = sorted_unique_list(
-            *[task.attributes.get('required_signoffs', [])
-              for task in dep_tasks.values()
-              ]
-        )
 
         yield job
 
 
 def group_tasks(config, tasks):
     group_by_fn = GROUP_BY_MAP[config['group-by']]
 
     groups = group_by_fn(config, tasks)
--- a/taskcluster/taskgraph/parameters.py
+++ b/taskcluster/taskgraph/parameters.py
@@ -73,18 +73,16 @@ PARAMETERS = {
     'release_enable_partners': False,
     'release_eta': '',
     'release_history': {},
     'release_partners': None,
     'release_partner_config': None,
     'release_partner_build_number': 1,
     'release_type': 'nightly',
     'release_product': None,
-    'required_signoffs': [],
-    'signoff_urls': {},
     'target_tasks_method': 'default',
     'try_mode': None,
     'try_options': None,
     'try_task_config': None,
     'version': get_version(),
 }
 
 COMM_PARAMETERS = {
--- a/taskcluster/taskgraph/target_tasks.py
+++ b/taskcluster/taskgraph/target_tasks.py
@@ -80,24 +80,16 @@ def filter_release_tasks(task, parameter
             return False
 
     if task.attributes.get('shipping_phase') not in (None, 'build'):
         return False
 
     return True
 
 
-def filter_out_missing_signoffs(task, parameters):
-    for signoff in parameters['required_signoffs']:
-        if signoff not in parameters['signoff_urls'] and \
-           signoff in task.attributes.get('required_signoffs', []):
-            return False
-    return True
-
-
 def standard_filter(task, parameters):
     return all(
         filter_func(task, parameters) for filter_func in
         (filter_out_cron, filter_for_project, filter_for_hg_branch)
     )
 
 
 def _try_task_config(full_task_graph, parameters, graph_config):
@@ -323,36 +315,31 @@ def target_tasks_promote_desktop(full_ta
         if task.attributes.get('shipping_product') != parameters['release_product']:
             return False
 
         # 'secondary' balrog/update verify/final verify tasks only run for RCs
         if parameters.get('release_type') != 'release-rc':
             if 'secondary' in task.kind:
                 return False
 
-        if not filter_out_missing_signoffs(task, parameters):
-            return False
-
         if task.attributes.get('shipping_phase') == 'promote':
             return True
 
     return [l for l, t in full_task_graph.tasks.iteritems() if filter(t)]
 
 
 @_target_task('push_desktop')
 def target_tasks_push_desktop(full_task_graph, parameters, graph_config):
     """Select the set of tasks required to push a build of desktop to cdns.
     Previous build deps will be optimized out via action task."""
     filtered_for_candidates = target_tasks_promote_desktop(
         full_task_graph, parameters, graph_config,
     )
 
     def filter(task):
-        if not filter_out_missing_signoffs(task, parameters):
-            return False
         # Include promotion tasks; these will be optimized out
         if task.label in filtered_for_candidates:
             return True
         if task.attributes.get('shipping_product') == parameters['release_product'] and \
                 task.attributes.get('shipping_phase') == 'push':
             return True
 
     return [l for l, t in full_task_graph.tasks.iteritems() if filter(t)]
@@ -371,18 +358,16 @@ def target_tasks_ship_desktop(full_task_
         )
     else:
         # ship_firefox runs after `push`; include all push tasks.
         filtered_for_candidates = target_tasks_push_desktop(
             full_task_graph, parameters, graph_config,
         )
 
     def filter(task):
-        if not filter_out_missing_signoffs(task, parameters):
-            return False
         # Include promotion tasks; these will be optimized out
         if task.label in filtered_for_candidates:
             return True
         if task.attributes.get('shipping_product') != parameters['release_product'] or \
                 task.attributes.get('shipping_phase') != 'ship':
             return False
 
         if 'secondary' in task.kind:
--- a/taskcluster/taskgraph/transforms/balrog_submit.py
+++ b/taskcluster/taskgraph/transforms/balrog_submit.py
@@ -33,18 +33,16 @@ balrog_description_schema = schema.exten
     # unique label to describe this balrog task, defaults to balrog-{dep.label}
     Optional('label'): basestring,
 
     # treeherder is allowed here to override any defaults we use for beetmover.  See
     # taskcluster/taskgraph/transforms/task.py for the schema details, and the
     # below transforms for defaults of various values.
     Optional('treeherder'): task_description_schema['treeherder'],
 
-    Optional('attributes'): task_description_schema['attributes'],
-
     # Shipping product / phase
     Optional('shipping-product'): task_description_schema['shipping-product'],
     Optional('shipping-phase'): task_description_schema['shipping-phase'],
 })
 
 
 @transforms.add
 def validate(config, jobs):
--- a/taskcluster/taskgraph/transforms/beetmover_repackage.py
+++ b/taskcluster/taskgraph/transforms/beetmover_repackage.py
@@ -125,28 +125,26 @@ UPSTREAM_ARTIFACT_SIGNED_PATHS = _compil
 UPSTREAM_ARTIFACT_REPACKAGE_PATHS = [
     'target.dmg',
 ]
 # Until bug 1331141 is fixed, if you are adding any new artifacts here that
 # need to be transfered to S3, please be aware you also need to follow-up
 # with a beetmover patch in https://github.com/mozilla-releng/beetmoverscript/.
 # See example in bug 1348286
 UPSTREAM_ARTIFACT_SIGNED_REPACKAGE_PATHS = [
+    'target.complete.mar',
+    'target.bz2.complete.mar',
     'target.installer.exe',
     'target.stub-installer.exe',
 ]
 
 UPSTREAM_ARTIFACT_SIGNED_MSI_PATHS = [
     'target.installer.msi',
 ]
 
-UPSTREAM_ARTIFACT_SIGNED_MAR_PATHS = [
-    'target.complete.mar',
-]
-
 # Voluptuous uses marker objects as dictionary *keys*, but they are not
 # comparable, so we cast all of the keys back to regular strings
 task_description_schema = {str(k): v for k, v in task_description_schema.schema.iteritems()}
 
 transforms = TransformSequence()
 
 # shortcut for a string where task references are allowed
 taskref_or_string = Any(
@@ -160,18 +158,16 @@ beetmover_description_schema = schema.ex
     # unique label to describe this beetmover task, defaults to {dep.label}-beetmover
     Required('label'): basestring,
 
     # treeherder is allowed here to override any defaults we use for beetmover.  See
     # taskcluster/taskgraph/transforms/task.py for the schema details, and the
     # below transforms for defaults of various values.
     Optional('treeherder'): task_description_schema['treeherder'],
 
-    Optional('attributes'): task_description_schema['attributes'],
-
     # locale is passed only for l10n beetmoving
     Optional('locale'): basestring,
     Required('shipping-phase'): task_description_schema['shipping-phase'],
     # Optional until we fix asan (run_on_projects?)
     Optional('shipping-product'): task_description_schema['shipping-product'],
 })
 
 
@@ -206,43 +202,39 @@ def make_task_description(config, jobs):
                 locale=attributes.get('locale', 'en-US'),
                 build_platform=attributes.get('build_platform'),
                 build_type=attributes.get('build_type')
             )
         )
 
         upstream_deps = job['dependent-tasks']
 
+        # TODO fix the upstreamArtifact generation to not need this?
         signing_name = "build-signing"
         build_name = "build"
         repackage_name = "repackage"
         repackage_signing_name = "repackage-signing"
         msi_signing_name = "repackage-signing-msi"
-        mar_signing_name = "mar-signing"
         if job.get('locale'):
             signing_name = "nightly-l10n-signing"
             build_name = "nightly-l10n"
             repackage_name = "repackage-l10n"
             repackage_signing_name = "repackage-signing-l10n"
-            mar_signing_name = "mar-signing-l10n"
         dependencies = {
             "build": upstream_deps[build_name],
             "repackage": upstream_deps[repackage_name],
+            "repackage-signing": upstream_deps[repackage_signing_name],
             "signing": upstream_deps[signing_name],
-            "mar-signing": upstream_deps[mar_signing_name],
         }
         if 'partials-signing' in upstream_deps:
             dependencies['partials-signing'] = upstream_deps['partials-signing']
         if msi_signing_name in upstream_deps:
             dependencies[msi_signing_name] = upstream_deps[msi_signing_name]
-        if repackage_signing_name in upstream_deps:
-            dependencies["repackage-signing"] = upstream_deps[repackage_signing_name]
 
         attributes = copy_attributes_from_dependent_job(dep_job)
-        attributes.update(job.get('attributes', {}))
         if job.get('locale'):
             attributes['locale'] = job['locale']
 
         bucket_scope = get_beetmover_bucket_scope(config)
         action_scope = get_beetmover_action_scope(config)
 
         task = {
             'label': label,
@@ -262,17 +254,16 @@ def make_task_description(config, jobs):
 
 def generate_upstream_artifacts(job, dependencies, platform, locale=None, project=None):
 
     build_mapping = UPSTREAM_ARTIFACT_UNSIGNED_PATHS
     build_signing_mapping = UPSTREAM_ARTIFACT_SIGNED_PATHS
     repackage_mapping = UPSTREAM_ARTIFACT_REPACKAGE_PATHS
     repackage_signing_mapping = UPSTREAM_ARTIFACT_SIGNED_REPACKAGE_PATHS
     msi_signing_mapping = UPSTREAM_ARTIFACT_SIGNED_MSI_PATHS
-    mar_signing_mapping = UPSTREAM_ARTIFACT_SIGNED_MAR_PATHS
 
     artifact_prefix = get_artifact_prefix(job)
     if locale:
         artifact_prefix = '{}/{}'.format(artifact_prefix, locale)
         platform = "{}-l10n".format(platform)
 
     upstream_artifacts = []
 
@@ -306,17 +297,16 @@ def generate_upstream_artifacts(job, dep
                         "paths": ["{}/{}".format(artifact_prefix, path) for path in usable_paths],
                         "locale": locale or "en-US",
                     })
 
     for task_type, cot_type, paths in [
         ('repackage', 'repackage', repackage_mapping),
         ('repackage-signing', 'repackage', repackage_signing_mapping),
         ('repackage-signing-msi', 'repackage', msi_signing_mapping),
-        ('mar-signing', 'signing', mar_signing_mapping),
     ]:
         if task_type not in dependencies:
             continue
 
         paths = ["{}/{}".format(artifact_prefix, path) for path in paths]
         paths = [
             path for path in paths
             if path in dependencies[task_type].release_artifacts]
--- a/taskcluster/taskgraph/transforms/beetmover_repackage_l10n.py
+++ b/taskcluster/taskgraph/transforms/beetmover_repackage_l10n.py
@@ -31,15 +31,14 @@ def make_beetmover_description(config, j
         treeherder = {
             'symbol': join_symbol(group, symbol),
         }
 
         beet_description = {
             'label': job['label'],
             'primary-dependency': dep_job,
             'dependent-tasks': job['dependent-tasks'],
-            'attributes': job['attributes'],
             'treeherder': treeherder,
             'locale': locale,
             'shipping-phase': job['shipping-phase'],
             'shipping-product': job['shipping-product'],
         }
         yield beet_description
--- a/taskcluster/taskgraph/transforms/partials.py
+++ b/taskcluster/taskgraph/transforms/partials.py
@@ -53,40 +53,51 @@ def make_task_description(config, jobs):
 
         treeherder.setdefault('platform',
                               "{}/opt".format(dep_th_platform))
         treeherder.setdefault('kind', 'build')
         treeherder.setdefault('tier', 1)
 
         dependent_kind = str(dep_job.kind)
         dependencies = {dependent_kind: dep_job.label}
+        signing_dependencies = dep_job.dependencies
+        # This is so we get the build task etc in our dependencies to
+        # have better beetmover support.
+        dependencies.update(signing_dependencies)
 
         attributes = copy_attributes_from_dependent_job(dep_job)
         locale = dep_job.attributes.get('locale')
         if locale:
             attributes['locale'] = locale
             treeherder['symbol'] = "p({})".format(locale)
-        attributes['shipping_phase'] = job['shipping-phase']
 
         build_locale = locale or 'en-US'
 
         builds = get_builds(config.params['release_history'], dep_th_platform,
                             build_locale)
 
         # If the list is empty there's no available history for this platform
         # and locale combination, so we can't build any partials.
         if not builds:
             continue
 
-        dep_task_ref = '<{}>'.format(dependent_kind)
+        signing_task = None
+        for dependency in sorted(dependencies.keys()):
+            if 'repackage-signing-l10n' in dependency:
+                signing_task = dependency
+                break
+            if 'repackage-signing' in dependency:
+                signing_task = dependency
+                break
+        signing_task_ref = '<{}>'.format(signing_task)
 
         extra = {'funsize': {'partials': list()}}
         update_number = 1
         artifact_path = "{}{}".format(
-            get_taskcluster_artifact_prefix(dep_job, dep_task_ref, locale=locale),
+            get_taskcluster_artifact_prefix(dep_job, signing_task_ref, locale=locale),
             'target.complete.mar'
         )
         for build in sorted(builds):
             partial_info = {
                 'locale': build_locale,
                 'from_mar': builds[build]['mar_url'],
                 'to_mar': {'task-reference': artifact_path},
                 'platform': get_balrog_platform_name(dep_th_platform),
rename from taskcluster/taskgraph/transforms/mar_signing.py
rename to taskcluster/taskgraph/transforms/partials_signing.py
--- a/taskcluster/taskgraph/transforms/mar_signing.py
+++ b/taskcluster/taskgraph/transforms/partials_signing.py
@@ -1,33 +1,32 @@
 # 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/.
 """
-Transform the {partials,mar}-signing task into an actual task description.
+Transform the partials task into an actual task description.
 """
 from __future__ import absolute_import, print_function, unicode_literals
 
 from taskgraph.transforms.base import TransformSequence
-from taskgraph.util.attributes import copy_attributes_from_dependent_job, sorted_unique_list
+from taskgraph.util.attributes import copy_attributes_from_dependent_job
 from taskgraph.util.scriptworker import (
     get_signing_cert_scope_per_platform,
     get_worker_type_for_scope,
 )
 from taskgraph.util.partials import get_balrog_platform_name, get_partials_artifacts
 from taskgraph.util.taskcluster import get_artifact_prefix
-from taskgraph.util.treeherder import join_symbol
 
 import logging
 logger = logging.getLogger(__name__)
 
 transforms = TransformSequence()
 
 
-def generate_partials_artifacts(job, release_history, platform, locale=None):
+def generate_upstream_artifacts(job, release_history, platform, locale=None):
     artifact_prefix = get_artifact_prefix(job)
     if locale:
         artifact_prefix = '{}/{}'.format(artifact_prefix, locale)
     else:
         locale = 'en-US'
 
     artifacts = get_partials_artifacts(release_history, platform, locale)
 
@@ -58,93 +57,65 @@ def generate_partials_artifacts(job, rel
     }
 
     if old_mar_upstream_artifacts["paths"]:
         upstream_artifacts.append(old_mar_upstream_artifacts)
 
     return upstream_artifacts
 
 
-def generate_complete_artifacts(job, platform, locale=None):
-    artifact_prefix = get_artifact_prefix(job)
-    if locale:
-        artifact_prefix = '{}/{}'.format(artifact_prefix, locale)
-
-    upstream_artifacts = [{
-        "taskId": {"task-reference": '<{}>'.format(job.kind)},
-        "taskType": 'build',
-        "paths": [
-            "{}/target.complete.mar".format(artifact_prefix)
-        ],
-        "formats": ["autograph_hash_only_mar384"],
-    }]
-
-    return upstream_artifacts
-
-
 @transforms.add
 def make_task_description(config, jobs):
     for job in jobs:
         dep_job = job['primary-dependency']
-        locale = dep_job.attributes.get('locale')
 
         treeherder = job.get('treeherder', {})
-        treeherder['symbol'] = join_symbol(
-            job.get('treeherder-group', 'ms'),
-            locale or 'N'
-        )
+        treeherder.setdefault('symbol', 'ps(N)')
 
         dep_th_platform = dep_job.task.get('extra', {}).get(
             'treeherder', {}).get('machine', {}).get('platform', '')
-        label = job.get('label', "{}-{}".format(config.kind, dep_job.label))
+        label = job.get('label', "partials-signing-{}".format(dep_job.label))
         dep_th_platform = dep_job.task.get('extra', {}).get(
             'treeherder', {}).get('machine', {}).get('platform', '')
         treeherder.setdefault('platform',
                               "{}/opt".format(dep_th_platform))
         treeherder.setdefault('kind', 'build')
         treeherder.setdefault('tier', 1)
 
         dependent_kind = str(dep_job.kind)
         dependencies = {dependent_kind: dep_job.label}
         signing_dependencies = dep_job.dependencies
         # This is so we get the build task etc in our dependencies to
         # have better beetmover support.
         dependencies.update(signing_dependencies)
 
         attributes = copy_attributes_from_dependent_job(dep_job)
-        attributes['required_signoffs'] = sorted_unique_list(
-            attributes.get('required_signoffs', []),
-            job.pop('required_signoffs')
-        )
-        attributes['shipping_phase'] = job['shipping-phase']
+        locale = dep_job.attributes.get('locale')
         if locale:
             attributes['locale'] = locale
+            treeherder['symbol'] = 'ps({})'.format(locale)
 
         balrog_platform = get_balrog_platform_name(dep_th_platform)
-        if config.kind == 'partials-signing':
-            upstream_artifacts = generate_partials_artifacts(
-                dep_job, config.params['release_history'], balrog_platform, locale)
-        else:
-            upstream_artifacts = generate_complete_artifacts(
-                dep_job, balrog_platform, locale)
+        upstream_artifacts = generate_upstream_artifacts(
+            dep_job, config.params['release_history'], balrog_platform, locale)
 
         build_platform = dep_job.attributes.get('build_platform')
         is_nightly = dep_job.attributes.get('nightly')
         signing_cert_scope = get_signing_cert_scope_per_platform(
             build_platform, is_nightly, config
         )
 
         scopes = [signing_cert_scope, 'project:releng:signing:format:autograph_hash_only_mar384']
         if any("mar" in upstream_details["formats"] for upstream_details in upstream_artifacts):
             scopes.append('project:releng:signing:format:mar')
 
         task = {
             'label': label,
-            'description': "{} {}".format(
-                dep_job.task["metadata"]["description"], job['description-suffix']),
+            'description': "{} Partials".format(
+                dep_job.task["metadata"]["description"]),
             'worker-type': get_worker_type_for_scope(config, signing_cert_scope),
             'worker': {'implementation': 'scriptworker-signing',
                        'upstream-artifacts': upstream_artifacts,
                        'max-run-time': 3600},
             'dependencies': dependencies,
             'attributes': attributes,
             'scopes': scopes,
             'run-on-projects': dep_job.attributes.get('run_on_projects'),
--- a/taskcluster/taskgraph/transforms/per_platform_dummy.py
+++ b/taskcluster/taskgraph/transforms/per_platform_dummy.py
@@ -3,29 +3,28 @@
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 """
 Transform the repackage task into an actual task description.
 """
 
 from __future__ import absolute_import, print_function, unicode_literals
 
 from taskgraph.transforms.base import TransformSequence
-from taskgraph.util.attributes import copy_attributes_from_dependent_job
 
 transforms = TransformSequence()
 
 
 @transforms.add
 def one_task_per_product_and_platform(config, jobs):
     unique_products_and_platforms = set()
     for job in jobs:
         dep_task = job["primary-dependency"]
         if 'primary-dependency' in job:
             del job['primary-dependency']
         product = dep_task.attributes.get("shipping_product")
         platform = dep_task.attributes.get("build_platform")
         if (product, platform) not in unique_products_and_platforms:
-            attributes = copy_attributes_from_dependent_job(dep_task)
-            attributes.update(job.get('attributes', {}))
-            job['attributes'] = attributes
+            job.setdefault("attributes", {})
+            job["attributes"]["shipping_product"] = product
+            job["attributes"]["build_platform"] = platform
             job["name"] = "{}-{}".format(product, platform)
             yield job
             unique_products_and_platforms.add((product, platform))
--- a/taskcluster/taskgraph/transforms/release_deps.py
+++ b/taskcluster/taskgraph/transforms/release_deps.py
@@ -19,17 +19,16 @@ def add_dependencies(config, jobs):
     for job in jobs:
         dependencies = {}
         # Add any kind_dependencies_tasks with matching product as dependencies
         product = job.get('shipping-product')
         phase = job.get('shipping-phase')
         if product is None:
             continue
 
-        required_signoffs = set(job.setdefault('attributes', {}).get('required_signoffs', []))
         for dep_task in config.kind_dependencies_tasks:
             # Weed out unwanted tasks.
             # XXX we have run-on-projects which specifies the on-push behavior;
             # we need another attribute that specifies release promotion,
             # possibly which action(s) each task belongs in.
             if product == 'fennec':
                 # Don't ship single locale fennec anymore - Bug 1408083
                 attr = dep_task.attributes.get
@@ -43,15 +42,12 @@ def add_dependencies(config, jobs):
             if dep_task.attributes.get("build_platform") and \
                job.get("attributes", {}).get("build_platform"):
                 if dep_task.attributes["build_platform"] != job["attributes"]["build_platform"]:
                     continue
             # Add matching product tasks to deps
             if dep_task.task.get('shipping-product') == product or \
                     dep_task.attributes.get('shipping_product') == product:
                 dependencies[dep_task.label] = dep_task.label
-                required_signoffs.update(dep_task.attributes.get('required_signoffs', []))
 
         job.setdefault('dependencies', {}).update(dependencies)
-        if required_signoffs:
-            job['attributes']['required_signoffs'] = sorted(required_signoffs)
 
         yield job
--- a/taskcluster/taskgraph/transforms/release_generate_checksums_beetmover.py
+++ b/taskcluster/taskgraph/transforms/release_generate_checksums_beetmover.py
@@ -50,18 +50,16 @@ release_generate_checksums_beetmover_sch
 
     # treeherder is allowed here to override any defaults we use for beetmover.  See
     # taskcluster/taskgraph/transforms/task.py for the schema details, and the
     # below transforms for defaults of various values.
     Optional('treeherder'): task_description_schema['treeherder'],
 
     Optional('shipping-phase'): task_description_schema['shipping-phase'],
     Optional('shipping-product'): task_description_schema['shipping-product'],
-
-    Optional('attributes'): task_description_schema['attributes'],
 })
 
 
 @transforms.add
 def validate(config, jobs):
     for job in jobs:
         label = job.get('primary-dependency', object).__dict__.get('label', '?no-label?')
         validate_schema(
--- a/taskcluster/taskgraph/transforms/release_snap_push.py
+++ b/taskcluster/taskgraph/transforms/release_snap_push.py
@@ -29,17 +29,16 @@ push_snap_description_schema = Schema({
     Required('treeherder'): task_description_schema['treeherder'],
     Required('run-on-projects'): task_description_schema['run-on-projects'],
     Required('worker-type'): optionally_keyed_by('release-level', basestring),
     Required('worker'): object,
     Required('scopes'): optionally_keyed_by('project', [basestring]),
     Required('shipping-phase'): task_description_schema['shipping-phase'],
     Required('shipping-product'): task_description_schema['shipping-product'],
     Optional('extra'): task_description_schema['extra'],
-    Optional('attributes'): task_description_schema['attributes'],
 })
 
 
 @transforms.add
 def validate_jobs_schema_transform(config, jobs):
     for job in jobs:
         label = job.get('label', '?no-label?')
         validate_schema(
--- a/taskcluster/taskgraph/transforms/repackage_signing.py
+++ b/taskcluster/taskgraph/transforms/repackage_signing.py
@@ -31,16 +31,18 @@ repackage_signing_description_schema = s
     Required('depname', default='repackage'): basestring,
     Optional('label'): basestring,
     Optional('treeherder'): task_description_schema['treeherder'],
     Optional('shipping-product'): task_description_schema['shipping-product'],
     Optional('shipping-phase'): task_description_schema['shipping-phase'],
 })
 
 SIGNING_FORMATS = {
+    'target.complete.mar': ["autograph_hash_only_mar384"],
+    'target.bz2.complete.mar': ["mar"],
     "target.installer.exe": ["sha2signcode"],
     "target.stub-installer.exe": ["sha2signcodestub"],
     "target.installer.msi": ["sha2signcode"],
 }
 
 
 @transforms.add
 def validate(config, jobs):
--- a/taskcluster/taskgraph/util/attributes.py
+++ b/taskcluster/taskgraph/util/attributes.py
@@ -31,17 +31,16 @@ RELEASE_PROMOTION_PROJECTS = {
     'try-comm-central',
 } | RELEASE_PROJECTS
 
 _OPTIONAL_ATTRIBUTES = (
     'artifact_prefix',
     'l10n_chunk',
     'locale',
     'nightly',
-    'required_signoffs',
     'signed',
     'shipping_phase',
     'shipping_product',
     'stub-installer',
 )
 
 
 def attrmatch(attributes, **kwargs):
@@ -127,14 +126,8 @@ def copy_attributes_from_dependent_job(d
     }
 
     attributes.update({
         attr: dep_job.attributes[attr]
         for attr in _OPTIONAL_ATTRIBUTES if attr in dep_job.attributes
     })
 
     return attributes
-
-
-def sorted_unique_list(*args):
-    """Join one or more lists, and return a sorted list of unique members"""
-    combined = set().union(*args)
-    return sorted(combined)
--- a/taskcluster/taskgraph/util/verify.py
+++ b/taskcluster/taskgraph/util/verify.py
@@ -176,43 +176,16 @@ def verify_dependency_tiers(task, taskgr
                     continue
                 if tier < tiers[d]:
                     raise Exception(
                         '{} (tier {}) cannot depend on {} (tier {})'
                         .format(task.label, printable_tier(tier),
                                 d, printable_tier(tiers[d])))
 
 
-@verifications.add('full_task_graph')
-def verify_required_signoffs(task, taskgraph, scratch_pad):
-    """
-    Task with required signoffs can't be dependencies of tasks with less
-    required signoffs.
-    """
-    all_required_signoffs = scratch_pad
-    if task is not None:
-        all_required_signoffs[task.label] = set(task.attributes.get('required_signoffs', []))
-    else:
-        def printable_signoff(signoffs):
-            if len(signoffs) == 1:
-                return 'required signoff {}'.format(*signoffs)
-            elif signoffs:
-                return 'required signoffs {}'.format(', '.join(signoffs))
-            else:
-                return 'no required signoffs'
-        for task in taskgraph.tasks.itervalues():
-            required_signoffs = all_required_signoffs[task.label]
-            for d in task.dependencies.itervalues():
-                if required_signoffs < all_required_signoffs[d]:
-                    raise Exception(
-                        '{} ({}) cannot depend on {} ({})'
-                        .format(task.label, printable_signoff(required_signoffs),
-                                d, printable_signoff(all_required_signoffs[d])))
-
-
 @verifications.add('optimized_task_graph')
 def verify_always_optimized(task, taskgraph, scratch_pad):
     """
         This function ensures that always-optimized tasks have been optimized.
     """
     if task is None:
         return
     if task.task.get('workerType') == 'always-optimized':