merge mozilla-central to autoland. r=merge a=merge
authorSebastian Hengst <archaeopteryx@coole-files.de>
Thu, 31 Aug 2017 14:37:58 +0200
changeset 377959 93ed0305b3a3d2966fddb2f9871796d9a4f7ef94
parent 377958 18d3376156f0a0fe044ede1c67cc1cbcc75cad2e (current diff)
parent 377948 fb22415719a9d971a2646fa2d1b74e134ca00c3d (diff)
child 377960 b4ea43ebf3461ba307a3b673f3ac9261506cd2a4
push id32419
push userkwierso@gmail.com
push dateThu, 31 Aug 2017 19:58:11 +0000
treeherdermozilla-central@13d241d08912 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge, merge
milestone57.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
merge mozilla-central to autoland. r=merge a=merge
--- a/accessible/windows/msaa/Platform.cpp
+++ b/accessible/windows/msaa/Platform.cpp
@@ -192,24 +192,25 @@ a11y::IsHandlerRegistered()
 {
   nsresult rv;
   nsCOMPtr<nsIWindowsRegKey> regKey =
     do_CreateInstance("@mozilla.org/windows-registry-key;1", &rv);
   if (NS_FAILED(rv)) {
     return false;
   }
 
+  nsAutoString clsid;
+  GUIDToString(CLSID_AccessibleHandler, clsid);
+
   nsAutoString subKey;
-  subKey.AppendLiteral("CLSID\\");
-  nsAutoString iid;
-  GUIDToString(CLSID_AccessibleHandler, iid);
-  subKey.Append(iid);
-  subKey.AppendLiteral("\\InprocHandler32");
+  subKey.AppendLiteral(u"SOFTWARE\\Classes\\CLSID\\");
+  subKey.Append(clsid);
+  subKey.AppendLiteral(u"\\InprocHandler32");
 
-  rv = regKey->Open(nsIWindowsRegKey::ROOT_KEY_CLASSES_ROOT, subKey,
+  rv = regKey->Open(nsIWindowsRegKey::ROOT_KEY_LOCAL_MACHINE, subKey,
                     nsIWindowsRegKey::ACCESS_READ);
   if (NS_FAILED(rv)) {
     return false;
   }
 
   return true;
 }
 
--- a/browser/config/mozconfigs/linux32/devedition
+++ b/browser/config/mozconfigs/linux32/devedition
@@ -13,10 +13,12 @@ ac_add_options --enable-verify-mar
 # This will overwrite the default of stripping everything and keep the symbol table.
 # This is useful for profiling and debugging and only increases the package size
 # by 2 MBs.
 STRIP_FLAGS="--strip-debug"
 
 ac_add_options --with-branding=browser/branding/aurora
 
 mk_add_options MOZ_PGO=1
+# Enable MOZ_ALLOW_LEGACY_EXTENSIONS
+ac_add_options "MOZ_ALLOW_LEGACY_EXTENSIONS=1"
 
 . "$topsrcdir/build/mozconfig.common.override"
--- a/browser/config/mozconfigs/linux32/l10n-mozconfig-devedition
+++ b/browser/config/mozconfigs/linux32/l10n-mozconfig-devedition
@@ -14,10 +14,12 @@ export MOZILLA_OFFICIAL=1
 # Enable Telemetry
 export MOZ_TELEMETRY_REPORTING=1
 
 ac_add_options --disable-stdcxx-compat
 
 # Don't autoclobber l10n, as this can lead to missing binaries and broken builds
 # Bug 1283438
 mk_add_options AUTOCLOBBER=
+# Enable MOZ_ALLOW_LEGACY_EXTENSIONS
+ac_add_options "MOZ_ALLOW_LEGACY_EXTENSIONS=1"
 
 . "$topsrcdir/build/mozconfig.common.override"
--- a/browser/config/mozconfigs/linux64/devedition
+++ b/browser/config/mozconfigs/linux64/devedition
@@ -13,10 +13,12 @@ ac_add_options --enable-verify-mar
 # This will overwrite the default of stripping everything and keep the symbol table.
 # This is useful for profiling and debugging and only increases the package size
 # by 2 MBs.
 STRIP_FLAGS="--strip-debug"
 
 ac_add_options --with-branding=browser/branding/aurora
 
 mk_add_options MOZ_PGO=1
+# Enable MOZ_ALLOW_LEGACY_EXTENSIONS
+ac_add_options "MOZ_ALLOW_LEGACY_EXTENSIONS=1"
 
 . "$topsrcdir/build/mozconfig.common.override"
--- a/browser/config/mozconfigs/linux64/l10n-mozconfig-devedition
+++ b/browser/config/mozconfigs/linux64/l10n-mozconfig-devedition
@@ -10,9 +10,12 @@ export MOZILLA_OFFICIAL=1
 export MOZ_TELEMETRY_REPORTING=1
 
 ac_add_options --disable-stdcxx-compat
 
 # Don't autoclobber l10n, as this can lead to missing binaries and broken builds
 # Bug 1283438
 mk_add_options AUTOCLOBBER=
 
+# Enable MOZ_ALLOW_LEGACY_EXTENSIONS
+ac_add_options "MOZ_ALLOW_LEGACY_EXTENSIONS=1"
+
 . "$topsrcdir/build/mozconfig.common.override"
--- a/browser/config/mozconfigs/macosx64/devedition
+++ b/browser/config/mozconfigs/macosx64/devedition
@@ -19,9 +19,12 @@ if test `uname -s` != Linux; then
 fi
 
 if test "${MOZ_UPDATE_CHANNEL}" = "nightly"; then
 ac_add_options --with-macbundlename-prefix=Firefox
 fi
 
 ac_add_options --with-branding=browser/branding/aurora
 
+# Enable MOZ_ALLOW_LEGACY_EXTENSIONS
+ac_add_options "MOZ_ALLOW_LEGACY_EXTENSIONS=1"
+
 . "$topsrcdir/build/mozconfig.common.override"
--- a/browser/config/mozconfigs/macosx64/l10n-mozconfig-devedition
+++ b/browser/config/mozconfigs/macosx64/l10n-mozconfig-devedition
@@ -21,9 +21,12 @@ export MOZILLA_OFFICIAL=1
 
 # Enable Telemetry
 export MOZ_TELEMETRY_REPORTING=1
 
 # Don't autoclobber l10n, as this can lead to missing binaries and broken builds
 # Bug 1283438
 mk_add_options AUTOCLOBBER=
 
+# Enable MOZ_ALLOW_LEGACY_EXTENSIONS
+ac_add_options "MOZ_ALLOW_LEGACY_EXTENSIONS=1"
+
 . "$topsrcdir/build/mozconfig.common.override"
--- a/browser/config/mozconfigs/win32/devedition
+++ b/browser/config/mozconfigs/win32/devedition
@@ -10,9 +10,12 @@ fi
 MOZ_REQUIRE_SIGNING=0
 
 ac_add_options --enable-verify-mar
 
 ac_add_options --with-branding=browser/branding/aurora
 
 mk_add_options MOZ_PGO=1
 
+# Enable MOZ_ALLOW_LEGACY_EXTENSIONS
+ac_add_options "MOZ_ALLOW_LEGACY_EXTENSIONS=1"
+
 . "$topsrcdir/build/mozconfig.common.override"
--- a/browser/config/mozconfigs/win32/l10n-mozconfig-devedition
+++ b/browser/config/mozconfigs/win32/l10n-mozconfig-devedition
@@ -9,9 +9,12 @@ export MOZILLA_OFFICIAL=1
 
 # Enable Telemetry
 export MOZ_TELEMETRY_REPORTING=1
 
 # Don't autoclobber l10n, as this can lead to missing binaries and broken builds
 # Bug 1283438
 mk_add_options AUTOCLOBBER=
 
+# Enable MOZ_ALLOW_LEGACY_EXTENSIONS
+ac_add_options "MOZ_ALLOW_LEGACY_EXTENSIONS=1"
+
 . "$topsrcdir/build/mozconfig.common.override"
--- a/browser/config/mozconfigs/win64/devedition
+++ b/browser/config/mozconfigs/win64/devedition
@@ -11,9 +11,12 @@ fi
 MOZ_REQUIRE_SIGNING=0
 
 ac_add_options --enable-verify-mar
 
 ac_add_options --with-branding=browser/branding/aurora
 
 mk_add_options MOZ_PGO=1
 
+# Enable MOZ_ALLOW_LEGACY_EXTENSIONS
+ac_add_options "MOZ_ALLOW_LEGACY_EXTENSIONS=1"
+
 . "$topsrcdir/build/mozconfig.common.override"
--- a/browser/config/mozconfigs/win64/l10n-mozconfig-devedition
+++ b/browser/config/mozconfigs/win64/l10n-mozconfig-devedition
@@ -10,9 +10,12 @@ export MOZILLA_OFFICIAL=1
 
 # Enable Telemetry
 export MOZ_TELEMETRY_REPORTING=1
 
 # Don't autoclobber l10n, as this can lead to missing binaries and broken builds
 # Bug 1283438
 mk_add_options AUTOCLOBBER=
 
+# Enable MOZ_ALLOW_LEGACY_EXTENSIONS
+ac_add_options "MOZ_ALLOW_LEGACY_EXTENSIONS=1"
+
 . "$topsrcdir/build/mozconfig.common.override"
--- a/browser/extensions/onboarding/content/onboarding.css
+++ b/browser/extensions/onboarding/content/onboarding.css
@@ -28,16 +28,17 @@
   position: absolute;
   cursor: pointer;
   top: 34px;
   offset-inline-start: 34px;
   border: none;
   /* Set to none so no grey contrast background in the high-contrast mode */
   background: none;
   /* make sure the icon stay above the activity-stream searchbar */
+  /* We want this always under #onboarding-overlay */
   z-index: 10;
 }
 
 /* Keyboard focus styling */
 #onboarding-overlay-button:-moz-focusring {
   outline: solid 2px rgba(0, 0, 0, 0.1);
   -moz-outline-radius: 5px;
   outline-offset: 5px;
--- a/browser/extensions/onboarding/test/browser/browser_onboarding_accessibility.js
+++ b/browser/extensions/onboarding/test/browser/browser_onboarding_accessibility.js
@@ -25,16 +25,35 @@ add_task(async function test_onboarding_
       "Onboarding button semantically controls an overlay dialog");
     is(button.firstChild.getAttribute("role"), "presentation",
       "Onboarding button icon should have presentation only semantics");
   });
 
   await BrowserTestUtils.removeTab(tab);
 });
 
+add_task(async function test_onboarding_overlay_button_no_activity_steam() {
+  /* https://bugzilla.mozilla.org/show_bug.cgi?id=1393564 */
+  resetOnboardingDefaultState();
+  Preferences.set("browser.newtabpage.activity-stream.enabled", false);
+
+  info("Wait for onboarding overlay loaded");
+  let tab = await openTab(ABOUT_NEWTAB_URL);
+  let browser = tab.linkedBrowser;
+  await promiseOnboardingOverlayLoaded(browser);
+
+  info("Click on overlay button and ensure dialog opens");
+  await BrowserTestUtils.synthesizeMouseAtCenter("#onboarding-overlay-button",
+                                                 {}, browser);
+  await promiseOnboardingOverlayOpened(browser);
+
+  Preferences.reset("browser.newtabpage.activity-stream.enabled");
+  await BrowserTestUtils.removeTab(tab);
+});
+
 add_task(async function test_onboarding_notification_bar() {
   resetOnboardingDefaultState();
   skipMuteNotificationOnFirstSession();
 
   let tab = await openTab(ABOUT_NEWTAB_URL);
   await promiseOnboardingOverlayLoaded(tab.linkedBrowser);
   await promiseTourNotificationOpened(tab.linkedBrowser);
 
--- a/devtools/client/framework/components/toolbox-tab.js
+++ b/devtools/client/framework/components/toolbox-tab.js
@@ -50,15 +50,25 @@ module.exports = createClass({
         id: `toolbox-tab-${id}`,
         "data-id": id,
         title: tooltip,
         type: "button",
         tabIndex: focusedButton === id ? "0" : "-1",
         onFocus: () => focusButton(id),
         onClick: () => selectTool(id),
       },
+      span(
+        {
+          className: "devtools-tab-line"
+        }
+      ),
       ...this.renderIcon(panelDefinition, isHighlighted),
-      iconOnly ? null : span({
-        className: "devtools-tab-label"
-      }, label)
+      iconOnly ?
+        null :
+        span(
+          {
+            className: "devtools-tab-label"
+          },
+          label
+        )
     );
   }
 });
--- a/devtools/client/inspector/rules/models/element-style.js
+++ b/devtools/client/inspector/rules/models/element-style.js
@@ -5,17 +5,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 const promise = require("promise");
 const Rule = require("devtools/client/inspector/rules/models/rule");
 const {promiseWarn} = require("devtools/client/inspector/shared/utils");
 const {ELEMENT_STYLE} = require("devtools/shared/specs/styles");
-const {getCssProperties} = require("devtools/shared/fronts/css-properties");
+const {getCssProperties, isCssVariable} = require("devtools/shared/fronts/css-properties");
 
 /**
  * ElementStyle is responsible for the following:
  *   Keeps track of which properties are overridden.
  *   Maintains a list of Rule objects for a given element.
  *
  * @param {Element} element
  *        The element whose style we are viewing.
@@ -35,16 +35,17 @@ function ElementStyle(element, ruleView,
     showUserAgentStyles) {
   this.element = element;
   this.ruleView = ruleView;
   this.store = store || {};
   this.pageStyle = pageStyle;
   this.showUserAgentStyles = showUserAgentStyles;
   this.rules = [];
   this.cssProperties = getCssProperties(this.ruleView.inspector.toolbox);
+  this.variables = new Map();
 
   // We don't want to overwrite this.store.userProperties so we only create it
   // if it doesn't already exist.
   if (!("userProperties" in this.store)) {
     this.store.userProperties = new UserProperties();
   }
 
   if (!("disabled" in this.store)) {
@@ -194,17 +195,19 @@ ElementStyle.prototype = {
     this.rules.push(rule);
     return true;
   },
 
   /**
    * Calls markOverridden with all supported pseudo elements
    */
   markOverriddenAll: function () {
+    this.variables.clear();
     this.markOverridden();
+
     for (let pseudo of this.cssProperties.pseudoElements) {
       this.markOverridden(pseudo);
     }
   },
 
   /**
    * Mark the properties listed in this.rules for a given pseudo element
    * with an overridden flag if an earlier property overrides it.
@@ -283,16 +286,20 @@ ElementStyle.prototype = {
         overridden = !!earlier;
       }
 
       computedProp._overriddenDirty =
         (!!computedProp.overridden !== overridden);
       computedProp.overridden = overridden;
       if (!computedProp.overridden && computedProp.textProp.enabled) {
         taken[computedProp.name] = computedProp;
+
+        if (isCssVariable(computedProp.name)) {
+          this.variables.set(computedProp.name, computedProp.value);
+        }
       }
     }
 
     // For each TextProperty, mark it overridden if all of its
     // computed properties are marked overridden.  Update the text
     // property's associated editor, if any.  This will clear the
     // _overriddenDirty state on all computed properties.
     for (let textProp of textProps) {
@@ -323,17 +330,30 @@ ElementStyle.prototype = {
       }
       dirty = computedProp._overriddenDirty || dirty;
       delete computedProp._overriddenDirty;
     }
 
     dirty = (!!prop.overridden !== overridden) || dirty;
     prop.overridden = overridden;
     return dirty;
-  }
+  },
+
+ /**
+  * Returns the current value of a CSS variable; or null if the
+  * variable is not defined.
+  *
+  * @param  {String} name
+  *         The name of the variable.
+  * @return {String} the variable's value or null if the variable is
+  *         not defined.
+  */
+  getVariable: function (name) {
+    return this.variables.get(name);
+  },
 };
 
 /**
  * Store of CSSStyleDeclarations mapped to properties that have been changed by
  * the user.
  */
 function UserProperties() {
   this.map = new Map();
--- a/devtools/client/inspector/rules/test/browser.ini
+++ b/devtools/client/inspector/rules/test/browser.ini
@@ -31,16 +31,18 @@ support-files =
   doc_sourcemaps.scss
   doc_sourcemaps2.css
   doc_sourcemaps2.css^headers^
   doc_sourcemaps2.html
   doc_style_editor_link.css
   doc_test_image.png
   doc_urls_clickable.css
   doc_urls_clickable.html
+  doc_variables_1.html
+  doc_variables_2.html
   head.js
   !/devtools/client/commandline/test/helpers.js
   !/devtools/client/framework/test/shared-head.js
   !/devtools/client/inspector/test/head.js
   !/devtools/client/inspector/test/shared-head.js
   !/devtools/client/shared/test/test-actor.js
   !/devtools/client/shared/test/test-actor-registry.js
 
@@ -90,16 +92,18 @@ support-files =
 [browser_rules_completion-new-property_03.js]
 [browser_rules_completion-new-property_04.js]
 [browser_rules_completion-new-property_multiline.js]
 [browser_rules_computed-lists_01.js]
 [browser_rules_computed-lists_02.js]
 [browser_rules_completion-popup-hidden-after-navigation.js]
 [browser_rules_content_01.js]
 [browser_rules_content_02.js]
+[browser_rules_variables_01.js]
+[browser_rules_variables_02.js]
 skip-if = e10s && debug # Bug 1250058 - Docshell leak on debug e10s
 [browser_rules_context-menu-show-mdn-docs-01.js]
 [browser_rules_context-menu-show-mdn-docs-02.js]
 [browser_rules_context-menu-show-mdn-docs-03.js]
 [browser_rules_copy_styles.js]
 subsuite = clipboard
 skip-if = (os == 'linux' && bits == 32 && debug) # bug 1328915, disable linux32 debug devtools for timeouts
 [browser_rules_cssom.js]
new file mode 100644
--- /dev/null
+++ b/devtools/client/inspector/rules/test/browser_rules_variables_01.js
@@ -0,0 +1,35 @@
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test for variables in rule view.
+
+const TEST_URI = URL_ROOT + "doc_variables_1.html";
+
+add_task(function* () {
+  yield addTab(TEST_URI);
+  let {inspector, view} = yield openRuleView();
+  yield selectNode("#target", inspector);
+
+  info("Tests basic support for CSS Variables for both single variable " +
+  "and double variable. Formats tested: var(x, constant), var(x, var(y))");
+
+  let unsetColor = getRuleViewProperty(view, "div", "color").valueSpan
+    .querySelector(".ruleview-variable-unmatched");
+  let setColor = unsetColor.previousElementSibling;
+  is(unsetColor.textContent, " red", "red is unmatched in color");
+  is(setColor.textContent, "--color", "--color is not set correctly");
+  is(setColor.title, "--color = chartreuse", "--color's title is not set correctly");
+
+  let unsetVar = getRuleViewProperty(view, "div", "background-color").valueSpan
+    .querySelector(".ruleview-variable-unmatched");
+  let setVar = unsetVar.nextElementSibling;
+  let setVarName = setVar.firstElementChild.firstElementChild;
+  is(unsetVar.textContent, "--not-set",
+     "--not-set is unmatched in background-color");
+  is(setVar.textContent, " var(--bg)", "var(--bg) parsed incorrectly");
+  is(setVarName.textContent, "--bg", "--bg is not set correctly");
+  is(setVarName.title, "--bg = seagreen", "--bg's title is not set correctly");
+});
new file mode 100644
--- /dev/null
+++ b/devtools/client/inspector/rules/test/browser_rules_variables_02.js
@@ -0,0 +1,190 @@
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test for variables in rule view.
+
+const TEST_URI = URL_ROOT + "doc_variables_2.html";
+
+add_task(function* () {
+  yield addTab(TEST_URI);
+  let {inspector, view} = yield openRuleView();
+
+  yield testBasic(inspector, view);
+  yield testNestedCssFunctions(inspector, view);
+  yield testBorderShorthandAndInheritance(inspector, view);
+  yield testSingleLevelVariable(inspector, view);
+  yield testDoubleLevelVariable(inspector, view);
+  yield testTripleLevelVariable(inspector, view);
+});
+
+function* testBasic(inspector, view) {
+  info("Test support for basic variable functionality for var() with 2 variables." +
+       "Format: var(--var1, var(--var2))");
+
+  yield selectNode("#a", inspector);
+  let unsetVar = getRuleViewProperty(view, "#a", "font-size").valueSpan
+    .querySelector(".ruleview-variable-unmatched");
+  let setVarParent = unsetVar.nextElementSibling;
+  let setVar = getVarFromParent(setVarParent);
+  is(unsetVar.textContent, "--var-not-defined",
+    "--var-not-defined is not set correctly");
+  is(unsetVar.title, "--var-not-defined is not set",
+    "--var-not-defined's title is not set correctly");
+  is(setVarParent.textContent, " var(--var-defined-font-size)",
+    "var(--var-defined-font-size) parsed incorrectly");
+  is(setVar.textContent, "--var-defined-font-size",
+    "--var-defined-font-size is not set correctly");
+  is(setVar.title, "--var-defined-font-size = 60px",
+    "--bg's title is not set correctly");
+}
+
+function* testNestedCssFunctions(inspector, view) {
+  info("Test support for variable functionality for a var() nested inside " +
+  "another CSS function. Format: rgb(0, 0, var(--var1, var(--var2)))");
+
+  yield selectNode("#b", inspector);
+  let unsetVarParent = getRuleViewProperty(view, "#b", "color").valueSpan
+    .querySelector(".ruleview-variable-unmatched");
+  let unsetVar = getVarFromParent(unsetVarParent);
+  let setVar = unsetVarParent.previousElementSibling;
+  is(unsetVarParent.textContent, " var(--var-defined-r-2)",
+    "var(--var-defined-r-2) not parsed correctly");
+  is(unsetVar.textContent, "--var-defined-r-2",
+    "--var-defined-r-2 is not set correctly");
+  is(unsetVar.title, "--var-defined-r-2 = 0",
+    "--var-defined-r-2's title is not set correctly");
+  is(setVar.textContent, "--var-defined-r-1",
+    "--var-defined-r-1 is not set correctly");
+  is(setVar.title, "--var-defined-r-1 = 255",
+    "--var-defined-r-1's title is not set correctly");
+}
+
+function* testBorderShorthandAndInheritance(inspector, view) {
+  info("Test support for variable functionality for shorthands/CSS styles with spaces " +
+  "like \"margin: w x y z\". Also tests functionality for inherticance of CSS" +
+  " variables. Format: var(l, var(m)) var(x) rgb(var(r) var(g) var(b))");
+
+  yield selectNode("#c", inspector);
+  let unsetVarL = getRuleViewProperty(view, "#c", "border").valueSpan
+    .querySelector(".ruleview-variable-unmatched");
+  let setVarMParent = unsetVarL.nextElementSibling;
+
+  // var(x) is the next sibling of the parent of M
+  let setVarXParent = setVarMParent.parentNode.nextElementSibling;
+
+  // var(r) is the next sibling of var(x), and var(g) is the next sibling of var(r), etc.
+  let setVarRParent = setVarXParent.nextElementSibling;
+  let setVarGParent = setVarRParent.nextElementSibling;
+  let setVarBParent = setVarGParent.nextElementSibling;
+
+  let setVarM = getVarFromParent(setVarMParent);
+  let setVarX = setVarXParent.firstElementChild;
+  let setVarR = setVarRParent.firstElementChild;
+  let setVarG = setVarGParent.firstElementChild;
+  let setVarB = setVarBParent.firstElementChild;
+
+  is(unsetVarL.textContent, "--var-undefined",
+    "--var-undefined is not set correctly");
+  is(unsetVarL.title, "--var-undefined is not set",
+    "--var-undefined's title is not set correctly");
+
+  is(setVarM.textContent, "--var-border-px",
+    "--var-border-px is not set correctly");
+  is(setVarM.title, "--var-border-px = 10px",
+    "--var-border-px's title is not set correctly");
+
+  is(setVarX.textContent, "--var-border-style",
+    "--var-border-style is not set correctly");
+  is(setVarX.title, "--var-border-style = solid",
+    "var-border-style's title is not set correctly");
+
+  is(setVarR.textContent, "--var-border-r",
+    "--var-defined-r is not set correctly");
+  is(setVarR.title, "--var-border-r = 255",
+    "--var-defined-r's title is not set correctly");
+
+  is(setVarG.textContent, "--var-border-g",
+    "--var-defined-g is not set correctly");
+  is(setVarG.title, "--var-border-g = 0",
+    "--var-defined-g's title is not set correctly");
+
+  is(setVarB.textContent, "--var-border-b",
+    "--var-defined-b is not set correctly");
+  is(setVarB.title, "--var-border-b = 0",
+    "--var-defined-b's title is not set correctly");
+}
+
+function* testSingleLevelVariable(inspector, view) {
+  info("Test support for variable functionality of a single level of " +
+  "undefined variables. Format: var(x, constant)");
+
+  yield selectNode("#d", inspector);
+  let unsetVar = getRuleViewProperty(view, "#d", "font-size").valueSpan
+    .querySelector(".ruleview-variable-unmatched");
+
+  is(unsetVar.textContent, "--var-undefined",
+    "--var-undefined is not set correctly");
+  is(unsetVar.title, "--var-undefined is not set",
+    "--var-undefined's title is not set correctly");
+}
+
+function* testDoubleLevelVariable(inspector, view) {
+  info("Test support for variable functionality of double level of " +
+  "undefined variables. Format: var(x, var(y, constant))");
+
+  yield selectNode("#e", inspector);
+  let allUnsetVars = getRuleViewProperty(view, "#e", "color").valueSpan
+    .querySelectorAll(".ruleview-variable-unmatched");
+
+  is(allUnsetVars.length, 2, "The number of unset variables is mismatched.");
+
+  let unsetVar1 = allUnsetVars[0];
+  let unsetVar2 = allUnsetVars[1];
+
+  is(unsetVar1.textContent, "--var-undefined",
+    "--var-undefined is not set correctly");
+  is(unsetVar1.title, "--var-undefined is not set",
+    "--var-undefined's title is not set correctly");
+
+  is(unsetVar2.textContent, "--var-undefined-2",
+    "--var-undefined is not set correctly");
+  is(unsetVar2.title, "--var-undefined-2 is not set",
+    "--var-undefined-2's title is not set correctly");
+}
+
+function* testTripleLevelVariable(inspector, view) {
+  info("Test support for variable functionality of triple level of " +
+  "undefined variables. Format: var(x, var(y, var(z, constant)))");
+
+  yield selectNode("#f", inspector);
+  let allUnsetVars = getRuleViewProperty(view, "#f", "border-style").valueSpan
+    .querySelectorAll(".ruleview-variable-unmatched");
+
+  is(allUnsetVars.length, 3, "The number of unset variables is mismatched.");
+
+  let unsetVar1 = allUnsetVars[0];
+  let unsetVar2 = allUnsetVars[1];
+  let unsetVar3 = allUnsetVars[2];
+
+  is(unsetVar1.textContent, "--var-undefined",
+    "--var-undefined is not set correctly");
+  is(unsetVar1.title, "--var-undefined is not set",
+    "--var-undefined's title is not set correctly");
+
+  is(unsetVar2.textContent, "--var-undefined-2",
+    "--var-undefined-2 is not set correctly");
+  is(unsetVar2.title, "--var-undefined-2 is not set",
+    "--var-defined-r-2's title is not set correctly");
+
+  is(unsetVar3.textContent, "--var-undefined-3",
+    "--var-undefined-3 is not set correctly");
+  is(unsetVar3.title, "--var-undefined-3 is not set",
+    "--var-defined-r-3's title is not set correctly");
+}
+
+function getVarFromParent(varParent) {
+  return varParent.firstElementChild.firstElementChild;
+}
new file mode 100644
--- /dev/null
+++ b/devtools/client/inspector/rules/test/doc_variables_1.html
@@ -0,0 +1,23 @@
+<!-- Any copyright is dedicated to the Public Domain.
+     http://creativecommons.org/publicdomain/zero/1.0/ -->
+<html>
+<head>
+  <title>variables test</title>
+
+  <style>
+    * {
+      --color: tomato;
+      --bg: violet;
+    }
+
+    div {
+      --color: chartreuse;
+      color: var(--color, red);
+      background-color: var(--not-set, var(--bg));
+    }
+  </style>
+</head>
+<body>
+  <div id="target" style="--bg: seagreen;"> the ocean </div>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/devtools/client/inspector/rules/test/doc_variables_2.html
@@ -0,0 +1,45 @@
+<!-- Any copyright is dedicated to the Public Domain.
+     http://creativecommons.org/publicdomain/zero/1.0/ -->
+<html>
+<head>
+  <title>variables test</title>
+  <style>
+    :root {
+      --var-border-px: 10px;
+      --var-border-style: solid;
+      --var-border-r: 255;
+      --var-border-g: 0;
+      --var-border-b: 0;
+    }
+    #a {
+      --var-defined-font-size: 60px;
+      font-size: var(--var-not-defined, var(--var-defined-font-size));
+    }
+    #b {
+      --var-defined-r-1: 255;
+      --var-defined-r-2: 0;
+      color: rgb(var(--var-defined-r-1, var(--var-defined-r-2)), 0, 0);
+    }
+    #c {
+      border: var(--var-undefined, var(--var-border-px)) var(--var-border-style) rgb(var(--var-border-r), var(--var-border-g), var(--var-border-b))
+    }
+    #d {
+      font-size: var(--var-undefined, 30px);
+    }
+    #e {
+      color: var(--var-undefined, var(--var-undefined-2, blue));
+    }
+    #f {
+      border-style: var(--var-undefined, var(--var-undefined-2, var(--var-undefined-3, solid)));
+    }
+  </style>
+</head>
+<body>
+  <div id="a">A</div><br>
+  <div id="b">B</div><br>
+  <div id="c">C</div><br>
+  <div id="d">D</div><br>
+  <div id="e">E</div><br>
+  <div id="f">F</div>
+</body>
+</html>
--- a/devtools/client/inspector/rules/views/text-property-editor.js
+++ b/devtools/client/inspector/rules/views/text-property-editor.js
@@ -358,17 +358,19 @@ TextPropertyEditor.prototype = {
       colorClass: "ruleview-color",
       colorSwatchClass: SHARED_SWATCH_CLASS + " " + COLOR_SWATCH_CLASS,
       filterClass: "ruleview-filter",
       filterSwatchClass: SHARED_SWATCH_CLASS + " " + FILTER_SWATCH_CLASS,
       gridClass: "ruleview-grid",
       shapeClass: "ruleview-shape",
       defaultColorType: !propDirty,
       urlClass: "theme-link",
-      baseURI: this.sheetHref
+      baseURI: this.sheetHref,
+      unmatchedVariableClass: "ruleview-variable-unmatched",
+      isVariableInUse: varName => this.rule.elementStyle.getVariable(varName),
     };
     let frag = outputParser.parseCssProperty(name, val, parserOptions);
     this.valueSpan.innerHTML = "";
     this.valueSpan.appendChild(frag);
 
     this.ruleView.emit("property-value-updated", this.valueSpan);
 
     // Attach the color picker tooltip to the color swatches
