Merge inbound to mozilla-central. a=merge
authorGurzau Raul <rgurzau@mozilla.com>
Mon, 18 Mar 2019 23:38:05 +0200
changeset 464782 fe798624cda039ae74289929e35487560d354db2
parent 464770 444c3fe889a6e4488b11b3f12c6e0fe2b93feaa2 (current diff)
parent 464781 93677154ad256f4b7e04c67ba2c28bfe27ad7565 (diff)
child 464783 2abb636ad481768b7c88619080cf224b2c266b2d
child 464870 d10f74dcdf3f38af647ec84855ee6dd35dd58be2
child 464918 6ed5e5a3e39ec3155a2a36f4d567e927df3a6f39
push id35725
push userrgurzau@mozilla.com
push dateMon, 18 Mar 2019 21:38:44 +0000
treeherdermozilla-central@fe798624cda0 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone68.0a1
first release with
nightly linux32
fe798624cda0 / 68.0a1 / 20190318213844 / files
nightly linux64
fe798624cda0 / 68.0a1 / 20190318213844 / files
nightly mac
fe798624cda0 / 68.0a1 / 20190318213844 / files
nightly win32
fe798624cda0 / 68.0a1 / 20190318213844 / files
nightly win64
fe798624cda0 / 68.0a1 / 20190318213844 / files
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
releases
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge inbound to mozilla-central. a=merge
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -1840,8 +1840,15 @@ pref("browser.discovery.enabled", true);
 pref("browser.discovery.containers.enabled", true);
 pref("browser.discovery.sites", "addons.mozilla.org");
 
 pref("browser.engagement.recent_visited_origins.expiry", 86400); // 24 * 60 * 60 (24 hours in seconds)
 
 pref("browser.aboutConfig.showWarning", true);
 
 pref("browser.toolbars.keyboard_navigation", true);
+
+// Prefs to control the Firefox Account toolbar menu.
+// This pref will surface existing Firefox Account information
+// as a button next to the hamburger menu. It allows
+// quick access to sign-in and manage your Firefox Account.
+pref("identity.fxaccounts.toolbar.enabled", true);
+pref("identity.fxaccounts.toolbar.accessed", false);
--- a/browser/base/content/browser-pageActions.js
+++ b/browser/base/content/browser-pageActions.js
@@ -1078,68 +1078,17 @@ BrowserPageActions.sendToDevice = {
   onLocationChange() {
     let action = PageActions.actionForID("sendToDevice");
     let browser = gBrowser.selectedBrowser;
     let url = browser.currentURI.spec;
     action.setDisabled(!gSync.isSendableURI(url), window);
   },
 
   onShowingSubview(panelViewNode) {
-    let bodyNode = panelViewNode.querySelector(".panel-subview-body");
-    let panelNode = panelViewNode.closest("panel");
-    let browser = gBrowser.selectedBrowser;
-    let url = browser.currentURI.spec;
-    let title = browser.contentTitle;
-    let multiselected = gBrowser.selectedTab.multiselected;
-
-    // This is on top because it also clears the device list between state
-    // changes.
-    gSync.populateSendTabToDevicesMenu(bodyNode, url, title, multiselected, (clientId, name, clientType, lastModified) => {
-      if (!name) {
-        return document.createXULElement("toolbarseparator");
-      }
-      let item = document.createXULElement("toolbarbutton");
-      item.classList.add("pageAction-sendToDevice-device", "subviewbutton");
-      if (clientId) {
-        item.classList.add("subviewbutton-iconic");
-        if (lastModified) {
-          item.setAttribute("tooltiptext", gSync.formatLastSyncDate(lastModified));
-        }
-      }
-
-      item.addEventListener("command", event => {
-        if (panelNode) {
-          PanelMultiView.hidePopup(panelNode);
-        }
-        // There are items in the subview that don't represent devices: "Sign
-        // in", "Learn about Sync", etc.  Device items will be .sendtab-target.
-        if (event.target.classList.contains("sendtab-target")) {
-          let action = PageActions.actionForID("sendToDevice");
-          let messageId = gSync.offline && "sendToDeviceOffline";
-          showBrowserPageActionFeedback(action, event, messageId);
-        }
-      });
-      return item;
-    });
-
-    bodyNode.removeAttribute("state");
-    // In the first ~10 sec after startup, Sync may not be loaded and the list
-    // of devices will be empty.
-    if (gSync.sendTabConfiguredAndLoading) {
-      bodyNode.setAttribute("state", "notready");
-      // Force a background Sync
-      Services.tm.dispatchToMainThread(async () => {
-        await Weave.Service.sync({why: "pageactions", engines: []}); // [] = clients engine only
-        // There's no way Sync is still syncing at this point, but we check
-        // anyway to avoid infinite looping.
-        if (!window.closed && !gSync.sendTabConfiguredAndLoading) {
-          this.onShowingSubview(panelViewNode);
-        }
-      });
-    }
+    gSync.populateSendTabToDevicesView(panelViewNode, this.onShowingSubview.bind(this));
   },
 };
 
 // add search engine
 BrowserPageActions.addSearchEngine = {
   get action() {
     return PageActions.actionForID("addSearchEngine");
   },
--- a/browser/base/content/browser-sync.js
+++ b/browser/base/content/browser-sync.js
@@ -11,16 +11,18 @@ ChromeUtils.defineModuleGetter(this, "Fx
   "resource://gre/modules/FxAccounts.jsm");
 ChromeUtils.defineModuleGetter(this, "EnsureFxAccountsWebChannel",
   "resource://gre/modules/FxAccountsWebChannel.jsm");
 ChromeUtils.defineModuleGetter(this, "Weave",
   "resource://services-sync/main.js");
 
 const MIN_STATUS_ANIMATION_DURATION = 1600;
 
+const FXA_NO_AVATAR_ZEROS = "00000000000000000000000000000000";
+
 var gSync = {
   _initialized: false,
   // The last sync start time. Used to calculate the leftover animation time
   // once syncing completes (bug 1239042).
   _syncStartTime: 0,
   _syncAnimationTimer: 0,
 
   _obs: [
@@ -93,17 +95,17 @@ var gSync = {
           }
         });
     XPCOMUtils.defineLazyPreferenceGetter(this, "PRODUCT_INFO_BASE_URL",
         "app.productInfo.baseURL");
     XPCOMUtils.defineLazyPreferenceGetter(this, "SYNC_ENABLED",
         "identity.fxaccounts.enabled");
   },
 
-  _maybeUpdateUIState() {
+  maybeUpdateUIState() {
     // Update the UI.
     if (UIState.isReady()) {
       const state = UIState.get();
       // If we are not configured, the UI is already in the right state when
       // we open the window. We can avoid a repaint.
       if (state.status != UIState.STATUS_NOT_CONFIGURED) {
         this.updateAllUI(state);
       }
@@ -138,17 +140,17 @@ var gSync = {
     document.getElementById("sync-setup").hidden = false;
 
     for (let topic of this._obs) {
       Services.obs.addObserver(this, topic, true);
     }
 
     this._generateNodeGetters();
 
-    this._maybeUpdateUIState();
+    this.maybeUpdateUIState();
 
     EnsureFxAccountsWebChannel();
 
     this._initialized = true;
   },
 
   uninit() {
     if (!this._initialized) {
@@ -186,16 +188,161 @@ var gSync = {
     }
   },
 
   updateAllUI(state) {
     this.updatePanelPopup(state);
     this.updateState(state);
     this.updateSyncButtonsTooltip(state);
     this.updateSyncStatus(state);
+    this.updateFxAToolbarPanel(state);
+  },
+
+  updateSendToDeviceTitle() {
+    let string = gBrowserBundle.GetStringFromName("sendTabsToDevice.label");
+    let title = PluralForm.get(1, string).replace("#1", 1);
+    if (gBrowser.selectedTab.multiselected) {
+      let tabCount = gBrowser.selectedTabs.length;
+      title = PluralForm.get(tabCount, string).replace("#1", tabCount);
+    }
+
+    document.getElementById("PanelUI-fxa-menu-sendtab-button").setAttribute("label", title);
+  },
+
+  showSendToDeviceView(anchor) {
+    PanelUI.showSubView("PanelUI-sendTabToDevice", anchor);
+    let panelViewNode = document.getElementById("PanelUI-sendTabToDevice");
+    this.populateSendTabToDevicesView(panelViewNode, this.populateSendTabToDevicesView);
+  },
+
+  populateSendTabToDevicesView(panelViewNode, reloadFunc) {
+    let bodyNode = panelViewNode.querySelector(".panel-subview-body");
+    let panelNode = panelViewNode.closest("panel");
+    let browser = gBrowser.selectedBrowser;
+    let url = browser.currentURI.spec;
+    let title = browser.contentTitle;
+    let multiselected = gBrowser.selectedTab.multiselected;
+
+    // This is on top because it also clears the device list between state
+    // changes.
+    this.populateSendTabToDevicesMenu(bodyNode, url, title, multiselected, (clientId, name, clientType, lastModified) => {
+      if (!name) {
+        return document.createXULElement("toolbarseparator");
+      }
+      let item = document.createXULElement("toolbarbutton");
+      item.classList.add("pageAction-sendToDevice-device", "subviewbutton");
+      if (clientId) {
+        item.classList.add("subviewbutton-iconic");
+        if (lastModified) {
+          item.setAttribute("tooltiptext", gSync.formatLastSyncDate(lastModified));
+        }
+      }
+
+      item.addEventListener("command", event => {
+        if (panelNode) {
+          PanelMultiView.hidePopup(panelNode);
+        }
+        // There are items in the subview that don't represent devices: "Sign
+        // in", "Learn about Sync", etc.  Device items will be .sendtab-target.
+        if (event.target.classList.contains("sendtab-target")) {
+          let action = PageActions.actionForID("sendToDevice");
+          let messageId = gSync.offline && "sendToDeviceOffline";
+          showBrowserPageActionFeedback(action, event, messageId);
+        }
+      });
+      return item;
+    });
+
+    bodyNode.removeAttribute("state");
+    // In the first ~10 sec after startup, Sync may not be loaded and the list
+    // of devices will be empty.
+    if (gSync.sendTabConfiguredAndLoading) {
+      bodyNode.setAttribute("state", "notready");
+      // Force a background Sync
+      Services.tm.dispatchToMainThread(async () => {
+        await Weave.Service.sync({why: "pageactions", engines: []}); // [] = clients engine only
+        // There's no way Sync is still syncing at this point, but we check
+        // anyway to avoid infinite looping.
+        if (!window.closed && !gSync.sendTabConfiguredAndLoading) {
+          reloadFunc(panelViewNode);
+        }
+      });
+    }
+  },
+
+  toggleAccountPanel(viewId, aEvent) {
+    // Don't show the panel if the window is in customization mode.
+    if (document.documentElement.hasAttribute("customizing")) {
+      return;
+    }
+
+    if ((aEvent.type == "mousedown" && aEvent.button != 0) ||
+        (aEvent.type == "keypress" && aEvent.charCode != KeyEvent.DOM_VK_SPACE &&
+        aEvent.keyCode != KeyEvent.DOM_VK_RETURN)) {
+      return;
+    }
+
+    if (!gFxaToolbarAccessed) {
+      Services.prefs.setBoolPref("identity.fxaccounts.toolbar.accessed", true);
+      document.documentElement.removeAttribute("fxa_avatar_badged");
+    }
+
+    const anchor = document.getElementById("fxa-toolbar-menu-button");
+    if (anchor.getAttribute("open") == "true") {
+      PanelUI.hide();
+    } else {
+      PanelUI.showSubView(viewId, anchor);
+    }
+  },
+
+  updateFxAToolbarPanel(state = {}) {
+    if (!gFxaToolbarEnabled) {
+      return;
+    }
+
+    const mainWindowEl = document.documentElement;
+
+    // The Firefox Account toolbar currently handles 3 different states for
+    // users. The default `not_configured state shows an empty avatar, `unverified`
+    // state shows an avatar with an email icon and the `verified` state will show
+    // the users custom profile image or a filled avatar.
+    let stateValue = "not_configured";
+    if (state.status === UIState.STATUS_LOGIN_FAILED || state.status === UIState.STATUS_NOT_VERIFIED) {
+      stateValue = "unverified";
+    } else if (state.status === UIState.STATUS_SIGNED_IN) {
+      stateValue = "signedin";
+      // Firefox Account specifies a `default` avatar image that uses the convention
+      // of all 0s in url. The default used in the design of the toolbar menu is
+      // different from the one provided by Firefox Account. Perform a check and only
+      // change avatar *if* this is not a default avatar.
+      if (state.avatarURL && !state.avatarURL.includes(FXA_NO_AVATAR_ZEROS)) {
+        // The user has specified a custom avatar, attempt to load the image on all the menu buttons.
+        const bgImage = `url("${state.avatarURL}")`;
+        let img = new Image();
+        img.onload = () => {
+          // If the image has successfully loaded, update the menu buttons else
+          // we will use the default avatar image.
+          mainWindowEl.style.setProperty("--avatar-image-url", bgImage);
+        };
+        img.onerror = () => {
+          // If the image failed to load, remove the property and default
+          // to standard avatar.
+          mainWindowEl.style.removeProperty("--avatar-image-url");
+        };
+        img.src = state.avatarURL;
+      } else {
+        mainWindowEl.style.removeProperty("--avatar-image-url");
+      }
+
+      document.getElementById("fxa-menu-email").value = state.email;
+
+      let defaultPanelTitle = this.fxaStrings.GetStringFromName("account.title");
+      document.getElementById("PanelUI-fxa").setAttribute("title", state.displayName ? state.displayName : defaultPanelTitle);
+    }
+    mainWindowEl.setAttribute("fxastatus", stateValue);
   },
 
   updatePanelPopup(state) {
     let defaultLabel = this.appMenuStatus.getAttribute("defaultlabel");
     // The localization string is for the signed in text, but it's the default text as well
     let defaultTooltiptext = this.appMenuStatus.getAttribute("signedinTooltiptext");
 
     const status = state.status;
@@ -312,16 +459,31 @@ var gSync = {
   },
 
   openSendToDevicePromo() {
     let url = this.PRODUCT_INFO_BASE_URL;
     url += "send-tabs/?utm_source=" + Services.appinfo.name.toLowerCase();
     switchToTabHavingURI(url, true, { replaceQueryString: true });
   },
 
+  async openFxAChangeAvatar(entryPoint) {
+    const url = await FxAccounts.config.promiseChangeAvatarURI(entryPoint);
+    switchToTabHavingURI(url, true, { replaceQueryString: true });
+  },
+
+  async openFxAEmailFirstPage(entryPoint) {
+    const url = await FxAccounts.config.promiseEmailFirstURI(entryPoint);
+    switchToTabHavingURI(url, true, { replaceQueryString: true });
+  },
+
+  async openFxAManagePage(entryPoint) {
+    const url = await FxAccounts.config.promiseManageURI(entryPoint);
+    switchToTabHavingURI(url, true, { replaceQueryString: true });
+  },
+
   async sendTabToDevice(url, targets, title) {
     const fxaCommandsDevices = [];
     const oldSendTabClients = [];
     for (const target of targets) {
       if (fxAccounts.commands.sendTab.isDeviceCompatible(target)) {
         fxaCommandsDevices.push(target);
       } else if (target.clientRecord) {
         oldSendTabClients.push(target.clientRecord);
@@ -589,38 +751,50 @@ var gSync = {
   },
 
   // Functions called by observers
   onActivityStart() {
     clearTimeout(this._syncAnimationTimer);
     this._syncStartTime = Date.now();
 
     let label = this.syncStrings.GetStringFromName("syncingtabs.label");
-    let syncIcon = document.getElementById("appMenu-fxa-icon");
-    let syncNow = document.getElementById("PanelUI-remotetabs-syncnow");
-    syncIcon.setAttribute("syncstatus", "active");
-    syncIcon.setAttribute("label", label);
-    syncIcon.setAttribute("disabled", "true");
-    syncNow.setAttribute("syncstatus", "active");
-    syncNow.setAttribute("label", label);
-    syncNow.setAttribute("disabled", "true");
+    let remotetabsSyncNowEl = document.getElementById("PanelUI-remotetabs-syncnow");
+    let fxaMenuSyncNowEl = document.getElementById("PanelUI-fxa-menu-syncnow-button");
+    let syncElements = [
+      document.getElementById("appMenu-fxa-icon"),
+      remotetabsSyncNowEl,
+      fxaMenuSyncNowEl,
+    ];
+
+    syncElements.forEach((el) => {
+      el.setAttribute("syncstatus", "active");
+      el.setAttribute("disabled", "true");
+    });
+
+    remotetabsSyncNowEl.setAttribute("label", label);
+    fxaMenuSyncNowEl.setAttribute("label", fxaMenuSyncNowEl.getAttribute("syncinglabel"));
   },
 
   _onActivityStop() {
     if (!gBrowser)
       return;
+
     let label = this.syncStrings.GetStringFromName("syncnow.label");
-    let syncIcon = document.getElementById("appMenu-fxa-icon");
-    let syncNow = document.getElementById("PanelUI-remotetabs-syncnow");
-    syncIcon.removeAttribute("syncstatus");
-    syncIcon.removeAttribute("disabled");
-    syncIcon.setAttribute("label", label);
-    syncNow.removeAttribute("syncstatus");
-    syncNow.removeAttribute("disabled");
-    syncNow.setAttribute("label", label);
+    let syncElements = [
+      document.getElementById("appMenu-fxa-icon"),
+      document.getElementById("PanelUI-remotetabs-syncnow"),
+      document.getElementById("PanelUI-fxa-menu-syncnow-button"),
+    ];
+
+    syncElements.forEach((el) => {
+      el.removeAttribute("syncstatus");
+      el.removeAttribute("disabled");
+      el.setAttribute("label", label);
+    });
+
     Services.obs.notifyObservers(null, "test:browser-sync:activity-stop");
   },
 
   onActivityStop() {
     let now = Date.now();
     let syncDuration = now - this._syncStartTime;
 
     if (syncDuration < MIN_STATUS_ANIMATION_DURATION) {
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -356,16 +356,26 @@ XPCOMUtils.defineLazyPreferenceGetter(th
   (aPref, aOldVal, aNewVal) => {
     if (aNewVal) {
       ToolbarKeyboardNavigator.init();
     } else {
       ToolbarKeyboardNavigator.uninit();
     }
   });
 
+XPCOMUtils.defineLazyPreferenceGetter(this, "gFxaToolbarEnabled",
+  "identity.fxaccounts.toolbar.enabled", false, (aPref, aOldVal, aNewVal) => {
+    showFxaToolbarMenu(aNewVal);
+  });
+
+XPCOMUtils.defineLazyPreferenceGetter(this, "gFxaToolbarAccessed",
+  "identity.fxaccounts.toolbar.accessed", false, (aPref, aOldVal, aNewVal) => {
+    showFxaToolbarMenu(gFxaToolbarEnabled);
+  });
+
 customElements.setElementCreationCallback("translation-notification", () => {
   Services.scriptloader.loadSubScript(
     "chrome://browser/content/translation-notification.js", window);
 });
 
 var gBrowser;
 var gLastValidURLStr = "";
 var gInPrintPreviewMode = false;
@@ -455,16 +465,44 @@ var gNavigatorBundle = {
   getString(key) {
     return gBrowserBundle.GetStringFromName(key);
   },
   getFormattedString(key, array) {
     return gBrowserBundle.formatStringFromName(key, array, array.length);
   },
 };
 
+function showFxaToolbarMenu(enable) {
+  // We only show the Firefox Account toolbar menu if the feature is enabled and
+  // if sync is enabled.
+  const syncEnabled = Services.prefs.getBoolPref("identity.fxaccounts.enabled", false);
+  const mainWindowEl = document.documentElement;
+  const fxaPanelEl = document.getElementById("PanelUI-fxa");
+  if (enable && syncEnabled) {
+    fxaPanelEl.addEventListener("ViewShowing", gSync.updateSendToDeviceTitle);
+
+    mainWindowEl.setAttribute("fxastatus", "not_configured");
+    // We have to manually update the sync state UI when toggling the FxA toolbar
+    // because it could show an invalid icon if the user is logged in and no sync
+    // event was performed yet.
+    gSync.maybeUpdateUIState();
+
+    // We set an attribute here so that we can toggle the custom
+    // badge depending on whether the FxA menu was ever accessed.
+    if (!gFxaToolbarAccessed) {
+      mainWindowEl.setAttribute("fxa_avatar_badged", "badged");
+    } else {
+      mainWindowEl.removeAttribute("fxa_avatar_badged");
+    }
+  } else {
+    mainWindowEl.removeAttribute("fxastatus");
+    fxaPanelEl.removeEventListener("ViewShowing", gSync.updateSendToDeviceTitle);
+  }
+}
+
 function UpdateBackForwardCommands(aWebNavigation) {
   var backCommand = document.getElementById("Browser:Back");
   var forwardCommand = document.getElementById("Browser:Forward");
 
   // Avoid setting attributes on commands if the value hasn't changed!
   // Remember, guys, setting attributes on elements is expensive!  They
   // get inherited into anonymous content, broadcast to other widgets, etc.!
   // Don't do it if the value hasn't changed! - dwh
@@ -1397,16 +1435,18 @@ var gBrowserInit = {
       }
       let nonQuery = url.prePath + url.filePath;
       if (nonQuery in gPageIcons) {
         gBrowser.setIcon(gBrowser.selectedTab, gPageIcons[nonQuery]);
       }
     });
 
     this._setInitialFocus();
+
+    showFxaToolbarMenu(gFxaToolbarEnabled);
   },
 
   onLoad() {
     gBrowser.addEventListener("DOMUpdateBlockedPopups", gPopupBlockerObserver);
 
     Services.obs.addObserver(gPluginHandler.NPAPIPluginCrashed, "plugin-crashed");
 
     window.addEventListener("AppCommand", HandleAppCommandEvent, true);
--- a/browser/base/content/browser.xul
+++ b/browser/base/content/browser.xul
@@ -1082,16 +1082,29 @@
                        removable="true"
                        onmousedown="PanelUI.showSubView('appMenu-libraryView', this, event);"
                        onkeypress="PanelUI.showSubView('appMenu-libraryView', this, event);"
                        closemenu="none"
                        cui-areatype="toolbar"
                        tooltiptext="&libraryButton.tooltip;"
                        label="&places.library.title;"/>
 
+        <toolbarbutton id="fxa-toolbar-menu-button" class="toolbarbutton-1 badged-button"
+                       onmousedown="gSync.toggleAccountPanel('PanelUI-fxa', event)"
+                       onkeypress="gSync.toggleAccountPanel('PanelUI-fxa, event)"
+                       consumeanchor="fxa-toolbar-menu-button"
+                       closemenu="none"
+                       label="&fxa.menu.firefoxAccount;"
+                       tooltiptext="&fxa.menu.firefoxAccount;"
+                       cui-areatype="toolbar"
+                       removable="true">
+                       <vbox>
+                        <image id="fxa-avatar-image"/>
+                       </vbox>
+        </toolbarbutton>
       </hbox>
 
       <toolbarbutton id="nav-bar-overflow-button"
                      class="toolbarbutton-1 chromeclass-toolbar-additional overflow-button"
                      skipintoolbarset="true"
                      tooltiptext="&navbarOverflow.label;">
         <box class="toolbarbutton-animatable-box">
           <image class="toolbarbutton-animatable-image"/>
--- a/browser/base/content/test/keyboard/browser_toolbarKeyNav.js
+++ b/browser/base/content/test/keyboard/browser_toolbarKeyNav.js
@@ -169,22 +169,24 @@ add_task(async function testTabStopNoBut
 add_task(async function testArrowsToolbarbuttons() {
   await BrowserTestUtils.withNewTab("about:blank", async function() {
     startFromUrlBar();
     await expectFocusAfterKey("Tab", "library-button");
     EventUtils.synthesizeKey("KEY_ArrowLeft");
     is(document.activeElement.id, "library-button",
        "ArrowLeft at end of button group does nothing");
     await expectFocusAfterKey("ArrowRight", "sidebar-button");
+    await expectFocusAfterKey("ArrowRight", "fxa-toolbar-menu-button");
     // This next check also confirms that the overflow menu button is skipped,
     // since it is currently invisible.
     await expectFocusAfterKey("ArrowRight", "PanelUI-menu-button");
     EventUtils.synthesizeKey("KEY_ArrowRight");
     is(document.activeElement.id, "PanelUI-menu-button",
        "ArrowRight at end of button group does nothing");
+    await expectFocusAfterKey("ArrowLeft", "fxa-toolbar-menu-button");
     await expectFocusAfterKey("ArrowLeft", "sidebar-button");
     await expectFocusAfterKey("ArrowLeft", "library-button");
   });
 });
 
 // Test that right/left arrows move through buttons wihch aren't toolbarbuttons
 // but have role="button".
 add_task(async function testArrowsRoleButton() {
@@ -224,21 +226,20 @@ add_task(async function testArrowsDisabl
 // Test that right arrow reaches the overflow menu button when it is visible.
 add_task(async function testArrowsOverflowButton() {
   await BrowserTestUtils.withNewTab("about:blank", async function() {
     // Move something to the overflow menu to make the button appear.
     CustomizableUI.addWidgetToArea("home-button", CustomizableUI.AREA_FIXED_OVERFLOW_PANEL);
     startFromUrlBar();
     await expectFocusAfterKey("Tab", "library-button");
     await expectFocusAfterKey("ArrowRight", "sidebar-button");
+    await expectFocusAfterKey("ArrowRight", "fxa-toolbar-menu-button");
     await expectFocusAfterKey("ArrowRight", "nav-bar-overflow-button");
-    await expectFocusAfterKey("ArrowRight", "PanelUI-menu-button");
-    await expectFocusAfterKey("ArrowLeft", "nav-bar-overflow-button");
     // Make sure the button is not reachable once it is invisible again.
     await expectFocusAfterKey("ArrowRight", "PanelUI-menu-button");
     resetToolbarWithoutDevEditionButtons();
     // Flush layout so its invisibility can be detected.
     document.getElementById("nav-bar-overflow-button").clientWidth;
-    await expectFocusAfterKey("ArrowLeft", "sidebar-button");
+    await expectFocusAfterKey("ArrowLeft", "fxa-toolbar-menu-button");
   });
 });
 
 registerCleanupFunction(() => CustomizableUI.reset());
--- a/browser/base/content/test/sync/browser_sync.js
+++ b/browser/base/content/test/sync/browser_sync.js
@@ -46,16 +46,18 @@ add_task(async function test_ui_state_si
     tooltip: statusBarTooltip,
     fxastatus: "signedin",
     avatarURL: "https://foo.bar",
     syncing: false,
     syncNowTooltip: lastSyncTooltip,
   });
   checkRemoteTabsPanel("PanelUI-remotetabs-main", false);
   checkMenuBarItem("sync-syncnowitem");
+  checkFxaToolbarButtonPanel("PanelUI-fxa-menu");
+  checkFxaToolbarButtonAvatar("signedin");
   gSync.relativeTimeFormat = origRelativeTimeFormat;
 });
 
 add_task(async function test_ui_state_syncing() {
   let state = {
     status: UIState.STATUS_SIGNED_IN,
     email: "foo@bar.com",
     displayName: "Foo Bar",
@@ -90,16 +92,18 @@ add_task(async function test_ui_state_un
   let signedOffLabel = gSync.appMenuStatus.getAttribute("defaultlabel");
   let statusBarTooltip = gSync.appMenuStatus.getAttribute("signedinTooltiptext");
   checkPanelUIStatusBar({
     label: signedOffLabel,
     tooltip: statusBarTooltip,
   });
   checkRemoteTabsPanel("PanelUI-remotetabs-setupsync");
   checkMenuBarItem("sync-setup");
+  checkFxaToolbarButtonPanel("PanelUI-fxa-signin");
+  checkFxaToolbarButtonAvatar("not_configured");
 });
 
 add_task(async function test_ui_state_unverified() {
   let state = {
     status: UIState.STATUS_NOT_VERIFIED,
     email: "foo@bar.com",
     lastSync: new Date(),
     syncing: false,
@@ -114,16 +118,18 @@ add_task(async function test_ui_state_un
     tooltip: tooltipText,
     fxastatus: "unverified",
     avatarURL: null,
     syncing: false,
     syncNowTooltip: tooltipText,
   });
   checkRemoteTabsPanel("PanelUI-remotetabs-unverified", false);
   checkMenuBarItem("sync-unverifieditem");
+  checkFxaToolbarButtonPanel("PanelUI-fxa-unverified");
+  checkFxaToolbarButtonAvatar("unverified");
 });
 
 add_task(async function test_ui_state_loginFailed() {
   let state = {
     status: UIState.STATUS_LOGIN_FAILED,
     email: "foo@bar.com",
   };
 
@@ -136,16 +142,18 @@ add_task(async function test_ui_state_lo
     tooltip: tooltipText,
     fxastatus: "login-failed",
     avatarURL: null,
     syncing: false,
     syncNowTooltip: tooltipText,
   });
   checkRemoteTabsPanel("PanelUI-remotetabs-reauthsync", false);
   checkMenuBarItem("sync-reauthitem");
+  checkFxaToolbarButtonPanel("PanelUI-fxa-unverified");
+  checkFxaToolbarButtonAvatar("unverified");
 });
 
 function checkPanelUIStatusBar({label, tooltip, fxastatus, avatarURL, syncing, syncNowTooltip}) {
   let labelNode = document.getElementById("appMenu-fxa-label");
   let tooltipNode = document.getElementById("appMenu-fxa-status");
   let statusNode = document.getElementById("appMenu-fxa-container");
   let avatar = document.getElementById("appMenu-fxa-avatar");
 
@@ -163,30 +171,29 @@ function checkPanelUIStatusBar({label, t
   }
 
   if (syncing != undefined && syncNowTooltip != undefined) {
     checkSyncNowButton("appMenu-fxa-icon", syncing, syncNowTooltip);
   }
 }
 
 function checkRemoteTabsPanel(expectedShownItemId, syncing, syncNowTooltip) {
-  checkItemsVisiblities(["PanelUI-remotetabs-main",
-                         "PanelUI-remotetabs-setupsync",
-                         "PanelUI-remotetabs-reauthsync",
-                         "PanelUI-remotetabs-unverified"],
-                        expectedShownItemId);
+  checkItemsVisibilities(["PanelUI-remotetabs-main",
+                          "PanelUI-remotetabs-setupsync",
+                          "PanelUI-remotetabs-reauthsync",
+                          "PanelUI-remotetabs-unverified"],
+                          expectedShownItemId);
 
   if (syncing != undefined && syncNowTooltip != undefined) {
     checkSyncNowButton("PanelUI-remotetabs-syncnow", syncing, syncNowTooltip);
   }
 }
 
 function checkMenuBarItem(expectedShownItemId) {
-  checkItemsVisiblities(["sync-setup", "sync-syncnowitem", "sync-reauthitem", "sync-unverifieditem"],
-                        expectedShownItemId);
+  checkItemsVisibilities(["sync-setup", "sync-syncnowitem", "sync-reauthitem", "sync-unverifieditem"], expectedShownItemId);
 }
 
 function checkSyncNowButton(buttonId, syncing, tooltip = null) {
   const remoteTabsButton = document.getElementById(buttonId);
 
   is(remoteTabsButton.getAttribute("syncstatus"), syncing ? "active" : "", "button active has the right value");
   if (tooltip) {
     is(remoteTabsButton.getAttribute("tooltiptext"), tooltip, "button tooltiptext is set to the right value");
@@ -199,18 +206,49 @@ function checkSyncNowButton(buttonId, sy
   is(remoteTabsButton.hasAttribute("disabled"), syncing, "disabled has the right value");
   if (syncing) {
     is(remoteTabsButton.getAttribute("label"), gSync.syncStrings.GetStringFromName("syncingtabs.label"), "label is set to the right value");
   } else {
     is(remoteTabsButton.getAttribute("label"), gSync.syncStrings.GetStringFromName("syncnow.label"), "label is set to the right value");
   }
 }
 
+async function checkFxaToolbarButtonPanel(expectedShownItemId) {
+  let panel = document.getElementById("PanelUI-fxa");
+  let promisePanelOpen = BrowserTestUtils.waitForEvent(panel, "ViewShown");
+  document.getElementById("PanelUI-fxa-menu").click();
+  await promisePanelOpen;
+  checkItemsDisplayed(["PanelUI-fxa-signin", "PanelUI-fxa-unverified", "PanelUI-fxa-menu"], expectedShownItemId);
+}
+
+// fxaStatus is one of 'not_configured', 'unverified', or 'signedin'.
+function checkFxaToolbarButtonAvatar(fxaStatus) {
+  const avatar = document.getElementById("fxa-avatar-image");
+  const avatarURL = getComputedStyle(avatar).listStyleImage;
+  const expected = {
+    not_configured: "url(\"chrome://browser/skin/fxa/avatar-empty-badged.svg\")",
+    unverified: "url(\"chrome://browser/skin/fxa/avatar-confirm.svg\")",
+    signedin: "url(\"chrome://browser/skin/fxa/avatar.svg\")",
+  };
+  ok(avatarURL == expected[fxaStatus], `expected avatar URL to be ${expected[fxaStatus]}, but got ${avatarURL}`);
+}
+
+// Only one item displayed at a time.
+function checkItemsDisplayed(itemsIds, expectedShownItemId) {
+  for (let id of itemsIds) {
+    if (id == expectedShownItemId) {
+      ok(BrowserTestUtils.is_visible(document.getElementById(id)), `view ${id} should be visible`);
+    } else {
+      ok(BrowserTestUtils.is_hidden(document.getElementById(id)), `view ${id} should be hidden`);
+    }
+  }
+}
+
 // Only one item visible at a time.
-function checkItemsVisiblities(itemsIds, expectedShownItemId) {
+function checkItemsVisibilities(itemsIds, expectedShownItemId) {
   for (let id of itemsIds) {
     if (id == expectedShownItemId) {
       ok(!document.getElementById(id).hidden, "menuitem " + id + " should be visible");
     } else {
       ok(document.getElementById(id).hidden, "menuitem " + id + " should be hidden");
     }
   }
 }
--- a/browser/components/customizableui/CustomizableUI.jsm
+++ b/browser/components/customizableui/CustomizableUI.jsm
@@ -1,12 +1,11 @@
 /* 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/. */
-
 "use strict";
 
 var EXPORTED_SYMBOLS = ["CustomizableUI"];
 
 const {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm");
 const {XPCOMUtils} = ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
 const {AppConstants} = ChromeUtils.import("resource://gre/modules/AppConstants.jsm");
 
@@ -50,17 +49,17 @@ const kSubviewEvents = [
   "ViewShowing",
   "ViewHiding",
 ];
 
 /**
  * The current version. We can use this to auto-add new default widgets as necessary.
  * (would be const but isn't because of testing purposes)
  */
-var kVersion = 15;
+var kVersion = 16;
 
 /**
  * Buttons removed from built-ins by version they were removed. kVersion must be
  * bumped any time a new id is added to this. Use the button id as key, and
  * version the button is removed in as the value.  e.g. "pocket-button": 5
  */
 var ObsoleteBuiltinButtons = {
   "feed-button": 15,
@@ -197,16 +196,17 @@ var CustomizableUIInternal = {
       "stop-reload-button",
       "home-button",
       "spring",
       "urlbar-container",
       "spring",
       "downloads-button",
       "library-button",
       "sidebar-button",
+      "fxa-toolbar-menu-button",
     ];
 
     if (AppConstants.MOZ_DEV_EDITION) {
       navbarPlacements.splice(2, 0, "developer-button");
     }
 
     this.registerArea(CustomizableUI.AREA_NAVBAR, {
       type: CustomizableUI.TYPE_TOOLBAR,
@@ -265,16 +265,17 @@ var CustomizableUIInternal = {
   },
 
   _defineBuiltInWidgets() {
     for (let widgetDefinition of CustomizableWidgets) {
       this.createBuiltinWidget(widgetDefinition);
     }
   },
 
+  // eslint-disable-next-line complexity
   _updateForNewVersion() {
     // We should still enter even if gSavedState.currentVersion >= kVersion
     // because the per-widget pref facility is independent of versioning.
     if (!gSavedState) {
       // Flip all the prefs so we don't try to re-introduce later:
       for (let [, widget] of gPalette) {
         if (widget.defaultArea && widget._introducedInVersion === "pref") {
           let prefId = "browser.toolbarbuttons.introduced." + widget.id;
@@ -471,16 +472,25 @@ var CustomizableUIInternal = {
     // Remove unsupported custom toolbar saved placements
     if (currentVersion < 14 && gSavedState.placements) {
       for (let area in gSavedState.placements) {
         if (!this._builtinAreas.has(area)) {
           delete gSavedState.placements[area];
         }
       }
     }
+
+    // Add the FxA toolbar menu as the right most button item
+    if (currentVersion < 16 && gSavedState.placements) {
+      let navbarPlacements = gSavedState.placements[CustomizableUI.AREA_NAVBAR];
+      // Place the menu item as the first item to the left of the hamburger menu
+      if (navbarPlacements) {
+        navbarPlacements.push("fxa-toolbar-menu-button");
+      }
+    }
   },
 
   /**
    * _markObsoleteBuiltinButtonsSeen
    * when upgrading, ensure obsoleted buttons are in seen state.
    */
   _markObsoleteBuiltinButtonsSeen() {
     if (!gSavedState)
--- a/browser/components/customizableui/content/panelUI.inc.xul
+++ b/browser/components/customizableui/content/panelUI.inc.xul
@@ -712,16 +712,77 @@
                      smoothscroll="false"
                      flatList="true"
                      tooltip="bhTooltip">
           <!-- Recent Highlights will go here -->
         </toolbaritem>
       </vbox>
     </panelview>
 
+    <panelview id="PanelUI-fxa" class="PanelUI-subView" descriptionheightworkaround="true">
+      <vbox class="panel-subview-body">
+        <vbox id="PanelUI-fxa-signin">
+          <image class="fxaChooseWhatToSyncDevices"/>
+          <label class="PanelUI-fxa-signin-instruction-callout">&fxa.signin.callout.label;</label>
+          <label class="PanelUI-fxa-signin-instruction-label">&fxa.signin.description.label;</label>
+          <toolbarbutton class="PanelUI-fxa-signin-button" label="&fxa.signin.button.label;" oncommand="gSync.openFxAEmailFirstPage('fxa_discoverability_native');"/>
+        </vbox>
+        <vbox id="PanelUI-fxa-unverified" class="PanelUI-fxa-unverified-instruction-box">
+          <image class="fxaGraphicMail"/>
+          <label class="PanelUI-fxa-signin-instruction-callout">&fxa.unverified.callout.label;</label>
+          <label class="PanelUI-fxa-signin-instruction-label">&fxa.unverified.description.label;</label>
+          <toolbarbutton class="PanelUI-fxa-signin-button" label="&fxa.unverified.button.label;" oncommand="gSync.openPrefs('fxa_discoverability_native');"/>
+        </vbox>
+        <vbox id="PanelUI-fxa-menu">
+          <hbox flex="1" pack="center" class="fxa-avatar-subpanel">
+            <toolbarbutton align="left" oncommand="gSync.openFxAChangeAvatar('fxa_discoverability_native');">
+              <image id="fxa-menu-avatar"/>
+            </toolbarbutton>
+            <vbox flex="1">
+              <label class="fxa-avatar-subpanel-description" >&fxa.menu.signedInAs.label;</label>
+              <label id="fxa-menu-email"></label>
+            </vbox>
+          </hbox>
+          <toolbarseparator/>
+          <toolbarbutton id="PanelUI-fxa-menu-sendtab-button"
+                         class="subviewbutton subviewbutton-iconic subviewbutton-nav"
+                         closemenu="none"
+                         oncommand="gSync.showSendToDeviceView(this);"/>
+          <toolbarbutton id="PanelUI-fxa-menu-remotetabs-button"
+                         class="subviewbutton subviewbutton-iconic subviewbutton-nav"
+                         label="&appMenuRemoteTabs.label;"
+                         closemenu="none"
+                         oncommand="PanelUI.showSubView('PanelUI-remotetabs', this)"/>
+          <toolbarbutton id="PanelUI-fxa-menu-view-sidebar"
+                         class="subviewbutton subviewbutton-iconic"
+                         label="&appMenuRemoteTabs.sidebar.label;"
+                         oncommand="SidebarUI.toggle('viewTabsSidebar');"/>
+          <toolbarseparator/>
+          <toolbarbutton class="subviewbutton" label="&fxa.menu.connectAnotherDevice.label;" oncommand="gSync.openConnectAnotherDevice('fxa_discoverability_native');"/>
+          <toolbarbutton class="subviewbutton" label="&fxa.menu.manageAccount.label;" oncommand="gSync.openFxAManagePage('fxa_discoverability_native');"/>
+          <toolbarbutton class="subviewbutton" label="&fxa.menu.syncSettings.label;" oncommand="gSync.openPrefs('fxa_discoverability_native');"/>
+          <toolbarseparator/>
+          <toolbarbutton id="PanelUI-fxa-menu-syncnow-button"
+                         label="&syncSyncNowItem.label;"
+                         syncinglabel="&syncSyncNowItemSyncing.label;"
+                         class="subviewbutton subviewbutton-iconic"
+                         oncommand="gSync.doSync();"
+                         closemenu="none"/>
+        </vbox>
+      </vbox>
+    </panelview>
+    <!-- This panelview is used to contain the dynamically created buttons for send tab to devices -->
+    <panelview id="PanelUI-sendTabToDevice" flex="1" class="PanelUI-subView">
+      <vbox class="panel-subview-body">
+        <toolbarbutton id="PanelUI-sendTabToDevice-syncingDevices" class="subviewbutton subviewbutton-iconic pageAction-sendToDevice-notReady"
+                       label="&sendToDevice.syncNotReady.label;"
+                       disabled="true"/>
+      </vbox>
+    </panelview>
+
     <panelview id="PanelUI-bookmarkingTools" class="PanelUI-subView">
       <vbox class="panel-subview-body">
         <toolbarbutton id="panelMenu_toggleBookmarksMenu"
                        class="subviewbutton subviewbutton-iconic"
                        label-show="&addBookmarksMenu.label;"
                        label-hide="&removeBookmarksMenu.label;"
                        oncommand="BookmarkingUI.toggleMenuButtonInToolbar(this);"/>
         <toolbarbutton id="panelMenu_viewBookmarksSidebar"
--- a/browser/components/customizableui/test/browser_1042100_default_placements_update.js
+++ b/browser/components/customizableui/test/browser_1042100_default_placements_update.js
@@ -116,64 +116,65 @@ function test() {
   is(navbarPlacements[0], "back-button", "Back button is in the right place.");
   is(navbarPlacements[1], "forward-button", "Fwd button is in the right place.");
   is(navbarPlacements[2], "stop-reload-button", "Stop/reload button is in the right place.");
   is(navbarPlacements[3], "home-button", "Home button is in the right place.");
   is(navbarPlacements[4], "urlbar-container", "URL bar is in the right place.");
   is(navbarPlacements[5], "downloads-button", "Downloads button is in the right place.");
   is(navbarPlacements[6], "library-button", "Library button is in the right place.");
   is(navbarPlacements[7], "sidebar-button", "Sidebar button is in the right place.");
-  is(navbarPlacements.length, 8, "Should have 8 items");
+  is(navbarPlacements[8], "fxa-toolbar-menu-button", "FxA button is in the right place.");
+  is(navbarPlacements.length, 9, "Should have 9 items");
 
   let overflowPlacements = CustomizableUIBSPass.gSavedState.placements["widget-overflow-fixed-list"];
   Assert.deepEqual(overflowPlacements, ["panic-button"]);
 
-  // Finally test that the downloads migration works:
+  // Finally, test the downloads and fxa avatar button migrations work.
   let oldNavbarPlacements = [
     "urlbar-container", "customizableui-special-spring3", "search-container",
   ];
   CustomizableUIBSPass.gSavedState = {
     currentVersion: 10,
     placements: {
       "nav-bar": Array.from(oldNavbarPlacements),
       "widget-overflow-fixed-list": ["downloads-button"],
     },
   };
   CustomizableUIInternal._updateForNewVersion();
   navbarPlacements = CustomizableUIBSPass.gSavedState.placements["nav-bar"];
-  Assert.deepEqual(navbarPlacements, oldNavbarPlacements.concat(["downloads-button"]),
+  Assert.deepEqual(navbarPlacements, oldNavbarPlacements.concat(["downloads-button", "fxa-toolbar-menu-button"]),
                    "Downloads button inserted in navbar");
   Assert.deepEqual(CustomizableUIBSPass.gSavedState.placements["widget-overflow-fixed-list"], [],
                    "Overflow panel is empty");
 
   CustomizableUIBSPass.gSavedState = {
     currentVersion: 10,
     placements: {
       "nav-bar": ["downloads-button"].concat(oldNavbarPlacements),
     },
   };
   CustomizableUIInternal._updateForNewVersion();
   navbarPlacements = CustomizableUIBSPass.gSavedState.placements["nav-bar"];
-  Assert.deepEqual(navbarPlacements, oldNavbarPlacements.concat(["downloads-button"]),
+  Assert.deepEqual(navbarPlacements, oldNavbarPlacements.concat(["downloads-button", "fxa-toolbar-menu-button"]),
                    "Downloads button reinserted in navbar");
 
   oldNavbarPlacements = [
     "urlbar-container", "customizableui-special-spring3", "search-container", "other-widget",
   ];
   CustomizableUIBSPass.gSavedState = {
     currentVersion: 10,
     placements: {
       "nav-bar": Array.from(oldNavbarPlacements),
     },
   };
   CustomizableUIInternal._updateForNewVersion();
   navbarPlacements = CustomizableUIBSPass.gSavedState.placements["nav-bar"];
   let expectedNavbarPlacements = [
     "urlbar-container", "customizableui-special-spring3", "search-container",
-    "downloads-button", "other-widget",
+    "downloads-button", "other-widget", "fxa-toolbar-menu-button",
   ];
   Assert.deepEqual(navbarPlacements, expectedNavbarPlacements,
                    "Downloads button inserted in navbar before other widgets");
 
   gFuturePlacements.delete(CustomizableUI.AREA_NAVBAR);
   gPalette.delete(testWidgetNew.id);
   gPalette.delete(testWidgetOld.id);
 }
--- a/browser/themes/shared/customizableui/panelUI.inc.css
+++ b/browser/themes/shared/customizableui/panelUI.inc.css
@@ -535,16 +535,17 @@ toolbarbutton[constrain-size="true"][cui
 }
 
 @keyframes syncRotate {
   from { transform: rotate(0); }
   to { transform: rotate(360deg); }
 }
 
 #appMenu-fxa-icon[syncstatus="active"] > .toolbarbutton-icon,
+#PanelUI-fxa-menu-syncnow-button[syncstatus="active"] > .toolbarbutton-icon,
 #PanelUI-remotetabs-syncnow[syncstatus="active"] > .toolbarbutton-icon {
   animation: syncRotate 0.8s linear infinite;
   fill: var(--toolbarbutton-icon-fill-attention);
 }
 
 #appMenu-fxa-status {
   -moz-box-align: center;
 }
@@ -666,40 +667,151 @@ toolbarbutton[constrain-size="true"][cui
   background-color: @appmenuWarningBackgroundColorHoverBrightText@;
 }
 
 :root[lwt-popup-brighttext] #appMenu-fxa-container[fxastatus="login-failed"] > #appMenu-fxa-status:hover:active,
 :root[lwt-popup-brighttext] #appMenu-fxa-container[fxastatus="unverified"] > #appMenu-fxa-status:hover:active {
   background-color: @appmenuWarningBackgroundColorActiveBrightText@;
 }
 
+/* Firefox Account Toolbar Panel */
+
+#fxa-menu-avatar,
+#fxa-avatar-image {
+  width: 16px;
+  height: 16px;
+}
+
+:root {
+  --avatar-image-url: url(chrome://browser/skin/fxa/avatar.svg);
+}
+
+:root[fxastatus="unverified"] #fxa-avatar-image {
+  list-style-image: url(chrome://browser/skin/fxa/avatar-confirm.svg);
+}
+
+:root[fxastatus="not_configured"] #fxa-avatar-image {
+  list-style-image: url(chrome://browser/skin/fxa/avatar-empty.svg);
+}
+
+:root[fxastatus="not_configured"][fxa_avatar_badged="badged"] #fxa-avatar-image {
+  list-style-image: url(chrome://browser/skin/fxa/avatar-empty-badged.svg);
+}
+
+:root:not([fxastatus]) #fxa-toolbar-menu-button {
+  display: none;
+}
+
+:root[fxastatus="signedin"] #fxa-menu-avatar,
+:root[fxastatus="signedin"] #fxa-avatar-image {
+  border-radius: 50%;
+  list-style-image: var(--avatar-image-url);
+}
+
+:root[fxastatus="signedin"] #PanelUI-fxa-signin,
+:root[fxastatus="signedin"] #PanelUI-fxa-unverified,
+:root[fxastatus="unverified"] #PanelUI-fxa-signin,
+:root[fxastatus="unverified"] #PanelUI-fxa-menu,
+:root[fxastatus="not_configured"] #PanelUI-fxa-unverified,
+:root[fxastatus="not_configured"] #PanelUI-fxa-menu {
+  display: none;
+}
+
+:root[fxastatus="not_configured"] #PanelUI-fxa > .panel-subview-body,
+:root[fxastatus="unverified"] #PanelUI-fxa > .panel-subview-body {
+  -moz-box-align: center;
+}
+
+#PanelUI-fxa-signin,
+#PanelUI-fxa-unverified {
+  margin-top: 20px;
+  -moz-box-align: center;
+}
+
+.PanelUI-fxa-signin-instruction-callout {
+  font-size: 1.3em;
+  font-weight: normal;
+  padding: .6em 0 .6em;
+}
+
+.PanelUI-fxa-signin-instruction-label {
+  font-weight: lighter;
+}
+
+.fxa-avatar-subpanel {
+  padding-top: 8px;
+  padding-bottom: 4px;
+}
+
+.fxa-avatar-subpanel-description {
+  color: var(--panel-disabled-color);
+}
+
+#fxa-menu-avatar {
+  height: 32px;
+  width: 32px;
+  pointer-events: none;
+  margin-inline-start: 20px;
+  -moz-context-properties: fill, fill-opacity;
+  fill: var(--arrowpanel-color);
+}
+
+/* From the FxA menu -> remote tabs, we don't need to clutter the view with
+   redundant buttons because these are accessible from the main menu */
+panelmultiview[mainViewId="PanelUI-fxa"] #PanelUI-remotetabs-view-sidebar,
+panelmultiview[mainViewId="PanelUI-fxa"] #PanelUI-remotetabs-syncnow {
+  display: none;
+}
+
+#PanelUI-sendTabToDevice > .panel-subview-body:not([state]) > #PanelUI-sendTabToDevice-syncingDevices {
+  display: none;
+}
+
+.fxaChooseWhatToSyncDevices {
+  height: 102px;
+  width: 201px;
+  list-style-image: url(chrome://browser/skin/fxa/choose-what-to-sync-devices.svg);
+}
+
+.fxaGraphicMail {
+  height: 71px;
+  width: 143px;
+  list-style-image: url(chrome://browser/skin/fxa/graphic-mail.svg);
+}
+
 #PanelUI-remotetabs {
   --panel-ui-sync-illustration-height: 91px;
 }
 
+.PanelUI-fxa-signin-instruction-label,
+.PanelUI-fxa-signin-instruction-callout,
 .PanelUI-remotetabs-instruction-label {
   /* If you change the margin here, the min-height of the synced tabs panel
     (e.g. #PanelUI-remotetabs[mainview] #PanelUI-remotetabs-setupsync, etc) may
     need adjusting (see bug 1248506) */
   margin: 0;
   text-align: center;
   text-shadow: none;
   max-width: 15em;
+}
+
+.PanelUI-remotetabs-instruction-label {
   color: var(--panel-disabled-color);
 }
 
 /* The boxes with "instructions" get extra top and bottom padding for space
    around the illustration and buttons */
 .PanelUI-remotetabs-instruction-box {
   /* If you change the padding here, the min-height of the synced tabs panel
     (e.g. #PanelUI-remotetabs[mainview] #PanelUI-remotetabs-setupsync, etc) may
     need adjusting (see bug 1248506) */
   padding-bottom: 30px;
 }
 
+.PanelUI-fxa-signin-button,
 .PanelUI-remotetabs-button {
   -moz-appearance: none;
   background-color: #0060df;
   /* !important for the color as an OSX specific rule when a lightweight theme
      is used for buttons in the toolbox overrides. See bug 1238531 for details */
   color: white !important;
   border-radius: 2px;
   /* If you change the margin or padding below, the min-height of the synced tabs
@@ -707,20 +819,26 @@ toolbarbutton[constrain-size="true"][cui
      etc) may need adjusting (see bug 1248506) */
   margin-top: 15px;
   margin-bottom: 15px;
   padding: 8px;
   text-shadow: none;
   min-width: 200px;
 }
 
+.PanelUI-fxa-signin-button {
+  padding: 1em;
+}
+
+.PanelUI-fxa-signin-button:hover,
 .PanelUI-remotetabs-button:hover {
   background-color: #003eaa;
 }
 
+.PanelUI-fxa-signin-button:hover:active,
 .PanelUI-remotetabs-button:hover:active {
   background-color: #002275;
 }
 
 .remotetabs-promo-link {
   margin: 0;
 }
 
@@ -749,16 +867,17 @@ toolbarbutton[constrain-size="true"][cui
 .fxaSyncIllustration {
   list-style-image: url(chrome://browser/skin/fxa/sync-illustration.svg);
 }
 
 .fxaSyncIllustrationIssue {
   list-style-image: url(chrome://browser/skin/fxa/sync-illustration-issue.svg);
 }
 
+.PanelUI-fxa-signin-button > .toolbarbutton-text,
 .PanelUI-remotetabs-button > .toolbarbutton-text {
   /* !important to override ".cui-widget-panel toolbarbutton > .toolbarbutton-text" above. */
   text-align: center !important;
   text-shadow: none;
 }
 
 #PanelUI-remotetabs[mainview] { /* panel anchored to toolbar button might be too skinny */
   min-width: 19em;
@@ -1203,16 +1322,20 @@ menuitem.panel-subview-footer@menuStateA
 #PanelUI-remotetabs-tabslist > toolbarbutton[itemtype="tab"] > .toolbarbutton-icon,
 #PanelUI-recentlyClosedWindows > toolbarbutton > .toolbarbutton-icon,
 #PanelUI-recentlyClosedTabs > toolbarbutton > .toolbarbutton-icon,
 #PanelUI-historyItems > toolbarbutton > .toolbarbutton-icon {
   width: 16px;
   height: 16px;
 }
 
+#appMenu-fxa-avatar {
+  border-radius: 50%;
+}
+
 toolbarpaletteitem[place="palette"] > .toolbarbutton-1 > .toolbarbutton-menu-dropmarker,
 #bookmarks-menu-button[cui-areatype="menu-panel"] > .toolbarbutton-menu-dropmarker,
 #bookmarks-menu-button[overflowedItem] > .toolbarbutton-menu-dropmarker {
   display: none;
 }
 
 #search-container[cui-areatype="menu-panel"] {
   padding-top: 6px;
new file mode 100644
--- /dev/null
+++ b/browser/themes/shared/fxa/avatar-confirm.svg
@@ -0,0 +1,8 @@
+<!-- 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/. -->
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="context-fill" fill-opacity="context-fill-opacity">
+  <path d="M14.92 9A6.63 6.63 0 0 0 15 8a7 7 0 1 0-7 7v-2a4.94 4.94 0 0 1-4.11-2.19A1 1 0 0 1 4 9.58C5 8.36 6.48 10 8 10h.09A1.51 1.51 0 0 1 9.5 9zM8 9a3 3 0 1 1 3-3 3 3 0 0 1-3 3z"/>
+  <path d="M15.5 10h-6a.5.5 0 0 0-.5.5v.32l3.5 2.1 3.5-2.1v-.32a.5.5 0 0 0-.5-.5z"/>
+  <path d="M12.5 14a.48.48 0 0 1-.26-.07L9 12v3.5a.5.5 0 0 0 .5.5h6a.5.5 0 0 0 .5-.5V12l-3.24 1.95a.48.48 0 0 1-.26.05z"/>
+</svg>
new file mode 100644
--- /dev/null
+++ b/browser/themes/shared/fxa/avatar-empty-badged.svg
@@ -0,0 +1,8 @@
+<!-- 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/. -->
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
+  <path fill="context-fill" fill-opacity="context-fill-opacity" d="M8 9a3 3 0 1 0-3-3 3 3 0 0 0 3 3zm4.05.58C11 8.36 9.52 10 8 10s-3-1.64-4-.42a1 1 0 0 0-.06 1.23 5 5 0 0 0 8.22 0 1 1 0 0 0-.11-1.23z"/>
+  <circle fill="#0A84FF" cx="13.5" cy="2.5" r="2.5"/>
+  <path fill="context-fill" fill-opacity="context-fill-opacity" d="M14.13 5.93a.4.4 0 0 0-.35.48A5.89 5.89 0 0 1 14 8.1 6 6 0 0 1 8 14h-.1A6 6 0 0 1 8 2h.1a6 6 0 0 1 1.42.2.48.48 0 0 0 .56-.38.48.48 0 0 0-.3-.59A7 7 0 1 0 7.89 15H8a7 7 0 0 0 7-6.89 7.09 7.09 0 0 0-.24-1.91c-.04-.14-.22-.36-.63-.27z"/>
+</svg>
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/browser/themes/shared/fxa/avatar-empty.svg
@@ -0,0 +1,9 @@
+<!-- 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/. -->
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="context-fill" fill-opacity="context-fill-opacity">
+  <path d="M12.05 9.58C11 8.36 9.52 10 8 10s-3-1.64-4-.42a1 1 0 0 0-.06 1.23 5 5 0 0 0 8.22 0 1 1 0 0 0-.11-1.23z"/>
+  <circle cx="8" cy="6" r="3"/>
+  <path d="M8 1a7 7 0 1 0 7 7 7 7 0 0 0-7-7zm0 13a6 6 0 1 1 6-6 6 6 0 0 1-6 6z"/>
+</svg>
+
new file mode 100644
--- /dev/null
+++ b/browser/themes/shared/fxa/avatar.svg
@@ -0,0 +1,6 @@
+<!-- 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/. -->
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="context-fill" fill-opacity="context-fill-opacity">
+  <path d="M8 1a7 7 0 1 0 7 7 7 7 0 0 0-7-7zm0 2a3 3 0 1 1-3 3 3 3 0 0 1 3-3zm4.11 7.81a5 5 0 0 1-8.22 0A1 1 0 0 1 4 9.58C5 8.36 6.48 10 8 10s3-1.64 4.05-.42a1 1 0 0 1 .06 1.23z"/>
+</svg>
new file mode 100644
--- /dev/null
+++ b/browser/themes/shared/fxa/choose-what-to-sync-devices.svg
@@ -0,0 +1,39 @@
+<!-- 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/. -->
+<svg xmlns="http://www.w3.org/2000/svg" width="201" height="102">
+  <g>
+    <path fill="#eaeaee" d="M142.1 93.3c-3 0-5.9-.3-8.8-.9-.3-.1-.4-.3-.4-.6.1-.3.3-.4.6-.4 2.8.6 5.6.9 8.5.9.7 0 1.5 0 2.2-.1.3 0 .5.2.5.4 0 .3-.2.5-.5.5-.7.2-1.4.2-2.1.2zm8.6-.9c-.3 0-.5-.2-.5-.5 0-.2.2-.4.4-.5.7-.1 1.4-.3 2.1-.5.3-.1.5.1.6.4.1.3-.1.5-.4.6-.7.2-1.4.4-2.1.5h-.1zm6.2-1.8c-.3 0-.5-.2-.5-.5 0-.2.1-.4.3-.5 2.7-1 5.2-2.3 7.6-3.9.2-.2.5-.1.7.1s.1.5-.1.7c-2.5 1.6-5.1 2.9-7.8 4-.1.1-.1.1-.2.1zm-31.8-.8h-.2c-2.7-1.2-5.2-2.7-7.6-4.4-.2-.2-.3-.5-.1-.7.2-.2.5-.3.7-.1 2.3 1.7 4.8 3.1 7.4 4.3.3.1.4.4.3.7-.1 0-.3.2-.5.2zm-10.9-7.1c-.1 0-.2 0-.3-.1-.5-.5-1.1-1-1.6-1.5-.2-.2-.2-.5 0-.7.2-.2.5-.2.7 0 .5.5 1 1 1.6 1.5.2.2.2.5 0 .7-.1 0-.2.1-.4.1zm57.2-1.6c-.3 0-.5-.2-.5-.5 0-.1.1-.3.1-.4 2.5-2.5 4.7-5.4 6.5-8.5.1-.2.4-.3.7-.2.2.1.3.4.2.7-1.8 3.2-4.1 6.1-6.7 8.7 0 .2-.2.2-.3.2zm-63-4.9c-.2 0-.3-.1-.4-.2-2.2-3-3.9-6.2-5.3-9.6-.1-.3 0-.5.3-.6s.5 0 .6.3c1.3 3.3 3 6.5 5.1 9.4.2.2.1.5-.1.7h-.2zm72.4-9.5c-.3 0-.5-.2-.5-.5V66c.3-.7.5-1.3.7-2 .1-.3.4-.4.6-.3.3.1.4.4.3.6-.2.7-.5 1.4-.7 2.1 0 .1-.2.3-.4.3zm1.9-6.3h-.1c-.3-.1-.4-.3-.4-.6.6-2.8.9-5.7.9-8.5v-.2c0-.3.2-.5.5-.5s.5.2.5.5v.2c0 2.9-.3 5.9-.9 8.7-.1.2-.3.4-.5.4zM101 58.3c-.2 0-.5-.2-.5-.4-.3-2.2-.5-4.4-.5-6.7 0-.7 0-1.4.1-2.1 0-.3.2-.5.5-.5s.5.2.5.5c0 .7-.1 1.4-.1 2.1 0 2.2.2 4.4.5 6.5 0 .3-.2.6-.5.6zm82.3-11c-.3 0-.5-.2-.5-.4-.3-2.9-.9-5.7-1.8-8.4-.1-.3.1-.5.3-.6s.5.1.6.3c.9 2.8 1.5 5.7 1.9 8.6 0 .2-.2.4-.5.5.1 0 .1 0 0 0 .1 0 0 0 0 0zm-82.4-2c-.3 0-.5-.3-.5-.6.1-.7.2-1.5.4-2.2.1-.3.3-.4.6-.4.3.1.4.3.4.6-.1.7-.3 1.4-.4 2.1 0 .4-.2.5-.5.5zm2.3-8.4c-.3 0-.5-.2-.5-.5v-.2c1.3-3.4 3.1-6.7 5.2-9.7.2-.2.5-.3.7-.1s.3.5.1.7c-2.1 2.9-3.8 6.1-5.1 9.4 0 .3-.2.4-.4.4zm74.7-6.1c-.2 0-.3-.1-.4-.2-1.8-3.1-4-5.9-6.6-8.5-.2-.2-.2-.5 0-.7.2-.2.5-.2.7 0 2.6 2.6 4.9 5.5 6.7 8.7.1.2.1.5-.2.7h-.2zm-63.8-9.9c-.3 0-.5-.2-.5-.5 0-.1.1-.3.2-.4 2.2-2 4.6-3.7 7.1-5.2.2-.1.5-.1.7.2s.1.5-.2.7c-2.5 1.4-4.8 3.1-6.9 5.1-.1.1-.3.1-.4.1zm52.2-2.8c-.1 0-.2 0-.3-.1l-1.8-1.2c-.2-.1-.3-.5-.1-.7.1-.2.5-.3.7-.1l1.8 1.2c.2.2.3.5.1.7-.1.1-.2.2-.4.2zm-5.6-3.4c-.1 0-.2 0-.2-.1-2.6-1.3-5.2-2.3-8-3.1-.3-.1-.4-.3-.4-.6.1-.3.3-.4.6-.4 2.8.8 5.6 1.8 8.2 3.1.2.1.3.4.2.7 0 .3-.2.4-.4.4zm-35.7-.9c-.3 0-.5-.2-.5-.5 0-.2.1-.4.3-.5.7-.3 1.3-.6 2-.8.3-.1.5 0 .6.3.1.3 0 .5-.3.6-.7.3-1.3.5-2 .8 0 .1 0 .1-.1.1zm8.2-2.7c-.3 0-.5-.2-.5-.5 0-.2.2-.4.4-.5 3.6-.8 7.3-1.1 10.9-.9.3 0 .5.2.5.5s-.2.5-.5.5c-3.6-.2-7.2.1-10.7.9h-.1z"/>
+    <animateTransform attributeName="transform" attributeType="XML" dur="10s" from="0 142 51" repeatCount="indefinite" to="-360 142 51" type="rotate"/>
+  </g>
+  <g>
+    <path fill="#eaeaee" d="M62.8 93.3c-3 0-5.9-.3-8.8-.9-.3-.1-.4-.3-.4-.6.1-.3.3-.4.6-.4 2.8.6 5.6.9 8.5.9.7 0 1.5 0 2.2-.1.3 0 .5.2.5.4 0 .3-.2.5-.5.5-.6.2-1.3.2-2.1.2zm8.6-.9c-.3 0-.5-.2-.5-.5 0-.2.2-.4.4-.5.7-.1 1.4-.3 2.1-.5.3-.1.5.1.6.4.1.3-.1.5-.4.6-.7.2-1.4.4-2.1.5h-.1zm6.3-1.8c-.3 0-.5-.2-.5-.5 0-.2.1-.4.3-.5 2.7-1 5.2-2.3 7.6-3.9.2-.2.5-.1.7.1s.1.5-.1.7c-2.5 1.6-5.1 2.9-7.8 4-.1.1-.2.1-.2.1zm-31.8-.8h-.2c-2.7-1.2-5.2-2.7-7.6-4.4-.2-.2-.3-.5-.1-.7.2-.2.5-.3.7-.1 2.3 1.7 4.8 3.1 7.4 4.3.3.1.4.4.3.7-.1 0-.3.2-.5.2zM35 82.7c-.1 0-.2 0-.3-.1-.5-.5-1.1-1-1.6-1.5-.2-.2-.2-.5 0-.7.2-.2.5-.2.7 0 .5.5 1 1 1.6 1.5.2.2.2.5 0 .7-.1 0-.3.1-.4.1zm57.2-1.6c-.3 0-.5-.2-.5-.5 0-.1.1-.3.1-.4 2.5-2.5 4.7-5.4 6.5-8.5.1-.2.4-.3.7-.2.2.1.3.4.2.7-1.8 3.2-4.1 6.1-6.7 8.7-.1.2-.2.2-.3.2zm-63-4.9c-.2 0-.3-.1-.4-.2-2.2-3-3.9-6.2-5.3-9.6-.1-.3 0-.5.3-.6s.5 0 .6.3c1.3 3.3 3 6.5 5.1 9.4.2.2.1.5-.1.7h-.2zm72.4-9.5c-.3 0-.5-.2-.5-.5V66c.3-.7.5-1.3.7-2 .1-.3.4-.4.6-.3.3.1.4.4.3.6-.2.7-.5 1.4-.7 2.1 0 .1-.2.3-.4.3zm1.8-6.3h-.1c-.3-.1-.4-.3-.4-.6.6-2.8.9-5.7.9-8.5v-.2c0-.3.2-.5.5-.5s.5.2.5.5v.2c0 2.9-.3 5.9-.9 8.7 0 .2-.2.4-.5.4zm-81.7-2.1c-.2 0-.5-.2-.5-.4-.3-2.2-.5-4.4-.5-6.7 0-.7 0-1.4.1-2.1 0-.3.2-.5.5-.5s.5.2.5.5c0 .7-.1 1.4-.1 2.1 0 2.2.2 4.4.5 6.5.1.3-.1.6-.5.6.1 0 0 0 0 0zm82.4-11c-.3 0-.5-.2-.5-.4-.3-2.9-.9-5.7-1.8-8.4-.1-.3.1-.5.3-.6s.5.1.6.3c.9 2.8 1.5 5.7 1.9 8.6 0 .2-.2.4-.5.5zm-82.4-2s-.1 0 0 0c-.3 0-.5-.3-.5-.6.1-.7.2-1.5.4-2.2.1-.3.3-.4.6-.4.3.1.4.3.4.6-.1.7-.3 1.4-.4 2.1 0 .4-.3.5-.5.5zm2.2-8.4c-.3 0-.5-.2-.5-.5v-.2c1.3-3.4 3.1-6.7 5.2-9.7.2-.2.5-.3.7-.1s.3.5.1.7c-2.1 2.9-3.8 6.1-5.1 9.4 0 .3-.2.4-.4.4zm74.8-6.1c-.2 0-.3-.1-.4-.2-1.8-3.1-4-5.9-6.6-8.5-.2-.2-.2-.5 0-.7.2-.2.5-.2.7 0 2.6 2.6 4.9 5.5 6.7 8.7.1.2.1.5-.2.7h-.2zm-63.8-9.9c-.3 0-.5-.2-.5-.5 0-.1.1-.3.2-.4 2.2-2 4.6-3.7 7.1-5.2.2-.1.5-.1.7.2s.1.5-.2.7c-2.5 1.4-4.8 3.1-6.9 5.1-.2.1-.3.1-.4.1zm52.2-2.8c-.1 0-.2 0-.3-.1L85 16.8c-.2-.1-.3-.5-.1-.7.1-.2.5-.3.7-.1l1.8 1.2c.2.2.3.5.1.7-.1.1-.3.2-.4.2zm-5.6-3.4c-.1 0-.2 0-.2-.1-2.6-1.3-5.2-2.3-8-3.1-.3-.1-.4-.3-.4-.6.1-.3.3-.4.6-.4 2.8.8 5.6 1.8 8.2 3.1.2.1.3.4.2.7 0 .3-.2.4-.4.4zm-35.7-.9c-.3 0-.5-.2-.5-.5 0-.2.1-.4.3-.5.7-.3 1.3-.6 2-.8.3-.1.5 0 .6.3.1.3 0 .5-.3.6-.7.3-1.3.5-2 .8 0 .1-.1.1-.1.1zm8.2-2.7c-.3 0-.5-.2-.5-.5 0-.2.2-.4.4-.5 3.6-.8 7.3-1.1 10.9-.9.3 0 .5.2.5.5s-.2.5-.5.5c-3.6-.2-7.2.1-10.7.9H54z"/>
+    <animateTransform attributeName="transform" attributeType="XML" dur="10s" from="0 62 51" repeatCount="indefinite" to="360 62 51" type="rotate"/>
+  </g>
+  <g>
+    <path fill="#eaeaee" d="M62.6 102.5c-.9 0-1.7 0-2.6-.1-.3 0-.5-.2-.5-.5s.3-.5.5-.5c3.8.2 7.6 0 11.3-.7.3 0 .5.1.6.4 0 .3-.1.5-.4.6-2.9.5-5.9.8-8.9.8zm-10.1-1h-.1c-.9-.2-1.9-.4-2.8-.7-.3-.1-.4-.3-.4-.6s.3-.4.6-.4c.9.2 1.8.5 2.8.6.3.1.4.3.4.6-.1.3-.3.5-.5.5zM46 99.7h-.2l-.9-.3c-.3-.1-.4-.4-.3-.6s.4-.4.6-.3l.9.3c.3.1.4.4.3.6 0 .2-.2.3-.4.3zm33.8-.2c-.3 0-.5-.2-.5-.5 0-.2.1-.4.3-.5l.9-.3c.3-.1.5 0 .6.3s0 .5-.3.6l-.9.3c0 .1 0 .1-.1.1zm4.4-1.8c-.3 0-.5-.2-.5-.5 0-.2.1-.4.3-.5.9-.4 1.7-.8 2.5-1.3.2-.1.5 0 .7.2s0 .5-.2.7c-.8.5-1.7.9-2.6 1.3 0 .1-.1.1-.2.1zm-46.9-2c-.1 0-.2 0-.2-.1-3.4-1.9-6.5-4.2-9.3-6.9-.2-.2-.2-.5 0-.7.2-.2.5-.2.7 0 2.8 2.6 5.8 4.8 9.1 6.7.2.1.3.4.2.7-.1.3-.3.4-.5.3zm55.9-3.4c-.3 0-.5-.2-.5-.5 0-.2.1-.3.2-.4 3-2.3 5.8-4.9 8.2-7.8.2-.2.5-.2.7-.1s.2.5.1.7c-2.5 3-5.3 5.6-8.4 8-.1 0-.2.1-.3.1zm-70.3-9c-.2 0-.3-.1-.4-.2-.6-.7-1.2-1.5-1.7-2.3-.2-.2-.1-.5.1-.7.2-.2.5-.1.7.1.5.8 1.1 1.5 1.7 2.3.2.2.1.5-.1.7-.1.1-.2.1-.3.1zm-3.8-5.5c-.2 0-.3-.1-.4-.2-.2-.3-.3-.6-.5-.8-.1-.2-.1-.5.2-.7s.5-.1.7.2c.2.3.3.5.5.8.1.2.1.5-.2.7h-.3zm87.4-.5c-.3 0-.5-.2-.5-.5 0-.1 0-.2.1-.3.2-.3.3-.5.5-.8.1-.2.4-.3.7-.2.2.1.3.4.2.7-.2.3-.3.6-.5.8-.2.2-.3.3-.5.3zm2.2-4.2h-.2c-.3-.1-.4-.4-.2-.7.4-.9.8-1.7 1.1-2.6.1-.3.4-.4.7-.3.3.1.4.4.3.6-.4.9-.7 1.8-1.1 2.7-.2.2-.4.3-.6.3zM15 69.2c-.2 0-.4-.1-.5-.3-1.3-3.6-2.2-7.4-2.7-11.2 0-.3.2-.5.4-.6.3 0 .5.2.6.4.5 3.8 1.4 7.5 2.7 11 .1.3 0 .5-.3.6-.1.1-.2.1-.2.1zm97.1-6.1h-.1c-.3-.1-.4-.3-.4-.6.8-3.7 1.3-7.5 1.3-11.3V51c0-.3.2-.5.5-.5s.5.2.5.5v.2c0 3.9-.4 7.7-1.3 11.5-.1.3-.3.4-.5.4zM11.9 50.4c-.3 0-.5-.2-.5-.5 0-1 .1-1.9.2-2.9 0-.3.3-.5.5-.5.3 0 .5.3.5.5-.1.9-.1 1.9-.2 2.8 0 .4-.2.6-.5.6zm.6-6.6c-.4 0-.5-.3-.5-.6.1-.3.1-.6.2-1 0-.3.3-.5.6-.4s.5.3.4.6c-.1.3-.1.6-.2.9 0 .3-.2.5-.5.5zm100.1-.8c-.2 0-.5-.2-.5-.4-.1-.3-.1-.6-.2-.9-.1-.3.1-.5.4-.6s.5.1.6.4c.1.3.1.6.2 1 .1.2-.1.4-.5.5.1 0 .1 0 0 0 .1 0 .1 0 0 0zm-1-4.7c-.2 0-.4-.2-.5-.4-.2-.9-.5-1.8-.8-2.7-.1-.3.1-.5.3-.6.3-.1.5.1.6.3.3.9.6 1.8.8 2.8.1.3-.1.5-.4.6.1 0 0 0 0 0zm-96.7-3.8h-.2c-.3-.1-.4-.4-.3-.6 1.3-3.6 3-7.1 5.1-10.4.1-.2.5-.3.7-.1.2.1.3.5.1.7-2.1 3.2-3.8 6.6-5 10.2 0 .1-.2.2-.4.2zm92.9-6c-.2 0-.4-.1-.4-.3-1.7-3.4-3.9-6.5-6.3-9.4-.2-.2-.2-.5.1-.7s.5-.2.7.1c2.5 3 4.7 6.2 6.4 9.6.1.2 0 .5-.2.7h-.3zM24.6 18.1c-.3 0-.5-.2-.5-.5 0-.1 0-.2.1-.3.6-.7 1.3-1.4 2-2.1.2-.2.5-.2.7 0 .2.2.2.5 0 .7-.7.7-1.3 1.4-1.9 2.1-.1.1-.2.1-.4.1zm4.8-4.7c-.3 0-.5-.2-.5-.5 0-.1.1-.3.2-.4.2-.2.5-.4.7-.6.2-.2.5-.1.7.1.2.2.1.5-.1.7-.2.2-.5.4-.7.6-.1.1-.2.1-.3.1zm65.9-.5c-.1 0-.2 0-.3-.1-.2-.2-.5-.4-.7-.6-.3-.2-.3-.5-.1-.7s.5-.2.7-.1c.3.2.5.4.7.6.2.2.2.5.1.7-.1.2-.2.2-.4.2zM91.5 10c-.1 0-.2 0-.3-.1-.8-.5-1.6-1.1-2.4-1.6-.2-.1-.3-.5-.2-.7s.5-.3.7-.2c.8.5 1.6 1 2.4 1.6.2.2.3.5.1.7 0 .2-.1.3-.3.3zM37.1 7.9c-.3 0-.5-.2-.5-.5 0-.2.1-.3.2-.4 3.3-1.9 6.9-3.5 10.6-4.7.3-.1.5.1.6.3.1.3-.1.5-.3.6-3.6 1.1-7.1 2.7-10.4 4.6 0 0-.1.1-.2.1zM82.3 5h-.2c-3.5-1.5-7.2-2.5-10.9-3.2-.3 0-.5-.3-.4-.6 0-.3.3-.5.6-.4 3.8.7 7.5 1.7 11.1 3.2.3.1.4.4.3.7-.1.1-.3.3-.5.3zM55.1 1.6c-.3 0-.5-.2-.5-.5 0-.2.2-.5.4-.5.9-.1 1.9-.3 2.9-.3.3 0 .5.2.5.5s-.2.5-.5.5c-.9 0-1.8.1-2.8.3zm6.7-.6c-.3 0-.5-.2-.5-.5s.2-.5.5-.5h.9c.3 0 .5.2.5.5s-.2.5-.5.5h-.9z"/>
+    <animateTransform attributeName="transform" attributeType="XML" dur="10s" from="0 62 51" repeatCount="indefinite" to="-360 62 51" type="rotate"/>
+  </g>
+  <g>
+    <path fill="#eaeaee" d="M142.1 102.5c-.9 0-1.7 0-2.6-.1-.3 0-.5-.2-.5-.5s.3-.5.5-.5c3.8.2 7.6 0 11.3-.7.3 0 .5.1.6.4 0 .3-.1.5-.4.6-2.9.5-5.9.8-8.9.8zm-10.2-1h-.1c-.9-.2-1.9-.4-2.8-.7-.3-.1-.4-.3-.4-.6s.3-.4.6-.4c.9.2 1.8.5 2.8.6.3.1.4.3.4.6s-.2.5-.5.5zm-6.4-1.8h-.2l-.9-.3c-.3-.1-.4-.4-.3-.6s.4-.4.6-.3l.9.3c.3.1.4.4.3.6 0 .2-.2.3-.4.3zm33.8-.2c-.3 0-.5-.2-.5-.5 0-.2.1-.4.3-.5l.9-.3c.3-.1.5 0 .6.3s0 .5-.3.6l-.9.3c0 .1-.1.1-.1.1zm4.4-1.8c-.3 0-.5-.2-.5-.5 0-.2.1-.4.3-.5.9-.4 1.7-.8 2.5-1.3.2-.1.5 0 .7.2s0 .5-.2.7c-.8.5-1.7.9-2.6 1.3-.1.1-.1.1-.2.1zm-46.9-2c-.1 0-.2 0-.2-.1-3.4-1.9-6.5-4.2-9.3-6.9-.2-.2-.2-.5 0-.7.2-.2.5-.2.7 0 2.8 2.6 5.8 4.8 9.1 6.7.2.1.3.4.2.7-.2.2-.3.3-.5.3zm55.9-3.4c-.3 0-.5-.2-.5-.5 0-.2.1-.3.2-.4 3-2.3 5.8-4.9 8.2-7.8.2-.2.5-.2.7-.1s.2.5.1.7c-2.5 3-5.3 5.6-8.4 8-.1 0-.2.1-.3.1zm-70.3-9c-.2 0-.3-.1-.4-.2-.6-.7-1.2-1.5-1.7-2.3-.2-.2-.1-.5.1-.7.2-.2.5-.1.7.1.5.8 1.1 1.5 1.7 2.3.2.2.1.5-.1.7-.1.1-.2.1-.3.1zm-3.9-5.5c-.2 0-.3-.1-.4-.2-.2-.3-.3-.6-.5-.8-.1-.2-.1-.5.2-.7s.5-.1.7.2c.2.3.3.5.5.8.1.2.1.5-.2.7h-.3zm87.4-.5c-.3 0-.5-.2-.5-.5 0-.1 0-.2.1-.3.2-.3.3-.5.5-.8.1-.2.4-.3.7-.2.2.1.3.4.2.7-.2.3-.3.6-.5.8-.1.2-.3.3-.5.3zm2.3-4.2h-.2c-.3-.1-.4-.4-.2-.7.4-.9.8-1.7 1.1-2.6.1-.3.4-.4.7-.3.3.1.4.4.3.6-.4.9-.7 1.8-1.1 2.7-.3.2-.4.3-.6.3zm-93.8-3.9c-.2 0-.4-.1-.5-.3-1.3-3.6-2.2-7.4-2.7-11.2 0-.3.2-.5.4-.6.3 0 .5.2.6.4.5 3.8 1.4 7.5 2.7 11 .1.3 0 .5-.3.6-.1.1-.1.1-.2.1zm97.2-6.1h-.1c-.3-.1-.4-.3-.4-.6.8-3.7 1.3-7.5 1.3-11.3V51c0-.3.2-.5.5-.5s.5.2.5.5v.2c0 3.9-.4 7.7-1.3 11.5-.1.3-.3.4-.5.4zM91.4 50.4c-.3 0-.5-.2-.5-.5 0-1 .1-1.9.2-2.9 0-.3.3-.5.5-.5.3 0 .5.3.5.5-.1.9-.1 1.9-.2 2.8 0 .4-.3.6-.5.6zm.6-6.6c-.1 0-.1 0 0 0-.4 0-.5-.3-.5-.6.1-.3.1-.6.2-1 0-.3.3-.5.6-.4s.5.3.4.6c-.1.3-.1.6-.2.9-.1.3-.3.5-.5.5zm100.1-.8c-.2 0-.5-.2-.5-.4-.1-.3-.1-.6-.2-.9-.1-.3.1-.5.4-.6s.5.1.6.4c.1.3.1.6.2 1 0 .2-.1.4-.5.5.1 0 .1 0 0 0zm-1.1-4.7c-.2 0-.4-.2-.5-.4-.2-.9-.5-1.8-.8-2.7-.1-.3.1-.5.3-.6.3-.1.5.1.6.3.3.9.6 1.8.8 2.8.1.3-.1.5-.4.6.1 0 .1 0 0 0zm-96.6-3.8h-.2c-.3-.1-.4-.4-.3-.6 1.3-3.6 3-7.1 5.1-10.4.1-.2.5-.3.7-.1.2.1.3.5.1.7-2.1 3.2-3.8 6.6-5 10.2 0 .1-.2.2-.4.2zm92.8-6c-.2 0-.4-.1-.4-.3-1.7-3.4-3.9-6.5-6.3-9.4-.2-.2-.2-.5.1-.7s.5-.2.7.1c2.5 3 4.7 6.2 6.4 9.6.1.2 0 .5-.2.7h-.3zm-83.1-10.4c-.3 0-.5-.2-.5-.5 0-.1 0-.2.1-.3.6-.7 1.3-1.4 2-2.1.2-.2.5-.2.7 0 .2.2.2.5 0 .7-.7.7-1.3 1.4-1.9 2.1-.1.1-.3.1-.4.1zm4.7-4.7c-.3 0-.5-.2-.5-.5 0-.1.1-.3.2-.4.2-.2.5-.4.7-.6.2-.2.5-.1.7.1.2.2.1.5-.1.7-.2.2-.5.4-.7.6 0 .1-.2.1-.3.1zm66-.5c-.1 0-.2 0-.3-.1-.2-.2-.5-.4-.7-.6-.2-.2-.2-.5-.1-.7s.5-.2.7-.1c.3.2.5.4.7.6.2.2.2.5.1.7-.1.1-.3.2-.4.2zM171 10c-.1 0-.2 0-.3-.1-.8-.5-1.6-1.1-2.4-1.6-.2-.1-.3-.5-.2-.7s.5-.3.7-.2c.8.5 1.6 1 2.4 1.6.2.2.3.5.1.7 0 .2-.2.3-.3.3zm-54.4-2.1c-.3 0-.5-.2-.5-.5 0-.2.1-.3.2-.4 3.3-1.9 6.9-3.5 10.6-4.7.3-.1.5.1.6.3.1.3-.1.5-.3.6-3.6 1.1-7.1 2.7-10.4 4.6 0 0-.1.1-.2.1zm45.2-3h-.2c-3.5-1.5-7.2-2.5-10.9-3.2-.3 0-.5-.3-.4-.6 0-.3.3-.5.6-.4 3.8.7 7.5 1.7 11.1 3.2.3.1.4.4.3.7-.2.2-.3.3-.5.3zm-27.3-3.3c-.3 0-.5-.2-.5-.5 0-.2.2-.5.4-.5.9-.1 1.9-.3 2.9-.3.3 0 .5.2.5.5s-.2.5-.5.5c-.8 0-1.7.1-2.8.3.1 0 .1 0 0 0zm6.7-.6c-.3 0-.5-.2-.5-.5s.2-.5.5-.5h.9c.3 0 .5.2.5.5s-.2.5-.4.5h-1z"/>
+    <animateTransform attributeName="transform" attributeType="XML" dur="10s" from="0 142 51" repeatCount="indefinite" to="360 142 51" type="rotate"/>
+  </g>
+  <path fill="#eaeaee" d="M142.1 6c12 0 23.5 4.8 32 13.3h2.8c-16.3-17.8-43.3-20.4-62.7-6.1h3.5c7.2-4.7 15.7-7.2 24.4-7.2zm0 90.5c-9.3 0-18.4-2.9-26.1-8.3h-3.3c20.3 16.2 50 12.9 66.2-7.4.1-.1.2-.2.2-.3h-2.6c-8.5 10.1-21.1 16-34.4 16zM62.6 6c8.7 0 17.2 2.5 24.5 7.2h3.5C74.5 1.3 52.7.9 36.1 12.1h3.8C46.8 8.1 54.6 6 62.6 6zm0 90.5c-10.1 0-20-3.4-27.9-9.7h-3.1c17.2 15.1 42.6 15.6 60.5 1.4h-3.3c-7.7 5.4-16.9 8.3-26.2 8.3z"/>
+  <rect width="26" height="45.5" x="170" y="25" fill="#fff" rx="2" ry="2"/>
+  <path fill="#90e8f0" d="M178.7 51.9c0 .3.1.7.4.9l.5.3 2.9-1.5 2.4 1.3c.3.2.7.2 1-.1l.5-.4-.4-3.4 2-2.1c.2-.3.3-.6.2-.9l-.2-.5-3.2-.6-1.3-2.6c-.2-.3-.5-.5-.9-.5h-.6l-1.5 3.1-2.7.5c-.3.1-.6.3-.7.6l-.1.6 2.3 2.5-.6 2.8zm2.9-5.6l1-1.9 1 1.9 2.1.4-1.5 1.6.3 2.2-1.9-1-1.9 1 .3-2.2-1.5-1.6 2.1-.4z"/>
+  <rect width="42" height="58.5" x="5" y="18" fill="#fff" rx="2" ry="2"/>
+  <path fill="#90e8f0" d="M31.7 45.3l-3.6-.6-1.4-2.8c-.2-.4-.6-.6-1-.6H25l-1.7 3.4-3 .5c-.4.1-.7.3-.8.7l-.2.6 2.6 2.8-.5 3.2c0 .4.1.8.4 1l.6.4 3.2-1.7 2.7 1.4c.4.2.8.2 1.1-.1l.5-.4-.5-3.8 2.2-2.3c.3-.3.4-.7.3-1l-.2-.7zm-4.2 3.4l.3 2.3-2-1.1-2 1.1.3-2.4-1.6-1.8 2.3-.4 1-2.1 1 2.1 2.3.4-1.6 1.9z"/>
+  <rect width="63" height="39" x="73" y="21" fill="#fff" rx="2" ry="2"/>
+  <path fill="#ccedf0" d="M195.6 21.3H170c-1.9 0-3.4 1.5-3.4 3.5V75c0 1.9 1.5 3.4 3.4 3.4h25.6c1.9 0 3.4-1.5 3.4-3.4V24.7c.1-1.9-1.5-3.4-3.4-3.4zm-8.7 52.8c0 .9-.7 1.6-1.6 1.6h-5c-.9 0-1.6-.7-1.6-1.5v-.1c0-.9.7-1.6 1.6-1.6h5c.9 0 1.6.7 1.6 1.6zm8.9-5.4c0 .9-.8 1.7-1.7 1.7h-22.3c-.9 0-1.7-.8-1.7-1.7V26.8c0-.9.8-1.7 1.7-1.7H194c.9 0 1.7.8 1.7 1.7l.1 41.9zM5.7 84.8h41c2 0 3.7-1.6 3.7-3.7V17.8c0-2-1.6-3.7-3.7-3.7h-41c-2 0-3.7 1.6-3.7 3.7v63.3c0 2.1 1.6 3.7 3.7 3.7zm14.4-4v-1.2c0-.7.6-1.3 1.3-1.3h9.5c.7 0 1.3.6 1.3 1.3v1.3c0 .7-.6 1.3-1.3 1.3h-9.5c-.7 0-1.3-.6-1.3-1.4zM5.5 20c0-1 .8-1.8 1.8-1.8H45c1 0 1.8.8 1.8 1.8v54.4c0 1-.8 1.8-1.8 1.9H7.3c-1 0-1.8-.8-1.8-1.8V20zm68.7 43h61.4c2 0 3.6-1.6 3.6-3.5V18.8c0-2-1.6-3.6-3.6-3.6H74.2c-2 0-3.6 1.6-3.6 3.5v40.7c0 2.1 1.6 3.7 3.6 3.6zm29.5-45c0-.6.5-1.1 1.2-1.1.6 0 1.2.5 1.2 1.1 0 .6-.5 1.1-1.2 1.1-.6 0-1.1-.5-1.1-1l-.1-.1zM74 23.3c0-1.2.9-2.1 2.1-2.1h57.7c1.2 0 2.1.9 2.1 2.1v34.4c0 1.2-.9 2.1-2.1 2.1H76.1c-1.2 0-2.1-.9-2.1-2.1V23.3zm70.4 62.9c1.2.1 2.2-.8 2.2-2v-1c0-.3-.1-.5-.2-.7l-7.3-16.1c-.4-.8-1.2-1.3-2.1-1.2H72.3c-.9 0-1.7.5-2.1 1.3l-6.9 16.1c-.1.2-.1.5-.1.7v1.1c.1 1.2 1 2.1 2.2 2l79-.2zm-8.5-15c.1.4.3.8.3 1.1.1.3-.4.6-.9.6h-3.7c-.3 0-.6-.1-.6-.3-.1-.4-.2-.8-.3-1.1-.1-.3.3-.6.8-.6h3.7c.3-.1.5.1.7.3zm-5.5-4h3.5c.3 0 .5.1.6.3.1.4.2.7.3 1.1.1.3-.3.6-.8.6h-3.4c-.3 0-.5-.1-.6-.3-.1-.4-.3-.7-.4-1.1-.1-.2.3-.5.8-.6zm-3.2 4l.2 1.2c.1.3-.4.6-.9.6h-3.8c-.3 0-.6-.1-.7-.3l-.3-1.2c-.1-.3.4-.7.9-.7h3.9c.4.1.7.2.7.4zm-5.2-4h3.7c.3 0 .6.1.6.3l.2 1.1c.1.3-.4.6-.9.6H122c-.3 0-.6-.1-.6-.3l-.3-1.1c-.1-.2.3-.5.9-.6zm-4.4 4c.1.5.1.7.2 1.2 0 .3-.4.6-.9.6H113c-.4 0-.7-.2-.7-.4l-.3-1.2c-.1-.3.4-.6.9-.6h4.1c.3.1.6.3.6.4zm-5-4h3.9c.4 0 .7.2.7.4l.2 1.1c0 .3-.4.6-.8.6h-3.8c-.4 0-.7-.2-.7-.4l-.2-1.1c-.2-.2.2-.5.7-.6zm-10 .5c.1-.2.4-.4.8-.4h3.5c.4 0 .7.2.7.4l.2 1.1c0 .3-.3.5-.8.5h-3.9c-.5 0-.8-.2-.8-.5.1-.4.2-.6.3-1.1zm-.2 3.7c.1-.2.4-.4.8-.4h3.7c.4 0 .8.2.8.4l.2 1.2c.1.3-.3.6-.8.6H103c-.5 0-.9-.3-.8-.6 0-.5.1-.7.2-1.2zm-8.6-4.1h3.9c.5 0 .9.3.8.6s-.2.7-.3 1.1c-.1.3-.4.4-.7.4h-3.8c-.5 0-.9-.3-.8-.6s.2-.7.3-1.1c0-.2.3-.3.6-.4zm-1.4 4.1c0-.2.3-.4.7-.4h4.1c.5 0 .9.3.8.6-.1.5-.2.7-.3 1.2-.1.3-.4.4-.7.4h-3.9c-.5 0-.9-.3-.9-.6.1-.4.1-.7.2-1.2zm-13.2.3c-.2.4-.3.8-.4 1.2-.1.2-.3.3-.6.4h-3.7c-.5 0-1-.3-.9-.7.1-.4.2-.8.4-1.1.1-.2.4-.4.7-.4h3.7c.6 0 .9.3.8.6zm.9-2.6c-.1.2-.4.4-.7.3H76c-.5 0-.8-.3-.7-.6.1-.4.3-.7.4-1.1.1-.2.3-.4.6-.3h3.5c.5 0 .9.3.8.6s-.3.7-.5 1.1zm7.8 3.8c-.1.2-.3.3-.7.3h-3.8c-.5 0-1-.3-.9-.7s.2-.7.3-1.2c.1-.2.3-.4.7-.4h3.9c.5 0 1 .3.9.7s-.3.8-.4 1.3zm.9-3.9c-.1.2-.4.4-.7.3h-3.6c-.5 0-.9-.3-.8-.6s.2-.7.3-1.1c.1-.2.4-.4.6-.3h3.7c.5 0 .9.3.8.6s-.2.8-.3 1.1zM91 80c.3-1.7.5-2.6.8-4.4 0-.2.3-.4.7-.4l25.3-.2c.4 0 .7.2.7.4.2 1.5.4 3 .4 4.5 0 .3-.4.6-.9.6H91.8c-.4 0-.8-.2-.8-.5z"/>
+  <linearGradient id="a" x1="-5.898" x2="221.772" y1="-53.462" y2="174.198" gradientUnits="userSpaceOnUse">
+    <stop offset="0" stop-color="#00c8d7"/>
+    <stop offset="1" stop-color="#0a84ff"/>
+  </linearGradient>
+  <path fill="url(#a)" d="M195.6 19.3h-.2v-.9c0-.5-.4-.9-.9-.9h-4.8c-.5 0-.9.4-.9.9v.9H170c-3 0-5.4 2.4-5.4 5.4V75c0 3 2.4 5.4 5.4 5.4h25.6c3 0 5.5-2.4 5.5-5.4V24.7c0-3-2.5-5.4-5.5-5.4zm3.5 55.7c0 1.9-1.5 3.4-3.4 3.4h-25.6c-1.9 0-3.4-1.5-3.4-3.4V24.7c0-1.9 1.5-3.4 3.4-3.5h25.6c1.9 0 3.4 1.5 3.4 3.5V75zm-12.2-.9c0 .9-.7 1.6-1.6 1.6h-5c-.9 0-1.6-.7-1.6-1.5v-.1c0-.9.7-1.6 1.6-1.6h5c.9 0 1.6.7 1.6 1.6zM40.1 86.8h6.5c3.1 0 5.7-2.5 5.7-5.7V17.8c0-3-2.3-5.4-5.3-5.6H5.7c-3.1 0-5.7 2.5-5.7 5.6v63.3c0 3.1 2.5 5.7 5.7 5.7h34.4zM2 17.8c0-2 1.6-3.7 3.7-3.7h41c2 0 3.7 1.6 3.7 3.7v63.3c0 2-1.6 3.7-3.7 3.7h-41c-2 0-3.7-1.6-3.7-3.7V17.8zm19.4 64.4c-.7 0-1.3-.6-1.3-1.3v-1.3c0-.7.6-1.3 1.3-1.3h9.5c.7 0 1.3.6 1.3 1.3v1.3c0 .7-.6 1.3-1.3 1.3h-9.5zm100.5 6h22.4c2.3.1 4.2-1.7 4.2-4v-1c0-.5-.1-1.1-.3-1.6l-7.3-16.1c-.3-.8-.9-1.4-1.6-1.8 1.2-1.1 1.9-2.6 1.9-4.2V18.8c0-3.1-2.5-5.5-5.6-5.6H74.2c-3.1 0-5.5 2.5-5.6 5.6v40.7c0 1.5.6 3 1.7 4-.9.4-1.6 1.1-2 2l-6.8 16.2c-.2.5-.3 1-.3 1.5v1.1c.1 2.3 1.9 4.1 4.2 4l56.5-.1zm-56.5-2c-1.2.1-2.2-.8-2.2-2v-1c0-.2 0-.5.1-.7l6.9-16.1c.4-.8 1.2-1.3 2.1-1.3H137c.9 0 1.7.5 2.1 1.2l7.3 16.1c.1.2.2.5.2.7v1c-.1 1.2-1 2.1-2.2 2l-79 .1zm5.2-26.7V18.8c0-2 1.6-3.6 3.6-3.6h61.4c2 0 3.6 1.6 3.6 3.5v40.7c0 2-1.6 3.5-3.6 3.5H74.2c-2 .2-3.6-1.4-3.6-3.4zM91 80c.3-1.7.5-2.6.8-4.4 0-.2.3-.4.7-.4l25.3-.2c.4 0 .7.2.7.4.2 1.5.4 3 .4 4.5 0 .3-.4.6-.9.6H91.8c-.4 0-.8-.2-.8-.5zm-2.8-8.3c-.1.5-.2.7-.3 1.2-.1.2-.3.3-.7.3h-3.8c-.5 0-1-.3-.9-.7s.2-.7.3-1.2c.1-.2.3-.4.7-.4h3.9c.5.2 1 .5.8.8zm4 .9c.1-.5.1-.7.2-1.2 0-.2.3-.4.7-.4h4.1c.5 0 .9.3.8.6-.1.5-.2.7-.3 1.2-.1.3-.4.4-.7.4h-4c-.4 0-.9-.3-.8-.6zm24.7-1.7c.4 0 .7.2.7.4.1.5.1.7.2 1.2 0 .3-.4.6-.9.6H113c-.4 0-.7-.2-.7-.4l-.3-1.2c-.1-.3.4-.6.9-.6h4zm-14.8 1.7l.2-1.2c.1-.2.4-.4.8-.4h3.7c.4 0 .8.2.8.4l.2 1.2c.1.3-.3.6-.8.6h-4.1c-.5 0-.9-.3-.8-.6zm20.6-1.7h3.9c.3 0 .6.1.6.3l.2 1.2c.1.3-.4.6-.9.6h-3.8c-.3 0-.6-.1-.7-.3l-.3-1.2c0-.3.4-.6 1-.6zM89.2 68c-.2.4-.2.7-.4 1.1-.1.2-.4.4-.7.3h-3.6c-.5 0-.9-.3-.8-.6s.2-.7.3-1.1c.1-.2.4-.4.6-.3h3.7c.5 0 1 .3.9.6zm4.6-.6h3.9c.5 0 .9.3.8.6s-.2.7-.3 1.1c-.1.3-.4.4-.7.4h-3.8c-.5 0-.9-.3-.8-.6s.2-.7.3-1.1c0-.3.3-.4.6-.4zM112 69l-.2-1.1c-.1-.3.3-.6.8-.6h3.9c.4 0 .7.2.7.4l.2 1.1c0 .3-.4.6-.8.6h-3.8c-.5-.1-.8-.2-.8-.4zm-9.7-.1c.1-.4.2-.7.3-1.1.1-.2.4-.4.8-.4h3.5c.4 0 .7.2.7.4l.2 1.1c0 .3-.3.5-.8.5h-3.9c-.5 0-.8-.3-.8-.5zm19 .1l-.3-1.1c-.1-.3.4-.6.9-.6h3.7c.3 0 .6.1.6.3l.2 1.1c.1.3-.4.6-.9.6H122c-.3 0-.6-.1-.7-.3zm-42.1 2.8c-.2.4-.3.8-.4 1.2-.1.2-.3.3-.6.4h-3.7c-.5 0-1-.3-.9-.7.1-.4.2-.8.4-1.2.1-.2.4-.4.7-.4h3.7c.6 0 .9.3.8.7zm1.4-3.8c-.2.4-.2.7-.4 1.1-.1.2-.4.4-.7.3H76c-.5 0-.8-.3-.7-.6.1-.4.3-.7.4-1.1.1-.2.3-.4.6-.3h3.5c.4 0 .9.3.8.6zm50.9 2.9h3.7c.3 0 .5.1.7.3.1.4.3.8.3 1.1.1.3-.4.6-.9.6h-3.7c-.3 0-.6-.1-.6-.3-.1-.4-.2-.8-.3-1.1-.2-.3.2-.6.8-.6zm-1.6-1.9c-.1-.4-.3-.7-.4-1.1-.1-.3.4-.6.9-.6h3.5c.3 0 .5.1.6.3.1.4.2.7.3 1.1.1.3-.3.6-.8.6h-3.4c-.3 0-.6-.1-.7-.3zm-25-49.7c-.6 0-1.2-.5-1.2-1.2 0-.6.5-1.1 1.2-1.1.6 0 1.2.5 1.2 1.1-.1.7-.6 1.2-1.2 1.2z"/>
+  <path fill="#90e8f0" d="M111.5 37.9l-.2-.6-3.6-.6-1.4-2.8c-.2-.4-.6-.6-1-.6h-.7l-1.7 3.4-3 .5c-.4.1-.7.3-.8.7l-.2.6 2.6 2.8-.4 3.2c0 .4.1.8.4 1l.6.4 3.2-1.7 2.7 1.4c.4.2.8.2 1.1-.1l.5-.4-.5-3.8 2.2-2.3c.2-.3.3-.8.2-1.1zm-4.5 2.7l.3 2.4-2-1.1-2 1.1.3-2.4-1.7-1.8 2.3-.4 1-2.1 1 2.1 2.3.4-1.5 1.8z"/>
+  <linearGradient id="b" x1="3.801" x2="219.4" y1="-49.841" y2="165.759" gradientUnits="userSpaceOnUse">
+    <stop offset="0" stop-color="#00c8d7"/>
+    <stop offset="1" stop-color="#0a84ff"/>
+  </linearGradient>
+  <path fill="url(#b)" d="M176.8 34.5c0 3.2 2.6 5.7 5.7 5.7s5.7-2.6 5.7-5.7-2.6-5.7-5.7-5.7-5.7 2.5-5.7 5.7zm5.8-4c2.2 0 4 1.8 4 4s-1.8 4-4 4-4-1.8-4-4 1.8-4 4-4zm-.9 4.3V32c0-.3.2-.5.5-.5s.5.2.5.5v2.3h2.3c.3 0 .5.2.5.5s-.2.5-.5.5h-2.8c-.2 0-.5-.2-.5-.5zm2.2 24.8c-.5-.1-.8-.5-.8-1 0-.4.4-.7.8-.8h1c-.6-.6-1.5-1-2.4-1-1.5 0-2.8 1-3.2 2.5-.1.4-.4.7-.9.7h-.2c-.5-.1-.8-.6-.6-1 .7-2.7 3.4-4.3 6.1-3.6.8.2 1.5.6 2.1 1.1V56c.1-.5.5-.8 1-.8.4 0 .7.4.8.8v2.8c0 .5-.4.9-.9.9l-2.8-.1zm-1.3 5.5c-1.2 0-2.4-.5-3.3-1.2v.4c-.1.5-.5.8-1 .8-.4 0-.7-.4-.8-.8v-2.7c0-.5.4-.9.9-.9h2.8c.5.1.8.5.8 1 0 .4-.4.7-.8.8h-1c.6.6 1.5 1 2.4 1 1.5 0 2.8-1 3.2-2.5.2-.5.7-.7 1.1-.5.4.1.6.5.6 1-.6 2.1-2.6 3.7-4.9 3.6zm-156.8-28c3.5 0 6.3-2.8 6.3-6.3s-2.8-6.3-6.3-6.3-6.3 2.8-6.3 6.3c-.1 3.5 2.7 6.4 6.3 6.3-.1.1-.1.1 0 0zm0-10.6c2.4 0 4.3 1.9 4.3 4.3s-1.9 4.3-4.3 4.3-4.3-1.9-4.3-4.3 1.9-4.2 4.3-4.3zm-.9 4.7v-3c0-.3.2-.5.5-.5s.5.2.5.5v2.5h2.5c.3 0 .5.2.5.5s-.2.5-.5.5h-3c-.3 0-.5-.2-.5-.4v-.1zm6.4 28.3v3c0 .6-.4 1-1 1h-3c-.6 0-1-.4-1-1s.4-1 1-1h1c-.7-.7-1.6-1-2.5-1-1.6 0-3.1 1.1-3.5 2.7-.1.5-.5.8-1 .8h-.2c-.5-.1-.9-.7-.7-1.2.7-3 3.7-4.8 6.6-4.1.9.2 1.7.6 2.4 1.2v-.3c0-.6.4-1 1-1s1 .4.9.9zm-.2 5.9c-.7 3-3.7 4.8-6.6 4.1-.9-.2-1.7-.6-2.4-1.2v.3c0 .6-.4 1-1 1s-1-.4-1-1v-3c0-.6.4-1 1-1h3c.6 0 1 .4 1 1s-.4 1-1 1h-1c.7.7 1.6 1 2.5 1 1.6.1 3.1-1 3.6-2.6.2-.5.7-.8 1.2-.7.6.2.9.7.7 1.1zm57.3-19.6c3.5 0 6.3-2.8 6.3-6.3s-2.8-6.3-6.3-6.3-6.3 2.8-6.3 6.3c0 3.4 2.8 6.3 6.3 6.3zm0-10.7c2.4 0 4.3 1.9 4.3 4.3s-1.9 4.3-4.3 4.3-4.3-1.9-4.3-4.3 1.9-4.3 4.3-4.3zm-.9 4.7v-3c0-.3.2-.5.5-.5s.5.2.5.5v2.5h2.6c.3 0 .5.2.5.5s-.2.5-.5.5H88c-.3 0-.5-.2-.5-.5zm29 4.2v-3c0-.6.4-1 1-1h3c.6 0 1 .4 1 1s-.4 1-1 1h-1c.7.7 1.6 1 2.5 1 1.6 0 3.1-1.1 3.4-2.7.2-.5.7-.8 1.2-.7.5.1.8.7.7 1.2-.7 3-3.7 4.8-6.6 4.1-.9-.2-1.7-.6-2.4-1.2v.3c0 .6-.4 1-1 1s-.9-.4-.8-1zm.2-5.9c.7-3 3.7-4.8 6.6-4.1.9.2 1.7.6 2.4 1.2v-.3c0-.6.4-1 1-1s1 .4 1 1v3c0 .6-.4 1-1 1h-3c-.6 0-1-.4-1-1s.4-1 1-1h1c-.7-.7-1.6-1-2.5-1-1.6 0-3.1 1.1-3.4 2.7-.1.5-.5.8-1 .8h-.2c-.6-.2-1-.7-.9-1.3z"/>
+</svg>
new file mode 100644
--- /dev/null
+++ b/browser/themes/shared/fxa/graphic-mail.svg
@@ -0,0 +1,112 @@
+<!-- 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/. -->
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 143 71">
+  <style>.st1{fill:#ccedf0}.st2{fill:#1da4e9}.st6{fill:#fff}</style>
+  <path
+    d="M50 45.1H38c-.3 0-.5-.2-.5-.5s.2-.5.5-.5h12c.3 0 .5.2.5.5s-.2.5-.5.5zm-21 0h-1c-.3 0-.5-.2-.5-.5s.2-.5.5-.5h1c.3 0 .5.2.5.5s-.2.5-.5.5zm-5 0h-3c-.3 0-.5-.2-.5-.5s.2-.5.5-.5h3c.3 0 .5.2.5.5s-.2.5-.5.5zm-11 0H1c-.3 0-.5-.2-.5-.5s.2-.5.5-.5h12c.3 0 .5.2.5.5s-.2.5-.5.5zm41.1 3.8c0-.6-.4-1-1-1H1c-.6 0-1 .4-1 1s.4 1 1 1h52.1c.6 0 1-.5 1-1zm-3.1 7h-6c-.3 0-.5-.2-.5-.5s.2-.5.5-.5h6c.3 0 .5.2.5.5s-.2.5-.5.5zm-8 0h-4c-.3 0-.5-.2-.5-.5s.2-.5.5-.5h4c.3 0 .5.2.5.5s-.2.5-.5.5zm-12 0H21c-.3 0-.5-.2-.5-.5s.2-.5.5-.5h10c.3 0 .5.2.5.5s-.2.5-.5.5zm-16 0h-2c-.3 0-.5-.2-.5-.5s.2-.5.5-.5h2c.3 0 .5.2.5.5s-.2.5-.5.5zm-6 0H1c-.3 0-.5-.2-.5-.5s.2-.5.5-.5h8c.3 0 .5.2.5.5s-.2.5-.5.5z"
+    fill="#eaeaee"/>
+  <g id="Layer_2">
+    <path class="st1"
+          d="M96.8 66.6c-.2 0-.4-.1-.6-.2L70.6 48.6 44.9 66.5c-.3.2-.7.2-1 .1-.3-.2-.5-.5-.5-.9V38.1c0-3.2 2.7-5.9 5.9-5.9h42.6c3.2 0 5.9 2.6 5.9 5.9v27.5c0 .4-.2.7-.5.9-.2.1-.3.1-.5.1z"/>
+    <path class="st2"
+          d="M91.9 33.3c2.7 0 4.9 2.2 4.9 4.9v27.5L70.6 47.4 44.3 65.6V38.1c0-2.7 2.2-4.9 4.9-4.9l42.7.1m0-2H49.3c-3.8 0-6.9 3-6.9 6.8v27.5c0 .7.4 1.4 1.1 1.8.7.3 1.5.3 2.1-.1l25.1-17.4 25.1 17.4c.6.4 1.4.5 2.1.1.7-.3 1.1-1 1.1-1.8V38.1c-.2-3.8-3.3-6.8-7.1-6.8z"/>
+  </g>
+  <g id="Layer_3">
+    <path class="st1"
+          d="M48.9 70h-.5c-.1 0-.2 0-.3-.1h.2c-1.2-.1-2.3-.6-3.2-1.4 0 0-.1 0-.1-.1l-.1-.1-.5-.5c-.4-.5-.7-1-1-1.6v-.5c0-.3.1-.6.4-.8 1.3-1.5 6-5.2 22.7-17.7 1.2-1 2.6-1.7 4.2-1.8 1.3 0 2.5.5 4.2 1.8C91.3 59.7 96.1 63.5 97.5 65c.4.3.5.8.3 1.3-.9 2.3-3.1 3.7-5.5 3.7H48.9z"/>
+    <path class="st2"
+          d="M70.5 46.6c1.3.1 2.6.7 3.6 1.6 0 0 22.9 17.1 22.7 17.6-.7 1.9-2.6 3.2-4.7 3.2H48.9c-.2 0-.3 0-.5-.1h-.1c-1-.1-2-.5-2.7-1.2-.2-.2-.4-.3-.5-.5-.4-.4-.6-.9-.8-1.4-.3-.5 22.6-17.6 22.6-17.6 1-.9 2.3-1.5 3.6-1.6m0-2c-1.8.1-3.5.8-4.8 2-.1 0-5.8 4.3-11.4 8.6-3.3 2.5-6 4.6-7.8 6.1-1.1.9-2 1.6-2.6 2.1-.9.8-1.8 1.6-1.6 2.8 0 .2.1.3.1.5.3.7.6 1.3 1.1 1.9l.6.6c0 .1.1.1.1.1l.1.1c1 .9 2.3 1.4 3.6 1.6h.4c.2 0 .4.1.6.1h43.3c2.8 0 5.4-1.7 6.5-4.3.2-.3.2-.6.2-.9 0-1-.8-1.7-1.6-2.4-.6-.5-1.5-1.2-2.6-2.1-1.9-1.5-4.5-3.5-7.9-6.1-5.6-4.3-11.4-8.6-11.5-8.6-1.3-1.3-3-2-4.8-2.1z"/>
+  </g>
+  <linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="379.562" y1="1053.255" x2="509.452" y2="1183.146"
+                  gradientTransform="translate(-364.41 -1094.055)">
+    <stop offset="0" stop-color="#00c8d7"/>
+    <stop offset="1" stop-color="#0a84ff"/>
+  </linearGradient>
+  <path
+    d="M70.4 16.4c1.3.1 2.5.6 3.4 1.5l.9.7c.4.2.7.4.9.7l.2.1c-.3-.3-.5-.6-.6-1-.4-1.5-1.4-2.8-2.8-3.6-.6-.3-1.3-.4-2-.4-.5 0-1.1.1-1.6.3.5.6 1 1.1 1.6 1.7z"
+    fill="url(#SVGID_1_)"/>
+  <path d="M71.1 39.1s.1-.1 0 0c.1-.1 0 0 0 0z" fill="#9cf"/>
+  <path d="M73.2 48.1s-.1.1 0 0z" fill="#59acff"/>
+  <path id="_Group_" class="st6"
+        d="M70.5 56.5c-1.3 0-2.5-.5-4.2-1.8L44 37.2c-.3-.3-.5-.7-.3-1.1.3-.9.8-1.7 1.4-2.3.3-.2.5-.4.8-.6l20.7-15.9c1.1-1 2.5-1.6 4-1.7 1.5.1 2.9.7 4 1.7l19.8 15.5c.7.3 1.3.7 1.8 1.3.7.7 1.1 1.5 1.3 2.5 0 .3-.1.7-.4.9L74.7 54.8c-1.2 1-2.7 1.6-4.2 1.7z"/>
+  <g id="Chain">
+    <path
+      d="M61.8 44.4l1.9-1.9c.4-.4.8-.6 1.2-.8.5-1.4 1.2-2.7 2.3-3.8-2.2-.2-4.4.6-5.9 2.2l-3.5 3.3-2.7 2.7-.1.1 2.7 2.1.1-.1 4-3.8zm11.1 5.2s0-.1 0 0c1.8-2.8 1.7-6.2-.2-8.7-1.3.2-2.3 1.2-2.5 2.5.9 1.6.7 3.7-.6 5l-1.3 1.3-3.8 3.8-.1.1 2.5 2c.1.1.2.1.2.2l.1-.1 3.4-3.4 2.3-2.7z"
+      fill="#90e8f0"/>
+    <linearGradient id="SVGID_2_" gradientUnits="userSpaceOnUse" x1="430.109" y1="1136.555" x2="450.432" y2="1136.555"
+                    gradientTransform="translate(-364.41 -1094.055)">
+      <stop offset="0" stop-color="#02bdde"/>
+      <stop offset="1" stop-color="#04aee7"/>
+    </linearGradient>
+    <path
+      d="M78.6 38.5c-2.8-2.8-7.2-3-10.2-.5-.1.1-.3.2-.4.4-.6.6-1.2 1.4-1.6 2.2-.1.2-.2.5-.3.7-.9 2.6-.4 5.5 1.4 7.5.5-.5 1.1-1.1 1.5-1.6.3-.4.6-.9.7-1.3-.6-1.1-.7-2.4-.3-3.5 0-.1.1-.2.2-.3s.1-.3.2-.4l.1-.1c.3-.4.6-.8 1.1-1.1 0 0 .1 0 .1-.1.1-.1.2-.1.3-.2 1.6-.9 3.6-.6 4.9.7l6.7 6.7.3.3c0 .1.1.1.1.2l2.7-2.1c0-.1-.1-.1-.1-.2-.2-.2-.4-.4-.5-.6l-6.9-6.7z"
+      fill="url(#SVGID_2_)"/>
+    <linearGradient id="SVGID_3_" gradientUnits="userSpaceOnUse" x1="71.406" y1="770.773" x2="76.783" y2="770.773"
+                    gradientTransform="translate(0 -718)">
+      <stop offset="0" stop-color="#02bdde"/>
+      <stop offset="1" stop-color="#04aee7"/>
+    </linearGradient>
+    <path d="M71.4 52.8l2.5 2.5.1.1 2.7-2.1-.1-.2-2.9-2.9-2.3 2.6z" fill="url(#SVGID_3_)"/>
+    <animateTransform id="anim1" attributeName="transform" attributeType="XML" type="translate" from="0 0" to="0 -1.5"
+                      begin="0s; anim3.end" dur="0.4s" fill="freeze"/>
+    <animateTransform id="anim2" attributeName="transform" attributeType="XML" type="translate" from="0 -1.5" to="0 0"
+                      begin="anim1.end" dur="0.4s" fill="freeze"/>
+    <animateTransform id="anim3" attributeName="transform" attributeType="XML" type="translate" from="0 0" to="0 0"
+                      begin="anim2.end" dur="5s" fill="freeze"/>
+  </g>
+  <path id="_Group_2" class="st2"
+        d="M70.5 16.4c1.3.1 2.4.6 3.4 1.5 0 0 21 15.9 21.5 16.5.5.5.9 1.2 1.1 1.9L74.2 53.7c-1 .9-2.2 1.4-3.5 1.6-1.3-.1-2.6-.7-3.6-1.6L44.7 36.3c.2-.7.6-1.3 1.1-1.9.2-.2 21.4-16.5 21.4-16.5.9-.8 2.1-1.4 3.3-1.5m0-2c-1.7.1-3.3.8-4.6 1.9 0 0-21.3 16.4-21.5 16.6-.8.8-1.3 1.7-1.7 2.7-.3.8 0 1.7.7 2.2l22.4 17.4c1.3 1.1 3 1.8 4.8 1.9 1.8-.1 3.4-.8 4.7-2l22.4-17.4c.5-.4.8-1.1.8-1.8-.2-1.1-.8-2.2-1.6-3-.6-.7-21.8-16.8-21.8-16.8-1.3-.9-2.9-1.5-4.6-1.7z"/>
+  <g>
+    <linearGradient id="SVGID_5_" gradientUnits="userSpaceOnUse" x1="58.063" y1="-5.732" x2="89.598" y2="25.803">
+      <stop offset="0" stop-color="#00c8d7"/>
+      <stop offset="1" stop-color="#0a84ff"/>
+    </linearGradient>
+    <path
+      d="M68.9 9.6c-.5 0-1-.4-1.1-.9-.3-1.2-1.3-2.2-2.5-2.5-.6-.1-1-.8-.8-1.4.1-.4.4-.7.8-.8 1.2-.3 2.2-1.3 2.5-2.5.1-.6.8-1 1.4-.8.4.1.7.4.8.8.3 1.2 1.3 2.2 2.5 2.5.6.1 1 .8.8 1.4-.1.4-.4.7-.8.8-1.3.4-2.2 1.3-2.5 2.5-.1.5-.6.9-1.1.9z"
+      fill="url(#SVGID_5_)"/>
+    <path class="st6"
+          d="M68.9 2c.1 0 .1 0 0 0 .5 1.7 1.7 2.9 3.3 3.3.1 0 .1.1.1.2s-.1.1-.1.1c-1.6.4-2.8 1.6-3.2 3.2 0 .1-.1.1-.2.1s-.1-.1-.1-.1c-.4-1.6-1.6-2.8-3.2-3.2-.1 0-.1-.1-.1-.2s.1-.1.1-.1c1.6-.4 2.8-1.6 3.2-3.2.1-.1.1-.1.2-.1m0-2c-1 0-1.8.7-2.1 1.6-.2.9-.9 1.5-1.7 1.7-1.1.3-1.8 1.4-1.6 2.6.2.8.8 1.4 1.6 1.6.9.2 1.5.9 1.7 1.7.3 1.1 1.4 1.8 2.6 1.6.8-.2 1.4-.8 1.6-1.6.2-.9.9-1.5 1.7-1.7 1.1-.3 1.8-1.4 1.6-2.6-.2-.8-.8-1.4-1.6-1.6-.9-.2-1.5-.9-1.7-1.7C70.7.7 69.9 0 68.9 0z"/>
+    <animate id="anim1y" attributeName="opacity" attributeType="XML" from="0" to="0" begin="0s; anim3y.end" dur="1s"
+             fill="freeze"/>
+    <animate id="anim2y" attributeName="opacity" attributeType="XML" from="0" to="1" begin="anim1y.end" dur="0.4s"
+             fill="freeze"/>
+    <animate id="anim3y" attributeName="opacity" attributeType="XML" from="1" to="0" begin="anim2y.end" dur="0.4s"
+             fill="freeze"/>
+  </g>
+  <g>
+    <linearGradient id="SVGID_6_" gradientUnits="userSpaceOnUse" x1="61.027" y1="-4.906" x2="109.386" y2="43.452">
+      <stop offset="0" stop-color="#00c8d7"/>
+      <stop offset="1" stop-color="#0a84ff"/>
+    </linearGradient>
+    <path
+      d="M78.8 17.8c.6-2.4 2.5-4.3 4.9-4.9.7-.2 1.1-.8.9-1.5-.1-.5-.5-.8-.9-.9-2.4-.6-4.3-2.5-4.9-4.9-.2-.7-.8-1.1-1.5-.9-.5.1-.8.5-.9.9-.6 2.4-2.5 4.3-4.9 4.9-.7.2-1.1.8-.9 1.5.1.5.5.8.9.9 2.4.6 4.3 2.5 4.9 4.9.2.7.8 1.1 1.5.9.5-.1.8-.4.9-.9z"
+      fill="url(#SVGID_6_)"/>
+    <path class="st6"
+          d="M77.4 5.9c.1 0 .2.1.2.2.7 2.8 2.9 4.9 5.6 5.6.1 0 .2.1.2.3 0 .1-.1.2-.2.2-2.8.7-4.9 2.9-5.6 5.6 0 .1-.1.2-.2.2s-.2-.1-.2-.2c-.7-2.8-2.9-4.9-5.6-5.6-.1 0-.2-.2-.2-.3s.1-.2.2-.2c2.8-.7 4.9-2.9 5.6-5.6 0-.1.1-.2.2-.2m0-2c-1 0-1.9.7-2.2 1.7-.5 2.1-2.1 3.7-4.2 4.2-1.2.3-1.9 1.5-1.6 2.7.2.8.8 1.5 1.7 1.7 2.1.5 3.7 2.1 4.2 4.2.3 1.2 1.5 1.9 2.7 1.6.8-.2 1.5-.8 1.6-1.7.5-2.1 2.1-3.7 4.2-4.2 1.2-.3 1.9-1.5 1.6-2.7-.2-.8-.8-1.5-1.7-1.6-2.1-.5-3.7-2.1-4.2-4.2-.2-.9-1.1-1.6-2.1-1.7z"/>
+    <animate id="anim1z" attributeName="opacity" attributeType="XML" from="0" to="0" begin="0s; anim3z.end" dur="2s"
+             fill="freeze"/>
+    <animate id="anim2z" attributeName="opacity" attributeType="XML" from="0" to="1" begin="anim1z.end" dur="0.4s"
+             fill="freeze"/>
+    <animate id="anim3z" attributeName="opacity" attributeType="XML" from="1" to="0" begin="anim2z.end" dur="0.4s"
+             fill="freeze"/>
+  </g>
+  <g>
+    <linearGradient id="SVGID_4_" gradientUnits="userSpaceOnUse" x1="406.081" y1="1091.23" x2="474.017" y2="1159.166"
+                    gradientTransform="translate(-364.41 -1094.055)">
+      <stop offset="0" stop-color="#00c8d7"/>
+      <stop offset="1" stop-color="#0a84ff"/>
+    </linearGradient>
+    <path
+      d="M66.4 29.5c1-3.8 3.9-6.8 7.7-7.7.6-.2 1-.7 1-1.3 0-.6-.4-1.2-1.1-1.3-3.8-1-6.7-3.9-7.7-7.7-.2-.7-.9-1.2-1.6-1-.5.1-.9.5-1 1-1 3.8-3.9 6.7-7.7 7.7-.6.1-1.1.7-1.1 1.3 0 .6.4 1.2 1.1 1.3 3.8 1 6.7 3.9 7.7 7.7.2.7.9 1.2 1.6 1 .6-.1 1-.5 1.1-1z"
+      fill="url(#SVGID_4_)"/>
+    <path class="st6"
+          d="M65.1 11.6c.2 0 .3.1.4.3 1.1 4.1 4.3 7.4 8.4 8.4.2 0 .3.2.3.4 0 .1-.1.3-.3.3-4.1 1.1-7.4 4.3-8.4 8.4 0 .2-.2.3-.4.3s-.3-.1-.4-.3c-1.1-4.1-4.3-7.4-8.4-8.4-.2 0-.3-.2-.3-.4 0-.1.1-.3.3-.3 4.1-1.1 7.4-4.3 8.4-8.4 0-.1.2-.3.4-.3m0-2c-1.1 0-2 .7-2.3 1.8-.9 3.4-3.6 6.1-7 7-1.3.3-2 1.6-1.7 2.8.2.9.9 1.5 1.7 1.7 3.4.9 6.1 3.6 7 7 .3 1.3 1.6 2 2.8 1.7.9-.2 1.5-.9 1.7-1.7.9-3.4 3.6-6.1 7-7 1.3-.3 2-1.6 1.7-2.9-.2-.9-.9-1.5-1.8-1.7-3.4-.9-6.1-3.6-7-7-.1-.9-1-1.7-2.1-1.7z"/>
+    <animate id="anim1x" attributeName="opacity" attributeType="XML" from="0" to="0" begin="0s; anim3x.end" dur="2.5s"
+             fill="freeze"/>
+    <animate id="anim2x" attributeName="opacity" attributeType="XML" from="0" to="1" begin="anim1x.end" dur="0.4s"
+             fill="freeze"/>
+    <animate id="anim3x" attributeName="opacity" attributeType="XML" from="1" to="0" begin="anim2x.end" dur="0.4s"
+             fill="freeze"/>
+  </g>
+</svg>
--- a/browser/themes/shared/jar.inc.mn
+++ b/browser/themes/shared/jar.inc.mn
@@ -129,17 +129,23 @@
   skin/classic/browser/preferences/in-content/sync.svg         (../shared/incontentprefs/sync.svg)
   skin/classic/browser/preferences/in-content/syncDisconnect.css (../shared/incontentprefs/syncDisconnect.css)
 * skin/classic/browser/preferences/in-content/containers.css   (../shared/incontentprefs/containers.css)
 * skin/classic/browser/preferences/containers.css              (../shared/preferences/containers.css)
   skin/classic/browser/fxa/default-avatar.svg                  (../shared/fxa/default-avatar.svg)
   skin/classic/browser/fxa/fxa-spinner.svg                     (../shared/fxa/fxa-spinner.svg)
   skin/classic/browser/fxa/sync-illustration.svg               (../shared/fxa/sync-illustration.svg)
   skin/classic/browser/fxa/sync-illustration-issue.svg         (../shared/fxa/sync-illustration-issue.svg)
-
+  
+  skin/classic/browser/fxa/avatar.svg                          (../shared/fxa/avatar.svg)
+  skin/classic/browser/fxa/avatar-confirm.svg                  (../shared/fxa/avatar-confirm.svg)
+  skin/classic/browser/fxa/avatar-empty.svg                    (../shared/fxa/avatar-empty.svg)
+  skin/classic/browser/fxa/avatar-empty-badged.svg             (../shared/fxa/avatar-empty-badged.svg)
+  skin/classic/browser/fxa/graphic-mail.svg                    (../shared/fxa/graphic-mail.svg)
+  skin/classic/browser/fxa/choose-what-to-sync-devices.svg     (../shared/fxa/choose-what-to-sync-devices.svg)
 
   skin/classic/browser/accessibility.svg              (../shared/icons/accessibility.svg)
   skin/classic/browser/accessibility-active.svg       (../shared/icons/accessibility-active.svg)
   skin/classic/browser/add.svg                        (../shared/icons/add.svg)
   skin/classic/browser/arrow-left.svg                 (../shared/icons/arrow-left.svg)
   skin/classic/browser/back.svg                       (../shared/icons/back.svg)
   skin/classic/browser/back-12.svg                    (../shared/icons/back-12.svg)
   skin/classic/browser/bookmark.svg                   (../shared/icons/bookmark.svg)
--- a/browser/themes/shared/menupanel.inc.css
+++ b/browser/themes/shared/menupanel.inc.css
@@ -76,29 +76,33 @@
 #appMenu-library-pocket-button {
   list-style-image: url("chrome://browser/skin/pocket.svg");
 }
 
 #appMenu-library-history-button {
   list-style-image: url(chrome://browser/skin/history.svg);
 }
 
+#PanelUI-fxa-menu-remotetabs-button,
 #appMenuRecentlyClosedTabs,
 #appMenu-library-remotetabs-button {
   list-style-image: url("chrome://browser/skin/tab.svg");
 }
 
+#PanelUI-fxa-menu-syncnow-button,
 #PanelUI-remotetabs-syncnow {
   list-style-image: url("chrome://browser/skin/sync.svg");
 }
 
+#PanelUI-fxa-menu-view-managedevices
 #PanelUI-remotetabs-view-managedevices {
   list-style-image: url("chrome://browser/skin/device-phone.svg");
 }
 
+#PanelUI-fxa-menu-view-sidebar,
 #appMenuViewHistorySidebar,
 #PanelUI-remotetabs-view-sidebar,
 #panelMenu_viewBookmarksSidebar {
   list-style-image: url("chrome://browser/skin/sidebars-right.svg");
 }
 
 #appMenuViewHistorySidebar:-moz-locale-dir(ltr):not([positionend]),
 #appMenuViewHistorySidebar:-moz-locale-dir(rtl)[positionend],
--- a/browser/themes/shared/urlbar-searchbar.inc.css
+++ b/browser/themes/shared/urlbar-searchbar.inc.css
@@ -175,16 +175,17 @@
   list-style-image: url("chrome://browser/skin/link.svg");
 }
 
 #pageAction-panel-emailLink,
 #pageAction-urlbar-emailLink {
   list-style-image: url("chrome://browser/skin/mail.svg");
 }
 
+#PanelUI-fxa-menu-sendtab-button,
 #pageAction-panel-sendToDevice,
 #pageAction-urlbar-sendToDevice {
   list-style-image: url("chrome://browser/skin/send-to-device.svg");
 }
 
 #pageAction-panel-pinTab:-moz-locale-dir(rtl) > .toolbarbutton-icon,
 #pageAction-urlbar-pinTab:-moz-locale-dir(rtl),
 #pageAction-panel-sendToDevice:-moz-locale-dir(rtl) > .toolbarbutton-icon,
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..182b013de0f35607e7655415cde3a3fc558d3d5d
GIT binary patch
literal 1706
zc$^FHW@h1H00I9wxqb{tfP+DXp|~W!C^0=%KQx4sf!WNrGe+01Gp4kHn}Lz#D<cB~
ziwIC%08SGGkW5HPO-n4zDIwCN18620U@<A9q@<u&TU$FVKVL5?u}CjBzbZ2)Cs8*u
zzeF#;C>>%GhaXOxM8JMzo15#moYS4z2*|SsVoNe?(#uUPNlZyBNd#Nj1GKIO)5_$8
z+<(WXr<U|JHa6Ou@7FR`R}VBd-tVHnUqe<yWqx2#P(aSo_4B9AUq5|H#FF-PEJ%(7
z1xMq{qccH{^Z{ZUN*t+MW&{r!1zhed^_j@X`{DVs@YH8dR9>Y$oBrad_)_N9C&pf0
zTwQCwe7*eX^Vdm2-Cr(DMGnl1^K$(h`QG;%fZ~jafq@U<PQBv7oXnC`u+1+6J7Yi?
z&1P=CFDG?8Lp@IGpYqiSG)PNGN=Qj52na|3f|jH^4V?`(9{cCHnd!T{)sSdGwwGr`
zp<i7y`@u^<o;uJt9-zI+MX8A;sqraEdRfI_Tf>+4#eguHt+xFK{SF%lxV-<@73!DC
zY8K-tGa+F8+9TpSzNtl?{3un;>?QdB+_EM)_gUZF9)DwdV7gvl$C|0fJR783dPMdl
zuq004e7I$fFl)u*o59<<e3YJ^+!=6@yEf@T<+D3F*Dp@ZHji8Vu4(Gs#kB=#?|VMx
zo@H!PWmi0>aN|krhAnmJ`$DBc6a%!LIviE6XcT<<MgDi5bnY%IgWH?emp$WN`s96Q
zYV^CZ^KQDFtXg$CTH(y8lNQS@zH|Rb`2|;*Ka*0N8U9RPE?pe3$XDX`{x!YwpSc6P
z8JXmmab;==c;<ZT2%-^LoE4HWF|rD7(*#gVb3!r=k=+TKikaDwP3^&AD&efp3Q6Bs
zZN-)GCEzY-X#7Totw_ZHE2J2}V?Ab;Mp(b3aW*3b)`K%R;Q+wQtjGc2%*23^by*=<
o7mt~k84+S8BiPK{Kr>OZB`YLbVwlLv2GYe2ga?6oE;EC80N9G!?*IS*
new file mode 100644
--- /dev/null
+++ b/dom/localstorage/test/unit/test_groupMismatch.js
@@ -0,0 +1,46 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+/**
+ * This test is mainly to verify that metadata files with old group information
+ * get updated, so writing to local storage won't cause a crash because of null
+ * quota object. See bug 1516333.
+ */
+
+async function testSteps()
+{
+  const principal = getPrincipal("https://foo.bar.mozilla-iot.org");
+
+  info("Clearing");
+
+  let request = clear();
+  await requestFinished(request);
+
+  info("Installing package");
+
+  // The profile contains one initialized origin directory, a script for origin
+  // initialization and the storage database:
+  // - storage/default/https+++foo.bar.mozilla-iot.org
+  // - create_db.js
+  // - storage.sqlite
+  // The file create_db.js in the package was run locally, specifically it was
+  // temporarily added to xpcshell.ini and then executed:
+  //   mach xpcshell-test --interactive dom/localstorage/test/unit/create_db.js
+  // Note: to make it become the profile in the test, additional manual steps
+  // are needed.
+  // 1. Manually change the group in .metadata and .metadata-v2 from
+  //    "bar.mozilla-iot.org" to "mozilla-iot.org".
+  // 2. Remove the folder "storage/temporary".
+  // 3. Remove the file "storage/ls-archive.sqlite".
+  installPackage("groupMismatch_profile");
+
+  info("Getting storage");
+
+  let storage = getLocalStorage(principal);
+
+  info("Adding item");
+
+  storage.setItem("foo", "bar");
+}
--- a/dom/localstorage/test/unit/xpcshell.ini
+++ b/dom/localstorage/test/unit/xpcshell.ini
@@ -2,16 +2,17 @@
 # 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/.
 
 [DEFAULT]
 head = head.js
 support-files =
   archive_profile.zip
   corruptedDatabase_profile.zip
+  groupMismatch_profile.zip
   migration_profile.zip
   stringLength_profile.zip
 
 [test_archive.js]
 [test_corruptedDatabase.js]
 [test_databaseShadowing1.js]
 run-sequentially = test_databaseShadowing2.js depends on a file produced by this test
 [test_databaseShadowing2.js]
@@ -25,13 +26,14 @@ run-sequentially = test_databaseShadowin
 [test_databaseShadowing_clearOriginsByPattern2.js]
 run-sequentially = this test depends on a file produced by test_databaseShadowing_clearOriginsByPattern1.js
 [test_databaseShadowing_clearOriginsByPrefix1.js]
 run-sequentially = test_databaseShadowing_clearOriginsByPrefix2.js depends on a file produced by this test
 [test_databaseShadowing_clearOriginsByPrefix2.js]
 run-sequentially = this test depends on a file produced by test_databaseShadowing_clearOriginsByPrefix1.js
 [test_eviction.js]
 [test_groupLimit.js]
+[test_groupMismatch.js]
 [test_migration.js]
 [test_originInit.js]
 [test_snapshotting.js]
 [test_stringLength.js]
 [test_usage.js]
--- a/dom/quota/ActorsParent.cpp
+++ b/dom/quota/ActorsParent.cpp
@@ -3567,16 +3567,76 @@ nsresult QuotaManager::GetDirectoryMetad
 
   // Currently unused (used to be isApp).
   bool dummy;
   rv = binaryStream->ReadBoolean(&dummy);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
+  rv = binaryStream->Close();
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+ if (!origin.EqualsLiteral(kChromeOrigin)) {
+    OriginAttributes originAttributes;
+    nsCString originNoSuffix;
+    if (NS_WARN_IF(!originAttributes.PopulateFromOrigin(origin,
+                                                        originNoSuffix))) {
+      return NS_ERROR_FAILURE;
+    }
+
+    RefPtr<MozURL> url;
+    rv = MozURL::Init(getter_AddRefs(url), originNoSuffix);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
+    }
+
+    nsCString baseDomain;
+    rv = url->BaseDomain(baseDomain);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
+    }
+
+    nsCString upToDateGroup = baseDomain + suffix;
+
+    if (group != upToDateGroup) {
+      group = upToDateGroup;
+
+      rv = CreateDirectoryMetadata(
+          aDirectory, timestamp, suffix, group, origin);
+      if (NS_WARN_IF(NS_FAILED(rv))) {
+        return rv;
+      }
+
+      rv = CreateDirectoryMetadata2(
+          aDirectory, timestamp, persisted, suffix, group, origin);
+      if (NS_WARN_IF(NS_FAILED(rv))) {
+        return rv;
+      }
+
+  #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
+      ContentPrincipalInfo contentPrincipalInfo;
+      contentPrincipalInfo.attrs() = originAttributes;
+      contentPrincipalInfo.originNoSuffix() = originNoSuffix;
+      contentPrincipalInfo.spec() = originNoSuffix;
+      contentPrincipalInfo.baseDomain() = baseDomain;
+
+      PrincipalInfo principalInfo(contentPrincipalInfo);
+
+      nsTArray<PrincipalInfo> principalInfos;
+      principalInfos.AppendElement(principalInfo);
+
+      RefPtr<PrincipalVerifier> principalVerifier =
+          PrincipalVerifier::CreateAndDispatch(std::move(principalInfos));
+  #endif
+    }
+  }
+
   *aTimestamp = timestamp;
   *aPersisted = persisted;
   aSuffix = suffix;
   aGroup = group;
   aOrigin = origin;
   return NS_OK;
 }
 
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..182b013de0f35607e7655415cde3a3fc558d3d5d
GIT binary patch
literal 1706
zc$^FHW@h1H00I9wxqb{tfP+DXp|~W!C^0=%KQx4sf!WNrGe+01Gp4kHn}Lz#D<cB~
ziwIC%08SGGkW5HPO-n4zDIwCN18620U@<A9q@<u&TU$FVKVL5?u}CjBzbZ2)Cs8*u
zzeF#;C>>%GhaXOxM8JMzo15#moYS4z2*|SsVoNe?(#uUPNlZyBNd#Nj1GKIO)5_$8
z+<(WXr<U|JHa6Ou@7FR`R}VBd-tVHnUqe<yWqx2#P(aSo_4B9AUq5|H#FF-PEJ%(7
z1xMq{qccH{^Z{ZUN*t+MW&{r!1zhed^_j@X`{DVs@YH8dR9>Y$oBrad_)_N9C&pf0
zTwQCwe7*eX^Vdm2-Cr(DMGnl1^K$(h`QG;%fZ~jafq@U<PQBv7oXnC`u+1+6J7Yi?
z&1P=CFDG?8Lp@IGpYqiSG)PNGN=Qj52na|3f|jH^4V?`(9{cCHnd!T{)sSdGwwGr`
zp<i7y`@u^<o;uJt9-zI+MX8A;sqraEdRfI_Tf>+4#eguHt+xFK{SF%lxV-<@73!DC
zY8K-tGa+F8+9TpSzNtl?{3un;>?QdB+_EM)_gUZF9)DwdV7gvl$C|0fJR783dPMdl
zuq004e7I$fFl)u*o59<<e3YJ^+!=6@yEf@T<+D3F*Dp@ZHji8Vu4(Gs#kB=#?|VMx
zo@H!PWmi0>aN|krhAnmJ`$DBc6a%!LIviE6XcT<<MgDi5bnY%IgWH?emp$WN`s96Q
zYV^CZ^KQDFtXg$CTH(y8lNQS@zH|Rb`2|;*Ka*0N8U9RPE?pe3$XDX`{x!YwpSc6P
z8JXmmab;==c;<ZT2%-^LoE4HWF|rD7(*#gVb3!r=k=+TKikaDwP3^&AD&efp3Q6Bs
zZN-)GCEzY-X#7Totw_ZHE2J2}V?Ab;Mp(b3aW*3b)`K%R;Q+wQtjGc2%*23^by*=<
o7mt~k84+S8BiPK{Kr>OZB`YLbVwlLv2GYe2ga?6oE;EC80N9G!?*IS*
new file mode 100644
--- /dev/null
+++ b/dom/quota/test/unit/test_groupMismatch.js
@@ -0,0 +1,68 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+/**
+ * This test is mainly to verify that metadata files with old group information
+ * get updated. See bug 1535995.
+ */
+
+async function testSteps()
+{
+  const principal = getPrincipal("https://foo.bar.mozilla-iot.org");
+  const metadataFile = getRelativeFile(
+    "storage/default/https+++foo.bar.mozilla-iot.org/.metadata-v2");
+
+  async function readMetadataFile() {
+    let file = await File.createFromNsIFile(metadataFile);
+
+    let buffer = await new Promise(resolve => {
+      let reader = new FileReader();
+      reader.onloadend = () => resolve(reader.result);
+      reader.readAsArrayBuffer(file);
+    });
+
+    return buffer;
+  }
+
+  info("Clearing");
+
+  let request = clear();
+  await requestFinished(request);
+
+  info("Installing package");
+
+  // The profile contains one initialized origin directory, a script for origin
+  // initialization and the storage database:
+  // - storage/default/https+++foo.bar.mozilla-iot.org
+  // - create_db.js
+  // - storage.sqlite
+  // The file create_db.js in the package was run locally, specifically it was
+  // temporarily added to xpcshell.ini and then executed:
+  //   mach xpcshell-test --interactive dom/localstorage/test/unit/create_db.js
+  // Note: to make it become the profile in the test, additional manual steps
+  // are needed.
+  // 1. Manually change the group in .metadata and .metadata-v2 from
+  //    "bar.mozilla-iot.org" to "mozilla-iot.org".
+  // 2. Remove the folder "storage/temporary".
+  // 3. Remove the file "storage/ls-archive.sqlite".
+  installPackage("groupMismatch_profile");
+
+  info("Reading out contents of metadata file");
+
+  let metadataBuffer = await readMetadataFile();
+
+  info("Initializing origin");
+
+  request = initOrigin(principal, "default");
+  await requestFinished(request);
+
+  info("Reading out contents of metadata file");
+
+  let metadataBuffer2 = await readMetadataFile();
+
+  info("Verifying blobs differ");
+
+  ok(!compareBuffers(metadataBuffer, metadataBuffer2), "Metadata differ");
+}
--- a/dom/quota/test/unit/xpcshell.ini
+++ b/dom/quota/test/unit/xpcshell.ini
@@ -4,16 +4,17 @@
 
 [DEFAULT]
 head = head.js
 support-files =
   basics_profile.zip
   createLocalStorage_profile.zip
   defaultStorageUpgrade_profile.zip
   getUsage_profile.zip
+  groupMismatch_profile.zip
   idbSubdirUpgrade1_profile.zip
   idbSubdirUpgrade2_profile.zip
   morgueCleanup_profile.zip
   obsoleteOriginAttributes_profile.zip
   originAttributesUpgrade_profile.zip
   removeAppsUpgrade_profile.zip
   removeLocalStorage1_profile.zip
   removeLocalStorage2_profile.zip
@@ -21,16 +22,17 @@ support-files =
   tempMetadataCleanup_profile.zip
   version2_1upgrade_profile.zip
 
 [test_basics.js]
 [test_bad_origin_directory.js]
 [test_createLocalStorage.js]
 [test_defaultStorageUpgrade.js]
 [test_getUsage.js]
+[test_groupMismatch.js]
 [test_idbSubdirUpgrade.js]
 [test_initTemporaryStorage.js]
 [test_morgueCleanup.js]
 [test_obsoleteOriginAttributesUpgrade.js]
 [test_originAttributesUpgrade.js]
 [test_persist.js]
 [test_persist_eviction.js]
 [test_persist_globalLimit.js]
--- a/gfx/thebes/gfxPrefs.h
+++ b/gfx/thebes/gfxPrefs.h
@@ -561,17 +561,16 @@ class gfxPrefs final {
   DECL_GFX_PREF(Live, "gl.multithreaded",                      GLMultithreaded, bool, false);
 #endif
   DECL_GFX_PREF(Live, "gl.require-hardware",                   RequireHardwareGL, bool, false);
   DECL_GFX_PREF(Live, "gl.use-tls-is-current",                 UseTLSIsCurrent, int32_t, 0);
 
   DECL_GFX_PREF(Live, "image.animated.decode-on-demand.threshold-kb", ImageAnimatedDecodeOnDemandThresholdKB, uint32_t, 20480);
   DECL_GFX_PREF(Live, "image.animated.decode-on-demand.batch-size", ImageAnimatedDecodeOnDemandBatchSize, uint32_t, 6);
   DECL_GFX_PREF(Live, "image.animated.decode-on-demand.recycle", ImageAnimatedDecodeOnDemandRecycle, bool, false);
-  DECL_GFX_PREF(Once, "image.animated.generate-full-frames",   ImageAnimatedGenerateFullFrames, bool, false);
   DECL_GFX_PREF(Live, "image.animated.resume-from-last-displayed", ImageAnimatedResumeFromLastDisplayed, bool, false);
   DECL_GFX_PREF(Live, "image.cache.factor2.threshold-surfaces", ImageCacheFactor2ThresholdSurfaces, int32_t, -1);
   DECL_GFX_PREF(Live, "image.cache.max-rasterized-svg-threshold-kb", ImageCacheMaxRasterizedSVGThresholdKB, int32_t, 90*1024);
   DECL_GFX_PREF(Once, "image.cache.size",                      ImageCacheSize, int32_t, 5*1024*1024);
   DECL_GFX_PREF(Once, "image.cache.timeweight",                ImageCacheTimeWeight, int32_t, 500);
   DECL_GFX_PREF(Live, "image.decode-immediately.enabled",      ImageDecodeImmediatelyEnabled, bool, false);
   DECL_GFX_PREF(Live, "image.downscale-during-decode.enabled", ImageDownscaleDuringDecodeEnabled, bool, true);
   DECL_GFX_PREF(Live, "image.infer-src-animation.threshold-ms", ImageInferSrcAnimationThresholdMS, uint32_t, 2000);
--- a/image/AnimationParams.h
+++ b/image/AnimationParams.h
@@ -28,16 +28,25 @@ enum class DisposalMethod : int8_t {
   CLEAR_ALL = -1,   // Clear the whole image, revealing what's underneath.
   NOT_SPECIFIED,    // Leave the frame and let the new frame draw on top.
   KEEP,             // Leave the frame and let the new frame draw on top.
   CLEAR,            // Clear the frame's area, revealing what's underneath.
   RESTORE_PREVIOUS  // Restore the previous (composited) frame.
 };
 
 struct AnimationParams {
+  AnimationParams(const gfx::IntRect& aBlendRect, const FrameTimeout& aTimeout,
+                  uint32_t aFrameNum, BlendMethod aBlendMethod,
+                  DisposalMethod aDisposalMethod)
+      : mBlendRect(aBlendRect),
+        mTimeout(aTimeout),
+        mFrameNum(aFrameNum),
+        mBlendMethod(aBlendMethod),
+        mDisposalMethod(aDisposalMethod) {}
+
   gfx::IntRect mBlendRect;
   FrameTimeout mTimeout;
   uint32_t mFrameNum;
   BlendMethod mBlendMethod;
   DisposalMethod mDisposalMethod;
 };
 
 }  // namespace image
--- a/image/AnimationSurfaceProvider.cpp
+++ b/image/AnimationSurfaceProvider.cpp
@@ -26,29 +26,22 @@ AnimationSurfaceProvider::AnimationSurfa
       mDecodingMutex("AnimationSurfaceProvider::mDecoder"),
       mDecoder(aDecoder.get()),
       mFramesMutex("AnimationSurfaceProvider::mFrames") {
   MOZ_ASSERT(!mDecoder->IsMetadataDecode(),
              "Use MetadataDecodingTask for metadata decodes");
   MOZ_ASSERT(!mDecoder->IsFirstFrameDecode(),
              "Use DecodedSurfaceProvider for single-frame image decodes");
 
-  // We may produce paletted surfaces for GIF which means the frames are smaller
-  // than one would expect.
-  size_t pixelSize = !aDecoder->ShouldBlendAnimation() &&
-                             aDecoder->GetType() == DecoderType::GIF
-                         ? sizeof(uint8_t)
-                         : sizeof(uint32_t);
-
   // Calculate how many frames we need to decode in this animation before we
   // enter decode-on-demand mode.
   IntSize frameSize = aSurfaceKey.Size();
   size_t threshold =
       (size_t(gfxPrefs::ImageAnimatedDecodeOnDemandThresholdKB()) * 1024) /
-      (pixelSize * frameSize.width * frameSize.height);
+      (sizeof(uint32_t) * frameSize.width * frameSize.height);
   size_t batch = gfxPrefs::ImageAnimatedDecodeOnDemandBatchSize();
 
   mFrames.reset(
       new AnimationFrameRetainedBuffer(threshold, batch, aCurrentFrame));
 }
 
 AnimationSurfaceProvider::~AnimationSurfaceProvider() {
   DropImageReference();
@@ -408,23 +401,18 @@ void AnimationSurfaceProvider::RequestFr
   if (mFrames->MayDiscard() || mFrames->IsRecycling()) {
     MOZ_ASSERT_UNREACHABLE("Already replaced frame queue!");
     return;
   }
 
   auto oldFrameQueue =
       static_cast<AnimationFrameRetainedBuffer*>(mFrames.get());
 
-  // We only recycle if it is a full frame. Partial frames may be sized
-  // differently from each other. We do not support recycling with WebRender
-  // and shared surfaces at this time as there is additional synchronization
-  // required to know when it is safe to recycle.
   MOZ_ASSERT(!mDecoder->GetFrameRecycler());
-  if (gfxPrefs::ImageAnimatedDecodeOnDemandRecycle() &&
-      mDecoder->ShouldBlendAnimation()) {
+  if (gfxPrefs::ImageAnimatedDecodeOnDemandRecycle()) {
     mFrames.reset(new AnimationFrameRecyclingQueue(std::move(*oldFrameQueue)));
     mDecoder->SetFrameRecycler(this);
   } else {
     mFrames.reset(new AnimationFrameDiscardingQueue(std::move(*oldFrameQueue)));
   }
 }
 
 void AnimationSurfaceProvider::AnnounceSurfaceAvailable() {
--- a/image/Decoder.cpp
+++ b/image/Decoder.cpp
@@ -41,18 +41,16 @@ class MOZ_STACK_CLASS AutoRecordDecoderT
  private:
   Decoder* mDecoder;
   TimeStamp mStartTime;
 };
 
 Decoder::Decoder(RasterImage* aImage)
     : mImageData(nullptr),
       mImageDataLength(0),
-      mColormap(nullptr),
-      mColormapSize(0),
       mImage(aImage),
       mFrameRecycler(nullptr),
       mProgress(NoProgress),
       mFrameCount(0),
       mLoopLength(FrameTimeout::Zero()),
       mDecoderFlags(DefaultDecoderFlags()),
       mSurfaceFlags(DefaultSurfaceFlags()),
       mInitialized(false),
@@ -248,30 +246,26 @@ DecoderFinalStatus Decoder::FinalStatus(
 DecoderTelemetry Decoder::Telemetry() const {
   MOZ_ASSERT(mIterator);
   return DecoderTelemetry(SpeedHistogram(),
                           mIterator ? mIterator->ByteCount() : 0,
                           mIterator ? mIterator->ChunkCount() : 0, mDecodeTime);
 }
 
 nsresult Decoder::AllocateFrame(const gfx::IntSize& aOutputSize,
-                                const gfx::IntRect& aFrameRect,
                                 gfx::SurfaceFormat aFormat,
-                                uint8_t aPaletteDepth,
                                 const Maybe<AnimationParams>& aAnimParams) {
-  mCurrentFrame =
-      AllocateFrameInternal(aOutputSize, aFrameRect, aFormat, aPaletteDepth,
-                            aAnimParams, std::move(mCurrentFrame));
+  mCurrentFrame = AllocateFrameInternal(aOutputSize, aFormat, aAnimParams,
+                                        std::move(mCurrentFrame));
 
   if (mCurrentFrame) {
     mHasFrameToTake = true;
 
     // Gather the raw pointers the decoders will use.
     mCurrentFrame->GetImageData(&mImageData, &mImageDataLength);
-    mCurrentFrame->GetPaletteData(&mColormap, &mColormapSize);
 
     // We should now be on |aFrameNum|. (Note that we're comparing the frame
     // number, which is zero-based, with the frame count, which is one-based.)
     MOZ_ASSERT_IF(aAnimParams, aAnimParams->mFrameNum + 1 == mFrameCount);
 
     // If we're past the first frame, PostIsAnimated() should've been called.
     MOZ_ASSERT_IF(mFrameCount > 1, HasAnimation());
 
@@ -279,73 +273,66 @@ nsresult Decoder::AllocateFrame(const gf
     MOZ_ASSERT(!mInFrame, "Starting new frame but not done with old one!");
     mInFrame = true;
   }
 
   return mCurrentFrame ? NS_OK : NS_ERROR_FAILURE;
 }
 
 RawAccessFrameRef Decoder::AllocateFrameInternal(
-    const gfx::IntSize& aOutputSize, const gfx::IntRect& aFrameRect,
-    SurfaceFormat aFormat, uint8_t aPaletteDepth,
+    const gfx::IntSize& aOutputSize, SurfaceFormat aFormat,
     const Maybe<AnimationParams>& aAnimParams,
     RawAccessFrameRef&& aPreviousFrame) {
   if (HasError()) {
     return RawAccessFrameRef();
   }
 
   uint32_t frameNum = aAnimParams ? aAnimParams->mFrameNum : 0;
   if (frameNum != mFrameCount) {
     MOZ_ASSERT_UNREACHABLE("Allocating frames out of order");
     return RawAccessFrameRef();
   }
 
-  if (aOutputSize.width <= 0 || aOutputSize.height <= 0 ||
-      aFrameRect.Width() <= 0 || aFrameRect.Height() <= 0) {
+  if (aOutputSize.width <= 0 || aOutputSize.height <= 0) {
     NS_WARNING("Trying to add frame with zero or negative size");
     return RawAccessFrameRef();
   }
 
   if (frameNum == 1) {
     MOZ_ASSERT(aPreviousFrame, "Must provide a previous frame when animated");
     aPreviousFrame->SetRawAccessOnly();
   }
 
   if (frameNum > 0) {
-    if (ShouldBlendAnimation()) {
-      if (aPreviousFrame->GetDisposalMethod() !=
-          DisposalMethod::RESTORE_PREVIOUS) {
-        // If the new restore frame is the direct previous frame, then we know
-        // the dirty rect is composed only of the current frame's blend rect and
-        // the restore frame's clear rect (if applicable) which are handled in
-        // filters.
-        mRestoreFrame = std::move(aPreviousFrame);
-        mRestoreDirtyRect.SetBox(0, 0, 0, 0);
-      } else {
-        // We only need the previous frame's dirty rect, because while there may
-        // have been several frames between us and mRestoreFrame, the only areas
-        // that changed are the restore frame's clear rect, the current frame
-        // blending rect, and the previous frame's blending rect. All else is
-        // forgotten due to us restoring the same frame again.
-        mRestoreDirtyRect = aPreviousFrame->GetBoundedBlendRect();
-      }
+    if (aPreviousFrame->GetDisposalMethod() !=
+        DisposalMethod::RESTORE_PREVIOUS) {
+      // If the new restore frame is the direct previous frame, then we know
+      // the dirty rect is composed only of the current frame's blend rect and
+      // the restore frame's clear rect (if applicable) which are handled in
+      // filters.
+      mRestoreFrame = std::move(aPreviousFrame);
+      mRestoreDirtyRect.SetBox(0, 0, 0, 0);
+    } else {
+      // We only need the previous frame's dirty rect, because while there may
+      // have been several frames between us and mRestoreFrame, the only areas
+      // that changed are the restore frame's clear rect, the current frame
+      // blending rect, and the previous frame's blending rect. All else is
+      // forgotten due to us restoring the same frame again.
+      mRestoreDirtyRect = aPreviousFrame->GetBoundedBlendRect();
     }
   }
 
   RawAccessFrameRef ref;
 
   // If we have a frame recycler, it must be for an animated image producing
   // full frames. If the higher layers are discarding frames because of the
   // memory footprint, then the recycler will allow us to reuse the buffers.
   // Each frame should be the same size and have mostly the same properties.
   if (mFrameRecycler) {
-    MOZ_ASSERT(ShouldBlendAnimation());
-    MOZ_ASSERT(aPaletteDepth == 0);
     MOZ_ASSERT(aAnimParams);
-    MOZ_ASSERT(aFrameRect.IsEqualEdges(IntRect(IntPoint(0, 0), aOutputSize)));
 
     ref = mFrameRecycler->RecycleFrame(mRecycleRect);
     if (ref) {
       // If the recycled frame is actually the current restore frame, we cannot
       // use it. If the next restore frame is the new frame we are creating, in
       // theory we could reuse it, but we would need to store the restore frame
       // animation parameters elsewhere. For now we just drop it.
       bool blocked = ref.get() == mRestoreFrame.get();
@@ -363,19 +350,18 @@ RawAccessFrameRef Decoder::AllocateFrame
   // Produce a new frame to store the data.
   if (!ref) {
     // There is no underlying data to reuse, so reset the recycle rect to be
     // the full frame, to ensure the restore frame is fully copied.
     mRecycleRect = IntRect(IntPoint(0, 0), aOutputSize);
 
     bool nonPremult = bool(mSurfaceFlags & SurfaceFlags::NO_PREMULTIPLY_ALPHA);
     auto frame = MakeNotNull<RefPtr<imgFrame>>();
-    if (NS_FAILED(frame->InitForDecoder(
-            aOutputSize, aFrameRect, aFormat, aPaletteDepth, nonPremult,
-            aAnimParams, ShouldBlendAnimation(), bool(mFrameRecycler)))) {
+    if (NS_FAILED(frame->InitForDecoder(aOutputSize, aFormat, nonPremult,
+                                        aAnimParams, bool(mFrameRecycler)))) {
       NS_WARNING("imgFrame::Init should succeed");
       return RawAccessFrameRef();
     }
 
     ref = frame->RawAccessRef();
     if (!ref) {
       frame->Abort();
       return RawAccessFrameRef();
--- a/image/Decoder.h
+++ b/image/Decoder.h
@@ -267,24 +267,16 @@ class Decoder {
   /**
    * Should we stop decoding after the first frame?
    */
   bool IsFirstFrameDecode() const {
     return bool(mDecoderFlags & DecoderFlags::FIRST_FRAME_ONLY);
   }
 
   /**
-   * Should blend the current frame with the previous frames to produce a
-   * complete frame instead of a partial frame for animated images.
-   */
-  bool ShouldBlendAnimation() const {
-    return bool(mDecoderFlags & DecoderFlags::BLEND_ANIMATION);
-  }
-
-  /**
    * @return the number of complete animation frames which have been decoded so
    * far, if it has changed since the last call to TakeCompleteFrameCount();
    * otherwise, returns Nothing().
    */
   Maybe<uint32_t> TakeCompleteFrameCount();
 
   // The number of frames we have, including anything in-progress. Thus, this
   // is only 0 if we haven't begun any frames.
@@ -413,30 +405,21 @@ class Decoder {
    * current frame we are producing for its animation parameters.
    */
   imgFrame* GetCurrentFrame() { return mCurrentFrame.get(); }
 
   /**
    * For use during decoding only. Allows the BlendAnimationFilter to get the
    * frame it should be pulling the previous frame data from.
    */
-  const RawAccessFrameRef& GetRestoreFrameRef() const {
-    MOZ_ASSERT(ShouldBlendAnimation());
-    return mRestoreFrame;
-  }
+  const RawAccessFrameRef& GetRestoreFrameRef() const { return mRestoreFrame; }
 
-  const gfx::IntRect& GetRestoreDirtyRect() const {
-    MOZ_ASSERT(ShouldBlendAnimation());
-    return mRestoreDirtyRect;
-  }
+  const gfx::IntRect& GetRestoreDirtyRect() const { return mRestoreDirtyRect; }
 
-  const gfx::IntRect& GetRecycleRect() const {
-    MOZ_ASSERT(ShouldBlendAnimation());
-    return mRecycleRect;
-  }
+  const gfx::IntRect& GetRecycleRect() const { return mRecycleRect; }
 
   const gfx::IntRect& GetFirstFrameRefreshArea() const {
     return mFirstFrameRefreshArea;
   }
 
   bool HasFrameToTake() const { return mHasFrameToTake; }
   void ClearHasFrameToTake() {
     MOZ_ASSERT(mHasFrameToTake);
@@ -537,22 +520,19 @@ class Decoder {
   // May not be called mid-frame.
   //
   // For animated images, specify the loop count. -1 means loop forever, 0
   // means a single iteration, stopping on the last frame.
   void PostDecodeDone(int32_t aLoopCount = 0);
 
   /**
    * Allocates a new frame, making it our current frame if successful.
-   *
-   * If a non-paletted frame is desired, pass 0 for aPaletteDepth.
    */
   nsresult AllocateFrame(const gfx::IntSize& aOutputSize,
-                         const gfx::IntRect& aFrameRect,
-                         gfx::SurfaceFormat aFormat, uint8_t aPaletteDepth = 0,
+                         gfx::SurfaceFormat aFormat,
                          const Maybe<AnimationParams>& aAnimParams = Nothing());
 
  private:
   /// Report that an error was encountered while decoding.
   void PostError();
 
   /**
    * CompleteDecode() finishes up the decoding process after Decode() determines
@@ -567,28 +547,25 @@ class Decoder {
     if (mFrameCount == 0) {
       return 0;
     }
 
     return mInFrame ? mFrameCount - 1 : mFrameCount;
   }
 
   RawAccessFrameRef AllocateFrameInternal(
-      const gfx::IntSize& aOutputSize, const gfx::IntRect& aFrameRect,
-      gfx::SurfaceFormat aFormat, uint8_t aPaletteDepth,
+      const gfx::IntSize& aOutputSize, gfx::SurfaceFormat aFormat,
       const Maybe<AnimationParams>& aAnimParams,
       RawAccessFrameRef&& aPreviousFrame);
 
  protected:
   Maybe<Downscaler> mDownscaler;
 
-  uint8_t* mImageData;  // Pointer to image data in either Cairo or 8bit format
+  uint8_t* mImageData;  // Pointer to image data in BGRA/X
   uint32_t mImageDataLength;
-  uint32_t* mColormap;  // Current colormap to be used in Cairo format
-  uint32_t mColormapSize;
 
  private:
   RefPtr<RasterImage> mImage;
   Maybe<SourceBufferIterator> mIterator;
   IDecoderFrameRecycler* mFrameRecycler;
 
   // The current frame the decoder is producing.
   RawAccessFrameRef mCurrentFrame;
--- a/image/DecoderFlags.h
+++ b/image/DecoderFlags.h
@@ -26,24 +26,16 @@ enum class DecoderFlags : uint8_t {
 
   /**
    * By default, a surface is considered substitutable. That means callers are
    * willing to accept a less than ideal match to display. If a caller requires
    * a specific size and won't accept alternatives, then this flag should be
    * set.
    */
   CANNOT_SUBSTITUTE = 1 << 4,
-
-  /**
-   * By default, an animation decoder will produce partial frames that need to
-   * be combined with the previously displayed/composited frame by FrameAnimator
-   * to produce a complete frame. If this flag is set, the decoder will perform
-   * this blending at decode time, and the frames produced are complete.
-   */
-  BLEND_ANIMATION = 1 << 5
 };
 MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(DecoderFlags)
 
 /**
  * @return the default set of decode flags.
  */
 inline DecoderFlags DefaultDecoderFlags() { return DecoderFlags(); }
 
--- a/image/DownscalingFilter.h
+++ b/image/DownscalingFilter.h
@@ -107,20 +107,16 @@ class DownscalingFilter final : public S
 
   template <typename... Rest>
   nsresult Configure(const DownscalingConfig& aConfig, const Rest&... aRest) {
     nsresult rv = mNext.Configure(aRest...);
     if (NS_FAILED(rv)) {
       return rv;
     }
 
-    if (mNext.IsValidPalettedPipe()) {
-      NS_WARNING("Created a downscaler for a paletted surface?");
-      return NS_ERROR_INVALID_ARG;
-    }
     if (mNext.InputSize() == aConfig.mInputSize) {
       NS_WARNING("Created a downscaler, but not downscaling?");
       return NS_ERROR_INVALID_ARG;
     }
     if (mNext.InputSize().width > aConfig.mInputSize.width) {
       NS_WARNING("Created a downscaler, but width is larger");
       return NS_ERROR_INVALID_ARG;
     }
--- a/image/FrameAnimator.cpp
+++ b/image/FrameAnimator.cpp
@@ -1,27 +1,22 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
  * This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "FrameAnimator.h"
 
-#include "mozilla/MemoryReporting.h"
 #include "mozilla/Move.h"
 #include "mozilla/CheckedInt.h"
 #include "imgIContainer.h"
 #include "LookupResult.h"
-#include "MainThreadUtils.h"
 #include "RasterImage.h"
 #include "gfxPrefs.h"
 
-#include "pixman.h"
-#include <algorithm>
-
 namespace mozilla {
 
 using namespace gfx;
 
 namespace image {
 
 ///////////////////////////////////////////////////////////////////////////////
 // AnimationState implementation.
@@ -274,40 +269,17 @@ RefreshResult FrameAnimator::AdvanceFram
     return ret;
   }
 
   if (nextFrame->GetTimeout() == FrameTimeout::Forever()) {
     ret.mAnimationFinished = true;
   }
 
   if (nextFrameIndex == 0) {
-    MOZ_ASSERT(nextFrame->IsFullFrame());
     ret.mDirtyRect = aState.FirstFrameRefreshArea();
-  } else if (!nextFrame->IsFullFrame()) {
-    MOZ_ASSERT(nextFrameIndex == currentFrameIndex + 1);
-    RawAccessFrameRef currentRef =
-        aCurrentFrame->RawAccessRef(/* aFinished */ true);
-    RawAccessFrameRef nextRef = nextFrame->RawAccessRef(/* aFinished */ true);
-
-    // Change frame
-    if (!DoBlend(currentRef, nextRef, nextFrameIndex, &ret.mDirtyRect)) {
-      // something went wrong, move on to next
-      NS_WARNING("FrameAnimator::AdvanceFrame(): Compositing of frame failed");
-      nextFrame->SetCompositingFailed(true);
-      aState.mCurrentAnimationFrameTime =
-          GetCurrentImgFrameEndTime(aState, aCurrentFrame->GetTimeout());
-      aState.mCurrentAnimationFrameIndex = nextFrameIndex;
-      aState.mCompositedFrameRequested = false;
-      aCurrentFrame = std::move(nextFrame);
-      aFrames.Advance(nextFrameIndex);
-
-      return ret;
-    }
-
-    nextFrame->SetCompositingFailed(false);
   } else {
     ret.mDirtyRect = nextFrame->GetDirtyRect();
   }
 
   aState.mCurrentAnimationFrameTime =
       GetCurrentImgFrameEndTime(aState, aCurrentFrame->GetTimeout());
 
   // If we can get closer to the current time by a multiple of the image's loop
@@ -454,39 +426,30 @@ RefreshResult FrameAnimator::RequestRefr
   // composited frame was previously invalid (so we may need to repaint
   // everything) and either the frame index is valid (to know we were doing
   // blending on the main thread, instead of on the decoder threads in advance),
   // or the current frame is a full frame (blends off the main thread).
   //
   // If for some reason we forget to reset aState.mCompositedFrameInvalid, then
   // GetCompositedFrame will fail, even if we have all the data available for
   // display.
-  if (currentFrameEndTime > aTime && aState.mCompositedFrameInvalid &&
-      (mLastCompositedFrameIndex >= 0 || currentFrame->IsFullFrame())) {
+  if (currentFrameEndTime > aTime && aState.mCompositedFrameInvalid) {
     aState.mCompositedFrameInvalid = false;
     ret.mDirtyRect = IntRect(IntPoint(0, 0), mSize);
   }
 
   MOZ_ASSERT(!aState.mIsCurrentlyDecoded || !aState.mCompositedFrameInvalid);
 
   return ret;
 }
 
 LookupResult FrameAnimator::GetCompositedFrame(AnimationState& aState,
                                                bool aMarkUsed) {
   aState.mCompositedFrameRequested = true;
 
-  // If we have a composited version of this frame, return that.
-  if (!aState.mCompositedFrameInvalid && mLastCompositedFrameIndex >= 0 &&
-      (uint32_t(mLastCompositedFrameIndex) ==
-       aState.mCurrentAnimationFrameIndex)) {
-    return LookupResult(DrawableSurface(mCompositingFrame->DrawableRef()),
-                        MatchType::EXACT);
-  }
-
   LookupResult result = SurfaceCache::Lookup(
       ImageKey(mImage),
       RasterSurfaceKey(mSize, DefaultSurfaceFlags(), PlaybackType::eAnimated),
       aMarkUsed);
 
   if (aState.mCompositedFrameInvalid) {
     MOZ_ASSERT(gfxPrefs::ImageMemAnimatedDiscardable());
     MOZ_ASSERT(aState.GetHasRequestedDecode());
@@ -507,481 +470,13 @@ LookupResult FrameAnimator::GetComposite
   // get the frame we're looking for; treat this as if the lookup failed.
   if (NS_FAILED(result.Surface().Seek(aState.mCurrentAnimationFrameIndex))) {
     if (result.Type() == MatchType::NOT_FOUND) {
       return result;
     }
     return LookupResult(MatchType::PENDING);
   }
 
-  MOZ_ASSERT(!result.Surface()->GetIsPaletted(),
-             "About to return a paletted frame");
-
   return result;
 }
 
-static void DoCollectSizeOfCompositingSurfaces(
-    const RawAccessFrameRef& aSurface, SurfaceMemoryCounterType aType,
-    nsTArray<SurfaceMemoryCounter>& aCounters, MallocSizeOf aMallocSizeOf) {
-  // Concoct a SurfaceKey for this surface.
-  SurfaceKey key = RasterSurfaceKey(
-      aSurface->GetImageSize(), DefaultSurfaceFlags(), PlaybackType::eStatic);
-
-  // Extract the surface's memory usage information.
-  aSurface->AddSizeOfExcludingThis(
-      aMallocSizeOf, [&](imgFrame::AddSizeOfCbData& aMetadata) {
-        // Create a counter for this surface.
-        SurfaceMemoryCounter counter(key, /* aIsLocked = */ true,
-                                     /* aCannotSubstitute */ false,
-                                     /* aIsFactor2 */ false, aType);
-
-        // Record it.
-        counter.Values().SetDecodedHeap(aMetadata.heap);
-        counter.Values().SetDecodedNonHeap(aMetadata.nonHeap);
-        counter.Values().SetExternalHandles(aMetadata.handles);
-        counter.Values().SetFrameIndex(aMetadata.index);
-        counter.Values().SetExternalId(aMetadata.externalId);
-
-        aCounters.AppendElement(counter);
-      });
-}
-
-void FrameAnimator::CollectSizeOfCompositingSurfaces(
-    nsTArray<SurfaceMemoryCounter>& aCounters,
-    MallocSizeOf aMallocSizeOf) const {
-  if (mCompositingFrame) {
-    DoCollectSizeOfCompositingSurfaces(mCompositingFrame,
-                                       SurfaceMemoryCounterType::COMPOSITING,
-                                       aCounters, aMallocSizeOf);
-  }
-
-  if (mCompositingPrevFrame) {
-    DoCollectSizeOfCompositingSurfaces(
-        mCompositingPrevFrame, SurfaceMemoryCounterType::COMPOSITING_PREV,
-        aCounters, aMallocSizeOf);
-  }
-}
-
-//******************************************************************************
-// DoBlend gets called when the timer for animation get fired and we have to
-// update the composited frame of the animation.
-bool FrameAnimator::DoBlend(const RawAccessFrameRef& aPrevFrame,
-                            const RawAccessFrameRef& aNextFrame,
-                            uint32_t aNextFrameIndex, IntRect* aDirtyRect) {
-  if (!aPrevFrame || !aNextFrame) {
-    MOZ_ASSERT_UNREACHABLE("Should have RawAccessFrameRefs to blend!");
-    return false;
-  }
-
-  DisposalMethod prevDisposalMethod = aPrevFrame->GetDisposalMethod();
-  bool prevHasAlpha = aPrevFrame->FormatHasAlpha();
-  if (prevDisposalMethod == DisposalMethod::RESTORE_PREVIOUS &&
-      !mCompositingPrevFrame) {
-    prevDisposalMethod = DisposalMethod::CLEAR;
-  }
-
-  IntRect prevRect = aPrevFrame->GetBoundedBlendRect();
-  bool isFullPrevFrame = prevRect.IsEqualRect(0, 0, mSize.width, mSize.height);
-
-  // Optimization: DisposeClearAll if the previous frame is the same size as
-  //               container and it's clearing itself
-  if (isFullPrevFrame && (prevDisposalMethod == DisposalMethod::CLEAR)) {
-    prevDisposalMethod = DisposalMethod::CLEAR_ALL;
-  }
-
-  DisposalMethod nextDisposalMethod = aNextFrame->GetDisposalMethod();
-  bool nextHasAlpha = aNextFrame->FormatHasAlpha();
-
-  IntRect nextRect = aNextFrame->GetBoundedBlendRect();
-  bool isFullNextFrame = nextRect.IsEqualRect(0, 0, mSize.width, mSize.height);
-
-  if (!aNextFrame->GetIsPaletted()) {
-    // Optimization: Skip compositing if the previous frame wants to clear the
-    //               whole image
-    if (prevDisposalMethod == DisposalMethod::CLEAR_ALL) {
-      aDirtyRect->SetRect(0, 0, mSize.width, mSize.height);
-      return true;
-    }
-
-    // Optimization: Skip compositing if this frame is the same size as the
-    //               container and it's fully drawing over prev frame (no alpha)
-    if (isFullNextFrame &&
-        (nextDisposalMethod != DisposalMethod::RESTORE_PREVIOUS) &&
-        !nextHasAlpha) {
-      aDirtyRect->SetRect(0, 0, mSize.width, mSize.height);
-      return true;
-    }
-  }
-
-  // Calculate area that needs updating
-  switch (prevDisposalMethod) {
-    default:
-      MOZ_FALLTHROUGH_ASSERT("Unexpected DisposalMethod");
-    case DisposalMethod::NOT_SPECIFIED:
-    case DisposalMethod::KEEP:
-      *aDirtyRect = nextRect;
-      break;
-
-    case DisposalMethod::CLEAR_ALL:
-      // Whole image container is cleared
-      aDirtyRect->SetRect(0, 0, mSize.width, mSize.height);
-      break;
-
-    case DisposalMethod::CLEAR:
-      // Calc area that needs to be redrawn (the combination of previous and
-      // this frame)
-      // XXX - This could be done with multiple framechanged calls
-      //       Having aPrevFrame way at the top of the image, and aNextFrame
-      //       way at the bottom, and both frames being small, we'd be
-      //       telling framechanged to refresh the whole image when only two
-      //       small areas are needed.
-      aDirtyRect->UnionRect(nextRect, prevRect);
-      break;
-
-    case DisposalMethod::RESTORE_PREVIOUS:
-      aDirtyRect->SetRect(0, 0, mSize.width, mSize.height);
-      break;
-  }
-
-  // Optimization:
-  //   Skip compositing if the last composited frame is this frame
-  //   (Only one composited frame was made for this animation.  Example:
-  //    Only Frame 3 of a 10 frame image required us to build a composite frame
-  //    On the second loop, we do not need to rebuild the frame
-  //    since it's still sitting in compositingFrame)
-  if (mLastCompositedFrameIndex == int32_t(aNextFrameIndex)) {
-    return true;
-  }
-
-  bool needToBlankComposite = false;
-
-  // Create the Compositing Frame
-  if (!mCompositingFrame) {
-    RefPtr<imgFrame> newFrame = new imgFrame;
-    nsresult rv = newFrame->InitForAnimator(mSize, SurfaceFormat::B8G8R8A8);
-    if (NS_FAILED(rv)) {
-      mCompositingFrame.reset();
-      return false;
-    }
-    mCompositingFrame = newFrame->RawAccessRef();
-    needToBlankComposite = true;
-  } else if (int32_t(aNextFrameIndex) != mLastCompositedFrameIndex + 1) {
-    // If we are not drawing on top of last composited frame,
-    // then we are building a new composite frame, so let's clear it first.
-    needToBlankComposite = true;
-  }
-
-  // More optimizations possible when next frame is not transparent
-  // But if the next frame has DisposalMethod::RESTORE_PREVIOUS,
-  // this "no disposal" optimization is not possible,
-  // because the frame in "after disposal operation" state
-  // needs to be stored in compositingFrame, so it can be
-  // copied into compositingPrevFrame later.
-  bool doDisposal = true;
-  if (!nextHasAlpha && nextDisposalMethod != DisposalMethod::RESTORE_PREVIOUS) {
-    if (isFullNextFrame) {
-      // Optimization: No need to dispose prev.frame when
-      // next frame is full frame and not transparent.
-      doDisposal = false;
-      // No need to blank the composite frame
-      needToBlankComposite = false;
-    } else {
-      if ((prevRect.X() >= nextRect.X()) && (prevRect.Y() >= nextRect.Y()) &&
-          (prevRect.XMost() <= nextRect.XMost()) &&
-          (prevRect.YMost() <= nextRect.YMost())) {
-        // Optimization: No need to dispose prev.frame when
-        // next frame fully overlaps previous frame.
-        doDisposal = false;
-      }
-    }
-  }
-
-  if (doDisposal) {
-    // Dispose of previous: clear, restore, or keep (copy)
-    switch (prevDisposalMethod) {
-      case DisposalMethod::CLEAR:
-        if (needToBlankComposite) {
-          // If we just created the composite, it could have anything in its
-          // buffer. Clear whole frame
-          ClearFrame(mCompositingFrame.Data(), mCompositingFrame->GetRect());
-        } else {
-          // Only blank out previous frame area (both color & Mask/Alpha)
-          ClearFrame(mCompositingFrame.Data(), mCompositingFrame->GetRect(),
-                     prevRect);
-        }
-        break;
-
-      case DisposalMethod::CLEAR_ALL:
-        ClearFrame(mCompositingFrame.Data(), mCompositingFrame->GetRect());
-        break;
-
-      case DisposalMethod::RESTORE_PREVIOUS:
-        // It would be better to copy only the area changed back to
-        // compositingFrame.
-        if (mCompositingPrevFrame) {
-          CopyFrameImage(
-              mCompositingPrevFrame.Data(), mCompositingPrevFrame->GetRect(),
-              mCompositingFrame.Data(), mCompositingFrame->GetRect());
-
-          // destroy only if we don't need it for this frame's disposal
-          if (nextDisposalMethod != DisposalMethod::RESTORE_PREVIOUS) {
-            mCompositingPrevFrame.reset();
-          }
-        } else {
-          ClearFrame(mCompositingFrame.Data(), mCompositingFrame->GetRect());
-        }
-        break;
-
-      default:
-        MOZ_FALLTHROUGH_ASSERT("Unexpected DisposalMethod");
-      case DisposalMethod::NOT_SPECIFIED:
-      case DisposalMethod::KEEP:
-        // Copy previous frame into compositingFrame before we put the new
-        // frame on top
-        // Assumes that the previous frame represents a full frame (it could be
-        // smaller in size than the container, as long as the frame before it
-        // erased itself)
-        // Note: Frame 1 never gets into DoBlend(), so (aNextFrameIndex - 1)
-        // will always be a valid frame number.
-        if (mLastCompositedFrameIndex != int32_t(aNextFrameIndex - 1)) {
-          if (isFullPrevFrame && !aPrevFrame->GetIsPaletted()) {
-            // Just copy the bits
-            CopyFrameImage(aPrevFrame.Data(), prevRect,
-                           mCompositingFrame.Data(),
-                           mCompositingFrame->GetRect());
-          } else {
-            if (needToBlankComposite) {
-              // Only blank composite when prev is transparent or not full.
-              if (prevHasAlpha || !isFullPrevFrame) {
-                ClearFrame(mCompositingFrame.Data(),
-                           mCompositingFrame->GetRect());
-              }
-            }
-            DrawFrameTo(aPrevFrame.Data(), aPrevFrame->GetRect(),
-                        aPrevFrame.PaletteDataLength(), prevHasAlpha,
-                        mCompositingFrame.Data(), mCompositingFrame->GetRect(),
-                        aPrevFrame->GetBlendMethod(),
-                        aPrevFrame->GetBlendRect());
-          }
-        }
-    }
-  } else if (needToBlankComposite) {
-    // If we just created the composite, it could have anything in its
-    // buffers. Clear them
-    ClearFrame(mCompositingFrame.Data(), mCompositingFrame->GetRect());
-  }
-
-  // Check if the frame we are composing wants the previous image restored after
-  // it is done. Don't store it (again) if last frame wanted its image restored
-  // too
-  if ((nextDisposalMethod == DisposalMethod::RESTORE_PREVIOUS) &&
-      (prevDisposalMethod != DisposalMethod::RESTORE_PREVIOUS)) {
-    // We are storing the whole image.
-    // It would be better if we just stored the area that aNextFrame is going to
-    // overwrite.
-    if (!mCompositingPrevFrame) {
-      RefPtr<imgFrame> newFrame = new imgFrame;
-      nsresult rv = newFrame->InitForAnimator(mSize, SurfaceFormat::B8G8R8A8);
-      if (NS_FAILED(rv)) {
-        mCompositingPrevFrame.reset();
-        return false;
-      }
-
-      mCompositingPrevFrame = newFrame->RawAccessRef();
-    }
-
-    CopyFrameImage(mCompositingFrame.Data(), mCompositingFrame->GetRect(),
-                   mCompositingPrevFrame.Data(),
-                   mCompositingPrevFrame->GetRect());
-
-    mCompositingPrevFrame->Finish();
-  }
-
-  // blit next frame into it's correct spot
-  DrawFrameTo(aNextFrame.Data(), aNextFrame->GetRect(),
-              aNextFrame.PaletteDataLength(), nextHasAlpha,
-              mCompositingFrame.Data(), mCompositingFrame->GetRect(),
-              aNextFrame->GetBlendMethod(), aNextFrame->GetBlendRect());
-
-  // Tell the image that it is fully 'downloaded'.
-  mCompositingFrame->Finish();
-
-  mLastCompositedFrameIndex = int32_t(aNextFrameIndex);
-
-  return true;
-}
-
-//******************************************************************************
-// Fill aFrame with black. Does also clears the mask.
-void FrameAnimator::ClearFrame(uint8_t* aFrameData, const IntRect& aFrameRect) {
-  if (!aFrameData) {
-    return;
-  }
-
-  memset(aFrameData, 0, aFrameRect.Width() * aFrameRect.Height() * 4);
-}
-
-//******************************************************************************
-void FrameAnimator::ClearFrame(uint8_t* aFrameData, const IntRect& aFrameRect,
-                               const IntRect& aRectToClear) {
-  if (!aFrameData || aFrameRect.Width() <= 0 || aFrameRect.Height() <= 0 ||
-      aRectToClear.Width() <= 0 || aRectToClear.Height() <= 0) {
-    return;
-  }
-
-  IntRect toClear = aFrameRect.Intersect(aRectToClear);
-  if (toClear.IsEmpty()) {
-    return;
-  }
-
-  uint32_t bytesPerRow = aFrameRect.Width() * 4;
-  for (int row = toClear.Y(); row < toClear.YMost(); ++row) {
-    memset(aFrameData + toClear.X() * 4 + row * bytesPerRow, 0,
-           toClear.Width() * 4);
-  }
-}
-
-//******************************************************************************
-// Whether we succeed or fail will not cause a crash, and there's not much
-// we can do about a failure, so there we don't return a nsresult
-bool FrameAnimator::CopyFrameImage(const uint8_t* aDataSrc,
-                                   const IntRect& aRectSrc, uint8_t* aDataDest,
-                                   const IntRect& aRectDest) {
-  uint32_t dataLengthSrc = aRectSrc.Width() * aRectSrc.Height() * 4;
-  uint32_t dataLengthDest = aRectDest.Width() * aRectDest.Height() * 4;
-
-  if (!aDataDest || !aDataSrc || dataLengthSrc != dataLengthDest) {
-    return false;
-  }
-
-  memcpy(aDataDest, aDataSrc, dataLengthDest);
-
-  return true;
-}
-
-nsresult FrameAnimator::DrawFrameTo(const uint8_t* aSrcData,
-                                    const IntRect& aSrcRect,
-                                    uint32_t aSrcPaletteLength,
-                                    bool aSrcHasAlpha, uint8_t* aDstPixels,
-                                    const IntRect& aDstRect,
-                                    BlendMethod aBlendMethod,
-                                    const IntRect& aBlendRect) {
-  NS_ENSURE_ARG_POINTER(aSrcData);
-  NS_ENSURE_ARG_POINTER(aDstPixels);
-
-  // According to both AGIF and APNG specs, offsets are unsigned
-  if (aSrcRect.X() < 0 || aSrcRect.Y() < 0) {
-    NS_WARNING("FrameAnimator::DrawFrameTo: negative offsets not allowed");
-    return NS_ERROR_FAILURE;
-  }
-
-  // Outside the destination frame, skip it
-  if ((aSrcRect.X() > aDstRect.Width()) || (aSrcRect.Y() > aDstRect.Height())) {
-    return NS_OK;
-  }
-
-  if (aSrcPaletteLength) {
-    // Larger than the destination frame, clip it
-    int32_t width = std::min(aSrcRect.Width(), aDstRect.Width() - aSrcRect.X());
-    int32_t height =
-        std::min(aSrcRect.Height(), aDstRect.Height() - aSrcRect.Y());
-
-    // The clipped image must now fully fit within destination image frame
-    NS_ASSERTION((aSrcRect.X() >= 0) && (aSrcRect.Y() >= 0) &&
-                     (aSrcRect.X() + width <= aDstRect.Width()) &&
-                     (aSrcRect.Y() + height <= aDstRect.Height()),
-                 "FrameAnimator::DrawFrameTo: Invalid aSrcRect");
-
-    // clipped image size may be smaller than source, but not larger
-    NS_ASSERTION(
-        (width <= aSrcRect.Width()) && (height <= aSrcRect.Height()),
-        "FrameAnimator::DrawFrameTo: source must be smaller than dest");
-
-    // Get pointers to image data
-    const uint8_t* srcPixels = aSrcData + aSrcPaletteLength;
-    uint32_t* dstPixels = reinterpret_cast<uint32_t*>(aDstPixels);
-    const uint32_t* colormap = reinterpret_cast<const uint32_t*>(aSrcData);
-
-    // Skip to the right offset
-    dstPixels += aSrcRect.X() + (aSrcRect.Y() * aDstRect.Width());
-    if (!aSrcHasAlpha) {
-      for (int32_t r = height; r > 0; --r) {
-        for (int32_t c = 0; c < width; c++) {
-          dstPixels[c] = colormap[srcPixels[c]];
-        }
-        // Go to the next row in the source resp. destination image
-        srcPixels += aSrcRect.Width();
-        dstPixels += aDstRect.Width();
-      }
-    } else {
-      for (int32_t r = height; r > 0; --r) {
-        for (int32_t c = 0; c < width; c++) {
-          const uint32_t color = colormap[srcPixels[c]];
-          if (color) {
-            dstPixels[c] = color;
-          }
-        }
-        // Go to the next row in the source resp. destination image
-        srcPixels += aSrcRect.Width();
-        dstPixels += aDstRect.Width();
-      }
-    }
-  } else {
-    pixman_image_t* src = pixman_image_create_bits(
-        aSrcHasAlpha ? PIXMAN_a8r8g8b8 : PIXMAN_x8r8g8b8, aSrcRect.Width(),
-        aSrcRect.Height(),
-        reinterpret_cast<uint32_t*>(const_cast<uint8_t*>(aSrcData)),
-        aSrcRect.Width() * 4);
-    if (!src) {
-      return NS_ERROR_OUT_OF_MEMORY;
-    }
-    pixman_image_t* dst = pixman_image_create_bits(
-        PIXMAN_a8r8g8b8, aDstRect.Width(), aDstRect.Height(),
-        reinterpret_cast<uint32_t*>(aDstPixels), aDstRect.Width() * 4);
-    if (!dst) {
-      pixman_image_unref(src);
-      return NS_ERROR_OUT_OF_MEMORY;
-    }
-
-    // XXX(seth): This is inefficient but we'll remove it quite soon when we
-    // move frame compositing into SurfacePipe. For now we need this because
-    // RemoveFrameRectFilter has transformed PNG frames with frame rects into
-    // imgFrame's with no frame rects, but with a region of 0 alpha where the
-    // frame rect should be. This works really nicely if we're using
-    // BlendMethod::OVER, but BlendMethod::SOURCE will result in that frame rect
-    // area overwriting the previous frame, which makes the animation look
-    // wrong. This quick hack fixes that by first compositing the whle new frame
-    // with BlendMethod::OVER, and then recopying the area that uses
-    // BlendMethod::SOURCE if needed. To make this work, the decoder has to
-    // provide a "blend rect" that tells us where to do this. This is just the
-    // frame rect, but hidden in a way that makes it invisible to most of the
-    // system, so we can keep eliminating dependencies on it.
-    auto op =
-        aBlendMethod == BlendMethod::SOURCE ? PIXMAN_OP_SRC : PIXMAN_OP_OVER;
-
-    if (aBlendMethod == BlendMethod::OVER ||
-        (aBlendMethod == BlendMethod::SOURCE &&
-         aSrcRect.IsEqualEdges(aBlendRect))) {
-      // We don't need to do anything clever. (Or, in the case where no blend
-      // rect was specified, we can't.)
-      pixman_image_composite32(op, src, nullptr, dst, 0, 0, 0, 0, aSrcRect.X(),
-                               aSrcRect.Y(), aSrcRect.Width(),
-                               aSrcRect.Height());
-    } else {
-      // We need to do the OVER followed by SOURCE trick above.
-      pixman_image_composite32(PIXMAN_OP_OVER, src, nullptr, dst, 0, 0, 0, 0,
-                               aSrcRect.X(), aSrcRect.Y(), aSrcRect.Width(),
-                               aSrcRect.Height());
-      pixman_image_composite32(PIXMAN_OP_SRC, src, nullptr, dst, aBlendRect.X(),
-                               aBlendRect.Y(), 0, 0, aBlendRect.X(),
-                               aBlendRect.Y(), aBlendRect.Width(),
-                               aBlendRect.Height());
-    }
-
-    pixman_image_unref(src);
-    pixman_image_unref(dst);
-  }
-
-  return NS_OK;
-}
-
 }  // namespace image
 }  // namespace mozilla
--- a/image/FrameAnimator.h
+++ b/image/FrameAnimator.h
@@ -3,17 +3,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/. */
 
 #ifndef mozilla_image_FrameAnimator_h
 #define mozilla_image_FrameAnimator_h
 
 #include "mozilla/Maybe.h"
-#include "mozilla/MemoryReporting.h"
 #include "mozilla/TimeStamp.h"
 #include "gfxTypes.h"
 #include "imgFrame.h"
 #include "nsCOMPtr.h"
 #include "nsRect.h"
 #include "SurfaceCache.h"
 #include "gfxPrefs.h"
 
@@ -274,17 +273,17 @@ struct RefreshResult {
 
   // Whether the animation has finished playing.
   bool mAnimationFinished : 1;
 };
 
 class FrameAnimator {
  public:
   FrameAnimator(RasterImage* aImage, const gfx::IntSize& aSize)
-      : mImage(aImage), mSize(aSize), mLastCompositedFrameIndex(-1) {
+      : mImage(aImage), mSize(aSize) {
     MOZ_COUNT_CTOR(FrameAnimator);
   }
 
   ~FrameAnimator() { MOZ_COUNT_DTOR(FrameAnimator); }
 
   /**
    * Call when you need to re-start animating. Ensures we start from the first
    * frame.
@@ -303,25 +302,16 @@ class FrameAnimator {
 
   /**
    * Get the full frame for the current frame of the animation (it may or may
    * not have required compositing). It may not be available because it hasn't
    * been decoded yet, in which case we return an empty LookupResult.
    */
   LookupResult GetCompositedFrame(AnimationState& aState, bool aMarkUsed);
 
-  /**
-   * Collect an accounting of the memory occupied by the compositing surfaces we
-   * use during animation playback. All of the actual animation frames are
-   * stored in the SurfaceCache, so we don't need to report them here.
-   */
-  void CollectSizeOfCompositingSurfaces(
-      nsTArray<SurfaceMemoryCounter>& aCounters,
-      MallocSizeOf aMallocSizeOf) const;
-
  private:  // methods
   /**
    * Advances the animation. Typically, this will advance a single frame, but it
    * may advance multiple frames. This may happen if we have infrequently
    * "ticking" refresh drivers (e.g. in background tabs), or extremely short-
    * lived animation frames.
    *
    * @param aTime the time that the animation should advance to. This will
@@ -341,86 +331,20 @@ class FrameAnimator {
    * Get the time the frame we're currently displaying is supposed to end.
    *
    * In the error case (like if the requested frame is not currently
    * decoded), returns None().
    */
   TimeStamp GetCurrentImgFrameEndTime(AnimationState& aState,
                                       FrameTimeout aCurrentTimeout) const;
 
-  bool DoBlend(const RawAccessFrameRef& aPrevFrame,
-               const RawAccessFrameRef& aNextFrame, uint32_t aNextFrameIndex,
-               gfx::IntRect* aDirtyRect);
-
-  /** Clears an area of <aFrame> with transparent black.
-   *
-   * @param aFrameData Target Frame data
-   * @param aFrameRect The rectangle of the data pointed ot by aFrameData
-   *
-   * @note Does also clears the transparency mask
-   */
-  static void ClearFrame(uint8_t* aFrameData, const gfx::IntRect& aFrameRect);
-
-  //! @overload
-  static void ClearFrame(uint8_t* aFrameData, const gfx::IntRect& aFrameRect,
-                         const gfx::IntRect& aRectToClear);
-
-  //! Copy one frame's image and mask into another
-  static bool CopyFrameImage(const uint8_t* aDataSrc,
-                             const gfx::IntRect& aRectSrc, uint8_t* aDataDest,
-                             const gfx::IntRect& aRectDest);
-
-  /**
-   * Draws one frame's image to into another, at the position specified by
-   * aSrcRect.
-   *
-   * @aSrcData the raw data of the current frame being drawn
-   * @aSrcRect the size of the source frame, and the position of that frame in
-   *           the composition frame
-   * @aSrcPaletteLength the length (in bytes) of the palette at the beginning
-   *                    of the source data (0 if image is not paletted)
-   * @aSrcHasAlpha whether the source data represents an image with alpha
-   * @aDstPixels the raw data of the composition frame where the current frame
-   *             is drawn into (32-bit ARGB)
-   * @aDstRect the size of the composition frame
-   * @aBlendMethod the blend method for how to blend src on the composition
-   * frame.
-   */
-  static nsresult DrawFrameTo(const uint8_t* aSrcData,
-                              const gfx::IntRect& aSrcRect,
-                              uint32_t aSrcPaletteLength, bool aSrcHasAlpha,
-                              uint8_t* aDstPixels, const gfx::IntRect& aDstRect,
-                              BlendMethod aBlendMethod,
-                              const gfx::IntRect& aBlendRect);
-
  private:  // data
   //! A weak pointer to our owning image.
   RasterImage* mImage;
 
   //! The intrinsic size of the image.
   gfx::IntSize mSize;
-
-  /** For managing blending of frames
-   *
-   * Some animations will use the compositingFrame to composite images
-   * and just hand this back to the caller when it is time to draw the frame.
-   * NOTE: When clearing compositingFrame, remember to set
-   *       lastCompositedFrameIndex to -1.  Code assume that if
-   *       lastCompositedFrameIndex >= 0 then compositingFrame exists.
-   */
-  RawAccessFrameRef mCompositingFrame;
-
-  /** the previous composited frame, for DISPOSE_RESTORE_PREVIOUS
-   *
-   * The Previous Frame (all frames composited up to the current) needs to be
-   * stored in cases where the image specifies it wants the last frame back
-   * when it's done with the current frame.
-   */
-  RawAccessFrameRef mCompositingPrevFrame;
-
-  //! Track the last composited frame for Optimizations (See DoComposite code)
-  int32_t mLastCompositedFrameIndex;
 };
 
 }  // namespace image
 }  // namespace mozilla
 
 #endif  // mozilla_image_FrameAnimator_h
--- a/image/Image.cpp
+++ b/image/Image.cpp
@@ -92,21 +92,20 @@ void ImageResource::SetCurrentImage(Imag
       image, TimeStamp(), mLastFrameID++, mImageProducerID));
 
   if (aDirtyRect) {
     aContainer->SetCurrentImagesInTransaction(imageList);
   } else {
     aContainer->SetCurrentImages(imageList);
   }
 
-  // If we are generating full frames, and we are animated, then we should
-  // request that the image container be treated as such, to avoid display
-  // list rebuilding to update frames for WebRender.
-  if (gfxPrefs::ImageAnimatedGenerateFullFrames() &&
-      mProgressTracker->GetProgress() & FLAG_IS_ANIMATED) {
+  // If we are animated, then we should request that the image container be
+  // treated as such, to avoid display list rebuilding to update frames for
+  // WebRender.
+  if (mProgressTracker->GetProgress() & FLAG_IS_ANIMATED) {
     if (aDirtyRect) {
       layers::SharedSurfacesChild::UpdateAnimation(aContainer, aSurface,
                                                    aDirtyRect.ref());
     } else {
       IntRect dirtyRect(IntPoint(0, 0), aSurface->GetSize());
       layers::SharedSurfacesChild::UpdateAnimation(aContainer, aSurface,
                                                    dirtyRect);
     }
--- a/image/RasterImage.cpp
+++ b/image/RasterImage.cpp
@@ -367,24 +367,16 @@ LookupResult RasterImage::LookupFrame(co
     }
   }
 
   if (!result) {
     // We still weren't able to get a frame. Give up.
     return result;
   }
 
-  if (result.Surface()->GetCompositingFailed()) {
-    DrawableSurface tmp = std::move(result.Surface());
-    return result;
-  }
-
-  MOZ_ASSERT(!result.Surface()->GetIsPaletted(),
-             "Should not have a paletted frame");
-
   // Sync decoding guarantees that we got the frame, but if it's owned by an
   // async decoder that's currently running, the contents of the frame may not
   // be available yet. Make sure we get everything.
   if (mAllSourceData && (aFlags & FLAG_SYNC_DECODE)) {
     result.Surface()->WaitUntilFinished();
   }
 
   // If we could have done some decoding in this function we need to check if
@@ -677,19 +669,16 @@ size_t RasterImage::SizeOfSourceWithComp
   return mSourceBuffer->SizeOfIncludingThisWithComputedFallback(
       aState.mMallocSizeOf);
 }
 
 void RasterImage::CollectSizeOfSurfaces(
     nsTArray<SurfaceMemoryCounter>& aCounters,
     MallocSizeOf aMallocSizeOf) const {
   SurfaceCache::CollectSizeOfSurfaces(ImageKey(this), aCounters, aMallocSizeOf);
-  if (mFrameAnimator) {
-    mFrameAnimator->CollectSizeOfCompositingSurfaces(aCounters, aMallocSizeOf);
-  }
 }
 
 bool RasterImage::SetMetadata(const ImageMetadata& aMetadata,
                               bool aFromMetadataDecode) {
   MOZ_ASSERT(NS_IsMainThread());
 
   if (mError) {
     return true;
@@ -1202,20 +1191,16 @@ bool RasterImage::Decode(const IntSize& 
     surfaceFlags &= ~SurfaceFlags::NO_PREMULTIPLY_ALPHA;
   }
 
   // Create a decoder.
   RefPtr<IDecodingTask> task;
   nsresult rv;
   bool animated = mAnimationState && aPlaybackType == PlaybackType::eAnimated;
   if (animated) {
-    if (gfxPrefs::ImageAnimatedGenerateFullFrames()) {
-      decoderFlags |= DecoderFlags::BLEND_ANIMATION;
-    }
-
     size_t currentFrame = mAnimationState->GetCurrentAnimationFrameIndex();
     rv = DecoderFactory::CreateAnimationDecoder(
         mDecoderType, WrapNotNull(this), mSourceBuffer, mSize, decoderFlags,
         surfaceFlags, currentFrame, getter_AddRefs(task));
   } else {
     rv = DecoderFactory::CreateDecoder(
         mDecoderType, WrapNotNull(this), mSourceBuffer, mSize, aSize,
         decoderFlags, surfaceFlags, getter_AddRefs(task));
@@ -1356,17 +1341,17 @@ ImgDrawResult RasterImage::DrawInternal(
   bool frameIsFinished = aSurface->IsFinished();
 
 #ifdef DEBUG
   NotifyDrawingObservers();
 #endif
 
   // By now we may have a frame with the requested size. If not, we need to
   // adjust the drawing parameters accordingly.
-  IntSize finalSize = aSurface->GetImageSize();
+  IntSize finalSize = aSurface->GetSize();
   bool couldRedecodeForBetterFrame = false;
   if (finalSize != aSize) {
     gfx::Size scale(double(aSize.width) / finalSize.width,
                     double(aSize.height) / finalSize.height);
     aContext->Multiply(gfxMatrix::Scaling(scale.width, scale.height));
     region.Scale(1.0 / scale.width, 1.0 / scale.height);
 
     couldRedecodeForBetterFrame = CanDownscaleDuringDecode(aSize, aFlags);
--- a/image/SurfaceFilters.h
+++ b/image/SurfaceFilters.h
@@ -69,25 +69,16 @@ class DeinterlacingFilter final : public
   template <typename... Rest>
   nsresult Configure(const DeinterlacingConfig<PixelType>& aConfig,
                      const Rest&... aRest) {
     nsresult rv = mNext.Configure(aRest...);
     if (NS_FAILED(rv)) {
       return rv;
     }
 
-    if (sizeof(PixelType) == 1 && !mNext.IsValidPalettedPipe()) {
-      NS_WARNING("Paletted DeinterlacingFilter used with non-paletted pipe?");
-      return NS_ERROR_INVALID_ARG;
-    }
-    if (sizeof(PixelType) == 4 && mNext.IsValidPalettedPipe()) {
-      NS_WARNING("Non-paletted DeinterlacingFilter used with paletted pipe?");
-      return NS_ERROR_INVALID_ARG;
-    }
-
     gfx::IntSize outputSize = mNext.InputSize();
     mProgressiveDisplay = aConfig.mProgressiveDisplay;
 
     const uint32_t bufferSize =
         outputSize.width * outputSize.height * sizeof(PixelType);
 
     // Use the size of the SurfaceCache as a heuristic to avoid gigantic
     // allocations. Even if DownscalingFilter allowed us to allocate space for
@@ -110,20 +101,16 @@ class DeinterlacingFilter final : public
 
     // Clear the buffer to avoid writing uninitialized memory to the output.
     memset(mBuffer.get(), 0, bufferSize);
 
     ConfigureFilter(outputSize, sizeof(PixelType));
     return NS_OK;
   }
 
-  bool IsValidPalettedPipe() const override {
-    return sizeof(PixelType) == 1 && mNext.IsValidPalettedPipe();
-  }
-
   Maybe<SurfaceInvalidRect> TakeInvalidRect() override {
     return mNext.TakeInvalidRect();
   }
 
  protected:
   uint8_t* DoResetToFirstRow() override {
     mNext.ResetToFirstRow();
     mPass = 0;
@@ -368,21 +355,16 @@ class BlendAnimationFilter final : publi
   template <typename... Rest>
   nsresult Configure(const BlendAnimationConfig& aConfig,
                      const Rest&... aRest) {
     nsresult rv = mNext.Configure(aRest...);
     if (NS_FAILED(rv)) {
       return rv;
     }
 
-    if (!aConfig.mDecoder || !aConfig.mDecoder->ShouldBlendAnimation()) {
-      MOZ_ASSERT_UNREACHABLE("Expected image decoder that is blending!");
-      return NS_ERROR_INVALID_ARG;
-    }
-
     imgFrame* currentFrame = aConfig.mDecoder->GetCurrentFrame();
     if (!currentFrame) {
       MOZ_ASSERT_UNREACHABLE("Decoder must have current frame!");
       return NS_ERROR_FAILURE;
     }
 
     mFrameRect = mUnclampedFrameRect = currentFrame->GetBlendRect();
     gfx::IntSize outputSize = mNext.InputSize();
@@ -426,17 +408,17 @@ class BlendAnimationFilter final : publi
     // is a full frame and uses source blending, there is no need to consider
     // the disposal method of the previous frame.
     gfx::IntRect dirtyRect(outputRect);
     gfx::IntRect clearRect;
     if (!fullFrame || blendMethod != BlendMethod::SOURCE) {
       const RawAccessFrameRef& restoreFrame =
           aConfig.mDecoder->GetRestoreFrameRef();
       if (restoreFrame) {
-        MOZ_ASSERT(restoreFrame->GetImageSize() == outputSize);
+        MOZ_ASSERT(restoreFrame->GetSize() == outputSize);
         MOZ_ASSERT(restoreFrame->IsFinished());
 
         // We can safely use this pointer without holding a RawAccessFrameRef
         // because the decoder will keep it alive for us.
         mBaseFrameStartPtr = restoreFrame.Data();
         MOZ_ASSERT(mBaseFrameStartPtr);
 
         gfx::IntRect restoreBlendRect = restoreFrame->GetBoundedBlendRect();
@@ -776,21 +758,16 @@ class RemoveFrameRectFilter final : publ
   template <typename... Rest>
   nsresult Configure(const RemoveFrameRectConfig& aConfig,
                      const Rest&... aRest) {
     nsresult rv = mNext.Configure(aRest...);
     if (NS_FAILED(rv)) {
       return rv;
     }
 
-    if (mNext.IsValidPalettedPipe()) {
-      NS_WARNING("RemoveFrameRectFilter used with paletted pipe?");
-      return NS_ERROR_INVALID_ARG;
-    }
-
     mFrameRect = mUnclampedFrameRect = aConfig.mFrameRect;
     gfx::IntSize outputSize = mNext.InputSize();
 
     // Forbid frame rects with negative size.
     if (aConfig.mFrameRect.Width() < 0 || aConfig.mFrameRect.Height() < 0) {
       return NS_ERROR_INVALID_ARG;
     }
 
@@ -1006,21 +983,16 @@ class ADAM7InterpolatingFilter final : p
   template <typename... Rest>
   nsresult Configure(const ADAM7InterpolatingConfig& aConfig,
                      const Rest&... aRest) {
     nsresult rv = mNext.Configure(aRest...);
     if (NS_FAILED(rv)) {
       return rv;
     }
 
-    if (mNext.IsValidPalettedPipe()) {
-      NS_WARNING("ADAM7InterpolatingFilter used with paletted pipe?");
-      return NS_ERROR_INVALID_ARG;
-    }
-
     // We have two intermediate buffers, one for the previous row with final
     // pixel values and one for the row that the previous filter in the chain is
     // currently writing to.
     size_t inputWidthInBytes = mNext.InputSize().width * sizeof(uint32_t);
     mPreviousRow.reset(new (fallible) uint8_t[inputWidthInBytes]);
     if (MOZ_UNLIKELY(!mPreviousRow)) {
       return NS_ERROR_OUT_OF_MEMORY;
     }
--- a/image/SurfacePipe.cpp
+++ b/image/SurfacePipe.cpp
@@ -49,30 +49,24 @@ uint8_t* AbstractSurfaceSink::DoAdvanceR
                          IntRect(0, invalidY, InputSize().width, 1));
 
   mRow = min(uint32_t(InputSize().height), mRow + 1);
 
   return mRow < uint32_t(InputSize().height) ? GetRowPointer() : nullptr;
 }
 
 nsresult SurfaceSink::Configure(const SurfaceConfig& aConfig) {
-  // For non-paletted surfaces, the surface size is just the output size.
   IntSize surfaceSize = aConfig.mOutputSize;
 
-  // Non-paletted surfaces should not have frame rects, so we just pass
-  // AllocateFrame() a frame rect which covers the entire surface.
-  IntRect frameRect(0, 0, surfaceSize.width, surfaceSize.height);
-
   // Allocate the frame.
   // XXX(seth): Once every Decoder subclass uses SurfacePipe, we probably want
   // to allocate the frame directly here and get rid of Decoder::AllocateFrame
   // altogether.
-  nsresult rv = aConfig.mDecoder->AllocateFrame(
-      surfaceSize, frameRect, aConfig.mFormat,
-      /* aPaletteDepth */ 0, aConfig.mAnimParams);
+  nsresult rv = aConfig.mDecoder->AllocateFrame(surfaceSize, aConfig.mFormat,
+                                                aConfig.mAnimParams);
   if (NS_FAILED(rv)) {
     return rv;
   }
 
   mImageData = aConfig.mDecoder->mImageData;
   mImageDataLength = aConfig.mDecoder->mImageDataLength;
   mFlipVertically = aConfig.mFlipVertically;
 
@@ -95,56 +89,10 @@ uint8_t* SurfaceSink::GetRowPointer() co
   MOZ_ASSERT(rowPtr >= mImageData);
   MOZ_ASSERT(rowPtr < mImageData + mImageDataLength);
   MOZ_ASSERT(rowPtr + InputSize().width * sizeof(uint32_t) <=
              mImageData + mImageDataLength);
 
   return rowPtr;
 }
 
-nsresult PalettedSurfaceSink::Configure(const PalettedSurfaceConfig& aConfig) {
-  MOZ_ASSERT(aConfig.mFormat == SurfaceFormat::B8G8R8A8);
-
-  // For paletted surfaces, the surface size is the size of the frame rect.
-  IntSize surfaceSize = aConfig.mFrameRect.Size();
-
-  // Allocate the frame.
-  // XXX(seth): Once every Decoder subclass uses SurfacePipe, we probably want
-  // to allocate the frame directly here and get rid of Decoder::AllocateFrame
-  // altogether.
-  nsresult rv = aConfig.mDecoder->AllocateFrame(
-      aConfig.mOutputSize, aConfig.mFrameRect, aConfig.mFormat,
-      aConfig.mPaletteDepth, aConfig.mAnimParams);
-  if (NS_FAILED(rv)) {
-    return rv;
-  }
-
-  mImageData = aConfig.mDecoder->mImageData;
-  mImageDataLength = aConfig.mDecoder->mImageDataLength;
-  mFlipVertically = aConfig.mFlipVertically;
-  mFrameRect = aConfig.mFrameRect;
-
-  MOZ_ASSERT(mImageData);
-  MOZ_ASSERT(
-      mImageDataLength ==
-      uint32_t(mFrameRect.Width() * mFrameRect.Height() * sizeof(uint8_t)));
-
-  ConfigureFilter(surfaceSize, sizeof(uint8_t));
-  return NS_OK;
-}
-
-uint8_t* PalettedSurfaceSink::GetRowPointer() const {
-  // If we're flipping vertically, reverse the order in which we traverse the
-  // rows.
-  uint32_t row = mFlipVertically ? InputSize().height - (mRow + 1) : mRow;
-
-  uint8_t* rowPtr = mImageData + row * InputSize().width * sizeof(uint8_t);
-
-  MOZ_ASSERT(rowPtr >= mImageData);
-  MOZ_ASSERT(rowPtr < mImageData + mImageDataLength);
-  MOZ_ASSERT(rowPtr + InputSize().width * sizeof(uint8_t) <=
-             mImageData + mImageDataLength);
-
-  return rowPtr;
-}
-
 }  // namespace image
 }  // namespace mozilla
--- a/image/SurfacePipe.h
+++ b/image/SurfacePipe.h
@@ -419,19 +419,16 @@ class SurfaceFilter {
     return IsSurfaceFinished() ? WriteState::FINISHED
                                : WriteState::NEED_MORE_DATA;
   }
 
   //////////////////////////////////////////////////////////////////////////////
   // Methods Subclasses Should Override
   //////////////////////////////////////////////////////////////////////////////
 
-  /// @return true if this SurfaceFilter can be used with paletted surfaces.
-  virtual bool IsValidPalettedPipe() const { return false; }
-
   /**
    * @return a SurfaceInvalidRect representing the region of the surface that
    *         has been written to since the last time TakeInvalidRect() was
    *         called, or Nothing() if the region is empty (i.e. nothing has been
    *         written).
    */
   virtual Maybe<SurfaceInvalidRect> TakeInvalidRect() = 0;
 
@@ -742,67 +739,25 @@ struct SurfaceConfig {
   Decoder* mDecoder;           /// Which Decoder to use to allocate the surface.
   gfx::IntSize mOutputSize;    /// The size of the surface.
   gfx::SurfaceFormat mFormat;  /// The surface format (BGRA or BGRX).
   bool mFlipVertically;        /// If true, write the rows from bottom to top.
   Maybe<AnimationParams> mAnimParams;  /// Given for animated images.
 };
 
 /**
- * A sink for normal (i.e., non-paletted) surfaces. It handles the allocation of
- * the surface and protects against buffer overflow. This sink should be used
- * for all non-animated images and for the first frame of animated images.
+ * A sink for surfaces. It handles the allocation of the surface and protects
+ * against buffer overflow. This sink should be used for images.
  *
  * Sinks must always be at the end of the SurfaceFilter chain.
  */
 class SurfaceSink final : public AbstractSurfaceSink {
  public:
   nsresult Configure(const SurfaceConfig& aConfig);
 
  protected:
   uint8_t* GetRowPointer() const override;
 };
 
-class PalettedSurfaceSink;
-
-struct PalettedSurfaceConfig {
-  using Filter = PalettedSurfaceSink;
-  Decoder* mDecoder;           /// Which Decoder to use to allocate the surface.
-  gfx::IntSize mOutputSize;    /// The logical size of the surface.
-  gfx::IntRect mFrameRect;     /// The surface subrect which contains data.
-  gfx::SurfaceFormat mFormat;  /// The surface format (BGRA or BGRX).
-  uint8_t mPaletteDepth;       /// The palette depth of this surface.
-  bool mFlipVertically;        /// If true, write the rows from bottom to top.
-  Maybe<AnimationParams> mAnimParams;  /// Given for animated images.
-};
-
-/**
- * A sink for paletted surfaces. It handles the allocation of the surface and
- * protects against buffer overflow. This sink can be used for frames of
- * animated images except the first.
- *
- * Sinks must always be at the end of the SurfaceFilter chain.
- *
- * XXX(seth): We'll remove all support for paletted surfaces in bug 1247520,
- * which means we can remove PalettedSurfaceSink entirely.
- */
-class PalettedSurfaceSink final : public AbstractSurfaceSink {
- public:
-  bool IsValidPalettedPipe() const override { return true; }
-
-  nsresult Configure(const PalettedSurfaceConfig& aConfig);
-
- protected:
-  uint8_t* GetRowPointer() const override;
-
- private:
-  /**
-   * The surface subrect which contains data. Note that the surface size we
-   * actually allocate is the size of the frame rect, not the logical size of
-   * the surface.
-   */
-  gfx::IntRect mFrameRect;
-};
-
 }  // namespace image
 }  // namespace mozilla
 
 #endif  // mozilla_image_SurfacePipe_h
--- a/image/SurfacePipeFactory.h
+++ b/image/SurfacePipeFactory.h
@@ -54,20 +54,16 @@ enum class SurfacePipeFlags {
 
   FLIP_VERTICALLY = 1 << 2,  // If set, flip the image vertically.
 
   PROGRESSIVE_DISPLAY = 1 << 3,  // If set, we expect the image to be displayed
                                  // progressively. This enables features that
                                  // result in a better user experience for
                                  // progressive display but which may be more
                                  // computationally expensive.
-
-  BLEND_ANIMATION = 1 << 4  // If set, produce the next full frame of an
-                            // animation instead of a partial frame to be
-                            // blended later.
 };
 MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(SurfacePipeFlags)
 
 class SurfacePipeFactory {
  public:
   /**
    * Creates and initializes a normal (i.e., non-paletted) SurfacePipe.
    *
@@ -97,34 +93,31 @@ class SurfacePipeFactory {
     const bool deinterlace = bool(aFlags & SurfacePipeFlags::DEINTERLACE);
     const bool flipVertically =
         bool(aFlags & SurfacePipeFlags::FLIP_VERTICALLY);
     const bool progressiveDisplay =
         bool(aFlags & SurfacePipeFlags::PROGRESSIVE_DISPLAY);
     const bool downscale = aInputSize != aOutputSize;
     const bool removeFrameRect = !aFrameRect.IsEqualEdges(
         nsIntRect(0, 0, aInputSize.width, aInputSize.height));
-    const bool blendAnimation =
-        bool(aFlags & SurfacePipeFlags::BLEND_ANIMATION);
+    const bool blendAnimation = aAnimParams.isSome();
 
     // Don't interpolate if we're sure we won't show this surface to the user
     // until it's completely decoded. The final pass of an ADAM7 image doesn't
     // need interpolation, so we only need to interpolate if we'll be displaying
     // the image while it's still being decoded.
     const bool adam7Interpolate =
         bool(aFlags & SurfacePipeFlags::ADAM7_INTERPOLATE) &&
         progressiveDisplay;
 
     if (deinterlace && adam7Interpolate) {
       MOZ_ASSERT_UNREACHABLE("ADAM7 deinterlacing is handled by libpng");
       return Nothing();
     }
 
-    MOZ_ASSERT_IF(blendAnimation, aAnimParams);
-
     // Construct configurations for the SurfaceFilters. Note that the order of
     // these filters is significant. We want to deinterlace or interpolate raw
     // input rows, before any other transformations, and we want to remove the
     // frame rect (which may involve adding blank rows or columns to the image)
     // before any downscaling, so that the new rows and columns are taken into
     // account.
     DeinterlacingConfig<uint32_t> deinterlacingConfig{progressiveDisplay};
     ADAM7InterpolatingConfig interpolatingConfig;
@@ -190,66 +183,16 @@ class SurfacePipeFactory {
           pipe = MakePipe(surfaceConfig);
         }
       }
     }
 
     return pipe;
   }
 
-  /**
-   * Creates and initializes a paletted SurfacePipe.
-   *
-   * XXX(seth): We'll remove all support for paletted surfaces in bug 1247520,
-   * which means we can remove CreatePalettedSurfacePipe() entirely.
-   *
-   * @param aDecoder The decoder whose current frame the SurfacePipe will write
-   *                 to.
-   * @param aInputSize The original size of the image.
-   * @param aFrameRect The portion of the image that actually contains data.
-   * @param aFormat The surface format of the image; generally B8G8R8A8 or
-   *                B8G8R8X8.
-   * @param aPaletteDepth The palette depth of the image.
-   * @param aAnimParams Extra parameters used by animated images.
-   * @param aFlags Flags enabling or disabling various functionality for the
-   *               SurfacePipe; see the SurfacePipeFlags documentation for more
-   *               information.
-   *
-   * @return A SurfacePipe if the parameters allowed one to be created
-   *         successfully, or Nothing() if the SurfacePipe could not be
-   *         initialized.
-   */
-  static Maybe<SurfacePipe> CreatePalettedSurfacePipe(
-      Decoder* aDecoder, const nsIntSize& aInputSize,
-      const nsIntRect& aFrameRect, gfx::SurfaceFormat aFormat,
-      uint8_t aPaletteDepth, const Maybe<AnimationParams>& aAnimParams,
-      SurfacePipeFlags aFlags) {
-    const bool deinterlace = bool(aFlags & SurfacePipeFlags::DEINTERLACE);
-    const bool flipVertically =
-        bool(aFlags & SurfacePipeFlags::FLIP_VERTICALLY);
-    const bool progressiveDisplay =
-        bool(aFlags & SurfacePipeFlags::PROGRESSIVE_DISPLAY);
-
-    // Construct configurations for the SurfaceFilters.
-    DeinterlacingConfig<uint8_t> deinterlacingConfig{progressiveDisplay};
-    PalettedSurfaceConfig palettedSurfaceConfig{
-        aDecoder,      aInputSize,     aFrameRect, aFormat,
-        aPaletteDepth, flipVertically, aAnimParams};
-
-    Maybe<SurfacePipe> pipe;
-
-    if (deinterlace) {
-      pipe = MakePipe(deinterlacingConfig, palettedSurfaceConfig);
-    } else {
-      pipe = MakePipe(palettedSurfaceConfig);
-    }
-
-    return pipe;
-  }
-
  private:
   template <typename... Configs>
   static Maybe<SurfacePipe> MakePipe(const Configs&... aConfigs) {
     auto pipe = MakeUnique<typename detail::FilterPipeline<Configs...>::Type>();
     nsresult rv = pipe->Configure(aConfigs...);
     if (NS_FAILED(rv)) {
       return Nothing();
     }
--- a/image/decoders/nsBMPDecoder.cpp
+++ b/image/decoders/nsBMPDecoder.cpp
@@ -652,19 +652,19 @@ LexerTransition<nsBMPDecoder::State> nsB
     mColors = MakeUnique<ColorTableEntry[]>(256);
     memset(mColors.get(), 0, 256 * sizeof(ColorTableEntry));
 
     // OS/2 Bitmaps have no padding byte.
     mBytesPerColor = (mH.mBIHSize == InfoHeaderLength::WIN_V2) ? 3 : 4;
   }
 
   MOZ_ASSERT(!mImageData, "Already have a buffer allocated?");
-  nsresult rv = AllocateFrame(
-      OutputSize(), FullOutputFrame(),
-      mMayHaveTransparency ? SurfaceFormat::B8G8R8A8 : SurfaceFormat::B8G8R8X8);
+  nsresult rv = AllocateFrame(OutputSize(), mMayHaveTransparency
+                                                ? SurfaceFormat::B8G8R8A8
+                                                : SurfaceFormat::B8G8R8X8);
   if (NS_FAILED(rv)) {
     return Transition::TerminateFailure();
   }
   MOZ_ASSERT(mImageData, "Should have a buffer now");
 
   if (mDownscaler) {
     // BMPs store their rows in reverse order, so the downscaler needs to
     // reverse them again when writing its output. Unless the height is
--- a/image/decoders/nsGIFDecoder2.cpp
+++ b/image/decoders/nsGIFDecoder2.cpp
@@ -81,16 +81,18 @@ static const uint8_t PACKED_FIELDS_TABLE
 
 nsGIFDecoder2::nsGIFDecoder2(RasterImage* aImage)
     : Decoder(aImage),
       mLexer(Transition::To(State::GIF_HEADER, GIF_HEADER_LEN),
              Transition::TerminateSuccess()),
       mOldColor(0),
       mCurrentFrameIndex(-1),
       mColorTablePos(0),
+      mColormap(nullptr),
+      mColormapSize(0),
       mColorMask('\0'),
       mGIFOpen(false),
       mSawTransparency(false) {
   // Clear out the structure, excluding the arrays.
   memset(&mGIFStruct, 0, sizeof(mGIFStruct));
 }
 
 nsGIFDecoder2::~nsGIFDecoder2() { free(mGIFStruct.local_colormap); }
@@ -159,64 +161,44 @@ bool nsGIFDecoder2::CheckForTransparency
 }
 
 //******************************************************************************
 nsresult nsGIFDecoder2::BeginImageFrame(const IntRect& aFrameRect,
                                         uint16_t aDepth, bool aIsInterlaced) {
   MOZ_ASSERT(HasSize());
 
   bool hasTransparency = CheckForTransparency(aFrameRect);
-  bool blendAnimation = ShouldBlendAnimation();
 
   // Make sure there's no animation if we're downscaling.
   MOZ_ASSERT_IF(Size() != OutputSize(), !GetImageMetadata().HasAnimation());
 
-  AnimationParams animParams{
-      aFrameRect, FrameTimeout::FromRawMilliseconds(mGIFStruct.delay_time),
-      uint32_t(mGIFStruct.images_decoded), BlendMethod::OVER,
-      DisposalMethod(mGIFStruct.disposal_method)};
+  Maybe<AnimationParams> animParams;
+  if (!IsFirstFrameDecode()) {
+    animParams.emplace(aFrameRect,
+                       FrameTimeout::FromRawMilliseconds(mGIFStruct.delay_time),
+                       uint32_t(mGIFStruct.images_decoded), BlendMethod::OVER,
+                       DisposalMethod(mGIFStruct.disposal_method));
+  }
 
   SurfacePipeFlags pipeFlags =
       aIsInterlaced ? SurfacePipeFlags::DEINTERLACE : SurfacePipeFlags();
 
   gfx::SurfaceFormat format;
   if (mGIFStruct.images_decoded == 0) {
     // The first frame may be displayed progressively.
     pipeFlags |= SurfacePipeFlags::PROGRESSIVE_DISPLAY;
 
     format =
         hasTransparency ? SurfaceFormat::B8G8R8A8 : SurfaceFormat::B8G8R8X8;
   } else {
     format = SurfaceFormat::B8G8R8A8;
   }
 
-  if (blendAnimation) {
-    pipeFlags |= SurfacePipeFlags::BLEND_ANIMATION;
-  }
-
-  Maybe<SurfacePipe> pipe;
-  if (mGIFStruct.images_decoded == 0 || blendAnimation) {
-    // The first frame is always decoded into an RGB surface.
-    pipe = SurfacePipeFactory::CreateSurfacePipe(this, Size(), OutputSize(),
-                                                 aFrameRect, format,
-                                                 Some(animParams), pipeFlags);
-  } else {
-    // This is an animation frame (and not the first). To minimize the memory
-    // usage of animations, the image data is stored in paletted form.
-    //
-    // We should never use paletted surfaces with a draw target directly, so
-    // the only practical difference between B8G8R8A8 and B8G8R8X8 is the
-    // cleared pixel value if we get truncated. We want 0 in that case to
-    // ensure it is an acceptable value for the color map as was the case
-    // historically.
-    MOZ_ASSERT(Size() == OutputSize());
-    pipe = SurfacePipeFactory::CreatePalettedSurfacePipe(
-        this, Size(), aFrameRect, format, aDepth, Some(animParams), pipeFlags);
-  }
-
+  Maybe<SurfacePipe> pipe = SurfacePipeFactory::CreateSurfacePipe(
+      this, Size(), OutputSize(), aFrameRect, format, animParams, pipeFlags);
   mCurrentFrameIndex = mGIFStruct.images_decoded;
 
   if (!pipe) {
     mPipe = SurfacePipe();
     return NS_ERROR_FAILURE;
   }
 
   mPipe = std::move(*pipe);
@@ -250,16 +232,18 @@ void nsGIFDecoder2::EndImageFrame() {
   PostFrameStop(opacity);
 
   // Reset the transparent pixel
   if (mOldColor) {
     mColormap[mGIFStruct.tpixel] = mOldColor;
     mOldColor = 0;
   }
 
+  mColormap = nullptr;
+  mColormapSize = 0;
   mCurrentFrameIndex = -1;
 }
 
 template <typename PixelSize>
 PixelSize nsGIFDecoder2::ColormapIndexToPixel(uint8_t aIndex) {
   MOZ_ASSERT(sizeof(PixelSize) == sizeof(uint32_t));
 
   // Retrieve the next color, clamping to the size of the colormap.
@@ -869,21 +853,16 @@ LexerTransition<nsGIFDecoder2::State> ns
       int64_t(frameRect.Width()) * int64_t(frameRect.Height());
 
   if (haveLocalColorTable) {
     // We have a local color table, so prepare to read it into the palette of
     // the current frame.
     mGIFStruct.local_colormap_size = 1 << depth;
 
     if (!mColormap) {
-      // Allocate a buffer to store the local color tables. This could be if the
-      // first frame has a local color table, or for subsequent frames when
-      // blending the animation during decoding.
-      MOZ_ASSERT(mGIFStruct.images_decoded == 0 || ShouldBlendAnimation());
-
       // Ensure our current colormap buffer is large enough to hold the new one.
       mColormapSize = sizeof(uint32_t) << realDepth;
       if (mGIFStruct.local_colormap_buffer_size < mColormapSize) {
         if (mGIFStruct.local_colormap) {
           free(mGIFStruct.local_colormap);
         }
         mGIFStruct.local_colormap_buffer_size = mColormapSize;
         mGIFStruct.local_colormap =
@@ -1014,28 +993,21 @@ LexerTransition<nsGIFDecoder2::State> ns
     const char* aData, size_t aLength) {
   const uint8_t* data = reinterpret_cast<const uint8_t*>(aData);
   size_t length = aLength;
 
   while (mGIFStruct.pixels_remaining > 0 &&
          (length > 0 || mGIFStruct.bits >= mGIFStruct.codesize)) {
     size_t bytesRead = 0;
 
-    auto result =
-        mGIFStruct.images_decoded == 0 || ShouldBlendAnimation()
-            ? mPipe.WritePixelBlocks<uint32_t>(
-                  [&](uint32_t* aPixelBlock, int32_t aBlockSize) {
-                    return YieldPixels<uint32_t>(data, length, &bytesRead,
-                                                 aPixelBlock, aBlockSize);
-                  })
-            : mPipe.WritePixelBlocks<uint8_t>(
-                  [&](uint8_t* aPixelBlock, int32_t aBlockSize) {
-                    return YieldPixels<uint8_t>(data, length, &bytesRead,
-                                                aPixelBlock, aBlockSize);
-                  });
+    auto result = mPipe.WritePixelBlocks<uint32_t>(
+        [&](uint32_t* aPixelBlock, int32_t aBlockSize) {
+          return YieldPixels<uint32_t>(data, length, &bytesRead, aPixelBlock,
+                                       aBlockSize);
+        });
 
     if (MOZ_UNLIKELY(bytesRead > length)) {
       MOZ_ASSERT_UNREACHABLE("Overread?");
       bytesRead = length;
     }
 
     // Advance our position in the input based upon what YieldPixel() consumed.
     data += bytesRead;
--- a/image/decoders/nsGIFDecoder2.h
+++ b/image/decoders/nsGIFDecoder2.h
@@ -138,16 +138,18 @@ class nsGIFDecoder2 : public Decoder {
   // The frame number of the currently-decoding frame when we're in the middle
   // of decoding it, and -1 otherwise.
   int32_t mCurrentFrameIndex;
 
   // When we're reading in the global or local color table, this records our
   // current position - i.e., the offset into which the next byte should be
   // written.
   size_t mColorTablePos;
+  uint32_t* mColormap;  // Current colormap to be used in Cairo format
+  uint32_t mColormapSize;
 
   uint8_t mColorMask;  // Apply this to the pixel to keep within colormap
   bool mGIFOpen;
   bool mSawTransparency;
 
   gif_struct mGIFStruct;
 
   SurfacePipe mPipe;  /// The SurfacePipe used to write to the output surface.
--- a/image/decoders/nsJPEGDecoder.cpp
+++ b/image/decoders/nsJPEGDecoder.cpp
@@ -380,18 +380,17 @@ LexerTransition<nsJPEGDecoder::State> ns
       // when not doing a progressive decode.
       mInfo.buffered_image =
           mDecodeStyle == PROGRESSIVE && jpeg_has_multiple_scans(&mInfo);
 
       /* Used to set up image size so arrays can be allocated */
       jpeg_calc_output_dimensions(&mInfo);
 
       MOZ_ASSERT(!mImageData, "Already have a buffer allocated?");
-      nsresult rv = AllocateFrame(OutputSize(), FullOutputFrame(),
-                                  SurfaceFormat::B8G8R8X8);
+      nsresult rv = AllocateFrame(OutputSize(), SurfaceFormat::B8G8R8X8);
       if (NS_FAILED(rv)) {
         mState = JPEG_ERROR;
         MOZ_LOG(sJPEGDecoderAccountingLog, LogLevel::Debug,
                 ("} (could not initialize image frame)"));
         return Transition::TerminateFailure();
       }
 
       MOZ_ASSERT(mImageData, "Should have a buffer now");
--- a/image/decoders/nsPNGDecoder.cpp
+++ b/image/decoders/nsPNGDecoder.cpp
@@ -190,17 +190,17 @@ nsresult nsPNGDecoder::CreateFrame(const
   // Make sure there's no animation or padding if we're downscaling.
   MOZ_ASSERT_IF(Size() != OutputSize(), mNumFrames == 0);
   MOZ_ASSERT_IF(Size() != OutputSize(), !GetImageMetadata().HasAnimation());
   MOZ_ASSERT_IF(Size() != OutputSize(),
                 transparency != TransparencyType::eFrameRect);
 
   Maybe<AnimationParams> animParams;
 #ifdef PNG_APNG_SUPPORTED
-  if (png_get_valid(mPNG, mInfo, PNG_INFO_acTL)) {
+  if (!IsFirstFrameDecode() && png_get_valid(mPNG, mInfo, PNG_INFO_acTL)) {
     mAnimInfo = AnimFrameInfo(mPNG, mInfo);
 
     if (mAnimInfo.mDispose == DisposalMethod::CLEAR) {
       // We may have to display the background under this image during
       // animation playback, so we regard it as transparent.
       PostHasTransparency();
     }
 
@@ -217,20 +217,16 @@ nsresult nsPNGDecoder::CreateFrame(const
                                    ? SurfacePipeFlags::ADAM7_INTERPOLATE
                                    : SurfacePipeFlags();
 
   if (mNumFrames == 0) {
     // The first frame may be displayed progressively.
     pipeFlags |= SurfacePipeFlags::PROGRESSIVE_DISPLAY;
   }
 
-  if (ShouldBlendAnimation()) {
-    pipeFlags |= SurfacePipeFlags::BLEND_ANIMATION;
-  }
-
   Maybe<SurfacePipe> pipe = SurfacePipeFactory::CreateSurfacePipe(
       this, Size(), OutputSize(), aFrameInfo.mFrameRect, mFormat, animParams,
       pipeFlags);
 
   if (!pipe) {
     mPipe = SurfacePipe();
     return NS_ERROR_FAILURE;
   }
@@ -534,17 +530,18 @@ void nsPNGDecoder::info_callback(png_str
 
   png_bytep trans = nullptr;
   int num_trans = 0;
 
   nsPNGDecoder* decoder =
       static_cast<nsPNGDecoder*>(png_get_progressive_ptr(png_ptr));
 
   if (decoder->mGotInfoCallback) {
-    MOZ_LOG(sPNGLog, LogLevel::Warning, ("libpng called info_callback more than once\n"));
+    MOZ_LOG(sPNGLog, LogLevel::Warning,
+            ("libpng called info_callback more than once\n"));
     return;
   }
 
   decoder->mGotInfoCallback = true;
 
   // Always decode to 24-bit RGB or 32-bit RGBA
   png_get_IHDR(png_ptr, info_ptr, &width, &height, &bit_depth, &color_type,
                &interlace_type, &compression_type, &filter_type);
--- a/image/decoders/nsWebPDecoder.cpp
+++ b/image/decoders/nsWebPDecoder.cpp
@@ -226,26 +226,23 @@ nsresult nsWebPDecoder::CreateFrame(cons
     MOZ_LOG(sWebPLog, LogLevel::Error,
             ("[this=%p] nsWebPDecoder::CreateFrame -- create decoder error\n",
              this));
     return NS_ERROR_FAILURE;
   }
 
   SurfacePipeFlags pipeFlags = SurfacePipeFlags();
 
-  if (ShouldBlendAnimation()) {
-    pipeFlags |= SurfacePipeFlags::BLEND_ANIMATION;
+  Maybe<AnimationParams> animParams;
+  if (!IsFirstFrameDecode()) {
+    animParams.emplace(aFrameRect, mTimeout, mCurrentFrame, mBlend, mDisposal);
   }
 
-  AnimationParams animParams{aFrameRect, mTimeout, mCurrentFrame, mBlend,
-                             mDisposal};
-
   Maybe<SurfacePipe> pipe = SurfacePipeFactory::CreateSurfacePipe(
-      this, Size(), OutputSize(), aFrameRect, mFormat, Some(animParams),
-      pipeFlags);
+      this, Size(), OutputSize(), aFrameRect, mFormat, animParams, pipeFlags);
   if (!pipe) {
     MOZ_LOG(sWebPLog, LogLevel::Error,
             ("[this=%p] nsWebPDecoder::CreateFrame -- no pipe\n", this));
     return NS_ERROR_FAILURE;
   }
 
   mFrameRect = aFrameRect;
   mPipe = std::move(*pipe);
--- a/image/imgFrame.cpp
+++ b/image/imgFrame.cpp
@@ -91,22 +91,20 @@ static bool ShouldUseHeap(const IntSize&
   if (bufferSize < gfxPrefs::ImageMemVolatileMinThresholdKB()) {
     return true;
   }
 
   return false;
 }
 
 static already_AddRefed<DataSourceSurface> AllocateBufferForImage(
-    const IntSize& size, SurfaceFormat format, bool aIsAnimated = false,
-    bool aIsFullFrame = true) {
+    const IntSize& size, SurfaceFormat format, bool aIsAnimated = false) {
   int32_t stride = VolatileSurfaceStride(size, format);
 
-  if (gfxVars::GetUseWebRenderOrDefault() && gfxPrefs::ImageMemShared() &&
-      aIsFullFrame) {
+  if (gfxVars::GetUseWebRenderOrDefault() && gfxPrefs::ImageMemShared()) {
     RefPtr<SourceSurfaceSharedData> newSurf = new SourceSurfaceSharedData();
     if (newSurf->Init(size, stride, format)) {
       return newSurf.forget();
     }
   } else if (ShouldUseHeap(size, stride, aIsAnimated)) {
     RefPtr<SourceSurfaceAlignedRawData> newSurf =
         new SourceSurfaceAlignedRawData();
     if (newSurf->Init(size, format, false, 0, stride)) {
@@ -171,203 +169,137 @@ static bool ClearSurface(DataSourceSurfa
     // Otherwise, it's allocated via mmap and refers to a zeroed page and will
     // be COW once it's written to.
     memset(data, 0, stride * aSize.height);
   }
 
   return true;
 }
 
-static bool AllowedImageAndFrameDimensions(const nsIntSize& aImageSize,
-                                           const nsIntRect& aFrameRect) {
-  if (!SurfaceCache::IsLegalSize(aImageSize)) {
-    return false;
-  }
-  if (!SurfaceCache::IsLegalSize(aFrameRect.Size())) {
-    return false;
-  }
-  nsIntRect imageRect(0, 0, aImageSize.width, aImageSize.height);
-  if (!imageRect.Contains(aFrameRect)) {
-    NS_WARNING("Animated image frame does not fit inside bounds of image");
-  }
-  return true;
-}
-
 imgFrame::imgFrame()
     : mMonitor("imgFrame"),
       mDecoded(0, 0, 0, 0),
       mLockCount(0),
       mRecycleLockCount(0),
       mAborted(false),
       mFinished(false),
       mOptimizable(false),
       mShouldRecycle(false),
       mTimeout(FrameTimeout::FromRawMilliseconds(100)),
       mDisposalMethod(DisposalMethod::NOT_SPECIFIED),
       mBlendMethod(BlendMethod::OVER),
       mFormat(SurfaceFormat::UNKNOWN),
-      mPalettedImageData(nullptr),
-      mPaletteDepth(0),
-      mNonPremult(false),
-      mIsFullFrame(false),
-      mCompositingFailed(false) {}
+      mNonPremult(false) {}
 
 imgFrame::~imgFrame() {
 #ifdef DEBUG
   MonitorAutoLock lock(mMonitor);
   MOZ_ASSERT(mAborted || AreAllPixelsWritten());
   MOZ_ASSERT(mAborted || mFinished);
 #endif
-
-  free(mPalettedImageData);
-  mPalettedImageData = nullptr;
 }
 
 nsresult imgFrame::InitForDecoder(const nsIntSize& aImageSize,
-                                  const nsIntRect& aRect, SurfaceFormat aFormat,
-                                  uint8_t aPaletteDepth, bool aNonPremult,
+                                  SurfaceFormat aFormat, bool aNonPremult,
                                   const Maybe<AnimationParams>& aAnimParams,
-                                  bool aIsFullFrame, bool aShouldRecycle) {
+                                  bool aShouldRecycle) {
   // Assert for properties that should be verified by decoders,
   // warn for properties related to bad content.
-  if (!AllowedImageAndFrameDimensions(aImageSize, aRect)) {
+  if (!SurfaceCache::IsLegalSize(aImageSize)) {
     NS_WARNING("Should have legal image size");
     mAborted = true;
     return NS_ERROR_FAILURE;
   }
 
   mImageSize = aImageSize;
-  mFrameRect = aRect;
 
   // May be updated shortly after InitForDecoder by BlendAnimationFilter
   // because it needs to take into consideration the previous frames to
   // properly calculate. We start with the whole frame as dirty.
-  mDirtyRect = aRect;
+  mDirtyRect = GetRect();
 
   if (aAnimParams) {
     mBlendRect = aAnimParams->mBlendRect;
     mTimeout = aAnimParams->mTimeout;
     mBlendMethod = aAnimParams->mBlendMethod;
     mDisposalMethod = aAnimParams->mDisposalMethod;
-    mIsFullFrame = aAnimParams->mFrameNum == 0 || aIsFullFrame;
   } else {
-    mBlendRect = aRect;
-    mIsFullFrame = true;
-  }
-
-  // We only allow a non-trivial frame rect (i.e., a frame rect that doesn't
-  // cover the entire image) for paletted animation frames. We never draw those
-  // frames directly; we just use FrameAnimator to composite them and produce a
-  // BGRA surface that we actually draw. We enforce this here to make sure that
-  // imgFrame::Draw(), which is responsible for drawing all other kinds of
-  // frames, never has to deal with a non-trivial frame rect.
-  if (aPaletteDepth == 0 &&
-      !mFrameRect.IsEqualEdges(IntRect(IntPoint(), mImageSize))) {
-    MOZ_ASSERT_UNREACHABLE(
-        "Creating a non-paletted imgFrame with a "
-        "non-trivial frame rect");
-    return NS_ERROR_FAILURE;
+    mBlendRect = GetRect();
   }
 
   if (aShouldRecycle) {
     // If we are recycling then we should always use BGRA for the underlying
     // surface because if we use BGRX, the next frame composited into the
     // surface could be BGRA and cause rendering problems.
-    MOZ_ASSERT(mIsFullFrame);
-    MOZ_ASSERT(aPaletteDepth == 0);
     MOZ_ASSERT(aAnimParams);
     mFormat = SurfaceFormat::B8G8R8A8;
   } else {
     mFormat = aFormat;
   }
 
-  mPaletteDepth = aPaletteDepth;
   mNonPremult = aNonPremult;
   mShouldRecycle = aShouldRecycle;
 
-  if (aPaletteDepth != 0) {
-    // We're creating for a paletted image.
-    if (aPaletteDepth > 8) {
-      NS_WARNING("Should have legal palette depth");
-      NS_ERROR("This Depth is not supported");
-      mAborted = true;
-      return NS_ERROR_FAILURE;
-    }
+  MOZ_ASSERT(!mLockedSurface, "Called imgFrame::InitForDecoder() twice?");
 
-    // Use the fallible allocator here. Paletted images always use 1 byte per
-    // pixel, so calculating the amount of memory we need is straightforward.
-    size_t dataSize = PaletteDataLength() + mFrameRect.Area();
-    mPalettedImageData =
-        static_cast<uint8_t*>(calloc(dataSize, sizeof(uint8_t)));
-    if (!mPalettedImageData) {
-      NS_WARNING("Call to calloc for paletted image data should succeed");
-    }
-    NS_ENSURE_TRUE(mPalettedImageData, NS_ERROR_OUT_OF_MEMORY);
-  } else {
-    MOZ_ASSERT(!mLockedSurface, "Called imgFrame::InitForDecoder() twice?");
+  bool postFirstFrame = aAnimParams && aAnimParams->mFrameNum > 0;
+  mRawSurface = AllocateBufferForImage(mImageSize, mFormat, postFirstFrame);
+  if (!mRawSurface) {
+    mAborted = true;
+    return NS_ERROR_OUT_OF_MEMORY;
+  }
 
-    bool postFirstFrame = aAnimParams && aAnimParams->mFrameNum > 0;
-    mRawSurface = AllocateBufferForImage(mFrameRect.Size(), mFormat,
-                                         postFirstFrame, mIsFullFrame);
-    if (!mRawSurface) {
+  if (StaticPrefs::browser_measurement_render_anims_and_video_solid() &&
+      aAnimParams) {
+    mBlankRawSurface = AllocateBufferForImage(mImageSize, mFormat);
+    if (!mBlankRawSurface) {
       mAborted = true;
       return NS_ERROR_OUT_OF_MEMORY;
     }
+  }
 
-    if (StaticPrefs::browser_measurement_render_anims_and_video_solid() &&
-        aAnimParams) {
-      mBlankRawSurface = AllocateBufferForImage(mFrameRect.Size(), mFormat);
-      if (!mBlankRawSurface) {
-        mAborted = true;
-        return NS_ERROR_OUT_OF_MEMORY;
-      }
-    }
+  mLockedSurface = CreateLockedSurface(mRawSurface, mImageSize, mFormat);
+  if (!mLockedSurface) {
+    NS_WARNING("Failed to create LockedSurface");
+    mAborted = true;
+    return NS_ERROR_OUT_OF_MEMORY;
+  }
 
-    mLockedSurface =
-        CreateLockedSurface(mRawSurface, mFrameRect.Size(), mFormat);
-    if (!mLockedSurface) {
-      NS_WARNING("Failed to create LockedSurface");
+  if (mBlankRawSurface) {
+    mBlankLockedSurface =
+        CreateLockedSurface(mBlankRawSurface, mImageSize, mFormat);
+    if (!mBlankLockedSurface) {
+      NS_WARNING("Failed to create BlankLockedSurface");
       mAborted = true;
       return NS_ERROR_OUT_OF_MEMORY;
     }
+  }
 
-    if (mBlankRawSurface) {
-      mBlankLockedSurface =
-          CreateLockedSurface(mBlankRawSurface, mFrameRect.Size(), mFormat);
-      if (!mBlankLockedSurface) {
-        NS_WARNING("Failed to create BlankLockedSurface");
-        mAborted = true;
-        return NS_ERROR_OUT_OF_MEMORY;
-      }
-    }
+  if (!ClearSurface(mRawSurface, mImageSize, mFormat)) {
+    NS_WARNING("Could not clear allocated buffer");
+    mAborted = true;
+    return NS_ERROR_OUT_OF_MEMORY;
+  }
 
-    if (!ClearSurface(mRawSurface, mFrameRect.Size(), mFormat)) {
-      NS_WARNING("Could not clear allocated buffer");
+  if (mBlankRawSurface) {
+    if (!GreenSurface(mBlankRawSurface, mImageSize, mFormat)) {
+      NS_WARNING("Could not clear allocated blank buffer");
       mAborted = true;
       return NS_ERROR_OUT_OF_MEMORY;
     }
-
-    if (mBlankRawSurface) {
-      if (!GreenSurface(mBlankRawSurface, mFrameRect.Size(), mFormat)) {
-        NS_WARNING("Could not clear allocated blank buffer");
-        mAborted = true;
-        return NS_ERROR_OUT_OF_MEMORY;
-      }
-    }
   }
 
   return NS_OK;
 }
 
 nsresult imgFrame::InitForDecoderRecycle(const AnimationParams& aAnimParams) {
   // We want to recycle this frame, but there is no guarantee that consumers are
   // done with it in a timely manner. Let's ensure they are done with it first.
   MonitorAutoLock lock(mMonitor);
 
-  MOZ_ASSERT(mIsFullFrame);
   MOZ_ASSERT(mLockCount > 0);
   MOZ_ASSERT(mLockedSurface);
 
   if (!mShouldRecycle) {
     // This frame either was never marked as recyclable, or the flag was cleared
     // for a caller which does not support recycling.
     return NS_ERROR_NOT_AVAILABLE;
   }
@@ -413,17 +345,17 @@ nsresult imgFrame::InitForDecoderRecycle
       timeout -= delta;
     }
   }
 
   mBlendRect = aAnimParams.mBlendRect;
   mTimeout = aAnimParams.mTimeout;
   mBlendMethod = aAnimParams.mBlendMethod;
   mDisposalMethod = aAnimParams.mDisposalMethod;
-  mDirtyRect = mFrameRect;
+  mDirtyRect = GetRect();
 
   return NS_OK;
 }
 
 nsresult imgFrame::InitWithDrawable(
     gfxDrawable* aDrawable, const nsIntSize& aSize, const SurfaceFormat aFormat,
     SamplingFilter aSamplingFilter, uint32_t aImageFlags,
     gfx::BackendType aBackend, DrawTarget* aTargetDT) {
@@ -431,82 +363,78 @@ nsresult imgFrame::InitWithDrawable(
   // warn for properties related to bad content.
   if (!SurfaceCache::IsLegalSize(aSize)) {
     NS_WARNING("Should have legal image size");
     mAborted = true;
     return NS_ERROR_FAILURE;
   }
 
   mImageSize = aSize;
-  mFrameRect = IntRect(IntPoint(0, 0), aSize);
-
   mFormat = aFormat;
-  mPaletteDepth = 0;
 
   RefPtr<DrawTarget> target;
 
   bool canUseDataSurface = Factory::DoesBackendSupportDataDrawtarget(aBackend);
   if (canUseDataSurface) {
     // It's safe to use data surfaces for content on this platform, so we can
     // get away with using volatile buffers.
     MOZ_ASSERT(!mLockedSurface, "Called imgFrame::InitWithDrawable() twice?");
 
-    mRawSurface = AllocateBufferForImage(mFrameRect.Size(), mFormat);
+    mRawSurface = AllocateBufferForImage(mImageSize, mFormat);
     if (!mRawSurface) {
       mAborted = true;
       return NS_ERROR_OUT_OF_MEMORY;
     }
 
-    mLockedSurface =
-        CreateLockedSurface(mRawSurface, mFrameRect.Size(), mFormat);
+    mLockedSurface = CreateLockedSurface(mRawSurface, mImageSize, mFormat);
     if (!mLockedSurface) {
       NS_WARNING("Failed to create LockedSurface");
       mAborted = true;
       return NS_ERROR_OUT_OF_MEMORY;
     }
 
-    if (!ClearSurface(mRawSurface, mFrameRect.Size(), mFormat)) {
+    if (!ClearSurface(mRawSurface, mImageSize, mFormat)) {
       NS_WARNING("Could not clear allocated buffer");
       mAborted = true;
       return NS_ERROR_OUT_OF_MEMORY;
     }
 
     target = gfxPlatform::CreateDrawTargetForData(
-        mLockedSurface->GetData(), mFrameRect.Size(), mLockedSurface->Stride(),
+        mLockedSurface->GetData(), mImageSize, mLockedSurface->Stride(),
         mFormat);
   } else {
     // We can't use data surfaces for content, so we'll create an offscreen
     // surface instead.  This means if someone later calls RawAccessRef(), we
     // may have to do an expensive readback, but we warned callers about that in
     // the documentation for this method.
     MOZ_ASSERT(!mOptSurface, "Called imgFrame::InitWithDrawable() twice?");
 
     if (aTargetDT && !gfxVars::UseWebRender()) {
-      target = aTargetDT->CreateSimilarDrawTarget(mFrameRect.Size(), mFormat);
+      target = aTargetDT->CreateSimilarDrawTarget(mImageSize, mFormat);
     } else {
       if (gfxPlatform::GetPlatform()->SupportsAzureContentForType(aBackend)) {
         target = gfxPlatform::GetPlatform()->CreateDrawTargetForBackend(
-            aBackend, mFrameRect.Size(), mFormat);
+            aBackend, mImageSize, mFormat);
       } else {
         target = gfxPlatform::GetPlatform()->CreateOffscreenContentDrawTarget(
-            mFrameRect.Size(), mFormat);
+            mImageSize, mFormat);
       }
     }
   }
 
   if (!target || !target->IsValid()) {
     mAborted = true;
     return NS_ERROR_OUT_OF_MEMORY;
   }
 
   // Draw using the drawable the caller provided.
   RefPtr<gfxContext> ctx = gfxContext::CreateOrNull(target);
   MOZ_ASSERT(ctx);  // Already checked the draw target above.
-  gfxUtils::DrawPixelSnapped(ctx, aDrawable, SizeDouble(mFrameRect.Size()),
-                             ImageRegion::Create(ThebesRect(mFrameRect)),
+  gfxUtils::DrawPixelSnapped(ctx, aDrawable, SizeDouble(mImageSize),
+                             ImageRegion::Create(ThebesRect(GetRect())),
                              mFormat, aSamplingFilter, aImageFlags);
 
   if (canUseDataSurface && !mLockedSurface) {
     NS_WARNING("Failed to create VolatileDataSourceSurface");
     mAborted = true;
     return NS_ERROR_OUT_OF_MEMORY;
   }
 
@@ -553,17 +481,17 @@ nsresult imgFrame::Optimize(DrawTarget* 
   if (ShutdownTracker::ShutdownHasStarted()) {
     return NS_OK;
   }
 
   if (gDisableOptimize) {
     return NS_OK;
   }
 
-  if (mPalettedImageData || mOptSurface) {
+  if (mOptSurface) {
     return NS_OK;
   }
 
   // XXX(seth): It's currently unclear if there's any reason why we can't
   // optimize non-premult surfaces. We should look into removing this.
   if (mNonPremult) {
     return NS_OK;
   }
@@ -656,23 +584,16 @@ bool imgFrame::Draw(gfxContext* aContext
                     float aOpacity) {
   AUTO_PROFILER_LABEL("imgFrame::Draw", GRAPHICS);
 
   MOZ_ASSERT(NS_IsMainThread());
   NS_ASSERTION(!aRegion.Rect().IsEmpty(), "Drawing empty region!");
   NS_ASSERTION(!aRegion.IsRestricted() ||
                    !aRegion.Rect().Intersect(aRegion.Restriction()).IsEmpty(),
                "We must be allowed to sample *some* source pixels!");
-  MOZ_ASSERT(mFrameRect.IsEqualEdges(IntRect(IntPoint(), mImageSize)),
-             "Directly drawing an image with a non-trivial frame rect!");
-
-  if (mPalettedImageData) {
-    MOZ_ASSERT_UNREACHABLE("Directly drawing a paletted image!");
-    return false;
-  }
 
   // Perform the draw and freeing of the surface outside the lock. We want to
   // avoid contention with the decoder if we can. The surface may also attempt
   // to relock the monitor if it is freed (e.g. RecyclingSourceSurface).
   RefPtr<SourceSurface> surf;
   SurfaceWithFormat surfaceResult;
   ImageRegion region(aRegion);
   gfxRect imageRect(0, 0, mImageSize.width, mImageSize.height);
@@ -729,166 +650,126 @@ nsresult imgFrame::ImageUpdated(const ns
   return ImageUpdatedInternal(aUpdateRect);
 }
 
 nsresult imgFrame::ImageUpdatedInternal(const nsIntRect& aUpdateRect) {
   mMonitor.AssertCurrentThreadOwns();
 
   // Clamp to the frame rect to ensure that decoder bugs don't result in a
   // decoded rect that extends outside the bounds of the frame rect.
-  IntRect updateRect = mFrameRect.Intersect(aUpdateRect);
+  IntRect updateRect = aUpdateRect.Intersect(GetRect());
   if (updateRect.IsEmpty()) {
     return NS_OK;
   }
 
   mDecoded.UnionRect(mDecoded, updateRect);
 
-  // Paletted images cannot invalidate.
-  if (mPalettedImageData) {
-    return NS_OK;
-  }
-
   // Update our invalidation counters for any consumers watching for changes
   // in the surface.
   if (mRawSurface) {
     mRawSurface->Invalidate(updateRect);
   }
   if (mLockedSurface && mRawSurface != mLockedSurface) {
     mLockedSurface->Invalidate(updateRect);
   }
   return NS_OK;
 }
 
 void imgFrame::Finish(Opacity aFrameOpacity /* = Opacity::SOME_TRANSPARENCY */,
                       bool aFinalize /* = true */) {
   MonitorAutoLock lock(mMonitor);
   MOZ_ASSERT(mLockCount > 0, "Image data should be locked");
 
-  if (mPalettedImageData) {
-    ImageUpdatedInternal(mFrameRect);
-  } else if (!mDecoded.IsEqualEdges(mFrameRect)) {
+  IntRect frameRect(GetRect());
+  if (!mDecoded.IsEqualEdges(frameRect)) {
     // The decoder should have produced rows starting from either the bottom or
     // the top of the image. We need to calculate the region for which we have
     // not yet invalidated.
-    IntRect delta(0, 0, mFrameRect.width, 0);
+    IntRect delta(0, 0, frameRect.width, 0);
     if (mDecoded.y == 0) {
       delta.y = mDecoded.height;
-      delta.height = mFrameRect.height - mDecoded.height;
-    } else if (mDecoded.y + mDecoded.height == mFrameRect.height) {
-      delta.height = mFrameRect.height - mDecoded.y;
+      delta.height = frameRect.height - mDecoded.height;
+    } else if (mDecoded.y + mDecoded.height == frameRect.height) {
+      delta.height = frameRect.height - mDecoded.y;
     } else {
       MOZ_ASSERT_UNREACHABLE("Decoder only updated middle of image!");
-      delta = mFrameRect;
+      delta = frameRect;
     }
 
     ImageUpdatedInternal(delta);
   }
 
-  MOZ_ASSERT(mDecoded.IsEqualEdges(mFrameRect));
+  MOZ_ASSERT(mDecoded.IsEqualEdges(frameRect));
 
   if (aFinalize) {
     FinalizeSurfaceInternal();
   }
 
   mFinished = true;
 
   // The image is now complete, wake up anyone who's waiting.
   mMonitor.NotifyAll();
 }
 
 uint32_t imgFrame::GetImageBytesPerRow() const {
   mMonitor.AssertCurrentThreadOwns();
 
   if (mRawSurface) {
-    return mFrameRect.Width() * BytesPerPixel(mFormat);
-  }
-
-  if (mPaletteDepth) {
-    return mFrameRect.Width();
+    return mImageSize.width * BytesPerPixel(mFormat);
   }
 
   return 0;
 }
 
 uint32_t imgFrame::GetImageDataLength() const {
-  return GetImageBytesPerRow() * mFrameRect.Height();
+  return GetImageBytesPerRow() * mImageSize.height;
 }
 
 void imgFrame::GetImageData(uint8_t** aData, uint32_t* aLength) const {
   MonitorAutoLock lock(mMonitor);
   GetImageDataInternal(aData, aLength);
 }
 
 void imgFrame::GetImageDataInternal(uint8_t** aData, uint32_t* aLength) const {
   mMonitor.AssertCurrentThreadOwns();
   MOZ_ASSERT(mLockCount > 0, "Image data should be locked");
+  MOZ_ASSERT(mLockedSurface);
 
   if (mLockedSurface) {
     // TODO: This is okay for now because we only realloc shared surfaces on
     // the main thread after decoding has finished, but if animations want to
     // read frame data off the main thread, we will need to reconsider this.
     *aData = mLockedSurface->GetData();
     MOZ_ASSERT(
         *aData,
         "mLockedSurface is non-null, but GetData is null in GetImageData");
-  } else if (mPalettedImageData) {
-    *aData = mPalettedImageData + PaletteDataLength();
-    MOZ_ASSERT(
-        *aData,
-        "mPalettedImageData is non-null, but result is null in GetImageData");
   } else {
-    MOZ_ASSERT(
-        false,
-        "Have neither mLockedSurface nor mPalettedImageData in GetImageData");
     *aData = nullptr;
   }
 
   *aLength = GetImageDataLength();
 }
 
 uint8_t* imgFrame::GetImageData() const {
   uint8_t* data;
   uint32_t length;
   GetImageData(&data, &length);
   return data;
 }
 
-bool imgFrame::GetIsPaletted() const { return mPalettedImageData != nullptr; }
-
-void imgFrame::GetPaletteData(uint32_t** aPalette, uint32_t* length) const {
-  AssertImageDataLocked();
-
-  if (!mPalettedImageData) {
-    *aPalette = nullptr;
-    *length = 0;
-  } else {
-    *aPalette = (uint32_t*)mPalettedImageData;
-    *length = PaletteDataLength();
-  }
-}
-
-uint32_t* imgFrame::GetPaletteData() const {
-  uint32_t* data;
-  uint32_t length;
-  GetPaletteData(&data, &length);
-  return data;
-}
-
 uint8_t* imgFrame::LockImageData(bool aOnlyFinished) {
   MonitorAutoLock lock(mMonitor);
 
   MOZ_ASSERT(mLockCount >= 0, "Unbalanced locks and unlocks");
   if (mLockCount < 0 || (aOnlyFinished && !mFinished)) {
     return nullptr;
   }
 
   uint8_t* data;
-  if (mPalettedImageData) {
-    data = mPalettedImageData;
-  } else if (mLockedSurface) {
+  if (mLockedSurface) {
     data = mLockedSurface->GetData();
   } else {
     data = nullptr;
   }
 
   // If the raw data is still available, we should get a valid pointer for it.
   if (!data) {
     MOZ_ASSERT_UNREACHABLE("It's illegal to re-lock an optimized imgFrame");
@@ -995,17 +876,17 @@ already_AddRefed<SourceSurface> imgFrame
   }
 
   MOZ_ASSERT(!mShouldRecycle, "Should recycle but no locked surface!");
 
   if (!mRawSurface) {
     return nullptr;
   }
 
-  return CreateLockedSurface(mRawSurface, mFrameRect.Size(), mFormat);
+  return CreateLockedSurface(mRawSurface, mImageSize, mFormat);
 }
 
 void imgFrame::Abort() {
   MonitorAutoLock lock(mMonitor);
 
   mAborted = true;
 
   // Wake up anyone who's waiting.
@@ -1033,37 +914,24 @@ void imgFrame::WaitUntilFinished() const
 
     // Not complete yet, so we'll have to wait.
     mMonitor.Wait();
   }
 }
 
 bool imgFrame::AreAllPixelsWritten() const {
   mMonitor.AssertCurrentThreadOwns();
-  return mDecoded.IsEqualInterior(mFrameRect);
-}
-
-bool imgFrame::GetCompositingFailed() const {
-  MOZ_ASSERT(NS_IsMainThread());
-  return mCompositingFailed;
-}
-
-void imgFrame::SetCompositingFailed(bool val) {
-  MOZ_ASSERT(NS_IsMainThread());
-  mCompositingFailed = val;
+  return mDecoded.IsEqualInterior(GetRect());
 }
 
 void imgFrame::AddSizeOfExcludingThis(MallocSizeOf aMallocSizeOf,
                                       const AddSizeOfCb& aCallback) const {
   MonitorAutoLock lock(mMonitor);
 
   AddSizeOfCbData metadata;
-  if (mPalettedImageData) {
-    metadata.heap += aMallocSizeOf(mPalettedImageData);
-  }
   if (mLockedSurface) {
     metadata.heap += aMallocSizeOf(mLockedSurface);
   }
   if (mOptSurface) {
     metadata.heap += aMallocSizeOf(mOptSurface);
   }
   if (mRawSurface) {
     metadata.heap += aMallocSizeOf(mRawSurface);
--- a/image/imgFrame.h
+++ b/image/imgFrame.h
@@ -45,34 +45,20 @@ class imgFrame {
   /**
    * Initialize this imgFrame with an empty surface and prepare it for being
    * written to by a decoder.
    *
    * This is appropriate for use with decoded images, but it should not be used
    * when drawing content into an imgFrame, as it may use a different graphics
    * backend than normal content drawing.
    */
-  nsresult InitForDecoder(const nsIntSize& aImageSize, const nsIntRect& aRect,
-                          SurfaceFormat aFormat, uint8_t aPaletteDepth,
+  nsresult InitForDecoder(const nsIntSize& aImageSize, SurfaceFormat aFormat,
                           bool aNonPremult,
                           const Maybe<AnimationParams>& aAnimParams,
-                          bool aIsFullFrame, bool aShouldRecycle);
-
-  nsresult InitForAnimator(const nsIntSize& aSize, SurfaceFormat aFormat) {
-    nsIntRect frameRect(0, 0, aSize.width, aSize.height);
-    AnimationParams animParams{frameRect, FrameTimeout::Forever(),
-                               /* aFrameNum */ 1, BlendMethod::OVER,
-                               DisposalMethod::NOT_SPECIFIED};
-    // We set aIsFullFrame to false because we don't want the compositing frame
-    // to be allocated into shared memory for WebRender. mIsFullFrame is only
-    // otherwise used for frames produced by Decoder, so it isn't relevant.
-    return InitForDecoder(aSize, frameRect, aFormat, /* aPaletteDepth */ 0,
-                          /* aNonPremult */ false, Some(animParams),
-                          /* aIsFullFrame */ false, /* aShouldRecycle */ false);
-  }
+                          bool aShouldRecycle);
 
   /**
    * Reinitialize this imgFrame with the new parameters, but otherwise retain
    * the underlying buffer.
    *
    * This is appropriate for use with animated images, where the decoder was
    * given an IDecoderFrameRecycler object which may yield a recycled imgFrame
    * that was discarded to save memory.
@@ -169,45 +155,34 @@ class imgFrame {
   void WaitUntilFinished() const;
 
   /**
    * Returns the number of bytes per pixel this imgFrame requires.  This is a
    * worst-case value that does not take into account the effects of format
    * changes caused by Optimize(), since an imgFrame is not optimized throughout
    * its lifetime.
    */
-  uint32_t GetBytesPerPixel() const { return GetIsPaletted() ? 1 : 4; }
+  uint32_t GetBytesPerPixel() const { return 4; }
 
-  const IntSize& GetImageSize() const { return mImageSize; }
-  const IntRect& GetRect() const { return mFrameRect; }
-  IntSize GetSize() const { return mFrameRect.Size(); }
+  const IntSize& GetSize() const { return mImageSize; }
+  IntRect GetRect() const { return IntRect(IntPoint(0, 0), mImageSize); }
   const IntRect& GetBlendRect() const { return mBlendRect; }
   IntRect GetBoundedBlendRect() const {
-    return mBlendRect.Intersect(mFrameRect);
+    return mBlendRect.Intersect(GetRect());
   }
   FrameTimeout GetTimeout() const { return mTimeout; }
   BlendMethod GetBlendMethod() const { return mBlendMethod; }
   DisposalMethod GetDisposalMethod() const { return mDisposalMethod; }
   bool FormatHasAlpha() const { return mFormat == SurfaceFormat::B8G8R8A8; }
   void GetImageData(uint8_t** aData, uint32_t* length) const;
   uint8_t* GetImageData() const;
 
-  bool GetIsPaletted() const;
-  void GetPaletteData(uint32_t** aPalette, uint32_t* length) const;
-  uint32_t* GetPaletteData() const;
-  uint8_t GetPaletteDepth() const { return mPaletteDepth; }
-
   const IntRect& GetDirtyRect() const { return mDirtyRect; }
   void SetDirtyRect(const IntRect& aDirtyRect) { mDirtyRect = aDirtyRect; }
 
-  bool IsFullFrame() const { return mIsFullFrame; }
-
-  bool GetCompositingFailed() const;
-  void SetCompositingFailed(bool val);
-
   void SetOptimizable();
 
   void FinalizeSurface();
   already_AddRefed<SourceSurface> GetSourceSurface();
 
   struct AddSizeOfCbData {
     AddSizeOfCbData()
         : heap(0), nonHeap(0), handles(0), index(0), externalId(0) {}
@@ -251,20 +226,16 @@ class imgFrame {
   /**
    * @param aTemporary  If true, it will assume the caller does not require a
    *                    wrapping RecycleSourceSurface to protect the underlying
    *                    surface from recycling. The reference to the surface
    *                    must be freed before releasing the main thread context.
    */
   already_AddRefed<SourceSurface> GetSourceSurfaceInternal(bool aTemporary);
 
-  uint32_t PaletteDataLength() const {
-    return mPaletteDepth ? (size_t(1) << mPaletteDepth) * sizeof(uint32_t) : 0;
-  }
-
   struct SurfaceWithFormat {
     RefPtr<gfxDrawable> mDrawable;
     SurfaceFormat mFormat;
     SurfaceWithFormat() : mFormat(SurfaceFormat::UNKNOWN) {}
     SurfaceWithFormat(gfxDrawable* aDrawable, SurfaceFormat aFormat)
         : mDrawable(aDrawable), mFormat(aFormat) {}
     SurfaceWithFormat(SurfaceWithFormat&& aOther)
         : mDrawable(std::move(aOther.mDrawable)), mFormat(aOther.mFormat) {}
@@ -331,27 +302,16 @@ class imgFrame {
 
   //////////////////////////////////////////////////////////////////////////////
   // Effectively const data, only mutated in the Init methods.
   //////////////////////////////////////////////////////////////////////////////
 
   //! The size of the buffer we are decoding to.
   IntSize mImageSize;
 
-  //! XXX(aosmond): This means something different depending on the context. We
-  //!               should correct this.
-  //!
-  //! There are several different contexts for mFrameRect:
-  //! - If for non-animated image, it will be originate at (0, 0) and matches
-  //!   the dimensions of mImageSize.
-  //! - If for an APNG, it also matches the above.
-  //! - If for a GIF which is producing full frames, it matches the above.
-  //! - If for a GIF which is producing partial frames, it matches mBlendRect.
-  IntRect mFrameRect;
-
   //! The contents for the frame, as represented in the encoded image. This may
   //! differ from mImageSize because it may be a partial frame. For the first
   //! frame, this means we need to shift the data in place, and for animated
   //! frames, it likely need to combine with a previous frame to get the full
   //! contents.
   IntRect mBlendRect;
 
   //! This is the region that has changed between this frame and the previous
@@ -361,51 +321,33 @@ class imgFrame {
 
   //! The timeout for this frame.
   FrameTimeout mTimeout;
 
   DisposalMethod mDisposalMethod;
   BlendMethod mBlendMethod;
   SurfaceFormat mFormat;
 
-  // The palette and image data for images that are paletted, since Cairo
-  // doesn't support these images.
-  // The paletted data comes first, then the image data itself.
-  // Total length is PaletteDataLength() + GetImageDataLength().
-  uint8_t* mPalettedImageData;
-  uint8_t mPaletteDepth;
-
   bool mNonPremult;
-
-  //! True if the frame has all of the data stored in it, false if it needs to
-  //! be combined with another frame (e.g. the previous frame) to be complete.
-  bool mIsFullFrame;
-
-  //////////////////////////////////////////////////////////////////////////////
-  // Main-thread-only mutable data.
-  //////////////////////////////////////////////////////////////////////////////
-
-  bool mCompositingFailed;
 };
 
 /**
  * A reference to an imgFrame that holds the imgFrame's surface in memory,
  * allowing drawing. If you have a DrawableFrameRef |ref| and |if (ref)| returns
  * true, then calls to Draw() and GetSourceSurface() are guaranteed to succeed.
  */
 class DrawableFrameRef final {
   typedef gfx::DataSourceSurface DataSourceSurface;
 
  public:
   DrawableFrameRef() {}
 
   explicit DrawableFrameRef(imgFrame* aFrame) : mFrame(aFrame) {
     MOZ_ASSERT(aFrame);
     MonitorAutoLock lock(aFrame->mMonitor);
-    MOZ_ASSERT(!aFrame->GetIsPaletted(), "Paletted must use RawAccessFrameRef");
 
     if (aFrame->mRawSurface) {
       mRef.emplace(aFrame->mRawSurface, DataSourceSurface::READ);
       if (!mRef->IsMapped()) {
         mFrame = nullptr;
         mRef.reset();
       }
     } else {
@@ -449,20 +391,20 @@ class DrawableFrameRef final {
 
   RefPtr<imgFrame> mFrame;
   Maybe<DataSourceSurface::ScopedMap> mRef;
 };
 
 /**
  * A reference to an imgFrame that holds the imgFrame's surface in memory in a
  * format appropriate for access as raw data. If you have a RawAccessFrameRef
- * |ref| and |if (ref)| is true, then calls to GetImageData() and
- * GetPaletteData() are guaranteed to succeed. This guarantee is stronger than
- * DrawableFrameRef, so everything that a valid DrawableFrameRef guarantees is
- * also guaranteed by a valid RawAccessFrameRef.
+ * |ref| and |if (ref)| is true, then calls to GetImageData() is guaranteed to
+ * succeed. This guarantee is stronger than DrawableFrameRef, so everything that
+ * a valid DrawableFrameRef guarantees is also guaranteed by a valid
+ * RawAccessFrameRef.
  *
  * This may be considerably more expensive than is necessary just for drawing,
  * so only use this when you need to read or write the raw underlying image data
  * that the imgFrame holds.
  *
  * Once all an imgFrame's RawAccessFrameRefs go out of scope, new
  * RawAccessFrameRefs cannot be created.
  */
@@ -524,17 +466,16 @@ class RawAccessFrameRef final {
     if (mFrame) {
       mFrame->UnlockImageData();
     }
     mFrame = nullptr;
     mData = nullptr;
   }
 
   uint8_t* Data() const { return mData; }
-  uint32_t PaletteDataLength() const { return mFrame->PaletteDataLength(); }
 
  private:
   RawAccessFrameRef(const RawAccessFrameRef& aOther) = delete;
   RawAccessFrameRef& operator=(const RawAccessFrameRef& aOther) = delete;
 
   RefPtr<imgFrame> mFrame;
   uint8_t* mData;
 };
--- a/image/test/gtest/Common.cpp
+++ b/image/test/gtest/Common.cpp
@@ -350,17 +350,17 @@ void CheckGeneratedSurface(SourceSurface
   const int32_t heightBelow = surfaceSize.height - aRect.YMost();
   EXPECT_TRUE(RectIsSolidColor(
       aSurface, IntRect(0, aRect.YMost(), surfaceSize.width, heightBelow),
       aOuterColor, aFuzz));
 }
 
 void CheckGeneratedPalettedImage(Decoder* aDecoder, const IntRect& aRect) {
   RawAccessFrameRef currentFrame = aDecoder->GetCurrentFrameRef();
-  IntSize imageSize = currentFrame->GetImageSize();
+  IntSize imageSize = currentFrame->GetSize();
 
   // This diagram shows how the surface is divided into regions that the code
   // below tests for the correct content. The output rect is the bounds of the
   // region labeled 'C'.
   //
   // +---------------------------+
   // |             A             |
   // +---------+--------+--------+
--- a/image/test/gtest/TestADAM7InterpolatingFilter.cpp
+++ b/image/test/gtest/TestADAM7InterpolatingFilter.cpp
@@ -588,21 +588,8 @@ TEST(ImageADAM7InterpolatingFilter, ADAM
   // A 0x0 input size is invalid, so configuration should fail.
   AssertConfiguringADAM7InterpolatingFilterFails(IntSize(0, 0));
 }
 
 TEST(ImageADAM7InterpolatingFilter, ADAM7InterpolationFailsForMinus1_Minus1) {
   // A negative input size is invalid, so configuration should fail.
   AssertConfiguringADAM7InterpolatingFilterFails(IntSize(-1, -1));
 }
-
-TEST(ImageADAM7InterpolatingFilter,
-     ConfiguringPalettedADAM7InterpolatingFilterFails) {
-  RefPtr<Decoder> decoder = CreateTrivialDecoder();
-  ASSERT_TRUE(decoder != nullptr);
-
-  // ADAM7InterpolatingFilter does not support paletted images, so configuration
-  // should fail.
-  AssertConfiguringPipelineFails(
-      decoder, ADAM7InterpolatingConfig{},
-      PalettedSurfaceConfig{decoder, IntSize(100, 100), IntRect(0, 0, 50, 50),
-                            SurfaceFormat::B8G8R8A8, 8, false});
-}
--- a/image/test/gtest/TestAnimationFrameBuffer.cpp
+++ b/image/test/gtest/TestAnimationFrameBuffer.cpp
@@ -13,19 +13,18 @@ using namespace mozilla::image;
 
 static already_AddRefed<imgFrame> CreateEmptyFrame(
     const IntSize& aSize = IntSize(1, 1),
     const IntRect& aFrameRect = IntRect(0, 0, 1, 1), bool aCanRecycle = true) {
   RefPtr<imgFrame> frame = new imgFrame();
   AnimationParams animParams{aFrameRect, FrameTimeout::Forever(),
                              /* aFrameNum */ 1, BlendMethod::OVER,
                              DisposalMethod::NOT_SPECIFIED};
-  nsresult rv = frame->InitForDecoder(aSize, IntRect(IntPoint(0, 0), aSize),
-                                      SurfaceFormat::B8G8R8A8, 0, false,
-                                      Some(animParams), true, aCanRecycle);
+  nsresult rv = frame->InitForDecoder(aSize, SurfaceFormat::B8G8R8A8, false,
+                                      Some(animParams), aCanRecycle);
   EXPECT_TRUE(NS_SUCCEEDED(rv));
   RawAccessFrameRef frameRef = frame->RawAccessRef();
   frame->SetRawAccessOnly();
   // Normally the blend animation filter would set the dirty rect, but since
   // we aren't producing an actual animation here, we need to fake it.
   frame->SetDirtyRect(aFrameRect);
   frame->Finish();
   return frame.forget();
--- a/image/test/gtest/TestBlendAnimationFilter.cpp
+++ b/image/test/gtest/TestBlendAnimationFilter.cpp
@@ -17,17 +17,17 @@
 
 using namespace mozilla;
 using namespace mozilla::gfx;
 using namespace mozilla::image;
 
 static already_AddRefed<Decoder> CreateTrivialBlendingDecoder() {
   gfxPrefs::GetSingleton();
   DecoderType decoderType = DecoderFactory::GetDecoderType("image/gif");
-  DecoderFlags decoderFlags = DecoderFlags::BLEND_ANIMATION;
+  DecoderFlags decoderFlags = DefaultDecoderFlags();
   SurfaceFlags surfaceFlags = DefaultSurfaceFlags();
   auto sourceBuffer = MakeNotNull<RefPtr<SourceBuffer>>();
   return DecoderFactory::CreateAnonymousDecoder(
       decoderType, sourceBuffer, Nothing(), decoderFlags, surfaceFlags);
 }
 
 template <typename Func>
 RawAccessFrameRef WithBlendAnimationFilter(Decoder* aDecoder,
--- a/image/test/gtest/TestDecoders.cpp
+++ b/image/test/gtest/TestDecoders.cpp
@@ -340,17 +340,17 @@ static void WithSingleChunkAnimationDeco
   RefPtr<IDecodingTask> task = DecoderFactory::CreateMetadataDecoder(
       decoderType, rasterImage, sourceBuffer);
   ASSERT_TRUE(task != nullptr);
 
   // Run the metadata decoder synchronously.
   task->Run();
 
   // Create a decoder.
-  DecoderFlags decoderFlags = DecoderFlags::BLEND_ANIMATION;
+  DecoderFlags decoderFlags = DefaultDecoderFlags();
   SurfaceFlags surfaceFlags = DefaultSurfaceFlags();
   RefPtr<Decoder> decoder = DecoderFactory::CreateAnonymousDecoder(
       decoderType, sourceBuffer, Nothing(), decoderFlags, surfaceFlags);
   ASSERT_TRUE(decoder != nullptr);
 
   // Create an AnimationSurfaceProvider which will manage the decoding process
   // and make this decoder's output available in the surface cache.
   SurfaceKey surfaceKey = RasterSurfaceKey(aTestCase.mOutputSize, surfaceFlags,
--- a/image/test/gtest/TestDeinterlacingFilter.cpp
+++ b/image/test/gtest/TestDeinterlacingFilter.cpp
@@ -25,28 +25,16 @@ void WithDeinterlacingFilter(const IntSi
   ASSERT_TRUE(bool(decoder));
 
   WithFilterPipeline(
       decoder, std::forward<Func>(aFunc),
       DeinterlacingConfig<uint32_t>{aProgressiveDisplay},
       SurfaceConfig{decoder, aSize, SurfaceFormat::B8G8R8A8, false});
 }
 
-template <typename Func>
-void WithPalettedDeinterlacingFilter(const IntSize& aSize, Func aFunc) {
-  RefPtr<Decoder> decoder = CreateTrivialDecoder();
-  ASSERT_TRUE(decoder != nullptr);
-
-  WithFilterPipeline(
-      decoder, std::forward<Func>(aFunc),
-      DeinterlacingConfig<uint8_t>{/* mProgressiveDisplay = */ true},
-      PalettedSurfaceConfig{decoder, aSize, IntRect(0, 0, 100, 100),
-                            SurfaceFormat::B8G8R8A8, 8, false});
-}
-
 void AssertConfiguringDeinterlacingFilterFails(const IntSize& aSize) {
   RefPtr<Decoder> decoder = CreateTrivialDecoder();
   ASSERT_TRUE(decoder != nullptr);
 
   AssertConfiguringPipelineFails(
       decoder, DeinterlacingConfig<uint32_t>{/* mProgressiveDisplay = */ true},
       SurfaceConfig{decoder, aSize, SurfaceFormat::B8G8R8A8, false});
 }
@@ -111,23 +99,16 @@ TEST_F(ImageDeinterlacingFilter, WritePi
                           [](Decoder* aDecoder, SurfaceFilter* aFilter) {
                             CheckWritePixels(
                                 aDecoder, aFilter,
                                 /* aOutputRect = */ Some(IntRect(0, 0, 1, 1)),
                                 /* aInputRect = */ Some(IntRect(0, 0, 1, 1)));
                           });
 }
 
-TEST_F(ImageDeinterlacingFilter, PalettedWritePixels) {
-  WithPalettedDeinterlacingFilter(
-      IntSize(100, 100), [](Decoder* aDecoder, SurfaceFilter* aFilter) {
-        CheckPalettedWritePixels(aDecoder, aFilter);
-      });
-}
-
 TEST_F(ImageDeinterlacingFilter, WritePixelsNonProgressiveOutput51_52) {
   WithDeinterlacingFilter(
       IntSize(51, 52), /* aProgressiveDisplay = */ false,
       [](Decoder* aDecoder, SurfaceFilter* aFilter) {
         // Fill the image. The output should be green for even rows and red for
         // odd rows but we need to write the rows in the order that the
         // deinterlacer expects them.
         uint32_t count = 0;
--- a/image/test/gtest/TestDownscalingFilter.cpp
+++ b/image/test/gtest/TestDownscalingFilter.cpp
@@ -209,20 +209,8 @@ TEST(ImageDownscalingFilter, WritePixels
         EXPECT_TRUE(RowsAreSolidColor(surface, 6, 3, BGRAColor::Red(),
                                       /* aFuzz = */ 3));
         EXPECT_TRUE(RowsAreSolidColor(surface, 11, 3, BGRAColor::Green(),
                                       /* aFuzz = */ 3));
         EXPECT_TRUE(RowsAreSolidColor(surface, 16, 4, BGRAColor::Red(),
                                       /* aFuzz = */ 3));
       });
 }
-
-TEST(ImageDownscalingFilter, ConfiguringPalettedDownscaleFails) {
-  RefPtr<Decoder> decoder = CreateTrivialDecoder();
-  ASSERT_TRUE(decoder != nullptr);
-
-  // DownscalingFilter does not support paletted images, so configuration should
-  // fail.
-  AssertConfiguringPipelineFails(
-      decoder, DownscalingConfig{IntSize(100, 100), SurfaceFormat::B8G8R8A8},
-      PalettedSurfaceConfig{decoder, IntSize(20, 20), IntRect(0, 0, 20, 20),
-                            SurfaceFormat::B8G8R8A8, 8, false});
-}
--- a/image/test/gtest/TestFrameAnimator.cpp
+++ b/image/test/gtest/TestFrameAnimator.cpp
@@ -37,17 +37,17 @@ static void CheckFrameAnimatorBlendResul
       aImage->GetFrame(imgIContainer::FRAME_CURRENT, imgIContainer::FLAG_NONE);
   ASSERT_TRUE(surface != nullptr);
   CheckGeneratedSurface(surface, IntRect(0, 0, 50, 50), BGRAColor::Green(),
                         BGRAColor::Red());
 }
 
 template <typename Func>
 static void WithFrameAnimatorDecode(const ImageTestCase& aTestCase,
-                                    bool aBlendFilter, Func aResultChecker) {
+                                    Func aResultChecker) {
   // Create an image.
   RefPtr<Image> image = ImageFactory::CreateAnonymousImage(
       nsDependentCString(aTestCase.mMimeType));
   ASSERT_TRUE(!image->HasError());
 
   NotNull<RefPtr<RasterImage>> rasterImage =
       WrapNotNull(static_cast<RasterImage*>(image.get()));
 
@@ -75,61 +75,44 @@ static void WithFrameAnimatorDecode(cons
 
   // Run the metadata decoder synchronously.
   task->Run();
   task = nullptr;
 
   // Create an AnimationSurfaceProvider which will manage the decoding process
   // and make this decoder's output available in the surface cache.
   DecoderFlags decoderFlags = DefaultDecoderFlags();
-  if (aBlendFilter) {
-    decoderFlags |= DecoderFlags::BLEND_ANIMATION;
-  }
   SurfaceFlags surfaceFlags = DefaultSurfaceFlags();
   rv = DecoderFactory::CreateAnimationDecoder(
       decoderType, rasterImage, sourceBuffer, aTestCase.mSize, decoderFlags,
       surfaceFlags, 0, getter_AddRefs(task));
   EXPECT_EQ(rv, NS_OK);
   ASSERT_TRUE(task != nullptr);
 
   // Run the full decoder synchronously.
   task->Run();
 
   // Call the lambda to verify the expected results.
   aResultChecker(rasterImage.get());
 }
 
-static void CheckFrameAnimatorBlend(const ImageTestCase& aTestCase,
-                                    bool aBlendFilter) {
-  WithFrameAnimatorDecode(aTestCase, aBlendFilter, [&](RasterImage* aImage) {
+static void CheckFrameAnimatorBlend(const ImageTestCase& aTestCase) {
+  WithFrameAnimatorDecode(aTestCase, [&](RasterImage* aImage) {
     CheckFrameAnimatorBlendResults(aTestCase, aImage);
   });
 }
 
 class ImageFrameAnimator : public ::testing::Test {
  protected:
   AutoInitializeImageLib mInit;
 };
 
-TEST_F(ImageFrameAnimator, BlendGIFWithAnimator) {
-  CheckFrameAnimatorBlend(BlendAnimatedGIFTestCase(), /* aBlendFilter */ false);
-}
-
 TEST_F(ImageFrameAnimator, BlendGIFWithFilter) {
-  CheckFrameAnimatorBlend(BlendAnimatedGIFTestCase(), /* aBlendFilter */ true);
-}
-
-TEST_F(ImageFrameAnimator, BlendPNGWithAnimator) {
-  CheckFrameAnimatorBlend(BlendAnimatedPNGTestCase(), /* aBlendFilter */ false);
+  CheckFrameAnimatorBlend(BlendAnimatedGIFTestCase());
 }
 
 TEST_F(ImageFrameAnimator, BlendPNGWithFilter) {
-  CheckFrameAnimatorBlend(BlendAnimatedPNGTestCase(), /* aBlendFilter */ true);
-}
-
-TEST_F(ImageFrameAnimator, BlendWebPWithAnimator) {
-  CheckFrameAnimatorBlend(BlendAnimatedWebPTestCase(),
-                          /* aBlendFilter */ false);
+  CheckFrameAnimatorBlend(BlendAnimatedPNGTestCase());
 }
 
 TEST_F(ImageFrameAnimator, BlendWebPWithFilter) {
-  CheckFrameAnimatorBlend(BlendAnimatedWebPTestCase(), /* aBlendFilter */ true);
+  CheckFrameAnimatorBlend(BlendAnimatedWebPTestCase());
 }
--- a/image/test/gtest/TestRemoveFrameRectFilter.cpp
+++ b/image/test/gtest/TestRemoveFrameRectFilter.cpp
@@ -284,20 +284,8 @@ TEST(ImageRemoveFrameRectFilter, RemoveF
 }
 
 TEST(ImageRemoveFrameRectFilter,
      RemoveFrameRectFailsFor100_100_to_0_0_Minus1_Minus1) {
   // A negative size frame rect is disallowed.
   AssertConfiguringRemoveFrameRectFilterFails(IntSize(100, 100),
                                               IntRect(0, 0, -1, -1));
 }
-
-TEST(ImageRemoveFrameRectFilter, ConfiguringPalettedRemoveFrameRectFails) {
-  RefPtr<Decoder> decoder = CreateTrivialDecoder();
-  ASSERT_TRUE(decoder != nullptr);
-
-  // RemoveFrameRectFilter does not support paletted images, so configuration
-  // should fail.
-  AssertConfiguringPipelineFails(
-      decoder, RemoveFrameRectConfig{IntRect(0, 0, 50, 50)},
-      PalettedSurfaceConfig{decoder, IntSize(100, 100), IntRect(0, 0, 50, 50),
-                            SurfaceFormat::B8G8R8A8, 8, false});
-}
--- a/image/test/gtest/TestSurfacePipeIntegration.cpp
+++ b/image/test/gtest/TestSurfacePipeIntegration.cpp
@@ -75,57 +75,16 @@ void CheckSurfacePipeMethodResults(Surfa
   EXPECT_EQ(IntRect(0, 0, 100, 100), invalidRect->mOutputSpaceRect);
 
   aPipe->ResetToFirstRow();
   EXPECT_FALSE(aPipe->IsSurfaceFinished());
   invalidRect = aPipe->TakeInvalidRect();
   EXPECT_TRUE(invalidRect.isNothing());
 }
 
-void CheckPalettedSurfacePipeMethodResults(
-    SurfacePipe* aPipe, Decoder* aDecoder,
-    const IntRect& aRect = IntRect(0, 0, 100, 100)) {
-  // Check that the pipeline ended up in the state we expect.  Note that we're
-  // explicitly testing the SurfacePipe versions of these methods, so we don't
-  // want to use AssertCorrectPipelineFinalState() here.
-  EXPECT_TRUE(aPipe->IsSurfaceFinished());
-  Maybe<SurfaceInvalidRect> invalidRect = aPipe->TakeInvalidRect();
-  EXPECT_TRUE(invalidRect.isSome());
-  EXPECT_EQ(IntRect(0, 0, 100, 100), invalidRect->mInputSpaceRect);
-  EXPECT_EQ(IntRect(0, 0, 100, 100), invalidRect->mOutputSpaceRect);
-
-  // Check the generated image.
-  CheckGeneratedPalettedImage(aDecoder, aRect);
-
-  // Reset and clear the image before the next test.
-  aPipe->ResetToFirstRow();
-  EXPECT_FALSE(aPipe->IsSurfaceFinished());
-  invalidRect = aPipe->TakeInvalidRect();
-  EXPECT_TRUE(invalidRect.isNothing());
-
-  uint32_t count = 0;
-  auto result = aPipe->WritePixels<uint8_t>([&]() {
-    ++count;
-    return AsVariant(uint8_t(0));
-  });
-  EXPECT_EQ(WriteState::FINISHED, result);
-  EXPECT_EQ(100u * 100u, count);
-
-  EXPECT_TRUE(aPipe->IsSurfaceFinished());
-  invalidRect = aPipe->TakeInvalidRect();
-  EXPECT_TRUE(invalidRect.isSome());
-  EXPECT_EQ(IntRect(0, 0, 100, 100), invalidRect->mInputSpaceRect);
-  EXPECT_EQ(IntRect(0, 0, 100, 100), invalidRect->mOutputSpaceRect);
-
-  aPipe->ResetToFirstRow();
-  EXPECT_FALSE(aPipe->IsSurfaceFinished());
-  invalidRect = aPipe->TakeInvalidRect();
-  EXPECT_TRUE(invalidRect.isNothing());
-}
-
 class ImageSurfacePipeIntegration : public ::testing::Test {
  protected:
   AutoInitializeImageLib mInit;
 };
 
 TEST_F(ImageSurfacePipeIntegration, SurfacePipe) {
   // Test that SurfacePipe objects can be initialized and move constructed.
   SurfacePipe pipe = TestSurfacePipeFactory::SimpleSurfacePipe();
@@ -220,110 +179,16 @@ TEST_F(ImageSurfacePipeIntegration, Surf
     CheckSurfacePipeMethodResults(&pipe, decoder, IntRect(0, 0, 0, 0));
   }
 
   // Mark the frame as finished so we don't get an assertion.
   RawAccessFrameRef currentFrame = decoder->GetCurrentFrameRef();
   currentFrame->Finish();
 }
 
-TEST_F(ImageSurfacePipeIntegration, PalettedSurfacePipe) {
-  // Create a SurfacePipe containing a PalettedSurfaceSink.
-  RefPtr<Decoder> decoder = CreateTrivialDecoder();
-  ASSERT_TRUE(decoder != nullptr);
-
-  auto sink = MakeUnique<PalettedSurfaceSink>();
-  nsresult rv = sink->Configure(
-      PalettedSurfaceConfig{decoder, IntSize(100, 100), IntRect(0, 0, 100, 100),
-                            SurfaceFormat::B8G8R8A8, 8, false});
-  ASSERT_TRUE(NS_SUCCEEDED(rv));
-
-  SurfacePipe pipe = TestSurfacePipeFactory::SurfacePipeFromPipeline(sink);
-
-  // Test that WritePixels() gets passed through to the underlying pipeline.
-  {
-    uint32_t count = 0;
-    auto result = pipe.WritePixels<uint8_t>([&]() {
-      ++count;
-      return AsVariant(uint8_t(255));
-    });
-    EXPECT_EQ(WriteState::FINISHED, result);
-    EXPECT_EQ(100u * 100u, count);
-    CheckPalettedSurfacePipeMethodResults(&pipe, decoder);
-  }
-
-  // Create a buffer the same size as one row of the surface, containing all
-  // 255 pixels. We'll use this for the WriteBuffer() tests.
-  uint8_t buffer[100];
-  for (int i = 0; i < 100; ++i) {
-    buffer[i] = 255;
-  }
-
-  // Test that WriteBuffer() gets passed through to the underlying pipeline.
-  {
-    uint32_t count = 0;
-    WriteState result = WriteState::NEED_MORE_DATA;
-    while (result == WriteState::NEED_MORE_DATA) {
-      result = pipe.WriteBuffer(buffer);
-      ++count;
-    }
-    EXPECT_EQ(WriteState::FINISHED, result);
-    EXPECT_EQ(100u, count);
-    CheckPalettedSurfacePipeMethodResults(&pipe, decoder);
-  }
-
-  // Test that the 3 argument version of WriteBuffer() gets passed through to
-  // the underlying pipeline.
-  {
-    uint32_t count = 0;
-    WriteState result = WriteState::NEED_MORE_DATA;
-    while (result == WriteState::NEED_MORE_DATA) {
-      result = pipe.WriteBuffer(buffer, 0, 100);
-      ++count;
-    }
-    EXPECT_EQ(WriteState::FINISHED, result);
-    EXPECT_EQ(100u, count);
-    CheckPalettedSurfacePipeMethodResults(&pipe, decoder);
-  }
-
-  // Test that WritePixelBlocks() gets passed through to the underlying
-  // pipeline.
-  {
-    uint32_t count = 0;
-    WriteState result = pipe.WritePixelBlocks<uint8_t>(
-        [&](uint8_t* aBlockStart, int32_t aLength) {
-          ++count;
-          EXPECT_EQ(int32_t(100), aLength);
-          memcpy(aBlockStart, buffer, 100 * sizeof(uint8_t));
-          return MakeTuple(int32_t(100), Maybe<WriteState>());
-        });
-
-    EXPECT_EQ(WriteState::FINISHED, result);
-    EXPECT_EQ(100u, count);
-    CheckPalettedSurfacePipeMethodResults(&pipe, decoder);
-  }
-
-  // Test that WriteEmptyRow() gets passed through to the underlying pipeline.
-  {
-    uint32_t count = 0;
-    WriteState result = WriteState::NEED_MORE_DATA;
-    while (result == WriteState::NEED_MORE_DATA) {
-      result = pipe.WriteEmptyRow();
-      ++count;
-    }
-    EXPECT_EQ(WriteState::FINISHED, result);
-    EXPECT_EQ(100u, count);
-    CheckPalettedSurfacePipeMethodResults(&pipe, decoder, IntRect(0, 0, 0, 0));
-  }
-
-  // Mark the frame as finished so we don't get an assertion.
-  RawAccessFrameRef currentFrame = decoder->GetCurrentFrameRef();
-  currentFrame->Finish();
-}
-
 TEST_F(ImageSurfacePipeIntegration, DeinterlaceDownscaleWritePixels) {
   RefPtr<Decoder> decoder = CreateTrivialDecoder();
   ASSERT_TRUE(decoder != nullptr);
 
   auto test = [](Decoder* aDecoder, SurfaceFilter* aFilter) {
     CheckWritePixels(aDecoder, aFilter,
                      /* aOutputRect = */ Some(IntRect(0, 0, 25, 25)));
   };
@@ -463,44 +328,16 @@ TEST_F(ImageSurfacePipeIntegration,
   WithFilterPipeline(
       decoder, test,
       DeinterlacingConfig<uint32_t>{/* mProgressiveDisplay = */ true},
       RemoveFrameRectConfig{IntRect(50, 50, 100, 100)},
       DownscalingConfig{IntSize(100, 100), SurfaceFormat::B8G8R8A8},
       SurfaceConfig{decoder, IntSize(20, 20), SurfaceFormat::B8G8R8A8, false});
 }
 
-TEST_F(ImageSurfacePipeIntegration,
-       ConfiguringPalettedRemoveFrameRectDownscaleFails) {
-  RefPtr<Decoder> decoder = CreateTrivialDecoder();
-  ASSERT_TRUE(decoder != nullptr);
-
-  // This is an invalid pipeline for paletted images, so configuration should
-  // fail.
-  AssertConfiguringPipelineFails(
-      decoder, RemoveFrameRectConfig{IntRect(0, 0, 50, 50)},
-      DownscalingConfig{IntSize(100, 100), SurfaceFormat::B8G8R8A8},
-      PalettedSurfaceConfig{decoder, IntSize(100, 100), IntRect(0, 0, 50, 50),
-                            SurfaceFormat::B8G8R8A8, 8, false});
-}
-
-TEST_F(ImageSurfacePipeIntegration,
-       ConfiguringPalettedDeinterlaceDownscaleFails) {
-  RefPtr<Decoder> decoder = CreateTrivialDecoder();
-  ASSERT_TRUE(decoder != nullptr);
-
-  // This is an invalid pipeline for paletted images, so configuration should
-  // fail.
-  AssertConfiguringPipelineFails(
-      decoder, DeinterlacingConfig<uint8_t>{/* mProgressiveDisplay = */ true},
-      DownscalingConfig{IntSize(100, 100), SurfaceFormat::B8G8R8A8},
-      PalettedSurfaceConfig{decoder, IntSize(100, 100), IntRect(0, 0, 20, 20),
-                            SurfaceFormat::B8G8R8A8, 8, false});
-}
-
 TEST_F(ImageSurfacePipeIntegration, ConfiguringHugeDeinterlacingBufferFails) {
   RefPtr<Decoder> decoder = CreateTrivialDecoder();
   ASSERT_TRUE(decoder != nullptr);
 
   // When DownscalingFilter is used, we may succeed in allocating an output
   // surface for huge images, because we only need to store the scaled-down
   // version of the image. However, regardless of downscaling,
   // DeinterlacingFilter needs to allocate a buffer as large as the size of the
--- a/image/test/gtest/TestSurfaceSink.cpp
+++ b/image/test/gtest/TestSurfaceSink.cpp
@@ -41,27 +41,16 @@ void WithSurfaceSink(Func aFunc) {
 
   const bool flipVertically = Orientation == Orient::FLIP_VERTICALLY;
 
   WithFilterPipeline(decoder, std::forward<Func>(aFunc),
                      SurfaceConfig{decoder, IntSize(100, 100),
                                    SurfaceFormat::B8G8R8A8, flipVertically});
 }
 
-template <typename Func>
-void WithPalettedSurfaceSink(const IntRect& aFrameRect, Func aFunc) {
-  RefPtr<Decoder> decoder = CreateTrivialDecoder();
-  ASSERT_TRUE(decoder != nullptr);
-
-  WithFilterPipeline(
-      decoder, std::forward<Func>(aFunc),
-      PalettedSurfaceConfig{decoder, IntSize(100, 100), aFrameRect,
-                            SurfaceFormat::B8G8R8A8, 8, false});
-}
-
 void ResetForNextPass(SurfaceFilter* aSink) {
   aSink->ResetToFirstRow();
   EXPECT_FALSE(aSink->IsSurfaceFinished());
   Maybe<SurfaceInvalidRect> invalidRect = aSink->TakeInvalidRect();
   EXPECT_TRUE(invalidRect.isNothing());
 }
 
 template <typename WriteFunc, typename CheckFunc>
@@ -90,28 +79,16 @@ void CheckIterativeWrite(Decoder* aDecod
                          const IntRect& aOutputRect, WriteFunc aWriteFunc) {
   // Ignore the row passed to WriteFunc, since no callers use it.
   auto writeFunc = [&](uint32_t) { return aWriteFunc(); };
 
   DoCheckIterativeWrite(aSink, writeFunc,
                         [&] { CheckGeneratedImage(aDecoder, aOutputRect); });
 }
 
-template <typename WriteFunc>
-void CheckPalettedIterativeWrite(Decoder* aDecoder, PalettedSurfaceSink* aSink,
-                                 const IntRect& aOutputRect,
-                                 WriteFunc aWriteFunc) {
-  // Ignore the row passed to WriteFunc, since no callers use it.
-  auto writeFunc = [&](uint32_t) { return aWriteFunc(); };
-
-  DoCheckIterativeWrite(aSink, writeFunc, [&] {
-    CheckGeneratedPalettedImage(aDecoder, aOutputRect);
-  });
-}
-
 TEST(ImageSurfaceSink, SurfaceSinkInitialization) {
   WithSurfaceSink<Orient::NORMAL>([](Decoder* aDecoder, SurfaceSink* aSink) {
     // Check initial state.
     EXPECT_FALSE(aSink->IsSurfaceFinished());
     Maybe<SurfaceInvalidRect> invalidRect = aSink->TakeInvalidRect();
     EXPECT_TRUE(invalidRect.isNothing());
 
     // Check that the surface is zero-initialized. We verify this by calling
@@ -956,580 +933,8 @@ TEST(ImageSurfaceSink, SurfaceSinkFlipVe
 
       // Check that the generated image is correct.
       RawAccessFrameRef currentFrame = aDecoder->GetCurrentFrameRef();
       RefPtr<SourceSurface> surface = currentFrame->GetSourceSurface();
       EXPECT_TRUE(IsSolidColor(surface, BGRAColor::Green()));
     }
   });
 }
-
-TEST(ImageSurfaceSink, PalettedSurfaceSinkInitialization) {
-  WithPalettedSurfaceSink(
-      IntRect(0, 0, 100, 100),
-      [](Decoder* aDecoder, PalettedSurfaceSink* aSink) {
-        // Check initial state.
-        EXPECT_FALSE(aSink->IsSurfaceFinished());
-        Maybe<SurfaceInvalidRect> invalidRect = aSink->TakeInvalidRect();
-        EXPECT_TRUE(invalidRect.isNothing());
-
-        // Check that the paletted image data is zero-initialized.
-        RawAccessFrameRef currentFrame = aDecoder->GetCurrentFrameRef();
-        uint8_t* imageData = nullptr;
-        uint32_t imageLength = 0;
-        currentFrame->GetImageData(&imageData, &imageLength);
-        ASSERT_TRUE(imageData != nullptr);
-        ASSERT_EQ(100u * 100u, imageLength);
-        for (uint32_t i = 0; i < imageLength; ++i) {
-          ASSERT_EQ(uint8_t(0), imageData[i]);
-        }
-      });
-}
-
-TEST(ImageSurfaceSink, PalettedSurfaceSinkWritePixelsFor0_0_100_100) {
-  WithPalettedSurfaceSink(IntRect(0, 0, 100, 100),
-                          [](Decoder* aDecoder, PalettedSurfaceSink* aSink) {
-                            CheckPalettedWritePixels(aDecoder, aSink);
-                          });
-}
-
-TEST(ImageSurfaceSink, PalettedSurfaceSinkWritePixelsFor25_25_50_50) {
-  WithPalettedSurfaceSink(
-      IntRect(25, 25, 50, 50),
-      [](Decoder* aDecoder, PalettedSurfaceSink* aSink) {
-        CheckPalettedWritePixels(
-            aDecoder, aSink,
-            /* aOutputRect = */ Some(IntRect(0, 0, 50, 50)),
-            /* aInputRect = */ Some(IntRect(0, 0, 50, 50)),
-            /* aInputWriteRect = */ Some(IntRect(25, 25, 50, 50)),
-            /* aOutputWriteRect = */ Some(IntRect(25, 25, 50, 50)));
-      });
-}
-
-TEST(ImageSurfaceSink, PalettedSurfaceSinkWritePixelsForMinus25_Minus25_50_50) {
-  WithPalettedSurfaceSink(
-      IntRect(-25, -25, 50, 50),
-      [](Decoder* aDecoder, PalettedSurfaceSink* aSink) {
-        CheckPalettedWritePixels(
-            aDecoder, aSink,
-            /* aOutputRect = */ Some(IntRect(0, 0, 50, 50)),
-            /* aInputRect = */ Some(IntRect(0, 0, 50, 50)),
-            /* aInputWriteRect = */ Some(IntRect(-25, -25, 50, 50)),
-            /* aOutputWriteRect = */ Some(IntRect(-25, -25, 50, 50)));
-      });
-}
-
-TEST(ImageSurfaceSink, PalettedSurfaceSinkWritePixelsFor75_Minus25_50_50) {
-  WithPalettedSurfaceSink(
-      IntRect(75, -25, 50, 50),
-      [](Decoder* aDecoder, PalettedSurfaceSink* aSink) {
-        CheckPalettedWritePixels(
-            aDecoder, aSink,
-            /* aOutputRect = */ Some(IntRect(0, 0, 50, 50)),
-            /* aInputRect = */ Some(IntRect(0, 0, 50, 50)),
-            /* aInputWriteRect = */ Some(IntRect(75, -25, 50, 50)),
-            /* aOutputWriteRect = */ Some(IntRect(75, -25, 50, 50)));
-      });
-}
-
-TEST(ImageSurfaceSink, PalettedSurfaceSinkWritePixelsForMinus25_75_50_50) {
-  WithPalettedSurfaceSink(
-      IntRect(-25, 75, 50, 50),
-      [](Decoder* aDecoder, PalettedSurfaceSink* aSink) {
-        CheckPalettedWritePixels(
-            aDecoder, aSink,
-            /* aOutputRect = */ Some(IntRect(0, 0, 50, 50)),
-            /* aInputRect = */ Some(IntRect(0, 0, 50, 50)),
-            /* aInputWriteRect = */ Some(IntRect(-25, 75, 50, 50)),
-            /* aOutputWriteRect = */ Some(IntRect(-25, 75, 50, 50)));
-      });
-}
-
-TEST(ImageSurfaceSink, PalettedSurfaceSinkWritePixelsFor75_75_50_50) {
-  WithPalettedSurfaceSink(
-      IntRect(75, 75, 50, 50),
-      [](Decoder* aDecoder, PalettedSurfaceSink* aSink) {
-        CheckPalettedWritePixels(
-            aDecoder, aSink,
-            /* aOutputRect = */ Some(IntRect(0, 0, 50, 50)),
-            /* aInputRect = */ Some(IntRect(0, 0, 50, 50)),
-            /* aInputWriteRect = */ Some(IntRect(75, 75, 50, 50)),
-            /* aOutputWriteRect = */ Some(IntRect(75, 75, 50, 50)));
-      });
-}
-
-TEST(ImageSurfaceSink, PalettedSurfaceSinkWritePixelsFinish) {
-  WithPalettedSurfaceSink(IntRect(0, 0, 100, 100),
-                          [](Decoder* aDecoder, PalettedSurfaceSink* aSink) {
-                            // Write nothing into the surface; just finish
-                            // immediately.
-                            uint32_t count = 0;
-                            auto result = aSink->WritePixels<uint8_t>([&] {
-                              count++;
-                              return AsVariant(WriteState::FINISHED);
-                            });
-                            EXPECT_EQ(WriteState::FINISHED, result);
-                            EXPECT_EQ(1u, count);
-
-                            AssertCorrectPipelineFinalState(
-                                aSink, IntRect(0, 0, 100, 100),
-                                IntRect(0, 0, 100, 100));
-
-                            // Attempt to write more and make sure that nothing
-                            // gets written.
-                            count = 0;
-                            result = aSink->WritePixels<uint8_t>([&]() {
-                              count++;
-                              return AsVariant(uint8_t(128));
-                            });
-                            EXPECT_EQ(WriteState::FINISHED, result);
-                            EXPECT_EQ(0u, count);
-                            EXPECT_TRUE(aSink->IsSurfaceFinished());
-
-                            // Check that the generated image is correct.
-                            EXPECT_TRUE(IsSolidPalettedColor(aDecoder, 0));
-                          });
-}
-
-TEST(ImageSurfaceSink, PalettedSurfaceSinkWritePixelsEarlyExit) {
-  auto checkEarlyExit = [](Decoder* aDecoder, PalettedSurfaceSink* aSink,
-                           WriteState aState) {
-    // Write half a row of green pixels and then exit early with |aState|. If
-    // the lambda keeps getting called, we'll write red pixels, which will cause
-    // the test to fail.
-    uint32_t count = 0;
-    auto result = aSink->WritePixels<uint8_t>([&]() -> NextPixel<uint8_t> {
-      if (count == 50) {
-        return AsVariant(aState);
-      }
-      return count++ < 50 ? AsVariant(uint8_t(255)) : AsVariant(uint8_t(128));
-    });
-
-    EXPECT_EQ(aState, result);
-    EXPECT_EQ(50u, count);
-    CheckGeneratedPalettedImage(aDecoder, IntRect(0, 0, 50, 1));
-
-    if (aState != WriteState::FINISHED) {
-      // We should still be able to write more at this point.
-      EXPECT_FALSE(aSink->IsSurfaceFinished());
-
-      // Verify that we can resume writing. We'll finish up the same row.
-      count = 0;
-      result = aSink->WritePixels<uint8_t>([&]() -> NextPixel<uint8_t> {
-        if (count == 50) {
-          return AsVariant(WriteState::NEED_MORE_DATA);
-        }
-        ++count;
-        return AsVariant(uint8_t(255));
-      });
-
-      EXPECT_EQ(WriteState::NEED_MORE_DATA, result);
-      EXPECT_EQ(50u, count);
-      EXPECT_FALSE(aSink->IsSurfaceFinished());
-      CheckGeneratedPalettedImage(aDecoder, IntRect(0, 0, 100, 1));
-
-      return;
-    }
-
-    // We should've finished the surface at this point.
-    AssertCorrectPipelineFinalState(aSink, IntRect(0, 0, 100, 100),
-                                    IntRect(0, 0, 100, 100));
-
-    // Attempt to write more and make sure that nothing gets written.
-    count = 0;
-    result = aSink->WritePixels<uint8_t>([&] {
-      count++;
-      return AsVariant(uint8_t(128));
-    });
-
-    EXPECT_EQ(WriteState::FINISHED, result);
-    EXPECT_EQ(0u, count);
-    EXPECT_TRUE(aSink->IsSurfaceFinished());
-
-    // Check that the generated image is still correct.
-    CheckGeneratedPalettedImage(aDecoder, IntRect(0, 0, 50, 1));
-  };
-
-  WithPalettedSurfaceSink(IntRect(0, 0, 100, 100),
-                          [&](Decoder* aDecoder, PalettedSurfaceSink* aSink) {
-                            checkEarlyExit(aDecoder, aSink,
-                                           WriteState::NEED_MORE_DATA);
-                          });
-
-  WithPalettedSurfaceSink(IntRect(0, 0, 100, 100),
-                          [&](Decoder* aDecoder, PalettedSurfaceSink* aSink) {
-                            checkEarlyExit(aDecoder, aSink,
-                                           WriteState::FAILURE);
-                          });
-
-  WithPalettedSurfaceSink(IntRect(0, 0, 100, 100),
-                          [&](Decoder* aDecoder, PalettedSurfaceSink* aSink) {
-                            checkEarlyExit(aDecoder, aSink,
-                                           WriteState::FINISHED);
-                          });
-}
-
-TEST(ImageSurfaceSink, PalettedSurfaceSinkWritePixelsToRow) {
-  WithPalettedSurfaceSink(
-      IntRect(0, 0, 100, 100),
-      [](Decoder* aDecoder, PalettedSurfaceSink* aSink) {
-        // Write the first 99 rows of our 100x100 surface and verify that even
-        // though our lambda will yield pixels forever, only one row is written
-        // per call to WritePixelsToRow().
-        for (int row = 0; row < 99; ++row) {
-          uint32_t count = 0;
-          WriteState result = aSink->WritePixelsToRow<uint8_t>([&] {
-            ++count;
-            return AsVariant(uint8_t(255));
-          });
-
-          EXPECT_EQ(WriteState::NEED_MORE_DATA, result);
-          EXPECT_EQ(100u, count);
-          EXPECT_FALSE(aSink->IsSurfaceFinished());
-
-          Maybe<SurfaceInvalidRect> invalidRect = aSink->TakeInvalidRect();
-          EXPECT_TRUE(invalidRect.isSome());
-          EXPECT_EQ(IntRect(0, row, 100, 1), invalidRect->mInputSpaceRect);
-          EXPECT_EQ(IntRect(0, row, 100, 1), invalidRect->mOutputSpaceRect);
-
-          CheckGeneratedPalettedImage(aDecoder, IntRect(0, 0, 100, row + 1));
-        }
-
-        // Write the final line, which should finish the surface.
-        uint32_t count = 0;
-        WriteState result = aSink->WritePixelsToRow<uint8_t>([&] {
-          ++count;
-          return AsVariant(uint8_t(255));
-        });
-
-        EXPECT_EQ(WriteState::FINISHED, result);
-        EXPECT_EQ(100u, count);
-
-        // Note that the final invalid rect we expect here is only the last row;
-        // that's because we called TakeInvalidRect() repeatedly in the loop
-        // above.
-        AssertCorrectPipelineFinalState(aSink, IntRect(0, 99, 100, 1),
-                                        IntRect(0, 99, 100, 1));
-
-        // Check that the generated image is correct.
-        CheckGeneratedPalettedImage(aDecoder, IntRect(0, 0, 100, 100));
-
-        // Attempt to write more and make sure that nothing gets written.
-        count = 0;
-        result = aSink->WritePixelsToRow<uint8_t>([&] {
-          count++;
-          return AsVariant(uint8_t(128));
-        });
-
-        EXPECT_EQ(WriteState::FINISHED, result);
-        EXPECT_EQ(0u, count);
-        EXPECT_TRUE(aSink->IsSurfaceFinished());
-
-        // Check that the generated image is still correct.
-        CheckGeneratedPalettedImage(aDecoder, IntRect(0, 0, 100, 100));
-      });
-}
-
-TEST(ImageSurfaceSink, PalettedSurfaceSinkWritePixelsToRowEarlyExit) {
-  auto checkEarlyExit = [](Decoder* aDecoder, PalettedSurfaceSink* aSink,
-                           WriteState aState) {
-    // Write half a row of 255s and then exit early with |aState|. If the lambda
-    // keeps getting called, we'll write 128s, which will cause the test to
-    // fail.
-    uint32_t count = 0;
-    auto result = aSink->WritePixelsToRow<uint8_t>([&]() -> NextPixel<uint8_t> {
-      if (count == 50) {
-        return AsVariant(aState);
-      }
-      return count++ < 50 ? AsVariant(uint8_t(255)) : AsVariant(uint8_t(128));
-    });
-
-    EXPECT_EQ(aState, result);
-    EXPECT_EQ(50u, count);
-    CheckGeneratedPalettedImage(aDecoder, IntRect(0, 0, 50, 1));
-
-    if (aState != WriteState::FINISHED) {
-      // We should still be able to write more at this point.
-      EXPECT_FALSE(aSink->IsSurfaceFinished());
-
-      // Verify that we can resume the same row and still stop at the end.
-      count = 0;
-      WriteState result = aSink->WritePixelsToRow<uint8_t>([&] {
-        ++count;
-        return AsVariant(uint8_t(255));
-      });
-
-      EXPECT_EQ(WriteState::NEED_MORE_DATA, result);
-      EXPECT_EQ(50u, count);
-      EXPECT_FALSE(aSink->IsSurfaceFinished());
-      CheckGeneratedPalettedImage(aDecoder, IntRect(0, 0, 100, 1));
-
-      return;
-    }
-
-    // We should've finished the surface at this point.
-    AssertCorrectPipelineFinalState(aSink, IntRect(0, 0, 100, 100),
-                                    IntRect(0, 0, 100, 100));
-
-    // Attempt to write more and make sure that nothing gets written.
-    count = 0;
-    result = aSink->WritePixelsToRow<uint8_t>([&] {
-      count++;
-      return AsVariant(uint8_t(128));
-    });
-
-    EXPECT_EQ(WriteState::FINISHED, result);
-    EXPECT_EQ(0u, count);
-    EXPECT_TRUE(aSink->IsSurfaceFinished());
-
-    // Check that the generated image is still correct.
-    CheckGeneratedPalettedImage(aDecoder, IntRect(0, 0, 50, 1));
-  };
-
-  WithPalettedSurfaceSink(IntRect(0, 0, 100, 100),
-                          [&](Decoder* aDecoder, PalettedSurfaceSink* aSink) {
-                            checkEarlyExit(aDecoder, aSink,
-                                           WriteState::NEED_MORE_DATA);
-                          });
-
-  WithPalettedSurfaceSink(IntRect(0, 0, 100, 100),
-                          [&](Decoder* aDecoder, PalettedSurfaceSink* aSink) {
-                            checkEarlyExit(aDecoder, aSink,
-                                           WriteState::FAILURE);
-                          });
-
-  WithPalettedSurfaceSink(IntRect(0, 0, 100, 100),
-                          [&](Decoder* aDecoder, PalettedSurfaceSink* aSink) {
-                            checkEarlyExit(aDecoder, aSink,
-                                           WriteState::FINISHED);
-                          });
-}
-
-TEST(ImageSurfaceSink, PalettedSurfaceSinkWriteBuffer) {
-  WithPalettedSurfaceSink(IntRect(0, 0, 100, 100),
-                          [](Decoder* aDecoder, PalettedSurfaceSink* aSink) {
-                            // Create a buffer the same size as one row of the
-                            // surface (which is 100x100), containing 60 pixels
-                            // of 255 in the middle and 20 transparent pixels of
-                            // 0 on either side.
-                            uint8_t buffer[100];
-                            for (int i = 0; i < 100; ++i) {
-                              buffer[i] = 20 <= i && i < 80 ? 255 : 0;
-                            }
-
-                            // Write the buffer to every row of the surface and
-                            // check that the generated image is correct.
-                            CheckPalettedIterativeWrite(
-                                aDecoder, aSink, IntRect(20, 0, 60, 100),
-                                [&] { return aSink->WriteBuffer(buffer); });
-                          });
-}
-
-TEST(ImageSurfaceSink, PalettedSurfaceSinkWriteBufferPartialRow) {
-  WithPalettedSurfaceSink(IntRect(0, 0, 100, 100),
-                          [](Decoder* aDecoder, PalettedSurfaceSink* aSink) {
-                            // Create a buffer the same size as one row of the
-                            // surface, containing all 255 pixels.
-                            uint8_t buffer[100];
-                            for (int i = 0; i < 100; ++i) {
-                              buffer[i] = 255;
-                            }
-
-                            // Write the buffer to the middle 60 pixels of every
-                            // row of the surface and check that the generated
-                            // image is correct.
-                            CheckPalettedIterativeWrite(
-                                aDecoder, aSink, IntRect(20, 0, 60, 100), [&] {
-                                  return aSink->WriteBuffer(buffer, 20, 60);
-                                });
-                          });
-}
-
-TEST(ImageSurfaceSink,
-     PalettedSurfaceSinkWriteBufferPartialRowStartColOverflow) {
-  WithPalettedSurfaceSink(
-      IntRect(0, 0, 100, 100),
-      [](Decoder* aDecoder, PalettedSurfaceSink* aSink) {
-        // Create a buffer the same size as one row of the surface, containing
-        // all 255 pixels.
-        uint8_t buffer[100];
-        for (int i = 0; i < 100; ++i) {
-          buffer[i] = 255;
-        }
-
-        {
-          // Write the buffer to successive rows until every row of the surface
-          // has been written. We place the start column beyond the end of the
-          // row, which will prevent us from writing anything, so we check that
-          // the generated image is entirely 0.
-          CheckPalettedIterativeWrite(
-              aDecoder, aSink, IntRect(0, 0, 0, 0),
-              [&] { return aSink->WriteBuffer(buffer, 100, 100); });
-        }
-
-        ResetForNextPass(aSink);
-
-        {
-          // Write the buffer to successive rows until every row of the surface
-          // has been written. We use column 50 as the start column, but we
-          // still write the buffer, which means we overflow the right edge of
-          // the surface by 50 pixels. We check that the left half of the
-          // generated image is 0 and the right half is 255.
-          CheckPalettedIterativeWrite(
-              aDecoder, aSink, IntRect(50, 0, 50, 100),
-              [&] { return aSink->WriteBuffer(buffer, 50, 100); });
-        }
-      });
-}
-
-TEST(ImageSurfaceSink, PalettedSurfaceSinkWriteBufferPartialRowBufferOverflow) {
-  WithPalettedSurfaceSink(
-      IntRect(0, 0, 100, 100),
-      [](Decoder* aDecoder, PalettedSurfaceSink* aSink) {
-        // Create a buffer twice as large as a row of the surface. The first
-        // half (which is as large as a row of the image) will contain 255
-        // pixels, while the second half will contain 128 pixels.
-        uint8_t buffer[200];
-        for (int i = 0; i < 200; ++i) {
-          buffer[i] = i < 100 ? 255 : 128;
-        }
-
-        {
-          // Write the buffer to successive rows until every row of the surface
-          // has been written. The buffer extends 100 pixels to the right of a
-          // row of the surface, but bounds checking will prevent us from
-          // overflowing the buffer. We check that the generated image is
-          // entirely 255 since the pixels on the right side of the buffer
-          // shouldn't have been written to the surface.
-          CheckPalettedIterativeWrite(
-              aDecoder, aSink, IntRect(0, 0, 100, 100),
-              [&] { return aSink->WriteBuffer(buffer, 0, 200); });
-        }
-
-        ResetForNextPass(aSink);
-
-        {
-          // Write from the buffer to the middle of each row of the surface.
-          // That means that the left side of each row should be 0, since we
-          // didn't write anything there. A buffer overflow would cause us to
-          // write buffer contents into the left side of each row. We check that
-          // the generated image is 0 on the left side and 255 on the right.
-          CheckPalettedIterativeWrite(
-              aDecoder, aSink, IntRect(50, 0, 50, 100),
-              [&] { return aSink->WriteBuffer(buffer, 50, 200); });
-        }
-      });
-}
-
-TEST(ImageSurfaceSink, PalettedSurfaceSinkWriteBufferFromNullSource) {
-  WithPalettedSurfaceSink(
-      IntRect(0, 0, 100, 100),
-      [](Decoder* aDecoder, PalettedSurfaceSink* aSink) {
-        // Calling WriteBuffer() with a null pointer should fail without making
-        // any changes to the surface.
-        uint8_t* nullBuffer = nullptr;
-        WriteState result = aSink->WriteBuffer(nullBuffer);
-
-        EXPECT_EQ(WriteState::FAILURE, result);
-        EXPECT_FALSE(aSink->IsSurfaceFinished());
-        Maybe<SurfaceInvalidRect> invalidRect = aSink->TakeInvalidRect();
-        EXPECT_TRUE(invalidRect.isNothing());
-
-        // Check that nothing got written to the surface.
-        CheckGeneratedPalettedImage(aDecoder, IntRect(0, 0, 0, 0));
-      });
-}
-
-TEST(ImageSurfaceSink, PalettedSurfaceSinkWriteEmptyRow) {
-  WithPalettedSurfaceSink(
-      IntRect(0, 0, 100, 100),
-      [](Decoder* aDecoder, PalettedSurfaceSink* aSink) {
-        {
-          // Write an empty row to each row of the surface. We check that the
-          // generated image is entirely 0.
-          CheckPalettedIterativeWrite(aDecoder, aSink, IntRect(0, 0, 0, 0),
-                                      [&] { return aSink->WriteEmptyRow(); });
-        }
-
-        ResetForNextPass(aSink);
-
-        {
-          // Write a partial row before we begin calling WriteEmptyRow(). We
-          // check that the generated image is entirely 0, which is to be
-          // expected since WriteEmptyRow() overwrites the current row even if
-          // some data has already been written to it.
-          uint32_t count = 0;
-          auto result =
-              aSink->WritePixels<uint8_t>([&]() -> NextPixel<uint8_t> {
-                if (count == 50) {
-                  return AsVariant(WriteState::NEED_MORE_DATA);
-                }
-                ++count;
-                return AsVariant(uint8_t(255));
-              });
-
-          EXPECT_EQ(WriteState::NEED_MORE_DATA, result);
-          EXPECT_EQ(50u, count);
-          EXPECT_FALSE(aSink->IsSurfaceFinished());
-
-          CheckPalettedIterativeWrite(aDecoder, aSink, IntRect(0, 0, 0, 0),
-                                      [&] { return aSink->WriteEmptyRow(); });
-        }
-
-        ResetForNextPass(aSink);
-
-        {
-          // Create a buffer the same size as one row of the surface, containing
-          // all 255 pixels.
-          uint8_t buffer[100];
-          for (int i = 0; i < 100; ++i) {
-            buffer[i] = 255;
-          }
-
-          // Write an empty row to the middle 60 rows of the surface. The first
-          // 20 and last 20 rows will be 255. (We need to use
-          // DoCheckIterativeWrite() here because we need a custom function to
-          // check the output, since it can't be described by a simple rect.)
-          auto writeFunc = [&](uint32_t aRow) {
-            if (aRow < 20 || aRow >= 80) {
-              return aSink->WriteBuffer(buffer);
-            } else {
-              return aSink->WriteEmptyRow();
-            }
-          };
-
-          auto checkFunc = [&] {
-            EXPECT_TRUE(PalettedRowsAreSolidColor(aDecoder, 0, 20, 255));
-            EXPECT_TRUE(PalettedRowsAreSolidColor(aDecoder, 20, 60, 0));
-            EXPECT_TRUE(PalettedRowsAreSolidColor(aDecoder, 80, 20, 255));
-          };
-
-          DoCheckIterativeWrite(aSink, writeFunc, checkFunc);
-        }
-      });
-}
-
-TEST(ImageSurfaceSink, PalettedSurfaceSinkWriteUnsafeComputedRow) {
-  WithPalettedSurfaceSink(IntRect(0, 0, 100, 100),
-                          [](Decoder* aDecoder, PalettedSurfaceSink* aSink) {
-                            // Create an all-255 buffer the same size as one row
-                            // of the surface.
-                            uint8_t buffer[100];
-                            for (int i = 0; i < 100; ++i) {
-                              buffer[i] = 255;
-                            }
-
-                            // Write the buffer to successive rows until every
-                            // row of the surface has been written. We only
-                            // write to the right half of each row, so we check
-                            // that the left side of the generated image is 0
-                            // and the right side is 255.
-                            CheckPalettedIterativeWrite(
-                                aDecoder, aSink, IntRect(50, 0, 50, 100), [&] {
-                                  return aSink->WriteUnsafeComputedRow<uint8_t>(
-                                      [&](uint8_t* aRow, uint32_t aLength) {
-                                        EXPECT_EQ(100u, aLength);
-                                        memcpy(aRow + 50, buffer,
-                                               50 * sizeof(uint8_t));
-                                      });
-                                });
-                          });
-}
--- a/js/public/ErrorReport.h
+++ b/js/public/ErrorReport.h
@@ -166,22 +166,27 @@ class JSErrorNotes {
                    JSErrorCallback errorCallback, void* userRef,
                    const unsigned errorNumber, ...);
 
   JS_PUBLIC_API size_t length();
 
   // Create a deep copy of notes.
   js::UniquePtr<JSErrorNotes> copy(JSContext* cx);
 
-  class iterator final
-      : public std::iterator<std::input_iterator_tag, js::UniquePtr<Note>> {
+  class iterator final {
    private:
     js::UniquePtr<Note>* note_;
 
    public:
+    using iterator_category = std::input_iterator_tag;
+    using value_type = js::UniquePtr<Note>;
+    using difference_type = ptrdiff_t;
+    using pointer = value_type*;
+    using reference = value_type&;
+
     explicit iterator(js::UniquePtr<Note>* note = nullptr) : note_(note) {}
 
     bool operator==(iterator other) const { return note_ == other.note_; }
     bool operator!=(iterator other) const { return !(*this == other); }
     iterator& operator++() {
       note_++;
       return *this;
     }
--- a/js/src/gc/GC.cpp
+++ b/js/src/gc/GC.cpp
@@ -7543,16 +7543,18 @@ bool GCRuntime::shouldRepeatForDeadZone(
     }
   }
 
   return false;
 }
 
 void GCRuntime::collect(bool nonincrementalByAPI, SliceBudget budget,
                         JS::GCReason reason) {
+  MOZ_ASSERT(reason != JS::GCReason::NO_REASON);
+
   // Checks run for each request, even if we do not actually GC.
   checkCanCallAPI();
 
   // Check if we are allowed to GC at this time before proceeding.
   if (!checkIfGCAllowedInCurrentState(reason)) {
     return;
   }
 
--- a/js/src/gc/Statistics.cpp
+++ b/js/src/gc/Statistics.cpp
@@ -62,20 +62,23 @@ const char* js::gcstats::ExplainInvocati
 }
 
 JS_PUBLIC_API const char* JS::ExplainGCReason(JS::GCReason reason) {
   switch (reason) {
 #define SWITCH_REASON(name, _) \
   case JS::GCReason::name:     \
     return #name;
     GCREASONS(SWITCH_REASON)
+#undef SWITCH_REASON
+
+    case JS::GCReason::NO_REASON:
+      return "NO_REASON";
 
     default:
       MOZ_CRASH("bad GC reason");
-#undef SWITCH_REASON
   }
 }
 
 const char* js::gcstats::ExplainAbortReason(gc::AbortReason reason) {
   switch (reason) {
 #define SWITCH_REASON(name, _) \
   case gc::AbortReason::name:  \
     return #name;
--- a/js/src/jsapi-tests/testBinASTReader.cpp
+++ b/js/src/jsapi-tests/testBinASTReader.cpp
@@ -235,23 +235,21 @@ void runTestFromPath(JSContext* cx, cons
       }
       if (!subPath.append(0)) {
         MOZ_CRASH();
       }
       runTestFromPath<Tok>(cx, subPath.begin());
       continue;
     }
 
-    {
-      // Make sure that we run GC between two tests. Otherwise, since we're
-      // running everything from the same cx and without returning to JS, there
-      // is nothing to deallocate the ASTs.
-      JS::PrepareForFullGC(cx);
-      cx->runtime()->gc.gc(GC_NORMAL, JS::GCReason::NO_REASON);
-    }
+    // Make sure that we run GC between two tests. Otherwise, since we're
+    // running everything from the same cx and without returning to JS, there is
+    // nothing to deallocate the ASTs.
+    JS_GC(cx);
+
     LifoAllocScope allocScope(&cx->tempLifoAlloc());
 
     // Find files whose name ends with ".binjs".
     fprintf(stderr, "Considering %s\n", d_name);
     if (namlen < sizeof(BIN_SUFFIX)) {
       continue;
     }
     if (strncmp(d_name + namlen - (sizeof(BIN_SUFFIX) - 1), BIN_SUFFIX,
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -4795,20 +4795,16 @@ pref("image.animated.decode-on-demand.th
 // animation's currently displayed frame.
 pref("image.animated.decode-on-demand.batch-size", 6);
 
 // Whether we should recycle already displayed frames instead of discarding
 // them. This saves on the allocation itself, and may be able to reuse the
 // contents as well. Only applies if generating full frames.
 pref("image.animated.decode-on-demand.recycle", true);
 
-// Whether we should generate full frames at decode time or partial frames which
-// are combined at display time (historical behavior and default).
-pref("image.animated.generate-full-frames", true);
-
 // Resume an animated image from the last displayed frame rather than
 // advancing when out of view.
 pref("image.animated.resume-from-last-displayed", true);
 
 // Maximum number of surfaces for an image before entering "factor of 2" mode.
 // This in addition to the number of "native" sizes of an image. A native size
 // is a size for which we can decode a frame without up or downscaling. Most
 // images only have 1, but some (i.e. ICOs) may have multiple frames for the
--- a/services/fxaccounts/FxAccountsConfig.jsm
+++ b/services/fxaccounts/FxAccountsConfig.jsm
@@ -48,17 +48,19 @@ var FxAccountsConfig = {
 
   async promiseEmailURI(email, entrypoint) {
     return this._buildURL("", {
       extraParams: {entrypoint, email},
     });
   },
 
   async promiseEmailFirstURI(entrypoint) {
-    return this._buildURL("", {entrypoint});
+    return this._buildURL("", {
+      extraParams: {entrypoint, action: "email"},
+    });
   },
 
   async promiseForceSigninURI(entrypoint) {
     return this._buildURL("force_auth", {
       extraParams: {entrypoint},
       addAccountIdentifiers: true,
     });
   },
--- a/taskcluster/ci/beetmover-checksums/kind.yml
+++ b/taskcluster/ci/beetmover-checksums/kind.yml
@@ -15,11 +15,21 @@ kind-dependencies:
 only-for-attributes:
    - nightly
 
 job-template:
    shipping-phase: promote
    attributes:
       artifact_prefix: public
       artifact_map:
-         by-platform:
-            android.*: taskcluster/taskgraph/manifests/fennec_nightly_checksums.yml
-            default: taskcluster/taskgraph/manifests/firefox_nightly_checksums.yml
+         by-project:
+            default:
+               by-platform:
+                  android.*: taskcluster/taskgraph/manifests/fennec_nightly_checksums.yml
+                  default: taskcluster/taskgraph/manifests/firefox_nightly_checksums.yml
+            mozilla-beta:
+               by-platform:
+                  android.*: taskcluster/taskgraph/manifests/fennec_candidates_checksums.yml
+                  default: taskcluster/taskgraph/manifests/firefox_candidates_checksums.yml
+            mozilla-release:
+               by-platform:
+                  android.*: taskcluster/taskgraph/manifests/fennec_candidates_checksums.yml
+                  default: taskcluster/taskgraph/manifests/firefox_candidates_checksums.yml
--- a/taskcluster/ci/beetmover/kind.yml
+++ b/taskcluster/ci/beetmover/kind.yml
@@ -29,9 +29,13 @@ not-for-build-platforms:
     - win64-devedition-nightly/opt
     - win64-aarch64-devedition-nightly/opt
     - linux64-asan-reporter-nightly/opt
     - win64-asan-reporter-nightly/opt
 
 job-template:
     shipping-phase: promote
     attributes:
-        artifact_map: taskcluster/taskgraph/manifests/fennec_nightly.yml
+        artifact_map:
+            by-project:
+                mozilla-release: taskcluster/taskgraph/manifests/fennec_candidates.yml
+                mozilla-beta: taskcluster/taskgraph/manifests/fennec_candidates.yml
+                default: taskcluster/taskgraph/manifests/fennec_nightly.yml
--- a/taskcluster/ci/release-beetmover-source-checksums/kind.yml
+++ b/taskcluster/ci/release-beetmover-source-checksums/kind.yml
@@ -10,9 +10,10 @@ transforms:
    - taskgraph.transforms.task:transforms
 
 kind-dependencies:
    - release-source-checksums-signing
 
 job-template:
    shipping-phase: promote
    attributes:
+      artifact_prefix: public
       artifact_map: taskcluster/taskgraph/manifests/source_checksums.yml
new file mode 100644
--- /dev/null
+++ b/taskcluster/taskgraph/manifests/fennec_candidates.yml
@@ -0,0 +1,156 @@
+---
+s3_bucket_paths:
+  - pub/mobile/candidates
+default_locales:  # if given an empty locale, use these locales
+  - en-US
+  - multi
+tasktype_map:  # map task reference to task type.
+  build: build
+  build-signing: signing
+  nightly-l10n-signing: signing
+platform_names:
+  path_platform:
+    by-platform:
+      android-x86-nightly: 'android-x86'
+      android-x86_64-nightly: 'android-x86_64'
+      android-api-16-nightly: 'android-api-16'
+      android-aarch64-nightly: 'android-aarch64'
+  filename_platform:
+    by-platform:
+      android-x86-nightly: 'android-i386'
+      android-x86_64-nightly: 'android-x86_64'
+      android-api-16-nightly: 'android-arm'
+      android-aarch64-nightly: 'android-aarch64'
+
+# A default entry, which the mappings below extend and override.
+# Final 'destinations' will be the product of:
+# s3_bucket_paths + destinations + locale_prefix + pretty_name
+default: &default
+  from:
+    - build
+  all_locales: false
+  description: "TODO"
+  locale_prefix: '${locale}/'
+  # anything between artifact prefix and filename within upstream task
+  source_path_modifier:
+    by-locale:
+      default: '${locale}'
+      multi: ''
+  destinations:
+    - ${version}-candidates/build${build_number}/${path_platform}
+
+# configuration for individual files. Extends 'default', above.
+mapping:
+  buildhub.json:
+    <<: *default
+    all_locales: true
+    description: "Build related information to be consumed by Buildhub service"
+    pretty_name: buildhub.json
+    checksums_path: ${path_platform}/${locale}/buildhub.json
+  target.common.tests.tar.gz:
+    <<: *default
+    description: "Mixture of reftests, mochitests, UI and others, commonly bundled together in a test suite"
+    pretty_name: fennec-${version}.${locale}.${filename_platform}.common.tests.tar.gz
+    checksums_path: fennec-${version}.${locale}.${filename_platform}.common.tests.tar.gz
+  target.cppunittest.tests.tar.gz:
+    <<: *default
+    description: "C++ unittests related in-tree test infrastructure"
+    pretty_name: fennec-${version}.${locale}.${filename_platform}.cppunittest.tests.tar.gz
+    checksums_path: fennec-${version}.${locale}.${filename_platform}.cppunittest.tests.tar.gz
+  target.crashreporter-symbols.zip:
+    <<: *default
+    description: "Crashreporter symbols to be consumed by Socorro"
+    pretty_name: fennec-${version}.${locale}.${filename_platform}.crashreporter-symbols.zip
+    checksums_path: fennec-${version}.${locale}.${filename_platform}.crashreporter-symbols.zip
+  target.json:
+    <<: *default
+    all_locales: true
+    description: "Various compile and moz_app flags baked together in a json file"
+    pretty_name: fennec-${version}.${locale}.${filename_platform}.json
+    checksums_path: fennec-${version}.${locale}.${filename_platform}.json
+  target.mochitest.tests.tar.gz:
+    <<: *default
+    description: "Results for running the mochitest testing framework via Javascript function calls"
+    pretty_name: fennec-${version}.${locale}.${filename_platform}.mochitest.tests.tar.gz
+    checksums_path: fennec-${version}.${locale}.${filename_platform}.mochitest.tests.tar.gz
+  target.mozinfo.json:
+    <<: *default
+    all_locales: true
+    description: "Various compile and moz_app flags baked together in a json file"
+    pretty_name: fennec-${version}.${locale}.${filename_platform}.mozinfo.json
+    checksums_path: fennec-${version}.${locale}.${filename_platform}.mozinfo.json
+  target.reftest.tests.tar.gz:
+    <<: *default
+    description: "Results for running the reftest testing framework via display of two Web pages comparison"
+    pretty_name: fennec-${version}.${locale}.${filename_platform}.reftest.tests.tar.gz
+    checksums_path: fennec-${version}.${locale}.${filename_platform}.reftest.tests.tar.gz
+  target.talos.tests.tar.gz:
+    <<: *default
+    description: "Results for running the talos testing framework to measure performance"
+    pretty_name: fennec-${version}.${locale}.${filename_platform}.talos.tests.tar.gz
+    checksums_path: fennec-${version}.${locale}.${filename_platform}.talos.tests.tar.gz
+  target.awsy.tests.tar.gz:
+    <<: *default
+    description: "Results for running the awsy testing framework to track memory usage"
+    pretty_name: fennec-${version}.${locale}.${filename_platform}.awsy.tests.tar.gz
+    checksums_path: fennec-${version}.${locale}.${filename_platform}.awsy.tests.tar.gz
+  target.test_packages.json:
+    <<: *default
+    description: "File containing metadata about all other files and testing harnesses specifics"
+    all_locales: true
+    pretty_name: fennec-${version}.${locale}.${filename_platform}.test_packages.json
+    checksums_path: fennec-${version}.${locale}.${filename_platform}.test_packages.json
+  target.txt:
+    <<: *default
+    description: "File containing buildid and revision"
+    all_locales: true
+    pretty_name: fennec-${version}.${locale}.${filename_platform}.txt
+    checksums_path: fennec-${version}.${locale}.${filename_platform}.txt
+  target.web-platform.tests.tar.gz:
+    <<: *default
+    description: "Results for running the webplatform testing framework to cover standard Web platform features"
+    pretty_name: fennec-${version}.${locale}.${filename_platform}.web-platform.tests.tar.gz
+    checksums_path: fennec-${version}.${locale}.${filename_platform}.web-platform.tests.tar.gz
+  target.xpcshell.tests.tar.gz:
+    <<: *default
+    description: "Results for running the xpcshell testing framework to enable XPConnect console application"
+    pretty_name: fennec-${version}.${locale}.${filename_platform}.xpcshell.tests.tar.gz
+    checksums_path: fennec-${version}.${locale}.${filename_platform}.xpcshell.tests.tar.gz
+  target_info.txt:
+    <<: *default
+    all_locales: true
+    locale_prefix: ''
+    description: "File containing the buildID"
+    pretty_name: ${path_platform}_info.txt
+    checksums_path: ${path_platform}_info.txt
+    destinations:
+      - ${version}-candidates/build${build_number}
+  mozharness.zip:
+    <<: *default
+    description: "File containing the mozharness set of scripts and configuration used by various automation tools"
+    pretty_name: mozharness.zip
+    checksums_path: mozharness.zip
+  robocop.apk:
+    <<: *default
+    description: "APK to enable the Robocop testing framework "
+    all_locales: true
+    pretty_name: robocop.apk
+    checksums_path: robocop.apk
+  target.jsshell.zip:
+    <<: *default
+    description: "Set of shells to allow test snippets of Javascript code without needing to reload the page"
+    pretty_name: jsshell-${filename_platform}.zip
+    checksums_path: jsshell-${filename_platform}.zip
+  target.apk:
+    <<: *default
+    description: "The main installer we ship our mobile products baked within"
+    all_locales: true
+    from:
+      - build-signing
+      - nightly-l10n-signing
+    pretty_name: fennec-${version}.${locale}.${filename_platform}.apk
+    checksums_path: fennec-${version}.${locale}.${filename_platform}.apk
+    update_balrog_manifest:
+      by-locale:
+        multi: true
+        default: false
new file mode 100644
--- /dev/null
+++ b/taskcluster/taskgraph/manifests/fennec_candidates_checksums.yml
@@ -0,0 +1,50 @@
+---
+s3_bucket_paths:
+  - pub/mobile/candidates
+default_locales:  # if given an empty locale, use these locales
+  - en-US
+tasktype_map:  # Map task reference to task type.
+  checksums-signing: signing
+platform_names:
+  path_platform:
+    by-platform:
+      android-x86-nightly: 'android-x86'
+      android-x86_64-nightly: 'android-x86_64'
+      android-api-16-nightly: 'android-api-16'
+      android-aarch64-nightly: 'android-aarch64'
+  filename_platform:
+    by-platform:
+      android-x86-nightly: 'android-i386'
+      android-x86_64-nightly: 'android-x86_64'
+      android-api-16-nightly: 'android-arm'
+      android-aarch64-nightly: 'android-aarch64'
+
+# A default entry, which the mappings below extend and override.
+# Final 'destinations' will be the product of:
+# s3_bucket_paths + destinations + locale_prefix + pretty_name
+default: &default
+  from:
+    - checksums-signing
+  all_locales: true
+  description: "TODO"
+  locale_prefix:
+    by-locale:
+      default: ''
+      en-US: '${locale}/'
+  source_path_modifier: ''
+  destinations:
+    - ${version}-candidates/build${build_number}/beetmover-checksums/${path_platform}
+    - ${version}-candidates/build${build_number}/${path_platform}
+
+# Configuration for individual files. Extends 'default', above.
+mapping:
+  target.checksums:
+    <<: *default
+    description: "Checksums file containing size, hash, sha algorithm and filename"
+    pretty_name: fennec-${version}.${locale}.${filename_platform}.checksums.beet
+    checksums_path: fennec-${version}.${locale}.${filename_platform}.checksums
+  target.checksums.asc:
+    <<: *default
+    description: "Detached signature for the checksums file"
+    pretty_name: fennec-${version}.${locale}.${filename_platform}.checksums.asc
+    checksums_path: fennec-${version}.${locale}.${filename_platform}.checksums.asc
--- a/taskcluster/taskgraph/manifests/fennec_geckoview.yml
+++ b/taskcluster/taskgraph/manifests/fennec_geckoview.yml
@@ -1,13 +1,9 @@
 ---
-metadata:
-  name: "Beetmover for checksums artifacts"
-  description: "Uploads checksum files to s3 buckets"
-  owner: "release@mozilla.com"
 s3_bucket_paths:
   - maven2
 default_locales:  # Ignored for geckoview
   - en-US
 tasktype_map:  # Map task reference to task type.
   build: build
 base_artifact_prefix: ''
 platform_names:
@@ -17,17 +13,17 @@ platform_names:
 # A default entry, which the mappings below extend and override.
 # Final 'destinations' will be the product of:
 # s3_bucket_paths + destinations + locale_prefix + pretty_name
 default: &default
   from:
     - build
   locale_prefix: ''
   source_path_modifier: org/mozilla/geckoview/${artifact_id}/${major_version}.${minor_version}.${build_date}
-  description: "TODO"
+  description: ""
   destinations:  # locale_prefix is appended
     - org/mozilla/geckoview/${artifact_id}/${major_version}.${minor_version}.${build_date}
 
 # Configuration for individual files. Extends 'default', above.
 upstream_mapping:
   target.maven.zip:
     from:
       - build
--- a/taskcluster/taskgraph/manifests/fennec_nightly.yml
+++ b/taskcluster/taskgraph/manifests/fennec_nightly.yml
@@ -1,13 +1,9 @@
 ---
-metadata:
-  description: Fennec nightly artifacts
-  name: Beetmover declarative artifacts manifest
-  owner: release@mozilla.com
 s3_bucket_paths:
   - pub/mobile/nightly
 default_locales:  # if given an empty locale, use these locales
   - en-US
   - multi
 tasktype_map:  # Map task reference to task type.
   build: build
   build-signing: signing
@@ -54,98 +50,114 @@ default: &default
         - latest-${branch}-${path_platform}-l10n
         - ${year}/${month}/${upload_date}-${branch}-${path_platform}-l10n
 
 # Configuration for individual files. Extends 'default', above.
 mapping:
   buildhub.json:
     <<: *default
     all_locales: true
+    description: "Build related information to be consumed by Buildhub service"
     locale_prefix: ''
     pretty_name: fennec-${version}.${locale}.${filename_platform}.buildhub.json
     checksums_path: fennec-${version}.${locale}.${filename_platform}.buildhub.json
   target.common.tests.tar.gz:
     <<: *default
+    description: "Mixture of reftests, mochitests, UI and others, commonly bundled together in a test suite"
     pretty_name: fennec-${version}.${locale}.${filename_platform}.common.tests.tar.gz
     checksums_path: fennec-${version}.${locale}.${filename_platform}.common.tests.tar.gz
   target.cppunittest.tests.tar.gz:
     <<: *default
+    description: "C++ unittests related in-tree test infrastructure"
     pretty_name: fennec-${version}.${locale}.${filename_platform}.cppunittest.tests.tar.gz
     checksums_path: fennec-${version}.${locale}.${filename_platform}.cppunittest.tests.tar.gz
   target.crashreporter-symbols.zip:
     <<: *default
+    description: "Crashreporter symbols to be consumed by Socorro"
     pretty_name: fennec-${version}.${locale}.${filename_platform}.crashreporter-symbols.zip
     checksums_path: fennec-${version}.${locale}.${filename_platform}.crashreporter-symbols.zip
   target.json:
     <<: *default
     all_locales: true
+    description: "Various compile and moz_app flags baked together in a json file"
     pretty_name: fennec-${version}.${locale}.${filename_platform}.json
     checksums_path: fennec-${version}.${locale}.${filename_platform}.json
   target.mochitest.tests.tar.gz:
     <<: *default
+    description: "Results for running the mochitest testing framework via Javascript function calls"
     pretty_name: fennec-${version}.${locale}.${filename_platform}.mochitest.tests.tar.gz
     checksums_path: fennec-${version}.${locale}.${filename_platform}.mochitest.tests.tar.gz
   target.mozinfo.json:
     <<: *default
     all_locales: true
+    description: "Various compile and moz_app flags baked together in a json file"
     pretty_name: fennec-${version}.${locale}.${filename_platform}.mozinfo.json
     checksums_path: fennec-${version}.${locale}.${filename_platform}.mozinfo.json
   target.reftest.tests.tar.gz:
     <<: *default
+    description: "Results for running the reftest testing framework via display of two Web pages comparison"
     pretty_name: fennec-${version}.${locale}.${filename_platform}.reftest.tests.tar.gz
     checksums_path: fennec-${version}.${locale}.${filename_platform}.reftest.tests.tar.gz
   target.talos.tests.tar.gz:
     <<: *default
+    description: "Results for running the talos testing framework to measure performance"
     pretty_name: fennec-${version}.${locale}.${filename_platform}.talos.tests.tar.gz
     checksums_path: fennec-${version}.${locale}.${filename_platform}.talos.tests.tar.gz
   target.awsy.tests.tar.gz:
     <<: *default
+    description: "Results for running the awsy testing framework to track memory usage"
     pretty_name: fennec-${version}.${locale}.${filename_platform}.awsy.tests.tar.gz
     checksums_path: fennec-${version}.${locale}.${filename_platform}.awsy.tests.tar.gz
   target.test_packages.json:
     <<: *default
+    description: "File containing metadata about all other files and testing harnesses specifics"
     all_locales: true
     pretty_name: fennec-${version}.${locale}.${filename_platform}.test_packages.json
     checksums_path: fennec-${version}.${locale}.${filename_platform}.test_packages.json
   target.txt:
     <<: *default
+    description: "File containing buildid and revision"
     all_locales: true
     pretty_name: fennec-${version}.${locale}.${filename_platform}.txt
     checksums_path: fennec-${version}.${locale}.${filename_platform}.txt
   target.web-platform.tests.tar.gz:
     <<: *default
-    description: "Web platform test suite <TODO>"
+    description: "Results for running the webplatform testing framework to cover standard Web platform features"
     pretty_name: fennec-${version}.${locale}.${filename_platform}.web-platform.tests.tar.gz
     checksums_path: fennec-${version}.${locale}.${filename_platform}.web-platform.tests.tar.gz
   target.xpcshell.tests.tar.gz:
     <<: *default
-    description: "XPCShell test suite <TODO>"
+    description: "Results for running the xpcshell testing framework to enable XPConnect console application"
     pretty_name: fennec-${version}.${locale}.${filename_platform}.xpcshell.tests.tar.gz
     checksums_path: fennec-${version}.${locale}.${filename_platform}.xpcshell.tests.tar.gz
   target_info.txt:
     <<: *default
     all_locales: true
-    description: "File containing the buildID for the particular release"
+    description: "File containing the buildID"
     pretty_name: fennec-${version}.${locale}.${filename_platform}_info.txt
     checksums_path: fennec-${version}.${locale}.${filename_platform}_info.txt
   mozharness.zip:
     <<: *default
+    description: "File containing the mozharness set of scripts and configuration used by various automation tools"
     pretty_name: mozharness.zip
     checksums_path: mozharness.zip
   robocop.apk:
     <<: *default
+    description: "APK to enable the Robocop testing framework "
     all_locales: true
     pretty_name: robocop.apk
     checksums_path: robocop.apk
   target.jsshell.zip:
     <<: *default
+    description: "Set of shells to allow test snippets of Javascript code without needing to reload the page"
     pretty_name: jsshell-${filename_platform}.zip
     checksums_path: jsshell-${filename_platform}.zip
   target.apk:
     <<: *default
+    description: "The main installer we ship our mobile products baked within"
     all_locales: true
     from:
       - build-signing
       - nightly-l10n-signing
     pretty_name: fennec-${version}.${locale}.${filename_platform}.apk
     checksums_path: fennec-${version}.${locale}.${filename_platform}.apk
     update_balrog_manifest:
       by-locale:
--- a/taskcluster/taskgraph/manifests/fennec_nightly_checksums.yml
+++ b/taskcluster/taskgraph/manifests/fennec_nightly_checksums.yml
@@ -1,13 +1,9 @@
 ---
-metadata:
-  name: "Beetmover for checksums artifacts"
-  description: "Uploads checksum files to s3 buckets"
-  owner: "release@mozilla.com"
 s3_bucket_paths:
   - pub/mobile/nightly
 default_locales:  # if given an empty locale, use these locales
   - en-US
 tasktype_map:  # Map task reference to task type.
   checksums-signing: signing
 platform_names:
   path_platform:
@@ -47,14 +43,16 @@ default: &default
       default:
         - latest-${branch}-${path_platform}-l10n
         - ${year}/${month}/${upload_date}-${branch}-${path_platform}-l10n
 
 # Configuration for individual files. Extends 'default', above.
 mapping:
   target.checksums:
     <<: *default
+    description: "Checksums file containing size, hash, sha algorithm and filename"
     pretty_name: fennec-${version}.${locale}.${filename_platform}.checksums
     checksums_path: fennec-${version}.${locale}.${filename_platform}.checksums
   target.checksums.asc:
     <<: *default
+    description: "Detached signature for the checksums file"
     pretty_name: fennec-${version}.${locale}.${filename_platform}.checksums.asc
     checksums_path: fennec-${version}.${locale}.${filename_platform}.checksums.asc
--- a/taskcluster/taskgraph/manifests/firefox_nightly_checksums.yml
+++ b/taskcluster/taskgraph/manifests/firefox_nightly_checksums.yml
@@ -1,13 +1,9 @@
 ---
-metadata:
-  name: "Beetmover for checksums artifacts"
-  description: "Uploads checksum files to s3 buckets"
-  owner: "release@mozilla.com"
 s3_bucket_paths:
   by-platform:
     .*devedition.*:
       - pub/devedition/nightly
     default:
       - pub/firefox/nightly
 default_locales:  # if given an empty locale, use these locales
   - en-US
@@ -42,14 +38,16 @@ default: &default
       default:
         - latest-${branch}-l10n
         - ${year}/${month}/${upload_date}-${branch}-l10n
 
 # Configuration for individual files. Extends 'default', above.
 mapping:
   target.checksums:
     <<: *default
+    description: "Checksums file containing size, hash, sha algorithm and filename"
     pretty_name: firefox-${version}.${locale}.${filename_platform}.checksums
     checksums_path: firefox-${version}.${locale}.${filename_platform}.checksums
   target.checksums.asc:
     <<: *default
+    description: "Detached signature for the checksums file"
     pretty_name: firefox-${version}.${locale}.${filename_platform}.checksums.asc
     checksums_path: firefox-${version}.${locale}.${filename_platform}.checksums.asc
--- a/taskcluster/taskgraph/manifests/release_checksums.yml
+++ b/taskcluster/taskgraph/manifests/release_checksums.yml
@@ -1,26 +1,22 @@
 ---
-metadata:
-  name: "Beetmover for checksums artifacts"
-  description: "Uploads checksum files to s3 buckets"
-  owner: "release@mozilla.com"
 s3_bucket_paths:
   by-platform:
     fennec-release:
       - pub/mobile/candidates
     devedition-release:
       - pub/devedition/candidates
     firefox-release:
       - pub/firefox/candidates
 default_locales:  # if given an empty locale, use these locales
   - en-US
 tasktype_map:  # Map task reference to task type.
+  release-generate-checksums: build
   release-generate-checksums-signing: signing
-  release-generate-checksums: build
 platform_names:
   path_platform: ''
   filename_platform: ''
 
 # A default entry, which the mappings below extend and override.
 # Final 'destinations' will be the product of:
 # s3_bucket_paths + destinations + locale_prefix + pretty_name
 default: &default
@@ -32,38 +28,45 @@ default: &default
   source_path_modifier: ''
   destinations:  # locale_prefix is appended
     - ${version}-candidates/build${build_number}
 
 # Configuration for individual files. Extends 'default', above.
 mapping:
   SHA256SUMMARY:
     <<: *default
+    description: "Merkle-tree for the release artifacts with sha 256 hashes"
     from:
       - release-generate-checksums
     pretty_name: SHA256SUMMARY
     checksums_path: SHA256SUMMARY
   SHA512SUMMARY:
     <<: *default
+    description: "Merkle-tree for the release artifacts with sha 512 hashes"
     from:
       - release-generate-checksums
     pretty_name: SHA512SUMMARY
     checksums_path: SHA512SUMMARY
   KEY:
     <<: *default
+    description: "Public side of the key that was used to sign the release artifacts"
     pretty_name: KEY
     checksums_path: KEY
   SHA256SUMS:
     <<: *default
+    description: "Aggregated checksums with main installers details per platform in sha512 hashes"
     pretty_name: SHA256SUMS
     checksums_path: SHA256SUMS
   SHA256SUMS.asc:
     <<: *default
+    description: "Detached signature for the checksums file"
     pretty_name: SHA256SUMS.asc
     checksums_path: SHA256SUMS.asc
   SHA512SUMS:
     <<: *default
+    description: "Aggregated checksums with main installers details per platform in sha256 hashes"
     pretty_name: SHA512SUMS
     checksums_path: SHA512SUMS
   SHA512SUMS.asc:
     <<: *default
+    description: "Detached signature for the checksums file"
     pretty_name: SHA512SUMS.asc
     checksums_path: SHA512SUMS.asc
--- a/taskcluster/taskgraph/manifests/source_checksums.yml
+++ b/taskcluster/taskgraph/manifests/source_checksums.yml
@@ -1,13 +1,9 @@
 ---
-metadata:
-  description: Source file archives
-  name: Beetmover source archive manifest
-  owner: release@mozilla.com
 s3_bucket_paths:
   by-platform:
     fennec-source:
       - pub/mobile/candidates
     devedition-source:
       - pub/devedition/candidates
     firefox-source:
       - pub/firefox/candidates
@@ -31,20 +27,32 @@ default: &default
   source_path_modifier: ''
   destinations:  # locale_prefix is appended
     - ${version}-candidates/build${build_number}/beetmover-checksums/source
 
 # Configuration for individual files. Extends 'default', above.
 mapping:
   target-source.checksums:
     <<: *default
+    description: "Checksums file for the source zip files"
     pretty_name:
       by-platform:
         firefox-source: firefox-${version}.checksums.beet
         devedition-source: firefox-${version}.checksums.beet
         fennec-source: fennec-${version}.checksums.beet
+    checksums_path:
+      by-platform:
+        firefox-source: firefox-${version}.checksums.beet
+        devedition-source: firefox-${version}.checksums.beet
+        fennec-source: fennec-${version}.checksums.beet
   target-source.checksums.asc:
     <<: *default
+    description: "Detached signature for the checksums file"
     pretty_name:
       by-platform:
         firefox-source: firefox-${version}.checksums.asc
         devedition-source: firefox-${version}.checksums.asc
         fennec-source: fennec-${version}.checksums.asc
+    checksums_path:
+      by-platform:
+        firefox-source: firefox-${version}.checksums.asc
+        devedition-source: firefox-${version}.checksums.asc
+        fennec-source: fennec-${version}.checksums.asc
--- a/taskcluster/taskgraph/manifests/source_files.yml
+++ b/taskcluster/taskgraph/manifests/source_files.yml
@@ -1,13 +1,9 @@
 ---
-metadata:
-  description: Source file archives
-  name: Beetmover source archive manifest
-  owner: release@mozilla.com
 s3_bucket_paths:
   by-platform:
     fennec-source:
       - pub/mobile/candidates
     devedition-source:
       - pub/devedition/candidates
     firefox-source:
       - pub/firefox/candidates
@@ -31,30 +27,32 @@ default: &default
   source_path_modifier: ''
   destinations:  # locale_prefix is appended
     - ${version}-candidates/build${build_number}/source
 
 # Configuration for individual files. Extends 'default', above.
 mapping:
   source.tar.xz:
     <<: *default
+    description: "Source file with the in-tree code archived"
     pretty_name:
       by-platform:
         firefox-source: firefox-${version}.source.tar.xz
         devedition-source: firefox-${version}.source.tar.xz
         fennec-source: fennec-${version}.source.tar.xz
     checksums_path:
       by-platform:
-        firefox-source: firefox-${version}.source.tar.xz
-        devedition-source: firefox-${version}.source.tar.xz
-        fennec-source: fennec-${version}.source.tar.xz
+        firefox-source: source/firefox-${version}.source.tar.xz
+        devedition-source: source/firefox-${version}.source.tar.xz
+        fennec-source: source/fennec-${version}.source.tar.xz
   source.tar.xz.asc:
     <<: *default
+    description: "Detached signature for the source file"
     pretty_name:
       by-platform:
         firefox-source: firefox-${version}.source.tar.xz.asc
         devedition-source: firefox-${version}.source.tar.xz.asc
         fennec-source: fennec-${version}.source.tar.xz.asc
     checksums_path:
       by-platform:
-        firefox-source: firefox-${version}.source.tar.xz.asc
-        devedition-source: firefox-${version}.source.tar.xz.asc
-        fennec-source: fennec-${version}.source.tar.xz.asc
+        firefox-source: source/firefox-${version}.source.tar.xz.asc
+        devedition-source: source/firefox-${version}.source.tar.xz.asc
+        fennec-source: source/fennec-${version}.source.tar.xz.asc
--- a/taskcluster/taskgraph/transforms/beetmover.py
+++ b/taskcluster/taskgraph/transforms/beetmover.py
@@ -307,17 +307,17 @@ def make_task_worker(config, jobs):
             else:
                 build_task = dependency
 
         signing_task_ref = "<" + str(signing_task) + ">"
         build_task_ref = "<" + str(build_task) + ">"
 
         if should_use_artifact_map(platform, config.params['project']):
             upstream_artifacts = generate_beetmover_upstream_artifacts(
-                job, platform, locale
+                config, job, platform, locale
             )
         else:
             upstream_artifacts = generate_upstream_artifacts(
                 job, signing_task_ref, build_task_ref, platform, locale
             )
         worker = {
             'implementation': 'beetmover',
             'release-properties': craft_release_properties(config, job),
--- a/taskcluster/taskgraph/transforms/beetmover_checksums.py
+++ b/taskcluster/taskgraph/transforms/beetmover_checksums.py
@@ -142,17 +142,17 @@ def make_beetmover_checksums_worker(conf
 
         worker = {
             'implementation': 'beetmover',
             'release-properties': craft_release_properties(config, job),
         }
 
         if should_use_artifact_map(platform, config.params['project']):
             upstream_artifacts = generate_beetmover_upstream_artifacts(
-                job, platform, locale
+                config, job, platform, locale
             )
             worker['artifact-map'] = generate_beetmover_artifact_map(
                 config, job, platform=platform, locale=locale)
         else:
             upstream_artifacts = generate_upstream_artifacts(
                 refs, platform, locale
             )
             # Clean up un-used artifact map, to avoid confusion
--- a/taskcluster/taskgraph/transforms/beetmover_source_checksums.py
+++ b/taskcluster/taskgraph/transforms/beetmover_source_checksums.py
@@ -133,17 +133,18 @@ def make_beetmover_checksums_worker(conf
                 refs['beetmover'] = "<{}>".format(dependency)
             else:
                 refs['signing'] = "<{}>".format(dependency)
         if None in refs.values():
             raise NotImplementedError(
                 "Beetmover checksums must have a beetmover and signing dependency!")
 
         if should_use_artifact_map(platform, config.params['project']):
-            upstream_artifacts = generate_beetmover_upstream_artifacts(job, platform, locale)
+            upstream_artifacts = generate_beetmover_upstream_artifacts(config,
+                                                                       job, platform, locale)
         else:
             upstream_artifacts = generate_upstream_artifacts(refs, platform, locale)
 
         worker = {
             'implementation': 'beetmover',
             'release-properties': craft_release_properties(config, job),
             'upstream-artifacts': upstream_artifacts,
         }
--- a/taskcluster/taskgraph/transforms/release_generate_checksums_beetmover.py
+++ b/taskcluster/taskgraph/transforms/release_generate_checksums_beetmover.py
@@ -153,17 +153,17 @@ def make_task_worker(config, jobs):
             'implementation': 'beetmover',
             'release-properties': craft_release_properties(config, job),
         }
 
         platform = job["attributes"]["build_platform"]
         # Works with Firefox/Devedition. Commented for migration.
         if should_use_artifact_map(platform, config.params['project']):
             upstream_artifacts = generate_beetmover_upstream_artifacts(
-                job, platform=None, locale=None
+                config, job, platform=None, locale=None
             )
         else:
             upstream_artifacts = generate_upstream_artifacts(
                 job, signing_task_ref, build_task_ref
             )
 
         worker['upstream-artifacts'] = upstream_artifacts
 
--- a/taskcluster/taskgraph/transforms/release_generate_checksums_signing.py
+++ b/taskcluster/taskgraph/transforms/release_generate_checksums_signing.py
@@ -46,21 +46,21 @@ def make_release_generate_checksums_sign
         treeherder.setdefault('tier', 1)
         treeherder.setdefault('kind', 'build')
 
         job_template = "{}-{}".format(dep_job.label, "signing")
         label = job.get("label", job_template)
         description = "Signing of the overall release-related checksums"
 
         dependencies = {
-            "build": dep_job.label
+            str(dep_job.kind): dep_job.label
         }
 
         upstream_artifacts = [{
-            "taskId": {"task-reference": "<build>"},
+            "taskId": {"task-reference": "<{}>".format(str(dep_job.kind))},
             "taskType": "build",
             "paths": [
                 get_artifact_path(dep_job, "SHA256SUMS"),
                 get_artifact_path(dep_job, "SHA512SUMS"),
             ],
             "formats": ["gpg"]
         }]
 
--- a/taskcluster/taskgraph/util/scriptworker.py
+++ b/taskcluster/taskgraph/util/scriptworker.py
@@ -396,32 +396,37 @@ def get_worker_type_for_scope(config, sc
                 for scopes in config.graph_config['scriptworker']['worker-types'].values()
                 for scope in scopes
             ),
         )
     )
 
 
 # generate_beetmover_upstream_artifacts {{{1
-def generate_beetmover_upstream_artifacts(job, platform, locale=None, dependencies=None):
+def generate_beetmover_upstream_artifacts(config, job, platform, locale=None, dependencies=None):
     """Generate the upstream artifacts for beetmover, using the artifact map.
 
     Currently only applies to beetmover tasks.
 
     Args:
         job (dict): The current job being generated
         dependencies (list): A list of the job's dependency labels.
         platform (str): The current build platform
         locale (str): The current locale being beetmoved.
 
     Returns:
         list: A list of dictionaries conforming to the upstream_artifacts spec.
     """
     base_artifact_prefix = get_artifact_prefix(job)
-    resolve_keyed_by(job, 'attributes.artifact_map', 'artifact map', platform=platform)
+    resolve_keyed_by(
+        job, 'attributes.artifact_map',
+        'artifact map',
+        project=config.params['project'],
+        platform=platform
+    )
     map_config = load_yaml(job['attributes']['artifact_map'])
     upstream_artifacts = list()
 
     if not locale:
         locales = map_config['default_locales']
     else:
         locales = [locale]
 
@@ -526,17 +531,22 @@ def generate_beetmover_artifact_map(conf
         platform (str): The current build platform
         locale (str): The current locale being beetmoved.
 
     Returns:
         list: A list of dictionaries containing source->destination
             maps for beetmover.
     """
     platform = kwargs.get('platform', '')
-    resolve_keyed_by(job, 'attributes.artifact_map', 'artifact map', platform=platform)
+    resolve_keyed_by(
+        job, 'attributes.artifact_map',
+        'artifact map',
+        project=config.params['project'],
+        platform=platform
+    )
     map_config = load_yaml(job['attributes']['artifact_map'])
     base_artifact_prefix = map_config.get('base_artifact_prefix', get_artifact_prefix(job))
 
     artifacts = list()
 
     dependencies = job['dependencies'].keys()
 
     if kwargs.get('locale'):
@@ -552,18 +562,16 @@ def generate_beetmover_artifact_map(conf
             # Relevancy checks
             if dep not in map_config['mapping'][filename]['from']:
                 # We don't get this file from this dependency.
                 continue
             if locale != 'en-US' and not map_config['mapping'][filename]['all_locales']:
                 # This locale either doesn't produce or shouldn't upload this file.
                 continue
 
-            # Filling in destinations
-
             # deepcopy because the next time we look at this file the locale will differ.
             file_config = deepcopy(map_config['mapping'][filename])
 
             for field in [
                 'destinations',
                 'locale_prefix',
                 'source_path_modifier',
                 'update_balrog_manifest',
@@ -606,27 +614,26 @@ def generate_beetmover_artifact_map(conf
                 if file_config.get('balrog_format'):
                     paths[key]['balrog_format'] = file_config['balrog_format']
 
         if not paths:
             # No files for this dependency/locale combination.
             continue
 
         # Render all variables for the artifact map
-
         platforms = deepcopy(map_config['platform_names'])
         if platform:
             for key in platforms.keys():
                 resolve_keyed_by(platforms, key, key, platform=platform)
 
         upload_date = datetime.fromtimestamp(config.params['build_date'])
 
         kwargs.update({
             'locale': locale,
-            'version': config.params['app_version'],
+            'version': config.params['version'],
             'branch': config.params['project'],
             'build_number': config.params['build_number'],
             'filename_platform': platforms['filename_platform'],
             'path_platform': platforms['path_platform'],
             'year': upload_date.year,
             'month': upload_date.strftime("%m"),  # zero-pad the month
             'upload_date': upload_date.strftime("%Y-%m-%d-%H-%M-%S")
         })
@@ -645,13 +652,13 @@ def should_use_artifact_map(platform, pr
     """Return True if this task uses the beetmover artifact map.
 
     This function exists solely for the beetmover artifact map
     migration.
     """
     platforms = ['android', 'fennec']
     # FIXME: once we're ready to switch fully to declarative artifacts on other
     # branches, we can expand this
-    projects = ['mozilla-central']
+    projects = ['mozilla-central', 'mozilla-beta', 'mozilla-release']
 
     if any([pl in platform for pl in platforms]) and any([pj in project for pj in projects]):
         return True
     return False
--- a/toolkit/components/telemetry/tests/unit/test_TelemetryEnvironment.js
+++ b/toolkit/components/telemetry/tests/unit/test_TelemetryEnvironment.js
@@ -286,16 +286,17 @@ function spoofTheme(aId, aName, aDesc) {
     textcolor: Math.random().toString(),
     accentcolor: Math.random().toString(),
   };
 }
 
 function spoofGfxAdapter() {
   try {
     let gfxInfo = Cc["@mozilla.org/gfx/info;1"].getService(Ci.nsIGfxInfoDebug);
+    gfxInfo.fireTestProcess();
     gfxInfo.spoofVendorID(GFX_VENDOR_ID);
     gfxInfo.spoofDeviceID(GFX_DEVICE_ID);
   } catch (x) {
     // If we can't test gfxInfo, that's fine, we'll note it later.
   }
 }
 
 function spoofProfileReset() {
--- a/toolkit/mozapps/extensions/test/xpcshell/test_gfxBlacklist_Device.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_gfxBlacklist_Device.js
@@ -25,16 +25,17 @@ async function run_test() {
 
   // We can't do anything if we can't spoof the stuff we need.
   if (!(gfxInfo instanceof Ci.nsIGfxInfoDebug)) {
     do_test_finished();
     return;
   }
 
   gfxInfo.QueryInterface(Ci.nsIGfxInfoDebug);
+  gfxInfo.fireTestProcess();
 
   // Set the vendor/device ID, etc, to match the test file.
   switch (Services.appinfo.OS) {
     case "WINNT":
       gfxInfo.spoofVendorID("0xabcd");
       gfxInfo.spoofDeviceID("0x9876");
       gfxInfo.spoofDriverVersion("8.52.322.2201");
       // Windows 7
--- a/toolkit/mozapps/extensions/test/xpcshell/test_gfxBlacklist_DriverNew.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_gfxBlacklist_DriverNew.js
@@ -25,16 +25,17 @@ async function run_test() {
 
   // We can't do anything if we can't spoof the stuff we need.
   if (!(gfxInfo instanceof Ci.nsIGfxInfoDebug)) {
     do_test_finished();
     return;
   }
 
   gfxInfo.QueryInterface(Ci.nsIGfxInfoDebug);
+  gfxInfo.fireTestProcess();
 
   // Set the vendor/device ID, etc, to match the test file.
   switch (Services.appinfo.OS) {
     case "WINNT":
       gfxInfo.spoofVendorID("0xabcd");
       gfxInfo.spoofDeviceID("0x1234");
       gfxInfo.spoofDriverVersion("8.52.322.2202");
       // Windows 7
--- a/toolkit/mozapps/extensions/test/xpcshell/test_gfxBlacklist_Equal_DriverNew.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_gfxBlacklist_Equal_DriverNew.js
@@ -25,16 +25,17 @@ async function run_test() {
 
   // We can't do anything if we can't spoof the stuff we need.
   if (!(gfxInfo instanceof Ci.nsIGfxInfoDebug)) {
     do_test_finished();
     return;
   }
 
   gfxInfo.QueryInterface(Ci.nsIGfxInfoDebug);
+  gfxInfo.fireTestProcess();
 
   // Set the vendor/device ID, etc, to match the test file.
   switch (Services.appinfo.OS) {
     case "WINNT":
       gfxInfo.spoofVendorID("0xdcdc");
       gfxInfo.spoofDeviceID("0x1234");
       gfxInfo.spoofDriverVersion("8.52.322.1112");
       // Windows 7
--- a/toolkit/mozapps/extensions/test/xpcshell/test_gfxBlacklist_Equal_DriverOld.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_gfxBlacklist_Equal_DriverOld.js
@@ -26,16 +26,17 @@ async function run_test() {
 
   // We can't do anything if we can't spoof the stuff we need.
   if (!(gfxInfo instanceof Ci.nsIGfxInfoDebug)) {
     do_test_finished();
     return;
   }
 
   gfxInfo.QueryInterface(Ci.nsIGfxInfoDebug);
+  gfxInfo.fireTestProcess();
 
   // Set the vendor/device ID, etc, to match the test file.
   switch (Services.appinfo.OS) {
     case "WINNT":
       gfxInfo.spoofVendorID("0xdcdc");
       gfxInfo.spoofDeviceID("0x1234");
       gfxInfo.spoofDriverVersion("8.52.322.1110");
       // Windows 7
--- a/toolkit/mozapps/extensions/test/xpcshell/test_gfxBlacklist_Equal_OK.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_gfxBlacklist_Equal_OK.js
@@ -25,16 +25,17 @@ async function run_test() {
 
   // We can't do anything if we can't spoof the stuff we need.
   if (!(gfxInfo instanceof Ci.nsIGfxInfoDebug)) {
     do_test_finished();
     return;
   }
 
   gfxInfo.QueryInterface(Ci.nsIGfxInfoDebug);
+  gfxInfo.fireTestProcess();
 
   // Set the vendor/device ID, etc, to match the test file.
   switch (Services.appinfo.OS) {
     case "WINNT":
       gfxInfo.spoofVendorID("0xdcdc");
       gfxInfo.spoofDeviceID("0x1234");
       gfxInfo.spoofDriverVersion("8.52.322.1111");
       // Windows 7
--- a/toolkit/mozapps/extensions/test/xpcshell/test_gfxBlacklist_GTE_DriverOld.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_gfxBlacklist_GTE_DriverOld.js
@@ -25,16 +25,17 @@ async function run_test() {
 
   // We can't do anything if we can't spoof the stuff we need.
   if (!(gfxInfo instanceof Ci.nsIGfxInfoDebug)) {
     do_test_finished();
     return;
   }
 
   gfxInfo.QueryInterface(Ci.nsIGfxInfoDebug);
+  gfxInfo.fireTestProcess();
 
   // Set the vendor/device ID, etc, to match the test file.
   switch (Services.appinfo.OS) {
     case "WINNT":
       gfxInfo.spoofVendorID("0xabab");
       gfxInfo.spoofDeviceID("0x1234");
       gfxInfo.spoofDriverVersion("8.52.322.2201");
       // Windows 7
--- a/toolkit/mozapps/extensions/test/xpcshell/test_gfxBlacklist_GTE_OK.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_gfxBlacklist_GTE_OK.js
@@ -25,16 +25,17 @@ async function run_test() {
 
   // We can't do anything if we can't spoof the stuff we need.
   if (!(gfxInfo instanceof Ci.nsIGfxInfoDebug)) {
     do_test_finished();
     return;
   }
 
   gfxInfo.QueryInterface(Ci.nsIGfxInfoDebug);
+  gfxInfo.fireTestProcess();
 
   // Set the vendor/device ID, etc, to match the test file.
   switch (Services.appinfo.OS) {
     case "WINNT":
       gfxInfo.spoofVendorID("0xabab");
       gfxInfo.spoofDeviceID("0x1234");
       gfxInfo.spoofDriverVersion("8.52.322.2202");
       // Windows 7
--- a/toolkit/mozapps/extensions/test/xpcshell/test_gfxBlacklist_No_Comparison.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_gfxBlacklist_No_Comparison.js
@@ -25,16 +25,17 @@ async function run_test() {
 
   // We can't do anything if we can't spoof the stuff we need.
   if (!(gfxInfo instanceof Ci.nsIGfxInfoDebug)) {
     do_test_finished();
     return;
   }
 
   gfxInfo.QueryInterface(Ci.nsIGfxInfoDebug);
+  gfxInfo.fireTestProcess();
 
   gfxInfo.spoofVendorID("0xabcd");
   gfxInfo.spoofDeviceID("0x6666");
 
   // Spoof the OS version so it matches the test file.
   switch (Services.appinfo.OS) {
     case "WINNT":
       // Windows 7
--- a/toolkit/mozapps/extensions/test/xpcshell/test_gfxBlacklist_OK.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_gfxBlacklist_OK.js
@@ -25,16 +25,17 @@ async function run_test() {
 
   // We can't do anything if we can't spoof the stuff we need.
   if (!(gfxInfo instanceof Ci.nsIGfxInfoDebug)) {
     do_test_finished();
     return;
   }
 
   gfxInfo.QueryInterface(Ci.nsIGfxInfoDebug);
+  gfxInfo.fireTestProcess();
 
   // Set the vendor/device ID, etc, to match the test file.
   switch (Services.appinfo.OS) {
     case "WINNT":
       gfxInfo.spoofVendorID("0xabcd");
       gfxInfo.spoofDeviceID("0x1234");
       gfxInfo.spoofDriverVersion("8.52.322.2201");
       // Windows 7
--- a/toolkit/mozapps/extensions/test/xpcshell/test_gfxBlacklist_OSVersion_match.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_gfxBlacklist_OSVersion_match.js
@@ -24,16 +24,17 @@ async function run_test() {
 
   // We can't do anything if we can't spoof the stuff we need.
   if (!(gfxInfo instanceof Ci.nsIGfxInfoDebug)) {
     do_test_finished();
     return;
   }
 
   gfxInfo.QueryInterface(Ci.nsIGfxInfoDebug);
+  gfxInfo.fireTestProcess();
 
   // Set the vendor/device ID, etc, to match the test file.
   gfxInfo.spoofDriverVersion("8.52.322.2201");
   gfxInfo.spoofVendorID("0xabcd");
   gfxInfo.spoofDeviceID("0x1234");
 
   // Spoof the version of the OS appropriately to test the test file.
   switch (Services.appinfo.OS) {
--- a/toolkit/mozapps/extensions/test/xpcshell/test_gfxBlacklist_OSVersion_mismatch_DriverVersion.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_gfxBlacklist_OSVersion_mismatch_DriverVersion.js
@@ -25,16 +25,17 @@ async function run_test() {
 
   // We can't do anything if we can't spoof the stuff we need.
   if (!(gfxInfo instanceof Ci.nsIGfxInfoDebug)) {
     do_test_finished();
     return;
   }
 
   gfxInfo.QueryInterface(Ci.nsIGfxInfoDebug);
+  gfxInfo.fireTestProcess();
 
   // Set the vendor/device ID, etc, to match the test file.
   gfxInfo.spoofDriverVersion("8.52.322.2202");
   gfxInfo.spoofVendorID("0xabcd");
   gfxInfo.spoofDeviceID("0x1234");
 
   // Spoof the version of the OS appropriately to test the test file.
   switch (Services.appinfo.OS) {
--- a/toolkit/mozapps/extensions/test/xpcshell/test_gfxBlacklist_OSVersion_mismatch_OSVersion.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_gfxBlacklist_OSVersion_mismatch_OSVersion.js
@@ -25,16 +25,17 @@ async function run_test() {
 
   // We can't do anything if we can't spoof the stuff we need.
   if (!(gfxInfo instanceof Ci.nsIGfxInfoDebug)) {
     do_test_finished();
     return;
   }
 
   gfxInfo.QueryInterface(Ci.nsIGfxInfoDebug);
+  gfxInfo.fireTestProcess();
 
   // Set the vendor/device ID, etc, to match the test file.
   gfxInfo.spoofDriverVersion("8.52.322.2201");
   gfxInfo.spoofVendorID("0xabcd");
   gfxInfo.spoofDeviceID("0x1234");
 
   // Spoof the version of the OS appropriately to test the test file.
   switch (Services.appinfo.OS) {
--- a/toolkit/mozapps/extensions/test/xpcshell/test_gfxBlacklist_Vendor.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_gfxBlacklist_Vendor.js
@@ -25,16 +25,17 @@ async function run_test() {
 
   // We can't do anything if we can't spoof the stuff we need.
   if (!(gfxInfo instanceof Ci.nsIGfxInfoDebug)) {
     do_test_finished();
     return;
   }
 
   gfxInfo.QueryInterface(Ci.nsIGfxInfoDebug);
+  gfxInfo.fireTestProcess();
 
   // Set the vendor/device ID, etc, to match the test file.
   switch (Services.appinfo.OS) {
     case "WINNT":
       gfxInfo.spoofVendorID("0xdcba");
       gfxInfo.spoofDeviceID("0x1234");
       gfxInfo.spoofDriverVersion("8.52.322.2201");
       // Windows 7
--- a/toolkit/mozapps/extensions/test/xpcshell/test_gfxBlacklist_Version.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_gfxBlacklist_Version.js
@@ -24,16 +24,17 @@ async function run_test() {
 
   // We can't do anything if we can't spoof the stuff we need.
   if (!(gfxInfo instanceof Ci.nsIGfxInfoDebug)) {
     do_test_finished();
     return;
   }
 
   gfxInfo.QueryInterface(Ci.nsIGfxInfoDebug);
+  gfxInfo.fireTestProcess();
 
   // Set the vendor/device ID, etc, to match the test file.
   switch (Services.appinfo.OS) {
     case "WINNT":
       gfxInfo.spoofVendorID("0xabcd");
       gfxInfo.spoofDeviceID("0x1234");
       gfxInfo.spoofDriverVersion("8.52.322.2201");
       // Windows 7
--- a/toolkit/mozapps/extensions/test/xpcshell/test_gfxBlacklist_prefs.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_gfxBlacklist_prefs.js
@@ -29,16 +29,17 @@ async function run_test() {
 
   // We can't do anything if we can't spoof the stuff we need.
   if (!(gfxInfo instanceof Ci.nsIGfxInfoDebug)) {
     do_test_finished();
     return;
   }
 
   gfxInfo.QueryInterface(Ci.nsIGfxInfoDebug);
+  gfxInfo.fireTestProcess();
 
   // Set the vendor/device ID, etc, to match the test file.
   switch (Services.appinfo.OS) {
     case "WINNT":
       gfxInfo.spoofVendorID("0xabcd");
       gfxInfo.spoofDeviceID("0x1234");
       gfxInfo.spoofDriverVersion("8.52.322.2201");
       // Windows 7
--- a/toolkit/xre/glxtest.cpp
+++ b/toolkit/xre/glxtest.cpp
@@ -53,16 +53,30 @@ typedef XID GLXPbuffer;
 
 // stuff from gl.h
 typedef uint8_t GLubyte;
 typedef uint32_t GLenum;
 #define GL_VENDOR 0x1F00
 #define GL_RENDERER 0x1F01
 #define GL_VERSION 0x1F02
 
+// GLX_MESA_query_renderer
+#define GLX_RENDERER_VENDOR_ID_MESA                            0x8183
+#define GLX_RENDERER_DEVICE_ID_MESA                            0x8184
+#define GLX_RENDERER_VERSION_MESA                              0x8185
+#define GLX_RENDERER_ACCELERATED_MESA                          0x8186
+#define GLX_RENDERER_VIDEO_MEMORY_MESA                         0x8187
+#define GLX_RENDERER_UNIFIED_MEMORY_ARCHITECTURE_MESA          0x8188
+#define GLX_RENDERER_PREFERRED_PROFILE_MESA                    0x8189
+#define GLX_RENDERER_OPENGL_CORE_PROFILE_VERSION_MESA          0x818A
+#define GLX_RENDERER_OPENGL_COMPATIBILITY_PROFILE_VERSION_MESA 0x818B
+#define GLX_RENDERER_OPENGL_ES_PROFILE_VERSION_MESA            0x818C
+#define GLX_RENDERER_OPENGL_ES2_PROFILE_VERSION_MESA           0x818D
+#define GLX_RENDERER_ID_MESA                                   0x818E
+
 namespace mozilla {
 namespace widget {
 // the read end of the pipe, which will be used by GfxInfo
 extern int glxtest_pipe;
 // the PID of the glxtest process, to pass to waitpid()
 extern pid_t glxtest_pid;
 }  // namespace widget
 }  // namespace mozilla
@@ -196,32 +210,72 @@ void glxtest() {
   ///// Get a GL context and make it current //////
   GLXContext context = glXCreateContext(dpy, vInfo, nullptr, True);
   glXMakeCurrent(dpy, window, context);
 
   ///// Look for this symbol to determine texture_from_pixmap support /////
   void *glXBindTexImageEXT = glXGetProcAddress("glXBindTexImageEXT");
 
   ///// Get GL vendor/renderer/versions strings /////
-  enum { bufsize = 1024 };
+  enum { bufsize = 2048 };
   char buf[bufsize];
-  const GLubyte *vendorString = glGetString(GL_VENDOR);
-  const GLubyte *rendererString = glGetString(GL_RENDERER);
-  const GLubyte *versionString = glGetString(GL_VERSION);
+  const GLubyte* versionString = glGetString(GL_VERSION);
+  const GLubyte* vendorString = glGetString(GL_VENDOR);
+  const GLubyte* rendererString = glGetString(GL_RENDERER);
 
-  if (!vendorString || !rendererString || !versionString)
+  if (!versionString || !vendorString || !rendererString)
     fatal_error("glGetString returned null");
 
   int length =
       snprintf(buf, bufsize, "VENDOR\n%s\nRENDERER\n%s\nVERSION\n%s\nTFP\n%s\n",
                vendorString, rendererString, versionString,
                glXBindTexImageEXT ? "TRUE" : "FALSE");
   if (length >= bufsize)
     fatal_error("GL strings length too large for buffer size");
 
+  // If GLX_MESA_query_renderer is available, populate additional data.
+  typedef Bool (*PFNGLXQUERYCURRENTRENDERERINTEGERMESAPROC) (int attribute, unsigned int* value);
+  PFNGLXQUERYCURRENTRENDERERINTEGERMESAPROC glXQueryCurrentRendererIntegerMESAProc =
+    cast<PFNGLXQUERYCURRENTRENDERERINTEGERMESAPROC>(glXGetProcAddress("glXQueryCurrentRendererIntegerMESA"));
+  if (glXQueryCurrentRendererIntegerMESAProc) {
+    unsigned int vendorId, deviceId, accelerated, videoMemoryMB;
+    glXQueryCurrentRendererIntegerMESAProc(GLX_RENDERER_VENDOR_ID_MESA, &vendorId);
+    glXQueryCurrentRendererIntegerMESAProc(GLX_RENDERER_DEVICE_ID_MESA, &deviceId);
+    glXQueryCurrentRendererIntegerMESAProc(GLX_RENDERER_ACCELERATED_MESA, &accelerated);
+    glXQueryCurrentRendererIntegerMESAProc(GLX_RENDERER_VIDEO_MEMORY_MESA, &videoMemoryMB);
+
+    // Truncate IDs to 4 digits- that's all PCI IDs are.
+    vendorId &= 0xFFFF;
+    deviceId &= 0xFFFF;
+
+    length += snprintf(buf + length, bufsize,
+                       "MESA_VENDOR_ID\n0x%04x\n"
+                       "MESA_DEVICE_ID\n0x%04x\n"
+                       "MESA_ACCELERATED\n%s\n"
+                       "MESA_VRAM\n%dMB\n",
+                       vendorId, deviceId, accelerated ? "TRUE" : "FALSE",
+                       videoMemoryMB);
+
+    if (length >= bufsize)
+      fatal_error("GL strings length too large for buffer size");
+  }
+
+  // From Mesa's GL/internal/dri_interface.h, to be used by DRI clients.
+  typedef const char * (* PFNGLXGETSCREENDRIVERPROC) (Display *dpy, int scrNum);
+  PFNGLXGETSCREENDRIVERPROC glXGetScreenDriverProc =
+    cast<PFNGLXGETSCREENDRIVERPROC>(glXGetProcAddress("glXGetScreenDriver"));
+  if (glXGetScreenDriverProc) {
+    const char* driDriver = glXGetScreenDriverProc(dpy, DefaultScreen(dpy));
+    if (driDriver) {
+      length += snprintf(buf + length, bufsize, "DRI_DRIVER\n%s\n", driDriver);
+      if (length >= bufsize)
+        fatal_error("GL strings length too large for buffer size");
+    }
+  }
+
   ///// Clean up. Indeed, the parent process might fail to kill us (e.g. if it
   ///// doesn't need to check GL info) so we might be staying alive for longer
   ///// than expected, so it's important to consume as little memory as
   ///// possible. Also we want to check that we're able to do that too without
   ///// generating X errors.
   glXMakeCurrent(dpy, None,
                  nullptr);  // must release the GL context before destroying it
   glXDestroyContext(dpy, context);
--- a/widget/GfxDriverInfo.cpp
+++ b/widget/GfxDriverInfo.cpp
@@ -376,14 +376,19 @@ const nsAString& GfxDriverInfo::GetDevic
     DECLARE_VENDOR_ID(VendorNVIDIA, "0x10de");
     DECLARE_VENDOR_ID(VendorAMD, "0x1022");
     DECLARE_VENDOR_ID(VendorATI, "0x1002");
     DECLARE_VENDOR_ID(VendorMicrosoft, "0x1414");
     DECLARE_VENDOR_ID(VendorParallels, "0x1ab8");
     // Choose an arbitrary Qualcomm PCI VENdor ID for now.
     // TODO: This should be "QCOM" when Windows device ID parsing is reworked.
     DECLARE_VENDOR_ID(VendorQualcomm, "0x5143");
+    DECLARE_VENDOR_ID(VendorMesaAll, "mesa/all");
+    DECLARE_VENDOR_ID(VendorMesaLLVMPipe, "mesa/llvmpipe");
+    DECLARE_VENDOR_ID(VendorMesaSoftPipe, "mesa/softpipe");
+    DECLARE_VENDOR_ID(VendorMesaSWRast, "mesa/swrast");
+    DECLARE_VENDOR_ID(VendorMesaUnknown, "mesa/unknown");
     // Suppress a warning.
     DECLARE_VENDOR_ID(DeviceVendorMax, "");
   }
 
   return *sDeviceVendors[id];
 }
--- a/widget/GfxDriverInfo.h
+++ b/widget/GfxDriverInfo.h
@@ -122,16 +122,28 @@ enum DeviceVendor {
   VendorAll,  // There is an assumption that this is the first enum
   VendorIntel,
   VendorNVIDIA,
   VendorAMD,
   VendorATI,
   VendorMicrosoft,
   VendorParallels,
   VendorQualcomm,
+
+  // Wildcard for all Mesa drivers.
+  VendorMesaAll,
+  // Note that the following list of Mesa drivers is not comprehensive; we pull
+  // the DRI driver at runtime. These drivers are provided for convenience when
+  // populating the local blocklist.
+  VendorMesaLLVMPipe,
+  VendorMesaSoftPipe,
+  VendorMesaSWRast,
+  // A generic ID to be provided when we can't determine the DRI driver on Mesa.
+  VendorMesaUnknown,
+
   DeviceVendorMax
 };
 
 /* Array of devices to match, or an empty array for all devices */
 typedef nsTArray<nsString> GfxDeviceFamily;
 
 struct GfxDriverInfo {
   // If |ownDevices| is true, you are transferring ownership of the devices
@@ -271,17 +283,17 @@ inline void PadDriverDecimal(char *aStri
   }
   aString[4] = 0;
 }
 
 inline bool ParseDriverVersion(const nsAString &aVersion,
                                uint64_t *aNumericVersion) {
   *aNumericVersion = 0;
 
-#if defined(XP_WIN)
+#if defined(XP_WIN) || defined(MOZ_X11)
   int a, b, c, d;
   char aStr[8], bStr[8], cStr[8], dStr[8];
   /* honestly, why do I even bother */
   if (!SplitDriverVersion(NS_LossyConvertUTF16toASCII(aVersion).get(), aStr,
                           bStr, cStr, dStr))
     return false;
 
   PadDriverDecimal(bStr);
--- a/widget/GfxInfoBase.cpp
+++ b/widget/GfxInfoBase.cpp
@@ -690,17 +690,17 @@ int32_t GfxInfoBase::FindBlocklistedDevi
       (NS_FAILED(GetAdapterVendorID2(adapterVendorID[1])) ||
        NS_FAILED(GetAdapterDeviceID2(adapterDeviceID[1])) ||
        NS_FAILED(GetAdapterDriverVersion2(adapterDriverVersionString[1])));
   // No point in going on if we don't have adapter info
   if (adapterInfoFailed[0] && adapterInfoFailed[1]) {
     return 0;
   }
 
-#if defined(XP_WIN) || defined(ANDROID)
+#if defined(XP_WIN) || defined(ANDROID) || defined(MOZ_X11)
   uint64_t driverVersion[2] = {0, 0};
   if (!adapterInfoFailed[0]) {
     ParseDriverVersion(adapterDriverVersionString[0], &driverVersion[0]);
   }
   if (!adapterInfoFailed[1]) {
     ParseDriverVersion(adapterDriverVersionString[1], &driverVersion[1]);
   }
 #endif
@@ -723,21 +723,17 @@ int32_t GfxInfoBase::FindBlocklistedDevi
       continue;
     }
 
     if (info[i].mOperatingSystemVersion &&
         info[i].mOperatingSystemVersion != OperatingSystemVersion()) {
       continue;
     }
 
-    if (!info[i].mAdapterVendor.Equals(
-            GfxDriverInfo::GetDeviceVendor(VendorAll),
-            nsCaseInsensitiveStringComparator()) &&
-        !info[i].mAdapterVendor.Equals(adapterVendorID[infoIndex],
-                                       nsCaseInsensitiveStringComparator())) {
+    if (!DoesVendorMatch(info[i].mAdapterVendor, adapterVendorID[infoIndex])) {
       continue;
     }
 
     if (info[i].mDevices != GfxDriverInfo::allDevices &&
         info[i].mDevices->Length()) {
       bool deviceMatches = false;
       for (uint32_t j = 0; j < info[i].mDevices->Length(); j++) {
         if ((*info[i].mDevices)[j].Equals(
@@ -764,17 +760,17 @@ int32_t GfxInfoBase::FindBlocklistedDevi
     if (!info[i].mProduct.IsEmpty() && !info[i].mProduct.Equals(Product())) {
       continue;
     }
     if (!info[i].mManufacturer.IsEmpty() &&
         !info[i].mManufacturer.Equals(Manufacturer())) {
       continue;
     }
 
-#if defined(XP_WIN) || defined(ANDROID)
+#if defined(XP_WIN) || defined(ANDROID) || defined(MOZ_X11)
     switch (info[i].mComparisonOp) {
       case DRIVER_LESS_THAN:
         match = driverVersion[infoIndex] < info[i].mDriverVersion;
         break;
       case DRIVER_BUILD_ID_LESS_THAN:
         match = (driverVersion[infoIndex] & 0xFFFF) < info[i].mDriverVersion;
         break;
       case DRIVER_LESS_THAN_OR_EQUAL:
@@ -878,16 +874,24 @@ int32_t GfxInfoBase::FindBlocklistedDevi
 }
 
 void GfxInfoBase::SetFeatureStatus(
     const nsTArray<dom::GfxInfoFeatureStatus>& aFS) {
   MOZ_ASSERT(!sFeatureStatus);
   sFeatureStatus = new nsTArray<dom::GfxInfoFeatureStatus>(aFS);
 }
 
+bool GfxInfoBase::DoesVendorMatch(const nsAString& aBlocklistVendor,
+                                  const nsAString& aAdapterVendor) {
+  return aBlocklistVendor.Equals(aAdapterVendor,
+                                 nsCaseInsensitiveStringComparator()) ||
+         aBlocklistVendor.Equals(GfxDriverInfo::GetDeviceVendor(VendorAll),
+                                 nsCaseInsensitiveStringComparator());
+}
+
 nsresult GfxInfoBase::GetFeatureStatusImpl(
     int32_t aFeature, int32_t* aStatus, nsAString& aSuggestedVersion,
     const nsTArray<GfxDriverInfo>& aDriverInfo, nsACString& aFailureId,
     OperatingSystem* aOS /* = nullptr */) {
   if (aFeature <= 0) {
     gfxWarning() << "Invalid feature <= 0";
     return NS_OK;
   }
--- a/widget/GfxInfoBase.h
+++ b/widget/GfxInfoBase.h
@@ -120,16 +120,20 @@ class GfxInfoBase : public nsIGfxInfo,
       const nsTArray<GfxDriverInfo>& aDriverInfo, nsACString& aFailureId,
       OperatingSystem* aOS = nullptr);
 
   // Gets the driver info table. Used by GfxInfoBase to check for general cases
   // (while subclasses check for more specific ones).
   virtual const nsTArray<GfxDriverInfo>& GetGfxDriverInfo() = 0;
 
   virtual void DescribeFeatures(JSContext* aCx, JS::Handle<JSObject*> obj);
+
+  virtual bool DoesVendorMatch(const nsAString& aBlocklistVendor,
+                               const nsAString& aAdapterVendor);
+
   bool InitFeatureObject(JSContext* aCx, JS::Handle<JSObject*> aContainer,
                          const char* aName,
                          mozilla::gfx::FeatureStatus& aKnownStatus,
                          JS::MutableHandle<JSObject*> aOutObj);
 
   NS_IMETHOD ControlGPUProcessForXPCShell(bool aEnable, bool* _retval) override;
 
  private:
--- a/widget/GfxInfoX11.cpp
+++ b/widget/GfxInfoX11.cpp
@@ -9,53 +9,52 @@
 #include <sys/types.h>
 #include <sys/wait.h>
 #include <errno.h>
 #include <sys/utsname.h>
 #include "nsCRTGlue.h"
 #include "nsExceptionHandler.h"
 #include "nsICrashReporter.h"
 #include "prenv.h"
+#include "nsPrintfCString.h"
+#include "nsWhitespaceTokenizer.h"
 
 #include "GfxInfoX11.h"
 
+#ifdef DEBUG
+bool fire_glxtest_process();
+#endif
+
 namespace mozilla {
 namespace widget {
 
 #ifdef DEBUG
 NS_IMPL_ISUPPORTS_INHERITED(GfxInfo, GfxInfoBase, nsIGfxInfoDebug)
 #endif
 
 // these global variables will be set when firing the glxtest process
 int glxtest_pipe = -1;
 pid_t glxtest_pid = 0;
 
 nsresult GfxInfo::Init() {
   mGLMajorVersion = 0;
-  mMajorVersion = 0;
-  mMinorVersion = 0;
-  mRevisionVersion = 0;
+  mGLMinorVersion = 0;
+  mHasTextureFromPixmap = false;
   mIsMesa = false;
-  mIsNVIDIA = false;
-  mIsFGLRX = false;
-  mIsNouveau = false;
-  mIsIntel = false;
-  mIsOldSwrast = false;
-  mIsLlvmpipe = false;
-  mHasTextureFromPixmap = false;
+  mIsAccelerated = true;
   return GfxInfoBase::Init();
 }
 
 void GfxInfo::AddCrashReportAnnotations() {
   CrashReporter::AnnotateCrashReport(CrashReporter::Annotation::AdapterVendorID,
-                                     mVendor);
+                                     mVendorId);
   CrashReporter::AnnotateCrashReport(CrashReporter::Annotation::AdapterDeviceID,
-                                     mRenderer);
+                                     mDeviceId);
   CrashReporter::AnnotateCrashReport(
-      CrashReporter::Annotation::AdapterDriverVersion, mVersion);
+      CrashReporter::Annotation::AdapterDriverVersion, mDriverVersion);
 }
 
 void GfxInfo::GetData() {
   // to understand this function, see bug 639842. We retrieve the OpenGL driver
   // information in a separate process to protect against bad drivers.
 
   // if glxtest_pipe == -1, that means that we already read the information
   if (glxtest_pipe == -1) return;
@@ -103,60 +102,81 @@ void GfxInfo::GetData() {
                                 WIFEXITED(glxtest_status) &&
                                 WEXITSTATUS(glxtest_status) != EXIT_SUCCESS;
   bool received_signal =
       !waiting_for_glxtest_process_failed && WIFSIGNALED(glxtest_status);
 
   bool error = waiting_for_glxtest_process_failed || exited_with_error_code ||
                received_signal;
 
+  nsCString glVendor;
+  nsCString glRenderer;
+  nsCString glVersion;
   nsCString textureFromPixmap;
+
+  // Available if GLX_MESA_query_renderer is supported.
+  nsCString mesaVendor;
+  nsCString mesaDevice;
+  nsCString mesaAccelerated;
+  // Available if using a DRI-based libGL stack.
+  nsCString driDriver;
+
   nsCString *stringToFill = nullptr;
   char *bufptr = buf;
   if (!error) {
     while (true) {
       char *line = NS_strtok("\n", &bufptr);
       if (!line) break;
       if (stringToFill) {
         stringToFill->Assign(line);
         stringToFill = nullptr;
       } else if (!strcmp(line, "VENDOR"))
-        stringToFill = &mVendor;
+        stringToFill = &glVendor;
       else if (!strcmp(line, "RENDERER"))
-        stringToFill = &mRenderer;
+        stringToFill = &glRenderer;
       else if (!strcmp(line, "VERSION"))
-        stringToFill = &mVersion;
+        stringToFill = &glVersion;
       else if (!strcmp(line, "TFP"))
         stringToFill = &textureFromPixmap;
+      else if (!strcmp(line, "MESA_VENDOR_ID"))
+        stringToFill = &mesaVendor;
+      else if (!strcmp(line, "MESA_DEVICE_ID"))
+        stringToFill = &mesaDevice;
+      else if (!strcmp(line, "MESA_ACCELERATED"))
+        stringToFill = &mesaAccelerated;
+      else if (!strcmp(line, "MESA_VRAM"))
+        stringToFill = &mAdapterRAM;
+      else if (!strcmp(line, "DRI_DRIVER"))
+        stringToFill = &driDriver;
     }
   }
 
   if (!strcmp(textureFromPixmap.get(), "TRUE")) mHasTextureFromPixmap = true;
 
   // only useful for Linux kernel version check for FGLRX driver.
   // assumes X client == X server, which is sad.
   struct utsname unameobj;
   if (uname(&unameobj) >= 0) {
     mOS.Assign(unameobj.sysname);
     mOSRelease.Assign(unameobj.release);
   }
 
   const char *spoofedVendor = PR_GetEnv("MOZ_GFX_SPOOF_GL_VENDOR");
-  if (spoofedVendor) mVendor.Assign(spoofedVendor);
+  if (spoofedVendor) glVendor.Assign(spoofedVendor);
   const char *spoofedRenderer = PR_GetEnv("MOZ_GFX_SPOOF_GL_RENDERER");
-  if (spoofedRenderer) mRenderer.Assign(spoofedRenderer);
+  if (spoofedRenderer) glRenderer.Assign(spoofedRenderer);
   const char *spoofedVersion = PR_GetEnv("MOZ_GFX_SPOOF_GL_VERSION");
-  if (spoofedVersion) mVersion.Assign(spoofedVersion);
+  if (spoofedVersion) glVersion.Assign(spoofedVersion);
   const char *spoofedOS = PR_GetEnv("MOZ_GFX_SPOOF_OS");
   if (spoofedOS) mOS.Assign(spoofedOS);
   const char *spoofedOSRelease = PR_GetEnv("MOZ_GFX_SPOOF_OS_RELEASE");
   if (spoofedOSRelease) mOSRelease.Assign(spoofedOSRelease);
 
-  if (error || mVendor.IsEmpty() || mRenderer.IsEmpty() || mVersion.IsEmpty() ||
-      mOS.IsEmpty() || mOSRelease.IsEmpty()) {
+  if (error || glVendor.IsEmpty() || glRenderer.IsEmpty() ||
+      glVersion.IsEmpty() || mOS.IsEmpty() || mOSRelease.IsEmpty()) {
     mAdapterDescription.AppendLiteral("GLXtest process failed");
     if (waiting_for_glxtest_process_failed)
       mAdapterDescription.AppendPrintf(
           " (waitpid failed with errno=%d for pid %d)", waitpid_errno,
           glxtest_pid);
     if (exited_with_error_code)
       mAdapterDescription.AppendPrintf(" (exited with status %d)",
                                        WEXITSTATUS(glxtest_status));
@@ -168,93 +188,144 @@ void GfxInfo::GetData() {
       mAdapterDescription.Append(nsDependentCString(buf));
       mAdapterDescription.Append('\n');
     }
 
     CrashReporter::AppendAppNotesToCrashReport(mAdapterDescription);
     return;
   }
 
-  mAdapterDescription.Append(mVendor);
-  mAdapterDescription.AppendLiteral(" -- ");
-  mAdapterDescription.Append(mRenderer);
-
-  AddCrashReportAnnotations();
-
-  // determine the major OpenGL version. That's the first integer in the version
-  // string.
-  mGLMajorVersion = strtol(mVersion.get(), 0, 10);
-
-  // determine driver type (vendor) and where in the version string
-  // the actual driver version numbers should be expected to be found
-  // (whereToReadVersionNumbers)
-  const char *whereToReadVersionNumbers = nullptr;
-  const char *Mesa_in_version_string = strstr(mVersion.get(), "Mesa");
-  if (Mesa_in_version_string) {
-    mIsMesa = true;
-    // with Mesa, the version string contains "Mesa major.minor" and that's all
-    // the version information we get: there is no actual driver version info.
-    whereToReadVersionNumbers = Mesa_in_version_string + strlen("Mesa");
-    if (strcasestr(mVendor.get(), "nouveau")) mIsNouveau = true;
-    if (strcasestr(mRenderer.get(),
-                   "intel"))  // yes, intel is in the renderer string
-      mIsIntel = true;
-    if (strcasestr(mRenderer.get(), "llvmpipe")) mIsLlvmpipe = true;
-    if (strcasestr(mRenderer.get(), "software rasterizer")) mIsOldSwrast = true;
-  } else if (strstr(mVendor.get(), "NVIDIA Corporation")) {
-    mIsNVIDIA = true;
-    // with the NVIDIA driver, the version string contains "NVIDIA major.minor"
-    // note that here the vendor and version strings behave differently, that's
-    // why we don't put this above alongside Mesa_in_version_string.
-    const char *NVIDIA_in_version_string = strstr(mVersion.get(), "NVIDIA");
-    if (NVIDIA_in_version_string)
-      whereToReadVersionNumbers = NVIDIA_in_version_string + strlen("NVIDIA");
-  } else if (strstr(mVendor.get(), "ATI Technologies Inc")) {
-    mIsFGLRX = true;
-    // with the FGLRX driver, the version string only gives a OpenGL version :/
-    // so let's return that. that can at least give a rough idea of how old the
-    // driver is.
-    whereToReadVersionNumbers = mVersion.get();
-  }
-
-  // read major.minor version numbers of the driver (not to be confused with the
-  // OpenGL version)
-  if (whereToReadVersionNumbers) {
-    // copy into writable buffer, for tokenization
-    strncpy(buf, whereToReadVersionNumbers, buf_size);
-    bufptr = buf;
-
-    // now try to read major.minor version numbers. In case of failure,
-    // gracefully exit: these numbers have been initialized as 0 anyways
-    char *token = NS_strtok(".", &bufptr);
-    if (token) {
-      mMajorVersion = strtol(token, 0, 10);
-      token = NS_strtok(".", &bufptr);
-      if (token) {
-        mMinorVersion = strtol(token, 0, 10);
-        token = NS_strtok(".", &bufptr);
-        if (token) mRevisionVersion = strtol(token, 0, 10);
+  // Scan the GL_VERSION string for the GL and driver versions.
+  nsCWhitespaceTokenizer tokenizer(glVersion);
+  while (tokenizer.hasMoreTokens()) {
+    nsCString token(tokenizer.nextToken());
+    unsigned int major = 0, minor = 0, revision = 0, patch = 0;
+    if (sscanf(token.get(), "%u.%u.%u.%u", &major, &minor, &revision, &patch) >=
+        2) {
+      // A survey of GL_VENDOR strings indicates that the first version is
+      // always the GL version, the second is usually the driver version.
+      if (mGLMajorVersion == 0) {
+        mGLMajorVersion = major;
+        mGLMinorVersion = minor;
+      } else if (mDriverVersion.IsEmpty()) {  // Not already spoofed.
+        mDriverVersion =
+            nsPrintfCString("%u.%u.%u.%u", major, minor, revision, patch);
       }
     }
   }
-}
+
+  if (mGLMajorVersion == 0) {
+    NS_WARNING("Failed to parse GL version!");
+    return;
+  }
+
+  // Mesa always exposes itself in the GL_VERSION string, but not always the
+  // GL_VENDOR string.
+  mIsMesa = glVersion.Find("Mesa") != -1;
 
-static inline uint64_t version(uint32_t major, uint32_t minor,
-                               uint32_t revision = 0) {
-  return (uint64_t(major) << 32) + (uint64_t(minor) << 16) + uint64_t(revision);
+  // We need to use custom vendor IDs for mesa so we can treat them
+  // differently than the proprietary drivers.
+  if (mIsMesa) {
+    mIsAccelerated = !mesaAccelerated.Equals("FALSE");
+    // Process software rasterizers before the DRI driver string; we may be
+    // forcing software rasterization on a DRI-accelerated X server by using
+    // LIBGL_ALWAYS_SOFTWARE or a similar restriction.
+    if (strcasestr(glRenderer.get(), "llvmpipe")) {
+      CopyUTF16toUTF8(GfxDriverInfo::GetDeviceVendor(VendorMesaLLVMPipe),
+                      mVendorId);
+      mIsAccelerated = false;
+    } else if (strcasestr(glRenderer.get(), "softpipe")) {
+      CopyUTF16toUTF8(GfxDriverInfo::GetDeviceVendor(VendorMesaSoftPipe),
+                      mVendorId);
+      mIsAccelerated = false;
+    } else if (strcasestr(glRenderer.get(), "software rasterizer") ||
+               !mIsAccelerated) {
+      // Fallback to reporting swrast if GLX_MESA_query_renderer tells us
+      // we're using an unaccelerated context.
+      CopyUTF16toUTF8(GfxDriverInfo::GetDeviceVendor(VendorMesaSWRast),
+                      mVendorId);
+      mIsAccelerated = false;
+    } else if (!driDriver.IsEmpty()) {
+      mVendorId = nsPrintfCString("mesa/%s", driDriver.get());
+    } else {
+      // Some other mesa configuration where we couldn't get enough info.
+      NS_WARNING("Failed to detect Mesa driver being used!");
+      CopyUTF16toUTF8(GfxDriverInfo::GetDeviceVendor(VendorMesaUnknown),
+                      mVendorId);
+    }
+
+    if (!mesaDevice.IsEmpty()) {
+      mDeviceId = mesaDevice;
+    } else {
+      NS_WARNING(
+          "Failed to get Mesa device ID! GLX_MESA_query_renderer unsupported?");
+    }
+  } else if (glVendor.EqualsLiteral("NVIDIA Corporation")) {
+    CopyUTF16toUTF8(GfxDriverInfo::GetDeviceVendor(VendorNVIDIA), mVendorId);
+    // TODO: Use NV-CONTROL X11 extension to query Device ID and VRAM.
+  } else if (glVendor.EqualsLiteral("ATI Technologies Inc.")) {
+    CopyUTF16toUTF8(GfxDriverInfo::GetDeviceVendor(VendorATI), mVendorId);
+    // TODO: Look into ways to find the device ID on FGLRX.
+  } else {
+    NS_WARNING("Failed to detect GL vendor!");
+  }
+
+  // Fallback to GL_VENDOR and GL_RENDERER.
+  if (mVendorId.IsEmpty()) {
+    mVendorId.Assign(glVendor.get());
+  }
+  if (mDeviceId.IsEmpty()) {
+    mDeviceId.Assign(glRenderer.get());
+  }
+
+  mAdapterDescription.Assign(glRenderer);
+
+  AddCrashReportAnnotations();
 }
 
 const nsTArray<GfxDriverInfo> &GfxInfo::GetGfxDriverInfo() {
-  // Nothing here yet.
-  // if (!sDriverInfo->Length()) {
-  //
-  //}
+  if (!sDriverInfo->Length()) {
+    // Mesa 10.0 provides the GLX_MESA_query_renderer extension, which allows us
+    // to query device IDs backing a GL context for blacklisting.
+    APPEND_TO_DRIVER_BLOCKLIST(
+        OperatingSystem::Linux,
+        (nsAString &)GfxDriverInfo::GetDeviceVendor(VendorMesaAll),
+        GfxDriverInfo::allDevices, GfxDriverInfo::allFeatures,
+        nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION, DRIVER_LESS_THAN,
+        V(10, 0, 0, 0), "FEATURE_FAILURE_OLD_MESA", "Mesa 10.0");
+
+    // NVIDIA baseline (ported from old blocklist)
+    APPEND_TO_DRIVER_BLOCKLIST(
+        OperatingSystem::Linux,
+        (nsAString &)GfxDriverInfo::GetDeviceVendor(VendorNVIDIA),
+        GfxDriverInfo::allDevices, GfxDriverInfo::allFeatures,
+        nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION, DRIVER_LESS_THAN,
+        V(257, 21, 0, 0), "FEATURE_FAILURE_OLD_NVIDIA", "NVIDIA 257.21");
+
+    // fglrx baseline (chosen arbitrarily as 2013-07-22 release).
+    APPEND_TO_DRIVER_BLOCKLIST(
+        OperatingSystem::Linux,
+        (nsAString &)GfxDriverInfo::GetDeviceVendor(VendorATI),
+        GfxDriverInfo::allDevices, GfxDriverInfo::allFeatures,
+        nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION, DRIVER_LESS_THAN,
+        V(13, 15, 100, 1), "FEATURE_FAILURE_OLD_FGLRX", "fglrx 13.15.100.1");
+  }
   return *sDriverInfo;
 }
 
+bool GfxInfo::DoesVendorMatch(const nsAString &aBlocklistVendor,
+                              const nsAString &aAdapterVendor) {
+  if (mIsMesa &&
+      aBlocklistVendor.Equals(GfxDriverInfo::GetDeviceVendor(VendorMesaAll),
+                              nsCaseInsensitiveStringComparator())) {
+    return true;
+  }
+  return GfxInfoBase::DoesVendorMatch(aBlocklistVendor, aAdapterVendor);
+}
+
 nsresult GfxInfo::GetFeatureStatusImpl(
     int32_t aFeature, int32_t *aStatus, nsAString &aSuggestedDriverVersion,
     const nsTArray<GfxDriverInfo> &aDriverInfo, nsACString &aFailureId,
     OperatingSystem *aOS /* = nullptr */)
 
 {
   NS_ENSURE_ARG_POINTER(aStatus);
   *aStatus = nsIGfxInfo::FEATURE_STATUS_UNKNOWN;
@@ -263,122 +334,39 @@ nsresult GfxInfo::GetFeatureStatusImpl(
   if (aOS) *aOS = os;
 
   if (sShutdownOccurred) {
     return NS_OK;
   }
 
   GetData();
 
+  if (mGLMajorVersion == 0) {
+    // If we failed to get a GL version, glxtest failed.
+    *aStatus = nsIGfxInfo::FEATURE_BLOCKED_DEVICE;
+    aFailureId = "FEATURE_FAILURE_GLXTEST_FAILED";
+    return NS_OK;
+  }
+
   if (mGLMajorVersion == 1) {
     // We're on OpenGL 1. In most cases that indicates really old hardware.
     // We better block them, rather than rely on them to fail gracefully,
     // because they don't! see bug 696636
     *aStatus = nsIGfxInfo::FEATURE_BLOCKED_DEVICE;
     aFailureId = "FEATURE_FAILURE_OPENGL_1";
     return NS_OK;
   }
 
-  // Don't evaluate any special cases if we're checking the downloaded
-  // blocklist.
-  if (!aDriverInfo.Length()) {
-    // Blacklist software GL implementations from using layers acceleration.
-    // On the test infrastructure, we'll force-enable layers acceleration.
-    if (aFeature == nsIGfxInfo::FEATURE_OPENGL_LAYERS &&
-        (mIsLlvmpipe || mIsOldSwrast) &&
-        !PR_GetEnv("MOZ_LAYERS_ALLOW_SOFTWARE_GL")) {
-      *aStatus = nsIGfxInfo::FEATURE_BLOCKED_DEVICE;
-      aFailureId = "FEATURE_FAILURE_SOFTWARE_GL";
-      return NS_OK;
-    }
-
-    if (aFeature == nsIGfxInfo::FEATURE_WEBRENDER) {
-      *aStatus = nsIGfxInfo::FEATURE_BLOCKED_OS_VERSION;
-      aFailureId = "FEATURE_UNQUALIFIED_WEBRENDER_LINUX";
-      return NS_OK;
-    }
-
-    // Only check features relevant to Linux.
-    if (aFeature == nsIGfxInfo::FEATURE_OPENGL_LAYERS ||
-        aFeature == nsIGfxInfo::FEATURE_WEBGL_OPENGL ||
-        aFeature == nsIGfxInfo::FEATURE_WEBGL2 ||
-        aFeature == nsIGfxInfo::FEATURE_WEBGL_MSAA) {
-      // whitelist the linux test slaves' current configuration.
-      // this is necessary as they're still using the slightly outdated 190.42
-      // driver. this isn't a huge risk, as at least this is the exact setting
-      // in which we do continuous testing, and this only affects GeForce 9400
-      // cards on linux on this precise driver version, which is very few users.
-      // We do the same thing on Windows XP, see in widget/windows/GfxInfo.cpp
-      if (mIsNVIDIA && !strcmp(mRenderer.get(), "GeForce 9400/PCI/SSE2") &&
-          !strcmp(mVersion.get(), "3.2.0 NVIDIA 190.42")) {
-        *aStatus = nsIGfxInfo::FEATURE_STATUS_OK;
-        return NS_OK;
-      }
-
-      if (mIsMesa) {
-        if (mIsNouveau &&
-            version(mMajorVersion, mMinorVersion) < version(8, 0)) {
-          *aStatus = nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION;
-          aFailureId = "FEATURE_FAILURE_MESA_1";
-          aSuggestedDriverVersion.AssignLiteral("Mesa 8.0");
-        } else if (version(mMajorVersion, mMinorVersion, mRevisionVersion) <
-                   version(7, 10, 3)) {
-          *aStatus = nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION;
-          aFailureId = "FEATURE_FAILURE_MESA_2";
-          aSuggestedDriverVersion.AssignLiteral("Mesa 7.10.3");
-        } else if (mIsOldSwrast) {
-          *aStatus = nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION;
-          aFailureId = "FEATURE_FAILURE_SW_RAST";
-        } else if (mIsLlvmpipe &&
-                   version(mMajorVersion, mMinorVersion) < version(9, 1)) {
-          // bug 791905, Mesa bug 57733, fixed in Mesa 9.1 according to
-          // https://bugs.freedesktop.org/show_bug.cgi?id=57733#c3
-          *aStatus = nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION;
-          aFailureId = "FEATURE_FAILURE_MESA_3";
-        } else if (aFeature == nsIGfxInfo::FEATURE_WEBGL_MSAA) {
-          if (mIsIntel &&
-              version(mMajorVersion, mMinorVersion) < version(8, 1)) {
-            *aStatus = nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION;
-            aFailureId = "FEATURE_FAILURE_MESA_4";
-            aSuggestedDriverVersion.AssignLiteral("Mesa 8.1");
-          }
-        }
-
-      } else if (mIsNVIDIA) {
-        if (version(mMajorVersion, mMinorVersion, mRevisionVersion) <
-            version(257, 21)) {
-          *aStatus = nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION;
-          aFailureId = "FEATURE_FAILURE_OLD_NV";
-          aSuggestedDriverVersion.AssignLiteral("NVIDIA 257.21");
-        }
-      } else if (mIsFGLRX) {
-        // FGLRX does not report a driver version number, so we have the OpenGL
-        // version instead. by requiring OpenGL 3, we effectively require recent
-        // drivers.
-        if (version(mMajorVersion, mMinorVersion, mRevisionVersion) <
-            version(3, 0)) {
-          *aStatus = nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION;
-          aFailureId = "FEATURE_FAILURE_OLD_FGLRX";
-          aSuggestedDriverVersion.AssignLiteral("<Something recent>");
-        }
-        // Bug 724640: FGLRX + Linux 2.6.32 is a crashy combo
-        bool unknownOS = mOS.IsEmpty() || mOSRelease.IsEmpty();
-        bool badOS =
-            mOS.Find("Linux", true) != -1 && mOSRelease.Find("2.6.32") != -1;
-        if (unknownOS || badOS) {
-          *aStatus = nsIGfxInfo::FEATURE_BLOCKED_OS_VERSION;
-          aFailureId = "FEATURE_FAILURE_OLD_OS";
-        }
-      } else {
-        // like on windows, let's block unknown vendors. Think of virtual
-        // machines. Also, this case is hit whenever the GLXtest probe failed to
-        // get driver info or crashed.
-        *aStatus = nsIGfxInfo::FEATURE_BLOCKED_DEVICE;
-      }
-    }
+  // Blacklist software GL implementations from using layers acceleration.
+  // On the test infrastructure, we'll force-enable layers acceleration.
+  if (aFeature == nsIGfxInfo::FEATURE_OPENGL_LAYERS && !mIsAccelerated &&
+      !PR_GetEnv("MOZ_LAYERS_ALLOW_SOFTWARE_GL")) {
+    *aStatus = nsIGfxInfo::FEATURE_BLOCKED_DEVICE;
+    aFailureId = "FEATURE_FAILURE_SOFTWARE_GL";
+    return NS_OK;
   }
 
   return GfxInfoBase::GetFeatureStatusImpl(
       aFeature, aStatus, aSuggestedDriverVersion, aDriverInfo, aFailureId, &os);
 }
 
 NS_IMETHODIMP
 GfxInfo::GetD2DEnabled(bool *aEnabled) { return NS_ERROR_FAILURE; }
@@ -405,17 +393,18 @@ GfxInfo::GetAdapterDescription(nsAString
 
 NS_IMETHODIMP
 GfxInfo::GetAdapterDescription2(nsAString &aAdapterDescription) {
   return NS_ERROR_FAILURE;
 }
 
 NS_IMETHODIMP
 GfxInfo::GetAdapterRAM(nsAString &aAdapterRAM) {
-  aAdapterRAM.Truncate();
+  GetData();
+  CopyUTF8toUTF16(mAdapterRAM, aAdapterRAM);
   return NS_OK;
 }
 
 NS_IMETHODIMP
 GfxInfo::GetAdapterRAM2(nsAString &aAdapterRAM) { return NS_ERROR_FAILURE; }
 
 NS_IMETHODIMP
 GfxInfo::GetAdapterDriver(nsAString &aAdapterDriver) {
@@ -426,17 +415,17 @@ GfxInfo::GetAdapterDriver(nsAString &aAd
 NS_IMETHODIMP
 GfxInfo::GetAdapterDriver2(nsAString &aAdapterDriver) {
   return NS_ERROR_FAILURE;
 }
 
 NS_IMETHODIMP
 GfxInfo::GetAdapterDriverVersion(nsAString &aAdapterDriverVersion) {
   GetData();
-  CopyASCIItoUTF16(mVersion, aAdapterDriverVersion);
+  CopyASCIItoUTF16(mDriverVersion, aAdapterDriverVersion);
   return NS_OK;
 }
 
 NS_IMETHODIMP
 GfxInfo::GetAdapterDriverVersion2(nsAString &aAdapterDriverVersion) {
   return NS_ERROR_FAILURE;
 }
 
@@ -449,29 +438,29 @@ GfxInfo::GetAdapterDriverDate(nsAString 
 NS_IMETHODIMP
 GfxInfo::GetAdapterDriverDate2(nsAString &aAdapterDriverDate) {
   return NS_ERROR_FAILURE;
 }
 
 NS_IMETHODIMP
 GfxInfo::GetAdapterVendorID(nsAString &aAdapterVendorID) {
   GetData();
-  CopyUTF8toUTF16(mVendor, aAdapterVendorID);
+  CopyUTF8toUTF16(mVendorId, aAdapterVendorID);
   return NS_OK;
 }
 
 NS_IMETHODIMP
 GfxInfo::GetAdapterVendorID2(nsAString &aAdapterVendorID) {
   return NS_ERROR_FAILURE;
 }
 
 NS_IMETHODIMP
 GfxInfo::GetAdapterDeviceID(nsAString &aAdapterDeviceID) {
   GetData();
-  CopyUTF8toUTF16(mRenderer, aAdapterDeviceID);
+  CopyUTF8toUTF16(mDeviceId, aAdapterDeviceID);
   return NS_OK;
 }
 
 NS_IMETHODIMP
 GfxInfo::GetAdapterDeviceID2(nsAString &aAdapterDeviceID) {
   return NS_ERROR_FAILURE;
 }
 
@@ -489,31 +478,47 @@ NS_IMETHODIMP
 GfxInfo::GetIsGPU2Active(bool *aIsGPU2Active) { return NS_ERROR_FAILURE; }
 
 #ifdef DEBUG
 
 // Implement nsIGfxInfoDebug
 // We don't support spoofing anything on Linux
 
 NS_IMETHODIMP GfxInfo::SpoofVendorID(const nsAString &aVendorID) {
-  CopyUTF16toUTF8(aVendorID, mVendor);
+  GetData();
+  CopyUTF16toUTF8(aVendorID, mVendorId);
+  mIsAccelerated = !(mVendorId.EqualsLiteral("mesa/llvmpipe") ||
+                     mVendorId.EqualsLiteral("mesa/softpipe") ||
+                     mVendorId.EqualsLiteral("mesa/swrast"));
   return NS_OK;
 }
 
 NS_IMETHODIMP GfxInfo::SpoofDeviceID(const nsAString &aDeviceID) {
-  CopyUTF16toUTF8(aDeviceID, mRenderer);
+  GetData();
+  CopyUTF16toUTF8(aDeviceID, mDeviceId);
   return NS_OK;
 }
 
 NS_IMETHODIMP GfxInfo::SpoofDriverVersion(const nsAString &aDriverVersion) {
-  CopyUTF16toUTF8(aDriverVersion, mVersion);
+  GetData();
+  CopyUTF16toUTF8(aDriverVersion, mDriverVersion);
   return NS_OK;
 }
 
 NS_IMETHODIMP GfxInfo::SpoofOSVersion(uint32_t aVersion) {
   // We don't support OS versioning on Linux. There's just "Linux".
   return NS_OK;
 }
 
+NS_IMETHODIMP GfxInfo::FireTestProcess() {
+  // If the pid is zero, then we have never run the test process to query for
+  // driver information. This would normally be run on startup, but we need to
+  // manually invoke it for XPC shell tests.
+  if (glxtest_pid == 0) {
+    fire_glxtest_process();
+  }
+  return NS_OK;
+}
+
 #endif
 
 }  // end namespace widget
 }  // end namespace mozilla
--- a/widget/GfxInfoX11.h
+++ b/widget/GfxInfoX11.h
@@ -4,16 +4,17 @@
  * This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef __GfxInfoX11_h__
 #define __GfxInfoX11_h__
 
 #include "GfxInfoBase.h"
+#include "nsString.h"
 
 namespace mozilla {
 namespace widget {
 
 class GfxInfo final : public GfxInfoBase {
  public:
   // We only declare the subset of nsIGfxInfo that we actually implement. The
   // rest is brought forward from GfxInfoBase.
@@ -55,27 +56,31 @@ class GfxInfo final : public GfxInfoBase
   ~GfxInfo() {}
 
   virtual nsresult GetFeatureStatusImpl(
       int32_t aFeature, int32_t* aStatus, nsAString& aSuggestedDriverVersion,
       const nsTArray<GfxDriverInfo>& aDriverInfo, nsACString& aFailureId,
       OperatingSystem* aOS = nullptr) override;
   virtual const nsTArray<GfxDriverInfo>& GetGfxDriverInfo() override;
 
+  virtual bool DoesVendorMatch(const nsAString& aBlocklistVendor,
+                               const nsAString& aAdapterVendor) override;
+
  private:
-  nsCString mVendor;
-  nsCString mRenderer;
-  nsCString mVersion;
+  nsCString mVendorId;
+  nsCString mDeviceId;
+  nsCString mDriverVersion;
   nsCString mAdapterDescription;
+  nsCString mAdapterRAM;
   nsCString mOS;
   nsCString mOSRelease;
-  bool mIsMesa, mIsNVIDIA, mIsFGLRX, mIsNouveau, mIsIntel, mIsOldSwrast,
-      mIsLlvmpipe;
   bool mHasTextureFromPixmap;
-  int mGLMajorVersion, mMajorVersion, mMinorVersion, mRevisionVersion;
+  unsigned int mGLMajorVersion, mGLMinorVersion;
+  bool mIsMesa;
+  bool mIsAccelerated;
 
   void AddCrashReportAnnotations();
 };
 
 }  // namespace widget
 }  // namespace mozilla
 
 #endif /* __GfxInfoX11_h__ */
--- a/widget/android/GfxInfo.cpp
+++ b/widget/android/GfxInfo.cpp
@@ -528,16 +528,20 @@ NS_IMETHODIMP GfxInfo::SpoofDriverVersio
 }
 
 NS_IMETHODIMP GfxInfo::SpoofOSVersion(uint32_t aVersion) {
   EnsureInitialized();
   mOSVersion = aVersion;
   return NS_OK;
 }
 
+NS_IMETHODIMP GfxInfo::FireTestProcess() {
+  return NS_OK;
+}
+
 #endif
 
 nsString GfxInfo::Model() {
   EnsureInitialized();
   return mModel;
 }
 
 nsString GfxInfo::Hardware() {
--- a/widget/cocoa/GfxInfo.mm
+++ b/widget/cocoa/GfxInfo.mm
@@ -349,9 +349,14 @@ NS_IMETHODIMP GfxInfo::SpoofDriverVersio
 }
 
 /* void spoofOSVersion (in unsigned long aVersion); */
 NS_IMETHODIMP GfxInfo::SpoofOSVersion(uint32_t aVersion) {
   mOSXVersion = aVersion;
   return NS_OK;
 }
 
+/* void fireTestProcess (); */
+NS_IMETHODIMP GfxInfo::FireTestProcess() {
+  return NS_OK;
+}
+
 #endif
--- a/widget/nsIGfxInfoDebug.idl
+++ b/widget/nsIGfxInfoDebug.idl
@@ -11,9 +11,14 @@
 interface nsIGfxInfoDebug : nsISupports
 {
   void spoofVendorID(in AString aVendorID);
   void spoofDeviceID(in AString aDeviceID);
   
   void spoofDriverVersion(in AString aDriverVersion);
 
   void spoofOSVersion(in unsigned long aVersion);
+
+  /* Manually invoke any test processes required to query for driver
+     information. This is used by XPC shell tests which do not run these queries
+     by default. */
+  void fireTestProcess();
 };
--- a/widget/windows/GfxInfo.cpp
+++ b/widget/windows/GfxInfo.cpp
@@ -1822,9 +1822,13 @@ NS_IMETHODIMP GfxInfo::SpoofDriverVersio
   return NS_OK;
 }
 
 NS_IMETHODIMP GfxInfo::SpoofOSVersion(uint32_t aVersion) {
   mWindowsVersion = aVersion;
   return NS_OK;
 }
 
+NS_IMETHODIMP GfxInfo::FireTestProcess() {
+  return NS_OK;
+}
+
 #endif