Merge mozilla-central to autoland
authorarthur.iakab <aiakab@mozilla.com>
Sat, 29 Sep 2018 07:17:34 +0300
changeset 494607 dddc696490f879565d67445acefcb638138e3d4a
parent 494606 64fa36b13e1a9d9b9c7da4b4d991b4c7570ce215 (current diff)
parent 494579 13b0256be4492426f51005b65d3526f7ca89683e (diff)
child 494608 4d786f9a4ba01d567e6c6072efdf85fbf2bc59e0
push id9984
push userffxbld-merge
push dateMon, 15 Oct 2018 21:07:35 +0000
treeherdermozilla-beta@183d27ea8570 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone64.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge mozilla-central to autoland
dom/base/moz.build
dom/media/ReaderProxy.cpp
dom/media/ReaderProxy.h
modules/libpref/init/StaticPrefList.h
security/nss/automation/clang-format/setup.sh
security/nss/automation/taskcluster/docker-clang-3.9/Dockerfile
security/nss/automation/taskcluster/docker-clang-3.9/bin/checkout.sh
security/nss/automation/taskcluster/docker-clang-3.9/setup.sh
security/nss/automation/taskcluster/docker-decision/setup.sh
security/nss/automation/taskcluster/docker-fuzz/setup.sh
security/nss/automation/taskcluster/docker-gcc-4.4/setup.sh
security/nss/automation/taskcluster/docker/setup.sh
--- a/accessible/base/DocManager.cpp
+++ b/accessible/base/DocManager.cpp
@@ -339,17 +339,19 @@ DocManager::OnStatusChange(nsIWebProgres
 {
   MOZ_ASSERT_UNREACHABLE("notification excluded in AddProgressListener(...)");
   return NS_OK;
 }
 
 NS_IMETHODIMP
 DocManager::OnSecurityChange(nsIWebProgress* aWebProgress,
                              nsIRequest* aRequest,
-                             uint32_t aState)
+                             uint32_t aOldState,
+                             uint32_t aState,
+                             const nsAString& aContentBlockingLogJSON)
 {
   MOZ_ASSERT_UNREACHABLE("notification excluded in AddProgressListener(...)");
   return NS_OK;
 }
 
 ////////////////////////////////////////////////////////////////////////////////
 // nsIDOMEventListener
 
--- a/browser/base/content/browser-contentblocking.js
+++ b/browser/base/content/browser-contentblocking.js
@@ -459,17 +459,18 @@ var ContentBlocking = {
 
   shieldHistogramAdd(value) {
     if (PrivateBrowsingUtils.isWindowPrivate(window)) {
       return;
     }
     Services.telemetry.getHistogramById("TRACKING_PROTECTION_SHIELD").add(value);
   },
 
-  onSecurityChange(state, webProgress, isSimulated) {
+  onSecurityChange(oldState, state, webProgress, isSimulated,
+                   contentBlockingLogJSON) {
     let baseURI = this._baseURIForChannelClassifier;
 
     // Don't deal with about:, file: etc.
     if (!baseURI) {
       this.iconBox.removeAttribute("animate");
       this.iconBox.removeAttribute("active");
       this.iconBox.removeAttribute("hasException");
       return;
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -4984,17 +4984,18 @@ var XULBrowserWindow = {
   _lastLocation: null,
 
   // This is called in multiple ways:
   //  1. Due to the nsIWebProgressListener.onSecurityChange notification.
   //  2. Called by tabbrowser.xml when updating the current browser.
   //  3. Called directly during this object's initializations.
   // aRequest will be null always in case 2 and 3, and sometimes in case 1 (for
   // instance, there won't be a request when STATE_BLOCKED_TRACKING_CONTENT is observed).
-  onSecurityChange(aWebProgress, aRequest, aState, aIsSimulated) {
+  onSecurityChange(aWebProgress, aRequest, aOldState, aState,
+                   aContentBlockingLogJSON, aIsSimulated) {
     // Don't need to do anything if the data we use to update the UI hasn't
     // changed
     let uri = gBrowser.currentURI;
     let spec = uri.spec;
     if (this._state == aState &&
         this._lastLocation == spec) {
       // Switching to a tab of the same URL doesn't change most security
       // information, but tab specific permissions may be different.
@@ -5011,17 +5012,18 @@ var XULBrowserWindow = {
     // Make sure the "https" part of the URL is striked out or not,
     // depending on the current mixed active content blocking state.
     gURLBar.formatValue();
 
     try {
       uri = Services.uriFixup.createExposableURI(uri);
     } catch (e) {}
     gIdentityHandler.updateIdentity(this._state, uri);
-    ContentBlocking.onSecurityChange(this._state, aWebProgress, aIsSimulated);
+    ContentBlocking.onSecurityChange(aOldState, this._state, aWebProgress, aIsSimulated,
+                                     aContentBlockingLogJSON);
   },
 
   // simulate all change notifications after switching tabs
   onUpdateCurrentBrowser: function XWB_onUpdateCurrentBrowser(aStateFlags, aStatus, aMessage, aTotalProgress) {
     if (FullZoom.updateBackgroundTabs)
       FullZoom.onLocationChange(gBrowser.currentURI, true);
 
     CombinedStopReload.onTabSwitch();
--- a/browser/base/content/tabbrowser.js
+++ b/browser/base/content/tabbrowser.js
@@ -940,18 +940,23 @@ window._gBrowser = {
     this._callProgressListeners(null, "onLocationChange",
                                 [webProgress, null, newBrowser.currentURI, 0, true],
                                 true, false);
 
     let securityUI = newBrowser.securityUI;
     if (securityUI) {
       // Include the true final argument to indicate that this event is
       // simulated (instead of being observed by the webProgressListener).
+      // Note: check state first to make sure the security UI object updates its
+      // state from the docshell correctly.
+      let state = securityUI.state;
+      let oldState = securityUI.oldState;
       this._callProgressListeners(null, "onSecurityChange",
-                                  [webProgress, null, securityUI.state, true],
+                                  [webProgress, null, oldState, state,
+                                   securityUI.contentBlockingLogJSON, true],
                                   true, false);
     }
 
     let listener = this._tabListeners.get(newTab);
     if (listener && listener.mStateFlags) {
       this._callProgressListeners(null, "onUpdateCurrentBrowser",
                                   [listener.mStateFlags, listener.mStatus,
                                    listener.mMessage, listener.mTotalProgress],
@@ -1661,22 +1666,27 @@ window._gBrowser = {
     this._tabListeners.set(tab, listener);
     filter.addProgressListener(listener, Ci.nsIWebProgress.NOTIFY_ALL);
 
     // Restore the progress listener.
     aBrowser.webProgress.addProgressListener(filter, Ci.nsIWebProgress.NOTIFY_ALL);
 
     // Restore the securityUI state.
     let securityUI = aBrowser.securityUI;
+    // Make sure to call the state getter before the oldState getter to give
+    // the securityUI object a chance to sync its state with the docshell
     let state = securityUI ? securityUI.state :
       Ci.nsIWebProgressListener.STATE_IS_INSECURE;
+    let oldState = securityUI ? securityUI.oldState :
+      Ci.nsIWebProgressListener.STATE_IS_INSECURE;
     // Include the true final argument to indicate that this event is
     // simulated (instead of being observed by the webProgressListener).
     this._callProgressListeners(aBrowser, "onSecurityChange",
-                                [aBrowser.webProgress, null, state, true],
+                                [aBrowser.webProgress, null, oldState, state,
+                                 securityUI.contentBlockingLogJSON, true],
                                 true, false);
 
     if (aShouldBeRemote) {
       // Switching the browser to be remote will connect to a new child
       // process so the browser can no longer be considered to be
       // crashed.
       tab.removeAttribute("crashed");
     } else {
@@ -5071,19 +5081,20 @@ class TabProgressListener {
       return;
 
     this._callProgressListeners("onStatusChange",
                                 [aWebProgress, aRequest, aStatus, aMessage]);
 
     this.mMessage = aMessage;
   }
 
-  onSecurityChange(aWebProgress, aRequest, aState) {
+  onSecurityChange(aWebProgress, aRequest, aOldState, aState, aContentBlockingLogJSON) {
     this._callProgressListeners("onSecurityChange",
-                                [aWebProgress, aRequest, aState]);
+                                [aWebProgress, aRequest, aOldState, aState,
+                                 aContentBlockingLogJSON]);
   }
 
   onRefreshAttempted(aWebProgress, aURI, aDelay, aSameURI) {
     return this._callProgressListeners("onRefreshAttempted",
                                        [aWebProgress, aURI, aDelay, aSameURI]);
   }
 }
 TabProgressListener.prototype.QueryInterface = ChromeUtils.generateQI(
--- a/browser/base/content/test/general/browser_alltabslistener.js
+++ b/browser/base/content/test/general/browser_alltabslistener.js
@@ -21,17 +21,18 @@ var gFrontProgressListener = {
     ok(gFrontNotificationsPos < gFrontNotifications.length, "Got an expected notification for the front notifications listener");
     is(state, gFrontNotifications[gFrontNotificationsPos], "Got a notification for the front notifications listener");
     gFrontNotificationsPos++;
   },
 
   onStatusChange(aWebProgress, aRequest, aStatus, aMessage) {
   },
 
-  onSecurityChange(aWebProgress, aRequest, aState) {
+  onSecurityChange(aWebProgress, aRequest, aOldState, aState,
+                   aContentBlockingLogJSON) {
     var state = "onSecurityChange";
     info("FrontProgress: " + state + " 0x" + aState.toString(16));
     ok(gFrontNotificationsPos < gFrontNotifications.length, "Got an expected notification for the front notifications listener");
     is(state, gFrontNotifications[gFrontNotificationsPos], "Got a notification for the front notifications listener");
     gFrontNotificationsPos++;
   },
 };
 
@@ -61,17 +62,18 @@ var gAllProgressListener = {
     gAllNotificationsPos++;
   },
 
   onStatusChange(aBrowser, aWebProgress, aRequest, aStatus, aMessage) {
     var state = "onStatusChange";
     ok(aBrowser == gTestBrowser, state + " notification came from the correct browser");
   },
 
-  onSecurityChange(aBrowser, aWebProgress, aRequest, aState) {
+  onSecurityChange(aBrowser, aWebProgress, aRequest, aOldState, aState,
+                   aContentBlockingLogJSON) {
     var state = "onSecurityChange";
     info("AllProgress: " + state + " 0x" + aState.toString(16));
     ok(aBrowser == gTestBrowser, state + " notification came from the correct browser");
     ok(gAllNotificationsPos < gAllNotifications.length, "Got an expected notification for the all notifications listener");
     is(state, gAllNotifications[gAllNotificationsPos], "Got a notification for the all notifications listener");
     gAllNotificationsPos++;
   },
 };
--- a/browser/components/extensions/parent/ext-tabs.js
+++ b/browser/components/extensions/parent/ext-tabs.js
@@ -1196,17 +1196,18 @@ this.tabs = class extends ExtensionAPI {
                 }
                 if (pageSettings.footerRight !== null) {
                   printSettings.footerStrRight = pageSettings.footerRight;
                 }
 
                 let printProgressListener = {
                   onLocationChange(webProgress, request, location, flags) { },
                   onProgressChange(webProgress, request, curSelfProgress, maxSelfProgress, curTotalProgress, maxTotalProgress) { },
-                  onSecurityChange(webProgress, request, state) { },
+                  onSecurityChange(webProgress, request, oldState, state,
+                                   contentBlockingLogJSON) { },
                   onStateChange(webProgress, request, flags, status) {
                     if ((flags & Ci.nsIWebProgressListener.STATE_STOP) && (flags & Ci.nsIWebProgressListener.STATE_IS_DOCUMENT)) {
                       resolve(retval == 0 ? "saved" : "replaced");
                     }
                   },
                   onStatusChange: function(webProgress, request, status, message) {
                     if (status != 0) {
                       resolve(retval == 0 ? "not_saved" : "not_replaced");
--- a/browser/components/shell/nsMacShellService.cpp
+++ b/browser/components/shell/nsMacShellService.cpp
@@ -186,17 +186,19 @@ nsMacShellService::OnStatusChange(nsIWeb
                                   const char16_t* aMessage)
 {
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsMacShellService::OnSecurityChange(nsIWebProgress* aWebProgress,
                                     nsIRequest* aRequest,
-                                    uint32_t aState)
+                                    uint32_t aOldState,
+                                    uint32_t aState,
+                                    const nsAString& aContentBlockingLogJSON)
 {
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsMacShellService::OnStateChange(nsIWebProgress* aWebProgress,
                                  nsIRequest* aRequest,
                                  uint32_t aStateFlags,
--- a/browser/components/urlbar/UrlbarController.jsm
+++ b/browser/components/urlbar/UrlbarController.jsm
@@ -9,17 +9,17 @@ var EXPORTED_SYMBOLS = ["QueryContext", 
 ChromeUtils.import("resource://gre/modules/Services.jsm");
 
 // XXX This is a fake manager to provide a basic integration test whilst we
 // are still constructing the manager.
 // eslint-disable-next-line require-jsdoc
 const ProvidersManager = {
   queryStart(queryContext, controller) {
     queryContext.results = [];
-    for (let i = 0; i < 12; i++) {
+    for (let i = 0; i < queryContext.maxResults; i++) {
       const SWITCH_TO_TAB = Math.random() < .3;
       let url = "http://www." + queryContext.searchString;
       while (Math.random() < .9) {
         url += queryContext.searchString;
       }
       let title = queryContext.searchString;
       while (Math.random() < .5) {
         title += queryContext.isPrivate ? " private" : " foo bar";
--- a/browser/components/urlbar/UrlbarInput.jsm
+++ b/browser/components/urlbar/UrlbarInput.jsm
@@ -12,16 +12,20 @@ XPCOMUtils.defineLazyModuleGetters(this,
   PrivateBrowsingUtils: "resource://gre/modules/PrivateBrowsingUtils.jsm",
   QueryContext: "resource:///modules/UrlbarController.jsm",
   Services: "resource://gre/modules/Services.jsm",
   UrlbarController: "resource:///modules/UrlbarController.jsm",
   UrlbarPrefs: "resource:///modules/UrlbarPrefs.jsm",
   UrlbarView: "resource:///modules/UrlbarView.jsm",
 });
 
+XPCOMUtils.defineLazyServiceGetter(this, "ClipboardHelper",
+                                   "@mozilla.org/widget/clipboardhelper;1",
+                                   "nsIClipboardHelper");
+
 /**
  * Represents the urlbar <textbox>.
  * Also forwards important textbox properties and methods.
  */
 class UrlbarInput {
   /**
    * @param {object} options
    *   The initial options for UrlbarInput.
@@ -30,16 +34,18 @@ class UrlbarInput {
    * @param {object} options.panel
    *   The <panel> element.
    * @param {UrlbarController} [options.controller]
    *   Optional fake controller to override the built-in UrlbarController.
    *   Intended for use in unit tests only.
    */
   constructor(options = {}) {
     this.textbox = options.textbox;
+    this.textbox.clickSelectsAll = UrlbarPrefs.get("clickSelectsAll");
+
     this.panel = options.panel;
     this.window = this.textbox.ownerGlobal;
     this.controller = options.controller || new UrlbarController();
     this.view = new UrlbarView(this);
     this.valueIsTyped = false;
     this.userInitiatedFocus = false;
     this.isPrivate = PrivateBrowsingUtils.isWindowPrivate(this.window);
 
@@ -85,20 +91,21 @@ class UrlbarInput {
             return this[setter](val);
           }
           return this.textbox[property] = val;
         },
       });
     }
 
     this.addEventListener("input", this);
-    this.inputField.addEventListener("select", this);
+    this.inputField.addEventListener("mousedown", this);
     this.inputField.addEventListener("overflow", this);
     this.inputField.addEventListener("underflow", this);
     this.inputField.addEventListener("scrollend", this);
+    this.inputField.addEventListener("select", this);
 
     this.inputField.controllers.insertControllerAt(0, new CopyCutController(this));
   }
 
   /* Shortens the given value, usually by removing http:// and trailing slashes,
    * such that calling nsIURIFixup::createFixupURI with the result will produce
    * the same URI.
    *
@@ -296,24 +303,33 @@ class UrlbarInput {
       }
     }
 
     return action;
   }
 
   // Event handlers below.
 
+  _onmousedown(event) {
+    if (event.button == 0 &&
+        event.detail == 2 &&
+        UrlbarPrefs.get("doubleClickSelectsAll")) {
+      this.editor.selectAll();
+      event.preventDefault();
+    }
+  }
+
   _oninput(event) {
     this.valueIsTyped = true;
 
-    // XXX Fill in lastKey & maxResults, and add anything else we need.
+    // XXX Fill in lastKey, and add anything else we need.
     this.controller.handleQuery(new QueryContext({
       searchString: event.target.value,
       lastKey: "",
-      maxResults: 12,
+      maxResults: UrlbarPrefs.get("maxRichResults"),
       isPrivate: this.isPrivate,
     }));
   }
 
   _onselect(event) {
     if (!Services.clipboard.supportsSelectionClipboard()) {
       return;
     }
@@ -322,17 +338,17 @@ class UrlbarInput {
       return;
     }
 
     let val = this._getSelectedValueForClipboard();
     if (!val) {
       return;
     }
 
-    Services.clipboard.copyStringToClipboard(val, Services.clipboard.kSelectionClipboard);
+    ClipboardHelper.copyStringToClipboard(val, Services.clipboard.kSelectionClipboard);
   }
 
   _onoverflow(event) {
     const targetIsPlaceholder =
       !event.originalTarget.classList.contains("anonymous-div");
     // We only care about the non-placeholder text.
     // This shouldn't be needed, see bug 1487036.
     if (targetIsPlaceholder) {
@@ -391,19 +407,17 @@ class CopyCutController {
 
       let event = urlbar.window.document.createEvent("UIEvents");
       event.initUIEvent("input", true, false, this.window, 0);
       urlbar.dispatchEvent(event);
 
       urlbar.window.SetPageProxyState("invalid");
     }
 
-    Cc["@mozilla.org/widget/clipboardhelper;1"]
-      .getService(Ci.nsIClipboardHelper)
-      .copyString(val);
+    ClipboardHelper.copyString(val);
   }
 
   /**
    * @param {string} command
    * @returns {boolean}
    *   Whether the command is handled by this controller.
    */
   supportsCommand(command) {
--- a/browser/components/urlbar/UrlbarPrefs.jsm
+++ b/browser/components/urlbar/UrlbarPrefs.jsm
@@ -38,21 +38,31 @@ const PREF_URLBAR_DEFAULTS = new Map([
   // autofilled even if the user hasn't actually visited them.
   ["autoFill.searchEngines", false],
 
   // Affects the frecency threshold of the autofill algorithm.  The threshold is
   // the mean of all origin frecencies plus one standard deviation multiplied by
   // this value.  See UnifiedComplete.
   ["autoFill.stddevMultiplier", [0.0, "getFloatPref"]],
 
+  // If true, this optimizes for replacing the full URL rather than editing
+  // part of it. This also copies the urlbar value to the selection clipboard
+  // on systems that support it.
+  ["clickSelectsAll", false],
+
   // The amount of time (ms) to wait after the user has stopped typing before
   // fetching results.  However, we ignore this for the very first result (the
   // "heuristic" result).  We fetch it as fast as possible.
   ["delay", 50],
 
+  // If true, this optimizes for replacing the full URL rather than selecting a
+  // portion of it. This also copies the urlbar value to the selection
+  // clipboard on systems that support it.
+  ["doubleClickSelectsAll", false],
+
   // When true, `javascript:` URLs are not included in search results.
   ["filter.javascript", true],
 
   // Allows results from one search to be reused in the next search.  One of the
   // INSERTMETHOD values.
   ["insertMethod", UrlbarUtils.INSERTMETHOD.MERGE_RELATED],
 
   // Controls how URLs are matched against the user's search string.  See
--- a/browser/components/urlbar/tests/browser/browser_UrlbarInput_unit.js
+++ b/browser/components/urlbar/tests/browser/browser_UrlbarInput_unit.js
@@ -110,16 +110,17 @@ add_task(function test_input_starts_quer
       value: "search",
     },
     type: "input",
   });
 
   checkHandleQueryCall(fakeController.handleQuery, {
     searchString: "search",
     isPrivate: false,
+    maxResults: UrlbarPrefs.get("maxRichResults"),
   });
 
   sandbox.resetHistory();
 });
 
 add_task(function test_input_with_private_browsing() {
   PrivateBrowsingUtils.isWindowPrivate.returns(true);
 
--- a/browser/components/urlbar/tests/unit/head.js
+++ b/browser/components/urlbar/tests/unit/head.js
@@ -39,17 +39,17 @@ Services.scriptloader.loadSubScript("res
 /**
  * @param {string} searchString The search string to insert into the context.
  * @returns {QueryContext} Creates a dummy query context with pre-filled required options.
  */
 function createContext(searchString = "foo") {
   return new QueryContext({
     searchString,
     lastKey: searchString ? searchString[searchString.length - 1] : "",
-    maxResults: 1,
+    maxResults: Services.prefs.getIntPref("browser.urlbar.maxRichResults"),
     isPrivate: true,
   });
 }
 
 /**
  * Waits for the given notification from the supplied controller.
  *
  * @param {UrlbarController} controller The controller to wait for a response from.
--- a/browser/components/urlbar/tests/unit/test_UrlbarController_integration.js
+++ b/browser/components/urlbar/tests/unit/test_UrlbarController_integration.js
@@ -41,15 +41,15 @@ add_task(async function test_basic_searc
   controller.handleQuery(context);
 
   let params = await startedPromise;
 
   Assert.equal(params[0], context);
 
   params = await resultsPromise;
 
-  Assert.equal(params[0].results.length, 12,
+  Assert.equal(params[0].results.length, Services.prefs.getIntPref("browser.urlbar.maxRichResults"),
     "Should have given the expected amount of results");
 
   for (let result of params[0].results) {
     Assert.ok(result.url.includes(TEST_URL));
   }
 });
--- a/browser/installer/windows/nsis/uninstaller.nsi
+++ b/browser/installer/windows/nsis/uninstaller.nsi
@@ -651,21 +651,22 @@ Function un.onGUIEnd
     ${WinVerGetServicePackLevel} $3
     StrCpy $R1 "${URLUninstallSurvey}$0.$1.$2.$3"
 
     ; We can't just open the URL normally because we are most likely running
     ; elevated without an unelevated process to redirect through, and we're
     ; not going to go around starting elevated web browsers. But to start an
     ; unelevated process directly from here we need a pretty nasty hack; see
     ; the ExecInExplorer plugin code itself for the details.
+    ; If we were the default browser and we've now been uninstalled, we need
+    ; to take steps to make sure the user doesn't see an "open with" dialog;
+    ; they're helping us out by answering this survey, they don't need more
+    ; friction. Sometimes Windows 7 and 8 automatically switch the default to
+    ; IE, but it isn't reliable, so we'll manually invoke IE in that case.
+    ; Windows 10 always seems to just clear the default browser, so for it
+    ; we'll manually invoke Edge using Edge's custom URI scheme.
     ${If} ${AtLeastWin10}
-      ; If we were the default browser and we've now been uninstalled, we need
-      ; to take steps to make sure the user doesn't see an "open with" dialog;
-      ; they're helping us out by answering this survey, they don't need more
-      ; friction. Windows 7 and 8 automatically switch the default to IE, so
-      ; nothing to do for those, but 10 leaves the default empty and does show
-      ; the dialog, so we have to force Edge there.
       ExecInExplorer::Exec "microsoft-edge:$R1"
     ${Else}
-      ExecInExplorer::Exec "$R1"
+      ExecInExplorer::Exec "iexplore.exe" /cmdargs "$R1"
     ${EndIf}
   ${EndIf}
 FunctionEnd
--- a/devtools/client/inspector/animation/reducers/animations.js
+++ b/devtools/client/inspector/animation/reducers/animations.js
@@ -9,17 +9,17 @@ const {
   UPDATE_DETAIL_VISIBILITY,
   UPDATE_ELEMENT_PICKER_ENABLED,
   UPDATE_HIGHLIGHTED_NODE,
   UPDATE_PLAYBACK_RATES,
   UPDATE_SELECTED_ANIMATION,
   UPDATE_SIDEBAR_SIZE,
 } = require("../actions/index");
 
-const TimeScale = require("../utils/timescale");
+loader.lazyRequireGetter(this, "TimeScale", "devtools/client/inspector/animation/utils/timescale");
 
 const INITIAL_STATE = {
   animations: [],
   detailVisibility: false,
   elementPickerEnabled: false,
   highlightedNode: null,
   playbackRates: [],
   selectedAnimation: null,
--- a/devtools/client/inspector/breadcrumbs.js
+++ b/devtools/client/inspector/breadcrumbs.js
@@ -2,27 +2,27 @@
 /* vim: set ft=javascript 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/. */
 
 "use strict";
 
 const promise = require("promise");
+const flags = require("devtools/shared/flags");
+const {ELLIPSIS} = require("devtools/shared/l10n");
+const EventEmitter = require("devtools/shared/event-emitter");
 
-const {ELLIPSIS} = require("devtools/shared/l10n");
+loader.lazyRequireGetter(this, "KeyShortcuts", "devtools/client/shared/key-shortcuts");
 
 const MAX_LABEL_LENGTH = 40;
 
 const NS_XHTML = "http://www.w3.org/1999/xhtml";
 const SCROLL_REPEAT_MS = 100;
 
-const EventEmitter = require("devtools/shared/event-emitter");
-const KeyShortcuts = require("devtools/client/shared/key-shortcuts");
-
 // Some margin may be required for visible element detection.
 const SCROLL_MARGIN = 1;
 
 const SHADOW_ROOT_TAGNAME = "#shadow-root";
 
 /**
  * Component to replicate functionality of XUL arrowscrollbox
  * for breadcrumbs
@@ -45,18 +45,16 @@ ArrowScrollBox.prototype = {
 
   /**
    * Build the HTML, add to the DOM and start listening to
    * events
    */
   init: function() {
     this.constructHtml();
 
-    this.onUnderflow();
-
     this.onScroll = this.onScroll.bind(this);
     this.onStartBtnClick = this.onStartBtnClick.bind(this);
     this.onEndBtnClick = this.onEndBtnClick.bind(this);
     this.onStartBtnDblClick = this.onStartBtnDblClick.bind(this);
     this.onEndBtnDblClick = this.onEndBtnDblClick.bind(this);
     this.onUnderflow = this.onUnderflow.bind(this);
     this.onOverflow = this.onOverflow.bind(this);
 
@@ -382,21 +380,26 @@ HTMLBreadcrumbs.prototype = {
     this.scroll = this.scroll.bind(this);
     this.arrowScrollBox.on("overflow", this.scroll);
 
     this.outer.addEventListener("click", this, true);
     this.outer.addEventListener("mouseover", this, true);
     this.outer.addEventListener("mouseout", this, true);
     this.outer.addEventListener("focus", this, true);
 
-    this.shortcuts = new KeyShortcuts({ window: this.win, target: this.outer });
     this.handleShortcut = this.handleShortcut.bind(this);
 
-    this.shortcuts.on("Right", this.handleShortcut);
-    this.shortcuts.on("Left", this.handleShortcut);
+    if (flags.testing) {
+      // In tests, we start listening immediately to avoid having to simulate a focus.
+      this.initKeyShortcuts();
+    } else {
+      this.outer.addEventListener("focus", () => {
+        this.initKeyShortcuts();
+      }, { once: true });
+    }
 
     // We will save a list of already displayed nodes in this array.
     this.nodeHierarchy = [];
 
     // Last selected node in nodeHierarchy.
     this.currentIndex = -1;
 
     // Used to build a unique breadcrumb button Id.
@@ -407,18 +410,23 @@ HTMLBreadcrumbs.prototype = {
     this.updateSelectors = this.updateSelectors.bind(this);
     this.selection.on("new-node-front", this.update);
     this.selection.on("pseudoclass", this.updateSelectors);
     this.selection.on("attribute-changed", this.updateSelectors);
     this.inspector.on("markupmutation", this.updateWithMutations);
     this.update();
   },
 
+  initKeyShortcuts() {
+    this.shortcuts = new KeyShortcuts({ window: this.win, target: this.outer });
+    this.shortcuts.on("Right", this.handleShortcut);
+    this.shortcuts.on("Left", this.handleShortcut);
+  },
+
   /**
-
    * Build a string that represents the node: tagName#id.class1.class2.
    * @param {NodeFront} node The node to pretty-print
    * @return {String}
    */
   prettyPrintNodeAsText: function(node) {
     let text = node.isShadowRoot ? SHADOW_ROOT_TAGNAME : node.displayName;
     if (node.isPseudoElement) {
       text = node.isBeforePseudoElement ? "::before" : "::after";
@@ -616,17 +624,20 @@ HTMLBreadcrumbs.prototype = {
     this.selection.off("pseudoclass", this.updateSelectors);
     this.selection.off("attribute-changed", this.updateSelectors);
     this.inspector.off("markupmutation", this.updateWithMutations);
 
     this.container.removeEventListener("click", this, true);
     this.container.removeEventListener("mouseover", this, true);
     this.container.removeEventListener("mouseout", this, true);
     this.container.removeEventListener("focus", this, true);
-    this.shortcuts.destroy();
+
+    if (this.shortcuts) {
+      this.shortcuts.destroy();
+    }
 
     this.empty();
 
     this.arrowScrollBox.off("overflow", this.scroll);
     this.arrowScrollBox.destroy();
     this.arrowScrollBox = null;
     this.outer = null;
     this.container = null;
--- a/devtools/client/inspector/flexbox/components/FlexItem.js
+++ b/devtools/client/inspector/flexbox/components/FlexItem.js
@@ -2,21 +2,25 @@
  * 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 { PureComponent } = require("devtools/client/shared/vendor/react");
 const dom = require("devtools/client/shared/vendor/react-dom-factories");
 const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
-const { translateNodeFrontToGrip } = require("devtools/client/inspector/shared/utils");
 
-const { REPS, MODE } = require("devtools/client/shared/components/reps/reps");
-const { Rep } = REPS;
-const ElementNode = REPS.ElementNode;
+loader.lazyGetter(this, "Rep", function() {
+  return require("devtools/client/shared/components/reps/reps").REPS.Rep;
+});
+loader.lazyGetter(this, "MODE", function() {
+  return require("devtools/client/shared/components/reps/reps").MODE;
+});
+
+loader.lazyRequireGetter(this, "translateNodeFrontToGrip", "devtools/client/inspector/shared/utils", true);
 
 const Types = require("../types");
 
 class FlexItem extends PureComponent {
   static get propTypes() {
     return {
       flexItem: PropTypes.shape(Types.flexItem).isRequired,
       onToggleFlexItemShown: PropTypes.func.isRequired,
@@ -33,17 +37,17 @@ class FlexItem extends PureComponent {
     return (
       dom.li({},
         dom.button(
           {
             className: "devtools-button devtools-monospace",
             onClick: () => onToggleFlexItemShown(nodeFront),
           },
           Rep({
-            defaultRep: ElementNode,
+            defaultRep: Rep.ElementNode,
             mode: MODE.TINY,
             object: translateNodeFrontToGrip(nodeFront),
           })
         )
       )
     );
   }
 }
--- a/devtools/client/inspector/flexbox/flexbox.js
+++ b/devtools/client/inspector/flexbox/flexbox.js
@@ -1,16 +1,16 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
-const { throttle } = require("devtools/client/inspector/shared/utils");
 const flags = require("devtools/shared/flags");
+const { throttle } = require("devtools/shared/throttle");
 
 const {
   clearFlexbox,
   toggleFlexItemShown,
   updateFlexbox,
   updateFlexboxColor,
   updateFlexboxHighlighted,
 } = require("./actions/flexbox");
--- a/devtools/client/inspector/grids/components/GridItem.js
+++ b/devtools/client/inspector/grids/components/GridItem.js
@@ -2,21 +2,25 @@
  * 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 { createRef, PureComponent } = require("devtools/client/shared/vendor/react");
 const dom = require("devtools/client/shared/vendor/react-dom-factories");
 const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
-const { translateNodeFrontToGrip } = require("devtools/client/inspector/shared/utils");
 
-const { REPS, MODE } = require("devtools/client/shared/components/reps/reps");
-const { Rep } = REPS;
-const ElementNode = REPS.ElementNode;
+loader.lazyGetter(this, "Rep", function() {
+  return require("devtools/client/shared/components/reps/reps").REPS.Rep;
+});
+loader.lazyGetter(this, "MODE", function() {
+  return require("devtools/client/shared/components/reps/reps").MODE;
+});
+
+loader.lazyRequireGetter(this, "translateNodeFrontToGrip", "devtools/client/inspector/shared/utils", true);
 
 const Types = require("../types");
 
 class GridItem extends PureComponent {
   static get propTypes() {
     return {
       getSwatchColorPickerTooltip: PropTypes.func.isRequired,
       grid: PropTypes.shape(Types.grid).isRequired,
@@ -104,17 +108,17 @@ class GridItem extends PureComponent {
               checked: grid.highlighted,
               disabled: grid.disabled,
               type: "checkbox",
               value: grid.id,
               onChange: this.onGridCheckboxClick,
             }
           ),
           Rep({
-            defaultRep: ElementNode,
+            defaultRep: Rep.ElementNode,
             mode: MODE.TINY,
             object: translateNodeFrontToGrip(grid.nodeFront),
             onDOMNodeMouseOut: () => onHideBoxModelHighlighter(),
             onDOMNodeMouseOver: () => onShowBoxModelHighlighterForNode(grid.nodeFront),
             onInspectIconClick: () => this.onGridInspectIconClick(grid.nodeFront),
           })
         ),
         dom.div(
--- a/devtools/client/inspector/grids/grid-inspector.js
+++ b/devtools/client/inspector/grids/grid-inspector.js
@@ -1,17 +1,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/. */
 
 "use strict";
 
 const Services = require("Services");
-const { throttle } = require("devtools/client/inspector/shared/utils");
 const flags = require("devtools/shared/flags");
+const { throttle } = require("devtools/shared/throttle");
 
 const {
   updateGridColor,
   updateGridHighlighted,
   updateGrids,
 } = require("./actions/grids");
 const {
   updateShowGridAreas,
--- a/devtools/client/inspector/inspector.js
+++ b/devtools/client/inspector/inspector.js
@@ -604,27 +604,16 @@ Inspector.prototype = {
     });
 
     this.splitBox = this.ReactDOM.render(splitter,
       this.panelDoc.getElementById("inspector-splitter-box"));
 
     this.panelWin.addEventListener("resize", this.onPanelWindowResize, true);
   },
 
-  /**
-   * Splitter clean up.
-   */
-  teardownSplitter: function() {
-    this.panelWin.removeEventListener("resize", this.onPanelWindowResize, true);
-
-    this.sidebar.off("show", this.onSidebarShown);
-    this.sidebar.off("hide", this.onSidebarHidden);
-    this.sidebar.off("destroy", this.onSidebarHidden);
-  },
-
   _onLazyPanelResize: async function() {
     // We can be called on a closed window because of the deferred task.
     if (window.closed) {
       return;
     }
 
     // Use window.top because promiseDocumentFlushed() in a subframe doesn't
     // work, see https://bugzilla.mozilla.org/show_bug.cgi?id=1441173
@@ -779,35 +768,37 @@ Inspector.prototype = {
    * panel. Otherwise, we specify the default tab when handling the sidebar setup.
    *
    * @params {String} defaultTab
    *         Thie id of the default tab for the sidebar.
    */
   async addRuleView({ defaultTab = "ruleview", skipQueue = false } = {}) {
     const ruleViewSidebar = this.sidebarSplitBox.startPanelContainer;
 
+    if (this.is3PaneModeEnabled || defaultTab === "ruleview") {
+      // Force the rule view panel creation by calling getPanel
+      this.getPanel("ruleview");
+    }
+
     if (this.is3PaneModeEnabled) {
       // Convert to 3 pane mode by removing the rule view from the inspector sidebar
       // and adding the rule view to the middle (in landscape/horizontal mode) or
       // bottom-left (in portrait/vertical mode) panel.
       ruleViewSidebar.style.display = "block";
 
       this.setSidebarSplitBoxState();
 
-      // Force the rule view panel creation by calling getPanel
-      this.getPanel("ruleview");
-
       await this.sidebar.removeTab("ruleview");
 
       this.ruleViewSideBar.addExistingTab(
         "ruleview",
         INSPECTOR_L10N.getStr("inspector.sidebar.ruleViewTitle"),
         true);
 
-      this.ruleViewSideBar.show("ruleview");
+      this.ruleViewSideBar.show();
     } else {
       // Removes the rule view from the 3 pane mode and adds the rule view to the main
       // inspector sidebar.
       ruleViewSidebar.style.display = "none";
 
       // Set the width of the split box (right panel in horziontal mode and bottom panel
       // in vertical mode) to be the width of the inspector sidebar.
       const splitterBox = this.panelDoc.getElementById("inspector-splitter-box");
@@ -894,18 +885,16 @@ Inspector.prototype = {
 
     this.sidebar = new ToolSidebar(sidebar, this, "inspector", options);
 
     const ruleSideBar = this.panelDoc.getElementById("inspector-rules-sidebar");
     this.ruleViewSideBar = new ToolSidebar(ruleSideBar, this, "inspector", {
       hideTabstripe: true
     });
 
-    this.sidebar.on("select", this.onSidebarSelect);
-
     let defaultTab = Services.prefs.getCharPref("devtools.inspector.activeSidebar");
 
     if (this.is3PaneModeEnabled && defaultTab === "ruleview") {
       defaultTab = "computedview";
     }
 
     // Append all side panels
 
@@ -1008,21 +997,22 @@ Inspector.prototype = {
           }
         },
         defaultTab == changesId);
     }
 
     this.sidebar.addAllQueuedTabs();
 
     // Persist splitter state in preferences.
+    this.sidebar.on("select", this.onSidebarSelect);
     this.sidebar.on("show", this.onSidebarShown);
     this.sidebar.on("hide", this.onSidebarHidden);
     this.sidebar.on("destroy", this.onSidebarHidden);
 
-    this.sidebar.show(defaultTab);
+    this.sidebar.show();
   },
 
   /**
    * Setup any extension sidebar already registered to the toolbox when the inspector.
    * has been created for the first time.
    */
   setupExtensionSidebars() {
     for (const [sidebarId, {title}] of this.toolbox.inspectorExtensionSidebars) {
@@ -1426,19 +1416,23 @@ Inspector.prototype = {
 
     if (this.walker) {
       this.walker.off("new-root", this.onNewRoot);
       this.pageStyle = null;
     }
 
     this.cancelUpdate();
 
+    this.panelWin.removeEventListener("resize", this.onPanelWindowResize, true);
     this.selection.off("new-node-front", this.onNewSelection);
     this.selection.off("detached-front", this.onDetached);
     this.sidebar.off("select", this.onSidebarSelect);
+    this.sidebar.off("show", this.onSidebarShown);
+    this.sidebar.off("hide", this.onSidebarHidden);
+    this.sidebar.off("destroy", this.onSidebarHidden);
     this.target.off("will-navigate", this._onBeforeNavigate);
     this.target.off("thread-paused", this._updateDebuggerPausedWarning);
     this.target.off("thread-resumed", this._updateDebuggerPausedWarning);
     this._toolbox.off("select", this._updateDebuggerPausedWarning);
 
     for (const [, panel] of this._panels) {
       panel.destroy();
     }
@@ -1475,17 +1469,16 @@ Inspector.prototype = {
     }
 
     const cssPropertiesDestroyer = this._cssProperties.front.destroy();
     const sidebarDestroyer = this.sidebar.destroy();
     const ruleViewSideBarDestroyer = this.ruleViewSideBar ?
       this.ruleViewSideBar.destroy() : null;
     const markupDestroyer = this._destroyMarkup();
 
-    this.teardownSplitter();
     this.teardownToolbar();
 
     this.breadcrumbs.destroy();
     this.reflowTracker.destroy();
     this.styleChangeTracker.destroy();
 
     if (this.changesManager) {
       this.changesManager.destroy();
--- a/devtools/client/inspector/layout/components/ComputedProperty.js
+++ b/devtools/client/inspector/layout/components/ComputedProperty.js
@@ -2,20 +2,25 @@
  * 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 { PureComponent } = require("devtools/client/shared/vendor/react");
 const dom = require("devtools/client/shared/vendor/react-dom-factories");
 const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
-const { translateNodeFrontToGrip } = require("devtools/client/inspector/shared/utils");
 
-const { REPS, MODE } = require("devtools/client/shared/components/reps/reps");
-const { Rep } = REPS;
+loader.lazyGetter(this, "Rep", function() {
+  return require("devtools/client/shared/components/reps/reps").REPS.Rep;
+});
+loader.lazyGetter(this, "MODE", function() {
+  return require("devtools/client/shared/components/reps/reps").MODE;
+});
+
+loader.lazyRequireGetter(this, "translateNodeFrontToGrip", "devtools/client/inspector/shared/utils", true);
 
 class ComputedProperty extends PureComponent {
   static get propTypes() {
     return {
       name: PropTypes.string.isRequired,
       onHideBoxModelHighlighter: PropTypes.func,
       onShowBoxModelHighlighterForNode: PropTypes.func,
       referenceElement: PropTypes.object,
--- a/devtools/client/inspector/markup/markup.js
+++ b/devtools/client/inspector/markup/markup.js
@@ -1,35 +1,37 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 const promise = require("promise");
 const Services = require("Services");
+const flags = require("devtools/shared/flags");
 const nodeConstants = require("devtools/shared/dom-node-constants");
 const nodeFilterConstants = require("devtools/shared/dom-node-filter-constants");
 const EventEmitter = require("devtools/shared/event-emitter");
 const {LocalizationHelper} = require("devtools/shared/l10n");
 const {PluralForm} = require("devtools/shared/plural-form");
 const AutocompletePopup = require("devtools/client/shared/autocomplete-popup");
 const KeyShortcuts = require("devtools/client/shared/key-shortcuts");
 const {scrollIntoViewIfNeeded} = require("devtools/client/shared/scroll");
-const {UndoStack} = require("devtools/client/shared/undo");
-const {HTMLTooltip} = require("devtools/client/shared/widgets/tooltip/HTMLTooltip");
 const {PrefObserver} = require("devtools/client/shared/prefs");
 const MarkupElementContainer = require("devtools/client/inspector/markup/views/element-container");
 const MarkupReadOnlyContainer = require("devtools/client/inspector/markup/views/read-only-container");
 const MarkupTextContainer = require("devtools/client/inspector/markup/views/text-container");
-const SlottedNodeContainer = require("devtools/client/inspector/markup/views/slotted-node-container");
 const RootContainer = require("devtools/client/inspector/markup/views/root-container");
 
+loader.lazyRequireGetter(this, "SlottedNodeContainer", "devtools/client/inspector/markup/views/slotted-node-container");
+loader.lazyRequireGetter(this, "HTMLTooltip", "devtools/client/shared/widgets/tooltip/HTMLTooltip", true);
+loader.lazyRequireGetter(this, "UndoStack", "devtools/client/shared/undo", true);
+
 const INSPECTOR_L10N =
-      new LocalizationHelper("devtools/client/locales/inspector.properties");
+  new LocalizationHelper("devtools/client/locales/inspector.properties");
 
 // Page size for pageup/pagedown
 const PAGE_SIZE = 10;
 const DEFAULT_MAX_CHILDREN = 100;
 const NEW_SELECTION_HIGHLIGHTER_TIMER = 1000;
 const DRAG_DROP_AUTOSCROLL_EDGE_MAX_DISTANCE = 50;
 const DRAG_DROP_AUTOSCROLL_EDGE_RATIO = 0.1;
 const DRAG_DROP_MIN_AUTOSCROLL_SPEED = 2;
@@ -59,16 +61,17 @@ const ATTR_COLLAPSE_LENGTH_PREF = "devto
  * @param  {Inspector} inspector
  *         The inspector we're watching.
  * @param  {iframe} frame
  *         An iframe in which the caller has kindly loaded markup.xhtml.
  */
 function MarkupView(inspector, frame, controllerWindow) {
   EventEmitter.decorate(this);
 
+  this.controllerWindow = controllerWindow;
   this.inspector = inspector;
   this.highlighters = inspector.highlighters;
   this.walker = this.inspector.walker;
   this._frame = frame;
   this.win = this._frame.contentWindow;
   this.doc = this._frame.contentDocument;
   this._elt = this.doc.querySelector("#root");
   this.telemetry = this.inspector.telemetry;
@@ -80,19 +83,16 @@ function MarkupView(inspector, frame, co
   this.collapseAttributeLength = Services.prefs.getIntPref(ATTR_COLLAPSE_LENGTH_PREF);
 
   // Creating the popup to be used to show CSS suggestions.
   // The popup will be attached to the toolbox document.
   this.popup = new AutocompletePopup(inspector.toolbox.doc, {
     autoSelect: true,
   });
 
-  this.undo = new UndoStack();
-  this.undo.installController(controllerWindow);
-
   this._containers = new Map();
   // This weakmap will hold keys used with the _containers map, in order to retrieve the
   // slotted container for a given node front.
   this._slottedContainerKeys = new WeakMap();
 
   // Binding functions that need to be called in scope.
   this._handleRejectionIfNotDestroyed = this._handleRejectionIfNotDestroyed.bind(this);
   this._isImagePreviewTarget = this._isImagePreviewTarget.bind(this);
@@ -119,66 +119,90 @@ function MarkupView(inspector, frame, co
   this.inspector.selection.on("new-node-front", this._onNewSelection);
   this.walker.on("display-change", this._onDisplayChange);
   this.walker.on("mutations", this._mutationObserver);
   this.win.addEventListener("copy", this._onCopy);
   this.win.addEventListener("mouseup", this._onMouseUp);
   this.toolbox.on("picker-canceled", this._onToolboxPickerCanceled);
   this.toolbox.on("picker-node-hovered", this._onToolboxPickerHover);
 
+  if (flags.testing) {
+    // In tests, we start listening immediately to avoid having to simulate a mousemove.
+    this._initTooltips();
+  } else {
+    this._elt.addEventListener("mousemove", () => {
+      this._initTooltips();
+    }, { once: true });
+  }
+
   this._onNewSelection();
-  this._initTooltips();
 
   this._prefObserver = new PrefObserver("devtools.markup");
   this._prefObserver.on(ATTR_COLLAPSE_ENABLED_PREF, this._onCollapseAttributesPrefChange);
   this._prefObserver.on(ATTR_COLLAPSE_LENGTH_PREF, this._onCollapseAttributesPrefChange);
 
   this._initShortcuts();
 }
 
 MarkupView.prototype = {
   /**
    * How long does a node flash when it mutates (in ms).
    */
   CONTAINER_FLASHING_DURATION: 500,
 
   _selectedContainer: null,
 
+  get eventDetailsTooltip() {
+    if (!this._eventDetailsTooltip) {
+      // This tooltip will be attached to the toolbox document.
+      this._eventDetailsTooltip = new HTMLTooltip(this.toolbox.doc, {
+        type: "arrow",
+        consumeOutsideClicks: false,
+      });
+    }
+
+    return this._eventDetailsTooltip;
+  },
+
   get toolbox() {
     return this.inspector.toolbox;
   },
 
+  get undo() {
+    if (!this._undo) {
+      this._undo = new UndoStack();
+      this._undo.installController(this.controllerWindow);
+    }
+
+    return this._undo;
+  },
+
   /**
    * Handle promise rejections for various asynchronous actions, and only log errors if
    * the markup view still exists.
    * This is useful to silence useless errors that happen when the markup view is
    * destroyed while still initializing (and making protocol requests).
    */
   _handleRejectionIfNotDestroyed: function(e) {
     if (!this._destroyer) {
       console.error(e);
     }
   },
 
   _initTooltips: function() {
     // The tooltips will be attached to the toolbox document.
-    this.eventDetailsTooltip = new HTMLTooltip(this.toolbox.doc, {
-      type: "arrow",
-      consumeOutsideClicks: false,
-    });
     this.imagePreviewTooltip = new HTMLTooltip(this.toolbox.doc, {
       type: "arrow",
       useXulWrapper: true,
     });
     this._enableImagePreviewTooltip();
   },
 
   _enableImagePreviewTooltip: function() {
-    this.imagePreviewTooltip.startTogglingOnHover(this._elt,
-      this._isImagePreviewTarget);
+    this.imagePreviewTooltip.startTogglingOnHover(this._elt, this._isImagePreviewTarget);
   },
 
   _disableImagePreviewTooltip: function() {
     this.imagePreviewTooltip.stopTogglingOnHover();
   },
 
   _onToolboxPickerHover: function(nodeFront) {
     this.showNode(nodeFront).then(() => {
@@ -1890,23 +1914,35 @@ MarkupView.prototype = {
     }
 
     this._destroyer = promise.resolve();
 
     this._clearBriefBoxModelTimer();
 
     this._hoveredContainer = null;
 
+    if (this._eventDetailsTooltip) {
+      this._eventDetailsTooltip.destroy();
+      this._eventDetailsTooltip = null;
+    }
+
     if (this.htmlEditor) {
       this.htmlEditor.destroy();
       this.htmlEditor = null;
     }
 
-    this.undo.destroy();
-    this.undo = null;
+    if (this.imagePreviewTooltip) {
+      this.imagePreviewTooltip.destroy();
+      this.imagePreviewTooltip = null;
+    }
+
+    if (this._undo) {
+      this._undo.destroy();
+      this._undo = null;
+    }
 
     this.popup.destroy();
     this.popup = null;
 
     this._elt.removeEventListener("blur", this._onBlur, true);
     this._elt.removeEventListener("click", this._onMouseClick);
     this._elt.removeEventListener("mousemove", this._onMouseMove);
     this._elt.removeEventListener("mouseout", this._onMouseOut);
@@ -1926,22 +1962,17 @@ MarkupView.prototype = {
 
     this._elt = null;
 
     for (const [, container] of this._containers) {
       container.destroy();
     }
     this._containers = null;
 
-    this.eventDetailsTooltip.destroy();
-    this.eventDetailsTooltip = null;
-
-    this.imagePreviewTooltip.destroy();
-    this.imagePreviewTooltip = null;
-
+    this.controllerWindow = null;
     this.doc = null;
     this.highlighters = null;
     this.win = null;
 
     this._lastDropTarget = null;
     this._lastDragTarget = null;
 
     return this._destroyer;
--- a/devtools/client/inspector/markup/views/element-container.js
+++ b/devtools/client/inspector/markup/views/element-container.js
@@ -1,45 +1,43 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
-const PREVIEW_MAX_DIM_PREF = "devtools.inspector.imagePreviewTooltipSize";
-
 const promise = require("promise");
 const Services = require("Services");
-const nodeConstants = require("devtools/shared/dom-node-constants");
-const clipboardHelper = require("devtools/shared/platform/clipboard");
-const {setImageTooltip, setBrokenImageTooltip} =
-      require("devtools/client/shared/widgets/tooltip/ImageTooltipHelper");
 const MarkupContainer = require("devtools/client/inspector/markup/views/markup-container");
 const ElementEditor = require("devtools/client/inspector/markup/views/element-editor");
+const {ELEMENT_NODE} = require("devtools/shared/dom-node-constants");
 const {extend} = require("devtools/shared/extend");
 
-// Lazy load this module as _buildEventTooltipContent is only called on click
-loader.lazyRequireGetter(this, "setEventTooltip",
-  "devtools/client/shared/widgets/tooltip/EventTooltipHelper", true);
+loader.lazyRequireGetter(this, "setEventTooltip", "devtools/client/shared/widgets/tooltip/EventTooltipHelper", true);
+loader.lazyRequireGetter(this, "setImageTooltip", "devtools/client/shared/widgets/tooltip/ImageTooltipHelper", true);
+loader.lazyRequireGetter(this, "setBrokenImageTooltip", "devtools/client/shared/widgets/tooltip/ImageTooltipHelper", true);
+loader.lazyRequireGetter(this, "clipboardHelper", "devtools/shared/platform/clipboard");
+
+const PREVIEW_MAX_DIM_PREF = "devtools.inspector.imagePreviewTooltipSize";
 
 /**
  * An implementation of MarkupContainer for Elements that can contain
  * child nodes.
  * Allows editing of tag name, attributes, expanding / collapsing.
  *
  * @param  {MarkupView} markupView
  *         The markup view that owns this container.
  * @param  {NodeFront} node
  *         The node to display.
  */
 function MarkupElementContainer(markupView, node) {
   MarkupContainer.prototype.initialize.call(this, markupView, node,
     "elementcontainer");
 
-  if (node.nodeType === nodeConstants.ELEMENT_NODE) {
+  if (node.nodeType === ELEMENT_NODE) {
     this.editor = new ElementEditor(this, node);
   } else {
     throw new Error("Invalid node for MarkupElementContainer");
   }
 
   this.tagLine.appendChild(this.editor.elt);
 }
 
--- a/devtools/client/inspector/markup/views/element-editor.js
+++ b/devtools/client/inspector/markup/views/element-editor.js
@@ -1,27 +1,26 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 const Services = require("Services");
 const TextEditor = require("devtools/client/inspector/markup/views/text-editor");
-const {
-  getAutocompleteMaxWidth,
-  flashElementOn,
-  flashElementOff,
-  parseAttributeValues,
-} = require("devtools/client/inspector/markup/utils");
 const { truncateString } = require("devtools/shared/inspector/utils");
 const { editableField, InplaceEditor } = require("devtools/client/shared/inplace-editor");
 const { parseAttribute } = require("devtools/client/shared/node-attribute-parser");
 const { getCssProperties } = require("devtools/shared/fronts/css-properties");
 
+loader.lazyRequireGetter(this, "flashElementOn", "devtools/client/inspector/markup/utils", true);
+loader.lazyRequireGetter(this, "flashElementOff", "devtools/client/inspector/markup/utils", true);
+loader.lazyRequireGetter(this, "getAutocompleteMaxWidth", "devtools/client/inspector/markup/utils", true);
+loader.lazyRequireGetter(this, "parseAttributeValues", "devtools/client/inspector/markup/utils", true);
+
 const {LocalizationHelper} = require("devtools/shared/l10n");
 const INSPECTOR_L10N =
   new LocalizationHelper("devtools/client/locales/inspector.properties");
 
 // Page size for pageup/pagedown
 const COLLAPSE_DATA_URL_REGEX = /^data.+base64/;
 const COLLAPSE_DATA_URL_LENGTH = 60;
 
--- a/devtools/client/inspector/markup/views/markup-container.js
+++ b/devtools/client/inspector/markup/views/markup-container.js
@@ -31,33 +31,38 @@ function MarkupContainer() { }
 
 /**
  * Unique identifier used to set markup container node id.
  * @type {Number}
  */
 let markupContainerID = 0;
 
 MarkupContainer.prototype = {
+  // Get the UndoStack from the MarkupView.
+  get undo() {
+    // undo is a lazy getter in the MarkupView.
+    return this.markup.undo;
+  },
+
   /*
    * Initialize the MarkupContainer.  Should be called while one
    * of the other contain classes is instantiated.
    *
    * @param  {MarkupView} markupView
    *         The markup view that owns this container.
    * @param  {NodeFront} node
    *         The node to display.
    * @param  {String} type
    *         The type of container to build. One of TYPES.TEXT_CONTAINER,
    *         TYPES.ELEMENT_CONTAINER, TYPES.READ_ONLY_CONTAINER
    */
   initialize: function(markupView, node, type) {
     this.markup = markupView;
     this.node = node;
     this.type = type;
-    this.undo = this.markup.undo;
     this.win = this.markup._frame.contentWindow;
     this.id = "treeitem-" + markupContainerID++;
     this.htmlElt = this.win.document.documentElement;
 
     this.buildMarkup();
 
     this.elt.container = this;
 
--- a/devtools/client/inspector/markup/views/slotted-node-editor.js
+++ b/devtools/client/inspector/markup/views/slotted-node-editor.js
@@ -1,17 +1,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/. */
 
 "use strict";
 
 const {LocalizationHelper} = require("devtools/shared/l10n");
 const INSPECTOR_L10N =
-      new LocalizationHelper("devtools/client/locales/inspector.properties");
+  new LocalizationHelper("devtools/client/locales/inspector.properties");
 
 function SlottedNodeEditor(container, node) {
   this.container = container;
   this.markup = this.container.markup;
   this.buildMarkup();
   this.tag.textContent = "<" + node.nodeName.toLowerCase() + ">";
 
   // Make the "tag" part of this editor focusable.
--- a/devtools/client/inspector/markup/views/text-editor.js
+++ b/devtools/client/inspector/markup/views/text-editor.js
@@ -1,21 +1,22 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
-const {getAutocompleteMaxWidth} = require("devtools/client/inspector/markup/utils");
-const {editableField} = require("devtools/client/shared/inplace-editor");
-const {getCssProperties} = require("devtools/shared/fronts/css-properties");
-const {LocalizationHelper} = require("devtools/shared/l10n");
+const { editableField } = require("devtools/client/shared/inplace-editor");
+const { LocalizationHelper } = require("devtools/shared/l10n");
+
+loader.lazyRequireGetter(this, "getAutocompleteMaxWidth", "devtools/client/inspector/markup/utils", true);
+loader.lazyRequireGetter(this, "getCssProperties", "devtools/shared/fronts/css-properties", true);
 
 const INSPECTOR_L10N =
-      new LocalizationHelper("devtools/client/locales/inspector.properties");
+  new LocalizationHelper("devtools/client/locales/inspector.properties");
 
 /**
  * Creates a simple text editor node, used for TEXT and COMMENT
  * nodes.
  *
  * @param  {MarkupContainer} container
  *         The container owning this editor.
  * @param  {DOMNode} node
--- a/devtools/client/inspector/rules/models/element-style.js
+++ b/devtools/client/inspector/rules/models/element-style.js
@@ -2,20 +2,21 @@
  * 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 promise = require("promise");
 const Rule = require("devtools/client/inspector/rules/models/rule");
 const UserProperties = require("devtools/client/inspector/rules/models/user-properties");
-const { promiseWarn } = require("devtools/client/inspector/shared/utils");
-const { getCssProperties, isCssVariable } = require("devtools/shared/fronts/css-properties");
 const { ELEMENT_STYLE } = require("devtools/shared/specs/styles");
 
+loader.lazyRequireGetter(this, "promiseWarn", "devtools/client/inspector/shared/utils", true);
+loader.lazyRequireGetter(this, "isCssVariable", "devtools/shared/fronts/css-properties", true);
+
 /**
  * ElementStyle is responsible for the following:
  *   Keeps track of which properties are overridden.
  *   Maintains a list of Rule objects for a given element.
  *
  * @param  {Element} element
  *         The element whose style we are viewing.
  * @param  {CssRuleView} ruleView
@@ -32,17 +33,17 @@ const { ELEMENT_STYLE } = require("devto
  */
 function ElementStyle(element, ruleView, store, pageStyle, showUserAgentStyles) {
   this.element = element;
   this.ruleView = ruleView;
   this.store = store || {};
   this.pageStyle = pageStyle;
   this.showUserAgentStyles = showUserAgentStyles;
   this.rules = [];
-  this.cssProperties = getCssProperties(this.ruleView.inspector.toolbox);
+  this.cssProperties = this.ruleView.cssProperties;
   this.variables = new Map();
 
   // We don't want to overwrite this.store.userProperties so we only create it
   // if it doesn't already exist.
   if (!("userProperties" in this.store)) {
     this.store.userProperties = new UserProperties();
   }
 
--- a/devtools/client/inspector/rules/models/rule.js
+++ b/devtools/client/inspector/rules/models/rule.js
@@ -5,20 +5,21 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 const promise = require("promise");
 const CssLogic = require("devtools/shared/inspector/css-logic");
 const {ELEMENT_STYLE} = require("devtools/shared/specs/styles");
 const TextProperty = require("devtools/client/inspector/rules/models/text-property");
-const {promiseWarn} = require("devtools/client/inspector/shared/utils");
-const {parseNamedDeclarations} = require("devtools/shared/css/parsing-utils");
 const Services = require("Services");
 
+loader.lazyRequireGetter(this, "promiseWarn", "devtools/client/inspector/shared/utils", true);
+loader.lazyRequireGetter(this, "parseNamedDeclarations", "devtools/shared/css/parsing-utils", true);
+
 const STYLE_INSPECTOR_PROPERTIES = "devtools/shared/locales/styleinspector.properties";
 const {LocalizationHelper} = require("devtools/shared/l10n");
 const STYLE_INSPECTOR_L10N = new LocalizationHelper(STYLE_INSPECTOR_PROPERTIES);
 
 /**
  * Rule is responsible for the following:
  *   Manages a single style declaration or rule.
  *   Applies changes to the properties in a rule.
--- a/devtools/client/inspector/rules/models/text-property.js
+++ b/devtools/client/inspector/rules/models/text-property.js
@@ -1,18 +1,17 @@
 /* -*- indent-tabs-mode: nil; js-indent-level: 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/. */
 
 "use strict";
 
-const {escapeCSSComment} = require("devtools/shared/css/parsing-utils");
-const {getCssProperties} = require("devtools/shared/fronts/css-properties");
+loader.lazyRequireGetter(this, "escapeCSSComment", "devtools/shared/css/parsing-utils", true);
 
 /**
  * TextProperty is responsible for the following:
  *   Manages a single property from the authoredText attribute of the
  *     relevant declaration.
  *   Maintains a list of computed properties that come from this
  *     property declaration.
  *   Changes to the TextProperty are sent to its related Rule for
@@ -37,21 +36,19 @@ const {getCssProperties} = require("devt
 function TextProperty(rule, name, value, priority, enabled = true,
                       invisible = false) {
   this.rule = rule;
   this.name = name;
   this.value = value;
   this.priority = priority;
   this.enabled = !!enabled;
   this.invisible = invisible;
+  this.cssProperties = this.rule.elementStyle.ruleView.cssProperties;
   this.panelDoc = this.rule.elementStyle.ruleView.inspector.panelDoc;
 
-  const toolbox = this.rule.elementStyle.ruleView.inspector.toolbox;
-  this.cssProperties = getCssProperties(toolbox);
-
   this.updateComputed();
 }
 
 TextProperty.prototype = {
   /**
    * Update the editor associated with this text property,
    * if any.
    */
--- a/devtools/client/inspector/rules/views/rule-editor.js
+++ b/devtools/client/inspector/rules/views/rule-editor.js
@@ -24,20 +24,21 @@ const {
   parsePseudoClassesAndAttributes,
   SELECTOR_ATTRIBUTE,
   SELECTOR_ELEMENT,
   SELECTOR_PSEUDO_CLASS
 } = require("devtools/shared/css/parsing-utils");
 const promise = require("promise");
 const Services = require("Services");
 const EventEmitter = require("devtools/shared/event-emitter");
-const {Tools} = require("devtools/client/definitions");
-const {gDevTools} = require("devtools/client/framework/devtools");
 const CssLogic = require("devtools/shared/inspector/css-logic");
 
+loader.lazyRequireGetter(this, "Tools", "devtools/client/definitions", true);
+loader.lazyRequireGetter(this, "gDevTools", "devtools/client/framework/devtools", true);
+
 const STYLE_INSPECTOR_PROPERTIES = "devtools/shared/locales/styleinspector.properties";
 const {LocalizationHelper} = require("devtools/shared/l10n");
 const STYLE_INSPECTOR_L10N = new LocalizationHelper(STYLE_INSPECTOR_PROPERTIES);
 
 /**
  * RuleEditor is responsible for the following:
  *   Owns a Rule object and creates a list of TextPropertyEditors
  *     for its TextProperties.
--- a/devtools/client/inspector/rules/views/text-property-editor.js
+++ b/devtools/client/inspector/rules/views/text-property-editor.js
@@ -1,32 +1,27 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 const Services = require("Services");
-
 const { l10n } = require("devtools/shared/inspector/css-logic");
-const {getCssProperties} = require("devtools/shared/fronts/css-properties");
-const {InplaceEditor, editableField} =
-      require("devtools/client/shared/inplace-editor");
+const { InplaceEditor, editableField } = require("devtools/client/shared/inplace-editor");
 const {
   createChild,
   appendText,
   advanceValidate,
-  blurOnMultipleProperties
+  blurOnMultipleProperties,
 } = require("devtools/client/inspector/shared/utils");
-const {
-  parseDeclarations,
-  parseSingleValue,
-} = require("devtools/shared/css/parsing-utils");
 
 loader.lazyRequireGetter(this, "openContentLink", "devtools/client/shared/link", true);
+loader.lazyRequireGetter(this, "parseDeclarations", "devtools/shared/css/parsing-utils", true);
+loader.lazyRequireGetter(this, "parseSingleValue", "devtools/shared/css/parsing-utils", true);
 
 const HTML_NS = "http://www.w3.org/1999/xhtml";
 
 const SHARED_SWATCH_CLASS = "ruleview-swatch";
 const COLOR_SWATCH_CLASS = "ruleview-colorswatch";
 const BEZIER_SWATCH_CLASS = "ruleview-bezierswatch";
 const FILTER_SWATCH_CLASS = "ruleview-filterswatch";
 const ANGLE_SWATCH_CLASS = "ruleview-angleswatch";
@@ -70,28 +65,28 @@ const GENERIC_FONT_FAMILIES = [
  * @param {RuleEditor} ruleEditor
  *        The rule editor that owns this TextPropertyEditor.
  * @param {TextProperty} property
  *        The text property to edit.
  */
 function TextPropertyEditor(ruleEditor, property) {
   this.ruleEditor = ruleEditor;
   this.ruleView = this.ruleEditor.ruleView;
+  this.cssProperties = this.ruleView.cssProperties;
   this.doc = this.ruleEditor.doc;
   this.popup = this.ruleView.popup;
   this.prop = property;
   this.prop.editor = this;
   this.browserWindow = this.doc.defaultView.top;
   this._populatedComputed = false;
   this._hasPendingClick = false;
   this._clickedElementOptions = null;
 
   this.toolbox = this.ruleView.inspector.toolbox;
   this.telemetry = this.toolbox.telemetry;
-  this.cssProperties = getCssProperties(this.toolbox);
 
   this.getGridlineNames = this.getGridlineNames.bind(this);
   this.update = this.update.bind(this);
   this.updatePropertyState = this.updatePropertyState.bind(this);
   this._onEnableClicked = this._onEnableClicked.bind(this);
   this._onExpandClicked = this._onExpandClicked.bind(this);
   this._onNameDone = this._onNameDone.bind(this);
   this._onStartEditing = this._onStartEditing.bind(this);
--- a/devtools/client/inspector/shared/utils.js
+++ b/devtools/client/inspector/shared/utils.js
@@ -1,21 +1,21 @@
 /* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* vim: set ft=javascript 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/. */
 
 "use strict";
 
-const {parseDeclarations} = require("devtools/shared/css/parsing-utils");
 const promise = require("promise");
-const {getCSSLexer} = require("devtools/shared/css/lexer");
-const {KeyCodes} = require("devtools/client/shared/keycodes");
-const {throttle} = require("devtools/shared/throttle");
+
+loader.lazyRequireGetter(this, "KeyCodes", "devtools/client/shared/keycodes", true);
+loader.lazyRequireGetter(this, "getCSSLexer", "devtools/shared/css/lexer", true);
+loader.lazyRequireGetter(this, "parseDeclarations", "devtools/shared/css/parsing-utils", true);
 
 const HTML_NS = "http://www.w3.org/1999/xhtml";
 
 /**
  * Called when a character is typed in a value editor.  This decides
  * whether to advance or not, first by checking to see if ";" was
  * typed, and then by lexing the input and seeing whether the ";"
  * would be a terminator at this point.
@@ -194,10 +194,9 @@ function translateNodeFrontToGrip(nodeFr
 }
 
 exports.advanceValidate = advanceValidate;
 exports.appendText = appendText;
 exports.blurOnMultipleProperties = blurOnMultipleProperties;
 exports.createChild = createChild;
 exports.getSelectorFromGrip = getSelectorFromGrip;
 exports.promiseWarn = promiseWarn;
-exports.throttle = throttle;
 exports.translateNodeFrontToGrip = translateNodeFrontToGrip;
--- a/devtools/client/shared/redux/create-store.js
+++ b/devtools/client/shared/redux/create-store.js
@@ -2,19 +2,20 @@
  * 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 { createStore, applyMiddleware } = require("devtools/client/shared/vendor/redux");
 const { thunk } = require("./middleware/thunk");
 const { waitUntilService } = require("./middleware/wait-service");
 const { task } = require("./middleware/task");
-const { log } = require("./middleware/log");
 const { promise } = require("./middleware/promise");
-const { history } = require("./middleware/history");
+
+loader.lazyRequireGetter(this, "history", "devtools/client/shared/redux/middleware/history", true);
+loader.lazyRequireGetter(this, "log", "devtools/client/shared/redux/middleware/log", true);
 
 /**
  * This creates a dispatcher with all the standard middleware in place
  * that all code requires. It can also be optionally configured in
  * various ways, such as logging and recording.
  *
  * @param {object} opts:
  *        - log: log all dispatched actions to console
--- a/devtools/client/themes/breadcrumbs.css
+++ b/devtools/client/themes/breadcrumbs.css
@@ -23,16 +23,17 @@
 .scrollbutton-down {
   -moz-appearance: none;
   background: transparent;
   box-shadow: none;
   border: none;
   list-style-image: none;
   margin: 0;
   padding: 0;
+  visibility: collapse;
 }
 
 .scrollbutton-up > .toolbarbutton-icon,
 .scrollbutton-down > .toolbarbutton-icon {
   -moz-appearance: none;
   width: 20px;
   height: 16px;
   background-size: 16px;
--- a/devtools/server/actors/highlighters/accessible.js
+++ b/devtools/server/actors/highlighters/accessible.js
@@ -1,25 +1,27 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 const { AutoRefreshHighlighter } = require("./auto-refresh");
-const { getBounds, Infobar } = require("./utils/accessibility");
 const {
   CanvasFrameAnonymousContentHelper,
   createNode,
   createSVGNode,
   isNodeValid,
 } = require("./utils/markup");
 const { TEXT_NODE } = require("devtools/shared/dom-node-constants");
 const { setIgnoreLayoutChanges } = require("devtools/shared/layout/utils");
 
+loader.lazyRequireGetter(this, "getBounds", "devtools/server/actors/highlighters/utils/accessibility", true);
+loader.lazyRequireGetter(this, "Infobar", "devtools/server/actors/highlighters/utils/accessibility", true);
+
 /**
  * The AccessibleHighlighter draws the bounds of an accessible object.
  *
  * Usage example:
  *
  * let h = new AccessibleHighlighter(env);
  * h.show(node, { x, y, w, h, [duration] });
  * h.hide();
--- a/devtools/shared/inspector/css-logic.js
+++ b/devtools/shared/inspector/css-logic.js
@@ -1,17 +1,16 @@
 /* -*- indent-tabs-mode: nil; js-indent-level: 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/. */
 
 "use strict";
 
-const { getTabPrefs } = require("devtools/shared/indentation");
 const InspectorUtils = require("InspectorUtils");
 
 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
@@ -45,18 +44,19 @@ const MAX_DATA_URL_LENGTH = 40;
  * @constructor
  */
 
 const Services = require("Services");
 
 loader.lazyImporter(this, "findCssSelector", "resource://gre/modules/css-selector.js");
 loader.lazyImporter(this, "getCssPath", "resource://gre/modules/css-selector.js");
 loader.lazyImporter(this, "getXPath", "resource://gre/modules/css-selector.js");
+loader.lazyRequireGetter(this, "getCSSLexer", "devtools/shared/css/lexer", true);
+loader.lazyRequireGetter(this, "getTabPrefs", "devtools/shared/indentation", true);
 
-const CSSLexer = require("devtools/shared/css/lexer");
 const {LocalizationHelper} = require("devtools/shared/l10n");
 const styleInspectorL10N =
   new LocalizationHelper("devtools/shared/locales/styleinspector.properties");
 
 /**
  * Special values for filter, in addition to an href these values can be used
  */
 exports.FILTER = {
@@ -185,17 +185,17 @@ function prettifyCSS(text, ruleCount) {
   // * A "}" symbol ensures there is a preceding newline, and
   //   decreases the indentation level.
   // * Ensure there is whitespace before a "{".
   //
   // This approach can be confused sometimes, but should do ok on a
   // minified file.
   let indent = "";
   let indentLevel = 0;
-  const tokens = CSSLexer.getCSSLexer(text);
+  const tokens = getCSSLexer(text);
   let result = "";
   let pushbackToken = undefined;
 
   // A helper function that reads tokens, looking for the next
   // non-comment, non-whitespace token.  Comment and whitespace tokens
   // are appended to |result|.  If this encounters EOF, it returns
   // null.  Otherwise it returns the last whitespace token that was
   // seen.  This function also updates |pushbackToken|.
--- a/docshell/base/nsDocShell.cpp
+++ b/docshell/base/nsDocShell.cpp
@@ -7002,17 +7002,19 @@ nsDocShell::OnStatusChange(nsIWebProgres
                            nsresult aStatus, const char16_t* aMessage)
 {
   MOZ_ASSERT_UNREACHABLE("notification excluded in AddProgressListener(...)");
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsDocShell::OnSecurityChange(nsIWebProgress* aWebProgress,
-                             nsIRequest* aRequest, uint32_t aState)
+                             nsIRequest* aRequest, uint32_t aOldState,
+                             uint32_t aState,
+                             const nsAString& aContentBlockingLogJSON)
 {
   MOZ_ASSERT_UNREACHABLE("notification excluded in AddProgressListener(...)");
   return NS_OK;
 }
 
 nsresult
 nsDocShell::EndPageLoad(nsIWebProgress* aProgress,
                         nsIChannel* aChannel, nsresult aStatus)
--- a/docshell/base/nsDocShellTreeOwner.cpp
+++ b/docshell/base/nsDocShellTreeOwner.cpp
@@ -762,17 +762,19 @@ nsDocShellTreeOwner::OnStatusChange(nsIW
                                     const char16_t* aMessage)
 {
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsDocShellTreeOwner::OnSecurityChange(nsIWebProgress* aWebProgress,
                                       nsIRequest* aRequest,
-                                      uint32_t aState)
+                                      uint32_t aOldState,
+                                      uint32_t aState,
+                                      const nsAString& aContentBlockingLogJSON)
 {
   return NS_OK;
 }
 
 //*****************************************************************************
 // nsDocShellTreeOwner: Accessors
 //*****************************************************************************
 
new file mode 100644
--- /dev/null
+++ b/dom/base/ContentBlockingLog.h
@@ -0,0 +1,155 @@
+/* -*- 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/. */
+
+#ifndef mozilla_dom_ContentBlockingLog_h
+#define mozilla_dom_ContentBlockingLog_h
+
+#include "mozilla/JSONWriter.h"
+#include "mozilla/StaticPrefs.h"
+#include "mozilla/UniquePtr.h"
+#include "nsClassHashtable.h"
+#include "nsHashKeys.h"
+#include "nsReadableUtils.h"
+#include "nsTArray.h"
+#include "nsWindowSizes.h"
+
+namespace mozilla {
+namespace dom {
+
+class ContentBlockingLog final
+{
+  struct LogEntry {
+    uint32_t mType;
+    uint32_t mRepeatCount;
+    bool mBlocked;
+  };
+
+  // Each element is a tuple of (type, blocked, repeatCount). The type values
+  // come from the blocking types defined in nsIWebProgressListener.
+  typedef nsTArray<LogEntry> OriginLog;
+  typedef nsClassHashtable<nsStringHashKey, OriginLog> OriginLogHashTable;
+
+  struct StringWriteFunc : public JSONWriteFunc
+  {
+    nsAString& mBuffer; // The lifetime of the struct must be bound to the buffer
+    explicit StringWriteFunc(nsAString& aBuffer)
+      : mBuffer(aBuffer)
+    {}
+
+    void Write(const char* aStr) override
+    {
+      mBuffer.Append(NS_ConvertUTF8toUTF16(aStr));
+    }
+  };
+
+public:
+  ContentBlockingLog() = default;
+  ~ContentBlockingLog() = default;
+
+  void RecordLog(const nsAString& aOrigin, uint32_t aType, bool aBlocked)
+  {
+    if (aOrigin.IsVoid()) {
+      return;
+    }
+    auto entry = mLog.LookupForAdd(aOrigin);
+    if (entry) {
+      auto& log = entry.Data();
+      if (!log->IsEmpty()) {
+        auto& last = log->LastElement();
+        if (last.mType == aType &&
+            last.mBlocked == aBlocked) {
+          ++last.mRepeatCount;
+          // Don't record recorded events.  This helps compress our log.
+          return;
+        }
+      }
+      if (log->Length() ==
+            std::max(1u, StaticPrefs::browser_contentblocking_originlog_length())) {
+        // Cap the size at the maximum length adjustable by the pref
+        log->RemoveElementAt(0);
+      }
+      log->AppendElement(LogEntry{aType, 1u, aBlocked});
+    } else {
+      entry.OrInsert([=] {
+        auto log(MakeUnique<OriginLog>());
+        log->AppendElement(LogEntry{aType, 1u, aBlocked});
+        return log.release();
+      });
+    }
+  }
+
+  nsAutoString Stringify()
+  {
+    nsAutoString buffer;
+
+    JSONWriter w(MakeUnique<StringWriteFunc>(buffer));
+    w.Start();
+
+    for (auto iter = mLog.Iter(); !iter.Done(); iter.Next()) {
+      if (!iter.UserData()) {
+        w.StartArrayProperty(NS_ConvertUTF16toUTF8(iter.Key()).get(), w.SingleLineStyle);
+        w.EndArray();
+        continue;
+      }
+
+      w.StartArrayProperty(NS_ConvertUTF16toUTF8(iter.Key()).get(), w.SingleLineStyle);
+      for (auto& item: *iter.UserData()) {
+        w.StartArrayElement(w.SingleLineStyle);
+        {
+          w.IntElement(item.mType);
+          w.BoolElement(item.mBlocked);
+          w.IntElement(item.mRepeatCount);
+        }
+        w.EndArray();
+      }
+      w.EndArray();
+    }
+
+    w.End();
+
+    return buffer;
+  }
+
+  bool HasBlockedAnyOfType(uint32_t aType)
+  {
+    for (auto iter = mLog.Iter(); !iter.Done(); iter.Next()) {
+      if (!iter.UserData()) {
+        continue;
+      }
+
+      for (auto& item: *iter.UserData()) {
+        if ((item.mType & aType) != 0) {
+          return true;
+        }
+      }
+    }
+    return false;
+  }
+
+  void AddSizeOfExcludingThis(nsWindowSizes& aSizes) const
+  {
+    aSizes.mDOMOtherSize += mLog.ShallowSizeOfExcludingThis(aSizes.mState.mMallocSizeOf);
+
+    // Now add the sizes of each origin log queue.
+    // The const_cast is needed because the nsTHashtable::Iterator interface is
+    // not const-safe.  :-(
+    for (auto iter = const_cast<OriginLogHashTable&>(mLog).Iter();
+         !iter.Done(); iter.Next()) {
+      if (iter.UserData()) {
+        aSizes.mDOMOtherSize +=
+          iter.UserData()->ShallowSizeOfIncludingThis(aSizes.mState.mMallocSizeOf);
+      }
+    }
+  }
+
+private:
+  OriginLogHashTable mLog;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif
--- a/dom/base/moz.build
+++ b/dom/base/moz.build
@@ -148,16 +148,17 @@ EXPORTS.mozilla.dom += [
     'CharacterData.h',
     'ChildIterator.h',
     'ChildProcessMessageManager.h',
     'ChromeMessageBroadcaster.h',
     'ChromeMessageSender.h',
     'ChromeNodeList.h',
     'ChromeUtils.h',
     'Comment.h',
+    'ContentBlockingLog.h',
     'ContentFrameMessageManager.h',
     'ContentProcessMessageManager.h',
     'CustomElementRegistry.h',
     'DirectionalityUtils.h',
     'DispatcherTrait.h',
     'DocGroup.h',
     'DocumentFragment.h',
     'DocumentOrShadowRoot.h',
--- a/dom/base/nsDocument.cpp
+++ b/dom/base/nsDocument.cpp
@@ -1398,22 +1398,16 @@ nsIDocument::nsIDocument()
     mHasMixedActiveContentLoaded(false),
     mHasMixedActiveContentBlocked(false),
     mHasMixedDisplayContentLoaded(false),
     mHasMixedDisplayContentBlocked(false),
     mHasMixedContentObjectSubrequest(false),
     mHasCSP(false),
     mHasUnsafeEvalCSP(false),
     mHasUnsafeInlineCSP(false),
-    mHasTrackingContentBlocked(false),
-    mHasSlowTrackingContentBlocked(false),
-    mHasAllCookiesBlocked(false),
-    mHasTrackingCookiesBlocked(false),
-    mHasForeignCookiesBlocked(false),
-    mHasCookiesBlockedByPermission(false),
     mHasTrackingContentLoaded(false),
     mBFCacheDisallowed(false),
     mHasHadDefaultView(false),
     mStyleSheetChangeEventsEnabled(false),
     mIsSrcdocDocument(false),
     mDidDocumentOpen(false),
     mHasDisplayDocument(false),
     mFontFaceSetDirty(true),
@@ -11784,16 +11778,18 @@ nsIDocument::DocAddSizeOfExcludingThis(n
   aSizes.mDOMMediaQueryLists +=
     mDOMMediaQueryLists.sizeOfExcludingThis(aSizes.mState.mMallocSizeOf);
 
   for (const MediaQueryList* mql : mDOMMediaQueryLists) {
     aSizes.mDOMMediaQueryLists +=
       mql->SizeOfExcludingThis(aSizes.mState.mMallocSizeOf);
   }
 
+  mContentBlockingLog.AddSizeOfExcludingThis(aSizes);
+
   // Measurement of the following members may be added later if DMD finds it
   // is worthwhile:
   // - many!
 }
 
 void
 nsIDocument::DocAddSizeOfIncludingThis(nsWindowSizes& aWindowSizes) const
 {
--- a/dom/base/nsGlobalWindowOuter.cpp
+++ b/dom/base/nsGlobalWindowOuter.cpp
@@ -5276,63 +5276,95 @@ nsGlobalWindowOuter::FirePopupBlockedEve
 
   event->SetTrusted(true);
 
   aDoc->DispatchEvent(*event);
 }
 
 void
 nsGlobalWindowOuter::NotifyContentBlockingState(unsigned aState,
-                                                nsIChannel* aChannel)
+                                                nsIChannel* aChannel,
+                                                bool aBlocked,
+                                                nsIURI* aURIHint)
 {
   nsCOMPtr<nsIDocShell> docShell = GetDocShell();
   if (!docShell) {
     return;
   }
   nsCOMPtr<nsIDocument> doc = docShell->GetDocument();
   NS_ENSURE_TRUE_VOID(doc);
 
   // This event might come after the user has navigated to another page.
   // To prevent showing the TrackingProtection UI on the wrong page, we need to
   // check that the loading URI for the channel is the same as the URI currently
   // loaded in the document.
-  if (!SameLoadingURI(doc, aChannel)) {
+  if (!SameLoadingURI(doc, aChannel) &&
+      aState == nsIWebProgressListener::STATE_BLOCKED_TRACKING_CONTENT) {
     return;
   }
 
   // Notify nsIWebProgressListeners of this security event.
   // Can be used to change the UI state.
   nsresult rv = NS_OK;
   nsCOMPtr<nsISecurityEventSink> eventSink = do_QueryInterface(docShell, &rv);
   NS_ENSURE_SUCCESS_VOID(rv);
   uint32_t state = 0;
   nsCOMPtr<nsISecureBrowserUI> securityUI;
   docShell->GetSecurityUI(getter_AddRefs(securityUI));
   if (!securityUI) {
     return;
   }
   securityUI->GetState(&state);
+  nsAutoString origin;
+  origin.SetIsVoid(true);
+  if (aURIHint) {
+    nsContentUtils::GetUTFOrigin(aURIHint, origin);
+  }
+  bool unblocked = false;
   if (aState == nsIWebProgressListener::STATE_BLOCKED_TRACKING_CONTENT) {
-    doc->SetHasTrackingContentBlocked(true);
+    doc->SetHasTrackingContentBlocked(aBlocked, origin);
+    if (!aBlocked) {
+      unblocked = !doc->GetHasTrackingContentBlocked();
+    }
   } else if (aState == nsIWebProgressListener::STATE_BLOCKED_SLOW_TRACKING_CONTENT) {
-    doc->SetHasSlowTrackingContentBlocked(true);
+    doc->SetHasSlowTrackingContentBlocked(aBlocked, origin);
+    if (!aBlocked) {
+      unblocked = !doc->GetHasSlowTrackingContentBlocked();
+    }
   } else if (aState == nsIWebProgressListener::STATE_COOKIES_BLOCKED_BY_PERMISSION) {
-    doc->SetHasCookiesBlockedByPermission(true);
+    doc->SetHasCookiesBlockedByPermission(aBlocked, origin);
+    if (!aBlocked) {
+      unblocked = !doc->GetHasCookiesBlockedByPermission();
+    }
   } else if (aState == nsIWebProgressListener::STATE_COOKIES_BLOCKED_TRACKER) {
-    doc->SetHasTrackingCookiesBlocked(true);
+    doc->SetHasTrackingCookiesBlocked(aBlocked, origin);
+    if (!aBlocked) {
+      unblocked = !doc->GetHasTrackingCookiesBlocked();
+    }
   } else if (aState == nsIWebProgressListener::STATE_COOKIES_BLOCKED_ALL) {
-    doc->SetHasAllCookiesBlocked(true);
+    doc->SetHasAllCookiesBlocked(aBlocked, origin);
+    if (!aBlocked) {
+      unblocked = !doc->GetHasAllCookiesBlocked();
+    }
   } else if (aState == nsIWebProgressListener::STATE_COOKIES_BLOCKED_FOREIGN) {
-    doc->SetHasForeignCookiesBlocked(true);
+    doc->SetHasForeignCookiesBlocked(aBlocked, origin);
+    if (!aBlocked) {
+      unblocked = !doc->GetHasForeignCookiesBlocked();
+    }
   } else {
     // Ignore nsIWebProgressListener::STATE_BLOCKED_UNSAFE_CONTENT;
   }
-  state |= aState;
-
-  eventSink->OnSecurityChange(aChannel, state);
+  const uint32_t oldState = state;
+  if (aBlocked) {
+    state |= aState;
+  } else if (unblocked) {
+    state &= ~aState;
+  }
+
+  eventSink->OnSecurityChange(aChannel, oldState, state, doc->GetContentBlockingLog());
 }
 
 //static
 bool
 nsGlobalWindowOuter::SameLoadingURI(nsIDocument *aDoc, nsIChannel *aChannel)
 {
   nsCOMPtr<nsIURI> docURI = aDoc->GetDocumentURI();
   nsCOMPtr<nsILoadInfo> channelLoadInfo = aChannel->GetLoadInfo();
--- a/dom/base/nsGlobalWindowOuter.h
+++ b/dom/base/nsGlobalWindowOuter.h
@@ -479,17 +479,19 @@ public:
   virtual void
   FirePopupBlockedEvent(nsIDocument* aDoc,
                         nsIURI* aPopupURI,
                         const nsAString& aPopupWindowName,
                         const nsAString& aPopupWindowFeatures) override;
 
   virtual void
   NotifyContentBlockingState(unsigned aState,
-                             nsIChannel* aChannel) override;
+                             nsIChannel* aChannel,
+                             bool aBlocked,
+                             nsIURI* aURIHint) override;
 
   virtual uint32_t GetSerial() override {
     return mSerial;
   }
 
   void AddSizeOfIncludingThis(nsWindowSizes& aWindowSizes) const;
 
   void AllowScriptsToClose()
--- a/dom/base/nsIDocument.h
+++ b/dom/base/nsIDocument.h
@@ -2,16 +2,17 @@
 /* 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/. */
 #ifndef nsIDocument_h___
 #define nsIDocument_h___
 
 #include "mozilla/FlushType.h"           // for enum
+#include "mozilla/Pair.h"                // for Pair
 #include "nsAutoPtr.h"                   // for member
 #include "nsCOMArray.h"                  // for member
 #include "nsCompatibility.h"             // for member
 #include "nsCOMPtr.h"                    // for member
 #include "nsGkAtoms.h"                   // for static class members
 #include "nsIApplicationCache.h"
 #include "nsIApplicationCacheContainer.h"
 #include "nsIContentViewer.h"
@@ -24,30 +25,32 @@
 #include "nsIPresShell.h"
 #include "nsIChannelEventSink.h"
 #include "nsIProgressEventSink.h"
 #include "nsISecurityEventSink.h"
 #include "nsIScriptGlobalObject.h"       // for member (in nsCOMPtr)
 #include "nsIServiceManager.h"
 #include "nsIURI.h"                      // for use in inline functions
 #include "nsIUUIDGenerator.h"
+#include "nsIWebProgressListener.h"      // for nsIWebProgressListener
 #include "nsPIDOMWindow.h"               // for use in inline functions
 #include "nsPropertyTable.h"             // for member
 #include "nsStringFwd.h"
 #include "nsTHashtable.h"                // for member
 #include "nsURIHashKey.h"
 #include "mozilla/net/ReferrerPolicy.h"  // for member
 #include "nsWeakReference.h"
 #include "mozilla/UseCounter.h"
 #include "mozilla/WeakPtr.h"
 #include "Units.h"
 #include "nsContentListDeclarations.h"
 #include "nsExpirationTracker.h"
 #include "nsClassHashtable.h"
 #include "mozilla/CORSMode.h"
+#include "mozilla/dom/ContentBlockingLog.h"
 #include "mozilla/dom/DispatcherTrait.h"
 #include "mozilla/dom/DocumentOrShadowRoot.h"
 #include "mozilla/EnumSet.h"
 #include "mozilla/LinkedList.h"
 #include "mozilla/NotNull.h"
 #include "mozilla/SegmentedVector.h"
 #include "mozilla/ServoBindingTypes.h"
 #include "mozilla/StyleSheet.h"
@@ -972,109 +975,141 @@ public:
    * Set unsafe-eval CSP flag for this document.
    */
   void SetHasUnsafeEvalCSP(bool aHasUnsafeEvalCSP)
   {
     mHasUnsafeEvalCSP = aHasUnsafeEvalCSP;
   }
 
   /**
+   * Get the content blocking log.
+   */
+  mozilla::dom::ContentBlockingLog* GetContentBlockingLog()
+  {
+    return &mContentBlockingLog;
+  }
+
+  /**
    * Get tracking content blocked flag for this document.
    */
   bool GetHasTrackingContentBlocked()
   {
-    return mHasTrackingContentBlocked;
+    return mContentBlockingLog.HasBlockedAnyOfType(
+        nsIWebProgressListener::STATE_BLOCKED_TRACKING_CONTENT);
   }
 
   /**
    * Get slow tracking content blocked flag for this document.
    */
   bool GetHasSlowTrackingContentBlocked()
   {
-    return mHasSlowTrackingContentBlocked;
+    return mContentBlockingLog.HasBlockedAnyOfType(
+        nsIWebProgressListener::STATE_BLOCKED_SLOW_TRACKING_CONTENT);
   }
 
   /**
    * Get all cookies blocked flag for this document.
    */
   bool GetHasAllCookiesBlocked()
   {
-    return mHasAllCookiesBlocked;
+    return mContentBlockingLog.HasBlockedAnyOfType(
+        nsIWebProgressListener::STATE_COOKIES_BLOCKED_ALL);
   }
 
   /**
    * Get tracking cookies blocked flag for this document.
    */
   bool GetHasTrackingCookiesBlocked()
   {
-    return mHasTrackingCookiesBlocked;
+    return mContentBlockingLog.HasBlockedAnyOfType(
+        nsIWebProgressListener::STATE_COOKIES_BLOCKED_TRACKER);
   }
 
   /**
    * Get third-party cookies blocked flag for this document.
    */
   bool GetHasForeignCookiesBlocked()
   {
-    return mHasForeignCookiesBlocked;
+    return mContentBlockingLog.HasBlockedAnyOfType(
+        nsIWebProgressListener::STATE_COOKIES_BLOCKED_FOREIGN);
   }
 
   /**
    * Get cookies blocked by site permission flag for this document.
    */
   bool GetHasCookiesBlockedByPermission()
   {
-    return mHasCookiesBlockedByPermission;
+    return mContentBlockingLog.HasBlockedAnyOfType(
+        nsIWebProgressListener::STATE_COOKIES_BLOCKED_BY_PERMISSION);
   }
 
   /**
    * Set the tracking content blocked flag for this document.
    */
-  void SetHasTrackingContentBlocked(bool aHasTrackingContentBlocked)
-  {
-    mHasTrackingContentBlocked = aHasTrackingContentBlocked;
+  void SetHasTrackingContentBlocked(bool aHasTrackingContentBlocked,
+                                    const nsAString& aOriginBlocked)
+  {
+    RecordContentBlockingLog(aOriginBlocked,
+                             nsIWebProgressListener::STATE_BLOCKED_TRACKING_CONTENT,
+                             aHasTrackingContentBlocked);
   }
 
   /**
    * Set the slow tracking content blocked flag for this document.
    */
-  void SetHasSlowTrackingContentBlocked(bool aHasSlowTrackingContentBlocked)
-  {
-    mHasSlowTrackingContentBlocked = aHasSlowTrackingContentBlocked;
+  void SetHasSlowTrackingContentBlocked(bool aHasSlowTrackingContentBlocked,
+                                        const nsAString& aOriginBlocked)
+  {
+    RecordContentBlockingLog(aOriginBlocked,
+                             nsIWebProgressListener::STATE_BLOCKED_SLOW_TRACKING_CONTENT,
+                             aHasSlowTrackingContentBlocked);
   }
 
   /**
    * Set the all cookies blocked flag for this document.
    */
-  void SetHasAllCookiesBlocked(bool aHasAllCookiesBlocked)
-  {
-    mHasAllCookiesBlocked = aHasAllCookiesBlocked;
+  void SetHasAllCookiesBlocked(bool aHasAllCookiesBlocked,
+                               const nsAString& aOriginBlocked)
+  {
+    RecordContentBlockingLog(aOriginBlocked,
+                             nsIWebProgressListener::STATE_COOKIES_BLOCKED_ALL,
+                             aHasAllCookiesBlocked);
   }
 
   /**
    * Set the tracking cookies blocked flag for this document.
    */
-  void SetHasTrackingCookiesBlocked(bool aHasTrackingCookiesBlocked)
-  {
-    mHasTrackingCookiesBlocked = aHasTrackingCookiesBlocked;
+  void SetHasTrackingCookiesBlocked(bool aHasTrackingCookiesBlocked,
+                                    const nsAString& aOriginBlocked)
+  {
+    RecordContentBlockingLog(aOriginBlocked,
+                             nsIWebProgressListener::STATE_COOKIES_BLOCKED_TRACKER,
+                             aHasTrackingCookiesBlocked);
   }
 
   /**
    * Set the third-party cookies blocked flag for this document.
    */
-  void SetHasForeignCookiesBlocked(bool aHasForeignCookiesBlocked)
-  {
-    mHasForeignCookiesBlocked = aHasForeignCookiesBlocked;
+  void SetHasForeignCookiesBlocked(bool aHasForeignCookiesBlocked,
+                                   const nsAString& aOriginBlocked)
+  {
+    RecordContentBlockingLog(aOriginBlocked,
+                             nsIWebProgressListener::STATE_COOKIES_BLOCKED_FOREIGN,
+                             aHasForeignCookiesBlocked);
   }
 
   /**
    * Set the cookies blocked by site permission flag for this document.
    */
-  void SetHasCookiesBlockedByPermission(bool aHasCookiesBlockedByPermission)
-  {
-    mHasCookiesBlockedByPermission = aHasCookiesBlockedByPermission;
+  void SetHasCookiesBlockedByPermission(bool aHasCookiesBlockedByPermission,
+                                        const nsAString& aOriginBlocked)
+  {
+    RecordContentBlockingLog(aOriginBlocked,
+                             nsIWebProgressListener::STATE_COOKIES_BLOCKED_BY_PERMISSION,
+                             aHasCookiesBlockedByPermission);
   }
 
   /**
    * Get tracking content loaded flag for this document.
    */
   bool GetHasTrackingContentLoaded()
   {
     return mHasTrackingContentLoaded;
@@ -3845,16 +3880,22 @@ protected:
   void NotifyStyleSheetApplicableStateChanged();
   // Just like EnableStyleSheetsForSet, but doesn't check whether
   // aSheetSet is null and allows the caller to control whether to set
   // aSheetSet as the preferred set in the CSSLoader.
   void EnableStyleSheetsForSetInternal(const nsAString& aSheetSet,
                                        bool aUpdateCSSLoader);
 
 private:
+  void RecordContentBlockingLog(const nsAString& aOrigin,
+                                uint32_t aType, bool aBlocked)
+  {
+    mContentBlockingLog.RecordLog(aOrigin, aType, aBlocked);
+  }
+
   mutable std::bitset<eDeprecatedOperationCount> mDeprecationWarnedAbout;
   mutable std::bitset<eDocumentWarningCount> mDocWarningWarnedAbout;
 
   // Lazy-initialization to have mDocGroup initialized in prior to the
   // SelectorCaches.
   mozilla::UniquePtr<SelectorCache> mSelectorCache;
 
 protected:
@@ -4144,34 +4185,16 @@ protected:
   bool mHasCSP : 1;
 
   // True if a document load has a CSP with unsafe-eval attached.
   bool mHasUnsafeEvalCSP : 1;
 
   // True if a document load has a CSP with unsafe-inline attached.
   bool mHasUnsafeInlineCSP : 1;
 
-  // True if a document has blocked Tracking Content
-  bool mHasTrackingContentBlocked : 1;
-
-  // True if a document has blocked Slow Tracking Content
-  bool mHasSlowTrackingContentBlocked : 1;
-
-  // True if a document has blocked All Cookies
-  bool mHasAllCookiesBlocked : 1;
-
-  // True if a document has blocked Tracking Cookies
-  bool mHasTrackingCookiesBlocked : 1;
-
-  // True if a document has blocked Foreign Cookies
-  bool mHasForeignCookiesBlocked : 1;
-
-  // True if a document has blocked Cookies By Site Permission
-  bool mHasCookiesBlockedByPermission : 1;
-
   // True if a document has loaded Tracking Content
   bool mHasTrackingContentLoaded : 1;
 
   // True if DisallowBFCaching has been called on this document.
   bool mBFCacheDisallowed : 1;
 
   bool mHasHadDefaultView : 1;
 
@@ -4514,16 +4537,21 @@ protected:
 
   RefPtr<mozilla::dom::DocGroup> mDocGroup;
 
   // The set of all the tracking script URLs.  URLs are added to this set by
   // calling NoteScriptTrackingStatus().  Currently we assume that a URL not
   // existing in the set means the corresponding script isn't a tracking script.
   nsTHashtable<nsCStringHashKey> mTrackingScripts;
 
+  // The log of all content blocking actions taken on this document.  This is only
+  // stored on top-level documents and includes the activity log for all of the
+  // nested subdocuments as well.
+  mozilla::dom::ContentBlockingLog mContentBlockingLog;
+
   // List of ancestor principals.  This is set at the point a document
   // is connected to a docshell and not mutated thereafter.
   nsTArray<nsCOMPtr<nsIPrincipal>> mAncestorPrincipals;
   // List of ancestor outerWindowIDs that correspond to the ancestor principals.
   nsTArray<uint64_t> mAncestorOuterWindowIDs;
 
   // Pointer to our parser if we're currently in the process of being
   // parsed into.
--- a/dom/base/nsPIDOMWindow.h
+++ b/dom/base/nsPIDOMWindow.h
@@ -1089,17 +1089,19 @@ public:
   virtual void
   FirePopupBlockedEvent(nsIDocument* aDoc,
                         nsIURI* aPopupURI,
                         const nsAString& aPopupWindowName,
                         const nsAString& aPopupWindowFeatures) = 0;
 
   virtual void
   NotifyContentBlockingState(unsigned aState,
-                             nsIChannel* aChannel) = 0;
+                             nsIChannel* aChannel,
+                             bool aBlocked = true,
+                             nsIURI* aURIHint = nullptr) = 0;
 
   // WebIDL-ish APIs
   void MarkUncollectableForCCGeneration(uint32_t aGeneration)
   {
     mMarkedCCGeneration = aGeneration;
   }
 
   uint32_t GetMarkedCCGeneration()
--- a/dom/bindings/test/test_dom_xrays.html
+++ b/dom/bindings/test/test_dom_xrays.html
@@ -27,27 +27,33 @@ function checkXrayProperty(obj, name, va
 {
   var instance = obj;
   do {
     var value = values.shift();
     if (typeof value == "undefined") {
       ok(!obj.hasOwnProperty(name), "hasOwnProperty shouldn't see \"" + name + "\" through Xrays");
       is(Object.getOwnPropertyDescriptor(obj, name), undefined, "getOwnPropertyDescriptor shouldn't see \"" + name + "\" through Xrays");
       ok(!Object.keys(obj).includes(name), "Enumerating the Xray should not return \"" + name + "\"");
+      ok(!Object.getOwnPropertyNames(obj).includes(name),
+         `The Xray's property names should not include ${name}`)
     } else {
       ok(obj.hasOwnProperty(name), "hasOwnProperty should see \"" + name + "\" through Xrays");
       var pd = Object.getOwnPropertyDescriptor(obj, name);
       ok(pd, "getOwnPropertyDescriptor should see \"" + name + "\" through Xrays");
       if (pd && pd.get) {
         is(pd.get.call(instance), value, "Should get the right value for \"" + name + "\" through Xrays");
       } else {
         is(obj[name], value, "Should get the right value for \"" + name + "\" through Xrays");
       }
-      if (pd && pd.enumerable) {
-        ok(Object.keys(obj).indexOf("" + name) > -1, "Enumerating the Xray should return \"" + name + "\"");
+      if (pd) {
+        if (pd.enumerable) {
+          ok(Object.keys(obj).indexOf("" + name) > -1, "Enumerating the Xray should return \"" + name + "\"");
+        }
+        ok(Object.getOwnPropertyNames(obj).indexOf("" + name) > -1,
+           `The Xray's property names should include ${name}`)
       }
     }
   } while ((obj = Object.getPrototypeOf(obj)));
 }
 
 function checkWindowXrayProperty(obj, name, windowValue, windowPrototypeValue, namedPropertiesValue, eventTargetValue)
 {
   checkXrayProperty(obj, name, [ windowValue, windowPrototypeValue, namedPropertiesValue, eventTargetValue ]);
@@ -96,16 +102,23 @@ function test()
   is(obj, win.Object.prototype, "Object.prototype should be at the end of the prototype chain");
 
   // Named properties shouldn't shadow WebIDL- or ECMAScript-defined properties.
   checkWindowXrayProperty(win, "addEventListener", undefined, undefined, undefined, eventTargetProto.addEventListener);
   is(win.addEventListener, eventTargetProto.addEventListener, "Named properties shouldn't shadow WebIDL-defined properties");
 
   is(win.toString, win.Object.prototype.toString, "Named properties shouldn't shadow ECMAScript-defined properties");
 
+  // WebIDL interface names should be exposed.
+  var waivedWin = Cu.waiveXrays(win);
+  checkWindowXrayProperty(win, "Element", Cu.unwaiveXrays(waivedWin.Element))
+
+  // JS standard classes should be exposed.
+  checkWindowXrayProperty(win, "Array", Cu.unwaiveXrays(waivedWin.Array))
+
   // HTMLDocument
   // Unforgeable properties live on the instance.
   checkXrayProperty(doc, "location", [ win.location ]);
   is(String(win.location), document.getElementById("t").src,
      "Should have the right stringification");
 
   // HTMLHtmlElement
   var elem = doc.documentElement;
--- a/dom/browser-element/BrowserElementChildPreload.js
+++ b/dom/browser-element/BrowserElementChildPreload.js
@@ -1534,17 +1534,18 @@ BrowserElementChild.prototype = {
             } catch (e) {}
 
             sendAsyncMsg('error', { type: 'other' });
             return;
         }
       }
     },
 
-    onSecurityChange: function(webProgress, request, state) {
+    onSecurityChange: function(webProgress, request, oldState, state,
+                               contentBlockingLogJSON) {
       if (webProgress != docShell) {
         return;
       }
 
       var securityStateDesc;
       if (state & Ci.nsIWebProgressListener.STATE_IS_SECURE) {
         securityStateDesc = 'secure';
       }
--- a/dom/clients/manager/ClientNavigateOpChild.cpp
+++ b/dom/clients/manager/ClientNavigateOpChild.cpp
@@ -123,17 +123,18 @@ public:
                  nsresult aStatus, const char16_t* aMessage) override
   {
     MOZ_CRASH("Unexpected notification.");
     return NS_OK;
   }
 
   NS_IMETHOD
   OnSecurityChange(nsIWebProgress* aWebProgress, nsIRequest* aRequest,
-                   uint32_t aState) override
+                   uint32_t aOldState, uint32_t aState,
+                   const nsAString& aContentBlockingLogJSON) override
   {
     MOZ_CRASH("Unexpected notification.");
     return NS_OK;
   }
 
   NS_DECL_ISUPPORTS
 };
 
--- a/dom/clients/manager/ClientOpenWindowUtils.cpp
+++ b/dom/clients/manager/ClientOpenWindowUtils.cpp
@@ -128,17 +128,19 @@ public:
   {
     MOZ_ASSERT(false, "Unexpected notification.");
     return NS_OK;
   }
 
   NS_IMETHOD
   OnSecurityChange(nsIWebProgress* aWebProgress,
                    nsIRequest* aRequest,
-                   uint32_t aState) override
+                   uint32_t aOldState,
+                   uint32_t aState,
+                   const nsAString& aContentBlockingLogJSON) override
   {
     MOZ_ASSERT(false, "Unexpected notification.");
     return NS_OK;
   }
 
 private:
   ~WebProgressListener()
   {
--- a/dom/html/HTMLFormElement.cpp
+++ b/dom/html/HTMLFormElement.cpp
@@ -2126,17 +2126,19 @@ HTMLFormElement::OnStatusChange(nsIWebPr
 {
   MOZ_ASSERT_UNREACHABLE("notification excluded in AddProgressListener(...)");
   return NS_OK;
 }
 
 NS_IMETHODIMP
 HTMLFormElement::OnSecurityChange(nsIWebProgress* aWebProgress,
                                   nsIRequest* aRequest,
-                                  uint32_t state)
+                                  uint32_t aOldState,
+                                  uint32_t aState,
+                                  const nsAString& aContentBlockingLogJSON)
 {
   MOZ_ASSERT_UNREACHABLE("notification excluded in AddProgressListener(...)");
   return NS_OK;
 }
 
 NS_IMETHODIMP_(int32_t)
 HTMLFormElement::IndexOfControl(nsIFormControl* aControl)
 {
--- a/dom/html/nsHTMLDNSPrefetch.cpp
+++ b/dom/html/nsHTMLDNSPrefetch.cpp
@@ -572,17 +572,19 @@ nsHTMLDNSPrefetch::nsDeferrals::OnStatus
                                                const char16_t* aMessage)
 {
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsHTMLDNSPrefetch::nsDeferrals::OnSecurityChange(nsIWebProgress *aWebProgress,
                                                  nsIRequest *aRequest,
-                                                 uint32_t state)
+                                                 uint32_t aOldState,
+                                                 uint32_t aState,
+                                                 const nsAString& aContentBlockingLogJSON)
 {
   return NS_OK;
 }
 
 //////////// nsIObserver method
 
 NS_IMETHODIMP
 nsHTMLDNSPrefetch::nsDeferrals::Observe(nsISupports *subject,
--- a/dom/presentation/PresentationCallbacks.cpp
+++ b/dom/presentation/PresentationCallbacks.cpp
@@ -268,13 +268,15 @@ PresentationResponderLoadingCallback::On
 {
   // Do nothing.
   return NS_OK;
 }
 
 NS_IMETHODIMP
 PresentationResponderLoadingCallback::OnSecurityChange(nsIWebProgress* aWebProgress,
                                                        nsIRequest* aRequest,
-                                                       uint32_t state)
+                                                       uint32_t aOldState,
+                                                       uint32_t aState,
+                                                       const nsAString& aContentBlockingLogJSON)
 {
   // Do nothing.
   return NS_OK;
 }
--- a/dom/security/nsContentSecurityManager.cpp
+++ b/dom/security/nsContentSecurityManager.cpp
@@ -14,25 +14,30 @@
 #include "nsILoadInfo.h"
 #include "nsIOService.h"
 #include "nsContentUtils.h"
 #include "nsCORSListenerProxy.h"
 #include "nsIStreamListener.h"
 #include "nsCDefaultURIFixup.h"
 #include "nsIURIFixup.h"
 #include "nsIImageLoadingContent.h"
+#include "nsIRedirectHistoryEntry.h"
 
 #include "mozilla/dom/Element.h"
 #include "mozilla/dom/nsMixedContentBlocker.h"
 #include "mozilla/dom/TabChild.h"
+#include "mozilla/Logging.h"
+
 
 NS_IMPL_ISUPPORTS(nsContentSecurityManager,
                   nsIContentSecurityManager,
                   nsIChannelEventSink)
 
+static mozilla::LazyLogModule sCSMLog("CSMLog");
+
 /* static */ bool
 nsContentSecurityManager::AllowTopLevelNavigationToDataURI(nsIChannel* aChannel)
 {
   // Let's block all toplevel document navigations to a data: URI.
   // In all cases where the toplevel document is navigated to a
   // data: URI the triggeringPrincipal is a codeBasePrincipal, or
   // a NullPrincipal. In other cases, e.g. typing a data: URL into
   // the URL-Bar, the triggeringPrincipal is a SystemPrincipal;
@@ -612,16 +617,178 @@ DoContentSecurityChecks(nsIChannel* aCha
       return NS_ERROR_CONTENT_BLOCKED_SHOW_ALT;
     }
     return NS_ERROR_CONTENT_BLOCKED;
   }
 
   return NS_OK;
 }
 
+static void
+LogPrincipal(nsIPrincipal* aPrincipal, const nsAString& aPrincipalName) {
+  if (nsContentUtils::IsSystemPrincipal(aPrincipal)) {
+    MOZ_LOG(sCSMLog, LogLevel::Debug, ("  %s: SystemPrincipal\n",
+      NS_ConvertUTF16toUTF8(aPrincipalName).get()));
+    return;
+  }
+  if (aPrincipal) {
+    if (aPrincipal->GetIsNullPrincipal()) {
+      MOZ_LOG(sCSMLog, LogLevel::Debug, ("  %s: NullPrincipal\n",
+       NS_ConvertUTF16toUTF8(aPrincipalName).get()));
+      return;
+    }
+    if (aPrincipal->GetIsExpandedPrincipal()) {
+      MOZ_LOG(sCSMLog, LogLevel::Debug, ("  %s: Expanded Principal\n",
+        NS_ConvertUTF16toUTF8(aPrincipalName).get()));
+      return;
+    }
+    nsCOMPtr<nsIURI> principalURI;
+    nsAutoCString principalSpec;
+    aPrincipal->GetURI(getter_AddRefs(principalURI));
+    if (principalURI) {
+      principalURI->GetSpec(principalSpec);
+    }
+    MOZ_LOG(sCSMLog, LogLevel::Debug, ("  %s: %s\n",
+     NS_ConvertUTF16toUTF8(aPrincipalName).get(), principalSpec.get()));
+    return;
+  }
+  MOZ_LOG(sCSMLog, LogLevel::Debug, ("  %s: nullptr\n",
+   NS_ConvertUTF16toUTF8(aPrincipalName).get()));
+}
+
+static void
+LogSecurityFlags(nsSecurityFlags securityFlags) {
+  struct DebugSecFlagType {
+    unsigned long secFlag;
+    char secTypeStr[128];
+  };
+  static const DebugSecFlagType secTypes[] =
+{
+  { nsILoadInfo::SEC_ONLY_FOR_EXPLICIT_CONTENTSEC_CHECK,
+    "SEC_ONLY_FOR_EXPLICIT_CONTENTSEC_CHECK" },
+  { nsILoadInfo::SEC_REQUIRE_SAME_ORIGIN_DATA_INHERITS,
+    "SEC_REQUIRE_SAME_ORIGIN_DATA_INHERITS" },
+  { nsILoadInfo::SEC_REQUIRE_SAME_ORIGIN_DATA_IS_BLOCKED,
+    "SEC_REQUIRE_SAME_ORIGIN_DATA_IS_BLOCKED" },
+  { nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_INHERITS,
+    "SEC_ALLOW_CROSS_ORIGIN_DATA_INHERITS" },
+  { nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL,
+    "SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL" },
+  { nsILoadInfo::SEC_REQUIRE_CORS_DATA_INHERITS,
+    "SEC_REQUIRE_CORS_DATA_INHERITS" },
+  { nsILoadInfo::SEC_COOKIES_DEFAULT,
+    "SEC_COOKIES_DEFAULT" },
+  { nsILoadInfo::SEC_COOKIES_INCLUDE,
+    "SEC_COOKIES_INCLUDE" },
+  { nsILoadInfo::SEC_COOKIES_SAME_ORIGIN,
+    "SEC_COOKIES_SAME_ORIGIN" },
+  { nsILoadInfo::SEC_COOKIES_OMIT,
+    "SEC_COOKIES_OMIT" },
+  { nsILoadInfo::SEC_FORCE_INHERIT_PRINCIPAL,
+    "SEC_FORCE_INHERIT_PRINCIPAL" },
+  { nsILoadInfo::SEC_SANDBOXED,
+    "SEC_SANDBOXED" },
+  { nsILoadInfo::SEC_ABOUT_BLANK_INHERITS,
+    "SEC_ABOUT_BLANK_INHERITS" },
+  { nsILoadInfo::SEC_ALLOW_CHROME,
+    "SEC_ALLOW_CHROME" },
+  { nsILoadInfo::SEC_DISALLOW_SCRIPT,
+    "SEC_DISALLOW_SCRIPT" },
+  { nsILoadInfo::SEC_DONT_FOLLOW_REDIRECTS,
+    "SEC_DONT_FOLLOW_REDIRECTS" },
+  { nsILoadInfo::SEC_LOAD_ERROR_PAGE,
+    "SEC_LOAD_ERROR_PAGE" },
+  { nsILoadInfo::SEC_FORCE_INHERIT_PRINCIPAL_OVERRULE_OWNER,
+    "SEC_FORCE_INHERIT_PRINCIPAL_OVERRULE_OWNER" }
+  };
+
+for (const DebugSecFlagType flag : secTypes) {
+  if (securityFlags & flag.secFlag) {
+      MOZ_LOG(sCSMLog, LogLevel::Debug, ("    %s,\n", flag.secTypeStr));
+    }
+  }
+}
+static void
+DebugDoContentSecurityCheck(nsIChannel* aChannel, nsILoadInfo* aLoadInfo) {
+  nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(aChannel));
+
+    // we only log http channels, unless loglevel is 5.
+  if (httpChannel || MOZ_LOG_TEST(sCSMLog, LogLevel::Verbose)) {
+    nsCOMPtr<nsIURI> channelURI;
+    nsAutoCString channelSpec;
+    nsAutoCString channelMethod;
+    NS_GetFinalChannelURI(aChannel, getter_AddRefs(channelURI));
+    if (channelURI) {
+      channelURI->GetSpec(channelSpec);
+    }
+
+    MOZ_LOG(sCSMLog, LogLevel::Debug, ("doContentSecurityCheck {\n"));
+    MOZ_LOG(sCSMLog, LogLevel::Debug, ("  channelURI: %s\n",
+     channelSpec.get()));
+
+    // Log HTTP-specific things
+    if (httpChannel) {
+      nsresult rv;
+      rv = httpChannel->GetRequestMethod(channelMethod);
+      if (!NS_FAILED(rv)) {
+        MOZ_LOG(sCSMLog, LogLevel::Debug, ("  HTTP Method: %s\n",
+         channelMethod.get()));
+      }
+    }
+
+    // Log Principals
+    nsCOMPtr<nsIPrincipal> requestPrincipal = aLoadInfo->TriggeringPrincipal();
+    LogPrincipal(aLoadInfo->LoadingPrincipal(),
+     NS_LITERAL_STRING("loadingPrincipal"));
+    LogPrincipal(requestPrincipal, NS_LITERAL_STRING("triggeringPrincipal"));
+    LogPrincipal(aLoadInfo->PrincipalToInherit(),
+      NS_LITERAL_STRING("principalToInherit"));
+
+    // Log Redirect Chain
+    MOZ_LOG(sCSMLog, LogLevel::Debug, ("  RedirectChain:\n"));
+    for (nsIRedirectHistoryEntry* redirectHistoryEntry :
+     aLoadInfo->RedirectChain()) {
+      nsCOMPtr<nsIPrincipal> principal;
+      redirectHistoryEntry->GetPrincipal(getter_AddRefs(principal));
+      LogPrincipal(principal, NS_LITERAL_STRING("->"));
+    }
+
+    MOZ_LOG(sCSMLog, LogLevel::Debug, ("  internalContentPolicyType: %d\n",
+     aLoadInfo->InternalContentPolicyType()));
+    MOZ_LOG(sCSMLog, LogLevel::Debug, ("  externalContentPolicyType: %d\n",
+     aLoadInfo->GetExternalContentPolicyType()));
+    MOZ_LOG(sCSMLog, LogLevel::Debug, ("  upgradeInsecureRequests: %s\n",
+     aLoadInfo->GetUpgradeInsecureRequests() ? "true" : "false"));
+    MOZ_LOG(sCSMLog, LogLevel::Debug, ("  initalSecurityChecksDone: %s\n",
+     aLoadInfo->GetInitialSecurityCheckDone() ? "true" : "false"));
+    MOZ_LOG(sCSMLog, LogLevel::Debug, ("  enforceSecurity: %s\n",
+     aLoadInfo->GetEnforceSecurity() ? "true" : "false"));
+
+    // Log CSPrequestPrincipal
+    nsCOMPtr<nsIContentSecurityPolicy> csp;
+    requestPrincipal->GetCsp(getter_AddRefs(csp));
+    if (csp) {
+      nsAutoString parsedPolicyStr;
+      uint32_t count = 0;
+      csp->GetPolicyCount(&count);
+      MOZ_LOG(sCSMLog, LogLevel::Debug, ("  CSP (%d): ", count));
+      for (uint32_t i = 0; i < count; ++i) {
+        csp->GetPolicyString(i, parsedPolicyStr);
+        MOZ_LOG(sCSMLog, LogLevel::Debug, ("    %s\n",
+         NS_ConvertUTF16toUTF8(parsedPolicyStr).get()));
+      }
+    }
+
+    // Security Flags
+    MOZ_LOG(sCSMLog, LogLevel::Debug, ("  securityFlags: "));
+    LogSecurityFlags(aLoadInfo->GetSecurityFlags());
+    MOZ_LOG(sCSMLog, LogLevel::Debug, ("}\n\n"));
+  }
+}
+
 /*
  * Based on the security flags provided in the loadInfo of the channel,
  * doContentSecurityCheck() performs the following content security checks
  * before opening the channel:
  *
  * (1) Same Origin Policy Check (if applicable)
  * (2) Allow Cross Origin but perform sanity checks whether a principal
  *     is allowed to access the following URL.
@@ -642,16 +809,20 @@ nsContentSecurityManager::doContentSecur
   NS_ENSURE_ARG(aChannel);
   nsCOMPtr<nsILoadInfo> loadInfo = aChannel->GetLoadInfo();
 
   if (!loadInfo) {
     MOZ_ASSERT(false, "channel needs to have loadInfo to perform security checks");
     return NS_ERROR_UNEXPECTED;
   }
 
+  if (MOZ_UNLIKELY(MOZ_LOG_TEST(sCSMLog, LogLevel::Debug))) {
+    DebugDoContentSecurityCheck(aChannel, loadInfo);
+  }
+
   // if dealing with a redirected channel then we have already installed
   // streamlistener and redirect proxies and so we are done.
   if (loadInfo->GetInitialSecurityCheckDone()) {
     return NS_OK;
   }
 
   // make sure that only one of the five security flags is set in the loadinfo
   // e.g. do not require same origin and allow cross origin at the same time
--- a/dom/security/nsMixedContentBlocker.cpp
+++ b/dom/security/nsMixedContentBlocker.cpp
@@ -103,16 +103,17 @@ public:
     }
     nsCOMPtr<nsIDocShellTreeItem> sameTypeRoot;
     docShell->GetSameTypeRootTreeItem(getter_AddRefs(sameTypeRoot));
     NS_ASSERTION(sameTypeRoot, "No document shell root tree item from document shell tree item!");
 
     // now get the document from sameTypeRoot
     nsCOMPtr<nsIDocument> rootDoc = sameTypeRoot->GetDocument();
     NS_ASSERTION(rootDoc, "No root document from document shell root tree item.");
+    ContentBlockingLog* contentBlockingLog = rootDoc->GetContentBlockingLog();
 
     // Get eventSink and the current security state from the docShell
     nsCOMPtr<nsISecurityEventSink> eventSink = do_QueryInterface(docShell);
     NS_ASSERTION(eventSink, "No eventSink from docShell.");
     nsCOMPtr<nsIDocShell> rootShell = do_GetInterface(sameTypeRoot);
     NS_ASSERTION(rootShell, "No root docshell from document shell root tree item.");
     uint32_t state = nsIWebProgressListener::STATE_IS_BROKEN;
     nsCOMPtr<nsISecureBrowserUI> securityUI;
@@ -142,22 +143,25 @@ public:
           // set state security flag to broken, since there is mixed content
           state |= nsIWebProgressListener::STATE_IS_BROKEN;
 
           // If mixed display content is loaded, make sure to include that in the state.
           if (rootDoc->GetHasMixedDisplayContentLoaded()) {
             state |= nsIWebProgressListener::STATE_LOADED_MIXED_DISPLAY_CONTENT;
           }
 
-          eventSink->OnSecurityChange(mContext,
-                                      (state | nsIWebProgressListener::STATE_LOADED_MIXED_ACTIVE_CONTENT));
+          eventSink->OnSecurityChange(mContext, state,
+                                      (state | nsIWebProgressListener::STATE_LOADED_MIXED_ACTIVE_CONTENT),
+                                      contentBlockingLog);
         } else {
           // root not secure, mixed active content loaded in an https subframe
           if (NS_SUCCEEDED(stateRV)) {
-            eventSink->OnSecurityChange(mContext, (state | nsIWebProgressListener::STATE_LOADED_MIXED_ACTIVE_CONTENT));
+            eventSink->OnSecurityChange(mContext, state,
+                                        (state | nsIWebProgressListener::STATE_LOADED_MIXED_ACTIVE_CONTENT),
+                                        contentBlockingLog);
           }
         }
       }
 
     } else if (mType == eMixedDisplay) {
       // See if the pref will change here. If it will, only then do we need to call OnSecurityChange() to update the UI.
       if (rootDoc->GetHasMixedDisplayContentLoaded()) {
         return NS_OK;
@@ -174,22 +178,25 @@ public:
           // set state security flag to broken, since there is mixed content
           state |= nsIWebProgressListener::STATE_IS_BROKEN;
 
           // If mixed active content is loaded, make sure to include that in the state.
           if (rootDoc->GetHasMixedActiveContentLoaded()) {
             state |= nsIWebProgressListener::STATE_LOADED_MIXED_ACTIVE_CONTENT;
           }
 
-          eventSink->OnSecurityChange(mContext,
-                                      (state | nsIWebProgressListener::STATE_LOADED_MIXED_DISPLAY_CONTENT));
+          eventSink->OnSecurityChange(mContext, state,
+                                      (state | nsIWebProgressListener::STATE_LOADED_MIXED_DISPLAY_CONTENT),
+                                      contentBlockingLog);
         } else {
           // root not secure, mixed display content loaded in an https subframe
           if (NS_SUCCEEDED(stateRV)) {
-            eventSink->OnSecurityChange(mContext, (state | nsIWebProgressListener::STATE_LOADED_MIXED_DISPLAY_CONTENT));
+            eventSink->OnSecurityChange(mContext, state,
+                                        (state | nsIWebProgressListener::STATE_LOADED_MIXED_DISPLAY_CONTENT),
+                                        contentBlockingLog);
           }
         }
       }
     }
 
     return NS_OK;
   }
 private:
@@ -877,16 +884,17 @@ nsMixedContentBlocker::ShouldLoad(bool a
       *aDecision = nsIContentPolicy::ACCEPT;
       return NS_OK;
     }
   }
 
   // Get the root document from the sameTypeRoot
   nsCOMPtr<nsIDocument> rootDoc = sameTypeRoot->GetDocument();
   NS_ASSERTION(rootDoc, "No root document from document shell root tree item.");
+  ContentBlockingLog* contentBlockingLog = rootDoc->GetContentBlockingLog();
 
   // Get eventSink and the current security state from the docShell
   nsCOMPtr<nsISecurityEventSink> eventSink = do_QueryInterface(docShell);
   NS_ASSERTION(eventSink, "No eventSink from docShell.");
   nsCOMPtr<nsIDocShell> rootShell = do_GetInterface(sameTypeRoot);
   NS_ASSERTION(rootShell, "No root docshell from document shell root tree item.");
   uint32_t state = nsIWebProgressListener::STATE_IS_BROKEN;
   nsCOMPtr<nsISecureBrowserUI> securityUI;
@@ -962,31 +970,36 @@ nsMixedContentBlocker::ShouldLoad(bool a
         // set state security flag to broken, since there is mixed content
         state |= nsIWebProgressListener::STATE_IS_BROKEN;
 
         // If mixed active content is loaded, make sure to include that in the state.
         if (rootDoc->GetHasMixedActiveContentLoaded()) {
           state |= nsIWebProgressListener::STATE_LOADED_MIXED_ACTIVE_CONTENT;
         }
 
-        eventSink->OnSecurityChange(aRequestingContext,
-                                    (state | nsIWebProgressListener::STATE_LOADED_MIXED_DISPLAY_CONTENT));
+        eventSink->OnSecurityChange(aRequestingContext, state,
+                                    (state | nsIWebProgressListener::STATE_LOADED_MIXED_DISPLAY_CONTENT),
+                                    contentBlockingLog);
       } else {
         // User has overriden the pref and the root is not https;
         // mixed display content was allowed on an https subframe.
         if (NS_SUCCEEDED(stateRV)) {
-          eventSink->OnSecurityChange(aRequestingContext, (state | nsIWebProgressListener::STATE_LOADED_MIXED_DISPLAY_CONTENT));
+          eventSink->OnSecurityChange(aRequestingContext, state,
+                                      (state | nsIWebProgressListener::STATE_LOADED_MIXED_DISPLAY_CONTENT),
+                                      contentBlockingLog);
         }
       }
     } else {
       *aDecision = nsIContentPolicy::REJECT_REQUEST;
       LogMixedContentMessage(classification, aContentLocation, rootDoc, eBlocked);
       if (!rootDoc->GetHasMixedDisplayContentBlocked() && NS_SUCCEEDED(stateRV)) {
         rootDoc->SetHasMixedDisplayContentBlocked(true);
-        eventSink->OnSecurityChange(aRequestingContext, (state | nsIWebProgressListener::STATE_BLOCKED_MIXED_DISPLAY_CONTENT));
+        eventSink->OnSecurityChange(aRequestingContext, state,
+                                    (state | nsIWebProgressListener::STATE_BLOCKED_MIXED_DISPLAY_CONTENT),
+                                    contentBlockingLog);
       }
     }
     return NS_OK;
 
   } else if (sBlockMixedScript && classification == eMixedScript) {
     // If the content is active content, and the pref says active content should be blocked, block it
     // unless the user has choosen to override the pref
     if (allowMixedContent) {
@@ -1004,42 +1017,47 @@ nsMixedContentBlocker::ShouldLoad(bool a
         // set state security flag to broken, since there is mixed content
         state |= nsIWebProgressListener::STATE_IS_BROKEN;
 
         // If mixed display content is loaded, make sure to include that in the state.
         if (rootDoc->GetHasMixedDisplayContentLoaded()) {
           state |= nsIWebProgressListener::STATE_LOADED_MIXED_DISPLAY_CONTENT;
         }
 
-        eventSink->OnSecurityChange(aRequestingContext,
-                                    (state | nsIWebProgressListener::STATE_LOADED_MIXED_ACTIVE_CONTENT));
+        eventSink->OnSecurityChange(aRequestingContext, state,
+                                    (state | nsIWebProgressListener::STATE_LOADED_MIXED_ACTIVE_CONTENT),
+                                    contentBlockingLog);
 
         return NS_OK;
       } else {
         // User has already overriden the pref and the root is not https;
         // mixed active content was allowed on an https subframe.
         if (NS_SUCCEEDED(stateRV)) {
-          eventSink->OnSecurityChange(aRequestingContext, (state | nsIWebProgressListener::STATE_LOADED_MIXED_ACTIVE_CONTENT));
+          eventSink->OnSecurityChange(aRequestingContext, state,
+                                      (state | nsIWebProgressListener::STATE_LOADED_MIXED_ACTIVE_CONTENT),
+                                      contentBlockingLog);
         }
         return NS_OK;
       }
     } else {
       //User has not overriden the pref by Disabling protection. Reject the request and update the security state.
       *aDecision = nsIContentPolicy::REJECT_REQUEST;
       LogMixedContentMessage(classification, aContentLocation, rootDoc, eBlocked);
       // See if the pref will change here. If it will, only then do we need to call OnSecurityChange() to update the UI.
       if (rootDoc->GetHasMixedActiveContentBlocked()) {
         return NS_OK;
       }
       rootDoc->SetHasMixedActiveContentBlocked(true);
 
       // The user has not overriden the pref, so make sure they still have an option by calling eventSink
       // which will invoke the doorhanger
       if (NS_SUCCEEDED(stateRV)) {
-         eventSink->OnSecurityChange(aRequestingContext, (state | nsIWebProgressListener::STATE_BLOCKED_MIXED_ACTIVE_CONTENT));
+         eventSink->OnSecurityChange(aRequestingContext, state,
+                                     (state | nsIWebProgressListener::STATE_BLOCKED_MIXED_ACTIVE_CONTENT),
+                                     contentBlockingLog);
       }
       return NS_OK;
     }
   } else {
     // The content is not blocked by the mixed content prefs.
 
     // Log a message that we are loading mixed content.
     LogMixedContentMessage(classification, aContentLocation, rootDoc, eUserOverride);
--- a/dom/tests/mochitest/general/test_interfaces.js
+++ b/dom/tests/mochitest/general/test_interfaces.js
@@ -49,29 +49,26 @@ var ecmaGlobals =
     {name: "CountQueuingStrategy", insecureContext: true, disabled: true},
     {name: "DataView", insecureContext: true},
     {name: "Date", insecureContext: true},
     {name: "Error", insecureContext: true},
     {name: "EvalError", insecureContext: true},
     {name: "Float32Array", insecureContext: true},
     {name: "Float64Array", insecureContext: true},
     {name: "Function", insecureContext: true},
-    // NB: We haven't bothered to resolve constants like Infinity and NaN on
-    // Xrayed windows (which are seen from the XBL scope). We could support
-    // this if needed with some refactoring.
-    {name: "Infinity", insecureContext: true, xbl: false},
+    {name: "Infinity", insecureContext: true},
     {name: "Int16Array", insecureContext: true},
     {name: "Int32Array", insecureContext: true},
     {name: "Int8Array", insecureContext: true},
     {name: "InternalError", insecureContext: true},
     {name: "Intl", insecureContext: true},
     {name: "JSON", insecureContext: true},
     {name: "Map", insecureContext: true},
     {name: "Math", insecureContext: true},
-    {name: "NaN", insecureContext: true, xbl: false},
+    {name: "NaN", insecureContext: true},
     {name: "Number", insecureContext: true},
     {name: "Object", insecureContext: true},
     {name: "Promise", insecureContext: true},
     {name: "Proxy", insecureContext: true},
     {name: "RangeError", insecureContext: true},
     {name: "ReadableStream", insecureContext: true, disabled: true},
     {name: "ReferenceError", insecureContext: true},
     {name: "Reflect", insecureContext: true},
@@ -1318,28 +1315,29 @@ function runTest(isXBLScope) {
     // we want to allow those until we can remove them.
     if (!/^[A-Z]/.test(name) && !legacyMozPrefixedInterfaces.includes(name)) {
       continue;
     }
     ok(interfaceMap[name],
        "If this is failing: DANGER, are you sure you want to expose the new interface " + name +
        " to all webpages as a property on the window (XBL: " + isXBLScope + ")? Do not make a change to this file without a " +
        " review from a DOM peer for that specific change!!! (or a JS peer for changes to ecmaGlobals)");
+
+    ok(name in window,
+       `${name} is exposed as an own property on the window but tests false for "in" in the ${isXBLScope ? "XBL" : "global"} scope`);
+    ok(Object.getOwnPropertyDescriptor(window, name),
+       `${name} is exposed as an own property on the window but has no property descriptor in the ${isXBLScope ? "XBL" : "global"} scope`);
+
     delete interfaceMap[name];
   }
   for (var name of Object.keys(interfaceMap)) {
     ok(name in window === interfaceMap[name],
        name + " should " + (interfaceMap[name] ? "" : " NOT") + " be defined on the " + (isXBLScope ? "XBL" : "global") +" scope");
     if (!interfaceMap[name]) {
       delete interfaceMap[name];
     }
   }
-  if (isXBLScope) {
-    todo_is(Object.keys(interfaceMap).length, 0,
-            "The following interface(s) are not enumerated: " + Object.keys(interfaceMap).join(", "));
-  } else {
-    is(Object.keys(interfaceMap).length, 0,
-       "The following interface(s) are not enumerated: " + Object.keys(interfaceMap).join(", "));
-  }
+  is(Object.keys(interfaceMap).length, 0,
+     "The following interface(s) are not enumerated: " + Object.keys(interfaceMap).join(", "));
 }
 
 runTest(false);
 SimpleTest.waitForExplicitFinish();
--- a/dom/workers/test/mochitest.ini
+++ b/dom/workers/test/mochitest.ini
@@ -192,9 +192,10 @@ scheme=https
 [test_navigator_workers_hardwareConcurrency.html]
 [test_bug1278777.html]
 [test_setTimeoutWith0.html]
 [test_bug1301094.html]
 [test_subworkers_suspended.html]
 skip-if = toolkit == 'android' #bug 1366501
 [test_bug1317725.html]
 [test_sharedworker_event_listener_leaks.html]
+skip-if = (bits == 64 && os == 'linux' && asan && !debug) # Disabled on Linux64 opt asan, bug 1493563
 [test_fileReaderSync_when_closing.html]
--- a/editor/composer/nsEditingSession.cpp
+++ b/editor/composer/nsEditingSession.cpp
@@ -808,17 +808,20 @@ nsEditingSession::OnStatusChange(nsIWebP
 
 /*---------------------------------------------------------------------------
 
   OnSecurityChange
 
 ----------------------------------------------------------------------------*/
 NS_IMETHODIMP
 nsEditingSession::OnSecurityChange(nsIWebProgress *aWebProgress,
-                                   nsIRequest *aRequest, uint32_t state)
+                                   nsIRequest *aRequest,
+                                   uint32_t aOldState,
+                                   uint32_t aState,
+                                   const nsAString& aContentBlockingLogJSON)
 {
     MOZ_ASSERT_UNREACHABLE("notification excluded in AddProgressListener(...)");
     return NS_OK;
 }
 
 
 /*---------------------------------------------------------------------------
 
--- a/editor/composer/test/test_bug434998.xul
+++ b/editor/composer/test/test_bug434998.xul
@@ -82,17 +82,18 @@ https://bugzilla.mozilla.org/show_bug.cg
     onLocationChange : function(aWebProgress, aRequest, aLocation, aFlags)
     {
     },
 
     onStatusChange : function(aWebProgress, aRequest, aStatus, aMessage)
     {
     },
 
-    onSecurityChange : function(aWebProgress, aRequest, aState)
+    onSecurityChange : function(aWebProgress, aRequest, aOldState, aState,
+                                aContentBlockingLogJSON)
     {
     },
 
     mEditor: null
   };
 
   var progress, progressListener;
 
--- a/editor/libeditor/tests/test_bug607584.xul
+++ b/editor/libeditor/tests/test_bug607584.xul
@@ -88,17 +88,18 @@ https://bugzilla.mozilla.org/show_bug.cg
     onLocationChange : function(aWebProgress, aRequest, aLocation, aFlags)
       {
       },
   
     onStatusChange : function(aWebProgress, aRequest, aStatus, aMessage)
       {
       },
   
-    onSecurityChange : function(aWebProgress, aRequest, aState)
+    onSecurityChange : function(aWebProgress, aRequest, aOldState, aState,
+                                aContentBlockingLogJSON)
       {
       },
   
       mEditor: null
   };
 
   var progress, progressListener;
 
--- a/editor/libeditor/tests/test_bug616590.xul
+++ b/editor/libeditor/tests/test_bug616590.xul
@@ -77,17 +77,18 @@ https://bugzilla.mozilla.org/show_bug.cg
     onLocationChange : function(aWebProgress, aRequest, aLocation, aFlags)
     {
     },
 
     onStatusChange : function(aWebProgress, aRequest, aStatus, aMessage)
     {
     },
 
-    onSecurityChange : function(aWebProgress, aRequest, aState)
+    onSecurityChange : function(aWebProgress, aRequest, aOldState, aState,
+                                aContentBlockingLogJSON)
     {
     },
 
     mEditor: null
   };
 
   var progress, progressListener;
 
--- a/editor/libeditor/tests/test_bug780908.xul
+++ b/editor/libeditor/tests/test_bug780908.xul
@@ -86,17 +86,18 @@ adapted from test_bug607584.xul by Kent 
     onLocationChange : function(aWebProgress, aRequest, aLocation, aFlags)
       {
       },
   
     onStatusChange : function(aWebProgress, aRequest, aStatus, aMessage)
       {
       },
   
-    onSecurityChange : function(aWebProgress, aRequest, aState)
+    onSecurityChange : function(aWebProgress, aRequest, aOldState, aState,
+                                aContentBlockingLogJSON)
       {
       },
   
       mEditor: null
   };
 
   var progress, progressListener;
 
--- a/gfx/angle/checkout/out/gen/angle/id/commit.h
+++ b/gfx/angle/checkout/out/gen/angle/id/commit.h
@@ -1,3 +1,3 @@
-#define ANGLE_COMMIT_HASH "786e980807f1"
+#define ANGLE_COMMIT_HASH "4fb8d7f978ad"
 #define ANGLE_COMMIT_HASH_SIZE 12
-#define ANGLE_COMMIT_DATE "2018-09-25 17:58:13 -0700"
+#define ANGLE_COMMIT_DATE "2018-09-28 14:28:35 -0700"
--- a/gfx/angle/checkout/src/common/mathutil.cpp
+++ b/gfx/angle/checkout/src/common/mathutil.cpp
@@ -65,9 +65,20 @@ void convert999E5toRGBFloats(unsigned in
 {
     const RGB9E5Data *inputData = reinterpret_cast<const RGB9E5Data*>(&input);
 
     *red = inputData->R * pow(2.0f, (int)inputData->E - g_sharedexp_bias - g_sharedexp_mantissabits);
     *green = inputData->G * pow(2.0f, (int)inputData->E - g_sharedexp_bias - g_sharedexp_mantissabits);
     *blue = inputData->B * pow(2.0f, (int)inputData->E - g_sharedexp_bias - g_sharedexp_mantissabits);
 }
 
+int BitCountPolyfill(uint32_t bits)
+{
+    int ones = 0;
+    while (bits)
+    {
+        ones += bool(bits & 1);
+        bits >>= 1;
+    }
+    return ones;
+}
+
 }  // namespace gl
--- a/gfx/angle/checkout/src/common/mathutil.h
+++ b/gfx/angle/checkout/src/common/mathutil.h
@@ -924,43 +924,65 @@ inline uint32_t BitfieldReverse(uint32_t
     for (size_t j = 0u; j < 32u; ++j)
     {
         result |= (((value >> j) & 1u) << (31u - j));
     }
     return result;
 }
 
 // Count the 1 bits.
-#if defined(ANGLE_PLATFORM_WINDOWS)
+#if defined(_M_IX86) || defined(_M_X64)
+#define ANGLE_HAS_BITCOUNT_32
 inline int BitCount(uint32_t bits)
 {
     return static_cast<int>(__popcnt(bits));
 }
-#if defined(ANGLE_IS_64_BIT_CPU)
+#if defined(_M_X64)
+#define ANGLE_HAS_BITCOUNT_64
 inline int BitCount(uint64_t bits)
 {
     return static_cast<int>(__popcnt64(bits));
 }
-#endif  // defined(ANGLE_IS_64_BIT_CPU)
-#endif  // defined(ANGLE_PLATFORM_WINDOWS)
+#endif  // defined(_M_X64)
+#endif  // defined(_M_IX86) || defined(_M_X64)
 
 #if defined(ANGLE_PLATFORM_POSIX)
+#define ANGLE_HAS_BITCOUNT_32
 inline int BitCount(uint32_t bits)
 {
     return __builtin_popcount(bits);
 }
 
 #if defined(ANGLE_IS_64_BIT_CPU)
+#define ANGLE_HAS_BITCOUNT_64
 inline int BitCount(uint64_t bits)
 {
     return __builtin_popcountll(bits);
 }
 #endif  // defined(ANGLE_IS_64_BIT_CPU)
 #endif  // defined(ANGLE_PLATFORM_POSIX)
 
+int BitCountPolyfill(uint32_t bits);
+
+#if !defined(ANGLE_HAS_BITCOUNT_32)
+inline int BitCount(const uint32_t bits)
+{
+    return BitCountPolyfill(bits);
+}
+#endif  // !defined(ANGLE_HAS_BITCOUNT_32)
+
+#if !defined(ANGLE_HAS_BITCOUNT_64)
+inline int BitCount(const uint64_t bits)
+{
+    return BitCount(static_cast<uint32_t>(bits >> 32)) + BitCount(static_cast<uint32_t>(bits));
+}
+#endif  // !defined(ANGLE_HAS_BITCOUNT_64)
+#undef ANGLE_HAS_BITCOUNT_32
+#undef ANGLE_HAS_BITCOUNT_64
+
 inline int BitCount(uint8_t bits)
 {
     return BitCount(static_cast<uint32_t>(bits));
 }
 
 inline int BitCount(uint16_t bits)
 {
     return BitCount(static_cast<uint32_t>(bits));
--- a/gfx/angle/checkout/src/common/platform.h
+++ b/gfx/angle/checkout/src/common/platform.h
@@ -4,17 +4,17 @@
 // found in the LICENSE file.
 //
 
 // platform.h: Operating system specific includes and defines.
 
 #ifndef COMMON_PLATFORM_H_
 #define COMMON_PLATFORM_H_
 
-#if defined(_WIN32) || defined(_WIN64)
+#if defined(_WIN32)
 #   define ANGLE_PLATFORM_WINDOWS 1
 #elif defined(__APPLE__)
 #   define ANGLE_PLATFORM_APPLE 1
 #   define ANGLE_PLATFORM_POSIX 1
 #elif defined(ANDROID)
 #   define ANGLE_PLATFORM_ANDROID 1
 #   define ANGLE_PLATFORM_POSIX 1
 #elif defined(__linux__) || defined(EMSCRIPTEN)
--- a/gfx/angle/cherry_picks.txt
+++ b/gfx/angle/cherry_picks.txt
@@ -1,8 +1,19 @@
+commit 4fb8d7f978adda36086377ea7846951faa9f6bd3
+Author: Jeff Gilbert <jgilbert@mozilla.com>
+Date:   Wed Sep 26 18:04:05 2018 -0700
+
+    Polyfill BitCount for ARM/ARM64 on MSVC.
+    
+    Also _WIN64 implies _WIN32.
+    
+    Bug: angleproject:2858
+    Change-Id: I63e2ffd2e9e304171ea6adb99836733981cc1813
+
 commit 786e980807f104d35bf31a769d31699024b89c2f
 Author: Jeff Gilbert <jgilbert@mozilla.com>
 Date:   Tue Sep 25 14:18:35 2018 -0700
 
     Stream support for R16*_UNORM and P010/P016.
     
     Bug: angleproject:2850
     Change-Id: Ib23b3012b5244c8e3edbdfa05d9b4e4869bbfed8
--- a/gfx/layers/apz/src/APZCTreeManager.cpp
+++ b/gfx/layers/apz/src/APZCTreeManager.cpp
@@ -538,17 +538,19 @@ APZCTreeManager::UpdateHitTestingTreeImp
         state.mNodesToDestroy[i].get(),
         state.mNodesToDestroy[i]->GetApzc());
     state.mNodesToDestroy[i]->Destroy();
   }
 
 #if ENABLE_APZCTM_LOGGING
   // Make the hit-test tree line up with the layer dump
   printf_stderr("APZCTreeManager (%p)\n", this);
-  mRootNode->Dump("  ");
+  if (mRootNode) {
+    mRootNode->Dump("  ");
+  }
 #endif
 }
 
 void
 APZCTreeManager::UpdateFocusState(LayersId aRootLayerTreeId,
                                   LayersId aOriginatingLayersId,
                                   const FocusTarget& aFocusTarget)
 {
--- a/js/src/jsapi.cpp
+++ b/js/src/jsapi.cpp
@@ -1086,29 +1086,30 @@ JS_EnumerateStandardClasses(JSContext* c
     AssertHeapIsIdle();
     CHECK_THREAD(cx);
     cx->check(obj);
     Handle<GlobalObject*> global = obj.as<GlobalObject>();
     return GlobalObject::initStandardClasses(cx, global);
 }
 
 static bool
-EnumerateUnresolvedStandardClasses(JSContext* cx, Handle<GlobalObject*> global,
-                                   AutoIdVector& properties, const JSStdName* table)
+EnumerateStandardClassesInTable(JSContext* cx, Handle<GlobalObject*> global,
+                                AutoIdVector& properties, const JSStdName* table,
+                                bool includeResolved)
 {
     for (unsigned i = 0; !table[i].isSentinel(); i++) {
         if (table[i].isDummy()) {
             continue;
         }
 
         JSProtoKey key = table[i].key;
 
         // If the standard class has been resolved, the properties have been
         // defined on the global so we don't need to add them here.
-        if (global->isStandardClassResolved(key)) {
+        if (!includeResolved && global->isStandardClassResolved(key)) {
             continue;
         }
 
         if (GlobalObject::skipDeselectedConstructor(cx, key)) {
             continue;
         }
 
         if (const Class* clasp = ProtoKeyToClass(key)) {
@@ -1124,44 +1125,62 @@ EnumerateUnresolvedStandardClasses(JSCon
         if (!properties.append(id)) {
             return false;
         }
     }
 
     return true;
 }
 
-JS_PUBLIC_API(bool)
-JS_NewEnumerateStandardClasses(JSContext* cx, JS::HandleObject obj, JS::AutoIdVector& properties,
-                               bool enumerableOnly)
+static bool
+EnumerateStandardClasses(JSContext* cx, JS::HandleObject obj, JS::AutoIdVector& properties,
+                         bool enumerableOnly, bool includeResolved)
 {
     if (enumerableOnly) {
-        // There are no enumerable lazy properties.
+        // There are no enumerable standard classes and "undefined" is
+        // not enumerable.
         return true;
     }
 
     Handle<GlobalObject*> global = obj.as<GlobalObject>();
 
     // It's fine to always append |undefined| here, it's non-configurable and
     // the enumeration code filters duplicates.
     if (!properties.append(NameToId(cx->names().undefined))) {
         return false;
     }
 
-    if (!EnumerateUnresolvedStandardClasses(cx, global, properties, standard_class_names)) {
+    if (!EnumerateStandardClassesInTable(cx, global, properties, standard_class_names,
+                                         includeResolved)) {
         return false;
     }
-    if (!EnumerateUnresolvedStandardClasses(cx, global, properties, builtin_property_names)) {
+    if (!EnumerateStandardClassesInTable(cx, global, properties, builtin_property_names,
+                                         includeResolved)) {
         return false;
     }
 
     return true;
 }
 
 JS_PUBLIC_API(bool)
+JS_NewEnumerateStandardClasses(JSContext* cx, JS::HandleObject obj, JS::AutoIdVector& properties,
+                               bool enumerableOnly)
+{
+    return EnumerateStandardClasses(cx, obj, properties, enumerableOnly, false);
+}
+
+JS_PUBLIC_API(bool)
+JS_NewEnumerateStandardClassesIncludingResolved(JSContext* cx, JS::HandleObject obj,
+                                                JS::AutoIdVector& properties,
+                                                bool enumerableOnly)
+{
+    return EnumerateStandardClasses(cx, obj, properties, enumerableOnly, true);
+}
+
+JS_PUBLIC_API(bool)
 JS_GetClassObject(JSContext* cx, JSProtoKey key, MutableHandleObject objp)
 {
     AssertHeapIsIdle();
     CHECK_THREAD(cx);
     JSObject* obj = GlobalObject::getOrCreateConstructor(cx, key);
     if (!obj) {
         return false;
     }
--- a/js/src/jsapi.h
+++ b/js/src/jsapi.h
@@ -952,20 +952,37 @@ extern JS_PUBLIC_API(bool)
 JS_ResolveStandardClass(JSContext* cx, JS::HandleObject obj, JS::HandleId id, bool* resolved);
 
 extern JS_PUBLIC_API(bool)
 JS_MayResolveStandardClass(const JSAtomState& names, jsid id, JSObject* maybeObj);
 
 extern JS_PUBLIC_API(bool)
 JS_EnumerateStandardClasses(JSContext* cx, JS::HandleObject obj);
 
+/**
+ * Fill "properties" with a list of standard class names that have not yet been
+ * resolved on "obj".  This can be used as (part of) a newEnumerate class hook on a
+ * global.  Already-resolved things are excluded because they might have been deleted
+ * by script after being resolved and enumeration considers already-defined
+ * properties anyway.
+ */
 extern JS_PUBLIC_API(bool)
 JS_NewEnumerateStandardClasses(JSContext* cx, JS::HandleObject obj, JS::AutoIdVector& properties,
                                bool enumerableOnly);
 
+/**
+ * Fill "properties" with a list of standard class names.  This can be used for
+ * proxies that want to define behavior that looks like enumerating a global without
+ * touching the global itself.
+ */
+extern JS_PUBLIC_API(bool)
+JS_NewEnumerateStandardClassesIncludingResolved(JSContext* cx, JS::HandleObject obj,
+                                                JS::AutoIdVector& properties,
+                                                bool enumerableOnly);
+
 extern JS_PUBLIC_API(bool)
 JS_GetClassObject(JSContext* cx, JSProtoKey key, JS::MutableHandle<JSObject*> objp);
 
 extern JS_PUBLIC_API(bool)
 JS_GetClassPrototype(JSContext* cx, JSProtoKey key, JS::MutableHandle<JSObject*> objp);
 
 namespace JS {
 
--- a/js/src/wasm/WasmModule.cpp
+++ b/js/src/wasm/WasmModule.cpp
@@ -163,23 +163,25 @@ Module::finishTier2(const LinkData& link
         // set*Entry functions.
         if (cr.isFunction()) {
             code().setTieringEntry(cr.funcIndex(), base + cr.funcTierEntry());
         } else if (cr.isJitEntry()) {
             code().setJitEntry(cr.funcIndex(), base + cr.begin());
         }
     }
 
-    // Tier-2 is done; let everyone know.
+    // Tier-2 is done; let everyone know. Mark tier-2 active for testing
+    // purposes so that wasmHasTier2CompilationCompleted() only returns true
+    // after tier-2 has been fully cached.
 
-    testingTier2Active_ = false;
     if (tier2Listener_) {
         serialize(linkData2, *tier2Listener_);
         tier2Listener_ = nullptr;
     }
+    testingTier2Active_ = false;
 
     return true;
 }
 
 void
 Module::testingBlockOnTier2Complete() const
 {
     while (testingTier2Active_) {
@@ -201,17 +203,16 @@ Module::serializedSize(const LinkData& l
            SerializedVectorSize(elemSegments_) +
            SerializedVectorSize(customSections_) +
            code_->serializedSize();
 }
 
 /* virtual */ void
 Module::serialize(const LinkData& linkData, uint8_t* begin, size_t size) const
 {
-    MOZ_RELEASE_ASSERT(!testingTier2Active_);
     MOZ_RELEASE_ASSERT(!metadata().debugEnabled);
     MOZ_RELEASE_ASSERT(code_->hasTier(Tier::Serialized));
 
     JS::BuildIdCharVector buildId;
     JS::GetOptimizedEncodingBuildId(&buildId);
 
     uint8_t* cursor = begin;
     cursor = SerializePodVector(cursor, buildId);
--- a/js/xpconnect/loader/ScriptPreloader.cpp
+++ b/js/xpconnect/loader/ScriptPreloader.cpp
@@ -11,26 +11,28 @@
 #include "mozilla/URLPreloader.h"
 
 #include "mozilla/ArrayUtils.h"
 #include "mozilla/ClearOnShutdown.h"
 #include "mozilla/FileUtils.h"
 #include "mozilla/Logging.h"
 #include "mozilla/ScopeExit.h"
 #include "mozilla/Services.h"
+#include "mozilla/Telemetry.h"
 #include "mozilla/Unused.h"
 #include "mozilla/dom/ContentChild.h"
 #include "mozilla/dom/ContentParent.h"
 
 #include "MainThreadUtils.h"
 #include "nsDebug.h"
 #include "nsDirectoryServiceUtils.h"
 #include "nsIFile.h"
 #include "nsIObserverService.h"
 #include "nsJSUtils.h"
+#include "nsMemoryReporterManager.h"
 #include "nsNetUtil.h"
 #include "nsProxyRelease.h"
 #include "nsThreadUtils.h"
 #include "nsXULAppAPI.h"
 #include "xpcpublic.h"
 
 #define STARTUP_COMPLETE_TOPIC "browser-delayed-startup-finished"
 #define DOC_ELEM_INSERTED_TOPIC "document-element-inserted"
@@ -418,16 +420,25 @@ ScriptPreloader::FinishContentStartup()
 
     mSaveTimer = nullptr;
 
     mStartupFinished = true;
 
     if (mChildActor) {
         mChildActor->SendScriptsAndFinalize(mScripts);
     }
+
+#ifdef XP_WIN
+    // Record the amount of USS at startup. This is Windows-only for now,
+    // we could turn it on for Linux relatively cheaply. On macOS it can have
+    // a perf impact.
+    mozilla::Telemetry::Accumulate(
+        mozilla::Telemetry::MEMORY_UNIQUE_CONTENT_STARTUP,
+        nsMemoryReporterManager::ResidentUnique() / 1024);
+#endif
 }
 
 bool
 ScriptPreloader::WillWriteScripts()
 {
     return Active() && (XRE_IsParentProcess() || mChildActor);
 }
 
--- a/js/xpconnect/loader/moz.build
+++ b/js/xpconnect/loader/moz.build
@@ -44,14 +44,15 @@ EXTRA_JS_MODULES += [
 ]
 
 FINAL_LIBRARY = 'xul'
 
 LOCAL_INCLUDES += [
     '../src',
     '../wrappers',
     '/dom/base',
+    '/xpcom/base/',
 ]
 
 include('/ipc/chromium/chromium-config.mozbuild')
 
 if CONFIG['CC_TYPE'] in ('clang', 'gcc'):
     CXXFLAGS += ['-Wno-shadow']
--- a/js/xpconnect/src/XPCJSRuntime.cpp
+++ b/js/xpconnect/src/XPCJSRuntime.cpp
@@ -109,16 +109,18 @@ const char* const XPCJSRuntime::mStrings
     "fileName",             // IDX_FILENAME
     "lineNumber",           // IDX_LINENUMBER
     "columnNumber",         // IDX_COLUMNNUMBER
     "stack",                // IDX_STACK
     "message",              // IDX_MESSAGE
     "lastIndex",            // IDX_LASTINDEX
     "then",                 // IDX_THEN
     "isInstance",           // IDX_ISINSTANCE
+    "Infinity",             // IDX_INFINITY
+    "NaN",                  // IDX_NAN
 };
 
 /***************************************************************************/
 
 // *Some* NativeSets are referenced from mClassInfo2NativeSetMap.
 // *All* NativeSets are referenced from mNativeSetMap.
 // So, in mClassInfo2NativeSetMap we just clear references to the unmarked.
 // In mNativeSetMap we clear the references to the unmarked *and* delete them.
--- a/js/xpconnect/src/xpcprivate.h
+++ b/js/xpconnect/src/xpcprivate.h
@@ -406,16 +406,18 @@ public:
         IDX_FILENAME                ,
         IDX_LINENUMBER              ,
         IDX_COLUMNNUMBER            ,
         IDX_STACK                   ,
         IDX_MESSAGE                 ,
         IDX_LASTINDEX               ,
         IDX_THEN                    ,
         IDX_ISINSTANCE              ,
+        IDX_INFINITY                ,
+        IDX_NAN                     ,
         IDX_TOTAL_COUNT // just a count of the above
     };
 
     inline JS::HandleId GetStringID(unsigned index) const;
     inline const char* GetStringName(unsigned index) const;
 
 private:
     XPCJSContext();
--- a/js/xpconnect/wrappers/XrayWrapper.cpp
+++ b/js/xpconnect/wrappers/XrayWrapper.cpp
@@ -15,16 +15,17 @@
 
 #include "XPCWrapper.h"
 #include "xpcprivate.h"
 
 #include "jsapi.h"
 #include "nsJSUtils.h"
 #include "nsPrintfCString.h"
 
+#include "mozilla/FloatingPoint.h"
 #include "mozilla/dom/BindingUtils.h"
 #include "mozilla/dom/WindowBinding.h"
 #include "mozilla/dom/XrayExpandoClass.h"
 #include "nsGlobalWindow.h"
 
 using namespace mozilla::dom;
 using namespace JS;
 using namespace mozilla;
@@ -1598,16 +1599,24 @@ XrayTraits::resolveOwnProperty(JSContext
             found = true;
         } else if (id == GetJSIDByIndex(cx, XPCJSContext::IDX_EVAL)) {
             RootedObject eval(cx);
             if (!js::GetRealmOriginalEval(cx, &eval)) {
                 return false;
             }
             desc.value().set(ObjectValue(*eval));
             found = true;
+        } else if (id == GetJSIDByIndex(cx, XPCJSContext::IDX_INFINITY)) {
+            desc.value().setNaN();
+            desc.setAttributes(JSPROP_PERMANENT | JSPROP_READONLY);
+            found = true;
+        } else if (id == GetJSIDByIndex(cx, XPCJSContext::IDX_NAN)) {
+            desc.value().setDouble(PositiveInfinity<double>());
+            desc.setAttributes(JSPROP_PERMANENT | JSPROP_READONLY);
+            found = true;
         }
     }
 
     if (found) {
         if (!JS_WrapPropertyDescriptor(cx, desc)) {
             return false;
         }
         // Pretend the property lives on the wrapper.
@@ -1737,16 +1746,25 @@ DOMXrayTraits::enumerateNames(JSContext*
             if (!JS_IndexToId(cx, i, &indexId)) {
                 return false;
             }
             props.infallibleAppend(indexId);
         }
     }
 
     JS::Rooted<JSObject*> obj(cx, getTargetObject(wrapper));
+    if (JS_IsGlobalObject(obj)) {
+        // We could do this in a shared enumerateNames with JSXrayTraits, but we
+        // don't really have globals we expose via those.
+        JSAutoRealm ar(cx, obj);
+        if (!JS_NewEnumerateStandardClassesIncludingResolved(
+          cx, obj, props, !(flags & JSITER_HIDDEN))) {
+            return false;
+        }
+    }
     return XrayOwnPropertyKeys(cx, wrapper, obj, flags, props);
 }
 
 bool
 DOMXrayTraits::call(JSContext* cx, HandleObject wrapper,
                     const JS::CallArgs& args, const js::Wrapper& baseInstance)
 {
     RootedObject obj(cx, getTargetObject(wrapper));
--- a/layout/base/tests/chrome/printpreview_bug396024_helper.xul
+++ b/layout/base/tests/chrome/printpreview_bug396024_helper.xul
@@ -18,17 +18,18 @@ var SimpleTest = window.opener.wrappedJS
 var gWbp;
 function printpreview() {
   gWbp = window.frames[1].docShell.printPreview;
   var listener = {
     onLocationChange: function(webProgress, request, location, flags) { },
     onProgressChange: function(webProgress, request, curSelfProgress, 
                                maxSelfProgress, curTotalProgress,
                                maxTotalProgress) { },
-    onSecurityChange: function(webProgress, request, state) { },
+    onSecurityChange: function(webProgress, request, oldState, state,
+                               contentBlockingLogJSON) { },
     onStateChange: function(webProgress, request, stateFlags, status) { },
     onStatusChange: function(webProgress, request, status, message) { },
     QueryInterface: function(iid) {
       if (iid.equals(Ci.nsIWebProgressListener) ||
           iid.equals(Ci.nsISupportsWeakReference))
             return this;
       throw Cr.NS_NOINTERFACE;
     }
--- a/layout/base/tests/chrome/printpreview_bug482976_helper.xul
+++ b/layout/base/tests/chrome/printpreview_bug482976_helper.xul
@@ -18,17 +18,18 @@ var SimpleTest = window.opener.wrappedJS
 var gWbp;
 function printpreview() {
   gWbp = window.frames[1].docShell.printPreview;
   var listener = {
     onLocationChange: function(webProgress, request, location, flags) { },
     onProgressChange: function(webProgress, request, curSelfProgress, 
                                maxSelfProgress, curTotalProgress,
                                maxTotalProgress) { },
-    onSecurityChange: function(webProgress, request, state) { },
+    onSecurityChange: function(webProgress, request, oldState, state,
+                               contentBlockingLogJSON) { },
     onStateChange: function(webProgress, request, stateFlags, status) { },
     onStatusChange: function(webProgress, request, status, message) { },
     QueryInterface: function(iid) {
       if (iid.equals(Ci.nsIWebProgessListener) ||
           iid.equals(Ci.nsISupportsWeakReference))
             return this;
       throw Cr.NS_NOINTERFACE;
     }
--- a/layout/base/tests/chrome/printpreview_helper.xul
+++ b/layout/base/tests/chrome/printpreview_helper.xul
@@ -25,17 +25,18 @@ filePath = file.path;
 
 function printpreview() {
   gWbp = window.frames[1].docShell.printPreview;
   var listener = {
     onLocationChange: function(webProgress, request, location, flags) { },
     onProgressChange: function(webProgress, request, curSelfProgress, 
                                maxSelfProgress, curTotalProgress,
                                maxTotalProgress) { },
-    onSecurityChange: function(webProgress, request, state) { },
+    onSecurityChange: function(webProgress, request, oldState, state,
+                               contentBlockingLogJSON) { },
     onStateChange: function(webProgress, request, stateFlags, status) { },
     onStatusChange: function(webProgress, request, status, message) { },
     QueryInterface: function(iid) {
       if (iid.equals(Ci.nsIWebProgressListener) ||
           iid.equals(Ci.nsISupportsWeakReference))
             return this;
       throw Cr.NS_NOINTERFACE;
     }
--- a/layout/printing/ipc/RemotePrintJobChild.cpp
+++ b/layout/printing/ipc/RemotePrintJobChild.cpp
@@ -158,17 +158,20 @@ RemotePrintJobChild::OnStatusChange(nsIW
     Unused << SendStatusChange(aStatus);
   }
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
 RemotePrintJobChild::OnSecurityChange(nsIWebProgress* aProgress,
-                                      nsIRequest* aRequest, uint32_t aState)
+                                      nsIRequest* aRequest,
+                                      uint32_t aState,
+                                      uint32_t aOldState,
+                                      const nsAString& aContentBlockingLogJSON)
 {
   return NS_OK;
 }
 
 // End of nsIWebProgressListener
 
 RemotePrintJobChild::~RemotePrintJobChild()
 {
--- a/layout/printing/nsPrintJob.cpp
+++ b/layout/printing/nsPrintJob.cpp
@@ -2111,17 +2111,19 @@ nsPrintJob::OnStatusChange(nsIWebProgres
 {
   MOZ_ASSERT_UNREACHABLE("notification excluded in AddProgressListener(...)");
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsPrintJob::OnSecurityChange(nsIWebProgress* aWebProgress,
                              nsIRequest* aRequest,
-                             uint32_t aState)
+                             uint32_t aState,
+                             uint32_t aOldState,
+                             const nsAString& aContentBlockingLogJSON)
 {
   MOZ_ASSERT_UNREACHABLE("notification excluded in AddProgressListener(...)");
   return NS_OK;
 }
 
 //-------------------------------------------------------
 
 void
--- a/layout/reftests/css-invalid/input/reftest.list
+++ b/layout/reftests/css-invalid/input/reftest.list
@@ -13,21 +13,21 @@ fuzzy(0-11,0-4) fuzzy-if(skiaContent,0-2
 == input-email-invalid.html input-withtext-ref.html
 == input-email-valid.html input-email-ref.html
 == input-url-invalid.html input-withtext-ref.html
 == input-url-valid.html input-url-ref.html
 == input-pattern-valid.html input-withtext-ref.html
 == input-pattern-invalid.html input-withtext-ref.html
 == input-type-barred.html input-button-ref.html
 fuzzy(0-11,0-4) == input-type-invalid.html input-ref.html
-fuzzy-if(webrender,0-15,0-14) == input-disabled-fieldset-1.html input-fieldset-ref.html
-fuzzy-if(webrender,0-15,0-14) == input-disabled-fieldset-2.html input-fieldset-ref.html
+== input-disabled-fieldset-1.html input-fieldset-ref.html
+== input-disabled-fieldset-2.html input-fieldset-ref.html
 == input-fieldset-legend.html input-fieldset-legend-ref.html
-fuzzy-if(webrender,0-92,0-2) == input-radio-required.html input-radio-ref.html
-fuzzy-if(skiaContent,0-2,0-10) fuzzy-if(webrender,0-92,0-2) == input-radio-customerror.html input-radio-ref.html
-fuzzy-if(skiaContent,0-2,0-10) fuzzy-if(webrender,0-92,0-2) == input-radio-dyn-valid-1.html input-radio-checked-ref.html
-fuzzy-if(skiaContent,0-2,0-10) fuzzy-if(webrender,0-92,0-2) == input-radio-dyn-valid-2.html input-radio-ref.html
-fuzzy-if(skiaContent,0-2,0-10) fuzzy-if(webrender,0-92,0-2) == input-radio-nogroup-required-valid.html input-radio-ref.html
-fuzzy-if(skiaContent,0-2,0-10) fuzzy-if(webrender,0-92,0-2) == input-radio-nogroup-required-invalid.html input-radio-checked-ref.html
-fuzzy-if(skiaContent,0-2,0-10) fuzzy-if(webrender,0-92,0-2) == input-radio-focus-click.html input-radio-ref.html
+== input-radio-required.html input-radio-ref.html
+fuzzy-if(skiaContent,0-2,0-10) == input-radio-customerror.html input-radio-ref.html
+fuzzy-if(skiaContent,0-2,0-10) == input-radio-dyn-valid-1.html input-radio-checked-ref.html
+fuzzy-if(skiaContent,0-2,0-10) == input-radio-dyn-valid-2.html input-radio-ref.html
+fuzzy-if(skiaContent,0-2,0-10) == input-radio-nogroup-required-valid.html input-radio-ref.html
+fuzzy-if(skiaContent,0-2,0-10) == input-radio-nogroup-required-invalid.html input-radio-checked-ref.html
+fuzzy-if(skiaContent,0-2,0-10) == input-radio-focus-click.html input-radio-ref.html
 == input-submit.html input-submit-ref.html
 == input-image.html input-image-ref.html
 # input type='hidden' shouldn't show
--- a/layout/tools/layout-debug/ui/content/layoutdebug.js
+++ b/layout/tools/layout-debug/ui/content/layoutdebug.js
@@ -72,17 +72,18 @@ nsLDBBrowserContentListener.prototype = 
       this.setButtonEnabled(this.mBackButton, gBrowser.canGoBack);
     },
 
   onStatusChange : function(aWebProgress, aRequest, aStatus, aMessage)
     {
       this.mStatusText.value = aMessage;
     },
 
-  onSecurityChange : function(aWebProgress, aRequest, aState)
+  onSecurityChange : function(aWebProgress, aRequest, aOldState, aState,
+                              aContentBlockingLogJSON)
     {
     },
 
   // non-interface methods
   setButtonEnabled : function(aButtonElement, aEnabled)
     {
       if (aEnabled)
         aButtonElement.removeAttribute("disabled");
--- a/mobile/android/chrome/content/browser.js
+++ b/mobile/android/chrome/content/browser.js
@@ -4699,17 +4699,18 @@ Tab.prototype = {
       Services.obs.notifyObservers(this.browser, "Session:NotifyLocationChange");
     }
   },
 
   // Properties used to cache security state used to update the UI
   _state: null,
   _hostChanged: false, // onLocationChange will flip this bit
 
-  onSecurityChange: function(aWebProgress, aRequest, aState) {
+  onSecurityChange: function(aWebProgress, aRequest, aOldState, aState,
+                             aContentBlockingLogJSON) {
     // Don't need to do anything if the data we use to update the UI hasn't changed
     if (this._state == aState && !this._hostChanged)
       return;
 
     this._state = aState;
     this._hostChanged = false;
 
     let identity = IdentityHandler.checkIdentity(aState, this.browser);
--- a/mobile/android/geckoview/src/main/AndroidManifest.xml
+++ b/mobile/android/geckoview/src/main/AndroidManifest.xml
@@ -1,16 +1,15 @@
 <?xml version="1.0" encoding="utf-8"?>
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
           package="org.mozilla.geckoview">
 
     <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
     <uses-permission android:name="android.permission.INTERNET"/>
     <uses-permission android:name="android.permission.WAKE_LOCK"/>
-    <uses-permission android:name="android.permission.VIBRATE"/>
 
     <uses-feature
             android:name="android.hardware.location"
             android:required="false"/>
     <uses-feature
             android:name="android.hardware.location.gps"
             android:required="false"/>
     <uses-feature
--- a/mobile/android/modules/geckoview/GeckoViewProgress.jsm
+++ b/mobile/android/modules/geckoview/GeckoViewProgress.jsm
@@ -247,17 +247,18 @@ class GeckoViewProgress extends GeckoVie
         type: "GeckoView:PageStop",
         success: isSuccess
       };
 
       this.eventDispatcher.sendRequest(message);
     }
   }
 
-  onSecurityChange(aWebProgress, aRequest, aState) {
+  onSecurityChange(aWebProgress, aRequest, aOldState, aState,
+                   aContentBlockingLogJSON) {
     debug `onSecurityChange`;
 
     // Don't need to do anything if the data we use to update the UI hasn't changed
     if (this._state === aState && !this._hostChanged) {
       return;
     }
 
     this._state = aState;
--- a/mobile/android/modules/geckoview/GeckoViewTrackingProtection.jsm
+++ b/mobile/android/modules/geckoview/GeckoViewTrackingProtection.jsm
@@ -15,17 +15,18 @@ class GeckoViewTrackingProtection extend
     const flags = Ci.nsIWebProgress.NOTIFY_SECURITY;
     this.progressFilter =
       Cc["@mozilla.org/appshell/component/browser-status-filter;1"]
       .createInstance(Ci.nsIWebProgress);
     this.progressFilter.addProgressListener(this, flags);
     this.browser.addProgressListener(this.progressFilter, flags);
   }
 
-  onSecurityChange(aWebProgress, aRequest, aState) {
+  onSecurityChange(aWebProgress, aRequest, aOldState, aState,
+                   aContentBlockingLogJSON) {
     debug `onSecurityChange`;
 
     if (!(aState & Ci.nsIWebProgressListener.STATE_BLOCKED_TRACKING_CONTENT) ||
         !aRequest || !(aRequest instanceof Ci.nsIClassifiedChannel)) {
       return;
     }
 
     let channel = aRequest.QueryInterface(Ci.nsIChannel);
--- a/mobile/android/tests/browser/robocop/robocop.ini
+++ b/mobile/android/tests/browser/robocop/robocop.ini
@@ -122,17 +122,17 @@ skip-if = android_version == "18"
 [src/org/mozilla/gecko/tests/testAndroidCastDeviceProvider.java]
 
 # Using UITest
 [src/org/mozilla/gecko/tests/testAboutHomePageNavigation.java]
 disabled=see bug 947550, bug 979038 and bug 977952
 [src/org/mozilla/gecko/tests/testAboutHomeVisibility.java]
 [src/org/mozilla/gecko/tests/testActivityStreamPocketReferrer.java]
 # fails on debug builds, see bug 1481834
-skip-if = debug
+skip-if = debug || android_version == "18"
 [src/org/mozilla/gecko/tests/testAppMenuPathways.java]
 [src/org/mozilla/gecko/tests/testBackButtonInEditMode.java]
 [src/org/mozilla/gecko/tests/testBrowserDatabaseHelperUpgrades.java]
 [src/org/mozilla/gecko/tests/testEventDispatcher.java]
 [src/org/mozilla/gecko/tests/testInputConnection.java]
 [src/org/mozilla/gecko/tests/testJavascriptBridge.java]
 [src/org/mozilla/gecko/tests/testReaderCacheMigration.java]
 [src/org/mozilla/gecko/tests/testReadingListToBookmarksMigration.java]
--- a/modules/libjar/nsJARChannel.cpp
+++ b/modules/libjar/nsJARChannel.cpp
@@ -58,17 +58,19 @@ static LazyLogModule gJarProtocolLog("ns
 // nsJARInputThunk
 //
 // this class allows us to do some extra work on the stream transport thread.
 //-----------------------------------------------------------------------------
 
 class nsJARInputThunk : public nsIInputStream
 {
 public:
-    NS_DECL_THREADSAFE_ISUPPORTS
+    // Preserve refcount changes when record/replaying, as otherwise the thread
+    // which destroys the thunk may vary between recording and replaying.
+    NS_DECL_THREADSAFE_ISUPPORTS_WITH_RECORDING(recordreplay::Behavior::Preserve)
     NS_DECL_NSIINPUTSTREAM
 
     nsJARInputThunk(nsIZipReader *zipReader,
                     nsIURI* fullJarURI,
                     const nsACString &jarEntry,
                     bool usingJarCache)
         : mUsingJarCache(usingJarCache)
         , mJarReader(zipReader)
--- a/modules/libpref/init/StaticPrefList.h
+++ b/modules/libpref/init/StaticPrefList.h
@@ -1617,16 +1617,24 @@ VARCACHE_PREF(
 )
 
 VARCACHE_PREF(
   "browser.contentblocking.allowlist.annotations.enabled",
    browser_contentblocking_allowlist_annotations_enabled,
   bool, true
 )
 
+// How many recent block/unblock actions per origins we remember in the
+// Content Blocking log for each top-level window.
+VARCACHE_PREF(
+  "browser.contentblocking.originlog.length",
+   browser_contentblocking_originlog_length,
+  uint32_t, 32
+)
+
 // Whether FastBlock has been enabled.
 VARCACHE_PREF(
   "browser.fastblock.enabled",
   browser_fastblock_enabled,
   bool, false
 )
 
 // Anti-tracking permission expiration
--- a/netwerk/base/nsChannelClassifier.cpp
+++ b/netwerk/base/nsChannelClassifier.cpp
@@ -618,18 +618,19 @@ nsChannelClassifier::NotifyTrackingProte
     uint32_t state = 0;
     nsCOMPtr<nsISecureBrowserUI> securityUI;
     docShell->GetSecurityUI(getter_AddRefs(securityUI));
     if (!securityUI) {
       return NS_OK;
     }
     doc->SetHasTrackingContentLoaded(true);
     securityUI->GetState(&state);
+    const uint32_t oldState = state;
     state |= nsIWebProgressListener::STATE_LOADED_TRACKING_CONTENT;
-    eventSink->OnSecurityChange(nullptr, state);
+    eventSink->OnSecurityChange(nullptr, oldState, state, doc->GetContentBlockingLog());
 
     return NS_OK;
 }
 
 void
 nsChannelClassifier::Start()
 {
   nsresult rv = StartInternal();
--- a/netwerk/base/nsISecureBrowserUI.idl
+++ b/netwerk/base/nsISecureBrowserUI.idl
@@ -9,15 +9,40 @@
 interface nsIDocShell;
 interface nsITransportSecurityInfo;
 
 [scriptable, uuid(718c662a-f810-4a80-a6c9-0b1810ecade2)]
 interface nsISecureBrowserUI : nsISupports
 {
     void init(in nsIDocShell docShell);
 
+    // A value composed of the Security State Flags and the Security
+    // Strength Flags defined in nsIWebProgressListener.
+    // Any undefined bits are reserved for future use.
+    // This represents the security state before the change.
+    readonly attribute unsigned long oldState;
+    // A value composed of the Security State Flags and the Security
+    // Strength Flags defined in nsIWebProgressListener.
+    // Any undefined bits are reserved for future use.
+    // This represents the security state after the change.
     readonly attribute unsigned long state;
+    // An optional JSON string representing a log of the content blocking
+    // events happened so far.  This will be a JSON object containing keys
+    // representing origins that content blocking has acted on, with values
+    // being an array of items, each representing one action.  Each action
+    // itself is an an array containing three elements, the first element
+    // being a blocking code from one of the Security State Flags above, and
+    // the second element being a boolean representing whether the origin
+    // was blocked (if true) or unblocked (if false) in that category, and
+    // the third element being the number of times that combination has been
+    // repeated consecutively.
+    // The reason JSON strings was chosen here was that we needed a format
+    // that is transferrable across processes in JS; and also the consumer
+    // of this data (browser-contentblocking.js) would be able to use the
+    // JSON.parse() API to parse out an object representation of the value.
+    readonly attribute AString contentBlockingLogJSON;
+    // Information about the connection security.
     readonly attribute nsITransportSecurityInfo secInfo;
 };
 
 %{C++
 #define NS_SECURE_BROWSER_UI_CONTRACTID "@mozilla.org/secure_browser_ui;1"
 %}
--- a/netwerk/base/nsISecurityEventSink.idl
+++ b/netwerk/base/nsISecurityEventSink.idl
@@ -1,26 +1,39 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
 /* 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 "nsISupports.idl"
-interface nsIURI;
 
-[scriptable, uuid(a71aee68-dd38-4736-bd79-035fea1a1ec6)]
+%{ C++
+namespace mozilla {
+namespace dom {
+class ContentBlockingLog;
+}
+}
+%}
+
+[ptr] native ContentBlockingLog(mozilla::dom::ContentBlockingLog);
+
+[builtinclass, uuid(a71aee68-dd38-4736-bd79-035fea1a1ec6)]
 interface nsISecurityEventSink : nsISupports
 {
 
     /**
       * Fired when a security change occurs due to page transitions,
       * or end document load. This interface should be called by
       * a security package (eg Netscape Personal Security Manager)
       * to notify nsIWebProgressListeners that security state has
       * changed. State flags are in nsIWebProgressListener.idl
+      * The old state flags before the change must also be passed in.
+      * cbLog represents the log of the content blocking actions on
+      * the page so far.
       */
 
-    void onSecurityChange(in nsISupports i_Context, in unsigned long state);
+    void onSecurityChange(in nsISupports i_Context, in unsigned long oldState,
+                          in unsigned long state, in ContentBlockingLog cbLog);
 };
 
 
 
 
--- a/other-licenses/nsis/Contrib/ExecInExplorer/ExecInExplorer.cpp
+++ b/other-licenses/nsis/Contrib/ExecInExplorer/ExecInExplorer.cpp
@@ -1,11 +1,11 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
-* License, v. 2.0. If a copy of the MPL was not distributed with this
-* file, you can obtain one at http://mozilla.org/MPL/2.0/. */
+ * 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/. */
 
 // This file is an NSIS plugin which exports a function that starts a process
 // from a provided path by using the shell automation API to have explorer.exe
 // invoke ShellExecute. This roundabout method of starting a process is useful
 // because it means the new process will use the integrity level and security
 // token of the shell, so it allows starting an unelevated process from inside
 // an elevated one. The method is based on
 // https://blogs.msdn.microsoft.com/oldnewthing/20131118-00/?p=2643
@@ -84,51 +84,57 @@ GetApplicationFromShellView(IShellView* 
       shellViewFolder->Release();
     }
     viewDisp->Release();
   }
   return shellDispatch;
 }
 
 static bool
-ShellExecInExplorerProcess(wchar_t* path)
+ShellExecInExplorerProcess(wchar_t* path, wchar_t* args = nullptr)
 {
   bool rv = false;
   if (SUCCEEDED(CoInitialize(nullptr))) {
     IShellView *desktopView = GetDesktopWindowShellView();
     if (desktopView) {
       IShellDispatch2 *shellDispatch = GetApplicationFromShellView(desktopView);
       if (shellDispatch) {
         BSTR bstrPath = SysAllocString(path);
-        rv = SUCCEEDED(shellDispatch->ShellExecuteW(bstrPath,
-                                                    VARIANT{}, VARIANT{},
+        VARIANT vArgs;
+        VariantInit(&vArgs);
+        if (args) {
+          vArgs.vt = VT_BSTR;
+          vArgs.bstrVal = SysAllocString(args);
+        }
+        rv = SUCCEEDED(shellDispatch->ShellExecuteW(bstrPath, vArgs, VARIANT{},
                                                     VARIANT{}, VARIANT{}));
+        VariantClear(&vArgs);
         SysFreeString(bstrPath);
         shellDispatch->Release();
       }
       desktopView->Release();
     }
     CoUninitialize();
   }
   return rv;
 }
 
 struct stack_t {
   stack_t* next;
   TCHAR text[MAX_PATH];
 };
 
 /**
-* Removes an element from the top of the NSIS stack
-*
-* @param  stacktop A pointer to the top of the stack
-* @param  str      The string to pop to
-* @param  len      The max length
-* @return 0 on success
-*/
+ * Removes an element from the top of the NSIS stack
+ *
+ * @param  stacktop A pointer to the top of the stack
+ * @param  str      The string to pop to
+ * @param  len      The max length
+ * @return 0 on success
+ */
 int
 popstring(stack_t **stacktop, TCHAR *str, int len)
 {
   // Removes the element from the top of the stack and puts it in the buffer
   stack_t *th;
   if (!stacktop || !*stacktop) {
     return 1;
   }
@@ -136,23 +142,23 @@ popstring(stack_t **stacktop, TCHAR *str
   th = (*stacktop);
   lstrcpyn(str, th->text, len);
   *stacktop = th->next;
   HeapFree(GetProcessHeap(), 0, th);
   return 0;
 }
 
 /**
-* Adds an element to the top of the NSIS stack
-*
-* @param  stacktop A pointer to the top of the stack
-* @param  str      The string to push on the stack
-* @param  len      The length of the string to push on the stack
-* @return 0 on success
-*/
+ * Adds an element to the top of the NSIS stack
+ *
+ * @param  stacktop A pointer to the top of the stack
+ * @param  str      The string to push on the stack
+ * @param  len      The length of the string to push on the stack
+ * @return 0 on success
+ */
 void
 pushstring(stack_t **stacktop, const TCHAR *str, int len)
 {
   stack_t *th;
   if (!stacktop) {
     return;
   }
   th = (stack_t*)HeapAlloc(GetProcessHeap(), 0, sizeof(stack_t) + len);
@@ -167,21 +173,41 @@ pushstring(stack_t **stacktop, const TCH
 * @param  stacktop  Pointer to the top of the stack, AKA the first parameter to
                     the plugin call. Should contain the file or URL to execute.
 * @return 1 if the file/URL was executed successfully, 0 if it was not
 */
 extern "C" void __declspec(dllexport)
 Exec(HWND, int, TCHAR *, stack_t **stacktop, void *)
 {
   wchar_t path[MAX_PATH + 1];
+  wchar_t args[MAX_PATH + 1];
+  bool rv = false;
+  bool restoreArgString = false;
   // We're skipping building the C runtime to keep the file size low, so we
   // can't use a normal string initialization because that would call memset.
   path[0] = L'\0';
+  args[0] = L'\0';
   popstring(stacktop, path, MAX_PATH);
-  bool rv = ShellExecInExplorerProcess(path);
+  if (!stacktop || !*stacktop) {
+    popstring(stacktop, args, MAX_PATH);
+    // This stack item may not be for us, but we don't know yet.
+    restoreArgString = true;
+  }
+
+  if (lstrcmpW(args, L"/cmdargs") == 0) {
+    popstring(stacktop, args, MAX_PATH);
+    rv = ShellExecInExplorerProcess(path, args);
+  } else {
+    // If the stack wasn't empty, then we popped something that wasn't for us.
+    if (restoreArgString) {
+      pushstring(stacktop, args, lstrlenW(args));
+    }
+    rv = ShellExecInExplorerProcess(path);
+  }
+
   pushstring(stacktop, rv ? L"1" : L"0", 2);
 }
 
 BOOL APIENTRY
 DllMain(HMODULE, DWORD, LPVOID)
 {
   return TRUE;
 }
index 7f37d8aa46b29923ad95af149c8151ec6165a4c0..e05eda1961a290669a578c0ab64251000a97e6ad
GIT binary patch
literal 4096
zc%1E4Z)_Y#6`yks=Nug09mFI;L9G8MSE<c;z1JpAdY4?9u8E5;**oWaqDfk>_tw5O
zd%I?L&$1&7hvE}CRx8>ML{y|wfPN?_&{l1cgCa1*$~D3vRT|<z{2@X{SkyumGEr!a
zmN)yyz7|(aCH{QrJI%eB-+S}kn>TM~Ch_DmXahpX3(YhUnnX6m(TCF=XbqqLc>{W_
z?rPJdr~7Kt(VU<X!-_npuz7-GB}vwZex6VY5+O*$<41akyqx93n>TN2b!`0d$=-=~
zAMKcR+wYI<yZH}`@45Lm3)?gMci_+V#BRQ0@eYSqW^KOL8m9y<hv&aPMutYH+v7!F
z`{qxNt;Jo_^hr;H-&(ZK(fbtq1)veuFktm-5UO?N)UFzIl4XhGpRgtc>?t<4S)d90
z0`F)>D2qW7{Cd_&V%#>l1b(;QN6uG?u;+acg*ARu1OB@WPC(wZxT{R`g_SI;u?W4q
z!3L^xQWau6tf&fyoHf8&aBfijdV0Jlb53k=N3Y@E^d7s96@E<r14`?Qrxvq4y_q3I
z-8LJe1PYX<{HJ%owi)zFg}#H8m>dK6kaq~>=AcudS1@WecEI4&_zLn*TtQg>EMqoq
z2W+xFj?h4vK5x*sm?32U%tjJKI(^GXFQtY&M*2pXeod!WQbRr?eYQg1z&2AAdI@tQ
zeO~va7&95w6HBEl8fT=JEA%oJ>7G=s4=g3kfSy<e**+tEuR`B*Wy2uL%~%Rdy`H!S
z8lTp|-JKKwQJucW=sq*))9Gcmv*dPGoKE3R>8^ibJCwR!Yb55AxguUZ>aEao*h__;
zx7N=@OH-qLIz4Zs7a;Hj4CbD}i}W#OP){s?>y8S&XfJ3Hf`bUQ>xo6cx8jiIQn_=^
zO6Wy5hy}MZ?{?<O^qa6L6D91l;L~FSOfqNnTLyiRDW90JldjYCr76vS0e`&AKsj;I
zoGsJS`uLlt76baUKQjf~pSj}CT-W{nAkwGtI6z<Y2Yq%w2x>hU5ShuZmZq?6|8tkj
z#s{#o0yt^KPBvyt#-}&*LSSZN9FV7<`qrgm`kzi*nz3jSpAWyTM>7sVeSKqb8yqS<
zz4{|w(2q=I^zkj!?SXP>;a$@Nw){`iEScJVeRt`u7Rh<Rg3;+^3`-6bRgeAFI@5<x
zd82s~P=sr5KPqf`yVjPx>dCzf%&_c%g3l`D!eRY$*Z?@#WjP?L9N3y~t#8~Gzs=bq
zXOy0}tsPu-5x|k%0@I|S*TWZ+geu3=w_tq9R_n3-Gsa<W5?uMq4dvYsvIV*sao4&a
zt(fMiY2RsjD;TA64Pdj%WN~~e(w8RRfbNB99Bz&O@i&r=;f>|m^6safvbN$QeE&;u
zKdnG(XhG<+&~`wZZ?<JnNkxdD2zOtx4jRVdEw)Spei~YT%lixpzbSnuaV>bJ9u+Ig
ze=Yqgp3Lvkej6-4jDC#z>fZU|mKsZK%GT~g-uI4+FMa>dXR78uRKEPej~=}qeEu(J
z>c}f>e;eXZ9&S_?DQFO>)}(f1UrF&_oasLP?6u%?Z-K8T7IrINI`_R7?_LYO@EAIG
zWz)C!xFNc4fz8nH2Ynpw>f?25OE69R5`s$#a9qxA>ws|-S{zy*ycg0<Sc@Lzxh{zw
z9TsJUSHf9Qv>?}5z`ld6`?xhPSr}p8k6{7dy{(U62o6{)2kw3Bz8}K8J;pK4t;$<Z
zo;;cDA4K;1qRJM9ZJh>e%NiEJ*jd01tzi#i4Da*I8n(-d4HDUB`GNbsmBgt<eSj4`
zs~F|lbBSuP*ECaw^#TUxJ-ShaeHSpeGSFld_5;A+^jeqxQ?(h}aENDz4~Y0-a=_|$
zDm;(SPinrvYm6duysBapp_W=v)f6s2oI&UzyFWZ8Wl)>d7kTKSFVd;Qba#ZZb{ZY^
z^c(^!owAtam2R11H9?k8&mp?oRWZ;(IV}m2pb4xfoCLoQ0CG^_S&i?KRE?E5z)k?x
zRiz_~yfgnuH+>*|)b;n@B@fyL|J$5=R^#|9CD+00ymnpT8W~^beH-0Bniq)?UQr=0
zu~2)M3=zD<$yq@fjD^xiJ9l=32%Lm0E6Nfd3ytw=X#c*=o1(0$^7(#oi~s|Y8VeN^
zX|KxV_&lrb%nO_%tMY)hlaup%Sv4OXX%7*3RuTqyRqL(x2Bruiswo9k>yid!$6E7;
zSc^1U^I)dRa|K1v#+)%A3jdV?c;T~*B8&(kKgg@A!fG)-1q!$vy7>`aBt-ll3$bdK
zG$MbQS3*QVIKbiBi-iVQQRPEK+nSTM5B3smdrwT!w$;Vqw6(c;gTei5dB8qD{A7qE
z$rza=Me=9lb#j*6(VlECw!hwfxxJZskcv@x>W9>M>H>9{x=Q_lny2a_pNX_aVv%$t
Y8ySmyJ@R7Y)yQPzqu1QW^#7!P0pi(0b^rhX
--- a/security/manager/ssl/nsSecureBrowserUIImpl.cpp
+++ b/security/manager/ssl/nsSecureBrowserUIImpl.cpp
@@ -16,17 +16,18 @@
 #include "nsITransportSecurityInfo.h"
 #include "nsIWebProgress.h"
 
 using namespace mozilla;
 
 LazyLogModule gSecureBrowserUILog("nsSecureBrowserUI");
 
 nsSecureBrowserUIImpl::nsSecureBrowserUIImpl()
-  : mState(0)
+  : mOldState(0)
+  , mState(0)
 {
   MOZ_ASSERT(NS_IsMainThread());
 }
 
 NS_IMPL_ISUPPORTS(nsSecureBrowserUIImpl,
                   nsISecureBrowserUI,
                   nsIWebProgressListener,
                   nsISupportsWeakReference)
@@ -57,16 +58,30 @@ nsSecureBrowserUIImpl::Init(nsIDocShell*
   if (NS_FAILED(rv)) {
     return rv;
   }
 
   return wp->AddProgressListener(this, nsIWebProgress::NOTIFY_LOCATION);
 }
 
 NS_IMETHODIMP
+nsSecureBrowserUIImpl::GetOldState(uint32_t* aOldState)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  NS_ENSURE_ARG(aOldState);
+
+  MOZ_LOG(gSecureBrowserUILog, LogLevel::Debug, ("GetOldState %p", this));
+  // Only sync our state with the docshell in GetState().
+  MOZ_LOG(gSecureBrowserUILog, LogLevel::Debug, ("  mOldState: %x", mOldState));
+
+  *aOldState = mOldState;
+  return NS_OK;
+}
+
+NS_IMETHODIMP
 nsSecureBrowserUIImpl::GetState(uint32_t* aState)
 {
   MOZ_ASSERT(NS_IsMainThread());
   NS_ENSURE_ARG(aState);
 
   MOZ_LOG(gSecureBrowserUILog, LogLevel::Debug, ("GetState %p", this));
   // With respect to mixed content and tracking protection, we won't know when
   // the state of our document (or a subdocument) has changed, so we ask the
@@ -74,16 +89,36 @@ nsSecureBrowserUIImpl::GetState(uint32_t
   CheckForBlockedContent();
   MOZ_LOG(gSecureBrowserUILog, LogLevel::Debug, ("  mState: %x", mState));
 
   *aState = mState;
   return NS_OK;
 }
 
 NS_IMETHODIMP
+nsSecureBrowserUIImpl::GetContentBlockingLogJSON(nsAString& aContentBlockingLogJSON)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  MOZ_LOG(gSecureBrowserUILog, LogLevel::Debug, ("GetContentBlockingLogJSON %p", this));
+  aContentBlockingLogJSON.Truncate();
+  nsCOMPtr<nsIDocShell> docShell = do_QueryReferent(mDocShell);
+  if (docShell) {
+    nsIDocument* doc = docShell->GetDocument();
+    if (doc) {
+      aContentBlockingLogJSON = doc->GetContentBlockingLog()->Stringify();
+    }
+  }
+  MOZ_LOG(gSecureBrowserUILog, LogLevel::Debug,
+          ("  ContentBlockingLogJSON: %s", NS_ConvertUTF16toUTF8(aContentBlockingLogJSON).get()));
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP
 nsSecureBrowserUIImpl::GetSecInfo(nsITransportSecurityInfo** result)
 {
   MOZ_ASSERT(NS_IsMainThread());
   NS_ENSURE_ARG_POINTER(result);
 
   *result = mTopLevelSecurityInfo;
   NS_IF_ADDREF(*result);
 
@@ -110,16 +145,18 @@ nsSecureBrowserUIImpl::CheckForBlockedCo
       sameTypeRoot,
       "No document shell root tree item from document shell tree item!");
     docShell = do_QueryInterface(sameTypeRoot);
     if (!docShell) {
       return;
     }
   }
 
+  mOldState = mState;
+
   // Has mixed content been loaded or blocked in nsMixedContentBlocker?
   // This only applies to secure documents.
   if (mState & STATE_IS_SECURE) {
     if (docShell->GetHasMixedActiveContentLoaded()) {
       mState |= STATE_IS_BROKEN | STATE_LOADED_MIXED_ACTIVE_CONTENT;
       mState &= ~STATE_IS_SECURE;
       mState &= ~STATE_SECURE_HIGH;
     }
@@ -275,16 +312,17 @@ nsSecureBrowserUIImpl::OnLocationChange(
   // initialized with.
   nsCOMPtr<nsIWebProgress> originalWebProgress = do_QueryReferent(mWebProgress);
   if (aWebProgress != originalWebProgress) {
     return NS_OK;
   }
 
   // Clear any state that varies by location.
   if (!(aFlags & LOCATION_CHANGE_SAME_DOCUMENT)) {
+    mOldState = 0;
     mState = 0;
     mTopLevelSecurityInfo = nullptr;
   }
 
   // NB: aRequest may be null. It may also not be QI-able to nsIChannel.
   nsCOMPtr<nsIChannel> channel(do_QueryInterface(aRequest));
   if (channel) {
     MOZ_LOG(gSecureBrowserUILog, LogLevel::Debug,
@@ -294,19 +332,28 @@ nsSecureBrowserUIImpl::OnLocationChange(
       return rv;
     }
 
     nsCOMPtr<nsISecurityEventSink> eventSink;
     NS_QueryNotificationCallbacks(channel, eventSink);
     if (NS_WARN_IF(!eventSink)) {
       return NS_ERROR_INVALID_ARG;
     }
+    mozilla::dom::ContentBlockingLog* contentBlockingLog = nullptr;
+    nsCOMPtr<nsIDocShell> docShell = do_QueryReferent(mDocShell);
+    if (docShell) {
+      nsIDocument* doc = docShell->GetDocument();
+      if (doc) {
+        contentBlockingLog = doc->GetContentBlockingLog();
+      }
+    }
     MOZ_LOG(gSecureBrowserUILog, LogLevel::Debug,
             ("  calling OnSecurityChange %p %x\n", aRequest, mState));
-    Unused << eventSink->OnSecurityChange(aRequest, mState);
+    Unused << eventSink->OnSecurityChange(aRequest, mOldState, mState,
+                                          contentBlockingLog);
   }
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsSecureBrowserUIImpl::OnStateChange(nsIWebProgress*,
                                      nsIRequest*,
@@ -335,13 +382,14 @@ nsSecureBrowserUIImpl::OnStatusChange(ns
                                       nsresult,
                                       const char16_t*)
 {
   MOZ_ASSERT_UNREACHABLE("Should have been excluded in AddProgressListener()");
   return NS_OK;
 }
 
 nsresult
-nsSecureBrowserUIImpl::OnSecurityChange(nsIWebProgress*, nsIRequest*, uint32_t)
+nsSecureBrowserUIImpl::OnSecurityChange(nsIWebProgress*, nsIRequest*, uint32_t,
+                                        uint32_t, const nsAString&)
 {
   MOZ_ASSERT_UNREACHABLE("Should have been excluded in AddProgressListener()");
   return NS_OK;
 }
--- a/security/manager/ssl/nsSecureBrowserUIImpl.h
+++ b/security/manager/ssl/nsSecureBrowserUIImpl.h
@@ -26,21 +26,22 @@ public:
 
   NS_DECL_ISUPPORTS
   NS_DECL_NSIWEBPROGRESSLISTENER
   NS_DECL_NSISECUREBROWSERUI
 
 protected:
   virtual ~nsSecureBrowserUIImpl() {};
 
-  // Do mixed content and tracking protection checks. May update mState.
+  // Do mixed content and tracking protection checks. May update mState and mOldState.
   void CheckForBlockedContent();
   // Given some information about a request from an OnLocationChange event,
-  // update mState and mTopLevelSecurityInfo.
+  // update mState, mOldState and mTopLevelSecurityInfo.
   nsresult UpdateStateAndSecurityInfo(nsIChannel* channel, nsIURI* uri);
 
+  uint32_t mOldState;
   uint32_t mState;
   nsWeakPtr mDocShell;
   nsWeakPtr mWebProgress;
   nsCOMPtr<nsITransportSecurityInfo> mTopLevelSecurityInfo;
 };
 
 #endif // nsSecureBrowserUIImpl_h
--- a/security/nss/TAG-INFO
+++ b/security/nss/TAG-INFO
@@ -1,1 +1,1 @@
-2c85f81f9b5e
+a706ba3c4fa9
--- a/security/nss/automation/clang-format/Dockerfile
+++ b/security/nss/automation/clang-format/Dockerfile
@@ -1,26 +1,35 @@
-FROM ubuntu:16.04
-MAINTAINER Franziskus Kiefer <franziskuskiefer@gmail.com>
-
-RUN useradd -d /home/worker -s /bin/bash -m worker
-WORKDIR /home/worker
+# Minimal image with clang-format 3.9.
+FROM ubuntu:18.04
+LABEL maintainer="Martin Thomson <martin.thomson@gmail.com>"
 
-# Install dependencies.
-ADD setup.sh /tmp/setup.sh
-RUN bash /tmp/setup.sh
+RUN apt-get update \
+ && apt-get install -y --no-install-recommends \
+    ca-certificates \
+    clang-format-3.9 \
+    locales \
+    mercurial \
+ && rm -rf /var/lib/apt/lists/* \
+ && apt-get autoremove -y && apt-get clean -y
 
-# Change user.
-USER worker
+RUN update-alternatives --install /usr/bin/clang-format \
+    clang-format $(which clang-format-3.9) 10
 
-# Env variables.
-ENV HOME /home/worker
 ENV SHELL /bin/bash
 ENV USER worker
-ENV LOGNAME worker
+ENV LOGNAME $USER
+ENV HOME /home/$USER
 ENV HOSTNAME taskcluster-worker
 ENV LANG en_US.UTF-8
-ENV LC_ALL en_US.UTF-8
+ENV LC_ALL $LANG
 ENV HOST localhost
 ENV DOMSUF localdomain
 
-# Entrypoint.
+RUN locale-gen $LANG \
+ && DEBIAN_FRONTEND=noninteractive dpkg-reconfigure locales
+
+RUN useradd -d $HOME -s $SHELL -m $USER
+WORKDIR $HOME
+USER $USER
+
+# Entrypoint - which only works if /home/worker/nss is mounted.
 ENTRYPOINT ["/home/worker/nss/automation/clang-format/run_clang_format.sh"]
deleted file mode 100644
--- a/security/nss/automation/clang-format/setup.sh
+++ /dev/null
@@ -1,44 +0,0 @@
-#!/usr/bin/env bash
-
-set -v -e -x
-
-# Update packages.
-export DEBIAN_FRONTEND=noninteractive
-apt-get -y update && apt-get -y upgrade
-
-# Install packages.
-apt_packages=()
-apt_packages+=('ca-certificates')
-apt_packages+=('curl')
-apt_packages+=('xz-utils')
-apt_packages+=('mercurial')
-apt_packages+=('git')
-apt_packages+=('locales')
-apt-get install -y --no-install-recommends ${apt_packages[@]}
-
-# Download clang.
-curl -L https://releases.llvm.org/3.9.1/clang+llvm-3.9.1-x86_64-linux-gnu-ubuntu-16.04.tar.xz -o clang.tar.xz
-curl -L https://releases.llvm.org/3.9.1/clang+llvm-3.9.1-x86_64-linux-gnu-ubuntu-16.04.tar.xz.sig -o clang.tar.xz.sig
-# Verify the signature.
-gpg --keyserver pool.sks-keyservers.net --recv-keys B6C8F98282B944E3B0D5C2530FC3042E345AD05D
-gpg --verify clang.tar.xz.sig
-# Install into /usr/local/.
-tar xJvf *.tar.xz -C /usr/local --strip-components=1
-
-# Cleanup.
-function cleanup() {
-  rm -f clang.tar.xz clang.tar.xz.sig
-}
-trap cleanup ERR EXIT
-
-locale-gen en_US.UTF-8
-dpkg-reconfigure locales
-
-# Cleanup.
-rm -rf ~/.ccache ~/.cache
-apt-get autoremove -y
-apt-get clean
-apt-get autoclean
-
-# We're done. Remove this script.
-rm $0
--- a/security/nss/automation/taskcluster/docker-aarch64/Dockerfile
+++ b/security/nss/automation/taskcluster/docker-aarch64/Dockerfile
@@ -15,16 +15,15 @@ RUN bash /tmp/setup.sh
 # Change user.
 # USER worker # See bug 1347473.
 
 # Env variables.
 ENV HOME /home/worker
 ENV SHELL /bin/bash
 ENV USER worker
 ENV LOGNAME worker
-ENV HOSTNAME taskcluster-worker
 ENV LANG en_US.UTF-8
 ENV LC_ALL en_US.UTF-8
 ENV HOST localhost
 ENV DOMSUF localdomain
 
 # Set a default command for debugging.
 CMD ["/bin/bash", "--login"]
--- a/security/nss/automation/taskcluster/docker-arm/Dockerfile
+++ b/security/nss/automation/taskcluster/docker-arm/Dockerfile
@@ -12,16 +12,15 @@ RUN chmod +x /home/worker/bin/*
 ADD setup.sh /tmp/setup.sh
 RUN bash /tmp/setup.sh
 
 # Env variables.
 ENV HOME /home/worker
 ENV SHELL /bin/bash
 ENV USER worker
 ENV LOGNAME worker
-ENV HOSTNAME taskcluster-worker
 ENV LANG en_US.UTF-8
 ENV LC_ALL en_US.UTF-8
 ENV HOST localhost
 ENV DOMSUF localdomain
 
 # Set a default command for debugging.
 CMD ["/bin/bash", "--login"]
new file mode 100644
--- /dev/null
+++ b/security/nss/automation/taskcluster/docker-builds/Dockerfile
@@ -0,0 +1,75 @@
+# Dockerfile for building extra builds.  This includes more tools than the
+# default image, so it's a fair bit bigger.  Only use this for builds where
+# the smaller docker image is missing something.  These builds will run on
+# the leaner configuration.
+FROM ubuntu:18.04
+LABEL maintainer="Martin Thomson <martin.thomson@gmail.com>"
+
+RUN dpkg --add-architecture i386
+RUN apt-get update \
+ && apt-get install -y --no-install-recommends \
+    build-essential \
+    ca-certificates \
+    clang-4.0 \
+    clang \
+    cmake \
+    curl \
+    g++-4.8-multilib \
+    g++-5-multilib \
+    g++-6-multilib \
+    g++-multilib \
+    git \
+    gyp \
+    libelf-dev \
+    libdw-dev \
+    libssl-dev \
+    libssl-dev:i386 \
+    libxml2-utils \
+    lib32z1-dev \
+    linux-libc-dev:i386 \
+    llvm-dev \
+    locales \
+    mercurial \
+    ninja-build \
+    pkg-config \
+    valgrind \
+    zlib1g-dev \
+ && rm -rf /var/lib/apt/lists/* \
+ && apt-get autoremove -y && apt-get clean -y
+
+# Latest version of abigail-tools
+RUN apt-get update \
+ && apt-get install -y --no-install-recommends automake libtool libxml2-dev \
+ && git clone git://sourceware.org/git/libabigail.git /tmp/libabigail \
+ && cd /tmp/libabigail \
+ && autoreconf -fi \
+ && ./configure --prefix=/usr --disable-static --disable-apidoc --disable-manual \
+ && make && make install \
+ && rm -rf /tmp/libabigail \
+ && apt-get remove -y automake libtool libxml2-dev \
+ && rm -rf /var/lib/apt/lists/* \
+ && apt-get autoremove -y && apt-get clean -y
+
+ENV SHELL /bin/bash
+ENV USER worker
+ENV LOGNAME $USER
+ENV HOME /home/$USER
+ENV LANG en_US.UTF-8
+ENV LC_ALL $LANG
+ENV HOST localhost
+ENV DOMSUF localdomain
+
+RUN locale-gen $LANG \
+ && DEBIAN_FRONTEND=noninteractive dpkg-reconfigure locales
+
+RUN useradd -d $HOME -s $SHELL -m $USER
+WORKDIR $HOME
+
+# Add build and test scripts.
+ADD bin $HOME/bin
+RUN chmod +x $HOME/bin/*
+
+USER $USER
+
+# Set a default command for debugging.
+CMD ["/bin/bash", "--login"]
rename from security/nss/automation/taskcluster/docker-clang-3.9/bin/checkout.sh
rename to security/nss/automation/taskcluster/docker-builds/bin/checkout.sh
deleted file mode 100644
--- a/security/nss/automation/taskcluster/docker-clang-3.9/Dockerfile
+++ /dev/null
@@ -1,30 +0,0 @@
-FROM ubuntu:16.04
-MAINTAINER Tim Taubert <ttaubert@mozilla.com>
-
-RUN useradd -d /home/worker -s /bin/bash -m worker
-WORKDIR /home/worker
-
-# Add build and test scripts.
-ADD bin /home/worker/bin
-RUN chmod +x /home/worker/bin/*
-
-# Install dependencies.
-ADD setup.sh /tmp/setup.sh
-RUN bash /tmp/setup.sh
-
-# Change user.
-USER worker
-
-# Env variables.
-ENV HOME /home/worker
-ENV SHELL /bin/bash
-ENV USER worker
-ENV LOGNAME worker
-ENV HOSTNAME taskcluster-worker
-ENV LANG en_US.UTF-8
-ENV LC_ALL en_US.UTF-8
-ENV HOST localhost
-ENV DOMSUF localdomain
-
-# Set a default command for debugging.
-CMD ["/bin/bash", "--login"]
deleted file mode 100644
--- a/security/nss/automation/taskcluster/docker-clang-3.9/setup.sh
+++ /dev/null
@@ -1,46 +0,0 @@
-#!/usr/bin/env bash
-
-set -v -e -x
-
-# Update packages.
-export DEBIAN_FRONTEND=noninteractive
-apt-get -y update && apt-get -y upgrade
-
-# Need this to add keys for PPAs below.
-apt-get install -y --no-install-recommends apt-utils
-
-apt_packages=()
-apt_packages+=('ca-certificates')
-apt_packages+=('curl')
-apt_packages+=('locales')
-apt_packages+=('xz-utils')
-
-# Latest Mercurial.
-apt_packages+=('mercurial')
-apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 41BD8711B1F0EC2B0D85B91CF59CE3A8323293EE
-echo "deb http://ppa.launchpad.net/mercurial-ppa/releases/ubuntu xenial main" > /etc/apt/sources.list.d/mercurial.list
-
-# Install packages.
-apt-get -y update
-apt-get install -y --no-install-recommends ${apt_packages[@]}
-
-# Download clang.
-curl -LO https://releases.llvm.org/3.9.1/clang+llvm-3.9.1-x86_64-linux-gnu-ubuntu-16.04.tar.xz
-curl -LO https://releases.llvm.org/3.9.1/clang+llvm-3.9.1-x86_64-linux-gnu-ubuntu-16.04.tar.xz.sig
-# Verify the signature.
-gpg --keyserver pool.sks-keyservers.net --recv-keys B6C8F98282B944E3B0D5C2530FC3042E345AD05D
-gpg --verify *.tar.xz.sig
-# Install into /usr/local/.
-tar xJvf *.tar.xz -C /usr/local --strip-components=1
-# Cleanup.
-rm *.tar.xz*
-
-locale-gen en_US.UTF-8
-dpkg-reconfigure locales
-
-# Cleanup.
-rm -rf ~/.ccache ~/.cache
-apt-get autoremove -y
-apt-get clean
-apt-get autoclean
-rm $0
new file mode 100644
--- /dev/null
+++ b/security/nss/automation/taskcluster/docker-clang-format/Dockerfile
@@ -0,0 +1,38 @@
+# Minimal image with clang-format 3.9.
+FROM ubuntu:18.04
+LABEL maintainer="Martin Thomson <martin.thomson@gmail.com>"
+
+RUN apt-get update \
+ && apt-get install -y --no-install-recommends \
+    ca-certificates \
+    clang-format-3.9 \
+    locales \
+    mercurial \
+ && rm -rf /var/lib/apt/lists/* \
+ && apt-get autoremove -y && apt-get clean -y
+
+RUN update-alternatives --install /usr/bin/clang-format \
+    clang-format $(which clang-format-3.9) 10
+
+ENV SHELL /bin/bash
+ENV USER worker
+ENV LOGNAME $USER
+ENV HOME /home/$USER
+ENV LANG en_US.UTF-8
+ENV LC_ALL $LANG
+ENV HOST localhost
+ENV DOMSUF localdomain
+
+RUN locale-gen $LANG \
+ && DEBIAN_FRONTEND=noninteractive dpkg-reconfigure locales
+
+RUN useradd -d $HOME -s $SHELL -m $USER
+WORKDIR $HOME
+
+ADD bin $HOME/bin
+RUN chmod +x $HOME/bin/*
+
+USER $USER
+
+# Set a default command for debugging.
+CMD ["/bin/bash", "--login"]
copy from security/nss/automation/taskcluster/docker-clang-3.9/bin/checkout.sh
copy to security/nss/automation/taskcluster/docker-clang-format/bin/checkout.sh
--- a/security/nss/automation/taskcluster/docker-decision/Dockerfile
+++ b/security/nss/automation/taskcluster/docker-decision/Dockerfile
@@ -1,30 +1,37 @@
-FROM ubuntu:16.04
-MAINTAINER Tim Taubert <ttaubert@mozilla.com>
-
-RUN useradd -d /home/worker -s /bin/bash -m worker
-WORKDIR /home/worker
-
-# Add build and test scripts.
-ADD bin /home/worker/bin
-RUN chmod +x /home/worker/bin/*
+# Minimal image for running the decision task.
+FROM ubuntu:18.04
+LABEL maintainer="Martin Thomson <martin.thomson@gmail.com>"
 
-# Install dependencies.
-ADD setup.sh /tmp/setup.sh
-RUN bash /tmp/setup.sh
+RUN apt-get update \
+ && apt-get install -y --no-install-recommends \
+    ca-certificates \
+    curl \
+    locales \
+    mercurial \
+    nodejs \
+ && rm -rf /var/lib/apt/lists/* \
+ && apt-get autoremove -y && apt-get clean -y
 
-# Change user.
-USER worker
-
-# Env variables.
-ENV HOME /home/worker
 ENV SHELL /bin/bash
 ENV USER worker
-ENV LOGNAME worker
-ENV HOSTNAME taskcluster-worker
+ENV LOGNAME $USER
+ENV HOME /home/$USER
 ENV LANG en_US.UTF-8
-ENV LC_ALL en_US.UTF-8
+ENV LC_ALL $LANG
 ENV HOST localhost
 ENV DOMSUF localdomain
 
+RUN locale-gen $LANG \
+ && DEBIAN_FRONTEND=noninteractive dpkg-reconfigure locales
+
+RUN useradd -d $HOME -s $SHELL -m $USER
+WORKDIR $HOME
+
+# Add build and test scripts.
+ADD bin $HOME/bin
+RUN chmod +x $HOME/bin/*
+
+USER $USER
+
 # Set a default command for debugging.
 CMD ["/bin/bash", "--login"]
deleted file mode 100644
--- a/security/nss/automation/taskcluster/docker-decision/setup.sh
+++ /dev/null
@@ -1,31 +0,0 @@
-#!/usr/bin/env bash
-
-set -v -e -x
-
-# Update packages.
-export DEBIAN_FRONTEND=noninteractive
-apt-get -y update && apt-get -y upgrade
-
-# Need those to install newer packages below.
-apt-get install -y --no-install-recommends apt-utils curl ca-certificates locales
-
-# Latest Mercurial.
-apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 41BD8711B1F0EC2B0D85B91CF59CE3A8323293EE
-echo "deb http://ppa.launchpad.net/mercurial-ppa/releases/ubuntu xenial main" > /etc/apt/sources.list.d/mercurial.list
-
-# Install packages.
-apt-get -y update && apt-get install -y --no-install-recommends mercurial
-
-# Latest Node.JS.
-curl -sL https://deb.nodesource.com/setup_6.x | bash -
-apt-get install -y --no-install-recommends nodejs
-
-locale-gen en_US.UTF-8
-dpkg-reconfigure locales
-
-# Cleanup.
-rm -rf ~/.ccache ~/.cache
-apt-get autoremove -y
-apt-get clean
-apt-get autoclean
-rm $0
--- a/security/nss/automation/taskcluster/docker-fuzz/Dockerfile
+++ b/security/nss/automation/taskcluster/docker-fuzz/Dockerfile
@@ -1,33 +1,59 @@
-FROM ubuntu:16.04
-MAINTAINER Tim Taubert <ttaubert@mozilla.com>
-
-RUN useradd -d /home/worker -s /bin/bash -m worker
-WORKDIR /home/worker
-
-# Add build and test scripts.
-ADD bin /home/worker/bin
-RUN chmod +x /home/worker/bin/*
+# Dockerfile for running fuzzing tests.
+#
+# Note that when running this, you need to add `--cap-add SYS_PTRACE` to the
+# docker invocation or ASAN won't work.
+# On taskcluster use `features: ["allowPtrace"]`.
+# See https://github.com/google/sanitizers/issues/764#issuecomment-276700920
+FROM ubuntu:18.04
+LABEL maintainer="Martin Thomson <martin.thomson@gmail.com>"
 
-# Install dependencies.
-ADD setup.sh /tmp/setup.sh
-RUN bash /tmp/setup.sh
+RUN dpkg --add-architecture i386
+RUN apt-get update \
+ && apt-get install -y --no-install-recommends \
+    build-essential \
+    ca-certificates \
+    clang \
+    clang-tools \
+    curl \
+    g++-multilib \
+    git \
+    gyp \
+    libssl-dev \
+    libssl-dev:i386 \
+    libxml2-utils \
+    lib32z1-dev \
+    linux-libc-dev:i386 \
+    llvm-dev \
+    locales \
+    mercurial \
+    ninja-build \
+    pkg-config \
+    valgrind \
+    zlib1g-dev \
+ && rm -rf /var/lib/apt/lists/* \
+ && apt-get autoremove -y && apt-get clean -y
 
-# Change user.
-USER worker
-
-# Env variables.
-ENV HOME /home/worker
 ENV SHELL /bin/bash
 ENV USER worker
-ENV LOGNAME worker
-ENV HOSTNAME taskcluster-worker
+ENV LOGNAME $USER
+ENV HOME /home/$USER
 ENV LANG en_US.UTF-8
-ENV LC_ALL en_US.UTF-8
+ENV LC_ALL $LANG
 ENV HOST localhost
 ENV DOMSUF localdomain
 
-# LLVM 4.0
-ENV PATH "${PATH}:/home/worker/third_party/llvm-build/Release+Asserts/bin/"
+RUN locale-gen $LANG \
+ && DEBIAN_FRONTEND=noninteractive dpkg-reconfigure locales
+
+RUN useradd -d $HOME -s $SHELL -m $USER
+WORKDIR $HOME
+
+# Add build and test scripts.
+ADD bin $HOME/bin
+RUN chmod +x $HOME/bin/*
+
+# Change user.
+USER $USER
 
 # Set a default command for debugging.
 CMD ["/bin/bash", "--login"]
deleted file mode 100644
--- a/security/nss/automation/taskcluster/docker-fuzz/setup.sh
+++ /dev/null
@@ -1,58 +0,0 @@
-#!/usr/bin/env bash
-
-set -v -e -x
-
-# Update packages.
-export DEBIAN_FRONTEND=noninteractive
-apt-get -y update && apt-get -y upgrade
-
-# Need this to add keys for PPAs below.
-apt-get install -y --no-install-recommends apt-utils
-
-apt_packages=()
-apt_packages+=('build-essential')
-apt_packages+=('ca-certificates')
-apt_packages+=('curl')
-apt_packages+=('git')
-apt_packages+=('gyp')
-apt_packages+=('libssl-dev')
-apt_packages+=('libxml2-utils')
-apt_packages+=('locales')
-apt_packages+=('ninja-build')
-apt_packages+=('pkg-config')
-apt_packages+=('zlib1g-dev')
-
-# 32-bit builds
-apt_packages+=('gcc-multilib')
-apt_packages+=('g++-multilib')
-
-# Latest Mercurial.
-apt_packages+=('mercurial')
-apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 41BD8711B1F0EC2B0D85B91CF59CE3A8323293EE
-echo "deb http://ppa.launchpad.net/mercurial-ppa/releases/ubuntu xenial main" > /etc/apt/sources.list.d/mercurial.list
-
-# Install packages.
-apt-get -y update
-apt-get install -y --no-install-recommends ${apt_packages[@]}
-
-# 32-bit builds
-dpkg --add-architecture i386
-apt-get -y update
-apt-get install -y --no-install-recommends libssl-dev:i386
-
-# Install LLVM/clang-4.0.
-mkdir clang-tmp
-git clone -n --depth 1 https://chromium.googlesource.com/chromium/src/tools/clang clang-tmp/clang
-git -C clang-tmp/clang checkout HEAD scripts/update.py
-clang-tmp/clang/scripts/update.py
-rm -fr clang-tmp
-
-locale-gen en_US.UTF-8
-dpkg-reconfigure locales
-
-# Cleanup.
-rm -rf ~/.ccache ~/.cache
-apt-get autoremove -y
-apt-get clean
-apt-get autoclean
-rm $0
new file mode 100644
--- /dev/null
+++ b/security/nss/automation/taskcluster/docker-fuzz32/Dockerfile
@@ -0,0 +1,73 @@
+# Dockerfile for running fuzzing tests on linux32.
+#
+# This is a temporary workaround for bugs in clang that make it incompatible
+# with Ubuntu 18.04 (see bug 1488148). This image can be removed once a new
+# release of LLVM includes the necessary fixes.
+
+FROM ubuntu:16.04
+LABEL maintainer="Martin Thomson <martin.thomson@gmail.com>"
+
+RUN dpkg --add-architecture i386
+RUN apt-get update \
+ && apt-get install -y --no-install-recommends \
+    build-essential \
+    ca-certificates \
+    curl \
+    g++-multilib \
+    git \
+    gyp \
+    libssl-dev \
+    libssl-dev:i386 \
+    libxml2-utils \
+    lib32z1-dev \
+    linux-libc-dev:i386 \
+    locales \
+    mercurial \
+    ninja-build \
+    pkg-config \
+    software-properties-common \
+    valgrind \
+    zlib1g-dev \
+ && rm -rf /var/lib/apt/lists/* \
+ && apt-get autoremove -y && apt-get clean -y
+
+# Install clang and tools from the LLVM PPA.
+RUN curl -sf https://apt.llvm.org/llvm-snapshot.gpg.key | apt-key add - \
+ && apt-add-repository "deb http://apt.llvm.org/xenial/ llvm-toolchain-xenial-6.0 main" \
+ && apt-get update \
+ && apt-get install -y --no-install-recommends \
+    clang-6.0 \
+    clang-tools-6.0 \
+    llvm-6.0-dev \
+ && rm -rf /var/lib/apt/lists/* \
+ && apt-get autoremove -y && apt-get clean -y
+
+# Alias all the clang commands.
+RUN for i in $(dpkg -L clang-6.0 clang-tools-6.0 | grep '^/usr/bin/' | xargs -i basename {} -6.0); do \
+      update-alternatives --install "/usr/bin/$i" "$i" "/usr/bin/${i}-6.0" 10; \
+    done
+
+ENV SHELL /bin/bash
+ENV USER worker
+ENV LOGNAME $USER
+ENV HOME /home/$USER
+ENV LANG en_US.UTF-8
+ENV LC_ALL $LANG
+ENV HOST localhost
+ENV DOMSUF localdomain
+
+RUN locale-gen $LANG \
+ && DEBIAN_FRONTEND=noninteractive dpkg-reconfigure locales
+
+RUN useradd -d $HOME -s $SHELL -m $USER
+WORKDIR $HOME
+
+# Add build and test scripts.
+ADD bin $HOME/bin
+RUN chmod +x $HOME/bin/*
+
+# Change user.
+USER $USER
+
+# Set a default command for debugging.
+CMD ["/bin/bash", "--login"]
copy from security/nss/automation/taskcluster/docker-clang-3.9/bin/checkout.sh
copy to security/nss/automation/taskcluster/docker-fuzz32/bin/checkout.sh
--- a/security/nss/automation/taskcluster/docker-gcc-4.4/Dockerfile
+++ b/security/nss/automation/taskcluster/docker-gcc-4.4/Dockerfile
@@ -1,30 +1,39 @@
 FROM ubuntu:14.04
-MAINTAINER Tim Taubert <ttaubert@mozilla.com>
-
-RUN useradd -d /home/worker -s /bin/bash -m worker
-WORKDIR /home/worker
-
-# Add build and test scripts.
-ADD bin /home/worker/bin
-RUN chmod +x /home/worker/bin/*
+LABEL maintainer="Martin Thomson <martin.thomson@gmail.com>"
 
-# Install dependencies.
-ADD setup.sh /tmp/setup.sh
-RUN bash /tmp/setup.sh
+RUN dpkg --add-architecture i386
+RUN apt-get update \
+ && apt-get install -y --no-install-recommends \
+    ca-certificates \
+    g++-4.4 \
+    gcc-4.4 \
+    locales \
+    make \
+    mercurial \
+    zlib1g-dev \
+ && rm -rf /var/lib/apt/lists/* \
+ && apt-get autoremove -y && apt-get clean -y
 
-# Change user.
-USER worker
-
-# Env variables.
-ENV HOME /home/worker
 ENV SHELL /bin/bash
 ENV USER worker
-ENV LOGNAME worker
-ENV HOSTNAME taskcluster-worker
+ENV LOGNAME $USER
+ENV HOME /home/$USER
 ENV LANG en_US.UTF-8
-ENV LC_ALL en_US.UTF-8
+ENV LC_ALL $LANG
 ENV HOST localhost
 ENV DOMSUF localdomain
 
+RUN locale-gen $LANG \
+ && DEBIAN_FRONTEND=noninteractive dpkg-reconfigure locales
+
+RUN useradd -d $HOME -s $SHELL -m $USER
+WORKDIR $HOME
+
+# Add build and test scripts.
+ADD bin $HOME/bin
+RUN chmod +x $HOME/bin/*
+
+USER $USER
+
 # Set a default command for debugging.
 CMD ["/bin/bash", "--login"]
deleted file mode 100644
--- a/security/nss/automation/taskcluster/docker-gcc-4.4/setup.sh
+++ /dev/null
@@ -1,30 +0,0 @@
-#!/usr/bin/env bash
-
-set -v -e -x
-
-# Update packages.
-export DEBIAN_FRONTEND=noninteractive
-apt-get -y update && apt-get -y upgrade
-
-apt_packages=()
-apt_packages+=('ca-certificates')
-apt_packages+=('g++-4.4')
-apt_packages+=('gcc-4.4')
-apt_packages+=('locales')
-apt_packages+=('make')
-apt_packages+=('mercurial')
-apt_packages+=('zlib1g-dev')
-
-# Install packages.
-apt-get -y update
-apt-get install -y --no-install-recommends ${apt_packages[@]}
-
-locale-gen en_US.UTF-8
-dpkg-reconfigure locales
-
-# Cleanup.
-rm -rf ~/.ccache ~/.cache
-apt-get autoremove -y
-apt-get clean
-apt-get autoclean
-rm $0
new file mode 100644
--- /dev/null
+++ b/security/nss/automation/taskcluster/docker-interop/Dockerfile
@@ -0,0 +1,56 @@
+# Dockerfile for running interop tests.
+# This includes Rust, golang, and nodejs.
+FROM ubuntu:18.04
+LABEL maintainer="Martin Thomson <martin.thomson@gmail.com>"
+
+RUN dpkg --add-architecture i386
+RUN apt-get update \
+ && apt-get install -y --no-install-recommends \
+    build-essential \
+    ca-certificates \
+    clang \
+    cmake \
+    curl \
+    g++-multilib \
+    git \
+    golang \
+    gyp \
+    libxml2-utils \
+    lib32z1-dev \
+    linux-libc-dev:i386 \
+    llvm-dev \
+    locales \
+    mercurial \
+    ninja-build \
+    npm \
+    pkg-config \
+    zlib1g-dev \
+ && rm -rf /var/lib/apt/lists/* \
+ && apt-get autoremove -y && apt-get clean -y
+
+ENV SHELL /bin/bash
+ENV USER worker
+ENV LOGNAME $USER
+ENV HOME /home/$USER
+ENV LANG en_US.UTF-8
+ENV LC_ALL $LANG
+ENV HOST localhost
+ENV DOMSUF localdomain
+
+RUN locale-gen $LANG \
+ && DEBIAN_FRONTEND=noninteractive dpkg-reconfigure locales
+
+RUN useradd -d $HOME -s $SHELL -m $USER
+WORKDIR $HOME
+
+# Add build and test scripts.
+ADD bin $HOME/bin
+RUN chmod +x $HOME/bin/*
+
+USER $USER
+
+# Install Rust stable as $USER.
+RUN curl https://sh.rustup.rs -sSf | sh -s -- -y
+
+# Set a default command for debugging.
+CMD ["/bin/bash", "--login"]
copy from security/nss/automation/taskcluster/docker-clang-3.9/bin/checkout.sh
copy to security/nss/automation/taskcluster/docker-interop/bin/checkout.sh
--- a/security/nss/automation/taskcluster/docker/Dockerfile
+++ b/security/nss/automation/taskcluster/docker/Dockerfile
@@ -1,30 +1,49 @@
-FROM ubuntu:16.04
-MAINTAINER Tim Taubert <ttaubert@mozilla.com>
-
-RUN useradd -d /home/worker -s /bin/bash -m worker
-WORKDIR /home/worker
+# Lean image for running the bulk of the NSS CI tests on taskcluster.
+FROM ubuntu:18.04
+LABEL maintainer="Martin Thomson <martin.thomson@gmail.com>"
 
-# Add build and test scripts.
-ADD bin /home/worker/bin
-RUN chmod +x /home/worker/bin/*
+RUN dpkg --add-architecture i386
+RUN apt-get update \
+ && apt-get install -y --no-install-recommends \
+    build-essential \
+    ca-certificates \
+    clang \
+    curl \
+    g++-multilib \
+    git \
+    gyp \
+    libxml2-utils \
+    lib32z1-dev \
+    linux-libc-dev:i386 \
+    llvm-dev \
+    locales \
+    mercurial \
+    ninja-build \
+    pkg-config \
+    zlib1g-dev \
+ && rm -rf /var/lib/apt/lists/* \
+ && apt-get autoremove -y && apt-get clean -y
 
-# Install dependencies.
-ADD setup.sh /tmp/setup.sh
-RUN bash /tmp/setup.sh
-
-# Env variables.
-ENV HOME /home/worker
 ENV SHELL /bin/bash
 ENV USER worker
-ENV LOGNAME worker
-ENV HOSTNAME taskcluster-worker
+ENV LOGNAME $USER
+ENV HOME /home/$USER
 ENV LANG en_US.UTF-8
-ENV LC_ALL en_US.UTF-8
+ENV LC_ALL $LANG
 ENV HOST localhost
 ENV DOMSUF localdomain
 
-# Rust + Go
-ENV PATH "${PATH}:/home/worker/.cargo/bin/:/usr/lib/go-1.6/bin"
+RUN locale-gen $LANG \
+ && DEBIAN_FRONTEND=noninteractive dpkg-reconfigure locales
+
+RUN useradd -d $HOME -s $SHELL -m $USER
+WORKDIR $HOME
+
+# Add build and test scripts.
+ADD bin $HOME/bin
+RUN chmod +x $HOME/bin/*
+
+USER $USER
 
 # Set a default command for debugging.
 CMD ["/bin/bash", "--login"]
deleted file mode 100644
--- a/security/nss/automation/taskcluster/docker/setup.sh
+++ /dev/null
@@ -1,75 +0,0 @@
-#!/usr/bin/env bash
-
-set -v -e -x
-
-# Update packages.
-export DEBIAN_FRONTEND=noninteractive
-apt-get -y update && apt-get -y upgrade
-
-# Need this to add keys for PPAs below.
-apt-get install -y --no-install-recommends apt-utils
-
-apt_packages=()
-apt_packages+=('build-essential')
-apt_packages+=('ca-certificates')
-apt_packages+=('clang-5.0')
-apt_packages+=('curl')
-apt_packages+=('npm')
-apt_packages+=('git')
-apt_packages+=('golang-1.6')
-apt_packages+=('libxml2-utils')
-apt_packages+=('locales')
-apt_packages+=('ninja-build')
-apt_packages+=('pkg-config')
-apt_packages+=('zlib1g-dev')
-apt_packages+=('cmake')
-
-# 32-bit builds
-apt_packages+=('lib32z1-dev')
-apt_packages+=('gcc-multilib')
-apt_packages+=('g++-multilib')
-
-# ct-verif and sanitizers
-apt_packages+=('valgrind')
-
-# Latest Mercurial.
-apt_packages+=('mercurial')
-apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 41BD8711B1F0EC2B0D85B91CF59CE3A8323293EE
-echo "deb http://ppa.launchpad.net/mercurial-ppa/releases/ubuntu xenial main" > /etc/apt/sources.list.d/mercurial.list
-
-# gcc 4.8 and 6
-apt_packages+=('g++-6')
-apt_packages+=('g++-4.8')
-apt_packages+=('g++-6-multilib')
-apt_packages+=('g++-4.8-multilib')
-apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 60C317803A41BA51845E371A1E9377A2BA9EF27F
-echo "deb http://ppa.launchpad.net/ubuntu-toolchain-r/test/ubuntu xenial main" > /etc/apt/sources.list.d/toolchain.list
-
-# Install packages.
-apt-get -y update
-apt-get install -y --no-install-recommends ${apt_packages[@]}
-
-# Latest version of abigail-tools
-apt-get install -y libxml2-dev autoconf libelf-dev libdw-dev libtool
-git clone git://sourceware.org/git/libabigail.git
-cd ./libabigail
-autoreconf -fi
-./configure --prefix=/usr --disable-static --disable-apidoc --disable-manual
-make
-make install
-cd ..
-apt-get remove -y libxml2-dev autoconf libtool
-rm -rf libabigail
-
-# Install latest Rust (stable).
-su worker -c "curl https://sh.rustup.rs -sSf | sh -s -- -y"
-
-locale-gen en_US.UTF-8
-dpkg-reconfigure locales
-
-# Cleanup.
-rm -rf ~/.ccache ~/.cache
-apt-get autoremove -y
-apt-get clean
-apt-get autoclean
-rm $0
--- a/security/nss/automation/taskcluster/graph/src/extend.js
+++ b/security/nss/automation/taskcluster/graph/src/extend.js
@@ -5,31 +5,47 @@
 import merge from "./merge";
 import * as queue from "./queue";
 
 const LINUX_IMAGE = {
   name: "linux",
   path: "automation/taskcluster/docker"
 };
 
-const LINUX_CLANG39_IMAGE = {
-  name: "linux-clang-3.9",
-  path: "automation/taskcluster/docker-clang-3.9"
+const LINUX_BUILDS_IMAGE = {
+  name: "linux-builds",
+  path: "automation/taskcluster/docker-builds"
+};
+
+const LINUX_INTEROP_IMAGE = {
+  name: "linux-interop",
+  path: "automation/taskcluster/docker-interop"
+};
+
+const CLANG_FORMAT_IMAGE = {
+  name: "clang-format",
+  path: "automation/taskcluster/docker-clang-format"
 };
 
 const LINUX_GCC44_IMAGE = {
   name: "linux-gcc-4.4",
   path: "automation/taskcluster/docker-gcc-4.4"
 };
 
 const FUZZ_IMAGE = {
   name: "fuzz",
   path: "automation/taskcluster/docker-fuzz"
 };
 
+// Bug 1488148 - temporary image for fuzzing 32-bit builds.
+const FUZZ_IMAGE_32 = {
+  name: "fuzz32",
+  path: "automation/taskcluster/docker-fuzz32"
+};
+
 const HACL_GEN_IMAGE = {
   name: "hacl",
   path: "automation/taskcluster/docker-hacl"
 };
 
 const SAW_IMAGE = {
   name: "saw",
   path: "automation/taskcluster/docker-saw"
@@ -54,17 +70,17 @@ queue.filter(task => {
     }
 
     // Make modular builds only on Linux make.
     if (task.symbol == "modular" && task.collection != "make") {
       return false;
     }
   }
 
-  if (task.tests == "bogo" || task.tests == "interop") {
+  if (task.tests == "bogo" || task.tests == "interop" || task.tests == "tlsfuzzer") {
     // No windows
     if (task.platform == "windows2012-64" ||
         task.platform == "windows2012-32") {
       return false;
     }
 
     // No ARM; TODO: enable
     if (task.platform == "aarch64") {
@@ -84,17 +100,19 @@ queue.filter(task => {
 
   // Only old make builds have -Ddisable_libpkix=0 and can run chain tests.
   if (task.tests == "chains" && task.collection != "make") {
     return false;
   }
 
   if (task.group == "Test") {
     // Don't run test builds on old make platforms, and not for fips gyp.
-    if (task.collection == "make" || task.collection == "fips") {
+    // Disable on aarch64, see bug 1488331.
+    if (task.collection == "make" || task.collection == "fips"
+        || task.platform == "aarch64") {
       return false;
     }
   }
 
   // Don't run all additional hardware tests on ARM.
   if (task.group == "Cipher" && task.platform == "aarch64" && task.env &&
       (task.env.NSS_DISABLE_PCLMUL == "1" || task.env.NSS_DISABLE_HW_AES == "1"
        || task.env.NSS_DISABLE_AVX == "1")) {
@@ -188,18 +206,18 @@ export default async function main() {
     ],
   });
 
   await scheduleLinux("Linux 64 (ASan, debug)", {
     env: {
       UBSAN_OPTIONS: "print_stacktrace=1",
       NSS_DISABLE_ARENA_FREE_LIST: "1",
       NSS_DISABLE_UNLOAD: "1",
-      CC: "clang-5.0",
-      CCC: "clang++-5.0",
+      CC: "clang",
+      CCC: "clang++",
     },
     platform: "linux64",
     collection: "asan",
     image: LINUX_IMAGE,
     features: ["allowPtrace"],
   }, "--ubsan --asan");
 
   await scheduleLinux("Linux 64 (FIPS opt)", {
@@ -246,47 +264,47 @@ export default async function main() {
     image: "franziskus/nss-aarch64-ci",
     provisioner: "localprovisioner",
     workerType: "nss-aarch64",
     platform: "aarch64",
     maxRunTime: 7200
   };
 
   await scheduleLinux("Linux AArch64 (debug)",
-    merge({
+    merge(aarch64_base, {
       command: [
         "/bin/bash",
         "-c",
         "bin/checkout.sh && nss/automation/taskcluster/scripts/build_gyp.sh"
       ],
       collection: "debug",
-    }, aarch64_base)
+    })
   );
 
   await scheduleLinux("Linux AArch64 (opt)",
-    merge({
+    merge(aarch64_base, {
       command: [
         "/bin/bash",
         "-c",
         "bin/checkout.sh && nss/automation/taskcluster/scripts/build_gyp.sh --opt"
       ],
       collection: "opt",
-    }, aarch64_base)
+    })
   );
 
   await scheduleLinux("Linux AArch64 (debug, make)",
-    merge({
+    merge(aarch64_base, {
       env: {USE_64: "1"},
       command: [
          "/bin/bash",
          "-c",
          "bin/checkout.sh && nss/automation/taskcluster/scripts/build.sh"
       ],
       collection: "make",
-    }, aarch64_base)
+    })
   );
 
   await scheduleMac("Mac (opt)", {collection: "opt"}, "--opt");
   await scheduleMac("Mac (debug)", {collection: "debug"});
 }
 
 
 async function scheduleMac(name, base, args = "") {
@@ -298,34 +316,34 @@ async function scheduleMac(name, base, a
       HOST: "localhost",
     },
     provisioner: "localprovisioner",
     workerType: "nss-macos-10-12",
     platform: "mac"
   });
 
   // Build base definition.
-  let build_base = merge({
+  let build_base = merge(mac_base, {
     command: [
       MAC_CHECKOUT_CMD,
       ["bash", "-c",
        "nss/automation/taskcluster/scripts/build_gyp.sh", args]
     ],
     provisioner: "localprovisioner",
     workerType: "nss-macos-10-12",
     platform: "mac",
     maxRunTime: 7200,
     artifacts: [{
       expires: 24 * 7,
       type: "directory",
       path: "public"
     }],
     kind: "build",
     symbol: "B"
-  }, mac_base);
+  });
 
   // The task that builds NSPR+NSS.
   let task_build = queue.scheduleTask(merge(build_base, {name}));
 
   // The task that generates certificates.
   let task_cert = queue.scheduleTask(merge(build_base, {
     name: "Certificates",
     command: [
@@ -346,34 +364,38 @@ async function scheduleMac(name, base, a
     ]
   }));
 
   return queue.submit();
 }
 
 /*****************************************************************************/
 
-async function scheduleLinux(name, base, args = "") {
-  // Build base definition.
-  let build_base = merge({
+async function scheduleLinux(name, overrides, args = "") {
+  // Construct a base definition.  This takes |overrides| second because
+  // callers expect to be able to overwrite the |command| key.
+  let base = merge({
     command: [
       "/bin/bash",
       "-c",
       "bin/checkout.sh && nss/automation/taskcluster/scripts/build_gyp.sh " + args
     ],
+  }, overrides);
+  // The base for building.
+  let build_base = merge(base, {
     artifacts: {
       public: {
         expires: 24 * 7,
         type: "directory",
         path: "/home/worker/artifacts"
       }
     },
     kind: "build",
-    symbol: "B"
-  }, base);
+    symbol: "B",
+  });
 
   // The task that builds NSPR+NSS.
   let task_build = queue.scheduleTask(merge(build_base, {name}));
 
   // Make builds run FIPS tests, which need an extra FIPS build.
   if (base.collection == "make") {
     let extra_build = queue.scheduleTask(merge(build_base, {
       env: { NSS_FORCE_FIPS: "1" },
@@ -429,24 +451,27 @@ async function scheduleLinux(name, base,
     command: [
       "/bin/bash",
       "-c",
       "bin/checkout.sh && nss/automation/taskcluster/scripts/run_tests.sh"
     ]
   }));
 
   // Extra builds.
-  let extra_base = merge({group: "Builds"}, build_base);
+  let extra_base = merge(build_base, {
+    group: "Builds",
+    image: LINUX_BUILDS_IMAGE,
+  });
   queue.scheduleTask(merge(extra_base, {
-    name: `${name} w/ clang-5.0`,
+    name: `${name} w/ clang-4`,
     env: {
-      CC: "clang-5.0",
-      CCC: "clang++-5.0",
+      CC: "clang-4.0",
+      CCC: "clang++-4.0",
     },
-    symbol: "clang-5.0"
+    symbol: "clang-4"
   }));
 
   queue.scheduleTask(merge(extra_base, {
     name: `${name} w/ gcc-4.4`,
     image: LINUX_GCC44_IMAGE,
     env: {
       USE_64: "1",
       CC: "gcc-4.4",
@@ -469,36 +494,46 @@ async function scheduleLinux(name, base,
     env: {
       CC: "gcc-4.8",
       CCC: "g++-4.8"
     },
     symbol: "gcc-4.8"
   }));
 
   queue.scheduleTask(merge(extra_base, {
-    name: `${name} w/ gcc-6.1`,
+    name: `${name} w/ gcc-5`,
+    env: {
+      CC: "gcc-5",
+      CCC: "g++-5"
+    },
+    symbol: "gcc-5"
+  }));
+
+  queue.scheduleTask(merge(extra_base, {
+    name: `${name} w/ gcc-6`,
     env: {
       CC: "gcc-6",
       CCC: "g++-6"
     },
-    symbol: "gcc-6.1"
+    symbol: "gcc-6"
   }));
 
   queue.scheduleTask(merge(extra_base, {
     name: `${name} w/ modular builds`,
+    image: LINUX_IMAGE,
     env: {NSS_BUILD_MODULAR: "1"},
     command: [
       "/bin/bash",
       "-c",
       "bin/checkout.sh && nss/automation/taskcluster/scripts/build.sh",
     ],
     symbol: "modular"
   }));
 
-  await scheduleTestBuilds(merge(base, {group: "Test"}), args);
+  await scheduleTestBuilds(name + " Test", merge(base, {group: "Test"}), args);
 
   return queue.submit();
 }
 
 /*****************************************************************************/
 
 function scheduleFuzzingRun(base, name, target, max_len, symbol = null, corpus = null) {
   const MAX_FUZZ_TIME = 300;
@@ -529,33 +564,33 @@ async function scheduleFuzzing() {
     },
     features: ["allowPtrace"],
     platform: "linux64",
     collection: "fuzz",
     image: FUZZ_IMAGE
   };
 
   // Build base definition.
-  let build_base = merge({
+  let build_base = merge(base, {
     command: [
       "/bin/bash",
       "-c",
       "bin/checkout.sh && " +
       "nss/automation/taskcluster/scripts/build_gyp.sh -g -v --fuzz"
     ],
     artifacts: {
       public: {
         expires: 24 * 7,
         type: "directory",
         path: "/home/worker/artifacts"
       }
     },
     kind: "build",
     symbol: "B"
-  }, base);
+  });
 
   // The task that builds NSPR+NSS.
   let task_build = queue.scheduleTask(merge(build_base, {
     name: "Linux x64 (debug, fuzz)"
   }));
 
   // The task that builds NSPR+NSS (TLS fuzzing mode).
   let task_build_tls = queue.scheduleTask(merge(build_base, {
@@ -630,37 +665,37 @@ async function scheduleFuzzing32() {
       NSS_DISABLE_ARENA_FREE_LIST: "1",
       NSS_DISABLE_UNLOAD: "1",
       CC: "clang",
       CCC: "clang++"
     },
     features: ["allowPtrace"],
     platform: "linux32",
     collection: "fuzz",
-    image: FUZZ_IMAGE
+    image: FUZZ_IMAGE_32
   };
 
   // Build base definition.
-  let build_base = merge({
+  let build_base = merge(base, {
     command: [
       "/bin/bash",
       "-c",
       "bin/checkout.sh && " +
       "nss/automation/taskcluster/scripts/build_gyp.sh -g -v --fuzz -m32"
     ],
     artifacts: {
       public: {
         expires: 24 * 7,
         type: "directory",
         path: "/home/worker/artifacts"
       }
     },
     kind: "build",
     symbol: "B"
-  }, base);
+  });
 
   // The task that builds NSPR+NSS.
   let task_build = queue.scheduleTask(merge(build_base, {
     name: "Linux 32 (debug, fuzz)"
   }));
 
   // The task that builds NSPR+NSS (TLS fuzzing mode).
   let task_build_tls = queue.scheduleTask(merge(build_base, {
@@ -723,44 +758,51 @@ async function scheduleFuzzing32() {
   scheduleFuzzingRun(tls_fm_base, "DTLS Client", "dtls-client", 20000, "dtls-client");
   scheduleFuzzingRun(tls_fm_base, "DTLS Server", "dtls-server", 20000, "dtls-server");
 
   return queue.submit();
 }
 
 /*****************************************************************************/
 
-async function scheduleTestBuilds(base, args = "") {
+async function scheduleTestBuilds(name, base, args = "") {
   // Build base definition.
-  let build = merge({
+  let build = merge(base, {
     command: [
       "/bin/bash",
       "-c",
       "bin/checkout.sh && " +
       "nss/automation/taskcluster/scripts/build_gyp.sh -g -v --test --ct-verif " + args
     ],
     artifacts: {
       public: {
         expires: 24 * 7,
         type: "directory",
         path: "/home/worker/artifacts"
       }
     },
     kind: "build",
     symbol: "B",
-    name: "Linux 64 (debug, test)"
-  }, base);
+    name: `${name} build`,
+  });
+
+  // On linux we have a specialized build image for building.
+  if (build.platform === "linux32" || build.platform === "linux64") {
+    build = merge(build, {
+      image: LINUX_BUILDS_IMAGE,
+    });
+  }
 
   // The task that builds NSPR+NSS.
   let task_build = queue.scheduleTask(build);
 
   // Schedule tests.
   queue.scheduleTask(merge(base, {
     parent: task_build,
-    name: "mpi",
+    name: `${name} mpi tests`,
     command: [
       "/bin/bash",
       "-c",
       "bin/checkout.sh && nss/automation/taskcluster/scripts/run_tests.sh"
     ],
     tests: "mpi",
     cycle: "standard",
     symbol: "mpi",
@@ -768,17 +810,17 @@ async function scheduleTestBuilds(base, 
   }));
   queue.scheduleTask(merge(base, {
     parent: task_build,
     command: [
       "/bin/bash",
       "-c",
       "bin/checkout.sh && nss/automation/taskcluster/scripts/run_tests.sh"
     ],
-    name: "Gtests",
+    name: `${name} gtests`,
     symbol: "Gtest",
     tests: "gtests",
     cycle: "standard",
     kind: "test"
   }));
 
   return queue.submit();
 }
@@ -876,28 +918,39 @@ async function scheduleWindows(name, bas
   }));
 
   return queue.submit();
 }
 
 /*****************************************************************************/
 
 function scheduleTests(task_build, task_cert, test_base) {
-  test_base = merge({kind: "test"}, test_base);
+  test_base = merge(test_base, {kind: "test"});
 
   // Schedule tests that do NOT need certificates.
   let no_cert_base = merge(test_base, {parent: task_build});
   queue.scheduleTask(merge(no_cert_base, {
     name: "Gtests", symbol: "Gtest", tests: "ssl_gtests gtests", cycle: "standard"
   }));
   queue.scheduleTask(merge(no_cert_base, {
-    name: "Bogo tests", symbol: "Bogo", tests: "bogo", cycle: "standard"
+    name: "Bogo tests",
+    symbol: "Bogo",
+    tests: "bogo",
+    cycle: "standard",
+    image: LINUX_INTEROP_IMAGE,
   }));
   queue.scheduleTask(merge(no_cert_base, {
-    name: "Interop tests", symbol: "Interop", tests: "interop", cycle: "standard"
+    name: "Interop tests",
+    symbol: "Interop",
+    tests: "interop",
+    cycle: "standard",
+    image: LINUX_INTEROP_IMAGE,
+  }));
+  queue.scheduleTask(merge(no_cert_base, {
+    name: "tlsfuzzer tests", symbol: "tlsfuzzer", tests: "tlsfuzzer", cycle: "standard"
   }));
   queue.scheduleTask(merge(no_cert_base, {
     name: "Chains tests", symbol: "Chains", tests: "chains"
   }));
   queue.scheduleTask(merge(no_cert_base, {
     name: "Cipher tests", symbol: "Default", tests: "cipher", group: "Cipher"
   }));
   queue.scheduleTask(merge(no_cert_base, {
@@ -969,32 +1022,32 @@ function scheduleTests(task_build, task_
 /*****************************************************************************/
 
 async function scheduleTools() {
   let base = {
     platform: "nss-tools",
     kind: "test"
   };
 
-  //ABI check task
+  // ABI check task
   queue.scheduleTask(merge(base, {
     symbol: "abi",
     name: "abi",
-    image: LINUX_IMAGE,
+    image: LINUX_BUILDS_IMAGE,
     command: [
       "/bin/bash",
       "-c",
       "bin/checkout.sh && nss/automation/taskcluster/scripts/check_abi.sh"
     ],
   }));
 
   queue.scheduleTask(merge(base, {
-    symbol: "clang-format-3.9",
-    name: "clang-format-3.9",
-    image: LINUX_CLANG39_IMAGE,
+    symbol: "clang-format",
+    name: "clang-format",
+    image: CLANG_FORMAT_IMAGE,
     command: [
       "/bin/bash",
       "-c",
       "bin/checkout.sh && nss/automation/clang-format/run_clang_format.sh"
     ]
   }));
 
   queue.scheduleTask(merge(base, {
--- a/security/nss/automation/taskcluster/graph/src/try_syntax.js
+++ b/security/nss/automation/taskcluster/graph/src/try_syntax.js
@@ -32,17 +32,17 @@ function parseOptions(opts) {
   if (platforms.length == 0 && opts.platform != "none") {
     platforms = allPlatforms;
   }
 
   // Parse unit tests.
   let aliases = {"gtests": "gtest"};
   let allUnitTests = ["bogo", "crmf", "chains", "cipher", "db", "ec", "fips",
                       "gtest", "interop", "lowhash", "merge", "sdr", "smime", "tools",
-                      "ssl", "mpi", "scert", "spki", "policy"];
+                      "ssl", "mpi", "scert", "spki", "policy", "tlsfuzzer"];
   let unittests = intersect(opts.unittests.split(/\s*,\s*/).map(t => {
     return aliases[t] || t;
   }), allUnitTests);
 
   // If the given value is "all" run all tests.
   // If it's nonsense then don't run any tests.
   if (opts.unittests == "all") {
     unittests = allUnitTests;
--- a/security/nss/automation/taskcluster/scripts/build_image.sh
+++ b/security/nss/automation/taskcluster/scripts/build_image.sh
@@ -8,17 +8,17 @@ raise_error() {
    echo "[taskcluster-image-build:error] $1"
    exit 1
 }
 
 # Ensure that the PROJECT is specified so the image can be indexed
 test -n "$PROJECT" || raise_error "Project must be provided."
 test -n "$HASH" || raise_error "Context Hash must be provided."
 
-CONTEXT_PATH=/home/worker/nss/$CONTEXT_PATH
+CONTEXT_PATH="/home/worker/nss/$CONTEXT_PATH"
 
-test -d $CONTEXT_PATH || raise_error "Context Path $CONTEXT_PATH does not exist."
+test -d "$CONTEXT_PATH" || raise_error "Context Path $CONTEXT_PATH does not exist."
 test -f "$CONTEXT_PATH/Dockerfile" || raise_error "Dockerfile must be present in $CONTEXT_PATH."
 
-docker build -t $PROJECT:$HASH $CONTEXT_PATH
+docker build -t "$PROJECT:$HASH" "$CONTEXT_PATH"
 
 mkdir /artifacts
-docker save $PROJECT:$HASH > /artifacts/image.tar
+docker save "$PROJECT:$HASH" > /artifacts/image.tar
--- a/security/nss/automation/taskcluster/scripts/tools.sh
+++ b/security/nss/automation/taskcluster/scripts/tools.sh
@@ -1,18 +1,17 @@
 #!/usr/bin/env bash
 
 set -v -e -x
 
+# Assert that we're not running as root.
 if [[ $(id -u) -eq 0 ]]; then
-    # Stupid Docker. It works without sometimes... But not always.
-    echo "127.0.0.1 localhost.localdomain" >> /etc/hosts
-
-    # Drop privileges by re-running this script.
-    # Note: this mangles arguments, better to avoid running scripts as root.
+    # This exec is still needed until aarch64 images are updated (Bug 1488325).
+    # Remove when images are updated.  Until then, assert that things are good.
+    [[ $(uname -m) == aarch64 ]]
     exec su worker -c "$0 $*"
 fi
 
 export PATH="${PATH}:/home/worker/.cargo/bin/:/usr/lib/go-1.6/bin"
 
 # Usage: hg_clone repo dir [revision=@]
 hg_clone() {
     repo=$1
--- a/security/nss/cmd/tstclnt/Makefile
+++ b/security/nss/cmd/tstclnt/Makefile
@@ -1,10 +1,10 @@
 #! 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).   #
 #######################################################################
 
--- a/security/nss/cmd/tstclnt/tstclnt.c
+++ b/security/nss/cmd/tstclnt/tstclnt.c
@@ -23,16 +23,17 @@
 #include <errno.h>
 #include <fcntl.h>
 #include <stdarg.h>
 
 #include "nspr.h"
 #include "prio.h"
 #include "prnetdb.h"
 #include "nss.h"
+#include "nssb64.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"
@@ -219,17 +220,18 @@ PrintUsageHeader()
 {
     fprintf(stderr,
             "Usage:  %s -h host [-a 1st_hs_name ] [-a 2nd_hs_name ] [-p port]\n"
             "  [-D | -d certdir] [-C] [-b | -R root-module] \n"
             "  [-n nickname] [-Bafosvx] [-c ciphers] [-Y] [-Z]\n"
             "  [-V [min-version]:[max-version]] [-K] [-T] [-U]\n"
             "  [-r N] [-w passwd] [-W pwfile] [-q [-t seconds]]\n"
             "  [-I groups] [-J signatureschemes]\n"
-            "  [-A requestfile] [-L totalconnections] [-P {client,server}] [-Q]\n"
+            "  [-A requestfile] [-L totalconnections] [-P {client,server}]\n"
+            "  [-N encryptedSniKeys] [-Q]\n"
             "\n",
             progName);
 }
 
 static void
 PrintParameterUsage()
 {
     fprintf(stderr, "%-20s Send different SNI name. 1st_hs_name - at first\n"
@@ -303,16 +305,17 @@ PrintParameterUsage()
                     "%-20s ecdsa_secp521r1_sha512,\n"
                     "%-20s rsa_pss_rsae_sha256, rsa_pss_rsae_sha384, rsa_pss_rsae_sha512,\n"
                     "%-20s rsa_pss_pss_sha256, rsa_pss_pss_sha384, rsa_pss_pss_sha512,\n"
                     "%-20s dsa_sha1, dsa_sha256, dsa_sha384, dsa_sha512\n",
             "-J", "", "", "", "", "", "", "");
     fprintf(stderr, "%-20s Enable alternative TLS 1.3 handshake\n", "-X alt-server-hello");
     fprintf(stderr, "%-20s Use DTLS\n", "-P {client, server}");
     fprintf(stderr, "%-20s Exit after handshake\n", "-Q");
+    fprintf(stderr, "%-20s Encrypted SNI Keys\n", "-N");
 }
 
 static void
 Usage()
 {
     PrintUsageHeader();
     PrintParameterUsage();
     exit(1);
@@ -980,16 +983,17 @@ PRUint8 *zeroRttData;
 unsigned int zeroRttLen = 0;
 PRBool enableAltServerHello = PR_FALSE;
 PRBool useDTLS = PR_FALSE;
 PRBool actAsServer = PR_FALSE;
 PRBool stopAfterHandshake = PR_FALSE;
 PRBool requestToExit = PR_FALSE;
 char *versionString = NULL;
 PRBool handshakeComplete = PR_FALSE;
+char *encryptedSNIKeys = NULL;
 
 static int
 writeBytesToServer(PRFileDesc *s, const PRUint8 *buf, int nb)
 {
     SECStatus rv;
     const PRUint8 *bufp = buf;
     PRPollDesc pollDesc;
 
@@ -1419,16 +1423,36 @@ run()
         rv = SSL_SignatureSchemePrefSet(s, enabledSigSchemes, enabledSigSchemeCount);
         if (rv < 0) {
             SECU_PrintError(progName, "SSL_SignatureSchemePrefSet failed");
             error = 1;
             goto done;
         }
     }
 
+    if (encryptedSNIKeys) {
+        SECItem esniKeysBin = { siBuffer, NULL, 0 };
+
+        if (!NSSBase64_DecodeBuffer(NULL, &esniKeysBin, encryptedSNIKeys,
+                                    strlen(encryptedSNIKeys))) {
+            SECU_PrintError(progName, "ESNIKeys record is invalid base64");
+            error = 1;
+            goto done;
+        }
+
+        rv = SSL_EnableESNI(s, esniKeysBin.data, esniKeysBin.len,
+                            "dummy.invalid");
+        SECITEM_FreeItem(&esniKeysBin, PR_FALSE);
+        if (rv < 0) {
+            SECU_PrintError(progName, "SSL_EnableESNI failed");
+            error = 1;
+            goto done;
+        }
+    }
+
     serverCertAuth.dbHandle = CERT_GetDefaultCertDB();
 
     SSL_AuthCertificateHook(s, ownAuthCertificate, &serverCertAuth);
     if (override) {
         SSL_BadCertHook(s, ownBadCertHandler, NULL);
     }
     if (actAsServer) {
         rv = installServerCertificate(s, nickname);
@@ -1678,17 +1702,17 @@ main(int argc, char **argv)
         }
     }
 
     /* Note: 'B' was used in the past but removed in 3.28
      *       'z' was removed in 3.39
      * Please leave some time before reusing these.
      */
     optstate = PL_CreateOptState(argc, argv,
-                                 "46A:CDFGHI:J:KL:M:OP:QR:STUV:W:X:YZa:bc:d:fgh:m:n:op:qr:st:uvw:");
+                                 "46A:CDFGHI:J:KL:M:N:OP:QR:STUV:W:X:YZa:bc:d:fgh:m:n:op:qr:st:uvw:");
     while ((optstatus = PL_GetNextOpt(optstate)) == PL_OPT_OK) {
         switch (optstate->option) {
             case '?':
             default:
                 Usage();
                 break;
 
             case '4':
@@ -1755,16 +1779,20 @@ main(int argc, char **argv)
                     case 0:
                     default:
                         serverCertAuth.allowOCSPSideChannelData = PR_TRUE;
                         serverCertAuth.allowCRLSideChannelData = PR_TRUE;
                         break;
                 };
                 break;
 
+            case 'N':
+                encryptedSNIKeys = PORT_Strdup(optstate->value);
+                break;
+
             case 'P':
                 useDTLS = PR_TRUE;
                 if (!strcmp(optstate->value, "server")) {
                     actAsServer = 1;
                 } else {
                     if (strcmp(optstate->value, "client")) {
                         Usage();
                     }
@@ -2103,16 +2131,17 @@ done:
 
     PORT_Free((void *)requestFile);
     PORT_Free(hs1SniHostName);
     PORT_Free(hs2SniHostName);
     PORT_Free(nickname);
     PORT_Free(pwdata.data);
     PORT_Free(host);
     PORT_Free(zeroRttData);
+    PORT_Free(encryptedSNIKeys);
 
     if (enabledGroups) {
         PORT_Free(enabledGroups);
     }
     if (NSS_IsInitialized()) {
         SSL_ClearSessionCache();
         if (initializedServerSessionCache) {
             if (SSL_ShutdownServerSessionIDCache() != SECSuccess) {
--- a/security/nss/coreconf/coreconf.dep
+++ b/security/nss/coreconf/coreconf.dep
@@ -5,9 +5,8 @@
 
 /*
  * 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/gtests/pk11_gtest/pk11_cipherop_unittest.cc
+++ b/security/nss/gtests/pk11_gtest/pk11_cipherop_unittest.cc
@@ -5,68 +5,69 @@
 #include "gtest/gtest.h"
 
 #include <assert.h>
 #include <limits.h>
 #include <prinit.h>
 #include <nss.h>
 #include <pk11pub.h>
 
-static const size_t kKeyLen = 128/8;
+static const size_t kKeyLen = 128 / 8;
 
 namespace nss_test {
 
 //
 // The ciper tests using the bltest command cover a great deal of testing.
 // However, Bug 1489691 revealed a corner case which is covered here.
 // This test will make multiple calls to PK11_CipherOp using the same
 // cipher context with data that is not cipher block aligned.
 //
 
-static SECStatus GetBytes(PK11Context *ctx, uint8_t *bytes, size_t len)
-{
+static SECStatus GetBytes(PK11Context* ctx, uint8_t* bytes, size_t len) {
   std::vector<uint8_t> in(len, 0);
 
   int outlen;
   SECStatus rv = PK11_CipherOp(ctx, bytes, &outlen, len, &in[0], len);
   if (static_cast<size_t>(outlen) != len) {
     return SECFailure;
   }
   return rv;
 }
 
 TEST(Pkcs11CipherOp, SingleCtxMultipleUnalignedCipherOps) {
   PK11SlotInfo* slot;
   PK11SymKey* key;
   PK11Context* ctx;
 
-  NSSInitContext* globalctx = NSS_InitContext("", "", "", "", NULL,
-                    NSS_INIT_READONLY | NSS_INIT_NOCERTDB | NSS_INIT_NOMODDB |
-                      NSS_INIT_FORCEOPEN | NSS_INIT_NOROOTINIT);
+  NSSInitContext* globalctx =
+      NSS_InitContext("", "", "", "", NULL,
+                      NSS_INIT_READONLY | NSS_INIT_NOCERTDB | NSS_INIT_NOMODDB |
+                          NSS_INIT_FORCEOPEN | NSS_INIT_NOROOTINIT);
 
   const CK_MECHANISM_TYPE cipher = CKM_AES_CTR;
 
   slot = PK11_GetInternalSlot();
   ASSERT_TRUE(slot);
 
   // Use arbitrary bytes for the AES key
   uint8_t key_bytes[kKeyLen];
   for (size_t i = 0; i < kKeyLen; i++) {
     key_bytes[i] = i;
   }
 
-  SECItem keyItem = { siBuffer, key_bytes, kKeyLen };
+  SECItem keyItem = {siBuffer, key_bytes, kKeyLen};
 
   // The IV can be all zeros since we only encrypt once with
   // each AES key.
-  CK_AES_CTR_PARAMS param = { 128, {} };
-  SECItem paramItem = { siBuffer, reinterpret_cast<unsigned char*>(&param), sizeof(CK_AES_CTR_PARAMS) };
+  CK_AES_CTR_PARAMS param = {128, {}};
+  SECItem paramItem = {siBuffer, reinterpret_cast<unsigned char*>(&param),
+                       sizeof(CK_AES_CTR_PARAMS)};
 
-  key = PK11_ImportSymKey(slot, cipher, PK11_OriginUnwrap,
-                                        CKA_ENCRYPT, &keyItem, NULL);
+  key = PK11_ImportSymKey(slot, cipher, PK11_OriginUnwrap, CKA_ENCRYPT,
+                          &keyItem, NULL);
   ctx = PK11_CreateContextBySymKey(cipher, CKA_ENCRYPT, key, &paramItem);
   ASSERT_TRUE(key);
   ASSERT_TRUE(ctx);
 
   uint8_t outbuf[128];
   ASSERT_EQ(GetBytes(ctx, outbuf, 7), SECSuccess);
   ASSERT_EQ(GetBytes(ctx, outbuf, 17), SECSuccess);
 
--- a/security/nss/gtests/ssl_gtest/manifest.mn
+++ b/security/nss/gtests/ssl_gtest/manifest.mn
@@ -47,16 +47,17 @@ CPPSRCS = \
       ssl_versionpolicy_unittest.cc \
       selfencrypt_unittest.cc \
       test_io.cc \
       tls_agent.cc \
       tls_connect.cc \
       tls_hkdf_unittest.cc \
       tls_filter.cc \
       tls_protect.cc \
+      tls_esni_unittest.cc \
       $(NULL)
 
 INCLUDES += -I$(CORE_DEPTH)/gtests/google_test/gtest/include \
             -I$(CORE_DEPTH)/gtests/common \
             -I$(CORE_DEPTH)/cpputil
 
 REQUIRES = nspr nss libdbm gtest cpputil
 
--- a/security/nss/gtests/ssl_gtest/rsa8193.h
+++ b/security/nss/gtests/ssl_gtest/rsa8193.h
@@ -201,9 +201,9 @@ static const uint8_t rsa8193[] = {
     0x3a, 0x3c, 0xb7, 0x5f, 0xab, 0x1e, 0x51, 0x17, 0x4f, 0xec, 0xc1, 0x6d,
     0x82, 0x79, 0x8e, 0xba, 0x7c, 0x47, 0x8e, 0x99, 0x00, 0x17, 0x9e, 0xda,
     0x10, 0x42, 0x70, 0x25, 0x42, 0x84, 0xc8, 0xb1, 0x95, 0x56, 0xb2, 0x08,
     0xa0, 0x4f, 0xdc, 0xcd, 0x9e, 0x31, 0x4b, 0x0c, 0x0b, 0x03, 0x5d, 0x2c,
     0x26, 0xbc, 0xa9, 0x4b, 0x19, 0xdf, 0x90, 0x01, 0x9a, 0xe0, 0x06, 0x05,
     0x13, 0x34, 0x9d, 0x34, 0xb8, 0xef, 0x13, 0x3a, 0x20, 0xf5, 0x74, 0x02,
     0x70, 0x3b, 0x41, 0x60, 0x1f, 0x5e, 0x76, 0x0a, 0xb1, 0x17, 0xd5, 0xcf,
     0x79, 0xef, 0xf7, 0xab, 0xe7, 0xd6, 0x0f, 0xad, 0x85, 0x2c, 0x52, 0x67,
-    0xb5, 0xa0, 0x4a, 0xfd, 0xaf};
\ No newline at end of file
+    0xb5, 0xa0, 0x4a, 0xfd, 0xaf};
--- a/security/nss/gtests/ssl_gtest/ssl_extension_unittest.cc
+++ b/security/nss/gtests/ssl_gtest/ssl_extension_unittest.cc
@@ -39,38 +39,16 @@ class TlsExtensionTruncator : public Tls
     return CHANGE;
   }
 
  private:
   uint16_t extension_;
   size_t length_;
 };
 
-class TlsExtensionDamager : public TlsExtensionFilter {
- public:
-  TlsExtensionDamager(const std::shared_ptr<TlsAgent>& a, uint16_t extension,
-                      size_t index)
-      : TlsExtensionFilter(a), extension_(extension), index_(index) {}
-  virtual PacketFilter::Action FilterExtension(uint16_t extension_type,
-                                               const DataBuffer& input,
-                                               DataBuffer* output) {
-    if (extension_type != extension_) {
-      return KEEP;
-    }
-
-    *output = input;
-    output->data()[index_] += 73;  // Increment selected for maximum damage
-    return CHANGE;
-  }
-
- private:
-  uint16_t extension_;
-  size_t index_;
-};
-
 class TlsExtensionAppender : public TlsHandshakeFilter {
  public:
   TlsExtensionAppender(const std::shared_ptr<TlsAgent>& a,
                        uint8_t handshake_type, uint16_t ext, DataBuffer& data)
       : TlsHandshakeFilter(a, {handshake_type}), extension_(ext), data_(data) {}
 
   virtual PacketFilter::Action FilterHandshake(const HandshakeHeader& header,
                                                const DataBuffer& input,
@@ -606,34 +584,33 @@ TEST_F(TlsExtensionTest13Stream, WrongSe
   MakeTlsFilter<TlsExtensionReplacer>(server_, ssl_tls13_key_share_xtn, buf);
   client_->ExpectSendAlert(kTlsAlertIllegalParameter);
   server_->ExpectSendAlert(kTlsAlertBadRecordMac);
   ConnectExpectFail();
   EXPECT_EQ(SSL_ERROR_RX_MALFORMED_KEY_SHARE, client_->error_code());
   EXPECT_EQ(SSL_ERROR_BAD_MAC_READ, server_->error_code());
 }
 
-// TODO(ekr@rtfm.com): This is the wrong error code. See bug 1307269.
 TEST_F(TlsExtensionTest13Stream, UnknownServerKeyShare) {
   const uint16_t wrong_group = 0xffff;
 
   static const uint8_t key_share[] = {
       wrong_group >> 8,
       wrong_group & 0xff,  // Group we didn't offer.
       0x00,
       0x02,  // length = 2
       0x01,
       0x02};
   DataBuffer buf(key_share, sizeof(key_share));
   EnsureTlsSetup();
   MakeTlsFilter<TlsExtensionReplacer>(server_, ssl_tls13_key_share_xtn, buf);
-  client_->ExpectSendAlert(kTlsAlertMissingExtension);
+  client_->ExpectSendAlert(kTlsAlertIllegalParameter);
   server_->ExpectSendAlert(kTlsAlertBadRecordMac);
   ConnectExpectFail();
-  EXPECT_EQ(SSL_ERROR_MISSING_KEY_SHARE, client_->error_code());
+  EXPECT_EQ(SSL_ERROR_RX_MALFORMED_KEY_SHARE, client_->error_code());
   EXPECT_EQ(SSL_ERROR_BAD_MAC_READ, server_->error_code());
 }
 
 TEST_F(TlsExtensionTest13Stream, AddServerSignatureAlgorithmsOnResumption) {
   SetupForResume();
   DataBuffer empty;
   MakeTlsFilter<TlsExtensionInjector>(server_, ssl_signature_algorithms_xtn,
                                       empty);
--- a/security/nss/gtests/ssl_gtest/ssl_gtest.gyp
+++ b/security/nss/gtests/ssl_gtest/ssl_gtest.gyp
@@ -46,16 +46,17 @@
         'ssl_v2_client_hello_unittest.cc',
         'ssl_version_unittest.cc',
         'ssl_versionpolicy_unittest.cc',
         'test_io.cc',
         'tls_agent.cc',
         'tls_connect.cc',
         'tls_filter.cc',
         'tls_hkdf_unittest.cc',
+        'tls_esni_unittest.cc',
         'tls_protect.cc'
       ],
       'dependencies': [
         '<(DEPTH)/exports.gyp:nss_exports',
         '<(DEPTH)/lib/util/util.gyp:nssutil3',
         '<(DEPTH)/gtests/google_test/google_test.gyp:gtest',
         '<(DEPTH)/lib/smime/smime.gyp:smime',
         '<(DEPTH)/lib/ssl/ssl.gyp:ssl',
--- a/security/nss/gtests/ssl_gtest/ssl_loopback_unittest.cc
+++ b/security/nss/gtests/ssl_gtest/ssl_loopback_unittest.cc
@@ -315,16 +315,63 @@ TEST_F(TlsConnectStreamTls13, DropRecord
   auto filter = MakeTlsFilter<DropTlsRecord>(client_, 2);
   filter->EnableDecryption();
   Connect();
   client_->SendData(26, 26);  // This should be dropped, so it won't be counted.
   client_->ResetSentBytes();
   SendReceive();
 }
 
+// Check that a server can use 0.5 RTT if client authentication isn't enabled.
+TEST_P(TlsConnectTls13, WriteBeforeClientFinished) {
+  EnsureTlsSetup();
+  StartConnect();
+  client_->Handshake();  // ClientHello
+  server_->Handshake();  // ServerHello
+
+  server_->SendData(10);
+  client_->ReadBytes(10);  // Client should emit the Finished as a side-effect.
+  server_->Handshake();    // Server consumes the Finished.
+  CheckConnected();
+}
+
+// We don't allow 0.5 RTT if client authentication is requested.
+TEST_P(TlsConnectTls13, WriteBeforeClientFinishedClientAuth) {
+  client_->SetupClientAuth();
+  server_->RequestClientAuth(false);
+  StartConnect();
+  client_->Handshake();  // ClientHello
+  server_->Handshake();  // ServerHello
+
+  static const uint8_t data[] = {1, 2, 3};
+  EXPECT_GT(0, PR_Write(server_->ssl_fd(), data, sizeof(data)));
+  EXPECT_EQ(PR_WOULD_BLOCK_ERROR, PORT_GetError());
+
+  Handshake();
+  CheckConnected();
+  SendReceive();
+}
+
+// 0.5 RTT should fail with client authentication required.
+TEST_P(TlsConnectTls13, WriteBeforeClientFinishedClientAuthRequired) {
+  client_->SetupClientAuth();
+  server_->RequestClientAuth(true);
+  StartConnect();
+  client_->Handshake();  // ClientHello
+  server_->Handshake();  // ServerHello
+
+  static const uint8_t data[] = {1, 2, 3};
+  EXPECT_GT(0, PR_Write(server_->ssl_fd(), data, sizeof(data)));
+  EXPECT_EQ(PR_WOULD_BLOCK_ERROR, PORT_GetError());
+
+  Handshake();
+  CheckConnected();
+  SendReceive();
+}
+
 // The next two tests takes advantage of the fact that we
 // automatically read the first 1024 bytes, so if
 // we provide 1200 bytes, they overrun the read buffer
 // provided by the calling test.
 
 // DTLS should return an error.
 TEST_P(TlsConnectDatagram, ShortRead) {
   Connect();
--- a/security/nss/gtests/ssl_gtest/ssl_resumption_unittest.cc
+++ b/security/nss/gtests/ssl_gtest/ssl_resumption_unittest.cc
@@ -543,16 +543,47 @@ TEST_P(TlsConnectTls13, TestTls13ResumeN
   EXPECT_EQ(0U, cr_capture->buffer().len()) << "expect nothing captured yet";
 
   // Sanity check whether the client certificate matches the one
   // decrypted from ticket.
   ScopedCERTCertificate cert2(SSL_PeerCertificate(server_->ssl_fd()));
   EXPECT_TRUE(SECITEM_ItemsAreEqual(&cert1->derCert, &cert2->derCert));
 }
 
+// Here we test that 0.5 RTT is available at the server when resuming, even if
+// configured to request a client certificate.  The resumed handshake relies on
+// the authentication from the original handshake, so no certificate is
+// requested this time around.  The server can write before the handshake
+// completes because the PSK binder is sufficient authentication for the client.
+TEST_P(TlsConnectTls13, WriteBeforeHandshakeCompleteOnResumption) {
+  ConfigureSessionCache(RESUME_BOTH, RESUME_TICKET);
+  client_->SetupClientAuth();
+  server_->RequestClientAuth(true);
+  Connect();
+  SendReceive();  // Absorb the session ticket.
+  ScopedCERTCertificate cert1(SSL_LocalCertificate(client_->ssl_fd()));
+
+  Reset();
+  ConfigureSessionCache(RESUME_BOTH, RESUME_TICKET);
+  ExpectResumption(RESUME_TICKET);
+  server_->RequestClientAuth(false);
+  StartConnect();
+  client_->Handshake();  // ClientHello
+  server_->Handshake();  // ServerHello
+
+  server_->SendData(10);
+  client_->ReadBytes(10);  // Client should emit the Finished as a side-effect.
+  server_->Handshake();    // Server consumes the Finished.
+  CheckConnected();
+
+  // Check whether the client certificate matches the one from the ticket.
+  ScopedCERTCertificate cert2(SSL_PeerCertificate(server_->ssl_fd()));
+  EXPECT_TRUE(SECITEM_ItemsAreEqual(&cert1->derCert, &cert2->derCert));
+}
+
 // We need to enable different cipher suites at different times in the following
 // tests.  Those cipher suites need to be suited to the version.
 static uint16_t ChooseOneCipher(uint16_t version) {
   if (version >= SSL_LIBRARY_VERSION_TLS_1_3) {
     return TLS_AES_128_GCM_SHA256;
   }
   return TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA;
 }
new file mode 100644
--- /dev/null
+++ b/security/nss/gtests/ssl_gtest/tls_esni_unittest.cc
@@ -0,0 +1,453 @@
+/* -*- 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 <ctime>
+
+#include "secerr.h"
+#include "ssl.h"
+
+#include "gtest_utils.h"
+#include "tls_agent.h"
+#include "tls_connect.h"
+
+namespace nss_test {
+
+static const char* kDummySni("dummy.invalid");
+
+std::vector<uint16_t> kDefaultSuites = {TLS_AES_256_GCM_SHA384,
+                                        TLS_AES_128_GCM_SHA256};
+std::vector<uint16_t> kChaChaSuite = {TLS_CHACHA20_POLY1305_SHA256};
+std::vector<uint16_t> kBogusSuites = {0};
+std::vector<uint16_t> kTls12Suites = {
+    TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256};
+
+static void NamedGroup2ECParams(SSLNamedGroup group, SECItem* params) {
+  auto groupDef = ssl_LookupNamedGroup(group);
+  ASSERT_NE(nullptr, groupDef);
+
+  auto oidData = SECOID_FindOIDByTag(groupDef->oidTag);
+  ASSERT_NE(nullptr, oidData);
+  ASSERT_NE(nullptr,
+            SECITEM_AllocItem(nullptr, params, (2 + oidData->oid.len)));
+
+  /*
+   * params->data needs to contain the ASN encoding of an object ID (OID)
+   * representing the named curve. The actual OID is in
+   * oidData->oid.data so we simply prepend 0x06 and OID length
+   */
+  params->data[0] = SEC_ASN1_OBJECT_ID;
+  params->data[1] = oidData->oid.len;
+  memcpy(params->data + 2, oidData->oid.data, oidData->oid.len);
+}
+
+/* Checksum is a 4-byte array. */
+static void UpdateEsniKeysChecksum(DataBuffer* buf) {
+  SECStatus rv;
+  PRUint8 sha256[32];
+
+  /* Stomp the checksum. */
+  PORT_Memset(buf->data() + 2, 0, 4);
+
+  rv = PK11_HashBuf(ssl3_HashTypeToOID(ssl_hash_sha256), sha256, buf->data(),
+                    buf->len());
+  ASSERT_EQ(SECSuccess, rv);
+  buf->Write(2, sha256, 4);
+}
+
+static void GenerateEsniKey(time_t windowStart, SSLNamedGroup group,
+                            std::vector<uint16_t>& cipher_suites,
+                            DataBuffer* record,
+                            ScopedSECKEYPublicKey* pubKey = nullptr,
+                            ScopedSECKEYPrivateKey* privKey = nullptr) {
+  SECKEYECParams ecParams = {siBuffer, NULL, 0};
+  NamedGroup2ECParams(group, &ecParams);
+
+  SECKEYPublicKey* pub = nullptr;
+  SECKEYPrivateKey* priv = SECKEY_CreateECPrivateKey(&ecParams, &pub, nullptr);
+  ASSERT_NE(nullptr, priv);
+  SECITEM_FreeItem(&ecParams, PR_FALSE);
+  PRUint8 encoded[1024];
+  unsigned int encoded_len;
+
+  SECStatus rv = SSL_EncodeESNIKeys(
+      &cipher_suites[0], cipher_suites.size(), group, pub, 100, windowStart,
+      windowStart + 10, encoded, &encoded_len, sizeof(encoded));
+  ASSERT_EQ(SECSuccess, rv);
+  ASSERT_GT(encoded_len, 0U);
+
+  if (pubKey) {
+    pubKey->reset(pub);
+  } else {
+    SECKEY_DestroyPublicKey(pub);
+  }
+  if (privKey) {
+    privKey->reset(priv);
+  } else {
+    SECKEY_DestroyPrivateKey(priv);
+  }
+  record->Truncate(0);
+  record->Write(0, encoded, encoded_len);
+}
+
+static void SetupEsni(const std::shared_ptr<TlsAgent>& client,
+                      const std::shared_ptr<TlsAgent>& server,
+                      SSLNamedGroup group = ssl_grp_ec_curve25519) {
+  ScopedSECKEYPublicKey pub;
+  ScopedSECKEYPrivateKey priv;
+  DataBuffer record;
+
+  GenerateEsniKey(time(nullptr), ssl_grp_ec_curve25519, kDefaultSuites, &record,
+                  &pub, &priv);
+  SECStatus rv = SSL_SetESNIKeyPair(server->ssl_fd(), priv.get(), record.data(),
+                                    record.len());
+  ASSERT_EQ(SECSuccess, rv);
+
+  rv = SSL_EnableESNI(client->ssl_fd(), record.data(), record.len(), kDummySni);
+  ASSERT_EQ(SECSuccess, rv);
+}
+
+static void CheckSniExtension(const DataBuffer& data) {
+  TlsParser parser(data.data(), data.len());
+  uint32_t tmp;
+  ASSERT_TRUE(parser.Read(&tmp, 2));
+  ASSERT_EQ(parser.remaining(), tmp);
+  ASSERT_TRUE(parser.Read(&tmp, 1));
+  ASSERT_EQ(0U, tmp); /* sni_nametype_hostname */
+  DataBuffer name;
+  ASSERT_TRUE(parser.ReadVariable(&name, 2));
+  ASSERT_EQ(0U, parser.remaining());
+  DataBuffer expected(reinterpret_cast<const uint8_t*>(kDummySni),
+                      strlen(kDummySni));
+  ASSERT_EQ(expected, name);
+}
+
+static void ClientInstallEsni(std::shared_ptr<TlsAgent>& agent,
+                              const DataBuffer& record, PRErrorCode err = 0) {
+  SECStatus rv =
+      SSL_EnableESNI(agent->ssl_fd(), record.data(), record.len(), kDummySni);
+  if (err == 0) {
+    ASSERT_EQ(SECSuccess, rv);
+  } else {
+    ASSERT_EQ(SECFailure, rv);
+    ASSERT_EQ(err, PORT_GetError());
+  }
+}
+
+TEST_P(TlsAgentTestClient13, EsniInstall) {
+  EnsureInit();
+  DataBuffer record;
+  GenerateEsniKey(time(0), ssl_grp_ec_curve25519, kDefaultSuites, &record);
+  ClientInstallEsni(agent_, record);
+}
+
+// The next set of tests fail at setup time.
+TEST_P(TlsAgentTestClient13, EsniInvalidHash) {
+  EnsureInit();
+  DataBuffer record;
+  GenerateEsniKey(time(0), ssl_grp_ec_curve25519, kDefaultSuites, &record);
+  record.data()[2]++;
+  ClientInstallEsni(agent_, record, SSL_ERROR_RX_MALFORMED_ESNI_KEYS);
+}
+
+TEST_P(TlsAgentTestClient13, EsniInvalidVersion) {
+  EnsureInit();
+  DataBuffer record;
+  GenerateEsniKey(time(0), ssl_grp_ec_curve25519, kDefaultSuites, &record);
+  record.Write(0, 0xffff, 2);
+  ClientInstallEsni(agent_, record, SSL_ERROR_UNSUPPORTED_VERSION);
+}
+
+TEST_P(TlsAgentTestClient13, EsniShort) {
+  EnsureInit();
+  DataBuffer record;
+  GenerateEsniKey(time(0), ssl_grp_ec_curve25519, kDefaultSuites, &record);
+  record.Truncate(record.len() - 1);
+  UpdateEsniKeysChecksum(&record);
+  ClientInstallEsni(agent_, record, SSL_ERROR_RX_MALFORMED_ESNI_KEYS);
+}
+
+TEST_P(TlsAgentTestClient13, EsniLong) {
+  EnsureInit();
+  DataBuffer record;
+  GenerateEsniKey(time(0), ssl_grp_ec_curve25519, kDefaultSuites, &record);
+  record.Write(record.len(), 1, 1);
+  UpdateEsniKeysChecksum(&record);
+  ClientInstallEsni(agent_, record, SSL_ERROR_RX_MALFORMED_ESNI_KEYS);
+}
+
+TEST_P(TlsAgentTestClient13, EsniExtensionMismatch) {
+  EnsureInit();
+  DataBuffer record;
+  GenerateEsniKey(time(0), ssl_grp_ec_curve25519, kDefaultSuites, &record);
+  record.Write(record.len() - 1, 1, 1);
+  UpdateEsniKeysChecksum(&record);
+  ClientInstallEsni(agent_, record, SSL_ERROR_RX_MALFORMED_ESNI_KEYS);
+}
+
+// The following tests fail by ignoring the Esni block.
+TEST_P(TlsAgentTestClient13, EsniUnknownGroup) {
+  EnsureInit();
+  DataBuffer record;
+  GenerateEsniKey(time(0), ssl_grp_ec_curve25519, kDefaultSuites, &record);
+  record.Write(8, 0xffff, 2);  // Fake group
+  UpdateEsniKeysChecksum(&record);
+  ClientInstallEsni(agent_, record, 0);
+  auto filter =
+      MakeTlsFilter<TlsExtensionCapture>(agent_, ssl_tls13_encrypted_sni_xtn);
+  agent_->Handshake();
+  ASSERT_EQ(TlsAgent::STATE_CONNECTING, agent_->state());
+  ASSERT_TRUE(!filter->captured());
+}
+
+TEST_P(TlsAgentTestClient13, EsniUnknownCS) {
+  EnsureInit();
+  DataBuffer record;
+  GenerateEsniKey(time(0), ssl_grp_ec_curve25519, kBogusSuites, &record);
+  ClientInstallEsni(agent_, record, 0);
+  auto filter =
+      MakeTlsFilter<TlsExtensionCapture>(agent_, ssl_tls13_encrypted_sni_xtn);
+  agent_->Handshake();
+  ASSERT_EQ(TlsAgent::STATE_CONNECTING, agent_->state());
+  ASSERT_TRUE(!filter->captured());
+}
+
+TEST_P(TlsAgentTestClient13, EsniInvalidCS) {
+  EnsureInit();
+  DataBuffer record;
+  GenerateEsniKey(time(0), ssl_grp_ec_curve25519, kTls12Suites, &record);
+  UpdateEsniKeysChecksum(&record);
+  ClientInstallEsni(agent_, record, 0);
+  auto filter =
+      MakeTlsFilter<TlsExtensionCapture>(agent_, ssl_tls13_encrypted_sni_xtn);
+  agent_->Handshake();
+  ASSERT_EQ(TlsAgent::STATE_CONNECTING, agent_->state());
+  ASSERT_TRUE(!filter->captured());
+}
+
+TEST_P(TlsAgentTestClient13, EsniNotReady) {
+  EnsureInit();
+  DataBuffer record;
+  GenerateEsniKey(time(0) + 1000, ssl_grp_ec_curve25519, kDefaultSuites,
+                  &record);
+  ClientInstallEsni(agent_, record, 0);
+  auto filter =
+      MakeTlsFilter<TlsExtensionCapture>(agent_, ssl_tls13_encrypted_sni_xtn);
+  agent_->Handshake();
+  ASSERT_TRUE(!filter->captured());
+}
+
+TEST_P(TlsAgentTestClient13, EsniExpired) {
+  EnsureInit();
+  DataBuffer record;
+  GenerateEsniKey(time(0) - 1000, ssl_grp_ec_curve25519, kDefaultSuites,
+                  &record);
+  ClientInstallEsni(agent_, record, 0);
+  auto filter =
+      MakeTlsFilter<TlsExtensionCapture>(agent_, ssl_tls13_encrypted_sni_xtn);
+  agent_->Handshake();
+  ASSERT_TRUE(!filter->captured());
+}
+
+TEST_P(TlsAgentTestClient13, NoSniSoNoEsni) {
+  EnsureInit();
+  DataBuffer record;
+  GenerateEsniKey(time(0), ssl_grp_ec_curve25519, kDefaultSuites, &record);
+  SSL_SetURL(agent_->ssl_fd(), "");
+  ClientInstallEsni(agent_, record, 0);
+  auto filter =
+      MakeTlsFilter<TlsExtensionCapture>(agent_, ssl_tls13_encrypted_sni_xtn);
+  agent_->Handshake();
+  ASSERT_TRUE(!filter->captured());
+}
+
+static int32_t SniCallback(TlsAgent* agent, const SECItem* srvNameAddr,
+                           PRUint32 srvNameArrSize) {
+  EXPECT_EQ(1U, srvNameArrSize);
+  SECItem expected = {
+      siBuffer, reinterpret_cast<unsigned char*>(const_cast<char*>("server")),
+      6};
+  EXPECT_TRUE(!SECITEM_CompareItem(&expected, &srvNameAddr[0]));
+  return SECSuccess;
+}
+
+TEST_P(TlsConnectTls13, ConnectEsni) {
+  EnsureTlsSetup();
+  SetupEsni(client_, server_);
+  auto cFilterSni =
+      MakeTlsFilter<TlsExtensionCapture>(client_, ssl_server_name_xtn);
+  auto cFilterEsni =
+      MakeTlsFilter<TlsExtensionCapture>(client_, ssl_tls13_encrypted_sni_xtn);
+  client_->SetFilter(std::make_shared<ChainedPacketFilter>(
+      ChainedPacketFilterInit({cFilterSni, cFilterEsni})));
+  auto sfilter =
+      MakeTlsFilter<TlsExtensionCapture>(server_, ssl_server_name_xtn);
+  sfilter->EnableDecryption();
+  server_->SetSniCallback(SniCallback);
+  Connect();
+  CheckSniExtension(cFilterSni->extension());
+  ASSERT_TRUE(cFilterEsni->captured());
+  // Check that our most preferred suite got chosen.
+  uint32_t suite;
+  ASSERT_TRUE(cFilterEsni->extension().Read(0, 2, &suite));
+  ASSERT_EQ(TLS_AES_128_GCM_SHA256, static_cast<PRUint16>(suite));
+  ASSERT_TRUE(!sfilter->captured());
+}
+
+TEST_P(TlsConnectTls13, ConnectEsniHrr) {
+  EnsureTlsSetup();
+  const std::vector<SSLNamedGroup> groups = {ssl_grp_ec_secp384r1};
+  server_->ConfigNamedGroups(groups);
+  SetupEsni(client_, server_);
+  auto hrr_capture = MakeTlsFilter<TlsHandshakeRecorder>(
+      server_, kTlsHandshakeHelloRetryRequest);
+  auto filter =
+      MakeTlsFilter<TlsExtensionCapture>(client_, ssl_server_name_xtn);
+  auto cfilter =
+      MakeTlsFilter<TlsExtensionCapture>(client_, ssl_server_name_xtn);
+  server_->SetSniCallback(SniCallback);
+  Connect();
+  CheckSniExtension(cfilter->extension());
+  EXPECT_NE(0UL, hrr_capture->buffer().len());
+}
+
+TEST_P(TlsConnectTls13, ConnectEsniNoDummy) {
+  EnsureTlsSetup();
+  ScopedSECKEYPublicKey pub;
+  ScopedSECKEYPrivateKey priv;
+  DataBuffer record;
+
+  GenerateEsniKey(time(nullptr), ssl_grp_ec_curve25519, kDefaultSuites, &record,
+                  &pub, &priv);
+  SECStatus rv = SSL_SetESNIKeyPair(server_->ssl_fd(), priv.get(),
+                                    record.data(), record.len());
+  ASSERT_EQ(SECSuccess, rv);
+  rv = SSL_EnableESNI(client_->ssl_fd(), record.data(), record.len(), "");
+  ASSERT_EQ(SECSuccess, rv);
+
+  auto cfilter =
+      MakeTlsFilter<TlsExtensionCapture>(client_, ssl_server_name_xtn);
+  auto sfilter =
+      MakeTlsFilter<TlsExtensionCapture>(server_, ssl_server_name_xtn);
+  server_->SetSniCallback(SniCallback);
+  Connect();
+  ASSERT_TRUE(!cfilter->captured());
+  ASSERT_TRUE(!sfilter->captured());
+}
+
+TEST_P(TlsConnectTls13, ConnectEsniNullDummy) {
+  EnsureTlsSetup();
+  ScopedSECKEYPublicKey pub;
+  ScopedSECKEYPrivateKey priv;
+  DataBuffer record;
+
+  GenerateEsniKey(time(nullptr), ssl_grp_ec_curve25519, kDefaultSuites, &record,
+                  &pub, &priv);
+  SECStatus rv = SSL_SetESNIKeyPair(server_->ssl_fd(), priv.get(),
+                                    record.data(), record.len());
+  ASSERT_EQ(SECSuccess, rv);
+  rv = SSL_EnableESNI(client_->ssl_fd(), record.data(), record.len(), nullptr);
+  ASSERT_EQ(SECSuccess, rv);
+
+  auto cfilter =
+      MakeTlsFilter<TlsExtensionCapture>(client_, ssl_server_name_xtn);
+  auto sfilter =
+      MakeTlsFilter<TlsExtensionCapture>(server_, ssl_server_name_xtn);
+  server_->SetSniCallback(SniCallback);
+  Connect();
+  ASSERT_TRUE(!cfilter->captured());
+  ASSERT_TRUE(!sfilter->captured());
+}
+
+/* Tell the client that it supports AES but the server that it supports ChaCha
+ */
+TEST_P(TlsConnectTls13, ConnectEsniCSMismatch) {
+  EnsureTlsSetup();
+  ScopedSECKEYPublicKey pub;
+  ScopedSECKEYPrivateKey priv;
+  DataBuffer record;
+
+  GenerateEsniKey(time(nullptr), ssl_grp_ec_curve25519, kDefaultSuites, &record,
+                  &pub, &priv);
+  PRUint8 encoded[1024];
+  unsigned int encoded_len;
+
+  SECStatus rv = SSL_EncodeESNIKeys(
+      &kChaChaSuite[0], kChaChaSuite.size(), ssl_grp_ec_curve25519, pub.get(),
+      100, time(0), time(0) + 10, encoded, &encoded_len, sizeof(encoded));
+  rv = SSL_SetESNIKeyPair(server_->ssl_fd(), priv.get(), encoded, encoded_len);
+  ASSERT_EQ(SECSuccess, rv);
+  rv = SSL_EnableESNI(client_->ssl_fd(), record.data(), record.len(), "");
+  ASSERT_EQ(SECSuccess, rv);
+  ConnectExpectAlert(server_, illegal_parameter);
+  server_->CheckErrorCode(SSL_ERROR_RX_MALFORMED_CLIENT_HELLO);
+}
+
+TEST_P(TlsConnectTls13, ConnectEsniP256) {
+  EnsureTlsSetup();
+  SetupEsni(client_, server_, ssl_grp_ec_secp256r1);
+  auto cfilter =
+      MakeTlsFilter<TlsExtensionCapture>(client_, ssl_server_name_xtn);
+  auto sfilter =
+      MakeTlsFilter<TlsExtensionCapture>(server_, ssl_server_name_xtn);
+  server_->SetSniCallback(SniCallback);
+  Connect();
+  CheckSniExtension(cfilter->extension());
+  ASSERT_TRUE(!sfilter->captured());
+}
+
+TEST_P(TlsConnectTls13, ConnectMismatchedEsniKeys) {
+  EnsureTlsSetup();
+  SetupEsni(client_, server_);
+  // Now install a new set of keys on the client, so we have a mismatch.
+  DataBuffer record;
+  GenerateEsniKey(time(0), ssl_grp_ec_curve25519, kDefaultSuites, &record);
+  ClientInstallEsni(client_, record, 0);
+  ConnectExpectAlert(server_, illegal_parameter);
+  server_->CheckErrorCode(SSL_ERROR_RX_MALFORMED_CLIENT_HELLO);
+}
+
+TEST_P(TlsConnectTls13, ConnectDamagedEsniExtensionCH) {
+  EnsureTlsSetup();
+  SetupEsni(client_, server_);
+  auto filter = MakeTlsFilter<TlsExtensionDamager>(
+      client_, ssl_tls13_encrypted_sni_xtn, 50);  // in the ciphertext
+  ConnectExpectAlert(server_, illegal_parameter);
+  server_->CheckErrorCode(SSL_ERROR_RX_MALFORMED_CLIENT_HELLO);
+}
+
+TEST_P(TlsConnectTls13, ConnectRemoveEsniExtensionEE) {
+  EnsureTlsSetup();
+  SetupEsni(client_, server_);
+  auto filter =
+      MakeTlsFilter<TlsExtensionDropper>(server_, ssl_tls13_encrypted_sni_xtn);
+  filter->EnableDecryption();
+  ConnectExpectAlert(client_, missing_extension);
+  client_->CheckErrorCode(SSL_ERROR_MISSING_ESNI_EXTENSION);
+}
+
+TEST_P(TlsConnectTls13, ConnectShortEsniExtensionEE) {
+  EnsureTlsSetup();
+  SetupEsni(client_, server_);
+  DataBuffer shortNonce;
+  auto filter = MakeTlsFilter<TlsExtensionReplacer>(
+      server_, ssl_tls13_encrypted_sni_xtn, shortNonce);
+  filter->EnableDecryption();
+  ConnectExpectAlert(client_, illegal_parameter);
+  client_->CheckErrorCode(SSL_ERROR_RX_MALFORMED_ESNI_EXTENSION);
+}
+
+TEST_P(TlsConnectTls13, ConnectBogusEsniExtensionEE) {
+  EnsureTlsSetup();
+  SetupEsni(client_, server_);
+  const uint8_t bogusNonceBuf[16] = {0};
+  DataBuffer bogusNonce(bogusNonceBuf, sizeof(bogusNonceBuf));
+  auto filter = MakeTlsFilter<TlsExtensionReplacer>(
+      server_, ssl_tls13_encrypted_sni_xtn, bogusNonce);
+  filter->EnableDecryption();
+  ConnectExpectAlert(client_, illegal_parameter);
+  client_->CheckErrorCode(SSL_ERROR_RX_MALFORMED_ESNI_EXTENSION);
+}
+}
--- a/security/nss/gtests/ssl_gtest/tls_filter.cc
+++ b/security/nss/gtests/ssl_gtest/tls_filter.cc
@@ -873,16 +873,27 @@ PacketFilter::Action TlsExtensionReplace
 PacketFilter::Action TlsExtensionDropper::FilterExtension(
     uint16_t extension_type, const DataBuffer& input, DataBuffer* output) {
   if (extension_type == extension_) {
     return DROP;
   }
   return KEEP;
 }
 
+PacketFilter::Action TlsExtensionDamager::FilterExtension(
+    uint16_t extension_type, const DataBuffer& input, DataBuffer* output) {
+  if (extension_type != extension_) {
+    return KEEP;
+  }
+
+  *output = input;
+  output->data()[index_] += 73;  // Increment selected for maximum damage
+  return CHANGE;
+}
+
 PacketFilter::Action TlsExtensionInjector::FilterHandshake(
     const HandshakeHeader& header, const DataBuffer& input,
     DataBuffer* output) {
   TlsParser parser(input);
   if (!TlsExtensionFilter::FindExtensions(&parser, header)) {
     return KEEP;
   }
   size_t offset = parser.consumed();
--- a/security/nss/gtests/ssl_gtest/tls_filter.h
+++ b/security/nss/gtests/ssl_gtest/tls_filter.h
@@ -460,16 +460,30 @@ class TlsExtensionInjector : public TlsH
                                        const DataBuffer& input,
                                        DataBuffer* output) override;
 
  private:
   const uint16_t extension_;
   const DataBuffer data_;
 };
 
+class TlsExtensionDamager : public TlsExtensionFilter {
+ public:
+  TlsExtensionDamager(const std::shared_ptr<TlsAgent>& a, uint16_t extension,
+                      size_t index)
+      : TlsExtensionFilter(a), extension_(extension), index_(index) {}
+  virtual PacketFilter::Action FilterExtension(uint16_t extension_type,
+                                               const DataBuffer& input,
+                                               DataBuffer* output);
+
+ private:
+  uint16_t extension_;
+  size_t index_;
+};
+
 typedef std::function<void(void)> VoidFunction;
 
 class AfterRecordN : public TlsRecordFilter {
  public:
   AfterRecordN(const std::shared_ptr<TlsAgent>& src,
                const std::shared_ptr<TlsAgent>& dest, unsigned int record,
                VoidFunction func)
       : TlsRecordFilter(src),
--- a/security/nss/lib/freebl/ctr.c
+++ b/security/nss/lib/freebl/ctr.c
@@ -216,17 +216,17 @@ CTR_Update_HW_AES(CTRContext *ctr, unsig
         if (inlen == 0) {
             return SECSuccess;
         }
         PORT_Assert(ctr->bufPtr == blocksize);
     }
 
     if (inlen >= blocksize) {
         rv = intel_aes_ctr_worker(((AESContext *)(ctr->context))->Nr)(
-                 ctr, outbuf, outlen, maxout, inbuf, inlen, blocksize);
+            ctr, outbuf, outlen, maxout, inbuf, inlen, blocksize);
         if (rv != SECSuccess) {
             return SECFailure;
         }
         fullblocks = (inlen / blocksize) * blocksize;
         *outlen += fullblocks;
         outbuf += fullblocks;
         inbuf += fullblocks;
         inlen -= fullblocks;
--- a/security/nss/lib/ssl/SSLerrs.h
+++ b/security/nss/lib/ssl/SSLerrs.h
@@ -547,8 +547,17 @@ ER3(SSL_ERROR_HANDSHAKE_FAILED, (SSL_ERR
 ER3(SSL_ERROR_BAD_RESUMPTION_TOKEN_ERROR, (SSL_ERROR_BASE + 173),
     "SSL received an invalid resumption token.")
 
 ER3(SSL_ERROR_RX_MALFORMED_DTLS_ACK, (SSL_ERROR_BASE + 174),
     "SSL received a malformed DTLS ACK")
 
 ER3(SSL_ERROR_DH_KEY_TOO_LONG, (SSL_ERROR_BASE + 175),
     "SSL received a DH key share that's too long (>8192 bit).")
+
+ER3(SSL_ERROR_RX_MALFORMED_ESNI_KEYS, (SSL_ERROR_BASE + 176),
+    "SSL received a malformed ESNI keys structure")
+
+ER3(SSL_ERROR_RX_MALFORMED_ESNI_EXTENSION, (SSL_ERROR_BASE + 177),
+    "SSL received a malformed ESNI extension")
+
+ER3(SSL_ERROR_MISSING_ESNI_EXTENSION, (SSL_ERROR_BASE + 178),
+    "SSL did not receive an ESNI extension")
--- a/security/nss/lib/ssl/config.mk
+++ b/security/nss/lib/ssl/config.mk
@@ -55,8 +55,12 @@ ifeq ($(OS_ARCH), BeOS)
 EXTRA_SHARED_LIBS += -lbe
 endif
 
 endif
 
 ifdef NSS_DISABLE_TLS_1_3
 DEFINES += -DNSS_DISABLE_TLS_1_3
 endif
+
+ifeq (,$(filter-out DragonFly FreeBSD Linux NetBSD OpenBSD, $(OS_TARGET)))
+CFLAGS += -std=gnu99
+endif
--- a/security/nss/lib/ssl/manifest.mn
+++ b/security/nss/lib/ssl/manifest.mn
@@ -1,9 +1,8 @@
-#
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 CORE_DEPTH = ../..
 
 # DEFINES = -DTRACE
 
 EXPORTS = \
@@ -51,15 +50,16 @@ CSRCS = \
         ssl3ecc.c \
         tls13con.c \
         tls13exthandle.c \
         tls13hashstate.c \
         tls13hkdf.c \
         tls13replay.c \
         sslcert.c \
         sslgrp.c \
+        tls13esni.c \
         $(NULL)
 
 LIBRARY_NAME = ssl
 LIBRARY_VERSION = 3
 
 # This part of the code, including all sub-dirs, can be optimized for size
 export ALLOW_OPT_CODE_SIZE = 1
--- a/security/nss/lib/ssl/ssl.gyp
+++ b/security/nss/lib/ssl/ssl.gyp
@@ -38,16 +38,17 @@
         'sslreveal.c',
         'sslsecur.c',
         'sslsnce.c',
         'sslsock.c',
         'sslspec.c',
         'ssltrace.c',
         'sslver.c',
         'tls13con.c',
+        'tls13esni.c',
         'tls13exthandle.c',
         'tls13hashstate.c',
         'tls13hkdf.c',
         'tls13replay.c',
       ],
       'conditions': [
         [ 'OS=="win"', {
           'sources': [
@@ -62,16 +63,21 @@
             'unix_err.c'
           ],
         }],
         [ 'fuzz_tls==1', {
           'defines': [
             'UNSAFE_FUZZER_MODE',
           ],
         }],
+        [ 'OS=="dragonfly" or OS=="freebsd" or OS=="netbsd" or OS=="openbsd" or OS=="linux"', {
+          'cflags': [
+            '-std=gnu99',
+          ],
+        }],
       ],
       'dependencies': [
         '<(DEPTH)/exports.gyp:nss_exports',
       ],
     },
     {
       'target_name': 'ssl3',
       'type': 'shared_library',
--- a/security/nss/lib/ssl/ssl3con.c
+++ b/security/nss/lib/ssl/ssl3con.c
@@ -651,17 +651,17 @@ ssl_LookupCipherSuiteCfgMutable(ssl3Ciph
         if (suites[i].cipher_suite == suite)
             return &suites[i];
     }
     /* return NULL and let the caller handle it.  */
     PORT_SetError(SSL_ERROR_UNKNOWN_CIPHER_SUITE);
     return NULL;
 }
 
-const static ssl3CipherSuiteCfg *
+const ssl3CipherSuiteCfg *
 ssl_LookupCipherSuiteCfg(ssl3CipherSuite suite, const ssl3CipherSuiteCfg *suites)
 {
     return ssl_LookupCipherSuiteCfgMutable(suite,
                                            CONST_CAST(ssl3CipherSuiteCfg, suites));
 }
 
 static PRBool
 ssl_NamedGroupTypeEnabled(const sslSocket *ss, SSLKEAType keaType)
@@ -849,19 +849,19 @@ ssl3_config_match_init(sslSocket *ss)
         PORT_SetError(SSL_ERROR_NO_CIPHERS_SUPPORTED);
     }
     return numPresent;
 }
 
 /* Return PR_TRUE if suite is usable.  This if the suite is permitted by policy,
  * enabled, has a certificate (as needed), has a viable key agreement method, is
  * usable with the negotiated TLS version, and is otherwise usable. */
-static PRBool
-config_match(const ssl3CipherSuiteCfg *suite, PRUint8 policy,
-             const SSLVersionRange *vrange, const sslSocket *ss)
+PRBool
+ssl3_config_match(const ssl3CipherSuiteCfg *suite, PRUint8 policy,
+                  const SSLVersionRange *vrange, const sslSocket *ss)
 {
     const ssl3CipherSuiteDef *cipher_def;
     const ssl3KEADef *kea_def;
 
     PORT_Assert(policy != SSL_NOT_ALLOWED);
     if (policy == SSL_NOT_ALLOWED)
         return PR_FALSE;
 
@@ -894,17 +894,17 @@ static unsigned int
 count_cipher_suites(sslSocket *ss, PRUint8 policy)
 {
     unsigned int i, count = 0;
 
     if (SSL_ALL_VERSIONS_DISABLED(&ss->vrange)) {
         return 0;
     }
     for (i = 0; i < ssl_V3_SUITES_IMPLEMENTED; i++) {
-        if (config_match(&ss->cipherSuites[i], policy, &ss->vrange, ss))
+        if (ssl3_config_match(&ss->cipherSuites[i], policy, &ss->vrange, ss))
             count++;
     }
     if (count == 0) {
         PORT_SetError(SSL_ERROR_SSL_DISABLED);
     }
     return count;
 }
 
@@ -4793,17 +4793,17 @@ ssl3_SendClientHello(sslSocket *ss, sslC
     if (sid) {
         PRBool sidOK = PR_TRUE;
         const ssl3CipherSuiteCfg *suite;
 
         /* Check that the cipher suite we need is enabled. */
         suite = ssl_LookupCipherSuiteCfg(sid->u.ssl3.cipherSuite,
                                          ss->cipherSuites);
         PORT_Assert(suite);
-        if (!suite || !config_match(suite, ss->ssl3.policy, &ss->vrange, ss)) {
+        if (!suite || !ssl3_config_match(suite, ss->ssl3.policy, &ss->vrange, ss)) {
             sidOK = PR_FALSE;
         }
 
         /* Check that we can recover the master secret. */
         if (sidOK) {
             PK11SlotInfo *slot = NULL;
             if (sid->u.ssl3.masterValid) {
                 slot = SECMOD_LookupSlot(sid->u.ssl3.masterModuleID,
@@ -4941,23 +4941,32 @@ ssl3_SendClientHello(sslSocket *ss, sslC
      * is OK for the ticket to change, so we just need to make sure we hold
      * the lock across the calls to ssl_ConstructExtensions.
      */
     if (sid->u.ssl3.lock) {
         unlockNeeded = PR_TRUE;
         PR_RWLock_Rlock(sid->u.ssl3.lock);
     }
 
+    /* Generate a new random if this is the first attempt. */
+    if (type == client_hello_initial) {
+        rv = ssl3_GetNewRandom(ss->ssl3.hs.client_random);
+        if (rv != SECSuccess) {
+            goto loser; /* err set by GetNewRandom. */
+        }
+    }
+
     if (ss->vrange.max >= SSL_LIBRARY_VERSION_TLS_1_3 &&
         type == client_hello_initial) {
         rv = tls13_SetupClientHello(ss);
         if (rv != SECSuccess) {
             goto loser;
         }
     }
+
     if (isTLS || (ss->firstHsDone && ss->peerRequestedProtection)) {
         rv = ssl_ConstructExtensions(ss, &extensionBuf, ssl_hs_client_hello);
         if (rv != SECSuccess) {
             goto loser;
         }
     }
 
     if (IS_DTLS(ss)) {
@@ -5019,23 +5028,16 @@ ssl3_SendClientHello(sslSocket *ss, sslC
         rv = ssl3_AppendHandshakeNumber(ss, dtlsVersion, 2);
     } else {
         rv = ssl3_AppendHandshakeNumber(ss, ss->clientHelloVersion, 2);
     }
     if (rv != SECSuccess) {
         goto loser; /* err set by ssl3_AppendHandshake* */
     }
 
-    /* Generate a new random if this is the first attempt. */
-    if (type == client_hello_initial) {
-        rv = ssl3_GetNewRandom(ss->ssl3.hs.client_random);
-        if (rv != SECSuccess) {
-            goto loser; /* err set by GetNewRandom. */
-        }
-    }
     rv = ssl3_AppendHandshake(ss, ss->ssl3.hs.client_random,
                               SSL3_RANDOM_LENGTH);
     if (rv != SECSuccess) {
         goto loser; /* err set by ssl3_AppendHandshake* */
     }
 
     if (sid->version < SSL_LIBRARY_VERSION_TLS_1_3) {
         rv = ssl3_AppendHandshakeVariable(
@@ -5080,17 +5082,17 @@ ssl3_SendClientHello(sslSocket *ss, sslC
                                         sizeof(ssl3CipherSuite));
         if (rv != SECSuccess) {
             goto loser; /* err set by ssl3_AppendHandshake* */
         }
         actual_count++;
     }
     for (i = 0; i < ssl_V3_SUITES_IMPLEMENTED; i++) {
         ssl3CipherSuiteCfg *suite = &ss->cipherSuites[i];
-        if (config_match(suite, ss->ssl3.policy, &ss->vrange, ss)) {
+        if (ssl3_config_match(suite, ss->ssl3.policy, &ss->vrange, ss)) {
             actual_count++;
             if (actual_count > num_suites) {
                 /* set error card removal/insertion error */
                 PORT_SetError(SSL_ERROR_TOKEN_INSERTION_REMOVAL);
                 goto loser;
             }
             rv = ssl3_AppendHandshakeNumber(ss, suite->cipher_suite,
                                             sizeof(ssl3CipherSuite));
@@ -6321,17 +6323,17 @@ ssl_ClientSetCipherSuite(sslSocket *ss, 
     if (ssl3_config_match_init(ss) == 0) {
         PORT_Assert(PR_FALSE);
         return SECFailure;
     }
     for (i = 0; i < ssl_V3_SUITES_IMPLEMENTED; i++) {
         ssl3CipherSuiteCfg *suiteCfg = &ss->cipherSuites[i];
         if (suite == suiteCfg->cipher_suite) {
             SSLVersionRange vrange = { version, version };
-            if (!config_match(suiteCfg, ss->ssl3.policy, &vrange, ss)) {
+            if (!ssl3_config_match(suiteCfg, ss->ssl3.policy, &vrange, ss)) {
                 /* config_match already checks whether the cipher suite is
                  * acceptable for the version, but the check is repeated here
                  * in order to give a more precise error code. */
                 if (!ssl3_CipherSuiteAllowedForVersionRange(suite, &vrange)) {
                     PORT_SetError(SSL_ERROR_CIPHER_DISALLOWED_FOR_VERSION);
                 } else {
                     PORT_SetError(SSL_ERROR_NO_CYPHER_OVERLAP);
                 }
@@ -7853,16 +7855,40 @@ ssl3_KEASupportsTickets(const ssl3KEADef
     if (kea_def->signKeyType == dsaKey) {
         /* TODO: Fix session tickets for DSS. The server code rejects the
          * session ticket received from the client. Bug 1174677 */
         return PR_FALSE;
     }
     return PR_TRUE;
 }
 
+SECStatus
+ssl3_NegotiateCipherSuiteInner(sslSocket *ss, const SECItem *suites,
+                               PRUint16 version, PRUint16 *suitep)
+{
+    unsigned int j;
+    unsigned int i;
+
+    for (j = 0; j < ssl_V3_SUITES_IMPLEMENTED; j++) {
+        ssl3CipherSuiteCfg *suite = &ss->cipherSuites[j];
+        SSLVersionRange vrange = { version, version };
+        if (!ssl3_config_match(suite, ss->ssl3.policy, &vrange, ss)) {
+            continue;
+        }
+        for (i = 0; i + 1 < suites->len; i += 2) {
+            PRUint16 suite_i = (suites->data[i] << 8) | suites->data[i + 1];
+            if (suite_i == suite->cipher_suite) {
+                *suitep = suite_i;
+                return SECSuccess;
+            }
+        }
+    }
+    return SECFailure;
+}
+
 /* Select a cipher suite.
 **
 ** NOTE: This suite selection algorithm should be the same as the one in
 ** ssl3_HandleV2ClientHello().
 **
 ** If TLS 1.0 is enabled, we could handle the case where the client
 ** offered TLS 1.1 but offered only export cipher suites by choosing TLS
 ** 1.0 and selecting one of those export cipher suites. However, a secure
@@ -7871,34 +7897,26 @@ ssl3_KEASupportsTickets(const ssl3KEADef
 ** cipher suites. Therefore, we refuse to negotiate export cipher suites
 ** with any client that indicates support for TLS 1.1 or higher when we
 ** (the server) have TLS 1.1 support enabled.
 */
 SECStatus
 ssl3_NegotiateCipherSuite(sslSocket *ss, const SECItem *suites,
                           PRBool initHashes)
 {
-    unsigned int j;
-    unsigned int i;
-
-    for (j = 0; j < ssl_V3_SUITES_IMPLEMENTED; j++) {
-        ssl3CipherSuiteCfg *suite = &ss->cipherSuites[j];
-        SSLVersionRange vrange = { ss->version, ss->version };
-        if (!config_match(suite, ss->ssl3.policy, &vrange, ss)) {
-            continue;
-        }
-        for (i = 0; i + 1 < suites->len; i += 2) {
-            PRUint16 suite_i = (suites->data[i] << 8) | suites->data[i + 1];
-            if (suite_i == suite->cipher_suite) {
-                ss->ssl3.hs.cipher_suite = suite_i;
-                return ssl3_SetupCipherSuite(ss, initHashes);
-            }
-        }
-    }
-    return SECFailure;
+    PRUint16 selected;
+    SECStatus rv;
+
+    rv = ssl3_NegotiateCipherSuiteInner(ss, suites, ss->version, &selected);
+    if (rv != SECSuccess) {
+        return SECFailure;
+    }
+
+    ss->ssl3.hs.cipher_suite = selected;
+    return ssl3_SetupCipherSuite(ss, initHashes);
 }
 
 /*
  * Call the SNI config hook.
  *
  * Called from:
  *   ssl3_HandleClientHello
  *   tls13_HandleClientHelloPart2
@@ -8020,19 +8038,22 @@ ssl3_ServerCallSNICallback(sslSocket *ss
                     /* no ciphers are working/supported */
                     errCode = PORT_GetError();
                     desc = handshake_failure;
                     ret = SSL_SNI_SEND_ALERT;
                     break;
                 }
                 /* Need to tell the client that application has picked
                  * the name from the offered list and reconfigured the socket.
+                 * Don't do this if we negotiated ESNI.
                  */
-                ssl3_RegisterExtensionSender(ss, &ss->xtnData, ssl_server_name_xtn,
-                                             ssl_SendEmptyExtension);
+                if (!ssl3_ExtensionNegotiated(ss, ssl_tls13_encrypted_sni_xtn)) {
+                    ssl3_RegisterExtensionSender(ss, &ss->xtnData, ssl_server_name_xtn,
+                                                 ssl_SendEmptyExtension);
+                }
             } else {
                 /* Callback returned index outside of the boundary. */
                 PORT_Assert((unsigned int)ret < ss->xtnData.sniNameArrSize);
                 errCode = SSL_ERROR_INTERNAL_ERROR_ALERT;
                 desc = internal_error;
                 ret = SSL_SNI_SEND_ALERT;
                 break;
             }
@@ -8626,17 +8647,17 @@ ssl3_HandleClientHelloPart2(sslSocket *s
             if (j == 0)
                 break;
 #ifdef PARANOID
             /* Double check that the cached cipher suite is still enabled,
              * implemented, and allowed by policy.  Might have been disabled.
              * The product policy won't change during the process lifetime.
              * Implemented ("isPresent") shouldn't change for servers.
              */
-            if (!config_match(suite, ss->ssl3.policy, &vrange, ss))
+            if (!ssl3_config_match(suite, ss->ssl3.policy, &vrange, ss))
                 break;
 #else
             if (!suite->enabled)
                 break;
 #endif
             /* Double check that the cached cipher suite is in the client's
              * list.  If it isn't, fall through and start a new session. */
             for (i = 0; i + 1 < suites->len; i += 2) {
@@ -9008,17 +9029,17 @@ ssl3_HandleV2ClientHello(sslSocket *ss, 
     /* Select a cipher suite.
     **
     ** NOTE: This suite selection algorithm should be the same as the one in
     ** ssl3_HandleClientHello().
     */
     for (j = 0; j < ssl_V3_SUITES_IMPLEMENTED; j++) {
         ssl3CipherSuiteCfg *suite = &ss->cipherSuites[j];
         SSLVersionRange vrange = { ss->version, ss->version };
-        if (!config_match(suite, ss->ssl3.policy, &vrange, ss)) {
+        if (!ssl3_config_match(suite, ss->ssl3.policy, &vrange, ss)) {
             continue;
         }
         for (i = 0; i + 2 < suite_length; i += 3) {
             PRUint32 suite_i = (suites[i] << 16) | (suites[i + 1] << 8) | suites[i + 2];
             if (suite_i == suite->cipher_suite) {
                 ss->ssl3.hs.cipher_suite = suite_i;
                 rv = ssl3_SetupCipherSuite(ss, PR_TRUE);
                 if (rv != SECSuccess) {
--- a/security/nss/lib/ssl/ssl3ecc.c
+++ b/security/nss/lib/ssl/ssl3ecc.c
@@ -322,26 +322,23 @@ ssl3_HandleECDHClientKeyExchange(sslSock
     ss->sec.keaGroup = ssl_ECPubKey2NamedGroup(&clntPubKey);
     return SECSuccess;
 }
 
 /*
 ** Take an encoded key share and make a public key out of it.
 */
 SECStatus
-ssl_ImportECDHKeyShare(sslSocket *ss, SECKEYPublicKey *peerKey,
+ssl_ImportECDHKeyShare(SECKEYPublicKey *peerKey,
                        PRUint8 *b, PRUint32 length,
                        const sslNamedGroupDef *ecGroup)
 {
     SECStatus rv;
     SECItem ecPoint = { siBuffer, NULL, 0 };
 
-    PORT_Assert(ss->opt.noLocks || ssl_HaveRecvBufLock(ss));
-    PORT_Assert(ss->opt.noLocks || ssl_HaveSSL3HandshakeLock(ss));
-
     if (!length) {
         PORT_SetError(SSL_ERROR_RX_MALFORMED_ECDHE_KEY_SHARE);
         return SECFailure;
     }
 
     /* Fail if the ec point uses compressed representation */
     if (b[0] != EC_POINT_FORM_UNCOMPRESSED &&
         ecGroup->name != ssl_grp_ec_curve25519) {
@@ -611,17 +608,17 @@ ssl3_HandleECDHServerKeyExchange(sslSock
     peerKey = PORT_ArenaZNew(arena, SECKEYPublicKey);
     if (peerKey == NULL) {
         errCode = SEC_ERROR_NO_MEMORY;
         goto loser;
     }
     peerKey->arena = arena;
 
     /* create public key from point data */
-    rv = ssl_ImportECDHKeyShare(ss, peerKey, ec_point.data, ec_point.len,
+    rv = ssl_ImportECDHKeyShare(peerKey, ec_point.data, ec_point.len,
                                 ecGroup);
     if (rv != SECSuccess) {
         /* error code is set */
         desc = handshake_failure;
         errCode = PORT_GetError();
         goto alert_loser;
     }
     peerKey->pkcs11Slot = NULL;
--- a/security/nss/lib/ssl/ssl3ext.c
+++ b/security/nss/lib/ssl/ssl3ext.c
@@ -45,16 +45,17 @@ static const ssl3ExtensionHandler client
     { ssl_signature_algorithms_xtn, &ssl3_HandleSigAlgsXtn },
     { ssl_extended_master_secret_xtn, &ssl3_HandleExtendedMasterSecretXtn },
     { ssl_signed_cert_timestamp_xtn, &ssl3_ServerHandleSignedCertTimestampXtn },
     { ssl_tls13_key_share_xtn, &tls13_ServerHandleKeyShareXtn },
     { ssl_tls13_pre_shared_key_xtn, &tls13_ServerHandlePreSharedKeyXtn },
     { ssl_tls13_early_data_xtn, &tls13_ServerHandleEarlyDataXtn },
     { ssl_tls13_psk_key_exchange_modes_xtn, &tls13_ServerHandlePskModesXtn },
     { ssl_tls13_cookie_xtn, &tls13_ServerHandleCookieXtn },
+    { ssl_tls13_encrypted_sni_xtn, &tls13_ServerHandleEsniXtn },
     { ssl_record_size_limit_xtn, &ssl_HandleRecordSizeLimitXtn },
     { 0, NULL }
 };
 
 /* These two tables are used by the client, to handle server hello
  * extensions. */
 static const ssl3ExtensionHandler serverHelloHandlersTLS[] = {
     { ssl_server_name_xtn, &ssl3_HandleServerNameXtn },
@@ -131,16 +132,17 @@ static const sslExtensionBuilder clientH
       /* Some servers (e.g. WebSphere Application Server 7.0 and Tomcat) will
        * time out or terminate the connection if the last extension in the
        * client hello is empty. They are not intolerant of TLS 1.2, so list
        * signature_algorithms at the end. See bug 1243641. */
       { ssl_tls13_supported_versions_xtn, &tls13_ClientSendSupportedVersionsXtn },
       { ssl_signature_algorithms_xtn, &ssl3_SendSigAlgsXtn },
       { ssl_tls13_cookie_xtn, &tls13_ClientSendHrrCookieXtn },
       { ssl_tls13_psk_key_exchange_modes_xtn, &tls13_ClientSendPskModesXtn },
+      { ssl_tls13_encrypted_sni_xtn, &tls13_ClientSendEsniXtn },
       { ssl_record_size_limit_xtn, &ssl_SendRecordSizeLimitXtn },
       /* The pre_shared_key extension MUST be last. */
       { ssl_tls13_pre_shared_key_xtn, &tls13_ClientSendPreSharedKeyXtn },
       { 0, NULL }
     };
 
 static const sslExtensionBuilder clientHelloSendersSSL3[] = {
     { ssl_renegotiation_info_xtn, &ssl3_SendRenegotiationInfoXtn },
@@ -333,18 +335,16 @@ ssl3_ParseExtensions(sslSocket *ss, PRUi
         PRCList *cursor;
 
         /* Get the extension's type field */
         rv = ssl3_ConsumeHandshakeNumber(ss, &extension_type, 2, b, length);
         if (rv != SECSuccess) {
             return SECFailure; /* alert already sent */
         }
 
-        SSL_TRC(10, ("%d: SSL3[%d]: parsing extension %d",
-                     SSL_GETPID(), ss->fd, extension_type));
         /* Check whether an extension has been sent multiple times. */
         for (cursor = PR_NEXT_LINK(&ss->ssl3.hs.remoteExtensions);
              cursor != &ss->ssl3.hs.remoteExtensions;
              cursor = PR_NEXT_LINK(cursor)) {
             if (((TLSExtension *)cursor)->type == extension_type) {
                 (void)SSL3_SendAlert(ss, alert_fatal, illegal_parameter);
                 PORT_SetError(SSL_ERROR_RX_UNEXPECTED_EXTENSION);
                 return SECFailure;
@@ -352,16 +352,19 @@ ssl3_ParseExtensions(sslSocket *ss, PRUi
         }
 
         /* Get the data for this extension, so we can pass it or skip it. */
         rv = ssl3_ConsumeHandshakeVariable(ss, &extension_data, 2, b, length);
         if (rv != SECSuccess) {
             return rv; /* alert already sent */
         }
 
+        SSL_TRC(10, ("%d: SSL3[%d]: parsed extension %d len=%u",
+                     SSL_GETPID(), ss->fd, extension_type, extension_data.len));
+
         extension = PORT_ZNew(TLSExtension);
         if (!extension) {
             return SECFailure;
         }
 
         extension->type = (PRUint16)extension_type;
         extension->data = extension_data;
         PR_APPEND_LINK(&extension->link, &ss->ssl3.hs.remoteExtensions);
@@ -404,17 +407,19 @@ ssl_CallExtensionHandler(sslSocket *ss, 
                                       extension->data.data,
                                       extension->data.len,
                                       &alert, customHooks->handlerArg);
         }
     } else {
         /* Find extension_type in table of Hello Extension Handlers. */
         for (; handler->ex_handler != NULL; ++handler) {
             if (handler->ex_type == extension->type) {
-                rv = (*handler->ex_handler)(ss, &ss->xtnData, &extension->data);
+                SECItem tmp = extension->data;
+
+                rv = (*handler->ex_handler)(ss, &ss->xtnData, &tmp);
                 break;
             }
         }
     }
 
     if (rv != SECSuccess) {
         if (!ss->ssl3.fatalAlertSent) {
             /* Send an alert if the handler didn't already. */
@@ -955,16 +960,18 @@ ssl3_DestroyExtensionData(TLSExtensionDa
     tls13_DestroyKeyShares(&xtnData->remoteKeyShares);
     SECITEM_FreeItem(&xtnData->certReqContext, PR_FALSE);
     SECITEM_FreeItem(&xtnData->applicationToken, PR_FALSE);
     if (xtnData->certReqAuthorities.arena) {
         PORT_FreeArena(xtnData->certReqAuthorities.arena, PR_FALSE);
         xtnData->certReqAuthorities.arena = NULL;
     }
     PORT_Free(xtnData->advertised);
+    ssl_FreeEphemeralKeyPair(xtnData->esniPrivateKey);
+    SECITEM_FreeItem(&xtnData->keyShareExtension, PR_FALSE);
 }
 
 /* Free everything that has been allocated and then reset back to
  * the starting state. */
 void
 ssl3_ResetExtensionData(TLSExtensionData *xtnData, const sslSocket *ss)
 {
     ssl3_DestroyExtensionData(xtnData);
--- a/security/nss/lib/ssl/ssl3ext.h
+++ b/security/nss/lib/ssl/ssl3ext.h
@@ -6,16 +6,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/. */
 
 #ifndef __ssl3ext_h_
 #define __ssl3ext_h_
 
 #include "sslencode.h"
 
+#define TLS13_ESNI_NONCE_SIZE 16
+
 typedef enum {
     sni_nametype_hostname
 } SNINameType;
 typedef struct TLSExtensionDataStr TLSExtensionData;
 
 /* Registerable callback function that either appends extension to buffer
  * or returns length of data that it would have appended.
  */
@@ -96,16 +98,24 @@ struct TLSExtensionDataStr {
     SECItem cookie;                        /* HRR Cookie. */
     const sslNamedGroupDef *selectedGroup; /* For HRR. */
     /* The application token contains a value that was passed to the client via
      * a session ticket, or the cookie in a HelloRetryRequest. */
     SECItem applicationToken;
 
     /* The record size limit set by the peer. Our value is kept in ss->opt. */
     PRUint16 recordSizeLimit;
+
+    /* ESNI working state */
+    SECItem keyShareExtension;
+    ssl3CipherSuite esniSuite;
+    sslEphemeralKeyPair *esniPrivateKey;
+    /* Pointer into |ss->esniKeys->keyShares| */
+    TLS13KeyShareEntry *peerEsniShare;
+    PRUint8 esniNonce[TLS13_ESNI_NONCE_SIZE];
 };
 
 typedef struct TLSExtensionStr {
     PRCList link;  /* The linked list link */
     PRUint16 type; /* Extension type */
     SECItem data;  /* Pointers into the handshake data. */
 } TLSExtension;
 
--- a/security/nss/lib/ssl/ssl3exthandle.c
+++ b/security/nss/lib/ssl/ssl3exthandle.c
@@ -10,73 +10,115 @@
 #include "sslproto.h"
 #include "sslimpl.h"
 #include "pk11pub.h"
 #include "blapit.h"
 #include "prinit.h"
 #include "selfencrypt.h"
 #include "ssl3ext.h"
 #include "ssl3exthandle.h"
+#include "tls13esni.h"
 #include "tls13exthandle.h" /* For tls13_ServerSendStatusRequestXtn. */
 
+PRBool
+ssl_ShouldSendSNIExtension(const sslSocket *ss, const char *url)
+{
+    PRNetAddr netAddr;
+
+    /* must have a hostname */
+    if (!url || !url[0]) {
+        return PR_FALSE;
+    }
+    /* must not be an IPv4 or IPv6 address */
+    if (PR_SUCCESS == PR_StringToNetAddr(url, &netAddr)) {
+        /* is an IP address (v4 or v6) */
+        return PR_FALSE;
+    }
+
+    return PR_TRUE;
+}
+
 /* Format an SNI extension, using the name from the socket's URL,
  * unless that name is a dotted decimal string.
  * Used by client and server.
  */
 SECStatus
-ssl3_ClientSendServerNameXtn(const sslSocket *ss, TLSExtensionData *xtnData,
-                             sslBuffer *buf, PRBool *added)
+ssl3_ClientFormatServerNameXtn(const sslSocket *ss, const char *url,
+                               TLSExtensionData *xtnData,
+                               sslBuffer *buf)
 {
     unsigned int len;
-    PRNetAddr netAddr;
     SECStatus rv;
 
-    /* must have a hostname */
-    if (!ss->url || !ss->url[0]) {
-        return SECSuccess;
-    }
-    /* must not be an IPv4 or IPv6 address */
-    if (PR_SUCCESS == PR_StringToNetAddr(ss->url, &netAddr)) {
-        /* is an IP address (v4 or v6) */
-        return SECSuccess;
-    }
-    len = PORT_Strlen(ss->url);
+    len = PORT_Strlen(url);
     /* length of server_name_list */
     rv = sslBuffer_AppendNumber(buf, len + 3, 2);
     if (rv != SECSuccess) {
         return SECFailure;
     }
     /* Name Type (sni_host_name) */
     rv = sslBuffer_AppendNumber(buf, 0, 1);
     if (rv != SECSuccess) {
         return SECFailure;
     }
     /* HostName (length and value) */
-    rv = sslBuffer_AppendVariable(buf, (const PRUint8 *)ss->url, len, 2);
+    rv = sslBuffer_AppendVariable(buf, (const PRUint8 *)url, len, 2);
+    if (rv != SECSuccess) {
+        return SECFailure;
+    }
+
+    return SECSuccess;
+}
+
+SECStatus
+ssl3_ClientSendServerNameXtn(const sslSocket *ss, TLSExtensionData *xtnData,
+                             sslBuffer *buf, PRBool *added)
+{
+    SECStatus rv;
+
+    const char *url = ss->url;
+
+    /* We only make an ESNI private key if we are going to
+     * send ESNI. */
+    if (ss->xtnData.esniPrivateKey != NULL) {
+        url = ss->esniKeys->dummySni;
+    }
+
+    if (!ssl_ShouldSendSNIExtension(ss, url)) {
+        return SECSuccess;
+    }
+
+    rv = ssl3_ClientFormatServerNameXtn(ss, url, xtnData, buf);
     if (rv != SECSuccess) {
         return SECFailure;
     }
 
     *added = PR_TRUE;
     return SECSuccess;
 }
 
-/* Handle an incoming SNI extension. */
 SECStatus
 ssl3_HandleServerNameXtn(const sslSocket *ss, TLSExtensionData *xtnData,
                          SECItem *data)
 {
     SECItem *names = NULL;
     PRUint32 listLenBytes = 0;
     SECStatus rv;
 
     if (!ss->sec.isServer) {
         return SECSuccess; /* ignore extension */
     }
 
+    if (ssl3_ExtensionNegotiated(ss, ssl_tls13_encrypted_sni_xtn)) {
+        /* If we already have ESNI, make sure we don't overwrite
+         * the value. */
+        PORT_Assert(ss->version >= SSL_LIBRARY_VERSION_TLS_1_3);
+        return SECSuccess;
+    }
+
     /* Server side - consume client data and register server sender. */
     /* do not parse the data if don't have user extension handling function. */
     if (!ss->sniSocketConfig) {
         return SECSuccess;
     }
 
     /* length of server_name_list */
     rv = ssl3_ExtConsumeHandshakeNumber(ss, &listLenBytes, 2, &data->data, &data->len);
--- a/security/nss/lib/ssl/ssl3exthandle.h
+++ b/security/nss/lib/ssl/ssl3exthandle.h
@@ -86,16 +86,20 @@ SECStatus ssl3_ServerHandleSignedCertTim
 SECStatus ssl3_SendExtendedMasterSecretXtn(const sslSocket *ss,
                                            TLSExtensionData *xtnData,
                                            sslBuffer *buf, PRBool *added);
 SECStatus ssl3_HandleExtendedMasterSecretXtn(const sslSocket *ss,
                                              TLSExtensionData *xtnData,
                                              SECItem *data);
 SECStatus ssl3_ProcessSessionTicketCommon(sslSocket *ss, const SECItem *ticket,
                                           /* out */ SECItem *appToken);
+PRBool ssl_ShouldSendSNIExtension(const sslSocket *ss, const char *url);
+SECStatus ssl3_ClientFormatServerNameXtn(const sslSocket *ss, const char *url,
+                                         TLSExtensionData *xtnData,
+                                         sslBuffer *buf);
 SECStatus ssl3_ClientSendServerNameXtn(const sslSocket *ss,
                                        TLSExtensionData *xtnData,
                                        sslBuffer *buf, PRBool *added);
 SECStatus ssl3_HandleServerNameXtn(const sslSocket *ss,
                                    TLSExtensionData *xtnData,
                                    SECItem *data);
 SECStatus ssl_HandleSupportedGroupsXtn(const sslSocket *ss,
                                        TLSExtensionData *xtnData,
--- a/security/nss/lib/ssl/sslerr.h
+++ b/security/nss/lib/ssl/sslerr.h
@@ -259,15 +259,18 @@ typedef enum {
 
     SSL_ERROR_RX_UNEXPECTED_KEY_UPDATE = (SSL_ERROR_BASE + 169),
     SSL_ERROR_RX_MALFORMED_KEY_UPDATE = (SSL_ERROR_BASE + 170),
     SSL_ERROR_TOO_MANY_KEY_UPDATES = (SSL_ERROR_BASE + 171),
     SSL_ERROR_HANDSHAKE_FAILED = (SSL_ERROR_BASE + 172),
     SSL_ERROR_BAD_RESUMPTION_TOKEN_ERROR = (SSL_ERROR_BASE + 173),
     SSL_ERROR_RX_MALFORMED_DTLS_ACK = (SSL_ERROR_BASE + 174),
     SSL_ERROR_DH_KEY_TOO_LONG = (SSL_ERROR_BASE + 175),
+    SSL_ERROR_RX_MALFORMED_ESNI_KEYS = (SSL_ERROR_BASE + 176),
+    SSL_ERROR_RX_MALFORMED_ESNI_EXTENSION = (SSL_ERROR_BASE + 177),
+    SSL_ERROR_MISSING_ESNI_EXTENSION = (SSL_ERROR_BASE + 178),
     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_ */
--- a/security/nss/lib/ssl/sslexp.h
+++ b/security/nss/lib/ssl/sslexp.h
@@ -447,15 +447,72 @@ typedef SECStatus(PR_CALLBACK *SSLResump
 /* TLS 1.3 allows a server to set a limit on the number of bytes of early data
  * that can be received. This allows that limit to be set. This function has no
  * effect on a client. */
 #define SSL_SetMaxEarlyDataSize(fd, size)                    \
     SSL_EXPERIMENTAL_API("SSL_SetMaxEarlyDataSize",          \
                          (PRFileDesc * _fd, PRUint32 _size), \
                          (fd, size))
 
+/* Set the ESNI key pair on a socket (server side)
+ *
+ * fd -- the socket
+ * record/recordLen -- the encoded DNS record (not base64)
+ *
+ * Important: the suites that are advertised in the record must
+ * be configured on, or this call will fail.
+ */
+#define SSL_SetESNIKeyPair(fd,                                              \
+                           privKey, record, recordLen)                      \
+    SSL_EXPERIMENTAL_API("SSL_SetESNIKeyPair",                              \
+                         (PRFileDesc * _fd,                                 \
+                          SECKEYPrivateKey * _privKey,                      \
+                          const PRUint8 *_record, unsigned int _recordLen), \
+                         (fd, privKey,                                      \
+                          record, recordLen))
+
+/* Set the ESNI keys on a client
+ *
+ * fd -- the socket
+ * ensikeys/esniKeysLen -- the ESNI key structure (not base64)
+ * dummyESNI -- the dummy ESNI to use (if any)
+ */
+#define SSL_EnableESNI(fd, esniKeys, esniKeysLen, dummySNI) \
+    SSL_EXPERIMENTAL_API("SSL_EnableESNI",                  \
+                         (PRFileDesc * _fd,                 \
+                          const PRUint8 *_esniKeys,         \
+                          unsigned int _esniKeysLen,        \
+                          const char *_dummySNI),           \
+                         (fd, esniKeys, esniKeysLen, dummySNI))
+
+/*
+ * Generate an encoded ESNIKeys structure (presumably server side).
+ *
+ * cipherSuites -- the cipher suites that can be used
+ * cipherSuitesCount -- the number of suites in cipherSuites
+ * group -- the named group this key corresponds to
+ * pubKey -- the public key for the key pair
+ * pad -- the length to pad to
+ * notBefore/notAfter -- validity range
+ * out/outlen/maxlen -- where to output the data
+ */
+#define SSL_EncodeESNIKeys(cipherSuites, cipherSuiteCount,          \
+                           group, pubKey, pad, notBefore, notAfter, \
+                           out, outlen, maxlen)                     \
+    SSL_EXPERIMENTAL_API("SSL_EncodeESNIKeys",                      \
+                         (PRUint16 * _cipherSuites,                 \
+                          unsigned int _cipherSuiteCount,           \
+                          SSLNamedGroup _group,                     \
+                          SECKEYPublicKey *_pubKey,                 \
+                          PRUint16 _pad,                            \
+                          PRUint64 _notBefore, PRUint64 _notAfter,  \
+                          PRUint8 *_out, unsigned int *_outlen,     \
+                          unsigned int _maxlen),                    \
+                         (cipherSuites, cipherSuiteCount,           \
+                          group, pubKey, pad, notBefore, notAfter,  \
+                          out, outlen, maxlen))
+
 /* Deprecated experimental APIs */
-
 #define SSL_UseAltServerHelloType(fd, enable) SSL_DEPRECATED_EXPERIMENTAL_API
 
 SEC_END_PROTOS
 
 #endif /* __sslexp_h_ */
--- a/security/nss/lib/ssl/sslimpl.h
+++ b/security/nss/lib/ssl/sslimpl.h
@@ -31,16 +31,20 @@
 #include "prthread.h"
 #include "prclist.h"
 #include "private/pprthred.h"
 
 #include "sslt.h" /* for some formerly private types, now public */
 
 typedef struct sslSocketStr sslSocket;
 typedef struct sslNamedGroupDefStr sslNamedGroupDef;
+typedef struct sslEsniKeysStr sslEsniKeys;
+typedef struct sslEphemeralKeyPairStr sslEphemeralKeyPair;
+typedef struct TLS13KeyShareEntryStr TLS13KeyShareEntry;
+
 #include "sslencode.h"
 #include "sslexp.h"
 #include "ssl3ext.h"
 #include "sslspec.h"
 
 #if defined(DEBUG) || defined(TRACE)
 #ifdef __cplusplus
 #define Debug 1
@@ -554,21 +558,21 @@ typedef SECStatus (*sslRestartTarget)(ss
 typedef struct DTLSQueuedMessageStr {
     PRCList link;           /* The linked list link */
     ssl3CipherSpec *cwSpec; /* The cipher spec to use, null for none */
     SSLContentType type;    /* The message type */
     unsigned char *data;    /* The data */
     PRUint16 len;           /* The data length */
 } DTLSQueuedMessage;
 
-typedef struct TLS13KeyShareEntryStr {
+struct TLS13KeyShareEntryStr {
     PRCList link;                  /* The linked list link */
     const sslNamedGroupDef *group; /* The group for the entry */
     SECItem key_exchange;          /* The share itself */
-} TLS13KeyShareEntry;
+};
 
 typedef struct TLS13EarlyDataStr {
     PRCList link; /* The linked list link */
     SECItem data; /* The data */
 } TLS13EarlyData;
 
 typedef enum {
     handshake_hash_unknown = 0,
@@ -800,21 +804,21 @@ typedef struct {
 } SSL3Ciphertext;
 
 struct sslKeyPairStr {
     SECKEYPrivateKey *privKey;
     SECKEYPublicKey *pubKey;
     PRInt32 refCount; /* use PR_Atomic calls for this. */
 };
 
-typedef struct {
+struct sslEphemeralKeyPairStr {
     PRCList link;
     const sslNamedGroupDef *group;
     sslKeyPair *keys;
-} sslEphemeralKeyPair;
+};
 
 struct ssl3DHParamsStr {
     SSLNamedGroup name;
     SECItem prime; /* p */
     SECItem base;  /* g */
 };
 
 typedef struct SSLWrappedSymWrappingKeyStr {
@@ -1061,16 +1065,20 @@ struct sslSocketStr {
      * TLS extension related data.
      */
     /* True when the current session is a stateless resume. */
     PRBool statelessResume;
     TLSExtensionData xtnData;
 
     /* Whether we are doing stream or datagram mode */
     SSLProtocolVariant protocolVariant;
+
+    /* The information from the ESNI keys record
+     * (also the private key for the server). */
+    sslEsniKeys *esniKeys;
 };
 
 struct sslSelfEncryptKeysStr {
     PRCallOnceType setup;
     PRUint8 keyName[SELF_ENCRYPT_KEY_NAME_LEN];
     PK11SymKey *encKey;
     PK11SymKey *macKey;
 };
@@ -1165,16 +1173,17 @@ extern int ssl_SendSavedWriteData(sslSoc
 extern SECStatus ssl_SaveWriteData(sslSocket *ss,
                                    const void *p, unsigned int l);
 extern SECStatus ssl_BeginClientHandshake(sslSocket *ss);
 extern SECStatus ssl_BeginServerHandshake(sslSocket *ss);
 extern int ssl_Do1stHandshake(sslSocket *ss);
 
 extern SECStatus ssl3_InitPendingCipherSpecs(sslSocket *ss, PK11SymKey *secret,
                                              PRBool derive);
+extern void ssl_DestroyKeyMaterial(ssl3KeyMaterial *keyMaterial);
 extern sslSessionID *ssl3_NewSessionID(sslSocket *ss, PRBool is_server);
 extern sslSessionID *ssl_LookupSID(const PRIPv6Addr *addr, PRUint16 port,
                                    const char *peerID, const char *urlSvrName);
 extern void ssl_FreeSID(sslSessionID *sid);
 extern void ssl_DestroySID(sslSessionID *sid, PRBool freeIt);
 
 extern int ssl3_SendApplicationData(sslSocket *ss, const PRUint8 *in,
                                     int len, int flags);
@@ -1494,17 +1503,17 @@ extern SECStatus ssl3_SendECDHClientKeyE
                                                 SECKEYPublicKey *svrPubKey);
 extern SECStatus ssl3_HandleECDHServerKeyExchange(sslSocket *ss,
                                                   PRUint8 *b, PRUint32 length);
 extern SECStatus ssl3_HandleECDHClientKeyExchange(sslSocket *ss,
                                                   PRUint8 *b, PRUint32 length,
                                                   sslKeyPair *serverKeys);
 extern SECStatus ssl3_SendECDHServerKeyExchange(sslSocket *ss);
 extern SECStatus ssl_ImportECDHKeyShare(
-    sslSocket *ss, SECKEYPublicKey *peerKey,
+    SECKEYPublicKey *peerKey,
     PRUint8 *b, PRUint32 length, const sslNamedGroupDef *curve);
 
 extern SECStatus ssl3_ComputeCommonKeyHash(SSLHashType hashAlg,
                                            PRUint8 *hashBuf,
                                            unsigned int bufLen,
                                            SSL3Hashes *hashes);
 extern SECStatus ssl3_AppendSignatureAndHashAlgorithm(
     sslSocket *ss, const SSLSignatureAndHashAlg *sigAndHash);
@@ -1559,16 +1568,22 @@ extern SECStatus ssl3_ValidateAppProtoco
 /* Construct a new NSPR socket for the app to use */
 extern PRFileDesc *ssl_NewPRSocket(sslSocket *ss, PRFileDesc *fd);
 extern void ssl_FreePRSocket(PRFileDesc *fd);
 
 /* Internal config function so SSL3 can initialize the present state of
  * various ciphers */
 extern unsigned int ssl3_config_match_init(sslSocket *);
 
+/* Return PR_TRUE if suite is usable.  This if the suite is permitted by policy,
+ * enabled, has a certificate (as needed), has a viable key agreement method, is
+ * usable with the negotiated TLS version, and is otherwise usable. */
+PRBool ssl3_config_match(const ssl3CipherSuiteCfg *suite, PRUint8 policy,
+                         const SSLVersionRange *vrange, const sslSocket *ss);
+
 /* calls for accessing wrapping keys across processes. */
 extern SECStatus
 ssl_GetWrappingKey(unsigned int symWrapMechIndex, unsigned int wrapKeyIndex,
                    SSLWrappedSymWrappingKey *wswk);
 
 /* The caller passes in the new value it wants
  * to set.  This code tests the wrapped sym key entry in the file on disk.
  * If it is uninitialized, this function writes the caller's value into
@@ -1588,16 +1603,18 @@ extern SECStatus ssl_InitSymWrapKeysLock
 
 extern SECStatus ssl_FreeSymWrapKeysLock(void);
 
 extern SECStatus ssl_InitSessionCacheLocks(PRBool lazyInit);
 
 extern SECStatus ssl_FreeSessionCacheLocks(void);
 
 CK_MECHANISM_TYPE ssl3_Alg2Mech(SSLCipherAlgorithm calg);
+SECStatus ssl3_NegotiateCipherSuiteInner(sslSocket *ss, const SECItem *suites,
+                                         PRUint16 version, PRUint16 *suitep);
 SECStatus ssl3_NegotiateCipherSuite(sslSocket *ss, const SECItem *suites,
                                     PRBool initHashes);
 SECStatus ssl3_InitHandshakeHashes(sslSocket *ss);
 SECStatus ssl3_ServerCallSNICallback(sslSocket *ss);
 SECStatus ssl3_FlushHandshake(sslSocket *ss, PRInt32 flags);
 SECStatus ssl3_CompleteHandleCertificate(sslSocket *ss,
                                          PRUint8 *b, PRUint32 length);
 void ssl3_SendAlertForCertError(sslSocket *ss, PRErrorCode errCode);
@@ -1635,16 +1652,19 @@ SECStatus ssl_CreateStaticECDHEKey(sslSo
 SECStatus ssl3_FlushHandshake(sslSocket *ss, PRInt32 flags);
 PK11SymKey *ssl3_GetWrappingKey(sslSocket *ss,
                                 PK11SlotInfo *masterSecretSlot,
                                 CK_MECHANISM_TYPE masterWrapMech,
                                 void *pwArg);
 SECStatus ssl3_FillInCachedSID(sslSocket *ss, sslSessionID *sid,
                                PK11SymKey *secret);
 const ssl3CipherSuiteDef *ssl_LookupCipherSuiteDef(ssl3CipherSuite suite);
+const ssl3CipherSuiteCfg *ssl_LookupCipherSuiteCfg(ssl3CipherSuite suite,
+                                                   const ssl3CipherSuiteCfg *suites);
+
 SECStatus ssl3_SelectServerCert(sslSocket *ss);
 SECStatus ssl_PickSignatureScheme(sslSocket *ss,
                                   CERTCertificate *cert,
                                   SECKEYPublicKey *pubKey,
                                   SECKEYPrivateKey *privKey,
                                   const SSLSignatureScheme *peerSchemes,
                                   unsigned int peerSchemeCount,
                                   PRBool requireSha1);
--- a/security/nss/lib/ssl/sslsecur.c
+++ b/security/nss/lib/ssl/sslsecur.c
@@ -931,16 +931,35 @@ ssl_SecureSend(sslSocket *ss, const unsi
             (ss->opt.enableFalseStart || ss->opt.enable0RttData)) {
             ssl_GetSSL3HandshakeLock(ss);
             zeroRtt = ss->ssl3.hs.zeroRttState == ssl_0rtt_sent ||
                       ss->ssl3.hs.zeroRttState == ssl_0rtt_accepted;
             allowEarlySend = ss->ssl3.hs.canFalseStart || zeroRtt;
             firstClientWrite = ss->ssl3.hs.ws == idle_handshake;
             ssl_ReleaseSSL3HandshakeLock(ss);
         }
+        /* Allow the server to send 0.5 RTT data in TLS 1.3. Requesting a
+         * certificate implies that the server might condition its sending on
+         * client authentication, so force servers that do that to wait.
+         *
+         * What might not be obvious here is that this allows 0.5 RTT when doing
+         * PSK-based resumption.  As a result, 0.5 RTT is always enabled when
+         * early data is accepted.
+         *
+         * This check might be more conservative than absolutely necessary.
+         * It's possible that allowing 0.5 RTT data when the server requests,
+         * but does not require client authentication is safe because we can
+         * expect the server to check for a client certificate properly. */
+        if (ss->sec.isServer &&
+            ss->version >= SSL_LIBRARY_VERSION_TLS_1_3 &&
+            !tls13_ShouldRequestClientAuth(ss)) {
+            ssl_GetSSL3HandshakeLock(ss);
+            allowEarlySend = TLS13_IN_HS_STATE(ss, wait_finished);
+            ssl_ReleaseSSL3HandshakeLock(ss);
+        }
         if (!allowEarlySend && ss->handshake) {
             rv = ssl_Do1stHandshake(ss);
         }
         if (firstClientWrite) {
             /* Wait until after sending ClientHello and double-check 0-RTT. */
             ssl_GetSSL3HandshakeLock(ss);
             zeroRtt = ss->ssl3.hs.zeroRttState == ssl_0rtt_sent ||
                       ss->ssl3.hs.zeroRttState == ssl_0rtt_accepted;
--- a/security/nss/lib/ssl/sslsock.c
+++ b/security/nss/lib/ssl/sslsock.c
@@ -13,16 +13,17 @@
 #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"
+#include "tls13esni.h"
 
 static const sslSocketOps ssl_default_ops = { /* No SSL. */
                                               ssl_DefConnect,
                                               NULL,
                                               ssl_DefBind,
                                               ssl_DefListen,
                                               ssl_DefShutdown,
                                               ssl_DefClose,
@@ -356,16 +357,23 @@ ssl_DupSocket(sslSocket *os)
         ss->nextProtoArg = os->nextProtoArg;
         PORT_Memcpy((void *)ss->namedGroupPreferences,
                     os->namedGroupPreferences,
                     sizeof(ss->namedGroupPreferences));
         ss->additionalShares = os->additionalShares;
         ss->resumptionTokenCallback = os->resumptionTokenCallback;
         ss->resumptionTokenContext = os->resumptionTokenContext;
 
+        if (os->esniKeys) {
+            ss->esniKeys = tls13_CopyESNIKeys(os->esniKeys);
+            if (!ss->esniKeys) {
+                goto loser;
+            }
+        }
+
         /* Create security data */
         rv = ssl_CopySecurityInfo(ss, os);
         if (rv != SECSuccess) {
             goto loser;
         }
     }
 
     return ss;
@@ -441,16 +449,18 @@ ssl_DestroySocketContents(sslSocket *ss)
     ssl_ClearPRCList(&ss->extensionHooks, NULL);
 
     ssl_FreeEphemeralKeyPairs(ss);
     SECITEM_FreeItem(&ss->opt.nextProtoNego, PR_FALSE);
     ssl3_FreeSniNameArray(&ss->xtnData);
 
     ssl_ClearPRCList(&ss->ssl3.hs.dtlsSentHandshake, NULL);
     ssl_ClearPRCList(&ss->ssl3.hs.dtlsRcvdHandshake, NULL);
+
+    tls13_DestroyESNIKeys(ss->esniKeys);
 }
 
 /*
  * free an sslSocket struct, and all the stuff that hangs off of it
  */
 void
 ssl_FreeSocket(sslSocket *ss)
 {
@@ -3767,16 +3777,20 @@ ssl_GetKeyPairRef(sslKeyPair *keyPair)
 {
     PR_ATOMIC_INCREMENT(&keyPair->refCount);
     return keyPair;
 }
 
 void
 ssl_FreeKeyPair(sslKeyPair *keyPair)
 {
+    if (!keyPair) {
+        return;
+    }
+
     PRInt32 newCount = PR_ATOMIC_DECREMENT(&keyPair->refCount);
     if (!newCount) {
         SECKEY_DestroyPrivateKey(keyPair->privKey);
         SECKEY_DestroyPublicKey(keyPair->pubKey);
         PORT_Free(keyPair);
     }
 }
 
@@ -3826,16 +3840,20 @@ ssl_CopyEphemeralKeyPair(sslEphemeralKey
     pair->keys = ssl_GetKeyPairRef(keyPair->keys);
 
     return pair;
 }
 
 void
 ssl_FreeEphemeralKeyPair(sslEphemeralKeyPair *keyPair)
 {
+    if (!keyPair) {
+        return;
+    }
+
     ssl_FreeKeyPair(keyPair->keys);
     PR_REMOVE_LINK(&keyPair->link);
     PORT_Free(keyPair);
 }
 
 PRBool
 ssl_HaveEphemeralKeyPair(const sslSocket *ss, const sslNamedGroupDef *groupDef)
 {
@@ -3933,16 +3951,18 @@ ssl_NewSocket(PRBool makeLocks, SSLProto
     PR_INIT_CLIST(&ss->ssl3.hs.lastMessageFlight);
     PR_INIT_CLIST(&ss->ssl3.hs.cipherSpecs);
     PR_INIT_CLIST(&ss->ssl3.hs.bufferedEarlyData);
     ssl3_InitExtensionData(&ss->xtnData, ss);
     PR_INIT_CLIST(&ss->ssl3.hs.dtlsSentHandshake);
     PR_INIT_CLIST(&ss->ssl3.hs.dtlsRcvdHandshake);
     dtls_InitTimers(ss);
 
+    ss->esniKeys = NULL;
+
     if (makeLocks) {
         rv = ssl_MakeLocks(ss);
         if (rv != SECSuccess)
             goto loser;
     }
     rv = ssl_CreateSecurityInfo(ss);
     if (rv != SECSuccess)
         goto loser;
@@ -4009,16 +4029,19 @@ struct {
     EXP(KeyUpdate),
     EXP(SendSessionTicket),
     EXP(SetMaxEarlyDataSize),
     EXP(SetupAntiReplay),
     EXP(SetResumptionTokenCallback),
     EXP(SetResumptionToken),
     EXP(GetResumptionTokenInfo),
     EXP(DestroyResumptionTokenInfo),
+    EXP(SetESNIKeyPair),
+    EXP(EncodeESNIKeys),
+    EXP(EnableESNI),
 #endif
     { "", NULL }
 };
 #undef EXP
 #undef PUB
 
 void *
 SSL_GetExperimentalAPI(const char *name)
--- a/security/nss/lib/ssl/sslspec.c
+++ b/security/nss/lib/ssl/sslspec.c
@@ -198,17 +198,17 @@ ssl_SetupNullCipherSpec(sslSocket *ss, C
 void
 ssl_CipherSpecAddRef(ssl3CipherSpec *spec)
 {
     ++spec->refCt;
     SSL_TRC(10, ("%d: SSL[-]: Increment ref ct for %s spec %d. new ct = %d",
                  SSL_GETPID(), SPEC_DIR(spec), spec, spec->refCt));
 }
 
-static void
+void
 ssl_DestroyKeyMaterial(ssl3KeyMaterial *keyMaterial)
 {
     PK11_FreeSymKey(keyMaterial->key);
     PK11_FreeSymKey(keyMaterial->macKey);
     if (keyMaterial->macContext != NULL) {
         PK11_DestroyContext(keyMaterial->macContext, PR_TRUE);
     }
 }
--- a/security/nss/lib/ssl/sslt.h
+++ b/security/nss/lib/ssl/sslt.h
@@ -449,17 +449,18 @@ typedef enum {
     ssl_tls13_cookie_xtn = 44,
     ssl_tls13_psk_key_exchange_modes_xtn = 45,
     ssl_tls13_ticket_early_data_info_xtn = 46, /* Deprecated. */
     ssl_tls13_certificate_authorities_xtn = 47,
     ssl_signature_algorithms_cert_xtn = 50,
     ssl_tls13_key_share_xtn = 51,
     ssl_next_proto_nego_xtn = 13172, /* Deprecated. */
     ssl_renegotiation_info_xtn = 0xff01,
-    ssl_tls13_short_header_xtn = 0xff03 /* Deprecated. */
+    ssl_tls13_short_header_xtn = 0xff03, /* Deprecated. */
+    ssl_tls13_encrypted_sni_xtn = 0xffce,
 } SSLExtensionType;
 
 /* This is the old name for the supported_groups extensions. */
 #define ssl_elliptic_curves_xtn ssl_supported_groups_xtn
 
 /* SSL_MAX_EXTENSIONS includes the maximum number of extensions that are
  * supported for any single message type.  That is, a ClientHello; ServerHello
  * and TLS 1.3 NewSessionTicket and HelloRetryRequest extensions have fewer. */
--- a/security/nss/lib/ssl/tls13con.c
+++ b/security/nss/lib/ssl/tls13con.c
@@ -16,16 +16,17 @@
 #include "secmod.h"
 #include "sslimpl.h"
 #include "sslproto.h"
 #include "sslerr.h"
 #include "ssl3exthandle.h"
 #include "tls13hkdf.h"
 #include "tls13con.h"
 #include "tls13err.h"
+#include "tls13esni.h"
 #include "tls13exthandle.h"
 #include "tls13hashstate.h"
 
 static SECStatus tls13_SetCipherSpec(sslSocket *ss, PRUint16 epoch,
                                      CipherSpecDirection install,
                                      PRBool deleteSecret);
 static SECStatus tls13_AESGCM(
     ssl3KeyMaterial *keys,
@@ -128,31 +129,16 @@ const char keylogLabelClientTrafficSecre
 const char keylogLabelServerTrafficSecret[] = "SERVER_TRAFFIC_SECRET_0";
 const char keylogLabelEarlyExporterSecret[] = "EARLY_EXPORTER_SECRET";
 const char keylogLabelExporterSecret[] = "EXPORTER_SECRET";
 
 /* Belt and suspenders in case we ever add a TLS 1.4. */
 PR_STATIC_ASSERT(SSL_LIBRARY_VERSION_MAX_SUPPORTED <=
                  SSL_LIBRARY_VERSION_TLS_1_3);
 
-/* Use this instead of FATAL_ERROR when no alert shall be sent. */
-#define LOG_ERROR(ss, prError)                                                     \
-    do {                                                                           \
-        SSL_TRC(3, ("%d: TLS13[%d]: fatal error %d in %s (%s:%d)",                 \
-                    SSL_GETPID(), ss->fd, prError, __func__, __FILE__, __LINE__)); \
-        PORT_SetError(prError);                                                    \
-    } while (0)
-
-/* Log an error and generate an alert because something is irreparably wrong. */
-#define FATAL_ERROR(ss, prError, desc)       \
-    do {                                     \
-        LOG_ERROR(ss, prError);              \
-        tls13_FatalError(ss, prError, desc); \
-    } while (0)
-
 void
 tls13_FatalError(sslSocket *ss, PRErrorCode prError, SSL3AlertDescription desc)
 {
     PORT_Assert(desc != internal_error); /* These should never happen */
     (void)SSL3_SendAlert(ss, alert_fatal, desc);
     PORT_SetError(prError);
 }
 
@@ -350,46 +336,59 @@ tls13_ComputeHash(sslSocket *ss, SSL3Has
         return SECFailure;
     }
     hashes->len = tls13_GetHashSize(ss);
 
     return SECSuccess;
 }
 
 SECStatus
-tls13_CreateKeyShare(sslSocket *ss, const sslNamedGroupDef *groupDef)
+tls13_CreateKeyShare(sslSocket *ss, const sslNamedGroupDef *groupDef,
+                     sslEphemeralKeyPair **keyPair)
 {
     SECStatus rv;
-    sslEphemeralKeyPair *keyPair = NULL;
     const ssl3DHParams *params;
 
     PORT_Assert(groupDef);
     switch (groupDef->keaType) {
         case ssl_kea_ecdh:
-            rv = ssl_CreateECDHEphemeralKeyPair(ss, groupDef, &keyPair);
+            rv = ssl_CreateECDHEphemeralKeyPair(ss, groupDef, keyPair);
             if (rv != SECSuccess) {
                 return SECFailure;
             }
             break;
         case ssl_kea_dh:
             params = ssl_GetDHEParams(groupDef);
             PORT_Assert(params->name != ssl_grp_ffdhe_custom);
-            rv = ssl_CreateDHEKeyPair(groupDef, params, &keyPair);
+            rv = ssl_CreateDHEKeyPair(groupDef, params, keyPair);
             if (rv != SECSuccess) {
                 return SECFailure;
             }
             break;
         default:
             PORT_Assert(0);
             PORT_SetError(SEC_ERROR_LIBRARY_FAILURE);
             return SECFailure;
     }
 
+    return rv;
+}
+
+SECStatus
+tls13_AddKeyShare(sslSocket *ss, const sslNamedGroupDef *groupDef)
+{
+    sslEphemeralKeyPair *keyPair = NULL;
+    SECStatus rv;
+
+    rv = tls13_CreateKeyShare(ss, groupDef, &keyPair);
+    if (rv != SECSuccess) {
+        return SECFailure;
+    }
     PR_APPEND_LINK(&keyPair->link, &ss->ephemeralKeyPairs);
-    return rv;
+    return SECSuccess;
 }
 
 SECStatus
 SSL_SendAdditionalKeyShares(PRFileDesc *fd, unsigned int count)
 {
     sslSocket *ss = ssl_FindSocket(fd);
     if (!ss) {
         PORT_SetError(SEC_ERROR_INVALID_ARGS);
@@ -409,30 +408,36 @@ SSL_SendAdditionalKeyShares(PRFileDesc *
 SECStatus
 tls13_SetupClientHello(sslSocket *ss)
 {
     unsigned int i;
     SSL3Statistics *ssl3stats = SSL_GetStatistics();
     NewSessionTicket *session_ticket = NULL;
     sslSessionID *sid = ss->sec.ci.sid;
     unsigned int numShares = 0;
+    SECStatus rv;
 
     PORT_Assert(ss->opt.noLocks || ssl_HaveSSL3HandshakeLock(ss));
     PORT_Assert(ss->opt.noLocks || ssl_HaveXmitBufLock(ss));
     PORT_Assert(PR_CLIST_IS_EMPTY(&ss->ephemeralKeyPairs));
 
+    /* Do encrypted SNI. This may create a key share as a side effect. */
+    rv = tls13_ClientSetupESNI(ss);
+    if (rv != SECSuccess) {
+        return SECFailure;
+    }
+
     /* Select the first enabled group.
      * TODO(ekr@rtfm.com): be smarter about offering the group
      * that the other side negotiated if we are resuming. */
     for (i = 0; i < SSL_NAMED_GROUP_COUNT; ++i) {
-        SECStatus rv;
         if (!ss->namedGroupPreferences[i]) {
             continue;
         }
-        rv = tls13_CreateKeyShare(ss, ss->namedGroupPreferences[i]);
+        rv = tls13_AddKeyShare(ss, ss->namedGroupPreferences[i]);
         if (rv != SECSuccess) {
             return SECFailure;
         }
         if (++numShares > ss->additionalShares) {
             break;
         }
     }
 
@@ -451,18 +456,16 @@ tls13_SetupClientHello(sslSocket *ss)
     session_ticket = &sid->u.ssl3.locked.sessionTicket;
     PORT_Assert(session_ticket && session_ticket->ticket.data);
 
     if (ssl_TicketTimeValid(session_ticket)) {
         ss->statelessResume = PR_TRUE;
     }
 
     if (ss->statelessResume) {
-        SECStatus rv;
-
         PORT_Assert(ss->sec.ci.sid);
         rv = tls13_RecoverWrappedSharedSecret(ss, ss->sec.ci.sid);
         if (rv != SECSuccess) {
             FATAL_ERROR(ss, SEC_ERROR_LIBRARY_FAILURE, internal_error);
             SSL_AtomicIncrementLong(&ssl3stats->sch_sid_cache_not_ok);
             ssl_UncacheSessionID(ss);
             ssl_FreeSID(ss->sec.ci.sid);
             ss->sec.ci.sid = NULL;
@@ -482,17 +485,17 @@ tls13_SetupClientHello(sslSocket *ss)
             return SECFailure;
         }
     }
 
     return SECSuccess;
 }
 
 static SECStatus
-tls13_ImportDHEKeyShare(sslSocket *ss, SECKEYPublicKey *peerKey,
+tls13_ImportDHEKeyShare(SECKEYPublicKey *peerKey,
                         PRUint8 *b, PRUint32 length,
                         SECKEYPublicKey *pubKey)
 {
     SECStatus rv;
     SECItem publicValue = { siBuffer, NULL, 0 };
 
     publicValue.data = b;
     publicValue.len = length;
@@ -513,66 +516,72 @@ tls13_ImportDHEKeyShare(sslSocket *ss, S
     rv = SECITEM_CopyItem(peerKey->arena, &peerKey->u.dh.publicValue,
                           &publicValue);
     if (rv != SECSuccess)
         return SECFailure;
 
     return SECSuccess;
 }
 
-static SECStatus
+SECStatus
 tls13_HandleKeyShare(sslSocket *ss,
                      TLS13KeyShareEntry *entry,
-                     sslKeyPair *keyPair)
+                     sslKeyPair *keyPair,
+                     SSLHashType hash,
+                     PK11SymKey **out)
 {
     PORTCheapArenaPool arena;
     SECKEYPublicKey *peerKey;
     CK_MECHANISM_TYPE mechanism;
     PRErrorCode errorCode;
+    PK11SymKey *key;
     SECStatus rv;
+    int keySize = 0;
 
     PORT_InitCheapArena(&arena, DER_DEFAULT_CHUNKSIZE);
     peerKey = PORT_ArenaZNew(&arena.arena, SECKEYPublicKey);
     if (peerKey == NULL) {
         goto loser;
     }
     peerKey->arena = &arena.arena;
     peerKey->pkcs11Slot = NULL;
     peerKey->pkcs11ID = CK_INVALID_HANDLE;
 
     switch (entry->group->keaType) {
         case ssl_kea_ecdh:
-            rv = ssl_ImportECDHKeyShare(ss, peerKey,
+            rv = ssl_ImportECDHKeyShare(peerKey,
                                         entry->key_exchange.data,
                                         entry->key_exchange.len,
                                         entry->group);
             mechanism = CKM_ECDH1_DERIVE;
             break;
         case ssl_kea_dh:
-            rv = tls13_ImportDHEKeyShare(ss, peerKey,
+            rv = tls13_ImportDHEKeyShare(peerKey,
                                          entry->key_exchange.data,
                                          entry->key_exchange.len,
                                          keyPair->pubKey);
             mechanism = CKM_DH_PKCS_DERIVE;
+            keySize = peerKey->u.dh.publicValue.len;
             break;
         default:
             PORT_Assert(0);
             goto loser;
     }
     if (rv != SECSuccess) {
         goto loser;
     }
 
-    ss->ssl3.hs.dheSecret = PK11_PubDeriveWithKDF(
+    key = PK11_PubDeriveWithKDF(
         keyPair->privKey, peerKey, PR_FALSE, NULL, NULL, mechanism,
-        tls13_GetHkdfMechanism(ss), CKA_DERIVE, 0, CKD_NULL, NULL, NULL);
-    if (!ss->ssl3.hs.dheSecret) {
+        tls13_GetHkdfMechanismForHash(hash), CKA_DERIVE, keySize, CKD_NULL, NULL, NULL);
+    if (!key) {
         ssl_MapLowLevelError(SSL_ERROR_KEY_EXCHANGE_FAILURE);
         goto loser;
     }
+    *out = key;
     PORT_DestroyCheapArena(&arena);
     return SECSuccess;
 
 loser:
     PORT_DestroyCheapArena(&arena);
     errorCode = PORT_GetError(); /* don't overwrite the error code */
     tls13_FatalError(ss, errorCode, illegal_parameter);
     return SECFailure;
@@ -2020,17 +2029,17 @@ tls13_HandleClientKeyShare(sslSocket *ss
 
     PORT_Assert(ss->opt.noLocks || ssl_HaveRecvBufLock(ss));
     PORT_Assert(ss->opt.noLocks || ssl_HaveSSL3HandshakeLock(ss));
     PORT_Assert(peerShare);
 
     tls13_SetKeyExchangeType(ss, peerShare->group);
 
     /* Generate our key */
-    rv = tls13_CreateKeyShare(ss, peerShare->group);
+    rv = tls13_AddKeyShare(ss, peerShare->group);
     if (rv != SECSuccess) {
         return rv;
     }
 
     /* We should have exactly one key share. */
     PORT_Assert(!PR_CLIST_IS_EMPTY(&ss->ephemeralKeyPairs));
     PORT_Assert(PR_PREV_LINK(&ss->ephemeralKeyPairs) ==
                 PR_NEXT_LINK(&ss->ephemeralKeyPairs));
@@ -2040,17 +2049,19 @@ tls13_HandleClientKeyShare(sslSocket *ss
 
     /* Register the sender */
     rv = ssl3_RegisterExtensionSender(ss, &ss->xtnData, ssl_tls13_key_share_xtn,
                                       tls13_ServerSendKeyShareXtn);
     if (rv != SECSuccess) {
         return SECFailure; /* Error code set already. */
     }
 
-    rv = tls13_HandleKeyShare(ss, peerShare, keyPair->keys);
+    rv = tls13_HandleKeyShare(ss, peerShare, keyPair->keys,
+                              tls13_GetHash(ss),
+                              &ss->ssl3.hs.dheSecret);
     return rv; /* Error code set already. */
 }
 
 /*
  *     [draft-ietf-tls-tls13-11] Section 6.3.3.2
  *
  *     opaque DistinguishedName<1..2^16-1>;
  *
@@ -2307,18 +2318,18 @@ tls13_HandleCertificateRequest(sslSocket
         return SECFailure;
     }
 
     ss->ssl3.hs.clientCertRequested = PR_TRUE;
     TLS13_SET_HS_STATE(ss, wait_server_cert);
     return SECSuccess;
 }
 
-static PRBool
-tls13_CanRequestClientAuth(sslSocket *ss)
+PRBool
+tls13_ShouldRequestClientAuth(sslSocket *ss)
 {
     return ss->opt.requestCertificate &&
            ss->ssl3.hs.kea_def->authKeyType != ssl_auth_psk;
 }
 
 static SECStatus
 tls13_SendEncryptedServerSequence(sslSocket *ss)
 {
@@ -2345,17 +2356,17 @@ tls13_SendEncryptedServerSequence(sslSoc
         }
     }
 
     rv = tls13_SendEncryptedExtensions(ss);
     if (rv != SECSuccess) {
         return SECFailure; /* error code is set. */
     }
 
-    if (tls13_CanRequestClientAuth(ss)) {
+    if (tls13_ShouldRequestClientAuth(ss)) {
         rv = tls13_SendCertificateRequest(ss);
         if (rv != SECSuccess) {
             return SECFailure; /* error code is set. */
         }
     }
     if (ss->ssl3.hs.signatureScheme != ssl_sig_none) {
         SECKEYPrivateKey *svrPrivKey;
 
@@ -2464,17 +2475,17 @@ tls13_SendServerHelloSequence(sslSocket 
 
         rv = tls13_SetCipherSpec(ss,
                                  TrafficKeyHandshake,
                                  CipherSpecRead, PR_FALSE);
         if (rv != SECSuccess) {
             LOG_ERROR(ss, SEC_ERROR_LIBRARY_FAILURE);
             return SECFailure;
         }
-        if (tls13_CanRequestClientAuth(ss)) {
+        if (tls13_ShouldRequestClientAuth(ss)) {
             TLS13_SET_HS_STATE(ss, wait_client_cert);
         } else {
             TLS13_SET_HS_STATE(ss, wait_finished);
         }
     }
 
     ss->ssl3.hs.serverHelloTime = ssl_TimeUsec();
     return SECSuccess;
@@ -2640,17 +2651,19 @@ tls13_HandleServerKeyShare(sslSocket *ss
     keyPair = ssl_LookupEphemeralKeyPair(ss, entry->group);
     if (!keyPair) {
         FATAL_ERROR(ss, SSL_ERROR_RX_MALFORMED_KEY_SHARE, illegal_parameter);
         return SECFailure;
     }
 
     PORT_Assert(ssl_NamedGroupEnabled(ss, entry->group));
 
-    rv = tls13_HandleKeyShare(ss, entry, keyPair->keys);
+    rv = tls13_HandleKeyShare(ss, entry, keyPair->keys,
+                              tls13_GetHash(ss),
+                              &ss->ssl3.hs.dheSecret);
     if (rv != SECSuccess)
         return SECFailure; /* Error code set by caller. */
 
     tls13_SetKeyExchangeType(ss, entry->group);
     ss->sec.keaKeyBits = SECKEY_PublicKeyStrengthInBits(keyPair->keys->pubKey);
 
     return SECSuccess;
 }
@@ -3196,16 +3209,31 @@ tls13_SetSpecRecordVersion(sslSocket *ss
         spec->recordVersion = SSL_LIBRARY_VERSION_DTLS_1_2_WIRE;
     } else {
         spec->recordVersion = SSL_LIBRARY_VERSION_TLS_1_2;
     }
     SSL_TRC(10, ("%d: TLS13[%d]: set spec=%d record version to 0x%04x",
                  SSL_GETPID(), ss->fd, spec, spec->recordVersion));
 }
 
+SSLAEADCipher
+tls13_GetAead(const ssl3BulkCipherDef *cipherDef)
+{
+    switch (cipherDef->calg) {
+        case ssl_calg_aes_gcm:
+            return tls13_AESGCM;
+        case ssl_calg_chacha20:
+            return tls13_ChaCha20Poly1305;
+        default:
+            PORT_Assert(PR_FALSE);
+            PORT_SetError(SEC_ERROR_LIBRARY_FAILURE);
+            return NULL;
+    }
+}
+
 static SECStatus
 tls13_SetupPendingCipherSpec(sslSocket *ss, ssl3CipherSpec *spec)
 {
     ssl3CipherSuite suite = ss->ssl3.hs.cipher_suite;
 
     PORT_Assert(ss->opt.noLocks || ssl_HaveSSL3HandshakeLock(ss));
     PORT_Assert(spec->epoch);
 
@@ -3219,26 +3247,19 @@ tls13_SetupPendingCipherSpec(sslSocket *
     if (IS_DTLS(ss) && spec->direction == CipherSpecRead) {
         ssl_CipherSpecAddRef(spec);
     }
 
     SSL_TRC(3, ("%d: TLS13[%d]: Set Pending Cipher Suite to 0x%04x",
                 SSL_GETPID(), ss->fd, suite));
 
     spec->cipherDef = ssl_GetBulkCipherDef(ssl_LookupCipherSuiteDef(suite));
-    switch (spec->cipherDef->calg) {
-        case ssl_calg_aes_gcm:
-            spec->aead = tls13_AESGCM;
-            break;
-        case ssl_calg_chacha20:
-            spec->aead = tls13_ChaCha20Poly1305;
-            break;
-        default:
-            PORT_Assert(0);
-            return SECFailure;
+    spec->aead = tls13_GetAead(spec->cipherDef);
+    if (!spec->aead) {
+        return SECFailure;
     }
 
     if (spec->epoch == TrafficKeyEarlyApplicationData) {
         spec->earlyDataRemaining =
             ss->sec.ci.sid->u.ssl3.locked.sessionTicket.max_early_data_size;
     }
 
     tls13_SetSpecRecordVersion(ss, spec);
@@ -3410,19 +3431,40 @@ tls13_ComputeHandshakeHashes(sslSocket *
 
     return SECSuccess;
 
 loser:
     PK11_DestroyContext(ctx, PR_TRUE);
     return SECFailure;
 }
 
+TLS13KeyShareEntry *
+tls13_CopyKeyShareEntry(TLS13KeyShareEntry *o)
+{
+    TLS13KeyShareEntry *n;
+
+    PORT_Assert(o);
+    n = PORT_ZNew(TLS13KeyShareEntry);
+    if (!n) {
+        return NULL;
+    }
+
+    if (SECSuccess != SECITEM_CopyItem(NULL, &n->key_exchange, &o->key_exchange)) {
+        PORT_Free(n);
+    }
+    n->group = o->group;
+    return n;
+}
+
 void
 tls13_DestroyKeyShareEntry(TLS13KeyShareEntry *offer)
 {
+    if (!offer) {
+        return;
+    }
     SECITEM_ZfreeItem(&offer->key_exchange, PR_FALSE);
     PORT_ZFree(offer, sizeof(*offer));
 }
 
 void
 tls13_DestroyKeyShares(PRCList *list)
 {
     PRCList *cur_p;
@@ -3533,17 +3575,17 @@ tls13_AESGCM(ssl3KeyMaterial *keys,
              const unsigned char *in,
              int inlen,
              const unsigned char *additionalData,
              int additionalDataLen)
 {
     CK_GCM_PARAMS gcmParams;
     unsigned char nonce[12];
 
-    PORT_Assert(additionalDataLen > 8);
+    PORT_Assert(additionalDataLen >= 8);
     memset(&gcmParams, 0, sizeof(gcmParams));
     gcmParams.pIv = nonce;
     gcmParams.ulIvLen = sizeof(nonce);
     gcmParams.pAAD = (PRUint8 *)(additionalData + 8);
     gcmParams.ulAADLen = additionalDataLen - 8;
     gcmParams.ulTagBits = 128; /* GCM measures tag length in bits. */
 
     tls13_WriteNonce(keys, additionalData, 8,
@@ -3610,17 +3652,33 @@ tls13_HandleEncryptedExtensions(sslSocke
     /* If we are doing 0-RTT, then we already have an ALPN value. Stash
      * it for comparison. */
     if (ss->ssl3.hs.zeroRttState == ssl_0rtt_sent &&
         ss->xtnData.nextProtoState == SSL_NEXT_PROTO_EARLY_VALUE) {
         oldAlpn = ss->xtnData.nextProto;
         ss->xtnData.nextProto.data = NULL;
         ss->xtnData.nextProtoState = SSL_NEXT_PROTO_NO_SUPPORT;
     }
-    rv = ssl3_HandleExtensions(ss, &b, &length, ssl_hs_encrypted_extensions);
+
+    rv = ssl3_ParseExtensions(ss, &b, &length);
+    if (rv != SECSuccess) {
+        return SECFailure; /* Error code set below */
+    }
+
+    /* If we sent ESNI, check the nonce. */
+    if (ss->xtnData.esniPrivateKey) {
+        PORT_Assert(ssl3_ExtensionAdvertised(ss, ssl_tls13_encrypted_sni_xtn));
+        rv = tls13_ClientCheckEsniXtn(ss);
+        if (rv != SECSuccess) {
+            return SECFailure;
+        }
+    }
+
+    /* Handle the rest of the extensions. */
+    rv = ssl3_HandleParsedExtensions(ss, ssl_hs_encrypted_extensions);
     if (rv != SECSuccess) {
         return SECFailure; /* Error code set below */
     }
 
     /* We can only get here if we offered 0-RTT. */
     if (ssl3_ExtensionNegotiated(ss, ssl_tls13_early_data_xtn)) {
         PORT_Assert(ss->ssl3.hs.zeroRttState == ssl_0rtt_sent);
         if (!ss->statelessResume) {
@@ -4173,17 +4231,17 @@ tls13_ServerHandleFinished(sslSocket *ss
                 SSL_GETPID(), ss->fd));
 
     rv = tls13_CommonHandleFinished(ss, ss->ssl3.hs.clientHsTrafficSecret,
                                     b, length);
     if (rv != SECSuccess) {
         return SECFailure;
     }
 
-    if (!tls13_CanRequestClientAuth(ss) &&
+    if (!tls13_ShouldRequestClientAuth(ss) &&
         (ss->ssl3.hs.zeroRttState != ssl_0rtt_done)) {
         dtls_ReceivedFirstMessageInFlight(ss);
     }
 
     rv = tls13_SetCipherSpec(ss, TrafficKeyApplicationData,
                              CipherSpecRead, PR_FALSE);
     if (rv != SECSuccess) {
         FATAL_ERROR(ss, SEC_ERROR_LIBRARY_FAILURE, internal_error);
@@ -4742,17 +4800,18 @@ static const struct {
     { ssl_signed_cert_timestamp_xtn, _M3(client_hello, certificate_request,
                                          certificate) },
     { ssl_cert_status_xtn, _M3(client_hello, certificate_request,
                                certificate) },
     { ssl_tls13_cookie_xtn, _M2(client_hello, hello_retry_request) },
     { ssl_tls13_certificate_authorities_xtn, _M1(certificate_request) },
     { ssl_tls13_supported_versions_xtn, _M3(client_hello, server_hello,
                                             hello_retry_request) },
-    { ssl_record_size_limit_xtn, _M2(client_hello, encrypted_extensions) }
+    { ssl_record_size_limit_xtn, _M2(client_hello, encrypted_extensions) },
+    { ssl_tls13_encrypted_sni_xtn, _M2(client_hello, encrypted_extensions) }
 };
 
 tls13ExtensionStatus
 tls13_ExtensionStatus(PRUint16 extension, SSLHandshakeType message)
 {
     unsigned int i;
 
     PORT_Assert((message == ssl_hs_client_hello) ||
@@ -5226,17 +5285,17 @@ tls13_HandleEndOfEarlyData(sslSocket *ss
     rv = tls13_SetCipherSpec(ss, TrafficKeyHandshake,
                              CipherSpecRead, PR_FALSE);
     if (rv != SECSuccess) {
         PORT_SetError(SEC_ERROR_LIBRARY_FAILURE);
         return SECFailure;
     }
 
     ss->ssl3.hs.zeroRttState = ssl_0rtt_done;
-    if (tls13_CanRequestClientAuth(ss)) {
+    if (tls13_ShouldRequestClientAuth(ss)) {
         TLS13_SET_HS_STATE(ss, wait_client_cert);
     } else {
         TLS13_SET_HS_STATE(ss, wait_finished);
     }
     return SECSuccess;
 }
 
 SECStatus
--- a/security/nss/lib/ssl/tls13con.h
+++ b/security/nss/lib/ssl/tls13con.h
@@ -80,19 +80,27 @@ SECStatus tls13_HandlePostHelloHandshake
 SECStatus tls13_ConstructHelloRetryRequest(sslSocket *ss,
                                            ssl3CipherSuite cipherSuite,
                                            const sslNamedGroupDef *selectedGroup,
                                            PRUint8 *cookie,
                                            unsigned int cookieLen,
                                            sslBuffer *buffer);
 SECStatus tls13_HandleHelloRetryRequest(sslSocket *ss, const PRUint8 *b,
                                         PRUint32 length);
+SECStatus tls13_HandleKeyShare(sslSocket *ss,
+                               TLS13KeyShareEntry *entry,
+                               sslKeyPair *keyPair,
+                               SSLHashType hash,
+                               PK11SymKey **out);
+TLS13KeyShareEntry *tls13_CopyKeyShareEntry(TLS13KeyShareEntry *o);
 void tls13_DestroyKeyShareEntry(TLS13KeyShareEntry *entry);
 void tls13_DestroyKeyShares(PRCList *list);
-SECStatus tls13_CreateKeyShare(sslSocket *ss, const sslNamedGroupDef *groupDef);
+SECStatus tls13_CreateKeyShare(sslSocket *ss, const sslNamedGroupDef *groupDef,
+                               sslEphemeralKeyPair **keyPair);
+SECStatus tls13_AddKeyShare(sslSocket *ss, const sslNamedGroupDef *groupDef);
 void tls13_DestroyEarlyData(PRCList *list);
 SECStatus tls13_SetAlertCipherSpec(sslSocket *ss);
 tls13ExtensionStatus tls13_ExtensionStatus(PRUint16 extension,
                                            SSLHandshakeType message);
 SECStatus tls13_ProtectRecord(sslSocket *ss,
                               ssl3CipherSpec *cwSpec,
                               SSLContentType type,
                               const PRUint8 *pIn,
@@ -101,25 +109,42 @@ SECStatus tls13_ProtectRecord(sslSocket 
 PRInt32 tls13_Read0RttData(sslSocket *ss, void *buf, PRInt32 len);
 SECStatus tls13_HandleEarlyApplicationData(sslSocket *ss, sslBuffer *origBuf);
 PRBool tls13_ClientAllow0Rtt(const sslSocket *ss, const sslSessionID *sid);
 PRUint16 tls13_EncodeDraftVersion(SSL3ProtocolVersion version,
                                   SSLProtocolVariant variant);
 SECStatus tls13_ClientReadSupportedVersion(sslSocket *ss);
 SECStatus tls13_NegotiateVersion(sslSocket *ss,
                                  const TLSExtension *supported_versions);
+PRBool tls13_ShouldRequestClientAuth(sslSocket *ss);
 
 PRBool tls13_IsReplay(const sslSocket *ss, const sslSessionID *sid);
 void tls13_AntiReplayRollover(PRTime now);
 
 SECStatus SSLExp_SetupAntiReplay(PRTime window, unsigned int k,
                                  unsigned int bits);
 
 SECStatus SSLExp_HelloRetryRequestCallback(PRFileDesc *fd,
                                            SSLHelloRetryRequestCallback cb,
                                            void *arg);
 SECStatus tls13_SendKeyUpdate(sslSocket *ss, tls13KeyUpdateRequest request,
                               PRBool buffer);
 SECStatus SSLExp_KeyUpdate(PRFileDesc *fd, PRBool requestUpdate);
 PRBool tls13_MaybeTls13(sslSocket *ss);
+SSLAEADCipher tls13_GetAead(const ssl3BulkCipherDef *cipherDef);
 void tls13_SetSpecRecordVersion(sslSocket *ss, ssl3CipherSpec *spec);
 
+/* Use this instead of FATAL_ERROR when no alert shall be sent. */
+#define LOG_ERROR(ss, prError)                                                     \
+    do {                                                                           \
+        SSL_TRC(3, ("%d: TLS13[%d]: fatal error %d in %s (%s:%d)",                 \
+                    SSL_GETPID(), ss->fd, prError, __func__, __FILE__, __LINE__)); \
+        PORT_SetError(prError);                                                    \
+    } while (0)
+
+/* Log an error and generate an alert because something is irreparably wrong. */
+#define FATAL_ERROR(ss, prError, desc)       \
+    do {                                     \
+        LOG_ERROR(ss, prError);              \
+        tls13_FatalError(ss, prError, desc); \
+    } while (0)
+
 #endif /* __tls13con_h_ */
new file mode 100644
--- /dev/null
+++ b/security/nss/lib/ssl/tls13esni.c
@@ -0,0 +1,844 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * 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/. */
+
+#define TLS13_ESNI_VERSION 0xff01
+
+/*
+ *  struct {
+ *      uint16 version;
+ *      uint8 checksum[4];
+ *      KeyShareEntry keys<4..2^16-1>;
+ *      CipherSuite cipher_suites<2..2^16-2>;
+ *      uint16 padded_length;
+ *      uint64 not_before;
+ *      uint64 not_after;
+ *      Extension extensions<0..2^16-1>;
+ *  } ESNIKeys;
+ */
+#include "nss.h"
+#include "pk11func.h"
+#include "ssl.h"
+#include "sslproto.h"
+#include "sslimpl.h"
+#include "ssl3exthandle.h"
+#include "tls13esni.h"
+#include "tls13exthandle.h"
+#include "tls13hkdf.h"
+
+const char kHkdfPurposeEsniKey[] = "esni key";
+const char kHkdfPurposeEsniIv[] = "esni iv";
+
+void
+tls13_DestroyESNIKeys(sslEsniKeys *keys)
+{
+    if (!keys) {
+        return;
+    }
+    SECITEM_FreeItem(&keys->data, PR_FALSE);
+    PORT_Free((void *)keys->dummySni);
+    tls13_DestroyKeyShares(&keys->keyShares);
+    ssl_FreeEphemeralKeyPair(keys->privKey);
+    SECITEM_FreeItem(&keys->suites, PR_FALSE);
+    PORT_ZFree(keys, sizeof(sslEsniKeys));
+}
+
+sslEsniKeys *
+tls13_CopyESNIKeys(sslEsniKeys *okeys)
+{
+    sslEsniKeys *nkeys;
+    SECStatus rv;
+
+    PORT_Assert(okeys);
+
+    nkeys = PORT_ZNew(sslEsniKeys);
+    if (!nkeys) {
+        return NULL;
+    }
+    PR_INIT_CLIST(&nkeys->keyShares);
+    rv = SECITEM_CopyItem(NULL, &nkeys->data, &okeys->data);
+    if (rv != SECSuccess) {
+        goto loser;
+    }
+    if (okeys->dummySni) {
+        nkeys->dummySni = PORT_Strdup(okeys->dummySni);
+        if (!nkeys->dummySni) {
+            goto loser;
+        }
+    }
+    for (PRCList *cur_p = PR_LIST_HEAD(&okeys->keyShares);
+         cur_p != &okeys->keyShares;
+         cur_p = PR_NEXT_LINK(cur_p)) {
+        TLS13KeyShareEntry *copy = tls13_CopyKeyShareEntry(
+            (TLS13KeyShareEntry *)cur_p);
+        if (!copy) {
+            goto loser;
+        }
+        PR_APPEND_LINK(&copy->link, &nkeys->keyShares);
+    }
+    if (okeys->privKey) {
+        nkeys->privKey = ssl_CopyEphemeralKeyPair(okeys->privKey);
+        if (!nkeys->privKey) {
+            goto loser;
+        }
+    }
+    rv = SECITEM_CopyItem(NULL, &nkeys->suites, &okeys->suites);
+    if (rv != SECSuccess) {
+        goto loser;
+    }
+    nkeys->paddedLength = okeys->paddedLength;
+    nkeys->notBefore = okeys->notBefore;
+    nkeys->notAfter = okeys->notAfter;
+    return nkeys;
+
+loser:
+    tls13_DestroyESNIKeys(nkeys);
+    return NULL;
+}
+
+/* Checksum is a 4-byte array. */
+static SECStatus
+tls13_ComputeESNIKeysChecksum(const PRUint8 *buf, unsigned int len,
+                              PRUint8 *checksum)
+{
+    SECItem copy;
+    SECStatus rv;
+    PRUint8 sha256[32];
+
+    rv = SECITEM_MakeItem(NULL, &copy, buf, len);
+    if (rv != SECSuccess) {
+        return SECFailure;
+    }
+
+    /* Stomp the checksum. */
+    PORT_Memset(copy.data + 2, 0, 4);
+
+    rv = PK11_HashBuf(ssl3_HashTypeToOID(ssl_hash_sha256),
+                      sha256,
+                      copy.data, copy.len);
+    SECITEM_FreeItem(&copy, PR_FALSE);
+    if (rv != SECSuccess) {
+        return SECFailure;
+    }
+    PORT_Memcpy(checksum, sha256, 4);
+    return SECSuccess;
+}
+
+static SECStatus
+tls13_DecodeESNIKeys(SECItem *data, sslEsniKeys **keysp)
+{
+    SECStatus rv;
+    sslReadBuffer tmp;
+    PRUint64 tmpn;
+    sslEsniKeys *keys;
+    PRUint8 checksum[4];
+    sslReader rdr = SSL_READER(data->data, data->len);
+
+    rv = sslRead_ReadNumber(&rdr, 2, &tmpn);
+    if (rv != SECSuccess) {
+        return SECFailure;
+    }
+    if (tmpn != TLS13_ESNI_VERSION) {
+        PORT_SetError(SSL_ERROR_UNSUPPORTED_VERSION);
+        return SECFailure;
+    }
+    keys = PORT_ZNew(sslEsniKeys);
+    if (!keys) {
+        return SECFailure;
+    }
+    PR_INIT_CLIST(&keys->keyShares);
+
+    /* Make a copy. */
+    rv = SECITEM_CopyItem(NULL, &keys->data, data);
+    if (rv != SECSuccess) {
+        goto loser;
+    }
+
+    rv = tls13_ComputeESNIKeysChecksum(data->data, data->len, checksum);
+    if (rv != SECSuccess) {
+        goto loser;
+    }
+
+    /* Read and check checksum. */
+    rv = sslRead_Read(&rdr, 4, &tmp);
+    if (rv != SECSuccess) {
+        goto loser;
+    }
+
+    if (0 != NSS_SecureMemcmp(tmp.buf, checksum, 4)) {
+        goto loser;
+    }
+
+    /* Parse the key shares. */
+    rv = sslRead_ReadVariable(&rdr, 2, &tmp);
+    if (rv != SECSuccess) {
+        goto loser;
+    }
+
+    sslReader rdr2 = SSL_READER(tmp.buf, tmp.len);
+    while (SSL_READER_REMAINING(&rdr2)) {
+        TLS13KeyShareEntry *ks = NULL;
+
+        rv = tls13_DecodeKeyShareEntry(&rdr2, &ks);
+        if (rv != SECSuccess) {
+            goto loser;
+        }
+
+        if (ks) {
+            PR_APPEND_LINK(&ks->link, &keys->keyShares);
+        }
+    }
+
+    /* Parse cipher suites. */
+    rv = sslRead_ReadVariable(&rdr, 2, &tmp);
+    if (rv != SECSuccess) {
+        goto loser;
+    }
+    /* This can't be odd. */
+    if (tmp.len & 1) {
+        goto loser;
+    }
+    rv = SECITEM_MakeItem(NULL, &keys->suites, (PRUint8 *)tmp.buf, tmp.len);
+    if (rv != SECSuccess) {
+        goto loser;
+    }
+
+    /* Padded Length */
+    rv = sslRead_ReadNumber(&rdr, 2, &tmpn);
+    if (rv != SECSuccess) {
+        goto loser;
+    }
+    keys->paddedLength = (PRUint16)tmpn;
+
+    /* Not Before */
+    rv = sslRead_ReadNumber(&rdr, 8, &keys->notBefore);
+    if (rv != SECSuccess) {
+        goto loser;
+    }
+
+    /* Not After */
+    rv = sslRead_ReadNumber(&rdr, 8, &keys->notAfter);
+    if (rv != SECSuccess) {
+        goto loser;
+    }
+
+    /* Extensions, which we ignore. */
+    rv = sslRead_ReadVariable(&rdr, 2, &tmp);
+    if (rv != SECSuccess) {
+        goto loser;
+    }
+
+    /* Check that this is empty. */
+    if (SSL_READER_REMAINING(&rdr) > 0) {
+        goto loser;
+    }
+
+    *keysp = keys;
+    return SECSuccess;
+
+loser:
+    tls13_DestroyESNIKeys(keys);
+    PORT_SetError(SSL_ERROR_RX_MALFORMED_ESNI_KEYS);
+
+    return SECFailure;
+}
+
+/* Encode an ESNI keys structure. We only allow one key
+ * share. */
+SECStatus
+SSLExp_EncodeESNIKeys(PRUint16 *cipherSuites, unsigned int cipherSuiteCount,
+                      SSLNamedGroup group, SECKEYPublicKey *pubKey,
+                      PRUint16 pad, PRUint64 notBefore, PRUint64 notAfter,
+                      PRUint8 *out, unsigned int *outlen, unsigned int maxlen)
+{
+    unsigned int savedOffset;
+    SECStatus rv;
+    sslBuffer b = SSL_BUFFER_EMPTY;
+
+    rv = sslBuffer_AppendNumber(&b, TLS13_ESNI_VERSION, 2);
+    if (rv != SECSuccess) {
+        goto loser;
+    }
+
+    rv = sslBuffer_Skip(&b, 4, &savedOffset);
+    if (rv != SECSuccess) {
+        goto loser;
+    }
+
+    /* Length of vector. */
+    rv = sslBuffer_AppendNumber(
+        &b, tls13_SizeOfKeyShareEntry(pubKey), 2);
+    if (rv != SECSuccess) {
+        goto loser;
+    }
+
+    /* Our one key share. */
+    rv = tls13_EncodeKeyShareEntry(&b, group, pubKey);
+    if (rv != SECSuccess) {
+        goto loser;
+    }
+
+    /* Cipher suites. */
+    rv = sslBuffer_AppendNumber(&b, cipherSuiteCount * 2, 2);
+    if (rv != SECSuccess) {
+        goto loser;
+    }
+    for (unsigned int i = 0; i < cipherSuiteCount; i++) {
+        rv = sslBuffer_AppendNumber(&b, cipherSuites[i], 2);
+        if (rv != SECSuccess) {
+            goto loser;
+        }
+    }
+
+    /* Padding Length. Fixed for now. */
+    rv = sslBuffer_AppendNumber(&b, pad, 2);
+    if (rv != SECSuccess) {
+        goto loser;
+    }
+
+    /* Start time. */
+    rv = sslBuffer_AppendNumber(&b, notBefore, 8);
+    if (rv != SECSuccess) {
+        goto loser;
+    }
+
+    /* End time. */
+    rv = sslBuffer_AppendNumber(&b, notAfter, 8);
+    if (rv != SECSuccess) {
+        goto loser;
+    }
+
+    /* No extensions. */
+    rv = sslBuffer_AppendNumber(&b, 0, 2);
+    if (rv != SECSuccess) {
+        goto loser;
+    }
+
+    rv = tls13_ComputeESNIKeysChecksum(SSL_BUFFER_BASE(&b),
+                                       SSL_BUFFER_LEN(&b),
+                                       SSL_BUFFER_BASE(&b) + 2);
+    if (rv != SECSuccess) {
+        PORT_Assert(PR_FALSE);
+        goto loser;
+    }
+
+    if (SSL_BUFFER_LEN(&b) > maxlen) {
+        PORT_SetError(SEC_ERROR_INVALID_ARGS);
+        goto loser;
+    }
+    PORT_Memcpy(out, SSL_BUFFER_BASE(&b), SSL_BUFFER_LEN(&b));
+    *outlen = SSL_BUFFER_LEN(&b);
+
+    sslBuffer_Clear(&b);
+    return SECSuccess;
+loser:
+    sslBuffer_Clear(&b);
+    return SECFailure;
+}
+
+SECStatus
+SSLExp_SetESNIKeyPair(PRFileDesc *fd,
+                      SECKEYPrivateKey *privKey,
+                      const PRUint8 *record, unsigned int recordLen)
+{
+    sslSocket *ss;
+    SECStatus rv;
+    sslEsniKeys *keys = NULL;
+    SECKEYPublicKey *pubKey = NULL;
+    SECItem data = { siBuffer, CONST_CAST(PRUint8, record), recordLen };
+    PLArenaPool *arena = NULL;
+
+    ss = ssl_FindSocket(fd);
+    if (!ss) {
+        SSL_DBG(("%d: SSL[%d]: bad socket in %s",
+                 SSL_GETPID(), fd, __FUNCTION__));
+        return SECFailure;
+    }
+
+    rv = tls13_DecodeESNIKeys(&data, &keys);
+    if (rv != SECSuccess) {
+        return SECFailure;
+    }
+
+    /* Check the cipher suites. */
+    (void)ssl3_config_match_init(ss);
+    /* Make sure the cipher suite is OK. */
+    SSLVersionRange vrange = { SSL_LIBRARY_VERSION_TLS_1_3,
+                               SSL_LIBRARY_VERSION_TLS_1_3 };
+
+    sslReader csrdr = SSL_READER(keys->suites.data,
+                                 keys->suites.len);
+    while (SSL_READER_REMAINING(&csrdr)) {
+        PRUint64 asuite;
+
+        rv = sslRead_ReadNumber(&csrdr, 2, &asuite);
+        if (rv != SECSuccess) {
+            goto loser;
+        }
+        const ssl3CipherSuiteCfg *suiteCfg =
+            ssl_LookupCipherSuiteCfg(asuite, ss->cipherSuites);
+        if (!ssl3_config_match(suiteCfg, ss->ssl3.policy, &vrange, ss)) {
+            /* Illegal suite. */
+            PORT_SetError(SEC_ERROR_INVALID_ARGS);
+            goto loser;
+        }
+    }
+
+    if (PR_CLIST_IS_EMPTY(&keys->keyShares)) {
+        PORT_SetError(SEC_ERROR_INVALID_ARGS);
+        goto loser;
+    }
+    if (PR_PREV_LINK(&keys->keyShares) != PR_NEXT_LINK(&keys->keyShares)) {
+        PORT_SetError(SEC_ERROR_INVALID_ARGS);
+        goto loser;
+    }
+    TLS13KeyShareEntry *entry = (TLS13KeyShareEntry *)PR_LIST_HEAD(
+        &keys->keyShares);
+    if (entry->group->keaType != ssl_kea_ecdh) {
+        PORT_SetError(SEC_ERROR_INVALID_ARGS);
+        goto loser;
+    }
+    arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
+    if (!arena) {
+        goto loser;
+    }
+    pubKey = PORT_ArenaZNew(arena, SECKEYPublicKey);
+    if (!pubKey) {
+        goto loser;
+    }
+    pubKey->arena = arena;
+    arena = NULL; /* From here, this will be destroyed with the pubkey. */
+    /* Dummy PKCS11 values because this key isn't on a slot. */
+    pubKey->pkcs11Slot = NULL;
+    pubKey->pkcs11ID = CK_INVALID_HANDLE;
+    rv = ssl_ImportECDHKeyShare(pubKey,
+                                entry->key_exchange.data,
+                                entry->key_exchange.len,
+                                entry->group);
+    if (rv != SECSuccess) {
+        goto loser;
+    }
+
+    privKey = SECKEY_CopyPrivateKey(privKey);
+    if (!privKey) {
+        goto loser;
+    }
+    keys->privKey = ssl_NewEphemeralKeyPair(entry->group, privKey, pubKey);
+    if (!keys->privKey) {
+        goto loser;
+    }
+    pubKey = NULL;
+    ss->esniKeys = keys;
+    return SECSuccess;
+
+loser:
+    PORT_FreeArena(arena, PR_FALSE);
+    SECKEY_DestroyPublicKey(pubKey);
+    tls13_DestroyESNIKeys(keys);
+    return SECFailure;
+}
+
+SECStatus
+SSLExp_EnableESNI(PRFileDesc *fd,
+                  const PRUint8 *esniKeys,
+                  unsigned int esniKeysLen,
+                  const char *dummySNI)
+{
+    sslSocket *ss;
+    sslEsniKeys *keys = NULL;
+    SECItem data = { siBuffer, CONST_CAST(PRUint8, esniKeys), esniKeysLen };
+    SECStatus rv;
+
+    ss = ssl_FindSocket(fd);
+    if (!ss) {
+        SSL_DBG(("%d: SSL[%d]: bad socket in %s",
+                 SSL_GETPID(), fd, __FUNCTION__));
+        return SECFailure;
+    }
+
+    rv = tls13_DecodeESNIKeys(&data, &keys);
+    if (rv != SECSuccess) {
+        return SECFailure;
+    }
+
+    if (dummySNI) {
+        keys->dummySni = PORT_Strdup(dummySNI);
+        if (!keys->dummySni) {
+            tls13_DestroyESNIKeys(keys);
+            return SECFailure;
+        }
+    }
+
+    /* Delete in case it was set before. */
+    tls13_DestroyESNIKeys(ss->esniKeys);
+    ss->esniKeys = keys;
+
+    return SECSuccess;
+}
+
+/*
+ * struct {
+ *     opaque record_digest<0..2^16-1>;
+ *     KeyShareEntry esni_key_share;
+ *     Random client_hello_random;
+ * } ESNIContents;
+ */
+SECStatus
+tls13_ComputeESNIKeys(const sslSocket *ss,
+                      TLS13KeyShareEntry *entry,
+                      sslKeyPair *keyPair,
+                      const ssl3CipherSuiteDef *suite,
+                      const PRUint8 *esniKeysHash,
+                      const PRUint8 *keyShareBuf,
+                      unsigned int keyShareBufLen,
+                      const PRUint8 *clientRandom,
+                      ssl3KeyMaterial *keyMat)
+{
+    PK11SymKey *Z = NULL;
+    PK11SymKey *Zx = NULL;
+    SECStatus ret = SECFailure;
+    PRUint8 esniContentsBuf[256]; /* Just big enough. */
+    sslBuffer esniContents = SSL_BUFFER(esniContentsBuf);
+    PRUint8 hash[64];
+    const ssl3BulkCipherDef *cipherDef = ssl_GetBulkCipherDef(suite);
+    size_t keySize = cipherDef->key_size;
+    size_t ivSize = cipherDef->iv_size +
+                    cipherDef->explicit_nonce_size; /* This isn't always going to
+                                                     * work, but it does for
+                                                     * AES-GCM */
+    unsigned int hashSize = tls13_GetHashSizeForHash(suite->prf_hash);
+    SECStatus rv;
+
+    rv = tls13_HandleKeyShare(CONST_CAST(sslSocket, ss), entry, keyPair,
+                              suite->prf_hash, &Z);
+    if (rv != SECSuccess) {
+        goto loser;
+    }
+    rv = tls13_HkdfExtract(NULL, Z, suite->prf_hash, &Zx);
+    if (rv != SECSuccess) {
+        goto loser;
+    }
+
+    /* Encode ESNIContents. */
+    rv = sslBuffer_AppendVariable(&esniContents,
+                                  esniKeysHash, hashSize, 2);
+    if (rv != SECSuccess) {
+        goto loser;
+    }
+    rv = sslBuffer_Append(&esniContents, keyShareBuf, keyShareBufLen);
+    if (rv != SECSuccess) {
+        goto loser;
+    }
+    rv = sslBuffer_Append(&esniContents, clientRandom, SSL3_RANDOM_LENGTH);
+    if (rv != SECSuccess) {
+        goto loser;
+    }
+
+    PORT_Assert(hashSize <= sizeof(hash));
+    rv = PK11_HashBuf(ssl3_HashTypeToOID(suite->prf_hash),
+                      hash,
+                      SSL_BUFFER_BASE(&esniContents),
+                      SSL_BUFFER_LEN(&esniContents));
+    ;
+    if (rv != SECSuccess) {
+        goto loser;
+    }
+
+    rv = tls13_HkdfExpandLabel(Zx, suite->prf_hash,
+                               hash, hashSize,
+                               kHkdfPurposeEsniKey, strlen(kHkdfPurposeEsniKey),
+                               ssl3_Alg2Mech(cipherDef->calg),
+                               keySize,
+                               &keyMat->key);
+    if (rv != SECSuccess) {
+        goto loser;
+    }
+    rv = tls13_HkdfExpandLabelRaw(Zx, suite->prf_hash,
+                                  hash, hashSize,
+                                  kHkdfPurposeEsniIv, strlen(kHkdfPurposeEsniIv),
+                                  keyMat->iv, ivSize);
+    if (rv != SECSuccess) {
+        goto loser;
+    }
+
+    ret = SECSuccess;
+
+loser:
+    PK11_FreeSymKey(Z);
+    PK11_FreeSymKey(Zx);
+    return ret;
+}
+
+/* Set up ESNI. This generates a private key as a side effect. */
+SECStatus
+tls13_ClientSetupESNI(sslSocket *ss)
+{
+    ssl3CipherSuite suite;
+    sslEphemeralKeyPair *keyPair;
+    size_t i;
+    PRCList *cur;
+    SECStatus rv;
+    TLS13KeyShareEntry *share;
+    const sslNamedGroupDef *group = NULL;
+    PRTime now = PR_Now() / PR_USEC_PER_SEC;
+
+    if (!ss->esniKeys) {
+        return SECSuccess;
+    }
+
+    if ((ss->esniKeys->notBefore > now) || (ss->esniKeys->notAfter < now)) {
+        return SECSuccess;
+    }
+
+    /* If we're not sending SNI, don't send ESNI. */
+    if (!ssl_ShouldSendSNIExtension(ss, ss->url)) {
+        return SECSuccess;
+    }
+
+    /* Pick the group. */
+    for (i = 0; i < SSL_NAMED_GROUP_COUNT; ++i) {
+        for (cur = PR_NEXT_LINK(&ss->esniKeys->keyShares);
+             cur != &ss->esniKeys->keyShares;
+             cur = PR_NEXT_LINK(cur)) {
+            if (!ss->namedGroupPreferences[i]) {
+                continue;
+            }
+            share = (TLS13KeyShareEntry *)cur;
+            if (share->group->name == ss->namedGroupPreferences[i]->name) {
+                group = ss->namedGroupPreferences[i];
+                break;
+            }
+        }
+    }
+
+    if (!group) {
+        /* No compatible group. */
+        return SECSuccess;
+    }
+
+    rv = ssl3_NegotiateCipherSuiteInner(ss, &ss->esniKeys->suites,
+                                        SSL_LIBRARY_VERSION_TLS_1_3, &suite);
+    if (rv != SECSuccess) {
+        return SECSuccess;
+    }
+
+    rv = tls13_CreateKeyShare(ss, group, &keyPair);
+    if (rv != SECSuccess) {
+        return SECFailure;
+    }
+
+    ss->xtnData.esniPrivateKey = keyPair;
+    ss->xtnData.esniSuite = suite;
+    ss->xtnData.peerEsniShare = share;
+
+    return SECSuccess;
+}
+
+/*
+ * struct {
+ *     CipherSuite suite;
+ *     KeyShareEntry key_share;
+ *     opaque record_digest<0..2^16-1>;
+ *     opaque encrypted_sni<0..2^16-1>;
+ * } ClientEncryptedSNI;
+ *
+ * struct {
+ *     ServerNameList sni;
+ *     opaque zeros[ESNIKeys.padded_length - length(sni)];
+ * } PaddedServerNameList;
+ *
+ * struct {
+ *     uint8 nonce[16];
+ *     PaddedServerNameList realSNI;
+ * } ClientESNIInner;
+ */
+SECStatus
+tls13_FormatEsniAADInput(sslBuffer *aadInput,
+                         PRUint8 *keyShare, unsigned int keyShareLen)
+{
+    SECStatus rv;
+
+    /* 8 bytes of 0 for the sequence number. */
+    rv = sslBuffer_AppendNumber(aadInput, 0, 8);
+    if (rv != SECSuccess) {
+        return SECFailure;
+    }
+
+    /* Key share. */
+    PORT_Assert(keyShareLen > 0);
+    rv = sslBuffer_Append(aadInput, keyShare, keyShareLen);
+    if (rv != SECSuccess) {
+        return SECFailure;
+    }
+
+    return SECSuccess;
+}
+
+static SECStatus
+tls13_ServerGetEsniAEAD(const sslSocket *ss, PRUint64 suite,
+                        const ssl3CipherSuiteDef **suiteDefp,
+                        SSLAEADCipher *aeadp)
+{
+    SECStatus rv;
+    const ssl3CipherSuiteDef *suiteDef;
+    SSLAEADCipher aead;
+
+    /* Check against the suite list for ESNI */
+    PRBool csMatch = PR_FALSE;
+    sslReader csrdr = SSL_READER(ss->esniKeys->suites.data,
+                                 ss->esniKeys->suites.len);
+    while (SSL_READER_REMAINING(&csrdr)) {
+        PRUint64 asuite;
+
+        rv = sslRead_ReadNumber(&csrdr, 2, &asuite);
+        if (rv != SECSuccess) {
+            return SECFailure;
+        }
+        if (asuite == suite) {
+            csMatch = PR_TRUE;
+            break;
+        }
+    }
+    if (!csMatch) {
+        return SECFailure;
+    }
+
+    suiteDef = ssl_LookupCipherSuiteDef(suite);
+    PORT_Assert(suiteDef);
+    if (!suiteDef) {
+        return SECFailure;
+    }
+    aead = tls13_GetAead(ssl_GetBulkCipherDef(suiteDef));
+    if (!aead) {
+        return SECFailure;
+    }
+
+    *suiteDefp = suiteDef;
+    *aeadp = aead;
+    return SECSuccess;
+}
+
+SECStatus
+tls13_ServerDecryptEsniXtn(const sslSocket *ss, PRUint8 *in, unsigned int inLen,
+                           PRUint8 *out, int *outLen, int maxLen)
+{
+    sslReader rdr = SSL_READER(in, inLen);
+    PRUint64 suite;
+    const ssl3CipherSuiteDef *suiteDef;
+    SSLAEADCipher aead = NULL;
+    TLSExtension *keyShareExtension;
+    TLS13KeyShareEntry *entry = NULL;
+    ssl3KeyMaterial keyMat = { NULL };
+
+    sslBuffer aadInput = SSL_BUFFER_EMPTY;
+    const PRUint8 *keyShareBuf;
+    sslReadBuffer buf;
+    unsigned int keyShareBufLen;
+    PRUint8 hash[64];
+    SECStatus rv;
+
+    /* Read the cipher suite. */
+    rv = sslRead_ReadNumber(&rdr, 2, &suite);
+    if (rv != SECSuccess) {
+        goto loser;
+    }
+
+    /* Find the AEAD */
+    rv = tls13_ServerGetEsniAEAD(ss, suite, &suiteDef, &aead);
+    if (rv != SECSuccess) {
+        goto loser;
+    }
+
+    /* Note where the KeyShare starts. */
+    keyShareBuf = SSL_READER_CURRENT(&rdr);
+    rv = tls13_DecodeKeyShareEntry(&rdr, &entry);
+    if (rv != SECSuccess) {
+        goto loser;
+    }
+    keyShareBufLen = SSL_READER_CURRENT(&rdr) - keyShareBuf;
+    if (!entry || entry->group->name != ss->esniKeys->privKey->group->name) {
+        goto loser;
+    }
+
+    /* The hash of the ESNIKeys structure. */
+    rv = sslRead_ReadVariable(&rdr, 2, &buf);
+    if (rv != SECSuccess) {
+        goto loser;
+    }
+
+    /* Check that the hash matches. */
+    unsigned int hashLen = tls13_GetHashSizeForHash(suiteDef->prf_hash);
+    PORT_Assert(hashLen <= sizeof(hash));
+    rv = PK11_HashBuf(ssl3_HashTypeToOID(suiteDef->prf_hash),
+                      hash,
+                      ss->esniKeys->data.data, ss->esniKeys->data.len);
+    if (rv != SECSuccess) {
+        goto loser;
+    }
+
+    if (buf.len != hashLen) {
+        /* This is malformed. */
+        goto loser;
+    }
+    if (0 != NSS_SecureMemcmp(hash, buf.buf, hashLen)) {
+        goto loser;
+    }
+
+    rv = tls13_ComputeESNIKeys(ss, entry,
+                               ss->esniKeys->privKey->keys,
+                               suiteDef,
+                               hash, keyShareBuf, keyShareBufLen,
+                               ((sslSocket *)ss)->ssl3.hs.client_random,
+                               &keyMat);
+    if (rv != SECSuccess) {
+        goto loser;
+    }
+
+    /* Read the ciphertext. */
+    rv = sslRead_ReadVariable(&rdr, 2, &buf);
+    if (rv != SECSuccess) {
+        goto loser;
+    }
+
+    /* Check that this is empty. */
+    if (SSL_READER_REMAINING(&rdr) > 0) {
+        goto loser;
+    }
+
+    /* Find the key share extension. */
+    keyShareExtension = ssl3_FindExtension(CONST_CAST(sslSocket, ss),
+                                           ssl_tls13_key_share_xtn);
+    if (!keyShareExtension) {
+        goto loser;
+    }
+    rv = tls13_FormatEsniAADInput(&aadInput,
+                                  keyShareExtension->data.data,
+                                  keyShareExtension->data.len);
+    if (rv != SECSuccess) {
+        goto loser;
+    }
+
+    rv = aead(&keyMat, PR_TRUE /* Decrypt */,
+              out, outLen, maxLen,
+              buf.buf, buf.len,
+              SSL_BUFFER_BASE(&aadInput),
+              SSL_BUFFER_LEN(&aadInput));
+    sslBuffer_Clear(&aadInput);
+    if (rv != SECSuccess) {
+        goto loser;
+    }
+
+    ssl_DestroyKeyMaterial(&keyMat);
+    tls13_DestroyKeyShareEntry(entry);
+    return SECSuccess;
+
+loser:
+    FATAL_ERROR(CONST_CAST(sslSocket, ss), SSL_ERROR_RX_MALFORMED_ESNI_EXTENSION, illegal_parameter);
+    ssl_DestroyKeyMaterial(&keyMat); /* Safe because zeroed. */
+    if (entry) {
+        tls13_DestroyKeyShareEntry(entry);
+    }
+    return SECFailure;
+}
new file mode 100644
--- /dev/null
+++ b/security/nss/lib/ssl/tls13esni.h
@@ -0,0 +1,51 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is PRIVATE to SSL.
+ *
+ * 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 __tls13esni_h_
+#define __tls13esni_h_
+
+struct sslEsniKeysStr {
+    SECItem data; /* The encoded record. */
+    sslEphemeralKeyPair *privKey;
+    const char *dummySni;
+    PRCList keyShares; /* List of TLS13KeyShareEntry */
+    SECItem suites;
+    PRUint16 paddedLength;
+    PRUint64 notBefore;
+    PRUint64 notAfter;
+};
+
+SECStatus SSLExp_SetESNIKeyPair(PRFileDesc *fd,
+                                SECKEYPrivateKey *privKey,
+                                const PRUint8 *record, unsigned int recordLen);
+
+SECStatus SSLExp_EnableESNI(PRFileDesc *fd, const PRUint8 *esniKeys,
+                            unsigned int esniKeysLen, const char *dummySNI);
+SECStatus SSLExp_EncodeESNIKeys(PRUint16 *cipherSuites, unsigned int cipherSuiteCount,
+                                SSLNamedGroup group, SECKEYPublicKey *pubKey,
+                                PRUint16 pad, PRUint64 notBefore, PRUint64 notAfter,
+                                PRUint8 *out, unsigned int *outlen, unsigned int maxlen);
+sslEsniKeys *tls13_CopyESNIKeys(sslEsniKeys *okeys);
+void tls13_DestroyESNIKeys(sslEsniKeys *keys);
+SECStatus tls13_ClientSetupESNI(sslSocket *ss);
+SECStatus tls13_ComputeESNIKeys(const sslSocket *ss,
+                                TLS13KeyShareEntry *entry,
+                                sslKeyPair *keyPair,
+                                const ssl3CipherSuiteDef *suite,
+                                const PRUint8 *esniKeysHash,
+                                const PRUint8 *keyShareBuf,
+                                unsigned int keyShareBufLen,
+                                const PRUint8 *clientRandom,
+                                ssl3KeyMaterial *keyMat);
+SECStatus tls13_FormatEsniAADInput(sslBuffer *aadInput,
+                                   PRUint8 *keyShare, unsigned int keyShareLen);
+
+SECStatus tls13_ServerDecryptEsniXtn(const sslSocket *ss, PRUint8 *in, unsigned int inLen,
+                                     PRUint8 *out, int *outLen, int maxLen);
+
+#endif
--- a/security/nss/lib/ssl/tls13exthandle.c
+++ b/security/nss/lib/ssl/tls13exthandle.c
@@ -7,16 +7,17 @@
 #include "nssrenam.h"
 #include "nss.h"
 #include "ssl.h"
 #include "sslproto.h"
 #include "sslimpl.h"
 #include "pk11pub.h"
 #include "ssl3ext.h"
 #include "ssl3exthandle.h"
+#include "tls13esni.h"
 #include "tls13exthandle.h"
 
 SECStatus
 tls13_ServerSendStatusRequestXtn(const sslSocket *ss, TLSExtensionData *xtnData,
                                  sslBuffer *buf, PRBool *added)
 {
     const sslServerCert *serverCert = ss->sec.serverCert;
     const SECItem *item;
@@ -66,39 +67,39 @@ tls13_ServerSendStatusRequestXtn(const s
  * DH is Section 6.3.2.3.1.
  *
  *     opaque dh_Y<1..2^16-1>;
  *
  * ECDH is Section 6.3.2.3.2.
  *
  *     opaque point <1..2^8-1>;
  */
-static PRUint32
+PRUint32
 tls13_SizeOfKeyShareEntry(const SECKEYPublicKey *pubKey)
 {
     /* Size = NamedGroup(2) + length(2) + opaque<?> share */
     switch (pubKey->keyType) {
         case ecKey:
             return 2 + 2 + pubKey->u.ec.publicValue.len;
         case dhKey:
             return 2 + 2 + pubKey->u.dh.prime.len;
         default:
             PORT_Assert(0);
     }
     return 0;
 }
 
-static SECStatus
-tls13_EncodeKeyShareEntry(sslBuffer *buf, const sslEphemeralKeyPair *keyPair)
+SECStatus
+tls13_EncodeKeyShareEntry(sslBuffer *buf, SSLNamedGroup group,
+                          SECKEYPublicKey *pubKey)
 {
     SECStatus rv;
-    SECKEYPublicKey *pubKey = keyPair->keys->pubKey;
     unsigned int size = tls13_SizeOfKeyShareEntry(pubKey);
 
-    rv = sslBuffer_AppendNumber(buf, keyPair->group->name, 2);
+    rv = sslBuffer_AppendNumber(buf, group, 2);
     if (rv != SECSuccess)
         return rv;
     rv = sslBuffer_AppendNumber(buf, size - 4, 2);
     if (rv != SECSuccess)
         return rv;
 
     switch (pubKey->keyType) {
         case ecKey:
@@ -118,124 +119,145 @@ tls13_EncodeKeyShareEntry(sslBuffer *buf
 }
 
 SECStatus
 tls13_ClientSendKeyShareXtn(const sslSocket *ss, TLSExtensionData *xtnData,
                             sslBuffer *buf, PRBool *added)
 {
     SECStatus rv;
     PRCList *cursor;
+    unsigned int extStart;
     unsigned int lengthOffset;
 
     if (ss->vrange.max < SSL_LIBRARY_VERSION_TLS_1_3) {
         return SECSuccess;
     }
 
     /* Optimistically try to send an ECDHE key using the
      * preexisting key (in future will be keys) */
     SSL_TRC(3, ("%d: TLS13[%d]: send client key share xtn",
                 SSL_GETPID(), ss->fd));
 
+    extStart = SSL_BUFFER_LEN(buf);
+
     /* Save the offset to the length. */
     rv = sslBuffer_Skip(buf, 2, &lengthOffset);
     if (rv != SECSuccess) {
         return SECFailure;
     }
 
     for (cursor = PR_NEXT_LINK(&ss->ephemeralKeyPairs);
          cursor != &ss->ephemeralKeyPairs;
          cursor = PR_NEXT_LINK(cursor)) {
         sslEphemeralKeyPair *keyPair = (sslEphemeralKeyPair *)cursor;
-        rv = tls13_EncodeKeyShareEntry(buf, keyPair);
+        rv = tls13_EncodeKeyShareEntry(buf,
+                                       keyPair->group->name,
+                                       keyPair->keys->pubKey);
         if (rv != SECSuccess) {
             return SECFailure;
         }
     }
     rv = sslBuffer_InsertLength(buf, lengthOffset, 2);
     if (rv != SECSuccess) {
         return SECFailure;
     }
 
+    rv = SECITEM_MakeItem(NULL, &xtnData->keyShareExtension,
+                          SSL_BUFFER_BASE(buf) + extStart,
+                          SSL_BUFFER_LEN(buf) - extStart);
+    if (rv != SECSuccess) {
+        return SECFailure;
+    }
+
     *added = PR_TRUE;
     return SECSuccess;
 }
 
-static SECStatus
-tls13_HandleKeyShareEntry(const sslSocket *ss, TLSExtensionData *xtnData, SECItem *data)
+SECStatus
+tls13_DecodeKeyShareEntry(sslReader *rdr, TLS13KeyShareEntry **ksp)
 {
     SECStatus rv;
-    PRUint32 group;
+    PRUint64 group;
     const sslNamedGroupDef *groupDef;
     TLS13KeyShareEntry *ks = NULL;
-    SECItem share = { siBuffer, NULL, 0 };
+    sslReadBuffer share;
 
-    rv = ssl3_ExtConsumeHandshakeNumber(ss, &group, 2, &data->data, &data->len);
+    rv = sslRead_ReadNumber(rdr, 2, &group);
     if (rv != SECSuccess) {
-        PORT_SetError(SSL_ERROR_RX_MALFORMED_KEY_SHARE);
         goto loser;
     }
     groupDef = ssl_LookupNamedGroup(group);
-    rv = ssl3_ExtConsumeHandshakeVariable(ss, &share, 2, &data->data,
-                                          &data->len);
+    rv = sslRead_ReadVariable(rdr, 2, &share);
     if (rv != SECSuccess) {
         goto loser;
     }
+
+    /* This has to happen here because we want to consume
+     * the entire entry even if the group is unknown
+     * or disabled. */
     /* If the group is disabled, continue. */
     if (!groupDef) {
         return SECSuccess;
     }
 
     ks = PORT_ZNew(TLS13KeyShareEntry);
-    if (!ks)
+    if (!ks) {
         goto loser;
+    }
     ks->group = groupDef;
 
-    rv = SECITEM_CopyItem(NULL, &ks->key_exchange, &share);
-    if (rv != SECSuccess)
+    rv = SECITEM_MakeItem(NULL, &ks->key_exchange,
+                          share.buf, share.len);
+    if (rv != SECSuccess) {
         goto loser;
+    }
 
-    PR_APPEND_LINK(&ks->link, &xtnData->remoteKeyShares);
+    *ksp = ks;
     return SECSuccess;
 
 loser:
-    if (ks)
-        tls13_DestroyKeyShareEntry(ks);
+    tls13_DestroyKeyShareEntry(ks);
+
     return SECFailure;
 }
 /* Handle an incoming KeyShare extension at the client and copy to
  * |xtnData->remoteKeyShares| for future use. The key
  * share is processed in tls13_HandleServerKeyShare(). */
 SECStatus
 tls13_ClientHandleKeyShareXtn(const sslSocket *ss, TLSExtensionData *xtnData,
                               SECItem *data)
 {
     SECStatus rv;
     PORT_Assert(PR_CLIST_IS_EMPTY(&xtnData->remoteKeyShares));
+    TLS13KeyShareEntry *ks = NULL;
 
     PORT_Assert(!ss->sec.isServer);
 
     /* The server must not send this extension when negotiating < TLS 1.3. */
     if (ss->version < SSL_LIBRARY_VERSION_TLS_1_3) {
         PORT_SetError(SSL_ERROR_EXTENSION_DISALLOWED_FOR_VERSION);
         return SECFailure;
     }
 
     SSL_TRC(3, ("%d: SSL3[%d]: handle key_share extension",
                 SSL_GETPID(), ss->fd));
 
-    rv = tls13_HandleKeyShareEntry(ss, xtnData, data);
-    if (rv != SECSuccess) {
+    sslReader rdr = SSL_READER(data->data, data->len);
+    rv = tls13_DecodeKeyShareEntry(&rdr, &ks);
+    if ((rv != SECSuccess) || !ks) {
+        ssl3_ExtSendAlert(ss, alert_fatal, illegal_parameter);
         PORT_SetError(SSL_ERROR_RX_MALFORMED_KEY_SHARE);
         return SECFailure;
     }
 
-    if (data->len) {
+    if (SSL_READER_REMAINING(&rdr)) {
         PORT_SetError(SSL_ERROR_RX_MALFORMED_KEY_SHARE);
         return SECFailure;
     }
+    PR_APPEND_LINK(&ks->link, &xtnData->remoteKeyShares);
 
     return SECSuccess;
 }
 
 SECStatus
 tls13_ClientHandleKeyShareXtnHrr(const sslSocket *ss, TLSExtensionData *xtnData,
                                  SECItem *data)
 {
@@ -268,17 +290,17 @@ tls13_ClientHandleKeyShareXtnHrr(const s
         PORT_SetError(SSL_ERROR_RX_MALFORMED_HELLO_RETRY_REQUEST);
         return SECFailure;
     }
 
     /* Now delete all the key shares per [draft-ietf-tls-tls13 S 4.1.2] */
     ssl_FreeEphemeralKeyPairs(CONST_CAST(sslSocket, ss));
 
     /* And replace with our new share. */
-    rv = tls13_CreateKeyShare(CONST_CAST(sslSocket, ss), group);
+    rv = tls13_AddKeyShare(CONST_CAST(sslSocket, ss), group);
     if (rv != SECSuccess) {
         ssl3_ExtSendAlert(ss, alert_fatal, internal_error);
         PORT_SetError(SEC_ERROR_KEYGEN_FAIL);
         return SECFailure;
     }
 
     return SECSuccess;
 }
@@ -310,22 +332,34 @@ tls13_ServerHandleKeyShareXtn(const sslS
     if (rv != SECSuccess)
         goto loser;
     if (length != data->len) {
         /* Check for consistency */
         PORT_SetError(SSL_ERROR_RX_MALFORMED_KEY_SHARE);
         goto loser;
     }
 
-    while (data->len) {
-        rv = tls13_HandleKeyShareEntry(ss, xtnData, data);
-        if (rv != SECSuccess)
+    sslReader rdr = SSL_READER(data->data, data->len);
+    while (SSL_READER_REMAINING(&rdr)) {
+        TLS13KeyShareEntry *ks = NULL;
+        rv = tls13_DecodeKeyShareEntry(&rdr, &ks);
+        if (rv != SECSuccess) {
+            PORT_SetError(SSL_ERROR_RX_MALFORMED_KEY_SHARE);
             goto loser;
+        }
+        if (ks) {
+            /* |ks| == NULL if this is an unknown group. */
+            PR_APPEND_LINK(&ks->link, &xtnData->remoteKeyShares);
+        }
     }
 
+    /* Keep track of negotiated extensions. */
+    xtnData->negotiated[xtnData->numNegotiated++] =
+        ssl_tls13_key_share_xtn;
+
     return SECSuccess;