Merge mozilla-central to mozilla-beta a=same-version-merge l10n=same-version-merge DEVEDITION_68_0b2_BUILD1 DEVEDITION_68_0b2_RELEASE
authorAndreea Pavel <apavel@mozilla.com>
Thu, 16 May 2019 18:22:58 +0300
changeset 535928 2265bfc5920d3e510c4edf9b0caf8e3eaf9bdc9c
parent 535785 cf273dce0debb09b0556f9023d16242c8f7e9b7c (current diff)
parent 535927 4b635d928b2bd42890ffc97c55622072092351e9 (diff)
child 535929 d113f134040d1e024190d3298abdbfbdd96e4a64
push id2082
push userffxbld-merge
push dateMon, 01 Jul 2019 08:34:18 +0000
treeherdermozilla-release@2fb19d0466d2 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssame-version-merge
milestone68.0
Merge mozilla-central to mozilla-beta a=same-version-merge l10n=same-version-merge
dom/ipc/tests/browser_JSWindowActor.js
dom/ipc/tests/test_JSWindowActor.xul
gfx/wr/wrench/reftests/border/border-overlapping-ref.yaml
gfx/wr/wrench/reftests/border/border-overlapping.yaml
mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/ActivityResultHandler.java
mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/ActivityResultHandlerMap.java
mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/InputOptionsUtils.java
mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/JSONUtils.java
mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/MapUtils.java
mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/MenuUtils.java
mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/NonEvictingLruCache.java
mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/PrefUtils.java
mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/WindowUtils.java
testing/web-platform/meta/svg/geometry/parsing/cx-valid.svg.ini
testing/web-platform/meta/svg/geometry/parsing/cy-valid.svg.ini
testing/web-platform/meta/svg/geometry/parsing/rx-valid.svg.ini
testing/web-platform/meta/svg/geometry/parsing/ry-valid.svg.ini
testing/web-platform/meta/svg/geometry/parsing/x-valid.svg.ini
testing/web-platform/meta/svg/geometry/parsing/y-valid.svg.ini
testing/web-platform/meta/svg/shapes/ellipse-01.svg.ini
testing/web-platform/meta/svg/shapes/ellipse-02.svg.ini
testing/web-platform/meta/svg/shapes/ellipse-03.svg.ini
testing/web-platform/meta/svg/shapes/ellipse-05.svg.ini
testing/web-platform/meta/svg/shapes/ellipse-06.svg.ini
testing/web-platform/meta/svg/shapes/ellipse-07.svg.ini
testing/web-platform/meta/svg/shapes/ellipse-08.svg.ini
testing/web-platform/meta/svg/shapes/rx-ry-not-inherited.svg.ini
toolkit/components/search/tests/xpcshell/searchconfigs/xpcshell.ini
--- a/.taskcluster.yml
+++ b/.taskcluster.yml
@@ -40,42 +40,48 @@
 #     push: {owner, pushlog_id, revision},
 #     repository: {url, project, level},
 #     input,
 #     parameters,
 #     taskId,      // targetted taskId
 #     taskGroupId, // targetted taskGroupId
 #     action: {name, title, description, taskGroupId, symbol, repo_scope, cb_name}
 #     ownTaskId:   // taskId of the task that will be created
+#     clientId:    // clientId that triggered this hook
 #   }
 
 version: 1
 tasks:
   # NOTE: support for actions in ci-admin requires that the `tasks` property be an array *before* JSON-e rendering
   # takes place.
   - $if: 'tasks_for in ["hg-push", "action", "cron"]'
     then:
       $let:
         # sometimes the push user is just `ffxbld` or the like, but we want an email-like field..
         ownerEmail: {$if: '"@" in push.owner', then: '${push.owner}', else: '${push.owner}@noreply.mozilla.org'}
         # ensure there's no trailing `/` on the repo URL
         repoUrl: {$if: 'repository.url[-1] == "/"', then: {$eval: 'repository.url[:-1]'}, else: {$eval: 'repository.url'}}
+        # expire try earlier than other branches
+        expires:
+          $if: 'repository.project == "try"'
+          then: {$fromNow: '28 days'}
+          else: {$fromNow: '1 year'}
       in:
         taskId: {$if: 'tasks_for != "action"', then: '${ownTaskId}'}
         taskGroupId:
           $if: 'tasks_for == "action"'
           then:
             '${action.taskGroupId}'
           else:
             '${ownTaskId}' # same as taskId; this is how automation identifies a decision tsak
         schedulerId: 'gecko-level-${repository.level}'
 
         created: {$fromNow: ''}
         deadline: {$fromNow: '1 day'}
-        expires: {$fromNow: '1 year 1 second'} # 1 second so artifacts expire first, despite rounding errors
+        expires: {$eval: 'expires'}
         metadata:
           $merge:
             - owner: "${ownerEmail}"
               source: "${repoUrl}/raw-file/${push.revision}/.taskcluster.yml"
             - $if: 'tasks_for == "hg-push"'
               then:
                 name: "Gecko Decision Task"
                 description: 'The task that creates all of the other tasks in the task graph'
@@ -249,17 +255,17 @@ tasks:
                   --head-ref="$GECKO_HEAD_REF"
                   --head-rev="$GECKO_HEAD_REV"
                   ${extraArgs}
 
           artifacts:
             'public':
               type: 'directory'
               path: '/builds/worker/artifacts'
-              expires: {$fromNow: '1 year'}
+              expires: {$eval: expires}
 
         extra:
           $merge:
             - treeherder:
                 $merge:
                   - machine:
                       platform: gecko-decision
                   - $if: 'tasks_for == "hg-push"'
