Merge inbound to mozilla-central. a=merge
authorTiberius Oros <toros@mozilla.com>
Wed, 21 Mar 2018 12:03:08 +0200
changeset 465261 e636edf00e6fbdc3206c9df4a1548ae38b3d13fa
parent 465206 f4ddf30ecf57e0d4bc5dac5707c6c153ffb9c726 (current diff)
parent 465260 e8640719c6f96ddf8c5b8fd82dd1d6806dd3624f (diff)
child 465262 005891e345a4c1ecba4bb087c8717fe00b798f03
child 465275 f3aab55e34aba6292cc2b13d8cc954423d1504bd
child 465330 828a0bf882b7833ff2053252f5ad48ce533be4a2
push id1728
push userjlund@mozilla.com
push dateMon, 18 Jun 2018 21:12:27 +0000
treeherdermozilla-release@c296fde26f5f [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone61.0a1
first release with
nightly linux32
e636edf00e6f / 61.0a1 / 20180321102906 / files
nightly linux64
e636edf00e6f / 61.0a1 / 20180321102906 / files
nightly mac
e636edf00e6f / 61.0a1 / 20180321102906 / files
nightly win32
e636edf00e6f / 61.0a1 / 20180321102906 / files
nightly win64
e636edf00e6f / 61.0a1 / 20180321102906 / files
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
releases
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge inbound to mozilla-central. a=merge
browser/modules/BrowserErrorReporter.jsm
browser/modules/test/browser/browser_BrowserErrorReporter.js
dom/workers/test/extensions/traditional/WorkerTest.js
dom/workers/test/extensions/traditional/WorkerTest.manifest
dom/workers/test/extensions/traditional/install.rdf
dom/workers/test/extensions/traditional/jar.mn
dom/workers/test/extensions/traditional/moz.build
dom/workers/test/extensions/traditional/nsIWorkerTest.idl
dom/workers/test/extensions/traditional/worker-test@mozilla.org.xpi
dom/workers/test/extensions/traditional/worker.js
dom/workers/test/test_extension.xul
gfx/doc/README.displayitem
gfx/layers/basic/BasicDisplayItemLayer.cpp
js/src/vm/CaseFolding.txt
js/src/vm/DerivedCoreProperties.txt
js/src/vm/SpecialCasing.txt
mfbt/double-conversion/use-StandardInteger.patch
netwerk/test/httpserver/httpd.js
services/sync/tps/extensions/tps/components/tps-cmdline.js
testing/mochitest/bootstrap.js
testing/mochitest/moz.build
testing/talos/talos/pageloader/components/tp-cmdline.js
testing/talos/talos/startup_test/sessionrestore/addon/SessionRestoreTalosTest.js
testing/talos/talos/talos-powers/components/TalosPowersService.js
toolkit/mozapps/extensions/ChromeManifestParser.jsm
toolkit/mozapps/extensions/test/addons/test_bug299716_2/install.rdf
toolkit/mozapps/extensions/test/addons/test_bug299716_a_1/install.rdf
toolkit/mozapps/extensions/test/addons/test_bug299716_a_2/install.rdf
toolkit/mozapps/extensions/test/addons/test_bug299716_b_1/install.rdf
toolkit/mozapps/extensions/test/addons/test_bug299716_b_2/install.rdf
toolkit/mozapps/extensions/test/addons/test_bug299716_c_1/install.rdf
toolkit/mozapps/extensions/test/addons/test_bug299716_c_2/install.rdf
toolkit/mozapps/extensions/test/addons/test_bug299716_d_1/install.rdf
toolkit/mozapps/extensions/test/addons/test_bug299716_d_2/install.rdf
toolkit/mozapps/extensions/test/addons/test_bug299716_e_1/install.rdf
toolkit/mozapps/extensions/test/addons/test_bug299716_e_2/install.rdf
toolkit/mozapps/extensions/test/addons/test_bug299716_f_1/install.rdf
toolkit/mozapps/extensions/test/addons/test_bug299716_f_2/install.rdf
toolkit/mozapps/extensions/test/addons/test_bug299716_g_1/install.rdf
toolkit/mozapps/extensions/test/addons/test_bug299716_g_2/install.rdf
toolkit/mozapps/extensions/test/addons/test_bug394300_1/install.rdf
toolkit/mozapps/extensions/test/addons/test_bug394300_2/install.rdf
toolkit/mozapps/extensions/test/addons/test_chromemanifest_6/chrome.manifest
toolkit/mozapps/extensions/test/addons/test_chromemanifest_6/install.rdf
toolkit/mozapps/extensions/test/browser/addons/browser_update1_1/chrome.manifest
toolkit/mozapps/extensions/test/browser/addons/browser_update1_2/chrome.manifest
toolkit/mozapps/extensions/test/browser/browser_updatessl.rdf
toolkit/mozapps/extensions/test/browser/browser_updatessl.rdf^headers^
toolkit/mozapps/extensions/test/xpcshell/data/blocklistchange/addon_update1.rdf
toolkit/mozapps/extensions/test/xpcshell/data/blocklistchange/addon_update2.rdf
toolkit/mozapps/extensions/test/xpcshell/data/blocklistchange/addon_update3.rdf
toolkit/mozapps/extensions/test/xpcshell/data/test_backgroundupdate.rdf
toolkit/mozapps/extensions/test/xpcshell/data/test_bug299716.rdf
toolkit/mozapps/extensions/test/xpcshell/data/test_bug299716_2.rdf
toolkit/mozapps/extensions/test/xpcshell/data/test_bug394300.rdf
toolkit/mozapps/extensions/test/xpcshell/data/test_bug470377/update_1.rdf
toolkit/mozapps/extensions/test/xpcshell/data/test_bug470377/update_2.rdf
toolkit/mozapps/extensions/test/xpcshell/data/test_bug470377/update_3.rdf
toolkit/mozapps/extensions/test/xpcshell/data/test_bug470377/update_4.rdf
toolkit/mozapps/extensions/test/xpcshell/data/test_bug470377/update_5.rdf
toolkit/mozapps/extensions/test/xpcshell/data/test_bug655254.rdf
toolkit/mozapps/extensions/test/xpcshell/data/test_corrupt.rdf
toolkit/mozapps/extensions/test/xpcshell/data/test_delay_updates_complete.rdf
toolkit/mozapps/extensions/test/xpcshell/data/test_delay_updates_defer.rdf
toolkit/mozapps/extensions/test/xpcshell/data/test_delay_updates_ignore.rdf
toolkit/mozapps/extensions/test/xpcshell/data/test_dictionary.rdf
toolkit/mozapps/extensions/test/xpcshell/data/test_updatecompatmode_ignore.rdf
toolkit/mozapps/extensions/test/xpcshell/data/test_updatecompatmode_normal.rdf
toolkit/mozapps/extensions/test/xpcshell/data/test_updatecompatmode_strict.rdf
toolkit/mozapps/extensions/test/xpcshell/test_ChromeManifestParser.js
toolkit/mozapps/extensions/test/xpcshell/test_bootstrap_resource.js
toolkit/mozapps/extensions/test/xpcshell/test_bug299716.js
toolkit/mozapps/extensions/test/xpcshell/test_bug299716_2.js
tools/quitter/QuitterObserver.js
--- a/CLOBBER
+++ b/CLOBBER
@@ -17,9 +17,9 @@
 #
 # Modifying this file will now automatically clobber the buildbot machines \o/
 #
 
 # Are you updating CLOBBER because you think it's needed for your WebIDL
 # changes to stick? As of bug 928195, this shouldn't be necessary! Please
 # don't change CLOBBER for WebIDL changes any more.
 
-Merge day clobber
\ No newline at end of file
+Historically updating ICU has required a CLOBBER.  Bug 1445524 is a fairly notable ICU-related change, so play it safe and force a full rebuild, even if no problem along these lines has actually been observed.
--- a/browser/base/content/browser-pageActions.js
+++ b/browser/base/content/browser-pageActions.js
@@ -457,91 +457,97 @@ var BrowserPageActions = {
    * Updates the DOM nodes of an action to reflect either a changed property or
    * all properties.
    *
    * @param  action (PageActions.Action, required)
    *         The action to update.
    * @param  propertyName (string, optional)
    *         The name of the property to update.  If not given, then DOM nodes
    *         will be updated to reflect the current values of all properties.
+   * @param  value (optional)
+   *         If a property name is passed, this argument may contain its
+   *         current value, in order to prevent a further look-up.
    */
-  updateAction(action, propertyName = null) {
-    let propertyNames = propertyName ? [propertyName] : [
-      "iconURL",
-      "title",
-      "tooltip",
-    ];
-    for (let name of propertyNames) {
-      let upper = name[0].toUpperCase() + name.substr(1);
-      this[`_updateAction${upper}`](action);
+  updateAction(action, propertyName = null, value) {
+    if (propertyName) {
+      this[this._updateMethods[propertyName]](action, value);
+    } else {
+      for (let name of ["iconURL", "title", "tooltip"]) {
+        this[this._updateMethods[name]](action, value);
+      }
     }
   },
 
-  _updateActionDisabled(action) {
-    this._updateActionDisabledInPanel(action);
+  _updateMethods: {
+    disabled: "_updateActionDisabled",
+    iconURL: "_updateActionIconURL",
+    title: "_updateActionTitle",
+    tooltip: "_updateActionTooltip",
+  },
+
+  _updateActionDisabled(action, disabled) {
+    this._updateActionDisabledInPanel(action, disabled);
     this.placeActionInUrlbar(action);
   },
 
-  _updateActionDisabledInPanel(action) {
+  _updateActionDisabledInPanel(action, disabled = action.getDisabled(window)) {
     let panelButton = this.panelButtonNodeForActionID(action.id);
     if (panelButton) {
-      if (action.getDisabled(window)) {
+      if (disabled) {
         panelButton.setAttribute("disabled", "true");
       } else {
         panelButton.removeAttribute("disabled");
       }
     }
   },
 
-  _updateActionIconURL(action) {
-    let nodes = [
-      this.panelButtonNodeForActionID(action.id),
-      this.urlbarButtonNodeForActionID(action.id),
-    ].filter(n => !!n);
-    for (let node of nodes) {
-      for (let size of [16, 32]) {
-        let url = action.iconURLForSize(size, window);
-        let prop = `--pageAction-image-${size}px`;
-        if (url) {
-          node.style.setProperty(prop, `url("${url}")`);
-        } else {
-          node.style.removeProperty(prop);
-        }
+  _updateActionIconURL(action, properties = action.getIconProperties(window)) {
+    let panelButton = this.panelButtonNodeForActionID(action.id);
+    let urlbarButton = this.urlbarButtonNodeForActionID(action.id);
+
+    for (let [prop, value] of Object.entries(properties)) {
+      if (panelButton) {
+        panelButton.style.setProperty(prop, value);
+      }
+      if (urlbarButton) {
+        urlbarButton.style.setProperty(prop, value);
       }
     }
   },
 
-  _updateActionTitle(action) {
-    let title = action.getTitle(window);
+  _updateActionTitle(action, title = action.getTitle(window)) {
     if (!title) {
       // `title` is a required action property, but the bookmark action's is an
       // empty string since its actual title is set via
       // BookmarkingUI.updateBookmarkPageMenuItem().  The purpose of this early
       // return is to ignore that empty title.
       return;
     }
-    let attrNamesByNodeFnName = {
-      panelButtonNodeForActionID: "label",
-      urlbarButtonNodeForActionID: "aria-label",
-    };
-    for (let [fnName, attrName] of Object.entries(attrNamesByNodeFnName)) {
-      let node = this[fnName](action.id);
-      if (node) {
-        node.setAttribute(attrName, title);
-      }
+    let panelButton = this.panelButtonNodeForActionID(action.id);
+    if (panelButton) {
+      panelButton.setAttribute("label", title);
     }
-    // tooltiptext falls back to the title, so update it, too.
-    this._updateActionTooltip(action);
+
+    let urlbarButton = this.urlbarButtonNodeForActionID(action.id);
+    if (urlbarButton) {
+      urlbarButton.setAttribute("aria-label", title);
+
+      // tooltiptext falls back to the title, so update it, too.
+      this._updateActionTooltip(action, undefined, title, urlbarButton);
+    }
   },
 
-  _updateActionTooltip(action) {
-    let node = this.urlbarButtonNodeForActionID(action.id);
+  _updateActionTooltip(action, tooltip = action.getTooltip(window),
+                       title,
+                       node = this.urlbarButtonNodeForActionID(action.id)) {
     if (node) {
-      let tooltip = action.getTooltip(window) || action.getTitle(window);
-      node.setAttribute("tooltiptext", tooltip);
+      if (!tooltip && title === undefined) {
+        title = action.getTitle(window);
+      }
+      node.setAttribute("tooltiptext", tooltip || title);
     }
   },
 
   doCommandForAction(action, event, buttonNode) {
     if (event && event.type == "click" && event.button != 0) {
       return;
     }
     PageActions.logTelemetry("used", action, buttonNode);
--- a/browser/components/extensions/ext-chrome-settings-overrides.js
+++ b/browser/components/extensions/ext-chrome-settings-overrides.js
@@ -77,24 +77,26 @@ this.chrome_settings_overrides = class e
         Services.search.removeEngine(engine);
       } catch (e) {
         Cu.reportError(e);
       }
     }
   }
 
   static removeSearchSettings(id) {
-    this.processDefaultSearchSetting("removeSetting", id);
-    this.removeEngine(id);
+    return Promise.all([
+      this.processDefaultSearchSetting("removeSetting", id),
+      this.removeEngine(id),
+    ]);
   }
 
   static onUninstall(id) {
     // Note: We do not have to deal with homepage here as it is managed by
     // the ExtensionPreferencesManager.
-    this.removeSearchSettings(id);
+    return this.removeSearchSettings(id);
   }
 
   static onUpdate(id, manifest) {
     let haveHomepage = manifest && manifest.chrome_settings_overrides &&
                        manifest.chrome_settings_overrides.homepage;
     if (!haveHomepage) {
       ExtensionPreferencesManager.removeSetting(id, "homepage_override");
     }
--- a/browser/components/extensions/ext-pageAction.js
+++ b/browser/components/extensions/ext-pageAction.js
@@ -80,24 +80,26 @@ this.pageAction = class extends Extensio
                                      extension);
 
     this.tabContext.on("location-change", this.handleLocationChange.bind(this)); // eslint-disable-line mozilla/balanced-listeners
 
     pageActionMap.set(extension, this);
 
     this.defaults.icon = await StartupCache.get(
       extension, ["pageAction", "default_icon"],
-      () => IconDetails.normalize({path: options.default_icon}, extension));
+      () => this.normalize({path: options.default_icon || ""}));
+
+    this.lastValues = new DefaultWeakMap(() => ({}));
 
     if (!this.browserPageAction) {
       this.browserPageAction = PageActions.addAction(new PageActions.Action({
         id: widgetId,
         extensionID: extension.id,
         title: this.defaults.title,
-        iconURL: this.getIconData(this.defaults.icon),
+        iconURL: this.defaults.icon,
         pinnedToUrlbar: true,
         disabled: !this.defaults.show,
         onCommand: (event, buttonNode) => {
           this.handleClick(event.target.ownerGlobal);
         },
         onBeforePlacedInWindow: browserWindow => {
           if (this.extension.hasPermission("menus") ||
               this.extension.hasPermission("contextMenus")) {
@@ -155,40 +157,54 @@ this.pageAction = class extends Extensio
       delete this.tabContext.get(tab)[prop];
     }
 
     if (tab.selected) {
       this.updateButton(tab.ownerGlobal);
     }
   }
 
+  normalize(details, context = null) {
+    let icon = IconDetails.normalize(details, this.extension, context);
+    if (!Object.keys(icon).length) {
+      icon = null;
+    }
+    return icon;
+  }
+
   // Updates the page action button in the given window to reflect the
   // properties of the currently selected tab:
   //
   // Updates "tooltiptext" and "aria-label" to match "title" property.
   // Updates "image" to match the "icon" property.
   // Enables or disables the icon, based on the "show" and "patternMatching" properties.
   updateButton(window) {
     let tab = window.gBrowser.selectedTab;
     let tabData = this.tabContext.get(tab);
-    let title = tabData.title || this.extension.name;
-    this.browserPageAction.setTitle(title, window);
-    this.browserPageAction.setTooltip(title, window);
+    let last = this.lastValues.get(window);
 
-    // At least one of "show" or "patternMatching" must be defined here.
-    let {show = tabData.patternMatching} = tabData;
-    this.browserPageAction.setDisabled(!show, window);
+    window.requestAnimationFrame(() => {
+      let title = tabData.title || this.extension.name;
+      if (last.title !== title) {
+        this.browserPageAction.setTitle(title, window);
+        last.title = title;
+      }
 
-    let iconURL;
-    if (typeof(tabData.icon) == "string") {
-      iconURL = IconDetails.escapeUrl(tabData.icon);
-    } else {
-      iconURL = this.getIconData(tabData.icon);
-    }
-    this.browserPageAction.setIconURL(iconURL, window);
+      let show = tabData.show != null ? tabData.show : tabData.patternMatching;
+      if (last.show !== show) {
+        this.browserPageAction.setDisabled(!show, window);
+        last.show = show;
+      }
+
+      let icon = tabData.icon;
+      if (last.icon !== icon) {
+        this.browserPageAction.setIconURL(icon, window);
+        last.icon = icon;
+      }
+    });
   }
 
   // Checks whether the tab action is shown when the specified tab becomes active.
   // Does pattern matching if necessary, and caches the result as a tab-specific value.
   // @param {XULElement} tab
   //        The tab to be checked
   // @return boolean
   isShown(tab) {
@@ -202,28 +218,16 @@ this.pageAction = class extends Extensio
     // Otherwise pattern matching must have been configured. Do it, caching the result.
     if (tabData.patternMatching === undefined) {
       let uri = tab.linkedBrowser.currentURI;
       tabData.patternMatching = tabData.showMatches.matches(uri) && !tabData.hideMatches.matches(uri);
     }
     return tabData.patternMatching;
   }
 
-  getIconData(icons) {
-    let getIcon = size => {
-      let {icon} = IconDetails.getPreferredIcon(icons, this.extension, size);
-      // TODO: implement theme based icon for pageAction (Bug 1398156)
-      return IconDetails.escapeUrl(icon);
-    };
-    return {
-      "16": getIcon(16),
-      "32": getIcon(32),
-    };
-  }
-
   /**
    * Triggers this page action for the given window, with the same effects as
    * if it were clicked by a user.
    *
    * This has no effect if the page action is hidden for the selected tab.
    *
    * @param {Window} window
    */
@@ -362,20 +366,18 @@ this.pageAction = class extends Extensio
 
           let title = pageAction.getProperty(tab, "title");
           return Promise.resolve(title);
         },
 
         setIcon(details) {
           let tab = tabTracker.getTab(details.tabId);
 
-          let icon = IconDetails.normalize(details, extension, context);
-          if (!Object.keys(icon).length) {
-            icon = null;
-          }
+          let icon = pageAction.normalize(details, context);
+
           pageAction.setProperty(tab, "icon", icon);
         },
 
         setPopup(details) {
           let tab = tabTracker.getTab(details.tabId);
 
           // Note: Chrome resolves arguments to setIcon relative to the calling
           // context, but resolves arguments to setPopup relative to the extension
--- a/browser/components/extensions/test/browser/browser.ini
+++ b/browser/components/extensions/test/browser/browser.ini
@@ -1,9 +1,10 @@
 [DEFAULT]
 tags = webextensions in-process-webextensions
 
 [browser_ext_autocompletepopup.js]
 [browser_ext_legacy_extension_context_contentscript.js]
 [browser_ext_windows_allowScriptsToClose.js]
 
 [include:browser-common.ini]
+skip-if = os == 'win' # Windows WebExtensions always run OOP
 [parent:browser-common.ini]
--- a/browser/components/extensions/test/browser/browser_ext_browserAction_pageAction_icon.js
+++ b/browser/components/extensions/test/browser/browser_ext_browserAction_pageAction_icon.js
@@ -259,16 +259,17 @@ add_task(async function testDetailsObjec
   const RESOLUTION_PREF = "layout.css.devPixelsPerPx";
 
   await extension.startup();
 
   let pageActionId = BrowserPageActions.urlbarButtonNodeIDForActionID(makeWidgetId(extension.id));
   let browserActionWidget = getBrowserActionWidget(extension);
 
   let tests = await extension.awaitMessage("ready");
+  await promiseAnimationFrame();
 
   // The initial icon should be the default icon since no icon is in the manifest.
   const DEFAULT_ICON = "chrome://browser/content/extension.svg";
   let browserActionButton = browserActionWidget.forWindow(window).node;
   let pageActionImage = document.getElementById(pageActionId);
   is(getListStyleImage(browserActionButton), DEFAULT_ICON, `browser action has the correct default image`);
   is(getListStyleImage(pageActionImage), DEFAULT_ICON, `page action has the correct default image`);
 
--- a/browser/components/extensions/test/browser/browser_ext_pageAction_show_matches.js
+++ b/browser/components/extensions/test/browser/browser_ext_pageAction_show_matches.js
@@ -60,16 +60,17 @@ let urls = [
 
 function getId(tab) {
   let {Management: {global: {tabTracker}}} = ChromeUtils.import("resource://gre/modules/Extension.jsm", {});
   getId = tabTracker.getId.bind(tabTracker); // eslint-disable-line no-func-assign
   return getId(tab);
 }
 
 async function check(extension, tab, expected, msg) {
+  await promiseAnimationFrame();
   let widgetId = makeWidgetId(extension.id);
   let pageActionId = BrowserPageActions.urlbarButtonNodeIDForActionID(widgetId);
   is(gBrowser.selectedTab, tab, `tab ${tab.linkedBrowser.currentURI.spec} is selected`);
   let button = document.getElementById(pageActionId);
   // Sometimes we're hidden, sometimes a parent is hidden via css (e.g. about pages)
   let hidden = button === null || button.hidden ||
     window.getComputedStyle(button).display == "none";
   is(!hidden, expected, msg + " (computed)");
--- a/browser/components/extensions/test/xpcshell/test_ext_url_overrides_newtab.js
+++ b/browser/components/extensions/test/xpcshell/test_ext_url_overrides_newtab.js
@@ -23,17 +23,17 @@ const {
   promiseRestartManager,
   promiseShutdownManager,
   promiseStartupManager,
 } = AddonTestUtils;
 
 AddonTestUtils.init(this);
 AddonTestUtils.overrideCertDB();
 
-createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "42");
+createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "42", "42");
 
 function awaitEvent(eventName) {
   return new Promise(resolve => {
     Management.once(eventName, (e, ...args) => resolve(...args));
   });
 }
 
 const DEFAULT_NEW_TAB_URL = aboutNewTabService.newTabURL;
--- a/browser/components/extensions/test/xpcshell/test_ext_url_overrides_newtab_update.js
+++ b/browser/components/extensions/test/xpcshell/test_ext_url_overrides_newtab_update.js
@@ -27,17 +27,17 @@ const {
   promiseStartupManager,
 } = AddonTestUtils;
 
 AddonTestUtils.init(this);
 
 // Allow for unsigned addons.
 AddonTestUtils.overrideCertDB();
 
-createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "42");
+createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "42", "42");
 
 add_task(async function test_url_overrides_newtab_update() {
   const EXTENSION_ID = "test_url_overrides_update@tests.mozilla.org";
   const NEWTAB_URI = "webext-newtab-1.html";
   const PREF_EM_CHECK_UPDATE_SECURITY = "extensions.checkUpdateSecurity";
 
   const testServer = createHttpServer();
   const port = testServer.identity.primaryPort;
--- a/browser/extensions/activity-stream/bootstrap.js
+++ b/browser/extensions/activity-stream/bootstrap.js
@@ -4,16 +4,22 @@
 "use strict";
 
 ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.importGlobalProperties(["fetch"]);
 
 ChromeUtils.defineModuleGetter(this, "Services",
   "resource://gre/modules/Services.jsm");
 
+XPCOMUtils.defineLazyServiceGetter(this, "resProto",
+                                   "@mozilla.org/network/protocol;1?name=resource",
+                                   "nsISubstitutingProtocolHandler");
+
+const RESOURCE_HOST = "activity-stream";
+
 const BROWSER_READY_NOTIFICATION = "sessionstore-windows-restored";
 const RESOURCE_BASE = "resource://activity-stream";
 
 const ACTIVITY_STREAM_OPTIONS = {newTabURL: "about:newtab"};
 
 let activityStream;
 let modulesToUnload = new Set();
 let startupData;
@@ -143,31 +149,37 @@ function observe(subject, topic, data) {
   }
 }
 
 // The functions below are required by bootstrap.js
 
 this.install = function install(data, reason) {};
 
 this.startup = function startup(data, reason) {
+  resProto.setSubstitutionWithFlags(RESOURCE_HOST,
+                                    Services.io.newURI("chrome/content/", null, data.resourceURI),
+                                    resProto.ALLOW_CONTENT_ACCESS);
+
   // Cache startup data which contains stuff like the version number, etc.
   // so we can use it when we init
   startupData = data;
   startupReason = reason;
 
   // Only start Activity Stream up when the browser UI is ready
   if (Services.startup.startingUp) {
     Services.obs.addObserver(observe, BROWSER_READY_NOTIFICATION);
   } else {
     // Handle manual install or automatic install after manual uninstall
     onBrowserReady();
   }
 };
 
 this.shutdown = function shutdown(data, reason) {
+  resProto.setSubstitution(RESOURCE_HOST, null);
+
   // Uninitialize Activity Stream
   startupData = null;
   startupReason = null;
   uninit(reason);
 
   // Stop waiting for browser to be ready
   if (waitingForBrowserReady) {
     Services.obs.removeObserver(observe, BROWSER_READY_NOTIFICATION);
--- a/browser/extensions/formautofill/bootstrap.js
+++ b/browser/extensions/formautofill/bootstrap.js
@@ -15,16 +15,22 @@ ChromeUtils.import("resource://gre/modul
 ChromeUtils.defineModuleGetter(this, "AddonManager", "resource://gre/modules/AddonManager.jsm");
 ChromeUtils.defineModuleGetter(this, "AddonManagerPrivate",
                                "resource://gre/modules/AddonManager.jsm");
 ChromeUtils.defineModuleGetter(this, "formAutofillParent",
                                "resource://formautofill/FormAutofillParent.jsm");
 ChromeUtils.defineModuleGetter(this, "FormAutofillUtils",
                                "resource://formautofill/FormAutofillUtils.jsm");
 
+XPCOMUtils.defineLazyServiceGetter(this, "resProto",
+                                   "@mozilla.org/network/protocol;1?name=resource",
+                                   "nsISubstitutingProtocolHandler");
+
+const RESOURCE_HOST = "formautofill";
+
 function insertStyleSheet(domWindow, url) {
   let doc = domWindow.document;
   let styleSheetAttr = `href="${url}" type="text/css"`;
   let styleSheet = doc.createProcessingInstruction("xml-stylesheet", styleSheetAttr);
 
   doc.insertBefore(styleSheet, doc.documentElement);
 
   if (CACHED_STYLESHEETS.has(domWindow)) {
@@ -75,16 +81,19 @@ function startup(data) {
     // reset the sync related prefs incase the feature was previously available
     // but isn't now.
     Services.prefs.clearUserPref("services.sync.engine.addresses.available");
     Services.prefs.clearUserPref("services.sync.engine.creditcards.available");
     Services.telemetry.scalarSet("formautofill.availability", false);
     return;
   }
 
+  resProto.setSubstitution(RESOURCE_HOST,
+                           Services.io.newURI("chrome/res/", null, data.resourceURI));
+
   if (data.hasOwnProperty("instanceID") && data.instanceID) {
     if (AddonManagerPrivate.isDBLoaded()) {
       addUpgradeListener(data.instanceID);
     } else {
       // Wait for the extension database to be loaded so we don't cause its init.
       Services.obs.addObserver(function xpiDatabaseLoaded() {
         Services.obs.removeObserver(xpiDatabaseLoaded, "xpi-database-loaded");
         addUpgradeListener(data.instanceID);
@@ -116,16 +125,18 @@ function startup(data) {
   formAutofillParent.init().catch(Cu.reportError);
   Services.ppmm.loadProcessScript("data:,new " + function() {
     ChromeUtils.import("resource://formautofill/FormAutofillContent.jsm");
   }, true);
   Services.mm.loadFrameScript("chrome://formautofill/content/FormAutofillFrameScript.js", true);
 }
 
 function shutdown() {
+  resProto.setSubstitution(RESOURCE_HOST, null);
+
   Services.mm.removeMessageListener("FormAutoComplete:MaybeOpenPopup", onMaybeOpenPopup);
 
   let enumerator = Services.wm.getEnumerator("navigator:browser");
   while (enumerator.hasMoreElements()) {
     let win = enumerator.getNext();
     let domWindow = win.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindow);
     let cachedStyleSheets = CACHED_STYLESHEETS.get(domWindow);
 
--- a/browser/extensions/formautofill/test/unit/head.js
+++ b/browser/extensions/formautofill/test/unit/head.js
@@ -17,16 +17,20 @@ ChromeUtils.import("resource://testing-c
 ChromeUtils.import("resource://testing-common/MockDocument.jsm");
 ChromeUtils.import("resource://testing-common/TestUtils.jsm");
 
 ChromeUtils.defineModuleGetter(this, "DownloadPaths",
                                "resource://gre/modules/DownloadPaths.jsm");
 ChromeUtils.defineModuleGetter(this, "FileUtils",
                                "resource://gre/modules/FileUtils.jsm");
 
+XPCOMUtils.defineLazyServiceGetter(this, "resProto",
+                                   "@mozilla.org/network/protocol;1?name=resource",
+                                   "nsISubstitutingProtocolHandler");
+
 do_get_profile();
 
 // ================================================
 // Load mocking/stubbing library, sinon
 // docs: http://sinonjs.org/releases/v2.3.2/
 ChromeUtils.import("resource://gre/modules/Timer.jsm");
 Services.scriptloader.loadSubScript("resource://testing-common/sinon-2.3.2.js", this);
 /* globals sinon */
@@ -45,16 +49,19 @@ let bootstrapURI = Services.io.newFileUR
 if (!extensionDir.exists()) {
   extensionDir = extensionDir.parent;
   extensionDir.append(EXTENSION_ID + ".xpi");
   let jarURI = Services.io.newFileURI(extensionDir);
   bootstrapURI = "jar:" + jarURI.spec + "!/bootstrap.js";
 }
 Components.manager.addBootstrappedManifestLocation(extensionDir);
 
+let resURI = Services.io.newURI("chrome/res/", null, Services.io.newURI(bootstrapURI));
+resProto.setSubstitution("formautofill", resURI);
+
 // Returns a reference to a temporary file that is guaranteed not to exist and
 // is cleaned up later. See FileTestUtils.getTempFile for details.
 function getTempFile(leafName) {
   return FileTestUtils.getTempFile(leafName);
 }
 
 async function initProfileStorage(fileName, records, collectionName = "addresses") {
   let {FormAutofillStorage} = ChromeUtils.import("resource://formautofill/FormAutofillStorage.jsm", {});
--- a/browser/extensions/onboarding/bootstrap.js
+++ b/browser/extensions/onboarding/bootstrap.js
@@ -8,16 +8,22 @@
 ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
 XPCOMUtils.defineLazyModuleGetters(this, {
   OnboardingTourType: "resource://onboarding/modules/OnboardingTourType.jsm",
   OnboardingTelemetry: "resource://onboarding/modules/OnboardingTelemetry.jsm",
   Services: "resource://gre/modules/Services.jsm",
   UIState: "resource://services-sync/UIState.jsm",
 });
 
+XPCOMUtils.defineLazyServiceGetter(this, "resProto",
+                                   "@mozilla.org/network/protocol;1?name=resource",
+                                   "nsISubstitutingProtocolHandler");
+
+const RESOURCE_HOST = "onboarding";
+
 const {PREF_STRING, PREF_BOOL, PREF_INT} = Ci.nsIPrefBranch;
 
 const BROWSER_READY_NOTIFICATION = "browser-delayed-startup-finished";
 const BROWSER_SESSION_STORE_NOTIFICATION = "sessionstore-windows-restored";
 const PREF_WHITELIST = [
   ["browser.onboarding.enabled", PREF_BOOL],
   ["browser.onboarding.state", PREF_STRING],
   ["browser.onboarding.notification.finished", PREF_BOOL],
@@ -192,29 +198,35 @@ function observe(subject, topic, data) {
   }
 }
 
 function install(aData, aReason) {}
 
 function uninstall(aData, aReason) {}
 
 function startup(aData, aReason) {
+  resProto.setSubstitutionWithFlags(RESOURCE_HOST,
+                                    Services.io.newURI("chrome/content/", null, aData.resourceURI),
+                                    resProto.ALLOW_CONTENT_ACCESS);
+
   // Cache startup data which contains stuff like the version number, etc.
   // so we can use it when we init the telemetry
   startupData = aData;
   // Only start Onboarding when the browser UI is ready
   if (Services.startup.startingUp) {
     Services.obs.addObserver(observe, BROWSER_READY_NOTIFICATION);
     Services.obs.addObserver(observe, BROWSER_SESSION_STORE_NOTIFICATION);
   } else {
     onBrowserReady();
     syncTourChecker.init();
   }
 }
 
 function shutdown(aData, aReason) {
+  resProto.setSubstitution(RESOURCE_HOST, null);
+
   startupData = null;
   // Stop waiting for browser to be ready
   if (waitingForBrowserReady) {
     Services.obs.removeObserver(observe, BROWSER_READY_NOTIFICATION);
   }
   syncTourChecker.uninit();
 }
--- a/browser/extensions/onboarding/test/unit/head.js
+++ b/browser/extensions/onboarding/test/unit/head.js
@@ -4,29 +4,40 @@
 
 "use strict";
 
 /* global Cc, Ci, Cu */
 ChromeUtils.import("resource://gre/modules/Preferences.jsm");
 ChromeUtils.import("resource://gre/modules/Services.jsm");
 ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
 
+XPCOMUtils.defineLazyServiceGetter(this, "resProto",
+                                   "@mozilla.org/network/protocol;1?name=resource",
+                                   "nsISubstitutingProtocolHandler");
+
 // Load our bootstrap extension manifest so we can access our chrome/resource URIs.
 // Cargo culted from formautofill system add-on
 const EXTENSION_ID = "onboarding@mozilla.org";
 let extensionDir = Services.dirsvc.get("GreD", Ci.nsIFile);
 extensionDir.append("browser");
 extensionDir.append("features");
 extensionDir.append(EXTENSION_ID);
+let resourceURI;
 // If the unpacked extension doesn't exist, use the packed version.
 if (!extensionDir.exists()) {
   extensionDir.leafName += ".xpi";
+
+  resourceURI = "jar:" + Services.io.newFileURI(extensionDir).spec + "!/chrome/content/";
+} else {
+  resourceURI = Services.io.newFileURI(extensionDir).spec + "/chrome/content/";
 }
 Components.manager.addBootstrappedManifestLocation(extensionDir);
 
+resProto.setSubstitution("onboarding", Services.io.newURI(resourceURI));
+
 const TOURSET_VERSION = 1;
 const NEXT_TOURSET_VERSION = 2;
 const PREF_TOUR_TYPE = "browser.onboarding.tour-type";
 const PREF_TOURSET_VERSION = "browser.onboarding.tourset-version";
 const PREF_SEEN_TOURSET_VERSION = "browser.onboarding.seen-tourset-version";
 
 function resetOnboardingDefaultState() {
   // All the prefs should be reset to what prefs should looks like in a new user profile
--- a/browser/modules/BrowserErrorReporter.jsm
+++ b/browser/modules/BrowserErrorReporter.jsm
@@ -57,20 +57,26 @@ const PLATFORM_NAMES = {
  * The outgoing requests are designed to be compatible with Sentry. See
  * https://docs.sentry.io/clientdev/ for details on the data format that Sentry
  * expects.
  *
  * Errors may contain PII, such as in messages or local file paths in stack
  * traces; see bug 1426482 for privacy review and server-side mitigation.
  */
 class BrowserErrorReporter {
-  constructor(fetchMethod = this._defaultFetch, chromeOnly = true) {
+  constructor(options = {}) {
     // Test arguments for mocks and changing behavior
-    this.fetch = fetchMethod;
-    this.chromeOnly = chromeOnly;
+    this.fetch = options.fetch || defaultFetch;
+    this.chromeOnly = options.chromeOnly !== undefined ? options.chromeOnly : true;
+    this.registerListener = (
+      options.registerListener || (() => Services.console.registerListener(this))
+    );
+    this.unregisterListener = (
+      options.unregisterListener || (() => Services.console.unregisterListener(this))
+    );
 
     // Values that don't change between error reports.
     this.requestBodyTemplate = {
       logger: "javascript",
       platform: "javascript",
       release: Services.appinfo.version,
       environment: UpdateUtils.getUpdateChannel(false),
       contexts: {
@@ -115,38 +121,38 @@ class BrowserErrorReporter {
     logger.manageLevelFromPref(PREF_LOG_LEVEL);
 
     Object.defineProperty(this, "logger", {value: logger});
     return this.logger;
   }
 
   init() {
     if (this.collectionEnabled) {
-      Services.console.registerListener(this);
+      this.registerListener();
 
       // Processing already-logged messages in case any errors occurred before
       // startup.
       for (const message of Services.console.getMessageArray()) {
         this.observe(message);
       }
     }
   }
 
   uninit() {
     try {
-      Services.console.unregisterListener(this);
+      this.unregisterListener();
     } catch (err) {} // It probably wasn't registered.
   }
 
   handleEnabledPrefChanged(prefName, previousValue, newValue) {
     if (newValue) {
-      Services.console.registerListener(this);
+      this.registerListener();
     } else {
       try {
-        Services.console.unregisterListener(this);
+        this.unregisterListener();
       } catch (err) {} // It probably wasn't registered.
     }
   }
 
   async observe(message) {
     try {
       message.QueryInterface(Ci.nsIScriptError);
     } catch (err) {
@@ -160,70 +166,37 @@ class BrowserErrorReporter {
     }
 
     // Sample the amount of errors we send out
     const sampleRate = Number.parseFloat(Services.prefs.getCharPref(PREF_SAMPLE_RATE));
     if (!Number.isFinite(sampleRate) || (Math.random() >= sampleRate)) {
       return;
     }
 
-    const extensions = new Map();
-    for (let extension of WebExtensionPolicy.getActiveExtensions()) {
-      extensions.set(extension.mozExtensionHostname, extension);
-    }
-
-    // Replaces any instances of moz-extension:// URLs with internal UUIDs to use
-    // the add-on ID instead.
-    function mangleExtURL(string, anchored = true) {
-      let re = new RegExp(`${anchored ? "^" : ""}moz-extension://([^/]+)/`, "g");
-
-      return string.replace(re, (m0, m1) => {
-        let id = extensions.has(m1) ? extensions.get(m1).id : m1;
-        return `moz-extension://${id}/`;
-      });
-    }
-
-    // Parse the error type from the message if present (e.g. "TypeError: Whoops").
-    let errorMessage = message.errorMessage;
-    let errorName = "Error";
-    if (message.errorMessage.match(ERROR_PREFIX_RE)) {
-      const parts = message.errorMessage.split(":");
-      errorName = parts[0];
-      errorMessage = parts.slice(1).join(":").trim();
-    }
-
-    const frames = [];
-    let frame = message.stack;
-    // Avoid an infinite loop by limiting traces to 100 frames.
-    while (frame && frames.length < 100) {
-      const normalizedFrame = await this.normalizeStackFrame(frame);
-      normalizedFrame.module = mangleExtURL(normalizedFrame.module, false);
-      frames.push(normalizedFrame);
-      frame = frame.parent;
-    }
-    // Frames are sent in order from oldest to newest.
-    frames.reverse();
-
-    const requestBody = Object.assign({}, this.requestBodyTemplate, {
+    const exceptionValue = {};
+    const requestBody = {
+      ...this.requestBodyTemplate,
       timestamp: new Date().toISOString().slice(0, -1), // Remove trailing "Z"
       project: Services.prefs.getCharPref(PREF_PROJECT_ID),
       exception: {
-        values: [
-          {
-            type: errorName,
-            value: mangleExtURL(errorMessage),
-            module: message.sourceName,
-            stacktrace: {
-              frames,
-            }
-          },
-        ],
+        values: [exceptionValue],
       },
-      culprit: message.sourceName,
-    });
+      tags: {},
+    };
+
+    const transforms = [
+      addErrorMessage,
+      addStacktrace,
+      addModule,
+      mangleExtensionUrls,
+      tagExtensionErrors,
+    ];
+    for (const transform of transforms) {
+      await transform(message, exceptionValue, requestBody);
+    }
 
     const url = new URL(Services.prefs.getCharPref(PREF_SUBMIT_URL));
     url.searchParams.set("sentry_client", `${SDK_NAME}/${SDK_VERSION}`);
     url.searchParams.set("sentry_version", "7");
     url.searchParams.set("sentry_key", Services.prefs.getCharPref(PREF_PUBLIC_KEY));
 
     try {
       await this.fetch(url, {
@@ -236,18 +209,46 @@ class BrowserErrorReporter {
         referrer: "https://fake.mozilla.org",
         body: JSON.stringify(requestBody)
       });
       this.logger.debug("Sent error successfully.");
     } catch (error) {
       this.logger.warn(`Failed to send error: ${error}`);
     }
   }
+}
 
-  async normalizeStackFrame(frame) {
+function defaultFetch(...args) {
+  // Do not make network requests while running in automation
+  if (Cu.isInAutomation) {
+    return null;
+  }
+
+  return fetch(...args);
+}
+
+function addErrorMessage(message, exceptionValue) {
+  // Parse the error type from the message if present (e.g. "TypeError: Whoops").
+  let errorMessage = message.errorMessage;
+  let errorName = "Error";
+  if (message.errorMessage.match(ERROR_PREFIX_RE)) {
+    const parts = message.errorMessage.split(":");
+    errorName = parts[0];
+    errorMessage = parts.slice(1).join(":").trim();
+  }
+
+  exceptionValue.type = errorName;
+  exceptionValue.value = errorMessage;
+}
+
+async function addStacktrace(message, exceptionValue) {
+  const frames = [];
+  let frame = message.stack;
+  // Avoid an infinite loop by limiting traces to 100 frames.
+  while (frame && frames.length < 100) {
     const normalizedFrame = {
       function: frame.functionDisplayName,
       module: frame.source,
       lineno: frame.line,
       colno: frame.column,
     };
 
     try {
@@ -272,20 +273,54 @@ class BrowserErrorReporter {
         lineIndex + 1,
         Math.min(lineIndex + 1 + CONTEXT_LINES, sourceLines.length),
       );
     } catch (err) {
       // Could be a fetch issue, could be a line index issue. Not much we can
       // do to recover in either case.
     }
 
-    return normalizedFrame;
+    frames.push(normalizedFrame);
+    frame = frame.parent;
+  }
+  // Frames are sent in order from oldest to newest.
+  frames.reverse();
+
+  exceptionValue.stacktrace = {frames};
+}
+
+function addModule(message, exceptionValue) {
+  exceptionValue.module = message.sourceName;
+}
+
+function mangleExtensionUrls(message, exceptionValue) {
+  const extensions = new Map();
+  for (let extension of WebExtensionPolicy.getActiveExtensions()) {
+    extensions.set(extension.mozExtensionHostname, extension);
   }
 
-  async _defaultFetch(...args) {
-    // Do not make network requests while running in automation
-    if (Cu.isInAutomation) {
-      return null;
+  // Replaces any instances of moz-extension:// URLs with internal UUIDs to use
+  // the add-on ID instead.
+  function mangleExtURL(string, anchored = true) {
+    if (!string) {
+      return string;
     }
 
-    return fetch(...args);
+    let re = new RegExp(`${anchored ? "^" : ""}moz-extension://([^/]+)/`, "g");
+
+    return string.replace(re, (m0, m1) => {
+      let id = extensions.has(m1) ? extensions.get(m1).id : m1;
+      return `moz-extension://${id}/`;
+    });
+  }
+
+  exceptionValue.value = mangleExtURL(exceptionValue.value, false);
+  exceptionValue.module = mangleExtURL(exceptionValue.module);
+  for (const frame of exceptionValue.stacktrace.frames) {
+    frame.module = mangleExtURL(frame.module);
   }
 }
+
+function tagExtensionErrors(message, exceptionValue, requestBody) {
+  if (exceptionValue.module && exceptionValue.module.startsWith("moz-extension://")) {
+    requestBody.tags.isExtensionError = true;
+  }
+}
--- a/browser/modules/PageActions.jsm
+++ b/browser/modules/PageActions.jsm
@@ -27,16 +27,21 @@ ChromeUtils.defineModuleGetter(this, "Bi
 
 const ACTION_ID_BOOKMARK = "bookmark";
 const ACTION_ID_BOOKMARK_SEPARATOR = "bookmarkSeparator";
 const ACTION_ID_BUILT_IN_SEPARATOR = "builtInSeparator";
 
 const PREF_PERSISTED_ACTIONS = "browser.pageActions.persistedActions";
 const PERSISTED_ACTIONS_CURRENT_VERSION = 1;
 
+// Escapes the given raw URL string, and returns an equivalent CSS url()
+// value for it.
+function escapeCSSURL(url) {
+  return `url("${url.replace(/[\\\s"]/g, encodeURIComponent)}")`;
+}
 
 var PageActions = {
   /**
    * Inits.  Call to init.
    */
   init() {
     let callbacks = this._deferredAddActionCalls;
     delete this._deferredAddActionCalls;
@@ -612,16 +617,39 @@ function Action(options) {
     // (either the auto-generated ID or urlbarIDOverride).  That node will be
     // shown when the action is added to the urlbar and hidden when the action
     // is removed from the urlbar.
     _urlbarNodeInMarkup: false,
   });
   if (this._subview) {
     this._subview = new Subview(options.subview);
   }
+
+  /**
+   * A cache of the pre-computed CSS variable values for a given icon
+   * URLs object, as passed to _createIconProperties.
+   */
+  this._iconProperties = new WeakMap();
+
+  /**
+   * The global values for the action properties.
+   */
+  this._globalProps = {
+    disabled: this._disabled,
+    iconURL: this._iconURL,
+    iconProps: this._createIconProperties(this._iconURL),
+    title: this._title,
+    tooltip: this._tooltip,
+  };
+
+  /**
+   * A mapping of window-specific action property objects, each of which
+   * derives from the _globalProps object.
+   */
+  this._windowProps = new WeakMap();
 }
 
 Action.prototype = {
   /**
    * The ID of the action's parent extension (string, nullable)
    */
   get extensionID() {
     return this._extensionID;
@@ -656,48 +684,80 @@ Action.prototype = {
     }
     return this.pinnedToUrlbar;
   },
 
   /**
    * The action's disabled state (bool, nonnull)
    */
   getDisabled(browserWindow = null) {
-    return !!this._getProperty("disabled", browserWindow);
+    return !!this._getProperties(browserWindow).disabled;
   },
   setDisabled(value, browserWindow = null) {
     return this._setProperty("disabled", !!value, browserWindow);
   },
 
   /**
    * The action's icon URL string, or an object mapping sizes to URL strings
    * (string or object, nullable)
    */
   getIconURL(browserWindow = null) {
-    return this._getProperty("iconURL", browserWindow);
+    return this._getProperties(browserWindow).iconURL;
   },
   setIconURL(value, browserWindow = null) {
-    return this._setProperty("iconURL", value, browserWindow);
+    let props = this._getProperties(browserWindow, !!browserWindow);
+    props.iconURL = value;
+    props.iconProps = this._createIconProperties(value);
+
+    this._updateProperty("iconURL", props.iconProps, browserWindow);
+    return value;
+  },
+
+  /**
+   * The set of CSS variables which define the action's icons in various
+   * sizes. This is generated automatically from the iconURL property.
+   */
+  getIconProperties(browserWindow = null) {
+    return this._getProperties(browserWindow).iconProps;
+  },
+
+  _createIconProperties(urls) {
+    if (urls && typeof urls == "object") {
+      let props = this._iconProperties.get(urls);
+      if (!props) {
+        props = Object.freeze({
+          "--pageAction-image-16px": escapeCSSURL(this._iconURLForSize(urls, 16)),
+          "--pageAction-image-32px": escapeCSSURL(this._iconURLForSize(urls, 32)),
+        });
+        this._iconProperties.set(urls, props);
+      }
+      return props;
+    }
+
+    return Object.freeze({
+      "--pageAction-image-16px": null,
+      "--pageAction-image-32px": urls ? escapeCSSURL(urls) : null,
+    });
   },
 
   /**
    * The action's title (string, nonnull)
    */
   getTitle(browserWindow = null) {
-    return this._getProperty("title", browserWindow);
+    return this._getProperties(browserWindow).title;
   },
   setTitle(value, browserWindow = null) {
     return this._setProperty("title", value, browserWindow);
   },
 
   /**
    * The action's tooltip (string, nullable)
    */
   getTooltip(browserWindow = null) {
-    return this._getProperty("tooltip", browserWindow);
+    return this._getProperties(browserWindow).tooltip;
   },
   setTooltip(value, browserWindow = null) {
     return this._setProperty("tooltip", value, browserWindow);
   },
 
   /**
    * Sets a property, optionally for a particular browser window.
    *
@@ -705,66 +765,55 @@ Action.prototype = {
    *         The (non-underscored) name of the property.
    * @param  value
    *         The value.
    * @param  browserWindow (DOM window, optional)
    *         If given, then the property will be set in this window's state, not
    *         globally.
    */
   _setProperty(name, value, browserWindow) {
-    if (!browserWindow) {
-      // Set the global state.
-      this[`_${name}`] = value;
-    } else {
-      // Set the per-window state.
-      let props = this._propertiesByBrowserWindow.get(browserWindow);
-      if (!props) {
-        props = {};
-        this._propertiesByBrowserWindow.set(browserWindow, props);
-      }
-      props[name] = value;
-    }
+    let props = this._getProperties(browserWindow, !!browserWindow);
+    props[name] = value;
+
+    this._updateProperty(name, value, browserWindow);
+    return value;
+  },
+
+  _updateProperty(name, value, browserWindow) {
     // This may be called before the action has been added.
     if (PageActions.actionForID(this.id)) {
       for (let bpa of allBrowserPageActions(browserWindow)) {
-        bpa.updateAction(this, name);
+        bpa.updateAction(this, name, value);
       }
     }
-    return value;
   },
 
   /**
-   * Gets a property, optionally for a particular browser window.
+   * Returns the properties object for the given window, if it exists,
+   * or the global properties object if no window-specific properties
+   * exist.
    *
-   * @param  name (string, required)
-   *         The (non-underscored) name of the property.
-   * @param  browserWindow (DOM window, optional)
-   *         If given, then the property will be fetched from this window's
-   *         state.  If the property does not exist in the window's state, or if
-   *         no window is given, then the global value is returned.
-   * @return The property value.
+   * @param {Window?} window
+   *        The window for which to return the properties object, or
+   *        null to return the global properties object.
+   * @param {bool} [forceWindowSpecific = false]
+   *        If true, always returns a window-specific properties object.
+   *        If a properties object does not exist for the given window,
+   *        one is created and cached.
+   * @returns {object}
    */
-  _getProperty(name, browserWindow) {
-    if (browserWindow) {
-      // Try the per-window state.
-      let props = this._propertiesByBrowserWindow.get(browserWindow);
-      if (props && name in props) {
-        return props[name];
-      }
+  _getProperties(window, forceWindowSpecific = false) {
+    let props = window && this._windowProps.get(window);
+
+    if (!props && forceWindowSpecific) {
+      props = Object.create(this._globalProps);
+      this._windowProps.set(window, props);
     }
-    // Fall back to the global state.
-    return this[`_${name}`];
-  },
 
-  // maps browser windows => object with properties for that window
-  get _propertiesByBrowserWindow() {
-    if (!this.__propertiesByBrowserWindow) {
-      this.__propertiesByBrowserWindow = new WeakMap();
-    }
-    return this.__propertiesByBrowserWindow;
+    return props || this._globalProps;
   },
 
   /**
    * Override for the ID of the action's activated-action panel anchor (string,
    * nullable)
    */
   get anchorIDOverride() {
     return this._anchorIDOverride;
@@ -808,36 +857,44 @@ Action.prototype = {
     let iconURL = this.getIconURL(browserWindow);
     if (!iconURL) {
       return null;
     }
     if (typeof(iconURL) == "string") {
       return iconURL;
     }
     if (typeof(iconURL) == "object") {
-      // This case is copied from ExtensionParent.jsm so that our image logic is
-      // the same, so that WebExtensions page action tests that deal with icons
-      // pass.
-      let bestSize = null;
-      if (iconURL[preferredSize]) {
-        bestSize = preferredSize;
-      } else if (iconURL[2 * preferredSize]) {
-        bestSize = 2 * preferredSize;
-      } else {
-        let sizes = Object.keys(iconURL)
-                          .map(key => parseInt(key, 10))
-                          .sort((a, b) => a - b);
-        bestSize = sizes.find(candidate => candidate > preferredSize) || sizes.pop();
-      }
-      return iconURL[bestSize];
+      return this._iconURLForSize(iconURL, preferredSize);
     }
     return null;
   },
 
   /**
+   * Selects the best matching icon from the given URLs object for the
+   * given preferred size, as described in {@see iconURLForSize}.
+   */
+  _iconURLForSize(urls, preferredSize) {
+    // This case is copied from ExtensionParent.jsm so that our image logic is
+    // the same, so that WebExtensions page action tests that deal with icons
+    // pass.
+    let bestSize = null;
+    if (urls[preferredSize]) {
+      bestSize = preferredSize;
+    } else if (urls[2 * preferredSize]) {
+      bestSize = 2 * preferredSize;
+    } else {
+      let sizes = Object.keys(urls)
+                        .map(key => parseInt(key, 10))
+                        .sort((a, b) => a - b);
+      bestSize = sizes.find(candidate => candidate > preferredSize) || sizes.pop();
+    }
+    return urls[bestSize];
+  },
+
+  /**
    * Performs the command for an action.  If the action has an onCommand
    * handler, then it's called.  If the action has a subview or iframe, then a
    * panel is opened, displaying the subview or iframe.
    *
    * @param  browserWindow (DOM window, required)
    *         The browser window in which to perform the action.
    */
   doCommand(browserWindow) {
--- a/browser/modules/test/browser/browser_BrowserErrorReporter.js
+++ b/browser/modules/test/browser/browser_BrowserErrorReporter.js
@@ -11,69 +11,48 @@ registerCleanupFunction(function() {
   delete window.sinon;
 });
 
 const PREF_ENABLED = "browser.chrome.errorReporter.enabled";
 const PREF_PROJECT_ID = "browser.chrome.errorReporter.projectId";
 const PREF_PUBLIC_KEY = "browser.chrome.errorReporter.publicKey";
 const PREF_SAMPLE_RATE = "browser.chrome.errorReporter.sampleRate";
 const PREF_SUBMIT_URL = "browser.chrome.errorReporter.submitUrl";
+const TELEMETRY_ERROR_COLLECTED = "browser.errors.collected_count";
+const TELEMETRY_ERROR_COLLECTED_FILENAME = "browser.errors.collected_count_by_filename";
+const TELEMETRY_ERROR_COLLECTED_STACK = "browser.errors.collected_with_stack_count";
+const TELEMETRY_ERROR_REPORTED = "browser.errors.reported_success_count";
+const TELEMETRY_ERROR_REPORTED_FAIL = "browser.errors.reported_failure_count";
+const TELEMETRY_ERROR_SAMPLE_RATE = "browser.errors.sample_rate";
 
 function createScriptError(options = {}) {
   const scriptError = Cc["@mozilla.org/scripterror;1"].createInstance(Ci.nsIScriptError);
   scriptError.init(
     options.message || "",
     options.sourceName || null,
     options.sourceLine || null,
     options.lineNumber || null,
     options.columnNumber || null,
     options.flags || Ci.nsIScriptError.errorFlag,
     options.category || "chrome javascript",
   );
   return scriptError;
 }
 
-// Wrapper around Services.console.logMessage that waits for the message to be
-// logged before resolving, since messages are logged asynchronously.
-function logMessage(message) {
-  return new Promise(resolve => {
-    Services.console.registerListener({
-      observe(loggedMessage) {
-        if (loggedMessage.message === message.message) {
-          Services.console.unregisterListener(this);
-          resolve();
-        }
-      },
-    });
-    Services.console.logMessage(message);
-  });
+function noop() {
+  // Do nothing
 }
 
 // Clears the console of any previous messages. Should be called at the end of
 // each test that logs to the console.
 function resetConsole() {
   Services.console.logStringMessage("");
   Services.console.reset();
 }
 
-// Wrapper similar to logMessage, but for logStringMessage.
-function logStringMessage(message) {
-  return new Promise(resolve => {
-    Services.console.registerListener({
-      observe(loggedMessage) {
-        if (loggedMessage.message === message) {
-          Services.console.unregisterListener(this);
-          resolve();
-        }
-      },
-    });
-    Services.console.logStringMessage(message);
-  });
-}
-
 // Finds the fetch spy call for an error with a matching message.
 function fetchCallForMessage(fetchSpy, message) {
   for (const call of fetchSpy.getCalls()) {
     const body = JSON.parse(call.args[1].body);
     if (body.exception.values[0].value.includes(message)) {
       return call;
     }
   }
@@ -83,256 +62,235 @@ function fetchCallForMessage(fetchSpy, m
 
 // Helper to test if a fetch spy was called with the given error message.
 // Used in tests where unrelated JS errors from other code are logged.
 function fetchPassedError(fetchSpy, message) {
   return fetchCallForMessage(fetchSpy, message) !== null;
 }
 
 add_task(async function testInitPrefDisabled() {
-  const fetchSpy = sinon.spy();
-  const reporter = new BrowserErrorReporter(fetchSpy);
+  let listening = false;
+  const reporter = new BrowserErrorReporter({
+    registerListener() {
+      listening = true;
+    },
+  });
   await SpecialPowers.pushPrefEnv({set: [
     [PREF_ENABLED, false],
-    [PREF_SAMPLE_RATE, "1.0"],
   ]});
 
   reporter.init();
-  await logMessage(createScriptError({message: "Logged while disabled"}));
-  ok(
-    !fetchPassedError(fetchSpy, "Logged while disabled"),
-    "Reporter does not listen for errors if the enabled pref is false.",
-  );
-  reporter.uninit();
-  resetConsole();
+  ok(!listening, "Reporter does not listen for errors if the enabled pref is false.");
 });
 
 add_task(async function testInitUninitPrefEnabled() {
-  const fetchSpy = sinon.spy();
-  const reporter = new BrowserErrorReporter(fetchSpy);
+  let listening = false;
+  const reporter = new BrowserErrorReporter({
+    registerListener() {
+      listening = true;
+    },
+    unregisterListener() {
+      listening = false;
+    },
+  });
   await SpecialPowers.pushPrefEnv({set: [
     [PREF_ENABLED, true],
-    [PREF_SAMPLE_RATE, "1.0"],
   ]});
 
   reporter.init();
-  await logMessage(createScriptError({message: "Logged after init"}));
-  ok(
-    fetchPassedError(fetchSpy, "Logged after init"),
-    "Reporter listens for errors if the enabled pref is true.",
-  );
+  ok(listening, "Reporter listens for errors if the enabled pref is true.");
 
-  fetchSpy.reset();
-  ok(!fetchSpy.called, "Fetch spy was reset.");
   reporter.uninit();
-  await logMessage(createScriptError({message: "Logged after uninit"}));
-  ok(
-    !fetchPassedError(fetchSpy, "Logged after uninit"),
-    "Reporter does not listen for errors after uninit.",
-  );
-
-  resetConsole();
+  ok(!listening, "Reporter does not listen for errors after uninit.");
 });
 
 add_task(async function testInitPastMessages() {
   const fetchSpy = sinon.spy();
-  const reporter = new BrowserErrorReporter(fetchSpy);
+  const reporter = new BrowserErrorReporter({
+    fetch: fetchSpy,
+    registerListener: noop,
+    unregisterListener: noop,
+  });
   await SpecialPowers.pushPrefEnv({set: [
     [PREF_ENABLED, true],
     [PREF_SAMPLE_RATE, "1.0"],
   ]});
 
-  await logMessage(createScriptError({message: "Logged before init"}));
+  resetConsole();
+  Services.console.logMessage(createScriptError({message: "Logged before init"}));
   reporter.init();
-  ok(
-    fetchPassedError(fetchSpy, "Logged before init"),
-    "Reporter collects errors logged before initialization.",
+
+  // Include ok() to satisfy mochitest warning for test without any assertions
+  const errorWasLogged = await TestUtils.waitForCondition(
+    () => fetchPassedError(fetchSpy, "Logged before init"),
+    "Waiting for message to be logged",
   );
-  reporter.uninit();
-  resetConsole();
+  ok(errorWasLogged, "Reporter collects errors logged before initialization.");
+
 });
 
 add_task(async function testEnabledPrefWatcher() {
-  const fetchSpy = sinon.spy();
-  const reporter = new BrowserErrorReporter(fetchSpy);
+  let listening = false;
+  const reporter = new BrowserErrorReporter({
+    registerListener() {
+      listening = true;
+    },
+    unregisterListener() {
+      listening = false;
+    },
+  });
   await SpecialPowers.pushPrefEnv({set: [
     [PREF_ENABLED, false],
-    [PREF_SAMPLE_RATE, "1.0"],
   ]});
 
   reporter.init();
-  await logMessage(createScriptError({message: "Shouldn't report"}));
-  ok(
-    !fetchPassedError(fetchSpy, "Shouldn't report"),
-    "Reporter does not collect errors if the enable pref is false.",
-  );
+  ok(!listening, "Reporter does not collect errors if the enable pref is false.");
 
+  Services.console.logMessage(createScriptError({message: "Shouldn't report"}));
   await SpecialPowers.pushPrefEnv({set: [
     [PREF_ENABLED, true],
   ]});
-  ok(
-    !fetchPassedError(fetchSpy, "Shouldn't report"),
-    "Reporter does not collect past-logged errors if it is enabled mid-run.",
-  );
-  await logMessage(createScriptError({message: "Should report"}));
-  ok(
-    fetchPassedError(fetchSpy, "Should report"),
-    "Reporter collects errors logged after the enabled pref is turned on mid-run",
-  );
-
-  reporter.uninit();
-  resetConsole();
+  ok(listening, "Reporter collects errors if the enabled pref switches to true.");
 });
 
 add_task(async function testNonErrorLogs() {
   const fetchSpy = sinon.spy();
-  const reporter = new BrowserErrorReporter(fetchSpy);
+  const reporter = new BrowserErrorReporter({fetch: fetchSpy});
   await SpecialPowers.pushPrefEnv({set: [
     [PREF_ENABLED, true],
     [PREF_SAMPLE_RATE, "1.0"],
   ]});
 
-  reporter.init();
-
-  await logStringMessage("Not a scripterror instance.");
+  reporter.observe({message: "Not a scripterror instance."});
   ok(
     !fetchPassedError(fetchSpy, "Not a scripterror instance."),
     "Reporter does not collect normal log messages or warnings.",
   );
 
-  await logMessage(createScriptError({
+  await reporter.observe(createScriptError({
     message: "Warning message",
     flags: Ci.nsIScriptError.warningFlag,
   }));
   ok(
     !fetchPassedError(fetchSpy, "Warning message"),
     "Reporter does not collect normal log messages or warnings.",
   );
 
-  await logMessage(createScriptError({
+  await reporter.observe(createScriptError({
     message: "Non-chrome category",
     category: "totally from a website",
   }));
   ok(
     !fetchPassedError(fetchSpy, "Non-chrome category"),
     "Reporter does not collect normal log messages or warnings.",
   );
 
-  await logMessage(createScriptError({message: "Is error"}));
+  await reporter.observe(createScriptError({message: "Is error"}));
   ok(
     fetchPassedError(fetchSpy, "Is error"),
     "Reporter collects error messages.",
   );
-
-  reporter.uninit();
-  resetConsole();
 });
 
 add_task(async function testSampling() {
   const fetchSpy = sinon.spy();
-  const reporter = new BrowserErrorReporter(fetchSpy);
+  const reporter = new BrowserErrorReporter({fetch: fetchSpy});
   await SpecialPowers.pushPrefEnv({set: [
     [PREF_ENABLED, true],
     [PREF_SAMPLE_RATE, "1.0"],
   ]});
 
-  reporter.init();
-  await logMessage(createScriptError({message: "Should log"}));
+  await reporter.observe(createScriptError({message: "Should log"}));
   ok(
     fetchPassedError(fetchSpy, "Should log"),
     "A 1.0 sample rate will cause the reporter to always collect errors.",
   );
 
   await SpecialPowers.pushPrefEnv({set: [
     [PREF_SAMPLE_RATE, "0.0"],
   ]});
-  await logMessage(createScriptError({message: "Shouldn't log"}));
+  await reporter.observe(createScriptError({message: "Shouldn't log"}));
   ok(
     !fetchPassedError(fetchSpy, "Shouldn't log"),
     "A 0.0 sample rate will cause the reporter to never collect errors.",
   );
 
   await SpecialPowers.pushPrefEnv({set: [
     [PREF_SAMPLE_RATE, ")fasdf"],
   ]});
-  await logMessage(createScriptError({message: "Also shouldn't log"}));
+  await reporter.observe(createScriptError({message: "Also shouldn't log"}));
   ok(
     !fetchPassedError(fetchSpy, "Also shouldn't log"),
     "An invalid sample rate will cause the reporter to never collect errors.",
   );
-
-  reporter.uninit();
-  resetConsole();
 });
 
 add_task(async function testNameMessage() {
   const fetchSpy = sinon.spy();
-  const reporter = new BrowserErrorReporter(fetchSpy);
+  const reporter = new BrowserErrorReporter({fetch: fetchSpy});
   await SpecialPowers.pushPrefEnv({set: [
     [PREF_ENABLED, true],
     [PREF_SAMPLE_RATE, "1.0"],
   ]});
 
-  reporter.init();
-  await logMessage(createScriptError({message: "No name"}));
+  await reporter.observe(createScriptError({message: "No name"}));
   let call = fetchCallForMessage(fetchSpy, "No name");
   let body = JSON.parse(call.args[1].body);
   is(
     body.exception.values[0].type,
     "Error",
     "Reporter uses a generic type when no name is in the message.",
   );
   is(
     body.exception.values[0].value,
     "No name",
     "Reporter uses error message as the exception value.",
   );
 
-  await logMessage(createScriptError({message: "FooError: Has name"}));
+  await reporter.observe(createScriptError({message: "FooError: Has name"}));
   call = fetchCallForMessage(fetchSpy, "Has name");
   body = JSON.parse(call.args[1].body);
   is(
     body.exception.values[0].type,
     "FooError",
     "Reporter uses the error type from the message.",
   );
   is(
     body.exception.values[0].value,
     "Has name",
     "Reporter uses error message as the value parameter.",
   );
 
-  await logMessage(createScriptError({message: "FooError: Has :extra: colons"}));
+  await reporter.observe(createScriptError({message: "FooError: Has :extra: colons"}));
   call = fetchCallForMessage(fetchSpy, "Has :extra: colons");
   body = JSON.parse(call.args[1].body);
   is(
     body.exception.values[0].type,
     "FooError",
     "Reporter uses the error type from the message.",
   );
   is(
     body.exception.values[0].value,
     "Has :extra: colons",
     "Reporter uses error message as the value parameter.",
   );
-  reporter.uninit();
-  resetConsole();
 });
 
 add_task(async function testFetchArguments() {
   const fetchSpy = sinon.spy();
-  const reporter = new BrowserErrorReporter(fetchSpy);
+  const reporter = new BrowserErrorReporter({fetch: fetchSpy});
   await SpecialPowers.pushPrefEnv({set: [
     [PREF_ENABLED, true],
     [PREF_SAMPLE_RATE, "1.0"],
     [PREF_PROJECT_ID, "123"],
     [PREF_PUBLIC_KEY, "foobar"],
     [PREF_SUBMIT_URL, "https://errors.example.com/api/123/store/"],
   ]});
 
+  resetConsole();
   reporter.init();
   const testPageUrl = (
     "chrome://mochitests/content/browser/browser/modules/test/browser/" +
     "browser_BrowserErrorReporter.html"
   );
 
   SimpleTest.expectUncaughtException();
   await BrowserTestUtils.withNewTab(testPageUrl, async () => {
@@ -400,28 +358,28 @@ add_task(async function testFetchArgumen
           },
         ],
       },
       "Reporter builds stack trace from scriptError correctly.",
     );
   });
 
   reporter.uninit();
-  resetConsole();
 });
 
 add_task(async function testAddonIDMangle() {
   const fetchSpy = sinon.spy();
   // Passing false here disables category checks on errors, which would
   // otherwise block errors directly from extensions.
-  const reporter = new BrowserErrorReporter(fetchSpy, false);
+  const reporter = new BrowserErrorReporter({fetch: fetchSpy, chromeOnly: false});
   await SpecialPowers.pushPrefEnv({set: [
     [PREF_ENABLED, true],
     [PREF_SAMPLE_RATE, "1.0"],
   ]});
+  resetConsole();
   reporter.init();
 
   // Create and install test add-on
   const id = "browsererrorcollection@example.com";
   const extension = ExtensionTestUtils.loadExtension({
     manifest: {
       applications: {
         gecko: { id },
@@ -442,10 +400,52 @@ add_task(async function testAddonIDMangl
   const stackFrame = body.exception.values[0].stacktrace.frames[0];
   ok(
     stackFrame.module.startsWith(`moz-extension://${id}/`),
     "Stack frame filenames use the proper add-on ID instead of internal UUIDs.",
   );
 
   await extension.unload();
   reporter.uninit();
+});
+
+add_task(async function testExtensionTag() {
+  const fetchSpy = sinon.spy();
+  // Passing false here disables category checks on errors, which would
+  // otherwise block errors directly from extensions.
+  const reporter = new BrowserErrorReporter({fetch: fetchSpy, chromeOnly: false});
+  await SpecialPowers.pushPrefEnv({set: [
+    [PREF_ENABLED, true],
+    [PREF_SAMPLE_RATE, "1.0"],
+  ]});
   resetConsole();
+  reporter.init();
+
+  // Create and install test add-on
+  const id = "browsererrorcollection@example.com";
+  const extension = ExtensionTestUtils.loadExtension({
+    manifest: {
+      applications: {
+        gecko: { id },
+      },
+    },
+    background() {
+      throw new Error("testExtensionTag error");
+    },
+  });
+  await extension.startup();
+
+  // Just in case the error hasn't been thrown before add-on startup.
+  let call = await TestUtils.waitForCondition(
+    () => fetchCallForMessage(fetchSpy, "testExtensionTag error"),
+    `Wait for error from ${id} to be logged`,
+  );
+  let body = JSON.parse(call.args[1].body);
+  ok(body.tags.isExtensionError, "Errors from extensions have an isExtensionError tag.");
+
+  await extension.unload();
+  reporter.uninit();
+
+  await reporter.observe(createScriptError({message: "testExtensionTag not from extension"}));
+  call = fetchCallForMessage(fetchSpy, "testExtensionTag not from extension");
+  body = JSON.parse(call.args[1].body);
+  is(body.tags.isExtensionError, undefined, "Normal errors do not have an isExtensionError tag.");
 });
--- a/build/autoconf/icu.m4
+++ b/build/autoconf/icu.m4
@@ -75,26 +75,17 @@ if test -n "$USE_ICU"; then
     fi
     MOZ_ICU_VERSION="$version"
 
     # TODO: the l is actually endian-dependent
     # We could make this set as 'l' or 'b' for little or big, respectively,
     # but we'd need to check in a big-endian version of the file.
     ICU_DATA_FILE="icudt${version}l.dat"
 
-    dnl We won't build ICU data as a separate file when building
-    dnl JS standalone so that embedders don't have to deal with it.
-    dnl We also don't do it on Windows because sometimes the file goes
-    dnl missing -- possibly due to overzealous antivirus software? --
-    dnl which prevents the browser from starting up :(
-    if test -z "$JS_STANDALONE" -a -z "$MOZ_SYSTEM_ICU" -a "$OS_TARGET" != WINNT -a "$MOZ_WIDGET_TOOLKIT" != "android"; then
-        MOZ_ICU_DATA_ARCHIVE=1
-    else
-        MOZ_ICU_DATA_ARCHIVE=
-    fi
+    MOZ_ICU_DATA_ARCHIVE=
 fi
 
 AC_SUBST(MOZ_ICU_VERSION)
 AC_SUBST(ENABLE_INTL_API)
 AC_SUBST(USE_ICU)
 AC_SUBST(ICU_DATA_FILE)
 AC_SUBST(MOZ_ICU_DATA_ARCHIVE)
 
--- a/devtools/client/jsonview/components/JsonPanel.js
+++ b/devtools/client/jsonview/components/JsonPanel.js
@@ -83,17 +83,24 @@ define(function(require, exports, module
       if (isObject(member.value) && member.hasChildren && member.open) {
         return null;
       }
 
       // Render the value (summary) using Reps library.
       return Rep(Object.assign({}, props, {
         cropLimit: 50,
         noGrip: true,
-        openLink: url => window.open(url, "_blank"),
+        openLink(str) {
+          try {
+            let u = new URL(str);
+            if (u.protocol == "https:" || u.protocol == "http:") {
+              window.open(str, "_blank");
+            }
+          } catch (ex) { /* the link might be bust, then we do nothing */ }
+        },
       }));
     }
 
     renderTree() {
       // Append custom column for displaying values. This column
       // Take all available horizontal space.
       let columns = [{
         id: "value",
--- a/devtools/client/jsonview/converter-child.js
+++ b/devtools/client/jsonview/converter-child.js
@@ -77,32 +77,37 @@ Converter.prototype = {
   },
 
   onStartRequest: function(request, context) {
     // Set the content type to HTML in order to parse the doctype, styles
     // and scripts. The JSON will be manually inserted as text.
     request.QueryInterface(Ci.nsIChannel);
     request.contentType = "text/html";
 
+    // Enforce strict CSP:
+    try {
+      request.QueryInterface(Ci.nsIHttpChannel);
+      request.setResponseHeader("Content-Security-Policy",
+        "default-src 'none' ; script-src resource:; ", false);
+    } catch (ex) {
+      // If this is not an HTTP channel we can't and won't do anything.
+    }
+
     // Don't honor the charset parameter and use UTF-8 (see bug 741776).
     request.contentCharset = "UTF-8";
     this.decoder = new TextDecoder("UTF-8");
 
     // Changing the content type breaks saving functionality. Fix it.
     fixSave(request);
 
     // Because content might still have a reference to this window,
     // force setting it to a null principal to avoid it being same-
     // origin with (other) content.
     request.loadInfo.resetPrincipalToInheritToNullPrincipal();
 
-    // Because the JSON might be served with a CSP, we instrument
-    // the loadinfo so the Document can discard such a CSP.
-    request.loadInfo.allowDocumentToBeAgnosticToCSP = true;
-
     // Start the request.
     this.listener.onStartRequest(request, context);
 
     // Initialize stuff.
     let win = NetworkHelper.getWindowForRequest(request);
     this.data = exportData(win, request);
     insertJsonData(win, this.data.json);
     win.addEventListener("contentMessage", onContentMessage, false, true);
--- a/dom/canvas/CanvasRenderingContext2D.cpp
+++ b/dom/canvas/CanvasRenderingContext2D.cpp
@@ -1433,17 +1433,18 @@ CanvasRenderingContext2D::AllowOpenGLCan
   //   mCompositorBackend = el->GetCompositorBackendType();
   // }
   //
   // We could have LAYERS_NONE if there was no widget at the time of
   // canvas creation, but in that case the
   // HTMLCanvasElement::GetCompositorBackendType would return LAYERS_NONE
   // as well, so it wouldn't help much.
 
-  return (mCompositorBackend == LayersBackend::LAYERS_OPENGL) &&
+  return (mCompositorBackend == LayersBackend::LAYERS_OPENGL ||
+          mCompositorBackend == LayersBackend::LAYERS_WR) &&
     gfxPlatform::GetPlatform()->AllowOpenGLCanvas();
 }
 
 bool CanvasRenderingContext2D::SwitchRenderingMode(RenderingMode aRenderingMode)
 {
   if (!(IsTargetValid() || mBufferProvider) || mRenderingMode == aRenderingMode) {
     return false;
   }
--- a/dom/serviceworkers/ServiceWorkerEvents.cpp
+++ b/dom/serviceworkers/ServiceWorkerEvents.cpp
@@ -721,17 +721,17 @@ RespondWithHandler::ResolvedCallback(JSC
     responseURL = ir->GetUnfilteredURL();
 
     // Similar to how we apply the request fragment to redirects automatically
     // we also want to apply it automatically when propagating the response
     // URL from a service worker interception.  Currently response.url strips
     // the fragment, so this will never conflict with an existing fragment
     // on the response.  In the future we will have to check for a response
     // fragment and avoid overriding in that case.
-    if (!mRequestFragment.IsEmpty()) {
+    if (!mRequestFragment.IsEmpty() && !responseURL.IsEmpty()) {
       MOZ_ASSERT(!responseURL.Contains('#'));
       responseURL.Append(NS_LITERAL_CSTRING("#"));
       responseURL.Append(mRequestFragment);
     }
   }
 
   UniquePtr<RespondWithClosure> closure(new RespondWithClosure(mInterceptedChannel,
                                                                mRegistration,
--- a/dom/workers/moz.build
+++ b/dom/workers/moz.build
@@ -77,17 +77,16 @@ LOCAL_INCLUDES += [
 ]
 
 include('/ipc/chromium/chromium-config.mozbuild')
 
 FINAL_LIBRARY = 'xul'
 
 TEST_DIRS += [
     'test/extensions/bootstrap',
-    'test/extensions/traditional',
 ]
 
 MOCHITEST_MANIFESTS += [
     'test/mochitest.ini',
 ]
 
 MOCHITEST_CHROME_MANIFESTS += [
     'test/chrome.ini',
--- a/dom/workers/test/chrome.ini
+++ b/dom/workers/test/chrome.ini
@@ -64,17 +64,16 @@ skip-if = (os == 'linux') # Bug 1244697
 [test_WorkerDebuggerManager.xul]
 skip-if = (os == 'linux') # Bug 1244409
 [test_WorkerDebugger_console.xul]
 [test_WorkerDebugger_frozen.xul]
 [test_WorkerDebugger_promise.xul]
 [test_WorkerDebugger_suspended.xul]
 [test_chromeWorker.xul]
 [test_chromeWorkerJSM.xul]
-[test_extension.xul]
 [test_extensionBootstrap.xul]
 [test_file.xul]
 [test_fileBlobPosting.xul]
 [test_fileBlobSubWorker.xul]
 [test_filePosting.xul]
 [test_fileReadSlice.xul]
 [test_fileReaderSync.xul]
 [test_fileReaderSyncErrors.xul]
--- a/dom/workers/test/extensions/moz.build
+++ b/dom/workers/test/extensions/moz.build
@@ -1,7 +1,7 @@
 # -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
 # vim: set filetype=python:
 # 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/.
 
-DIRS += ['bootstrap', 'traditional']
+DIRS += ['bootstrap']
deleted file mode 100644
--- a/dom/workers/test/extensions/traditional/WorkerTest.js
+++ /dev/null
@@ -1,118 +0,0 @@
-/**
- * Any copyright is dedicated to the Public Domain.
- * http://creativecommons.org/publicdomain/zero/1.0/
- */
-
-ChromeUtils.import("resource://gre/modules/Services.jsm");
-ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
-
-var gWorkerAndCallback = {
-  _worker: null,
-  _callback: null,
-
-  _ensureStarted: function() {
-    if (!this._worker) {
-      throw new Error("Not yet started!");
-    }
-  },
-
-  start: function() {
-    if (!this._worker) {
-      var worker = new Worker("chrome://worker/content/worker.js");
-      worker.onerror = function(event) {
-        Cu.reportError(event.message);
-        event.preventDefault();
-      };
-
-      this._worker = worker;
-    }
-  },
-
-  stop: function() {
-    if (this._worker) {
-      try {
-        this.terminate();
-      }
-      catch(e) {
-        Cu.reportError(e);
-      }
-      this._worker = null;
-    }
-  },
-
-  set callback(val) {
-    this._ensureStarted();
-    if (val) {
-      var callback = val.QueryInterface(Ci.nsIWorkerTestCallback);
-      if (this.callback != callback) {
-        this._worker.onmessage = function(event) {
-          callback.onmessage(event.data);
-        };
-        this._worker.onerror = function(event) {
-          callback.onerror(event.message);
-          event.preventDefault();
-        };
-        this._callback = callback;
-      }
-    }
-    else {
-      this._worker.onmessage = null;
-      this._worker.onerror = null;
-      this._callback = null;
-    }
-  },
-
-  get callback() {
-    return this._callback;
-  },
-
-  postMessage: function(data) {
-    this._ensureStarted();
-    this._worker.postMessage(data);
-  },
-
-  terminate: function() {
-    this._ensureStarted();
-    this._worker.terminate();
-    this.callback = null;
-  }
-};
-
-function WorkerTest() {
-}
-WorkerTest.prototype = {
-  observe: function(subject, topic, data) {
-    switch(topic) {
-      case "profile-after-change":
-        gWorkerAndCallback.start();
-        Services.obs.addObserver(this, "profile-before-change");
-        break;
-      case "profile-before-change":
-        gWorkerAndCallback.stop();
-        break;
-      default:
-        Cu.reportError("Unknown topic: " + topic);
-    }
-  },
-
-  set callback(val) {
-    gWorkerAndCallback.callback = val;
-  },
-
-  get callback() {
-    return gWorkerAndCallback.callback;
-  },
-
-  postMessage: function(message) {
-    gWorkerAndCallback.postMessage(message);
-  },
-
-  terminate: function() {
-    gWorkerAndCallback.terminate();
-  },
-
-  QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver, Ci.nsIWorkerTest]),
-  classID: Components.ID("{3b52b935-551f-4606-ba4c-decc18b67bfd}")
-};
-
-this.NSGetFactory = XPCOMUtils.generateNSGetFactory([WorkerTest]);
deleted file mode 100644
--- a/dom/workers/test/extensions/traditional/WorkerTest.manifest
+++ /dev/null
@@ -1,3 +0,0 @@
-component {3b52b935-551f-4606-ba4c-decc18b67bfd} WorkerTest.js
-contract @mozilla.org/test/workertest;1 {3b52b935-551f-4606-ba4c-decc18b67bfd}
-category profile-after-change WorkerTest @mozilla.org/test/workertest;1
deleted file mode 100644
--- a/dom/workers/test/extensions/traditional/install.rdf
+++ /dev/null
@@ -1,30 +0,0 @@
-<?xml version="1.0"?>
-
-<RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
-     xmlns:em="http://www.mozilla.org/2004/em-rdf#">
-
-  <Description about="urn:mozilla:install-manifest">
-    <em:name>WorkerTest</em:name>
-    <em:description>Worker functions for use in testing.</em:description>
-    <em:creator>Mozilla</em:creator>
-    <em:version>2016.03.09</em:version>
-    <em:id>worker-test@mozilla.org</em:id>
-    <em:type>2</em:type>
-    <em:targetApplication>
-      <Description>
-        <!-- Firefox -->
-        <em:id>{ec8030f7-c20a-464f-9b0e-13a3a9e97384}</em:id>
-        <em:minVersion>45.0</em:minVersion>
-        <em:maxVersion>*</em:maxVersion>
-      </Description>
-    </em:targetApplication>
-    <em:targetApplication>
-      <Description>
-        <!-- Fennec -->
-        <em:id>{aa3c5121-dab2-40e2-81ca-7ea25febc110}</em:id>
-        <em:minVersion>45.0</em:minVersion>
-        <em:maxVersion>*</em:maxVersion>
-      </Description>
-    </em:targetApplication>
-  </Description>
-</RDF>
deleted file mode 100644
--- a/dom/workers/test/extensions/traditional/jar.mn
+++ /dev/null
@@ -1,3 +0,0 @@
-worker.jar:
-% content worker %content/
-  content/worker.js (worker.js)
deleted file mode 100644
--- a/dom/workers/test/extensions/traditional/moz.build
+++ /dev/null
@@ -1,30 +0,0 @@
-# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
-# vim: set filetype=python:
-# 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/.
-
-XPIDL_SOURCES += [
-    'nsIWorkerTest.idl',
-]
-
-XPIDL_MODULE = 'WorkerTest'
-
-EXTRA_COMPONENTS += [
-    'WorkerTest.js',
-    'WorkerTest.manifest',
-]
-
-XPI_NAME = 'worker'
-
-JAR_MANIFESTS += ['jar.mn']
-USE_EXTENSION_MANIFEST = True
-NO_JS_MANIFEST = True
-
-FINAL_TARGET_FILES += [
-    'install.rdf',
-]
-
-TEST_HARNESS_FILES.testing.mochitest.extensions += [
-    'worker-test@mozilla.org.xpi',
-]
deleted file mode 100644
--- a/dom/workers/test/extensions/traditional/nsIWorkerTest.idl
+++ /dev/null
@@ -1,23 +0,0 @@
-/* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */
-/* vim: set ts=2 et sw=2 tw=40: */
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-#include "nsISupports.idl"
-
-[scriptable, uuid(10f8ebdf-1373-4640-9c34-53dee99f526f)]
-interface nsIWorkerTestCallback : nsISupports
-{
-  void onmessage(in DOMString data);
-  void onerror(in DOMString data);
-};
-
-[scriptable, uuid(887a0614-a0f0-4c0e-80e0-cf31e6d4e286)]
-interface nsIWorkerTest : nsISupports
-{
-  void postMessage(in DOMString data);
-  void terminate();
-
-  attribute nsIWorkerTestCallback callback;
-};
deleted file mode 100644
index 8d2386894c720d74ce83a474c353a5b4d417f948..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
GIT binary patch
literal 0
Hc$@<O00001
deleted file mode 100644
--- a/dom/workers/test/extensions/traditional/worker.js
+++ /dev/null
@@ -1,7 +0,0 @@
-/**
- * Any copyright is dedicated to the Public Domain.
- * http://creativecommons.org/publicdomain/zero/1.0/
- */
-onmessage = function(event) {
-  postMessage(event.data);
-}
deleted file mode 100644
--- a/dom/workers/test/test_extension.xul
+++ /dev/null
@@ -1,55 +0,0 @@
-<?xml version="1.0"?>
-<!--
-  Any copyright is dedicated to the Public Domain.
-  http://creativecommons.org/publicdomain/zero/1.0/
--->
-<window title="DOM Worker Threads Test"
-        xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
-        onload="test();">
-
-  <script type="application/javascript"
-          src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
-  <script type="application/javascript"
-          src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/>
-  <script type="application/javascript" src="dom_worker_helper.js"/>
-
-  <script type="application/javascript">
-  <![CDATA[
-
-    function test() {
-      const message = "woohoo";
-
-      var workertest =
-        Cc["@mozilla.org/test/workertest;1"].createInstance(Ci.nsIWorkerTest);
-
-      workertest.callback = {
-        onmessage: function(data) {
-          is(data, message, "Correct message");
-          workertest.callback = null;
-          workertest = null;
-          SimpleTest.finish();
-        },
-        onerror: function(data) {
-          ok(false, "Worker had an error: " + data.message);
-          workertest.callback = null;
-          workertest = null;
-          SimpleTest.finish();
-        },
-        QueryInterface: XPCOMUtils.generateQI([Ci.nsIWorkerTestCallback])
-      };
-
-      workertest.postMessage(message);
-
-      SimpleTest.waitForExplicitFinish();
-    }
-
-  ]]>
-  </script>
-
-  <body xmlns="http://www.w3.org/1999/xhtml">
-    <p id="display"></p>
-    <div id="content" style="display:none;"></div>
-    <pre id="test"></pre>
-  </body>
-  <label id="test-result"/>
-</window>
new file mode 100644
--- /dev/null
+++ b/editor/libeditor/crashtests/1444630.html
@@ -0,0 +1,28 @@
+<!DOCTYPE HTML>
+<html class="reftest-wait">
+<head>
+<script type="text/javascript">
+function load()
+{
+  let textarea = document.getElementById("editor");
+  textarea.focus();
+
+  SpecialPowers.Cu.import(
+    "chrome://reftest/content/AsyncSpellCheckTestHelper.jsm")
+  .onSpellCheck(textarea, () => {
+    let isc = SpecialPowers.wrap(textarea).editor.getInlineSpellChecker(false);
+    let sc = isc.spellChecker;
+
+    textarea.setAttribute("lang", "en-US");
+    sc.UpdateCurrentDictionary(() => {
+      document.documentElement.classList.remove("reftest-wait");
+    });
+    sc.UninitSpellChecker();
+  });
+}
+</script>
+</head>
+<body onload="load()">
+<textarea id="editor" spellchecker="true">ABC</textarea>
+</body>
+</html>
--- a/editor/libeditor/crashtests/crashtests.list
+++ b/editor/libeditor/crashtests/crashtests.list
@@ -93,9 +93,10 @@ load 1402469.html
 load 1402526.html
 load 1402904.html
 load 1405747.html
 load 1408170.html
 load 1414581.html
 load 1415231.html
 load 1425091.html
 load 1443664.html
+skip-if(Android) needs-focus load 1444630.html
 load 1446451.html
--- a/editor/spellchecker/EditorSpellCheck.cpp
+++ b/editor/spellchecker/EditorSpellCheck.cpp
@@ -693,16 +693,17 @@ NS_IMETHODIMP
 EditorSpellCheck::UninitSpellChecker()
 {
   NS_ENSURE_TRUE(mSpellChecker, NS_ERROR_NOT_INITIALIZED);
 
   // Cleanup - kill the spell checker
   DeleteSuggestedWordList();
   mDictionaryList.Clear();
   mDictionaryIndex = 0;
+  mDictionaryFetcherGroup++;
   mSpellChecker = nullptr;
   return NS_OK;
 }
 
 
 NS_IMETHODIMP
 EditorSpellCheck::SetFilter(nsITextServicesFilter *aFilter)
 {
--- a/gfx/cairo/cairo/src/cairo-ft-font.c
+++ b/gfx/cairo/cairo/src/cairo-ft-font.c
@@ -98,17 +98,17 @@ static setLcdFilterFunc setLcdFilter;
 #define DOUBLE_TO_16_16(d) ((FT_Fixed)((d) * 65536.0))
 #define DOUBLE_FROM_16_16(t) ((double)(t) / 65536.0)
 
 /* This is the max number of FT_face objects we keep open at once
  */
 #define MAX_OPEN_FACES 10
 /* This is the maximum font size we allow to be passed to FT_Set_Char_Size
  */
-#define MAX_FONT_SIZE 1000
+#define MAX_FONT_SIZE 2000
 
 extern FT_Face mozilla_NewFTFace(FT_Library aFTLibrary, const char* aFileName, int aFaceIndex);
 extern FT_Face mozilla_NewFTFaceFromData(FT_Library aFTLibrary, const uint8_t* aData, size_t aDataSize, int aFaceIndex);
 extern void mozilla_ReleaseFTFace(FT_Face aFace);
 extern void mozilla_LockFTLibrary(FT_Library aFTLibrary);
 extern void mozilla_UnlockFTLibrary(FT_Library aFTLibrary);
 
 /**
--- a/gfx/cairo/max-font-size.patch
+++ b/gfx/cairo/max-font-size.patch
@@ -3,17 +3,17 @@ diff --git a/gfx/cairo/cairo/src/cairo-f
 +++ b/gfx/cairo/cairo/src/cairo-ft-font.c
 @@ -63,6 +63,10 @@
  /* This is the max number of FT_face objects we keep open at once
   */
  #define MAX_OPEN_FACES 10
 +
 +/* This is the maximum font size we allow to be passed to FT_Set_Char_Size
 + */
-+#define MAX_FONT_SIZE 1000
++#define MAX_FONT_SIZE 2000
  
  /*
   * The simple 2x2 matrix is converted into separate scale and shape
 @@ -682,9 +686,11 @@ _cairo_ft_unscaled_font_set_scale (cairo
      FT_Set_Transform(unscaled->face, &mat, NULL);
  
      if ((unscaled->face->face_flags & FT_FACE_FLAG_SCALABLE) != 0) {
 +	double x_scale = MIN(sf.x_scale, MAX_FONT_SIZE);
--- a/gfx/thebes/gfxPlatform.cpp
+++ b/gfx/thebes/gfxPlatform.cpp
@@ -1381,17 +1381,18 @@ bool gfxPlatform::AllowOpenGLCanvas()
   // For now, only allow Skia+OpenGL, unless it's blocked.
   // Allow acceleration on Skia if the preference is set, unless it's blocked
   // as long as we have the accelerated layers
 
   // The compositor backend is only set correctly in the parent process,
   // so we let content process always assume correct compositor backend.
   // The callers have to do the right thing.
   bool correctBackend = !XRE_IsParentProcess() ||
-    ((mCompositorBackend == LayersBackend::LAYERS_OPENGL) &&
+    ((mCompositorBackend == LayersBackend::LAYERS_OPENGL ||
+      mCompositorBackend == LayersBackend::LAYERS_WR) &&
      (GetContentBackendFor(mCompositorBackend) == BackendType::SKIA));
 
   if (gfxPrefs::CanvasAzureAccelerated() && correctBackend) {
     nsCOMPtr<nsIGfxInfo> gfxInfo = do_GetService("@mozilla.org/gfx/info;1");
     int32_t status;
     nsCString discardFailureId;
     return !gfxInfo ||
       (NS_SUCCEEDED(gfxInfo->GetFeatureStatus(nsIGfxInfo::FEATURE_CANVAS2D_ACCELERATION,
--- a/gfx/thebes/gfxPlatform.h
+++ b/gfx/thebes/gfxPlatform.h
@@ -281,17 +281,17 @@ public:
     }
 
     /// This function also lets us know if the current preferences/platform
     /// combination allows for both accelerated and not accelerated canvas
     /// implementations.  If it does, and other relevant preferences are
     /// asking for it, we will examine the commands in the first few seconds
     /// of the canvas usage, and potentially change to accelerated or
     /// non-accelerated canvas.
-    bool AllowOpenGLCanvas();
+    virtual bool AllowOpenGLCanvas();
     virtual void InitializeSkiaCacheLimits();
 
     static bool AsyncPanZoomEnabled();
 
     virtual void GetAzureBackendInfo(mozilla::widget::InfoObject &aObj);
     void GetApzSupportInfo(mozilla::widget::InfoObject& aObj);
     void GetTilesSupportInfo(mozilla::widget::InfoObject& aObj);
 
--- a/gfx/thebes/gfxWindowsPlatform.cpp
+++ b/gfx/thebes/gfxWindowsPlatform.cpp
@@ -504,16 +504,23 @@ gfxWindowsPlatform::UpdateRenderMode()
                       << ", D2D1 status:" << FeatureStatusToString(gfxConfig::GetValue(Feature::DIRECT2D))
                       << ", content:" << int(GetDefaultContentBackend())
                       << ", compositor:" << int(GetCompositorBackend());
       MOZ_CRASH("GFX: Failed to update reference draw target after device reset");
     }
   }
 }
 
+bool
+gfxWindowsPlatform::AllowOpenGLCanvas()
+{
+  // OpenGL canvas is not supported on windows
+  return false;
+}
+
 mozilla::gfx::BackendType
 gfxWindowsPlatform::GetContentBackendFor(mozilla::layers::LayersBackend aLayers)
 {
   mozilla::gfx::BackendType defaultBackend = gfxPlatform::GetDefaultContentBackend();
   if (aLayers == LayersBackend::LAYERS_D3D11) {
     return defaultBackend;
   }
 
--- a/gfx/thebes/gfxWindowsPlatform.h
+++ b/gfx/thebes/gfxWindowsPlatform.h
@@ -169,16 +169,18 @@ public:
     virtual bool CanUseHardwareVideoDecoding() override;
 
     virtual void CompositorUpdated() override;
 
     bool DidRenderingDeviceReset(DeviceResetReason* aResetReason = nullptr) override;
     void SchedulePaintIfDeviceReset() override;
     void CheckForContentOnlyDeviceReset();
 
+    bool AllowOpenGLCanvas() override;
+
     mozilla::gfx::BackendType GetContentBackendFor(mozilla::layers::LayersBackend aLayers) override;
 
     mozilla::gfx::BackendType GetPreferredCanvasBackend() override;
 
     static void GetDLLVersion(char16ptr_t aDLLPath, nsAString& aVersion);
 
     // returns ClearType tuning information for each display
     static void GetCleartypeParams(nsTArray<ClearTypeParameterInfo>& aParams);
--- a/js/src/builtin/String.cpp
+++ b/js/src/builtin/String.cpp
@@ -900,19 +900,19 @@ ToLowerCase(JSContext* cx, JSLinearStrin
     size_t resultLength;
     {
         AutoCheckCannotGC nogc;
         const CharT* chars = str->chars<CharT>(nogc);
 
         // We don't need extra special casing checks in the loop below,
         // because U+0130 LATIN CAPITAL LETTER I WITH DOT ABOVE and U+03A3
         // GREEK CAPITAL LETTER SIGMA already have simple lower case mappings.
-        MOZ_ASSERT(unicode::CanLowerCase(unicode::LATIN_CAPITAL_LETTER_I_WITH_DOT_ABOVE),
+        MOZ_ASSERT(unicode::ChangesWhenLowerCased(unicode::LATIN_CAPITAL_LETTER_I_WITH_DOT_ABOVE),
                    "U+0130 has a simple lower case mapping");
-        MOZ_ASSERT(unicode::CanLowerCase(unicode::GREEK_CAPITAL_LETTER_SIGMA),
+        MOZ_ASSERT(unicode::ChangesWhenLowerCased(unicode::GREEK_CAPITAL_LETTER_SIGMA),
                    "U+03A3 has a simple lower case mapping");
 
         // One element Latin-1 strings can be directly retrieved from the
         // static strings cache.
         if (IsSame<CharT, Latin1Char>::value) {
             if (length == 1) {
                 char16_t lower = unicode::ToLowerCase(chars[0]);
                 MOZ_ASSERT(lower <= JSString::MAX_LATIN1_CHAR);
@@ -925,25 +925,25 @@ ToLowerCase(JSContext* cx, JSLinearStrin
         // Look for the first character that changes when lowercased.
         size_t i = 0;
         for (; i < length; i++) {
             CharT c = chars[i];
             if (!IsSame<CharT, Latin1Char>::value) {
                 if (unicode::IsLeadSurrogate(c) && i + 1 < length) {
                     CharT trail = chars[i + 1];
                     if (unicode::IsTrailSurrogate(trail)) {
-                        if (unicode::CanLowerCaseNonBMP(c, trail))
+                        if (unicode::ChangesWhenLowerCasedNonBMP(c, trail))
                             break;
 
                         i++;
                         continue;
                     }
                 }
             }
-            if (unicode::CanLowerCase(c))
+            if (unicode::ChangesWhenLowerCased(c))
                 break;
         }
 
         // If no character needs to change, return the input string.
         if (i == length)
             return str;
 
         resultLength = length;
@@ -1109,62 +1109,62 @@ js::str_toLocaleLowerCase(JSContext* cx,
 
     args.rval().setString(result);
     return true;
 }
 
 #endif // EXPOSE_INTL_API
 
 static inline bool
-CanUpperCaseSpecialCasing(Latin1Char charCode)
+ToUpperCaseHasSpecialCasing(Latin1Char charCode)
 {
-    // Handle U+00DF LATIN SMALL LETTER SHARP S inline, all other Latin-1
-    // characters don't have special casing rules.
-    MOZ_ASSERT_IF(charCode != unicode::LATIN_SMALL_LETTER_SHARP_S,
-                  !unicode::CanUpperCaseSpecialCasing(charCode));
-
-    return charCode == unicode::LATIN_SMALL_LETTER_SHARP_S;
+    // U+00DF LATIN SMALL LETTER SHARP S is the only Latin-1 code point with
+    // special casing rules, so detect it inline.
+    bool hasUpperCaseSpecialCasing = charCode == unicode::LATIN_SMALL_LETTER_SHARP_S;
+    MOZ_ASSERT(hasUpperCaseSpecialCasing == unicode::ChangesWhenUpperCasedSpecialCasing(charCode));
+
+    return hasUpperCaseSpecialCasing;
 }
 
 static inline bool
-CanUpperCaseSpecialCasing(char16_t charCode)
+ToUpperCaseHasSpecialCasing(char16_t charCode)
 {
-    return unicode::CanUpperCaseSpecialCasing(charCode);
+    return unicode::ChangesWhenUpperCasedSpecialCasing(charCode);
 }
 
 static inline size_t
-LengthUpperCaseSpecialCasing(Latin1Char charCode)
+ToUpperCaseLengthSpecialCasing(Latin1Char charCode)
 {
     // U+00DF LATIN SMALL LETTER SHARP S is uppercased to two 'S'.
     MOZ_ASSERT(charCode == unicode::LATIN_SMALL_LETTER_SHARP_S);
 
     return 2;
 }
 
 static inline size_t
-LengthUpperCaseSpecialCasing(char16_t charCode)
+ToUpperCaseLengthSpecialCasing(char16_t charCode)
 {
-    MOZ_ASSERT(::CanUpperCaseSpecialCasing(charCode));
+    MOZ_ASSERT(ToUpperCaseHasSpecialCasing(charCode));
 
     return unicode::LengthUpperCaseSpecialCasing(charCode);
 }
 
 static inline void
-AppendUpperCaseSpecialCasing(char16_t charCode, Latin1Char* elements, size_t* index)
+ToUpperCaseAppendUpperCaseSpecialCasing(char16_t charCode, Latin1Char* elements, size_t* index)
 {
     // U+00DF LATIN SMALL LETTER SHARP S is uppercased to two 'S'.
     MOZ_ASSERT(charCode == unicode::LATIN_SMALL_LETTER_SHARP_S);
     static_assert('S' <= JSString::MAX_LATIN1_CHAR, "'S' is a Latin-1 character");
 
     elements[(*index)++] = 'S';
     elements[(*index)++] = 'S';
 }
 
 static inline void
-AppendUpperCaseSpecialCasing(char16_t charCode, char16_t* elements, size_t* index)
+ToUpperCaseAppendUpperCaseSpecialCasing(char16_t charCode, char16_t* elements, size_t* index)
 {
     unicode::AppendUpperCaseSpecialCasing(charCode, elements, index);
 }
 
 // See ToLowerCaseImpl for an explanation of the parameters.
 template <typename DestChar, typename SrcChar>
 static size_t
 ToUpperCaseImpl(DestChar* destChars, const SrcChar* srcChars, size_t startIndex, size_t srcLength,
@@ -1186,22 +1186,22 @@ ToUpperCaseImpl(DestChar* destChars, con
                     destChars[j++] = c;
                     destChars[j++] = trail;
                     i++;
                     continue;
                 }
             }
         }
 
-        if (MOZ_UNLIKELY(c > 0x7f && ::CanUpperCaseSpecialCasing(static_cast<SrcChar>(c)))) {
+        if (MOZ_UNLIKELY(c > 0x7f && ToUpperCaseHasSpecialCasing(static_cast<SrcChar>(c)))) {
             // Return if the output buffer is too small.
             if (srcLength == destLength)
                 return i;
 
-            ::AppendUpperCaseSpecialCasing(c, destChars, &j);
+            ToUpperCaseAppendUpperCaseSpecialCasing(c, destChars, &j);
             continue;
         }
 
         c = unicode::ToUpperCase(c);
         MOZ_ASSERT_IF((IsSame<DestChar, Latin1Char>::value), c <= JSString::MAX_LATIN1_CHAR);
         destChars[j++] = c;
     }
 
@@ -1221,18 +1221,18 @@ ToUpperCaseImpl(Latin1Char* destChars, c
 template <typename CharT>
 static size_t
 ToUpperCaseLength(const CharT* chars, size_t startIndex, size_t length)
 {
     size_t upperLength = length;
     for (size_t i = startIndex; i < length; i++) {
         char16_t c = chars[i];
 
-        if (c > 0x7f && ::CanUpperCaseSpecialCasing(static_cast<CharT>(c)))
-            upperLength += ::LengthUpperCaseSpecialCasing(static_cast<CharT>(c)) - 1;
+        if (c > 0x7f && ToUpperCaseHasSpecialCasing(static_cast<CharT>(c)))
+            upperLength += ToUpperCaseLengthSpecialCasing(static_cast<CharT>(c)) - 1;
     }
     return upperLength;
 }
 
 template <typename DestChar, typename SrcChar>
 static inline void
 CopyChars(DestChar* destChars, const SrcChar* srcChars, size_t length)
 {
@@ -1302,39 +1302,39 @@ ToUpperCase(JSContext* cx, JSLinearStrin
                     char16_t upper = unicode::ToUpperCase(c);
                     MOZ_ASSERT(upper <= JSString::MAX_LATIN1_CHAR);
                     MOZ_ASSERT(StaticStrings::hasUnit(upper));
 
                     return cx->staticStrings().getUnit(upper);
                 }
 
                 MOZ_ASSERT(unicode::ToUpperCase(c) > JSString::MAX_LATIN1_CHAR ||
-                           ::CanUpperCaseSpecialCasing(c));
+                           ToUpperCaseHasSpecialCasing(c));
             }
         }
 
         // Look for the first character that changes when uppercased.
         size_t i = 0;
         for (; i < length; i++) {
             CharT c = chars[i];
             if (!IsSame<CharT, Latin1Char>::value) {
                 if (unicode::IsLeadSurrogate(c) && i + 1 < length) {
                     CharT trail = chars[i + 1];
                     if (unicode::IsTrailSurrogate(trail)) {
-                        if (unicode::CanUpperCaseNonBMP(c, trail))
+                        if (unicode::ChangesWhenUpperCasedNonBMP(c, trail))
                             break;
 
                         i++;
                         continue;
                     }
                 }
             }
-            if (unicode::CanUpperCase(c))
+            if (unicode::ChangesWhenUpperCased(c))
                 break;
-            if (MOZ_UNLIKELY(c > 0x7f && ::CanUpperCaseSpecialCasing(c)))
+            if (MOZ_UNLIKELY(c > 0x7f && ToUpperCaseHasSpecialCasing(c)))
                 break;
         }
 
         // If no character needs to change, return the input string.
         if (i == length)
             return str;
 
         // The string changes when uppercased, so we must create a new string.
rename from js/src/vm/CaseFolding.txt
rename to js/src/util/CaseFolding.txt
rename from js/src/vm/DerivedCoreProperties.txt
rename to js/src/util/DerivedCoreProperties.txt
rename from js/src/vm/SpecialCasing.txt
rename to js/src/util/SpecialCasing.txt
--- a/js/src/util/Unicode.cpp
+++ b/js/src/util/Unicode.cpp
@@ -2674,17 +2674,17 @@ js::unicode::IsIdentifierPartNonBMP(uint
     if (codePoint >= 0x2F800 && codePoint <= 0x2FA1D) // CJK COMPATIBILITY IDEOGRAPH-2F800 .. CJK COMPATIBILITY IDEOGRAPH-2FA1D
         return true;
     if (codePoint >= 0xE0100 && codePoint <= 0xE01EF) // VARIATION SELECTOR-17 .. VARIATION SELECTOR-256
         return true;
     return false;
 }
 
 bool
-js::unicode::CanUpperCaseSpecialCasing(char16_t ch)
+js::unicode::ChangesWhenUpperCasedSpecialCasing(char16_t ch)
 {
     if (ch < 0x00DF || ch > 0xFB17)
         return false;
     if (ch <= 0x0587) {
         // U+00DF LATIN SMALL LETTER SHARP S
         // U+0149 LATIN SMALL LETTER N PRECEDED BY APOSTROPHE (LATIN SMALL LETTER APOSTROPHE N)
         // U+01F0 LATIN SMALL LETTER J WITH CARON (LATIN SMALL LETTER J HACEK)
         // U+0390 GREEK SMALL LETTER IOTA WITH DIALYTIKA AND TONOS (GREEK SMALL LETTER IOTA DIAERESIS TONOS)
--- a/js/src/util/Unicode.h
+++ b/js/src/util/Unicode.h
@@ -250,110 +250,126 @@ IsSpaceOrBOM2(char16_t ch)
     /* We accept BOM2 (0xFFFE) for compatibility reasons in the parser. */
     if (ch == NO_BREAK_SPACE || ch == BYTE_ORDER_MARK2)
         return true;
 
     return CharInfo(ch).isSpace();
 }
 
 /*
- * Returns the simple upper case mapping (see CanUpperCaseSpecialCasing for
- * details) of the given UTF-16 code unit.
+ * Returns the simple upper case mapping (possibly the identity mapping; see
+ * ChangesWhenUpperCasedSpecialCasing for details) of the given UTF-16 code
+ * unit.
  */
 inline char16_t
 ToUpperCase(char16_t ch)
 {
     if (ch < 128) {
         if (ch >= 'a' && ch <= 'z')
             return ch - ('a' - 'A');
         return ch;
     }
 
     const CharacterInfo& info = CharInfo(ch);
 
     return uint16_t(ch) + info.upperCase;
 }
 
 /*
- * Returns the simple lower case mapping (see CanUpperCaseSpecialCasing for
- * details) of the given UTF-16 code unit.
+ * Returns the simple lower case mapping (possibly the identity mapping; see
+ * ChangesWhenUpperCasedSpecialCasing for details) of the given UTF-16 code
+ * unit.
  */
 inline char16_t
 ToLowerCase(char16_t ch)
 {
     if (ch < 128) {
         if (ch >= 'A' && ch <= 'Z')
             return ch + ('a' - 'A');
         return ch;
     }
 
     const CharacterInfo& info = CharInfo(ch);
 
     return uint16_t(ch) + info.lowerCase;
 }
 
-// Returns true iff ToUpperCase(ch) != ch.
+/**
+ * Returns true iff ToUpperCase(ch) != ch.
+ *
+ * This function isn't guaranteed to correctly handle code points for which
+ * |ChangesWhenUpperCasedSpecialCasing| returns true, so it is *not* always the
+ * same as the value of the Changes_When_Uppercased Unicode property value for
+ * the code point.
+ */
 inline bool
-CanUpperCase(char16_t ch)
+ChangesWhenUpperCased(char16_t ch)
 {
     if (ch < 128)
         return ch >= 'a' && ch <= 'z';
     return CharInfo(ch).upperCase != 0;
 }
 
-// Returns true iff ToUpperCase(ch) != ch.
+/**
+ * Returns true iff ToUpperCase(ch) != ch.
+ *
+ * This function isn't guaranteed to correctly handle code points for which
+ * |ChangesWhenUpperCasedSpecialCasing| returns true, so it is *not* always the
+ * same as the value of the Changes_When_Uppercased Unicode property value for
+ * the code point.
+ */
 inline bool
-CanUpperCase(JS::Latin1Char ch)
+ChangesWhenUpperCased(JS::Latin1Char ch)
 {
     if (MOZ_LIKELY(ch < 128))
         return ch >= 'a' && ch <= 'z';
 
     // U+00B5 and U+00E0 to U+00FF, except U+00F7, have an uppercase form.
-    bool canUpper = ch == MICRO_SIGN ||
+    bool hasUpper = ch == MICRO_SIGN ||
                     (((ch & ~0x1F) == LATIN_SMALL_LETTER_A_WITH_GRAVE) && ch != DIVISION_SIGN);
-    MOZ_ASSERT(canUpper == CanUpperCase(char16_t(ch)));
-    return canUpper;
+    MOZ_ASSERT(hasUpper == ChangesWhenUpperCased(char16_t(ch)));
+    return hasUpper;
 }
 
 // Returns true iff ToLowerCase(ch) != ch.
 inline bool
-CanLowerCase(char16_t ch)
+ChangesWhenLowerCased(char16_t ch)
 {
     if (ch < 128)
         return ch >= 'A' && ch <= 'Z';
     return CharInfo(ch).lowerCase != 0;
 }
 
 // Returns true iff ToLowerCase(ch) != ch.
 inline bool
-CanLowerCase(JS::Latin1Char ch)
+ChangesWhenLowerCased(JS::Latin1Char ch)
 {
     if (MOZ_LIKELY(ch < 128))
         return ch >= 'A' && ch <= 'Z';
 
     // U+00C0 to U+00DE, except U+00D7, have a lowercase form.
-    bool canLower = ((ch & ~0x1F) == LATIN_CAPITAL_LETTER_A_WITH_GRAVE) &&
+    bool hasLower = ((ch & ~0x1F) == LATIN_CAPITAL_LETTER_A_WITH_GRAVE) &&
                     ((ch & MULTIPLICATION_SIGN) != MULTIPLICATION_SIGN);
-    MOZ_ASSERT(canLower == CanLowerCase(char16_t(ch)));
-    return canLower;
+    MOZ_ASSERT(hasLower == ChangesWhenLowerCased(char16_t(ch)));
+    return hasLower;
 }
 
 #define CHECK_RANGE(FROM, TO, LEAD, TRAIL_FROM, TRAIL_TO, DIFF) \
     if (lead == LEAD && trail >= TRAIL_FROM && trail <= TRAIL_TO) \
         return true;
 
 inline bool
-CanUpperCaseNonBMP(char16_t lead, char16_t trail)
+ChangesWhenUpperCasedNonBMP(char16_t lead, char16_t trail)
 {
     FOR_EACH_NON_BMP_UPPERCASE(CHECK_RANGE)
     return false;
 }
 
 inline bool
-CanLowerCaseNonBMP(char16_t lead, char16_t trail)
+ChangesWhenLowerCasedNonBMP(char16_t lead, char16_t trail)
 {
     FOR_EACH_NON_BMP_LOWERCASE(CHECK_RANGE)
     return false;
 }
 
 #undef CHECK_RANGE
 
 inline char16_t
@@ -376,34 +392,46 @@ ToLowerCaseNonBMPTrail(char16_t lead, ch
         return trail + DIFF;
     FOR_EACH_NON_BMP_LOWERCASE(CALC_TRAIL)
 #undef CALL_TRAIL
 
     return trail;
 }
 
 /*
- * Returns true if the given UTF-16 code unit has a language-independent,
- * unconditional or conditional special upper case mapping.
+ * Returns true if, independent of language/locale, the given UTF-16 code unit
+ * has a special upper case mapping.
  *
  * Unicode defines two case mapping modes:
- * 1. "simple case mappings" for one-to-one mappings which are independent of
- *    context and language (defined in UnicodeData.txt).
- * 2. "special case mappings" for mappings which can increase or decrease the
- *    string length; or are dependent on context or locale (defined in
- *    SpecialCasing.txt).
  *
- * The CanUpperCase() method defined above only supports simple case mappings.
- * In order to support the full case mappings of all Unicode characters,
- * callers need to check this method in addition to CanUpperCase().
+ *   1. "simple case mappings" (defined in UnicodeData.txt) for one-to-one
+ *      mappings that are always the same regardless of locale or context
+ *      within a string (e.g. "a"→"A").
+ *   2. "special case mappings" (defined in SpecialCasing.txt) for mappings
+ *      that alter string length (e.g. uppercasing "ß"→"SS") or where different
+ *      mappings occur depending on language/locale (e.g. uppercasing "i"→"I"
+ *      usually but "i"→"İ" in Turkish) or context within the string (e.g.
+ *      lowercasing "Σ" U+03A3 GREEK CAPITAL LETTER SIGMA to "ς" U+03C2 GREEK
+ *      SMALL LETTER FINAL SIGMA when the sigma appears [roughly speaking] at
+ *      the end of a word but "ς" U+03C3 GREEK SMALL LETTER SIGMA anywhere
+ *      else).
  *
- * NOTE: All special upper case mappings are unconditional in Unicode 9.
+ * The ChangesWhenUpperCased*() functions defined above will return true for
+ * code points that have simple case mappings, but they may not return the
+ * right result for code points that have special case mappings.  To correctly
+ * support full case mappings for all code points, callers must determine
+ * whether this function returns true or false for the code point, then use
+ * AppendUpperCaseSpecialCasing in the former case and ToUpperCase in the
+ * latter.
+ *
+ * NOTE: All special upper case mappings are unconditional (that is, they don't
+ *       depend on language/locale or context within the string) in Unicode 10.
  */
 bool
-CanUpperCaseSpecialCasing(char16_t ch);
+ChangesWhenUpperCasedSpecialCasing(char16_t ch);
 
 /*
  * Returns the length of the upper case mapping of |ch|.
  *
  * This function asserts if |ch| doesn't have a special upper case mapping.
  */
 size_t
 LengthUpperCaseSpecialCasing(char16_t ch);
--- a/js/src/util/make_unicode.py
+++ b/js/src/util/make_unicode.py
@@ -608,18 +608,18 @@ def make_non_bmp_file(version,
                       codepoint_table):
     file_name = 'UnicodeNonBMP.h';
     with io.open(file_name, mode='wb') as non_bmp_file:
         non_bmp_file.write(mpl_license)
         non_bmp_file.write('\n')
         non_bmp_file.write(warning_message)
         non_bmp_file.write(unicode_version_message.format(version))
         non_bmp_file.write("""
-#ifndef vm_UnicodeNonBMP_h
-#define vm_UnicodeNonBMP_h
+#ifndef util_UnicodeNonBMP_h
+#define util_UnicodeNonBMP_h
 
 // |macro| receives the following arguments
 //   macro(FROM, TO, LEAD, TRAIL_FROM, TRAIL_TO, DIFF)
 //     FROM:       code point where the range starts
 //     TO:         code point where the range ends
 //     LEAD:       common lead surrogate of FROM and TO
 //     TRAIL_FROM: trail surrogate of FROM
 //     TRAIL_FROM: trail surrogate of TO
@@ -632,17 +632,17 @@ def make_non_bmp_file(version,
         non_bmp_file.write('\n')
         make_non_bmp_convert_macro(non_bmp_file, 'UPPERCASE', non_bmp_upper_map, codepoint_table)
         non_bmp_file.write('\n')
         make_non_bmp_convert_macro(non_bmp_file, 'CASE_FOLDING', non_bmp_folding_map, codepoint_table)
         non_bmp_file.write('\n')
         make_non_bmp_convert_macro(non_bmp_file, 'REV_CASE_FOLDING', non_bmp_rev_folding_map, codepoint_table)
 
         non_bmp_file.write("""
-#endif /* vm_UnicodeNonBMP_h */
+#endif /* util_UnicodeNonBMP_h */
 """)
 
 def write_special_casing_methods(unconditional_toupper, codepoint_table, println):
     def hexlit(n):
         """ Returns C++ hex-literal for |n|. """
         return '0x{:04X}'.format(n)
 
     def describe_range(ranges, depth):
@@ -718,20 +718,20 @@ def write_special_casing_methods(uncondi
             describe_range(child_ranges, depth)
             println(indent, 'return {};'.format(range_test_expr))
         else:
             println(indent, 'if (ch <= {}) {{'.format(hexlit(max_child)))
             describe_range(child_ranges, depth + 1)
             println(indent, '    return {};'.format(range_test_expr))
             println(indent, '}')
 
-    def write_CanUpperCaseSpecialCasing():
+    def write_ChangesWhenUpperCasedSpecialCasing():
         """ Checks if the input has a special upper case mapping. """
         println('bool')
-        println('js::unicode::CanUpperCaseSpecialCasing(char16_t ch)')
+        println('js::unicode::ChangesWhenUpperCasedSpecialCasing(char16_t ch)')
         println('{')
 
         assert unconditional_toupper, "|unconditional_toupper| is not empty"
 
         # Sorted list of code units with special upper case mappings.
         code_list = sorted(unconditional_toupper.iterkeys())
 
         # Fail-fast if the input character isn't a special casing character.
@@ -811,17 +811,17 @@ def write_special_casing_methods(uncondi
                                                                           codepoint_table.name(ch)))
             println('        return;')
         println('    }')
         println('')
         println('    MOZ_ASSERT_UNREACHABLE("Bad character input.");')
 
         println('}')
 
-    write_CanUpperCaseSpecialCasing()
+    write_ChangesWhenUpperCasedSpecialCasing()
     println('')
     write_LengthUpperCaseSpecialCasing()
     println('')
     write_AppendUpperCaseSpecialCasing()
 
 def write_ascii_lookup_tables(table, index, write, println):
     def is_id_compat(code):
         return code == ord(u'\N{DOLLAR SIGN}') or code == ord(u'\N{LOW LINE}')
@@ -877,17 +877,17 @@ def write_ascii_lookup_tables(table, ind
 
     println('')
     println('#undef ____')
 
 def make_bmp_mapping_test(version, codepoint_table, unconditional_tolower, unconditional_toupper):
     def unicodeEsc(n):
         return '\u{:04X}'.format(n)
 
-    file_name = '../tests/ecma_5/String/string-upper-lower-mapping.js'
+    file_name = '../tests/non262/String/string-upper-lower-mapping.js'
     with io.open(file_name, mode='wb') as output:
         write = partial(print, file=output, sep='', end='')
         println = partial(print, file=output, sep='', end='\n')
 
         write(warning_message)
         write(unicode_version_message.format(version))
         write(public_domain)
         println('var mapping = [')
@@ -914,17 +914,17 @@ for (var i = 0; i <= 0xffff; i++) {
     assertEq(char.toLowerCase(), info[1]);
 }
 
 if (typeof reportCompare === "function")
     reportCompare(true, true);
 """)
 
 def make_non_bmp_mapping_test(version, non_bmp_upper_map, non_bmp_lower_map, codepoint_table):
-    file_name = '../tests/ecma_6/String/string-code-point-upper-lower-mapping.js'
+    file_name = '../tests/non262/String/string-code-point-upper-lower-mapping.js'
     with io.open(file_name, mode='wb') as test_non_bmp_mapping:
         test_non_bmp_mapping.write(warning_message)
         test_non_bmp_mapping.write(unicode_version_message.format(version))
         test_non_bmp_mapping.write(public_domain)
 
         for code in sorted(non_bmp_upper_map.keys()):
             test_non_bmp_mapping.write("""\
 assertEq(String.fromCodePoint(0x{:04X}).toUpperCase().codePointAt(0), 0x{:04X}); // {}, {}
@@ -941,17 +941,17 @@ assertEq(String.fromCodePoint(0x{:04X}).
 if (typeof reportCompare === "function")
     reportCompare(true, true);
 """)
 
 def make_space_test(version, test_space_table, codepoint_table):
     def hex_and_name(c):
         return '    0x{:04X} /* {} */'.format(c, codepoint_table.name(c))
 
-    file_name = '../tests/ecma_5/String/string-space-trim.js'
+    file_name = '../tests/non262/String/string-space-trim.js'
     with io.open(file_name, mode='wb') as test_space:
         test_space.write(warning_message)
         test_space.write(unicode_version_message.format(version))
         test_space.write(public_domain)
         test_space.write('var onlySpace = String.fromCharCode(\n')
         test_space.write(',\n'.join(map(hex_and_name, test_space_table)))
         test_space.write('\n);\n')
         test_space.write("""
@@ -963,17 +963,17 @@ assertEq((onlySpace + 'aaaa' + onlySpace
 if (typeof reportCompare === "function")
     reportCompare(true, true);
 """)
 
 def make_regexp_space_test(version, test_space_table, codepoint_table):
     def hex_and_name(c):
         return '    0x{:04X} /* {} */'.format(c, codepoint_table.name(c))
 
-    file_name = '../tests/ecma_6/RegExp/character-class-escape-s.js'
+    file_name = '../tests/non262/RegExp/character-class-escape-s.js'
     with io.open(file_name, mode='wb') as test_space:
         test_space.write(warning_message)
         test_space.write(unicode_version_message.format(version))
         test_space.write(public_domain)
         test_space.write('var onlySpace = String.fromCodePoint(\n')
         test_space.write(',\n'.join(map(hex_and_name, test_space_table)))
         test_space.write('\n);\n')
         test_space.write("""
@@ -997,17 +997,17 @@ assertEq(/^[^\S]+$/u.exec(onlySpace) !==
 if (typeof reportCompare === "function")
     reportCompare(true, true);
 """)
 
 def make_icase_test(version, folding_tests, codepoint_table):
     def char_hex(c):
         return '0x{:04X}'.format(c)
 
-    file_name = '../tests/ecma_6/RegExp/unicode-ignoreCase.js'
+    file_name = '../tests/non262/RegExp/unicode-ignoreCase.js'
     with io.open(file_name, mode='wb') as test_icase:
         test_icase.write(warning_message)
         test_icase.write(unicode_version_message.format(version))
         test_icase.write(public_domain)
         test_icase.write("""
 var BUGNUMBER = 1135377;
 var summary = "Implement RegExp unicode flag -- ignoreCase flag.";
 
@@ -1174,17 +1174,17 @@ def make_unicode_file(version,
     file_name = 'Unicode.cpp'
     with io.open(file_name, 'wb') as data_file:
         write = partial(print, file=data_file, sep='', end='')
         println = partial(print, file=data_file, sep='', end='\n')
 
         write(warning_message)
         write(unicode_version_message.format(version))
         write(public_domain)
-        println('#include "vm/Unicode.h"')
+        println('#include "util/Unicode.h"')
         println('')
         println('using namespace js;')
         println('using namespace js::unicode;')
         write(comment)
 
         write_table('CharacterInfo',
                     'js_charinfo', table,
                     'index1', index1,
@@ -1572,19 +1572,19 @@ def update_unicode(args):
     make_non_bmp_mapping_test(unicode_version, non_bmp_upper_map, non_bmp_lower_map, codepoint_table)
     make_space_test(unicode_version, test_space_table, codepoint_table)
     make_regexp_space_test(unicode_version, test_space_table, codepoint_table)
     make_icase_test(unicode_version, folding_tests, codepoint_table)
 
 if __name__ == '__main__':
     import argparse
 
-    # This script must be run from js/src/vm to work correctly.
-    if '/'.join(os.path.normpath(os.getcwd()).split(os.sep)[-3:]) != 'js/src/vm':
-        raise RuntimeError('%s must be run from js/src/vm' % sys.argv[0])
+    # This script must be run from js/src/util to work correctly.
+    if '/'.join(os.path.normpath(os.getcwd()).split(os.sep)[-3:]) != 'js/src/util':
+        raise RuntimeError('%s must be run from js/src/util' % sys.argv[0])
 
     parser = argparse.ArgumentParser(description='Update Unicode data.')
 
     parser.add_argument('--version',
                         help='Optional Unicode version number. If specified, downloads the\
                               selected version from <http://unicode.org/Public>. If not specified\
                               uses the existing local files to generate the Unicode data. The\
                               number must match a published Unicode version, e.g. use\
--- a/media/webrtc/signaling/src/media-conduit/MediaConduitInterface.h
+++ b/media/webrtc/signaling/src/media-conduit/MediaConduitInterface.h
@@ -6,17 +6,16 @@
 #define MEDIA_CONDUIT_ABSTRACTION_
 
 #include "nsISupportsImpl.h"
 #include "nsXPCOM.h"
 #include "nsDOMNavigationTiming.h"
 #include "mozilla/RefPtr.h"
 #include "mozilla/RefCounted.h"
 #include "mozilla/UniquePtr.h"
-#include "double-conversion/utils.h" // for DISALLOW_COPY_AND_ASSIGN
 #include "RtpSourceObserver.h"
 #include "CodecConfig.h"
 #include "VideoTypes.h"
 #include "MediaConduitErrors.h"
 
 #include "ImageContainer.h"
 
 #include "webrtc/call.h"
@@ -85,17 +84,20 @@ private:
   }
 
   explicit WebRtcCallWrapper(UniquePtr<webrtc::Call>&& aCall)
   {
     MOZ_ASSERT(aCall);
     mCall = std::move(aCall);
   }
 
-  DISALLOW_COPY_AND_ASSIGN(WebRtcCallWrapper);
+  // Don't allow copying/assigning.
+  WebRtcCallWrapper(const WebRtcCallWrapper&) = delete;
+  void operator=(const WebRtcCallWrapper&) = delete;
+
   UniquePtr<webrtc::Call> mCall;
   webrtc::RtcEventLogNullImpl mEventLog;
 };
 
 
 /**
  * Abstract Interface for transporting RTP packets - audio/vidoeo
  * The consumers of this interface are responsible for passing in
--- a/media/webrtc/signaling/src/media-conduit/VideoConduit.h
+++ b/media/webrtc/signaling/src/media-conduit/VideoConduit.h
@@ -306,17 +306,19 @@ public:
                              uint32_t* cumulativeLost,
                              int32_t* rttMs) override;
   bool GetRTCPSenderReport(DOMHighResTimeStamp* timestamp,
                            unsigned int* packetsSent,
                            uint64_t* bytesSent) override;
   uint64_t MozVideoLatencyAvg();
 
 private:
-  DISALLOW_COPY_AND_ASSIGN(WebrtcVideoConduit);
+  // Don't allow copying/assigning.
+  WebrtcVideoConduit(const WebrtcVideoConduit&) = delete;
+  void operator=(const WebrtcVideoConduit&) = delete;
 
   /** Shared statistics for receive and transmit video streams
    */
   class StreamStatistics {
   public:
     void Update(const double aFrameRate, const double aBitrate);
     /**
      * Returns gathered stream statistics
--- a/mfbt/double-conversion/GIT-INFO
+++ b/mfbt/double-conversion/GIT-INFO
@@ -1,9 +1,37 @@
-commit fe9b384793c4e79bd32133dc9053f27b75a5eeae
-Merge: 2a257b7 1d5a688
-Author: Florian Loitsch <floitsch@google.com>
-Date:   Fri Sep 15 11:32:00 2017 +0200
+commit 1b5fa314800a0e68e2b5d00d17e87a5b1fa3ac5d
+Author: Shane <sffc@sffc1.com>
+Date:   Fri Mar 2 01:26:53 2018 -0800
+
+    Clarify output charset in DoubleToAscii documentation (#61)
+    
+    * Clarify output charset in DoubleToAscii documentation
+    
+    * Fixing typo in charset docs.
 
-    Merge pull request #52 from uburuntu/master
-    
-    Some refactorings: remove unused static, replace deprecated headers, init member in constructor
-
+diff --git a/double-conversion/double-conversion.h b/double-conversion/double-conversion.h
+index 9978bde..1ccd7fc 100644
+--- a/double-conversion/double-conversion.h
++++ b/double-conversion/double-conversion.h
+@@ -294,13 +294,18 @@ class DoubleToStringConverter {
+   // should be at least kBase10MaximalLength + 1 characters long.
+   static const int kBase10MaximalLength = 17;
+ 
+-  // Converts the given double 'v' to ascii. 'v' must not be NaN, +Infinity, or
+-  // -Infinity. In SHORTEST_SINGLE-mode this restriction also applies to 'v'
+-  // after it has been casted to a single-precision float. That is, in this
+-  // mode static_cast<float>(v) must not be NaN, +Infinity or -Infinity.
++  // Converts the given double 'v' to digit characters. 'v' must not be NaN,
++  // +Infinity, or -Infinity. In SHORTEST_SINGLE-mode this restriction also
++  // applies to 'v' after it has been casted to a single-precision float. That
++  // is, in this mode static_cast<float>(v) must not be NaN, +Infinity or
++  // -Infinity.
+   //
+   // The result should be interpreted as buffer * 10^(point-length).
+   //
++  // The digits are written to the buffer in the platform's charset, which is
++  // often UTF-8 (with ASCII-range digits) but may be another charset, such
++  // as EBCDIC.
++  //
+   // The output depends on the given mode:
+   //  - SHORTEST: produce the least amount of digits for which the internal
+   //   identity requirement is still satisfied. If the digits are printed
--- a/mfbt/double-conversion/add-mfbt-api-markers.patch
+++ b/mfbt/double-conversion/add-mfbt-api-markers.patch
@@ -102,24 +102,24 @@ diff --git a/mfbt/double-conversion/doub
    // A higher precision can be achieved by using more digits, but the shortest
    // accurate representation of any double will never use more digits than
    // kBase10MaximalLength.
    // Note that DoubleToAscii null-terminates its input. So the given buffer
    // should be at least kBase10MaximalLength + 1 characters long.
 -  static const int kBase10MaximalLength = 17;
 +  static const MFBT_DATA int kBase10MaximalLength = 17;
  
-   // Converts the given double 'v' to ascii. 'v' must not be NaN, +Infinity, or
-   // -Infinity. In SHORTEST_SINGLE-mode this restriction also applies to 'v'
-   // after it has been casted to a single-precision float. That is, in this
-   // mode static_cast<float>(v) must not be NaN, +Infinity or -Infinity.
+   // Converts the given double 'v' to digit characters. 'v' must not be NaN,
+   // +Infinity, or -Infinity. In SHORTEST_SINGLE-mode this restriction also
+   // applies to 'v' after it has been casted to a single-precision float. That
+   // is, in this mode static_cast<float>(v) must not be NaN, +Infinity or
+   // -Infinity.
    //
    // The result should be interpreted as buffer * 10^(point-length).
-   //
-@@ -327,44 +328,44 @@ class DoubleToStringConverter {
+@@ -332,44 +333,44 @@ class DoubleToStringConverter {
    // DoubleToAscii expects the given buffer to be big enough to hold all
    // digits and a terminating null-character. In SHORTEST-mode it expects a
    // buffer of at least kBase10MaximalLength + 1. In all other modes the
    // requested_digits parameter and the padding-zeroes limit the size of the
    // output. Don't forget the decimal point, the exponent character and the
    // terminating null-character when computing the maximal output size.
    // The given length is only used in debug mode to ensure the buffer is big
    // enough.
--- a/mfbt/double-conversion/double-conversion/bignum.h
+++ b/mfbt/double-conversion/double-conversion/bignum.h
@@ -131,14 +131,14 @@ class Bignum {
   Chunk bigits_buffer_[kBigitCapacity];
   // A vector backed by bigits_buffer_. This way accesses to the array are
   // checked for out-of-bounds errors.
   Vector<Chunk> bigits_;
   int used_digits_;
   // The Bignum's value equals value(bigits_) * 2^(exponent_ * kBigitSize).
   int exponent_;
 
-  DISALLOW_COPY_AND_ASSIGN(Bignum);
+  DC_DISALLOW_COPY_AND_ASSIGN(Bignum);
 };
 
 }  // namespace double_conversion
 
 #endif  // DOUBLE_CONVERSION_BIGNUM_H_
--- a/mfbt/double-conversion/double-conversion/double-conversion.h
+++ b/mfbt/double-conversion/double-conversion/double-conversion.h
@@ -291,23 +291,28 @@ class DoubleToStringConverter {
   // The maximal number of digits that are needed to emit a double in base 10.
   // A higher precision can be achieved by using more digits, but the shortest
   // accurate representation of any double will never use more digits than
   // kBase10MaximalLength.
   // Note that DoubleToAscii null-terminates its input. So the given buffer
   // should be at least kBase10MaximalLength + 1 characters long.
   static const MFBT_DATA int kBase10MaximalLength = 17;
 
-  // Converts the given double 'v' to ascii. 'v' must not be NaN, +Infinity, or
-  // -Infinity. In SHORTEST_SINGLE-mode this restriction also applies to 'v'
-  // after it has been casted to a single-precision float. That is, in this
-  // mode static_cast<float>(v) must not be NaN, +Infinity or -Infinity.
+  // Converts the given double 'v' to digit characters. 'v' must not be NaN,
+  // +Infinity, or -Infinity. In SHORTEST_SINGLE-mode this restriction also
+  // applies to 'v' after it has been casted to a single-precision float. That
+  // is, in this mode static_cast<float>(v) must not be NaN, +Infinity or
+  // -Infinity.
   //
   // The result should be interpreted as buffer * 10^(point-length).
   //
+  // The digits are written to the buffer in the platform's charset, which is
+  // often UTF-8 (with ASCII-range digits) but may be another charset, such
+  // as EBCDIC.
+  //
   // The output depends on the given mode:
   //  - SHORTEST: produce the least amount of digits for which the internal
   //   identity requirement is still satisfied. If the digits are printed
   //   (together with the correct exponent) then reading this number will give
   //   'v' again. The buffer will choose the representation that is closest to
   //   'v'. If there are two at the same distance, than the one farther away
   //   from 0 is chosen (halfway cases - ending with 5 - are rounded up).
   //   In this mode the 'requested_digits' parameter is ignored.
@@ -371,17 +376,17 @@ class DoubleToStringConverter {
   const char* const infinity_symbol_;
   const char* const nan_symbol_;
   const char exponent_character_;
   const int decimal_in_shortest_low_;
   const int decimal_in_shortest_high_;
   const int max_leading_padding_zeroes_in_precision_mode_;
   const int max_trailing_padding_zeroes_in_precision_mode_;
 
-  DISALLOW_IMPLICIT_CONSTRUCTORS(DoubleToStringConverter);
+  DC_DISALLOW_IMPLICIT_CONSTRUCTORS(DoubleToStringConverter);
 };
 
 
 class StringToDoubleConverter {
  public:
   // Enumeration for allowing octals and ignoring junk when converting
   // strings to numbers.
   enum Flags {
@@ -535,14 +540,14 @@ class StringToDoubleConverter {
   const char* const nan_symbol_;
 
   template <class Iterator>
   double StringToIeee(Iterator start_pointer,
                       int length,
                       bool read_as_double,
                       int* processed_characters_count) const;
 
-  DISALLOW_IMPLICIT_CONSTRUCTORS(StringToDoubleConverter);
+  DC_DISALLOW_IMPLICIT_CONSTRUCTORS(StringToDoubleConverter);
 };
 
 }  // namespace double_conversion
 
 #endif  // DOUBLE_CONVERSION_DOUBLE_CONVERSION_H_
--- a/mfbt/double-conversion/double-conversion/ieee.h
+++ b/mfbt/double-conversion/double-conversion/ieee.h
@@ -252,17 +252,17 @@ class Double {
       biased_exponent = 0;
     } else {
       biased_exponent = static_cast<uint64_t>(exponent + kExponentBias);
     }
     return (significand & kSignificandMask) |
         (biased_exponent << kPhysicalSignificandSize);
   }
 
-  DISALLOW_COPY_AND_ASSIGN(Double);
+  DC_DISALLOW_COPY_AND_ASSIGN(Double);
 };
 
 class Single {
  public:
   static const uint32_t kSignMask = 0x80000000;
   static const uint32_t kExponentMask = 0x7F800000;
   static const uint32_t kSignificandMask = 0x007FFFFF;
   static const uint32_t kHiddenBit = 0x00800000;
@@ -389,14 +389,14 @@ class Single {
   static const int kExponentBias = 0x7F + kPhysicalSignificandSize;
   static const int kDenormalExponent = -kExponentBias + 1;
   static const int kMaxExponent = 0xFF - kExponentBias;
   static const uint32_t kInfinity = 0x7F800000;
   static const uint32_t kNaN = 0x7FC00000;
 
   const uint32_t d32_;
 
-  DISALLOW_COPY_AND_ASSIGN(Single);
+  DC_DISALLOW_COPY_AND_ASSIGN(Single);
 };
 
 }  // namespace double_conversion
 
 #endif  // DOUBLE_CONVERSION_DOUBLE_H_
--- a/mfbt/double-conversion/double-conversion/strtod.cc
+++ b/mfbt/double-conversion/double-conversion/strtod.cc
@@ -200,17 +200,17 @@ static bool DoubleStrtod(Vector<const ch
 #if !defined(DOUBLE_CONVERSION_CORRECT_DOUBLE_OPERATIONS)
   // On x86 the floating-point stack can be 64 or 80 bits wide. If it is
   // 80 bits wide (as is the case on Linux) then double-rounding occurs and the
   // result is not accurate.
   // We know that Windows32 uses 64 bits and is therefore accurate.
   // Note that the ARM simulator is compiled for 32bits. It therefore exhibits
   // the same problem.
   return false;
-#endif
+#else
   if (trimmed.length() <= kMaxExactDoubleIntegerDecimalDigits) {
     int read_digits;
     // The trimmed input fits into a double.
     // If the 10^exponent (resp. 10^-exponent) fits into a double too then we
     // can compute the result-double simply by multiplying (resp. dividing) the
     // two numbers.
     // This is possible because IEEE guarantees that floating-point operations
     // return the best possible approximation.
@@ -238,16 +238,17 @@ static bool DoubleStrtod(Vector<const ch
       *result = static_cast<double>(ReadUint64(trimmed, &read_digits));
       ASSERT(read_digits == trimmed.length());
       *result *= exact_powers_of_ten[remaining_digits];
       *result *= exact_powers_of_ten[exponent - remaining_digits];
       return true;
     }
   }
   return false;
+#endif
 }
 
 
 // Returns 10^exponent as an exact DiyFp.
 // The given exponent must be in the range [1; kDecimalExponentDistance[.
 static DiyFp AdjustmentPowerOfTen(int exponent) {
   ASSERT(0 < exponent);
   ASSERT(exponent < PowersOfTenCache::kDecimalExponentDistance);
--- a/mfbt/double-conversion/double-conversion/utils.h
+++ b/mfbt/double-conversion/double-conversion/utils.h
@@ -93,18 +93,34 @@ inline void abort_noreturn() { MOZ_CRASH
 #endif
 
 #if defined(__GNUC__)
 #define DOUBLE_CONVERSION_UNUSED __attribute__((unused))
 #else
 #define DOUBLE_CONVERSION_UNUSED
 #endif
 
+#if defined(_WIN32) && !defined(__MINGW32__)
+
+typedef signed char int8_t;
+typedef unsigned char uint8_t;
+typedef short int16_t;  // NOLINT
+typedef unsigned short uint16_t;  // NOLINT
+typedef int int32_t;
+typedef unsigned int uint32_t;
+typedef __int64 int64_t;
+typedef unsigned __int64 uint64_t;
+// intptr_t and friends are defined in crtdefs.h through stdio.h.
+
+#else
+
 #include <stdint.h>
 
+#endif
+
 typedef uint16_t uc16;
 
 // The following macro works on both 32 and 64-bit platforms.
 // Usage: instead of writing 0x1234567890123456
 //      write UINT64_2PART_C(0x12345678,90123456);
 #define UINT64_2PART_C(a, b) (((static_cast<uint64_t>(a) << 32) + 0x##b##u))
 
 
@@ -115,32 +131,32 @@ typedef uint16_t uc16;
 #ifndef ARRAY_SIZE
 #define ARRAY_SIZE(a)                                   \
   ((sizeof(a) / sizeof(*(a))) /                         \
   static_cast<size_t>(!(sizeof(a) % sizeof(*(a)))))
 #endif
 
 // A macro to disallow the evil copy constructor and operator= functions
 // This should be used in the private: declarations for a class
-#ifndef DISALLOW_COPY_AND_ASSIGN
-#define DISALLOW_COPY_AND_ASSIGN(TypeName)      \
+#ifndef DC_DISALLOW_COPY_AND_ASSIGN
+#define DC_DISALLOW_COPY_AND_ASSIGN(TypeName)      \
   TypeName(const TypeName&);                    \
   void operator=(const TypeName&)
 #endif
 
 // A macro to disallow all the implicit constructors, namely the
 // default constructor, copy constructor and operator= functions.
 //
 // This should be used in the private: declarations for a class
 // that wants to prevent anyone from instantiating it. This is
 // especially useful for classes containing only static methods.
-#ifndef DISALLOW_IMPLICIT_CONSTRUCTORS
-#define DISALLOW_IMPLICIT_CONSTRUCTORS(TypeName) \
+#ifndef DC_DISALLOW_IMPLICIT_CONSTRUCTORS
+#define DC_DISALLOW_IMPLICIT_CONSTRUCTORS(TypeName) \
   TypeName();                                    \
-  DISALLOW_COPY_AND_ASSIGN(TypeName)
+  DC_DISALLOW_COPY_AND_ASSIGN(TypeName)
 #endif
 
 namespace double_conversion {
 
 static const int kCharSize = sizeof(char);
 
 // Returns the maximum of the two parameters.
 template <typename T>
@@ -272,17 +288,17 @@ class StringBuilder {
   }
 
  private:
   Vector<char> buffer_;
   int position_;
 
   bool is_finalized() const { return position_ < 0; }
 
-  DISALLOW_IMPLICIT_CONSTRUCTORS(StringBuilder);
+  DC_DISALLOW_IMPLICIT_CONSTRUCTORS(StringBuilder);
 };
 
 // The type-based aliasing rule allows the compiler to assume that pointers of
 // different types (for some definition of different) never alias each other.
 // Thus the following code does not work:
 //
 // float f = foo();
 // int fbits = *(int*)(&f);
--- a/mfbt/double-conversion/update.sh
+++ b/mfbt/double-conversion/update.sh
@@ -6,17 +6,16 @@
 # double-conversion source that we need.  If no revision is specified, the tip
 # revision is used.  See GIT-INFO for the last revision used.
 
 set -e
 
 LOCAL_PATCHES=""
 
 LOCAL_PATCHES="$LOCAL_PATCHES add-mfbt-api-markers.patch"
-LOCAL_PATCHES="$LOCAL_PATCHES use-StandardInteger.patch"
 LOCAL_PATCHES="$LOCAL_PATCHES use-mozilla-assertions.patch"
 LOCAL_PATCHES="$LOCAL_PATCHES ToPrecision-exponential.patch"
 
 TMPDIR=`mktemp --directory`
 LOCAL_CLONE="$TMPDIR/new-double-conversion"
 
 git clone https://github.com/google/double-conversion.git "$LOCAL_CLONE"
 
deleted file mode 100644
--- a/mfbt/double-conversion/use-StandardInteger.patch
+++ /dev/null
@@ -1,38 +0,0 @@
-diff --git a/mfbt/double-conversion/double-conversion/utils.h b/mfbt/double-conversion/double-conversion/utils.h
---- a/mfbt/double-conversion/double-conversion/utils.h
-+++ b/mfbt/double-conversion/double-conversion/utils.h
-@@ -93,34 +93,18 @@ inline void abort_noreturn() { abort(); 
- #endif
- 
- #if defined(__GNUC__)
- #define DOUBLE_CONVERSION_UNUSED __attribute__((unused))
- #else
- #define DOUBLE_CONVERSION_UNUSED
- #endif
- 
--#if defined(_WIN32) && !defined(__MINGW32__)
--
--typedef signed char int8_t;
--typedef unsigned char uint8_t;
--typedef short int16_t;  // NOLINT
--typedef unsigned short uint16_t;  // NOLINT
--typedef int int32_t;
--typedef unsigned int uint32_t;
--typedef __int64 int64_t;
--typedef unsigned __int64 uint64_t;
--// intptr_t and friends are defined in crtdefs.h through stdio.h.
--
--#else
--
- #include <stdint.h>
- 
--#endif
--
- typedef uint16_t uc16;
- 
- // The following macro works on both 32 and 64-bit platforms.
- // Usage: instead of writing 0x1234567890123456
- //      write UINT64_2PART_C(0x12345678,90123456);
- #define UINT64_2PART_C(a, b) (((static_cast<uint64_t>(a) << 32) + 0x##b##u))
- 
- 
--- a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoSession.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoSession.java
@@ -1694,24 +1694,29 @@ public class GeckoSession extends LayerS
 
         @IntDef({TARGET_WINDOW_NONE, TARGET_WINDOW_CURRENT, TARGET_WINDOW_NEW})
         public @interface TargetWindow {}
         public static final int TARGET_WINDOW_NONE = 0;
         public static final int TARGET_WINDOW_CURRENT = 1;
         public static final int TARGET_WINDOW_NEW = 2;
 
         /**
-         * A request to open an URI.
+         * A request to open an URI. This is called before each page load to
+         * allow custom behavior implementation.
+         * For example, this can be used to override the behavior of
+         * TAGET_WINDOW_NEW requests, which defaults to requesting a new
+         * GeckoSession via onNewSession.
+         *
          * @param session The GeckoSession that initiated the callback.
          * @param uri The URI to be loaded.
          * @param target The target where the window has requested to open. One of
          *               TARGET_WINDOW_*.
-         *
-         * @return Whether or not the load was handled. Returning false will allow Gecko
-         *         to continue the load as normal.
+         * @param response A response which will state whether or not the load
+         *                 was handled. If unhandled, Gecko will continue the
+         *                 load as normal.
          */
         void onLoadRequest(GeckoSession session, String uri,
                            @TargetWindow int target,
                            Response<Boolean> response);
 
         /**
         * A request has been made to open a new session. The URI is provided only for
         * informational purposes. Do not call GeckoSession.loadUri() here. Additionally, the
--- a/netwerk/test/httpserver/httpd.js
+++ b/netwerk/test/httpserver/httpd.js
@@ -64,17 +64,17 @@ function NS_ASSERT(cond, msg)
   {
     dumpn("###!!!");
     dumpn("###!!! ASSERTION" + (msg ? ": " + msg : "!"));
     dumpn("###!!! Stack follows:");
 
     var stack = new Error().stack.split(/\n/);
     dumpn(stack.map(function(val) { return "###!!!   " + val; }).join("\n"));
 
-    throw Cr.NS_ERROR_ABORT;
+    throw Components.Exception("", Cr.NS_ERROR_ABORT);
   }
 }
 
 /** Constructs an HTTP error object. */
 function HttpError(code, description)
 {
   this.code = code;
   this.description = description;
@@ -504,17 +504,17 @@ nsHttpServer.prototype =
   start: function(port)
   {
     this._start(port, "localhost")
   },
 
   _start: function(port, host)
   {
     if (this._socket)
-      throw Cr.NS_ERROR_ALREADY_INITIALIZED;
+      throw Components.Exception("", Cr.NS_ERROR_ALREADY_INITIALIZED);
 
     this._port = port;
     this._doQuit = this._socketClosed = false;
 
     this._host = host;
 
     // The listen queue needs to be long enough to handle
     // network.http.max-persistent-connections-per-server or
@@ -574,27 +574,27 @@ nsHttpServer.prototype =
       socket.asyncListen(this);
       this._port = socket.port;
       this._identity._initialize(socket.port, host, true);
       this._socket = socket;
     }
     catch (e)
     {
       dump("\n!!! could not start server on port " + port + ": " + e + "\n\n");
-      throw Cr.NS_ERROR_NOT_AVAILABLE;
+      throw Components.Exception("", Cr.NS_ERROR_NOT_AVAILABLE);
     }
   },
 
   //
   // see nsIHttpServer.stop
   //
   stop: function(callback)
   {
     if (!this._socket)
-      throw Cr.NS_ERROR_UNEXPECTED;
+      throw Components.Exception("", Cr.NS_ERROR_UNEXPECTED);
 
     // If no argument was provided to stop, return a promise.
     let returnValue = undefined;
     if (!callback) {
       returnValue = new Promise(resolve => {
         callback = resolve;
       });
     }
@@ -619,32 +619,32 @@ nsHttpServer.prototype =
   },
 
   //
   // see nsIHttpServer.registerFile
   //
   registerFile: function(path, file)
   {
     if (file && (!file.exists() || file.isDirectory()))
-      throw Cr.NS_ERROR_INVALID_ARG;
+      throw Components.Exception("", Cr.NS_ERROR_INVALID_ARG);
 
     this._handler.registerFile(path, file);
   },
 
   //
   // see nsIHttpServer.registerDirectory
   //
   registerDirectory: function(path, directory)
   {
     // XXX true path validation!
     if (path.charAt(0) != "/" ||
         path.charAt(path.length - 1) != "/" ||
         (directory &&
          (!directory.exists() || !directory.isDirectory())))
-      throw Cr.NS_ERROR_INVALID_ARG;
+      throw Components.Exception("", Cr.NS_ERROR_INVALID_ARG);
 
     // XXX determine behavior of nonexistent /foo/bar when a /foo/bar/ mapping
     //     exists!
 
     this._handler.registerDirectory(path, directory);
   },
 
   //
@@ -760,17 +760,17 @@ nsHttpServer.prototype =
   //
   QueryInterface: function(iid)
   {
     if (iid.equals(Ci.nsIHttpServer) ||
         iid.equals(Ci.nsIServerSocketListener) ||
         iid.equals(Ci.nsISupports))
       return this;
 
-    throw Cr.NS_ERROR_NO_INTERFACE;
+    throw Components.Exception("", Cr.NS_ERROR_NO_INTERFACE);
   },
 
 
   // NON-XPCOM PUBLIC API
 
   /**
    * Returns true iff this server is not running (and is not in the process of
    * serving any requests still to be processed when the server was last
@@ -935,37 +935,37 @@ ServerIdentity.prototype =
   // NSIHTTPSERVERIDENTITY
 
   //
   // see nsIHttpServerIdentity.primaryScheme
   //
   get primaryScheme()
   {
     if (this._primaryPort === -1)
-      throw Cr.NS_ERROR_NOT_INITIALIZED;
+      throw Components.Exception("", Cr.NS_ERROR_NOT_INITIALIZED);
     return this._primaryScheme;
   },
 
   //
   // see nsIHttpServerIdentity.primaryHost
   //
   get primaryHost()
   {
     if (this._primaryPort === -1)
-      throw Cr.NS_ERROR_NOT_INITIALIZED;
+      throw Components.Exception("", Cr.NS_ERROR_NOT_INITIALIZED);
     return this._primaryHost;
   },
 
   //
   // see nsIHttpServerIdentity.primaryPort
   //
   get primaryPort()
   {
     if (this._primaryPort === -1)
-      throw Cr.NS_ERROR_NOT_INITIALIZED;
+      throw Components.Exception("", Cr.NS_ERROR_NOT_INITIALIZED);
     return this._primaryPort;
   },
 
   //
   // see nsIHttpServerIdentity.add
   //
   add: function(scheme, host, port)
   {
@@ -1051,17 +1051,17 @@ ServerIdentity.prototype =
   //
   // see nsISupports.QueryInterface
   //
   QueryInterface: function(iid)
   {
     if (iid.equals(Ci.nsIHttpServerIdentity) || iid.equals(Ci.nsISupports))
       return this;
 
-    throw Cr.NS_ERROR_NO_INTERFACE;
+    throw Components.Exception("", Cr.NS_ERROR_NO_INTERFACE);
   },
 
 
   // PRIVATE IMPLEMENTATION
 
   /**
    * Initializes the primary name for the corresponding server, based on the
    * provided port number.
@@ -1121,27 +1121,27 @@ ServerIdentity.prototype =
    *   if any argument doesn't match the corresponding production
    */
   _validate: function(scheme, host, port)
   {
     if (scheme !== "http" && scheme !== "https")
     {
       dumpn("*** server only supports http/https schemes: '" + scheme + "'");
       dumpStack();
-      throw Cr.NS_ERROR_ILLEGAL_VALUE;
+      throw Components.Exception("", Cr.NS_ERROR_ILLEGAL_VALUE);
     }
     if (!HOST_REGEX.test(host))
     {
       dumpn("*** unexpected host: '" + host + "'");
-      throw Cr.NS_ERROR_ILLEGAL_VALUE;
+      throw Components.Exception("", Cr.NS_ERROR_ILLEGAL_VALUE);
     }
     if (port < 0 || port > 65535)
     {
       dumpn("*** unexpected port: '" + port + "'");
-      throw Cr.NS_ERROR_ILLEGAL_VALUE;
+      throw Components.Exception("", Cr.NS_ERROR_ILLEGAL_VALUE);
     }
   }
 };
 
 
 /**
  * Represents a connection to the server (and possibly in the future the thread
  * on which the connection is processed).
@@ -1416,17 +1416,17 @@ RequestReader.prototype =
   // see nsISupports.QueryInterface
   //
   QueryInterface: function(aIID)
   {
     if (aIID.equals(Ci.nsIInputStreamCallback) ||
         aIID.equals(Ci.nsISupports))
       return this;
 
-    throw Cr.NS_ERROR_NO_INTERFACE;
+    throw Components.Exception("", Cr.NS_ERROR_NO_INTERFACE);
   },
 
 
   // PRIVATE API
 
   /**
    * Processes unprocessed, downloaded data as a request line.
    *
@@ -2499,29 +2499,29 @@ ServerHandler.prototype =
 
   //
   // see nsIHttpServer.registerPathHandler
   //
   registerPathHandler: function(path, handler)
   {
     // XXX true path validation!
     if (path.charAt(0) != "/")
-      throw Cr.NS_ERROR_INVALID_ARG;
+      throw Components.Exception("", Cr.NS_ERROR_INVALID_ARG);
 
     this._handlerToField(handler, this._overridePaths, path);
   },
 
   //
   // see nsIHttpServer.registerPrefixHandler
   //
   registerPrefixHandler: function(path, handler)
   {
     // XXX true path validation!
     if (path.charAt(0) != "/" || path.charAt(path.length - 1) != "/")
-      throw Cr.NS_ERROR_INVALID_ARG;
+      throw Components.Exception("", Cr.NS_ERROR_INVALID_ARG);
 
     this._handlerToField(handler, this._overridePrefixes, path);
   },
 
   //
   // see nsIHttpServer.registerDirectory
   //
   registerDirectory: function(path, directory)
@@ -2530,17 +2530,17 @@ ServerHandler.prototype =
     // determining exactly how a path maps onto a mapped directory --
     // conditional is required here to deal with "/".substring(1, 0) being
     // converted to "/".substring(0, 1) per the JS specification
     var key = path.length == 1 ? "" : path.substring(1, path.length - 1);
 
     // the path-to-directory mapping code requires that the first character not
     // be "/", or it will go into an infinite loop
     if (key.charAt(0) == "/")
-      throw Cr.NS_ERROR_INVALID_ARG;
+      throw Components.Exception("", Cr.NS_ERROR_INVALID_ARG);
 
     key = toInternalPath(key, false);
 
     if (directory)
     {
       dumpn("*** mapping '" + path + "' to the location " + directory.path);
       this._pathDirectoryMap.put(key, directory);
     }
@@ -3194,17 +3194,17 @@ ServerHandler.prototype =
    *   an uninitialized Response should be initialized when this method
    *   completes with information which represents the desired error code in the
    *   ideal case or a fallback code in abnormal circumstances (i.e., 500 is a
    *   fallback for 505, per HTTP specs)
    */
   _handleError: function(errorCode, metadata, response)
   {
     if (!metadata)
-      throw Cr.NS_ERROR_NULL_POINTER;
+      throw Components.Exception("", Cr.NS_ERROR_NULL_POINTER);
 
     var errorX00 = errorCode - (errorCode % 100);
 
     try
     {
       if (!(errorCode in HTTP_ERROR_CODES))
         dumpn("*** WARNING: requested invalid error: " + errorCode);
 
@@ -3625,17 +3625,17 @@ Response.prototype =
   // PUBLIC CONSTRUCTION API
 
   //
   // see nsIHttpResponse.bodyOutputStream
   //
   get bodyOutputStream()
   {
     if (this._finished)
-      throw Cr.NS_ERROR_NOT_AVAILABLE;
+      throw Components.Exception("", Cr.NS_ERROR_NOT_AVAILABLE);
 
     if (!this._bodyOutputStream)
     {
       var pipe = new Pipe(true, false, Response.SEGMENT_SIZE, PR_UINT32_MAX,
                           null);
       this._bodyOutputStream = pipe.outputStream;
       this._bodyInputStream = pipe.inputStream;
       if (this._processAsync || this._powerSeized)
@@ -3646,97 +3646,97 @@ Response.prototype =
   },
 
   //
   // see nsIHttpResponse.write
   //
   write: function(data)
   {
     if (this._finished)
-      throw Cr.NS_ERROR_NOT_AVAILABLE;
+      throw Components.Exception("", Cr.NS_ERROR_NOT_AVAILABLE);
 
     var dataAsString = String(data);
     this.bodyOutputStream.write(dataAsString, dataAsString.length);
   },
 
   //
   // see nsIHttpResponse.setStatusLine
   //
   setStatusLine: function(httpVersion, code, description)
   {
     if (!this._headers || this._finished || this._powerSeized)
-      throw Cr.NS_ERROR_NOT_AVAILABLE;
+      throw Components.Exception("", Cr.NS_ERROR_NOT_AVAILABLE);
     this._ensureAlive();
 
     if (!(code >= 0 && code < 1000))
-      throw Cr.NS_ERROR_INVALID_ARG;
+      throw Components.Exception("", Cr.NS_ERROR_INVALID_ARG);
 
     try
     {
       var httpVer;
       // avoid version construction for the most common cases
       if (!httpVersion || httpVersion == "1.1")
         httpVer = nsHttpVersion.HTTP_1_1;
       else if (httpVersion == "1.0")
         httpVer = nsHttpVersion.HTTP_1_0;
       else
         httpVer = new nsHttpVersion(httpVersion);
     }
     catch (e)
     {
-      throw Cr.NS_ERROR_INVALID_ARG;
+      throw Components.Exception("", Cr.NS_ERROR_INVALID_ARG);
     }
 
     // Reason-Phrase = *<TEXT, excluding CR, LF>
     // TEXT          = <any OCTET except CTLs, but including LWS>
     //
     // XXX this ends up disallowing octets which aren't Unicode, I think -- not
     //     much to do if description is IDL'd as string
     if (!description)
       description = "";
     for (var i = 0; i < description.length; i++)
       if (isCTL(description.charCodeAt(i)) && description.charAt(i) != "\t")
-        throw Cr.NS_ERROR_INVALID_ARG;
+        throw Components.Exception("", Cr.NS_ERROR_INVALID_ARG);
 
     // set the values only after validation to preserve atomicity
     this._httpDescription = description;
     this._httpCode = code;
     this._httpVersion = httpVer;
   },
 
   //
   // see nsIHttpResponse.setHeader
   //
   setHeader: function(name, value, merge)
   {
     if (!this._headers || this._finished || this._powerSeized)
-      throw Cr.NS_ERROR_NOT_AVAILABLE;
+      throw Components.Exception("", Cr.NS_ERROR_NOT_AVAILABLE);
     this._ensureAlive();
 
     this._headers.setHeader(name, value, merge);
   },
 
   setHeaderNoCheck: function(name, value)
   {
     if (!this._headers || this._finished || this._powerSeized)
-      throw Cr.NS_ERROR_NOT_AVAILABLE;
+      throw Components.Exception("", Cr.NS_ERROR_NOT_AVAILABLE);
     this._ensureAlive();
 
     this._headers.setHeaderNoCheck(name, value);
   },
 
   //
   // see nsIHttpResponse.processAsync
   //
   processAsync: function()
   {
     if (this._finished)
-      throw Cr.NS_ERROR_UNEXPECTED;
+      throw Components.Exception("", Cr.NS_ERROR_UNEXPECTED);
     if (this._powerSeized)
-      throw Cr.NS_ERROR_NOT_AVAILABLE;
+      throw Components.Exception("", Cr.NS_ERROR_NOT_AVAILABLE);
     if (this._processAsync)
       return;
     this._ensureAlive();
 
     dumpn("*** processing connection " + this._connection.number + " async");
     this._processAsync = true;
 
     /*
@@ -3757,19 +3757,19 @@ Response.prototype =
   },
 
   //
   // see nsIHttpResponse.seizePower
   //
   seizePower: function()
   {
     if (this._processAsync)
-      throw Cr.NS_ERROR_NOT_AVAILABLE;
+      throw Components.Exception("", Cr.NS_ERROR_NOT_AVAILABLE);
     if (this._finished)
-      throw Cr.NS_ERROR_UNEXPECTED;
+      throw Components.Exception("", Cr.NS_ERROR_UNEXPECTED);
     if (this._powerSeized)
       return;
     this._ensureAlive();
 
     dumpn("*** forcefully seizing power over connection " +
           this._connection.number + "...");
 
     // Purge any already-written data without sending it.  We could as easily
@@ -3793,17 +3793,17 @@ Response.prototype =
   },
 
   //
   // see nsIHttpResponse.finish
   //
   finish: function()
   {
     if (!this._processAsync && !this._powerSeized)
-      throw Cr.NS_ERROR_UNEXPECTED;
+      throw Components.Exception("", Cr.NS_ERROR_UNEXPECTED);
     if (this._finished)
       return;
 
     dumpn("*** finishing connection " + this._connection.number);
     this._startAsyncProcessor(); // in case bodyOutputStream was never accessed
     if (this._bodyOutputStream)
       this._bodyOutputStream.close();
     this._finished = true;
@@ -3815,17 +3815,17 @@ Response.prototype =
   //
   // see nsISupports.QueryInterface
   //
   QueryInterface: function(iid)
   {
     if (iid.equals(Ci.nsIHttpResponse) || iid.equals(Ci.nsISupports))
       return this;
 
-    throw Cr.NS_ERROR_NO_INTERFACE;
+    throw Components.Exception("", Cr.NS_ERROR_NO_INTERFACE);
   },
 
 
   // POST-CONSTRUCTION API (not exposed externally)
 
   /**
    * The HTTP version number of this, as a string (e.g. "1.1").
    */
@@ -4112,17 +4112,17 @@ Response.prototype =
           }
         },
 
         QueryInterface: function(aIID)
         {
           if (aIID.equals(Ci.nsIRequestObserver) || aIID.equals(Ci.nsISupports))
             return this;
 
-          throw Cr.NS_ERROR_NO_INTERFACE;
+          throw Components.Exception("", Cr.NS_ERROR_NO_INTERFACE);
         }
       };
 
     var headerCopier = this._asyncCopier =
       new WriteThroughCopier(responseHeadPipe.inputStream,
                              this._connection.output,
                              copyObserver, null);
 
@@ -4175,17 +4175,17 @@ Response.prototype =
           }
         },
 
         QueryInterface: function(aIID)
         {
           if (aIID.equals(Ci.nsIRequestObserver) || aIID.equals(Ci.nsISupports))
             return this;
 
-          throw Cr.NS_ERROR_NO_INTERFACE;
+          throw Components.Exception("", Cr.NS_ERROR_NO_INTERFACE);
         }
       };
 
     dumpn("*** starting async copier of body data...");
     this._asyncCopier =
       new WriteThroughCopier(this._bodyInputStream, this._connection.output,
                             copyObserver, null);
   },
@@ -4201,17 +4201,17 @@ Response.prototype =
  * Size of the segments in the buffer used in storing response data and writing
  * it to the socket.
  */
 Response.SEGMENT_SIZE = 8192;
 
 /** Serves double duty in WriteThroughCopier implementation. */
 function notImplemented()
 {
-  throw Cr.NS_ERROR_NOT_IMPLEMENTED;
+  throw Components.Exception("", Cr.NS_ERROR_NOT_IMPLEMENTED);
 }
 
 /** Returns true iff the given exception represents stream closure. */
 function streamClosed(e)
 {
   return e === Cr.NS_BASE_STREAM_CLOSED ||
          (typeof e === "object" && e.result === Cr.NS_BASE_STREAM_CLOSED);
 }
@@ -4236,17 +4236,17 @@ function wouldBlock(e)
  * @param context : nsISupports
  *   context passed to observer when notified of start/stop
  * @throws NS_ERROR_NULL_POINTER
  *   if source, sink, or observer are null
  */
 function WriteThroughCopier(source, sink, observer, context)
 {
   if (!source || !sink || !observer)
-    throw Cr.NS_ERROR_NULL_POINTER;
+    throw Components.Exception("", Cr.NS_ERROR_NULL_POINTER);
 
   /** Stream from which data is being read. */
   this._source = source;
 
   /** Stream to which data is being written. */
   this._sink = sink;
 
   /** Observer watching this copy. */
@@ -4304,17 +4304,17 @@ WriteThroughCopier.prototype =
     if (iid.equals(Ci.nsIInputStreamCallback) ||
         iid.equals(Ci.nsIOutputStreamCallback) ||
         iid.equals(Ci.nsIRequest) ||
         iid.equals(Ci.nsISupports))
     {
       return this;
     }
 
-    throw Cr.NS_ERROR_NO_INTERFACE;
+    throw Components.Exception("", Cr.NS_ERROR_NO_INTERFACE);
   },
 
 
   // NSIINPUTSTREAMCALLBACK
 
   /**
    * Receives a more-data-in-input notification and writes the corresponding
    * data to the output.
@@ -4366,17 +4366,17 @@ WriteThroughCopier.prototype =
         this._pendingData.push(String.fromCharCode.apply(String, data));
       }
 
       dumpn("*** " + bytesConsumed + " bytes read");
 
       // Handle the zero-data edge case in the same place as all other edge
       // cases are handled.
       if (bytesWanted === 0)
-        throw Cr.NS_BASE_STREAM_CLOSED;
+        throw Components.Exception("", Cr.NS_BASE_STREAM_CLOSED);
     }
     catch (e)
     {
       if (streamClosed(e))
       {
         dumpn("*** input stream closed");
         e = bytesWanted === 0 ? Cr.NS_OK : Cr.NS_ERROR_UNEXPECTED;
       }
@@ -4790,25 +4790,25 @@ const headerUtils =
    *   fieldName converted to lowercase if it is a valid header, for characters
    *   where case conversion is possible
    */
   normalizeFieldName: function(fieldName)
   {
     if (fieldName == "")
     {
       dumpn("*** Empty fieldName");
-      throw Cr.NS_ERROR_INVALID_ARG;
+      throw Components.Exception("", Cr.NS_ERROR_INVALID_ARG);
     }
 
     for (var i = 0, sz = fieldName.length; i < sz; i++)
     {
       if (!IS_TOKEN_ARRAY[fieldName.charCodeAt(i)])
       {
         dumpn(fieldName + " is not a valid header field name!");
-        throw Cr.NS_ERROR_INVALID_ARG;
+        throw Components.Exception("", Cr.NS_ERROR_INVALID_ARG);
       }
     }
 
     return fieldName.toLowerCase();
   },
 
   /**
    * Ensures that fieldValue is a valid header field value (although not
@@ -4847,17 +4847,17 @@ const headerUtils =
     val = val.replace(/^ +/, "").replace(/ +$/, "");
 
     // that should have taken care of all CTLs, so val should contain no CTLs
     dumpn("*** Normalized value: '" + val + "'");
     for (var i = 0, len = val.length; i < len; i++)
       if (isCTL(val.charCodeAt(i)))
       {
         dump("*** Char " + i + " has charcode " + val.charCodeAt(i));
-        throw Cr.NS_ERROR_INVALID_ARG;
+        throw Components.Exception("", Cr.NS_ERROR_INVALID_ARG);
       }
 
     // XXX disallows quoted-pair where CHAR is a CTL -- will not invalidly
     //     normalize, however, so this can be construed as a tightening of the
     //     spec and not entirely as a bug
     return val;
   }
 };
@@ -5060,17 +5060,17 @@ nsHttpHeaders.prototype =
    */
   getHeaderValues: function(fieldName)
   {
     var name = headerUtils.normalizeFieldName(fieldName);
 
     if (name in this._headers)
       return this._headers[name];
     else
-      throw Cr.NS_ERROR_NOT_AVAILABLE;
+      throw Components.Exception("", Cr.NS_ERROR_NOT_AVAILABLE);
   },
 
   /**
    * Returns true if a header with the given field name exists in this, false
    * otherwise.
    *
    * @param fieldName : string
    *   the field name whose existence is to be determined in this
@@ -5121,27 +5121,27 @@ nsSimpleEnumerator.prototype =
 {
   hasMoreElements: function()
   {
     return this._nextIndex < this._items.length;
   },
   getNext: function()
   {
     if (!this.hasMoreElements())
-      throw Cr.NS_ERROR_NOT_AVAILABLE;
+      throw Components.Exception("", Cr.NS_ERROR_NOT_AVAILABLE);
 
     return this._items[this._nextIndex++];
   },
   QueryInterface: function(aIID)
   {
     if (Ci.nsISimpleEnumerator.equals(aIID) ||
         Ci.nsISupports.equals(aIID))
       return this;
 
-    throw Cr.NS_ERROR_NO_INTERFACE;
+    throw Components.Exception("", Cr.NS_ERROR_NO_INTERFACE);
   }
 };
 
 
 /**
  * A representation of the data in an HTTP request.
  *
  * @param port : uint
@@ -5307,17 +5307,17 @@ Request.prototype =
   //
   // see nsISupports.QueryInterface
   //
   QueryInterface: function(iid)
   {
     if (iid.equals(Ci.nsIHttpRequest) || iid.equals(Ci.nsISupports))
       return this;
 
-    throw Cr.NS_ERROR_NO_INTERFACE;
+    throw Components.Exception("", Cr.NS_ERROR_NO_INTERFACE);
   },
 
 
   // PRIVATE IMPLEMENTATION
   
   /** Ensures a property bag has been created for ad-hoc behaviors. */
   _ensurePropertyBag: function()
   {
--- a/netwerk/test/httpserver/test/test_headers.js
+++ b/netwerk/test/httpserver/test/test_headers.js
@@ -46,17 +46,17 @@ function assertInvalidHeader(fieldName, 
   try
   {
     headers.setHeader(fieldName, fieldValue, false);
     throw "Setting (" + fieldName + ", " +
           fieldValue + ") as header succeeded!";
   }
   catch (e)
   {
-    if (e !== Cr.NS_ERROR_INVALID_ARG)
+    if (e.result !== Cr.NS_ERROR_INVALID_ARG)
       do_throw("Unexpected exception thrown: " + e);
   }
 }
 
 
 function run_test()
 {
   testHeaderValidity();
@@ -116,28 +116,28 @@ function testGetHeader()
 
   try
   {
     headers.getHeader(":");
     throw "Failed to throw for invalid header";
   }
   catch (e)
   {
-    if (e !== Cr.NS_ERROR_INVALID_ARG)
+    if (e.result !== Cr.NS_ERROR_INVALID_ARG)
       do_throw("headers.getHeader(':') must throw invalid arg");
   }
 
   try
   {
     headers.getHeader("valid");
     throw 'header doesn\'t exist';
   }
   catch (e)
   {
-    if (e !== Cr.NS_ERROR_NOT_AVAILABLE)
+    if (e.result !== Cr.NS_ERROR_NOT_AVAILABLE)
       do_throw("shouldn't be a header named 'valid' in headers!");
   }
 }
 
 function testHeaderEnumerator()
 {
   var headers = new nsHttpHeaders();
 
@@ -178,12 +178,12 @@ function testHasHeader()
 
   try
   {
     headers.hasHeader(":");
     throw "failed to throw";
   }
   catch (e)
   {
-    if (e !== Cr.NS_ERROR_INVALID_ARG)
+    if (e.result !== Cr.NS_ERROR_INVALID_ARG)
       do_throw(".hasHeader for an invalid name should throw");
   }
 }
--- a/netwerk/test/httpserver/test/test_host.js
+++ b/netwerk/test/httpserver/test/test_host.js
@@ -227,39 +227,39 @@ function checkPrimariesThrow(id)
 {
   var threw = false;
   try
   {
     id.primaryScheme;
   }
   catch (e)
   {
-    threw = e === Cr.NS_ERROR_NOT_INITIALIZED;
+    threw = e.result === Cr.NS_ERROR_NOT_INITIALIZED;
   }
   Assert.ok(threw);
 
   threw = false;
   try
   {
     id.primaryHost;
   }
   catch (e)
   {
-    threw = e === Cr.NS_ERROR_NOT_INITIALIZED;
+    threw = e.result === Cr.NS_ERROR_NOT_INITIALIZED;
   }
   Assert.ok(threw);
 
   threw = false;
   try
   {
     id.primaryPort;
   }
   catch (e)
   {
-    threw = e === Cr.NS_ERROR_NOT_INITIALIZED;
+    threw = e.result === Cr.NS_ERROR_NOT_INITIALIZED;
   }
   Assert.ok(threw);
 }
 
 /**
  * Utility function to check for a 400 response.
  */
 function check400(data)
--- a/netwerk/test/unit/test_NetUtil.js
+++ b/netwerk/test/unit/test_NetUtil.js
@@ -572,16 +572,17 @@ function test_newChannel_with_options()
 
   function checkEqualToIOSChannel(channel) {
     Assert.ok(iosChannel.URI.equals(channel.URI));  
   }
 
   checkEqualToIOSChannel(NetUtil.newChannel({
     uri,
     loadingPrincipal: Services.scriptSecurityManager.getSystemPrincipal(),
+    securityFlags: Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL,
     contentPolicyType: Ci.nsIContentPolicy.TYPE_OTHER,
   }));
 
   checkEqualToIOSChannel(NetUtil.newChannel({
     uri,
     loadUsingSystemPrincipal: true,
   }));
 
@@ -593,27 +594,32 @@ function test_newChannel_with_wrong_opti
   let uri = "data:text/plain,";
   let systemPrincipal = Services.scriptSecurityManager.getSystemPrincipal();
 
   Assert.throws(() => {
     NetUtil.newChannel({ uri, loadUsingSystemPrincipal: true }, null, null);
   }, /requires a single object argument/);
 
   Assert.throws(() => {
-    NetUtil.newChannel({});
+    NetUtil.newChannel({ loadUsingSystemPrincipal: true });
   }, /requires the 'uri' property/);
 
   Assert.throws(() => {
-    NetUtil.newChannel({ uri });
+    NetUtil.newChannel({ uri, loadingNode: true });
+  }, /requires the 'securityFlags'/);
+
+  Assert.throws(() => {
+    NetUtil.newChannel({ uri, securityFlags: 0 });
   }, /requires at least one of the 'loadingNode'/);
 
   Assert.throws(() => {
     NetUtil.newChannel({
       uri,
       loadingPrincipal: systemPrincipal,
+      securityFlags: 0,
     });
   }, /requires the 'contentPolicyType'/);
 
   Assert.throws(() => {
     NetUtil.newChannel({
       uri,
       loadUsingSystemPrincipal: systemPrincipal,
     });
@@ -850,16 +856,16 @@ function test_readInputStreamToString_in
   test_deprecated_newChannel_API_with_nsIFile,
   test_readInputStreamToString,
   test_readInputStreamToString_no_input_stream,
   test_readInputStreamToString_no_bytes_arg,
   test_readInputStreamToString_blocking_stream,
   test_readInputStreamToString_too_many_bytes,
   test_readInputStreamToString_with_charset,
   test_readInputStreamToString_invalid_sequence,
-].forEach(add_test);
+].forEach(f => add_test(f));
 var index = 0;
 
 function run_test()
 {
   run_next_test();
 }
 
--- a/netwerk/test/unit/test_file_protocol.js
+++ b/netwerk/test/unit/test_file_protocol.js
@@ -8,17 +8,17 @@ const special_type = "application/x-our-
 
 [
   test_read_file,
   test_read_dir_1,
   test_read_dir_2,
   test_upload_file,
   test_load_replace,
   do_test_finished
-].forEach(add_test);
+].forEach(f => add_test(f));
 
 function getFile(key) {
   var dirSvc = Cc["@mozilla.org/file/directory_service;1"]
                  .getService(Ci.nsIProperties);
   return dirSvc.get(key, Ci.nsIFile);
 }
 
 function new_file_input_stream(file, buffered) {
--- a/netwerk/test/unit/test_predictor.js
+++ b/netwerk/test/unit/test_predictor.js
@@ -593,17 +593,17 @@ function registerObserver() {
   Services.obs.addObserver(observer, "predictor-reset-complete");
 }
 
 function unregisterObserver() {
   Services.obs.removeObserver(observer, "predictor-reset-complete");
 }
 
 function run_test_real() {
-  tests.forEach(add_test);
+  tests.forEach(f => add_test(f));
   do_get_profile();
 
   Services.prefs.setBoolPref("network.predictor.enabled", true);
   Services.prefs.setBoolPref("network.predictor.cleaned-up", true);
   Services.prefs.setBoolPref("network.predictor.doing-tests", true);
 
   predictor = Cc["@mozilla.org/network/predictor;1"].getService(Ci.nsINetworkPredictor);
 
--- a/netwerk/test/unit/test_reopen.js
+++ b/netwerk/test/unit/test_reopen.js
@@ -18,17 +18,17 @@ var httpserv = null;
 
 [
   test_data_channel,
   test_http_channel,
   test_file_channel,
   // Commented by default as it relies on external ressources
   //test_ftp_channel,
   end
-].forEach(add_test);
+].forEach(f => add_test(f));
 
 // Utility functions
 
 function makeChan(url) {
   return chan = NetUtil.newChannel({uri: url, loadUsingSystemPrincipal: true})
                        .QueryInterface(Ci.nsIChannel);
 }
 
--- a/security/manager/ssl/tests/unit/test_cert_blocklist.js
+++ b/security/manager/ssl/tests/unit/test_cert_blocklist.js
@@ -348,18 +348,18 @@ function run_test() {
     // Check the cert validates before we load the blocklist
     file = "test_onecrl/same-issuer-ee.pem";
     verify_cert(file, PRErrorCodeSuccess);
 
     run_next_test();
   });
 
   // blocklist load is async so we must use add_test from here
-  add_task(function* () {
-    yield fetch_blocklist();
+  add_task(function () {
+    return fetch_blocklist();
   });
 
   add_test(function() {
     // The blocklist will be loaded now. Let's check the data is sane.
     // In particular, we should still have the revoked issuer / serial pair
     // that was in both revocations.txt and the blocklist.
     ok(test_is_revoked(certList, "another imaginary issuer", "serial2."),
       "issuer / serial pair should be blocked");
--- a/security/nss/TAG-INFO
+++ b/security/nss/TAG-INFO
@@ -1,1 +1,1 @@
-f0d4789c8916
+c5dffd6269ea
--- a/security/nss/cmd/shlibsign/shlibsign.c
+++ b/security/nss/cmd/shlibsign/shlibsign.c
@@ -143,17 +143,17 @@ writeItem(PRFileDesc *fd, CK_VOID_PTR pV
 
     encodeInt(buf, ulValueLen);
     bytesWritten = PR_Write(fd, buf, 4);
     if (bytesWritten != 4) {
         lperror(file);
         return PR_FAILURE;
     }
     bytesWritten = PR_Write(fd, pValue, ulValueLen);
-    if (bytesWritten != ulValueLen) {
+    if (bytesWritten < 0 || (CK_ULONG)bytesWritten != ulValueLen) {
         lperror(file);
         return PR_FAILURE;
     }
     return PR_SUCCESS;
 }
 
 static const unsigned char prime[] = { 0x00,
                                        0x97, 0x44, 0x1d, 0xcc, 0x0d, 0x39, 0x0d, 0x8d,
--- a/security/nss/coreconf/coreconf.dep
+++ b/security/nss/coreconf/coreconf.dep
@@ -5,8 +5,9 @@
 
 /*
  * A dummy header file that is a dependency for all the object files.
  * Used to force a full recompilation of NSS in Mozilla's Tinderbox
  * depend builds.  See comments in rules.mk.
  */
 
 #error "Do not include this header file."
+
--- a/security/nss/cpputil/tls_parser.cc
+++ b/security/nss/cpputil/tls_parser.cc
@@ -41,16 +41,31 @@ bool TlsParser::Read(DataBuffer* val, si
     return false;
   }
 
   val->Assign(ptr(), len);
   consume(len);
   return true;
 }
 
+bool TlsParser::ReadFromMark(DataBuffer* val, size_t len, size_t mark) {
+  auto saved = offset_;
+  offset_ = mark;
+
+  if (remaining() < len) {
+    offset_ = saved;
+    return false;
+  }
+
+  val->Assign(ptr(), len);
+
+  offset_ = saved;
+  return true;
+}
+
 bool TlsParser::ReadVariable(DataBuffer* val, size_t len_size) {
   uint32_t len;
   if (!Read(&len, len_size)) {
     return false;
   }
   return Read(val, len);
 }
 
--- a/security/nss/cpputil/tls_parser.h
+++ b/security/nss/cpputil/tls_parser.h
@@ -118,16 +118,17 @@ class TlsParser {
   TlsParser(const uint8_t* data, size_t len) : buffer_(data, len), offset_(0) {}
   explicit TlsParser(const DataBuffer& buf) : buffer_(buf), offset_(0) {}
 
   bool Read(uint8_t* val);
   // Read an integral type of specified width.
   bool Read(uint32_t* val, size_t size);
   // Reads len bytes into dest buffer, overwriting it.
   bool Read(DataBuffer* dest, size_t len);
+  bool ReadFromMark(DataBuffer* val, size_t len, size_t mark);
   // Reads bytes into dest buffer, overwriting it.  The number of bytes is
   // determined by reading from len_size bytes from the stream first.
   bool ReadVariable(DataBuffer* dest, size_t len_size);
 
   bool Skip(size_t len);
   bool SkipVariable(size_t len_size);
 
   size_t consumed() const { return offset_; }
--- a/security/nss/gtests/ssl_gtest/libssl_internals.c
+++ b/security/nss/gtests/ssl_gtest/libssl_internals.c
@@ -232,32 +232,33 @@ PRBool SSLInt_SendAlert(PRFileDesc *fd, 
 SECStatus SSLInt_AdvanceReadSeqNum(PRFileDesc *fd, PRUint64 to) {
   sslSocket *ss;
   ssl3CipherSpec *spec;
 
   ss = ssl_FindSocket(fd);
   if (!ss) {
     return SECFailure;
   }
-  if (to >= RECORD_SEQ_MAX) {
+  if (to > RECORD_SEQ_MAX) {
     PORT_SetError(SEC_ERROR_INVALID_ARGS);
     return SECFailure;
   }
   ssl_GetSpecWriteLock(ss);
   spec = ss->ssl3.crSpec;
-  spec->seqNum = to;
+  spec->nextSeqNum = to;
 
   /* For DTLS, we need to fix the record sequence number.  For this, we can just
    * scrub the entire structure on the assumption that the new sequence number
    * is far enough past the last received sequence number. */
-  if (spec->seqNum <= spec->recvdRecords.right + DTLS_RECVD_RECORDS_WINDOW) {
+  if (spec->nextSeqNum <=
+      spec->recvdRecords.right + DTLS_RECVD_RECORDS_WINDOW) {
     PORT_SetError(SEC_ERROR_INVALID_ARGS);
     return SECFailure;
   }
-  dtls_RecordSetRecvd(&spec->recvdRecords, spec->seqNum);
+  dtls_RecordSetRecvd(&spec->recvdRecords, spec->nextSeqNum - 1);
 
   ssl_ReleaseSpecWriteLock(ss);
   return SECSuccess;
 }
 
 SECStatus SSLInt_AdvanceWriteSeqNum(PRFileDesc *fd, PRUint64 to) {
   sslSocket *ss;
 
@@ -265,31 +266,31 @@ SECStatus SSLInt_AdvanceWriteSeqNum(PRFi
   if (!ss) {
     return SECFailure;
   }
   if (to >= RECORD_SEQ_MAX) {
     PORT_SetError(SEC_ERROR_INVALID_ARGS);
     return SECFailure;
   }
   ssl_GetSpecWriteLock(ss);
-  ss->ssl3.cwSpec->seqNum = to;
+  ss->ssl3.cwSpec->nextSeqNum = to;
   ssl_ReleaseSpecWriteLock(ss);
   return SECSuccess;
 }
 
 SECStatus SSLInt_AdvanceWriteSeqByAWindow(PRFileDesc *fd, PRInt32 extra) {
   sslSocket *ss;
   sslSequenceNumber to;
 
   ss = ssl_FindSocket(fd);
   if (!ss) {
     return SECFailure;
   }
   ssl_GetSpecReadLock(ss);
-  to = ss->ssl3.cwSpec->seqNum + DTLS_RECVD_RECORDS_WINDOW + extra;
+  to = ss->ssl3.cwSpec->nextSeqNum + DTLS_RECVD_RECORDS_WINDOW + extra;
   ssl_ReleaseSpecReadLock(ss);
   return SSLInt_AdvanceWriteSeqNum(fd, to);
 }
 
 SSLKEAType SSLInt_GetKEAType(SSLNamedGroup group) {
   const sslNamedGroupDef *groupDef = ssl_LookupNamedGroup(group);
   if (!groupDef) return ssl_kea_null;
 
--- a/security/nss/gtests/ssl_gtest/ssl_agent_unittest.cc
+++ b/security/nss/gtests/ssl_gtest/ssl_agent_unittest.cc
@@ -3,30 +3,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 "ssl.h"
 #include "sslerr.h"
 #include "sslproto.h"
 
-// This is an internal header, used to get TLS_1_3_DRAFT_VERSION.
-#include "ssl3prot.h"
-
 #include <memory>
 
 #include "databuffer.h"
 #include "tls_agent.h"
 #include "tls_connect.h"
 #include "tls_filter.h"
 #include "tls_parser.h"
 
 namespace nss_test {
 
-static const uint8_t kD13 = TLS_1_3_DRAFT_VERSION;
 // This is a 1-RTT ClientHello with ECDHE.
 const static uint8_t kCannedTls13ClientHello[] = {
     0x01, 0x00, 0x00, 0xcf, 0x03, 0x03, 0x6c, 0xb3, 0x46, 0x81, 0xc8, 0x1a,
     0xf9, 0xd2, 0x05, 0x97, 0x48, 0x7c, 0xa8, 0x31, 0x03, 0x1c, 0x06, 0xa8,
     0x62, 0xb1, 0x90, 0xd6, 0x21, 0x44, 0x7f, 0xc1, 0x9b, 0x87, 0x3e, 0xad,
     0x91, 0x85, 0x00, 0x00, 0x06, 0x13, 0x01, 0x13, 0x03, 0x13, 0x02, 0x01,
     0x00, 0x00, 0xa0, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x09, 0x00, 0x00, 0x06,
     0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0xff, 0x01, 0x00, 0x01, 0x00, 0x00,
@@ -37,26 +33,17 @@ const static uint8_t kCannedTls13ClientH
     0xbf, 0x73, 0x47, 0x3c, 0x9c, 0x65, 0x8c, 0x47, 0x6d, 0x57, 0x22, 0x8a,
     0xc2, 0xb3, 0xc6, 0x80, 0x72, 0x86, 0x08, 0x86, 0x8f, 0x52, 0xc5, 0xcb,
     0xbf, 0x2a, 0xb5, 0x59, 0x64, 0xcc, 0x0c, 0x49, 0x95, 0x36, 0xe4, 0xd9,
     0x2f, 0xd4, 0x24, 0x66, 0x71, 0x6f, 0x5d, 0x70, 0xe2, 0xa0, 0xea, 0x26,
     0x00, 0x2b, 0x00, 0x03, 0x02, 0x7f, kD13, 0x00, 0x0d, 0x00, 0x20, 0x00,
     0x1e, 0x04, 0x03, 0x05, 0x03, 0x06, 0x03, 0x02, 0x03, 0x08, 0x04, 0x08,
     0x05, 0x08, 0x06, 0x04, 0x01, 0x05, 0x01, 0x06, 0x01, 0x02, 0x01, 0x04,
     0x02, 0x05, 0x02, 0x06, 0x02, 0x02, 0x02};
-
-const static uint8_t kCannedTls13ServerHello[] = {
-    0x03, 0x03, 0x9c, 0xbc, 0x14, 0x9b, 0x0e, 0x2e, 0xfa, 0x0d, 0xf3,
-    0xf0, 0x5c, 0x70, 0x7a, 0xe0, 0xd1, 0x9b, 0x3e, 0x5a, 0x44, 0x6b,
-    0xdf, 0xe5, 0xc2, 0x28, 0x64, 0xf7, 0x00, 0xc1, 0x9c, 0x08, 0x76,
-    0x08, 0x00, 0x13, 0x01, 0x00, 0x00, 0x2e, 0x00, 0x33, 0x00, 0x24,
-    0x00, 0x1d, 0x00, 0x20, 0xc2, 0xcf, 0x23, 0x17, 0x64, 0x23, 0x03,
-    0xf0, 0xfb, 0x45, 0x98, 0x26, 0xd1, 0x65, 0x24, 0xa1, 0x6c, 0xa9,
-    0x80, 0x8f, 0x2c, 0xac, 0x0a, 0xea, 0x53, 0x3a, 0xcb, 0xe3, 0x08,
-    0x84, 0xae, 0x19, 0x00, 0x2b, 0x00, 0x02, 0x7f, kD13};
+static const size_t kFirstFragmentSize = 20;
 static const char *k0RttData = "ABCDEF";
 
 TEST_P(TlsAgentTest, EarlyFinished) {
   DataBuffer buffer;
   MakeTrivialHandshakeRecord(kTlsHandshakeFinished, 0, &buffer);
   ExpectAlert(kTlsAlertUnexpectedMessage);
   ProcessMessage(buffer, TlsAgent::STATE_ERROR,
                  SSL_ERROR_RX_UNEXPECTED_FINISHED);
@@ -69,75 +56,79 @@ TEST_P(TlsAgentTest, EarlyCertificateVer
   ProcessMessage(buffer, TlsAgent::STATE_ERROR,
                  SSL_ERROR_RX_UNEXPECTED_CERT_VERIFY);
 }
 
 TEST_P(TlsAgentTestClient13, CannedHello) {
   DataBuffer buffer;
   EnsureInit();
   DataBuffer server_hello;
-  MakeHandshakeMessage(kTlsHandshakeServerHello, kCannedTls13ServerHello,
-                       sizeof(kCannedTls13ServerHello), &server_hello);
+  auto sh = MakeCannedTls13ServerHello();
+  MakeHandshakeMessage(kTlsHandshakeServerHello, sh.data(), sh.len(),
+                       &server_hello);
   MakeRecord(kTlsHandshakeType, SSL_LIBRARY_VERSION_TLS_1_3,
              server_hello.data(), server_hello.len(), &buffer);
   ProcessMessage(buffer, TlsAgent::STATE_CONNECTING);
 }
 
 TEST_P(TlsAgentTestClient13, EncryptedExtensionsInClear) {
   DataBuffer server_hello;
-  MakeHandshakeMessage(kTlsHandshakeServerHello, kCannedTls13ServerHello,
-                       sizeof(kCannedTls13ServerHello), &server_hello);
+  auto sh = MakeCannedTls13ServerHello();
+  MakeHandshakeMessage(kTlsHandshakeServerHello, sh.data(), sh.len(),
+                       &server_hello);
   DataBuffer encrypted_extensions;
   MakeHandshakeMessage(kTlsHandshakeEncryptedExtensions, nullptr, 0,
                        &encrypted_extensions, 1);
   server_hello.Append(encrypted_extensions);
   DataBuffer buffer;
   MakeRecord(kTlsHandshakeType, SSL_LIBRARY_VERSION_TLS_1_3,
              server_hello.data(), server_hello.len(), &buffer);
   EnsureInit();
   ExpectAlert(kTlsAlertUnexpectedMessage);
   ProcessMessage(buffer, TlsAgent::STATE_ERROR,
                  SSL_ERROR_RX_UNEXPECTED_HANDSHAKE);
 }
 
 TEST_F(TlsAgentStreamTestClient, EncryptedExtensionsInClearTwoPieces) {
   DataBuffer server_hello;
-  MakeHandshakeMessage(kTlsHandshakeServerHello, kCannedTls13ServerHello,
-                       sizeof(kCannedTls13ServerHello), &server_hello);
+  auto sh = MakeCannedTls13ServerHello();
+  MakeHandshakeMessage(kTlsHandshakeServerHello, sh.data(), sh.len(),
+                       &server_hello);
   DataBuffer encrypted_extensions;
   MakeHandshakeMessage(kTlsHandshakeEncryptedExtensions, nullptr, 0,
                        &encrypted_extensions, 1);
   server_hello.Append(encrypted_extensions);
   DataBuffer buffer;
   MakeRecord(kTlsHandshakeType, SSL_LIBRARY_VERSION_TLS_1_3,
-             server_hello.data(), 20, &buffer);
+             server_hello.data(), kFirstFragmentSize, &buffer);
 
   DataBuffer buffer2;
   MakeRecord(kTlsHandshakeType, SSL_LIBRARY_VERSION_TLS_1_3,
-             server_hello.data() + 20, server_hello.len() - 20, &buffer2);
+             server_hello.data() + kFirstFragmentSize,
+             server_hello.len() - kFirstFragmentSize, &buffer2);
 
   EnsureInit();
   agent_->SetVersionRange(SSL_LIBRARY_VERSION_TLS_1_3,
                           SSL_LIBRARY_VERSION_TLS_1_3);
   ProcessMessage(buffer, TlsAgent::STATE_CONNECTING);
   ExpectAlert(kTlsAlertUnexpectedMessage);
   ProcessMessage(buffer2, TlsAgent::STATE_ERROR,
                  SSL_ERROR_RX_UNEXPECTED_HANDSHAKE);
 }
 
 TEST_F(TlsAgentDgramTestClient, EncryptedExtensionsInClearTwoPieces) {
+  auto sh = MakeCannedTls13ServerHello();
   DataBuffer server_hello_frag1;
-  MakeHandshakeMessageFragment(
-      kTlsHandshakeServerHello, kCannedTls13ServerHello,
-      sizeof(kCannedTls13ServerHello), &server_hello_frag1, 0, 0, 20);
+  MakeHandshakeMessageFragment(kTlsHandshakeServerHello, sh.data(), sh.len(),
+                               &server_hello_frag1, 0, 0, kFirstFragmentSize);
   DataBuffer server_hello_frag2;
-  MakeHandshakeMessageFragment(
-      kTlsHandshakeServerHello, kCannedTls13ServerHello + 20,
-      sizeof(kCannedTls13ServerHello), &server_hello_frag2, 0, 20,
-      sizeof(kCannedTls13ServerHello) - 20);
+  MakeHandshakeMessageFragment(kTlsHandshakeServerHello,
+                               sh.data() + kFirstFragmentSize, sh.len(),
+                               &server_hello_frag2, 0, kFirstFragmentSize,
+                               sh.len() - kFirstFragmentSize);
   DataBuffer encrypted_extensions;
   MakeHandshakeMessage(kTlsHandshakeEncryptedExtensions, nullptr, 0,
                        &encrypted_extensions, 1);
   server_hello_frag2.Append(encrypted_extensions);
   DataBuffer buffer;
   MakeRecord(kTlsHandshakeType, SSL_LIBRARY_VERSION_TLS_1_3,
              server_hello_frag1.data(), server_hello_frag1.len(), &buffer);
 
--- a/security/nss/gtests/ssl_gtest/ssl_ciphersuite_unittest.cc
+++ b/security/nss/gtests/ssl_gtest/ssl_ciphersuite_unittest.cc
@@ -161,18 +161,18 @@ class TlsCipherSuiteTestBase : public Tl
       case ssl_calg_rc2:
       case ssl_calg_des:
       case ssl_calg_idea:
       case ssl_calg_fortezza:
       case ssl_calg_camellia:
       case ssl_calg_seed:
         break;
     }
-    EXPECT_TRUE(false) << "No limit for " << csinfo_.cipherSuiteName;
-    return 1ULL < 48;
+    ADD_FAILURE() << "No limit for " << csinfo_.cipherSuiteName;
+    return 0;
   }
 
   uint64_t last_safe_write() const {
     uint64_t limit = record_limit() - 1;
     if (version_ < SSL_LIBRARY_VERSION_TLS_1_1 &&
         (csinfo_.symCipher == ssl_calg_3des ||
          csinfo_.symCipher == ssl_calg_aes)) {
       // 1/n-1 record splitting needs space for two records.
@@ -241,22 +241,23 @@ TEST_P(TlsCipherSuiteTest, ReadLimit) {
   ConnectAndCheckCipherSuite();
   if (version_ < SSL_LIBRARY_VERSION_TLS_1_3) {
     uint64_t last = last_safe_write();
     EXPECT_EQ(SECSuccess, SSLInt_AdvanceWriteSeqNum(client_->ssl_fd(), last));
     EXPECT_EQ(SECSuccess, SSLInt_AdvanceReadSeqNum(server_->ssl_fd(), last));
 
     client_->SendData(10, 10);
     server_->ReadBytes();  // This should be OK.
+    server_->ReadBytes();  // Read twice to flush any 1,N-1 record splitting.
   } else {
     // In TLS 1.3, reading or writing triggers a KeyUpdate.  That would mean
     // that the sequence numbers would reset and we wouldn't hit the limit.  So
-    // we move the sequence number to one less than the limit directly and don't
-    // test sending and receiving just before the limit.
-    uint64_t last = record_limit() - 1;
+    // move the sequence number to the limit directly and don't test sending and
+    // receiving just before the limit.
+    uint64_t last = record_limit();
     EXPECT_EQ(SECSuccess, SSLInt_AdvanceReadSeqNum(server_->ssl_fd(), last));
   }
 
   // The payload needs to be big enough to pass for encrypted.  The code checks
   // the limit before it tries to decrypt.
   static const uint8_t payload[32] = {6};
   DataBuffer record;
   uint64_t epoch;
--- a/security/nss/gtests/ssl_gtest/ssl_drop_unittest.cc
+++ b/security/nss/gtests/ssl_gtest/ssl_drop_unittest.cc
@@ -61,27 +61,31 @@ TEST_P(TlsConnectDatagramPre13, DropClie
 }
 
 // This drops the server's second flight three times.
 TEST_P(TlsConnectDatagramPre13, DropServerSecondFlightThrice) {
   server_->SetFilter(std::make_shared<SelectiveDropFilter>(0xe));
   Connect();
 }
 
-class TlsDropDatagram13 : public TlsConnectDatagram13 {
+class TlsDropDatagram13 : public TlsConnectDatagram13,
+                          public ::testing::WithParamInterface<bool> {
  public:
   TlsDropDatagram13()
       : client_filters_(),
         server_filters_(),
         expected_client_acks_(0),
         expected_server_acks_(1) {}
 
   void SetUp() override {
     TlsConnectDatagram13::SetUp();
     ConfigureSessionCache(RESUME_NONE, RESUME_NONE);
+    int short_header = GetParam() ? PR_TRUE : PR_FALSE;
+    client_->SetOption(SSL_ENABLE_DTLS_SHORT_HEADER, short_header);
+    server_->SetOption(SSL_ENABLE_DTLS_SHORT_HEADER, short_header);
     SetFilters();
   }
 
   void SetFilters() {
     EnsureTlsSetup();
     client_filters_.Init(client_);
     server_filters_.Init(server_);
   }
@@ -181,66 +185,66 @@ class TlsDropDatagram13 : public TlsConn
   size_t expected_client_acks_;
   size_t expected_server_acks_;
 };
 
 // All of these tests produce a minimum one ACK, from the server
 // to the client upon receiving the client Finished.
 // Dropping complete first and second flights does not produce
 // ACKs
-TEST_F(TlsDropDatagram13, DropClientFirstFlightOnce) {
+TEST_P(TlsDropDatagram13, DropClientFirstFlightOnce) {
   client_filters_.drop_->Reset({0});
   StartConnect();
   client_->Handshake();
   server_->Handshake();
   CheckedHandshakeSendReceive();
   CheckAcks(server_filters_, 0, {0x0002000000000000ULL});
 }
 
-TEST_F(TlsDropDatagram13, DropServerFirstFlightOnce) {
+TEST_P(TlsDropDatagram13, DropServerFirstFlightOnce) {
   server_filters_.drop_->Reset(0xff);
   StartConnect();
   client_->Handshake();
   // Send the first flight, all dropped.
   server_->Handshake();
   server_filters_.drop_->Disable();
   CheckedHandshakeSendReceive();
   CheckAcks(server_filters_, 0, {0x0002000000000000ULL});
 }
 
 // Dropping the server's first record also does not produce
 // an ACK because the next record is ignored.
 // TODO(ekr@rtfm.com): We should generate an empty ACK.
-TEST_F(TlsDropDatagram13, DropServerFirstRecordOnce) {
+TEST_P(TlsDropDatagram13, DropServerFirstRecordOnce) {
   server_filters_.drop_->Reset({0});
   StartConnect();
   client_->Handshake();
   server_->Handshake();
   Handshake();
   CheckedHandshakeSendReceive();
   CheckAcks(server_filters_, 0, {0x0002000000000000ULL});
 }
 
 // Dropping the second packet of the server's flight should
 // produce an ACK.
-TEST_F(TlsDropDatagram13, DropServerSecondRecordOnce) {
+TEST_P(TlsDropDatagram13, DropServerSecondRecordOnce) {
   server_filters_.drop_->Reset({1});
   StartConnect();
   client_->Handshake();
   server_->Handshake();
   HandshakeAndAck(client_);
   expected_client_acks_ = 1;
   CheckedHandshakeSendReceive();
   CheckAcks(client_filters_, 0, {0});  // ServerHello
   CheckAcks(server_filters_, 0, {0x0002000000000000ULL});
 }
 
 // Drop the server ACK and verify that the client retransmits
 // the ClientHello.
-TEST_F(TlsDropDatagram13, DropServerAckOnce) {
+TEST_P(TlsDropDatagram13, DropServerAckOnce) {
   StartConnect();
   client_->Handshake();
   server_->Handshake();
   // At this point the server has sent it's first flight,
   // so make it drop the ACK.
   server_filters_.drop_->Reset({0});
   client_->Handshake();  // Send the client Finished.
   server_->Handshake();  // Receive the Finished and send the ACK.
@@ -258,17 +262,17 @@ TEST_F(TlsDropDatagram13, DropServerAckO
   EXPECT_EQ(PR_WOULD_BLOCK_ERROR, PORT_GetError());
   CheckPostHandshake();
   // There should be two copies of the finished ACK
   CheckAcks(server_filters_, 0, {0x0002000000000000ULL});
   CheckAcks(server_filters_, 1, {0x0002000000000000ULL});
 }
 
 // Drop the client certificate verify.
-TEST_F(TlsDropDatagram13, DropClientCertVerify) {
+TEST_P(TlsDropDatagram13, DropClientCertVerify) {
   StartConnect();
   client_->SetupClientAuth();
   server_->RequestClientAuth(true);
   client_->Handshake();
   server_->Handshake();
   // Have the client drop Cert Verify
   client_filters_.drop_->Reset({1});
   expected_server_acks_ = 2;
@@ -279,17 +283,17 @@ TEST_F(TlsDropDatagram13, DropClientCert
   CheckAcks(
       server_filters_, 1,
       {0x0002000000000000ULL,    // CH (we drop everything after this on client)
        0x0002000000000003ULL,    // CT (2)
        0x0002000000000004ULL});  // FIN (2)
 }
 
 // Shrink the MTU down so that certs get split and drop the first piece.
-TEST_F(TlsDropDatagram13, DropFirstHalfOfServerCertificate) {
+TEST_P(TlsDropDatagram13, DropFirstHalfOfServerCertificate) {
   server_filters_.drop_->Reset({2});
   StartConnect();
   ShrinkPostServerHelloMtu();
   client_->Handshake();
   server_->Handshake();
   // Check that things got split.
   EXPECT_EQ(6UL,
             server_filters_.records_->count());  // SH, EE, CT1, CT2, CV, FIN
@@ -306,17 +310,17 @@ TEST_F(TlsDropDatagram13, DropFirstHalfO
   CheckAcks(client_filters_, 0,
             {0,                        // SH
              0x0002000000000000ULL,    // EE
              0x0002000000000002ULL});  // CT2
   CheckAcks(server_filters_, 0, {0x0002000000000000ULL});
 }
 
 // Shrink the MTU down so that certs get split and drop the second piece.
-TEST_F(TlsDropDatagram13, DropSecondHalfOfServerCertificate) {
+TEST_P(TlsDropDatagram13, DropSecondHalfOfServerCertificate) {
   server_filters_.drop_->Reset({3});
   StartConnect();
   ShrinkPostServerHelloMtu();
   client_->Handshake();
   server_->Handshake();
   // Check that things got split.
   EXPECT_EQ(6UL,
             server_filters_.records_->count());  // SH, EE, CT1, CT2, CV, FIN
@@ -519,21 +523,21 @@ class TlsFragmentationAndRecoveryTest : 
 
   size_t server_record_len(size_t index) const {
     return server_filters_.records_->record(index).buffer.len();
   }
 
   size_t cert_len_;
 };
 
-TEST_F(TlsFragmentationAndRecoveryTest, DropFirstHalf) { RunTest(0); }
+TEST_P(TlsFragmentationAndRecoveryTest, DropFirstHalf) { RunTest(0); }
 
-TEST_F(TlsFragmentationAndRecoveryTest, DropSecondHalf) { RunTest(1); }
+TEST_P(TlsFragmentationAndRecoveryTest, DropSecondHalf) { RunTest(1); }
 
-TEST_F(TlsDropDatagram13, NoDropsDuringZeroRtt) {
+TEST_P(TlsDropDatagram13, NoDropsDuringZeroRtt) {
   SetupForZeroRtt();
   SetFilters();
   std::cerr << "Starting second handshake" << std::endl;
   client_->Set0RttEnabled(true);
   server_->Set0RttEnabled(true);
   ExpectResumption(RESUME_TICKET);
   ZeroRttSendReceive(true, true);
   Handshake();
@@ -541,17 +545,17 @@ TEST_F(TlsDropDatagram13, NoDropsDuringZ
   CheckConnected();
   SendReceive();
   EXPECT_EQ(0U, client_filters_.ack_->count());
   CheckAcks(server_filters_, 0,
             {0x0001000000000001ULL,    // EOED
              0x0002000000000000ULL});  // Finished
 }
 
-TEST_F(TlsDropDatagram13, DropEEDuringZeroRtt) {
+TEST_P(TlsDropDatagram13, DropEEDuringZeroRtt) {
   SetupForZeroRtt();
   SetFilters();
   std::cerr << "Starting second handshake" << std::endl;
   client_->Set0RttEnabled(true);
   server_->Set0RttEnabled(true);
   ExpectResumption(RESUME_TICKET);
   server_filters_.drop_->Reset({1});
   ZeroRttSendReceive(true, true);
@@ -586,17 +590,17 @@ class TlsReorderDatagram13 : public TlsD
     for (auto i : indices) {
       agent->SendRecordDirect(records->record(i));
     }
   }
 };
 
 // Reorder the server records so that EE comes at the end
 // of the flight and will still produce an ACK.
-TEST_F(TlsDropDatagram13, ReorderServerEE) {
+TEST_P(TlsDropDatagram13, ReorderServerEE) {
   server_filters_.drop_->Reset({1});
   StartConnect();
   client_->Handshake();
   server_->Handshake();
   // We dropped EE, now reinject.
   server_->SendRecordDirect(server_filters_.record(1));
   expected_client_acks_ = 1;
   HandshakeAndAck(client_);
@@ -642,70 +646,70 @@ class TlsSendCipherSpecCapturer {
                           SSLInt_CipherSpecToIv(newSpec));
     EXPECT_EQ(true, ret);
     self->send_cipher_specs_.push_back(spec);
   }
 
   std::vector<std::shared_ptr<TlsCipherSpec>> send_cipher_specs_;
 };
 
-TEST_F(TlsDropDatagram13, SendOutOfOrderAppWithHandshakeKey) {
+TEST_P(TlsDropDatagram13, SendOutOfOrderAppWithHandshakeKey) {
   StartConnect();
   TlsSendCipherSpecCapturer capturer(client_);
   client_->Handshake();
   server_->Handshake();
   client_->Handshake();
   EXPECT_EQ(TlsAgent::STATE_CONNECTED, client_->state());
   server_->Handshake();
   EXPECT_EQ(TlsAgent::STATE_CONNECTED, server_->state());
   // After the client sends Finished, inject an app data record
   // with the handshake key. This should produce an alert.
   uint8_t buf[] = {'a', 'b', 'c'};
   auto spec = capturer.spec(0);
   ASSERT_NE(nullptr, spec.get());
   ASSERT_EQ(2, spec->epoch());
-  ASSERT_TRUE(client_->SendEncryptedRecord(
-      spec, SSL_LIBRARY_VERSION_DTLS_1_2_WIRE, 0x0002000000000002,
-      kTlsApplicationDataType, DataBuffer(buf, sizeof(buf))));
+  ASSERT_TRUE(client_->SendEncryptedRecord(spec, 0x0002000000000002,
+                                           kTlsApplicationDataType,
+                                           DataBuffer(buf, sizeof(buf))));
 
   // Now have the server consume the bogus message.
   server_->ExpectSendAlert(illegal_parameter, kTlsAlertFatal);
   server_->Handshake();
   EXPECT_EQ(TlsAgent::STATE_ERROR, server_->state());
   EXPECT_EQ(SSL_ERROR_RX_UNKNOWN_RECORD_TYPE, PORT_GetError());
 }
 
-TEST_F(TlsDropDatagram13, SendOutOfOrderHsNonsenseWithHandshakeKey) {
+TEST_P(TlsDropDatagram13, SendOutOfOrderHsNonsenseWithHandshakeKey) {
   StartConnect();
   TlsSendCipherSpecCapturer capturer(client_);
   client_->Handshake();
   server_->Handshake();
   client_->Handshake();
   EXPECT_EQ(TlsAgent::STATE_CONNECTED, client_->state());
   server_->Handshake();
   EXPECT_EQ(TlsAgent::STATE_CONNECTED, server_->state());
   // Inject a new bogus handshake record, which the server responds
   // to by just ACKing the original one (we ignore the contents).
   uint8_t buf[] = {'a', 'b', 'c'};
   auto spec = capturer.spec(0);
   ASSERT_NE(nullptr, spec.get());
   ASSERT_EQ(2, spec->epoch());
-  ASSERT_TRUE(client_->SendEncryptedRecord(
-      spec, SSL_LIBRARY_VERSION_DTLS_1_2_WIRE, 0x0002000000000002,
-      kTlsHandshakeType, DataBuffer(buf, sizeof(buf))));
+  ASSERT_TRUE(client_->SendEncryptedRecord(spec, 0x0002000000000002,
+                                           kTlsHandshakeType,
+                                           DataBuffer(buf, sizeof(buf))));
   server_->Handshake();
   EXPECT_EQ(2UL, server_filters_.ack_->count());
   // The server acknowledges client Finished twice.
   CheckAcks(server_filters_, 0, {0x0002000000000000ULL});
   CheckAcks(server_filters_, 1, {0x0002000000000000ULL});
 }
 
 // Shrink the MTU down so that certs get split and then swap the first and
 // second pieces of the server certificate.
-TEST_F(TlsReorderDatagram13, ReorderServerCertificate) {
+TEST_P(TlsReorderDatagram13, ReorderServerCertificate) {
   StartConnect();
   ShrinkPostServerHelloMtu();
   client_->Handshake();
   // Drop the entire handshake flight so we can reorder.
   server_filters_.drop_->Reset(0xff);
   server_->Handshake();
   // Check that things got split.
   EXPECT_EQ(6UL,
@@ -717,17 +721,17 @@ TEST_F(TlsReorderDatagram13, ReorderServ
   server_filters_.records_->Clear();
   // Wait for client to send ACK.
   ShiftDtlsTimers();
   CheckedHandshakeSendReceive();
   EXPECT_EQ(2UL, server_filters_.records_->count());  // ACK + Data
   CheckAcks(server_filters_, 0, {0x0002000000000000ULL});
 }
 
-TEST_F(TlsReorderDatagram13, DataAfterEOEDDuringZeroRtt) {
+TEST_P(TlsReorderDatagram13, DataAfterEOEDDuringZeroRtt) {
   SetupForZeroRtt();
   SetFilters();
   std::cerr << "Starting second handshake" << std::endl;
   client_->Set0RttEnabled(true);
   server_->Set0RttEnabled(true);
   ExpectResumption(RESUME_TICKET);
   // Send the client's first flight of zero RTT data.
   ZeroRttSendReceive(true, true);
@@ -756,17 +760,17 @@ TEST_F(TlsReorderDatagram13, DataAfterEO
   // Acknowledgements for EOED and Finished.
   CheckAcks(server_filters_, 0, {0x0001000000000002ULL, 0x0002000000000000ULL});
   uint8_t buf[8];
   rv = PR_Read(server_->ssl_fd(), buf, sizeof(buf));
   EXPECT_EQ(-1, rv);
   EXPECT_EQ(PR_WOULD_BLOCK_ERROR, PORT_GetError());
 }
 
-TEST_F(TlsReorderDatagram13, DataAfterFinDuringZeroRtt) {
+TEST_P(TlsReorderDatagram13, DataAfterFinDuringZeroRtt) {
   SetupForZeroRtt();
   SetFilters();
   std::cerr << "Starting second handshake" << std::endl;
   client_->Set0RttEnabled(true);
   server_->Set0RttEnabled(true);
   ExpectResumption(RESUME_TICKET);
   // Send the client's first flight of zero RTT data.
   ZeroRttSendReceive(true, true);
@@ -807,38 +811,54 @@ static void GetCipherAndLimit(uint16_t v
 
   if (version < SSL_LIBRARY_VERSION_TLS_1_2) {
     *cipher = TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA;
     *limit = 0x5aULL << 28;
   } else if (version == SSL_LIBRARY_VERSION_TLS_1_2) {
     *cipher = TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256;
     *limit = (1ULL << 48) - 1;
   } else {
+    // This test probably isn't especially useful for TLS 1.3, which has a much
+    // shorter sequence number encoding.  That space can probably be searched in
+    // a reasonable amount of time.
     *cipher = TLS_CHACHA20_POLY1305_SHA256;
-    *limit = (1ULL << 48) - 1;
+    // Assume that we are starting with an expected sequence number of 0.
+    *limit = (1ULL << 29) - 1;
   }
 }
 
 // This simulates a huge number of drops on one side.
+// See Bug 12965514 where a large gap was handled very inefficiently.
 TEST_P(TlsConnectDatagram, MissLotsOfPackets) {
   uint16_t cipher;
   uint64_t limit;
 
   GetCipherAndLimit(version_, &cipher, &limit);
 
   EnsureTlsSetup();
   server_->EnableSingleCipher(cipher);
   Connect();
 
   // Note that the limit for ChaCha is 2^48-1.
   EXPECT_EQ(SECSuccess,
             SSLInt_AdvanceWriteSeqNum(client_->ssl_fd(), limit - 10));
   SendReceive();
 }
 
+// Send a sequence number of 0xfffffffd and it should be interpreted as that
+// (and not -3 or UINT64_MAX - 2).
+TEST_F(TlsConnectDatagram13, UnderflowSequenceNumber) {
+  Connect();
+  // This is only valid if short headers are disabled.
+  client_->SetOption(SSL_ENABLE_DTLS_SHORT_HEADER, PR_FALSE);
+  EXPECT_EQ(SECSuccess,
+            SSLInt_AdvanceWriteSeqNum(client_->ssl_fd(), (1ULL << 30) - 3));
+  SendReceive();
+}
+
 class TlsConnectDatagram12Plus : public TlsConnectDatagram {
  public:
   TlsConnectDatagram12Plus() : TlsConnectDatagram() {}
 };
 
 // This simulates missing a window's worth of packets.
 TEST_P(TlsConnectDatagram12Plus, MissAWindow) {
   EnsureTlsSetup();
@@ -860,10 +880,16 @@ TEST_P(TlsConnectDatagram12Plus, MissAWi
   EXPECT_EQ(SECSuccess, SSLInt_AdvanceWriteSeqByAWindow(client_->ssl_fd(), 1));
   SendReceive();
 }
 
 INSTANTIATE_TEST_CASE_P(Datagram12Plus, TlsConnectDatagram12Plus,
                         TlsConnectTestBase::kTlsV12Plus);
 INSTANTIATE_TEST_CASE_P(DatagramPre13, TlsConnectDatagramPre13,
                         TlsConnectTestBase::kTlsV11V12);
+INSTANTIATE_TEST_CASE_P(DatagramDrop13, TlsDropDatagram13,
+                        ::testing::Values(true, false));
+INSTANTIATE_TEST_CASE_P(DatagramReorder13, TlsReorderDatagram13,
+                        ::testing::Values(true, false));
+INSTANTIATE_TEST_CASE_P(DatagramFragment13, TlsFragmentationAndRecoveryTest,
+                        ::testing::Values(true, false));
 
 }  // namespace nss_test
--- a/security/nss/gtests/ssl_gtest/ssl_fragment_unittest.cc
+++ b/security/nss/gtests/ssl_gtest/ssl_fragment_unittest.cc
@@ -15,34 +15,36 @@
 #include "tls_filter.h"
 #include "tls_parser.h"
 
 namespace nss_test {
 
 // This class cuts every unencrypted handshake record into two parts.
 class RecordFragmenter : public PacketFilter {
  public:
-  RecordFragmenter() : sequence_number_(0), splitting_(true) {}
+  RecordFragmenter(bool is_dtls13)
+      : is_dtls13_(is_dtls13), sequence_number_(0), splitting_(true) {}
 
  private:
   class HandshakeSplitter {
    public:
-    HandshakeSplitter(const DataBuffer& input, DataBuffer* output,
-                      uint64_t* sequence_number)
-        : input_(input),
+    HandshakeSplitter(bool is_dtls13, const DataBuffer& input,
+                      DataBuffer* output, uint64_t* sequence_number)
+        : is_dtls13_(is_dtls13),
+          input_(input),
           output_(output),
           cursor_(0),
           sequence_number_(sequence_number) {}
 
    private:
     void WriteRecord(TlsRecordHeader& record_header,
                      DataBuffer& record_fragment) {
-      TlsRecordHeader fragment_header(record_header.version(),
-                                      record_header.content_type(),
-                                      *sequence_number_);
+      TlsRecordHeader fragment_header(
+          record_header.variant(), record_header.version(),
+          record_header.content_type(), *sequence_number_);
       ++*sequence_number_;
       if (::g_ssl_gtest_verbose) {
         std::cerr << "Fragment: " << fragment_header << ' ' << record_fragment
                   << std::endl;
       }
       cursor_ = fragment_header.Write(output_, cursor_, record_fragment);
     }
 
@@ -83,17 +85,17 @@ class RecordFragmenter : public PacketFi
     }
 
    public:
     bool Split() {
       TlsParser parser(input_);
       while (parser.remaining()) {
         TlsRecordHeader header;
         DataBuffer record;
-        if (!header.Parse(0, &parser, &record)) {
+        if (!header.Parse(is_dtls13_, 0, &parser, &record)) {
           ADD_FAILURE() << "bad record header";
           return false;
         }
 
         if (::g_ssl_gtest_verbose) {
           std::cerr << "Record: " << header << ' ' << record << std::endl;
         }
 
@@ -113,51 +115,55 @@ class RecordFragmenter : public PacketFi
         if (!SplitRecord(header, record)) {
           return false;
         }
       }
       return true;
     }
 
    private:
+    bool is_dtls13_;
     const DataBuffer& input_;
     DataBuffer* output_;
     size_t cursor_;
     uint64_t* sequence_number_;
   };
 
  protected:
   virtual PacketFilter::Action Filter(const DataBuffer& input,
                                       DataBuffer* output) override {
     if (!splitting_) {
       return KEEP;
     }
 
     output->Allocate(input.len());
-    HandshakeSplitter splitter(input, output, &sequence_number_);
+    HandshakeSplitter splitter(is_dtls13_, input, output, &sequence_number_);
     if (!splitter.Split()) {
       // If splitting fails, we obviously reached encrypted packets.
       // Stop splitting from that point onward.
       splitting_ = false;
       return KEEP;
     }
 
     return CHANGE;
   }
 
  private:
+  bool is_dtls13_;
   uint64_t sequence_number_;
   bool splitting_;
 };
 
 TEST_P(TlsConnectDatagram, FragmentClientPackets) {
-  client_->SetFilter(std::make_shared<RecordFragmenter>());
+  bool is_dtls13 = version_ >= SSL_LIBRARY_VERSION_TLS_1_3;
+  client_->SetFilter(std::make_shared<RecordFragmenter>(is_dtls13));
   Connect();
   SendReceive();
 }
 
 TEST_P(TlsConnectDatagram, FragmentServerPackets) {
-  server_->SetFilter(std::make_shared<RecordFragmenter>());
+  bool is_dtls13 = version_ >= SSL_LIBRARY_VERSION_TLS_1_3;
+  server_->SetFilter(std::make_shared<RecordFragmenter>(is_dtls13));
   Connect();
   SendReceive();
 }
 
 }  // namespace nss_test
--- a/security/nss/gtests/ssl_gtest/ssl_hrr_unittest.cc
+++ b/security/nss/gtests/ssl_gtest/ssl_hrr_unittest.cc
@@ -76,18 +76,19 @@ class CorrectMessageSeqAfterHrrFilter : 
   PacketFilter::Action FilterRecord(const TlsRecordHeader& header,
                                     const DataBuffer& record, size_t* offset,
                                     DataBuffer* output) {
     if (filtered_packets() > 0 || header.content_type() != content_handshake) {
       return KEEP;
     }
 
     DataBuffer buffer(record);
-    TlsRecordHeader new_header = {header.version(), header.content_type(),
-                                  header.sequence_number() + 1};
+    TlsRecordHeader new_header(header.variant(), header.version(),
+                               header.content_type(),
+                               header.sequence_number() + 1);
 
     // Correct message_seq.
     buffer.Write(4, 1U, 2);
 
     *offset = new_header.Write(output, *offset, buffer);
     return CHANGE;
   }
 };
@@ -562,26 +563,49 @@ void TriggerHelloRetryRequest(std::share
                                                       RetryHello, &cb_called));
 
   // Start the handshake.
   client->StartConnect();
   server->StartConnect();
   client->Handshake();
   server->Handshake();
   EXPECT_EQ(1U, cb_called);
+  // Stop the callback from being called in future handshakes.
+  EXPECT_EQ(SECSuccess,
+            SSL_HelloRetryRequestCallback(server->ssl_fd(), nullptr, nullptr));
+}
+
+TEST_P(TlsConnectTls13, VersionNumbersAfterRetry) {
+  ConfigureSelfEncrypt();
+  EnsureTlsSetup();
+  auto r = MakeTlsFilter<TlsRecordRecorder>(client_);
+  TriggerHelloRetryRequest(client_, server_);
+  Handshake();
+  ASSERT_GT(r->count(), 1UL);
+  auto ch1 = r->record(0);
+  if (ch1.header.is_dtls()) {
+    ASSERT_EQ(SSL_LIBRARY_VERSION_TLS_1_1, ch1.header.version());
+  } else {
+    ASSERT_EQ(SSL_LIBRARY_VERSION_TLS_1_0, ch1.header.version());
+  }
+  auto ch2 = r->record(1);
+  ASSERT_EQ(SSL_LIBRARY_VERSION_TLS_1_2, ch2.header.version());
+
+  CheckConnected();
 }
 
 TEST_P(TlsConnectTls13, RetryStateless) {
   ConfigureSelfEncrypt();
   EnsureTlsSetup();
 
   TriggerHelloRetryRequest(client_, server_);
   MakeNewServer();
 
   Handshake();
+  CheckConnected();
   SendReceive();
 }
 
 TEST_P(TlsConnectTls13, RetryStatefulDropCookie) {
   ConfigureSelfEncrypt();
   EnsureTlsSetup();
 
   TriggerHelloRetryRequest(client_, server_);
@@ -902,17 +926,20 @@ class HelloRetryRequestAgentTest : publi
     DataBuffer hrr_data;
     const uint8_t ssl_hello_retry_random[] = {
         0xCF, 0x21, 0xAD, 0x74, 0xE5, 0x9A, 0x61, 0x11, 0xBE, 0x1D, 0x8C,
         0x02, 0x1E, 0x65, 0xB8, 0x91, 0xC2, 0xA2, 0x11, 0x16, 0x7A, 0xBB,
         0x8C, 0x5E, 0x07, 0x9E, 0x09, 0xE2, 0xC8, 0xA8, 0x33, 0x9C};
 
     hrr_data.Allocate(len + 6);
     size_t i = 0;
-    i = hrr_data.Write(i, 0x0303, 2);
+    i = hrr_data.Write(i, variant_ == ssl_variant_datagram
+                              ? SSL_LIBRARY_VERSION_DTLS_1_2_WIRE
+                              : SSL_LIBRARY_VERSION_TLS_1_2,
+                       2);
     i = hrr_data.Write(i, ssl_hello_retry_random,
                        sizeof(ssl_hello_retry_random));
     i = hrr_data.Write(i, static_cast<uint32_t>(0), 1);  // session_id
     i = hrr_data.Write(i, TLS_AES_128_GCM_SHA256, 2);
     i = hrr_data.Write(i, ssl_compression_null, 1);
     // Add extensions.  First a length, which includes the supported version.
     i = hrr_data.Write(i, static_cast<uint32_t>(len) + 6, 2);
     // Now the supported version.
--- a/security/nss/gtests/ssl_gtest/ssl_loopback_unittest.cc
+++ b/security/nss/gtests/ssl_gtest/ssl_loopback_unittest.cc
@@ -378,17 +378,18 @@ class TlsPreCCSHeaderInjector : public T
   virtual PacketFilter::Action FilterRecord(
       const TlsRecordHeader& record_header, const DataBuffer& input,
       size_t* offset, DataBuffer* output) override {
     if (record_header.content_type() != kTlsChangeCipherSpecType) return KEEP;
 
     std::cerr << "Injecting Finished header before CCS\n";
     const uint8_t hhdr[] = {kTlsHandshakeFinished, 0x00, 0x00, 0x0c};
     DataBuffer hhdr_buf(hhdr, sizeof(hhdr));
-    TlsRecordHeader nhdr(record_header.version(), kTlsHandshakeType, 0);
+    TlsRecordHeader nhdr(record_header.variant(), record_header.version(),
+                         kTlsHandshakeType, 0);
     *offset = nhdr.Write(output, *offset, hhdr_buf);
     *offset = record_header.Write(output, *offset, input);
     return CHANGE;
   }
 };
 
 TEST_P(TlsConnectStreamPre13, ClientFinishedHeaderBeforeCCS) {
   MakeTlsFilter<TlsPreCCSHeaderInjector>(client_);
--- a/security/nss/gtests/ssl_gtest/ssl_record_unittest.cc
+++ b/security/nss/gtests/ssl_gtest/ssl_record_unittest.cc
@@ -163,16 +163,39 @@ TEST_F(TlsConnectStreamTls13, TooLargeRe
   EXPECT_EQ(SSL_ERROR_RX_RECORD_TOO_LONG, PORT_GetError());
 
   // Read the server alert.
   rv = PR_Read(client_->ssl_fd(), buf, sizeof(buf));
   EXPECT_GT(0, rv);
   EXPECT_EQ(SSL_ERROR_RECORD_OVERFLOW_ALERT, PORT_GetError());
 }
 
+class ShortHeaderChecker : public PacketFilter {
+ public:
+  PacketFilter::Action Filter(const DataBuffer& input, DataBuffer* output) {
+    // The first octet should be 0b001xxxxx.
+    EXPECT_EQ(1, input.data()[0] >> 5);
+    return KEEP;
+  }
+};
+
+TEST_F(TlsConnectDatagram13, ShortHeadersClient) {
+  Connect();
+  client_->SetOption(SSL_ENABLE_DTLS_SHORT_HEADER, PR_TRUE);
+  client_->SetFilter(std::make_shared<ShortHeaderChecker>());
+  SendReceive();
+}
+
+TEST_F(TlsConnectDatagram13, ShortHeadersServer) {
+  Connect();
+  server_->SetOption(SSL_ENABLE_DTLS_SHORT_HEADER, PR_TRUE);
+  server_->SetFilter(std::make_shared<ShortHeaderChecker>());
+  SendReceive();
+}
+
 const static size_t kContentSizesArr[] = {
     1, kMacSize - 1, kMacSize, 30, 31, 32, 36, 256, 257, 287, 288};
 
 auto kContentSizes = ::testing::ValuesIn(kContentSizesArr);
 const static bool kTrueFalseArr[] = {true, false};
 auto kTrueFalse = ::testing::ValuesIn(kTrueFalseArr);
 
 INSTANTIATE_TEST_CASE_P(TlsPadding, TlsPaddingTest,
--- a/security/nss/gtests/ssl_gtest/tls_agent.cc
+++ b/security/nss/gtests/ssl_gtest/tls_agent.cc
@@ -39,16 +39,26 @@ const std::string TlsAgent::kServerRsaPs
 const std::string TlsAgent::kServerRsaDecrypt = "rsa_decrypt";
 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";
 
+static const uint8_t kCannedTls13ServerHello[] = {
+    0x03, 0x03, 0x9c, 0xbc, 0x14, 0x9b, 0x0e, 0x2e, 0xfa, 0x0d, 0xf3,
+    0xf0, 0x5c, 0x70, 0x7a, 0xe0, 0xd1, 0x9b, 0x3e, 0x5a, 0x44, 0x6b,
+    0xdf, 0xe5, 0xc2, 0x28, 0x64, 0xf7, 0x00, 0xc1, 0x9c, 0x08, 0x76,
+    0x08, 0x00, 0x13, 0x01, 0x00, 0x00, 0x2e, 0x00, 0x33, 0x00, 0x24,
+    0x00, 0x1d, 0x00, 0x20, 0xc2, 0xcf, 0x23, 0x17, 0x64, 0x23, 0x03,
+    0xf0, 0xfb, 0x45, 0x98, 0x26, 0xd1, 0x65, 0x24, 0xa1, 0x6c, 0xa9,
+    0x80, 0x8f, 0x2c, 0xac, 0x0a, 0xea, 0x53, 0x3a, 0xcb, 0xe3, 0x08,
+    0x84, 0xae, 0x19, 0x00, 0x2b, 0x00, 0x02, 0x7f, kD13};
+
 TlsAgent::TlsAgent(const std::string& nm, Role rl, SSLProtocolVariant var)
     : name_(nm),
       variant_(var),
       role_(rl),
       server_key_bits_(0),
       adapter_(new DummyPrSocket(role_str(), var)),
       ssl_fd_(nullptr),
       state_(STATE_INIT),
@@ -942,22 +952,23 @@ void TlsAgent::SendBuffer(const DataBuff
     error_code_ = PR_GetError();
     expect_readwrite_error_ = false;
   } else {
     ASSERT_EQ(buf.len(), static_cast<size_t>(rv));
   }
 }
 
 bool TlsAgent::SendEncryptedRecord(const std::shared_ptr<TlsCipherSpec>& spec,
-                                   uint16_t wireVersion, uint64_t seq,
-                                   uint8_t ct, const DataBuffer& buf) {
-  LOGV("Writing " << buf.len() << " bytes");
-  // Ensure we are a TLS 1.3 cipher agent.
+                                   uint64_t seq, uint8_t ct,
+                                   const DataBuffer& buf) {
+  LOGV("Encrypting " << buf.len() << " bytes");
+  // Ensure that we are doing TLS 1.3.
   EXPECT_GE(expected_version_, SSL_LIBRARY_VERSION_TLS_1_3);
-  TlsRecordHeader header(wireVersion, kTlsApplicationDataType, seq);
+  TlsRecordHeader header(variant_, expected_version_, kTlsApplicationDataType,
+                         seq);
   DataBuffer padded = buf;
   padded.Write(padded.len(), ct, 1);
   DataBuffer ciphertext;
   if (!spec->Protect(header, padded, &ciphertext)) {
     return false;
   }
 
   DataBuffer record;
@@ -1069,25 +1080,30 @@ void TlsAgentTestBase::ProcessMessage(co
   if (expected_state == TlsAgent::STATE_ERROR) {
     ASSERT_EQ(error_code, agent_->error_code());
   }
 }
 
 void TlsAgentTestBase::MakeRecord(SSLProtocolVariant variant, uint8_t type,
                                   uint16_t version, const uint8_t* buf,
                                   size_t len, DataBuffer* out,
-                                  uint64_t seq_num) {
+                                  uint64_t sequence_number) {
   size_t index = 0;
   index = out->Write(index, type, 1);
   if (variant == ssl_variant_stream) {
     index = out->Write(index, version, 2);
+  } else if (version >= SSL_LIBRARY_VERSION_TLS_1_3 &&
+             type == kTlsApplicationDataType) {
+    uint32_t epoch = (sequence_number >> 48) & 0x3;
+    uint32_t seqno = sequence_number & ((1ULL << 30) - 1);
+    index = out->Write(index, (epoch << 30) | seqno, 4);
   } 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, sequence_number >> 32, 4);
+    index = out->Write(index, sequence_number & 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 {
@@ -1135,9 +1151,17 @@ void TlsAgentTestBase::MakeTrivialHandsh
 
   index = out->Write(index, hs_type, 1);  // Handshake record type.
   index = out->Write(index, hs_len, 3);   // Handshake length
   for (size_t i = 0; i < hs_len; ++i) {
     index = out->Write(index, 1, 1);
   }
 }
 
+DataBuffer TlsAgentTestBase::MakeCannedTls13ServerHello() {
+  DataBuffer sh(kCannedTls13ServerHello, sizeof(kCannedTls13ServerHello));
+  if (variant_ == ssl_variant_datagram) {
+    sh.Write(0, SSL_LIBRARY_VERSION_DTLS_1_2_WIRE, 2);
+  }
+  return sh;
+}
+
 }  // namespace nss_test
--- a/security/nss/gtests/ssl_gtest/tls_agent.h
+++ b/security/nss/gtests/ssl_gtest/tls_agent.h
@@ -5,16 +5,19 @@
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef tls_agent_h_
 #define tls_agent_h_
 
 #include "prio.h"
 #include "ssl.h"
 
+// This is an internal header, used to get TLS_1_3_DRAFT_VERSION.
+#include "ssl3prot.h"
+
 #include <functional>
 #include <iostream>
 
 #include "test_io.h"
 
 #define GTEST_HAS_RTTI 0
 #include "gtest/gtest.h"
 #include "scoped_ptrs.h"
@@ -52,16 +55,18 @@ typedef std::function<SECStatus(TlsAgent
     AuthCertificateCallbackFunction;
 
 typedef std::function<void(TlsAgent* agent)> HandshakeCallbackFunction;
 
 typedef std::function<int32_t(TlsAgent* agent, const SECItem* srvNameArr,
                               PRUint32 srvNameArrSize)>
     SniCallbackFunction;
 
+static const uint8_t kD13 = TLS_1_3_DRAFT_VERSION;
+
 class TlsAgent : public PollTarget {
  public:
   enum Role { CLIENT, SERVER };
   enum State { STATE_INIT, STATE_CONNECTING, STATE_CONNECTED, STATE_ERROR };
 
   static const std::string kClient;     // the client key is sign only
   static const std::string kRsa2048;    // bigger sign and encrypt for either
   static const std::string kServerRsa;  // both sign and encrypt
@@ -138,18 +143,17 @@ class TlsAgent : public PollTarget {
   void EnableSrtp();
   void CheckSrtp() const;
   void CheckErrorCode(int32_t expected) const;
   void WaitForErrorCode(int32_t expected, uint32_t delay) const;
   // Send data on the socket, encrypting it.
   void SendData(size_t bytes, size_t blocksize = 1024);
   void SendBuffer(const DataBuffer& buf);
   bool SendEncryptedRecord(const std::shared_ptr<TlsCipherSpec>& spec,
-                           uint16_t wireVersion, uint64_t seq, uint8_t ct,
-                           const DataBuffer& buf);
+                           uint64_t seq, uint8_t ct, const DataBuffer& buf);
   // Send data directly to the underlying socket, skipping the TLS layer.
   void SendDirect(const DataBuffer& buf);
   void SendRecordDirect(const TlsRecord& record);
   void ReadBytes(size_t max = 16384U);
   void ResetSentBytes();  // Hack to test drops.
   void EnableExtendedMasterSecret();
   void CheckExtendedMasterSecret(bool expected);
   void CheckEarlyDataAccepted(bool expected);
@@ -438,16 +442,17 @@ class TlsAgentTestBase : public ::testin
   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;
+  DataBuffer MakeCannedTls13ServerHello();
   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;
   }
 
   void Init(const std::string& server_name = TlsAgent::kServerRsa);
   void Reset(const std::string& server_name = TlsAgent::kServerRsa);
--- a/security/nss/gtests/ssl_gtest/tls_filter.cc
+++ b/security/nss/gtests/ssl_gtest/tls_filter.cc
@@ -25,21 +25,19 @@ void TlsVersioned::WriteStream(std::ostr
   stream << (is_dtls() ? "DTLS " : "TLS ");
   switch (version()) {
     case 0:
       stream << "(no version)";
       break;
     case SSL_LIBRARY_VERSION_TLS_1_0:
       stream << "1.0";
       break;
-    case SSL_LIBRARY_VERSION_DTLS_1_0_WIRE:
     case SSL_LIBRARY_VERSION_TLS_1_1:
       stream << (is_dtls() ? "1.0" : "1.1");
       break;
-    case SSL_LIBRARY_VERSION_DTLS_1_2_WIRE:
     case SSL_LIBRARY_VERSION_TLS_1_2:
       stream << "1.2";
       break;
     case SSL_LIBRARY_VERSION_TLS_1_3:
       stream << "1.3";
       break;
     default:
       stream << "Invalid version: " << version();
@@ -62,53 +60,85 @@ void TlsRecordFilter::CipherSpecChanged(
               << (sending ? "send" : "receive")
               << " cipher spec changed:  " << newSpec->epoch << " ("
               << newSpec->phase << ")" << std::endl;
   }
   if (!sending) {
     return;
   }
 
-  self->in_sequence_number_ = 0;
-  self->out_sequence_number_ = 0;
+  uint64_t seq_no;
+  if (self->agent()->variant() == ssl_variant_datagram) {
+    seq_no = static_cast<uint64_t>(SSLInt_CipherSpecToEpoch(newSpec)) << 48;
+  } else {
+    seq_no = 0;
+  }
+  self->in_sequence_number_ = seq_no;
+  self->out_sequence_number_ = seq_no;
   self->dropped_record_ = false;
   self->cipher_spec_.reset(new TlsCipherSpec());
   bool ret = self->cipher_spec_->Init(
       SSLInt_CipherSpecToEpoch(newSpec), SSLInt_CipherSpecToAlgorithm(newSpec),
       SSLInt_CipherSpecToKey(newSpec), SSLInt_CipherSpecToIv(newSpec));
   EXPECT_EQ(true, ret);
 }
 
+bool TlsRecordFilter::is_dtls13() const {
+  if (agent()->variant() != ssl_variant_datagram) {
+    return false;
+  }
+  if (agent()->state() == TlsAgent::STATE_CONNECTED) {
+    return agent()->version() >= SSL_LIBRARY_VERSION_TLS_1_3;
+  }
+  SSLPreliminaryChannelInfo info;
+  EXPECT_EQ(SECSuccess, SSL_GetPreliminaryChannelInfo(agent()->ssl_fd(), &info,
+                                                      sizeof(info)));
+  return (info.protocolVersion >= SSL_LIBRARY_VERSION_TLS_1_3) ||
+         info.canSendEarlyData;
+}
+
 PacketFilter::Action TlsRecordFilter::Filter(const DataBuffer& input,
                                              DataBuffer* output) {
+  // Disable during shutdown.
+  if (!agent()) {
+    return KEEP;
+  }
+
   bool changed = false;
   size_t offset = 0U;
+
   output->Allocate(input.len());
-
   TlsParser parser(input);
 
   while (parser.remaining()) {
     TlsRecordHeader header;
     DataBuffer record;
 
-    if (!header.Parse(in_sequence_number_, &parser, &record)) {
+    if (!header.Parse(is_dtls13(), in_sequence_number_, &parser, &record)) {
       ADD_FAILURE() << "not a valid record";
       return KEEP;
     }
 
-    // Track the sequence number, which is necessary for stream mode (the
-    // sequence number is in the header for datagram).
+    // Track the sequence number, which is necessary for stream mode when
+    // decrypting and for TLS 1.3 datagram to recover the sequence number.
     //
-    // This isn't perfectly robust.  If there is a change from an active cipher
+    // We reset the counter when the cipher spec changes, but that notification
+    // appears before a record is sent.  If multiple records are sent with
+    // different cipher specs, this would fail.  This filters out cleartext
+    // records, so we don't get confused by handshake messages that are sent at
+    // the same time as encrypted records.  Sequence numbers are therefore
+    // likely to be incorrect for cleartext records.
+    //
+    // This isn't perfectly robust: if there is a change from an active cipher
     // spec to another active cipher spec (KeyUpdate for instance) AND writes
-    // are consolidated across that change AND packets were dropped from the
-    // older epoch, we will not correctly re-encrypt records in the old epoch to
-    // update their sequence numbers.
-    if (cipher_spec_ && header.content_type() == kTlsApplicationDataType) {
-      ++in_sequence_number_;
+    // are consolidated across that change, this code could use the wrong
+    // sequence numbers when re-encrypting records with the old keys.
+    if (header.content_type() == kTlsApplicationDataType) {
+      in_sequence_number_ =
+          (std::max)(in_sequence_number_, header.sequence_number() + 1);
     }
 
     if (FilterRecord(header, record, &offset, output) != KEEP) {
       changed = true;
     } else {
       offset = header.Write(output, offset, record);
     }
   }
@@ -126,21 +156,24 @@ PacketFilter::Action TlsRecordFilter::Fi
 PacketFilter::Action TlsRecordFilter::FilterRecord(
     const TlsRecordHeader& header, const DataBuffer& record, size_t* offset,
     DataBuffer* output) {
   DataBuffer filtered;
   uint8_t inner_content_type;
   DataBuffer plaintext;
 
   if (!Unprotect(header, record, &inner_content_type, &plaintext)) {
+    if (g_ssl_gtest_verbose) {
+      std::cerr << "unprotect failed: " << header << ":" << record << std::endl;
+    }
     return KEEP;
   }
 
-  TlsRecordHeader real_header = {header.version(), inner_content_type,
-                                 header.sequence_number()};
+  TlsRecordHeader real_header(header.variant(), header.version(),
+                              inner_content_type, header.sequence_number());
 
   PacketFilter::Action action = FilterRecord(real_header, plaintext, &filtered);
   // In stream mode, even if something doesn't change we need to re-encrypt if
   // previous packets were dropped.
   if (action == KEEP) {
     if (header.is_dtls() || !dropped_record_) {
       return KEEP;
     }
@@ -161,68 +194,186 @@ PacketFilter::Action TlsRecordFilter::Fi
 
   uint64_t seq_num;
   if (header.is_dtls() || !cipher_spec_ ||
       header.content_type() != kTlsApplicationDataType) {
     seq_num = header.sequence_number();
   } else {
     seq_num = out_sequence_number_++;
   }
-  TlsRecordHeader out_header = {header.version(), header.content_type(),
-                                seq_num};
+  TlsRecordHeader out_header(header.variant(), header.version(),
+                             header.content_type(), seq_num);
 
   DataBuffer ciphertext;
   bool rv = Protect(out_header, inner_content_type, filtered, &ciphertext);
   EXPECT_TRUE(rv);
   if (!rv) {
     return KEEP;
   }
   *offset = out_header.Write(output, *offset, ciphertext);
   return CHANGE;
 }
 
-bool TlsRecordHeader::Parse(uint64_t seqno, TlsParser* parser,
+size_t TlsRecordHeader::header_length() const {
+  // If we have a header, return it's length.
+  if (header_.len()) {
+    return header_.len();
+  }
+
+  // Otherwise make a dummy header and return the length.
+  DataBuffer buf;
+  return WriteHeader(&buf, 0, 0);
+}
+
+uint64_t TlsRecordHeader::RecoverSequenceNumber(uint64_t expected,
+                                                uint32_t partial,
+                                                size_t partial_bits) {
+  EXPECT_GE(32U, partial_bits);
+  uint64_t mask = (1 << partial_bits) - 1;
+  // First we determine the highest possible value.  This is half the
+  // expressible range above the expected value.
+  uint64_t cap = expected + (1ULL << (partial_bits - 1));
+  // Add the partial piece in.  e.g., xxxx789a and 1234 becomes xxxx1234.
+  uint64_t seq_no = (cap & ~mask) | partial;
+  // If the partial value is higher than the same partial piece from the cap,
+  // then the real value has to be lower.  e.g., xxxx1234 can't become xxxx5678.
+  if (partial > (cap & mask)) {
+    seq_no -= 1ULL << partial_bits;
+  }
+  return seq_no;
+}
+
+// Determine the full epoch and sequence number from an expected and raw value.
+// The expected and output values are packed as they are in DTLS 1.2 and
+// earlier: with 16 bits of epoch and 48 bits of sequence number.
+uint64_t TlsRecordHeader::ParseSequenceNumber(uint64_t expected, uint32_t raw,
+                                              size_t seq_no_bits,
+                                              size_t epoch_bits) {
+  uint64_t epoch_mask = (1ULL << epoch_bits) - 1;
+  uint64_t epoch = RecoverSequenceNumber(
+      expected >> 48, (raw >> seq_no_bits) & epoch_mask, epoch_bits);
+  if (epoch > (expected >> 48)) {
+    // If the epoch has changed, reset the expected sequence number.
+    expected = 0;
+  } else {
+    // Otherwise, retain just the sequence number part.
+    expected &= (1ULL << 48) - 1;
+  }
+  uint64_t seq_no_mask = (1ULL << seq_no_bits) - 1;
+  uint64_t seq_no =
+      RecoverSequenceNumber(expected, raw & seq_no_mask, seq_no_bits);
+  return (epoch << 48) | seq_no;
+}
+
+bool TlsRecordHeader::Parse(bool is_dtls13, uint64_t seqno, TlsParser* parser,
                             DataBuffer* body) {
+  auto mark = parser->consumed();
+
   if (!parser->Read(&content_type_)) {
     return false;
   }
 
+  if (is_dtls13) {
+    variant_ = ssl_variant_datagram;
+    version_ = SSL_LIBRARY_VERSION_TLS_1_3;
+
+#ifndef UNSAFE_FUZZER_MODE
+    // Deal with the 7 octet header.
+    if (content_type_ == kTlsApplicationDataType) {
+      uint32_t tmp;
+      if (!parser->Read(&tmp, 4)) {
+        return false;
+      }
+      sequence_number_ = ParseSequenceNumber(seqno, tmp, 30, 2);
+      if (!parser->ReadFromMark(&header_, parser->consumed() + 2 - mark,
+                                mark)) {
+        return false;
+      }
+      return parser->ReadVariable(body, 2);
+    }
+
+    // The short, 2 octet header.
+    if ((content_type_ & 0xe0) == 0x20) {
+      uint32_t tmp;
+      if (!parser->Read(&tmp, 1)) {
+        return false;
+      }
+      // Need to use the low 5 bits of the first octet too.
+      tmp |= (content_type_ & 0x1f) << 8;
+      content_type_ = kTlsApplicationDataType;
+      sequence_number_ = ParseSequenceNumber(seqno, tmp, 12, 1);
+
+      if (!parser->ReadFromMark(&header_, parser->consumed() - mark, mark)) {
+        return false;
+      }
+      return parser->Read(body, parser->remaining());
+    }
+
+    // The full 13 octet header can only be used for a few types.
+    EXPECT_TRUE(content_type_ == kTlsAlertType ||
+                content_type_ == kTlsHandshakeType ||
+                content_type_ == kTlsAckType);
+#endif
+  }
+
   uint32_t ver;
   if (!parser->Read(&ver, 2)) {
     return false;
   }
-  version_ = ver;
+  if (!is_dtls13) {
+    variant_ = IsDtls(ver) ? ssl_variant_datagram : ssl_variant_stream;
+  }
+  version_ = NormalizeTlsVersion(ver);
 
-  // If this is DTLS, overwrite the sequence number.
-  if (IsDtls(ver)) {
+  if (is_dtls()) {
+    // If this is DTLS, read the sequence number.
     uint32_t tmp;
     if (!parser->Read(&tmp, 4)) {
       return false;
     }
     sequence_number_ = static_cast<uint64_t>(tmp) << 32;
     if (!parser->Read(&tmp, 4)) {
       return false;
     }
     sequence_number_ |= static_cast<uint64_t>(tmp);
   } else {
     sequence_number_ = seqno;
   }
+  if (!parser->ReadFromMark(&header_, parser->consumed() + 2 - mark, mark)) {
+    return false;
+  }
   return parser->ReadVariable(body, 2);
 }
 
+size_t TlsRecordHeader::WriteHeader(DataBuffer* buffer, size_t offset,
+                                    size_t body_len) const {
+  offset = buffer->Write(offset, content_type_, 1);
+  if (is_dtls() && version_ >= SSL_LIBRARY_VERSION_TLS_1_3 &&
+      content_type() == kTlsApplicationDataType) {
+    // application_data records in TLS 1.3 have a different header format.
+    // Always use the long header here for simplicity.
+    uint32_t e = (sequence_number_ >> 48) & 0x3;
+    uint32_t seqno = sequence_number_ & ((1ULL << 30) - 1);
+    offset = buffer->Write(offset, (e << 30) | seqno, 4);
+  } else {
+    uint16_t v = is_dtls() ? TlsVersionToDtlsVersion(version_) : version_;
+    offset = buffer->Write(offset, v, 2);
+    if (is_dtls()) {
+      // write epoch (2 octet), and seqnum (6 octet)
+      offset = buffer->Write(offset, sequence_number_ >> 32, 4);
+      offset = buffer->Write(offset, sequence_number_ & 0xffffffff, 4);
+    }
+  }
+  offset = buffer->Write(offset, body_len, 2);
+  return offset;
+}
+
 size_t TlsRecordHeader::Write(DataBuffer* buffer, size_t offset,
                               const DataBuffer& body) const {
-  offset = buffer->Write(offset, content_type_, 1);
-  offset = buffer->Write(offset, version_, 2);
-  if (is_dtls()) {
-    // write epoch (2 octet), and seqnum (6 octet)
-    offset = buffer->Write(offset, sequence_number_ >> 32, 4);
-    offset = buffer->Write(offset, sequence_number_ & 0xffffffff, 4);
-  }
-  offset = buffer->Write(offset, body.len(), 2);
+  offset = WriteHeader(buffer, offset, body.len());
   offset = buffer->Write(offset, body);
   return offset;
 }
 
 bool TlsRecordFilter::Unprotect(const TlsRecordHeader& header,
                                 const DataBuffer& ciphertext,
                                 uint8_t* inner_content_type,
                                 DataBuffer* plaintext) {
@@ -401,16 +552,17 @@ bool TlsHandshakeFilter::HandshakeHeader
   return true;
 }
 
 bool TlsHandshakeFilter::HandshakeHeader::Parse(
     TlsParser* parser, const TlsRecordHeader& record_header,
     const DataBuffer& preceding_fragment, DataBuffer* body, bool* complete) {
   *complete = false;
 
+  variant_ = record_header.variant();
   version_ = record_header.version();
   if (!parser->Read(&handshake_type_)) {
     return false;  // malformed
   }
 
   uint32_t length;
   if (!ReadLength(parser, record_header, preceding_fragment.len(), &length,
                   complete)) {
--- a/security/nss/gtests/ssl_gtest/tls_filter.h
+++ b/security/nss/gtests/ssl_gtest/tls_filter.h
@@ -6,66 +6,83 @@
 
 #ifndef tls_filter_h_
 #define tls_filter_h_
 
 #include <functional>
 #include <memory>
 #include <set>
 #include <vector>
-
+#include "sslt.h"
 #include "test_io.h"
 #include "tls_agent.h"
 #include "tls_parser.h"
 #include "tls_protect.h"
 
 extern "C" {
 #include "libssl_internals.h"
 }
 
 namespace nss_test {
 
 class TlsCipherSpec;
 
 class TlsVersioned {
  public:
-  TlsVersioned() : version_(0) {}
-  explicit TlsVersioned(uint16_t v) : version_(v) {}
+  TlsVersioned() : variant_(ssl_variant_stream), version_(0) {}
+  TlsVersioned(SSLProtocolVariant var, uint16_t ver)
+      : variant_(var), version_(ver) {}
 
-  bool is_dtls() const { return IsDtls(version_); }
+  bool is_dtls() const { return variant_ == ssl_variant_datagram; }
+  SSLProtocolVariant variant() const { return variant_; }
   uint16_t version() const { return version_; }
 
   void WriteStream(std::ostream& stream) const;
 
  protected:
+  SSLProtocolVariant variant_;
   uint16_t version_;
 };
 
 class TlsRecordHeader : public TlsVersioned {
  public:
-  TlsRecordHeader() : TlsVersioned(), content_type_(0), sequence_number_(0) {}
-  TlsRecordHeader(uint16_t ver, uint8_t ct, uint64_t seqno)
-      : TlsVersioned(ver), content_type_(ct), sequence_number_(seqno) {}
+  TlsRecordHeader()
+      : TlsVersioned(), content_type_(0), sequence_number_(0), header_() {}
+  TlsRecordHeader(SSLProtocolVariant var, uint16_t ver, uint8_t ct,
+                  uint64_t seqno)
+      : TlsVersioned(var, ver),
+        content_type_(ct),
+        sequence_number_(seqno),
+        header_() {}
 
   uint8_t content_type() const { return content_type_; }
   uint64_t sequence_number() const { return sequence_number_; }
   uint16_t epoch() const {
     return static_cast<uint16_t>(sequence_number_ >> 48);
   }
-  size_t header_length() const { return is_dtls() ? 13 : 5; }
+  size_t header_length() const;
+  const DataBuffer& header() const { return header_; }
 
   // Parse the header; return true if successful; body in an outparam if OK.
-  bool Parse(uint64_t sequence_number, TlsParser* parser, DataBuffer* body);
+  bool Parse(bool is_dtls13, uint64_t sequence_number, TlsParser* parser,
+             DataBuffer* body);
   // Write the header and body to a buffer at the given offset.
   // Return the offset of the end of the write.
   size_t Write(DataBuffer* buffer, size_t offset, const DataBuffer& body) const;
+  size_t WriteHeader(DataBuffer* buffer, size_t offset, size_t body_len) const;
 
  private:
+  static uint64_t RecoverSequenceNumber(uint64_t expected, uint32_t partial,
+                                        size_t partial_bits);
+  static uint64_t ParseSequenceNumber(uint64_t expected, uint32_t raw,
+                                      size_t seq_no_bits, size_t epoch_bits);
+
   uint8_t content_type_;
   uint64_t sequence_number_;
+  DataBuffer header_;
 };
 
 struct TlsRecord {
   const TlsRecordHeader header;
   const DataBuffer buffer;
 };
 
 // Make a filter and install it on a TlsAgent.
@@ -122,16 +139,18 @@ class TlsRecordFilter : public PacketFil
   // It returns an action (KEEP, CHANGE, DROP).  It writes to the `changed`
   // outparam with the new record contents if it chooses to CHANGE the record.
   virtual PacketFilter::Action FilterRecord(const TlsRecordHeader& header,
                                             const DataBuffer& data,
                                             DataBuffer* changed) {
     return KEEP;
   }
 
+  bool is_dtls13() const;
+
  private:
   static void CipherSpecChanged(void* arg, PRBool sending,
                                 ssl3CipherSpec* newSpec);
 
   std::weak_ptr<TlsAgent> agent_;
   size_t count_;
   std::unique_ptr<TlsCipherSpec> cipher_spec_;
   // Whether we dropped a record since the cipher spec changed.
--- a/security/nss/gtests/ssl_gtest/tls_protect.cc
+++ b/security/nss/gtests/ssl_gtest/tls_protect.cc
@@ -49,46 +49,47 @@ bool AeadCipher::AeadInner(bool decrypt,
   } else {
     rv = PK11_Encrypt(key_, mech_, &param, out, &uoutlen, maxlen, in, inlen);
   }
   *outlen = (int)uoutlen;
 
   return rv == SECSuccess;
 }
 
-bool AeadCipherAesGcm::Aead(bool decrypt, uint64_t seq, const uint8_t *in,
-                            size_t inlen, uint8_t *out, size_t *outlen,
-                            size_t maxlen) {
+bool AeadCipherAesGcm::Aead(bool decrypt, const uint8_t *hdr, size_t hdr_len,
+                            uint64_t seq, const uint8_t *in, size_t inlen,
+                            uint8_t *out, size_t *outlen, size_t maxlen) {
   CK_GCM_PARAMS aeadParams;
   unsigned char nonce[12];
 
   memset(&aeadParams, 0, sizeof(aeadParams));
   aeadParams.pIv = nonce;
   aeadParams.ulIvLen = sizeof(nonce);
-  aeadParams.pAAD = NULL;
-  aeadParams.ulAADLen = 0;
+  aeadParams.pAAD = const_cast<uint8_t *>(hdr);
+  aeadParams.ulAADLen = hdr_len;
   aeadParams.ulTagBits = 128;
 
   FormatNonce(seq, nonce);
   return AeadInner(decrypt, (unsigned char *)&aeadParams, sizeof(aeadParams),
                    in, inlen, out, outlen, maxlen);
 }
 
-bool AeadCipherChacha20Poly1305::Aead(bool decrypt, uint64_t seq,
+bool AeadCipherChacha20Poly1305::Aead(bool decrypt, const uint8_t *hdr,
+                                      size_t hdr_len, uint64_t seq,
                                       const uint8_t *in, size_t inlen,
                                       uint8_t *out, size_t *outlen,
                                       size_t maxlen) {
   CK_NSS_AEAD_PARAMS aeadParams;
   unsigned char nonce[12];
 
   memset(&aeadParams, 0, sizeof(aeadParams));
   aeadParams.pNonce = nonce;
   aeadParams.ulNonceLen = sizeof(nonce);
-  aeadParams.pAAD = NULL;
-  aeadParams.ulAADLen = 0;
+  aeadParams.pAAD = const_cast<uint8_t *>(hdr);
+  aeadParams.ulAADLen = hdr_len;
   aeadParams.ulTagLen = 16;
 
   FormatNonce(seq, nonce);
   return AeadInner(decrypt, (unsigned char *)&aeadParams, sizeof(aeadParams),
                    in, inlen, out, outlen, maxlen);
 }
 
 bool TlsCipherSpec::Init(uint16_t epoc, SSLCipherAlgorithm cipher,
@@ -109,37 +110,43 @@ bool TlsCipherSpec::Init(uint16_t epoc, 
 }
 
 bool TlsCipherSpec::Unprotect(const TlsRecordHeader &header,
                               const DataBuffer &ciphertext,
                               DataBuffer *plaintext) {
   // Make space.
   plaintext->Allocate(ciphertext.len());
 
+  auto header_bytes = header.header();
   size_t len;
   bool ret =
-      aead_->Aead(true, header.sequence_number(), ciphertext.data(),
-                  ciphertext.len(), plaintext->data(), &len, plaintext->len());
+      aead_->Aead(true, header_bytes.data(), header_bytes.len(),
+                  header.sequence_number(), ciphertext.data(), ciphertext.len(),
+                  plaintext->data(), &len, plaintext->len());
   if (!ret) return false;
 
   plaintext->Truncate(len);
 
   return true;
 }
 
 bool TlsCipherSpec::Protect(const TlsRecordHeader &header,
                             const DataBuffer &plaintext,
                             DataBuffer *ciphertext) {
   // Make a padded buffer.
 
   ciphertext->Allocate(plaintext.len() +
                        32);  // Room for any plausible auth tag
   size_t len;
+
+  DataBuffer header_bytes;
+  (void)header.WriteHeader(&header_bytes, 0, plaintext.len() + 16);
   bool ret =
-      aead_->Aead(false, header.sequence_number(), plaintext.data(),
-                  plaintext.len(), ciphertext->data(), &len, ciphertext->len());
+      aead_->Aead(false, header_bytes.data(), header_bytes.len(),
+                  header.sequence_number(), plaintext.data(), plaintext.len(),
+                  ciphertext->data(), &len, ciphertext->len());
   if (!ret) return false;
   ciphertext->Truncate(len);
 
   return true;
 }
 
 }  // namespace nss_test
--- a/security/nss/gtests/ssl_gtest/tls_protect.h
+++ b/security/nss/gtests/ssl_gtest/tls_protect.h
@@ -18,18 +18,19 @@ namespace nss_test {
 class TlsRecordHeader;
 
 class AeadCipher {
  public:
   AeadCipher(CK_MECHANISM_TYPE mech) : mech_(mech), key_(nullptr) {}
   virtual ~AeadCipher();
 
   bool Init(PK11SymKey *key, const uint8_t *iv);
-  virtual bool Aead(bool decrypt, uint64_t seq, const uint8_t *in, size_t inlen,
-                    uint8_t *out, size_t *outlen, size_t maxlen) = 0;
+  virtual bool Aead(bool decrypt, const uint8_t *hdr, size_t hdr_len,
+                    uint64_t seq, const uint8_t *in, size_t inlen, uint8_t *out,
+                    size_t *outlen, size_t maxlen) = 0;
 
  protected:
   void FormatNonce(uint64_t seq, uint8_t *nonce);
   bool AeadInner(bool decrypt, void *params, size_t param_length,
                  const uint8_t *in, size_t inlen, uint8_t *out, size_t *outlen,
                  size_t maxlen);
 
   CK_MECHANISM_TYPE mech_;
@@ -37,27 +38,29 @@ class AeadCipher {
   uint8_t iv_[12];
 };
 
 class AeadCipherChacha20Poly1305 : public AeadCipher {
  public:
   AeadCipherChacha20Poly1305() : AeadCipher(CKM_NSS_CHACHA20_POLY1305) {}
 
  protected:
-  bool Aead(bool decrypt, uint64_t seq, const uint8_t *in, size_t inlen,
-            uint8_t *out, size_t *outlen, size_t maxlen);
+  bool Aead(bool decrypt, const uint8_t *hdr, size_t hdr_len, uint64_t seq,
+            const uint8_t *in, size_t inlen, uint8_t *out, size_t *outlen,
+            size_t maxlen);
 };
 
 class AeadCipherAesGcm : public AeadCipher {
  public:
   AeadCipherAesGcm() : AeadCipher(CKM_AES_GCM) {}
 
  protected:
-  bool Aead(bool decrypt, uint64_t seq, const uint8_t *in, size_t inlen,
-            uint8_t *out, size_t *outlen, size_t maxlen);
+  bool Aead(bool decrypt, const uint8_t *hdr, size_t hdr_len, uint64_t seq,
+            const uint8_t *in, size_t inlen, uint8_t *out, size_t *outlen,
+            size_t maxlen);
 };
 
 // Our analog of ssl3CipherSpec
 class TlsCipherSpec {
  public:
   TlsCipherSpec() : epoch_(0), aead_() {}
 
   bool Init(uint16_t epoch, SSLCipherAlgorithm cipher, PK11SymKey *key,
--- a/security/nss/lib/ssl/dtls13con.c
+++ b/security/nss/lib/ssl/dtls13con.c
@@ -6,16 +6,53 @@
 /*
  * DTLS 1.3 Protocol
  */
 
 #include "ssl.h"
 #include "sslimpl.h"
 #include "sslproto.h"
 
+SECStatus
+dtls13_InsertCipherTextHeader(const sslSocket *ss, ssl3CipherSpec *cwSpec,
+                              sslBuffer *wrBuf, PRBool *needsLength)
+{
+    PRUint32 seq;
+    SECStatus rv;
+
+    /* Avoid using short records for the handshake.  We pack multiple records
+     * into the one datagram for the handshake. */
+    if (ss->opt.enableDtlsShortHeader &&
+        cwSpec->epoch != TrafficKeyHandshake) {
+        *needsLength = PR_FALSE;
+        /* The short header is comprised of two octets in the form
+         * 0b001essssssssssss where 'e' is the low bit of the epoch and 's' is
+         * the low 12 bits of the sequence number. */
+        seq = 0x2000 |
+              (((uint64_t)cwSpec->epoch & 1) << 12) |
+              (cwSpec->nextSeqNum & 0xfff);
+        return sslBuffer_AppendNumber(wrBuf, seq, 2);
+    }
+
+    rv = sslBuffer_AppendNumber(wrBuf, content_application_data, 1);
+    if (rv != SECSuccess) {
+        return SECFailure;
+    }
+
+    /* The epoch and sequence number are encoded on 4 octets, with the epoch
+     * consuming the first two bits. */
+    seq = (((uint64_t)cwSpec->epoch & 3) << 30) | (cwSpec->nextSeqNum & 0x3fffffff);
+    rv = sslBuffer_AppendNumber(wrBuf, seq, 4);
+    if (rv != SECSuccess) {
+        return SECFailure;
+    }
+    *needsLength = PR_TRUE;
+    return SECSuccess;
+}
+
 /* DTLS 1.3 Record map for ACK processing.
  * This represents a single fragment, so a record which includes
  * multiple fragments will have one entry for each fragment on the
  * sender. We use the same structure on the receiver for convenience
  * but the only value we actually use is |record|.
  */
 typedef struct DTLSHandshakeRecordEntryStr {
     PRCList link;
--- a/security/nss/lib/ssl/dtls13con.h
+++ b/security/nss/lib/ssl/dtls13con.h
@@ -4,16 +4,20 @@
  *
  * 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 __dtls13con_h_
 #define __dtls13con_h_
 
+SECStatus dtls13_InsertCipherTextHeader(const sslSocket *ss,
+                                        ssl3CipherSpec *cwSpec,
+                                        sslBuffer *wrBuf,
+                                        PRBool *needsLength);
 SECStatus dtls13_RememberFragment(sslSocket *ss, PRCList *list,
                                   PRUint32 sequence, PRUint32 offset,
                                   PRUint32 length, DTLSEpoch epoch,
                                   sslSequenceNumber record);
 PRBool dtls_NextUnackedRange(sslSocket *ss, PRUint16 msgSeq, PRUint32 offset,
                              PRUint32 len, PRUint32 *startOut, PRUint32 *endOut);
 SECStatus dtls13_SetupAcks(sslSocket *ss);
 SECStatus dtls13_HandleOutOfEpochRecord(sslSocket *ss, const ssl3CipherSpec *spec,
--- a/security/nss/lib/ssl/dtlscon.c
+++ b/security/nss/lib/ssl/dtlscon.c
@@ -771,17 +771,17 @@ dtls_FragmentHandshake(sslSocket *ss, DT
             fragment = SSL_BUFFER_BASE(&tmp);
         }
 
         /* Record that we are sending first, because encrypting
          * increments the sequence number. */
         rv = dtls13_RememberFragment(ss, &ss->ssl3.hs.dtlsSentHandshake,
                                      msgSeq, fragmentOffset, fragmentLen,
                                      msg->cwSpec->epoch,
-                                     msg->cwSpec->seqNum);
+                                     msg->cwSpec->nextSeqNum);
         if (rv != SECSuccess) {
             return SECFailure;
         }
 
         rv = dtls_SendFragment(ss, msg, fragment,
                                fragmentLen + DTLS_HS_HDR_LEN);
         if (rv != SECSuccess) {
             return SECFailure;
@@ -1314,16 +1314,117 @@ DTLS_GetHandshakeTimeout(PRFileDesc *soc
     if (!found) {
         PORT_SetError(SSL_ERROR_NO_TIMERS_FOUND);
         return SECFailure;
     }
 
     return SECSuccess;
 }
 
+PRBool
+dtls_IsLongHeader(SSL3ProtocolVersion version, PRUint8 firstOctet)
+{
+#ifndef UNSAFE_FUZZER_MODE
+    return version < SSL_LIBRARY_VERSION_TLS_1_3 ||
+           firstOctet == content_handshake ||
+           firstOctet == content_ack ||
+           firstOctet == content_alert;
+#else
+    return PR_TRUE;
+#endif
+}
+
+DTLSEpoch
+dtls_ReadEpoch(const ssl3CipherSpec *crSpec, const PRUint8 *hdr)
+{
+    DTLSEpoch epoch;
+    DTLSEpoch maxEpoch;
+    DTLSEpoch partial;
+
+    if (dtls_IsLongHeader(crSpec->version, hdr[0])) {
+        return ((DTLSEpoch)hdr[3] << 8) | hdr[4];
+    }
+
+    /* A lot of how we recover the epoch here will depend on how we plan to
+     * manage KeyUpdate.  In the case that we decide to install a new read spec
+     * as a KeyUpdate is handled, crSpec will always be the highest epoch we can
+     * possibly receive.  That makes this easier to manage. */
+    if ((hdr[0] & 0xe0) == 0x20) {
+        /* Use crSpec->epoch, or crSpec->epoch - 1 if the last bit differs. */
+        if (((hdr[0] >> 4) & 1) == (crSpec->epoch & 1)) {
+            return crSpec->epoch;
+        }
+        return crSpec->epoch - 1;
+    }
+
+    /* dtls_GatherData should ensure that this works. */
+    PORT_Assert(hdr[0] == content_application_data);
+
+    /* This uses the same method as is used to recover the sequence number in
+     * dtls_ReadSequenceNumber, except that the maximum value is set to the
+     * current epoch. */
+    partial = hdr[1] >> 6;
+    maxEpoch = PR_MAX(crSpec->epoch, 3);
+    epoch = (maxEpoch & 0xfffc) | partial;
+    if (partial > (maxEpoch & 0x03)) {
+        epoch -= 4;
+    }
+    return epoch;
+}
+
+static sslSequenceNumber
+dtls_ReadSequenceNumber(const ssl3CipherSpec *spec, const PRUint8 *hdr)
+{
+    sslSequenceNumber cap;
+    sslSequenceNumber partial;
+    sslSequenceNumber seqNum;
+    sslSequenceNumber mask;
+
+    if (dtls_IsLongHeader(spec->version, hdr[0])) {
+        static const unsigned int seqNumOffset = 5; /* type, version, epoch */
+        static const unsigned int seqNumLength = 6;
+        sslReader r = SSL_READER(hdr + seqNumOffset, seqNumLength);
+        (void)sslRead_ReadNumber(&r, seqNumLength, &seqNum);
+        return seqNum;
+    }
+
+    /* Only the least significant bits of the sequence number is available here.
+     * This recovers the value based on the next expected sequence number.
+     *
+     * This works by determining the maximum possible sequence number, which is
+     * half the range of possible values above the expected next value (the
+     * expected next value is in |spec->seqNum|).  Then, the last part of the
+     * sequence number is replaced.  If that causes the value to exceed the
+     * maximum, subtract an entire range.
+     */
+    if ((hdr[0] & 0xe0) == 0x20) {
+        /* A 12-bit sequence number. */
+        cap = spec->nextSeqNum + (1ULL << 11);
+        partial = (((sslSequenceNumber)hdr[0] & 0xf) << 8) |
+                  (sslSequenceNumber)hdr[1];
+        mask = (1ULL << 12) - 1;
+    } else {
+        /* A 30-bit sequence number. */
+        cap = spec->nextSeqNum + (1ULL << 29);
+        partial = (((sslSequenceNumber)hdr[1] & 0x3f) << 24) |
+                  ((sslSequenceNumber)hdr[2] << 16) |
+                  ((sslSequenceNumber)hdr[3] << 8) |
+                  (sslSequenceNumber)hdr[4];
+        mask = (1ULL << 30) - 1;
+    }
+    seqNum = (cap & ~mask) | partial;
+    /* The second check prevents the value from underflowing if we get a large
+     * gap at the start of a connection, where this subtraction would cause the
+     * sequence number to wrap to near UINT64_MAX. */
+    if ((partial > (cap & mask)) && (seqNum > mask)) {
+        seqNum -= mask + 1;
+    }
+    return seqNum;
+}
+
 /*
  * DTLS relevance checks:
  * Note that this code currently ignores all out-of-epoch packets,
  * which means we lose some in the case of rehandshake +
  * loss/reordering. Since DTLS is explicitly unreliable, this
  * seems like a good tradeoff for implementation effort and is
  * consistent with the guidance of RFC 6347 Sections 4.1 and 4.2.4.1.
  *
@@ -1331,17 +1432,17 @@ DTLS_GetHandshakeTimeout(PRFileDesc *soc
  * is relevant, this function returns PR_TRUE and sets |*seqNumOut| to the
  * packet sequence number (removing the epoch).
  */
 PRBool
 dtls_IsRelevant(sslSocket *ss, const ssl3CipherSpec *spec,
                 const SSL3Ciphertext *cText,
                 sslSequenceNumber *seqNumOut)
 {
-    sslSequenceNumber seqNum = cText->seq_num & RECORD_SEQ_MASK;
+    sslSequenceNumber seqNum = dtls_ReadSequenceNumber(spec, cText->hdr);
     if (dtls_RecordGetRecvd(&spec->recvdRecords, seqNum) != 0) {
         SSL_TRC(10, ("%d: SSL3[%d]: dtls_IsRelevant, rejecting "
                      "potentially replayed packet",
                      SSL_GETPID(), ss->fd));
         return PR_FALSE;
     }
 
     *seqNumOut = seqNum;
--- a/security/nss/lib/ssl/dtlscon.h
+++ b/security/nss/lib/ssl/dtlscon.h
@@ -36,13 +36,15 @@ extern int dtls_RecordGetRecvd(const DTL
                                sslSequenceNumber seq);
 extern void dtls_RecordSetRecvd(DTLSRecvdRecords *records,
                                 sslSequenceNumber seq);
 extern void dtls_RehandshakeCleanup(sslSocket *ss);
 extern SSL3ProtocolVersion
 dtls_TLSVersionToDTLSVersion(SSL3ProtocolVersion tlsv);
 extern SSL3ProtocolVersion
 dtls_DTLSVersionToTLSVersion(SSL3ProtocolVersion dtlsv);
+DTLSEpoch dtls_ReadEpoch(const ssl3CipherSpec *crSpec, const PRUint8 *hdr);
 extern PRBool dtls_IsRelevant(sslSocket *ss, const ssl3CipherSpec *spec,
                               const SSL3Ciphertext *cText,
                               sslSequenceNumber *seqNum);
 void dtls_ReceivedFirstMessageInFlight(sslSocket *ss);
+PRBool dtls_IsLongHeader(SSL3ProtocolVersion version, PRUint8 firstOctet);
 #endif
--- a/security/nss/lib/ssl/ssl.h
+++ b/security/nss/lib/ssl/ssl.h
@@ -249,16 +249,27 @@ SSL_IMPORT PRFileDesc *DTLS_ImportFD(PRF
 #define SSL_ENABLE_0RTT_DATA 33
 
 /* Enables TLS 1.3 compatibility mode.  In this mode, the client includes a fake
  * session ID in the handshake and sends a ChangeCipherSpec.  A server will
  * always use the setting chosen by the client, so the value of this option has
  * no effect for a server. This setting is ignored for DTLS. */
 #define SSL_ENABLE_TLS13_COMPAT_MODE 35
 
+/* Enables the sending of DTLS records using the short (two octet) record
+ * header.  Only do this if there are 2^10 or fewer packets in flight at a time;
+ * using this with a larger number of packets in flight could mean that packets
+ * are dropped if there is reordering.
+ *
+ * This applies to TLS 1.3 only.  This is not a parameter that is negotiated
+ * during the TLS handshake. Unlike other socket options, this option can be
+ * changed after a handshake is complete.
+ */
+#define SSL_ENABLE_DTLS_SHORT_HEADER 36
+
 #ifdef SSL_DEPRECATED_FUNCTION
 /* Old deprecated function names */
 SSL_IMPORT SECStatus SSL_Enable(PRFileDesc *fd, int option, PRIntn on);
 SSL_IMPORT SECStatus SSL_EnableDefault(int option, PRIntn on);
 #endif
 
 /* Set (and get) options for sockets and defaults for newly created sockets.
  *
--- a/security/nss/lib/ssl/ssl3con.c
+++ b/security/nss/lib/ssl/ssl3con.c
@@ -985,37 +985,32 @@ ssl_ClientReadVersion(sslSocket *ss, PRU
     SSL3ProtocolVersion v;
     PRUint32 temp;
     SECStatus rv;
 
     rv = ssl3_ConsumeHandshakeNumber(ss, &temp, 2, b, len);
     if (rv != SECSuccess) {
         return SECFailure; /* alert has been sent */
     }
-
-#ifdef TLS_1_3_DRAFT_VERSION
-    if (temp == SSL_LIBRARY_VERSION_TLS_1_3) {
-        (void)SSL3_SendAlert(ss, alert_fatal, protocol_version);
-        PORT_SetError(SSL_ERROR_UNSUPPORTED_VERSION);
-        return SECFailure;
-    }
-    if (temp == tls13_EncodeDraftVersion(SSL_LIBRARY_VERSION_TLS_1_3)) {
-        v = SSL_LIBRARY_VERSION_TLS_1_3;
-    } else {
-        v = (SSL3ProtocolVersion)temp;
-    }
-#else
     v = (SSL3ProtocolVersion)temp;
-#endif
 
     if (IS_DTLS(ss)) {
-        /* If this fails, we get 0 back and the next check to fails. */
         v = dtls_DTLSVersionToTLSVersion(v);
-    }
-
+        /* Check for failure. */
+        if (!v || v > SSL_LIBRARY_VERSION_MAX_SUPPORTED) {
+            SSL3_SendAlert(ss, alert_fatal, illegal_parameter);
+            return SECFailure;
+        }
+    }
+
+    /* You can't negotiate TLS 1.3 this way. */
+    if (v >= SSL_LIBRARY_VERSION_TLS_1_3) {
+        SSL3_SendAlert(ss, alert_fatal, illegal_parameter);
+        return SECFailure;
+    }
     *version = v;
     return SECSuccess;
 }
 
 static SECStatus
 ssl3_GetNewRandom(SSL3Random random)
 {
     SECStatus rv;
@@ -1410,17 +1405,17 @@ ssl3_SetupPendingCipherSpec(sslSocket *s
     if (!spec) {
         return SECFailure;
     }
 
     spec->cipherDef = ssl_GetBulkCipherDef(suiteDef);
     spec->macDef = ssl_GetMacDef(ss, suiteDef);
 
     spec->epoch = prev->epoch + 1;
-    spec->seqNum = 0;
+    spec->nextSeqNum = 0;
     if (IS_DTLS(ss) && direction == CipherSpecRead) {
         dtls_InitRecvdRecords(&spec->recvdRecords);
     }
     ssl_SetSpecVersions(ss, spec);
 
     ssl_SaveCipherSpec(ss, spec);
     *specp = spec;
     return SECSuccess;
@@ -1999,82 +1994,89 @@ ssl3_MACEncryptRecord(ssl3CipherSpec *cw
 {
     SECStatus rv;
     PRUint32 macLen = 0;
     PRUint32 fragLen;
     PRUint32 p1Len, p2Len, oddLen = 0;
     unsigned int ivLen = 0;
     unsigned char pseudoHeaderBuf[13];
     sslBuffer pseudoHeader = SSL_BUFFER(pseudoHeaderBuf);
+    int len;
 
     if (cwSpec->cipherDef->type == type_block &&
         cwSpec->version >= SSL_LIBRARY_VERSION_TLS_1_1) {
         /* Prepend the per-record explicit IV using technique 2b from
          * RFC 4346 section 6.2.3.2: The IV is a cryptographically
          * strong random number XORed with the CBC residue from the previous
          * record.
          */
         ivLen = cwSpec->cipherDef->iv_size;
-        if (ivLen > wrBuf->space) {
+        if (ivLen > SSL_BUFFER_SPACE(wrBuf)) {
             PORT_SetError(SEC_ERROR_LIBRARY_FAILURE);
             return SECFailure;
         }
-        rv = PK11_GenerateRandom(wrBuf->buf, ivLen);
+        rv = PK11_GenerateRandom(SSL_BUFFER_NEXT(wrBuf), ivLen);
         if (rv != SECSuccess) {
             ssl_MapLowLevelError(SSL_ERROR_GENERATE_RANDOM_FAILURE);
             return rv;
         }
         rv = cwSpec->cipher(cwSpec->cipherContext,
-                            wrBuf->buf,         /* output */
-                            (int *)&wrBuf->len, /* outlen */
-                            ivLen,              /* max outlen */
-                            wrBuf->buf,         /* input */
-                            ivLen);             /* input len */
-        if (rv != SECSuccess || wrBuf->len != ivLen) {
+                            SSL_BUFFER_NEXT(wrBuf), /* output */
+                            &len,                   /* outlen */
+                            ivLen,                  /* max outlen */
+                            SSL_BUFFER_NEXT(wrBuf), /* input */
+                            ivLen);                 /* input len */
+        if (rv != SECSuccess || len != ivLen) {
             PORT_SetError(SSL_ERROR_ENCRYPTION_FAILURE);
             return SECFailure;
         }
+
+        rv = sslBuffer_Skip(wrBuf, len, NULL);
+        PORT_Assert(rv == SECSuccess); /* Can't fail. */
     }
 
     rv = ssl3_BuildRecordPseudoHeader(
-        cwSpec->epoch, cwSpec->seqNum, type,
+        cwSpec->epoch, cwSpec->nextSeqNum, type,
         cwSpec->version >= SSL_LIBRARY_VERSION_TLS_1_0, cwSpec->recordVersion,
         isDTLS, contentLen, &pseudoHeader);
     PORT_Assert(rv == SECSuccess);
     if (cwSpec->cipherDef->type == type_aead) {
         const int nonceLen = cwSpec->cipherDef->explicit_nonce_size;
         const int tagLen = cwSpec->cipherDef->tag_size;
 
-        if (nonceLen + contentLen + tagLen > wrBuf->space) {
+        if (nonceLen + contentLen + tagLen > SSL_BUFFER_SPACE(wrBuf)) {
             PORT_SetError(SEC_ERROR_LIBRARY_FAILURE);
             return SECFailure;
         }
 
         rv = cwSpec->aead(
             &cwSpec->keyMaterial,
-            PR_FALSE,           /* do encrypt */
-            wrBuf->buf,         /* output  */
-            (int *)&wrBuf->len, /* out len */
-            wrBuf->space,       /* max out */
-            pIn, contentLen,    /* input   */
+            PR_FALSE,                /* do encrypt */
+            SSL_BUFFER_NEXT(wrBuf),  /* output  */
+            &len,                    /* out len */
+            SSL_BUFFER_SPACE(wrBuf), /* max out */
+            pIn, contentLen,         /* input   */
             SSL_BUFFER_BASE(&pseudoHeader), SSL_BUFFER_LEN(&pseudoHeader));
         if (rv != SECSuccess) {
             PORT_SetError(SSL_ERROR_ENCRYPTION_FAILURE);
             return SECFailure;
         }
+
+        rv = sslBuffer_Skip(wrBuf, len, NULL);
+        PORT_Assert(rv == SECSuccess); /* Can't fail. */
     } else {
         int blockSize = cwSpec->cipherDef->block_size;
 
         /*
          * Add the MAC
          */
         rv = ssl3_ComputeRecordMAC(cwSpec, SSL_BUFFER_BASE(&pseudoHeader),
                                    SSL_BUFFER_LEN(&pseudoHeader),
                                    pIn, contentLen,
-                                   wrBuf->buf + ivLen + contentLen, &macLen);
+                                   SSL_BUFFER_NEXT(wrBuf) + contentLen, &macLen);
         if (rv != SECSuccess) {
             ssl_MapLowLevelError(SSL_ERROR_MAC_COMPUTATION_FAILURE);
             return SECFailure;
         }
         p1Len = contentLen;
         p2Len = macLen;
         fragLen = contentLen + macLen; /* needs to be encrypted */
         PORT_Assert(fragLen <= MAX_FRAGMENT_LENGTH + 1024);
@@ -2090,81 +2092,87 @@ ssl3_MACEncryptRecord(ssl3CipherSpec *cw
 
             oddLen = contentLen % blockSize;
             /* Assume blockSize is a power of two */
             padding_length = blockSize - 1 - ((fragLen) & (blockSize - 1));
             fragLen += padding_length + 1;
             PORT_Assert((fragLen % blockSize) == 0);
 
             /* Pad according to TLS rules (also acceptable to SSL3). */
-            pBuf = &wrBuf->buf[ivLen + fragLen - 1];
+            pBuf = SSL_BUFFER_NEXT(wrBuf) + fragLen - 1;
             for (i = padding_length + 1; i > 0; --i) {
                 *pBuf-- = padding_length;
             }
             /* now, if contentLen is not a multiple of block size, fix it */
             p2Len = fragLen - p1Len;
         }
         if (p1Len < 256) {
             oddLen = p1Len;
             p1Len = 0;
         } else {
             p1Len -= oddLen;
         }
         if (oddLen) {
             p2Len += oddLen;
             PORT_Assert((blockSize < 2) ||
                         (p2Len % blockSize) == 0);
-            memmove(wrBuf->buf + ivLen + p1Len, pIn + p1Len, oddLen);
+            memmove(SSL_BUFFER_NEXT(wrBuf) + p1Len, pIn + p1Len, oddLen);
         }
         if (p1Len > 0) {
             int cipherBytesPart1 = -1;
             rv = cwSpec->cipher(cwSpec->cipherContext,
-                                wrBuf->buf + ivLen, /* output */
-                                &cipherBytesPart1,  /* actual outlen */
-                                p1Len,              /* max outlen */
+                                SSL_BUFFER_NEXT(wrBuf), /* output */
+                                &cipherBytesPart1,      /* actual outlen */
+                                p1Len,                  /* max outlen */
                                 pIn,
                                 p1Len); /* input, and inputlen */
             PORT_Assert(rv == SECSuccess && cipherBytesPart1 == (int)p1Len);
             if (rv != SECSuccess || cipherBytesPart1 != (int)p1Len) {
                 PORT_SetError(SSL_ERROR_ENCRYPTION_FAILURE);
                 return SECFailure;
             }
-            wrBuf->len += cipherBytesPart1;
+            rv = sslBuffer_Skip(wrBuf, p1Len, NULL);
+            PORT_Assert(rv == SECSuccess);
         }
         if (p2Len > 0) {
             int cipherBytesPart2 = -1;
             rv = cwSpec->cipher(cwSpec->cipherContext,
-                                wrBuf->buf + ivLen + p1Len,
+                                SSL_BUFFER_NEXT(wrBuf),
                                 &cipherBytesPart2, /* output and actual outLen */
                                 p2Len,             /* max outlen */
-                                wrBuf->buf + ivLen + p1Len,
+                                SSL_BUFFER_NEXT(wrBuf),
                                 p2Len); /* input and inputLen*/
             PORT_Assert(rv == SECSuccess && cipherBytesPart2 == (int)p2Len);
             if (rv != SECSuccess || cipherBytesPart2 != (int)p2Len) {
                 PORT_SetError(SSL_ERROR_ENCRYPTION_FAILURE);
                 return SECFailure;
             }
-            wrBuf->len += cipherBytesPart2;
+            rv = sslBuffer_Skip(wrBuf, p2Len, NULL);
+            PORT_Assert(rv == SECSuccess);
         }
     }
 
     return SECSuccess;
 }
 
 /* Note: though this can report failure, it shouldn't. */
-static SECStatus
+SECStatus
 ssl_InsertRecordHeader(const sslSocket *ss, ssl3CipherSpec *cwSpec,
-                       SSL3ContentType contentType, unsigned int len,
-                       sslBuffer *wrBuf)
+                       SSL3ContentType contentType, sslBuffer *wrBuf,
+                       PRBool *needsLength)
 {
     SECStatus rv;
 
 #ifndef UNSAFE_FUZZER_MODE
     if (cwSpec->version >= SSL_LIBRARY_VERSION_TLS_1_3 &&
-        cwSpec->cipherDef->calg != ssl_calg_null) {
+        cwSpec->epoch > TrafficKeyClearText) {
+        if (IS_DTLS(ss)) {
+            return dtls13_InsertCipherTextHeader(ss, cwSpec, wrBuf,
+                                                 needsLength);
+        }
         contentType = content_application_data;
     }
 #endif
     rv = sslBuffer_AppendNumber(wrBuf, contentType, 1);
     if (rv != SECSuccess) {
         return SECFailure;
     }
 
@@ -2172,93 +2180,90 @@ ssl_InsertRecordHeader(const sslSocket *
     if (rv != SECSuccess) {
         return SECFailure;
     }
     if (IS_DTLS(ss)) {
         rv = sslBuffer_AppendNumber(wrBuf, cwSpec->epoch, 2);
         if (rv != SECSuccess) {
             return SECFailure;
         }
-        rv = sslBuffer_AppendNumber(wrBuf, cwSpec->seqNum, 6);
+        rv = sslBuffer_AppendNumber(wrBuf, cwSpec->nextSeqNum, 6);
         if (rv != SECSuccess) {
             return SECFailure;
         }
     }
-    rv = sslBuffer_AppendNumber(wrBuf, len, 2);
-    if (rv != SECSuccess) {
-        return SECFailure;
-    }
-
+    *needsLength = PR_TRUE;
     return SECSuccess;
 }
 
 SECStatus
 ssl_ProtectRecord(sslSocket *ss, ssl3CipherSpec *cwSpec, SSL3ContentType type,
                   const PRUint8 *pIn, PRUint32 contentLen, sslBuffer *wrBuf)
 {
-    unsigned int headerLen = IS_DTLS(ss) ? DTLS_RECORD_HEADER_LENGTH
-                                         : SSL3_RECORD_HEADER_LENGTH;
-    sslBuffer protBuf = SSL_BUFFER_FIXED(SSL_BUFFER_BASE(wrBuf) + headerLen,
-                                         SSL_BUFFER_SPACE(wrBuf) - headerLen);
-    PRBool isTLS13;
+    PRBool needsLength;
+    unsigned int lenOffset;
     SECStatus rv;
 
     PORT_Assert(cwSpec->direction == CipherSpecWrite);
     PORT_Assert(SSL_BUFFER_LEN(wrBuf) == 0);
     PORT_Assert(cwSpec->cipherDef->max_records <= RECORD_SEQ_MAX);
-    if (cwSpec->seqNum >= cwSpec->cipherDef->max_records) {
+
+    if (cwSpec->nextSeqNum >= cwSpec->cipherDef->max_records) {
         /* We should have automatically updated before here in TLS 1.3. */
         PORT_Assert(cwSpec->version < SSL_LIBRARY_VERSION_TLS_1_3);
         SSL_TRC(3, ("%d: SSL[-]: write sequence number at limit 0x%0llx",
-                    SSL_GETPID(), cwSpec->seqNum));
+                    SSL_GETPID(), cwSpec->nextSeqNum));
         PORT_SetError(SSL_ERROR_TOO_MANY_RECORDS);
         return SECFailure;
     }
 
-    isTLS13 = (PRBool)(cwSpec->version >= SSL_LIBRARY_VERSION_TLS_1_3);
+    rv = ssl_InsertRecordHeader(ss, cwSpec, type, wrBuf, &needsLength);
+    if (rv != SECSuccess) {
+        return SECFailure;
+    }
+    if (needsLength) {
+        rv = sslBuffer_Skip(wrBuf, 2, &lenOffset);
+        if (rv != SECSuccess) {
+            return SECFailure;
+        }
+    }
 
 #ifdef UNSAFE_FUZZER_MODE
     {
         int len;
-        rv = Null_Cipher(NULL, SSL_BUFFER_BASE(&protBuf), &len,
-                         SSL_BUFFER_SPACE(&protBuf), pIn, contentLen);
+        rv = Null_Cipher(NULL, SSL_BUFFER_NEXT(wrBuf), &len,
+                         SSL_BUFFER_SPACE(wrBuf), pIn, contentLen);
         if (rv != SECSuccess) {
             return SECFailure; /* error was set */
         }
-        rv = sslBuffer_Skip(&protBuf, len, NULL);
+        rv = sslBuffer_Skip(wrBuf, len, NULL);
         PORT_Assert(rv == SECSuccess); /* Can't fail. */
     }
 #else
-    if (isTLS13) {
-        rv = tls13_ProtectRecord(ss, cwSpec, type, pIn, contentLen, &protBuf);
+    if (cwSpec->version >= SSL_LIBRARY_VERSION_TLS_1_3) {
+        rv = tls13_ProtectRecord(ss, cwSpec, type, pIn, contentLen, wrBuf);
     } else {
         rv = ssl3_MACEncryptRecord(cwSpec, ss->sec.isServer, IS_DTLS(ss), type,
-                                   pIn, contentLen, &protBuf);
+                                   pIn, contentLen, wrBuf);
     }
 #endif
     if (rv != SECSuccess) {
         return SECFailure; /* error was set */
     }
 
-    PORT_Assert(protBuf.len <= MAX_FRAGMENT_LENGTH + (isTLS13 ? 256 : 1024));
-
-    rv = ssl_InsertRecordHeader(ss, cwSpec, type, SSL_BUFFER_LEN(&protBuf),
-                                wrBuf);
-    if (rv != SECSuccess) {
-        return SECFailure;
-    }
-
-    PORT_Assert(SSL_BUFFER_LEN(wrBuf) == headerLen);
-    rv = sslBuffer_Skip(wrBuf, SSL_BUFFER_LEN(&protBuf), NULL);
-    if (rv != SECSuccess) {
-        PORT_Assert(0); /* Can't fail. */
-        return SECFailure;
-    }
-    ++cwSpec->seqNum;
-
+    if (needsLength) {
+        /* Insert the length. */
+        rv = sslBuffer_InsertLength(wrBuf, lenOffset, 2);
+        if (rv != SECSuccess) {
+            PORT_Assert(0); /* Can't fail. */
+            return SECFailure;
+        }
+    }
+
+    ++cwSpec->nextSeqNum;
     return SECSuccess;
 }
 
 SECStatus
 ssl_ProtectNextRecord(sslSocket *ss, ssl3CipherSpec *spec, SSL3ContentType type,
                       const PRUint8 *pIn, unsigned int nIn,
                       unsigned int *written)
 {
@@ -2286,16 +2291,17 @@ ssl_ProtectNextRecord(sslSocket *ss, ssl
     if (rv != SECSuccess) {
         return SECFailure;
     }
     PRINT_BUF(50, (ss, "send (encrypted) record data:",
                    SSL_BUFFER_BASE(wrBuf), SSL_BUFFER_LEN(wrBuf)));
     *written = contentLen;
     return SECSuccess;
 }
+
 /* Process the plain text before sending it.
  * Returns the number of bytes of plaintext that were successfully sent
  *  plus the number of bytes of plaintext that were copied into the
  *  output (write) buffer.
  * Returns SECFailure on a hard IO error, memory error, or crypto error.
  * Does NOT return SECWouldBlock.
  *
  * Notes on the use of the private ssl flags:
@@ -2363,17 +2369,17 @@ ssl3_SendRecord(sslSocket *ss,
     while (nIn > 0) {
         unsigned int written = 0;
         PRInt32 sent;
 
         ssl_GetSpecReadLock(ss);
         rv = ssl_ProtectNextRecord(ss, spec, type, pIn, nIn, &written);
         ssl_ReleaseSpecReadLock(ss);
         if (rv != SECSuccess) {
-            return SECFailure;
+            goto loser;
         }
 
         PORT_Assert(written > 0);
         /* DTLS should not fragment non-application data here. */
         if (IS_DTLS(ss) && type != content_application_data) {
             PORT_Assert(written == nIn);
         }
 
@@ -6160,17 +6166,16 @@ ssl3_HandleServerHello(sslSocket *ss, PR
 {
     PRUint32 cipher;
     int errCode = SSL_ERROR_RX_MALFORMED_SERVER_HELLO;
     PRUint32 compression;
     SECStatus rv;
     SECItem sidBytes = { siBuffer, NULL, 0 };
     PRBool isHelloRetry;
     SSL3AlertDescription desc = illegal_parameter;
-    TLSExtension *versionExtension;
     const PRUint8 *savedMsg = b;
     const PRUint32 savedLength = length;
 #ifndef TLS_1_3_DRAFT_VERSION
     SSL3ProtocolVersion downgradeCheckVersion;
 #endif
 
     SSL_TRC(3, ("%d: SSL3[%d]: handle server_hello handshake",
                 SSL_GETPID(), ss->fd));
@@ -6251,26 +6256,20 @@ ssl3_HandleServerHello(sslSocket *ss, PR
             goto alert_loser;
         }
         rv = ssl3_ParseExtensions(ss, &b, &length);
         if (rv != SECSuccess) {
             goto alert_loser; /* malformed */
         }
     }
 
-    /* Update the version based on the extension, as necessary. */
-    versionExtension = ssl3_FindExtension(ss, ssl_tls13_supported_versions_xtn);
-    if (versionExtension) {
-        rv = ssl_ClientReadVersion(ss, &versionExtension->data.data,
-                                   &versionExtension->data.len,
-                                   &ss->version);
-        if (rv != SECSuccess) {
-            errCode = PORT_GetError();
-            goto loser; /* An alert is sent by ssl_ClientReadVersion */
-        }
+    /* Read supported_versions if present. */
+    rv = tls13_ClientReadSupportedVersion(ss);
+    if (rv != SECSuccess) {
+        goto loser;
     }
 
     PORT_Assert(!SSL_ALL_VERSIONS_DISABLED(&ss->vrange));
     /* Check that the version is within the configured range. */
     if (ss->vrange.min > ss->version || ss->vrange.max < ss->version) {
         desc = (ss->version > SSL_LIBRARY_VERSION_3_0)
                    ? protocol_version
                    : handshake_failure;
@@ -6345,18 +6344,19 @@ ssl3_HandleServerHello(sslSocket *ss, PR
             goto alert_loser;
         }
     }
 #endif
 
     /* Finally, now all the version-related checks have passed. */
     ss->ssl3.hs.preliminaryInfo |= ssl_preinfo_version;
     /* Update the write cipher spec to match the version. But not after
-     * HelloRetryRequest, because cwSpec might be a 0-RTT cipher spec. */
-    if (!ss->firstHsDone && !ss->ssl3.hs.helloRetry) {
+     * HelloRetryRequest, because cwSpec might be a 0-RTT cipher spec,
+     * in which case this is a no-op. */
+    if (!ss->firstHsDone && !isHelloRetry) {
         ssl_GetSpecWriteLock(ss);
         ssl_SetSpecVersions(ss, ss->ssl3.cwSpec);
         ssl_ReleaseSpecWriteLock(ss);
     }
 
     /* Check that the session ID is as expected. */
     if (ss->version >= SSL_LIBRARY_VERSION_TLS_1_3) {
         PRUint8 buf[SSL3_SESSIONID_BYTES];
@@ -8843,22 +8843,20 @@ loser:
 SECStatus
 ssl_ConstructServerHello(sslSocket *ss, PRBool helloRetry,
                          const sslBuffer *extensionBuf, sslBuffer *messageBuf)
 {
     SECStatus rv;
     SSL3ProtocolVersion version;
     sslSessionID *sid = ss->sec.ci.sid;
 
-    if (IS_DTLS(ss) && ss->version < SSL_LIBRARY_VERSION_TLS_1_3) {
-        version = dtls_TLSVersionToDTLSVersion(ss->version);
-    } else {
-        version = PR_MIN(ss->version, SSL_LIBRARY_VERSION_TLS_1_2);
-    }
-
+    version = PR_MIN(ss->version, SSL_LIBRARY_VERSION_TLS_1_2);
+    if (IS_DTLS(ss)) {
+        version = dtls_TLSVersionToDTLSVersion(version);
+    }
     rv = sslBuffer_AppendNumber(messageBuf, version, 2);
     if (rv != SECSuccess) {
         return SECFailure;
     }
     /* Random already generated in ssl3_HandleClientHello */
     rv = sslBuffer_Append(messageBuf, helloRetry ? ssl_hello_retry_random : ss->ssl3.hs.server_random,
                           SSL3_RANDOM_LENGTH);
     if (rv != SECSuccess) {
@@ -11842,16 +11840,17 @@ ssl3_UnprotectRecord(sslSocket *ss,
                      SSL3Ciphertext *cText, sslBuffer *plaintext,
                      SSL3AlertDescription *alert)
 {
     const ssl3BulkCipherDef *cipher_def = spec->cipherDef;
     PRBool isTLS;
     unsigned int good;
     unsigned int ivLen = 0;
     SSL3ContentType rType;
+    SSL3ProtocolVersion rVersion;
     unsigned int minLength;
     unsigned int originalLen = 0;
     PRUint8 headerBuf[13];
     sslBuffer header = SSL_BUFFER(headerBuf);
     PRUint8 hash[MAX_MAC_LENGTH];
     PRUint8 givenHashBuf[MAX_MAC_LENGTH];
     PRUint8 *givenHash;
     unsigned int hashBytes = MAX_MAC_LENGTH + 1;
@@ -11914,28 +11913,30 @@ ssl3_UnprotectRecord(sslSocket *ss,
     isTLS = (PRBool)(spec->version > SSL_LIBRARY_VERSION_3_0);
 
     if (isTLS && cText->buf->len - ivLen > (MAX_FRAGMENT_LENGTH + 2048)) {
         *alert = record_overflow;
         PORT_SetError(SSL_ERROR_RX_RECORD_TOO_LONG);
         return SECFailure;
     }
 
-    rType = cText->type;
+    rType = (SSL3ContentType)cText->hdr[0];
+    rVersion = ((SSL3ProtocolVersion)cText->hdr[1] << 8) |
+               (SSL3ProtocolVersion)cText->hdr[2];
     if (cipher_def->type == type_aead) {
         /* XXX For many AEAD ciphers, the plaintext is shorter than the
          * ciphertext by a fixed byte count, but it is not true in general.
          * Each AEAD cipher should provide a function that returns the
          * plaintext length for a given ciphertext. */
         unsigned int decryptedLen =
             cText->buf->len - cipher_def->explicit_nonce_size -
             cipher_def->tag_size;
         rv = ssl3_BuildRecordPseudoHeader(
-            spec->epoch, IS_DTLS(ss) ? cText->seq_num : spec->seqNum,
-            rType, isTLS, cText->version, IS_DTLS(ss), decryptedLen, &header);
+            spec->epoch, cText->seqNum,
+            rType, isTLS, rVersion, IS_DTLS(ss), decryptedLen, &header);
         PORT_Assert(rv == SECSuccess);
         rv = spec->aead(&spec->keyMaterial,
                         PR_TRUE,                /* do decrypt */
                         plaintext->buf,         /* out */
                         (int *)&plaintext->len, /* outlen */
                         plaintext->space,       /* maxout */
                         cText->buf->buf,        /* in */
                         cText->buf->len,        /* inlen */
@@ -11972,18 +11973,18 @@ ssl3_UnprotectRecord(sslSocket *ss,
             } else {
                 good &= SECStatusToMask(ssl_RemoveTLSCBCPadding(
                     plaintext, macSize));
             }
         }
 
         /* compute the MAC */
         rv = ssl3_BuildRecordPseudoHeader(
-            spec->epoch, IS_DTLS(ss) ? cText->seq_num : spec->seqNum,
-            rType, isTLS, cText->version, IS_DTLS(ss),
+            spec->epoch, cText->seqNum,
+            rType, isTLS, rVersion, IS_DTLS(ss),
             plaintext->len - spec->macDef->mac_size, &header);
         PORT_Assert(rv == SECSuccess);
         if (cipher_def->type == type_block) {
             rv = ssl3_ComputeRecordMACConstantTime(
                 spec, SSL_BUFFER_BASE(&header), SSL_BUFFER_LEN(&header),
                 plaintext->buf, plaintext->len, originalLen,
                 hash, &hashBytes);
 
@@ -12023,23 +12024,29 @@ ssl3_UnprotectRecord(sslSocket *ss,
         /* always log mac error, in case attacker can read server logs. */
         PORT_SetError(SSL_ERROR_BAD_MAC_READ);
         *alert = bad_record_mac;
         return SECFailure;
     }
     return SECSuccess;
 }
 
-static SECStatus
+SECStatus
 ssl3_HandleNonApplicationData(sslSocket *ss, SSL3ContentType rType,
                               DTLSEpoch epoch, sslSequenceNumber seqNum,
                               sslBuffer *databuf)
 {
     SECStatus rv;
 
+    /* check for Token Presence */
+    if (!ssl3_ClientAuthTokenPresent(ss->sec.ci.sid)) {
+        PORT_SetError(SSL_ERROR_TOKEN_INSERTION_REMOVAL);
+        return SECFailure;
+    }
+
     ssl_GetSSL3HandshakeLock(ss);
 
     /* All the functions called in this switch MUST set error code if
     ** they return SECFailure or SECWouldBlock.
     */
     switch (rType) {
         case content_change_cipher_spec:
             rv = ssl3_HandleChangeCipherSpecs(ss, databuf);
@@ -12075,25 +12082,26 @@ ssl3_HandleNonApplicationData(sslSocket 
 
 /* Find the cipher spec to use for a given record. For TLS, this
  * is the current cipherspec. For DTLS, we look up by epoch.
  * In DTLS < 1.3 this just means the current epoch or nothing,
  * but in DTLS >= 1.3, we keep multiple reading cipherspecs.
  * Returns NULL if no appropriate cipher spec is found.
  */
 static ssl3CipherSpec *
-ssl3_GetCipherSpec(sslSocket *ss, sslSequenceNumber seq)
+ssl3_GetCipherSpec(sslSocket *ss, SSL3Ciphertext *cText)
 {
     ssl3CipherSpec *crSpec = ss->ssl3.crSpec;
     ssl3CipherSpec *newSpec = NULL;
-    DTLSEpoch epoch = seq >> 48;
+    DTLSEpoch epoch;
 
     if (!IS_DTLS(ss)) {
         return crSpec;
     }
+    epoch = dtls_ReadEpoch(crSpec, cText->hdr);
     if (crSpec->epoch == epoch) {
         return crSpec;
     }
     if (ss->version >= SSL_LIBRARY_VERSION_TLS_1_3) {
         /* Try to find the cipher spec. */
         newSpec = ssl_FindCipherSpecByEpoch(ss, CipherSpecRead,
                                             epoch);
         if (newSpec != NULL) {
@@ -12123,141 +12131,124 @@ ssl3_GetCipherSpec(sslSocket *ss, sslSeq
  *
  * Caller must hold the RecvBufLock.
  *
  * This function aquires and releases the SSL3Handshake Lock, holding the
  * lock around any calls to functions that handle records other than
  * Application Data records.
  */
 SECStatus
-ssl3_HandleRecord(sslSocket *ss, SSL3Ciphertext *cText, sslBuffer *databuf)
+ssl3_HandleRecord(sslSocket *ss, SSL3Ciphertext *cText)
 {
     SECStatus rv;
     PRBool isTLS;
     DTLSEpoch epoch;
-    sslSequenceNumber seqNum = 0;
     ssl3CipherSpec *spec = NULL;
     PRBool outOfOrderSpec = PR_FALSE;
     SSL3ContentType rType;
-    sslBuffer *plaintext;
+    sslBuffer *plaintext = &ss->gs.buf;
     SSL3AlertDescription alert = internal_error;
     PORT_Assert(ss->opt.noLocks || ssl_HaveRecvBufLock(ss));
 
     /* check for Token Presence */
     if (!ssl3_ClientAuthTokenPresent(ss->sec.ci.sid)) {
         PORT_SetError(SSL_ERROR_TOKEN_INSERTION_REMOVAL);
         return SECFailure;
     }
 
-    /* cText is NULL when we're called from ssl3_RestartHandshakeAfterXXX().
-     * This implies that databuf holds a previously deciphered SSL Handshake
-     * message.
-     */
-    if (cText == NULL) {
-        SSL_DBG(("%d: SSL3[%d]: HandleRecord, resuming handshake",
-                 SSL_GETPID(), ss->fd));
-        /* Note that this doesn't pass the epoch and sequence number of the
-         * record through, which DTLS 1.3 depends on.  DTLS doesn't support
-         * asynchronous certificate validation, so that should be OK. */
-        PORT_Assert(!IS_DTLS(ss));
-        return ssl3_HandleNonApplicationData(ss, content_handshake,
-                                             0, 0, databuf);
-    }
+    /* Clear out the buffer in case this exits early.  Any data then won't be
+     * processed twice. */
+    plaintext->len = 0;
 
     ssl_GetSpecReadLock(ss); /******************************************/
-    spec = ssl3_GetCipherSpec(ss, cText->seq_num);
+    spec = ssl3_GetCipherSpec(ss, cText);
     if (!spec) {
         PORT_Assert(IS_DTLS(ss));
         ssl_ReleaseSpecReadLock(ss); /*****************************/
-        databuf->len = 0;            /* Needed to ensure data not left around */
         return SECSuccess;
     }
     if (spec != ss->ssl3.crSpec) {
         PORT_Assert(IS_DTLS(ss));
         SSL_TRC(3, ("%d: DTLS[%d]: Handling out-of-epoch record from epoch=%d",
                     SSL_GETPID(), ss->fd, spec->epoch));
         outOfOrderSpec = PR_TRUE;
     }
     isTLS = (PRBool)(spec->version > SSL_LIBRARY_VERSION_3_0);
     if (IS_DTLS(ss)) {
-        if (!dtls_IsRelevant(ss, spec, cText, &seqNum)) {
+        if (!dtls_IsRelevant(ss, spec, cText, &cText->seqNum)) {
             ssl_ReleaseSpecReadLock(ss); /*****************************/
-            databuf->len = 0;            /* Needed to ensure data not left around */
-
             return SECSuccess;
         }
     } else {
-        seqNum = spec->seqNum + 1;
-    }
-    if (seqNum >= spec->cipherDef->max_records) {
+        cText->seqNum = spec->nextSeqNum;
+    }
+    if (cText->seqNum >= spec->cipherDef->max_records) {
         ssl_ReleaseSpecReadLock(ss); /*****************************/
         SSL_TRC(3, ("%d: SSL[%d]: read sequence number at limit 0x%0llx",
-                    SSL_GETPID(), ss->fd, seqNum));
+                    SSL_GETPID(), ss->fd, cText->seqNum));
         PORT_SetError(SSL_ERROR_TOO_MANY_RECORDS);
         return SECFailure;
     }
 
-    plaintext = databuf;
-    plaintext->len = 0; /* filled in by Unprotect call below. */
-
     /* We're waiting for another ClientHello, which will appear unencrypted.
      * Use the content type to tell whether this is should be discarded.
      *
      * XXX If we decide to remove the content type from encrypted records, this
      *     will become much more difficult to manage. */
     if (ss->ssl3.hs.zeroRttIgnore == ssl_0rtt_ignore_hrr &&
-        cText->type == content_application_data) {
+        cText->hdr[0] == content_application_data) {
         ssl_ReleaseSpecReadLock(ss); /*****************************/
         PORT_Assert(ss->ssl3.hs.ws == wait_client_hello);
-        databuf->len = 0;
         return SECSuccess;
     }
 
     if (plaintext->space < MAX_FRAGMENT_LENGTH) {
         rv = sslBuffer_Grow(plaintext, MAX_FRAGMENT_LENGTH + 2048);
         if (rv != SECSuccess) {
             ssl_ReleaseSpecReadLock(ss); /*************************/
             SSL_DBG(("%d: SSL3[%d]: HandleRecord, tried to get %d bytes",
                      SSL_GETPID(), ss->fd, MAX_FRAGMENT_LENGTH + 2048));
             /* sslBuffer_Grow has set a memory error code. */
             /* Perhaps we should send an alert. (but we have no memory!) */
             return SECFailure;
         }
     }
 
 #ifdef UNSAFE_FUZZER_MODE
+    rType = cText->hdr[0];
     rv = Null_Cipher(NULL, plaintext->buf, (int *)&plaintext->len,
                      plaintext->space, cText->buf->buf, cText->buf->len);
 #else
     /* IMPORTANT: Unprotect functions MUST NOT send alerts
      * because we still hold the spec read lock. Instead, if they
      * return SECFailure, they set *alert to the alert to be sent. */
     if (spec->version < SSL_LIBRARY_VERSION_TLS_1_3 ||
         spec->cipherDef->calg == ssl_calg_null) {
         /* Unencrypted TLS 1.3 records use the pre-TLS 1.3 format. */
+        rType = cText->hdr[0];
         rv = ssl3_UnprotectRecord(ss, spec, cText, plaintext, &alert);
     } else {
-        rv = tls13_UnprotectRecord(ss, spec, cText, plaintext, &alert);
+        rv = tls13_UnprotectRecord(ss, spec, cText, plaintext, &rType, &alert);
     }
 #endif
 
     if (rv != SECSuccess) {
         ssl_ReleaseSpecReadLock(ss); /***************************/
 
         SSL_DBG(("%d: SSL3[%d]: decryption failed", SSL_GETPID(), ss->fd));
 
         /* Ensure that we don't process this data again. */
-        databuf->len = 0;
+        plaintext->len = 0;
 
         /* Ignore a CCS if the alternative handshake is negotiated.  Note that
          * this will fail if the server fails to negotiate the alternative
          * handshake type in a 0-RTT session that is resumed from a session that
          * did negotiate it.  We don't care about that corner case right now. */
         if (ss->version >= SSL_LIBRARY_VERSION_TLS_1_3 &&
-            cText->type == content_change_cipher_spec &&
+            cText->hdr[0] == content_change_cipher_spec &&
             ss->ssl3.hs.ws != idle_handshake &&
             cText->buf->len == 1 &&
             cText->buf->buf[0] == change_cipher_spec_choice) {
             /* Ignore the CCS. */
             return SECSuccess;
         }
         if (IS_DTLS(ss) ||
             (ss->sec.isServer &&
@@ -12270,62 +12261,65 @@ ssl3_HandleRecord(sslSocket *ss, SSL3Cip
             /* Reset the error code in case SSL3_SendAlert called
              * PORT_SetError(). */
             PORT_SetError(errCode);
             return SECFailure;
         }
     }
 
     /* SECSuccess */
-    spec->seqNum = PR_MAX(spec->seqNum, seqNum);
     if (IS_DTLS(ss)) {
-        dtls_RecordSetRecvd(&spec->recvdRecords, seqNum);
+        dtls_RecordSetRecvd(&spec->recvdRecords, cText->seqNum);
+        spec->nextSeqNum = PR_MAX(spec->nextSeqNum, cText->seqNum + 1);
+    } else {
+        ++spec->nextSeqNum;
     }
     epoch = spec->epoch;
 
     ssl_ReleaseSpecReadLock(ss); /*****************************************/
 
     /*
      * The decrypted data is now in plaintext.
      */
-    rType = cText->type; /* This must go after decryption because TLS 1.3
-                          * has encrypted content types. */
 
     /* IMPORTANT: We are in DTLS 1.3 mode and we have processed something
      * from the wrong epoch. Divert to a divert processing function to make
      * sure we don't accidentally use the data unsafely. */
     if (outOfOrderSpec) {
         PORT_Assert(IS_DTLS(ss) && ss->version >= SSL_LIBRARY_VERSION_TLS_1_3);
-        return dtls13_HandleOutOfEpochRecord(ss, spec, rType, databuf);
+        return dtls13_HandleOutOfEpochRecord(ss, spec, rType, plaintext);
     }
 
     /* Check the length of the plaintext. */
-    if (isTLS && databuf->len > MAX_FRAGMENT_LENGTH) {
+    if (isTLS && plaintext->len > MAX_FRAGMENT_LENGTH) {
+        plaintext->len = 0;
         SSL3_SendAlert(ss, alert_fatal, record_overflow);
         PORT_SetError(SSL_ERROR_RX_RECORD_TOO_LONG);
         return SECFailure;
     }
 
     /* Application data records are processed by the caller of this
     ** function, not by this function.
     */
     if (rType == content_application_data) {
         if (ss->firstHsDone)
             return SECSuccess;
         if (ss->version >= SSL_LIBRARY_VERSION_TLS_1_3 &&
             ss->sec.isServer &&
             ss->ssl3.hs.zeroRttState == ssl_0rtt_accepted) {
-            return tls13_HandleEarlyApplicationData(ss, databuf);
-        }
+            return tls13_HandleEarlyApplicationData(ss, plaintext);
+        }
+        plaintext->len = 0;
         (void)SSL3_SendAlert(ss, alert_fatal, unexpected_message);
         PORT_SetError(SSL_ERROR_RX_UNEXPECTED_APPLICATION_DATA);
         return SECFailure;
     }
 
-    return ssl3_HandleNonApplicationData(ss, rType, epoch, seqNum, databuf);
+    return ssl3_HandleNonApplicationData(ss, rType, epoch, cText->seqNum,
+                                         plaintext);
 }
 
 /*
  * Initialization functions
  */
 
 void
 ssl_InitSecState(sslSecurityInfo *sec)
--- a/security/nss/lib/ssl/ssl3gthr.c
+++ b/security/nss/lib/ssl/ssl3gthr.c
@@ -153,16 +153,17 @@ ssl3_GatherData(sslSocket *ss, sslGather
                  * Always assume v3 after we received the first record. */
                 if (!ssl2gs ||
                     ss->gs.rejectV2Records ||
                     ssl3_isLikelyV3Hello(gs->hdr)) {
                     /* Should have a non-SSLv2 record header in gs->hdr. Extract
                      * the length of the following encrypted data, and then
                      * read in the rest of the record into gs->inbuf. */
                     gs->remainder = (gs->hdr[3] << 8) | gs->hdr[4];
+                    gs->hdrLen = SSL3_RECORD_HEADER_LENGTH;
                 } else {
                     /* Probably an SSLv2 record header. No need to handle any
                      * security escapes (gs->hdr[0] & 0x40) as we wouldn't get
                      * here if one was set. See ssl3_isLikelyV3Hello(). */
                     gs->remainder = ((gs->hdr[0] & 0x7f) << 8) | gs->hdr[1];
                     ssl2gs->isV2 = PR_TRUE;
                     v2HdrLength = 2;
 
@@ -259,18 +260,19 @@ ssl3_GatherData(sslSocket *ss, sslGather
  *      (a) an error or EOF occurs,
  *  (b) PR_WOULD_BLOCK_ERROR,
  *  (c) data (entire DTLS record) has been received.
  */
 static int
 dtls_GatherData(sslSocket *ss, sslGather *gs, int flags)
 {
     int nb;
-    int err;
-    int rv = 1;
+    PRUint8 contentType;
+    unsigned int headerLen;
+    SECStatus rv;
 
     SSL_TRC(30, ("dtls_GatherData"));
 
     PORT_Assert(ss->opt.noLocks || ssl_HaveRecvBufLock(ss));
 
     gs->state = GS_HEADER;
     gs->offset = 0;
 
@@ -280,91 +282,107 @@ dtls_GatherData(sslSocket *ss, sslGather
 
         /* Resize to the maximum possible size so we can fit a full datagram */
         /* This is the max fragment length for an encrypted fragment
         ** plus the size of the record header.
         ** This magic constant is copied from ssl3_GatherData, with 5 changed
         ** to 13 (the size of the record header).
         */
         if (gs->dtlsPacket.space < MAX_FRAGMENT_LENGTH + 2048 + 13) {
-            err = sslBuffer_Grow(&gs->dtlsPacket,
-                                 MAX_FRAGMENT_LENGTH + 2048 + 13);
-            if (err) { /* realloc has set error code to no mem. */
-                return err;
+            rv = sslBuffer_Grow(&gs->dtlsPacket,
+                                MAX_FRAGMENT_LENGTH + 2048 + 13);
+            if (rv != SECSuccess) {
+                return -1; /* Code already set. */
             }
         }
 
         /* recv() needs to read a full datagram at a time */
         nb = ssl_DefRecv(ss, gs->dtlsPacket.buf, gs->dtlsPacket.space, flags);
-
         if (nb > 0) {
             PRINT_BUF(60, (ss, "raw gather data:", gs->dtlsPacket.buf, nb));
         } else if (nb == 0) {
             /* EOF */
             SSL_TRC(30, ("%d: SSL3[%d]: EOF", SSL_GETPID(), ss->fd));
-            rv = 0;
-            return rv;
+            return 0;
         } else /* if (nb < 0) */ {
             SSL_DBG(("%d: SSL3[%d]: recv error %d", SSL_GETPID(), ss->fd,
                      PR_GetError()));
-            rv = SECFailure;
-            return rv;
+            return -1;
         }
 
         gs->dtlsPacket.len = nb;
     }
 
+    contentType = gs->dtlsPacket.buf[gs->dtlsPacketOffset];
+    if (dtls_IsLongHeader(ss->version, contentType)) {
+        headerLen = 13;
+    } else if (contentType == content_application_data) {
+        headerLen = 7;
+    } else if ((contentType & 0xe0) == 0x20) {
+        headerLen = 2;
+    } else {
+        SSL_DBG(("%d: SSL3[%d]: invalid first octet (%d) for DTLS",
+                 SSL_GETPID(), ss->fd, contentType));
+        PORT_SetError(SSL_ERROR_RX_UNKNOWN_RECORD_TYPE);
+        gs->dtlsPacketOffset = 0;
+        gs->dtlsPacket.len = 0;
+        return -1;
+    }
+
     /* At this point we should have >=1 complete records lined up in
      * dtlsPacket. Read off the header.
      */
-    if ((gs->dtlsPacket.len - gs->dtlsPacketOffset) < 13) {
+    if ((gs->dtlsPacket.len - gs->dtlsPacketOffset) < headerLen) {
         SSL_DBG(("%d: SSL3[%d]: rest of DTLS packet "
                  "too short to contain header",
                  SSL_GETPID(), ss->fd));
-        PR_SetError(PR_WOULD_BLOCK_ERROR, 0);
+        PORT_SetError(PR_WOULD_BLOCK_ERROR);
         gs->dtlsPacketOffset = 0;
         gs->dtlsPacket.len = 0;
-        rv = SECFailure;
-        return rv;
+        return -1;
     }
-    memcpy(gs->hdr, gs->dtlsPacket.buf + gs->dtlsPacketOffset, 13);
-    gs->dtlsPacketOffset += 13;
+    memcpy(gs->hdr, SSL_BUFFER_BASE(&gs->dtlsPacket) + gs->dtlsPacketOffset,
+           headerLen);
+    gs->hdrLen = headerLen;
+    gs->dtlsPacketOffset += headerLen;
 
     /* Have received SSL3 record header in gs->hdr. */
-    gs->remainder = (gs->hdr[11] << 8) | gs->hdr[12];
+    if (headerLen == 13) {
+        gs->remainder = (gs->hdr[11] << 8) | gs->hdr[12];
+    } else if (headerLen == 7) {
+        gs->remainder = (gs->hdr[5] << 8) | gs->hdr[6];
+    } else {
+        PORT_Assert(headerLen = 2);
+        gs->remainder = gs->dtlsPacket.len - gs->dtlsPacketOffset;
+    }
 
     if ((gs->dtlsPacket.len - gs->dtlsPacketOffset) < gs->remainder) {
         SSL_DBG(("%d: SSL3[%d]: rest of DTLS packet too short "
                  "to contain rest of body",
                  SSL_GETPID(), ss->fd));
-        PR_SetError(PR_WOULD_BLOCK_ERROR, 0);
+        PORT_SetError(PR_WOULD_BLOCK_ERROR);
         gs->dtlsPacketOffset = 0;
         gs->dtlsPacket.len = 0;
-        rv = SECFailure;
-        return rv;
+        return -1;
     }
 
     /* OK, we have at least one complete packet, copy into inbuf */
-    if (gs->remainder > gs->inbuf.space) {
-        err = sslBuffer_Grow(&gs->inbuf, gs->remainder);
-        if (err) { /* realloc has set error code to no mem. */
-            return err;
-        }
+    gs->inbuf.len = 0;
+    rv = sslBuffer_Append(&gs->inbuf,
+                          SSL_BUFFER_BASE(&gs->dtlsPacket) + gs->dtlsPacketOffset,
+                          gs->remainder);
+    if (rv != SECSuccess) {
+        return -1; /* code already set. */
     }
-
-    SSL_TRC(20, ("%d: SSL3[%d]: dtls gathered record type=%d len=%d",
-                 SSL_GETPID(), ss->fd, gs->hdr[0], gs->inbuf.len));
-
-    memcpy(gs->inbuf.buf, gs->dtlsPacket.buf + gs->dtlsPacketOffset,
-           gs->remainder);
-    gs->inbuf.len = gs->remainder;
     gs->offset = gs->remainder;
     gs->dtlsPacketOffset += gs->remainder;
     gs->state = GS_INIT;
 
+    SSL_TRC(20, ("%d: SSL3[%d]: dtls gathered record type=%d len=%d",
+                 SSL_GETPID(), ss->fd, contentType, gs->inbuf.len));
     return 1;
 }
 
 /* Gather in a record and when complete, Handle that record.
  * Repeat this until the handshake is complete,
  * or until application data is available.
  *
  * Returns  1 when the handshake is completed without error, or
@@ -437,17 +455,21 @@ ssl3_GatherCompleteHandshake(sslSocket *
         ssl_ReleaseSSL3HandshakeLock(ss);
 
         if (handleRecordNow) {
             /* ssl3_HandleHandshake previously returned SECWouldBlock and the
              * as-yet-unprocessed plaintext of that previous handshake record.
              * We need to process it now before we overwrite it with the next
              * handshake record.
              */
-            rv = ssl3_HandleRecord(ss, NULL, &ss->gs.buf);
+            SSL_DBG(("%d: SSL3[%d]: resuming handshake",
+                     SSL_GETPID(), ss->fd));
+            PORT_Assert(!IS_DTLS(ss));
+            rv = ssl3_HandleNonApplicationData(ss, content_handshake,
+                                               0, 0, &ss->gs.buf);
         } else {
             /* State for SSLv2 client hello support. */
             ssl2Gather ssl2gs = { PR_FALSE, 0 };
             ssl2Gather *ssl2gs_ptr = NULL;
 
             /* If we're a server and waiting for a client hello, accept v2. */
             if (ss->sec.isServer && ss->ssl3.hs.ws == wait_client_hello) {
                 ssl2gs_ptr = &ssl2gs;
@@ -490,42 +512,35 @@ ssl3_GatherCompleteHandshake(sslSocket *
                 if (rv < 0) {
                     return rv;
                 }
             } else {
                 /* decipher it, and handle it if it's a handshake.
                  * If it's application data, ss->gs.buf will not be empty upon return.
                  * If it's a change cipher spec, alert, or handshake message,
                  * ss->gs.buf.len will be 0 when ssl3_HandleRecord returns SECSuccess.
+                 *
+                 * cText only needs to be valid for this next function call, so
+                 * it can borrow gs.hdr.
                  */
-                cText.type = (SSL3ContentType)ss->gs.hdr[0];
-                cText.version = (ss->gs.hdr[1] << 8) | ss->gs.hdr[2];
-
-                if (IS_DTLS(ss)) {
-                    sslSequenceNumber seq_num;
-
-                    /* DTLS sequence number */
-                    PORT_Memcpy(&seq_num, &ss->gs.hdr[3], sizeof(seq_num));
-                    cText.seq_num = PR_ntohll(seq_num);
-                }
-
+                cText.hdr = ss->gs.hdr;
+                cText.hdrLen = ss->gs.hdrLen;
                 cText.buf = &ss->gs.inbuf;
-                rv = ssl3_HandleRecord(ss, &cText, &ss->gs.buf);
+                rv = ssl3_HandleRecord(ss, &cText);
             }
         }
         if (rv < 0) {
             return ss->recvdCloseNotify ? 0 : rv;
         }
         if (ss->gs.buf.len > 0) {
             /* We have application data to return to the application. This
              * prioritizes returning application data to the application over
              * completing any renegotiation handshake we may be doing.
              */
             PORT_Assert(ss->firstHsDone);
-            PORT_Assert(cText.type == content_application_data);
             break;
         }
 
         PORT_Assert(keepGoing);
         ssl_GetSSL3HandshakeLock(ss);
         if (ss->ssl3.hs.ws == idle_handshake) {
             /* We are done with the current handshake so stop trying to
              * handshake. Note that it would be safe to test ss->firstHsDone
--- a/security/nss/lib/ssl/ssl3prot.h
+++ b/security/nss/lib/ssl/ssl3prot.h
@@ -11,17 +11,17 @@
 #define __ssl3proto_h_
 
 typedef PRUint16 SSL3ProtocolVersion;
 /* version numbers are defined in sslproto.h */
 
 /* The TLS 1.3 draft version. Used to avoid negotiating
  * between incompatible pre-standard TLS 1.3 drafts.
  * TODO(ekr@rtfm.com): Remove when TLS 1.3 is published. */
-#define TLS_1_3_DRAFT_VERSION 23
+#define TLS_1_3_DRAFT_VERSION 26
 
 typedef PRUint16 ssl3CipherSuite;
 /* The cipher suites are defined in sslproto.h */
 
 #define MAX_CERT_TYPES 10
 #define MAX_MAC_LENGTH 64
 #define MAX_PADDING_LENGTH 64
 #define MAX_KEY_LENGTH 64
--- a/security/nss/lib/ssl/sslimpl.h
+++ b/security/nss/lib/ssl/sslimpl.h
@@ -256,16 +256,17 @@ typedef struct sslOptionsStr {
     unsigned int reuseServerECDHEKey : 1;
     unsigned int enableFallbackSCSV : 1;
     unsigned int enableServerDhe : 1;
     unsigned int enableExtendedMS : 1;
     unsigned int enableSignedCertTimestamps : 1;
     unsigned int requireDHENamedGroups : 1;
     unsigned int enable0RttData : 1;
     unsigned int enableTls13CompatMode : 1;
+    unsigned int enableDtlsShortHeader : 1;
 } sslOptions;
 
 typedef enum { sslHandshakingUndetermined = 0,
                sslHandshakingAsClient,
                sslHandshakingAsServer
 } sslHandshakingType;
 
 #define SSL_LOCK_RANK_SPEC 255
@@ -320,19 +321,21 @@ struct sslGatherStr {
 
     /* Buffer for ssl3 to read (encrypted) data from the socket */
     sslBuffer inbuf; /*recvBufLock*/
 
     /* The ssl[23]_GatherData functions read data into this buffer, rather
     ** than into buf or inbuf, while in the GS_HEADER state.
     ** The portion of the SSL record header put here always comes off the wire
     ** as plaintext, never ciphertext.
-    ** For SSL3/TLS, the plaintext portion is 5 bytes long. For DTLS it is 13.
+    ** For SSL3/TLS, the plaintext portion is 5 bytes long. For DTLS it
+    ** varies based on version and header type.
     */
     unsigned char hdr[13];
+    unsigned int hdrLen;
 
     /* Buffer for DTLS data read off the wire as a single datagram */
     sslBuffer dtlsPacket;
 
     /* the start of the buffered DTLS record in dtlsPacket */
     unsigned int dtlsPacketOffset;
 
     /* tracks whether we've seen a v3-type record before and must reject
@@ -775,19 +778,23 @@ struct ssl3StateStr {
 };
 
 /* Ethernet MTU but without subtracting the headers,
  * so slightly larger than expected */
 #define DTLS_MAX_MTU 1500U
 #define IS_DTLS(ss) (ss->protocolVariant == ssl_variant_datagram)
 
 typedef struct {
-    SSL3ContentType type;
-    SSL3ProtocolVersion version;
-    sslSequenceNumber seq_num; /* DTLS only */
+    /* |seqNum| eventually contains the reconstructed sequence number. */
+    sslSequenceNumber seqNum;
+    /* The header of the cipherText. */
+    const PRUint8 *hdr;
+    unsigned int hdrLen;
+
+    /* |buf| is the payload of the ciphertext. */
     sslBuffer *buf;
 } SSL3Ciphertext;
 
 struct sslKeyPairStr {
     SECKEYPrivateKey *privKey;
     SECKEYPublicKey *pubKey;
     PRInt32 refCount; /* use PR_Atomic calls for this. */
 };
@@ -1370,18 +1377,21 @@ extern SECStatus ssl3_AuthCertificateCom
 extern SECStatus ssl3_HandleV2ClientHello(
     sslSocket *ss, unsigned char *buffer, unsigned int length, PRUint8 padding);
 
 SECStatus ssl3_SendClientHello(sslSocket *ss, sslClientHelloType type);
 
 /*
  * input into the SSL3 machinery from the actualy network reading code
  */
-SECStatus ssl3_HandleRecord(
-    sslSocket *ss, SSL3Ciphertext *cipher, sslBuffer *out);
+SECStatus ssl3_HandleRecord(sslSocket *ss, SSL3Ciphertext *cipher);
+SECStatus ssl3_HandleNonApplicationData(sslSocket *ss, SSL3ContentType rType,
+                                        DTLSEpoch epoch,
+                                        sslSequenceNumber seqNum,
+                                        sslBuffer *databuf);
 SECStatus ssl_RemoveTLSCBCPadding(sslBuffer *plaintext, unsigned int macSize);
 
 int ssl3_GatherAppDataRecord(sslSocket *ss, int flags);
 int ssl3_GatherCompleteHandshake(sslSocket *ss, int flags);
 
 /* Create a new ref counted key pair object from two keys. */
 extern sslKeyPair *ssl_NewKeyPair(SECKEYPrivateKey *privKey,
                                   SECKEYPublicKey *pubKey);
@@ -1631,16 +1641,19 @@ SECStatus ssl_PickSignatureScheme(sslSoc
                                   const SSLSignatureScheme *peerSchemes,
                                   unsigned int peerSchemeCount,
                                   PRBool requireSha1);
 SECOidTag ssl3_HashTypeToOID(SSLHashType hashType);
 SSLHashType ssl_SignatureSchemeToHashType(SSLSignatureScheme scheme);
 KeyType ssl_SignatureSchemeToKeyType(SSLSignatureScheme scheme);
 
 SECStatus ssl3_SetupCipherSuite(sslSocket *ss, PRBool initHashes);
+SECStatus ssl_InsertRecordHeader(const sslSocket *ss, ssl3CipherSpec *cwSpec,
+                                 SSL3ContentType contentType, sslBuffer *wrBuf,
+                                 PRBool *needsLength);
 
 /* Pull in DTLS functions */
 #include "dtlscon.h"
 
 /* Pull in TLS 1.3 functions */
 #include "tls13con.h"
 #include "dtls13con.h"
 
--- a/security/nss/lib/ssl/sslsecur.c
+++ b/security/nss/lib/ssl/sslsecur.c
@@ -786,17 +786,17 @@ tls13_CheckKeyUpdate(sslSocket *ss, Ciph
     ssl_GetSpecReadLock(ss);
     if (dir == CipherSpecRead) {
         spec = ss->ssl3.crSpec;
         margin = spec->cipherDef->max_records / 8;
     } else {
         spec = ss->ssl3.cwSpec;
         margin = spec->cipherDef->max_records / 4;
     }
-    seqNum = spec->seqNum;
+    seqNum = spec->nextSeqNum;
     keyUpdate = seqNum > spec->cipherDef->max_records - margin;
     ssl_ReleaseSpecReadLock(ss);
     if (!keyUpdate) {
         return SECSuccess;
     }
 
     SSL_TRC(5, ("%d: SSL[%d]: automatic key update at %llx for %s cipher spec",
                 SSL_GETPID(), ss->fd, seqNum,
--- a/security/nss/lib/ssl/sslsock.c
+++ b/security/nss/lib/ssl/sslsock.c
@@ -76,17 +76,18 @@ static sslOptions ssl_defaults = {
     .enableALPN = PR_TRUE,
     .reuseServerECDHEKey = PR_TRUE,
     .enableFallbackSCSV = PR_FALSE,
     .enableServerDhe = PR_TRUE,
     .enableExtendedMS = PR_FALSE,
     .enableSignedCertTimestamps = PR_FALSE,
     .requireDHENamedGroups = PR_FALSE,
     .enable0RttData = PR_FALSE,
-    .enableTls13CompatMode = PR_FALSE
+    .enableTls13CompatMode = PR_FALSE,
+    .enableDtlsShortHeader = PR_FALSE
 };
 
 /*
  * default range of enabled SSL/TLS protocols
  */
 static SSLVersionRange versions_defaults_stream = {
     SSL_LIBRARY_VERSION_TLS_1_0,
     SSL_LIBRARY_VERSION_TLS_1_2
@@ -802,16 +803,20 @@ SSL_OptionSet(PRFileDesc *fd, PRInt32 wh
         case SSL_ENABLE_0RTT_DATA:
             ss->opt.enable0RttData = val;
             break;
 
         case SSL_ENABLE_TLS13_COMPAT_MODE:
             ss->opt.enableTls13CompatMode = val;
             break;
 
+        case SSL_ENABLE_DTLS_SHORT_HEADER:
+            ss->opt.enableDtlsShortHeader = val;
+            break;
+
         default:
             PORT_SetError(SEC_ERROR_INVALID_ARGS);
             rv = SECFailure;
     }
 
     /* We can't use the macros for releasing the locks here,
      * because ss->opt.noLocks might have changed just above.
      * We must release these locks (monitors) here, if we aquired them above,
@@ -938,16 +943,19 @@ SSL_OptionGet(PRFileDesc *fd, PRInt32 wh
             val = ss->opt.requireDHENamedGroups;
             break;
         case SSL_ENABLE_0RTT_DATA:
             val = ss->opt.enable0RttData;
             break;
         case SSL_ENABLE_TLS13_COMPAT_MODE:
             val = ss->opt.enableTls13CompatMode;
             break;
+        case SSL_ENABLE_DTLS_SHORT_HEADER:
+            val = ss->opt.enableDtlsShortHeader;
+            break;
         default:
             PORT_SetError(SEC_ERROR_INVALID_ARGS);
             rv = SECFailure;
     }
 
     ssl_ReleaseSSL3HandshakeLock(ss);
     ssl_Release1stHandshakeLock(ss);
 
@@ -1058,16 +1066,19 @@ SSL_OptionGetDefault(PRInt32 which, PRIn
             val = ssl_defaults.enableSignedCertTimestamps;
             break;
         case SSL_ENABLE_0RTT_DATA:
             val = ssl_defaults.enable0RttData;
             break;
         case SSL_ENABLE_TLS13_COMPAT_MODE:
             val = ssl_defaults.enableTls13CompatMode;
             break;
+        case SSL_ENABLE_DTLS_SHORT_HEADER:
+            val = ssl_defaults.enableDtlsShortHeader;
+            break;
         default:
             PORT_SetError(SEC_ERROR_INVALID_ARGS);
             rv = SECFailure;
     }
 
     *pVal = val;
     return rv;
 }
@@ -1241,16 +1252,20 @@ SSL_OptionSetDefault(PRInt32 which, PRIn
         case SSL_ENABLE_0RTT_DATA:
             ssl_defaults.enable0RttData = val;
             break;
 
         case SSL_ENABLE_TLS13_COMPAT_MODE:
             ssl_defaults.enableTls13CompatMode = val;
             break;
 
+        case SSL_ENABLE_DTLS_SHORT_HEADER:
+            ssl_defaults.enableDtlsShortHeader = val;
+            break;
+
         default:
             PORT_SetError(SEC_ERROR_INVALID_ARGS);
             return SECFailure;
     }
     return SECSuccess;
 }
 
 SECStatus
--- a/security/nss/lib/ssl/sslspec.h
+++ b/security/nss/lib/ssl/sslspec.h
@@ -157,17 +157,19 @@ struct ssl3CipherSpecStr {
     SSLAEADCipher aead;
     void *cipherContext;
 
     PK11SymKey *masterSecret;
     ssl3KeyMaterial keyMaterial;
 
     DTLSEpoch epoch;
     const char *phase;
-    sslSequenceNumber seqNum;
+
+    /* The next sequence number to be sent or received. */
+    sslSequenceNumber nextSeqNum;
     DTLSRecvdRecords recvdRecords;
 
     /* The number of 0-RTT bytes that can be sent or received in TLS 1.3. This
      * will be zero for everything but 0-RTT. */
     PRUint32 earlyDataRemaining;
 };
 
 typedef void (*sslCipherSpecChangedFunc)(void *arg,
--- a/security/nss/lib/ssl/tls13con.c
+++ b/security/nss/lib/ssl/tls13con.c
@@ -787,17 +787,17 @@ tls13_HandleKeyUpdate(sslSocket *ss, PRU
     }
 
     if (update == update_requested) {
         PRBool sendUpdate;
         if (ss->ssl3.peerRequestedKeyUpdate) {
             /* Only send an update if we have sent with the current spec.  This
              * prevents us from being forced to crank forward pointlessly. */
             ssl_GetSpecReadLock(ss);
-            sendUpdate = ss->ssl3.cwSpec->seqNum > 0;
+            sendUpdate = ss->ssl3.cwSpec->nextSeqNum > 0;
             ssl_ReleaseSpecReadLock(ss);
         } else {
             sendUpdate = PR_TRUE;
         }
         if (sendUpdate) {
             /* Respond immediately (don't buffer). */
             rv = tls13_SendKeyUpdate(ss, update_not_requested, PR_FALSE);
             if (rv != SECSuccess) {
@@ -1615,17 +1615,17 @@ tls13_HandleClientHelloPart2(sslSocket *
          * we generate are sent with the right sequence numbers. */
         if (IS_DTLS(ss)) {
             /* Count the first ClientHello and the HelloRetryRequest. */
             ss->ssl3.hs.sendMessageSeq = 1;
             ss->ssl3.hs.recvMessageSeq = 1;
             ssl_GetSpecWriteLock(ss);
             /* Increase the write sequence number.  The read sequence number
              * will be reset after this to early data or handshake. */
-            ss->ssl3.cwSpec->seqNum = 1;
+            ss->ssl3.cwSpec->nextSeqNum = 1;
             ssl_ReleaseSpecWriteLock(ss);
         }
 
         if (!ssl3_ExtensionNegotiated(ss, ssl_tls13_cookie_xtn) ||
             !ss->xtnData.cookie.len) {
             FATAL_ERROR(ss, SSL_ERROR_MISSING_COOKIE_EXTENSION,
                         missing_extension);
             goto loser;
@@ -2002,17 +2002,17 @@ tls13_SendHelloRetryRequest(sslSocket *s
         rv = ssl3_FlushHandshake(ss, 0);
         if (rv != SECSuccess) {
             goto loser; /* error code set by ssl3_FlushHandshake */
         }
     }
 
     /* We depend on this being exactly one record and one message. */
     PORT_Assert(!IS_DTLS(ss) || (ss->ssl3.hs.sendMessageSeq == 1 &&
-                                 ss->ssl3.cwSpec->seqNum == 1));
+                                 ss->ssl3.cwSpec->nextSeqNum == 1));
     ssl_ReleaseXmitBufLock(ss);
 
     ss->ssl3.hs.helloRetry = PR_TRUE;
 
     /* We received early data but have to ignore it because we sent a retry. */
     if (ss->ssl3.hs.zeroRttState == ssl_0rtt_sent) {
         ss->ssl3.hs.zeroRttState = ssl_0rtt_ignored;
         ss->ssl3.hs.zeroRttIgnore = ssl_0rtt_ignore_hrr;
@@ -2204,16 +2204,18 @@ tls13_HandleHelloRetryRequest(sslSocket 
         ssl_CipherSpecRelease(ss->ssl3.cwSpec);
         ss->ssl3.cwSpec = ssl_FindCipherSpecByEpoch(ss, CipherSpecWrite,
                                                     TrafficKeyClearText);
         PORT_Assert(ss->ssl3.cwSpec);
         ssl_ReleaseSpecWriteLock(ss);
     } else {
         PORT_Assert(ss->ssl3.hs.zeroRttState == ssl_0rtt_none);
     }
+    /* Set the spec version, because we want to send CH now with 0303 */
+    tls13_SetSpecRecordVersion(ss, ss->ssl3.cwSpec);
 
     /* Extensions must contain more than just supported_versions.  This will
      * ensure that a HelloRetryRequest isn't a no-op: we must have at least two
      * extensions, supported_versions plus one other.  That other must be one
      * that we understand and recognize as being valid for HelloRetryRequest,
      * and all the extensions we permit cause us to modify our second
      * ClientHello in some meaningful way. */
     if (ssl_ListCount(&ss->ssl3.hs.remoteExtensions) <= 1) {
@@ -2243,16 +2245,17 @@ tls13_HandleHelloRetryRequest(sslSocket 
     ssl_GetXmitBufLock(ss);
     if (ss->opt.enableTls13CompatMode && !IS_DTLS(ss) &&
         ss->ssl3.hs.zeroRttState == ssl_0rtt_none) {
         rv = ssl3_SendChangeCipherSpecsInt(ss);
         if (rv != SECSuccess) {
             goto loser;
         }
     }
+
     rv = ssl3_SendClientHello(ss, client_hello_retry);
     if (rv != SECSuccess) {
         goto loser;
     }
 
     ssl_ReleaseXmitBufLock(ss);
     return SECSuccess;
 
@@ -3311,17 +3314,17 @@ tls13_SetCipherSpec(sslSocket *ss, PRUin
     }
 
     /* Create the new spec. */
     spec = ssl_CreateCipherSpec(ss, direction);
     if (!spec) {
         return SECFailure;
     }
     spec->epoch = epoch;
-    spec->seqNum = 0;
+    spec->nextSeqNum = 0;
     if (IS_DTLS(ss)) {
         dtls_InitRecvdRecords(&spec->recvdRecords);
     }
 
     /* This depends on spec having a valid direction and epoch. */
     rv = tls13_SetupPendingCipherSpec(ss, spec);
     if (rv != SECSuccess) {
         goto loser;
@@ -3531,48 +3534,50 @@ tls13_AESGCM(ssl3KeyMaterial *keys,
              const unsigned char *in,
              int inlen,
              const unsigned char *additionalData,
              int additionalDataLen)
 {
     CK_GCM_PARAMS gcmParams;
     unsigned char nonce[12];
 
+    PORT_Assert(additionalDataLen > 8);
     memset(&gcmParams, 0, sizeof(gcmParams));
     gcmParams.pIv = nonce;
     gcmParams.ulIvLen = sizeof(nonce);
-    gcmParams.pAAD = NULL;
-    gcmParams.ulAADLen = 0;
+    gcmParams.pAAD = (PRUint8 *)(additionalData + 8);
+    gcmParams.ulAADLen = additionalDataLen - 8;
     gcmParams.ulTagBits = 128; /* GCM measures tag length in bits. */
 
-    tls13_WriteNonce(keys, additionalData, additionalDataLen,
+    tls13_WriteNonce(keys, additionalData, 8,
                      nonce, sizeof(nonce));
     return tls13_AEAD(keys, doDecrypt, out, outlen, maxout, in, inlen,
                       CKM_AES_GCM,
                       (unsigned char *)&gcmParams, sizeof(gcmParams));
 }
 
 static SECStatus
 tls13_ChaCha20Poly1305(ssl3KeyMaterial *keys, PRBool doDecrypt,
                        unsigned char *out, int *outlen, int maxout,
                        const unsigned char *in, int inlen,
                        const unsigned char *additionalData,
                        int additionalDataLen)
 {
     CK_NSS_AEAD_PARAMS aeadParams;
     unsigned char nonce[12];
 
+    PORT_Assert(additionalDataLen > 8);
     memset(&aeadParams, 0, sizeof(aeadParams));
     aeadParams.pNonce = nonce;
     aeadParams.ulNonceLen = sizeof(nonce);
-    aeadParams.pAAD = NULL; /* No AAD in TLS 1.3. */
-    aeadParams.ulAADLen = 0;
+    aeadParams.pAAD = (PRUint8 *)(additionalData + 8);
+    aeadParams.ulAADLen = additionalDataLen - 8;
     aeadParams.ulTagLen = 16; /* The Poly1305 tag is 16 octets. */
 
-    tls13_WriteNonce(keys, additionalData, additionalDataLen,
+    tls13_WriteNonce(keys, additionalData, 8,
                      nonce, sizeof(nonce));
     return tls13_AEAD(keys, doDecrypt, out, outlen, maxout, in, inlen,
                       CKM_NSS_CHACHA20_POLY1305,
                       (unsigned char *)&aeadParams, sizeof(aeadParams));
 }
 
 static SECStatus
 tls13_HandleEncryptedExtensions(sslSocket *ss, PRUint8 *b, PRUint32 length)
@@ -4775,39 +4780,48 @@ tls13_ExtensionStatus(PRUint16 extension
     return tls13_extension_allowed;
 }
 
 #undef _M
 #undef _M1
 #undef _M2
 #undef _M3
 
-/* TLS 1.3 doesn't actually have additional data but the aead function
- * signature overloads additional data to carry the record sequence
- * number and that's what we put here. The TLS 1.3 AEAD functions
- * just use this input as the sequence number and not as additional
- * data. */
+/* We cheat a bit on additional data because the AEAD interface
+ * which doesn't have room for the record number. The AAD we
+ * format is serialized record number followed by the true AD
+ * (i.e., the record header) plus the serialized record number. */
 static SECStatus
-tls13_FormatAdditionalData(sslSocket *ss, PRUint8 *aad, unsigned int length,
-                           DTLSEpoch epoch, sslSequenceNumber seqNum)
+tls13_FormatAdditionalData(
+    sslSocket *ss,
+    const PRUint8 *header, unsigned int headerLen,
+    DTLSEpoch epoch, sslSequenceNumber seqNum,
+    PRUint8 *aad, unsigned int *aadLength, unsigned int maxLength)
 {
     SECStatus rv;
-    sslBuffer buf = SSL_BUFFER_FIXED(aad, length);
-
-    PORT_Assert(length == 8);
+    sslBuffer buf = SSL_BUFFER_FIXED(aad, maxLength);
+
     if (IS_DTLS(ss)) {
         rv = sslBuffer_AppendNumber(&buf, epoch, 2);
         if (rv != SECSuccess) {
             return SECFailure;
         }
     }
     rv = sslBuffer_AppendNumber(&buf, seqNum, IS_DTLS(ss) ? 6 : 8);
     if (rv != SECSuccess) {
         return SECFailure;
     }
+
+    rv = sslBuffer_Append(&buf, header, headerLen);
+    if (rv != SECSuccess) {
+        return SECFailure;
+    }
+
+    *aadLength = buf.len;
+
     return SECSuccess;
 }
 
 PRInt32
 tls13_LimitEarlyData(sslSocket *ss, SSL3ContentType type, PRInt32 toSend)
 {
     PRInt32 reduced;
 
@@ -4838,53 +4852,78 @@ tls13_ProtectRecord(sslSocket *ss,
 {
     const ssl3BulkCipherDef *cipher_def = cwSpec->cipherDef;
     const int tagLen = cipher_def->tag_size;
     SECStatus rv;
 
     PORT_Assert(cwSpec->direction == CipherSpecWrite);
     SSL_TRC(3, ("%d: TLS13[%d]: spec=%d epoch=%d (%s) protect 0x%0llx len=%u",
                 SSL_GETPID(), ss->fd, cwSpec, cwSpec->epoch, cwSpec->phase,
-                cwSpec->seqNum, contentLen));
-
-    if (contentLen + 1 + tagLen > wrBuf->space) {
+                cwSpec->nextSeqNum, contentLen));
+
+    if (contentLen + 1 + tagLen > SSL_BUFFER_SPACE(wrBuf)) {
         PORT_SetError(SEC_ERROR_LIBRARY_FAILURE);
         return SECFailure;
     }
 
     /* Copy the data into the wrBuf. We're going to encrypt in-place
      * in the AEAD branch anyway */
-    PORT_Memcpy(wrBuf->buf, pIn, contentLen);
+    PORT_Memcpy(SSL_BUFFER_NEXT(wrBuf), pIn, contentLen);
 
     if (cipher_def->calg == ssl_calg_null) {
         /* Shortcut for plaintext */
-        wrBuf->len = contentLen;
+        rv = sslBuffer_Skip(wrBuf, contentLen, NULL);
+        PORT_Assert(rv == SECSuccess);
     } else {
-        PRUint8 aad[8];
+        PRUint8 hdr[13];
+        sslBuffer buf = SSL_BUFFER_FIXED(hdr, sizeof(hdr));
+        PRBool needsLength;
+        PRUint8 aad[21];
+        unsigned int aadLen;
+        int len;
+
         PORT_Assert(cipher_def->type == type_aead);
 
         /* Add the content type at the end. */
-        wrBuf->buf[contentLen] = type;
-
-        rv = tls13_FormatAdditionalData(ss, aad, sizeof(aad), cwSpec->epoch,
-                                        cwSpec->seqNum);
+        *(SSL_BUFFER_NEXT(wrBuf) + contentLen) = type;
+
+        /* Create the header (ugly that we have to do it twice). */
+        rv = ssl_InsertRecordHeader(ss, cwSpec, content_application_data,
+                                    &buf, &needsLength);
+        if (rv != SECSuccess) {
+            return SECFailure;
+        }
+        if (needsLength) {
+            rv = sslBuffer_AppendNumber(&buf, contentLen + 1 +
+                                                  cwSpec->cipherDef->tag_size,
+                                        2);
+            if (rv != SECSuccess) {
+                return SECFailure;
+            }
+        }
+        rv = tls13_FormatAdditionalData(ss, SSL_BUFFER_BASE(&buf), SSL_BUFFER_LEN(&buf),
+                                        cwSpec->epoch, cwSpec->nextSeqNum,
+                                        aad, &aadLen, sizeof(aad));
         if (rv != SECSuccess) {
             return SECFailure;
         }
         rv = cwSpec->aead(&cwSpec->keyMaterial,
-                          PR_FALSE,                   /* do encrypt */
-                          wrBuf->buf,                 /* output  */
-                          (int *)&wrBuf->len,         /* out len */
-                          wrBuf->space,               /* max out */
-                          wrBuf->buf, contentLen + 1, /* input   */
-                          aad, sizeof(aad));
+                          PR_FALSE,                /* do encrypt */
+                          SSL_BUFFER_NEXT(wrBuf),  /* output  */
+                          &len,                    /* out len */
+                          SSL_BUFFER_SPACE(wrBuf), /* max out */
+                          SSL_BUFFER_NEXT(wrBuf),  /* input */
+                          contentLen + 1,          /* input len */
+                          aad, aadLen);
         if (rv != SECSuccess) {
             PORT_SetError(SSL_ERROR_ENCRYPTION_FAILURE);
             return SECFailure;
         }
+        rv = sslBuffer_Skip(wrBuf, len, NULL);
+        PORT_Assert(rv == SECSuccess);
     }
 
     return SECSuccess;
 }
 
 /* Unprotect a TLS 1.3 record and leave the result in plaintext.
  *
  * Called by ssl3_HandleRecord. Caller must hold the spec read lock.
@@ -4892,79 +4931,86 @@ tls13_ProtectRecord(sslSocket *ss,
  *
  * If SECFailure is returned, we:
  * 1. Set |*alert| to the alert to be sent.
  * 2. Call PORT_SetError() witn an appropriate code.
  */
 SECStatus
 tls13_UnprotectRecord(sslSocket *ss,
                       ssl3CipherSpec *spec,
-                      SSL3Ciphertext *cText, sslBuffer *plaintext,
+                      SSL3Ciphertext *cText,
+                      sslBuffer *plaintext,
+                      SSL3ContentType *innerType,
                       SSL3AlertDescription *alert)
 {
     const ssl3BulkCipherDef *cipher_def = spec->cipherDef;
-    sslSequenceNumber seqNum;
-    PRUint8 aad[8];
+    PRUint8 aad[21];
+    unsigned int aadLen;
     SECStatus rv;
 
     *alert = bad_record_mac; /* Default alert for most issues. */
 
     PORT_Assert(spec->direction == CipherSpecRead);
-    if (IS_DTLS(ss)) {
-        seqNum = cText->seq_num & RECORD_SEQ_MASK;
-    } else {
-        seqNum = spec->seqNum;
-    }
     SSL_TRC(3, ("%d: TLS13[%d]: spec=%d epoch=%d (%s) unprotect 0x%0llx len=%u",
-                SSL_GETPID(), ss->fd, spec, spec->epoch, spec->phase, seqNum,
-                cText->buf->len));
+                SSL_GETPID(), ss->fd, spec, spec->epoch, spec->phase,
+                cText->seqNum, cText->buf->len));
 
     /* We can perform this test in variable time because the record's total
      * length and the ciphersuite are both public knowledge. */
     if (cText->buf->len < cipher_def->tag_size) {
         SSL_TRC(3,
                 ("%d: TLS13[%d]: record too short to contain valid AEAD data",
                  SSL_GETPID(), ss->fd));
         PORT_SetError(SSL_ERROR_BAD_MAC_READ);
         return SECFailure;
     }
 
-    /* Verify that the content type is right, even though we overwrite it. */
-    if (cText->type != content_application_data) {
+    /* Verify that the content type is right, even though we overwrite it.
+     * Also allow the DTLS short header in TLS 1.3. */
+    if (!(cText->hdr[0] == content_application_data ||
+          (IS_DTLS(ss) &&
+           ss->version >= SSL_LIBRARY_VERSION_TLS_1_3 &&
+           (cText->hdr[0] & 0xe0) == 0x20))) {
         SSL_TRC(3,
-                ("%d: TLS13[%d]: record has invalid exterior content type=%d",
-                 SSL_GETPID(), ss->fd, cText->type));
+                ("%d: TLS13[%d]: record has invalid exterior type=%2.2x",
+                 SSL_GETPID(), ss->fd, cText->hdr[0]));
         /* Do we need a better error here? */
         PORT_SetError(SSL_ERROR_BAD_MAC_READ);
         return SECFailure;
     }
 
-    /* Check the version number in the record. */
-    if (cText->version != spec->recordVersion) {
-        /* Do we need a better error here? */
-        SSL_TRC(3,
-                ("%d: TLS13[%d]: record has bogus version",
-                 SSL_GETPID(), ss->fd));
-        return SECFailure;
+    /* Check the version number in the record. Stream only. */
+    if (!IS_DTLS(ss)) {
+        SSL3ProtocolVersion version =
+            ((SSL3ProtocolVersion)cText->hdr[1] << 8) |
+            (SSL3ProtocolVersion)cText->hdr[2];
+        if (version != spec->recordVersion) {
+            /* Do we need a better error here? */
+            SSL_TRC(3, ("%d: TLS13[%d]: record has bogus version",
+                        SSL_GETPID(), ss->fd));
+            return SECFailure;
+        }
     }
 
     /* Decrypt */
     PORT_Assert(cipher_def->type == type_aead);
-    rv = tls13_FormatAdditionalData(ss, aad, sizeof(aad), spec->epoch, seqNum);
+    rv = tls13_FormatAdditionalData(ss, cText->hdr, cText->hdrLen,
+                                    spec->epoch, cText->seqNum,
+                                    aad, &aadLen, sizeof(aad));
     if (rv != SECSuccess) {
         return SECFailure;
     }
     rv = spec->aead(&spec->keyMaterial,
                     PR_TRUE,                /* do decrypt */
                     plaintext->buf,         /* out */
                     (int *)&plaintext->len, /* outlen */
                     plaintext->space,       /* maxout */
                     cText->buf->buf,        /* in */
                     cText->buf->len,        /* inlen */
-                    aad, sizeof(aad));
+                    aad, aadLen);
     if (rv != SECSuccess) {
         SSL_TRC(3,
                 ("%d: TLS13[%d]: record has bogus MAC",
                  SSL_GETPID(), ss->fd));
         PORT_SetError(SSL_ERROR_BAD_MAC_READ);
         return SECFailure;
     }
 
@@ -4972,44 +5018,41 @@ tls13_UnprotectRecord(sslSocket *ss,
      * content type, so read from the right until we receive a
      * nonzero byte. */
     while (plaintext->len > 0 && !(plaintext->buf[plaintext->len - 1])) {
         --plaintext->len;
     }
 
     /* Bogus padding. */
     if (plaintext->len < 1) {
-        SSL_TRC(3,
-                ("%d: TLS13[%d]: empty record",
-                 SSL_GETPID(), ss->fd, cText->type));
+        SSL_TRC(3, ("%d: TLS13[%d]: empty record", SSL_GETPID(), ss->fd));
         /* It's safe to report this specifically because it happened
          * after the MAC has been verified. */
         PORT_SetError(SSL_ERROR_BAD_BLOCK_PADDING);
         return SECFailure;
     }
 
     /* Record the type. */
-    cText->type = plaintext->buf[plaintext->len - 1];
+    *innerType = (SSL3ContentType)plaintext->buf[plaintext->len - 1];
     --plaintext->len;
 
     /* Check that we haven't received too much 0-RTT data. */
     if (spec->epoch == TrafficKeyEarlyApplicationData &&
-        cText->type == content_application_data) {
+        *innerType == content_application_data) {
         if (plaintext->len > spec->earlyDataRemaining) {
             *alert = unexpected_message;
             PORT_SetError(SSL_ERROR_TOO_MUCH_EARLY_DATA);
             return SECFailure;
         }
         spec->earlyDataRemaining -= plaintext->len;
     }
 
     SSL_TRC(10,
-            ("%d: TLS13[%d]: %s received record of length=%d type=%d",
-             SSL_GETPID(), ss->fd, SSL_ROLE(ss),
-             plaintext->len, cText->type));
+            ("%d: TLS13[%d]: %s received record of length=%d, type=%d",
+             SSL_GETPID(), ss->fd, SSL_ROLE(ss), plaintext->len, *innerType));
 
     return SECSuccess;
 }
 
 /* 0-RTT is only permitted if:
  *
  * 1. We are doing TLS 1.3
  * 2. This isn't a second ClientHello (in response to HelloRetryRequest)
@@ -5222,16 +5265,68 @@ tls13_EncodeDraftVersion(SSL3ProtocolVer
 #ifdef TLS_1_3_DRAFT_VERSION
     if (version == SSL_LIBRARY_VERSION_TLS_1_3) {
         return 0x7f00 | TLS_1_3_DRAFT_VERSION;
     }
 #endif
     return (PRUint16)version;
 }
 
+SECStatus
+tls13_ClientReadSupportedVersion(sslSocket *ss)
+{
+    PRUint32 temp;
+    SSL3ProtocolVersion v;
+    TLSExtension *versionExtension;
+    SECItem it;
+    SECStatus rv;
+
+    /* Update the version based on the extension, as necessary. */
+    versionExtension = ssl3_FindExtension(ss, ssl_tls13_supported_versions_xtn);
+    if (!versionExtension) {
+        return SECSuccess;
+    }
+
+    /* Struct copy so we don't damage the extension. */
+    it = versionExtension->data;
+
+    rv = ssl3_ConsumeHandshakeNumber(ss, &temp, 2, &it.data, &it.len);
+    if (rv != SECSuccess) {
+        return SECFailure;
+    }
+    if (it.len) {
+        FATAL_ERROR(ss, SSL_ERROR_RX_MALFORMED_SERVER_HELLO, illegal_parameter);
+        return SECFailure;
+    }
+    v = (SSL3ProtocolVersion)temp;
+
+    /* You cannot negotiate < TLS 1.3 with supported_versions. */
+    if (v < SSL_LIBRARY_VERSION_TLS_1_3) {
+        FATAL_ERROR(ss, SSL_ERROR_RX_MALFORMED_SERVER_HELLO, illegal_parameter);
+        return SECFailure;
+    }
+
+#ifdef TLS_1_3_DRAFT_VERSION
+    if (temp == SSL_LIBRARY_VERSION_TLS_1_3) {
+        FATAL_ERROR(ss, SSL_ERROR_UNSUPPORTED_VERSION, protocol_version);
+        return SECFailure;
+    }
+    if (temp == tls13_EncodeDraftVersion(SSL_LIBRARY_VERSION_TLS_1_3)) {
+        v = SSL_LIBRARY_VERSION_TLS_1_3;
+    } else {
+        v = (SSL3ProtocolVersion)temp;
+    }
+#else
+    v = (SSL3ProtocolVersion)temp;
+#endif
+
+    ss->version = v;
+    return SECSuccess;
+}
+
 /* Pick the highest version we support that is also advertised. */
 SECStatus
 tls13_NegotiateVersion(sslSocket *ss, const TLSExtension *supportedVersions)
 {
     PRUint16 version;
     /* Make a copy so we're nondestructive. */
     SECItem data = supportedVersions->data;
     SECItem versions;
--- a/security/nss/lib/ssl/tls13con.h
+++ b/security/nss/lib/ssl/tls13con.h
@@ -23,16 +23,17 @@ typedef enum {
     update_requested = 1
 } tls13KeyUpdateRequest;
 
 #define TLS13_MAX_FINISHED_SIZE 64
 
 SECStatus tls13_UnprotectRecord(
     sslSocket *ss, ssl3CipherSpec *spec,
     SSL3Ciphertext *cText, sslBuffer *plaintext,
+    SSL3ContentType *innerType,
     SSL3AlertDescription *alert);
 
 #if defined(WIN32)
 #define __func__ __FUNCTION__
 #endif
 
 void tls13_SetHsState(sslSocket *ss, SSL3WaitState ws,
                       const char *func, const char *file, int line);
@@ -96,16 +97,17 @@ SECStatus tls13_ProtectRecord(sslSocket 
                               SSL3ContentType type,
                               const PRUint8 *pIn,
                               PRUint32 contentLen,
                               sslBuffer *wrBuf);
 PRInt32 tls13_Read0RttData(sslSocket *ss, void *buf, PRInt32 len);
 SECStatus tls13_HandleEarlyApplicationData(sslSocket *ss, sslBuffer *origBuf);
 PRBool tls13_ClientAllow0Rtt(const sslSocket *ss, const sslSessionID *sid);
 PRUint16 tls13_EncodeDraftVersion(SSL3ProtocolVersion version);
+SECStatus tls13_ClientReadSupportedVersion(sslSocket *ss);
 SECStatus tls13_NegotiateVersion(sslSocket *ss,
                                  const TLSExtension *supported_versions);
 
 PRBool tls13_IsReplay(const sslSocket *ss, const sslSessionID *sid);
 void tls13_AntiReplayRollover(PRTime now);
 
 SECStatus SSLExp_SetupAntiReplay(PRTime window, unsigned int k,
                                  unsigned int bits);
--- a/security/nss/tests/ssl/ssl.sh
+++ b/security/nss/tests/ssl/ssl.sh
@@ -278,44 +278,40 @@ ssl_cov()
   VMAX="tls1.1"
 
   ignore_blank_lines ${SSLCOV} | \
   while read ectype testmax param testname
   do
       echo "${testname}" | grep "EXPORT" > /dev/null
       EXP=$?
 
-      if [ "$ectype" = "ECC" ] ; then
-          echo "$SCRIPTNAME: skipping  $testname (ECC only)"
-      else
-          echo "$SCRIPTNAME: running $testname ----------------------------"
-          VMAX="ssl3"
-          if [ "$testmax" = "TLS10" ]; then
-              VMAX="tls1.0"
-          fi
-          if [ "$testmax" = "TLS11" ]; then
-              VMAX="tls1.1"
-          fi
-          if [ "$testmax" = "TLS12" ]; then
-              VMAX="tls1.2"
-          fi
+      echo "$SCRIPTNAME: running $testname ----------------------------"
+      VMAX="ssl3"
+      if [ "$testmax" = "TLS10" ]; then
+          VMAX="tls1.0"
+      fi
+      if [ "$testmax" = "TLS11" ]; then
+          VMAX="tls1.1"
+      fi
+      if [ "$testmax" = "TLS12" ]; then
+          VMAX="tls1.2"
+      fi
 
-          echo "tstclnt -4 -p ${PORT} -h ${HOSTADDR} -c ${param} -V ${VMIN}:${VMAX} ${CLIENT_OPTIONS} \\"
-          echo "        -f -d ${P_R_CLIENTDIR} $verbose -w nss < ${REQUEST_FILE}"
+      echo "tstclnt -4 -p ${PORT} -h ${HOSTADDR} -c ${param} -V ${VMIN}:${VMAX} ${CLIENT_OPTIONS} \\"
+      echo "        -f -d ${P_R_CLIENTDIR} $verbose -w nss < ${REQUEST_FILE}"
 
-          rm ${TMP}/$HOST.tmp.$$ 2>/dev/null
-          ${PROFTOOL} ${BINDIR}/tstclnt -4 -p ${PORT} -h ${HOSTADDR} -c ${param} -V ${VMIN}:${VMAX} ${CLIENT_OPTIONS} -f \
-                  -d ${P_R_CLIENTDIR} $verbose -w nss < ${REQUEST_FILE} \
-                  >${TMP}/$HOST.tmp.$$  2>&1
-          ret=$?
-          cat ${TMP}/$HOST.tmp.$$
-          rm ${TMP}/$HOST.tmp.$$ 2>/dev/null
-          html_msg $ret 0 "${testname}" \
-                   "produced a returncode of $ret, expected is 0"
-      fi
+      rm ${TMP}/$HOST.tmp.$$ 2>/dev/null
+      ${PROFTOOL} ${BINDIR}/tstclnt -4 -p ${PORT} -h ${HOSTADDR} -c ${param} -V ${VMIN}:${VMAX} ${CLIENT_OPTIONS} -f \
+              -d ${P_R_CLIENTDIR} $verbose -w nss < ${REQUEST_FILE} \
+              >${TMP}/$HOST.tmp.$$  2>&1
+      ret=$?
+      cat ${TMP}/$HOST.tmp.$$
+      rm ${TMP}/$HOST.tmp.$$ 2>/dev/null
+      html_msg $ret 0 "${testname}" \
+               "produced a returncode of $ret, expected is 0"
   done
 
   kill_selfserv
   html "</TABLE><BR>"
 }
 
 ############################## ssl_auth ################################
 # local shell function to perform SSL  Client Authentication tests
@@ -330,18 +326,16 @@ ssl_auth()
   do
       echo "${testname}" | grep "don't require client auth" > /dev/null
       CAUTH=$?
 
       if [ "${CLIENT_MODE}" = "fips" -a "${CAUTH}" -eq 0 ] ; then
           echo "$SCRIPTNAME: skipping  $testname (non-FIPS only)"
       elif [ "$ectype" = "SNI" -a "$NORM_EXT" = "Extended Test" ] ; then
           echo "$SCRIPTNAME: skipping  $testname for $NORM_EXT"
-      elif [ "$ectype" = "ECC" ] ; then
-          echo "$SCRIPTNAME: skipping  $testname (ECC only)"
       else
           cparam=`echo $cparam | sed -e 's;_; ;g' -e "s/TestUser/$USER_NICKNAME/g" `
           if [ "$ectype" = "SNI" ]; then
               cparam=`echo $cparam | sed -e "s/Host/$HOST/g" -e "s/Dom/$DOMSUF/g" `
               sparam=`echo $sparam | sed -e "s/Host/$HOST/g" -e "s/Dom/$DOMSUF/g" `
           fi
           start_selfserv
 
@@ -545,18 +539,16 @@ ssl_stress()
   do
       echo "${testname}" | grep "client auth" > /dev/null
       CAUTH=$?
       echo "${testname}" | grep "no login" > /dev/null
       NOLOGIN=$?
 
       if [ "$ectype" = "SNI" -a "$NORM_EXT" = "Extended Test" ] ; then
           echo "$SCRIPTNAME: skipping  $testname for $NORM_EXT"
-      elif [ "$ectype" = "ECC" ] ; then
-          echo "$SCRIPTNAME: skipping  $testname (ECC only)"
       elif [ "${CLIENT_MODE}" = "fips" -a "${CAUTH}" -ne 0 ] ; then
           echo "$SCRIPTNAME: skipping  $testname (non-FIPS only)"
       elif [ "${NOLOGIN}" -eq 0 ] && \
            [ "${CLIENT_MODE}" = "fips" -o "$NORM_EXT" = "Extended Test" ] ; then
           echo "$SCRIPTNAME: skipping  $testname for $NORM_EXT"
       else
           cparam=`echo $cparam | sed -e 's;_; ;g' -e "s/TestUser/$USER_NICKNAME/g" `
           if [ "$ectype" = "SNI" ]; then
@@ -610,19 +602,17 @@ ssl_crl_ssl()
   # Cert number $UNREVOKED_CERT_GRP_1 was not revoked
   CRL_GROUP_BEGIN=$CRL_GRP_1_BEGIN
   CRL_GROUP_RANGE=$CRL_GRP_1_RANGE
   UNREVOKED_CERT=$UNREVOKED_CERT_GRP_1
 
   ignore_blank_lines ${SSLAUTH} | \
   while read ectype value sparam cparam testname
   do
-    if [ "$ectype" = "ECC" ] ; then
-        echo "$SCRIPTNAME: skipping $testname (ECC only)"
-    elif [ "$ectype" = "SNI" ]; then
+    if [ "$ectype" = "SNI" ]; then
         continue
     else
 	servarg=`echo $sparam | awk '{r=split($0,a,"-r") - 1;print r;}'`
 	pwd=`echo $cparam | grep nss`
 	user=`echo $cparam | grep TestUser`
 	_cparam=$cparam
 	case $servarg in
 	    1) if [ -z "$pwd" -o -z "$user" ]; then
@@ -724,53 +714,49 @@ ssl_policy()
 
   start_selfserv # Launch the server
 
   ignore_blank_lines ${SSLPOLICY} | \
   while read value ectype testmax param policy testname
   do
       VMIN="ssl3"
 
-      if [ "$ectype" = "ECC" ] ; then
-          echo "$SCRIPTNAME: skipping  $testname (ECC only)"
-      else
-          echo "$SCRIPTNAME: running $testname ----------------------------"
-          VMAX="ssl3"
-          if [ "$testmax" = "TLS10" ]; then
-              VMAX="tls1.0"
-          fi
-          if [ "$testmax" = "TLS11" ]; then
-              VMAX="tls1.1"
-          fi
-          if [ "$testmax" = "TLS12" ]; then
-              VMAX="tls1.2"
-          fi
+      echo "$SCRIPTNAME: running $testname ----------------------------"
+      VMAX="ssl3"
+      if [ "$testmax" = "TLS10" ]; then
+          VMAX="tls1.0"
+      fi
+      if [ "$testmax" = "TLS11" ]; then
+          VMAX="tls1.1"
+      fi
+      if [ "$testmax" = "TLS12" ]; then
+          VMAX="tls1.2"
+      fi
 
-          # load the policy
-          policy=`echo ${policy} | sed -e 's;_; ;g'`
-          setup_policy "$policy" ${P_R_CLIENTDIR}
+      # load the policy
+      policy=`echo ${policy} | sed -e 's;_; ;g'`
+      setup_policy "$policy" ${P_R_CLIENTDIR}
 
-          echo "tstclnt -4 -p ${PORT} -h ${HOSTADDR} -c ${param} -V ${VMIN}:${VMAX} ${CLIENT_OPTIONS} \\"
-          echo "        -f -d ${P_R_CLIENTDIR} $verbose -w nss < ${REQUEST_FILE}"
+      echo "tstclnt -4 -p ${PORT} -h ${HOSTADDR} -c ${param} -V ${VMIN}:${VMAX} ${CLIENT_OPTIONS} \\"
+      echo "        -f -d ${P_R_CLIENTDIR} $verbose -w nss < ${REQUEST_FILE}"
 
-          rm ${TMP}/$HOST.tmp.$$ 2>/dev/null
-          ${PROFTOOL} ${BINDIR}/tstclnt -4 -p ${PORT} -h ${HOSTADDR} -c ${param} -V ${VMIN}:${VMAX} ${CLIENT_OPTIONS} -f \
-                  -d ${P_R_CLIENTDIR} $verbose -w nss < ${REQUEST_FILE} \
-                  >${TMP}/$HOST.tmp.$$  2>&1
-          ret=$?
-          cat ${TMP}/$HOST.tmp.$$
-          rm ${TMP}/$HOST.tmp.$$ 2>/dev/null
+      rm ${TMP}/$HOST.tmp.$$ 2>/dev/null
+      ${PROFTOOL} ${BINDIR}/tstclnt -4 -p ${PORT} -h ${HOSTADDR} -c ${param} -V ${VMIN}:${VMAX} ${CLIENT_OPTIONS} -f \
+              -d ${P_R_CLIENTDIR} $verbose -w nss < ${REQUEST_FILE} \
+              >${TMP}/$HOST.tmp.$$  2>&1
+      ret=$?
+      cat ${TMP}/$HOST.tmp.$$
+      rm ${TMP}/$HOST.tmp.$$ 2>/dev/null
 
-          #workaround for bug #402058
-          [ $ret -ne 0 ] && ret=1
-          [ ${value} -ne 0 ] && value=1
+      #workaround for bug #402058
+      [ $ret -ne 0 ] && ret=1
+      [ ${value} -ne 0 ] && value=1
 
-          html_msg $ret ${value} "${testname}" \
-                   "produced a returncode of $ret, expected is ${value}"
-      fi
+      html_msg $ret ${value} "${testname}" \
+               "produced a returncode of $ret, expected is ${value}"
   done
   cp ${P_R_CLIENTDIR}/pkcs11.txt.sav ${P_R_CLIENTDIR}/pkcs11.txt
 
   kill_selfserv
   html "</TABLE><BR>"
 }
 
 list_enabled_suites()
@@ -999,19 +985,17 @@ ssl_crl_cache()
   while [ $? -eq 0 -a -f ${SSLAUTH_TMP} ]
     do
     sparam=$SERV_ARG
     start_selfserv
     exec < ${SSLAUTH_TMP}
     while read ectype value sparam cparam testname
       do
       [ "$ectype" = "" ] && continue
-      if [ "$ectype" = "ECC" ] ; then
-        echo "$SCRIPTNAME: skipping  $testname (ECC only)"
-      elif [ "$ectype" = "SNI" ]; then
+      if [ "$ectype" = "SNI" ]; then
           continue
       else
         servarg=`echo $sparam | awk '{r=split($0,a,"-r") - 1;print r;}'`
         pwd=`echo $cparam | grep nss`
         user=`echo $cparam | grep TestUser`
         _cparam=$cparam
         case $servarg in
             1) if [ -z "$pwd" -o -z "$user" ]; then
--- a/services/crypto/tests/unit/test_jwcrypto.js
+++ b/services/crypto/tests/unit/test_jwcrypto.js
@@ -266,9 +266,9 @@ var TESTS = [
   test_get_assertion,
   test_get_assertion_with_offset,
   test_assertion_lifetime,
   test_audience_encoding_bug972582,
 ];
 
 TESTS = TESTS.concat([test_rsa, test_dsa]);
 
-TESTS.forEach(add_test);
+TESTS.forEach(f => add_test(f));
--- a/services/sync/tests/unit/test_addons_store.js
+++ b/services/sync/tests/unit/test_addons_store.js
@@ -39,16 +39,36 @@ loadSystemAddon();
 awaitPromise(overrideBuiltIns({ "system": [SYSTEM_ADDON_ID] }));
 startupManager();
 
 let engine;
 let tracker;
 let store;
 let reconciler;
 
+const proxyService = Cc["@mozilla.org/network/protocol-proxy-service;1"]
+  .getService(Ci.nsIProtocolProxyService);
+
+const proxyFilter = {
+  proxyInfo: proxyService.newProxyInfo("http", "localhost", HTTP_PORT, 0, 4096, null),
+
+  applyFilter(service, channel, defaultProxyInfo, callback) {
+    if (channel.URI.host === "example.com") {
+      callback.onProxyFilterResult(this.proxyInfo);
+    } else {
+      callback.onProxyFilterResult(defaultProxyInfo);
+    }
+  },
+};
+
+proxyService.registerChannelFilter(proxyFilter, 0);
+registerCleanupFunction(() => {
+  proxyService.unregisterChannelFilter(proxyFilter);
+});
+
 /**
  * Create a AddonsRec for this application with the fields specified.
  *
  * @param  id       Sync GUID of record
  * @param  addonId  ID of add-on
  * @param  enabled  Boolean whether record is enabled
  * @param  deleted  Boolean whether record was deleted
  */
rename from services/sync/tps/extensions/tps/components/tps-cmdline.js
rename to services/sync/tps/extensions/tps/bootstrap.js
--- a/services/sync/tps/extensions/tps/components/tps-cmdline.js
+++ b/services/sync/tps/extensions/tps/bootstrap.js
@@ -1,40 +1,52 @@
 /* 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/. */
 
-const TPS_ID                         = "tps@mozilla.org";
-const TPS_CMDLINE_CONTRACTID         = "@mozilla.org/commandlinehandler/general-startup;1?type=tps";
-const TPS_CMDLINE_CLSID              = Components.ID("{4e5bd3f0-41d3-11df-9879-0800200c9a66}");
-const CATMAN_CONTRACTID              = "@mozilla.org/categorymanager;1";
-const nsISupports                    = Ci.nsISupports;
-
-const nsICategoryManager             = Ci.nsICategoryManager;
-const nsICmdLineHandler              = Ci.nsICmdLineHandler;
-const nsICommandLine                 = Ci.nsICommandLine;
-const nsICommandLineHandler          = Ci.nsICommandLineHandler;
-const nsIComponentRegistrar          = Ci.nsIComponentRegistrar;
-const nsISupportsString              = Ci.nsISupportsString;
-const nsIWindowWatcher               = Ci.nsIWindowWatcher;
 
 ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
 ChromeUtils.import("resource://gre/modules/Services.jsm");
 ChromeUtils.import("resource://gre/modules/osfile.jsm");
 
-function TPSCmdLineHandler() {}
+XPCOMUtils.defineLazyServiceGetter(this, "categoryManager",
+                                   "@mozilla.org/categorymanager;1",
+                                   "nsICategoryManager");
+
+const Cm = Components.manager.QueryInterface(Ci.nsIComponentRegistrar);
+
+const CATMAN_CONTRACTID         = "@mozilla.org/categorymanager;1";
+
+const CATEGORY_NAME = "command-line-handler";
+const CATEGORY_ENTRY = "m-tps";
+
+function TPSCmdLine() {}
 
-TPSCmdLineHandler.prototype = {
-  classDescription: "TPSCmdLineHandler",
-  classID: TPS_CMDLINE_CLSID,
-  contractID: TPS_CMDLINE_CONTRACTID,
+TPSCmdLine.prototype = {
+  factory: XPCOMUtils._getFactory(TPSCmdLine),
+  classDescription: "TPSCmdLine",
+  classID: Components.ID("{4e5bd3f0-41d3-11df-9879-0800200c9a66}"),
+  contractID: "@mozilla.org/commandlinehandler/general-startup;1?type=tps",
+
+  QueryInterface:   XPCOMUtils.generateQI([Ci.nsICommandLineHandler]),
 
-  QueryInterface: XPCOMUtils.generateQI([nsISupports,
-                                         nsICommandLineHandler,
-                                         nsICmdLineHandler]),   /* nsISupports */
+  register() {
+    Cm.registerFactory(this.classID, this.classDescription,
+                       this.contractID, this.factory);
+
+    categoryManager.addCategoryEntry(CATEGORY_NAME, CATEGORY_ENTRY,
+                                     this.contractID, false, true);
+  },
+
+  unregister() {
+    categoryManager.deleteCategoryEntry(CATEGORY_NAME, CATEGORY_ENTRY,
+                                        this.contractID, false);
+
+    Cm.unregisterFactory(this.classID, this.factory);
+  },
 
   /* nsICmdLineHandler */
   commandLineArgument: "-tps",
   prefNameForStartup: "general.startup.tps",
   helpText: "Run TPS tests with the given test file.",
   handlesArgs: true,
   defaultArgs: "",
   openWindowWithArgs: true,
@@ -70,78 +82,18 @@ TPSCmdLineHandler.prototype = {
   },
 
   helpInfo: "  --tps <file>              Run TPS tests with the given test file.\n" +
             "  --tpsphase <phase>        Run the specified phase in the TPS test.\n" +
             "  --tpslogfile <file>       Logfile for TPS output.\n" +
             "  --ignore-unused-engines   Don't load engines not used in tests.\n",
 };
 
-
-var TPSCmdLineFactory = {
-  createInstance(outer, iid) {
-    if (outer != null) {
-      throw new Error(Cr.NS_ERROR_NO_AGGREGATION);
-    }
-
-    return new TPSCmdLineHandler().QueryInterface(iid);
-  }
-};
-
-
-var TPSCmdLineModule = {
-  registerSelf(compMgr, fileSpec, location, type) {
-    compMgr = compMgr.QueryInterface(nsIComponentRegistrar);
-
-    compMgr.registerFactoryLocation(TPS_CMDLINE_CLSID,
-                                    "TPS CommandLine Service",
-                                    TPS_CMDLINE_CONTRACTID,
-                                    fileSpec,
-                                    location,
-                                    type);
-
-    var catman = Cc[CATMAN_CONTRACTID].getService(nsICategoryManager);
-    catman.addCategoryEntry("command-line-argument-handlers",
-                            "TPS command line handler",
-                            TPS_CMDLINE_CONTRACTID, true, true);
-    catman.addCategoryEntry("command-line-handler",
-                            "m-tps",
-                            TPS_CMDLINE_CONTRACTID, true, true);
-  },
-
-  unregisterSelf(compMgr, fileSpec, location) {
-    compMgr = compMgr.QueryInterface(nsIComponentRegistrar);
+function startup(data, reason) {
+  TPSCmdLine.prototype.register();
+}
 
-    compMgr.unregisterFactoryLocation(TPS_CMDLINE_CLSID, fileSpec);
-    let catman = Cc[CATMAN_CONTRACTID].getService(nsICategoryManager);
-    catman.deleteCategoryEntry("command-line-argument-handlers",
-                               "TPS command line handler", true);
-    catman.deleteCategoryEntry("command-line-handler",
-                               "m-tps", true);
-  },
-
-  getClassObject(compMgr, cid, iid) {
-    if (cid.equals(TPS_CMDLINE_CLSID)) {
-      return TPSCmdLineFactory;
-    }
-
-    if (!iid.equals(Ci.nsIFactory)) {
-      throw new Error(Cr.NS_ERROR_NOT_IMPLEMENTED);
-    }
+function shutdown(data, reason) {
+  TPSCmdLine.prototype.unregister();
+}
 
-    throw new Error(Cr.NS_ERROR_NO_INTERFACE);
-  },
-
-  canUnload(compMgr) {
-    return true;
-  }
-};
-
-/**
-* XPCOMUtils.generateNSGetFactory was introduced in Mozilla 2 (Firefox 4).
-* XPCOMUtils.generateNSGetModule is for Mozilla 1.9.2 (Firefox 3.6).
-*/
-if (XPCOMUtils.generateNSGetFactory)
-    var NSGetFactory = XPCOMUtils.generateNSGetFactory([TPSCmdLineHandler]);
-
-function NSGetModule(compMgr, fileSpec) {
-  return TPSCmdLineModule;
-}
+function install(data, reason) {}
+function uninstall(data, reason) {}
--- a/services/sync/tps/extensions/tps/chrome.manifest
+++ b/services/sync/tps/extensions/tps/chrome.manifest
@@ -1,5 +1,1 @@
 resource tps resource/
-
-component {4e5bd3f0-41d3-11df-9879-0800200c9a66} components/tps-cmdline.js
-contract @mozilla.org/commandlinehandler/general-startup;1?type=tps {4e5bd3f0-41d3-11df-9879-0800200c9a66}
-category command-line-handler m-tps @mozilla.org/commandlinehandler/general-startup;1?type=tps
--- a/services/sync/tps/extensions/tps/install.rdf
+++ b/services/sync/tps/extensions/tps/install.rdf
@@ -3,16 +3,17 @@
    - License, v. 2.0. If a copy of the MPL was not distributed with this
    - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
 
 <RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
      xmlns:em="http://www.mozilla.org/2004/em-rdf#">
   <Description about="urn:mozilla:install-manifest">
     <em:id>tps@mozilla.org</em:id>
     <em:version>0.5</em:version>
+    <em:bootstrap>true</em:bootstrap>
 
     <em:targetApplication>
       <!-- Firefox -->
       <Description>
         <em:id>{ec8030f7-c20a-464f-9b0e-13a3a9e97384}</em:id>
         <em:minVersion>24.0.*</em:minVersion>
         <em:maxVersion>31.0.*</em:maxVersion>
       </Description>
--- a/testing/mochitest/bootstrap.js
+++ b/testing/mochitest/bootstrap.js
@@ -1,15 +1,83 @@
 /* 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/. */
 
+ChromeUtils.import("resource://gre/modules/AppConstants.jsm");
+ChromeUtils.import("resource://gre/modules/NetUtil.jsm");
 ChromeUtils.import("resource://gre/modules/Services.jsm");
 ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
 
+/////// Android ///////
+
+Cu.importGlobalProperties(["TextDecoder"]);
+
+class DefaultMap extends Map {
+  constructor(defaultConstructor = undefined, init = undefined) {
+    super(init);
+    if (defaultConstructor) {
+      this.defaultConstructor = defaultConstructor;
+    }
+  }
+
+  get(key) {
+    let value = super.get(key);
+    if (value === undefined && !this.has(key)) {
+      value = this.defaultConstructor(key);
+      this.set(key, value);
+    }
+    return value;
+  }
+}
+
+const windowTracker = {
+  init() {
+    Services.obs.addObserver(this, "chrome-document-global-created");
+  },
+
+  overlays: new DefaultMap(() => new Set()),
+
+  async observe(window, topic, data) {
+    if (topic === "chrome-document-global-created") {
+      await new Promise(resolve =>
+        window.addEventListener("DOMContentLoaded", resolve, {once: true}));
+
+      let {document} = window;
+      let {documentURI} = document;
+
+      if (this.overlays.has(documentURI)) {
+        for (let overlay of this.overlays.get(documentURI)) {
+          document.loadOverlay(overlay, null);
+        }
+      }
+    }
+  },
+};
+
+function readSync(uri) {
+  let channel = NetUtil.newChannel({uri, loadUsingSystemPrincipal: true});
+  let buffer = NetUtil.readInputStream(channel.open2());
+  return new TextDecoder().decode(buffer);
+}
+
+function androidStartup(data, reason) {
+  windowTracker.init();
+
+  for (let line of readSync(data.resourceURI.resolve("chrome.manifest")).split("\n")) {
+    let [directive, ...args] = line.trim().split(/\s+/);
+    if (directive === "overlay") {
+      let [url, overlay] = args;
+      windowTracker.overlays.get(url).add(overlay);
+    }
+  }
+}
+
+/////// Desktop ///////
+
 var WindowListener = {
   // browser-test.js is only loaded into the first window. Setup that
   // needs to happen in all navigator:browser windows should go here.
   setupWindow: function(win) {
     win.nativeConsole = win.console;
     ChromeUtils.defineModuleGetter(win, "console",
       "resource://gre/modules/Console.jsm");
   },
@@ -49,26 +117,33 @@ function loadMochitest(e) {
   Services.wm.addListener(WindowListener);
 
   Services.scriptloader.loadSubScript("chrome://mochikit/content/chrome-harness.js", win);
   Services.scriptloader.loadSubScript("chrome://mochikit/content/mochitest-e10s-utils.js", win);
   Services.scriptloader.loadSubScript("chrome://mochikit/content/browser-test.js", win);
 }
 
 function startup(data, reason) {
-  let win = Services.wm.getMostRecentWindow("navigator:browser");
-  // wait for event fired from start_desktop.js containing the
-  // suite and url to load
-  win.addEventListener('mochitest-load', loadMochitest);
+  if (AppConstants.platform == "android") {
+    androidStartup(data, reason);
+  } else {
+    let win = Services.wm.getMostRecentWindow("navigator:browser");
+    // wait for event fired from start_desktop.js containing the
+    // suite and url to load
+    win.addEventListener('mochitest-load', loadMochitest);
+  }
 }
 
 function shutdown(data, reason) {
-  let windows = Services.wm.getEnumerator("navigator:browser");
-  while (windows.hasMoreElements()) {
-    let win = windows.getNext().QueryInterface(Ci.nsIDOMWindow);
-    WindowListener.tearDownWindow(win);
+  if (AppConstants.platform != "android") {
+    let windows = Services.wm.getEnumerator("navigator:browser");
+    while (windows.hasMoreElements()) {
+      let win = windows.getNext().QueryInterface(Ci.nsIDOMWindow);
+      WindowListener.tearDownWindow(win);
+    }
+
+    Services.wm.removeListener(WindowListener);
   }
-
-  Services.wm.removeListener(WindowListener);
 }
 
 function install(data, reason) {}
 function uninstall(data, reason) {}
+
--- a/testing/mochitest/install.rdf
+++ b/testing/mochitest/install.rdf
@@ -1,18 +1,16 @@
 <?xml version="1.0"?>
 
 <RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
      xmlns:em="http://www.mozilla.org/2004/em-rdf#">
   <Description about="urn:mozilla:install-manifest">
     <em:id>mochikit@mozilla.org</em:id>
     <em:version>1.0</em:version>
-#ifdef MOCHITEST_BOOTSTRAP
     <em:bootstrap>true</em:bootstrap>
-#endif
     <em:targetApplication>
       <Description>
         <em:id>toolkit@mozilla.org</em:id>
 #expand        <em:minVersion>__MOZILLA_VERSION_U__</em:minVersion>
                <!-- Set to * so toolkit/mozapps/update/chrome tests pass. -->
                <em:maxVersion>*</em:maxVersion>
       </Description>
     </em:targetApplication>
--- a/testing/mochitest/moz.build
+++ b/testing/mochitest/moz.build
@@ -14,19 +14,17 @@ DIRS += [
 XPI_NAME = 'mochijar'
 
 JAR_MANIFESTS += ['jar.mn']
 
 USE_EXTENSION_MANIFEST = True
 
 FINAL_TARGET_PP_FILES += ['install.rdf']
 
-if CONFIG['OS_TARGET'] != 'Android':
-    DEFINES['MOCHITEST_BOOTSTRAP'] = True
-    FINAL_TARGET_FILES += ['bootstrap.js']
+FINAL_TARGET_FILES += ['bootstrap.js']
 
 MOCHITEST_MANIFESTS += [
     'tests/MochiKit-1.4.2/tests/mochitest.ini',
 ]
 MOCHITEST_CHROME_MANIFESTS += ['chrome/chrome.ini']
 
 GENERATED_FILES += [
     'automation.py',
--- a/testing/mochitest/runrobocop.py
+++ b/testing/mochitest/runrobocop.py
@@ -221,17 +221,16 @@ class RobocopTestRunner(MochitestDesktop
         self.options.extraPrefs.append('browser.snippets.enabled=false')
         self.options.extraPrefs.append('extensions.autoupdate.enabled=false')
 
         # Override the telemetry init delay for integration testing.
         self.options.extraPrefs.append('toolkit.telemetry.initDelay=1')
 
         self.options.extensionsToExclude.extend([
             'mochikit@mozilla.org',
-            'worker-test@mozilla.org.xpi',
             'workerbootstrap-test@mozilla.org.xpi',
             'indexedDB-test@mozilla.org.xpi',
         ])
 
         manifest = MochitestDesktop.buildProfile(self, self.options)
         self.localProfile = self.options.profilePath
         self.log.debug("Profile created at %s" % self.localProfile)
         # some files are not needed for robocop; save time by not pushing
new file mode 100644
--- /dev/null
+++ b/testing/talos/talos/bootstrap.js
@@ -0,0 +1,79 @@
+"use strict";
+
+// PLEASE NOTE:
+//
+// The canonical version of this file lives in testing/talos/talos, and
+// is duplicated in a number of test add-ons in directories below it.
+// Please do not update one withput updating all.
+
+// Reads the chrome.manifest from a legacy non-restartless extension and loads
+// its overlays into the appropriate top-level windows.
+
+ChromeUtils.import("resource://gre/modules/NetUtil.jsm");
+ChromeUtils.import("resource://gre/modules/Services.jsm");
+
+Cu.importGlobalProperties(["TextDecoder"]);
+
+class DefaultMap extends Map {
+  constructor(defaultConstructor = undefined, init = undefined) {
+    super(init);
+    if (defaultConstructor) {
+      this.defaultConstructor = defaultConstructor;
+    }
+  }
+
+  get(key) {
+    let value = super.get(key);
+    if (value === undefined && !this.has(key)) {
+      value = this.defaultConstructor(key);
+      this.set(key, value);
+    }
+    return value;
+  }
+}
+
+const windowTracker = {
+  init() {
+    Services.ww.registerNotification(this);
+  },
+
+  overlays: new DefaultMap(() => new Set()),
+
+  async observe(window, topic, data) {
+    if (topic === "domwindowopened") {
+      await new Promise(resolve =>
+        window.addEventListener("DOMWindowCreated", resolve, {once: true}));
+
+      let {document} = window;
+      let {documentURI} = document;
+
+      if (this.overlays.has(documentURI)) {
+        for (let overlay of this.overlays.get(documentURI)) {
+          document.loadOverlay(overlay, null);
+        }
+      }
+    }
+  },
+};
+
+function readSync(uri) {
+  let channel = NetUtil.newChannel({uri, loadUsingSystemPrincipal: true});
+  let buffer = NetUtil.readInputStream(channel.open2());
+  return new TextDecoder().decode(buffer);
+}
+
+function startup(data, reason) {
+  windowTracker.init();
+
+  for (let line of readSync(data.resourceURI.resolve("chrome.manifest")).split("\n")) {
+    let [directive, ...args] = line.trim().split(/\s+/);
+    if (directive === "overlay") {
+      let [url, overlay] = args;
+      windowTracker.overlays.get(url).add(overlay);
+    }
+  }
+}
+
+function shutdown(data, reason) {}
+function install(data, reason) {}
+function uninstall(data, reason) {}
--- a/testing/talos/talos/generate-tart-xpi.html
+++ b/testing/talos/talos/generate-tart-xpi.html
@@ -1,24 +1,25 @@
-<html>
+<html>
  <!--
  * 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/.
  -->
 <head>
   <meta charset="UTF-8"/>
   <title>TART addon xpi generator</title>
   <script src="scripts/jszip.min.js"></script>
   <script src="scripts/xpigen.js"></script>
 
   <script>
     /* import-globals-from scripts/xpigen.js */
     var base = "tests/tart/addon/";
     var files = [
+      "bootstrap.js",
       "chrome.manifest",
       "install.rdf",
       "content/framescript.js",
       "content/tart.overlay.xul",
       "content/tart.html",
       "content/tart.ico",
       "content/tart.js",
       "content/blank.icon.html",
--- a/testing/talos/talos/generate-tresize-xpi.html
+++ b/testing/talos/talos/generate-tresize-xpi.html
@@ -9,16 +9,17 @@
   <title>Tresize addon xpi generator</title>
   <script src="scripts/jszip.min.js"></script>
   <script src="scripts/xpigen.js"></script>
 
   <script>
     /* import-globals-from scripts/xpigen.js */
     var base = "startup_test/tresize/addon/";
     var files = [
+      "bootstrap.js",
       "chrome.manifest",
       "install.rdf",
       "content/framescript.js",
       "content/Profiler.js",
       "content/tresize.overlay.xul",
       "content/tresize.js",
       "content/tresize-test.html"
     ];
--- a/testing/talos/talos/mtio-whitelist.json
+++ b/testing/talos/talos/mtio-whitelist.json
@@ -118,16 +118,17 @@
  "{profile}\\extensions\\pageloader@mozilla.org": {},
  "{profile}\\extensions\\pageloader@mozilla.org\\chrome\\profiler.js": {},
  "{profile}\\extensions\\pageloader@mozilla.org\\chrome\\talos-content.js": {},
  "{profile}\\extensions\\pageloader@mozilla.org\\chrome\\tscroll.js": {},
  "{profile}\\extensions\\pageloader@mozilla.org\\install.rdf": {},
  "{profile}\\extensions\\pageloader@mozilla.org\\plugins": {},
  "{profile}\\extensions\\pageloader@mozilla.org\\searchplugins": {},
  "{profile}\\extensions\\talos-powers@mozilla.org": {},
+ "{profile}\\extensions\\talos-powers@mozilla.org\\bootstrap.js": {},
  "{profile}\\extensions\\talos-powers@mozilla.org\\chrome.manifest": {},
  "{profile}\\extensions\\talos-powers@mozilla.org\\chrome\\talos-powers-content.js": {},
  "{profile}\\extensions\\talos-powers@mozilla.org\\components\\talospowersservice.js": {},
  "{profile}\\extensions\\talos-powers@mozilla.org\\install.rdf": {},
  "{profile}\\extensions\\talos-powers@mozilla.org\\plugins": {},
  "{profile}\\extensions\\talos-powers@mozilla.org\\searchplugins": {},
  "{profile}\\favicons.sqlite": {},
  "{profile}\\favicons.sqlite-journal": {},
rename from testing/talos/talos/pageloader/components/tp-cmdline.js
rename to testing/talos/talos/pageloader/bootstrap.js
--- a/testing/talos/talos/pageloader/components/tp-cmdline.js
+++ b/testing/talos/talos/pageloader/bootstrap.js
@@ -35,42 +35,52 @@
  * the provisions above, a recipient may use your version of this file under
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
 
 // This only implements nsICommandLineHandler, since it needs
 // to handle multiple arguments.
 
-const TP_CMDLINE_CONTRACTID     = "@mozilla.org/commandlinehandler/general-startup;1?type=tp";
-const TP_CMDLINE_CLSID          = Components.ID("{8AF052F5-8EFE-4359-8266-E16498A82E8B}");
-const CATMAN_CONTRACTID         = "@mozilla.org/categorymanager;1";
-const nsISupports               = Ci.nsISupports;
+ChromeUtils.import("resource://gre/modules/Services.jsm");
+ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
+
+XPCOMUtils.defineLazyServiceGetter(this, "categoryManager",
+                                   "@mozilla.org/categorymanager;1",
+                                   "nsICategoryManager");
 
-const nsICategoryManager        = Ci.nsICategoryManager;
-const nsICommandLine            = Ci.nsICommandLine;
-const nsICommandLineHandler     = Ci.nsICommandLineHandler;
-const nsIComponentRegistrar     = Ci.nsIComponentRegistrar;
-const nsISupportsString         = Ci.nsISupportsString;
-const nsIWindowWatcher          = Ci.nsIWindowWatcher;
+const Cm = Components.manager.QueryInterface(Ci.nsIComponentRegistrar);
+
+const CATMAN_CONTRACTID         = "@mozilla.org/categorymanager;1";
+
+const CATEGORY_NAME = "command-line-handler";
+const CATEGORY_ENTRY = "m-tp";
 
-ChromeUtils.import("resource://gre/modules/Services.jsm");
-
-function PageLoaderCmdLineHandler() {}
-PageLoaderCmdLineHandler.prototype =
+function PageLoaderCmdLine() {}
+PageLoaderCmdLine.prototype =
 {
-  /* nsISupports */
-  QueryInterface: function handler_QI(iid) {
-    if (iid.equals(nsISupports))
-      return this;
+  factory: XPCOMUtils._getFactory(PageLoaderCmdLine),
+  classDescription: "Loads pages. Tests them.",
+  classID:          Components.ID("{8AF052F5-8EFE-4359-8266-E16498A82E8B}"),
+  contractID:       "@mozilla.org/commandlinehandler/general-startup;1?type=tp",
+  QueryInterface:   XPCOMUtils.generateQI([Ci.nsICommandLineHandler]),
+
+  register() {
+    Cm.registerFactory(this.classID, this.classDescription,
+                       this.contractID, this.factory);
 
-    if (nsICommandLineHandler && iid.equals(nsICommandLineHandler))
-      return this;
+    categoryManager.addCategoryEntry(CATEGORY_NAME, CATEGORY_ENTRY,
+                                     this.contractID, false, true);
+  },
 
-    throw Cr.NS_ERROR_NO_INTERFACE;
+  unregister() {
+    categoryManager.deleteCategoryEntry(CATEGORY_NAME, CATEGORY_ENTRY,
+                                        this.contractID, false);
+
+    Cm.unregisterFactory(this.classID, this.factory);
   },
 
   /* nsICommandLineHandler */
   handle: function handler_handle(cmdLine) {
     var args = {};
 
     var tpmanifest = Services.prefs.getCharPref("talos.tpmanifest", null);
     if (tpmanifest == null) {
@@ -88,67 +98,18 @@ PageLoaderCmdLineHandler.prototype =
     Services.ww.openWindow(null, chromeURL, "_blank",
                             "chrome,dialog=no,all", args);
 
     // Don't pass command line to the default app processor
     cmdLine.preventDefault = true;
   },
 };
 
-
-var PageLoaderCmdLineFactory =
-{
-  createInstance(outer, iid) {
-    if (outer != null) {
-      throw Cr.NS_ERROR_NO_AGGREGATION;
-    }
-
-    return new PageLoaderCmdLineHandler().QueryInterface(iid);
-  }
-};
-
-function NSGetFactory(cid) {
-  if (!cid.equals(TP_CMDLINE_CLSID))
-    throw Cr.NS_ERROR_NOT_IMPLEMENTED;
-
-  return PageLoaderCmdLineFactory;
+function startup(data, reason) {
+  PageLoaderCmdLine.prototype.register();
 }
 
-var PageLoaderCmdLineModule =
-{
-  registerSelf(compMgr, fileSpec, location, type) {
-    compMgr = compMgr.QueryInterface(nsIComponentRegistrar);
-
-    compMgr.registerFactoryLocation(TP_CMDLINE_CLSID,
-                                    "PageLoader CommandLine Service",
-                                    TP_CMDLINE_CONTRACTID,
-                                    fileSpec,
-                                    location,
-                                    type);
-
-    var catman = Cc[CATMAN_CONTRACTID].getService(nsICategoryManager);
-    catman.addCategoryEntry("command-line-handler",
-                            "m-tp",
-                            TP_CMDLINE_CONTRACTID, true, true);
-  },
+function shutdown(data, reason) {
+  PageLoaderCmdLine.prototype.unregister();
+}
 
-  unregisterSelf(compMgr, fileSpec, location) {
-    compMgr = compMgr.QueryInterface(nsIComponentRegistrar);
-
-    compMgr.unregisterFactoryLocation(TP_CMDLINE_CLSID, fileSpec);
-    var catman = Cc[CATMAN_CONTRACTID].getService(nsICategoryManager);
-    catman.deleteCategoryEntry("command-line-handler",
-                               "m-tp", true);
-  },
-
-  getClassObject(compMgr, cid, iid) {
-    return NSGetFactory(cid);
-  },
-
-  canUnload(compMgr) {
-    return true;
-  }
-};
-
-
-function NSGetModule(compMgr, fileSpec) {
-  return PageLoaderCmdLineModule;
-}
+function install(data, reason) {}
+function uninstall(data, reason) {}
--- a/testing/talos/talos/pageloader/chrome.manifest
+++ b/testing/talos/talos/pageloader/chrome.manifest
@@ -1,4 +1,1 @@
 content pageloader chrome/
-component {8AF052F5-8EFE-4359-8266-E16498A82E8B} components/tp-cmdline.js
-contract @mozilla.org/commandlinehandler/general-startup;1?type=tp {8AF052F5-8EFE-4359-8266-E16498A82E8B}
-category command-line-handler m-tp @mozilla.org/commandlinehandler/general-startup;1?type=tp
--- a/testing/talos/talos/pageloader/install.rdf
+++ b/testing/talos/talos/pageloader/install.rdf
@@ -1,15 +1,16 @@
 <?xml version="1.0"?>
 
 <RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
      xmlns:em="http://www.mozilla.org/2004/em-rdf#">
   <Description about="urn:mozilla:install-manifest">
     <em:id>pageloader@mozilla.org</em:id>
     <em:version>1.0.32</em:version>
+    <em:bootstrap>true</em:bootstrap>
     <em:targetApplication>
       <Description>
         <em:id>{ec8030f7-c20a-464f-9b0e-13a3a9e97384}</em:id>
         <em:minVersion>44.0</em:minVersion>
         <em:maxVersion>*</em:maxVersion>
       </Description>
     </em:targetApplication>
     <!-- Front End MetaData -->
rename from testing/talos/talos/startup_test/sessionrestore/addon/SessionRestoreTalosTest.js
rename to testing/talos/talos/startup_test/sessionrestore/addon/bootstrap.js
--- a/testing/talos/talos/startup_test/sessionrestore/addon/SessionRestoreTalosTest.js
+++ b/testing/talos/talos/startup_test/sessionrestore/addon/bootstrap.js
@@ -11,43 +11,30 @@ ChromeUtils.import("resource://gre/modul
 ChromeUtils.defineModuleGetter(this, "Services",
   "resource://gre/modules/Services.jsm");
 ChromeUtils.defineModuleGetter(this, "setTimeout",
   "resource://gre/modules/Timer.jsm");
 ChromeUtils.defineModuleGetter(this, "StartupPerformance",
   "resource:///modules/sessionstore/StartupPerformance.jsm");
 
 // Observer Service topics.
-const STARTUP_TOPIC = "profile-after-change";
 const WINDOW_READY_TOPIC = "browser-delayed-startup-finished";
 
 // Process Message Manager topics.
 const MSG_REQUEST = "session-restore-test?duration";
 const MSG_PROVIDE = "session-restore-test:duration";
 
-function nsSessionRestoreTalosTest() { }
-
-nsSessionRestoreTalosTest.prototype = {
-  classID: Components.ID("{716346e5-0c45-4aa2-b601-da36f3c74bd8}"),
-
-  _xpcom_factory: XPCOMUtils.generateSingletonFactory(nsSessionRestoreTalosTest),
-
-  // ////////////////////////////////////////////////////////////////////////////
-  // // nsISupports
-
+const sessionRestoreTest = {
   QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver]),
 
   // ////////////////////////////////////////////////////////////////////////////
   // // nsIObserver
 
   observe: function DS_observe(aSubject, aTopic, aData) {
     switch (aTopic) {
-      case STARTUP_TOPIC:
-        this.init();
-        break;
       case StartupPerformance.RESTORED_TOPIC:
         this.onReady(true);
         break;
       case WINDOW_READY_TOPIC:
         Services.obs.removeObserver(this, WINDOW_READY_TOPIC);
         this.onWindow(aSubject);
         break;
       default:
@@ -142,12 +129,16 @@ nsSessionRestoreTalosTest.prototype = {
       let url = new URL(args.queryElementAt(0, Ci.nsISupportsString).data);
       queryString = url.search;
     }
 
     win.gBrowser.addTab("chrome://session-restore-test/content/index.html" + queryString);
   }
 };
 
-// //////////////////////////////////////////////////////////////////////////////
-// // Module
 
-this.NSGetFactory = XPCOMUtils.generateNSGetFactory([nsSessionRestoreTalosTest]);
+function startup(data, reason) {
+  sessionRestoreTest.init();
+}
+
+function shutdown(data, reason) {}
+function install(data, reason) {}
+function uninstall(data, reason) {}
--- a/testing/talos/talos/startup_test/sessionrestore/addon/chrome.manifest
+++ b/testing/talos/talos/startup_test/sessionrestore/addon/chrome.manifest
@@ -1,8 +1,1 @@
-# Register a component to be informed of startup. This component can then register
-# itself to watch sessionstore-windows-restored. Once it has observed
-# sessionstore-windows-restored, it will open the webpage with the harness.
-component {716346e5-0c45-4aa2-b601-da36f3c74bd8} SessionRestoreTalosTest.js
-contract @mozilla.org/talos/session-restore-test;1 {716346e5-0c45-4aa2-b601-da36f3c74bd8}
-category profile-after-change nsSessionRestoreTalosTest @mozilla.org/talos/session-restore-test;1
-
 content session-restore-test content/
--- a/testing/talos/talos/startup_test/sessionrestore/addon/install.rdf
+++ b/testing/talos/talos/startup_test/sessionrestore/addon/install.rdf
@@ -1,14 +1,15 @@
 <?xml version="1.0"?><RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#"     xmlns:em="http://www.mozilla.org/2004/em-rdf#"><Description about="urn:mozilla:install-manifest">
 
 <!-- Required Items -->
 <em:id>session-restore-test-2@mozilla.org</em:id>
 <em:name>Session Restore Startup Performance Test</em:name>
 <em:version>2.0.11</em:version>
+<em:bootstrap>true</em:bootstrap>
 
 <em:targetApplication>
     <Description>
         <em:id>{ec8030f7-c20a-464f-9b0e-13a3a9e97384}</em:id>
         <em:minVersion>1.5</em:minVersion>
         <em:maxVersion>*</em:maxVersion>
     </Description>
 </em:targetApplication>
new file mode 100644
--- /dev/null
+++ b/testing/talos/talos/startup_test/tresize/addon/bootstrap.js
@@ -0,0 +1,79 @@
+"use strict";
+
+// PLEASE NOTE:
+//
+// The canonical version of this file lives in testing/talos/talos, and
+// is duplicated in a number of test add-ons in directories below it.
+// Please do not update one withput updating all.
+
+// Reads the chrome.manifest from a legacy non-restartless extension and loads
+// its overlays into the appropriate top-level windows.
+
+ChromeUtils.import("resource://gre/modules/NetUtil.jsm");
+ChromeUtils.import("resource://gre/modules/Services.jsm");
+
+Cu.importGlobalProperties(["TextDecoder"]);
+
+class DefaultMap extends Map {
+  constructor(defaultConstructor = undefined, init = undefined) {
+    super(init);
+    if (defaultConstructor) {
+      this.defaultConstructor = defaultConstructor;
+    }
+  }
+
+  get(key) {
+    let value = super.get(key);
+    if (value === undefined && !this.has(key)) {
+      value = this.defaultConstructor(key);
+      this.set(key, value);
+    }
+    return value;
+  }
+}
+
+const windowTracker = {
+  init() {
+    Services.ww.registerNotification(this);
+  },
+
+  overlays: new DefaultMap(() => new Set()),
+
+  async observe(window, topic, data) {
+    if (topic === "domwindowopened") {
+      await new Promise(resolve =>
+        window.addEventListener("DOMWindowCreated", resolve, {once: true}));
+
+      let {document} = window;
+      let {documentURI} = document;
+
+      if (this.overlays.has(documentURI)) {
+        for (let overlay of this.overlays.get(documentURI)) {
+          document.loadOverlay(overlay, null);
+        }
+      }
+    }
+  },
+};
+
+function readSync(uri) {
+  let channel = NetUtil.newChannel({uri, loadUsingSystemPrincipal: true});
+  let buffer = NetUtil.readInputStream(channel.open2());
+  return new TextDecoder().decode(buffer);
+}
+
+function startup(data, reason) {
+  windowTracker.init();
+
+  for (let line of readSync(data.resourceURI.resolve("chrome.manifest")).split("\n")) {
+    let [directive, ...args] = line.trim().split(/\s+/);
+    if (directive === "overlay") {
+      let [url, overlay] = args;
+      windowTracker.overlays.get(url).add(overlay);
+    }
+  }
+}
+
+function shutdown(data, reason) {}
+function install(data, reason) {}
+function uninstall(data, reason) {}
--- a/testing/talos/talos/startup_test/tresize/addon/install.rdf
+++ b/testing/talos/talos/startup_test/tresize/addon/install.rdf
@@ -1,14 +1,15 @@
 <?xml version="1.0"?><RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:em="http://www.mozilla.org/2004/em-rdf#"><Description about="urn:mozilla:install-manifest">
 
 <!-- Required Items -->
 <em:id>tresize@mozilla.org</em:id>
 <em:name>TResize - Firefox Window Resize Test</em:name>
 <em:version>1.0.3</em:version>
+<em:bootstrap>true</em:bootstrap>
 
 <em:targetApplication>
     <Description>
         <em:id>{ec8030f7-c20a-464f-9b0e-13a3a9e97384}</em:id>
         <em:minVersion>1.5</em:minVersion>
         <em:maxVersion>*</em:maxVersion>
     </Description>
 </em:targetApplication>
rename from testing/talos/talos/talos-powers/components/TalosPowersService.js
rename to testing/talos/talos/talos-powers/bootstrap.js
--- a/testing/talos/talos/talos-powers/components/TalosPowersService.js
+++ b/testing/talos/talos/talos-powers/bootstrap.js
@@ -3,53 +3,51 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
 ChromeUtils.defineModuleGetter(this, "Services",
   "resource://gre/modules/Services.jsm");
 ChromeUtils.defineModuleGetter(this, "OS",
   "resource://gre/modules/osfile.jsm");
 
+const Cm = Components.manager.QueryInterface(Ci.nsIComponentRegistrar);
+
 const FRAME_SCRIPT = "chrome://talos-powers/content/talos-powers-content.js";
 
 function TalosPowersService() {
   this.wrappedJSObject = this;
+
+  this.init();
 }
 
 TalosPowersService.prototype = {
+  factory: XPCOMUtils._getFactory(TalosPowersService),
   classDescription: "Talos Powers",
   classID: Components.ID("{f5d53443-d58d-4a2f-8df0-98525d4f91ad}"),
   contractID: "@mozilla.org/talos/talos-powers-service;1",
-  QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver]),
+  QueryInterface: XPCOMUtils.generateQI([]),
+
+  register() {
+    Cm.registerFactory(this.classID, this.classDescription,