Merge mozilla-central to mozilla-inbound. a=merge
authorDaniel Varga <dvarga@mozilla.com>
Thu, 16 May 2019 00:33:52 +0300
changeset 532986 0e8d68920793aca5c383e4977aa3899a2bc5876b
parent 532985 846d0d928675e4017254197e5eba61a6af552378 (current diff)
parent 532789 9e68d485f7ccea5d8f065fb5f833858e71b4b04a (diff)
child 532987 c1b13e664eb4bf1e49d4dab83a9cf93a5277e4c3
push id11276
push userrgurzau@mozilla.com
push dateMon, 20 May 2019 13:11:24 +0000
treeherdermozilla-beta@847755a7c325 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone68.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge mozilla-central to mozilla-inbound. a=merge
gfx/wr/webrender/src/batch.rs
gfx/wr/webrender/src/frame_builder.rs
gfx/wr/webrender/src/glyph_rasterizer/mod.rs
gfx/wr/webrender/src/glyph_rasterizer/no_pathfinder.rs
gfx/wr/webrender/src/glyph_rasterizer/pathfinder.rs
gfx/wr/webrender/src/internal_types.rs
gfx/wr/webrender/src/lib.rs
gfx/wr/webrender/src/picture.rs
gfx/wr/webrender/src/prim_store/mod.rs
gfx/wr/webrender/src/prim_store/picture.rs
gfx/wr/webrender/src/prim_store/text_run.rs
gfx/wr/webrender/src/render_backend.rs
gfx/wr/webrender/src/render_task.rs
gfx/wr/webrender/src/renderer.rs
gfx/wr/webrender/src/resource_cache.rs
gfx/wr/webrender/src/tiling.rs
tools/update-verify/release/updates/verify.sh
--- a/accessible/base/nsAccessiblePivot.cpp
+++ b/accessible/base/nsAccessiblePivot.cpp
@@ -5,39 +5,36 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "nsAccessiblePivot.h"
 
 #include "HyperTextAccessible.h"
 #include "nsAccUtils.h"
 #include "States.h"
 #include "xpcAccessibleDocument.h"
+#include "nsTArray.h"
+#include "mozilla/Maybe.h"
 
 using namespace mozilla::a11y;
+using mozilla::Maybe;
 
 /**
  * An object that stores a given traversal rule during the pivot movement.
  */
 class RuleCache {
  public:
   explicit RuleCache(nsIAccessibleTraversalRule* aRule)
-      : mRule(aRule),
-        mAcceptRoles(nullptr),
-        mAcceptRolesLength{0},
-        mPreFilter{0} {}
-  ~RuleCache() {
-    if (mAcceptRoles) free(mAcceptRoles);
-  }
+      : mRule(aRule), mPreFilter{0} {}
+  ~RuleCache() {}
 
   nsresult ApplyFilter(Accessible* aAccessible, uint16_t* aResult);
 
  private:
   nsCOMPtr<nsIAccessibleTraversalRule> mRule;
-  uint32_t* mAcceptRoles;
-  uint32_t mAcceptRolesLength;
+  Maybe<nsTArray<uint32_t>> mAcceptRoles;
   uint32_t mPreFilter;
 };
 
 ////////////////////////////////////////////////////////////////////////////////
 // nsAccessiblePivot
 
 nsAccessiblePivot::nsAccessiblePivot(Accessible* aRoot)
     : mRoot(aRoot),
@@ -817,33 +814,34 @@ bool nsAccessiblePivot::NotifyOfPivotCha
                                             int16_t aReason,
                                             int16_t aBoundaryType,
                                             bool aIsFromUserInput) {
   if (aOldPosition == mPosition && aOldStart == mStartOffset &&
       aOldEnd == mEndOffset)
     return false;
 
   nsCOMPtr<nsIAccessible> xpcOldPos = ToXPC(aOldPosition);  // death grip
-  nsTObserverArray<nsCOMPtr<nsIAccessiblePivotObserver> >::ForwardIterator iter(
+  nsTObserverArray<nsCOMPtr<nsIAccessiblePivotObserver>>::ForwardIterator iter(
       mObservers);
   while (iter.HasMore()) {
     nsIAccessiblePivotObserver* obs = iter.GetNext();
     obs->OnPivotChanged(this, xpcOldPos, aOldStart, aOldEnd, ToXPC(mPosition),
                         mStartOffset, mEndOffset, aReason, aBoundaryType,
                         aIsFromUserInput);
   }
 
   return true;
 }
 
 nsresult RuleCache::ApplyFilter(Accessible* aAccessible, uint16_t* aResult) {
   *aResult = nsIAccessibleTraversalRule::FILTER_IGNORE;
 
   if (!mAcceptRoles) {
-    nsresult rv = mRule->GetMatchRoles(&mAcceptRoles, &mAcceptRolesLength);
+    mAcceptRoles.emplace();
+    nsresult rv = mRule->GetMatchRoles(*mAcceptRoles);
     NS_ENSURE_SUCCESS(rv, rv);
     rv = mRule->GetPreFilter(&mPreFilter);
     NS_ENSURE_SUCCESS(rv, rv);
   }
 
   if (mPreFilter) {
     uint64_t state = aAccessible->State();
 
@@ -869,21 +867,21 @@ nsresult RuleCache::ApplyFilter(Accessib
       nsIFrame* frame = aAccessible->GetFrame();
       if (frame->StyleEffects()->mOpacity == 0.0f) {
         *aResult |= nsIAccessibleTraversalRule::FILTER_IGNORE_SUBTREE;
         return NS_OK;
       }
     }
   }
 
-  if (mAcceptRolesLength > 0) {
+  if (mAcceptRoles->Length() > 0) {
     uint32_t accessibleRole = aAccessible->Role();
     bool matchesRole = false;
-    for (uint32_t idx = 0; idx < mAcceptRolesLength; idx++) {
-      matchesRole = mAcceptRoles[idx] == accessibleRole;
+    for (uint32_t idx = 0; idx < mAcceptRoles->Length(); idx++) {
+      matchesRole = mAcceptRoles->ElementAt(idx) == accessibleRole;
       if (matchesRole) break;
     }
     if (!matchesRole) return NS_OK;
   }
 
   uint16_t matchResult = nsIAccessibleTraversalRule::FILTER_IGNORE;
   nsresult rv = mRule->Match(ToXPC(aAccessible), &matchResult);
   if (NS_SUCCEEDED(rv)) {
--- a/accessible/interfaces/nsIAccessiblePivot.idl
+++ b/accessible/interfaces/nsIAccessiblePivot.idl
@@ -240,21 +240,19 @@ interface nsIAccessibleTraversalRule : n
 
   /**
    * Retrieve a list of roles that the traversal rule should test for. Any node
    * with a role not in this list will automatically be ignored. An empty list
    * will match all roles. It should be assumed that this method is called once
    * at the start of a traversal, so changing the method's return result after
    * that would have no affect.
    *
-   * @param aRoles [out] an array of the roles to match.
-   * @param aCount [out] the length of the array.
+   * @return an array of the roles to match.
    */
-  void getMatchRoles([array, size_is(aCount)]out unsigned long aRoles,
-                     [retval]out unsigned long aCount);
+  Array<unsigned long> getMatchRoles();
 
   /**
    * Determines if a given accessible is to be accepted in our traversal rule
    *
    * @param aAccessible [in] accessible to examine.
    * @return a bitfield of FILTER_MATCH and FILTER_IGNORE_SUBTREE.
    */
   unsigned short match(in nsIAccessible aAccessible);
--- a/accessible/interfaces/nsIAccessibleTable.idl
+++ b/accessible/interfaces/nsIAccessibleTable.idl
@@ -157,39 +157,33 @@ interface nsIAccessibleTable : nsISuppor
   /**
    * Return an array of selected cells.
    */
   readonly attribute nsIArray selectedCells;
 
   /**
    * Return an array of cell indices currently selected.
    *
-   * @param  cellsArraySize  [in] length of array
-   * @param  cellsArray      [in] array of indexes of selected cells
+   * @return array of indexes of selected cells
    */
-  void getSelectedCellIndices(out unsigned long cellsArraySize,
-                              [retval, array, size_is(cellsArraySize)] out long cellsArray);
+  Array<uint32_t> getSelectedCellIndices();
 
   /**
    * Return an array of column indices currently selected.
    *
-   * @param  columnsArraySize  [in] length of array
-   * @param  columnsArray      [in] array of indices of selected columns
+   * @return array of indices of selected columns
    */
-  void getSelectedColumnIndices(out unsigned long columnsArraySize,
-                                [retval, array, size_is(columnsArraySize)] out long columnsArray);
+  Array<uint32_t> getSelectedColumnIndices();
 
   /**
    * Return an array of row indices currently selected.
    *
-   * @param  rowsArraySize  [in] Length of array
-   * @param  rowsArray      [in] array of indices of selected rows
+   * @return array of indices of selected rows
    */
-  void getSelectedRowIndices(out unsigned long rowsArraySize,
-                             [retval, array, size_is(rowsArraySize)] out long rowsArray);
+  Array<uint32_t> getSelectedRowIndices();
 
   /**
    * Select a row and unselects all previously selected rows.
    *
    * @param  rowIndex  [in] the row index to select
    */
   void selectRow(in long rowIndex);
 
--- a/accessible/jsat/Traversal.jsm
+++ b/accessible/jsat/Traversal.jsm
@@ -31,19 +31,18 @@ function BaseTraversalRule(aRoles, aMatc
     }
   }
   this._matchFunc = aMatchFunc || function() { return Filters.MATCH; };
   this.preFilter = aPreFilter || gSimplePreFilter;
   this.containerRule = aContainerRule;
 }
 
 BaseTraversalRule.prototype = {
-    getMatchRoles: function BaseTraversalRule_getmatchRoles(aRoles) {
-      aRoles.value = this._matchRoles;
-      return aRoles.value.length;
+    getMatchRoles: function BaseTraversalRule_getmatchRoles() {
+      return this._matchRoles;
     },
 
     match: function BaseTraversalRule_match(aAccessible) {
       let role = aAccessible.role;
       if (role == Roles.INTERNAL_FRAME) {
         return (Utils.getMessageManagerForFrame(aAccessible.DOMNode)) ?
           Filters.MATCH | Filters.IGNORE_SUBTREE : Filters.IGNORE;
       }
--- a/accessible/tests/mochitest/pivot.js
+++ b/accessible/tests/mochitest/pivot.js
@@ -18,38 +18,36 @@ const NS_ERROR_INVALID_ARG = 0x80070057;
 // //////////////////////////////////////////////////////////////////////////////
 // Traversal rules
 
 /**
  * Rule object to traverse all focusable nodes and text nodes.
  */
 var HeadersTraversalRule =
 {
-  getMatchRoles(aRules) {
-    aRules.value = [ROLE_HEADING];
-    return aRules.value.length;
+  getMatchRoles() {
+    return [ROLE_HEADING];
   },
 
   preFilter: PREFILTER_INVISIBLE,
 
   match(aAccessible) {
     return FILTER_MATCH;
   },
 
   QueryInterface: ChromeUtils.generateQI([nsIAccessibleTraversalRule]),
 };
 
 /**
  * Traversal rule for all focusable nodes or leafs.
  */
 var ObjectTraversalRule =
 {
-  getMatchRoles(aRules) {
-    aRules.value = [];
-    return 0;
+  getMatchRoles() {
+    return [];
   },
 
   preFilter: PREFILTER_INVISIBLE | PREFILTER_TRANSPARENT,
 
   match(aAccessible) {
     var rv = FILTER_IGNORE;
     var role = aAccessible.role;
     if (hasState(aAccessible, STATE_FOCUSABLE) &&
--- a/accessible/tests/mochitest/table.js
+++ b/accessible/tests/mochitest/table.js
@@ -387,20 +387,19 @@ function testTableSelection(aIdentifier,
       selCols.push(colIdx);
   }
 
   // selectedColsCount test
   is(acc.selectedColumnCount, selCols.length,
      msg + "Wrong count of selected columns for " + prettyName(aIdentifier));
 
   // getSelectedColumns test
-  var actualSelColsCountObj = { value: null };
-  var actualSelCols = acc.getSelectedColumnIndices(actualSelColsCountObj);
+  var actualSelCols = acc.getSelectedColumnIndices();
 
-  var actualSelColsCount = actualSelColsCountObj.value;
+  var actualSelColsCount = actualSelCols.length;
   is(actualSelColsCount, selCols.length,
       msg + "Wrong count of selected columns for " + prettyName(aIdentifier) +
       "from getSelectedColumns.");
 
   for (let i = 0; i < actualSelColsCount; i++) {
     is(actualSelCols[i], selCols[i],
         msg + "Column at index " + selCols[i] + " should be selected.");
   }
@@ -427,20 +426,19 @@ function testTableSelection(aIdentifier,
       selRows.push(rowIdx);
   }
 
   // selectedRowCount test
   is(acc.selectedRowCount, selRows.length,
      msg + "Wrong count of selected rows for " + prettyName(aIdentifier));
 
   // getSelectedRows test
-  var actualSelrowCountObj = { value: null };
-  var actualSelRows = acc.getSelectedRowIndices(actualSelrowCountObj);
+  var actualSelRows = acc.getSelectedRowIndices();
 
-  var actualSelrowCount = actualSelrowCountObj.value;
+  var actualSelrowCount = actualSelRows.length;
   is(actualSelrowCount, selRows.length,
       msg + "Wrong count of selected rows for " + prettyName(aIdentifier) +
       "from getSelectedRows.");
 
   for (let i = 0; i < actualSelrowCount; i++) {
     is(actualSelRows[i], selRows[i],
         msg + "Row at index " + selRows[i] + " should be selected.");
   }
@@ -464,20 +462,19 @@ function testTableSelection(aIdentifier,
     }
   }
 
   // selectedCellCount tests
   is(acc.selectedCellCount, selCells.length,
      msg + "Wrong count of selected cells for " + prettyName(aIdentifier));
 
   // getSelectedCellIndices test
-  var actualSelCellsCountObj = { value: null };
-  var actualSelCells = acc.getSelectedCellIndices(actualSelCellsCountObj);
+  var actualSelCells = acc.getSelectedCellIndices();
 
-  var actualSelCellsCount = actualSelCellsCountObj.value;
+  var actualSelCellsCount = actualSelCells.length;
   is(actualSelCellsCount, selCells.length,
      msg + "Wrong count of selected cells for " + prettyName(aIdentifier) +
      "from getSelectedCells.");
 
   for (let i = 0; i < actualSelCellsCount; i++) {
     is(actualSelCells[i], selCells[i],
        msg + "getSelectedCellIndices: Cell at index " + selCells[i] +
        " should be selected.");
--- a/accessible/xpcom/xpcAccessibleTable.cpp
+++ b/accessible/xpcom/xpcAccessibleTable.cpp
@@ -250,78 +250,38 @@ xpcAccessibleTable::GetSelectedCells(nsI
     selCells->AppendElement(static_cast<nsIAccessible*>(ToXPC(cell)));
   }
 
   NS_ADDREF(*aSelectedCells = selCells);
   return NS_OK;
 }
 
 NS_IMETHODIMP
-xpcAccessibleTable::GetSelectedCellIndices(uint32_t* aCellsArraySize,
-                                           int32_t** aCellsArray) {
-  NS_ENSURE_ARG_POINTER(aCellsArraySize);
-  *aCellsArraySize = 0;
-
-  NS_ENSURE_ARG_POINTER(aCellsArray);
-  *aCellsArray = 0;
-
+xpcAccessibleTable::GetSelectedCellIndices(nsTArray<uint32_t>& aCellsArray) {
   if (!Intl()) return NS_ERROR_FAILURE;
 
-  AutoTArray<uint32_t, XPC_TABLE_DEFAULT_SIZE> cellsArray;
-  Intl()->SelectedCellIndices(&cellsArray);
-
-  *aCellsArraySize = cellsArray.Length();
-  *aCellsArray =
-      static_cast<int32_t*>(moz_xmalloc(*aCellsArraySize * sizeof(int32_t)));
-  memcpy(*aCellsArray, cellsArray.Elements(),
-         *aCellsArraySize * sizeof(int32_t));
+  Intl()->SelectedCellIndices(&aCellsArray);
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
-xpcAccessibleTable::GetSelectedColumnIndices(uint32_t* aColsArraySize,
-                                             int32_t** aColsArray) {
-  NS_ENSURE_ARG_POINTER(aColsArraySize);
-  *aColsArraySize = 0;
-
-  NS_ENSURE_ARG_POINTER(aColsArray);
-  *aColsArray = 0;
-
+xpcAccessibleTable::GetSelectedColumnIndices(nsTArray<uint32_t>& aColsArray) {
   if (!Intl()) return NS_ERROR_FAILURE;
 
-  AutoTArray<uint32_t, XPC_TABLE_DEFAULT_SIZE> colsArray;
-  Intl()->SelectedColIndices(&colsArray);
-
-  *aColsArraySize = colsArray.Length();
-  *aColsArray =
-      static_cast<int32_t*>(moz_xmalloc(*aColsArraySize * sizeof(int32_t)));
-  memcpy(*aColsArray, colsArray.Elements(), *aColsArraySize * sizeof(int32_t));
+  Intl()->SelectedColIndices(&aColsArray);
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
-xpcAccessibleTable::GetSelectedRowIndices(uint32_t* aRowsArraySize,
-                                          int32_t** aRowsArray) {
-  NS_ENSURE_ARG_POINTER(aRowsArraySize);
-  *aRowsArraySize = 0;
-
-  NS_ENSURE_ARG_POINTER(aRowsArray);
-  *aRowsArray = 0;
-
+xpcAccessibleTable::GetSelectedRowIndices(nsTArray<uint32_t>& aRowsArray) {
   if (!Intl()) return NS_ERROR_FAILURE;
 
-  AutoTArray<uint32_t, XPC_TABLE_DEFAULT_SIZE> rowsArray;
-  Intl()->SelectedRowIndices(&rowsArray);
-
-  *aRowsArraySize = rowsArray.Length();
-  *aRowsArray =
-      static_cast<int32_t*>(moz_xmalloc(*aRowsArraySize * sizeof(int32_t)));
-  memcpy(*aRowsArray, rowsArray.Elements(), *aRowsArraySize * sizeof(int32_t));
+  Intl()->SelectedRowIndices(&aRowsArray);
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
 xpcAccessibleTable::GetColumnIndexAt(int32_t aCellIdx, int32_t* aColIdx) {
   NS_ENSURE_ARG_POINTER(aColIdx);
   *aColIdx = -1;
--- a/accessible/xpcom/xpcAccessibleTable.h
+++ b/accessible/xpcom/xpcAccessibleTable.h
@@ -50,22 +50,19 @@ class xpcAccessibleTable : public xpcAcc
   NS_IMETHOD IsColumnSelected(int32_t aColIdx, bool* _retval) final;
   NS_IMETHOD IsRowSelected(int32_t aRowIdx, bool* _retval) final;
   NS_IMETHOD IsCellSelected(int32_t aRowIdx, int32_t aColIdx,
                             bool* _retval) final;
   NS_IMETHOD GetSelectedCellCount(uint32_t* aSelectedCellCount) final;
   NS_IMETHOD GetSelectedColumnCount(uint32_t* aSelectedColumnCount) final;
   NS_IMETHOD GetSelectedRowCount(uint32_t* aSelectedRowCount) final;
   NS_IMETHOD GetSelectedCells(nsIArray** aSelectedCell) final;
-  NS_IMETHOD GetSelectedCellIndices(uint32_t* aCellsArraySize,
-                                    int32_t** aCellsArray) final;
-  NS_IMETHOD GetSelectedColumnIndices(uint32_t* aColsArraySize,
-                                      int32_t** aColsArray) final;
-  NS_IMETHOD GetSelectedRowIndices(uint32_t* aRowsArraySize,
-                                   int32_t** aRowsArray) final;
+  NS_IMETHOD GetSelectedCellIndices(nsTArray<uint32_t>& aCellsArray) final;
+  NS_IMETHOD GetSelectedColumnIndices(nsTArray<uint32_t>& aColsArray) final;
+  NS_IMETHOD GetSelectedRowIndices(nsTArray<uint32_t>& aRowsArray) final;
   NS_IMETHOD SelectColumn(int32_t aColIdx) final;
   NS_IMETHOD SelectRow(int32_t aRowIdx) final;
   NS_IMETHOD UnselectColumn(int32_t aColIdx) final;
   NS_IMETHOD UnselectRow(int32_t aRowIdx) final;
   NS_IMETHOD IsProbablyForLayout(bool* aIsForLayout) final;
 
  protected:
   virtual ~xpcAccessibleTable() {}
--- a/browser/app/winlauncher/moz.build
+++ b/browser/app/winlauncher/moz.build
@@ -6,22 +6,29 @@
 
 Library('winlauncher')
 
 FORCE_STATIC_LIB = True
 
 UNIFIED_SOURCES += [
     '/ipc/mscom/ProcessRuntime.cpp',
     '/widget/windows/WindowsConsole.cpp',
-    'DllBlocklistWin.cpp',
     'ErrorHandler.cpp',
     'LauncherProcessWin.cpp',
     'LaunchUnelevated.cpp',
 ]
 
+SOURCES += [
+    'DllBlocklistWin.cpp',
+]
+# Our patched NtMapViewOfSection can be called before the process's import
+# table is populated.  Don't let the compiler insert any instrumentation
+# that might call an import.
+SOURCES['DllBlocklistWin.cpp'].no_pgo = True
+
 OS_LIBS += [
     'ntdll',
     'oleaut32',
     'ole32',
     'rpcrt4',
     'version',
 ]
 
--- a/browser/base/content/browser-siteIdentity.js
+++ b/browser/base/content/browser-siteIdentity.js
@@ -462,16 +462,42 @@ var gIdentityHandler = {
       return this._IDNService.convertToDisplayIDN(this._uri.host, {});
     } catch (e) {
       // If something goes wrong (e.g. host is an IP address) just fail back
       // to the full domain.
       return this._uri.host;
     }
   },
 
+  getHostForDisplay() {
+    let host = "";
+
+    try {
+      host = this.getEffectiveHost();
+    } catch (e) {
+      // Some URIs might have no hosts.
+    }
+
+    let readerStrippedURI = ReaderMode.getOriginalUrlObjectForDisplay(this._uri.displaySpec);
+    if (readerStrippedURI) {
+      host = readerStrippedURI.host;
+    }
+
+    if (this._pageExtensionPolicy) {
+      host = this._pageExtensionPolicy.name;
+    }
+
+    // Fallback for special protocols.
+    if (!host) {
+      host = this._uri.specIgnoringRef;
+    }
+
+    return host;
+  },
+
   /**
    * Return the CSS class name to set on the "fullscreen-warning" element to
    * display information about connection security in the notification shown
    * when a site enters the fullscreen mode.
    */
   get pointerlockFsWarningClassName() {
     // Note that the fullscreen warning does not handle _isSecureInternalUI.
     if (this._uriHasHost && this._isEV) {
@@ -736,39 +762,19 @@ var gIdentityHandler = {
       updateAttribute(element, "ciphers", ciphers);
       updateAttribute(element, "mixedcontent", mixedcontent);
       updateAttribute(element, "isbroken", this._isBroken);
     }
 
     // Initialize the optional strings to empty values
     let supplemental = "";
     let verifier = "";
-    let host = "";
+    let host = this.getHostForDisplay();
     let owner = "";
 
-    try {
-      host = this.getEffectiveHost();
-    } catch (e) {
-      // Some URIs might have no hosts.
-    }
-
-    let readerStrippedURI = ReaderMode.getOriginalUrlObjectForDisplay(this._uri.displaySpec);
-    if (readerStrippedURI) {
-      host = readerStrippedURI.host;
-    }
-
-    if (this._pageExtensionPolicy) {
-      host = this._pageExtensionPolicy.name;
-    }
-
-    // Fallback for special protocols.
-    if (!host) {
-      host = this._uri.specIgnoringRef;
-    }
-
     // Fill in the CA name if we have a valid TLS certificate.
     if (this._isSecure || this._isCertUserOverridden) {
       verifier = this._identityIconLabels.tooltipText;
     }
 
     // Fill in organization information if we have a valid EV certificate.
     if (this._isEV) {
       let iData = this.getIdentityData();
@@ -942,17 +948,21 @@ var gIdentityHandler = {
     let htmlString = "<a href=\"" + value + "\">" + value + "</a>";
 
     let windowUtils = window.windowUtils;
     let scale = windowUtils.screenPixelsPerCSSPixel / windowUtils.fullZoom;
     let canvas = document.createElementNS("http://www.w3.org/1999/xhtml", "canvas");
     canvas.width = 550 * scale;
     let ctx = canvas.getContext("2d");
     ctx.font = `${14 * scale}px sans-serif`;
-    ctx.fillText(`${value}`, 10, 50);
+    ctx.fillText(`${value}`, 20 * scale, 14 * scale);
+    let tabIcon = document.getAnonymousElementByAttribute(gBrowser.selectedTab, "anonid", "tab-icon-image");
+    let image = new Image();
+    image.src = tabIcon.src;
+    ctx.drawImage(image, 0, 0, 16 * scale, 16 * scale);
 
     let dt = event.dataTransfer;
     dt.setData("text/x-moz-url", urlString);
     dt.setData("text/uri-list", value);
     dt.setData("text/plain", value);
     dt.setData("text/html", htmlString);
     dt.setDragImage(canvas, 16, 16);
   },
--- a/browser/base/content/browser-siteProtections.js
+++ b/browser/base/content/browser-siteProtections.js
@@ -12,28 +12,45 @@ var gProtectionsHandler = {
   get _protectionsPopup() {
     delete this._protectionsPopup;
     return this._protectionsPopup = document.getElementById("protections-popup");
   },
   get _protectionsIconBox() {
     delete this._protectionsIconBox;
     return this._protectionsIconBox = document.getElementById("tracking-protection-icon-animatable-box");
   },
+  get _protectionsPopupMainViewHeaderLabel() {
+    delete this._protectionsPopupMainViewHeaderLabel;
+    return this._protectionsPopupMainViewHeaderLabel =
+      document.getElementById("protections-popup-mainView-panel-header-span");
+  },
 
   handleProtectionsButtonEvent(event) {
     event.stopPropagation();
     if ((event.type == "click" && event.button != 0) ||
         (event.type == "keypress" && event.charCode != KeyEvent.DOM_VK_SPACE &&
          event.keyCode != KeyEvent.DOM_VK_RETURN)) {
       return; // Left click, space or enter only
     }
 
     // Make sure that the display:none style we set in xul is removed now that
     // the popup is actually needed
     this._protectionsPopup.hidden = false;
 
+    // Refresh strings.
+    this.refreshProtectionsPopup();
+
     // Now open the popup, anchored off the primary chrome element
     PanelMultiView.openPopup(this._protectionsPopup, this._protectionsIconBox, {
       position: "bottomcenter topleft",
       triggerEvent: event,
     }).catch(Cu.reportError);
   },
+
+  refreshProtectionsPopup() {
+    let host = gIdentityHandler.getHostForDisplay();
+
+    // Push the appropriate strings out to the UI.
+    this._protectionsPopupMainViewHeaderLabel.textContent =
+      // gNavigatorBundle.getFormattedString("protections.header", [host]);
+      `Tracking Protections for ${host}`;
+  },
 };
--- a/browser/base/content/browser.xul
+++ b/browser/base/content/browser.xul
@@ -841,24 +841,16 @@
         <toolbaritem id="urlbar-container" flex="400" persist="width"
                      removable="false"
                      class="chromeclass-location" overflows="false">
             <toolbartabstop/>
             <textbox id="urlbar" flex="1"
                      placeholder="&urlbar.placeholder2;"
                      defaultPlaceholder="&urlbar.placeholder2;"
                      focused="true"
-                     type="autocomplete"
-                     autocompletesearch="unifiedcomplete"
-                     autocompletesearchparam="enable-actions"
-                     autocompletepopup="PopupAutoCompleteRichResult"
-                     completeselectedindex="true"
-                     tabscrolling="true"
-                     ontextentered="this.handleCommand(param);"
-                     ontextreverted="return this.handleRevert();"
                      pageproxystate="invalid">
               <!-- Use onclick instead of normal popup= syntax since the popup
                    code fires onmousedown, and hence eats our favicon drag events. -->
               <box id="identity-box" role="button"
                    align="center"
                    aria-label="&urlbar.viewSiteInfo.label;"
                    onclick="gIdentityHandler.handleIdentityButtonEvent(event);"
                    onkeypress="gIdentityHandler.handleIdentityButtonEvent(event);"
--- a/browser/base/content/test/performance/browser_startup_content_mainthreadio.js
+++ b/browser/base/content/test/performance/browser_startup_content_mainthreadio.js
@@ -68,28 +68,16 @@ const processes = {
       condition: !WIN, // Visible on Windows with an open marker
       stat: 1,
     },
     { // bug 1376994
       path: "XCurProcD:omni.ja",
       condition: !WIN, // Visible on Windows with an open marker
       stat: 1,
     },
-    { // bug 1543761
-      path: "GreD:chrome.manifest",
-      condition: !WIN, // Visible on Windows with an open marker
-      stat: 1,
-      close: 1,
-    },
-    { // bug 1376994, bug 1543761
-      path: "XCurProcD:chrome.manifest",
-      condition: !WIN, // Visible on Windows with an open marker
-      stat: 1,
-      close: 1,
-    },
     { // Exists call in ScopedXREEmbed::SetAppDir
       path: "XCurProcD:",
       condition: WIN,
       stat: 1,
     },
     { // bug 1357205
       path: "XREAppFeat:webcompat@mozilla.org.xpi",
       condition: !WIN,
@@ -107,28 +95,16 @@ const processes = {
       condition: !WIN, // Visible on Windows with an open marker
       stat: 1,
     },
     { // bug 1376994
       path: "XCurProcD:omni.ja",
       condition: !WIN, // Visible on Windows with an open marker
       stat: 1,
     },
-    { // bug 1543761
-      path: "GreD:chrome.manifest",
-      condition: !WIN, // Visible on Windows with an open marker
-      stat: 1,
-      close: 1,
-    },
-    { // bug 1376994, bug 1543761
-      path: "XCurProcD:chrome.manifest",
-      condition: !WIN, // Visible on Windows with an open marker
-      stat: 1,
-      close: 1,
-    },
     { // Exists call in ScopedXREEmbed::SetAppDir
       path: "XCurProcD:",
       condition: WIN,
       stat: 1,
     },
     { // bug 1357205
       path: "XREAppFeat:webcompat@mozilla.org.xpi",
       condition: !WIN,
@@ -141,28 +117,16 @@ const processes = {
       condition: !WIN, // Visible on Windows with an open marker
       stat: 1,
     },
     { // bug 1376994
       path: "XCurProcD:omni.ja",
       condition: !WIN, // Visible on Windows with an open marker
       stat: 1,
     },
-    { // bug 1543761
-      path: "GreD:chrome.manifest",
-      condition: !WIN, // Visible on Windows with an open marker
-      stat: 1,
-      close: 1,
-    },
-    { // bug 1376994, bug 1543761
-      path: "XCurProcD:chrome.manifest",
-      condition: !WIN, // Visible on Windows with an open marker
-      stat: 1,
-      close: 1,
-    },
     { // Exists call in ScopedXREEmbed::SetAppDir
       path: "XCurProcD:",
       condition: WIN,
       stat: 1,
     },
     { // bug 1357205
       path: "XREAppFeat:webcompat@mozilla.org.xpi",
       condition: !WIN,
--- a/browser/base/content/test/performance/browser_startup_mainthreadio.js
+++ b/browser/base/content/test/performance/browser_startup_mainthreadio.js
@@ -195,28 +195,16 @@ const startupPhases = {
       stat: 1,
     },
     { // bug 1541601
       path: "PrfDef:channel-prefs.js",
       stat: 1,
       read: 1,
       close: 1,
     },
-    { // bug 1543761
-      path: "GreD:chrome.manifest",
-      condition: !WIN, // Visible on Windows with an open marker
-      stat: 1,
-      close: 1,
-    },
-    { // bug 1376994, bug 1543761
-      path: "XCurProcD:chrome.manifest",
-      condition: !WIN, // Visible on Windows with an open marker
-      stat: 1,
-      close: 1,
-    },
     { // At least the read seems unavoidable
       path: "PrefD:prefs.js",
       stat: 1,
       read: 1,
       close: 1,
     },
     { // bug 1543752
       path: "PrefD:user.js",
--- a/browser/base/content/urlbarBindings.xml
+++ b/browser/base/content/urlbarBindings.xml
@@ -52,17 +52,25 @@ file, You can obtain one at http://mozil
                       tooltiptext="&urlbar.openHistoryPopup.tooltip;"
                       allowevents="true"
                       xbl:inherits="open,parentfocused=focused,usertyping"/>
       <children includes="hbox"/>
     </content>
   </binding>
 
   <binding id="legacy-urlbar" extends="chrome://global/content/bindings/autocomplete.xml#autocomplete">
-    <content newlines="stripsurroundingwhitespace">
+    <content newlines="stripsurroundingwhitespace"
+             type="autocomplete"
+             autocompletesearch="unifiedcomplete"
+             autocompletesearchparam="enable-actions"
+             autocompletepopup="PopupAutoCompleteRichResult"
+             completeselectedindex="true"
+             tabscrolling="true"
+             ontextentered="this.handleCommand(param);"
+             ontextreverted="return this.handleRevert();">
       <children includes="box"/>
       <xul:moz-input-box anonid="moz-input-box"
                          tooltip="aHTMLTooltip"
                          class="urlbar-input-box"
                          flex="1">
         <children/>
         <html:input anonid="scheme"
                     class="urlbar-scheme textbox-input"
--- a/browser/components/controlcenter/content/protectionsPanel.inc.xul
+++ b/browser/components/controlcenter/content/protectionsPanel.inc.xul
@@ -10,16 +10,20 @@
        orient="vertical">
 
   <panelmultiview id="protections-popup-multiView"
                   mainViewId="protections-popup-mainView">
     <panelview id="protections-popup-mainView"
                descriptionheightworkaround="true">
       <vbox id="protections-popup-mainView-panel-header">
         <label>
-          <html:span id="protections-popup-mainView-panel-header-span">
-            <html:h1>Watch this space! :)</html:h1>
-          </html:span>
+          <html:span id="protections-popup-mainView-panel-header-span"/>
+        </label>
+      </vbox>
+
+      <vbox id="protections-popup-placeholder" class="identity-popup-section">
+        <label>
+          <html:h2>Watch this space! :)</html:h2>
         </label>
       </vbox>
     </panelview>
   </panelmultiview>
 </panel>
--- a/browser/components/extensions/test/xpcshell/test_ext_browsingData_passwords.js
+++ b/browser/components/extensions/test/xpcshell/test_ext_browsingData_passwords.js
@@ -10,19 +10,18 @@ const REFERENCE_DATE = Date.now();
 const LOGIN_USERNAME = "username";
 const LOGIN_PASSWORD = "password";
 const LOGIN_USERNAME_FIELD = "username_field";
 const LOGIN_PASSWORD_FIELD = "password_field";
 const OLD_HOST = "http://mozilla.org";
 const NEW_HOST = "http://mozilla.com";
 
 function checkLoginExists(host, shouldExist) {
-  let count = {value: 0};
-  loginManager.findLogins(count, host, "", null);
-  equal(count.value, shouldExist ? 1 : 0, `Login was ${shouldExist ? "" : "not "} found.`);
+  let logins = loginManager.findLogins(host, "", null);
+  equal(logins.length, shouldExist ? 1 : 0, `Login was ${shouldExist ? "" : "not "} found.`);
 }
 
 function addLogin(host, timestamp) {
   checkLoginExists(host, false);
   let login = Cc["@mozilla.org/login-manager/loginInfo;1"]
               .createInstance(Ci.nsILoginInfo);
   login.init(host, "", null, LOGIN_USERNAME, LOGIN_PASSWORD,
              LOGIN_USERNAME_FIELD, LOGIN_PASSWORD_FIELD);
--- a/browser/components/migration/tests/marionette/test_refresh_firefox.py
+++ b/browser/components/migration/tests/marionette/test_refresh_firefox.py
@@ -193,17 +193,17 @@ class TestFirefoxRefresh(MarionetteTestC
           let storage = new FxAccountsStorageManager();
           let data = {email: "test@test.com", uid: "uid", keyFetchToken: "top-secret"};
           storage.initialize(data);
           storage.finalize().then(resolve);
         """)
 
     def checkPassword(self):
         loginInfo = self.marionette.execute_script("""
-          let ary = Services.logins.findLogins({},
+          let ary = Services.logins.findLogins(
             "test.marionette.mozilla.com",
             "http://test.marionette.mozilla.com/some/form/",
             null, {});
           return ary.length ? ary : {username: "null", password: "null"};
         """)
         self.assertEqual(len(loginInfo), 1)
         self.assertEqual(loginInfo[0]['username'], self._username)
         self.assertEqual(loginInfo[0]['password'], self._password)
--- a/browser/components/migration/tests/unit/test_Chrome_passwords.js
+++ b/browser/components/migration/tests/unit/test_Chrome_passwords.js
@@ -153,59 +153,59 @@ add_task(async function setup() {
 add_task(async function test_importIntoEmptyDB() {
   for (let login of TEST_LOGINS) {
     await promiseSetPassword(login);
   }
 
   let migrator = await MigrationUtils.getMigrator("chrome");
   Assert.ok(await migrator.isSourceAvailable(), "Sanity check the source exists");
 
-  let logins = Services.logins.getAllLogins({});
+  let logins = Services.logins.getAllLogins();
   Assert.equal(logins.length, 0, "There are no logins initially");
 
   // Migrate the logins.
   await promiseMigration(migrator, MigrationUtils.resourceTypes.PASSWORDS, PROFILE);
 
-  logins = Services.logins.getAllLogins({});
+  logins = Services.logins.getAllLogins();
   Assert.equal(logins.length, TEST_LOGINS.length, "Check login count after importing the data");
   Assert.equal(logins.length, MigrationUtils._importQuantities.logins,
                "Check telemetry matches the actual import.");
 
   for (let i = 0; i < TEST_LOGINS.length; i++) {
     checkLoginsAreEqual(logins[i], TEST_LOGINS[i], i + 1);
   }
 });
 
 // Test that existing logins for the same primary key don't get overwritten
 add_task(async function test_importExistingLogins() {
   let migrator = await MigrationUtils.getMigrator("chrome");
   Assert.ok(await migrator.isSourceAvailable(), "Sanity check the source exists");
 
   Services.logins.removeAllLogins();
-  let logins = Services.logins.getAllLogins({});
+  let logins = Services.logins.getAllLogins();
   Assert.equal(logins.length, 0, "There are no logins after removing all of them");
 
   let newLogins = [];
 
   // Create 3 new logins that are different but where the key properties are still the same.
   for (let i = 0; i < 3; i++) {
     newLogins.push(generateDifferentLogin(TEST_LOGINS[i]));
     Services.logins.addLogin(newLogins[i]);
   }
 
-  logins = Services.logins.getAllLogins({});
+  logins = Services.logins.getAllLogins();
   Assert.equal(logins.length, newLogins.length, "Check login count after the insertion");
 
   for (let i = 0; i < newLogins.length; i++) {
     checkLoginsAreEqual(logins[i], newLogins[i], i + 1);
   }
   // Migrate the logins.
   await promiseMigration(migrator, MigrationUtils.resourceTypes.PASSWORDS, PROFILE);
 
-  logins = Services.logins.getAllLogins({});
+  logins = Services.logins.getAllLogins();
   Assert.equal(logins.length, TEST_LOGINS.length,
                "Check there are still the same number of logins after re-importing the data");
   Assert.equal(logins.length, MigrationUtils._importQuantities.logins,
                "Check telemetry matches the actual import.");
 
   for (let i = 0; i < newLogins.length; i++) {
     checkLoginsAreEqual(logins[i], newLogins[i], i + 1);
   }
--- a/browser/components/migration/tests/unit/test_IE7_passwords.js
+++ b/browser/components/migration/tests/unit/test_IE7_passwords.js
@@ -316,59 +316,59 @@ add_task(async function setup() {
 
 add_task(async function test_passwordsNotAvailable() {
   if (AppConstants.isPlatformAndVersionAtLeast("win", "6.2")) {
     return;
   }
 
   let migrator = getFirstResourceOfType(MigrationUtils.resourceTypes.PASSWORDS);
   Assert.ok(migrator.exists, "The migrator has to exist");
-  let logins = Services.logins.getAllLogins({});
+  let logins = Services.logins.getAllLogins();
   Assert.equal(logins.length, 0, "There are no logins at the beginning of the test");
 
   let uris = []; // the uris of the migrated logins
   for (let url of TESTED_URLS) {
     uris.push(makeURI(url));
      // in this test, there is no IE login data in the registry, so after the migration, the number
      // of logins in the store should be 0
     await migrator._migrateURIs(uris);
-    logins = Services.logins.getAllLogins({});
+    logins = Services.logins.getAllLogins();
     Assert.equal(logins.length, 0,
                  "There are no logins after doing the migration without adding values to the registry");
   }
 });
 
 add_task(async function test_passwordsAvailable() {
   if (AppConstants.isPlatformAndVersionAtLeast("win", "6.2")) {
     return;
   }
 
   let crypto = new OSCrypto();
   let hashes = []; // the hashes of all migrator websites, this is going to be used for the clean up
 
   registerCleanupFunction(() => {
     Services.logins.removeAllLogins();
-    logins = Services.logins.getAllLogins({});
+    logins = Services.logins.getAllLogins();
     Assert.equal(logins.length, 0, "There are no logins after the cleanup");
     // remove all the values created in this test from the registry
     removeAllValues(Storage2Key, hashes);
     // restore all backed up values
     restore(Storage2Key);
 
     // clean the dummy value
     if (Storage2Key.hasValue("dummy")) {
       Storage2Key.removeValue("dummy");
     }
     Storage2Key.close();
     crypto.finalize();
   });
 
   let migrator = getFirstResourceOfType(MigrationUtils.resourceTypes.PASSWORDS);
   Assert.ok(migrator.exists, "The migrator has to exist");
-  let logins = Services.logins.getAllLogins({});
+  let logins = Services.logins.getAllLogins();
   Assert.equal(logins.length, 0, "There are no logins at the beginning of the test");
 
   let uris = []; // the uris of the migrated logins
 
   let loginCount = 0;
   for (let current in TESTED_WEBSITES) {
     let website = TESTED_WEBSITES[current];
     // backup the current the registry value if it exists and replace the existing value/create a
@@ -376,17 +376,17 @@ add_task(async function test_passwordsAv
     backupAndStore(Storage2Key, website.hash,
                    crypto.encryptData(crypto.arrayToString(website.data),
                                       website.uri.spec));
     Assert.ok(migrator.exists, "The migrator has to exist");
     uris.push(website.uri);
     hashes.push(website.hash);
 
     await migrator._migrateURIs(uris);
-    logins = Services.logins.getAllLogins({});
+    logins = Services.logins.getAllLogins();
     // check that the number of logins in the password manager has increased as expected which means
     // that all the values for the current website were imported
     loginCount += website.logins.length;
     Assert.equal(logins.length, loginCount,
                  "The number of logins has increased after the migration");
     // NB: because telemetry records any login data passed to the login manager, it
     // also gets told about logins that are duplicates or invalid (for one reason
     // or another) and so its counts might exceed those of the login manager itself.
--- a/browser/components/newtab/common/Actions.jsm
+++ b/browser/components/newtab/common/Actions.jsm
@@ -25,16 +25,17 @@ this.globalImportContext = globalImportC
 // }
 const actionTypes = {};
 for (const type of [
   "ADDONS_INFO_REQUEST",
   "ADDONS_INFO_RESPONSE",
   "ARCHIVE_FROM_POCKET",
   "AS_ROUTER_INITIALIZED",
   "AS_ROUTER_PREF_CHANGED",
+  "AS_ROUTER_TARGETING_UPDATE",
   "AS_ROUTER_TELEMETRY_USER_EVENT",
   "BLOCK_URL",
   "BOOKMARK_URL",
   "COPY_DOWNLOAD_LINK",
   "DELETE_BOOKMARK_BY_ID",
   "DELETE_FROM_POCKET",
   "DELETE_HISTORY_URL",
   "DIALOG_CANCEL",
@@ -44,17 +45,16 @@ for (const type of [
   "DISCOVERY_STREAM_CONFIG_SET_VALUE",
   "DISCOVERY_STREAM_FEEDS_UPDATE",
   "DISCOVERY_STREAM_FEED_UPDATE",
   "DISCOVERY_STREAM_IMPRESSION_STATS",
   "DISCOVERY_STREAM_LAYOUT_RESET",
   "DISCOVERY_STREAM_LAYOUT_UPDATE",
   "DISCOVERY_STREAM_LINK_BLOCKED",
   "DISCOVERY_STREAM_LOADED_CONTENT",
-  "DISCOVERY_STREAM_OPT_OUT",
   "DISCOVERY_STREAM_SPOCS_CAPS",
   "DISCOVERY_STREAM_SPOCS_ENDPOINT",
   "DISCOVERY_STREAM_SPOCS_FILL",
   "DISCOVERY_STREAM_SPOCS_UPDATE",
   "DISCOVERY_STREAM_SPOC_IMPRESSION",
   "DOWNLOAD_CHANGED",
   "FAKE_FOCUS_SEARCH",
   "FILL_SEARCH_TERM",
--- a/browser/components/newtab/content-src/asrouter/asrouter-content.jsx
+++ b/browser/components/newtab/content-src/asrouter/asrouter-content.jsx
@@ -103,16 +103,17 @@ export class ASRouterUISurface extends R
     }
   }
 
   sendUserActionTelemetry(extraProps = {}) {
     const {message, bundle} = this.state;
     if (!message && !extraProps.message_id) {
       throw new Error(`You must provide a message_id for bundled messages`);
     }
+    // snippets_user_event, onboarding_user_event
     const eventType = `${message.provider || bundle.provider}_user_event`;
     ASRouterUtils.sendTelemetry({
       message_id: message.id || extraProps.message_id,
       source: extraProps.id,
       action: eventType,
       ...extraProps,
     });
   }
@@ -155,50 +156,67 @@ export class ASRouterUISurface extends R
     return options => ASRouterUtils.blockById(id, options);
   }
 
   onDismissById(id) {
     return () => ASRouterUtils.dismissById(id);
   }
 
   dismissBundle(bundle) {
-    return () => ASRouterUtils.dismissBundle(bundle);
+    return () => {
+      ASRouterUtils.dismissBundle(bundle);
+      this.sendUserActionTelemetry({
+        event: "DISMISS",
+        id: "onboarding-cards",
+        message_id: bundle.map(m => m.id).join(","),
+        // Passing the action because some bundles (Trailhead) don't have a provider set
+        action: "onboarding_user_event",
+      });
+    };
   }
 
   triggerOnboarding() {
     ASRouterUtils.sendMessage({type: "TRIGGER", data: {trigger: {id: "showOnboarding"}}});
   }
 
+  clearMessage(id) {
+    if (id === this.state.message.id) {
+      this.setState({message: {}});
+      // Remove any styles related to the RTAMO message
+      document.body.classList.remove("welcome", "hide-main", "amo");
+    }
+  }
+
   onMessageFromParent({data: action}) {
     switch (action.type) {
       case "SET_MESSAGE":
         this.setState({message: action.data});
         break;
       case "SET_BUNDLED_MESSAGES":
         this.setState({bundle: action.data});
         break;
       case "CLEAR_MESSAGE":
-        if (action.data.id === this.state.message.id) {
-          this.setState({message: {}});
-          // Remove any styles related to the RTAMO message
-          document.body.classList.remove("welcome", "hide-main", "amo");
-        }
+        this.clearMessage(action.data.id);
         break;
       case "CLEAR_PROVIDER":
         if (action.data.id === this.state.message.provider) {
           this.setState({message: {}});
         }
         break;
       case "CLEAR_BUNDLE":
         if (this.state.bundle.bundle) {
           this.setState({bundle: {}});
         }
         break;
       case "CLEAR_ALL":
         this.setState({message: {}, bundle: {}});
+        break;
+      case "AS_ROUTER_TARGETING_UPDATE":
+        action.data.forEach(id => this.clearMessage(id));
+        break;
     }
   }
 
   componentWillMount() {
     if (global.document) {
       // Add locale data for StartupOverlay because it uses react-intl
       addLocaleData(global.document.documentElement.lang);
     }
@@ -254,17 +272,17 @@ export class ASRouterUISurface extends R
 
   renderOnboarding() {
     if (this.state.bundle.template === "onboarding") {
       return (
         <OnboardingMessage
           {...this.state.bundle}
           UISurface="NEWTAB_OVERLAY"
           onAction={ASRouterUtils.executeAction}
-          onDoneButton={this.dismissBundle(this.state.bundle.bundle)}
+          onDismissBundle={this.dismissBundle(this.state.bundle.bundle)}
           sendUserActionTelemetry={this.sendUserActionTelemetry} />);
     }
     return null;
   }
 
   renderFirstRunOverlay() {
     const {message} = this.state;
     if (message.template === "fxa_overlay") {
@@ -278,33 +296,35 @@ export class ASRouterUISurface extends R
         </IntlProvider>
       );
     } else if (message.template === "return_to_amo_overlay") {
       global.document.body.classList.add("amo");
       return (
         <LocalizationProvider messages={generateMessages({"amo_html": message.content.text})}>
           <ReturnToAMO
             {...message}
+            UISurface="NEWTAB_OVERLAY"
             onReady={this.triggerOnboarding}
             onBlock={this.onDismissById(message.id)}
-            onAction={ASRouterUtils.executeAction} />
+            onAction={ASRouterUtils.executeAction}
+            sendUserActionTelemetry={this.sendUserActionTelemetry} />
         </LocalizationProvider>
       );
     }
     return null;
   }
 
   renderTrailhead() {
     const {message} = this.state;
     if (message.template === "trailhead") {
       return (<Trailhead
         document={this.props.document}
         message={message}
         onAction={ASRouterUtils.executeAction}
-        onDoneButton={this.dismissBundle(this.state.bundle.bundle)}
+        onDismissBundle={this.dismissBundle(this.state.message.bundle)}
         sendUserActionTelemetry={this.sendUserActionTelemetry}
         dispatch={this.props.dispatch}
         fxaEndpoint={this.props.fxaEndpoint} />);
     }
     return null;
   }
 
   renderPreviewBanner() {
--- a/browser/components/newtab/content-src/asrouter/components/ModalOverlay/ModalOverlay.jsx
+++ b/browser/components/newtab/content-src/asrouter/components/ModalOverlay/ModalOverlay.jsx
@@ -37,18 +37,18 @@ export class ModalOverlayWrapper extends
 }
 
 ModalOverlayWrapper.defaultProps = {document: global.document};
 
 export class ModalOverlay extends React.PureComponent {
   render() {
     const {title, button_label} = this.props;
     return (
-      <ModalOverlayWrapper onClose={this.props.onDoneButton}>
+      <ModalOverlayWrapper onClose={this.props.onDismissBundle}>
         <h2> {title} </h2>
         {this.props.children}
         <div className="footer">
           <button className="button primary modalButton"
-            onClick={this.props.onDoneButton}> {button_label} </button>
+            onClick={this.props.onDismissBundle}> {button_label} </button>
         </div>
       </ModalOverlayWrapper>);
   }
 }
--- a/browser/components/newtab/content-src/asrouter/templates/ReturnToAMO/ReturnToAMO.jsx
+++ b/browser/components/newtab/content-src/asrouter/templates/ReturnToAMO/ReturnToAMO.jsx
@@ -5,25 +5,37 @@ export class ReturnToAMO extends React.P
   constructor(props) {
     super(props);
     this.onClickAddExtension = this.onClickAddExtension.bind(this);
     this.onBlockButton = this.onBlockButton.bind(this);
   }
 
   componentDidMount() {
     this.props.onReady();
+    this.props.sendUserActionTelemetry({
+      event: "IMPRESSION",
+      id: this.props.UISurface,
+    });
   }
 
   onClickAddExtension() {
     this.props.onAction(this.props.content.primary_button.action);
+    this.props.sendUserActionTelemetry({
+      event: "INSTALL",
+      id: this.props.UISurface,
+    });
   }
 
   onBlockButton() {
     this.props.onBlock();
     document.body.classList.remove("welcome", "hide-main", "amo");
+    this.props.sendUserActionTelemetry({
+      event: "BLOCK",
+      id: this.props.UISurface,
+    });
   }
 
   renderText() {
     const customElement = <img src={this.props.content.addon_icon} width="20px" height="20px" />;
     return (<RichText
       customElements={{icon: customElement}}
       amo_html={this.props.content.text}
       localization_id="amo_html" />);
--- a/browser/components/newtab/content-src/asrouter/templates/Trailhead/Trailhead.jsx
+++ b/browser/components/newtab/content-src/asrouter/templates/Trailhead/Trailhead.jsx
@@ -114,17 +114,27 @@ export class _Trailhead extends React.Pu
     const {dialog} = this;
     if (event.relatedTarget &&
         !(dialog.compareDocumentPosition(event.relatedTarget) &
           dialog.DOCUMENT_POSITION_CONTAINED_BY)) {
       dialog.querySelector(FOCUSABLE_SELECTOR).focus();
     }
   }
 
-  onSubmit() {
+  onSubmit(event) {
+    // Dynamically require the email on submission so screen readers don't read
+    // out it's always required because there's also ways to skip the modal
+    const {email} = event.target.elements;
+    if (!email.value.length) {
+      email.required = true;
+      email.checkValidity();
+      event.preventDefault();
+      return;
+    }
+
     this.props.dispatch(ac.UserEvent({event: "SUBMIT_EMAIL", ...this._getFormInfo()}));
 
     global.addEventListener("visibilitychange", this.closeModal);
   }
 
   closeModal(ev) {
     global.removeEventListener("visibilitychange", this.closeModal);
     this.props.document.body.classList.remove("welcome");
@@ -132,16 +142,19 @@ export class _Trailhead extends React.Pu
     this.setState({isModalOpen: false});
     this.revealCards();
 
     // If closeModal() was triggered by a visibilitychange event, the user actually
     // submitted the email form so we don't send a SKIPPED_SIGNIN ping.
     if (!ev || ev.type !== "visibilitychange") {
       this.props.dispatch(ac.UserEvent({event: "SKIPPED_SIGNIN", ...this._getFormInfo()}));
     }
+
+    // Bug 1190882 - Focus in a disappearing dialog confuses screen readers
+    this.props.document.activeElement.blur();
   }
 
   /**
    * Report to telemetry additional information about the form submission.
    */
   _getFormInfo() {
     const value = {has_flow_params: this.state.flowId.length > 0};
     return {value};
@@ -152,16 +165,17 @@ export class _Trailhead extends React.Pu
     error.classList.add("active");
     e.target.classList.add("invalid");
     e.preventDefault(); // Override built-in form validation popup
     e.target.focus();
   }
 
   hideCardPanel() {
     this.setState({showCardPanel: false});
+    this.props.onDismissBundle();
   }
 
   revealCards() {
     this.setState({showCards: true});
   }
 
   getStringValue(str) {
     if (str.property_id) {
@@ -251,17 +265,16 @@ export class _Trailhead extends React.Pu
             <input name="flow_begin_time" type="hidden" value={this.state.flowBeginTime} />
             <input name="style" type="hidden" value="trailhead" />
             <p data-l10n-id="onboarding-join-form-email-error" className="error" />
             <input
               data-l10n-id={content.form.email.string_id}
               placeholder={this.getStringValue(content.form.email)}
               name="email"
               type="email"
-              required="required"
               onInvalid={this.onInputInvalid}
               onChange={this.onInputChange} />
             <p className="trailheadTerms" data-l10n-id="onboarding-join-form-legal">
               <a data-l10n-name="terms" target="_blank" rel="noopener noreferrer"
                 href={this.addUtmParams("https://accounts.firefox.com/legal/terms")} />
               <a data-l10n-name="privacy" target="_blank" rel="noopener noreferrer"
                 href={this.addUtmParams("https://accounts.firefox.com/legal/privacy")} />
             </p>
--- a/browser/components/newtab/content-src/components/ASRouterAdmin/ASRouterAdmin.jsx
+++ b/browser/components/newtab/content-src/components/ASRouterAdmin/ASRouterAdmin.jsx
@@ -20,17 +20,16 @@ function relativeTime(timestamp) {
   } else if (minutes === 1) {
     return "1 minute ago";
   } else if (minutes < 600) {
     return `${minutes} minutes ago`;
   }
   return new Date(timestamp).toLocaleString();
 }
 
-const OPT_OUT_PREF = "discoverystream.optOut.0";
 const LAYOUT_VARIANTS = {
   "basic": "Basic default layout (on by default in nightly)",
   "dev-test-all": "A little bit of everything. Good layout for testing all components",
   "dev-test-feeds": "Stress testing for slow feeds",
 };
 
 export class ToggleStoryButton extends React.PureComponent {
   constructor(props) {
@@ -55,20 +54,16 @@ export class DiscoveryStreamAdmin extend
     this.onEnableToggle = this.onEnableToggle.bind(this);
     this.changeEndpointVariant = this.changeEndpointVariant.bind(this);
     this.onStoryToggle = this.onStoryToggle.bind(this);
     this.state = {
       toggledStories: {},
     };
   }
 
-  get isOptedOut() {
-    return this.props.otherPrefs[OPT_OUT_PREF];
-  }
-
   setConfigValue(name, value) {
     this.props.dispatch(ac.OnlyToMain({type: at.DISCOVERY_STREAM_CONFIG_SET_VALUE, data: {name, value}}));
   }
 
   onEnableToggle(event) {
     this.setConfigValue("enabled", event.target.checked);
   }
 
@@ -195,23 +190,20 @@ export class DiscoveryStreamAdmin extend
           <td className="min">Data last fetched</td>
           <td>{relativeTime(feeds.data[feed.url] ? feeds.data[feed.url].lastUpdated : null) || "(no data)"}</td>
         </Row>
       </React.Fragment>
     );
   }
 
   render() {
-    const {isOptedOut} = this;
     const {config, lastUpdated, layout} = this.props.state;
     return (<div>
 
-      <div className="dsEnabled"><input type="checkbox" checked={config.enabled} onChange={this.onEnableToggle} /> enabled
-        {isOptedOut ? (<span className="optOutNote">(Note: User has opted-out. Check this box to reset)</span>) : ""}</div>
-
+      <div className="dsEnabled"><input type="checkbox" checked={config.enabled} onChange={this.onEnableToggle} /> enabled </div>
       <h3>Endpoint variant</h3>
       <p>You can also change this manually by changing this pref: <code>browser.newtabpage.activity-stream.discoverystream.config</code></p>
       <table style={config.enabled ? null : {opacity: 0.5}}><tbody>
         {Object.keys(LAYOUT_VARIANTS).map(id => (<Row key={id}>
           <td className="min"><input type="radio" value={id} checked={this.isCurrentVariant(id)} onChange={this.changeEndpointVariant} /></td>
           <td className="min">{id}</td>
           <td>{LAYOUT_VARIANTS[id]}</td>
         </Row>))}
--- a/browser/components/newtab/content-src/components/ASRouterAdmin/ASRouterAdmin.scss
+++ b/browser/components/newtab/content-src/components/ASRouterAdmin/ASRouterAdmin.scss
@@ -214,14 +214,9 @@
     font-size: 16px;
     margin-bottom: 20px;
     border: 1px solid $border-color;
   }
 
   .ds-component {
     margin-bottom: 20px;
   }
-
-  .optOutNote {
-    font-size: 12px;
-    margin-inline-start: 4px;
-  }
 }
--- a/browser/components/newtab/css/activity-stream-linux.css
+++ b/browser/components/newtab/css/activity-stream-linux.css
@@ -1758,19 +1758,16 @@ main {
       text-decoration: underline; }
   .asrouter-admin .dsEnabled {
     padding: 10px;
     font-size: 16px;
     margin-bottom: 20px;
     border: 1px solid var(--newtab-border-secondary-color); }
   .asrouter-admin .ds-component {
     margin-bottom: 20px; }
-  .asrouter-admin .optOutNote {
-    font-size: 12px;
-    margin-inline-start: 4px; }
 
 .pocket-logged-in-cta {
   font-size: 13px;
   margin-inline-end: 20px;
   display: flex;
   align-items: flex-start; }
   .pocket-logged-in-cta .pocket-cta-button {
     white-space: nowrap;
--- a/browser/components/newtab/css/activity-stream-mac.css
+++ b/browser/components/newtab/css/activity-stream-mac.css
@@ -1761,19 +1761,16 @@ main {
       text-decoration: underline; }
   .asrouter-admin .dsEnabled {
     padding: 10px;
     font-size: 16px;
     margin-bottom: 20px;
     border: 1px solid var(--newtab-border-secondary-color); }
   .asrouter-admin .ds-component {
     margin-bottom: 20px; }
-  .asrouter-admin .optOutNote {
-    font-size: 12px;
-    margin-inline-start: 4px; }
 
 .pocket-logged-in-cta {
   font-size: 13px;
   margin-inline-end: 20px;
   display: flex;
   align-items: flex-start; }
   .pocket-logged-in-cta .pocket-cta-button {
     white-space: nowrap;
--- a/browser/components/newtab/css/activity-stream-windows.css
+++ b/browser/components/newtab/css/activity-stream-windows.css
@@ -1758,19 +1758,16 @@ main {
       text-decoration: underline; }
   .asrouter-admin .dsEnabled {
     padding: 10px;
     font-size: 16px;
     margin-bottom: 20px;
     border: 1px solid var(--newtab-border-secondary-color); }
   .asrouter-admin .ds-component {
     margin-bottom: 20px; }
-  .asrouter-admin .optOutNote {
-    font-size: 12px;
-    margin-inline-start: 4px; }
 
 .pocket-logged-in-cta {
   font-size: 13px;
   margin-inline-end: 20px;
   display: flex;
   align-items: flex-start; }
   .pocket-logged-in-cta .pocket-cta-button {
     white-space: nowrap;
--- a/browser/components/newtab/data/content/activity-stream.bundle.js
+++ b/browser/components/newtab/data/content/activity-stream.bundle.js
@@ -192,17 +192,17 @@ const globalImportContext = typeof Windo
 
 // Create an object that avoids accidental differing key/value pairs:
 // {
 //   INIT: "INIT",
 //   UNINIT: "UNINIT"
 // }
 const actionTypes = {};
 
-for (const type of ["ADDONS_INFO_REQUEST", "ADDONS_INFO_RESPONSE", "ARCHIVE_FROM_POCKET", "AS_ROUTER_INITIALIZED", "AS_ROUTER_PREF_CHANGED", "AS_ROUTER_TELEMETRY_USER_EVENT", "BLOCK_URL", "BOOKMARK_URL", "COPY_DOWNLOAD_LINK", "DELETE_BOOKMARK_BY_ID", "DELETE_FROM_POCKET", "DELETE_HISTORY_URL", "DIALOG_CANCEL", "DIALOG_OPEN", "DISCOVERY_STREAM_CONFIG_CHANGE", "DISCOVERY_STREAM_CONFIG_SETUP", "DISCOVERY_STREAM_CONFIG_SET_VALUE", "DISCOVERY_STREAM_FEEDS_UPDATE", "DISCOVERY_STREAM_FEED_UPDATE", "DISCOVERY_STREAM_IMPRESSION_STATS", "DISCOVERY_STREAM_LAYOUT_RESET", "DISCOVERY_STREAM_LAYOUT_UPDATE", "DISCOVERY_STREAM_LINK_BLOCKED", "DISCOVERY_STREAM_LOADED_CONTENT", "DISCOVERY_STREAM_OPT_OUT", "DISCOVERY_STREAM_SPOCS_CAPS", "DISCOVERY_STREAM_SPOCS_ENDPOINT", "DISCOVERY_STREAM_SPOCS_FILL", "DISCOVERY_STREAM_SPOCS_UPDATE", "DISCOVERY_STREAM_SPOC_IMPRESSION", "DOWNLOAD_CHANGED", "FAKE_FOCUS_SEARCH", "FILL_SEARCH_TERM", "HANDOFF_SEARCH_TO_AWESOMEBAR", "HIDE_SEARCH", "INIT", "NEW_TAB_INIT", "NEW_TAB_INITIAL_STATE", "NEW_TAB_LOAD", "NEW_TAB_REHYDRATED", "NEW_TAB_STATE_REQUEST", "NEW_TAB_UNLOAD", "OPEN_DOWNLOAD_FILE", "OPEN_LINK", "OPEN_NEW_WINDOW", "OPEN_PRIVATE_WINDOW", "OPEN_WEBEXT_SETTINGS", "PAGE_PRERENDERED", "PLACES_BOOKMARK_ADDED", "PLACES_BOOKMARK_REMOVED", "PLACES_HISTORY_CLEARED", "PLACES_LINKS_CHANGED", "PLACES_LINK_BLOCKED", "PLACES_LINK_DELETED", "PLACES_SAVED_TO_POCKET", "POCKET_CTA", "POCKET_LINK_DELETED_OR_ARCHIVED", "POCKET_LOGGED_IN", "POCKET_WAITING_FOR_SPOC", "PREFS_INITIAL_VALUES", "PREF_CHANGED", "PREVIEW_REQUEST", "PREVIEW_REQUEST_CANCEL", "PREVIEW_RESPONSE", "REMOVE_DOWNLOAD_FILE", "RICH_ICON_MISSING", "SAVE_SESSION_PERF_DATA", "SAVE_TO_POCKET", "SCREENSHOT_UPDATED", "SECTION_DEREGISTER", "SECTION_DISABLE", "SECTION_ENABLE", "SECTION_MOVE", "SECTION_OPTIONS_CHANGED", "SECTION_REGISTER", "SECTION_UPDATE", "SECTION_UPDATE_CARD", "SETTINGS_CLOSE", "SETTINGS_OPEN", "SET_PREF", "SHOW_DOWNLOAD_FILE", "SHOW_FIREFOX_ACCOUNTS", "SHOW_SEARCH", "SKIPPED_SIGNIN", "SNIPPETS_BLOCKLIST_CLEARED", "SNIPPETS_BLOCKLIST_UPDATED", "SNIPPETS_DATA", "SNIPPETS_PREVIEW_MODE", "SNIPPETS_RESET", "SNIPPET_BLOCKED", "SUBMIT_EMAIL", "SYSTEM_TICK", "TELEMETRY_IMPRESSION_STATS", "TELEMETRY_PERFORMANCE_EVENT", "TELEMETRY_UNDESIRED_EVENT", "TELEMETRY_USER_EVENT", "TOP_SITES_CANCEL_EDIT", "TOP_SITES_CLOSE_SEARCH_SHORTCUTS_MODAL", "TOP_SITES_EDIT", "TOP_SITES_INSERT", "TOP_SITES_OPEN_SEARCH_SHORTCUTS_MODAL", "TOP_SITES_PIN", "TOP_SITES_PREFS_UPDATED", "TOP_SITES_UNPIN", "TOP_SITES_UPDATED", "TOTAL_BOOKMARKS_REQUEST", "TOTAL_BOOKMARKS_RESPONSE", "TRAILHEAD_ENROLL_EVENT", "UNINIT", "UPDATE_PINNED_SEARCH_SHORTCUTS", "UPDATE_SEARCH_SHORTCUTS", "UPDATE_SECTION_PREFS", "WEBEXT_CLICK", "WEBEXT_DISMISS"]) {
+for (const type of ["ADDONS_INFO_REQUEST", "ADDONS_INFO_RESPONSE", "ARCHIVE_FROM_POCKET", "AS_ROUTER_INITIALIZED", "AS_ROUTER_PREF_CHANGED", "AS_ROUTER_TARGETING_UPDATE", "AS_ROUTER_TELEMETRY_USER_EVENT", "BLOCK_URL", "BOOKMARK_URL", "COPY_DOWNLOAD_LINK", "DELETE_BOOKMARK_BY_ID", "DELETE_FROM_POCKET", "DELETE_HISTORY_URL", "DIALOG_CANCEL", "DIALOG_OPEN", "DISCOVERY_STREAM_CONFIG_CHANGE", "DISCOVERY_STREAM_CONFIG_SETUP", "DISCOVERY_STREAM_CONFIG_SET_VALUE", "DISCOVERY_STREAM_FEEDS_UPDATE", "DISCOVERY_STREAM_FEED_UPDATE", "DISCOVERY_STREAM_IMPRESSION_STATS", "DISCOVERY_STREAM_LAYOUT_RESET", "DISCOVERY_STREAM_LAYOUT_UPDATE", "DISCOVERY_STREAM_LINK_BLOCKED", "DISCOVERY_STREAM_LOADED_CONTENT", "DISCOVERY_STREAM_SPOCS_CAPS", "DISCOVERY_STREAM_SPOCS_ENDPOINT", "DISCOVERY_STREAM_SPOCS_FILL", "DISCOVERY_STREAM_SPOCS_UPDATE", "DISCOVERY_STREAM_SPOC_IMPRESSION", "DOWNLOAD_CHANGED", "FAKE_FOCUS_SEARCH", "FILL_SEARCH_TERM", "HANDOFF_SEARCH_TO_AWESOMEBAR", "HIDE_SEARCH", "INIT", "NEW_TAB_INIT", "NEW_TAB_INITIAL_STATE", "NEW_TAB_LOAD", "NEW_TAB_REHYDRATED", "NEW_TAB_STATE_REQUEST", "NEW_TAB_UNLOAD", "OPEN_DOWNLOAD_FILE", "OPEN_LINK", "OPEN_NEW_WINDOW", "OPEN_PRIVATE_WINDOW", "OPEN_WEBEXT_SETTINGS", "PAGE_PRERENDERED", "PLACES_BOOKMARK_ADDED", "PLACES_BOOKMARK_REMOVED", "PLACES_HISTORY_CLEARED", "PLACES_LINKS_CHANGED", "PLACES_LINK_BLOCKED", "PLACES_LINK_DELETED", "PLACES_SAVED_TO_POCKET", "POCKET_CTA", "POCKET_LINK_DELETED_OR_ARCHIVED", "POCKET_LOGGED_IN", "POCKET_WAITING_FOR_SPOC", "PREFS_INITIAL_VALUES", "PREF_CHANGED", "PREVIEW_REQUEST", "PREVIEW_REQUEST_CANCEL", "PREVIEW_RESPONSE", "REMOVE_DOWNLOAD_FILE", "RICH_ICON_MISSING", "SAVE_SESSION_PERF_DATA", "SAVE_TO_POCKET", "SCREENSHOT_UPDATED", "SECTION_DEREGISTER", "SECTION_DISABLE", "SECTION_ENABLE", "SECTION_MOVE", "SECTION_OPTIONS_CHANGED", "SECTION_REGISTER", "SECTION_UPDATE", "SECTION_UPDATE_CARD", "SETTINGS_CLOSE", "SETTINGS_OPEN", "SET_PREF", "SHOW_DOWNLOAD_FILE", "SHOW_FIREFOX_ACCOUNTS", "SHOW_SEARCH", "SKIPPED_SIGNIN", "SNIPPETS_BLOCKLIST_CLEARED", "SNIPPETS_BLOCKLIST_UPDATED", "SNIPPETS_DATA", "SNIPPETS_PREVIEW_MODE", "SNIPPETS_RESET", "SNIPPET_BLOCKED", "SUBMIT_EMAIL", "SYSTEM_TICK", "TELEMETRY_IMPRESSION_STATS", "TELEMETRY_PERFORMANCE_EVENT", "TELEMETRY_UNDESIRED_EVENT", "TELEMETRY_USER_EVENT", "TOP_SITES_CANCEL_EDIT", "TOP_SITES_CLOSE_SEARCH_SHORTCUTS_MODAL", "TOP_SITES_EDIT", "TOP_SITES_INSERT", "TOP_SITES_OPEN_SEARCH_SHORTCUTS_MODAL", "TOP_SITES_PIN", "TOP_SITES_PREFS_UPDATED", "TOP_SITES_UNPIN", "TOP_SITES_UPDATED", "TOTAL_BOOKMARKS_REQUEST", "TOTAL_BOOKMARKS_RESPONSE", "TRAILHEAD_ENROLL_EVENT", "UNINIT", "UPDATE_PINNED_SEARCH_SHORTCUTS", "UPDATE_SEARCH_SHORTCUTS", "UPDATE_SECTION_PREFS", "WEBEXT_CLICK", "WEBEXT_DISMISS"]) {
   actionTypes[type] = type;
 } // These are acceptable actions for AS Router messages to have. They can show up
 // as call-to-action buttons in snippets, onboarding tour, etc.
 
 
 const ASRouterActions = {};
 
 for (const type of ["INSTALL_ADDON_FROM_URL", "OPEN_APPLICATIONS_MENU", "OPEN_PRIVATE_BROWSER_WINDOW", "OPEN_URL", "OPEN_ABOUT_PAGE", "OPEN_PREFERENCES_PAGE", "SHOW_FIREFOX_ACCOUNTS", "PIN_CURRENT_TAB"]) {
@@ -836,17 +836,16 @@ function relativeTime(timestamp) {
     return "1 minute ago";
   } else if (minutes < 600) {
     return `${minutes} minutes ago`;
   }
 
   return new Date(timestamp).toLocaleString();
 }
 
-const OPT_OUT_PREF = "discoverystream.optOut.0";
 const LAYOUT_VARIANTS = {
   "basic": "Basic default layout (on by default in nightly)",
   "dev-test-all": "A little bit of everything. Good layout for testing all components",
   "dev-test-feeds": "Stress testing for slow feeds"
 };
 class ToggleStoryButton extends react__WEBPACK_IMPORTED_MODULE_4___default.a.PureComponent {
   constructor(props) {
     super(props);
@@ -870,20 +869,16 @@ class DiscoveryStreamAdmin extends react
     this.onEnableToggle = this.onEnableToggle.bind(this);
     this.changeEndpointVariant = this.changeEndpointVariant.bind(this);
     this.onStoryToggle = this.onStoryToggle.bind(this);
     this.state = {
       toggledStories: {}
     };
   }
 
-  get isOptedOut() {
-    return this.props.otherPrefs[OPT_OUT_PREF];
-  }
-
   setConfigValue(name, value) {
     this.props.dispatch(common_Actions_jsm__WEBPACK_IMPORTED_MODULE_0__["actionCreators"].OnlyToMain({
       type: common_Actions_jsm__WEBPACK_IMPORTED_MODULE_0__["actionTypes"].DISCOVERY_STREAM_CONFIG_SET_VALUE,
       data: {
         name,
         value
       }
     }));
@@ -991,32 +986,27 @@ class DiscoveryStreamAdmin extends react
       className: "min"
     }, "Feed url"), react__WEBPACK_IMPORTED_MODULE_4___default.a.createElement("td", null, feed.url)), react__WEBPACK_IMPORTED_MODULE_4___default.a.createElement(Row, null, react__WEBPACK_IMPORTED_MODULE_4___default.a.createElement("td", {
       className: "min"
     }, "Data last fetched"), react__WEBPACK_IMPORTED_MODULE_4___default.a.createElement("td", null, relativeTime(feeds.data[feed.url] ? feeds.data[feed.url].lastUpdated : null) || "(no data)")));
   }
 
   render() {
     const {
-      isOptedOut
-    } = this;
-    const {
       config,
       lastUpdated,
       layout
     } = this.props.state;
     return react__WEBPACK_IMPORTED_MODULE_4___default.a.createElement("div", null, react__WEBPACK_IMPORTED_MODULE_4___default.a.createElement("div", {
       className: "dsEnabled"
     }, react__WEBPACK_IMPORTED_MODULE_4___default.a.createElement("input", {
       type: "checkbox",
       checked: config.enabled,
       onChange: this.onEnableToggle
-    }), " enabled", isOptedOut ? react__WEBPACK_IMPORTED_MODULE_4___default.a.createElement("span", {
-      className: "optOutNote"
-    }, "(Note: User has opted-out. Check this box to reset)") : ""), react__WEBPACK_IMPORTED_MODULE_4___default.a.createElement("h3", null, "Endpoint variant"), react__WEBPACK_IMPORTED_MODULE_4___default.a.createElement("p", null, "You can also change this manually by changing this pref: ", react__WEBPACK_IMPORTED_MODULE_4___default.a.createElement("code", null, "browser.newtabpage.activity-stream.discoverystream.config")), react__WEBPACK_IMPORTED_MODULE_4___default.a.createElement("table", {
+    }), " enabled "), react__WEBPACK_IMPORTED_MODULE_4___default.a.createElement("h3", null, "Endpoint variant"), react__WEBPACK_IMPORTED_MODULE_4___default.a.createElement("p", null, "You can also change this manually by changing this pref: ", react__WEBPACK_IMPORTED_MODULE_4___default.a.createElement("code", null, "browser.newtabpage.activity-stream.discoverystream.config")), react__WEBPACK_IMPORTED_MODULE_4___default.a.createElement("table", {
       style: config.enabled ? null : {
         opacity: 0.5
       }
     }, react__WEBPACK_IMPORTED_MODULE_4___default.a.createElement("tbody", null, Object.keys(LAYOUT_VARIANTS).map(id => react__WEBPACK_IMPORTED_MODULE_4___default.a.createElement(Row, {
       key: id
     }, react__WEBPACK_IMPORTED_MODULE_4___default.a.createElement("td", {
       className: "min"
     }, react__WEBPACK_IMPORTED_MODULE_4___default.a.createElement("input", {
@@ -1941,17 +1931,18 @@ class ASRouterUISurface extends react__W
   sendUserActionTelemetry(extraProps = {}) {
     const {
       message,
       bundle
     } = this.state;
 
     if (!message && !extraProps.message_id) {
       throw new Error(`You must provide a message_id for bundled messages`);
-    }
+    } // snippets_user_event, onboarding_user_event
+
 
     const eventType = `${message.provider || bundle.provider}_user_event`;
     ASRouterUtils.sendTelemetry({
       message_id: message.id || extraProps.message_id,
       source: extraProps.id,
       action: eventType,
       ...extraProps
     });
@@ -2009,30 +2000,49 @@ class ASRouterUISurface extends react__W
     return options => ASRouterUtils.blockById(id, options);
   }
 
   onDismissById(id) {
     return () => ASRouterUtils.dismissById(id);
   }
 
   dismissBundle(bundle) {
-    return () => ASRouterUtils.dismissBundle(bundle);
+    return () => {
+      ASRouterUtils.dismissBundle(bundle);
+      this.sendUserActionTelemetry({
+        event: "DISMISS",
+        id: "onboarding-cards",
+        message_id: bundle.map(m => m.id).join(","),
+        // Passing the action because some bundles (Trailhead) don't have a provider set
+        action: "onboarding_user_event"
+      });
+    };
   }
 
   triggerOnboarding() {
     ASRouterUtils.sendMessage({
       type: "TRIGGER",
       data: {
         trigger: {
           id: "showOnboarding"
         }
       }
     });
   }
 
+  clearMessage(id) {
+    if (id === this.state.message.id) {
+      this.setState({
+        message: {}
+      }); // Remove any styles related to the RTAMO message
+
+      document.body.classList.remove("welcome", "hide-main", "amo");
+    }
+  }
+
   onMessageFromParent({
     data: action
   }) {
     switch (action.type) {
       case "SET_MESSAGE":
         this.setState({
           message: action.data
         });
@@ -2040,24 +2050,17 @@ class ASRouterUISurface extends react__W
 
       case "SET_BUNDLED_MESSAGES":
         this.setState({
           bundle: action.data
         });
         break;
 
       case "CLEAR_MESSAGE":
-        if (action.data.id === this.state.message.id) {
-          this.setState({
-            message: {}
-          }); // Remove any styles related to the RTAMO message
-
-          document.body.classList.remove("welcome", "hide-main", "amo");
-        }
-
+        this.clearMessage(action.data.id);
         break;
 
       case "CLEAR_PROVIDER":
         if (action.data.id === this.state.message.provider) {
           this.setState({
             message: {}
           });
         }
@@ -2073,16 +2076,21 @@ class ASRouterUISurface extends react__W
 
         break;
 
       case "CLEAR_ALL":
         this.setState({
           message: {},
           bundle: {}
         });
+        break;
+
+      case "AS_ROUTER_TARGETING_UPDATE":
+        action.data.forEach(id => this.clearMessage(id));
+        break;
     }
   }
 
   componentWillMount() {
     if (global.document) {
       // Add locale data for StartupOverlay because it uses react-intl
       Object(react_intl__WEBPACK_IMPORTED_MODULE_0__["addLocaleData"])(global.document.documentElement.lang);
     }
@@ -2150,17 +2158,17 @@ class ASRouterUISurface extends react__W
     }))));
   }
 
   renderOnboarding() {
     if (this.state.bundle.template === "onboarding") {
       return react__WEBPACK_IMPORTED_MODULE_8___default.a.createElement(_templates_OnboardingMessage_OnboardingMessage__WEBPACK_IMPORTED_MODULE_7__["OnboardingMessage"], _extends({}, this.state.bundle, {
         UISurface: "NEWTAB_OVERLAY",
         onAction: ASRouterUtils.executeAction,
-        onDoneButton: this.dismissBundle(this.state.bundle.bundle),
+        onDismissBundle: this.dismissBundle(this.state.bundle.bundle),
         sendUserActionTelemetry: this.sendUserActionTelemetry
       }));
     }
 
     return null;
   }
 
   renderFirstRunOverlay() {
@@ -2180,36 +2188,38 @@ class ASRouterUISurface extends react__W
       }));
     } else if (message.template === "return_to_amo_overlay") {
       global.document.body.classList.add("amo");
       return react__WEBPACK_IMPORTED_MODULE_8___default.a.createElement(fluent_react__WEBPACK_IMPORTED_MODULE_5__["LocalizationProvider"], {
         messages: Object(_rich_text_strings__WEBPACK_IMPORTED_MODULE_3__["generateMessages"])({
           "amo_html": message.content.text
         })
       }, react__WEBPACK_IMPORTED_MODULE_8___default.a.createElement(_templates_ReturnToAMO_ReturnToAMO__WEBPACK_IMPORTED_MODULE_10__["ReturnToAMO"], _extends({}, message, {
+        UISurface: "NEWTAB_OVERLAY",
         onReady: this.triggerOnboarding,
         onBlock: this.onDismissById(message.id),
-        onAction: ASRouterUtils.executeAction
+        onAction: ASRouterUtils.executeAction,
+        sendUserActionTelemetry: this.sendUserActionTelemetry
       })));
     }
 
     return null;
   }
 
   renderTrailhead() {
     const {
       message
     } = this.state;
 
     if (message.template === "trailhead") {
       return react__WEBPACK_IMPORTED_MODULE_8___default.a.createElement(_templates_Trailhead_Trailhead__WEBPACK_IMPORTED_MODULE_13__["Trailhead"], {
         document: this.props.document,
         message: message,
         onAction: ASRouterUtils.executeAction,
-        onDoneButton: this.dismissBundle(this.state.bundle.bundle),
+        onDismissBundle: this.dismissBundle(this.state.message.bundle),
         sendUserActionTelemetry: this.sendUserActionTelemetry,
         dispatch: this.props.dispatch,
         fxaEndpoint: this.props.fxaEndpoint
       });
     }
 
     return null;
   }
@@ -2743,22 +2753,22 @@ ModalOverlayWrapper.defaultProps = {
 };
 class ModalOverlay extends react__WEBPACK_IMPORTED_MODULE_0___default.a.PureComponent {
   render() {
     const {
       title,
       button_label
     } = this.props;
     return react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(ModalOverlayWrapper, {
-      onClose: this.props.onDoneButton
+      onClose: this.props.onDismissBundle
     }, react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement("h2", null, " ", title, " "), this.props.children, react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement("div", {
       className: "footer"
     }, react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement("button", {
       className: "button primary modalButton",
-      onClick: this.props.onDoneButton
+      onClick: this.props.onDismissBundle
     }, " ", button_label, " ")));
   }
 
 }
 /* WEBPACK VAR INJECTION */}.call(this, __webpack_require__(1)))
 
 /***/ }),
 /* 16 */
@@ -2782,25 +2792,37 @@ class ReturnToAMO extends react__WEBPACK
   constructor(props) {
     super(props);
     this.onClickAddExtension = this.onClickAddExtension.bind(this);
     this.onBlockButton = this.onBlockButton.bind(this);
   }
 
   componentDidMount() {
     this.props.onReady();
+    this.props.sendUserActionTelemetry({
+      event: "IMPRESSION",
+      id: this.props.UISurface
+    });
   }
 
   onClickAddExtension() {
     this.props.onAction(this.props.content.primary_button.action);
+    this.props.sendUserActionTelemetry({
+      event: "INSTALL",
+      id: this.props.UISurface
+    });
   }
 
   onBlockButton() {
     this.props.onBlock();
     document.body.classList.remove("welcome", "hide-main", "amo");
+    this.props.sendUserActionTelemetry({
+      event: "BLOCK",
+      id: this.props.UISurface
+    });
   }
 
   renderText() {
     const customElement = react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement("img", {
       src: this.props.content.addon_icon,
       width: "20px",
       height: "20px"
     });
@@ -3415,17 +3437,30 @@ class _Trailhead extends react__WEBPACK_
       dialog
     } = this;
 
     if (event.relatedTarget && !(dialog.compareDocumentPosition(event.relatedTarget) & dialog.DOCUMENT_POSITION_CONTAINED_BY)) {
       dialog.querySelector(FOCUSABLE_SELECTOR).focus();
     }
   }
 
-  onSubmit() {
+  onSubmit(event) {
+    // Dynamically require the email on submission so screen readers don't read
+    // out it's always required because there's also ways to skip the modal
+    const {
+      email
+    } = event.target.elements;
+
+    if (!email.value.length) {
+      email.required = true;
+      email.checkValidity();
+      event.preventDefault();
+      return;
+    }
+
     this.props.dispatch(common_Actions_jsm__WEBPACK_IMPORTED_MODULE_0__["actionCreators"].UserEvent({
       event: "SUBMIT_EMAIL",
       ...this._getFormInfo()
     }));
     global.addEventListener("visibilitychange", this.closeModal);
   }
 
   closeModal(ev) {
@@ -3438,17 +3473,20 @@ class _Trailhead extends react__WEBPACK_
     this.revealCards(); // If closeModal() was triggered by a visibilitychange event, the user actually
     // submitted the email form so we don't send a SKIPPED_SIGNIN ping.
 
     if (!ev || ev.type !== "visibilitychange") {
       this.props.dispatch(common_Actions_jsm__WEBPACK_IMPORTED_MODULE_0__["actionCreators"].UserEvent({
         event: "SKIPPED_SIGNIN",
         ...this._getFormInfo()
       }));
-    }
+    } // Bug 1190882 - Focus in a disappearing dialog confuses screen readers
+
+
+    this.props.document.activeElement.blur();
   }
   /**
    * Report to telemetry additional information about the form submission.
    */
 
 
   _getFormInfo() {
     const value = {
@@ -3467,16 +3505,17 @@ class _Trailhead extends react__WEBPACK_
 
     e.target.focus();
   }
 
   hideCardPanel() {
     this.setState({
       showCardPanel: false
     });
+    this.props.onDismissBundle();
   }
 
   revealCards() {
     this.setState({
       showCards: true
     });
   }
 
@@ -3639,17 +3678,16 @@ class _Trailhead extends react__WEBPACK_
     }), react__WEBPACK_IMPORTED_MODULE_4___default.a.createElement("p", {
       "data-l10n-id": "onboarding-join-form-email-error",
       className: "error"
     }), react__WEBPACK_IMPORTED_MODULE_4___default.a.createElement("input", {
       "data-l10n-id": content.form.email.string_id,
       placeholder: this.getStringValue(content.form.email),
       name: "email",
       type: "email",
-      required: "required",
       onInvalid: this.onInputInvalid,
       onChange: this.onInputChange
     }), react__WEBPACK_IMPORTED_MODULE_4___default.a.createElement("p", {
       className: "trailheadTerms",
       "data-l10n-id": "onboarding-join-form-legal"
     }, react__WEBPACK_IMPORTED_MODULE_4___default.a.createElement("a", {
       "data-l10n-name": "terms",
       target: "_blank",
--- a/browser/components/newtab/docs/v2-system-addon/data_events.md
+++ b/browser/components/newtab/docs/v2-system-addon/data_events.md
@@ -980,17 +980,17 @@ This reports the user's interaction with
 {
   "client_id": "26288a14-5cc4-d14f-ae0a-bb01ef45be9c",
   "action": "onboarding_user_event",
   "addon_version": "20180710100040",
   "impression_id": "n/a",
   "locale": "en-US",
   "source": "ONBOARDING",
   "message_id": "onboarding_message_1",
-  "event": "CLICK_BUTTION"
+  "event": ["IMPRESSION" | "CLICK_BUTTION" | "INSTALL" | "BLOCK"]
 }
 ```
 
 #### CFR interaction pings for all the prerelease channels and shield experiment
 ```js
 {
   "client_id": "26288a14-5cc4-d14f-ae0a-bb01ef45be9c",
   "action": "cfr_user_event",
--- a/browser/components/newtab/lib/ASRouter.jsm
+++ b/browser/components/newtab/lib/ASRouter.jsm
@@ -20,16 +20,18 @@ const {ASRouterActions: ra, actionTypes:
 const {CFRMessageProvider} = ChromeUtils.import("resource://activity-stream/lib/CFRMessageProvider.jsm");
 const {OnboardingMessageProvider} = ChromeUtils.import("resource://activity-stream/lib/OnboardingMessageProvider.jsm");
 const {RemoteSettings} = ChromeUtils.import("resource://services-settings/remote-settings.js");
 const {CFRPageActions} = ChromeUtils.import("resource://activity-stream/lib/CFRPageActions.jsm");
 const {AttributionCode} = ChromeUtils.import("resource:///modules/AttributionCode.jsm");
 
 ChromeUtils.defineModuleGetter(this, "ASRouterPreferences",
   "resource://activity-stream/lib/ASRouterPreferences.jsm");
+ChromeUtils.defineModuleGetter(this, "TARGETING_PREFERENCES",
+  "resource://activity-stream/lib/ASRouterPreferences.jsm");
 ChromeUtils.defineModuleGetter(this, "ASRouterTargeting",
   "resource://activity-stream/lib/ASRouterTargeting.jsm");
 ChromeUtils.defineModuleGetter(this, "QueryCache",
   "resource://activity-stream/lib/ASRouterTargeting.jsm");
 ChromeUtils.defineModuleGetter(this, "ASRouterTriggerListeners",
   "resource://activity-stream/lib/ASRouterTriggerListeners.jsm");
 ChromeUtils.defineModuleGetter(this, "TelemetryEnvironment",
   "resource://gre/modules/TelemetryEnvironment.jsm");
@@ -400,21 +402,36 @@ class _ASRouter {
     this._localProviders = localProviders;
     this.onMessage = this.onMessage.bind(this);
     this.handleMessageRequest = this.handleMessageRequest.bind(this);
     this.addImpression = this.addImpression.bind(this);
     this._handleTargetingError = this._handleTargetingError.bind(this);
     this.onPrefChange = this.onPrefChange.bind(this);
   }
 
-  // Update message providers and fetch new messages on pref change
-  async onPrefChange() {
-    this._loadLocalProviders();
-    this._updateMessageProviders();
-    await this.loadMessagesFromAllProviders();
+  async onPrefChange(prefName) {
+    if (TARGETING_PREFERENCES.includes(prefName)) {
+      // Notify all tabs of messages that have become invalid after pref change
+      const invalidMessages = [];
+      for (const msg of this._getUnblockedMessages()) {
+        if (!msg.targeting) {
+          continue;
+        }
+        const isMatch = await ASRouterTargeting.isMatch(msg.targeting);
+        if (!isMatch) {
+          invalidMessages.push(msg.id);
+        }
+      }
+      this.messageChannel.sendAsyncMessage(OUTGOING_MESSAGE_NAME, {type: at.AS_ROUTER_TARGETING_UPDATE, data: invalidMessages});
+    } else {
+      // Update message providers and fetch new messages on pref change
+      this._loadLocalProviders();
+      this._updateMessageProviders();
+      await this.loadMessagesFromAllProviders();
+    }
   }
 
   // Replace all frequency time period aliases with their millisecond values
   // This allows us to avoid accounting for special cases later on
   normalizeItemFrequency({frequency}) {
     if (frequency && frequency.custom) {
       for (const setting of frequency.custom) {
         if (setting.period === "daily") {
--- a/browser/components/newtab/lib/ASRouterPreferences.jsm
+++ b/browser/components/newtab/lib/ASRouterPreferences.jsm
@@ -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/. */
 "use strict";
 
 const {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm");
 
 const PROVIDER_PREF_BRANCH = "browser.newtabpage.activity-stream.asrouter.providers.";
 const DEVTOOLS_PREF = "browser.newtabpage.activity-stream.asrouter.devtoolsEnabled";
+const FXA_USERNAME_PREF = "services.sync.username";
 
 const DEFAULT_STATE = {
   _initialized: false,
   _providers: null,
   _providerPrefBranch: PROVIDER_PREF_BRANCH,
   _devtoolsEnabled: null,
   _devtoolsPref: DEVTOOLS_PREF,
 };
@@ -22,16 +23,20 @@ const MIGRATE_PREFS = [
 ];
 
 const USER_PREFERENCES = {
   snippets: "browser.newtabpage.activity-stream.feeds.snippets",
   cfrAddons: "browser.newtabpage.activity-stream.asrouter.userprefs.cfr.addons",
   cfrFeatures: "browser.newtabpage.activity-stream.asrouter.userprefs.cfr.features",
 };
 
+// Preferences that influence targeting attributes. When these change we need
+// to re-evaluate if the message targeting still matches
+const TARGETING_PREFERENCES = [FXA_USERNAME_PREF];
+
 const TEST_PROVIDERS = [{
   id: "snippets_local_testing",
   type: "local",
   localProvider: "SnippetsTestMessageProvider",
   enabled: true,
 }, {
   id: "panel_local_testing",
   type: "local",
@@ -164,29 +169,36 @@ class _ASRouterPreferences {
       return;
     }
     this._migratePrefs();
     Services.prefs.addObserver(this._providerPrefBranch, this);
     Services.prefs.addObserver(this._devtoolsPref, this);
     for (const id of Object.keys(USER_PREFERENCES)) {
       Services.prefs.addObserver(USER_PREFERENCES[id], this);
     }
+    for (const targetingPref of TARGETING_PREFERENCES) {
+      Services.prefs.addObserver(targetingPref, this);
+    }
     this._initialized = true;
   }
 
   uninit() {
     if (this._initialized) {
       Services.prefs.removeObserver(this._providerPrefBranch, this);
       Services.prefs.removeObserver(this._devtoolsPref, this);
       for (const id of Object.keys(USER_PREFERENCES)) {
         Services.prefs.removeObserver(USER_PREFERENCES[id], this);
       }
+      for (const targetingPref of TARGETING_PREFERENCES) {
+        Services.prefs.removeObserver(targetingPref, this);
+      }
     }
     Object.assign(this, DEFAULT_STATE);
     this._callbacks.clear();
   }
 }
 this._ASRouterPreferences = _ASRouterPreferences;
 
 this.ASRouterPreferences = new _ASRouterPreferences();
 this.TEST_PROVIDERS = TEST_PROVIDERS;
+this.TARGETING_PREFERENCES = TARGETING_PREFERENCES;
 
-const EXPORTED_SYMBOLS = ["_ASRouterPreferences", "ASRouterPreferences", "TEST_PROVIDERS"];
+const EXPORTED_SYMBOLS = ["_ASRouterPreferences", "ASRouterPreferences", "TEST_PROVIDERS", "TARGETING_PREFERENCES"];
--- a/browser/components/newtab/lib/AboutPreferences.jsm
+++ b/browser/components/newtab/lib/AboutPreferences.jsm
@@ -101,17 +101,16 @@ this.AboutPreferences = class AboutPrefe
     let sectionsCopy = JSON.parse(JSON.stringify(sections));
     sectionsCopy.forEach(obj => {
       if (obj.id === "highlights") {
         obj.shouldHidePref = true;
       }
 
       if (obj.id === "topstories") {
         obj.rowsPref = "";
-        obj.pref.descString = {id: "prefs_content_discovery_description"};
       }
     });
     return sectionsCopy;
   }
 
   async observe(window) {
     const discoveryStreamConfig = this.store.getState().DiscoveryStream.config;
     let sections = this.store.getState().Sections;
--- a/browser/components/newtab/lib/ActivityStream.jsm
+++ b/browser/components/newtab/lib/ActivityStream.jsm
@@ -241,20 +241,16 @@ const PREFS_CONFIG = new Map([
         layout_endpoint: "https://getpocket.cdn.mozilla.net/v3/newtab/layout?version=1&consumer_key=$apiKey&layout_variant=basic",
       });
     },
   }],
   ["discoverystream.endpoints", {
     title: "Endpoint prefixes (comma-separated) that are allowed to be requested",
     value: "https://getpocket.cdn.mozilla.net/",
   }],
-  ["discoverystream.optOut.0", {
-    title: "Opt out of new layout v0",
-    value: false,
-  }],
   ["discoverystream.spoc.impressions", {
     title: "Track spoc impressions",
     skipBroadcast: true,
     value: "{}",
   }],
   ["discoverystream.rec.impressions", {
     title: "Track rec impressions",
     skipBroadcast: true,
@@ -297,17 +293,17 @@ const FEEDS_DATA = [
   {
     name: "section.highlights",
     factory: () => new HighlightsFeed(),
     title: "Fetches content recommendations from places db",
     value: true,
   },
   {
     name: "section.topstories",
-    factory: () => new TopStoriesFeed(),
+    factory: () => new TopStoriesFeed(PREFS_CONFIG.get("discoverystream.config")),
     title: "Fetches content recommendations from a configurable content provider",
     // Dynamically determine if Pocket should be shown for a geo / locale
     getValue: ({geo, locale}) => {
       const locales = ({
         "US": ["en-CA", "en-GB", "en-US", "en-ZA"],
         "CA": ["en-CA", "en-GB", "en-US", "en-ZA"],
         "DE": ["de", "de-DE", "de-AT", "de-CH"],
       })[geo];
--- a/browser/components/newtab/lib/DiscoveryStreamFeed.jsm
+++ b/browser/components/newtab/lib/DiscoveryStreamFeed.jsm
@@ -21,17 +21,16 @@ const COMPONENT_FEEDS_UPDATE_TIME = 30 *
 const SPOCS_FEEDS_UPDATE_TIME = 30 * 60 * 1000; // 30 minutes
 const DEFAULT_RECS_EXPIRE_TIME = 60 * 60 * 1000; // 1 hour
 const MIN_DOMAIN_AFFINITIES_UPDATE_TIME = 12 * 60 * 60 * 1000; // 12 hours
 const MAX_LIFETIME_CAP = 500; // Guard against misconfiguration on the server
 const DEFAULT_MAX_HISTORY_QUERY_RESULTS = 1000;
 const FETCH_TIMEOUT = 45 * 1000;
 const PREF_CONFIG = "discoverystream.config";
 const PREF_ENDPOINTS = "discoverystream.endpoints";
-const PREF_OPT_OUT = "discoverystream.optOut.0";
 const PREF_SHOW_SPONSORED = "showSponsored";
 const PREF_SPOC_IMPRESSIONS = "discoverystream.spoc.impressions";
 const PREF_REC_IMPRESSIONS = "discoverystream.rec.impressions";
 
 let defaultLayoutResp;
 
 this.DiscoveryStreamFeed = class DiscoveryStreamFeed {
   constructor() {
@@ -85,20 +84,16 @@ this.DiscoveryStreamFeed = class Discove
       this._prefCache.config = JSON.parse(this.store.getState().Prefs.values[PREF_CONFIG]);
       const layoutUrl = this._prefCache.config.layout_endpoint;
 
       const apiKeyPref = this._prefCache.config.api_key_pref;
       if (layoutUrl && apiKeyPref) {
         const apiKey = Services.prefs.getCharPref(apiKeyPref, "");
         this._prefCache.config.layout_endpoint = this.finalLayoutEndpoint(layoutUrl, apiKey);
       }
-
-      // Modify the cached config with the user set opt-out for other consumers
-      this._prefCache.config.enabled = this._prefCache.config.enabled &&
-        !this.store.getState().Prefs.values[PREF_OPT_OUT];
     } catch (e) {
       // istanbul ignore next
       this._prefCache.config = {};
       // istanbul ignore next
       Cu.reportError(`Could not parse preference. Try resetting ${PREF_CONFIG} in about:config. ${e}`);
     }
     return this._prefCache.config;
   }
@@ -911,35 +906,27 @@ this.DiscoveryStreamFeed = class Discove
         break;
       case at.SYSTEM_TICK:
         // Only refresh if we loaded once in .enable()
         if (this.config.enabled && this.loaded && await this.checkIfAnyCacheExpired()) {
           await this.refreshAll({updateOpenTabs: false});
         }
         break;
       case at.DISCOVERY_STREAM_CONFIG_SET_VALUE:
-        // Disable opt-out if we're explicitly trying to enable
-        if (action.data.name === "enabled" && action.data.value) {
-          this.store.dispatch(ac.SetPref(PREF_OPT_OUT, false));
-        }
-
         // Use the original string pref to then set a value instead of
         // this.config which has some modifications
         this.store.dispatch(ac.SetPref(PREF_CONFIG, JSON.stringify({
           ...JSON.parse(this.store.getState().Prefs.values[PREF_CONFIG]),
           [action.data.name]: action.data.value,
         })));
         break;
       case at.DISCOVERY_STREAM_CONFIG_CHANGE:
         // When the config pref changes, load or unload data as needed.
         await this.onPrefChange();
         break;
-      case at.DISCOVERY_STREAM_OPT_OUT:
-        this.store.dispatch(ac.SetPref(PREF_OPT_OUT, true));
-        break;
       case at.DISCOVERY_STREAM_IMPRESSION_STATS:
         if (action.data.tiles && action.data.tiles[0] && action.data.tiles[0].id) {
           this.recordTopRecImpressions(action.data.tiles[0].id);
         }
         break;
       case at.DISCOVERY_STREAM_SPOC_IMPRESSION:
         if (this.showSpocs) {
           this.recordCampaignImpression(action.data.campaignId);
@@ -976,17 +963,16 @@ this.DiscoveryStreamFeed = class Discove
         break;
       case at.UNINIT:
         // When this feed is shutting down:
         this.uninitPrefs();
         break;
       case at.PREF_CHANGED:
         switch (action.data.name) {
           case PREF_CONFIG:
-          case PREF_OPT_OUT:
             // Clear the cached config and broadcast the newly computed value
             this._prefCache.config = null;
             this.store.dispatch(ac.BroadcastToContent({
               type: at.DISCOVERY_STREAM_CONFIG_CHANGE,
               data: this.config,
             }));
             break;
           // Check if spocs was disabled. Remove them if they were.
--- a/browser/components/newtab/lib/TopStoriesFeed.jsm
+++ b/browser/components/newtab/lib/TopStoriesFeed.jsm
@@ -25,27 +25,43 @@ const STORIES_NOW_THRESHOLD = 24 * 60 * 
 const MIN_DOMAIN_AFFINITIES_UPDATE_TIME = 12 * 60 * 60 * 1000; // 12 hours
 const DEFAULT_RECS_EXPIRE_TIME = 60 * 60 * 1000; // 1 hour
 const SECTION_ID = "topstories";
 const IMPRESSION_SOURCE = "TOP_STORIES";
 const SPOC_IMPRESSION_TRACKING_PREF = "feeds.section.topstories.spoc.impressions";
 const REC_IMPRESSION_TRACKING_PREF = "feeds.section.topstories.rec.impressions";
 const OPTIONS_PREF = "feeds.section.topstories.options";
 const MAX_LIFETIME_CAP = 500; // Guard against misconfiguration on the server
+const DISCOVERY_STREAM_PREF = "discoverystream.config";
 
 this.TopStoriesFeed = class TopStoriesFeed {
-  constructor() {
+  constructor(ds) {
+    // Use discoverystream config pref default values for fast path and
+    // if needed lazy load activity stream top stories feed based on
+    // actual user preference when PREFS_INITIAL_VALUES and PREF_CHANGED is invoked
+    this.discoveryStreamEnabled = ds && ds.value && JSON.parse(ds.value).enabled;
+    if (!this.discoveryStreamEnabled) {
+      this.initializeProperties();
+    }
+  }
+
+  initializeProperties() {
+    this.contentUpdateQueue = [];
     this.spocCampaignMap = new Map();
-    this.contentUpdateQueue = [];
     this.cache = new PersistentCache(SECTION_ID, true);
     this._prefs = new Prefs();
+    this.propertiesInitialized = true;
   }
 
   async onInit() {
     SectionsManager.enableSection(SECTION_ID);
+    if (this.discoveryStreamEnabled) {
+      return;
+    }
+
     try {
       const {options} = SectionsManager.sections.get(SECTION_ID);
       const apiKey = this.getApiKeyFromPref(options.api_key_pref);
       this.stories_endpoint = this.produceFinalEndpointUrl(options.stories_endpoint, apiKey);
       this.topics_endpoint = this.produceFinalEndpointUrl(options.topics_endpoint, apiKey);
       this.read_more_endpoint = options.read_more_endpoint;
       this.stories_referrer = options.stories_referrer;
       this.personalized = options.personalized;
@@ -94,17 +110,22 @@ this.TopStoriesFeed = class TopStoriesFe
   async clearCache() {
     await this.cache.set("stories", {});
     await this.cache.set("topics", {});
     await this.cache.set("spocs", {});
   }
 
   uninit() {
     this.storiesLoaded = false;
-    Services.obs.removeObserver(this, "idle-daily");
+    try {
+      Services.obs.removeObserver(this, "idle-daily");
+    } catch (e) {
+      // Attempt to remove unassociated observer which is possible when discovery stream
+      // is enabled and user never used activity stream experience
+    }
     SectionsManager.disableSection(SECTION_ID);
   }
 
   getPocketState(target) {
     const action = {type: at.POCKET_LOGGED_IN, data: pktApi.isUserLoggedIn()};
     this.store.dispatch(ac.OnlyToOneContent(action, target));
   }
 
@@ -638,20 +659,62 @@ this.TopStoriesFeed = class TopStoriesFe
           model_keys: data.model_keys,
         };
       }
       return true;
     }
     return false;
   }
 
-  async onAction(action) {
+  lazyLoadTopStories(dsPref) {
+    try {
+      this.discoveryStreamEnabled = JSON.parse(dsPref).enabled;
+    } catch (e) {
+      // Load activity stream top stories if fail to determine discovery stream state
+      this.discoveryStreamEnabled = false;
+    }
+
+    // Return without invoking initialization if top stories are loaded
+    if (this.storiesLoaded) {
+      return;
+    }
+
+    if (!this.discoveryStreamEnabled && !this.propertiesInitialized) {
+      this.initializeProperties();
+    }
+    this.init();
+  }
+
+  handleDisabled(action) {
     switch (action.type) {
-      case at.INIT:
-        this.init();
+      case at.PREFS_INITIAL_VALUES:
+        this.lazyLoadTopStories(action.data[DISCOVERY_STREAM_PREF]);
+        break;
+      case at.PREF_CHANGED:
+        if (action.data.name === DISCOVERY_STREAM_PREF) {
+          this.lazyLoadTopStories(action.data.value);
+        }
+        break;
+      case at.UNINIT:
+        this.uninit();
+        break;
+    }
+  }
+
+  async onAction(action) {
+    if (this.discoveryStreamEnabled) {
+      this.handleDisabled(action);
+      return;
+    }
+    switch (action.type) {
+      // Check for pref initial values to lazy load activity stream top stories
+      // Here we are not using usual INIT and relying on PREFS_INITIAL_VALUES
+      // to check discoverystream pref and load activity stream top stories only if needed.
+      case at.PREFS_INITIAL_VALUES:
+        this.lazyLoadTopStories(action.data[DISCOVERY_STREAM_PREF]);
         break;
       case at.SYSTEM_TICK:
         let stories;
         let topics;
         if (Date.now() - this.storiesLastUpdated >= STORIES_UPDATE_TIME) {
           stories = await this.fetchStories();
         }
         if (Date.now() - this.topicsLastUpdated >= TOPICS_UPDATE_TIME) {
@@ -705,16 +768,19 @@ this.TopStoriesFeed = class TopStoriesFe
                 .map(t => t.id);
               this.recordTopRecImpressions(topRecs);
             }
           }
         }
         break;
       }
       case at.PREF_CHANGED:
+        if (action.data.name === DISCOVERY_STREAM_PREF) {
+          this.lazyLoadTopStories(action.data.value);
+        }
         // Check if spocs was disabled. Remove them if they were.
         if (action.data.name === "showSponsored" && !action.data.value) {
           await this.removeSpocs();
         }
         if (action.data.name === "pocketCta") {
           this.dispatchPocketCta(action.data.value, true);
         }
         if (action.data.name === OPTIONS_PREF) {
--- a/browser/components/newtab/locales-src/de/strings.properties
+++ b/browser/components/newtab/locales-src/de/strings.properties
@@ -88,16 +88,17 @@ section_disclaimer_topstories_buttontext
 # for a "Firefox Home" section. "Firefox" should be treated as a brand and kept
 # in English, while "Home" should be localized matching the about:preferences
 # sidebar mozilla-central string for the panel that has preferences related to
 # what is shown for the homepage, new windows, and new tabs.
 prefs_home_header=Inhalte des Firefox-Startbildschirms
 prefs_home_description=Wählen Sie, welche Inhalte auf Ihrem Firefox-Startbildschirm angezeigt werden sollen.
 
 prefs_content_discovery_header=Firefox-Startseite
+
 prefs_content_discovery_description="Neues aus dem Netz" macht auf gute Inhalte im Internet aufmerksam.
 prefs_content_discovery_button="Neues aus dem Netz" nicht anzeigen
 
 # LOCALIZATION NOTE (prefs_section_rows_option): This is a semi-colon list of
 # plural forms used in a drop down of multiple row options (1 row, 2 rows).
 # See: http://developer.mozilla.org/en/docs/Localization_and_Plurals
 prefs_section_rows_option={num} Zeile;{num} Zeilen
 prefs_search_header=Internetsuche
@@ -185,31 +186,31 @@ section_menu_action_add_topsite=Wichtige Seite hinzufügen
 section_menu_action_add_search_engine=Suchmaschine hinzufügen
 section_menu_action_move_up=Nach oben schieben
 section_menu_action_move_down=Nach unten schieben
 section_menu_action_privacy_notice=Datenschutzhinweis
 
 # LOCALIZATION NOTE (firstrun_*). These strings are displayed only once, on the
 # firstrun of the browser, they give an introduction to Firefox and Sync.
 firstrun_title=Firefox für unterwegs
-firstrun_content=Nehmen Sie Ihre Lesezeichen, Chronik, Passwörter und andere Einstellungen auf allen Geräten mit.
-firstrun_learn_more_link=Jetzt mehr über Firefox-Konten erfahren
+firstrun_content=Nimm Lesezeichen, Chronik, Passwörter und andere Einstellungen mit auf alle deine Geräten.
+firstrun_learn_more_link=Weitere Infos zum Firefox-Konto
 
 # LOCALIZATION NOTE (firstrun_form_header and firstrun_form_sub_header):
 # firstrun_form_sub_header is a continuation of firstrun_form_header, they are one sentence.
 # firstrun_form_header is displayed more boldly as the call to action.
 firstrun_form_header=E-Mail-Adresse eingeben
-firstrun_form_sub_header=um sich bei Firefox Sync anzumelden.
+firstrun_form_sub_header=um dich bei Firefox Sync anzumelden
 
 firstrun_email_input_placeholder=E-Mail
 firstrun_invalid_input=Gültige E-Mail-Adresse erforderlich
 
 # LOCALIZATION NOTE (firstrun_extra_legal_links): {terms} is equal to firstrun_terms_of_service, and
 # {privacy} is equal to firstrun_privacy_notice. {terms} and {privacy} are clickable links.
-firstrun_extra_legal_links=Indem Sie fortfahren, stimmen Sie den {terms} und dem {privacy} zu.
+firstrun_extra_legal_links=Indem du fortfährst, stimmst du unseren {terms} und dem {privacy} zu.
 firstrun_terms_of_service=Nutzungsbedingungen
 firstrun_privacy_notice=Datenschutzhinweis
 
 firstrun_continue_to_login=Weiter
 firstrun_skip_login=Diesen Schritt überspringen
 
 # LOCALIZATION NOTE (context_menu_title): Action tooltip to open a context menu
 context_menu_title=Menü öffnen
--- a/browser/components/newtab/locales-src/it/strings.properties
+++ b/browser/components/newtab/locales-src/it/strings.properties
@@ -102,14 +102,14 @@ section_menu_action_move_down=Sposta giù
 section_menu_action_privacy_notice=Informativa sulla privacy
 firstrun_title=Porta Firefox con te
 firstrun_content=Ritrova segnalibri, cronologia, password e altre impostazioni su tutti i tuoi dispositivi.
 firstrun_learn_more_link=Ulteriori informazioni sull’account Firefox
 firstrun_form_header=Inserisci la tua email
 firstrun_form_sub_header=per utilizzare Firefox Sync.
 firstrun_email_input_placeholder=Email
 firstrun_invalid_input=Inserire un indirizzo email valido
-firstrun_extra_legal_links=Proseguendo, accetto le {terms} e l’{privacy}.
+firstrun_extra_legal_links=Proseguendo, accetti le {terms} e l’{privacy}.
 firstrun_terms_of_service=condizioni di utilizzo del servizio
 firstrun_privacy_notice=informativa sulla privacy
 firstrun_continue_to_login=Continua
 firstrun_skip_login=Ignora questo passaggio
 context_menu_title=Apri menu
--- a/browser/components/newtab/locales-src/ka/strings.properties
+++ b/browser/components/newtab/locales-src/ka/strings.properties
@@ -1,15 +1,15 @@
 newtab_page_title=ახალი ჩანართი
 
 header_top_sites=რჩეული საიტები
 header_highlights=მნიშვნელოვანი
 # LOCALIZATION NOTE(header_recommended_by): This is followed by the name
 # of the corresponding content provider.
-header_recommended_by={provider}-ის შემოთავაზებული
+header_recommended_by={provider} გირჩევთ
 
 # LOCALIZATION NOTE(context_menu_button_sr): This is for screen readers when
 # the context menu button is focused/active. Title is the label or hostname of
 # the site.
 context_menu_button_sr=კონტექსტური მენიუს გახსნა {title}
 
 # LOCALIZATION NOTE(section_context_menu_button_sr): This is for screen readers when
 # the section edit context menu button is focused/active.
@@ -88,16 +88,17 @@ section_disclaimer_topstories_buttontext=კარგი, გასაგებია
 # for a "Firefox Home" section. "Firefox" should be treated as a brand and kept
 # in English, while "Home" should be localized matching the about:preferences
 # sidebar mozilla-central string for the panel that has preferences related to
 # what is shown for the homepage, new windows, and new tabs.
 prefs_home_header=Firefox საწყისი გვერდი
 prefs_home_description=აირჩიეთ, თუ რისი გამოჩენა გსურთ Firefox-ის საწყის გვერდზე.
 
 prefs_content_discovery_header=Firefox მთავარი
+
 prefs_content_discovery_description=შიგთავსის მოძიება Firefox-ის მთავარ გვერდზე, საშუალებას გაძლევთ აღმოაჩინოთ მაღალი ხარისხის, გამოსადეგი სტატიები მთელ ინტერნეტში.
 prefs_content_discovery_button=შიგთავსის მოძიების გამორთვა
 
 # LOCALIZATION NOTE (prefs_section_rows_option): This is a semi-colon list of
 # plural forms used in a drop down of multiple row options (1 row, 2 rows).
 # See: http://developer.mozilla.org/en/docs/Localization_and_Plurals
 prefs_section_rows_option={num} რიგად;{num} რიგად
 prefs_search_header=საძიებო ველი
@@ -152,17 +153,17 @@ pocket_more_reccommendations=მეტი შემოთავაზებები
 pocket_how_it_works=როგორ მუშაობს
 pocket_cta_button=გამოიყენეთ Pocket
 pocket_cta_text=გადაინახეთ სასურველი შიგთავსი Pocket-ში და მიეცით გონებას საკვები, შთამბეჭდავი საკითხავი მასალის სახით.
 
 highlights_empty_state=დაიწყეთ გვერდების დათვალიერება და აქ გამოჩნდება თქვენთვის სასურველი სტატიები, ვიდეოები და ბოლოს მონახულებული ან ჩანიშნული საიტები.
 # LOCALIZATION NOTE (topstories_empty_state): When there are no recommendations,
 # in the space that would have shown a few stories, this is shown instead.
 # {provider} is replaced by the name of the content provider for this section.
-topstories_empty_state=უკვე ყველაფერი წაკითხული გაქვთ. {provider}-იდან ახალი რჩეული სტატიების მისაღებად, მოგვიანებით შემოიარეთ. თუ ვერ ითმენთ, აირჩიეთ რომელიმე მოთხოვნადი თემა, ახალი საინტერესო სტატიების მოსაძიებლად.
+topstories_empty_state=უკვე ყველაფერს გაეცანით. მოგვიანებით შემოიარეთ მეტი რჩეული სტატიის სანახავად, რომელსაც {provider} მოგაწვდით. ვერ ითმენთ? აირჩიეთ რომელიმე ფართოდ გავრცელებული საკითხი, ახალი საინტერესო სტატიების მოსაძიებლად.
 
 # LOCALIZATION NOTE (manual_migration_explanation2): This message is shown to encourage users to
 # import their browser profile from another browser they might be using.
 manual_migration_explanation2=გადმოიტანეთ სხვა ბრაუზერებიდან თქვენი სანიშნები, ისტორია და პაროლები Firefox-ში.
 # LOCALIZATION NOTE (manual_migration_cancel_button): This message is shown on a button that cancels the
 # process of importing another browser’s profile into Firefox.
 manual_migration_cancel_button=არა, გმადლობთ
 # LOCALIZATION NOTE (manual_migration_import_button): This message is shown on a button that starts the process
--- a/browser/components/newtab/locales-src/lt/strings.properties
+++ b/browser/components/newtab/locales-src/lt/strings.properties
@@ -88,16 +88,17 @@ section_disclaimer_topstories_buttontext
 # for a "Firefox Home" section. "Firefox" should be treated as a brand and kept
 # in English, while "Home" should be localized matching the about:preferences
 # sidebar mozilla-central string for the panel that has preferences related to
 # what is shown for the homepage, new windows, and new tabs.
 prefs_home_header=„Firefox“ pradžios turinys
 prefs_home_description=Pasirinkite, kokį turinį norite matyti „Firefox“ pradžios ekrane
 
 prefs_content_discovery_header=„Firefox“ pradžios tinklalapis
+
 prefs_content_discovery_description=„Firefox“ turinio atradimas pradžios tinklalapyje leidžia atrasti aukštos kokybės ir jums galimai įdomius straipsnius iš interneto.
 prefs_content_discovery_button=Išjungti turinio atradimą
 
 # LOCALIZATION NOTE (prefs_section_rows_option): This is a semi-colon list of
 # plural forms used in a drop down of multiple row options (1 row, 2 rows).
 # See: http://developer.mozilla.org/en/docs/Localization_and_Plurals
 prefs_section_rows_option={num} eilutė;{num} eilutės;{num} eilučių
 prefs_search_header=Paieška internete
@@ -186,17 +187,17 @@ section_menu_action_add_search_engine=Pridėti ieškyklę
 section_menu_action_move_up=Pakelti
 section_menu_action_move_down=Nuleisti
 section_menu_action_privacy_notice=Privatumo pranešimas
 
 # LOCALIZATION NOTE (firstrun_*). These strings are displayed only once, on the
 # firstrun of the browser, they give an introduction to Firefox and Sync.
 firstrun_title=Pasiimkite „Firefox“ su savimi
 firstrun_content=Turėkite savo adresyną, žurnalą, slaptažodžius ir kitas nuostatas visuose savo įrenginiuose.
-firstrun_learn_more_link=Sužinokite daugiau apie „Firefox“ paskyras
+firstrun_learn_more_link=Sužinokite apie „Firefox“ paskyras daugiau
 
 # LOCALIZATION NOTE (firstrun_form_header and firstrun_form_sub_header):
 # firstrun_form_sub_header is a continuation of firstrun_form_header, they are one sentence.
 # firstrun_form_header is displayed more boldly as the call to action.
 firstrun_form_header=Įveskite savo el. paštą
 firstrun_form_sub_header=norėdami tęsti su „Firefox Sync“.
 
 firstrun_email_input_placeholder=El. paštas
--- a/browser/components/newtab/locales-src/sk/strings.properties
+++ b/browser/components/newtab/locales-src/sk/strings.properties
@@ -88,16 +88,17 @@ section_disclaimer_topstories_buttontext
 # for a "Firefox Home" section. "Firefox" should be treated as a brand and kept
 # in English, while "Home" should be localized matching the about:preferences
 # sidebar mozilla-central string for the panel that has preferences related to
 # what is shown for the homepage, new windows, and new tabs.
 prefs_home_header=Obsah domovskej stránky Firefoxu
 prefs_home_description=Vyberte si obsah, ktorý chcete mať na domovskej stránke svojho Firefoxu.
 
 prefs_content_discovery_header=Domovská stránka Firefoxu
+
 prefs_content_discovery_description=Odporúčanie obsahu na domovskej stránke Firefoxu vám umožňuje objaviť vysokokvalitné a relevantné články z celého internetu.
 prefs_content_discovery_button=Vypnúť odporúčanie obsahu
 
 # LOCALIZATION NOTE (prefs_section_rows_option): This is a semi-colon list of
 # plural forms used in a drop down of multiple row options (1 row, 2 rows).
 # See: http://developer.mozilla.org/en/docs/Localization_and_Plurals
 prefs_section_rows_option={num} riadok;{num} riadky;{num} riadkov
 prefs_search_header=Vyhľadávanie na webe
@@ -156,17 +157,17 @@ pocket_cta_text=Ukladajte si články do služby Pocket a užívajte si skvelé čítanie.
 highlights_empty_state=Začnite s prehliadaním a my vám na tomto mieste ukážeme skvelé články, videá a ostatné stránky, ktoré ste nedávno navštívili alebo pridali medzi záložky.
 # LOCALIZATION NOTE (topstories_empty_state): When there are no recommendations,
 # in the space that would have shown a few stories, this is shown instead.
 # {provider} is replaced by the name of the content provider for this section.
 topstories_empty_state=Už ste prečítali všetko. Ďalšie príbehy zo služby {provider} tu nájdete opäť neskôr. Nemôžete sa dočkať? Vyberte si populárnu tému a pozrite sa na ďalšie skvelé príbehy z celého webu.
 
 # LOCALIZATION NOTE (manual_migration_explanation2): This message is shown to encourage users to
 # import their browser profile from another browser they might be using.
-manual_migration_explanation2=Vyskúšajte Firefox so záložkami, históriou prehliadania a heslami s iných prehliadačov.
+manual_migration_explanation2=Vyskúšajte Firefox so záložkami, históriou prehliadania a heslami z iných prehliadačov.
 # LOCALIZATION NOTE (manual_migration_cancel_button): This message is shown on a button that cancels the
 # process of importing another browser’s profile into Firefox.
 manual_migration_cancel_button=Nie, ďakujem
 # LOCALIZATION NOTE (manual_migration_import_button): This message is shown on a button that starts the process
 # of importing another browser’s profile profile into Firefox.
 manual_migration_import_button=Importovať teraz
 
 # LOCALIZATION NOTE (error_fallback_default_*): This message and suggested
--- a/browser/components/newtab/locales-src/vi/strings.properties
+++ b/browser/components/newtab/locales-src/vi/strings.properties
@@ -35,17 +35,17 @@ menu_action_open_private_window=Mở trong cửa sổ riêng tư mới
 menu_action_dismiss=Bỏ qua
 menu_action_delete=Xóa khỏi lịch sử
 menu_action_pin=Ghim
 menu_action_unpin=Bỏ ghim
 confirm_history_delete_p1=Bạn có chắc bạn muốn xóa bỏ mọi thứ của trang này từ lịch sử?
 # LOCALIZATION NOTE (confirm_history_delete_notice_p2): this string is displayed in
 # the same dialog as confirm_history_delete_p1. "This action" refers to deleting a
 # page from history.
-confirm_history_delete_notice_p2=Hành động này không thể hoàn tác.
+confirm_history_delete_notice_p2=Thao tác này không thể hoàn tác được.
 menu_action_save_to_pocket=Lưu vào Pocket
 menu_action_delete_pocket=Xóa khỏi Pocket
 menu_action_archive_pocket=Lưu trữ trong Pocket
 
 # LOCALIZATION NOTE (menu_action_show_file_*): These are platform specific strings
 # found in the context menu of an item that has been downloaded. The intention behind
 # "this action" is that it will show where the downloaded file exists on the file system
 # for each operating system.
--- a/browser/components/newtab/prerendered/locales/de/activity-stream-strings.js
+++ b/browser/components/newtab/prerendered/locales/de/activity-stream-strings.js
@@ -89,21 +89,21 @@ window.gActivityStreamStrings = {
   "section_menu_action_manage_section": "Abschnitt verwalten",
   "section_menu_action_manage_webext": "Erweiterung verwalten",
   "section_menu_action_add_topsite": "Wichtige Seite hinzufügen",
   "section_menu_action_add_search_engine": "Suchmaschine hinzufügen",
   "section_menu_action_move_up": "Nach oben schieben",
   "section_menu_action_move_down": "Nach unten schieben",
   "section_menu_action_privacy_notice": "Datenschutzhinweis",
   "firstrun_title": "Firefox für unterwegs",
-  "firstrun_content": "Nehmen Sie Ihre Lesezeichen, Chronik, Passwörter und andere Einstellungen auf allen Geräten mit.",
-  "firstrun_learn_more_link": "Jetzt mehr über Firefox-Konten erfahren",
+  "firstrun_content": "Nimm Lesezeichen, Chronik, Passwörter und andere Einstellungen mit auf alle deine Geräten.",
+  "firstrun_learn_more_link": "Weitere Infos zum Firefox-Konto",
   "firstrun_form_header": "E-Mail-Adresse eingeben",
-  "firstrun_form_sub_header": "um sich bei Firefox Sync anzumelden.",
+  "firstrun_form_sub_header": "um dich bei Firefox Sync anzumelden",
   "firstrun_email_input_placeholder": "E-Mail",
   "firstrun_invalid_input": "Gültige E-Mail-Adresse erforderlich",
-  "firstrun_extra_legal_links": "Indem Sie fortfahren, stimmen Sie den {terms} und dem {privacy} zu.",
+  "firstrun_extra_legal_links": "Indem du fortfährst, stimmst du unseren {terms} und dem {privacy} zu.",
   "firstrun_terms_of_service": "Nutzungsbedingungen",
   "firstrun_privacy_notice": "Datenschutzhinweis",
   "firstrun_continue_to_login": "Weiter",
   "firstrun_skip_login": "Diesen Schritt überspringen",
   "context_menu_title": "Menü öffnen"
 };
--- a/browser/components/newtab/prerendered/locales/it/activity-stream-strings.js
+++ b/browser/components/newtab/prerendered/locales/it/activity-stream-strings.js
@@ -95,15 +95,15 @@ window.gActivityStreamStrings = {
   "section_menu_action_privacy_notice": "Informativa sulla privacy",
   "firstrun_title": "Porta Firefox con te",
   "firstrun_content": "Ritrova segnalibri, cronologia, password e altre impostazioni su tutti i tuoi dispositivi.",
   "firstrun_learn_more_link": "Ulteriori informazioni sull’account Firefox",
   "firstrun_form_header": "Inserisci la tua email",
   "firstrun_form_sub_header": "per utilizzare Firefox Sync.",
   "firstrun_email_input_placeholder": "Email",
   "firstrun_invalid_input": "Inserire un indirizzo email valido",
-  "firstrun_extra_legal_links": "Proseguendo, accetto le {terms} e l’{privacy}.",
+  "firstrun_extra_legal_links": "Proseguendo, accetti le {terms} e l’{privacy}.",
   "firstrun_terms_of_service": "condizioni di utilizzo del servizio",
   "firstrun_privacy_notice": "informativa sulla privacy",
   "firstrun_continue_to_login": "Continua",
   "firstrun_skip_login": "Ignora questo passaggio",
   "context_menu_title": "Apri menu"
 };
--- a/browser/components/newtab/prerendered/locales/ka/activity-stream-prerendered-noscripts.html
+++ b/browser/components/newtab/prerendered/locales/ka/activity-stream-prerendered-noscripts.html
@@ -5,12 +5,12 @@
     <meta http-equiv="Content-Security-Policy" content="default-src 'none'; object-src 'none'; script-src resource: chrome:; connect-src https:; img-src https: data: blob:; style-src 'unsafe-inline';">
     <title>ახალი ჩანართი</title>
     <link rel="icon" type="image/png" href="chrome://branding/content/icon32.png"/>
     <link rel="stylesheet" href="chrome://browser/content/contentSearchUI.css" />
     <link rel="stylesheet" href="resource://activity-stream/css/activity-stream.css" />
   </head>
   <body class="activity-stream">
     <div id="header-asrouter-container" role="presentation"></div>
-    <div id="root"><div><div class="outer-wrapper fixed-to-top"><main><div class="non-collapsible-section"><div class="search-wrapper"><div class="search-inner-wrapper"><label for="newtab-search-text" class="search-label"><span class="sr-only"><span>ინტერნეტში ძიება</span></span></label><input type="search" id="newtab-search-text" maxLength="256" placeholder="ინტერნეტში ძიება" title="ინტერნეტში ძიება"/><button id="searchSubmit" class="search-button" title="ძიება"><span class="sr-only"><span>ძიება</span></span></button></div></div></div><div class="body-wrapper"><div class="sections-list"><section class="collapsible-section top-sites animation-enabled" data-section-id="topsites"><div class="section-top-bar"><h3 class="section-title"><span class="click-target-container"><span class="click-target"><span class="icon icon-small-spacer icon-topsites"></span><span>რჩეული საიტები</span></span><span class="click-target"></span><span class="learn-more-link-wrapper"></span></span></h3><div><button class="context-menu-button icon" title="მენიუს გახსნა"><span class="sr-only"><span>გვერდის ნაწილების პარამეტრები</span></span></button></div></div><div class="section-body"><ul class="top-sites-list"><li class="top-site-outer placeholder "><div class="top-site-inner"><a tabindex="0" draggable="true"><div class="tile" aria-hidden="true"><div class="screenshot" style="background-image:none"></div></div><div class="title "><span dir="auto"></span></div></a><button class="context-menu-button edit-button icon" title="საიტის ჩასწორება"></button></div></li><li class="top-site-outer placeholder "><div class="top-site-inner"><a tabindex="0" draggable="true"><div class="tile" aria-hidden="true"><div class="screenshot" style="background-image:none"></div></div><div class="title "><span dir="auto"></span></div></a><button class="context-menu-button edit-button icon" title="საიტის ჩასწორება"></button></div></li><li class="top-site-outer placeholder "><div class="top-site-inner"><a tabindex="0" draggable="true"><div class="tile" aria-hidden="true"><div class="screenshot" style="background-image:none"></div></div><div class="title "><span dir="auto"></span></div></a><button class="context-menu-button edit-button icon" title="საიტის ჩასწორება"></button></div></li><li class="top-site-outer placeholder "><div class="top-site-inner"><a tabindex="0" draggable="true"><div class="tile" aria-hidden="true"><div class="screenshot" style="background-image:none"></div></div><div class="title "><span dir="auto"></span></div></a><button class="context-menu-button edit-button icon" title="საიტის ჩასწორება"></button></div></li><li class="top-site-outer placeholder "><div class="top-site-inner"><a tabindex="0" draggable="true"><div class="tile" aria-hidden="true"><div class="screenshot" style="background-image:none"></div></div><div class="title "><span dir="auto"></span></div></a><button class="context-menu-button edit-button icon" title="საიტის ჩასწორება"></button></div></li><li class="top-site-outer placeholder "><div class="top-site-inner"><a tabindex="0" draggable="true"><div class="tile" aria-hidden="true"><div class="screenshot" style="background-image:none"></div></div><div class="title "><span dir="auto"></span></div></a><button class="context-menu-button edit-button icon" title="საიტის ჩასწორება"></button></div></li><li class="top-site-outer placeholder hide-for-narrow"><div class="top-site-inner"><a tabindex="0" draggable="true"><div class="tile" aria-hidden="true"><div class="screenshot" style="background-image:none"></div></div><div class="title "><span dir="auto"></span></div></a><button class="context-menu-button edit-button icon" title="საიტის ჩასწორება"></button></div></li><li class="top-site-outer placeholder hide-for-narrow"><div class="top-site-inner"><a tabindex="0" draggable="true"><div class="tile" aria-hidden="true"><div class="screenshot" style="background-image:none"></div></div><div class="title "><span dir="auto"></span></div></a><button class="context-menu-button edit-button icon" title="საიტის ჩასწორება"></button></div></li></ul><div class="edit-topsites-wrapper"></div></div></section><section class="collapsible-section section normal-cards animation-enabled" data-section-id="topstories"><div class="section-top-bar"><h3 class="section-title"><span class="click-target-container"><span class="click-target"><span class="icon icon-small-spacer icon-pocket"></span><span>Pocket-ის შემოთავაზებული</span></span><span class="click-target"></span><span class="learn-more-link-wrapper"></span></span></h3><div><button class="context-menu-button icon" title="მენიუს გახსნა"><span class="sr-only"><span>გვერდის ნაწილების პარამეტრები</span></span></button></div></div><div class="section-body"><ul class="section-list" style="padding:0"></ul><div class="top-stories-bottom-container"><div class="wrapper-more-recommendations"></div></div></div></section><section class="collapsible-section section normal-cards animation-enabled" data-section-id="highlights"><div class="section-top-bar"><h3 class="section-title"><span class="click-target-container"><span class="click-target"><span class="icon icon-small-spacer icon-highlights"></span><span>მნიშვნელოვანი</span></span><span class="click-target"></span><span class="learn-more-link-wrapper"></span></span></h3><div><button class="context-menu-button icon" title="მენიუს გახსნა"><span class="sr-only"><span>გვერდის ნაწილების პარამეტრები</span></span></button></div></div><div class="section-body"><ul class="section-list" style="padding:0"></ul></div></section></div><div class="prefs-button"><button class="icon icon-settings" title="მოირგეთ ახალი ჩანართის გვერდი"></button></div></div></main></div></div></div>
+    <div id="root"><div><div class="outer-wrapper fixed-to-top"><main><div class="non-collapsible-section"><div class="search-wrapper"><div class="search-inner-wrapper"><label for="newtab-search-text" class="search-label"><span class="sr-only"><span>ინტერნეტში ძიება</span></span></label><input type="search" id="newtab-search-text" maxLength="256" placeholder="ინტერნეტში ძიება" title="ინტერნეტში ძიება"/><button id="searchSubmit" class="search-button" title="ძიება"><span class="sr-only"><span>ძიება</span></span></button></div></div></div><div class="body-wrapper"><div class="sections-list"><section class="collapsible-section top-sites animation-enabled" data-section-id="topsites"><div class="section-top-bar"><h3 class="section-title"><span class="click-target-container"><span class="click-target"><span class="icon icon-small-spacer icon-topsites"></span><span>რჩეული საიტები</span></span><span class="click-target"></span><span class="learn-more-link-wrapper"></span></span></h3><div><button class="context-menu-button icon" title="მენიუს გახსნა"><span class="sr-only"><span>გვერდის ნაწილების პარამეტრები</span></span></button></div></div><div class="section-body"><ul class="top-sites-list"><li class="top-site-outer placeholder "><div class="top-site-inner"><a tabindex="0" draggable="true"><div class="tile" aria-hidden="true"><div class="screenshot" style="background-image:none"></div></div><div class="title "><span dir="auto"></span></div></a><button class="context-menu-button edit-button icon" title="საიტის ჩასწორება"></button></div></li><li class="top-site-outer placeholder "><div class="top-site-inner"><a tabindex="0" draggable="true"><div class="tile" aria-hidden="true"><div class="screenshot" style="background-image:none"></div></div><div class="title "><span dir="auto"></span></div></a><button class="context-menu-button edit-button icon" title="საიტის ჩასწორება"></button></div></li><li class="top-site-outer placeholder "><div class="top-site-inner"><a tabindex="0" draggable="true"><div class="tile" aria-hidden="true"><div class="screenshot" style="background-image:none"></div></div><div class="title "><span dir="auto"></span></div></a><button class="context-menu-button edit-button icon" title="საიტის ჩასწორება"></button></div></li><li class="top-site-outer placeholder "><div class="top-site-inner"><a tabindex="0" draggable="true"><div class="tile" aria-hidden="true"><div class="screenshot" style="background-image:none"></div></div><div class="title "><span dir="auto"></span></div></a><button class="context-menu-button edit-button icon" title="საიტის ჩასწორება"></button></div></li><li class="top-site-outer placeholder "><div class="top-site-inner"><a tabindex="0" draggable="true"><div class="tile" aria-hidden="true"><div class="screenshot" style="background-image:none"></div></div><div class="title "><span dir="auto"></span></div></a><button class="context-menu-button edit-button icon" title="საიტის ჩასწორება"></button></div></li><li class="top-site-outer placeholder "><div class="top-site-inner"><a tabindex="0" draggable="true"><div class="tile" aria-hidden="true"><div class="screenshot" style="background-image:none"></div></div><div class="title "><span dir="auto"></span></div></a><button class="context-menu-button edit-button icon" title="საიტის ჩასწორება"></button></div></li><li class="top-site-outer placeholder hide-for-narrow"><div class="top-site-inner"><a tabindex="0" draggable="true"><div class="tile" aria-hidden="true"><div class="screenshot" style="background-image:none"></div></div><div class="title "><span dir="auto"></span></div></a><button class="context-menu-button edit-button icon" title="საიტის ჩასწორება"></button></div></li><li class="top-site-outer placeholder hide-for-narrow"><div class="top-site-inner"><a tabindex="0" draggable="true"><div class="tile" aria-hidden="true"><div class="screenshot" style="background-image:none"></div></div><div class="title "><span dir="auto"></span></div></a><button class="context-menu-button edit-button icon" title="საიტის ჩასწორება"></button></div></li></ul><div class="edit-topsites-wrapper"></div></div></section><section class="collapsible-section section normal-cards animation-enabled" data-section-id="topstories"><div class="section-top-bar"><h3 class="section-title"><span class="click-target-container"><span class="click-target"><span class="icon icon-small-spacer icon-pocket"></span><span>Pocket გირჩევთ</span></span><span class="click-target"></span><span class="learn-more-link-wrapper"></span></span></h3><div><button class="context-menu-button icon" title="მენიუს გახსნა"><span class="sr-only"><span>გვერდის ნაწილების პარამეტრები</span></span></button></div></div><div class="section-body"><ul class="section-list" style="padding:0"></ul><div class="top-stories-bottom-container"><div class="wrapper-more-recommendations"></div></div></div></section><section class="collapsible-section section normal-cards animation-enabled" data-section-id="highlights"><div class="section-top-bar"><h3 class="section-title"><span class="click-target-container"><span class="click-target"><span class="icon icon-small-spacer icon-highlights"></span><span>მნიშვნელოვანი</span></span><span class="click-target"></span><span class="learn-more-link-wrapper"></span></span></h3><div><button class="context-menu-button icon" title="მენიუს გახსნა"><span class="sr-only"><span>გვერდის ნაწილების პარამეტრები</span></span></button></div></div><div class="section-body"><ul class="section-list" style="padding:0"></ul></div></section></div><div class="prefs-button"><button class="icon icon-settings" title="მოირგეთ ახალი ჩანართის გვერდი"></button></div></div></main></div></div></div>
     <div id="footer-asrouter-container" role="presentation"></div>
   </body>
 </html>
--- a/browser/components/newtab/prerendered/locales/ka/activity-stream-prerendered.html
+++ b/browser/components/newtab/prerendered/locales/ka/activity-stream-prerendered.html
@@ -5,17 +5,17 @@
     <meta http-equiv="Content-Security-Policy" content="default-src 'none'; object-src 'none'; script-src resource: chrome:; connect-src https:; img-src https: data: blob:; style-src 'unsafe-inline';">
     <title>ახალი ჩანართი</title>
     <link rel="icon" type="image/png" href="chrome://branding/content/icon32.png"/>
     <link rel="stylesheet" href="chrome://browser/content/contentSearchUI.css" />
     <link rel="stylesheet" href="resource://activity-stream/css/activity-stream.css" />
   </head>
   <body class="activity-stream">
     <div id="header-asrouter-container" role="presentation"></div>
-    <div id="root"><div><div class="outer-wrapper fixed-to-top"><main><div class="non-collapsible-section"><div class="search-wrapper"><div class="search-inner-wrapper"><label for="newtab-search-text" class="search-label"><span class="sr-only"><span>ინტერნეტში ძიება</span></span></label><input type="search" id="newtab-search-text" maxLength="256" placeholder="ინტერნეტში ძიება" title="ინტერნეტში ძიება"/><button id="searchSubmit" class="search-button" title="ძიება"><span class="sr-only"><span>ძიება</span></span></button></div></div></div><div class="body-wrapper"><div class="sections-list"><section class="collapsible-section top-sites animation-enabled" data-section-id="topsites"><div class="section-top-bar"><h3 class="section-title"><span class="click-target-container"><span class="click-target"><span class="icon icon-small-spacer icon-topsites"></span><span>რჩეული საიტები</span></span><span class="click-target"></span><span class="learn-more-link-wrapper"></span></span></h3><div><button class="context-menu-button icon" title="მენიუს გახსნა"><span class="sr-only"><span>გვერდის ნაწილების პარამეტრები</span></span></button></div></div><div class="section-body"><ul class="top-sites-list"><li class="top-site-outer placeholder "><div class="top-site-inner"><a tabindex="0" draggable="true"><div class="tile" aria-hidden="true"><div class="screenshot" style="background-image:none"></div></div><div class="title "><span dir="auto"></span></div></a><button class="context-menu-button edit-button icon" title="საიტის ჩასწორება"></button></div></li><li class="top-site-outer placeholder "><div class="top-site-inner"><a tabindex="0" draggable="true"><div class="tile" aria-hidden="true"><div class="screenshot" style="background-image:none"></div></div><div class="title "><span dir="auto"></span></div></a><button class="context-menu-button edit-button icon" title="საიტის ჩასწორება"></button></div></li><li class="top-site-outer placeholder "><div class="top-site-inner"><a tabindex="0" draggable="true"><div class="tile" aria-hidden="true"><div class="screenshot" style="background-image:none"></div></div><div class="title "><span dir="auto"></span></div></a><button class="context-menu-button edit-button icon" title="საიტის ჩასწორება"></button></div></li><li class="top-site-outer placeholder "><div class="top-site-inner"><a tabindex="0" draggable="true"><div class="tile" aria-hidden="true"><div class="screenshot" style="background-image:none"></div></div><div class="title "><span dir="auto"></span></div></a><button class="context-menu-button edit-button icon" title="საიტის ჩასწორება"></button></div></li><li class="top-site-outer placeholder "><div class="top-site-inner"><a tabindex="0" draggable="true"><div class="tile" aria-hidden="true"><div class="screenshot" style="background-image:none"></div></div><div class="title "><span dir="auto"></span></div></a><button class="context-menu-button edit-button icon" title="საიტის ჩასწორება"></button></div></li><li class="top-site-outer placeholder "><div class="top-site-inner"><a tabindex="0" draggable="true"><div class="tile" aria-hidden="true"><div class="screenshot" style="background-image:none"></div></div><div class="title "><span dir="auto"></span></div></a><button class="context-menu-button edit-button icon" title="საიტის ჩასწორება"></button></div></li><li class="top-site-outer placeholder hide-for-narrow"><div class="top-site-inner"><a tabindex="0" draggable="true"><div class="tile" aria-hidden="true"><div class="screenshot" style="background-image:none"></div></div><div class="title "><span dir="auto"></span></div></a><button class="context-menu-button edit-button icon" title="საიტის ჩასწორება"></button></div></li><li class="top-site-outer placeholder hide-for-narrow"><div class="top-site-inner"><a tabindex="0" draggable="true"><div class="tile" aria-hidden="true"><div class="screenshot" style="background-image:none"></div></div><div class="title "><span dir="auto"></span></div></a><button class="context-menu-button edit-button icon" title="საიტის ჩასწორება"></button></div></li></ul><div class="edit-topsites-wrapper"></div></div></section><section class="collapsible-section section normal-cards animation-enabled" data-section-id="topstories"><div class="section-top-bar"><h3 class="section-title"><span class="click-target-container"><span class="click-target"><span class="icon icon-small-spacer icon-pocket"></span><span>Pocket-ის შემოთავაზებული</span></span><span class="click-target"></span><span class="learn-more-link-wrapper"></span></span></h3><div><button class="context-menu-button icon" title="მენიუს გახსნა"><span class="sr-only"><span>გვერდის ნაწილების პარამეტრები</span></span></button></div></div><div class="section-body"><ul class="section-list" style="padding:0"></ul><div class="top-stories-bottom-container"><div class="wrapper-more-recommendations"></div></div></div></section><section class="collapsible-section section normal-cards animation-enabled" data-section-id="highlights"><div class="section-top-bar"><h3 class="section-title"><span class="click-target-container"><span class="click-target"><span class="icon icon-small-spacer icon-highlights"></span><span>მნიშვნელოვანი</span></span><span class="click-target"></span><span class="learn-more-link-wrapper"></span></span></h3><div><button class="context-menu-button icon" title="მენიუს გახსნა"><span class="sr-only"><span>გვერდის ნაწილების პარამეტრები</span></span></button></div></div><div class="section-body"><ul class="section-list" style="padding:0"></ul></div></section></div><div class="prefs-button"><button class="icon icon-settings" title="მოირგეთ ახალი ჩანართის გვერდი"></button></div></div></main></div></div></div>
+    <div id="root"><div><div class="outer-wrapper fixed-to-top"><main><div class="non-collapsible-section"><div class="search-wrapper"><div class="search-inner-wrapper"><label for="newtab-search-text" class="search-label"><span class="sr-only"><span>ინტერნეტში ძიება</span></span></label><input type="search" id="newtab-search-text" maxLength="256" placeholder="ინტერნეტში ძიება" title="ინტერნეტში ძიება"/><button id="searchSubmit" class="search-button" title="ძიება"><span class="sr-only"><span>ძიება</span></span></button></div></div></div><div class="body-wrapper"><div class="sections-list"><section class="collapsible-section top-sites animation-enabled" data-section-id="topsites"><div class="section-top-bar"><h3 class="section-title"><span class="click-target-container"><span class="click-target"><span class="icon icon-small-spacer icon-topsites"></span><span>რჩეული საიტები</span></span><span class="click-target"></span><span class="learn-more-link-wrapper"></span></span></h3><div><button class="context-menu-button icon" title="მენიუს გახსნა"><span class="sr-only"><span>გვერდის ნაწილების პარამეტრები</span></span></button></div></div><div class="section-body"><ul class="top-sites-list"><li class="top-site-outer placeholder "><div class="top-site-inner"><a tabindex="0" draggable="true"><div class="tile" aria-hidden="true"><div class="screenshot" style="background-image:none"></div></div><div class="title "><span dir="auto"></span></div></a><button class="context-menu-button edit-button icon" title="საიტის ჩასწორება"></button></div></li><li class="top-site-outer placeholder "><div class="top-site-inner"><a tabindex="0" draggable="true"><div class="tile" aria-hidden="true"><div class="screenshot" style="background-image:none"></div></div><div class="title "><span dir="auto"></span></div></a><button class="context-menu-button edit-button icon" title="საიტის ჩასწორება"></button></div></li><li class="top-site-outer placeholder "><div class="top-site-inner"><a tabindex="0" draggable="true"><div class="tile" aria-hidden="true"><div class="screenshot" style="background-image:none"></div></div><div class="title "><span dir="auto"></span></div></a><button class="context-menu-button edit-button icon" title="საიტის ჩასწორება"></button></div></li><li class="top-site-outer placeholder "><div class="top-site-inner"><a tabindex="0" draggable="true"><div class="tile" aria-hidden="true"><div class="screenshot" style="background-image:none"></div></div><div class="title "><span dir="auto"></span></div></a><button class="context-menu-button edit-button icon" title="საიტის ჩასწორება"></button></div></li><li class="top-site-outer placeholder "><div class="top-site-inner"><a tabindex="0" draggable="true"><div class="tile" aria-hidden="true"><div class="screenshot" style="background-image:none"></div></div><div class="title "><span dir="auto"></span></div></a><button class="context-menu-button edit-button icon" title="საიტის ჩასწორება"></button></div></li><li class="top-site-outer placeholder "><div class="top-site-inner"><a tabindex="0" draggable="true"><div class="tile" aria-hidden="true"><div class="screenshot" style="background-image:none"></div></div><div class="title "><span dir="auto"></span></div></a><button class="context-menu-button edit-button icon" title="საიტის ჩასწორება"></button></div></li><li class="top-site-outer placeholder hide-for-narrow"><div class="top-site-inner"><a tabindex="0" draggable="true"><div class="tile" aria-hidden="true"><div class="screenshot" style="background-image:none"></div></div><div class="title "><span dir="auto"></span></div></a><button class="context-menu-button edit-button icon" title="საიტის ჩასწორება"></button></div></li><li class="top-site-outer placeholder hide-for-narrow"><div class="top-site-inner"><a tabindex="0" draggable="true"><div class="tile" aria-hidden="true"><div class="screenshot" style="background-image:none"></div></div><div class="title "><span dir="auto"></span></div></a><button class="context-menu-button edit-button icon" title="საიტის ჩასწორება"></button></div></li></ul><div class="edit-topsites-wrapper"></div></div></section><section class="collapsible-section section normal-cards animation-enabled" data-section-id="topstories"><div class="section-top-bar"><h3 class="section-title"><span class="click-target-container"><span class="click-target"><span class="icon icon-small-spacer icon-pocket"></span><span>Pocket გირჩევთ</span></span><span class="click-target"></span><span class="learn-more-link-wrapper"></span></span></h3><div><button class="context-menu-button icon" title="მენიუს გახსნა"><span class="sr-only"><span>გვერდის ნაწილების პარამეტრები</span></span></button></div></div><div class="section-body"><ul class="section-list" style="padding:0"></ul><div class="top-stories-bottom-container"><div class="wrapper-more-recommendations"></div></div></div></section><section class="collapsible-section section normal-cards animation-enabled" data-section-id="highlights"><div class="section-top-bar"><h3 class="section-title"><span class="click-target-container"><span class="click-target"><span class="icon icon-small-spacer icon-highlights"></span><span>მნიშვნელოვანი</span></span><span class="click-target"></span><span class="learn-more-link-wrapper"></span></span></h3><div><button class="context-menu-button icon" title="მენიუს გახსნა"><span class="sr-only"><span>გვერდის ნაწილების პარამეტრები</span></span></button></div></div><div class="section-body"><ul class="section-list" style="padding:0"></ul></div></section></div><div class="prefs-button"><button class="icon icon-settings" title="მოირგეთ ახალი ჩანართის გვერდი"></button></div></div></main></div></div></div>
     <div id="footer-asrouter-container" role="presentation"></div>
     <script src="resource://activity-stream/prerendered/static/activity-stream-initial-state.js"></script>
     <script src="chrome://browser/content/contentSearchUI.js"></script>
     <script src="chrome://browser/content/contentTheme.js"></script>
     <script src="resource://activity-stream/vendor/react.js"></script>
     <script src="resource://activity-stream/vendor/react-dom.js"></script>
     <script src="resource://activity-stream/vendor/prop-types.js"></script>
     <script src="resource://activity-stream/vendor/react-intl.js"></script>
--- a/browser/components/newtab/prerendered/locales/ka/activity-stream-strings.js
+++ b/browser/components/newtab/prerendered/locales/ka/activity-stream-strings.js
@@ -1,14 +1,14 @@
 // Note - this is a generated ka file.
 window.gActivityStreamStrings = {
   "newtab_page_title": "ახალი ჩანართი",
   "header_top_sites": "რჩეული საიტები",
   "header_highlights": "მნიშვნელოვანი",
-  "header_recommended_by": "{provider}-ის შემოთავაზებული",
+  "header_recommended_by": "{provider} გირჩევთ",
   "context_menu_button_sr": "კონტექსტური მენიუს გახსნა {title}",
   "section_context_menu_button_sr": "გვერდის ნაწილების პარამეტრები",
   "type_label_visited": "მონახულებული",
   "type_label_bookmarked": "ჩანიშნული",
   "type_label_recommended": "პოპულარული",
   "type_label_pocket": "შენახულია Pocket-ში",
   "type_label_downloaded": "ჩამოტვირთული",
   "menu_action_bookmark": "ჩანიშვნა",
@@ -75,17 +75,17 @@ window.gActivityStreamStrings = {
   "topsites_form_image_validation": "სურათი ვერ ჩაიტვირთა. სცადეთ სხვა URL-ბმული.",
   "pocket_read_more": "პოპულარული თემები:",
   "pocket_read_even_more": "მეტი სიახლის ნახვა",
   "pocket_more_reccommendations": "მეტი შემოთავაზებები",
   "pocket_how_it_works": "როგორ მუშაობს",
   "pocket_cta_button": "გამოიყენეთ Pocket",
   "pocket_cta_text": "გადაინახეთ სასურველი შიგთავსი Pocket-ში და მიეცით გონებას საკვები, შთამბეჭდავი საკითხავი მასალის სახით.",
   "highlights_empty_state": "დაიწყეთ გვერდების დათვალიერება და აქ გამოჩნდება თქვენთვის სასურველი სტატიები, ვიდეოები და ბოლოს მონახულებული ან ჩანიშნული საიტები.",
-  "topstories_empty_state": "უკვე ყველაფერი წაკითხული გაქვთ. {provider}-იდან ახალი რჩეული სტატიების მისაღებად, მოგვიანებით შემოიარეთ. თუ ვერ ითმენთ, აირჩიეთ რომელიმე მოთხოვნადი თემა, ახალი საინტერესო სტატიების მოსაძიებლად.",
+  "topstories_empty_state": "უკვე ყველაფერს გაეცანით. მოგვიანებით შემოიარეთ მეტი რჩეული სტატიის სანახავად, რომელსაც {provider} მოგაწვდით. ვერ ითმენთ? აირჩიეთ რომელიმე ფართოდ გავრცელებული საკითხი, ახალი საინტერესო სტატიების მოსაძიებლად.",
   "error_fallback_default_info": "სამწუხაროდ, შიგთავსის ჩატვირთვისას რაღაც ხარვეზი წარმოიქმნა.",
   "error_fallback_default_refresh_suggestion": "განაახლეთ გვერდი და სცადეთ ხელახლა.",
   "section_menu_action_remove_section": "ამ ნაწილის მოცილება",
   "section_menu_action_collapse_section": "ამ ნაწილის აკეცვა",
   "section_menu_action_expand_section": "ამ ნაწილის გაშლა",
   "section_menu_action_manage_section": "გვერდის ნაწილების მართვა",
   "section_menu_action_manage_webext": "გაფართოების მართვა",
   "section_menu_action_add_topsite": "რჩეული საიტის დამატება",
--- a/browser/components/newtab/prerendered/locales/lt/activity-stream-strings.js
+++ b/browser/components/newtab/prerendered/locales/lt/activity-stream-strings.js
@@ -90,17 +90,17 @@ window.gActivityStreamStrings = {
   "section_menu_action_manage_webext": "Tvarkyti priedą",
   "section_menu_action_add_topsite": "Pridėti lankomą svetainę",
   "section_menu_action_add_search_engine": "Pridėti ieškyklę",
   "section_menu_action_move_up": "Pakelti",
   "section_menu_action_move_down": "Nuleisti",
   "section_menu_action_privacy_notice": "Privatumo pranešimas",
   "firstrun_title": "Pasiimkite „Firefox“ su savimi",
   "firstrun_content": "Turėkite savo adresyną, žurnalą, slaptažodžius ir kitas nuostatas visuose savo įrenginiuose.",
-  "firstrun_learn_more_link": "Sužinokite daugiau apie „Firefox“ paskyras",
+  "firstrun_learn_more_link": "Sužinokite apie „Firefox“ paskyras daugiau",
   "firstrun_form_header": "Įveskite savo el. paštą",
   "firstrun_form_sub_header": "norėdami tęsti su „Firefox Sync“.",
   "firstrun_email_input_placeholder": "El. paštas",
   "firstrun_invalid_input": "Reikalingas galiojantis el. pašto adresas",
   "firstrun_extra_legal_links": "Tęsdami sutinkate su {terms} ir {privacy}.",
   "firstrun_terms_of_service": "paslaugos teikimo nuostatais",
   "firstrun_privacy_notice": "Privatumo pranešimu",
   "firstrun_continue_to_login": "Tęsti",
--- a/browser/components/newtab/prerendered/locales/vi/activity-stream-strings.js
+++ b/browser/components/newtab/prerendered/locales/vi/activity-stream-strings.js
@@ -15,17 +15,17 @@ window.gActivityStreamStrings = {
   "menu_action_remove_bookmark": "Xóa đánh dấu",
   "menu_action_open_new_window": "Mở trong cửa sổ mới",
   "menu_action_open_private_window": "Mở trong cửa sổ riêng tư mới",
   "menu_action_dismiss": "Bỏ qua",
   "menu_action_delete": "Xóa khỏi lịch sử",
   "menu_action_pin": "Ghim",
   "menu_action_unpin": "Bỏ ghim",
   "confirm_history_delete_p1": "Bạn có chắc bạn muốn xóa bỏ mọi thứ của trang này từ lịch sử?",
-  "confirm_history_delete_notice_p2": "Hành động này không thể hoàn tác.",
+  "confirm_history_delete_notice_p2": "Thao tác này không thể hoàn tác được.",
   "menu_action_save_to_pocket": "Lưu vào Pocket",
   "menu_action_delete_pocket": "Xóa khỏi Pocket",
   "menu_action_archive_pocket": "Lưu trữ trong Pocket",
   "menu_action_show_file_mac_os": "Hiển thị trong Finder",
   "menu_action_show_file_windows": "Mở thư mục chứa",
   "menu_action_show_file_linux": "Mở thư mục chứa",
   "menu_action_show_file_default": "Hiện tập tin",
   "menu_action_open_file": "Mở tập tin",
--- a/browser/components/newtab/test/unit/asrouter/ASRouter.test.js
+++ b/browser/components/newtab/test/unit/asrouter/ASRouter.test.js
@@ -230,16 +230,30 @@ describe("ASRouter", () => {
       assert.calledOnce(ASRouterPreferences.init);
       assert.calledWith(ASRouterPreferences.addListener, Router.onPrefChange);
     });
     it("should call ASRouterPreferences.uninit and remove the listener on uninit", () => {
       Router.uninit();
       assert.calledOnce(ASRouterPreferences.uninit);
       assert.calledWith(ASRouterPreferences.removeListener, Router.onPrefChange);
     });
+    it("should send a AS_ROUTER_TARGETING_UPDATE message", async () => {
+      const messageTargeted = {id: "1", campaign: "foocampaign", targeting: "true"};
+      const messageNotTargeted = {id: "2", campaign: "foocampaign"};
+      await Router.setState({messages: [messageTargeted, messageNotTargeted]});
+      sandbox.stub(ASRouterTargeting, "isMatch").resolves(false);
+
+      await Router.onPrefChange("services.sync.username");
+
+      assert.calledOnce(channel.sendAsyncMessage);
+      const [, {type, data}] = channel.sendAsyncMessage.firstCall.args;
+      assert.equal(type, "AS_ROUTER_TARGETING_UPDATE");
+      assert.equal(data[0], messageTargeted.id);
+      assert.lengthOf(data, 1);
+    });
     it("should call loadMessagesFromAllProviders on pref change", () => {
       sandbox.spy(Router, "loadMessagesFromAllProviders");
 
       ASRouterPreferences.observe(null, null, MESSAGE_PROVIDER_PREF_NAME);
 
       assert.calledOnce(Router.loadMessagesFromAllProviders);
     });
     it("should update the list of providers on pref change", () => {
--- a/browser/components/newtab/test/unit/asrouter/ASRouterPreferences.test.js
+++ b/browser/components/newtab/test/unit/asrouter/ASRouterPreferences.test.js
@@ -8,18 +8,19 @@ const CFR_USER_PREF_ADDONS = "browser.ne
 const CFR_USER_PREF_FEATURES = "browser.newtabpage.activity-stream.asrouter.userprefs.cfr.features";
 
 /** NUMBER_OF_PREFS_TO_OBSERVE includes:
  *  1. asrouter.providers. pref branch
  *  2. asrouter.devtoolsEnabled
  *  3. browser.newtabpage.activity-stream.feeds.snippets (user preference - snippets)
  *  4. browser.newtabpage.activity-stream.asrouter.userprefs.cfr.addons (user preference - cfr)
  *  4. browser.newtabpage.activity-stream.asrouter.userprefs.cfr.features (user preference - cfr)
+ *  5. services.sync.username
  */
-const NUMBER_OF_PREFS_TO_OBSERVE = 5;
+const NUMBER_OF_PREFS_TO_OBSERVE = 6;
 
 describe("ASRouterPreferences", () => {
   let ASRouterPreferences;
   let sandbox;
   let addObserverStub;
   let stringPrefStub;
   let boolPrefStub;
 
--- a/browser/components/newtab/test/unit/asrouter/asrouter-content.test.jsx
+++ b/browser/components/newtab/test/unit/asrouter/asrouter-content.test.jsx
@@ -299,10 +299,23 @@ describe("ASRouterUISurface", () => {
       assert.calledOnce(ASRouterUtils.sendTelemetry);
       const [payload] = ASRouterUtils.sendTelemetry.firstCall.args;
 
       assert.propertyVal(payload, "message_id", FAKE_MESSAGE.id);
       assert.propertyVal(payload, "event", "IMPRESSION");
       assert.propertyVal(payload, "action", `${FAKE_MESSAGE.provider}_user_event`);
       assert.propertyVal(payload, "source", "NEWTAB_FOOTER_BAR");
     });
+
+    it("should call .sendTelemetry with the right message data when a bundle is dismissed", () => {
+      wrapper.instance().dismissBundle([{id: 1}, {id: 2}, {id: 3}])();
+
+      assert.calledOnce(ASRouterUtils.sendTelemetry);
+      assert.calledWith(ASRouterUtils.sendTelemetry, {
+        action: "onboarding_user_event",
+        event: "DISMISS",
+        id: "onboarding-cards",
+        message_id: "1,2,3",
+        source: "onboarding-cards",
+      });
+    });
   });
 });
--- a/browser/components/newtab/test/unit/asrouter/templates/Trailhead.test.jsx
+++ b/browser/components/newtab/test/unit/asrouter/templates/Trailhead.test.jsx
@@ -31,16 +31,19 @@ describe("<Trailhead>", () => {
     dispatch = sandbox.stub();
     onAction = sandbox.stub();
     sandbox.stub(global, "fetch")
       .resolves({ok: true, status: 200, json: () => Promise.resolve({flowId: 123, flowBeginTime: 456})});
 
     dummyNode = document.createElement("body");
     sandbox.stub(dummyNode, "querySelector").returns(dummyNode);
     const fakeDocument = {
+      get activeElement() {
+        return dummyNode;
+      },
       get body() {
         return dummyNode;
       },
       getElementById() {
         return dummyNode;
       },
     };
 
@@ -63,19 +66,30 @@ describe("<Trailhead>", () => {
     assert.calledWith(dispatch, ac.UserEvent({event: at.SKIPPED_SIGNIN, value: {has_flow_params: false}}));
   });
 
   it("should NOT emit UserEvent SKIPPED_SIGNIN when closeModal is triggered by visibilitychange event", () => {
     wrapper.instance().closeModal({type: "visibilitychange"});
     assert.notCalled(dispatch);
   });
 
-  it("should emit UserEvent SUBMIT_EMAIL when you submit the form", () => {
+  it("should prevent submissions with no email", () => {
+    const form = wrapper.find("form");
+    const preventDefault = sandbox.stub();
+
+    form.simulate("submit", {preventDefault});
+
+    assert.calledOnce(preventDefault);
+    assert.notCalled(dispatch);
+  });
+  it("should emit UserEvent SUBMIT_EMAIL when you submit a valid email", () => {
     let form = wrapper.find("form");
     assert.ok(form.exists());
+    form.getDOMNode().elements.email.value = "a@b.c";
+
     form.simulate("submit");
 
     assert.calledOnce(dispatch);
     assert.isUserEventAction(dispatch.firstCall.args[0]);
     assert.calledWith(dispatch, ac.UserEvent({event: at.SUBMIT_EMAIL, value: {has_flow_params: false}}));
   });
 
   it("should add utm_* query params to card actions", () => {
--- a/browser/components/newtab/test/unit/content-src/components/ReturnToAMO.test.jsx
+++ b/browser/components/newtab/test/unit/content-src/components/ReturnToAMO.test.jsx
@@ -1,22 +1,84 @@
 import {mountWithIntl} from "test/unit/utils";
 import React from "react";
 import {ReturnToAMO} from "content-src/asrouter/templates/ReturnToAMO/ReturnToAMO";
 
 describe("<ReturnToAMO>", () => {
   let dispatch;
   let onReady;
+  let sandbox;
+  let wrapper;
+  let sendUserActionTelemetryStub;
+  let content;
   beforeEach(() => {
-    dispatch = sinon.stub();
-    onReady = sinon.stub();
-    const content = {
+    sandbox = sinon.createSandbox();
+    dispatch = sandbox.stub();
+    onReady = sandbox.stub();
+    sendUserActionTelemetryStub = sandbox.stub();
+    content = {
       primary_button: {},
       secondary_button: {},
     };
+  });
 
-    mountWithIntl(<ReturnToAMO onReady={onReady} dispatch={dispatch} content={content} />);
+  afterEach(() => {
+    sandbox.restore();
+  });
+
+  describe("not mounted", () => {
+    it("should send an IMPRESSION on mount", () => {
+      assert.notCalled(sendUserActionTelemetryStub);
+
+      wrapper = mountWithIntl(<ReturnToAMO onReady={onReady}
+        dispatch={dispatch}
+        content={content}
+        onBlock={sandbox.stub()}
+        onAction={sandbox.stub()}
+        UISurface="NEWTAB_OVERLAY"
+        sendUserActionTelemetry={sendUserActionTelemetryStub} />);
+
+      assert.calledOnce(sendUserActionTelemetryStub);
+      assert.calledWithExactly(sendUserActionTelemetryStub, {
+        event: "IMPRESSION",
+        id: wrapper.instance().props.UISurface,
+      });
+    });
   });
 
-  it("should call onReady on componentDidMount", () => {
-    assert.calledOnce(onReady);
+  describe("mounted", () => {
+    beforeEach(() => {
+      wrapper = mountWithIntl(<ReturnToAMO onReady={onReady}
+        dispatch={dispatch}
+        content={content}
+        onBlock={sandbox.stub()}
+        onAction={sandbox.stub()}
+        UISurface="NEWTAB_OVERLAY"
+        sendUserActionTelemetry={sendUserActionTelemetryStub} />);
+
+      // Clear the IMPRESSION ping
+      sendUserActionTelemetryStub.reset();
+    });
+
+    it("should call onReady on componentDidMount", () => {
+      assert.calledOnce(onReady);
+    });
+
+    it("should send telemetry on block", () => {
+      wrapper.instance().onBlockButton();
+
+      assert.calledOnce(sendUserActionTelemetryStub);
+      assert.calledWithExactly(sendUserActionTelemetryStub, {
+        event: "BLOCK",
+        id: wrapper.instance().props.UISurface,
+      });
+    });
+
+    it("should send telemetry on install", () => {
+      wrapper.instance().onClickAddExtension();
+
+      assert.calledWithExactly(sendUserActionTelemetryStub, {
+        event: "INSTALL",
+        id: wrapper.instance().props.UISurface,
+      });
+    });
   });
 });
--- a/browser/components/newtab/test/unit/lib/AboutPreferences.test.js
+++ b/browser/components/newtab/test/unit/lib/AboutPreferences.test.js
@@ -108,17 +108,16 @@ describe("AboutPreferences Feed", () => 
 
       await instance.observe(window, PREFERENCES_LOADED_EVENT);
 
       assert.calledOnce(stub);
       assert.equal(stub.firstCall.args[2][0].id, "search");
       assert.equal(stub.firstCall.args[2][1].id, "topsites");
       assert.equal(stub.firstCall.args[2][2].id, "topstories");
       assert.isEmpty(stub.firstCall.args[2][2].rowsPref);
-      assert.equal(stub.firstCall.args[2][2].pref.descString.id, "prefs_content_discovery_description");
     });
   });
   describe("#strings", () => {
     let activityStreamLocale;
     let fetchStub;
     let fetchText;
     beforeEach(() => {
       global.Cc["@mozilla.org/browser/aboutnewtab-service;1"] = {
--- a/browser/components/newtab/test/unit/lib/DiscoveryStreamFeed.test.js
+++ b/browser/components/newtab/test/unit/lib/DiscoveryStreamFeed.test.js
@@ -1200,29 +1200,16 @@ describe("DiscoveryStreamFeed", () => {
       assert.calledWithMatch(feed.store.dispatch, {
         data: {
           name: CONFIG_PREF_NAME,
           value: JSON.stringify({enabled: true, other: "value", layout_endpoint: "foo.com"}),
         },
         type: at.SET_PREF,
       });
     });
-    it("should disable opt-out when setting config enabled", () => {
-      sandbox.spy(feed.store, "dispatch");
-
-      feed.onAction({type: at.DISCOVERY_STREAM_CONFIG_SET_VALUE, data: {name: "enabled", value: true}});
-
-      assert.calledWithMatch(feed.store.dispatch, {
-        data: {
-          name: "discoverystream.optOut.0",
-          value: false,
-        },
-        type: at.SET_PREF,
-      });
-    });
   });
 
   describe("#onAction: DISCOVERY_STREAM_CONFIG_CHANGE", () => {
     it("should call this.loadLayout if config.enabled changes to true ", async () => {
       sandbox.stub(feed.cache, "set").returns(Promise.resolve());
       // First initialize
       await feed.onAction({type: at.INIT});
       assert.isFalse(feed.loaded);
@@ -1279,55 +1266,32 @@ describe("DiscoveryStreamFeed", () => {
       await feed.onAction({type: at.DISCOVERY_STREAM_CONFIG_CHANGE});
 
       assert.notCalled(feed.loadLayout);
       assert.calledOnce(feed.resetCache);
       assert.isFalse(feed.loaded);
     });
   });
 
-  describe("#onAction: DISCOVERY_STREAM_OPT_OUT", () => {
-    it("should update opt-out pref", async () => {
-      sandbox.spy(feed.store, "dispatch");
-
-      await feed.onAction({type: at.DISCOVERY_STREAM_OPT_OUT});
-
-      assert.calledWithMatch(feed.store.dispatch, {
-        data: {
-          name: "discoverystream.optOut.0",
-          value: true,
-        },
-        type: at.SET_PREF,
-      });
-    });
-  });
-
   describe("#onAction: UNINIT", () => {
     it("should reset pref cache", async () => {
       feed._prefCache = {cached: "value"};
 
       await feed.onAction({type: at.UNINIT});
 
       assert.deepEqual(feed._prefCache, {});
     });
   });
 
   describe("#onAction: PREF_CHANGED", () => {
     it("should update state.DiscoveryStream.config when the pref changes", async () => {
       setPref(CONFIG_PREF_NAME, {enabled: true, show_spocs: false, layout_endpoint: "foo"});
 
       assert.deepEqual(feed.store.getState().DiscoveryStream.config, {enabled: true, show_spocs: false, layout_endpoint: "foo"});
     });
-    it("should handle pref changes when opt out changes", async () => {
-      setPref(CONFIG_PREF_NAME, {enabled: true, show_spocs: false, layout_endpoint: "foo"});
-
-      setPref("discoverystream.optOut.0", true);
-
-      assert.deepEqual(feed.store.getState().DiscoveryStream.config, {enabled: false, show_spocs: false, layout_endpoint: "foo"});
-    });
     it("should fire loadSpocs is showSponsored pref changes", async () => {
       sandbox.stub(feed, "loadSpocs").returns(Promise.resolve());
 
       await feed.onAction({type: at.PREF_CHANGED, data: {name: "showSponsored"}});
 
       assert.calledOnce(feed.loadSpocs);
     });
   });
--- a/browser/components/newtab/test/unit/lib/TopStoriesFeed.test.js
+++ b/browser/components/newtab/test/unit/lib/TopStoriesFeed.test.js
@@ -86,38 +86,135 @@ describe("Top Stories Feed", () => {
     instance.store = {getState() { return {Prefs: {values: {showSponsored: true}}}; }, dispatch: sinon.spy()};
     instance.storiesLastUpdated = 0;
     instance.topicsLastUpdated = 0;
   });
   afterEach(() => {
     globals.restore();
     clock.restore();
   });
+
+  describe("#lazyloading TopStories", () => {
+    beforeEach(() => {
+      instance.discoveryStreamEnabled = true;
+    });
+    it("should bind parseOptions to SectionsManager.onceInitialized when discovery stream is true", () => {
+      instance.discoveryStreamEnabled = false;
+      instance.onAction({type: at.PREFS_INITIAL_VALUES, data: {"discoverystream.config": JSON.stringify({enabled: true})}});
+      assert.calledOnce(sectionsManagerStub.onceInitialized);
+    });
+    it("should bind parseOptions to SectionsManager.onceInitialized when discovery stream is false", () => {
+      instance.onAction({type: at.PREFS_INITIAL_VALUES, data: {"discoverystream.config": JSON.stringify({enabled: false})}});
+      assert.calledOnce(sectionsManagerStub.onceInitialized);
+    });
+    it("Should initialize properties once while lazy loading if not initialized earlier", () => {
+      instance.discoveryStreamEnabled = false;
+      instance.propertiesInitialized = false;
+      sinon.stub(instance, "initializeProperties");
+      instance.lazyLoadTopStories();
+      assert.calledOnce(instance.initializeProperties);
+    });
+    it("should not re-initialize properties", () => {
+      // For discovery stream experience disabled TopStoriesFeed properties
+      // are initialized in constructor and should not be called again while lazy loading topstories
+      sinon.stub(instance, "initializeProperties");
+      instance.discoveryStreamEnabled = false;
+      instance.propertiesInitialized = true;
+      instance.lazyLoadTopStories();
+      assert.notCalled(instance.initializeProperties);
+    });
+    it("should have early exit onInit when discovery is true", async () => {
+      sinon.stub(instance, "doContentUpdate");
+      await instance.onInit();
+      assert.notCalled(instance.doContentUpdate);
+      assert.isUndefined(instance.storiesLoaded);
+    });
+    it("should complete onInit when discovery is false", async () => {
+      instance.discoveryStreamEnabled = false;
+      sinon.stub(instance, "doContentUpdate");
+      await instance.onInit();
+      assert.calledOnce(instance.doContentUpdate);
+      assert.isTrue(instance.storiesLoaded);
+    });
+    it("should handle limited actions when discoverystream is enabled", async () => {
+      sinon.spy(instance, "handleDisabled");
+      sinon.stub(instance, "getPocketState");
+      instance.onAction({type: at.PREFS_INITIAL_VALUES, data: {"discoverystream.config": JSON.stringify({enabled: true})}});
+      assert.calledOnce(instance.handleDisabled);
+
+      instance.onAction({type: at.NEW_TAB_REHYDRATED, meta: {fromTarget: {}}});
+      assert.notCalled(instance.getPocketState);
+    });
+    it("should handle NEW_TAB_REHYDRATED when discoverystream is disabled", async () => {
+      instance.discoveryStreamEnabled = false;
+      sinon.spy(instance, "handleDisabled");
+      sinon.stub(instance, "getPocketState");
+      instance.onAction({type: at.PREFS_INITIAL_VALUES, data: {"discoverystream.config": JSON.stringify({enabled: false})}});
+      assert.notCalled(instance.handleDisabled);
+
+      instance.onAction({type: at.NEW_TAB_REHYDRATED, meta: {fromTarget: {}}});
+      assert.calledOnce(instance.getPocketState);
+    });
+    it("should handle UNINIT when discoverystream is enabled", async () => {
+      sinon.stub(instance, "uninit");
+      instance.onAction({type: at.UNINIT});
+      assert.calledOnce(instance.uninit);
+    });
+    it("should fire init on PREF_CHANGED", () => {
+      sinon.stub(instance, "onInit");
+      instance.onAction({type: at.PREF_CHANGED, data: {name: "discoverystream.config", value: {}}});
+      assert.calledOnce(instance.onInit);
+    });
+    it("should not fire init on PREF_CHANGED if stories are loaded", () => {
+      sinon.stub(instance, "onInit");
+      sinon.spy(instance, "lazyLoadTopStories");
+      instance.storiesLoaded = true;
+      instance.onAction({type: at.PREF_CHANGED, data: {name: "discoverystream.config", value: {}}});
+      assert.calledOnce(instance.lazyLoadTopStories);
+      assert.notCalled(instance.onInit);
+    });
+    it("should fire init on PREF_CHANGED when discoverystream is disabled", () => {
+      instance.discoveryStreamEnabled = false;
+      sinon.stub(instance, "onInit");
+      instance.onAction({type: at.PREF_CHANGED, data: {name: "discoverystream.config", value: {}}});
+      assert.calledOnce(instance.onInit);
+    });
+    it("should not fire init on PREF_CHANGED when discoverystream is disabled and stories are loaded", () => {
+      instance.discoveryStreamEnabled = false;
+      sinon.stub(instance, "onInit");
+      sinon.spy(instance, "lazyLoadTopStories");
+      instance.storiesLoaded = true;
+      instance.onAction({type: at.PREF_CHANGED, data: {name: "discoverystream.config", value: {}}});
+      assert.calledOnce(instance.lazyLoadTopStories);
+      assert.notCalled(instance.onInit);
+    });
+  });
+
   describe("#init", () => {
     it("should create a TopStoriesFeed", () => {
       assert.instanceOf(instance, TopStoriesFeed);
     });
     it("should bind parseOptions to SectionsManager.onceInitialized", () => {
-      instance.onAction({type: at.INIT});
+      instance.onAction({type: at.PREFS_INITIAL_VALUES, data: {}});
       assert.calledOnce(sectionsManagerStub.onceInitialized);
     });
     it("should initialize endpoints based on options", async () => {
       await instance.onInit();
       assert.equal("https://somedomain.org/stories?key=test-api-key", instance.stories_endpoint);
       assert.equal("https://somedomain.org/referrer", instance.stories_referrer);
       assert.equal("https://somedomain.org/topics?key=test-api-key", instance.topics_endpoint);
     });
     it("should enable its section", () => {
-      instance.onAction({type: at.INIT});
+      instance.onAction({type: at.PREFS_INITIAL_VALUES, data: {}});
       assert.calledOnce(sectionsManagerStub.enableSection);
       assert.calledWith(sectionsManagerStub.enableSection, SECTION_ID);
     });
     it("init should fire onInit", () => {
       instance.onInit = sinon.spy();
-      instance.onAction({type: at.INIT});
+      instance.onAction({type: at.PREFS_INITIAL_VALUES, data: {}});
       assert.calledOnce(instance.onInit);
     });
     it("should fetch stories on init", async () => {
       instance.fetchStories = sinon.spy();
       await instance.onInit();
       assert.calledOnce(instance.fetchStories);
     });
     it("should fetch topics on init", async () => {
--- a/browser/themes/shared/controlcenter/panel.inc.css
+++ b/browser/themes/shared/controlcenter/panel.inc.css
@@ -8,16 +8,20 @@
 :-moz-any([when-connection],[when-mixedcontent],[when-ciphers],[when-loginforms]) {
   display: none;
 }
 
 #identity-popup {
   --identity-popup-width: 33rem;
 }
 
+#protections-popup {
+  --protections-popup-width: 33rem;
+}
+
 /* This is used by screenshots tests to hide intermittently different
  * identity popup shadows (see bug 1425253). */
 #identity-popup.no-shadow {
   -moz-window-shadow: none;
 }
 
 /* Show the right elements for the right connection states. */
 #identity-popup[connection=not-secure] [when-connection~=not-secure],
@@ -54,33 +58,40 @@
 #identity-popup-securityView-body:-moz-any([mixedcontent],[ciphers]) > description[when-connection=not-secure],
 /* Hide 'passive-loaded (only)' message when there is mixed passive content loaded and active blocked. */
 #identity-popup-securityView-body[mixedcontent~=passive-loaded][mixedcontent~=active-blocked] > description[when-mixedcontent=passive-loaded] {
   display: none;
 }
 
 /* Make sure hidden elements don't accidentally become visible from one of the
    above selectors (see Bug 1194258) */
-#identity-popup [hidden] {
+#identity-popup [hidden],
+#protections-popup [hidden] {
   display: none !important;
 }
 
-#identity-popup > .panel-arrowcontainer > .panel-arrowcontent {
+#identity-popup > .panel-arrowcontainer > .panel-arrowcontent,
+#protections-popup > .panel-arrowcontainer > .panel-arrowcontent {
   padding: 0;
   /* Set default fill for icons in the identity popup.
      Individual icons can override this. */
   fill: currentColor;
   fill-opacity: .6;
 }
 
 #identity-popup-mainView {
   min-width: var(--identity-popup-width);
   max-width: var(--identity-popup-width);
 }
 
+#protections-popup-mainView {
+  min-width: var(--protections-popup-width);
+  max-width: var(--protections-popup-width);
+}
+
 .identity-popup-section {
   border-top: 1px solid var(--panel-separator-color);
 }
 
 .identity-popup-security-content,
 #identity-popup-permissions-content,
 #identity-popup-content-blocking-content {
   background-repeat: no-repeat;
@@ -162,16 +173,17 @@
 
 .identity-popup-content-blocking-empty-label,
 #tracking-protection-preferences-button > .toolbarbutton-text,
 .tracking-protection-button,
 #identity-popup-trackersView-strict-info > label,
 .identity-popup-cookiesView-list-header,
 .identity-popup-content-blocking-list-item > label,
 #identity-popup-mainView-panel-header > label,
+#protections-popup-mainView-panel-header > label,
 #identity-popup-trackersView > .panel-header,
 #identity-popup-securityView > .panel-header,
 #identity-popup-breakageReportView > .panel-header,
 #identity-popup-content-blocking-report-breakage,
 .identity-popup-content-blocking-category-label,
 .identity-popup-content-blocking-category-state-label,
 .identity-popup-permission-label,
 .identity-popup-permission-state-label,
@@ -179,24 +191,35 @@
 #identity-popup-security-descriptions > description,
 #identity-popup-securityView-body > description,
 #identity-popup-permissions-content > description,
 #identity-popup-content-blocking-content > description {
   font-size: 110%;
   margin: 0;
 }
 
-#identity-popup-mainView-panel-header {
+#identity-popup-mainView-panel-header,
+#protections-popup-mainView-panel-header {
   padding: 4px 1em;
   min-height: 40px;
   -moz-box-pack: center;
   -moz-box-align: center;
 }
 
-#identity-popup-mainView-panel-header-span {
+#protections-popup > .panel-arrowcontainer > .panel-arrowbox > .panel-arrow {
+  fill: rgb(100, 70, 228);
+}
+
+#protections-popup-mainView-panel-header {
+  color: white;
+  background: linear-gradient(45deg, rgb(117, 67, 229), rgb(3, 96, 223));
+}
+
+#identity-popup-mainView-panel-header-span,
+#protections-popup-mainView-panel-header-span {
   display: inline-block;
   font-weight: 600;
   text-align: center;
   overflow-wrap: break-word;
   /* This is needed for the overflow-wrap to work correctly.
    * 33em is the panel width, panel-header has 1em padding on each side. */
   max-width: calc(var(--identity-popup-width) - 2em);
 }
--- a/build/moz.configure/bindgen.configure
+++ b/build/moz.configure/bindgen.configure
@@ -162,19 +162,19 @@ def bindgen_config_paths(llvm_config, li
 
         if host.os == 'OpenBSD':
             libclang_choices = glob.glob(path + '/libclang.so.*.*')
 
         # At least one of the choices must be found.
         for choice in libclang_choices:
             libclang = os.path.join(path, choice)
             if os.path.exists(libclang):
-                return (True, None)
+                return (libclang, None)
         else:
-            return (False, list(set(libclang_choices)))
+            return (None, list(set(libclang_choices)))
 
     # XXX: we want this code to be run for both Gecko and JS, but we don't
     # necessarily want to force a bindgen/Rust dependency on JS just yet.
     # Actually, we don't want to force an error if we're not building the
     # browser generally.  We therefore whitelist the projects that require
     # bindgen facilities at this point and leave it at that.
     bindgen_projects = ('browser', 'mobile/android')
 
@@ -214,26 +214,27 @@ def bindgen_config_paths(llvm_config, li
 
         if not os.path.exists(libclang_path):
             die(dedent('''\
             The directory {} returned by `llvm-config {}` does not exist.
             clang is required to build Firefox.  Please install the
             necessary packages, or run `mach bootstrap`.
             '''.format(libclang_path, libclang_arg)))
 
-        (found, searched) = search_for_libclang(libclang_path)
-        if not found:
+        (libclang, searched) = search_for_libclang(libclang_path)
+        if not libclang:
             die(dedent('''\
             Could not find the clang shared library in the path {}
             returned by `llvm-config {}` (searched for files {}).
             clang is required to build Firefox.  Please install the
             necessary packages, or run `mach bootstrap`.
             '''.format(libclang_path, libclang_arg, searched)))
 
         return namespace(
+            libclang=libclang,
             libclang_path=libclang_path,
             clang_path=clang_resolved,
         )
 
     if (not libclang_path and clang_path) or \
        (libclang_path and not clang_path):
         if build_project not in bindgen_projects:
             return namespace()
@@ -246,34 +247,56 @@ def bindgen_config_paths(llvm_config, li
     clang_path = clang_path[0]
 
     if not os.path.exists(libclang_path) or \
        not os.path.isdir(libclang_path):
         die(dedent('''\
         The argument to --with-libclang-path is not a directory: {}
         '''.format(libclang_path)))
 
-    (found, searched) = search_for_libclang(libclang_path)
-    if not found:
+    (libclang, searched) = search_for_libclang(libclang_path)
+    if not libclang:
         die(dedent('''\
         Could not find the clang shared library in the path {}
         specified by --with-libclang-path (searched for files {}).
         '''.format(libclang_path, searched)))
 
     clang_resolved = find_program(clang_path)
     if not clang_resolved:
         die(dedent('''\
         The argument to --with-clang-path is not a file: {}
         '''.format(clang_path)))
 
     return namespace(
+        libclang=libclang,
         libclang_path=libclang_path,
         clang_path=clang_resolved,
     )
 
+@depends(bindgen_config_paths.libclang)
+@checking('that libclang is new enough', lambda s: 'yes' if s else 'no')
+@imports(_from='ctypes', _import='CDLL')
+@imports(_from='textwrap', _import='dedent')
+def min_libclang_version(libclang):
+    try:
+        lib = CDLL(libclang)
+        # We want at least 4.0. The API we test below is enough for that.
+        # Just accessing it should throw if not found.
+        fun = lib.clang_EvalResult_getAsLongLong
+        return True
+    except:
+        die(dedent('''\
+        The libclang located at {} is too old (need at least 4.0).
+
+        Please make sure to update it or point to a newer libclang using
+        --with-libclang-path.
+        '''.format(libclang)))
+        return False
+
+
 set_config('MOZ_LIBCLANG_PATH', bindgen_config_paths.libclang_path)
 set_config('MOZ_CLANG_PATH', bindgen_config_paths.clang_path)
 
 
 @depends(target, target_is_unix, cxx_compiler, bindgen_cflags_android,
          bindgen_config_paths.clang_path, macos_sdk)
 def basic_bindgen_cflags(target, is_unix, compiler_info, android_cflags,
                          clang_path, macos_sdk):
--- a/build/moz.configure/toolchain.configure
+++ b/build/moz.configure/toolchain.configure
@@ -252,17 +252,18 @@ def ccache(value):
 
 
 ccache = check_prog('CCACHE', progs=(), input=ccache)
 
 js_option(env='CCACHE_PREFIX',
           nargs=1,
           help='Compiler prefix to use when using ccache')
 
-set_config('CCACHE_PREFIX', depends_if('CCACHE_PREFIX')(lambda prefix: prefix[0]))
+ccache_prefix = depends_if('CCACHE_PREFIX')(lambda prefix: prefix[0])
+set_config('CCACHE_PREFIX', ccache_prefix)
 
 # Distinguish ccache from sccache.
 
 
 @depends_if(ccache)
 def ccache_is_sccache(ccache):
     return check_cmd_output(ccache, '--version').startswith('sccache')
 
@@ -1162,22 +1163,25 @@ host_c_compiler = compiler('C', host, ot
 host_cxx_compiler = compiler('C++', host, c_compiler=host_c_compiler,
                              other_compiler=cxx_compiler,
                              other_c_compiler=c_compiler)
 
 # Generic compiler-based conditions.
 building_with_gcc = depends(c_compiler)(lambda info: info.type == 'gcc')
 
 
-@depends(cxx_compiler)
+@depends(cxx_compiler, ccache_prefix)
 @imports('os')
-def cxx_is_icecream(info):
+def cxx_is_icecream(info, ccache_prefix):
     if (os.path.islink(info.compiler) and os.path.basename(
             os.readlink(info.compiler)) == 'icecc'):
         return True
+    if ccache_prefix and os.path.basename(ccache_prefix) == 'icecc':
+        return True
+
 set_config('CXX_IS_ICECREAM', cxx_is_icecream)
 
 
 @depends(c_compiler)
 def msvs_version(info):
     # clang-cl emulates the same version scheme as cl. And MSVS_VERSION needs to
     # be set for GYP on Windows.
     if info.type == 'clang-cl':
--- a/devtools/client/debugger/bin/try-runner.js
+++ b/devtools/client/debugger/bin/try-runner.js
@@ -21,36 +21,36 @@ function execOut(...args) {
     out = execFileSync(...args);
   } catch (e) {
     out = e.stdout;
     err = e.stderr;
   }
   return { out: out.toString(), err: err && err.toString() };
 }
 
-function logErrors(text, regexp) {
+function logErrors(tool, text, regexp) {
   const errors = text.match(regexp) || [];
   for (const error of errors) {
-    console.log(`TEST-UNEXPECTED-FAIL | ${error}`);
+    console.log(`TEST-UNEXPECTED-FAIL ${tool} | ${error}`);
   }
   return errors;
 }
 
 function logStart(name) {
   console.log(`TEST START | ${name}`);
 }
 
 function runFlowJson() {
   const { out } = execOut(flow, ["check", "--json"]);
   const results = JSON.parse(out);
 
   if (!results.passed) {
     for (const error of results.errors) {
       for (const message of error.message) {
-        console.log(`TEST-UNEXPECTED-FAIL | ${message.descr}`);
+        console.log(`TEST-UNEXPECTED-FAIL flow | ${message.descr}`);
       }
     }
   }
 
   return results.passed;
 }
 
 function runFlow() {
@@ -59,50 +59,54 @@ function runFlow() {
   console.log(out);
   return runFlowJson();
 }
 
 function eslint() {
   logStart("Eslint");
   const { out } = execOut("yarn", ["lint:js"]);
   console.log(out);
-  const errors = logErrors(out, / {2}error {2}(.*)/g);
+  const errors = logErrors("eslint", out, / {2}error {2}(.*)/g);
   return errors.length == 0;
 }
 
 function jest() {
   logStart("Jest");
   const { out, err } = execOut("yarn", ["test"]);
   console.log(err);
-  const errors = logErrors(err || "", / {4}✕(.*)/g);
+  const errors = logErrors("jest", err || "", / {4}✕(.*)/g);
   return errors.length == 0;
 }
 
 function stylelint() {
   logStart("Stylelint");
   const { out } = execOut("yarn", ["lint:css"]);
   console.log(out);
-  const errors = logErrors(out, / {2}✖(.*)/g);
+  const errors = logErrors("stylelint", out, / {2}✖(.*)/g);
   return errors.length == 0;
 }
 
 function jsxAccessibility() {
   logStart("Eslint (JSX Accessibility)");
 
   const { out } = execOut("yarn", ["lint:jsx-a11y"]);
   console.log(out);
-  const errors = logErrors(out, / {2}error {2}(.*)/g);
+  const errors = logErrors(
+    "eslint (jsx accessibility)",
+    out,
+    / {2}error {2}(.*)/g
+  );
   return errors.length == 0;
 }
 
 function lintMd() {
   logStart("Remark");
 
   const { out, err } = execOut("yarn", ["lint:md"]);
-  const errors = logErrors(err || "", /warning(.+)/g);
+  const errors = logErrors("remark", err || "", /warning(.+)/g);
   return errors.length == 0;
 }
 
 chdir(dbgPath);
 const flowPassed = runFlow();
 const eslintPassed = eslint();
 const jestPassed = jest();
 const styleLintPassed = stylelint();
--- a/devtools/client/debugger/dist/vendors.js
+++ b/devtools/client/debugger/dist/vendors.js
@@ -1436,37 +1436,37 @@ module.exports = {
   workerHandler
 };
 
 /***/ }),
 
 /***/ 15:
 /***/ (function(module, exports) {
 
-var g;
-
-// This works in non-strict mode
-g = (function() {
-	return this;
-})();
-
-try {
-	// This works if eval is allowed (see CSP)
-	g = g || Function("return this")() || (1,eval)("this");
-} catch(e) {
-	// This works if the window reference is available
-	if(typeof window === "object")
-		g = window;
-}
-
-// g can still be undefined, but nothing to do about it...
-// We return undefined, instead of nothing here, so it's
-// easier to handle this case. if(!global) { ...}
-
-module.exports = g;
+var g;
+
+// This works in non-strict mode
+g = (function() {
+	return this;
+})();
+
+try {
+	// This works if eval is allowed (see CSP)
+	g = g || Function("return this")() || (1,eval)("this");
+} catch(e) {
+	// This works if the window reference is available
+	if(typeof window === "object")
+		g = window;
+}
+
+// g can still be undefined, but nothing to do about it...
+// We return undefined, instead of nothing here, so it's
+// easier to handle this case. if(!global) { ...}
+
+module.exports = g;
 
 
 /***/ }),
 
 /***/ 183:
 /***/ (function(module, exports, __webpack_require__) {
 
 "use strict";
@@ -1858,38 +1858,38 @@ TabPanels.defaultProps = {
   className: null
 };
 
 /***/ }),
 
 /***/ 22:
 /***/ (function(module, exports) {
 
-module.exports = function(module) {
-	if(!module.webpackPolyfill) {
-		module.deprecate = function() {};
-		module.paths = [];
-		// module.parent = undefined by default
-		if(!module.children) module.children = [];
-		Object.defineProperty(module, "loaded", {
-			enumerable: true,
-			get: function() {
-				return module.l;
-			}
-		});
-		Object.defineProperty(module, "id", {
-			enumerable: true,
-			get: function() {
-				return module.i;
-			}
-		});
-		module.webpackPolyfill = 1;
-	}
-	return module;
-};
+module.exports = function(module) {
+	if(!module.webpackPolyfill) {
+		module.deprecate = function() {};
+		module.paths = [];
+		// module.parent = undefined by default
+		if(!module.children) module.children = [];
+		Object.defineProperty(module, "loaded", {
+			enumerable: true,
+			get: function() {
+				return module.l;
+			}
+		});
+		Object.defineProperty(module, "id", {
+			enumerable: true,
+			get: function() {
+				return module.i;
+			}
+		});
+		module.webpackPolyfill = 1;
+	}
+	return module;
+};
 
 
 /***/ }),
 
 /***/ 35:
 /***/ (function(module, exports) {
 
 // shim for using process in browser
@@ -2714,21 +2714,16 @@ function inToolbox() {
   try {
     return window.parent.document.documentURI == "about:devtools-toolbox";
   } catch (e) {
     // If `window` is not available, it's very likely that we are in the toolbox.
     return true;
   }
 }
 
-// Copied from m-c DevToolsUtils.
-function getTopWindow(win) {
-  return win.windowRoot ? win.windowRoot.ownerGlobal : win.top;
-}
-
 /**
  * A partial implementation of the Menu API provided by electron:
  * https://github.com/electron/electron/blob/master/docs/api/menu.md.
  *
  * Extra features:
  *  - Emits an 'open' and 'close' event when the menu is opened/closed
 
  * @param String id (non standard)
@@ -2761,35 +2756,36 @@ Menu.prototype.append = function (menuIt
  *
  * @param {int} pos
  * @param {MenuItem} menuItem
  */
 Menu.prototype.insert = function (pos, menuItem) {
   throw Error("Not implemented");
 };
 
+// Copied from m-c DevToolsUtils.
+function getTopWindow(win) {
+  return win.windowRoot ? win.windowRoot.ownerGlobal : win.top;
+}
+
 /**
  * Show the Menu at a specified location on the screen
  *
  * Missing features:
  *   - browserWindow - BrowserWindow (optional) - Default is null.
  *   - positioningItem Number - (optional) OS X
  *
  * @param {int} screenX
  * @param {int} screenY
- * @param {Document} doc
- *        The document that should own the context menu.
+ * @param Toolbox toolbox (non standard)
+ *        Needed so we in which window to inject XUL
  */
 Menu.prototype.popup = function (screenX, screenY, doc) {
-  // The context-menu will be created in the topmost window to preserve keyboard
-  // navigation. See Bug 1543940. Keep a reference on the window owning the menu to hide
-  // the popup on unload.
   const win = doc.defaultView;
   doc = getTopWindow(doc.defaultView).document;
-
   let popupset = doc.querySelector("popupset");
   if (!popupset) {
     popupset = doc.createXULElement("popupset");
     doc.documentElement.appendChild(popupset);
   }
   // See bug 1285229, on Windows, opening the same popup multiple times in a
   // row ends up duplicating the popup. The newly inserted popup doesn't
   // dismiss the old one. So remove any previously displayed popup before
@@ -2801,17 +2797,16 @@ Menu.prototype.popup = function (screenX
 
   popup = this.createPopup(doc);
   popup.setAttribute("menu-api", "true");
 
   if (this.id) {
     popup.id = this.id;
   }
   this._createMenuItems(popup);
-
   // The context menu will be created in the topmost chrome window. Hide it manually when
   // the owner document is unloaded.
   const onWindowUnload = () => popup.hidePopup();
   win.addEventListener("unload", onWindowUnload);
 
   // Remove the menu from the DOM once it's hidden.
   popup.addEventListener("popuphidden", e => {
     if (e.target === popup) {
--- a/devtools/client/debugger/packages/devtools-source-map/src/index.js
+++ b/devtools/client/debugger/packages/devtools-source-map/src/index.js
@@ -83,17 +83,17 @@ export const getOriginalLocation = async
   options: LocationOptions = {}
 ): Promise<SourceLocation> => _getOriginalLocation(location, options);
 
 export const getOriginalLocations = async (
   sourceId: SourceId,
   locations: SourceLocation[],
   options: LocationOptions = {}
 ): Promise<SourceLocation[]> =>
-  dispatcher.invoke("getOriginalLocations", locations, options);
+  dispatcher.invoke("getOriginalLocations", sourceId, locations, options);
 
 export const getGeneratedRangesForOriginal = async (
   sourceId: SourceId,
   url: string,
   mergeUnmappedRegions?: boolean
 ): Promise<Range[]> =>
   dispatcher.invoke(
     "getGeneratedRangesForOriginal",
--- a/devtools/client/debugger/src/actions/sources/tests/loadSource.spec.js
+++ b/devtools/client/debugger/src/actions/sources/tests/loadSource.spec.js
@@ -64,17 +64,17 @@ describe("loadSourceText", () => {
         getBreakpointPositions: async () => ({ "1": [0] }),
         getBreakableLines: async () => []
       },
       {},
       {
         getGeneratedRangesForOriginal: async () => [
           { start: { line: 1, column: 0 }, end: { line: 1, column: 1 } }
         ],
-        getOriginalLocations: async items =>
+        getOriginalLocations: async (sourceId, items) =>
           items.map(item => ({
             ...item,
             sourceId:
               item.sourceId === fooGenSource1.id
                 ? fooOrigSource1.id
                 : fooOrigSource2.id
           })),
         getOriginalSourceText: async s => ({
--- a/devtools/client/debugger/src/actions/tests/pending-breakpoints.spec.js
+++ b/devtools/client/debugger/src/actions/tests/pending-breakpoints.spec.js
@@ -60,17 +60,17 @@ function mockSourceMaps() {
     getOriginalSourceText: async source => ({
       id: source.id,
       text: "",
       contentType: "text/javascript"
     }),
     getGeneratedRangesForOriginal: async () => [
       { start: { line: 0, column: 0 }, end: { line: 10, column: 10 } }
     ],
-    getOriginalLocations: async items => items
+    getOriginalLocations: async (sourceId, items) => items
   };
 }
 
 describe("when adding breakpoints", () => {
   it("a corresponding pending breakpoint should be added", async () => {
     const { dispatch, getState, cx } = createStore(
       mockClient({ "5": [1] }),
       loadInitialState(),
@@ -382,17 +382,17 @@ describe("adding sources", () => {
         line: location.line,
         column: location.column,
         sourceId: _source.id
       }),
       getOriginalLocation: async location => location,
       getGeneratedRangesForOriginal: async () => [
         { start: { line: 0, column: 0 }, end: { line: 10, column: 10 } }
       ],
-      getOriginalLocations: async items => items
+      getOriginalLocations: async (sourceId, items) => items
     });
 
     const { getState, dispatch } = store;
 
     expect(selectors.getBreakpointCount(getState())).toEqual(0);
 
     await dispatch(actions.newGeneratedSource(makeSource("bar.js")));
     await dispatch(
--- a/devtools/client/shared/source-map/index.js
+++ b/devtools/client/shared/source-map/index.js
@@ -605,17 +605,17 @@ const getOriginalRanges = exports.getOri
 const getGeneratedRanges = exports.getGeneratedRanges = async (location, originalSource) => _getGeneratedRanges(location, originalSource);
 
 const getGeneratedLocation = exports.getGeneratedLocation = async (location, originalSource) => _getGeneratedLocation(location, originalSource);
 
 const getAllGeneratedLocations = exports.getAllGeneratedLocations = async (location, originalSource) => _getAllGeneratedLocations(location, originalSource);
 
 const getOriginalLocation = exports.getOriginalLocation = async (location, options = {}) => _getOriginalLocation(location, options);
 
-const getOriginalLocations = exports.getOriginalLocations = async (locations, options = {}) => dispatcher.invoke("getOriginalLocations", locations, options);
+const getOriginalLocations = exports.getOriginalLocations = async (sourceId, locations, options = {}) => dispatcher.invoke("getOriginalLocations", sourceId, locations, options);
 
 const getGeneratedRangesForOriginal = exports.getGeneratedRangesForOriginal = async (sourceId, url, mergeUnmappedRegions) => dispatcher.invoke("getGeneratedRangesForOriginal", sourceId, url, mergeUnmappedRegions);
 
 const getFileGeneratedRange = exports.getFileGeneratedRange = async originalSource => dispatcher.invoke("getFileGeneratedRange", originalSource);
 
 const getLocationScopes = exports.getLocationScopes = dispatcher.task("getLocationScopes");
 
 const getOriginalSourceText = exports.getOriginalSourceText = async originalSource => dispatcher.invoke("getOriginalSourceText", originalSource);
--- a/docshell/base/nsDocShell.cpp
+++ b/docshell/base/nsDocShell.cpp
@@ -13136,26 +13136,30 @@ NS_IMETHODIMP
 nsDocShell::ResumeRedirectedLoad(uint64_t aIdentifier, int32_t aHistoryIndex) {
   RefPtr<nsDocShell> self = this;
   RefPtr<ChildProcessChannelListener> cpcl =
       ChildProcessChannelListener::GetSingleton();
 
   // Call into InternalLoad with the pending channel when it is received.
   cpcl->RegisterCallback(
       aIdentifier, [self, aHistoryIndex](nsIChildChannel* aChannel) {
+        if (NS_WARN_IF(self->mIsBeingDestroyed)) {
+          return;
+        }
+
         RefPtr<nsDocShellLoadState> loadState;
         nsresult rv = nsDocShellLoadState::CreateFromPendingChannel(
             aChannel, getter_AddRefs(loadState));
         if (NS_WARN_IF(NS_FAILED(rv))) {
           return;
         }
 
         // If we're performing a history load, locate the correct history entry,
         // and set the relevant bits on our loadState.
-        if (aHistoryIndex >= 0) {
+        if (aHistoryIndex >= 0 && self->mSessionHistory) {
           nsCOMPtr<nsISHistory> legacySHistory =
               self->mSessionHistory->LegacySHistory();
 
           nsCOMPtr<nsISHEntry> entry;
           rv = legacySHistory->GetEntryAtIndex(aHistoryIndex,
                                                getter_AddRefs(entry));
           if (NS_SUCCEEDED(rv)) {
             legacySHistory->InternalSetRequestedIndex(aHistoryIndex);
--- a/dom/base/nsDocumentEncoder.cpp
+++ b/dom/base/nsDocumentEncoder.cpp
@@ -1078,23 +1078,24 @@ class nsHTMLCopyEncoder : public nsDocum
   enum Endpoint { kStart, kEnd };
 
   nsresult PromoteRange(nsRange* inRange);
   nsresult PromoteAncestorChain(nsCOMPtr<nsINode>* ioNode,
                                 int32_t* ioStartOffset, int32_t* ioEndOffset);
   nsresult GetPromotedPoint(Endpoint aWhere, nsINode* aNode, int32_t aOffset,
                             nsCOMPtr<nsINode>* outNode, int32_t* outOffset,
                             nsINode* aCommon);
-  nsCOMPtr<nsINode> GetChildAt(nsINode* aParent, int32_t aOffset);
-  bool IsMozBR(Element* aNode);
-  nsresult GetNodeLocation(nsINode* inChild, nsCOMPtr<nsINode>* outParent,
-                           int32_t* outOffset);
+  static nsCOMPtr<nsINode> GetChildAt(nsINode* aParent, int32_t aOffset);
+  static bool IsMozBR(Element* aNode);
+  static nsresult GetNodeLocation(nsINode* inChild,
+                                  nsCOMPtr<nsINode>* outParent,
+                                  int32_t* outOffset);
   bool IsRoot(nsINode* aNode);
-  bool IsFirstNode(nsINode* aNode);
-  bool IsLastNode(nsINode* aNode);
+  static bool IsFirstNode(nsINode* aNode);
+  static bool IsLastNode(nsINode* aNode);
   virtual bool IncludeInContext(nsINode* aNode) override;
   virtual int32_t GetImmediateContextCount(
       const nsTArray<nsINode*>& aAncestorArray) override;
 
   bool mIsTextWidget;
 };
 
 nsHTMLCopyEncoder::nsHTMLCopyEncoder() { mIsTextWidget = false; }
--- a/dom/security/test/general/test_same_site_cookies_about.html
+++ b/dom/security/test/general/test_same_site_cookies_about.html
@@ -18,126 +18,67 @@
  *    (a) cross-origin iframe
  *    (b) same-origin iframe
  *    which both load a:
  *     * nested about:srcdoc frame and nested about:blank frame
  *     * navigate about:srcdoc frame and navigate about:blank frame
  * 3) We evaluate that the same-site cookie is available in the same-origin case.
  */
 
-SimpleTest.registerCleanupFunction(() => {
-  SpecialPowers.clearUserPref("network.cookie.same-site.enabled");
-});
 SimpleTest.waitForExplicitFinish();
 
 const SAME_ORIGIN = "http://mochi.test:8888/"
 const CROSS_ORIGIN = "http://example.com/";
 const PATH = "tests/dom/security/test/general/file_same_site_cookies_about.sjs";
 
 let curTest = 0;
 
 var tests = [
   // NAVIGATION TESTS
   {
     description: "nested same origin iframe about:srcdoc navigation [mochi.test -> mochi.test -> about:srcdoc -> mochi.test]",
     frameSRC: SAME_ORIGIN + PATH + "?loadsrcdocframeNav",
-    sameSiteEnabled: true,
     result: "myKey=mySameSiteAboutCookie", // cookie should be set for baseline test
   },
   {
     description: "nested cross origin iframe about:srcdoc navigation [mochi.test -> example.com -> about:srcdoc -> mochi.test]",
     frameSRC: CROSS_ORIGIN + PATH + "?loadsrcdocframeNav",
-    sameSiteEnabled: true,
     result: "", // no same-site cookie should be available
   },
   {
     description: "nested same origin iframe about:blank navigation [mochi.test -> mochi.test -> about:blank -> mochi.test]",
     frameSRC: SAME_ORIGIN + PATH + "?loadblankframeNav",
-    sameSiteEnabled: true,
     result: "myKey=mySameSiteAboutCookie", // cookie should be set for baseline test
   },
   {
     description: "nested cross origin iframe about:blank navigation [mochi.test -> example.com -> about:blank -> mochi.test]",
     frameSRC: CROSS_ORIGIN + PATH + "?loadblankframeNav",
-    sameSiteEnabled: true,
     result: "", // no same-site cookie should be available
   },
-  {
-    description: "nested same origin iframe about:srcdoc navigation [mochi.test -> mochi.test -> about:srcdoc -> mochi.test] (same-site off)",
-    frameSRC: SAME_ORIGIN + PATH + "?loadsrcdocframeNav",
-    sameSiteEnabled: false,
-    result: "myKey=mySameSiteAboutCookie",
-  },
-  {
-    description: "nested cross origin iframe about:srcdoc navigation [mochi.test -> example.com -> about:srcdoc -> mochi.test] (same-site off)",
-    frameSRC: CROSS_ORIGIN + PATH + "?loadsrcdocframeNav",
-    sameSiteEnabled: false,
-    result: "myKey=mySameSiteAboutCookie",
-  },
-  {
-    description: "nested same origin iframe about:blank navigation [mochi.test -> mochi.test -> about:blank -> mochi.test] (same-site off)",
-    frameSRC: SAME_ORIGIN + PATH + "?loadblankframeNav",
-    sameSiteEnabled: false,
-    result: "myKey=mySameSiteAboutCookie",
-  },
-  {
-    description: "nested cross origin iframe about:blank navigation [mochi.test -> example.com -> about:blank -> mochi.test] (same-site off)",
-    frameSRC: CROSS_ORIGIN + PATH + "?loadblankframeNav",
-    sameSiteEnabled: false,
-    result: "myKey=mySameSiteAboutCookie",
-  },
   // INCLUSION TESTS
   {
     description: "nested same origin iframe about:srcdoc inclusion [mochi.test -> mochi.test -> about:srcdoc -> mochi.test]",
     frameSRC: SAME_ORIGIN + PATH + "?loadsrcdocframeInc",
-    sameSiteEnabled: true,
     result: "myKey=mySameSiteAboutCookie", // cookie should be set for baseline test
   },
   {
     description: "nested cross origin iframe about:srcdoc inclusion [mochi.test -> example.com -> about:srcdoc -> mochi.test]",
     frameSRC: CROSS_ORIGIN + PATH + "?loadsrcdocframeInc",
-    sameSiteEnabled: true,
     result: "", // no same-site cookie should be available
   },
   {
     description: "nested same origin iframe about:blank inclusion [mochi.test -> mochi.test -> about:blank -> mochi.test]",
     frameSRC: SAME_ORIGIN + PATH + "?loadblankframeInc",
-    sameSiteEnabled: true,
     result: "myKey=mySameSiteAboutCookie", // cookie should be set for baseline test
   },
   {
     description: "nested cross origin iframe about:blank inclusion [mochi.test -> example.com -> about:blank -> mochi.test]",
     frameSRC: CROSS_ORIGIN + PATH + "?loadblankframeInc",
-    sameSiteEnabled: true,
     result: "", // no same-site cookie should be available
   },
-  {
-    description: "nested same origin iframe about:srcdoc inclusion [mochi.test -> mochi.test -> about:srcdoc -> mochi.test] (same-site off)",
-    frameSRC: SAME_ORIGIN + PATH + "?loadsrcdocframeInc",
-    sameSiteEnabled: false,
-    result: "myKey=mySameSiteAboutCookie",
-  },
-  {
-    description: "nested cross origin iframe about:srcdoc inclusion [mochi.test -> example.com -> about:srcdoc -> mochi.test] (same-site off)",
-    frameSRC: CROSS_ORIGIN + PATH + "?loadsrcdocframeInc",
-    sameSiteEnabled: false,
-    result: "myKey=mySameSiteAboutCookie",
-  },
-  {
-    description: "nested same origin iframe about:blank inclusion [mochi.test -> mochi.test -> about:blank -> mochi.test] (same-site off)",
-    frameSRC: SAME_ORIGIN + PATH + "?loadblankframeInc",
-    sameSiteEnabled: false,
-    result: "myKey=mySameSiteAboutCookie",
-  },
-  {
-    description: "nested cross origin iframe about:blank inclusion [mochi.test -> example.com -> about:blank -> mochi.test] (same-site off)",
-    frameSRC: CROSS_ORIGIN + PATH + "?loadblankframeInc",
-    sameSiteEnabled: false,
-    result: "myKey=mySameSiteAboutCookie",
-  },
 ];
 
 window.addEventListener("message", receiveMessage);
 function receiveMessage(event) {
   is(event.data.result, tests[curTest].result, tests[curTest].description);
   curTest += 1;
 
   // lets see if we ran all the tests
@@ -151,17 +92,16 @@ function receiveMessage(event) {
 }
 
 function setupQueryResultAndRunTest() {
   let testframe = document.getElementById("testframe");
   testframe.src = tests[curTest].frameSRC + curTest;
 }
 
 function setCookieAndInitTest() {
-  SpecialPowers.setBoolPref("network.cookie.same-site.enabled", tests[curTest].sameSiteEnabled);
   var cookieImage = document.getElementById("cookieImage");
   cookieImage.onload = function() {
     ok(true, "trying to set cookie for test (" + tests[curTest].description + ")");
     setupQueryResultAndRunTest();
   }
   cookieImage.onerror = function() {
     ok(false, "could not load image for test (" + tests[curTest].description + ")");
   }
--- a/dom/security/test/general/test_same_site_cookies_cross_origin_context.html
+++ b/dom/security/test/general/test_same_site_cookies_cross_origin_context.html
@@ -19,55 +19,36 @@
  *    in the context of http://mochi.test
  * 2) We load an iframe from http://example.com and check if the cookie
  *    is available.
  * 3) We observe that:
  *    (a) same site cookie has been discarded in a cross origin context.
  *    (b) the regular cookie is available.
  */
 
-SimpleTest.registerCleanupFunction(() => {
-  SpecialPowers.clearUserPref("network.cookie.same-site.enabled");
-});
 SimpleTest.waitForExplicitFinish();
 
 const CROSS_ORIGIN = "http://example.com/";
 const PATH = "tests/dom/security/test/general/file_same_site_cookies_cross_origin_context.sjs";
 
 let curTest = 0;
 
 var tests = [
   {
-    description: "regular cookie in cross origin context (same-site: on)",
+    description: "regular cookie in cross origin context",
     imgSRC: CROSS_ORIGIN + PATH + "?setRegularCookie",
     frameSRC: CROSS_ORIGIN + PATH + "?loadFrame",
-    sameSiteEnabled: true,
     result: "myKey=regularCookie",
   },
   {
-    description: "same-site cookie in cross origin context (same-site: on)",
+    description: "same-site cookie in cross origin context",
     imgSRC: CROSS_ORIGIN + PATH + "?setSameSiteCookie",
     frameSRC: CROSS_ORIGIN + PATH + "?loadFrame",
-    sameSiteEnabled: true,
     result: "", // no cookie should be set
   },
-  {
-    description: "regular cookie in cross origin context (same-site: off)",
-    imgSRC: CROSS_ORIGIN + PATH + "?setRegularCookie",
-    frameSRC: CROSS_ORIGIN + PATH + "?loadFrame",
-    sameSiteEnabled: false,
-    result: "myKey=regularCookie",
-  },
-  {
-    description: "same-site cookie in cross origin context (same-site: off)",
-    imgSRC: CROSS_ORIGIN + PATH + "?setSameSiteCookie",
-    frameSRC: CROSS_ORIGIN + PATH + "?loadFrame",
-    sameSiteEnabled: false,
-    result: "myKey=strictSameSiteCookie",
-  },
 ];
 
 
 window.addEventListener("message", receiveMessage);
 function receiveMessage(event) {
   is(event.data.result, tests[curTest].result, tests[curTest].description);
   curTest += 1;
 
@@ -82,17 +63,16 @@ function receiveMessage(event) {
 }
 
 function setupQueryResultAndRunTest() {
   let testframe = document.getElementById("testframe");
   testframe.src = tests[curTest].frameSRC + curTest;
 }
 
 function setCookieAndInitTest() {
-  SpecialPowers.setBoolPref("network.cookie.same-site.enabled", tests[curTest].sameSiteEnabled);
   var cookieImage = document.getElementById("cookieImage");
   cookieImage.onload = function() {
     ok(true, "trying to set cookie for test (" + tests[curTest].description + ")");
     setupQueryResultAndRunTest();
   }
   cookieImage.onerror = function() {
     ok(false, "could not load image for test (" + tests[curTest].description + ")");
   }
--- a/dom/security/test/general/test_same_site_cookies_from_script.html
+++ b/dom/security/test/general/test_same_site_cookies_from_script.html
@@ -18,56 +18,37 @@
  *    inline script in top-level context of http://mochi.test.
  * 2) We load an iframe from http://example.com and check if the cookie
  *    is available.
  * 3) We observe that:
  *    (a) same site cookie is available in same origin context.
  *    (a) same site cookie has been discarded in a cross origin context.
  */
 
-SimpleTest.registerCleanupFunction(() => {
-  SpecialPowers.clearUserPref("network.cookie.same-site.enabled");
-});
 SimpleTest.waitForExplicitFinish();
 
 const SAME_ORIGIN = "http://mochi.test:8888/";
 const CROSS_ORIGIN = "http://example.com/";
 const PATH = "tests/dom/security/test/general/file_same_site_cookies_from_script.sjs";
 
 let curTest = 0;
 
 var tests = [
   {
     description: "same-site cookie inline script within same-site context",
     setCookieSrc: SAME_ORIGIN + PATH + "?setSameSiteCookieUsingInlineScript",
     getCookieSrc: SAME_ORIGIN + PATH + "?getCookieFrame",
-    sameSiteEnabled: true,
     result: "myKey=sameSiteCookieInlineScript",
   },
   {
     description: "same-site cookie inline script within cross-site context",
     setCookieSrc: CROSS_ORIGIN + PATH + "?setSameSiteCookieUsingInlineScript",
     getCookieSrc: CROSS_ORIGIN + PATH + "?getCookieFrame",
-    sameSiteEnabled: true,
     result: "", // same-site cookie should be discarded in cross site context
   },
-  {
-    description: "same-site cookie inline script within same-site context",
-    setCookieSrc: SAME_ORIGIN + PATH + "?setSameSiteCookieUsingInlineScript",
-    getCookieSrc: SAME_ORIGIN + PATH + "?getCookieFrame",
-    sameSiteEnabled: false,
-    result: "myKey=sameSiteCookieInlineScript",
-  },
-  {
-    description: "same-site cookie inline script within cross-site context",
-    setCookieSrc: CROSS_ORIGIN + PATH + "?setSameSiteCookieUsingInlineScript",
-    getCookieSrc: CROSS_ORIGIN + PATH + "?getCookieFrame",
-    sameSiteEnabled: false,
-    result: "myKey=sameSiteCookieInlineScript",
-  },
 ];
 
 window.addEventListener("message", receiveMessage);
 function receiveMessage(event) {
   is(event.data.result, tests[curTest].result, tests[curTest].description);
   curTest += 1;
 
   // lets see if we ran all the tests
@@ -81,17 +62,16 @@ function receiveMessage(event) {
 }
 
 function setupQueryResultAndRunTest() {
   let getCookieFrame = document.getElementById("getCookieFrame");
   getCookieFrame.src = tests[curTest].getCookieSrc + curTest;
 }
 
 function setCookieAndInitTest() {
-  SpecialPowers.setBoolPref("network.cookie.same-site.enabled", tests[curTest].sameSiteEnabled);
   var cookieFrame = document.getElementById("setCookieFrame");
   setCookieFrame.onload = function() {
     ok(true, "trying to set cookie for test (" + tests[curTest].description + ")");
     setupQueryResultAndRunTest();
   }
   setCookieFrame.onerror = function() {
     ok(false, "could not load image for test (" + tests[curTest].description + ")");
   }
--- a/dom/security/test/general/test_same_site_cookies_subrequest.html
+++ b/dom/security/test/general/test_same_site_cookies_subrequest.html
@@ -22,84 +22,49 @@
  *
  * In detail:
  * We perform an XHR request to the *.sjs file which is processed async on
  * the server and waits till the image request has been processed by the server.
  * Once the image requets was processed, the server responds to the initial
  * XHR request with the expecuted result (the cookie value).
  */
 
-SimpleTest.registerCleanupFunction(() => {
-  SpecialPowers.clearUserPref("network.cookie.same-site.enabled");
-});
 SimpleTest.waitForExplicitFinish();
 
 const SAME_ORIGIN = "http://mochi.test:8888/";
 const CROSS_ORIGIN = "http://example.com/";
 const PATH = "tests/dom/security/test/general/file_same_site_cookies_subrequest.sjs";
 
 let curTest = 0;
 
 var tests = [
   {
     description: "same origin site using cookie policy 'samesite=strict'",
     imgSRC: SAME_ORIGIN + PATH + "?setStrictSameSiteCookie",
     frameSRC: SAME_ORIGIN + PATH + "?loadFrame",
-    sameSiteEnabled: true,
     result: "myKey=strictSameSiteCookie",
   },
   {
     description: "cross origin site using cookie policy 'samesite=strict'",
     imgSRC: SAME_ORIGIN + PATH + "?setStrictSameSiteCookie",
     frameSRC: CROSS_ORIGIN + PATH + "?loadFrame",
-    sameSiteEnabled: true,
     result: "myKey=noCookie",
   },
   {
     description: "same origin site using cookie policy 'samesite=lax'",
     imgSRC: SAME_ORIGIN + PATH + "?setLaxSameSiteCookie",
     frameSRC: SAME_ORIGIN + PATH + "?loadFrame",
-    sameSiteEnabled: true,
     result: "myKey=laxSameSiteCookie",
   },
   {
     description: "cross origin site using cookie policy 'samesite=lax'",
     imgSRC: SAME_ORIGIN + PATH + "?setLaxSameSiteCookie",
     frameSRC: CROSS_ORIGIN + PATH + "?loadFrame",
-    sameSiteEnabled: true,
     result: "myKey=noCookie",
   },
-  {
-    description: "same origin site using cookie policy 'samesite=strict'",
-    imgSRC: SAME_ORIGIN + PATH + "?setStrictSameSiteCookie",
-    frameSRC: SAME_ORIGIN + PATH + "?loadFrame",
-    sameSiteEnabled: false,
-    result: "myKey=strictSameSiteCookie",
-  },
-  {
-    description: "cross origin site using cookie policy 'samesite=strict'",
-    imgSRC: SAME_ORIGIN + PATH + "?setStrictSameSiteCookie",
-    frameSRC: CROSS_ORIGIN + PATH + "?loadFrame",
-    sameSiteEnabled: false,
-    result: "myKey=strictSameSiteCookie",
-  },
-  {
-    description: "same origin site using cookie policy 'samesite=lax'",
-    imgSRC: SAME_ORIGIN + PATH + "?setLaxSameSiteCookie",
-    frameSRC: SAME_ORIGIN + PATH + "?loadFrame",
-    sameSiteEnabled: false,
-    result: "myKey=laxSameSiteCookie",
-  },
-  {
-    description: "cross origin site using cookie policy 'samesite=lax'",
-    imgSRC: SAME_ORIGIN + PATH + "?setLaxSameSiteCookie",
-    frameSRC: CROSS_ORIGIN + PATH + "?loadFrame",
-    sameSiteEnabled: false,
-    result: "myKey=laxSameSiteCookie",
-  },
 ];
 
 function checkResult(aCookieVal) {
   is(aCookieVal, tests[curTest].result, tests[curTest].description);
   curTest += 1;
 
   // lets see if we ran all the tests
   if (curTest == tests.length) {
@@ -124,17 +89,16 @@ function setupQueryResultAndRunTest() {
   // give it some time and load the test frame
   SimpleTest.executeSoon(function() {
     let testframe = document.getElementById("testframe");
     testframe.src = tests[curTest].frameSRC + curTest;
   });
 }
 
 function setCookieAndInitTest() {
-  SpecialPowers.setBoolPref("network.cookie.same-site.enabled", tests[curTest].sameSiteEnabled);
   var cookieImage = document.getElementById("cookieImage");
   cookieImage.onload = function() {
     ok(true, "set cookie for test (" + tests[curTest].description + ")");
     setupQueryResultAndRunTest();
   }
   cookieImage.onerror = function() {
     ok(false, "could not set cookie for test (" + tests[curTest].description + ")");
   }
--- a/dom/security/test/general/test_same_site_cookies_toplevel_nav.html
+++ b/dom/security/test/general/test_same_site_cookies_toplevel_nav.html
@@ -23,82 +23,47 @@
  *
  * In detail:
  * We perform an XHR request to the *.sjs file which is processed async on
  * the server and waits till the image request has been processed by the server.
  * Once the image requets was processed, the server responds to the initial
  * XHR request with the expecuted result (the cookie value).
  */
 
-SimpleTest.registerCleanupFunction(() => {
-  SpecialPowers.clearUserPref("network.cookie.same-site.enabled");
-});
 SimpleTest.waitForExplicitFinish();
 
 const SAME_ORIGIN = "http://mochi.test:8888/";
 const CROSS_ORIGIN = "http://example.com/";
 const PATH = "tests/dom/security/test/general/file_same_site_cookies_toplevel_nav.sjs";
 
 let curTest = 0;
 
 var tests = [
   {
     description: "same origin navigation using cookie policy 'samesite=strict'",
     imgSRC: SAME_ORIGIN + PATH + "?setStrictSameSiteCookie",
     frameSRC: SAME_ORIGIN + PATH + "?loadFrame",
-    sameSiteEnabled: true,
     result: "myKey=strictSameSiteCookie",
   },
   {
     description: "cross origin navigation using cookie policy 'samesite=strict'",
     imgSRC: SAME_ORIGIN + PATH + "?setStrictSameSiteCookie",
     frameSRC: CROSS_ORIGIN + PATH + "?loadFrame",
-    sameSiteEnabled: true,
     result: "myKey=noCookie",
   },
   {
     description: "same origin navigation using cookie policy 'samesite=lax'",
     imgSRC: SAME_ORIGIN + PATH + "?setLaxSameSiteCookie",
     frameSRC: SAME_ORIGIN + PATH + "?loadFrame",
-    sameSiteEnabled: true,
     result: "myKey=laxSameSiteCookie",
   },
   {
     description: "cross origin navigation using cookie policy 'samesite=lax'",
     imgSRC: SAME_ORIGIN + PATH + "?setLaxSameSiteCookie",
     frameSRC: CROSS_ORIGIN + PATH + "?loadFrame",
-    sameSiteEnabled: true,
-    result: "myKey=laxSameSiteCookie",
-  },
-  {
-    description: "same origin navigation using cookie policy 'samesite=strict'",
-    imgSRC: SAME_ORIGIN + PATH + "?setStrictSameSiteCookie",
-    frameSRC: SAME_ORIGIN + PATH + "?loadFrame",
-    sameSiteEnabled: false,
-    result: "myKey=strictSameSiteCookie",
-  },
-  {
-    description: "cross origin navigation using cookie policy 'samesite=strict'",
-    imgSRC: SAME_ORIGIN + PATH + "?setStrictSameSiteCookie",
-    frameSRC: CROSS_ORIGIN + PATH + "?loadFrame",
-    sameSiteEnabled: false,
-    result: "myKey=strictSameSiteCookie",
-  },
-  {
-    description: "same origin navigation using cookie policy 'samesite=lax'",
-    imgSRC: SAME_ORIGIN + PATH + "?setLaxSameSiteCookie",
-    frameSRC: SAME_ORIGIN + PATH + "?loadFrame",
-    sameSiteEnabled: false,
-    result: "myKey=laxSameSiteCookie",
-  },
-  {
-    description: "cross origin navigation using cookie policy 'samesite=lax'",
-    imgSRC: SAME_ORIGIN + PATH + "?setLaxSameSiteCookie",
-    frameSRC: CROSS_ORIGIN + PATH + "?loadFrame",
-    sameSiteEnabled: false,
     result: "myKey=laxSameSiteCookie",
   },
 ];
 
 function checkResult(aCookieVal) {
   is(aCookieVal, tests[curTest].result, tests[curTest].description);
   curTest += 1;
 
@@ -125,17 +90,16 @@ function setupQueryResultAndRunTest() {
   // give it some time and load the test window
   SimpleTest.executeSoon(function() {
     let testframe = document.getElementById("testframe");
     testframe.src = tests[curTest].frameSRC + curTest;
   });
 }
 
 function setCookieAndInitTest() {
-  SpecialPowers.setBoolPref("network.cookie.same-site.enabled", tests[curTest].sameSiteEnabled);
   var cookieImage = document.getElementById("cookieImage");
   cookieImage.onload = function() {
     ok(true, "set cookie for test (" + tests[curTest].description + ")");
     setupQueryResultAndRunTest();
   }
   cookieImage.onerror = function() {
     ok(false, "could not set cookie for test (" + tests[curTest].description + ")");
   }
--- a/gfx/wr/Cargo.lock
+++ b/gfx/wr/Cargo.lock
@@ -1804,16 +1804,17 @@ dependencies = [
  "glutin 0.17.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "image 0.21.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "lazy_static 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "log 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "mozangle 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)",
  "osmesa-src 0.1.1 (git+https://github.com/servo/osmesa-src)",
  "osmesa-sys 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "ron 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)",
+ "semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "serde 1.0.88 (registry+https://github.com/rust-lang/crates.io-index)",
  "serde_json 1.0.17 (registry+https://github.com/rust-lang/crates.io-index)",
  "time 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)",
  "webrender 0.60.0",
  "webrender_api 0.60.0",
  "winit 0.16.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "yaml-rust 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
--- a/gfx/wr/wrench/Cargo.toml
+++ b/gfx/wr/wrench/Cargo.toml
@@ -24,16 +24,17 @@ ron = "0.1.5"
 time = "0.1"
 crossbeam = "0.2"
 osmesa-sys = { version = "0.1.2", optional = true }
 osmesa-src = { git = "https://github.com/servo/osmesa-src", optional = true }
 webrender = {path = "../webrender", features=["capture","replay","debugger","png","profiler","no_static_freetype"]}
 webrender_api = {path = "../webrender_api", features=["serialize","deserialize"]}
 winit = "0.16"
 serde = {version = "1.0", features = ["derive"] }
+semver = "0.9.0"
 
 [target.'cfg(target_os = "macos")'.dependencies]
 core-graphics = "0.17.1"
 core-foundation = "0.6"
 
 [features]
 default = [ "env_logger" ]
 headless = [ "osmesa-sys", "osmesa-src" ]
--- a/gfx/wr/wrench/reftests/text/reftest.list
+++ b/gfx/wr/wrench/reftests/text/reftest.list
@@ -27,17 +27,17 @@ fuzzy(1,1) platform(linux) == shadow-ord
 != synthetic-bold.yaml synthetic-bold-not-ref.yaml
 fuzzy(1,1786) options(disable-subpixel) == synthetic-bold-transparent.yaml synthetic-bold-transparent-ref.yaml
 != synthetic-bold-transparent.yaml synthetic-bold.yaml
 != synthetic-italics.yaml synthetic-italics-ref.yaml
 != synthetic-italics-custom.yaml synthetic-italics-ref.yaml
 != synthetic-italics-custom.yaml synthetic-italics.yaml
 options(disable-aa) == ahem.yaml ahem-ref.yaml
 platform(linux) == isolated-text.yaml isolated-text.png
-platform(mac) fuzzy(3,67) == white-opacity.yaml white-opacity.png
+platform(mac) skip_on(mac,>=10.14) fuzzy(3,67) == white-opacity.yaml white-opacity.png
 fuzzy(1,4) platform(linux) options(disable-subpixel) == colors.yaml colors-alpha.png
 # Run without dual-source blending path, batches are broken when text colors change.
 fuzzy(1,6) platform(linux) options(disable-dual-source-blending) draw_calls(5) == colors.yaml colors-subpx.png
 # Run with both dual-source blending, ensuring batching is improved.
 fuzzy(1,6) platform(linux) draw_calls(2) == colors.yaml colors-subpx.png
 platform(linux) options(disable-subpixel) == border-radius.yaml border-radius-alpha.png
 platform(linux) == border-radius.yaml border-radius-subpx.png
 options(disable-aa) == transparent-no-aa.yaml transparent-no-aa-ref.yaml
@@ -55,17 +55,17 @@ platform(mac) fuzzy(195,30) == color-bit
 platform(linux) == writing-modes.yaml writing-modes-ref.yaml
 platform(linux) == blurred-shadow-local-clip-rect.yaml blurred-shadow-local-clip-rect-ref.png
 fuzzy(1,1) platform(linux) == two-shadows.yaml two-shadows.png
 == shadow-clip.yaml shadow-clip-ref.yaml
 == shadow-fast-clip.yaml shadow-fast-clip-ref.yaml
 == shadow-partial-glyph.yaml shadow-partial-glyph-ref.yaml
 fuzzy(1,107) platform(linux) == shadow-transforms.yaml shadow-transforms.png
 fuzzy(1,113) platform(linux) == raster-space.yaml raster-space.png
-!= allow-subpixel.yaml allow-subpixel-ref.yaml
+skip_on(mac,>=10.14) != allow-subpixel.yaml allow-subpixel-ref.yaml
 == bg-color.yaml bg-color-ref.yaml
 != large-glyphs.yaml blank.yaml
 == snap-text-offset.yaml snap-text-offset-ref.yaml
 == shadow-border.yaml shadow-solid-ref.yaml
 == shadow-image.yaml shadow-solid-ref.yaml
 options(disable-aa) == snap-clip.yaml snap-clip-ref.yaml
 platform(linux) == perspective-clip.yaml perspective-clip.png
 options(disable-subpixel) == raster-space-snap.yaml raster-space-snap-ref.yaml
--- a/gfx/wr/wrench/src/parse_function.rs
+++ b/gfx/wr/wrench/src/parse_function.rs
@@ -1,22 +1,26 @@
 /* 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 std::str::CharIndices;
 
-// support arguments like '4', 'ab', '4.0'
+// support arguments like '4', 'ab', '4.0', '>=10.14'
 fn acceptable_arg_character(c: char) -> bool {
-    c.is_alphanumeric() || c == '.' || c == '-'
+    c.is_alphanumeric() || c == '.' || c == '-' || c == '<' || c == '>' || c == '='
 }
 
-// A crapy parser for parsing strings like "translate(1, 3)"
+// A crapy parser for parsing strings like "translate(1, 3) blahblah"
+// Returns a tuple with three components:
+// - First component is the function name (e.g. "translate")
+// - Second component is the list of arguments (e.g. vec!["1", "3"])
+// - Third component is the rest of the string "blahblah"
 pub fn parse_function(s: &str) -> (&str, Vec<&str>, &str) {
-    // XXX: This it not particular easy to read. Sorry.
+    // XXX: This is not particularly easy to read. Sorry.
     struct Parser<'a> {
         itr: CharIndices<'a>,
         start: usize,
         o: Option<(usize, char)>,
     }
     impl<'a> Parser<'a> {
         fn skip_whitespace(&mut self) {
             while let Some(k) = self.o {
--- a/gfx/wr/wrench/src/reftest.rs
+++ b/gfx/wr/wrench/src/reftest.rs
@@ -1,47 +1,34 @@
 /* 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 crate::{WindowWrapper, NotifierEvent};
 use base64;
+use semver;
 use image::load as load_piston_image;
 use image::png::PNGEncoder;
 use image::{ColorType, ImageFormat};
 use crate::parse_function::parse_function;
 use crate::png::save_flipped;
 use std::cmp;
 use std::fmt::{Display, Error, Formatter};
 use std::fs::File;
 use std::io::{BufRead, BufReader};
 use std::path::{Path, PathBuf};
+use std::process::Command;
 use std::sync::mpsc::Receiver;
 use webrender::RenderResults;
 use webrender::api::*;
 use webrender::api::units::*;
 use crate::wrench::{Wrench, WrenchThing};
 use crate::yaml_frame_reader::YamlFrameReader;
 
 
-#[cfg(target_os = "windows")]
-const PLATFORM: &str = "win";
-#[cfg(target_os = "linux")]
-const PLATFORM: &str = "linux";
-#[cfg(target_os = "macos")]
-const PLATFORM: &str = "mac";
-#[cfg(target_os = "android")]
-const PLATFORM: &str = "android";
-#[cfg(not(any(target_os = "linux", target_os = "macos", target_os = "windows", target_os = "android")))]
-const PLATFORM: &str = "other";
-#[cfg(debug)]
-const MODE: &str = "debug";
-#[cfg(not(debug))]
-const MODE: &str = "release";
-
 const OPTION_DISABLE_SUBPX: &str = "disable-subpixel";
 const OPTION_DISABLE_AA: &str = "disable-aa";
 const OPTION_DISABLE_DUAL_SOURCE_BLENDING: &str = "disable-dual-source-blending";
 const OPTION_ALLOW_MIPMAPS: &str = "allow-mipmaps";
 
 pub struct ReftestOptions {
     // These override values that are lower.
     pub allow_max_difference: usize,
@@ -197,17 +184,17 @@ impl ReftestImage {
         format!("data:image/png;base64,{}", png_base64)
     }
 }
 
 struct ReftestManifest {
     reftests: Vec<Reftest>,
 }
 impl ReftestManifest {
-    fn new(manifest: &Path, options: &ReftestOptions) -> ReftestManifest {
+    fn new(manifest: &Path, environment: &ReftestEnvironment, options: &ReftestOptions) -> ReftestManifest {
         let dir = manifest.parent().unwrap();
         let f =
             File::open(manifest).expect(&format!("couldn't open manifest: {}", manifest.display()));
         let file = BufReader::new(&f);
 
         let mut reftests = Vec::new();
 
         for line in file.lines() {
@@ -235,32 +222,32 @@ impl ReftestManifest {
             let mut paths = vec![];
             for (i, token) in tokens.iter().enumerate() {
                 match *token {
                     "include" => {
                         assert!(i == 0, "include must be by itself");
                         let include = dir.join(tokens[1]);
 
                         reftests.append(
-                            &mut ReftestManifest::new(include.as_path(), options).reftests,
+                            &mut ReftestManifest::new(include.as_path(), environment, options).reftests,
                         );
 
                         break;
                     }
                     platform if platform.starts_with("skip_on") => {
                         // e.g. skip_on(android,debug) will skip only when
                         // running on a debug android build.
                         let (_, args, _) = parse_function(platform);
-                        if args.iter().all(|arg| arg == &PLATFORM || arg == &MODE) {
+                        if args.iter().all(|arg| environment.has(arg)) {
                             break;
                         }
                     }
                     platform if platform.starts_with("platform") => {
                         let (_, args, _) = parse_function(platform);
-                        if !args.iter().any(|arg| arg == &PLATFORM) {
+                        if !args.iter().any(|arg| arg == &environment.platform) {
                             // Skip due to platform not matching
                             break;
                         }
                     }
                     function if function.starts_with("zoom") => {
                         let (_, args, _) = parse_function(function);
                         zoom_factor = args[0].parse().unwrap();
                     }
@@ -357,28 +344,106 @@ impl ReftestManifest {
     }
 }
 
 struct YamlRenderOutput {
     image: ReftestImage,
     results: RenderResults,
 }
 
+struct ReftestEnvironment {
+    pub platform: &'static str,
+    pub version: Option<semver::Version>,
+    pub mode: &'static str,
+}
+
+impl ReftestEnvironment {
+    fn new() -> Self {
+        Self {
+            platform: Self::platform(),
+            version: Self::version(),
+            mode: Self::mode(),
+        }
+    }
+
+    fn has(&self, condition: &str) -> bool {
+        if self.platform == condition || self.mode == condition {
+            return true;
+        }
+        match (&self.version, &semver::VersionReq::parse(condition)) {
+            (None, _) => false,
+            (_, Err(_)) => false,
+            (Some(v), Ok(r)) => r.matches(v),
+        }
+    }
+
+    fn platform() -> &'static str {
+        if cfg!(target_os = "windows") {
+            "win"
+        } else if cfg!(target_os = "linux") {
+            "linux"
+        } else if cfg!(target_os = "macos") {
+            "mac"
+        } else if cfg!(target_os = "android") {
+            "android"
+        } else {
+            "other"
+        }
+    }
+
+    fn version() -> Option<semver::Version> {
+        if cfg!(target_os = "macos") {
+            use std::str;
+            let version_bytes = Command::new("defaults")
+                .arg("read")
+                .arg("loginwindow")
+                .arg("SystemVersionStampAsString")
+                .output()
+                .expect("Failed to get macOS version")
+                .stdout;
+            let mut version_string = str::from_utf8(&version_bytes)
+                .expect("Failed to read macOS version")
+                .trim()
+                .to_string();
+            // On some machines this produces just the major.minor and on
+            // some machines this gives major.minor.patch. But semver requires
+            // the patch so we fake one if it's not there.
+            if version_string.chars().filter(|c| *c == '.').count() == 1 {
+                version_string.push_str(".0");
+            }
+            Some(semver::Version::parse(&version_string)
+                 .expect(&format!("Failed to parse macOS version {}", version_string)))
+        } else {
+            None
+        }
+    }
+
+    fn mode() -> &'static str {
+        if cfg!(debug_assertions) {
+            "debug"
+        } else {
+            "release"
+        }
+    }
+}
+
 pub struct ReftestHarness<'a> {
     wrench: &'a mut Wrench,
     window: &'a mut WindowWrapper,
     rx: &'a Receiver<NotifierEvent>,
+    environment: ReftestEnvironment,
 }
 impl<'a> ReftestHarness<'a> {
     pub fn new(wrench: &'a mut Wrench, window: &'a mut WindowWrapper, rx: &'a Receiver<NotifierEvent>) -> Self {
-        ReftestHarness { wrench, window, rx }
+        let environment = ReftestEnvironment::new();
+        ReftestHarness { wrench, window, rx, environment }
     }
 
     pub fn run(mut self, base_manifest: &Path, reftests: Option<&Path>, options: &ReftestOptions) -> usize {
-        let manifest = ReftestManifest::new(base_manifest, options);
+        let manifest = ReftestManifest::new(base_manifest, &self.environment, options);
         let reftests = manifest.find(reftests.unwrap_or(&PathBuf::new()));
 
         let mut total_passing = 0;
         let mut failing = Vec::new();
 
         for t in reftests {
             if self.run_reftest(t) {
                 total_passing += 1;
--- a/js/src/jit/BaselineCompiler.cpp
+++ b/js/src/jit/BaselineCompiler.cpp
@@ -1396,17 +1396,69 @@ bool BaselineCompilerCodeGen::emitArgume
     }
   }
 
   return true;
 }
 
 template <>
 bool BaselineInterpreterCodeGen::emitArgumentTypeChecks() {
-  MOZ_CRASH("NYI: interpreter emitArgumentTypeChecks");
+  Register scratch1 = R1.scratchReg();
+
+  // If the script is not a function, we're done.
+  Label done;
+  masm.loadPtr(frame.addressOfCalleeToken(), scratch1);
+  masm.branchTestPtr(Assembler::NonZero, scratch1, Imm32(CalleeTokenScriptBit),
+                     &done);
+
+  // CalleeToken_Function or CalleeToken_FunctionConstructing.
+  masm.andPtr(Imm32(uint32_t(CalleeTokenMask)), scratch1);
+
+  // Store nargs in the frame's scratch slot.
+  masm.load16ZeroExtend(Address(scratch1, JSFunction::offsetOfNargs()),
+                        scratch1);
+  masm.store32(scratch1, frame.addressOfScratchValue());
+
+  // Type check |this|.
+  masm.loadValue(frame.addressOfThis(), R0);
+  if (!emitNextIC()) {
+    return false;
+  }
+  frame.bumpInterpreterICEntry();
+
+  // Type check arguments. Scratch1 holds the next argument's index.
+  masm.move32(Imm32(0), scratch1);
+
+  // Bounds check.
+  Label top;
+  masm.bind(&top);
+  masm.branch32(Assembler::Equal, frame.addressOfScratchValue(), scratch1,
+                &done);
+  {
+    // Load the argument, increment argument index. Use the frame's return value
+    // slot to store this index across the IC call.
+    BaseValueIndex addr(BaselineFrameReg, scratch1,
+                        BaselineFrame::offsetOfArg(0));
+    masm.loadValue(addr, R0);
+    masm.add32(Imm32(1), scratch1);
+    masm.store32(scratch1, frame.addressOfReturnValue());
+
+    // Type check the argument.
+    if (!emitNextIC()) {
+      return false;
+    }
+    frame.bumpInterpreterICEntry();
+
+    // Restore argument index.
+    masm.load32(frame.addressOfReturnValue(), scratch1);
+    masm.jump(&top);
+  }
+
+  masm.bind(&done);
+  return true;
 }
 
 bool BaselineCompiler::emitDebugTrap() {
   MOZ_ASSERT(compileDebugInstrumentation());
   MOZ_ASSERT(frame.numUnsyncedSlots() == 0);
 
   JSScript* script = handler.script();
   bool enabled =
@@ -2068,32 +2120,42 @@ template <typename Handler>
 bool BaselineCodeGen<Handler>::emit_JSOP_LOOPHEAD() {
   if (!emit_JSOP_JUMPTARGET()) {
     return false;
   }
   return emitInterruptCheck();
 }
 
 template <typename Handler>
-bool BaselineCodeGen<Handler>::emit_JSOP_LOOPENTRY() {
-  if (!emit_JSOP_JUMPTARGET()) {
-    return false;
-  }
-  frame.syncStack(0);
-  if (!emitWarmUpCounterIncrement()) {
-    return false;
+bool BaselineCodeGen<Handler>::emitIncExecutionProgressCounter(
+    Register scratch) {
+  if (!mozilla::recordreplay::IsRecordingOrReplaying()) {
+    return true;
   }
 
   auto incCounter = [this]() {
     masm.inc64(
         AbsoluteAddress(mozilla::recordreplay::ExecutionProgressCounter()));
     return true;
   };
   return emitTestScriptFlag(JSScript::MutableFlags::TrackRecordReplayProgress,
-                            true, incCounter, R2.scratchReg());
+                            true, incCounter, scratch);
+}
+
+template <typename Handler>
+bool BaselineCodeGen<Handler>::emit_JSOP_LOOPENTRY() {
+  if (!emit_JSOP_JUMPTARGET()) {
+    return false;
+  }
+  frame.syncStack(0);
+  if (!emitWarmUpCounterIncrement()) {
+    return false;
+  }
+
+  return emitIncExecutionProgressCounter(R0.scratchReg());
 }
 
 template <typename Handler>
 bool BaselineCodeGen<Handler>::emit_JSOP_VOID() {
   frame.pop();
   frame.push(UndefinedValue());
   return true;
 }
@@ -5679,16 +5741,48 @@ bool BaselineCodeGen<Handler>::emit_JSOP
   return emitReturn();
 }
 
 template <typename Handler>
 bool BaselineCodeGen<Handler>::emit_JSOP_AWAIT() {
   return emit_JSOP_YIELD();
 }
 
+template <>
+template <typename F>
+bool BaselineCompilerCodeGen::emitAfterYieldDebugInstrumentation(
+    const F& ifDebuggee, Register) {
+  if (handler.compileDebugInstrumentation()) {
+    return ifDebuggee();
+  }
+  return true;
+}
+
+template <>
+template <typename F>
+bool BaselineInterpreterCodeGen::emitAfterYieldDebugInstrumentation(
+    const F& ifDebuggee, Register scratch) {
+  // Note that we can't use emitDebugInstrumentation here because the frame's
+  // DEBUGGEE flag hasn't been initialized yet.
+
+  // If the current Realm is not a debuggee we're done.
+  Label done;
+  masm.loadPtr(AbsoluteAddress(cx->addressOfRealm()), scratch);
+  masm.branchTest32(Assembler::Zero,
+                    Address(scratch, Realm::offsetOfDebugModeBits()),
+                    Imm32(Realm::debugModeIsDebuggeeBit()), &done);
+
+  if (!ifDebuggee()) {
+    return false;
+  }
+
+  masm.bind(&done);
+  return true;
+}
+
 template <typename Handler>
 bool BaselineCodeGen<Handler>::emit_JSOP_AFTERYIELD() {
   if (!emit_JSOP_JUMPTARGET()) {
     return false;
   }
 
   auto ifDebuggee = [this]() {
     frame.assertSyncedStack();
@@ -5708,17 +5802,17 @@ bool BaselineCodeGen<Handler>::emit_JSOP
     masm.branchTest32(Assembler::Zero, ReturnReg, ReturnReg, &done);
     {
       masm.loadValue(frame.addressOfReturnValue(), JSReturnOperand);
       masm.jump(&return_);
     }
     masm.bind(&done);
     return true;
   };
-  return emitDebugInstrumentation(ifDebuggee);
+  return emitAfterYieldDebugInstrumentation(ifDebuggee, R0.scratchReg());
 }
 
 template <typename Handler>
 bool BaselineCodeGen<Handler>::emit_JSOP_FINALYIELDRVAL() {
   // Store generator in R0.
   frame.popRegsAndSync(1);
   masm.unboxObject(R0, R0.scratchReg());
 
@@ -6410,23 +6504,17 @@ bool BaselineCodeGen<Handler>::emitProlo
 
   // Handle env chain pre-initialization (in case GC gets run
   // during stack check).  For global and eval scripts, the env
   // chain is in R1.  For function scripts, the env chain is in
   // the callee, nullptr is stored for now so that GC doesn't choke
   // on a bogus EnvironmentChain value in the frame.
   emitPreInitEnvironmentChain(R1.scratchReg());
 
-  auto incCounter = [this]() {
-    masm.inc64(
-        AbsoluteAddress(mozilla::recordreplay::ExecutionProgressCounter()));
-    return true;
-  };
-  if (!emitTestScriptFlag(JSScript::MutableFlags::TrackRecordReplayProgress,
-                          true, incCounter, R2.scratchReg())) {
+  if (!emitIncExecutionProgressCounter(R2.scratchReg())) {
     return false;
   }
 
   // Functions with a large number of locals require two stack checks.
   // The VMCall for a fallible stack check can only occur after the
   // env chain has been initialized, as that is required for proper
   // exception handling if the VMCall returns false.  The env chain
   // initialization can only happen after the UndefinedValues for the
@@ -6664,17 +6752,19 @@ bool BaselineInterpreterGenerator::emitI
 
   Address pcAddr = frame.addressOfInterpreterPC();
 
   // Entry point for interpreting a bytecode op. No registers are live. PC is
   // loaded from frame->interpreterPC.
   masm.bind(handler.interpretOpLabel());
   interpretOpOffset_ = masm.currentOffset();
 
-  // Emit a patchable call for debugger breakpoints/stepping.
+  // Emit a patchable call for debugger breakpoints/stepping. Note: there must
+  // be no code between interpretOpOffset_ and this debug trap. EnterBaseline
+  // and BaselineCompileFromBaselineInterpreter depend on this.
   if (!emitDebugTrap()) {
     return false;
   }
 
   // Load pc, bytecode op.
   masm.loadPtr(pcAddr, PCRegAtStart);
   masm.load8ZeroExtend(Address(PCRegAtStart, 0), scratch1);
 
@@ -6696,17 +6786,17 @@ bool BaselineInterpreterGenerator::emitI
     if (!BytecodeFallsThrough(op)) {
       // Nothing to do.
       masm.assumeUnreachable("unexpected fall through");
       return true;
     }
 
     // Bump frame->interpreterICEntry if needed.
     if (BytecodeOpHasIC(op)) {
-      masm.addPtr(Imm32(sizeof(ICEntry)), frame.addressOfInterpreterICEntry());
+      frame.bumpInterpreterICEntry();
     }
 
     // Bump frame->interpreterPC, keep pc in PCRegAtStart.
     masm.loadPtr(pcAddr, PCRegAtStart);
     masm.addPtr(Imm32(opLength), PCRegAtStart);
     masm.storePtr(PCRegAtStart, pcAddr);
 
     if (!emitDebugTrap()) {
--- a/js/src/jit/BaselineCompiler.h
+++ b/js/src/jit/BaselineCompiler.h
@@ -378,16 +378,20 @@ class BaselineCodeGen {
   template <typename F1, typename F2>
   MOZ_MUST_USE bool emitDebugInstrumentation(
       const F1& ifDebuggee, const mozilla::Maybe<F2>& ifNotDebuggee);
   template <typename F>
   MOZ_MUST_USE bool emitDebugInstrumentation(const F& ifDebuggee) {
     return emitDebugInstrumentation(ifDebuggee, mozilla::Maybe<F>());
   }
 
+  template <typename F>
+  MOZ_MUST_USE bool emitAfterYieldDebugInstrumentation(const F& ifDebuggee,
+                                                       Register scratch);
+
   // ifSet should be a function emitting code for when the script has |flag|
   // set. ifNotSet emits code for when the flag isn't set.
   template <typename F1, typename F2>
   MOZ_MUST_USE bool emitTestScriptFlag(JSScript::ImmutableFlags flag,
                                        const F1& ifSet, const F2& ifNotSet,
                                        Register scratch);
 
   // If |script->hasFlag(flag) == value|, execute the code emitted by |emit|.
@@ -400,16 +404,18 @@ class BaselineCodeGen {
                                        const F& emit, Register scratch);
 
   MOZ_MUST_USE bool emitGeneratorResume(GeneratorResumeKind resumeKind);
   MOZ_MUST_USE bool emitEnterGeneratorCode(Register script,
                                            Register resumeIndex,
                                            Register scratch);
   void emitJumpToInterpretOpLabel();
 
+  MOZ_MUST_USE bool emitIncExecutionProgressCounter(Register scratch);
+
   MOZ_MUST_USE bool emitCheckThis(ValueOperand val, bool reinit = false);
   void emitLoadReturnValue(ValueOperand val);
   void emitPushNonArrowFunctionNewTarget();
   void emitGetAliasedVar(ValueOperand dest);
 
   MOZ_MUST_USE bool emitNextIC();
   MOZ_MUST_USE bool emitInterruptCheck();
   MOZ_MUST_USE bool emitWarmUpCounterIncrement();
--- a/js/src/jit/BaselineFrameInfo.cpp
+++ b/js/src/jit/BaselineFrameInfo.cpp
@@ -1,16 +1,17 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
  * vim: set ts=8 sts=2 et sw=2 tw=80:
  * This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "jit/BaselineFrameInfo.h"
 
+#include "jit/BaselineIC.h"
 #ifdef DEBUG
 #  include "jit/BytecodeAnalysis.h"
 #endif
 
 #include "jit/BaselineFrameInfo-inl.h"
 #include "jit/MacroAssembler-inl.h"
 
 using namespace js;
@@ -156,16 +157,20 @@ void InterpreterFrameInfo::popRegsAndSyn
       popValue(R0);
       break;
     }
     default:
       MOZ_CRASH("Invalid uses");
   }
 }
 
+void InterpreterFrameInfo::bumpInterpreterICEntry() {
+  masm.addPtr(Imm32(sizeof(ICEntry)), addressOfInterpreterICEntry());
+}
+
 void CompilerFrameInfo::storeStackValue(int32_t depth, const Address& dest,
                                         const ValueOperand& scratch) {
   const StackValue* source = peek(depth);
   switch (source->kind()) {
     case StackValue::Constant:
       masm.storeValue(source->constant(), dest);
       break;
     case StackValue::Register:
--- a/js/src/jit/BaselineFrameInfo.h
+++ b/js/src/jit/BaselineFrameInfo.h
@@ -391,16 +391,18 @@ class InterpreterFrameInfo : public Fram
   void pushScratchValue() { masm.pushValue(addressOfScratchValue()); }
 
   void storeStackValue(int32_t depth, const Address& dest,
                        const ValueOperand& scratch) {
     masm.loadValue(addressOfStackValue(depth), scratch);
     masm.storeValue(scratch, dest);
   }
 
+  void bumpInterpreterICEntry();
+
   Address addressOfInterpreterScript() const {
     return Address(BaselineFrameReg,
                    BaselineFrame::reverseOffsetOfInterpreterScript());
   }
   Address addressOfInterpreterPC() const {
     return Address(BaselineFrameReg,
                    BaselineFrame::reverseOffsetOfInterpreterPC());
   }
--- a/js/src/jit/BaselineJIT.cpp
+++ b/js/src/jit/BaselineJIT.cpp
@@ -149,16 +149,20 @@ JitExecStatus jit::EnterBaselineAtBranch
     if (fp->isDebuggee()) {
       MOZ_RELEASE_ASSERT(baseline->hasDebugInstrumentation());
       data.jitcode += MacroAssembler::ToggledCallSize(data.jitcode);
     }
   } else {
     const BaselineInterpreter& interp =
         cx->runtime()->jitRuntime()->baselineInterpreter();
     data.jitcode = interp.interpretOpAddr().value;
+    if (fp->isDebuggee()) {
+      // Skip the debug trap emitted by emitInterpreterLoop.
+      data.jitcode += MacroAssembler::ToggledCallSize(data.jitcode);
+    }
   }
 
   // Note: keep this in sync with SetEnterJitData.
 
   data.osrFrame = fp;
   data.osrNumStackValues =
       fp->script()->nfixed() + cx->interpreterRegs().stackDepth();
 
@@ -418,18 +422,26 @@ bool jit::BaselineCompileFromBaselineInt
     case Method_CantCompile:
     case Method_Skipped:
       *res = nullptr;
       return true;
 
     case Method_Compiled: {
       if (*pc == JSOP_LOOPENTRY) {
         PCMappingSlotInfo slotInfo;
-        *res = script->baselineScript()->nativeCodeForPC(script, pc, &slotInfo);
+        BaselineScript* baselineScript = script->baselineScript();
+        *res = baselineScript->nativeCodeForPC(script, pc, &slotInfo);
         MOZ_ASSERT(slotInfo.isStackSynced());
+        if (frame->isDebuggee()) {
+          // Skip the debug trap emitted by emitInterpreterLoop because the
+          // Baseline Interpreter already handled it for the current op. This
+          // matches EnterBaseline.
+          MOZ_RELEASE_ASSERT(baselineScript->hasDebugInstrumentation());
+          *res += MacroAssembler::ToggledCallSize(*res);
+        }
       } else {
         *res = script->baselineScript()->warmUpCheckPrologueAddr();
       }
       frame->prepareForBaselineInterpreterToJitOSR();
       return true;
     }
   }
 
--- a/js/src/jsdate.cpp
+++ b/js/src/jsdate.cpp
@@ -958,17 +958,17 @@ static bool ParseISOStyleDate(const Char
   }
 
   if (PEEK('+') || PEEK('-')) {
     if (PEEK('-')) {
       dateMul = -1;
     }
     ++i;
     NEED_NDIGITS(6, year);
-  } else if (!PEEK('T')) {
+  } else {
     NEED_NDIGITS(4, year);
   }
   DONE_DATE_UNLESS('-');
   NEED_NDIGITS_OR_LESS(2, month);
   DONE_DATE_UNLESS('-');
   NEED_NDIGITS_OR_LESS(2, day);
 
 done_date:
@@ -1046,16 +1046,17 @@ done:
 
   *result = TimeClip(msec);
   return NumbersAreIdentical(msec, result->toDouble());
 
 #undef PEEK
 #undef NEED
 #undef DONE_UNLESS
 #undef NEED_NDIGITS
+#undef NEED_NDIGITS_OR_LESS
 }
 
 template <typename CharT>
 static bool ParseDate(const CharT* s, size_t length, ClippedTime* result) {
   if (ParseISOStyleDate(s, length, result)) {
     return true;
   }
 
--- a/js/src/tests/non262/Date/15.9.4.2.js
+++ b/js/src/tests/non262/Date/15.9.4.2.js
@@ -60,36 +60,36 @@ function test()
   check("2009-07T19:53:21.001+12:00",    dd(2009,7,1,7,53,21,1));
   check("2009-07T19:53:21+12:00",        dd(2009,7,1,7,53,21,0));
   check("2009-07T19:53+12:00",           dd(2009,7,1,7,53,0,0));
 
   check("2009T19:53:21.001+12:00",       dd(2009,1,1,7,53,21,1));
   check("2009T19:53:21+12:00",           dd(2009,1,1,7,53,21,0));
   check("2009T19:53+12:00",              dd(2009,1,1,7,53,0,0));
 
-  check("T19:53:21.001+12:00",           dd(1970,1,1,7,53,21,1));
-  check("T19:53:21+12:00",               dd(1970,1,1,7,53,21,0));
-  check("T19:53+12:00",                  dd(1970,1,1,7,53,0,0));
+  checkInvalid("T19:53:21.001+12:00");
+  checkInvalid("T19:53:21+12:00");
+  checkInvalid("T19:53+12:00");
 
   // formats without timezone uses the timezone as at that date
   check("2009-07-23T19:53:21.001",       dd(2009,7,23,19,53,21,1)+Jul2009TZ);
   check("2009-07-23T19:53:21",           dd(2009,7,23,19,53,21,0)+Jul2009TZ);
   check("2009-07-23T19:53",              dd(2009,7,23,19,53,0,0)+Jul2009TZ);
 
   check("2009-07T19:53:21.001",          dd(2009,7,1,19,53,21,1)+Jul2009TZ);
   check("2009-07T19:53:21",              dd(2009,7,1,19,53,21,0)+Jul2009TZ);
   check("2009-07T19:53",                 dd(2009,7,1,19,53,0,0)+Jul2009TZ);
 
   check("2009T19:53:21.001",             dd(2009,1,1,19,53,21,1)+Jan2009TZ);
   check("2009T19:53:21",                 dd(2009,1,1,19,53,21,0)+Jan2009TZ);
   check("2009T19:53",                    dd(2009,1,1,19,53,0,0)+Jan2009TZ);
 
-  check("T19:53:21.001",                 dd(1970,1,1,19,53,21,1)+Jan1970TZ);
-  check("T19:53:21",                     dd(1970,1,1,19,53,21,0)+Jan1970TZ);
-  check("T19:53",                        dd(1970,1,1,19,53,0,0)+Jan1970TZ);
+  checkInvalid("T19:53:21.001");
+  checkInvalid("T19:53:21");
+  checkInvalid("T19:53");
 
   // with no time at all assume UTC
   check("2009-07-23",                    dd(2009,7,23,0,0,0,0));
   check("2009-07",                       dd(2009,7,1,0,0,0,0));
   check("2009",                          dd(2009,1,1,0,0,0,0));
 
   // one field too big
   checkInvalid("2009-13-23T19:53:21.001+12:00");
--- a/js/src/tests/non262/Date/non-iso.js
+++ b/js/src/tests/non262/Date/non-iso.js
@@ -34,17 +34,17 @@ assertEq(new Date("1997-03-08 1:19").get
          new Date("1997-03-08T1:19").getTime());
 assertEq(new Date("1997-03-08 1:1").getTime(),
          new Date("1997-03-08T1:1").getTime());
 assertEq(new Date("1997-03-08 1:1:01").getTime(),
          new Date("1997-03-08T1:1:01").getTime());
 assertEq(new Date("1997-03-08 1:1:1").getTime(),
          new Date("1997-03-08T1:1:1").getTime());
 assertEq(new Date("1997-03-08 11").getTime(),
-         new Date("1997-03-08T11").getTime());
+         new Date("1997-03-08T11").getTime()); // Date(NaN)
 assertEq(new Date("1997-03-08").getTime(),
          new Date("1997-03-08").getTime());
 assertEq(new Date("1997-03-8").getTime(),
          new Date("1997-03-08").getTime());
 assertEq(new Date("1997-3-8").getTime(),
          new Date("1997-03-08").getTime());
 assertEq(new Date("1997-3-8 ").getTime(),
          new Date("1997-03-08T").getTime()); // Date(NaN)
--- a/js/src/vm/Realm.h
+++ b/js/src/vm/Realm.h
@@ -806,16 +806,20 @@ class JS::Realm : public JS::shadow::Rea
   void sweepSavedStacks();
 
   static constexpr size_t offsetOfCompartment() {
     return offsetof(JS::Realm, compartment_);
   }
   static constexpr size_t offsetOfRegExps() {
     return offsetof(JS::Realm, regExps);
   }
+  static constexpr size_t offsetOfDebugModeBits() {
+    return offsetof(JS::Realm, debugModeBits_);
+  }
+  static constexpr uint32_t debugModeIsDebuggeeBit() { return IsDebuggee; }
 
   // Note: global_ is a read-barriered object, but it's fine to skip the read
   // barrier when the realm is active. See the comment in JSContext::global().
   static constexpr size_t offsetOfActiveGlobal() {
     static_assert(sizeof(global_) == sizeof(uintptr_t),
                   "JIT code assumes field is pointer-sized");
     return offsetof(JS::Realm, global_);
   }
--- a/mobile/android/app/build.gradle
+++ b/mobile/android/app/build.gradle
@@ -142,16 +142,17 @@ android {
                 } else {
                     exclude 'org/mozilla/gecko/mma/MmaLeanplumImp.java'
                     exclude 'org/mozilla/gecko/mma/LeanplumVariables.java'
                 }
 
                 if (!mozconfig.substs.MOZ_ANDROID_GCM) {
                     exclude 'org/mozilla/gecko/gcm/**/*.java'
                     exclude 'org/mozilla/gecko/push/**/*.java'
+                    exclude 'org/mozilla/gecko/advertising/**'
                 }
 
                 if (!mozconfig.substs.MOZ_ANDROID_GOOGLE_PLAY_SERVICES) {
                     exclude 'org/mozilla/gecko/util/WebAuthnUtils.java'
                 }
             }
 
             res {
@@ -172,16 +173,17 @@ android {
         test {
             java {
                 // Bug 1229149 tracks pushing this into a :services Gradle project.
                 srcDir "${topsrcdir}/mobile/android/services/src/test/java"
 
                 if (!mozconfig.substs.MOZ_ANDROID_GCM) {
                     exclude 'org/mozilla/gecko/gcm/**/*.java'
                     exclude 'org/mozilla/gecko/push/**/*.java'
+                    exclude 'org/mozilla/gecko/advertising/**'
                 }
             }
             resources {
                 // Bug 1229149 tracks pushing this into a :services Gradle project.
                 srcDir "${topsrcdir}/mobile/android/services/src/test/resources"
             }
         }
 
@@ -239,16 +241,18 @@ dependencies {
         implementation "com.google.android.gms:play-services-ads-identifier:$google_play_services_version"
         implementation "com.google.android.gms:play-services-basement:$google_play_services_version"
     }
 
     if (mozconfig.substs.MOZ_ANDROID_GCM) {
         implementation "com.google.android.gms:play-services-basement:$google_play_services_version"
         implementation "com.google.android.gms:play-services-base:$google_play_services_version"
         implementation "com.google.android.gms:play-services-gcm:$google_play_services_version"
+        implementation "com.google.android.gms:play-services-ads-identifier:$google_play_services_version"
+        implementation "org.mindrot:jbcrypt:0.4"
     }
 
     if (mozconfig.substs.MOZ_ANDROID_GOOGLE_PLAY_SERVICES) {
         implementation "com.google.android.gms:play-services-fido:$google_play_services_fido_version"
     }
 
     // Include LeakCanary in local builds, but not in official builds.
     if (mozconfig.substs.MOZILLA_OFFICIAL) {
--- a/mobile/android/base/java/org/mozilla/gecko/BrowserApp.java
+++ b/mobile/android/base/java/org/mozilla/gecko/BrowserApp.java
@@ -133,16 +133,17 @@ import org.mozilla.gecko.tabqueue.TabQue
 import org.mozilla.gecko.tabs.TabHistoryController;
 import org.mozilla.gecko.tabs.TabHistoryController.OnShowTabHistory;
 import org.mozilla.gecko.tabs.TabHistoryFragment;
 import org.mozilla.gecko.tabs.TabHistoryPage;
 import org.mozilla.gecko.tabs.TabsPanel;
 import org.mozilla.gecko.telemetry.TelemetryCorePingDelegate;
 import org.mozilla.gecko.telemetry.TelemetryUploadService;
 import org.mozilla.gecko.telemetry.measurements.SearchCountMeasurements;
+import org.mozilla.gecko.telemetry.TelemetryActivationPingDelegate;
 import org.mozilla.gecko.toolbar.AutocompleteHandler;
 import org.mozilla.gecko.toolbar.BrowserToolbar;
 import org.mozilla.gecko.toolbar.BrowserToolbar.CommitEventSource;
 import org.mozilla.gecko.toolbar.BrowserToolbar.TabEditingState;
 import org.mozilla.gecko.toolbar.PwaConfirm;
 import org.mozilla.gecko.updater.PostUpdateHandler;
 import org.mozilla.gecko.updater.UpdateServiceHelper;
 import org.mozilla.gecko.util.ActivityUtils;
@@ -312,23 +313,25 @@ public class BrowserApp extends GeckoApp
     // (starting the animation), the HomePager is hidden, and the HomePager animation completes,
     // both the web content and the HomePager will be hidden. This flag is used to prevent the
     // race by determining if the web content should be hidden at the animation's end.
     private boolean mHideWebContentOnAnimationEnd;
 
     private final DynamicToolbar mDynamicToolbar = new DynamicToolbar();
 
     private final TelemetryCorePingDelegate mTelemetryCorePingDelegate = new TelemetryCorePingDelegate();
+    private final TelemetryActivationPingDelegate mTelemetryActivationPingDelegate = new TelemetryActivationPingDelegate();
 
     private final List<BrowserAppDelegate> delegates = Collections.unmodifiableList(Arrays.asList(
             new ScreenshotDelegate(),
             new BookmarkStateChangeDelegate(),
             new ReaderViewBookmarkPromotion(),
             new PostUpdateHandler(),
             mTelemetryCorePingDelegate,
+            mTelemetryActivationPingDelegate,
             new OfflineTabStatusDelegate(),
             new AdjustBrowserAppDelegate(mTelemetryCorePingDelegate)
     ));
 
     @NonNull
     private SearchEngineManager mSearchEngineManager; // Contains reference to Context - DO NOT LEAK!
     private OnboardingHelper mOnboardingHelper;       // Contains reference to Context - DO NOT LEAK!
 
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/java/org/mozilla/gecko/advertising/AdvertisingUtil.java
@@ -0,0 +1,35 @@
+package org.mozilla.gecko.advertising;
+
+import android.content.Context;
+import android.util.Log;
+
+import com.google.android.gms.ads.identifier.AdvertisingIdClient;
+
+
+import org.mindrot.jbcrypt.BCrypt;
+import org.mozilla.gecko.annotation.ReflectionTarget;
+
+@ReflectionTarget
+public class AdvertisingUtil {
+    private static final String LOG_TAG = AdvertisingUtil.class.getCanonicalName();
+
+    /* Use the same SALT for all BCrypt hashings. We want the SALT to be stable for all Fennec users but it should differ from the one from Fenix.
+     * Generated using Bcrypt.gensalt(). */
+    private static final String BCRYPT_SALT = "$2a$10$ZfglUfcbmTyaBbAQ7SL9OO";
+
+    /**
+     * Retrieves the advertising ID hashed with BCrypt. Requires Google Play Services. Note: This method must not run on
+     * the main thread.
+     */
+    @ReflectionTarget
+    public static String getAdvertisingId(Context caller) {
+        try {
+            AdvertisingIdClient.Info info = AdvertisingIdClient.getAdvertisingIdInfo(caller);
+            String advertisingId = info.getId();
+            return advertisingId != null ? BCrypt.hashpw(advertisingId, BCRYPT_SALT) : null;
+        } catch (Throwable t) {
+            Log.e(LOG_TAG, "Error retrieving advertising ID. " + t.getMessage());
+        }
+        return null;
+    }
+}
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/java/org/mozilla/gecko/telemetry/TelemetryActivationPingDelegate.java
@@ -0,0 +1,99 @@
+package org.mozilla.gecko.telemetry;
+
+import android.content.Context;
+import android.os.Bundle;
+import android.support.annotation.WorkerThread;
+import android.util.Log;
+
+import org.mozilla.gecko.AppConstants;
+import org.mozilla.gecko.BrowserApp;
+import org.mozilla.gecko.GeckoProfile;
+import org.mozilla.gecko.GeckoThread;
+import org.mozilla.gecko.delegates.BrowserAppDelegate;
+import org.mozilla.gecko.telemetry.pingbuilders.TelemetryActivationPingBuilder;
+import org.mozilla.gecko.util.StringUtils;
+import org.mozilla.gecko.util.ThreadUtils;
+
+import java.io.IOException;
+import java.lang.reflect.Method;
+
+/**
+ * An activity-lifecycle delegate for uploading the activation ping.
+ */
+public class TelemetryActivationPingDelegate extends BrowserAppDelegate {
+    private static final String LOGTAG = StringUtils.safeSubstring(
+            "Gecko" + TelemetryActivationPingDelegate.class.getSimpleName(), 0, 23);
+
+    private TelemetryDispatcher telemetryDispatcher; // lazy
+
+
+    @Override
+    public void onCreate(BrowserApp browserApp, Bundle savedInstanceState) {
+        super.onCreate(browserApp, savedInstanceState);
+        uploadActivationPing(browserApp);
+    }
+
+    private void uploadActivationPing(final BrowserApp activity) {
+        if (!AppConstants.MOZ_ANDROID_GCM) {
+            return;
+        }
+
+        if (TelemetryActivationPingBuilder.activationPingAlreadySent(activity)) {
+            return;
+        }
+
+        ThreadUtils.postToBackgroundThread(() -> {
+            if (activity == null) {
+                return;
+            }
+
+            if (!TelemetryUploadService.isUploadEnabledByAppConfig(activity)) {
+                Log.d(LOGTAG, "Activation ping upload disabled by app config. Returning.");
+                return;
+            }
+
+            String identifier = null;
+
+            try {
+                final Class<?> clazz = Class.forName("org.mozilla.gecko.advertising.AdvertisingUtil");
+                final Method getAdvertisingId = clazz.getMethod("getAdvertisingId", Context.class);
+                identifier = (String) getAdvertisingId.invoke(null, activity);
+            } catch (Exception e) {
+                Log.w(LOGTAG, "Unable to get identifier: " + e);
+            }
+
+            final GeckoProfile profile = GeckoThread.getActiveProfile();
+            String clientID = null;
+            try {
+                clientID = profile.getClientId();
+            } catch (final IOException e) {
+                Log.w(LOGTAG, "Unable to get client ID: " + e);
+                if (identifier == null) {
+                    //Activation ping is mandatory to be sent with either the identifier or the clientID.
+                    Log.d(LOGTAG, "Activation ping failed to send - both identifier and clientID were unable to be retrieved.");
+                    return;
+                }
+            }
+
+            final TelemetryActivationPingBuilder pingBuilder = new TelemetryActivationPingBuilder(activity);
+            if (identifier != null) {
+                pingBuilder.setIdentifier(identifier);
+            } else {
+                pingBuilder.setClientID(clientID);
+            }
+
+            getTelemetryDispatcher().queuePingForUpload(activity, pingBuilder);
+        });
+    }
+
+    @WorkerThread // via constructor
+    private TelemetryDispatcher getTelemetryDispatcher() {
+        if (telemetryDispatcher == null) {
+            final GeckoProfile profile = GeckoThread.getActiveProfile();
+            final String profilePath = profile.getDir().getAbsolutePath();
+            final String profileName = profile.getName();
+            telemetryDispatcher = new TelemetryDispatcher(profilePath, profileName);
+        }
+        return telemetryDispatcher;
+    }
+}
--- a/mobile/android/base/java/org/mozilla/gecko/telemetry/TelemetryCorePingDelegate.java
+++ b/mobile/android/base/java/org/mozilla/gecko/telemetry/TelemetryCorePingDelegate.java
@@ -16,21 +16,21 @@ import android.view.accessibility.Access
 import org.mozilla.gecko.BrowserApp;
 import org.mozilla.gecko.GeckoApp;
 import org.mozilla.gecko.GeckoProfile;
 import org.mozilla.gecko.GeckoSharedPrefs;
 import org.mozilla.gecko.GeckoThread;
 import org.mozilla.gecko.Telemetry;
 import org.mozilla.gecko.TelemetryContract;
 import org.mozilla.gecko.adjust.AttributionHelperListener;
-import org.mozilla.gecko.telemetry.measurements.CampaignIdMeasurements;
 import org.mozilla.gecko.delegates.BrowserAppDelegateWithReference;
 import org.mozilla.gecko.distribution.DistributionStoreCallback;
 import org.mozilla.gecko.search.SearchEngineManager;
 import org.mozilla.gecko.sync.ExtendedJSONObject;
+import org.mozilla.gecko.telemetry.measurements.CampaignIdMeasurements;
 import org.mozilla.gecko.telemetry.measurements.SearchCountMeasurements;
 import org.mozilla.gecko.telemetry.measurements.SessionMeasurements;
 import org.mozilla.gecko.telemetry.pingbuilders.TelemetryCorePingBuilder;
 import org.mozilla.gecko.util.StringUtils;
 import org.mozilla.gecko.util.ThreadUtils;
 
 import java.io.IOException;
 import java.util.List;
@@ -134,57 +134,53 @@ public class TelemetryCorePingDelegate e
         if (getBrowserApp() == null) {
             return;
         }
 
         // The containing method can be called from onStart: queue this work so that
         // the first launch of the activity doesn't trigger profile init too early.
         //
         // Additionally, getAndIncrementSequenceNumber must be called from a worker thread.
-        ThreadUtils.postToBackgroundThread(new Runnable() {
-            @WorkerThread
-            @Override
-            public void run() {
-                final BrowserApp activity = getBrowserApp();
-                if (activity == null) {
-                    return;
-                }
+        ThreadUtils.postToBackgroundThread(() -> {
+            final BrowserApp activity = getBrowserApp();
+            if (activity == null) {
+                return;
+            }
 
-                final GeckoProfile profile = GeckoThread.getActiveProfile();
-                if (!TelemetryUploadService.isUploadEnabledByProfileConfig(activity, profile)) {
-                    Log.d(LOGTAG, "Core ping upload disabled by profile config. Returning.");
-                    return;
-                }
+            final GeckoProfile profile = GeckoThread.getActiveProfile();
+            if (!TelemetryUploadService.isUploadEnabledByProfileConfig(activity, profile)) {
+                Log.d(LOGTAG, "Core ping upload disabled by profile config. Returning.");
+                return;
+            }
 
-                final String clientID;
-                final boolean hadCanaryClientId;
-                try {
-                    clientID = profile.getClientId();
-                    hadCanaryClientId = profile.getIfHadCanaryClientId();
-                } catch (final IOException e) {
-                    Log.w(LOGTAG, "Unable to get client ID properties to generate core ping: " + e);
-                    return;
-                }
+            final String clientID;
+            final boolean hadCanaryClientId;
+            try {
+                clientID = profile.getClientId();
+                hadCanaryClientId = profile.getIfHadCanaryClientId();
+            } catch (final IOException e) {
+                Log.w(LOGTAG, "Unable to get client ID properties to generate core ping: " + e);
+                return;
+            }
 
-                // Each profile can have different telemetry data so we intentionally grab the shared prefs for the profile.
-                final SharedPreferences sharedPrefs = getSharedPreferences(activity);
-                final SessionMeasurements.SessionMeasurementsContainer sessionMeasurementsContainer =
-                        sessionMeasurements.getAndResetSessionMeasurements(activity);
-                final TelemetryCorePingBuilder pingBuilder = new TelemetryCorePingBuilder(activity)
-                        .setClientID(clientID)
-                        .setHadCanaryClientId(hadCanaryClientId)
-                        .setDefaultSearchEngine(TelemetryCorePingBuilder.getEngineIdentifier(engine))
-                        .setProfileCreationDate(TelemetryCorePingBuilder.getProfileCreationDate(activity, profile))
-                        .setSequenceNumber(TelemetryCorePingBuilder.getAndIncrementSequenceNumber(sharedPrefs))
-                        .setSessionCount(sessionMeasurementsContainer.sessionCount)
-                        .setSessionDuration(sessionMeasurementsContainer.elapsedSeconds);
-                maybeSetOptionalMeasurements(activity, sharedPrefs, pingBuilder);
+            // Each profile can have different telemetry data so we intentionally grab the shared prefs for the profile.
+            final SharedPreferences sharedPrefs = getSharedPreferences(activity);
+            final SessionMeasurements.SessionMeasurementsContainer sessionMeasurementsContainer =
+                    sessionMeasurements.getAndResetSessionMeasurements(activity);
+            final TelemetryCorePingBuilder pingBuilder = new TelemetryCorePingBuilder(activity)
+                    .setClientID(clientID)
+                    .setHadCanaryClientId(hadCanaryClientId)
+                    .setDefaultSearchEngine(TelemetryCorePingBuilder.getEngineIdentifier(engine))
+                    .setProfileCreationDate(TelemetryCorePingBuilder.getProfileCreationDate(activity, profile))
+                    .setSequenceNumber(TelemetryCorePingBuilder.getAndIncrementSequenceNumber(sharedPrefs))
+                    .setSessionCount(sessionMeasurementsContainer.sessionCount)
+                    .setSessionDuration(sessionMeasurementsContainer.elapsedSeconds);
+            maybeSetOptionalMeasurements(activity, sharedPrefs, pingBuilder);
 
-                getTelemetryDispatcher(activity).queuePingForUpload(activity, pingBuilder);
-            }
+            getTelemetryDispatcher(activity).queuePingForUpload(activity, pingBuilder);
         });
     }
 
     private void maybeSetOptionalMeasurements(final Context context, final SharedPreferences sharedPrefs,
                                               final TelemetryCorePingBuilder pingBuilder) {
         final String distributionId = sharedPrefs.getString(DistributionStoreCallback.PREF_DISTRIBUTION_ID, null);
         if (distributionId != null) {
             pingBuilder.setOptDistributionID(distributionId);
--- a/mobile/android/base/java/org/mozilla/gecko/telemetry/TelemetryDispatcher.java
+++ b/mobile/android/base/java/org/mozilla/gecko/telemetry/TelemetryDispatcher.java
@@ -4,16 +4,18 @@
  * file, you can obtain one at http://mozilla.org/MPL/2.0/.
  */
 
 package org.mozilla.gecko.telemetry;
 
 import android.content.Context;
 import android.support.annotation.WorkerThread;
 import android.util.Log;
+
+import org.mozilla.gecko.telemetry.pingbuilders.TelemetryActivationPingBuilder;
 import org.mozilla.gecko.telemetry.pingbuilders.TelemetryCorePingBuilder;
 import org.mozilla.gecko.telemetry.pingbuilders.TelemetryCrashPingBuilder;
 import org.mozilla.gecko.telemetry.schedulers.TelemetryUploadScheduler;
 import org.mozilla.gecko.telemetry.schedulers.TelemetryUploadAllPingsImmediatelyScheduler;
 import org.mozilla.gecko.telemetry.stores.TelemetryJSONFilePingStore;
 import org.mozilla.gecko.telemetry.stores.TelemetryPingStore;
 import org.mozilla.gecko.util.ThreadUtils;
 
@@ -86,16 +88,24 @@ public class TelemetryDispatcher {
      * Queues the given ping for upload and potentially schedules upload. This method can be called from any thread.
      */
     public void queuePingForUpload(final Context context, final TelemetryCorePingBuilder pingBuilder) {
         final TelemetryOutgoingPing ping = pingBuilder.build();
         queuePingForUpload(context, ping, coreStore, uploadAllPingsImmediatelyScheduler);
     }
 
     /**
+     * Queues the given ping for upload and potentially schedules upload. This method can be called from any thread.
+     */
+    public void queuePingForUpload(final Context context, final TelemetryActivationPingBuilder pingBuilder) {
+        final TelemetryOutgoingPing ping = pingBuilder.build();
+        queuePingForUpload(context, ping, coreStore, uploadAllPingsImmediatelyScheduler);
+    }
+
+    /**
      * Queues the given crash ping for upload and potentially schedules upload. This method can be called from any thread.
      */
     public void queuePingForUpload(final Context context, final TelemetryCrashPingBuilder pingBuilder) {
         final TelemetryOutgoingPing ping = pingBuilder.build();
         queuePingForUpload(context, ping, crashStore, uploadAllPingsImmediatelyScheduler);
     }
 
     /* package-private */ static class QueuePingRunnable implements Runnable {
--- a/mobile/android/base/java/org/mozilla/gecko/telemetry/TelemetryUploadService.java
+++ b/mobile/android/base/java/org/mozilla/gecko/telemetry/TelemetryUploadService.java
@@ -14,16 +14,17 @@ import org.mozilla.gecko.GeckoProfile;
 import org.mozilla.gecko.JobIdsConstants;
 import org.mozilla.gecko.preferences.GeckoPreferences;
 import org.mozilla.gecko.restrictions.Restrictable;
 import org.mozilla.gecko.restrictions.Restrictions;
 import org.mozilla.gecko.sync.ExtendedJSONObject;
 import org.mozilla.gecko.sync.net.BaseResource;
 import org.mozilla.gecko.sync.net.BaseResourceDelegate;
 import org.mozilla.gecko.sync.net.Resource;
+import org.mozilla.gecko.telemetry.pingbuilders.TelemetryActivationPingBuilder;
 import org.mozilla.gecko.telemetry.stores.TelemetryPingStore;
 import org.mozilla.gecko.util.DateUtil;
 import org.mozilla.gecko.util.NetworkUtils;
 import org.mozilla.gecko.util.StringUtils;
 
 import java.io.IOException;
 import java.net.URISyntaxException;
 import java.security.GeneralSecurityException;
@@ -120,27 +121,46 @@ public class TelemetryUploadService exte
             delegate.setDocID(ping.getDocID());
             final String url = serverSchemeHostPort + "/" + ping.getURLPath();
             uploadPayload(url, ping.getPayload(), delegate);
 
             // There are minimal gains in trying to upload if we already failed one attempt.
             if (delegate.hadConnectionError()) {
                 break;
             }
+
+            checkPingsPersistence(context, ping.getDocID());
         }
 
         final boolean wereAllUploadsSuccessful = !delegate.hadConnectionError();
         if (wereAllUploadsSuccessful) {
             // We don't log individual successful uploads to avoid log spam.
             Log.d(LOGTAG, "Telemetry upload success!");
         }
         store.onUploadAttemptComplete(successfulUploadIDs);
         return wereAllUploadsSuccessful;
     }
 
+    /**
+     * Check if we have any pings that need to persist their succesful upload status in order to prevent further attempts.
+     * E.g. {@link TelemetryActivationPingBuilder}
+     * @param context
+     */
+    private static void checkPingsPersistence(Context context, String successfulUploadID) {
+        final String activationID = TelemetryActivationPingBuilder.getActivationPingId(context);
+
+        if (activationID == null) {
+            return;
+        }
+
+        if (activationID.equals(successfulUploadID)) {
+            TelemetryActivationPingBuilder.setActivationPingSent(context, true);
+        }
+    }
+
     private static void uploadPayload(final String url, final ExtendedJSONObject payload, final ResultDelegate delegate) {
         final BaseResource resource;
         try {
             resource = new BaseResource(url);
         } catch (final URISyntaxException e) {
             Log.w(LOGTAG, "URISyntaxException for server URL when creating BaseResource: returning.");
             return;
         }
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/java/org/mozilla/gecko/telemetry/pingbuilders/TelemetryActivationPingBuilder.java
@@ -0,0 +1,128 @@
+package org.mozilla.gecko.telemetry.pingbuilders;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.os.Build;
+import android.support.annotation.NonNull;
+
+import org.mozilla.gecko.AppConstants;
+import org.mozilla.gecko.GeckoSharedPrefs;
+import org.mozilla.gecko.Locales;
+import org.mozilla.gecko.distribution.DistributionStoreCallback;
+import org.mozilla.gecko.telemetry.TelemetryOutgoingPing;
+import org.mozilla.gecko.util.DateUtil;
+import org.mozilla.gecko.util.StringUtils;
+
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.Calendar;
+import java.util.Locale;
+
+/**
+ * Builds a {@link TelemetryOutgoingPing} representing a activation ping.
+ *
+ */
+public class TelemetryActivationPingBuilder extends TelemetryPingBuilder {
+    private static final String LOGTAG = StringUtils.safeSubstring(TelemetryActivationPingBuilder.class.getSimpleName(), 0, 23);
+
+    //Using MOZ_APP_BASENAME would be more elegant but according to the server side schema we need to be sure that we always send the "Fennec" value.
+    private static final String APP_NAME_VALUE = "Fennec";
+
+    private static final String PREFS_ACTIVATION_ID = "activation_ping_id";
+    private static final String PREFS_ACTIVATION_SENT = "activation_ping_sent";
+
+    private static final String NAME = "activation";
+    private static final int VERSION_VALUE = 1;
+
+    private static final String IDENTIFIER = "identifier";
+    private static final String CLIENT_ID = "clientId";
+    private static final String MANUFACTURER = "manufacturer";
+    private static final String MODEL = "model";
+    private static final String DISTRIBUTION_ID = "distribution_id";
+    private static final String LOCALE = "locale";
+    private static final String OS_ATTR = "os";
+    private static final String OS_VERSION = "osversion";
+    private static final String PING_CREATION_DATE = "created";
+    private static final String TIMEZONE_OFFSET = "tz";
+    private static final String APP_NAME = "app_name";
+    private static final String CHANNEL = "channel";
+
+    public TelemetryActivationPingBuilder(final Context context) {
+        super(VERSION_VALUE, true);
+        initPayloadConstants(context);
+    }
+
+    private void initPayloadConstants(final Context context) {
+        payload.put(MANUFACTURER, Build.MANUFACTURER);
+        payload.put(MODEL, Build.MODEL);
+        payload.put(LOCALE, Locales.getLanguageTag(Locale.getDefault()));
+        payload.put(OS_ATTR, TelemetryPingBuilder.OS_NAME);
+        payload.put(OS_VERSION, Integer.toString(Build.VERSION.SDK_INT)); // A String for cross-platform reasons.
+
+        final Calendar nowCalendar = Calendar.getInstance();
+        final DateFormat pingCreationDateFormat = new SimpleDateFormat("yyyy-MM-dd", Locale.US);
+        payload.put(PING_CREATION_DATE, pingCreationDateFormat.format(nowCalendar.getTime()));
+        payload.put(TIMEZONE_OFFSET, DateUtil.getTimezoneOffsetInMinutesForGivenDate(nowCalendar));
+        payload.put(APP_NAME, APP_NAME_VALUE);
+        payload.put(CHANNEL, AppConstants.ANDROID_PACKAGE_NAME);
+
+        SharedPreferences prefs = GeckoSharedPrefs.forApp(context);
+        final String distributionId = prefs.getString(DistributionStoreCallback.PREF_DISTRIBUTION_ID, null);
+        if (distributionId != null) {
+            payload.put(DISTRIBUTION_ID, distributionId);
+        }
+
+        prefs.edit().putString(PREFS_ACTIVATION_ID, docID).apply();
+    }
+
+    public static boolean activationPingAlreadySent(Context context) {
+        return GeckoSharedPrefs.forApp(context).getBoolean(PREFS_ACTIVATION_SENT, false);
+    }
+
+    public static void setActivationPingSent(Context context, boolean value) {
+        SharedPreferences prefs = GeckoSharedPrefs.forApp(context);
+        prefs.edit().putBoolean(PREFS_ACTIVATION_SENT, value).apply();
+        prefs.edit().remove(PREFS_ACTIVATION_ID).apply();
+    }
+
+    public static String getActivationPingId(Context context) {
+        return GeckoSharedPrefs.forApp(context).getString(PREFS_ACTIVATION_ID, null);
+    }
+
+    @Override
+    public String getDocType() {
+        return NAME;
+    }
+
+    @Override
+    public String[] getMandatoryFields() {
+        return new String[] {
+                MANUFACTURER,
+                MODEL,
+                LOCALE,
+                OS_ATTR,
+                OS_VERSION,
+                PING_CREATION_DATE,
+                TIMEZONE_OFFSET,
+                APP_NAME,
+                CHANNEL
+        };
+    }
+
+    public TelemetryActivationPingBuilder setIdentifier(@NonNull final String identifier) {
+        if (identifier == null) {
+            throw new IllegalArgumentException("Expected non-null identifier");
+        }
+
+        payload.put(IDENTIFIER, identifier);
+        return this;
+    }
+
+    public TelemetryActivationPingBuilder setClientID(@NonNull final String clientID) {
+        if (clientID == null) {
+            throw new IllegalArgumentException("Expected non-null clientID");
+        }
+        payload.put(CLIENT_ID, clientID);
+        return this;
+    }
+}
--- a/mobile/android/base/java/org/mozilla/gecko/telemetry/pingbuilders/TelemetryPingBuilder.java
+++ b/mobile/android/base/java/org/mozilla/gecko/telemetry/pingbuilders/TelemetryPingBuilder.java
@@ -20,16 +20,19 @@ import java.util.UUID;
  * This base class handles the common ping operations under the hood:
  *   * Validating mandatory fields
  *   * Forming the server url
  */
 abstract class TelemetryPingBuilder {
     // In the server url, the initial path directly after the "scheme://host:port/"
     private static final String SERVER_INITIAL_PATH = "submit/telemetry";
 
+    // Modern pings now use a structured ingestion where we capture the schema version as one of the URI parameters.
+    private static final String SERVER_INITIAL_PATH_MODERN = "submit/mobile";
+
     // By default Fennec ping's use the old telemetry version, this can be overridden
     private static final int DEFAULT_TELEMETRY_VERSION = 1;
 
     // Unified telemetry is version 4
     public static final int UNIFIED_TELEMETRY_VERSION = 4;
 
     // We deliberately call the OS/platform Android to avoid confusion with desktop Linux
     public static final String OS_NAME = "Android";
@@ -43,16 +46,22 @@ abstract class TelemetryPingBuilder {
     }
 
     public TelemetryPingBuilder(int version) {
         docID = UUID.randomUUID().toString();
         serverPath = getTelemetryServerPath(getDocType(), docID, version);
         payload = new ExtendedJSONObject();
     }
 
+    public TelemetryPingBuilder(int version, boolean modernPing) {
+        docID = UUID.randomUUID().toString();
+        serverPath = modernPing ? getModernTelemetryServerPath(getDocType(), docID, version) : getTelemetryServerPath(getDocType(), docID, version);
+        payload = new ExtendedJSONObject();
+    }
+
     /**
      * @return the name of the ping (e.g. "core")
      */
     public abstract String getDocType();
 
     /**
      * @return the fields that are mandatory for the resultant ping to be uploaded to
      *         the server. These will be validated before the ping is built.
@@ -95,9 +104,27 @@ abstract class TelemetryPingBuilder {
                 docID + '/' +
                 docType + '/' +
                 appName + '/' +
                 appVersion + '/' +
                 appUpdateChannel + '/' +
                 appBuildId +
                 (version == UNIFIED_TELEMETRY_VERSION ? "?v=4" : "");
     }
+
+    /**
+     * Returns a url of the format:
+     *   http://hostname/submit/mobile/docType/appVersion/docId/
+     *
+     *   User for modern structured ingestion.
+     *
+     * @param docType The name of the ping (e.g. "main")
+     * @param docID A UUID that identifies the ping
+     * @param version The ping format version
+     * @return a url at which to POST the telemetry data to
+     */
+    private static String getModernTelemetryServerPath(final String docType, final String docID, int version) {
+        return SERVER_INITIAL_PATH_MODERN + '/' +
+                docType + '/' +
+                version + '/' +
+                docID;
+    }
 }
--- a/mobile/android/chrome/content/PermissionsHelper.js
+++ b/mobile/android/chrome/content/PermissionsHelper.js
@@ -162,17 +162,17 @@ var PermissionsHelper = {
    *        The permission type string stored in permission manager.
    *        e.g. "geolocation", "indexedDB", "popup"
    */
   clearPermission: function clearPermission(aPrincipal, aType, aContext) {
     // Password saving isn't a nsIPermissionManager permission type, so handle
     // it seperately.
     if (aType == "password") {
       // Get rid of exisiting stored logings
-      let logins = Services.logins.findLogins({}, aURI.displayPrePath, "", "");
+      let logins = Services.logins.findLogins(aURI.displayPrePath, "", "");
       for (let i = 0; i < logins.length; i++) {
         Services.logins.removeLogin(logins[i]);
       }
       // Re-set login saving to enabled
       Services.logins.setLoginSavingEnabled(aURI.displayPrePath, true);
     } else {
       Services.perms.removeFromPrincipal(aPrincipal, aType);
       // Clear content prefs set in ContentPermissionPrompt.js
--- a/mobile/android/chrome/content/aboutLogins.js
+++ b/mobile/android/chrome/content/aboutLogins.js
@@ -280,17 +280,17 @@ var Logins = {
 
     try {
       if ((newUsername === origUsername) && (newPassword === origPassword)) {
         Snackbars.show(gStringBundle.GetStringFromName("editLogin.saved1"), Snackbars.LENGTH_LONG);
         this._showList();
         return;
       }
 
-      let logins = Services.logins.findLogins({}, this._selectedLogin.hostname, this._selectedLogin.formSubmitURL, this._selectedLogin.httpRealm);
+      let logins = Services.logins.findLogins(this._selectedLogin.hostname, this._selectedLogin.formSubmitURL, this._selectedLogin.httpRealm);
 
       for (let i = 0; i < logins.length; i++) {
         if (logins[i].username == origUsername) {
           let propBag = Cc["@mozilla.org/hash-property-bag;1"].
             createInstance(Ci.nsIWritablePropertyBag);
           if (newUsername !== origUsername) {
             propBag.setProperty("username", newUsername);
           }
--- a/mobile/android/chrome/content/browser.js
+++ b/mobile/android/chrome/content/browser.js
@@ -4216,17 +4216,17 @@ Tab.prototype = {
         }
 
         break;
       }
 
       case "DOMFormHasPassword": {
         // Send logins for this hostname to Java.
         let hostname = aEvent.target.baseURIObject.displayPrePath;
-        let foundLogins = Services.logins.findLogins({}, hostname, "", "");
+        let foundLogins = Services.logins.findLogins(hostname, "", "");
         if (foundLogins.length > 0) {
           let displayHost = IdentityHandler.getEffectiveHost();
           let title = { text: displayHost, resource: hostname };
           let selectObj = { title: title, logins: foundLogins };
           GlobalEventDispatcher.sendRequest({
             type: "Doorhanger:Logins",
             data: selectObj,
           });
--- a/mobile/android/components/LoginManagerPrompter.js
+++ b/mobile/android/components/LoginManagerPrompter.js
@@ -258,20 +258,18 @@ LoginManagerPrompter.prototype = {
    *
    * Called when we detect a password change in a form submission, but we
    * don't know which existing login (username) it's for. Asks the user
    * to select a username and confirm the password change.
    *
    * Note: The caller doesn't know the username for aNewLogin, so this
    *       function fills in .username and .usernameField with the values
    *       from the login selected by the user.
-   *
-   * Note; XPCOM stupidity: |count| is just |logins.length|.
    */
-  promptToChangePasswordWithUsernames: function(logins, count, aNewLogin) {
+  promptToChangePasswordWithUsernames: function(logins, aNewLogin) {
     var usernames = logins.map(l => l.username);
     var dialogText  = this._getLocalizedString("userSelectText2");
     var dialogTitle = this._getLocalizedString("passwordChangeTitle");
     var selectedIndex = { value: null };
 
     // If user selects ok, outparam.value is set to the index
     // of the selected username.
     var ok = Services.prompt.select(null,
--- a/mobile/android/components/PromptService.js
+++ b/mobile/android/components/PromptService.js
@@ -362,17 +362,17 @@ InternalPrompt.prototype = {
     let checkMsg = null;
     let check = { value: false };
     let hostname, realm;
     [hostname, realm, aUser] = PromptUtils.getHostnameAndRealm(aPasswordRealm);
 
     let canSave = PromptUtils.canSaveLogin(hostname, aSavePassword);
     if (canSave) {
       // Look for existing logins.
-      let foundLogins = PromptUtils.pwmgr.findLogins({}, hostname, null, realm);
+      let foundLogins = PromptUtils.pwmgr.findLogins(hostname, null, realm);
       [checkMsg, check] = PromptUtils.getUsernameAndPassword(foundLogins, aUser, aPass);
     }
 
     // (eslint-disable: see bug 1177904)
     let ok = false;
     if (aUser)
       ok = this.nsIPrompt_promptUsernameAndPassword(aTitle, aText, aUser, aPass, checkMsg, check); // eslint-disable-line no-undef
     else
@@ -387,17 +387,17 @@ InternalPrompt.prototype = {
   /* ----------  nsIAuthPrompt2  ---------- */
 
   promptAuth: function promptAuth(aChannel, aLevel, aAuthInfo) {
     let checkMsg = null;
     let check = { value: false };
     let message = PromptUtils.makeDialogText(aChannel, aAuthInfo);
     let [username, password] = PromptUtils.getAuthInfo(aAuthInfo);
     let [hostname, httpRealm] = PromptUtils.getAuthTarget(aChannel, aAuthInfo);
-    let foundLogins = PromptUtils.pwmgr.findLogins({}, hostname, null, httpRealm);
+    let foundLogins = PromptUtils.pwmgr.findLogins(hostname, null, httpRealm);
 
     let canSave = PromptUtils.canSaveLogin(hostname, null);
     if (canSave)
       [checkMsg, check] = PromptUtils.getUsernameAndPassword(foundLogins, username, password);
 
     if (username.value && password.value) {
       PromptUtils.setAuthInfo(aAuthInfo, username.value, password.value);
     }
@@ -437,17 +437,17 @@ InternalPrompt.prototype = {
     if (!hashKey)
       return;
 
     // If login manger has logins for this host, defer prompting if we're
     // already waiting on a master password entry.
     let prompt = this._asyncPrompts[hashKey];
     let prompter = prompt.prompter;
     let [hostname, httpRealm] = PromptUtils.getAuthTarget(prompt.channel, prompt.authInfo);
-    let foundLogins = PromptUtils.pwmgr.findLogins({}, hostname, null, httpRealm);
+    let foundLogins = PromptUtils.pwmgr.findLogins(hostname, null, httpRealm);
     if (foundLogins.length > 0 && PromptUtils.pwmgr.uiBusy)
       return;
 
     this._asyncPromptInProgress = true;
     prompt.inProgress = true;
 
     let self = this;
 
--- a/modules/libpref/init/StaticPrefList.h
+++ b/modules/libpref/init/StaticPrefList.h
@@ -2177,16 +2177,23 @@ VARCACHE_PREF(
 // Keep the old default of accepting all cookies
 // In Firefox Desktop this pref is set by browser.contentblocking.features.[standard, strict] see firefox.js for details.
 VARCACHE_PREF(
   "network.cookie.cookieBehavior",
   network_cookie_cookieBehavior,
   RelaxedAtomicInt32, 0
 )
 
+// Stale threshold for cookies in seconds.
+VARCACHE_PREF(
+  "network.cookie.staleThreshold",
+   network_cookie_staleThreshold,
+  uint32_t, 60
+)
+
 // Cookie lifetime policy. Possible values:
 // 0 - accept all cookies
 // 1 - deprecated. don't use it.
 // 2 - accept as session cookies
 // 3 - deprecated. don't use it.
 VARCACHE_PREF(
   "network.cookie.lifetimePolicy",
   network_cookie_lifetimePolicy,
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -2345,17 +2345,16 @@ pref("network.proxy.socks_remote_dns",  
 pref("network.proxy.proxy_over_tls",        true);
 pref("network.proxy.no_proxies_on",         "");
 // Set true to allow resolving proxy for localhost
 pref("network.proxy.allow_hijacking_localhost", false);
 pref("network.proxy.failover_timeout",      1800); // 30 minutes
 pref("network.online",                      true); //online/offline
 pref("network.cookie.thirdparty.sessionOnly", false);
 pref("network.cookie.thirdparty.nonsecureSessionOnly", false);
-pref("network.cookie.same-site.enabled",    true); // Honor the SameSite cookie attribute
 
 // The interval in seconds to move the cookies in the child process.
 // Set to 0 to disable moving the cookies.
 pref("network.cookie.move.interval_sec",    10);
 
 pref("network.cookie.maxNumber", 3000);
 pref("network.cookie.maxPerHost", 180);
 // Cookies quota for each host. If cookies exceed the limit maxPerHost,
--- a/mozglue/linker/moz.build
+++ b/mozglue/linker/moz.build
@@ -16,17 +16,27 @@ SOURCES += [
 Library('linker')
 
 FINAL_LIBRARY = 'mozglue'
 
 DEFINES['IMPL_MFBT'] = True
 
 DisableStlWrapping()
 
-TEST_DIRS += ['tests']
+# Avoid building the linker tests if building with icecc since it doesn't deal
+# well with .incbin.
+#
+# A better solution would be to set ICECC=no in the environment before building
+# these objects to force the local build, but moz.build lacks such a capability
+# at the moment.
+#
+# TODO: Remove this when https://github.com/icecc/icecream/pull/463 is merged
+# and in a release.
+if not CONFIG['CXX_IS_ICECREAM']:
+    TEST_DIRS += ['tests']
 
 if CONFIG['CC_TYPE'] in ('clang', 'gcc'):
     CXXFLAGS += ['-Wno-error=shadow']
 
 DEFINES['XZ_USE_CRC64'] = 1
 
 USE_LIBS += [
     'xz-embedded',
--- a/netwerk/cookie/CookieServiceChild.cpp
+++ b/netwerk/cookie/CookieServiceChild.cpp
@@ -341,17 +341,17 @@ void CookieServiceChild::GetCookieString
       continue;
     }
 
     // if the cookie is secure and the host scheme isn't, we can't send it
     if (cookie->IsSecure() && !isSecure) continue;
 
     int32_t sameSiteAttr = 0;
     cookie->GetSameSite(&sameSiteAttr);
-    if (aIsSameSiteForeign && nsCookieService::IsSameSiteEnabled()) {
+    if (aIsSameSiteForeign) {
       // it if's a cross origin request and the cookie is same site only
       // (strict) don't send it
       if (sameSiteAttr == nsICookie2::SAMESITE_STRICT) {
         continue;
       }
       // if it's a cross origin request, the cookie is same site lax, but it's
       // not a top-level navigation, don't send it
       if (sameSiteAttr == nsICookie2::SAMESITE_LAX && !aIsSafeTopLevelNav) {
--- a/netwerk/cookie/nsCookie.cpp
+++ b/netwerk/cookie/nsCookie.cpp
@@ -1,15 +1,16 @@
 /* -*- Mode: C++; tab-width: 4; 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 "mozilla/Encoding.h"
 #include "mozilla/dom/ToJSValue.h"
+#include "mozilla/StaticPrefs.h"
 #include "nsAutoPtr.h"
 #include "nsCookie.h"
 #include <stdlib.h>
 
 /******************************************************************************
  * nsCookie:
  * string helper impl
  ******************************************************************************/
@@ -121,17 +122,18 @@ size_t nsCookie::SizeOfIncludingThis(
   // members, since the strings are stored in-line with the nsCookie.
   return aMallocSizeOf(this);
 }
 
 bool nsCookie::IsStale() const {
   int64_t currentTimeInUsec = PR_Now();
 
   return currentTimeInUsec - LastAccessed() >
-         CookieStaleThreshold() * PR_USEC_PER_SEC;
+         mozilla::StaticPrefs::network_cookie_staleThreshold() *
+             PR_USEC_PER_SEC;
 }
 
 /******************************************************************************
  * nsCookie:
  * xpcom impl
  ******************************************************************************/
 
 // xpcom getters
--- a/netwerk/cookie/nsCookie.h
+++ b/netwerk/cookie/nsCookie.h
@@ -7,17 +7,16 @@
 #define nsCookie_h__
 
 #include "nsICookie.h"
 #include "nsICookie2.h"
 #include "nsString.h"
 
 #include "mozilla/MemoryReporting.h"
 #include "mozilla/BasePrincipal.h"
-#include "mozilla/Preferences.h"
 
 using mozilla::OriginAttributes;
 
 /**
  * The nsCookie class is the main cookie storage medium for use within cookie
  * code. It implements nsICookie2, which extends nsICookie, a frozen interface
  * for xpcom access of cookie objects.
  */
@@ -50,27 +49,16 @@ class nsCookie final : public nsICookie2
         mLastAccessed(aLastAccessed),
         mCreationTime(aCreationTime),
         mIsSession(aIsSession),
         mIsSecure(aIsSecure),
         mIsHttpOnly(aIsHttpOnly),
         mOriginAttributes(aOriginAttributes),
         mSameSite(aSameSite) {}
 
-  static int CookieStaleThreshold() {
-    static bool initialized = false;
-    static int value = 60;
-    if (!initialized) {
-      mozilla::Preferences::AddIntVarCache(&value,
-                                           "network.cookie.staleThreshold", 60);
-      initialized = true;
-    }
-    return value;
-  }
-
  public:
   // Generate a unique and monotonically increasing creation time. See comment
   // in nsCookie.cpp.
   static int64_t GenerateUniqueCreationTime(int64_t aCreationTime);
 
   // public helper to create an nsCookie object. use |operator delete|
   // to destroy an object created by this method.
   static nsCookie* Create(const nsACString& aName, const nsACString& aValue,
--- a/netwerk/cookie/nsCookieService.cpp
+++ b/netwerk/cookie/nsCookieService.cpp
@@ -75,17 +75,16 @@ using namespace mozilla::net;
 #define DEFAULT_APP_KEY(baseDomain) nsCookieKey(baseDomain, OriginAttributes())
 
 /******************************************************************************
  * nsCookieService impl:
  * useful types & constants
  ******************************************************************************/
 
 static StaticRefPtr<nsCookieService> gCookieService;
-bool nsCookieService::sSameSiteEnabled = false;
 
 // XXX_hack. See bug 178993.
 // This is a hack to hide HttpOnly cookies from older browsers
 #define HTTP_ONLY_PREFIX "#HttpOnly_"
 
 #define COOKIES_FILE "cookies.sqlite"
 #define COOKIES_SCHEMA_VERSION 9
 
@@ -2971,26 +2970,16 @@ bool nsCookieService::DomainMatches(nsCo
                                     const nsACString& aHost) {
   // first, check for an exact host or domain cookie match, e.g. "google.com"
   // or ".google.com"; second a subdomain match, e.g.
   // host = "mail.google.com", cookie domain = ".google.com".
   return aCookie->RawHost() == aHost ||
          (aCookie->IsDomain() && StringEndsWith(aHost, aCookie->Host()));
 }
 
-bool nsCookieService::IsSameSiteEnabled() {
-  static bool prefInitialized = false;
-  if (!prefInitialized) {
-    Preferences::AddBoolVarCache(&sSameSiteEnabled,
-                                 "network.cookie.same-site.enabled", false);
-    prefInitialized = true;
-  }
-  return sSameSiteEnabled;
-}
-
 bool nsCookieService::PathMatches(nsCookie* aCookie, const nsACString& aPath) {
   // calculate cookie path length, excluding trailing '/'
   uint32_t cookiePathLen = aCookie->Path().Length();
   if (cookiePathLen > 0 && aCookie->Path().Last() == '/') --cookiePathLen;
 
   // if the given path is shorter than the cookie path, it doesn't match
   // if the given path doesn't start with the cookie path, it doesn't match.
   if (!StringBeginsWith(aPath, Substring(aCookie->Path(), 0, cookiePathLen)))
@@ -3106,17 +3095,17 @@ void nsCookieService::GetCookiesForURI(
     // check the host, since the base domain lookup is conservative.
     if (!DomainMatches(cookie, hostFromURI)) continue;
 
     // if the cookie is secure and the host scheme isn't, we can't send it
     if (cookie->IsSecure() && !isSecure) continue;
 
     int32_t sameSiteAttr = 0;
     cookie->GetSameSite(&sameSiteAttr);
-    if (aIsSameSiteForeign && IsSameSiteEnabled()) {
+    if (aIsSameSiteForeign) {
       // it if's a cross origin request and the cookie is same site only
       // (strict) don't send it
       if (sameSiteAttr == nsICookie2::SAMESITE_STRICT) {
         continue;
       }
       // if it's a cross origin request, the cookie is same site lax, but it's
       // not a top-level navigation, don't send it
       if (sameSiteAttr == nsICookie2::SAMESITE_LAX && !aIsSafeTopLevelNav) {
@@ -3369,17 +3358,17 @@ bool nsCookieService::CanSetCookie(nsIUR
     COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, aCookieHeader,
                       "non-https cookie can't set secure flag");
     return newCookie;
   }
 
   // If the new cookie is same-site but in a cross site context,
   // browser must ignore the cookie.
   if ((aCookieAttributes.sameSite != nsICookie2::SAMESITE_UNSET) &&
-      aThirdPartyUtil && IsSameSiteEnabled()) {
+      aThirdPartyUtil) {
     // Do not treat loads triggered by web extensions as foreign
     bool addonAllowsLoad = false;
     if (aChannel) {
       nsCOMPtr<nsIURI> channelURI;
       NS_GetFinalChannelURI(aChannel, getter_AddRefs(channelURI));
       nsCOMPtr<nsILoadInfo> loadInfo = aChannel->LoadInfo();
       addonAllowsLoad = BasePrincipal::Cast(loadInfo->TriggeringPrincipal())
                             ->AddonAllowsLoad(channelURI);
--- a/netwerk/cookie/nsCookieService.h
+++ b/netwerk/cookie/nsCookieService.h
@@ -245,17 +245,16 @@ class nsCookieService final : public nsI
   static nsAutoCString GetPathFromURI(nsIURI* aHostURI);
   static nsresult GetBaseDomain(nsIEffectiveTLDService* aTLDService,
                                 nsIURI* aHostURI, nsCString& aBaseDomain,
                                 bool& aRequireHostMatch);
   static nsresult GetBaseDomainFromHost(nsIEffectiveTLDService* aTLDService,
                                         const nsACString& aHost,
                                         nsCString& aBaseDomain);
   static bool DomainMatches(nsCookie* aCookie, const nsACString& aHost);
-  static bool IsSameSiteEnabled();
   static bool PathMatches(nsCookie* aCookie, const nsACString& aPath);
   static bool CanSetCookie(nsIURI* aHostURI, const nsCookieKey& aKey,
                            nsCookieAttributes& aCookieAttributes,
                            bool aRequireHostMatch, CookieStatus aStatus,
                            nsDependentCString& aCookieHeader,
                            int64_t aServerTime, bool aFromHttp,
                            nsIChannel* aChannel, bool& aSetCookie,
                            mozIThirdPartyUtil* aThirdPartyUtil);
--- a/python/mozbuild/mozbuild/action/tooltool.py
+++ b/python/mozbuild/mozbuild/action/tooltool.py
@@ -526,17 +526,17 @@ class Manifest(object):
                 raise InvalidManifest("trying to read invalid manifest file")
 
     def dump(self, output_file, fmt='json'):
         assert fmt in self.valid_formats
         if fmt == 'json':
             rv = json.dump(
                 self.file_records, output_file, indent=2, cls=FileRecordJSONEncoder,
                 separators=(',', ': '))
-            print >> output_file, ''
+            print('', file=output_file)
             return rv
 
     def dumps(self, fmt='json'):
         assert fmt in self.valid_formats
         if fmt == 'json':
             return json.dumps(self.file_records, cls=FileRecordJSONEncoder)
 
 
--- a/python/mozbuild/mozbuild/telemetry.py
+++ b/python/mozbuild/mozbuild/telemetry.py
@@ -213,20 +213,17 @@ def get_build_opts(substs):
                 ('opt', 'MOZ_OPTIMIZE', bool),
                 ('ccache', 'CCACHE', bool),
                 ('sccache', 'MOZ_USING_SCCACHE', bool),
             )
         }
         compiler = substs.get('CC_TYPE', None)
         if compiler:
             opts['compiler'] = str(compiler)
-        # icecream may be enabled by setting CC/CXX to symlinks to icecc,
-        # or if using it together with ccache by setting CCACHE_PREFIX=icecc.
-        prefix = os.path.basename(substs.get('CCACHE_PREFIX', ''))
-        if substs.get('CXX_IS_ICECREAM', None) or prefix == 'icecc':
+        if substs.get('CXX_IS_ICECREAM', None):
             opts['icecream'] = True
         return opts
     except BuildEnvironmentNotFoundException:
         return {}
 
 
 def get_build_attrs(attrs):
     '''
--- a/security/sandbox/moz.build
+++ b/security/sandbox/moz.build
@@ -139,16 +139,20 @@ elif CONFIG['OS_ARCH'] == 'WINNT':
         'chromium/sandbox/win/src/target_process.cc',
         'chromium/sandbox/win/src/target_services.cc',
         'chromium/sandbox/win/src/top_level_dispatcher.cc',
         'chromium/sandbox/win/src/win2k_threadpool.cc',
         'chromium/sandbox/win/src/win_utils.cc',
         'chromium/sandbox/win/src/window.cc',
         'win/SandboxInitialization.cpp',
     ]
+    # Sandbox interceptors can be called before the process's import table
+    # is populated.  Don't let the compiler insert any instrumentation that
+    # might call an import.
+    SOURCES['chromium/sandbox/win/src/process_thread_interception.cc'].no_pgo = True
 
     if CONFIG['CPU_ARCH'] in ('x86_64', 'aarch64'):
         SOURCES += [
             'chromium/sandbox/win/src/interceptors_64.cc',
             'chromium/sandbox/win/src/resolver_64.cc',
             'chromium/sandbox/win/src/service_resolver_64.cc',
         ]
     else:
--- a/services/fxaccounts/FxAccountsStorage.jsm
+++ b/services/fxaccounts/FxAccountsStorage.jsm
@@ -485,17 +485,17 @@ LoginManagerStorage.prototype = {
   // was unlocked (even if no existing logins existed) or false if it was
   // locked (meaning we don't even know if it existed or not.)
   async _clearLoginMgrData() {
     try { // Services.logins might be third-party and broken...
       await Services.logins.initializationPromise;
       if (!this._isLoggedIn) {
         return false;
       }
-      let logins = Services.logins.findLogins({}, FXA_PWDMGR_HOST, null, FXA_PWDMGR_REALM);
+      let logins = Services.logins.findLogins(FXA_PWDMGR_HOST, null, FXA_PWDMGR_REALM);
       for (let login of logins) {
         Services.logins.removeLogin(login);
       }
       return true;
     } catch (ex) {
       log.error("Failed to clear login data: ${}", ex);
       return false;
     }
@@ -531,17 +531,17 @@ LoginManagerStorage.prototype = {
       let login = new loginInfo(FXA_PWDMGR_HOST,
                                 null, // aFormSubmitURL,
                                 FXA_PWDMGR_REALM, // aHttpRealm,
                                 uid, // aUsername
                                 JSON.stringify(contents), // aPassword
                                 "", // aUsernameField
                                 "");// aPasswordField
 
-      let existingLogins = Services.logins.findLogins({}, FXA_PWDMGR_HOST, null,
+      let existingLogins = Services.logins.findLogins(FXA_PWDMGR_HOST, null,
                                                       FXA_PWDMGR_REALM);
       if (existingLogins.length) {
         Services.logins.modifyLogin(existingLogins[0], login);
       } else {
         Services.logins.addLogin(login);
       }
       log.trace("finished write of user data to the login manager");
     } catch (ex) {
@@ -561,17 +561,17 @@ LoginManagerStorage.prototype = {
       // read the data from the login manager and merge it for return.
       await Services.logins.initializationPromise;
 
       if (!this._isLoggedIn) {
         log.info("returning partial account data as the login manager is locked.");
         throw new this.STORAGE_LOCKED();
       }
 
-      let logins = Services.logins.findLogins({}, FXA_PWDMGR_HOST, null, FXA_PWDMGR_REALM);
+      let logins = Services.logins.findLogins(FXA_PWDMGR_HOST, null, FXA_PWDMGR_REALM);
       if (logins.length == 0) {
         // This could happen if the MP was locked when we wrote the data.
         log.info("Can't find any credentials in the login manager");
         return null;
       }
       let login = logins[0];
       // Support either the uid or the email as the username - as of bug 1183951
       // we store the uid, but we support having either for b/w compat.
--- a/services/fxaccounts/tests/xpcshell/test_loginmgr_storage.js
+++ b/services/fxaccounts/tests/xpcshell/test_loginmgr_storage.js
@@ -21,17 +21,17 @@ LoginManagerStorage.prototype.__defineGe
 function setLoginMgrLoggedInState(loggedIn) {
   isLoggedIn = loggedIn;
 }
 
 
 initTestLogging("Trace");
 
 function getLoginMgrData() {
-  let logins = Services.logins.findLogins({}, FXA_PWDMGR_HOST, null, FXA_PWDMGR_REALM);
+  let logins = Services.logins.findLogins(FXA_PWDMGR_HOST, null, FXA_PWDMGR_REALM);
   if (logins.length == 0) {
     return null;
   }
   Assert.equal(logins.length, 1, "only 1 login available");
   return logins[0];
 }
 
 function createFxAccounts() {
--- a/services/sync/modules/browserid_identity.js
+++ b/services/sync/modules/browserid_identity.js
@@ -343,17 +343,17 @@ this.BrowserIDManager.prototype = {
     // nothing to do here until we decide to migrate away from FxA.
   },
 
   /**
    * Deletes Sync credentials from the password manager.
    */
   deleteSyncCredentials() {
     for (let host of Utils.getSyncCredentialsHosts()) {
-      let logins = Services.logins.findLogins({}, host, "", "");
+      let logins = Services.logins.findLogins(host, "", "");
       for (let login of logins) {
         Services.logins.removeLogin(login);
       }
     }
   },
 
   /**
    * Verify the current auth state, unlocking the master-password if necessary.
--- a/services/sync/modules/engines/passwords.js
+++ b/services/sync/modules/engines/passwords.js
@@ -80,17 +80,17 @@ PasswordEngine.prototype = {
   async _syncFinish() {
     await SyncEngine.prototype._syncFinish.call(this);
 
     // Delete the Weave credentials from the server once.
     if (!Svc.Prefs.get("deletePwdFxA", false)) {
       try {
         let ids = [];
         for (let host of Utils.getSyncCredentialsHosts()) {
-          for (let info of Services.logins.findLogins({}, host, "", "")) {
+          for (let info of Services.logins.findLogins(host, "", "")) {
             ids.push(info.QueryInterface(Ci.nsILoginMetaInfo).guid);
           }
         }
         if (ids.length) {
           let coll = new Collection(this.engineURL, null, this.service);
           coll.ids = ids;
           let ret = await coll.delete();
           this._log.debug("Delete result: " + ret);
@@ -115,17 +115,17 @@ PasswordEngine.prototype = {
   },
 
   async _findDupe(item) {
     let login = this._store._nsLoginInfoFromRecord(item);
     if (!login) {
       return null;
     }
 
-    let logins = Services.logins.findLogins({}, login.hostname, login.formSubmitURL, login.httpRealm);
+    let logins = Services.logins.findLogins(login.hostname, login.formSubmitURL, login.httpRealm);
 
     await Async.promiseYield(); // Yield back to main thread after synchronous operation.
 
     // Look for existing logins that match the hostname, but ignore the password.
     for (let local of logins) {
       if (login.matches(local, true) && local instanceof Ci.nsILoginMetaInfo) {
         return local.guid;
       }
@@ -194,31 +194,31 @@ PasswordStore.prototype = {
 
     return info;
   },
 
   async _getLoginFromGUID(id) {
     let prop = this._newPropertyBag();
     prop.setPropertyAsAUTF8String("guid", id);
 
-    let logins = Services.logins.searchLogins({}, prop);
+    let logins = Services.logins.searchLogins(prop);
     await Async.promiseYield(); // Yield back to main thread after synchronous operation.
 
     if (logins.length > 0) {
       this._log.trace(logins.length + " items matching " + id + " found.");
       return logins[0];
     }
 
     this._log.trace("No items matching " + id + " found. Ignoring");
     return null;
   },
 
   async getAllIDs() {
     let items = {};
-    let logins = Services.logins.getAllLogins({});
+    let logins = Services.logins.getAllLogins();
 
     for (let i = 0; i < logins.length; i++) {
       // Skip over Weave password/passphrase entries.
       let metaInfo = logins[i].QueryInterface(Ci.nsILoginMetaInfo);
       if (Utils.getSyncCredentialsHosts().has(metaInfo.hostname)) {
         continue;
       }
 
@@ -397,17 +397,17 @@ class PasswordValidator extends Collecti
       "password",
       "passwordField",
       "username",
       "usernameField",
     ]);
   }
 
   getClientItems() {
-    let logins = Services.logins.getAllLogins({});
+    let logins = Services.logins.getAllLogins();
     let syncHosts = Utils.getSyncCredentialsHosts();
     let result = logins.map(l => l.QueryInterface(Ci.nsILoginMetaInfo))
                        .filter(l => !syncHosts.has(l.hostname));
     return Promise.resolve(result);
   }
 
   normalizeClientItem(item) {
     return {
--- a/services/sync/tests/unit/test_password_engine.js
+++ b/services/sync/tests/unit/test_password_engine.js
@@ -100,17 +100,17 @@ add_task(async function test_password_en
 
   _("Add new login to upload during first sync");
   let newLogin;
   {
     let login = new LoginInfo("https://example.com", "", null, "username",
       "password", "", "");
     Services.logins.addLogin(login);
 
-    let logins = Services.logins.findLogins({}, "https://example.com", "", "");
+    let logins = Services.logins.findLogins("https://example.com", "", "");
     equal(logins.length, 1, "Should find new login in login manager");
     newLogin = logins[0].QueryInterface(Ci.nsILoginMetaInfo);
 
     // Insert a server record that's older, so that we prefer the local one.
     let rec = new LoginRec("passwords", newLogin.guid);
     rec.formSubmitURL = newLogin.formSubmitURL;
     rec.httpRealm = newLogin.httpRealm;
     rec.hostname = newLogin.hostname;
@@ -130,17 +130,17 @@ add_task(async function test_password_en
       "0ldpa55", "", "");
     Services.logins.addLogin(login);
 
     let props = new PropertyBag();
     let localPasswordChangeTime = Date.now() - 1 * 60 * 60 * 24 * 1000;
     props.setProperty("timePasswordChanged", localPasswordChangeTime);
     Services.logins.modifyLogin(login, props);
 
-    let logins = Services.logins.findLogins({}, "https://mozilla.com", "", "");
+    let logins = Services.logins.findLogins("https://mozilla.com", "", "");
     equal(logins.length, 1, "Should find old login in login manager");
     oldLogin = logins[0].QueryInterface(Ci.nsILoginMetaInfo);
     equal(oldLogin.timePasswordChanged, localPasswordChangeTime);
 
     let rec = new LoginRec("passwords", oldLogin.guid);
     rec.hostname = oldLogin.hostname;
     rec.formSubmitURL = oldLogin.formSubmitURL;
     rec.httpRealm = oldLogin.httpRealm;
@@ -159,17 +159,17 @@ add_task(async function test_password_en
 
   try {
     await sync_engine_and_validate_telem(engine, false);
 
     let newRec = collection.cleartext(newLogin.guid);
     equal(newRec.password, "password",
       "Should update remote password for newer login");
 
-    let logins = Services.logins.findLogins({}, "https://mozilla.com", "", "");
+    let logins = Services.logins.findLogins("https://mozilla.com", "", "");
     equal(logins[0].password, "n3wpa55",
       "Should update local password for older login");
   } finally {
     await cleanup(engine, server);
   }
 });
 
 add_task(async function test_password_dupe() {
@@ -202,17 +202,17 @@ add_task(async function test_password_du
 
   _("Create local record with same details and guid1");
   await engine._store.create(Object.assign({}, details, { id: guid1 }));
 
   try {
     _("Perform sync");
     await sync_engine_and_validate_telem(engine, false);
 
-    let logins = Services.logins.findLogins({}, "https://www.example.com", "", "");
+    let logins = Services.logins.findLogins("https://www.example.com", "", "");
 
     equal(logins.length, 1);
     equal(logins[0].QueryInterface(Ci.nsILoginMetaInfo).guid, guid2);
     equal(null, collection.payload(guid1));
   } finally {
     await cleanup(engine, server);
   }
 });
--- a/services/sync/tests/unit/test_password_store.js
+++ b/services/sync/tests/unit/test_password_store.js
@@ -6,24 +6,23 @@ const {Service} = ChromeUtils.import("re
 
 
 async function checkRecord(name, record, expectedCount, timeCreated,
                      expectedTimeCreated, timePasswordChanged,
                      expectedTimePasswordChanged, recordIsUpdated) {
   let engine = Service.engineManager.get("passwords");
   let store = engine._store;
 
-  let count = {};
-  let logins = Services.logins.findLogins(count, record.hostname,
+  let logins = Services.logins.findLogins(record.hostname,
                                           record.formSubmitURL, null);
 
   _("Record" + name + ":" + JSON.stringify(logins));
-  _("Count" + name + ":" + count.value);
+  _("Count" + name + ":" + logins.length);
 
-  Assert.equal(count.value, expectedCount);
+  Assert.equal(logins.length, expectedCount);
 
   if (expectedCount > 0) {
     Assert.ok(!!(await store.getAllIDs())[record.id]);
     let stored_record = logins[0].QueryInterface(Ci.nsILoginMetaInfo);
 
     if (timeCreated !== undefined) {
       Assert.equal(stored_record.timeCreated, expectedTimeCreated);
     }
@@ -165,30 +164,28 @@ add_task(async function run_test() {
 
   let engine = Service.engineManager.get("passwords");
   let store = engine._store;
 
   try {
     Assert.equal((await store.applyIncomingBatch([recordA, recordB])).length, 0);
 
     // Only the good record makes it to Services.logins.
-    let badCount = {};
-    let goodCount = {};
-    let badLogins = Services.logins.findLogins(badCount, recordA.hostname,
+    let badLogins = Services.logins.findLogins(recordA.hostname,
                                                recordA.formSubmitURL,
                                                recordA.httpRealm);
-    let goodLogins = Services.logins.findLogins(goodCount, recordB.hostname,
+    let goodLogins = Services.logins.findLogins(recordB.hostname,
                                                 recordB.formSubmitURL, null);
 
     _("Bad: " + JSON.stringify(badLogins));
     _("Good: " + JSON.stringify(goodLogins));
-    _("Count: " + badCount.value + ", " + goodCount.value);
+    _("Count: " + badLogins.length + ", " + goodLogins.length);
 
-    Assert.equal(goodCount.value, 1);
-    Assert.equal(badCount.value, 0);
+    Assert.equal(goodLogins.length, 1);
+    Assert.equal(badLogins.length, 0);
 
     Assert.ok(!!(await store.getAllIDs())[BOGUS_GUID_B]);
     Assert.ok(!(await store.getAllIDs())[BOGUS_GUID_A]);
 
     await test_LoginRec_toString(store, recordB);
 
     await test_apply_records_with_times("http://afoo.baz.com", undefined, undefined);
     await test_apply_records_with_times("http://bfoo.baz.com", 1000, undefined);
--- a/services/sync/tps/extensions/tps/resource/modules/passwords.jsm
+++ b/services/sync/tps/extensions/tps/resource/modules/passwords.jsm
@@ -93,18 +93,17 @@ Password.prototype = {
    * Find
    *
    * Finds a password entry in the login manager, for the password
    * represented by this object's properties.
    *
    * @return the guid of the password if found, otherwise -1
    */
   Find() {
-    let logins = Services.logins.findLogins({},
-                                            this.props.hostname,
+    let logins = Services.logins.findLogins(this.props.hostname,
                                             this.props.submitURL,
                                             this.props.realm);
     for (var i = 0; i < logins.length; i++) {
       if (logins[i].username == this.props.username &&
           logins[i].password == this.props.password &&
           logins[i].usernameField == this.props.usernameField &&
           logins[i].passwordField == this.props.passwordField) {
         logins[i].QueryInterface(Ci.nsILoginMetaInfo);
--- a/taskcluster/docs/signing.rst
+++ b/taskcluster/docs/signing.rst
@@ -28,30 +28,30 @@ An example signing task payload:
     "payload": {
       "upstreamArtifacts": [{
         "paths": ["public/build/target.dmg"],
         "formats": ["macapp"],
         "taskId": "abcde",
         "taskType": "build"
       }, {
         "paths": ["public/build/target.tar.gz"],
-        "formats": ["gpg"],
+        "formats": ["autograph_gpg"],
         "taskId": "12345",
         "taskType": "build"
       }]
     }
   }
 
 In the above example, scriptworker would download the ``target.dmg`` from task
 ``abcde`` and ``target.tar.gz`` from task ``12345`` and verify their shas and
 task definitions via `chain of trust`_ verification. Then it will launch
 `signingscript`_, which requests a signing token from the signing server pool.
 
 Signingscript determines it wants to sign ``target.dmg`` with the ``macapp``
-format, and ``target.tar.gz`` with the ``gpg`` format. Each of the
+format, and ``target.tar.gz`` with the ``autograph_gpg`` format. Each of the
 `signing formats`_ has their own behavior. After performing any format-specific
 checks or optimizations, it calls `signtool`_ to submit the file to the signing
 servers and poll them for signed output. Once it downloads all of the signed
 output files, it exits and scriptworker uploads the signed binaries.
 
 We can specify multiple paths from a single task for a given set of formats,
 and multiple formats for a given set of paths.
 
@@ -85,23 +85,18 @@ in `60.0`_. To generate these, we have t
 .. _signing formats:
 
 Signing formats
 ---------------
 
 The known signingscript formats are listed in the fourth column of the
 `signing password files`_.
 
-The formats are specified in the ``upstreamArtifacts`` list-of-dicts. The task
-must have a superset of scopes to match. For example, a Firefox signing task
-with an ``upstreamArtifacts`` that lists both ``gpg`` and ``macapp`` formats must
-have both ``project:releng:signing:format:gpg`` and
-``project:releng:signing:format:macapp`` in its scopes.
-
-``gpg`` signing results in a detached ``.asc`` signature file. Because of its
+The formats are specified in the ``upstreamArtifacts`` list-of-dicts.
+``autograph_gpg`` signing results in a detached ``.asc`` signature file. Because of its
 nature, we gpg-sign at the end if given multiple formats for a given set of
 files.
 
 ``jar`` signing is Android apk signing. After signing, we ``zipalign`` the apk.
 This includes the ``focus-jar`` format, which is just a way to specify a different
 set of keys for the Focus app.
 
 ``macapp`` signing accepts either a ``dmg`` or ``tar.gz``; it converts ``dmg``
--- a/taskcluster/taskgraph/transforms/checksums_signing.py
+++ b/taskcluster/taskgraph/transforms/checksums_signing.py
@@ -8,17 +8,16 @@ Transform the checksums signing task int
 from __future__ import absolute_import, print_function, unicode_literals
 
 from taskgraph.loader.single_dep import schema
 from taskgraph.transforms.base import TransformSequence
 from taskgraph.util.attributes import copy_attributes_from_dependent_job
 from taskgraph.util.scriptworker import (
     get_signing_cert_scope,
     get_worker_type_for_scope,
-    add_scope_prefix,
 )
 from taskgraph.util.treeherder import replace_group
 from taskgraph.transforms.task import task_description_schema
 from voluptuous import Required, Optional
 
 checksums_signing_description_schema = schema.extend({
     Required('depname', default='beetmover'): basestring,
     Optional('label'): basestring,
@@ -70,30 +69,29 @@ def make_checksums_signing_description(c
             attributes['locale'] = dep_job.attributes.get('locale')
 
         upstream_artifacts = [{
             "taskId": {"task-reference": "<beetmover>"},
             "taskType": "beetmover",
             "paths": [
                 "public/target.checksums",
             ],
-            "formats": ["gpg"]
+            "formats": ["autograph_gpg"]
         }]
 
         signing_cert_scope = get_signing_cert_scope(config)
         task = {
             'label': label,
             'description': description,
             'worker-type': get_worker_type_for_scope(config, signing_cert_scope),
             'worker': {'implementation': 'scriptworker-signing',
                        'upstream-artifacts': upstream_artifacts,
                        'max-run-time': 3600},
             'scopes': [
                 signing_cert_scope,
-                add_scope_prefix(config, 'signing:format:gpg'),
             ],
             'dependencies': dependencies,
             'attributes': attributes,
             'run-on-projects': dep_job.attributes.get('run_on_projects'),
             'treeherder': treeherder,
         }
 
         yield task
--- a/taskcluster/taskgraph/transforms/geckodriver_signing.py
+++ b/taskcluster/taskgraph/transforms/geckodriver_signing.py
@@ -90,17 +90,17 @@ def make_repackage_signing_description(c
         yield task
 
 
 def _craft_upstream_artifacts(dependency_kind, build_platform):
     if build_platform.startswith('win'):
         signing_format = 'sha2signcode'
         extension = 'zip'
     elif build_platform.startswith('linux'):
-        signing_format = 'gpg'
+        signing_format = 'autograph_gpg'
         extension = 'tar.gz'
     else:
         raise ValueError('Unsupported build platform "{}"'.format(build_platform))
 
     return [{
         'taskId': {'task-reference': '<{}>'.format(dependency_kind)},
         'taskType': 'repackage',
         'paths': ['public/geckodriver.{}'.format(extension)],
--- a/taskcluster/taskgraph/transforms/openh264_signing.py
+++ b/taskcluster/taskgraph/transforms/openh264_signing.py
@@ -59,18 +59,17 @@ def make_signing_description(config, job
 
         scopes = [signing_cert_scope]
 
         if 'win' in build_platform:
             # job['primary-dependency'].task['payload']['command']
             scopes.append(add_scope_prefix(config, "signing:format:sha2signcode"))
             formats = ['sha2signcode']
         else:
-            scopes.append(add_scope_prefix(config, 'signing:format:gpg'))
-            formats = ['gpg']
+            formats = ['autograph_gpg']
 
         rev = attributes['openh264_rev']
         upstream_artifacts = [{
             "taskId": {"task-reference": "<openh264>"},
             "taskType": "build",
             "paths": [
                 "private/openh264/openh264-{}-{}.zip".format(build_platform, rev),
             ],
--- a/taskcluster/taskgraph/transforms/release_generate_checksums_signing.py
+++ b/taskcluster/taskgraph/transforms/release_generate_checksums_signing.py
@@ -8,17 +8,16 @@ Transform the release-generate-checksums
 from __future__ import absolute_import, print_function, unicode_literals
 
 from taskgraph.loader.single_dep import schema
 from taskgraph.transforms.base import TransformSequence
 from taskgraph.util.attributes import copy_attributes_from_dependent_job
 from taskgraph.util.scriptworker import (
     get_signing_cert_scope,
     get_worker_type_for_scope,
-    add_scope_prefix,
 )
 from taskgraph.util.taskcluster import get_artifact_path
 from taskgraph.transforms.task import task_description_schema
 from voluptuous import Required, Optional
 
 release_generate_checksums_signing_schema = schema.extend({
     Required('depname', default='release-generate-checksums'): basestring,
     Optional('label'): basestring,
@@ -56,31 +55,30 @@ def make_release_generate_checksums_sign
 
         upstream_artifacts = [{
             "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"]
+            "formats": ["autograph_gpg"]
         }]
 
         signing_cert_scope = get_signing_cert_scope(config)
 
         task = {
             'label': label,
             'description': description,
             'worker-type': get_worker_type_for_scope(config, signing_cert_scope),
             'worker': {'implementation': 'scriptworker-signing',
                        'upstream-artifacts': upstream_artifacts,
                        'max-run-time': 3600},
             'scopes': [
                 signing_cert_scope,
-                add_scope_prefix(config, 'signing:format:gpg'),
             ],
             'dependencies': dependencies,
             'attributes': attributes,
             'run-on-projects': dep_job.attributes.get('run_on_projects'),
             'treeherder': treeherder,
         }
 
         yield task
--- a/taskcluster/taskgraph/transforms/repackage_signing_partner.py
+++ b/taskcluster/taskgraph/transforms/repackage_signing_partner.py
@@ -67,45 +67,45 @@ def make_repackage_signing_description(c
             dependencies = {"repackage": dep_job.label}
 
         attributes = copy_attributes_from_dependent_job(dep_job)
         attributes['repackage_type'] = 'repackage-signing'
 
         signing_cert_scope = get_signing_cert_scope_per_platform(
             build_platform, is_nightly, config
         )
-        scopes = [signing_cert_scope, add_scope_prefix(config, 'signing:format:gpg')]
+        scopes = [signing_cert_scope]
 
         if 'win' in build_platform:
             upstream_artifacts = [{
                 "taskId": {"task-reference": "<repackage>"},
                 "taskType": "repackage",
                 "paths": [
                     get_artifact_path(dep_job, "{}/target.installer.exe".format(repack_id)),
                 ],
-                "formats": ["sha2signcode", "gpg"]
+                "formats": ["sha2signcode", "autograph_gpg"]
             }]
             scopes.append(add_scope_prefix(config, "signing:format:sha2signcode"))
         elif 'mac' in build_platform:
             upstream_artifacts = [{
                 "taskId": {"task-reference": "<repackage>"},
                 "taskType": "repackage",
                 "paths": [
                     get_artifact_path(dep_job, "{}/target.dmg".format(repack_id)),
                 ],
-                "formats": ["gpg"]
+                "formats": ["autograph_gpg"]
             }]
         elif 'linux' in build_platform:
             upstream_artifacts = [{
                 "taskId": {"task-reference": "<repack>"},
                 "taskType": "repackage",
                 "paths": [
                     get_artifact_path(dep_job, "{}/target.tar.bz2".format(repack_id)),
                 ],
-                "formats": ["gpg"]
+                "formats": ["autograph_gpg"]
             }]
 
         task = {
             'label': label,
             'description': description,
             'worker-type': get_worker_type_for_scope(config, signing_cert_scope),
             'worker': {'implementation': 'scriptworker-signing',
                        'upstream-artifacts': upstream_artifacts,
--- a/taskcluster/taskgraph/transforms/source_checksums_signing.py
+++ b/taskcluster/taskgraph/transforms/source_checksums_signing.py
@@ -8,17 +8,16 @@ Transform the checksums signing task int
 from __future__ import absolute_import, print_function, unicode_literals
 
 from taskgraph.loader.single_dep import schema
 from taskgraph.transforms.base import TransformSequence
 from taskgraph.util.attributes import copy_attributes_from_dependent_job
 from taskgraph.util.scriptworker import (
     get_signing_cert_scope,
     get_worker_type_for_scope,
-    add_scope_prefix,
 )
 from taskgraph.transforms.task import task_description_schema
 from voluptuous import Required, Optional
 
 checksums_signing_description_schema = schema.extend({
     Required('depname', default='beetmover'): basestring,
     Optional('label'): basestring,
     Optional('treeherder'): task_description_schema['treeherder'],
@@ -52,31 +51,30 @@ def make_checksums_signing_description(c
         attributes = copy_attributes_from_dependent_job(dep_job)
 
         upstream_artifacts = [{
             "taskId": {"task-reference": "<beetmover>"},
             "taskType": "beetmover",
             "paths": [
                 "public/target-source.checksums",
             ],
-            "formats": ["gpg"]
+            "formats": ["autograph_gpg"]
         }]
 
         signing_cert_scope = get_signing_cert_scope(config)
 
         task = {
             'label': label,
             'description': description,
             'worker-type': get_worker_type_for_scope(config, signing_cert_scope),
             'worker': {'implementation': 'scriptworker-signing',
                        'upstream-artifacts': upstream_artifacts,
                        'max-run-time': 3600},
             'scopes': [
                 signing_cert_scope,
-                add_scope_prefix(config, 'signing:format:gpg'),
             ],
             'dependencies': dependencies,
             'attributes': attributes,
             'run-on-projects': dep_job.attributes.get('run_on_projects'),
             'treeherder': treeherder,
         }
 
         yield task
--- a/taskcluster/taskgraph/util/signed_artifacts.py
+++ b/taskcluster/taskgraph/util/signed_artifacts.py
@@ -19,17 +19,17 @@ def generate_specifications_of_artifacts
 ):
     build_platform = task.attributes.get('build_platform')
     use_stub = task.attributes.get('stub-installer')
     if kind == 'release-source-signing':
         artifacts_specifications = [{
             'artifacts': [
                 get_artifact_path(task, 'source.tar.xz')
             ],
-            'formats': ['gpg'],
+            'formats': ['autograph_gpg'],
         }]
     elif 'android' in build_platform:
         artifacts_specifications = [{
             'artifacts': [
                 get_artifact_path(task, '{locale}/target.apk'),
             ],
             'formats': ['autograph_apk_fennec_sha1'],
         }]
@@ -59,17 +59,17 @@ def generate_specifications_of_artifacts
 
         if use_stub:
             artifacts_specifications[0]['artifacts'] += [
                 get_artifact_path(task, '{locale}/setup-stub.exe')
             ]
     elif 'linux' in build_platform:
         artifacts_specifications = [{
             'artifacts': [get_artifact_path(task, '{locale}/target.tar.bz2')],
-            'formats': ['gpg', 'widevine'],
+            'formats': ['autograph_gpg', 'widevine'],
         }]
     else:
         raise Exception("Platform not implemented for signing")
 
     if not keep_locale_template:
         artifacts_specifications = _strip_locale_template(artifacts_specifications)
 
     if is_partner_kind(kind):
@@ -103,12 +103,12 @@ def get_signed_artifacts(input, formats)
     """
     Get the list of signed artifacts for the given input and formats.
     """
     artifacts = set()
     if input.endswith('.dmg'):
         artifacts.add(input.replace('.dmg', '.tar.gz'))
     else:
         artifacts.add(input)
-    if 'gpg' in formats:
+    if 'autograph_gpg' in formats:
         artifacts.add('{}.asc'.format(input))
 
     return artifacts
--- a/toolkit/components/passwordmgr/LoginHelper.jsm
+++ b/toolkit/components/passwordmgr/LoginHelper.jsm
@@ -167,24 +167,24 @@ var LoginHelper = {
       }
     }
     return propertyBag.QueryInterface(Ci.nsIPropertyBag)
                       .QueryInterface(Ci.nsIPropertyBag2)
                       .QueryInterface(Ci.nsIWritablePropertyBag2);
   },
 
   /**
-   * Helper to avoid the `count` argument and property bags when calling
+   * Helper to avoid the property bags when calling
    * Services.logins.searchLogins from JS.
    *
    * @param {Object} aSearchOptions - A regular JS object to copy to a property bag before searching
    * @return {nsILoginInfo[]} - The result of calling searchLogins.
    */
   searchLoginsWithObject(aSearchOptions) {
-    return Services.logins.searchLogins({}, this.newPropertyBag(aSearchOptions));
+    return Services.logins.searchLogins(this.newPropertyBag(aSearchOptions));
   },
 
   /**
    * @param {string} aURL
    * @returns {string} which is the hostPort of aURL if supported by the scheme
    *                   otherwise, returns the original aURL.
    */
   maybeGetHostPortForURL(aURL) {
@@ -722,17 +722,17 @@ var LoginHelper = {
 
         if (foundMatchingNewLogin) {
           continue;
         }
       }
 
       // While here we're passing formSubmitURL and httpRealm, they could be empty/null and get
       // ignored in that case, leading to multiple logins for the same username.
-      let existingLogins = Services.logins.findLogins({}, login.hostname,
+      let existingLogins = Services.logins.findLogins(login.hostname,
                                                       login.formSubmitURL,
                                                       login.httpRealm);
       // Check for an existing login that matches *including* the password.
       // If such a login exists, we do not need to add a new login.
       if (existingLogins.some(l => login.matches(l, false /* ignorePassword */))) {
         continue;
       }
       // Now check for a login with the same username, where it may be that we have an
--- a/toolkit/components/passwordmgr/LoginManager.jsm
+++ b/toolkit/components/passwordmgr/LoginManager.jsm
@@ -142,17 +142,17 @@ LoginManager.prototype = {
   _gatherTelemetry(referenceTimeMs) {
     function clearAndGetHistogram(histogramId) {
       let histogram = Services.telemetry.getHistogramById(histogramId);
       histogram.clear();
       return histogram;
     }
 
     clearAndGetHistogram("PWMGR_BLOCKLIST_NUM_SITES").add(
-      this.getAllDisabledHosts({}).length
+      this.getAllDisabledHosts().length
     );
     clearAndGetHistogram("PWMGR_NUM_SAVED_PASSWORDS").add(
       this.countLogins("", "", "")
     );
     clearAndGetHistogram("PWMGR_NUM_HTTPAUTH_PASSWORDS").add(
       this.countLogins("", null, "")
     );
     Services.obs.notifyObservers(null, "weave:telemetry:histogram", "PWMGR_BLOCKLIST_NUM_SITES");
@@ -163,17 +163,17 @@ LoginManager.prototype = {
     clearAndGetHistogram("PWMGR_SAVING_ENABLED").add(LoginHelper.enabled);
     Services.obs.notifyObservers(null, "weave:telemetry:histogram", "PWMGR_SAVING_ENABLED");
 
     // Don't try to get logins if MP is enabled, since we don't want to show a MP prompt.
     if (!this.isLoggedIn) {
       return;
     }
 
-    let logins = this.getAllLogins({});
+    let logins = this.getAllLogins();
 
     let usernamePresentHistogram = clearAndGetHistogram("PWMGR_USERNAME_PRESENT");
     let loginLastUsedDaysHistogram = clearAndGetHistogram("PWMGR_LOGIN_LAST_USED_DAYS");
 
     let hostnameCount = new Map();
     for (let login of logins) {
       usernamePresentHistogram.add(!!login.username);
 
@@ -253,17 +253,17 @@ LoginManager.prototype = {
 
   /**
    * Add a new login to login storage.
    */
   addLogin(login) {
     this._checkLogin(login);
 
     // Look for an existing entry.
-    var logins = this.findLogins({}, login.hostname, login.formSubmitURL,
+    var logins = this.findLogins(login.hostname, login.formSubmitURL,
                                  login.httpRealm);
 
     if (logins.some(l => login.matches(l, true))) {
       throw new Error("This login already exists.");
     }
 
     log.debug("Adding login");
     return this._storage.addLogin(login);
@@ -318,22 +318,21 @@ LoginManager.prototype = {
     log.debug("Modifying login");
     return this._storage.modifyLogin(oldLogin, newLogin);
   },
 
 
   /**
    * Get a dump of all stored logins. Used by the login manager UI.
    *
-   * @param count - only needed for XPCOM.
    * @return {nsILoginInfo[]} - If there are no logins, the array is empty.
    */
-  getAllLogins(count) {
+  getAllLogins() {
     log.debug("Getting a list of all logins");
-    return this._storage.getAllLogins(count);
+    return this._storage.getAllLogins();
   },
 
 
   /**
    * Remove all stored logins.
    */
   removeAllLogins() {
     log.debug("Removing all logins");
@@ -343,68 +342,63 @@ LoginManager.prototype = {
   /**
    * Get a list of all origins for which logins are disabled.
    *
    * @param {Number} count - only needed for XPCOM.
    *
    * @return {String[]} of disabled origins. If there are no disabled origins,
    *                    the array is empty.
    */
-  getAllDisabledHosts(count) {
+  getAllDisabledHosts() {
     log.debug("Getting a list of all disabled origins");
 
     let disabledHosts = [];
     for (let perm of Services.perms.enumerator) {
       if (perm.type == PERMISSION_SAVE_LOGINS && perm.capability == Services.perms.DENY_ACTION) {
         disabledHosts.push(perm.principal.URI.displayPrePath);
       }
     }
 
-    if (count) {
-      count.value = disabledHosts.length;
-    } // needed for XPCOM
-
     log.debug("getAllDisabledHosts: returning", disabledHosts.length, "disabled hosts.");
     return disabledHosts;
   },
 
 
   /**
    * Search for the known logins for entries matching the specified criteria.
    */
-  findLogins(count, origin, formActionOrigin, httpRealm) {
+  findLogins(origin, formActionOrigin, httpRealm) {
     log.debug("Searching for logins matching origin:", origin,
               "formActionOrigin:", formActionOrigin, "httpRealm:", httpRealm);
 
-    return this._storage.findLogins(count, origin, formActionOrigin,
-                                    httpRealm);
+    return this._storage.findLogins(origin, formActionOrigin, httpRealm);
   },
 
 
   /**
    * Public wrapper around _searchLogins to convert the nsIPropertyBag to a
    * JavaScript object and decrypt the results.
    *
    * @return {nsILoginInfo[]} which are decrypted.
    */
-  searchLogins(count, matchData) {
+  searchLogins(matchData) {
     log.debug("Searching for logins");
 
     matchData.QueryInterface(Ci.nsIPropertyBag2);
     if (!matchData.hasKey("guid")) {
       if (!matchData.hasKey("hostname")) {
         log.warn("searchLogins: A `hostname` is recommended");
       }
 
       if (!matchData.hasKey("formSubmitURL") && !matchData.hasKey("httpRealm")) {
         log.warn("searchLogins: `formSubmitURL` or `httpRealm` is recommended");
       }
     }
 
-    return this._storage.searchLogins(count, matchData);
+    return this._storage.searchLogins(matchData);
   },
 
 
   /**
    * Search for the known logins for entries matching the specified criteria,
    * returns only the count.
    */
   countLogins(origin, formActionOrigin, httpRealm) {
--- a/toolkit/components/passwordmgr/LoginManagerParent.jsm
+++ b/toolkit/components/passwordmgr/LoginManagerParent.jsm
@@ -389,18 +389,17 @@ var LoginManagerParent = {
         formLogin.usernameField = oldLogin.usernameField;
 
         prompter.promptToChangePassword(oldLogin, formLogin, dismissedPrompt);
       } else {
         // Note: It's possible that that we already have the correct u+p saved
         // but since we don't have the username, we don't know if the user is
         // changing a second account to the new password so we ask anyways.
 
-        prompter.promptToChangePasswordWithUsernames(
-          logins, logins.length, formLogin);
+        prompter.promptToChangePasswordWithUsernames(logins, formLogin);
       }
 
       return;
     }
 
 
     var existingLogin = null;
     // Look for an existing login that matches the form login.
--- a/toolkit/components/passwordmgr/LoginManagerPrompter.jsm
+++ b/toolkit/components/passwordmgr/LoginManagerPrompter.jsm
@@ -374,18 +374,17 @@ LoginManagerPrompter.prototype = {
       }
 
       // if checkBoxLabel is null, the checkbox won't be shown at all.
       if (canRememberLogin) {
         checkBoxLabel = this._getLocalizedString("rememberPassword");
       }
 
       // Look for existing logins.
-      var foundLogins = Services.logins.findLogins({}, hostname, null,
-                                                   realm);
+      var foundLogins = Services.logins.findLogins(hostname, null, realm);
 
       // XXX Like the original code, we can't deal with multiple
       // account selection. (bug 227632)
       if (foundLogins.length > 0) {
         selectedLogin = foundLogins[0];
 
         // If the caller provided a username, try to use it. If they
         // provided only a password, this will try to find a password-only
@@ -478,18 +477,17 @@ LoginManagerPrompter.prototype = {
 
       // if checkBoxLabel is null, the checkbox won't be shown at all.
       if (canRememberLogin) {
         checkBoxLabel = this._getLocalizedString("rememberPassword");
       }
 
       if (!aPassword.value) {
         // Look for existing logins.
-        var foundLogins = Services.logins.findLogins({}, hostname, null,
-                                                     realm);
+        var foundLogins = Services.logins.findLogins(hostname, null, realm);
 
         // XXX Like the original code, we can't deal with multiple
         // account selection (bug 227632). We can deal with finding the
         // account based on the supplied username - but in this case we'll
         // just return the first match.
         for (var i = 0; i < foundLogins.length; ++i) {
           if (foundLogins[i].username == username) {
             aPassword.value = foundLogins[i].password;
@@ -1253,21 +1251,19 @@ LoginManagerPrompter.prototype = {
   /**
    * Called when we detect a password change in a form submission, but we
    * don't know which existing login (username) it's for. Asks the user
    * to select a username and confirm the password change.
    *
    * Note: The caller doesn't know the username for aNewLogin, so this
    *       function fills in .username and .usernameField with the values
    *       from the login selected by the user.
-   *
-   * Note; XPCOM stupidity: |count| is just |logins.length|.
    */
-  promptToChangePasswordWithUsernames(logins, count, aNewLogin) {
-    this.log("promptToChangePasswordWithUsernames with count:", count);
+  promptToChangePasswordWithUsernames(logins, aNewLogin) {
+    this.log("promptToChangePasswordWithUsernames with count:", logins.length);
 
     var usernames = logins.map(l => l.username || this._getLocalizedString("noUsername"));
     var dialogText  = this._getLocalizedString("userSelectText2");
     var dialogTitle = this._getLocalizedString("passwordChangeTitle");
     var selectedIndex = { value: null };
 
     // If user selects ok, outparam.value is set to the index
     // of the selected username.
--- a/toolkit/components/passwordmgr/nsILoginManager.idl
+++ b/toolkit/components/passwordmgr/nsILoginManager.idl
@@ -92,45 +92,35 @@ interface nsILoginManager : nsISupports 
    */
   void removeAllLogins();
 
 
   /**
    * Fetch all logins in the login manager. An array is always returned;
    * if there are no logins the array is empty.
    *
-   * @param count
-   *        The number of elements in the array. JS callers can simply use
-   *        the array's .length property and omit this param.
-   * @param logins
-   *        An array of nsILoginInfo objects.
+   * @return An array of nsILoginInfo objects.
    *
    * NOTE: This can be called from JS as:
    *       var logins = pwmgr.getAllLogins();
    *       (|logins| is an array).
    */
-  void getAllLogins([optional] out unsigned long count,
-                    [retval, array, size_is(count)] out nsILoginInfo logins);
+  Array<nsILoginInfo> getAllLogins();
 
 
   /**
    * Obtain a list of all hosts for which password saving is disabled.
    *
-   * @param count
-   *        The number of elements in the array. JS callers can simply use
-   *        the array's .length property and omit this param.
-   * @param hostnames
-   *        An array of hostname strings, in origin URL format without a
-   *        pathname. For example: "https://www.site.com".
+   * @return An array of hostname strings, in origin URL format without a
+   *         pathname. For example: "https://www.site.com".
    *
    * NOTE: This can be called from JS as:
-   *       var logins = pwmgr.getDisabledAllLogins();
+   *       var logins = pwmgr.getAllDisabledHosts();
    */
-  void getAllDisabledHosts([optional] out unsigned long count,
-                    [retval, array, size_is(count)] out wstring hostnames);
+  Array<AString> getAllDisabledHosts();
 
 
   /**
    * Check to see if saving logins has been disabled for a host.
    *
    * @param aHost
    *        The hostname to check. This argument should be in the origin
    *        URL format, without a pathname. For example: "http://foo.com".
@@ -152,45 +142,39 @@ interface nsILoginManager : nsISupports 
    */
   void setLoginSavingEnabled(in AString aHost, in boolean isEnabled);
 
 
   /**
    * Search for logins matching the specified criteria. Called when looking
    * for logins that might be applicable to a form or authentication request.
    *
-   * @param count
-   *        The number of elements in the array. JS callers can simply use
-   *        the array's .length property, and supply an dummy object for
-   *        this out param. For example: |findLogins({}, hostname, ...)|
    * @param aHostname
    *        The hostname to restrict searches to, in URL format. For
    *        example: "http://www.site.com".
    *        To find logins for a given nsIURI, you would typically pass in
    *        its prePath.
    * @param aActionURL
    *        For form logins, this argument should be the URL to which the
    *        form will be submitted. For protocol logins, specify null.
    *        An empty string ("") will match any value (except null).
    * @param aHttpRealm
    *        For protocol logins, this argument should be the HTTP Realm
    *        for which the login applies. This is obtained from the
    *        WWW-Authenticate header. See RFC2617. For form logins,
    *        specify null.
    *        An empty string ("") will match any value (except null).
-   * @param logins
-   *        An array of nsILoginInfo objects.
+   * @return An array of nsILoginInfo objects.
    *
    * NOTE: This can be called from JS as:
-   *       var logins = pwmgr.findLogins({}, hostname, ...);
+   *       var logins = pwmgr.findLogins(hostname, ...);
    *
    */
-  void findLogins(out unsigned long count, in AString aHostname,
-                  in AString aActionURL,   in AString aHttpRealm,
-                  [retval, array, size_is(count)] out nsILoginInfo logins);
+  Array<nsILoginInfo> findLogins(in AString aHostname, in AString aActionURL,
+				 in AString aHttpRealm);
 
 
   /**
    * Search for logins matching the specified criteria, as with
    * findLogins(). This interface only returns the number of matching
    * logins (and not the logins themselves), which allows a caller to
    * check for logins without causing the user to be prompted for a master
    * password to decrypt the logins.
@@ -210,33 +194,27 @@ interface nsILoginManager : nsISupports 
    */
   unsigned long countLogins(in AString aHostname, in AString aActionURL,
                             in AString aHttpRealm);
 
   /**
    * Search for logins in the login manager. An array is always returned;
    * if there are no logins the array is empty.
    *
-   * @param count
-   *        The number of elements in the array. JS callers can simply use
-   *        the array's .length property, and supply an dummy object for
-   *        this out param. For example: |searchLogins({}, matchData)|
    * @param matchData
    *        The data used to search. This does not follow the same
    *        requirements as findLogins for those fields. Wildcard matches are
    *        simply not specified.
-   * @param logins
-   *        An array of nsILoginInfo objects.
+   * @return An array of nsILoginInfo objects.
    *
    * NOTE: This can be called from JS as:
-   *       var logins = pwmgr.searchLogins({}, matchData);
+   *       var logins = pwmgr.searchLogins(matchData);
    *       (|logins| is an array).
    */
-  void searchLogins(out unsigned long count, in nsIPropertyBag matchData,
-                    [retval, array, size_is(count)] out nsILoginInfo logins);
+  Array<nsILoginInfo> searchLogins(in nsIPropertyBag matchData);
 
  /**
   * True when a master password prompt is being displayed.
   */
   readonly attribute boolean uiBusy;
 
  /**
   * True when the master password has already been entered, and so a caller
--- a/toolkit/components/passwordmgr/nsILoginManagerPrompter.idl
+++ b/toolkit/components/passwordmgr/nsILoginManagerPrompter.idl
@@ -74,28 +74,25 @@ interface nsILoginManagerPrompter : nsIS
   /**
    * Ask the user if they want to change the password for one of
    * multiple logins, when the caller can't determine exactly which
    * login should be changed. If the user consents, modifyLogin() will
    * be called.
    *
    * @param logins
    *        An array of existing logins.
-   * @param count
-   *        (length of the array)
    * @param aNewLogin
    *        The new login.
    *
    * Note: Because the caller does not know the username of the login
    *       to be changed, aNewLogin.username and aNewLogin.usernameField
    *       will be set (using the user's selection) before modifyLogin()
    *       is called.
    */
   void promptToChangePasswordWithUsernames(
-          [array, size_is(count)] in nsILoginInfo logins,
-          in uint32_t count,
+          in Array<nsILoginInfo> logins,
           in nsILoginInfo aNewLogin);
 };
 %{C++
 
 #define NS_LOGINMANAGERPROMPTER_CONTRACTID "@mozilla.org/login-manager/prompter/;1"
 
 %}
--- a/toolkit/components/passwordmgr/nsILoginManagerStorage.idl
+++ b/toolkit/components/passwordmgr/nsILoginManagerStorage.idl
@@ -104,82 +104,65 @@ interface nsILoginManagerStorage : nsISu
    */
   void removeAllLogins();
 
 
   /**
    * Fetch all logins in the login manager. An array is always returned;
    * if there are no logins the array is empty.
    *
-   * @param count
-   *        The number of elements in the array. JS callers can simply use
-   *        the array's .length property and omit this param.
-   * @param logins
-   *        An array of nsILoginInfo objects.
+   * @return An array of nsILoginInfo objects.
    *
    * NOTE: This can be called from JS as:
    *       var logins = pwmgr.getAllLogins();
    *       (|logins| is an array).
    */
-  void getAllLogins([optional] out unsigned long count,
-                    [retval, array, size_is(count)] out nsILoginInfo logins);
+  Array<nsILoginInfo> getAllLogins();
 
 
   /**
    * Search for logins in the login manager. An array is always returned;
    * if there are no logins the array is empty.
    *
-   * @param count
-   *        The number of elements in the array. JS callers can simply use
-   *        the array's .length property, and supply an dummy object for
-   *        this out param. For example: |searchLogins({}, matchData)|
    * @param matchData
    *        The data used to search. This does not follow the same
    *        requirements as findLogins for those fields. Wildcard matches are
    *        simply not specified.
-   * @param logins
-   *        An array of nsILoginInfo objects.
+   * @return An array of nsILoginInfo objects.
    *
    * NOTE: This can be called from JS as:
-   *       var logins = pwmgr.searchLogins({}, matchData);
+   *       var logins = pwmgr.searchLogins(matchData);
    *       (|logins| is an array).
    */
-  void searchLogins(out unsigned long count, in nsIPropertyBag matchData,
-                    [retval, array, size_is(count)] out nsILoginInfo logins);
+  Array<nsILoginInfo> searchLogins(in nsIPropertyBag matchData);
 
 
   /**
    * Search for logins matching the specified criteria. Called when looking
    * for logins that might be applicable to a form or authentication request.
    *
-   * @param count
-   *        The number of elements in the array. JS callers can simply use
-   *        the array's .length property, and supply an dummy object for
-   *        this out param. For example: |findLogins({}, hostname, ...)|
    * @param aHostname
    *        The hostname to restrict searches to, in URL format. For
    *        example: "http://www.site.com".
    * @param aActionURL
    *        For form logins, this argument should be the URL to which the
    *        form will be submitted. For protocol logins, specify null.
    * @param aHttpRealm
    *        For protocol logins, this argument should be the HTTP Realm
    *        for which the login applies. This is obtained from the
    *        WWW-Authenticate header. See RFC2617. For form logins,
    *        specify null.
-   * @param logins
-   *        An array of nsILoginInfo objects.
+   * @return An array of nsILoginInfo objects.
    *
    * NOTE: This can be called from JS as:
-   *       var logins = pwmgr.findLogins({}, hostname, ...);
+   *       var logins = pwmgr.findLogins(hostname, ...);
    *
    */
-  void findLogins(out unsigned long count, in AString aHostname,
-                  in AString aActionURL,   in AString aHttpRealm,
-                  [retval, array, size_is(count)] out nsILoginInfo logins);
+  Array<nsILoginInfo> findLogins(in AString aHostname, in AString aActionURL,
+				 in AString aHttpRealm);
 
 
   /**
    * Search for logins matching the specified criteria, as with
    * findLogins(). This interface only returns the number of matching
    * logins (and not the logins themselves), which allows a caller to
    * check for logins without causing the user to be prompted for a master
    * password to decrypt the logins.
--- a/toolkit/components/passwordmgr/storage-json.js
+++ b/toolkit/components/passwordmgr/storage-json.js
@@ -205,17 +205,17 @@ this.LoginManagerStorage_json.prototype 
     // Check if the new GUID is duplicate.
     if (newLogin.guid != oldStoredLogin.guid &&
         !this._isGuidUnique(newLogin.guid)) {
       throw new Error("specified GUID already exists");
     }
 
     // Look for an existing entry in case key properties changed.
     if (!newLogin.matches(oldLogin, true)) {
-      let logins = this.findLogins({}, newLogin.hostname,
+      let logins = this.findLogins(newLogin.hostname,
                                    newLogin.formSubmitURL,
                                    newLogin.httpRealm);
 
       if (logins.some(login => newLogin.matches(login, true))) {
         throw new Error("This login already exists.");
       }
     }
 
@@ -243,36 +243,33 @@ this.LoginManagerStorage_json.prototype 
     }
 
     LoginHelper.notifyStorageChanged("modifyLogin", [oldStoredLogin, newLogin]);
   },
 
   /**
    * @return {nsILoginInfo[]}
    */
-  getAllLogins(count) {
+  getAllLogins() {
     let [logins, ids] = this._searchLogins({});
 
     // decrypt entries for caller.
     logins = this._decryptLogins(logins);
 
     this.log("_getAllLogins: returning", logins.length, "logins.");
-    if (count) {
-      count.value = logins.length;
-    } // needed for XPCOM
     return logins;
   },
 
   /**
    * Public wrapper around _searchLogins to convert the nsIPropertyBag to a
    * JavaScript object and decrypt the results.
    *
    * @return {nsILoginInfo[]} which are decrypted.
    */
-  searchLogins(count, matchData) {
+  searchLogins(matchData) {
     let realMatchData = {};
     let options = {};
     // Convert nsIPropertyBag to normal JS object
     for (let prop of matchData.enumerator) {
       switch (prop.name) {
         // Some property names aren't field names but are special options to affect the search.
         case "schemeUpgrades": {
           options[prop.name] = prop.value;
@@ -285,17 +282,16 @@ this.LoginManagerStorage_json.prototype 
       }
     }
 
     let [logins, ids] = this._searchLogins(realMatchData, options);
 
     // Decrypt entries found for the caller.
     logins = this._decryptLogins(logins);
 
-    count.value = logins.length; // needed for XPCOM
     return logins;
   },
 
   /**
    * Private method to perform arbitrary searches on any field. Decryption is
    * left to the caller.
    *
    * Returns [logins, ids] for logins that match the arguments, where logins
@@ -393,17 +389,17 @@ this.LoginManagerStorage_json.prototype 
 
     this.log("Removing all logins");
     this._store.data.logins = [];
     this._store.saveSoon();
 
     LoginHelper.notifyStorageChanged("removeAllLogins", null);
   },
 
-  findLogins(count, hostname, formSubmitURL, httpRealm) {
+  findLogins(hostname, formSubmitURL, httpRealm) {
     let loginData = {
       hostname,
       formSubmitURL,
       httpRealm,
     };
     let matchData = { };
     for (let field of ["hostname", "formSubmitURL", "httpRealm"]) {
       if (loginData[field] != "") {
@@ -411,17 +407,16 @@ this.LoginManagerStorage_json.prototype 
       }
     }
     let [logins, ids] = this._searchLogins(matchData);
 
     // Decrypt entries found for the caller.
     logins = this._decryptLogins(logins);
 
     this.log("_findLogins: returning", logins.length, "logins");
-    count.value = logins.length; // needed for XPCOM
     return logins;
   },
 
   countLogins(hostname, formSubmitURL, httpRealm) {
     let loginData = {
       hostname,
       formSubmitURL,
       httpRealm,
--- a/toolkit/components/passwordmgr/storage-mozStorage.js
+++ b/toolkit/components/passwordmgr/storage-mozStorage.js
@@ -325,17 +325,17 @@ LoginManagerStorage_mozStorage.prototype
     // Check if the new GUID is duplicate.
     if (newLogin.guid != oldStoredLogin.guid &&
         !this._isGuidUnique(newLogin.guid)) {
       throw new Error("specified GUID already exists");
     }
 
     // Look for an existing entry in case key properties changed.
     if (!newLogin.matches(oldLogin, true)) {
-      let logins = this.findLogins({}, newLogin.hostname,
+      let logins = this.findLogins(newLogin.hostname,
                                    newLogin.formSubmitURL,
                                    newLogin.httpRealm);
 
       if (logins.some(login => newLogin.matches(login, true))) {
         throw new Error("This login already exists.");
       }
     }
 
@@ -391,37 +391,34 @@ LoginManagerStorage_mozStorage.prototype
 
     LoginHelper.notifyStorageChanged("modifyLogin", [oldStoredLogin, newLogin]);
   },
 
 
   /**
    * Returns an array of nsILoginInfo.
    */
-  getAllLogins(count) {
+  getAllLogins() {
     let [logins, ids] = this._searchLogins({});
 
     // decrypt entries for caller.
     logins = this._decryptLogins(logins);
 
     this.log("_getAllLogins: returning " + logins.length + " logins.");
-    if (count) {
-      count.value = logins.length;
-    } // needed for XPCOM
     return logins;
   },
 
 
   /**
    * Public wrapper around _searchLogins to convert the nsIPropertyBag to a
    * JavaScript object and decrypt the results.
    *
    * @return {nsILoginInfo[]} which are decrypted.
    */
-  searchLogins(count, matchData) {
+  searchLogins(matchData) {
     let realMatchData = {};
     let options = {};
     // Convert nsIPropertyBag to normal JS object
     for (let prop of matchData.enumerator) {
       switch (prop.name) {
         // Some property names aren't field names but are special options to affect the search.
         case "schemeUpgrades": {
           options[prop.name] = prop.value;
@@ -434,17 +431,16 @@ LoginManagerStorage_mozStorage.prototype
       }
     }
 
     let [logins, ids] = this._searchLogins(realMatchData, options);
 
     // Decrypt entries found for the caller.
     logins = this._decryptLogins(logins);
 
-    count.value = logins.length; // needed for XPCOM
     return logins;
   },
 
 
   /**
    * Private method to perform arbitrary searches on any field. Decryption is
    * left to the caller.
    *
@@ -604,17 +600,17 @@ LoginManagerStorage_mozStorage.prototype
         stmt.reset();
       }
     }
 
     LoginHelper.notifyStorageChanged("removeAllLogins", null);
   },
 
 
-  findLogins(count, hostname, formSubmitURL, httpRealm) {
+  findLogins(hostname, formSubmitURL, httpRealm) {
     let loginData = {
       hostname,
       formSubmitURL,
       httpRealm,
     };
     let matchData = { };
     for (let field of ["hostname", "formSubmitURL", "httpRealm"]) {
       if (loginData[field] != "") {
@@ -622,17 +618,16 @@ LoginManagerStorage_mozStorage.prototype
       }
     }
     let [logins, ids] = this._searchLogins(matchData);
 
     // Decrypt entries found for the caller.
     logins = this._decryptLogins(logins);
 
     this.log("_findLogins: returning " + logins.length + " logins");
-    count.value = logins.length; // needed for XPCOM
     return logins;
   },
 
 
   countLogins(hostname, formSubmitURL, httpRealm) {
     let _countLoginsHelper = (hostname, formSubmitURL, httpRealm) => {
       // Do checks for null and empty strings, adjust conditions and params
       let [conditions, params] =
--- a/toolkit/components/passwordmgr/test/browser/browser_passwordmgr_editing.js
+++ b/toolkit/components/passwordmgr/test/browser/browser_passwordmgr_editing.js
@@ -34,54 +34,54 @@ async function togglePasswords() {
   pwmgrdlg.document.querySelector("#togglePasswords").doCommand();
   await ContentTaskUtils.waitForCondition(() => !signonsTree.columns.getNamedColumn("passwordCol").hidden,
                                           "Waiting for Passwords Column to Show/Hide");
   await new Promise(resolve => waitForFocus(resolve, pwmgrdlg));
   pwmgrdlg.document.documentElement.clientWidth; // flush to ensure UI up-to-date
 }
 
 async function editUsernamePromises(site, oldUsername, newUsername) {
-  is(Services.logins.findLogins({}, site, "", "").length, 1, "Correct login found");
-  let login = Services.logins.findLogins({}, site, "", "")[0];
+  is(Services.logins.findLogins(site, "", "").length, 1, "Correct login found");
+  let login = Services.logins.findLogins(site, "", "")[0];
   is(login.username, oldUsername, "Correct username saved");
   is(getUsername(0), oldUsername, "Correct username shown");
   let focusPromise = BrowserTestUtils.waitForEvent(signonsTree.inputField, "focus", true);
   synthesizeDblClickOnCell(signonsTree, 1, 0);
   await focusPromise;
 
   EventUtils.sendString(newUsername, pwmgrdlg);
   let signonsIntro = doc.querySelector("#signonsIntro");
   EventUtils.sendMouseEvent({type: "click"}, signonsIntro, pwmgrdlg);
   await ContentTaskUtils.waitForCondition(() => !signonsTree.getAttribute("editing"),
                                           "Waiting for editing to stop");
 
-  is(Services.logins.findLogins({}, site, "", "").length, 1, "Correct login replaced");
-  login = Services.logins.findLogins({}, site, "", "")[0];
+  is(Services.logins.findLogins(site, "", "").length, 1, "Correct login replaced");
+  login = Services.logins.findLogins(site, "", "")[0];
   is(login.username, newUsername, "Correct username updated");
   is(getUsername(0), newUsername, "Correct username shown after the update");
 }
 
 async function editPasswordPromises(site, oldPassword, newPassword) {
-  is(Services.logins.findLogins({}, site, "", "").length, 1, "Correct login found");
-  let login = Services.logins.findLogins({}, site, "", "")[0];
+  is(Services.logins.findLogins(site, "", "").length, 1, "Correct login found");
+  let login = Services.logins.findLogins(site, "", "")[0];
   is(login.password, oldPassword, "Correct password saved");
   is(getPassword(0), oldPassword, "Correct password shown");
 
   let focusPromise = BrowserTestUtils.waitForEvent(signonsTree.inputField, "focus", true);
   synthesizeDblClickOnCell(signonsTree, 2, 0);
   await focusPromise;
 
   EventUtils.sendString(newPassword, pwmgrdlg);
   let signonsIntro = doc.querySelector("#signonsIntro");
   EventUtils.sendMouseEvent({type: "click"}, signonsIntro, pwmgrdlg);
   await ContentTaskUtils.waitForCondition(() => !signonsTree.getAttribute("editing"),
                                           "Waiting for editing to stop");
 
-  is(Services.logins.findLogins({}, site, "", "").length, 1, "Correct login replaced");
-  login = Services.logins.findLogins({}, site, "", "")[0];
+  is(Services.logins.findLogins(site, "", "").length, 1, "Correct login replaced");
+  login = Services.logins.findLogins(site, "", "")[0];
   is(login.password, newPassword, "Correct password updated");
   is(getPassword(0), newPassword, "Correct password shown after the update");
 }
 
 add_task(async function test_setup() {
   registerCleanupFunction(function() {
     Services.logins.removeAllLogins();
   });
--- a/toolkit/components/passwordmgr/test/mochitest/test_prompt_promptAuth_proxy.html
+++ b/toolkit/components/passwordmgr/test/mochitest/test_prompt_promptAuth_proxy.html
@@ -156,21 +156,21 @@ add_task(async function test_noAutologin
   action = {
     buttonClick: "ok",
   };
   proxyAuthinfo.username = "";
   proxyAuthinfo.password = "";
   proxyAuthinfo.realm    = "Proxy Realm";
   proxyAuthinfo.flags    = Ci.nsIAuthInformation.AUTH_PROXY;
 
-  var time1 = pwmgr.findLogins({}, mozproxy, null, "Proxy Realm")[0].QueryInterface(Ci.nsILoginMetaInfo).timeLastUsed;
+  var time1 = pwmgr.findLogins(mozproxy, null, "Proxy Realm")[0].QueryInterface(Ci.nsILoginMetaInfo).timeLastUsed;
   promptDone = handlePrompt(state, action);
   isOk = prompter2.promptAuth(proxyChannel, level, proxyAuthinfo);
   await promptDone;
-  var time2 = pwmgr.findLogins({}, mozproxy, null, "Proxy Realm")[0].QueryInterface(Ci.nsILoginMetaInfo).timeLastUsed;
+  var time2 = pwmgr.findLogins(mozproxy, null, "Proxy Realm")[0].QueryInterface(Ci.nsILoginMetaInfo).timeLastUsed;
 
   ok(isOk, "Checking dialog return value (accept)");
   isnot(time1, time2, "Checking that timeLastUsed was updated");
   is(proxyAuthinfo.username, "proxuser", "Checking returned username");
   is(proxyAuthinfo.password, "proxpass", "Checking returned password");
 });
 
 add_task(async function test_autologin() {
@@ -179,19 +179,19 @@ add_task(async function test_autologin()
   // Enable the autologin pref.
   SpecialPowers.Services.prefs.setBoolPref("signon.autologin.proxy", true);
 
   proxyAuthinfo.username = "";
   proxyAuthinfo.password = "";
   proxyAuthinfo.realm    = "Proxy Realm";
   proxyAuthinfo.flags    = Ci.nsIAuthInformation.AUTH_PROXY;
 
-  let time1 = pwmgr.findLogins({}, mozproxy, null, "Proxy Realm")[0].QueryInterface(Ci.nsILoginMetaInfo).timeLastUsed;
+  let time1 = pwmgr.findLogins(mozproxy, null, "Proxy Realm")[0].QueryInterface(Ci.nsILoginMetaInfo).timeLastUsed;
   isOk = prompter2.promptAuth(proxyChannel, level, proxyAuthinfo);
-  let time2 = pwmgr.findLogins({}, mozproxy, null, "Proxy Realm")[0].QueryInterface(Ci.nsILoginMetaInfo).timeLastUsed;
+  let time2 = pwmgr.findLogins(mozproxy, null, "Proxy Realm")[0].QueryInterface(Ci.nsILoginMetaInfo).timeLastUsed;
 
   ok(isOk, "Checking dialog return value (accept)");
   isnot(time1, time2, "Checking that timeLastUsed was updated");
   is(proxyAuthinfo.username, "proxuser", "Checking returned username");
   is(proxyAuthinfo.password, "proxpass", "Checking returned password");
 });
 
 add_task(async function test_autologin_incorrect() {
@@ -215,21 +215,21 @@ add_task(async function test_autologin_i
     buttonClick: "ok",
   };
 
   proxyAuthinfo.username = "";
   proxyAuthinfo.password = "";
   proxyAuthinfo.realm    = "Proxy Realm";
   proxyAuthinfo.flags    = (Ci.nsIAuthInformation.AUTH_PROXY | Ci.nsIAuthInformation.PREVIOUS_FAILED);
 
-  let time1 = pwmgr.findLogins({}, mozproxy, null, "Proxy Realm")[0].QueryInterface(Ci.nsILoginMetaInfo).timeLastUsed;
+  let time1 = pwmgr.findLogins(mozproxy, null, "Proxy Realm")[0].QueryInterface(Ci.nsILoginMetaInfo).timeLastUsed;
   promptDone = handlePrompt(state, action);
   isOk = prompter2.promptAuth(proxyChannel, level, proxyAuthinfo);
   await promptDone;
-  let time2 = pwmgr.findLogins({}, mozproxy, null, "Proxy Realm")[0].QueryInterface(Ci.nsILoginMetaInfo).timeLastUsed;
+  let time2 = pwmgr.findLogins(mozproxy, null, "Proxy Realm")[0].QueryInterface(Ci.nsILoginMetaInfo).timeLastUsed;
 
   ok(isOk, "Checking dialog return value (accept)");
   isnot(time1, time2, "Checking that timeLastUsed was updated");
   is(proxyAuthinfo.username, "proxuser", "Checking returned username");
   is(proxyAuthinfo.password, "proxpass", "Checking returned password");
 });
 
 add_task(async function test_autologin_private() {
--- a/toolkit/components/passwordmgr/test/unit/test_legacy_empty_formSubmitURL.js
+++ b/toolkit/components/passwordmgr/test/unit/test_legacy_empty_formSubmitURL.js
@@ -64,41 +64,40 @@ add_task(function test_addLogin_wildcard
 /**
  * Verifies that findLogins, searchLogins, and countLogins include all logins
  * that have an empty formSubmitURL in the store, even when a formSubmitURL is
  * specified.
  */
 add_task(function test_search_all_wildcard() {
   // Search a given formSubmitURL on any host.
   let matchData = newPropertyBag({ formSubmitURL: "http://www.example.com" });
-  Assert.equal(Services.logins.searchLogins({}, matchData).length, 2);
+  Assert.equal(Services.logins.searchLogins(matchData).length, 2);
 
-  Assert.equal(Services.logins.findLogins({}, "", "http://www.example.com",
+  Assert.equal(Services.logins.findLogins("", "http://www.example.com",
                                           null).length, 2);
 
   Assert.equal(Services.logins.countLogins("", "http://www.example.com",
                                            null), 2);
 
   // Restrict the search to one host.
   matchData.setProperty("hostname", "http://any.example.com");
-  Assert.equal(Services.logins.searchLogins({}, matchData).length, 1);
+  Assert.equal(Services.logins.searchLogins(matchData).length, 1);
 
-  Assert.equal(Services.logins.findLogins({}, "http://any.example.com",
+  Assert.equal(Services.logins.findLogins("http://any.example.com",
                                           "http://www.example.com",
                                           null).length, 1);
 
   Assert.equal(Services.logins.countLogins("http://any.example.com",
                                            "http://www.example.com",
                                            null), 1);
 });
 
 /**
  * Verifies that specifying an empty string for formSubmitURL in searchLogins
  * includes only logins that have an empty formSubmitURL in the store.
  */
 add_task(function test_searchLogins_wildcard() {
-  let logins = Services.logins.searchLogins({},
-                                            newPropertyBag({ formSubmitURL: "" }));
+  let logins = Services.logins.searchLogins(newPropertyBag({ formSubmitURL: "" }));
 
   let loginInfo = TestData.formLogin({ hostname: "http://any.example.com",
                                        formSubmitURL: "" });
   LoginTestUtils.assertLoginListsEqual(logins, [loginInfo]);
 });
--- a/toolkit/components/passwordmgr/test/unit/test_logins_decrypt_failure.js
+++ b/toolkit/components/passwordmgr/test/unit/test_logins_decrypt_failure.js
@@ -33,38 +33,38 @@ add_task(function test_logins_decrypt_fa
     Services.logins.addLogin(loginInfo);
   }
 
   // This makes the existing logins non-decryptable.
   resetMasterPassword();
 
   // These functions don't see the non-decryptable entries anymore.
   Assert.equal(Services.logins.getAllLogins().length, 0);
-  Assert.equal(Services.logins.findLogins({}, "", "", "").length, 0);
-  Assert.equal(Services.logins.searchLogins({}, newPropertyBag()).length, 0);
+  Assert.equal(Services.logins.findLogins("", "", "").length, 0);
+  Assert.equal(Services.logins.searchLogins(newPropertyBag()).length, 0);
   Assert.throws(() => Services.logins.modifyLogin(logins[0], newPropertyBag()),
                 /No matching logins/);
   Assert.throws(() => Services.logins.removeLogin(logins[0]),
                 /No matching logins/);
 
   // The function that counts logins sees the non-decryptable entries also.
   Assert.equal(Services.logins.countLogins("", "", ""), logins.length);
 
   // Equivalent logins can be added.
   for (let loginInfo of logins) {
     Services.logins.addLogin(loginInfo);
   }
   LoginTestUtils.checkLogins(logins);
   Assert.equal(Services.logins.countLogins("", "", ""), logins.length * 2);
 
   // Finding logins doesn't return the non-decryptable duplicates.
-  Assert.equal(Services.logins.findLogins({}, "http://www.example.com",
+  Assert.equal(Services.logins.findLogins("http://www.example.com",
                                           "", "").length, 1);
   let matchData = newPropertyBag({ hostname: "http://www.example.com" });
-  Assert.equal(Services.logins.searchLogins({}, matchData).length, 1);
+  Assert.equal(Services.logins.searchLogins(matchData).length, 1);
 
   // Removing single logins does not remove non-decryptable logins.
   for (let loginInfo of TestData.loginList()) {
     Services.logins.removeLogin(loginInfo);
   }
   Assert.equal(Services.logins.getAllLogins().length, 0);
   Assert.equal(Services.logins.countLogins("", "", ""), logins.length);
 
@@ -95,29 +95,29 @@ add_task(function test_add_logins_with_d
 
   Services.logins.addLogin(login);
 
   // We can search for this login by GUID.
   let searchProp = Cc["@mozilla.org/hash-property-bag;1"]
                    .createInstance(Ci.nsIWritablePropertyBag2);
   searchProp.setPropertyAsAUTF8String("guid", login.guid);
 
-  equal(Services.logins.searchLogins({}, searchProp).length, 1);
+  equal(Services.logins.searchLogins(searchProp).length, 1);
 
   // We should fail to re-add it as it remains good.
   Assert.throws(() => Services.logins.addLogin(login),
                 /This login already exists./);
   // We should fail to re-add a different login with the same GUID.
   Assert.throws(() => Services.logins.addLogin(loginDupeGuid),
                 /specified GUID already exists/);
 
   // This makes the existing login non-decryptable.
   resetMasterPassword();
 
   // We can no longer find it in our search.
-  equal(Services.logins.searchLogins({}, searchProp).length, 0);
+  equal(Services.logins.searchLogins(searchProp).length, 0);
 
   // So we should be able to re-add a login with that same GUID.
   Services.logins.addLogin(login);
-  equal(Services.logins.searchLogins({}, searchProp).length, 1);
+  equal(Services.logins.searchLogins(searchProp).length, 1);
 
   Services.logins.removeAllLogins();
 });
--- a/toolkit/components/passwordmgr/test/unit/test_logins_metainfo.js
+++ b/toolkit/components/passwordmgr/test/unit/test_logins_metainfo.js
@@ -19,17 +19,17 @@ XPCOMUtils.defineLazyServiceGetter(this,
 var gLooksLikeUUIDRegex = /^\{\w{8}-\w{4}-\w{4}-\w{4}-\w{12}\}$/;
 
 /**
  * Retrieves the only login among the current data that matches the hostname of
  * the given nsILoginInfo.  In case there is more than one login for the
  * hostname, the test fails.
  */
 function retrieveLoginMatching(aLoginInfo) {
-  let logins = Services.logins.findLogins({}, aLoginInfo.hostname, "", "");
+  let logins = Services.logins.findLogins(aLoginInfo.hostname, "", "");
   Assert.equal(logins.length, 1);
   return logins[0].QueryInterface(Ci.nsILoginMetaInfo);
 }
 
 /**
  * Checks that the nsILoginInfo and nsILoginMetaInfo properties of two different
  * login instances are equal.
  */
@@ -230,33 +230,33 @@ add_task(function test_modifyLogin_nsIPr
   LoginTestUtils.checkLogins([gLoginInfo1, gLoginInfo2, gLoginInfo3]);
 });
 
 /**
  * Tests searching logins using nsILoginMetaInfo properties.
  */
 add_task(function test_searchLogins_metainfo() {
   // Find by GUID.
-  let logins = Services.logins.searchLogins({}, newPropertyBag({
+  let logins = Services.logins.searchLogins(newPropertyBag({
     guid: gLoginMetaInfo1.guid,
   }));
   Assert.equal(logins.length, 1);
   let foundLogin = logins[0].QueryInterface(Ci.nsILoginMetaInfo);
   assertMetaInfoEqual(foundLogin, gLoginMetaInfo1);
 
   // Find by timestamp.
-  logins = Services.logins.searchLogins({}, newPropertyBag({
+  logins = Services.logins.searchLogins(newPropertyBag({
     timePasswordChanged: gLoginMetaInfo2.timePasswordChanged,
   }));
   Assert.equal(logins.length, 1);
   foundLogin = logins[0].QueryInterface(Ci.nsILoginMetaInfo);
   assertMetaInfoEqual(foundLogin, gLoginMetaInfo2);
 
   // Find using two properties at the same time.
-  logins = Services.logins.searchLogins({}, newPropertyBag({
+  logins = Services.logins.searchLogins(newPropertyBag({
     guid: gLoginMetaInfo3.guid,
     timePasswordChanged: gLoginMetaInfo3.timePasswordChanged,
   }));
   Assert.equal(logins.length, 1);
   foundLogin = logins[0].QueryInterface(Ci.nsILoginMetaInfo);
   assertMetaInfoEqual(foundLogin, gLoginMetaInfo3);
 });
 
--- a/toolkit/components/passwordmgr/test/unit/test_logins_search.js
+++ b/toolkit/components/passwordmgr/test/unit/test_logins_search.js
@@ -36,19 +36,17 @@ function buildExpectedLogins(aQuery) {
  *        don't make the current test meaningless.
  */
 function checkSearchLogins(aQuery, aExpectedCount) {
   info("Testing searchLogins for " + JSON.stringify(aQuery));
 
   let expectedLogins = buildExpectedLogins(aQuery);
   Assert.equal(expectedLogins.length, aExpectedCount);
 
-  let outCount = {};
-  let logins = Services.logins.searchLogins(outCount, newPropertyBag(aQuery));
-  Assert.equal(outCount.value, expectedLogins.length);
+  let logins = Services.logins.searchLogins(newPropertyBag(aQuery));
   LoginTestUtils.assertLoginListsEqual(logins, expectedLogins);
 }
 
 /**
  * Tests findLogins, searchLogins, and countLogins with the same query.
  *
  * @param aQuery
  *        The "hostname", "formSubmitURL", and "httpRealm" properties of this
@@ -69,20 +67,17 @@ function checkAllSearches(aQuery, aExpec
   // The findLogins and countLogins functions support wildcard matches by
   // specifying empty strings as parameters, while searchLogins requires
   // omitting the property entirely.
   let hostname = ("hostname" in aQuery) ? aQuery.hostname : "";
   let formSubmitURL = ("formSubmitURL" in aQuery) ? aQuery.formSubmitURL : "";
   let httpRealm = ("httpRealm" in aQuery) ? aQuery.httpRealm : "";
 
   // Test findLogins.
-  let outCount = {};
-  let logins = Services.logins.findLogins(outCount, hostname, formSubmitURL,
-                                          httpRealm);
-  Assert.equal(outCount.value, expectedLogins.length);
+  let logins = Services.logins.findLogins(hostname, formSubmitURL, httpRealm);
   LoginTestUtils.assertLoginListsEqual(logins, expectedLogins);
 
   // Test countLogins.
   let count = Services.logins.countLogins(hostname, formSubmitURL, httpRealm);
   Assert.equal(count, expectedLogins.length);
 
   // Test searchLogins.
   checkSearchLogins(aQuery, aExpectedCount);
@@ -167,18 +162,17 @@ add_task(function test_searchLogins() {
   checkSearchLogins({ hostname: "http://www6.example.com",
                       usernameField: "" }, 1);
 });
 
 /**
  * Tests searchLogins with invalid arguments.
  */
 add_task(function test_searchLogins_invalid() {
-  Assert.throws(() => Services.logins.searchLogins({},
-                                                   newPropertyBag({ username: "value" })),
+  Assert.throws(() => Services.logins.searchLogins(newPropertyBag({ username: "value" })),
                 /Unexpected field/);
 });
 
 /**
  * Tests that matches are case-sensitive, compare the full field value, and are
  * strict when interpreting the prePath of URIs.
  */
 add_task(function test_search_all_full_case_sensitive() {
--- a/toolkit/components/passwordmgr/test/unit/test_search_schemeUpgrades.js
+++ b/toolkit/components/passwordmgr/test/unit/test_search_schemeUpgrades.js
@@ -42,19 +42,17 @@ function buildExpectedLogins(aQuery) {
  *        don't make the current test meaningless.
  */
 function checkSearch(aQuery, aExpectedCount) {
   info("Testing searchLogins for " + JSON.stringify(aQuery));
 
   let expectedLogins = buildExpectedLogins(aQuery);
   Assert.equal(expectedLogins.length, aExpectedCount);
 
-  let outCount = {};
-  let logins = Services.logins.searchLogins(outCount, newPropertyBag(aQuery));
-  Assert.equal(outCount.value, expectedLogins.length);
+  let logins = Services.logins.searchLogins(newPropertyBag(aQuery));
   LoginTestUtils.assertLoginListsEqual(logins, expectedLogins);
 }
 
 /**
  * Prepare data for the following tests.
  */
 add_task(function test_initialize() {
   for (let login of TestData.loginList()) {
--- a/toolkit/components/reader/test/browser_bug1453818_samesite_cookie.js
+++ b/toolkit/components/reader/test/browser_bug1453818_samesite_cookie.js
@@ -20,34 +20,28 @@ async function clickLink(browser) {
 async function checkCookiePresent(browser) {
   await ContentTask.spawn(browser, null, async function() {
     let cookieSpan = content.document.getElementById("cookieSpan");
     ok(cookieSpan, "cookieSpan element should be in document");
     is(cookieSpan.textContent, "foo=bar", "The SameSite cookie was sent correctly.");
   });
 }
 
-async function checkCookie(sameSiteEnabled, browser) {
-  if (sameSiteEnabled) {
-    info("Check that the SameSite cookie was not sent.");
-    await ContentTask.spawn(browser, null, async function() {
-      let cookieSpan = content.document.getElementById("cookieSpan");
-      ok(cookieSpan, "cookieSpan element should be in document");
-      is(cookieSpan.textContent, "", "The SameSite cookie was blocked correctly.");
-    });
-  } else {
-    info("Check that the SameSite cookie was sent.");
-    await checkCookiePresent(browser);
-  }
+async function checkCookie(browser) {
+  info("Check that the SameSite cookie was not sent.");
+  await ContentTask.spawn(browser, null, async function() {
+    let cookieSpan = content.document.getElementById("cookieSpan");
+    ok(cookieSpan, "cookieSpan element should be in document");
+    is(cookieSpan.textContent, "", "The SameSite cookie was blocked correctly.");
+  });
 }
 
-async function runTest(sameSiteEnabled) {
+async function runTest() {
   await SpecialPowers.pushPrefEnv({
-    set: [["network.cookie.same-site.enabled", sameSiteEnabled],
-          ["reader.parse-on-load.enabled", true]],
+    set: [["reader.parse-on-load.enabled", true]],
   });
 
   info("Set a SameSite=strict cookie.");
   await BrowserTestUtils.withNewTab(TEST_ORIGIN1 + "setSameSiteCookie.html", () => {});
 
   info("Check that the cookie has been correctly set.");
   await BrowserTestUtils.withNewTab(TEST_ORIGIN1 + "getCookies.html", async function(browser) {
     await checkCookiePresent(browser);
@@ -64,32 +58,32 @@ async function runTest(sameSiteEnabled) 
       pageLoaded = BrowserTestUtils.waitForContentEvent(browser, "DOMContentLoaded");
       return t;
     }, false);
 
     info("Waiting for the page to load in normal mode...");
     await pageLoaded;
 
     await clickLink(browser);
-    await checkCookie(sameSiteEnabled, browser);
+    await checkCookie(browser);
     await BrowserTestUtils.removeTab(tab);
   }
 
   info("Open the cross-origin page again.");
   await BrowserTestUtils.withNewTab(TEST_ORIGIN2 + "linkToGetCookies.html", async function(browser) {
     let pageShown = BrowserTestUtils.waitForContentEvent(browser, "AboutReaderContentReady");
     let readerButton = document.getElementById("reader-mode-button");
     ok(readerButton, "readerButton should be available");
     readerButton.click();
 
     info("Waiting for the page to be displayed in reader mode...");
     await pageShown;
 
     await clickLink(browser);
-    await checkCookie(sameSiteEnabled, browser);
+    await checkCookie(browser);
   });
 }
 
 add_task(async function() {
   await runTest(true);
 });
 
 add_task(async function() {
--- a/toolkit/components/search/SearchService.jsm
+++ b/toolkit/components/search/SearchService.jsm
@@ -19,16 +19,20 @@ XPCOMUtils.defineLazyModuleGetters(this,
   RemoteSettings: "resource://services-settings/remote-settings.js",
   SearchEngine: "resource://gre/modules/SearchEngine.jsm",
   SearchStaticData: "resource://gre/modules/SearchStaticData.jsm",
   SearchUtils: "resource://gre/modules/SearchUtils.jsm",
   Services: "resource://gre/modules/Services.jsm",
   setTimeout: "resource://gre/modules/Timer.jsm",
 });
 
+XPCOMUtils.defineLazyServiceGetters(this, {
+  gEnvironment: ["@mozilla.org/process/environment;1", "nsIEnvironment"],
+});
+
 XPCOMUtils.defineLazyPreferenceGetter(this, "gGeoSpecificDefaultsEnabled",
   SearchUtils.BROWSER_SEARCH_PREF + "geoSpecificDefaults", false);
 
 // Can't use defineLazyPreferenceGetter because we want the value
 // from the default branch
 XPCOMUtils.defineLazyGetter(this, "distroID", () => {
   return Services.prefs.getDefaultBranch("distribution.").getCharPref("id", "");
 });
@@ -843,16 +847,17 @@ SearchService.prototype = {
    *
    * @returns {Promise} A promise, resolved successfully if loading data
    * succeeds.
    */
   async _loadEngines(cache, isReload) {
     SearchUtils.log("_loadEngines: start");
     Services.obs.notifyObservers(null, SearchUtils.TOPIC_SEARCH_SERVICE, "find-jar-engines");
     let engines = await this._findEngines();
+    SearchUtils.log("_loadEngines: loading - " + engines.join(","));
 
     // Get the non-empty distribution directories into distDirs...
     let distDirs = [];
     let locations;
     try {
       locations = Services.dirsvc.get(NS_APP_DISTRIBUTION_SEARCH_DIR_LIST,
                                       Ci.nsISimpleEnumerator);
     } catch (e) {
@@ -894,23 +899,26 @@ SearchService.prototype = {
       return [name, locale];
     }
 
     function extensionId(name) {
       return name + "@" + EXT_SIGNING_ADDRESS;
     }
 
     let buildID = Services.appinfo.platformBuildID;
-    let rebuildCache = !cache.engines ||
+    let rebuildCache = gEnvironment.get("RELOAD_ENGINES") ||
+                       !cache.engines ||
                        cache.version != CACHE_VERSION ||
                        cache.locale != Services.locale.requestedLocale ||
                        cache.buildID != buildID ||
                        cache.visibleDefaultEngines.length != this._visibleDefaultEngines.length ||
                        this._visibleDefaultEngines.some(notInCacheVisibleEngines);
 
+    await WebExtensionPolicy.readyPromise;
+
     // If we are reiniting, delete previously installed built in
     // extensions that arent in the current engine list.
     for (let id of this._extensions.keys()) {
       let policy = WebExtensionPolicy.getByID(id);
       if (!policy) {
         // If we are reiniting due to a remote settings update, we may have
         // removed all the engines and be rebuilding without the cache. In this
         // case, we won't have the cached engines loaded, so just skip this check.
@@ -966,21 +974,26 @@ SearchService.prototype = {
           // but if there wasnt an valid cache, mark as installing.
           for (let [locale, installed] of localeMap) {
             if (installed === true) {
               localeMap.set(locale, null);
             }
           }
           this._extensions.set(id, localeMap);
           let path = EXT_SEARCH_PREFIX + id.split("@")[0] + "/";
-          await AddonManager.installBuiltinAddon(path);
-          // The AddonManager will install the engine asynchronously
-          // We can manually wait on that happening here.
-          await ExtensionParent.apiManager.global.pendingSearchSetupTasks.get(id);
-          SearchUtils.log("_loadEngines: " + id + " installed");
+          try {
+            await AddonManager.installBuiltinAddon(path);
+            // The AddonManager will install the engine asynchronously
+            // We can manually wait on that happening here.
+            await ExtensionParent.apiManager.global.pendingSearchSetupTasks.get(id);
+            SearchUtils.log("_loadEngines: " + id + " installed");
+          } catch (err) {
+            this._extensions.delete(id);
+            Cu.reportError("Failed to install engine: " + err.message + "\n" + err.stack);
+          }
         }
       }
     }
 
     SearchUtils.log("_loadEngines: loading user-installed engines from the obsolete cache");
     this._loadEnginesFromCache(cache, true);
 
     this._loadEnginesMetadataFromCache(cache);
@@ -1810,16 +1823,17 @@ SearchService.prototype = {
       if (engine && (engine.alias == alias || engine._internalAliases.includes(alias))) {
         return engine;
       }
     }
     return null;
   },
 
   async addEngineWithDetails(name, iconURL, alias, description, method, template, extensionID) {
+    SearchUtils.log("addEngineWithDetails: Adding \"" + template + "\".");
     let isCurrent = false;
     var params;
 
     if (iconURL && typeof iconURL == "object") {
       params = iconURL;
     } else {
       params = {
         iconURL,
--- a/toolkit/forgetaboutsite/test/unit/test_removeDataFromDomain.js
+++ b/toolkit/forgetaboutsite/test/unit/test_removeDataFromDomain.js
@@ -100,19 +100,18 @@ function add_login(aHost) {
  * Checks to see if a login exists for a host.
  *
  * @param aHost
  *        The host to check for the test login.
  * @param aExists
  *        True if the login should exist, false otherwise.
  */
 function check_login_exists(aHost, aExists) {
-  let count = { value: 0 };
-  Services.logins.findLogins(count, aHost, "", null);
-  Assert.equal(count.value, aExists ? 1 : 0);
+  let logins = Services.logins.findLogins(aHost, "", null);
+  Assert.equal(logins.length, aExists ? 1 : 0);
 }
 
 /**
  * Adds a permission for the specified URI to the permission manager.
  *
  * @param aURI
  *        The URI to add the test permission for.
  */
--- a/tools/lint/eslint/eslint-plugin-mozilla/manifest.tt
+++ b/tools/lint/eslint/eslint-plugin-mozilla/manifest.tt
@@ -1,10 +1,10 @@
 [
   {
     "algorithm": "sha512",
     "visibility": "public",
     "filename": "eslint-plugin-mozilla.tar.gz",
     "unpack": true,
-    "digest": "d81aa82356c3c6253c7ab817d21520e5bea76387028b3397b0612da3d3c72197447d53933800f2b5096c65166fe288ba93704c852637de83c7c140bf8ba31010",
-    "size": 4703159
+    "digest": "d5c6b7f5ffb39abd05bb006ec3dffa2c70111417bf9a04b6794604debc45e3c022e3c487450f92ba06cf8bb63a1714713f18c42f515ff4551b698035cbe0a46b",
+    "size": 4665292
   }
 ]
--- a/tools/lint/eslint/eslint-plugin-mozilla/package-lock.json
+++ b/tools/lint/eslint/eslint-plugin-mozilla/package-lock.json
@@ -573,33 +573,33 @@
       "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz",
       "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==",
       "dev": true,
       "requires": {
         "pump": "^3.0.0"
       }
     },
     "glob": {
-      "version": "7.1.3",
-      "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz",
-      "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==",
+      "version": "7.1.4",
+      "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.4.tgz",
+      "integrity": "sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A==",
       "dev": true,
       "requires": {
         "fs.realpath": "^1.0.0",
         "inflight": "^1.0.4",
         "inherits": "2",
         "minimatch": "^3.0.4",
         "once": "^1.3.0",
         "path-is-absolute": "^1.0.0"
       }
     },
     "globals": {
-      "version": "11.11.0",
-      "resolved": "https://registry.npmjs.org/globals/-/globals-11.11.0.tgz",
-      "integrity": "sha512-WHq43gS+6ufNOEqlrDBxVEbb8ntfXrfAUU2ZOpCxrBdGKW3gyv8mCxAfIBD0DroPKGrJ2eSsXsLtY9MPntsyTw==",
+      "version": "11.12.0",
+      "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz",
+      "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==",
       "dev": true
     },
     "growl": {
       "version": "1.10.5",
       "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz",
       "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==",
       "dev": true
     },
@@ -690,33 +690,33 @@
       "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4="
     },
     "ini-parser": {
       "version": "0.0.2",
       "resolved": "https://registry.npmjs.org/ini-parser/-/ini-parser-0.0.2.tgz",
       "integrity": "sha1-+kF4flZ3Y7P/Zdel2alO23QHh+8="
     },
     "inquirer": {
-      "version": "6.2.2",
-      "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-6.2.2.tgz",
-      "integrity": "sha512-Z2rREiXA6cHRR9KBOarR3WuLlFzlIfAEIiB45ll5SSadMg7WqOh1MKEjjndfuH5ewXdixWCxqnVfGOQzPeiztA==",
+      "version": "6.3.1",
+      "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-6.3.1.tgz",
+      "integrity": "sha512-MmL624rfkFt4TG9y/Jvmt8vdmOo836U7Y0Hxr2aFk3RelZEGX4Igk0KabWrcaaZaTv9uzglOqWh1Vly+FAWAXA==",
       "dev": true,
       "requires": {
         "ansi-escapes": "^3.2.0",
         "chalk": "^2.4.2",
         "cli-cursor": "^2.1.0",
         "cli-width": "^2.0.0",
         "external-editor": "^3.0.3",
         "figures": "^2.0.0",
         "lodash": "^4.17.11",
         "mute-stream": "0.0.7",
         "run-async": "^2.2.0",
         "rxjs": "^6.4.0",
         "string-width": "^2.1.0",
-        "strip-ansi": "^5.0.0",
+        "strip-ansi": "^5.1.0",
         "through": "^2.3.6"
       },
       "dependencies": {
         "ansi-regex": {
           "version": "4.1.0",
           "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz",
           "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==",
           "dev": true
@@ -924,36 +924,36 @@
       "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz",
       "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=",
       "dev": true,
       "requires": {
         "minimist": "0.0.8"
       }
     },
     "mocha": {
-      "version": "6.1.1",
-      "resolved": "https://registry.npmjs.org/mocha/-/mocha-6.1.1.tgz",
-      "integrity": "sha512-ayfr68s4kyDnCU0hjkTk5Z8J8dqr1iPUuVjmd+dLFgaGKOPlgx1XrOGn5k3H1LlXNnLBb8voZMYMKxchiA4Ujg==",
+      "version": "6.1.4",
+      "resolved": "https://registry.npmjs.org/mocha/-/mocha-6.1.4.tgz",
+      "integrity": "sha512-PN8CIy4RXsIoxoFJzS4QNnCH4psUCPWc4/rPrst/ecSJJbLBkubMiyGCP2Kj/9YnWbotFqAoeXyXMucj7gwCFg==",
       "dev": true,
       "requires": {
         "ansi-colors": "3.2.3",
         "browser-stdout": "1.3.1",
         "debug": "3.2.6",
         "diff": "3.5.0",
         "escape-string-regexp": "1.0.5",
         "find-up": "3.0.0",
         "glob": "7.1.3",
         "growl": "1.10.5",
         "he": "1.2.0",
-        "js-yaml": "3.13.0",
+        "js-yaml": "3.13.1",
         "log-symbols": "2.2.0",
         "minimatch": "3.0.4",
         "mkdirp": "0.5.1",
         "ms": "2.1.1",
-        "node-environment-flags": "1.0.4",
+        "node-environment-flags": "1.0.5",
         "object.assign": "4.1.0",
         "strip-json-comments": "2.0.1",
         "supports-color": "6.0.0",
         "which": "1.3.1",
         "wide-align": "1.1.3",
         "yargs": "13.2.2",
         "yargs-parser": "13.0.0",
         "yargs-unparser": "1.5.0"
@@ -963,24 +963,28 @@
           "version": "3.2.6",
           "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz",
           "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==",
           "dev": true,
           "requires": {
             "ms": "^2.1.1"
           }
         },
-        "js-yaml": {
-          "version": "3.13.0",
-          "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.0.tgz",
-          "integrity": "sha512-pZZoSxcCYco+DIKBTimr67J6Hy+EYGZDY/HCWC+iAEA9h1ByhMXAIVUXMcMFpOCxQ/xjXmPI2MkDL5HRm5eFrQ==",
+        "glob": {
+          "version": "7.1.3",
+          "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz",
+          "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==",
           "dev": true,
           "requires": {
-            "argparse": "^1.0.7",
-            "esprima": "^4.0.0"
+            "fs.realpath": "^1.0.0",
+            "inflight": "^1.0.4",
+            "inherits": "2",
+            "minimatch": "^3.0.4",
+            "once": "^1.3.0",
+            "path-is-absolute": "^1.0.0"
           }
         },
         "supports-color": {
           "version": "6.0.0",
           "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.0.0.tgz",
           "integrity": "sha512-on9Kwidc1IUQo+bQdhi8+Tijpo0e1SS6RoGo2guUwn5vdaxw8RXOF9Vb2ws+ihWOmh4JnCJOvaziZWP1VABaLg==",
           "dev": true,
           "requires": {
@@ -1009,22 +1013,23 @@
     },
     "nice-try": {
       "version": "1.0.5",
       "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz",
       "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==",
       "dev": true
     },
     "node-environment-flags": {
-      "version": "1.0.4",
-      "resolved": "https://registry.npmjs.org/node-environment-flags/-/node-environment-flags-1.0.4.tgz",
-      "integrity": "sha512-M9rwCnWVLW7PX+NUWe3ejEdiLYinRpsEre9hMkU/6NS4h+EEulYaDH1gCEZ2gyXsmw+RXYDaV2JkkTNcsPDJ0Q==",
+      "version": "1.0.5",
+      "resolved": "https://registry.npmjs.org/node-environment-flags/-/node-environment-flags-1.0.5.tgz",
+      "integrity": "sha512-VNYPRfGfmZLx0Ye20jWzHUjyTW/c+6Wq+iLhDzUI4XmhrDd9l/FozXV3F2xOaXjvp0co0+v1YSR3CMP6g+VvLQ==",
       "dev": true,
       "requires": {
-        "object.getownpropertydescriptors": "^2.0.3"
+        "object.getownpropertydescriptors": "^2.0.3",
+        "semver": "^5.7.0"
       }
     },
     "npm-run-path": {
       "version": "2.0.2",
       "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz",
       "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=",
       "dev": true,
       "requires": {
@@ -1275,19 +1280,19 @@
       "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.3.0.tgz",
       "integrity": "sha1-A3GrSuC91yDUFm19/aZP96RFpsA=",
       "dev": true,
       "requires": {
         "is-promise": "^2.1.0"
       }
     },
     "rxjs": {
-      "version": "6.4.0",
-      "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.4.0.tgz",
-      "integrity": "sha512-Z9Yfa11F6B9Sg/BK9MnqnQ+aQYicPLtilXBp2yUtDt2JRCE0h26d33EnfO3ZxoNxG0T92OUucP3Ct7cpfkdFfw==",
+      "version": "6.5.2",
+      "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.5.2.tgz",
+      "integrity": "sha512-HUb7j3kvb7p7eCUHE3FqjoDsC1xfZQ4AHFWfTKSpZ+sAhhz5X1WX0ZuUqWbzB2QhSLp3DoLUG+hMdEDKqWo2Zg==",
       "dev": true,
       "requires": {
         "tslib": "^1.9.0"
       }
     },
     "safe-buffer": {
       "version": "5.1.2",
       "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
@@ -1398,19 +1403,19 @@
       "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
       "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
       "dev": true,
       "requires": {
         "has-flag": "^3.0.0"
       }
     },
     "table": {
-      "version": "5.2.3",
-      "resolved": "https://registry.npmjs.org/table/-/table-5.2.3.tgz",
-      "integrity": "sha512-N2RsDAMvDLvYwFcwbPyF3VmVSSkuF+G1e+8inhBLtHpvwXGw4QRPEZhihQNeEN0i1up6/f6ObCJXNdlRG3YVyQ==",
+      "version": "5.3.3",
+      "resolved": "https://registry.npmjs.org/table/-/table-5.3.3.tgz",
+      "integrity": "sha512-3wUNCgdWX6PNpOe3amTTPWPuF6VGvgzjKCaO1snFj0z7Y3mUPWf5+zDtxUVGispJkDECPmR29wbzh6bVMOHbcw==",
       "dev": true,
       "requires": {
         "ajv": "^6.9.1",
         "lodash": "^4.17.11",
         "slice-ansi": "^2.1.0",
         "string-width": "^3.0.0"
       },
       "dependencies": {
--- a/tools/lint/eslint/eslint-plugin-mozilla/package.json
+++ b/tools/lint/eslint/eslint-plugin-mozilla/package.json
@@ -21,17 +21,17 @@
   "main": "lib/index.js",
   "dependencies": {
     "ini-parser": "0.0.2",
     "sax": "1.2.4",
     "htmlparser2": "3.10.1"
   },
   "devDependencies": {
     "eslint": "5.16.0",
-    "mocha": "6.1.1"
+    "mocha": "6.1.4"
   },
   "peerDependencies": {
     "eslint": "^5.0.1",
     "eslint-plugin-no-unsanitized": "^3.0.0"
   },
   "engines": {
     "node": ">=6.9.1"
   },
--- a/tools/lint/eslint/setup_helper.py
+++ b/tools/lint/eslint/setup_helper.py
@@ -173,17 +173,19 @@ def call_process(name, cmd, cwd=None, ap
 
     return True
 
 
 def expected_eslint_modules():
     # Read the expected version of ESLint and external modules
     expected_modules_path = os.path.join(get_project_root(), "package.json")
     with open(expected_modules_path, "r") as f:
-        expected_modules = json.load(f)["dependencies"]
+        sections = json.load(f)
+        expected_modules = sections["dependencies"]
+        expected_modules.update(sections["devDependencies"])
 
     # Also read the in-tree ESLint plugin mozilla information, to ensure the
     # dependencies are up to date.
     mozilla_json_path = os.path.join(get_eslint_module_path(),
                                      "eslint-plugin-mozilla", "package.json")
     with open(mozilla_json_path, "r") as f:
         expected_modules.update(json.load(f)["dependencies"])
 
--- a/tools/update-verify/release/common/check_updates.sh
+++ b/tools/update-verify/release/common/check_updates.sh
@@ -59,26 +59,26 @@ check_updates () {
 
     cd_dir=$(ls -d ${PWD}/source/${platform_dirname})
     cd "${cd_dir}"
     set -x
     "$updater" "$update_abspath" "$cwd" "$cwd" 0
     set +x
     cd ../..
   else
-    echo "FAIL: no dir in source/$platform_dirname"
+    echo "TEST-UNEXPECTED-FAIL: no dir in source/$platform_dirname"
     return 1
   fi
 
   cat update/update.log
   update_status=`cat update/update.status`
 
   if [ "$update_status" != "succeeded" ]
   then
-    echo "FAIL: update status was not successful: $update_status"
+    echo "TEST-UNEXPECTED-FAIL: update status was not successful: $update_status"
     return 1
   fi
 
   # If we were testing an OS X mar on Linux, the unpack step copied the
   # precomplete file from Contents/Resources to the root of the install
   # to ensure the Linux updater binary could find it. However, only the
   # precomplete file in Contents/Resources was updated, which means
   # the copied version in the root of the install will usually have some
@@ -99,16 +99,16 @@ check_updates () {
   fi
   cd ../..
 
   ../compare-directories.py source/${platform_dirname} target/${platform_dirname}  ${channel} > "${diff_file}"
   diffErr=$?
   cat "${diff_file}"
   if [ $diffErr == 2 ]
   then
-    echo "FAIL: differences found after update"
+    echo "TEST-UNEXPECTED-FAIL: differences found after update"
     return 1
   elif [ $diffErr != 0 ]
   then
-    echo "FAIL: unknown error from diff: $diffErr"
+    echo "TEST-UNEXPECTED-FAIL: unknown error from diff: $diffErr"
     return 3
   fi
 }
--- a/tools/update-verify/release/common/download_builds.sh
+++ b/tools/update-verify/release/common/download_builds.sh
@@ -21,16 +21,16 @@ download_builds() {
     do
     source_file=`basename "$url"`
     if [ -f "$source_file" ]; then rm "$source_file"; fi
     cd downloads 
     if [ -f "$source_file" ]; then rm "$source_file"; fi
     cached_download "${source_file}" "${url}"
     status=$?
     if [ $status != 0 ]; then
-      echo "FAIL: Could not download source $source_file from $url"
+      echo "TEST-UNEXPECTED-FAIL: Could not download source $source_file from $url"
       echo "skipping.."
       cd ../
       return $status
     fi
     cd ../
   done
 }
--- a/tools/update-verify/release/common/download_mars.sh
+++ b/tools/update-verify/release/common/download_mars.sh
@@ -26,48 +26,48 @@ download_mars () {
         try=$(($try+1))
     done
 
     echo; echo;  # padding
 
     update_line=`fgrep "<update " update.xml`
     grep_rv=$?
     if [ 0 -ne $grep_rv ]; then
-        echo "FAIL: no <update/> found for $update_url"
+        echo "TEST-UNEXPECTED-FAIL: no <update/> found for $update_url"
         return 1
     fi
     command=`echo $update_line | sed -e 's/^.*<update //' -e 's:>.*$::' -e 's:\&amp;:\&:g'`
     eval "export $command"
 
     if [ ! -z "$to_build_id" -a "$buildID" != "$to_build_id" ]; then
-        echo "FAIL: expected buildID $to_build_id does not match actual $buildID"
+        echo "TEST-UNEXPECTED-FAIL: expected buildID $to_build_id does not match actual $buildID"
         return 1
     fi
 
     if [ ! -z "$to_display_version" -a "$displayVersion" != "$to_display_version" ]; then
-        echo "FAIL: expected displayVersion $to_display_version does not match actual $displayVersion"
+        echo "TEST-UNEXPECTED-FAIL: expected displayVersion $to_display_version does not match actual $displayVersion"
         return 1
     fi
 
     if [ ! -z "$to_app_version" -a "$appVersion" != "$to_app_version" ]; then
-        echo "FAIL: expected appVersion $to_app_version does not match actual $appVersion"
+        echo "TEST-UNEXPECTED-FAIL: expected appVersion $to_app_version does not match actual $appVersion"
         return 1
     fi
 
     mkdir -p update/
     if [ -z $only ]; then
       only="partial complete"
     fi
     for patch_type in $only
       do
       line=`fgrep "patch type=\"$patch_type" update.xml`
       grep_rv=$?
 
       if [ 0 -ne $grep_rv ]; then
-        echo "FAIL: no $patch_type update found for $update_url"
+        echo "TEST-UNEXPECTED-FAIL: no $patch_type update found for $update_url"
         return 1
       fi
 
       command=`echo $line | sed -e 's/^.*<patch //' -e 's:/>.*$::' -e 's:\&amp;:\&:g'`
       eval "export $command"
 
       if [ "$test_only" == "1" ]
       then
@@ -80,26 +80,26 @@ download_mars () {
       if [ "$?" != 0 ]; then
         echo "Could not download $patch_type!"
         echo "from: $URL"
       fi
       actual_size=`perl -e "printf \"%d\n\", (stat(\"update/$patch_type.mar\"))[7]"`
       actual_hash=`openssl dgst -$hashFunction update/$patch_type.mar | sed -e 's/^.*= //'`
 
       if [ $actual_size != $size ]; then
-          echo "FAIL: $patch_type from $update_url wrong size"
-          echo "FAIL: update.xml size: $size"
-          echo "FAIL: actual size: $actual_size"
+          echo "TEST-UNEXPECTED-FAIL: $patch_type from $update_url wrong size"
+          echo "TEST-UNEXPECTED-FAIL: update.xml size: $size"
+          echo "TEST-UNEXPECTED-FAIL: actual size: $actual_size"
           return 1
       fi
 
       if [ $actual_hash != $hashValue ]; then
-          echo "FAIL: $patch_type from $update_url wrong hash"
-          echo "FAIL: update.xml hash: $hashValue"
-          echo "FAIL: actual hash: $actual_hash"
+          echo "TEST-UNEXPECTED-FAIL: $patch_type from $update_url wrong hash"
+          echo "TEST-UNEXPECTED-FAIL: update.xml hash: $hashValue"
+          echo "TEST-UNEXPECTED-FAIL: actual hash: $actual_hash"
           return 1
       fi
 
       cp update/$patch_type.mar update/update.mar
       echo $actual_size > update/$patch_type.size
 
     done
 }
--- a/tools/update-verify/release/updates/verify.sh
+++ b/tools/update-verify/release/updates/verify.sh
@@ -147,17 +147,17 @@ do
             "${to_build_id}" "${to_app_version}" "${to_display_version}"
           err=$?
         else
           download_mars "${aus_server}/update/3/${update_path}/update.xml?force=1" ${patch_type} 0 \
             "${to_build_id}" "${to_app_version}" "${to_display_version}"
           err=$?
         fi
         if [ "$err" != "0" ]; then
-          echo "FAIL: [${release} ${locale} ${patch_type}] download_mars returned non-zero exit code: ${err}"
+          echo "TEST-UNEXPECTED-FAIL: [${release} ${locale} ${patch_type}] download_mars returned non-zero exit code: ${err}"
           continue
         fi
       else
         mkdir -p updates/${update_path}/complete
         mkdir -p updates/${update_path}/partial
         $retry wget -q -O ${patch_type} updates/${update_path}/${patch_type}/update.xml "${aus_server}/update/3/${update_path}/update.xml?force=1"
 
       fi
@@ -239,49 +239,49 @@ do
             continue
         fi
 
         from_path=`echo $from | sed "s/%locale%/${locale}/"`
         to_path=`echo $to | sed "s/%locale%/${locale}/"`
         download_builds "${ftp_server_from}${from_path}" "${ftp_server_to}${to_path}"
         err=$?
         if [ "$err" != "0" ]; then
-          echo "FAIL: [$release $locale $patch_type] download_builds returned non-zero exit code: $err"
+          echo "TEST-UNEXPECTED-FAIL: [$release $locale $patch_type] download_builds returned non-zero exit code: $err"
           continue
         fi
         source_file=`basename "$from_path"`
         target_file=`basename "$to_path"`
         diff_file="results.diff"
         if [ -e ${diff_file} ]; then
           rm ${diff_file}
         fi
         check_updates "${platform}" "downloads/${source_file}" "downloads/${target_file}" ${locale} ${use_old_updater} ${updater} ${diff_file} ${channel} ${mar_channel_IDs}
         err=$?
         if [ "$err" == "0" ]; then
           continue
         elif [ "$err" == "1" ]; then
-          echo "FAIL: [$release $locale $patch_type] check_updates returned failure for $platform downloads/$source_file vs. downloads/$target_file: $err"
+          echo "TEST-UNEXPECTED-FAIL: [$release $locale $patch_type] check_updates returned failure for $platform downloads/$source_file vs. downloads/$target_file: $err"
         elif [ "$err" == "2" ]; then
           echo "WARN: [$release $locale $patch_type] check_updates returned warning for $platform downloads/$source_file vs. downloads/$target_file: $err"
         else
-          echo "FAIL: [$release $locale $patch_type] check_updates returned unknown error for $platform downloads/$source_file vs. downloads/$target_file: $err"
+          echo "TEST-UNEXPECTED-FAIL: [$release $locale $patch_type] check_updates returned unknown error for $platform downloads/$source_file vs. downloads/$target_file: $err"
         fi
 
         if [ -s ${diff_file} ]; then
           echo "Found diffs for ${patch_type} update from ${aus_server}/update/3/${update_path}/update.xml?force=1" >> ${diff_summary_log}
           cat ${diff_file} >> ${diff_summary_log}
           echo "" >> ${diff_summary_log}
         fi
       fi
     done
     if [ -f update/partial.size ] && [ -f update/complete.size ]; then
         partial_size=`cat update/partial.size`
         complete_size=`cat update/complete.size`
         if [ $partial_size -gt $complete_size ]; then
-            echo "FAIL: [$release $locale $patch_type] partial updates are larger than complete updates"
+            echo "TEST-UNEXPECTED-FAIL: [$release $locale $patch_type] partial updates are larger than complete updates"
         elif [ $partial_size -eq $complete_size ]; then
             echo "WARN: [$release $locale $patch_type] partial updates are the same size as complete updates, this should only happen for major updates"
         else
             echo "SUCCESS: [$release $locale $patch_type] partial updates are smaller than complete updates, all is well in the universe"
         fi
     fi
   done
 done < $config_file
--- a/tools/update-verify/scripts/chunked-verify.sh
+++ b/tools/update-verify/scripts/chunked-verify.sh
@@ -45,13 +45,13 @@ set +x
 echo "Scanning log for failures and warnings"
 echo "--------------------------------------"
 
 # Test for a failure, note we are set -e.
 # Grep returns 0 on a match and 1 on no match
 # Testing for failures first is important because it's OK to to mark as failed
 # when there's failures+warnings, but not OK to mark as warnings in the same
 # situation.
-( ! grep 'FAIL:' $SCRIPTS_DIR/../verify_log.txt ) || print_failed_msg
+( ! grep 'TEST-UNEXPECTED-FAIL:' $SCRIPTS_DIR/../verify_log.txt ) || print_failed_msg
 ( ! grep 'WARN:' $SCRIPTS_DIR/../verify_log.txt ) || print_warning_msg
 
 echo "-------------------------"
 echo "All is well"
--- a/xpcom/components/nsComponentManager.cpp
+++ b/xpcom/components/nsComponentManager.cpp
@@ -491,51 +491,48 @@ nsresult nsComponentManagerImpl::Init() 
     // This needs to be called very early, before anything in nsLayoutModule is
     // used, and before any calls are made into the JS engine.
     nsLayoutModuleInitialize();
 
     mJSLoaderReady = true;
 
     // The overall order in which chrome.manifests are expected to be treated
     // is the following:
-    // - greDir
-    // - greDir's omni.ja
-    // - appDir
-    // - appDir's omni.ja
+    // - greDir's omni.ja or greDir
+    // - appDir's omni.ja or appDir
 
     InitializeModuleLocations();
     ComponentLocation* cl = sModuleLocations->AppendElement();
-    nsCOMPtr<nsIFile> lf =
-        CloneAndAppend(greDir, NS_LITERAL_CSTRING("chrome.manifest"));
     cl->type = NS_APP_LOCATION;
-    cl->location.Init(lf);
-
     RefPtr<nsZipArchive> greOmnijar =
         mozilla::Omnijar::GetReader(mozilla::Omnijar::GRE);
     if (greOmnijar) {
-      cl = sModuleLocations->AppendElement();
-      cl->type = NS_APP_LOCATION;
       cl->location.Init(greOmnijar, "chrome.manifest");
-    }
-
-    bool equals = false;
-    appDir->Equals(greDir, &equals);
-    if (!equals) {
-      cl = sModuleLocations->AppendElement();
-      cl->type = NS_APP_LOCATION;
-      lf = CloneAndAppend(appDir, NS_LITERAL_CSTRING("chrome.manifest"));
+    } else {
+      nsCOMPtr<nsIFile> lf =
+          CloneAndAppend(greDir, NS_LITERAL_CSTRING("chrome.manifest"));
       cl->location.Init(lf);
     }
 
     RefPtr<nsZipArchive> appOmnijar =
         mozilla::Omnijar::GetReader(mozilla::Omnijar::APP);
     if (appOmnijar) {
       cl = sModuleLocations->AppendElement();
       cl->type = NS_APP_LOCATION;
       cl->location.Init(appOmnijar, "chrome.manifest");
+    } else {
+      bool equals = false;
+      appDir->Equals(greDir, &equals);
+      if (!equals) {
+        cl = sModuleLocations->AppendElement();
+        cl->type = NS_APP_LOCATION;
+        nsCOMPtr<nsIFile> lf =
+            CloneAndAppend(appDir, NS_LITERAL_CSTRING("chrome.manifest"));
+        cl->location.Init(lf);
+      }
     }
 
     RereadChromeManifests(false);
   }
 
   nsCategoryManager::GetSingleton()->SuppressNotifications(false);
 
   RegisterWeakMemoryReporter(this);