merge mozilla-inbound to mozilla-central. r=merge a=merge
authorSebastian Hengst <archaeopteryx@coole-files.de>
Thu, 31 Aug 2017 14:36:51 +0200
changeset 656655 fb22415719a9d971a2646fa2d1b74e134ca00c3d
parent 656654 c9079d347aaa559beecf0db6e5414f92ef043485 (current diff)
parent 656491 a2723b65046096e587b968a3b6b1cb056c914b78 (diff)
child 656656 1989519c5034b759b91e9241bcfc914cce671228
child 656669 93ed0305b3a3d2966fddb2f9871796d9a4f7ef94
child 656680 90922503ef351dece72f9e3c876c7eb49bd16a33
child 656683 6007d13fbaffd2b32e013362e2be87ddc9e6d658
child 656696 958257f5e185a1179ffaf06802efaeaf8f9ee892
child 656697 fe1d62cbd616f9c407396e18f96952ef789bee4f
child 656698 ffb35b08eef891a0ae00aa2c243a358b025919a0
child 656711 4d573b57d0b45bd9b36e88950b7f567436b1c204
child 656712 ee5eb935f73f7729706c6781451710e6a86935e3
child 656731 bd99b156f9e465654134c28bb595801095470159
child 656761 7941be087d4d1251a90f84c0a7162cffa4b1799d
child 656763 ccc8ce45da5f0c88e37fb1b9a1bb7bf9873f2751
child 656770 ee9738539125d058bdcf138bdadf80d738140508
child 656771 c683b6055587e6c7de58af48ee2518f4a0539093
child 656772 9c800ec2734b9b78b574d3877177d79b6acfd930
child 656773 344928369aac031b4d46d8a33f0c700c61615169
child 656775 e47d186d0a5aba6f2d140be4fb1bd9bc11d3558c
child 656779 068fda08a2e69d16bbdfc998c50aeccb146500f4
child 656780 dc2840fea95f22f354f75ffa378b5bfbdfcfee3b
child 656782 4f503b4503c3d476b15bb81ae64793581e6360d0
child 656841 5593f19422c3283afc8e8f5aab20a91c4dafbfe3
child 656842 d126a184d933a69288b7c44c90412320a0cdc28d
child 656843 d790f3be71037aeee7d8b55880280beb310afb79
child 656862 568e314287f384c0480c6fc7bb9f7beb30a5e2e8
child 656863 2ea2c47d133a906c251bcab5652c6370b7accd53
child 656864 c1c8115c51be0cd6e189cafa9ed5f0771531dafa
child 656866 f036b79c3f450463f349ba683fb7c48658cd9336
child 656867 e033a0187fe96505926f8cb2fcd79b8ee7f3c519
child 656868 b4b95e85b7b1ad30a408382ea1e646363f607e6a
child 656879 62aa05ae30d9a5dba68d4f5849896dfbea8a1f75
child 656897 6d9936496fbe26004f8473140505d67e1aa09df3
child 656906 9f5def6048b7a4eb61e2d79b159475a9ba83d502
child 656946 ed0d1e893a5afd9584e7c92b618d39c10468888c
child 656961 9636f23e36df28785a7a51965d8b8e4012932bb4
child 656984 41e72d61f2740ea293613c41442ea32270206ef0
child 656996 96efac7afa5ad5c8e9374ca15fccdc5247c826b3
child 657346 e4e5f06f1be619756d31954533e68eb18b356e88
child 657357 90e47f34f8d5b0b9b1b2a1c3bba61e7b6d1c2bd8
child 657418 daaf62a2559256b40e3914a0ba20affdce3e4ee4
child 657447 bfdea0f64b9671faac79a2117962d0fba9cce697
child 657506 f73a1f41a0aada6f3252c590b5396a45216adf9d
child 657540 bd509911278327f6a9293d038a14da0737a21275
child 657599 a3d769c12c9f99da9fe1482cb1f8f34ce117d7b5
child 657697 345b17f0db03afbe6de35e52661e8a89ba5571b8
child 657769 26fb2a615fa1699afe15a411999865b5789ce5e2
child 657782 f254635a94ff2c08f0d89f82455c0023c29499d7
child 658364 1be6661dfa6ce69a9fb77fbb7dca6d1d0f27b7df
child 659086 668e56bacc872b05cd8e4e505f4adc5abdaab7e8
child 660788 66124064c869d6d95e1f76cd1a0c78247820a9b5
push id77274
push userbmo:ato@sny.no
push dateThu, 31 Aug 2017 12:54:27 +0000
reviewersmerge, merge
milestone57.0a1
merge mozilla-inbound to mozilla-central. r=merge a=merge MozReview-Commit-ID: 4IUksc3Mn9i
devtools/client/inspector/rules/test/browser.ini
ipc/mscom/RegistrationAnnotator.cpp
modules/libpref/init/all.js
--- 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