Merge inbound to central, a=merge
authorWes Kocher <wkocher@mozilla.com>
Wed, 17 May 2017 14:48:30 -0700
changeset 579879 2c783a7b6d05b4b2b417bc5f21b7e40cbf3df077
parent 579844 b133ec74e3d0813c0951603209fa283ef0efd8b2 (current diff)
parent 579766 b2eb05d5fad2fea928f47d3e6a329024c0aaf70e (diff)
child 579880 98457e4aee85ab6302c9b0e3a72476f0c272b3f5
child 579881 5d19c7b9d11668b6960fd2d718136a5bef9338a5
child 579893 db4859b60cc07efc7d27cf1fda3ef3d4ed445db7
child 579915 92b9e213e8c90bbf0fda11a4daef928db4065e98
child 579925 c212d4ce2a65075823808162c3542309226994ab
child 579929 73fe22928891ef226e0cf4d31d4901c0da61d1c1
child 579938 5cb8639712e73ee01995dc321b0d8852b153090f
child 579939 043add1312d992baf55d933dc75fed75d070377c
child 579956 32d08616332fa232532ad7ce0b7a203922520010
child 579959 3c93f00bfc7fbee85c60aa0f73ff26b73e714043
child 579964 32a1d6ad19dc6e94639da452117982c9715971e8
child 579992 35a56a33d37df4d3f4f955d4dfdee5aea9b1a852
child 580063 651357d7c04d4ba4d812d3f2831a6cb770e010af
child 580068 7cc6128238179733b9a22d5c34cd6658bbf82839
child 580173 abe5c3dde2f55dca53beddc43868e16fabe7927e
child 580222 37fd0ac2b77d822e156d75968f70e308d6d8a977
child 580240 86c242be1f071da12e32095ba8af7dd074c4b453
child 580241 4c1e54663acfed3d98c0a16bf3c21b0c4a2d7636
child 580248 76b3b372f7488d765ab8de5df80f20d3a442eca7
child 580249 5d1aadea893e863e0d3b7d780bd3124834cd290b
child 580312 a0a088c026f88a3e0b42e3392e2bc0832db0cd33
child 580464 c89a5f4e565bad77eb6eb4b8d8e928fb3eae5fad
child 580472 64539990c881f963e2e19e2def38b58739b08888
child 580475 56bea7b451209e8245518ced621e58fb5fe65116
child 580484 b617c941761529da30ebe87e1e31f99462d7fcb6
child 580485 87e4776390163984171ab334386366331d455b9f
child 580487 f8456b7599158d4ff261c4cc5ac12b52b3caa8d3
child 580513 c81795a4bee8aae4b25daaf8b2ecfc901fab2b6e
child 580561 55e1d30b2342b4592dea1cf5f115e9d5e6f2d0dc
child 580597 53ed01a3a138e1ed20567244f2ba559f2a36bb3e
child 580622 7579624e391a4044b51befd037f3afb0c8c29a3d
child 580824 96d76b0648c47236fc9b894e822a3de56b6b6a9e
child 580841 bf5ad07d4595c9752430b30f740a7a8557a185f8
child 580886 50f51c3cee3fe7d3ed4b0dba1402feb0e7329c08
child 580991 811d201951e745a28e422ec134b192dd9c2b0a03
child 581012 238a6fb0792d7ee8bbba018d70ba630d2141e31a
child 581895 08596177deb40050e335c809e5b436d6c68d967d
child 581914 2b806bdc7c7693e6465a890d62b52310fe1a56e2
child 581952 9c1d31ef3dacb927823f9b2c0702115756c706f4
child 581966 46c0a643304095a32fca82f816bcff768aa870c3
child 581973 87983f88047f866edcc51e6c9d5c8e1e761f04ed
child 581975 bde354ae7ecd3c1125273ef05e8fe0030945a110
child 581986 b50dccd87a495166e7ac4504d5f4ebc96421c291
child 582451 3f6ea0c998300c35f1b2ea60b83042b569b9fb2c
child 582453 01054b75d71a1da5ac129192877c4b4adbedd96c
child 582463 601272da97f702039b59c0d6b4a3e901800f0efa
child 582693 0dd4d8454d449f1356ca1d0b436aa4fc2693c2e0
child 582698 5b1c93591f606e86c8f981d110991d3976f2baae
child 582732 ac1c0cd512e9203f5c2f5d91623956b6015b1cce
child 583154 9e3bab090efc9541ee0ced052caf60681ddcbb68
child 583165 7faecfe93d32c8240317cbd5692ace2552c6e0d0
child 583212 33dc9752d747f0f4d2cc451dd889ad6c58ae0b36
child 584359 9bf9be292a07f788f0ee844f59fc10954177f163
child 588164 31af6c5e252869e2fd2a6d1168322c88196d8b82
child 588826 aca68dfd354fcb4cfea224dfa80962626af8c930
child 588883 a1e6eb8e43a5944eedb37cac3f664955e0c5559d
child 590234 422065675cc4780bda8cabaa363185a644607e4c
child 590864 82e7d2e1e5724904a4c8e3834248b3ef619f7826
child 592437 807b6efa3308dffe1fab90547a8e4393020d5324
push id59395
push userbmo:mrbkap@mozilla.com
push dateWed, 17 May 2017 23:42:06 +0000
reviewersmerge
milestone55.0a1
Merge inbound to central, a=merge UPGRADE_NSS_RELEASE MozReview-Commit-ID: 9BuuGYyJ3RJ
testing/web-platform/meta/XMLHttpRequest/getallresponseheaders.htm.ini
testing/web-platform/meta/XMLHttpRequest/getresponseheader-chunked-trailer.htm.ini
--- a/browser/base/jar.mn
+++ b/browser/base/jar.mn
@@ -152,17 +152,17 @@ browser.jar:
         content/browser/webext-panels.js              (content/webext-panels.js)
 *       content/browser/webext-panels.xul             (content/webext-panels.xul)
 *       content/browser/baseMenuOverlay.xul           (content/baseMenuOverlay.xul)
         content/browser/nsContextMenu.js              (content/nsContextMenu.js)
 # XXX: We should exclude this one as well (bug 71895)
 *       content/browser/hiddenWindow.xul              (content/hiddenWindow.xul)
 #ifdef XP_MACOSX
 *       content/browser/macBrowserOverlay.xul         (content/macBrowserOverlay.xul)
-*       content/browser/softwareUpdateOverlay.xul  (content/softwareUpdateOverlay.xul)
+*       content/browser/softwareUpdateOverlay.xul     (content/softwareUpdateOverlay.xul)
 #endif
 *       content/browser/viewSourceOverlay.xul         (content/viewSourceOverlay.xul)
 #ifndef XP_MACOSX
 *       content/browser/webrtcIndicator.xul           (content/webrtcIndicator.xul)
         content/browser/webrtcIndicator.js            (content/webrtcIndicator.js)
 #endif
 # the following files are browser-specific overrides
 *       content/browser/license.html                  (/toolkit/content/license.html)
--- a/browser/extensions/webcompat-reporter/test/browser/browser.ini
+++ b/browser/extensions/webcompat-reporter/test/browser/browser.ini
@@ -2,8 +2,9 @@
 support-files =
   head.js
   test.html
   webcompat.html
 
 [browser_disabled_cleanup.js]
 [browser_button_state.js]
 [browser_report_site_issue.js]
+skip-if = !e10s && (os == "linux" && (debug || asan))
--- a/browser/themes/linux/browser.css
+++ b/browser/themes/linux/browser.css
@@ -263,17 +263,17 @@ menuitem.bookmark-item {
 #close-button {
   list-style-image: url("chrome://global/skin/icons/Close.gif");
 }
 
 /* Location bar */
 #main-window {
   --urlbar-border-color: ThreeDShadow;
   --urlbar-border-color-hover: var(--urlbar-border-color);
-  --urlbar-background-color: moz-field;
+  --urlbar-background-color: -moz-field;
 }
 
 #navigator-toolbox:-moz-lwtheme {
   --urlbar-border-color: rgba(0,0,0,.3);
 }
 
 %ifdef MOZ_PHOTON_THEME
 
--- a/browser/themes/shared/location-search-bar.inc.css
+++ b/browser/themes/shared/location-search-bar.inc.css
@@ -9,19 +9,27 @@
   background-color: var(--urlbar-background-color);
   border: 1px solid var(--urlbar-border-color);
   border-radius: var(--toolbarbutton-border-radius);
   box-shadow: 0 1px 4px hsla(0, 0%, 0%, .05);
   padding: 0;
   margin: 0 2px;
 }
 
-#urlbar:-moz-lwtheme:not(:hover):not([focused="true"]),
-.searchbar-textbox:-moz-lwtheme:not(:hover):not([focused="true"]) {
-  background-color: hsla(0, 100%, 100%, .8);
+#urlbar:-moz-lwtheme,
+.searchbar-textbox:-moz-lwtheme {
+  background-color: hsla(0,0%,100%,.8);
+  color: black;
+}
+
+#urlbar:-moz-lwtheme:hover,
+#urlbar:-moz-lwtheme[focused="true"],
+.searchbar-textbox:-moz-lwtheme:hover,
+.searchbar-textbox:-moz-lwtheme[focused="true"] {
+  background-color: white;
 }
 
 #urlbar:hover,
 .searchbar-textbox:hover {
   border: 1px solid var(--urlbar-border-color-hover);
   box-shadow: 0 1px 6px hsla(0, 0%, 0%, .1), 0 0 1px 0 rgba(0, 0, 0, .1);
 }
 
--- a/build/moz.configure/old.configure
+++ b/build/moz.configure/old.configure
@@ -99,16 +99,18 @@ def prepare_configure(old_configure, moz
             refresh = False
 
     if refresh:
         log.info('Refreshing %s with %s', old_configure, autoconf)
         script = subprocess.check_output([
             shell, autoconf,
             '--localdir=%s' % os.path.dirname(old_configure),
             old_configure + '.in'])
+        if not script:
+            die('Generated old-configure is empty! Check that your autoconf 2.13 program works!')
 
         # Make old-configure append to config.log, where we put our own log.
         # This could be done with a m4 macro, but it's way easier this way
         script = script.replace('>./config.log', '>>./config.log')
 
         with open(old_configure, 'wb') as fh:
             fh.write(script)
 
--- a/config/check_spidermonkey_style.py
+++ b/config/check_spidermonkey_style.py
@@ -59,16 +59,18 @@ ignored_js_src_dirs = [
 # We ignore #includes of these files, because they don't follow the usual rules.
 included_inclnames_to_ignore = set([
     'ffi.h',                    # generated in ctypes/libffi/
     'devtools/sharkctl.h',      # we ignore devtools/ in general
     'devtools/Instruments.h',   # we ignore devtools/ in general
     'double-conversion.h',      # strange MFBT case
     'javascript-trace.h',       # generated in $OBJDIR if HAVE_DTRACE is defined
     'frontend/ReservedWordsGenerated.h', # generated in $OBJDIR
+    'gc/StatsPhasesGenerated.h',         # generated in $OBJDIR
+    'gc/StatsPhasesGenerated.cpp',       # generated in $OBJDIR
     'jscustomallocator.h',      # provided by embedders;  allowed to be missing
     'js-config.h',              # generated in $OBJDIR
     'fdlibm.h',                 # fdlibm
     'pratom.h',                 # NSPR
     'prcvar.h',                 # NSPR
     'prerror.h',                # NSPR
     'prinit.h',                 # NSPR
     'prio.h',                   # NSPR
@@ -99,16 +101,18 @@ included_inclnames_to_ignore = set([
     'vtune/VTuneWrapper.h'      # VTune
 ])
 
 # These files have additional constraints on where they are #included, so we
 # ignore #includes of them when checking #include ordering.
 oddly_ordered_inclnames = set([
     'ctypes/typedefs.h',        # Included multiple times in the body of ctypes/CTypes.h
     'frontend/ReservedWordsGenerated.h', # Included in the body of frontend/TokenStream.h
+    'gc/StatsPhasesGenerated.h',         # Included in the body of gc/Statistics.h
+    'gc/StatsPhasesGenerated.cpp',       # Included in the body of gc/Statistics.cpp
     'jswin.h',                  # Must be #included before <psapi.h>
     'machine/endian.h',         # Must be included after <sys/types.h> on BSD
     'winbase.h',                # Must precede other system headers(?)
     'windef.h'                  # Must precede other system headers(?)
 ])
 
 # The files in tests/style/ contain code that fails this checking in various
 # ways.  Here is the output we expect.  If the actual output differs from
--- a/devtools/client/jsonview/test/browser.ini
+++ b/devtools/client/jsonview/test/browser.ini
@@ -26,10 +26,12 @@ subsuite = clipboard
 skip-if = (os == 'linux' && bits == 32 && debug) # bug 1328915, disable linux32 debug devtools for timeouts
 [browser_jsonview_copy_rawdata.js]
 subsuite = clipboard
 skip-if = (os == 'linux' && bits == 32 && debug) # bug 1328915, disable linux32 debug devtools for timeouts
 [browser_jsonview_filter.js]
 [browser_jsonview_invalid_json.js]
 [browser_jsonview_valid_json.js]
 [browser_jsonview_save_json.js]
+support-files =
+  !/toolkit/content/tests/browser/common/mockTransfer.js
 [browser_jsonview_manifest.js]
 [browser_json_refresh.js]
--- a/devtools/client/jsonview/test/browser_jsonview_save_json.js
+++ b/devtools/client/jsonview/test/browser_jsonview_save_json.js
@@ -1,38 +1,143 @@
 /* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* vim: set ts=2 et sw=2 tw=80: */
+/* eslint-disable no-unused-vars, no-undef */
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
 "use strict";
 
-const TEST_JSON_URL = URL_ROOT + "valid_json.json";
+const JSON_FILE = "simple_json.json";
+const TEST_JSON_URL = URL_ROOT + JSON_FILE;
+const rawdataTab = ".rawdata > a";
+const saveButton = "button.save";
+const prettifyButton = "button.prettyprint";
 
 let { MockFilePicker } = SpecialPowers;
+MockFilePicker.init(window);
+MockFilePicker.returnValue = MockFilePicker.returnOK;
 
-MockFilePicker.init(window);
-MockFilePicker.returnValue = MockFilePicker.returnCancel;
+Cc["@mozilla.org/moz/jssubscript-loader;1"]
+  .getService(Ci.mozIJSSubScriptLoader)
+  .loadSubScript("chrome://mochitests/content/browser/toolkit/content/tests/browser/common/mockTransfer.js",
+                 this);
+
+function click(selector) {
+  return BrowserTestUtils.synthesizeMouseAtCenter(selector, {}, gBrowser.selectedBrowser);
+}
+
+function rightClick(selector) {
+  return BrowserTestUtils.synthesizeMouseAtCenter(
+    selector,
+    {type: "contextmenu", button: 2},
+    gBrowser.selectedBrowser
+  );
+}
 
+function awaitFileSave() {
+  return new Promise((resolve) => {
+    MockFilePicker.showCallback = (fp) => {
+      ok(true, "File picker was opened");
+      let fileName = fp.defaultString;
+      is(fileName, JSON_FILE, "File picker should provide the correct default filename.");
+      let destFile = destDir.clone();
+      destFile.append(fileName);
+      MockFilePicker.setFiles([destFile]);
+      MockFilePicker.showCallback = null;
+      mockTransferCallback = function (downloadSuccess) {
+        ok(downloadSuccess, "JSON should have been downloaded successfully");
+        ok(destFile.exists(), "The downloaded file should exist.");
+        resolve(destFile);
+      };
+    };
+  });
+}
+
+function getFileContents(file) {
+  return new Promise((resolve, reject) => {
+    NetUtil.asyncFetch(file, function (inputStream, status) {
+      if (Components.isSuccessCode(status)) {
+        info("Fetched downloaded contents.");
+        resolve(NetUtil.readInputStreamToString(inputStream, inputStream.available()));
+      } else {
+        reject();
+      }
+    });
+  });
+}
+
+function createTemporarySaveDirectory() {
+  let saveDir = Cc["@mozilla.org/file/directory_service;1"]
+                  .getService(Ci.nsIProperties)
+                  .get("TmpD", Ci.nsIFile);
+  saveDir.append("jsonview-testsavedir");
+  if (!saveDir.exists()) {
+    info("Creating temporary save directory.");
+    saveDir.create(Ci.nsIFile.DIRECTORY_TYPE, 0o755);
+  }
+  info("Temporary save directory: " + saveDir.path);
+  return saveDir;
+}
+
+let destDir = createTemporarySaveDirectory();
+mockTransferRegisterer.register();
+MockFilePicker.displayDirectory = destDir;
 registerCleanupFunction(function () {
+  mockTransferRegisterer.unregister();
   MockFilePicker.cleanup();
+  destDir.remove(true);
+  ok(!destDir.exists(), "Destination dir should be removed");
 });
 
 add_task(function* () {
   info("Test save JSON started");
-
   yield addJsonViewTab(TEST_JSON_URL);
 
-  let promise = new Promise((resolve) => {
-    MockFilePicker.showCallback = () => {
-      MockFilePicker.showCallback = null;
-      ok(true, "File picker was opened");
+  let promise, rawJSON, prettyJSON;
+  yield fetch(new Request(TEST_JSON_URL))
+    .then(response => response.text())
+    .then(function (data) {
+      info("Fetched JSON contents.");
+      rawJSON = data;
+      prettyJSON = JSON.stringify(JSON.parse(data), null, "  ");
+    });
+
+  // Attempt to save original JSON via "Save As" command
+  promise = awaitFileSave();
+  yield new Promise((resolve) => {
+    info("Register to handle popupshown.");
+    document.addEventListener("popupshown", function (event) {
+      info("Context menu opened.");
+      let savePageCommand = document.getElementById("context-savepage");
+      savePageCommand.doCommand();
+      info("SavePage command done.");
+      event.target.hidePopup();
+      info("Context menu hidden.");
       resolve();
-    };
+    }, {once: true});
+    rightClick("body");
+    info("Right clicked.");
+  });
+  yield promise.then(getFileContents).then(function (data) {
+    is(data, rawJSON, "Original JSON contents should have been saved.");
   });
 
-  let browser = gBrowser.selectedBrowser;
-  yield BrowserTestUtils.synthesizeMouseAtCenter(
-    ".jsonPanelBox button.save",
-    {}, browser);
+  // Attempt to save original JSON via "Save" button
+  promise = awaitFileSave();
+  yield click(saveButton);
+  info("Clicked Save button.");
+  yield promise.then(getFileContents).then(function (data) {
+    is(data, rawJSON, "Original JSON contents should have been saved.");
+  });
 
-  yield promise;
+  // Attempt to save prettified JSON via "Save" button
+  yield click(rawdataTab);
+  info("Switched to Raw Data tab.");
+  yield click(prettifyButton);
+  info("Clicked Pretty Print button.");
+  promise = awaitFileSave();
+  yield click(saveButton);
+  info("Clicked Save button.");
+  yield promise.then(getFileContents).then(function (data) {
+    is(data, prettyJSON, "Prettified JSON contents should have been saved.");
+  });
 });
--- a/devtools/client/shared/components/test/mochitest/chrome.ini
+++ b/devtools/client/shared/components/test/mochitest/chrome.ini
@@ -2,16 +2,18 @@
 support-files =
   head.js
 
 [test_frame_01.html]
 [test_HSplitBox_01.html]
 [test_notification_box_01.html]
 [test_notification_box_02.html]
 [test_notification_box_03.html]
+[test_searchbox.html]
+[test_searchbox-with-autocomplete.html]
 [test_sidebar_toggle.html]
 [test_stack-trace.html]
 [test_tabs_accessibility.html]
 [test_tabs_menu.html]
 [test_tree_01.html]
 [test_tree_02.html]
 [test_tree_03.html]
 [test_tree_04.html]
--- a/devtools/client/shared/components/test/mochitest/head.js
+++ b/devtools/client/shared/components/test/mochitest/head.js
@@ -206,8 +206,30 @@ function renderComponent(component, prop
 }
 
 function shallowRenderComponent(component, props) {
   const el = React.createElement(component, props);
   const renderer = TestUtils.createRenderer();
   renderer.render(el, {});
   return renderer.getRenderOutput();
 }
+
+/**
+ * Creates a React Component for testing
+ *
+ * @param {string} factory - factory object of the component to be created
+ * @param {object} props - React props for the component
+ * @returns {object} - container Node, Object with React component
+ * and querySelector function with $ as name.
+ */
+async function createComponentTest(factory, props) {
+  const container = document.createElement("div");
+  document.body.appendChild(container);
+
+  const component = ReactDOM.render(factory(props), container);
+  await forceRender(component);
+
+  return {
+    container,
+    component,
+    $: (s) => container.querySelector(s),
+  };
+}
new file mode 100644
--- /dev/null
+++ b/devtools/client/shared/components/test/mochitest/test_searchbox-with-autocomplete.html
@@ -0,0 +1,192 @@
+<!-- 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/. -->
+<!DOCTYPE html>
+<html>
+<!--
+Test the searchbox and autocomplete-popup components
+-->
+<head>
+  <meta charset="utf-8">
+  <title>SearchBox component test</title>
+  <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+  <script src="chrome://mochikit/content/tests/SimpleTest/SpawnTask.js"></script>
+  <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
+  <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
+</head>
+<body>
+<script src="head.js"></script>
+<script>
+/* import-globals-from head.js */
+"use strict";
+window.onload = async function () {
+  /**
+   * Takes a DOMNode with its children as list items,
+   * Typically UL > LI and each item's text value is
+   * compared with the reference item's value as a test
+   *
+   * @params {Node} - Node to be compared
+   * @reference {array} - Reference array for comparison
+   */
+  function compareAutocompleteList(list, reference) {
+    let items = [...list.children].map(el => el.textContent);
+    for (let i = 0; i < items.length; i++) {
+      let item = items[i];
+      let ref = reference[i];
+      is(item, ref, `Item ${i} in list is correct`);
+    }
+  }
+
+  let React = browserRequire("devtools/client/shared/vendor/react");
+  let SearchBox = React.createFactory(
+    browserRequire("devtools/client/shared/components/search-box")
+  );
+  const { component, $ } = await createComponentTest(SearchBox, {
+    type: "search",
+    autocompleteList: [
+      "foo",
+      "BAR",
+      "baZ",
+      "abc",
+      "pqr",
+      "xyz",
+      "ABC",
+    ],
+    onChange: () => null,
+  });
+  const { refs } = component;
+
+  async function testSearchBoxWithAutocomplete() {
+    ok(!$(".devtools-autocomplete-popup"), "Autocomplete list not visible");
+
+    $(".devtools-searchinput").focus();
+    await forceRender(component); // Wait for state update
+
+    compareAutocompleteList($(".devtools-autocomplete-listbox"), [
+      "ABC",
+      "BAR",
+      "abc",
+      "baZ",
+      "foo",
+      "pqr",
+      "xyz",
+    ]);
+
+    is(refs.autocomplete.state.selectedIndex, -1, "Initialised selectedIndex is -1");
+
+    // Blur event
+    $(".devtools-searchinput").blur();
+    await forceRender(component);
+    ok(!component.state.focused, "focused state was properly set");
+    ok(!$(".devtools-autocomplete-popup"), "Autocomplete list removed from DOM");
+  }
+
+  async function testKeyEventsWithAutocomplete() {
+    // Filtering of list
+    $(".devtools-searchinput").focus();
+    await forceRender(component);
+    sendString("aB");
+    await forceRender(component);
+    compareAutocompleteList($(".devtools-autocomplete-listbox"), ["ABC", "abc"]);
+
+    // Clear the initial input
+    synthesizeKey("VK_BACK_SPACE", {});
+    synthesizeKey("VK_BACK_SPACE", {});
+
+    // ArrowDown
+    synthesizeKey("VK_DOWN", {});
+    await forceRender(component);
+    is(refs.autocomplete.state.selectedIndex, 0, "selectedIndex is 0");
+    ok($(".devtools-autocomplete-listbox .autocomplete-item:nth-child(1)")
+      .className.includes("autocomplete-selected"),
+      "Selection class applied");
+
+    // ArrowUp should roll back to the bottom of the list
+    synthesizeKey("VK_UP", {});
+    await forceRender(component);
+    is(refs.autocomplete.state.selectedIndex, 6, "ArrowUp works");
+
+    // PageDown should take -5 places up
+    synthesizeKey("VK_PAGE_UP", {});
+    await forceRender(component);
+    is(refs.autocomplete.state.selectedIndex, 1, "PageUp works");
+
+    // PageDown should take +5 places down
+    synthesizeKey("VK_PAGE_DOWN", {});
+    await forceRender(component);
+    is(refs.autocomplete.state.selectedIndex, 6, "PageDown works");
+
+    // Home should take to the top of the list
+    synthesizeKey("VK_HOME", {});
+    await forceRender(component);
+    is(refs.autocomplete.state.selectedIndex, 0, "Home works");
+
+    // End should take to the bottom of the list
+    synthesizeKey("VK_END", {});
+    await forceRender(component);
+    is(refs.autocomplete.state.selectedIndex, 6, "End works");
+
+    // Key down in existing state should rollover to the top
+    synthesizeKey("VK_DOWN", {});
+    await forceRender(component);
+    // Tab should select the component and hide popup
+    synthesizeKey("VK_TAB", {});
+    await forceRender(component);
+    is(component.state.value, "ABC", "Tab hit selects the item");
+    ok(!$(".devtools-autocomplete-popup"), "Tab hit hides the popup");
+
+    // Activate popup by removing a key
+    synthesizeKey("VK_BACK_SPACE", {});
+    await forceRender(component);
+    ok($(".devtools-autocomplete-popup"), "Popup is up");
+    compareAutocompleteList($(".devtools-autocomplete-listbox"), ["ABC", "abc"]);
+
+    // Enter key selection
+    synthesizeKey("VK_UP", {});
+    await forceRender(component);
+    synthesizeKey("VK_RETURN", {});
+    is(component.state.value, "abc", "Enter selection");
+    ok(!$(".devtools-autocomplete-popup"), "Enter/Return hides the popup");
+
+    // Escape should remove the autocomplete component
+    synthesizeKey("VK_ESCAPE", {});
+    await forceRender(component);
+    ok(!$(".devtools-autocomplete-popup"),
+      "Autocomplete list removed from DOM on Escape");
+  }
+
+  async function testMouseEventsWithAutocomplete() {
+    $(".devtools-searchinput").focus();
+    await setState(component, {
+      value: "",
+      focused: true,
+    });
+    await forceRender(component);
+
+    // ArrowDown
+    synthesizeKey("VK_DOWN", {});
+    await forceRender(component);
+    synthesizeMouseAtCenter($(".devtools-searchinput"), {}, window);
+    await forceRender(component);
+    is(component.state.focused, true, "Component should now be focused");
+
+    sendString("pq");
+    await forceRender(component);
+    synthesizeMouseAtCenter(
+      $(".devtools-autocomplete-listbox .autocomplete-item:nth-child(1)"),
+      {}, window
+    );
+    await forceRender(component);
+    is(component.state.value, "pqr", "Mouse click selects the item.");
+    ok(!$(".devtools-autocomplete-popup"), "Mouse click on item hides the popup");
+  }
+
+  add_task(async function () {
+    await testSearchBoxWithAutocomplete();
+    await testKeyEventsWithAutocomplete();
+    await testMouseEventsWithAutocomplete();
+  });
+};
+</script>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/devtools/client/shared/components/test/mochitest/test_searchbox.html
@@ -0,0 +1,76 @@
+<!-- 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/. -->
+<!DOCTYPE html>
+<html>
+<!--
+Test the searchbox component
+-->
+<head>
+  <meta charset="utf-8">
+  <title>SearchBox component test</title>
+  <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+  <script src="chrome://mochikit/content/tests/SimpleTest/SpawnTask.js"></script>
+  <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
+  <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
+</head>
+<body>
+<script src="head.js"></script>
+<script>
+/* import-globals-from head.js */
+"use strict";
+window.onload = function () {
+  let React = browserRequire("devtools/client/shared/vendor/react");
+  let SearchBox = React.createFactory(
+    browserRequire("devtools/client/shared/components/search-box")
+  );
+  ok(SearchBox, "Got the SearchBox factory");
+
+  async function testSimpleSearchBox() {
+    // Test initial state
+    const { component, $ } = await createComponentTest(SearchBox, {
+      type: "search",
+      keyShortcut: "CmdOrCtrl+F",
+      placeholder: "crazy placeholder",
+    });
+
+    is(component.state.value, "", "Initial value is blank");
+    ok(!component.state.focused, "Input isn't initially focused");
+    ok($(".devtools-searchinput-clear").hidden, "Clear button hidden");
+    is($(".devtools-searchinput").placeholder, "crazy placeholder",
+     "Placeholder is properly set");
+
+    synthesizeKey("f", { accelKey: true });
+    await forceRender(component); // Wait for state update
+    ok(component.state.focused, "Shortcut key focused the input box");
+
+    $(".devtools-searchinput").blur();
+    await forceRender(component);
+    ok(!component.state.focused, "`focused` state set to false after blur");
+
+    // Test changing value in state
+    await setState(component, {
+      value: "foo",
+    });
+
+    is(component.state.value, "foo", "value was properly set on state");
+    is($(".devtools-searchinput").value, "foo", "value was properly set on element");
+
+    // Filling input should show clear button
+    ok(!$(".devtools-searchinput-clear").hidden, "Clear button shown");
+
+    // Clearing value should hide clear button
+    await setState(component, {
+      value: "",
+    });
+    await forceRender(component);
+    ok($(".devtools-searchinput-clear").hidden, "Clear button was hidden");
+  }
+
+  add_task(async function () {
+    await testSimpleSearchBox();
+  });
+};
+</script>
+</body>
+</html>
--- a/docshell/shistory/nsSHEntry.cpp
+++ b/docshell/shistory/nsSHEntry.cpp
@@ -961,11 +961,14 @@ nsSHEntry::SetLastTouched(uint32_t aLast
 {
   mShared->mLastTouched = aLastTouched;
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsSHEntry::SetSHistory(nsISHistory* aSHistory)
 {
-  mShared->mSHistory = do_GetWeakReference(aSHistory);
+  nsWeakPtr shistory = do_GetWeakReference(aSHistory);
+  // mSHistory can not be changed once it's set
+  MOZ_DIAGNOSTIC_ASSERT(!mShared->mSHistory || (mShared->mSHistory == shistory));
+  mShared->mSHistory = shistory;
   return NS_OK;
 }
--- a/docshell/shistory/nsSHEntryShared.cpp
+++ b/docshell/shistory/nsSHEntryShared.cpp
@@ -129,16 +129,20 @@ nsSHEntryShared::SetContentViewer(nsICon
 {
   NS_PRECONDITION(!aViewer || !mContentViewer,
                   "SHEntryShared already contains viewer");
 
   if (mContentViewer || !aViewer) {
     DropPresentationState();
   }
 
+  // If we're setting mContentViewer to null, state should already be cleared
+  // in the DropPresentationState() call above; If we're setting it to a
+  // non-null content viewer, the entry shouldn't have been tracked either.
+  MOZ_DIAGNOSTIC_ASSERT(!GetExpirationState()->IsTracked());
   mContentViewer = aViewer;
 
   if (mContentViewer) {
     // mSHistory is only set for root entries, but in general bfcache only
     // applies to root entries as well. BFCache for subframe navigation has been
     // disabled since 2005 in bug 304860.
     nsCOMPtr<nsISHistoryInternal> shistory = do_QueryReferent(mSHistory);
     if (shistory) {
--- a/docshell/shistory/nsSHistory.cpp
+++ b/docshell/shistory/nsSHistory.cpp
@@ -1315,16 +1315,17 @@ nsSHistory::AddToExpirationTracker(nsIBF
   mHistoryTracker->AddObject(entry);
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsSHistory::RemoveFromExpirationTracker(nsIBFCacheEntry* aEntry)
 {
   RefPtr<nsSHEntryShared> entry = static_cast<nsSHEntryShared*>(aEntry);
+  MOZ_DIAGNOSTIC_ASSERT(mHistoryTracker && !mHistoryTracker->IsEmpty());
   if (!mHistoryTracker || !entry) {
     return NS_ERROR_FAILURE;
   }
 
   mHistoryTracker->RemoveObject(entry);
   return NS_OK;
 }
 
@@ -1922,26 +1923,29 @@ nsSHistory::InitiateLoad(nsISHEntry* aFr
   return aFrameDS->LoadURI(nextURI, loadInfo,
                            nsIWebNavigation::LOAD_FLAGS_NONE, false);
 
 }
 
 NS_IMETHODIMP
 nsSHistory::SetRootDocShell(nsIDocShell* aDocShell)
 {
+  MOZ_DIAGNOSTIC_ASSERT(!aDocShell || !mRootDocShell);
   mRootDocShell = aDocShell;
 
   // Init mHistoryTracker on setting mRootDocShell so we can bind its event
   // target to the tabGroup.
   if (mRootDocShell) {
     nsCOMPtr<nsPIDOMWindowOuter> win = mRootDocShell->GetWindow();
     if (!win) {
       return NS_ERROR_UNEXPECTED;
     }
 
+    // mHistroyTracker can only be set once.
+    MOZ_DIAGNOSTIC_ASSERT(!mHistoryTracker);
     RefPtr<mozilla::dom::TabGroup> tabGroup = win->TabGroup();
     mHistoryTracker = mozilla::MakeUnique<HistoryTracker>(
       this,
       mozilla::Preferences::GetUint(CONTENT_VIEWER_TIMEOUT_SECONDS,
                                     CONTENT_VIEWER_TIMEOUT_SECONDS_DEFAULT),
       tabGroup->EventTargetFor(mozilla::TaskCategory::Other));
   }
 
--- a/dom/animation/test/chrome/test_animation_observers_sync.html
+++ b/dom/animation/test/chrome/test_animation_observers_sync.html
@@ -838,16 +838,46 @@ function createPseudo(test, element, typ
       "records after iterationComposite is changed");
 
     anim.effect.iterationComposite = 'accumulate';
     assert_equals_records(observer.takeRecords(),
       [], "no record after setting the same iterationComposite");
 
   }, "set_iterationComposite");
 
+  test(t => {
+    var div = addDiv(t);
+    var observer =
+      setupSynchronousObserver(t,
+                               aOptions.subtree ? div.parentNode : div,
+                               aOptions.subtree);
+
+    var anim = div.animate({ opacity: [ 0, 1 ] },
+                           { duration: 100 * MS_PER_SEC });
+
+    assert_equals_records(observer.takeRecords(),
+      [{ added: [anim], changed: [], removed: [] }],
+      "records after animation is added");
+
+    anim.effect.setKeyframes({ opacity: 0.1 });
+    assert_equals_records(observer.takeRecords(),
+      [{ added: [], changed: [anim], removed: [] }],
+      "records after keyframes are changed");
+
+    anim.effect.setKeyframes({ opacity: 0.1 });
+    assert_equals_records(observer.takeRecords(),
+      [], "no record after setting the same keyframes");
+
+    anim.effect.setKeyframes(null);
+    assert_equals_records(observer.takeRecords(),
+      [{ added: [], changed: [anim], removed: [] }],
+      "records after keyframes are set to empty");
+
+  }, "set_keyframes");
+
 });
 
 test(t => {
   var div = addDiv(t);
   var observer = setupSynchronousObserver(t, div, true);
 
   var child = document.createElement("div");
   div.appendChild(child);
--- a/dom/base/Navigator.cpp
+++ b/dom/base/Navigator.cpp
@@ -1206,22 +1206,16 @@ Navigator::SendBeaconInternal(const nsAS
 
   if (aBody) {
     aRv = aBody->GetAsStream(getter_AddRefs(in), &length,
                              contentTypeWithCharset, charset);
     if (NS_WARN_IF(aRv.Failed())) {
       return false;
     }
 
-    if (aType == eBeaconTypeArrayBuffer) {
-      MOZ_ASSERT(contentTypeWithCharset.IsEmpty());
-      MOZ_ASSERT(charset.IsEmpty());
-      contentTypeWithCharset.Assign("application/octet-stream");
-    }
-
     nsCOMPtr<nsIUploadChannel2> uploadChannel = do_QueryInterface(channel);
     if (!uploadChannel) {
       aRv.Throw(NS_ERROR_FAILURE);
       return false;
     }
 
     uploadChannel->ExplicitSetUploadStream(in, contentTypeWithCharset, length,
                                            NS_LITERAL_CSTRING("POST"),
--- a/dom/media/ipc/RemoteVideoDecoder.cpp
+++ b/dom/media/ipc/RemoteVideoDecoder.cpp
@@ -133,22 +133,31 @@ RemoteDecoderModule::SupportsMimeType(co
 
 bool
 RemoteDecoderModule::Supports(const TrackInfo& aTrackInfo,
                               DecoderDoctorDiagnostics* aDiagnostics) const
 {
   return mWrapped->Supports(aTrackInfo, aDiagnostics);
 }
 
+static inline bool
+IsRemoteAcceleratedCompositor(KnowsCompositor* aKnows)
+{
+  TextureFactoryIdentifier ident = aKnows->GetTextureFactoryIdentifier();
+  return ident.mParentBackend != LayersBackend::LAYERS_BASIC &&
+         ident.mParentProcessType == GeckoProcessType_GPU;
+}
+
 already_AddRefed<MediaDataDecoder>
 RemoteDecoderModule::CreateVideoDecoder(const CreateDecoderParams& aParams)
 {
   if (!MediaPrefs::PDMUseGPUDecoder() ||
       !aParams.mKnowsCompositor ||
-      aParams.mKnowsCompositor->GetTextureFactoryIdentifier().mParentProcessType != GeckoProcessType_GPU) {
+      !IsRemoteAcceleratedCompositor(aParams.mKnowsCompositor))
+  {
     return mWrapped->CreateVideoDecoder(aParams);
   }
 
   RefPtr<RemoteVideoDecoder> object = new RemoteVideoDecoder();
 
   SynchronousTask task("InitIPDL");
   bool success;
   VideoDecoderManagerChild::GetManagerThread()->Dispatch(NS_NewRunnableFunction([&]() {
--- a/dom/tests/mochitest/beacon/beacon-handler.sjs
+++ b/dom/tests/mochitest/beacon/beacon-handler.sjs
@@ -84,17 +84,20 @@ function handleRequest(request, response
     var data = "";
     for (var i=0; i < bytes.length; i++) {
       // We are only passing strings at this point.
       if (bytes[i] < 32) continue;
       var charcode = String.fromCharCode(bytes[i]);
       data += charcode;
     }
 
-    var mimetype = request.getHeader("Content-Type");
+    var mimetype = "";
+    if (request.hasHeader("Content-Type")) {
+      mimetype = request.getHeader("Content-Type");
+    }
 
     // check to see if this is form data.
     if (mimetype.indexOf("multipart/form-data") != -1) {
 
       // trim the mime type to make testing easier.
       mimetype = "multipart/form-data";
       // Extract only the form-data name.
 
--- a/dom/tests/mochitest/beacon/test_beaconFrame.html
+++ b/dom/tests/mochitest/beacon/test_beaconFrame.html
@@ -100,17 +100,17 @@ function stringToFormData(input) {
 }
 
 function identity(data) {
     return data;
 }
 
 var tests = [
     function() { createIframeWithData("hi!", "text/plain;charset=UTF-8", identity); },
-    function() { createIframeWithData("123", "application/octet-stream", stringToArrayBuffer); },
+    function() { createIframeWithData("123", "", stringToArrayBuffer); },
     function() { createIframeWithData("abc", "text/html", stringToBlob); },
     function() { createIframeWithData("qwerty", "multipart/form-data", stringToFormData); },
     function() { SimpleTest.finish(); },
 ];
 
 </script>
 </pre>
 </body>
--- a/dom/webidl/DedicatedWorkerGlobalScope.webidl
+++ b/dom/webidl/DedicatedWorkerGlobalScope.webidl
@@ -10,15 +10,17 @@
  * Software ASA.
  * You are granted a license to use, reproduce and create derivative works of
  * this document.
  */
 
 [Global=(Worker,DedicatedWorker),
  Exposed=DedicatedWorker]
 interface DedicatedWorkerGlobalScope : WorkerGlobalScope {
+  readonly attribute DOMString name;
+
   [Throws]
   void postMessage(any message, optional sequence<object> transfer = []);
 
   void close();
 
   attribute EventHandler onmessage;
 };
--- a/dom/webidl/SharedWorker.webidl
+++ b/dom/webidl/SharedWorker.webidl
@@ -1,12 +1,12 @@
 /* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/.
  */
 
-[Constructor(DOMString scriptURL, optional DOMString name)]
+[Constructor(USVString scriptURL, optional (DOMString or WorkerOptions) options)]
 interface SharedWorker : EventTarget {
     readonly attribute MessagePort port;
 };
 
 SharedWorker implements AbstractWorker;
--- a/dom/webidl/Worker.webidl
+++ b/dom/webidl/Worker.webidl
@@ -7,27 +7,33 @@
  * http://www.whatwg.org/specs/web-apps/current-work/multipage/workers.html
  *
  * © Copyright 2004-2011 Apple Computer, Inc., Mozilla Foundation, and Opera
  * Software ASA.
  * You are granted a license to use, reproduce and create derivative works of
  * this document.
  */
 
-[Constructor(DOMString scriptURL),
+[Constructor(USVString scriptURL, optional WorkerOptions options),
  Func="mozilla::dom::workers::WorkerPrivate::WorkerAvailable",
  Exposed=(Window,DedicatedWorker,SharedWorker,System)]
 interface Worker : EventTarget {
   void terminate();
 
   [Throws]
   void postMessage(any message, optional sequence<object> transfer = []);
 
   attribute EventHandler onmessage;
 };
 
 Worker implements AbstractWorker;
 
-[Constructor(DOMString scriptURL),
+dictionary WorkerOptions {
+  // WorkerType type = "classic"; TODO: Bug 1247687
+  // RequestCredentials credentials = "omit"; // credentials is only used if type is "module" TODO: Bug 1247687
+  DOMString name = "";
+};
+
+[Constructor(USVString scriptURL),
  Func="mozilla::dom::workers::ChromeWorkerPrivate::WorkerAvailable",
  Exposed=(Window,DedicatedWorker,SharedWorker,System)]
 interface ChromeWorker : Worker {
 };
--- a/dom/workers/RuntimeService.cpp
+++ b/dom/workers/RuntimeService.cpp
@@ -251,26 +251,26 @@ GetWorkerPref(const nsACString& aPref,
   return result;
 }
 
 // This fn creates a key for a SharedWorker that contains the name, script
 // spec, and the serialized origin attributes:
 // "name|scriptSpec^key1=val1&key2=val2&key3=val3"
 void
 GenerateSharedWorkerKey(const nsACString& aScriptSpec,
-                        const nsACString& aName,
+                        const nsAString& aName,
                         const OriginAttributes& aAttrs,
                         nsCString& aKey)
 {
   nsAutoCString suffix;
   aAttrs.CreateSuffix(suffix);
 
   aKey.Truncate();
   aKey.SetCapacity(aName.Length() + aScriptSpec.Length() + suffix.Length() + 2);
-  aKey.Append(aName);
+  aKey.Append(NS_ConvertUTF16toUTF8(aName));
   aKey.Append('|');
   aKey.Append(aScriptSpec);
   aKey.Append(suffix);
 }
 
 void
 LoadContextOptions(const char* aPrefName, void* /* aClosure */)
 {
@@ -1680,17 +1680,17 @@ RuntimeService::RegisterWorker(WorkerPri
     else if (isServiceWorker) {
       domainInfo->mActiveServiceWorkers.AppendElement(aWorkerPrivate);
     }
     else {
       domainInfo->mActiveWorkers.AppendElement(aWorkerPrivate);
     }
 
     if (isSharedWorker) {
-      const nsCString& sharedWorkerName = aWorkerPrivate->WorkerName();
+      const nsString& sharedWorkerName(aWorkerPrivate->WorkerName());
       nsAutoCString key;
       GenerateSharedWorkerKey(sharedWorkerScriptSpec, sharedWorkerName,
                               aWorkerPrivate->GetOriginAttributes(), key);
       MOZ_ASSERT(!domainInfo->mSharedWorkerInfos.Get(key));
 
       SharedWorkerInfo* sharedWorkerInfo =
         new SharedWorkerInfo(aWorkerPrivate, sharedWorkerScriptSpec,
                              sharedWorkerName);
@@ -2427,17 +2427,17 @@ RuntimeService::ResumeWorkersForWindow(n
   for (uint32_t index = 0; index < workers.Length(); index++) {
     workers[index]->ParentWindowResumed();
   }
 }
 
 nsresult
 RuntimeService::CreateSharedWorker(const GlobalObject& aGlobal,
                                    const nsAString& aScriptURL,
-                                   const nsACString& aName,
+                                   const nsAString& aName,
                                    SharedWorker** aSharedWorker)
 {
   AssertIsOnMainThread();
 
   nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(aGlobal.GetAsSupports());
   MOZ_ASSERT(window);
 
   JSContext* cx = aGlobal.Context();
@@ -2452,17 +2452,17 @@ RuntimeService::CreateSharedWorker(const
   return CreateSharedWorkerFromLoadInfo(cx, &loadInfo, aScriptURL, aName,
                                         aSharedWorker);
 }
 
 nsresult
 RuntimeService::CreateSharedWorkerFromLoadInfo(JSContext* aCx,
                                                WorkerLoadInfo* aLoadInfo,
                                                const nsAString& aScriptURL,
-                                               const nsACString& aName,
+                                               const nsAString& aName,
                                                SharedWorker** aSharedWorker)
 {
   AssertIsOnMainThread();
   MOZ_ASSERT(aLoadInfo);
   MOZ_ASSERT(aLoadInfo->mResolvedScriptURI);
 
   RefPtr<WorkerPrivate> workerPrivate;
   {
@@ -2497,17 +2497,18 @@ RuntimeService::CreateSharedWorkerFromLo
   // WorkerPrivate already exists and its secure context state doesn't match
   // what we want for the new SharedWorker.
   bool shouldAttachToWorkerPrivate = true;
   bool created = false;
   ErrorResult rv;
   if (!workerPrivate) {
     workerPrivate =
       WorkerPrivate::Constructor(aCx, aScriptURL, false,
-                                 WorkerTypeShared, aName, aLoadInfo, rv);
+                                 WorkerTypeShared, aName, NullCString(),
+                                 aLoadInfo, rv);
     NS_ENSURE_TRUE(workerPrivate, rv.StealNSResult());
 
     created = true;
   } else {
     // Check whether the secure context state matches.  The current compartment
     // of aCx is the compartment of the SharedWorker constructor that was
     // invoked, which is the compartment of the document that will be hooked up
     // to the worker, so that's what we want to check.
--- a/dom/workers/RuntimeService.h
+++ b/dom/workers/RuntimeService.h
@@ -25,21 +25,21 @@ class SharedWorker;
 class WorkerThread;
 
 class RuntimeService final : public nsIObserver
 {
   struct SharedWorkerInfo
   {
     WorkerPrivate* mWorkerPrivate;
     nsCString mScriptSpec;
-    nsCString mName;
+    nsString mName;
 
     SharedWorkerInfo(WorkerPrivate* aWorkerPrivate,
                      const nsACString& aScriptSpec,
-                     const nsACString& aName)
+                     const nsAString& aName)
     : mWorkerPrivate(aWorkerPrivate), mScriptSpec(aScriptSpec), mName(aName)
     { }
   };
 
   struct WorkerDomainInfo
   {
     nsCString mDomain;
     nsTArray<WorkerPrivate*> mActiveWorkers;
@@ -146,17 +146,17 @@ public:
   SuspendWorkersForWindow(nsPIDOMWindowInner* aWindow);
 
   void
   ResumeWorkersForWindow(nsPIDOMWindowInner* aWindow);
 
   nsresult
   CreateSharedWorker(const GlobalObject& aGlobal,
                      const nsAString& aScriptURL,
-                     const nsACString& aName,
+                     const nsAString& aName,
                      SharedWorker** aSharedWorker);
 
   void
   ForgetSharedWorker(WorkerPrivate* aWorkerPrivate);
 
   const NavigatorProperties&
   GetNavigatorProperties() const
   {
@@ -270,15 +270,15 @@ private:
 
   static void
   WorkerPrefChanged(const char* aPrefName, void* aClosure);
 
   nsresult
   CreateSharedWorkerFromLoadInfo(JSContext* aCx,
                                  WorkerLoadInfo* aLoadInfo,
                                  const nsAString& aScriptURL,
-                                 const nsACString& aName,
+                                 const nsAString& aName,
                                  SharedWorker** aSharedWorker);
 };
 
 END_WORKERS_NAMESPACE
 
 #endif /* mozilla_dom_workers_runtimeservice_h__ */
--- a/dom/workers/ServiceWorkerEvents.cpp
+++ b/dom/workers/ServiceWorkerEvents.cpp
@@ -108,17 +108,17 @@ CancelChannelRunnable::CancelChannelRunn
 NS_IMETHODIMP
 CancelChannelRunnable::Run()
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   // TODO: When bug 1204254 is implemented, this time marker should be moved to
   // the point where the body of the network request is complete.
   mChannel->SetHandleFetchEventEnd(TimeStamp::Now());
-  mChannel->SaveTimeStampsToUnderlyingChannel();
+  mChannel->SaveTimeStamps();
 
   mChannel->Cancel(mStatus);
   mRegistration->MaybeScheduleUpdate();
   return NS_OK;
 }
 
 FetchEvent::FetchEvent(EventTarget* aOwner)
   : ExtendableEvent(aOwner)
@@ -232,18 +232,20 @@ public:
     loadInfo->MaybeIncreaseTainting(mInternalResponse->GetTainting());
 
     rv = mChannel->FinishSynthesizedResponse(mResponseURLSpec);
     if (NS_WARN_IF(NS_FAILED(rv))) {
       mChannel->Cancel(NS_ERROR_INTERCEPTION_FAILED);
       return NS_OK;
     }
 
-    mChannel->SetHandleFetchEventEnd(TimeStamp::Now());
-    mChannel->SaveTimeStampsToUnderlyingChannel();
+    TimeStamp timeStamp = TimeStamp::Now();
+    mChannel->SetHandleFetchEventEnd(timeStamp);
+    mChannel->SetFinishSynthesizedResponseEnd(timeStamp);
+    mChannel->SaveTimeStamps();
 
     nsCOMPtr<nsIObserverService> obsService = services::GetObserverService();
     if (obsService) {
       obsService->NotifyObservers(underlyingChannel, "service-worker-synthesized-response", nullptr);
     }
 
     return rv;
   }
@@ -549,16 +551,17 @@ public:
 };
 
 NS_IMPL_ISUPPORTS0(RespondWithHandler)
 
 void
 RespondWithHandler::ResolvedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue)
 {
   AutoCancel autoCancel(this, mRequestURL);
+  mInterceptedChannel->SetFinishResponseStart(TimeStamp::Now());
 
   if (!aValue.isObject()) {
     NS_WARNING("FetchEvent::RespondWith was passed a promise resolved to a non-Object value");
 
     nsCString sourceSpec;
     uint32_t line = 0;
     uint32_t column = 0;
     nsString valueString;
@@ -715,16 +718,18 @@ RespondWithHandler::ResolvedCallback(JSC
 void
 RespondWithHandler::RejectedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue)
 {
   nsCString sourceSpec = mRespondWithScriptSpec;
   uint32_t line = mRespondWithLineNumber;
   uint32_t column = mRespondWithColumnNumber;
   nsString valueString;
 
+  mInterceptedChannel->SetFinishResponseStart(TimeStamp::Now());
+
   ExtractErrorValues(aCx, aValue, sourceSpec, &line, &column, valueString);
 
   ::AsyncLog(mInterceptedChannel, sourceSpec, line, column,
              NS_LITERAL_CSTRING("InterceptionRejectedResponseWithURL"),
              mRequestURL, valueString);
 
   CancelRequest(NS_ERROR_INTERCEPTION_FAILED);
 }
--- a/dom/workers/ServiceWorkerPrivate.cpp
+++ b/dom/workers/ServiceWorkerPrivate.cpp
@@ -1502,24 +1502,27 @@ private:
   ~FetchEventRunnable() {}
 
   class ResumeRequest final : public Runnable {
     nsMainThreadPtrHandle<nsIInterceptedChannel> mChannel;
   public:
     explicit ResumeRequest(nsMainThreadPtrHandle<nsIInterceptedChannel>& aChannel)
       : mChannel(aChannel)
     {
+      mChannel->SetFinishResponseStart(TimeStamp::Now());
     }
 
     NS_IMETHOD Run() override
     {
       AssertIsOnMainThread();
 
-      mChannel->SetHandleFetchEventEnd(TimeStamp::Now());
-      mChannel->SaveTimeStampsToUnderlyingChannel();
+      TimeStamp timeStamp = TimeStamp::Now();
+      mChannel->SetHandleFetchEventEnd(timeStamp);
+      mChannel->SetChannelResetEnd(timeStamp);
+      mChannel->SaveTimeStamps();
 
       nsresult rv = mChannel->ResetInterception();
       NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
                            "Failed to resume intercepted network request");
       return rv;
     }
   };
 
@@ -1823,17 +1826,19 @@ ServiceWorkerPrivate::SpawnWorkerIfNeede
   AutoJSAPI jsapi;
   jsapi.Init();
   ErrorResult error;
   NS_ConvertUTF8toUTF16 scriptSpec(mInfo->ScriptSpec());
 
   mWorkerPrivate = WorkerPrivate::Constructor(jsapi.cx(),
                                               scriptSpec,
                                               false, WorkerTypeService,
-                                              mInfo->Scope(), &info, error);
+                                              NullString(),
+                                              mInfo->Scope(),
+                                              &info, error);
   if (NS_WARN_IF(error.Failed())) {
     return error.StealNSResult();
   }
 
   RenewKeepAliveToken(aWhy);
 
   if (aNewWorkerCreated) {
     *aNewWorkerCreated = true;
--- a/dom/workers/SharedWorker.cpp
+++ b/dom/workers/SharedWorker.cpp
@@ -7,16 +7,17 @@
 #include "SharedWorker.h"
 
 #include "nsPIDOMWindow.h"
 
 #include "mozilla/EventDispatcher.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/dom/MessagePort.h"
 #include "mozilla/dom/SharedWorkerBinding.h"
+#include "mozilla/dom/WorkerBinding.h"
 #include "mozilla/Telemetry.h"
 #include "nsContentUtils.h"
 #include "nsIClassInfoImpl.h"
 #include "nsIDOMEvent.h"
 
 #include "RuntimeService.h"
 #include "WorkerPrivate.h"
 
@@ -44,30 +45,33 @@ SharedWorker::~SharedWorker()
 {
   AssertIsOnMainThread();
 }
 
 // static
 already_AddRefed<SharedWorker>
 SharedWorker::Constructor(const GlobalObject& aGlobal,
                           const nsAString& aScriptURL,
-                          const mozilla::dom::Optional<nsAString>& aName,
+                          const StringOrWorkerOptions& aOptions,
                           ErrorResult& aRv)
 {
   AssertIsOnMainThread();
 
   RuntimeService* rts = RuntimeService::GetOrCreateService();
   if (!rts) {
     aRv = NS_ERROR_NOT_AVAILABLE;
     return nullptr;
   }
 
-  nsCString name;
-  if (aName.WasPassed()) {
-    name = NS_ConvertUTF16toUTF8(aName.Value());
+  nsAutoString name;
+  if (aOptions.IsString()) {
+    name = aOptions.GetAsString();
+  } else {
+    MOZ_ASSERT(aOptions.IsWorkerOptions());
+    name = aOptions.GetAsWorkerOptions().mName;
   }
 
   RefPtr<SharedWorker> sharedWorker;
   nsresult rv = rts->CreateSharedWorker(aGlobal, aScriptURL, name,
                                         getter_AddRefs(sharedWorker));
   if (NS_FAILED(rv)) {
     aRv = rv;
     return nullptr;
--- a/dom/workers/SharedWorker.h
+++ b/dom/workers/SharedWorker.h
@@ -15,16 +15,17 @@
 class nsIDOMEvent;
 class nsPIDOMWindowInner;
 
 namespace mozilla {
 class EventChainPreVisitor;
 
 namespace dom {
 class MessagePort;
+class StringOrWorkerOptions;
 }
 } // namespace mozilla
 
 BEGIN_WORKERS_NAMESPACE
 
 class RuntimeService;
 class WorkerPrivate;
 
@@ -38,17 +39,17 @@ class SharedWorker final : public DOMEve
   RefPtr<WorkerPrivate> mWorkerPrivate;
   RefPtr<MessagePort> mMessagePort;
   nsTArray<nsCOMPtr<nsIDOMEvent>> mFrozenEvents;
   bool mFrozen;
 
 public:
   static already_AddRefed<SharedWorker>
   Constructor(const GlobalObject& aGlobal, const nsAString& aScriptURL,
-              const Optional<nsAString>& aName, ErrorResult& aRv);
+              const StringOrWorkerOptions& aOptions, ErrorResult& aRv);
 
   MessagePort*
   Port();
 
   bool
   IsFrozen() const
   {
     return mFrozen;
--- a/dom/workers/WorkerPrivate.cpp
+++ b/dom/workers/WorkerPrivate.cpp
@@ -2721,32 +2721,31 @@ typename WorkerPrivateParent<Derived>::c
     WorkerPrivateParent<Derived>::cycleCollection();
 
 template <class Derived>
 WorkerPrivateParent<Derived>::WorkerPrivateParent(
                                            WorkerPrivate* aParent,
                                            const nsAString& aScriptURL,
                                            bool aIsChromeWorker,
                                            WorkerType aWorkerType,
-                                           const nsACString& aWorkerName,
+                                           const nsAString& aWorkerName,
+                                           const nsACString& aServiceWorkerScope,
                                            WorkerLoadInfo& aLoadInfo)
 : mMutex("WorkerPrivateParent Mutex"),
   mCondVar(mMutex, "WorkerPrivateParent CondVar"),
   mParent(aParent), mScriptURL(aScriptURL),
-  mWorkerName(aWorkerName), mLoadingWorkerScript(false),
-  mBusyCount(0), mParentWindowPausedDepth(0), mParentStatus(Pending),
-  mParentFrozen(false), mIsChromeWorker(aIsChromeWorker),
-  mMainThreadObjectsForgotten(false), mIsSecureContext(false),
-  mWorkerType(aWorkerType),
+  mWorkerName(aWorkerName), mServiceWorkerScope(aServiceWorkerScope),
+  mLoadingWorkerScript(false), mBusyCount(0), mParentWindowPausedDepth(0),
+  mParentStatus(Pending), mParentFrozen(false),
+  mIsChromeWorker(aIsChromeWorker), mMainThreadObjectsForgotten(false),
+  mIsSecureContext(false), mWorkerType(aWorkerType),
   mCreationTimeStamp(TimeStamp::Now()),
   mCreationTimeHighRes((double)PR_Now() / PR_USEC_PER_MSEC)
 {
-  MOZ_ASSERT_IF(!IsDedicatedWorker(),
-                !aWorkerName.IsVoid() && NS_IsMainThread());
-  MOZ_ASSERT_IF(IsDedicatedWorker(), aWorkerName.IsEmpty());
+  MOZ_ASSERT_IF(!IsDedicatedWorker(), NS_IsMainThread());
 
   if (aLoadInfo.mWindow) {
     AssertIsOnMainThread();
     MOZ_ASSERT(aLoadInfo.mWindow->IsInnerWindow(),
                "Should have inner window here!");
     BindToOwner(aLoadInfo.mWindow);
   }
 
@@ -4415,21 +4414,23 @@ WorkerDebugger::ReportErrorToDebuggerOnM
   report.mMessage = aMessage;
   report.mFilename = aFilename;
   LogErrorToConsole(report, 0);
 }
 
 WorkerPrivate::WorkerPrivate(WorkerPrivate* aParent,
                              const nsAString& aScriptURL,
                              bool aIsChromeWorker, WorkerType aWorkerType,
-                             const nsACString& aWorkerName,
+                             const nsAString& aWorkerName,
+                             const nsACString& aServiceWorkerScope,
                              WorkerLoadInfo& aLoadInfo)
   : WorkerPrivateParent<WorkerPrivate>(aParent, aScriptURL,
                                        aIsChromeWorker, aWorkerType,
-                                       aWorkerName, aLoadInfo)
+                                       aWorkerName, aServiceWorkerScope,
+                                       aLoadInfo)
   , mDebuggerRegistered(false)
   , mDebugger(nullptr)
   , mJSContext(nullptr)
   , mPRThread(nullptr)
   , mNumHoldersPreventingShutdownStart(0)
   , mDebuggerEventLoopLevel(0)
   , mMainThreadEventTarget(do_GetMainThread())
   , mWorkerControlEventTarget(new WorkerControlEventTarget(this))
@@ -4442,19 +4443,16 @@ WorkerPrivate::WorkerPrivate(WorkerPriva
   , mPendingEventQueueClearing(false)
   , mCancelAllPendingRunnables(false)
   , mPeriodicGCTimerRunning(false)
   , mIdleGCTimerRunning(false)
   , mWorkerScriptExecutedSuccessfully(false)
   , mFetchHandlerWasAdded(false)
   , mOnLine(false)
 {
-  MOZ_ASSERT_IF(!IsDedicatedWorker(), !aWorkerName.IsVoid());
-  MOZ_ASSERT_IF(IsDedicatedWorker(), aWorkerName.IsEmpty());
-
   if (aParent) {
     aParent->AssertIsOnWorkerThread();
     aParent->GetAllPreferences(mPreferences);
     mOnLine = aParent->OnLine();
   }
   else {
     AssertIsOnMainThread();
     RuntimeService::GetDefaultPreferences(mPreferences);
@@ -4501,21 +4499,22 @@ WorkerPrivate::~WorkerPrivate()
 {
   mWorkerControlEventTarget->ForgetWorkerPrivate(this);
 }
 
 // static
 already_AddRefed<WorkerPrivate>
 WorkerPrivate::Constructor(const GlobalObject& aGlobal,
                            const nsAString& aScriptURL,
+                           const WorkerOptions& aOptions,
                            ErrorResult& aRv)
 {
   return WorkerPrivate::Constructor(aGlobal, aScriptURL, false,
-                                    WorkerTypeDedicated, EmptyCString(),
-                                    nullptr, aRv);
+                                    WorkerTypeDedicated,
+                                    aOptions.mName, nullptr, aRv);
 }
 
 // static
 bool
 WorkerPrivate::WorkerAvailable(JSContext* aCx, JSObject* /* unused */)
 {
   // If we're already on a worker workers are clearly enabled.
   if (!NS_IsMainThread()) {
@@ -4533,17 +4532,17 @@ WorkerPrivate::WorkerAvailable(JSContext
 
 // static
 already_AddRefed<ChromeWorkerPrivate>
 ChromeWorkerPrivate::Constructor(const GlobalObject& aGlobal,
                                  const nsAString& aScriptURL,
                                  ErrorResult& aRv)
 {
   return WorkerPrivate::Constructor(aGlobal, aScriptURL, true,
-                                    WorkerTypeDedicated, EmptyCString(),
+                                    WorkerTypeDedicated, EmptyString(),
                                     nullptr, aRv)
                                     .downcast<ChromeWorkerPrivate>();
 }
 
 // static
 bool
 ChromeWorkerPrivate::WorkerAvailable(JSContext* aCx, JSObject* /* unused */)
 {
@@ -4558,47 +4557,42 @@ ChromeWorkerPrivate::WorkerAvailable(JSC
   return GetWorkerPrivateFromContext(aCx)->IsChromeWorker();
 }
 
 // static
 already_AddRefed<WorkerPrivate>
 WorkerPrivate::Constructor(const GlobalObject& aGlobal,
                            const nsAString& aScriptURL,
                            bool aIsChromeWorker, WorkerType aWorkerType,
-                           const nsACString& aWorkerName,
+                           const nsAString& aWorkerName,
                            WorkerLoadInfo* aLoadInfo, ErrorResult& aRv)
 {
   JSContext* cx = aGlobal.Context();
   return Constructor(cx, aScriptURL, aIsChromeWorker, aWorkerType,
-                     aWorkerName, aLoadInfo, aRv);
+                     aWorkerName, NullCString(), aLoadInfo, aRv);
 }
 
 // static
 already_AddRefed<WorkerPrivate>
 WorkerPrivate::Constructor(JSContext* aCx,
                            const nsAString& aScriptURL,
                            bool aIsChromeWorker, WorkerType aWorkerType,
-                           const nsACString& aWorkerName,
+                           const nsAString& aWorkerName,
+                           const nsACString& aServiceWorkerScope,
                            WorkerLoadInfo* aLoadInfo, ErrorResult& aRv)
 {
   WorkerPrivate* parent = NS_IsMainThread() ?
                           nullptr :
                           GetCurrentThreadWorkerPrivate();
   if (parent) {
     parent->AssertIsOnWorkerThread();
   } else {
     AssertIsOnMainThread();
   }
 
-  // Only service and shared workers can have names.
-  MOZ_ASSERT_IF(aWorkerType != WorkerTypeDedicated,
-                !aWorkerName.IsVoid());
-  MOZ_ASSERT_IF(aWorkerType == WorkerTypeDedicated,
-                aWorkerName.IsEmpty());
-
   Maybe<WorkerLoadInfo> stackLoadInfo;
   if (!aLoadInfo) {
     stackLoadInfo.emplace();
 
     nsresult rv = GetLoadInfo(aCx, nullptr, parent, aScriptURL,
                               aIsChromeWorker, InheritLoadGroup,
                               aWorkerType, stackLoadInfo.ptr());
     aRv.MightThrowJSException();
@@ -4625,17 +4619,18 @@ WorkerPrivate::Constructor(JSContext* aC
   else {
     runtimeService = RuntimeService::GetService();
   }
 
   MOZ_ASSERT(runtimeService);
 
   RefPtr<WorkerPrivate> worker =
     new WorkerPrivate(parent, aScriptURL, aIsChromeWorker,
-                      aWorkerType, aWorkerName, *aLoadInfo);
+                      aWorkerType, aWorkerName, aServiceWorkerScope,
+                      *aLoadInfo);
 
   // Gecko contexts always have an explicitly-set default locale (set by
   // XPJSRuntime::Initialize for the main thread, set by
   // WorkerThreadPrimaryRunnable::Run for workers just before running worker
   // code), so this is never SpiderMonkey's builtin default locale.
   JS::UniqueChars defaultLocale = JS_GetDefaultLocale(aCx);
   if (NS_WARN_IF(!defaultLocale)) {
     aRv.Throw(NS_ERROR_UNEXPECTED);
@@ -6916,17 +6911,17 @@ WorkerPrivate::GetOrCreateGlobalScope(JS
 
   if (!mScope) {
     RefPtr<WorkerGlobalScope> globalScope;
     if (IsSharedWorker()) {
       globalScope = new SharedWorkerGlobalScope(this, WorkerName());
     } else if (IsServiceWorker()) {
       globalScope = new ServiceWorkerGlobalScope(this, ServiceWorkerScope());
     } else {
-      globalScope = new DedicatedWorkerGlobalScope(this);
+      globalScope = new DedicatedWorkerGlobalScope(this, WorkerName());
     }
 
     JS::Rooted<JSObject*> global(aCx);
     NS_ENSURE_TRUE(globalScope->WrapGlobalObject(aCx, &global), nullptr);
 
     JSAutoCompartment ac(aCx, global);
 
     // RegisterBindings() can spin a nested event loop so we have to set mScope
--- a/dom/workers/WorkerPrivate.h
+++ b/dom/workers/WorkerPrivate.h
@@ -64,16 +64,17 @@ class ThrottledEventQueue;
 namespace dom {
 class Function;
 class MessagePort;
 class MessagePortIdentifier;
 class PromiseNativeHandler;
 class StructuredCloneHolder;
 class WorkerDebuggerGlobalScope;
 class WorkerGlobalScope;
+struct WorkerOptions;
 } // namespace dom
 namespace ipc {
 class PrincipalInfo;
 } // namespace ipc
 } // namespace mozilla
 
 struct PRThread;
 
@@ -211,19 +212,20 @@ protected:
 
   // Protected by mMutex.
   RefPtr<EventTarget> mEventTarget;
   nsTArray<RefPtr<WorkerRunnable>> mPreStartRunnables;
 
 private:
   WorkerPrivate* mParent;
   nsString mScriptURL;
-  // This is the worker name for shared workers or the worker scope
-  // for service workers.
-  nsCString mWorkerName;
+  // This is the worker name for shared workers and dedicated workers.
+  nsString mWorkerName;
+  // This is the worker scope for service workers.
+  nsCString mServiceWorkerScope;
   LocationInfo mLocationInfo;
   // The lifetime of these objects within LoadInfo is managed explicitly;
   // they do not need to be cycle collected.
   WorkerLoadInfo mLoadInfo;
 
   Atomic<bool> mLoadingWorkerScript;
 
   // Only used for top level workers.
@@ -261,17 +263,18 @@ protected:
   // The worker is owned by its thread, which is represented here.  This is set
   // in Construct() and emptied by WorkerFinishedRunnable, and conditionally
   // traversed by the cycle collector if the busy count is zero.
   RefPtr<WorkerPrivate> mSelfRef;
 
   WorkerPrivateParent(WorkerPrivate* aParent,
                       const nsAString& aScriptURL, bool aIsChromeWorker,
                       WorkerType aWorkerType,
-                      const nsACString& aSharedWorkerName,
+                      const nsAString& aWorkerName,
+                      const nsACString& aServiceWorkerScope,
                       WorkerLoadInfo& aLoadInfo);
 
   ~WorkerPrivateParent();
 
 private:
   Derived*
   ParentAsWorkerPrivate() const
   {
@@ -525,17 +528,17 @@ public:
   {
     return mLoadInfo.mServiceWorkerID;
   }
 
   const nsCString&
   ServiceWorkerScope() const
   {
     MOZ_DIAGNOSTIC_ASSERT(IsServiceWorker());
-    return mWorkerName;
+    return mServiceWorkerScope;
   }
 
   nsIURI*
   GetBaseURI() const
   {
     AssertIsOnMainThread();
     return mLoadInfo.mBaseURI;
   }
@@ -830,20 +833,19 @@ public:
     case WorkerTypeService:
       return nsIContentPolicy::TYPE_INTERNAL_SERVICE_WORKER;
     default:
       MOZ_ASSERT_UNREACHABLE("Invalid worker type");
       return nsIContentPolicy::TYPE_INVALID;
     }
   }
 
-  const nsCString&
+  const nsString&
   WorkerName() const
   {
-    MOZ_ASSERT(IsSharedWorker());
     return mWorkerName;
   }
 
   bool
   IsStorageAllowed() const
   {
     return mLoadInfo.mStorageAllowed;
   }
@@ -1048,27 +1050,29 @@ class WorkerPrivate : public WorkerPriva
   bool mOnLine;
 
 protected:
   ~WorkerPrivate();
 
 public:
   static already_AddRefed<WorkerPrivate>
   Constructor(const GlobalObject& aGlobal, const nsAString& aScriptURL,
+              const WorkerOptions& aOptions,
               ErrorResult& aRv);
 
   static already_AddRefed<WorkerPrivate>
   Constructor(const GlobalObject& aGlobal, const nsAString& aScriptURL,
               bool aIsChromeWorker, WorkerType aWorkerType,
-              const nsACString& aSharedWorkerName,
+              const nsAString& aWorkerName,
               WorkerLoadInfo* aLoadInfo, ErrorResult& aRv);
 
   static already_AddRefed<WorkerPrivate>
   Constructor(JSContext* aCx, const nsAString& aScriptURL, bool aIsChromeWorker,
-              WorkerType aWorkerType, const nsACString& aSharedWorkerName,
+              WorkerType aWorkerType, const nsAString& aWorkerName,
+              const nsACString& aServiceWorkerScope,
               WorkerLoadInfo* aLoadInfo, ErrorResult& aRv);
 
   static bool
   WorkerAvailable(JSContext* /* unused */, JSObject* /* unused */);
 
   enum LoadGroupBehavior
   {
     InheritLoadGroup,
@@ -1457,17 +1461,18 @@ public:
   // the worker thread.  Implement nsICancelableRunnable if you wish to take
   // action on cancelation.
   nsIEventTarget*
   ControlEventTarget();
 
 private:
   WorkerPrivate(WorkerPrivate* aParent,
                 const nsAString& aScriptURL, bool aIsChromeWorker,
-                WorkerType aWorkerType, const nsACString& aSharedWorkerName,
+                WorkerType aWorkerType, const nsAString& aWorkerName,
+                const nsACString& aServiceWorkerScope,
                 WorkerLoadInfo& aLoadInfo);
 
   bool
   MayContinueRunning()
   {
     AssertIsOnWorkerThread();
 
     Status status;
--- a/dom/workers/WorkerScope.cpp
+++ b/dom/workers/WorkerScope.cpp
@@ -484,18 +484,20 @@ WorkerGlobalScope::CreateImageBitmap(JSC
     return ImageBitmap::Create(this, aImage, aOffset, aLength, aFormat, aLayout,
                                aRv);
   } else {
     aRv.Throw(NS_ERROR_TYPE_ERR);
     return nullptr;
   }
 }
 
-DedicatedWorkerGlobalScope::DedicatedWorkerGlobalScope(WorkerPrivate* aWorkerPrivate)
-: WorkerGlobalScope(aWorkerPrivate)
+DedicatedWorkerGlobalScope::DedicatedWorkerGlobalScope(WorkerPrivate* aWorkerPrivate,
+                                                       const nsString& aName)
+  : WorkerGlobalScope(aWorkerPrivate)
+  , mName(aName)
 {
 }
 
 bool
 DedicatedWorkerGlobalScope::WrapGlobalObject(JSContext* aCx,
                                              JS::MutableHandle<JSObject*> aReflector)
 {
   mWorkerPrivate->AssertIsOnWorkerThread();
@@ -542,17 +544,17 @@ DedicatedWorkerGlobalScope::PostMessage(
 void
 DedicatedWorkerGlobalScope::Close(JSContext* aCx)
 {
   mWorkerPrivate->AssertIsOnWorkerThread();
   mWorkerPrivate->CloseInternal(aCx);
 }
 
 SharedWorkerGlobalScope::SharedWorkerGlobalScope(WorkerPrivate* aWorkerPrivate,
-                                                 const nsCString& aName)
+                                                 const nsString& aName)
 : WorkerGlobalScope(aWorkerPrivate), mName(aName)
 {
 }
 
 bool
 SharedWorkerGlobalScope::WrapGlobalObject(JSContext* aCx,
                                           JS::MutableHandle<JSObject*> aReflector)
 {
--- a/dom/workers/WorkerScope.h
+++ b/dom/workers/WorkerScope.h
@@ -202,53 +202,61 @@ public:
   {
     MOZ_ASSERT(mWindowInteractionsAllowed > 0);
     mWindowInteractionsAllowed--;
   }
 };
 
 class DedicatedWorkerGlobalScope final : public WorkerGlobalScope
 {
+  const nsString mName;
+
   ~DedicatedWorkerGlobalScope() { }
 
 public:
-  explicit DedicatedWorkerGlobalScope(WorkerPrivate* aWorkerPrivate);
+  DedicatedWorkerGlobalScope(WorkerPrivate* aWorkerPrivate,
+                             const nsString& aName);
 
   virtual bool
   WrapGlobalObject(JSContext* aCx,
                    JS::MutableHandle<JSObject*> aReflector) override;
 
+  void GetName(DOMString& aName) const
+  {
+    aName.AsAString() = mName;
+  }
+
   void
   PostMessage(JSContext* aCx, JS::Handle<JS::Value> aMessage,
               const Sequence<JSObject*>& aTransferable,
               ErrorResult& aRv);
 
   void
   Close(JSContext* aCx);
 
   IMPL_EVENT_HANDLER(message)
 };
 
 class SharedWorkerGlobalScope final : public WorkerGlobalScope
 {
-  const nsCString mName;
+  const nsString mName;
 
   ~SharedWorkerGlobalScope() { }
 
 public:
   SharedWorkerGlobalScope(WorkerPrivate* aWorkerPrivate,
-                          const nsCString& aName);
+                          const nsString& aName);
 
   virtual bool
   WrapGlobalObject(JSContext* aCx,
                    JS::MutableHandle<JSObject*> aReflector) override;
 
   void GetName(DOMString& aName) const
   {
-    aName.AsAString() = NS_ConvertUTF8toUTF16(mName);
+    aName.AsAString() = mName;
   }
 
   void
   Close(JSContext* aCx);
 
   IMPL_EVENT_HANDLER(connect)
 };
 
--- a/dom/workers/test/test_subworkers_suspended.html
+++ b/dom/workers/test/test_subworkers_suspended.html
@@ -61,17 +61,17 @@
         info("New location: " + testUrl2);
         testWin.location.href = testUrl2;
       });
     } else {
       is(e.persisted, true, "test page should have been persisted in pageshow");
 
       var promise = new Promise((resolve, reject) => {
         info("Waiting a few seconds...");
-        setTimeout(resolve, 5000);
+        setTimeout(resolve, 10000);
       });
 
       promise.then(function() {
         info("Retrieving data from cache...");
         return cacheData();
       })
 
       .then(function(content) {
--- a/dom/xhr/XMLHttpRequestMainThread.cpp
+++ b/dom/xhr/XMLHttpRequestMainThread.cpp
@@ -3762,20 +3762,30 @@ XMLHttpRequestMainThread::TruncateRespon
 }
 
 NS_IMPL_ISUPPORTS(XMLHttpRequestMainThread::nsHeaderVisitor, nsIHttpHeaderVisitor)
 
 NS_IMETHODIMP XMLHttpRequestMainThread::
 nsHeaderVisitor::VisitHeader(const nsACString &header, const nsACString &value)
 {
   if (mXHR.IsSafeHeader(header, mHttpChannel)) {
-    mHeaders.Append(header);
-    mHeaders.AppendLiteral(": ");
-    mHeaders.Append(value);
-    mHeaders.AppendLiteral("\r\n");
+    if (!Preferences::GetBool("dom.xhr.lowercase_header.enabled", false)) {
+      if(!mHeaderList.InsertElementSorted(HeaderEntry(header, value),
+                                          fallible)) {
+        return NS_ERROR_OUT_OF_MEMORY;
+      }
+      return NS_OK;
+    }
+
+    nsAutoCString lowerHeader(header);
+    ToLowerCase(lowerHeader);
+    if (!mHeaderList.InsertElementSorted(HeaderEntry(lowerHeader, value),
+                                         fallible)) {
+      return NS_ERROR_OUT_OF_MEMORY;
+    }
   }
   return NS_OK;
 }
 
 void
 XMLHttpRequestMainThread::MaybeCreateBlobStorage()
 {
   MOZ_ASSERT(mResponseType == XMLHttpRequestResponseType::Blob);
--- a/dom/xhr/XMLHttpRequestMainThread.h
+++ b/dom/xhr/XMLHttpRequestMainThread.h
@@ -591,26 +591,61 @@ protected:
   nsCOMPtr<nsIURI> mRequestURL;
   nsCOMPtr<nsIDocument> mResponseXML;
 
   nsCOMPtr<nsIStreamListener> mXMLParserStreamListener;
 
   // used to implement getAllResponseHeaders()
   class nsHeaderVisitor : public nsIHttpHeaderVisitor
   {
+    struct HeaderEntry final
+    {
+      nsCString mName;
+      nsCString mValue;
+
+      HeaderEntry(const nsACString& aName, const nsACString& aValue)
+        : mName(aName), mValue(aValue)
+      {}
+
+      bool
+      operator==(const HeaderEntry& aOther) const
+      {
+        return mName == aOther.mName;
+      }
+
+      bool
+      operator<(const HeaderEntry& aOther) const
+      {
+        return mName < aOther.mName;
+      }
+    };
+
   public:
     NS_DECL_ISUPPORTS
     NS_DECL_NSIHTTPHEADERVISITOR
     nsHeaderVisitor(const XMLHttpRequestMainThread& aXMLHttpRequest,
                     NotNull<nsIHttpChannel*> aHttpChannel)
       : mXHR(aXMLHttpRequest), mHttpChannel(aHttpChannel) {}
-    const nsACString &Headers() { return mHeaders; }
+    const nsACString &Headers()
+    {
+      for (uint32_t i = 0; i < mHeaderList.Length(); i++) {
+        HeaderEntry& header = mHeaderList.ElementAt(i);
+
+        mHeaders.Append(header.mName);
+        mHeaders.AppendLiteral(": ");
+        mHeaders.Append(header.mValue);
+        mHeaders.AppendLiteral("\r\n");
+      }
+      return mHeaders;
+    }
+
   private:
     virtual ~nsHeaderVisitor() {}
 
+    nsTArray<HeaderEntry> mHeaderList;
     nsCString mHeaders;
     const XMLHttpRequestMainThread& mXHR;
     NotNull<nsCOMPtr<nsIHttpChannel>> mHttpChannel;
   };
 
   // The bytes of our response body. Only used for DEFAULT, ARRAYBUFFER and
   // BLOB responseTypes
   nsCString mResponseBody;
--- a/ipc/chromium/src/base/process_util.h
+++ b/ipc/chromium/src/base/process_util.h
@@ -43,17 +43,17 @@
 #if defined(OS_WIN)
 typedef PROCESSENTRY32 ProcessEntry;
 typedef IO_COUNTERS IoCounters;
 #elif defined(OS_POSIX)
 // TODO(port): we should not rely on a Win32 structure.
 struct ProcessEntry {
   int pid;
   int ppid;
-  char szExeFile[NAME_MAX + 1];
+  char szExeFile[_POSIX_PATH_MAX + 1];
 };
 
 struct IoCounters {
   unsigned long long ReadOperationCount;
   unsigned long long WriteOperationCount;
   unsigned long long OtherOperationCount;
   unsigned long long ReadTransferCount;
   unsigned long long WriteTransferCount;
--- a/js/src/gc/Barrier.h
+++ b/js/src/gc/Barrier.h
@@ -320,16 +320,19 @@ struct InternalBarrierMethods<jsid>
 // Base class of all barrier types.
 template <typename T>
 class BarrieredBase
 {
   protected:
     // BarrieredBase is not directly instantiable.
     explicit BarrieredBase(const T& v) : value(v) {}
 
+    // BarrieredBase subclasses cannot be copy constructed by default.
+    BarrieredBase(const BarrieredBase<T>& other) = default;
+
     // Storage for all barrier classes. |value| must be a GC thing reference
     // type: either a direct pointer to a GC thing or a supported tagged
     // pointer that can reference GC things, such as JS::Value or jsid. Nested
     // barrier types are NOT supported. See assertTypeConstraints.
     T value;
 
   public:
     // Note: this is public because C++ cannot friend to a specific template instantiation.
--- a/js/src/gc/GCInternals.h
+++ b/js/src/gc/GCInternals.h
@@ -80,25 +80,25 @@ class MOZ_RAII AutoStopVerifyingBarriers
         }
     }
 
     ~AutoStopVerifyingBarriers() {
         // Nasty special case: verification runs a minor GC, which *may* nest
         // inside of an outer minor GC. This is not allowed by the
         // gc::Statistics phase tree. So we pause the "real" GC, if in fact one
         // is in progress.
-        gcstats::Phase outer = gc->stats().currentPhase();
-        if (outer != gcstats::PHASE_NONE)
+        gcstats::PhaseKind outer = gc->stats().currentPhaseKind();
+        if (outer != gcstats::PhaseKind::NONE)
             gc->stats().endPhase(outer);
-        MOZ_ASSERT(gc->stats().currentPhase() == gcstats::PHASE_NONE);
+        MOZ_ASSERT(gc->stats().currentPhaseKind() == gcstats::PhaseKind::NONE);
 
         if (restartPreVerifier)
             gc->startVerifyPreBarriers();
 
-        if (outer != gcstats::PHASE_NONE)
+        if (outer != gcstats::PhaseKind::NONE)
             gc->stats().beginPhase(outer);
     }
 };
 #else
 struct MOZ_RAII AutoStopVerifyingBarriers
 {
     AutoStopVerifyingBarriers(JSRuntime*, bool) {}
 };
--- a/js/src/gc/GCRuntime.h
+++ b/js/src/gc/GCRuntime.h
@@ -968,24 +968,24 @@ class GCRuntime
                                JS::gcreason::Reason reason, bool canAllocateMoreCode);
     void traceRuntimeForMajorGC(JSTracer* trc, AutoLockForExclusiveAccess& lock);
     void traceRuntimeAtoms(JSTracer* trc, AutoLockForExclusiveAccess& lock);
     void traceRuntimeCommon(JSTracer* trc, TraceOrMarkRuntime traceOrMark,
                             AutoLockForExclusiveAccess& lock);
     void bufferGrayRoots();
     void maybeDoCycleCollection();
     void markCompartments();
-    IncrementalProgress drainMarkStack(SliceBudget& sliceBudget, gcstats::Phase phase);
-    template <class CompartmentIterT> void markWeakReferences(gcstats::Phase phase);
-    void markWeakReferencesInCurrentGroup(gcstats::Phase phase);
-    template <class ZoneIterT, class CompartmentIterT> void markGrayReferences(gcstats::Phase phase);
+    IncrementalProgress drainMarkStack(SliceBudget& sliceBudget, gcstats::PhaseKind phase);
+    template <class CompartmentIterT> void markWeakReferences(gcstats::PhaseKind phase);
+    void markWeakReferencesInCurrentGroup(gcstats::PhaseKind phase);
+    template <class ZoneIterT, class CompartmentIterT> void markGrayReferences(gcstats::PhaseKind phase);
     void markBufferedGrayRoots(JS::Zone* zone);
-    void markGrayReferencesInCurrentGroup(gcstats::Phase phase);
-    void markAllWeakReferences(gcstats::Phase phase);
-    void markAllGrayReferences(gcstats::Phase phase);
+    void markGrayReferencesInCurrentGroup(gcstats::PhaseKind phase);
+    void markAllWeakReferences(gcstats::PhaseKind phase);
+    void markAllGrayReferences(gcstats::PhaseKind phase);
 
     void beginSweepPhase(JS::gcreason::Reason reason, AutoLockForExclusiveAccess& lock);
     void groupZonesForSweeping(JS::gcreason::Reason reason, AutoLockForExclusiveAccess& lock);
     MOZ_MUST_USE bool findInterZoneEdges();
     void getNextSweepGroup();
     void endMarkingSweepGroup();
     void beginSweepingSweepGroup();
     bool shouldReleaseObservedTypes();
@@ -1223,18 +1223,18 @@ class GCRuntime
     ActiveThreadData<size_t> sweepPhaseIndex;
     ActiveThreadData<JS::Zone*> sweepZone;
     ActiveThreadData<size_t> sweepActionIndex;
     ActiveThreadData<bool> abortSweepAfterCurrentGroup;
 
     /*
      * Concurrent sweep infrastructure.
      */
-    void startTask(GCParallelTask& task, gcstats::Phase phase, AutoLockHelperThreadState& locked);
-    void joinTask(GCParallelTask& task, gcstats::Phase phase, AutoLockHelperThreadState& locked);
+    void startTask(GCParallelTask& task, gcstats::PhaseKind phase, AutoLockHelperThreadState& locked);
+    void joinTask(GCParallelTask& task, gcstats::PhaseKind phase, AutoLockHelperThreadState& locked);
     friend class AutoRunParallelTask;
 
     /*
      * List head of arenas allocated during the sweep phase.
      */
     ActiveThreadData<Arena*> arenasAllocatedDuringSweep;
 
     /*
@@ -1361,19 +1361,19 @@ class GCRuntime
     const void* addressOfNurseryPosition() {
         return nursery_.refNoCheck().addressOfPosition();
     }
     const void* addressOfNurseryCurrentEnd() {
         return nursery_.refNoCheck().addressOfCurrentEnd();
     }
 
     void minorGC(JS::gcreason::Reason reason,
-                 gcstats::Phase phase = gcstats::PHASE_MINOR_GC) JS_HAZ_GC_CALL;
+                 gcstats::PhaseKind phase = gcstats::PhaseKind::MINOR_GC) JS_HAZ_GC_CALL;
     void evictNursery(JS::gcreason::Reason reason = JS::gcreason::EVICT_NURSERY) {
-        minorGC(reason, gcstats::PHASE_EVICT_NURSERY);
+        minorGC(reason, gcstats::PhaseKind::EVICT_NURSERY);
     }
     void freeAllLifoBlocksAfterMinorGC(LifoAlloc* lifo);
 
     friend class js::GCHelperState;
     friend class MarkingValidator;
     friend class AutoTraceSession;
     friend class AutoEnterIteration;
 };
new file mode 100644
--- /dev/null
+++ b/js/src/gc/GenerateStatsPhases.py
@@ -0,0 +1,292 @@
+# 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/.
+
+# Generate graph structures for GC statistics recording.
+#
+# Stats phases are nested and form a directed acyclic graph starting
+# from a set of root phases. Importantly, a phase may appear under more
+# than one parent phase.
+#
+# For example, the following arrangement is possible:
+#
+#            +---+
+#            | A |
+#            +---+
+#              |
+#      +-------+-------+
+#      |       |       |
+#      v       v       v
+#    +---+   +---+   +---+
+#    | B |   | C |   | D |
+#    +---+   +---+   +---+
+#              |       |
+#              +---+---+
+#                  |
+#                  v
+#                +---+
+#                | E |
+#                +---+
+#
+# This graph is expanded into a tree (or really a forest) and phases
+# with multiple parents are duplicated.
+#
+# For example, the input example above would be expanded to:
+#
+#            +---+
+#            | A |
+#            +---+
+#              |
+#      +-------+-------+
+#      |       |       |
+#      v       v       v
+#    +---+   +---+   +---+
+#    | B |   | C |   | D |
+#    +---+   +---+   +---+
+#              |       |
+#              v       v
+#            +---+   +---+
+#            | E |   | E'|
+#            +---+   +---+
+
+import sys
+import collections
+
+class PhaseKind():
+    def __init__(self, name, descr, bucket, children = []):
+        self.name = name
+        self.descr = descr
+        self.bucket = bucket
+        self.children = children
+
+# The root marking phase appears in several places in the graph.
+MarkRootsPhaseKind = PhaseKind("MARK_ROOTS", "Mark Roots", 48, [
+    PhaseKind("BUFFER_GRAY_ROOTS", "Buffer Gray Roots", 49),
+    PhaseKind("MARK_CCWS", "Mark Cross Compartment Wrappers", 50),
+    PhaseKind("MARK_STACK", "Mark C and JS stacks", 51),
+    PhaseKind("MARK_RUNTIME_DATA", "Mark Runtime-wide Data", 52),
+    PhaseKind("MARK_EMBEDDING", "Mark Embedding", 53),
+    PhaseKind("MARK_COMPARTMENTS", "Mark Compartments", 54),
+])
+
+PhaseKindGraphRoots = [
+    PhaseKind("MUTATOR", "Mutator Running", 0),
+    PhaseKind("GC_BEGIN", "Begin Callback", 1),
+    PhaseKind("WAIT_BACKGROUND_THREAD", "Wait Background Thread", 2),
+    PhaseKind("MARK_DISCARD_CODE", "Mark Discard Code", 3),
+    PhaseKind("RELAZIFY_FUNCTIONS", "Relazify Functions", 4),
+    PhaseKind("PURGE", "Purge", 5),
+    PhaseKind("MARK", "Mark", 6, [
+        PhaseKind("UNMARK", "Unmark", 7),
+        MarkRootsPhaseKind,
+        PhaseKind("MARK_DELAYED", "Mark Delayed", 8),
+        ]),
+    PhaseKind("SWEEP", "Sweep", 9, [
+        PhaseKind("SWEEP_MARK", "Mark During Sweeping", 10, [
+            PhaseKind("SWEEP_MARK_TYPES", "Mark Types During Sweeping", 11),
+            PhaseKind("SWEEP_MARK_INCOMING_BLACK", "Mark Incoming Black Pointers", 12),
+            PhaseKind("SWEEP_MARK_WEAK", "Mark Weak", 13),
+            PhaseKind("SWEEP_MARK_INCOMING_GRAY", "Mark Incoming Gray Pointers", 14),
+            PhaseKind("SWEEP_MARK_GRAY", "Mark Gray", 15),
+            PhaseKind("SWEEP_MARK_GRAY_WEAK", "Mark Gray and Weak", 16)
+        ]),
+        PhaseKind("FINALIZE_START", "Finalize Start Callbacks", 17, [
+            PhaseKind("WEAK_ZONES_CALLBACK", "Per-Slice Weak Callback", 57),
+            PhaseKind("WEAK_COMPARTMENT_CALLBACK", "Per-Compartment Weak Callback", 58)
+        ]),
+        PhaseKind("SWEEP_ATOMS", "Sweep Atoms", 18),
+        PhaseKind("SWEEP_COMPARTMENTS", "Sweep Compartments", 20, [
+            PhaseKind("SWEEP_DISCARD_CODE", "Sweep Discard Code", 21),
+            PhaseKind("SWEEP_INNER_VIEWS", "Sweep Inner Views", 22),
+            PhaseKind("SWEEP_CC_WRAPPER", "Sweep Cross Compartment Wrappers", 23),
+            PhaseKind("SWEEP_BASE_SHAPE", "Sweep Base Shapes", 24),
+            PhaseKind("SWEEP_INITIAL_SHAPE", "Sweep Initial Shapes", 25),
+            PhaseKind("SWEEP_TYPE_OBJECT", "Sweep Type Objects", 26),
+            PhaseKind("SWEEP_BREAKPOINT", "Sweep Breakpoints", 27),
+            PhaseKind("SWEEP_REGEXP", "Sweep Regexps", 28),
+            PhaseKind("SWEEP_COMPRESSION", "Sweep Compression Tasks", 62),
+            PhaseKind("SWEEP_WEAKMAPS", "Sweep WeakMaps", 63),
+            PhaseKind("SWEEP_UNIQUEIDS", "Sweep Unique IDs", 64),
+            PhaseKind("SWEEP_JIT_DATA", "Sweep JIT Data", 65),
+            PhaseKind("SWEEP_WEAK_CACHES", "Sweep Weak Caches", 66),
+            PhaseKind("SWEEP_MISC", "Sweep Miscellaneous", 29),
+            PhaseKind("SWEEP_TYPES", "Sweep type information", 30, [
+                PhaseKind("SWEEP_TYPES_BEGIN", "Sweep type tables and compilations", 31),
+                PhaseKind("SWEEP_TYPES_END", "Free type arena", 32),
+            ]),
+        ]),
+        PhaseKind("SWEEP_OBJECT", "Sweep Object", 33),
+        PhaseKind("SWEEP_STRING", "Sweep String", 34),
+        PhaseKind("SWEEP_SCRIPT", "Sweep Script", 35),
+        PhaseKind("SWEEP_SCOPE", "Sweep Scope", 59),
+        PhaseKind("SWEEP_REGEXP_SHARED", "Sweep RegExpShared", 61),
+        PhaseKind("SWEEP_SHAPE", "Sweep Shape", 36),
+        PhaseKind("SWEEP_JITCODE", "Sweep JIT code", 37),
+        PhaseKind("FINALIZE_END", "Finalize End Callback", 38),
+        PhaseKind("DESTROY", "Deallocate", 39)
+        ]),
+    PhaseKind("COMPACT", "Compact", 40, [
+        PhaseKind("COMPACT_MOVE", "Compact Move", 41),
+        PhaseKind("COMPACT_UPDATE", "Compact Update", 42, [
+            MarkRootsPhaseKind,
+            PhaseKind("COMPACT_UPDATE_CELLS", "Compact Update Cells", 43),
+        ]),
+    ]),
+    PhaseKind("GC_END", "End Callback", 44),
+    PhaseKind("MINOR_GC", "All Minor GCs", 45, [
+        MarkRootsPhaseKind,
+    ]),
+    PhaseKind("EVICT_NURSERY", "Minor GCs to Evict Nursery", 46, [
+        MarkRootsPhaseKind,
+    ]),
+    PhaseKind("TRACE_HEAP", "Trace Heap", 47, [
+        MarkRootsPhaseKind,
+    ]),
+    PhaseKind("BARRIER", "Barriers", 55, [
+        PhaseKind("UNMARK_GRAY", "Unmark gray", 56),
+    ]),
+    PhaseKind("PURGE_SHAPE_TABLES", "Purge ShapeTables", 60)
+]
+
+# Make a linear list of all unique phases by performing a depth first
+# search on the phase graph starting at the roots.  This will be used to
+# generate the PhaseKind enum.
+
+def findAllPhaseKinds():
+    phases = []
+    seen = set()
+
+    def dfs(phase):
+        if phase in seen:
+            return
+        phases.append(phase)
+        seen.add(phase)
+        for child in phase.children:
+            dfs(child)
+
+    for phase in PhaseKindGraphRoots:
+        dfs(phase)
+    return phases
+
+AllPhaseKinds = findAllPhaseKinds()
+
+# Expand the DAG into a tree, duplicating phases which have more than
+# one parent.
+
+class Phase:
+    def __init__(self, phaseKind, parent, depth):
+        self.phaseKind = phaseKind
+        self.parent = parent
+        self.depth = depth
+        self.children = []
+        self.nextSibling = None
+        self.nextInPhaseKind = None
+
+def expandPhases():
+    phases = []
+    phasesForPhase = collections.defaultdict(list)
+
+    def traverse(phaseKind, parent, depth):
+        ep = Phase(phaseKind, parent, depth)
+        phases.append(ep)
+
+        # Update list of expanded phases for this phase kind.
+        if phasesForPhase[phaseKind]:
+            phasesForPhase[phaseKind][-1].nextInPhaseKind = ep
+        phasesForPhase[phaseKind].append(ep)
+
+        # Recurse over children.
+        for child in phaseKind.children:
+            child_ep = traverse(child, ep, depth + 1)
+            if ep.children:
+                ep.children[-1].nextSibling = child_ep
+            ep.children.append(child_ep)
+        return ep
+
+    for phaseKind in PhaseKindGraphRoots:
+        traverse(phaseKind, None, 0)
+
+    return phases, phasesForPhase
+
+AllPhases, PhasesForPhaseKind = expandPhases()
+
+# Name expanded phases based on phase kind name and index if there are
+# multiple expanded phases corresponding to a single phase kind.
+
+for phaseKind in AllPhaseKinds:
+    phases = PhasesForPhaseKind[phaseKind]
+    if len(phases) == 1:
+        phases[0].name = "%s" % phaseKind.name
+    else:
+        for index, xphase in enumerate(phases):
+            xphase.name = "%s_%d" % (phaseKind.name, index + 1)
+
+# Generate code.
+
+def writeList(out, items):
+    if items:
+        out.write(",\n".join("  " + item for item in items) + "\n")
+
+def writeEnumClass(out, name, type, items, extraItems):
+    items = [ "FIRST" ] + items + [ "LIMIT" ] + extraItems
+    items[1] += " = " + items[0]
+    out.write("enum class %s : %s {\n" % (name, type));
+    writeList(out, items)
+    out.write("};\n")
+
+def generateHeader(out):
+    #
+    # Generate PhaseKind enum.
+    #
+    phaseKindNames = map(lambda phaseKind: phaseKind.name, AllPhaseKinds)
+    extraPhaseKinds = [
+        "NONE = LIMIT",
+        "EXPLICIT_SUSPENSION = LIMIT",
+        "IMPLICIT_SUSPENSION"
+    ]
+    writeEnumClass(out, "PhaseKind", "uint8_t", phaseKindNames, extraPhaseKinds)
+    out.write("\n")
+
+    #
+    # Generate Phase enum.
+    #
+    expandedPhaseNames = map(lambda xphase: xphase.name, AllPhases)
+    extraPhases = [
+        "NONE = LIMIT",
+        "EXPLICIT_SUSPENSION = LIMIT",
+        "IMPLICIT_SUSPENSION"
+    ]
+    writeEnumClass(out, "Phase", "uint8_t", expandedPhaseNames, extraPhases)
+
+def generateCpp(out):
+    #
+    # Generate the PhaseKindInfo table.
+    #
+    out.write("static const PhaseKindTable phaseKinds = {\n")
+    for phaseKind in AllPhaseKinds:
+        xPhase = PhasesForPhaseKind[phaseKind][0]
+        out.write("    /* PhaseKind::%s */ PhaseKindInfo { Phase::%s, %d },\n" %
+                  (phaseKind.name, xPhase.name, phaseKind.bucket))
+    out.write("};\n")
+    out.write("\n")
+
+    #
+    # Generate the PhaseInfo tree.
+    #
+    def name(xphase):
+        return "Phase::" + xphase.name if xphase else "Phase::NONE"
+
+    out.write("static const PhaseTable phases = {\n")
+    for xphase in AllPhases:
+        firstChild = xphase.children[0] if xphase.children else None
+        phaseKind = xphase.phaseKind
+        out.write("    /* %s */ PhaseInfo { %s, %s, %s, %s, PhaseKind::%s, %d, \"%s\" },\n" %
+                  (name(xphase),
+                   name(xphase.parent),
+                   name(firstChild),
+                   name(xphase.nextSibling),
+                   name(xphase.nextInPhaseKind),
+                   phaseKind.name,
+                   xphase.depth,
+                   phaseKind.descr))
+    out.write("};\n")
--- a/js/src/gc/Marking.cpp
+++ b/js/src/gc/Marking.cpp
@@ -2518,17 +2518,17 @@ GCMarker::markDelayedChildren(Arena* are
      * allocatedDuringIncremental flag if we continue marking.
      */
 }
 
 bool
 GCMarker::markDelayedChildren(SliceBudget& budget)
 {
     GCRuntime& gc = runtime()->gc;
-    gcstats::AutoPhase ap(gc.stats(), gc.state() == State::Mark, gcstats::PHASE_MARK_DELAYED);
+    gcstats::AutoPhase ap(gc.stats(), gc.state() == State::Mark, gcstats::PhaseKind::MARK_DELAYED);
 
     MOZ_ASSERT(unmarkedArenaStackTop);
     do {
         /*
          * If marking gets delayed at the same arena again, we must repeat
          * marking of its things. For that we pop arena from the stack and
          * clear its hasDelayedMarking flag before we begin the marking.
          */
@@ -3401,18 +3401,18 @@ TypedUnmarkGrayCellRecursively(T* t)
 {
     MOZ_ASSERT(t);
 
     JSRuntime* rt = t->runtimeFromActiveCooperatingThread();
     MOZ_ASSERT(!JS::CurrentThreadIsHeapCollecting());
     MOZ_ASSERT(!JS::CurrentThreadIsHeapCycleCollecting());
 
     UnmarkGrayTracer unmarker(rt);
-    gcstats::AutoPhase outerPhase(rt->gc.stats(), gcstats::PHASE_BARRIER);
-    gcstats::AutoPhase innerPhase(rt->gc.stats(), gcstats::PHASE_UNMARK_GRAY);
+    gcstats::AutoPhase outerPhase(rt->gc.stats(), gcstats::PhaseKind::BARRIER);
+    gcstats::AutoPhase innerPhase(rt->gc.stats(), gcstats::PhaseKind::UNMARK_GRAY);
     unmarker.unmark(JS::GCCellPtr(t, MapTypeToTraceKind<T>::kind));
     return unmarker.unmarkedAny;
 }
 
 struct UnmarkGrayCellRecursivelyFunctor {
     template <typename T> bool operator()(T* t) { return TypedUnmarkGrayCellRecursively(t); }
 };
 
--- a/js/src/gc/Nursery.cpp
+++ b/js/src/gc/Nursery.cpp
@@ -733,17 +733,17 @@ js::Nursery::doCollection(JS::gcreason::
     maybeEndProfile(ProfileKey::TraceGenericEntries);
 
     maybeStartProfile(ProfileKey::MarkRuntime);
     rt->gc.traceRuntimeForMinorGC(&mover, session.lock);
     maybeEndProfile(ProfileKey::MarkRuntime);
 
     maybeStartProfile(ProfileKey::MarkDebugger);
     {
-        gcstats::AutoPhase ap(rt->gc.stats(), gcstats::PHASE_MARK_ROOTS);
+        gcstats::AutoPhase ap(rt->gc.stats(), gcstats::PhaseKind::MARK_ROOTS);
         Debugger::traceAllForMovingGC(&mover);
     }
     maybeEndProfile(ProfileKey::MarkDebugger);
 
     maybeStartProfile(ProfileKey::ClearNewObjectCache);
     rt->caches().newObjectCache.clearNurseryObjects(rt);
     maybeEndProfile(ProfileKey::ClearNewObjectCache);
 
--- a/js/src/gc/RootMarking.cpp
+++ b/js/src/gc/RootMarking.cpp
@@ -254,80 +254,80 @@ PropertyDescriptor::trace(JSTracer* trc)
 void
 js::gc::GCRuntime::traceRuntimeForMajorGC(JSTracer* trc, AutoLockForExclusiveAccess& lock)
 {
     // FinishRoots will have asserted that every root that we do not expect
     // is gone, so we can simply skip traceRuntime here.
     if (rt->isBeingDestroyed())
         return;
 
-    gcstats::AutoPhase ap(stats(), gcstats::PHASE_MARK_ROOTS);
+    gcstats::AutoPhase ap(stats(), gcstats::PhaseKind::MARK_ROOTS);
     if (rt->atomsCompartment(lock)->zone()->isCollecting())
         traceRuntimeAtoms(trc, lock);
     JSCompartment::traceIncomingCrossCompartmentEdgesForZoneGC(trc);
     traceRuntimeCommon(trc, MarkRuntime, lock);
 }
 
 void
 js::gc::GCRuntime::traceRuntimeForMinorGC(JSTracer* trc, AutoLockForExclusiveAccess& lock)
 {
     // Note that we *must* trace the runtime during the SHUTDOWN_GC's minor GC
     // despite having called FinishRoots already. This is because FinishRoots
     // does not clear the crossCompartmentWrapper map. It cannot do this
     // because Proxy's trace for CrossCompartmentWrappers asserts presence in
     // the map. And we can reach its trace function despite having finished the
     // roots via the edges stored by the pre-barrier verifier when we finish
     // the verifier for the last time.
-    gcstats::AutoPhase ap(stats(), gcstats::PHASE_MARK_ROOTS);
+    gcstats::AutoPhase ap(stats(), gcstats::PhaseKind::MARK_ROOTS);
 
     jit::JitRuntime::TraceJitcodeGlobalTableForMinorGC(trc);
 
     traceRuntimeCommon(trc, TraceRuntime, lock);
 }
 
 void
 js::TraceRuntime(JSTracer* trc)
 {
     MOZ_ASSERT(!trc->isMarkingTracer());
 
     JSRuntime* rt = trc->runtime();
     EvictAllNurseries(rt);
     AutoPrepareForTracing prep(TlsContext.get(), WithAtoms);
-    gcstats::AutoPhase ap(rt->gc.stats(), gcstats::PHASE_TRACE_HEAP);
+    gcstats::AutoPhase ap(rt->gc.stats(), gcstats::PhaseKind::TRACE_HEAP);
     rt->gc.traceRuntime(trc, prep.session().lock);
 }
 
 void
 js::gc::GCRuntime::traceRuntime(JSTracer* trc, AutoLockForExclusiveAccess& lock)
 {
     MOZ_ASSERT(!rt->isBeingDestroyed());
 
-    gcstats::AutoPhase ap(stats(), gcstats::PHASE_MARK_ROOTS);
+    gcstats::AutoPhase ap(stats(), gcstats::PhaseKind::MARK_ROOTS);
     traceRuntimeAtoms(trc, lock);
     traceRuntimeCommon(trc, TraceRuntime, lock);
 }
 
 void
 js::gc::GCRuntime::traceRuntimeAtoms(JSTracer* trc, AutoLockForExclusiveAccess& lock)
 {
-    gcstats::AutoPhase ap(stats(), gcstats::PHASE_MARK_RUNTIME_DATA);
+    gcstats::AutoPhase ap(stats(), gcstats::PhaseKind::MARK_RUNTIME_DATA);
     TracePermanentAtoms(trc);
     TraceAtoms(trc, lock);
     TraceWellKnownSymbols(trc);
     jit::JitRuntime::Trace(trc, lock);
 }
 
 void
 js::gc::GCRuntime::traceRuntimeCommon(JSTracer* trc, TraceOrMarkRuntime traceOrMark,
                                       AutoLockForExclusiveAccess& lock)
 {
     MOZ_ASSERT(!TlsContext.get()->suppressGC);
 
     {
-        gcstats::AutoPhase ap(stats(), gcstats::PHASE_MARK_STACK);
+        gcstats::AutoPhase ap(stats(), gcstats::PhaseKind::MARK_STACK);
 
         JSContext* cx = TlsContext.get();
         for (const CooperatingContext& target : rt->cooperatingContexts()) {
             // Trace active interpreter and JIT stack roots.
             TraceInterpreterActivations(cx, target, trc);
             jit::TraceJitActivations(cx, target, trc);
             wasm::TraceActivations(cx, target, trc);
 
@@ -365,17 +365,17 @@ js::gc::GCRuntime::traceRuntimeCommon(JS
     // Trace the Gecko Profiler.
     rt->geckoProfiler().trace(trc);
 
     // Trace helper thread roots.
     HelperThreadState().trace(trc);
 
     // Trace the embedding's black and gray roots.
     if (!JS::CurrentThreadIsHeapMinorCollecting()) {
-        gcstats::AutoPhase ap(stats(), gcstats::PHASE_MARK_EMBEDDING);
+        gcstats::AutoPhase ap(stats(), gcstats::PhaseKind::MARK_EMBEDDING);
 
         /*
          * The embedding can register additional roots here.
          *
          * We don't need to trace these in a minor GC because all pointers into
          * the nursery should be in the store buffer, and we want to avoid the
          * time taken to trace all these roots.
          */
@@ -426,17 +426,17 @@ js::gc::GCRuntime::finishRoots()
 #ifdef DEBUG
     // The nsWrapperCache may not be empty before our shutdown GC, so we have
     // to skip that table when verifying that we are fully unrooted.
     auto prior = grayRootTracer;
     grayRootTracer = Callback<JSTraceDataOp>(nullptr, nullptr);
 
     AssertNoRootsTracer trc(rt, TraceWeakMapKeysValues);
     AutoPrepareForTracing prep(TlsContext.get(), WithAtoms);
-    gcstats::AutoPhase ap(rt->gc.stats(), gcstats::PHASE_TRACE_HEAP);
+    gcstats::AutoPhase ap(rt->gc.stats(), gcstats::PhaseKind::TRACE_HEAP);
     traceRuntime(&trc, prep.session().lock);
 
     // Restore the wrapper tracing so that we leak instead of leaving dangling
     // pointers.
     grayRootTracer = prior;
 #endif // DEBUG
 }
 
@@ -476,17 +476,17 @@ void
 js::gc::GCRuntime::bufferGrayRoots()
 {
     // Precondition: the state has been reset to "unused" after the last GC
     //               and the zone's buffers have been cleared.
     MOZ_ASSERT(grayBufferState == GrayBufferState::Unused);
     for (GCZonesIter zone(rt); !zone.done(); zone.next())
         MOZ_ASSERT(zone->gcGrayRoots().empty());
 
-    gcstats::AutoPhase ap(stats(), gcstats::PHASE_BUFFER_GRAY_ROOTS);
+    gcstats::AutoPhase ap(stats(), gcstats::PhaseKind::BUFFER_GRAY_ROOTS);
 
     BufferGrayRootsTracer grayBufferer(rt);
     if (JSTraceDataOp op = grayRootTracer.op)
         (*op)(&grayBufferer, grayRootTracer.data);
 
     // Propagate the failure flag from the marker to the runtime.
     if (grayBufferer.failed()) {
       grayBufferState = GrayBufferState::Failed;
--- a/js/src/gc/Statistics.cpp
+++ b/js/src/gc/Statistics.cpp
@@ -26,33 +26,34 @@
 #include "vm/Runtime.h"
 #include "vm/Time.h"
 
 using namespace js;
 using namespace js::gc;
 using namespace js::gcstats;
 
 using mozilla::DebugOnly;
+using mozilla::EnumeratedArray;
 using mozilla::IntegerRange;
 using mozilla::PodArrayZero;
 using mozilla::PodZero;
 using mozilla::TimeStamp;
 using mozilla::TimeDuration;
 
 /*
  * If this fails, then you can either delete this assertion and allow all
  * larger-numbered reasons to pile up in the last telemetry bucket, or switch
  * to GC_REASON_3 and bump the max value.
  */
 JS_STATIC_ASSERT(JS::gcreason::NUM_TELEMETRY_REASONS >= JS::gcreason::NUM_REASONS);
 
-static inline decltype(mozilla::MakeEnumeratedRange(PHASE_FIRST, PHASE_LIMIT))
-AllPhases()
+static inline decltype(mozilla::MakeEnumeratedRange(PhaseKind::FIRST, PhaseKind::LIMIT))
+AllPhaseKinds()
 {
-    return mozilla::MakeEnumeratedRange(PHASE_FIRST, PHASE_LIMIT);
+    return mozilla::MakeEnumeratedRange(PhaseKind::FIRST, PhaseKind::LIMIT);
 }
 
 const char*
 js::gcstats::ExplainInvocationKind(JSGCInvocationKind gckind)
 {
     MOZ_ASSERT(gckind == GC_NORMAL || gckind == GC_SHRINK);
     if (gckind == GC_NORMAL)
          return "Normal";
@@ -85,229 +86,102 @@ js::gcstats::ExplainAbortReason(gc::Abor
         GC_ABORT_REASONS(SWITCH_REASON)
 
         default:
           MOZ_CRASH("bad GC abort reason");
 #undef SWITCH_REASON
     }
 }
 
+struct PhaseKindInfo
+{
+    Phase firstPhase;
+    uint8_t telemetryBucket;
+};
+
+// PhaseInfo objects form a tree.
+struct PhaseInfo
+{
+    Phase parent;
+    Phase firstChild;
+    Phase nextSibling;
+    Phase nextInPhase;
+    PhaseKind phaseKind;
+    uint8_t depth;
+    const char* name;
+};
+
+// A table of ExpandePhaseInfo indexed by Phase.
+using PhaseTable = EnumeratedArray<Phase, Phase::LIMIT, PhaseInfo>;
+
+// A table of PhaseKindInfo indexed by Phase.
+using PhaseKindTable = EnumeratedArray<PhaseKind, PhaseKind::LIMIT, PhaseKindInfo>;
+
+#include "gc/StatsPhasesGenerated.cpp"
+
 static double
 t(TimeDuration duration)
 {
     return duration.ToMilliseconds();
 }
 
-struct PhaseInfo
+Phase
+Statistics::currentPhase() const
 {
-    Phase index;
-    const char* name;
-    Phase parent;
-    uint8_t telemetryBucket;
-};
-
-// The zeroth entry in the timing arrays is used for phases that have a
-// unique lineage.
-static const size_t PHASE_DAG_NONE = 0;
-
-// These are really just fields of PhaseInfo, but I have to initialize them
-// programmatically, which prevents making phases[] const. (And marking these
-// fields mutable does not work on Windows; the whole thing gets created in
-// read-only memory anyway.)
-struct ExtraPhaseInfo
-{
-    // Depth in the tree of each phase type
-    size_t depth;
+    return phaseNestingDepth ? phaseNesting[phaseNestingDepth - 1] : Phase::NONE;
+}
 
-    // Index into the set of parallel arrays of timing data, for parents with
-    // at least one multi-parented child
-    size_t dagSlot;
-
-    ExtraPhaseInfo() : depth(0), dagSlot(0) {}
-};
-
-static const Phase PHASE_NO_PARENT = PHASE_LIMIT;
+PhaseKind
+Statistics::currentPhaseKind() const
+{
+    // Public API to get the current phase.  Return the current phase,
+    // suppressing the synthetic PhaseKind::MUTATOR phase.
 
-struct DagChildEdge {
-    Phase parent;
-    Phase child;
-} dagChildEdges[] = {
-    { PHASE_MARK, PHASE_MARK_ROOTS },
-    { PHASE_MINOR_GC, PHASE_MARK_ROOTS },
-    { PHASE_TRACE_HEAP, PHASE_MARK_ROOTS },
-    { PHASE_EVICT_NURSERY, PHASE_MARK_ROOTS },
-    { PHASE_COMPACT_UPDATE, PHASE_MARK_ROOTS }
-};
-
-/*
- * Note that PHASE_MUTATOR never has any child phases. If beginPhase is called
- * while PHASE_MUTATOR is active, it will automatically be suspended and
- * resumed when the phase stack is next empty. Timings for these phases are
- * thus exclusive of any other phase.
- */
+    Phase phase = currentPhase();
+    MOZ_ASSERT_IF(phase == Phase::MUTATOR, phaseNestingDepth == 1);
+    if (phase == Phase::NONE || phase == Phase::MUTATOR)
+        return PhaseKind::NONE;
 
-static const PhaseInfo phases[] = {
-    { PHASE_MUTATOR, "Mutator Running", PHASE_NO_PARENT, 0 },
-    { PHASE_GC_BEGIN, "Begin Callback", PHASE_NO_PARENT, 1 },
-    { PHASE_WAIT_BACKGROUND_THREAD, "Wait Background Thread", PHASE_NO_PARENT, 2 },
-    { PHASE_MARK_DISCARD_CODE, "Mark Discard Code", PHASE_NO_PARENT, 3 },
-    { PHASE_RELAZIFY_FUNCTIONS, "Relazify Functions", PHASE_NO_PARENT, 4 },
-    { PHASE_PURGE, "Purge", PHASE_NO_PARENT, 5 },
-    { PHASE_MARK, "Mark", PHASE_NO_PARENT, 6 },
-        { PHASE_UNMARK, "Unmark", PHASE_MARK, 7 },
-        /* PHASE_MARK_ROOTS */
-        { PHASE_MARK_DELAYED, "Mark Delayed", PHASE_MARK, 8 },
-    { PHASE_SWEEP, "Sweep", PHASE_NO_PARENT, 9 },
-        { PHASE_SWEEP_MARK, "Mark During Sweeping", PHASE_SWEEP, 10 },
-            { PHASE_SWEEP_MARK_TYPES, "Mark Types During Sweeping", PHASE_SWEEP_MARK, 11 },
-            { PHASE_SWEEP_MARK_INCOMING_BLACK, "Mark Incoming Black Pointers", PHASE_SWEEP_MARK, 12 },
-            { PHASE_SWEEP_MARK_WEAK, "Mark Weak", PHASE_SWEEP_MARK, 13 },
-            { PHASE_SWEEP_MARK_INCOMING_GRAY, "Mark Incoming Gray Pointers", PHASE_SWEEP_MARK, 14 },
-            { PHASE_SWEEP_MARK_GRAY, "Mark Gray", PHASE_SWEEP_MARK, 15 },
-            { PHASE_SWEEP_MARK_GRAY_WEAK, "Mark Gray and Weak", PHASE_SWEEP_MARK, 16 },
-        { PHASE_FINALIZE_START, "Finalize Start Callbacks", PHASE_SWEEP, 17 },
-            { PHASE_WEAK_ZONES_CALLBACK, "Per-Slice Weak Callback", PHASE_FINALIZE_START, 57 },
-            { PHASE_WEAK_COMPARTMENT_CALLBACK, "Per-Compartment Weak Callback", PHASE_FINALIZE_START, 58 },
-        { PHASE_SWEEP_ATOMS, "Sweep Atoms", PHASE_SWEEP, 18 },
-        { PHASE_SWEEP_COMPARTMENTS, "Sweep Compartments", PHASE_SWEEP, 20 },
-            { PHASE_SWEEP_DISCARD_CODE, "Sweep Discard Code", PHASE_SWEEP_COMPARTMENTS, 21 },
-            { PHASE_SWEEP_INNER_VIEWS, "Sweep Inner Views", PHASE_SWEEP_COMPARTMENTS, 22 },
-            { PHASE_SWEEP_CC_WRAPPER, "Sweep Cross Compartment Wrappers", PHASE_SWEEP_COMPARTMENTS, 23 },
-            { PHASE_SWEEP_BASE_SHAPE, "Sweep Base Shapes", PHASE_SWEEP_COMPARTMENTS, 24 },
-            { PHASE_SWEEP_INITIAL_SHAPE, "Sweep Initial Shapes", PHASE_SWEEP_COMPARTMENTS, 25 },
-            { PHASE_SWEEP_TYPE_OBJECT, "Sweep Type Objects", PHASE_SWEEP_COMPARTMENTS, 26 },
-            { PHASE_SWEEP_BREAKPOINT, "Sweep Breakpoints", PHASE_SWEEP_COMPARTMENTS, 27 },
-            { PHASE_SWEEP_REGEXP, "Sweep Regexps", PHASE_SWEEP_COMPARTMENTS, 28 },
-            { PHASE_SWEEP_COMPRESSION, "Sweep Compression Tasks", PHASE_SWEEP_COMPARTMENTS, 62 },
-            { PHASE_SWEEP_WEAKMAPS, "Sweep WeakMaps", PHASE_SWEEP_COMPARTMENTS, 63 },
-            { PHASE_SWEEP_UNIQUEIDS, "Sweep Unique IDs", PHASE_SWEEP_COMPARTMENTS, 64 },
-            { PHASE_SWEEP_JIT_DATA, "Sweep JIT Data", PHASE_SWEEP_COMPARTMENTS, 65 },
-            { PHASE_SWEEP_WEAK_CACHES, "Sweep Weak Caches", PHASE_SWEEP_COMPARTMENTS, 66 },
-            { PHASE_SWEEP_MISC, "Sweep Miscellaneous", PHASE_SWEEP_COMPARTMENTS, 29 },
-            { PHASE_SWEEP_TYPES, "Sweep type information", PHASE_SWEEP_COMPARTMENTS, 30 },
-                { PHASE_SWEEP_TYPES_BEGIN, "Sweep type tables and compilations", PHASE_SWEEP_TYPES, 31 },
-                { PHASE_SWEEP_TYPES_END, "Free type arena", PHASE_SWEEP_TYPES, 32 },
-        { PHASE_SWEEP_OBJECT, "Sweep Object", PHASE_SWEEP, 33 },
-        { PHASE_SWEEP_STRING, "Sweep String", PHASE_SWEEP, 34 },
-        { PHASE_SWEEP_SCRIPT, "Sweep Script", PHASE_SWEEP, 35 },
-        { PHASE_SWEEP_SCOPE, "Sweep Scope", PHASE_SWEEP, 59 },
-        { PHASE_SWEEP_REGEXP_SHARED, "Sweep RegExpShared", PHASE_SWEEP, 61 },
-        { PHASE_SWEEP_SHAPE, "Sweep Shape", PHASE_SWEEP, 36 },
-        { PHASE_SWEEP_JITCODE, "Sweep JIT code", PHASE_SWEEP, 37 },
-        { PHASE_FINALIZE_END, "Finalize End Callback", PHASE_SWEEP, 38 },
-        { PHASE_DESTROY, "Deallocate", PHASE_SWEEP, 39 },
-    { PHASE_COMPACT, "Compact", PHASE_NO_PARENT, 40 },
-        { PHASE_COMPACT_MOVE, "Compact Move", PHASE_COMPACT, 41 },
-        { PHASE_COMPACT_UPDATE, "Compact Update", PHASE_COMPACT, 42 },
-            /* PHASE_MARK_ROOTS */
-            { PHASE_COMPACT_UPDATE_CELLS, "Compact Update Cells", PHASE_COMPACT_UPDATE, 43 },
-    { PHASE_GC_END, "End Callback", PHASE_NO_PARENT, 44 },
-    { PHASE_MINOR_GC, "All Minor GCs", PHASE_NO_PARENT, 45 },
-        /* PHASE_MARK_ROOTS */
-    { PHASE_EVICT_NURSERY, "Minor GCs to Evict Nursery", PHASE_NO_PARENT, 46 },
-        /* PHASE_MARK_ROOTS */
-    { PHASE_TRACE_HEAP, "Trace Heap", PHASE_NO_PARENT, 47 },
-        /* PHASE_MARK_ROOTS */
-    { PHASE_BARRIER, "Barriers", PHASE_NO_PARENT, 55 },
-        { PHASE_UNMARK_GRAY, "Unmark gray", PHASE_BARRIER, 56 },
-    { PHASE_MARK_ROOTS, "Mark Roots", PHASE_MULTI_PARENTS, 48 },
-        { PHASE_BUFFER_GRAY_ROOTS, "Buffer Gray Roots", PHASE_MARK_ROOTS, 49 },
-        { PHASE_MARK_CCWS, "Mark Cross Compartment Wrappers", PHASE_MARK_ROOTS, 50 },
-        { PHASE_MARK_STACK, "Mark C and JS stacks", PHASE_MARK_ROOTS, 51 },
-        { PHASE_MARK_RUNTIME_DATA, "Mark Runtime-wide Data", PHASE_MARK_ROOTS, 52 },
-        { PHASE_MARK_EMBEDDING, "Mark Embedding", PHASE_MARK_ROOTS, 53 },
-        { PHASE_MARK_COMPARTMENTS, "Mark Compartments", PHASE_MARK_ROOTS, 54 },
-    { PHASE_PURGE_SHAPE_TABLES, "Purge ShapeTables", PHASE_NO_PARENT, 60 },
+    return phases[phase].phaseKind;
+}
 
-    { PHASE_LIMIT, nullptr, PHASE_NO_PARENT, 66 }
-
-    // The current number of telemetryBuckets is equal to the value for
-    // PHASE_LIMIT. If you insert new phases somewhere, start at that number and
-    // count up. Do not change any existing numbers.
-};
-
-static mozilla::EnumeratedArray<Phase, PHASE_LIMIT, ExtraPhaseInfo> phaseExtra;
-
-// Mapping from all nodes with a multi-parented child to a Vector of all
-// multi-parented children and their descendants. (Single-parented children will
-// not show up in this list.)
-static mozilla::Vector<Phase, 0, SystemAllocPolicy> dagDescendants[Statistics::NumTimingArrays];
+Phase
+Statistics::lookupChildPhase(PhaseKind phaseKind) const
+{
+    if (phaseKind == PhaseKind::IMPLICIT_SUSPENSION)
+        return Phase::IMPLICIT_SUSPENSION;
+    if (phaseKind == PhaseKind::EXPLICIT_SUSPENSION)
+        return Phase::EXPLICIT_SUSPENSION;
 
-// Preorder iterator over all phases in the expanded tree. Positions are
-// returned as <phase,dagSlot> pairs (dagSlot will be zero aka PHASE_DAG_NONE
-// for the top nodes with a single path from the parent, and 1 or more for
-// nodes in multiparented subtrees).
-struct AllPhaseIterator {
-    // If 'descendants' is empty, the current Phase position.
-    int current;
-
-    // The depth of the current multiparented node that we are processing, or
-    // zero if we are pointing to the top portion of the tree.
-    int baseLevel;
+    MOZ_ASSERT(phaseKind < PhaseKind::LIMIT);
 
-    // When looking at multiparented descendants, the dag slot (index into
-    // PhaseTimeTables) containing the entries for the current parent.
-    size_t activeSlot;
-
-    // When iterating over a multiparented subtree, the list of (remaining)
-    // subtree nodes.
-    mozilla::Vector<Phase, 0, SystemAllocPolicy>::Range descendants;
-
-    explicit AllPhaseIterator()
-      : current(0)
-      , baseLevel(0)
-      , activeSlot(PHASE_DAG_NONE)
-      , descendants(dagDescendants[PHASE_DAG_NONE].all()) /* empty range */
-    {
+    // Most phases only correspond to a single expanded phase so check for that
+    // first.
+    Phase phase = phaseKinds[phaseKind].firstPhase;
+    if (phases[phase].nextInPhase == Phase::NONE) {
+        MOZ_ASSERT(phases[phase].parent == currentPhase());
+        return phase;
     }
 
-    void get(Phase* phase, size_t* dagSlot, int* level = nullptr) {
-        MOZ_ASSERT(!done());
-        *dagSlot = activeSlot;
-        *phase = descendants.empty() ? Phase(current) : descendants.front();
-        if (level)
-            *level = phaseExtra[*phase].depth + baseLevel;
+    // Otherwise search all expanded phases that correspond to the required
+    // phase to find the one whose parent is the current expanded phase.
+    Phase parent = currentPhase();
+    while (phases[phase].parent != parent) {
+        phase = phases[phase].nextInPhase;
+        MOZ_ASSERT(phase != Phase::NONE);
     }
 
-    void advance() {
-        MOZ_ASSERT(!done());
-
-        if (!descendants.empty()) {
-            // Currently iterating over a multiparented subtree.
-            descendants.popFront();
-            if (!descendants.empty())
-                return;
-
-            // Just before leaving the last child, reset the iterator to look
-            // at "main" phases (in PHASE_DAG_NONE) instead of multiparented
-            // subtree phases.
-            ++current;
-            activeSlot = PHASE_DAG_NONE;
-            baseLevel = 0;
-            return;
-        }
+    return phase;
+}
 
-        auto phase = Phase(current);
-        if (phaseExtra[phase].dagSlot != PHASE_DAG_NONE) {
-            // The current phase has a shared subtree. Load them up into
-            // 'descendants' and advance to the first child.
-            activeSlot = phaseExtra[phase].dagSlot;
-            descendants = dagDescendants[activeSlot].all();
-            MOZ_ASSERT(!descendants.empty());
-            baseLevel += phaseExtra[phase].depth + 1;
-            return;
-        }
-
-        ++current;
-    }
-
-    bool done() const {
-        return phases[current].parent == PHASE_MULTI_PARENTS;
-    }
-};
+inline decltype(mozilla::MakeEnumeratedRange(Phase::FIRST, Phase::LIMIT))
+AllPhases()
+{
+    return mozilla::MakeEnumeratedRange(Phase::FIRST, Phase::LIMIT);
+}
 
 void
 Statistics::gcDuration(TimeDuration* total, TimeDuration* maxPause) const
 {
     *total = *maxPause = 0;
     for (auto& slice : slices_) {
         *total += slice.duration();
         if (slice.duration() > *maxPause)
@@ -325,62 +199,55 @@ Statistics::sccDurations(TimeDuration* t
         *total += sccTimes[i];
         *maxPause = Max(*maxPause, sccTimes[i]);
     }
 }
 
 typedef Vector<UniqueChars, 8, SystemAllocPolicy> FragmentVector;
 
 static UniqueChars
-Join(const FragmentVector& fragments, const char* separator = "") {
+Join(const FragmentVector& fragments, const char* separator = "")
+{
     const size_t separatorLength = strlen(separator);
     size_t length = 0;
     for (size_t i = 0; i < fragments.length(); ++i) {
         length += fragments[i] ? strlen(fragments[i].get()) : 0;
         if (i < (fragments.length() - 1))
             length += separatorLength;
     }
 
     char* joined = js_pod_malloc<char>(length + 1);
+    if (!joined)
+        return UniqueChars();
+
     joined[length] = '\0';
-
     char* cursor = joined;
     for (size_t i = 0; i < fragments.length(); ++i) {
         if (fragments[i])
             strcpy(cursor, fragments[i].get());
         cursor += fragments[i] ? strlen(fragments[i].get()) : 0;
         if (i < (fragments.length() - 1)) {
             if (separatorLength)
                 strcpy(cursor, separator);
             cursor += separatorLength;
         }
     }
 
     return UniqueChars(joined);
 }
 
 static TimeDuration
-SumChildTimes(size_t phaseSlot, Phase phase, const Statistics::PhaseTimeTable& phaseTimes)
+SumChildTimes(Phase phase, const Statistics::PhaseTimeTable& phaseTimes)
 {
-    // Sum the contributions from single-parented children.
     TimeDuration total = 0;
-    size_t depth = phaseExtra[phase].depth;
-    for (unsigned i = phase + 1; i < PHASE_LIMIT && phaseExtra[Phase(i)].depth > depth; i++) {
-        if (phases[i].parent == phase)
-            total += phaseTimes[phaseSlot][Phase(i)];
-    }
-
-    // Sum the contributions from multi-parented children.
-    size_t dagSlot = phaseExtra[phase].dagSlot;
-    MOZ_ASSERT(dagSlot <= Statistics::MaxMultiparentPhases - 1);
-    if (dagSlot != PHASE_DAG_NONE) {
-        for (auto edge : dagChildEdges) {
-            if (edge.parent == phase)
-                total += phaseTimes[dagSlot][edge.child];
-        }
+    for (phase = phases[phase].firstChild;
+         phase != Phase::NONE;
+         phase = phases[phase].nextSibling)
+    {
+        total += phaseTimes[phase];
     }
     return total;
 }
 
 UniqueChars
 Statistics::formatCompactSliceMessage() const
 {
     // Skip if we OOM'ed.
@@ -465,25 +332,22 @@ Statistics::formatCompactSummaryMessage(
 
 UniqueChars
 Statistics::formatCompactSlicePhaseTimes(const PhaseTimeTable& phaseTimes) const
 {
     static const TimeDuration MaxUnaccountedTime = TimeDuration::FromMicroseconds(100);
 
     FragmentVector fragments;
     char buffer[128];
-    for (AllPhaseIterator iter; !iter.done(); iter.advance()) {
-        Phase phase;
-        size_t dagSlot;
-        int level;
-        iter.get(&phase, &dagSlot, &level);
+    for (auto phase : AllPhases()) {
+        DebugOnly<uint8_t> level = phases[phase].depth;
         MOZ_ASSERT(level < 4);
 
-        TimeDuration ownTime = phaseTimes[dagSlot][phase];
-        TimeDuration childTime = SumChildTimes(dagSlot, phase, phaseTimes);
+        TimeDuration ownTime = phaseTimes[phase];
+        TimeDuration childTime = SumChildTimes(phase, phaseTimes);
         if (ownTime > MaxUnaccountedTime) {
             SprintfLiteral(buffer, "%s: %.3fms", phases[phase].name, t(ownTime));
             if (!fragments.append(DuplicateString(buffer)))
                 return UniqueChars(nullptr);
 
             if (childTime && (ownTime - childTime) > MaxUnaccountedTime) {
                 MOZ_ASSERT(level < 3);
                 SprintfLiteral(buffer, "%s: %.3fms", "Other", t(ownTime - childTime));
@@ -592,24 +456,20 @@ Statistics::formatDetailedSliceDescripti
 
 UniqueChars
 Statistics::formatDetailedPhaseTimes(const PhaseTimeTable& phaseTimes) const
 {
     static const TimeDuration MaxUnaccountedChildTime = TimeDuration::FromMicroseconds(50);
 
     FragmentVector fragments;
     char buffer[128];
-    for (AllPhaseIterator iter; !iter.done(); iter.advance()) {
-        Phase phase;
-        size_t dagSlot;
-        int level;
-        iter.get(&phase, &dagSlot, &level);
-
-        TimeDuration ownTime = phaseTimes[dagSlot][phase];
-        TimeDuration childTime = SumChildTimes(dagSlot, phase, phaseTimes);
+    for (auto phase : AllPhases()) {
+        uint8_t level = phases[phase].depth;
+        TimeDuration ownTime = phaseTimes[phase];
+        TimeDuration childTime = SumChildTimes(phase, phaseTimes);
         if (!ownTime.IsZero()) {
             SprintfLiteral(buffer, "      %*s: %.3fms\n",
                            level * 2, phases[phase].name, t(ownTime));
             if (!fragments.append(DuplicateString(buffer)))
                 return UniqueChars(nullptr);
 
             if (childTime && (ownTime - childTime) > MaxUnaccountedChildTime) {
                 SprintfLiteral(buffer, "      %*s: %.3fms\n",
@@ -777,38 +637,33 @@ SanitizeJsonKey(const char* const buffer
     }
 
     return UniqueChars(mut);
 }
 
 void
 Statistics::formatJsonPhaseTimes(const PhaseTimeTable& phaseTimes, JSONPrinter& json) const
 {
-    for (AllPhaseIterator iter; !iter.done(); iter.advance()) {
-        Phase phase;
-        size_t dagSlot;
-        iter.get(&phase, &dagSlot);
-
+    for (auto phase : AllPhases()) {
         UniqueChars name = SanitizeJsonKey(phases[phase].name);
         if (!name)
             json.outOfMemory();
-        TimeDuration ownTime = phaseTimes[dagSlot][phase];
+        TimeDuration ownTime = phaseTimes[phase];
         if (!ownTime.IsZero())
             json.property(name.get(), ownTime, JSONPrinter::MILLISECONDS);
     }
 }
 
 Statistics::Statistics(JSRuntime* rt)
   : runtime(rt),
     fp(nullptr),
     nonincrementalReason_(gc::AbortReason::None),
     preBytes(0),
     maxPauseInInterval(0),
     phaseNestingDepth(0),
-    activeDagSlot(PHASE_DAG_NONE),
     suspended(0),
     sliceCallback(nullptr),
     nurseryCollectionCallback(nullptr),
     aborted(false),
     enableProfiling_(false),
     sliceCount_(0)
 {
     for (auto& count : counts)
@@ -848,64 +703,47 @@ Statistics::~Statistics()
     if (fp && fp != stdout && fp != stderr)
         fclose(fp);
 }
 
 /* static */ bool
 Statistics::initialize()
 {
 #ifdef DEBUG
+    // Sanity check generated tables.
     for (auto i : AllPhases()) {
-        MOZ_ASSERT(phases[i].index == i);
-        for (auto j : AllPhases())
-            MOZ_ASSERT_IF(i != j, phases[i].telemetryBucket != phases[j].telemetryBucket);
+        auto parent = phases[i].parent;
+        if (parent != Phase::NONE) {
+            MOZ_ASSERT(phases[i].depth == phases[parent].depth + 1);
+        }
+        auto firstChild = phases[i].firstChild;
+        if (firstChild != Phase::NONE) {
+            MOZ_ASSERT(i == phases[firstChild].parent);
+            MOZ_ASSERT(phases[i].depth == phases[firstChild].depth - 1);
+        }
+        auto nextSibling = phases[i].nextSibling;
+        if (nextSibling != Phase::NONE) {
+            MOZ_ASSERT(parent == phases[nextSibling].parent);
+            MOZ_ASSERT(phases[i].depth == phases[nextSibling].depth);
+        }
+        auto nextInPhase = phases[i].nextInPhase;
+        if (nextInPhase != Phase::NONE) {
+            MOZ_ASSERT(phases[i].phaseKind == phases[nextInPhase].phaseKind);
+            MOZ_ASSERT(parent != phases[nextInPhase].parent);
+        }
+    }
+    for (auto i : AllPhaseKinds()) {
+        MOZ_ASSERT(phases[phaseKinds[i].firstPhase].phaseKind == i);
+        for (auto j : AllPhaseKinds()) {
+            MOZ_ASSERT_IF(i != j,
+                          phaseKinds[i].telemetryBucket != phaseKinds[j].telemetryBucket);
+        }
     }
 #endif
 
-    // Create a static table of descendants for every phase with multiple
-    // children. This assumes that all descendants come linearly in the
-    // list, which is reasonable since full dags are not supported; any
-    // path from the leaf to the root must encounter at most one node with
-    // multiple parents.
-    size_t dagSlot = 0;
-    for (size_t i = 0; i < mozilla::ArrayLength(dagChildEdges); i++) {
-        Phase parent = dagChildEdges[i].parent;
-        if (!phaseExtra[parent].dagSlot)
-            phaseExtra[parent].dagSlot = ++dagSlot;
-
-        Phase child = dagChildEdges[i].child;
-        MOZ_ASSERT(phases[child].parent == PHASE_MULTI_PARENTS);
-        int j = child;
-        do {
-            if (!dagDescendants[phaseExtra[parent].dagSlot].append(Phase(j)))
-                return false;
-            j++;
-        } while (j != PHASE_LIMIT && phases[j].parent != PHASE_MULTI_PARENTS);
-    }
-    MOZ_ASSERT(dagSlot <= MaxMultiparentPhases - 1);
-
-    // Fill in the depth of each node in the tree. Multi-parented nodes
-    // have depth 0.
-    mozilla::Vector<Phase, 0, SystemAllocPolicy> stack;
-    if (!stack.append(PHASE_LIMIT)) // Dummy entry to avoid special-casing the first node
-        return false;
-    for (auto i : AllPhases()) {
-        if (phases[i].parent == PHASE_NO_PARENT ||
-            phases[i].parent == PHASE_MULTI_PARENTS)
-        {
-            stack.clear();
-        } else {
-            while (stack.back() != phases[i].parent)
-                stack.popBack();
-        }
-        phaseExtra[i].depth = stack.length();
-        if (!stack.append(i))
-            return false;
-    }
-
     return true;
 }
 
 JS::GCSliceCallback
 Statistics::setSliceCallback(JS::GCSliceCallback newCallback)
 {
     JS::GCSliceCallback oldCallback = sliceCallback;
     sliceCallback = newCallback;
@@ -932,78 +770,75 @@ TimeDuration
 Statistics::getMaxGCPauseSinceClear()
 {
     return maxPauseInInterval;
 }
 
 // Sum up the time for a phase, including instances of the phase with different
 // parents.
 static TimeDuration
-SumPhase(Phase phase, const Statistics::PhaseTimeTable& times)
+SumPhase(PhaseKind phaseKind, const Statistics::PhaseTimeTable& times)
 {
     TimeDuration sum = 0;
-    for (const auto& phaseTimes : times)
-        sum += phaseTimes[phase];
+    for (Phase phase = phaseKinds[phaseKind].firstPhase;
+         phase != Phase::NONE;
+         phase = phases[phase].nextInPhase)
+    {
+        sum += times[phase];
+    }
     return sum;
 }
 
 static void
-CheckSelfTime(Phase parent, Phase child, const Statistics::PhaseTimeTable& times, TimeDuration selfTimes[PHASE_LIMIT], TimeDuration childTime)
+CheckSelfTime(Phase parent,
+              Phase child,
+              const Statistics::PhaseTimeTable& times,
+              const Statistics::PhaseTimeTable& selfTimes,
+              TimeDuration childTime)
 {
     if (selfTimes[parent] < childTime) {
         fprintf(stderr,
-                "Parent %s time = %.3fms"
-                " with %.3fms remaining, "
-                "child %s time %.3fms\n",
-                phases[parent].name, SumPhase(parent, times).ToMilliseconds(),
+                "Parent %s time = %.3fms with %.3fms remaining, child %s time %.3fms\n",
+                phases[parent].name,
+                times[parent].ToMilliseconds(),
                 selfTimes[parent].ToMilliseconds(),
-                phases[child].name, childTime.ToMilliseconds());
+                phases[child].name,
+                childTime.ToMilliseconds());
+        MOZ_CRASH();
     }
 }
 
-static Phase
+static PhaseKind
 LongestPhaseSelfTime(const Statistics::PhaseTimeTable& times)
 {
-    TimeDuration selfTimes[PHASE_LIMIT];
-
-    // Start with total times, including children's times.
-    for (auto i : AllPhases())
-        selfTimes[i] = SumPhase(i, times);
+    // Start with total times per expanded phase, including children's times.
+    Statistics::PhaseTimeTable selfTimes(times);
 
     // We have the total time spent in each phase, including descendant times.
     // Loop over the children and subtract their times from their parent's self
     // time.
     for (auto i : AllPhases()) {
         Phase parent = phases[i].parent;
-        if (parent == PHASE_MULTI_PARENTS) {
-            // Current phase i has multiple parents. Each "instance" of this
-            // phase is in a parallel array of times indexed by 'dagSlot', so
-            // subtract only the dagSlot-specific child's time from the parent.
-            for (auto edge : dagChildEdges) {
-                if (edge.parent == i) {
-                    size_t dagSlot = phaseExtra[edge.parent].dagSlot;
-                    MOZ_ASSERT(dagSlot <= Statistics::MaxMultiparentPhases - 1);
-                    CheckSelfTime(edge.parent, edge.child, times,
-                                  selfTimes, times[dagSlot][edge.child]);
-                    MOZ_ASSERT(selfTimes[edge.parent] >= times[dagSlot][edge.child]);
-                    selfTimes[edge.parent] -= times[dagSlot][edge.child];
-                }
-            }
-        } else if (parent != PHASE_NO_PARENT) {
-            CheckSelfTime(parent, i, times, selfTimes, selfTimes[i]);
-            MOZ_ASSERT(selfTimes[parent] >= selfTimes[i]);
-            selfTimes[parent] -= selfTimes[i];
+        if (parent != Phase::NONE) {
+            CheckSelfTime(parent, i, times, selfTimes, times[i]);
+            selfTimes[parent] -= times[i];
         }
     }
 
+    // Sum expanded phases corresponding to the same phase.
+    EnumeratedArray<PhaseKind, PhaseKind::LIMIT, TimeDuration> phaseTimes;
+    for (auto i : AllPhaseKinds())
+        phaseTimes[i] = SumPhase(i, selfTimes);
+
+    // Loop over this table to find the longest phase.
     TimeDuration longestTime = 0;
-    Phase longestPhase = PHASE_NONE;
-    for (auto i : AllPhases()) {
-        if (selfTimes[i] > longestTime) {
-            longestTime = selfTimes[i];
+    PhaseKind longestPhase = PhaseKind::NONE;
+    for (auto i : AllPhaseKinds()) {
+        if (phaseTimes[i] > longestTime) {
+            longestTime = phaseTimes[i];
             longestPhase = i;
         }
     }
 
     return longestPhase;
 }
 
 void
@@ -1032,35 +867,30 @@ Statistics::beginGC(JSGCInvocationKind k
 
     preBytes = runtime->gc.usage.gcBytes();
     startingMajorGCNumber = runtime->gc.majorGCCount();
 }
 
 void
 Statistics::endGC()
 {
-    for (auto j : IntegerRange(NumTimingArrays)) {
-        for (auto i : AllPhases())
-            phaseTotals[j][i] += phaseTimes[j][i];
-    }
-
     TimeDuration sccTotal, sccLongest;
     sccDurations(&sccTotal, &sccLongest);
 
     runtime->addTelemetry(JS_TELEMETRY_GC_IS_ZONE_GC, !zoneStats.isCollectingAllZones());
-    TimeDuration markTotal = SumPhase(PHASE_MARK, phaseTimes);
-    TimeDuration markRootsTotal = SumPhase(PHASE_MARK_ROOTS, phaseTimes);
+    TimeDuration markTotal = SumPhase(PhaseKind::MARK, phaseTimes);
+    TimeDuration markRootsTotal = SumPhase(PhaseKind::MARK_ROOTS, phaseTimes);
     runtime->addTelemetry(JS_TELEMETRY_GC_MARK_MS, t(markTotal));
-    runtime->addTelemetry(JS_TELEMETRY_GC_SWEEP_MS, t(phaseTimes[PHASE_DAG_NONE][PHASE_SWEEP]));
+    runtime->addTelemetry(JS_TELEMETRY_GC_SWEEP_MS, t(phaseTimes[Phase::SWEEP]));
     if (runtime->gc.isCompactingGc()) {
         runtime->addTelemetry(JS_TELEMETRY_GC_COMPACT_MS,
-                              t(phaseTimes[PHASE_DAG_NONE][PHASE_COMPACT]));
+                              t(phaseTimes[Phase::COMPACT]));
     }
     runtime->addTelemetry(JS_TELEMETRY_GC_MARK_ROOTS_MS, t(markRootsTotal));
-    runtime->addTelemetry(JS_TELEMETRY_GC_MARK_GRAY_MS, t(phaseTimes[PHASE_DAG_NONE][PHASE_SWEEP_MARK_GRAY]));
+    runtime->addTelemetry(JS_TELEMETRY_GC_MARK_GRAY_MS, t(phaseTimes[Phase::SWEEP_MARK_GRAY]));
     runtime->addTelemetry(JS_TELEMETRY_GC_NON_INCREMENTAL, nonincremental());
     if (nonincremental())
         runtime->addTelemetry(JS_TELEMETRY_GC_NON_INCREMENTAL_REASON, uint32_t(nonincrementalReason_));
     runtime->addTelemetry(JS_TELEMETRY_GC_INCREMENTAL_DISABLED, !runtime->gc.isIncrementalGCAllowed());
     runtime->addTelemetry(JS_TELEMETRY_GC_SCC_SWEEP_TOTAL_MS, t(sccTotal));
     runtime->addTelemetry(JS_TELEMETRY_GC_SCC_SWEEP_MAX_PAUSE_MS, t(sccLongest));
 
     if (!aborted) {
@@ -1151,18 +981,19 @@ Statistics::endSlice()
         if (slices_.back().budget.isTimeBudget()) {
             int64_t budget_ms = slices_.back().budget.timeBudget.budget;
             runtime->addTelemetry(JS_TELEMETRY_GC_BUDGET_MS, budget_ms);
             if (budget_ms == runtime->gc.defaultSliceBudget())
                 runtime->addTelemetry(JS_TELEMETRY_GC_ANIMATION_MS, t(sliceTime));
 
             // Record any phase that goes more than 2x over its budget.
             if (sliceTime.ToMilliseconds() > 2 * budget_ms) {
-                Phase longest = LongestPhaseSelfTime(slices_.back().phaseTimes);
-                runtime->addTelemetry(JS_TELEMETRY_GC_SLOW_PHASE, phases[longest].telemetryBucket);
+                PhaseKind longest = LongestPhaseSelfTime(slices_.back().phaseTimes);
+                uint8_t bucket = phaseKinds[longest].telemetryBucket;
+                runtime->addTelemetry(JS_TELEMETRY_GC_SLOW_PHASE, bucket);
             }
         }
 
         sliceCount_++;
     }
 
     bool last = !runtime->gc.isIncrementalGCInProgress();
     if (last)
@@ -1180,160 +1011,170 @@ Statistics::endSlice()
                              JS::GCDescription(!wasFullGC, gckind, slices_.back().reason));
     }
 
     // Do this after the slice callback since it uses these values.
     if (last) {
         for (auto& count : counts)
             count = 0;
 
-        // Clear the timers at the end of a GC because we accumulate time in
-        // between GCs for some (which come before PHASE_GC_BEGIN in the list.)
-        PodZero(&phaseStartTimes[PHASE_GC_BEGIN], PHASE_LIMIT - PHASE_GC_BEGIN);
-        for (size_t d = PHASE_DAG_NONE; d < NumTimingArrays; d++)
-            PodZero(&phaseTimes[d][PHASE_GC_BEGIN], PHASE_LIMIT - PHASE_GC_BEGIN);
+        // Clear the timers at the end of a GC, preserving the data for PhaseKind::MUTATOR.
+        auto mutatorStartTime = phaseStartTimes[Phase::MUTATOR];
+        auto mutatorTime = phaseTimes[Phase::MUTATOR];
+        PodZero(&phaseStartTimes);
+        PodZero(&phaseTimes);
+        phaseStartTimes[Phase::MUTATOR] = mutatorStartTime;
+        phaseTimes[Phase::MUTATOR] = mutatorTime;
     }
 }
 
 bool
 Statistics::startTimingMutator()
 {
     if (phaseNestingDepth != 0) {
         // Should only be called from outside of GC.
         MOZ_ASSERT(phaseNestingDepth == 1);
-        MOZ_ASSERT(phaseNesting[0] == PHASE_MUTATOR);
+        MOZ_ASSERT(phaseNesting[0] == Phase::MUTATOR);
         return false;
     }
 
     MOZ_ASSERT(suspended == 0);
 
     timedGCTime = 0;
-    phaseStartTimes[PHASE_MUTATOR] = TimeStamp();
-    phaseTimes[PHASE_DAG_NONE][PHASE_MUTATOR] = 0;
+    phaseStartTimes[Phase::MUTATOR] = TimeStamp();
+    phaseTimes[Phase::MUTATOR] = 0;
     timedGCStart = TimeStamp();
 
-    beginPhase(PHASE_MUTATOR);
+    beginPhase(PhaseKind::MUTATOR);
     return true;
 }
 
 bool
 Statistics::stopTimingMutator(double& mutator_ms, double& gc_ms)
 {
     // This should only be called from outside of GC, while timing the mutator.
-    if (phaseNestingDepth != 1 || phaseNesting[0] != PHASE_MUTATOR)
+    if (phaseNestingDepth != 1 || phaseNesting[0] != Phase::MUTATOR)
         return false;
 
-    endPhase(PHASE_MUTATOR);
-    mutator_ms = t(phaseTimes[PHASE_DAG_NONE][PHASE_MUTATOR]);
+    endPhase(PhaseKind::MUTATOR);
+    mutator_ms = t(phaseTimes[Phase::MUTATOR]);
     gc_ms = t(timedGCTime);
 
     return true;
 }
 
 void
-Statistics::suspendPhases(Phase suspension)
+Statistics::suspendPhases(PhaseKind suspension)
 {
-    MOZ_ASSERT(suspension == PHASE_EXPLICIT_SUSPENSION || suspension == PHASE_IMPLICIT_SUSPENSION);
+    MOZ_ASSERT(suspension == PhaseKind::EXPLICIT_SUSPENSION ||
+               suspension == PhaseKind::IMPLICIT_SUSPENSION);
     while (phaseNestingDepth) {
         MOZ_ASSERT(suspended < mozilla::ArrayLength(suspendedPhases));
         Phase parent = phaseNesting[phaseNestingDepth - 1];
         suspendedPhases[suspended++] = parent;
         recordPhaseEnd(parent);
     }
-    suspendedPhases[suspended++] = suspension;
+    suspendedPhases[suspended++] = lookupChildPhase(suspension);
 }
 
 void
 Statistics::resumePhases()
 {
-    DebugOnly<Phase> popped = suspendedPhases[--suspended];
-    MOZ_ASSERT(popped == PHASE_EXPLICIT_SUSPENSION || popped == PHASE_IMPLICIT_SUSPENSION);
+    suspended--;
+#ifdef DEBUG
+    Phase popped = suspendedPhases[suspended];
+    MOZ_ASSERT(popped == Phase::EXPLICIT_SUSPENSION ||
+               popped == Phase::IMPLICIT_SUSPENSION);
+#endif
+
     while (suspended &&
-           suspendedPhases[suspended - 1] != PHASE_EXPLICIT_SUSPENSION &&
-           suspendedPhases[suspended - 1] != PHASE_IMPLICIT_SUSPENSION)
+           suspendedPhases[suspended - 1] != Phase::EXPLICIT_SUSPENSION &&
+           suspendedPhases[suspended - 1] != Phase::IMPLICIT_SUSPENSION)
     {
         Phase resumePhase = suspendedPhases[--suspended];
-        if (resumePhase == PHASE_MUTATOR)
+        if (resumePhase == Phase::MUTATOR)
             timedGCTime += TimeStamp::Now() - timedGCStart;
-        beginPhase(resumePhase);
+        recordPhaseBegin(resumePhase);
     }
 }
 
 void
-Statistics::beginPhase(Phase phase)
+Statistics::beginPhase(PhaseKind phaseKind)
 {
     // No longer timing these phases. We should never see these.
-    MOZ_ASSERT(phase != PHASE_GC_BEGIN && phase != PHASE_GC_END);
-
-    Phase parent = phaseNestingDepth ? phaseNesting[phaseNestingDepth - 1] : PHASE_NO_PARENT;
+    MOZ_ASSERT(phaseKind != PhaseKind::GC_BEGIN && phaseKind != PhaseKind::GC_END);
 
-    // PHASE_MUTATOR is suspended while performing GC.
-    if (parent == PHASE_MUTATOR) {
-        suspendPhases(PHASE_IMPLICIT_SUSPENSION);
-        parent = phaseNestingDepth ? phaseNesting[phaseNestingDepth - 1] : PHASE_NO_PARENT;
+    // PhaseKind::MUTATOR is suspended while performing GC.
+    if (currentPhase() == Phase::MUTATOR) {
+        suspendPhases(PhaseKind::IMPLICIT_SUSPENSION);
     }
 
+    recordPhaseBegin(lookupChildPhase(phaseKind));
+}
+
+void
+Statistics::recordPhaseBegin(Phase phase)
+{
     // Guard against any other re-entry.
     MOZ_ASSERT(!phaseStartTimes[phase]);
 
-    MOZ_ASSERT(phases[phase].index == phase);
     MOZ_ASSERT(phaseNestingDepth < MAX_NESTING);
-    MOZ_ASSERT(phases[phase].parent == parent || phases[phase].parent == PHASE_MULTI_PARENTS);
+    MOZ_ASSERT(phases[phase].parent == currentPhase());
 
     phaseNesting[phaseNestingDepth] = phase;
     phaseNestingDepth++;
 
-    if (phases[phase].parent == PHASE_MULTI_PARENTS) {
-        MOZ_ASSERT(parent != PHASE_NO_PARENT);
-        activeDagSlot = phaseExtra[parent].dagSlot;
-    }
-    MOZ_ASSERT(activeDagSlot <= MaxMultiparentPhases - 1);
-
     phaseStartTimes[phase] = TimeStamp::Now();
 }
 
 void
 Statistics::recordPhaseEnd(Phase phase)
 {
     TimeStamp now = TimeStamp::Now();
 
-    if (phase == PHASE_MUTATOR)
+    if (phase == Phase::MUTATOR)
         timedGCStart = now;
 
     phaseNestingDepth--;
 
     TimeDuration t = now - phaseStartTimes[phase];
     if (!slices_.empty())
-        slices_.back().phaseTimes[activeDagSlot][phase] += t;
-    phaseTimes[activeDagSlot][phase] += t;
+        slices_.back().phaseTimes[phase] += t;
+    phaseTimes[phase] += t;
     phaseStartTimes[phase] = TimeStamp();
 }
 
 void
-Statistics::endPhase(Phase phase)
+Statistics::endPhase(PhaseKind phaseKind)
 {
+    Phase phase = currentPhase();
+    MOZ_ASSERT(phase != Phase::NONE);
+    MOZ_ASSERT(phases[phase].phaseKind == phaseKind);
+
     recordPhaseEnd(phase);
 
-    if (phases[phase].parent == PHASE_MULTI_PARENTS)
-        activeDagSlot = PHASE_DAG_NONE;
-
     // When emptying the stack, we may need to return to timing the mutator
-    // (PHASE_MUTATOR).
-    if (phaseNestingDepth == 0 && suspended > 0 && suspendedPhases[suspended - 1] == PHASE_IMPLICIT_SUSPENSION)
+    // (PhaseKind::MUTATOR).
+    if (phaseNestingDepth == 0 &&
+        suspended > 0 &&
+        suspendedPhases[suspended - 1] == Phase::IMPLICIT_SUSPENSION)
+    {
         resumePhases();
+    }
 }
 
 void
-Statistics::endParallelPhase(Phase phase, const GCParallelTask* task)
+Statistics::endParallelPhase(PhaseKind phaseKind, const GCParallelTask* task)
 {
+    Phase phase = lookupChildPhase(phaseKind);
     phaseNestingDepth--;
 
     if (!slices_.empty())
-        slices_.back().phaseTimes[PHASE_DAG_NONE][phase] += task->duration();
-    phaseTimes[PHASE_DAG_NONE][phase] += task->duration();
+        slices_.back().phaseTimes[phase] += task->duration();
+    phaseTimes[phase] += task->duration();
     phaseStartTimes[phase] = TimeStamp();
 }
 
 TimeStamp
 Statistics::beginSCC()
 {
     return TimeStamp::Now();
 }
@@ -1436,17 +1277,17 @@ Statistics::printSliceProfile()
     fprintf(stderr, "MajorGC: %20s %1d -> %1d      ",
             ExplainReason(slice.reason), int(slice.initialState), int(slice.finalState));
 
     ProfileDurations times;
     times[ProfileKey::Total] = slice.duration();
     totalTimes_[ProfileKey::Total] += times[ProfileKey::Total];
 
 #define GET_PROFILE_TIME(name, text, phase)                                   \
-    times[ProfileKey::name] = slice.phaseTimes[PHASE_DAG_NONE][phase];                     \
+    times[ProfileKey::name] = SumPhase(phase, slice.phaseTimes);              \
     totalTimes_[ProfileKey::name] += times[ProfileKey::name];
 FOR_EACH_GC_PROFILE_TIME(GET_PROFILE_TIME)
 #undef GET_PROFILE_TIME
 
     printProfileTimes(times);
 }
 
 void
--- a/js/src/gc/Statistics.h
+++ b/js/src/gc/Statistics.h
@@ -25,92 +25,20 @@
 using mozilla::Maybe;
 
 namespace js {
 
 class GCParallelTask;
 
 namespace gcstats {
 
-enum Phase : uint8_t {
-    PHASE_FIRST,
+// Phase data is generated by a script. If you need to add phases, edit
+// js/src/gc/GenerateStatsPhases.py
 
-    PHASE_MUTATOR = PHASE_FIRST,
-    PHASE_GC_BEGIN,
-    PHASE_WAIT_BACKGROUND_THREAD,
-    PHASE_MARK_DISCARD_CODE,
-    PHASE_RELAZIFY_FUNCTIONS,
-    PHASE_PURGE,
-    PHASE_MARK,
-    PHASE_UNMARK,
-    PHASE_MARK_DELAYED,
-    PHASE_SWEEP,
-    PHASE_SWEEP_MARK,
-    PHASE_SWEEP_MARK_TYPES,
-    PHASE_SWEEP_MARK_INCOMING_BLACK,
-    PHASE_SWEEP_MARK_WEAK,
-    PHASE_SWEEP_MARK_INCOMING_GRAY,
-    PHASE_SWEEP_MARK_GRAY,
-    PHASE_SWEEP_MARK_GRAY_WEAK,
-    PHASE_FINALIZE_START,
-    PHASE_WEAK_ZONES_CALLBACK,
-    PHASE_WEAK_COMPARTMENT_CALLBACK,
-    PHASE_SWEEP_ATOMS,
-    PHASE_SWEEP_COMPARTMENTS,
-    PHASE_SWEEP_DISCARD_CODE,
-    PHASE_SWEEP_INNER_VIEWS,
-    PHASE_SWEEP_CC_WRAPPER,
-    PHASE_SWEEP_BASE_SHAPE,
-    PHASE_SWEEP_INITIAL_SHAPE,
-    PHASE_SWEEP_TYPE_OBJECT,
-    PHASE_SWEEP_BREAKPOINT,
-    PHASE_SWEEP_REGEXP,
-    PHASE_SWEEP_COMPRESSION,
-    PHASE_SWEEP_WEAKMAPS,
-    PHASE_SWEEP_UNIQUEIDS,
-    PHASE_SWEEP_JIT_DATA,
-    PHASE_SWEEP_WEAK_CACHES,
-    PHASE_SWEEP_MISC,
-    PHASE_SWEEP_TYPES,
-    PHASE_SWEEP_TYPES_BEGIN,
-    PHASE_SWEEP_TYPES_END,
-    PHASE_SWEEP_OBJECT,
-    PHASE_SWEEP_STRING,
-    PHASE_SWEEP_SCRIPT,
-    PHASE_SWEEP_SCOPE,
-    PHASE_SWEEP_REGEXP_SHARED,
-    PHASE_SWEEP_SHAPE,
-    PHASE_SWEEP_JITCODE,
-    PHASE_FINALIZE_END,
-    PHASE_DESTROY,
-    PHASE_COMPACT,
-    PHASE_COMPACT_MOVE,
-    PHASE_COMPACT_UPDATE,
-    PHASE_COMPACT_UPDATE_CELLS,
-    PHASE_GC_END,
-    PHASE_MINOR_GC,
-    PHASE_EVICT_NURSERY,
-    PHASE_TRACE_HEAP,
-    PHASE_BARRIER,
-    PHASE_UNMARK_GRAY,
-    PHASE_MARK_ROOTS,
-    PHASE_BUFFER_GRAY_ROOTS,
-    PHASE_MARK_CCWS,
-    PHASE_MARK_STACK,
-    PHASE_MARK_RUNTIME_DATA,
-    PHASE_MARK_EMBEDDING,
-    PHASE_MARK_COMPARTMENTS,
-    PHASE_PURGE_SHAPE_TABLES,
-
-    PHASE_LIMIT,
-    PHASE_NONE = PHASE_LIMIT,
-    PHASE_EXPLICIT_SUSPENSION = PHASE_LIMIT,
-    PHASE_IMPLICIT_SUSPENSION,
-    PHASE_MULTI_PARENTS
-};
+#include "gc/StatsPhasesGenerated.h"
 
 enum Stat {
     STAT_NEW_CHUNK,
     STAT_DESTROY_CHUNK,
     STAT_MINOR_GC,
 
     // Number of times a 'put' into a storebuffer overflowed, triggering a
     // compaction
@@ -146,99 +74,81 @@ struct ZoneGCStats
 
     ZoneGCStats()
       : collectedZoneCount(0), zoneCount(0), sweptZoneCount(0),
         collectedCompartmentCount(0), compartmentCount(0), sweptCompartmentCount(0)
     {}
 };
 
 #define FOR_EACH_GC_PROFILE_TIME(_)                                           \
-    _(BeginCallback, "bgnCB",  PHASE_GC_BEGIN)                                \
-    _(WaitBgThread,  "waitBG", PHASE_WAIT_BACKGROUND_THREAD)                  \
-    _(DiscardCode,   "discrd", PHASE_MARK_DISCARD_CODE)                       \
-    _(RelazifyFunc,  "relzfy", PHASE_RELAZIFY_FUNCTIONS)                      \
-    _(PurgeTables,   "prgTbl", PHASE_PURGE_SHAPE_TABLES)                      \
-    _(Purge,         "purge",  PHASE_PURGE)                                   \
-    _(Mark,          "mark",   PHASE_MARK)                                    \
-    _(Sweep,         "sweep",  PHASE_SWEEP)                                   \
-    _(Compact,       "cmpct",  PHASE_COMPACT)                                 \
-    _(EndCallback,   "endCB",  PHASE_GC_END)                                  \
-    _(Barriers,      "brrier", PHASE_BARRIER)
+    _(BeginCallback, "bgnCB",  PhaseKind::GC_BEGIN)                           \
+    _(WaitBgThread,  "waitBG", PhaseKind::WAIT_BACKGROUND_THREAD)             \
+    _(DiscardCode,   "discrd", PhaseKind::MARK_DISCARD_CODE)                  \
+    _(RelazifyFunc,  "relzfy", PhaseKind::RELAZIFY_FUNCTIONS)                 \
+    _(PurgeTables,   "prgTbl", PhaseKind::PURGE_SHAPE_TABLES)                 \
+    _(Purge,         "purge",  PhaseKind::PURGE)                              \
+    _(Mark,          "mark",   PhaseKind::MARK)                               \
+    _(Sweep,         "sweep",  PhaseKind::SWEEP)                              \
+    _(Compact,       "cmpct",  PhaseKind::COMPACT)                            \
+    _(EndCallback,   "endCB",  PhaseKind::GC_END)                             \
+    _(Barriers,      "brrier", PhaseKind::BARRIER)
 
 const char* ExplainAbortReason(gc::AbortReason reason);
 const char* ExplainInvocationKind(JSGCInvocationKind gckind);
 
 /*
  * Struct for collecting timing statistics on a "phase tree". The tree is
  * specified as a limited DAG, but the timings are collected for the whole tree
  * that you would get by expanding out the DAG by duplicating subtrees rooted
  * at nodes with multiple parents.
  *
  * During execution, a child phase can be activated multiple times, and the
  * total time will be accumulated. (So for example, you can start and end
- * PHASE_MARK_ROOTS multiple times before completing the parent phase.)
+ * PhaseKind::MARK_ROOTS multiple times before completing the parent phase.)
  *
  * Incremental GC is represented by recording separate timing results for each
  * slice within the overall GC.
  */
 struct Statistics
 {
     template <typename T, size_t Length>
     using Array = mozilla::Array<T, Length>;
 
     template <typename IndexType, IndexType SizeAsEnumValue, typename ValueType>
     using EnumeratedArray = mozilla::EnumeratedArray<IndexType, SizeAsEnumValue, ValueType>;
 
     using TimeDuration = mozilla::TimeDuration;
     using TimeStamp = mozilla::TimeStamp;
 
-    /*
-     * Phases are allowed to have multiple parents, though any path from root
-     * to leaf is allowed at most one multi-parented phase. We keep a full set
-     * of timings for each of the multi-parented phases, to be able to record
-     * all the timings in the expanded tree induced by our dag.
-     *
-     * Note that this wastes quite a bit of space, since we have a whole
-     * separate array of timing data containing all the phases. We could be
-     * more clever and keep an array of pointers biased by the offset of the
-     * multi-parented phase, and thereby preserve the simple
-     * timings[slot][PHASE_*] indexing. But the complexity doesn't seem worth
-     * the few hundred bytes of savings. If we want to extend things to full
-     * DAGs, this decision should be reconsidered.
-     */
-    static const size_t MaxMultiparentPhases = 6;
-    static const size_t NumTimingArrays = MaxMultiparentPhases + 1;
-
-    /* Create a convenient type for referring to tables of phase times. */
-    using PhaseTimeTable =
-        Array<EnumeratedArray<Phase, PHASE_LIMIT, TimeDuration>, NumTimingArrays>;
+    // Create a convenient type for referring to tables of phase times.
+    using PhaseTimeTable = EnumeratedArray<Phase, Phase::LIMIT, TimeDuration>;
 
     static MOZ_MUST_USE bool initialize();
 
     explicit Statistics(JSRuntime* rt);
     ~Statistics();
 
     Statistics(const Statistics&) = delete;
     Statistics& operator=(const Statistics&) = delete;
 
-    void beginPhase(Phase phase);
-    void endPhase(Phase phase);
-    void endParallelPhase(Phase phase, const GCParallelTask* task);
+    void beginPhase(PhaseKind phaseKind);
+    void endPhase(PhaseKind phaseKind);
+    void endParallelPhase(PhaseKind phaseKind, const GCParallelTask* task);
 
     // Occasionally, we may be in the middle of something that is tracked by
     // this class, and we need to do something unusual (eg evict the nursery)
     // that doesn't normally nest within the current phase. Suspend the
     // currently tracked phase stack, at which time the caller is free to do
     // other tracked operations.
     //
-    // This also happens internally with the PHASE_MUTATOR "phase". While in
+    // This also happens internally with the PhaseKind::MUTATOR "phase". While in
     // this phase, any beginPhase will automatically suspend the non-GC phase,
     // until that inner stack is complete, at which time it will automatically
     // resume the non-GC phase. Explicit suspensions do not get auto-resumed.
-    void suspendPhases(Phase suspension = PHASE_EXPLICIT_SUSPENSION);
+    void suspendPhases(PhaseKind suspension = PhaseKind::EXPLICIT_SUSPENSION);
 
     // Resume a suspended stack of phases.
     void resumePhases();
 
     void beginSlice(const ZoneGCStats& zoneStats, JSGCInvocationKind gckind,
                     SliceBudget budget, JS::gcreason::Reason reason);
     void endSlice();
 
@@ -288,24 +198,17 @@ struct Statistics
 
     JS::GCSliceCallback setSliceCallback(JS::GCSliceCallback callback);
     JS::GCNurseryCollectionCallback setNurseryCollectionCallback(
         JS::GCNurseryCollectionCallback callback);
 
     TimeDuration clearMaxGCPauseAccumulator();
     TimeDuration getMaxGCPauseSinceClear();
 
-    // Return the current phase, suppressing the synthetic PHASE_MUTATOR phase.
-    Phase currentPhase() {
-        if (phaseNestingDepth == 0)
-            return PHASE_NONE;
-        if (phaseNestingDepth == 1)
-            return phaseNesting[0] == PHASE_MUTATOR ? PHASE_NONE : phaseNesting[0];
-        return phaseNesting[phaseNestingDepth - 1];
-    }
+    PhaseKind currentPhaseKind() const;
 
     static const size_t MAX_NESTING = 20;
 
     struct SliceData {
         SliceData(SliceBudget budget, JS::gcreason::Reason reason,
                   TimeStamp start, size_t startFaults, gc::State initialState)
           : budget(budget), reason(reason),
             initialState(initialState),
@@ -368,28 +271,25 @@ struct Statistics
 
     JSGCInvocationKind gckind;
 
     gc::AbortReason nonincrementalReason_;
 
     SliceDataVector slices_;
 
     /* Most recent time when the given phase started. */
-    EnumeratedArray<Phase, PHASE_LIMIT, TimeStamp> phaseStartTimes;
+    EnumeratedArray<Phase, Phase::LIMIT, TimeStamp> phaseStartTimes;
 
     /* Bookkeeping for GC timings when timingMutator is true */
     TimeStamp timedGCStart;
     TimeDuration timedGCTime;
 
     /* Total time in a given phase for this GC. */
     PhaseTimeTable phaseTimes;
 
-    /* Total time in a given phase over all GCs. */
-    PhaseTimeTable phaseTotals;
-
     /* Number of events of this type for this GC. */
     EnumeratedArray<Stat,
                     STAT_LIMIT,
                     mozilla::Atomic<uint32_t, mozilla::ReleaseAcquire>> counts;
 
     /* Allocated space before the GC started. */
     size_t preBytes;
 
@@ -398,23 +298,22 @@ struct Statistics
     uint64_t startingMajorGCNumber;
 
     /* Records the maximum GC pause in an API-controlled interval (in us). */
     mutable TimeDuration maxPauseInInterval;
 
     /* Phases that are currently on stack. */
     Array<Phase, MAX_NESTING> phaseNesting;
     size_t phaseNestingDepth;
-    size_t activeDagSlot;
 
     /*
      * Certain phases can interrupt the phase stack, eg callback phases. When
      * this happens, we move the suspended phases over to a sepearate list,
-     * terminated by a dummy PHASE_SUSPENSION phase (so that we can nest
-     * suspensions by suspending multiple stacks with a PHASE_SUSPENSION in
+     * terminated by a dummy PhaseKind::SUSPENSION phase (so that we can nest
+     * suspensions by suspending multiple stacks with a PhaseKind::SUSPENSION in
      * between).
      */
     Array<Phase, MAX_NESTING * 3> suspendedPhases;
     size_t suspended;
 
     /* Sweep times for SCCs of compartments. */
     Vector<TimeDuration, 0, SystemAllocPolicy> sccTimes;
 
@@ -441,19 +340,23 @@ FOR_EACH_GC_PROFILE_TIME(DEFINE_TIME_KEY
 
     using ProfileDurations = EnumeratedArray<ProfileKey, ProfileKey::KeyCount, TimeDuration>;
 
     TimeDuration profileThreshold_;
     bool enableProfiling_;
     ProfileDurations totalTimes_;
     uint64_t sliceCount_;
 
+    Phase currentPhase() const;
+    Phase lookupChildPhase(PhaseKind phaseKind) const;
+
     void beginGC(JSGCInvocationKind kind);
     void endGC();
 
+    void recordPhaseBegin(Phase phase);
     void recordPhaseEnd(Phase phase);
 
     void gcDuration(TimeDuration* total, TimeDuration* maxPause) const;
     void sccDurations(TimeDuration* total, TimeDuration* maxPause) const;
     void printStats();
 
     UniqueChars formatCompactSlicePhaseTimes(const PhaseTimeTable& phaseTimes) const;
 
@@ -483,49 +386,49 @@ struct MOZ_RAII AutoGCSlice
     }
     ~AutoGCSlice() { stats.endSlice(); }
 
     Statistics& stats;
 };
 
 struct MOZ_RAII AutoPhase
 {
-    AutoPhase(Statistics& stats, Phase phase)
-      : stats(stats), task(nullptr), phase(phase), enabled(true)
+    AutoPhase(Statistics& stats, PhaseKind phaseKind)
+      : stats(stats), task(nullptr), phaseKind(phaseKind), enabled(true)
     {
-        stats.beginPhase(phase);
+        stats.beginPhase(phaseKind);
     }
 
-    AutoPhase(Statistics& stats, bool condition, Phase phase)
-      : stats(stats), task(nullptr), phase(phase), enabled(condition)
+    AutoPhase(Statistics& stats, bool condition, PhaseKind phaseKind)
+      : stats(stats), task(nullptr), phaseKind(phaseKind), enabled(condition)
     {
         if (enabled)
-            stats.beginPhase(phase);
+            stats.beginPhase(phaseKind);
     }
 
-    AutoPhase(Statistics& stats, const GCParallelTask& task, Phase phase)
-      : stats(stats), task(&task), phase(phase), enabled(true)
+    AutoPhase(Statistics& stats, const GCParallelTask& task, PhaseKind phaseKind)
+      : stats(stats), task(&task), phaseKind(phaseKind), enabled(true)
     {
         if (enabled)
-            stats.beginPhase(phase);
+            stats.beginPhase(phaseKind);
     }
 
     ~AutoPhase() {
         if (enabled) {
             // Bug 1309651 - we only record active thread time (including time
             // spent waiting to join with helper threads), but should start
             // recording total work on helper threads sometime by calling
             // endParallelPhase here if task is nonnull.
-            stats.endPhase(phase);
+            stats.endPhase(phaseKind);
         }
     }
 
     Statistics& stats;
     const GCParallelTask* task;
-    Phase phase;
+    PhaseKind phaseKind;
     bool enabled;
 };
 
 struct MOZ_RAII AutoSCC
 {
     AutoSCC(Statistics& stats, unsigned scc)
       : stats(stats), scc(scc)
     {
--- a/js/src/gc/Verifier.cpp
+++ b/js/src/gc/Verifier.cpp
@@ -194,17 +194,17 @@ gc::GCRuntime::startVerifyPreBarriers()
     if (!trc)
         return;
 
     AutoPrepareForTracing prep(TlsContext.get(), WithAtoms);
 
     for (auto chunk = allNonEmptyChunks(); !chunk.done(); chunk.next())
         chunk->bitmap.clear();
 
-    gcstats::AutoPhase ap(stats(), gcstats::PHASE_TRACE_HEAP);
+    gcstats::AutoPhase ap(stats(), gcstats::PhaseKind::TRACE_HEAP);
 
     const size_t size = 64 * 1024 * 1024;
     trc->root = (VerifyNode*)js_malloc(size);
     if (!trc->root)
         goto oom;
     trc->edgeptr = (char*)trc->root;
     trc->term = trc->edgeptr + size;
 
@@ -675,17 +675,17 @@ CheckGrayMarkingTracer::check(AutoLockFo
 JS_FRIEND_API(bool)
 js::CheckGrayMarkingState(JSRuntime* rt)
 {
     MOZ_ASSERT(!JS::CurrentThreadIsHeapCollecting());
     MOZ_ASSERT(!rt->gc.isIncrementalGCInProgress());
     if (!rt->gc.areGrayBitsValid())
         return true;
 
-    gcstats::AutoPhase ap(rt->gc.stats(), gcstats::PHASE_TRACE_HEAP);
+    gcstats::AutoPhase ap(rt->gc.stats(), gcstats::PhaseKind::TRACE_HEAP);
     AutoTraceSession session(rt, JS::HeapState::Tracing);
     CheckGrayMarkingTracer tracer(rt);
     if (!tracer.init())
         return true; // Ignore failure
 
     return tracer.check(session.lock);
 }
 
--- a/js/src/jscompartment.cpp
+++ b/js/src/jscompartment.cpp
@@ -693,17 +693,17 @@ JSCompartment::traceOutgoingCrossCompart
             TraceEdge(trc, wrapper->slotOfPrivate(), "cross-compartment wrapper");
         }
     }
 }
 
 /* static */ void
 JSCompartment::traceIncomingCrossCompartmentEdgesForZoneGC(JSTracer* trc)
 {
-    gcstats::AutoPhase ap(trc->runtime()->gc.stats(), gcstats::PHASE_MARK_CCWS);
+    gcstats::AutoPhase ap(trc->runtime()->gc.stats(), gcstats::PhaseKind::MARK_CCWS);
     MOZ_ASSERT(JS::CurrentThreadIsHeapMajorCollecting());
     for (CompartmentsIter c(trc->runtime(), SkipAtoms); !c.done(); c.next()) {
         if (!c->zone()->isCollecting())
             c->traceOutgoingCrossCompartmentWrappers(trc);
     }
     Debugger::traceIncomingCrossCompartmentEdges(trc);
 }
 
--- a/js/src/jsfriendapi.cpp
+++ b/js/src/jsfriendapi.cpp
@@ -1184,17 +1184,17 @@ js::DumpHeap(JSContext* cx, FILE* fp, js
         EvictAllNurseries(cx->runtime(), JS::gcreason::API);
 
     DumpHeapTracer dtrc(fp, cx);
 
     fprintf(dtrc.output, "# Roots.\n");
     {
         JSRuntime* rt = cx->runtime();
         js::gc::AutoPrepareForTracing prep(cx, WithAtoms);
-        gcstats::AutoPhase ap(rt->gc.stats(), gcstats::PHASE_TRACE_HEAP);
+        gcstats::AutoPhase ap(rt->gc.stats(), gcstats::PhaseKind::TRACE_HEAP);
         rt->gc.traceRuntime(&dtrc, prep.session().lock);
     }
 
     fprintf(dtrc.output, "# Weak maps.\n");
     WeakMapBase::traceAllMappings(&dtrc);
 
     fprintf(dtrc.output, "==========\n");
 
--- a/js/src/jsfun.cpp
+++ b/js/src/jsfun.cpp
@@ -535,19 +535,22 @@ fun_resolve(JSContext* cx, HandleObject 
                 return true;
 
             if (!JSFunction::getUnresolvedLength(cx, fun, &v))
                 return false;
         } else {
             if (fun->hasResolvedName())
                 return true;
 
+            RootedAtom name(cx);
+            if (!JSFunction::getUnresolvedName(cx, fun, &name))
+                return false;
+
             // Don't define an own .name property for unnamed functions.
-            JSAtom* name = fun->getUnresolvedName(cx);
-            if (name == nullptr)
+            if (!name)
                 return true;
 
             v.setString(name);
         }
 
         if (!NativeDefineProperty(cx, fun, id, v, nullptr, nullptr,
                                   JSPROP_READONLY | JSPROP_RESOLVING))
         {
@@ -1039,17 +1042,17 @@ js::FunctionToString(JSContext* cx, Hand
     }
 
     if (haveSource && !script->scriptSource()->hasSourceData() &&
         !JSScript::loadSource(cx, script->scriptSource(), &haveSource))
     {
         return nullptr;
     }
 
-    auto AppendPrelude = [&out, &fun]() {
+    auto AppendPrelude = [cx, &out, &fun]() {
         if (fun->isAsync()) {
             if (!out.append("async "))
                 return false;
         }
 
         if (!fun->isArrow()) {
             if (!out.append("function"))
                 return false;
@@ -1058,16 +1061,20 @@ js::FunctionToString(JSContext* cx, Hand
                 if (!out.append('*'))
                     return false;
             }
         }
 
         if (fun->explicitName()) {
             if (!out.append(' '))
                 return false;
+            if (fun->isBoundFunction()) {
+                if (!out.append(cx->names().boundWithSpace))
+                    return false;
+            }
             if (!out.append(fun->explicitName()))
                 return false;
         }
         return true;
     };
 
     if (haveSource) {
         Rooted<JSFlatString*> src(cx, JSScript::sourceDataForToString(cx, script));
@@ -1360,33 +1367,52 @@ JSFunction::getUnresolvedLength(JSContex
     uint16_t length;
     if (!JSFunction::getLength(cx, fun, &length))
         return false;
 
     v.setInt32(length);
     return true;
 }
 
-JSAtom*
-JSFunction::getUnresolvedName(JSContext* cx)
+/* static */ bool
+JSFunction::getUnresolvedName(JSContext* cx, HandleFunction fun, MutableHandleAtom v)
 {
-    MOZ_ASSERT(!IsInternalFunctionObject(*this));
-    MOZ_ASSERT(!hasResolvedName());
+    MOZ_ASSERT(!IsInternalFunctionObject(*fun));
+    MOZ_ASSERT(!fun->hasResolvedName());
 
-    if (isClassConstructor()) {
+    JSAtom* name = fun->explicitOrCompileTimeName();
+    if (fun->isClassConstructor()) {
         // It's impossible to have an empty named class expression. We use
         // empty as a sentinel when creating default class constructors.
-        MOZ_ASSERT(explicitOrCompileTimeName() != cx->names().empty);
+        MOZ_ASSERT(name != cx->names().empty);
 
         // Unnamed class expressions should not get a .name property at all.
-        return explicitOrCompileTimeName();
+        if (name)
+            v.set(name);
+        return true;
     }
 
-    return explicitOrCompileTimeName() != nullptr ? explicitOrCompileTimeName()
-                                                  : cx->names().empty;
+    if (fun->isBoundFunction()) {
+        // Bound functions are never unnamed.
+        MOZ_ASSERT(name);
+
+        StringBuffer sb(cx);
+        if (!sb.append(cx->names().boundWithSpace) || !sb.append(name))
+            return false;
+
+        JSAtom* boundName = sb.finishAtom();
+        if (!boundName)
+            return false;
+
+        v.set(boundName);
+        return true;
+    }
+
+    v.set(name != nullptr ? name : cx->names().empty);
+    return true;
 }
 
 static const js::Value&
 BoundFunctionEnvironmentSlotValue(const JSFunction* fun, uint32_t slotIndex)
 {
     MOZ_ASSERT(fun->isBoundFunction());
     MOZ_ASSERT(fun->environment()->is<CallObject>());
     CallObject* callObject = &fun->environment()->as<CallObject>();
--- a/js/src/jsfun.h
+++ b/js/src/jsfun.h
@@ -307,17 +307,18 @@ class JSFunction : public js::NativeObje
             lazyScript()->setAsyncKind(asyncKind);
         else
             nonLazyScript()->setAsyncKind(asyncKind);
     }
 
     static bool getUnresolvedLength(JSContext* cx, js::HandleFunction fun,
                                     js::MutableHandleValue v);
 
-    JSAtom* getUnresolvedName(JSContext* cx);
+    static bool getUnresolvedName(JSContext* cx, js::HandleFunction fun,
+                                  js::MutableHandleAtom v);
 
     JSAtom* explicitName() const {
         return (hasCompileTimeName() || hasGuessedAtom()) ? nullptr : atom_.get();
     }
     JSAtom* explicitOrCompileTimeName() const {
         return hasGuessedAtom() ? nullptr : atom_.get();
     }
 
--- a/js/src/jsgc.cpp
+++ b/js/src/jsgc.cpp
@@ -318,97 +318,97 @@ const uint32_t Arena::ThingsPerArena[] =
 FOR_EACH_ALLOCKIND(EXPAND_THINGS_PER_ARENA)
 #undef EXPAND_THINGS_PER_ARENA
 };
 
 #undef COUNT
 
 struct js::gc::FinalizePhase
 {
-    gcstats::Phase statsPhase;
+    gcstats::PhaseKind statsPhase;
     AllocKinds kinds;
 };
 
 /*
  * Finalization order for objects swept incrementally on the active thread.
  */
 static const FinalizePhase ForegroundObjectFinalizePhase = {
-    gcstats::PHASE_SWEEP_OBJECT, {
+    gcstats::PhaseKind::SWEEP_OBJECT, {
         AllocKind::OBJECT0,
         AllocKind::OBJECT2,
         AllocKind::OBJECT4,
         AllocKind::OBJECT8,
         AllocKind::OBJECT12,
         AllocKind::OBJECT16
     }
 };
 
 /*
  * Finalization order for GC things swept incrementally on the active thread.
  */
 static const FinalizePhase IncrementalFinalizePhases[] = {
     {
-        gcstats::PHASE_SWEEP_STRING, {
+        gcstats::PhaseKind::SWEEP_STRING, {
             AllocKind::EXTERNAL_STRING
         }
     },
     {
-        gcstats::PHASE_SWEEP_SCRIPT, {
+        gcstats::PhaseKind::SWEEP_SCRIPT, {
             AllocKind::SCRIPT
         }
     },
     {
-        gcstats::PHASE_SWEEP_JITCODE, {
+        gcstats::PhaseKind::SWEEP_JITCODE, {
             AllocKind::JITCODE
         }
     }
 };
 
 /*
  * Finalization order for GC things swept on the background thread.
  */
 static const FinalizePhase BackgroundFinalizePhases[] = {
     {
-        gcstats::PHASE_SWEEP_SCRIPT, {
+        gcstats::PhaseKind::SWEEP_SCRIPT, {
             AllocKind::LAZY_SCRIPT
         }
     },
     {
-        gcstats::PHASE_SWEEP_OBJECT, {
+        gcstats::PhaseKind::SWEEP_OBJECT, {
             AllocKind::FUNCTION,
             AllocKind::FUNCTION_EXTENDED,
             AllocKind::OBJECT0_BACKGROUND,
             AllocKind::OBJECT2_BACKGROUND,
             AllocKind::OBJECT4_BACKGROUND,
             AllocKind::OBJECT8_BACKGROUND,
             AllocKind::OBJECT12_BACKGROUND,
             AllocKind::OBJECT16_BACKGROUND
         }
     },
     {
-        gcstats::PHASE_SWEEP_SCOPE, {
+        gcstats::PhaseKind::SWEEP_SCOPE, {
             AllocKind::SCOPE,
         }
     },
     {
-        gcstats::PHASE_SWEEP_REGEXP_SHARED, {
+        gcstats::PhaseKind::SWEEP_REGEXP_SHARED, {
             AllocKind::REGEXP_SHARED,
         }
     },
     {
-        gcstats::PHASE_SWEEP_STRING, {
+        gcstats::PhaseKind::SWEEP_STRING, {
             AllocKind::FAT_INLINE_STRING,
             AllocKind::STRING,
             AllocKind::FAT_INLINE_ATOM,
             AllocKind::ATOM,
             AllocKind::SYMBOL
         }
     },
     {
-        gcstats::PHASE_SWEEP_SHAPE, {
+        gcstats::PhaseKind::SWEEP_SHAPE, {
             AllocKind::SHAPE,
             AllocKind::ACCESSOR_SHAPE,
             AllocKind::BASE_SHAPE,
             AllocKind::OBJECT_GROUP
         }
     }
 };
 
@@ -2132,17 +2132,17 @@ ArenaLists::relocateArenas(Zone* zone, A
 
     return true;
 }
 
 bool
 GCRuntime::relocateArenas(Zone* zone, JS::gcreason::Reason reason, Arena*& relocatedListOut,
                           SliceBudget& sliceBudget)
 {
-    gcstats::AutoPhase ap(stats(), gcstats::PHASE_COMPACT_MOVE);
+    gcstats::AutoPhase ap(stats(), gcstats::PhaseKind::COMPACT_MOVE);
 
     MOZ_ASSERT(!zone->isPreservingCode());
     MOZ_ASSERT(CanRelocateZone(zone));
 
     js::CancelOffThreadIonCompile(rt, JS::Zone::Compact);
 
     if (!zone->arenas.relocateArenas(zone, relocatedListOut, reason, sliceBudget, stats()))
         return false;
@@ -2460,28 +2460,28 @@ GCRuntime::updateCellPointers(MovingTrac
 
     {
         AutoLockHelperThreadState lock;
 
         fgTask.emplace(rt, &fgArenas, lock);
 
         for (size_t i = 0; i < bgTaskCount && !bgArenas.done(); i++) {
             bgTasks[i].emplace(rt, &bgArenas, lock);
-            startTask(*bgTasks[i], gcstats::PHASE_COMPACT_UPDATE_CELLS, lock);
+            startTask(*bgTasks[i], gcstats::PhaseKind::COMPACT_UPDATE_CELLS, lock);
             tasksStarted = i;
         }
     }
 
     fgTask->runFromActiveCooperatingThread(rt);
 
     {
         AutoLockHelperThreadState lock;
 
         for (size_t i = 0; i < tasksStarted; i++)
-            joinTask(*bgTasks[i], gcstats::PHASE_COMPACT_UPDATE_CELLS, lock);
+            joinTask(*bgTasks[i], gcstats::PhaseKind::COMPACT_UPDATE_CELLS, lock);
     }
 }
 
 // After cells have been relocated any pointers to a cell's old locations must
 // be updated to point to the new location.  This happens by iterating through
 // all cells in heap and tracing their children (non-recursively) to update
 // them.
 //
@@ -2555,17 +2555,17 @@ GCRuntime::updateAllCellPointers(MovingT
  * part of the traversal.
  */
 void
 GCRuntime::updateZonePointersToRelocatedCells(Zone* zone, AutoLockForExclusiveAccess& lock)
 {
     MOZ_ASSERT(!rt->isBeingDestroyed());
     MOZ_ASSERT(zone->isGCCompacting());
 
-    gcstats::AutoPhase ap(stats(), gcstats::PHASE_COMPACT_UPDATE);
+    gcstats::AutoPhase ap(stats(), gcstats::PhaseKind::COMPACT_UPDATE);
     MovingTracer trc(rt);
 
     zone->fixupAfterMovingGC();
 
     // Fixup compartment global pointers as these get accessed during marking.
     for (CompartmentsInZoneIter comp(zone); !comp.done(); comp.next())
         comp->fixupAfterMovingGC();
 
@@ -2573,17 +2573,17 @@ GCRuntime::updateZonePointersToRelocated
 
     // Iterate through all cells that can contain relocatable pointers to update
     // them. Since updating each cell is independent we try to parallelize this
     // as much as possible.
     updateAllCellPointers(&trc, zone);
 
     // Mark roots to update them.
     {
-        gcstats::AutoPhase ap(stats(), gcstats::PHASE_MARK_ROOTS);
+        gcstats::AutoPhase ap(stats(), gcstats::PhaseKind::MARK_ROOTS);
 
         WeakMapBase::traceZone(zone, &trc);
         for (CompartmentsInZoneIter c(zone); !c.done(); c.next()) {
             if (c->watchpointMap)
                 c->watchpointMap->trace(&trc);
         }
     }
 
@@ -2598,28 +2598,28 @@ GCRuntime::updateZonePointersToRelocated
 /*
  * Update runtime-wide pointers to relocated cells.
  */
 void
 GCRuntime::updateRuntimePointersToRelocatedCells(AutoLockForExclusiveAccess& lock)
 {
     MOZ_ASSERT(!rt->isBeingDestroyed());
 
-    gcstats::AutoPhase ap1(stats(), gcstats::PHASE_COMPACT_UPDATE);
+    gcstats::AutoPhase ap1(stats(), gcstats::PhaseKind::COMPACT_UPDATE);
     MovingTracer trc(rt);
 
     JSCompartment::fixupCrossCompartmentWrappersAfterMovingGC(&trc);
 
     rt->geckoProfiler().fixupStringsMapAfterMovingGC();
 
     traceRuntimeForMajorGC(&trc, lock);
 
     // Mark roots to update them.
     {
-        gcstats::AutoPhase ap2(stats(), gcstats::PHASE_MARK_ROOTS);
+        gcstats::AutoPhase ap2(stats(), gcstats::PhaseKind::MARK_ROOTS);
         Debugger::traceAllForMovingGC(&trc);
         Debugger::traceIncomingCrossCompartmentEdges(&trc);
 
         // Mark all gray roots, making sure we call the trace callback to get the
         // current set.
         if (JSTraceDataOp op = grayRootTracer.op)
             (*op)(&trc, grayRootTracer.data);
     }
@@ -3879,42 +3879,42 @@ GCRuntime::beginMarkPhase(JS::gcreason::
     MemProfiler::MarkTenuredStart(rt);
     marker.start();
     GCMarker* gcmarker = &marker;
 
     /* For non-incremental GC the following sweep discards the jit code. */
     if (isIncremental) {
         js::CancelOffThreadIonCompile(rt, JS::Zone::Mark);
         for (GCZonesIter zone(rt); !zone.done(); zone.next()) {
-            gcstats::AutoPhase ap(stats(), gcstats::PHASE_MARK_DISCARD_CODE);
+            gcstats::AutoPhase ap(stats(), gcstats::PhaseKind::MARK_DISCARD_CODE);
             zone->discardJitCode(rt->defaultFreeOp());
         }
     }
 
     /*
      * Relazify functions after discarding JIT code (we can't relazify
      * functions with JIT code) and before the actual mark phase, so that
      * the current GC can collect the JSScripts we're unlinking here.
      * We do this only when we're performing a shrinking GC, as too much
      * relazification can cause performance issues when we have to reparse
      * the same functions over and over.
      */
     if (invocationKind == GC_SHRINK) {
         {
-            gcstats::AutoPhase ap(stats(), gcstats::PHASE_RELAZIFY_FUNCTIONS);
+            gcstats::AutoPhase ap(stats(), gcstats::PhaseKind::RELAZIFY_FUNCTIONS);
             for (GCZonesIter zone(rt); !zone.done(); zone.next()) {
                 if (zone->isSelfHostingZone())
                     continue;
                 RelazifyFunctions(zone, AllocKind::FUNCTION);
                 RelazifyFunctions(zone, AllocKind::FUNCTION_EXTENDED);
             }
         }
 
         /* Purge ShapeTables. */
-        gcstats::AutoPhase ap(stats(), gcstats::PHASE_PURGE_SHAPE_TABLES);
+        gcstats::AutoPhase ap(stats(), gcstats::PhaseKind::PURGE_SHAPE_TABLES);
         for (GCZonesIter zone(rt); !zone.done(); zone.next()) {
             if (zone->keepShapeTables() || zone->isSelfHostingZone())
                 continue;
             for (auto baseShape = zone->cellIter<BaseShape>(); !baseShape.done(); baseShape.next())
                 baseShape->maybePurgeTable();
         }
     }
 
@@ -3934,55 +3934,55 @@ GCRuntime::beginMarkPhase(JS::gcreason::
      * danger if we purge later is that the snapshot invariant of incremental
      * GC will be broken, as follows. If some object is reachable only through
      * some cache (say the dtoaCache) then it will not be part of the snapshot.
      * If we purge after root marking, then the mutator could obtain a pointer
      * to the object and start using it. This object might never be marked, so
      * a GC hazard would exist.
      */
     {
-        gcstats::AutoPhase ap(stats(), gcstats::PHASE_PURGE);
+        gcstats::AutoPhase ap(stats(), gcstats::PhaseKind::PURGE);
         purgeRuntime(lock);
     }
 
     /*
      * Mark phase.
      */
-    gcstats::AutoPhase ap1(stats(), gcstats::PHASE_MARK);
+    gcstats::AutoPhase ap1(stats(), gcstats::PhaseKind::MARK);
 
     {
-        gcstats::AutoPhase ap(stats(), gcstats::PHASE_UNMARK);
+        gcstats::AutoPhase ap(stats(), gcstats::PhaseKind::UNMARK);
 
         for (GCZonesIter zone(rt); !zone.done(); zone.next()) {
             /* Unmark everything in the zones being collected. */
             zone->arenas.unmarkAll();
         }
 
         for (GCZonesIter zone(rt); !zone.done(); zone.next()) {
             /* Unmark all weak maps in the zones being collected. */
             WeakMapBase::unmarkZone(zone);
         }
     }
 
     traceRuntimeForMajorGC(gcmarker, lock);
 
-    gcstats::AutoPhase ap2(stats(), gcstats::PHASE_MARK_ROOTS);
+    gcstats::AutoPhase ap2(stats(), gcstats::PhaseKind::MARK_ROOTS);
 
     if (isIncremental) {
         bufferGrayRoots();
         markCompartments();
     }
 
     return true;
 }
 
 void
 GCRuntime::markCompartments()
 {
-    gcstats::AutoPhase ap(stats(), gcstats::PHASE_MARK_COMPARTMENTS);
+    gcstats::AutoPhase ap(stats(), gcstats::PhaseKind::MARK_COMPARTMENTS);
 
     /*
      * This code ensures that if a compartment is "dead", then it will be
      * collected in this GC. A compartment is considered dead if its maybeAlive
      * flag is false. The maybeAlive flag is set if:
      *
      *   (1) the compartment has been entered (set in beginMarkPhase() above)
      *   (2) the compartment is not being collected (set in beginMarkPhase()
@@ -4040,17 +4040,17 @@ GCRuntime::markCompartments()
         MOZ_ASSERT(!comp->scheduledForDestruction);
         if (!comp->maybeAlive && !rt->isAtomsCompartment(comp))
             comp->scheduledForDestruction = true;
     }
 }
 
 template <class ZoneIterT>
 void
-GCRuntime::markWeakReferences(gcstats::Phase phase)
+GCRuntime::markWeakReferences(gcstats::PhaseKind phase)
 {
     MOZ_ASSERT(marker.isDrained());
 
     gcstats::AutoPhase ap1(stats(), phase);
 
     marker.enterWeakMarkingMode();
 
     // TODO bug 1167452: Make weak marking incremental
@@ -4077,52 +4077,52 @@ GCRuntime::markWeakReferences(gcstats::P
         MOZ_RELEASE_ASSERT(marker.drainMarkStack(unlimited));
     }
     MOZ_ASSERT(marker.isDrained());
 
     marker.leaveWeakMarkingMode();
 }
 
 void
-GCRuntime::markWeakReferencesInCurrentGroup(gcstats::Phase phase)
+GCRuntime::markWeakReferencesInCurrentGroup(gcstats::PhaseKind phase)
 {
     markWeakReferences<GCSweepGroupIter>(phase);
 }
 
 template <class ZoneIterT, class CompartmentIterT>
 void
-GCRuntime::markGrayReferences(gcstats::Phase phase)
+GCRuntime::markGrayReferences(gcstats::PhaseKind phase)
 {
     gcstats::AutoPhase ap(stats(), phase);
     if (hasBufferedGrayRoots()) {
         for (ZoneIterT zone(rt); !zone.done(); zone.next())
             markBufferedGrayRoots(zone);
     } else {
         MOZ_ASSERT(!isIncremental);
         if (JSTraceDataOp op = grayRootTracer.op)
             (*op)(&marker, grayRootTracer.data);
     }
     auto unlimited = SliceBudget::unlimited();
     MOZ_RELEASE_ASSERT(marker.drainMarkStack(unlimited));
 }
 
 void
-GCRuntime::markGrayReferencesInCurrentGroup(gcstats::Phase phase)
+GCRuntime::markGrayReferencesInCurrentGroup(gcstats::PhaseKind phase)
 {
     markGrayReferences<GCSweepGroupIter, GCCompartmentGroupIter>(phase);
 }
 
 void
-GCRuntime::markAllWeakReferences(gcstats::Phase phase)
+GCRuntime::markAllWeakReferences(gcstats::PhaseKind phase)
 {
     markWeakReferences<GCZonesIter>(phase);
 }
 
 void
-GCRuntime::markAllGrayReferences(gcstats::Phase phase)
+GCRuntime::markAllGrayReferences(gcstats::PhaseKind phase)
 {
     markGrayReferences<GCZonesIter, GCCompartmentsIter>(phase);
 }
 
 #ifdef JS_GC_ZEAL
 
 struct GCChunkHasher {
     typedef gc::Chunk* Lookup;
@@ -4240,20 +4240,20 @@ js::gc::MarkingValidator::nonIncremental
      */
     initialized = true;
 
     /* Re-do all the marking, but non-incrementally. */
     js::gc::State state = gc->incrementalState;
     gc->incrementalState = State::MarkRoots;
 
     {
-        gcstats::AutoPhase ap(gc->stats(), gcstats::PHASE_MARK);
+        gcstats::AutoPhase ap(gc->stats(), gcstats::PhaseKind::MARK);
 
         {
-            gcstats::AutoPhase ap(gc->stats(), gcstats::PHASE_UNMARK);
+            gcstats::AutoPhase ap(gc->stats(), gcstats::PhaseKind::UNMARK);
 
             for (GCZonesIter zone(runtime); !zone.done(); zone.next())
                 WeakMapBase::unmarkZone(zone);
 
             MOZ_ASSERT(gcmarker->isDrained());
             gcmarker->reset();
 
             for (auto chunk = gc->allNonEmptyChunks(); !chunk.done(); chunk.next())
@@ -4264,30 +4264,30 @@ js::gc::MarkingValidator::nonIncremental
 
         gc->incrementalState = State::Mark;
         auto unlimited = SliceBudget::unlimited();
         MOZ_RELEASE_ASSERT(gc->marker.drainMarkStack(unlimited));
     }
 
     gc->incrementalState = State::Sweep;
     {
-        gcstats::AutoPhase ap1(gc->stats(), gcstats::PHASE_SWEEP);
-        gcstats::AutoPhase ap2(gc->stats(), gcstats::PHASE_SWEEP_MARK);
-
-        gc->markAllWeakReferences(gcstats::PHASE_SWEEP_MARK_WEAK);
+        gcstats::AutoPhase ap1(gc->stats(), gcstats::PhaseKind::SWEEP);
+        gcstats::AutoPhase ap2(gc->stats(), gcstats::PhaseKind::SWEEP_MARK);
+
+        gc->markAllWeakReferences(gcstats::PhaseKind::SWEEP_MARK_WEAK);
 
         /* Update zone state for gray marking. */
         for (GCZonesIter zone(runtime); !zone.done(); zone.next()) {
             MOZ_ASSERT(zone->isGCMarkingBlack());
             zone->setGCState(Zone::MarkGray);
         }
         gc->marker.setMarkColorGray();
 
-        gc->markAllGrayReferences(gcstats::PHASE_SWEEP_MARK_GRAY);
-        gc->markAllWeakReferences(gcstats::PHASE_SWEEP_MARK_GRAY_WEAK);
+        gc->markAllGrayReferences(gcstats::PhaseKind::SWEEP_MARK_GRAY);
+        gc->markAllWeakReferences(gcstats::PhaseKind::SWEEP_MARK_GRAY_WEAK);
 
         /* Restore zone state. */
         for (GCZonesIter zone(runtime); !zone.done(); zone.next()) {
             MOZ_ASSERT(zone->isGCMarkingGray());
             zone->setGCState(Zone::Mark);
         }
         MOZ_ASSERT(gc->marker.isDrained());
         gc->marker.setMarkColorBlack();
@@ -4765,19 +4765,19 @@ js::DelayCrossCompartmentGrayMarking(JSO
 #endif
 }
 
 static void
 MarkIncomingCrossCompartmentPointers(JSRuntime* rt, const uint32_t color)
 {
     MOZ_ASSERT(color == BLACK || color == GRAY);
 
-    static const gcstats::Phase statsPhases[] = {
-        gcstats::PHASE_SWEEP_MARK_INCOMING_BLACK,
-        gcstats::PHASE_SWEEP_MARK_INCOMING_GRAY
+    static const gcstats::PhaseKind statsPhases[] = {
+        gcstats::PhaseKind::SWEEP_MARK_INCOMING_BLACK,
+        gcstats::PhaseKind::SWEEP_MARK_INCOMING_GRAY
     };
     gcstats::AutoPhase ap1(rt->gc.stats(), statsPhases[color]);
 
     bool unlinkList = color == GRAY;
 
     for (GCCompartmentGroupIter c(rt); !c.done(); c.next()) {
         MOZ_ASSERT_IF(color == GRAY, c->zone()->isGCMarkingGray());
         MOZ_ASSERT_IF(color == BLACK, c->zone()->isGCMarkingBlack());
@@ -4891,25 +4891,25 @@ js::NotifyGCPostSwap(JSObject* a, JSObje
         DelayCrossCompartmentGrayMarking(b);
     if (removedFlags & JS_GC_SWAP_OBJECT_B_REMOVED)
         DelayCrossCompartmentGrayMarking(a);
 }
 
 void
 GCRuntime::endMarkingSweepGroup()
 {
-    gcstats::AutoPhase ap(stats(), gcstats::PHASE_SWEEP_MARK);
+    gcstats::AutoPhase ap(stats(), gcstats::PhaseKind::SWEEP_MARK);
 
     /*
      * Mark any incoming black pointers from previously swept compartments
      * whose referents are not marked. This can occur when gray cells become
      * black by the action of UnmarkGray.
      */
     MarkIncomingCrossCompartmentPointers(rt, BLACK);
-    markWeakReferencesInCurrentGroup(gcstats::PHASE_SWEEP_MARK_WEAK);
+    markWeakReferencesInCurrentGroup(gcstats::PhaseKind::SWEEP_MARK_WEAK);
 
     /*
      * Change state of current group to MarkGray to restrict marking to this
      * group.  Note that there may be pointers to the atoms compartment, and
      * these will be marked through, as they are not marked with
      * MarkCrossCompartmentXXX.
      */
     for (GCSweepGroupIter zone(rt); !zone.done(); zone.next()) {
@@ -4917,18 +4917,18 @@ GCRuntime::endMarkingSweepGroup()
         zone->setGCState(Zone::MarkGray);
     }
     marker.setMarkColorGray();
 
     /* Mark incoming gray pointers from previously swept compartments. */
     MarkIncomingCrossCompartmentPointers(rt, GRAY);
 
     /* Mark gray roots and mark transitively inside the current compartment group. */
-    markGrayReferencesInCurrentGroup(gcstats::PHASE_SWEEP_MARK_GRAY);
-    markWeakReferencesInCurrentGroup(gcstats::PHASE_SWEEP_MARK_GRAY_WEAK);
+    markGrayReferencesInCurrentGroup(gcstats::PhaseKind::SWEEP_MARK_GRAY);
+    markWeakReferencesInCurrentGroup(gcstats::PhaseKind::SWEEP_MARK_GRAY_WEAK);
 
     /* Restore marking state. */
     for (GCSweepGroupIter zone(rt); !zone.done(); zone.next()) {
         MOZ_ASSERT(zone->isGCMarkingGray());
         zone->setGCState(Zone::Mark);
     }
     MOZ_ASSERT(marker.isDrained());
     marker.setMarkColorBlack();
@@ -5058,64 +5058,64 @@ static void
 SweepUniqueIds(JSRuntime* runtime)
 {
     FreeOp fop(nullptr);
     for (GCSweepGroupIter zone(runtime); !zone.done(); zone.next())
         zone->sweepUniqueIds(&fop);
 }
 
 void
-GCRuntime::startTask(GCParallelTask& task, gcstats::Phase phase, AutoLockHelperThreadState& locked)
+GCRuntime::startTask(GCParallelTask& task, gcstats::PhaseKind phase, AutoLockHelperThreadState& locked)
 {
     if (!task.startWithLockHeld(locked)) {
         AutoUnlockHelperThreadState unlock(locked);
         gcstats::AutoPhase ap(stats(), phase);
         task.runFromActiveCooperatingThread(rt);
     }
 }
 
 void
-GCRuntime::joinTask(GCParallelTask& task, gcstats::Phase phase, AutoLockHelperThreadState& locked)
+GCRuntime::joinTask(GCParallelTask& task, gcstats::PhaseKind phase, AutoLockHelperThreadState& locked)
 {
     gcstats::AutoPhase ap(stats(), task, phase);
     task.joinWithLockHeld(locked);
 }
 
 void
 GCRuntime::sweepDebuggerOnMainThread(FreeOp* fop)
 {
     // Detach unreachable debuggers and global objects from each other.
     // This can modify weakmaps and so must happen before weakmap sweeping.
     Debugger::sweepAll(fop);
 
-    gcstats::AutoPhase ap(stats(), gcstats::PHASE_SWEEP_COMPARTMENTS);
+    gcstats::AutoPhase ap(stats(), gcstats::PhaseKind::SWEEP_COMPARTMENTS);
 
     // Sweep debug environment information. This performs lookups in the Zone's
     // unique IDs table and so must not happen in parallel with sweeping that
     // table.
     {
-        gcstats::AutoPhase ap2(stats(), gcstats::PHASE_SWEEP_MISC);
+        gcstats::AutoPhase ap2(stats(), gcstats::PhaseKind::SWEEP_MISC);
         for (GCCompartmentGroupIter c(rt); !c.done(); c.next())
             c->sweepDebugEnvironments();
     }
 
     // Sweep breakpoints. This is done here to be with the other debug sweeping,
     // although note that it can cause JIT code to be patched.
     {
-        gcstats::AutoPhase ap(stats(), gcstats::PHASE_SWEEP_BREAKPOINT);
+        gcstats::AutoPhase ap(stats(), gcstats::PhaseKind::SWEEP_BREAKPOINT);
         for (GCSweepGroupIter zone(rt); !zone.done(); zone.next())
             zone->sweepBreakpoints(fop);
     }
 }
 
 void
 GCRuntime::sweepJitDataOnMainThread(FreeOp* fop)
 {
     {
-        gcstats::AutoPhase ap(stats(), gcstats::PHASE_SWEEP_JIT_DATA);
+        gcstats::AutoPhase ap(stats(), gcstats::PhaseKind::SWEEP_JIT_DATA);
 
         // Cancel any active or pending off thread compilations.
         js::CancelOffThreadIonCompile(rt, JS::Zone::Sweep);
 
         for (GCCompartmentGroupIter c(rt); !c.done(); c.next())
             c->sweepJitCompartment(fop);
 
         for (GCSweepGroupIter zone(rt); !zone.done(); zone.next()) {
@@ -5127,24 +5127,24 @@ GCRuntime::sweepJitDataOnMainThread(Free
         // work on a single zone-group at once.
 
         // Sweep entries containing about-to-be-finalized JitCode and
         // update relocated TypeSet::Types inside the JitcodeGlobalTable.
         jit::JitRuntime::SweepJitcodeGlobalTable(rt);
     }
 
     {
-        gcstats::AutoPhase apdc(stats(), gcstats::PHASE_SWEEP_DISCARD_CODE);
+        gcstats::AutoPhase apdc(stats(), gcstats::PhaseKind::SWEEP_DISCARD_CODE);
         for (GCSweepGroupIter zone(rt); !zone.done(); zone.next())
             zone->discardJitCode(fop);
     }
 
     {
-        gcstats::AutoPhase ap1(stats(), gcstats::PHASE_SWEEP_TYPES);
-        gcstats::AutoPhase ap2(stats(), gcstats::PHASE_SWEEP_TYPES_BEGIN);
+        gcstats::AutoPhase ap1(stats(), gcstats::PhaseKind::SWEEP_TYPES);
+        gcstats::AutoPhase ap2(stats(), gcstats::PhaseKind::SWEEP_TYPES_BEGIN);
         for (GCSweepGroupIter zone(rt); !zone.done(); zone.next())
             zone->beginSweepTypes(fop, releaseObservedTypes && !zone->isPreservingCode());
     }
 }
 
 using WeakCacheTaskVector = mozilla::Vector<SweepWeakCacheTask, 0, SystemAllocPolicy>;
 
 template <typename Functor>
@@ -5188,21 +5188,21 @@ PrepareWeakCacheTasks(JSRuntime* rt)
     return tasks;
 }
 
 class MOZ_RAII js::gc::AutoRunParallelTask : public GCParallelTask
 {
     using Func = void (*)(JSRuntime*);
 
     Func func_;
-    gcstats::Phase phase_;
+    gcstats::PhaseKind phase_;
     AutoLockHelperThreadState& lock_;
 
   public:
-    AutoRunParallelTask(JSRuntime* rt, Func func, gcstats::Phase phase,
+    AutoRunParallelTask(JSRuntime* rt, Func func, gcstats::PhaseKind phase,
                        AutoLockHelperThreadState& lock)
       : GCParallelTask(rt),
         func_(func),
         phase_(phase),
         lock_(lock)
     {
         runtime()->gc.startTask(*this, phase_, lock_);
     }
@@ -5243,63 +5243,63 @@ GCRuntime::beginSweepingSweepGroup()
 #endif
     }
 
     validateIncrementalMarking();
 
     FreeOp fop(rt);
 
     {
-        AutoPhase ap(stats(), PHASE_FINALIZE_START);
+        AutoPhase ap(stats(), PhaseKind::FINALIZE_START);
         callFinalizeCallbacks(&fop, JSFINALIZE_GROUP_PREPARE);
         {
-            AutoPhase ap2(stats(), PHASE_WEAK_ZONES_CALLBACK);
+            AutoPhase ap2(stats(), PhaseKind::WEAK_ZONES_CALLBACK);
             callWeakPointerZonesCallbacks();
         }
         {
-            AutoPhase ap2(stats(), PHASE_WEAK_COMPARTMENT_CALLBACK);
+            AutoPhase ap2(stats(), PhaseKind::WEAK_COMPARTMENT_CALLBACK);
             for (GCSweepGroupIter zone(rt); !zone.done(); zone.next()) {
                 for (CompartmentsInZoneIter comp(zone); !comp.done(); comp.next())
                     callWeakPointerCompartmentCallbacks(comp);
             }
         }
         callFinalizeCallbacks(&fop, JSFINALIZE_GROUP_START);
     }
 
     sweepDebuggerOnMainThread(&fop);
 
     {
         AutoLockHelperThreadState lock;
 
         Maybe<AutoRunParallelTask> sweepAtoms;
         if (sweepingAtoms)
-            sweepAtoms.emplace(rt, SweepAtoms, PHASE_SWEEP_ATOMS, lock);
-
-        AutoPhase ap(stats(), PHASE_SWEEP_COMPARTMENTS);
+            sweepAtoms.emplace(rt, SweepAtoms, PhaseKind::SWEEP_ATOMS, lock);
+
+        AutoPhase ap(stats(), PhaseKind::SWEEP_COMPARTMENTS);
         AutoSCC scc(stats(), sweepGroupIndex);
 
-        AutoRunParallelTask sweepCCWrappers(rt, SweepCCWrappers, PHASE_SWEEP_CC_WRAPPER, lock);
-        AutoRunParallelTask sweepObjectGroups(rt, SweepObjectGroups, PHASE_SWEEP_TYPE_OBJECT, lock);
-        AutoRunParallelTask sweepRegExps(rt, SweepRegExps, PHASE_SWEEP_REGEXP, lock);
-        AutoRunParallelTask sweepMisc(rt, SweepMisc, PHASE_SWEEP_MISC, lock);
-        AutoRunParallelTask sweepCompTasks(rt, SweepCompressionTasks, PHASE_SWEEP_COMPRESSION, lock);
-        AutoRunParallelTask sweepWeakMaps(rt, SweepWeakMaps, PHASE_SWEEP_WEAKMAPS, lock);
-        AutoRunParallelTask sweepUniqueIds(rt, SweepUniqueIds, PHASE_SWEEP_UNIQUEIDS, lock);
+        AutoRunParallelTask sweepCCWrappers(rt, SweepCCWrappers, PhaseKind::SWEEP_CC_WRAPPER, lock);
+        AutoRunParallelTask sweepObjectGroups(rt, SweepObjectGroups, PhaseKind::SWEEP_TYPE_OBJECT, lock);
+        AutoRunParallelTask sweepRegExps(rt, SweepRegExps, PhaseKind::SWEEP_REGEXP, lock);
+        AutoRunParallelTask sweepMisc(rt, SweepMisc, PhaseKind::SWEEP_MISC, lock);
+        AutoRunParallelTask sweepCompTasks(rt, SweepCompressionTasks, PhaseKind::SWEEP_COMPRESSION, lock);
+        AutoRunParallelTask sweepWeakMaps(rt, SweepWeakMaps, PhaseKind::SWEEP_WEAKMAPS, lock);
+        AutoRunParallelTask sweepUniqueIds(rt, SweepUniqueIds, PhaseKind::SWEEP_UNIQUEIDS, lock);
 
         WeakCacheTaskVector sweepCacheTasks = PrepareWeakCacheTasks(rt);
         for (auto& task : sweepCacheTasks)
-            startTask(task, PHASE_SWEEP_WEAK_CACHES, lock);
+            startTask(task, PhaseKind::SWEEP_WEAK_CACHES, lock);
 
         {
             AutoUnlockHelperThreadState unlock(lock);
             sweepJitDataOnMainThread(&fop);
         }
 
         for (auto& task : sweepCacheTasks)
-            joinTask(task, PHASE_SWEEP_WEAK_CACHES, lock);
+            joinTask(task, PhaseKind::SWEEP_WEAK_CACHES, lock);
     }
 
     // Queue all GC things in all zones for sweeping, either on the foreground
     // or on the background thread.
 
     for (GCSweepGroupIter zone(rt); !zone.done(); zone.next()) {
         AutoSCC scc(stats(), sweepGroupIndex);
 
@@ -5317,17 +5317,17 @@ GCRuntime::beginSweepingSweepGroup()
     sweepZone = currentSweepGroup;
     sweepActionIndex = 0;
 }
 
 void
 GCRuntime::endSweepingSweepGroup()
 {
     {
-        gcstats::AutoPhase ap(stats(), gcstats::PHASE_FINALIZE_END);
+        gcstats::AutoPhase ap(stats(), gcstats::PhaseKind::FINALIZE_END);
         FreeOp fop(rt);
         callFinalizeCallbacks(&fop, JSFINALIZE_GROUP_END);
     }
 
     /* Update the GC state for zones we have swept. */
     for (GCSweepGroupIter zone(rt); !zone.done(); zone.next()) {
         MOZ_ASSERT(zone->isGCSweeping());
         AutoLockGC lock(rt);
@@ -5366,17 +5366,17 @@ GCRuntime::beginSweepPhase(JS::gcreason:
     MOZ_ASSERT(!abortSweepAfterCurrentGroup);
 
     AutoSetThreadIsSweeping threadIsSweeping;
 
     releaseHeldRelocatedArenas();
 
     computeNonIncrementalMarkingForValidation(lock);
 
-    gcstats::AutoPhase ap(stats(), gcstats::PHASE_SWEEP);
+    gcstats::AutoPhase ap(stats(), gcstats::PhaseKind::SWEEP);
 
     sweepOnBackgroundThread =
         reason != JS::gcreason::DESTROY_RUNTIME && !TraceEnabled() && CanUseExtraThreads();
 
     releaseObservedTypes = shouldReleaseObservedTypes();
 
     AssertNoWrappersInGrayList(rt);
     DropStringWrappers(rt);
@@ -5417,17 +5417,17 @@ ArenaLists::foregroundFinalize(FreeOp* f
         arenaLists(thingKind) =
             finalized.insertListWithCursorAtEnd(arenaLists(thingKind));
     }
 
     return true;
 }
 
 IncrementalProgress
-GCRuntime::drainMarkStack(SliceBudget& sliceBudget, gcstats::Phase phase)
+GCRuntime::drainMarkStack(SliceBudget& sliceBudget, gcstats::PhaseKind phase)
 {
     /* Run a marking slice and return whether the stack is now empty. */
     gcstats::AutoPhase ap(stats(), phase);
     return marker.drainMarkStack(sliceBudget) ? Finished : NotFinished;
 }
 
 static void
 SweepThing(Shape* shape)
@@ -5475,32 +5475,32 @@ GCRuntime::sweepTypeInformation(GCRuntim
     // and dead scripts and object groups, so that no dead references remain in
     // them. Type inference can end up crawling these zones again, such as for
     // TypeCompartment::markSetsUnknown, and if this happens after sweeping for
     // the sweep group finishes we won't be able to determine which things in
     // the zone are live.
 
     MOZ_ASSERT(kind == AllocKind::LIMIT);
 
-    gcstats::AutoPhase ap1(gc->stats(), gcstats::PHASE_SWEEP_COMPARTMENTS);
-    gcstats::AutoPhase ap2(gc->stats(), gcstats::PHASE_SWEEP_TYPES);
+    gcstats::AutoPhase ap1(gc->stats(), gcstats::PhaseKind::SWEEP_COMPARTMENTS);
+    gcstats::AutoPhase ap2(gc->stats(), gcstats::PhaseKind::SWEEP_TYPES);
 
     ArenaLists& al = zone->arenas;
 
     AutoClearTypeInferenceStateOnOOM oom(zone);
 
     if (!SweepArenaList<JSScript>(&al.gcScriptArenasToUpdate.ref(), budget, &oom))
         return NotFinished;
 
     if (!SweepArenaList<ObjectGroup>(&al.gcObjectGroupArenasToUpdate.ref(), budget, &oom))
         return NotFinished;
 
     // Finish sweeping type information in the zone.
     {
-        gcstats::AutoPhase ap(gc->stats(), gcstats::PHASE_SWEEP_TYPES_END);
+        gcstats::AutoPhase ap(gc->stats(), gcstats::PhaseKind::SWEEP_TYPES_END);
         zone->types.endSweep(gc->rt);
     }
 
     return Finished;
 }
 
 /* static */ IncrementalProgress
 GCRuntime::mergeSweptObjectArenas(GCRuntime* gc, FreeOp* fop, Zone* zone, SliceBudget& budget,
@@ -5536,17 +5536,17 @@ GCRuntime::finalizeAllocKind(GCRuntime* 
 /* static */ IncrementalProgress
 GCRuntime::sweepShapeTree(GCRuntime* gc, FreeOp* fop, Zone* zone, SliceBudget& budget,
                           AllocKind kind)
 {
     // Remove dead shapes from the shape tree, but don't finalize them yet.
 
     MOZ_ASSERT(kind == AllocKind::LIMIT);
 
-    gcstats::AutoPhase ap(gc->stats(), gcstats::PHASE_SWEEP_SHAPE);
+    gcstats::AutoPhase ap(gc->stats(), gcstats::PhaseKind::SWEEP_SHAPE);
 
     ArenaLists& al = zone->arenas;
 
     if (!SweepArenaList<Shape>(&al.gcShapeArenasToUpdate.ref(), budget))
         return NotFinished;
 
     if (!SweepArenaList<AccessorShape>(&al.gcAccessorShapeArenasToUpdate.ref(), budget))
         return NotFinished;
@@ -5593,20 +5593,20 @@ GCRuntime::initializeSweepActions()
     return ok;
 }
 
 IncrementalProgress
 GCRuntime::performSweepActions(SliceBudget& budget, AutoLockForExclusiveAccess& lock)
 {
     AutoSetThreadIsSweeping threadIsSweeping;
 
-    gcstats::AutoPhase ap(stats(), gcstats::PHASE_SWEEP);
+    gcstats::AutoPhase ap(stats(), gcstats::PhaseKind::SWEEP);
     FreeOp fop(rt);
 
-    if (drainMarkStack(budget, gcstats::PHASE_SWEEP_MARK) == NotFinished)
+    if (drainMarkStack(budget, gcstats::PhaseKind::SWEEP_MARK) == NotFinished)
         return NotFinished;
 
     for (;;) {
         for (; sweepPhaseIndex < SweepPhases.length(); sweepPhaseIndex++) {
             const auto& actions = SweepPhases[sweepPhaseIndex];
             for (; sweepZone; sweepZone = sweepZone->nextNodeInGroup()) {
                 for (; sweepActionIndex < actions.length(); sweepActionIndex++) {
                     const auto& action = actions[sweepActionIndex];
@@ -5659,17 +5659,17 @@ GCRuntime::allCCVisibleZonesWereCollecte
     return true;
 }
 
 void
 GCRuntime::endSweepPhase(bool destroyingRuntime, AutoLockForExclusiveAccess& lock)
 {
     AutoSetThreadIsSweeping threadIsSweeping;
 
-    gcstats::AutoPhase ap(stats(), gcstats::PHASE_SWEEP);
+    gcstats::AutoPhase ap(stats(), gcstats::PhaseKind::SWEEP);
     FreeOp fop(rt);
 
     MOZ_ASSERT_IF(destroyingRuntime, !sweepOnBackgroundThread);
 
     /*
      * Recalculate whether GC was full or not as this may have changed due to
      * newly created zones.  Can only change from full to not full.
      */
@@ -5678,17 +5678,17 @@ GCRuntime::endSweepPhase(bool destroying
             if (!zone->isCollecting()) {
                 isFull = false;
                 break;
             }
         }
     }
 
     {
-        gcstats::AutoPhase ap(stats(), gcstats::PHASE_DESTROY);
+        gcstats::AutoPhase ap(stats(), gcstats::PhaseKind::DESTROY);
 
         /*
          * Sweep script filenames after sweeping functions in the generic loop
          * above. In this way when a scripted function's finalizer destroys the
          * script and calls rt->destroyScriptHook, the hook can still access the
          * script's filename. See bug 323267.
          */
         SweepScriptData(rt, lock);
@@ -5696,17 +5696,17 @@ GCRuntime::endSweepPhase(bool destroying
         /* Clear out any small pools that we're hanging on to. */
         if (rt->hasJitRuntime()) {
             rt->jitRuntime()->execAlloc().purge();
             rt->jitRuntime()->backedgeExecAlloc().purge();
         }
     }
 
     {
-        gcstats::AutoPhase ap(stats(), gcstats::PHASE_FINALIZE_END);
+        gcstats::AutoPhase ap(stats(), gcstats::PhaseKind::FINALIZE_END);
         callFinalizeCallbacks(&fop, JSFINALIZE_COLLECTION_END);
 
         if (allCCVisibleZonesWereCollected())
             grayBitsValid = true;
     }
 
     finishMarkingValidation();
 
@@ -5723,17 +5723,17 @@ GCRuntime::endSweepPhase(bool destroying
     AssertNoWrappersInGrayList(rt);
 }
 
 void
 GCRuntime::beginCompactPhase()
 {
     MOZ_ASSERT(!isBackgroundSweeping());
 
-    gcstats::AutoPhase ap(stats(), gcstats::PHASE_COMPACT);
+    gcstats::AutoPhase ap(stats(), gcstats::PhaseKind::COMPACT);
 
     MOZ_ASSERT(zonesToMaybeCompact.ref().isEmpty());
     for (GCZonesIter zone(rt); !zone.done(); zone.next()) {
         if (CanRelocateZone(zone))
             zonesToMaybeCompact.ref().append(zone);
     }
 
     MOZ_ASSERT(!relocatedArenasToRelease);
@@ -5742,17 +5742,17 @@ GCRuntime::beginCompactPhase()
 
 IncrementalProgress
 GCRuntime::compactPhase(JS::gcreason::Reason reason, SliceBudget& sliceBudget,
                         AutoLockForExclusiveAccess& lock)
 {
     assertBackgroundSweepingFinished();
     MOZ_ASSERT(startedCompacting);
 
-    gcstats::AutoPhase ap(stats(), gcstats::PHASE_COMPACT);
+    gcstats::AutoPhase ap(stats(), gcstats::PhaseKind::COMPACT);
 
     // TODO: JSScripts can move. If the sampler interrupts the GC in the
     // middle of relocating an arena, invalid JSScript pointers may be
     // accessed. Suppress all sampling until a finer-grained solution can be
     // found. See bug 1295775.
     AutoSuppressProfilerSampling suppressSampling(TlsContext.get());
 
     ZoneList relocatedZones;
@@ -5960,25 +5960,25 @@ GCRuntime::resetIncrementalGC(gc::AbortR
         isCompacting = false;
 
         auto unlimited = SliceBudget::unlimited();
         incrementalCollectSlice(unlimited, JS::gcreason::RESET, lock);
 
         isCompacting = wasCompacting;
 
         {
-            gcstats::AutoPhase ap(stats(), gcstats::PHASE_WAIT_BACKGROUND_THREAD);
+            gcstats::AutoPhase ap(stats(), gcstats::PhaseKind::WAIT_BACKGROUND_THREAD);
             rt->gc.waitBackgroundSweepOrAllocEnd();
         }
         break;
       }
 
       case State::Finalize: {
         {
-            gcstats::AutoPhase ap(stats(), gcstats::PHASE_WAIT_BACKGROUND_THREAD);
+            gcstats::AutoPhase ap(stats(), gcstats::PhaseKind::WAIT_BACKGROUND_THREAD);
             rt->gc.waitBackgroundSweepOrAllocEnd();
         }
 
         bool wasCompacting = isCompacting;
         isCompacting = false;
 
         auto unlimited = SliceBudget::unlimited();
         incrementalCollectSlice(unlimited, JS::gcreason::RESET, lock);
@@ -6164,17 +6164,17 @@ GCRuntime::incrementalCollectSlice(Slice
             AutoGCRooter::traceAllWrappers(target, &marker);
 
         /* If we needed delayed marking for gray roots, then collect until done. */
         if (!hasBufferedGrayRoots()) {
             budget.makeUnlimited();
             isIncremental = false;
         }
 
-        if (drainMarkStack(budget, gcstats::PHASE_MARK) == NotFinished)
+        if (drainMarkStack(budget, gcstats::PhaseKind::MARK) == NotFinished)
             break;
 
         MOZ_ASSERT(marker.isDrained());
 
         /*
          * In incremental GCs where we have already performed more than once
          * slice we yield after marking with the aim of starting the sweep in
          * the next slice, since the first slice of sweeping can be expensive.
@@ -6225,34 +6225,34 @@ GCRuntime::incrementalCollectSlice(Slice
         endSweepPhase(destroyingRuntime, lock);
 
         incrementalState = State::Finalize;
 
         MOZ_FALLTHROUGH;
 
       case State::Finalize:
         {
-            gcstats::AutoPhase ap(stats(), gcstats::PHASE_WAIT_BACKGROUND_THREAD);
+            gcstats::AutoPhase ap(stats(), gcstats::PhaseKind::WAIT_BACKGROUND_THREAD);
 
             // Yield until background finalization is done.
             if (!budget.isUnlimited()) {
                 // Poll for end of background sweeping
                 AutoLockGC lock(rt);
                 if (isBackgroundSweeping())
                     break;
             } else {
                 waitBackgroundSweepEnd();
             }
         }
 
         {
             // Re-sweep the zones list, now that background finalization is
             // finished to actually remove and free dead zones.
-            gcstats::AutoPhase ap1(stats(), gcstats::PHASE_SWEEP);
-            gcstats::AutoPhase ap2(stats(), gcstats::PHASE_DESTROY);
+            gcstats::AutoPhase ap1(stats(), gcstats::PhaseKind::SWEEP);
+            gcstats::AutoPhase ap2(stats(), gcstats::PhaseKind::DESTROY);
             AutoSetThreadIsSweeping threadIsSweeping;
             FreeOp fop(rt);
             sweepZoneGroups(&fop, destroyingRuntime);
         }
 
         MOZ_ASSERT(!startedCompacting);
         incrementalState = State::Compact;
 
@@ -6275,17 +6275,17 @@ GCRuntime::incrementalCollectSlice(Slice
 
         startDecommit();
         incrementalState = State::Decommit;
 
         MOZ_FALLTHROUGH;
 
       case State::Decommit:
         {
-            gcstats::AutoPhase ap(stats(), gcstats::PHASE_WAIT_BACKGROUND_THREAD);
+            gcstats::AutoPhase ap(stats(), gcstats::PhaseKind::WAIT_BACKGROUND_THREAD);
 
             // Yield until background decommit is done.
             if (!budget.isUnlimited() && decommitTask.isRunning())
                 break;
 
             decommitTask.join();
         }
 
@@ -6469,17 +6469,17 @@ GCRuntime::gcCycle(bool nonincrementalBy
     // It's ok if threads other than the active thread have suppressGC set, as
     // they are operating on zones which will not be collected from here.
     MOZ_ASSERT(!TlsContext.get()->suppressGC);
 
     // Assert if this is a GC unsafe region.
     TlsContext.get()->verifyIsSafeToGC();
 
     {
-        gcstats::AutoPhase ap(stats(), gcstats::PHASE_WAIT_BACKGROUND_THREAD);
+        gcstats::AutoPhase ap(stats(), gcstats::PhaseKind::WAIT_BACKGROUND_THREAD);
 
         // Background finalization and decommit are finished by defininition
         // before we can start a new GC session.
         if (!isIncrementalGCInProgress()) {
             assertBackgroundSweepingFinished();
             MOZ_ASSERT(!decommitTask.isRunning());
         }
 
@@ -6683,17 +6683,17 @@ GCRuntime::collect(bool nonincrementalBy
         repeat = (poked && cleanUpEverything) || wasReset || repeatForDeadZone;
     } while (repeat);
 
     if (reason == JS::gcreason::COMPARTMENT_REVIVED)
         maybeDoCycleCollection();
 
 #ifdef JS_GC_ZEAL
     if (rt->hasZealMode(ZealMode::CheckHeapAfterGC)) {
-        gcstats::AutoPhase ap(rt->gc.stats(), gcstats::PHASE_TRACE_HEAP);
+        gcstats::AutoPhase ap(rt->gc.stats(), gcstats::PhaseKind::TRACE_HEAP);
         CheckHeapAfterGC(rt);
     }
 #endif
 }
 
 js::AutoEnqueuePendingParseTasksAfterGC::~AutoEnqueuePendingParseTasksAfterGC()
 {
     if (!OffThreadParsingMustWaitForGC(gc_.rt))
@@ -6861,17 +6861,17 @@ GCRuntime::onOutOfMallocMemory(const Aut
 
     // Immediately decommit as many arenas as possible in the hopes that this
     // might let the OS scrape together enough pages to satisfy the failing
     // malloc request.
     decommitAllWithoutUnlocking(lock);
 }
 
 void
-GCRuntime::minorGC(JS::gcreason::Reason reason, gcstats::Phase phase)
+GCRuntime::minorGC(JS::gcreason::Reason reason, gcstats::PhaseKind phase)
 {
     MOZ_ASSERT(!JS::CurrentThreadIsHeapBusy());
 
     if (TlsContext.get()->suppressGC)
         return;
 
     gcstats::AutoPhase ap(rt->gc.stats(), phase);
 
--- a/js/src/jsweakmap.h
+++ b/js/src/jsweakmap.h
@@ -103,17 +103,17 @@ class WeakMapBase : public mozilla::Link
     // Zone containing this weak map.
     JS::Zone* zone_;
 
     // Whether this object has been traced during garbage collection.
     bool marked;
 };
 
 template <typename T>
-static T extractUnbarriered(WriteBarrieredBase<T> v)
+static T extractUnbarriered(const WriteBarrieredBase<T>& v)
 {
     return v.get();
 }
 template <typename T>
 static T* extractUnbarriered(T* v)
 {
     return v;
 }
--- a/js/src/moz.build
+++ b/js/src/moz.build
@@ -734,8 +734,16 @@ if CONFIG['GNU_CXX']:
     CXXFLAGS += ['-Wno-shadow', '-Werror=format']
 
 # Suppress warnings in third-party code.
 if CONFIG['CLANG_CXX']:
     SOURCES['jsdtoa.cpp'].flags += ['-Wno-implicit-fallthrough']
 
 if CONFIG['OS_ARCH'] == 'WINNT':
     DEFINES['NOMINMAX'] = True
+
+# Generate GC statistics phase data.
+GENERATED_FILES += ['gc/StatsPhasesGenerated.h']
+StatsPhasesGeneratedHeader = GENERATED_FILES['gc/StatsPhasesGenerated.h']
+StatsPhasesGeneratedHeader.script = 'gc/GenerateStatsPhases.py:generateHeader'
+GENERATED_FILES += ['gc/StatsPhasesGenerated.cpp']
+StatsPhasesGeneratedCpp = GENERATED_FILES['gc/StatsPhasesGenerated.cpp']
+StatsPhasesGeneratedCpp.script = 'gc/GenerateStatsPhases.py:generateCpp'
--- a/js/src/vm/SelfHosting.cpp
+++ b/js/src/vm/SelfHosting.cpp
@@ -498,49 +498,41 @@ intrinsic_FinishBoundFunctionInit(JSCont
 
         // 19.2.3.2 Function.prototype.bind, step 7 (implicit).
     }
 
     // 19.2.3.2 Function.prototype.bind, step 8.
     bound->setExtendedSlot(BOUND_FUN_LENGTH_SLOT, NumberValue(length));
 
     // Try to avoid invoking the resolve hook.
-    JSAtom* name = nullptr;
-    if (targetObj->is<JSFunction>() && !targetObj->as<JSFunction>().hasResolvedName())
-        name = targetObj->as<JSFunction>().getUnresolvedName(cx);
-
-    RootedString rootedName(cx);
-    if (name) {
-        rootedName = name;
-    } else {
+    RootedAtom name(cx);
+    if (targetObj->is<JSFunction>() && !targetObj->as<JSFunction>().hasResolvedName()) {
+        if (!JSFunction::getUnresolvedName(cx, targetObj.as<JSFunction>(), &name))
+            return false;
+    }
+
+    // 19.2.3.2 Function.prototype.bind, steps 9-11.
+    if (!name) {
         // 19.2.3.2 Function.prototype.bind, step 9.
         RootedValue targetName(cx);
         if (!GetProperty(cx, targetObj, targetObj, cx->names().name, &targetName))
             return false;
 
         // 19.2.3.2 Function.prototype.bind, step 10.
-        if (targetName.isString())
-            rootedName = targetName.toString();
+        if (targetName.isString() && !targetName.toString()->empty()) {
+            name = AtomizeString(cx, targetName.toString());
+            if (!name)
+                return false;
+        } else {
+            name = cx->names().empty;
+        }
     }
 
-    // 19.2.3.2 Function.prototype.bind, step 11 (Inlined SetFunctionName).
     MOZ_ASSERT(!bound->hasGuessedAtom());
-    if (rootedName && !rootedName->empty()) {
-        StringBuffer sb(cx);
-        if (!sb.append(cx->names().boundWithSpace) || !sb.append(rootedName))
-            return false;
-
-        RootedAtom nameAtom(cx, sb.finishAtom());
-        if (!nameAtom)
-            return false;
-
-        bound->setAtom(nameAtom);
-    } else {
-        bound->setAtom(cx->names().boundWithSpace);
-    }
+    bound->setAtom(name);
 
     args.rval().setUndefined();
     return true;
 }
 
 /*
  * Used to decompile values in the nearest non-builtin stack frame, falling
  * back to decompiling in the current frame. Helpful for printing higher-order
--- a/layout/base/nsLayoutUtils.cpp
+++ b/layout/base/nsLayoutUtils.cpp
@@ -7107,17 +7107,18 @@ nsLayoutUtils::GetDisplayRootFrame(nsIFr
   }
 }
 
 /* static */ nsIFrame*
 nsLayoutUtils::GetReferenceFrame(nsIFrame* aFrame)
 {
   nsIFrame *f = aFrame;
   for (;;) {
-    if (f->IsTransformed() || f->IsPreserve3DLeaf() || IsPopup(f)) {
+    const nsStyleDisplay* disp = f->StyleDisplay();
+    if (f->IsTransformed(disp) || f->IsPreserve3DLeaf(disp) || IsPopup(f)) {
       return f;
     }
     nsIFrame* parent = GetCrossDocParentFrame(f);
     if (!parent) {
       return f;
     }
     f = parent;
   }
--- a/layout/generic/ReflowInput.cpp
+++ b/layout/generic/ReflowInput.cpp
@@ -489,17 +489,17 @@ void ReflowInput::InitCBReflowInput()
     mCBReflowInput = nullptr;
     return;
   }
   if (mParentReflowInput->mFlags.mDummyParentReflowInput) {
     mCBReflowInput = mParentReflowInput;
     return;
   }
 
-  if (mParentReflowInput->mFrame == mFrame->GetContainingBlock()) {
+  if (mParentReflowInput->mFrame == mFrame->GetContainingBlock(0, mStyleDisplay)) {
     // Inner table frames need to use the containing block of the outer
     // table frame.
     if (mFrame->IsTableFrame()) {
       mCBReflowInput = mParentReflowInput->mCBReflowInput;
     } else {
       mCBReflowInput = mParentReflowInput;
     }
   } else {
@@ -521,17 +521,17 @@ IsQuirkContainingBlockHeight(const Reflo
   if (LayoutFrameType::Block == aFrameType ||
 #ifdef MOZ_XUL
       LayoutFrameType::XULLabel == aFrameType ||
 #endif
       LayoutFrameType::Scroll == aFrameType) {
     // Note: This next condition could change due to a style change,
     // but that would cause a style reflow anyway, which means we're ok.
     if (NS_AUTOHEIGHT == rs->ComputedHeight()) {
-      if (!rs->mFrame->IsAbsolutelyPositioned()) {
+      if (!rs->mFrame->IsAbsolutelyPositioned(rs->mStyleDisplay)) {
         return false;
       }
     }
   }
   return true;
 }
 
 void
@@ -1975,17 +1975,17 @@ CalcQuirkContainingBlockHeight(const Ref
       secondAncestorRI = firstAncestorRI;
       firstAncestorRI = rs;
 
       // If the current frame we're looking at is positioned, we don't want to
       // go any further (see bug 221784).  The behavior we want here is: 1) If
       // not auto-height, use this as the percentage base.  2) If auto-height,
       // keep looking, unless the frame is positioned.
       if (NS_AUTOHEIGHT == rs->ComputedHeight()) {
-        if (rs->mFrame->IsAbsolutelyPositioned()) {
+        if (rs->mFrame->IsAbsolutelyPositioned(rs->mStyleDisplay)) {
           break;
         } else {
           continue;
         }
       }
     } else if (LayoutFrameType::Canvas == frameType) {
       // Always continue on to the height calculation
     } else if (LayoutFrameType::PageContent == frameType) {
@@ -2056,17 +2056,17 @@ ReflowInput::ComputeContainingBlockRecta
   // formed by the content edge of the nearest block-level ancestor
   LogicalSize cbSize = aContainingBlockRI->ComputedSize();
 
   WritingMode wm = aContainingBlockRI->GetWritingMode();
 
   // mFrameType for abs-pos tables is NS_CSS_FRAME_TYPE_BLOCK, so we need to
   // special case them here.
   if (NS_FRAME_GET_TYPE(mFrameType) == NS_CSS_FRAME_TYPE_ABSOLUTE ||
-      (mFrame->IsTableFrame() && mFrame->IsAbsolutelyPositioned() &&
+      (mFrame->IsTableFrame() && mFrame->IsAbsolutelyPositioned(mStyleDisplay) &&
        (mFrame->GetParent()->GetStateBits() & NS_FRAME_OUT_OF_FLOW))) {
     // See if the ancestor is block-level or inline-level
     if (NS_FRAME_GET_TYPE(aContainingBlockRI->mFrameType) == NS_CSS_FRAME_TYPE_INLINE) {
       // Base our size on the actual size of the frame.  In cases when this is
       // completely bogus (eg initial reflow), this code shouldn't even be
       // called, since the code in nsInlineFrame::Reflow will pass in
       // the containing block dimensions to our constructor.
       // XXXbz we should be taking the in-flows into account too, but
@@ -2170,17 +2170,17 @@ ReflowInput::InitConstraints(nsPresConte
                            aContainingBlockSize.BSize(wm),
                            aBorder, aPadding);
 
   // If this is a reflow root, then set the computed width and
   // height equal to the available space
   if (nullptr == mParentReflowInput || mFlags.mDummyParentReflowInput) {
     // XXXldb This doesn't mean what it used to!
     InitOffsets(wm, OffsetPercentBasis(mFrame, wm, aContainingBlockSize),
-                aFrameType, mFlags, aBorder, aPadding);
+                aFrameType, mFlags, aBorder, aPadding, mStyleDisplay);
     // Override mComputedMargin since reflow roots start from the
     // frame's boundary, which is inside the margin.
     ComputedPhysicalMargin().SizeTo(0, 0, 0, 0);
     ComputedPhysicalOffsets().SizeTo(0, 0, 0, 0);
 
     ComputedISize() =
       AvailableISize() - ComputedLogicalBorderPadding().IStartEnd(wm);
     if (ComputedISize() < 0) {
@@ -2229,17 +2229,17 @@ ReflowInput::InitConstraints(nsPresConte
     // XXX Might need to also pass the CB height (not width) for page boxes,
     // too, if we implement them.
 
     // For calculating positioning offsets, margins, borders and
     // padding, we use the writing mode of the containing block
     WritingMode cbwm = cbrs->GetWritingMode();
     InitOffsets(cbwm, OffsetPercentBasis(mFrame, cbwm,
                                          cbSize.ConvertTo(cbwm, wm)),
-                aFrameType, mFlags, aBorder, aPadding);
+                aFrameType, mFlags, aBorder, aPadding, mStyleDisplay);
 
     // For calculating the size of this box, we use its own writing mode
     const nsStyleCoord &blockSize = mStylePosition->BSize(wm);
     nsStyleUnit blockSizeUnit = blockSize.GetUnit();
 
     // Check for a percentage based block size and a containing block
     // block size that depends on the content block size
     // XXX twiddling blockSizeUnit doesn't help anymore
@@ -2501,17 +2501,18 @@ UpdateProp(FrameProperties& aProps,
 }
 
 void
 SizeComputationInput::InitOffsets(WritingMode aWM,
                                   const LogicalSize& aPercentBasis,
                                   LayoutFrameType aFrameType,
                                   ReflowInputFlags aFlags,
                                   const nsMargin* aBorder,
-                                  const nsMargin* aPadding)
+                                  const nsMargin* aPadding,
+                                  const nsStyleDisplay* aDisplay)
 {
   DISPLAY_INIT_OFFSETS(mFrame, this, aPercentBasis, aBorder, aPadding);
 
   // Since we are in reflow, we don't need to store these properties anymore
   // unless they are dependent on width, in which case we store the new value.
   nsPresContext *presContext = mFrame->PresContext();
   FrameProperties props(presContext->PropertyTable(), mFrame);
   props.Delete(nsIFrame::UsedBorderProperty());
@@ -2524,17 +2525,17 @@ SizeComputationInput::InitOffsets(Writin
   // XXX We need to include 'auto' horizontal margins in this too!
   // ... but if we did that, we'd need to fix nsFrame::GetUsedMargin
   // to use it even when the margins are all zero (since sometimes
   // they get treated as auto)
   ::UpdateProp(props, nsIFrame::UsedMarginProperty(), needMarginProp,
                ComputedPhysicalMargin());
 
 
-  const nsStyleDisplay *disp = mFrame->StyleDisplay();
+  const nsStyleDisplay* disp = mFrame->StyleDisplayWithOptionalParam(aDisplay);
   bool isThemed = mFrame->IsThemed(disp);
   bool needPaddingProp;
   nsIntMargin widget;
   if (isThemed &&
       presContext->GetTheme()->GetWidgetPadding(presContext->DeviceContext(),
                                                 mFrame, disp->UsedAppearance(),
                                                 &widget)) {
     ComputedPhysicalPadding().top = presContext->DevPixelsToAppUnits(widget.top);
--- a/layout/generic/ReflowInput.h
+++ b/layout/generic/ReflowInput.h
@@ -296,17 +296,18 @@ private:
                       mozilla::LayoutFrameType aFrameType);
 
 protected:
   void InitOffsets(mozilla::WritingMode aWM,
                    const mozilla::LogicalSize& aPercentBasis,
                    mozilla::LayoutFrameType aFrameType,
                    ReflowInputFlags aFlags,
                    const nsMargin* aBorder = nullptr,
-                   const nsMargin* aPadding = nullptr);
+                   const nsMargin* aPadding = nullptr,
+                   const nsStyleDisplay* aDisplay = nullptr);
 
   /*
    * Convert nsStyleCoord to nscoord when percentages depend on the
    * inline size of the containing block, and enumerated values are for
    * inline size, min-inline-size, or max-inline-size.  Does not handle
    * auto inline sizes.
    */
   inline nscoord ComputeISizeValue(nscoord aContainingBlockISize,
--- a/layout/generic/nsBlockFrame.cpp
+++ b/layout/generic/nsBlockFrame.cpp
@@ -1448,17 +1448,17 @@ nsBlockFrame::Reflow(nsPresContext*     
       AutoLineCursorSetup autoLineCursor(this);
       absoluteContainer->Reflow(this, aPresContext, *reflowInput,
                                 state.mReflowStatus,
                                 containingBlock, flags,
                                 &aMetrics.mOverflowAreas);
     }
   }
 
-  FinishAndStoreOverflow(&aMetrics);
+  FinishAndStoreOverflow(&aMetrics, reflowInput->mStyleDisplay);
 
   aStatus = state.mReflowStatus;
 
 #ifdef DEBUG
   // Between when we drain pushed floats and when we complete reflow,
   // we're allowed to have multiple continuations of the same float on
   // our floats list, since a first-in-flow might get pushed to a later
   // continuation of its containing block.  But it's not permitted
--- a/layout/generic/nsFirstLetterFrame.cpp
+++ b/layout/generic/nsFirstLetterFrame.cpp
@@ -211,33 +211,33 @@ nsFirstLetterFrame::Reflow(nsPresContext
     // In the floating first-letter case, we need to set this ourselves;
     // nsLineLayout::BeginSpan will set it in the other case
     mBaseline = kidMetrics.BlockStartAscent();
 
     // Place and size the child and update the output metrics
     LogicalSize convertedSize = kidMetrics.Size(lineWM).ConvertTo(wm, lineWM);
     kid->SetRect(nsRect(bp.IStart(wm), bp.BStart(wm),
                         convertedSize.ISize(wm), convertedSize.BSize(wm)));
-    kid->FinishAndStoreOverflow(&kidMetrics);
+    kid->FinishAndStoreOverflow(&kidMetrics, rs.mStyleDisplay);
     kid->DidReflow(aPresContext, nullptr, nsDidReflowStatus::FINISHED);
 
     convertedSize.ISize(wm) += bp.IStartEnd(wm);
     convertedSize.BSize(wm) += bp.BStartEnd(wm);
     aMetrics.SetSize(wm, convertedSize);
     aMetrics.SetBlockStartAscent(kidMetrics.BlockStartAscent() +
                                  bp.BStart(wm));
 
     // Ensure that the overflow rect contains the child textframe's
     // overflow rect.
     // Note that if this is floating, the overline/underline drawable
     // area is in the overflow rect of the child textframe.
     aMetrics.UnionOverflowAreasWithDesiredBounds();
     ConsiderChildOverflow(aMetrics.mOverflowAreas, kid);
 
-    FinishAndStoreOverflow(&aMetrics);
+    FinishAndStoreOverflow(&aMetrics, aReflowInput.mStyleDisplay);
   } else {
     // Pretend we are a span and reflow the child frame
     nsLineLayout* ll = aReflowInput.mLineLayout;
     bool          pushedFrame;
 
     ll->SetInFirstLetter(
       mStyleContext->GetPseudo() == nsCSSPseudoElements::firstLetter);
     ll->BeginSpan(this, &aReflowInput, bp.IStart(wm),
--- a/layout/generic/nsFrame.cpp
+++ b/layout/generic/nsFrame.cpp
@@ -1302,18 +1302,19 @@ nsIFrame::GetMarginRectRelativeToSelf() 
   nsRect r(0, 0, mRect.width, mRect.height);
   r.Inflate(m);
   return r;
 }
 
 bool
 nsIFrame::IsTransformed(const nsStyleDisplay* aStyleDisplay) const
 {
+  MOZ_ASSERT(aStyleDisplay == StyleDisplay());
   return ((mState & NS_FRAME_MAY_BE_TRANSFORMED) &&
-          (StyleDisplayWithOptionalParam(aStyleDisplay)->HasTransform(this) ||
+          (aStyleDisplay->HasTransform(this) ||
            IsSVGTransformed() ||
            HasAnimationOfTransform()));
 }
 
 bool
 nsIFrame::HasAnimationOfTransform() const
 {
   return mContent &&
@@ -1336,19 +1337,19 @@ nsIFrame::HasOpacityInternal(float aThre
 bool
 nsIFrame::IsSVGTransformed(gfx::Matrix *aOwnTransforms,
                            gfx::Matrix *aFromParentTransforms) const
 {
   return false;
 }
 
 bool
-nsIFrame::Extend3DContext() const
-{
-  const nsStyleDisplay* disp = StyleDisplay();
+nsIFrame::Extend3DContext(const nsStyleDisplay* aStyleDisplay) const
+{
+  const nsStyleDisplay* disp = StyleDisplayWithOptionalParam(aStyleDisplay);
   if (disp->mTransformStyle != NS_STYLE_TRANSFORM_STYLE_PRESERVE_3D ||
       !IsFrameOfType(nsIFrame::eSupportsCSSTransforms)) {
     return false;
   }
 
   // If we're all scroll frame, then all descendants will be clipped, so we can't preserve 3d.
   if (IsScrollFrame()) {
     return false;
@@ -1362,50 +1363,46 @@ nsIFrame::Extend3DContext() const
   return !nsFrame::ShouldApplyOverflowClipping(this, disp) &&
          !GetClipPropClipRect(disp, effects, GetSize()) &&
          !nsSVGIntegrationUtils::UsingEffectsForFrame(this);
 }
 
 bool
 nsIFrame::Combines3DTransformWithAncestors(const nsStyleDisplay* aStyleDisplay) const
 {
+  MOZ_ASSERT(aStyleDisplay == StyleDisplay());
   if (!GetParent() || !GetParent()->Extend3DContext()) {
     return false;
   }
   return IsTransformed(aStyleDisplay) || BackfaceIsHidden(aStyleDisplay);
 }
 
 bool
 nsIFrame::In3DContextAndBackfaceIsHidden() const
 {
   // While both tests fail most of the time, test BackfaceIsHidden()
   // first since it's likely to fail faster.
   const nsStyleDisplay* disp = StyleDisplay();
   return BackfaceIsHidden(disp) && Combines3DTransformWithAncestors(disp);
 }
 
 bool
-nsIFrame::HasPerspective() const
-{
-  if (!IsTransformed()) {
+nsIFrame::HasPerspective(const nsStyleDisplay* aStyleDisplay) const
+{
+  MOZ_ASSERT(aStyleDisplay == StyleDisplay());
+  if (!IsTransformed(aStyleDisplay)) {
     return false;
   }
-  nsIFrame* containingBlock = GetContainingBlock(SKIP_SCROLLED_FRAME);
+  nsIFrame* containingBlock = GetContainingBlock(SKIP_SCROLLED_FRAME, aStyleDisplay);
   if (!containingBlock) {
     return false;
   }
   return containingBlock->ChildrenHavePerspective();
 }
 
-bool
-nsIFrame::ChildrenHavePerspective() const
-{
-  return StyleDisplay()->HasPerspectiveStyle();
-}
-
 nsRect
 nsIFrame::GetContentRectRelativeToSelf() const
 {
   nsMargin bp(GetUsedBorderAndPadding());
   bp.ApplySkipSides(GetSkipSides());
   nsRect r(0, 0, mRect.width, mRect.height);
   r.Deflate(bp);
   return r;
@@ -2370,19 +2367,19 @@ nsIFrame::BuildDisplayListForStackingCon
       return;
     }
   }
 
   if (disp->mWillChangeBitField != 0) {
     aBuilder->AddToWillChangeBudget(this, GetSize());
   }
 
-  bool extend3DContext = Extend3DContext();
+  bool extend3DContext = Extend3DContext(disp);
   Maybe<nsDisplayListBuilder::AutoPreserves3DContext> autoPreserves3DContext;
-  if (extend3DContext && !Combines3DTransformWithAncestors()) {
+  if (extend3DContext && !Combines3DTransformWithAncestors(disp)) {
     // Start a new preserves3d context to keep informations on
     // nsDisplayListBuilder.
     autoPreserves3DContext.emplace(aBuilder);
     // Save dirty rect on the builder to avoid being distorted for
     // multiple transforms along the chain.
     aBuilder->SetPreserves3DDirtyRect(aDirtyRect);
   }
 
@@ -2416,17 +2413,17 @@ nsIFrame::BuildDisplayListForStackingCon
       // fall through to the NoPrerender case
     case nsDisplayTransform::NoPrerender:
       if (overflow.IsEmpty() && !extend3DContext) {
         return;
       }
 
       // If we're in preserve-3d then grab the dirty rect that was given to the root
       // and transform using the combined transform.
-      if (Combines3DTransformWithAncestors()) {
+      if (Combines3DTransformWithAncestors(disp)) {
         dirtyRect = aBuilder->GetPreserves3DDirtyRect(this);
       }
 
       nsRect untransformedDirtyRect;
       if (nsDisplayTransform::UntransformRect(dirtyRect, overflow, this,
             &untransformedDirtyRect)) {
         dirtyRect = untransformedDirtyRect;
       } else {
@@ -2777,17 +2774,18 @@ nsIFrame::BuildDisplayListForStackingCon
 
     if (hasPerspective) {
       if (clipCapturedBy == ContainerItemType::ePerspective) {
         clipState.Restore();
       }
       resultList.AppendNewToTop(
         new (aBuilder) nsDisplayPerspective(
           aBuilder, this,
-          GetContainingBlock()->GetContent()->GetPrimaryFrame(), &resultList));
+          GetContainingBlock(0, disp)->GetContent()->GetPrimaryFrame(),
+          &resultList));
     }
   }
 
   if (clipCapturedBy == ContainerItemType::eOwnLayerForTransformWithRoundedClip) {
     clipState.Restore();
     resultList.AppendNewToTop(
       new (aBuilder) nsDisplayOwnLayer(aBuilder, this, &resultList,
                                        aBuilder->CurrentActiveScrolledRoot(), 0,
@@ -5884,17 +5882,17 @@ void
 nsFrame::FinishReflowWithAbsoluteFrames(nsPresContext*           aPresContext,
                                         ReflowOutput&     aDesiredSize,
                                         const ReflowInput& aReflowInput,
                                         nsReflowStatus&          aStatus,
                                         bool                     aConstrainBSize)
 {
   ReflowAbsoluteFrames(aPresContext, aDesiredSize, aReflowInput, aStatus, aConstrainBSize);
 
-  FinishAndStoreOverflow(&aDesiredSize);
+  FinishAndStoreOverflow(&aDesiredSize, aReflowInput.mStyleDisplay);
 }
 
 void
 nsFrame::ReflowAbsoluteFrames(nsPresContext*           aPresContext,
                               ReflowOutput&     aDesiredSize,
                               const ReflowInput& aReflowInput,
                               nsReflowStatus&          aStatus,
                               bool                     aConstrainBSize)
@@ -7007,26 +7005,28 @@ GetNearestBlockContainer(nsIFrame* frame
          frame->IsTableRowFrame()) {
     frame = frame->GetParent();
     NS_ASSERTION(frame, "How come we got to the root frame without seeing a containing block?");
   }
   return frame;
 }
 
 nsIFrame*
-nsIFrame::GetContainingBlock(uint32_t aFlags) const
-{
+nsIFrame::GetContainingBlock(uint32_t aFlags,
+                             const nsStyleDisplay* aStyleDisplay) const
+{
+  MOZ_ASSERT(aStyleDisplay == StyleDisplay());
   if (!GetParent()) {
     return nullptr;
   }
   // MathML frames might have absolute positioning style, but they would
   // still be in-flow.  So we have to check to make sure that the frame
   // is really out-of-flow too.
   nsIFrame* f;
-  if (IsAbsolutelyPositioned() &&
+  if (IsAbsolutelyPositioned(aStyleDisplay) &&
       (GetStateBits() & NS_FRAME_OUT_OF_FLOW)) {
     f = GetParent(); // the parent is always the containing block
   } else {
     f = GetNearestBlockContainer(GetParent());
   }
 
   if (aFlags & SKIP_SCROLLED_FRAME && f &&
       f->StyleContext()->GetPseudo() == nsCSSAnonBoxes::scrolledContent) {
@@ -8950,22 +8950,23 @@ ComputeAndIncludeOutlineArea(nsIFrame* a
   }
 
   nsRect& vo = aOverflowAreas.VisualOverflow();
   vo.UnionRectEdges(vo, innerRect.Union(outerRect));
 }
 
 bool
 nsIFrame::FinishAndStoreOverflow(nsOverflowAreas& aOverflowAreas,
-                                 nsSize aNewSize, nsSize* aOldSize)
+                                 nsSize aNewSize, nsSize* aOldSize,
+                                 const nsStyleDisplay* aStyleDisplay)
 {
   MOZ_ASSERT(FrameMaintainsOverflow(),
              "Don't call - overflow rects not maintained on these SVG frames");
 
-  const nsStyleDisplay* disp = StyleDisplay();
+  const nsStyleDisplay* disp = StyleDisplayWithOptionalParam(aStyleDisplay);
   bool hasTransform = IsTransformed(disp);
 
   nsRect bounds(nsPoint(0, 0), aNewSize);
   // Store the passed in overflow area if we are a preserve-3d frame or we have
   // a transform, and it's not just the frame bounds.
   if (hasTransform || Combines3DTransformWithAncestors(disp)) {
     if (!aOverflowAreas.VisualOverflow().IsEqualEdges(bounds) ||
         !aOverflowAreas.ScrollableOverflow().IsEqualEdges(bounds)) {
@@ -9067,26 +9068,26 @@ nsIFrame::FinishAndStoreOverflow(nsOverf
   bool sizeChanged = ((aOldSize ? *aOldSize : oldSize) != aNewSize);
 
   /* Since our size might not actually have been computed yet, we need to make sure that we use the
    * correct dimensions by overriding the stored bounding rectangle with the value the caller has
    * ensured us we'll use.
    */
   SetSize(aNewSize);
 
-  if (ChildrenHavePerspective() && sizeChanged) {
+  if (ChildrenHavePerspective(disp) && sizeChanged) {
     nsRect newBounds(nsPoint(0, 0), aNewSize);
     RecomputePerspectiveChildrenOverflow(this);
   }
 
   if (hasTransform) {
     Properties().Set(nsIFrame::PreTransformOverflowAreasProperty(),
                      new nsOverflowAreas(aOverflowAreas));
 
-    if (Combines3DTransformWithAncestors()) {
+    if (Combines3DTransformWithAncestors(disp)) {
       /* If we're a preserve-3d leaf frame, then our pre-transform overflow should be correct. Our
        * post-transform overflow is empty though, because we only contribute to the overflow area
        * of the preserve-3d root frame.
        * If we're an intermediate frame then the pre-transform overflow should contain all our
        * non-preserve-3d children, which is what we want. Again we have no post-transform overflow.
        */
       aOverflowAreas.SetAllTo(nsRect());
     } else {
@@ -9094,17 +9095,17 @@ nsIFrame::FinishAndStoreOverflow(nsOverf
         nsRect& o = aOverflowAreas.Overflow(otype);
         o = nsDisplayTransform::TransformRect(o, this);
       }
 
       /* If we're the root of the 3d context, then we want to include the overflow areas of all
        * the participants. This won't have happened yet as the code above set their overflow
        * area to empty. Manually collect these overflow areas now.
        */
-      if (Extend3DContext()) {
+      if (Extend3DContext(disp)) {
         ComputePreserve3DChildrenOverflow(aOverflowAreas);
       }
     }
   } else {
     Properties().Delete(nsIFrame::PreTransformOverflowAreasProperty());
   }
 
   /* Revert the size change in case some caller is depending on this. */
@@ -9172,29 +9173,30 @@ nsIFrame::ComputePreserve3DChildrenOverf
   for (; !lists.IsDone(); lists.Next()) {
     nsFrameList::Enumerator childFrames(lists.CurrentList());
     for (; !childFrames.AtEnd(); childFrames.Next()) {
       nsIFrame* child = childFrames.get();
 
       // If this child participates in the 3d context, then take the pre-transform
       // region (which contains all descendants that aren't participating in the 3d context)
       // and transform it into the 3d context root coordinate space.
-      if (child->Combines3DTransformWithAncestors()) {
+      const nsStyleDisplay* childDisp = child->StyleDisplay();
+      if (child->Combines3DTransformWithAncestors(childDisp)) {
         nsOverflowAreas childOverflow = child->GetOverflowAreasRelativeToSelf();
 
         NS_FOR_FRAME_OVERFLOW_TYPES(otype) {
           nsRect& o = childOverflow.Overflow(otype);
           o = nsDisplayTransform::TransformRect(o, child);
         }
 
         aOverflowAreas.UnionWith(childOverflow);
 
         // If this child also extends the 3d context, then recurse into it
         // looking for more participants.
-        if (child->Extend3DContext()) {
+        if (child->Extend3DContext(childDisp)) {
           child->ComputePreserve3DChildrenOverflow(aOverflowAreas);
         }
       }
     }
   }
 }
 
 uint32_t
--- a/layout/generic/nsIFrame.h
+++ b/layout/generic/nsIFrame.h
@@ -1666,17 +1666,20 @@ public:
    * Returns true if this frame is transformed (e.g. has CSS or SVG transforms)
    * or if its parent is an SVG frame that has children-only transforms (e.g.
    * an SVG viewBox attribute) or if its transform-style is preserve-3d or
    * the frame has transform animations.
    *
    * @param aStyleDisplay:  If the caller has this->StyleDisplay(), providing
    *   it here will improve performance.
    */
-  bool IsTransformed(const nsStyleDisplay* aStyleDisplay = nullptr) const;
+  bool IsTransformed(const nsStyleDisplay* aStyleDisplay) const;
+  bool IsTransformed() const {
+    return IsTransformed(StyleDisplay());
+  }
 
   /**
    * True if this frame has any animation of transform in effect.
    */
   bool HasAnimationOfTransform() const;
 
   /**
    * Returns true if the frame is translucent or the frame has opacity
@@ -1713,44 +1716,65 @@ public:
    */
   virtual bool IsSVGTransformed(Matrix *aOwnTransforms = nullptr,
                                 Matrix *aFromParentTransforms = nullptr) const;
 
   /**
    * Returns whether this frame will attempt to extend the 3d transforms of its
    * children. This requires transform-style: preserve-3d, as well as no clipping
    * or svg effects.
+   *
+   * @param aStyleDisplay:  If the caller has this->StyleDisplay(), providing
+   *   it here will improve performance.
    */
-  bool Extend3DContext() const;
+  bool Extend3DContext(const nsStyleDisplay* aStyleDisplay) const;
+  bool Extend3DContext() const {
+    return Extend3DContext(StyleDisplay());
+  }
 
   /**
    * Returns whether this frame has a parent that Extend3DContext() and has
    * its own transform (or hidden backface) to be combined with the parent's
    * transform.
    *
    * @param aStyleDisplay:  If the caller has this->StyleDisplay(), providing
    *   it here will improve performance.
    */
-  bool Combines3DTransformWithAncestors(const nsStyleDisplay* aStyleDisplay
-                                          = nullptr) const;
+  bool Combines3DTransformWithAncestors(const nsStyleDisplay* aStyleDisplay) const;
+  bool Combines3DTransformWithAncestors() const {
+    return Combines3DTransformWithAncestors(StyleDisplay());
+  }
 
   /**
    * Returns whether this frame has a hidden backface and has a parent that
    * Extend3DContext(). This is useful because in some cases the hidden
    * backface can safely be ignored if it could not be visible anyway.
    */
   bool In3DContextAndBackfaceIsHidden() const;
 
+  bool IsPreserve3DLeaf(const nsStyleDisplay* aStyleDisplay) const {
+    return Combines3DTransformWithAncestors(aStyleDisplay) &&
+           !Extend3DContext(aStyleDisplay);
+  }
   bool IsPreserve3DLeaf() const {
-    return Combines3DTransformWithAncestors() && !Extend3DContext();
+    return IsPreserve3DLeaf(StyleDisplay());
   }
 
-  bool HasPerspective() const;
-
-  bool ChildrenHavePerspective() const;
+  bool HasPerspective(const nsStyleDisplay* aStyleDisplay) const;
+  bool HasPerspective() const {
+    return HasPerspective(StyleDisplay());
+  }
+
+  bool ChildrenHavePerspective(const nsStyleDisplay* aStyleDisplay) const {
+    MOZ_ASSERT(aStyleDisplay == StyleDisplay());
+    return aStyleDisplay->HasPerspectiveStyle();
+  }
+  bool ChildrenHavePerspective() const {
+    return ChildrenHavePerspective(StyleDisplay());
+  }
 
   /**
    * Includes the overflow area of all descendants that participate in the current
    * 3d context into aOverflowAreas.
    */
   void ComputePreserve3DChildrenOverflow(nsOverflowAreas& aOverflowAreas);
 
   void RecomputePerspectiveChildrenOverflow(const nsIFrame* aStartFrame);
@@ -2748,17 +2772,21 @@ public:
    * investigating whether any of the callers actually require the default
    * behaviour.
    */
   enum {
     // If the containing block is an anonymous scrolled frame, then skip over
     // this and return the outer scroll frame.
     SKIP_SCROLLED_FRAME = 0x01
   };
-  nsIFrame* GetContainingBlock(uint32_t aFlags = 0) const;
+  nsIFrame* GetContainingBlock(uint32_t aFlags,
+                               const nsStyleDisplay* aStyleDisplay) const;
+  nsIFrame* GetContainingBlock(uint32_t aFlags = 0) const {
+    return GetContainingBlock(aFlags, StyleDisplay());
+  }
 
   /**
    * Is this frame a containing block for floating elements?
    * Note that very few frames are, so default to false.
    */
   virtual bool IsFloatContainingBlock() const { return false; }
 
   /**
@@ -3022,21 +3050,24 @@ public:
 
   /**
    * Store the overflow area in the frame's mOverflow.mVisualDeltas
    * fields or as a frame property in the frame manager so that it can
    * be retrieved later without reflowing the frame. Returns true if either of
    * the overflow areas changed.
    */
   bool FinishAndStoreOverflow(nsOverflowAreas& aOverflowAreas,
-                              nsSize aNewSize, nsSize* aOldSize = nullptr);
-
-  bool FinishAndStoreOverflow(ReflowOutput* aMetrics) {
+                              nsSize aNewSize, nsSize* aOldSize = nullptr,
+                              const nsStyleDisplay* aStyleDisplay = nullptr);
+
+  bool FinishAndStoreOverflow(ReflowOutput* aMetrics,
+                              const nsStyleDisplay* aStyleDisplay = nullptr) {
     return FinishAndStoreOverflow(aMetrics->mOverflowAreas,
-                                  nsSize(aMetrics->Width(), aMetrics->Height()));
+                                  nsSize(aMetrics->Width(), aMetrics->Height()),
+                                  nullptr, aStyleDisplay);
   }
 
   /**
    * Returns whether the frame has an overflow rect that is different from
    * its border-box.
    */
   bool HasOverflowAreas() const {
     return mOverflow.mType != NS_FRAME_OVERFLOW_NONE;
@@ -3597,17 +3628,17 @@ public:
   inline bool IsBlockInside() const;
   inline bool IsBlockOutside() const;
   inline bool IsInlineOutside() const;
   inline mozilla::StyleDisplay GetDisplay() const;
   inline bool IsFloating() const;
   inline bool IsAbsPosContainingBlock() const;
   inline bool IsFixedPosContainingBlock() const;
   inline bool IsRelativelyPositioned() const;
-  inline bool IsAbsolutelyPositioned() const;
+  inline bool IsAbsolutelyPositioned(const nsStyleDisplay* aStyleDisplay = nullptr) const;
 
   /**
    * Returns the vertical-align value to be used for layout, if it is one
    * of the enumerated values.  If this is an SVG text frame, it returns a value
    * that corresponds to the value of dominant-baseline.  If the
    * vertical-align property has length or percentage value, this returns
    * eInvalidVerticalAlign.
    */
@@ -3687,18 +3718,22 @@ public:
    */
   virtual mozilla::dom::Element*
   GetPseudoElement(mozilla::CSSPseudoElementType aType);
 
   /*
    * @param aStyleDisplay:  If the caller has this->StyleDisplay(), providing
    *   it here will improve performance.
    */
-  bool BackfaceIsHidden(const nsStyleDisplay* aStyleDisplay = nullptr) const {
-    return StyleDisplayWithOptionalParam(aStyleDisplay)->BackfaceIsHidden();
+  bool BackfaceIsHidden(const nsStyleDisplay* aStyleDisplay) const {
+    MOZ_ASSERT(aStyleDisplay == StyleDisplay());
+    return aStyleDisplay->BackfaceIsHidden();
+  }
+  bool BackfaceIsHidden() const {
+    return StyleDisplay()->BackfaceIsHidden();
   }
 
   /**
    * Returns true if the frame is scrolled out of view.
    */
   bool IsScrolledOutOfView();
 
   /**
--- a/layout/generic/nsIFrameInlines.h
+++ b/layout/generic/nsIFrameInlines.h
@@ -61,19 +61,20 @@ nsIFrame::IsFixedPosContainingBlock() co
 
 bool
 nsIFrame::IsRelativelyPositioned() const
 {
   return StyleDisplay()->IsRelativelyPositioned(this);
 }
 
 bool
-nsIFrame::IsAbsolutelyPositioned() const
+nsIFrame::IsAbsolutelyPositioned(const nsStyleDisplay* aStyleDisplay) const
 {
-  return StyleDisplay()->IsAbsolutelyPositioned(this);
+  const nsStyleDisplay* disp = StyleDisplayWithOptionalParam(aStyleDisplay);
+  return disp->IsAbsolutelyPositioned(this);
 }
 
 bool
 nsIFrame::IsBlockInside() const
 {
   return StyleDisplay()->IsBlockInside(this);
 }
 
--- a/layout/generic/nsImageFrame.cpp
+++ b/layout/generic/nsImageFrame.cpp
@@ -1043,17 +1043,17 @@ nsImageFrame::Reflow(nsPresContext*     
     static_assert(eOverflowType_LENGTH == 2, "Unknown overflow types?");
     nsRect& visualOverflow = aMetrics.VisualOverflow();
     visualOverflow.UnionRect(visualOverflow, altFeedbackSize);
   } else {
     // We've just reflowed and we should have an accurate size, so we're ready
     // to request a decode.
     MaybeDecodeForPredictedSize();
   }
-  FinishAndStoreOverflow(&aMetrics);
+  FinishAndStoreOverflow(&aMetrics, aReflowInput.mStyleDisplay);
 
   if ((GetStateBits() & NS_FRAME_FIRST_REFLOW) && !mReflowCallbackPosted) {
     nsIPresShell* shell = PresContext()->PresShell();
     mReflowCallbackPosted = true;
     shell->PostReflowCallback(this);
   }
 
   NS_FRAME_TRACE(NS_FRAME_TRACE_CALLS,
--- a/layout/generic/nsLeafFrame.cpp
+++ b/layout/generic/nsLeafFrame.cpp
@@ -59,17 +59,17 @@ nsLeafFrame::Reflow(nsPresContext* aPres
   NS_FRAME_TRACE(NS_FRAME_TRACE_CALLS,
                  ("enter nsLeafFrame::Reflow: aMaxSize=%d,%d",
                   aReflowInput.AvailableWidth(), aReflowInput.AvailableHeight()));
 
   NS_PRECONDITION(mState & NS_FRAME_IN_REFLOW, "frame is not in reflow");
 
   DoReflow(aPresContext, aMetrics, aReflowInput, aStatus);
 
-  FinishAndStoreOverflow(&aMetrics);
+  FinishAndStoreOverflow(&aMetrics, aReflowInput.mStyleDisplay);
 }
 
 void
 nsLeafFrame::DoReflow(nsPresContext* aPresContext,
                       ReflowOutput& aMetrics,
                       const ReflowInput& aReflowInput,
                       nsReflowStatus& aStatus)
 {
@@ -106,10 +106,10 @@ void
 nsLeafFrame::SizeToAvailSize(const ReflowInput& aReflowInput,
                              ReflowOutput& aDesiredSize)
 {
   WritingMode wm = aReflowInput.GetWritingMode();
   LogicalSize size(wm, aReflowInput.AvailableISize(), // FRAME
                    aReflowInput.AvailableBSize());
   aDesiredSize.SetSize(wm, size);
   aDesiredSize.SetOverflowAreasToDesiredBounds();
-  FinishAndStoreOverflow(&aDesiredSize);  
+  FinishAndStoreOverflow(&aDesiredSize, aReflowInput.mStyleDisplay);
 }
new file mode 100644
--- /dev/null
+++ b/layout/reftests/xul/mac-tab-toolbar-ref.xul
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+  <tabbox id="tab">
+    <tabs>
+      <tab label="zeroTab" id="0" selected="true" />
+      <tab label="firstTab" id="1" />
+      <tab label="secondtab" id="2" />
+      <tab label="thirdTab" id="3" />
+      <tab label="fourthTab" id="4" />
+      <tab label="fifthTab" id="5" />
+      <tab label="sixthTab" id="6" />
+      <tab label="seventhTab" id="7" />
+      <tab label="eightTab" id="8" />
+      <tab label="ninthTab" id="9" />
+    </tabs>
+  </tabbox>
+</window>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/xul/mac-tab-toolbar.xul
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" class="reftest-wait">
+  <tabbox id="tab">
+    <tabs>
+      <tab label="zeroTab" id="0" />
+      <tab label="firstTab" id="1" />
+      <tab label="secondtab" id="2" />
+      <tab label="thirdTab" id="3" />
+      <tab label="fourthTab" id="4" />
+      <tab label="fifthTab" id="5" />
+      <tab label="sixthTab" id="6" />
+      <tab label="seventhTab" id="7" />
+      <tab label="eightTab" id="8" />
+      <tab label="ninthTab" id="9" />
+    </tabs>
+  </tabbox>
+
+  <script type="text/javascript">
+    // Overly try to iterate and click through the tabs
+    // since its a timing specific bug.
+    var tabCount = 10;
+    var loops = 10;
+    var i = tabCount * loops;
+
+    function clickTabs() {
+      var currentTab = i % tabCount;
+      var tab = document.getElementById(currentTab);
+      tab.click();
+
+      if (i > 0) {
+        i--;
+        // Relinquish main thread so we can paint
+        setTimeout(clickTabs, 0);
+      } else {
+        // Test finished
+        document.documentElement.removeAttribute("class");
+      }
+    }
+
+    window.addEventListener('MozReftestInvalidate', clickTabs);
+  </script>
+</window>
--- a/layout/reftests/xul/reftest.list
+++ b/layout/reftests/xul/reftest.list
@@ -3,16 +3,17 @@
 == menuitem-key.xul menuitem-key-ref.xul
 # these random-if(Android) are due to differences between Android Native & Xul, see bug 732569
 random-if(Android) == menulist-shrinkwrap-1.xul menulist-shrinkwrap-1-ref.xul
 random-if(Android) fails-if(winWidget) == menulist-shrinkwrap-2.xul menulist-shrinkwrap-2-ref.xul
 == textbox-overflow-1.xul textbox-overflow-1-ref.xul # for bug 749658
 # accesskeys are not normally displayed on Mac, so skip this test
 skip-if(cocoaWidget) == accesskey.xul accesskey-ref.xul
 fails-if(cocoaWidget) fuzzy-if(xulRuntime.widgetToolkit=="gtk3",1,11) == tree-row-outline-1.xul tree-row-outline-1-ref.xul # win8: bug 1254832
+skip-if(!cocoaWidget) == mac-tab-toolbar.xul mac-tab-toolbar-ref.xul
 != tree-row-outline-1.xul tree-row-outline-1-notref.xul
 == text-crop.xul text-crop-ref.xul
 == text-small-caps-1.xul text-small-caps-1-ref.xul
 fuzzy-if(skiaContent,1,60) fuzzy-if(cocoaWidget&&browserIsRemote&&!skiaContent,1,31) fuzzy-if(winWidget&&browserIsRemote&&layersGPUAccelerated,1,50) == inactive-fixed-bg-bug1205630.xul inactive-fixed-bg-bug1205630-ref.html
 fuzzy-if(skiaContent,1,60) fuzzy-if(cocoaWidget&&browserIsRemote&&!skiaContent,1,31) fuzzy-if(winWidget&&browserIsRemote&&layersGPUAccelerated,1,50) == inactive-fixed-bg-bug1272525.xul inactive-fixed-bg-bug1272525-ref.html
 
 # Tests for XUL <image> with 'object-fit' & 'object-position':
 # These tests should be very similar to tests in our w3c-css/submitted/images3
--- a/layout/tools/reftest/reftestcommandline.py
+++ b/layout/tools/reftest/reftestcommandline.py
@@ -139,16 +139,20 @@ class ReftestArgumentsParser(argparse.Ar
         self.add_argument("--marionette-port-timeout",
                           default=None,
                           help=argparse.SUPPRESS)
 
         self.add_argument("--marionette-socket-timeout",
                           default=None,
                           help=argparse.SUPPRESS)
 
+        self.add_argument("--marionette-startup-timeout",
+                          default=None,
+                          help=argparse.SUPPRESS)
+
         self.add_argument("--setenv",
                           action="append",
                           type=str,
                           default=[],
                           dest="environment",
                           metavar="NAME=VALUE",
                           help="sets the given variable in the application's "
                           "environment")
--- a/layout/tools/reftest/runreftest.py
+++ b/layout/tools/reftest/runreftest.py
@@ -622,16 +622,17 @@ class RefTest(object):
         runner.start(debug_args=debug_args,
                      interactive=interactive,
                      outputTimeout=timeout)
         proc = runner.process_handler
 
         if self.use_marionette:
             marionette_args = {
                 'socket_timeout': options.marionette_socket_timeout,
+                'startup_timeout': options.marionette_startup_timeout,
                 'symbols_path': options.symbolsPath,
             }
             if options.marionette:
                 host, port = options.marionette.split(':')
                 marionette_args['host'] = host
                 marionette_args['port'] = int(port)
 
             marionette = Marionette(**marionette_args)
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -4640,16 +4640,19 @@ pref("layers.acceleration.disabled", fal
 // Preference that when switched at runtime will run a series of benchmarks
 // and output the result to stderr.
 pref("layers.bench.enabled", false);
 
 #if defined(XP_WIN)
 pref("layers.gpu-process.enabled", true);
 pref("layers.gpu-process.max_restarts", 3);
 pref("media.gpu-process-decoder", true);
+#ifdef NIGHTLY_BUILD
+pref("layers.gpu-process.allow-software", true);
+#endif
 #endif
 
 // Whether to force acceleration on, ignoring blacklists.
 #ifdef ANDROID
 // bug 838603 -- on Android, accidentally blacklisting OpenGL layers
 // means a startup crash for everyone.
 // Temporarily force-enable GL compositing.  This is default-disabled
 // deep within the bowels of the widgetry system.  Remove me when GL
@@ -5711,8 +5714,11 @@ pref("fuzzing.enabled", false);
 // to do that.
 pref("layers.advanced.border-layers", 2);
 pref("layers.advanced.boxshadow-inset-layers", 2);
 pref("layers.advanced.boxshadow-outer-layers", 2);
 pref("layers.advanced.caret-layers", 2);
 pref("layers.advanced.displaybuttonborder-layers", 2);
 pref("layers.advanced.outline-layers", 2);
 pref("layers.advanced.solid-color-layers", 2);
+
+// Enable lowercased response header name
+pref("dom.xhr.lowercase_header.enabled", true);
--- a/netwerk/base/nsINetworkInterceptController.idl
+++ b/netwerk/base/nsINetworkInterceptController.idl
@@ -117,18 +117,30 @@ interface nsIInterceptedChannel : nsISup
     void SetDispatchFetchEventEnd(in TimeStamp aTimeStamp);
 
     [noscript]
     void SetHandleFetchEventStart(in TimeStamp aTimeStamp);
 
     [noscript]
     void SetHandleFetchEventEnd(in TimeStamp aTimeStamp);
 
+    // Depending on the outcome we measure the time difference between
+    // |FinishResponseStart| and either |FinishSynthesizedResponseEnd| or
+    // |ChannelResetEnd|.
     [noscript]
-    void SaveTimeStampsToUnderlyingChannel();
+    void SetFinishResponseStart(in TimeStamp aTimeStamp);
+
+    [noscript]
+    void SetFinishSynthesizedResponseEnd(in TimeStamp aTimeStamp);
+
+    [noscript]
+    void SetChannelResetEnd(in TimeStamp aTimeStamp);
+
+    [noscript]
+    void SaveTimeStamps();
 
 %{C++
     already_AddRefed<nsIConsoleReportCollector>
     GetConsoleReportCollector()
     {
       nsCOMPtr<nsIConsoleReportCollector> reporter;
       GetConsoleReportCollector(getter_AddRefs(reporter));
       return reporter.forget();
--- a/netwerk/protocol/http/InterceptedChannel.cpp
+++ b/netwerk/protocol/http/InterceptedChannel.cpp
@@ -32,19 +32,20 @@ DoAddCacheEntryHeaders(nsHttpChannel *se
                        nsICacheEntry *entry,
                        nsHttpRequestHead *requestHead,
                        nsHttpResponseHead *responseHead,
                        nsISupports *securityInfo);
 
 NS_IMPL_ISUPPORTS(InterceptedChannelBase, nsIInterceptedChannel)
 
 InterceptedChannelBase::InterceptedChannelBase(nsINetworkInterceptController* aController)
-: mController(aController)
-, mReportCollector(new ConsoleReportCollector())
-, mClosed(false)
+  : mController(aController)
+  , mReportCollector(new ConsoleReportCollector())
+  , mClosed(false)
+  , mSynthesizedOrReset(Invalid)
 {
 }
 
 InterceptedChannelBase::~InterceptedChannelBase()
 {
 }
 
 NS_IMETHODIMP
@@ -128,17 +129,17 @@ InterceptedChannelBase::SetReleaseHandle
   MOZ_ASSERT(aHandle);
 
   // We need to keep it and mChannel alive until destructor clear it up.
   mReleaseHandle = aHandle;
   return NS_OK;
 }
 
 NS_IMETHODIMP
-InterceptedChannelBase::SaveTimeStampsToUnderlyingChannel()
+InterceptedChannelBase::SaveTimeStamps()
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   nsCOMPtr<nsIChannel> underlyingChannel;
   nsresult rv = GetChannel(getter_AddRefs(underlyingChannel));
   MOZ_ASSERT(NS_SUCCEEDED(rv));
 
   nsCOMPtr<nsITimedChannel> timedChannel =
@@ -158,16 +159,47 @@ InterceptedChannelBase::SaveTimeStampsTo
   MOZ_ASSERT(NS_SUCCEEDED(rv));
 
   rv = timedChannel->SetHandleFetchEventStart(mHandleFetchEventStart);
   MOZ_ASSERT(NS_SUCCEEDED(rv));
 
   rv = timedChannel->SetHandleFetchEventEnd(mHandleFetchEventEnd);
   MOZ_ASSERT(NS_SUCCEEDED(rv));
 
+  nsCOMPtr<nsIChannel> channel;
+  GetChannel(getter_AddRefs(channel));
+  if (NS_WARN_IF(!channel)) {
+    return NS_ERROR_FAILURE;
+  }
+
+  nsCString navigationOrSubresource = nsContentUtils::IsNonSubresourceRequest(channel) ?
+    NS_LITERAL_CSTRING("navigation") : NS_LITERAL_CSTRING("subresource");
+
+  // We may have null timestamps if the fetch dispatch runnable was cancelled
+  // and we defaulted to resuming the request.
+  if (!mFinishResponseStart.IsNull() && !mFinishResponseEnd.IsNull()) {
+    MOZ_ASSERT(mSynthesizedOrReset != Invalid);
+
+    Telemetry::HistogramID id = (mSynthesizedOrReset == Synthesized) ?
+      Telemetry::SERVICE_WORKER_FETCH_EVENT_FINISH_SYNTHESIZED_RESPONSE_MS :
+      Telemetry::SERVICE_WORKER_FETCH_EVENT_CHANNEL_RESET_MS;
+    Telemetry::Accumulate(id, navigationOrSubresource,
+      static_cast<uint32_t>((mFinishResponseEnd - mFinishResponseStart).ToMilliseconds()));
+  }
+
+  Telemetry::Accumulate(Telemetry::SERVICE_WORKER_FETCH_EVENT_DISPATCH_MS,
+    navigationOrSubresource,
+    static_cast<uint32_t>((mHandleFetchEventStart - mDispatchFetchEventStart).ToMilliseconds()));
+
+  if (!mFinishResponseEnd.IsNull()) {
+    Telemetry::Accumulate(Telemetry::SERVICE_WORKER_FETCH_INTERCEPTION_DURATION_MS,
+      navigationOrSubresource,
+      static_cast<uint32_t>((mFinishResponseEnd - mDispatchFetchEventStart).ToMilliseconds()));
+  }
+
   return rv;
 }
 
 /* static */
 already_AddRefed<nsIURI>
 InterceptedChannelBase::SecureUpgradeChannelURI(nsIChannel* aChannel)
 {
   nsCOMPtr<nsIURI> uri;
--- a/netwerk/protocol/http/InterceptedChannel.h
+++ b/netwerk/protocol/http/InterceptedChannel.h
@@ -50,16 +50,24 @@ protected:
 
   TimeStamp mLaunchServiceWorkerStart;
   TimeStamp mLaunchServiceWorkerEnd;
   TimeStamp mDispatchFetchEventStart;
   TimeStamp mDispatchFetchEventEnd;
   TimeStamp mHandleFetchEventStart;
   TimeStamp mHandleFetchEventEnd;
 
+  TimeStamp mFinishResponseStart;
+  TimeStamp mFinishResponseEnd;
+  enum {
+    Invalid = 0,
+    Synthesized,
+    Reset
+  } mSynthesizedOrReset;
+
   virtual ~InterceptedChannelBase();
 public:
   explicit InterceptedChannelBase(nsINetworkInterceptController* aController);
 
   // Notify the interception controller that the channel has been intercepted
   // and prepare the response body output stream.
   virtual void NotifyController() = 0;
 
@@ -106,17 +114,42 @@ public:
 
   NS_IMETHODIMP
   SetHandleFetchEventEnd(TimeStamp aTimeStamp) override
   {
     mHandleFetchEventEnd = aTimeStamp;
     return NS_OK;
   }
 
-  NS_IMETHODIMP SaveTimeStampsToUnderlyingChannel() override;
+  NS_IMETHODIMP
+  SetFinishResponseStart(TimeStamp aTimeStamp) override
+  {
+    mFinishResponseStart = aTimeStamp;
+    return NS_OK;
+  }
+
+  NS_IMETHODIMP
+  SetFinishSynthesizedResponseEnd(TimeStamp aTimeStamp) override
+  {
+    MOZ_ASSERT(mSynthesizedOrReset == Invalid);
+    mSynthesizedOrReset = Synthesized;
+    mFinishResponseEnd = aTimeStamp;
+    return NS_OK;
+  }
+
+  NS_IMETHODIMP
+  SetChannelResetEnd(TimeStamp aTimeStamp) override
+  {
+    MOZ_ASSERT(mSynthesizedOrReset == Invalid);
+    mSynthesizedOrReset = Reset;
+    mFinishResponseEnd = aTimeStamp;
+    return NS_OK;
+  }
+
+  NS_IMETHODIMP SaveTimeStamps() override;
 
   static already_AddRefed<nsIURI>
   SecureUpgradeChannelURI(nsIChannel* aChannel);
 };
 
 class InterceptedChannelChrome : public InterceptedChannelBase
 {
   // The actual channel being intercepted.
--- a/netwerk/protocol/http/nsHttpChannel.cpp
+++ b/netwerk/protocol/http/nsHttpChannel.cpp
@@ -3843,17 +3843,23 @@ nsHttpChannel::OnCacheEntryCheck(nsICach
     nsresult rv = NS_OK;
 
     LOG(("nsHttpChannel::OnCacheEntryCheck enter [channel=%p entry=%p]",
         this, entry));
 
     if (mRaceCacheWithNetwork && mFirstResponseSource == RESPONSE_FROM_NETWORK) {
         LOG(("Not using cached response because we've already got one from the network\n"));
         *aResult = ENTRY_NOT_WANTED;
+
+        // Net-win indicates that mOnStartRequestTimestamp is from net.
+        int64_t savedTime = (TimeStamp::Now() - mOnStartRequestTimestamp).ToMilliseconds();
+        Telemetry::Accumulate(Telemetry::NETWORK_RACE_CACHE_WITH_NETWORK_SAVED_TIME, savedTime);
         return NS_OK;
+    } else if (mRaceCacheWithNetwork && mFirstResponseSource == RESPONSE_PENDING) {
+        mOnCacheEntryCheckTimestamp = TimeStamp::Now();
     }
 
     nsAutoCString cacheControlRequestHeader;
     Unused << mRequestHead.GetHeader(nsHttp::Cache_Control, cacheControlRequestHeader);
     CacheControlParser cacheControlRequest(cacheControlRequestHeader);
 
     if (cacheControlRequest.NoStore()) {
         LOG(("Not using cached response based on no-store request cache directive\n"));
@@ -6889,16 +6895,27 @@ nsHttpChannel::OnStartRequest(nsIRequest
             request == mCachePump) {
             LOG(("  First response from cache\n"));
             mFirstResponseSource = RESPONSE_FROM_CACHE;
 
             // XXX: Consider cancelling H2 transactions  or H1 transactions
             // that are not keep-alive.
         } else if (WRONG_RACING_RESPONSE_SOURCE(request)) {
             LOG(("  Early return when racing. This response not needed."));
+
+            // Net wins, but OnCacheEntryCheck was already called.
+            if (mFirstResponseSource == RESPONSE_FROM_NETWORK &&
+                !mOnCacheEntryCheckTimestamp.IsNull()) {
+                TimeStamp currentTime = TimeStamp::Now();
+                int64_t savedTime = (currentTime - mOnStartRequestTimestamp).ToMilliseconds();
+                Telemetry::Accumulate(Telemetry::NETWORK_RACE_CACHE_WITH_NETWORK_SAVED_TIME, savedTime);
+
+                int64_t diffTime = (currentTime - mOnCacheEntryCheckTimestamp).ToMilliseconds();
+                Telemetry::Accumulate(Telemetry::NETWORK_RACE_CACHE_WITH_NETWORK_OCEC_ON_START_DIFF, diffTime);
+            }
             return NS_OK;
         }
     }
 
     // Make sure things are what we expect them to be...
     MOZ_ASSERT(request == mCachePump || request == mTransactionPump,
                "Unexpected request");
 
@@ -8839,17 +8856,17 @@ nsHttpChannel::SetDoNotTrack()
       mRequestHead.SetHeader(nsHttp::DoNotTrack,
                              NS_LITERAL_CSTRING("1"),
                              false);
     MOZ_ASSERT(NS_SUCCEEDED(rv));
   }
 }
 
 static const size_t kPositiveBucketNumbers = 34;
-static const int64_t positiveBucketLevels[kPositiveBucketNumbers] =
+static const int64_t kPositiveBucketLevels[kPositiveBucketNumbers] =
 {
 	0, 10, 20, 30, 40, 50, 60, 70, 80, 90,
 	100, 200, 300, 400, 500, 600, 700, 800, 900, 1000,
 	2000, 3000, 4000, 5000, 6000, 7000, 8000, 9000, 10000,
 	20000, 30000, 40000, 50000, 60000
 };
 
 /**
@@ -8869,20 +8886,20 @@ static const int64_t positiveBucketLevel
  * #6 indicates network wins by more than 1 minute.
  *
  * Other bucket numbers are reserved.
  */
 inline int64_t
 nsHttpChannel::ComputeTelemetryBucketNumber(int64_t difftime_ms)
 {
 	int64_t absBucketIndex =
-		std::lower_bound(positiveBucketLevels,
-                     positiveBucketLevels + kPositiveBucketNumbers,
+		std::lower_bound(kPositiveBucketLevels,
+                     kPositiveBucketLevels + kPositiveBucketNumbers,
 	                   static_cast<int64_t>(mozilla::Abs(difftime_ms)))
-		- positiveBucketLevels;
+		- kPositiveBucketLevels;
 
 	return difftime_ms >= 0 ? 40 + absBucketIndex
 	                        : 40 - absBucketIndex;
 }
 
 void
 nsHttpChannel::ReportNetVSCacheTelemetry()
 {
--- a/netwerk/protocol/http/nsHttpChannel.h
+++ b/netwerk/protocol/http/nsHttpChannel.h
@@ -541,16 +541,17 @@ private:
     uint32_t                          mRequestTime;
 
     nsCOMPtr<nsICacheEntry> mOfflineCacheEntry;
     uint32_t                          mOfflineCacheLastModifiedTime;
 
     mozilla::TimeStamp                mOnStartRequestTimestamp;
     // Timestamp of the time the cnannel was suspended.
     mozilla::TimeStamp                mSuspendTimestamp;
+    mozilla::TimeStamp                mOnCacheEntryCheckTimestamp;
     // Total time the channel spent suspended. This value is reported to
     // telemetry in nsHttpChannel::OnStartRequest().
     uint32_t                          mSuspendTotalTime;
 
     // States of channel interception
     enum {
         DO_NOT_INTERCEPT,  // no interception will occur
         MAYBE_INTERCEPT,   // interception in progress, but can be cancelled
--- a/security/nss.symbols
+++ b/security/nss.symbols
@@ -357,29 +357,31 @@ PK11_GetInternalKeySlot
 PK11_GetInternalSlot
 PK11_GetIVLength
 PK11_GetKeyData
 PK11_GetKeyGen
 PK11_GetLowLevelKeyIDForPrivateKey
 PK11_GetMechanism
 PK11_GetMinimumPwdLength
 PK11_GetModInfo
+PK11_GetModuleURI
 PK11_GetNextSafe
 PK11_GetNextSymKey
 PK11_GetPadMechanism
 PK11_GetPrivateKeyNickname
 PK11_GetPrivateModulusLen
 PK11_GetSlotID
 PK11_GetSlotInfo
 PK11_GetSlotName
 PK11_GetSlotPWValues
 PK11_GetSlotSeries
 PK11_GetSymKeyNickname
 PK11_GetTokenInfo
 PK11_GetTokenName
+PK11_GetTokenURI
 PK11_HasAttributeSet
 PK11_HashBuf
 PK11_HasRootCerts
 PK11_ImportCert
 PK11_ImportCertForKey
 PK11_ImportCRL
 PK11_ImportDERPrivateKeyInfoAndReturnKey
 PK11_ImportPublicKey
--- a/security/nss/TAG-INFO
+++ b/security/nss/TAG-INFO
@@ -1,1 +1,1 @@
-236a06d9c3c4
+57e38a8407b3
--- a/security/nss/automation/taskcluster/graph/src/extend.js
+++ b/security/nss/automation/taskcluster/graph/src/extend.js
@@ -450,16 +450,29 @@ async function scheduleTestBuilds(base, 
       "-c",
       "bin/checkout.sh && nss/automation/taskcluster/scripts/run_tests.sh"
     ],
     tests: "mpi",
     cycle: "standard",
     symbol: "mpi",
     kind: "test"
   }));
+  queue.scheduleTask(merge(base, {
+    parent: task_build,
+    command: [
+      "/bin/bash",
+      "-c",
+      "bin/checkout.sh && nss/automation/taskcluster/scripts/run_tests.sh"
+    ],
+    name: "Gtests",
+    symbol: "Gtest",
+    tests: "gtests",
+    cycle: "standard",
+    kind: "test"
+  }));
 
   return queue.submit();
 }
 
 
 /*****************************************************************************/
 
 async function scheduleWindows(name, base) {
--- a/security/nss/automation/taskcluster/scripts/run_clang_format.sh
+++ b/security/nss/automation/taskcluster/scripts/run_clang_format.sh
@@ -1,58 +1,62 @@
 #!/usr/bin/env bash
 
 source $(dirname "$0")/tools.sh
 
+set +x
+
 # Apply clang-format on the provided folder and verify that this doesn't change any file.
 # If any file differs after formatting, the script eventually exits with 1.
 # Any differences between formatted and unformatted files is printed to stdout to give a hint what's wrong.
 
-# Includes a default set of directories.
+# Includes a default set of directories NOT to clang-format on.
+blacklist=(
+     "./automation" \
+     "./coreconf" \
+     "./doc" \
+     "./pkg" \
+     "./tests" \
+     "./lib/libpkix" \
+     "./lib/zlib" \
+     "./lib/sqlite" \
+     "./gtests/google_test" \
+     "./.hg" \
+)
+
+top="$PWD/$(dirname $0)/../../.."
+cd "$top"
 
 if [ $# -gt 0 ]; then
     dirs=("$@")
 else
-    top=$(dirname $0)/../../..
-    dirs=( \
-         "$top/cmd" \
-         "$top/fuzz" \
-         "$top/lib/base" \
-         "$top/lib/certdb" \
-         "$top/lib/certhigh" \
-         "$top/lib/ckfw" \
-         "$top/lib/crmf" \
-         "$top/lib/cryptohi" \
-         "$top/lib/dbm" \
-         "$top/lib/dev" \
-         "$top/lib/freebl" \
-         "$top/lib/jar" \
-         "$top/lib/nss" \
-         "$top/lib/pk11wrap" \
-         "$top/lib/pkcs7" \
-         "$top/lib/pkcs12" \
-         "$top/lib/pki" \
-         "$top/lib/smime" \
-         "$top/lib/softoken" \
-         "$top/lib/ssl" \
-         "$top/lib/sysinit" \
-         "$top/lib/util" \
-         "$top/gtests/common" \
-         "$top/gtests/der_gtest" \
-         "$top/gtests/freebl_gtest" \
-         "$top/gtests/pk11_gtest" \
-         "$top/gtests/ssl_gtest" \
-         "$top/gtests/util_gtest" \
-         "$top/nss-tool" \
-         "$top/cpputil" \
-    )
+    dirs=($(find . ! -path . \( ! -regex '.*/' \) -maxdepth 2 -mindepth 1 -type d))
 fi
 
+format_folder()
+{
+    for black in "${blacklist[@]}"; do
+        if [[ "$1" == "$black"* ]]; then
+            echo "skip $1"
+            return 1
+        fi
+    done
+    return 0
+}
+
 for dir in "${dirs[@]}"; do
-    find "$dir" -type f \( -name '*.[ch]' -o -name '*.cc' \) -exec clang-format -i {} \+
+    if format_folder "$dir" ; then
+        c="${dir//[^\/]}"
+        echo "formatting $dir ..."
+        depth=""
+        if [ "${#c}" == "1" ]; then
+            depth="-maxdepth 1"
+        fi
+        find "$dir" $depth -type f \( -name '*.[ch]' -o -name '*.cc' \) -exec clang-format -i {} \+
+    fi
 done
 
 TMPFILE=$(mktemp /tmp/$(basename $0).XXXXXX)
 trap 'rm $TMPFILE' exit
 if (cd $(dirname $0); hg root >/dev/null 2>&1); then
     hg diff --git "$top" | tee $TMPFILE
 else
     git -C "$top" diff | tee $TMPFILE
--- a/security/nss/build.sh
+++ b/security/nss/build.sh
@@ -178,17 +178,20 @@ fi
 mkdir -p "$dist_dir"
 echo $target > "$dist_dir"/latest
 
 if [[ "$rebuild_nspr" = 1 && "$no_local_nspr" = 0 ]]; then
     nspr_build "${nspr_params[@]}"
     mv -f "$nspr_config".new "$nspr_config"
 fi
 if [ "$rebuild_gyp" = 1 ]; then
-
+    if ! hash gyp 2> /dev/null; then
+        echo "Please install gyp" 1>&2
+        exit 1
+    fi
     # These extra arguments aren't used in determining whether to rebuild.
     obj_dir="$dist_dir"/$target
     gyp_params+=(-Dnss_dist_obj_dir=$obj_dir)
     if [ "$no_local_nspr" = 0 ]; then
         set_nspr_path "$obj_dir/include/nspr:$obj_dir/lib"
     fi
 
     run_verbose run_scanbuild gyp -f ninja "${gyp_params[@]}" "$cwd"/nss.gyp
--- a/security/nss/cmd/certutil/certutil.c
+++ b/security/nss/cmd/certutil/certutil.c
@@ -997,19 +997,22 @@ ListModules(void)
 
     /* get them all! */
     list = PK11_GetAllTokens(CKM_INVALID_MECHANISM, PR_FALSE, PR_FALSE, NULL);
     if (list == NULL)
         return SECFailure;
 
     /* look at each slot*/
     for (le = list->head; le; le = le->next) {
+        char *token_uri = PK11_GetTokenURI(le->slot);
         printf("\n");
         printf("    slot: %s\n", PK11_GetSlotName(le->slot));
         printf("   token: %s\n", PK11_GetTokenName(le->slot));
+        printf("     uri: %s\n", token_uri);
+        PORT_Free(token_uri);
     }
     PK11_FreeSlotList(list);
 
     return SECSuccess;
 }
 
 static void
 PrintSyntax(char *progName)
--- a/security/nss/cmd/modutil/pk11.c
+++ b/security/nss/cmd/modutil/pk11.c
@@ -392,27 +392,33 @@ RawAddModule(char *dbmodulespec, char *m
     }
     return SUCCESS;
 }
 
 static void
 printModule(SECMODModule *module, int *count)
 {
     int slotCount = module->loaded ? module->slotCount : 0;
+    char *modUri;
     int i;
 
     if ((*count)++) {
         PR_fprintf(PR_STDOUT, "\n");
     }
     PR_fprintf(PR_STDOUT, "%3d. %s\n", *count, module->commonName);
 
     if (module->dllName) {
         PR_fprintf(PR_STDOUT, "\tlibrary name: %s\n", module->dllName);
     }
 
+    modUri = PK11_GetModuleURI(module);
+    if (modUri) {
+        PR_fprintf(PR_STDOUT, "\t   uri: %s\n", modUri);
+        PORT_Free(modUri);
+    }
     if (slotCount == 0) {
         PR_fprintf(PR_STDOUT,
                    "\t slots: There are no slots attached to this module\n");
     } else {
         PR_fprintf(PR_STDOUT, "\t slots: %d slot%s attached\n",
                    slotCount, (slotCount == 1 ? "" : "s"));
     }
 
@@ -420,20 +426,22 @@ printModule(SECMODModule *module, int *c
         PR_fprintf(PR_STDOUT, "\tstatus: Not loaded\n");
     } else {
         PR_fprintf(PR_STDOUT, "\tstatus: loaded\n");
     }
 
     /* Print slot and token names */
     for (i = 0; i < slotCount; i++) {
         PK11SlotInfo *slot = module->slots[i];
-
+        char *tokenUri = PK11_GetTokenURI(slot);
         PR_fprintf(PR_STDOUT, "\n");
         PR_fprintf(PR_STDOUT, "\t slot: %s\n", PK11_GetSlotName(slot));
         PR_fprintf(PR_STDOUT, "\ttoken: %s\n", PK11_GetTokenName(slot));
+        PR_fprintf(PR_STDOUT, "\t  uri: %s\n", tokenUri);
+        PORT_Free(tokenUri);
     }
     return;
 }
 
 /************************************************************************
  *
  * L i s t M o d u l e s
  *
--- a/security/nss/coreconf/config.gypi
+++ b/security/nss/coreconf/config.gypi
@@ -213,17 +213,17 @@
           ],
         },
         'conditions': [
           [ 'cc_use_gnu_ld==1', {
             'ldflags': [
               '-Wl,--version-script,<(INTERMEDIATE_DIR)/out.>(mapfile)',
             ],
           }],
-          [ 'OS=="win"', {
+          [ 'cc_use_gnu_ld!=1 and OS=="win"', {
             # On Windows, .def files are used directly as sources.
             'sources': [
               '>(mapfile)',
             ],
           }, {
             # On other platforms, .def files need processing.
             'sources': [
               '<(INTERMEDIATE_DIR)/out.>(mapfile)',
--- a/security/nss/coreconf/coreconf.dep
+++ b/security/nss/coreconf/coreconf.dep
@@ -5,8 +5,9 @@
 
 /*
  * A dummy header file that is a dependency for all the object files.
  * Used to force a full recompilation of NSS in Mozilla's Tinderbox
  * depend builds.  See comments in rules.mk.
  */
 
 #error "Do not include this header file."
+
--- a/security/nss/cpputil/scoped_ptrs.h
+++ b/security/nss/cpputil/scoped_ptrs.h
@@ -6,36 +6,39 @@
 
 #ifndef scoped_ptrs_h__
 #define scoped_ptrs_h__
 
 #include <memory>
 #include "cert.h"
 #include "keyhi.h"
 #include "pk11pub.h"
+#include "pkcs11uri.h"
 
 struct ScopedDelete {
   void operator()(CERTCertificate* cert) { CERT_DestroyCertificate(cert); }
   void operator()(CERTCertificateList* list) {
     CERT_DestroyCertificateList(list);
   }
+  void operator()(CERTName* name) { CERT_DestroyName(name); }
   void operator()(CERTCertList* list) { CERT_DestroyCertList(list); }
   void operator()(CERTSubjectPublicKeyInfo* spki) {
     SECKEY_DestroySubjectPublicKeyInfo(spki);
   }
   void operator()(PK11SlotInfo* slot) { PK11_FreeSlot(slot); }
   void operator()(PK11SymKey* key) { PK11_FreeSymKey(key); }
   void operator()(PRFileDesc* fd) { PR_Close(fd); }
   void operator()(SECAlgorithmID* id) { SECOID_DestroyAlgorithmID(id, true); }
   void operator()(SECItem* item) { SECITEM_FreeItem(item, true); }
   void operator()(SECKEYPublicKey* key) { SECKEY_DestroyPublicKey(key); }
   void operator()(SECKEYPrivateKey* key) { SECKEY_DestroyPrivateKey(key); }
   void operator()(SECKEYPrivateKeyList* list) {
     SECKEY_DestroyPrivateKeyList(list);
   }
+  void operator()(PK11URI* uri) { PK11URI_DestroyURI(uri); }
 };
 
 template <class T>
 struct ScopedMaybeDelete {
   void operator()(T* ptr) {
     if (ptr) {
       ScopedDelete del;
       del(ptr);
@@ -43,21 +46,23 @@ struct ScopedMaybeDelete {
   }
 };
 
 #define SCOPED(x) typedef std::unique_ptr<x, ScopedMaybeDelete<x> > Scoped##x
 
 SCOPED(CERTCertificate);
 SCOPED(CERTCertificateList);
 SCOPED(CERTCertList);
+SCOPED(CERTName);
 SCOPED(CERTSubjectPublicKeyInfo);
 SCOPED(PK11SlotInfo);
 SCOPED(PK11SymKey);
 SCOPED(PRFileDesc);
 SCOPED(SECAlgorithmID);
 SCOPED(SECItem);
 SCOPED(SECKEYPublicKey);
 SCOPED(SECKEYPrivateKey);
 SCOPED(SECKEYPrivateKeyList);
+SCOPED(PK11URI);
 
 #undef SCOPED
 
 #endif  // scoped_ptrs_h__
--- a/security/nss/cpputil/tls_parser.h
+++ b/security/nss/cpputil/tls_parser.h
@@ -11,17 +11,16 @@
 #include <cstring>
 #include <memory>
 #if defined(WIN32) || defined(WIN64)
 #include <winsock2.h>
 #else
 #include <arpa/inet.h>
 #endif
 #include "databuffer.h"
-
 #include "sslt.h"
 
 namespace nss_test {
 
 const uint8_t kTlsChangeCipherSpecType = 20;
 const uint8_t kTlsAlertType = 21;
 const uint8_t kTlsHandshakeType = 22;
 const uint8_t kTlsApplicationDataType = 23;
@@ -74,16 +73,20 @@ const uint8_t kTlsFakeChangeCipherSpec[]
     0x01   // Value
 };
 
 static const uint8_t kTls13PskKe = 0;
 static const uint8_t kTls13PskDhKe = 1;
 static const uint8_t kTls13PskAuth = 0;
 static const uint8_t kTls13PskSignAuth = 1;
 
+inline std::ostream& operator<<(std::ostream& os, SSLProtocolVariant v) {
+  return os << ((v == ssl_variant_stream) ? "TLS" : "DTLS");
+}
+
 inline bool IsDtls(uint16_t version) { return (version & 0x8000) == 0x8000; }
 
 inline uint16_t NormalizeTlsVersion(uint16_t version) {
   if (version == 0xfeff) {
     return 0x0302;  // special: DTLS 1.0 == TLS 1.1
   }
   if (IsDtls(version)) {
     return (version ^ 0xffff) + 0x0201;
@@ -130,15 +133,11 @@ class TlsParser {
  private:
   void consume(size_t len) { offset_ += len; }
   const uint8_t* ptr() const { return buffer_.data() + offset_; }
 
   DataBuffer buffer_;
   size_t offset_;
 };
 
-inline std::ostream& operator<<(std::ostream& os, SSLProtocolVariant v) {
-  return os << ((v == ssl_variant_stream) ? "TLS" : "DTLS");
-}
-
 }  // namespace nss_test
 
 #endif
--- a/security/nss/fuzz/mpi_invmod_target.cc
+++ b/security/nss/fuzz/mpi_invmod_target.cc
@@ -27,18 +27,17 @@ extern "C" int LLVMFuzzerTestOneInput(co
   int primeLen = std::max(static_cast<int>(size / 4), 3);
   uint8_t bp[primeLen];
   memcpy(bp, data, primeLen);
   do {
     bp[0] |= 0x80;            /* set high-order bit */
     bp[primeLen - 1] |= 0x01; /* set low-order bit  */
     ++count;
     assert(mp_read_unsigned_octets(&b, bp, primeLen) == MP_OKAY);
-  } while ((res = mpp_make_prime(&b, primeLen * 8, PR_FALSE, nullptr)) !=
-               MP_YES &&
+  } while ((res = mpp_make_prime(&b, primeLen * 8, PR_FALSE)) != MP_YES &&
            count < 10);
   if (res != MP_YES) {
     return 0;
   }
 
   // Use the same prime in OpenSSL B
   char tmp[max_size];
   mp_toradix(&b, tmp, 16);
new file mode 100644
--- /dev/null
+++ b/security/nss/gtests/certdb_gtest/Makefile
@@ -0,0 +1,43 @@
+#! gmake
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#######################################################################
+# (1) Include initial platform-independent assignments (MANDATORY).   #
+#######################################################################
+
+include manifest.mn
+
+#######################################################################
+# (2) Include "global" configuration information. (OPTIONAL)          #
+#######################################################################
+
+include $(CORE_DEPTH)/coreconf/config.mk
+
+#######################################################################
+# (3) Include "component" configuration information. (OPTIONAL)       #
+#######################################################################
+
+
+#######################################################################
+# (4) Include "local" platform-dependent assignments (OPTIONAL).      #
+#######################################################################
+
+include ../common/gtest.mk
+
+#######################################################################
+# (5) Execute "global" rules. (OPTIONAL)                              #
+#######################################################################
+
+include $(CORE_DEPTH)/coreconf/rules.mk
+
+#######################################################################
+# (6) Execute "component" rules. (OPTIONAL)                           #
+#######################################################################
+
+
+#######################################################################
+# (7) Execute "local" rules. (OPTIONAL).                              #
+#######################################################################
new file mode 100644
--- /dev/null
+++ b/security/nss/gtests/certdb_gtest/alg1485_unittest.cc
@@ -0,0 +1,57 @@
+/* -*- 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 <stdint.h>
+
+#include "gtest/gtest.h"
+
+#include "nss.h"
+#include "scoped_ptrs.h"
+
+namespace nss_test {
+
+typedef struct AVATestValuesStr {
+  std::string avaString;
+  bool expectedResult;
+} AVATestValues;
+
+class Alg1485Test : public ::testing::Test,
+                    public ::testing::WithParamInterface<AVATestValues> {};
+
+static const AVATestValues kAVATestStrings[] = {
+    {"CN=Marshall T. Rose, O=Dover Beach Consulting, L=Santa Clara, "
+     "ST=California, C=US",
+     true},
+    {"C=HU,L=Budapest,O=Organization,CN=Example - Qualified Citizen "
+     "CA,2.5.4.97=VATHU-10",
+     true},
+    {"C=HU,L=Budapest,O=Example,CN=Example - Qualified Citizen "
+     "CA,OID.2.5.4.97=VATHU-10",
+     true},
+    {"CN=Somebody,L=Set,O=Up,C=US,1=The,2=Bomb", true},
+    {"OID.2.5.4.6=😑", true},
+    {"2.5.4.6=😑", true},
+    {"OID.moocow=😑", false},      // OIDs must be numeric
+    {"3.2=bad", false},           // OIDs cannot be overly large; 3 is too big
+    {"256.257=bad", false},       // Still too big
+    {"YO=LO", false},             // Unknown Tag, 'YO'
+    {"CN=Tester,ZZ=Top", false},  // Unknown tag, 'ZZ'
+    // These tests are disabled pending Bug 1363416
+    // { "01.02.03=Nope", false }, // Numbers not in minimal form
+    // { "000001.0000000001=👌", false },
+    // { "CN=Somebody,L=Set,O=Up,C=US,01=The,02=Bomb", false },
+};
+
+TEST_P(Alg1485Test, TryParsingAVAStrings) {
+  const AVATestValues& param(GetParam());
+
+  ScopedCERTName certName(CERT_AsciiToName(param.avaString.c_str()));
+  ASSERT_EQ(certName != nullptr, param.expectedResult);
+}
+
+INSTANTIATE_TEST_CASE_P(ParseAVAStrings, Alg1485Test,
+                        ::testing::ValuesIn(kAVATestStrings));
+}
new file mode 100644
--- /dev/null
+++ b/security/nss/gtests/certdb_gtest/certdb_gtest.gyp
@@ -0,0 +1,29 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+{
+  'includes': [
+    '../../coreconf/config.gypi',
+    '../common/gtest.gypi',
+  ],
+  'targets': [
+    {
+      'target_name': 'certdb_gtest',
+      'type': 'executable',
+      'sources': [
+        'alg1485_unittest.cc',
+        '<(DEPTH)/gtests/common/gtests.cc'
+      ],
+      'dependencies': [
+        '<(DEPTH)/exports.gyp:nss_exports',
+        '<(DEPTH)/gtests/google_test/google_test.gyp:gtest',
+        '<(DEPTH)/lib/util/util.gyp:nssutil3',
+        '<(DEPTH)/lib/ssl/ssl.gyp:ssl3',
+        '<(DEPTH)/lib/nss/nss.gyp:nss3',
+      ]
+    }
+  ],
+  'variables': {
+    'module': 'nss'
+  }
+}
new file mode 100644
--- /dev/null
+++ b/security/nss/gtests/certdb_gtest/manifest.mn
@@ -0,0 +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/.
+CORE_DEPTH = ../..
+DEPTH      = ../..
+MODULE = nss
+
+CPPSRCS = \
+      alg1485_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
+
+PROGRAM = certdb_gtest
+
+EXTRA_LIBS = $(DIST)/lib/$(LIB_PREFIX)gtest.$(LIB_SUFFIX) $(EXTRA_OBJS) \
+             ../common/$(OBJDIR)/gtests$(OBJ_SUFFIX)
new file mode 100644
--- /dev/null
+++ b/security/nss/gtests/certhigh_gtest/Makefile
@@ -0,0 +1,43 @@
+#! gmake
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#######################################################################
+# (1) Include initial platform-independent assignments (MANDATORY).   #
+#######################################################################
+
+include manifest.mn
+
+#######################################################################
+# (2) Include "global" configuration information. (OPTIONAL)          #
+#######################################################################
+
+include $(CORE_DEPTH)/coreconf/config.mk
+
+#######################################################################
+# (3) Include "component" configuration information. (OPTIONAL)       #
+#######################################################################
+
+
+#######################################################################
+# (4) Include "local" platform-dependent assignments (OPTIONAL).      #
+#######################################################################
+
+include ../common/gtest.mk
+
+#######################################################################
+# (5) Execute "global" rules. (OPTIONAL)                              #
+#######################################################################
+
+include $(CORE_DEPTH)/coreconf/rules.mk
+
+#######################################################################
+# (6) Execute "component" rules. (OPTIONAL)                           #
+#######################################################################
+
+
+#######################################################################
+# (7) Execute "local" rules. (OPTIONAL).                              #
+#######################################################################
new file mode 100644
--- /dev/null
+++ b/security/nss/gtests/certhigh_gtest/certhigh_gtest.gyp
@@ -0,0 +1,29 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+{
+  'includes': [
+    '../../coreconf/config.gypi',
+    '../common/gtest.gypi',
+  ],
+  'targets': [
+    {
+      'target_name': 'certhigh_gtest',
+      'type': 'executable',
+      'sources': [
+        'certhigh_unittest.cc',
+        '<(DEPTH)/gtests/common/gtests.cc'
+      ],
+      'dependencies': [
+        '<(DEPTH)/exports.gyp:nss_exports',
+        '<(DEPTH)/gtests/google_test/google_test.gyp:gtest',
+        '<(DEPTH)/lib/util/util.gyp:nssutil3',
+        '<(DEPTH)/lib/ssl/ssl.gyp:ssl3',
+        '<(DEPTH)/lib/nss/nss.gyp:nss3',
+      ]
+    }
+  ],
+  'variables': {
+    'module': 'nss'
+  }
+}
new file mode 100644
--- /dev/null
+++ b/security/nss/gtests/certhigh_gtest/certhigh_unittest.cc
@@ -0,0 +1,59 @@
+/* -*- 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 <string>
+
+#include "gtest/gtest.h"
+
+#include "cert.h"
+#include "certt.h"
+#include "secitem.h"
+
+namespace nss_test {
+
+class CERT_FormatNameUnitTest : public ::testing::Test {};
+
+TEST_F(CERT_FormatNameUnitTest, Overflow) {
+  // Construct a CERTName consisting of a single RDN with 20 organizational unit
+  // AVAs and 20 domain component AVAs. The actual contents don't matter, just
+  // the types.
+
+  uint8_t oidValueBytes[] = {0x0c, 0x02, 0x58, 0x58};  // utf8String "XX"
+  SECItem oidValue = {siBuffer, oidValueBytes, sizeof(oidValueBytes)};
+  uint8_t oidTypeOUBytes[] = {0x55, 0x04, 0x0b};  // organizationalUnit
+  SECItem oidTypeOU = {siBuffer, oidTypeOUBytes, sizeof(oidTypeOUBytes)};
+  CERTAVA ouAVA = {oidTypeOU, oidValue};
+  uint8_t oidTypeDCBytes[] = {0x09, 0x92, 0x26, 0x89, 0x93,
+                              0xf2, 0x2c, 0x64, 0x1,  0x19};  // domainComponent
+  SECItem oidTypeDC = {siBuffer, oidTypeDCBytes, sizeof(oidTypeDCBytes)};
+  CERTAVA dcAVA = {oidTypeDC, oidValue};
+
+  const int kNumEachAVA = 20;
+  CERTAVA* avas[(2 * kNumEachAVA) + 1];
+  for (int i = 0; i < kNumEachAVA; i++) {
+    avas[2 * i] = &ouAVA;
+    avas[(2 * i) + 1] = &dcAVA;
+  }
+  avas[2 * kNumEachAVA] = nullptr;
+
+  CERTRDN rdn = {avas};
+  CERTRDN* rdns[2];
+  rdns[0] = &rdn;
+  rdns[1] = nullptr;
+
+  std::string expectedResult =
+      "XX<br>XX<br>XX<br>XX<br>XX<br>XX<br>XX<br>XX<br>XX<br>XX<br>XX<br>XX<br>"
+      "XX<br>XX<br>XX<br>XX<br>XX<br>XX<br>XX<br>XX<br>XX<br>XX<br>XX<br>XX<br>"
+      "XX<br>XX<br>XX<br>XX<br>XX<br>XX<br>XX<br>XX<br>XX<br>XX<br>XX<br>XX<br>"
+      "XX<br>XX<br>XX<br>XX<br>";
+
+  CERTName name = {nullptr, rdns};
+  char* result = CERT_FormatName(&name);
+  EXPECT_EQ(expectedResult, result);
+  PORT_Free(result);
+}
+
+}  // namespace nss_test
new file mode 100644
--- /dev/null
+++ b/security/nss/gtests/certhigh_gtest/manifest.mn
@@ -0,0 +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/.
+CORE_DEPTH = ../..
+DEPTH      = ../..
+MODULE = nss
+
+CPPSRCS = \
+      certhigh_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
+
+PROGRAM = certhigh_gtest
+
+EXTRA_LIBS = $(DIST)/lib/$(LIB_PREFIX)gtest.$(LIB_SUFFIX) $(EXTRA_OBJS) \
+             ../common/$(OBJDIR)/gtests$(OBJ_SUFFIX)
new file mode 100644
--- /dev/null
+++ b/security/nss/gtests/freebl_gtest/dh_unittest.cc
@@ -0,0 +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/.
+
+#include "blapi.h"
+#include "gtest/gtest.h"
+
+namespace nss_test {
+
+class DHTest : public ::testing::Test {
+ protected:
+  void TestGenParamSuccess(int size) {
+    DHParams *params;
+    for (int i = 0; i < 10; i++) {
+      EXPECT_EQ(SECSuccess, DH_GenParam(size, &params));
+      PORT_FreeArena(params->arena, PR_TRUE);
+    }
+  }
+};
+
+// Test parameter generation for minimum and some common key sizes
+TEST_F(DHTest, DhGenParamSuccessTest16) { TestGenParamSuccess(16); }
+TEST_F(DHTest, DhGenParamSuccessTest224) { TestGenParamSuccess(224); }
+TEST_F(DHTest, DhGenParamSuccessTest256) { TestGenParamSuccess(256); }
+
+}  // nss_test
--- a/security/nss/gtests/freebl_gtest/freebl_gtest.gyp
+++ b/security/nss/gtests/freebl_gtest/freebl_gtest.gyp
@@ -7,16 +7,17 @@
     '../common/gtest.gypi',
   ],
   'targets': [
     {
       'target_name': 'freebl_gtest',
       'type': 'executable',
       'sources': [
         'mpi_unittest.cc',
+        'dh_unittest.cc',
         '<(DEPTH)/gtests/common/gtests.cc'
       ],
       'dependencies': [
         '<(DEPTH)/exports.gyp:nss_exports',
         '<(DEPTH)/lib/util/util.gyp:nssutil3',
         '<(DEPTH)/gtests/google_test/google_test.gyp:gtest',
         '<(DEPTH)/lib/nss/nss.gyp:nss_static',
         '<(DEPTH)/lib/pk11wrap/pk11wrap.gyp:pk11wrap_static',
--- a/security/nss/gtests/freebl_gtest/mpi_unittest.cc
+++ b/security/nss/gtests/freebl_gtest/mpi_unittest.cc
@@ -35,35 +35,71 @@ void gettime(struct timespec *tp) {
   clock_gettime(CLOCK_MONOTONIC, tp);
 #endif
 }
 
 class MPITest : public ::testing::Test {
  protected:
   void TestCmp(const std::string a_string, const std::string b_string,
                int result) {
-    mp_int a, b, c;
+    mp_int a, b;
     MP_DIGITS(&a) = 0;
     MP_DIGITS(&b) = 0;
-    MP_DIGITS(&c) = 0;
     ASSERT_EQ(MP_OKAY, mp_init(&a));
     ASSERT_EQ(MP_OKAY, mp_init(&b));
 
     mp_read_radix(&a, a_string.c_str(), 16);
     mp_read_radix(&b, b_string.c_str(), 16);
     EXPECT_EQ(result, mp_cmp(&a, &b));
+
+    mp_clear(&a);
+    mp_clear(&b);
   }
 };
 
 TEST_F(MPITest, MpiCmp01Test) { TestCmp("0", "1", -1); }
 TEST_F(MPITest, MpiCmp10Test) { TestCmp("1", "0", 1); }
 TEST_F(MPITest, MpiCmp00Test) { TestCmp("0", "0", 0); }
 TEST_F(MPITest, MpiCmp11Test) { TestCmp("1", "1", 0); }
 
-TEST_F(MPITest, MpiCmpConstTest) {
+TEST_F(MPITest, MpiCmpUnalignedTest) {
+  mp_int a, b, c;
+  MP_DIGITS(&a) = 0;
+  MP_DIGITS(&b) = 0;
+  MP_DIGITS(&c) = 0;
+  ASSERT_EQ(MP_OKAY, mp_init(&a));
+  ASSERT_EQ(MP_OKAY, mp_init(&b));
+  ASSERT_EQ(MP_OKAY, mp_init(&c));
+
+  mp_read_radix(&a, "ffffffffffffffff3b4e802b4e1478", 16);
+  mp_read_radix(&b, "ffffffffffffffff3b4e802b4e1478", 16);
+  EXPECT_EQ(0, mp_cmp(&a, &b));
+
+  // Now change a and b such that they contain the same numbers but are not
+  // aligned.
+  // a = ffffffffffffff|ff3b4e802b4e1478
+  // b = ffffffffffffffff|3b4e802b4e1478
+  MP_DIGITS(&b)[0] &= 0x00ffffffffffffff;
+  MP_DIGITS(&b)[1] = 0xffffffffffffffff;
+  EXPECT_EQ(-1, mp_cmp(&a, &b));
+
+  ASSERT_EQ(MP_OKAY, mp_sub(&a, &b, &c));
+  char c_tmp[40];
+  ASSERT_EQ(MP_OKAY, mp_toradix(&c, c_tmp, 16));
+  ASSERT_TRUE(strncmp(c_tmp, "feffffffffffffff100000000000000", 31));
+
+  mp_clear(&a);
+  mp_clear(&b);
+  mp_clear(&c);
+}
+
+// This test is slow. Disable it by default so we can run these tests on CI.
+class DISABLED_MPITest : public ::testing::Test {};
+
+TEST_F(DISABLED_MPITest, MpiCmpConstTest) {
   mp_int a, b, c;
   MP_DIGITS(&a) = 0;
   MP_DIGITS(&b) = 0;
   MP_DIGITS(&c) = 0;
   ASSERT_EQ(MP_OKAY, mp_init(&a));
   ASSERT_EQ(MP_OKAY, mp_init(&b));
   ASSERT_EQ(MP_OKAY, mp_init(&c));
 
@@ -110,11 +146,15 @@ TEST_F(MPITest, MpiCmpConstTest) {
     gettime(&end);
     unsigned long long used = end.tv_sec * 1000000000L + end.tv_nsec;
     used -= static_cast<unsigned long long>(start.tv_sec * 1000000000L +
                                             start.tv_nsec);
     time_c += used;
     ASSERT_EQ(1, r);
   }
   printf("time c: %u\n", time_c / runs);
+
+  mp_clear(&a);
+  mp_clear(&b);
+  mp_clear(&c);
 }
 
 }  // nss_test
--- a/security/nss/gtests/manifest.mn
+++ b/security/nss/gtests/manifest.mn
@@ -3,14 +3,16 @@
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 CORE_DEPTH = ..
 DEPTH      = ..
 
 DIRS = \
 	google_test \
 	common \
+	certdb_gtest \
+	certhigh_gtest \
 	der_gtest \
 	util_gtest \
 	pk11_gtest \
 	ssl_gtest \
         nss_bogo_shim \
 	$(NULL)
--- a/security/nss/gtests/nss_bogo_shim/config.h
+++ b/security/nss/gtests/nss_bogo_shim/config.h
@@ -60,18 +60,18 @@ class ConfigEntry : public ConfigEntryBa
 class Config {
  public:
   enum Status { kOK, kUnknownFlag, kMalformedArgument, kMissingValue };
 
   Config() : entries_() {}
 
   template <typename T>
   void AddEntry(const std::string& name, T init) {
-    entries_[name] = std::unique_ptr<ConfigEntryBase>(
-      new ConfigEntry<T>(name, init));
+    entries_[name] =
+        std::unique_ptr<ConfigEntryBase>(new ConfigEntry<T>(name, init));
   }
 
   Status ParseArgs(int argc, char** argv);
 
   template <typename T>
   T get(const std::string& key) const {
     auto e = entry(key);
     assert(e->type() == typeid(T).name());
--- a/security/nss/gtests/ssl_gtest/ssl_0rtt_unittest.cc
+++ b/security/nss/gtests/ssl_gtest/ssl_0rtt_unittest.cc
@@ -222,27 +222,27 @@ TEST_P(TlsConnectTls13, TestTls13ZeroRtt
   server_->StartConnect();
 
   // We will send the early data xtn without sending actual early data. Thus
   // a 1.2 server shouldn't fail until the client sends an alert because the
   // client sends end_of_early_data only after reading the server's flight.
   client_->Set0RttEnabled(true);
 
   client_->ExpectSendAlert(kTlsAlertIllegalParameter);
-  if (mode_ == STREAM) {
+  if (variant_ == ssl_variant_stream) {
     server_->ExpectSendAlert(kTlsAlertUnexpectedMessage);
   }
   client_->Handshake();
   server_->Handshake();
   ASSERT_TRUE_WAIT(
       (client_->error_code() == SSL_ERROR_DOWNGRADE_WITH_EARLY_DATA), 2000);
 
   // DTLS will timeout as we bump the epoch when installing the early app data
   // cipher suite. Thus the encrypted alert will be ignored.
-  if (mode_ == STREAM) {
+  if (variant_ == ssl_variant_stream) {
     // The client sends an encrypted alert message.
     ASSERT_TRUE_WAIT(
         (server_->error_code() == SSL_ERROR_RX_UNEXPECTED_APPLICATION_DATA),
         2000);
   }
 }
 
 // The client should abort the connection when sending a 0-rtt handshake but
@@ -264,30 +264,30 @@ TEST_P(TlsConnectTls13, TestTls13ZeroRtt
   client_->StartConnect();
   server_->StartConnect();
 
   // Send the early data xtn in the CH, followed by early app data. The server
   // will fail right after sending its flight, when receiving the early data.
   client_->Set0RttEnabled(true);
   ZeroRttSendReceive(true, false, [this]() {
     client_->ExpectSendAlert(kTlsAlertIllegalParameter);
-    if (mode_ == STREAM) {
+    if (variant_ == ssl_variant_stream) {
       server_->ExpectSendAlert(kTlsAlertUnexpectedMessage);
     }
     return true;
   });
 
   client_->Handshake();
   server_->Handshake();
   ASSERT_TRUE_WAIT(
       (client_->error_code() == SSL_ERROR_DOWNGRADE_WITH_EARLY_DATA), 2000);
 
   // DTLS will timeout as we bump the epoch when installing the early app data
   // cipher suite. Thus the encrypted alert will be ignored.
-  if (mode_ == STREAM) {
+  if (variant_ == ssl_variant_stream) {
     // The server sends an alert when receiving the early app data record.
     ASSERT_TRUE_WAIT(
         (server_->error_code() == SSL_ERROR_RX_UNEXPECTED_APPLICATION_DATA),
         2000);
   }
 }
 
 static void CheckEarlyDataLimit(const std::shared_ptr<TlsAgent>& agent,
@@ -311,17 +311,17 @@ TEST_P(TlsConnectTls13, SendTooMuchEarly
   ExpectResumption(RESUME_TICKET);
 
   ExpectAlert(client_, kTlsAlertEndOfEarlyData);
   client_->Handshake();
   CheckEarlyDataLimit(client_, short_size);
 
   PRInt32 sent;
   // Writing more than the limit will succeed in TLS, but fail in DTLS.
-  if (mode_ == STREAM) {
+  if (variant_ == ssl_variant_stream) {
     sent = PR_Write(client_->ssl_fd(), big_message,
                     static_cast<PRInt32>(strlen(big_message)));
   } else {
     sent = PR_Write(client_->ssl_fd(), big_message,
                     static_cast<PRInt32>(strlen(big_message)));
     EXPECT_GE(0, sent);
     EXPECT_EQ(PR_WOULD_BLOCK_ERROR, PORT_GetError());
 
@@ -372,31 +372,31 @@ TEST_P(TlsConnectTls13, ReceiveTooMuchEa
   EXPECT_EQ(SECSuccess,
             SSLInt_SetSocketMaxEarlyDataSize(client_->ssl_fd(), 1000));
 
   // Send message
   const char* message = "0123456789abcdef";
   const PRInt32 message_len = static_cast<PRInt32>(strlen(message));
   EXPECT_EQ(message_len, PR_Write(client_->ssl_fd(), message, message_len));
 
-  if (mode_ == STREAM) {
+  if (variant_ == ssl_variant_stream) {
     // This error isn't fatal for DTLS.
     ExpectAlert(server_, kTlsAlertUnexpectedMessage);
   }
   server_->Handshake();  // Process ClientHello, send server flight.
   server_->Handshake();  // Just to make sure that we don't read ahead.
   CheckEarlyDataLimit(server_, limit);
 
   // Attempt to read early data.
   std::vector<uint8_t> buf(strlen(message) + 1);
   EXPECT_GT(0, PR_Read(server_->ssl_fd(), buf.data(), buf.capacity()));
-  if (mode_ == STREAM) {
+  if (variant_ == ssl_variant_stream) {
     server_->CheckErrorCode(SSL_ERROR_TOO_MUCH_EARLY_DATA);
   }
 
   client_->Handshake();  // Process the handshake.
   client_->Handshake();  // Process the alert.
-  if (mode_ == STREAM) {
+  if (variant_ == ssl_variant_stream) {
     client_->CheckErrorCode(SSL_ERROR_HANDSHAKE_UNEXPECTED_ALERT);
   }
 }
 
 }  // namespace nss_test
--- a/security/nss/gtests/ssl_gtest/ssl_agent_unittest.cc
+++ b/security/nss/gtests/ssl_gtest/ssl_agent_unittest.cc
@@ -199,19 +199,20 @@ TEST_F(TlsAgentStreamTestServer, Set0Rtt
   ProcessMessage(buffer, TlsAgent::STATE_CONNECTING);
   MakeRecord(kTlsApplicationDataType, SSL_LIBRARY_VERSION_TLS_1_3,
              reinterpret_cast<const uint8_t *>(k0RttData), strlen(k0RttData),
              &buffer);
   ExpectAlert(kTlsAlertBadRecordMac);
   ProcessMessage(buffer, TlsAgent::STATE_ERROR, SSL_ERROR_BAD_MAC_READ);
 }
 
-INSTANTIATE_TEST_CASE_P(AgentTests, TlsAgentTest,
-                        ::testing::Combine(TlsAgentTestBase::kTlsRolesAll,
-                                           TlsConnectTestBase::kTlsModesStream,
-                                           TlsConnectTestBase::kTlsVAll));
+INSTANTIATE_TEST_CASE_P(
+    AgentTests, TlsAgentTest,
+    ::testing::Combine(TlsAgentTestBase::kTlsRolesAll,
+                       TlsConnectTestBase::kTlsVariantsStream,
+                       TlsConnectTestBase::kTlsVAll));
 INSTANTIATE_TEST_CASE_P(ClientTests, TlsAgentTestClient,
-                        ::testing::Combine(TlsConnectTestBase::kTlsModesAll,
+                        ::testing::Combine(TlsConnectTestBase::kTlsVariantsAll,
                                            TlsConnectTestBase::kTlsVAll));
 INSTANTIATE_TEST_CASE_P(ClientTests13, TlsAgentTestClient13,
-                        ::testing::Combine(TlsConnectTestBase::kTlsModesAll,
+                        ::testing::Combine(TlsConnectTestBase::kTlsVariantsAll,
                                            TlsConnectTestBase::kTlsV13));
 }  // namespace nss_test
--- a/security/nss/gtests/ssl_gtest/ssl_auth_unittest.cc
+++ b/security/nss/gtests/ssl_gtest/ssl_auth_unittest.cc
@@ -711,18 +711,18 @@ TEST_F(TlsAgentStreamTestServer, Configu
   EXPECT_FALSE(agent_->ConfigServerCert(TlsAgent::kServerRsaPss, false,
                                         &ServerCertDataRsaPkcs1Decrypt));
 
   // Configuring for only rsa_pss should work.
   EXPECT_TRUE(agent_->ConfigServerCert(TlsAgent::kServerRsaPss, false,
                                        &ServerCertDataRsaPss));
 }
 
-// mode, version, certificate, auth type, signature scheme
-typedef std::tuple<std::string, uint16_t, std::string, SSLAuthType,
+// variant, version, certificate, auth type, signature scheme
+typedef std::tuple<SSLProtocolVariant, uint16_t, std::string, SSLAuthType,
                    SSLSignatureScheme>
     SignatureSchemeProfile;
 
 class TlsSignatureSchemeConfiguration
     : public TlsConnectTestBase,
       public ::testing::WithParamInterface<SignatureSchemeProfile> {
  public:
   TlsSignatureSchemeConfiguration()
@@ -773,59 +773,59 @@ TEST_P(TlsSignatureSchemeConfiguration, 
   server_->SetSignatureSchemes(&signature_scheme_, 1);
   Connect();
   CheckKeys(ssl_kea_ecdh, ssl_grp_ec_curve25519, auth_type_, signature_scheme_);
 }
 
 INSTANTIATE_TEST_CASE_P(
     SignatureSchemeRsa, TlsSignatureSchemeConfiguration,
     ::testing::Combine(
-        TlsConnectTestBase::kTlsModesAll, TlsConnectTestBase::kTlsV12Plus,
+        TlsConnectTestBase::kTlsVariantsAll, TlsConnectTestBase::kTlsV12Plus,
         ::testing::Values(TlsAgent::kServerRsaSign),
         ::testing::Values(ssl_auth_rsa_sign),
         ::testing::Values(ssl_sig_rsa_pkcs1_sha256, ssl_sig_rsa_pkcs1_sha384,
                           ssl_sig_rsa_pkcs1_sha512, ssl_sig_rsa_pss_sha256,
                           ssl_sig_rsa_pss_sha384)));
 // PSS with SHA-512 needs a bigger key to work.
 INSTANTIATE_TEST_CASE_P(
     SignatureSchemeBigRsa, TlsSignatureSchemeConfiguration,
-    ::testing::Combine(TlsConnectTestBase::kTlsModesAll,
+    ::testing::Combine(TlsConnectTestBase::kTlsVariantsAll,
                        TlsConnectTestBase::kTlsV12Plus,
                        ::testing::Values(TlsAgent::kRsa2048),
                        ::testing::Values(ssl_auth_rsa_sign),
                        ::testing::Values(ssl_sig_rsa_pss_sha512)));
 INSTANTIATE_TEST_CASE_P(
     SignatureSchemeRsaSha1, TlsSignatureSchemeConfiguration,
-    ::testing::Combine(TlsConnectTestBase::kTlsModesAll,
+    ::testing::Combine(TlsConnectTestBase::kTlsVariantsAll,
                        TlsConnectTestBase::kTlsV12,
                        ::testing::Values(TlsAgent::kServerRsa),
                        ::testing::Values(ssl_auth_rsa_sign),
                        ::testing::Values(ssl_sig_rsa_pkcs1_sha1)));
 INSTANTIATE_TEST_CASE_P(
     SignatureSchemeEcdsaP256, TlsSignatureSchemeConfiguration,
-    ::testing::Combine(TlsConnectTestBase::kTlsModesAll,
+    ::testing::Combine(TlsConnectTestBase::kTlsVariantsAll,
                        TlsConnectTestBase::kTlsV12Plus,
                        ::testing::Values(TlsAgent::kServerEcdsa256),
                        ::testing::Values(ssl_auth_ecdsa),
                        ::testing::Values(ssl_sig_ecdsa_secp256r1_sha256)));
 INSTANTIATE_TEST_CASE_P(
     SignatureSchemeEcdsaP384, TlsSignatureSchemeConfiguration,
-    ::testing::Combine(TlsConnectTestBase::kTlsModesAll,
+    ::testing::Combine(TlsConnectTestBase::kTlsVariantsAll,
                        TlsConnectTestBase::kTlsV12Plus,
                        ::testing::Values(TlsAgent::kServerEcdsa384),
                        ::testing::Values(ssl_auth_ecdsa),
                        ::testing::Values(ssl_sig_ecdsa_secp384r1_sha384)));
 INSTANTIATE_TEST_CASE_P(
     SignatureSchemeEcdsaP521, TlsSignatureSchemeConfiguration,
-    ::testing::Combine(TlsConnectTestBase::kTlsModesAll,
+    ::testing::Combine(TlsConnectTestBase::kTlsVariantsAll,
                        TlsConnectTestBase::kTlsV12Plus,
                        ::testing::Values(TlsAgent::kServerEcdsa521),
                        ::testing::Values(ssl_auth_ecdsa),
                        ::testing::Values(ssl_sig_ecdsa_secp521r1_sha512)));
 INSTANTIATE_TEST_CASE_P(
     SignatureSchemeEcdsaSha1, TlsSignatureSchemeConfiguration,
-    ::testing::Combine(TlsConnectTestBase::kTlsModesAll,
+    ::testing::Combine(TlsConnectTestBase::kTlsVariantsAll,
                        TlsConnectTestBase::kTlsV12,
                        ::testing::Values(TlsAgent::kServerEcdsa256,
                                          TlsAgent::kServerEcdsa384),
                        ::testing::Values(ssl_auth_ecdsa),
                        ::testing::Values(ssl_sig_ecdsa_sha1)));
 }
--- a/security/nss/gtests/ssl_gtest/ssl_ciphersuite_unittest.cc
+++ b/security/nss/gtests/ssl_gtest/ssl_ciphersuite_unittest.cc
@@ -17,27 +17,27 @@ extern "C" {
 }
 
 #include "gtest_utils.h"
 #include "tls_connect.h"
 #include "tls_parser.h"
 
 namespace nss_test {
 
-// mode, version, cipher suite
-typedef std::tuple<std::string, uint16_t, uint16_t, SSLNamedGroup,
+// variant, version, cipher suite
+typedef std::tuple<SSLProtocolVariant, uint16_t, uint16_t, SSLNamedGroup,
                    SSLSignatureScheme>
     CipherSuiteProfile;
 
 class TlsCipherSuiteTestBase : public TlsConnectTestBase {
  public:
-  TlsCipherSuiteTestBase(const std::string &mode, uint16_t version,
+  TlsCipherSuiteTestBase(SSLProtocolVariant variant, uint16_t version,
                          uint16_t cipher_suite, SSLNamedGroup group,
                          SSLSignatureScheme signature_scheme)
-      : TlsConnectTestBase(mode, version),
+      : TlsConnectTestBase(variant, version),
         cipher_suite_(cipher_suite),
         group_(group),
         signature_scheme_(signature_scheme),
         csinfo_({0}) {
     SECStatus rv =
         SSL_GetCipherSuiteInfo(cipher_suite_, &csinfo_, sizeof(csinfo_));
     EXPECT_EQ(SECSuccess, rv);
     if (rv == SECSuccess) {
@@ -254,26 +254,26 @@ TEST_P(TlsCipherSuiteTest, ReadLimit) {
   server_->ReadBytes();  // This should be OK.
 
   // The payload needs to be big enough to pass for encrypted.  In the extreme
   // case (TLS 1.3), this means 1 for payload, 1 for content type and 16 for
   // authentication tag.
   static const uint8_t payload[18] = {6};
   DataBuffer record;
   uint64_t epoch;
-  if (mode_ == DGRAM) {
+  if (variant_ == ssl_variant_datagram) {
     if (version_ == SSL_LIBRARY_VERSION_TLS_1_3) {
       epoch = 3;  // Application traffic keys.
     } else {
       epoch = 1;
     }
   } else {
     epoch = 0;
   }
-  TlsAgentTestBase::MakeRecord(mode_, kTlsApplicationDataType, version_,
+  TlsAgentTestBase::MakeRecord(variant_, kTlsApplicationDataType, version_,
                                payload, sizeof(payload), &record,
                                (epoch << 48) | record_limit());
   server_->adapter()->PacketReceived(record);
   server_->ExpectReadWriteError();
   server_->ReadBytes();
   EXPECT_EQ(SSL_ERROR_TOO_MANY_RECORDS, server_->error_code());
 }
 
@@ -291,17 +291,17 @@ TEST_P(TlsCipherSuiteTest, WriteLimit) {
 
 // This awful macro makes the test instantiations easier to read.
 #define INSTANTIATE_CIPHER_TEST_P(name, modes, versions, groups, sigalgs, ...) \
   static const uint16_t k##name##CiphersArr[] = {__VA_ARGS__};                 \
   static const ::testing::internal::ParamGenerator<uint16_t>                   \
       k##name##Ciphers = ::testing::ValuesIn(k##name##CiphersArr);             \
   INSTANTIATE_TEST_CASE_P(                                                     \
       CipherSuite##name, TlsCipherSuiteTest,                                   \
-      ::testing::Combine(TlsConnectTestBase::kTlsModes##modes,                 \
+      ::testing::Combine(TlsConnectTestBase::kTlsVariants##modes,              \
                          TlsConnectTestBase::kTls##versions, k##name##Ciphers, \
                          groups, sigalgs));
 
 static const auto kDummyNamedGroupParams = ::testing::Values(ssl_grp_none);
 static const auto kDummySignatureSchemesParams =
     ::testing::Values(ssl_sig_none);
 
 #ifndef NSS_DISABLE_TLS_1_3
@@ -400,17 +400,17 @@ inline std::ostream &operator<<(std::ost
                 << "\", key size = " << vals.keySize;
 }
 
 class SecurityStatusTest
     : public TlsCipherSuiteTestBase,
       public ::testing::WithParamInterface<SecStatusParams> {
  public:
   SecurityStatusTest()
-      : TlsCipherSuiteTestBase("TLS", GetParam().version,
+      : TlsCipherSuiteTestBase(ssl_variant_stream, GetParam().version,
                                GetParam().cipher_suite, ssl_grp_none,
                                ssl_sig_none) {}
 };
 
 // SSL_SecurityStatus produces fairly useless output when compared to
 // SSL_GetCipherSuiteInfo and SSL_GetChannelInfo, but we can't break it, so we
 // need to check it.
 TEST_P(SecurityStatusTest, CheckSecurityStatus) {
--- a/security/nss/gtests/ssl_gtest/ssl_damage_unittest.cc
+++ b/security/nss/gtests/ssl_gtest/ssl_damage_unittest.cc
@@ -77,17 +77,17 @@ TEST_P(TlsConnectGenericPre13, DamageSer
 TEST_P(TlsConnectTls13, DamageServerSignature) {
   EnsureTlsSetup();
   auto filter =
       std::make_shared<TlsLastByteDamager>(kTlsHandshakeCertificateVerify);
   server_->SetTlsRecordFilter(filter);
   filter->EnableDecryption();
   client_->ExpectSendAlert(kTlsAlertDecryptError);
   // The server can't read the client's alert, so it also sends an alert.
-  if (mode_ == STREAM) {
+  if (variant_ == ssl_variant_stream) {
     server_->ExpectSendAlert(kTlsAlertBadRecordMac);
     ConnectExpectFail();
     server_->CheckErrorCode(SSL_ERROR_BAD_MAC_READ);
   } else {
     ConnectExpectFailOneSide(TlsAgent::CLIENT);
   }
   client_->CheckErrorCode(SEC_ERROR_BAD_SIGNATURE);
 }
--- a/security/nss/gtests/ssl_gtest/ssl_dhe_unittest.cc
+++ b/security/nss/gtests/ssl_gtest/ssl_dhe_unittest.cc
@@ -267,20 +267,21 @@ class TlsDheSkeChangeYClient : public Tl
     ChangeY(input, output, 0, server_filter_->prime());
     return CHANGE;
   }
 
  private:
   std::shared_ptr<const TlsDheSkeChangeYServer> server_filter_;
 };
 
-/* This matrix includes: mode (stream/datagram), TLS version, what change to
+/* This matrix includes: variant (stream/datagram), TLS version, what change to
  * make to dh_Ys, whether the client will be configured to require DH named
  * groups.  Test all combinations. */
-typedef std::tuple<std::string, uint16_t, TlsDheSkeChangeY::ChangeYTo, bool>
+typedef std::tuple<SSLProtocolVariant, uint16_t, TlsDheSkeChangeY::ChangeYTo,
+                   bool>
     DamageDHYProfile;
 class TlsDamageDHYTest
     : public TlsConnectTestBase,
       public ::testing::WithParamInterface<DamageDHYProfile> {
  public:
   TlsDamageDHYTest()
       : TlsConnectTestBase(std::get<0>(GetParam()), std::get<1>(GetParam())) {}
 };
@@ -353,23 +354,23 @@ static const TlsDheSkeChangeY::ChangeYTo
     TlsDheSkeChangeY::kYPMinusOne, TlsDheSkeChangeY::kYGreaterThanP,
     TlsDheSkeChangeY::kYTooLarge,  TlsDheSkeChangeY::kYZeroPad};
 static ::testing::internal::ParamGenerator<TlsDheSkeChangeY::ChangeYTo> kAllY =
     ::testing::ValuesIn(kAllYArr);
 static const bool kTrueFalseArr[] = {true, false};
 static ::testing::internal::ParamGenerator<bool> kTrueFalse =
     ::testing::ValuesIn(kTrueFalseArr);
 
-INSTANTIATE_TEST_CASE_P(DamageYStream, TlsDamageDHYTest,
-                        ::testing::Combine(TlsConnectTestBase::kTlsModesStream,
-                                           TlsConnectTestBase::kTlsV10ToV12,
-                                           kAllY, kTrueFalse));
+INSTANTIATE_TEST_CASE_P(
+    DamageYStream, TlsDamageDHYTest,
+    ::testing::Combine(TlsConnectTestBase::kTlsVariantsStream,
+                       TlsConnectTestBase::kTlsV10ToV12, kAllY, kTrueFalse));
 INSTANTIATE_TEST_CASE_P(
     DamageYDatagram, TlsDamageDHYTest,
-    ::testing::Combine(TlsConnectTestBase::kTlsModesDatagram,
+    ::testing::Combine(TlsConnectTestBase::kTlsVariantsDatagram,
                        TlsConnectTestBase::kTlsV11V12, kAllY, kTrueFalse));
 
 class TlsDheSkeMakePEven : public TlsHandshakeFilter {
  public:
   virtual PacketFilter::Action FilterHandshake(
       const TlsHandshakeFilter::HandshakeHeader& header,
       const DataBuffer& input, DataBuffer* output) {
     if (header.handshake_type() != kTlsHandshakeServerKeyExchange) {
--- a/security/nss/gtests/ssl_gtest/ssl_ecdh_unittest.cc
+++ b/security/nss/gtests/ssl_gtest/ssl_ecdh_unittest.cc
@@ -569,18 +569,18 @@ TEST_P(TlsConnectGenericPre13, ConnectEC
 TEST_P(TlsConnectGenericPre13, ConnectECDHEmptyClientPoint) {
   // add packet filter
   client_->SetPacketFilter(std::make_shared<ECCClientKEXFilter>());
   ConnectExpectAlert(server_, kTlsAlertIllegalParameter);
   server_->CheckErrorCode(SSL_ERROR_RX_MALFORMED_CLIENT_KEY_EXCH);
 }
 
 INSTANTIATE_TEST_CASE_P(KeyExchangeTest, TlsKeyExchangeTest,
-                        ::testing::Combine(TlsConnectTestBase::kTlsModesAll,
+                        ::testing::Combine(TlsConnectTestBase::kTlsVariantsAll,
                                            TlsConnectTestBase::kTlsV11Plus));
 
 #ifndef NSS_DISABLE_TLS_1_3
 INSTANTIATE_TEST_CASE_P(KeyExchangeTest, TlsKeyExchangeTest13,
-                        ::testing::Combine(TlsConnectTestBase::kTlsModesAll,
+                        ::testing::Combine(TlsConnectTestBase::kTlsVariantsAll,
                                            TlsConnectTestBase::kTlsV13));
 #endif
 
 }  // namespace nss_test
--- a/security/nss/gtests/ssl_gtest/ssl_exporter_unittest.cc
+++ b/security/nss/gtests/ssl_gtest/ssl_exporter_unittest.cc
@@ -72,16 +72,43 @@ TEST_P(TlsConnectTls13, ExporterContextE
   } else {
     server_->EnableSingleCipher(TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA);
   }
   Connect();
   CheckKeys();
   ExportAndCompare(client_, server_, false);
 }
 
+TEST_P(TlsConnectGenericPre13, ExporterContextLengthTooLong) {
+  static const uint8_t kExporterContextTooLong[PR_UINT16_MAX] = {
+      0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC, 0xDE, 0xFF};
+
+  EnsureTlsSetup();
+  Connect();
+  CheckKeys();
+
+  static const size_t exporter_len = 10;
+  uint8_t client_value[exporter_len] = {0};
+  EXPECT_EQ(SECFailure,
+            SSL_ExportKeyingMaterial(client_->ssl_fd(), kExporterLabel,
+                                     strlen(kExporterLabel), PR_TRUE,
+                                     kExporterContextTooLong,
+                                     sizeof(kExporterContextTooLong),
+                                     client_value, sizeof(client_value)));
+  EXPECT_EQ(PORT_GetError(), SEC_ERROR_INVALID_ARGS);
+  uint8_t server_value[exporter_len] = {0xff};
+  EXPECT_EQ(SECFailure,
+            SSL_ExportKeyingMaterial(server_->ssl_fd(), kExporterLabel,
+                                     strlen(kExporterLabel), PR_TRUE,
+                                     kExporterContextTooLong,
+                                     sizeof(kExporterContextTooLong),
+                                     server_value, sizeof(server_value)));
+  EXPECT_EQ(PORT_GetError(), SEC_ERROR_INVALID_ARGS);
+}
+
 // This has a weird signature so that it can be passed to the SNI callback.
 int32_t RegularExporterShouldFail(TlsAgent* agent, const SECItem* srvNameArr,
                                   PRUint32 srvNameArrSize) {
   uint8_t val[10];
   EXPECT_EQ(SECFailure, SSL_ExportKeyingMaterial(
                             agent->ssl_fd(), kExporterLabel,
                             strlen(kExporterLabel), PR_TRUE, kExporterContext,
                             sizeof(kExporterContext), val, sizeof(val)))
@@ -94,16 +121,17 @@ TEST_P(TlsConnectTls13, EarlyExporter) {
   ExpectAlert(client_, kTlsAlertEndOfEarlyData);
   client_->Set0RttEnabled(true);
   server_->Set0RttEnabled(true);
   ExpectResumption(RESUME_TICKET);
 
   client_->Handshake();  // Send ClientHello.
   uint8_t client_value[10] = {0};
   RegularExporterShouldFail(client_.get(), nullptr, 0);
+
   EXPECT_EQ(SECSuccess,
             SSL_ExportEarlyKeyingMaterial(
                 client_->ssl_fd(), kExporterLabel, strlen(kExporterLabel),
                 kExporterContext, sizeof(kExporterContext), client_value,
                 sizeof(client_value)));
 
   server_->SetSniCallback(RegularExporterShouldFail);
   server_->Handshake();  // Handle ClientHello.
--- a/security/nss/gtests/ssl_gtest/ssl_extension_unittest.cc
+++ b/security/nss/gtests/ssl_gtest/ssl_extension_unittest.cc
@@ -161,20 +161,18 @@ class TlsExtensionAppender : public TlsH
 
   const uint8_t handshake_type_;
   const uint16_t extension_;
   const DataBuffer data_;
 };
 
 class TlsExtensionTestBase : public TlsConnectTestBase {
  protected:
-  TlsExtensionTestBase(Mode mode, uint16_t version)
-      : TlsConnectTestBase(mode, version) {}
-  TlsExtensionTestBase(const std::string& mode, uint16_t version)
-      : TlsConnectTestBase(mode, version) {}
+  TlsExtensionTestBase(SSLProtocolVariant variant, uint16_t version)
+      : TlsConnectTestBase(variant, version) {}
 
   void ClientHelloErrorTest(std::shared_ptr<PacketFilter> filter,
                             uint8_t desc = kTlsAlertDecodeError) {
     client_->SetPacketFilter(filter);
     ConnectExpectAlert(server_, desc);
   }
 
   void ServerHelloErrorTest(std::shared_ptr<PacketFilter> filter,
@@ -211,39 +209,41 @@ class TlsExtensionTestBase : public TlsC
     client_->CheckErrorCode(client_error);
     server_->CheckErrorCode(server_error);
   }
 };
 
 class TlsExtensionTestDtls : public TlsExtensionTestBase,
                              public ::testing::WithParamInterface<uint16_t> {
  public:
-  TlsExtensionTestDtls() : TlsExtensionTestBase(DGRAM, GetParam()) {}
+  TlsExtensionTestDtls()
+      : TlsExtensionTestBase(ssl_variant_datagram, GetParam()) {}
 };
 
-class TlsExtensionTest12Plus
-    : public TlsExtensionTestBase,
-      public ::testing::WithParamInterface<std::tuple<std::string, uint16_t>> {
+class TlsExtensionTest12Plus : public TlsExtensionTestBase,
+                               public ::testing::WithParamInterface<
+                                   std::tuple<SSLProtocolVariant, uint16_t>> {
  public:
   TlsExtensionTest12Plus()
       : TlsExtensionTestBase(std::get<0>(GetParam()), std::get<1>(GetParam())) {
   }
 };
 
-class TlsExtensionTest12
-    : public TlsExtensionTestBase,
-      public ::testing::WithParamInterface<std::tuple<std::string, uint16_t>> {
+class TlsExtensionTest12 : public TlsExtensionTestBase,
+                           public ::testing::WithParamInterface<
+                               std::tuple<SSLProtocolVariant, uint16_t>> {
  public:
   TlsExtensionTest12()
       : TlsExtensionTestBase(std::get<0>(GetParam()), std::get<1>(GetParam())) {
   }
 };
 
-class TlsExtensionTest13 : public TlsExtensionTestBase,
-                           public ::testing::WithParamInterface<std::string> {
+class TlsExtensionTest13
+    : public TlsExtensionTestBase,
+      public ::testing::WithParamInterface<SSLProtocolVariant> {
  public:
   TlsExtensionTest13()
       : TlsExtensionTestBase(GetParam(), SSL_LIBRARY_VERSION_TLS_1_3) {}
 
   void ConnectWithBogusVersionList(const uint8_t* buf, size_t len) {
     DataBuffer versions_buf(buf, len);
     client_->SetPacketFilter(std::make_shared<TlsExtensionReplacer>(
         ssl_tls13_supported_versions_xtn, versions_buf));
@@ -261,31 +261,31 @@ class TlsExtensionTest13 : public TlsExt
         ssl_tls13_supported_versions_xtn, versions_buf));
     ConnectExpectFail();
   }
 };
 
 class TlsExtensionTest13Stream : public TlsExtensionTestBase {
  public:
   TlsExtensionTest13Stream()
-      : TlsExtensionTestBase(STREAM, SSL_LIBRARY_VERSION_TLS_1_3) {}
+      : TlsExtensionTestBase(ssl_variant_stream, SSL_LIBRARY_VERSION_TLS_1_3) {}
 };
 
-class TlsExtensionTestGeneric
-    : public TlsExtensionTestBase,
-      public ::testing::WithParamInterface<std::tuple<std::string, uint16_t>> {
+class TlsExtensionTestGeneric : public TlsExtensionTestBase,
+                                public ::testing::WithParamInterface<
+                                    std::tuple<SSLProtocolVariant, uint16_t>> {
  public:
   TlsExtensionTestGeneric()
       : TlsExtensionTestBase(std::get<0>(GetParam()), std::get<1>(GetParam())) {
   }
 };
 
-class TlsExtensionTestPre13
-    : public TlsExtensionTestBase,
-      public ::testing::WithParamInterface<std::tuple<std::string, uint16_t>> {
+class TlsExtensionTestPre13 : public TlsExtensionTestBase,
+                              public ::testing::WithParamInterface<
+                                  std::tuple<SSLProtocolVariant, uint16_t>> {
  public:
   TlsExtensionTestPre13()
       : TlsExtensionTestBase(std::get<0>(GetParam()), std::get<1>(GetParam())) {
   }
 };
 
 TEST_P(TlsExtensionTestGeneric, DamageSniLength) {
   ClientHelloErrorTest(
@@ -987,19 +987,19 @@ TEST_P(TlsExtensionTest13, EmptyVersionL
 
 TEST_P(TlsExtensionTest13, OddVersionList) {
   static const uint8_t ext[] = {0x00, 0x01, 0x00};
   ConnectWithBogusVersionList(ext, sizeof(ext));
 }
 
 // TODO: this only tests extensions in server messages.  The client can extend
 // Certificate messages, which is not checked here.
-class TlsBogusExtensionTest
-    : public TlsConnectTestBase,
-      public ::testing::WithParamInterface<std::tuple<std::string, uint16_t>> {
+class TlsBogusExtensionTest : public TlsConnectTestBase,
+                              public ::testing::WithParamInterface<
+                                  std::tuple<SSLProtocolVariant, uint16_t>> {
  public:
   TlsBogusExtensionTest()
       : TlsConnectTestBase(std::get<0>(GetParam()), std::get<1>(GetParam())) {}
 
  protected:
   virtual void ConnectAndFail(uint8_t message) = 0;
 
   void AddFilter(uint8_t message, uint16_t extension) {
@@ -1039,17 +1039,17 @@ class TlsBogusExtensionTest13 : public T
 
     client_->StartConnect();
     server_->StartConnect();
     client_->Handshake();  // ClientHello
     server_->Handshake();  // ServerHello
 
     client_->ExpectSendAlert(kTlsAlertUnsupportedExtension);
     client_->Handshake();
-    if (mode_ == STREAM) {
+    if (variant_ == ssl_variant_stream) {
       server_->ExpectSendAlert(kTlsAlertBadRecordMac);
     }
     server_->Handshake();
   }
 };
 
 TEST_P(TlsBogusExtensionTestPre13, AddBogusExtensionServerHello) {
   Run(kTlsHandshakeServerHello);
@@ -1134,45 +1134,48 @@ TEST_P(TlsConnectStream, IncludePadding)
 
   auto capture = std::make_shared<TlsExtensionCapture>(ssl_padding_xtn);
   client_->SetPacketFilter(capture);
   client_->StartConnect();
   client_->Handshake();
   EXPECT_TRUE(capture->captured());
 }
 
-INSTANTIATE_TEST_CASE_P(ExtensionStream, TlsExtensionTestGeneric,
-                        ::testing::Combine(TlsConnectTestBase::kTlsModesStream,
-                                           TlsConnectTestBase::kTlsVAll));
+INSTANTIATE_TEST_CASE_P(
+    ExtensionStream, TlsExtensionTestGeneric,
+    ::testing::Combine(TlsConnectTestBase::kTlsVariantsStream,
+                       TlsConnectTestBase::kTlsVAll));
 INSTANTIATE_TEST_CASE_P(
     ExtensionDatagram, TlsExtensionTestGeneric,
-    ::testing::Combine(TlsConnectTestBase::kTlsModesDatagram,
+    ::testing::Combine(TlsConnectTestBase::kTlsVariantsDatagram,
                        TlsConnectTestBase::kTlsV11Plus));
 INSTANTIATE_TEST_CASE_P(ExtensionDatagramOnly, TlsExtensionTestDtls,
                         TlsConnectTestBase::kTlsV11Plus);
 
 INSTANTIATE_TEST_CASE_P(ExtensionTls12Plus, TlsExtensionTest12Plus,
-                        ::testing::Combine(TlsConnectTestBase::kTlsModesAll,
+                        ::testing::Combine(TlsConnectTestBase::kTlsVariantsAll,
                                            TlsConnectTestBase::kTlsV12Plus));
 
-INSTANTIATE_TEST_CASE_P(ExtensionPre13Stream, TlsExtensionTestPre13,
-                        ::testing::Combine(TlsConnectTestBase::kTlsModesStream,
-                                           TlsConnectTestBase::kTlsV10ToV12));
+INSTANTIATE_TEST_CASE_P(
+    ExtensionPre13Stream, TlsExtensionTestPre13,
+    ::testing::Combine(TlsConnectTestBase::kTlsVariantsStream,
+                       TlsConnectTestBase::kTlsV10ToV12));
 INSTANTIATE_TEST_CASE_P(ExtensionPre13Datagram, TlsExtensionTestPre13,
-                        ::testing::Combine(TlsConnectTestBase::kTlsModesAll,
+                        ::testing::Combine(TlsConnectTestBase::kTlsVariantsAll,
                                            TlsConnectTestBase::kTlsV11V12));
 
 INSTANTIATE_TEST_CASE_P(ExtensionTls13, TlsExtensionTest13,
-                        TlsConnectTestBase::kTlsModesAll);
+                        TlsConnectTestBase::kTlsVariantsAll);
 
-INSTANTIATE_TEST_CASE_P(BogusExtensionStream, TlsBogusExtensionTestPre13,
-                        ::testing::Combine(TlsConnectTestBase::kTlsModesStream,
-                                           TlsConnectTestBase::kTlsV10ToV12));
+INSTANTIATE_TEST_CASE_P(
+    BogusExtensionStream, TlsBogusExtensionTestPre13,
+    ::testing::Combine(TlsConnectTestBase::kTlsVariantsStream,
+                       TlsConnectTestBase::kTlsV10ToV12));
 INSTANTIATE_TEST_CASE_P(
     BogusExtensionDatagram, TlsBogusExtensionTestPre13,
-    ::testing::Combine(TlsConnectTestBase::kTlsModesDatagram,
+    ::testing::Combine(TlsConnectTestBase::kTlsVariantsDatagram,
                        TlsConnectTestBase::kTlsV11V12));
 
 INSTANTIATE_TEST_CASE_P(BogusExtension13, TlsBogusExtensionTest13,
-                        ::testing::Combine(TlsConnectTestBase::kTlsModesAll,
+                        ::testing::Combine(TlsConnectTestBase::kTlsVariantsAll,
                                            TlsConnectTestBase::kTlsV13));
 
 }  // namespace nss_test
--- a/security/nss/gtests/ssl_gtest/ssl_gather_unittest.cc
+++ b/security/nss/gtests/ssl_gtest/ssl_gather_unittest.cc
@@ -6,17 +6,17 @@
 
 #include "gtest_utils.h"
 #include "tls_connect.h"
 
 namespace nss_test {
 
 class GatherV2ClientHelloTest : public TlsConnectTestBase {
  public:
-  GatherV2ClientHelloTest() : TlsConnectTestBase(STREAM, 0) {}
+  GatherV2ClientHelloTest() : TlsConnectTestBase(ssl_variant_stream, 0) {}
 
   void ConnectExpectMalformedClientHello(const DataBuffer &data) {
     EnsureTlsSetup();
     server_->ExpectSendAlert(kTlsAlertIllegalParameter);
     client_->SendDirect(data);
     server_->StartConnect();
     server_->Handshake();
     ASSERT_TRUE_WAIT(
--- a/security/nss/gtests/ssl_gtest/ssl_hrr_unittest.cc
+++ b/security/nss/gtests/ssl_gtest/ssl_hrr_unittest.cc
@@ -101,17 +101,17 @@ TEST_P(TlsConnectTls13, SecondClientHell
   client_->ConfigNamedGroups(groups);
   server_->ConfigNamedGroups(groups);
   client_->Set0RttEnabled(true);
   server_->Set0RttEnabled(true);
 
   // A new client that tries to resume with 0-RTT but doesn't send the
   // correct key share(s). The server will respond with an HRR.
   auto orig_client =
-      std::make_shared<TlsAgent>(client_->name(), TlsAgent::CLIENT, mode_);
+      std::make_shared<TlsAgent>(client_->name(), TlsAgent::CLIENT, variant_);
   client_.swap(orig_client);
   client_->SetVersionRange(SSL_LIBRARY_VERSION_TLS_1_1,
                            SSL_LIBRARY_VERSION_TLS_1_3);
   client_->ConfigureSessionCache(RESUME_BOTH);
   client_->Set0RttEnabled(true);
   client_->StartConnect();
 
   // Swap in the new client.
@@ -125,17 +125,17 @@ TEST_P(TlsConnectTls13, SecondClientHell
 
   // Swap the client we created manually with the one that successfully
   // received a PSK, and try to resume with 0-RTT. The client doesn't know
   // about the HRR so it will send the early_data xtn as well as 0-RTT data.
   client_.swap(orig_client);
   orig_client.reset();
 
   // Correct the DTLS message sequence number after an HRR.
-  if (mode_ == DGRAM) {
+  if (variant_ == ssl_variant_datagram) {
     client_->SetPacketFilter(
         std::make_shared<CorrectMessageSeqAfterHrrFilter>());
   }
 
   server_->SetPeer(client_);
   client_->Handshake();
 
   // Send 0-RTT data.
@@ -248,17 +248,17 @@ TEST_F(TlsConnectTest, Select12AfterHell
   client_->StartConnect();
   server_->StartConnect();
 
   client_->Handshake();
   server_->Handshake();
 
   // Here we replace the TLS server with one that does TLS 1.2 only.
   // This will happily send the client a TLS 1.2 ServerHello.
-  server_.reset(new TlsAgent(server_->name(), TlsAgent::SERVER, mode_));
+  server_.reset(new TlsAgent(server_->name(), TlsAgent::SERVER, variant_));
   client_->SetPeer(server_);
   server_->SetPeer(client_);
   server_->SetVersionRange(SSL_LIBRARY_VERSION_TLS_1_2,
                            SSL_LIBRARY_VERSION_TLS_1_2);
   server_->StartConnect();
   ExpectAlert(client_, kTlsAlertIllegalParameter);
   Handshake();
   EXPECT_EQ(SSL_ERROR_ILLEGAL_PARAMETER_ALERT, server_->error_code());
@@ -352,17 +352,17 @@ TEST_P(HelloRetryRequestAgentTest, Handl
   ProcessMessage(hrr, TlsAgent::STATE_CONNECTING);
   const size_t cookie_pos = 2 + 2;  // cookie_xtn, extension len
   DataBuffer cookie(canned_cookie_hrr + cookie_pos,
                     sizeof(canned_cookie_hrr) - cookie_pos);
   EXPECT_EQ(cookie, capture->extension());
 }
 
 INSTANTIATE_TEST_CASE_P(HelloRetryRequestAgentTests, HelloRetryRequestAgentTest,
-                        ::testing::Combine(TlsConnectTestBase::kTlsModesAll,
+                        ::testing::Combine(TlsConnectTestBase::kTlsVariantsAll,
                                            TlsConnectTestBase::kTlsV13));
 #ifndef NSS_DISABLE_TLS_1_3
 INSTANTIATE_TEST_CASE_P(HelloRetryRequestKeyExchangeTests, TlsKeyExchange13,
-                        ::testing::Combine(TlsConnectTestBase::kTlsModesAll,
+                        ::testing::Combine(TlsConnectTestBase::kTlsVariantsAll,
                                            TlsConnectTestBase::kTlsV13));
 #endif
 
 }  // namespace nss_test
--- a/security/nss/gtests/ssl_gtest/ssl_loopback_unittest.cc
+++ b/security/nss/gtests/ssl_gtest/ssl_loopback_unittest.cc
@@ -125,17 +125,17 @@ TEST_P(TlsConnectTls13, CaptureAlertClie
 
   server_->StartConnect();
   client_->StartConnect();
 
   client_->Handshake();
   client_->ExpectSendAlert(kTlsAlertDecodeError);
   server_->Handshake();
   client_->Handshake();
-  if (mode_ == STREAM) {
+  if (variant_ == ssl_variant_stream) {
     // DTLS just drops the alert it can't decrypt.
     server_->ExpectSendAlert(kTlsAlertBadRecordMac);
   }
   server_->Handshake();
   EXPECT_EQ(kTlsAlertFatal, alert_recorder->level());
   EXPECT_EQ(kTlsAlertDecodeError, alert_recorder->description());
 }
 
@@ -222,17 +222,18 @@ TEST_P(TlsConnectStream, ShortRead) {
   ASSERT_EQ(1200U, client_->received_bytes());
 }
 
 TEST_P(TlsConnectGeneric, ConnectWithCompressionMaybe) {
   EnsureTlsSetup();
   client_->EnableCompression();
   server_->EnableCompression();
   Connect();
-  EXPECT_EQ(client_->version() < SSL_LIBRARY_VERSION_TLS_1_3 && mode_ != DGRAM,
+  EXPECT_EQ(client_->version() < SSL_LIBRARY_VERSION_TLS_1_3 &&
+                variant_ != ssl_variant_datagram,
             client_->is_compressed());
   SendReceive();
 }
 
 TEST_P(TlsConnectDatagram, TestDtlsHolddownExpiry) {
   Connect();
   std::cerr << "Expiring holddown timer\n";
   SSLInt_ForceTimerExpiry(client_->ssl_fd());
@@ -315,51 +316,54 @@ TEST_F(TlsConnectStreamTls13, Tls13Faile
 TEST_F(TlsConnectStreamTls13, NegotiateShortHeaders) {
   client_->SetShortHeadersEnabled();
   server_->SetShortHeadersEnabled();
   client_->ExpectShortHeaders();
   server_->ExpectShortHeaders();
   Connect();
 }
 
-INSTANTIATE_TEST_CASE_P(GenericStream, TlsConnectGeneric,
-                        ::testing::Combine(TlsConnectTestBase::kTlsModesStream,
-                                           TlsConnectTestBase::kTlsVAll));
+INSTANTIATE_TEST_CASE_P(
+    GenericStream, TlsConnectGeneric,
+    ::testing::Combine(TlsConnectTestBase::kTlsVariantsStream,
+                       TlsConnectTestBase::kTlsVAll));
 INSTANTIATE_TEST_CASE_P(
     GenericDatagram, TlsConnectGeneric,
-    ::testing::Combine(TlsConnectTestBase::kTlsModesDatagram,
+    ::testing::Combine(TlsConnectTestBase::kTlsVariantsDatagram,
                        TlsConnectTestBase::kTlsV11Plus));
 
 INSTANTIATE_TEST_CASE_P(StreamOnly, TlsConnectStream,
                         TlsConnectTestBase::kTlsVAll);
 INSTANTIATE_TEST_CASE_P(DatagramOnly, TlsConnectDatagram,
                         TlsConnectTestBase::kTlsV11Plus);
 
-INSTANTIATE_TEST_CASE_P(Pre12Stream, TlsConnectPre12,
-                        ::testing::Combine(TlsConnectTestBase::kTlsModesStream,
-                                           TlsConnectTestBase::kTlsV10V11));
+INSTANTIATE_TEST_CASE_P(
+    Pre12Stream, TlsConnectPre12,
+    ::testing::Combine(TlsConnectTestBase::kTlsVariantsStream,
+                       TlsConnectTestBase::kTlsV10V11));
 INSTANTIATE_TEST_CASE_P(
     Pre12Datagram, TlsConnectPre12,
-    ::testing::Combine(TlsConnectTestBase::kTlsModesDatagram,
+    ::testing::Combine(TlsConnectTestBase::kTlsVariantsDatagram,
                        TlsConnectTestBase::kTlsV11));
 
 INSTANTIATE_TEST_CASE_P(Version12Only, TlsConnectTls12,
-                        TlsConnectTestBase::kTlsModesAll);
+                        TlsConnectTestBase::kTlsVariantsAll);
 #ifndef NSS_DISABLE_TLS_1_3
 INSTANTIATE_TEST_CASE_P(Version13Only, TlsConnectTls13,
-                        TlsConnectTestBase::kTlsModesAll);
+                        TlsConnectTestBase::kTlsVariantsAll);
 #endif
 
-INSTANTIATE_TEST_CASE_P(Pre13Stream, TlsConnectGenericPre13,
-                        ::testing::Combine(TlsConnectTestBase::kTlsModesStream,
-                                           TlsConnectTestBase::kTlsV10ToV12));
+INSTANTIATE_TEST_CASE_P(
+    Pre13Stream, TlsConnectGenericPre13,
+    ::testing::Combine(TlsConnectTestBase::kTlsVariantsStream,
+                       TlsConnectTestBase::kTlsV10ToV12));
 INSTANTIATE_TEST_CASE_P(
     Pre13Datagram, TlsConnectGenericPre13,
-    ::testing::Combine(TlsConnectTestBase::kTlsModesDatagram,
+    ::testing::Combine(TlsConnectTestBase::kTlsVariantsDatagram,
                        TlsConnectTestBase::kTlsV11V12));
 INSTANTIATE_TEST_CASE_P(Pre13StreamOnly, TlsConnectStreamPre13,
                         TlsConnectTestBase::kTlsV10ToV12);
 
 INSTANTIATE_TEST_CASE_P(Version12Plus, TlsConnectTls12Plus,
-                        ::testing::Combine(TlsConnectTestBase::kTlsModesAll,
+                        ::testing::Combine(TlsConnectTestBase::kTlsVariantsAll,
                                            TlsConnectTestBase::kTlsV12Plus));
 
 }  // namespace nspr_test
--- a/security/nss/gtests/ssl_gtest/ssl_resumption_unittest.cc
+++ b/security/nss/gtests/ssl_gtest/ssl_resumption_unittest.cc
@@ -518,17 +518,17 @@ class SelectedVersionReplacer : public T
  private:
   uint16_t version_;
 };
 
 // Test how the client handles the case where the server picks a
 // lower version number on resumption.
 TEST_P(TlsConnectGenericPre13, TestResumptionOverrideVersion) {
   uint16_t override_version = 0;
-  if (mode_ == STREAM) {
+  if (variant_ == ssl_variant_stream) {
     switch (version_) {
       case SSL_LIBRARY_VERSION_TLS_1_0:
         return;  // Skip the test.
       case SSL_LIBRARY_VERSION_TLS_1_1:
         override_version = SSL_LIBRARY_VERSION_TLS_1_0;
         break;
       case SSL_LIBRARY_VERSION_TLS_1_2:
         override_version = SSL_LIBRARY_VERSION_TLS_1_1;
--- a/security/nss/gtests/ssl_gtest/ssl_skip_unittest.cc
+++ b/security/nss/gtests/ssl_gtest/ssl_skip_unittest.cc
@@ -73,49 +73,49 @@ class TlsHandshakeSkipFilter : public Tl
   // The type of handshake message to drop.
   uint8_t handshake_type_;
   // Whether this filter has ever skipped a handshake message.  Track this so
   // that sequence numbers on DTLS handshake messages can be rewritten in
   // subsequent calls.
   bool skipped_;
 };
 
-class TlsSkipTest
-    : public TlsConnectTestBase,
-      public ::testing::WithParamInterface<std::tuple<std::string, uint16_t>> {
+class TlsSkipTest : public TlsConnectTestBase,
+                    public ::testing::WithParamInterface<
+                        std::tuple<SSLProtocolVariant, uint16_t>> {
  protected:
   TlsSkipTest()
       : TlsConnectTestBase(std::get<0>(GetParam()), std::get<1>(GetParam())) {}
 
   void ServerSkipTest(std::shared_ptr<PacketFilter> filter,
                       uint8_t alert = kTlsAlertUnexpectedMessage) {
     server_->SetPacketFilter(filter);
     ConnectExpectAlert(client_, alert);
   }
 };
 
 class Tls13SkipTest : public TlsConnectTestBase,
-                      public ::testing::WithParamInterface<std::string> {
+                      public ::testing::WithParamInterface<SSLProtocolVariant> {
  protected:
   Tls13SkipTest()
       : TlsConnectTestBase(GetParam(), SSL_LIBRARY_VERSION_TLS_1_3) {}
 
   void ServerSkipTest(std::shared_ptr<TlsRecordFilter> filter, int32_t error) {
     EnsureTlsSetup();
     server_->SetTlsRecordFilter(filter);
     filter->EnableDecryption();
     client_->ExpectSendAlert(kTlsAlertUnexpectedMessage);
-    if (mode_ == STREAM) {
+    if (variant_ == ssl_variant_stream) {
       server_->ExpectSendAlert(kTlsAlertBadRecordMac);
       ConnectExpectFail();
     } else {
       ConnectExpectFailOneSide(TlsAgent::CLIENT);
     }
     client_->CheckErrorCode(error);
-    if (mode_ == STREAM) {
+    if (variant_ == ssl_variant_stream) {
       server_->CheckErrorCode(SSL_ERROR_BAD_MAC_READ);
     } else {
       ASSERT_EQ(TlsAgent::STATE_CONNECTING, server_->state());
     }
   }
 
   void ClientSkipTest(std::shared_ptr<TlsRecordFilter> filter, int32_t error) {
     EnsureTlsSetup();
@@ -222,17 +222,18 @@ TEST_P(Tls13SkipTest, SkipClientCertific
   client_->SetupClientAuth();
   server_->RequestClientAuth(true);
   client_->ExpectReceiveAlert(kTlsAlertUnexpectedMessage);
   ClientSkipTest(
       std::make_shared<TlsHandshakeSkipFilter>(kTlsHandshakeCertificateVerify),
       SSL_ERROR_RX_UNEXPECTED_FINISHED);
 }
 
-INSTANTIATE_TEST_CASE_P(SkipTls10, TlsSkipTest,
-                        ::testing::Combine(TlsConnectTestBase::kTlsModesStream,
-                                           TlsConnectTestBase::kTlsV10));
+INSTANTIATE_TEST_CASE_P(
+    SkipTls10, TlsSkipTest,
+    ::testing::Combine(TlsConnectTestBase::kTlsVariantsStream,
+                       TlsConnectTestBase::kTlsV10));
 INSTANTIATE_TEST_CASE_P(SkipVariants, TlsSkipTest,
-                        ::testing::Combine(TlsConnectTestBase::kTlsModesAll,
+                        ::testing::Combine(TlsConnectTestBase::kTlsVariantsAll,
                                            TlsConnectTestBase::kTlsV11V12));
 INSTANTIATE_TEST_CASE_P(Skip13Variants, Tls13SkipTest,
-                        TlsConnectTestBase::kTlsModesAll);
+                        TlsConnectTestBase::kTlsVariantsAll);
 }  // namespace nss_test
--- a/security/nss/gtests/ssl_gtest/ssl_v2_client_hello_unittest.cc
+++ b/security/nss/gtests/ssl_gtest/ssl_v2_client_hello_unittest.cc
@@ -136,20 +136,21 @@ class SSLv2ClientHelloFilter : public Pa
   uint8_t reported_pad_len_;
   uint16_t client_random_len_;
   std::vector<uint16_t> ciphers_;
   bool send_escape_;
 };
 
 class SSLv2ClientHelloTestF : public TlsConnectTestBase {
  public:
-  SSLv2ClientHelloTestF() : TlsConnectTestBase(STREAM, 0), filter_(nullptr) {}
+  SSLv2ClientHelloTestF()
+      : TlsConnectTestBase(ssl_variant_stream, 0), filter_(nullptr) {}
 
-  SSLv2ClientHelloTestF(Mode mode, uint16_t version)
-      : TlsConnectTestBase(mode, version), filter_(nullptr) {}
+  SSLv2ClientHelloTestF(SSLProtocolVariant variant, uint16_t version)
+      : TlsConnectTestBase(variant, version), filter_(nullptr) {}
 
   void SetUp() {
     TlsConnectTestBase::SetUp();
     filter_ = std::make_shared<SSLv2ClientHelloFilter>(client_, version_);
     client_->SetPacketFilter(filter_);
   }
 
   void RequireSafeRenegotiation() {
@@ -188,17 +189,18 @@ class SSLv2ClientHelloTestF : public Tls
   std::shared_ptr<SSLv2ClientHelloFilter> filter_;
 };
 
 // Parameterized version of SSLv2ClientHelloTestF we can
 // use with TEST_P to test multiple TLS versions easily.
 class SSLv2ClientHelloTest : public SSLv2ClientHelloTestF,
                              public ::testing::WithParamInterface<uint16_t> {
  public:
-  SSLv2ClientHelloTest() : SSLv2ClientHelloTestF(STREAM, GetParam()) {}
+  SSLv2ClientHelloTest()
+      : SSLv2ClientHelloTestF(ssl_variant_stream, GetParam()) {}
 };
 
 // Test negotiating TLS 1.0 - 1.2.
 TEST_P(SSLv2ClientHelloTest, Connect) {
   SetAvailableCipherSuite(TLS_DHE_RSA_WITH_AES_128_CBC_SHA);
   Connect();
 }
 
--- a/security/nss/gtests/ssl_gtest/ssl_version_unittest.cc
+++ b/security/nss/gtests/ssl_gtest/ssl_version_unittest.cc
@@ -255,17 +255,17 @@ TEST_P(TlsConnectGeneric, AlertBeforeSer
   EnsureTlsSetup();
   client_->ExpectReceiveAlert(kTlsAlertUnrecognizedName, kTlsAlertWarning);
   client_->StartConnect();
   server_->StartConnect();
   client_->Handshake();  // Send ClientHello.
   static const uint8_t kWarningAlert[] = {kTlsAlertWarning,
                                           kTlsAlertUnrecognizedName};
   DataBuffer alert;
-  TlsAgentTestBase::MakeRecord(mode_, kTlsAlertType,
+  TlsAgentTestBase::MakeRecord(variant_, kTlsAlertType,
                                SSL_LIBRARY_VERSION_TLS_1_0, kWarningAlert,
                                PR_ARRAY_SIZE(kWarningAlert), &alert);
   client_->adapter()->PacketReceived(alert);
   Handshake();
   CheckConnected();
 }
 
 class Tls13NoSupportedVersions : public TlsConnectStreamTls12 {
--- a/security/nss/gtests/ssl_gtest/ssl_versionpolicy_unittest.cc
+++ b/security/nss/gtests/ssl_gtest/ssl_versionpolicy_unittest.cc
@@ -87,22 +87,18 @@ typedef std::tuple<SSLProtocolVariant,  
                    uint16_t>            // input max
     PolicyVersionRangeInput;
 
 class TestPolicyVersionRange
     : public TlsConnectTestBase,
       public ::testing::WithParamInterface<PolicyVersionRangeInput> {
  public:
   TestPolicyVersionRange()
-      : TlsConnectTestBase(((static_cast<SSLProtocolVariant>(
-                                 std::get<0>(GetParam())) == ssl_variant_stream)
-                                ? STREAM
-                                : DGRAM),
-                           0),
-        variant_(static_cast<SSLProtocolVariant>(std::get<0>(GetParam()))),
+      : TlsConnectTestBase(std::get<0>(GetParam()), 0),
+        variant_(std::get<0>(GetParam())),
         policy_("policy", std::get<1>(GetParam()), std::get<2>(GetParam())),
         input_("input", std::get<3>(GetParam()), std::get<4>(GetParam())),
         library_("supported-by-library",
                  ((variant_ == ssl_variant_stream)
                       ? SSL_LIBRARY_VERSION_MIN_SUPPORTED_STREAM
                       : SSL_LIBRARY_VERSION_MIN_SUPPORTED_DATAGRAM),
                  SSL_LIBRARY_VERSION_MAX_SUPPORTED) {
     TlsConnectTestBase::SkipVersionChecks();
@@ -119,19 +115,17 @@ class TestPolicyVersionRange
     rv = NSS_OptionSet(NSS_DTLS_VERSION_MIN_POLICY, policy.min);
     ASSERT_EQ(SECSuccess, rv);
     rv = NSS_OptionSet(NSS_DTLS_VERSION_MAX_POLICY, policy.max);
     ASSERT_EQ(SECSuccess, rv);
   }
 
   void CreateDummySocket(std::shared_ptr<DummyPrSocket>* dummy_socket,
                          ScopedPRFileDesc* ssl_fd) {
-    (*dummy_socket)
-        .reset(new DummyPrSocket(
-            "dummy", (variant_ == ssl_variant_stream) ? STREAM : DGRAM));
+    (*dummy_socket).reset(new DummyPrSocket("dummy", variant_));
     *ssl_fd = (*dummy_socket)->CreateFD();
     if (variant_ == ssl_variant_stream) {
       SSL_ImportFD(nullptr, ssl_fd->get());
     } else {
       DTLS_ImportFD(nullptr, ssl_fd->get());
     }
   }
 
@@ -270,21 +264,16 @@ static const uint16_t kExpandedVersionsA
     SSL_LIBRARY_VERSION_TLS_1_3,
 #endif
     SSL_LIBRARY_VERSION_MAX_SUPPORTED + 1
     /* clang-format on */
 };
 static ::testing::internal::ParamGenerator<uint16_t> kExpandedVersions =
     ::testing::ValuesIn(kExpandedVersionsArr);
 
-static const SSLProtocolVariant kVariantsArr[] = {ssl_variant_stream,
-                                                  ssl_variant_datagram};
-static ::testing::internal::ParamGenerator<SSLProtocolVariant> kVariants =
-    ::testing::ValuesIn(kVariantsArr);
-
 TEST_P(TestPolicyVersionRange, TestAllTLSVersionsAndPolicyCombinations) {
   ASSERT_TRUE(variant_ == ssl_variant_stream ||
               variant_ == ssl_variant_datagram)
       << "testing unsupported ssl variant";
 
   std::cerr << "testing: " << variant_ << policy_ << input_ << library_
             << std::endl;
 
@@ -393,12 +382,13 @@ TEST_P(TestPolicyVersionRange, TestAllTL
 
   // Because we found overlap between policy and supported versions,
   // and because we have used SetDefault to enable at least one version,
   // it should be possible to execute an SSL/TLS connection.
   Connect();
 }
 
 INSTANTIATE_TEST_CASE_P(TLSVersionRanges, TestPolicyVersionRange,
-                        ::testing::Combine(kVariants, kExpandedVersions,
+                        ::testing::Combine(TlsConnectTestBase::kTlsVariantsAll,
                                            kExpandedVersions, kExpandedVersions,
+                                           kExpandedVersions,
                                            kExpandedVersions));
 }  // namespace nss_test
--- a/security/nss/gtests/ssl_gtest/test_io.cc
+++ b/security/nss/gtests/ssl_gtest/test_io.cc
@@ -35,19 +35,18 @@ ScopedPRFileDesc DummyPrSocket::CreateFD
   return DummyIOLayerMethods::CreateFD(test_fd_identity, this);
 }
 
 void DummyPrSocket::PacketReceived(const DataBuffer &packet) {
   input_.push(Packet(packet));
 }
 
 int32_t DummyPrSocket::Read(PRFileDesc *f, void *data, int32_t len) {
-  PR_ASSERT(mode_ == STREAM);
-
-  if (mode_ != STREAM) {
+  PR_ASSERT(variant_ == ssl_variant_stream);
+  if (variant_ != ssl_variant_stream) {
     PR_SetError(PR_INVALID_METHOD_ERROR, 0);
     return -1;
   }
 
   if (input_.empty()) {
     LOGV("Read --> wouldblock " << len);
     PR_SetError(PR_WOULD_BLOCK_ERROR, 0);
     return -1;
@@ -70,17 +69,17 @@ int32_t DummyPrSocket::Read(PRFileDesc *
 int32_t DummyPrSocket::Recv(PRFileDesc *f, void *buf, int32_t buflen,
                             int32_t flags, PRIntervalTime to) {
   PR_ASSERT(flags == 0);
   if (flags != 0) {
     PR_SetError(PR_NOT_IMPLEMENTED_ERROR, 0);
     return -1;
   }
 
-  if (mode() != DGRAM) {
+  if (variant() != ssl_variant_datagram) {
     return Read(f, buf, buflen);
   }
 
   if (input_.empty()) {
     PR_SetError(PR_WOULD_BLOCK_ERROR, 0);
     return -1;
   }
 
--- a/security/nss/gtests/ssl_gtest/test_io.h
+++ b/security/nss/gtests/ssl_gtest/test_io.h
@@ -13,16 +13,17 @@
 #include <ostream>
 #include <queue>
 #include <string>
 
 #include "databuffer.h"
 #include "dummy_io.h"
 #include "prio.h"
 #include "scoped_ptrs.h"
+#include "sslt.h"
 
 namespace nss_test {
 
 class DataBuffer;
 class DummyPrSocket;  // Fwd decl.
 
 // Allow us to inspect a packet before it is written.
 class PacketFilter {
@@ -39,27 +40,21 @@ class PacketFilter {
   //
   // A filter that modifies the data places the modified data in *output and
   // returns CHANGE.  A filter that does not modify data returns LEAVE, in which
   // case the value in *output is ignored.  A Filter can return DROP, in which
   // case the packet is dropped (and *output is ignored).
   virtual Action Filter(const DataBuffer& input, DataBuffer* output) = 0;
 };
 
-enum Mode { STREAM, DGRAM };
-
-inline std::ostream& operator<<(std::ostream& os, Mode m) {
-  return os << ((m == STREAM) ? "TLS" : "DTLS");
-}
-
 class DummyPrSocket : public DummyIOLayerMethods {
  public:
-  DummyPrSocket(const std::string& name, Mode mode)
+  DummyPrSocket(const std::string& name, SSLProtocolVariant variant)
       : name_(name),
-        mode_(mode),
+        variant_(variant),
         peer_(),
         input_(),
         filter_(nullptr),
         writeable_(true) {}
   virtual ~DummyPrSocket() {}
 
   // Create a file descriptor that will reference this object.  The fd must not
   // live longer than this adapter; call PR_Close() before.
@@ -73,17 +68,17 @@ class DummyPrSocket : public DummyIOLaye
 
   void PacketReceived(const DataBuffer& data);
   int32_t Read(PRFileDesc* f, void* data, int32_t len) override;
   int32_t Recv(PRFileDesc* f, void* buf, int32_t buflen, int32_t flags,
                PRIntervalTime to) override;
   int32_t Write(PRFileDesc* f, const void* buf, int32_t length) override;
   void CloseWrites() { writeable_ = false; }
 
-  Mode mode() const { return mode_; }
+  SSLProtocolVariant variant() const { return variant_; }
   bool readable() const { return !input_.empty(); }
 
  private:
   class Packet : public DataBuffer {
    public:
     Packet(const DataBuffer& buf) : DataBuffer(buf), offset_(0) {}
 
     void Advance(size_t delta) {
@@ -94,17 +89,17 @@ class DummyPrSocket : public DummyIOLaye
     size_t offset() const { return offset_; }
     size_t remaining() const { return len() - offset_; }
 
    private:
     size_t offset_;
   };
 
   const std::string name_;
-  Mode mode_;
+  SSLProtocolVariant variant_;
   std::weak_ptr<DummyPrSocket> peer_;
   std::queue<Packet> input_;
   std::shared_ptr<PacketFilter> filter_;
   bool writeable_;
 };
 
 // Marker interface.
 class PollTarget {};
--- a/security/nss/gtests/ssl_gtest/tls_agent.cc
+++ b/security/nss/gtests/ssl_gtest/tls_agent.cc
@@ -38,22 +38,23 @@ const std::string TlsAgent::kServerRsaDe
 const std::string TlsAgent::kServerRsaChain = "rsa_chain";
 const std::string TlsAgent::kServerEcdsa256 = "ecdsa256";
 const std::string TlsAgent::kServerEcdsa384 = "ecdsa384";
 const std::string TlsAgent::kServerEcdsa521 = "ecdsa521";
 const std::string TlsAgent::kServerEcdhRsa = "ecdh_rsa";
 const std::string TlsAgent::kServerEcdhEcdsa = "ecdh_ecdsa";
 const std::string TlsAgent::kServerDsa = "dsa";
 
-TlsAgent::TlsAgent(const std::string& name, Role role, Mode mode)
+TlsAgent::TlsAgent(const std::string& name, Role role,
+                   SSLProtocolVariant variant)
     : name_(name),
-      mode_(mode),
+      variant_(variant),
       role_(role),
       server_key_bits_(0),
-      adapter_(new DummyPrSocket(role_str(), mode)),
+      adapter_(new DummyPrSocket(role_str(), variant)),
       ssl_fd_(nullptr),
       state_(STATE_INIT),
       timer_handle_(nullptr),
       falsestart_enabled_(false),
       expected_version_(0),
       expected_cipher_suite_(0),
       expect_resumption_(false),
       expect_client_auth_(false),
@@ -71,18 +72,17 @@ TlsAgent::TlsAgent(const std::string& na
       expect_readwrite_error_(false),
       handshake_callback_(),
       auth_certificate_callback_(),
       sni_callback_(),
       expect_short_headers_(false),
       skip_version_checks_(false) {
   memset(&info_, 0, sizeof(info_));
   memset(&csinfo_, 0, sizeof(csinfo_));
-  SECStatus rv = SSL_VersionRangeGetDefault(
-      mode_ == STREAM ? ssl_variant_stream : ssl_variant_datagram, &vrange_);
+  SECStatus rv = SSL_VersionRangeGetDefault(variant_, &vrange_);
   EXPECT_EQ(SECSuccess, rv);
 }
 
 TlsAgent::~TlsAgent() {
   if (timer_handle_) {
     timer_handle_->Cancel();
   }
 
@@ -149,17 +149,17 @@ bool TlsAgent::EnsureTlsSetup(PRFileDesc
   // Don't set up twice
   if (ssl_fd_) return true;
 
   ScopedPRFileDesc dummy_fd(adapter_->CreateFD());
   EXPECT_NE(nullptr, dummy_fd);
   if (!dummy_fd) {
     return false;
   }
-  if (adapter_->mode() == STREAM) {
+  if (adapter_->variant() == ssl_variant_stream) {
     ssl_fd_.reset(SSL_ImportFD(modelSocket, dummy_fd.get()));
   } else {
     ssl_fd_.reset(DTLS_ImportFD(modelSocket, dummy_fd.get()));
   }
 
   EXPECT_NE(nullptr, ssl_fd_);
   if (!ssl_fd_) {
     return false;
@@ -752,17 +752,18 @@ void TlsAgent::Connected() {
   rv = SSL_GetCipherSuiteInfo(info_.cipherSuite, &csinfo_, sizeof(csinfo_));
   EXPECT_EQ(SECSuccess, rv);
   EXPECT_EQ(sizeof(csinfo_), csinfo_.length);
 
   if (expected_version_ >= SSL_LIBRARY_VERSION_TLS_1_3) {
     PRInt32 cipherSuites = SSLInt_CountTls13CipherSpecs(ssl_fd());
     // We use one ciphersuite in each direction, plus one that's kept around
     // by DTLS for retransmission.
-    PRInt32 expected = ((mode_ == DGRAM) && (role_ == CLIENT)) ? 3 : 2;
+    PRInt32 expected =
+        ((variant_ == ssl_variant_datagram) && (role_ == CLIENT)) ? 3 : 2;
     EXPECT_EQ(expected, cipherSuites);
     if (expected != cipherSuites) {
       SSLInt_PrintTls13CipherSpecs(ssl_fd());
     }
   }
 
   PRBool short_headers;
   rv = SSLInt_UsingShortHeaders(ssl_fd(), &short_headers);
@@ -830,17 +831,17 @@ void TlsAgent::Handshake() {
     Poller::Instance()->Wait(READABLE_EVENT, adapter_, this,
                              &TlsAgent::ReadableCallback);
     return;
   }
 
   int32_t err = PR_GetError();
   if (err == PR_WOULD_BLOCK_ERROR) {
     LOGV("Would have blocked");
-    if (mode_ == DGRAM) {
+    if (variant_ == ssl_variant_datagram) {
       if (timer_handle_) {
         timer_handle_->Cancel();
         timer_handle_ = nullptr;
       }
 
       PRIntervalTime timeout;
       rv = DTLS_GetHandshakeTimeout(ssl_fd(), &timeout);
       if (rv == SECSuccess) {
@@ -981,17 +982,17 @@ void TlsAgentTestBase::TearDown() {
   agent_ = nullptr;
   SSL_ClearSessionCache();
   SSL_ShutdownServerSessionIDCache();
 }
 
 void TlsAgentTestBase::Reset(const std::string& server_name) {
   agent_.reset(
       new TlsAgent(role_ == TlsAgent::CLIENT ? TlsAgent::kClient : server_name,
-                   role_, mode_));
+                   role_, variant_));
   if (version_) {
     agent_->SetVersionRange(version_, version_);
   }
   agent_->adapter()->SetPeer(sink_adapter_);
   agent_->StartConnect();
 }
 
 void TlsAgentTestBase::EnsureInit() {
@@ -1019,35 +1020,37 @@ void TlsAgentTestBase::ProcessMessage(co
 
   ASSERT_EQ(expected_state, agent_->state());
 
   if (expected_state == TlsAgent::STATE_ERROR) {
     ASSERT_EQ(error_code, agent_->error_code());
   }
 }
 
-void TlsAgentTestBase::MakeRecord(Mode mode, uint8_t type, uint16_t version,
-                                  const uint8_t* buf, size_t len,
-                                  DataBuffer* out, uint64_t seq_num) {
+void TlsAgentTestBase::MakeRecord(SSLProtocolVariant variant, uint8_t type,
+                                  uint16_t version, const uint8_t* buf,
+                                  size_t len, DataBuffer* out,
+                                  uint64_t seq_num) {
   size_t index = 0;
   index = out->Write(index, type, 1);
-  index = out->Write(
-      index, mode == STREAM ? version : TlsVersionToDtlsVersion(version), 2);
-  if (mode == DGRAM) {
+  if (variant == ssl_variant_stream) {
+    index = out->Write(index, version, 2);
+  } else {
+    index = out->Write(index, TlsVersionToDtlsVersion(version), 2);
     index = out->Write(index, seq_num >> 32, 4);
     index = out->Write(index, seq_num & PR_UINT32_MAX, 4);
   }
   index = out->Write(index, len, 2);
   out->Write(index, buf, len);
 }
 
 void TlsAgentTestBase::MakeRecord(uint8_t type, uint16_t version,
                                   const uint8_t* buf, size_t len,
                                   DataBuffer* out, uint64_t seq_num) const {
-  MakeRecord(mode_, type, version, buf, len, out, seq_num);
+  MakeRecord(variant_, type, version, buf, len, out, seq_num);
 }
 
 void TlsAgentTestBase::MakeHandshakeMessage(uint8_t hs_type,
                                             const uint8_t* data, size_t hs_len,
                                             DataBuffer* out,
                                             uint64_t seq_num) const {
   return MakeHandshakeMessageFragment(hs_type, data, hs_len, out, seq_num, 0,
                                       0);
@@ -1056,17 +1059,17 @@ void TlsAgentTestBase::MakeHandshakeMess
 void TlsAgentTestBase::MakeHandshakeMessageFragment(
     uint8_t hs_type, const uint8_t* data, size_t hs_len, DataBuffer* out,
     uint64_t seq_num, uint32_t fragment_offset,
     uint32_t fragment_length) const {
   size_t index = 0;
   if (!fragment_length) fragment_length = hs_len;
   index = out->Write(index, hs_type, 1);  // Handshake record type.
   index = out->Write(index, hs_len, 3);   // Handshake length
-  if (mode_ == DGRAM) {
+  if (variant_ == ssl_variant_datagram) {
     index = out->Write(index, seq_num, 2);
     index = out->Write(index, fragment_offset, 3);
     index = out->Write(index, fragment_length, 3);
   }
   if (data) {
     index = out->Write(index, data, fragment_length);
   } else {
     for (size_t i = 0; i < fragment_length; ++i) {
--- a/security/nss/gtests/ssl_gtest/tls_agent.h
+++ b/security/nss/gtests/ssl_gtest/tls_agent.h
@@ -69,17 +69,17 @@ class TlsAgent : public PollTarget {
   static const std::string kServerRsaChain;  // A cert that requires a chain.
   static const std::string kServerEcdsa256;
   static const std::string kServerEcdsa384;
   static const std::string kServerEcdsa521;
   static const std::string kServerEcdhEcdsa;
   static const std::string kServerEcdhRsa;
   static const std::string kServerDsa;
 
-  TlsAgent(const std::string& name, Role role, Mode mode);
+  TlsAgent(const std::string& name, Role role, SSLProtocolVariant variant);
   virtual ~TlsAgent();
 
   void SetPeer(std::shared_ptr<TlsAgent>& peer) {
     adapter_->SetPeer(peer->adapter_);
   }
 
   void SetTlsRecordFilter(std::shared_ptr<TlsRecordFilter> filter) {
     filter->SetAgent(this);
@@ -353,17 +353,17 @@ class TlsAgent : public PollTarget {
 
   void DisableLameGroups();
   void ConfigStrongECGroups(bool en);
   void ConfigAllDHGroups(bool en);
   void CheckCallbacks() const;
   void Connected();
 
   const std::string name_;
-  Mode mode_;
+  SSLProtocolVariant variant_;
   Role role_;
   uint16_t server_key_bits_;
   std::shared_ptr<DummyPrSocket> adapter_;
   ScopedPRFileDesc ssl_fd_;
   State state_;
   std::shared_ptr<Poller::Timer> timer_handle_;
   bool falsestart_enabled_;
   uint16_t expected_version_;
@@ -396,100 +396,100 @@ inline std::ostream& operator<<(std::ost
                                 const TlsAgent::State& state) {
   return stream << TlsAgent::state_str(state);
 }
 
 class TlsAgentTestBase : public ::testing::Test {
  public:
   static ::testing::internal::ParamGenerator<std::string> kTlsRolesAll;
 
-  TlsAgentTestBase(TlsAgent::Role role, Mode mode, uint16_t version = 0)
+  TlsAgentTestBase(TlsAgent::Role role, SSLProtocolVariant variant,
+                   uint16_t version = 0)
       : agent_(nullptr),
         role_(role),
-        mode_(mode),
+        variant_(variant),
         version_(version),
-        sink_adapter_(new DummyPrSocket("sink", mode)) {}
+        sink_adapter_(new DummyPrSocket("sink", variant)) {}
   virtual ~TlsAgentTestBase() {}
 
   void SetUp();
   void TearDown();
 
   void ExpectAlert(uint8_t alert);
 
-  static void MakeRecord(Mode mode, uint8_t type, uint16_t version,
-                         const uint8_t* buf, size_t len, DataBuffer* out,
-                         uint64_t seq_num = 0);
+  static void MakeRecord(SSLProtocolVariant variant, uint8_t type,
+                         uint16_t version, const uint8_t* buf, size_t len,
+                         DataBuffer* out, uint64_t seq_num = 0);
   void MakeRecord(uint8_t type, uint16_t version, const uint8_t* buf,
                   size_t len, DataBuffer* out, uint64_t seq_num = 0) const;
   void MakeHandshakeMessage(uint8_t hs_type, const uint8_t* data, size_t hs_len,
                             DataBuffer* out, uint64_t seq_num = 0) const;
   void MakeHandshakeMessageFragment(uint8_t hs_type, const uint8_t* data,
                                     size_t hs_len, DataBuffer* out,
                                     uint64_t seq_num, uint32_t fragment_offset,
                                     uint32_t fragment_length) const;
   static void MakeTrivialHandshakeRecord(uint8_t hs_type, size_t hs_len,
                                          DataBuffer* out);
   static inline TlsAgent::Role ToRole(const std::string& str) {
     return str == "CLIENT" ? TlsAgent::CLIENT : TlsAgent::SERVER;
   }
 
-  static inline Mode ToMode(const std::string& str) {
-    return str == "TLS" ? STREAM : DGRAM;
-  }
-
   void Init(const std::string& server_name = TlsAgent::kServerRsa);
   void Reset(const std::string& server_name = TlsAgent::kServerRsa);
 
  protected:
   void EnsureInit();
   void ProcessMessage(const DataBuffer& buffer, TlsAgent::State expected_state,
                       int32_t error_code = 0);
 
   std::unique_ptr<TlsAgent> agent_;
   TlsAgent::Role role_;
-  Mode mode_;
+  SSLProtocolVariant variant_;
   uint16_t version_;
   // This adapter is here just to accept packets from this agent.
   std::shared_ptr<DummyPrSocket> sink_adapter_;
 };
 
-class TlsAgentTest : public TlsAgentTestBase,
-                     public ::testing::WithParamInterface<
-                         std::tuple<std::string, std::string, uint16_t>> {
+class TlsAgentTest
+    : public TlsAgentTestBase,
+      public ::testing::WithParamInterface<
+          std::tuple<std::string, SSLProtocolVariant, uint16_t>> {
  public:
   TlsAgentTest()
       : TlsAgentTestBase(ToRole(std::get<0>(GetParam())),
-                         ToMode(std::get<1>(GetParam())),
-                         std::get<2>(GetParam())) {}
+                         std::get<1>(GetParam()), std::get<2>(GetParam())) {}
 };
 
-class TlsAgentTestClient
-    : public TlsAgentTestBase,
-      public ::testing::WithParamInterface<std::tuple<std::string, uint16_t>> {
+class TlsAgentTestClient : public TlsAgentTestBase,
+                           public ::testing::WithParamInterface<
+                               std::tuple<SSLProtocolVariant, uint16_t>> {
  public:
   TlsAgentTestClient()
-      : TlsAgentTestBase(TlsAgent::CLIENT, ToMode(std::get<0>(GetParam())),
+      : TlsAgentTestBase(TlsAgent::CLIENT, std::get<0>(GetParam()),
                          std::get<1>(GetParam())) {}
 };
 
 class TlsAgentTestClient13 : public TlsAgentTestClient {};
 
 class TlsAgentStreamTestClient : public TlsAgentTestBase {
  public:
-  TlsAgentStreamTestClient() : TlsAgentTestBase(TlsAgent::CLIENT, STREAM) {}
+  TlsAgentStreamTestClient()
+      : TlsAgentTestBase(TlsAgent::CLIENT, ssl_variant_stream) {}
 };
 
 class TlsAgentStreamTestServer : public TlsAgentTestBase {
  public:
-  TlsAgentStreamTestServer() : TlsAgentTestBase(TlsAgent::SERVER, STREAM) {}
+  TlsAgentStreamTestServer()
+      : TlsAgentTestBase(TlsAgent::SERVER, ssl_variant_stream) {}
 };
 
 class TlsAgentDgramTestClient : public TlsAgentTestBase {
  public:
-  TlsAgentDgramTestClient() : TlsAgentTestBase(TlsAgent::CLIENT, DGRAM) {}
+  TlsAgentDgramTestClient()
+      : TlsAgentTestBase(TlsAgent::CLIENT, ssl_variant_datagram) {}
 };
 
 inline bool operator==(const SSLVersionRange& vr1, const SSLVersionRange& vr2) {
   return vr1.min == vr2.min && vr1.max == vr2.max;
 }
 
 }  // namespace nss_test
 
--- a/security/nss/gtests/ssl_gtest/tls_connect.cc
+++ b/security/nss/gtests/ssl_gtest/tls_connect.cc
@@ -15,27 +15,30 @@ extern "C" {
 #include "gtest_utils.h"
 #include "scoped_ptrs.h"
 #include "sslproto.h"
 
 extern std::string g_working_dir_path;
 
 namespace nss_test {
 
-static const std::string kTlsModesStreamArr[] = {"TLS"};
-::testing::internal::ParamGenerator<std::string>
-    TlsConnectTestBase::kTlsModesStream =
-        ::testing::ValuesIn(kTlsModesStreamArr);
-static const std::string kTlsModesDatagramArr[] = {"DTLS"};
-::testing::internal::ParamGenerator<std::string>
-    TlsConnectTestBase::kTlsModesDatagram =
-        ::testing::ValuesIn(kTlsModesDatagramArr);
-static const std::string kTlsModesAllArr[] = {"TLS", "DTLS"};
-::testing::internal::ParamGenerator<std::string>
-    TlsConnectTestBase::kTlsModesAll = ::testing::ValuesIn(kTlsModesAllArr);
+static const SSLProtocolVariant kTlsVariantsStreamArr[] = {ssl_variant_stream};
+::testing::internal::ParamGenerator<SSLProtocolVariant>
+    TlsConnectTestBase::kTlsVariantsStream =
+        ::testing::ValuesIn(kTlsVariantsStreamArr);
+static const SSLProtocolVariant kTlsVariantsDatagramArr[] = {
+    ssl_variant_datagram};
+::testing::internal::ParamGenerator<SSLProtocolVariant>
+    TlsConnectTestBase::kTlsVariantsDatagram =
+        ::testing::ValuesIn(kTlsVariantsDatagramArr);
+static const SSLProtocolVariant kTlsVariantsAllArr[] = {ssl_variant_stream,
+                                                        ssl_variant_datagram};
+::testing::internal::ParamGenerator<SSLProtocolVariant>
+    TlsConnectTestBase::kTlsVariantsAll =
+        ::testing::ValuesIn(kTlsVariantsAllArr);
 
 static const uint16_t kTlsV10Arr[] = {SSL_LIBRARY_VERSION_TLS_1_0};
 ::testing::internal::ParamGenerator<uint16_t> TlsConnectTestBase::kTlsV10 =
     ::testing::ValuesIn(kTlsV10Arr);
 static const uint16_t kTlsV11Arr[] = {SSL_LIBRARY_VERSION_TLS_1_1};
 ::testing::internal::ParamGenerator<uint16_t> TlsConnectTestBase::kTlsV11 =
     ::testing::ValuesIn(kTlsV11Arr);
 static const uint16_t kTlsV12Arr[] = {SSL_LIBRARY_VERSION_TLS_1_2};
@@ -95,41 +98,39 @@ std::string VersionString(uint16_t versi
       return "1.3";
     default:
       std::cerr << "Invalid version: " << version << std::endl;
       EXPECT_TRUE(false);
       return "";
   }
 }
 
-TlsConnectTestBase::TlsConnectTestBase(Mode mode, uint16_t version)
-    : mode_(mode),
-      client_(new TlsAgent(TlsAgent::kClient, TlsAgent::CLIENT, mode_)),
-      server_(new TlsAgent(TlsAgent::kServerRsa, TlsAgent::SERVER, mode_)),
+TlsConnectTestBase::TlsConnectTestBase(SSLProtocolVariant variant,
+                                       uint16_t version)
+    : variant_(variant),
+      client_(new TlsAgent(TlsAgent::kClient, TlsAgent::CLIENT, variant_)),
+      server_(new TlsAgent(TlsAgent::kServerRsa, TlsAgent::SERVER, variant_)),
       client_model_(nullptr),
       server_model_(nullptr),
       version_(version),
       expected_resumption_mode_(RESUME_NONE),
       session_ids_(),
       expect_extended_master_secret_(false),
       expect_early_data_accepted_(false),
       skip_version_checks_(false) {
   std::string v;
-  if (mode_ == DGRAM && version_ == SSL_LIBRARY_VERSION_TLS_1_1) {
+  if (variant_ == ssl_variant_datagram &&
+      version_ == SSL_LIBRARY_VERSION_TLS_1_1) {
     v = "1.0";
   } else {
     v = VersionString(version_);
   }
-  std::cerr << "Version: " << mode_ << " " << v << std::endl;
+  std::cerr << "Version: " << variant_ << " " << v << std::endl;
 }
 
-TlsConnectTestBase::TlsConnectTestBase(const std::string& mode,
-                                       uint16_t version)
-    : TlsConnectTestBase(TlsConnectTestBase::ToMode(mode), version) {}
-
 TlsConnectTestBase::~TlsConnectTestBase() {}
 
 // Check the group of each of the supported groups
 void TlsConnectTestBase::CheckGroups(
     const DataBuffer& groups, std::function<void(SSLNamedGroup)> check_group) {
   DuplicateGroupChecker group_set;
   uint32_t tmp = 0;
   EXPECT_TRUE(groups.Read(0, 2, &tmp));
@@ -203,18 +204,18 @@ void TlsConnectTestBase::Reset() {
   // Take a copy of the names because they are about to disappear.
   std::string server_name = server_->name();
   std::string client_name = client_->name();
   Reset(server_name, client_name);
 }
 
 void TlsConnectTestBase::Reset(const std::string& server_name,
                                const std::string& client_name) {
-  client_.reset(new TlsAgent(client_name, TlsAgent::CLIENT, mode_));
-  server_.reset(new TlsAgent(server_name, TlsAgent::SERVER, mode_));
+  client_.reset(new TlsAgent(client_name, TlsAgent::CLIENT, variant_));
+  server_.reset(new TlsAgent(server_name, TlsAgent::SERVER, variant_));
   if (skip_version_checks_) {
     client_->SkipVersionChecks();
     server_->SkipVersionChecks();
   }
 
   Init();
 }
 
@@ -509,19 +510,19 @@ void TlsConnectTestBase::EnableAlpn(cons
   server_->EnableAlpn(val, len);
 }
 
 void TlsConnectTestBase::EnsureModelSockets() {
   // Make sure models agents are available.
   if (!client_model_) {
     ASSERT_EQ(server_model_, nullptr);
     client_model_.reset(
-        new TlsAgent(TlsAgent::kClient, TlsAgent::CLIENT, mode_));
+        new TlsAgent(TlsAgent::kClient, TlsAgent::CLIENT, variant_));
     server_model_.reset(
-        new TlsAgent(TlsAgent::kServerRsa, TlsAgent::SERVER, mode_));
+        new TlsAgent(TlsAgent::kServerRsa, TlsAgent::SERVER, variant_));
     if (skip_version_checks_) {
       client_model_->SkipVersionChecks();
       server_model_->SkipVersionChecks();
     }
   }
 }
 
 void TlsConnectTestBase::CheckAlpn(const std::string& val) {
--- a/security/nss/gtests/ssl_gtest/tls_connect.h
+++ b/security/nss/gtests/ssl_gtest/tls_connect.h
@@ -20,32 +20,34 @@
 
 namespace nss_test {
 
 extern std::string VersionString(uint16_t version);
 
 // A generic TLS connection test base.
 class TlsConnectTestBase : public ::testing::Test {
  public:
-  static ::testing::internal::ParamGenerator<std::string> kTlsModesStream;
-  static ::testing::internal::ParamGenerator<std::string> kTlsModesDatagram;
-  static ::testing::internal::ParamGenerator<std::string> kTlsModesAll;
+  static ::testing::internal::ParamGenerator<SSLProtocolVariant>
+      kTlsVariantsStream;
+  static ::testing::internal::ParamGenerator<SSLProtocolVariant>
+      kTlsVariantsDatagram;
+  static ::testing::internal::ParamGenerator<SSLProtocolVariant>
+      kTlsVariantsAll;
   static ::testing::internal::ParamGenerator<uint16_t> kTlsV10;
   static ::testing::internal::ParamGenerator<uint16_t> kTlsV11;
   static ::testing::internal::ParamGenerator<uint16_t> kTlsV12;
   static ::testing::internal::ParamGenerator<uint16_t> kTlsV10V11;
   static ::testing::internal::ParamGenerator<uint16_t> kTlsV11V12;
   static ::testing::internal::ParamGenerator<uint16_t> kTlsV10ToV12;
   static ::testing::internal::ParamGenerator<uint16_t> kTlsV13;
   static ::testing::internal::ParamGenerator<uint16_t> kTlsV11Plus;
   static ::testing::internal::ParamGenerator<uint16_t> kTlsV12Plus;
   static ::testing::internal::ParamGenerator<uint16_t> kTlsVAll;
 
-  TlsConnectTestBase(Mode mode, uint16_t version);
-  TlsConnectTestBase(const std::string& mode, uint16_t version);
+  TlsConnectTestBase(SSLProtocolVariant variant, uint16_t version);
   virtual ~TlsConnectTestBase();
 
   void SetUp();
   void TearDown();
 
   // Initialize client and server.
   void Init();
   // Clear the statistics.
@@ -109,36 +111,32 @@ class TlsConnectTestBase : public ::test
       std::function<bool()> post_clienthello_check = nullptr);
   void Receive(size_t amount);
   void ExpectExtendedMasterSecret(bool expected);
   void ExpectEarlyDataAccepted(bool expected);
   void DisableECDHEServerKeyReuse();
   void SkipVersionChecks();
 
  protected:
-  Mode mode_;
+  SSLProtocolVariant variant_;
   std::shared_ptr<TlsAgent> client_;
   std::shared_ptr<TlsAgent> server_;
   std::unique_ptr<TlsAgent> client_model_;
   std::unique_ptr<TlsAgent> server_model_;
   uint16_t version_;
   SessionResumptionMode expected_resumption_mode_;
   std::vector<std::vector<uint8_t>> session_ids_;
 
   // A simple value of "a", "b".  Note that the preferred value of "a" is placed
   // at the end, because the NSS API follows the now defunct NPN specification,
   // which places the preferred (and default) entry at the end of the list.
   // NSS will move this final entry to the front when used with ALPN.
   const uint8_t alpn_dummy_val_[4] = {0x01, 0x62, 0x01, 0x61};
 
  private:
-  static inline Mode ToMode(const std::string& str) {
-    return str == "TLS" ? STREAM : DGRAM;
-  }
-
   void CheckResumption(SessionResumptionMode expected);
   void CheckExtendedMasterSecret();
   void CheckEarlyDataAccepted();
 
   bool expect_extended_master_secret_;
   bool expect_early_data_accepted_;
   bool skip_version_checks_;
 
@@ -154,100 +152,101 @@ class TlsConnectTestBase : public ::test
    private:
     std::set<SSLNamedGroup> groups_;
   };
 };
 
 // A non-parametrized TLS test base.
 class TlsConnectTest : public TlsConnectTestBase {
  public:
-  TlsConnectTest() : TlsConnectTestBase(STREAM, 0) {}
+  TlsConnectTest() : TlsConnectTestBase(ssl_variant_stream, 0) {}
 };
 
 // A non-parametrized DTLS-only test base.
 class DtlsConnectTest : public TlsConnectTestBase {
  public:
-  DtlsConnectTest() : TlsConnectTestBase(DGRAM, 0) {}
+  DtlsConnectTest() : TlsConnectTestBase(ssl_variant_datagram, 0) {}
 };
 
 // A TLS-only test base.
 class TlsConnectStream : public TlsConnectTestBase,
                          public ::testing::WithParamInterface<uint16_t> {
  public:
-  TlsConnectStream() : TlsConnectTestBase(STREAM, GetParam()) {}
+  TlsConnectStream() : TlsConnectTestBase(ssl_variant_stream, GetParam()) {}
 };
 
 // A TLS-only test base for tests before 1.3
 class TlsConnectStreamPre13 : public TlsConnectStream {};
 
 // A DTLS-only test base.
 class TlsConnectDatagram : public TlsConnectTestBase,
                            public ::testing::WithParamInterface<uint16_t> {
  public:
-  TlsConnectDatagram() : TlsConnectTestBase(DGRAM, GetParam()) {}
+  TlsConnectDatagram() : TlsConnectTestBase(ssl_variant_datagram, GetParam()) {}
 };
 
-// A generic test class that can be either STREAM or DGRAM and a single version
-// of TLS.  This is configured in ssl_loopback_unittest.cc.  All uses of this
-// should use TEST_P().
-class TlsConnectGeneric
-    : public TlsConnectTestBase,
-      public ::testing::WithParamInterface<std::tuple<std::string, uint16_t>> {
+// A generic test class that can be either stream or datagram and a single
+// version of TLS.  This is configured in ssl_loopback_unittest.cc.
+class TlsConnectGeneric : public TlsConnectTestBase,
+                          public ::testing::WithParamInterface<
+                              std::tuple<SSLProtocolVariant, uint16_t>> {
  public:
   TlsConnectGeneric();
 };
 
 // A Pre TLS 1.2 generic test.
-class TlsConnectPre12
-    : public TlsConnectTestBase,
-      public ::testing::WithParamInterface<std::tuple<std::string, uint16_t>> {
+class TlsConnectPre12 : public TlsConnectTestBase,
+                        public ::testing::WithParamInterface<
+                            std::tuple<SSLProtocolVariant, uint16_t>> {
  public:
   TlsConnectPre12();
 };
 
 // A TLS 1.2 only generic test.
-class TlsConnectTls12 : public TlsConnectTestBase,
-                        public ::testing::WithParamInterface<std::string> {
+class TlsConnectTls12
+    : public TlsConnectTestBase,
+      public ::testing::WithParamInterface<SSLProtocolVariant> {
  public:
   TlsConnectTls12();
 };
 
 // A TLS 1.2 only stream test.
 class TlsConnectStreamTls12 : public TlsConnectTestBase {
  public:
   TlsConnectStreamTls12()
-      : TlsConnectTestBase(STREAM, SSL_LIBRARY_VERSION_TLS_1_2) {}
+      : TlsConnectTestBase(ssl_variant_stream, SSL_LIBRARY_VERSION_TLS_1_2) {}
 };
 
 // A TLS 1.2+ generic test.
-class TlsConnectTls12Plus
-    : public TlsConnectTestBase,
-      public ::testing::WithParamInterface<std::tuple<std::string, uint16_t>> {
+class TlsConnectTls12Plus : public TlsConnectTestBase,
+                            public ::testing::WithParamInterface<
+                                std::tuple<SSLProtocolVariant, uint16_t>> {
  public:
   TlsConnectTls12Plus();
 };
 
 // A TLS 1.3 only generic test.
-class TlsConnectTls13 : public TlsConnectTestBase,
-                        public ::testing::WithParamInterface<std::string> {
+class TlsConnectTls13
+    : public TlsConnectTestBase,
+      public ::testing::WithParamInterface<SSLProtocolVariant> {
  public:
   TlsConnectTls13();
 };
 
 // A TLS 1.3 only stream test.
 class TlsConnectStreamTls13 : public TlsConnectTestBase {
  public:
   TlsConnectStreamTls13()
-      : TlsConnectTestBase(STREAM, SSL_LIBRARY_VERSION_TLS_1_3) {}
+      : TlsConnectTestBase(ssl_variant_stream, SSL_LIBRARY_VERSION_TLS_1_3) {}
 };
 
 class TlsConnectDatagram13 : public TlsConnectTestBase {
  public:
   TlsConnectDatagram13()
-      : TlsConnectTestBase(DGRAM, SSL_LIBRARY_VERSION_TLS_1_3) {}
+      : TlsConnectTestBase(ssl_variant_datagram, SSL_LIBRARY_VERSION_TLS_1_3) {}
 };
 
 // A variant that is used only with Pre13.
 class TlsConnectGenericPre13 : public TlsConnectGeneric {};
 
 class TlsKeyExchangeTest : public TlsConnectGeneric {
  protected:
   std::shared_ptr<TlsExtensionCapture> groups_capture_;
--- a/security/nss/gtests/util_gtest/manifest.mn
+++ b/security/nss/gtests/util_gtest/manifest.mn
@@ -4,16 +4,17 @@
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 CORE_DEPTH = ../..
 DEPTH      = ../..
 MODULE = nss
 
 CPPSRCS = \
 	util_utf8_unittest.cc \
 	util_b64_unittest.cc \
+	util_pkcs11uri_unittest.cc \
 	$(NULL)
 
 INCLUDES += \
 	-I$(CORE_DEPTH)/gtests/google_test/gtest/include \
 	-I$(CORE_DEPTH)/gtests/common \
 	-I$(CORE_DEPTH)/cpputil \
 	$(NULL)
 
--- a/security/nss/gtests/util_gtest/util_gtest.gyp
+++ b/security/nss/gtests/util_gtest/util_gtest.gyp
@@ -8,16 +8,17 @@
   ],
   'targets': [
     {
       'target_name': 'util_gtest',
       'type': 'executable',
       'sources': [
         'util_utf8_unittest.cc',
         'util_b64_unittest.cc',
+	'util_pkcs11uri_unittest.cc',
         '<(DEPTH)/gtests/common/gtests.cc',
       ],
       'dependencies': [
         '<(DEPTH)/exports.gyp:nss_exports',
         '<(DEPTH)/gtests/google_test/google_test.gyp:gtest',
         '<(DEPTH)/lib/util/util.gyp:nssutil',
         '<(DEPTH)/lib/nss/nss.gyp:nss_static',
         '<(DEPTH)/lib/pk11wrap/pk11wrap.gyp:pk11wrap_static',
new file mode 100644
--- /dev/null
+++ b/security/nss/gtests/util_gtest/util_pkcs11uri_unittest.cc
@@ -0,0 +1,167 @@
+/* -*- 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 <climits>
+#include <memory>
+#include "pkcs11uri.h"
+
+#include "gtest/gtest.h"
+#include "scoped_ptrs.h"
+
+namespace nss_test {
+
+class PK11URITest : public ::testing::Test {
+ public:
+  bool TestCreate(const PK11URIAttribute *pattrs, size_t num_pattrs,
+                  const PK11URIAttribute *qattrs, size_t num_qattrs) {
+    ScopedPK11URI tmp(
+        PK11URI_CreateURI(pattrs, num_pattrs, qattrs, num_qattrs));
+    return tmp != nullptr;
+  }
+
+  void TestCreateRetrieve(const PK11URIAttribute *pattrs, size_t num_pattrs,
+                          const PK11URIAttribute *qattrs, size_t num_qattrs) {
+    ScopedPK11URI tmp(
+        PK11URI_CreateURI(pattrs, num_pattrs, qattrs, num_qattrs));
+    ASSERT_TRUE(tmp);
+
+    size_t i;
+    for (i = 0; i < num_pattrs; i++) {
+      const char *value = PK11URI_GetPathAttribute(tmp.get(), pattrs[i].name);
+      ASSERT_TRUE(value);
+      ASSERT_EQ(std::string(value), std::string(pattrs[i].value));
+    }
+    for (i = 0; i < num_qattrs; i++) {
+      const char *value = PK11URI_GetQueryAttribute(tmp.get(), qattrs[i].name);
+      ASSERT_TRUE(value);
+      ASSERT_EQ(std::string(value), std::string(qattrs[i].value));
+    }
+  }
+
+  void TestCreateFormat(const PK11URIAttribute *pattrs, size_t num_pattrs,
+                        const PK11URIAttribute *qattrs, size_t num_qattrs,
+                        const std::string &formatted) {
+    ScopedPK11URI tmp(
+        PK11URI_CreateURI(pattrs, num_pattrs, qattrs, num_qattrs));
+    ASSERT_TRUE(tmp);
+    char *out = PK11URI_FormatURI(nullptr, tmp.get());
+    ASSERT_TRUE(out);
+    ASSERT_EQ(std::string(out), formatted);
+    PORT_Free(out);
+  }
+
+  bool TestParse(const std::string &str) {
+    ScopedPK11URI tmp(PK11URI_ParseURI(str.c_str()));
+    return tmp != nullptr;
+  }
+
+  void TestParseRetrieve(const std::string &str, const PK11URIAttribute *pattrs,
+                         size_t num_pattrs, const PK11URIAttribute *qattrs,
+                         size_t num_qattrs) {
+    ScopedPK11URI tmp(PK11URI_ParseURI(str.c_str()));
+    ASSERT_TRUE(tmp);
+
+    size_t i;
+    for (i = 0; i < num_pattrs; i++) {
+      const char *value = PK11URI_GetPathAttribute(tmp.get(), pattrs[i].name);
+      ASSERT_TRUE(value);
+      ASSERT_EQ(std::string(value), std::string(pattrs[i].value));
+    }
+    for (i = 0; i < num_qattrs; i++) {
+      const char *value = PK11URI_GetQueryAttribute(tmp.get(), qattrs[i].name);
+      ASSERT_TRUE(value);
+      ASSERT_EQ(std::string(value), std::string(qattrs[i].value));
+    }
+  }
+
+  void TestParseFormat(const std::string &str, const std::string &formatted) {
+    ScopedPK11URI tmp(PK11URI_ParseURI(str.c_str()));
+    ASSERT_TRUE(tmp);
+    char *out = PK11URI_FormatURI(nullptr, tmp.get());
+    ASSERT_TRUE(out);
+    ASSERT_EQ(std::string(out), formatted);
+    PORT_Free(out);
+  }
+
+ protected:
+};
+
+const PK11URIAttribute pattrs[] = {
+    {"token", "aaa"}, {"manufacturer", "bbb"}, {"vendor", "ccc"}};
+
+const PK11URIAttribute qattrs[] = {{"pin-source", "|grep foo /etc/passwd"},
+                                   {"pin-value", "secret"},
+                                   {"vendor", "ddd"}};
+
+const PK11URIAttribute pattrs_invalid[] = {{"token", "aaa"},
+                                           {"manufacturer", "bbb"},
+                                           {"vendor", "ccc"},
+                                           {"$%*&", "invalid"},
+                                           {"", "empty"}};
+
+const PK11URIAttribute qattrs_invalid[] = {
+    {"pin-source", "|grep foo /etc/passwd"},
+    {"pin-value", "secret"},
+    {"vendor", "ddd"},
+    {"$%*&", "invalid"},
+    {"", "empty"}};
+
+TEST_F(PK11URITest, CreateTest) {
+  EXPECT_TRUE(
+      TestCreate(pattrs, PR_ARRAY_SIZE(pattrs), qattrs, PR_ARRAY_SIZE(qattrs)));
+  EXPECT_FALSE(TestCreate(pattrs_invalid, PR_ARRAY_SIZE(pattrs_invalid), qattrs,
+                          PR_ARRAY_SIZE(qattrs)));
+  EXPECT_FALSE(TestCreate(pattrs, PR_ARRAY_SIZE(pattrs), qattrs_invalid,
+                          PR_ARRAY_SIZE(qattrs_invalid)));
+  EXPECT_FALSE(TestCreate(pattrs_invalid, PR_ARRAY_SIZE(pattrs_invalid),
+                          qattrs_invalid, PR_ARRAY_SIZE(qattrs_invalid)));
+}
+
+TEST_F(PK11URITest, CreateRetrieveTest) {
+  TestCreateRetrieve(pattrs, PR_ARRAY_SIZE(pattrs), qattrs,
+                     PR_ARRAY_SIZE(qattrs));
+}
+
+TEST_F(PK11URITest, CreateFormatTest) {
+  TestCreateFormat(pattrs, PR_ARRAY_SIZE(pattrs), qattrs, PR_ARRAY_SIZE(qattrs),
+                   "pkcs11:token=aaa;manufacturer=bbb;vendor=ccc?pin-source=|"
+                   "grep%20foo%20/etc/passwd&pin-value=secret&vendor=ddd");
+}
+
+TEST_F(PK11URITest, ParseTest) {
+  EXPECT_FALSE(TestParse("pkcs11:token=aaa;token=bbb"));
+  EXPECT_FALSE(TestParse("pkcs11:dup=aaa;dup=bbb"));
+  EXPECT_FALSE(TestParse("pkcs11:?pin-value=aaa&pin-value=bbb"));
+  EXPECT_FALSE(TestParse("pkcs11:=empty"));
+  EXPECT_FALSE(TestParse("pkcs11:token=%2;manufacturer=aaa"));
+}
+
+TEST_F(PK11URITest, ParseRetrieveTest) {
+  TestParseRetrieve(
+      "pkcs11:token=aaa;manufacturer=bbb;vendor=ccc?pin-source=|"
+      "grep%20foo%20/etc/passwd&pin-value=secret&vendor=ddd",
+      pattrs, PR_ARRAY_SIZE(pattrs), qattrs, PR_ARRAY_SIZE(qattrs));
+}
+
+TEST_F(PK11URITest, ParseFormatTest) {
+  TestParseFormat("pkcs11:", "pkcs11:");
+  TestParseFormat("pkcs11:token=aaa", "pkcs11:token=aaa");
+  TestParseFormat("pkcs11:token=aaa;manufacturer=bbb",
+                  "pkcs11:token=aaa;manufacturer=bbb");
+  TestParseFormat("pkcs11:manufacturer=bbb;token=aaa",
+                  "pkcs11:token=aaa;manufacturer=bbb");
+  TestParseFormat("pkcs11:manufacturer=bbb;token=aaa;vendor2=ddd;vendor1=ccc",
+                  "pkcs11:token=aaa;manufacturer=bbb;vendor1=ccc;vendor2=ddd");
+  TestParseFormat("pkcs11:?pin-value=secret", "pkcs11:?pin-value=secret");
+  TestParseFormat("pkcs11:?dup=aaa&dup=bbb", "pkcs11:?dup=aaa&dup=bbb");
+  TestParseFormat(
+      "pkcs11:?pin-source=|grep%20foo%20/etc/passwd&pin-value=secret",
+      "pkcs11:?pin-source=|grep%20foo%20/etc/passwd&pin-value=secret");
+  TestParseFormat("pkcs11:token=aaa?pin-value=secret",
+                  "pkcs11:token=aaa?pin-value=secret");
+}
+
+}  // namespace nss_test
--- a/security/nss/lib/certdb/alg1485.c
+++ b/security/nss/lib/certdb/alg1485.c
@@ -370,16 +370,17 @@ loser:
 static CERTAVA*
 ParseRFC1485AVA(PLArenaPool* arena, const char** pbp, const char* endptr)
 {
     CERTAVA* a;
     const NameToKind* n2k;
     const char* bp;
     int vt = -1;
     int valLen;
+    PRBool isDottedOid = PR_FALSE;
     SECOidTag kind = SEC_OID_UNKNOWN;
     SECStatus rv = SECFailure;
     SECItem derOid = { 0, NULL, 0 };
     SECItem derVal = { 0, NULL, 0 };
     char sep = 0;
 
     char tagBuf[32];
     char valBuf[1024];
@@ -396,18 +397,19 @@ ParseRFC1485AVA(PLArenaPool* arena, cons
     }
     *pbp = bp;
     /* if we haven't finished, insist that we've stopped on a separator */
     if (sep && sep != ',' && sep != ';' && sep != '+') {
         goto loser;
     }
 
     /* is this a dotted decimal OID attribute type ? */
-    if (!PL_strncasecmp("oid.", tagBuf, 4)) {
+    if (!PL_strncasecmp("oid.", tagBuf, 4) || isdigit(tagBuf[0])) {
         rv = SEC_StringToOID(arena, &derOid, tagBuf, strlen(tagBuf));
+        isDottedOid = (PRBool)(rv == SECSuccess);
     } else {
         for (n2k = name2kinds; n2k->name; n2k++) {
             SECOidData* oidrec;
             if (PORT_Strcasecmp(n2k->name, tagBuf) == 0) {
                 kind = n2k->kind;
                 vt = n2k->valueType;
                 oidrec = SECOID_FindOIDByTag(kind);
                 if (oidrec == NULL)
@@ -423,34 +425,36 @@ ParseRFC1485AVA(PLArenaPool* arena, cons
     /* Is this a hex encoding of a DER attribute value ? */
     if ('#' == valBuf[0]) {
         /* convert attribute value from hex to binary */
         rv = hexToBin(arena, &derVal, valBuf + 1, valLen - 1);
         if (rv)
             goto loser;
         a = CERT_CreateAVAFromRaw(arena, &derOid, &derVal);
     } else {
-        if (kind == SEC_OID_UNKNOWN)
-            goto loser;
         if (kind == SEC_OID_AVA_COUNTRY_NAME && valLen != 2)
             goto loser;
         if (vt == SEC_ASN1_PRINTABLE_STRING &&
             !IsPrintable((unsigned char*)valBuf, valLen))
             goto loser;
         if (vt == SEC_ASN1_DS) {
             /* RFC 4630: choose PrintableString or UTF8String */
             if (IsPrintable((unsigned char*)valBuf, valLen))
                 vt = SEC_ASN1_PRINTABLE_STRING;
             else
                 vt = SEC_ASN1_UTF8_STRING;
         }
 
         derVal.data = (unsigned char*)valBuf;
         derVal.len = valLen;
-        a = CERT_CreateAVAFromSECItem(arena, kind, vt, &derVal);
+        if (kind == SEC_OID_UNKNOWN && isDottedOid) {
+            a = CERT_CreateAVAFromRaw(arena, &derOid, &derVal);
+        } else {
+            a = CERT_CreateAVAFromSECItem(arena, kind, vt, &derVal);
+        }
     }
     return a;
 
 loser:
     /* matched no kind -- invalid tag */
     PORT_SetError(SEC_ERROR_INVALID_AVA);
     return 0;
 }
--- a/security/nss/lib/certhigh/certhtml.c
+++ b/security/nss/lib/certhigh/certhtml.c
@@ -97,104 +97,125 @@ CERT_FormatName(CERTName *name)
                     if (cn) {
                         break;
                     }
                     cn = CERT_DecodeAVAValue(&ava->value);
                     if (!cn) {
                         goto loser;
                     }
                     len += cn->len;
+                    // cn will always have BREAK after it
+                    len += BREAKLEN;
                     break;
                 case SEC_OID_AVA_COUNTRY_NAME:
                     if (country) {
                         break;
                     }
                     country = CERT_DecodeAVAValue(&ava->value);
                     if (!country) {
                         goto loser;
                     }
                     len += country->len;
+                    // country may have COMMA after it (if we over-count len,
+                    // that's fine - we'll just allocate a buffer larger than we
+                    // need)
+                    len += COMMALEN;
                     break;
                 case SEC_OID_AVA_LOCALITY:
                     if (loc) {
                         break;
                     }
                     loc = CERT_DecodeAVAValue(&ava->value);
                     if (!loc) {
                         goto loser;
                     }
                     len += loc->len;
+                    // loc may have COMMA after it
+                    len += COMMALEN;
                     break;
                 case SEC_OID_AVA_STATE_OR_PROVINCE:
                     if (state) {
                         break;
                     }
                     state = CERT_DecodeAVAValue(&ava->value);
                     if (!state) {
                         goto loser;
                     }
                     len += state->len;
+                    // state currently won't have COMMA after it, but this is a
+                    // (probably vain) attempt to future-proof this code
+                    len += COMMALEN;
                     break;
                 case SEC_OID_AVA_ORGANIZATION_NAME:
                     if (org) {
                         break;
                     }
                     org = CERT_DecodeAVAValue(&ava->value);
                     if (!org) {
                         goto loser;
                     }
                     len += org->len;
+                    // org will have BREAK after it
+                    len += BREAKLEN;
                     break;
                 case SEC_OID_AVA_DN_QUALIFIER:
                     if (dq) {
                         break;
                     }
                     dq = CERT_DecodeAVAValue(&ava->value);
                     if (!dq) {
                         goto loser;
                     }
                     len += dq->len;
+                    // dq will have BREAK after it
+                    len += BREAKLEN;
                     break;
                 case SEC_OID_AVA_ORGANIZATIONAL_UNIT_NAME:
                     if (ou_count < MAX_OUS) {
                         orgunit[ou_count] = CERT_DecodeAVAValue(&ava->value);
                         if (!orgunit[ou_count]) {
                             goto loser;
                         }
                         len += orgunit[ou_count++]->len;
+                        // each ou will have BREAK after it
+                        len += BREAKLEN;
                     }
                     break;
                 case SEC_OID_AVA_DC:
                     if (dc_count < MAX_DC) {
                         dc[dc_count] = CERT_DecodeAVAValue(&ava->value);
                         if (!dc[dc_count]) {
                             goto loser;
                         }
                         len += dc[dc_count++]->len;
+                        // each dc will have BREAK after it
+                        len += BREAKLEN;
                     }
                     break;
                 case SEC_OID_PKCS9_EMAIL_ADDRESS:
                 case SEC_OID_RFC1274_MAIL:
                     if (email) {
                         break;
                     }
                     email = CERT_DecodeAVAValue(&ava->value);
                     if (!email) {
                         goto loser;
                     }
                     len += email->len;
+                    // email will have BREAK after it
+                    len += BREAKLEN;
                     break;
                 default:
                     break;
             }
         }
     }
 
-    /* XXX - add some for formatting */
-    len += 128;
+    // there may be a final BREAK
+    len += BREAKLEN;
 
     /* allocate buffer */
     buf = (char *)PORT_Alloc(len);
     if (!buf) {
         goto loser;
     }
 
     tmpbuf = buf;
--- a/security/nss/lib/dev/dev.h
+++ b/security/nss/lib/dev/dev.h
@@ -307,16 +307,25 @@ nssToken_GetModule(
 NSS_EXTERN NSSSlot *
 nssToken_GetSlot(
     NSSToken *tok);
 
 NSS_EXTERN PRBool
 nssToken_NeedsPINInitialization(
     NSSToken *token);
 
+NSS_EXTERN nssCryptokiObject **
+nssToken_FindObjectsByTemplate(
+    NSSToken *token,
+    nssSession *sessionOpt,
+    CK_ATTRIBUTE_PTR obj_template,
+    CK_ULONG otsize,
+    PRUint32 maximumOpt,
+    PRStatus *statusOpt);
+
 NSS_EXTERN nssCryptokiObject *
 nssToken_ImportCertificate(
     NSSToken *tok,
     nssSession *sessionOpt,
     NSSCertificateType certType,
     NSSItem *id,
     const NSSUTF8 *nickname,
     NSSDER *encoding,
--- a/security/nss/lib/dev/devtoken.c
+++ b/security/nss/lib/dev/devtoken.c
@@ -363,18 +363,18 @@ loser:
         nss_SetError(ckrv);
         nss_SetError(NSS_ERROR_PKCS11);
         if (statusOpt)
             *statusOpt = PR_FAILURE;
     }
     return (nssCryptokiObject **)NULL;
 }
 
-static nssCryptokiObject **
-find_objects_by_template(
+NSS_IMPLEMENT nssCryptokiObject **
+nssToken_FindObjectsByTemplate(
     NSSToken *token,
     nssSession *sessionOpt,
     CK_ATTRIBUTE_PTR obj_template,
     CK_ULONG otsize,
     PRUint32 maximumOpt,
     PRStatus *statusOpt)
 {
     CK_OBJECT_CLASS objclass = (CK_OBJECT_CLASS)-1;
@@ -576,19 +576,19 @@ nssToken_FindObjects(
     NSS_CK_SET_ATTRIBUTE_VAR(attr, CKA_CLASS, objclass);
     NSS_CK_TEMPLATE_FINISH(obj_template, attr, obj_size);
 
     if (searchType == nssTokenSearchType_TokenForced) {
         objects = find_objects(token, sessionOpt,
                                obj_template, obj_size,
                                maximumOpt, statusOpt);
     } else {
-        objects = find_objects_by_template(token, sessionOpt,
-                                           obj_template, obj_size,
-                                           maximumOpt, statusOpt);
+        objects = nssToken_FindObjectsByTemplate(token, sessionOpt,
+                                                 obj_template, obj_size,
+                                                 maximumOpt, statusOpt);
     }
     return objects;
 }
 
 NSS_IMPLEMENT nssCryptokiObject **
 nssToken_FindCertificatesBySubject(
     NSSToken *token,
     nssSession *sessionOpt,
@@ -607,19 +607,19 @@ nssToken_FindCertificatesBySubject(
         NSS_CK_SET_ATTRIBUTE_ITEM(attr, CKA_TOKEN, &g_ck_false);
     } else if (searchType == nssTokenSearchType_TokenOnly) {
         NSS_CK_SET_ATTRIBUTE_ITEM(attr, CKA_TOKEN, &g_ck_true);
     }
     NSS_CK_SET_ATTRIBUTE_ITEM(attr, CKA_CLASS, &g_ck_class_cert);
     NSS_CK_SET_ATTRIBUTE_ITEM(attr, CKA_SUBJECT, subject);
     NSS_CK_TEMPLATE_FINISH(subj_template, attr, stsize);
     /* now locate the token certs matching this template */
-    objects = find_objects_by_template(token, sessionOpt,
-                                       subj_template, stsize,
-                                       maximumOpt, statusOpt);
+    objects = nssToken_FindObjectsByTemplate(token, sessionOpt,
+                                             subj_template, stsize,
+                                             maximumOpt, statusOpt);
     return objects;
 }
 
 NSS_IMPLEMENT nssCryptokiObject **
 nssToken_FindCertificatesByNickname(
     NSSToken *token,
     nssSession *sessionOpt,
     const NSSUTF8 *name,
@@ -637,30 +637,30 @@ nssToken_FindCertificatesByNickname(
     if (searchType == nssTokenSearchType_SessionOnly) {
         NSS_CK_SET_ATTRIBUTE_ITEM(attr, CKA_TOKEN, &g_ck_false);
     } else if (searchType == nssTokenSearchType_TokenOnly) {
         NSS_CK_SET_ATTRIBUTE_ITEM(attr, CKA_TOKEN, &g_ck_true);
     }
     NSS_CK_SET_ATTRIBUTE_ITEM(attr, CKA_CLASS, &g_ck_class_cert);
     NSS_CK_TEMPLATE_FINISH(nick_template, attr, ntsize);
     /* now locate the token certs matching this template */
-    objects = find_objects_by_template(token, sessionOpt,
-                                       nick_template, ntsize,
-                                       maximumOpt, statusOpt);
+    objects = nssToken_FindObjectsByTemplate(token, sessionOpt,
+                                             nick_template, ntsize,
+                                             maximumOpt, statusOpt);
     if (!objects) {
         /* This is to workaround the fact that PKCS#11 doesn't specify
          * whether the '\0' should be included.  XXX Is that still true?
          * im - this is not needed by the current softoken.  However, I'm
          * leaving it in until I have surveyed more tokens to see if it needed.
          * well, its needed by the builtin token...
          */
         nick_template[0].ulValueLen++;
-        objects = find_objects_by_template(token, sessionOpt,
-                                           nick_template, ntsize,
-                                           maximumOpt, statusOpt);
+        objects = nssToken_FindObjectsByTemplate(token, sessionOpt,
+                                                 nick_template, ntsize,
+                                                 maximumOpt, statusOpt);
     }
     return objects;
 }
 
 /* XXX
  * This function *does not* use the token object cache, because not even
  * the softoken will return a value for CKA_NSS_EMAIL from a call
  * to GetAttributes.  The softoken does allow searches with that attribute,
@@ -727,19 +727,19 @@ nssToken_FindCertificatesByID(
     if (searchType == nssTokenSearchType_SessionOnly) {
         NSS_CK_SET_ATTRIBUTE_ITEM(attr, CKA_TOKEN, &g_ck_false);
     } else if (searchType == nssTokenSearchType_TokenOnly) {
         NSS_CK_SET_ATTRIBUTE_ITEM(attr, CKA_TOKEN, &g_ck_true);
     }
     NSS_CK_SET_ATTRIBUTE_ITEM(attr, CKA_CLASS, &g_ck_class_cert);
     NSS_CK_TEMPLATE_FINISH(id_template, attr, idtsize);
     /* now locate the token certs matching this template */
-    objects = find_objects_by_template(token, sessionOpt,
-                                       id_template, idtsize,
-                                       maximumOpt, statusOpt);
+    objects = nssToken_FindObjectsByTemplate(token, sessionOpt,
+                                             id_template, idtsize,
+                                             maximumOpt, statusOpt);
     return objects;
 }
 
 /*
  * decode the serial item and return our result.
  * NOTE serialDecode's data is really stored in serial. Don't free it.
  */
 static PRStatus
@@ -817,19 +817,19 @@ nssToken_FindCertificateByIssuerAndSeria
     NSS_CK_SET_ATTRIBUTE_ITEM(attr, CKA_SERIAL_NUMBER, serial);
     NSS_CK_TEMPLATE_FINISH(cert_template, attr, ctsize);
     /* get the object handle */
     if (searchType == nssTokenSearchType_TokenForced) {
         objects = find_objects(token, sessionOpt,
                                cert_template, ctsize,
                                1, statusOpt);
     } else {
-        objects = find_objects_by_template(token, sessionOpt,
-                                           cert_template, ctsize,
-                                           1, statusOpt);
+        objects = nssToken_FindObjectsByTemplate(token, sessionOpt,
+                                                 cert_template, ctsize,
+                                                 1, statusOpt);
     }
     if (objects) {
         rvObject = objects[0];
         nss_ZFreeIf(objects);
     }
 
     /*
      * NSS used to incorrectly store serial numbers in their decoded form.
@@ -844,19 +844,19 @@ nssToken_FindCertificateByIssuerAndSeria
             return NULL;
         }
         NSS_CK_SET_ATTRIBUTE_ITEM(serialAttr, CKA_SERIAL_NUMBER, &serialDecode);
         if (searchType == nssTokenSearchType_TokenForced) {
             objects = find_objects(token, sessionOpt,
                                    cert_template, ctsize,
                                    1, statusOpt);
         } else {
-            objects = find_objects_by_template(token, sessionOpt,
-                                               cert_template, ctsize,
-                                               1, statusOpt);
+            objects = nssToken_FindObjectsByTemplate(token, sessionOpt,
+                                                     cert_template, ctsize,
+                                                     1, statusOpt);
         }
         if (objects) {
             rvObject = objects[0];
             nss_ZFreeIf(objects);
         }
     }
     return rvObject;
 }
@@ -880,19 +880,19 @@ nssToken_FindCertificateByEncodedCertifi
         NSS_CK_SET_ATTRIBUTE_ITEM(attr, CKA_TOKEN, &g_ck_false);
     } else if (searchType == nssTokenSearchType_TokenOnly) {
         NSS_CK_SET_ATTRIBUTE_ITEM(attr, CKA_TOKEN, &g_ck_true);
     }
     NSS_CK_SET_ATTRIBUTE_ITEM(attr, CKA_CLASS, &g_ck_class_cert);
     NSS_CK_SET_ATTRIBUTE_ITEM(attr, CKA_VALUE, encodedCertificate);
     NSS_CK_TEMPLATE_FINISH(cert_template, attr, ctsize);
     /* get the object handle */
-    objects = find_objects_by_template(token, sessionOpt,
-                                       cert_template, ctsize,
-                                       1, statusOpt);
+    objects = nssToken_FindObjectsByTemplate(token, sessionOpt,
+                                             cert_template, ctsize,
+                                             1, statusOpt);
     if (objects) {
         rvObject = objects[0];
         nss_ZFreeIf(objects);
     }
     return rvObject;
 }
 
 NSS_IMPLEMENT nssCryptokiObject **
@@ -912,19 +912,19 @@ nssToken_FindPrivateKeys(
     NSS_CK_SET_ATTRIBUTE_ITEM(attr, CKA_CLASS, &g_ck_class_privkey);
     if (searchType == nssTokenSearchType_SessionOnly) {
         NSS_CK_SET_ATTRIBUTE_ITEM(attr, CKA_TOKEN, &g_ck_false);
     } else if (searchType == nssTokenSearchType_TokenOnly) {
         NSS_CK_SET_ATTRIBUTE_ITEM(attr, CKA_TOKEN, &g_ck_true);
     }
     NSS_CK_TEMPLATE_FINISH(key_template, attr, ktsize);
 
-    objects = find_objects_by_template(token, sessionOpt,
-                                       key_template, ktsize,
-                                       maximumOpt, statusOpt);
+    objects = nssToken_FindObjectsByTemplate(token, sessionOpt,
+                                             key_template, ktsize,
+                                             maximumOpt, statusOpt);
     return objects;
 }
 
 /* XXX ?there are no session cert objects, so only search token objects */
 NSS_IMPLEMENT nssCryptokiObject *
 nssToken_FindPrivateKeyByID(
     NSSToken *token,
     nssSession *sessionOpt,
@@ -937,19 +937,19 @@ nssToken_FindPrivateKeyByID(
     nssCryptokiObject *rvKey = NULL;
 
     NSS_CK_TEMPLATE_START(key_template, attr, ktsize);
     NSS_CK_SET_ATTRIBUTE_ITEM(attr, CKA_CLASS, &g_ck_class_privkey);
     NSS_CK_SET_ATTRIBUTE_ITEM(attr, CKA_TOKEN, &g_ck_true);
     NSS_CK_SET_ATTRIBUTE_ITEM(attr, CKA_ID, keyID);
     NSS_CK_TEMPLATE_FINISH(key_template, attr, ktsize);
 
-    objects = find_objects_by_template(token, sessionOpt,
-                                       key_template, ktsize,
-                                       1, NULL);
+    objects = nssToken_FindObjectsByTemplate(token, sessionOpt,
+                                             key_template, ktsize,
+                                             1, NULL);
     if (objects) {
         rvKey = objects[0];
         nss_ZFreeIf(objects);
     }
     return rvKey;
 }
 
 /* XXX ?there are no session cert objects, so only search token objects */
@@ -966,19 +966,19 @@ nssToken_FindPublicKeyByID(
     nssCryptokiObject *rvKey = NULL;
 
     NSS_CK_TEMPLATE_START(key_template, attr, ktsize);
     NSS_CK_SET_ATTRIBUTE_ITEM(attr, CKA_CLASS, &g_ck_class_pubkey);
     NSS_CK_SET_ATTRIBUTE_ITEM(attr, CKA_TOKEN, &g_ck_true);
     NSS_CK_SET_ATTRIBUTE_ITEM(attr, CKA_ID, keyID);
     NSS_CK_TEMPLATE_FINISH(key_template, attr, ktsize);
 
-    objects = find_objects_by_template(token, sessionOpt,
-                                       key_template, ktsize,
-                                       1, NULL);
+    objects = nssToken_FindObjectsByTemplate(token, sessionOpt,
+                                             key_template, ktsize,
+                                             1, NULL);
     if (objects) {
         rvKey = objects[0];
         nss_ZFreeIf(objects);
     }
     return rvKey;
 }
 
 static void
@@ -1125,19 +1125,19 @@ nssToken_FindTrustForCertificate(
     NSS_CK_TEMPLATE_START(tobj_template, attr, tobj_size);
     if (searchType == nssTokenSearchType_TokenOnly) {
         NSS_CK_SET_ATTRIBUTE_ITEM(attr, CKA_TOKEN, &g_ck_true);
     }
     NSS_CK_SET_ATTRIBUTE_VAR(attr, CKA_CLASS, tobjc);
     NSS_CK_SET_ATTRIBUTE_ITEM(attr, CKA_ISSUER, certIssuer);
     NSS_CK_SET_ATTRIBUTE_ITEM(attr, CKA_SERIAL_NUMBER, certSerial);
     NSS_CK_TEMPLATE_FINISH(tobj_template, attr, tobj_size);
-    objects = find_objects_by_template(token, session,
-                                       tobj_template, tobj_size,
-                                       1, NULL);
+    objects = nssToken_FindObjectsByTemplate(token, session,
+                                             tobj_template, tobj_size,
+                                             1, NULL);
     if (objects) {
         object = objects[0];
         nss_ZFreeIf(objects);
     }
     return object;
 }
 
 NSS_IMPLEMENT nssCryptokiObject *
@@ -1210,19 +1210,19 @@ nssToken_FindCRLsBySubject(
     } else if (searchType == nssTokenSearchType_TokenOnly ||
                searchType == nssTokenSearchType_TokenForced) {
         NSS_CK_SET_ATTRIBUTE_ITEM(attr, CKA_TOKEN, &g_ck_true);
     }
     NSS_CK_SET_ATTRIBUTE_VAR(attr, CKA_CLASS, crlobjc);
     NSS_CK_SET_ATTRIBUTE_ITEM(attr, CKA_SUBJECT, subject);
     NSS_CK_TEMPLATE_FINISH(crlobj_template, attr, crlobj_size);
 
-    objects = find_objects_by_template(token, session,
-                                       crlobj_template, crlobj_size,
-                                       maximumOpt, statusOpt);
+    objects = nssToken_FindObjectsByTemplate(token, session,
+                                             crlobj_template, crlobj_size,
+                                             maximumOpt, statusOpt);
     return objects;
 }
 
 NSS_IMPLEMENT PRStatus
 nssToken_GetCachedObjectAttributes(
     NSSToken *token,
     NSSArena *arenaOpt,
     nssCryptokiObject *object,
--- a/security/nss/lib/freebl/blapii.h
+++ b/security/nss/lib/freebl/blapii.h
@@ -4,16 +4,17 @@
  * This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef _BLAPII_H_
 #define _BLAPII_H_
 
 #include "blapit.h"
+#include "mpi.h"
 
 /* max block size of supported block ciphers */
 #define MAX_BLOCK_SIZE 16
 
 typedef SECStatus (*freeblCipherFunc)(void *cx, unsigned char *output,
                                       unsigned int *outputLen, unsigned int maxOutputLen,
                                       const unsigned char *input, unsigned int inputLen,
                                       unsigned int blocksize);
@@ -54,15 +55,16 @@ SEC_END_PROTOS
 #define NO_SANITIZE_ALIGNMENT __attribute__((no_sanitize("alignment")))
 #else
 #define NO_SANITIZE_ALIGNMENT
 #endif
 
 #undef HAVE_NO_SANITIZE_ATTR
 
 SECStatus RSA_Init();
+SECStatus generate_prime(mp_int *prime, int primeLen);
 
 /* Freebl state. */
 PRBool aesni_support();
 PRBool clmul_support();
 PRBool avx_support();