--- a/devtools/client/shared/components/tabs/tabs.css
+++ b/devtools/client/shared/components/tabs/tabs.css
@@ -76,49 +76,39 @@
   border-bottom: 1px solid var(--theme-splitter-color);
   background: var(--theme-tab-toolbar-background);
 }
 
 .theme-dark .tabs .tabs-menu-item,
 .theme-light .tabs .tabs-menu-item {
   margin: 0;
   padding: 0;
-  border-style: solid;
-  border-width: 0;
-  border-inline-start-width: 1px;
-  border-color: var(--theme-splitter-color);
   color: var(--theme-content-color1);
 }
 
 .theme-dark .tabs .tabs-menu-item:last-child,
 .theme-light:not(.theme-firebug) .tabs .tabs-menu-item:last-child {
   border-inline-end-width: 1px;
 }
 
 .theme-dark .tabs .tabs-menu-item a,
 .theme-light .tabs .tabs-menu-item a {
   padding: 3px 15px;
 }
 
-.theme-dark .tabs .tabs-menu-item:hover:not(.is-active),
-.theme-light .tabs .tabs-menu-item:hover:not(.is-active) {
+.theme-dark .tabs .tabs-menu-item:hover,
+.theme-light .tabs .tabs-menu-item:hover {
   background-color: var(--theme-toolbar-hover);
 }
 
 .theme-dark .tabs .tabs-menu-item:hover:active:not(.is-active),
 .theme-light .tabs .tabs-menu-item:hover:active:not(.is-active) {
   background-color: var(--theme-toolbar-hover-active);
 }
 
-.theme-dark .tabs .tabs-menu-item.is-active,
-.theme-light .tabs .tabs-menu-item.is-active {
-  background-color: var(--theme-selection-background);
-  color: var(--theme-selection-color);
-}
-
 /* Dark Theme */
 
 .theme-dark .tabs .tabs-menu-item {
   color: var(--theme-body-color-alt);
 }
 
 .theme-dark .tabs .tabs-menu-item:hover:not(.is-active) {
   color: var(--theme-focus-outline-color);
--- a/devtools/client/shared/components/tabs/tabs.js
+++ b/devtools/client/shared/components/tabs/tabs.js
@@ -258,16 +258,17 @@ define(function (require, exports, modul
           // See also `onKeyDown()` event handler.
           return (
             DOM.li({
               className,
               key: index,
               ref,
               role: "presentation",
             },
+              DOM.span({className: "devtools-tab-line"}),
               DOM.a({
                 id: id ? id + "-tab" : "tab-" + index,
                 tabIndex: isTabSelected ? 0 : -1,
                 "aria-controls": id ? id + "-panel" : "panel-" + index,
                 "aria-selected": isTabSelected,
                 role: "tab",
                 onClick: this.onClickTab.bind(this, index),
               },
--- a/devtools/client/shared/output-parser.js
+++ b/devtools/client/shared/output-parser.js
@@ -13,16 +13,20 @@ const {
   BASIC_SHAPE_FUNCTIONS,
   BEZIER_KEYWORDS,
   COLOR_TAKING_FUNCTIONS,
   CSS_TYPES
 } = require("devtools/shared/css/properties-db");
 const {appendText} = require("devtools/client/inspector/shared/utils");
 const Services = require("Services");
 
+const STYLE_INSPECTOR_PROPERTIES = "devtools/shared/locales/styleinspector.properties";
+const {LocalizationHelper} = require("devtools/shared/l10n");
+const STYLE_INSPECTOR_L10N = new LocalizationHelper(STYLE_INSPECTOR_PROPERTIES);
+
 const HTML_NS = "http://www.w3.org/1999/xhtml";
 const CSS_GRID_ENABLED_PREF = "layout.css.grid.enabled";
 const CSS_SHAPES_ENABLED_PREF = "devtools.inspector.shapesHighlighter.enabled";
 
 /**
  * This module is used to process text for output by developer tools. This means
  * linking JS files with the debugger, CSS files with the style editor, JS
  * functions with the debugger, placing color swatches next to colors and
@@ -91,92 +95,215 @@ OutputParser.prototype = {
       return this._parse(value, options);
     }
     this._appendTextNode(value);
 
     return this._toDOM();
   },
 
   /**
-   * Given an initial FUNCTION token, read tokens from |tokenStream|
-   * and collect all the (non-comment) text.  Return the collected
-   * text.  The function token and the close paren are included in the
-   * result.
+   * Read tokens from |tokenStream| and collect all the (non-comment)
+   * text. Return the collected texts and variable data (if any).
+   * Stop when an unmatched closing paren is seen.
+   * If |stopAtComma| is true, then also stop when a top-level
+   * (unparenthesized) comma is seen.
    *
-   * @param  {CSSToken} initialToken
-   *         The FUNCTION token.
    * @param  {String} text
-   *         The original CSS text.
+   *         The original source text.
    * @param  {CSSLexer} tokenStream
    *         The token stream from which to read.
-   * @return {String}
-   *         The text of body of the function call.
+   * @param  {Object} options
+   *         The options object in use; @see _mergeOptions.
+   * @param  {Boolean} stopAtComma
+   *         If true, stop at a comma.
+   * @return {Object}
+   *         An object of the form {tokens, functionData, sawComma, sawVariable}.
+   *         |tokens| is a list of the non-comment, non-whitespace tokens
+   *         that were seen. The stopping token (paren or comma) will not
+   *         be included.
+   *         |functionData| is a list of parsed strings and nodes that contain the
+   *         data between the matching parenthesis. The stopping token's text will
+   *         not be included.
+   *         |sawComma| is true if the stop was due to a comma, or false otherwise.
+   *         |sawVariable| is true if a variable was seen while parsing the text.
    */
-  _collectFunctionText: function (initialToken, text, tokenStream) {
-    let result = text.substring(initialToken.startOffset,
-                                initialToken.endOffset);
+  _parseMatchingParens: function (text, tokenStream, options, stopAtComma) {
     let depth = 1;
+    let functionData = [];
+    let tokens = [];
+    let sawVariable = false;
+
     while (depth > 0) {
       let token = tokenStream.nextToken();
       if (!token) {
         break;
       }
       if (token.tokenType === "comment") {
         continue;
       }
-      result += text.substring(token.startOffset, token.endOffset);
+
       if (token.tokenType === "symbol") {
-        if (token.text === "(") {
+        if (stopAtComma && depth === 1 && token.text === ",") {
+          return { tokens, functionData, sawComma: true, sawVariable };
+        } else if (token.text === "(") {
           ++depth;
         } else if (token.text === ")") {
           --depth;
+          if (depth === 0) {
+            break;
+          }
         }
+      } else if (token.tokenType === "function" && token.text === "var" &&
+                 options.isVariableInUse) {
+        sawVariable = true;
+        let variableNode = this._parseVariable(token, text, tokenStream, options);
+        functionData.push(variableNode);
       } else if (token.tokenType === "function") {
         ++depth;
       }
+
+      if (token.tokenType !== "function" || token.text !== "var" ||
+          !options.isVariableInUse) {
+        functionData.push(text.substring(token.startOffset, token.endOffset));
+      }
+
+      if (token.tokenType !== "whitespace") {
+        tokens.push(token);
+      }
     }
-    return result;
+
+    return { tokens, functionData, sawComma: false, sawVariable };
   },
 
   /**
-   * Parse a string.
+   * Parse var() use and return a variable node to be added to the output state.
+   * This will read tokens up to and including the ")" that closes the "var("
+   * invocation.
+   *
+   * @param  {CSSToken} initialToken
+   *         The "var(" token that was already seen.
+   * @param  {String} text
+   *         The original input text.
+   * @param  {CSSLexer} tokenStream
+   *         The token stream from which to read.
+   * @param  {Object} options
+   *         The options object in use; @see _mergeOptions.
+   * @return {Object}
+   *         A node for the variable, with the appropriate text and
+   *         title. Eg. a span with "var(--var1)" as the textContent
+   *         and a title for --var1 like "--var1 = 10" or
+   *         "--var1 is not set".
+   */
+  _parseVariable: function (initialToken, text, tokenStream, options) {
+    // Handle the "var(".
+    let varText = text.substring(initialToken.startOffset,
+                                 initialToken.endOffset);
+    let variableNode = this._createNode("span", {}, varText);
+
+    // Parse the first variable name within the parens of var().
+    let {tokens, functionData, sawComma, sawVariable} =
+        this._parseMatchingParens(text, tokenStream, options, true);
+
+    let result = sawVariable ? "" : functionData.join("");
+
+    // Display options for the first and second argument in the var().
+    let firstOpts = {};
+    let secondOpts = {};
+
+    let varValue;
+
+    // Get the variable value if it is in use.
+    if (tokens && tokens.length === 1) {
+      varValue = options.isVariableInUse(tokens[0].text);
+    }
+
+    // Get the variable name.
+    let varName = text.substring(tokens[0].startOffset, tokens[0].endOffset);
+
+    if (typeof varValue === "string") {
+      // The variable value is valid, set the variable name's title of the first argument
+      // in var() to display the variable name and value.
+      firstOpts.title =
+        STYLE_INSPECTOR_L10N.getFormatStr("rule.variableValue", varName, varValue);
+      secondOpts.class = options.unmatchedVariableClass;
+    } else {
+      // The variable name is not valid, mark it unmatched.
+      firstOpts.class = options.unmatchedVariableClass;
+      firstOpts.title = STYLE_INSPECTOR_L10N.getFormatStr("rule.variableUnset",
+                                                          varName);
+    }
+
+    variableNode.appendChild(this._createNode("span", firstOpts, result));
+
+    // If we saw a ",", then append it and show the remainder using
+    // the correct highlighting.
+    if (sawComma) {
+      variableNode.appendChild(this.doc.createTextNode(","));
+
+      // Parse the text up until the close paren, being sure to
+      // disable the special case for filter.
+      let subOptions = Object.assign({}, options);
+      subOptions.expectFilter = false;
+      let saveParsed = this.parsed;
+      this.parsed = [];
+      let rest = this._doParse(text, subOptions, tokenStream, true);
+      this.parsed = saveParsed;
+
+      let span = this._createNode("span", secondOpts);
+      span.appendChild(rest);
+      variableNode.appendChild(span);
+    }
+    variableNode.appendChild(this.doc.createTextNode(")"));
+
+    return variableNode;
+  },
+
+  /* eslint-disable complexity */
+  /**
+   * The workhorse for @see _parse. This parses some CSS text,
+   * stopping at EOF; or optionally when an umatched close paren is
+   * seen.
    *
    * @param  {String} text
-   *         Text to parse.
-   * @param  {Object} [options]
-   *         Options object. For valid options and default values see
-   *         _mergeOptions().
+   *         The original input text.
+   * @param  {Object} options
+   *         The options object in use; @see _mergeOptions.
+   * @param  {CSSLexer} tokenStream
+   *         The token stream from which to read
+   * @param  {Boolean} stopAtCloseParen
+   *         If true, stop at an umatched close paren.
    * @return {DocumentFragment}
    *         A document fragment.
    */
-  _parse: function (text, options = {}) {
-    text = text.trim();
-    this.parsed.length = 0;
-
-    let tokenStream = getCSSLexer(text);
-    let parenDepth = 0;
+  _doParse: function (text, options, tokenStream, stopAtCloseParen) {
+    let parenDepth = stopAtCloseParen ? 1 : 0;
     let outerMostFunctionTakesColor = false;
 
     let colorOK = function () {
       return options.supportsColor ||
         (options.expectFilter && parenDepth === 1 &&
          outerMostFunctionTakesColor);
     };
 
     let angleOK = function (angle) {
       return (new angleUtils.CssAngle(angle)).valid;
     };
 
     let spaceNeeded = false;
-    let token = tokenStream.nextToken();
-    while (token) {
+    let done = false;
+
+    while (!done) {
+      let token = tokenStream.nextToken();
+      if (!token) {
+        break;
+      }
+
       if (token.tokenType === "comment") {
         // This doesn't change spaceNeeded, because we didn't emit
         // anything to the output.
-        token = tokenStream.nextToken();
         continue;
       }
 
       switch (token.tokenType) {
         case "function": {
           if (COLOR_TAKING_FUNCTIONS.includes(token.text) ||
               ANGLE_TAKING_FUNCTIONS.includes(token.text)) {
             // The function can accept a color or an angle argument, and we know
@@ -185,31 +312,54 @@ OutputParser.prototype = {
             // can be handled in a single place.
             this._appendTextNode(text.substring(token.startOffset,
                                                 token.endOffset));
             if (parenDepth === 0) {
               outerMostFunctionTakesColor = COLOR_TAKING_FUNCTIONS.includes(
                 token.text);
             }
             ++parenDepth;
+          } else if (token.text === "var" && options.isVariableInUse) {
+            let variableNode = this._parseVariable(token, text, tokenStream, options);
+            this.parsed.push(variableNode);
           } else {
-            let functionText = this._collectFunctionText(token, text,
-                                                         tokenStream);
+            let {functionData, sawVariable} = this._parseMatchingParens(text, tokenStream,
+              options);
+
+            let functionName = text.substring(token.startOffset, token.endOffset);
 
-            if (options.expectCubicBezier && token.text === "cubic-bezier") {
-              this._appendCubicBezier(functionText, options);
-            } else if (colorOK() &&
-                       colorUtils.isValidCSSColor(functionText, this.cssColor4)) {
-              this._appendColor(functionText, options);
-            } else if (options.expectShape &&
-                       Services.prefs.getBoolPref(CSS_SHAPES_ENABLED_PREF) &&
-                       BASIC_SHAPE_FUNCTIONS.includes(token.text)) {
-              this._appendShape(functionText, options);
+            if (sawVariable) {
+              // If function contains variable, we need to add both strings
+              // and nodes.
+              this._appendTextNode(functionName);
+              for (let data of functionData) {
+                if (typeof data === "string") {
+                  this._appendTextNode(data);
+                } else if (data) {
+                  this.parsed.push(data);
+                }
+              }
+              this._appendTextNode(")");
             } else {
-              this._appendTextNode(functionText);
+              // If no variable in function, join the text together and add
+              // to DOM accordingly.
+              let functionText = functionName + functionData.join("") + ")";
+
+              if (options.expectCubicBezier && token.text === "cubic-bezier") {
+                this._appendCubicBezier(functionText, options);
+              } else if (colorOK() &&
+                         colorUtils.isValidCSSColor(functionText, this.cssColor4)) {
+                this._appendColor(functionText, options);
+              } else if (options.expectShape &&
+                         Services.prefs.getBoolPref(CSS_SHAPES_ENABLED_PREF) &&
+                         BASIC_SHAPE_FUNCTIONS.includes(token.text)) {
+                this._appendShape(functionText, options);
+              } else {
+                this._appendTextNode(functionText);
+              }
             }
           }
           break;
         }
 
         case "ident":
           if (options.expectCubicBezier &&
               BEZIER_KEYWORDS.indexOf(token.text) >= 0) {
@@ -257,16 +407,22 @@ OutputParser.prototype = {
                           token.text, options);
           break;
 
         case "symbol":
           if (token.text === "(") {
             ++parenDepth;
           } else if (token.text === ")") {
             --parenDepth;
+
+            if (stopAtCloseParen && parenDepth === 0) {
+              done = true;
+              break;
+            }
+
             if (parenDepth === 0) {
               outerMostFunctionTakesColor = false;
             }
           }
           // falls through
         default:
           this._appendTextNode(
             text.substring(token.startOffset, token.endOffset));
@@ -274,28 +430,46 @@ OutputParser.prototype = {
       }
 
       // If this token might possibly introduce token pasting when
       // color-cycling, require a space.
       spaceNeeded = (token.tokenType === "ident" || token.tokenType === "at" ||
                      token.tokenType === "id" || token.tokenType === "hash" ||
                      token.tokenType === "number" || token.tokenType === "dimension" ||
                      token.tokenType === "percentage" || token.tokenType === "dimension");
-
-      token = tokenStream.nextToken();
     }
 
     let result = this._toDOM();
 
     if (options.expectFilter && !options.filterSwatch) {
       result = this._wrapFilter(text, options, result);
     }
 
     return result;
   },
+  /* eslint-enable complexity */
+
+  /**
+   * Parse a string.
+   *
+   * @param  {String} text
+   *         Text to parse.
+   * @param  {Object} [options]
+   *         Options object. For valid options and default values see
+   *         _mergeOptions().
+   * @return {DocumentFragment}
+   *         A document fragment.
+   */
+  _parse: function (text, options = {}) {
+    text = text.trim();
+    this.parsed.length = 0;
+
+    let tokenStream = getCSSLexer(text);
+    return this._doParse(text, options, tokenStream, false);
+  },
 
   /**
    * Return true if it's a display:[inline-]grid token.
    *
    * @param  {String} text
    *         the parsed text.
    * @param  {Object} token
    *         the parsed token.
@@ -1237,16 +1411,24 @@ OutputParser.prototype = {
    *                                    // _wrapFilter.  Used only for
    *                                    // previewing with the filter swatch.
    *           - gridClass: ""          // The class to use for the grid icon.
    *           - shapeClass: ""         // The class to use for the shape icon.
    *           - supportsColor: false   // Does the CSS property support colors?
    *           - urlClass: ""           // The class to be used for url() links.
    *           - baseURI: undefined     // A string used to resolve
    *                                    // relative links.
+   *           - isVariableInUse        // A function taking a single
+   *                                    // argument, the name of a variable.
+   *                                    // This should return the variable's
+   *                                    // value, if it is in use; or null.
+   *           - unmatchedVariableClass: ""
+   *                                    // The class to use for a component
+   *                                    // of a "var(...)" that is not in
+   *                                    // use.
    * @return {Object}
    *         Overridden options object
    */
   _mergeOptions: function (overrides) {
     let defaults = {
       defaultColorType: true,
       angleClass: "",
       angleSwatchClass: "",
@@ -1255,16 +1437,18 @@ OutputParser.prototype = {
       colorClass: "",
       colorSwatchClass: "",
       filterSwatch: false,
       gridClass: "",
       shapeClass: "",
       supportsColor: false,
       urlClass: "",
       baseURI: undefined,
+      isVariableInUse: null,
+      unmatchedVariableClass: null,
     };
 
     for (let item in overrides) {
       defaults[item] = overrides[item];
     }
     return defaults;
   }
 };
--- a/devtools/client/shared/test/browser_outputparser.js
+++ b/devtools/client/shared/test/browser_outputparser.js
@@ -24,16 +24,17 @@ function* performTest() {
 
   let parser = new OutputParser(doc, cssProperties);
   testParseCssProperty(doc, parser);
   testParseCssVar(doc, parser);
   testParseURL(doc, parser);
   testParseFilter(doc, parser);
   testParseAngle(doc, parser);
   testParseShape(doc, parser);
+  testParseVariable(doc, parser);
 
   host.destroy();
 }
 
 // Class name used in color swatch.
 var COLOR_TEST_CLASS = "test-class";
 
 // Create a new CSS color-parsing test.  |name| is the name of the CSS
@@ -408,8 +409,55 @@ function testParseShape(doc, parser) {
     let frag = parser.parseCssProperty("clip-path", definition, {
       shapeClass: "ruleview-shape"
     });
     let spans = frag.querySelectorAll(".ruleview-shape-point");
     is(spans.length, spanCount, desc + " span count");
     is(frag.textContent, definition, desc + " text content");
   }
 }
+
+function testParseVariable(doc, parser) {
+  let TESTS = [
+    {
+      text: "var(--seen)",
+      variables: {"--seen": "chartreuse" },
+      expected: "<span>var(<span title=\"--seen = chartreuse\">--seen</span>)</span>"
+    },
+    {
+      text: "var(--not-seen)",
+      variables: {},
+      expected: "<span>var(<span class=\"unmatched-class\" " +
+        "title=\"--not-seen is not set\">--not-seen</span>)</span>"
+    },
+    {
+      text: "var(--seen, seagreen)",
+      variables: {"--seen": "chartreuse" },
+      expected: "<span>var(<span title=\"--seen = chartreuse\">--seen</span>," +
+        "<span class=\"unmatched-class\"> <span data-color=\"seagreen\"><span>seagreen" +
+        "</span></span></span>)</span>"
+    },
+    {
+      text: "var(--not-seen, var(--seen))",
+      variables: {"--seen": "chartreuse" },
+      expected: "<span>var(<span class=\"unmatched-class\" " +
+        "title=\"--not-seen is not set\">--not-seen</span>,<span> <span>var(<span " +
+        "title=\"--seen = chartreuse\">--seen</span>)</span></span>)</span>"
+    },
+  ];
+
+  for (let test of TESTS) {
+    let getValue = function (varName) {
+      return test.variables[varName];
+    };
+
+    let frag = parser.parseCssProperty("color", test.text, {
+      isVariableInUse: getValue,
+      unmatchedVariableClass: "unmatched-class"
+    });
+
+    let target = doc.querySelector("div");
+    target.appendChild(frag);
+
+    is(target.innerHTML, test.expected, test.text);
+    target.innerHTML = "";
+  }
+}
--- a/devtools/client/themes/common.css
+++ b/devtools/client/themes/common.css
@@ -2,16 +2,26 @@
  * 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/. */
 
 @import url("resource://devtools/client/themes/splitters.css");
 @namespace html url("http://www.w3.org/1999/xhtml");
 
 :root {
   font: message-box;
+
+  --tab-line-selected-color: highlight;
+}
+
+:root.theme-light {
+  --tab-line-hover-color: rgba(0,0,0,.2);
+}
+
+:root.theme-dark {
+  --tab-line-hover-color: rgba(255,255,255,.2);
 }
 
 :root[platform="mac"] {
   --monospace-font-family: Menlo, monospace;
 }
 
 :root[platform="win"] {
   --monospace-font-family: Consolas, monospace;
@@ -678,16 +688,40 @@ checkbox:-moz-focusring {
   border-inline-start: 1px solid var(--theme-splitter-color);
 
   background: var(--theme-tab-toolbar-background);
   background-image: url("chrome://devtools/skin/images/dropmarker.svg");
   background-repeat: no-repeat;
   background-position: center;
 }
 
+.devtools-tab-line {
+  position: absolute;
+  top: 0;
+  left: 0;
+  width: 100%;
+  height: 2px;
+  background: transparent;
+}
+
+.devtools-tab:hover .devtools-tab-line,
+.tabs-menu-item:hover .devtools-tab-line {
+  background: var(--tab-line-hover-color);
+}
+
+.devtools-tab.selected .devtools-tab-line,
+.tabs-menu-item.is-active .devtools-tab-line {
+  background: var(--tab-line-selected-color);
+}
+
+/* Hide the tab line in the firebug theme */
+.theme-firebug .devtools-tab-line {
+  background: transparent !important;
+}
+
 /* No result message styles */
 
 .devtools-sidepanel-no-result {
   font-style: italic;
   text-align: center;
   padding: 0.5em;
   -moz-user-select: none;
   font-size: 12px;
--- a/devtools/client/themes/rules.css
+++ b/devtools/client/themes/rules.css
@@ -561,17 +561,18 @@
 }
 
 .ruleview-selectorcontainer {
   word-wrap: break-word;
   cursor: text;
 }
 
 .ruleview-selector-separator,
-.ruleview-selector-unmatched {
+.ruleview-selector-unmatched,
+.ruleview-variable-unmatched {
   color: #888;
 }
 
 .ruleview-selector-matched > .ruleview-selector-attribute {
   /* TODO: Bug 1178535 Awaiting UX feedback on highlight colors */
 }
 
 .ruleview-selector-matched > .ruleview-selector-pseudo-class {
--- a/devtools/client/themes/toolbox.css
+++ b/devtools/client/themes/toolbox.css
@@ -84,34 +84,32 @@
 #toolbox-option-container,
 #toolbox-controls,
 #toolbox-dock-buttons {
   display: flex;
   align-items: stretch;
 }
 
 /* Toolbox tabs */
+
 .devtools-tab {
   position: relative;
   display: flex;
   align-items: center;
   min-width: 32px;
   min-height: 24px;
   margin: 0;
   padding: 0;
-  border-style: solid;
-  border-width: 0;
-  border-inline-start-width: 1px;
+  border: none;
   white-space: nowrap;
   overflow: hidden;
   text-overflow: ellipsis;
   background-color: transparent;
 }
 
-
 .devtools-tab-label {
   mask-image: linear-gradient(to left, transparent 0, black 6px);
   /* Set the end padding on the label to make sure the label gets faded out properly */
   padding-inline-end: 10px;
   min-width: 1px;
 }
 
 .devtools-tab-label:-moz-locale-dir(rtl) {
@@ -191,22 +189,16 @@
   margin-inline-end: 5px;
   opacity: 0.8;
   max-height: 16px;
   width: 16px; /* Prevents collapse during theme switching */
   vertical-align: text-top;
   flex-shrink: 0;
 }
 
-/* Support invertable icon flags and make icon white when it's on a blue background */
-.theme-light .devtools-tab.icon-invertable-light-theme:not(.selected) > img,
-.devtools-tab.icon-invertable-dark-theme.selected > img {
-  filter: invert(1);
-}
-
 /* Don't apply any filter to non-invertable command button icons */
 .command-button:not(.command-button-invertable),
 /* icon-invertable-light-theme icons are white, so do not invert them for the dark theme */
 .theme-dark .devtools-tab.icon-invertable-light-theme > img,
 /* Since "highlighted" icons are green, we should omit the filter */
 .devtools-tab.icon-invertable.highlighted:not(.selected) > img {
   filter: none;
 }
@@ -217,22 +209,16 @@
 }
 
 .devtools-tab:hover > img,
 .devtools-tab:active > img,
 .devtools-tab.selected > img {
   opacity: 1;
 }
 
-.devtools-tabbar .devtools-tab.selected,
-.devtools-tabbar .devtools-tab.selected:hover:active {
-  color: var(--theme-selection-color);
-  background-color: var(--theme-selection-background);
-}
-
 .toolbox-tabs .devtools-tab.selected,
 .toolbox-tabs .devtools-tab.highlighted,
 .toolbox-tabs .devtools-tab.selected + .devtools-tab,
 .toolbox-tabs .devtools-tab.highlighted + .devtools-tab {
   border-inline-start-color: transparent;
 }
 
 .toolbox-tabs .devtools-tab:first-child {
--- a/devtools/shared/fronts/css-properties.js
+++ b/devtools/shared/fronts/css-properties.js
@@ -144,17 +144,18 @@ CssProperties.prototype = {
 
   /**
    * Checks to see if the property is an inherited one.
    *
    * @param {String} property The property name to be checked.
    * @return {Boolean}
    */
   isInherited(property) {
-    return this.properties[property] && this.properties[property].isInherited;
+    return (this.properties[property] && this.properties[property].isInherited) ||
+            isCssVariable(property);
   },
 
   /**
    * Checks if the property supports the given CSS type.
    * CSS types should come from devtools/shared/css/properties-db.js' CSS_TYPES.
    *
    * @param {String} property The property to be checked.
    * @param {Number} type One of the type values from CSS_TYPES.
@@ -345,10 +346,11 @@ function reattachCssColorValues(db) {
   }
 }
 
 module.exports = {
   CssPropertiesFront,
   CssProperties,
   getCssProperties,
   getClientCssProperties,
-  initCssProperties
+  initCssProperties,
+  isCssVariable,
 };
--- a/devtools/shared/locales/en-US/styleinspector.properties
+++ b/devtools/shared/locales/en-US/styleinspector.properties
@@ -62,16 +62,28 @@ rule.warning.title=Invalid property valu
 # of the search button that is shown next to a property that has been overridden
 # in the rule view.
 rule.filterProperty.title=Filter rules containing this property
 
 # LOCALIZATION NOTE (ruleView.empty): Text displayed when the highlighter is
 # first opened and there's no node selected in the rule view.
 rule.empty=No element selected.
 
+# LOCALIZATION NOTE (rule.variableValue): Text displayed in a tooltip
+# when the mouse is over a variable use (like "var(--something)") in
+# the rule view.  The first argument is the variable name and the
+# second argument is the value.
+rule.variableValue=%S = %S
+
+# LOCALIZATION NOTE (rule.variableUnset): Text displayed in a tooltip
+# when the mouse is over a variable use (like "var(--something)"),
+# where the variable is not set.  the rule view.  The argument is the
+# variable name.
+rule.variableUnset=%S is not set
+
 # LOCALIZATION NOTE (ruleView.selectorHighlighter.tooltip): Text displayed in a
 # tooltip when the mouse is over a selector highlighter icon in the rule view.
 rule.selectorHighlighter.tooltip=Highlight all elements matching this selector
 
 # LOCALIZATION NOTE (rule.colorSwatch.tooltip): Text displayed in a tooltip
 # when the mouse is over a color swatch in the rule view.
 rule.colorSwatch.tooltip=Click to open the color picker, Shift+click to change the color format
 
--- a/dom/abort/tests/mochitest.ini
+++ b/dom/abort/tests/mochitest.ini
@@ -1,8 +1,9 @@
 [DEFAULT]
 support-files =
   file_abort_controller.html
   file_abort_controller_fetch.html
   worker_abort_controller_fetch.js
+  slow.sjs
 
 [test_abort_controller.html]
 [test_abort_controller_fetch.html]
copy from dom/tests/mochitest/fetch/slow.sjs
copy to dom/abort/tests/slow.sjs
--- a/dom/base/nsDocument.cpp
+++ b/dom/base/nsDocument.cpp
@@ -94,16 +94,17 @@
 #include "nsThreadUtils.h"
 #include "nsNodeInfoManager.h"
 #include "nsIFileChannel.h"
 #include "nsIMultiPartChannel.h"
 #include "nsIRefreshURI.h"
 #include "nsIWebNavigation.h"
 #include "nsIScriptError.h"
 #include "nsISimpleEnumerator.h"
+#include "nsIRequestContext.h"
 #include "nsStyleSheetService.h"
 
 #include "nsNetUtil.h"     // for NS_NewURI
 #include "nsIInputStreamChannel.h"
 #include "nsIAuthPrompt.h"
 #include "nsIAuthPrompt2.h"
 
 #include "nsIScriptSecurityManager.h"
@@ -2325,16 +2326,28 @@ nsDocument::ResetToURI(nsIURI *aURI, nsI
   if (aLoadGroup) {
     mDocumentLoadGroup = do_GetWeakReference(aLoadGroup);
     // there was an assertion here that aLoadGroup was not null.  This
     // is no longer valid: nsDocShell::SetDocument does not create a
     // load group, and it works just fine
 
     // XXXbz what does "just fine" mean exactly?  And given that there
     // is no nsDocShell::SetDocument, what is this talking about?
+
+    // Inform the associated request context about this load start so
+    // any of its internal load progress flags gets reset.
+    nsCOMPtr<nsIRequestContextService> rcsvc =
+      do_GetService("@mozilla.org/network/request-context-service;1");
+    if (rcsvc) {
+      nsCOMPtr<nsIRequestContext> rc;
+      rcsvc->GetRequestContextFromLoadGroup(aLoadGroup, getter_AddRefs(rc));
+      if (rc) {
+        rc->BeginLoad();
+      }
+    }
   }
 
   mLastModified.Truncate();
   // XXXbz I guess we're assuming that the caller will either pass in
   // a channel with a useful type or call SetContentType?
   SetContentTypeInternal(EmptyCString());
   mContentLanguage.Truncate();
   mBaseTarget.Truncate();
--- a/dom/fetch/FetchDriver.cpp
+++ b/dom/fetch/FetchDriver.cpp
@@ -374,16 +374,19 @@ FetchDriver::HttpFetch()
     mRequest->Headers()->GetUnsafeHeaders(unsafeHeaders);
     nsCOMPtr<nsILoadInfo> loadInfo = chan->GetLoadInfo();
     if (loadInfo) {
       loadInfo->SetCorsPreflightInfo(unsafeHeaders, false);
     }
   }
 
   if (mIsTrackingFetch && nsContentUtils::IsLowerNetworkPriority()) {
+    cos->AddClassFlags(nsIClassOfService::Throttleable |
+                       nsIClassOfService::Tail);
+
     nsCOMPtr<nsISupportsPriority> p = do_QueryInterface(chan);
     if (p) {
       p->SetPriority(nsISupportsPriority::PRIORITY_LOWEST);
     }
   }
 
   rv = chan->AsyncOpen2(this);
   NS_ENSURE_SUCCESS(rv, rv);
--- a/dom/html/TextTrackManager.cpp
+++ b/dom/html/TextTrackManager.cpp
@@ -500,16 +500,24 @@ public:
 
   NS_IMETHOD Run() {
     WEBVTT_LOGV("SimpleTextTrackEvent cue %p mName %s mTime %lf",
       mCue.get(), NS_ConvertUTF16toUTF8(mName).get(), mTime);
     mCue->DispatchTrustedEvent(mName);
     return NS_OK;
   }
 
+  void Dispatch() {
+    if (nsCOMPtr<nsIGlobalObject> global = mCue->GetOwnerGlobal()) {
+      global->Dispatch(TaskCategory::Other, do_AddRef(this));
+    } else {
+      NS_DispatchToMainThread(do_AddRef(this));
+    }
+  }
+
 private:
   nsString mName;
   double mTime;
   TextTrack* mTrack;
   RefPtr<TextTrackCue> mCue;
 };
 
 class CompareSimpleTextTrackEvents {
@@ -845,17 +853,17 @@ TextTrackManager::TimeMarchesOn()
         CompareSimpleTextTrackEvents(mMediaElement));
       affectedTracks.AddTextTrack(cue->GetTrack(), CompareTextTracks(mMediaElement));
     }
     cue->SetActive(true);
   }
 
   // Fire the eventList
   for (uint32_t i = 0; i < eventList.Length(); ++i) {
-    NS_DispatchToMainThread(eventList[i].forget());
+    eventList[i]->Dispatch();
   }
 
   // Step 16.
   for (uint32_t i = 0; i < affectedTracks.Length(); ++i) {
     TextTrack* ttrack = affectedTracks[i];
     if (ttrack) {
       ttrack->DispatchAsyncTrustedEvent(NS_LITERAL_STRING("cuechange"));
       HTMLTrackElement* trackElement = ttrack->GetTrackElement();
--- a/dom/ipc/ContentChild.cpp
+++ b/dom/ipc/ContentChild.cpp
@@ -3628,16 +3628,19 @@ ContentChild::GetSpecificMessageEventTar
 {
   switch(aMsg.type()) {
     case PJavaScript::Msg_DropTemporaryStrongReferences__ID:
     case PJavaScript::Msg_DropObject__ID:
     case PContent::Msg_NotifyVisited__ID:
     case PContent::Msg_DataStoragePut__ID:
     case PContent::Msg_DataStorageRemove__ID:
     case PContent::Msg_DataStorageClear__ID:
+    case PContent::Msg_PIPCBlobInputStreamConstructor__ID:
+    case PContent::Msg_BlobURLRegistration__ID:
+    case PContent::Msg_BlobURLUnregistration__ID:
       return do_AddRef(SystemGroup::EventTargetFor(TaskCategory::Other));
     default:
       return nullptr;
   }
 }
 
 #ifdef NIGHTLY_BUILD
 void
--- a/dom/quota/ActorsParent.cpp
+++ b/dom/quota/ActorsParent.cpp
@@ -3067,17 +3067,18 @@ QuotaObject::MaybeUpdateSize(int64_t aSi
 
     NS_ASSERTION(mOriginInfo, "How come?!");
 
     for (DirectoryLockImpl* lock : locks) {
       MOZ_ASSERT(!lock->GetPersistenceType().IsNull());
       MOZ_ASSERT(!lock->GetGroup().IsEmpty());
       MOZ_ASSERT(lock->GetOriginScope().IsOrigin());
       MOZ_ASSERT(!lock->GetOriginScope().GetOrigin().IsEmpty());
-      MOZ_ASSERT(lock->GetOriginScope().GetOrigin() != mOriginInfo->mOrigin,
+      MOZ_ASSERT(!(lock->GetOriginScope().GetOrigin() == mOriginInfo->mOrigin &&
+                   lock->GetPersistenceType().Value() == groupInfo->mPersistenceType),
                  "Deleted itself!");
 
       quotaManager->LockedRemoveQuotaForOrigin(
                                              lock->GetPersistenceType().Value(),
                                              lock->GetGroup(),
                                              lock->GetOriginScope().GetOrigin());
     }
 
--- a/dom/script/ScriptLoader.cpp
+++ b/dom/script/ScriptLoader.cpp
@@ -132,20 +132,23 @@ ScriptLoader::ScriptLoader(nsIDocument *
     mEnabled(true),
     mDeferEnabled(false),
     mDocumentParsingDone(false),
     mBlockingDOMContentLoaded(false),
     mLoadEventFired(false),
     mGiveUpEncoding(false),
     mReporter(new ConsoleReportCollector())
 {
+  LOG(("ScriptLoader::ScriptLoader %p", this));
 }
 
 ScriptLoader::~ScriptLoader()
 {
+  LOG(("ScriptLoader::~ScriptLoader %p", this));
+
   mObservers.Clear();
 
   if (mParserBlockingRequest) {
     mParserBlockingRequest->FireScriptAvailable(NS_ERROR_ABORT);
   }
 
   for (ScriptLoadRequest* req = mXSLTRequests.getFirst(); req;
        req = req->getNext()) {
@@ -1050,25 +1053,42 @@ ScriptLoader::StartLoad(ScriptLoadReques
       cic->PreferAlternativeDataType(kNullMimeType);
     }
   }
 
   nsIScriptElement* script = aRequest->mElement;
   bool async = script ? script->GetScriptAsync() : aRequest->mPreloadAsAsync;
   bool defer = script ? script->GetScriptDeferred() : aRequest->mPreloadAsDefer;
 
+  LOG(("ScriptLoadRequest (%p): async=%d defer=%d tracking=%d",
+       aRequest, async, defer, aRequest->IsTracking()));
+
   nsCOMPtr<nsIClassOfService> cos(do_QueryInterface(channel));
   if (cos) {
     if (aRequest->mScriptFromHead && !async && !defer) {
       // synchronous head scripts block loading of most other non js/css
-      // content such as images
+      // content such as images, Leader implicitely disallows tailing
       cos->AddClassFlags(nsIClassOfService::Leader);
-    } else if (!defer) {
-      // other scripts are neither blocked nor prioritized unless marked deferred
+    } else if (defer && !async) {
+      // head/body deferred scripts are blocked by leaders but are not
+      // allowed tailing because they block DOMContentLoaded
+      cos->AddClassFlags(nsIClassOfService::TailForbidden);
+    } else {
+      // other scripts (=body sync or head/body async) are neither blocked
+      // nor prioritized
       cos->AddClassFlags(nsIClassOfService::Unblocked);
+
+      if (async) {
+        // async scripts are allowed tailing, since those and only those
+        // don't block DOMContentLoaded; this flag doesn't enforce tailing,
+        // just overweights the Unblocked flag when the channel is found
+        // to be a thrird-party tracker and thus set the Tail flag to engage
+        // tailing.
+        cos->AddClassFlags(nsIClassOfService::TailAllowed);
+      }
     }
   }
 
   nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(channel));
   if (httpChannel) {
     // HTTP content negotation has little value in this context.
     rv = httpChannel->SetRequestHeader(NS_LITERAL_CSTRING("Accept"),
                                        NS_LITERAL_CSTRING("*/*"),
@@ -1201,18 +1221,21 @@ CSPAllowsInlineScript(nsIScriptElement* 
 
 ScriptLoadRequest*
 ScriptLoader::CreateLoadRequest(ScriptKind aKind,
                                 nsIScriptElement* aElement,
                                 uint32_t aVersion, CORSMode aCORSMode,
                                 const SRIMetadata& aIntegrity)
 {
   if (aKind == ScriptKind::Classic) {
-    return new ScriptLoadRequest(aKind, aElement, aVersion, aCORSMode,
+    ScriptLoadRequest* slr = new ScriptLoadRequest(aKind, aElement, aVersion, aCORSMode,
                                  aIntegrity);
+
+    LOG(("ScriptLoader %p creates ScriptLoadRequest %p", this, slr));
+    return slr;
   }
 
   MOZ_ASSERT(aKind == ScriptKind::Module);
   return new ModuleLoadRequest(aElement, aVersion, aCORSMode, aIntegrity, this);
 }
 
 bool
 ScriptLoader::ProcessScriptElement(nsIScriptElement* aElement)
--- a/dom/tests/mochitest/fetch/test_fetch_observer.html
+++ b/dom/tests/mochitest/fetch/test_fetch_observer.html
@@ -8,17 +8,18 @@
   <title>Test FetchObserver</title>
   <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
 </head>
 <body>
 <script class="testbody" type="text/javascript">
 
 SpecialPowers.pushPrefEnv({"set": [["dom.fetchObserver.enabled", true ],
-                                   ["dom.abortController.enabled", true ]]}, () => {
+                                   ["dom.abortController.enabled", true ],
+                                   ["dom.abortController.fetch.enabled", true ]]}, () => {
   let ifr = document.createElement('iframe');
   ifr.src = "file_fetch_observer.html";
   document.body.appendChild(ifr);
 
   onmessage = function(e) {
     if (e.data.type == "finish") {
       SimpleTest.finish();
       return;
--- a/dom/tests/mochitest/general/test_interfaces.js
+++ b/dom/tests/mochitest/general/test_interfaces.js
@@ -97,19 +97,19 @@ var legacyMozPrefixedInterfaces =
   ];
 // IMPORTANT: Do not change the list above without review from a DOM peer,
 //            except to remove items from it!
 
 // IMPORTANT: Do not change the list below without review from a DOM peer!
 var interfaceNamesInGlobalScope =
   [
 // IMPORTANT: Do not change this list without review from a DOM peer!
-    "AbortController",
+    {name: "AbortController", nightly: true},
 // IMPORTANT: Do not change this list without review from a DOM peer!
-    "AbortSignal",
+    {name: "AbortSignal", nightly: true},
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "AnalyserNode",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     {name: "Animation"},
 // IMPORTANT: Do not change this list without review from a DOM peer!
     {name: "AnimationEffectReadOnly", release: false},
 // IMPORTANT: Do not change this list without review from a DOM peer!
     {name: "AnimationEffectTiming", release: false},
--- a/dom/workers/test/serviceworkers/test_serviceworker_interfaces.js
+++ b/dom/workers/test/serviceworkers/test_serviceworker_interfaces.js
@@ -75,19 +75,19 @@ var ecmaGlobals =
   ];
 // IMPORTANT: Do not change the list above without review from
 //            a JavaScript Engine peer!
 
 // IMPORTANT: Do not change the list below without review from a DOM peer!
 var interfaceNamesInGlobalScope =
   [
 // IMPORTANT: Do not change this list without review from a DOM peer!
-    "AbortController",
+    { name: "AbortController", nightly: true },
 // IMPORTANT: Do not change this list without review from a DOM peer!
-    "AbortSignal",
+    { name: "AbortSignal", nightly: true },
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "Blob",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "BroadcastChannel",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "Cache",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "CacheStorage",
--- a/dom/workers/test/test_worker_interfaces.js
+++ b/dom/workers/test/test_worker_interfaces.js
@@ -75,19 +75,19 @@ var ecmaGlobals =
   ];
 // IMPORTANT: Do not change the list above without review from
 //            a JavaScript Engine peer!
 
 // IMPORTANT: Do not change the list below without review from a DOM peer!
 var interfaceNamesInGlobalScope =
   [
 // IMPORTANT: Do not change this list without review from a DOM peer!
-    "AbortController",
+    { name: "AbortController", nightly: true },
 // IMPORTANT: Do not change this list without review from a DOM peer!
-    "AbortSignal",
+    { name: "AbortSignal", nightly: true },
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "Blob",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "BroadcastChannel",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "Cache",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "CacheStorage",
--- a/dom/xhr/XMLHttpRequestMainThread.cpp
+++ b/dom/xhr/XMLHttpRequestMainThread.cpp
@@ -40,16 +40,17 @@
 #include "nsReadableUtils.h"
 
 #include "nsIURI.h"
 #include "nsILoadGroup.h"
 #include "nsNetUtil.h"
 #include "nsStringStream.h"
 #include "nsIAuthPrompt.h"
 #include "nsIAuthPrompt2.h"
+#include "nsIClassOfService.h"
 #include "nsIOutputStream.h"
 #include "nsISupportsPrimitives.h"
 #include "nsISupportsPriority.h"
 #include "nsIInterfaceRequestorUtils.h"
 #include "nsStreamUtils.h"
 #include "nsThreadUtils.h"
 #include "nsIUploadChannel.h"
 #include "nsIUploadChannel2.h"
@@ -2615,22 +2616,29 @@ XMLHttpRequestMainThread::MaybeLowerChan
   if (!nsJSUtils::GetCallingLocation(cx, fileNameString)) {
     return;
   }
 
   if (!doc->IsScriptTracking(fileNameString)) {
     return;
   }
 
+  nsCOMPtr<nsIClassOfService> cos = do_QueryInterface(mChannel);
+  if (cos) {
+    // Adding TailAllowed to overrule the Unblocked flag, but to preserve
+    // the effect of Unblocked when tailing is off.
+    cos->AddClassFlags(nsIClassOfService::Throttleable |
+                       nsIClassOfService::Tail |
+                       nsIClassOfService::TailAllowed);
+  }
+
   nsCOMPtr<nsISupportsPriority> p = do_QueryInterface(mChannel);
-  if (!p) {
-    return;
-  }
-
-  p->SetPriority(nsISupportsPriority::PRIORITY_LOWEST);
+  if (p) {
+    p->SetPriority(nsISupportsPriority::PRIORITY_LOWEST);
+  }
 }
 
 nsresult
 XMLHttpRequestMainThread::InitiateFetch(nsIInputStream* aUploadStream,
                                         int64_t aUploadLength,
                                         nsACString& aUploadContentType)
 {
   nsresult rv;
--- a/gfx/gl/GLLibraryEGL.cpp
+++ b/gfx/gl/GLLibraryEGL.cpp
@@ -52,16 +52,18 @@ static const char* sEGLExtensionNames[] 
     "EGL_ANGLE_platform_angle_d3d",
     "EGL_ANGLE_d3d_share_handle_client_buffer",
     "EGL_KHR_create_context",
     "EGL_KHR_stream",
     "EGL_KHR_stream_consumer_gltexture",
     "EGL_EXT_device_query",
     "EGL_NV_stream_consumer_gltexture_yuv",
     "EGL_ANGLE_stream_producer_d3d_texture_nv12",
+    "EGL_ANGLE_device_creation",
+    "EGL_ANGLE_device_creation_d3d11",
 };
 
 #if defined(ANDROID)
 
 static PRLibrary* LoadApitraceLibrary()
 {
     // Initialization of gfx prefs here is only needed during the unit tests...
     gfxPrefs::GetSingleton();
@@ -648,16 +650,28 @@ GLLibraryEGL::EnsureInitialized(bool for
             END_OF_SYMBOLS
         };
         if (!fnLoadSymbols(nvStreamSymbols)) {
             NS_ERROR("EGL supports ANGLE_stream_producer_d3d_texture_nv12 without exposing its functions!");
             MarkExtensionUnsupported(ANGLE_stream_producer_d3d_texture_nv12);
         }
     }
 
+    if (IsExtensionSupported(ANGLE_device_creation)) {
+        const GLLibraryLoader::SymLoadStruct createDeviceSymbols[] = {
+            SYMBOL(CreateDeviceANGLE),
+            SYMBOL(ReleaseDeviceANGLE),
+            END_OF_SYMBOLS
+        };
+        if (!fnLoadSymbols(createDeviceSymbols)) {
+            NS_ERROR("EGL supports ANGLE_device_creation without exposing its functions!");
+            MarkExtensionUnsupported(ANGLE_device_creation);
+        }
+    }
+
     mInitialized = true;
     reporter.SetSuccessful();
     return true;
 }
 
 #undef SYMBOL
 #undef END_OF_SYMBOLS
 
--- a/gfx/gl/GLLibraryEGL.h
+++ b/gfx/gl/GLLibraryEGL.h
@@ -106,16 +106,18 @@ public:
         ANGLE_platform_angle_d3d,
         ANGLE_d3d_share_handle_client_buffer,
         KHR_create_context,
         KHR_stream,
         KHR_stream_consumer_gltexture,
         EXT_device_query,
         NV_stream_consumer_gltexture_yuv,
         ANGLE_stream_producer_d3d_texture_nv12,
+        ANGLE_device_creation,
+        ANGLE_device_creation_d3d11,
         Extensions_Max
     };
 
     bool IsExtensionSupported(EGLExtensions aKnownExtension) const {
         return mAvailableExtensions[aKnownExtension];
     }
 
     void MarkExtensionUnsupported(EGLExtensions aKnownExtension) {
@@ -317,16 +319,23 @@ public:
 
     // ANGLE_stream_producer_d3d_texture_nv12
     EGLBoolean  fCreateStreamProducerD3DTextureNV12ANGLE(EGLDisplay dpy, EGLStreamKHR stream, const EGLAttrib* attrib_list) const
         WRAP(   fCreateStreamProducerD3DTextureNV12ANGLE(dpy, stream, attrib_list) )
 
     EGLBoolean  fStreamPostD3DTextureNV12ANGLE(EGLDisplay dpy, EGLStreamKHR stream, void* texture, const EGLAttrib* attrib_list) const
         WRAP(   fStreamPostD3DTextureNV12ANGLE(dpy, stream, texture, attrib_list) )
 
+    // ANGLE_device_creation
+    EGLDeviceEXT fCreateDeviceANGLE(EGLint device_type, void* native_device, const EGLAttrib* attrib_list) const
+        WRAP(   fCreateDeviceANGLE(device_type, native_device, attrib_list) )
+
+    EGLBoolean fReleaseDeviceANGLE(EGLDeviceEXT device)
+        WRAP(   fReleaseDeviceANGLE(device) )
+
     void           fANGLEPlatformInitialize(angle::Platform* platform) const
         VOID_WRAP( fANGLEPlatformInitialize(platform) )
 
     void fANGLEPlatformShutdown() const
         VOID_WRAP( fANGLEPlatformShutdown() )
 
 #undef WRAP
 #undef VOID_WRAP
@@ -476,16 +485,22 @@ private:
         // ANGLE_stream_producer_d3d_texture_nv12
         EGLBoolean (GLAPIENTRY * fCreateStreamProducerD3DTextureNV12ANGLE)(EGLDisplay dpy,
                                                                            EGLStreamKHR stream,
                                                                            const EGLAttrib* attrib_list);
         EGLBoolean (GLAPIENTRY * fStreamPostD3DTextureNV12ANGLE)(EGLDisplay dpy,
                                                                  EGLStreamKHR stream,
                                                                  void* texture,
                                                                  const EGLAttrib* attrib_list);
+        // ANGLE_device_creation
+        EGLDeviceEXT (GLAPIENTRY * fCreateDeviceANGLE) (EGLint device_type,
+                                                        void* native_device,
+                                                        const EGLAttrib* attrib_list);
+        EGLBoolean (GLAPIENTRY * fReleaseDeviceANGLE) (EGLDeviceEXT device);
+
         void       (GLAPIENTRY * fANGLEPlatformInitialize)(angle::Platform* platform);
         void       (GLAPIENTRY * fANGLEPlatformShutdown)();
     } mSymbols;
 
 private:
     bool mInitialized;
     PRLibrary* mEGLLibrary;
     EGLDisplay mEGLDisplay;
--- a/intl/locale/nsLanguageAtomService.cpp
+++ b/intl/locale/nsLanguageAtomService.cpp
@@ -59,21 +59,29 @@ nsLanguageAtomService::LookupCharSet(Not
   return NS_Atomize(group);
 }
 
 nsIAtom*
 nsLanguageAtomService::GetLocaleLanguage()
 {
   do {
     if (!mLocaleLanguage) {
-      nsAutoCString locale;
-      OSPreferences::GetInstance()->GetSystemLocale(locale);
+      AutoTArray<nsCString, 10> regionalPrefsLocales;
+      if (OSPreferences::GetInstance()->GetRegionalPrefsLocales(
+                                          regionalPrefsLocales)) {
+        // use lowercase for all language atoms
+        ToLowerCase(regionalPrefsLocales[0]);
+        mLocaleLanguage = NS_Atomize(regionalPrefsLocales[0]);
+      } else {
+        nsAutoCString locale;
+        OSPreferences::GetInstance()->GetSystemLocale(locale);
 
-      ToLowerCase(locale); // use lowercase for all language atoms
-      mLocaleLanguage = NS_Atomize(locale);
+        ToLowerCase(locale); // use lowercase for all language atoms
+        mLocaleLanguage = NS_Atomize(locale);
+      }
     }
   } while (0);
 
   return mLocaleLanguage;
 }
 
 nsIAtom*
 nsLanguageAtomService::GetLanguageGroup(nsIAtom *aLanguage, bool* aNeedsToCache)
--- a/ipc/mscom/Interceptor.cpp
+++ b/ipc/mscom/Interceptor.cpp
@@ -188,31 +188,41 @@ Interceptor::GetClassForHandler(DWORD aD
       aDestContext == MSHCTX_DIFFERENTMACHINE) {
     return E_INVALIDARG;
   }
 
   MOZ_ASSERT(mEventSink);
   return mEventSink->GetHandler(WrapNotNull(aHandlerClsid));
 }
 
+REFIID
+Interceptor::MarshalAs(REFIID aIid) const
+{
+#if defined(MOZ_MSCOM_REMARSHAL_NO_HANDLER)
+  return IsCallerExternalProcess() ? aIid : mEventSink->MarshalAs(aIid);
+#else
+  return mEventSink->MarshalAs(aIid);
+#endif // defined(MOZ_MSCOM_REMARSHAL_NO_HANDLER)
+}
+
 HRESULT
 Interceptor::GetUnmarshalClass(REFIID riid, void* pv, DWORD dwDestContext,
                                void* pvDestContext, DWORD mshlflags,
                                CLSID* pCid)
 {
-  return mStdMarshal->GetUnmarshalClass(riid, pv, dwDestContext, pvDestContext,
-                                        mshlflags, pCid);
+  return mStdMarshal->GetUnmarshalClass(MarshalAs(riid), pv, dwDestContext,
+                                        pvDestContext, mshlflags, pCid);
 }
 
 HRESULT
 Interceptor::GetMarshalSizeMax(REFIID riid, void* pv, DWORD dwDestContext,
                                void* pvDestContext, DWORD mshlflags,
                                DWORD* pSize)
 {
-  HRESULT hr = mStdMarshal->GetMarshalSizeMax(riid, pv, dwDestContext,
+  HRESULT hr = mStdMarshal->GetMarshalSizeMax(MarshalAs(riid), pv, dwDestContext,
                                               pvDestContext, mshlflags, pSize);
   if (FAILED(hr)) {
     return hr;
   }
 
   DWORD payloadSize = 0;
   hr = mEventSink->GetHandlerPayloadSize(WrapNotNull(&payloadSize));
   if (hr == E_NOTIMPL) {
@@ -415,17 +425,17 @@ Interceptor::GetInitialInterceptorForIID
   // the main thread, creating potential for deadlocks.
   aLock.Unlock();
 
   // Now we transfer aTarget's ownership into mInterceptorMap.
   mInterceptorMap.AppendElement(MapEntry(aTargetIid,
                                          unkInterceptor,
                                          aTarget.release()));
 
-  if (mEventSink->MarshalAs(aTargetIid) == aTargetIid) {
+  if (MarshalAs(aTargetIid) == aTargetIid) {
     return unkInterceptor->QueryInterface(aTargetIid, aOutInterceptor);
   }
 
   return GetInterceptorForIID(aTargetIid, aOutInterceptor);
 }
 
 /**
  * This method contains the core guts of the handling of QueryInterface calls
@@ -444,17 +454,17 @@ Interceptor::GetInterceptorForIID(REFIID
 
   if (aIid == IID_IUnknown) {
     // Special case: When we see IUnknown, we just provide a reference to this
     RefPtr<IInterceptor> intcpt(this);
     intcpt.forget(aOutInterceptor);
     return S_OK;
   }
 
-  REFIID interceptorIid = mEventSink->MarshalAs(aIid);
+  REFIID interceptorIid = MarshalAs(aIid);
 
   RefPtr<IUnknown> unkInterceptor;
   IUnknown* interfaceForQILog = nullptr;
 
   // (1) Check to see if we already have an existing interceptor for
   // interceptorIid.
 
   { // Scope for lock
--- a/ipc/mscom/Interceptor.h
+++ b/ipc/mscom/Interceptor.h
@@ -124,16 +124,17 @@ private:
                                       REFIID aTargetIid,
                                       STAUniquePtr<IUnknown> aTarget,
                                       void** aOutInterface);
   MapEntry* Lookup(REFIID aIid);
   HRESULT QueryInterfaceTarget(REFIID aIid, void** aOutput);
   HRESULT ThreadSafeQueryInterface(REFIID aIid,
                                    IUnknown** aOutInterface) override;
   HRESULT CreateInterceptor(REFIID aIid, IUnknown* aOuter, IUnknown** aOutput);
+  REFIID MarshalAs(REFIID aIid) const;
 
 private:
   InterceptorTargetPtr<IUnknown>  mTarget;
   RefPtr<IInterceptorSink>  mEventSink;
   mozilla::Mutex            mMutex; // Guards mInterceptorMap
   // Using a nsTArray since the # of interfaces is not going to be very high
   nsTArray<MapEntry>        mInterceptorMap;
   RefPtr<IUnknown>          mStdMarshalUnk;
--- a/ipc/mscom/PassthruProxy.h
+++ b/ipc/mscom/PassthruProxy.h
@@ -3,16 +3,17 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef mozilla_mscom_PassthruProxy_h
 #define mozilla_mscom_PassthruProxy_h
 
 #include "mozilla/Atomics.h"
+#include "mozilla/mscom/Ptr.h"
 #include "mozilla/NotNull.h"
 
 #include <objbase.h>
 
 namespace mozilla {
 namespace mscom {
 namespace detail {
 
--- a/ipc/mscom/ProxyStream.cpp
+++ b/ipc/mscom/ProxyStream.cpp
@@ -85,17 +85,21 @@ ProxyStream::ProxyStream(REFIID aIID, co
 #endif // defined(ACCESSIBILITY) && defined(MOZ_CRASHREPORTER)
 
   HRESULT unmarshalResult = S_OK;
 
   // We need to convert to an interface here otherwise we mess up const
   // correctness with IPDL. We'll request an IUnknown and then QI the
   // actual interface later.
 
+#if defined(ACCESSIBILITY) && defined(MOZ_CRASHREPORTER)
   auto marshalFn = [this, &strActCtx, &unmarshalResult, &aIID]() -> void
+#else
+  auto marshalFn = [this, &unmarshalResult, &aIID]() -> void
+#endif // defined(ACCESSIBILITY) && defined(MOZ_CRASHREPORTER)
   {
 #if defined(ACCESSIBILITY) && defined(MOZ_CRASHREPORTER)
     auto curActCtx = ActivationContext::GetCurrent();
     if (curActCtx.isOk()) {
       strActCtx.AppendPrintf("0x%p", curActCtx.unwrap());
     } else {
       strActCtx.AppendPrintf("HRESULT 0x%08X", curActCtx.unwrapErr());
     }
--- a/ipc/mscom/RegistrationAnnotator.cpp
+++ b/ipc/mscom/RegistrationAnnotator.cpp
@@ -44,16 +44,17 @@ static const char16_t kSoftwareClasses[]
 static const char16_t kInterface[] = u"\\Interface\\";
 static const char16_t kDefaultValue[] = u"";
 static const char16_t kThreadingModel[] = u"ThreadingModel";
 static const char16_t kBackslash[] = u"\\";
 static const char16_t kFlags[] = u"FLAGS";
 static const char16_t kProxyStubClsid32[] = u"\\ProxyStubClsid32";
 static const char16_t kClsid[] = u"\\CLSID\\";
 static const char16_t kInprocServer32[] = u"\\InprocServer32";
+static const char16_t kInprocHandler32[] = u"\\InprocHandler32";
 static const char16_t kTypeLib[] = u"\\TypeLib";
 static const char16_t kVersion[] = u"Version";
 static const char16_t kWin32[] = u"Win32";
 static const char16_t kWin64[] = u"Win64";
 
 static bool
 GetStringValue(HKEY aBaseKey, const nsAString& aStrSubKey,
                const nsAString& aValueName, nsAString& aOutput)
@@ -166,16 +167,34 @@ AnnotateClsidRegistrationForHive(JSONWri
                            NS_ConvertUTF16toUTF8(pathToServerDll).get());
     }
   }
 
   nsAutoString apartment;
   if (GetStringValue(aHive, inprocServerSubkey, kThreadingModel, apartment)) {
     aJson.StringProperty("ThreadingModel", NS_ConvertUTF16toUTF8(apartment).get());
   }
+
+  nsAutoString inprocHandlerSubkey(clsidSubkey);
+  inprocHandlerSubkey.AppendLiteral(kInprocHandler32);
+  nsAutoString pathToHandlerDll;
+  if (GetStringValue(aHive, inprocHandlerSubkey, kDefaultValue, pathToHandlerDll)) {
+    aJson.StringProperty("HandlerPath", NS_ConvertUTF16toUTF8(pathToHandlerDll).get());
+    if (GetLoadedPath(pathToHandlerDll)) {
+      aJson.StringProperty("LoadedHandlerPath",
+                           NS_ConvertUTF16toUTF8(pathToHandlerDll).get());
+    }
+  }
+
+  nsAutoString handlerApartment;
+  if (GetStringValue(aHive, inprocHandlerSubkey, kThreadingModel,
+                     handlerApartment)) {
+    aJson.StringProperty("HandlerThreadingModel",
+                         NS_ConvertUTF16toUTF8(handlerApartment).get());
+  }
 }
 
 static void
 CheckTlbPath(JSONWriter& aJson, const nsAString& aTypelibPath)
 {
   const nsString& flatPath = PromiseFlatString(aTypelibPath);
   DWORD bufCharLen = ExpandEnvironmentStrings(flatPath.get(), nullptr, 0);
 
--- a/ipc/mscom/Utils.cpp
+++ b/ipc/mscom/Utils.cpp
@@ -237,41 +237,16 @@ GUIDToString(REFGUID aGuid, nsAString& a
   int result = StringFromGUID2(aGuid, char16ptr_t(aOutString.BeginWriting()), kBufLenWithNul);
   MOZ_ASSERT(result);
   if (result) {
     // Truncate the terminator
     aOutString.SetLength(result - 1);
   }
 }
 
-bool
-IsCallerExternalProcess()
-{
-  MOZ_ASSERT(XRE_IsContentProcess());
-
-  /**
-   * CoGetCallerTID() gives us the caller's thread ID when that thread resides
-   * in a single-threaded apartment. Since our chrome main thread does live
-   * inside an STA, we will therefore be able to check whether the caller TID
-   * equals our chrome main thread TID. This enables us to distinguish
-   * between our chrome thread vs other out-of-process callers. We check for
-   * S_FALSE to ensure that the caller is a different process from ours, which
-   * is the only scenario that we care about.
-   */
-  DWORD callerTid;
-  if (::CoGetCallerTID(&callerTid) != S_FALSE) {
-    return false;
-  }
-
-  // Now check whether the caller is our parent process main thread.
-  const DWORD parentMainTid =
-    dom::ContentChild::GetSingleton()->GetChromeMainThreadId();
-  return callerTid != parentMainTid;
-}
-
 #endif // defined(MOZILLA_INTERNAL_API)
 
 #if defined(ACCESSIBILITY)
 
 static bool
 IsVtableIndexFromParentInterface(TYPEATTR* aTypeAttr,
                                  unsigned long aVtableIndex)
 {
@@ -313,16 +288,41 @@ IsVtableIndexFromParentInterface(REFIID 
 
   typeInfo->ReleaseTypeAttr(typeAttr);
   return result;
 }
 
 #if defined(MOZILLA_INTERNAL_API)
 
 bool
+IsCallerExternalProcess()
+{
+  MOZ_ASSERT(XRE_IsContentProcess());
+
+  /**
+   * CoGetCallerTID() gives us the caller's thread ID when that thread resides
+   * in a single-threaded apartment. Since our chrome main thread does live
+   * inside an STA, we will therefore be able to check whether the caller TID
+   * equals our chrome main thread TID. This enables us to distinguish
+   * between our chrome thread vs other out-of-process callers. We check for
+   * S_FALSE to ensure that the caller is a different process from ours, which
+   * is the only scenario that we care about.
+   */
+  DWORD callerTid;
+  if (::CoGetCallerTID(&callerTid) != S_FALSE) {
+    return false;
+  }
+
+  // Now check whether the caller is our parent process main thread.
+  const DWORD parentMainTid =
+    dom::ContentChild::GetSingleton()->GetChromeMainThreadId();
+  return callerTid != parentMainTid;
+}
+
+bool
 IsInterfaceEqualToOrInheritedFrom(REFIID aInterface, REFIID aFrom,
                                   unsigned long aVtableIndexHint)
 {
   if (aInterface == aFrom) {
     return true;
   }
 
   // We expect this array to be length 1 but that is not guaranteed by the API.
--- a/ipc/mscom/Utils.h
+++ b/ipc/mscom/Utils.h
@@ -43,24 +43,25 @@ uint32_t CreateStream(const uint8_t* aBu
  *                  be positioned to point at the beginning of the proxy data.
  * @param aOutStream Outparam to receive the newly created stream.
  * @return HRESULT error code.
  */
 uint32_t CopySerializedProxy(IStream* aInStream, IStream** aOutStream);
 
 #if defined(MOZILLA_INTERNAL_API)
 void GUIDToString(REFGUID aGuid, nsAString& aOutString);
-bool IsCallerExternalProcess();
 #endif // defined(MOZILLA_INTERNAL_API)
 
 #if defined(ACCESSIBILITY)
 bool IsVtableIndexFromParentInterface(REFIID aInterface,
                                       unsigned long aVtableIndex);
 
 #if defined(MOZILLA_INTERNAL_API)
+bool IsCallerExternalProcess();
+
 bool IsInterfaceEqualToOrInheritedFrom(REFIID aInterface, REFIID aFrom,
                                        unsigned long aVtableIndexHint);
 #endif // defined(MOZILLA_INTERNAL_API)
 
 #endif // defined(ACCESSIBILITY)
 
 } // namespace mscom
 } // namespace mozilla
--- a/ipc/mscom/moz.build
+++ b/ipc/mscom/moz.build
@@ -6,34 +6,32 @@
 
 EXPORTS.mozilla.mscom += [
     'Aggregation.h',
     'AgileReference.h',
     'AsyncInvoker.h',
     'COMApartmentRegion.h',
     'COMPtrHolder.h',
     'EnsureMTA.h',
-    'FastMarshaler.h',
     'MainThreadClientInfo.h',
     'MainThreadRuntime.h',
     'Objref.h',
     'PassthruProxy.h',
     'ProxyStream.h',
     'Ptr.h',
     'Utils.h',
 ]
 
 SOURCES += [
     'VTableBuilder.c',
 ]
 
 UNIFIED_SOURCES += [
     'AgileReference.cpp',
     'EnsureMTA.cpp',
-    'FastMarshaler.cpp',
     'MainThreadClientInfo.cpp',
     'MainThreadRuntime.cpp',
     'Objref.cpp',
     'PassthruProxy.cpp',
     'ProxyStream.cpp',
     'Utils.cpp',
 ]
 
@@ -45,16 +43,17 @@ if CONFIG['MOZ_CRASHREPORTER']:
 if CONFIG['ACCESSIBILITY']:
     DIRS += [
         'oop',
     ]
 
     EXPORTS.mozilla.mscom += [
         'ActivationContext.h',
         'DispatchForwarder.h',
+        'FastMarshaler.h',
         'IHandlerProvider.h',
         'Interceptor.h',
         'InterceptorLog.h',
         'MainThreadHandoff.h',
         'MainThreadInvoker.h',
         'Registration.h',
         'SpinEvent.h',
         'StructStream.h',
@@ -66,16 +65,17 @@ if CONFIG['ACCESSIBILITY']:
         'Registration.cpp',
         'SpinEvent.cpp',
         'WeakRef.cpp',
     ]
 
     UNIFIED_SOURCES += [
         'ActivationContext.cpp',
         'DispatchForwarder.cpp',
+        'FastMarshaler.cpp',
         'InterceptorLog.cpp',
         'MainThreadHandoff.cpp',
         'MainThreadInvoker.cpp',
         'StructStream.cpp',
     ]
 
 LOCAL_INCLUDES += [
     '/xpcom/base',
--- a/ipc/mscom/oop/Handler.cpp
+++ b/ipc/mscom/oop/Handler.cpp
@@ -102,18 +102,18 @@ Handler::InternalRelease()
   return newRefCnt;
 }
 
 HRESULT
 Handler::GetUnmarshalClass(REFIID riid, void* pv, DWORD dwDestContext,
                            void* pvDestContext, DWORD mshlflags,
                            CLSID* pCid)
 {
-  return mUnmarshal->GetUnmarshalClass(riid, pv, dwDestContext, pvDestContext,
-                                       mshlflags, pCid);
+  return mUnmarshal->GetUnmarshalClass(MarshalAs(riid), pv, dwDestContext,
+                                       pvDestContext, mshlflags, pCid);
 }
 
 HRESULT
 Handler::GetMarshalSizeMax(REFIID riid, void* pv, DWORD dwDestContext,
                            void* pvDestContext, DWORD mshlflags,
                            DWORD* pSize)
 {
   if (!pSize) {
@@ -182,22 +182,19 @@ Handler::MarshalInterface(IStream* pStm,
   ULARGE_INTEGER objrefPos;
 
   // Save the current position as it points to the location where the OBJREF
   // will be written.
   hr = pStm->Seek(seekTo, STREAM_SEEK_CUR, &objrefPos);
   if (FAILED(hr)) {
     return hr;
   }
+#endif // defined(MOZ_MSCOM_REMARSHAL_NO_HANDLER)
 
-  // When marshaling without a handler, we just use the riid as passed in.
-  REFIID marshalAs = riid;
-#else
   REFIID marshalAs = MarshalAs(riid);
-#endif // defined(MOZ_MSCOM_REMARSHAL_NO_HANDLER)
 
   hr = mInnerUnk->QueryInterface(marshalAs, getter_AddRefs(unkToMarshal));
   if (FAILED(hr)) {
     return hr;
   }
 
   hr = mUnmarshal->MarshalInterface(pStm, marshalAs, unkToMarshal.get(),
                                     dwDestContext, pvDestContext, mshlflags);
@@ -261,44 +258,47 @@ Handler::DisconnectObject(DWORD dwReserv
 {
   return mUnmarshal->DisconnectObject(dwReserved);
 }
 
 template <size_t N>
 static HRESULT
 BuildClsidPath(wchar_t (&aPath)[N], REFCLSID aClsid)
 {
-  const wchar_t kClsid[] = {L'C', L'L', L'S', L'I', L'D', L'\\'};
+  const wchar_t kSubkey[] = L"SOFTWARE\\Classes\\CLSID\\";
+
+  // We exclude kSubkey's null terminator in the length because we include
+  // the stringified GUID's null terminator.
+  constexpr uint32_t kSubkeyLen = mozilla::ArrayLength(kSubkey) - 1;
+
   const size_t kReqdGuidLen = 39;
-  static_assert(N >= kReqdGuidLen + mozilla::ArrayLength(kClsid),
-                "aPath array is too short");
-  if (wcsncpy_s(aPath, kClsid, mozilla::ArrayLength(kClsid))) {
+  static_assert(N >= kReqdGuidLen + kSubkeyLen, "aPath array is too short");
+  if (wcsncpy_s(aPath, kSubkey, kSubkeyLen)) {
     return E_INVALIDARG;
   }
 
   int guidConversionResult =
-    StringFromGUID2(aClsid, &aPath[mozilla::ArrayLength(kClsid)],
-                    N - mozilla::ArrayLength(kClsid));
+    StringFromGUID2(aClsid, &aPath[kSubkeyLen], N - kSubkeyLen);
   if (!guidConversionResult) {
     return E_INVALIDARG;
   }
 
   return S_OK;
 }
 
 HRESULT
 Handler::Unregister(REFCLSID aClsid)
 {
   wchar_t path[256] = {};
   HRESULT hr = BuildClsidPath(path, aClsid);
   if (FAILED(hr)) {
     return hr;
   }
 
-  hr = HRESULT_FROM_WIN32(SHDeleteKey(HKEY_CLASSES_ROOT, path));
+  hr = HRESULT_FROM_WIN32(SHDeleteKey(HKEY_LOCAL_MACHINE, path));
   if (FAILED(hr)) {
     return hr;
   }
 
   return S_OK;
 }
 
 HRESULT
@@ -307,30 +307,30 @@ Handler::Register(REFCLSID aClsid)
   wchar_t path[256] = {};
   HRESULT hr = BuildClsidPath(path, aClsid);
   if (FAILED(hr)) {
     return hr;
   }
 
   HKEY rawClsidKey;
   DWORD disposition;
-  LONG result = RegCreateKeyEx(HKEY_CLASSES_ROOT, path, 0, nullptr,
+  LONG result = RegCreateKeyEx(HKEY_LOCAL_MACHINE, path, 0, nullptr,
                                REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS,
                                nullptr, &rawClsidKey, &disposition);
   if (result != ERROR_SUCCESS) {
     return HRESULT_FROM_WIN32(result);
   }
   nsAutoRegKey clsidKey(rawClsidKey);
 
   if (wcscat_s(path, L"\\InprocHandler32")) {
     return E_UNEXPECTED;
   }
 
   HKEY rawInprocHandlerKey;
-  result = RegCreateKeyEx(HKEY_CLASSES_ROOT, path, 0, nullptr,
+  result = RegCreateKeyEx(HKEY_LOCAL_MACHINE, path, 0, nullptr,
                           REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS,
                           nullptr, &rawInprocHandlerKey, &disposition);
   if (result != ERROR_SUCCESS) {
     Unregister(aClsid);
     return HRESULT_FROM_WIN32(result);
   }
   nsAutoRegKey inprocHandlerKey(rawInprocHandlerKey);
 
--- a/js/src/jit/BacktrackingAllocator.h
+++ b/js/src/jit/BacktrackingAllocator.h
@@ -3,22 +3,30 @@
  * 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 jit_BacktrackingAllocator_h
 #define jit_BacktrackingAllocator_h
 
 #include "mozilla/Array.h"
+#include "mozilla/Attributes.h"
 
 #include "ds/PriorityQueue.h"
 #include "ds/SplayTree.h"
 #include "jit/RegisterAllocator.h"
 #include "jit/StackSlotAllocator.h"
 
+// Gives better traces in Nightly/debug builds (could be EARLY_BETA_OR_EARLIER)
+#if defined(NIGHTLY_BUILD) || defined(DEBUG)
+#define AVOID_INLINE_FOR_DEBUGGING MOZ_NEVER_INLINE
+#else
+#define AVOID_INLINE_FOR_DEBUGGING
+#endif
+
 // Backtracking priority queue based register allocator based on that described
 // in the following blog post:
 //
 // http://blog.llvm.org/2011/09/greedy-register-allocation-in-llvm-30.html
 
 namespace js {
 namespace jit {
 
@@ -750,31 +758,31 @@ class BacktrackingAllocator : protected 
     MOZ_MUST_USE bool computeRequirement(LiveBundle* bundle, Requirement *prequirement,
                                          Requirement *phint);
     MOZ_MUST_USE bool tryAllocateRegister(PhysicalRegister& r, LiveBundle* bundle, bool* success,
                                           bool* pfixed, LiveBundleVector& conflicting);
     MOZ_MUST_USE bool evictBundle(LiveBundle* bundle);
     MOZ_MUST_USE bool splitAndRequeueBundles(LiveBundle* bundle,
                                              const LiveBundleVector& newBundles);
     MOZ_MUST_USE bool spill(LiveBundle* bundle);
-    MOZ_MUST_USE bool tryAllocatingRegistersForSpillBundles();
+    AVOID_INLINE_FOR_DEBUGGING MOZ_MUST_USE bool tryAllocatingRegistersForSpillBundles();
 
     bool isReusedInput(LUse* use, LNode* ins, bool considerCopy);
     bool isRegisterUse(UsePosition* use, LNode* ins, bool considerCopy = false);
     bool isRegisterDefinition(LiveRange* range);
     MOZ_MUST_USE bool pickStackSlot(SpillSet* spill);
     MOZ_MUST_USE bool insertAllRanges(LiveRangeSet& set, LiveBundle* bundle);
 
     // Reification methods.
-    MOZ_MUST_USE bool pickStackSlots();
-    MOZ_MUST_USE bool resolveControlFlow();
-    MOZ_MUST_USE bool reifyAllocations();
-    MOZ_MUST_USE bool populateSafepoints();
-    MOZ_MUST_USE bool annotateMoveGroups();
-    MOZ_MUST_USE bool deadRange(LiveRange* range);
+    AVOID_INLINE_FOR_DEBUGGING MOZ_MUST_USE bool pickStackSlots();
+    AVOID_INLINE_FOR_DEBUGGING MOZ_MUST_USE bool resolveControlFlow();
+    AVOID_INLINE_FOR_DEBUGGING MOZ_MUST_USE bool reifyAllocations();
+    AVOID_INLINE_FOR_DEBUGGING MOZ_MUST_USE bool populateSafepoints();
+    AVOID_INLINE_FOR_DEBUGGING MOZ_MUST_USE bool annotateMoveGroups();
+    AVOID_INLINE_FOR_DEBUGGING MOZ_MUST_USE bool deadRange(LiveRange* range);
     size_t findFirstNonCallSafepoint(CodePosition from);
     size_t findFirstSafepoint(CodePosition pos, size_t startFrom);
     void addLiveRegistersForRange(VirtualRegister& reg, LiveRange* range);
 
     MOZ_MUST_USE bool addMove(LMoveGroup* moves, LiveRange* from, LiveRange* to,
                               LDefinition::Type type) {
         LAllocation fromAlloc = from->bundle()->allocation();
         LAllocation toAlloc = to->bundle()->allocation();
--- a/js/xpconnect/src/XPCComponents.cpp
+++ b/js/xpconnect/src/XPCComponents.cpp
@@ -3177,17 +3177,28 @@ class WrappedJSHolder : public nsISuppor
     NS_DECL_ISUPPORTS
     WrappedJSHolder() {}
 
     RefPtr<nsXPCWrappedJS> mWrappedJS;
 
 private:
     virtual ~WrappedJSHolder() {}
 };
-NS_IMPL_ISUPPORTS0(WrappedJSHolder);
+
+NS_IMPL_ADDREF(WrappedJSHolder)
+NS_IMPL_RELEASE(WrappedJSHolder)
+
+// nsINamed is always supported by nsXPCWrappedJSClass.
+// We expose this interface only for the identity in telemetry analysis.
+NS_INTERFACE_TABLE_HEAD(WrappedJSHolder)
+  if (aIID.Equals(NS_GET_IID(nsINamed))) {
+    return mWrappedJS->QueryInterface(aIID, aInstancePtr);
+  }
+NS_INTERFACE_TABLE0(WrappedJSHolder)
+NS_INTERFACE_TABLE_TAIL
 
 NS_IMETHODIMP
 nsXPCComponents_Utils::GenerateXPCWrappedJS(HandleValue aObj, HandleValue aScope,
                                             JSContext* aCx, nsISupports** aOut)
 {
     if (!aObj.isObject())
         return NS_ERROR_INVALID_ARG;
     RootedObject obj(aCx, &aObj.toObject());
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -2243,16 +2243,29 @@ pref("network.http.throttle.resume-backg
 // throttle for this period of time.  This prevents comet and unresponsive
 // http requests to engage long-standing throttling.
 pref("network.http.throttle.time-window", 3000);
 
 // Give higher priority to requests resulting from a user interaction event
 // like click-to-play, image fancy-box zoom, navigation.
 pref("network.http.on_click_priority", true);
 
+// Some requests during a page load are marked as "tail", mainly trackers, but not only.
+// This pref controls whether such requests are put to the tail, behind other requests
+// emerging during page loading process.
+pref("network.http.tailing.enabled", true);
+// When the page load has not yet reached DOMContentLoaded point, tail requestes are delayed
+// by (non-tailed requests count + 1) * delay-quantum milliseconds.
+pref("network.http.tailing.delay-quantum", 600);
+// The same as above, but applied after the document load reached DOMContentLoaded event.
+pref("network.http.tailing.delay-quantum-after-domcontentloaded", 100);
+// Upper limit for the calculated delay, prevents long standing and comet-like requests
+// tail forever.  This is in milliseconds as well.
+pref("network.http.tailing.delay-max", 6000);
+
 pref("permissions.default.image",           1); // 1-Accept, 2-Deny, 3-dontAcceptForeign
 
 pref("network.proxy.type",                  5);
 pref("network.proxy.ftp",                   "");
 pref("network.proxy.ftp_port",              0);
 pref("network.proxy.http",                  "");
 pref("network.proxy.http_port",             0);
 pref("network.proxy.ssl",                   "");
--- a/netwerk/base/RequestContextService.cpp
+++ b/netwerk/base/RequestContextService.cpp
@@ -9,59 +9,138 @@
 #include "nsIXULRuntime.h"
 #include "nsServiceManagerUtils.h"
 #include "nsThreadUtils.h"
 #include "RequestContextService.h"
 
 #include "mozilla/Atomics.h"
 #include "mozilla/Logging.h"
 #include "mozilla/Services.h"
+#include "mozilla/TimeStamp.h"
 
 #include "mozilla/net/PSpdyPush.h"
 
+#include "../protocol/http/nsHttpHandler.h"
+
 namespace mozilla {
 namespace net {
 
 LazyLogModule gRequestContextLog("RequestContext");
 #undef LOG
 #define LOG(args) MOZ_LOG(gRequestContextLog, LogLevel::Info, args)
 
 // nsIRequestContext
 class RequestContext final : public nsIRequestContext
+                           , public nsITimerCallback
 {
 public:
   NS_DECL_THREADSAFE_ISUPPORTS
   NS_DECL_NSIREQUESTCONTEXT
+  NS_DECL_NSITIMERCALLBACK
 
   explicit RequestContext(const uint64_t id);
 private:
   virtual ~RequestContext();
 
+  void ProcessTailQueue(nsresult aResult);
+  // Reschedules the timer if needed
+  void ScheduleUnblock();
+  // Hard-reschedules the timer
+  void RescheduleUntailTimer(TimeStamp const& now);
+
   uint64_t mID;
   Atomic<uint32_t>       mBlockingTransactionCount;
   nsAutoPtr<SpdyPushCache> mSpdyCache;
   nsCString mUserAgentOverride;
+
+  typedef nsCOMPtr<nsIRequestTailUnblockCallback> PendingTailRequest;
+  // Number of known opened non-tailed requets
+  uint32_t mNonTailRequests;
+  // Queue of requests that have been tailed, when conditions are met
+  // we call each of them to unblock and drop the reference
+  nsTArray<PendingTailRequest> mTailQueue;
+  // Loosly scheduled timer, never scheduled further to the future than
+  // mUntailAt time
+  nsCOMPtr<nsITimer> mUntailTimer;
+  // Timestamp when the timer is expected to fire,
+  // always less than or equal to mUntailAt
+  TimeStamp mTimerScheduledAt;
+  // Timestamp when we want to actually untail queued requets based on
+  // the number of request count change in the past; iff this timestamp
+  // is set, we tail requests
+  TimeStamp mUntailAt;
+
+  // This member is true only between DOMContentLoaded notification and
+  // next document load beginning for this request context.
+  // Top level request contexts are recycled.
+  bool mAfterDOMContentLoaded;
 };
 
-NS_IMPL_ISUPPORTS(RequestContext, nsIRequestContext)
+NS_IMPL_ISUPPORTS(RequestContext, nsIRequestContext, nsITimerCallback)
 
 RequestContext::RequestContext(const uint64_t aID)
   : mID(aID)
   , mBlockingTransactionCount(0)
+  , mNonTailRequests(0)
+  , mAfterDOMContentLoaded(false)
 {
   LOG(("RequestContext::RequestContext this=%p id=%" PRIx64, this, mID));
 }
 
 RequestContext::~RequestContext()
 {
+  MOZ_ASSERT(mTailQueue.Length() == 0);
+
   LOG(("RequestContext::~RequestContext this=%p blockers=%u",
        this, static_cast<uint32_t>(mBlockingTransactionCount)));
 }
 
 NS_IMETHODIMP
+RequestContext::BeginLoad()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  LOG(("RequestContext::BeginLoad %p", this));
+
+  if (IsNeckoChild() && gNeckoChild) {
+    // Tailing is not supported on the child process
+    gNeckoChild->SendRequestContextLoadBegin(mID);
+    return NS_OK;
+  }
+
+  mAfterDOMContentLoaded = false;
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+RequestContext::DOMContentLoaded()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  LOG(("RequestContext::DOMContentLoaded %p", this));
+
+  if (IsNeckoChild() && gNeckoChild) {
+    // Tailing is not supported on the child process
+    gNeckoChild->SendRequestContextAfterDOMContentLoaded(mID);
+    return NS_OK;
+  }
+
+  if (mAfterDOMContentLoaded) {
+    // There is a possibility of a duplicate notification
+    return NS_OK;
+  }
+
+  mAfterDOMContentLoaded = true;
+
+  // Conditions for the delay calculation has changed.
+  ScheduleUnblock();
+  return NS_OK;
+}
+
+NS_IMETHODIMP
 RequestContext::GetBlockingTransactionCount(uint32_t *aBlockingTransactionCount)
 {
   NS_ENSURE_ARG_POINTER(aBlockingTransactionCount);
   *aBlockingTransactionCount = mBlockingTransactionCount;
   return NS_OK;
 }
 
 NS_IMETHODIMP
@@ -115,16 +194,227 @@ RequestContext::GetUserAgentOverride(nsA
 
 NS_IMETHODIMP
 RequestContext::SetUserAgentOverride(const nsACString& aUserAgentOverride)
 {
   mUserAgentOverride = aUserAgentOverride;
   return NS_OK;
 }
 
+NS_IMETHODIMP
+RequestContext::AddNonTailRequest()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  ++mNonTailRequests;
+  LOG(("RequestContext::AddNonTailRequest this=%p, cnt=%u",
+       this, mNonTailRequests));
+
+  ScheduleUnblock();
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+RequestContext::RemoveNonTailRequest()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(mNonTailRequests > 0);
+
+  LOG(("RequestContext::RemoveNonTailRequest this=%p, cnt=%u",
+       this, mNonTailRequests - 1));
+
+  --mNonTailRequests;
+
+  ScheduleUnblock();
+  return NS_OK;
+}
+
+void
+RequestContext::ScheduleUnblock()
+{
+  MOZ_ASSERT(!IsNeckoChild());
+  MOZ_ASSERT(NS_IsMainThread());
+
+  if (!gHttpHandler) {
+    return;
+  }
+
+  uint32_t quantum = gHttpHandler->TailBlockingDelayQuantum(mAfterDOMContentLoaded);
+  uint32_t delayMax = gHttpHandler->TailBlockingDelayMax();
+
+  CheckedInt<uint32_t> delay = quantum * mNonTailRequests;
+
+  if (!mAfterDOMContentLoaded) {
+    // Before DOMContentLoaded notification we want to make sure that tailed
+    // requests don't start when there is a short delay during which we may
+    // not have any active requests on the page happening.
+    delay += quantum;
+  }
+
+  if (!delay.isValid() || delay.value() > delayMax) {
+    delay = delayMax;
+  }
+
+  LOG(("RequestContext::ScheduleUnblock this=%p non-tails=%u tail-queue=%zu delay=%u after-DCL=%d",
+       this, mNonTailRequests, mTailQueue.Length(), delay.value(), mAfterDOMContentLoaded));
+
+  TimeStamp now = TimeStamp::NowLoRes();
+  mUntailAt = now + TimeDuration::FromMilliseconds(delay.value());
+
+  if (mTimerScheduledAt.IsNull() || mUntailAt < mTimerScheduledAt) {
+    LOG(("RequestContext %p timer would fire too late, rescheduling", this));
+    RescheduleUntailTimer(now);
+  }
+}
+
+void
+RequestContext::RescheduleUntailTimer(TimeStamp const& now)
+{
+  MOZ_ASSERT(mUntailAt >= now);
+
+  if (mUntailTimer) {
+    mUntailTimer->Cancel();
+  }
+
+  if (!mTailQueue.Length()) {
+    mUntailTimer = nullptr;
+    mTimerScheduledAt = TimeStamp();
+    return;
+  }
+
+  TimeDuration interval = mUntailAt - now;
+  if (!mTimerScheduledAt.IsNull() && mUntailAt < mTimerScheduledAt) {
+    // When the number of untailed requests goes down,
+    // let's half the interval, since it's likely we would
+    // reschedule for a shorter time again very soon.
+    // This will likely save rescheduling this timer.
+    interval = interval / int64_t(2);
+    mTimerScheduledAt = mUntailAt - interval;
+  } else {
+    mTimerScheduledAt = mUntailAt;
+  }
+
+  uint32_t delay = interval.ToMilliseconds();
+  mUntailTimer = do_CreateInstance("@mozilla.org/timer;1");
+  mUntailTimer->InitWithCallback(this, delay, nsITimer::TYPE_ONE_SHOT);
+
+  LOG(("RequestContext::RescheduleUntailTimer %p in %d", this, delay));
+}
+
+NS_IMETHODIMP
+RequestContext::Notify(nsITimer *timer)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(timer == mUntailTimer);
+  MOZ_ASSERT(!mTimerScheduledAt.IsNull());
+  MOZ_ASSERT(mTailQueue.Length());
+
+  mUntailTimer = nullptr;
+
+  TimeStamp now = TimeStamp::NowLoRes();
+  if (mUntailAt > mTimerScheduledAt && mUntailAt > now) {
+    LOG(("RequestContext %p timer fired too soon, rescheduling", this));
+    RescheduleUntailTimer(now);
+    return NS_OK;
+  }
+
+  // Must drop to allow re-engage of the timer
+  mTimerScheduledAt = TimeStamp();
+
+  ProcessTailQueue(NS_OK);
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+RequestContext::IsContextTailBlocked(nsIRequestTailUnblockCallback * aRequest,
+                                     bool *aBlocked)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  LOG(("RequestContext::IsContextTailBlocked this=%p, request=%p, queued=%zu",
+       this, aRequest, mTailQueue.Length()));
+
+  *aBlocked = false;
+
+  if (mUntailAt.IsNull() || mUntailAt <= TimeStamp::NowLoRes()) {
+    LOG(("  untail time passed"));
+    // To save the expansive compare to now next time
+    mUntailAt = TimeStamp();
+    return NS_OK;
+  }
+
+  if (mAfterDOMContentLoaded && !mNonTailRequests) {
+    LOG(("  after DOMContentLoaded and no untailed requests"));
+    return NS_OK;
+  }
+
+  if (!gHttpHandler) {
+    // Xpcshell tests may not have http handler
+    LOG(("  missing gHttpHandler?"));
+    return NS_OK;
+  }
+
+  *aBlocked = true;
+  mTailQueue.AppendElement(aRequest);
+
+  LOG(("  request queued"));
+
+  if (!mUntailTimer) {
+    ScheduleUnblock();
+  }
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+RequestContext::CancelTailedRequest(nsIRequestTailUnblockCallback * aRequest)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  bool removed = mTailQueue.RemoveElement(aRequest);
+
+  LOG(("RequestContext::CancelTailedRequest %p req=%p removed=%d",
+       this, aRequest, removed));
+
+  return NS_OK;
+}
+
+void
+RequestContext::ProcessTailQueue(nsresult aResult)
+{
+  LOG(("RequestContext::ProcessTailQueue this=%p, queued=%zu, rv=%" PRIx32,
+       this, mTailQueue.Length(), static_cast<uint32_t>(aResult)));
+
+  if (mUntailTimer) {
+    mUntailTimer->Cancel();
+    mUntailTimer = nullptr;
+  }
+
+  // Must drop to stop tailing requests
+  mUntailAt = TimeStamp();
+
+  nsTArray<PendingTailRequest> queue;
+  queue.SwapElements(mTailQueue);
+
+  for (auto request : queue) {
+    LOG(("  untailing %p", request.get()));
+    request->OnTailUnblock(aResult);
+  }
+}
+
+NS_IMETHODIMP
+RequestContext::CancelTailPendingRequests(nsresult aResult)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(NS_FAILED(aResult));
+
+  ProcessTailQueue(aResult);
+  return NS_OK;
+}
 
 //nsIRequestContextService
 RequestContextService *RequestContextService::sSelf = nullptr;
 
 NS_IMPL_ISUPPORTS(RequestContextService, nsIRequestContextService, nsIObserver)
 
 RequestContextService::RequestContextService()
   : mNextRCID(1)
@@ -142,23 +432,34 @@ RequestContextService::~RequestContextSe
   MOZ_ASSERT(NS_IsMainThread());
   Shutdown();
   sSelf = nullptr;
 }
 
 nsresult
 RequestContextService::Init()
 {
+  nsresult rv;
+
   MOZ_ASSERT(NS_IsMainThread());
   nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
   if (!obs) {
     return NS_ERROR_NOT_AVAILABLE;
   }
 
-  return obs->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false);
+  rv = obs->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false);
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+  obs->AddObserver(this, "content-document-interactive", false);
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+
+  return NS_OK;
 }
 
 void
 RequestContextService::Shutdown()
 {
   MOZ_ASSERT(NS_IsMainThread());
   mTable.Clear();
 }
@@ -190,16 +491,30 @@ RequestContextService::GetRequestContext
     mTable.Put(rcID, newSC);
     newSC.swap(*rc);
   }
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
+RequestContextService::GetRequestContextFromLoadGroup(nsILoadGroup *aLoadGroup, nsIRequestContext **rc)
+{
+  nsresult rv;
+
+  uint64_t rcID;
+  rv = aLoadGroup->GetRequestContextID(&rcID);
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+
+  return GetRequestContext(rcID, rc);
+}
+
+NS_IMETHODIMP
 RequestContextService::NewRequestContext(nsIRequestContext **rc)
 {
   MOZ_ASSERT(NS_IsMainThread());
   NS_ENSURE_ARG_POINTER(rc);
   *rc = nullptr;
 
   uint64_t rcID = ((static_cast<uint64_t>(mRCIDNamespace) << 32) & 0xFFFFFFFF00000000LL) | mNextRCID++;
 
@@ -208,29 +523,66 @@ RequestContextService::NewRequestContext
   newSC.swap(*rc);
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
 RequestContextService::RemoveRequestContext(const uint64_t rcID)
 {
+  if (IsNeckoChild() && gNeckoChild) {
+    gNeckoChild->SendRemoveRequestContext(rcID);
+  }
+
   MOZ_ASSERT(NS_IsMainThread());
   mTable.Remove(rcID);
   return NS_OK;
 }
 
 NS_IMETHODIMP
 RequestContextService::Observe(nsISupports *subject, const char *topic,
                                   const char16_t *data_unicode)
 {
   MOZ_ASSERT(NS_IsMainThread());
   if (!strcmp(NS_XPCOM_SHUTDOWN_OBSERVER_ID, topic)) {
     Shutdown();
+    return NS_OK;
   }
 
+  if (!strcmp("content-document-interactive", topic)) {
+    nsCOMPtr<nsIDocument> document(do_QueryInterface(subject));
+    MOZ_ASSERT(document);
+    // We want this be triggered also for iframes, since those track their
+    // own request context ids.
+    if (!document) {
+      return NS_OK;
+    }
+    nsIDocShell* ds = document->GetDocShell();
+    // XML documents don't always have a docshell assigned
+    if (!ds) {
+      return NS_OK;
+    }
+    nsCOMPtr<nsIDocumentLoader> dl(do_QueryInterface(ds));
+    if (!dl) {
+      return NS_OK;
+    }
+    nsCOMPtr<nsILoadGroup> lg;
+    dl->GetLoadGroup(getter_AddRefs(lg));
+    if (!lg) {
+      return NS_OK;
+    }
+    nsCOMPtr<nsIRequestContext> rc;
+    GetRequestContextFromLoadGroup(lg, getter_AddRefs(rc));
+    if (rc) {
+      rc->DOMContentLoaded();
+    }
+
+    return NS_OK;
+  }
+
+  MOZ_ASSERT(false, "Unexpected observer topic");
   return NS_OK;
 }
 
 } // ::mozilla::net
 } // ::mozilla
 
 #undef LOG
--- a/netwerk/base/TCPFastOpenLayer.h
+++ b/netwerk/base/TCPFastOpenLayer.h
@@ -26,17 +26,18 @@ typedef enum {
   TFO_FAILED_CONNECTION_REFUSED,
   TFO_FAILED_NET_TIMEOUT,
   TFO_FAILED_UNKNOW_ERROR,
   TFO_FAILED_BACKUP_CONNECTION,
   TFO_FAILED_CONNECTION_REFUSED_NO_TFO_FAILED_TOO,
   TFO_FAILED_NET_TIMEOUT__NO_TFO_FAILED_TOO,
   TFO_FAILED_UNKNOW_ERROR_NO_TFO_FAILED_TOO,
   TFO_FAILED_BACKUP_CONNECTION_NO_TFO_FAILED_TOO,
-  TFO_FAILED
+  TFO_FAILED,
+  TFO_HTTP // TFO is disabled for non-secure connections.
 } TFOResult;
 
 nsresult AttachTCPFastOpenIOLayer(PRFileDesc *fd);
 
 // Get the result of TCP Fast Open.
 void TCPFastOpenFinish(PRFileDesc *fd, PRErrorCode &err,
                        bool &fastOpenNotSupported, uint8_t &tfoStatus);
 
--- a/netwerk/base/nsChannelClassifier.cpp
+++ b/netwerk/base/nsChannelClassifier.cpp
@@ -201,30 +201,39 @@ SetIsTrackingResourceHelper(nsIChannel* 
   }
 }
 
 static void
 LowerPriorityHelper(nsIChannel* aChannel)
 {
   MOZ_ASSERT(aChannel);
 
-  nsCOMPtr<nsISupportsPriority> p = do_QueryInterface(aChannel);
-  if (p) {
-    p->SetPriority(nsISupportsPriority::PRIORITY_LOWEST);
-  }
-}
-
-static void
-SetThrottleableHelper(nsIChannel* aChannel)
-{
-  MOZ_ASSERT(aChannel);
+  bool isBlockingResource = false;
 
   nsCOMPtr<nsIClassOfService> cos(do_QueryInterface(aChannel));
   if (cos) {
-    cos->AddClassFlags(nsIClassOfService::Throttleable);
+    uint32_t cosFlags = 0;
+    cos->GetClassFlags(&cosFlags);
+    isBlockingResource = cosFlags & (nsIClassOfService::UrgentStart |
+                                     nsIClassOfService::Leader |
+                                     nsIClassOfService::Unblocked);
+
+    // Requests not allowed to be tailed are usually those with higher
+    // prioritization.  That overweights being a tracker: don't throttle
+    // them when not in background.
+    if (!(cosFlags & nsIClassOfService::TailForbidden)) {
+      cos->AddClassFlags(nsIClassOfService::Throttleable);
+    }
+  }
+
+  if (!isBlockingResource) {
+    nsCOMPtr<nsISupportsPriority> p = do_QueryInterface(aChannel);
+    if (p) {
+      p->SetPriority(nsISupportsPriority::PRIORITY_LOWEST);
+    }
   }
 }
 
 NS_IMPL_ISUPPORTS(nsChannelClassifier,
                   nsIURIClassifierCallback,
                   nsIObserver)
 
 nsChannelClassifier::nsChannelClassifier(nsIChannel *aChannel)
@@ -1020,17 +1029,16 @@ IsTrackerBlacklistedCallback::OnClassify
          mChannelClassifier.get(), channel.get()));
 
     MOZ_ASSERT(mChannelClassifier->ShouldEnableTrackingAnnotation());
 
     SetIsTrackingResourceHelper(channel);
     if (CachedPrefs::GetInstance()->IsLowerNetworkPriority()) {
       LowerPriorityHelper(channel);
     }
-    SetThrottleableHelper(channel);
 
     // We don't want to disable speculative connection when tracking protection
     // is disabled. So, change the status to NS_OK.
     status = NS_OK;
 
     return mChannelCallback->OnClassifyComplete(
       status, aLists, aProvider, aPrefix);
   }
@@ -1067,17 +1075,16 @@ IsTrackerBlacklistedCallback::OnClassify
          mChannelClassifier.get(), channel.get(),
          uri->GetSpecOrDefault().get()));
   }
 
   SetIsTrackingResourceHelper(channel);
   if (CachedPrefs::GetInstance()->IsLowerNetworkPriority()) {
     LowerPriorityHelper(channel);
   }
-  SetThrottleableHelper(channel);
 
   return mChannelCallback->OnClassifyComplete(
       NS_OK, aLists, aProvider, aPrefix);
 }
 
 } // end of unnamed namespace/
 
 already_AddRefed<nsIURI>
--- a/netwerk/base/nsIClassOfService.idl
+++ b/netwerk/base/nsIClassOfService.idl
@@ -40,9 +40,18 @@ interface nsIClassOfService : nsISupport
   const unsigned long Leader = 1 << 0;
   const unsigned long Follower = 1 << 1;
   const unsigned long Speculative = 1 << 2;
   const unsigned long Background = 1 << 3;
   const unsigned long Unblocked = 1 << 4;
   const unsigned long Throttleable = 1 << 5;
   const unsigned long UrgentStart = 1 << 6;
   const unsigned long DontThrottle = 1 << 7;
+  // Enforce tailing on this load; any of Leader, Unblocked, UrgentStart, TailForbidden
+  // overrule this flag (disable tailing.)
+  const unsigned long Tail = 1 << 8;
+  // Tailing may be engaged regardless if the load is marked Unblocked when
+  // some other conditions are met later, like when the load is found to be
+  // a tracker.
+  const unsigned long TailAllowed = 1 << 9;
+  // Tailing not allowed under any circumstances or combination of flags.
+  const unsigned long TailForbidden = 1 << 10;
 };
--- a/netwerk/base/nsIRequestContext.idl
+++ b/netwerk/base/nsIRequestContext.idl
@@ -9,35 +9,70 @@
 // Forward-declare mozilla::net::SpdyPushCache
 namespace mozilla {
 namespace net {
 class SpdyPushCache;
 }
 }
 %}
 
+interface nsILoadGroup;
+interface nsIChannel;
+interface nsIStreamListener;
+
 [ptr] native SpdyPushCachePtr(mozilla::net::SpdyPushCache);
 
 /**
+ * Requests capable of tail-blocking must implement this
+ * interfaces (typically channels).
+ * If the request is tail-blocked, it will be held in its request
+ * context queue until unblocked.
+ */
+[scriptable, uuid(7EB361D4-37A5-42C9-AFAE-F6C88FE7C394)]
+interface nsIRequestTailUnblockCallback : nsISupports
+{
+  /**
+   * Called when the requests is unblocked and proceed.
+   * @param result
+   *    NS_OK - the request is OK to go, unblocking is not
+   *            caused by cancelation of the request.
+   *    any error - the request must behave as it were canceled
+   *                with the result as status.
+   */
+  void onTailUnblock(in nsresult aResult);
+};
+
+/**
  * The nsIRequestContext is used to maintain state about connections
  * that are in some way associated with each other (often by being part
  * of the same load group) and how they interact with blocking items like
  * HEAD css/js loads.
  *
  * This used to be known as nsILoadGroupConnectionInfo and nsISchedulingContext.
  */
 [scriptable, uuid(658e3e6e-8633-4b1a-8d66-fa9f72293e63)]
 interface nsIRequestContext : nsISupports
 {
   /**
    * A unique identifier for this request context
    */
   [noscript] readonly attribute unsigned long long ID;
 
   /**
+   * Called by the associated document when its load starts.  This resets
+   * context's internal states.
+   */
+  void beginLoad();
+
+  /**
+  * Called when the associated document notified the DOMContentLoaded event.
+  */
+  void DOMContentLoaded();
+
+  /**
    * Number of active blocking transactions associated with this context
    */
   readonly attribute unsigned long blockingTransactionCount;
 
   /**
    * Increase the number of active blocking transactions associated
    * with this context by one.
    */
@@ -57,16 +92,45 @@ interface nsIRequestContext : nsISupport
    * ends.
    */
   [noscript] attribute SpdyPushCachePtr spdyPushCache;
 
   /**
    * This holds a cached value of the user agent override.
    */
   [noscript] attribute ACString userAgentOverride;
+
+  /**
+   * Increases/decrease the number of non-tailed requests in this context.
+   * If the count drops to zero, all tail-blocked callbacks are notified
+   * shortly after that to be unblocked.
+   */
+  void addNonTailRequest();
+  void removeNonTailRequest();
+
+  /**
+   * If the request context is in tail-blocked state, the callback
+   * is queued and result is true.  The callback will be notified
+   * about tail-unblocking or when the request context is canceled.
+   */
+  [must_use] boolean isContextTailBlocked(in nsIRequestTailUnblockCallback callback);
+
+  /**
+   * Called when the request is sitting in the tail queue but has been
+   * canceled before untailing.  This just removes the request from the
+   * queue so that it is not notified on untail and not referenced.
+   */
+  void cancelTailedRequest(in nsIRequestTailUnblockCallback request);
+
+  /**
+   * This notifies all queued tail-blocked requests, they will be notified
+   * aResult and released afterwards.  Called by the load group when
+   * it's canceled.
+   */
+  void cancelTailPendingRequests(in nsresult aResult);
 };
 
 /**
  * The nsIRequestContextService is how anyone gets access to a request
  * context when they haven't been explicitly given a strong reference to an
  * existing one. It is responsible for creating and handing out strong
  * references to nsIRequestContexts, but only keeps weak references itself.
  * The shared request context will go away once no one else is keeping a
@@ -77,16 +141,20 @@ interface nsIRequestContext : nsISupport
  */
 [uuid(7fcbf4da-d828-4acc-b144-e5435198f727)]
 interface nsIRequestContextService : nsISupports
 {
   /**
    * Get an existing request context from its ID
    */
   nsIRequestContext getRequestContext(in unsigned long long id);
+  /**
+   * Shorthand to get request context from a load group
+   */
+  nsIRequestContext getRequestContextFromLoadGroup(in nsILoadGroup lg);
 
   /**
    * Create a new request context
    */
   nsIRequestContext newRequestContext();
 
   /**
    * Remove an existing request context from its ID
--- a/netwerk/base/nsLoadGroup.cpp
+++ b/netwerk/base/nsLoadGroup.cpp
@@ -18,18 +18,16 @@
 #include "nsITimedChannel.h"
 #include "nsIInterfaceRequestor.h"
 #include "nsIRequestObserver.h"
 #include "nsIRequestContext.h"
 #include "CacheObserver.h"
 #include "MainThreadUtils.h"
 #include "mozilla/Unused.h"
 
-#include "mozilla/net/NeckoChild.h"
-
 namespace mozilla {
 namespace net {
 
 //
 // Log module for nsILoadGroup logging...
 //
 // To enable logging (see prlog.h for full details):
 //
@@ -124,22 +122,17 @@ nsLoadGroup::~nsLoadGroup()
     DebugOnly<nsresult> rv = Cancel(NS_BINDING_ABORTED);
     NS_ASSERTION(NS_SUCCEEDED(rv), "Cancel failed");
 
     mDefaultLoadRequest = nullptr;
 
     if (mRequestContext) {
         uint64_t rcid;
         mRequestContext->GetID(&rcid);
-
-        if (IsNeckoChild() && gNeckoChild) {
-            gNeckoChild->SendRemoveRequestContext(rcid);
-        } else {
-            mRequestContextService->RemoveRequestContext(rcid);
-        }
+        mRequestContextService->RemoveRequestContext(rcid);
     }
 
     LOG(("LOADGROUP [%p]: Destroyed.\n", this));
 }
 
 
 ////////////////////////////////////////////////////////////////////////////////
 // nsISupports methods:
@@ -269,16 +262,20 @@ nsLoadGroup::Cancel(nsresult status)
 
         // Remember the first failure and return it...
         if (NS_FAILED(rv) && NS_SUCCEEDED(firstError))
             firstError = rv;
 
         NS_RELEASE(request);
     }
 
+    if (mRequestContext) {
+        Unused << mRequestContext->CancelTailPendingRequests(status);
+    }
+
 #if defined(DEBUG)
     NS_ASSERTION(mRequests.EntryCount() == 0, "Request list is not empty.");
     NS_ASSERTION(mForegroundCount == 0, "Foreground URLs are active.");
 #endif
 
     mStatus = NS_OK;
     mIsCanceling = false;
 
--- a/netwerk/ipc/NeckoParent.cpp
+++ b/netwerk/ipc/NeckoParent.cpp
@@ -894,16 +894,50 @@ NeckoParent::RecvPredReset()
     do_GetService("@mozilla.org/network/predictor;1", &rv);
   NS_ENSURE_SUCCESS(rv, IPC_FAIL_NO_REASON(this));
 
   predictor->Reset();
   return IPC_OK();
 }
 
 mozilla::ipc::IPCResult
+NeckoParent::RecvRequestContextLoadBegin(const uint64_t& rcid)
+{
+  nsCOMPtr<nsIRequestContextService> rcsvc =
+    do_GetService("@mozilla.org/network/request-context-service;1");
+  if (!rcsvc) {
+    return IPC_OK();
+  }
+  nsCOMPtr<nsIRequestContext> rc;
+  rcsvc->GetRequestContext(rcid, getter_AddRefs(rc));
+  if (rc) {
+    rc->BeginLoad();
+  }
+
+  return IPC_OK();
+}
+
+mozilla::ipc::IPCResult
+NeckoParent::RecvRequestContextAfterDOMContentLoaded(const uint64_t& rcid)
+{
+  nsCOMPtr<nsIRequestContextService> rcsvc =
+    do_GetService("@mozilla.org/network/request-context-service;1");
+  if (!rcsvc) {
+    return IPC_OK();
+  }
+  nsCOMPtr<nsIRequestContext> rc;
+  rcsvc->GetRequestContext(rcid, getter_AddRefs(rc));
+  if (rc) {
+    rc->DOMContentLoaded();
+  }
+
+  return IPC_OK();
+}
+
+mozilla::ipc::IPCResult
 NeckoParent::RecvRemoveRequestContext(const uint64_t& rcid)
 {
   nsCOMPtr<nsIRequestContextService> rcsvc =
     do_GetService("@mozilla.org/network/request-context-service;1");
   if (!rcsvc) {
     return IPC_OK();
   }
 
--- a/netwerk/ipc/NeckoParent.h
+++ b/netwerk/ipc/NeckoParent.h
@@ -223,16 +223,18 @@ protected:
                                                   const bool& hasVerifier) override;
 
   virtual mozilla::ipc::IPCResult RecvPredLearn(const ipc::URIParams& aTargetURI,
                                                 const ipc::OptionalURIParams& aSourceURI,
                                                 const PredictorPredictReason& aReason,
                                                 const OriginAttributes& aOriginAttributes) override;
   virtual mozilla::ipc::IPCResult RecvPredReset() override;
 
+  virtual mozilla::ipc::IPCResult RecvRequestContextLoadBegin(const uint64_t& rcid) override;
+  virtual mozilla::ipc::IPCResult RecvRequestContextAfterDOMContentLoaded(const uint64_t& rcid) override;
   virtual mozilla::ipc::IPCResult RecvRemoveRequestContext(const uint64_t& rcid) override;
 
   /* WebExtensions */
   virtual mozilla::ipc::IPCResult
     RecvGetExtensionStream(const URIParams& aURI,
                            GetExtensionStreamResolver&& aResolve) override;
 
   virtual mozilla::ipc::IPCResult
--- a/netwerk/ipc/PNecko.ipdl
+++ b/netwerk/ipc/PNecko.ipdl
@@ -111,16 +111,18 @@ parent:
    * These are called from the child with the results of the auth prompt.
    * callbackId is the id that was passed in PBrowser::AsyncAuthPrompt,
    * corresponding to an nsIAuthPromptCallback
    */
   async OnAuthAvailable(uint64_t callbackId, nsString user,
                         nsString password, nsString domain);
   async OnAuthCancelled(uint64_t callbackId, bool userCancel);
 
+  async RequestContextLoadBegin(uint64_t rcid);
+  async RequestContextAfterDOMContentLoaded(uint64_t rcid);
   async RemoveRequestContext(uint64_t rcid);
 
   async PAltDataOutputStream(nsCString type, PHttpChannel channel);
 
   async PStunAddrsRequest();
 
   /**
    * WebExtension-specific remote resource loading
--- a/netwerk/protocol/http/HttpBaseChannel.cpp
+++ b/netwerk/protocol/http/HttpBaseChannel.cpp
@@ -178,16 +178,17 @@ HttpBaseChannel::HttpBaseChannel()
   , mAllowAltSvc(true)
   , mBeConservative(false)
   , mResponseTimeoutEnabled(true)
   , mAllRedirectsSameOrigin(true)
   , mAllRedirectsPassTimingAllowCheck(true)
   , mResponseCouldBeSynthesized(false)
   , mBlockAuthPrompt(false)
   , mAllowStaleCacheContent(false)
+  , mAddedAsNonTailRequest(false)
   , mTlsFlags(0)
   , mSuspendCount(0)
   , mInitialRwin(0)
   , mProxyResolveFlags(0)
   , mContentDispositionHint(UINT32_MAX)
   , mHttpHandler(gHttpHandler)
   , mReferrerPolicy(NS_GetDefaultReferrerPolicy())
   , mRedirectCount(0)
@@ -230,22 +231,48 @@ HttpBaseChannel::~HttpBaseChannel()
   LOG(("Destroying HttpBaseChannel @%p\n", this));
 
   // Make sure we don't leak
   CleanRedirectCacheChainIfNecessary();
 
   ReleaseMainThreadOnlyReferences();
 }
 
+namespace { // anon
+
+class NonTailRemover : public nsISupports
+{
+  NS_DECL_THREADSAFE_ISUPPORTS
+
+  explicit NonTailRemover(nsIRequestContext* rc)
+    : mRequestContext(rc)
+  {
+  }
+
+private:
+  virtual ~NonTailRemover()
+  {
+    MOZ_ASSERT(NS_IsMainThread());
+    mRequestContext->RemoveNonTailRequest();
+  }
+
+  nsCOMPtr<nsIRequestContext> mRequestContext;
+};
+
+NS_IMPL_ISUPPORTS0(NonTailRemover)
+
+} // anon
+
 void
 HttpBaseChannel::ReleaseMainThreadOnlyReferences()
 {
   if (NS_IsMainThread()) {
     // Already on main thread, let dtor to
     // take care of releasing references
+    RemoveAsNonTailRequest();
     return;
   }
 
   nsTArray<nsCOMPtr<nsISupports>> arrayToRelease;
   arrayToRelease.AppendElement(mURI.forget());
   arrayToRelease.AppendElement(mOriginalURI.forget());
   arrayToRelease.AppendElement(mDocumentURI.forget());
   arrayToRelease.AppendElement(mLoadGroup.forget());
@@ -257,16 +284,24 @@ HttpBaseChannel::ReleaseMainThreadOnlyRe
   arrayToRelease.AppendElement(mAPIRedirectToURI.forget());
   arrayToRelease.AppendElement(mProxyURI.forget());
   arrayToRelease.AppendElement(mPrincipal.forget());
   arrayToRelease.AppendElement(mTopWindowURI.forget());
   arrayToRelease.AppendElement(mListener.forget());
   arrayToRelease.AppendElement(mListenerContext.forget());
   arrayToRelease.AppendElement(mCompressListener.forget());
 
+  if (mAddedAsNonTailRequest) {
+    // RemoveNonTailRequest() on our request context must be called on the main thread
+    MOZ_RELEASE_ASSERT(mRequestContext, "Someone released rc or set flags w/o having it?");
+
+    nsCOMPtr<nsISupports> nonTailRemover(new NonTailRemover(mRequestContext));
+    arrayToRelease.AppendElement(nonTailRemover.forget());
+  }
+
   NS_DispatchToMainThread(new ProxyReleaseRunnable(Move(arrayToRelease)));
 }
 
 void
 HttpBaseChannel::SetIsTrackingResource()
 {
   LOG(("HttpBaseChannel::SetIsTrackingResource %p", this));
   mIsTrackingResource = true;
@@ -3009,16 +3044,48 @@ HttpBaseChannel::ShouldIntercept(nsIURI*
                                                         &shouldIntercept);
     if (NS_FAILED(rv)) {
       return false;
     }
   }
   return shouldIntercept;
 }
 
+void
+HttpBaseChannel::AddAsNonTailRequest()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  if (EnsureRequestContext()) {
+    LOG(("HttpBaseChannel::AddAsNonTailRequest this=%p, rc=%p, already added=%d",
+         this, mRequestContext.get(), (bool)mAddedAsNonTailRequest));
+
+    if (!mAddedAsNonTailRequest) {
+      mRequestContext->AddNonTailRequest();
+      mAddedAsNonTailRequest = true;
+    }
+  }
+}
+
+void
+HttpBaseChannel::RemoveAsNonTailRequest()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  if (mRequestContext) {
+    LOG(("HttpBaseChannel::RemoveAsNonTailRequest this=%p, rc=%p, already added=%d",
+         this, mRequestContext.get(), (bool)mAddedAsNonTailRequest));
+
+    if (mAddedAsNonTailRequest) {
+      mRequestContext->RemoveNonTailRequest();
+      mAddedAsNonTailRequest = false;
+    }
+  }
+}
+
 #ifdef DEBUG
 void HttpBaseChannel::AssertPrivateBrowsingId()
 {
   nsCOMPtr<nsILoadContext> loadContext;
   NS_QueryNotificationCallbacks(this, loadContext);
   // For addons it's possible that mLoadInfo is null.
   if (!mLoadInfo) {
     return;
@@ -3170,18 +3237,22 @@ HttpBaseChannel::DoNotifyListener()
                "We should not call OnStopRequest twice");
 
     nsCOMPtr<nsIStreamListener> listener = mListener;
     listener->OnStopRequest(this, mListenerContext, mStatus);
 
     mOnStopRequestCalled = true;
   }
 
+  // This channel has finished its job, potentially release any tail-blocked
+  // requests with this.
+  RemoveAsNonTailRequest();
+
   // We have to make sure to drop the references to listeners and callbacks
-  // no longer  needed
+  // no longer needed.
   ReleaseListeners();
 
   DoNotifyListenerCleanup();
 
   // If this is a navigation, then we must let the docshell flush the reports
   // to the console later.  The LoadDocument() is pointing at the detached
   // document that started the navigation.  We want to show the reports on the
   // new document.  Otherwise the console is wiped and the user never sees
@@ -4089,25 +4160,50 @@ HttpBaseChannel::EnsureRequestContextID(
     }
 
     nsCOMPtr<nsILoadGroup> rootLoadGroup;
     childLoadGroup->GetRootLoadGroup(getter_AddRefs(rootLoadGroup));
     if (!rootLoadGroup) {
         return false;
     }
 
-    // Set the load group connection scope on the transaction
+    // Set the load group connection scope on this channel and its transaction
     rootLoadGroup->GetRequestContextID(&mRequestContextID);
 
     LOG(("HttpBaseChannel::EnsureRequestContextID this=%p id=%" PRIx64,
          this, mRequestContextID));
 
     return true;
 }
 
+bool
+HttpBaseChannel::EnsureRequestContext()
+{
+    if (mRequestContext) {
+        // Already have a request context, no need to do the rest of this work
+        return true;
+    }
+
+    if (!EnsureRequestContextID()) {
+        return false;
+    }
+
+    nsIRequestContextService* rcsvc = gHttpHandler->GetRequestContextService();
+    if (!rcsvc) {
+        return false;
+    }
+
+    rcsvc->GetRequestContext(mRequestContextID, getter_AddRefs(mRequestContext));
+    if (!mRequestContext) {
+        return false;
+    }
+
+    return true;
+}
+
 void
 HttpBaseChannel::EnsureTopLevelOuterContentWindowId()
 {
   if (mTopLevelOuterContentWindowId) {
     return;
   }
 
   nsCOMPtr<nsILoadContext> loadContext;
--- a/netwerk/protocol/http/HttpBaseChannel.h
+++ b/netwerk/protocol/http/HttpBaseChannel.h
@@ -535,16 +535,20 @@ protected:
   uint32_t                          mResponseCouldBeSynthesized : 1;
 
   uint32_t                          mBlockAuthPrompt : 1;
 
   // If true, we behave as if the LOAD_FROM_CACHE flag has been set.
   // Used to enforce that flag's behavior but not expose it externally.
   uint32_t                          mAllowStaleCacheContent : 1;
 
+  // True iff this request has been calculated in its request context as
+  // a non tail request.  We must remove it again when this channel is done.
+  uint32_t                          mAddedAsNonTailRequest : 1;
+
   // An opaque flags for non-standard behavior of the TLS system.
   // It is unlikely this will need to be set outside of telemetry studies
   // relating to the TLS implementation.
   uint32_t                          mTlsFlags;
 
   // Current suspension depth for this channel object
   uint32_t                          mSuspendCount;
 
@@ -610,16 +614,24 @@ protected:
   uint64_t mDecodedBodySize;
   uint64_t mEncodedBodySize;
 
   // The network interface id that's associated with this channel.
   nsCString mNetworkInterfaceId;
 
   uint64_t mRequestContextID;
   bool EnsureRequestContextID();
+  nsCOMPtr<nsIRequestContext> mRequestContext;
+  bool EnsureRequestContext();
+
+  // Adds/removes this channel as a non-tailed request in its request context
+  // these helpers ensure we add it only once and remove it only when added
+  // via mAddedAsNonTailRequest member tracking.
+  void AddAsNonTailRequest();
+  void RemoveAsNonTailRequest();
 
   // ID of the top-level document's inner window this channel is being
   // originated from.
   uint64_t mContentWindowId;
 
   uint64_t mTopLevelOuterContentWindowId;
   void EnsureTopLevelOuterContentWindowId();
 
--- a/netwerk/protocol/http/nsHttpChannel.cpp
+++ b/netwerk/protocol/http/nsHttpChannel.cpp
@@ -271,16 +271,20 @@ private:
 void
 AutoRedirectVetoNotifier::ReportRedirectResult(bool succeeded)
 {
     if (!mChannel)
         return;
 
     mChannel->mRedirectChannel = nullptr;
 
+    if (succeeded) {
+        mChannel->RemoveAsNonTailRequest();
+    }
+
     nsCOMPtr<nsIRedirectResultListener> vetoHook;
     NS_QueryNotificationCallbacks(mChannel,
                                   NS_GET_IID(nsIRedirectResultListener),
                                   getter_AddRefs(vetoHook));
 
     nsHttpChannel* channel = mChannel;
     mChannel = nullptr;
 
@@ -331,16 +335,17 @@ nsHttpChannel::nsHttpChannel()
     , mIsCorsPreflightDone(0)
     , mStronglyFramed(false)
     , mUsedNetwork(0)
     , mAuthConnectionRestartable(0)
     , mReqContentLengthDetermined(0)
     , mReqContentLength(0U)
     , mPushedStream(nullptr)
     , mLocalBlocklist(false)
+    , mOnTailUnblock(nullptr)
     , mWarningReporter(nullptr)
     , mIsReadingFromCache(false)
     , mFirstResponseSource(RESPONSE_PENDING)
     , mOnCacheAvailableCalled(false)
     , mRaceCacheWithNetwork(false)
     , mRaceDelay(0)
     , mCacheAsyncOpenCalled(false)
     , mIgnoreCacheEntry(false)
@@ -531,27 +536,50 @@ nsHttpChannel::OnBeforeConnectContinue()
     }
 }
 
 nsresult
 nsHttpChannel::Connect()
 {
     LOG(("nsHttpChannel::Connect [this=%p]\n", this));
 
-    // Consider opening a TCP connection right away.
-    SpeculativeConnect();
-
     // Don't allow resuming when cache must be used
     if (mResuming && (mLoadFlags & LOAD_ONLY_FROM_CACHE)) {
         LOG(("Resuming from cache is not supported yet"));
         return NS_ERROR_DOCUMENT_NOT_CACHED;
     }
 
+    bool isTrackingResource = mIsTrackingResource; // is atomic
+    LOG(("nsHttpChannel %p tracking resource=%d, local blocklist=%d, cos=%u",
+          this, isTrackingResource, mLocalBlocklist, mClassOfService));
+
+    if (isTrackingResource || mLocalBlocklist) {
+        AddClassFlags(nsIClassOfService::Tail);
+    }
+
+    if (WaitingForTailUnblock()) {
+        MOZ_DIAGNOSTIC_ASSERT(!mOnTailUnblock);
+        mOnTailUnblock = &nsHttpChannel::ConnectOnTailUnblock;
+        return NS_OK;
+    }
+
+    return ConnectOnTailUnblock();
+}
+
+nsresult
+nsHttpChannel::ConnectOnTailUnblock()
+{
+    nsresult rv;
+
+    LOG(("nsHttpChannel::ConnectOnTailUnblock [this=%p]\n", this));
+
+    // Consider opening a TCP connection right away.
+    SpeculativeConnect();
+
     // open a cache entry for this channel...
-    nsresult rv;
     bool isHttps = false;
     rv = mURI->SchemeIs("https", &isHttps);
     NS_ENSURE_SUCCESS(rv,rv);
     rv = OpenCacheEntry(isHttps);
 
     // do not continue if asyncOpenCacheEntry is in progress
     if (AwaitingCacheCallbacks()) {
         LOG(("nsHttpChannel::Connect %p AwaitingCacheCallbacks forces async\n", this));
@@ -1059,40 +1087,16 @@ nsHttpChannel::ContinueHandleAsyncFallba
     mIsPending = false;
 
     if (mLoadGroup)
         mLoadGroup->RemoveRequest(this, nullptr, mStatus);
 
     return rv;
 }
 
-void
-nsHttpChannel::SetupTransactionRequestContext()
-{
-    if (!EnsureRequestContextID()) {
-        return;
-    }
-
-    nsIRequestContextService *rcsvc =
-        gHttpHandler->GetRequestContextService();
-    if (!rcsvc) {
-        return;
-    }
-
-    nsCOMPtr<nsIRequestContext> rc;
-    nsresult rv = rcsvc->GetRequestContext(mRequestContextID,
-                                           getter_AddRefs(rc));
-
-    if (NS_FAILED(rv)) {
-        return;
-    }
-
-    mTransaction->SetRequestContext(rc);
-}
-
 nsresult
 nsHttpChannel::SetupTransaction()
 {
     LOG(("nsHttpChannel::SetupTransaction [this=%p, cos=%u, prio=%d]\n",
          this, mClassOfService, mPriority));
 
     NS_ENSURE_TRUE(!mTransaction, NS_ERROR_ALREADY_INITIALIZED);
 
@@ -1312,17 +1316,19 @@ nsHttpChannel::SetupTransaction()
                             mTopLevelOuterContentWindowId,
                             getter_AddRefs(responseStream));
     if (NS_FAILED(rv)) {
         mTransaction = nullptr;
         return rv;
     }
 
     mTransaction->SetClassOfService(mClassOfService);
-    SetupTransactionRequestContext();
+    if (EnsureRequestContext()) {
+        mTransaction->SetRequestContext(mRequestContext);
+    }
 
     rv = nsInputStreamPump::Create(getter_AddRefs(mTransactionPump),
                                    responseStream);
     return rv;
 }
 
 // NOTE: This function duplicates code from nsBaseChannel. This will go away
 // once HTTP uses nsBaseChannel (part of bug 312760)
@@ -5962,16 +5968,17 @@ NS_INTERFACE_MAP_BEGIN(nsHttpChannel)
     NS_INTERFACE_MAP_ENTRY(nsIThreadRetargetableStreamListener)
     NS_INTERFACE_MAP_ENTRY(nsIDNSListener)
     NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
     NS_INTERFACE_MAP_ENTRY(nsICorsPreflightCallback)
     NS_INTERFACE_MAP_ENTRY(nsIRaceCacheWithNetwork)
     NS_INTERFACE_MAP_ENTRY(nsITimerCallback)
     NS_INTERFACE_MAP_ENTRY(nsIHstsPrimingCallback)
     NS_INTERFACE_MAP_ENTRY(nsIChannelWithDivertableParentListener)
+    NS_INTERFACE_MAP_ENTRY(nsIRequestTailUnblockCallback)
     // we have no macro that covers this case.
     if (aIID.Equals(NS_GET_IID(nsHttpChannel)) ) {
         AddRef();
         *aInstancePtr = this;
         return NS_OK;
     } else
 NS_INTERFACE_MAP_END_INHERITING(HttpBaseChannel)
 
@@ -6003,16 +6010,20 @@ nsHttpChannel::Cancel(nsresult status)
     CancelNetworkRequest(status);
     mCacheInputStream.CloseAndRelease();
     if (mCachePump)
         mCachePump->Cancel(status);
     if (mAuthProvider)
         mAuthProvider->Cancel(status);
     if (mPreflightChannel)
         mPreflightChannel->Cancel(status);
+    if (mRequestContext && mOnTailUnblock) {
+        mOnTailUnblock = nullptr;
+        mRequestContext->CancelTailedRequest(this);
+    }
     return NS_OK;
 }
 
 void
 nsHttpChannel::CancelNetworkRequest(nsresult aStatus)
 {
     if (mTransaction) {
         nsresult rv = gHttpHandler->CancelTransaction(mTransaction, aStatus);
@@ -6124,16 +6135,31 @@ nsHttpChannel::AsyncOpen(nsIStreamListen
     }
 
     rv = NS_CheckPortSafety(mURI);
     if (NS_FAILED(rv)) {
         ReleaseListeners();
         return rv;
     }
 
+    if (WaitingForTailUnblock()) {
+        // This channel is marked as Tail and is part of a request context
+        // that has positive number of non-tailed requestst, hence this channel
+        // has been put to a queue.
+        // When tail is unblocked, OnTailUnblock on this channel will be called
+        // to continue AsyncOpen.
+        mListener = listener;
+        mListenerContext = context;
+        MOZ_DIAGNOSTIC_ASSERT(!mOnTailUnblock);
+        mOnTailUnblock = &nsHttpChannel::AsyncOpenOnTailUnblock;
+
+        LOG(("  put on hold until tail is unblocked"));
+        return NS_OK;
+    }
+
     if (mInterceptCache != INTERCEPTED && ShouldIntercept()) {
         mInterceptCache = MAYBE_INTERCEPT;
         SetCouldBeSynthesized();
     }
 
     // Remember the cookie header that was set, if any
     nsAutoCString cookieHeader;
     if (NS_SUCCEEDED(mRequestHead.GetHeader(nsHttp::Cookie, cookieHeader))) {
@@ -6188,16 +6214,22 @@ nsHttpChannel::AsyncOpen(nsIStreamListen
     if (NS_FAILED(rv)) {
         CloseCacheEntry(false);
         Unused << AsyncAbort(rv);
     }
 
     return NS_OK;
 }
 
+nsresult
+nsHttpChannel::AsyncOpenOnTailUnblock()
+{
+    return AsyncOpen(mListener, mListenerContext);
+}
+
 namespace {
 
 class InitLocalBlockListXpcCallback final : public nsIURIClassifierCallback {
 public:
   using CallbackType = nsHttpChannel::InitLocalBlockListCallback;
 
   explicit InitLocalBlockListXpcCallback(const CallbackType& aCallback)
     : mCallback(aCallback)
@@ -6528,17 +6560,22 @@ nsHttpChannel::BeginConnectContinue()
     }
 
     // We are about to do a async lookup to check if the URI is a
     // tracker. The result will be delivered along with the callback.
     // Chances are the lookup is not needed so InitLocalBlockList()
     // will return false and then we can BeginConnectActual() right away.
     RefPtr<nsHttpChannel> self = this;
     bool willCallback = InitLocalBlockList([self](bool aLocalBlockList) -> void  {
+        MOZ_ASSERT(self->mLocalBlocklist <= aLocalBlockList, "Unmarking local block-list flag?");
+
         self->mLocalBlocklist = aLocalBlockList;
+
+        LOG(("nsHttpChannel %p on-local-blacklist=%d", self.get(), aLocalBlockList));
+
         nsresult rv = self->BeginConnectActual();
         if (NS_FAILED(rv)) {
             // Since this error is thrown asynchronously so that the caller
             // of BeginConnect() will not do clean up for us. We have to do
             // it on our own.
             self->CloseCacheEntry(false);
             Unused << self->AsyncAbort(rv);
         }
@@ -6732,33 +6769,43 @@ nsHttpChannel::ContinueBeginConnectWithR
          " mCanceled=%u]\n",
          this, static_cast<uint32_t>(rv), static_cast<bool>(mCanceled)));
     return rv;
 }
 
 void
 nsHttpChannel::ContinueBeginConnect()
 {
+    LOG(("nsHttpChannel::ContinueBeginConnect this=%p", this));
+
     nsresult rv = ContinueBeginConnectWithResult();
     if (NS_FAILED(rv)) {
         CloseCacheEntry(false);
         Unused << AsyncAbort(rv);
     }
 }
 
 //-----------------------------------------------------------------------------
 // HttpChannel::nsIClassOfService
 //-----------------------------------------------------------------------------
 
 void
 nsHttpChannel::OnClassOfServiceUpdated()
 {
+    LOG(("nsHttpChannel::OnClassOfServiceUpdated this=%p, cos=%u",
+         this, mClassOfService));
+
     if (mTransaction) {
         gHttpHandler->UpdateClassOfServiceOnTransaction(mTransaction, mClassOfService);
     }
+    if (EligibleForTailing()) {
+        RemoveAsNonTailRequest();
+    } else {
+        AddAsNonTailRequest();
+    }
 }
 
 NS_IMETHODIMP
 nsHttpChannel::SetClassFlags(uint32_t inFlags)
 {
     uint32_t previous = mClassOfService;
     mClassOfService = inFlags;
     if (previous != mClassOfService) {
@@ -7520,17 +7567,20 @@ nsHttpChannel::OnStopRequest(nsIRequest 
     if (mListener) {
         LOG(("nsHttpChannel %p calling OnStopRequest\n", this));
         MOZ_ASSERT(mOnStartRequestCalled,
                    "OnStartRequest should be called before OnStopRequest");
         MOZ_ASSERT(!mOnStopRequestCalled,
                    "We should not call OnStopRequest twice");
         mListener->OnStopRequest(this, mListenerContext, status);
         mOnStopRequestCalled = true;
-    }
+
+    }
+
+    RemoveAsNonTailRequest();
 
     // If a preferred alt-data type was set, this signals the consumer is
     // interested in reading and/or writing the alt-data representation.
     // We need to hold a reference to the cache entry in case the listener calls
     // openAlternativeOutputStream() after CloseCacheEntry() clears mCacheEntry.
     if (!mPreferredCachedAltDataType.IsEmpty()) {
         mAltDataCacheEntry = mCacheEntry;
     }
@@ -9327,16 +9377,108 @@ nsHttpChannel::Notify(nsITimer *aTimer)
         return TriggerNetwork(0);
     } else {
         MOZ_CRASH("Unknown timer");
     }
 
     return NS_OK;
 }
 
+bool
+nsHttpChannel::EligibleForTailing()
+{
+  if (!(mClassOfService & nsIClassOfService::Tail)) {
+      return false;
+  }
+
+  if (mClassOfService & (nsIClassOfService::UrgentStart |
+                         nsIClassOfService::Leader |
+                         nsIClassOfService::TailForbidden)) {
+      return false;
+  }
+
+  if (mClassOfService & nsIClassOfService::Unblocked &&
+      !(mClassOfService & nsIClassOfService::TailAllowed)) {
+      return false;
+  }
+
+  if (IsNavigation()) {
+      return false;
+  }
+
+  return true;
+}
+
+bool
+nsHttpChannel::WaitingForTailUnblock()
+{
+  nsresult rv;
+
+  if (!gHttpHandler->IsTailBlockingEnabled()) {
+    LOG(("nsHttpChannel %p tail-blocking disabled", this));
+    return false;
+  }
+
+  if (!EligibleForTailing()) {
+    LOG(("nsHttpChannel %p not eligible for tail-blocking", this));
+    AddAsNonTailRequest();
+    return false;
+  }
+
+  if (!EnsureRequestContext()) {
+    LOG(("nsHttpChannel %p no request context", this));
+    return false;
+  }
+
+  LOG(("nsHttpChannel::WaitingForTailUnblock this=%p, rc=%p",
+       this, mRequestContext.get()));
+
+  bool blocked;
+  rv = mRequestContext->IsContextTailBlocked(this, &blocked);
+  if (NS_FAILED(rv)) {
+    return false;
+  }
+
+  LOG(("  blocked=%d", blocked));
+
+  return blocked;
+}
+
+//-----------------------------------------------------------------------------
+// nsHttpChannel::nsIRequestTailUnblockCallback
+//-----------------------------------------------------------------------------
+
+// Must be implemented in the leaf class because we don't have
+// AsyncAbort in HttpBaseChannel.
+NS_IMETHODIMP
+nsHttpChannel::OnTailUnblock(nsresult rv)
+{
+    LOG(("nsHttpChannel::OnTailUnblock this=%p rv=%" PRIx32 " rc=%p",
+         this, static_cast<uint32_t>(rv), mRequestContext.get()));
+
+    MOZ_RELEASE_ASSERT(mOnTailUnblock);
+
+    if (NS_FAILED(mStatus)) {
+        rv = mStatus;
+    }
+
+    if (NS_SUCCEEDED(rv)) {
+        auto callback = mOnTailUnblock;
+        mOnTailUnblock = nullptr;
+        rv = (this->*callback)();
+    }
+
+    if (NS_FAILED(rv)) {
+        CloseCacheEntry(false);
+        return AsyncAbort(rv);
+    }
+
+    return NS_OK;
+}
+
 void
 nsHttpChannel::SetWarningReporter(HttpChannelSecurityWarningReporter *aReporter)
 {
     LOG(("nsHttpChannel [this=%p] SetWarningReporter [%p]", this, aReporter));
     mWarningReporter = aReporter;
 }
 
 HttpChannelSecurityWarningReporter*
--- a/netwerk/protocol/http/nsHttpChannel.h
+++ b/netwerk/protocol/http/nsHttpChannel.h
@@ -80,16 +80,17 @@ class nsHttpChannel final : public HttpB
                           , public nsIThreadRetargetableRequest
                           , public nsIThreadRetargetableStreamListener
                           , public nsIDNSListener
                           , public nsSupportsWeakReference
                           , public nsICorsPreflightCallback
                           , public nsIChannelWithDivertableParentListener
                           , public nsIHstsPrimingCallback
                           , public nsIRaceCacheWithNetwork
+                          , public nsIRequestTailUnblockCallback
                           , public nsITimerCallback
 {
 public:
     NS_DECL_ISUPPORTS_INHERITED
     NS_DECL_NSIREQUESTOBSERVER
     NS_DECL_NSISTREAMLISTENER
     NS_DECL_NSITHREADRETARGETABLESTREAMLISTENER
     NS_DECL_NSICACHEINFOCHANNEL
@@ -104,16 +105,17 @@ public:
     NS_DECL_NSIASYNCVERIFYREDIRECTCALLBACK
     NS_DECL_NSIHSTSPRIMINGCALLBACK
     NS_DECL_NSITHREADRETARGETABLEREQUEST
     NS_DECL_NSIDNSLISTENER
     NS_DECL_NSICHANNELWITHDIVERTABLEPARENTLISTENER
     NS_DECLARE_STATIC_IID_ACCESSOR(NS_HTTPCHANNEL_IID)
     NS_DECL_NSIRACECACHEWITHNETWORK
     NS_DECL_NSITIMERCALLBACK
+    NS_DECL_NSIREQUESTTAILUNBLOCKCALLBACK
 
     // nsIHttpAuthenticableChannel. We can't use
     // NS_DECL_NSIHTTPAUTHENTICABLECHANNEL because it duplicates cancel() and
     // others.
     NS_IMETHOD GetIsSSL(bool *aIsSSL) override;
     NS_IMETHOD GetProxyMethodIsConnect(bool *aProxyMethodIsConnect) override;
     NS_IMETHOD GetServerResponseHeader(nsACString & aServerResponseHeader) override;
     NS_IMETHOD GetProxyChallenges(nsACString & aChallenges) override;
@@ -320,17 +322,16 @@ private:
     MOZ_MUST_USE nsresult BeginConnectContinue();
     MOZ_MUST_USE nsresult ContinueBeginConnectWithResult();
     void     ContinueBeginConnect();
     MOZ_MUST_USE nsresult OnBeforeConnect();
     void     OnBeforeConnectContinue();
     MOZ_MUST_USE nsresult Connect();
     void     SpeculativeConnect();
     MOZ_MUST_USE nsresult SetupTransaction();
-    void     SetupTransactionRequestContext();
     MOZ_MUST_USE nsresult CallOnStartRequest();
     MOZ_MUST_USE nsresult ProcessResponse();
     void                  AsyncContinueProcessResponse();
     MOZ_MUST_USE nsresult ContinueProcessResponse1();
     MOZ_MUST_USE nsresult ContinueProcessResponse2(nsresult);
     MOZ_MUST_USE nsresult ContinueProcessResponse3(nsresult);
     MOZ_MUST_USE nsresult ProcessNormal();
     MOZ_MUST_USE nsresult ContinueProcessNormal(nsresult);
@@ -668,16 +669,35 @@ private:
     // True if the channel's principal was found on a phishing, malware, or
     // tracking (if tracking protection is enabled) blocklist
     bool                              mLocalBlocklist;
 
     MOZ_MUST_USE nsresult WaitForRedirectCallback();
     void PushRedirectAsyncFunc(nsContinueRedirectionFunc func);
     void PopRedirectAsyncFunc(nsContinueRedirectionFunc func);
 
+    // If this resource is eligible for tailing based on class-of-service flags
+    // and load flags.  We don't tail Leaders/Unblocked/UrgentStart and top-level
+    // loads.
+    bool EligibleForTailing();
+
+    // Called exclusively only from AsyncOpen or after all classification callbacks.
+    // If this channel is 1) Tail, 2) assigned a request context, 3) the context is
+    // still in the tail-blocked phase, then the method will queue this channel.
+    // OnTailUnblock will be called after the context is tail-unblocked or canceled.
+    bool WaitingForTailUnblock();
+
+    // A function we trigger when untail callback is triggered by our request
+    // context in case this channel was tail-blocked.
+    nsresult (nsHttpChannel::*mOnTailUnblock)();
+    // Called on untail when tailed during AsyncOpen execution.
+    nsresult AsyncOpenOnTailUnblock();
+    // Called on untail when tailed because of being a tracking resource.
+    nsresult ConnectOnTailUnblock();
+
     nsCString mUsername;
 
     // If non-null, warnings should be reported to this object.
     RefPtr<HttpChannelSecurityWarningReporter> mWarningReporter;
 
     RefPtr<ADivertableParentChannel> mParentChannel;
 
     // True if the channel is reading from cache.
--- a/netwerk/protocol/http/nsHttpConnection.cpp
+++ b/netwerk/protocol/http/nsHttpConnection.cpp
@@ -121,18 +121,19 @@ nsHttpConnection::~nsHttpConnection()
                               totalKBRead);
     }
     if (mForceSendTimer) {
         mForceSendTimer->Cancel();
         mForceSendTimer = nullptr;
     }
 
     if ((mFastOpenStatus != TFO_FAILED) &&
+        (mFastOpenStatus != TFO_HTTP) &&
         ((mFastOpenStatus != TFO_NOT_TRIED) ||
- #if defined(_WIN64) && defined(WIN95)
+#if defined(_WIN64) && defined(WIN95)
          (gHttpHandler->UseFastOpen() &&
           gSocketTransportService &&
           gSocketTransportService->HasFileDesc2PlatformOverlappedIOHandleFunc()))) {
 #else
          gHttpHandler->UseFastOpen())) {
 #endif
         // TFO_FAILED will be reported in the replacement connection with more
         // details.
--- a/netwerk/protocol/http/nsHttpConnectionMgr.cpp
+++ b/netwerk/protocol/http/nsHttpConnectionMgr.cpp
@@ -3627,22 +3627,16 @@ nsHttpConnectionMgr::GetOrCreateConnecti
             return wildCardEnt;
         }
     }
 
     // step 3
     if (!specificEnt) {
         RefPtr<nsHttpConnectionInfo> clone(specificCI->Clone());
         specificEnt = new nsConnectionEntry(clone);
-#if defined(_WIN64) && defined(WIN95)
-        specificEnt->mUseFastOpen = gHttpHandler->UseFastOpen() &&
-                                    gSocketTransportService->HasFileDesc2PlatformOverlappedIOHandleFunc();
-#else
-        specificEnt->mUseFastOpen = gHttpHandler->UseFastOpen();
-#endif
         mCT.Put(clone->HashKey(), specificEnt);
     }
     return specificEnt;
 }
 
 nsresult
 ConnectionHandle::OnHeadersAvailable(nsAHttpTransaction *trans,
                                      nsHttpRequestHead *req,
@@ -3766,33 +3760,37 @@ nsHalfOpenSocket::nsHalfOpenSocket(nsCon
     , mIsFromPredictor(isFromPredictor)
     , mAllow1918(true)
     , mHasConnected(false)
     , mPrimaryConnectedOK(false)
     , mBackupConnectedOK(false)
     , mFreeToUse(true)
     , mPrimaryStreamStatus(NS_OK)
     , mFastOpenInProgress(false)
-    , mFastOpenStatus(TFO_NOT_TRIED)
     , mEnt(ent)
 {
     MOZ_ASSERT(ent && trans, "constructor with null arguments");
     LOG(("Creating nsHalfOpenSocket [this=%p trans=%p ent=%s key=%s]\n",
          this, trans, ent->mConnInfo->Origin(), ent->mConnInfo->HashKey().get()));
 
     if (speculative) {
         Telemetry::AutoCounter<Telemetry::HTTPCONNMGR_TOTAL_SPECULATIVE_CONN> totalSpeculativeConn;
         ++totalSpeculativeConn;
 
         if (isFromPredictor) {
           Telemetry::AutoCounter<Telemetry::PREDICTOR_TOTAL_PRECONNECTS_CREATED> totalPreconnectsCreated;
           ++totalPreconnectsCreated;
         }
     }
 
+    if (mEnt->mConnInfo->FirstHopSSL()) {
+      mFastOpenStatus = TFO_NOT_TRIED;
+    } else {
+      mFastOpenStatus = TFO_HTTP;
+    }
     MOZ_ASSERT(mEnt);
 }
 
 nsHttpConnectionMgr::nsHalfOpenSocket::~nsHalfOpenSocket()
 {
     MOZ_ASSERT(!mStreamOut);
     MOZ_ASSERT(!mBackupStreamOut);
     MOZ_ASSERT(!mSynTimer);
@@ -4955,17 +4953,28 @@ nsConnectionEntry::nsConnectionEntry(nsH
     : mConnInfo(ci)
     , mUsingSpdy(false)
     , mPreferIPv4(false)
     , mPreferIPv6(false)
     , mUsedForConnection(false)
     , mDoNotDestroy(false)
 {
     MOZ_COUNT_CTOR(nsConnectionEntry);
-    mUseFastOpen = gHttpHandler->UseFastOpen();
+
+    if (mConnInfo->FirstHopSSL()) {
+#if defined(_WIN64) && defined(WIN95)
+        mUseFastOpen = gHttpHandler->UseFastOpen() &&
+                       gSocketTransportService->HasFileDesc2PlatformOverlappedIOHandleFunc();
+#else
+        mUseFastOpen = gHttpHandler->UseFastOpen();
+#endif
+    } else {
+        // Only allow the TCP fast open on a secure connection.
+        mUseFastOpen = false;
+    }
 
     LOG(("nsConnectionEntry::nsConnectionEntry this=%p key=%s",
          this, ci->HashKey().get()));
 }
 
 bool
 nsHttpConnectionMgr::nsConnectionEntry::AvailableForDispatchNow()
 {
--- a/netwerk/protocol/http/nsHttpHandler.cpp
+++ b/netwerk/protocol/http/nsHttpHandler.cpp
@@ -193,16 +193,20 @@ nsHttpHandler::nsHttpHandler()
     , mMaxPersistentConnectionsPerServer(2)
     , mMaxPersistentConnectionsPerProxy(4)
     , mThrottleEnabled(true)
     , mThrottleSuspendFor(3000)
     , mThrottleResumeFor(200)
     , mThrottleResumeIn(400)
     , mThrottleTimeWindow(3000)
     , mUrgentStartEnabled(true)
+    , mTailBlockingEnabled(true)
+    , mTailDelayQuantum(600)
+    , mTailDelayQuantumAfterDCL(100)
+    , mTailDelayMax(6000)
     , mRedirectionLimit(10)
     , mPhishyUserPassLength(1)
     , mQoSBits(0x00)
     , mEnforceAssocReq(false)
     , mLastUniqueID(NowInSeconds())
     , mSessionStartTime(0)
     , mLegacyAppName("Mozilla")
     , mLegacyAppVersion("5.0")
@@ -1648,16 +1652,32 @@ nsHttpHandler::PrefsChanged(nsIPrefBranc
                                         mThrottleTimeWindow);
       }
     }
 
     if (PREF_CHANGED(HTTP_PREF("on_click_priority"))) {
         Unused << prefs->GetBoolPref(HTTP_PREF("on_click_priority"), &mUrgentStartEnabled);
     }
 
+    if (PREF_CHANGED(HTTP_PREF("tailing.enabled"))) {
+        Unused << prefs->GetBoolPref(HTTP_PREF("tailing.enabled"), &mTailBlockingEnabled);
+    }
+    if (PREF_CHANGED(HTTP_PREF("tailing.delay-quantum"))) {
+        Unused << prefs->GetIntPref(HTTP_PREF("tailing.delay-quantum"), &val);
+        mTailDelayQuantum = (uint32_t)clamped(val, 0, 60000);
+    }
+    if (PREF_CHANGED(HTTP_PREF("tailing.delay-quantum-after-domcontentloaded"))) {
+        Unused << prefs->GetIntPref(HTTP_PREF("tailing.delay-quantum-after-domcontentloaded"), &val);
+        mTailDelayQuantumAfterDCL = (uint32_t)clamped(val, 0, 60000);
+    }
+    if (PREF_CHANGED(HTTP_PREF("tailing.delay-max"))) {
+        Unused << prefs->GetIntPref(HTTP_PREF("tailing.delay-max"), &val);
+        mTailDelayMax = (uint32_t)clamped(val, 0, 60000);
+    }
+
     if (PREF_CHANGED(HTTP_PREF("focused_window_transaction_ratio"))) {
         float ratio = 0;
         rv = prefs->GetFloatPref(HTTP_PREF("focused_window_transaction_ratio"), &ratio);
         if (NS_SUCCEEDED(rv)) {
             if (ratio > 0 && ratio < 1) {
                 mFocusedWindowTransactionRatio = ratio;
             } else {
                 NS_WARNING("Wrong value for focused_window_transaction_ratio");
--- a/netwerk/protocol/http/nsHttpHandler.h
+++ b/netwerk/protocol/http/nsHttpHandler.h
@@ -131,16 +131,21 @@ public:
     uint32_t       MaxConnectionsPerOrigin() { return mMaxPersistentConnectionsPerServer; }
     bool           UseRequestTokenBucket() { return mRequestTokenBucketEnabled; }
     uint16_t       RequestTokenBucketMinParallelism() { return mRequestTokenBucketMinParallelism; }
     uint32_t       RequestTokenBucketHz() { return mRequestTokenBucketHz; }
     uint32_t       RequestTokenBucketBurst() {return mRequestTokenBucketBurst; }
 
     bool           PromptTempRedirect()      { return mPromptTempRedirect; }
     bool           IsUrgentStartEnabled() { return mUrgentStartEnabled; }
+    bool           IsTailBlockingEnabled() { return mTailBlockingEnabled; }
+    uint32_t       TailBlockingDelayQuantum(bool aAfterDOMContentLoaded) {
+      return aAfterDOMContentLoaded ? mTailDelayQuantumAfterDCL : mTailDelayQuantum;
+    }
+    uint32_t       TailBlockingDelayMax() { return mTailDelayMax; }
 
     // TCP Keepalive configuration values.
 
     // Returns true if TCP keepalive should be enabled for short-lived conns.
     bool TCPKeepaliveEnabledForShortLivedConns() {
       return mTCPKeepaliveShortLivedEnabled;
     }
     // Return time (secs) that a connection is consider short lived (for TCP
@@ -459,16 +464,20 @@ private:
 
     bool mThrottleEnabled;
     uint32_t mThrottleSuspendFor;
     uint32_t mThrottleResumeFor;
     uint32_t mThrottleResumeIn;
     uint32_t mThrottleTimeWindow;
 
     bool mUrgentStartEnabled;
+    bool mTailBlockingEnabled;
+    uint32_t mTailDelayQuantum;
+    uint32_t mTailDelayQuantumAfterDCL;
+    uint32_t mTailDelayMax;
 
     uint8_t  mRedirectionLimit;
 
     // we'll warn the user if we load an URL containing a userpass field
     // unless its length is less than this threshold.  this warning is
     // intended to protect the user against spoofing attempts that use
     // the userpass field of the URL to obscure the actual origin server.
     uint8_t  mPhishyUserPassLength;
--- a/testing/gtest/bench.py
+++ b/testing/gtest/bench.py
@@ -1,9 +1,9 @@
-#!/usr/bin/python3
+#!/usr/bin/env python3
 
 import sys, subprocess, json, statistics
 
 proc = subprocess.Popen(["./mach", "gtest", sys.argv[1]], stdout=subprocess.PIPE)
 for line in proc.stdout:
     if line.startswith(b"PERFHERDER_DATA:"):
         data = json.loads(line[len("PERFHERDER_DATA:"):].decode("utf8"))
         for suite in data["suites"]:
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/meta/dom/__dir__.ini
@@ -0,0 +1,1 @@
+prefs: [dom.abortController.enabled:true]
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/meta/dom/abort/__dir__.ini
@@ -0,0 +1,1 @@
+prefs: [dom.abortController.enabled:true]
--- a/testing/web-platform/meta/fetch/api/abort/__dir__.ini
+++ b/testing/web-platform/meta/fetch/api/abort/__dir__.ini
@@ -1,2 +1,4 @@
 prefs: [javascript.options.streams:true,
+        dom.abortController.enabled:true,
+        dom.abortController.fetch.enabled:true,
         dom.streams.enabled:true]
--- a/toolkit/components/places/FaviconHelpers.cpp
+++ b/toolkit/components/places/FaviconHelpers.cpp
@@ -3,16 +3,17 @@
  * This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "FaviconHelpers.h"
 
 #include "nsICacheEntry.h"
 #include "nsICachingChannel.h"
+#include "nsIClassOfService.h"
 #include "nsIAsyncVerifyRedirectCallback.h"
 #include "nsIPrincipal.h"
 
 #include "nsNavHistory.h"
 #include "nsFaviconService.h"
 #include "mozilla/storage.h"
 #include "mozilla/Telemetry.h"
 #include "nsNetUtil.h"
@@ -588,16 +589,22 @@ AsyncFetchAndSetIconForPage::FetchFromNe
     NS_ENSURE_SUCCESS(rv, rv);
   }
 
   nsCOMPtr<nsISupportsPriority> priorityChannel = do_QueryInterface(channel);
   if (priorityChannel) {
     priorityChannel->AdjustPriority(nsISupportsPriority::PRIORITY_LOWEST);
   }
 
+  nsCOMPtr<nsIClassOfService> cos = do_QueryInterface(channel);
+  if (cos) {
+    cos->AddClassFlags(nsIClassOfService::Tail |
+                       nsIClassOfService::Throttleable);
+  }
+
   rv = channel->AsyncOpen2(this);
   if (NS_SUCCEEDED(rv)) {
     mRequest = channel;
   }
   return rv;
 }
 
 NS_IMETHODIMP
--- a/toolkit/modules/Timer.jsm
+++ b/toolkit/modules/Timer.jsm
@@ -40,18 +40,18 @@ function _setTimeoutOrIsInterval(aCallba
     notify() {
       if (!aIsInterval) {
         gTimerTable.delete(id);
       }
       aCallback.apply(null, aArgs);
     },
 
     // nsINamed
-    name: aIsInterval ? "setInterval() in Timer.jsm"
-                      : "setTimeout() in Timer.jsm",
+    name: (aIsInterval ? "setInterval() for " : "setTimeout() for ") +
+            Cu.generateXPCWrappedJS(aCallback).QueryInterface(Ci.nsINamed).name,
   };
 
   timer.initWithCallback(callback, aMilliseconds,
     aIsInterval ? timer.TYPE_REPEATING_SLACK : timer.TYPE_ONE_SHOT);
 
   gTimerTable.set(id, timer);
   return id;
 }
--- a/toolkit/mozapps/extensions/test/xpcshell/test_backgroundupdate.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_backgroundupdate.js
@@ -82,16 +82,19 @@ function run_test_2() {
     targetApplications: [{
       id: "xpcshell@tests.mozilla.org",
       minVersion: "1",
       maxVersion: "1"
     }],
     name: "Test Addon 3",
   }, profileDir);
 
+  // Disable rcwn to make cache behavior deterministic.
+  Services.prefs.setBoolPref("network.http.rcwn.enabled", false);
+
   // Background update uses a different pref, if set
   Services.prefs.setCharPref("extensions.update.background.url",
                              "http://localhost:" + gPort + "/data/test_backgroundupdate.rdf");
   restartManager();
 
   // Do hotfix checks
   Services.prefs.setCharPref("extensions.hotfix.id", "hotfix@tests.mozilla.org");
   Services.prefs.setCharPref("extensions.hotfix.url", "http://localhost:" + gPort + "/missing.rdf");
--- a/xpcom/reflect/xptcall/md/unix/xptcinvoke_asm_mips64.S
+++ b/xpcom/reflect/xptcall/md/unix/xptcinvoke_asm_mips64.S
@@ -62,20 +62,20 @@ NESTED(_NS_InvokeByIndex, FRAMESZ, ra)
     PTR_SUBU sp, sp, 16
     REG_S    t0, 0(sp)
     REG_S    a0, 8(sp)
 
     # copy the param into the stack areas
     jal      invoke_copy_to_stack
 
     REG_L    t3, 8(sp)     # get previous a0
-    REG_L    sp, 0(sp)     # get orig sp back
+    REG_L    s0, 0(sp)     # get orig sp back and save away our stack pointer
 
-    REG_L    a0, A0OFF(sp) # a0 - that
-    REG_L    a1, A1OFF(sp) # a1 - methodIndex
+    REG_L    a0, A0OFF(s0) # a0 - that
+    REG_L    a1, A1OFF(s0) # a1 - methodIndex
 
     # t1 = methodIndex * pow(2, PTRLOG)
     # (use shift instead of mult)
     sll      t1, a1, PTRLOG
 
     # calculate the function we need to jump to,
     # which must then be saved in t9
     PTR_L    t9, 0(a0)
@@ -100,23 +100,22 @@ NESTED(_NS_InvokeByIndex, FRAMESZ, ra)
     l.d      $f13,  0(t1)
     l.d      $f14,  8(t1)
     l.d      $f15, 16(t1)
     l.d      $f16, 24(t1)
     l.d      $f17, 32(t1)
     l.d      $f18, 40(t1)
     l.d      $f19, 48(t1)
 
-    # save away our stack pointer and create
-    # the stack pointer for the function
-    move     s0, sp
+    # create the stack pointer for the function
     move     sp, t3
 
     jalr     t9
 
+    ## restore stack pointer.
     move     sp, s0
 
     RESTORE_GP64
     REG_L    ra, RAOFF(sp)
     REG_L    s0, S0OFF(sp)
     PTR_ADDU sp, FRAMESZ
     j    ra
 .end _NS_InvokeByIndex