--- 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/actors/NetErrorChild.jsm
+++ b/browser/actors/NetErrorChild.jsm
@@ -478,26 +478,16 @@ class NetErrorChild extends ActorChild {
         let certRange = this._getCertValidityRange(docShell);
 
         let approximateDate = now - difference * 1000;
         // If the difference is more than a day, we last fetched the date in the last 5 days,
         // and adjusting the date per the interval would make the cert valid, warn the user:
         if (Math.abs(difference) > 60 * 60 * 24 && (now - lastFetched) <= 60 * 60 * 24 * 5 &&
             certRange.notBefore < approximateDate && certRange.notAfter > approximateDate) {
           clockSkew = true;
-          let systemDate = formatter.format(new Date());
-          // negative difference means local time is behind server time
-          approximateDate = formatter.format(new Date(approximateDate));
-
-          doc.getElementById("wrongSystemTime_URL").textContent = doc.location.hostname;
-          doc.getElementById("wrongSystemTime_systemDate").textContent = systemDate;
-          doc.getElementById("wrongSystemTime_actualDate").textContent = approximateDate;
-
-          doc.getElementById("errorShortDesc").style.display = "none";
-          doc.getElementById("wrongSystemTimePanel").style.display = "block";
 
         // If there is no clock skew with Kinto servers, check against the build date.
         // (The Kinto ping could have happened when the time was still right, or not at all)
         } else {
           let appBuildID = Services.appinfo.appBuildID;
 
           let year = parseInt(appBuildID.substr(0, 4), 10);
           let month = parseInt(appBuildID.substr(4, 2), 10) - 1;
@@ -507,35 +497,29 @@ class NetErrorChild extends ActorChild {
           let systemDate = new Date();
 
           // We don't check the notBefore of the cert with the build date,
           // as it is of course almost certain that it is now later than the build date,
           // so we shouldn't exclude the possibility that the cert has become valid
           // since the build date.
           if (buildDate > systemDate && new Date(certRange.notAfter) > buildDate) {
             clockSkew = true;
-
-            doc.getElementById("wrongSystemTimeWithoutReference_URL")
-              .textContent = doc.location.hostname;
-            doc.getElementById("wrongSystemTimeWithoutReference_systemDate")
-              .textContent = formatter.format(systemDate);
           }
         }
 
         let systemDate = formatter.format(new Date());
         doc.getElementById("wrongSystemTime_systemDate1").textContent = systemDate;
         if (clockSkew) {
           doc.body.classList.add("illustrated", "clockSkewError");
           let clockErrTitle = doc.getElementById("et_clockSkewError");
           let clockErrDesc = doc.getElementById("ed_clockSkewError");
           // eslint-disable-next-line no-unsanitized/property
           doc.querySelector(".title-text").textContent = clockErrTitle.textContent;
           let desc = doc.getElementById("errorShortDescText");
           doc.getElementById("errorShortDesc").style.display = "block";
-          doc.getElementById("wrongSystemTimePanel").style.display = "none";
           doc.getElementById("certificateErrorReporting").style.display = "none";
           if (desc) {
             // eslint-disable-next-line no-unsanitized/property
             desc.innerHTML = clockErrDesc.innerHTML;
           }
           let errorPageContainer = doc.getElementById("errorPageContainer");
           let textContainer = doc.getElementById("text-container");
           errorPageContainer.style.backgroundPosition = `left top calc(50vh - ${textContainer.clientHeight / 2}px)`;
--- 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/aboutNetError.xhtml
+++ b/browser/base/content/aboutNetError.xhtml
@@ -165,24 +165,16 @@
               <p id="errorWhatToDoText" />
           </div>
 
           <div id="errorWhatToDo2">
               <p id="errorWhatToDoText2" />
               <p id="badStsCertExplanation" hidden="true">&certerror.whatShouldIDo.badStsCertExplanation1;</p>
           </div>
 
-          <div id="wrongSystemTimePanel">
-            &certerror.wrongSystemTime2;
-          </div>
-
-          <div id="wrongSystemTimeWithoutReferencePanel">
-            &certerror.wrongSystemTimeWithoutReference;
-          </div>
-
           <!-- Long Description (Note: See netError.dtd for used XHTML tags) -->
           <div id="errorLongDesc" />
 
           <div id="learnMoreContainer">
             <p><a id="learnMoreLink" target="new" data-telemetry-id="learn_more_link">&errorReporting.learnMore;</a></p>
           </div>
         </div>
 
--- 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.js
+++ b/browser/base/content/browser.js
@@ -3721,20 +3721,18 @@ var browserDragAndDrop = {
   getTriggeringPrincipal(aEvent) {
     return Services.droppedLinkHandler.getTriggeringPrincipal(aEvent);
   },
 
   getCSP(aEvent) {
     return Services.droppedLinkHandler.getCSP(aEvent);
   },
 
-  validateURIsForDrop(aEvent, aURIsCount, aURIs) {
-    return Services.droppedLinkHandler.validateURIsForDrop(aEvent,
-                                                           aURIsCount,
-                                                           aURIs);
+  validateURIsForDrop(aEvent, aURIs) {
+    return Services.droppedLinkHandler.validateURIsForDrop(aEvent, aURIs);
   },
 
   dropLinks(aEvent, aDisallowInherit) {
     return Services.droppedLinkHandler.dropLinks(aEvent, aDisallowInherit);
   },
 };
 
 var homeButtonObserver = {
@@ -3747,17 +3745,17 @@ var homeButtonObserver = {
           if (link.url.includes("|")) {
             urls.push(...link.url.split("|"));
           } else {
             urls.push(link.url);
           }
         }
 
         try {
-          browserDragAndDrop.validateURIsForDrop(aEvent, urls.length, urls);
+          browserDragAndDrop.validateURIsForDrop(aEvent, urls);
         } catch (e) {
           return;
         }
 
         setTimeout(openHomeDialog, 0, urls.join("|"));
       }
     },
 
--- 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/nsContextMenu.js
+++ b/browser/base/content/nsContextMenu.js
@@ -776,17 +776,20 @@ nsContextMenu.prototype = {
     popup.insertBefore(fragment, insertBeforeElement);
   },
 
   initSyncItems() {
     gSync.updateContentContextMenu(this);
   },
 
   openPasswordManager() {
-    LoginHelper.openPasswordManager(window, gContextMenuContentData.documentURIObject.host);
+    LoginHelper.openPasswordManager(window, {
+      filterString: gContextMenuContentData.documentURIObject.host,
+      entryPoint: "contextmenu",
+    });
   },
 
   inspectNode() {
     return DevToolsShim.inspectNode(gBrowser.selectedTab, this.targetSelectors);
   },
 
   inspectA11Y() {
     return DevToolsShim.inspectA11Y(gBrowser.selectedTab, this.targetSelectors);
--- a/browser/base/content/pageinfo/security.js
+++ b/browser/base/content/pageinfo/security.js
@@ -175,17 +175,20 @@ var security = {
       }
     }
   },
 
   /**
    * Open the login manager window
    */
   viewPasswords() {
-    LoginHelper.openPasswordManager(window, this._getSecurityInfo().hostName);
+    LoginHelper.openPasswordManager(window, {
+      filterString: this._getSecurityInfo().hostName,
+      entryPoint: "pageinfo",
+    });
   },
 
   _cert: null,
 };
 
 function securityOnLoad(uri, windowInfo) {
   security.init(uri, windowInfo);
 
--- 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/test/siteIdentity/browser.ini
+++ b/browser/base/content/test/siteIdentity/browser.ini
@@ -109,8 +109,9 @@ support-files =
   iframe_navigation.html
 [browser_navigation_failures.js]
 [browser_secure_transport_insecure_scheme.js]
 [browser_ignore_same_page_navigation.js]
 [browser_mixed_content_with_navigation.js]
 support-files =
   file_mixedPassiveContent.html
   file_bug1045809_1.html
+[browser_deprecatedTLSVersions.js]
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/siteIdentity/browser_deprecatedTLSVersions.js
@@ -0,0 +1,61 @@
+/*
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ *
+ * Tests for Bug 1535210 - Set SSL STATE_IS_BROKEN flag for TLS1.0 and TLS 1.1 connections
+ */
+
+const HTTPS_TLS1_0 = "https://tls1.example.com";
+const HTTPS_TLS1_1 = "https://tls11.example.com";
+const HTTPS_TLS1_2 = "https://tls12.example.com";
+const HTTPS_TLS1_3 = "https://tls13.example.com";
+
+
+function getIdentityMode(aWindow = window) {
+  return aWindow.document.getElementById("identity-box").className;
+}
+
+function getConnectionState() {
+  // Prevents items that are being lazy loaded causing issues
+  document.getElementById("identity-box").click();
+  gIdentityHandler.refreshIdentityPopup();
+  return document.getElementById("identity-popup").getAttribute("connection");
+}
+
+add_task(async function() {
+  await BrowserTestUtils.withNewTab("about:blank", async function(browser) {
+    // Try deprecated versions
+    await BrowserTestUtils.loadURI(browser, HTTPS_TLS1_0);
+    await BrowserTestUtils.browserLoaded(browser);
+    isSecurityState(browser, "broken");
+    is(getIdentityMode(), "unknownIdentity weakCipher", "Identity should be unknownIdentity");
+    is(getConnectionState(), "not-secure", "connectionState should be not-secure");
+
+    await BrowserTestUtils.loadURI(browser, HTTPS_TLS1_1);
+    await BrowserTestUtils.browserLoaded(browser);
+    isSecurityState(browser, "broken");
+    is(getIdentityMode(), "unknownIdentity weakCipher", "Identity should be unknownIdentity");
+    is(getConnectionState(), "not-secure", "connectionState should be not-secure");
+
+    // Transition to secure
+    await BrowserTestUtils.loadURI(browser, HTTPS_TLS1_2);
+    await BrowserTestUtils.browserLoaded(browser);
+    isSecurityState(browser, "secure");
+    is(getIdentityMode(), "verifiedDomain", "Identity should be verified");
+    is(getConnectionState(), "secure", "connectionState should be secure");
+
+    // Transition back to broken
+    await BrowserTestUtils.loadURI(browser, HTTPS_TLS1_1);
+    await BrowserTestUtils.browserLoaded(browser);
+    isSecurityState(browser, "broken");
+    is(getIdentityMode(), "unknownIdentity weakCipher", "Identity should be unknownIdentity");
+    is(getConnectionState(), "not-secure", "connectionState should be not-secure");
+
+    // TLS1.3 for completeness
+    await BrowserTestUtils.loadURI(browser, HTTPS_TLS1_3);
+    await BrowserTestUtils.browserLoaded(browser);
+    isSecurityState(browser, "secure");
+    is(getIdentityMode(), "verifiedDomain", "Identity should be verified");
+    is(getConnectionState(), "secure", "connectionState should be secure");
+  });
+});
--- a/browser/base/content/test/static/browser_misused_characters_in_strings.js
+++ b/browser/base/content/test/static/browser_misused_characters_in_strings.js
@@ -29,24 +29,16 @@ let gWhitelist = [{
     key: "certerror.whatShouldIDo.badStsCertExplanation1",
     type: "single-quote",
   }, {
     file: "netError.dtd",
     key: "inadequateSecurityError.longDesc",
     type: "single-quote",
   }, {
     file: "netError.dtd",
-    key: "certerror.wrongSystemTime2",
-    type: "single-quote",
-  }, {
-    file: "netError.dtd",
-    key: "certerror.wrongSystemTimeWithoutReference",
-    type: "single-quote",
-  }, {
-    file: "netError.dtd",
     key: "clockSkewError.longDesc",
     type: "single-quote",
   }, {
     file: "netError.dtd",
     key: "certerror.mitm.longDesc",
     type: "single-quote",
   }, {
     file: "netError.dtd",
--- 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/customizableui/content/panelUI.inc.xul
+++ b/browser/components/customizableui/content/panelUI.inc.xul
@@ -331,17 +331,17 @@
         <toolbarbutton id="appMenu-library-button"
                        class="subviewbutton subviewbutton-iconic subviewbutton-nav"
                        label="&places.library.title;"
                        closemenu="none"
                        oncommand="PanelUI.showSubView('appMenu-libraryView', this)"/>
         <toolbarbutton id="appMenu-logins-button"
                        class="subviewbutton subviewbutton-iconic"
                        label="&logins.label;"
-                       oncommand="LoginHelper.openPasswordManager(window)"
+                       oncommand="LoginHelper.openPasswordManager(window, { entryPoint: 'mainmenu' })"
                        />
         <toolbarbutton id="appMenu-addons-button"
                        class="subviewbutton subviewbutton-iconic"
                        label="&addons.label;"
                        key="key_openAddons"
                        command="Tools:Addons"
                        />
         <toolbarbutton id="appMenu-preferences-button"
@@ -420,17 +420,17 @@
 #endif
       </vbox>
     </panelview>
     <panelview id="PanelUI-history" flex="1">
       <vbox class="panel-subview-body">
         <toolbarbutton id="appMenuViewHistorySidebar"
                        label="&appMenuHistory.viewSidebar.label;"
                        label-checked="&appMenuHistory.hideSidebar.label;"
-                       label-unchecked="&appMenuHistory.viewSidebar.label;"                      
+                       label-unchecked="&appMenuHistory.viewSidebar.label;"
                        type="checkbox"
                        class="subviewbutton subviewbutton-iconic"
                        key="key_gotoHistory"
                        oncommand="SidebarUI.toggle('viewHistorySidebar');">
                        <observes element="sidebar-box" attribute="positionend"/>
         </toolbarbutton>
         <toolbarbutton id="appMenuClearRecentHistory"
                        label="&appMenuHistory.clearRecent.label;"
--- 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/components/preferences/in-content/privacy.js
+++ b/browser/components/preferences/in-content/privacy.js
@@ -289,16 +289,18 @@ var gPrivacyPane = {
     /* Initialize Content Blocking */
     this.initContentBlocking();
 
     this.blockAutoplayReadPrefs();
     this.trackingProtectionReadPrefs();
     this.networkCookieBehaviorReadPrefs();
     this._initTrackingProtectionExtensionControl();
 
+    Services.telemetry.setEventRecordingEnabled("pwmgr", true);
+
     Preferences.get("media.autoplay.default").on("change",
       gPrivacyPane.blockAutoplayReadPrefs.bind(gPrivacyPane));
 
     Preferences.get("privacy.trackingprotection.enabled").on("change",
       gPrivacyPane.trackingProtectionReadPrefs.bind(gPrivacyPane));
     Preferences.get("privacy.trackingprotection.pbmode.enabled").on("change",
       gPrivacyPane.trackingProtectionReadPrefs.bind(gPrivacyPane));
 
@@ -1417,19 +1419,22 @@ var gPrivacyPane = {
   },
 
   /**
  * Shows the sites where the user has saved passwords and the associated login
  * information.
  */
   showPasswords() {
     if (LoginHelper.managementURI) {
-      window.docShell.messageManager.sendAsyncMessage("PasswordManager:OpenPreferences", {});
+      window.docShell.messageManager.sendAsyncMessage("PasswordManager:OpenPreferences", {
+        entryPoint: "preferences",
+      });
       return;
     }
+    Services.telemetry.recordEvent("pwmgr", "open_management", "preferences");
     gSubDialog.open("chrome://passwordmgr/content/passwordManager.xul");
   },
 
   /**
    * Enables/disables the Exceptions button used to configure sites where
    * passwords are never saved. When browser is set to start in Private
    * Browsing mode, the "Remember passwords" UI is useless, so we disable it.
    */
--- a/browser/components/preferences/in-content/tests/browser_password_management.js
+++ b/browser/components/preferences/in-content/tests/browser_password_management.js
@@ -1,9 +1,12 @@
 "use strict";
+
+ChromeUtils.import("resource://testing-common/TelemetryTestUtils.jsm", this);
+
 const PM_URL = "chrome://passwordmgr/content/passwordManager.xul";
 const PREF_MANAGEMENT_URI = "signon.management.overrideURI";
 
 var passwordsDialog;
 
 add_task(async function test_setup() {
   Services.logins.removeAllLogins();
 
@@ -16,32 +19,38 @@ add_task(async function test_setup() {
 
   registerCleanupFunction(async function() {
     Services.logins.removeAllLogins();
     Services.prefs.clearUserPref(PREF_MANAGEMENT_URI);
   });
 });
 
 add_task(async function test_openPasswordSubDialog() {
+  Services.telemetry.clearEvents();
   await openPreferencesViaOpenPreferencesAPI("privacy", {leaveOpen: true});
 
   let dialogOpened = promiseLoadSubDialog(PM_URL);
 
   await ContentTask.spawn(gBrowser.selectedBrowser, null, function() {
     let doc = content.document;
     let savePasswordCheckBox = doc.getElementById("savePasswords");
     Assert.ok(!savePasswordCheckBox.checked,
               "Save Password CheckBox should be unchecked by default");
     savePasswordCheckBox.click();
 
     let showPasswordsButton = doc.getElementById("showPasswords");
     showPasswordsButton.click();
   });
 
   passwordsDialog = await dialogOpened;
+
+  // check telemetry events while we are in here
+  TelemetryTestUtils.assertEvents(
+    [["pwmgr", "open_management", "preferences"]],
+    {category: "pwmgr", method: "open_management"});
 });
 
 add_task(async function test_deletePasswordWithKey() {
   let doc = passwordsDialog.document;
 
   let tree = doc.getElementById("signonsTree");
   Assert.equal(tree.view.rowCount, 1, "Row count should initially be 1");
   tree.focus();
@@ -55,16 +64,17 @@ add_task(async function test_deletePassw
 
   await TestUtils.waitForCondition(() => tree.view.rowCount == 0);
 
   is_element_visible(content.gSubDialog._dialogs[0]._box,
     "Subdialog is visible after deleting an element");
 });
 
 add_task(async function subdialog_cleanup() {
+  Services.telemetry.clearEvents();
   // Undo the save password change.
   await ContentTask.spawn(gBrowser.selectedBrowser, null, function() {
     let doc = content.document;
     let savePasswordCheckBox = doc.getElementById("savePasswords");
     if (savePasswordCheckBox.checked) {
       savePasswordCheckBox.click();
     }
   });
--- a/browser/components/search/extensions/ebay/_locales/be/messages.json
+++ b/browser/components/search/extensions/ebay/_locales/be/messages.json
@@ -4,14 +4,14 @@
   },
   "extensionDescription": {
     "message": "eBay - Online auctions"
   },
   "searchUrl": {
     "message": "https://rover.ebay.com/rover/1/1553-53471-19255-0/1"
   },
   "searchForm": {
-    "message": "https://www.ebay.at/"
+    "message": "https://www.befr.ebay.be/"
   },
   "searchUrlGetParams": {
     "message": "ff3=4&toolid=20004&campid=5338192028&customid=&mpre=https://www.befr.ebay.be/sch/{searchTerms}"
   }
-}
\ No newline at end of file
+}
--- a/browser/locales/en-US/browser/newtab/onboarding.ftl
+++ b/browser/locales/en-US/browser/newtab/onboarding.ftl
@@ -1,40 +1,48 @@
 # 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/.
 
-## UI strings for the simplified onboarding modal
+### UI strings for the simplified onboarding modal / about:welcome
+### Various strings use a non-breaking space to avoid a single dangling /
+### widowed word, so test on various window sizes if you also want this.
+
+## These button action text can be split onto multiple lines, so use explicit
+## newlines in translations to control where the line break appears (e.g., to
+## avoid breaking quoted text).
 
 onboarding-button-label-learn-more = Learn More
 onboarding-button-label-try-now = Try It Now
 onboarding-button-label-get-started = Get Started
 
+## Welcome modal dialog strings
+
 onboarding-welcome-header = Welcome to { -brand-short-name }
 onboarding-welcome-body = You’ve got the browser.<br/>Meet the rest of { -brand-product-name }.
 onboarding-welcome-learn-more = Learn more about the benefits.
 
 onboarding-join-form-header = Join { -brand-product-name }
-onboarding-join-form-body = Enter your email address to get started.
+onboarding-join-form-body = Enter your email address to get started.
 onboarding-join-form-email =
     .placeholder = Enter email
 onboarding-join-form-email-error = Valid email required
-onboarding-join-form-legal = By proceeding, you agree to the <a data-l10n-name="terms">Terms of Service</a> and <a data-l10n-name="privacy">Privacy Notice</a>.
+onboarding-join-form-legal = By proceeding, you agree to the <a data-l10n-name="terms">Terms of Service</a> and <a data-l10n-name="privacy">Privacy Notice</a>.
 onboarding-join-form-continue = Continue
 
 onboarding-start-browsing-button-label = Start Browsing
 
 ## These are individual benefit messages shown with an image, title and
 ## description.
 
 onboarding-benefit-products-title = Useful Products
 onboarding-benefit-products-text = Get things done with a family of tools that respects your privacy across your devices.
 
 onboarding-benefit-knowledge-title = Practical Knowledge
-onboarding-benefit-knowledge-text = Learn everything you need to know to stay smarter and safer online.
+onboarding-benefit-knowledge-text = Learn everything you need to know to stay smarter and safer online.
 
 onboarding-benefit-privacy-title = True Privacy
 # "Personal Data Promise" is a concept that should be translated consistently
 # across the product. It refers to a concept shown elsewhere to the user: "The
 # Firefox Personal Data Promise is the way we honor your data in everything we
 # make and do. We take less data. We keep it safe. And we make sure that we are
 # transparent about how we use it."
 onboarding-benefit-privacy-text = Everything we do honors our Personal Data Promise: Take less. Keep it safe. No secrets.
@@ -60,54 +68,54 @@ onboarding-ghostery-text = Browse faster
 # Note: "Sync" in this case is a generic verb, as in "to synchronize"
 onboarding-fxa-title = Sync
 onboarding-fxa-text = Sign up for a { -fxaccount-brand-name } and sync your bookmarks, passwords, and open tabs everywhere you use { -brand-short-name }.
 
 onboarding-tracking-protection-title2 = Protection From Tracking
 onboarding-tracking-protection-text2 = { -brand-short-name } helps stop websites from tracking you online, making it harder for ads to follow you around the web.
 onboarding-tracking-protection-button2 = How it Works
 
-onboarding-data-sync-title = Take Your Settings with You
+onboarding-data-sync-title = Take Your Settings with You
 # "Sync" is short for synchronize.
 onboarding-data-sync-text2 = Sync your bookmarks, passwords, and more everywhere you use { -brand-product-name }.
 onboarding-data-sync-button2 = Sign in to { -sync-brand-short-name }
 
-onboarding-firefox-monitor-title = Stay Alert to Data Breaches
+onboarding-firefox-monitor-title = Stay Alert to Data Breaches
 onboarding-firefox-monitor-text = { -monitor-brand-name } monitors if your email has appeared in a data breach and alerts you if it appears in a new breach.
 onboarding-firefox-monitor-button = Sign up for Alerts
 
 onboarding-browse-privately-title = Browse Privately
 onboarding-browse-privately-text = Private Browsing clears your search and browsing history to keep it secret from anyone who uses your computer.
 onboarding-browse-privately-button = Open a Private Window
 
-onboarding-firefox-send-title = Keep Your Shared Files Private
+onboarding-firefox-send-title = Keep Your Shared Files Private
 onboarding-firefox-send-text2 = Upload your files to { -send-brand-name } to share them with end-to-end encryption and a link that automatically expires.
 onboarding-firefox-send-button = Try { -send-brand-name }
 
 onboarding-mobile-phone-title = Get { -brand-product-name } on Your Phone
 onboarding-mobile-phone-text = Download { -brand-product-name } for iOS or Android and sync your data across devices.
 # "Mobile" is short for mobile/cellular phone, "Browser" is short for web
 # browser.
 onboarding-mobile-phone-button = Download Mobile Browser
 
-onboarding-send-tabs-title = Instantly Send Yourself Tabs
+onboarding-send-tabs-title = Instantly Send Yourself Tabs
 # "Send Tabs" refers to "Send Tab to Device" feature that appears when opening a
 # tab's context menu.
 onboarding-send-tabs-text = Send Tabs instantly shares pages between your devices without having to copy, paste, or leave the browser.
 onboarding-send-tabs-button = Start Using Send Tabs
 
 onboarding-pocket-anywhere-title = Read and Listen Anywhere
-onboarding-pocket-anywhere-text2 = Save your favorite content offline with the { -pocket-brand-name } App and read, listen, and watch whenever it’s convenient for you.
+onboarding-pocket-anywhere-text2 = Save your favorite content offline with the { -pocket-brand-name } App and read, listen, and watch whenever it’s convenient for you.
 onboarding-pocket-anywhere-button = Try { -pocket-brand-name }
 
-onboarding-lockwise-passwords-title = Take Your Passwords Everywhere
+onboarding-lockwise-passwords-title = Take Your Passwords Everywhere
 onboarding-lockwise-passwords-text2 = Keep the passwords you save secure and easily log in to your accounts with { -lockwise-brand-name }.
 onboarding-lockwise-passwords-button2 = Get the App
 
-onboarding-facebook-container-title = Set Boundaries with Facebook
+onboarding-facebook-container-title = Set Boundaries with Facebook
 onboarding-facebook-container-text2 = { -facebook-container-brand-name } keeps your profile separate from everything else, making it harder for Facebook to target you with ads.
 onboarding-facebook-container-button = Add the Extension
 
 
 ## Message strings belonging to the Return to AMO flow
 return-to-amo-sub-header = Great, you’ve got { -brand-short-name }
 
 # <icon></icon> will be replaced with the icon belonging to the extension
--- a/browser/locales/en-US/chrome/overrides/netError.dtd
+++ b/browser/locales/en-US/chrome/overrides/netError.dtd
@@ -200,22 +200,16 @@ was trying to connect. -->
 <!ENTITY remoteXUL.title "Remote XUL">
 <!ENTITY remoteXUL.longDesc "<p><ul><li>Please contact the website owners to inform them of this problem.</li></ul></p>">
 
 <!ENTITY sslv3Used.title "Unable to Connect Securely">
 <!-- LOCALIZATION NOTE (sslv3Used.longDesc2) - Do not translate
      "SSL_ERROR_UNSUPPORTED_VERSION". -->
 <!ENTITY sslv3Used.longDesc2 "Advanced info: SSL_ERROR_UNSUPPORTED_VERSION">
 
-<!-- LOCALIZATION NOTE (certerror.wrongSystemTime2,
-                        certerror.wrongSystemTimeWithoutReference) - The <span id='..' />
-     tags will be injected with actual values, please leave them unchanged. -->
-<!ENTITY certerror.wrongSystemTime2 "<p> &brandShortName; did not connect to <span id='wrongSystemTime_URL'/> because your computer’s clock appears to show the wrong time and this is preventing a secure connection.</p> <p>Your computer is set to <span id='wrongSystemTime_systemDate'/>, when it should be <span id='wrongSystemTime_actualDate'/>. To fix this problem, change your date and time settings to match the correct time.</p>">
-<!ENTITY certerror.wrongSystemTimeWithoutReference "<p>&brandShortName; did not connect to <span id='wrongSystemTimeWithoutReference_URL'/> because your computer’s clock appears to show the wrong time and this is preventing a secure connection.</p> <p>Your computer is set to <span id='wrongSystemTimeWithoutReference_systemDate'/>. To fix this problem, change your date and time settings to match the correct time.</p>">
-
 <!ENTITY certerror.pagetitle2  "Warning: Potential Security Risk Ahead">
 <!ENTITY certerror.sts.pagetitle  "Did Not Connect: Potential Security Issue">
 <!ENTITY certerror.whatShouldIDo.badStsCertExplanation1 "<span class='hostname'></span> has a security policy called HTTP Strict Transport Security (HSTS), which means that &brandShortName; can only connect to it securely. You can’t add an exception to visit this site.">
 <!ENTITY certerror.copyToClipboard.label "Copy text to clipboard">
 
 <!ENTITY inadequateSecurityError.title "Your connection is not secure">
 <!-- LOCALIZATION NOTE (inadequateSecurityError.longDesc) - Do not translate
      "NS_ERROR_NET_INADEQUATE_SECURITY". -->
--- 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/browser/tools/mozscreenshots/mozscreenshots/extension/configurations/Tabs.jsm
+++ b/browser/tools/mozscreenshots/mozscreenshots/extension/configurations/Tabs.jsm
@@ -144,26 +144,27 @@ async function allTabTitlesDisplayed(bro
     "about:privatebrowsing": "about:privatebrowsing",
   };
   specToTitleMap[PREFS_TAB] = "browser/skin/settings.svg";
   specToTitleMap[CUST_TAB] = "browser/skin/customize.svg";
   specToTitleMap[DEFAULT_FAVICON_TAB] = "No favicon";
 
   let tabTitlePromises = [];
   for (let tab of browserWindow.gBrowser.tabs) {
-    function tabTitleLoaded(spec) {
-      return () => {
-        return spec ? tab.label == specToTitleMap[spec] : false;
-      };
+    function getSpec() {
+      return tab.linkedBrowser &&
+             tab.linkedBrowser.documentURI &&
+             tab.linkedBrowser.documentURI.spec;
     }
-    let spec = tab.linkedBrowser &&
-               tab.linkedBrowser.documentURI &&
-               tab.linkedBrowser.documentURI.spec;
+    function tabTitleLoaded() {
+      let spec = getSpec();
+      return spec ? tab.label == specToTitleMap[spec] : false;
+    }
     let promise =
-      TestUtils.waitForCondition(tabTitleLoaded(spec), `Tab (${spec}) should be showing "${specToTitleMap[spec]}". Got "${tab.label}"`);
+      TestUtils.waitForCondition(tabTitleLoaded, `Tab (${getSpec()}) should be showing "${specToTitleMap[getSpec()]}". Got "${tab.label}"`);
     tabTitlePromises.push(promise);
   }
 
   return Promise.all(tabTitlePromises);
 }
 
 function fiveTabsHelper() {
   // some with no favicon and some with. Selected tab in middle.
--- 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':
index 449e713dd95de0a52c62ecdd45c0788cc60931e1..5a3baaf54bbb34ecfd85a47dfc5f03cf4fd18177
GIT binary patch
literal 229376
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
z000000000000000000000000000000000000000000000000000002^|A8Dq6Tbu(
zZ72C@EBV<)^3z6zV2bCnB!6(;S~Ay4i(rrECQ?W-bQTSLV1I$$Qj%w(M{sQ`L1EG8
znW+p8on|h{Cu$QM@mwB-$DoEXSS$uBp%;Z_Cdo8aAn4*#6WCOWiDZtk4#62u%VJSd
zzFyu)l540$FvLqXU?7>NuSy`|xpZbcmyy7t)AS^>b+OreRx*p7!HS_$bR@I2vGt=(
zarGd7)fg>FuBHaT22Ww~=o}UWov_(F4M~nVmJ`QMa3{HWBi>Xxho>gVQN_9pZ3CCi
zNvCsEBy*Lq<>_21C6!L$@hH?}l9D7xQIlYcr>8Sm=mt{gGzPjM1xc2?E&=(j@qO5A
znw%tC7TZz^o0`br(YZVs$y{jz0vXRpNnx<iwlK3|Xbi4oXAmSyNNEuq6<F+Weq0ir
z%JXCL22fJyis->$mzRqZ-V*K?Rtt-T$-@3Z7ompWx!|l|i(r-@M=(OrLtri-<lV_T
zD0nT16WHXf7B&fw2v-Wrg@c6dLS4Zn!FIuX!6-q1KtAt5UVYv)VJf;x0000000000
z00000000000000000000000000000000000000000000000000000000000000000
z000000000000000000000QhP81b6(D;Q1%@Rg{-aOTJL8x@P+AZecQ~?XZm8obBC5
zPq%K_UM+O$op>YBW5rqrTP%Z|P_big6XE^Kd;|RB@)GMS`6{tpqzO2@O-IG7v0ST;
zaxJl3i;i;5v0P$D+cm>-O*<-Ug5?@_RMrT~HSDOY0hX)ZQLY}AtJ_ho4wkFkQLYx2
ztJzVlG_YLtj>@WGxvCxIs$jXw9px%vxr!a_a|J9{zN4~oSgveGxiVO;bVs=aELTd8
K;I7bK?*9Xkx|p{B
index 33da46fa1c9d223d0dcc95d05555632eb48408fe..c0de19d70781a569d2f7930056f5c6125d23a6e8
GIT binary patch
literal 294912
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
zqjl|6`Ce4h0l#veI8Nf<ezuLoP=<O#v2yK|uGu@L;3{sX*e1X5CP}82f-sk!fwc88
z9krY6l<!?lbZ92GQK3{V+eaRYK1hF|B=@7-kzS`k(yhBBc495U0DIk}mfX1RSqOsZ
z9N%E7$33wg^^Mw-65G=0DGH7*cjz5`ui3S4iMb;Agw8{mZi`7$7Ok(+*zm1`|Nr#P
zzIo_v`aeB#0{{R3;QtAl%`eiJ*F-VjViq?q000000000000000000000000000000
z00000000000000000000000000000000000000000000000000000000000000000
z004mhk5Q455mQQX+mmk1AojPAP?C~SQ}U-3&}=Rxjrm9vvl_F!c>w?b0000000000
z00000000000000000000000000000000000000000000000000000000000000000
z00000000000000000000{0~7!N<>Whb3#H&N<>W)^Mr^r+K?#PFvdOl$p7nX00000
z00000000000000000000000000000000000000000000000000000000000000000
z00000000000000000000000000Px?O3TZGkQeIvTDMLYOPew{7it+dG4f90HgoL?!
z2Kb>c{von~7=M3^zqc&<Fv>5`7cJ`%;D_}ji#H=iVqeJ9BB_6FNx?#5Z}-O)Di#ub
zU3oSn>(48cEcBKEVg8|;LkaA`pbyClAq9WlPQ$`w84!u_^+gHD2pH}W@I<?Zc?*Q1
zLqY{MH|9lh|6GZdh1Cx25f+RIjSw*O_s006(ZQQh<W-PLTX|VzHn-fpxy|N{0^*zZ
z5*!v1dMF?`)F(n>a~L^)lpk6X9WL{8SQ!tLyfjjhf?D#J$+2a6VrpVGUJoBsurDeU
z<*yU^Ymk5cCP{<Hu%5)o1M=cX(O>t*zZ>lr7!ZsKju6mC2Zv(3FdnE-w193{s87Jp
zjPe{vwqM^H;~(GKzrx82BN4y8A#Us7056O$TEI9A<B9eZ*cBLx@xw%-LNNjUB%6Cg
zn$TTG6SGJ}8gq{*W*}x}^8x?>0000000000000000000000000000000000000000
z0000000000000000000000000000000000000000000000000000000_-}}XRGK;@
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
UkY=fYzHY(ZTHatVY4pl}0l1aA_W%F@
index c7a408b7a6cbc0d3e2a77fbdbf4abaffb578aa8d..43c8c200fb5a07b4f9785f8252552eb52ef17cd1
GIT binary patch
literal 2448
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
CSbppP
--- a/build/pgo/server-locations.txt
+++ b/build/pgo/server-locations.txt
@@ -281,21 +281,24 @@ https://fail-handshake.example.com:443  
 
 # Hosts for sha1 console warning tests
 https://sha1ee.example.com:443                                    privileged,cert=sha1_end_entity
 https://sha256ee.example.com:443                                  privileged,cert=sha256_end_entity
 
 # Hosts for imminent distrust warning tests
 https://imminently-distrusted.example.com:443                     privileged,cert=imminently_distrusted
 
-# Hosts for ssl3/rc4 console warning tests
-https://ssl3.example.com:443        privileged,ssl3
-https://rc4.example.com:443         privileged,rc4
-https://ssl3rc4.example.com:443     privileged,ssl3,rc4
-https://tls1.example.com:443        privileged,tls1
+# Hosts for ssl3/rc4/tls1 warning tests
+https://ssl3.example.com:443         privileged,ssl3
+https://rc4.example.com:443          privileged,rc4
+https://ssl3rc4.example.com:443      privileged,ssl3,rc4
+https://tls1.example.com:443         privileged,tls1
+https://tls11.example.com:443        privileged,tls1_1
+https://tls12.example.com:443        privileged,tls1_2
+https://tls13.example.com:443        privileged,tls1,tls1_3
 
 # Hosts for youtube rewrite tests
 https://mochitest.youtube.com:443
 
 # Host for U2F localhost tests
 https://localhost:443
 
 # Bug 1402530
--- a/devtools/client/aboutdebugging-new/src/components/connect/ConnectPage.css
+++ b/devtools/client/aboutdebugging-new/src/components/connect/ConnectPage.css
@@ -33,8 +33,18 @@
 }
 .connect-page__usb-section__heading__status {
   grid-area: status;
   line-height: 1;
   font-size: var(--caption-20-font-size);
   font-weight: var(--caption-20-font-weight);
   color: var(--caption-20-color);
 }
+
+.connect-page__troubleshoot {
+  font-size: var(--body-10-font-size);
+  font-weight: var(--body-10-font-weight);
+  margin-block-start: calc(var(--base-unit) * 2);
+}
+
+.connect-page__troubleshoot--network {
+  padding-inline: calc(var(--base-unit) * 6);
+}
--- a/devtools/client/aboutdebugging-new/src/components/connect/ConnectPage.js
+++ b/devtools/client/aboutdebugging-new/src/components/connect/ConnectPage.js
@@ -26,16 +26,21 @@ const NetworkLocationsForm = createFacto
 const NetworkLocationsList = createFactory(require("./NetworkLocationsList"));
 
 const { PAGE_TYPES, RUNTIMES } = require("../../constants");
 const Types = require("../../types/index");
 
 const USB_ICON_SRC = "chrome://devtools/skin/images/aboutdebugging-usb-icon.svg";
 const GLOBE_ICON_SRC = "chrome://devtools/skin/images/aboutdebugging-globe-icon.svg";
 
+const TROUBLESHOOT_USB_URL =
+  "https://developer.mozilla.org/docs/Tools/Remote_Debugging/Debugging_over_USB";
+const TROUBLESHOOT_NETWORK_URL =
+  "https://developer.mozilla.org/docs/Tools/Remote_Debugging/Debugging_over_a_network";
+
 class ConnectPage extends PureComponent {
   static get propTypes() {
     return {
       adbAddonStatus: Types.adbAddonStatus,
       dispatch: PropTypes.func.isRequired,
       networkLocations: PropTypes.arrayOf(Types.location).isRequired,
     };
   }
@@ -140,43 +145,43 @@ class ConnectPage extends PureComponent 
           this.renderUsbToggleButton(),
         ),
       },
       isAddonInstalled
         ? ConnectSteps(
           {
             steps: [
               {
-                localizationId: "about-debugging-setup-usb-step-enable-dev-menu",
-                url: "https://developer.mozilla.org/docs/Tools/Remote_Debugging/Debugging_over_USB",
+                localizationId: "about-debugging-setup-usb-step-enable-dev-menu2",
+              },
+              {
+                localizationId: "about-debugging-setup-usb-step-enable-debug2",
               },
               {
-                localizationId: "about-debugging-setup-usb-step-enable-debug",
-                url: "https://developer.mozilla.org/docs/Tools/Remote_Debugging/Debugging_over_USB",
+                localizationId: "about-debugging-setup-usb-step-enable-debug-firefox2",
               },
               {
-                localizationId: "about-debugging-setup-usb-step-enable-debug-firefox",
-                url: "https://developer.mozilla.org/docs/Tools/Remote_Debugging/Debugging_over_USB",
+                localizationId: "about-debugging-setup-usb-step-plug-device",
               },
-              { localizationId: "about-debugging-setup-usb-step-plug-device" },
             ],
           }
         )
         : Localized(
           {
             id: "about-debugging-setup-usb-disabled",
           },
           dom.aside(
             {
               className: "qa-connect-usb-disabled-message",
             },
             "Enabling this will download and add the required Android USB debugging " +
               "components to Firefox."
           )
         ),
+        this.renderTroubleshootText(RUNTIMES.USB),
     );
   }
 
   renderNetwork() {
     const { dispatch, networkLocations } = this.props;
 
     return Localized(
       {
@@ -186,18 +191,54 @@ class ConnectPage extends PureComponent 
       ConnectSection({
         className: "connect-page__breather",
         icon: GLOBE_ICON_SRC,
         title: "Network Location",
         extraContent: dom.div(
           {},
           NetworkLocationsList({ dispatch, networkLocations }),
           NetworkLocationsForm({ dispatch, networkLocations }),
+          this.renderTroubleshootText(RUNTIMES.NETWORK),
         ),
-      })
+      },
+      )
+    );
+  }
+
+  renderTroubleshootText(connectionType) {
+    const localizationId = connectionType === RUNTIMES.USB
+      ? "about-debugging-setup-usb-troubleshoot"
+      : "about-debugging-setup-network-troubleshoot";
+
+    const className = "connect-page__troubleshoot connect-page__troubleshoot--" +
+      `${connectionType === RUNTIMES.USB ? "usb" : "network"}`;
+
+    const url = connectionType === RUNTIMES.USB
+      ? TROUBLESHOOT_USB_URL
+      : TROUBLESHOOT_NETWORK_URL;
+
+    return dom.aside(
+      {
+        className,
+      },
+      Localized(
+        {
+          id: localizationId,
+          a: dom.a(
+            {
+              href: url,
+              target: "_blank",
+            }
+          ),
+        },
+        dom.p(
+          {},
+          localizationId,
+        ),
+      )
     );
   }
 
   render() {
     return dom.article(
       {
         className: "page connect-page qa-connect-page",
       },
@@ -216,42 +257,25 @@ class ConnectPage extends PureComponent 
         {
           id: "about-debugging-setup-intro",
         },
         dom.p(
           {},
           "Configure the connection method you wish to remotely debug your device with."
         )
       ),
-      dom.p(
-        {},
-        Localized(
-          {
-            id: "about-debugging-setup-link-android-devices",
-          },
-          dom.a(
-            {
-              href: "https://support.mozilla.org/kb/will-firefox-work-my-mobile-device#w_android-devices",
-              target: "_blank",
-            },
-            "View list of supported android devices"
-          )
-        ),
-      ),
       Localized(
         {
           id: "about-debugging-setup-this-firefox",
           a: Link({
             to: `/runtime/${RUNTIMES.THIS_FIREFOX}`,
           }),
         },
         dom.p(
-          {
-            className: "connect-page__breather",
-          },
+          {},
           "about-debugging-setup-this-firefox",
         ),
       ),
       dom.section(
         {
           className: "connect-page__breather",
         },
         Localized(
--- a/devtools/client/aboutdebugging-new/src/components/connect/ConnectSteps.js
+++ b/devtools/client/aboutdebugging-new/src/components/connect/ConnectSteps.js
@@ -12,42 +12,37 @@ const FluentReact = require("devtools/cl
 const Localized = createFactory(FluentReact.Localized);
 
 class ConnectSteps extends PureComponent {
   static get propTypes() {
     return {
       steps: PropTypes.arrayOf(
         PropTypes.shape({
           localizationId: PropTypes.string.isRequired,
-          url: PropTypes.string,
         }).isRequired,
       ),
     };
   }
 
   render() {
     return dom.ul(
       {
         className: "connect-page__step-list",
       },
       ...this.props.steps.map(step =>
         Localized(
           {
             id: step.localizationId,
-            a: step.url ? dom.a({
-              href: step.url,
-              target: "_blank",
-            }) : null,
           },
           dom.li(
             {
               className: "connect-page__step",
-              key: step,
+              key: step.localizationId,
             },
-            step
+            step.localizationId
           )
         )
       )
     );
   }
 }
 
 module.exports = ConnectSteps;
--- a/devtools/client/accessibility/accessibility.css
+++ b/devtools/client/accessibility/accessibility.css
@@ -523,16 +523,23 @@ body {
   background-color: var(--theme-selection-background);
 }
 
 .accessible .tree:focus .node.focused *,
 .accessible .tree .tree-node-active .node.focused * {
   color: var(--theme-selection-color);
 }
 
+/* Invert text selection color in selected rows */
+.accessible .tree:focus .node.focused ::selection,
+.accessible .tree .tree-node-active .node.focused ::selection {
+  color: var(--theme-selection-background);
+  background-color: var(--theme-selection-color);
+}
+
 .accessible .tree:focus .node.focused .open-inspector,
 .accessible .tree .tree-node-active .node.focused .open-inspector {
   background-color: var(--grey-30);
 }
 
 .accessible .tree:focus .node.focused:hover .open-inspector,
 .accessible .tree .tree-node-active .node.focused:hover .open-inspector {
   background-color: var(--theme-selection-color);
--- 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/parser-worker.js
+++ b/devtools/client/debugger/dist/parser-worker.js
@@ -17940,28 +17940,32 @@ var _mapExpression2 = _interopRequireDef
 var _devtoolsUtils = __webpack_require__(7);
 
 function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
 
 const { workerHandler } = _devtoolsUtils.workerUtils; /* 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/>. */
 
+function clearState() {
+  (0, _ast.clearASTs)();
+  (0, _getScopes.clearScopes)();
+  (0, _sources.clearSources)();
+  (0, _getSymbols.clearSymbols)();
+}
+
 self.onmessage = workerHandler({
   findOutOfScopeLocations: _findOutOfScopeLocations2.default,
   getSymbols: _getSymbols.getSymbols,
   getScopes: _getScopes2.default,
-  clearSymbols: _getSymbols.clearSymbols,
-  clearScopes: _getScopes.clearScopes,
-  clearASTs: _ast.clearASTs,
-  setSource: _sources.setSource,
-  clearSources: _sources.clearSources,
+  clearState,
   getNextStep: _steps.getNextStep,
   hasSyntaxError: _validate.hasSyntaxError,
-  mapExpression: _mapExpression2.default
+  mapExpression: _mapExpression2.default,
+  setSource: _sources.setSource
 });
 
 /***/ }),
 /* 200 */
 /***/ (function(module, exports, __webpack_require__) {
 
 "use strict";
 
--- a/devtools/client/debugger/dist/vendors.css
+++ b/devtools/client/debugger/dist/vendors.css
@@ -34,20 +34,22 @@
 .tree-indent {
   display: inline-block;
   width: 12px;
   margin-inline-start: 3px;
   border-inline-start: 1px solid #a2d1ff;
   flex-shrink: 0;
 }
 
-.debugger .tree-indent {
-  width: 16px;
-  margin-inline-start: 0px;
-  border-inline-start: 0;
+.tree-node[data-expandable="false"] .tree-last-indent {
+  /* The 13px value is taken from the total width and margins of the arrow
+  element of expandables nodes (10px width + 3px margins). That way the
+  node's text are indented the same for both expandable and non-expandable
+  nodes */
+  margin-inline-end: 13px;
 }
 
 /* For non expandable root nodes, we don't have .tree-indent elements, so we declare
    the margin on the start of the node */
 .tree-node[data-expandable="false"][aria-level="1"] {
   padding-inline-start: 15px;
 }
 
@@ -73,18 +75,24 @@
   transform: rotate(-90deg);
 }
 
 html[dir="rtl"] .tree-node button:not(.expanded) {
   transform: rotate(90deg);
 }
 
 .tree .tree-node.focused {
-  color: white;
-  background-color: var(--theme-selection-background, #0a84ff);
+  color: var(--theme-selection-color);
+  background-color: var(--theme-selection-background);
+}
+
+/* Invert text selection color in selected rows */
+.tree .tree-node.focused ::selection {
+  color: var(--theme-selection-background);
+  background-color: var(--theme-selection-color);
 }
 
 .tree-node.focused button.arrow {
   background-color: currentColor;
 }
 /* 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/. */
--- a/devtools/client/debugger/dist/vendors.js
+++ b/devtools/client/debugger/dist/vendors.js
@@ -229,16 +229,17 @@ class ArrowExpander extends Component {
     }
     return _reactDomFactories2.default.button({
       className: classNames.join(" ")
     });
   }
 }
 
 const treeIndent = _reactDomFactories2.default.span({ className: "tree-indent" }, "\u200B");
+const treeLastIndent = _reactDomFactories2.default.span({ className: "tree-indent tree-last-indent" }, "\u200B");
 
 class TreeNode extends Component {
   static get propTypes() {
     return {
       id: _propTypes2.default.any.isRequired,
       index: _propTypes2.default.number.isRequired,
       depth: _propTypes2.default.number.isRequired,
       focused: _propTypes2.default.bool.isRequired,
@@ -354,17 +355,23 @@ class TreeNode extends Component {
     let ariaExpanded;
     if (this.props.isExpandable) {
       ariaExpanded = false;
     }
     if (this.props.expanded) {
       ariaExpanded = true;
     }
 
-    const indents = Array.from({ length: depth }).fill(treeIndent);
+    const indents = Array.from({ length: depth }, (_, i) => {
+      if (i == depth - 1) {
+        return treeLastIndent;
+      }
+      return treeIndent;
+    });
+
     const items = indents.concat(renderItem(item, depth, focused, arrow, expanded));
 
     return _reactDomFactories2.default.div({
       id,
       className: `tree-node${focused ? " focused" : ""}${active ? " active" : ""}`,
       onClick: this.props.onClick,
       onKeyDownCapture: active ? this._onKeyDown : null,
       role: "treeitem",
@@ -1429,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";
@@ -1851,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
@@ -2707,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)
@@ -2754,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
@@ -2794,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-components/src/tree.css
+++ b/devtools/client/debugger/packages/devtools-components/src/tree.css
@@ -34,20 +34,22 @@
 .tree-indent {
   display: inline-block;
   width: 12px;
   margin-inline-start: 3px;
   border-inline-start: 1px solid #a2d1ff;
   flex-shrink: 0;
 }
 
-.debugger .tree-indent {
-  width: 16px;
-  margin-inline-start: 0px;
-  border-inline-start: 0;
+.tree-node[data-expandable="false"] .tree-last-indent {
+  /* The 13px value is taken from the total width and margins of the arrow
+  element of expandables nodes (10px width + 3px margins). That way the
+  node's text are indented the same for both expandable and non-expandable
+  nodes */
+  margin-inline-end: 13px;
 }
 
 /* For non expandable root nodes, we don't have .tree-indent elements, so we declare
    the margin on the start of the node */
 .tree-node[data-expandable="false"][aria-level="1"] {
   padding-inline-start: 15px;
 }
 
@@ -73,15 +75,21 @@
   transform: rotate(-90deg);
 }
 
 html[dir="rtl"] .tree-node button:not(.expanded) {
   transform: rotate(90deg);
 }
 
 .tree .tree-node.focused {
-  color: white;
-  background-color: var(--theme-selection-background, #0a84ff);
+  color: var(--theme-selection-color);
+  background-color: var(--theme-selection-background);
+}
+
+/* Invert text selection color in selected rows */
+.tree .tree-node.focused ::selection {
+  color: var(--theme-selection-background);
+  background-color: var(--theme-selection-color);
 }
 
 .tree-node.focused button.arrow {
   background-color: currentColor;
 }
--- a/devtools/client/debugger/packages/devtools-components/src/tree.js
+++ b/devtools/client/debugger/packages/devtools-components/src/tree.js
@@ -48,16 +48,20 @@ class ArrowExpander extends Component {
     }
     return dom.button({
       className: classNames.join(" ")
     });
   }
 }
 
 const treeIndent = dom.span({ className: "tree-indent" }, "\u200B");
+const treeLastIndent = dom.span(
+  { className: "tree-indent tree-last-indent" },
+  "\u200B"
+);
 
 class TreeNode extends Component {
   static get propTypes() {
     return {
       id: PropTypes.any.isRequired,
       index: PropTypes.number.isRequired,
       depth: PropTypes.number.isRequired,
       focused: PropTypes.bool.isRequired,
@@ -185,17 +189,23 @@ class TreeNode extends Component {
     let ariaExpanded;
     if (this.props.isExpandable) {
       ariaExpanded = false;
     }
     if (this.props.expanded) {
       ariaExpanded = true;
     }
 
-    const indents = Array.from({ length: depth }).fill(treeIndent);
+    const indents = Array.from({length: depth}, (_, i) => {
+      if (i == depth - 1) {
+        return treeLastIndent;
+      }
+      return treeIndent;
+    })
+
     const items = indents.concat(
       renderItem(item, depth, focused, arrow, expanded)
     );
 
     return dom.div(
       {
         id,
         className: `tree-node${focused ? " focused" : ""}${
--- 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/panel.js
+++ b/devtools/client/debugger/panel.js
@@ -28,17 +28,20 @@ DebuggerPanel.prototype = {
       actions,
       store,
       selectors,
       client
     } = await this.panelWin.Debugger.bootstrap({
       threadClient: this.toolbox.threadClient,
       tabTarget: this.toolbox.target,
       debuggerClient: this.toolbox.target.client,
-      sourceMaps: this.toolbox.sourceMapService,
+      workers: {
+        sourceMaps: this.toolbox.sourceMapService,
+        evaluationsParser: this.toolbox.parserService
+      },
       panel: this
     });
 
     this._actions = actions;
     this._store = store;
     this._selectors = selectors;
     this._client = client;
     this.isReady = true;
--- a/devtools/client/debugger/src/actions/ast.js
+++ b/devtools/client/debugger/src/actions/ast.js
@@ -3,23 +3,21 @@
  * file, You can obtain one at <http://mozilla.org/MPL/2.0/>. */
 
 // @flow
 
 import { getSourceWithContent, getSelectedLocation } from "../selectors";
 
 import { setInScopeLines } from "./ast/setInScopeLines";
 
-import * as parser from "../workers/parser";
-
 import type { Context } from "../types";
 import type { ThunkArgs, Action } from "./types";
 
 export function setOutOfScopeLocations(cx: Context) {
-  return async ({ dispatch, getState }: ThunkArgs) => {
+  return async ({ dispatch, getState, parser }: ThunkArgs) => {
     const location = getSelectedLocation(getState());
     if (!location) {
       return;
     }
 
     const { source, content } = getSourceWithContent(
       getState(),
       location.sourceId
--- a/devtools/client/debugger/src/actions/breakpoints/breakpointPositions.js
+++ b/devtools/client/debugger/src/actions/breakpoints/breakpointPositions.js
@@ -39,16 +39,17 @@ async function mapLocations(
   generatedLocations: SourceLocation[],
   { sourceMaps }: ThunkArgs
 ) {
   if (generatedLocations.length == 0) {
     return [];
   }
 
   const { sourceId } = generatedLocations[0];
+
   const originalLocations = await sourceMaps.getOriginalLocations(
     sourceId,
     generatedLocations
   );
 
   return zip(originalLocations, generatedLocations).map(
     ([location, generatedLocation]) => ({ location, generatedLocation })
   );
--- a/devtools/client/debugger/src/actions/expressions.js
+++ b/devtools/client/debugger/src/actions/expressions.js
@@ -17,35 +17,34 @@ import {
   getIsPaused,
   isMapScopesEnabled
 } from "../selectors";
 import { PROMISE } from "./utils/middleware/promise";
 import { wrapExpression } from "../utils/expressions";
 import { features } from "../utils/prefs";
 import { isOriginal } from "../utils/source";
 
-import * as parser from "../workers/parser";
 import type { Expression, ThreadContext } from "../types";
 import type { ThunkArgs } from "./types";
 
 /**
  * Add expression for debugger to watch
  *
  * @param {object} expression
  * @param {number} expression.id
  * @memberof actions/pause
  * @static
  */
 export function addExpression(cx: ThreadContext, input: string) {
-  return async ({ dispatch, getState }: ThunkArgs) => {
+  return async ({ dispatch, getState, evaluationsParser }: ThunkArgs) => {
     if (!input) {
       return;
     }
 
-    const expressionError = await parser.hasSyntaxError(input);
+    const expressionError = await evaluationsParser.hasSyntaxError(input);
 
     const expression = getExpression(getState(), input);
     if (expression) {
       return dispatch(evaluateExpression(cx, expression));
     }
 
     dispatch({ type: "ADD_EXPRESSION", cx, input, expressionError });
 
@@ -75,17 +74,17 @@ export function clearExpressionError() {
   return { type: "CLEAR_EXPRESSION_ERROR" };
 }
 
 export function updateExpression(
   cx: ThreadContext,
   input: string,
   expression: Expression
 ) {
-  return async ({ dispatch, getState }: ThunkArgs) => {
+  return async ({ dispatch, getState, parser }: ThunkArgs) => {
     if (!input) {
       return;
     }
 
     const expressionError = await parser.hasSyntaxError(input);
     dispatch({
       type: "UPDATE_EXPRESSION",
       cx,
@@ -172,34 +171,40 @@ function evaluateExpression(cx: ThreadCo
   };
 }
 
 /**
  * Gets information about original variable names from the source map
  * and replaces all posible generated names.
  */
 export function getMappedExpression(expression: string) {
-  return async function({ dispatch, getState, client, sourceMaps }: ThunkArgs) {
+  return async function({
+    dispatch,
+    getState,
+    client,
+    sourceMaps,
+    evaluationsParser
+  }: ThunkArgs) {
     const thread = getCurrentThread(getState());
     const mappings = getSelectedScopeMappings(getState(), thread);
     const bindings = getSelectedFrameBindings(getState(), thread);
 
     // We bail early if we do not need to map the expression. This is important
-    // because mapping an expression can be slow if the parser worker is
-    // busy doing other work.
+    // because mapping an expression can be slow if the evaluationsParser
+    // worker is busy doing other work.
     //
     // 1. there are no mappings - we do not need to map original expressions
     // 2. does not contain `await` - we do not need to map top level awaits
     // 3. does not contain `=` - we do not need to map assignments
     const shouldMapScopes = isMapScopesEnabled(getState()) && mappings;
     if (!shouldMapScopes && !expression.match(/(await|=)/)) {
       return null;
     }
 
-    return parser.mapExpression(
+    return evaluationsParser.mapExpression(
       expression,
       mappings,
       bindings || [],
       features.mapExpressionBindings && getIsPaused(getState(), thread),
       features.mapAwaitExpression
     );
   };
 }
--- a/devtools/client/debugger/src/actions/navigation.js
+++ b/devtools/client/debugger/src/actions/navigation.js
@@ -7,46 +7,42 @@
 import { clearDocuments } from "../utils/editor";
 import sourceQueue from "../utils/source-queue";
 import { getSourceList } from "../reducers/sources";
 import { waitForMs } from "../utils/utils";
 
 import { newGeneratedSources } from "./sources";
 import { updateWorkers } from "./debuggee";
 
-import {
-  clearASTs,
-  clearSymbols,
-  clearScopes,
-  clearSources
-} from "../workers/parser";
-
 import { clearWasmStates } from "../utils/wasm";
 import { getMainThread } from "../selectors";
 import type { Action, ThunkArgs } from "./types";
 
 /**
  * Redux actions for the navigation state
  * @module actions/navigation
  */
 
 /**
  * @memberof actions/navigation
  * @static
  */
 export function willNavigate(event: Object) {
-  return function({ dispatch, getState, client, sourceMaps }: ThunkArgs) {
+  return async function({
+    dispatch,
+    getState,
+    client,
+    sourceMaps,
+    parser
+  }: ThunkArgs) {
     sourceQueue.clear();
     sourceMaps.clearSourceMaps();
     clearWasmStates();
     clearDocuments();
-    clearSymbols();
-    clearASTs();
-    clearScopes();
-    clearSources();
+    parser.clear();
     client.detachWorkers();
     const thread = getMainThread(getState());
 
     dispatch({
       type: "NAVIGATE",
       mainThread: { ...thread, url: event.url }
     });
   };
--- a/devtools/client/debugger/src/actions/pause/commands.js
+++ b/devtools/client/debugger/src/actions/pause/commands.js
@@ -8,17 +8,16 @@
 import {
   getSource,
   getSourceContent,
   getTopFrame,
   getSelectedFrame,
   getThreadContext
 } from "../../selectors";
 import { PROMISE } from "../utils/middleware/promise";
-import { getNextStep } from "../../workers/parser";
 import { addHiddenBreakpoint } from "../breakpoints";
 import { evaluateExpressions } from "../expressions";
 import { selectLocation } from "../sources";
 import { fetchScopes } from "./fetchScopes";
 import { features } from "../../utils/prefs";
 import { recordEvent } from "../../utils/telemetry";
 import assert from "../../utils/assert";
 import { isFulfilled, type AsyncValue } from "../../utils/async-value";
@@ -208,29 +207,32 @@ function hasAwait(content: AsyncValue<So
 
 /**
  * @memberOf actions/pause
  * @static
  * @param stepType
  * @returns {function(ThunkArgs)}
  */
 export function astCommand(cx: ThreadContext, stepType: Command) {
-  return async ({ dispatch, getState, sourceMaps }: ThunkArgs) => {
+  return async ({ dispatch, getState, sourceMaps, parser }: ThunkArgs) => {
     if (!features.asyncStepping) {
       return dispatch(command(cx, stepType));
     }
 
     if (stepType == "stepOver") {
       // This type definition is ambiguous:
       const frame: any = getTopFrame(getState(), cx.thread);
       const source = getSource(getState(), frame.location.sourceId);
       const content = source ? getSourceContent(getState(), source.id) : null;
 
       if (source && hasAwait(content, frame.location)) {
-        const nextLocation = await getNextStep(source.id, frame.location);
+        const nextLocation = await parser.getNextStep(
+          source.id,
+          frame.location
+        );
         if (nextLocation) {
           await dispatch(addHiddenBreakpoint(cx, nextLocation));
           return dispatch(command(cx, "resume"));
         }
       }
     }
 
     return dispatch(command(cx, stepType));
--- a/devtools/client/debugger/src/actions/pause/mapScopes.js
+++ b/devtools/client/debugger/src/actions/pause/mapScopes.js
@@ -50,17 +50,18 @@ export function toggleMapScopes() {
   };
 }
 
 export function mapScopes(
   cx: ThreadContext,
   scopes: Promise<Scope>,
   frame: Frame
 ) {
-  return async function({ dispatch, getState, client, sourceMaps }: ThunkArgs) {
+  return async function(thunkArgs: ThunkArgs) {
+    const { dispatch, getState } = thunkArgs;
     assert(cx.thread == frame.thread, "Thread mismatch");
 
     const generatedSource = getSource(
       getState(),
       frame.generatedLocation.sourceId
     );
 
     const source = getSource(getState(), frame.location.sourceId);
@@ -94,18 +95,17 @@ export function mapScopes(
 
           return await buildMappedScopes(
             source,
             content && isFulfilled(content)
               ? content.value
               : { type: "text", value: "", contentType: undefined },
             frame,
             await scopes,
-            sourceMaps,
-            client
+            thunkArgs
           );
         } catch (e) {
           log(e);
           return null;
         }
       })()
     });
   };
--- a/devtools/client/debugger/src/actions/sources/loadSourceText.js
+++ b/devtools/client/debugger/src/actions/sources/loadSourceText.js
@@ -16,17 +16,16 @@ import {
   getSourceActorsForSource
 } from "../../selectors";
 import { addBreakpoint } from "../breakpoints";
 
 import { prettyPrintSource } from "./prettyPrint";
 import { setBreakableLines } from "./breakableLines";
 import { isFulfilled } from "../../utils/async-value";
 
-import * as parser from "../../workers/parser";
 import { isOriginal, isPretty } from "../../utils/source";
 import {
   memoizeableAction,
   type MemoizedAction
 } from "../../utils/memoizableAction";
 
 import { Telemetry } from "devtools-modules";
 
@@ -88,17 +87,17 @@ async function loadSource(
     text: response.source,
     contentType: response.contentType || "text/javascript"
   };
 }
 
 async function loadSourceTextPromise(
   cx: Context,
   source: Source,
-  { dispatch, getState, client, sourceMaps }: ThunkArgs
+  { dispatch, getState, client, sourceMaps, parser }: ThunkArgs
 ): Promise<?Source> {
   const epoch = getSourcesEpoch(getState());
   await dispatch({
     type: "LOAD_SOURCE_TEXT",
     sourceId: source.id,
     epoch,
     [PROMISE]: loadSource(getState(), source, { sourceMaps, client, getState })
   });
--- a/devtools/client/debugger/src/actions/sources/symbols.js
+++ b/devtools/client/debugger/src/actions/sources/symbols.js
@@ -5,27 +5,25 @@
 // @flow
 
 import { hasSymbols, getSymbols } from "../../selectors";
 
 import { PROMISE } from "../utils/middleware/promise";
 import { updateTab } from "../tabs";
 import { loadSourceText } from "./loadSourceText";
 
-import * as parser from "../../workers/parser";
-
 import {
   memoizeableAction,
   type MemoizedAction
 } from "../../utils/memoizableAction";
 
 import type { Source, Context } from "../../types";
 import type { Symbols } from "../../reducers/types";
 
-async function doSetSymbols(cx, source, { dispatch, getState }) {
+async function doSetSymbols(cx, source, { dispatch, getState, parser }) {
   const sourceId = source.id;
 
   await dispatch(loadSourceText({ cx, source }));
 
   await dispatch({
     type: "SET_SYMBOLS",
     cx,
     sourceId,
--- 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/__snapshots__/project-text-search.spec.js.snap
+++ b/devtools/client/debugger/src/actions/tests/__snapshots__/project-text-search.spec.js.snap
@@ -119,16 +119,37 @@ Array [
       },
     ],
     "sourceId": "bar",
     "type": "RESULT",
   },
 ]
 `;
 
+exports[`project text search should search a specific source 2`] = `
+Array [
+  Object {
+    "filepath": "http://localhost:8000/examples/bar",
+    "matches": Array [
+      Object {
+        "column": 9,
+        "line": 1,
+        "match": "bla",
+        "matchIndex": 9,
+        "sourceId": "bar",
+        "type": "MATCH",
+        "value": "function bla(x, y) {",
+      },
+    ],
+    "sourceId": "bar",
+    "type": "RESULT",
+  },
+]
+`;
+
 exports[`project text search should search all the loaded sources based on the query 1`] = `
 Array [
   Object {
     "filepath": "http://localhost:8000/examples/foo1",
     "matches": Array [
       Object {
         "column": 9,
         "line": 1,
--- 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/debugger/src/actions/types/index.js
+++ b/devtools/client/debugger/src/actions/types/index.js
@@ -14,16 +14,17 @@ import type { SearchOperation } from "..
 import type { BreakpointAction } from "./BreakpointAction";
 import type { SourceAction } from "./SourceAction";
 import type { SourceActorAction } from "./SourceActorAction";
 import type { UIAction } from "./UIAction";
 import type { PauseAction } from "./PauseAction";
 import type { ASTAction } from "./ASTAction";
 import { clientCommands } from "../../client/firefox";
 import type { Panel } from "../../client/firefox/types";
+import type { ParserDispatcher } from "../../workers/parser";
 
 /**
  * Flow types
  * @module actions/types
  */
 
 /**
  * Argument parameters via Thunk middleware for {@link https://github.com/gaearon/redux-thunk|Redux Thunk}
@@ -33,16 +34,18 @@ import type { Panel } from "../../client
  * @typedef {Object} ThunkArgs
  */
 export type ThunkArgs = {
   dispatch: (action: any) => Promise<any>,
   forkedDispatch: (action: any) => Promise<any>,
   getState: () => State,
   client: typeof clientCommands,
   sourceMaps: SourceMaps,
+  parser: ParserDispatcher,
+  evaluationsParser: ParserDispatcher,
   panel: Panel
 };
 
 export type Thunk = ThunkArgs => any;
 
 export type ActionType = Object | Function;
 
 type ProjectTextSearchResult = {
--- a/devtools/client/debugger/src/actions/utils/middleware/log.js
+++ b/devtools/client/debugger/src/actions/utils/middleware/log.js
@@ -15,17 +15,18 @@ const blacklist = [
   "OUT_OF_SCOPE_LOCATIONS",
   "MAP_SCOPES",
   "MAP_FRAMES",
   "ADD_SCOPES",
   "IN_SCOPE_LINES",
   "REMOVE_BREAKPOINT",
   "NODE_PROPERTIES_LOADED",
   "SET_FOCUSED_SOURCE_ITEM",
-  "NODE_EXPAND"
+  "NODE_EXPAND",
+  "IN_SCOPE_LINES"
 ];
 
 function cloneAction(action: any) {
   action = action || {};
   action = { ...action };
 
   // ADD_TAB, ...
   if (action.source && action.source.text) {
--- a/devtools/client/debugger/src/client/index.js
+++ b/devtools/client/debugger/src/client/index.js
@@ -54,47 +54,47 @@ function getClient(connection: any) {
   const {
     tab: { clientType }
   } = connection;
   return clientType == "firefox" ? firefox : chrome;
 }
 
 export async function onConnect(
   connection: Object,
-  sourceMaps: Object,
+  panelWorkers: Object,
   panel: Panel
 ) {
   // NOTE: the landing page does not connect to a JS process
   if (!connection) {
     return;
   }
 
   verifyPrefSchema();
 
   const client = getClient(connection);
   const commands = client.clientCommands;
 
   const initialState = await loadInitialState();
+  const workers = bootstrapWorkers(panelWorkers);
 
   const { store, actions, selectors } = bootstrapStore(
     commands,
-    sourceMaps,
+    workers,
     panel,
     initialState
   );
 
-  const workers = bootstrapWorkers();
   await client.onConnect(connection, actions);
 
   await syncBreakpoints();
   syncXHRBreakpoints();
   setupHelper({
     store,
     actions,
     selectors,
-    workers: { ...workers, sourceMaps },
+    workers,
     connection,
     client: client.clientCommands
   });
 
   bootstrapApp(store);
   return { store, actions, selectors, client: commands };
 }
--- a/devtools/client/debugger/src/components/App.css
+++ b/devtools/client/debugger/src/components/App.css
@@ -24,16 +24,22 @@ button:focus {
 }
 
 .debugger {
   display: flex;
   flex: 1;
   height: 100%;
 }
 
+.debugger .tree-indent {
+  width: 16px;
+  margin-inline-start: 0;
+  border-inline-start: 0;
+}
+
 .editor-pane {
   display: flex;
   position: relative;
   flex: 1;
   background-color: var(--theme-body-background);
   height: 100%;
   overflow: hidden;
 }
--- a/devtools/client/debugger/src/components/shared/ManagedTree.css
+++ b/devtools/client/debugger/src/components/shared/ManagedTree.css
@@ -25,17 +25,17 @@
 }
 
 .managed-tree .tree .node {
   padding: 2px 3px 2px 3px;
   position: relative;
 }
 
 .managed-tree .tree .node.focused {
-  color: white;
+  color: var(--theme-selection-color);
   background-color: var(--theme-selection-background);
 }
 
 html:not([dir="rtl"]) .managed-tree .tree .node > div {
   margin-left: 10px;
 }
 
 html[dir="rtl"] .managed-tree .tree .node > div {
--- a/devtools/client/debugger/src/main.js
+++ b/devtools/client/debugger/src/main.js
@@ -14,29 +14,29 @@ function unmountRoot() {
   ReactDOM.unmountComponentAtNode(mount);
 }
 
 module.exports = {
   bootstrap: ({
     threadClient,
     tabTarget,
     debuggerClient,
-    sourceMaps,
+    workers,
     panel
   }: any) =>
     onConnect(
       {
         tab: { clientType: "firefox" },
         tabConnection: {
           tabTarget,
           threadClient,
           debuggerClient
         }
       },
-      sourceMaps,
+      workers,
       panel
     ),
   destroy: () => {
     unmountRoot();
     sourceQueue.clear();
     teardownWorkers();
   }
 };
--- a/devtools/client/debugger/src/test/tests-setup.js
+++ b/devtools/client/debugger/src/test/tests-setup.js
@@ -18,22 +18,17 @@ import { prefs } from "../utils/prefs";
 
 import { startSourceMapWorker, stopSourceMapWorker } from "devtools-source-map";
 
 import {
   start as startPrettyPrintWorker,
   stop as stopPrettyPrintWorker
 } from "../workers/pretty-print";
 
-import {
-  start as startParserWorker,
-  stop as stopParserWorker,
-  clearSymbols,
-  clearASTs
-} from "../workers/parser";
+import { ParserDispatcher } from "../workers/parser";
 import {
   start as startSearchWorker,
   stop as stopSearchWorker
 } from "../workers/search";
 import { clearDocuments } from "../utils/editor";
 import { clearHistory } from "./utils/history";
 
 import env from "devtools-environment/test-flag";
@@ -63,42 +58,43 @@ global.URL = URL;
 global.indexedDB = mockIndexeddDB();
 
 Enzyme.configure({ adapter: new Adapter() });
 
 function formatException(reason, p) {
   console && console.log("Unhandled Rejection at:", p, "reason:", reason);
 }
 
+export const parserWorker = new ParserDispatcher();
+
 beforeAll(() => {
   startSourceMapWorker(
     path.join(rootPath, "node_modules/devtools-source-map/src/worker.js"),
     ""
   );
   startPrettyPrintWorker(
     path.join(rootPath, "src/workers/pretty-print/worker.js")
   );
-  startParserWorker(path.join(rootPath, "src/workers/parser/worker.js"));
+  parserWorker.start(path.join(rootPath, "src/workers/parser/worker.js"));
   startSearchWorker(path.join(rootPath, "src/workers/search/worker.js"));
   process.on("unhandledRejection", formatException);
 });
 
 afterAll(() => {
   stopSourceMapWorker();
   stopPrettyPrintWorker();
-  stopParserWorker();
+  parserWorker.stop();
   stopSearchWorker();
   process.removeListener("unhandledRejection", formatException);
 });
 
 afterEach(() => {});
 
 beforeEach(async () => {
-  clearASTs();
-  await clearSymbols();
+  parserWorker.clear();
   clearHistory();
   clearDocuments();
   prefs.projectDirectoryRoot = "";
 
   // Ensures window.dbg is there to track telemetry
   setupHelper({ selectors: {} });
 });
 
--- a/devtools/client/debugger/src/utils/bootstrap.js
+++ b/devtools/client/debugger/src/utils/bootstrap.js
@@ -11,85 +11,94 @@ const { Provider } = require("react-redu
 
 import { isFirefoxPanel, isDevelopment, isTesting } from "devtools-environment";
 import SourceMaps, {
   startSourceMapWorker,
   stopSourceMapWorker
 } from "devtools-source-map";
 import * as search from "../workers/search";
 import * as prettyPrint from "../workers/pretty-print";
-import * as parser from "../workers/parser";
+import { ParserDispatcher } from "../workers/parser";
 
 import configureStore from "../actions/utils/create-store";
 import reducers from "../reducers";
 import * as selectors from "../selectors";
 import App from "../components/App";
 import { asyncStore, prefs } from "./prefs";
 
 import type { Panel } from "../client/firefox/types";
 
+let parser;
+
 function renderPanel(component, store) {
   const root = document.createElement("div");
   root.className = "launchpad-root theme-body";
   root.style.setProperty("flex", "1");
   const mount = document.querySelector("#mount");
   if (!mount) {
     return;
   }
   mount.appendChild(root);
 
   ReactDOM.render(
     React.createElement(Provider, { store }, React.createElement(component)),
     root
   );
 }
 
+type Workers = {
+  sourceMaps: typeof SourceMaps,
+  evaluationsParser: typeof ParserDispatcher
+};
+
 export function bootstrapStore(
   client: any,
-  sourceMaps: typeof SourceMaps,
+  workers: Workers,
   panel: Panel,
   initialState: Object
 ) {
   const createStore = configureStore({
     log: prefs.logging || isTesting(),
     timing: isDevelopment(),
     makeThunkArgs: (args, state) => {
-      return { ...args, client, sourceMaps, panel };
+      return { ...args, client, ...workers, panel };
     }
   });
 
   const store = createStore(combineReducers(reducers), initialState);
   store.subscribe(() => updatePrefs(store.getState()));
 
   const actions = bindActionCreators(
     require("../actions").default,
     store.dispatch
   );
 
   return { store, actions, selectors };
 }
 
-export function bootstrapWorkers() {
+export function bootstrapWorkers(panelWorkers: Workers) {
   const workerPath = isDevelopment()
     ? "assets/build"
     : "resource://devtools/client/debugger/dist";
 
   if (isDevelopment()) {
     // When used in Firefox, the toolbox manages the source map worker.
     startSourceMapWorker(
       `${workerPath}/source-map-worker.js`,
       // This is relative to the worker itself.
       "./source-map-worker-assets/"
     );
   }
 
   prettyPrint.start(`${workerPath}/pretty-print-worker.js`);
+  parser = new ParserDispatcher();
+
   parser.start(`${workerPath}/parser-worker.js`);
   search.start(`${workerPath}/search-worker.js`);
-  return { prettyPrint, parser, search };
+  return { ...panelWorkers, prettyPrint, parser, search };
 }
 
 export function teardownWorkers() {
   if (!isFirefoxPanel()) {
     // When used in Firefox, the toolbox manages the source map worker.
     stopSourceMapWorker();
   }
   prettyPrint.stop();
--- a/devtools/client/debugger/src/utils/pause/mapScopes/index.js
+++ b/devtools/client/debugger/src/utils/pause/mapScopes/index.js
@@ -2,17 +2,16 @@
  * 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/>. */
 
 // @flow
 
 import typeof SourceMaps from "devtools-source-map";
 
 import {
-  getScopes,
   type SourceScope,
   type BindingData,
   type BindingLocation
 } from "../../../workers/parser";
 import type { RenderableScope } from "../scopes/getScope";
 import { locColumn } from "./locColumn";
 import {
   loadRangeMetadata,
@@ -32,16 +31,18 @@ import {
   type GeneratedBindingLocation
 } from "./buildGeneratedBindingList";
 import {
   originalRangeStartsInside,
   getApplicableBindingsForOriginalPosition
 } from "./getApplicableBindingsForOriginalPosition";
 
 import { log } from "../../log";
+import type { ThunkArgs } from "../../../actions/types";
+
 import type {
   PartialPosition,
   Frame,
   Scope,
   Source,
   SourceContent,
   BindingContents,
   ScopeBindings
@@ -49,26 +50,25 @@ import type {
 
 export type OriginalScope = RenderableScope;
 
 export async function buildMappedScopes(
   source: Source,
   content: SourceContent,
   frame: Frame,
   scopes: Scope,
-  sourceMaps: any,
-  client: any
+  { client, parser, sourceMaps }: ThunkArgs
 ): Promise<?{
   mappings: {
     [string]: string
   },
   scope: OriginalScope
 }> {
-  const originalAstScopes = await getScopes(frame.location);
-  const generatedAstScopes = await getScopes(frame.generatedLocation);
+  const originalAstScopes = await parser.getScopes(frame.location);
+  const generatedAstScopes = await parser.getScopes(frame.generatedLocation);
 
   if (!originalAstScopes || !generatedAstScopes) {
     return null;
   }
 
   const originalRanges = await loadRangeMetadata(
     source,
     frame,
--- a/devtools/client/debugger/src/utils/test-head.js
+++ b/devtools/client/debugger/src/utils/test-head.js
@@ -10,16 +10,17 @@
  */
 
 import { combineReducers } from "redux";
 import sourceMaps from "devtools-source-map";
 import reducers from "../reducers";
 import actions from "../actions";
 import * as selectors from "../selectors";
 import { getHistory } from "../test/utils/history";
+import { parserWorker } from "../test/tests-setup";
 import configureStore from "../actions/utils/create-store";
 import sourceQueue from "../utils/source-queue";
 import type { Source, OriginalSourceData, GeneratedSourceData } from "../types";
 
 /**
  * This file contains older interfaces used by tests that have not been
  * converted to use test-mockup.js
  */
@@ -36,17 +37,18 @@ function createStore(client: any, initia
 
   const store = configureStore({
     log: false,
     history: getHistory(),
     makeThunkArgs: args => {
       return {
         ...args,
         client,
-        sourceMaps: sourceMapsMock !== undefined ? sourceMapsMock : sourceMaps
+        sourceMaps: sourceMapsMock !== undefined ? sourceMapsMock : sourceMaps,
+        parser: parserWorker
       };
     }
   })(combineReducers(reducers), initialState);
   sourceQueue.clear();
   sourceQueue.initialize({
     newQueuedSources: sources =>
       store.dispatch(actions.newQueuedSources(sources))
   });
--- a/devtools/client/debugger/src/workers/parser/index.js
+++ b/devtools/client/debugger/src/workers/parser/index.js
@@ -7,87 +7,81 @@
 import { workerUtils } from "devtools-utils";
 const { WorkerDispatcher } = workerUtils;
 
 import type { AstSource, AstLocation, AstPosition } from "./types";
 import type { SourceLocation, SourceId, SourceContent } from "../../types";
 import type { SourceScope } from "./getScopes/visitor";
 import type { SymbolDeclarations } from "./getSymbols";
 
-const dispatcher = new WorkerDispatcher();
-export const start = (url: string, win: any = window) =>
-  dispatcher.start(url, win);
-export const stop = () => dispatcher.stop();
-
-export const findOutOfScopeLocations = async (
-  sourceId: string,
-  position: AstPosition
-): Promise<AstLocation[]> =>
-  dispatcher.invoke("findOutOfScopeLocations", sourceId, position);
-
-export const getNextStep = async (
-  sourceId: SourceId,
-  pausedPosition: AstPosition
-): Promise<?SourceLocation> =>
-  dispatcher.invoke("getNextStep", sourceId, pausedPosition);
+export class ParserDispatcher extends WorkerDispatcher {
+  async findOutOfScopeLocations(
+    sourceId: string,
+    position: AstPosition
+  ): Promise<AstLocation[]> {
+    return this.invoke("findOutOfScopeLocations", sourceId, position);
+  }
 
-export const clearASTs = async (): Promise<void> =>
-  dispatcher.invoke("clearASTs");
-
-export const getScopes = async (
-  location: SourceLocation
-): Promise<SourceScope[]> => dispatcher.invoke("getScopes", location);
+  async getNextStep(
+    sourceId: SourceId,
+    pausedPosition: AstPosition
+  ): Promise<?SourceLocation> {
+    return this.invoke("getNextStep", sourceId, pausedPosition);
+  }
 
-export const clearScopes = async (): Promise<void> =>
-  dispatcher.invoke("clearScopes");
+  async clearState(): Promise<void> {
+    return this.invoke("clearState");
+  }
 
-export const clearSymbols = async (): Promise<void> =>
-  dispatcher.invoke("clearSymbols");
+  async getScopes(location: SourceLocation): Promise<SourceScope[]> {
+    return this.invoke("getScopes", location);
+  }
 
-export const getSymbols = async (
-  sourceId: string
-): Promise<SymbolDeclarations> => dispatcher.invoke("getSymbols", sourceId);
+  async getSymbols(sourceId: string): Promise<SymbolDeclarations> {
+    return this.invoke("getSymbols", sourceId);
+  }
 
-export const setSource = async (
-  sourceId: SourceId,
-  content: SourceContent
-): Promise<void> => {
-  const astSource: AstSource = {
-    id: sourceId,
-    text: content.type === "wasm" ? "" : content.value,
-    contentType: content.contentType || null,
-    isWasm: content.type === "wasm"
-  };
+  async setSource(sourceId: SourceId, content: SourceContent): Promise<void> {
+    const astSource: AstSource = {
+      id: sourceId,
+      text: content.type === "wasm" ? "" : content.value,
+      contentType: content.contentType || null,
+      isWasm: content.type === "wasm"
+    };
 
-  await dispatcher.invoke("setSource", astSource);
-};
+    return this.invoke("setSource", astSource);
+  }
 
-export const clearSources = async (): Promise<void> =>
-  dispatcher.invoke("clearSources");
+  async hasSyntaxError(input: string): Promise<string | false> {
+    return this.invoke("hasSyntaxError", input);
+  }
 
-export const hasSyntaxError = async (input: string): Promise<string | false> =>
-  dispatcher.invoke("hasSyntaxError", input);
+  async mapExpression(
+    expression: string,
+    mappings: {
+      [string]: string | null
+    } | null,
+    bindings: string[],
+    shouldMapBindings?: boolean,
+    shouldMapAwait?: boolean
+  ): Promise<{ expression: string }> {
+    return this.invoke(
+      "mapExpression",
+      expression,
+      mappings,
+      bindings,
+      shouldMapBindings,
+      shouldMapAwait
+    );
+  }
 
-export const mapExpression = async (
-  expression: string,
-  mappings: {
-    [string]: string | null
-  } | null,
-  bindings: string[],
-  shouldMapBindings?: boolean,
-  shouldMapAwait?: boolean
-): Promise<{ expression: string }> =>
-  dispatcher.invoke(
-    "mapExpression",
-    expression,
-    mappings,
-    bindings,
-    shouldMapBindings,
-    shouldMapAwait
-  );
+  async clear() {
+    await this.clearState();
+  }
+}
 
 export type {
   SourceScope,
   BindingData,
   BindingLocation,
   BindingLocationType,
   BindingDeclarationLocation,
   BindingMetaValue,
--- a/devtools/client/debugger/src/workers/parser/worker.js
+++ b/devtools/client/debugger/src/workers/parser/worker.js
@@ -11,21 +11,25 @@ import { setSource, clearSources } from 
 import findOutOfScopeLocations from "./findOutOfScopeLocations";
 import { getNextStep } from "./steps";
 import { hasSyntaxError } from "./validate";
 import mapExpression from "./mapExpression";
 
 import { workerUtils } from "devtools-utils";
 const { workerHandler } = workerUtils;
 
+function clearState() {
+  clearASTs();
+  clearScopes();
+  clearSources();
+  clearSymbols();
+}
+
 self.onmessage = workerHandler({
   findOutOfScopeLocations,
   getSymbols,
   getScopes,
-  clearSymbols,
-  clearScopes,
-  clearASTs,
-  setSource,
-  clearSources,
+  clearState,
   getNextStep,
   hasSyntaxError,
-  mapExpression
+  mapExpression,
+  setSource
 });
--- a/devtools/client/debugger/test/mochitest/helpers.js
+++ b/devtools/client/debugger/test/mochitest/helpers.js
@@ -1055,18 +1055,18 @@ const keyMappings = {
   quickOpen: { code: "p", modifiers: cmdOrCtrl },
   quickOpenFunc: { code: "o", modifiers: cmdShift },
   quickOpenLine: { code: ":", modifiers: cmdOrCtrl },
   fileSearch: { code: "f", modifiers: cmdOrCtrl },
   fileSearchNext: { code: "g", modifiers: cmdOrCtrl },
   fileSearchPrev: { code: "g", modifiers: cmdShift },
   Enter: { code: "VK_RETURN" },
   ShiftEnter: { code: "VK_RETURN", modifiers: shiftOrAlt },
-  AltEnter: { 
-    code: "VK_RETURN", 
+  AltEnter: {
+    code: "VK_RETURN",
     modifiers: { altKey: true }
   },
   Up: { code: "VK_UP" },
   Down: { code: "VK_DOWN" },
   Right: { code: "VK_RIGHT" },
   Left: { code: "VK_LEFT" },
   End: endKey,
   Start: startKey,
--- a/devtools/client/framework/toolbox.js
+++ b/devtools/client/framework/toolbox.js
@@ -932,16 +932,34 @@ Toolbox.prototype = {
    * A common access point for the client-side mapping service for source maps that
    * any panel can use.  This is a "low-level" API that connects to
    * the source map worker.
    */
   get sourceMapService() {
     return this._createSourceMapService();
   },
 
+    /**
+   * A common access point for the client-side parser service that any panel can use.
+   */
+  get parserService() {
+    if (this._parserService) {
+      return this._parserService;
+    }
+
+    const { ParserDispatcher } =
+      require("devtools/client/debugger/src/workers/parser/index");
+
+    this._parserService = new ParserDispatcher();
+    this._parserService.start(
+      "resource://devtools/client/debugger/dist/parser-worker.js",
+      this.win);
+    return this._parserService;
+  },
+
   /**
    * Clients wishing to use source maps but that want the toolbox to
    * track the source and style sheet actor mapping can use this
    * source map service.  This is a higher-level service than the one
    * returned by |sourceMapService|, in that it automatically tracks
    * source and style sheet actor IDs.
    */
   get sourceMapURLService() {
@@ -3069,21 +3087,27 @@ Toolbox.prototype = {
     this.telemetry.toolClosed(this.currentToolId, this.sessionId, this);
 
     this._lastFocusedElement = null;
 
     if (this._sourceMapURLService) {
       this._sourceMapURLService.destroy();
       this._sourceMapURLService = null;
     }
+
     if (this._sourceMapService) {
       this._sourceMapService.stopSourceMapWorker();
       this._sourceMapService = null;
     }
 
+    if (this._parserService) {
+      this._parserService.stop();
+      this._parserService = null;
+    }
+
     if (this.webconsolePanel) {
       this._saveSplitConsoleHeight();
       this.webconsolePanel.removeEventListener("resize",
         this._saveSplitConsoleHeight);
       this.webconsolePanel = null;
     }
     if (this._componentMount) {
       this._componentMount.removeEventListener("keypress", this._onToolbarArrowKeypress);
--- a/devtools/client/locales/en-US/aboutdebugging.ftl
+++ b/devtools/client/locales/en-US/aboutdebugging.ftl
@@ -96,20 +96,16 @@ about-debugging-refresh-usb-devices-butt
 # Setup Page strings
 
 # Title of the Setup page.
 about-debugging-setup-title = Setup
 
 # Introduction text in the Setup page to explain how to configure remote debugging.
 about-debugging-setup-intro = Configure the connection method you wish to remotely debug your device with.
 
-# Link displayed in the Setup page that leads to MDN page with list of supported devices.
-# Temporarily leads to https://support.mozilla.org/en-US/kb/will-firefox-work-my-mobile-device#w_android-devices
-about-debugging-setup-link-android-devices = View list of supported Android devices
-
 # Explanatory text in the Setup page about what the 'This Firefox' page is for
 about-debugging-setup-this-firefox = Use <a>{ about-debugging-this-firefox-runtime-name }</a> to debug tabs, extensions and service workers on this version of { -brand-shorter-name }.
 
 # Title of the heading Connect section of the Setup page.
 about-debugging-setup-connect-heading = Connect a Device
 
 # USB section of the Setup page
 about-debugging-setup-usb-title = USB
@@ -129,31 +125,39 @@ about-debugging-setup-usb-disable-button
 about-debugging-setup-usb-updating-button = Updating…
 
 # USB section of the Setup page (USB status)
 about-debugging-setup-usb-status-enabled = Enabled
 about-debugging-setup-usb-status-disabled = Disabled
 about-debugging-setup-usb-status-updating = Updating…
 
 # USB section step by step guide
-about-debugging-setup-usb-step-enable-dev-menu = Enable Developer menu on your Android device. <a>Learn how</a>
+about-debugging-setup-usb-step-enable-dev-menu2 = Enable Developer menu on your Android device.
 
 # USB section step by step guide
-about-debugging-setup-usb-step-enable-debug = Enable USB Debugging in the Android Developer Menu. <a>Learn how</a>
+about-debugging-setup-usb-step-enable-debug2 = Enable USB Debugging in the Android Developer Menu.
 
 # USB section step by step guide
-about-debugging-setup-usb-step-enable-debug-firefox = Enable USB Debugging in Firefox on the Android device. <a>Learn how</a>
+about-debugging-setup-usb-step-enable-debug-firefox2 = Enable USB Debugging in Firefox on the Android device.
 
 # USB section step by step guide
 about-debugging-setup-usb-step-plug-device = Connect the Android device to your computer.
 
+# Text shown in the USB section of the setup page with a link to troubleshoot connection errors.
+# The link goes to https://developer.mozilla.org/docs/Tools/Remote_Debugging/Debugging_over_USB
+about-debugging-setup-usb-troubleshoot = Problems connecting to the USB device? <a>Troubleshoot</a>
+
 # Network section of the Setup page
 about-debugging-setup-network =
   .title = Network Location
 
+# Text shown in the Network section of the setup page with a link to troubleshoot connection errors.
+# The link goes to https://developer.mozilla.org/en-US/docs/Tools/Remote_Debugging/Debugging_over_a_network
+about-debugging-setup-network-troubleshoot = Problems connecting via network location? <a>Troubleshoot</a>
+
 # Text of a button displayed after the network locations "Host" input.
 # Clicking on it will add the new network location to the list.
 about-debugging-network-locations-add-button = Add
 
 # Text to display when there are no locations to show.
 about-debugging-network-locations-empty-text = No network locations have been added yet.
 
 # Text of the label for the text input that allows users to add new network locations in
--- a/devtools/client/netmonitor/src/components/RequestListContent.js
+++ b/devtools/client/netmonitor/src/components/RequestListContent.js
@@ -124,16 +124,26 @@ class RequestListContent extends Compone
   }
 
   componentWillUnmount() {
     this.refs.scrollEl.removeEventListener("scroll", this.onScroll, true);
 
     // Uninstall the tooltip event handler
     this.tooltip.stopTogglingOnHover();
     window.removeEventListener("resize", this.onResize);
+
+    // Performance optimization. React is slow when removing long lists
+    // of elements (i.e. the list of HTTP requests in this case), and so
+    // let's clean up the content through `innerHTML` property in advance.
+    // This significantly speeds up Network panel closing time.
+    // This isn't React friendly since React has no way to know the DOM
+    // has been modified. But, the panel is closing at this point anyway.
+    // See also: Bug 1546513 - Closing the network panel with many
+    // entries takes multiple seconds
+    this.refs.rowGroupEl.innerHTML = "";
   }
 
   /*
    * Removing onResize() method causes perf regression - too many repaints of the panel.
    * So it is needed in ComponentDidMount and ComponentDidUpdate. See Bug 1532914.
    */
   onResize() {
     const parent = this.refs.scrollEl.parentNode;
--- a/devtools/client/netmonitor/src/utils/request-utils.js
+++ b/devtools/client/netmonitor/src/utils/request-utils.js
@@ -310,23 +310,28 @@ function getUrlDetails(url) {
  * @param {string} query - query string of a url portion
  * @return {array} array of query params { name, value }
  */
 function parseQueryString(query) {
   if (!query) {
     return null;
   }
 
-  return query.replace(/^[?&]/, "").split("&").map(e => {
-    const param = e.split("=");
-    return {
-      name: param[0] ? getUnicodeUrlPath(param[0]) : "",
-      value: param[1] ? getUnicodeUrlPath(param.slice(1).join("=")) : "",
-    };
-  });
+  return query
+    .replace(/^[?&]/, "")
+    .split("&")
+    .map(e => {
+      const param = e.split("=");
+      return {
+        name: param[0] ? getUnicodeUrlPath(param[0]) : "",
+        value: param[1]
+          ? getUnicodeUrlPath(param.slice(1).join("=")).replace(/\+/g, " ")
+          : "",
+      };
+    });
 }
 
 /**
  * Parse a string of formdata sections into its components
  *
  * @param {string} sections - sections of formdata joined by &
  * @return {array} array of formdata params { name, value }
  */
--- a/devtools/client/netmonitor/test/browser.ini
+++ b/devtools/client/netmonitor/test/browser.ini
@@ -26,16 +26,17 @@ support-files =
   html_maps-test-page.html
   html_navigate-test-page.html
   html_params-test-page.html
   html_pause-test-page.html
   html_post-data-test-page.html
   html_post-array-data-test-page.html
   html_post-json-test-page.html
   html_post-raw-test-page.html
+  html_header-test-page.html
   html_post-raw-with-headers-test-page.html
   html_simple-test-page.html
   html_single-get-page.html
   html_send-beacon.html
   html_sorting-test-page.html
   html_statistics-test-page.html
   html_status-codes-test-page.html
   html_tracking-protection.html
@@ -133,16 +134,18 @@ skip-if = (os == 'mac') || (os == 'win' 
 [browser_net_filter-01.js]
 [browser_net_filter-02.js]
 [browser_net_filter-03.js]
 [browser_net_filter-04.js]
 [browser_net_filter-autocomplete.js]
 [browser_net_filter-flags.js]
 [browser_net_footer-summary.js]
 [browser_net_header-ref-policy.js]
+[browser_net_decode-url.js]
+[browser_net_decode-params.js]
 [browser_net_headers-alignment.js]
 [browser_net_headers_filter.js]
 [browser_net_headers_sorted.js]
 [browser_net_headers-resize.js]
 [browser_net_image-tooltip.js]
 [browser_net_json-b64.js]
 [browser_net_json-empty.js]
 [browser_net_json-null.js]
new file mode 100644
--- /dev/null
+++ b/devtools/client/netmonitor/test/browser_net_decode-params.js
@@ -0,0 +1,39 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/**
+ * Tests if "+" is replaces with spaces in the Params panel.
+ */
+add_task(async function() {
+  const { tab, monitor } = await initNetMonitor(POST_RAW_URL_WITH_HASH);
+  info("Starting test... ");
+
+  const { document, store, windowRequire } = monitor.panelWin;
+  const Actions = windowRequire("devtools/client/netmonitor/src/actions/index");
+  store.dispatch(Actions.batchEnable(false));
+
+  // Execute request.
+  await performRequests(monitor, tab, 1);
+
+  // Wait until the tab panel summary is displayed
+  wait = waitUntil(() =>
+    document.querySelectorAll(".tabpanel-summary-label")[0]);
+  EventUtils.sendMouseEvent({ type: "mousedown" },
+  document.querySelectorAll(".request-list-item")[0]);
+  await wait;
+
+  EventUtils.sendMouseEvent({ type: "click" },
+    document.querySelector("#params-tab"));
+
+  // The Params panel should render the following:
+  // Query String:
+  // file    foo # bar
+  const keyValue = document.querySelectorAll(".treeTable .treeRow")[1];
+
+  is(keyValue.innerText,
+  "file\tfoo # bar", "'+' in params in correctly decoded.");
+
+  return teardown(monitor);
+});
new file mode 100644
--- /dev/null
+++ b/devtools/client/netmonitor/test/browser_net_decode-url.js
@@ -0,0 +1,33 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/**
+ * Tests if "Request URL" containing "#" in its query is correctly decoded.
+ */
+add_task(async function() {
+  const { tab, monitor } = await initNetMonitor(POST_RAW_URL_WITH_HASH);
+  info("Starting test... ");
+
+  const { document, store, windowRequire } = monitor.panelWin;
+  const Actions = windowRequire("devtools/client/netmonitor/src/actions/index");
+  store.dispatch(Actions.batchEnable(false));
+
+  // Execute request.
+  await performRequests(monitor, tab, 1);
+
+  // Wait until the tab panel summary is displayed
+  wait = waitUntil(() =>
+    document.querySelectorAll(".tabpanel-summary-label")[0]);
+  EventUtils.sendMouseEvent({ type: "mousedown" },
+  document.querySelectorAll(".request-list-item")[0]);
+  await wait;
+
+  const requestURL = document.querySelectorAll(".tabpanel-summary-value")[0];
+
+  is(requestURL.textContent.endsWith("foo+%23+bar"),
+  true, "\"Request URL\" containing '#' is correctly decoded.");
+
+  return teardown(monitor);
+});
--- a/devtools/client/netmonitor/test/head.js
+++ b/devtools/client/netmonitor/test/head.js
@@ -50,16 +50,17 @@ const NAVIGATE_URL = EXAMPLE_URL + "html
 const CONTENT_TYPE_WITHOUT_CACHE_URL = EXAMPLE_URL + "html_content-type-without-cache-test-page.html";
 const CONTENT_TYPE_WITHOUT_CACHE_REQUESTS = 8;
 const CYRILLIC_URL = EXAMPLE_URL + "html_cyrillic-test-page.html";
 const STATUS_CODES_URL = EXAMPLE_URL + "html_status-codes-test-page.html";
 const POST_DATA_URL = EXAMPLE_URL + "html_post-data-test-page.html";
 const POST_ARRAY_DATA_URL = EXAMPLE_URL + "html_post-array-data-test-page.html";
 const POST_JSON_URL = EXAMPLE_URL + "html_post-json-test-page.html";
 const POST_RAW_URL = EXAMPLE_URL + "html_post-raw-test-page.html";
+const POST_RAW_URL_WITH_HASH = EXAMPLE_URL + "html_header-test-page.html";
 const POST_RAW_WITH_HEADERS_URL = EXAMPLE_URL + "html_post-raw-with-headers-test-page.html";
 const PARAMS_URL = EXAMPLE_URL + "html_params-test-page.html";
 const JSONP_URL = EXAMPLE_URL + "html_jsonp-test-page.html";
 const JSON_LONG_URL = EXAMPLE_URL + "html_json-long-test-page.html";
 const JSON_MALFORMED_URL = EXAMPLE_URL + "html_json-malformed-test-page.html";
 const JSON_CUSTOM_MIME_URL = EXAMPLE_URL + "html_json-custom-mime-test-page.html";
 const JSON_TEXT_MIME_URL = EXAMPLE_URL + "html_json-text-mime-test-page.html";
 const JSON_B64_URL = EXAMPLE_URL + "html_json-b64.html";
new file mode 100644
--- /dev/null
+++ b/devtools/client/netmonitor/test/html_header-test-page.html
@@ -0,0 +1,43 @@
+<!-- Any copyright is dedicated to the Public Domain.
+     http://creativecommons.org/publicdomain/zero/1.0/ -->
+<!doctype html>
+
+<html>
+  <head>
+    <meta charset="utf-8"/>
+    <meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate" />
+    <meta http-equiv="Pragma" content="no-cache" />
+    <meta http-equiv="Expires" content="0" />
+    <title>Network Monitor test page</title>
+  </head>
+
+  <body>
+    <p>POST raw test</p>
+
+    <script type="text/javascript">
+      /* exported performRequests */
+      "use strict";
+
+      function post(address, message, callback) {
+        const xhr = new XMLHttpRequest();
+        xhr.open("POST", address, true);
+        xhr.setRequestHeader("content-type", "application/x-www-form-urlencoded");
+
+        xhr.onreadystatechange = function() {
+          if (this.readyState == this.DONE) {
+            callback();
+          }
+        };
+        xhr.send(message);
+      }
+
+      function performRequests() {
+        const rawData = "";
+        post("sjs_simple-test-server.sjs?file=foo+%23+bar#home", rawData, function() {
+          // Done.
+        });
+      }
+    </script>
+  </body>
+
+</html>
--- a/devtools/client/shared/components/reps/reps.css
+++ b/devtools/client/shared/components/reps/reps.css
@@ -34,20 +34,22 @@
 .tree-indent {
   display: inline-block;
   width: 12px;
   margin-inline-start: 3px;
   border-inline-start: 1px solid #a2d1ff;
   flex-shrink: 0;
 }
 
-.debugger .tree-indent {
-  width: 16px;
-  margin-inline-start: 0px;
-  border-inline-start: 0;
+.tree-node[data-expandable="false"] .tree-last-indent {
+  /* The 13px value is taken from the total width and margins of the arrow
+  element of expandables nodes (10px width + 3px margins). That way the
+  node's text are indented the same for both expandable and non-expandable
+  nodes */
+  margin-inline-end: 13px;
 }
 
 /* For non expandable root nodes, we don't have .tree-indent elements, so we declare
    the margin on the start of the node */
 .tree-node[data-expandable="false"][aria-level="1"] {
   padding-inline-start: 15px;
 }
 
@@ -73,18 +75,24 @@
   transform: rotate(-90deg);
 }
 
 html[dir="rtl"] .tree-node button:not(.expanded) {
   transform: rotate(90deg);
 }
 
 .tree .tree-node.focused {
-  color: white;
-  background-color: var(--theme-selection-background, #0a84ff);
+  color: var(--theme-selection-color);
+  background-color: var(--theme-selection-background);
+}
+
+/* Invert text selection color in selected rows */
+.tree .tree-node.focused ::selection {
+  color: var(--theme-selection-background);
+  background-color: var(--theme-selection-color);
 }
 
 .tree-node.focused button.arrow {
   background-color: currentColor;
 }
 /* vim:set ts=2 sw=2 sts=2 et: */
 /* 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
--- a/devtools/client/shared/components/reps/reps.js
+++ b/devtools/client/shared/components/reps/reps.js
@@ -170,16 +170,17 @@ class ArrowExpander extends Component {
     }
     return _reactDomFactories2.default.button({
       className: classNames.join(" ")
     });
   }
 }
 
 const treeIndent = _reactDomFactories2.default.span({ className: "tree-indent" }, "\u200B");
+const treeLastIndent = _reactDomFactories2.default.span({ className: "tree-indent tree-last-indent" }, "\u200B");
 
 class TreeNode extends Component {
   static get propTypes() {
     return {
       id: _propTypes2.default.any.isRequired,
       index: _propTypes2.default.number.isRequired,
       depth: _propTypes2.default.number.isRequired,
       focused: _propTypes2.default.bool.isRequired,
@@ -295,17 +296,23 @@ class TreeNode extends Component {
     let ariaExpanded;
     if (this.props.isExpandable) {
       ariaExpanded = false;
     }
     if (this.props.expanded) {
       ariaExpanded = true;
     }
 
-    const indents = Array.from({ length: depth }).fill(treeIndent);
+    const indents = Array.from({ length: depth }, (_, i) => {
+      if (i == depth - 1) {
+        return treeLastIndent;
+      }
+      return treeIndent;
+    });
+
     const items = indents.concat(renderItem(item, depth, focused, arrow, expanded));
 
     return _reactDomFactories2.default.div({
       id,
       className: `tree-node${focused ? " focused" : ""}${active ? " active" : ""}`,
       onClick: this.props.onClick,
       onKeyDownCapture: active ? this._onKeyDown : null,
       role: "treeitem",
--- a/devtools/client/shared/components/tree/TreeView.css
+++ b/devtools/client/shared/components/tree/TreeView.css
@@ -89,16 +89,22 @@
 }
 
 .treeTable .treeRow.selected *,
 .treeTable .treeRow.selected .treeLabelCell::after {
   color: var(--theme-selection-color);
   fill: currentColor;
 }
 
+/* Invert text selection color in selected rows */
+.treeTable .treeRow.selected :not(input):not(textarea)::selection {
+  color: var(--theme-selection-background);
+  background-color: var(--theme-selection-color);
+}
+
 /* Filtering */
 .treeTable .treeRow.hidden {
   display: none !important;
 }
 
 .treeTable .treeValueCellDivider {
   display: flex;
   flex-wrap: wrap;
--- 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/devtools/client/shared/unicode-url.js
+++ b/devtools/client/shared/unicode-url.js
@@ -72,17 +72,17 @@ function getUnicodeUrlPath(urlPath) {
  *
  * If the `url` is a readable ASCII URL, such as http://example.org/a/b/c.js,
  * then this function will simply return the original `url`.
  *
  * If the `url` includes either an unreadable Punycode domain name or an
  * unreadable URI-encoded pathname, such as
  * http://xn--g6w.xn--8pv/%E8%A9%A6/%E6%B8%AC.js, then this function will return
  * the readable URL by decoding all its unreadable URL components to Unicode
- * characters.
+ * characters. The character `#` is not decoded from escape sequences.
  *
  * If the `url` is a malformed URL, then this function will return the original
  * `url`.
  *
  * If the `url` is a data: URI, then this function will return the original
  * `url`.
  *
  * @param {string}  url
@@ -95,17 +95,17 @@ function getUnicodeUrlPath(urlPath) {
 function getUnicodeUrl(url) {
   try {
     const { protocol, hostname } = new URL(url);
     if (protocol === "data:") {
       // Never convert a data: URI.
       return url;
     }
     const readableHostname = getUnicodeHostname(hostname);
-    url = decodeURIComponent(url);
+    url = decodeURI(url);
     return url.replace(hostname, readableHostname);
   } catch (err) {
   }
   return url;
 }
 
 module.exports = {
   getUnicodeHostname,
--- a/devtools/client/themes/common.css
+++ b/devtools/client/themes/common.css
@@ -54,16 +54,21 @@
  * Customize scrollbar colors on Linux + light theme, to avoid visual conflicts
  * between the light theme and the selected GTK theme (see bug 1471163).
  * This removes GTK scrollbars and uses a fallback design (see bug 1464723).
  */
 :root[platform="linux"].theme-light {
   scrollbar-color: var(--grey-40) var(--grey-20);
 }
 
+::selection {
+  background-color: var(--theme-selection-background);
+  color: var(--theme-selection-color);
+}
+
 .devtools-monospace {
   font-family: var(--monospace-font-family);
   font-size: var(--theme-code-font-size);
 }
 
 /**
  * For text that needs to be cut with an ellipsis …
  */
--- a/devtools/client/themes/dark-theme.css
+++ b/devtools/client/themes/dark-theme.css
@@ -17,21 +17,16 @@ body {
   color: var(--theme-body-color);
 }
 
 .theme-sidebar {
   background: var(--theme-sidebar-background);
   color: var(--theme-body-color);
 }
 
-::-moz-selection {
-  background-color: var(--theme-selection-background);
-  color: var(--theme-selection-color);
-}
-
 .theme-selected,
 .CodeMirror-hint-active {
   background-color: var(--theme-selection-background);
   color: var(--theme-selection-color);
 }
 
 .theme-bg-contrast,
 .variable-or-property:not([overridden])[changed] {
--- a/devtools/client/themes/light-theme.css
+++ b/devtools/client/themes/light-theme.css
@@ -17,21 +17,16 @@ body {
   color: var(--theme-body-color);
 }
 
 .theme-sidebar {
   background: var(--theme-sidebar-background);
   color: var(--theme-body-color);
 }
 
-::-moz-selection {
-  background-color: var(--theme-selection-background);
-  color: var(--theme-selection-color);
-}
-
 .theme-selected,
 .CodeMirror-hint-active {
   background-color: var(--theme-selection-background);
   color: var(--theme-selection-color);
 }
 
 .theme-bg-contrast,
 .variable-or-property:not([overridden])[changed] {
--- a/devtools/client/webconsole/webconsole.js
+++ b/devtools/client/webconsole/webconsole.js
@@ -296,27 +296,25 @@ class WebConsole {
       const res = this.parserService.mapExpression(
         expression, null, null, shouldMapBindings, shouldMapAwait);
       return res;
     }
 
     return null;
   }
 
-  /**
-   * A common access point for the client-side parser service that any panel can use.
-   */
   get parserService() {
     if (this._parserService) {
       return this._parserService;
     }
 
-    this._parserService =
+    const { ParserDispatcher } =
       require("devtools/client/debugger/src/workers/parser/index");
 
+    this._parserService = new ParserDispatcher();
     this._parserService.start(
       "resource://devtools/client/debugger/dist/parser-worker.js",
       this.chromeUtilsWindow);
     return this._parserService;
   }
 
   /**
    * Retrieves the current selection from the Inspector, if such a selection
--- 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/docshell/base/nsDocShellTreeOwner.cpp
+++ b/docshell/base/nsDocShellTreeOwner.cpp
@@ -868,36 +868,30 @@ nsDocShellTreeOwner::HandleEvent(Event* 
     bool canDropLink = false;
     handler->CanDropLink(dragEvent, false, &canDropLink);
     if (canDropLink) {
       aEvent->PreventDefault();
     }
   } else if (eventType.EqualsLiteral("drop")) {
     nsIWebNavigation* webnav = static_cast<nsIWebNavigation*>(mWebBrowser);
 
-    uint32_t linksCount;
-    nsIDroppedLinkItem** links;
-    if (webnav && NS_SUCCEEDED(handler->DropLinks(dragEvent, true, &linksCount,
-                                                  &links))) {
-      if (linksCount >= 1) {
+    nsTArray<RefPtr<nsIDroppedLinkItem>> links;
+    if (webnav && NS_SUCCEEDED(handler->DropLinks(dragEvent, true, links))) {
+      if (links.Length() >= 1) {
         nsCOMPtr<nsIPrincipal> triggeringPrincipal;
         handler->GetTriggeringPrincipal(dragEvent,
                                         getter_AddRefs(triggeringPrincipal));
         if (triggeringPrincipal) {
           nsCOMPtr<nsIWebBrowserChrome> webBrowserChrome =
               GetWebBrowserChrome();
           if (webBrowserChrome) {
             nsCOMPtr<nsIBrowserChild> browserChild =
                 do_QueryInterface(webBrowserChrome);
             if (browserChild) {
-              nsresult rv = browserChild->RemoteDropLinks(linksCount, links);
-              for (uint32_t i = 0; i < linksCount; i++) {
-                NS_RELEASE(links[i]);
-              }
-              free(links);
+              nsresult rv = browserChild->RemoteDropLinks(links);
               return rv;
             }
           }
           nsAutoString url;
           if (NS_SUCCEEDED(links[0]->GetUrl(url))) {
             if (!url.IsEmpty()) {
 #ifndef ANDROID
               MOZ_ASSERT(triggeringPrincipal,
@@ -907,21 +901,16 @@ nsDocShellTreeOwner::HandleEvent(Event* 
               LoadURIOptions loadURIOptions;
               loadURIOptions.mTriggeringPrincipal = triggeringPrincipal;
               nsCOMPtr<nsIContentSecurityPolicy> csp;
               handler->GetCSP(dragEvent, getter_AddRefs(csp));
               loadURIOptions.mCsp = csp;
               webnav->LoadURI(url, loadURIOptions);
             }
           }
-
-          for (uint32_t i = 0; i < linksCount; i++) {
-            NS_RELEASE(links[i]);
-          }
-          free(links);
         }
       }
     } else {
       aEvent->StopPropagation();
       aEvent->PreventDefault();
     }
   }
 
--- a/dom/base/ContentAreaDropListener.jsm
+++ b/dom/base/ContentAreaDropListener.jsm
@@ -271,17 +271,17 @@ ContentAreaDropListener.prototype =
       let name = links[0].name;
       if (name)
         aName.value = name;
     }
 
     return url;
   },
 
-  dropLinks: function(aEvent, aDisallowInherit, aCount)
+  dropLinks: function(aEvent, aDisallowInherit)
   {
     if (aEvent && this._eventTargetIsDisabled(aEvent))
       return [];
 
     let dataTransfer = aEvent.dataTransfer;
     let links = this._getDropLinks(dataTransfer);
     let triggeringPrincipal = this._getTriggeringPrincipalFromDataTransfer(dataTransfer, false);
 
@@ -292,40 +292,34 @@ ContentAreaDropListener.prototype =
       } catch (ex) {
         // Prevent the drop entirely if any of the links are invalid even if
         // one of them is valid.
         aEvent.stopPropagation();
         aEvent.preventDefault();
         throw ex;
       }
     }
-    if (aCount)
-      aCount.value = links.length;
 
     return links;
   },
 
-  validateURIsForDrop: function(aEvent, aURIsCount, aURIs, aDisallowInherit)
+  validateURIsForDrop: function(aEvent, aURIs, aDisallowInherit)
   {
     let dataTransfer = aEvent.dataTransfer;
     let triggeringPrincipal = this._getTriggeringPrincipalFromDataTransfer(dataTransfer, false);
 
     for (let uri of aURIs) {
       this._validateURI(dataTransfer, uri, aDisallowInherit,
                         triggeringPrincipal);
     }
   },
 
-  queryLinks: function(aDataTransfer, aCount)
+  queryLinks: function(aDataTransfer)
   {
-    let links = this._getDropLinks(aDataTransfer);
-    if (aCount) {
-      aCount.value = links.length;
-    }
-    return links;
+    return this._getDropLinks(aDataTransfer);
   },
 
   _eventTargetIsDisabled: function(aEvent)
   {
     let ownerDoc = aEvent.originalTarget.ownerDocument;
     if (!ownerDoc || !ownerDoc.defaultView)
       return false;
 
--- a/dom/base/DOMIntersectionObserver.cpp
+++ b/dom/base/DOMIntersectionObserver.cpp
@@ -288,16 +288,17 @@ void DOMIntersectionObserver::Update(Doc
                                                             : rootRect.Width();
     rootMargin.Side(side) =
         nsLayoutUtils::ComputeCBDependentValue(basis, mRootMargin.Get(side));
   }
 
   for (size_t i = 0; i < mObservationTargets.Length(); ++i) {
     Element* target = mObservationTargets.ElementAt(i);
     nsIFrame* targetFrame = target->GetPrimaryFrame();
+    nsIFrame* originalTargetFrame = targetFrame;
     nsRect targetRect;
     Maybe<nsRect> intersectionRect;
     bool isSameDoc = root && root->GetComposedDoc() == target->GetComposedDoc();
 
     if (rootFrame && targetFrame) {
       // If mRoot is set we are testing intersection with a container element
       // instead of the implicit root.
       if (mRoot) {
@@ -369,17 +370,17 @@ void DOMIntersectionObserver::Update(Doc
       nsRect intersectionRectRelativeToRoot =
           nsLayoutUtils::TransformFrameRectToAncestor(
               targetFrame, intersectionRect.value(),
               nsLayoutUtils::GetContainingBlockForClientRect(rootFrame));
       intersectionRect = EdgeInclusiveIntersection(
           intersectionRectRelativeToRoot, rootIntersectionRect);
       if (intersectionRect.isSome() && !isSameDoc) {
         nsRect rect = intersectionRect.value();
-        nsPresContext* presContext = targetFrame->PresContext();
+        nsPresContext* presContext = originalTargetFrame->PresContext();
         nsIFrame* rootScrollFrame =
             presContext->PresShell()->GetRootScrollFrame();
         if (rootScrollFrame) {
           nsLayoutUtils::TransformRect(rootFrame, rootScrollFrame, rect);
         }
         intersectionRect = Some(rect);
       }
     }
--- 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; }
@@ -1162,58 +1163,16 @@ nsHTMLCopyEncoder::SetSelection(Selectio
   for (nsCOMPtr<nsIContent> selContent(do_QueryInterface(commonParent));
        selContent; selContent = selContent->GetParent()) {
     // checking for selection inside a plaintext form widget
     if (selContent->IsAnyOfHTMLElements(nsGkAtoms::input,
                                         nsGkAtoms::textarea)) {
       mIsTextWidget = true;
       break;
     }
-#if defined(MOZ_THUNDERBIRD) || defined(MOZ_SUITE)
-    else if (selContent->IsHTMLElement(nsGkAtoms::body)) {
-      // Currently, setting mIsTextWidget to 'true' will result in the selection
-      // being encoded/copied as pre-formatted plain text.
-      // This is fine for copying pre-formatted plain text with Firefox, it is
-      // already not correct for copying pre-formatted "rich" text (bold,
-      // colour) with Firefox. As long as the serialisers aren't fixed, copying
-      // pre-formatted text in Firefox is broken. If we set mIsTextWidget,
-      // pre-formatted plain text is copied, but pre-formatted "rich" text loses
-      // the "rich" formatting. If we don't set mIsTextWidget, "rich" text
-      // attributes aren't lost, but white-space is lost.
-      // So far the story for Firefox.
-      //
-      // Thunderbird has two *conflicting* requirements.
-      // Case 1:
-      // When selecting and copying text, even pre-formatted text, as a quote
-      // to be placed into a reply, we *always* expect HTML to be copied.
-      // Case 2:
-      // When copying text in a so-called "plain text" message, that is
-      // one where the body carries style "white-space:pre-wrap", the text
-      // should be copied as pre-formatted plain text.
-      //
-      // Therefore the following code checks for "pre-wrap" on the body.
-      // This is a terrible hack.
-      //
-      // The proper fix would be this:
-      // For case 1:
-      // Communicate the fact that HTML is required to EncodeToString(),
-      // bug 1141786.
-      // For case 2:
-      // Wait for Firefox to get fixed to detect pre-formatting correctly,
-      // bug 1174452.
-      nsAutoString styleVal;
-      if (selContent->IsElement() &&
-          selContent->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::style,
-                                           styleVal) &&
-          styleVal.Find(NS_LITERAL_STRING("pre-wrap")) != kNotFound) {
-        mIsTextWidget = true;
-        break;
-      }
-    }
-#endif
   }
 
   // normalize selection if we are not in a widget
   if (mIsTextWidget) {
     mSelection = aSelection;
     mMimeType.AssignLiteral("text/plain");
     return NS_OK;
   }
--- a/dom/base/nsIDroppedLinkHandler.idl
+++ b/dom/base/nsIDroppedLinkHandler.idl
@@ -60,51 +60,46 @@ interface nsIDroppedLinkHandler : nsISup
    * otherwise.
    */
   AString dropLink(in DragEvent aEvent, out AString aName,
                    [optional] in boolean aDisallowInherit);
 
   /**
    * Given a drop event aEvent, determines links being dragged and returns
    * them. If links are returned the caller can, for instance, load them. If
-   * the count of links is 0, there is no valid link to be dropped.
+   * the returned array is empty, there is no valid link to be dropped.
    *
    * A NS_ERROR_DOM_SECURITY_ERR error will be thrown and the event cancelled if
    * the receiving target should not load the uri for security reasons. This
    * will occur if any of the following conditions are true:
    *  - the source of the drag initiated a link for dragging that
    *    it itself cannot access. This prevents a source document from tricking
    *    the user into a dragging a chrome url, for example.
    *  - aDisallowInherit is true, and the URI being dropped would inherit the
    *    current document's security context (URI_INHERITS_SECURITY_CONTEXT).
    */
-  void dropLinks(in DragEvent aEvent,
-                 [optional] in boolean aDisallowInherit,
-                 [optional] out unsigned long aCount,
-                 [retval, array, size_is(aCount)] out nsIDroppedLinkItem aLinks);
+  Array<nsIDroppedLinkItem> dropLinks(in DragEvent aEvent,
+                                      [optional] in boolean aDisallowInherit);
 
   /**
    * Given a drop event aEvent, validate the extra URIs for the event,
    * this is used when the caller extracts yet another URIs from the dropped
    * text, like home button that splits the text with "|".
    */
   void validateURIsForDrop(in DragEvent aEvent,
-                           in unsigned long aURIsCount,
-                           [array, size_is(aURIsCount)] in wstring aURIs,
+                           in Array<AString> aURIs,
                            [optional] in boolean aDisallowInherit);
 
   /**
    * Given a dataTransfer, allows caller to determine and verify links being
    * dragged. Since drag/drop performs a roundtrip of parent, child, parent,
    * it allows the parent to verify that the child did not modify links
    * being dropped.
    */
-  void queryLinks(in DataTransfer aDataTransfer,
-                  [optional] out unsigned long aCount,
-                  [retval, array, size_is(aCount)] out nsIDroppedLinkItem aLinks);
+  Array<nsIDroppedLinkItem> queryLinks(in DataTransfer aDataTransfer);
 
   /**
    * Given a drop event aEvent, determines the triggering principal for the
    * event and returns it.
    */
   nsIPrincipal getTriggeringPrincipal(in DragEvent aEvent);
 
   /**
--- a/dom/credentialmanagement/tests/mochitest/test_credman_iframes.html
+++ b/dom/credentialmanagement/tests/mochitest/test_credman_iframes.html
@@ -40,17 +40,18 @@ function handleEventMessage(event) {
   } else {
     ok(false, "Unexpected message in the test harness: " + event.data);
   }
 }
 
 window.addEventListener("message", handleEventMessage);
 SpecialPowers.pushPrefEnv({"set": [["security.webauth.webauthn", true],
                                    ["security.webauth.webauthn_enable_softtoken", true],
-                                   ["security.webauth.webauthn_enable_usbtoken", false]]},
+                                   ["security.webauth.webauthn_enable_usbtoken", false],
+                                   ["security.webauth.webauthn_enable_android_fido2", false]]},
 function() {
   document.getElementById("frame_top").src = "https://example.com/tests/dom/credentialmanagement/tests/mochitest/frame_credman_iframes.html";
 
   document.getElementById("frame_bottom").src = "https://test1.example.com/tests/dom/credentialmanagement/tests/mochitest/frame_credman_iframes.html";
 });
 </script>
 </body>
 </html>
--- a/dom/interfaces/base/nsIBrowser.idl
+++ b/dom/interfaces/base/nsIBrowser.idl
@@ -20,24 +20,22 @@ interface nsIBrowser : nsISupports
    * they are loaded in the same process as the content.
    */
   readonly attribute FrameLoader sameProcessAsFrameLoader;
 
   /*
    * Called by the child to inform the parent that links are dropped into
    * content area.
    *
-   * @param linksCount length of links
    * @param links a flat array of url, name, and type for each link
    * @param triggeringPrincipal a principal that initiated loading
    *                            of the dropped links
    */
-  void dropLinks(in unsigned long linksCount,
-                 [array, size_is(linksCount)] in wstring links,
-                 in nsIPrincipal aTriggeringPrincipal);
+  void dropLinks(in Array<AString> links,
+                 in nsIPrincipal triggeringPrincipal);
 
   /**
    * Swapping of frameloaders are usually initiated from a frameloader owner
    * or other components operating on frameloader owners. This is done by calling
    * swapFrameLoaders at MozFrameLoaderOwner webidl interface.
    *
    * This function aimed to provide the other way around -
    * if the swapping is initiated from frameloader itself or other platform level
@@ -68,22 +66,18 @@ interface nsIBrowser : nsISupports
    */
   readonly attribute nsIWebProgress remoteWebProgressManager;
 
   /**
    * Called by the child to inform the parent that a command update has occurred
    * and the supplied set of commands are now enabled and disabled.
    *
    * @param action command updater action
-   * @param enabledLength length of enabledCommands array
    * @param enabledCommands commands to enable
-   * @param disabledLength length of disabledCommands array
    * @param disabledCommand commands to disable
    */
   void enableDisableCommandsRemoteOnly(in AString action,
-                                       in unsigned long enabledLength,
-                                       [array, size_is(enabledLength)] in string enabledCommands,
-                                       in unsigned long disabledLength,
-                                       [array, size_is(disabledLength)] in string disabledCommands);
+                                       in Array<ACString> enabledCommands,
+                                       in Array<ACString> disabledCommands);
 
   readonly attribute nsIPrincipal contentPrincipal;
   readonly attribute nsIContentSecurityPolicy csp;
 };
--- a/dom/interfaces/base/nsIBrowserChild.idl
+++ b/dom/interfaces/base/nsIBrowserChild.idl
@@ -24,18 +24,17 @@ interface nsIBrowserChild : nsISupports
 
   [noscript, notxpcom] void enableDisableCommands(in AString action,
                                                   in CommandsArrayRef enabledCommands,
                                                   in CommandsArrayRef disabledCommands);
 
   [noscript] void remoteSizeShellTo(in int32_t width, in int32_t height,
                                     in int32_t shellItemWidth, in int32_t shellItemHeight);
 
-  [noscript] void remoteDropLinks(in unsigned long linksCount,
-                                  [array, size_is(linksCount)] in nsIDroppedLinkItem links);
+  void remoteDropLinks(in Array<nsIDroppedLinkItem> links);
 
   readonly attribute uint64_t tabId;
 
   /*
    * Indicates whether or not there are other tabs in this tab's window.
    */
   attribute boolean hasSiblings;
 
--- a/dom/ipc/BrowserChild.cpp
+++ b/dom/ipc/BrowserChild.cpp
@@ -730,35 +730,35 @@ BrowserChild::RemoteSizeShellTo(int32_t 
 
   bool sent = SendSizeShellTo(flags, aWidth, aHeight, aShellItemWidth,
                               aShellItemHeight);
 
   return sent ? NS_OK : NS_ERROR_FAILURE;
 }
 
 NS_IMETHODIMP
-BrowserChild::RemoteDropLinks(uint32_t aLinksCount,
-                              nsIDroppedLinkItem** aLinks) {
+BrowserChild::RemoteDropLinks(
+    const nsTArray<RefPtr<nsIDroppedLinkItem>>& aLinks) {
   nsTArray<nsString> linksArray;
   nsresult rv = NS_OK;
-  for (uint32_t i = 0; i < aLinksCount; i++) {
+  for (nsIDroppedLinkItem* link : aLinks) {
     nsString tmp;
-    rv = aLinks[i]->GetUrl(tmp);
+    rv = link->GetUrl(tmp);
     if (NS_FAILED(rv)) {
       return rv;
     }
     linksArray.AppendElement(tmp);
 
-    rv = aLinks[i]->GetName(tmp);
+    rv = link->GetName(tmp);
     if (NS_FAILED(rv)) {
       return rv;
     }
     linksArray.AppendElement(tmp);
 
-    rv = aLinks[i]->GetType(tmp);
+    rv = link->GetType(tmp);
     if (NS_FAILED(rv)) {
       return rv;
     }
     linksArray.AppendElement(tmp);
   }
   bool sent = SendDropLinks(linksArray);
 
   return sent ? NS_OK : NS_ERROR_FAILURE;
@@ -1811,17 +1811,18 @@ mozilla::ipc::IPCResult BrowserChild::Re
     nsCOMPtr<Document> document = GetTopLevelDocument();
     if (gfxPrefs::TouchActionEnabled()) {
       APZCCallbackHelper::SendSetAllowedTouchBehaviorNotification(
           mPuppetWidget, document, localEvent, aInputBlockId,
           mSetAllowedTouchBehaviorCallback);
     }
     UniquePtr<DisplayportSetListener> postLayerization =
         APZCCallbackHelper::SendSetTargetAPZCNotification(
-            mPuppetWidget, document, localEvent, aGuid.mLayersId, aInputBlockId);
+            mPuppetWidget, document, localEvent, aGuid.mLayersId,
+            aInputBlockId);
     if (postLayerization && postLayerization->Register()) {
       Unused << postLayerization.release();
     }
   }
 
   // Dispatch event to content (potentially a long-running operation)
   nsEventStatus status = DispatchWidgetEventViaAPZ(localEvent);
 
--- a/dom/ipc/BrowserParent.cpp
+++ b/dom/ipc/BrowserParent.cpp
@@ -804,34 +804,31 @@ mozilla::ipc::IPCResult BrowserParent::R
     // Verify that links have not been modified by the child. If links have
     // not been modified then it's safe to load those links using the
     // SystemPrincipal. If they have been modified by web content, then
     // we use a NullPrincipal which still allows to load web links.
     bool loadUsingSystemPrincipal = true;
     if (aLinks.Length() != mVerifyDropLinks.Length()) {
       loadUsingSystemPrincipal = false;
     }
-    UniquePtr<const char16_t*[]> links;
-    links = MakeUnique<const char16_t*[]>(aLinks.Length());
     for (uint32_t i = 0; i < aLinks.Length(); i++) {
       if (loadUsingSystemPrincipal) {
         if (!aLinks[i].Equals(mVerifyDropLinks[i])) {
           loadUsingSystemPrincipal = false;
         }
       }
-      links[i] = aLinks[i].get();
     }
     mVerifyDropLinks.Clear();
     nsCOMPtr<nsIPrincipal> triggeringPrincipal;
     if (loadUsingSystemPrincipal) {
       triggeringPrincipal = nsContentUtils::GetSystemPrincipal();
     } else {
       triggeringPrincipal = NullPrincipal::CreateWithoutOriginAttributes();
     }
-    browser->DropLinks(aLinks.Length(), links.get(), triggeringPrincipal);
+    browser->DropLinks(aLinks, triggeringPrincipal);
   }
   return IPC_OK();
 }
 
 mozilla::ipc::IPCResult BrowserParent::RecvEvent(const RemoteDOMEvent& aEvent) {
   RefPtr<Event> event = aEvent.mEvent;
   NS_ENSURE_TRUE(event, IPC_OK());
 
@@ -1389,52 +1386,46 @@ bool BrowserParent::QueryDropLinksForVer
     NS_WARNING("No dropHandler to query links for verification");
     return false;
   }
 
   // No more than one drop event can happen simultaneously; reset the link
   // verification array and store all links that are being dragged.
   mVerifyDropLinks.Clear();
 
-  uint32_t linksCount = 0;
-  nsIDroppedLinkItem** droppedLinkedItems = nullptr;
-  dropHandler->QueryLinks(initialDataTransfer, &linksCount,
-                          &droppedLinkedItems);
+  nsTArray<RefPtr<nsIDroppedLinkItem>> droppedLinkItems;
+  dropHandler->QueryLinks(initialDataTransfer, droppedLinkItems);
 
   // Since the entire event is cancelled if one of the links is invalid,
   // we can store all links on the parent side without any prior
   // validation checks.
   nsresult rv = NS_OK;
-  for (uint32_t i = 0; i < linksCount; i++) {
+  for (nsIDroppedLinkItem* item : droppedLinkItems) {
     nsString tmp;
-    rv = droppedLinkedItems[i]->GetUrl(tmp);
+    rv = item->GetUrl(tmp);
     if (NS_FAILED(rv)) {
       NS_WARNING("Failed to query url for verification");
       break;
     }
     mVerifyDropLinks.AppendElement(tmp);
 
-    rv = droppedLinkedItems[i]->GetName(tmp);
+    rv = item->GetName(tmp);
     if (NS_FAILED(rv)) {
       NS_WARNING("Failed to query name for verification");
       break;
     }
     mVerifyDropLinks.AppendElement(tmp);
 
-    rv = droppedLinkedItems[i]->GetType(tmp);
+    rv = item->GetType(tmp);
     if (NS_FAILED(rv)) {
       NS_WARNING("Failed to query type for verification");
       break;
     }
     mVerifyDropLinks.AppendElement(tmp);
   }
-  for (uint32_t i = 0; i < linksCount; i++) {
-    NS_IF_RELEASE(droppedLinkedItems[i]);
-  }
-  free(droppedLinkedItems);
   if (NS_FAILED(rv)) {
     mVerifyDropLinks.Clear();
     return false;
   }
   return true;
 }
 
 void BrowserParent::SendRealDragEvent(WidgetDragEvent& aEvent,
@@ -2158,35 +2149,18 @@ mozilla::ipc::IPCResult BrowserParent::R
     nsTArray<nsCString>&& aDisabledCommands) {
   nsCOMPtr<nsIBrowser> browser =
       mFrameElement ? mFrameElement->AsBrowser() : nullptr;
   bool isRemoteBrowser = false;
   if (browser) {
     browser->GetIsRemoteBrowser(&isRemoteBrowser);
   }
   if (isRemoteBrowser) {
-    UniquePtr<const char*[]> enabledCommands, disabledCommands;
-
-    if (aEnabledCommands.Length()) {
-      enabledCommands = MakeUnique<const char*[]>(aEnabledCommands.Length());
-      for (uint32_t c = 0; c < aEnabledCommands.Length(); c++) {
-        enabledCommands[c] = aEnabledCommands[c].get();
-      }
-    }
-
-    if (aDisabledCommands.Length()) {
-      disabledCommands = MakeUnique<const char*[]>(aDisabledCommands.Length());
-      for (uint32_t c = 0; c < aDisabledCommands.Length(); c++) {
-        disabledCommands[c] = aDisabledCommands[c].get();
-      }
-    }
-
-    browser->EnableDisableCommandsRemoteOnly(
-        aAction, aEnabledCommands.Length(), enabledCommands.get(),
-        aDisabledCommands.Length(), disabledCommands.get());
+    browser->EnableDisableCommandsRemoteOnly(aAction, aEnabledCommands,
+                                             aDisabledCommands);
   }
 
   return IPC_OK();
 }
 
 LayoutDeviceIntPoint BrowserParent::TransformPoint(
     const LayoutDeviceIntPoint& aPoint,
     const LayoutDeviceToLayoutDeviceMatrix4x4& aMatrix) {
--- a/dom/ipc/moz.build
+++ b/dom/ipc/moz.build
@@ -195,17 +195,21 @@ if CONFIG['OS_ARCH'] != 'WINNT':
 
 DEFINES['BIN_SUFFIX'] = '"%s"' % CONFIG['BIN_SUFFIX']
 
 if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'android':
     DEFINES['MOZ_ENABLE_FREETYPE'] = True
 
 JAR_MANIFESTS += ['jar.mn']
 
-BROWSER_CHROME_MANIFESTS += ['tests/browser.ini']
+BROWSER_CHROME_MANIFESTS += [
+    'tests/browser.ini',
+    'tests/JSWindowActor/browser.ini',
+]
+
 MOCHITEST_CHROME_MANIFESTS += ['tests/chrome.ini']
 MOCHITEST_MANIFESTS += ['tests/mochitest.ini']
 XPCSHELL_TESTS_MANIFESTS += ['tests/xpcshell.ini']
 
 CXXFLAGS += CONFIG['TK_CFLAGS']
 
 if CONFIG['CC_TYPE'] in ('clang', 'gcc'):
     CXXFLAGS += ['-Wno-error=shadow']
new file mode 100644
--- /dev/null
+++ b/dom/ipc/tests/JSWindowActor/browser.ini
@@ -0,0 +1,12 @@
+[DEFAULT]
+support-files =
+  head.js
+
+[browser_destroy_callbacks.js]
+[browser_event_listener.js]
+[browser_getActor.js]
+[browser_getActor_filter.js]
+[browser_observer_notification.js]
+[browser_registerWindowActor.js]
+[browser_sendAsyncMessage.js]
+[browser_sendQuery.js]
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/dom/ipc/tests/JSWindowActor/browser_destroy_callbacks.js
@@ -0,0 +1,74 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+declTest("destroy actor by iframe remove", {
+  allFrames: true,
+
+  async test(browser) {
+    await ContentTask.spawn(browser, {}, async function() {
+      // Create and append an iframe into the window's document.
+      let frame = content.document.createElement("iframe");
+      frame.id = "frame";
+      content.document.body.appendChild(frame);
+      await ContentTaskUtils.waitForEvent(frame, "load");
+      is(content.window.frames.length, 1, "There should be an iframe.");
+      let child = frame.contentWindow.window.getWindowGlobalChild();
+      let actorChild = child.getActor("Test");
+      ok(actorChild, "JSWindowActorChild should have value.");
+
+      let willDestroyPromise = new Promise(resolve => {
+        const TOPIC = "test-js-window-actor-willdestroy";
+        Services.obs.addObserver(function obs(subject, topic, data) {
+          ok(data, "willDestroyCallback data should be true.");
+
+          Services.obs.removeObserver(obs, TOPIC);
+          resolve();
+        }, TOPIC);
+      });
+
+      let didDestroyPromise = new Promise(resolve => {
+        const TOPIC = "test-js-window-actor-diddestroy";
+        Services.obs.addObserver(function obs(subject, topic, data) {
+          ok(data, "didDestroyCallback data should be true.");
+
+          Services.obs.removeObserver(obs, TOPIC);
+          resolve();
+        }, TOPIC);
+      });
+
+      info("Remove frame");
+      content.document.getElementById("frame").remove();
+      await Promise.all([willDestroyPromise, didDestroyPromise]);
+
+      Assert.throws(() => child.getActor("Test"),
+        /InvalidStateError/, "Should throw if frame destroy.");
+    });
+  },
+});
+
+declTest("destroy actor by page navigates", {
+  allFrames: true,
+
+  async test(browser) {
+    info("creating an in-process frame");
+    await ContentTask.spawn(browser, URL, async function(url) {
+      let frame = content.document.createElement("iframe");
+      frame.src = url;
+      content.document.body.appendChild(frame);
+    });
+
+    info("navigating page");
+    await ContentTask.spawn(browser, TEST_URL, async function(url) {
+      let frame = content.document.querySelector("iframe");
+      frame.contentWindow.location = url;
+      let child = frame.contentWindow.window.getWindowGlobalChild();
+      let actorChild = child.getActor("Test");
+      ok(actorChild, "JSWindowActorChild should have value.");
+      await ContentTaskUtils.waitForEvent(frame, "load");
+
+      Assert.throws(() => child.getActor("Test"),
+              /InvalidStateError/, "Should throw if frame destroy.");
+    });
+  },
+});
new file mode 100644
--- /dev/null
+++ b/dom/ipc/tests/JSWindowActor/browser_event_listener.js
@@ -0,0 +1,39 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+declTest("test event triggering actor creation", {
+  async test(browser) {
+    // Add a select element to the DOM of the loaded document.
+    await ContentTask.spawn(browser, {}, async function() {
+      content.document.body.innerHTML += `
+        <select id="testSelect">
+          <option>A</option>
+          <option>B</option>
+        </select>`;
+    });
+
+    // Wait for the observer notification.
+    let observePromise = new Promise(resolve => {
+      const TOPIC = "test-js-window-actor-parent-event";
+      Services.obs.addObserver(function obs(subject, topic, data) {
+        is(topic, TOPIC, "topic matches");
+
+        Services.obs.removeObserver(obs, TOPIC);
+        resolve({subject, data});
+      }, TOPIC);
+    });
+
+    // Click on the select to show the dropdown.
+    await BrowserTestUtils.synthesizeMouseAtCenter("#testSelect", {}, browser);
+
+    // Wait for the observer notification to fire, and inspect the results.
+    let {subject, data} = await observePromise;
+    is(data, "mozshowdropdown");
+
+    let parent = browser.browsingContext.currentWindowGlobal;
+    let actorParent = parent.getActor("Test");
+    ok(actorParent, "JSWindowActorParent should have value.");
+    is(subject.wrappedJSObject, actorParent, "Should have been recieved on the right actor");
+  },
+});
new file mode 100644
--- /dev/null
+++ b/dom/ipc/tests/JSWindowActor/browser_getActor.js
@@ -0,0 +1,22 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+declTest("getActor on both sides", {
+  async test(browser) {
+    let parent = browser.browsingContext.currentWindowGlobal;
+    ok(parent, "WindowGlobalParent should have value.");
+    let actorParent = parent.getActor("Test");
+    is(actorParent.show(), "TestParent", "actor show should have vaule.");
+    is(actorParent.manager, parent, "manager should match WindowGlobalParent.");
+
+    await ContentTask.spawn(browser, {}, async function() {
+      let child = content.window.getWindowGlobalChild();
+      ok(child, "WindowGlobalChild should have value.");
+      is(child.isInProcess, false, "Actor should be loaded in the content process.");
+      let actorChild = child.getActor("Test");
+      is(actorChild.show(), "TestChild", "actor show should have vaule.");
+      is(actorChild.manager, child, "manager should match WindowGlobalChild.");
+    });
+  },
+});
new file mode 100644
--- /dev/null
+++ b/dom/ipc/tests/JSWindowActor/browser_getActor_filter.js
@@ -0,0 +1,163 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+declTest("getActor with mismatch", {
+  matches: ["*://*/*"],
+
+  async test(browser) {
+    let parent = browser.browsingContext.currentWindowGlobal;
+    ok(parent, "WindowGlobalParent should have value.");
+    Assert.throws(() => parent.getActor("Test"),
+          /NS_ERROR_NOT_AVAILABLE/, "Should throw if it doesn't match.");
+
+    await ContentTask.spawn(browser, {}, async function() {
+      let child = content.window.getWindowGlobalChild();
+      ok(child, "WindowGlobalChild should have value.");
+
+      Assert.throws(() => child.getActor("Test"),
+        /NS_ERROR_NOT_AVAILABLE/, "Should throw if it doesn't match.");
+    });
+  },
+});
+
+declTest("getActor with matches", {
+  matches: ["*://*/*"],
+  url: TEST_URL,
+
+  async test(browser) {
+    let parent = browser.browsingContext.currentWindowGlobal;
+    ok(parent.getActor("Test"), "JSWindowActorParent should have value.");
+
+    await ContentTask.spawn(browser, {}, async function() {
+      let child = content.window.getWindowGlobalChild();
+      ok(child, "WindowGlobalChild should have value.");
+      ok(child.getActor("Test"), "JSWindowActorChild should have value.");
+    });
+  },
+});
+
+declTest("getActor with iframe matches", {
+  allFrames: true,
+  matches: ["*://*/*"],
+
+  async test(browser) {
+    await ContentTask.spawn(browser, TEST_URL, async function(url) {
+      // Create and append an iframe into the window's document.
+      let frame = content.document.createElement("iframe");
+      frame.src = url;
+      content.document.body.appendChild(frame);
+      await ContentTaskUtils.waitForEvent(frame, "load");
+
+      is(content.window.frames.length, 1, "There should be an iframe.");
+      let child = frame.contentWindow.window.getWindowGlobalChild();
+      ok(child.getActor("Test"), "JSWindowActorChild should have value.");
+    });
+  },
+});
+
+declTest("getActor with iframe mismatch", {
+  allFrames: true,
+  matches: ["about:home"],
+
+  async test(browser) {
+    await ContentTask.spawn(browser, TEST_URL, async function(url) {
+      // Create and append an iframe into the window's document.
+      let frame = content.document.createElement("iframe");
+      frame.src = url;
+      content.document.body.appendChild(frame);
+      await ContentTaskUtils.waitForEvent(frame, "load");
+
+      is(content.window.frames.length, 1, "There should be an iframe.");
+      let child = frame.contentWindow.window.getWindowGlobalChild();
+      Assert.throws(() => child.getActor("Test"),
+        /NS_ERROR_NOT_AVAILABLE/, "Should throw if it doesn't match.");
+    });
+  },
+});
+
+declTest("getActor with remoteType match", {
+  remoteTypes: ["web"],
+
+  async test(browser) {
+    let parent = browser.browsingContext.currentWindowGlobal;
+    ok(parent.getActor("Test"), "JSWindowActorParent should have value.");
+
+    await ContentTask.spawn(browser, {}, async function() {
+      let child = content.window.getWindowGlobalChild();
+      ok(child, "WindowGlobalChild should have value.");
+      ok(child.getActor("Test"), "JSWindowActorChild should have value.");
+    });
+  },
+});
+
+declTest("getActor with remoteType mismatch", {
+  remoteTypes: ["privileged"],
+  url: TEST_URL,
+
+  async test(browser) {
+    let parent = browser.browsingContext.currentWindowGlobal;
+    Assert.throws(() => parent.getActor("Test"),
+          /NS_ERROR_NOT_AVAILABLE/, "Should throw if its remoteTypes don't match.");
+
+    await ContentTask.spawn(browser, {}, async function() {
+      let child = content.window.getWindowGlobalChild();
+      ok(child, "WindowGlobalChild should have value.");
+      Assert.throws(() => child.getActor("Test"),
+          /NS_ERROR_NOT_AVAILABLE/, "Should throw if its remoteTypes don't match.");
+    });
+  },
+});
+
+declTest("getActor without allFrames", {
+  allFrames: false,
+
+  async test(browser) {
+    await ContentTask.spawn(browser, {}, async function() {
+      // Create and append an iframe into the window's document.
+      let frame = content.document.createElement("iframe");
+      content.document.body.appendChild(frame);
+      is(content.window.frames.length, 1, "There should be an iframe.");
+      let child = frame.contentWindow.window.getWindowGlobalChild();
+      Assert.throws(() => child.getActor("Test"),
+          /NS_ERROR_NOT_AVAILABLE/, "Should throw if allFrames is false.");
+    });
+  },
+});
+
+declTest("getActor with allFrames", {
+  allFrames: true,
+
+  async test(browser) {
+    await ContentTask.spawn(browser, {}, async function() {
+      // Create and append an iframe into the window's document.
+      let frame = content.document.createElement("iframe");
+      content.document.body.appendChild(frame);
+      is(content.window.frames.length, 1, "There should be an iframe.");
+      let child = frame.contentWindow.window.getWindowGlobalChild();
+      let actorChild = child.getActor("Test");
+      ok(actorChild, "JSWindowActorChild should have value.");
+    });
+  },
+});
+
+declTest("getActor without includeChrome", {
+  includeChrome: false,
+
+  async test(_browser, win) {
+    let parent = win.docShell.browsingContext.currentWindowGlobal;
+    SimpleTest.doesThrow(() =>
+      parent.getActor("Test"),
+      "Should throw if includeChrome is false.");
+  },
+});
+
+declTest("getActor with includeChrome", {
+  includeChrome: true,
+
+  async test(_browser, win) {
+    let parent = win.docShell.browsingContext.currentWindowGlobal;
+    let actorParent = parent.getActor("Test");
+    ok(actorParent, "JSWindowActorParent should have value.");
+  },
+});
new file mode 100644
--- /dev/null
+++ b/dom/ipc/tests/JSWindowActor/browser_observer_notification.js
@@ -0,0 +1,52 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+declTest("test observer triggering actor creation", {
+  async test(browser) {
+    await ContentTask.spawn(browser, {}, async function() {
+      const TOPIC = "test-js-window-actor-child-observer";
+      Services.obs.notifyObservers(content.window, TOPIC, "dataString");
+
+      let child = content.window.getWindowGlobalChild();
+      let actorChild = child.getActor("Test");
+      ok(actorChild, "JSWindowActorChild should have value.");
+      let {subject, topic, data} = actorChild.lastObserved;
+
+      is(subject.getWindowGlobalChild().getActor("Test"), actorChild, "Should have been recieved on the right actor");
+      is(topic, TOPIC, "Topic matches");
+      is(data, "dataString", "Data matches");
+    });
+  },
+});
+
+declTest("test observers with null data", {
+  async test(browser) {
+    await ContentTask.spawn(browser, {}, async function() {
+      const TOPIC = "test-js-window-actor-child-observer";
+      Services.obs.notifyObservers(content.window, TOPIC);
+
+      let child = content.window.getWindowGlobalChild();
+      let actorChild = child.getActor("Test");
+      ok(actorChild, "JSWindowActorChild should have value.");
+      let {subject, topic, data} = actorChild.lastObserved;
+
+      is(subject.getWindowGlobalChild().getActor("Test"), actorChild, "Should have been recieved on the right actor");
+      is(topic, TOPIC, "Topic matches");
+      is(data, null, "Data matches");
+    });
+  },
+});
+
+declTest("observers don't notify with wrong window", {
+  async test(browser) {
+    await ContentTask.spawn(browser, {}, async function() {
+      const TOPIC = "test-js-window-actor-child-observer";
+      Services.obs.notifyObservers(null, TOPIC);
+      let child = content.window.getWindowGlobalChild();
+      let actorChild = child.getActor("Test");
+      ok(actorChild, "JSWindowActorChild should have value.");
+      is(actorChild.lastObserved, undefined, "Should not receive wrong window's observer notification!");
+    });
+  },
+});
new file mode 100644
--- /dev/null
+++ b/dom/ipc/tests/JSWindowActor/browser_registerWindowActor.js
@@ -0,0 +1,11 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+declTest("double register", {
+  async test() {
+    SimpleTest.doesThrow(() =>
+      ChromeUtils.registerWindowActor("Test", windowActorOptions),
+      "Should throw if register has duplicate name.");
+  },
+});
new file mode 100644
--- /dev/null
+++ b/dom/ipc/tests/JSWindowActor/browser_sendAsyncMessage.js
@@ -0,0 +1,51 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+declTest("asyncMessage testing", {
+  async test(browser) {
+    let parent = browser.browsingContext.currentWindowGlobal;
+    let actorParent = parent.getActor("Test");
+    ok(actorParent, "JSWindowActorParent should have value.");
+
+    await ContentTask.spawn(browser, {}, async function() {
+      let child = content.window.getWindowGlobalChild();
+      let actorChild = child.getActor("Test");
+      ok(actorChild, "JSWindowActorChild should have value.");
+
+      let promise = new Promise(resolve => {
+        actorChild.sendAsyncMessage("init", {});
+        actorChild.done = (data) => resolve(data);
+      }).then(data => {
+        ok(data.initial, "Initial should be true.");
+        ok(data.toParent, "ToParent should be true.");
+        ok(data.toChild, "ToChild should be true.");
+      });
+
+      await promise;
+    });
+  },
+});
+
+declTest("asyncMessage without both sides", {
+  async test(browser) {
+    // If we don't create a parent actor, make sure the parent actor
+    // gets created by having sent the message.
+    await ContentTask.spawn(browser, {}, async function() {
+      let child = content.window.getWindowGlobalChild();
+      let actorChild = child.getActor("Test");
+      ok(actorChild, "JSWindowActorChild should have value.");
+
+      let promise = new Promise(resolve => {
+        actorChild.sendAsyncMessage("init", {});
+        actorChild.done = (data) => resolve(data);
+      }).then(data => {
+        ok(data.initial, "Initial should be true.");
+        ok(data.toParent, "ToParent should be true.");
+        ok(data.toChild, "ToChild should be true.");
+      });
+
+      await promise;
+    });
+  },
+});
new file mode 100644
--- /dev/null
+++ b/dom/ipc/tests/JSWindowActor/browser_sendQuery.js
@@ -0,0 +1,14 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+declTest("sendQuery testing", {
+  async test(browser) {
+    let parent = browser.browsingContext.currentWindowGlobal;
+    let actorParent = parent.getActor("Test");
+    ok(actorParent, "JSWindowActorParent should have value.");
+
+    let {result} = await actorParent.sendQuery("asyncAdd", {a: 10, b: 20});
+    is(result, 30);
+  },
+});
new file mode 100644
--- /dev/null
+++ b/dom/ipc/tests/JSWindowActor/head.js
@@ -0,0 +1,75 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Provide infrastructure for JSWindowActor tests.
+ */
+
+const URL = "about:blank";
+const TEST_URL = "http://test2.example.org/";
+let windowActorOptions = {
+  parent: {
+    moduleURI: "resource://testing-common/TestParent.jsm",
+  },
+  child: {
+    moduleURI: "resource://testing-common/TestChild.jsm",
+
+    events: {
+      "mozshowdropdown": {},
+    },
+
+    observers: [
+      "test-js-window-actor-child-observer",
+    ],
+  },
+};
+
+function declTest(name, cfg) {
+  let {
+    url = "about:blank",
+    allFrames = false,
+    includeChrome = false,
+    matches,
+    remoteTypes,
+    fission,
+    test,
+  } = cfg;
+
+  // Build the actor options object which will be used to register & unregister our window actor.
+  let actorOptions = {
+    parent: Object.assign({}, windowActorOptions.parent),
+    child: Object.assign({}, windowActorOptions.child),
+  };
+  actorOptions.allFrames = allFrames;
+  actorOptions.includeChrome = includeChrome;
+  if (matches !== undefined) {
+    actorOptions.matches = matches;
+  }
+  if (remoteTypes !== undefined) {
+    actorOptions.remoteTypes = remoteTypes;
+  }
+
+  // Add a new task for the actor test declared here.
+  add_task(async function() {
+    info("Entering test: " + name);
+
+    // Create a fresh window with the correct settings, and register our actor.
+    let win = await BrowserTestUtils.openNewBrowserWindow({remote: true, fission});
+    ChromeUtils.registerWindowActor("Test", actorOptions);
+
+    // Wait for the provided URL to load in our browser
+    let browser = win.gBrowser.selectedBrowser;
+    BrowserTestUtils.loadURI(browser, url);
+    await BrowserTestUtils.browserLoaded(browser);
+
+    // Run the provided test
+    info("browser ready");
+    await Promise.resolve(test(browser, win));
+
+    // Clean up after we're done.
+    ChromeUtils.unregisterWindowActor("Test");
+    await BrowserTestUtils.closeWindow(win);
+
+    info("Exiting test: " + name);
+  });
+}
--- a/dom/ipc/tests/browser.ini
+++ b/dom/ipc/tests/browser.ini
@@ -4,11 +4,10 @@ support-files =
   file_domainPolicy_base.html
   file_cancel_content_js.html
 
 [browser_domainPolicy.js]
 [browser_memory_distribution_telemetry.js]
 skip-if = !e10 # This is an e10s only probe.
 [browser_remote_navigation_delay_telemetry.js]
 skip-if = !e10s # This is an e10s only probe.
-[browser_JSWindowActor.js]
 [browser_cancel_content_js.js]
 skip-if = !e10s # This is an e10s only probe.
\ No newline at end of file
deleted file mode 100644
--- a/dom/ipc/tests/browser_JSWindowActor.js
+++ /dev/null
@@ -1,479 +0,0 @@
-/* Any copyright is dedicated to the Public Domain.
-   http://creativecommons.org/publicdomain/zero/1.0/ */
-"use strict";
-
-// This test opens and closes a large number of windows, which can be slow
-// especially on debug builds. This decreases the likelihood of the test timing
-// out.
-requestLongerTimeout(4);
-
-const URL = "about:blank";
-const TEST_URL = "http://test2.example.org/";
-let windowActorOptions = {
-  parent: {
-    moduleURI: "resource://testing-common/TestParent.jsm",
-  },
-  child: {
-    moduleURI: "resource://testing-common/TestChild.jsm",
-
-    events: {
-      "mozshowdropdown": {},
-    },
-
-    observers: [
-      "test-js-window-actor-child-observer",
-    ],
-  },
-};
-
-function declTest(name, cfg) {
-  let {
-    url = "about:blank",
-    allFrames = false,
-    includeChrome = false,
-    matches,
-    remoteTypes,
-    fission,
-    test,
-  } = cfg;
-
-  // Build the actor options object which will be used to register & unregister our window actor.
-  let actorOptions = {
-    parent: Object.assign({}, windowActorOptions.parent),
-    child: Object.assign({}, windowActorOptions.child),
-  };
-  actorOptions.allFrames = allFrames;
-  actorOptions.includeChrome = includeChrome;
-  if (matches !== undefined) {
-    actorOptions.matches = matches;
-  }
-  if (remoteTypes !== undefined) {
-    actorOptions.remoteTypes = remoteTypes;
-  }
-
-  // Add a new task for the actor test declared here.
-  add_task(async function() {
-    info("Entering test: " + name);
-
-    // Create a fresh window with the correct settings, and register our actor.
-    let win = await BrowserTestUtils.openNewBrowserWindow({remote: true, fission});
-    ChromeUtils.registerWindowActor("Test", actorOptions);
-
-    // Wait for the provided URL to load in our browser
-    let browser = win.gBrowser.selectedBrowser;
-    BrowserTestUtils.loadURI(browser, url);
-    await BrowserTestUtils.browserLoaded(browser);
-
-    // Run the provided test
-    info("browser ready");
-    await Promise.resolve(test(browser, win));
-
-    // Clean up after we're done.
-    ChromeUtils.unregisterWindowActor("Test");
-    await BrowserTestUtils.closeWindow(win);
-
-    info("Exiting test: " + name);
-  });
-}
-
-declTest("double register", {
-  async test() {
-    SimpleTest.doesThrow(() =>
-      ChromeUtils.registerWindowActor("Test", windowActorOptions),
-      "Should throw if register has duplicate name.");
-  },
-});
-
-declTest("getActor on both sides", {
-  async test(browser) {
-    let parent = browser.browsingContext.currentWindowGlobal;
-    ok(parent, "WindowGlobalParent should have value.");
-    let actorParent = parent.getActor("Test");
-    is(actorParent.show(), "TestParent", "actor show should have vaule.");
-    is(actorParent.manager, parent, "manager should match WindowGlobalParent.");
-
-    await ContentTask.spawn(browser, {}, async function() {
-      let child = content.window.getWindowGlobalChild();
-      ok(child, "WindowGlobalChild should have value.");
-      is(child.isInProcess, false, "Actor should be loaded in the content process.");
-      let actorChild = child.getActor("Test");
-      is(actorChild.show(), "TestChild", "actor show should have vaule.");
-      is(actorChild.manager, child, "manager should match WindowGlobalChild.");
-    });
-  },
-});
-
-declTest("asyncMessage testing", {
-  async test(browser) {
-    let parent = browser.browsingContext.currentWindowGlobal;
-    let actorParent = parent.getActor("Test");
-    ok(actorParent, "JSWindowActorParent should have value.");
-
-    await ContentTask.spawn(browser, {}, async function() {
-      let child = content.window.getWindowGlobalChild();
-      let actorChild = child.getActor("Test");
-      ok(actorChild, "JSWindowActorChild should have value.");
-
-      let promise = new Promise(resolve => {
-        actorChild.sendAsyncMessage("init", {});
-        actorChild.done = (data) => resolve(data);
-      }).then(data => {
-        ok(data.initial, "Initial should be true.");
-        ok(data.toParent, "ToParent should be true.");
-        ok(data.toChild, "ToChild should be true.");
-      });
-
-      await promise;
-    });
-  },
-});
-
-declTest("sendQuery testing", {
-  async test(browser) {
-    let parent = browser.browsingContext.currentWindowGlobal;
-    let actorParent = parent.getActor("Test");
-    ok(actorParent, "JSWindowActorParent should have value.");
-
-    let {result} = await actorParent.sendQuery("asyncAdd", {a: 10, b: 20});
-    is(result, 30);
-  },
-});
-
-declTest("asyncMessage without both sides", {
-  async test(browser) {
-    // If we don't create a parent actor, make sure the parent actor
-    // gets created by having sent the message.
-    await ContentTask.spawn(browser, {}, async function() {
-      let child = content.window.getWindowGlobalChild();
-      let actorChild = child.getActor("Test");
-      ok(actorChild, "JSWindowActorChild should have value.");
-
-      let promise = new Promise(resolve => {
-        actorChild.sendAsyncMessage("init", {});
-        actorChild.done = (data) => resolve(data);
-      }).then(data => {
-        ok(data.initial, "Initial should be true.");
-        ok(data.toParent, "ToParent should be true.");
-        ok(data.toChild, "ToChild should be true.");
-      });
-
-      await promise;
-    });
-  },
-});
-
-declTest("test event triggering actor creation", {
-  async test(browser) {
-    // Add a select element to the DOM of the loaded document.
-    await ContentTask.spawn(browser, {}, async function() {
-      content.document.body.innerHTML += `
-        <select id="testSelect">
-          <option>A</option>
-          <option>B</option>
-        </select>`;
-    });
-
-    // Wait for the observer notification.
-    let observePromise = new Promise(resolve => {
-      const TOPIC = "test-js-window-actor-parent-event";
-      Services.obs.addObserver(function obs(subject, topic, data) {
-        is(topic, TOPIC, "topic matches");
-
-        Services.obs.removeObserver(obs, TOPIC);
-        resolve({subject, data});
-      }, TOPIC);
-    });
-
-    // Click on the select to show the dropdown.
-    await BrowserTestUtils.synthesizeMouseAtCenter("#testSelect", {}, browser);
-
-    // Wait for the observer notification to fire, and inspect the results.
-    let {subject, data} = await observePromise;
-    is(data, "mozshowdropdown");
-
-    let parent = browser.browsingContext.currentWindowGlobal;
-    let actorParent = parent.getActor("Test");
-    ok(actorParent, "JSWindowActorParent should have value.");
-    is(subject.wrappedJSObject, actorParent, "Should have been recieved on the right actor");
-  },
-});
-
-declTest("test observer triggering actor creation", {
-  async test(browser) {
-    await ContentTask.spawn(browser, {}, async function() {
-      const TOPIC = "test-js-window-actor-child-observer";
-      Services.obs.notifyObservers(content.window, TOPIC, "dataString");
-
-      let child = content.window.getWindowGlobalChild();
-      let actorChild = child.getActor("Test");
-      ok(actorChild, "JSWindowActorChild should have value.");
-      let {subject, topic, data} = actorChild.lastObserved;
-
-      is(subject.getWindowGlobalChild().getActor("Test"), actorChild, "Should have been recieved on the right actor");
-      is(topic, TOPIC, "Topic matches");
-      is(data, "dataString", "Data matches");
-    });
-  },
-});
-
-declTest("test observers with null data", {
-  async test(browser) {
-    await ContentTask.spawn(browser, {}, async function() {
-      const TOPIC = "test-js-window-actor-child-observer";
-      Services.obs.notifyObservers(content.window, TOPIC);
-
-      let child = content.window.getWindowGlobalChild();
-      let actorChild = child.getActor("Test");
-      ok(actorChild, "JSWindowActorChild should have value.");
-      let {subject, topic, data} = actorChild.lastObserved;
-
-      is(subject.getWindowGlobalChild().getActor("Test"), actorChild, "Should have been recieved on the right actor");
-      is(topic, TOPIC, "Topic matches");
-      is(data, null, "Data matches");
-    });
-  },
-});
-
-declTest("observers don't notify with wrong window", {
-  async test(browser) {
-    await ContentTask.spawn(browser, {}, async function() {
-      const TOPIC = "test-js-window-actor-child-observer";
-      Services.obs.notifyObservers(null, TOPIC);
-      let child = content.window.getWindowGlobalChild();
-      let actorChild = child.getActor("Test");
-      ok(actorChild, "JSWindowActorChild should have value.");
-      is(actorChild.lastObserved, undefined, "Should not receive wrong window's observer notification!");
-    });
-  },
-});
-
-declTest("getActor with mismatch", {
-  matches: ["*://*/*"],
-
-  async test(browser) {
-    let parent = browser.browsingContext.currentWindowGlobal;
-    ok(parent, "WindowGlobalParent should have value.");
-    Assert.throws(() => parent.getActor("Test"),
-          /NS_ERROR_NOT_AVAILABLE/, "Should throw if it doesn't match.");
-
-    await ContentTask.spawn(browser, {}, async function() {
-      let child = content.window.getWindowGlobalChild();
-      ok(child, "WindowGlobalChild should have value.");
-
-      Assert.throws(() => child.getActor("Test"),
-        /NS_ERROR_NOT_AVAILABLE/, "Should throw if it doesn't match.");
-    });
-  },
-});
-
-declTest("getActor with matches", {
-  matches: ["*://*/*"],
-  url: TEST_URL,
-
-  async test(browser) {
-    let parent = browser.browsingContext.currentWindowGlobal;
-    ok(parent.getActor("Test"), "JSWindowActorParent should have value.");
-
-    await ContentTask.spawn(browser, {}, async function() {
-      let child = content.window.getWindowGlobalChild();
-      ok(child, "WindowGlobalChild should have value.");
-      ok(child.getActor("Test"), "JSWindowActorChild should have value.");
-    });
-  },
-});
-
-declTest("getActor with iframe matches", {
-  allFrames: true,
-  matches: ["*://*/*"],
-
-  async test(browser) {
-    await ContentTask.spawn(browser, TEST_URL, async function(url) {
-      // Create and append an iframe into the window's document.
-      let frame = content.document.createElement("iframe");
-      frame.src = url;
-      content.document.body.appendChild(frame);
-      await ContentTaskUtils.waitForEvent(frame, "load");
-
-      is(content.window.frames.length, 1, "There should be an iframe.");
-      let child = frame.contentWindow.window.getWindowGlobalChild();
-      ok(child.getActor("Test"), "JSWindowActorChild should have value.");
-    });
-  },
-});
-
-declTest("getActor with iframe mismatch", {
-  allFrames: true,
-  matches: ["about:home"],
-
-  async test(browser) {
-    await ContentTask.spawn(browser, TEST_URL, async function(url) {
-      // Create and append an iframe into the window's document.
-      let frame = content.document.createElement("iframe");
-      frame.src = url;
-      content.document.body.appendChild(frame);
-      await ContentTaskUtils.waitForEvent(frame, "load");
-
-      is(content.window.frames.length, 1, "There should be an iframe.");
-      let child = frame.contentWindow.window.getWindowGlobalChild();
-      Assert.throws(() => child.getActor("Test"),
-        /NS_ERROR_NOT_AVAILABLE/, "Should throw if it doesn't match.");
-    });
-  },
-});
-
-declTest("getActor with remoteType match", {
-  remoteTypes: ["web"],
-
-  async test(browser) {
-    let parent = browser.browsingContext.currentWindowGlobal;
-    ok(parent.getActor("Test"), "JSWindowActorParent should have value.");
-
-    await ContentTask.spawn(browser, {}, async function() {
-      let child = content.window.getWindowGlobalChild();
-      ok(child, "WindowGlobalChild should have value.");
-      ok(child.getActor("Test"), "JSWindowActorChild should have value.");
-    });
-  },
-});
-
-declTest("getActor with remoteType mismatch", {
-  remoteTypes: ["privileged"],
-  url: TEST_URL,
-
-  async test(browser) {
-    let parent = browser.browsingContext.currentWindowGlobal;
-    Assert.throws(() => parent.getActor("Test"),
-          /NS_ERROR_NOT_AVAILABLE/, "Should throw if its remoteTypes don't match.");
-
-    await ContentTask.spawn(browser, {}, async function() {
-      let child = content.window.getWindowGlobalChild();
-      ok(child, "WindowGlobalChild should have value.");
-      Assert.throws(() => child.getActor("Test"),
-          /NS_ERROR_NOT_AVAILABLE/, "Should throw if its remoteTypes don't match.");
-    });
-  },
-});
-
-declTest("getActor without allFrames", {
-  allFrames: false,
-
-  async test(browser) {
-    await ContentTask.spawn(browser, {}, async function() {
-      // Create and append an iframe into the window's document.
-      let frame = content.document.createElement("iframe");
-      content.document.body.appendChild(frame);
-      is(content.window.frames.length, 1, "There should be an iframe.");
-      let child = frame.contentWindow.window.getWindowGlobalChild();
-      Assert.throws(() => child.getActor("Test"),
-          /NS_ERROR_NOT_AVAILABLE/, "Should throw if allFrames is false.");
-    });
-  },
-});
-
-declTest("getActor with allFrames", {
-  allFrames: true,
-
-  async test(browser) {
-    await ContentTask.spawn(browser, {}, async function() {
-      // Create and append an iframe into the window's document.
-      let frame = content.document.createElement("iframe");
-      content.document.body.appendChild(frame);
-      is(content.window.frames.length, 1, "There should be an iframe.");
-      let child = frame.contentWindow.window.getWindowGlobalChild();
-      let actorChild = child.getActor("Test");
-      ok(actorChild, "JSWindowActorChild should have value.");
-    });
-  },
-});
-
-declTest("getActor without includeChrome", {
-  includeChrome: false,
-
-  async test(_browser, win) {
-    let parent = win.docShell.browsingContext.currentWindowGlobal;
-    SimpleTest.doesThrow(() =>
-      parent.getActor("Test"),
-      "Should throw if includeChrome is false.");
-  },
-});
-
-declTest("getActor with includeChrome", {
-  includeChrome: true,
-
-  async test(_browser, win) {
-    let parent = win.docShell.browsingContext.currentWindowGlobal;
-    let actorParent = parent.getActor("Test");
-    ok(actorParent, "JSWindowActorParent should have value.");
-  },
-});
-
-declTest("destroy actor by iframe remove", {
-  allFrames: true,
-
-  async test(browser) {
-    await ContentTask.spawn(browser, {}, async function() {
-      // Create and append an iframe into the window's document.
-      let frame = content.document.createElement("iframe");
-      frame.id = "frame";
-      content.document.body.appendChild(frame);
-      await ContentTaskUtils.waitForEvent(frame, "load");
-      is(content.window.frames.length, 1, "There should be an iframe.");
-      let child = frame.contentWindow.window.getWindowGlobalChild();
-      let actorChild = child.getActor("Test");
-      ok(actorChild, "JSWindowActorChild should have value.");
-
-      let willDestroyPromise = new Promise(resolve => {
-        const TOPIC = "test-js-window-actor-willdestroy";
-        Services.obs.addObserver(function obs(subject, topic, data) {
-          ok(data, "willDestroyCallback data should be true.");
-
-          Services.obs.removeObserver(obs, TOPIC);
-          resolve();
-        }, TOPIC);
-      });
-
-      let didDestroyPromise = new Promise(resolve => {
-        const TOPIC = "test-js-window-actor-diddestroy";
-        Services.obs.addObserver(function obs(subject, topic, data) {
-          ok(data, "didDestroyCallback data should be true.");
-
-          Services.obs.removeObserver(obs, TOPIC);
-          resolve();
-        }, TOPIC);
-      });
-
-      info("Remove frame");
-      content.document.getElementById("frame").remove();
-      await Promise.all([willDestroyPromise, didDestroyPromise]);
-
-      Assert.throws(() => child.getActor("Test"),
-        /InvalidStateError/, "Should throw if frame destroy.");
-    });
-  },
-});
-
-declTest("destroy actor by page navigates", {
-  allFrames: true,
-
-  async test(browser) {
-    info("creating an in-process frame");
-    await ContentTask.spawn(browser, URL, async function(url) {
-      let frame = content.document.createElement("iframe");
-      frame.src = url;
-      content.document.body.appendChild(frame);
-    });
-
-    info("navigating page");
-    await ContentTask.spawn(browser, TEST_URL, async function(url) {
-      let frame = content.document.querySelector("iframe");
-      frame.contentWindow.location = url;
-      let child = frame.contentWindow.window.getWindowGlobalChild();
-      let actorChild = child.getActor("Test");
-      ok(actorChild, "JSWindowActorChild should have value.");
-      await ContentTaskUtils.waitForEvent(frame, "load");
-
-      Assert.throws(() => child.getActor("Test"),
-              /InvalidStateError/, "Should throw if frame destroy.");
-    });
-  },
-});
--- a/dom/ipc/tests/chrome.ini
+++ b/dom/ipc/tests/chrome.ini
@@ -1,8 +1,7 @@
 [DEFAULT]
 skip-if = os == 'android'
 support-files =
   process_error.xul
 
 [test_process_error.xul]
-skip-if = !crashreporter
-[test_JSWindowActor.xul]
\ No newline at end of file
+skip-if = !crashreporter
\ No newline at end of file
deleted file mode 100644
--- a/dom/ipc/tests/test_JSWindowActor.xul
+++ /dev/null
@@ -1,96 +0,0 @@
-<?xml version="1.0"?>
-<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
-<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
-                 type="text/css"?>
-<window title="Test JSWindowActor"
-  xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
-  <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
-
-  <!-- test results are displayed in the html:body -->
-  <body xmlns="http://www.w3.org/1999/xhtml">
-  </body>
-
-  <!-- test code goes here -->
-  <script type="application/javascript"><![CDATA[
-  const {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm");
-  const URL = "about:blank";
-  let windowActorOptions = {
-    allFrames: true,
-    parent: {
-      moduleURI: "resource://testing-common/TestParent.jsm",
-    },
-    child: {
-      moduleURI: "resource://testing-common/TestChild.jsm",
-      observers: [
-        "test-js-window-actor-child-observer",
-      ],
-    },
-  };
-
-  add_task(async function registerWindowActor() {
-    ok(ChromeUtils, "Should be able to get the ChromeUtils interface");
-    ChromeUtils.registerWindowActor("Test", windowActorOptions);
-    SimpleTest.doesThrow(() =>
-      ChromeUtils.registerWindowActor("Test", windowActorOptions),
-      "Should throw if register has duplicate name.");
-    ChromeUtils.unregisterWindowActor("Test");
-  });
-
-  add_task(async function getActor() {
-    // Test in-process getActor function
-    ChromeUtils.registerWindowActor("Test", windowActorOptions);
-    let parent = this.window.docShell.browsingContext.currentWindowGlobal;
-    ok(parent, "WindowGlobalParent should have value.");
-    let actorParent = parent.getActor("Test");
-    is(actorParent.show(), "TestParent", "actor show should have vaule.");
-    is(actorParent.manager, parent, "manager should match WindowGlobalParent.");
-
-    let child = this.window.getWindowGlobalChild();
-    ok(child, "WindowGlobalChild should have value.");
-    is(child.isInProcess, true, "Actor should be in-process.");
-    let actorChild = child.getActor("Test");
-
-    is(actorChild.show(), "TestChild", "actor show should have vaule.");
-    is(actorChild.manager, child, "manager should match WindowGlobalChild.");
-    ok(parent.childActor===child, "Actor should be the same.");
-    ok(parent.childActor.getActor("Test")===child.getActor("Test"), "GetActor should be the same.");
-    ChromeUtils.unregisterWindowActor("Test");
-  });
-
-  add_task(async function asyncMessage() {
-    // Test in-process send/recvAsyncMessage function
-    ChromeUtils.registerWindowActor("Test", windowActorOptions);
-    let child = this.window.getWindowGlobalChild();
-    let actorChild = child.getActor("Test");
-    let promise = new Promise(resolve => {
-      actorChild.sendAsyncMessage("init", {});
-      actorChild.done = (data) => resolve(data);
-    }).then(data => {
-      ok(data.initial, "Initial should be true.");
-      ok(data.toParent, "ToParent should be true.");
-      ok(data.toChild, "ToChild should be true.");
-    });
-
-    await promise;
-    ChromeUtils.unregisterWindowActor("Test");
-  });
-
-  add_task(async function observers() {
-    // Test in-process observers notification
-    ChromeUtils.registerWindowActor("Test", windowActorOptions);
-    const TOPIC = "test-js-window-actor-child-observer";
-    Services.obs.notifyObservers(content.window, TOPIC, "dataString");
-
-    let child = content.window.getWindowGlobalChild();
-    let actorChild = child.getActor("Test");
-    ok(actorChild, "JSWindowActorChild should have value.");
-    let {subject, topic, data} = actorChild.lastObserved;
-
-    is(subject.getWindowGlobalChild().getActor("Test"), actorChild, "Should have been recieved on the right actor");
-    is(topic, TOPIC, "Topic matches");
-    is(data, "dataString", "Data matches");
-    ChromeUtils.unregisterWindowActor("Test");
-  });
-
-  ]]></script>
-</window>
--- a/dom/media/mp4/Index.cpp
+++ b/dom/media/mp4/Index.cpp
@@ -137,17 +137,17 @@ already_AddRefed<MediaRawData> SampleIte
     }
     if (moofParser->mSinf.mDefaultEncryptionType == AtomType("cenc")) {
       cryptoScheme = CryptoScheme::Cenc;
       writer->mCrypto.mCryptoScheme = CryptoScheme::Cenc;
       writer->mCrypto.mInitDataType = NS_LITERAL_STRING("cenc");
     } else if (moofParser->mSinf.mDefaultEncryptionType == AtomType("cbcs")) {
       cryptoScheme = CryptoScheme::Cbcs;
       writer->mCrypto.mCryptoScheme = CryptoScheme::Cbcs;
-      writer->mCrypto.mInitDataType = NS_LITERAL_STRING("cbcs");
+      writer->mCrypto.mInitDataType = NS_LITERAL_STRING("cenc");
     } else {
       MOZ_ASSERT_UNREACHABLE(
           "Sample description entry reports sample is encrypted, but no "
           "scheme, or an unsupported shceme is in use!");
       return nullptr;
     }
   }
 
--- 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'",