merge mozilla-inbound to mozilla-central. r=merge a=merge
authorSebastian Hengst <archaeopteryx@coole-files.de>
Sat, 05 Aug 2017 11:55:25 +0200
changeset 373070 933a04a91ce3bd44b230937083a835cb60637084
parent 373029 a9182f92641b24d0a972886aed27e784004a343f (current diff)
parent 373069 8163646237aa5bc72f6414c9e73fcb4073d053eb (diff)
child 373078 e18fe175011bda539515a3c088bbf60db30f7ba5
child 373111 c1e6dc8a781d2a2c8fa13d2bcfafc3a6131c5ab5
push id32288
push userarchaeopteryx@coole-files.de
push dateSat, 05 Aug 2017 09:55:48 +0000
treeherdermozilla-central@933a04a91ce3 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge, merge
milestone57.0a1
first release with
nightly linux32
933a04a91ce3 / 57.0a1 / 20170805100334 / files
nightly linux64
933a04a91ce3 / 57.0a1 / 20170805100334 / files
nightly mac
933a04a91ce3 / 57.0a1 / 20170805100334 / files
nightly win32
933a04a91ce3 / 57.0a1 / 20170805100334 / files
nightly win64
933a04a91ce3 / 57.0a1 / 20170805100334 / files
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
releases
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
merge mozilla-inbound to mozilla-central. r=merge a=merge MozReview-Commit-ID: 9IxbB2vCH4S
dom/events/EventListenerManager.cpp
js/src/frontend/BytecodeEmitter.cpp
taskcluster/ci/test/tests.yml
toolkit/mozapps/extensions/internal/WebExtensionBootstrap.js
--- a/accessible/generic/DocAccessible.cpp
+++ b/accessible/generic/DocAccessible.cpp
@@ -2139,29 +2139,36 @@ DocAccessible::DoARIAOwnsRelocation(Acce
     MOZ_ASSERT(owned->SafeElementAt(idx) != child, "Already in place!");
 
     if (owned->IndexOf(child) < idx) {
       continue; // ignore second entry of same ID
     }
 
     // A new child is found, check for loops.
     if (child->Parent() != aOwner) {
+      // Child is aria-owned by another container, skip.
+      if (child->IsRelocated()) {
+        continue;
+      }
+
       Accessible* parent = aOwner;
       while (parent && parent != child && !parent->IsDoc()) {
         parent = parent->Parent();
       }
       // A referred child cannot be a parent of the owner.
       if (parent == child) {
         continue;
       }
     }
 
     if (MoveChild(child, aOwner, insertIdx)) {
+      nsTArray<RefPtr<Accessible> >* relocated = mARIAOwnsHash.LookupOrAdd(aOwner);
+      MOZ_ASSERT(relocated == owned);
       child->SetRelocated(true);
-      owned->InsertElementAt(idx, child);
+      relocated->InsertElementAt(idx, child);
       idx++;
     }
   }
 
   // Put back children that are not seized anymore.
   PutChildrenBack(owned, idx);
   if (owned->Length() == 0) {
     mARIAOwnsHash.Remove(aOwner);
@@ -2217,17 +2224,18 @@ DocAccessible::PutChildrenBack(nsTArray<
     // 1. It was the last ordinal child, and the first aria-owned child.
     //    given:      <ul id="list" aria-owns="b"><li id="a"></li><li id="b"></li></ul>
     //    after load: $("list").setAttribute("aria-owns", "");
     // 2. The preceding adopted children were just reclaimed, eg:
     //    given:      <ul id="list"><li id="b"></li></ul>
     //    after load: $("list").setAttribute("aria-owns", "a b");
     //    later:      $("list").setAttribute("aria-owns", "");
     if (origContainer != owner || child->IndexInParent() != idxInParent) {
-      MoveChild(child, origContainer, idxInParent);
+      DebugOnly<bool> moved = MoveChild(child, origContainer, idxInParent);
+      MOZ_ASSERT(moved, "Failed to put child back.");
     } else {
       MOZ_ASSERT(!child->PrevSibling() || !child->PrevSibling()->IsRelocated(),
                  "No relocated child should appear before this one");
       MOZ_ASSERT(!child->NextSibling() || child->NextSibling()->IsRelocated(),
                  "No ordinal child should appear after this one");
     }
   }
 
@@ -2240,16 +2248,20 @@ DocAccessible::MoveChild(Accessible* aCh
 {
   MOZ_ASSERT(aChild, "No child");
   MOZ_ASSERT(aChild->Parent(), "No parent");
   MOZ_ASSERT(aIdxInParent <= static_cast<int32_t>(aNewParent->ChildCount()),
              "Wrong insertion point for a moving child");
 
   Accessible* curParent = aChild->Parent();
 
+  if (!aNewParent->IsAcceptableChild(aChild->GetContent())) {
+    return false;
+  }
+
 #ifdef A11Y_LOG
   logging::TreeInfo("move child", 0,
                     "old parent", curParent, "new parent", aNewParent,
                     "child", aChild, nullptr);
 #endif
 
   // Forget aria-owns info in case of ARIA owned element. The caller is expected
   // to update it if needed.
@@ -2271,20 +2283,16 @@ DocAccessible::MoveChild(Accessible* aCh
 
 #ifdef A11Y_LOG
     logging::TreeInfo("move child: parent tree after",
                       logging::eVerbose, curParent);
 #endif
     return true;
   }
 
-  if (!aNewParent->IsAcceptableChild(aChild->GetContent())) {
-    return false;
-  }
-
   MOZ_ASSERT(aIdxInParent <= static_cast<int32_t>(aNewParent->ChildCount()),
              "Wrong insertion point for a moving child");
 
   // If the child cannot be re-inserted into the tree, then make sure to remove
   // it from its present parent and then shutdown it.
   bool hasInsertionPoint = (aIdxInParent != -1) ||
     (aIdxInParent <= static_cast<int32_t>(aNewParent->ChildCount()));
 
--- a/accessible/tests/browser/e10s/browser_treeupdate_ariaowns.js
+++ b/accessible/tests/browser/e10s/browser_treeupdate_ariaowns.js
@@ -184,51 +184,36 @@ async function removeContainer(browser, 
 }
 
 async function stealAndRecacheChildren(browser, accDoc) {
   const id1 = "t3_container1";
   const id2 = "t3_container2";
   const acc1 = findAccessibleChildByID(accDoc, id1);
   const acc2 = findAccessibleChildByID(accDoc, id2);
 
-  /* ================ Steal from other ARIA owns ============================ */
+  /* ================ Attempt to steal from other ARIA owns ================= */
   let onReorder = waitForEvent(EVENT_REORDER, id2);
   await invokeSetAttribute(browser, id2, "aria-owns", "t3_child");
-  await onReorder;
-
-  let tree = {
-    SECTION: [ ]
-  };
-  testAccessibleTree(acc1, tree);
-
-  tree = {
-    SECTION: [
-      { CHECKBUTTON: [ ] }
-    ]
-  };
-  testAccessibleTree(acc2, tree);
-
-  /* ================ Append element to recache children ==================== */
-  onReorder = waitForEvent(EVENT_REORDER, id2);
   await ContentTask.spawn(browser, id2, id => {
     let div = content.document.createElement("div");
     div.setAttribute("role", "radio");
     content.document.getElementById(id).appendChild(div);
   });
   await onReorder;
 
-  tree = {
-    SECTION: [ ]
+  let tree = {
+    SECTION: [
+      { CHECKBUTTON: [ ] } // ARIA owned
+    ]
   };
   testAccessibleTree(acc1, tree);
 
   tree = {
     SECTION: [
-      { RADIOBUTTON: [ ] },
-      { CHECKBUTTON: [ ] } // ARIA owned
+      { RADIOBUTTON: [ ] }
     ]
   };
   testAccessibleTree(acc2, tree);
 }
 
 async function showHiddenElement(browser, accDoc) {
   const id = "t4_container1";
   const acc = findAccessibleChildByID(accDoc, id);
--- a/accessible/tests/browser/tree/browser_test_aria_owns.js
+++ b/accessible/tests/browser/tree/browser_test_aria_owns.js
@@ -50,35 +50,44 @@ async function runTests(browser, accDoc)
   await ContentTask.spawn(browser, null, async function() {
     let aa = document.createElement("li");
     aa.id = "aa";
     document.getElementById("one").appendChild(aa);
   });
 
   await onReorders;
 
+  // aria-owned child should be after ordinal children.
   testChildrenIds(one, ["aa", "a"]);
 
-  onReorders = waitForEvents([
-      [EVENT_REORDER, "two"],    // "b" will go to "three"
-      [EVENT_REORDER, "three"], // some children will be reclaimed and acquired
-      [EVENT_REORDER, "one"]]); // removing aria-owns will reorder native children
+  onReorders = waitForEvent(EVENT_REORDER, "one");
 
   await ContentTask.spawn(browser, null, async function() {
     // removing aria-owns should reorder the children
     document.getElementById("one").removeAttribute("aria-owns");
-    // child order will be overridden by aria-owns
-    document.getElementById("three").setAttribute("aria-owns", "b d");
   });
 
   await onReorders;
 
+  // with no aria-owns, layout order should prevail.
   testChildrenIds(one, ["a", "aa"]);
-  testChildrenIds(two, ["c"]);
-  testChildrenIds(three, ["b", "d"]);
+
+  onReorders = waitForEvents([
+      [EVENT_REORDER, "four"],    // "b" will go to "three"
+      [EVENT_REORDER, "two"]]); // some children will be reclaimed and acquired
+
+  await ContentTask.spawn(browser, null, async function() {
+    // child order will be overridden by aria-owns
+    document.getElementById("four").setAttribute("aria-owns", "b e");
+  });
+
+  await onReorders;
+
+  testChildrenIds(four, ["b", "e"]);
+  testChildrenIds(two, ["d", "c"]);
 }
 
 /**
  * Test caching of accessible object states
  */
 addAccessibleTask(`
     <ul id="one">
       <li id="a">Test</li>
--- a/accessible/tests/mochitest/treeupdate/test_ariaowns.html
+++ b/accessible/tests/mochitest/treeupdate/test_ariaowns.html
@@ -301,70 +301,78 @@
       }
 
       this.getID = function removeA11eteiner_getID() {
         return "Remove an accessible DOM element containing an element referred by ARIA owns";
       }
     }
 
     /**
-     * Steal an element from other ARIA owns element. This use case guarantees
-     * that result of setAttribute/removeAttribute doesn't depend on their order.
+     * Attempt to steal an element from other ARIA owns element. This should
+     * not be possible. The only child that will get owned into this
+     * container is a previously not aria-owned one.
      */
     function stealFromOtherARIAOwns() {
       this.eventSeq = [
-        new invokerChecker(EVENT_REORDER, getNode("t3_container2"))
+        new invokerChecker(EVENT_REORDER, getNode("t3_container3"))
       ];
 
       this.invoke = function stealFromOtherARIAOwns_invoke() {
-        getNode("t3_container2").setAttribute("aria-owns", "t3_child");
+        getNode("t3_container3").setAttribute("aria-owns", "t3_child t3_child2");
       }
 
       this.finalCheck = function stealFromOtherARIAOwns_finalCheck() {
         var tree =
           { SECTION: [
+            { CHECKBUTTON: [
+            ] }
           ] };
         testAccessibleTree("t3_container1", tree);
 
         tree =
           { SECTION: [
+          ] };
+        testAccessibleTree("t3_container2", tree);
+
+        tree =
+          { SECTION: [
             { CHECKBUTTON: [
             ] }
           ] };
-        testAccessibleTree("t3_container2", tree);
+        testAccessibleTree("t3_container3", tree);
       }
 
       this.getID = function stealFromOtherARIAOwns_getID() {
         return "Steal an element from other ARIA owns element";
       }
     }
 
     function appendElToRecacheChildren() {
       this.eventSeq = [
-        new invokerChecker(EVENT_REORDER, getNode("t3_container2"))
+        new invokerChecker(EVENT_REORDER, getNode("t3_container3"))
       ];
 
       this.invoke = function appendElToRecacheChildren_invoke() {
         var div = document.createElement("div");
         div.setAttribute("role", "radio")
-        getNode("t3_container2").appendChild(div);
+        getNode("t3_container3").appendChild(div);
       }
 
       this.finalCheck = function appendElToRecacheChildren_finalCheck() {
         var tree =
           { SECTION: [
           ] };
-        testAccessibleTree("t3_container1", tree);
+        testAccessibleTree("t3_container2", tree);
 
         tree =
           { SECTION: [
             { RADIOBUTTON: [ ] },
             { CHECKBUTTON: [ ] } // ARIA owned
           ] };
-        testAccessibleTree("t3_container2", tree);
+        testAccessibleTree("t3_container3", tree);
       }
 
       this.getID = function appendElToRecacheChildren_getID() {
         return "Append a child under @aria-owns element to trigger children recache";
       }
     }
 
     function showHiddenElement() {
@@ -744,17 +752,20 @@
 
   <div id="t2_container1" aria-owns="t2_owned"></div>
   <div id="t2_container2">
     <div id="t2_container3"><div id="t2_owned" role="checkbox"></div></div>
   </div>
 
   <div id="t3_container1" aria-owns="t3_child"></div>
   <div id="t3_child" role="checkbox"></div>
-  <div id="t3_container2"></div>
+  <div id="t3_container2">
+    <div id="t3_child2" role="checkbox"></div>
+  </div>
+  <div id="t3_container3"></div>
 
   <div id="t4_container1" aria-owns="t4_child1 t4_child2"></div>
   <div id="t4_container2">
     <div id="t4_child1" style="display:none" role="checkbox"></div>
     <div id="t4_child2" role="radio"></div>
   </div>
 
   <div id="t5_container">
--- a/browser/base/content/browser-places.js
+++ b/browser/base/content/browser-places.js
@@ -1926,18 +1926,18 @@ var BookmarkingUI = {
     if (widget.overflowed) {
       // Close the overflow panel because the Edit Bookmark panel will appear.
       widget.node.removeAttribute("closemenu");
     }
     this.onStarCommand(aEvent);
   },
 
   onStarCommand(aEvent) {
-    // Ignore clicks on the star if we are updating its state.
-    if (!this._pendingUpdate) {
+    // Ignore non-left clicks on the star, or if we are updating its state.
+    if (!this._pendingUpdate && (aEvent.type != "click" || aEvent.button == 0)) {
       let isBookmarked = this._itemGuids.size > 0;
       // Disable the old animation in photon
       if (!isBookmarked && !AppConstants.MOZ_PHOTON_THEME)
         this._showBookmarkedNotification();
       // Set up variables for new animation in Photon
       if (!isBookmarked && AppConstants.MOZ_PHOTON_ANIMATIONS) {
         BrowserUtils.setToolbarButtonHeightProperty(this.star);
         this.star.setAttribute("animate", "true");
--- a/browser/components/downloads/DownloadsCommon.jsm
+++ b/browser/components/downloads/DownloadsCommon.jsm
@@ -191,26 +191,32 @@ this.DownloadsCommon = {
    * Indicates whether we should show visual notification on the indicator
    * when a download event is triggered.
    */
   get animateNotifications() {
     return PrefObserver.animateNotifications;
   },
 
   /**
-   * Get access to one of the DownloadsData or PrivateDownloadsData objects,
-   * depending on the privacy status of the window in question.
+   * Get access to one of the DownloadsData, PrivateDownloadsData, or
+   * HistoryDownloadsData objects, depending on the privacy status of the
+   * specified window and on whether history downloads should be included.
    *
-   * @param aWindow
+   * @param window
    *        The browser window which owns the download button.
+   * @param [optional] history
+   *        True to include history downloads when the window is public.
    */
-  getData(aWindow) {
-    if (PrivateBrowsingUtils.isContentWindowPrivate(aWindow)) {
+  getData(window, history = false) {
+    if (PrivateBrowsingUtils.isContentWindowPrivate(window)) {
       return PrivateDownloadsData;
     }
+    if (history) {
+      return HistoryDownloadsData;
+    }
     return DownloadsData;
   },
 
   /**
    * Initializes the Downloads back-end and starts receiving events for both the
    * private and non-private downloads data objects.
    */
   initializeAllDataLinks() {
@@ -280,27 +286,16 @@ this.DownloadsCommon = {
         return DownloadsCommon.DOWNLOAD_PAUSED;
       }
       return DownloadsCommon.DOWNLOAD_CANCELED;
     }
     return DownloadsCommon.DOWNLOAD_NOTSTARTED;
   },
 
   /**
-   * Helper function required because the Downloads Panel and the Downloads View
-   * don't share the controller yet.
-   */
-  removeAndFinalizeDownload(download) {
-    Downloads.getList(Downloads.ALL)
-             .then(list => list.remove(download))
-             .then(() => download.finalize(true))
-             .catch(Cu.reportError);
-  },
-
-  /**
    * Given an iterable collection of Download objects, generates and returns
    * statistics about that collection.
    *
    * @param downloads An iterable collection of Download objects.
    *
    * @return Object whose properties are the generated statistics. Currently,
    *         we return the following properties:
    *
@@ -644,34 +639,44 @@ XPCOMUtils.defineLazyGetter(DownloadsCom
  * Retrieves the list of past and completed downloads from the underlying
  * Downloads API data, and provides asynchronous notifications allowing to
  * build a consistent view of the available data.
  *
  * Note that using this object does not automatically initialize the list of
  * downloads. This is useful to display a neutral progress indicator in
  * the main browser window until the autostart timeout elapses.
  *
- * Note that DownloadsData and PrivateDownloadsData are two equivalent singleton
- * objects, one accessing non-private downloads, and the other accessing private
- * ones.
+ * This powers the DownloadsData, PrivateDownloadsData, and HistoryDownloadsData
+ * singleton objects.
  */
-function DownloadsDataCtor(aPrivate) {
-  this._isPrivate = aPrivate;
+function DownloadsDataCtor({ isPrivate, isHistory } = {}) {
+  this._isPrivate = !!isPrivate;
 
   // Contains all the available Download objects and their integer state.
   this.oldDownloadStates = new Map();
 
+  // For the history downloads list we don't need to register this as a view,
+  // but we have to ensure that the DownloadsData object is initialized before
+  // we register more views. This ensures that the view methods of DownloadsData
+  // are invoked before those of views registered on HistoryDownloadsData,
+  // allowing the endTime property to be set correctly.
+  if (isHistory) {
+    DownloadsData.initializeDataLink();
+    this._promiseList = DownloadsData._promiseList
+                                     .then(() => DownloadHistory.getList());
+    return;
+  }
+
   // This defines "initializeDataLink" and "_promiseList" synchronously, then
   // continues execution only when "initializeDataLink" is called, allowing the
   // underlying data to be loaded only when actually needed.
   this._promiseList = (async () => {
     await new Promise(resolve => this.initializeDataLink = resolve);
-
-    let list = await Downloads.getList(this._isPrivate ? Downloads.PRIVATE
-                                                       : Downloads.PUBLIC);
+    let list = await Downloads.getList(isPrivate ? Downloads.PRIVATE
+                                                 : Downloads.PUBLIC);
     await list.addView(this);
     return list;
   })();
 }
 
 DownloadsDataCtor.prototype = {
   /**
    * Starts receiving events for current downloads.
@@ -705,17 +710,19 @@ DownloadsDataCtor.prototype = {
     return false;
   },
 
   /**
    * Asks the back-end to remove finished downloads from the list. This method
    * is only called after the data link has been initialized.
    */
   removeFinished() {
-    this._promiseList.then(list => list.removeFinished()).catch(Cu.reportError);
+    Downloads.getList(this._isPrivate ? Downloads.PRIVATE : Downloads.PUBLIC)
+             .then(list => list.removeFinished())
+             .catch(Cu.reportError);
     let indicatorData = this._isPrivate ? PrivateDownloadsIndicatorData
                                         : DownloadsIndicatorData;
     indicatorData.attention = DownloadsCommon.ATTENTION_NONE;
   },
 
   // Integration with the asynchronous Downloads back-end
 
   onDownloadAdded(download) {
@@ -830,22 +837,26 @@ DownloadsDataCtor.prototype = {
       browserWin.DownloadsIndicatorView.showEventNotification(aType);
       return;
     }
     this.panelHasShownBefore = true;
     browserWin.DownloadsPanel.showPanel();
   }
 };
 
+XPCOMUtils.defineLazyGetter(this, "HistoryDownloadsData", function() {
+  return new DownloadsDataCtor({ isHistory: true });
+});
+
 XPCOMUtils.defineLazyGetter(this, "PrivateDownloadsData", function() {
-  return new DownloadsDataCtor(true);
+  return new DownloadsDataCtor({ isPrivate: true });
 });
 
 XPCOMUtils.defineLazyGetter(this, "DownloadsData", function() {
-  return new DownloadsDataCtor(false);
+  return new DownloadsDataCtor();
 });
 
 // DownloadsViewPrototype
 
 /**
  * A prototype for an object that registers itself with DownloadsData as soon
  * as a view is registered with it.
  */
--- a/browser/components/downloads/DownloadsViewUI.jsm
+++ b/browser/components/downloads/DownloadsViewUI.jsm
@@ -20,16 +20,18 @@ Cu.import("resource://gre/modules/XPCOMU
 XPCOMUtils.defineLazyModuleGetter(this, "Downloads",
                                   "resource://gre/modules/Downloads.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "DownloadUtils",
                                   "resource://gre/modules/DownloadUtils.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "DownloadsCommon",
                                   "resource:///modules/DownloadsCommon.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "OS",
                                   "resource://gre/modules/osfile.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
+                                  "resource://gre/modules/PlacesUtils.jsm");
 
 this.DownloadsViewUI = {
   /**
    * Returns true if the given string is the name of a command that can be
    * handled by the Downloads user interface, including standard commands.
    */
   isCommandName(name) {
     return name.startsWith("cmd_") || name.startsWith("downloadsCmd_");
@@ -359,16 +361,18 @@ this.DownloadsViewUI.DownloadElementShel
       case "downloadsCmd_openReferrer":
         return !!this.download.source.referrer;
       case "downloadsCmd_confirmBlock":
       case "downloadsCmd_chooseUnblock":
       case "downloadsCmd_chooseOpen":
       case "downloadsCmd_unblock":
       case "downloadsCmd_unblockAndOpen":
         return this.download.hasBlockedData;
+      case "downloadsCmd_cancel":
+        return this.download.hasPartialData || !this.download.stopped;
     }
     return false;
   },
 
   downloadsCmd_cancel() {
     // This is the correct way to avoid race conditions when cancelling.
     this.download.cancel().catch(() => {});
     this.download.removePartialData().catch(Cu.reportError);
@@ -385,9 +389,25 @@ this.DownloadsViewUI.DownloadElementShel
     } else {
       this.download.cancel();
     }
   },
 
   downloadsCmd_confirmBlock() {
     this.download.confirmBlock().catch(Cu.reportError);
   },
+
+  cmd_delete() {
+    (async () => {
+      // Remove the associated history element first, if any, so that the views
+      // that combine history and session downloads won't resurrect the history
+      // download into the view just before it is deleted permanently.
+      try {
+        await PlacesUtils.history.remove(this.download.source.url);
+      } catch (ex) {
+        Cu.reportError(ex);
+      }
+      let list = await Downloads.getList(Downloads.ALL);
+      await list.remove(this.download);
+      await this.download.finalize(true);
+    })().catch(Cu.reportError);
+  },
 };
--- a/browser/components/downloads/content/allDownloadsViewOverlay.js
+++ b/browser/components/downloads/content/allDownloadsViewOverlay.js
@@ -2,377 +2,188 @@
  * 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/. */
 /* eslint-env mozilla/browser-window */
 
 var { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 
-XPCOMUtils.defineLazyModuleGetter(this, "DownloadUtils",
-                                  "resource://gre/modules/DownloadUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Downloads",
+                                  "resource://gre/modules/Downloads.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "DownloadsCommon",
                                   "resource:///modules/DownloadsCommon.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "DownloadsViewUI",
                                   "resource:///modules/DownloadsViewUI.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "FileUtils",
                                   "resource://gre/modules/FileUtils.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "NetUtil",
                                   "resource://gre/modules/NetUtil.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "OS",
                                   "resource://gre/modules/osfile.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
-                                  "resource://gre/modules/PlacesUtils.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "RecentWindow",
                                   "resource:///modules/RecentWindow.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "Services",
                                   "resource://gre/modules/Services.jsm");
 
-const DESTINATION_FILE_URI_ANNO  = "downloads/destinationFileURI";
-const DOWNLOAD_META_DATA_ANNO    = "downloads/metaData";
-
-/**
- * Represents a download from the browser history. It implements part of the
- * interface of the Download object.
- *
- * @param aPlacesNode
- *        The Places node from which the history download should be initialized.
- */
-function HistoryDownload(aPlacesNode) {
-  // TODO (bug 829201): history downloads should get the referrer from Places.
-  this.source = {
-    url: aPlacesNode.uri,
-  };
-  this.target = {
-    path: undefined,
-    exists: false,
-    size: undefined,
-  };
-
-  // In case this download cannot obtain its end time from the Places metadata,
-  // use the time from the Places node, that is the start time of the download.
-  this.endTime = aPlacesNode.time / 1000;
-}
-
-HistoryDownload.prototype = {
-  /**
-   * Pushes information from Places metadata into this object.
-   */
-  updateFromMetaData(metaData) {
-    try {
-      this.target.path = Cc["@mozilla.org/network/protocol;1?name=file"]
-                           .getService(Ci.nsIFileProtocolHandler)
-                           .getFileFromURLSpec(metaData.targetFileSpec).path;
-    } catch (ex) {
-      this.target.path = undefined;
-    }
-
-    if ("state" in metaData) {
-      this.succeeded = metaData.state == DownloadsCommon.DOWNLOAD_FINISHED;
-      this.canceled = metaData.state == DownloadsCommon.DOWNLOAD_CANCELED ||
-                      metaData.state == DownloadsCommon.DOWNLOAD_PAUSED;
-      this.endTime = metaData.endTime;
-
-      // Recreate partial error information from the state saved in history.
-      if (metaData.state == DownloadsCommon.DOWNLOAD_FAILED) {
-        this.error = { message: "History download failed." };
-      } else if (metaData.state == DownloadsCommon.DOWNLOAD_BLOCKED_PARENTAL) {
-        this.error = { becauseBlockedByParentalControls: true };
-      } else if (metaData.state == DownloadsCommon.DOWNLOAD_DIRTY) {
-        this.error = {
-          becauseBlockedByReputationCheck: true,
-          reputationCheckVerdict: metaData.reputationCheckVerdict || "",
-        };
-      } else {
-        this.error = null;
-      }
-
-      // Normal history downloads are assumed to exist until the user interface
-      // is refreshed, at which point these values may be updated.
-      this.target.exists = true;
-      this.target.size = metaData.fileSize;
-    } else {
-      // Metadata might be missing from a download that has started but hasn't
-      // stopped already. Normally, this state is overridden with the one from
-      // the corresponding in-progress session download. But if the browser is
-      // terminated abruptly and additionally the file with information about
-      // in-progress downloads is lost, we may end up using this state. We use
-      // the failed state to allow the download to be restarted.
-      //
-      // On the other hand, if the download is missing the target file
-      // annotation as well, it is just a very old one, and we can assume it
-      // succeeded.
-      this.succeeded = !this.target.path;
-      this.error = this.target.path ? { message: "Unstarted download." } : null;
-      this.canceled = false;
-
-      // These properties may be updated if the user interface is refreshed.
-      this.target.exists = false;
-      this.target.size = undefined;
-    }
-  },
-
-  /**
-   * History downloads are never in progress.
-   */
-  stopped: true,
-
-  /**
-   * No percentage indication is shown for history downloads.
-   */
-  hasProgress: false,
-
-  /**
-   * History downloads cannot be restarted using their partial data, even if
-   * they are indicated as paused in their Places metadata. The only way is to
-   * use the information from a persisted session download, that will be shown
-   * instead of the history download. In case this session download is not
-   * available, we show the history download as canceled, not paused.
-   */
-  hasPartialData: false,
-
-  /**
-   * This method mimicks the "start" method of session downloads, and is called
-   * when the user retries a history download.
-   *
-   * At present, we always ask the user for a new target path when retrying a
-   * history download. In the future we may consider reusing the known target
-   * path if the folder still exists and the file name is not already used,
-   * except when the user preferences indicate that the target path should be
-   * requested every time a new download is started.
-   */
-  start() {
-    let browserWin = RecentWindow.getMostRecentBrowserWindow();
-    let initiatingDoc = browserWin ? browserWin.document : document;
-
-    // Do not suggest a file name if we don't know the original target.
-    let leafName = this.target.path ? OS.Path.basename(this.target.path) : null;
-    DownloadURL(this.source.url, leafName, initiatingDoc);
-
-    return Promise.resolve();
-  },
-
-  /**
-   * This method mimicks the "refresh" method of session downloads, except that
-   * it cannot notify that the data changed to the Downloads View.
-   */
-  async refresh() {
-    try {
-      this.target.size = (await OS.File.stat(this.target.path)).size;
-      this.target.exists = true;
-    } catch (ex) {
-      // We keep the known file size from the metadata, if any.
-      this.target.exists = false;
-    }
-  },
-};
-
 /**
  * A download element shell is responsible for handling the commands and the
  * displayed data for a single download view element.
  *
  * The shell may contain a session download, a history download, or both.  When
  * both a history and a session download are present, the session download gets
  * priority and its information is displayed.
  *
  * On construction, a new richlistitem is created, and can be accessed through
  * the |element| getter. The shell doesn't insert the item in a richlistbox, the
  * caller must do it and remove the element when it's no longer needed.
  *
- * The caller is also responsible for forwarding status notifications for
- * session downloads, calling the onSessionDownloadChanged method.
+ * The caller is also responsible for forwarding status notifications, calling
+ * the onChanged method.
  *
- * @param [optional] aSessionDownload
- *        The session download, required if aHistoryDownload is not set.
- * @param [optional] aHistoryDownload
- *        The history download, required if aSessionDownload is not set.
+ * @param download
+ *        The Download object from the DownloadHistoryList.
  */
-function HistoryDownloadElementShell(aSessionDownload, aHistoryDownload) {
+function HistoryDownloadElementShell(download) {
+  this._download = download;
+
   this.element = document.createElement("richlistitem");
   this.element._shell = this;
 
   this.element.classList.add("download");
   this.element.classList.add("download-state");
-
-  if (aSessionDownload) {
-    this.sessionDownload = aSessionDownload;
-  }
-  if (aHistoryDownload) {
-    this.historyDownload = aHistoryDownload;
-  }
 }
 
 HistoryDownloadElementShell.prototype = {
   __proto__: DownloadsViewUI.DownloadElementShell.prototype,
 
   /**
-   * Manages the "active" state of the shell.  By default all the shells without
-   * a session download are inactive, thus their UI is not updated.  They must
-   * be activated when entering the visible area.  Session downloads are always
-   * active.
+   * Manages the "active" state of the shell.  By default all the shells are
+   * inactive, thus their UI is not updated.  They must be activated when
+   * entering the visible area.
    */
   ensureActive() {
     if (!this._active) {
       this._active = true;
       this.element.setAttribute("active", true);
-      this._updateUI();
+      this.onChanged();
     }
   },
   get active() {
     return !!this._active;
   },
 
   /**
    * Overrides the base getter to return the Download or HistoryDownload object
    * for displaying information and executing commands in the user interface.
    */
   get download() {
-    return this._sessionDownload || this._historyDownload;
-  },
-
-  _sessionDownload: null,
-  get sessionDownload() {
-    return this._sessionDownload;
-  },
-  set sessionDownload(aValue) {
-    if (this._sessionDownload != aValue) {
-      if (!aValue && !this._historyDownload) {
-        throw new Error("Should always have either a Download or a HistoryDownload");
-      }
-
-      this._sessionDownload = aValue;
-      if (aValue) {
-        this.sessionDownloadState = DownloadsCommon.stateOfDownload(aValue);
-      }
-
-      this.ensureActive();
-      this._updateUI();
-    }
-    return aValue;
+    return this._download;
   },
 
-  _historyDownload: null,
-  get historyDownload() {
-    return this._historyDownload;
-  },
-  set historyDownload(aValue) {
-    if (this._historyDownload != aValue) {
-      if (!aValue && !this._sessionDownload) {
-        throw new Error("Should always have either a Download or a HistoryDownload");
-      }
-
-      this._historyDownload = aValue;
-
-      // We don't need to update the UI if we had a session data item, because
-      // the places information isn't used in this case.
-      if (!this._sessionDownload) {
-        this._updateUI();
-      }
-    }
-    return aValue;
-  },
-
-  _updateUI() {
-    // There is nothing to do if the item has always been invisible.
-    if (!this.active) {
-      return;
-    }
-
+  onStateChanged() {
     // Since the state changed, we may need to check the target file again.
     this._targetFileChecked = false;
 
     this._updateState();
-  },
-
-  onStateChanged() {
-    this._updateState();
 
     if (this.element.selected) {
       goUpdateDownloadCommands();
     } else {
       // If a state change occurs in an item that is not currently selected,
       // this is the only command that may be affected.
       goUpdateCommand("downloadsCmd_clearDownloads");
     }
   },
 
-  onSessionDownloadChanged() {
-    let newState = DownloadsCommon.stateOfDownload(this.sessionDownload);
-    if (this.sessionDownloadState != newState) {
-      this.sessionDownloadState = newState;
+  onChanged() {
+    // There is nothing to do if the item has always been invisible.
+    if (!this.active) {
+      return;
+    }
+
+    let newState = DownloadsCommon.stateOfDownload(this.download);
+    if (this._downloadState !== newState) {
+      this._downloadState = newState;
       this.onStateChanged();
     }
 
     // This cannot be placed within onStateChanged because
     // when a download goes from hasBlockedData to !hasBlockedData
     // it will still remain in the same state.
     this.element.classList.toggle("temporary-block",
                                   !!this.download.hasBlockedData);
     this._updateProgress();
   },
+  _downloadState: null,
 
   isCommandEnabled(aCommand) {
     // The only valid command for inactive elements is cmd_delete.
     if (!this.active && aCommand != "cmd_delete") {
       return false;
     }
     switch (aCommand) {
       case "downloadsCmd_open":
         // This property is false if the download did not succeed.
         return this.download.target.exists;
       case "downloadsCmd_show":
         // TODO: Bug 827010 - Handle part-file asynchronously.
-        if (this._sessionDownload && this.download.target.partFilePath) {
+        if (this.download.target.partFilePath) {
           let partFile = new FileUtils.File(this.download.target.partFilePath);
           if (partFile.exists()) {
             return true;
           }
         }
 
         // This property is false if the download did not succeed.
         return this.download.target.exists;
       case "cmd_delete":
         // We don't want in-progress downloads to be removed accidentally.
         return this.download.stopped;
-      case "downloadsCmd_cancel":
-        return !!this._sessionDownload;
     }
     return DownloadsViewUI.DownloadElementShell.prototype
                           .isCommandEnabled.call(this, aCommand);
   },
 
   doCommand(aCommand) {
     if (DownloadsViewUI.isCommandName(aCommand)) {
       this[aCommand]();
     }
   },
 
+  downloadsCmd_retry() {
+    if (this.download.start) {
+      DownloadsViewUI.DownloadElementShell.prototype
+                     .downloadsCmd_retry.call(this);
+      return;
+    }
+
+    let browserWin = RecentWindow.getMostRecentBrowserWindow();
+    let initiatingDoc = browserWin ? browserWin.document : document;
+
+    // Do not suggest a file name if we don't know the original target.
+    let targetPath = this.download.target.path ?
+                     OS.Path.basename(this.download.target.path) : null;
+    DownloadURL(this.download.source.url, targetPath, initiatingDoc);
+  },
+
   downloadsCmd_open() {
     let file = new FileUtils.File(this.download.target.path);
     DownloadsCommon.openDownloadedFile(file, null, window);
   },
 
   downloadsCmd_show() {
     let file = new FileUtils.File(this.download.target.path);
     DownloadsCommon.showDownloadedFile(file);
   },
 
   downloadsCmd_openReferrer() {
     openURL(this.download.source.referrer);
   },
 
-  cmd_delete() {
-    if (this._sessionDownload) {
-      DownloadsCommon.removeAndFinalizeDownload(this.download);
-    }
-    if (this._historyDownload) {
-      PlacesUtils.history.remove(this.download.source.url);
-    }
-  },
-
   downloadsCmd_unblock() {
     this.confirmUnblock(window, "unblock");
   },
 
   downloadsCmd_chooseUnblock() {
     this.confirmUnblock(window, "chooseUnblock");
   },
 
@@ -416,37 +227,22 @@ HistoryDownloadElementShell.prototype = 
     // available, we cannot retrieve information about the target file.
     if (!this.download.target.path) {
       return;
     }
 
     // Start checking for existence.  This may be done twice if onSelect is
     // called again before the information is collected.
     if (!this._targetFileChecked) {
-      this._checkTargetFileOnSelect().catch(Cu.reportError);
+      this.download.refresh().catch(Cu.reportError).then(() => {
+        // Do not try to check for existence again even if this failed.
+        this._targetFileChecked = true;
+      });
     }
   },
-
-  async _checkTargetFileOnSelect() {
-    try {
-      await this.download.refresh();
-    } finally {
-      // Do not try to check for existence again if this failed once.
-      this._targetFileChecked = true;
-    }
-
-    // Update the commands only if the element is still selected.
-    if (this.element.selected) {
-      goUpdateDownloadCommands();
-    }
-
-    // Ensure the interface has been updated based on the new values. We need to
-    // do this because history downloads can't trigger update notifications.
-    this._updateProgress();
-  },
 };
 
 /**
  * Relays commands from the download.xml binding to the selected items.
  */
 const DownloadsView = {
   onDownloadCommand(event, command) {
     goDoCommand(command);
@@ -467,34 +263,27 @@ const DownloadsView = {
  * as they exist they "collapses" their history "counterpart" (So we don't show two
  * items for every download).
  */
 function DownloadsPlacesView(aRichListBox, aActive = true) {
   this._richlistbox = aRichListBox;
   this._richlistbox._placesView = this;
   window.controllers.insertControllerAt(0, this);
 
-  // Map download URLs to download element shells regardless of their type
-  this._downloadElementsShellsForURI = new Map();
-
-  // Map download data items to their element shells.
+  // Map downloads to their element shells.
   this._viewItemsForDownloads = new WeakMap();
 
-  // Points to the last session download element. We keep track of this
-  // in order to keep all session downloads above past downloads.
-  this._lastSessionDownloadElement = null;
-
   this._searchTerm = "";
 
   this._active = aActive;
 
   // Register as a downloads view. The places data will be initialized by
   // the places setter.
   this._initiallySelectedElement = null;
-  this._downloadsData = DownloadsCommon.getData(window.opener || window);
+  this._downloadsData = DownloadsCommon.getData(window.opener || window, true);
   this._downloadsData.addView(this);
 
   // Get the Download button out of the attention state since we're about to
   // view all downloads.
   DownloadsCommon.getIndicatorData(window).attention = DownloadsCommon.ATTENTION_NONE;
 
   // Make sure to unregister the view if the window is closed.
   window.addEventListener("unload", () => {
@@ -518,335 +307,16 @@ DownloadsPlacesView.prototype = {
   },
   set active(val) {
     this._active = val;
     if (this._active)
       this._ensureVisibleElementsAreActive();
     return this._active;
   },
 
-  /**
-   * This cache exists in order to optimize the load of the Downloads View, when
-   * Places annotations for history downloads must be read. In fact, annotations
-   * are stored in a single table, and reading all of them at once is much more
-   * efficient than an individual query.
-   *
-   * When this property is first requested, it reads the annotations for all the
-   * history downloads and stores them indefinitely.
-   *
-   * The historical annotations are not expected to change for the duration of
-   * the session, except in the case where a session download is running for the
-   * same URI as a history download. To ensure we don't use stale data, URIs
-   * corresponding to session downloads are permanently removed from the cache.
-   * This is a very small mumber compared to history downloads.
-   *
-   * This property returns a Map from each download source URI found in Places
-   * annotations to an object with the format:
-   *
-   * { targetFileSpec, state, endTime, fileSize, ... }
-   *
-   * The targetFileSpec property is the value of "downloads/destinationFileURI",
-   * while the other properties are taken from "downloads/metaData". Any of the
-   * properties may be missing from the object.
-   */
-  get _cachedPlacesMetaData() {
-    if (!this.__cachedPlacesMetaData) {
-      this.__cachedPlacesMetaData = new Map();
-
-      // Read the metadata annotations first, but ignore invalid JSON.
-      for (let result of PlacesUtils.annotations.getAnnotationsWithName(
-                                                 DOWNLOAD_META_DATA_ANNO)) {
-        try {
-          this.__cachedPlacesMetaData.set(result.uri.spec,
-                                          JSON.parse(result.annotationValue));
-        } catch (ex) {}
-      }
-
-      // Add the target file annotations to the metadata.
-      for (let result of PlacesUtils.annotations.getAnnotationsWithName(
-                                                 DESTINATION_FILE_URI_ANNO)) {
-        let metaData = this.__cachedPlacesMetaData.get(result.uri.spec);
-        if (!metaData) {
-          metaData = {};
-          this.__cachedPlacesMetaData.set(result.uri.spec, metaData);
-        }
-        metaData.targetFileSpec = result.annotationValue;
-      }
-    }
-
-    return this.__cachedPlacesMetaData;
-  },
-  __cachedPlacesMetaData: null,
-
-  /**
-   * Reads current metadata from Places annotations for the specified URI, and
-   * returns an object with the format:
-   *
-   * { targetFileSpec, state, endTime, fileSize, ... }
-   *
-   * The targetFileSpec property is the value of "downloads/destinationFileURI",
-   * while the other properties are taken from "downloads/metaData". Any of the
-   * properties may be missing from the object.
-   */
-  _getPlacesMetaDataFor(spec) {
-    let metaData = {};
-
-    try {
-      let uri = NetUtil.newURI(spec);
-      try {
-        metaData = JSON.parse(PlacesUtils.annotations.getPageAnnotation(
-                                          uri, DOWNLOAD_META_DATA_ANNO));
-      } catch (ex) {}
-      metaData.targetFileSpec = PlacesUtils.annotations.getPageAnnotation(
-                                            uri, DESTINATION_FILE_URI_ANNO);
-    } catch (ex) {}
-
-    return metaData;
-  },
-
-  /**
-   * Given a data item for a session download, or a places node for a past
-   * download, updates the view as necessary.
-   *  1. If the given data is a places node, we check whether there are any
-   *     elements for the same download url. If there are, then we just reset
-   *     their places node. Otherwise we add a new download element.
-   *  2. If the given data is a data item, we first check if there's a history
-   *     download in the list that is not associated with a data item. If we
-   *     found one, we use it for the data item as well and reposition it
-   *     alongside the other session downloads. If we don't, then we go ahead
-   *     and create a new element for the download.
-   *
-   * @param [optional] sessionDownload
-   *        A Download object, or null for history downloads.
-   * @param [optional] aPlacesNode
-   *        The Places node for a history download, or null for session downloads.
-   * @param [optional] aNewest
-   *        Whether the download should be added at the top of the list.
-   * @param [optional] aDocumentFragment
-   *        To speed up the appending of multiple elements to the end of the
-   *        list which are coming in a single batch (i.e. invalidateContainer),
-   *        a document fragment may be passed to which the new elements would
-   *        be appended. It's the caller's job to ensure the fragment is merged
-   *        to the richlistbox at the end.
-   */
-  _addDownloadData(sessionDownload, aPlacesNode, aNewest = false,
-                   aDocumentFragment = null) {
-    let downloadURI = aPlacesNode ? aPlacesNode.uri
-                                  : sessionDownload.source.url;
-    let shellsForURI = this._downloadElementsShellsForURI.get(downloadURI);
-    if (!shellsForURI) {
-      shellsForURI = new Set();
-      this._downloadElementsShellsForURI.set(downloadURI, shellsForURI);
-    }
-
-    // When a session download is attached to a shell, we ensure not to keep
-    // stale metadata around for the corresponding history download. This
-    // prevents stale state from being used if the view is rebuilt.
-    //
-    // Note that we will eagerly load the data in the cache at this point, even
-    // if we have seen no history download. The case where no history download
-    // will appear at all is rare enough in normal usage, so we can apply this
-    // simpler solution rather than keeping a list of cache items to ignore.
-    if (sessionDownload) {
-      this._cachedPlacesMetaData.delete(sessionDownload.source.url);
-    }
-
-    let newOrUpdatedShell = null;
-
-    // Trivial: if there are no shells for this download URI, we always
-    // need to create one.
-    let shouldCreateShell = shellsForURI.size == 0;
-
-    // However, if we do have shells for this download uri, there are
-    // few options:
-    // 1) There's only one shell and it's for a history download (it has
-    //    no data item). In this case, we update this shell and move it
-    //    if necessary
-    // 2) There are multiple shells, indicating multiple downloads for
-    //    the same download uri are running. In this case we create
-    //    another shell for the download (so we have one shell for each data
-    //    item).
-    //
-    // Note: If a cancelled session download is already in the list, and the
-    // download is retried, onDownloadAdded is called again for the same
-    // data item. Thus, we also check that we make sure we don't have a view item
-    // already.
-    if (!shouldCreateShell &&
-        sessionDownload && !this._viewItemsForDownloads.has(sessionDownload)) {
-      // If there's a past-download-only shell for this download-uri with no
-      // associated data item, use it for the new data item. Otherwise, go ahead
-      // and create another shell.
-      shouldCreateShell = true;
-      for (let shell of shellsForURI) {
-        if (!shell.sessionDownload) {
-          shouldCreateShell = false;
-          shell.sessionDownload = sessionDownload;
-          newOrUpdatedShell = shell;
-          this._viewItemsForDownloads.set(sessionDownload, shell);
-          break;
-        }
-      }
-    }
-
-    if (shouldCreateShell) {
-      // If we are adding a new history download here, it means there is no
-      // associated session download, thus we must read the Places metadata,
-      // because it will not be obscured by the session download.
-      let historyDownload = null;
-      if (aPlacesNode) {
-        let metaData = this._cachedPlacesMetaData.get(aPlacesNode.uri) ||
-                       this._getPlacesMetaDataFor(aPlacesNode.uri);
-        historyDownload = new HistoryDownload(aPlacesNode);
-        historyDownload.updateFromMetaData(metaData);
-      }
-      let shell = new HistoryDownloadElementShell(sessionDownload,
-                                                  historyDownload);
-      shell.element._placesNode = aPlacesNode;
-      newOrUpdatedShell = shell;
-      shellsForURI.add(shell);
-      if (sessionDownload) {
-        this._viewItemsForDownloads.set(sessionDownload, shell);
-      }
-    } else if (aPlacesNode) {
-      // We are updating information for a history download for which we have
-      // at least one download element shell already. There are two cases:
-      // 1) There are one or more download element shells for this source URI,
-      //    each with an associated session download. We update the Places node
-      //    because we may need it later, but we don't need to read the Places
-      //    metadata until the last session download is removed.
-      // 2) Occasionally, we may receive a duplicate notification for a history
-      //    download with no associated session download. We have exactly one
-      //    download element shell in this case, but the metdata cannot have
-      //    changed, just the reference to the Places node object is different.
-      // So, we update all the node references and keep the metadata intact.
-      for (let shell of shellsForURI) {
-        if (!shell.historyDownload) {
-          // Create the element to host the metadata when needed.
-          shell.historyDownload = new HistoryDownload(aPlacesNode);
-        }
-        shell.element._placesNode = aPlacesNode;
-      }
-    }
-
-    if (newOrUpdatedShell) {
-      if (aNewest) {
-        this._richlistbox.insertBefore(newOrUpdatedShell.element,
-                                       this._richlistbox.firstChild);
-        if (!this._lastSessionDownloadElement) {
-          this._lastSessionDownloadElement = newOrUpdatedShell.element;
-        }
-        // Some operations like retrying an history download move an element to
-        // the top of the richlistbox, along with other session downloads.
-        // More generally, if a new download is added, should be made visible.
-        this._richlistbox.ensureElementIsVisible(newOrUpdatedShell.element);
-      } else if (sessionDownload) {
-        let before = this._lastSessionDownloadElement ?
-          this._lastSessionDownloadElement.nextSibling : this._richlistbox.firstChild;
-        this._richlistbox.insertBefore(newOrUpdatedShell.element, before);
-        this._lastSessionDownloadElement = newOrUpdatedShell.element;
-      } else {
-        let appendTo = aDocumentFragment || this._richlistbox;
-        appendTo.appendChild(newOrUpdatedShell.element);
-      }
-
-      if (this.searchTerm) {
-        newOrUpdatedShell.element.hidden =
-          !newOrUpdatedShell.element._shell.matchesSearchTerm(this.searchTerm);
-      }
-    }
-
-    // If aDocumentFragment is defined this is a batch change, so it's up to
-    // the caller to append the fragment and activate the visible shells.
-    if (!aDocumentFragment) {
-      this._ensureVisibleElementsAreActive();
-      goUpdateCommand("downloadsCmd_clearDownloads");
-    }
-  },
-
-  _removeElement(aElement) {
-    // If the element was selected exclusively, select its next
-    // sibling first, if not, try for previous sibling, if any.
-    if ((aElement.nextSibling || aElement.previousSibling) &&
-        this._richlistbox.selectedItems &&
-        this._richlistbox.selectedItems.length == 1 &&
-        this._richlistbox.selectedItems[0] == aElement) {
-      this._richlistbox.selectItem(aElement.nextSibling ||
-                                   aElement.previousSibling);
-    }
-
-    if (this._lastSessionDownloadElement == aElement) {
-      this._lastSessionDownloadElement = aElement.previousSibling;
-    }
-
-    this._richlistbox.removeItemFromSelection(aElement);
-    this._richlistbox.removeChild(aElement);
-    this._ensureVisibleElementsAreActive();
-    goUpdateCommand("downloadsCmd_clearDownloads");
-  },
-
-  _removeHistoryDownloadFromView(aPlacesNode) {
-    let downloadURI = aPlacesNode.uri;
-    let shellsForURI = this._downloadElementsShellsForURI.get(downloadURI);
-    if (shellsForURI) {
-      for (let shell of shellsForURI) {
-        if (shell.sessionDownload) {
-          shell.historyDownload = null;
-        } else {
-          this._removeElement(shell.element);
-          shellsForURI.delete(shell);
-          if (shellsForURI.size == 0)
-            this._downloadElementsShellsForURI.delete(downloadURI);
-        }
-      }
-    }
-  },
-
-  _removeSessionDownloadFromView(download) {
-    let shells = this._downloadElementsShellsForURI
-                     .get(download.source.url);
-    if (shells.size == 0) {
-      throw new Error("Should have had at leaat one shell for this uri");
-    }
-
-    let shell = this._viewItemsForDownloads.get(download);
-    if (!shells.has(shell)) {
-      throw new Error("Missing download element shell in shells list for url");
-    }
-
-    // If there's more than one item for this download uri, we can let the
-    // view item for this this particular data item go away.
-    // If there's only one item for this download uri, we should only
-    // keep it if it is associated with a history download.
-    if (shells.size > 1 || !shell.historyDownload) {
-      this._removeElement(shell.element);
-      shells.delete(shell);
-      if (shells.size == 0) {
-        this._downloadElementsShellsForURI.delete(download.source.url);
-      }
-    } else {
-      // We have one download element shell containing both a session download
-      // and a history download, and we are now removing the session download.
-      // Previously, we did not use the Places metadata because it was obscured
-      // by the session download. Since this is no longer the case, we have to
-      // read the latest metadata before removing the session download.
-      let url = shell.historyDownload.source.url;
-      let metaData = this._getPlacesMetaDataFor(url);
-      shell.historyDownload.updateFromMetaData(metaData);
-      shell.sessionDownload = null;
-      // Move it below the session-download items;
-      if (this._lastSessionDownloadElement == shell.element) {
-        this._lastSessionDownloadElement = shell.element.previousSibling;
-      } else {
-        let before = this._lastSessionDownloadElement ?
-          this._lastSessionDownloadElement.nextSibling : this._richlistbox.firstChild;
-        this._richlistbox.insertBefore(shell.element, before);
-      }
-    }
-  },
-
   _ensureVisibleElementsAreActive() {
     if (!this.active || this._ensureVisibleTimer ||
         !this._richlistbox.firstChild) {
       return;
     }
 
     this._ensureVisibleTimer = setTimeout(() => {
       delete this._ensureVisibleTimer;
@@ -892,173 +362,38 @@ DownloadsPlacesView.prototype = {
     }, 10);
   },
 
   _place: "",
   get place() {
     return this._place;
   },
   set place(val) {
-    // Don't reload everything if we don't have to.
     if (this._place == val) {
       // XXXmano: places.js relies on this behavior (see Bug 822203).
       this.searchTerm = "";
-      return val;
-    }
-
-    this._place = val;
-
-    let history = PlacesUtils.history;
-    let queries = { }, options = { };
-    history.queryStringToQueries(val, queries, { }, options);
-    if (!queries.value.length) {
-      queries.value = [history.getNewQuery()];
+    } else {
+      this._place = val;
     }
-
-    let result = history.executeQueries(queries.value, queries.value.length,
-                                        options.value);
-    result.addObserver(this);
-    return val;
-  },
-
-  _result: null,
-  get result() {
-    return this._result;
-  },
-  set result(val) {
-    if (this._result == val) {
-      return val;
-    }
-
-    if (this._result) {
-      this._result.removeObserver(this);
-      this._resultNode.containerOpen = false;
-    }
-
-    if (val) {
-      this._result = val;
-      this._resultNode = val.root;
-      this._resultNode.containerOpen = true;
-      this._ensureInitialSelection();
-    } else {
-      delete this._resultNode;
-      delete this._result;
-    }
-
-    return val;
   },
 
   get selectedNodes() {
       return Array.filter(this._richlistbox.selectedItems,
-                          element => element._placesNode);
+                          element => element._shell.download.placesNode);
   },
 
   get selectedNode() {
     let selectedNodes = this.selectedNodes;
     return selectedNodes.length == 1 ? selectedNodes[0] : null;
   },
 
   get hasSelection() {
     return this.selectedNodes.length > 0;
   },
 
-  containerStateChanged(aNode, aOldState, aNewState) {
-    this.invalidateContainer(aNode)
-  },
-
-  invalidateContainer(aContainer) {
-    if (aContainer != this._resultNode) {
-      throw new Error("Unexpected container node");
-    }
-    if (!aContainer.containerOpen) {
-      throw new Error("Root container for the downloads query cannot be closed");
-    }
-
-    let suppressOnSelect = this._richlistbox.suppressOnSelect;
-    this._richlistbox.suppressOnSelect = true;
-    try {
-      // Remove the invalidated history downloads from the list and unset the
-      // places node for data downloads.
-      // Loop backwards since _removeHistoryDownloadFromView may removeChild().
-      for (let i = this._richlistbox.childNodes.length - 1; i >= 0; --i) {
-        let element = this._richlistbox.childNodes[i];
-        if (element._placesNode) {
-          this._removeHistoryDownloadFromView(element._placesNode);
-        }
-      }
-    } finally {
-      this._richlistbox.suppressOnSelect = suppressOnSelect;
-    }
-
-    if (aContainer.childCount > 0) {
-      let elementsToAppendFragment = document.createDocumentFragment();
-      for (let i = 0; i < aContainer.childCount; i++) {
-        try {
-          this._addDownloadData(null, aContainer.getChild(i), false,
-                                elementsToAppendFragment);
-        } catch (ex) {
-          Cu.reportError(ex);
-        }
-      }
-
-      // _addDownloadData may not add new elements if there were already
-      // data items in place.
-      if (elementsToAppendFragment.firstChild) {
-        this._appendDownloadsFragment(elementsToAppendFragment);
-        this._ensureVisibleElementsAreActive();
-      }
-    }
-
-    goUpdateDownloadCommands();
-  },
-
-  _appendDownloadsFragment(aDOMFragment) {
-    // Workaround multiple reflows hang by removing the richlistbox
-    // and adding it back when we're done.
-
-    // Hack for bug 836283: reset xbl fields to their old values after the
-    // binding is reattached to avoid breaking the selection state
-    let xblFields = new Map();
-    for (let key of Object.getOwnPropertyNames(this._richlistbox)) {
-      let value = this._richlistbox[key];
-      xblFields.set(key, value);
-    }
-
-    let parentNode = this._richlistbox.parentNode;
-    let nextSibling = this._richlistbox.nextSibling;
-    parentNode.removeChild(this._richlistbox);
-    this._richlistbox.appendChild(aDOMFragment);
-    parentNode.insertBefore(this._richlistbox, nextSibling);
-
-    for (let [key, value] of xblFields) {
-      this._richlistbox[key] = value;
-    }
-  },
-
-  nodeInserted(aParent, aPlacesNode) {
-    this._addDownloadData(null, aPlacesNode);
-  },
-
-  nodeRemoved(aParent, aPlacesNode, aOldIndex) {
-    this._removeHistoryDownloadFromView(aPlacesNode);
-  },
-
-  nodeAnnotationChanged() {},
-  nodeIconChanged() {},
-  nodeTitleChanged() {},
-  nodeKeywordChanged() {},
-  nodeDateAddedChanged() {},
-  nodeLastModifiedChanged() {},
-  nodeHistoryDetailsChanged() {},
-  nodeTagsChanged() {},
-  sortingChanged() {},
-  nodeMoved() {},
-  nodeURIChanged() {},
-  batching() {},
-
   get controller() {
     return this._richlistbox.controller;
   },
 
   get searchTerm() {
     return this._searchTerm;
   },
   set searchTerm(aValue) {
@@ -1100,30 +435,118 @@ DownloadsPlacesView.prototype = {
           this._richlistbox.selectedItem = firstDownloadElement;
           this._richlistbox.currentItem = firstDownloadElement;
           this._initiallySelectedElement = firstDownloadElement;
         });
       }
     }
   },
 
+  /**
+   * DocumentFragment object that contains all the new elements added during a
+   * batch operation, or null if no batch is in progress.
+   *
+   * Since newest downloads are displayed at the top, elements are normally
+   * prepended to the fragment, and then the fragment is prepended to the list.
+   */
+  batchFragment: null,
+
+  onDownloadBatchStarting() {
+    this.batchFragment = document.createDocumentFragment();
+
+    this.oldSuppressOnSelect = this._richlistbox.suppressOnSelect;
+    this._richlistbox.suppressOnSelect = true;
+  },
+
   onDownloadBatchEnded() {
+    this._richlistbox.suppressOnSelect = this.oldSuppressOnSelect;
+    delete this.oldSuppressOnSelect;
+
+    if (this.batchFragment.childElementCount) {
+      this._prependBatchFragment();
+    }
+    this.batchFragment = null;
+
     this._ensureInitialSelection();
+    this._ensureVisibleElementsAreActive();
+    goUpdateDownloadCommands();
   },
 
-  onDownloadAdded(download) {
-    this._addDownloadData(download, null, true);
+  _prependBatchFragment() {
+    // Workaround multiple reflows hang by removing the richlistbox
+    // and adding it back when we're done.
+
+    // Hack for bug 836283: reset xbl fields to their old values after the
+    // binding is reattached to avoid breaking the selection state
+    let xblFields = new Map();
+    for (let key of Object.getOwnPropertyNames(this._richlistbox)) {
+      let value = this._richlistbox[key];
+      xblFields.set(key, value);
+    }
+
+    let parentNode = this._richlistbox.parentNode;
+    let nextSibling = this._richlistbox.nextSibling;
+    parentNode.removeChild(this._richlistbox);
+    this._richlistbox.prepend(this.batchFragment);
+    parentNode.insertBefore(this._richlistbox, nextSibling);
+
+    for (let [key, value] of xblFields) {
+      this._richlistbox[key] = value;
+    }
+  },
+
+  onDownloadAdded(download, { insertBefore } = {}) {
+    let shell = new HistoryDownloadElementShell(download);
+    this._viewItemsForDownloads.set(download, shell);
+
+    // Since newest downloads are displayed at the top, either prepend the new
+    // element or insert it after the one indicated by the insertBefore option.
+    if (insertBefore) {
+      this._viewItemsForDownloads.get(insertBefore)
+          .element.insertAdjacentElement("afterend", shell.element);
+    } else {
+      (this.batchFragment || this._richlistbox).prepend(shell.element);
+    }
+
+    if (this.searchTerm) {
+      shell.element.hidden = !shell.matchesSearchTerm(this.searchTerm);
+    }
+
+    // Don't update commands and visible elements during a batch change.
+    if (!this.batchFragment) {
+      this._ensureVisibleElementsAreActive();
+      goUpdateCommand("downloadsCmd_clearDownloads");
+    }
   },
 
   onDownloadChanged(download) {
-    this._viewItemsForDownloads.get(download).onSessionDownloadChanged();
+    this._viewItemsForDownloads.get(download).onChanged();
   },
 
   onDownloadRemoved(download) {
-    this._removeSessionDownloadFromView(download);
+    let element = this._viewItemsForDownloads.get(download).element;
+
+    // If the element was selected exclusively, select its next
+    // sibling first, if not, try for previous sibling, if any.
+    if ((element.nextSibling || element.previousSibling) &&
+        this._richlistbox.selectedItems &&
+        this._richlistbox.selectedItems.length == 1 &&
+        this._richlistbox.selectedItems[0] == element) {
+      this._richlistbox.selectItem(element.nextSibling ||
+                                   element.previousSibling);
+    }
+
+    this._richlistbox.removeItemFromSelection(element);
+    element.remove();
+
+    // Don't update commands and visible elements during a batch change.
+    if (!this.batchFragment) {
+      this._ensureVisibleElementsAreActive();
+      goUpdateCommand("downloadsCmd_clearDownloads");
+    }
   },
 
   // nsIController
   supportsCommand(aCommand) {
     // Firstly, determine if this is a command that we can handle.
     if (!DownloadsViewUI.isCommandName(aCommand)) {
       return false;
     }
@@ -1255,17 +678,17 @@ DownloadsPlacesView.prototype = {
   },
 
   cmd_paste() {
     this._downloadURLFromClipboard();
   },
 
   downloadsCmd_clearDownloads() {
     this._downloadsData.removeFinished();
-    if (this.result) {
+    if (this._place) {
       Cc["@mozilla.org/browser/download-history;1"]
         .getService(Ci.nsIDownloadHistory)
         .removeAllDownloads();
     }
     // There may be no selection or focus change as a result
     // of these change, and we want the command updated immediately.
     goUpdateCommand("downloadsCmd_clearDownloads");
   },
--- a/browser/components/downloads/content/downloads.js
+++ b/browser/components/downloads/content/downloads.js
@@ -1090,17 +1090,16 @@ DownloadsViewItem.prototype = {
         if (!this.download.target.partFilePath) {
           return false;
         }
 
         let partFile = new FileUtils.File(this.download.target.partFilePath);
         return partFile.exists();
       }
       case "cmd_delete":
-      case "downloadsCmd_cancel":
       case "downloadsCmd_copyLocation":
       case "downloadsCmd_doDefault":
         return true;
       case "downloadsCmd_showBlockedInfo":
         return this.download.hasBlockedData;
     }
     return DownloadsViewUI.DownloadElementShell.prototype
                           .isCommandEnabled.call(this, aCommand);
@@ -1109,21 +1108,16 @@ DownloadsViewItem.prototype = {
   doCommand(aCommand) {
     if (this.isCommandEnabled(aCommand)) {
       this[aCommand]();
     }
   },
 
   // Item commands
 
-  cmd_delete() {
-    DownloadsCommon.removeAndFinalizeDownload(this.download);
-    PlacesUtils.history.remove(this.download.source.url).catch(Cu.reportError);
-  },
-
   downloadsCmd_unblock() {
     DownloadsPanel.hidePanel();
     this.confirmUnblock(window, "unblock");
   },
 
   downloadsCmd_chooseUnblock() {
     DownloadsPanel.hidePanel();
     this.confirmUnblock(window, "chooseUnblock");
--- a/browser/components/places/content/places.js
+++ b/browser/components/places/content/places.js
@@ -779,25 +779,25 @@ var PlacesSearchBox = {
     // XXX this might be to jumpy, maybe should search for "", so results
     // are ungrouped, and search box not reset
     if (filterString == "") {
       PO.onPlaceSelected(false);
       return;
     }
 
     let currentView = ContentArea.currentView;
-    let currentOptions = PO.getCurrentOptions();
 
     // Search according to the current scope, which was set by
     // PQB_setScope()
     switch (PlacesSearchBox.filterCollection) {
       case "bookmarks":
         currentView.applyFilter(filterString, this.folders);
         break;
       case "history": {
+        let currentOptions = PO.getCurrentOptions();
         if (currentOptions.queryType != Ci.nsINavHistoryQueryOptions.QUERY_TYPE_HISTORY) {
           let query = PlacesUtils.history.getNewQuery();
           query.searchTerms = filterString;
           let options = currentOptions.clone();
           // Make sure we're getting uri results.
           options.resultType = currentOptions.RESULTS_AS_URI;
           options.queryType = Ci.nsINavHistoryQueryOptions.QUERY_TYPE_HISTORY;
           options.includeHidden = true;
@@ -805,30 +805,18 @@ var PlacesSearchBox = {
         } else {
           TelemetryStopwatch.start(HISTORY_LIBRARY_SEARCH_TELEMETRY);
           currentView.applyFilter(filterString, null, true);
           TelemetryStopwatch.finish(HISTORY_LIBRARY_SEARCH_TELEMETRY);
         }
         break;
       }
       case "downloads": {
-        if (currentView == ContentTree.view) {
-          let query = PlacesUtils.history.getNewQuery();
-          query.searchTerms = filterString;
-          query.setTransitions([Ci.nsINavHistoryService.TRANSITION_DOWNLOAD], 1);
-          let options = currentOptions.clone();
-          // Make sure we're getting uri results.
-          options.resultType = currentOptions.RESULTS_AS_URI;
-          options.queryType = Ci.nsINavHistoryQueryOptions.QUERY_TYPE_HISTORY;
-          options.includeHidden = true;
-          currentView.load([query], options);
-        } else {
-          // The new downloads view doesn't use places for searching downloads.
-          currentView.searchTerm = filterString;
-        }
+        // The new downloads view doesn't use places for searching downloads.
+        currentView.searchTerm = filterString;
         break;
       }
       default:
         throw "Invalid filterCollection on search";
     }
 
     // Update the details panel
     PlacesOrganizer.updateDetailsPane();
--- a/browser/components/places/tests/browser/browser_library_downloads.js
+++ b/browser/components/places/tests/browser/browser_library_downloads.js
@@ -38,22 +38,21 @@ function test() {
       },
       handleCompletion() {
         // Make sure Downloads is present.
         isnot(win.PlacesOrganizer._places.selectedNode, null,
               "Downloads is present and selected");
 
 
         // Check results.
-        let contentRoot = win.ContentArea.currentView.result.root;
-        let len = contentRoot.childCount;
-        const TEST_URIS = ["http://ubuntu.org/", "http://google.com/"];
-        for (let i = 0; i < len; i++) {
-          is(contentRoot.getChild(i).uri, TEST_URIS[i],
-              "Comparing downloads shown at index " + i);
+        let testURIs = ["http://ubuntu.org/", "http://google.com/"];
+        for (let element of win.ContentArea.currentView
+                                           .associatedElement.children) {
+          is(element._shell.download.source.url, testURIs.shift(),
+             "URI matches");
         }
 
         win.close();
         PlacesTestUtils.clearHistory().then(finish);
       }
     })
   }
 
--- a/browser/installer/windows/nsis/defines.nsi.in
+++ b/browser/installer/windows/nsis/defines.nsi.in
@@ -116,19 +116,16 @@ VIAddVersionKey "ProductVersion"  "${App
 # It isn't possible to get the size of the installation prior to downloading
 # so the stub installer uses an estimate. The size is derived from the size of
 # the complete installer, the size of the extracted complete installer, and at
 # least 15 MB additional for working room.
 !define APPROXIMATE_REQUIRED_SPACE_MB "145"
 
 # Control positions in Dialog Units so they are placed correctly with
 # non-default DPI settings
-!define OPTIONS_ITEM_EDGE_DU 90u
-!define OPTIONS_ITEM_WIDTH_DU 356u
-!define OPTIONS_SUBITEM_EDGE_DU 119u
-!define OPTIONS_SUBITEM_WIDTH_DU 327u
+!define PROFILE_CLEANUP_LABEL_TOP_DU 39u
 !define NOW_INSTALLING_TOP_DU 70u
 !define INSTALL_BLURB_TOP_DU 137u
 !define INSTALL_FOOTER_TOP_DU -48u
 !define INSTALL_FOOTER_WIDTH_DU 250u
 !define PROGRESS_BAR_TOP_DU 112u
 !define APPNAME_BMP_EDGE_DU 19u
 !define APPNAME_BMP_TOP_DU 12u
--- a/browser/installer/windows/nsis/stub.nsi
+++ b/browser/installer/windows/nsis/stub.nsi
@@ -31,20 +31,22 @@ Var CheckboxSetAsDefault
 Var CheckboxShortcuts
 Var CheckboxSendPing
 Var CheckboxInstallMaintSvc
 Var DroplistArch
 Var LabelBlurb
 Var BgBitmapImage
 Var HwndBgBitmapControl
 Var CurrentBlurbIdx
+Var CheckboxCleanupProfile
 
 Var FontInstalling
 Var FontBlurb
 Var FontFooter
+Var FontCheckbox
 
 Var CanWriteToInstallDir
 Var HasRequiredSpaceAvailable
 Var IsDownloadFinished
 Var DownloadSizeBytes
 Var HalfOfDownload
 Var DownloadReset
 Var ExistingTopDir
@@ -84,22 +86,25 @@ Var ExistingVersion
 Var ExistingBuildID
 Var DownloadedBytes
 Var DownloadRetryCount
 Var OpenedDownloadPage
 Var DownloadServerIP
 Var PostSigningData
 Var PreviousInstallDir
 Var PreviousInstallArch
+Var ProfileCleanupPromptType
+Var ProfileCleanupHeaderString
+Var ProfileCleanupButtonString
 
 ; Uncomment the following to prevent pinging the metrics server when testing
 ; the stub installer
 ;!define STUB_DEBUG
 
-!define StubURLVersion "v7"
+!define StubURLVersion "v8"
 
 ; Successful install exit code
 !define ERR_SUCCESS 0
 
 /**
  * The following errors prefixed with ERR_DOWNLOAD apply to the download phase.
  */
 ; The download was cancelled by the user
@@ -279,16 +284,17 @@ ChangeUI all "nsisui.exe"
 !else
   LoadLanguageFile "locale.nlf"
 !endif
 
 !include "nsisstrings.nlf"
 
 Caption "$(INSTALLER_WIN_CAPTION)"
 
+Page custom createProfileCleanup
 Page custom createInstall ; Download / Installation page
 
 Function .onInit
   ; Remove the current exe directory from the search order.
   ; This only effects LoadLibrary calls and not implicitly loaded DLLs.
   System::Call 'kernel32::SetDllDirectoryW(w "")'
 
   StrCpy $LANGUAGE 0
@@ -423,16 +429,17 @@ Function .onInit
   StrCpy $OptionsPhaseSeconds "0"
   StrCpy $EndPreInstallPhaseTickCount "0"
   StrCpy $EndInstallPhaseTickCount "0"
   StrCpy $InitialInstallRequirementsCode ""
   StrCpy $IsDownloadFinished ""
   StrCpy $FirefoxLaunchCode "0"
   StrCpy $CheckboxShortcuts "1"
   StrCpy $CheckboxSendPing "1"
+  StrCpy $CheckboxCleanupProfile "0"
 !ifdef MOZ_MAINTENANCE_SERVICE
   ; We can only install the maintenance service if the user is an admin.
   Call IsUserAdmin
   Pop $0
   ${If} "$0" == "true"
     StrCpy $CheckboxInstallMaintSvc "1"
   ${Else}
     StrCpy $CheckboxInstallMaintSvc "0"
@@ -457,303 +464,21 @@ Function .onInit
 
   ${If} $0 == ""
     StrCpy $0 "$(^Font)"
   ${EndIf}
 
   CreateFont $FontInstalling "$0" "28" "400"
   CreateFont $FontBlurb      "$0" "15" "400"
   CreateFont $FontFooter     "$0" "13" "400"
+  CreateFont $FontCheckbox   "$0" "10" "400"
 
   InitPluginsDir
   File /oname=$PLUGINSDIR\bgstub.bmp "bgstub.bmp"
-FunctionEnd
 
-; .onGUIInit isn't needed except for RTL locales
-!ifdef ${AB_CD}_rtl
-Function .onGUIInit
-  ; Since NSIS RTL support doesn't mirror progress bars use Windows mirroring.
-  ${NSD_AddExStyle} $HWNDPARENT ${WS_EX_LAYOUTRTL}
-  ${RemoveExStyle} $HWNDPARENT ${WS_EX_RTLREADING}
-  ${RemoveExStyle} $HWNDPARENT ${WS_EX_RIGHT}
-  ${NSD_AddExStyle} $HWNDPARENT ${WS_EX_LEFT}|${WS_EX_LTRREADING}
-FunctionEnd
-!endif
-
-Function .onGUIEnd
-  Delete "$PLUGINSDIR\_temp"
-  Delete "$PLUGINSDIR\download.exe"
-  Delete "$PLUGINSDIR\${CONFIG_INI}"
-
-  ${UnloadUAC}
-FunctionEnd
-
-Function .onUserAbort
-  ${NSD_KillTimer} StartDownload
-  ${NSD_KillTimer} OnDownload
-  ${NSD_KillTimer} CheckInstall
-  ${NSD_KillTimer} FinishInstall
-  ${NSD_KillTimer} FinishProgressBar
-  ${NSD_KillTimer} DisplayDownloadError
-  ${NSD_KillTimer} NextBlurb
-  ${NSD_KillTimer} ClearBlurb
-
-  ${If} "$IsDownloadFinished" != ""
-    Call DisplayDownloadError
-    ; Aborting the abort will allow SendPing which is called by
-    ; DisplayDownloadError to hide the installer window and close the installer
-    ; after it sends the metrics ping.
-    Abort
-  ${EndIf}
-FunctionEnd
-
-Function SendPing
-  HideWindow
-  ; Try to send a ping if a download was attempted
-  ${If} $CheckboxSendPing == 1
-  ${AndIf} $IsDownloadFinished != ""
-    ; Get the tick count for the completion of all phases.
-    System::Call "kernel32::GetTickCount()l .s"
-    Pop $EndFinishPhaseTickCount
-
-    ; When the value of $IsDownloadFinished is false the download was started
-    ; but didn't finish. In this case the tick count stored in
-    ; $EndFinishPhaseTickCount is used to determine how long the download was
-    ; in progress.
-    ${If} "$IsDownloadFinished" == "false"
-    ${OrIf} "$EndDownloadPhaseTickCount" == ""
-      StrCpy $EndDownloadPhaseTickCount "$EndFinishPhaseTickCount"
-      ; Cancel the download in progress
-      InetBgDL::Get /RESET /END
-    ${EndIf}
-
-
-    ; When $DownloadFirstTransferSeconds equals an empty string the download
-    ; never successfully started so set the value to 0. It will be possible to
-    ; determine that the download didn't successfully start from the seconds for
-    ; the last download.
-    ${If} "$DownloadFirstTransferSeconds" == ""
-      StrCpy $DownloadFirstTransferSeconds "0"
-    ${EndIf}
-
-    ; When $StartLastDownloadTickCount equals an empty string the download never
-    ; successfully started so set the value to $EndDownloadPhaseTickCount to
-    ; compute the correct value.
-    ${If} $StartLastDownloadTickCount == ""
-      ; This could happen if the download never successfully starts
-      StrCpy $StartLastDownloadTickCount "$EndDownloadPhaseTickCount"
-    ${EndIf}
-
-    ; When $EndPreInstallPhaseTickCount equals 0 the installation phase was
-    ; never completed so set its value to $EndFinishPhaseTickCount to compute
-    ; the correct value.
-    ${If} "$EndPreInstallPhaseTickCount" == "0"
-      StrCpy $EndPreInstallPhaseTickCount "$EndFinishPhaseTickCount"
-    ${EndIf}
-
-    ; When $EndInstallPhaseTickCount equals 0 the installation phase was never
-    ; completed so set its value to $EndFinishPhaseTickCount to compute the
-    ; correct value.
-    ${If} "$EndInstallPhaseTickCount" == "0"
-      StrCpy $EndInstallPhaseTickCount "$EndFinishPhaseTickCount"
-    ${EndIf}
-
-    ; Get the seconds elapsed from the start of the download phase to the end of
-    ; the download phase.
-    ${GetSecondsElapsed} "$StartDownloadPhaseTickCount" "$EndDownloadPhaseTickCount" $0
-
-    ; Get the seconds elapsed from the start of the last download to the end of
-    ; the last download.
-    ${GetSecondsElapsed} "$StartLastDownloadTickCount" "$EndDownloadPhaseTickCount" $1
-
-    ; Get the seconds elapsed from the end of the download phase to the
-    ; completion of the pre-installation check phase.
-    ${GetSecondsElapsed} "$EndDownloadPhaseTickCount" "$EndPreInstallPhaseTickCount" $2
-
-    ; Get the seconds elapsed from the end of the pre-installation check phase
-    ; to the completion of the installation phase.
-    ${GetSecondsElapsed} "$EndPreInstallPhaseTickCount" "$EndInstallPhaseTickCount" $3
-
-    ; Get the seconds elapsed from the end of the installation phase to the
-    ; completion of all phases.
-    ${GetSecondsElapsed} "$EndInstallPhaseTickCount" "$EndFinishPhaseTickCount" $4
-
-    ${If} $DroplistArch == "$(VERSION_64BIT)"
-      StrCpy $R0 "1"
-    ${Else}
-      StrCpy $R0 "0"
-    ${EndIf}
-
-    ${If} ${RunningX64}
-      StrCpy $R1 "1"
-    ${Else}
-      StrCpy $R1 "0"
-    ${EndIf}
-
-    ; Though these values are sometimes incorrect due to bug 444664 it happens
-    ; so rarely it isn't worth working around it by reading the registry values.
-    ${WinVerGetMajor} $5
-    ${WinVerGetMinor} $6
-    ${WinVerGetBuild} $7
-    ${WinVerGetServicePackLevel} $8
-    ${If} ${IsServerOS}
-      StrCpy $9 "1"
-    ${Else}
-      StrCpy $9 "0"
-    ${EndIf}
-
-    ${If} "$ExitCode" == "${ERR_SUCCESS}"
-      ReadINIStr $R5 "$INSTDIR\application.ini" "App" "Version"
-      ReadINIStr $R6 "$INSTDIR\application.ini" "App" "BuildID"
-    ${Else}
-      StrCpy $R5 "0"
-      StrCpy $R6 "0"
-    ${EndIf}
-
-    ; Whether installed into the default installation directory
-    ${GetLongPath} "$INSTDIR" $R7
-    ${GetLongPath} "$InitialInstallDir" $R8
-    ${If} "$R7" == "$R8"
-      StrCpy $R7 "1"
-    ${Else}
-      StrCpy $R7 "0"
-    ${EndIf}
-
-    ClearErrors
-    WriteRegStr HKLM "Software\Mozilla" "${BrandShortName}InstallerTest" \
-                     "Write Test"
-    ${If} ${Errors}
-      StrCpy $R8 "0"
-    ${Else}
-      DeleteRegValue HKLM "Software\Mozilla" "${BrandShortName}InstallerTest"
-      StrCpy $R8 "1"
-    ${EndIf}
-
-    ${If} "$DownloadServerIP" == ""
-      StrCpy $DownloadServerIP "Unknown"
-    ${EndIf}
-
-    StrCpy $R2 ""
-    SetShellVarContext current ; Set SHCTX to the current user
-    ReadRegStr $R2 HKCU "Software\Classes\http\shell\open\command" ""
-    ${If} $R2 != ""
-      ${GetPathFromString} "$R2" $R2
-      ${GetParent} "$R2" $R3
-      ${GetLongPath} "$R3" $R3
-      ${If} $R3 == $INSTDIR
-        StrCpy $R2 "1" ; This Firefox install is set as default.
-      ${Else}
-        StrCpy $R2 "$R2" "" -11 # length of firefox.exe
-        ${If} "$R2" == "${FileMainEXE}"
-          StrCpy $R2 "2" ; Another Firefox install is set as default.
-        ${Else}
-          StrCpy $R2 "0"
-        ${EndIf}
-      ${EndIf}
-    ${Else}
-      StrCpy $R2 "0" ; Firefox is not set as default.
-    ${EndIf}
-
-    ${If} "$R2" == "0"
-      StrCpy $R3 ""
-      ReadRegStr $R2 HKLM "Software\Classes\http\shell\open\command" ""
-      ${If} $R2 != ""
-        ${GetPathFromString} "$R2" $R2
-        ${GetParent} "$R2" $R3
-        ${GetLongPath} "$R3" $R3
-        ${If} $R3 == $INSTDIR
-          StrCpy $R2 "1" ; This Firefox install is set as default.
-        ${Else}
-          StrCpy $R2 "$R2" "" -11 # length of firefox.exe
-          ${If} "$R2" == "${FileMainEXE}"
-            StrCpy $R2 "2" ; Another Firefox install is set as default.
-          ${Else}
-            StrCpy $R2 "0"
-          ${EndIf}
-        ${EndIf}
-      ${Else}
-        StrCpy $R2 "0" ; Firefox is not set as default.
-      ${EndIf}
-    ${EndIf}
-
-    ${If} $CanSetAsDefault == "true"
-      ${If} $CheckboxSetAsDefault == "1"
-        StrCpy $R3 "2"
-      ${Else}
-        StrCpy $R3 "3"
-      ${EndIf}
-    ${Else}
-      ${If} ${AtLeastWin8}
-        StrCpy $R3 "1"
-      ${Else}
-        StrCpy $R3 "0"
-      ${EndIf}
-    ${EndIf}
-
-!ifdef STUB_DEBUG
-    MessageBox MB_OK "${BaseURLStubPing} \
-                      $\nStub URL Version = ${StubURLVersion}${StubURLVersionAppend} \
-                      $\nBuild Channel = ${Channel} \
-                      $\nUpdate Channel = ${UpdateChannel} \
-                      $\nLocale = ${AB_CD} \
-                      $\nFirefox x64 = $R0 \
-                      $\nRunning x64 Windows = $R1 \
-                      $\nMajor = $5 \
-                      $\nMinor = $6 \
-                      $\nBuild = $7 \
-                      $\nServicePack = $8 \
-                      $\nIsServer = $9 \
-                      $\nExit Code = $ExitCode \
-                      $\nFirefox Launch Code = $FirefoxLaunchCode \
-                      $\nDownload Retry Count = $DownloadRetryCount \
-                      $\nDownloaded Bytes = $DownloadedBytes \
-                      $\nDownload Size Bytes = $DownloadSizeBytes \
-                      $\nIntroduction Phase Seconds = $IntroPhaseSeconds \
-                      $\nOptions Phase Seconds = $OptionsPhaseSeconds \
-                      $\nDownload Phase Seconds = $0 \
-                      $\nLast Download Seconds = $1 \
-                      $\nDownload First Transfer Seconds = $DownloadFirstTransferSeconds \
-                      $\nPreinstall Phase Seconds = $2 \
-                      $\nInstall Phase Seconds = $3 \
-                      $\nFinish Phase Seconds = $4 \
-                      $\nInitial Install Requirements Code = $InitialInstallRequirementsCode \
-                      $\nOpened Download Page = $OpenedDownloadPage \
-                      $\nExisting Profile = $ExistingProfile \
-                      $\nExisting Version = $ExistingVersion \
-                      $\nExisting Build ID = $ExistingBuildID \
-                      $\nNew Version = $R5 \
-                      $\nNew Build ID = $R6 \
-                      $\nDefault Install Dir = $R7 \
-                      $\nHas Admin = $R8 \
-                      $\nDefault Status = $R2 \
-                      $\nSet As Sefault Status = $R3 \
-                      $\nDownload Server IP = $DownloadServerIP \
-                      $\nPost-Signing Data = $PostSigningData"
-    ; The following will exit the installer
-    SetAutoClose true
-    StrCpy $R9 "2"
-    Call RelativeGotoPage
-!else
-    ${NSD_CreateTimer} OnPing ${DownloadIntervalMS}
-    InetBgDL::Get "${BaseURLStubPing}/${StubURLVersion}${StubURLVersionAppend}/${Channel}/${UpdateChannel}/${AB_CD}/$R0/$R1/$5/$6/$7/$8/$9/$ExitCode/$FirefoxLaunchCode/$DownloadRetryCount/$DownloadedBytes/$DownloadSizeBytes/$IntroPhaseSeconds/$OptionsPhaseSeconds/$0/$1/$DownloadFirstTransferSeconds/$2/$3/$4/$InitialInstallRequirementsCode/$OpenedDownloadPage/$ExistingProfile/$ExistingVersion/$ExistingBuildID/$R5/$R6/$R7/$R8/$R2/$R3/$DownloadServerIP/$PostSigningData" \
-                  "$PLUGINSDIR\_temp" /END
-!endif
-  ${Else}
-    ${If} "$IsDownloadFinished" == "false"
-      ; Cancel the download in progress
-      InetBgDL::Get /RESET /END
-    ${EndIf}
-    ; The following will exit the installer
-    SetAutoClose true
-    StrCpy $R9 "2"
-    Call RelativeGotoPage
-  ${EndIf}
-FunctionEnd
-
-Function createInstall
   SetShellVarContext all ; Set SHCTX to All Users
   ; If the user doesn't have write access to the installation directory set
   ; the installation directory to a subdirectory of the All Users application
   ; directory and if the user can't write to that location set the installation
   ; directory to a subdirectory of the users local application directory
   ; (e.g. non-roaming).
   Call CanWrite
   ${If} "$CanWriteToInstallDir" == "false"
@@ -796,17 +521,226 @@ Function createInstall
     Quit
   ${EndIf}
 
   Call CheckSpace
   ${If} "$HasRequiredSpaceAvailable" == "false"
     MessageBox MB_OK|MB_ICONEXCLAMATION "$(WARN_DISK_SPACE_QUIT)"
     Quit
   ${EndIf}
+FunctionEnd
 
+; .onGUIInit isn't needed except for RTL locales
+!ifdef ${AB_CD}_rtl
+Function .onGUIInit
+  ; Since NSIS RTL support doesn't mirror progress bars use Windows mirroring.
+  ${NSD_AddExStyle} $HWNDPARENT ${WS_EX_LAYOUTRTL}
+  ${RemoveExStyle} $HWNDPARENT ${WS_EX_RTLREADING}
+  ${RemoveExStyle} $HWNDPARENT ${WS_EX_RIGHT}
+  ${NSD_AddExStyle} $HWNDPARENT ${WS_EX_LEFT}|${WS_EX_LTRREADING}
+FunctionEnd
+!endif
+
+Function .onGUIEnd
+  Delete "$PLUGINSDIR\_temp"
+  Delete "$PLUGINSDIR\download.exe"
+  Delete "$PLUGINSDIR\${CONFIG_INI}"
+
+  ${UnloadUAC}
+FunctionEnd
+
+Function .onUserAbort
+  ${NSD_KillTimer} StartDownload
+  ${NSD_KillTimer} OnDownload
+  ${NSD_KillTimer} CheckInstall
+  ${NSD_KillTimer} FinishInstall
+  ${NSD_KillTimer} FinishProgressBar
+  ${NSD_KillTimer} DisplayDownloadError
+  ${NSD_KillTimer} NextBlurb
+  ${NSD_KillTimer} ClearBlurb
+
+  ${If} "$IsDownloadFinished" != ""
+    Call DisplayDownloadError
+  ${Else}
+    Call SendPing
+  ${EndIf}
+
+  ; Aborting the abort will allow SendPing which is called by
+  ; DisplayDownloadError to hide the installer window and close the installer
+  ; after it sends the metrics ping.
+  Abort
+FunctionEnd
+
+Function createProfileCleanup
+  Call ShouldPromptForProfileCleanup
+  ${Select} $ProfileCleanupPromptType
+  ${Case} 0
+    StrCpy $CheckboxCleanupProfile 0
+    Abort ; Skip this page
+  ${Case} 1
+    StrCpy $ProfileCleanupHeaderString $(STUB_CLEANUP_REINSTALL_HEADER)
+    StrCpy $ProfileCleanupButtonString $(STUB_CLEANUP_REINSTALL_BUTTON)
+  ${Case} 2
+    StrCpy $ProfileCleanupHeaderString $(STUB_CLEANUP_PAVEOVER_HEADER)
+    StrCpy $ProfileCleanupButtonString $(STUB_CLEANUP_PAVEOVER_BUTTON)
+  ${EndSelect}
+
+  nsDialogs::Create /NOUNLOAD 1018
+  Pop $Dialog
+
+  SetCtlColors $HWNDPARENT ${FOOTER_CONTROL_TEXT_COLOR_NORMAL} ${FOOTER_BKGRD_COLOR}
+
+  ; Since the text color for controls is set in this Dialog the foreground and
+  ; background colors of the Dialog must also be hardcoded.
+  SetCtlColors $Dialog ${COMMON_TEXT_COLOR_NORMAL} ${COMMON_BKGRD_COLOR}
+
+  FindWindow $7 "#32770" "" $HWNDPARENT
+  ${GetDlgItemWidthHeight} $HWNDPARENT $8 $9
+
+  ; Resize the Dialog to fill the entire window
+  System::Call 'user32::MoveWindow(i$Dialog,i0,i0,i $8,i $9,i0)'
+
+  GetDlgItem $0 $HWNDPARENT 1 ; Install button
+  ShowWindow $0 ${SW_HIDE}
+  EnableWindow $0 0
+
+  GetDlgItem $0 $HWNDPARENT 3 ; Back button
+  ShowWindow $0 ${SW_HIDE}
+  EnableWindow $0 0
+
+  GetDlgItem $0 $HWNDPARENT 2 ; Cancel button
+  ; Hide the Cancel button, but don't disable it (or else it won't be possible
+  ; to close the window)
+  ShowWindow $0 ${SW_HIDE}
+
+  GetDlgItem $0 $HWNDPARENT 10 ; Default browser checkbox
+  ; Hiding and then disabling allows Esc to still exit the installer
+  ShowWindow $0 ${SW_HIDE}
+  EnableWindow $0 0
+
+  GetDlgItem $0 $HWNDPARENT 11 ; Footer text
+  ShowWindow $0 ${SW_HIDE}
+  EnableWindow $0 0
+
+  ${GetDlgItemWidthHeight} $HWNDPARENT $R1 $R2
+  ${GetTextWidthHeight} $ProfileCleanupHeaderString $FontInstalling $R1 $R1 $R2
+  ${NSD_CreateLabelCenter} 0 ${PROFILE_CLEANUP_LABEL_TOP_DU} 100% $R2 \
+    $ProfileCleanupHeaderString
+  Pop $0
+  SendMessage $0 ${WM_SETFONT} $FontInstalling 0
+  SetCtlColors $0 ${INSTALL_BLURB_TEXT_COLOR} transparent
+
+  ${GetDlgItemBottomDU} $Dialog $0 $1
+  IntOp $1 $1 + 10 ; add a bit of padding between the header and the button
+  ${GetTextExtent} $ProfileCleanupButtonString $FontFooter $R1 $R2
+  ; Add some padding to both dimensions of the button.
+  IntOp $R1 $R1 + 100
+  IntOp $R2 $R2 + 10
+  ; Now that we know the size and the Y coordinate for the button, we can find
+  ; the correct X coordinate to get it properly centered.
+  ${GetDlgItemWidthHeight} $HWNDPARENT $R3 $R4
+  IntOp $R5 $R1 / 2
+  IntOp $R3 $R3 / 2
+  IntOp $R3 $R3 - $R5
+  ; We need a custom button because the default ones get drawn underneath the
+  ; background image we're about to insert.
+  ${NSD_CreateButton} $R3 $1 $R1 $R2 $ProfileCleanupButtonString
+  Pop $0
+  SendMessage $0 ${WM_SETFONT} $FontFooter 0
+  ${NSD_OnClick} $0 gotoInstallPage
+  ${NSD_SetFocus} $0
+
+  ; For the checkbox, first we need to know the width of the checkbox itself,
+  ; since it can vary with the display scaling and the theme.
+  System::Call 'User32::GetSystemMetrics(i 71) i .r1' ; 71 == SM_CXMENUCHECK
+  ; Now get the width of the label test, if it were all on one line.
+  ${GetTextExtent} $(STUB_CLEANUP_CHECKBOX_LABEL) $FontCheckbox $R1 $R2
+  ${GetDlgItemWidthHeight} $HWNDPARENT $R3 $R4
+  ; Add the checkbox width to the text width, then figure out how many lines
+  ; we're going to need in order to display that text in our dialog.
+  IntOp $R1 $R1 + $1
+  IntOp $R1 $R1 + 5
+  StrCpy $R5 $R1
+  StrCpy $R6 $R2
+  IntOp $R3 $R3 - 150 ; leave some padding on the sides of the dialog
+  ${While} $R1 > $R3
+    StrCpy $R5 $R3
+    IntOp $R2 $R2 + $R6
+    IntOp $R1 $R1 - $R3
+  ${EndWhile}
+  ${GetDlgItemBottomDU} $Dialog $0 $1
+  ; Now that we know the size for the checkbox, center it in the dialog.
+  ${GetDlgItemWidthHeight} $HWNDPARENT $R3 $R4
+  IntOp $R6 $R5 / 2
+  IntOp $R3 $R3 / 2
+  IntOp $R3 $R3 - $R6
+  IntOp $1 $1 + 20 ; add a bit of padding between the button and the checkbox
+  ${NSD_CreateCheckbox} $R3 $1 $R5 $R2 $(STUB_CLEANUP_CHECKBOX_LABEL)
+  Pop $CheckboxCleanupProfile
+  SendMessage $CheckboxCleanupProfile ${WM_SETFONT} $FontCheckbox 0
+  ; The uxtheme must be disabled on checkboxes in order to override the system
+  ; colors and have a transparent background.
+  System::Call 'uxtheme::SetWindowTheme(i $CheckboxCleanupProfile, w " ", w " ")'
+  SetCtlColors $CheckboxCleanupProfile ${INSTALL_BLURB_TEXT_COLOR} transparent
+  ; Setting the background color to transparent isn't enough to actually make a
+  ; checkbox background transparent, you also have to set the right style.
+  ${NSD_AddExStyle} $CheckboxCleanupProfile ${WS_EX_TRANSPARENT}
+  ; For some reason, clicking on the checkbox causes its text to be redrawn
+  ; one pixel to the side of where it was, but without clearing away the
+  ; existing text first, so it looks like the weight increases when you click.
+  ; Hack around this by manually hiding and then re-showing the textbox when
+  ; it gets clicked on.
+  ${NSD_OnClick} $CheckboxCleanupProfile RedrawWindow
+  ${NSD_Check} $CheckboxCleanupProfile
+
+  ${GetTextWidthHeight} "$(STUB_BLURB_FOOTER2)" $FontFooter \
+    ${INSTALL_FOOTER_WIDTH_DU} $R1 $R2
+  !ifdef ${AB_CD}_rtl
+    nsDialogs::CreateControl STATIC ${DEFAULT_STYLES}|${SS_NOTIFY} \
+      ${WS_EX_TRANSPARENT} 30u ${INSTALL_FOOTER_TOP_DU} ${INSTALL_FOOTER_WIDTH_DU} \
+       "$R2u" "$(STUB_BLURB_FOOTER2)"
+  !else
+    nsDialogs::CreateControl STATIC ${DEFAULT_STYLES}|${SS_NOTIFY}|${SS_RIGHT} \
+      ${WS_EX_TRANSPARENT} 175u ${INSTALL_FOOTER_TOP_DU} ${INSTALL_FOOTER_WIDTH_DU} \
+      "$R2u" "$(STUB_BLURB_FOOTER2)"
+  !endif
+  Pop $0
+  SendMessage $0 ${WM_SETFONT} $FontFooter 0
+  SetCtlColors $0 ${INSTALL_BLURB_TEXT_COLOR} transparent
+
+  ${NSD_CreateBitmap} 0 0 100% 100% ""
+  Pop $HwndBgBitmapControl
+  ${NSD_SetStretchedImage} $HwndBgBitmapControl $PLUGINSDIR\bgstub.bmp $BgBitmapImage
+  ; transparent bg on control prevents flicker on redraw
+  SetCtlColors $HwndBgBitmapControl ${INSTALL_BLURB_TEXT_COLOR} transparent
+
+  LockWindow off
+  nsDialogs::Show
+
+  ${NSD_FreeImage} $BgBitmapImage
+FunctionEnd
+
+Function RedrawWindow
+  Pop $0
+  ShowWindow $0 ${SW_HIDE}
+  ShowWindow $0 ${SW_SHOW}
+FunctionEnd
+
+Function gotoInstallPage
+  ; Eat the parameter that NSD_OnClick always passes but that we don't need.
+  Pop $0
+
+  ; Save the state of the checkbox before it's destroyed.
+  ${NSD_GetState} $CheckboxCleanupProfile $CheckboxCleanupProfile
+
+  StrCpy $R9 1
+  Call RelativeGotoPage
+FunctionEnd
+
+Function createInstall
   ; Begin setting up the download/install window
 
   nsDialogs::Create /NOUNLOAD 1018
   Pop $Dialog
 
   SetCtlColors $HWNDPARENT ${FOOTER_CONTROL_TEXT_COLOR_NORMAL} ${FOOTER_BKGRD_COLOR}
 
   ; Since the text color for controls is set in this Dialog the foreground and
@@ -828,18 +762,16 @@ Function createInstall
 
   ${NSD_CreateLabelCenter} 0% ${INSTALL_BLURB_TOP_DU} 100% 60u "$(STUB_BLURB1)"
   Pop $LabelBlurb
   SendMessage $LabelBlurb ${WM_SETFONT} $FontBlurb 0
   SetCtlColors $LabelBlurb ${INSTALL_BLURB_TEXT_COLOR} transparent
 
   StrCpy $CurrentBlurbIdx "0"
 
-  ; In some locales, the footer message may be too long to fit on one line.
-  ; Figure out how much height it needs and give it that much.
   ${GetTextWidthHeight} "$(STUB_BLURB_FOOTER2)" $FontFooter \
     ${INSTALL_FOOTER_WIDTH_DU} $R1 $R2
   !ifdef ${AB_CD}_rtl
     nsDialogs::CreateControl STATIC ${DEFAULT_STYLES}|${SS_NOTIFY} \
       ${WS_EX_TRANSPARENT} 30u ${INSTALL_FOOTER_TOP_DU} ${INSTALL_FOOTER_WIDTH_DU} "$R2u" \
       "$(STUB_BLURB_FOOTER2)"
   !else
     nsDialogs::CreateControl STATIC ${DEFAULT_STYLES}|${SS_NOTIFY}|${SS_RIGHT} \
@@ -861,17 +793,17 @@ Function createInstall
   ${NSD_SetStretchedImage} $HwndBgBitmapControl $PLUGINSDIR\bgstub.bmp $BgBitmapImage
   ; transparent bg on control prevents flicker on redraw
   SetCtlColors $HwndBgBitmapControl ${INSTALL_BLURB_TEXT_COLOR} transparent
 
   GetDlgItem $0 $HWNDPARENT 1 ; Install button
   EnableWindow $0 0
   ShowWindow $0 ${SW_HIDE}
 
-  GetDlgItem $0 $HWNDPARENT 3 ; Back button used for Options
+  GetDlgItem $0 $HWNDPARENT 3 ; Back button
   EnableWindow $0 0
   ShowWindow $0 ${SW_HIDE}
 
   GetDlgItem $0 $HWNDPARENT 2 ; Cancel button
   ; Focus the Cancel button otherwise it isn't possible to tab to it since it is
   ; the only control that can be tabbed to.
   ${NSD_SetFocus} $0
   ; Kill the Cancel button's focus so pressing enter won't cancel the install.
@@ -1252,16 +1184,259 @@ Function OnDownload
       ${EndIf}
       StrCpy $DownloadedBytes "$3"
       StrCpy $ProgressCompleted "$DownloadedBytes"
       Call SetProgressBars
     ${EndIf}
   ${EndIf}
 FunctionEnd
 
+Function SendPing
+  HideWindow
+  ${If} $CheckboxSendPing == 1
+    ; Get the tick count for the completion of all phases.
+    System::Call "kernel32::GetTickCount()l .s"
+    Pop $EndFinishPhaseTickCount
+
+    ; When the value of $IsDownloadFinished is false the download was started
+    ; but didn't finish. In this case the tick count stored in
+    ; $EndFinishPhaseTickCount is used to determine how long the download was
+    ; in progress.
+    ${If} "$IsDownloadFinished" == "false"
+    ${OrIf} "$EndDownloadPhaseTickCount" == ""
+      StrCpy $EndDownloadPhaseTickCount "$EndFinishPhaseTickCount"
+      ; Cancel the download in progress
+      InetBgDL::Get /RESET /END
+    ${EndIf}
+
+
+    ; When $DownloadFirstTransferSeconds equals an empty string the download
+    ; never successfully started so set the value to 0. It will be possible to
+    ; determine that the download didn't successfully start from the seconds for
+    ; the last download.
+    ${If} "$DownloadFirstTransferSeconds" == ""
+      StrCpy $DownloadFirstTransferSeconds "0"
+    ${EndIf}
+
+    ; When $StartLastDownloadTickCount equals an empty string the download never
+    ; successfully started so set the value to $EndDownloadPhaseTickCount to
+    ; compute the correct value.
+    ${If} $StartLastDownloadTickCount == ""
+      ; This could happen if the download never successfully starts
+      StrCpy $StartLastDownloadTickCount "$EndDownloadPhaseTickCount"
+    ${EndIf}
+
+    ; When $EndPreInstallPhaseTickCount equals 0 the installation phase was
+    ; never completed so set its value to $EndFinishPhaseTickCount to compute
+    ; the correct value.
+    ${If} "$EndPreInstallPhaseTickCount" == "0"
+      StrCpy $EndPreInstallPhaseTickCount "$EndFinishPhaseTickCount"
+    ${EndIf}
+
+    ; When $EndInstallPhaseTickCount equals 0 the installation phase was never
+    ; completed so set its value to $EndFinishPhaseTickCount to compute the
+    ; correct value.
+    ${If} "$EndInstallPhaseTickCount" == "0"
+      StrCpy $EndInstallPhaseTickCount "$EndFinishPhaseTickCount"
+    ${EndIf}
+
+    ; Get the seconds elapsed from the start of the download phase to the end of
+    ; the download phase.
+    ${GetSecondsElapsed} "$StartDownloadPhaseTickCount" "$EndDownloadPhaseTickCount" $0
+
+    ; Get the seconds elapsed from the start of the last download to the end of
+    ; the last download.
+    ${GetSecondsElapsed} "$StartLastDownloadTickCount" "$EndDownloadPhaseTickCount" $1
+
+    ; Get the seconds elapsed from the end of the download phase to the
+    ; completion of the pre-installation check phase.
+    ${GetSecondsElapsed} "$EndDownloadPhaseTickCount" "$EndPreInstallPhaseTickCount" $2
+
+    ; Get the seconds elapsed from the end of the pre-installation check phase
+    ; to the completion of the installation phase.
+    ${GetSecondsElapsed} "$EndPreInstallPhaseTickCount" "$EndInstallPhaseTickCount" $3
+
+    ; Get the seconds elapsed from the end of the installation phase to the
+    ; completion of all phases.
+    ${GetSecondsElapsed} "$EndInstallPhaseTickCount" "$EndFinishPhaseTickCount" $4
+
+    ${If} $DroplistArch == "$(VERSION_64BIT)"
+      StrCpy $R0 "1"
+    ${Else}
+      StrCpy $R0 "0"
+    ${EndIf}
+
+    ${If} ${RunningX64}
+      StrCpy $R1 "1"
+    ${Else}
+      StrCpy $R1 "0"
+    ${EndIf}
+
+    ; Though these values are sometimes incorrect due to bug 444664 it happens
+    ; so rarely it isn't worth working around it by reading the registry values.
+    ${WinVerGetMajor} $5
+    ${WinVerGetMinor} $6
+    ${WinVerGetBuild} $7
+    ${WinVerGetServicePackLevel} $8
+    ${If} ${IsServerOS}
+      StrCpy $9 "1"
+    ${Else}
+      StrCpy $9 "0"
+    ${EndIf}
+
+    ${If} "$ExitCode" == "${ERR_SUCCESS}"
+      ReadINIStr $R5 "$INSTDIR\application.ini" "App" "Version"
+      ReadINIStr $R6 "$INSTDIR\application.ini" "App" "BuildID"
+    ${Else}
+      StrCpy $R5 "0"
+      StrCpy $R6 "0"
+    ${EndIf}
+
+    ; Whether installed into the default installation directory
+    ${GetLongPath} "$INSTDIR" $R7
+    ${GetLongPath} "$InitialInstallDir" $R8
+    ${If} "$R7" == "$R8"
+      StrCpy $R7 "1"
+    ${Else}
+      StrCpy $R7 "0"
+    ${EndIf}
+
+    ClearErrors
+    WriteRegStr HKLM "Software\Mozilla" "${BrandShortName}InstallerTest" \
+                     "Write Test"
+    ${If} ${Errors}
+      StrCpy $R8 "0"
+    ${Else}
+      DeleteRegValue HKLM "Software\Mozilla" "${BrandShortName}InstallerTest"
+      StrCpy $R8 "1"
+    ${EndIf}
+
+    ${If} "$DownloadServerIP" == ""
+      StrCpy $DownloadServerIP "Unknown"
+    ${EndIf}
+
+    StrCpy $R2 ""
+    SetShellVarContext current ; Set SHCTX to the current user
+    ReadRegStr $R2 HKCU "Software\Classes\http\shell\open\command" ""
+    ${If} $R2 != ""
+      ${GetPathFromString} "$R2" $R2
+      ${GetParent} "$R2" $R3
+      ${GetLongPath} "$R3" $R3
+      ${If} $R3 == $INSTDIR
+        StrCpy $R2 "1" ; This Firefox install is set as default.
+      ${Else}
+        StrCpy $R2 "$R2" "" -11 # length of firefox.exe
+        ${If} "$R2" == "${FileMainEXE}"
+          StrCpy $R2 "2" ; Another Firefox install is set as default.
+        ${Else}
+          StrCpy $R2 "0"
+        ${EndIf}
+      ${EndIf}
+    ${Else}
+      StrCpy $R2 "0" ; Firefox is not set as default.
+    ${EndIf}
+
+    ${If} "$R2" == "0"
+      StrCpy $R3 ""
+      ReadRegStr $R2 HKLM "Software\Classes\http\shell\open\command" ""
+      ${If} $R2 != ""
+        ${GetPathFromString} "$R2" $R2
+        ${GetParent} "$R2" $R3
+        ${GetLongPath} "$R3" $R3
+        ${If} $R3 == $INSTDIR
+          StrCpy $R2 "1" ; This Firefox install is set as default.
+        ${Else}
+          StrCpy $R2 "$R2" "" -11 # length of firefox.exe
+          ${If} "$R2" == "${FileMainEXE}"
+            StrCpy $R2 "2" ; Another Firefox install is set as default.
+          ${Else}
+            StrCpy $R2 "0"
+          ${EndIf}
+        ${EndIf}
+      ${Else}
+        StrCpy $R2 "0" ; Firefox is not set as default.
+      ${EndIf}
+    ${EndIf}
+
+    ${If} $CanSetAsDefault == "true"
+      ${If} $CheckboxSetAsDefault == "1"
+        StrCpy $R3 "2"
+      ${Else}
+        StrCpy $R3 "3"
+      ${EndIf}
+    ${Else}
+      ${If} ${AtLeastWin8}
+        StrCpy $R3 "1"
+      ${Else}
+        StrCpy $R3 "0"
+      ${EndIf}
+    ${EndIf}
+
+!ifdef STUB_DEBUG
+    MessageBox MB_OK "${BaseURLStubPing} \
+                      $\nStub URL Version = ${StubURLVersion}${StubURLVersionAppend} \
+                      $\nBuild Channel = ${Channel} \
+                      $\nUpdate Channel = ${UpdateChannel} \
+                      $\nLocale = ${AB_CD} \
+                      $\nFirefox x64 = $R0 \
+                      $\nRunning x64 Windows = $R1 \
+                      $\nMajor = $5 \
+                      $\nMinor = $6 \
+                      $\nBuild = $7 \
+                      $\nServicePack = $8 \
+                      $\nIsServer = $9 \
+                      $\nExit Code = $ExitCode \
+                      $\nFirefox Launch Code = $FirefoxLaunchCode \
+                      $\nDownload Retry Count = $DownloadRetryCount \
+                      $\nDownloaded Bytes = $DownloadedBytes \
+                      $\nDownload Size Bytes = $DownloadSizeBytes \
+                      $\nIntroduction Phase Seconds = $IntroPhaseSeconds \
+                      $\nOptions Phase Seconds = $OptionsPhaseSeconds \
+                      $\nDownload Phase Seconds = $0 \
+                      $\nLast Download Seconds = $1 \
+                      $\nDownload First Transfer Seconds = $DownloadFirstTransferSeconds \
+                      $\nPreinstall Phase Seconds = $2 \
+                      $\nInstall Phase Seconds = $3 \
+                      $\nFinish Phase Seconds = $4 \
+                      $\nInitial Install Requirements Code = $InitialInstallRequirementsCode \
+                      $\nOpened Download Page = $OpenedDownloadPage \
+                      $\nExisting Profile = $ExistingProfile \
+                      $\nExisting Version = $ExistingVersion \
+                      $\nExisting Build ID = $ExistingBuildID \
+                      $\nNew Version = $R5 \
+                      $\nNew Build ID = $R6 \
+                      $\nDefault Install Dir = $R7 \
+                      $\nHas Admin = $R8 \
+                      $\nDefault Status = $R2 \
+                      $\nSet As Sefault Status = $R3 \
+                      $\nDownload Server IP = $DownloadServerIP \
+                      $\nPost-Signing Data = $PostSigningData \
+                      $\nProfile cleanup prompt shown = $ProfileCleanupPromptType \
+                      $\nDid profile cleanup = $CheckboxCleanupProfile"
+    ; The following will exit the installer
+    SetAutoClose true
+    StrCpy $R9 "2"
+    Call RelativeGotoPage
+!else
+    ${NSD_CreateTimer} OnPing ${DownloadIntervalMS}
+    InetBgDL::Get "${BaseURLStubPing}/${StubURLVersion}${StubURLVersionAppend}/${Channel}/${UpdateChannel}/${AB_CD}/$R0/$R1/$5/$6/$7/$8/$9/$ExitCode/$FirefoxLaunchCode/$DownloadRetryCount/$DownloadedBytes/$DownloadSizeBytes/$IntroPhaseSeconds/$OptionsPhaseSeconds/$0/$1/$DownloadFirstTransferSeconds/$2/$3/$4/$InitialInstallRequirementsCode/$OpenedDownloadPage/$ExistingProfile/$ExistingVersion/$ExistingBuildID/$R5/$R6/$R7/$R8/$R2/$R3/$DownloadServerIP/$PostSigningData/$ProfileCleanupPromptType/$CheckboxCleanupProfile" \
+                  "$PLUGINSDIR\_temp" /END
+!endif
+  ${Else}
+    ${If} "$IsDownloadFinished" == "false"
+      ; Cancel the download in progress
+      InetBgDL::Get /RESET /END
+    ${EndIf}
+    ; The following will exit the installer
+    SetAutoClose true
+    StrCpy $R9 "2"
+    Call RelativeGotoPage
+  ${EndIf}
+FunctionEnd
+
 Function OnPing
   InetBgDL::GetStats
   # $0 = HTTP status code, 0=Completed
   # $1 = Completed files
   # $2 = Remaining files
   # $3 = Number of downloaded bytes for the current file
   # $4 = Size of current file (Empty string if the size is unknown)
   # /RESET must be used if status $0 > 299 (e.g. failure)
@@ -1470,27 +1645,36 @@ Function LaunchApp
   StrCpy $FirefoxLaunchCode "2"
 
   ; Set the current working directory to the installation directory
   SetOutPath "$INSTDIR"
   ClearErrors
   ${GetParameters} $0
   ${GetOptions} "$0" "/UAC:" $1
   ${If} ${Errors}
-    Exec "$\"$INSTDIR\${FileMainEXE}$\""
+    ${If} $CheckboxCleanupProfile == 1
+      Exec "$\"$INSTDIR\${FileMainEXE}$\" -reset-profile -migration"
+    ${Else}
+      Exec "$\"$INSTDIR\${FileMainEXE}$\""
+    ${EndIf}
   ${Else}
+    StrCpy $R1 $CheckboxCleanupProfile
     GetFunctionAddress $0 LaunchAppFromElevatedProcess
     UAC::ExecCodeSegment $0
   ${EndIf}
 FunctionEnd
 
 Function LaunchAppFromElevatedProcess
   ; Set the current working directory to the installation directory
   SetOutPath "$INSTDIR"
-  Exec "$\"$INSTDIR\${FileMainEXE}$\""
+  ${If} $R1 == 1
+    Exec "$\"$INSTDIR\${FileMainEXE}$\" -reset-profile -migration"
+  ${Else}
+    Exec "$\"$INSTDIR\${FileMainEXE}$\""
+  ${EndIf}
 FunctionEnd
 
 Function CopyPostSigningData
   ${LineRead} "$EXEDIR\postSigningData" "1" $PostSigningData
   ${If} ${Errors}
     ClearErrors
     StrCpy $PostSigningData "0"
   ${Else}
@@ -1524,10 +1708,168 @@ Function DisplayDownloadError
 
   Call SendPing
 FunctionEnd
 
 Function OpenManualDownloadURL
   ExecShell "open" "${URLManualDownload}${URLManualDownloadAppend}"
 FunctionEnd
 
+Function ShouldPromptForProfileCleanup
+  Call GetLatestReleasedVersion
+
+  ; This will be our return value.
+  StrCpy $ProfileCleanupPromptType 0
+
+  ; Only consider installations of the same architecture we're installing.
+  ${If} $DroplistArch == "$(VERSION_64BIT)"
+    SetRegView 64
+  ${Else}
+    SetRegView 32
+  ${EndIf}
+
+  ; Make sure $APPDATA is the user's AppData and not ProgramData.
+  ; We'll set this back to all at the end of the function.
+  SetShellVarContext current
+
+  ; Check each Profile section in profiles.ini until we find the default profile.
+  StrCpy $R0 ""
+  ${If} ${FileExists} "$APPDATA\Mozilla\Firefox\profiles.ini"
+    StrCpy $0 0
+    ${Do}
+      ClearErrors
+      ; Check if the section exists by reading a value that must be present.
+      ReadINIStr $1 "$APPDATA\Mozilla\Firefox\profiles.ini" "Profile$0" "Path"
+      ${If} ${Errors}
+        ; We've run out of profile sections.
+        ${Break}
+      ${EndIf}
+
+      ClearErrors
+      ReadINIStr $1 "$APPDATA\Mozilla\Firefox\profiles.ini" "Profile$0" "Default"
+      ${IfNot} ${Errors}
+      ${AndIf} $1 == "1"
+        ; We've found the default profile
+        ReadINIStr $1 "$APPDATA\Mozilla\Firefox\profiles.ini" "Profile$0" "Path"
+        ReadINIStr $2 "$APPDATA\Mozilla\Firefox\profiles.ini" "Profile$0" "IsRelative"
+        ${If} $2 == "1"
+          StrCpy $R0 "$APPDATA\Mozilla\Firefox\$1"
+        ${Else}
+          StrCpy $R0 "$1"
+        ${EndIf}
+        GetFullPathName $R0 $R0
+        ${Break}
+      ${EndIf}
+
+      IntOp $0 $0 + 1
+    ${Loop}
+  ${EndIf}
+
+  ${If} $R0 == ""
+    ; No profile to clean up, so don't show the cleanup prompt.
+    GoTo end
+  ${EndIf}
+
+  ; We have at least one profile present. If we don't have any installations,
+  ; then we need to show the re-install prompt. We'll say there's an
+  ; installation present if HKCR\FirefoxURL* exists and points to a real path.
+  StrCpy $0 0
+  StrCpy $R9 ""
+  ${Do}
+    ClearErrors
+    EnumRegKey $1 HKCR "" $0
+    ${If} ${Errors}
+    ${OrIf} $1 == ""
+      ${Break}
+    ${EndIf}
+    ${WordFind} "$1" "-" "+1{" $2
+    ${If} $2 == "FirefoxURL"
+      ClearErrors
+      ReadRegStr $2 HKCR "$1\DefaultIcon" ""
+      ${IfNot} ${Errors}
+        ${GetPathFromString} $2 $1
+        ${If} ${FileExists} $1
+          StrCpy $R9 $1
+          ${Break}
+        ${EndIf}
+      ${EndIf}
+    ${EndIf}
+    IntOp $0 $0 + 1
+  ${Loop}
+  ${If} $R9 == ""
+    StrCpy $ProfileCleanupPromptType 1
+    GoTo end
+  ${EndIf}
+
+  ; Okay, there's at least one install, let's see if it's for this channel.
+  SetShellVarContext all
+  ${GetSingleInstallPath} "Software\Mozilla\${BrandFullNameInternal}" $0
+  ${If} $0 == "false"
+    SetShellVarContext current
+    ${GetSingleInstallPath} "Software\Mozilla\${BrandFullNameInternal}" $0
+    ${If} $0 == "false"
+      ; Existing installs are not for this channel. Don't show any prompt.
+      GoTo end
+    ${EndIf}
+  ${EndIf}
+
+  ; Find out what version the default profile was last used on.
+  ${If} ${FileExists} "$R0\compatibility.ini"
+    ClearErrors
+    ReadINIStr $0 "$R0\compatibility.ini" "Compatibility" "LastVersion"
+    ${If} ${Errors}
+      GoTo end
+    ${EndIf}
+    ${WordFind} $0 "." "+1{" $0
+
+    ; We don't know what version we're about to install because we haven't
+    ; downloaded it yet. Find out what the latest version released on this
+    ; channel is and assume we'll be installing that one.
+    Call GetLatestReleasedVersion
+    ${If} ${Errors}
+      ; Use this stub installer's version as a fallback when we can't get the
+      ; real current version; this may be behind, but it's better than nothing.
+      StrCpy $1 ${AppVersion}
+    ${EndIf}
+
+    ${WordFind} $1 "." "+1{" $1
+    IntOp $1 $1 - 2
+
+    ${If} $1 > $0
+      ; Default profile was last used more than two versions ago, so we need
+      ; to show the paveover version of the profile cleanup prompt.
+      StrCpy $ProfileCleanupPromptType 2
+    ${EndIf}
+  ${EndIf}
+
+  end:
+  SetRegView lastused
+  SetShellVarContext all
+FunctionEnd
+
+Function GetLatestReleasedVersion
+  ClearErrors
+  nsJSON::Set /tree requestConfig /value \
+    `{"Url": "https://product-details.mozilla.org/1.0/firefox_versions.json", "Async": false}`
+  IfErrors end
+  nsJSON::Set /http requestConfig
+  IfErrors end
+  ${Select} ${Channel}
+  ${Case} "unofficial"
+    StrCpy $1 "FIREFOX_NIGHTLY"
+  ${Case} "nightly"
+    StrCpy $1 "FIREFOX_NIGHTLY"
+  ${Case} "aurora"
+    StrCpy $1 "FIREFOX_AURORA"
+  ${Case} "beta"
+    StrCpy $1 "LATEST_FIREFOX_RELEASED_DEVEL_VERSION"
+  ${Case} "release"
+    StrCpy $1 "LATEST_FIREFOX_VERSION"
+  ${EndSelect}
+  nsJSON::Get "Output" $1 /end
+  IfErrors end
+  Pop $1
+
+  end:
+FunctionEnd
+
 Section
 SectionEnd
--- a/browser/locales/en-US/installer/nsisstrings.properties
+++ b/browser/locales/en-US/installer/nsisstrings.properties
@@ -15,16 +15,24 @@
 # for double ampersand) and prevents the letter following the ampersand from
 # being used as an accesskey.
 
 # You can use \n to create a newline in the string but only when the string
 # from en-US contains a \n.
 
 INSTALLER_WIN_CAPTION=$BrandShortName Installer
 
+# The \n in the next two strings can be moved or deleted as needed to make
+# the string fit in the 3 lines of space available.
+STUB_CLEANUP_PAVEOVER_HEADER=$BrandShortName is already installed.\nLet's update it.
+STUB_CLEANUP_REINSTALL_HEADER=$BrandShortName has been installed before.\nLet's get you a new copy.
+STUB_CLEANUP_PAVEOVER_BUTTON=&Update
+STUB_CLEANUP_REINSTALL_BUTTON=Re-&install
+STUB_CLEANUP_CHECKBOX_LABEL=&Restore default settings and remove old add-ons for optimal performance
+
 STUB_INSTALLING_LABEL=Now installing
 STUB_BLURB1=Fast, responsive online experiences
 STUB_BLURB2=Compatibility with more of your favorite sites
 STUB_BLURB3=Built-in privacy tools for safer browsing
 STUB_BLURB_FOOTER2=Built for people, not for profit
 
 WARN_MIN_SUPPORTED_OSVER_MSG=Sorry, $BrandShortName can't be installed. This version of $BrandShortName requires ${MinSupportedVer} or newer. Please click the OK button for additional information.
 WARN_MIN_SUPPORTED_CPU_MSG=Sorry, $BrandShortName can't be installed. This version of $BrandShortName requires a processor with ${MinSupportedCPU} support. Please click the OK button for additional information.
--- a/browser/modules/test/browser/browser_UsageTelemetry_content.js
+++ b/browser/modules/test/browser/browser_UsageTelemetry_content.js
@@ -75,17 +75,17 @@ add_task(async function test_context_men
   checkKeyedScalar(scalars, SCALAR_CONTEXT_MENU, "search", 1);
   Assert.equal(Object.keys(scalars[SCALAR_CONTEXT_MENU]).length, 1,
                "This search must only increment one entry in the scalar.");
 
   // Make sure SEARCH_COUNTS contains identical values.
   checkKeyedHistogram(search_hist, "other-MozSearch.contextmenu", 1);
 
   // Also check events.
-  let events = Services.telemetry.snapshotBuiltinEvents(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN, false);
+  let events = Services.telemetry.snapshotEvents(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN, false);
   events = (events.parent || []).filter(e => e[1] == "navigation" && e[2] == "search");
   checkEvents(events, [["navigation", "search", "contextmenu", null, {engine: "other-MozSearch"}]]);
 
   contextMenu.hidePopup();
   await BrowserTestUtils.removeTab(gBrowser.selectedTab);
   await BrowserTestUtils.removeTab(tab);
 });
 
@@ -111,14 +111,14 @@ add_task(async function test_about_newta
   checkKeyedScalar(scalars, SCALAR_ABOUT_NEWTAB, "search_enter", 1);
   Assert.equal(Object.keys(scalars[SCALAR_ABOUT_NEWTAB]).length, 1,
                "This search must only increment one entry in the scalar.");
 
   // Make sure SEARCH_COUNTS contains identical values.
   checkKeyedHistogram(search_hist, "other-MozSearch.newtab", 1);
 
   // Also check events.
-  let events = Services.telemetry.snapshotBuiltinEvents(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN, false);
+  let events = Services.telemetry.snapshotEvents(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN, false);
   events = (events.parent || []).filter(e => e[1] == "navigation" && e[2] == "search");
   checkEvents(events, [["navigation", "search", "about_newtab", "enter", {engine: "other-MozSearch"}]]);
 
   await BrowserTestUtils.removeTab(tab);
 });
--- a/browser/modules/test/browser/browser_UsageTelemetry_content_aboutHome.js
+++ b/browser/modules/test/browser/browser_UsageTelemetry_content_aboutHome.js
@@ -75,14 +75,14 @@ add_task(async function test_abouthome_s
   checkKeyedScalar(scalars, SCALAR_ABOUT_HOME, "search_enter", 1);
   Assert.equal(Object.keys(scalars[SCALAR_ABOUT_HOME]).length, 1,
                "This search must only increment one entry in the scalar.");
 
   // Make sure SEARCH_COUNTS contains identical values.
   checkKeyedHistogram(search_hist, "other-MozSearch.abouthome", 1);
 
   // Also check events.
-  let events = Services.telemetry.snapshotBuiltinEvents(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN, false);
+  let events = Services.telemetry.snapshotEvents(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN, false);
   events = (events.parent || []).filter(e => e[1] == "navigation" && e[2] == "search");
   checkEvents(events, [["navigation", "search", "about_home", "enter", {engine: "other-MozSearch"}]]);
 
   await BrowserTestUtils.removeTab(tab);
 });
--- a/browser/modules/test/browser/browser_UsageTelemetry_searchbar.js
+++ b/browser/modules/test/browser/browser_UsageTelemetry_searchbar.js
@@ -123,17 +123,17 @@ add_task(async function test_plainQuery(
   checkKeyedScalar(scalars, SCALAR_SEARCHBAR, "search_enter", 1);
   Assert.equal(Object.keys(scalars[SCALAR_SEARCHBAR]).length, 1,
                "This search must only increment one entry in the scalar.");
 
   // Make sure SEARCH_COUNTS contains identical values.
   checkKeyedHistogram(search_hist, "other-MozSearch.searchbar", 1);
 
   // Also check events.
-  let events = Services.telemetry.snapshotBuiltinEvents(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN, false);
+  let events = Services.telemetry.snapshotEvents(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN, false);
   events = (events.parent || []).filter(e => e[1] == "navigation" && e[2] == "search");
   checkEvents(events, [["navigation", "search", "searchbar", "enter", {engine: "other-MozSearch"}]]);
 
   // Check the histograms as well.
   let resultMethods = resultMethodHist.snapshot();
   checkHistogramResults(resultMethods,
     URLBAR_SELECTED_RESULT_METHODS.enter,
     "FX_SEARCHBAR_SELECTED_RESULT_METHOD");
@@ -167,17 +167,17 @@ add_task(async function test_oneOff_ente
   checkKeyedScalar(scalars, SCALAR_SEARCHBAR, "search_oneoff", 1);
   Assert.equal(Object.keys(scalars[SCALAR_SEARCHBAR]).length, 1,
                "This search must only increment one entry in the scalar.");
 
   // Make sure SEARCH_COUNTS contains identical values.
   checkKeyedHistogram(search_hist, "other-MozSearch2.searchbar", 1);
 
   // Also check events.
-  let events = Services.telemetry.snapshotBuiltinEvents(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN, false);
+  let events = Services.telemetry.snapshotEvents(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN, false);
   events = (events.parent || []).filter(e => e[1] == "navigation" && e[2] == "search");
   checkEvents(events, [["navigation", "search", "searchbar", "oneoff", {engine: "other-MozSearch2"}]]);
 
   // Check the histograms as well.
   let resultMethods = resultMethodHist.snapshot();
   checkHistogramResults(resultMethods,
     URLBAR_SELECTED_RESULT_METHODS.enter,
     "FX_SEARCHBAR_SELECTED_RESULT_METHOD");
@@ -292,17 +292,17 @@ add_task(async function test_suggestion_
   Assert.equal(Object.keys(scalars[SCALAR_SEARCHBAR]).length, 1,
                "This search must only increment one entry in the scalar.");
 
   // Make sure SEARCH_COUNTS contains identical values.
   let searchEngineId = "other-" + suggestionEngine.name;
   checkKeyedHistogram(search_hist, searchEngineId + ".searchbar", 1);
 
   // Also check events.
-  let events = Services.telemetry.snapshotBuiltinEvents(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN, false);
+  let events = Services.telemetry.snapshotEvents(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN, false);
   events = (events.parent || []).filter(e => e[1] == "navigation" && e[2] == "search");
   checkEvents(events, [["navigation", "search", "searchbar", "suggestion", {engine: searchEngineId}]]);
 
   // Check the histograms as well.
   let resultMethods = resultMethodHist.snapshot();
   checkHistogramResults(resultMethods,
     URLBAR_SELECTED_RESULT_METHODS.click,
     "FX_SEARCHBAR_SELECTED_RESULT_METHOD");
--- a/browser/modules/test/browser/browser_UsageTelemetry_urlbar.js
+++ b/browser/modules/test/browser/browser_UsageTelemetry_urlbar.js
@@ -132,17 +132,17 @@ add_task(async function test_simpleQuery
   checkKeyedScalar(scalars, SCALAR_URLBAR, "search_enter", 1);
   Assert.equal(Object.keys(scalars[SCALAR_URLBAR]).length, 1,
                "This search must only increment one entry in the scalar.");
 
   // Make sure SEARCH_COUNTS contains identical values.
   checkKeyedHistogram(search_hist, "other-MozSearch.urlbar", 1);
 
   // Also check events.
-  let events = Services.telemetry.snapshotBuiltinEvents(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN, false);
+  let events = Services.telemetry.snapshotEvents(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN, false);
   events = (events.parent || []).filter(e => e[1] == "navigation" && e[2] == "search");
   checkEvents(events, [["navigation", "search", "urlbar", "enter", {engine: "other-MozSearch"}]]);
 
   // Check the histograms as well.
   let resultIndexes = resultIndexHist.snapshot();
   checkHistogramResults(resultIndexes, 0, "FX_URLBAR_SELECTED_RESULT_INDEX");
 
   let resultTypes = resultTypeHist.snapshot();
@@ -191,17 +191,17 @@ add_task(async function test_searchAlias
   checkKeyedScalar(scalars, SCALAR_URLBAR, "search_alias", 1);
   Assert.equal(Object.keys(scalars[SCALAR_URLBAR]).length, 1,
                "This search must only increment one entry in the scalar.");
 
   // Make sure SEARCH_COUNTS contains identical values.
   checkKeyedHistogram(search_hist, "other-MozSearch.urlbar", 1);
 
   // Also check events.
-  let events = Services.telemetry.snapshotBuiltinEvents(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN, false);
+  let events = Services.telemetry.snapshotEvents(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN, false);
   events = (events.parent || []).filter(e => e[1] == "navigation" && e[2] == "search");
   checkEvents(events, [["navigation", "search", "urlbar", "alias", {engine: "other-MozSearch"}]]);
 
   // Check the histograms as well.
   let resultIndexes = resultIndexHist.snapshot();
   checkHistogramResults(resultIndexes, 0, "FX_URLBAR_SELECTED_RESULT_INDEX");
 
   let resultTypes = resultTypeHist.snapshot();
@@ -255,17 +255,17 @@ add_task(async function test_oneOff_ente
   checkKeyedScalar(scalars, SCALAR_URLBAR, "search_oneoff", 1);
   Assert.equal(Object.keys(scalars[SCALAR_URLBAR]).length, 1,
                "This search must only increment one entry in the scalar.");
 
   // Make sure SEARCH_COUNTS contains identical values.
   checkKeyedHistogram(search_hist, "other-MozSearch.urlbar", 1);
 
   // Also check events.
-  let events = Services.telemetry.snapshotBuiltinEvents(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN, false);
+  let events = Services.telemetry.snapshotEvents(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN, false);
   events = (events.parent || []).filter(e => e[1] == "navigation" && e[2] == "search");
   checkEvents(events, [["navigation", "search", "urlbar", "oneoff", {engine: "other-MozSearch"}]]);
 
   // Check the histograms as well.
   let resultIndexes = resultIndexHist.snapshot();
   checkHistogramResults(resultIndexes, 0, "FX_URLBAR_SELECTED_RESULT_INDEX");
 
   let resultTypes = resultTypeHist.snapshot();
@@ -402,17 +402,17 @@ add_task(async function test_suggestion_
   Assert.equal(Object.keys(scalars[SCALAR_URLBAR]).length, 1,
                "This search must only increment one entry in the scalar.");
 
   // Make sure SEARCH_COUNTS contains identical values.
   let searchEngineId = "other-" + suggestionEngine.name;
   checkKeyedHistogram(search_hist, searchEngineId + ".urlbar", 1);
 
   // Also check events.
-  let events = Services.telemetry.snapshotBuiltinEvents(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN, false);
+  let events = Services.telemetry.snapshotEvents(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN, false);
   events = (events.parent || []).filter(e => e[1] == "navigation" && e[2] == "search");
   checkEvents(events, [["navigation", "search", "urlbar", "suggestion", {engine: searchEngineId}]]);
 
   // Check the histograms as well.
   let resultIndexes = resultIndexHist.snapshot();
   checkHistogramResults(resultIndexes, 3, "FX_URLBAR_SELECTED_RESULT_INDEX");
 
   let resultTypes = resultTypeHist.snapshot();
--- a/build/moz.configure/old.configure
+++ b/build/moz.configure/old.configure
@@ -154,19 +154,16 @@ def old_configure_options(*options):
 
 
 @old_configure_options(
     '--cache-file',
     '--datadir',
     '--enable-accessibility',
     '--enable-address-sanitizer',
     '--enable-alsa',
-    '--enable-b2g-bt',
-    '--enable-b2g-camera',
-    '--enable-b2g-ril',
     '--enable-bundled-fonts',
     '--enable-clang-plugin',
     '--enable-content-sandbox',
     '--enable-cookies',
     '--enable-cpp-rtti',
     '--enable-crashreporter',
     '--enable-dbus',
     '--enable-debug-js-modules',
@@ -188,34 +185,31 @@ def old_configure_options(*options):
     '--enable-jitspew',
     '--enable-libjpeg-turbo',
     '--enable-libproxy',
     '--enable-llvm-hacks',
     '--enable-logrefcnt',
     '--enable-maintenance-service',
     '--enable-memory-sanitizer',
     '--enable-mobile-optimize',
-    '--enable-mozril-geoloc',
     '--enable-necko-wifi',
     '--enable-negotiateauth',
     '--enable-nfc',
     '--enable-nspr-build',
     '--enable-official-branding',
     '--enable-oom-breakpoint',
     '--enable-optimize',
     '--enable-parental-controls',
     '--enable-pie',
-    '--enable-png-arm-neon-support',
     '--enable-posix-nspr-emulation',
     '--enable-pref-extensions',
     '--enable-pulseaudio',
     '--enable-raw',
     '--enable-readline',
     '--enable-reflow-perf',
-    '--enable-safe-browsing',
     '--enable-sandbox',
     '--enable-signmar',
     '--enable-simulator',
     '--enable-small-chunk-size',
     '--enable-startup-notification',
     '--enable-startupcache',
     '--enable-stdcxx-compat',
     '--enable-strip',
@@ -226,17 +220,16 @@ def old_configure_options(*options):
     '--enable-system-sqlite',
     '--enable-tasktracer',
     '--enable-thread-sanitizer',
     '--enable-trace-logging',
     '--enable-ubsan-int-overflow',
     '--enable-ui-locale',
     '--enable-universalchardet',
     '--enable-updater',
-    '--enable-url-classifier',
     '--enable-valgrind',
     '--enable-verify-mar',
     '--enable-webrtc',
     '--enable-xul',
     '--enable-zipwriter',
     '--includedir',
     '--libdir',
     '--no-create',
--- a/build/moz.configure/toolchain.configure
+++ b/build/moz.configure/toolchain.configure
@@ -1034,16 +1034,54 @@ def wrap_system_includes(target, visibil
 
 set_define('HAVE_VISIBILITY_HIDDEN_ATTRIBUTE',
            depends(visibility_flags)(lambda v: bool(v) or None))
 set_define('HAVE_VISIBILITY_ATTRIBUTE',
            depends(visibility_flags)(lambda v: bool(v) or None))
 set_config('WRAP_SYSTEM_INCLUDES', wrap_system_includes)
 set_config('VISIBILITY_FLAGS', visibility_flags)
 
+@depends(c_compiler)
+@imports('multiprocessing')
+@imports(_from='__builtin__', _import='min')
+def pgo_flags(compiler):
+    if compiler.type in ('gcc', 'clang'):
+        return namespace(
+            gen_cflags=['-fprofile-generate'],
+            gen_ldflags=['-fprofile-generate'],
+            use_cflags=['-fprofile-use', '-fprofile-correction',
+                        '-Wcoverage-mismatch'],
+            use_ldflags=['-fprofile-use'],
+        )
+
+    if compiler.type == 'msvc':
+        num_cores = min(8, multiprocessing.cpu_count())
+        cgthreads = '-CGTHREADS:%s' % num_cores
+
+        return namespace(
+            gen_cflags=['-GL'],
+            gen_ldflags=['-LTCG:PGINSTRUMENT', '-PogoSafeMode', cgthreads],
+            # XXX: PGO builds can fail with warnings treated as errors,
+            # specifically "no profile data available" appears to be
+            # treated as an error sometimes. This might be a consequence
+            # of using WARNINGS_AS_ERRORS in some modules, combined
+            # with the linker doing most of the work in the whole-program
+            # optimization/PGO case. I think it's probably a compiler bug,
+            # but we work around it here.
+            use_cflags=['-GL', '-wd4624', '-wd4952'],
+            # XXX: should be -LTCG:PGOPTIMIZE, but that fails on libxul.
+            # Probably also a compiler bug, but what can you do?
+            use_ldflags=['-LTCG:PGUPDATE', cgthreads],
+        )
+
+set_config('PROFILE_GEN_CFLAGS', pgo_flags.gen_cflags)
+set_config('PROFILE_GEN_LDFLAGS', pgo_flags.gen_ldflags)
+set_config('PROFILE_USE_CFLAGS', pgo_flags.use_cflags)
+set_config('PROFILE_USE_LDFLAGS', pgo_flags.use_ldflags)
+
 # We only want to include windows.configure when we are compiling on
 # Windows, for Windows.
 @depends(target, host)
 def is_windows(target, host):
     return host.kernel == 'WINNT' and target.kernel == 'WINNT'
 
 include('windows.configure', when=is_windows)
 
--- a/devtools/client/inspector/rules/models/rule.js
+++ b/devtools/client/inspector/rules/models/rule.js
@@ -131,25 +131,31 @@ Rule.prototype = {
    * Get display name for this rule based on the original source
    * for this rule's style sheet.
    *
    * @return {Promise}
    *         Promise which resolves with location as an object containing
    *         both the full and short version of the source string.
    */
   getOriginalSourceStrings: function () {
-    return this.domRule.getOriginalLocation().then(({href,
-                                                     line, mediaText}) => {
+    return this.domRule.getOriginalLocation().then(({href, line, mediaText}) => {
       let mediaString = mediaText ? " @" + mediaText : "";
       let linePart = line > 0 ? (":" + line) : "";
+      let decodedHref = href;
+
+      if (decodedHref) {
+        try {
+          decodedHref = decodeURIComponent(href);
+        } catch (e) {}
+      }
 
       let sourceStrings = {
-        full: (href || CssLogic.l10n("rule.sourceInline")) + linePart +
+        full: (decodedHref || CssLogic.l10n("rule.sourceInline")) + linePart +
           mediaString,
-        short: CssLogic.shortSource({href: href}) + linePart + mediaString
+        short: CssLogic.shortSource({href: decodedHref}) + linePart + mediaString
       };
 
       return sourceStrings;
     });
   },
 
   /**
    * Returns true if the rule matches the creation options
--- a/devtools/client/inspector/rules/test/browser_rules_style-editor-link.js
+++ b/devtools/client/inspector/rules/test/browser_rules_style-editor-link.js
@@ -1,20 +1,22 @@
 /* vim: set ft=javascript ts=2 et sw=2 tw=80: */
 /* Any copyright is dedicated to the Public Domain.
  http://creativecommons.org/publicdomain/zero/1.0/ */
 
 "use strict";
 
 // Test the links from the rule-view to the styleeditor
 
-const STYLESHEET_URL = "data:text/css," + encodeURIComponent(
-  ["#first {",
-   "color: blue",
-   "}"].join("\n"));
+const STYLESHEET_DATA_URL_CONTENTS = ["#first {",
+                                      "color: blue",
+                                      "}"].join("\n");
+const STYLESHEET_DATA_URL =
+      `data:text/css,${encodeURIComponent(STYLESHEET_DATA_URL_CONTENTS)}`;
+const STYLESHEET_DECODED_DATA_URL = `data:text/css,${STYLESHEET_DATA_URL_CONTENTS}`;
 
 const EXTERNAL_STYLESHEET_FILE_NAME = "doc_style_editor_link.css";
 const EXTERNAL_STYLESHEET_URL = URL_ROOT + EXTERNAL_STYLESHEET_FILE_NAME;
 
 const DOCUMENT_URL = "data:text/html;charset=utf-8," + encodeURIComponent(`
   <html>
   <head>
   <title>Rule view style editor link test</title>
@@ -22,17 +24,17 @@ const DOCUMENT_URL = "data:text/html;cha
   html { color: #000000; }
   div { font-variant: small-caps; color: #000000; }
   .nomatches {color: #ff0000;}</style> <div id="first" style="margin: 10em;
   font-size: 14pt; font-family: helvetica, sans-serif; color: #AAA">
   </style>
   <style>
   div { font-weight: bold; }
   </style>
-  <link rel="stylesheet" type="text/css" href="${STYLESHEET_URL}">
+  <link rel="stylesheet" type="text/css" href="${STYLESHEET_DATA_URL}">
   <link rel="stylesheet" type="text/css" href="${EXTERNAL_STYLESHEET_URL}">
   </head>
   <body>
   <h1>Some header text</h1>
   <p id="salutation" style="font-size: 12pt">hi.</p>
   <p id="body" style="font-size: 12pt">I am a test-case. This text exists
   solely to provide some things to
   <span style="color: yellow" class="highlight">
@@ -169,25 +171,38 @@ function* testDisabledStyleEditor(view, 
   clickLinkByIndex(view, 1);
   yield onStyleEditorSelected;
   is(toolbox.currentToolId, "styleeditor", "Style Editor should be selected");
 
   Services.prefs.clearUserPref("devtools.styleeditor.enabled");
 }
 
 function testRuleViewLinkLabel(view) {
-  let link = getRuleViewLinkByIndex(view, 2);
+  info("Checking the data URL link label");
+
+  let link = getRuleViewLinkByIndex(view, 1);
   let labelElem = link.querySelector(".ruleview-rule-source-label");
   let value = labelElem.textContent;
   let tooltipText = labelElem.getAttribute("title");
 
-  is(value, EXTERNAL_STYLESHEET_FILE_NAME + ":1",
-    "rule view stylesheet display value matches filename and line number");
-  is(tooltipText, EXTERNAL_STYLESHEET_URL + ":1",
-    "rule view stylesheet tooltip text matches the full URI path");
+  is(value, `${STYLESHEET_DATA_URL_CONTENTS}:1`,
+    "Rule view data URL stylesheet display value matches contents");
+  is(tooltipText, `${STYLESHEET_DECODED_DATA_URL}:1`,
+    "Rule view data URL stylesheet tooltip text matches the full URI path");
+
+  info("Checking the external link label");
+  link = getRuleViewLinkByIndex(view, 2);
+  labelElem = link.querySelector(".ruleview-rule-source-label");
+  value = labelElem.textContent;
+  tooltipText = labelElem.getAttribute("title");
+
+  is(value, `${EXTERNAL_STYLESHEET_FILE_NAME}:1`,
+    "Rule view external stylesheet display value matches filename and line number");
+  is(tooltipText, `${EXTERNAL_STYLESHEET_URL}:1`,
+    "Rule view external stylesheet tooltip text matches the full URI path");
 }
 
 function testUnselectableRuleViewLink(view, index) {
   let link = getRuleViewLinkByIndex(view, index);
   let unselectable = link.hasAttribute("unselectable");
 
   ok(unselectable, "Rule view is unselectable");
 }
--- a/devtools/shared/inspector/css-logic.js
+++ b/devtools/shared/inspector/css-logic.js
@@ -4,16 +4,18 @@
  * 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 { getRootBindingParent } = require("devtools/shared/layout/utils");
 const { getTabPrefs } = require("devtools/shared/indentation");
 
+const MAX_DATA_URL_LENGTH = 40;
+
 /*
  * About the objects defined in this file:
  * - CssLogic contains style information about a view context. It provides
  *   access to 2 sets of objects: Css[Sheet|Rule|Selector] provide access to
  *   information that does not change when the selected element changes while
  *   Css[Property|Selector]Info provide information that is dependent on the
  *   selected element.
  *   Its key methods are highlight(), getPropertyInfo() and forEachSheet(), etc
@@ -104,16 +106,23 @@ exports.isContentStylesheet = function (
  * @param {CSSStyleSheet} sheet the DOM object for the style sheet.
  */
 exports.shortSource = function (sheet) {
   // Use a string like "inline" if there is no source href
   if (!sheet || !sheet.href) {
     return exports.l10n("rule.sourceInline");
   }
 
+  // If the sheet is a data URL, return a trimmed version of it.
+  let dataUrl = sheet.href.trim().match(/^data:.*?,((?:.|\r|\n)*)$/);
+  if (dataUrl) {
+    return dataUrl[1].length > MAX_DATA_URL_LENGTH ?
+      `${dataUrl[1].substr(0, MAX_DATA_URL_LENGTH - 1)}…` : dataUrl[1];
+  }
+
   // We try, in turn, the filename, filePath, query string, whole thing
   let url = {};
   try {
     url = new URL(sheet.href);
   } catch (ex) {
     // Some UA-provided stylesheets are not valid URLs.
   }
 
@@ -124,18 +133,17 @@ exports.shortSource = function (sheet) {
     }
     return url.pathname;
   }
 
   if (url.query) {
     return url.query;
   }
 
-  let dataUrl = sheet.href.match(/^(data:[^,]*),/);
-  return dataUrl ? dataUrl[1] : sheet.href;
+  return sheet.href;
 };
 
 const TAB_CHARS = "\t";
 const SPACE_CHARS = " ";
 
 /**
  * Prettify minified CSS text.
  * This prettifies CSS code where there is no indentation in usual places while
--- a/dom/events/EventListenerManager.cpp
+++ b/dom/events/EventListenerManager.cpp
@@ -1765,33 +1765,51 @@ EventListenerManager::TraceListeners(JST
       mozilla::TraceScriptHolder(listener.mListener.GetWebIDLCallback(), aTrc);
     }
     // We might have eWrappedJSListener, but that is the legacy type for
     // JS implemented event listeners, and trickier to handle here.
   }
 }
 
 bool
-EventListenerManager::HasUntrustedOrNonSystemGroupKeyEventListeners()
+EventListenerManager::HasNonSystemGroupListenersForUntrustedKeyEvents()
 {
   uint32_t count = mListeners.Length();
   for (uint32_t i = 0; i < count; ++i) {
     Listener* listener = &mListeners.ElementAt(i);
     if (!listener->mFlags.mInSystemGroup &&
         listener->mFlags.mAllowUntrustedEvents &&
         (listener->mTypeAtom == nsGkAtoms::onkeydown ||
          listener->mTypeAtom == nsGkAtoms::onkeypress ||
          listener->mTypeAtom == nsGkAtoms::onkeyup)) {
       return true;
     }
   }
   return false;
 }
 
 bool
+EventListenerManager::HasNonPassiveNonSystemGroupListenersForUntrustedKeyEvents()
+{
+  uint32_t count = mListeners.Length();
+  for (uint32_t i = 0; i < count; ++i) {
+    Listener* listener = &mListeners.ElementAt(i);
+    if (!listener->mFlags.mPassive &&
+        !listener->mFlags.mInSystemGroup &&
+        listener->mFlags.mAllowUntrustedEvents &&
+        (listener->mTypeAtom == nsGkAtoms::onkeydown ||
+         listener->mTypeAtom == nsGkAtoms::onkeypress ||
+         listener->mTypeAtom == nsGkAtoms::onkeyup)) {
+      return true;
+    }
+  }
+  return false;
+}
+
+bool
 EventListenerManager::HasApzAwareListeners()
 {
   uint32_t count = mListeners.Length();
   for (uint32_t i = 0; i < count; ++i) {
     Listener* listener = &mListeners.ElementAt(i);
     if (IsApzAwareListener(listener)) {
       return true;
     }
--- a/dom/events/EventListenerManager.h
+++ b/dom/events/EventListenerManager.h
@@ -465,17 +465,18 @@ public:
   }
 
   void MarkForCC();
 
   void TraceListeners(JSTracer* aTrc);
 
   dom::EventTarget* GetTarget() { return mTarget; }
 
-  bool HasUntrustedOrNonSystemGroupKeyEventListeners();
+  bool HasNonSystemGroupListenersForUntrustedKeyEvents();
+  bool HasNonPassiveNonSystemGroupListenersForUntrustedKeyEvents();
 
   bool HasApzAwareListeners();
   bool IsApzAwareListener(Listener* aListener);
   bool IsApzAwareEvent(nsIAtom* aEvent);
 
 protected:
   void HandleEventInternal(nsPresContext* aPresContext,
                            WidgetEvent* aEvent,
--- a/dom/events/EventTarget.cpp
+++ b/dom/events/EventTarget.cpp
@@ -53,20 +53,27 @@ EventTarget::SetEventHandler(const nsASt
 void
 EventTarget::SetEventHandler(nsIAtom* aType, const nsAString& aTypeString,
                              EventHandlerNonNull* aHandler)
 {
   GetOrCreateListenerManager()->SetEventHandler(aType, aTypeString, aHandler);
 }
 
 bool
-EventTarget::HasUntrustedOrNonSystemGroupKeyEventListeners() const
+EventTarget::HasNonSystemGroupListenersForUntrustedKeyEvents() const
 {
   EventListenerManager* elm = GetExistingListenerManager();
-  return elm && elm->HasUntrustedOrNonSystemGroupKeyEventListeners();
+  return elm && elm->HasNonSystemGroupListenersForUntrustedKeyEvents();
+}
+
+bool
+EventTarget::HasNonPassiveNonSystemGroupListenersForUntrustedKeyEvents() const
+{
+  EventListenerManager* elm = GetExistingListenerManager();
+  return elm && elm->HasNonPassiveNonSystemGroupListenersForUntrustedKeyEvents();
 }
 
 bool
 EventTarget::IsApzAware() const
 {
   EventListenerManager* elm = GetExistingListenerManager();
   return elm && elm->HasApzAwareListeners();
 }
--- a/dom/events/EventTarget.h
+++ b/dom/events/EventTarget.h
@@ -94,19 +94,23 @@ public:
    * Get the event listener manager, returning null if it does not already
    * exist.
    */
   virtual EventListenerManager* GetExistingListenerManager() const = 0;
 
   // Called from AsyncEventDispatcher to notify it is running.
   virtual void AsyncEventRunning(AsyncEventDispatcher* aEvent) {}
 
-  // Used by FocusTarget to determine whether this event target has listeners
-  // for untrusted or non system group key events.
-  bool HasUntrustedOrNonSystemGroupKeyEventListeners() const;
+  // Used by APZ to determine whether this event target has non-chrome event
+  // listeners for untrusted key events.
+  bool HasNonSystemGroupListenersForUntrustedKeyEvents() const;
+
+  // Used by APZ to determine whether this event target has non-chrome and
+  // non-passive event listeners for untrusted key events.
+  bool HasNonPassiveNonSystemGroupListenersForUntrustedKeyEvents() const;
 
   virtual bool IsApzAware() const;
 
 protected:
   EventHandlerNonNull* GetEventHandler(nsIAtom* aType,
                                        const nsAString& aTypeString);
   void SetEventHandler(nsIAtom* aType, const nsAString& aTypeString,
                        EventHandlerNonNull* aHandler);
--- a/dom/workers/test/serviceworkers/browser_download.js
+++ b/dom/workers/test/serviceworkers/browser_download.js
@@ -1,14 +1,13 @@
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
 Cu.import('resource://gre/modules/Services.jsm');
 var Downloads = Cu.import("resource://gre/modules/Downloads.jsm", {}).Downloads;
-var DownloadsCommon = Cu.import("resource:///modules/DownloadsCommon.jsm", {}).DownloadsCommon;
 Cu.import('resource://gre/modules/NetUtil.jsm');
 
 var gTestRoot = getRootDirectory(gTestPath).replace("chrome://mochitests/content/",
                                                     "http://mochi.test:8888/")
 
 function getFile(aFilename) {
   if (aFilename.startsWith('file:')) {
     var url = NetUtil.newURI(aFilename).QueryInterface(Ci.nsIFileURL);
@@ -54,19 +53,18 @@ function test() {
       var downloadListener;
 
       function downloadVerifier(aDownload) {
         if (aDownload.succeeded) {
           var file = getFile(aDownload.target.path);
           ok(file.exists(), 'download completed');
           is(file.fileSize, 33, 'downloaded file has correct size');
           file.remove(false);
-          DownloadsCommon.removeAndFinalizeDownload(aDownload);
-
-          downloadList.removeView(downloadListener);
+          downloadList.remove(aDownload).catch(Cu.reportError);
+          downloadList.removeView(downloadListener).catch(Cu.reportError);
           gBrowser.removeTab(tab);
           Services.ww.unregisterNotification(windowObserver);
 
           executeSoon(finish);
         }
       }
 
       downloadListener = {
--- a/dom/xbl/nsXBLWindowKeyHandler.cpp
+++ b/dom/xbl/nsXBLWindowKeyHandler.cpp
@@ -499,16 +499,23 @@ nsXBLWindowKeyHandler::HandleEvent(nsIDO
     //     combination, it will be executed when the event is normal keyboard
     //     events...
     bool isReserved = false;
     if (!HasHandlerForEvent(keyEvent, &isReserved)) {
       return NS_OK;
     }
   }
 
+  // If this event was handled by APZ then don't do the default action, and
+  // preventDefault to prevent any other listeners from handling the event.
+  if (widgetKeyboardEvent->mFlags.mHandledByAPZ) {
+    aEvent->PreventDefault();
+    return NS_OK;
+  }
+
   nsCOMPtr<nsIAtom> eventTypeAtom =
     ConvertEventToDOMEventType(*widgetKeyboardEvent);
   return WalkHandlers(keyEvent, eventTypeAtom);
 }
 
 void
 nsXBLWindowKeyHandler::HandleEventOnCaptureInDefaultEventGroup(
                          nsIDOMKeyEvent* aEvent)
--- a/gfx/layers/PaintThread.cpp
+++ b/gfx/layers/PaintThread.cpp
@@ -17,16 +17,58 @@ namespace mozilla {
 namespace layers {
 
 using namespace gfx;
 
 StaticAutoPtr<PaintThread> PaintThread::sSingleton;
 StaticRefPtr<nsIThread> PaintThread::sThread;
 PlatformThreadId PaintThread::sThreadId;
 
+// RAII make sure we clean up and restore our draw targets
+// when we paint async.
+struct AutoCapturedPaintSetup {
+  AutoCapturedPaintSetup(DrawTarget* aTarget,
+                         DrawTargetCapture* aCapture,
+                         CompositorBridgeChild* aBridge)
+  : mTarget(aTarget)
+  , mRestorePermitsSubpixelAA(aTarget->GetPermitSubpixelAA())
+  , mOldTransform(aTarget->GetTransform())
+  , mBridge(aBridge)
+  {
+    MOZ_ASSERT(mTarget);
+    MOZ_ASSERT(aCapture);
+
+    mTarget->SetTransform(aCapture->GetTransform());
+    mTarget->SetPermitSubpixelAA(aCapture->GetPermitSubpixelAA());
+  }
+
+  ~AutoCapturedPaintSetup()
+  {
+    mTarget->SetTransform(mOldTransform);
+    mTarget->SetPermitSubpixelAA(mRestorePermitsSubpixelAA);
+
+    // Textureclient forces a flush once we "end paint", so
+    // users of this texture expect all the drawing to be complete.
+    // Force a flush now.
+    // TODO: This might be a performance bottleneck because
+    // main thread painting only does one flush at the end of all paints
+    // whereas we force a flush after each draw target paint.
+    mTarget->Flush();
+
+    if (mBridge) {
+      mBridge->NotifyFinishedAsyncPaint();
+    }
+  }
+
+  DrawTarget* mTarget;
+  bool mRestorePermitsSubpixelAA;
+  Matrix mOldTransform;
+  RefPtr<CompositorBridgeChild> mBridge;
+};
+
 void
 PaintThread::Release()
 {
 }
 
 void
 PaintThread::AddRef()
 {
@@ -107,76 +149,58 @@ PaintThread::ShutdownOnPaintThread()
 /* static */ bool
 PaintThread::IsOnPaintThread()
 {
   return sThreadId == PlatformThread::CurrentId();
 }
 
 void
 PaintThread::PaintContentsAsync(CompositorBridgeChild* aBridge,
-                                gfx::DrawTargetCapture* aCapture,
                                 CapturedPaintState* aState,
                                 PrepDrawTargetForPaintingCallback aCallback)
 {
   MOZ_ASSERT(IsOnPaintThread());
-  MOZ_ASSERT(aCapture);
   MOZ_ASSERT(aState);
 
   DrawTarget* target = aState->mTarget;
+  DrawTargetCapture* capture = aState->mCapture;
 
-  Matrix oldTransform = target->GetTransform();
-  target->SetTransform(aState->mTargetTransform);
-  target->SetPermitSubpixelAA(aCapture->GetPermitSubpixelAA());
+  AutoCapturedPaintSetup setup(target, capture, aBridge);
 
   if (!aCallback(aState)) {
     return;
   }
 
   // Draw all the things into the actual dest target.
-  target->DrawCapturedDT(aCapture, Matrix());
-  target->SetTransform(oldTransform);
-
-  // Textureclient forces a flush once we "end paint", so
-  // users of this texture expect all the drawing to be complete.
-  // Force a flush now.
-  // TODO: This might be a performance bottleneck because
-  // main thread painting only does one flush at the end of all paints
-  // whereas we force a flush after each draw target paint.
-  target->Flush();
-
-  if (aBridge) {
-    aBridge->NotifyFinishedAsyncPaint();
-  }
+  target->DrawCapturedDT(capture, Matrix());
 }
 
 void
-PaintThread::PaintContents(DrawTargetCapture* aCapture,
-                           CapturedPaintState* aState,
+PaintThread::PaintContents(CapturedPaintState* aState,
                            PrepDrawTargetForPaintingCallback aCallback)
 {
   MOZ_ASSERT(NS_IsMainThread());
-  MOZ_ASSERT(aCapture);
   MOZ_ASSERT(aState);
 
   // If painting asynchronously, we need to acquire the compositor bridge which
   // owns the underlying MessageChannel. Otherwise we leave it null and use
   // synchronous dispatch.
   RefPtr<CompositorBridgeChild> cbc;
   if (!gfxPrefs::LayersOMTPForceSync()) {
     cbc = CompositorBridgeChild::Get();
     cbc->NotifyBeginAsyncPaint();
   }
-  RefPtr<DrawTargetCapture> capture(aCapture);
   RefPtr<CapturedPaintState> state(aState);
+  RefPtr<DrawTargetCapture> capture(aState->mCapture);
 
   RefPtr<PaintThread> self = this;
   RefPtr<Runnable> task = NS_NewRunnableFunction("PaintThread::PaintContents",
     [self, cbc, capture, state, aCallback]() -> void
   {
-    self->PaintContentsAsync(cbc, capture,
+    self->PaintContentsAsync(cbc,
                              state,
                              aCallback);
   });
 
   if (cbc) {
     sThread->Dispatch(task.forget());
   } else {
     SyncRunnable::DispatchToThread(sThread, task);
--- a/gfx/layers/PaintThread.h
+++ b/gfx/layers/PaintThread.h
@@ -22,30 +22,33 @@ class DrawTargetCapture;
 namespace layers {
 
 // Holds the key parts from a RotatedBuffer::PaintState
 // required to draw the captured paint state
 class CapturedPaintState {
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(CapturedPaintState)
 public:
   CapturedPaintState(nsIntRegion& aRegionToDraw,
+                     gfx::DrawTargetCapture* aCapture,
                      gfx::DrawTarget* aTarget,
                      gfx::DrawTarget* aTargetOnWhite,
                      gfx::Matrix aTargetTransform,
                      SurfaceMode aSurfaceMode,
                      gfxContentType aContentType)
   : mRegionToDraw(aRegionToDraw)
+  , mCapture(aCapture)
   , mTarget(aTarget)
   , mTargetOnWhite(aTargetOnWhite)
   , mTargetTransform(aTargetTransform)
   , mSurfaceMode(aSurfaceMode)
   , mContentType(aContentType)
   {}
 
   nsIntRegion mRegionToDraw;
+  RefPtr<gfx::DrawTargetCapture> mCapture;
   RefPtr<gfx::DrawTarget> mTarget;
   RefPtr<gfx::DrawTarget> mTargetOnWhite;
   gfx::Matrix mTargetTransform;
   SurfaceMode mSurfaceMode;
   gfxContentType mContentType;
 
 protected:
   virtual ~CapturedPaintState() {}
@@ -58,18 +61,17 @@ class CompositorBridgeChild;
 class PaintThread final
 {
   friend void DestroyPaintThread(UniquePtr<PaintThread>&& aPaintThread);
 
 public:
   static void Start();
   static void Shutdown();
   static PaintThread* Get();
-  void PaintContents(gfx::DrawTargetCapture* aCapture,
-                     CapturedPaintState* aState,
+  void PaintContents(CapturedPaintState* aState,
                      PrepDrawTargetForPaintingCallback aCallback);
 
   // Sync Runnables need threads to be ref counted,
   // But this thread lives through the whole process.
   // We're only temporarily using sync runnables so
   // Override release/addref but don't do anything.
   void Release();
   void AddRef();
@@ -77,17 +79,16 @@ public:
   // Helper for asserts.
   static bool IsOnPaintThread();
 
 private:
   bool Init();
   void ShutdownOnPaintThread();
   void InitOnPaintThread();
   void PaintContentsAsync(CompositorBridgeChild* aBridge,
-                          gfx::DrawTargetCapture* aCapture,
                           CapturedPaintState* aState,
                           PrepDrawTargetForPaintingCallback aCallback);
 
   static StaticAutoPtr<PaintThread> sSingleton;
   static StaticRefPtr<nsIThread> sThread;
   static PlatformThreadId sThreadId;
 };
 
--- a/gfx/layers/RotatedBuffer.cpp
+++ b/gfx/layers/RotatedBuffer.cpp
@@ -831,16 +831,17 @@ RotatedContentBuffer::BorrowDrawTargetFo
   ExpandDrawRegion(aPaintState, aIter, result->GetBackendType());
 
   nsIntRegion regionToDraw = aIter ? aIter->mDrawRegion
                                    : aPaintState.mRegionToDraw;
 
   // Can't stack allocate refcounted objects.
   RefPtr<CapturedPaintState> capturedPaintState =
     MakeAndAddRef<CapturedPaintState>(regionToDraw,
+                                      nullptr,
                                       mDTBuffer,
                                       mDTBufferOnWhite,
                                       Matrix(),
                                       aPaintState.mMode,
                                       aPaintState.mContentType);
 
   if (!RotatedContentBuffer::PrepareDrawTargetForPainting(capturedPaintState)) {
     return nullptr;
--- a/gfx/layers/apz/src/APZCTreeManager.cpp
+++ b/gfx/layers/apz/src/APZCTreeManager.cpp
@@ -1354,17 +1354,18 @@ APZCTreeManager::ReceiveInputEvent(Input
       // Dispatch the event to the input queue.
       result = mInputQueue->ReceiveInputEvent(
           targetApzc,
           /* aTargetConfirmed = */ true,
           keyInput, aOutInputBlockId);
 
       // Any keyboard event that is dispatched to the input queue at this point
       // should have been consumed
-      MOZ_ASSERT(result == nsEventStatus_eConsumeNoDefault);
+      MOZ_ASSERT(result == nsEventStatus_eConsumeDoDefault ||
+                 result == nsEventStatus_eConsumeNoDefault);
 
       keyInput.mHandledByAPZ = true;
       focusSetter.MarkAsNonFocusChanging();
 
       break;
     }
   }
   return result;
--- a/gfx/layers/apz/src/AsyncPanZoomController.cpp
+++ b/gfx/layers/apz/src/AsyncPanZoomController.cpp
@@ -280,16 +280,26 @@ typedef GenericFlingAnimation FlingAnima
  *
  * \li\b apz.frame_delay.enabled
  * If this is set to true, changes to the async scroll offset and async zoom
  * will not be immediately reflected in GetCurrentAsyncTransform() when called
  * with |AsyncTransformConsumer::eForCompositing|. Rather, the transform will
  * reflect the value of the async scroll offset and async zoom at the last time
  * SampleCompositedAsyncTransform() was called.
  *
+ * \li\b apz.keyboard.enabled
+ * Determines whether scrolling with the keyboard will be allowed to be handled
+ * by APZ.
+ *
+ * \li\b apz.keyboard.passive-listeners
+ * When enabled, APZ will interpret the passive event listener flag to mean
+ * that the event listener won't change the focused element or selection of
+ * the page. With this, web content can use passive key listeners and not have
+ * keyboard APZ disabled.
+ *
  * \li\b apz.max_velocity_inches_per_ms
  * Maximum velocity.  Velocity will be capped at this value if a faster fling
  * occurs.  Negative values indicate unlimited velocity.\n
  * Units: (real-world, i.e. screen) inches per millisecond
  *
  * \li\b apz.max_velocity_queue_size
  * Maximum size of velocity queue. The queue contains last N velocity records.
  * On touch end we calculate the average velocity in order to compensate
@@ -1745,17 +1755,17 @@ AsyncPanZoomController::OnKeyboard(const
   ReentrantMonitorAutoEnter lock(mMonitor);
 
   if (Maybe<CSSPoint> snapPoint = FindSnapPointNear(destination, scrollUnit)) {
     // If we're scroll snapping, use a smooth scroll animation to get
     // the desired physics. Note that SmoothScrollTo() will re-use an
     // existing smooth scroll animation if there is one.
     APZC_LOG("%p keyboard scrolling to snap point %s\n", this, Stringify(*snapPoint).c_str());
     SmoothScrollTo(*snapPoint);
-    return nsEventStatus_eConsumeNoDefault;
+    return nsEventStatus_eConsumeDoDefault;
   }
 
   // Use a keyboard scroll animation to scroll, reusing an existing one if it exists
   if (mState != KEYBOARD_SCROLL) {
     CancelAnimation();
     SetState(KEYBOARD_SCROLL);
 
     nsPoint initialPosition = CSSPoint::ToAppUnits(mFrameMetrics.GetScrollOffset());
@@ -1770,17 +1780,17 @@ AsyncPanZoomController::OnKeyboard(const
 
   KeyboardScrollAnimation* animation = mAnimation->AsKeyboardScrollAnimation();
   MOZ_ASSERT(animation);
 
   animation->UpdateDestination(aEvent.mTimeStamp,
                                CSSPixel::ToAppUnits(destination),
                                nsSize(velocity.x, velocity.y));
 
-  return nsEventStatus_eConsumeNoDefault;
+  return nsEventStatus_eConsumeDoDefault;
 }
 
 CSSPoint
 AsyncPanZoomController::GetKeyboardDestination(const KeyboardScrollAction& aAction) const
 {
   CSSSize lineScrollSize;
   CSSSize pageScrollSize;
   CSSPoint scrollOffset;
--- a/gfx/layers/apz/src/FocusTarget.cpp
+++ b/gfx/layers/apz/src/FocusTarget.cpp
@@ -59,17 +59,37 @@ HasListenersForKeyEvents(nsIContent* aCo
   }
 
   WidgetEvent event(true, eVoidEvent);
   nsTArray<EventTarget*> targets;
   nsresult rv = EventDispatcher::Dispatch(aContent, nullptr, &event, nullptr,
       nullptr, nullptr, &targets);
   NS_ENSURE_SUCCESS(rv, false);
   for (size_t i = 0; i < targets.Length(); i++) {
-    if (targets[i]->HasUntrustedOrNonSystemGroupKeyEventListeners()) {
+    if (targets[i]->HasNonSystemGroupListenersForUntrustedKeyEvents()) {
+      return true;
+    }
+  }
+  return false;
+}
+
+static bool
+HasListenersForNonPassiveKeyEvents(nsIContent* aContent)
+{
+  if (!aContent) {
+    return false;
+  }
+
+  WidgetEvent event(true, eVoidEvent);
+  nsTArray<EventTarget*> targets;
+  nsresult rv = EventDispatcher::Dispatch(aContent, nullptr, &event, nullptr,
+      nullptr, nullptr, &targets);
+  NS_ENSURE_SUCCESS(rv, false);
+  for (size_t i = 0; i < targets.Length(); i++) {
+    if (targets[i]->HasNonPassiveNonSystemGroupListenersForUntrustedKeyEvents()) {
       return true;
     }
   }
   return false;
 }
 
 static bool
 IsEditableNode(nsINode* aNode)
@@ -111,37 +131,46 @@ FocusTarget::FocusTarget(nsIPresShell* a
     mType = FocusTarget::eNone;
     return;
   }
 
   // Find the focused content and use it to determine whether there are key event
   // listeners or whether key events will be targeted at a different process
   // through a remote browser.
   nsCOMPtr<nsIContent> focusedContent = presShell->GetFocusedContentInOurWindow();
+  nsCOMPtr<nsIContent> keyEventTarget = focusedContent;
+
+  // If there is no focused element then event dispatch goes to the body of
+  // the page if it exists or the root element.
+  if (!keyEventTarget) {
+    keyEventTarget = document->GetUnfocusedKeyEventTarget();
+  }
 
   // Check if there are key event listeners that could prevent default or change
   // the focus or selection of the page.
-  mFocusHasKeyEventListeners =
-    HasListenersForKeyEvents(focusedContent ? focusedContent.get()
-                                            : document->GetUnfocusedKeyEventTarget());
+  if (gfxPrefs::APZKeyboardPassiveListeners()) {
+    mFocusHasKeyEventListeners = HasListenersForNonPassiveKeyEvents(keyEventTarget.get());
+  } else {
+    mFocusHasKeyEventListeners = HasListenersForKeyEvents(keyEventTarget.get());
+  }
 
-  // Check if the focused element is content editable or if the document
+  // Check if the key event target is content editable or if the document
   // is in design mode.
-  if (IsEditableNode(focusedContent) ||
+  if (IsEditableNode(keyEventTarget) ||
       IsEditableNode(document)) {
     FT_LOG("Creating nil target with seq=%" PRIu64 ", kl=%d (disabling for editable node)\n",
            aFocusSequenceNumber,
            static_cast<int>(mFocusHasKeyEventListeners));
 
     mType = FocusTarget::eNone;
     return;
   }
 
-  // Check if the focused element is a remote browser
-  if (TabParent* browserParent = TabParent::GetFrom(focusedContent)) {
+  // Check if the key event target is a remote browser
+  if (TabParent* browserParent = TabParent::GetFrom(keyEventTarget)) {
     RenderFrameParent* rfp = browserParent->GetRenderFrame();
 
     // The globally focused element for scrolling is in a remote layer tree
     if (rfp) {
       FT_LOG("Creating reflayer target with seq=%" PRIu64 ", kl=%d, lt=%" PRIu64 "\n",
              aFocusSequenceNumber,
              mFocusHasKeyEventListeners,
              rfp->GetLayersId());
--- a/gfx/layers/apz/src/InputQueue.cpp
+++ b/gfx/layers/apz/src/InputQueue.cpp
@@ -301,17 +301,20 @@ InputQueue::ReceiveKeyboardInput(const R
   if (aOutInputBlockId) {
     *aOutInputBlockId = block->GetBlockId();
   }
 
   mQueuedInputs.AppendElement(MakeUnique<QueuedInput>(aEvent, *block));
 
   ProcessQueue();
 
-  return nsEventStatus_eConsumeNoDefault;
+  // If APZ is allowing passive listeners then we must dispatch the event to
+  // content, otherwise we can consume the event.
+  return gfxPrefs::APZKeyboardPassiveListeners() ? nsEventStatus_eConsumeDoDefault
+                                                 : nsEventStatus_eConsumeNoDefault;
 }
 
 static bool
 CanScrollTargetHorizontally(const PanGestureInput& aInitialEvent,
                             PanGestureBlockState* aBlock)
 {
   PanGestureInput horizontalComponent = aInitialEvent;
   horizontalComponent.mPanDisplacement.y = 0;
--- a/gfx/layers/client/ClientPaintedLayer.cpp
+++ b/gfx/layers/client/ClientPaintedLayer.cpp
@@ -257,23 +257,23 @@ ClientPaintedLayer::PaintOffMainThread()
                                               ClientManager()->GetPaintedLayerCallbackData());
 
     ctx = nullptr;
 
     // TODO: Fixup component alpha
     DrawTarget* targetOnWhite = nullptr;
     RefPtr<CapturedPaintState> capturedState
       = MakeAndAddRef<CapturedPaintState>(state.mRegionToDraw,
+                                          captureDT,
                                           target, targetOnWhite,
                                           capturedTransform,
                                           state.mMode,
                                           state.mContentType);
 
-    PaintThread::Get()->PaintContents(captureDT,
-                                      capturedState,
+    PaintThread::Get()->PaintContents(capturedState,
                                       RotatedContentBuffer::PrepareDrawTargetForPainting);
 
     mContentClient->ReturnDrawTargetToBuffer(target);
 
     didUpdate = true;
   }
   mContentClient->EndPaint(nullptr);
 
--- a/gfx/thebes/gfxPrefs.h
+++ b/gfx/thebes/gfxPrefs.h
@@ -309,16 +309,17 @@ private:
   DECL_GFX_PREF(Once, "apz.fling_curve_function_y2",           APZCurveFunctionY2, float, 1.0f);
   DECL_GFX_PREF(Live, "apz.fling_curve_threshold_inches_per_ms", APZCurveThreshold, float, -1.0f);
   DECL_GFX_PREF(Live, "apz.fling_friction",                    APZFlingFriction, float, 0.002f);
   DECL_GFX_PREF(Live, "apz.fling_min_velocity_threshold",      APZFlingMinVelocityThreshold, float, 0.5f);
   DECL_GFX_PREF(Live, "apz.fling_stop_on_tap_threshold",       APZFlingStopOnTapThreshold, float, 0.05f);
   DECL_GFX_PREF(Live, "apz.fling_stopped_threshold",           APZFlingStoppedThreshold, float, 0.01f);
   DECL_GFX_PREF(Live, "apz.frame_delay.enabled",               APZFrameDelayEnabled, bool, false);
   DECL_GFX_PREF(Once, "apz.keyboard.enabled",                  APZKeyboardEnabled, bool, false);
+  DECL_GFX_PREF(Live, "apz.keyboard.passive-listeners",        APZKeyboardPassiveListeners, bool, false);
   DECL_GFX_PREF(Live, "apz.max_velocity_inches_per_ms",        APZMaxVelocity, float, -1.0f);
   DECL_GFX_PREF(Once, "apz.max_velocity_queue_size",           APZMaxVelocityQueueSize, uint32_t, 5);
   DECL_GFX_PREF(Live, "apz.min_skate_speed",                   APZMinSkateSpeed, float, 1.0f);
   DECL_GFX_PREF(Live, "apz.minimap.enabled",                   APZMinimap, bool, false);
   DECL_GFX_PREF(Live, "apz.minimap.visibility.enabled",        APZMinimapVisibilityEnabled, bool, false);
   DECL_GFX_PREF(Live, "apz.one_touch_pinch.enabled",           APZOneTouchPinchEnabled, bool, true);
   DECL_GFX_PREF(Live, "apz.overscroll.enabled",                APZOverscrollEnabled, bool, false);
   DECL_GFX_PREF(Live, "apz.overscroll.min_pan_distance_ratio", APZMinPanDistanceRatio, float, 1.0f);
--- a/ipc/chromium/src/base/pickle.cc
+++ b/ipc/chromium/src/base/pickle.cc
@@ -519,16 +519,119 @@ void Pickle::EndWrite(uint32_t length) {
     MOZ_RELEASE_ASSERT(padding <= 4);
     static const char padding_data[4] = {
       kBytePaddingMarker, kBytePaddingMarker, kBytePaddingMarker, kBytePaddingMarker,
     };
     buffers_.WriteBytes(padding_data, padding);
   }
 }
 
+bool Pickle::WriteBool(bool value) {
+#ifdef FUZZING
+  Singleton<mozilla::ipc::Faulty>::get()->FuzzBool(&value);
+#endif
+  return WriteInt(value ? 1 : 0);
+}
+
+bool Pickle::WriteInt16(int16_t value) {
+#ifdef FUZZING
+  Singleton<mozilla::ipc::Faulty>::get()->FuzzInt16(&value);
+#endif
+  return WriteBytes(&value, sizeof(value));
+}
+
+bool Pickle::WriteUInt16(uint16_t value) {
+#ifdef FUZZING
+  Singleton<mozilla::ipc::Faulty>::get()->FuzzUInt16(&value);
+#endif
+  return WriteBytes(&value, sizeof(value));
+}
+
+bool Pickle::WriteInt(int value) {
+#ifdef FUZZING
+  Singleton<mozilla::ipc::Faulty>::get()->FuzzInt(&value);
+#endif
+  return WriteBytes(&value, sizeof(value));
+}
+
+bool Pickle::WriteLong(long value) {
+  // Always written as a 64-bit value since the size for this type can
+  // differ between architectures.
+#ifdef FUZZING
+  Singleton<mozilla::ipc::Faulty>::get()->FuzzLong(&value);
+#endif
+  return WriteInt64(int64_t(value));
+}
+
+bool Pickle::WriteULong(unsigned long value) {
+  // Always written as a 64-bit value since the size for this type can
+  // differ between architectures.
+#ifdef FUZZING
+  Singleton<mozilla::ipc::Faulty>::get()->FuzzULong(&value);
+#endif
+  return WriteUInt64(uint64_t(value));
+}
+
+bool Pickle::WriteSize(size_t value) {
+  // Always written as a 64-bit value since the size for this type can
+  // differ between architectures.
+#ifdef FUZZING
+  Singleton<mozilla::ipc::Faulty>::get()->FuzzSize(&value);
+#endif
+  return WriteUInt64(uint64_t(value));
+}
+
+bool Pickle::WriteInt32(int32_t value) {
+#ifdef FUZZING
+  Singleton<mozilla::ipc::Faulty>::get()->FuzzInt(&value);
+#endif
+  return WriteBytes(&value, sizeof(value));
+}
+
+bool Pickle::WriteUInt32(uint32_t value) {
+#ifdef FUZZING
+  Singleton<mozilla::ipc::Faulty>::get()->FuzzUInt32(&value);
+#endif
+  return WriteBytes(&value, sizeof(value));
+}
+
+bool Pickle::WriteInt64(int64_t value) {
+#ifdef FUZZING
+  Singleton<mozilla::ipc::Faulty>::get()->FuzzInt64(&value);
+#endif
+  return WriteBytes(&value, sizeof(value));
+}
+
+bool Pickle::WriteUInt64(uint64_t value) {
+#ifdef FUZZING
+  Singleton<mozilla::ipc::Faulty>::get()->FuzzUInt64(&value);
+#endif
+  return WriteBytes(&value, sizeof(value));
+}
+
+bool Pickle::WriteDouble(double value) {
+#ifdef FUZZING
+  Singleton<mozilla::ipc::Faulty>::get()->FuzzDouble(&value);
+#endif
+  return WriteBytes(&value, sizeof(value));
+}
+
+bool Pickle::WriteIntPtr(intptr_t value) {
+  // Always written as a 64-bit value since the size for this type can
+  // differ between architectures.
+  return WriteInt64(int64_t(value));
+}
+
+bool Pickle::WriteUnsignedChar(unsigned char value) {
+#ifdef FUZZING
+  Singleton<mozilla::ipc::Faulty>::get()->FuzzUChar(&value);
+#endif
+  return WriteBytes(&value, sizeof(value));
+}
+
 bool Pickle::WriteBytes(const void* data, uint32_t data_len, uint32_t alignment) {
   DCHECK(alignment == 4 || alignment == 8);
   DCHECK(intptr_t(header_) % alignment == 0);
 
   BeginWrite(data_len, alignment);
 
   buffers_.WriteBytes(reinterpret_cast<const char*>(data), data_len);
 
--- a/ipc/chromium/src/base/pickle.h
+++ b/ipc/chromium/src/base/pickle.h
@@ -138,105 +138,30 @@ class Pickle {
   // generated IPDL code, as it is used to trigger the IPC_READ_LATENCY_MS
   // telemetry probe.
   void EndRead(PickleIterator& iter, uint32_t ipcMessageType = 0) const;
 
   // Methods for adding to the payload of the Pickle.  These values are
   // appended to the end of the Pickle's payload.  When reading values from a
   // Pickle, it is important to read them in the order in which they were added
   // to the Pickle.
-  bool WriteBool(bool value) {
-#ifdef FUZZING
-    Singleton<mozilla::ipc::Faulty>::get()->FuzzBool(&value);
-#endif
-    return WriteInt(value ? 1 : 0);
-  }
-  bool WriteInt16(int16_t value) {
-#ifdef FUZZING
-    Singleton<mozilla::ipc::Faulty>::get()->FuzzInt16(&value);
-#endif
-    return WriteBytes(&value, sizeof(value));
-  }
-  bool WriteUInt16(uint16_t value) {
-#ifdef FUZZING
-    Singleton<mozilla::ipc::Faulty>::get()->FuzzUInt16(&value);
-#endif
-    return WriteBytes(&value, sizeof(value));
-  }
-  bool WriteInt(int value) {
-#ifdef FUZZING
-    Singleton<mozilla::ipc::Faulty>::get()->FuzzInt(&value);
-#endif
-    return WriteBytes(&value, sizeof(value));
-  }
-  bool WriteLong(long value) {
-    // Always written as a 64-bit value since the size for this type can
-    // differ between architectures.
-#ifdef FUZZING
-    Singleton<mozilla::ipc::Faulty>::get()->FuzzLong(&value);
-#endif
-    return WriteInt64(int64_t(value));
-  }
-  bool WriteULong(unsigned long value) {
-    // Always written as a 64-bit value since the size for this type can
-    // differ between architectures.
-#ifdef FUZZING
-    Singleton<mozilla::ipc::Faulty>::get()->FuzzULong(&value);
-#endif
-    return WriteUInt64(uint64_t(value));
-  }
-  bool WriteSize(size_t value) {
-    // Always written as a 64-bit value since the size for this type can
-    // differ between architectures.
-#ifdef FUZZING
-    Singleton<mozilla::ipc::Faulty>::get()->FuzzSize(&value);
-#endif
-    return WriteUInt64(uint64_t(value));
-  }
-  bool WriteInt32(int32_t value) {
-#ifdef FUZZING
-    Singleton<mozilla::ipc::Faulty>::get()->FuzzInt(&value);
-#endif
-    return WriteBytes(&value, sizeof(value));
-  }
-  bool WriteUInt32(uint32_t value) {
-#ifdef FUZZING
-    Singleton<mozilla::ipc::Faulty>::get()->FuzzUInt32(&value);
-#endif
-    return WriteBytes(&value, sizeof(value));
-  }
-  bool WriteInt64(int64_t value) {
-#ifdef FUZZING
-    Singleton<mozilla::ipc::Faulty>::get()->FuzzInt64(&value);
-#endif
-    return WriteBytes(&value, sizeof(value));
-  }
-  bool WriteUInt64(uint64_t value) {
-#ifdef FUZZING
-    Singleton<mozilla::ipc::Faulty>::get()->FuzzUInt64(&value);
-#endif
-    return WriteBytes(&value, sizeof(value));
-  }
-  bool WriteDouble(double value) {
-#ifdef FUZZING
-    Singleton<mozilla::ipc::Faulty>::get()->FuzzDouble(&value);
-#endif
-    return WriteBytes(&value, sizeof(value));
-  }
-  bool WriteIntPtr(intptr_t value) {
-    // Always written as a 64-bit value since the size for this type can
-    // differ between architectures.
-    return WriteInt64(int64_t(value));
-  }
-  bool WriteUnsignedChar(unsigned char value) {
-#ifdef FUZZING
-    Singleton<mozilla::ipc::Faulty>::get()->FuzzUChar(&value);
-#endif
-    return WriteBytes(&value, sizeof(value));
-  }
+  bool WriteBool(bool value);
+  bool WriteInt16(int16_t value);
+  bool WriteUInt16(uint16_t value);
+  bool WriteInt(int value);
+  bool WriteLong(long value);
+  bool WriteULong(unsigned long value);
+  bool WriteSize(size_t value);
+  bool WriteInt32(int32_t value);
+  bool WriteUInt32(uint32_t value);
+  bool WriteInt64(int64_t value);
+  bool WriteUInt64(uint64_t value);
+  bool WriteDouble(double value);
+  bool WriteIntPtr(intptr_t value);
+  bool WriteUnsignedChar(unsigned char value);
   bool WriteString(const std::string& value);
   bool WriteWString(const std::wstring& value);
   bool WriteData(const char* data, uint32_t length);
   bool WriteBytes(const void* data, uint32_t data_len,
                   uint32_t alignment = sizeof(memberAlignmentType));
 
   bool WriteSentinel(uint32_t sentinel)
 #ifdef MOZ_PICKLE_SENTINEL_CHECKING
--- a/js/src/builtin/Promise.cpp
+++ b/js/src/builtin/Promise.cpp
@@ -31,28 +31,40 @@ MillisecondsSinceStartup()
 {
     auto now = mozilla::TimeStamp::Now();
     return (now - mozilla::TimeStamp::ProcessCreation()).ToMilliseconds();
 }
 
 enum PromiseHandler {
     PromiseHandlerIdentity = 0,
     PromiseHandlerThrower,
-    PromiseHandlerAsyncFunctionAwaitFulfilled,
-    PromiseHandlerAsyncFunctionAwaitRejected,
-    PromiseHandlerAsyncGeneratorAwaitFulfilled,
-    PromiseHandlerAsyncGeneratorAwaitRejected,
-
-    // Async Iteration proposal 6.1.1.2.1.
-    // Async iterator handlers take the resolved value and create new iterator
-    // objects.  To do so it needs to forward whether the iterator is done. In
-    // spec, this is achieved via the [[Done]] internal slot. We enumerate both
-    // true and false cases here.
-    PromiseHandlerAsyncIteratorValueUnwrapDone,
-    PromiseHandlerAsyncIteratorValueUnwrapNotDone,
+
+    // ES 2018 draft 25.5.5.4-5.
+    PromiseHandlerAsyncFunctionAwaitedFulfilled,
+    PromiseHandlerAsyncFunctionAwaitedRejected,
+
+    // Async Iteration proposal 4.1.
+    PromiseHandlerAsyncGeneratorAwaitedFulfilled,
+    PromiseHandlerAsyncGeneratorAwaitedRejected,
+
+    // Async Iteration proposal 11.4.3.5.1-2.
+    PromiseHandlerAsyncGeneratorResumeNextReturnFulfilled,
+    PromiseHandlerAsyncGeneratorResumeNextReturnRejected,
+
+    // Async Iteration proposal 11.4.3.7 steps 8.c-e.
+    PromiseHandlerAsyncGeneratorYieldReturnAwaitedFulfilled,
+    PromiseHandlerAsyncGeneratorYieldReturnAwaitedRejected,
+
+    // Async Iteration proposal 11.1.3.2.5.
+    // Async-from-Sync iterator handlers take the resolved value and create new
+    // iterator objects.  To do so it needs to forward whether the iterator is
+    // done. In spec, this is achieved via the [[Done]] internal slot. We
+    // enumerate both true and false cases here.
+    PromiseHandlerAsyncFromSyncIteratorValueUnwrapDone,
+    PromiseHandlerAsyncFromSyncIteratorValueUnwrapNotDone,
 };
 
 enum ResolutionMode {
     ResolveMode,
     RejectMode
 };
 
 enum ResolveFunctionSlots {
@@ -192,18 +204,18 @@ enum ReactionRecordSlots {
     ReactionRecordSlot_HandlerArg,
     ReactionRecordSlot_Generator,
     ReactionRecordSlots,
 };
 
 #define REACTION_FLAG_RESOLVED                  0x1
 #define REACTION_FLAG_FULFILLED                 0x2
 #define REACTION_FLAG_IGNORE_DEFAULT_RESOLUTION 0x4
-#define REACTION_FLAG_ASYNC_FUNCTION_AWAIT      0x8
-#define REACTION_FLAG_ASYNC_GENERATOR_AWAIT     0x10
+#define REACTION_FLAG_ASYNC_FUNCTION            0x8
+#define REACTION_FLAG_ASYNC_GENERATOR           0x10
 
 // ES2016, 25.4.1.2.
 class PromiseReactionRecord : public NativeObject
 {
   public:
     static const Class class_;
 
     JSObject* promise() { return getFixedSlot(ReactionRecordSlot_Promise).toObjectOrNull(); }
@@ -220,38 +232,38 @@ class PromiseReactionRecord : public Nat
         int32_t flags = this->flags();
         MOZ_ASSERT(!(flags & REACTION_FLAG_RESOLVED));
         MOZ_ASSERT(state != JS::PromiseState::Pending, "Can't revert a reaction to pending.");
         flags |= REACTION_FLAG_RESOLVED;
         if (state == JS::PromiseState::Fulfilled)
             flags |= REACTION_FLAG_FULFILLED;
         setFixedSlot(ReactionRecordSlot_Flags, Int32Value(flags));
     }
-    void setIsAsyncFunctionAwait() {
+    void setIsAsyncFunction() {
         int32_t flags = this->flags();
-        flags |= REACTION_FLAG_ASYNC_FUNCTION_AWAIT;
+        flags |= REACTION_FLAG_ASYNC_FUNCTION;
         setFixedSlot(ReactionRecordSlot_Flags, Int32Value(flags));
     }
-    bool isAsyncFunctionAwait() {
+    bool isAsyncFunction() {
         int32_t flags = this->flags();
-        return flags & REACTION_FLAG_ASYNC_FUNCTION_AWAIT;
+        return flags & REACTION_FLAG_ASYNC_FUNCTION;
     }
-    void setIsAsyncGeneratorAwait(Handle<AsyncGeneratorObject*> asyncGenObj) {
+    void setIsAsyncGenerator(Handle<AsyncGeneratorObject*> asyncGenObj) {
         int32_t flags = this->flags();
-        flags |= REACTION_FLAG_ASYNC_GENERATOR_AWAIT;
+        flags |= REACTION_FLAG_ASYNC_GENERATOR;
         setFixedSlot(ReactionRecordSlot_Flags, Int32Value(flags));
 
         setFixedSlot(ReactionRecordSlot_Generator, ObjectValue(*asyncGenObj));
     }
-    bool isAsyncGeneratorAwait() {
+    bool isAsyncGenerator() {
         int32_t flags = this->flags();
-        return flags & REACTION_FLAG_ASYNC_GENERATOR_AWAIT;
+        return flags & REACTION_FLAG_ASYNC_GENERATOR;
     }
     AsyncGeneratorObject* asyncGenerator() {
-        MOZ_ASSERT(isAsyncGeneratorAwait());
+        MOZ_ASSERT(isAsyncGenerator());
         return &getFixedSlot(ReactionRecordSlot_Generator).toObject()
                                                           .as<AsyncGeneratorObject>();
     }
     Value handler() {
         MOZ_ASSERT(targetState() != JS::PromiseState::Pending);
         uint32_t slot = targetState() == JS::PromiseState::Fulfilled
                         ? ReactionRecordSlot_OnFulfilled
                         : ReactionRecordSlot_OnRejected;
@@ -854,65 +866,85 @@ TriggerPromiseReactions(JSContext* cx, H
         if (!EnqueuePromiseReactionJob(cx, reaction, valueOrReason, state))
             return false;
     }
 
     return true;
 }
 
 static MOZ_MUST_USE bool
-AsyncFunctionAwaitPromiseReactionJob(JSContext* cx, Handle<PromiseReactionRecord*> reaction,
-                                     MutableHandleValue rval)
+AsyncFunctionPromiseReactionJob(JSContext* cx, Handle<PromiseReactionRecord*> reaction,
+                                MutableHandleValue rval)
 {
-    MOZ_ASSERT(reaction->isAsyncFunctionAwait());
+    MOZ_ASSERT(reaction->isAsyncFunction());
 
     RootedValue handlerVal(cx, reaction->handler());
     RootedValue argument(cx, reaction->handlerArg());
     Rooted<PromiseObject*> resultPromise(cx, &reaction->promise()->as<PromiseObject>());
     RootedValue generatorVal(cx, resultPromise->getFixedSlot(PromiseSlot_AwaitGenerator));
 
     int32_t handlerNum = int32_t(handlerVal.toNumber());
-    MOZ_ASSERT(handlerNum == PromiseHandlerAsyncFunctionAwaitFulfilled ||
-               handlerNum == PromiseHandlerAsyncFunctionAwaitRejected);
 
     // Await's handlers don't return a value, nor throw exception.
     // They fail only on OOM.
-    if (handlerNum == PromiseHandlerAsyncFunctionAwaitFulfilled) {
+    if (handlerNum == PromiseHandlerAsyncFunctionAwaitedFulfilled) {
         if (!AsyncFunctionAwaitedFulfilled(cx, resultPromise, generatorVal, argument))
             return false;
     } else {
+        MOZ_ASSERT(handlerNum == PromiseHandlerAsyncFunctionAwaitedRejected);
         if (!AsyncFunctionAwaitedRejected(cx, resultPromise, generatorVal, argument))
             return false;
     }
 
     rval.setUndefined();
     return true;
 }
 
 static MOZ_MUST_USE bool
-AsyncGeneratorAwaitPromiseReactionJob(JSContext* cx, Handle<PromiseReactionRecord*> reaction,
-                                      MutableHandleValue rval)
+AsyncGeneratorPromiseReactionJob(JSContext* cx, Handle<PromiseReactionRecord*> reaction,
+                                 MutableHandleValue rval)
 {
-    MOZ_ASSERT(reaction->isAsyncGeneratorAwait());
+    MOZ_ASSERT(reaction->isAsyncGenerator());
 
     RootedValue handlerVal(cx, reaction->handler());
     RootedValue argument(cx, reaction->handlerArg());
     Rooted<AsyncGeneratorObject*> asyncGenObj(cx, reaction->asyncGenerator());
 
     int32_t handlerNum = int32_t(handlerVal.toNumber());
-    MOZ_ASSERT(handlerNum == PromiseHandlerAsyncGeneratorAwaitFulfilled ||
-               handlerNum == PromiseHandlerAsyncGeneratorAwaitRejected);
 
     // Await's handlers don't return a value, nor throw exception.
     // They fail only on OOM.
-    if (handlerNum == PromiseHandlerAsyncGeneratorAwaitFulfilled) {
+    if (handlerNum == PromiseHandlerAsyncGeneratorAwaitedFulfilled) {
+        // 4.1.1.
         if (!AsyncGeneratorAwaitedFulfilled(cx, asyncGenObj, argument))
             return false;
+    } else if (handlerNum == PromiseHandlerAsyncGeneratorAwaitedRejected) {
+        // 4.1.2.
+        if (!AsyncGeneratorAwaitedRejected(cx, asyncGenObj, argument))
+            return false;
+    } else if (handlerNum == PromiseHandlerAsyncGeneratorResumeNextReturnFulfilled) {
+        asyncGenObj->setCompleted();
+        // 11.4.3.5.1 step 1.
+        if (!AsyncGeneratorResolve(cx, asyncGenObj, argument, true))
+            return false;
+    } else if (handlerNum == PromiseHandlerAsyncGeneratorResumeNextReturnRejected) {
+        asyncGenObj->setCompleted();
+        // 11.4.3.5.2 step 1.
+        if (!AsyncGeneratorReject(cx, asyncGenObj, argument))
+            return false;
+    } else if (handlerNum == PromiseHandlerAsyncGeneratorYieldReturnAwaitedFulfilled) {
+        asyncGenObj->setExecuting();
+        // 11.4.3.7 steps 8.d-e.
+        if (!AsyncGeneratorYieldReturnAwaitedFulfilled(cx, asyncGenObj, argument))
+            return false;
     } else {
-        if (!AsyncGeneratorAwaitedRejected(cx, asyncGenObj, argument))
+        MOZ_ASSERT(handlerNum == PromiseHandlerAsyncGeneratorYieldReturnAwaitedRejected);
+        asyncGenObj->setExecuting();
+        // 11.4.3.7 step 8.c.
+        if (!AsyncGeneratorYieldReturnAwaitedRejected(cx, asyncGenObj, argument))
             return false;
     }
 
     rval.setUndefined();
     return true;
 }
 
 // ES2016, 25.4.2.1.
@@ -953,20 +985,20 @@ PromiseReactionJob(JSContext* cx, unsign
             return false;
         }
         MOZ_RELEASE_ASSERT(reactionObj->is<PromiseReactionRecord>());
         ac.emplace(cx, reactionObj);
     }
 
     // Steps 1-2.
     Rooted<PromiseReactionRecord*> reaction(cx, &reactionObj->as<PromiseReactionRecord>());
-    if (reaction->isAsyncFunctionAwait())
-        return AsyncFunctionAwaitPromiseReactionJob(cx, reaction, args.rval());
-    if (reaction->isAsyncGeneratorAwait())
-        return AsyncGeneratorAwaitPromiseReactionJob(cx, reaction, args.rval());
+    if (reaction->isAsyncFunction())
+        return AsyncFunctionPromiseReactionJob(cx, reaction, args.rval());
+    if (reaction->isAsyncGenerator())
+        return AsyncGeneratorPromiseReactionJob(cx, reaction, args.rval());
 
     // Step 3.
     RootedValue handlerVal(cx, reaction->handler());
 
     RootedValue argument(cx, reaction->handlerArg());
 
     RootedValue handlerResult(cx);
     ResolutionMode resolutionMode = ResolveMode;
@@ -978,21 +1010,21 @@ PromiseReactionJob(JSContext* cx, unsign
         // Step 4.
         if (handlerNum == PromiseHandlerIdentity) {
             handlerResult = argument;
         } else if (handlerNum == PromiseHandlerThrower) {
             // Step 5.
             resolutionMode = RejectMode;
             handlerResult = argument;
         } else {
-            MOZ_ASSERT(handlerNum == PromiseHandlerAsyncIteratorValueUnwrapDone ||
-                       handlerNum == PromiseHandlerAsyncIteratorValueUnwrapNotDone);
-
-            bool done = handlerNum == PromiseHandlerAsyncIteratorValueUnwrapDone;
-            // Async Iteration proposal 6.1.1.2.1 step 1.
+            MOZ_ASSERT(handlerNum == PromiseHandlerAsyncFromSyncIteratorValueUnwrapDone ||
+                       handlerNum == PromiseHandlerAsyncFromSyncIteratorValueUnwrapNotDone);
+
+            bool done = handlerNum == PromiseHandlerAsyncFromSyncIteratorValueUnwrapDone;
+            // Async Iteration proposal 11.1.3.2.5 step 1.
             RootedObject resultObj(cx, CreateIterResultObject(cx, argument, done));
             if (!resultObj)
                 return false;
 
             handlerResult = ObjectValue(*resultObj);
         }
     } else {
         // Step 6.
@@ -2213,134 +2245,130 @@ js::OriginalPromiseThen(JSContext* cx, H
 
 static MOZ_MUST_USE bool PerformPromiseThenWithReaction(JSContext* cx,
                                                         Handle<PromiseObject*> promise,
                                                         Handle<PromiseReactionRecord*> reaction);
 
 // Some async/await functions are implemented here instead of
 // js/src/builtin/AsyncFunction.cpp, to call Promise internal functions.
 
-// Async Functions proposal 1.1.8 and 1.2.14 step 1.
+// ES 2018 draft 14.6.11 and 14.7.14 step 1.
 MOZ_MUST_USE PromiseObject*
 js::CreatePromiseObjectForAsync(JSContext* cx, HandleValue generatorVal)
 {
     // Step 1.
     Rooted<PromiseObject*> promise(cx, CreatePromiseObjectWithoutResolutionFunctions(cx));
     if (!promise)
         return nullptr;
 
     AddPromiseFlags(*promise, PROMISE_FLAG_ASYNC);
     promise->setFixedSlot(PromiseSlot_AwaitGenerator, generatorVal);
     return promise;
 }
 
-// Async Functions proposal 2.2 steps 3.f, 3.g.
+// ES 2018 draft 25.5.5.2 steps 3.f, 3.g.
 MOZ_MUST_USE bool
 js::AsyncFunctionThrown(JSContext* cx, Handle<PromiseObject*> resultPromise)
 {
     // Step 3.f.
     RootedValue exc(cx);
     if (!MaybeGetAndClearException(cx, &exc))
         return false;
 
     if (!RejectMaybeWrappedPromise(cx, resultPromise, exc))
         return false;
 
     // Step 3.g.
     return true;
 }
 
-// Async Functions proposal 2.2 steps 3.d-e, 3.g.
+// ES 2018 draft 25.5.5.2 steps 3.d-e, 3.g.
 MOZ_MUST_USE bool
 js::AsyncFunctionReturned(JSContext* cx, Handle<PromiseObject*> resultPromise, HandleValue value)
 {
     // Steps 3.d-e.
     if (!ResolvePromiseInternal(cx, resultPromise, value))
         return false;
 
     // Step 3.g.
     return true;
 }
 
-// Async Functions proposal 2.3 steps 2-8.
-MOZ_MUST_USE bool
-js::AsyncFunctionAwait(JSContext* cx, Handle<PromiseObject*> resultPromise, HandleValue value)
+// Helper function that performs the equivalent steps as
+// Async Iteration proposal 4.1 Await steps 2-3, 6-9 or similar.
+template <typename T>
+static MOZ_MUST_USE bool
+InternalAwait(JSContext* cx, HandleValue value, HandleObject resultPromise,
+              HandleValue onFulfilled, HandleValue onRejected, T extraStep)
 {
+    MOZ_ASSERT(onFulfilled.isNumber() || onFulfilled.isObject());
+    MOZ_ASSERT(onRejected.isNumber() || onRejected.isObject());
+
     // Step 2.
     Rooted<PromiseObject*> promise(cx, CreatePromiseObjectWithoutResolutionFunctions(cx));
     if (!promise)
         return false;
 
-    // Steps 3.
+    // Step 3.
     if (!ResolvePromiseInternal(cx, promise, value))
         return false;
 
-    // Steps 4-5.
-    RootedValue onFulfilled(cx, Int32Value(PromiseHandlerAsyncFunctionAwaitFulfilled));
-    RootedValue onRejected(cx, Int32Value(PromiseHandlerAsyncFunctionAwaitRejected));
-
     RootedObject incumbentGlobal(cx);
     if (!GetObjectFromIncumbentGlobal(cx, &incumbentGlobal))
         return false;
 
-    // Steps 6-7.
+    // Step 7-8.
     Rooted<PromiseReactionRecord*> reaction(cx, NewReactionRecord(cx, resultPromise,
                                                                   onFulfilled, onRejected,
                                                                   nullptr, nullptr,
                                                                   incumbentGlobal));
     if (!reaction)
         return false;
 
-    reaction->setIsAsyncFunctionAwait();
-
-    // Step 8.
+    // Step 6.
+    extraStep(reaction);
+
+    // Step 9.
     return PerformPromiseThenWithReaction(cx, promise, reaction);
 }
 
-// Async Iteration proposal 5.1 steps 2-9.
+// ES 2018 draft 25.5.5.3 steps 2-10.
+MOZ_MUST_USE bool
+js::AsyncFunctionAwait(JSContext* cx, Handle<PromiseObject*> resultPromise, HandleValue value)
+{
+    // Steps 4-5.
+    RootedValue onFulfilled(cx, Int32Value(PromiseHandlerAsyncFunctionAwaitedFulfilled));
+    RootedValue onRejected(cx, Int32Value(PromiseHandlerAsyncFunctionAwaitedRejected));
+
+    // Steps 2-3, 6-10.
+    auto extra = [](Handle<PromiseReactionRecord*> reaction) {
+        reaction->setIsAsyncFunction();
+    };
+    return InternalAwait(cx, value, resultPromise, onFulfilled, onRejected, extra);
+}
+
+// Async Iteration proposal 4.1 Await steps 2-9.
 MOZ_MUST_USE bool
 js::AsyncGeneratorAwait(JSContext* cx, Handle<AsyncGeneratorObject*> asyncGenObj,
                         HandleValue value)
 {
-    // Step 2.
-    Rooted<PromiseObject*> promise(cx, CreatePromiseObjectWithoutResolutionFunctions(cx));
-    if (!promise)
-        return false;
-
-    // Steps 3.
-    if (!ResolvePromiseInternal(cx, promise, value))
-        return false;
-
     // Steps 4-5.
-    RootedValue onFulfilled(cx, Int32Value(PromiseHandlerAsyncGeneratorAwaitFulfilled));
-    RootedValue onRejected(cx, Int32Value(PromiseHandlerAsyncGeneratorAwaitRejected));
-
-    RootedObject incumbentGlobal(cx);
-    if (!GetObjectFromIncumbentGlobal(cx, &incumbentGlobal))
-        return false;
-
-    // Step 6 (skipped).
-
-    // Steps 7-8.
-    Rooted<PromiseReactionRecord*> reaction(cx, NewReactionRecord(cx, nullptr,
-                                                                  onFulfilled, onRejected,
-                                                                  nullptr, nullptr,
-                                                                  incumbentGlobal));
-    if (!reaction)
-        return false;
-
-    reaction->setIsAsyncGeneratorAwait(asyncGenObj);
-
-    // Step 9.
-    return PerformPromiseThenWithReaction(cx, promise, reaction);
+    RootedValue onFulfilled(cx, Int32Value(PromiseHandlerAsyncGeneratorAwaitedFulfilled));
+    RootedValue onRejected(cx, Int32Value(PromiseHandlerAsyncGeneratorAwaitedRejected));
+
+    // Steps 2-3, 6-9.
+    auto extra = [&](Handle<PromiseReactionRecord*> reaction) {
+        reaction->setIsAsyncGenerator(asyncGenObj);
+    };
+    return InternalAwait(cx, value, nullptr, onFulfilled, onRejected, extra);
 }
 
-// Async Iteration proposal 6.1.3.2.1 %AsyncFromSyncIteratorPrototype%.next
-// Async Iteration proposal 6.1.3.2.2 %AsyncFromSyncIteratorPrototype%.return
-// Async Iteration proposal 6.1.3.2.3 %AsyncFromSyncIteratorPrototype%.throw
+// Async Iteration proposal 11.1.3.2.1 %AsyncFromSyncIteratorPrototype%.next
+// Async Iteration proposal 11.1.3.2.2 %AsyncFromSyncIteratorPrototype%.return
+// Async Iteration proposal 11.1.3.2.3 %AsyncFromSyncIteratorPrototype%.throw
 bool
 js::AsyncFromSyncIteratorMethod(JSContext* cx, CallArgs& args, CompletionKind completionKind)
 {
     // Step 1.
     RootedValue thisVal(cx, args.thisv());
 
     // Step 2.
     RootedObject resultPromise(cx, CreatePromiseObjectWithoutResolutionFunctions(cx));
@@ -2367,21 +2395,21 @@ js::AsyncFromSyncIteratorMethod(JSContex
         cx, &thisVal.toObject().as<AsyncFromSyncIteratorObject>());
 
     // Step 4.
     RootedObject iter(cx, asyncIter->iterator());
 
     RootedValue resultVal(cx);
     RootedValue func(cx);
     if (completionKind == CompletionKind::Normal) {
-        // 6.1.3.2.1 steps 5-6 (partially).
+        // 11.1.3.2.1 steps 5-6 (partially).
         if (!GetProperty(cx, iter, iter, cx->names().next, &func))
             return AbruptRejectPromise(cx, args, resultPromise, nullptr);
     } else if (completionKind == CompletionKind::Return) {
-        // 6.1.3.2.2 steps 5-6.
+        // 11.1.3.2.2 steps 5-6.
         if (!GetProperty(cx, iter, iter, cx->names().return_, &func))
             return AbruptRejectPromise(cx, args, resultPromise, nullptr);
 
         // Step 7.
         if (func.isNullOrUndefined()) {
             // Step 7.a.
             RootedObject resultObj(cx, CreateIterResultObject(cx, args.get(0), true));
             if (!resultObj)
@@ -2393,43 +2421,43 @@ js::AsyncFromSyncIteratorMethod(JSContex
             if (!ResolvePromiseInternal(cx, resultPromise, resultVal))
                 return AbruptRejectPromise(cx, args, resultPromise, nullptr);
 
             // Step 7.c.
             args.rval().setObject(*resultPromise);
             return true;
         }
     } else {
-        // 6.1.3.2.3 steps 5-6.
+        // 11.1.3.2.3 steps 5-6.
         MOZ_ASSERT(completionKind == CompletionKind::Throw);
         if (!GetProperty(cx, iter, iter, cx->names().throw_, &func))
             return AbruptRejectPromise(cx, args, resultPromise, nullptr);
 
         // Step 7.
         if (func.isNullOrUndefined()) {
             // Step 7.a.
             if (!RejectMaybeWrappedPromise(cx, resultPromise, args.get(0)))
                 return AbruptRejectPromise(cx, args, resultPromise, nullptr);
 
             // Step 7.b.
             args.rval().setObject(*resultPromise);
             return true;
         }
     }
 
-    // 6.1.3.2.1 steps 5-6 (partially).
-    // 6.1.3.2.2, 6.1.3.2.3 steps 8-9.
+    // 11.1.3.2.1 steps 5-6 (partially).
+    // 11.1.3.2.2, 11.1.3.2.3 steps 8-9.
     RootedValue iterVal(cx, ObjectValue(*iter));
     FixedInvokeArgs<1> args2(cx);
     args2[0].set(args.get(0));
     if (!js::Call(cx, func, iterVal, args2, &resultVal))
         return AbruptRejectPromise(cx, args, resultPromise, nullptr);
 
-    // 6.1.3.2.1 steps 5-6 (partially).
-    // 6.1.3.2.2, 6.1.3.2.3 steps 10.
+    // 11.1.3.2.1 steps 5-6 (partially).
+    // 11.1.3.2.2, 11.1.3.2.3 steps 10.
     if (!resultVal.isObject()) {
         CheckIsObjectKind kind;
         switch (completionKind) {
           case CompletionKind::Normal:
             kind = CheckIsObjectKind::IteratorNext;
             break;
           case CompletionKind::Throw:
             kind = CheckIsObjectKind::IteratorThrow;
@@ -2439,65 +2467,51 @@ js::AsyncFromSyncIteratorMethod(JSContex
             break;
         }
         MOZ_ALWAYS_FALSE(ThrowCheckIsObject(cx, kind));
         return AbruptRejectPromise(cx, args, resultPromise, nullptr);
     }
 
     RootedObject resultObj(cx, &resultVal.toObject());
 
-    // Following step numbers are for 6.1.3.2.1.
-    // For 6.1.3.2.2 and 6.1.3.2.3, steps 7-16 corresponds to steps 11-20.
+    // Following step numbers are for 11.1.3.2.1.
+    // For 11.1.3.2.2 and 11.1.3.2.3, steps 7-16 corresponds to steps 11-20.
 
     // Steps 7-8.
     RootedValue doneVal(cx);
     if (!GetProperty(cx, resultObj, resultObj, cx->names().done, &doneVal))
         return AbruptRejectPromise(cx, args, resultPromise, nullptr);
     bool done = ToBoolean(doneVal);
 
     // Steps 9-10.
     RootedValue value(cx);
     if (!GetProperty(cx, resultObj, resultObj, cx->names().value, &value))
         return AbruptRejectPromise(cx, args, resultPromise, nullptr);
 
-    // Step 11.
-    Rooted<PromiseObject*> promise(cx, CreatePromiseObjectWithoutResolutionFunctions(cx));
-    if (!promise)
-        return false;
-
-    // Step 12.
-    if (!ResolvePromiseInternal(cx, promise, value))
-        return false;
-
     // Steps 13-14.
     RootedValue onFulfilled(cx, Int32Value(done
-                                           ? PromiseHandlerAsyncIteratorValueUnwrapDone
-                                           : PromiseHandlerAsyncIteratorValueUnwrapNotDone));
-
-    RootedObject incumbentGlobal(cx);
-    if (!GetObjectFromIncumbentGlobal(cx, &incumbentGlobal))
-        return false;
-
-    // Step 15.
-    Rooted<PromiseReactionRecord*> reaction(cx, NewReactionRecord(cx, resultPromise, onFulfilled,
-                                                                  UndefinedHandleValue,
-                                                                  nullptr, nullptr,
-                                                                  incumbentGlobal));
-    if (!reaction)
-        return false;
-
-    if (!PerformPromiseThenWithReaction(cx, promise, reaction))
+                                           ? PromiseHandlerAsyncFromSyncIteratorValueUnwrapDone
+                                           : PromiseHandlerAsyncFromSyncIteratorValueUnwrapNotDone));
+    RootedValue onRejected(cx, Int32Value(PromiseHandlerThrower));
+
+    // Steps 11-12, 15.
+    auto extra = [](Handle<PromiseReactionRecord*> reaction) {
+    };
+    if (!InternalAwait(cx, value, resultPromise, onFulfilled, onRejected, extra))
         return false;
 
     // Step 16.
     args.rval().setObject(*resultPromise);
     return true;
 }
 
-// Async Iteration proposal 6.4.3.3.
+static MOZ_MUST_USE bool
+AsyncGeneratorResumeNext(JSContext* cx, Handle<AsyncGeneratorObject*> asyncGenObj);
+
+// Async Iteration proposal 11.4.3.3.
 MOZ_MUST_USE bool
 js::AsyncGeneratorResolve(JSContext* cx, Handle<AsyncGeneratorObject*> asyncGenObj,
                           HandleValue value, bool done)
 {
     // Step 1 (implicit).
 
     // Steps 2-3.
     MOZ_ASSERT(!asyncGenObj->isQueueEmpty());
@@ -2507,53 +2521,35 @@ js::AsyncGeneratorResolve(JSContext* cx,
         cx, AsyncGeneratorObject::dequeueRequest(cx, asyncGenObj));
     if (!request)
         return false;
 
     // Step 5.
     RootedObject resultPromise(cx, request->promise());
 
     // Step 6.
-    Rooted<PromiseObject*> promise(cx, CreatePromiseObjectWithoutResolutionFunctions(cx));
-    if (!promise)
+    RootedObject resultObj(cx, CreateIterResultObject(cx, value, done));
+    if (!resultObj)
         return false;
 
+    RootedValue resultValue(cx, ObjectValue(*resultObj));
+
     // Step 7.
-    if (!ResolvePromiseInternal(cx, promise, value))
-        return false;
-
-    // Steps 8-9.
-    RootedValue onFulfilled(cx, Int32Value(done
-                                           ? PromiseHandlerAsyncIteratorValueUnwrapDone
-                                           : PromiseHandlerAsyncIteratorValueUnwrapNotDone));
-
-    RootedObject incumbentGlobal(cx);
-    if (!GetObjectFromIncumbentGlobal(cx, &incumbentGlobal))
+    if (!ResolvePromiseInternal(cx, resultPromise, resultValue))
         return false;
 
-    // Step 10.
-    Rooted<PromiseReactionRecord*> reaction(cx, NewReactionRecord(cx, resultPromise, onFulfilled,
-                                                                  UndefinedHandleValue,
-                                                                  nullptr, nullptr,
-                                                                  incumbentGlobal));
-    if (!reaction)
-        return false;
-
-    if (!PerformPromiseThenWithReaction(cx, promise, reaction))
-        return false;
-
-    // Step 11.
+    // Step 8.
     if (!AsyncGeneratorResumeNext(cx, asyncGenObj))
         return false;
 
-    // Step 12.
+    // Step 9.
     return true;
 }
 
-// Async Iteration proposal 6.4.3.4.
+// Async Iteration proposal 11.4.3.4.
 MOZ_MUST_USE bool
 js::AsyncGeneratorReject(JSContext* cx, Handle<AsyncGeneratorObject*> asyncGenObj,
                          HandleValue exception)
 {
     // Step 1 (implicit).
 
     // Steps 2-3.
     MOZ_ASSERT(!asyncGenObj->isQueueEmpty());
@@ -2574,17 +2570,108 @@ js::AsyncGeneratorReject(JSContext* cx, 
     // Step 7.
     if (!AsyncGeneratorResumeNext(cx, asyncGenObj))
         return false;
 
     // Step 8.
     return true;
 }
 
-// Async Iteration proposal 6.4.3.6.
+// Async Iteration proposal 11.4.3.5.
+static MOZ_MUST_USE bool
+AsyncGeneratorResumeNext(JSContext* cx, Handle<AsyncGeneratorObject*> asyncGenObj)
+{
+    // Step 1 (implicit).
+
+    // Steps 2-3.
+    MOZ_ASSERT(!asyncGenObj->isExecuting());
+
+    // Step 4.
+    if (asyncGenObj->isAwaitingYieldReturn() || asyncGenObj->isAwaitingReturn())
+        return true;
+
+    // Steps 5-6.
+    if (asyncGenObj->isQueueEmpty())
+        return true;
+
+    // Steps 7-8.
+    Rooted<AsyncGeneratorRequest*> request(
+        cx, AsyncGeneratorObject::peekRequest(cx, asyncGenObj));
+    if (!request)
+        return false;
+
+    // Step 9.
+    CompletionKind completionKind = request->completionKind();
+
+    // Step 10.
+    if (completionKind != CompletionKind::Normal) {
+        // Step 10.a.
+        if (asyncGenObj->isSuspendedStart())
+            asyncGenObj->setCompleted();
+
+        // Step 10.b.
+        if (asyncGenObj->isCompleted()) {
+            RootedValue value(cx, request->completionValue());
+
+            // Step 10.b.i.
+            if (completionKind == CompletionKind::Return) {
+                // Steps 10.b.i.1.
+                asyncGenObj->setAwaitingReturn();
+
+                // Steps 10.b.i.4-6 (reordered).
+                RootedValue onFulfilled(cx, Int32Value(PromiseHandlerAsyncGeneratorResumeNextReturnFulfilled));
+                RootedValue onRejected(cx, Int32Value(PromiseHandlerAsyncGeneratorResumeNextReturnRejected));
+
+                // Steps 10.b.i.2-3, 7-10.
+                auto extra = [&](Handle<PromiseReactionRecord*> reaction) {
+                    reaction->setIsAsyncGenerator(asyncGenObj);
+                };
+                return InternalAwait(cx, value, nullptr, onFulfilled, onRejected, extra);
+            }
+
+            // Step 10.b.ii.1.
+            MOZ_ASSERT(completionKind == CompletionKind::Throw);
+
+            // Steps 10.b.ii.2-3.
+            return AsyncGeneratorReject(cx, asyncGenObj, value);
+        }
+    } else if (asyncGenObj->isCompleted()) {
+        // Step 11.
+        return AsyncGeneratorResolve(cx, asyncGenObj, UndefinedHandleValue, true);
+    }
+
+    // Step 12.
+    MOZ_ASSERT(asyncGenObj->isSuspendedStart() || asyncGenObj->isSuspendedYield());
+
+    // Step 16 (reordered).
+    asyncGenObj->setExecuting();
+
+    RootedValue argument(cx, request->completionValue());
+
+    if (completionKind == CompletionKind::Return) {
+        // 11.4.3.7 AsyncGeneratorYield step 8.b-e.
+        // Since we don't have the place that handles return from yield
+        // inside the generator, handle the case here, with extra state
+        // State_AwaitingYieldReturn.
+        asyncGenObj->setAwaitingYieldReturn();
+
+        RootedValue onFulfilled(cx, Int32Value(PromiseHandlerAsyncGeneratorYieldReturnAwaitedFulfilled));
+        RootedValue onRejected(cx, Int32Value(PromiseHandlerAsyncGeneratorYieldReturnAwaitedRejected));
+
+        auto extra = [&](Handle<PromiseReactionRecord*> reaction) {
+            reaction->setIsAsyncGenerator(asyncGenObj);
+        };
+        return InternalAwait(cx, argument, nullptr, onFulfilled, onRejected, extra);
+    }
+
+    // Steps 13-15, 17-21.
+    return AsyncGeneratorResume(cx, asyncGenObj, completionKind, argument);
+}
+
+// Async Iteration proposal 11.4.3.6.
 MOZ_MUST_USE bool
 js::AsyncGeneratorEnqueue(JSContext* cx, HandleValue asyncGenVal,
                           CompletionKind completionKind, HandleValue completionValue,
                           MutableHandleValue result)
 {
     // Step 1 (implicit).
 
     // Step 2.
--- a/js/src/frontend/BytecodeEmitter.cpp
+++ b/js/src/frontend/BytecodeEmitter.cpp
@@ -8609,16 +8609,23 @@ BytecodeEmitter::emitReturn(ParseNode* p
         if (!emitPrepareIteratorResult())
             return false;
     }
 
     /* Push a return value */
     if (ParseNode* pn2 = pn->pn_kid) {
         if (!emitTree(pn2))
             return false;
+
+        bool isAsyncGenerator = sc->asFunctionBox()->isAsync() &&
+                                sc->asFunctionBox()->isStarGenerator();
+        if (isAsyncGenerator) {
+            if (!emitAwait())
+                return false;
+        }
     } else {
         /* No explicit return value provided */
         if (!emit1(JSOP_UNDEFINED))
             return false;
     }
 
     if (needsIteratorResult) {
         if (!emitFinishIteratorResult(true))
@@ -8721,16 +8728,24 @@ BytecodeEmitter::emitYield(ParseNode* pn
     }
     if (pn->pn_kid) {
         if (!emitTree(pn->pn_kid))
             return false;
     } else {
         if (!emit1(JSOP_UNDEFINED))
             return false;
     }
+
+    // 11.4.3.7 AsyncGeneratorYield step 5.
+    bool isAsyncGenerator = sc->asFunctionBox()->isAsync();
+    if (isAsyncGenerator) {
+        if (!emitAwait())                                 // RESULT
+            return false;
+    }
+
     if (needsIteratorResult) {
         if (!emitFinishIteratorResult(false))
             return false;
     }
 
     if (!emitGetDotGenerator())
         return false;
 
@@ -8793,16 +8808,22 @@ BytecodeEmitter::emitYieldStar(ParseNode
         return false;
 
     JumpTarget tryStart{ offset() };
     if (!tryCatch.emitTry())                              // ITER RESULT
         return false;
 
     MOZ_ASSERT(this->stackDepth == startDepth);
 
+    // 11.4.3.7 AsyncGeneratorYield step 5.
+    if (isAsyncGenerator) {
+        if (!emitAwait())                                 // ITER RESULT
+            return false;
+    }
+
     // Load the generator object.
     if (!emitGetDotGenerator())                           // ITER RESULT GENOBJ
         return false;
 
     // Yield RESULT as-is, without re-boxing.
     if (!emitYieldOp(JSOP_YIELD))                         // ITER RECEIVED
         return false;
 
@@ -8942,21 +8963,16 @@ BytecodeEmitter::emitYieldStar(ParseNode
         return false;
     if (!emitAtomOp(cx->names().done, JSOP_GETPROP))      // ITER OLDRESULT FTYPE FVALUE RESULT DONE
         return false;
     if (!ifReturnDone.emitIfElse())                       // ITER OLDRESULT FTYPE FVALUE RESULT
         return false;
     if (!emitAtomOp(cx->names().value, JSOP_GETPROP))     // ITER OLDRESULT FTYPE FVALUE VALUE
         return false;
 
-    if (isAsyncGenerator) {
-        if (!emitAwait())                                 // ITER OLDRESULT FTYPE FVALUE VALUE
-            return false;
-    }
-
     if (!emitPrepareIteratorResult())                     // ITER OLDRESULT FTYPE FVALUE VALUE RESULT
         return false;
     if (!emit1(JSOP_SWAP))                                // ITER OLDRESULT FTYPE FVALUE RESULT VALUE
         return false;
     if (!emitFinishIteratorResult(true))                  // ITER OLDRESULT FTYPE FVALUE RESULT
         return false;
     if (!emit1(JSOP_SETRVAL))                             // ITER OLDRESULT FTYPE FVALUE
         return false;
@@ -9037,21 +9053,16 @@ BytecodeEmitter::emitYieldStar(ParseNode
     // result.value
     if (!emit1(JSOP_SWAP))                                       // RESULT ITER
         return false;
     if (!emit1(JSOP_POP))                                        // RESULT
         return false;
     if (!emitAtomOp(cx->names().value, JSOP_GETPROP))            // VALUE
         return false;
 
-    if (isAsyncGenerator) {
-        if (!emitAwait())                                        // VALUE
-            return false;
-    }
-
     MOZ_ASSERT(this->stackDepth == startDepth - 1);
 
     return true;
 }
 
 bool
 BytecodeEmitter::emitStatementList(ParseNode* pn)
 {
--- a/js/src/jit-test/tests/basic/testBug756919.js
+++ b/js/src/jit-test/tests/basic/testBug756919.js
@@ -1,10 +1,12 @@
-// |jit-test| allow-oom; allow-unhandlable-oom; allow-overrecursed
+if (!('oomTest' in this))
+    quit();
 
-gcparam("maxBytes", gcparam("gcBytes") + 1024);
-test();
-function test() {
-  var upvar = "";
-  function f() { upvar += ""; }
-  test();
-  eval('');
+function test(x) {
+    var upvar = "";
+    function f() { upvar += ""; }
+    if (x > 0)
+        test(x - 1);
+    eval('');
 }
+
+oomTest(() => test(10));
--- a/js/src/old-configure.in
+++ b/js/src/old-configure.in
@@ -766,30 +766,16 @@ case "$target" in
         MOZ_DEBUG_LDFLAGS='-DEBUG -DEBUGTYPE:CV'
         WARNINGS_AS_ERRORS='-WX'
         MOZ_OPTIMIZE_FLAGS="-O2"
         MOZ_FIX_LINK_PATHS=
         LDFLAGS="$LDFLAGS -LARGEADDRESSAWARE -NXCOMPAT"
         if test -z "$DEVELOPER_OPTIONS"; then
             LDFLAGS="$LDFLAGS -RELEASE"
         fi
-        dnl For profile-guided optimization
-        PROFILE_GEN_CFLAGS="-GL"
-        PROFILE_GEN_LDFLAGS="-LTCG:PGINSTRUMENT"
-        dnl XXX: PGO builds can fail with warnings treated as errors,
-        dnl specifically "no profile data available" appears to be
-        dnl treated as an error sometimes. This might be a consequence
-        dnl of using WARNINGS_AS_ERRORS in some modules, combined
-        dnl with the linker doing most of the work in the whole-program
-        dnl optimization/PGO case. I think it's probably a compiler bug,
-        dnl but we work around it here.
-        PROFILE_USE_CFLAGS="-GL -wd4624 -wd4952"
-        dnl XXX: should be -LTCG:PGOPTIMIZE, but that fails on libxul.
-        dnl Probably also a compiler bug, but what can you do?
-        PROFILE_USE_LDFLAGS="-LTCG:PGUPDATE"
         LDFLAGS="$LDFLAGS -DYNAMICBASE"
         RCFLAGS="-nologo"
     fi
     AC_DEFINE(HAVE__MSIZE)
     AC_DEFINE(WIN32_LEAN_AND_MEAN)
     dnl See http://support.microsoft.com/kb/143208 to use STL
     AC_DEFINE(NOMINMAX)
     BIN_SUFFIX='.exe'
@@ -1812,44 +1798,16 @@ if test -n "$GNU_CC" -a -n "$GNU_CXX"; t
     dnl Any gcc that supports firefox supports -pipe.
     CFLAGS="$CFLAGS -pipe"
     CXXFLAGS="$CXXFLAGS -pipe"
     AC_MSG_RESULT([yes])
 else
     AC_MSG_RESULT([no])
 fi
 
-dnl ========================================================
-dnl Profile guided optimization (gcc checks)
-dnl ========================================================
-dnl Test for profiling options
-dnl Under gcc 3.4+, use -fprofile-generate/-fprofile-use
-
-_SAVE_CFLAGS="$CFLAGS"
-CFLAGS="$CFLAGS -fprofile-generate -fprofile-correction"
-
-AC_MSG_CHECKING([whether C compiler supports -fprofile-generate])
-AC_TRY_COMPILE([], [return 0;],
-               [ PROFILE_GEN_CFLAGS="-fprofile-generate"
-                 result="yes" ], result="no")
-AC_MSG_RESULT([$result])
-
-if test $result = "yes"; then
-  PROFILE_GEN_LDFLAGS="-fprofile-generate"
-  PROFILE_USE_CFLAGS="-fprofile-use -fprofile-correction -Wcoverage-mismatch"
-  PROFILE_USE_LDFLAGS="-fprofile-use"
-fi
-
-CFLAGS="$_SAVE_CFLAGS"
-
-AC_SUBST(PROFILE_GEN_CFLAGS)
-AC_SUBST(PROFILE_GEN_LDFLAGS)
-AC_SUBST(PROFILE_USE_CFLAGS)
-AC_SUBST(PROFILE_USE_LDFLAGS)
-
 AC_LANG_CPLUSPLUS
 
 dnl ========================================================
 dnl Check for tm_zone, tm_gmtoff in struct tm
 dnl ========================================================
 AC_CACHE_CHECK(for tm_zone tm_gmtoff in struct tm,
     ac_cv_struct_tm_zone_tm_gmtoff,
     [AC_TRY_COMPILE([#include <time.h>],
--- a/js/src/vm/AsyncIteration.cpp
+++ b/js/src/vm/AsyncIteration.cpp
@@ -21,17 +21,17 @@
 #include "vm/NativeObject-inl.h"
 
 using namespace js;
 using namespace js::gc;
 
 #define UNWRAPPED_ASYNC_WRAPPED_SLOT 1
 #define WRAPPED_ASYNC_UNWRAPPED_SLOT 0
 
-// Async Iteration proposal 2.3.10 Runtime Semantics: EvaluateBody.
+// Async Iteration proposal 8.3.10 Runtime Semantics: EvaluateBody.
 static bool
 WrappedAsyncGenerator(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
 
     RootedFunction wrapped(cx, &args.callee().as<JSFunction>());
     RootedValue unwrappedVal(cx, wrapped->getExtendedSlot(WRAPPED_ASYNC_UNWRAPPED_SLOT));
     RootedFunction unwrapped(cx, &unwrappedVal.toObject().as<JSFunction>());
@@ -123,53 +123,67 @@ js::GetUnwrappedAsyncGenerator(JSFunctio
 {
     MOZ_ASSERT(IsWrappedAsyncGenerator(wrapped));
     JSFunction* unwrapped = &wrapped->getExtendedSlot(WRAPPED_ASYNC_UNWRAPPED_SLOT)
                             .toObject().as<JSFunction>();
     MOZ_ASSERT(unwrapped->isAsync());
     return unwrapped;
 }
 
-static MOZ_MUST_USE bool
-AsyncGeneratorResume(JSContext* cx, Handle<AsyncGeneratorObject*> asyncGenObj,
-                     CompletionKind completionKind, HandleValue argument);
-
-// Async Iteration proposal 5.1.1 Await Fulfilled Functions.
+// Async Iteration proposal 4.1.1 Await Fulfilled Functions.
 MOZ_MUST_USE bool
 js::AsyncGeneratorAwaitedFulfilled(JSContext* cx, Handle<AsyncGeneratorObject*> asyncGenObj,
                                    HandleValue value)
 {
     return AsyncGeneratorResume(cx, asyncGenObj, CompletionKind::Normal, value);
 }
 
-// Async Iteration proposal 5.1.2 Await Rejected Functions.
+// Async Iteration proposal 4.1.2 Await Rejected Functions.
 MOZ_MUST_USE bool
 js::AsyncGeneratorAwaitedRejected(JSContext* cx, Handle<AsyncGeneratorObject*> asyncGenObj,
                                   HandleValue reason)
 {
     return AsyncGeneratorResume(cx, asyncGenObj, CompletionKind::Throw, reason);
 }
 
+// Async Iteration proposal 11.4.3.7 step 8.d-e.
+MOZ_MUST_USE bool
+js::AsyncGeneratorYieldReturnAwaitedFulfilled(JSContext* cx,
+                                              Handle<AsyncGeneratorObject*> asyncGenObj,
+                                              HandleValue value)
+{
+    return AsyncGeneratorResume(cx, asyncGenObj, CompletionKind::Return, value);
+}
+
+// Async Iteration proposal 11.4.3.7 step 8.d-e.
+MOZ_MUST_USE bool
+js::AsyncGeneratorYieldReturnAwaitedRejected(JSContext* cx,
+                                             Handle<AsyncGeneratorObject*> asyncGenObj,
+                                             HandleValue reason)
+{
+    return AsyncGeneratorResume(cx, asyncGenObj, CompletionKind::Throw, reason);
+}
+
 const Class AsyncFromSyncIteratorObject::class_ = {
     "AsyncFromSyncIteratorObject",
     JSCLASS_HAS_RESERVED_SLOTS(AsyncFromSyncIteratorObject::Slots)
 };
 
-// Async Iteration proposal 6.1.3.1.
+// Async Iteration proposal 11.1.3.1.
 JSObject*
 js::CreateAsyncFromSyncIterator(JSContext* cx, HandleObject iter)
 {
     // Step 1 (implicit).
     // Done in bytecode emitted by emitAsyncIterator.
 
     // Steps 2-4.
     return AsyncFromSyncIteratorObject::create(cx, iter);
 }
 
-// Async Iteration proposal 6.1.3.1 steps 2-4.
+// Async Iteration proposal 11.1.3.1 steps 2-4.
 /* static */ JSObject*
 AsyncFromSyncIteratorObject::create(JSContext* cx, HandleObject iter)
 {
     // Step 2.
     RootedObject proto(cx, GlobalObject::getOrCreateAsyncFromSyncIteratorPrototype(cx,
                                                                                    cx->global()));
     if (!proto)
         return nullptr;
@@ -182,63 +196,63 @@ AsyncFromSyncIteratorObject::create(JSCo
 
     // Step 3.
     asyncIter->setIterator(iter);
 
     // Step 4.
     return asyncIter;
 }
 
-// Async Iteration proposal 6.1.3.2.1 %AsyncFromSyncIteratorPrototype%.next.
+// Async Iteration proposal 11.1.3.2.1 %AsyncFromSyncIteratorPrototype%.next.
 static bool
 AsyncFromSyncIteratorNext(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
     return AsyncFromSyncIteratorMethod(cx, args, CompletionKind::Normal);
 }
 
-// Async Iteration proposal 6.1.3.2.2 %AsyncFromSyncIteratorPrototype%.return.
+// Async Iteration proposal 11.1.3.2.2 %AsyncFromSyncIteratorPrototype%.return.
 static bool
 AsyncFromSyncIteratorReturn(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
     return AsyncFromSyncIteratorMethod(cx, args, CompletionKind::Return);
 }
 
-// Async Iteration proposal 6.1.3.2.3 %AsyncFromSyncIteratorPrototype%.throw.
+// Async Iteration proposal 11.1.3.2.3 %AsyncFromSyncIteratorPrototype%.throw.
 static bool
 AsyncFromSyncIteratorThrow(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
     return AsyncFromSyncIteratorMethod(cx, args, CompletionKind::Throw);
 }
 
-// Async Iteration proposal 6.4.1.2 AsyncGenerator.prototype.next.
+// Async Iteration proposal 11.4.1.2 AsyncGenerator.prototype.next.
 static bool
 AsyncGeneratorNext(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
 
     // Steps 1-3.
     return AsyncGeneratorEnqueue(cx, args.thisv(), CompletionKind::Normal, args.get(0),
                                  args.rval());
 }
 
-// Async Iteration proposal 6.4.1.3 AsyncGenerator.prototype.return.
+// Async Iteration proposal 11.4.1.3 AsyncGenerator.prototype.return.
 static bool
 AsyncGeneratorReturn(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
 
     // Steps 1-3.
     return AsyncGeneratorEnqueue(cx, args.thisv(), CompletionKind::Return, args.get(0),
                                  args.rval());
 }
 
-// Async Iteration proposal 6.4.1.4 AsyncGenerator.prototype.throw.
+// Async Iteration proposal 11.4.1.4 AsyncGenerator.prototype.throw.
 static bool
 AsyncGeneratorThrow(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
 
     // Steps 1-3.
     return AsyncGeneratorEnqueue(cx, args.thisv(), CompletionKind::Throw, args.get(0),
                                  args.rval());
@@ -366,48 +380,48 @@ AsyncGeneratorObject::peekRequest(JSCont
     return &requestVal.toObject().as<AsyncGeneratorRequest>();
 }
 
 const Class AsyncGeneratorRequest::class_ = {
     "AsyncGeneratorRequest",
     JSCLASS_HAS_RESERVED_SLOTS(AsyncGeneratorRequest::Slots)
 };
 
-// Async Iteration proposal 6.4.3.1.
+// Async Iteration proposal 11.4.3.1.
 /* static */ AsyncGeneratorRequest*
 AsyncGeneratorRequest::create(JSContext* cx, CompletionKind completionKind_,
                               HandleValue completionValue_, HandleObject promise_)
 {
     RootedObject obj(cx, NewNativeObjectWithGivenProto(cx, &class_, nullptr));
     if (!obj)
         return nullptr;
 
     Handle<AsyncGeneratorRequest*> request = obj.as<AsyncGeneratorRequest>();
     request->setCompletionKind(completionKind_);
     request->setCompletionValue(completionValue_);
     request->setPromise(promise_);
     return request;
 }
 
-// Async Iteration proposal 6.4.3.2 steps 5.d-g.
+// Async Iteration proposal 11.4.3.2 AsyncGeneratorStart steps 5.d-g.
 static MOZ_MUST_USE bool
 AsyncGeneratorReturned(JSContext* cx, Handle<AsyncGeneratorObject*> asyncGenObj,
                        HandleValue value)
 {
     // Step 5.d.
     asyncGenObj->setCompleted();
 
     // Step 5.e (done in bytecode).
     // Step 5.f.i (implicit).
 
     // Step 5.g.
     return AsyncGeneratorResolve(cx, asyncGenObj, value, true);
 }
 
-// Async Iteration proposal 6.4.3.2 steps 5.d, f.
+// Async Iteration proposal 11.4.3.2 AsyncGeneratorStart steps 5.d, f.
 static MOZ_MUST_USE bool
 AsyncGeneratorThrown(JSContext* cx, Handle<AsyncGeneratorObject*> asyncGenObj)
 {
     // Step 5.d.
     asyncGenObj->setCompleted();
 
     // Not much we can do about uncatchable exceptions, so just bail.
     if (!cx->isExceptionPending())
@@ -417,130 +431,81 @@ AsyncGeneratorThrown(JSContext* cx, Hand
     RootedValue value(cx);
     if (!GetAndClearException(cx, &value))
         return false;
 
     // Step 5.f.ii.
     return AsyncGeneratorReject(cx, asyncGenObj, value);
 }
 
-// Async Iteration proposal 6.4.3.5.
-MOZ_MUST_USE bool
-js::AsyncGeneratorResumeNext(JSContext* cx, Handle<AsyncGeneratorObject*> asyncGenObj)
+// Async Iteration proposal 11.4.3.7 (partially).
+// Most steps are done in generator.
+static MOZ_MUST_USE bool
+AsyncGeneratorYield(JSContext* cx, Handle<AsyncGeneratorObject*> asyncGenObj, HandleValue value)
 {
-    // Step 1 (implicit).
-
-    // Steps 2-3.
-    MOZ_ASSERT(!asyncGenObj->isExecuting());
+    // Step 5 is done in bytecode.
 
-    // Steps 4-5.
-    if (asyncGenObj->isQueueEmpty())
-        return true;
-
-    // Steps 6-7.
-    Rooted<AsyncGeneratorRequest*> request(
-        cx, AsyncGeneratorObject::peekRequest(cx, asyncGenObj));
-    if (!request)
-        return false;
-
-    // Step 8.
-    CompletionKind completionKind = request->completionKind();
+    // Step 6.
+    asyncGenObj->setSuspendedYield();
 
     // Step 9.
-    if (completionKind != CompletionKind::Normal) {
-        // Step 9.a.
-        if (asyncGenObj->isSuspendedStart())
-            asyncGenObj->setCompleted();
-
-        // Step 9.b.
-        if (asyncGenObj->isCompleted()) {
-            // Step 9.b.i.
-            RootedValue value(cx, request->completionValue());
-            if (completionKind == CompletionKind::Return)
-                return AsyncGeneratorResolve(cx, asyncGenObj, value, true);
-            // Step 9.b.ii.
-            return AsyncGeneratorReject(cx, asyncGenObj, value);
-        }
-    } else if (asyncGenObj->isCompleted()) {
-        // Step 10.
-        return AsyncGeneratorResolve(cx, asyncGenObj, UndefinedHandleValue, true);
-    }
-
-    // Step 11.
-    MOZ_ASSERT(asyncGenObj->isSuspendedStart() || asyncGenObj->isSuspendedYield());
-
-    // Step 15 (reordered).
-    asyncGenObj->setExecuting();
-
-    RootedValue argument(cx, request->completionValue());
-
-    // Steps 12-14, 16-20.
-    return AsyncGeneratorResume(cx, asyncGenObj, completionKind, argument);
-}
-
-// Async Iteration proposal 6.2.1.3 (partially).
-// Most steps are done in generator.
-static MOZ_MUST_USE bool
-AsyncGeneratorYield(JSContext* cx, Handle<AsyncGeneratorObject*> asyncGenObj,
-                    HandleValue value)
-{
-    // Step 5.
-    asyncGenObj->setSuspendedYield();
-
-    // Step 8.
     return AsyncGeneratorResolve(cx, asyncGenObj, value, false);
 }
 
-// Async Iteration proposal 6.4.3.5 steps 12-14, 16-20.
-// Async Iteration proposal 6.2.1.2 step 10.
-// Async Iteration proposal 6.4.3.2 step 5.f-g.
-// Async Iteration proposal 5.1 steps 2-9.
+// Async Iteration proposal 4.1 Await steps 2-9.
+// Async Iteration proposal 8.2.1 yield* steps 6.a.vii, 6.b.ii.7, 6.c.ix.
+// Async Iteration proposal 11.4.3.2 AsyncGeneratorStart step 5.f-g.
+// Async Iteration proposal 11.4.3.5 AsyncGeneratorResumeNext
+//   steps 12-14, 16-20.
 // Execution context switching is handled in generator.
-static MOZ_MUST_USE bool
-AsyncGeneratorResume(JSContext* cx, Handle<AsyncGeneratorObject*> asyncGenObj,
-                     CompletionKind completionKind, HandleValue argument)
+MOZ_MUST_USE bool
+js::AsyncGeneratorResume(JSContext* cx, Handle<AsyncGeneratorObject*> asyncGenObj,
+                         CompletionKind completionKind, HandleValue argument)
 {
     RootedValue generatorVal(cx, asyncGenObj->generatorVal());
 
-    // 6.4.3.5 steps 12-14, 16-20.
+    // 11.4.3.5 steps 12-14, 16-20.
     HandlePropertyName funName = completionKind == CompletionKind::Normal
                                  ? cx->names().StarGeneratorNext
                                  : completionKind == CompletionKind::Throw
                                  ? cx->names().StarGeneratorThrow
                                  : cx->names().StarGeneratorReturn;
     FixedInvokeArgs<1> args(cx);
     args[0].set(argument);
     RootedValue result(cx);
     if (!CallSelfHostedFunction(cx, funName, generatorVal, args, &result)) {
-        // 6.4.3.2 step 5.d, f.
+        // 11.4.3.2 step 5.d, f.
         return AsyncGeneratorThrown(cx, asyncGenObj);
     }
 
+    // 4.1 steps 2-9.
     if (asyncGenObj->generatorObj()->isAfterAwait())
         return AsyncGeneratorAwait(cx, asyncGenObj, result);
 
     // The following code corresponds to the following 3 cases:
     //   * yield
     //   * yield*
     //   * return
     // For yield and return, property access is done on an internal result
     // object and it's not observable.
     // For yield*, it's done on a possibly user-provided result object, and
     // it's observable.
+    //
+    // Note that IteratorComplete steps in 8.2.1 are done in bytecode.
 
-    // 2.2.1 yield* steps 6.a.vii, 6.b.ii.7, 6.c.ix.
+    // 8.2.1 yield* steps 6.a.vii, 6.b.ii.7, 6.c.ix.
     RootedObject resultObj(cx, &result.toObject());
     RootedValue value(cx);
     if (!GetProperty(cx, resultObj, resultObj, cx->names().value, &value))
         return false;
 
     if (asyncGenObj->generatorObj()->isAfterYield())
         return AsyncGeneratorYield(cx, asyncGenObj, value);
 
-    // 6.4.3.2 step 5.d-g.
+    // 11.4.3.2 step 5.d-g.
     return AsyncGeneratorReturned(cx, asyncGenObj, value);
 }
 
 static const JSFunctionSpec async_iterator_proto_methods[] = {
     JS_SELF_HOSTED_SYM_FN(asyncIterator, "AsyncIteratorIdentity", 0, 0),
     JS_FS_END
 };
 
@@ -559,49 +524,49 @@ static const JSFunctionSpec async_genera
 };
 
 /* static */ MOZ_MUST_USE bool
 GlobalObject::initAsyncGenerators(JSContext* cx, Handle<GlobalObject*> global)
 {
     if (global->getReservedSlot(ASYNC_ITERATOR_PROTO).isObject())
         return true;
 
-    // Async Iteration proposal 6.1.2 %AsyncIteratorPrototype%.
+    // Async Iteration proposal 11.1.2 %AsyncIteratorPrototype%.
     RootedObject asyncIterProto(cx, GlobalObject::createBlankPrototype<PlainObject>(cx, global));
     if (!asyncIterProto)
         return false;
     if (!DefinePropertiesAndFunctions(cx, asyncIterProto, nullptr, async_iterator_proto_methods))
         return false;
 
-    // Async Iteration proposal 6.1.3.2 %AsyncFromSyncIteratorPrototype%.
+    // Async Iteration proposal 11.1.3.2 %AsyncFromSyncIteratorPrototype%.
     RootedObject asyncFromSyncIterProto(
         cx, GlobalObject::createBlankPrototypeInheriting(cx, global, &PlainObject::class_,
                                                          asyncIterProto));
     if (!asyncFromSyncIterProto)
         return false;
     if (!DefinePropertiesAndFunctions(cx, asyncFromSyncIterProto, nullptr,
                                       async_from_sync_iter_methods) ||
         !DefineToStringTag(cx, asyncFromSyncIterProto, cx->names().AsyncFromSyncIterator))
     {
         return false;
     }
 
-    // Async Iteration proposal 6.4.1 %AsyncGeneratorPrototype%.
+    // Async Iteration proposal 11.4.1 %AsyncGeneratorPrototype%.
     RootedObject asyncGenProto(
         cx, GlobalObject::createBlankPrototypeInheriting(cx, global, &PlainObject::class_,
                                                          asyncIterProto));
     if (!asyncGenProto)
         return false;
     if (!DefinePropertiesAndFunctions(cx, asyncGenProto, nullptr, async_generator_methods) ||
         !DefineToStringTag(cx, asyncGenProto, cx->names().AsyncGenerator))
     {
         return false;
     }
 
-    // Async Iteration proposal 6.3.3 %AsyncGenerator%.
+    // Async Iteration proposal 11.3.3 %AsyncGenerator%.
     RootedObject asyncGenerator(cx, NewSingletonObjectWithFunctionPrototype(cx, global));
     if (!asyncGenerator)
         return false;
     if (!JSObject::setDelegate(cx, asyncGenerator))
         return false;
     if (!LinkConstructorAndPrototype(cx, asyncGenerator, asyncGenProto, JSPROP_READONLY,
                                      JSPROP_READONLY) ||
         !DefineToStringTag(cx, asyncGenerator, cx->names().AsyncGeneratorFunction))
@@ -610,17 +575,17 @@ GlobalObject::initAsyncGenerators(JSCont
     }
 
     RootedValue function(cx, global->getConstructor(JSProto_Function));
     if (!function.toObjectOrNull())
         return false;
     RootedObject proto(cx, &function.toObject());
     RootedAtom name(cx, cx->names().AsyncGeneratorFunction);
 
-    // Async Iteration proposal 6.3.2 %AsyncGeneratorFunction%.
+    // Async Iteration proposal 11.3.2 %AsyncGeneratorFunction%.
     RootedObject asyncGenFunction(
         cx, NewFunctionWithProto(cx, AsyncGeneratorConstructor, 1, JSFunction::NATIVE_CTOR,
                                  nullptr, name, proto, gc::AllocKind::FUNCTION, SingletonObject));
     if (!asyncGenFunction)
         return false;
     if (!LinkConstructorAndPrototype(cx, asyncGenFunction, asyncGenerator,
                                      JSPROP_PERMANENT | JSPROP_READONLY, JSPROP_READONLY))
     {
--- a/js/src/vm/AsyncIteration.h
+++ b/js/src/vm/AsyncIteration.h
@@ -33,21 +33,28 @@ IsWrappedAsyncGenerator(JSFunction* fun)
 JSFunction*
 GetWrappedAsyncGenerator(JSFunction* unwrapped);
 
 JSFunction*
 GetUnwrappedAsyncGenerator(JSFunction* wrapped);
 
 MOZ_MUST_USE bool
 AsyncGeneratorAwaitedFulfilled(JSContext* cx, Handle<AsyncGeneratorObject*> asyncGenObj,
-                              HandleValue value);
-
+                               HandleValue value);
 MOZ_MUST_USE bool
 AsyncGeneratorAwaitedRejected(JSContext* cx, Handle<AsyncGeneratorObject*> asyncGenObj,
-                             HandleValue reason);
+                              HandleValue reason);
+MOZ_MUST_USE bool
+AsyncGeneratorYieldReturnAwaitedFulfilled(JSContext* cx,
+                                          Handle<AsyncGeneratorObject*> asyncGenObj,
+                                          HandleValue value);
+MOZ_MUST_USE bool
+AsyncGeneratorYieldReturnAwaitedRejected(JSContext* cx,
+                                         Handle<AsyncGeneratorObject*> asyncGenObj,
+                                         HandleValue reason);
 
 class AsyncGeneratorRequest : public NativeObject
 {
   private:
     enum AsyncGeneratorRequestSlots {
         Slot_CompletionKind = 0,
         Slot_CompletionValue,
         Slot_Promise,
@@ -92,16 +99,22 @@ class AsyncGeneratorObject : public Nati
         Slot_QueueOrRequest,
         Slots
     };
 
     enum State {
         State_SuspendedStart,
         State_SuspendedYield,
         State_Executing,
+        // State_AwaitingYieldReturn corresponds to the case that
+        // AsyncGenerator#return is called while State_Executing,
+        // just like the case that AsyncGenerator#return is called
+        // while State_Completed.
+        State_AwaitingYieldReturn,
+        State_AwaitingReturn,
         State_Completed
     };
 
     State state() const {
         return static_cast<State>(getFixedSlot(Slot_State).toInt32());
     }
     void setState(State state_) {
         setFixedSlot(Slot_State, Int32Value(state_));
@@ -150,29 +163,41 @@ class AsyncGeneratorObject : public Nati
         return state() == State_SuspendedStart;
     }
     bool isSuspendedYield() const {
         return state() == State_SuspendedYield;
     }
     bool isExecuting() const {
         return state() == State_Executing;
     }
+    bool isAwaitingYieldReturn() const {
+        return state() == State_AwaitingYieldReturn;
+    }
+    bool isAwaitingReturn() const {
+        return state() == State_AwaitingReturn;
+    }
     bool isCompleted() const {
         return state() == State_Completed;
     }
 
     void setSuspendedStart() {
         setState(State_SuspendedStart);
     }
     void setSuspendedYield() {
         setState(State_SuspendedYield);
     }
     void setExecuting() {
         setState(State_Executing);
     }
+    void setAwaitingYieldReturn() {
+        setState(State_AwaitingYieldReturn);
+    }
+    void setAwaitingReturn() {
+        setState(State_AwaitingReturn);
+    }
     void setCompleted() {
         setState(State_Completed);
     }
 
     JS::Value generatorVal() const {
         return getFixedSlot(Slot_Generator);
     }
     GeneratorObject* generatorObj() const {
@@ -218,13 +243,14 @@ class AsyncFromSyncIteratorObject : publ
     create(JSContext* cx, HandleObject iter);
 
     JSObject* iterator() const {
         return &getFixedSlot(Slot_Iterator).toObject();
     }
 };
 
 MOZ_MUST_USE bool
-AsyncGeneratorResumeNext(JSContext* cx, Handle<AsyncGeneratorObject*> asyncGenObj);
+AsyncGeneratorResume(JSContext* cx, Handle<AsyncGeneratorObject*> asyncGenObj,
+                     CompletionKind completionKind, HandleValue argument);
 
 } // namespace js
 
 #endif /* vm_AsyncIteration_h */
--- a/layout/reftests/writing-mode/1089388-1-ref.html
+++ b/layout/reftests/writing-mode/1089388-1-ref.html
@@ -6,16 +6,17 @@
 .v-lr { writing-mode:vertical-lr;  }
 .v-rl { writing-mode:vertical-rl; }
 
 div {
   width: 300px;
   height: 200px;
   background: #ddd;
   margin: 50px;
+  text-rendering: optimizeLegibility;
 }
 </style>
 </head>
 
 <body>
 
 <div class="v-lr">
 First part of the block.
--- a/layout/reftests/writing-mode/1089388-1.html
+++ b/layout/reftests/writing-mode/1089388-1.html
@@ -6,16 +6,17 @@
 .v-lr { writing-mode:vertical-lr;  }
 .v-rl { writing-mode:vertical-rl; }
 
 div {
   width: 300px;
   height: 200px;
   background: #ddd;
   margin: 50px;
+  text-rendering: optimizeLegibility;
 }
 </style>
 
 <script>
 function doTest() {
   document.getElementById("test").textContent =
     "New text inserted by script, to cause a reflow that slides the following lines.";
   document.documentElement.removeAttribute("class");
--- a/layout/reftests/writing-mode/1089388-2-ref.html
+++ b/layout/reftests/writing-mode/1089388-2-ref.html
@@ -6,16 +6,17 @@
 .v-lr { writing-mode:vertical-lr;  }
 .v-rl { writing-mode:vertical-rl; }
 
 div {
   width: 300px;
   height: 200px;
   background: #ddd;
   margin: 50px;
+  text-rendering: optimizeLegibility;
 }
 </style>
 </head>
 
 <body>
 
 <div class="v-rl">
 First part of the block.
--- a/layout/reftests/writing-mode/1089388-2.html
+++ b/layout/reftests/writing-mode/1089388-2.html
@@ -6,16 +6,17 @@
 .v-lr { writing-mode:vertical-lr;  }
 .v-rl { writing-mode:vertical-rl; }
 
 div {
   width: 300px;
   height: 200px;
   background: #ddd;
   margin: 50px;
+  text-rendering: optimizeLegibility;
 }
 </style>
 
 <script>
 function doTest() {
   document.getElementById("test").textContent =
     "New text inserted by script, to cause a reflow that slides the following lines.";
   document.documentElement.removeAttribute("class");
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -709,16 +709,17 @@ pref("apz.frame_delay.enabled", true);
 #else
 pref("apz.frame_delay.enabled", false);
 #endif
 #if defined(NIGHTLY_BUILD) && !defined(MOZ_WIDGET_ANDROID)
 pref("apz.keyboard.enabled", true);
 #else
 pref("apz.keyboard.enabled", false);
 #endif
+pref("apz.keyboard.passive-listeners", false);
 pref("apz.max_velocity_inches_per_ms", "-1.0");
 pref("apz.max_velocity_queue_size", 5);
 pref("apz.min_skate_speed", "1.0");
 pref("apz.minimap.enabled", false);
 pref("apz.minimap.visibility.enabled", false);
 pref("apz.one_touch_pinch.enabled", true);
 pref("apz.overscroll.enabled", false);
 pref("apz.overscroll.min_pan_distance_ratio", "1.0");
--- a/old-configure.in
+++ b/old-configure.in
@@ -1015,32 +1015,16 @@ case "$target" in
         MOZ_DEBUG_LDFLAGS='-DEBUG -DEBUGTYPE:CV'
         WARNINGS_AS_ERRORS='-WX'
         MOZ_OPTIMIZE_FLAGS='-O1 -Oi'
         MOZ_FIX_LINK_PATHS=
         LDFLAGS="$LDFLAGS -LARGEADDRESSAWARE -NXCOMPAT"
         if test -z "$DEVELOPER_OPTIONS"; then
             LDFLAGS="$LDFLAGS -RELEASE"
         fi
-        dnl For profile-guided optimization
-        PROFILE_GEN_CFLAGS="-GL"
-        num_cores=$($PYTHON -c 'import multiprocessing; print(min(8,multiprocessing.cpu_count()))')
-        cgthreads="-CGTHREADS:${num_cores}"
-        PROFILE_GEN_LDFLAGS="-LTCG:PGINSTRUMENT -PogoSafeMode $cgthreads"
-        dnl XXX: PGO builds can fail with warnings treated as errors,
-        dnl specifically "no profile data available" appears to be
-        dnl treated as an error sometimes. This might be a consequence
-        dnl of using WARNINGS_AS_ERRORS in some modules, combined
-        dnl with the linker doing most of the work in the whole-program
-        dnl optimization/PGO case. I think it's probably a compiler bug,
-        dnl but we work around it here.
-        PROFILE_USE_CFLAGS="-GL -wd4624 -wd4952"
-        dnl XXX: should be -LTCG:PGOPTIMIZE, but that fails on libxul.
-        dnl Probably also a compiler bug, but what can you do?
-        PROFILE_USE_LDFLAGS="-LTCG:PGUPDATE $cgthreads"
         LDFLAGS="$LDFLAGS -DYNAMICBASE"
         RCFLAGS="-nologo"
         dnl Minimum reqiurement of Gecko is VS2015 or later which supports
         dnl both SSSE3 and SSE4.1.
         HAVE_TOOLCHAIN_SUPPORT_MSSSE3=1
         HAVE_TOOLCHAIN_SUPPORT_MSSE4_1=1
         dnl allow AVX2 code from VS2015
         HAVE_X86_AVX2=1
@@ -1914,17 +1898,17 @@ dnl = If NSS was not detected in the sys
 dnl = use the one in the source tree (mozilla/security/nss)
 dnl ========================================================
 
 MOZ_ARG_WITH_BOOL(system-nss,
 [  --with-system-nss       Use system installed NSS],
     _USE_SYSTEM_NSS=1 )
 
 if test -n "$_USE_SYSTEM_NSS"; then
-    AM_PATH_NSS(3.32, [MOZ_SYSTEM_NSS=1], [AC_MSG_ERROR([you don't have NSS installed or your version is too old])])
+    AM_PATH_NSS(3.33, [MOZ_SYSTEM_NSS=1], [AC_MSG_ERROR([you don't have NSS installed or your version is too old])])
 fi
 
 if test -n "$MOZ_SYSTEM_NSS"; then
    NSS_LIBS="$NSS_LIBS -lcrmf"
 else
    NSS_CFLAGS="-I${DIST}/include/nss"
    case "${OS_ARCH}" in
         # Only few platforms have been tested with GYP
@@ -2098,17 +2082,16 @@ VPX_ARM_ASM=
 LIBJPEG_TURBO_AS=
 LIBJPEG_TURBO_ASFLAGS=
 MOZ_PREF_EXTENSIONS=1
 MOZ_REFLOW_PERF=
 MOZ_SPELLCHECK=1
 MOZ_TOOLKIT_SEARCH=1
 MOZ_UI_LOCALE=en-US
 MOZ_UNIVERSALCHARDET=1
-MOZ_URL_CLASSIFIER=
 MOZ_XUL=1
 MOZ_ZIPWRITER=1
 MOZ_NO_SMART_CARDS=
 NECKO_COOKIES=1
 MOZ_USE_NATIVE_POPUP_WINDOWS=
 MOZ_EXCLUDE_HYPHENATION_DICTIONARIES=
 ACCESSIBILITY=1
 MOZ_TIME_MANAGER=
@@ -3723,28 +3706,16 @@ then
 fi
 
 if test -n "$MOZ_SYSTEM_SQLITE"; then
     AC_DEFINE(MOZ_SYSTEM_SQLITE)
 fi
 AC_SUBST(MOZ_SYSTEM_SQLITE)
 
 dnl ========================================================
-dnl = Enable url-classifier
-dnl ========================================================
-MOZ_ARG_ENABLE_BOOL(url-classifier,
-[  --enable-url-classifier Enable url classifier module],
-    MOZ_URL_CLASSIFIER=1,
-    MOZ_URL_CLASSIFIER= )
-if test -n "$MOZ_URL_CLASSIFIER"; then
-    AC_DEFINE(MOZ_URL_CLASSIFIER)
-fi
-AC_SUBST(MOZ_URL_CLASSIFIER)
-
-dnl ========================================================
 dnl = Disable zipwriter
 dnl ========================================================
 MOZ_ARG_DISABLE_BOOL(zipwriter,
 [  --disable-zipwriter     Disable zipwriter component],
     MOZ_ZIPWRITER=,
     MOZ_ZIPWRITER=1 )
 AC_SUBST(MOZ_ZIPWRITER)
 
@@ -4133,28 +4104,16 @@ MOZ_ARG_WITH_STRING(jitreport-granularit
                              2 - per-line information
                              3 - per-op information],
   JITREPORT_GRANULARITY=$withval,
   JITREPORT_GRANULARITY=3)
 
 AC_DEFINE_UNQUOTED(JS_DEFAULT_JITREPORT_GRANULARITY, $JITREPORT_GRANULARITY)
 
 dnl ========================================================
-dnl = Disable Mozilla's versions of RIL and Geolocation
-dnl ========================================================
-MOZ_ARG_DISABLE_BOOL(mozril-geoloc,
-[  --disable-mozril-geoloc         Disable Mozilla's RIL and geolocation],
-    DISABLE_MOZ_RIL_GEOLOC=1,
-    DISABLE_MOZ_RIL_GEOLOC= )
-if test -n "$DISABLE_MOZ_RIL_GEOLOC"; then
-   AC_DEFINE(DISABLE_MOZ_RIL_GEOLOC)
-fi
-AC_SUBST(DISABLE_MOZ_RIL_GEOLOC)
-
-dnl ========================================================
 dnl =
 dnl = Misc. Options
 dnl =
 dnl ========================================================
 MOZ_ARG_HEADER(Misc. Options)
 
 dnl ========================================================
 dnl = Define default location for MOZILLA_FIVE_HOME
@@ -4221,44 +4180,16 @@ if test -n "$GNU_CC" -a -n "$GNU_CXX"; t
     dnl Any gcc that supports firefox supports -pipe.
     CFLAGS="$CFLAGS -pipe"
     CXXFLAGS="$CXXFLAGS -pipe"
     AC_MSG_RESULT([yes])
 else
     AC_MSG_RESULT([no])
 fi
 
-dnl ========================================================
-dnl Profile guided optimization (gcc checks)
-dnl ========================================================
-dnl Test for profiling options
-dnl Under gcc 3.4+, use -fprofile-generate/-fprofile-use
-
-_SAVE_CFLAGS="$CFLAGS"
-CFLAGS="$CFLAGS -fprofile-generate -fprofile-correction"
-
-AC_MSG_CHECKING([whether C compiler supports -fprofile-generate])
-AC_TRY_COMPILE([], [return 0;],
-               [ PROFILE_GEN_CFLAGS="-fprofile-generate"
-                 result="yes" ], result="no")
-AC_MSG_RESULT([$result])
-
-if test $result = "yes"; then
-  PROFILE_GEN_LDFLAGS="-fprofile-generate"
-  PROFILE_USE_CFLAGS="-fprofile-use -fprofile-correction -Wcoverage-mismatch"
-  PROFILE_USE_LDFLAGS="-fprofile-use"
-fi
-
-CFLAGS="$_SAVE_CFLAGS"
-
-AC_SUBST(PROFILE_GEN_CFLAGS)
-AC_SUBST(PROFILE_GEN_LDFLAGS)
-AC_SUBST(PROFILE_USE_CFLAGS)
-AC_SUBST(PROFILE_USE_LDFLAGS)
-
 fi # ! SKIP_COMPILER_CHECKS
 
 AC_DEFINE(CPP_THROW_NEW, [throw()])
 AC_LANG_C
 
 if test "$COMPILE_ENVIRONMENT"; then
 MOZ_EXPAND_LIBS
 fi # COMPILE_ENVIRONMENT
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..36e5263d92f712a3047aa379f32c4d9063da9e8a
GIT binary patch
literal 18944
zc%1Eg3s@A_`S<JvSaoqRf*LOg8e?-Y3JkYpxe5!Ih%T<|Vh{-`WZ57f%!nF6pANLy
zOi1#zX}@05q;1mll7I8qCT(Kcq^PNAzFe%@gckpm<~n3)!at?BDsjHwduDb4HEo~g
z|Noxv*7Z5e%zNJRp7*@xy`I^%?9PWd6UT98G*#ue7dY*c&;8?1KibUeU!2K3H|_Ps
zFBsOmzIcPLp)IkcwfXMW+U<#3Ynz&yg~Tm&iLGK&Vnb76(K>hH_U3JMD`I1(FV$(3
z-*L{9aLYHN!tL1a*GFB$?!OV0gX_KR-!ac(*KeBR*|k1OyMCKJuW#7uqj8t>=5lh}
z8bcI!v5Tt=!v;Cy;u(gS9G8TLIT~#7E%*}`S#TV?H*#FGJ|=wS_Ul4o-T4MKC{7!q
zUA0Rb=6wX$?-qjx0^S9f=_<l&;kZ=5M~XOZ;Xfhk$On8L_&ePzgt}b<t}m?68A_Vq
zJN!umS1Ve#)e5y7w{yBile40wUNKev3JpyY4=u>ZU7;L!>|N2?*1DC`*Ls&e=c=pF
zYprW+-m0ygWWex>ufV%(<i9@ut3DO-u<{x9pu1PNMy{|(iJUw=P-+br`QH5MBYbc5
z5ny;^(c+amtX_}uQ)vFijk2v?Hm|LBIO7B}-y5{_6c_?A7*VsC;g`ETUgd|NDvxxJ
z*}9fs3`Wb<7XQ6oxAL^As?sY~%$O)G<NO>z#{lwoc-?_wtMUNro;TD7?)+8gyf=cs
z*z4|#1~E=~mpZGp0adI&e(39fNp<5~k8|9GVaYV>2@I5-<K4Zst{<RVepMRcgji|F
zAkOTICRGm3=!+&*lr9*BN~t4`6W95(FgiLi=}Nx=&&wjub7f0lrd(<ctToG}R##xH
zRZYmx=i)F;px6?;)^GIZs|lHa;E@5XjPCXR<)HJ-r7#^ETlpK>bD-EPAFbJ3CBI*N
z#G}!JTt6n+`wgmSRYi;P1W@G>8ayT}^)J_<)e(YmI;0P*ZrsQyYTF@~S!oC5S+?iR
z9x0S4*rd=jVY(EG6{4ijbTOu9l_6jVp4G73z_ntxE!e}~fRyZv;|Jt9pg2#C_Al88
zA|>^XLt_4_K!sI}mgfNAB|!Xoud>}p>H%h~1O;||cdz(I|B}j{+tqrz*(*lTT+woc
zHF$?VtE%2{M6k;<fxAu$Ef-cup$_3@DO4>iB{r`oHs=$Y31Cx*11(_&sakZsePM~%
zC(m5%_}#v_{6JwjrhX_E`&$-A!ZgT7f+oMQ5(3rqvD^*i^YW_7s|gz*+k1MWrCy^e
zvQe6bJVhF^z$1?YIzqPRO*CDz-&o^P6MR6zLe>E)g>Dl{q)?5pN(!wIa->kFkO^kR
zDF3aM!DRo9mGyyW(>Wj>Qq0mI*@+W=GcF9B1PYq!Ws~V_xQ8V5h%AnKG#2Ghx7s-e
zy65R}hFtpAehe=S+t@7k)i70IwuYs7y!@CuAdZKJ%5OmA7(WX#ze6+>7dYPBJBogO
z3<_!L>oGe?_ouF3$iM(zK7o-K)h5pIxRu8^Z9Bzj{!LhzQ95h{_ie@6t>fI@lc1-o
zEh(DQa=&H&`Ag!e{kx#G*=~NI$7S@&7whfwh3h=>r}c8Q?U!o}^@j#6^)rr@)CXqv
zr0%J=UnhRBS$0}$s=$Rw;x5UDtB>qIuZoEvb4i@vQ)=9A=fvp!yG)z_bGC-j4(YQ3
z(78}9v(!s_3odOEXO+6jm3NsHfOXS^9|H>C+kIS!l~-GXW_h(azx%itsy^b63rm8q
zNcX!{p&4ca+(i3svluOxm;(i7$<D!I>;`d$eYaHyTBSVBEiWRA-FusqXXpgLwC@JB
z-Kw4nE|g1%*#wW=V)k<`<uS6SqhM=H{L5x(p9SR9c8hw-tDdT=VJu3+H$jSSwZ*FM
zWq+W0yvJHi`&B-%Ukd5#4e)Rteyn@I_TXIHdi~}ae{_}K5^#np<zs!$kxdvBC>w=e
zm)FA}<6G+;6(fO)VYwn?>;5%qJ9<`G>GAoZ7GuCUY<s>}3f1pR5td7#CBhOZv{G0k
zg*FNEq|g>y_e11$EtSR6A)|D}DD@eG9kO#c_=(>d{76!oggQwv3!6$M#UQN5A8`wo
zBD*kiQR{RUjC>aTnN5G@x_X@bzPJJ|4%W2|OM+Gd;!^sT5mzDUXp~$w>Yolpt27-m
z^_xP$1pl;Pmp?YR-#>k5H}gxw=!O0<b`BFqSSy$cRD>KOwg;jajxwR0Fcd4_>+!nq
zfg=ztK1yaVzbkBRSv~6v5g}9T5^myqT`sTZ3Unx3big4W4Ni-A>#(!&bE+8E<FvqJ
zT}m~DmRI7!)&X6ZE@hg;C^_F+eFTad39p!IrPOKVgqwW_Ku+C>{V!q@33GkVg(0OH
zBnqK6hzPxT!UI4Fy56LSXYzP4bHE8rmq(O=ix5I+W4CJSdYXE%9~LO}H0iv-*8LSg
zC^X3}R)O~_)i(m+SfFgY`}ppe<ZWIZhY87L<ECR|HJv-aqnIDy<(+27p}n65YhgUM
z09Gq?S~wBmzuq1rtnsQlEfLmp88)>2Opup4;yGLQ%Mb|V1aoNaTqDdW*CfvLPG0Y{
z@Sw9m?EVW$kcM`#m>)N>J^XPjCoogWG>G@w%i?U^KbwNk<FWF`1JH)?fTiAU6H>jJ
zMP2Uv<ce{mJOvinDD8ql#(LEjEMSe^GXrxtjtR2^JIujZVaCo4`Jr5a7OR{RbYW^S
zizc|~b0$C<K)dL5X;g!;Q!@k;b`N1ND$rsMngVTBuclBnh~BivVeOTM<B?UtHR-eP
zWANJi@!~P3)hze>mjuc}cZy5!WE5vf7ma(Z_4c`9Ouc<W%_RiQLSsN2#Q@AuHVV&>
zNXGxWKId>J?Z-f8vF&-QS2}OmyHGkmy)#}qKcn*+>3nSGbm{yO?9us6aHGL%LERwC
zCe(UI*+^?N{toXnNxjjl9H;ia1oBtNHbk2DAwZ7maa+%y*j|ouNE(j^qvbd50Mit1
z0x^#NVe5I3V+smCRVp8r6pO7ZK=^Pb9rR}h^DEovPo<<J2!)c8CFC-eQv8XP^^Oy*
z7JnyQcP=3^zciP+dd%&<n<ku$Tox)teqb627Sspg+k*}k)y(ZcM_4NjF2l3GH8`Ix
z^}*S6X$o5DA_Sx8vW2iKi|KDcF_;J253Hu#bqS*roDQ?`>dMnM$@8#{gQr%7rE&>r
z!=ie7k^qlqaVdL7VWJ%tx&Pe2#sve?yM~}q{*%Wp#tVhM{iM-GkKgFSO+F<72}_AK
ztkfc%R|Tgk&f|OS&V{zG^}4*}%HC<r-GB=<A(eD(G2a^yEjj{C=k0OE;RdcLI)>#E
z{8(}KfDkiD>#B5po9Ckf(x2bw3~?5Yo02LuV4KP9@W;Ivy-Y5oMO|NSw+QpuqJHpK
zEUMZfht8edxImRYG+@Jx@_F($9$VK1az$-cs3N%n8)(VV*jHQEZRm*1Iy^Bea^Rf^
zYa^QKyYSHC9CcA*M9IGLwuvnT{zRPP;|iuYOE^9fqQP(QS<y3P+~fMVjuE(*E>=D$
z@JhfIuP3ouHK{3>(Awi1#_fnpc_Ru{!*y&1of#{kMEY;nh(@0G{td3esj#egzTbdU
zZe$7*^EG^c^*VxFjIHZ;VXnvXFXMX1$Oa4@qk3So74m<L$>(!~^P-UvGWkX?C-hxL
zC`oc)n)W$<jym0u-Ve-doo$hIwppi)-a^|}Wd<h{dy2^luF7tf&Tgg3R3dl-RI*Hg
zaQ%j`cqb;^Vb&5{SW1jrSIH{Gc-@e+NNMYehn|c%<IG;60pWJ1RdJ4~Dgeff8?|yy
zi1(XeF3OP$S}kN98U&OX5XZFGLTXAFA@Vw*jVwe`O5`_tl^!<Ai(wiILr<{5RA4Dj
z8Yl1{ps~?zY;1)TG6)$`?<HK@+bNK1DIxMZD9R~0hTzjF;|oL!=3oIzpb{&a;8d_w
zE~X^$HhusmV(2L~+piVR1$LQ(EBOI8EzSoGl4CY*BpOzGjYWtFTo>rH25l2m7@Jjk
z`ArpLBm)^ZNA)4aRU4dzD#p8%XSI^?vZRRRIDR8sWo0-Y%IvXrOi4UhPP9bNhY-d%
z4f*e6L#Q69p1k!aLW6lqXgG#EWT8Wh+c%e-qx?H~;C3o3de$2O?Hs9*4@JsXa5)cT
z;uvPM>4?M7xm>PT*nLtckPW8ZfC2h~!0%Es)P!v2=&gYmIhsFdI<m@P*!MQNXUQ|=
z7%aaBVL|iIhU*xU{7J;Wzsb=KL&qn;OOT!MvQ54Y?6~05H)3W`Tct)L#Qib&x~7B+
zyT>}OM}4A6RMQN~nAU1%7t+TD@y{Cbl)Av|VO#Mo$imdCmvU-C5?Da7PyU$ns){yw
zgi3=jI>>q=n7Uv$hs|IfIJkDo+z+w2<7n;=^|^<sSVHjORcX*5p9^%12b`lqqgRV7
zCNIntH~ZNnEUxrR7ga6M^1}LV(nAdrLg7}*EWDMl*aFib*A#t-^OEVRs9Z5-IiPMW
z_`qy~3uqXO)+6ZdDHt8Lz;O)SOWndkuSa?LKUCG*gKg`umb;V}{=$xQ<WR6|f9@x!
zXT-VtI}N`QXYKDa{zjZ3^?n9ePq7IGbF`-{gb~S~L45rX-+(Y5{16?NTWMi*ta3>A
zB`o=f(PcR~LSt*(`YBgD#f-UR<<$#WMp*BLoO&^U5^Im*r<v%SVV+yv5q7#5kF-%k
zSPcX>LiQ_>AsL?$8%a{fJWfa;YOh7Q7P78+7*C10?Y>o%7oRtD?vTzK1&?&zWb0lD
z&{P&a9B0QejD>KasIg$x5RQ%$3zLUDH_liXI*Yx%V$cvdJ~yU)@iYszFPy-|<~;1u
z#_nR%%_C0_H$_Y9kI>yjGr<fn*<b|tc6dOld@TRmAKVL$-*5~wm3wJoLb(5uW+x<&
z<x>gH(8jt;T~kPQG5%8__!r(I6?;)(oT3<?;Pb@Ah%EEX0Vnyq29m;Xgvv7)KX=}*
zUcBnOR()QgX6qitZb5Bl!$Cy*Mp&*N_9kJ6M-G(&;tN!q%VIpaEa_gTQJE*aA<BD<
z4cK&BBxtu;yvpkUd%}vN{DvVVQpzOE@`RN}`H?nOa&Wc>ETk;(g_|UYQ7DoeP?o=8
zCnqMlH!{(rDDG9Zv5CS+9&Mr_%D<;#4$=7!COAlH;bB^VpzS>5@+ylj>MBT5zR?pF
zr7{h0a$I2{DrI5BrA!w*qzmPYi#IQ6E1?B0ouJTnfek2iP0*OeI9v)&*yei<Dw8<*
z?-v*uq$05WLQi<Vm3<ekO5S%R`JXV%rP#IZ_27x^q~Cm%BcW9NpvNO;KnoU29SHNq
z$V)9klt-<wZdB9NQyxTnRM{ilz2aG~dTO(Nu0w}99#Tje^ea*wImza%wg%xX(<6m_
ziM$i}jW}!<<Agjpji&edt>EM>w;Z~nx_ry+3dHEuMwvx61WGJ&U%+02(&{KX9tdah
ziEa-+hFB7(%(cO<F*j-Jx}BXFS*F%%sg#<RSv|f@g|M=KEl^`tzVL~vx)k>(nl6D&
zADuB+Jn*BI$mwCJ9GmIl;!MZOrxJXZrB35MUm=+kE5rOougBLy^p&+NJ4w{5kD+HN
z^62+J!M$%k!+PptV&LS*dM0D*`ZB5NRhZb#4;0E5JwA02;+slg366g+x*bQv)!0C%
z+v7pR9fZvan#5O19q(Nu%+X+tEO({577A4-RdHAq75_D=IH)G<q9tAGR>kkCbkNEo
z++TwzoGMV+6|=%A5=`PL=XoO4ML{sm`$Q7EndfWGKxvYeji*2w2}^ZZd(7#)IyIhl
zxDgNk7E33ufsXiKfp2*ci~qWg<ohxwKa11)Py*ANj78A@YsqUNc?eX;dtAy{7XIQ8
z3*~-9p?LP1=K#=VB6DynaY3ZHyt=VO4~SJ*95RM^GS`<+u7%U{N$Bnq#5BDUI&9^b
zO69Y(!t3=7uHq;I33QxM)L|BFU5f25RPvmrlE>CnMDKwX(dvBkvd^C%=ok!igaRE(
zpkoxqGScVlrI(KZalFrYjHV5Ayh2I-3}AcZoOre=R49yUwb<K4^~cEa<+O#QO&sOB
ziux9T9|D-Nr(V7O7}U|*8JrM98e+XkKPx&#C#?Ns4{ib#O`Ng5MtB))pl?`PrLvHg
zST_tCqW28dCurPHn3FWxy1vbBOt!ACXl_$GFh!xutF)dka+8j{tdRn;#1zs_mEMJd
z*7lh5CLPNUW53qw1O%tNh)g{~Vi{GQ=3OrJRD>2tIfq%G&^qu`q7kR_mO?8i%4M&~
z)&1^rKi6j{Vka)6elAdukE@R>)b_F<TXqfx3Nrf&mUGw;Wm!<)*mTmEjmR2?ArlO?
zpyY#iko=ui6U=(OJ!oJeo2L;|hCb3?TglFTeqeF+X<k)Z%$fnw>7m!RRLA_yM`Y$f
z!k<YVy`OTR6y!j{ZOBc;G+8_$@3aQzM<fvIhEoeEsc}>^TD66~6V##C>Cm&Z=WI4r
z9OcIX;V1Zkk$^ZVr%}r4V!BC&9oD2?CL6eMq1M!oow51zdkT%nG+2QVv`&71M4nE#
zfj@398`-%~t~f1=<M*4B3+^+|T}9_)f7Rv)RGcQ&9Jzch@f#Su0kxj*NnOi+IYfeZ
z2Y=n?`hE9l&;?`C2WI8ltg8=y&-E|pl?IJ7j=H>V-wO=KqFnk9mag<U&yXQuqiH^R
z>t)rPN1lJq6koGa6-Rveh0LF5uGxdFD=pNh9Uy9DXN>UBx{DxET5f*=)MnG!>r9!c
zX^boU8uT^&PS3d^45=f;3DY9`z_M^Wj)orx+2dgNQDKjYzD;^Xsmj%7UZ%J?cEljY
zgHLZ{(bL$X<HwE|^^P$ABO|P1BF(21U)^a|)0K>iq{)$Tb#wJ$v*y^AYwtgqZxaf@
z{#j3;dPKQF(M^C?$hVDd+<0(A*Z148o4&n3_4=N}UypL`Lmf9pVdn^5FCF_Ne3zN@
z85(j8Il}Czf%32LG<>hi*UM-;`ytgc%*hPHVW;~V0cI&D;hbPN4~6G>{6l7zk&s}?
z=Z^=RgTWa7xO^y3HV6VY(g6S)kE)2F2WR>Xl!z%d&27v5sHXmaUFnO-rkKqfO1{)F
z%mqr~10_kqe2|$#r~D?t=GOg9FU-JDk|dWTp|Xwmt&-)mNCUa(lK3edOkih`z<BA!
z3R8h6busoD+4^06lgnj$zGR+DZO0~_Q4_ww<n|unJPI72QlJT8gbCsP4|E}X>jSni
zeDB7McIU8wbWTVlUKhw|n<)5<)45>#a%4^HFE$xO_}+uu{{5V^&uX?69g5{<Sh`%k
zr7YHgJ>Y>u@jW%+f&9qPr%mJ2$E~92XK2$0oQluN)NwSt3kT=n`GpZyNgd;R%U!-*
zY{Vbl*BKtdjR$qCk{m@jmqL4R0!{+ecDi)IAjEf{6f^>^a^=_Wvps|SyHDC4A;w+m
z4y)q+NS}U`KOWxhQEc^0m;U+?b=N3zGM8T^^P|M&K3(4Q_K;Soy<SGg!+OV8ndb}T
zyQaKu6!t-<%KCfC`pZE{8}W$dRH{fEPqM|0zen!rruUh98Y8&wy~uZ+Wt}tLpX9q_
zQE_<)Eo{p<f6*P-2+L_qK=4HT2w3H@mU91WPoNCWq)Sux8Z}`aIgZdCaRV5?;w1z*
zx=db62|WJUjCF0OFdzPcraz`n|1z_Mc&Ti}I590mNX||!JB=_Tn7faQVP^-NM_FKb
zPrrf6YJL)`qn|hoqkMxd#*&*8!-txBk}Fq_Fcp84dANg<+*|pCd%I;g;@+<H8#RUS
zMmV+(f3A;Pesv#Lj{MX;IYIqL`RTwEQQEqi$$s4)rQF263lu`K8K{mM>Y;?aw{UW9
zYRV6``%X^*nf=D#7T@4yFjH`?Z}>8pIhf@OT?UH^F7sK6roy6w*ZAg5fz9#9R0fcQ
zD@D^N<+XLyp(qWm^(edW94!9F33Z&FP^-eVZg!SB9_ScHLjB-7T0&h7pY2gTuxJT2
z<Vg<vW~#<RG2$G1jrE06kUZ$ZAGj6Qkk%C~F7ztmSr`GUhCJan-*KKHR>_B=#OXd7
zGjyI(#gJJnkYjE=A|%s0uO;+nHoYb3rlV}%Uc#f71ilu!qi;+3w$a^7bXQrVS5s2R
zD9&c@oNV0!q@cE2hghB(GKsg^ueWv6J2n87>u6JzxBqknckx5jbh4Od6pl!FCUG&H
zbRYC8uCw}^WnqI?Nwh@jwxAQs7p|p)N4FbQHqk!+p7xclco1SSD7b(Y8{;D>1#MWV
zpb$f`?A^C=G6tS$pio!bn(l$ARmp^=Gg`=3))Z*A&^f5P)&~bk^W_oWT?Ln^;~W-9
zb&%$UE+K|#h0gbuyBOYd<ueq!PtX&4n-1D8C1ZrXK15D>#NJ_rO^*rj_D(aVed*s3
zd5V#I3MRSJOvj25L!`G8X61)Kkm}7amhhplg%hvwRWfs<S(I;lLJWt$ZinncK4)h=
z;AKac7kc4_=p9X3C)$1a1uVy+g}4E@Uh!x>*MnoXPWOwSXt8}H7^U1x-D7lQZ1w8h
zTf^N|mo4d|GoQO8snbmP8I~2Kuh_`!?Y7AkG|{VyxtL?~)r#PM;A5sb>rPa_ysCUD
zimfM8UPNp3J<7E56d+O3n}(px_gqmJV3Xc7dK3@C7v2g+dz1%p5BsOc%8qP+v<PMO
zvx7`)No{9(m4+x%+f%$+5;XEEky2}_E>kxB3uNRo<!Y<v?-uooA`4n@Sy6B6qAw?S
z<+Fi~I1l;Dj&bFhOUyk}2Jk5R2Bx@9U+Ke<l}6{oj|G=ZT024Ff^ohB5l9>$;~u3_
z`xfV6TK&URb63a9Wg#RJE9vlZz;Cz{fzMWS*hQuZ!z@>}{F#J7$GWO89ae^cUNfBm
zGP~<m#Ti&1f4o5E0;QJ8XXXF7@Np|UcQSaCS)Z|-X3ysxXw>^2nA~COI!3PT;le2}
zdTr}_bc#c-_Wfweb28R9pM8yzPDVQDWaPJJbVqI_N506Ug^4yO^boxp@hH<7NZg5`
zt*9u1D_|`L5Nd;)J<7*M)^{iRJm`CqhZB3pqr9zw3-!$kFXtMM@{-nbGX~s<P#5nv
z1d%KKP=n59#U;uXWx>1fFaxm1zvKEMJD|N0vw9~<J-i{B^x3wk?yH<I*}6vA8kaE!
zY~2H-njYoi1??MiwyswR;0-t-FDpB!b19DuXaaMx*CRis2$E^Eb$^$lM~hWiOvi@o
z6h$V|(wnM6M>$yDX5T9lo8cidRG!g#EFR?}_VU%EJjx)#X?UsM)2+Je3-kgi4nuN`
z0Ekv!Kd2~ls2;qTxqao|=zf}C)$!cn8Xw*|UP<d`{!}d?F-7!wnrw=kZ0X+&W#34E
z9EC(b`{MA#QA#hn55Ip4=0tj6f9$0l+}%@uh`h90B$rX1;OAUOx37F}H}MTg{<z!Y
zwLL%2pHvg*u+%%wirafyjJD_JNEdbqE2ImR!p;7S8h=u~<L&nK^^OWl>zX@*v5vld
zJEYzd(otiN^_cDXXvg7>;b09Adv?;;o>eEL(6n~5d?GlvXVv3~uI*9MN8aG{o>jl0
zhqPPYKJ;g^>DAzg8u>q@i?@kU{8%v}uVM^Nmo7}(XS?V4bknN{gtq77Zhd?AFcxGy
z_lKIz@}Vj{Ym{S2yFBnGE97MQW*h}EVRlck*~J#iPN5l&9PJ4hBHv*)@xA$x>N<z|
zIU)CZq@W%o>Xylgy2|ucMoMhyS!*s=6A~wAP7BP`?gGh-+P^;kHJ{N^j{C`Kj{6o`
z7g`hAU1%<}OtfQYTY%Go)`@mM+Sk#ZMEg0~VYCxyXV4V1CBWy<tZ2Vpt<CWct{<bB
zZ|AsqXv@*E(bn9q^%r4W652wvOC=ol5!zd5y=Vu}9xa*RmwwJt&((5`TpL%1`z5Sp
zvA#sKDztmiV!7!IhJ>G^FJX3|E!SyO0g{93GBkl}<wQn_W3VJ{7k4AKbxNbpCScu&
z-c<C{=N4`yM?W1}$tct@3Ps>cK-<9W=2~=`w{vc89k-U<uSZ`yt`!*3h-(wp5bjSz
zb3<@$+_j)r<nHEf;~Idol1t`RVCEH=c?Fxj0DX1nq2KwqK(iHXJ7{s2Jx}JNgmdB<
zt{Km*+!hAQo5Zs=b{*#5!0rpVW;TO1&utM}S>Uf0cf?}@WJ9AH8172&b{p15|8Bz`
zHDjLbxD(lGiOyEQw&89Y=$GPJ%br>p=N0I0h+wS4C;@YbY>k@SroyH^ao}5EyojJy
zs8b2cLttmsB)3*bP6Suls%Ty><MSRS>AN9~20U?0g_|POr}8$rhl5VgUU87_F5n1L
z<}SbtyV<%4ZySS^f}bYbH|jVQpf?$BB_!9N_pSux$uo0$zb4sQ#=%NpG^1}9c+w~n
z>>faN>-gGzo35*2jabRXGypE>eKd=xYZW~=Fbmrbd?7-sPM<;RZ(>|&_CQ?FXEz{i
z`g53P(n!)tqOz0Ou4Zv<%yu-%YcwbK(z6d30{fYm#&4Z2Bf@HD6vOLkV7uBJ*%#_<
z(C;t*gnttli#Cq-q!Z14WxoJ8B@5Ad%XDhLEA*k2-^_6%JnSix<8Hv%B)FiTJ2-A1
z`o4sAKib1+-$HxLNz@FSkuz~-E{bbvyWPER?TT%Uja*S(W1UdPmDCAbx!8<5cU@~k
zZDYe8x)V5WZ9{koaoTVC*zfz6Zf|0+eC@Y@D;nrWdP~y?gaVqo3*j;R`@Zn+5m(e{
z&$L!rv(~?K&z?QowvhDc_lnsQv{H!H8NlKvU_8M_0Q<KI82iN_`R6|xBd~PFn@jVz
z;olFU+|+1oY116lUE0qC+3yCICM7cv00)c_7~?^h<oi8{4_)~h58==0)tVeOOv3Xc
zu58P{>el~X74N>yt!@(PTAS*G!q(cY_f)htdb!_5VC!1yn!MasCU6>?+v-Ydo3=I9
zao?PPHa9iZZ56y6_i6;TzOHs#aRbn}qY+rSSl7C{2zynqv$mnJb_@0H<Vpphh2~jb
zw?nLJBP#nC%w5;CZ31>LgE0!Lo9deZ@vdIGdaZK<Q}M%GVQXEjP)9R*IfoItPPcB-
zahG|5mp?~&TN^Yq%rbd3b4?|U&0A_43mWOCqc`eL>+7`V>-6X1);ds8qZ-?U)~(xH
zz=bhdzi;qzQQCdWZosFr`^LJa0*;#zz6bnwrZTZnXjrvdsN2x&ZP-><=&NnzUXKdj
zZfK75bCxLBr^X+xxpjN3(9jI4bkX|O)V2xE*4Ac(lGFO?T@5YZ?&&D9=;G#9cSF<N
zjdkm`_^~(KavI~@)qu^~R_AJM-daaCqUa+x_;7*K9SBp>5Ww%aw$@tO150>)SjZjD
z^=qAL_!K67?p{-STT5$0lTh#FMh&amXe9-WExy{d;&wQjtzItAw4t@OsjU&j%RssI
zZgBbwuBf4{rB>ML(?OV{(deqHyJzCL#pr5o6DFQ3jAd<i>kDL(Y6&l~&AE%Ka68wB
zdHUDq|CCP>vda{XM_!x8rE?ivCTB<F+=4hu(Yv1EWEO;f;r_4B|Jxq}+vs`P|8_71
zPI*w$Rp8Ssl+#Yc?#n+1jFcjHke}D!uM@dERXJ4g)Zn_7E5<#2UNV1jNt?&Oa;@++
zPp4zXzst$T841rlDT^%Db7#uY>sdxRY#^K@=yeh_o9eeMtcG+U8!<%ii&)mWmC<R5
zR5bMY=V~;Pjb#!&k6zbI(4iVGT*py@l+Min{%XC(YQ>du+O@25tGjI86|7#++~!@}
zbr`n>ICrxVRByB(4<^p<X8AIA6<8woV>CCbk0=`_h!->(v1T9cQ<xthzH%8~>-4b=
zI$z;+HT^^WN|{_;7*F}X2>QaLRsHkwOl9(%O6PK^UM|xjrZ*n8!Yeg_<Ce1jY+?he
zMTn2aN!2TTrdbaB|97wigTD`#;^fNYuP1++V&IqY+xT|=Yy1h`kUAr+H0{x}pQUxA
zccq&%=49|0MHyescr@b&8P8;RGatzOMdn+XA7@_5oM&HWudz4T_t?K?|Aqa|taq}8
zvML-ubNtaUBYS!F_p=XVpUfV}9?s6mc`)buIj`pgb8gHn&wVWS)7-^*Re8tA$P*D~
zV5{*-w<HxLtxpn@P9~j8+LQcb^3%!FQWmCUrMObODUB&Y%10^ZQf}f`@m2gz{%QWV
z{Ga#>ye)N3YI^D&sb5ZgEcMycgQ@SOs;P6*9BIz9=Cm)SJ(BixT7OzF?X$Fm^kwPG
z({E3&N`D~z@$>`fzf3=!9!$TKeqBa##?Fi{XM88)rx`~wqBF0}%*$Mp>CJ4&Y{}f6
z8OZ!v=Hr>KWWJkuJ~P%n*M5uLZr^0Dvv0Sz*>~ITw?Aor+WwsVS9VL*O<4t5zO3(O
zy_}Wc_=2O;@eRi_j#nM09kJPWWZ#*+C%Y?K$sWr#<?uNLId|vu<UEnnm!ss2<(PA4
z<R;|an#<>w<$7~B=eFeT%Y88SJGoEhK9l?F+~4OK@)qSe@=Ej8=G~LGKkqAfKhAq5
z@0WRR<b9k+&ddk7ElA2rawRn+eKF~Yq-T?UmGpknM@jL?Ur5eMz9V@<a#iwO$&Jb1
zOCCu6G<il!VoFZR>Xh1)`%<1tc`2nY<!nkQC5B(bXYmF6?Yx`c#JBN#_%HE2{I~g^
z^9T9&`Oo;M)Y#P7sn@37lIlwJrfyC3r?#hdrpl>bNqs!^DaicA)V|a=Qva4Zo*I>A
zO{+|+PHRu=O#5$X2h)zEy`J_qB)>4dEWI_oGyS3T$I@R;|84r&^bgWMO+TMLGh<=K
z-i$9otA3R6tBk`Lf6O?aQ47iM$ZXH-fQ~(t`E=&5GvCWhwrAN_*-Pzfp=l38&wgk>
zXrGgnomG^zCTo3GWmZF0ch&=0k7fNJ>*rYqvyNuHp7mDNzh`~HQRvv<sB(PMaoBOp
z@w($J$ADwlu_SwIwm<tj*+0(yMfPvAU(5a=J0WLj&dQukIa_mX&0U?lF89vd=G^w&
b``9i#k^9qJs%Pn2s!Nmp_nPZ}_2+*A-SuN2
--- a/security/nss/TAG-INFO
+++ b/security/nss/TAG-INFO
@@ -1,1 +1,1 @@
-NSS_3_32_RTM
+a0a4e05dcdd5
--- a/security/nss/automation/abi-check/previous-nss-release
+++ b/security/nss/automation/abi-check/previous-nss-release
@@ -1,1 +1,1 @@
-NSS_3_31_BRANCH
+NSS_3_32_BRANCH
--- a/security/nss/automation/clang-format/run_clang_format.sh
+++ b/security/nss/automation/clang-format/run_clang_format.sh
@@ -1,67 +1,68 @@
 #!/usr/bin/env bash
 
 if [[ $(id -u) -eq 0 ]]; then
     # Drop privileges by re-running this script.
     # Note: this mangles arguments, better to avoid running scripts as root.
     exec su worker -c "$0 $*"
 fi
 
+set -e
+
 # Apply clang-format on the provided folder and verify that this doesn't change any file.
 # If any file differs after formatting, the script eventually exits with 1.
 # Any differences between formatted and unformatted files is printed to stdout to give a hint what's wrong.
 
 # Includes a default set of directories NOT to clang-format on.
 blacklist=(
      "./automation" \
      "./coreconf" \
      "./doc" \
      "./pkg" \
      "./tests" \
      "./lib/libpkix" \
      "./lib/zlib" \
      "./lib/sqlite" \
      "./gtests/google_test" \
-     "./.hg" \
      "./out" \
 )
 
-top="$(dirname $0)/../.."
-cd "$top"
+top=$(cd "$(dirname $0)/../.."; pwd -P)
 
 if [ $# -gt 0 ]; then
     dirs=("$@")
 else
-    dirs=($(find . -maxdepth 2 -mindepth 1 -type d ! -path . \( ! -regex '.*/' \)))
+    cd "$top"
+    dirs=($(find . -maxdepth 2 -mindepth 1 -type d ! -path '*/.*' -print))
 fi
 
 format_folder()
 {
     for black in "${blacklist[@]}"; do
         if [[ "$1" == "$black"* ]]; then
             echo "skip $1"
             return 1
         fi
     done
     return 0
 }
 
 for dir in "${dirs[@]}"; do
-    if format_folder "$dir" ; then
+    if format_folder "$dir"; then
         c="${dir//[^\/]}"
         echo "formatting $dir ..."
-        depth=""
+        depth=()
         if [ "${#c}" == "1" ]; then
-            depth="-maxdepth 1"
+            depth+=(-maxdepth 1)
         fi
-        find "$dir" $depth -type f \( -name '*.[ch]' -o -name '*.cc' \) -exec clang-format -i {} \+
+        find "$dir" "${depth[@]}" -type f \( -name '*.[ch]' -o -name '*.cc' \) -exec clang-format -i {} \+
     fi
 done
 
 TMPFILE=$(mktemp /tmp/$(basename $0).XXXXXX)
-trap 'rm $TMPFILE' exit
-if (cd $(dirname $0); hg root >/dev/null 2>&1); then
+trap 'rm -f $TMPFILE' exit
+if [[ -d "$top/.hg" ]]; then
     hg diff --git "$top" | tee $TMPFILE
 else
     git -C "$top" diff | tee $TMPFILE
 fi
 [[ ! -s $TMPFILE ]]
--- a/security/nss/automation/release/nspr-version.txt
+++ b/security/nss/automation/release/nspr-version.txt
@@ -1,9 +1,9 @@
-4.16
+4.15
 
 # The first line of this file must contain the human readable NSPR
 # version number, which is the minimum required version of NSPR
 # that is supported by this version of NSS.
 #
 # This information is used by release automation,
 # when creating an NSS source archive.
 #
--- a/security/nss/cmd/lib/secutil.c
+++ b/security/nss/cmd/lib/secutil.c
@@ -986,17 +986,17 @@ secu_PrintUniversalString(FILE *out, con
     len = (int)(my.len / 4);
     tmp.data = (unsigned char *)PORT_Alloc(len);
     if (!tmp.data)
         goto loser;
     tmp.len = len;
     for (s = my.data, d = tmp.data; len > 0; len--) {
         PRUint32 bmpChar = (s[0] << 24) | (s[1] << 16) | (s[2] << 8) | s[3];
         s += 4;
-        if (!isprint(bmpChar))
+        if (!isprint(bmpChar & 0xFF))
             goto loser;
         *d++ = (unsigned char)bmpChar;
     }
     secu_PrintRawString(out, &tmp, m, level);
     PORT_Free(tmp.data);
     return;
 
 loser:
--- a/security/nss/cmd/modutil/error.h
+++ b/security/nss/cmd/modutil/error.h
@@ -104,17 +104,17 @@ static char *errStrings[] = {
     "ERROR: Unable to authenticate to token \"%s\".\n",
     "ERROR: Slot \"%s\" not found.\n",
     "ERROR: Failed to %s slot \"%s\".\n",
     "ERROR: Failed to update module \"%s\".\n",
     "ERROR: Failed to change defaults.\n",
     "ERROR: Failed to change default.\n",
     "ERROR: Unable to read from standard input.\n",
     "ERROR: Unknown error occurred.\n",
-    "ERROR: -nocertdb option can only be used with the -jar command.\n"
+    "ERROR: -nocertdb option can only be used with the -jar command.\n",
     "ERROR: NSS_Initialize() failed.\n"
 };
 
 typedef enum {
     FIPS_ENABLED_MSG = 0,
     FIPS_DISABLED_MSG,
     USING_DBDIR_MSG,
     CREATING_DB_MSG,
--- a/security/nss/cmd/pp/pp.c
+++ b/security/nss/cmd/pp/pp.c
@@ -79,25 +79,29 @@ main(int argc, char **argv)
                 ascii = 1;
                 break;
 
             case 'i':
                 inFile = PR_Open(optstate->value, PR_RDONLY, 0);
                 if (!inFile) {
                     fprintf(stderr, "%s: unable to open \"%s\" for reading\n",
                             progName, optstate->value);
+                    PORT_Free(typeTag);
+                    PL_DestroyOptState(optstate);
                     return -1;
                 }
                 break;
 
             case 'o':
                 outFile = fopen(optstate->value, "w");
                 if (!outFile) {
                     fprintf(stderr, "%s: unable to open \"%s\" for writing\n",
                             progName, optstate->value);
+                    PORT_Free(typeTag);
+                    PL_DestroyOptState(optstate);
                     return -1;
                 }
                 break;
 
             case 't':
                 typeTag = strdup(optstate->value);
                 break;
 
--- a/security/nss/cmd/tstclnt/tstclnt.c
+++ b/security/nss/cmd/tstclnt/tstclnt.c
@@ -26,16 +26,17 @@
 
 #include "nspr.h"
 #include "prio.h"
 #include "prnetdb.h"
 #include "nss.h"
 #include "ocsp.h"
 #include "ssl.h"
 #include "sslproto.h"
+#include "sslexp.h"
 #include "pk11func.h"
 #include "secmod.h"
 #include "plgetopt.h"
 #include "plstr.h"
 
 #if defined(WIN32)
 #include <fcntl.h>
 #include <io.h>
@@ -246,16 +247,17 @@ PrintParameterUsage(void)
     fprintf(stderr, "%-20s Require the use of FFDHE supported groups [RFC7919]\n", "-H");
     fprintf(stderr, "%-20s Read from a file instead of stdin\n", "-A");
     fprintf(stderr, "%-20s Allow 0-RTT data (TLS 1.3 only)\n", "-Z");
     fprintf(stderr, "%-20s Disconnect and reconnect up to N times total\n", "-L");
     fprintf(stderr, "%-20s Comma separated list of enabled groups for TLS key exchange.\n"
                     "%-20s The following values are valid:\n"
                     "%-20s P256, P384, P521, x25519, FF2048, FF3072, FF4096, FF6144, FF8192\n",
             "-I", "", "");
+    fprintf(stderr, "%-20s Enable alternate content type for TLS 1.3 ServerHello\n", "-X alt-server-hello");
 }
 
 static void
 Usage(const char *progName)
 {
     PrintUsageHeader(progName);
     PrintParameterUsage();
     exit(1);
@@ -909,16 +911,17 @@ ServerCertAuth serverCertAuth;
 char *hs1SniHostName = NULL;
 char *hs2SniHostName = NULL;
 PRUint16 portno = 443;
 int override = 0;
 char *requestString = NULL;
 PRInt32 requestStringLen = 0;
 PRBool requestSent = PR_FALSE;
 PRBool enableZeroRtt = PR_FALSE;
+PRBool enableAltServerHello = PR_FALSE;
 
 static int
 writeBytesToServer(PRFileDesc *s, const char *buf, int nb)
 {
     SECStatus rv;
     const char *bufp = buf;
     PRPollDesc pollDesc;
 
@@ -1173,16 +1176,26 @@ run_client(void)
         rv = SSL_OptionSet(s, SSL_ENABLE_0RTT_DATA, PR_TRUE);
         if (rv != SECSuccess) {
             SECU_PrintError(progName, "error enabling 0-RTT");
             error = 1;
             goto done;
         }
     }
 
+    /* Alternate ServerHello content type (TLS 1.3 only) */
+    if (enableAltServerHello) {
+        rv = SSL_UseAltServerHelloType(s, PR_TRUE);
+        if (rv != SECSuccess) {
+            SECU_PrintError(progName, "error enabling alternate ServerHello type");
+            error = 1;
+            goto done;
+        }
+    }
+
     /* require the use of fixed finite-field DH groups */
     if (requireDHNamedGroups) {
         rv = SSL_OptionSet(s, SSL_REQUIRE_DH_NAMED_GROUPS, PR_TRUE);
         if (rv != SECSuccess) {
             SECU_PrintError(progName, "error in requiring the use of fixed finite-field DH groups");
             error = 1;
             goto done;
         }
@@ -1507,17 +1520,17 @@ main(int argc, char **argv)
         }
     }
 
     SSL_VersionRangeGetSupported(ssl_variant_stream, &enabledVersions);
 
     /* XXX: 'B' was used in the past but removed in 3.28,
      *      please leave some time before resuing it. */
     optstate = PL_CreateOptState(argc, argv,
-                                 "46A:CDFGHI:KL:M:OR:STUV:W:YZa:bc:d:fgh:m:n:op:qr:st:uvw:z");
+                                 "46A:CDFGHI:KL:M:OR:STUV:W:X:YZa:bc:d:fgh:m:n:op:qr:st:uvw:z");
     while ((optstatus = PL_GetNextOpt(optstate)) == PL_OPT_OK) {
         switch (optstate->option) {
             case '?':
             default:
                 Usage(progName);
                 break;
 
             case '4':
@@ -1613,16 +1626,23 @@ main(int argc, char **argv)
                 if (SECU_ParseSSLVersionRangeString(optstate->value,
                                                     enabledVersions, &enabledVersions) !=
                     SECSuccess) {
                     fprintf(stderr, "Bad version specified.\n");
                     Usage(progName);
                 }
                 break;
 
+            case 'X':
+                if (!strcmp(optstate->value, "alt-server-hello")) {
+                    enableAltServerHello = PR_TRUE;
+                } else {
+                    Usage(progName);
+                }
+                break;
             case 'Y':
                 PrintCipherUsage(progName);
                 exit(0);
                 break;
 
             case 'Z':
                 enableZeroRtt = PR_TRUE;
                 break;
--- a/security/nss/coreconf/coreconf.dep
+++ b/security/nss/coreconf/coreconf.dep
@@ -5,8 +5,9 @@
 
 /*
  * A dummy header file that is a dependency for all the object files.
  * Used to force a full recompilation of NSS in Mozilla's Tinderbox
  * depend builds.  See comments in rules.mk.
  */
 
 #error "Do not include this header file."
+
--- a/security/nss/cpputil/tls_parser.h
+++ b/security/nss/cpputil/tls_parser.h
@@ -19,16 +19,17 @@
 #include "sslt.h"
 
 namespace nss_test {
 
 const uint8_t kTlsChangeCipherSpecType = 20;
 const uint8_t kTlsAlertType = 21;
 const uint8_t kTlsHandshakeType = 22;
 const uint8_t kTlsApplicationDataType = 23;
+const uint8_t kTlsAltHandshakeType = 24;
 
 const uint8_t kTlsHandshakeClientHello = 1;
 const uint8_t kTlsHandshakeServerHello = 2;
 const uint8_t kTlsHandshakeNewSessionTicket = 4;
 const uint8_t kTlsHandshakeHelloRetryRequest = 6;
 const uint8_t kTlsHandshakeEncryptedExtensions = 8;
 const uint8_t kTlsHandshakeCertificate = 11;
 const uint8_t kTlsHandshakeServerKeyExchange = 12;
--- a/security/nss/fuzz/config/clone_libfuzzer.sh
+++ b/security/nss/fuzz/config/clone_libfuzzer.sh
@@ -1,6 +1,6 @@
 #!/bin/sh
 
-LIBFUZZER_REVISION=56bd1d43451cca4b6a11d3be316bb77ab159b09d
+LIBFUZZER_REVISION=6937e68f927b6aefe526fcb9db8953f497e6e74d
 
 d=$(dirname $0)
 $d/git-copy.sh https://chromium.googlesource.com/chromium/llvm-project/llvm/lib/Fuzzer $LIBFUZZER_REVISION $d/../libFuzzer
--- a/security/nss/gtests/certdb_gtest/alg1485_unittest.cc
+++ b/security/nss/gtests/certdb_gtest/alg1485_unittest.cc
@@ -5,16 +5,17 @@
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include <stdint.h>
 
 #include "gtest/gtest.h"
 
 #include "nss.h"
 #include "scoped_ptrs.h"
+#include "prprf.h"
 
 namespace nss_test {
 
 typedef struct AVATestValuesStr {
   std::string avaString;
   bool expectedResult;
 } AVATestValues;
 
@@ -84,9 +85,28 @@ TEST_P(Alg1485CompareTest, CompareAVAStr
   ASSERT_TRUE(a && b);
   EXPECT_EQ(param.expectedResult, CERT_CompareName(a.get(), b.get()));
 }
 
 INSTANTIATE_TEST_CASE_P(ParseAVAStrings, Alg1485ParseTest,
                         ::testing::ValuesIn(kAVATestStrings));
 INSTANTIATE_TEST_CASE_P(CompareAVAStrings, Alg1485CompareTest,
                         ::testing::ValuesIn(kAVACompareStrings));
+
+TEST_F(Alg1485Test, ShortOIDTest) {
+  // This is not a valid OID (too short). CERT_GetOidString should return 0.
+  unsigned char data[] = {0x05};
+  const SECItem oid = {siBuffer, data, sizeof(data)};
+  char* result = CERT_GetOidString(&oid);
+  EXPECT_EQ(result, nullptr);
 }
+
+TEST_F(Alg1485Test, BrokenOIDTest) {
+  // This is not a valid OID (first bit of last byte is not set).
+  // CERT_GetOidString should return 0.
+  unsigned char data[] = {0x81, 0x82, 0x83, 0x84};
+  const SECItem oid = {siBuffer, data, sizeof(data)};
+  char* result = CERT_GetOidString(&oid);
+  EXPECT_EQ(15U, strlen(result));
+  EXPECT_EQ(0, strncmp("OID.UNSUPPORTED", result, 15));
+  PR_smprintf_free(result);
+}
+}
--- a/security/nss/gtests/manifest.mn
+++ b/security/nss/gtests/manifest.mn
@@ -18,18 +18,19 @@ UTIL_SRCDIRS = \
 endif
 
 ifneq ($(NSS_BUILD_SOFTOKEN_ONLY),1)
 ifneq ($(NSS_BUILD_UTIL_ONLY),1)
 NSS_SRCDIRS = \
 	certdb_gtest \
 	certhigh_gtest \
 	pk11_gtest \
+	softoken_gtest \
 	ssl_gtest \
-        nss_bogo_shim \
+	nss_bogo_shim \
 	$(NULL)
 endif
 endif
 
 DIRS = \
 	$(LIB_SRCDIRS) \
 	$(UTIL_SRCDIRS) \
 	$(NSS_SRCDIRS) \
new file mode 100644
--- /dev/null
+++ b/security/nss/gtests/softoken_gtest/Makefile
@@ -0,0 +1,45 @@
+#! gmake
+#
+# 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/.
+
+#######################################################################
+# (1) Include initial platform-independent assignments (MANDATORY).   #
+#######################################################################
+
+include manifest.mn
+
+#######################################################################
+# (2) Include "global" configuration information. (OPTIONAL)          #
+#######################################################################
+
+include $(CORE_DEPTH)/coreconf/config.mk
+
+#######################################################################
+# (3) Include "component" configuration information. (OPTIONAL)       #
+#######################################################################
+
+
+#######################################################################
+# (4) Include "local" platform-dependent assignments (OPTIONAL).      #
+#######################################################################
+
+include ../common/gtest.mk
+
+CFLAGS += -I$(CORE_DEPTH)/lib/util
+
+#######################################################################
+# (5) Execute "global" rules. (OPTIONAL)                              #
+#######################################################################
+
+include $(CORE_DEPTH)/coreconf/rules.mk
+
+#######################################################################
+# (6) Execute "component" rules. (OPTIONAL)                           #
+#######################################################################
+
+
+#######################################################################
+# (7) Execute "local" rules. (OPTIONAL).                              #
+#######################################################################
new file mode 100644
--- /dev/null
+++ b/security/nss/gtests/softoken_gtest/manifest.mn
@@ -0,0 +1,25 @@
+# -*- makefile -*-
+# 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/.
+CORE_DEPTH = ../..
+DEPTH      = ../..
+MODULE = nss
+
+CPPSRCS = \
+	softoken_gtest.cc \
+	$(NULL)
+
+INCLUDES += \
+	-I$(CORE_DEPTH)/gtests/google_test/gtest/include \
+	-I$(CORE_DEPTH)/cpputil \
+	$(NULL)
+
+REQUIRES = nspr gtest
+
+PROGRAM = softoken_gtest
+
+EXTRA_LIBS = \
+	$(DIST)/lib/$(LIB_PREFIX)gtest.$(LIB_SUFFIX) \
+	$(DIST)/lib/$(LIB_PREFIX)gtestutil.$(LIB_SUFFIX) \
+	$(NULL)
new file mode 100644
--- /dev/null
+++ b/security/nss/gtests/softoken_gtest/softoken_gtest.cc
@@ -0,0 +1,125 @@
+#include <cstdlib>
+
+#include "nspr.h"
+#include "nss.h"
+#include "pk11pub.h"
+
+#include "scoped_ptrs.h"
+
+#define GTEST_HAS_RTTI 0
+#include "gtest/gtest.h"
+
+namespace nss_test {
+
+// Given a prefix, attempts to create a unique directory that the user can do
+// work in without impacting other tests. For example, if given the prefix
+// "scratch", a directory like "scratch05c17b25" will be created in the current
+// working directory (or the location specified by NSS_GTEST_WORKDIR, if
+// defined).
+// Upon destruction, the implementation will attempt to delete the directory.
+// However, no attempt is made to first remove files in the directory - the
+// user is responsible for this. If the directory is not empty, deleting it will
+// fail.
+// Statistically, it is technically possible to fail to create a unique
+// directory name, but this is extremely unlikely given the expected workload of
+// this implementation.
+class ScopedUniqueDirectory {
+ public:
+  explicit ScopedUniqueDirectory(const std::string& prefix);
+
+  // NB: the directory must be empty upon destruction
+  ~ScopedUniqueDirectory() { assert(rmdir(mPath.c_str()) == 0); }
+
+  const std::string& GetPath() { return mPath; }
+
+ private:
+  static const int RETRY_LIMIT = 5;
+  static void GenerateRandomName(/*in/out*/ std::string& prefix);
+  static bool TryMakingDirectory(/*in/out*/ std::string& prefix);
+
+  std::string mPath;
+};
+
+ScopedUniqueDirectory::ScopedUniqueDirectory(const std::string& prefix) {
+  std::string path;
+  const char* workingDirectory = PR_GetEnvSecure("NSS_GTEST_WORKDIR");
+  if (workingDirectory) {
+    path.assign(workingDirectory);
+  }
+  path.append(prefix);
+  for (int i = 0; i < RETRY_LIMIT; i++) {
+    std::string pathCopy(path);
+    // TryMakingDirectory will modify its input. If it fails, we want to throw
+    // away the modified result.
+    if (TryMakingDirectory(pathCopy)) {
+      mPath.assign(pathCopy);
+      break;
+    }
+  }
+  assert(mPath.length() > 0);
+}
+
+void ScopedUniqueDirectory::GenerateRandomName(std::string& prefix) {
+  std::stringstream ss;
+  ss << prefix;
+  // RAND_MAX is at least 32767.
+  ss << std::setfill('0') << std::setw(4) << std::hex << rand() << rand();
+  // This will overwrite the value of prefix. This is a little inefficient, but
+  // at least it makes the code simple.
+  ss >> prefix;
+}
+
+bool ScopedUniqueDirectory::TryMakingDirectory(std::string& prefix) {
+  GenerateRandomName(prefix);
+#if defined(_WIN32)
+  return _mkdir(prefix.c_str()) == 0;
+#else
+  return mkdir(prefix.c_str(), 0777) == 0;
+#endif
+}
+
+class SoftokenTest : public ::testing::Test {
+ protected:
+  SoftokenTest() : mNSSDBDir("SoftokenTest.d-") {}
+
+  virtual void SetUp() {
+    std::string nssInitArg("sql:");
+    nssInitArg.append(mNSSDBDir.GetPath());
+    ASSERT_EQ(SECSuccess, NSS_Initialize(nssInitArg.c_str(), "", "", SECMOD_DB,
+                                         NSS_INIT_NOROOTINIT));
+  }
+
+  virtual void TearDown() {
+    ASSERT_EQ(SECSuccess, NSS_Shutdown());
+    const std::string& nssDBDirPath = mNSSDBDir.GetPath();
+    ASSERT_EQ(0, unlink((nssDBDirPath + "/cert9.db").c_str()));
+    ASSERT_EQ(0, unlink((nssDBDirPath + "/key4.db").c_str()));
+    ASSERT_EQ(0, unlink((nssDBDirPath + "/pkcs11.txt").c_str()));
+  }
+
+  ScopedUniqueDirectory mNSSDBDir;
+};
+
+TEST_F(SoftokenTest, ResetSoftokenEmptyPassword) {
+  ScopedPK11SlotInfo slot(PK11_GetInternalKeySlot());
+  ASSERT_TRUE(slot);
+  EXPECT_EQ(SECSuccess, PK11_InitPin(slot.get(), nullptr, nullptr));
+  EXPECT_EQ(SECSuccess, PK11_ResetToken(slot.get(), nullptr));
+  EXPECT_EQ(SECSuccess, PK11_InitPin(slot.get(), nullptr, nullptr));
+}
+
+TEST_F(SoftokenTest, ResetSoftokenNonEmptyPassword) {
+  ScopedPK11SlotInfo slot(PK11_GetInternalKeySlot());
+  ASSERT_TRUE(slot);
+  EXPECT_EQ(SECSuccess, PK11_InitPin(slot.get(), nullptr, "password"));
+  EXPECT_EQ(SECSuccess, PK11_ResetToken(slot.get(), nullptr));
+  EXPECT_EQ(SECSuccess, PK11_InitPin(slot.get(), nullptr, "password2"));
+}
+
+}  // namespace nss_test
+
+int main(int argc, char** argv) {
+  ::testing::InitGoogleTest(&argc, argv);
+
+  return RUN_ALL_TESTS();
+}
new file mode 100644
--- /dev/null
+++ b/security/nss/gtests/softoken_gtest/softoken_gtest.gyp
@@ -0,0 +1,51 @@
+# 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/.
+{
+  'includes': [
+    '../../coreconf/config.gypi',
+    '../common/gtest.gypi',
+  ],
+  'targets': [
+    {
+      'target_name': 'softoken_gtest',
+      'type': 'executable',
+      'sources': [
+        'softoken_gtest.cc',
+      ],
+      'dependencies': [
+        '<(DEPTH)/exports.gyp:nss_exports',
+        '<(DEPTH)/lib/util/util.gyp:nssutil3',
+        '<(DEPTH)/gtests/google_test/google_test.gyp:gtest',
+      ],
+      'conditions': [
+        [ 'test_build==1', {
+          'dependencies': [
+            '<(DEPTH)/lib/nss/nss.gyp:nss_static',
+            '<(DEPTH)/lib/pk11wrap/pk11wrap.gyp:pk11wrap_static',
+            '<(DEPTH)/lib/cryptohi/cryptohi.gyp:cryptohi',
+            '<(DEPTH)/lib/certhigh/certhigh.gyp:certhi',
+            '<(DEPTH)/lib/certdb/certdb.gyp:certdb',
+            '<(DEPTH)/lib/base/base.gyp:nssb',
+            '<(DEPTH)/lib/dev/dev.gyp:nssdev',
+            '<(DEPTH)/lib/pki/pki.gyp:nsspki',
+            '<(DEPTH)/lib/ssl/ssl.gyp:ssl',
+          ],
+        }, {
+          'dependencies': [
+            '<(DEPTH)/lib/nss/nss.gyp:nss3',
+            '<(DEPTH)/lib/ssl/ssl.gyp:ssl3',
+          ],
+        }],
+      ],
+    }
+  ],
+  'target_defaults': {
+    'include_dirs': [
+      '../../lib/util'
+    ]
+  },
+  'variables': {
+    'module': 'nss'
+  }
+}
--- a/security/nss/gtests/ssl_gtest/manifest.mn
+++ b/security/nss/gtests/ssl_gtest/manifest.mn
@@ -25,16 +25,17 @@ CPPSRCS = \
       ssl_exporter_unittest.cc \
       ssl_extension_unittest.cc \
       ssl_fragment_unittest.cc \
       ssl_fuzz_unittest.cc \
       ssl_gather_unittest.cc \
       ssl_gtest.cc \
       ssl_hrr_unittest.cc \
       ssl_loopback_unittest.cc \
+      ssl_misc_unittest.cc \
       ssl_record_unittest.cc \
       ssl_resumption_unittest.cc \
       ssl_skip_unittest.cc \
       ssl_staticrsa_unittest.cc \
       ssl_v2_client_hello_unittest.cc \
       ssl_version_unittest.cc \
       ssl_versionpolicy_unittest.cc \
       selfencrypt_unittest.cc \
--- a/security/nss/gtests/ssl_gtest/ssl_0rtt_unittest.cc
+++ b/security/nss/gtests/ssl_gtest/ssl_0rtt_unittest.cc
@@ -2,16 +2,17 @@
 /* vim: set ts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "secerr.h"
 #include "ssl.h"
 #include "sslerr.h"
+#include "sslexp.h"
 #include "sslproto.h"
 
 extern "C" {
 // This is not something that should make you happy.
 #include "libssl_internals.h"
 }
 
 #include "gtest_utils.h"
--- a/security/nss/gtests/ssl_gtest/ssl_gtest.gyp
+++ b/security/nss/gtests/ssl_gtest/ssl_gtest.gyp
@@ -26,16 +26,17 @@
         'ssl_exporter_unittest.cc',
         'ssl_extension_unittest.cc',
         'ssl_fuzz_unittest.cc',
         'ssl_fragment_unittest.cc',
         'ssl_gather_unittest.cc',
         'ssl_gtest.cc',
         'ssl_hrr_unittest.cc',
         'ssl_loopback_unittest.cc',
+        'ssl_misc_unittest.cc',
         'ssl_record_unittest.cc',
         'ssl_resumption_unittest.cc',
         'ssl_skip_unittest.cc',
         'ssl_staticrsa_unittest.cc',
         'ssl_v2_client_hello_unittest.cc',
         'ssl_version_unittest.cc',
         'ssl_versionpolicy_unittest.cc',
         'test_io.cc',
--- a/security/nss/gtests/ssl_gtest/ssl_loopback_unittest.cc
+++ b/security/nss/gtests/ssl_gtest/ssl_loopback_unittest.cc
@@ -1,20 +1,22 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set ts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include <functional>
 #include <memory>
+#include <vector>
 #include "secerr.h"
 #include "ssl.h"
 #include "sslerr.h"
 #include "sslproto.h"
+#include "ssl3prot.h"
 
 extern "C" {
 // This is not something that should make you happy.
 #include "libssl_internals.h"
 }
 
 #include "gtest_utils.h"
 #include "scoped_ptrs.h"
@@ -318,16 +320,52 @@ TEST_F(TlsConnectStreamTls13, Tls13Faile
 TEST_F(TlsConnectStreamTls13, NegotiateShortHeaders) {
   client_->SetShortHeadersEnabled();
   server_->SetShortHeadersEnabled();
   client_->ExpectShortHeaders();
   server_->ExpectShortHeaders();
   Connect();
 }
 
+TEST_F(TlsConnectStreamTls13, ClientAltHandshakeType) {
+  client_->SetAltHandshakeTypeEnabled();
+  auto filter = std::make_shared<TlsHeaderRecorder>();
+  server_->SetPacketFilter(filter);
+  Connect();
+  ASSERT_EQ(kTlsHandshakeType, filter->header(0)->content_type());
+}
+
+TEST_F(TlsConnectStreamTls13, ServerAltHandshakeType) {
+  server_->SetAltHandshakeTypeEnabled();
+  auto filter = std::make_shared<TlsHeaderRecorder>();
+  server_->SetPacketFilter(filter);
+  Connect();
+  ASSERT_EQ(kTlsHandshakeType, filter->header(0)->content_type());
+}
+
+TEST_F(TlsConnectStreamTls13, BothAltHandshakeType) {
+  client_->SetAltHandshakeTypeEnabled();
+  server_->SetAltHandshakeTypeEnabled();
+  auto header_filter = std::make_shared<TlsHeaderRecorder>();
+  auto sh_filter = std::make_shared<TlsInspectorRecordHandshakeMessage>(
+      kTlsHandshakeServerHello);
+  std::vector<std::shared_ptr<PacketFilter>> filters = {header_filter,
+                                                        sh_filter};
+  auto chained = std::make_shared<ChainedPacketFilter>(filters);
+  server_->SetPacketFilter(chained);
+  header_filter->SetAgent(server_.get());
+  header_filter->EnableDecryption();
+  Connect();
+  ASSERT_EQ(kTlsAltHandshakeType, header_filter->header(0)->content_type());
+  ASSERT_EQ(kTlsHandshakeType, header_filter->header(1)->content_type());
+  uint32_t ver;
+  ASSERT_TRUE(sh_filter->buffer().Read(0, 2, &ver));
+  ASSERT_EQ((uint32_t)(0x7a00 | TLS_1_3_DRAFT_VERSION), ver);
+}
+
 INSTANTIATE_TEST_CASE_P(
     GenericStream, TlsConnectGeneric,
     ::testing::Combine(TlsConnectTestBase::kTlsVariantsStream,
                        TlsConnectTestBase::kTlsVAll));
 INSTANTIATE_TEST_CASE_P(
     GenericDatagram, TlsConnectGeneric,
     ::testing::Combine(TlsConnectTestBase::kTlsVariantsDatagram,
                        TlsConnectTestBase::kTlsV11Plus));
new file mode 100644
--- /dev/null
+++ b/security/nss/gtests/ssl_gtest/ssl_misc_unittest.cc
@@ -0,0 +1,20 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "sslexp.h"
+
+#include "gtest_utils.h"
+
+namespace nss_test {
+
+class MiscTest : public ::testing::Test {};
+
+TEST_F(MiscTest, NonExistentExperimentalAPI) {
+  EXPECT_EQ(nullptr, SSL_GetExperimentalAPI("blah"));
+  EXPECT_EQ(SSL_ERROR_UNSUPPORTED_EXPERIMENTAL_API, PORT_GetError());
+}
+
+}  // namespace nss_test
--- a/security/nss/gtests/ssl_gtest/tls_agent.cc
+++ b/security/nss/gtests/ssl_gtest/tls_agent.cc
@@ -5,16 +5,17 @@
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "tls_agent.h"
 #include "databuffer.h"
 #include "keyhi.h"
 #include "pk11func.h"
 #include "ssl.h"
 #include "sslerr.h"
+#include "sslexp.h"
 #include "sslproto.h"
 #include "tls_parser.h"
 
 extern "C" {
 // This is not something that should make you happy.
 #include "libssl_internals.h"
 }
 
@@ -409,16 +410,23 @@ void TlsAgent::SetFallbackSCSVEnabled(bo
 
 void TlsAgent::SetShortHeadersEnabled() {
   EXPECT_TRUE(EnsureTlsSetup());
 
   SECStatus rv = SSLInt_EnableShortHeaders(ssl_fd());
   EXPECT_EQ(SECSuccess, rv);
 }
 
+void TlsAgent::SetAltHandshakeTypeEnabled() {
+  EXPECT_TRUE(EnsureTlsSetup());
+
+  SECStatus rv = SSL_UseAltServerHelloType(ssl_fd(), true);
+  EXPECT_EQ(SECSuccess, rv);
+}
+
 void TlsAgent::SetVersionRange(uint16_t minver, uint16_t maxver) {
   vrange_.min = minver;
   vrange_.max = maxver;
 
   if (ssl_fd()) {
     SECStatus rv = SSL_VersionRangeSet(ssl_fd(), &vrange_);
     EXPECT_EQ(SECSuccess, rv);
   }
--- a/security/nss/gtests/ssl_gtest/tls_agent.h
+++ b/security/nss/gtests/ssl_gtest/tls_agent.h
@@ -122,16 +122,17 @@ class TlsAgent : public PollTarget {
   void RequestClientAuth(bool requireAuth);
 
   void ConfigureSessionCache(SessionResumptionMode mode);
   void SetSessionTicketsEnabled(bool en);
   void SetSessionCacheEnabled(bool en);
   void Set0RttEnabled(bool en);
   void SetFallbackSCSVEnabled(bool en);
   void SetShortHeadersEnabled();
+  void SetAltHandshakeTypeEnabled();
   void SetVersionRange(uint16_t minver, uint16_t maxver);
   void GetVersionRange(uint16_t* minver, uint16_t* maxver);
   void CheckPreliminaryInfo();
   void ResetPreliminaryInfo();
   void SetExpectedVersion(uint16_t version);
   void SetServerKeyBits(uint16_t bits);
   void ExpectReadWriteError();
   void EnableFalseStart();
--- a/security/nss/gtests/ssl_gtest/tls_connect.cc
+++ b/security/nss/gtests/ssl_gtest/tls_connect.cc
@@ -1,15 +1,16 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set ts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "tls_connect.h"
+#include "sslexp.h"
 extern "C" {
 #include "libssl_internals.h"
 }
 
 #include <iostream>
 
 #include "databuffer.h"
 #include "gtest_utils.h"
--- a/security/nss/gtests/ssl_gtest/tls_filter.cc
+++ b/security/nss/gtests/ssl_gtest/tls_filter.cc
@@ -222,17 +222,18 @@ bool TlsRecordFilter::Protect(const TlsR
   padded.Write(padded.len(), inner_content_type, 1);
   return cipher_spec_->Protect(header, padded, ciphertext);
 }
 
 PacketFilter::Action TlsHandshakeFilter::FilterRecord(
     const TlsRecordHeader& record_header, const DataBuffer& input,
     DataBuffer* output) {
   // Check that the first byte is as requested.
-  if (record_header.content_type() != kTlsHandshakeType) {
+  if ((record_header.content_type() != kTlsHandshakeType) &&
+      (record_header.content_type() != kTlsAltHandshakeType)) {
     return KEEP;
   }
 
   bool changed = false;
   size_t offset = 0U;
   output->Allocate(input.len());  // Preallocate a little.
 
   TlsParser parser(input);
@@ -364,25 +365,40 @@ PacketFilter::Action TlsInspectorReplace
 
 PacketFilter::Action TlsConversationRecorder::FilterRecord(
     const TlsRecordHeader& header, const DataBuffer& input,
     DataBuffer* output) {
   buffer_.Append(input);
   return KEEP;
 }
 
+PacketFilter::Action TlsHeaderRecorder::FilterRecord(
+    const TlsRecordHeader& header, const DataBuffer& input,
+    DataBuffer* output) {
+  headers_.push_back(header);
+  return KEEP;
+}
+
+const TlsRecordHeader* TlsHeaderRecorder::header(size_t index) {
+  if (index > headers_.size() + 1) {
+    return nullptr;
+  }
+  return &headers_[index];
+}
+
 PacketFilter::Action ChainedPacketFilter::Filter(const DataBuffer& input,
                                                  DataBuffer* output) {
   DataBuffer in(input);
   bool changed = false;
   for (auto it = filters_.begin(); it != filters_.end(); ++it) {
     PacketFilter::Action action = (*it)->Filter(in, output);
     if (action == DROP) {
       return DROP;
     }
+
     if (action == CHANGE) {
       in = *output;
       changed = true;
     }
   }
   return changed ? CHANGE : KEEP;
 }
 
--- a/security/nss/gtests/ssl_gtest/tls_filter.h
+++ b/security/nss/gtests/ssl_gtest/tls_filter.h
@@ -128,16 +128,17 @@ inline std::ostream& operator<<(std::ost
   switch (hdr.content_type()) {
     case kTlsChangeCipherSpecType:
       stream << "CCS";
       break;
     case kTlsAlertType:
       stream << "Alert";
       break;
     case kTlsHandshakeType:
+    case kTlsAltHandshakeType:
       stream << "Handshake";
       break;
     case kTlsApplicationDataType:
       stream << "Data";
       break;
     default:
       stream << '<' << hdr.content_type() << '>';
       break;
@@ -225,17 +226,29 @@ class TlsConversationRecorder : public T
  public:
   TlsConversationRecorder(DataBuffer& buffer) : buffer_(buffer) {}
 
   virtual PacketFilter::Action FilterRecord(const TlsRecordHeader& header,
                                             const DataBuffer& input,
                                             DataBuffer* output);
 
  private:
-  DataBuffer& buffer_;
+  DataBuffer buffer_;
+};
+
+// Make a copy of the records
+class TlsHeaderRecorder : public TlsRecordFilter {
+ public:
+  virtual PacketFilter::Action FilterRecord(const TlsRecordHeader& header,
+                                            const DataBuffer& input,
+                                            DataBuffer* output);
+  const TlsRecordHeader* header(size_t index);
+
+ private:
+  std::vector<TlsRecordHeader> headers_;
 };
 
 // Runs multiple packet filters in series.
 class ChainedPacketFilter : public PacketFilter {
  public:
   ChainedPacketFilter() {}
   ChainedPacketFilter(const std::vector<std::shared_ptr<PacketFilter>> filters)
       : filters_(filters.begin(), filters.end()) {}
--- a/security/nss/lib/certdb/alg1485.c
+++ b/security/nss/lib/certdb/alg1485.c
@@ -698,24 +698,29 @@ CERT_GetOidString(const SECItem* oid)
 
 #define MAX_OID_LEN 1024 /* bytes */
 
     if (oid->len > MAX_OID_LEN) {
         PORT_SetError(SEC_ERROR_INPUT_LEN);
         return NULL;
     }
 
+    /* If the OID has length 1, we bail. */
+    if (oid->len < 2) {
+        return NULL;
+    }
+
     /* first will point to the next sequence of bytes to decode */
     first = (PRUint8*)oid->data;
     /* stop points to one past the legitimate data */
     stop = &first[oid->len];
 
     /*
-   * Check for our pseudo-encoded single-digit OIDs
-   */
+     * Check for our pseudo-encoded single-digit OIDs
+     */
     if ((*first == 0x80) && (2 == oid->len)) {
         /* Funky encoding.  The second byte is the number */
         rvString = PR_smprintf("%lu", (PRUint32)first[1]);
         if (!rvString) {
             PORT_SetError(SEC_ERROR_NO_MEMORY);
         }
         return rvString;
     }
@@ -723,16 +728,20 @@ CERT_GetOidString(const SECItem* oid)
     for (; first < stop; first = last + 1) {
         unsigned int bytesBeforeLast;
 
         for (last = first; last < stop; last++) {
             if (0 == (*last & 0x80)) {
                 break;
             }
         }
+        /* There's no first bit set, so this isn't valid. Bail.*/
+        if (last == stop) {
+            goto unsupported;
+        }
         bytesBeforeLast = (unsigned int)(last - first);
         if (bytesBeforeLast <= 3U) { /* 0-28 bit number */
             PRUint32 n = 0;
             PRUint32 c;
 
 #define CGET(i, m)    \
     c = last[-i] & m; \
     n |= c << (7 * i)
@@ -743,22 +752,22 @@ CERT_GetOidString(const SECItem* oid)
         if (!n)     \
         goto unsupported /* fall-through */
 
             switch (bytesBeforeLast) {
                 CASE(3, 0x7f);
                 CASE(2, 0x7f);
                 CASE(1, 0x7f);
                 case 0:
-                    n |=
-                        last[0] & 0x7f;
+                    n |= last[0] & 0x7f;
                     break;
             }
-            if (last[0] & 0x80)
+            if (last[0] & 0x80) {
                 goto unsupported;
+            }
 
             if (!rvString) {
                 /* This is the first number.. decompose it */
                 PRUint32 one = PR_MIN(n / 40, 2); /* never > 2 */
                 PRUint32 two = n - (one * 40);
 
                 rvString = PR_smprintf("OID.%lu.%lu", one, two);
             } else {
--- a/security/nss/lib/freebl/config.mk
+++ b/security/nss/lib/freebl/config.mk
@@ -85,13 +85,18 @@ EXTRA_SHARED_LIBS += \
 	-L$(NSSUTIL_LIB_DIR) \
 	-lnssutil3 \
 	-L$(NSPR_LIB_DIR) \
 	-lnspr4 \
 	$(NULL)
 endif
 endif
 
+ifeq ($(OS_ARCH), Linux)
+CFLAGS += -std=gnu99
+endif
+
 ifeq ($(OS_ARCH), Darwin)
+CFLAGS += -std=gnu99
 EXTRA_SHARED_LIBS += -dylib_file @executable_path/libplc4.dylib:$(DIST)/lib/libplc4.dylib -dylib_file @executable_path/libplds4.dylib:$(DIST)/lib/libplds4.dylib
 endif
 
 endif
--- a/security/nss/lib/freebl/freebl.gyp
+++ b/security/nss/lib/freebl/freebl.gyp
@@ -161,16 +161,17 @@
       }],
       [ 'OS=="mac"', {
         'xcode_settings': {
           # I'm not sure since when this is supported.
           # But I hope that doesn't matter. We also assume this is x86/x64.
           'OTHER_CFLAGS': [
             '-mpclmul',
             '-maes',
+            '-std=gnu99',
           ],
         },
       }],
       [ 'OS=="win" and target_arch=="ia32"', {
         'msvs_settings': {
           'VCCLCompilerTool': {
             #TODO: -Ox optimize flags
             'PreprocessorDefinitions': [
@@ -227,16 +228,19 @@
           }],
         ],
       }],
       [ 'OS=="linux"', {
         'defines': [
           'FREEBL_LOWHASH',
           'FREEBL_NO_DEPEND',
         ],
+        'cflags': [
+          '-std=gnu99',
+        ],
       }],
       [ 'OS=="linux" or OS=="android"', {
         'conditions': [
           [ 'target_arch=="x64"', {
             'defines': [
               'MP_IS_LITTLE_ENDIAN',
               'NSS_BEVAND_ARCFOUR',
               'MPI_AMD64',
--- a/security/nss/lib/nss/nss.h
+++ b/security/nss/lib/nss/nss.h
@@ -17,22 +17,22 @@
 
 /*
  * NSS's major version, minor version, patch level, build number, and whether
  * this is a beta release.
  *
  * The format of the version string should be
  *     "<major version>.<minor version>[.<patch level>[.<build number>]][ <ECC>][ <Beta>]"
  */
-#define NSS_VERSION "3.32" _NSS_CUSTOMIZED
+#define NSS_VERSION "3.33" _NSS_CUSTOMIZED " Beta"
 #define NSS_VMAJOR 3
-#define NSS_VMINOR 32
+#define NSS_VMINOR 33
 #define NSS_VPATCH 0
 #define NSS_VBUILD 0
-#define NSS_BETA PR_FALSE
+#define NSS_BETA PR_TRUE
 
 #ifndef RC_INVOKED
 
 #include "seccomon.h"
 
 typedef struct NSSInitParametersStr NSSInitParameters;
 
 /*
--- a/security/nss/lib/pki/pki3hack.c
+++ b/security/nss/lib/pki/pki3hack.c
@@ -175,26 +175,28 @@ STAN_AddModuleToDefaultTrustDomain(
 NSS_IMPLEMENT SECStatus
 STAN_RemoveModuleFromDefaultTrustDomain(
     SECMODModule *module)
 {
     NSSToken *token;
     NSSTrustDomain *td;
     int i;
     td = STAN_GetDefaultTrustDomain();
-    NSSRWLock_LockWrite(td->tokensLock);
     for (i = 0; i < module->slotCount; i++) {
         token = PK11Slot_GetNSSToken(module->slots[i]);
         if (token) {
             nssToken_NotifyCertsNotVisible(token);
+            NSSRWLock_LockWrite(td->tokensLock);
             nssList_Remove(td->tokenList, token);
+            NSSRWLock_UnlockWrite(td->tokensLock);
             PK11Slot_SetNSSToken(module->slots[i], NULL);
             nssToken_Destroy(token);
         }
     }
+    NSSRWLock_LockWrite(td->tokensLock);
     nssListIterator_Destroy(td->tokens);
     td->tokens = nssList_CreateIterator(td->tokenList);
     NSSRWLock_UnlockWrite(td->tokensLock);
     return SECSuccess;
 }
 
 NSS_IMPLEMENT PRStatus
 STAN_Shutdown()
--- a/security/nss/lib/softoken/pkcs11.c
+++ b/security/nss/lib/softoken/pkcs11.c
@@ -3561,17 +3561,16 @@ sftk_MechAllowsOperation(CK_MECHANISM_TY
 
 /* NSC_InitToken initializes a token. */
 CK_RV
 NSC_InitToken(CK_SLOT_ID slotID, CK_CHAR_PTR pPin,
               CK_ULONG ulPinLen, CK_CHAR_PTR pLabel)
 {
     SFTKSlot *slot = sftk_SlotFromID(slotID, PR_FALSE);
     SFTKDBHandle *handle;
-    SFTKDBHandle *certHandle;
     SECStatus rv;
     unsigned int i;
     SFTKObject *object;
 
     CHECK_FORK();
 
     if (slot == NULL)
         return CKR_SLOT_ID_INVALID;
@@ -3609,29 +3608,26 @@ NSC_InitToken(CK_SLOT_ID slotID, CK_CHAR
 
     /* then clear out the key database */
     handle = sftk_getKeyDB(slot);
     if (handle == NULL) {
         return CKR_TOKEN_WRITE_PROTECTED;
     }
 
     rv = sftkdb_ResetKeyDB(handle);
+    /* clear the password */
+    sftkdb_ClearPassword(handle);
+    /* update slot->needLogin (should be true now since no password is set) */
+    sftk_checkNeedLogin(slot, handle);
     sftk_freeDB(handle);
     if (rv != SECSuccess) {
         return CKR_DEVICE_ERROR;
     }
 
-    /* finally  mark all the user certs as non-user certs */
-    certHandle = sftk_getCertDB(slot);
-    if (certHandle == NULL)
-        return CKR_OK;
-
-    sftk_freeDB(certHandle);
-
-    return CKR_OK; /*is this the right function for not implemented*/
+    return CKR_OK;
 }
 
 /* NSC_InitPIN initializes the normal user's PIN. */
 CK_RV
 NSC_InitPIN(CK_SESSION_HANDLE hSession,
             CK_CHAR_PTR pPin, CK_ULONG ulPinLen)
 {
     SFTKSession *sp = NULL;
--- a/security/nss/lib/softoken/sdb.c
+++ b/security/nss/lib/softoken/sdb.c
@@ -1595,17 +1595,17 @@ loser:
     if (sqlDB) {
         sdb_closeDBLocal(sdb_p, sqlDB);
     }
     UNLOCK_SQLITE()
 
     return error;
 }
 
-static const char RESET_CMD[] = "DROP TABLE IF EXISTS %s;";
+static const char RESET_CMD[] = "DELETE FROM %s;";
 CK_RV
 sdb_Reset(SDB *sdb)
 {
     SDBPrivate *sdb_p = sdb->private;
     sqlite3 *sqlDB = NULL;
     char *newStr;
     int sqlerr = SQLITE_OK;
     CK_RV error = CKR_OK;
@@ -1616,27 +1616,29 @@ sdb_Reset(SDB *sdb)
     }
 
     LOCK_SQLITE()
     error = sdb_openDBLocal(sdb_p, &sqlDB, NULL);
     if (error != CKR_OK) {
         goto loser;
     }
 
-    /* delete the key table */
-    newStr = sqlite3_mprintf(RESET_CMD, sdb_p->table);
-    if (newStr == NULL) {
-        error = CKR_HOST_MEMORY;
-        goto loser;
+    if (tableExists(sqlDB, sdb_p->table)) {
+        /* delete the contents of the key table */
+        newStr = sqlite3_mprintf(RESET_CMD, sdb_p->table);
+        if (newStr == NULL) {
+            error = CKR_HOST_MEMORY;
+            goto loser;
+        }
+        sqlerr = sqlite3_exec(sqlDB, newStr, NULL, 0, NULL);
+        sqlite3_free(newStr);
+
+        if (sqlerr != SQLITE_OK)
+            goto loser;
     }
-    sqlerr = sqlite3_exec(sqlDB, newStr, NULL, 0, NULL);
-    sqlite3_free(newStr);
-
-    if (sqlerr != SQLITE_OK)
-        goto loser;
 
     /* delete the password entry table */
     sqlerr = sqlite3_exec(sqlDB, "DROP TABLE IF EXISTS metaData;",
                           NULL, 0, NULL);
 
 loser:
     /* fix up the error if necessary */
     if (error == CKR_OK) {
@@ -1861,40 +1863,39 @@ sdb_init(char *dbname, char *table, sdbD
 
     /* access to network filesystems are significantly slower than local ones
      * for database operations. In those cases we need to create a cached copy
      * of the database in a temporary location on the local disk. SQLITE
      * already provides a way to create a temporary table and initialize it,
      * so we use it for the cache (see sdb_buildCache for how it's done).*/
 
     /*
-      * we decide whether or not to use the cache based on the following input.
-      *
-      * NSS_SDB_USE_CACHE environment variable is non-existant or set to
-      *   anything other than "no" or "yes" ("auto", for instance).
-      *   This is the normal case. NSS will measure the performance of access
-      *   to the temp database versus the access to the users passed in
-      *   database location. If the temp database location is "significantly"
-      *   faster we will use the cache.
-      *
-      * NSS_SDB_USE_CACHE environment variable is set to "no": cache will not
-      *   be used.
-      *
-      * NSS_SDB_USE_CACHE environment variable is set to "yes": cache will
-      *   always be used.
-      *
-      * It is expected that most applications would use the "auto" selection,
-      * the environment variable is primarily to simplify testing, and to
-      * correct potential corner cases where  */
+     * we decide whether or not to use the cache based on the following input.
+     *
+     * NSS_SDB_USE_CACHE environment variable is set to anything other than
+     *   "yes" or "no" (for instance, "auto"): NSS will measure the performance
+     *   of access to the temp database versus the access to the user's
+     *   passed-in database location. If the temp database location is
+     *   "significantly" faster we will use the cache.
+     *
+     * NSS_SDB_USE_CACHE environment variable is nonexistent or set to "no":
+     *   cache will not be used.
+     *
+     * NSS_SDB_USE_CACHE environment variable is set to "yes": cache will
+     *   always be used.
+     *
+     * It is expected that most applications will not need this feature, and
+     * thus it is disabled by default.
+     */
 
     env = PR_GetEnvSecure("NSS_SDB_USE_CACHE");
 
-    if (env && PORT_Strcasecmp(env, "no") == 0) {
+    if (!env || PORT_Strcasecmp(env, "no") == 0) {
         enableCache = PR_FALSE;
-    } else if (env && PORT_Strcasecmp(env, "yes") == 0) {
+    } else if (PORT_Strcasecmp(env, "yes") == 0) {
         enableCache = PR_TRUE;
     } else {
         char *tempDir = NULL;
         PRUint32 tempOps = 0;
         /*
          *  Use PR_Access to determine how expensive it
          * is to check for the existance of a local file compared to the same
          * check in the temp directory. If the temp directory is faster, cache
@@ -2030,20 +2031,21 @@ s_open(const char *directory, const char
 #endif
 
     /* how long does it take to test for a non-existant file in our working
      * directory? Allows us to test if we may be on a network file system */
     accessOps = 1;
     {
         char *env;
         env = PR_GetEnvSecure("NSS_SDB_USE_CACHE");
-        /* If the environment variable is set to yes or no, sdb_init() will
-         * ignore the value of accessOps, and we can skip the measuring.*/
-        if (!env || ((PORT_Strcasecmp(env, "no") != 0) &&
-                     (PORT_Strcasecmp(env, "yes") != 0))) {
+        /* If the environment variable is undefined or set to yes or no,
+         * sdb_init() will ignore the value of accessOps, and we can skip the
+         * measuring.*/
+        if (env && PORT_Strcasecmp(env, "no") != 0 &&
+            PORT_Strcasecmp(env, "yes") != 0) {
             accessOps = sdb_measureAccess(directory);
         }
     }
 
     /*
      * open the cert data base
      */
     if (certdb) {
--- a/security/nss/lib/softoken/softkver.h
+++ b/security/nss/lib/softoken/softkver.h
@@ -16,16 +16,16 @@
 
 /*
  * Softoken's major version, minor version, patch level, build number,
  * and whether this is a beta release.
  *
  * The format of the version string should be
  *     "<major version>.<minor version>[.<patch level>[.<build number>]][ <ECC>][ <Beta>]"
  */
-#define SOFTOKEN_VERSION "3.32" SOFTOKEN_ECC_STRING
+#define SOFTOKEN_VERSION "3.33" SOFTOKEN_ECC_STRING " Beta"
 #define SOFTOKEN_VMAJOR 3
-#define SOFTOKEN_VMINOR 32
+#define SOFTOKEN_VMINOR 33
 #define SOFTOKEN_VPATCH 0
 #define SOFTOKEN_VBUILD 0
-#define SOFTOKEN_BETA PR_FALSE
+#define SOFTOKEN_BETA PR_TRUE
 
 #endif /* _SOFTKVER_H_ */
--- a/security/nss/lib/ssl/SSLerrs.h
+++ b/security/nss/lib/ssl/SSLerrs.h
@@ -506,8 +506,17 @@ ER3(SSL_ERROR_MALFORMED_PSK_KEY_EXCHANGE
 ER3(SSL_ERROR_MISSING_PSK_KEY_EXCHANGE_MODES, (SSL_ERROR_BASE + 159),
     "SSL expected a PSK key exchange modes extension.")
 
 ER3(SSL_ERROR_DOWNGRADE_WITH_EARLY_DATA, (SSL_ERROR_BASE + 160),
     "SSL got a pre-TLS 1.3 version even though we sent early data.")
 
 ER3(SSL_ERROR_TOO_MUCH_EARLY_DATA, (SSL_ERROR_BASE + 161),
     "SSL received more early data than permitted.")
+
+ER3(SSL_ERROR_RX_UNEXPECTED_END_OF_EARLY_DATA, (SSL_ERROR_BASE + 162),
+    "SSL received an unexpected End of Early Data message.")
+
+ER3(SSL_ERROR_RX_MALFORMED_END_OF_EARLY_DATA, (SSL_ERROR_BASE + 163),
+    "SSL received a malformed End of Early Data message.")
+
+ER3(SSL_ERROR_UNSUPPORTED_EXPERIMENTAL_API, (SSL_ERROR_BASE + 164),
+    "An experimental API was called, but not supported.")
--- a/security/nss/lib/ssl/exports.gyp
+++ b/security/nss/lib/ssl/exports.gyp
@@ -10,16 +10,17 @@
       'target_name': 'lib_ssl_exports',
       'type': 'none',
       'copies': [
         {
           'files': [
             'preenc.h',
             'ssl.h',
             'sslerr.h',
+            'sslexp.h',
             'sslproto.h',
             'sslt.h'
           ],
           'destination': '<(nss_public_dist_dir)/<(module)'
         }
       ]
     }
   ],
--- a/security/nss/lib/ssl/manifest.mn
+++ b/security/nss/lib/ssl/manifest.mn
@@ -5,16 +5,17 @@
 CORE_DEPTH = ../..
 
 # DEFINES = -DTRACE
 
 EXPORTS = \
         ssl.h \
         sslt.h \
         sslerr.h \
+        sslexp.h \
         sslproto.h \
         preenc.h \
         $(NULL)
 
 MODULE = nss
 MAPFILE = $(OBJDIR)/ssl.def
 
 CSRCS = \
--- a/security/nss/lib/ssl/ssl.def
+++ b/security/nss/lib/ssl/ssl.def
@@ -229,8 +229,14 @@ SSL_SetSessionTicketKeyPair;
 ;+};
 ;+NSS_3.30.0.1 { # Additional symbols for NSS 3.30 release
 ;+    global:
 SSL_AlertReceivedCallback;
 SSL_AlertSentCallback;
 ;+    local:
 ;+*;
 ;+};
+;+NSS_3.33 {    # NSS 3.33 release
+;+    global:
+SSL_GetExperimentalAPI;
+;+    local:
+;+*;
+;+};
--- a/security/nss/lib/ssl/ssl.h
+++ b/security/nss/lib/ssl/ssl.h
@@ -1369,11 +1369,18 @@ extern const char *NSSSSL_GetVersion(voi
  * return SECSuccess (normally), but that does not mean that the application
  * should continue using the connection. If the application passes a non-zero
  * value for second argument (error), or if SSL_AuthCertificateComplete returns
  * anything other than SECSuccess, then the application should close the
  * connection.
  */
 SSL_IMPORT SECStatus SSL_AuthCertificateComplete(PRFileDesc *fd,
                                                  PRErrorCode error);
+
+/*
+ * This is used to access experimental APIs.  Don't call this directly.  This is
+ * used to enable the experimental APIs that are defined in "sslexp.h".
+ */
+SSL_IMPORT void *SSL_GetExperimentalAPI(const char *name);
+
 SEC_END_PROTOS
 
 #endif /* __ssl_h_ */
--- a/security/nss/lib/ssl/ssl3con.c
+++ b/security/nss/lib/ssl/ssl3con.c
@@ -1085,17 +1085,18 @@ ssl_ClientReadVersion(sslSocket *ss, PRU
     }
 
 #ifdef TLS_1_3_DRAFT_VERSION
     if (temp == SSL_LIBRARY_VERSION_TLS_1_3) {
         (void)SSL3_SendAlert(ss, alert_fatal, protocol_version);
         PORT_SetError(SSL_ERROR_UNSUPPORTED_VERSION);
         return SECFailure;
     }
-    if (temp == tls13_EncodeDraftVersion(SSL_LIBRARY_VERSION_TLS_1_3)) {
+    if (temp == tls13_EncodeDraftVersion(SSL_LIBRARY_VERSION_TLS_1_3) || (ss->opt.enableAltHandshaketype &&
+                                                                          (temp == tls13_EncodeAltDraftVersion(SSL_LIBRARY_VERSION_TLS_1_3)))) {
         v = SSL_LIBRARY_VERSION_TLS_1_3;
     } else {
         v = (SSL3ProtocolVersion)temp;
     }
 #else
     v = (SSL3ProtocolVersion)temp;
 #endif
 
@@ -2972,30 +2973,36 @@ ssl3_FlushHandshake(sslSocket *ss, PRInt
  */
 static SECStatus
 ssl3_FlushHandshakeMessages(sslSocket *ss, PRInt32 flags)
 {
     static const PRInt32 allowedFlags = ssl_SEND_FLAG_FORCE_INTO_BUFFER |
                                         ssl_SEND_FLAG_CAP_RECORD_VERSION;
     PRInt32 count = -1;
     SECStatus rv;
+    SSL3ContentType ct = content_handshake;
 
     PORT_Assert(ss->opt.noLocks || ssl_HaveSSL3HandshakeLock(ss));
     PORT_Assert(ss->opt.noLocks || ssl_HaveXmitBufLock(ss));
 
     if (!ss->sec.ci.sendBuf.buf || !ss->sec.ci.sendBuf.len)
         return SECSuccess;
 
     /* only these flags are allowed */
     PORT_Assert(!(flags & ~allowedFlags));
     if ((flags & ~allowedFlags) != 0) {
         PORT_SetError(SEC_ERROR_INVALID_ARGS);
         return SECFailure;
     }
-    count = ssl3_SendRecord(ss, NULL, content_handshake,
+    /* Maybe send the first message with alt handshake type. */
+    if (ss->ssl3.hs.altHandshakeType) {
+        ct = content_alt_handshake;
+        ss->ssl3.hs.altHandshakeType = PR_FALSE;
+    }
+    count = ssl3_SendRecord(ss, NULL, ct,
                             ss->sec.ci.sendBuf.buf,
                             ss->sec.ci.sendBuf.len, flags);
     if (count < 0) {
         int err = PORT_GetError();
         PORT_Assert(err != PR_WOULD_BLOCK_ERROR);
         if (err == PR_WOULD_BLOCK_ERROR) {
             PORT_SetError(SEC_ERROR_LIBRARY_FAILURE);
         }
@@ -9316,17 +9323,17 @@ ssl3_SendServerHello(sslSocket *ss)
     rv = ssl3_AppendHandshakeHeader(ss, server_hello, length);
     if (rv != SECSuccess) {
         return rv; /* err set by AppendHandshake. */
     }
 
     if (IS_DTLS(ss) && ss->version < SSL_LIBRARY_VERSION_TLS_1_3) {
         version = dtls_TLSVersionToDTLSVersion(ss->version);
     } else {
-        version = tls13_EncodeDraftVersion(ss->version);
+        version = ss->ssl3.hs.altHandshakeType ? tls13_EncodeAltDraftVersion(ss->version) : tls13_EncodeDraftVersion(ss->version);
     }
 
     rv = ssl3_AppendHandshakeNumber(ss, version, 2);
     if (rv != SECSuccess) {
         return rv; /* err set by AppendHandshake. */
     }
     /* Random already generated in ssl3_HandleClientHello */
     rv = ssl3_AppendHandshake(
@@ -9747,23 +9754,22 @@ ssl3_HandleCertificateVerify(sslSocket *
         if (rv != SECSuccess) {
             errCode = PORT_GetError();
             desc = decrypt_error;
             goto alert_loser;
         }
 
         hashAlg = ssl_SignatureSchemeToHashType(sigScheme);
 
-        if (hashes->u.pointer_to_hash_input.data) {
-            rv = ssl3_ComputeHandshakeHash(hashes->u.pointer_to_hash_input.data,
-                                           hashes->u.pointer_to_hash_input.len,
-                                           hashAlg, &localHashes);
-        } else {
-            rv = SECFailure;
-        }
+        /* Read from the message buffer, but we need to use only up to the end
+         * of the previous handshake message. The length of the transcript up to
+         * that point is saved in |hashes->u.transcriptLen|. */
+        rv = ssl3_ComputeHandshakeHash(ss->ssl3.hs.messages.buf,
+                                       hashes->u.transcriptLen,
+                                       hashAlg, &localHashes);
 
         if (rv == SECSuccess) {
             hashesForVerify = &localHashes;
         } else {
             errCode = SSL_ERROR_DIGEST_FAILURE;
             desc = decrypt_error;
             goto alert_loser;
         }
@@ -11653,25 +11659,25 @@ ssl3_HandleHandshakeMessage(sslSocket *s
                  * ssl3_HandleCertificateVerify, which will tell us which
                  * hash function we must use.
                  *
                  * (ssl3_HandleCertificateVerify cannot simply look at the
                  * buffer length itself, because at the time we reach it,
                  * additional handshake messages will have been added to the
                  * buffer, e.g. the certificate_verify message itself.)
                  *
-                 * Therefore, we use SSL3Hashes.u.pointer_to_hash_input
-                 * to signal the current state of the buffer.
+                 * Therefore, we use SSL3Hashes.u.transcriptLen to save how much
+                 * data there is and read directly from ss->ssl3.hs.messages
+                 * when calculating the hashes.
                  *
                  * ssl3_HandleCertificateVerify will detect
                  *     hashType == handshake_hash_record
                  * and use that information to calculate the hash.
                  */
-                hashes.u.pointer_to_hash_input.data = ss->ssl3.hs.messages.buf;
-                hashes.u.pointer_to_hash_input.len = ss->ssl3.hs.messages.len;
+                hashes.u.transcriptLen = ss->ssl3.hs.messages.len;
                 hashesPtr = &hashes;
             } else {
                 computeHashes = PR_TRUE;
             }
         }
     } else {
         if (type == certificate_verify) {
             computeHashes = TLS13_IN_HS_STATE(ss, wait_cert_verify);
@@ -12724,16 +12730,24 @@ ssl3_HandleRecord(sslSocket *ss, SSL3Cip
 /* It's a record that must be handled by ssl itself, not the application.
     */
 process_it:
     /* XXX  Get the xmit lock here.  Odds are very high that we'll be xmiting
      * data ang getting the xmit lock here prevents deadlocks.
      */
     ssl_GetSSL3HandshakeLock(ss);
 
+    /* Special case: allow alt content type for TLS 1.3 ServerHello. */
+    if ((rType == content_alt_handshake) &&
+        (ss->vrange.max >= SSL_LIBRARY_VERSION_TLS_1_3) &&
+        (ss->ssl3.hs.ws == wait_server_hello) &&
+        (ss->opt.enableAltHandshaketype) &&
+        (!IS_DTLS(ss))) {
+        rType = content_handshake;
+    }
     /* All the functions called in this switch MUST set error code if
     ** they return SECFailure or SECWouldBlock.
     */
     switch (rType) {
         case content_change_cipher_spec:
             rv = ssl3_HandleChangeCipherSpecs(ss, databuf);
             break;
         case content_alert:
--- a/security/nss/lib/ssl/ssl3prot.h
+++ b/security/nss/lib/ssl/ssl3prot.h
@@ -36,17 +36,18 @@ typedef PRUint16 ssl3CipherSuite;
 #define DTLS_RECORD_HEADER_LENGTH 13
 
 #define MAX_FRAGMENT_LENGTH 16384
 
 typedef enum {
     content_change_cipher_spec = 20,
     content_alert = 21,
     content_handshake = 22,
-    content_application_data = 23
+    content_application_data = 23,
+    content_alt_handshake = 24
 } SSL3ContentType;
 
 typedef struct {
     SSL3ContentType type;
     SSL3ProtocolVersion version;
     PRUint16 length;
     SECItem fragment;
 } SSL3Plaintext;
@@ -230,17 +231,17 @@ typedef struct {
  * which, if |hashAlg==ssl_hash_none| is also a SSL3HashesIndividually
  * struct. */
 typedef struct {
     unsigned int len;
     SSLHashType hashAlg;
     union {
         PRUint8 raw[64];
         SSL3HashesIndividually s;
-        SECItem pointer_to_hash_input;
+        unsigned int transcriptLen;
     } u;
 } SSL3Hashes;
 
 typedef struct {
     union {
         PRUint8 anonymous;
         SSL3Hashes certified;
     } u;
--- a/security/nss/lib/ssl/sslerr.h
+++ b/security/nss/lib/ssl/sslerr.h
@@ -241,15 +241,20 @@ typedef enum {
     SSL_ERROR_RX_UNEXPECTED_HELLO_RETRY_REQUEST = (SSL_ERROR_BASE + 154),
     SSL_ERROR_RX_MALFORMED_HELLO_RETRY_REQUEST = (SSL_ERROR_BASE + 155),
     SSL_ERROR_BAD_2ND_CLIENT_HELLO = (SSL_ERROR_BASE + 156),
     SSL_ERROR_MISSING_SIGNATURE_ALGORITHMS_EXTENSION = (SSL_ERROR_BASE + 157),
     SSL_ERROR_MALFORMED_PSK_KEY_EXCHANGE_MODES = (SSL_ERROR_BASE + 158),
     SSL_ERROR_MISSING_PSK_KEY_EXCHANGE_MODES = (SSL_ERROR_BASE + 159),
     SSL_ERROR_DOWNGRADE_WITH_EARLY_DATA = (SSL_ERROR_BASE + 160),
     SSL_ERROR_TOO_MUCH_EARLY_DATA = (SSL_ERROR_BASE + 161),
+    SSL_ERROR_RX_UNEXPECTED_END_OF_EARLY_DATA = (SSL_ERROR_BASE + 162),
+    SSL_ERROR_RX_MALFORMED_END_OF_EARLY_DATA = (SSL_ERROR_BASE + 163),
+
+    SSL_ERROR_UNSUPPORTED_EXPERIMENTAL_API = (SSL_ERROR_BASE + 164),
+
     SSL_ERROR_END_OF_LIST   /* let the c compiler determine the value of this. */
 } SSLErrorCodes;
 #endif /* NO_SECURITY_ERROR_ENUM */
 
 /* clang-format on */
 
 #endif /* __SSL_ERR_H_ */
new file mode 100644
--- /dev/null
+++ b/security/nss/lib/ssl/sslexp.h
@@ -0,0 +1,37 @@
+/*
+ * This file contains prototypes for experimental SSL functions.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef __sslexp_h_
+#define __sslexp_h_
+
+#include "ssl.h"
+#include "sslerr.h"
+
+SEC_BEGIN_PROTOS
+
+/* The functions in this header file are not guaranteed to remain available in
+ * future NSS versions. Code that uses these functions needs to safeguard
+ * against the function not being available. */
+
+#define SSL_EXPERIMENTAL_API(name, arglist, args)                   \
+    (SSL_GetExperimentalAPI(name)                                   \
+         ? ((SECStatus(*) arglist)SSL_GetExperimentalAPI(name))args \
+         : SECFailure)
+
+/* Allow the ServerHello to be record type 24. Experiment to test:
+ * https://github.com/tlswg/tls13-spec/pull/1051
+ * This will either become part of the standard or be disabled
+ * after we have tested it.
+ */
+#define SSL_UseAltServerHelloType(fd, enable)                \
+    SSL_EXPERIMENTAL_API("SSL_UseAltServerHelloType",        \
+                         (PRFileDesc * _fd, PRBool _enable), \
+                         (fd, enable))
+
+SEC_END_PROTOS
+
+#endif /* __sslexp_h_ */
--- a/security/nss/lib/ssl/sslimpl.h
+++ b/security/nss/lib/ssl/sslimpl.h
@@ -293,16 +293,17 @@ typedef struct sslOptionsStr {
     unsigned int reuseServerECDHEKey : 1;
     unsigned int enableFallbackSCSV : 1;
     unsigned int enableServerDhe : 1;
     unsigned int enableExtendedMS : 1;
     unsigned int enableSignedCertTimestamps : 1;
     unsigned int requireDHENamedGroups : 1;
     unsigned int enable0RttData : 1;
     unsigned int enableShortHeaders : 1;
+    unsigned int enableAltHandshaketype : 1;
 } sslOptions;
 
 typedef enum { sslHandshakingUndetermined = 0,
                sslHandshakingAsClient,
                sslHandshakingAsServer
 } sslHandshakingType;
 
 #define SSL_LOCK_RANK_SPEC 255
@@ -878,16 +879,17 @@ typedef struct SSL3HandshakeStateStr {
     ssl3CipherSuite zeroRttSuite;   /* The cipher suite we used for 0-RTT. */
     PRCList bufferedEarlyData;      /* Buffered TLS 1.3 early data
                                      * on server.*/
     PRBool helloRetry;              /* True if HelloRetryRequest has been sent
                                      * or received. */
     ssl3KEADef kea_def_mutable;     /* Used to hold the writable kea_def
                                      * we use for TLS 1.3 */
     PRBool shortHeaders;            /* Assigned if we are doing short headers. */
+    PRBool altHandshakeType;        /* Assigned if we are doing the wrapped handshake. */
 } SSL3HandshakeState;
 
 /*
 ** This is the "ssl3" struct, as in "ss->ssl3".
 ** note:
 ** usually,   crSpec == cwSpec and prSpec == pwSpec.
 ** Sometimes, crSpec == pwSpec and prSpec == cwSpec.
 ** But there are never more than 2 actual specs.
--- a/security/nss/lib/ssl/sslsock.c
+++ b/security/nss/lib/ssl/sslsock.c
@@ -6,16 +6,17 @@
  *
  * This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 #include "seccomon.h"
 #include "cert.h"
 #include "keyhi.h"
 #include "ssl.h"
+#include "sslexp.h"
 #include "sslimpl.h"
 #include "sslproto.h"
 #include "nspr.h"
 #include "private/pprio.h"
 #include "nss.h"
 #include "pk11pqg.h"
 
 static const sslSocketOps ssl_default_ops = { /* No SSL. */
@@ -75,20 +76,21 @@ static sslOptions ssl_defaults = {
     PR_TRUE,               /* reuseServerECDHEKey */
     PR_FALSE,              /* enableFallbackSCSV */
     PR_TRUE,               /* enableServerDhe */
     PR_FALSE,              /* enableExtendedMS    */
     PR_FALSE,              /* enableSignedCertTimestamps */
     PR_FALSE,              /* requireDHENamedGroups */
     PR_FALSE,              /* enable0RttData */
 #ifdef NSS_ENABLE_TLS13_SHORT_HEADERS
-    PR_TRUE /* enableShortHeaders */
+    PR_TRUE, /* enableShortHeaders */
 #else
-    PR_FALSE /* enableShortHeaders */
+    PR_FALSE, /* enableShortHeaders */
 #endif
+    PR_FALSE /* enableAltHandshaketype */
 };
 
 /*
  * default range of enabled SSL/TLS protocols
  */
 static SSLVersionRange versions_defaults_stream = {
     SSL_LIBRARY_VERSION_TLS_1_0,
     SSL_LIBRARY_VERSION_TLS_1_2
@@ -2209,17 +2211,17 @@ ssl3_GetEffectiveVersionPolicy(SSLProtoc
         minPolicy > maxPolicy) {
         return SECFailure;
     }
     effectivePolicy->min = PR_MAX(effectivePolicy->min, minPolicy);
     effectivePolicy->max = PR_MIN(effectivePolicy->max, maxPolicy);
     return SECSuccess;
 }
 
-/* 
+/*
  * Assumes that rangeParam values are within the supported boundaries,
  * but should contain all potentially allowed versions, even if they contain
  * conflicting versions.
  * Will return the overlap, or a NONE range if system policy is invalid.
  */
 static SECStatus
 ssl3_CreateOverlapWithPolicy(SSLProtocolVariant protocolVariant,
                              SSLVersionRange *input,
@@ -3835,8 +3837,53 @@ SSL_CanBypass(CERTCertificate *cert, SEC
 {
     if (!pcanbypass) {
         PORT_SetError(SEC_ERROR_INVALID_ARGS);
         return SECFailure;
     }
     *pcanbypass = PR_FALSE;
     return SECSuccess;
 }
+
+/* Functions that are truly experimental use EXP, functions that are no longer
+ * experimental use PUB.
+ *
+ * When initially defining a new API, add that API here using the EXP() macro
+ * and name the function with a SSLExp_ prefix.  Define the experimental API as
+ * a macro in sslexp.h using the SSL_EXPERIMENTAL_API() macro defined there.
+ *
+ * Once an API is stable and proven, move the macro definition in sslexp.h to a
+ * proper function declaration in ssl.h.  Keeping the function in this list
+ * ensures that code built against the release that contained the experimental
+ * API will continue to work; use PUB() to reference the public function.
+ */
+#define EXP(n)                \
+    {                         \
+        "SSL_" #n, SSLExp_##n \
+    }
+#define PUB(n)             \
+    {                      \
+        "SSL_" #n, SSL_##n \
+    }
+struct {
+    const char *const name;
+    void *function;
+} ssl_experimental_functions[] = {
+#ifndef SSL_DISABLE_EXPERIMENTAL_API
+    EXP(UseAltServerHelloType),
+#endif
+    { "", NULL }
+};
+#undef EXP
+#undef PUB
+
+void *
+SSL_GetExperimentalAPI(const char *name)
+{
+    unsigned int i;
+    for (i = 0; i < PR_ARRAY_SIZE(ssl_experimental_functions); ++i) {
+        if (strcmp(name, ssl_experimental_functions[i].name) == 0) {
+            return ssl_experimental_functions[i].function;
+        }
+    }
+    PORT_SetError(SSL_ERROR_UNSUPPORTED_EXPERIMENTAL_API);
+    return NULL;
+}
--- a/security/nss/lib/ssl/tls13con.c
+++ b/security/nss/lib/ssl/tls13con.c
@@ -4470,16 +4470,27 @@ tls13_EncodeDraftVersion(SSL3ProtocolVer
 #ifdef TLS_1_3_DRAFT_VERSION
     if (version == SSL_LIBRARY_VERSION_TLS_1_3) {
         return 0x7f00 | TLS_1_3_DRAFT_VERSION;
     }
 #endif
     return (PRUint16)version;
 }
 
+PRUint16
+tls13_EncodeAltDraftVersion(SSL3ProtocolVersion version)
+{
+#ifdef TLS_1_3_DRAFT_VERSION
+    if (version == SSL_LIBRARY_VERSION_TLS_1_3) {
+        return 0x7a00 | TLS_1_3_DRAFT_VERSION;
+    }
+#endif
+    return (PRUint16)version;
+}
+
 /* Pick the highest version we support that is also advertised. */
 SECStatus
 tls13_NegotiateVersion(sslSocket *ss, const TLSExtension *supported_versions)
 {
     PRUint16 version;
     /* Make a copy so we're nondestructive*/
     SECItem data = supported_versions->data;
     SECItem versions;
@@ -4491,23 +4502,48 @@ tls13_NegotiateVersion(sslSocket *ss, co
         return SECFailure;
     }
     if (data.len || !versions.len || (versions.len & 1)) {
         FATAL_ERROR(ss, SSL_ERROR_RX_MALFORMED_CLIENT_HELLO, illegal_parameter);
         return SECFailure;
     }
     for (version = ss->vrange.max; version >= ss->vrange.min; --version) {
         PRUint16 wire = tls13_EncodeDraftVersion(version);
+        PRUint16 alt_wire = tls13_EncodeAltDraftVersion(version);
         unsigned long offset;
 
         for (offset = 0; offset < versions.len; offset += 2) {
             PRUint16 supported =
                 (versions.data[offset] << 8) | versions.data[offset + 1];
             if (supported == wire) {
                 ss->version = version;
                 return SECSuccess;
             }
+            if (ss->opt.enableAltHandshaketype && !IS_DTLS(ss) &&
+                supported == alt_wire) {
+                ss->version = version;
+                ss->ssl3.hs.altHandshakeType = PR_TRUE;
+                return SECSuccess;
+            }
         }
     }
 
     FATAL_ERROR(ss, SSL_ERROR_UNSUPPORTED_VERSION, protocol_version);
     return SECFailure;
 }
+
+SECStatus
+SSLExp_UseAltServerHelloType(PRFileDesc *fd, PRBool enable)
+{
+    sslSocket *ss;
+
+    ss = ssl_FindSocket(fd);
+    if (!ss) {
+        SSL_DBG(("%d: SSL[%d]: bad socket in SSLExp_UseAltServerHelloType",
+                 SSL_GETPID(), fd));
+        PORT_SetError(SEC_ERROR_INVALID_ARGS);
+        return SECFailure;
+    }
+
+    ss->opt.enableAltHandshaketype = enable;
+
+    return SECSuccess;
+}
--- a/security/nss/lib/ssl/tls13con.h
+++ b/security/nss/lib/ssl/tls13con.h
@@ -76,14 +76,15 @@ SECStatus tls13_ProtectRecord(sslSocket 
                               const PRUint8 *pIn,
                               PRUint32 contentLen,
                               sslBuffer *wrBuf);
 PRInt32 tls13_Read0RttData(sslSocket *ss, void *buf, PRInt32 len);
 SECStatus tls13_HandleEndOfEarlyData(sslSocket *ss);
 SECStatus tls13_HandleEarlyApplicationData(sslSocket *ss, sslBuffer *origBuf);
 PRBool tls13_ClientAllow0Rtt(const sslSocket *ss, const sslSessionID *sid);
 PRUint16 tls13_EncodeDraftVersion(SSL3ProtocolVersion version);
-PRUint16 tls13_DecodeDraftVersion(PRUint16 version);
+PRUint16 tls13_EncodeAltDraftVersion(SSL3ProtocolVersion version);
 SECStatus tls13_NegotiateVersion(sslSocket *ss,
                                  const TLSExtension *supported_versions);
 SECStatus tls13_SendNewSessionTicket(sslSocket *ss);
+SECStatus SSLExp_UseAltServerHelloType(PRFileDesc *fd, PRBool enable);
 
 #endif /* __tls13con_h_ */
--- a/security/nss/lib/ssl/tls13exthandle.c
+++ b/security/nss/lib/ssl/tls13exthandle.c
@@ -891,16 +891,20 @@ tls13_ClientSendSupportedVersionsXtn(con
     SSL_TRC(3, ("%d: TLS13[%d]: send supported_versions extension",
                 SSL_GETPID(), ss->fd));
 
     /* Extension type, extension len fiels, vector len field,
      * length of the values. */
     extensions_len = 2 + 2 + 1 +
                      2 * (ss->vrange.max - ss->vrange.min + 1);
 
+    if (ss->opt.enableAltHandshaketype && !IS_DTLS(ss)) {
+        extensions_len += 2;
+    }
+
     if (maxBytes < (PRUint32)extensions_len) {
         PORT_Assert(0);
         return 0;
     }
 
     if (append) {
         rv = ssl3_ExtAppendHandshakeNumber(ss, ssl_tls13_supported_versions_xtn, 2);
         if (rv != SECSuccess)
@@ -909,16 +913,25 @@ tls13_ClientSendSupportedVersionsXtn(con
         rv = ssl3_ExtAppendHandshakeNumber(ss, extensions_len - 4, 2);
         if (rv != SECSuccess)
             return -1;
 
         rv = ssl3_ExtAppendHandshakeNumber(ss, extensions_len - 5, 1);
         if (rv != SECSuccess)
             return -1;
 
+        if (ss->opt.enableAltHandshaketype && !IS_DTLS(ss)) {
+            rv = ssl3_ExtAppendHandshakeNumber(
+                ss, tls13_EncodeAltDraftVersion(
+                        SSL_LIBRARY_VERSION_TLS_1_3),
+                2);
+            if (rv != SECSuccess)
+                return -1;
+        }
+
         for (version = ss->vrange.max; version >= ss->vrange.min; --version) {
             rv = ssl3_ExtAppendHandshakeNumber(
                 ss, tls13_EncodeDraftVersion(version), 2);
             if (rv != SECSuccess)
                 return -1;
         }
 
         xtnData->advertised[xtnData->numAdvertised++] =
--- a/security/nss/lib/util/nssutil.h
+++ b/security/nss/lib/util/nssutil.h
@@ -14,22 +14,22 @@
 
 /*
  * NSS utilities's major version, minor version, patch level, build number,
  * and whether this is a beta release.
  *
  * The format of the version string should be
  *     "<major version>.<minor version>[.<patch level>[.<build number>]][ <Beta>]"
  */
-#define NSSUTIL_VERSION "3.32"
+#define NSSUTIL_VERSION "3.33 Beta"
 #define NSSUTIL_VMAJOR 3
-#define NSSUTIL_VMINOR 32
+#define NSSUTIL_VMINOR 33
 #define NSSUTIL_VPATCH 0
 #define NSSUTIL_VBUILD 0
-#define NSSUTIL_BETA PR_FALSE
+#define NSSUTIL_BETA PR_TRUE
 
 SEC_BEGIN_PROTOS
 
 /*
  * Returns a const string of the UTIL library version.
  */
 extern const char *NSSUTIL_GetVersion(void);
 
--- a/security/nss/lib/util/secoid.c
+++ b/security/nss/lib/util/secoid.c
@@ -1836,23 +1836,21 @@ secoid_HashDynamicOiddata(const SECOidDa
  * cheaper to rehash the table when it changes than it is to do the loop
  * each time.
  */
 static SECOidData *
 secoid_FindDynamic(const SECItem *key)
 {
     SECOidData *ret = NULL;
 
+    NSSRWLock_LockRead(dynOidLock);
     if (dynOidHash) {
-        NSSRWLock_LockRead(dynOidLock);
-        if (dynOidHash) { /* must check it again with lock held. */
-            ret = (SECOidData *)PL_HashTableLookup(dynOidHash, key);
-        }
-        NSSRWLock_UnlockRead(dynOidLock);
+        ret = (SECOidData *)PL_HashTableLookup(dynOidHash, key);
     }
+    NSSRWLock_UnlockRead(dynOidLock);
     if (ret == NULL) {
         PORT_SetError(SEC_ERROR_UNRECOGNIZED_OID);
     }
     return ret;
 }
 
 static dynXOid *
 secoid_FindDynamicByTag(SECOidTag tagnum)
@@ -1861,24 +1859,22 @@ secoid_FindDynamicByTag(SECOidTag tagnum
     int tagNumDiff;
 
     if (tagnum < SEC_OID_TOTAL) {
         PORT_SetError(SEC_ERROR_LIBRARY_FAILURE);
         return NULL;
     }
     tagNumDiff = tagnum - SEC_OID_TOTAL;
 
-    if (dynOidTable) {
-        NSSRWLock_LockRead(dynOidLock);
-        if (dynOidTable != NULL && /* must check it again with lock held. */
-            tagNumDiff < dynOidEntriesUsed) {
-            dxo = dynOidTable[tagNumDiff];
-        }
-        NSSRWLock_UnlockRead(dynOidLock);
+    NSSRWLock_LockRead(dynOidLock);
+    if (dynOidTable != NULL &&
+        tagNumDiff < dynOidEntriesUsed) {
+        dxo = dynOidTable[tagNumDiff];
     }
+    NSSRWLock_UnlockRead(dynOidLock);
     if (dxo == NULL) {
         PORT_SetError(SEC_ERROR_UNRECOGNIZED_OID);
     }
     return dxo;
 }
 
 /*
  * This routine is thread safe now.
--- a/security/nss/mach
+++ b/security/nss/mach
@@ -15,22 +15,26 @@ import os
 import platform
 from hashlib import sha256
 
 cwd = os.path.dirname(os.path.abspath(__file__))
 
 
 class cfAction(argparse.Action):
     docker_command = ["docker"]
+    restorecon = None
 
     def __call__(self, parser, args, values, option_string=None):
         if "noroot" not in values:
             self.setDockerCommand()
         else:
             values.remove("noroot")
+        files = [os.path.join('/home/worker/nss',
+                              os.path.relpath(os.path.abspath(x), start=cwd))
+                     for x in values]
 
         # First check if we can run docker.
         try:
             with open(os.devnull, "w") as f:
                 subprocess.check_call(
                     self.docker_command + ["images"], stdout=f)
         except:
             print("Please install docker and start the docker daemon.")
@@ -50,20 +54,22 @@ class cfAction(argparse.Action):
             ]
             with open(os.devnull, "w") as f:
                 subprocess.check_call(command, stdout=f)
         except:
             print("I have to build the docker image first.")
             self.buildImage(docker_image, cf_docker_folder)
 
         command = self.docker_command + [
-            'run', '-v', cwd + ':/home/worker/nss', '--rm', '-ti', docker_image
+                'run', '-v', cwd + ':/home/worker/nss:Z', '--rm', '-ti', docker_image
         ]
         # The clang format script returns 1 if something's to do. We don't care.
-        subprocess.call(command + values)
+        subprocess.call(command + files)
+        if self.restorecon is not None:
+            subprocess.call([self.restorecon, '-R', cwd])
 
     def filesChanged(self, path):
         hash = sha256()
         for dirname, dirnames, files in os.walk(path):
             for file in files:
                 with open(os.path.join(dirname, file), "rb") as f:
                     hash.update(f.read())
         chk_file = cwd + "/.chk"
@@ -82,16 +88,18 @@ class cfAction(argparse.Action):
         command = self.docker_command + [
             "build", "-t", docker_image, cf_docker_folder
         ]
         subprocess.check_call(command)
         return
 
     def setDockerCommand(self):
         if platform.system() == "Linux":
+            from distutils.spawn import find_executable
+            self.restorecon = find_executable('restorecon')
             self.docker_command = ["sudo"] + self.docker_command
 
 
 class buildAction(argparse.Action):
     def __call__(self, parser, args, values, option_string=None):
         cwd = os.path.dirname(os.path.abspath(__file__))
         subprocess.check_call([cwd + "/build.sh"] + values)
 
@@ -109,16 +117,23 @@ class testAction(argparse.Action):
         }
         command = cwd + "/tests/all.sh"
         subprocess.check_call(command, env=env)
 
     def __call__(self, parser, args, values, option_string=None):
         self.runTest(values)
 
 
+class commandsAction(argparse.Action):
+    commands = []
+    def __call__(self, parser, args, values, option_string=None):
+        for c in commandsAction.commands:
+            print(c)
+
+
 def parse_arguments():
     parser = argparse.ArgumentParser(
         description='NSS helper script. ' +
         'Make sure to separate sub-command arguments with --.')
     subparsers = parser.add_subparsers()
 
     parser_build = subparsers.add_parser(
         'build', help='All arguments are passed to build.sh')
@@ -138,16 +153,26 @@ def parse_arguments():
         'tests', help='Run tests through tests/all.sh.')
     tests = [
         "cipher", "lowhash", "chains", "cert", "dbtests", "tools", "fips",
         "sdr", "crmf", "smime", "ssl", "ocsp", "merge", "pkits", "ec",
         "gtests", "ssl_gtests"
     ]
     parser_test.add_argument(
         'test', choices=tests, help="Available tests", action=testAction)
+
+    parser_commands = subparsers.add_parser(
+        'mach-commands',
+        help="list commands")
+    parser_commands.add_argument(
+        'mach-commands',
+        nargs='*',
+        action=commandsAction)
+
+    commandsAction.commands = [c for c in subparsers.choices]
     return parser.parse_args()
 
 
 def main():
     parse_arguments()
 
 
 if __name__ == '__main__':
--- a/security/nss/nss.gyp
+++ b/security/nss/nss.gyp
@@ -163,16 +163,17 @@
             'cmd/tstclnt/tstclnt.gyp:tstclnt',
             'cmd/vfychain/vfychain.gyp:vfychain',
             'cmd/vfyserv/vfyserv.gyp:vfyserv',
             'gtests/certhigh_gtest/certhigh_gtest.gyp:certhigh_gtest',
             'gtests/der_gtest/der_gtest.gyp:der_gtest',
             'gtests/certdb_gtest/certdb_gtest.gyp:certdb_gtest',
             'gtests/freebl_gtest/freebl_gtest.gyp:prng_gtest',
             'gtests/pk11_gtest/pk11_gtest.gyp:pk11_gtest',
+            'gtests/softoken_gtest/softoken_gtest.gyp:softoken_gtest',
             'gtests/ssl_gtest/ssl_gtest.gyp:ssl_gtest',
             'gtests/util_gtest/util_gtest.gyp:util_gtest',
             'gtests/nss_bogo_shim/nss_bogo_shim.gyp:nss_bogo_shim',
           ],
           'conditions': [
             [ 'OS=="linux"', {
               'dependencies': [
                 'cmd/lowhashtest/lowhashtest.gyp:lowhashtest',
--- a/security/nss/tests/gtests/gtests.sh
+++ b/security/nss/tests/gtests/gtests.sh
@@ -78,13 +78,13 @@ gtest_start()
 gtest_cleanup()
 {
   html "</TABLE><BR>"
   cd ${QADIR}
   . common/cleanup.sh
 }
 
 ################## main #################################################
-GTESTS="prng_gtest certhigh_gtest certdb_gtest der_gtest pk11_gtest util_gtest freebl_gtest"
+GTESTS="prng_gtest certhigh_gtest certdb_gtest der_gtest pk11_gtest util_gtest freebl_gtest softoken_gtest"
 SOURCE_DIR="$PWD"/../..
 gtest_init $0
 gtest_start
 gtest_cleanup
--- a/taskcluster/ci/test/tests.yml
+++ b/taskcluster/ci/test/tests.yml
@@ -797,18 +797,18 @@ mochitest-devtools-chrome:
         extra-options:
             by-test-platform:
                 linux64-jsdcov/opt:
                     - --mochitest-suite=mochitest-devtools-chrome-coverage
                 default:
                     - --mochitest-suite=mochitest-devtools-chrome-chunked
     instance-size:
         by-test-platform:
-            # Bug 1281241: migrating to m3.large instances
-            linux64-asan/opt: legacy
+            # Bug 1361476 - try xlarge on asan to see if it avoids OOM
+            linux64-asan/opt: xlarge
             default: default
     # Bug 1296086: high number of intermittents observed with software GL and large instances
     allow-software-gl-layers: false
 
 mochitest-gpu:
     description: "Mochitest GPU run"
     suite: mochitest/gpu
     treeherder-symbol: tc-M(gpu)
--- a/testing/web-platform/tests/intersection-observer/timestamp.html
+++ b/testing/web-platform/tests/intersection-observer/timestamp.html
@@ -82,17 +82,18 @@ function step1() {
 function step2() {
   document.scrollingElement.scrollTop = 0;
   var topWindowTimeAfterNotification = performance.now();
   var iframeWindowTimeAfterNotification = targetIframe.contentWindow.performance.now();
 
   // Test results are only significant if there's a gap between
   // top window time and iframe window time.
   assert_greater_than(topWindowTimeBeforeNotification, iframeWindowTimeAfterNotification,
-    "Time ranges for top and iframe windows are disjoint.");
+    "Time ranges for top and iframe windows are disjoint. Times: " +
+      [topWindowTimeOnTestStart, topWindowTimeBeforeCreatingIframe, topWindowTimeBeforeNotification, topWindowTimeAfterNotification, iframeWindowTimeBeforeNotification, iframeWindowTimeAfterNotification]);
 
   assert_equals(topWindowEntries.length, 2, "Top window observer has two notifications.");
   assert_between_inclusive(
       topWindowEntries[1].time,
       topWindowTimeBeforeNotification,
       topWindowTimeAfterNotification,
       "Notification to top window observer is within the expected range.");
 
--- a/toolkit/components/extensions/Extension.jsm
+++ b/toolkit/components/extensions/Extension.jsm
@@ -42,16 +42,18 @@ Cu.import("resource://gre/modules/XPCOMU
 Cu.import("resource://gre/modules/Services.jsm");
 
 /* globals processCount */
 
 XPCOMUtils.defineLazyPreferenceGetter(this, "processCount", "dom.ipc.processCount.extension");
 
 XPCOMUtils.defineLazyModuleGetter(this, "AddonManager",
                                   "resource://gre/modules/AddonManager.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "AddonManagerPrivate",
+                                  "resource://gre/modules/AddonManager.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "AsyncShutdown",
                                   "resource://gre/modules/AsyncShutdown.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "ExtensionAPIs",
                                   "resource://gre/modules/ExtensionAPI.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "ExtensionCommon",
                                   "resource://gre/modules/ExtensionCommon.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "ExtensionPermissions",
                                   "resource://gre/modules/ExtensionPermissions.jsm");
@@ -745,16 +747,46 @@ this.ExtensionData = class {
     return results[0];
   }
 };
 
 const PROXIED_EVENTS = new Set(["test-harness-message", "add-permissions", "remove-permissions"]);
 
 const shutdownPromises = new Map();
 
+class BootstrapScope {
+  install(data, reason) {}
+  uninstall(data, reason) {}
+
+  startup(data, reason) {
+    this.extension = new Extension(data, this.BOOTSTRAP_REASON_TO_STRING_MAP[reason]);
+    return this.extension.startup();
+  }
+
+  shutdown(data, reason) {
+    this.extension.shutdown(this.BOOTSTRAP_REASON_TO_STRING_MAP[reason]);
+    this.extension = null;
+  }
+}
+
+XPCOMUtils.defineLazyGetter(BootstrapScope.prototype, "BOOTSTRAP_REASON_TO_STRING_MAP", () => {
+  const {BOOTSTRAP_REASONS} = AddonManagerPrivate;
+
+  return Object.freeze({
+    [BOOTSTRAP_REASONS.APP_STARTUP]: "APP_STARTUP",
+    [BOOTSTRAP_REASONS.APP_SHUTDOWN]: "APP_SHUTDOWN",
+    [BOOTSTRAP_REASONS.ADDON_ENABLE]: "ADDON_ENABLE",
+    [BOOTSTRAP_REASONS.ADDON_DISABLE]: "ADDON_DISABLE",
+    [BOOTSTRAP_REASONS.ADDON_INSTALL]: "ADDON_INSTALL",
+    [BOOTSTRAP_REASONS.ADDON_UNINSTALL]: "ADDON_UNINSTALL",
+    [BOOTSTRAP_REASONS.ADDON_UPGRADE]: "ADDON_UPGRADE",
+    [BOOTSTRAP_REASONS.ADDON_DOWNGRADE]: "ADDON_DOWNGRADE",
+  });
+});
+
 // We create one instance of this class per extension. |addonData|
 // comes directly from bootstrap.js when initializing.
 this.Extension = class extends ExtensionData {
   constructor(addonData, startupReason) {
     super(addonData.resourceURI);
 
     this.uuid = UUIDMap.get(addonData.id);
     this.instanceId = getUniqueId();
@@ -835,16 +867,20 @@ this.Extension = class extends Extension
             .filter(host => !origins.includes(host.pattern)));
 
       this.policy.permissions = Array.from(this.permissions);
       this.policy.allowedOrigins = this.whiteListedHosts;
     });
     /* eslint-enable mozilla/balanced-listeners */
   }
 
+  static getBootstrapScope(id, file) {
+    return new BootstrapScope();
+  }
+
   get groupFrameLoader() {
     let frameLoader = this._backgroundPageFrameLoader;
     for (let view of this.views) {
       if (view.viewType === "background" && view.xulBrowser) {
         return view.xulBrowser.frameLoader;
       }
       if (!frameLoader && view.xulBrowser) {
         frameLoader = view.xulBrowser.frameLoader;
--- a/toolkit/components/jsdownloads/src/DownloadHistory.jsm
+++ b/toolkit/components/jsdownloads/src/DownloadHistory.jsm
@@ -14,36 +14,68 @@
 "use strict";
 
 this.EXPORTED_SYMBOLS = [
   "DownloadHistory",
 ];
 
 const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
 
+Cu.import("resource://gre/modules/DownloadList.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 
+XPCOMUtils.defineLazyModuleGetter(this, "Downloads",
+                                  "resource://gre/modules/Downloads.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "OS",
+                                  "resource://gre/modules/osfile.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
                                   "resource://gre/modules/PlacesUtils.jsm");
 
+// Places query used to retrieve all history downloads for the related list.
+const HISTORY_PLACES_QUERY =
+      "place:transition=" + Ci.nsINavHistoryService.TRANSITION_DOWNLOAD +
+      "&sort=" + Ci.nsINavHistoryQueryOptions.SORT_BY_DATE_ASCENDING;
+
+const DESTINATIONFILEURI_ANNO = "downloads/destinationFileURI";
 const METADATA_ANNO = "downloads/metaData";
 
 const METADATA_STATE_FINISHED = 1;
 const METADATA_STATE_FAILED = 2;
 const METADATA_STATE_CANCELED = 3;
+const METADATA_STATE_PAUSED = 4;
 const METADATA_STATE_BLOCKED_PARENTAL = 6;
 const METADATA_STATE_DIRTY = 8;
 
 /**
  * Provides methods to retrieve downloads from previous sessions and store
  * downloads for future sessions.
  */
 this.DownloadHistory = {
   /**
+   * Retrieves the main DownloadHistoryList object which provides a view on
+   * downloads from previous browsing sessions, as well as downloads from this
+   * session that were not started from a private browsing window.
+   *
+   * @return {Promise}
+   * @resolves The requested DownloadHistoryList object.
+   * @rejects JavaScript exception.
+   */
+  getList() {
+    if (!this._promiseList) {
+      this._promiseList = Downloads.getList(Downloads.PUBLIC).then(list => {
+        return new DownloadHistoryList(list, HISTORY_PLACES_QUERY);
+      });
+    }
+
+    return this._promiseList;
+  },
+  _promiseList: null,
+
+  /**
    * Stores new detailed metadata for the given download in history. This is
    * normally called after a download finishes, fails, or is canceled.
    *
    * Failed or canceled downloads with partial data are not stored as paused,
    * because the information from the session download is required for resuming.
    *
    * @param download
    *        Download object whose metadata should be updated. If the object
@@ -83,9 +115,586 @@ this.DownloadHistory = {
                                  Services.io.newURI(download.source.url),
                                  METADATA_ANNO,
                                  JSON.stringify(metaData), 0,
                                  PlacesUtils.annotations.EXPIRE_WITH_HISTORY);
     } catch (ex) {
       Cu.reportError(ex);
     }
   },
+
+  /**
+   * Reads current metadata from Places annotations for the specified URI, and
+   * returns an object with the format:
+   *
+   * { targetFileSpec, state, endTime, fileSize, ... }
+   *
+   * The targetFileSpec property is the value of "downloads/destinationFileURI",
+   * while the other properties are taken from "downloads/metaData". Any of the
+   * properties may be missing from the object.
+   */
+  getPlacesMetaDataFor(spec) {
+    let metaData = {};
+
+    try {
+      let uri = Services.io.newURI(spec);
+      try {
+        metaData = JSON.parse(PlacesUtils.annotations.getPageAnnotation(
+                                          uri, METADATA_ANNO));
+      } catch (ex) {}
+      metaData.targetFileSpec = PlacesUtils.annotations.getPageAnnotation(
+                                            uri, DESTINATIONFILEURI_ANNO);
+    } catch (ex) {}
+
+    return metaData;
+  },
 };
+
+/**
+ * This cache exists in order to optimize the load of DownloadsHistoryList, when
+ * Places annotations for history downloads must be read. In fact, annotations
+ * are stored in a single table, and reading all of them at once is much more
+ * efficient than an individual query.
+ *
+ * When this property is first requested, it reads the annotations for all the
+ * history downloads and stores them indefinitely.
+ *
+ * The historical annotations are not expected to change for the duration of the
+ * session, except in the case where a session download is running for the same
+ * URI as a history download. To avoid using stale data, consumers should
+ * permanently remove from the cache any URI corresponding to a session
+ * download. This is a very small mumber compared to history downloads.
+ *
+ * This property returns a Map from each download source URI found in Places
+ * annotations to an object with the format:
+ *
+ * { targetFileSpec, state, endTime, fileSize, ... }
+ *
+ * The targetFileSpec property is the value of "downloads/destinationFileURI",
+ * while the other properties are taken from "downloads/metaData". Any of the
+ * properties may be missing from the object.
+ */
+XPCOMUtils.defineLazyGetter(this, "gCachedPlacesMetaData", function() {
+  let placesMetaData = new Map();
+
+  // Read the metadata annotations first, but ignore invalid JSON.
+  for (let result of PlacesUtils.annotations.getAnnotationsWithName(
+                                             METADATA_ANNO)) {
+    try {
+      placesMetaData.set(result.uri.spec, JSON.parse(result.annotationValue));
+    } catch (ex) {}
+  }
+
+  // Add the target file annotations to the metadata.
+  for (let result of PlacesUtils.annotations.getAnnotationsWithName(
+                                             DESTINATIONFILEURI_ANNO)) {
+    let metaData = placesMetaData.get(result.uri.spec);
+    if (!metaData) {
+      metaData = {};
+      placesMetaData.set(result.uri.spec, metaData);
+    }
+    metaData.targetFileSpec = result.annotationValue;
+  }
+
+  return placesMetaData;
+});
+
+/**
+ * Represents a download from the browser history. This object implements part
+ * of the interface of the Download object.
+ *
+ * While Download objects are shared between the public DownloadList and all the
+ * DownloadHistoryList instances, multiple HistoryDownload objects referring to
+ * the same item can be created for different DownloadHistoryList instances.
+ *
+ * @param placesNode
+ *        The Places node from which the history download should be initialized.
+ */
+function HistoryDownload(placesNode) {
+  this.placesNode = placesNode;
+
+  // History downloads should get the referrer from Places (bug 829201).
+  this.source = {
+    url: placesNode.uri,
+    isPrivate: false,
+  };
+  this.target = {
+    path: undefined,
+    exists: false,
+    size: undefined,
+  };
+
+  // In case this download cannot obtain its end time from the Places metadata,
+  // use the time from the Places node, that is the start time of the download.
+  this.endTime = placesNode.time / 1000;
+}
+
+HistoryDownload.prototype = {
+  /**
+   * DownloadSlot containing this history download.
+   */
+  slot: null,
+
+  /**
+   * Pushes information from Places metadata into this object.
+   */
+  updateFromMetaData(metaData) {
+    try {
+      this.target.path = Cc["@mozilla.org/network/protocol;1?name=file"]
+                           .getService(Ci.nsIFileProtocolHandler)
+                           .getFileFromURLSpec(metaData.targetFileSpec).path;
+    } catch (ex) {
+      this.target.path = undefined;
+    }
+
+    if ("state" in metaData) {
+      this.succeeded = metaData.state == METADATA_STATE_FINISHED;
+      this.canceled = metaData.state == METADATA_STATE_CANCELED ||
+                      metaData.state == METADATA_STATE_PAUSED;
+      this.endTime = metaData.endTime;
+
+      // Recreate partial error information from the state saved in history.
+      if (metaData.state == METADATA_STATE_FAILED) {
+        this.error = { message: "History download failed." };
+      } else if (metaData.state == METADATA_STATE_BLOCKED_PARENTAL) {
+        this.error = { becauseBlockedByParentalControls: true };
+      } else if (metaData.state == METADATA_STATE_DIRTY) {
+        this.error = {
+          becauseBlockedByReputationCheck: true,
+          reputationCheckVerdict: metaData.reputationCheckVerdict || "",
+        };
+      } else {
+        this.error = null;
+      }
+
+      // Normal history downloads are assumed to exist until the user interface
+      // is refreshed, at which point these values may be updated.
+      this.target.exists = true;
+      this.target.size = metaData.fileSize;
+    } else {
+      // Metadata might be missing from a download that has started but hasn't
+      // stopped already. Normally, this state is overridden with the one from
+      // the corresponding in-progress session download. But if the browser is
+      // terminated abruptly and additionally the file with information about
+      // in-progress downloads is lost, we may end up using this state. We use
+      // the failed state to allow the download to be restarted.
+      //
+      // On the other hand, if the download is missing the target file
+      // annotation as well, it is just a very old one, and we can assume it
+      // succeeded.
+      this.succeeded = !this.target.path;
+      this.error = this.target.path ? { message: "Unstarted download." } : null;
+      this.canceled = false;
+
+      // These properties may be updated if the user interface is refreshed.
+      this.target.exists = false;
+      this.target.size = undefined;
+    }
+  },
+
+  /**
+   * History downloads are never in progress.
+   */
+  stopped: true,
+
+  /**
+   * No percentage indication is shown for history downloads.
+   */
+  hasProgress: false,
+
+  /**
+   * History downloads cannot be restarted using their partial data, even if
+   * they are indicated as paused in their Places metadata. The only way is to
+   * use the information from a persisted session download, that will be shown
+   * instead of the history download. In case this session download is not
+   * available, we show the history download as canceled, not paused.
+   */
+  hasPartialData: false,
+
+  /**
+   * This method may be called when deleting a history download.
+   */
+  async finalize() {},
+
+  /**
+   * This method mimicks the "refresh" method of session downloads.
+   */
+  async refresh() {
+    try {
+      this.target.size = (await OS.File.stat(this.target.path)).size;
+      this.target.exists = true;
+    } catch (ex) {
+      // We keep the known file size from the metadata, if any.
+      this.target.exists = false;
+    }
+
+    this.slot.list._notifyAllViews("onDownloadChanged", this);
+  },
+};
+
+/**
+ * Represents one item in the list of public session and history downloads.
+ *
+ * The object may contain a session download, a history download, or both. When
+ * both a history and a session download are present, the session download gets
+ * priority and its information is accessed.
+ *
+ * @param list
+ *        The DownloadHistoryList that owns this DownloadSlot object.
+ */
+function DownloadSlot(list) {
+  this.list = list;
+}
+
+DownloadSlot.prototype = {
+  list: null,
+
+  /**
+   * Download object representing the session download contained in this slot.
+   */
+  sessionDownload: null,
+
+  /**
+   * HistoryDownload object contained in this slot.
+   */
+  get historyDownload() {
+    return this._historyDownload;
+  },
+  set historyDownload(historyDownload) {
+    this._historyDownload = historyDownload;
+    if (historyDownload) {
+      historyDownload.slot = this;
+    }
+  },
+  _historyDownload: null,
+
+  /**
+   * Returns the Download or HistoryDownload object for displaying information
+   * and executing commands in the user interface.
+   */
+  get download() {
+    return this.sessionDownload || this.historyDownload;
+  },
+};
+
+/**
+ * Represents an ordered collection of DownloadSlot objects containing a merged
+ * view on session downloads and history downloads. Views on this list will
+ * receive notifications for changes to both types of downloads.
+ *
+ * Downloads in this list are sorted from oldest to newest, with all session
+ * downloads after all the history downloads. When a new history download is
+ * added and the list also contains session downloads, the insertBefore option
+ * of the onDownloadAdded notification refers to the first session download.
+ *
+ * The list of downloads cannot be modified using the DownloadList methods.
+ *
+ * @param publicList
+ *        Underlying DownloadList containing public downloads.
+ * @param place
+ *        Places query used to retrieve history downloads.
+ */
+this.DownloadHistoryList = function(publicList, place) {
+  DownloadList.call(this);
+
+  // While "this._slots" contains all the data in order, the other properties
+  // provide fast access for the most common operations.
+  this._slots = [];
+  this._slotsForUrl = new Map();
+  this._slotForDownload = new WeakMap();
+
+  // Start the asynchronous queries to retrieve history and session downloads.
+  publicList.addView(this).catch(Cu.reportError);
+  let queries = {}, options = {};
+  PlacesUtils.history.queryStringToQueries(place, queries, {}, options);
+  if (!queries.value.length) {
+    queries.value = [PlacesUtils.history.getNewQuery()];
+  }
+
+  let result = PlacesUtils.history.executeQueries(queries.value,
+                                                  queries.value.length,
+                                                  options.value);
+  result.addObserver(this);
+}
+
+this.DownloadHistoryList.prototype = {
+  __proto__: DownloadList.prototype,
+
+  /**
+   * This is set when executing the Places query.
+   */
+  get result() {
+    return this._result;
+  },
+  set result(result) {
+    if (this._result == result) {
+      return;
+    }
+
+    if (this._result) {
+      PlacesUtils.annotations.removeObserver(this);
+      this._result.removeObserver(this);
+      this._result.root.containerOpen = false;
+    }
+
+    this._result = result;
+
+    if (this._result) {
+      this._result.root.containerOpen = true;
+      PlacesUtils.annotations.addObserver(this);
+    }
+  },
+  _result: null,
+
+  /**
+   * Index of the first slot that contains a session download. This is equal to
+   * the length of the list when there are no session downloads.
+   */
+  _firstSessionSlotIndex: 0,
+
+  _insertSlot({ slot, index, slotsForUrl }) {
+    // Add the slot to the ordered array.
+    this._slots.splice(index, 0, slot);
+    this._downloads.splice(index, 0, slot.download);
+    if (!slot.sessionDownload) {
+      this._firstSessionSlotIndex++;
+    }
+
+    // Add the slot to the fast access maps.
+    slotsForUrl.add(slot);
+    this._slotsForUrl.set(slot.download.source.url, slotsForUrl);
+
+    // Add the associated view items.
+    this._notifyAllViews("onDownloadAdded", slot.download, {
+      insertBefore: this._downloads[index + 1],
+    });
+  },
+
+  _removeSlot({ slot, slotsForUrl }) {
+    // Remove the slot from the ordered array.
+    let index = this._slots.indexOf(slot);
+    this._slots.splice(index, 1);
+    this._downloads.splice(index, 1);
+    if (this._firstSessionSlotIndex > index) {
+      this._firstSessionSlotIndex--;
+    }
+
+    // Remove the slot from the fast access maps.
+    slotsForUrl.delete(slot);
+    if (slotsForUrl.size == 0) {
+      this._slotsForUrl.delete(slot.download.source.url);
+    }
+
+    // Remove the associated view items.
+    this._notifyAllViews("onDownloadRemoved", slot.download);
+  },
+
+  /**
+   * Ensures that the information about a history download is stored in at least
+   * one slot, adding a new one at the end of the list if necessary.
+   *
+   * A reference to the same Places node will be stored in the HistoryDownload
+   * object for all the DownloadSlot objects associated with the source URL.
+   *
+   * @param placesNode
+   *        The Places node that represents the history download.
+   */
+  _insertPlacesNode(placesNode) {
+    let slotsForUrl = this._slotsForUrl.get(placesNode.uri) || new Set();
+
+    // If there are existing slots associated with this URL, we only have to
+    // ensure that the Places node reference is kept updated in case the more
+    // recent Places notification contained a different node object.
+    if (slotsForUrl.size > 0) {
+      for (let slot of slotsForUrl) {
+        if (!slot.historyDownload) {
+          slot.historyDownload = new HistoryDownload(placesNode);
+        } else {
+          slot.historyDownload.placesNode = placesNode;
+        }
+      }
+      return;
+    }
+
+    // If there are no existing slots for this URL, we have to create a new one.
+    // Since the history download is visible in the slot, we also have to update
+    // the object using the Places metadata.
+    let historyDownload = new HistoryDownload(placesNode);
+    historyDownload.updateFromMetaData(
+      gCachedPlacesMetaData.get(placesNode.uri) ||
+      DownloadHistory.getPlacesMetaDataFor(placesNode.uri));
+    let slot = new DownloadSlot(this);
+    slot.historyDownload = historyDownload;
+    this._insertSlot({ slot, slotsForUrl, index: this._firstSessionSlotIndex });
+  },
+
+  // nsINavHistoryResultObserver
+  containerStateChanged(node, oldState, newState) {
+    this.invalidateContainer(node);
+  },
+
+  // nsINavHistoryResultObserver
+  invalidateContainer(container) {
+    this._notifyAllViews("onDownloadBatchStarting");
+
+    // Remove all the current slots containing only history downloads.
+    for (let index = this._slots.length - 1; index >= 0; index--) {
+      let slot = this._slots[index];
+      if (slot.sessionDownload) {
+        // The visible data doesn't change, so we don't have to notify views.
+        slot.historyDownload = null;
+      } else {
+        let slotsForUrl = this._slotsForUrl.get(slot.download.source.url);
+        this._removeSlot({ slot, slotsForUrl });
+      }
+    }
+
+    // Add new slots or reuse existing ones for history downloads.
+    for (let index = 0; index < container.childCount; index++) {
+      try {
+        this._insertPlacesNode(container.getChild(index));
+      } catch (ex) {
+        Cu.reportError(ex);
+      }
+    }
+
+    this._notifyAllViews("onDownloadBatchEnded");
+  },
+
+  // nsINavHistoryResultObserver
+  nodeInserted(parent, placesNode) {
+    this._insertPlacesNode(placesNode);
+  },
+
+  // nsINavHistoryResultObserver
+  nodeRemoved(parent, placesNode, aOldIndex) {
+    let slotsForUrl = this._slotsForUrl.get(placesNode.uri);
+    for (let slot of slotsForUrl) {
+      if (slot.sessionDownload) {
+        // The visible data doesn't change, so we don't have to notify views.
+        slot.historyDownload = null;
+      } else {
+        this._removeSlot({ slot, slotsForUrl });
+      }
+    }
+  },
+
+  // nsINavHistoryResultObserver
+  nodeAnnotationChanged() {},
+  nodeIconChanged() {},
+  nodeTitleChanged() {},
+  nodeKeywordChanged() {},
+  nodeDateAddedChanged() {},
+  nodeLastModifiedChanged() {},
+  nodeHistoryDetailsChanged() {},
+  nodeTagsChanged() {},
+  sortingChanged() {},
+  nodeMoved() {},
+  nodeURIChanged() {},
+  batching() {},
+
+  // nsIAnnotationObserver
+  onPageAnnotationSet(page, name) {
+    // Annotations can only be added after a history node has been added, so we
+    // have to listen for changes to nodes we already added to the list.
+    if (name != DESTINATIONFILEURI_ANNO && name != METADATA_ANNO) {
+      return;
+    }
+
+    let slotsForUrl = this._slotsForUrl.get(page.spec);
+    if (!slotsForUrl) {
+      return;
+    }
+
+    for (let slot of slotsForUrl) {
+      if (slot.sessionDownload) {
+        // The visible data doesn't change, so we don't have to notify views.
+        return;
+      }
+      slot.historyDownload.updateFromMetaData(
+        DownloadHistory.getPlacesMetaDataFor(page.spec));
+      this._notifyAllViews("onDownloadChanged", slot.download);
+    }
+  },
+
+  // nsIAnnotationObserver
+  onItemAnnotationSet() {},
+  onPageAnnotationRemoved() {},
+  onItemAnnotationRemoved() {},
+
+  // DownloadList callback
+  onDownloadAdded(download) {
+    let url = download.source.url;
+    let slotsForUrl = this._slotsForUrl.get(url) || new Set();
+
+    // When a session download is attached to a slot, we ensure not to keep
+    // stale metadata around for the corresponding history download. This
+    // prevents stale state from being used if the view is rebuilt.
+    //
+    // Note that we will eagerly load the data in the cache at this point, even
+    // if we have seen no history download. The case where no history download
+    // will appear at all is rare enough in normal usage, so we can apply this
+    // simpler solution rather than keeping a list of cache items to ignore.
+    gCachedPlacesMetaData.delete(url);
+
+    // For every source URL, there can be at most one slot containing a history
+    // download without an associated session download. If we find one, then we
+    // can reuse it for the current session download, although we have to move
+    // it together with the other session downloads.
+    let slot = [...slotsForUrl][0];
+    if (slot && !slot.sessionDownload) {
+      // Remove the slot because we have to change its position.
+      this._removeSlot({ slot, slotsForUrl });
+    } else {
+      slot = new DownloadSlot(this);
+    }
+    slot.sessionDownload = download;
+    this._insertSlot({ slot, slotsForUrl, index: this._slots.length });
+    this._slotForDownload.set(download, slot);
+  },
+
+  // DownloadList callback
+  onDownloadChanged(download) {
+    let slot = this._slotForDownload.get(download);
+    this._notifyAllViews("onDownloadChanged", slot.download);
+  },
+
+  // DownloadList callback
+  onDownloadRemoved(download) {
+    let url = download.source.url;
+    let slotsForUrl = this._slotsForUrl.get(url);
+    let slot = this._slotForDownload.get(download);
+    this._removeSlot({ slot, slotsForUrl });
+
+    // If there was only one slot for this source URL and it also contained a
+    // history download, we should resurrect it in the correct area of the list.
+    if (slotsForUrl.size == 0 && slot.historyDownload) {
+      // We have one download slot containing both a session download and a
+      // history download, and we are now removing the session download.
+      // Previously, we did not use the Places metadata because it was obscured
+      // by the session download. Since this is no longer the case, we have to
+      // read the latest metadata before resurrecting the history download.
+      slot.historyDownload.updateFromMetaData(
+        DownloadHistory.getPlacesMetaDataFor(url));
+      slot.sessionDownload = null;
+      // Place the resurrected history slot after all the session slots.
+      this._insertSlot({ slot, slotsForUrl,
+                         index: this._firstSessionSlotIndex });
+    }
+
+    this._slotForDownload.delete(download);
+  },
+
+  // DownloadList
+  add() {
+    throw new Error("Not implemented.");
+  },
+
+  // DownloadList
+  remove() {
+    throw new Error("Not implemented.");
+  },
+
+  // DownloadList
+  removeFinished() {
+    throw new Error("Not implemented.");
+  },
+};
--- a/toolkit/components/jsdownloads/src/DownloadList.jsm
+++ b/toolkit/components/jsdownloads/src/DownloadList.jsm
@@ -173,28 +173,27 @@ this.DownloadList.prototype = {
    */
   removeView: function DL_removeView(aView) {
     this._views.delete(aView);
 
     return Promise.resolve();
   },
 
   /**
-   * Notifies all the views of a download addition, change, or removal.
+   * Notifies all the views of a download addition, change, removal, or other
+   * event. The additional arguments are passed to the called method.
    *
-   * @param aMethodName
+   * @param methodName
    *        String containing the name of the method to call on the view.
-   * @param aDownload
-   *        The Download object that changed.
    */
-  _notifyAllViews(aMethodName, aDownload) {
+  _notifyAllViews(methodName, ...args) {
     for (let view of this._views) {
       try {
-        if (aMethodName in view) {
-          view[aMethodName](aDownload);
+        if (methodName in view) {
+          view[methodName](...args);
         }
       } catch (ex) {
         Cu.reportError(ex);
       }
     }
   },
 
   /**
--- a/toolkit/components/jsdownloads/test/unit/common_test_Download.js
+++ b/toolkit/components/jsdownloads/test/unit/common_test_Download.js
@@ -2312,44 +2312,44 @@ add_task(async function test_toSerializa
 
 /**
  * Checks that downloads are added to browsing history when they start.
  */
 add_task(async function test_history() {
   mustInterruptResponses();
 
   // We will wait for the visit to be notified during the download.
-  await PlacesTestUtils.clearHistory();
+  await PlacesUtils.history.clear();
   let promiseVisit = promiseWaitForVisit(httpUrl("interruptible.txt"));
 
   // Start a download that is not allowed to finish yet.
   let download = await promiseStartDownload(httpUrl("interruptible.txt"));
 
   // The history notifications should be received before the download completes.
   let [time, transitionType] = await promiseVisit;
   do_check_eq(time, download.startTime.getTime() * 1000);
   do_check_eq(transitionType, Ci.nsINavHistoryService.TRANSITION_DOWNLOAD);
 
   // Restart and complete the download after clearing history.
-  await PlacesTestUtils.clearHistory();
+  await PlacesUtils.history.clear();
   download.cancel();
   continueResponses();
   await download.start();
 
   // The restart should not have added a new history visit.
   do_check_false(await promiseIsURIVisited(httpUrl("interruptible.txt")));
 });
 
 /**
  * Checks that downloads started by nsIHelperAppService are added to the
  * browsing history when they start.
  */
 add_task(async function test_history_tryToKeepPartialData() {
   // We will wait for the visit to be notified during the download.
-  await PlacesTestUtils.clearHistory();
+  await PlacesUtils.history.clear();
   let promiseVisit =
       promiseWaitForVisit(httpUrl("interruptible_resumable.txt"));
 
   // Start a download that is not allowed to finish yet.
   let beforeStartTimeMs = Date.now();
   let download = await promiseStartDownload_tryToKeepPartialData();
 
   // The history notifications should be received before the download completes.
--- a/toolkit/components/jsdownloads/test/unit/head.js
+++ b/toolkit/components/jsdownloads/test/unit/head.js
@@ -24,18 +24,16 @@ XPCOMUtils.defineLazyModuleGetter(this, 
 XPCOMUtils.defineLazyModuleGetter(this, "Downloads",
                                   "resource://gre/modules/Downloads.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "FileUtils",
                                   "resource://gre/modules/FileUtils.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "HttpServer",
                                   "resource://testing-common/httpd.js");
 XPCOMUtils.defineLazyModuleGetter(this, "NetUtil",
                                   "resource://gre/modules/NetUtil.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "PlacesTestUtils",
-                                  "resource://testing-common/PlacesTestUtils.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
                                   "resource://gre/modules/PlacesUtils.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "Promise",
                                   "resource://gre/modules/Promise.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "Services",
                                   "resource://gre/modules/Services.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "OS",
                                   "resource://gre/modules/osfile.jsm");
new file mode 100644
--- /dev/null
+++ b/toolkit/components/jsdownloads/test/unit/test_DownloadHistory.js
@@ -0,0 +1,226 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests the DownloadHistory module.
+ */
+
+"use strict";
+
+Cu.import("resource://gre/modules/DownloadHistory.jsm");
+
+XPCOMUtils.defineLazyServiceGetter(this, "gDownloadHistory",
+           "@mozilla.org/browser/download-history;1",
+           Ci.nsIDownloadHistory);
+
+let baseDate = new Date("2000-01-01");
+
+/**
+ * Waits for the download annotations to be set for the given page, required
+ * because the addDownload method will add these to the database asynchronously.
+ */
+function waitForAnnotations(sourceUriSpec) {
+  let sourceUri = Services.io.newURI(sourceUriSpec);
+  let destinationFileUriSet = false;
+  let metaDataSet = false;
+  return new Promise(resolve => {
+    PlacesUtils.annotations.addObserver({
+      onPageAnnotationSet(page, name) {
+        if (!page.equals(sourceUri)) {
+          return;
+        }
+        switch (name) {
+          case "downloads/destinationFileURI":
+            destinationFileUriSet = true;
+            break;
+          case "downloads/metaData":
+            metaDataSet = true;
+            break;
+        }
+        if (destinationFileUriSet && metaDataSet) {
+          PlacesUtils.annotations.removeObserver(this);
+          resolve();
+        }
+      },
+      onItemAnnotationSet() {},
+      onPageAnnotationRemoved() {},
+      onItemAnnotationRemoved() {},
+    });
+  });
+}
+
+/**
+ * Non-fatal assertion used to test whether the downloads in the list already
+ * match the expected state.
+ */
+function areEqual(a, b) {
+  if (a === b) {
+    Assert.equal(a, b);
+    return true;
+  }
+  do_print(a + " !== " + b);
+  return false;
+}
+
+/**
+ * Tests that various operations on session and history downloads are reflected
+ * by the DownloadHistoryList object, and that the order of results is correct.
+ */
+add_task(async function test_DownloadHistory() {
+  // Clean up at the beginning and at the end of the test.
+  async function cleanup() {
+    await PlacesUtils.history.clear();
+  }
+  do_register_cleanup(cleanup);
+  await cleanup();
+
+  let testDownloads = [
+    // History downloads should appear in order at the beginning of the list.
+    { offset: 10, canceled: true },
+    { offset: 20, succeeded: true },
+    { offset: 30, error: { becauseSourceFailed: true } },
+    { offset: 40, error: { becauseBlockedByParentalControls: true } },
+    { offset: 50, error: { becauseBlockedByReputationCheck: true } },
+    // Session downloads should show up after all the history download, in the
+    // same order as they were added.
+    { offset: 45, canceled: true, inSession: true },
+    { offset: 35, canceled: true, hasPartialData: true, inSession: true },
+    { offset: 55, succeeded: true, inSession: true },
+  ];
+  const NEXT_OFFSET = 60;
+
+  async function addTestDownload(properties) {
+    properties.source = { url: httpUrl("source" + properties.offset) };
+    let targetFile = getTempFile(TEST_TARGET_FILE_NAME + properties.offset);
+    properties.target = { path: targetFile.path };
+    properties.startTime = new Date(baseDate.getTime() + properties.offset);
+
+    let download = await Downloads.createDownload(properties);
+    if (properties.inSession) {
+      await publicList.add(download);
+    }
+
+    // Add the download to history using the XPCOM service, then use the
+    // DownloadHistory module to save the associated metadata.
+    let promiseAnnotations = waitForAnnotations(properties.source.url);
+    let promiseVisit = promiseWaitForVisit(properties.source.url);
+    gDownloadHistory.addDownload(Services.io.newURI(properties.source.url),
+                                 null,
+                                 properties.startTime.getTime() * 1000,
+                                 NetUtil.newURI(targetFile));
+    await promiseVisit;
+    DownloadHistory.updateMetaData(download);
+    await promiseAnnotations;
+  }
+
+  // Add all the test downloads to history.
+  let publicList = await promiseNewList();
+  for (let properties of testDownloads) {
+    await addTestDownload(properties);
+  }
+
+  // This allows waiting for an expected list at various points during the test.
+  let view = {
+    downloads: [],
+    onDownloadAdded(download, options = {}) {
+      if (options.insertBefore) {
+        let index = this.downloads.indexOf(options.insertBefore);
+        this.downloads.splice(index, 0, download);
+      } else {
+        this.downloads.push(download);
+      }
+      this.checkForExpectedDownloads();
+    },
+    onDownloadChanged(download) {
+      this.checkForExpectedDownloads();
+    },
+    onDownloadRemoved(download) {
+      let index = this.downloads.indexOf(download);
+      this.downloads.splice(index, 1);
+      this.checkForExpectedDownloads();
+    },
+    checkForExpectedDownloads() {
+      // Wait for all the expected downloads to be added or removed before doing
+      // the detailed tests. This is done to avoid creating irrelevant output.
+      if (this.downloads.length != testDownloads.length) {
+        return;
+      }
+      for (let i = 0; i < this.downloads.length; i++) {
+        if (this.downloads[i].source.url != testDownloads[i].source.url ||
+            this.downloads[i].target.path != testDownloads[i].target.path) {
+          return;
+        }
+      }
+      // Check and report the actual state of the downloads. Even if the items
+      // are in the expected order, the metadata for history downloads might not
+      // have been updated to the final state yet.
+      for (let i = 0; i < view.downloads.length; i++) {
+        let download = view.downloads[i];
+        let testDownload = testDownloads[i];
+        do_print("Checking download source " + download.source.url +
+                 " with target " + download.target.path);
+        if (!areEqual(download.succeeded, !!testDownload.succeeded) ||
+            !areEqual(download.canceled, !!testDownload.canceled) ||
+            !areEqual(download.hasPartialData, !!testDownload.hasPartialData) ||
+            !areEqual(!!download.error, !!testDownload.error)) {
+          return;
+        }
+        // If the above properties match, the error details should be correct.
+        if (download.error) {
+          if (testDownload.error.becauseSourceFailed) {
+            Assert.equal(download.error.message, "History download failed.");
+          }
+          Assert.equal(download.error.becauseBlockedByParentalControls,
+                       testDownload.error.becauseBlockedByParentalControls);
+          Assert.equal(download.error.becauseBlockedByReputationCheck,
+                       testDownload.error.becauseBlockedByReputationCheck);
+        }
+      }
+      this.resolveWhenExpected();
+    },
+    resolveWhenExpected: () => {},
+    async waitForExpected() {
+      let promise = new Promise(resolve => this.resolveWhenExpected = resolve);
+      this.checkForExpectedDownloads();
+      await promise;
+    },
+  };
+
+  // Initialize DownloadHistoryList only after having added the history and
+  // session downloads, and check that they are loaded in the correct order.
+  let list = await DownloadHistory.getList();
+  await list.addView(view);
+  await view.waitForExpected();
+
+  // Remove a download from history and verify that the change is reflected.
+  let downloadToRemove = testDownloads[1];
+  testDownloads.splice(1, 1);
+  await PlacesUtils.history.remove(downloadToRemove.source.url);
+  await view.waitForExpected();
+
+  // Add a download to history and verify it's placed before session downloads,
+  // even if the start date is more recent.
+  let downloadToAdd = { offset: NEXT_OFFSET, canceled: true };
+  testDownloads.splice(testDownloads.findIndex(d => d.inSession), 0,
+                       downloadToAdd);
+  await addTestDownload(downloadToAdd);
+  await view.waitForExpected();
+
+  // Add a session download and verify it's placed after all session downloads,
+  // even if the start date is less recent.
+  let sessionDownloadToAdd = { offset: 0, inSession: true, succeeded: true };
+  testDownloads.push(sessionDownloadToAdd);
+  await addTestDownload(sessionDownloadToAdd);
+  await view.waitForExpected();
+
+  // Add a session download for the same URI without a history entry, and verify
+  // it's visible and placed after all session downloads.
+  testDownloads.push(sessionDownloadToAdd);
+  await publicList.add(await Downloads.createDownload(sessionDownloadToAdd));
+  await view.waitForExpected();
+
+  // Clear history and check that session downloads with partial data remain.
+  testDownloads = testDownloads.filter(d => d.hasPartialData);
+  await PlacesUtils.history.clear();
+  await view.waitForExpected();
+});
--- a/toolkit/components/jsdownloads/test/unit/test_DownloadList.js
+++ b/toolkit/components/jsdownloads/test/unit/test_DownloadList.js
@@ -336,17 +336,17 @@ add_task(async function test_history_exp
 
   // Work with one finished download and one canceled download.
   await downloadOne.start();
   downloadTwo.start().catch(() => {});
   await downloadTwo.cancel();
 
   // We must replace the visits added while executing the downloads with visits
   // that are older than 7 days, otherwise they will not be expired.
-  await PlacesTestUtils.clearHistory();
+  await PlacesUtils.history.clear();
   await promiseExpirableDownloadVisit();
   await promiseExpirableDownloadVisit(httpUrl("interruptible.txt"));
 
   // After clearing history, we can add the downloads to be removed to the list.
   await list.add(downloadOne);
   await list.add(downloadTwo);
 
   // Force a history expiration.
@@ -378,17 +378,17 @@ add_task(async function test_history_cle
       }
     },
   };
   await list.addView(downloadView);
 
   await downloadOne.start();
   await downloadTwo.start();
 
-  await PlacesTestUtils.clearHistory();
+  await PlacesUtils.history.clear();
 
   // Wait for the removal notifications that may still be pending.
   await deferred.promise;
 });
 
 /**
  * Tests the removeFinished method to ensure that it only removes
  * finished downloads.
--- a/toolkit/components/jsdownloads/test/unit/xpcshell.ini
+++ b/toolkit/components/jsdownloads/test/unit/xpcshell.ini
@@ -3,16 +3,17 @@ head = head.js
 skip-if = toolkit == 'android'
 
 # Note: The "tail.js" file is not defined in the "tail" key because it calls
 #       the "add_test_task" function, that does not work properly in tail files.
 support-files =
   common_test_Download.js
 
 [test_DownloadCore.js]
+[test_DownloadHistory.js]
 [test_DownloadIntegration.js]
 [test_DownloadLegacy.js]
 [test_DownloadList.js]
 [test_Downloads.js]
 [test_DownloadStore.js]
 [test_PrivateTemp.js]
 # coverage flag is for bug 1336730
 skip-if = (os != 'linux' || coverage)
--- a/toolkit/components/passwordmgr/test/unit/test_logins_decrypt_failure.js
+++ b/toolkit/components/passwordmgr/test/unit/test_logins_decrypt_failure.js
@@ -15,17 +15,17 @@
  * Resets the token used to decrypt logins.  This is equivalent to resetting the
  * master password when it is not known.
  */
 function resetMasterPassword()
 {
   let token = Cc["@mozilla.org/security/pk11tokendb;1"]
                 .getService(Ci.nsIPK11TokenDB).getInternalKeyToken();
   token.reset();
-  token.changePassword("", "");
+  token.initPassword("");
 }
 
 // Tests
 
 /**
  * Resets the master password after some logins were added to the database.
  */
 add_task(function test_logins_decrypt_failure()
--- a/toolkit/components/telemetry/EventInfo.h
+++ b/toolkit/components/telemetry/EventInfo.h
@@ -30,28 +30,28 @@ struct CommonEventInfo {
 
   // The dataset this event is recorded in.
   uint32_t dataset;
 
   // Which processes to record this event in.
   mozilla::Telemetry::Common::RecordedProcessType record_in_processes;
 
   // Convenience functions for accessing event strings.
-  const char* expiration_version() const;
-  const char* category() const;
-  const char* extra_key(uint32_t index) const;
+  const nsCString expiration_version() const;
+  const nsCString category() const;
+  const nsCString extra_key(uint32_t index) const;
 };
 
 struct EventInfo {
   // The corresponding CommonEventInfo.
   const CommonEventInfo& common_info;
 
   // Indices for the method & object strings.
   uint32_t method_offset;
   uint32_t object_offset;
 
-  const char* method() const;
-  const char* object() const;
+  const nsCString method() const;
+  const nsCString object() const;
 };
 
 } // namespace
 
 #endif // TelemetryEventInfo_h__
--- a/toolkit/components/telemetry/Processes.yaml
+++ b/toolkit/components/telemetry/Processes.yaml
@@ -14,8 +14,13 @@ content:
 extension:
   gecko_enum: GeckoProcessType_Content
   description: >
     This is the WebExtension process. It is a re-used content process, with the data submitted
     separately to avoid skewing other content process Telemetry.
 gpu:
   gecko_enum: GeckoProcessType_GPU
   description: This is the compositor or GPU process.
+dynamic:
+  gecko_enum: GeckoProcessType_Default
+  description: >
+    This is not a real process, it is used to logically group add-on probes.
+    It contains data of any probes registered at runtime by add-ons.
--- a/toolkit/components/telemetry/Telemetry.cpp
+++ b/toolkit/components/telemetry/Telemetry.cpp
@@ -1802,23 +1802,31 @@ NS_IMETHODIMP
 TelemetryImpl::RecordEvent(const nsACString & aCategory, const nsACString & aMethod,
                            const nsACString & aObject, JS::HandleValue aValue,
                            JS::HandleValue aExtra, JSContext* aCx, uint8_t optional_argc)
 {
   return TelemetryEvent::RecordEvent(aCategory, aMethod, aObject, aValue, aExtra, aCx, optional_argc);
 }
 
 NS_IMETHODIMP
-TelemetryImpl::SnapshotBuiltinEvents(uint32_t aDataset, bool aClear, JSContext* aCx,
+TelemetryImpl::SnapshotEvents(uint32_t aDataset, bool aClear, JSContext* aCx,
                                      uint8_t optional_argc, JS::MutableHandleValue aResult)
 {
   return TelemetryEvent::CreateSnapshots(aDataset, aClear, aCx, optional_argc, aResult);
 }
 
 NS_IMETHODIMP
+TelemetryImpl::RegisterEvents(const nsACString& aCategory,
+                              JS::Handle<JS::Value> aEventData,
+                              JSContext* cx)
+{
+  return TelemetryEvent::RegisterEvents(aCategory, aEventData, cx);
+}
+
+NS_IMETHODIMP
 TelemetryImpl::ClearEvents()
 {
   TelemetryEvent::ClearEvents();
   return NS_OK;
 }
 
 NS_IMETHODIMP
 TelemetryImpl::SetEventRecordingEnabled(const nsACString& aCategory, bool aEnabled)
--- a/toolkit/components/telemetry/TelemetryEvent.cpp
+++ b/toolkit/components/telemetry/TelemetryEvent.cpp
@@ -1,29 +1,31 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include <prtime.h>
+#include <limits>
 #include "nsITelemetry.h"
 #include "nsHashKeys.h"
 #include "nsDataHashtable.h"
 #include "nsClassHashtable.h"
 #include "nsTArray.h"
 #include "mozilla/StaticMutex.h"
 #include "mozilla/Unused.h"
 #include "mozilla/Maybe.h"
 #include "mozilla/StaticPtr.h"
 #include "mozilla/Pair.h"
 #include "jsapi.h"
 #include "nsJSUtils.h"
 #include "nsXULAppAPI.h"
 #include "nsUTF8Utils.h"
+#include "nsPrintfCString.h"
 
 #include "TelemetryCommon.h"
 #include "TelemetryEvent.h"
 #include "TelemetryEventData.h"
 #include "ipc/TelemetryIPCAccumulator.h"
 
 using mozilla::StaticMutex;
 using mozilla::StaticMutexAutoLock;
@@ -84,108 +86,159 @@ namespace TelemetryIPCAccumulator = mozi
 ////////////////////////////////////////////////////////////////////////
 //
 // PRIVATE TYPES
 
 namespace {
 
 const uint32_t kEventCount = mozilla::Telemetry::EventID::EventCount;
 // This is a special event id used to mark expired events, to make expiry checks
-// faster at runtime.
-const uint32_t kExpiredEventId = kEventCount + 1;
-static_assert(kEventCount < kExpiredEventId, "Should not overflow.");
+// cheap at runtime.
+const uint32_t kExpiredEventId = std::numeric_limits<uint32_t>::max();
+static_assert(kExpiredEventId > kEventCount,
+              "Built-in event count should be less than the expired event id.");
 
 // This is the hard upper limit on the number of event records we keep in storage.
 // If we cross this limit, we will drop any further event recording until elements
 // are removed from storage.
 const uint32_t kMaxEventRecords = 1000;
 // Maximum length of any passed value string, in UTF8 byte sequence length.
 const uint32_t kMaxValueByteLength = 80;
 // Maximum length of any string value in the extra dictionary, in UTF8 byte sequence length.
 const uint32_t kMaxExtraValueByteLength = 80;
+// Maximum length of dynamic method names, in UTF8 byte sequence length.
+const uint32_t kMaxMethodNameByteLength = 20;
+// Maximum length of dynamic object names, in UTF8 byte sequence length.
+const uint32_t kMaxObjectNameByteLength = 20;
+// Maximum length of extra key names, in UTF8 byte sequence length.
+const uint32_t kMaxExtraKeyNameByteLength = 15;
+// The maximum number of valid extra keys for an event.
+const uint32_t kMaxExtraKeyCount = 10;
 
 typedef nsDataHashtable<nsCStringHashKey, uint32_t> StringUintMap;
 typedef nsClassHashtable<nsCStringHashKey, nsCString> StringMap;
 
+struct EventKey {
+  uint32_t id;
+  bool dynamic;
+};
+
+struct DynamicEventInfo {
+  DynamicEventInfo(const nsACString& category, const nsACString& method,
+                   const nsACString& object, const nsTArray<nsCString>& extra_keys,
+                   bool recordOnRelease)
+    : category(category)
+    , method(method)
+    , object(object)
+    , extra_keys(extra_keys)
+    , recordOnRelease(recordOnRelease)
+  {}
+
+  DynamicEventInfo(const DynamicEventInfo&) = default;
+  DynamicEventInfo& operator=(const DynamicEventInfo&) = delete;
+
+  const nsCString category;
+  const nsCString method;
+  const nsCString object;
+  const nsTArray<nsCString> extra_keys;
+  const bool recordOnRelease;
+
+  size_t
+  SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const
+  {
+    size_t n = 0;
+
+    n += category.SizeOfExcludingThisIfUnshared(aMallocSizeOf);
+    n += method.SizeOfExcludingThisIfUnshared(aMallocSizeOf);
+    n += object.SizeOfExcludingThisIfUnshared(aMallocSizeOf);
+    n += extra_keys.ShallowSizeOfExcludingThis(aMallocSizeOf);
+    for (auto& key : extra_keys) {
+      n += key.SizeOfExcludingThisIfUnshared(aMallocSizeOf);
+    }
+
+    return n;
+  }
+};
+
 enum class RecordEventResult {
   Ok,
   UnknownEvent,
   InvalidExtraKey,
   StorageLimitReached,
   ExpiredEvent,
   WrongProcess,
 };
 
+enum class RegisterEventResult {
+  Ok,
+  AlreadyRegistered,
+};
+
 typedef nsTArray<EventExtraEntry> ExtraArray;
 
 class EventRecord {
 public:
-  EventRecord(double timestamp, uint32_t eventId, const Maybe<nsCString>& value,
+  EventRecord(double timestamp, const EventKey& key, const Maybe<nsCString>& value,
               const ExtraArray& extra)
     : mTimestamp(timestamp)
-    , mEventId(eventId)
+    , mEventKey(key)
     , mValue(value)
     , mExtra(extra)
   {}
 
-  EventRecord(const EventRecord& other)
-    : mTimestamp(other.mTimestamp)
-    , mEventId(other.mEventId)
-    , mValue(other.mValue)
-    , mExtra(other.mExtra)
-  {}
+  EventRecord(const EventRecord& other) = default;
 
   EventRecord& operator=(const EventRecord& other) = delete;
 
   double Timestamp() const { return mTimestamp; }
-  uint32_t EventId() const { return mEventId; }
+  const EventKey& GetEventKey() const { return mEventKey; }
   const Maybe<nsCString>& Value() const { return mValue; }
   const ExtraArray& Extra() const { return mExtra; }
 
   size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const;
 
 private:
   const double mTimestamp;
-  const uint32_t mEventId;
+  const EventKey mEventKey;
   const Maybe<nsCString> mValue;
   const ExtraArray mExtra;
 };
 
 // Implements the methods for EventInfo.
-const char*
+const nsCString
 EventInfo::method() const
 {
-  return &gEventsStringTable[this->method_offset];
+  return nsCString(&gEventsStringTable[this->method_offset]);
 }
 
-const char*
+const nsCString
 EventInfo::object() const
 {
-  return &gEventsStringTable[this->object_offset];
+  return nsCString(&gEventsStringTable[this->object_offset]);
 }
 
 // Implements the methods for CommonEventInfo.
-const char*
+const nsCString
 CommonEventInfo::category() const
 {
-  return &gEventsStringTable[this->category_offset];
+  return nsCString(&gEventsStringTable[this->category_offset]);
 }
 
-const char*
+const nsCString
 CommonEventInfo::expiration_version() const
 {
-  return &gEventsStringTable[this->expiration_version_offset];
+  return nsCString(&gEventsStringTable[this->expiration_version_offset]);
 }
 
-const char*
+const nsCString
 CommonEventInfo::extra_key(uint32_t index) const
 {
   MOZ_ASSERT(index < this->extra_count);
   uint32_t key_index = gExtraKeysTable[this->extra_index + index];
-  return &gEventsStringTable[key_index];
+  return nsCString(&gEventsStringTable[key_index]);
 }
 
 // Implementation for the EventRecord class.
 size_t
 EventRecord::SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const
 {
   size_t n = 0;
 
@@ -212,19 +265,27 @@ UniqueEventName(const nsACString& catego
   name.AppendLiteral("#");
   name.Append(object);
   return name;
 }
 
 nsCString
 UniqueEventName(const EventInfo& info)
 {
-  return UniqueEventName(nsDependentCString(info.common_info.category()),
-                         nsDependentCString(info.method()),
-                         nsDependentCString(info.object()));
+  return UniqueEventName(info.common_info.category(),
+                         info.method(),
+                         info.object());
+}
+
+nsCString
+UniqueEventName(const DynamicEventInfo& info)
+{
+  return UniqueEventName(info.category,
+                         info.method,
+                         info.object);
 }
 
 bool
 IsExpiredDate(uint32_t expires_days_since_epoch) {
   if (expires_days_since_epoch == 0) {
     return false;
   }
 
@@ -250,193 +311,302 @@ TruncateToByteLength(nsCString& str, uin
 namespace {
 
 // Set to true once this global state has been initialized.
 bool gInitDone = false;
 
 bool gCanRecordBase;
 bool gCanRecordExtended;
 
-// The EventName -> EventID cache map.
-StringUintMap gEventNameIDMap(kEventCount);
+// The EventName -> EventKey cache map.
+nsClassHashtable<nsCStringHashKey, EventKey> gEventNameIDMap(kEventCount);
 
 // The CategoryName -> CategoryID cache map.
 StringUintMap gCategoryNameIDMap;
 
 // This tracks the IDs of the categories for which recording is enabled.
-nsTHashtable<nsUint32HashKey> gEnabledCategories;
+nsTHashtable<nsCStringHashKey> gEnabledCategories;
 
 // The main event storage. Events are inserted here, keyed by process id and
 // in recording order.
 typedef nsTArray<EventRecord> EventRecordArray;
 nsClassHashtable<nsUint32HashKey, EventRecordArray> gEventRecords;
 
+// The details on dynamic events that are recorded from addons are registered here.
+StaticAutoPtr<nsTArray<DynamicEventInfo>> gDynamicEventInfo;
+
 } // namespace
 
 ////////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////////
 //
 // PRIVATE: thread-safe helpers for event recording.
 
 namespace {
 
+unsigned int
+GetDataset(const StaticMutexAutoLock& lock, const EventKey& eventKey)
+{
+  if (!eventKey.dynamic) {
+    return gEventInfo[eventKey.id].common_info.dataset;
+  }
+
+  if (!gDynamicEventInfo) {
+    return nsITelemetry::DATASET_RELEASE_CHANNEL_OPTIN;
+  }
+
+  return (*gDynamicEventInfo)[eventKey.id].recordOnRelease ?
+           nsITelemetry::DATASET_RELEASE_CHANNEL_OPTOUT :
+           nsITelemetry::DATASET_RELEASE_CHANNEL_OPTIN;
+}
+
+nsCString
+GetCategory(const StaticMutexAutoLock& lock, const EventKey& eventKey)
+{
+  if (!eventKey.dynamic) {
+    return gEventInfo[eventKey.id].common_info.category();
+  }
+
+  if (!gDynamicEventInfo) {
+    return NS_LITERAL_CSTRING("");
+  }
+
+  return (*gDynamicEventInfo)[eventKey.id].category;
+}
+
 bool
-CanRecordEvent(const StaticMutexAutoLock& lock, const CommonEventInfo& info,
+CanRecordEvent(const StaticMutexAutoLock& lock, const EventK