Merge inbound to central, a=merge
authorWes Kocher <wkocher@mozilla.com>
Thu, 01 Jun 2017 17:26:55 -0700
changeset 361912 1e229cf8933b08191e4243a6b9dbb3c821db60ab
parent 361898 fec3a4b50acd88df3b4af2dfe645a919713852a9 (current diff)
parent 361911 130dc8355e27d6161af44fb2e0e65212f75bd730 (diff)
child 361913 877c3da58fa35bf4e58f2ac0cbcf59840702cc6e
child 361937 fcf76659f264e52a535658aa826271e7b0559c3f
child 361984 f0f74276deb7bdfb29388574aa5793dc7c53df17
push id31948
push userkwierso@gmail.com
push dateFri, 02 Jun 2017 00:27:01 +0000
treeherdermozilla-central@1e229cf8933b [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone55.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge inbound to central, a=merge MozReview-Commit-ID: AWwXRG9NEWV
--- a/devtools/client/inspector/grids/actions/highlighter-settings.js
+++ b/devtools/client/inspector/grids/actions/highlighter-settings.js
@@ -1,22 +1,36 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 const {
+  UPDATE_SHOW_GRID_AREAS,
   UPDATE_SHOW_GRID_LINE_NUMBERS,
   UPDATE_SHOW_INFINITE_LINES,
 } = require("./index");
 
 module.exports = {
 
   /**
+   * Update the grid highlighter's show grid areas preference.
+   *
+   * @param  {Boolean} enabled
+   *         Whether or not the grid highlighter should show the grid areas.
+   */
+  updateShowGridAreas(enabled) {
+    return {
+      type: UPDATE_SHOW_GRID_AREAS,
+      enabled,
+    };
+  },
+
+  /**
    * Update the grid highlighter's show grid line numbers preference.
    *
    * @param  {Boolean} enabled
    *         Whether or not the grid highlighter should show the grid line numbers.
    */
   updateShowGridLineNumbers(enabled) {
     return {
       type: UPDATE_SHOW_GRID_LINE_NUMBERS,
--- a/devtools/client/inspector/grids/actions/index.js
+++ b/devtools/client/inspector/grids/actions/index.js
@@ -12,15 +12,18 @@ createEnum([
   "UPDATE_GRID_COLOR",
 
   // Update the grid highlighted state.
   "UPDATE_GRID_HIGHLIGHTED",
 
   // Update the entire grids state with the new list of grids.
   "UPDATE_GRIDS",
 
+  // Update the grid highlighter's show grid areas state.
+  "UPDATE_SHOW_GRID_AREAS",
+
   // Update the grid highlighter's show grid line numbers state.
   "UPDATE_SHOW_GRID_LINE_NUMBERS",
 
   // Update the grid highlighter's show infinite lines state.
   "UPDATE_SHOW_INFINITE_LINES",
 
 ], module.exports);
--- a/devtools/client/inspector/grids/components/Grid.js
+++ b/devtools/client/inspector/grids/components/Grid.js
@@ -26,37 +26,39 @@ module.exports = createClass({
     showGridOutline: PropTypes.bool.isRequired,
     onHideBoxModelHighlighter: PropTypes.func.isRequired,
     onSetGridOverlayColor: PropTypes.func.isRequired,
     onShowBoxModelHighlighterForNode: PropTypes.func.isRequired,
     onShowGridAreaHighlight: PropTypes.func.isRequired,
     onShowGridCellHighlight: PropTypes.func.isRequired,
     onShowGridLineNamesHighlight: PropTypes.func.isRequired,
     onToggleGridHighlighter: PropTypes.func.isRequired,
+    onToggleShowGridAreas: PropTypes.func.isRequired,
     onToggleShowGridLineNumbers: PropTypes.func.isRequired,
     onToggleShowInfiniteLines: PropTypes.func.isRequired,
   },
 
   mixins: [ addons.PureRenderMixin ],
 
   render() {
     let {
       getSwatchColorPickerTooltip,
       grids,
       highlighterSettings,
       setSelectedNode,
       showGridOutline,
       onHideBoxModelHighlighter,
       onSetGridOverlayColor,
       onShowBoxModelHighlighterForNode,
+      onShowGridAreaHighlight,
+      onShowGridCellHighlight,
+      onToggleShowGridAreas,
       onToggleGridHighlighter,
       onToggleShowGridLineNumbers,
       onToggleShowInfiniteLines,
-      onShowGridAreaHighlight,
-      onShowGridCellHighlight,
     } = this.props;
 
     return grids.length ?
       dom.div(
         {
           id: "layout-grid-container",
         },
         dom.div(
@@ -69,16 +71,17 @@ module.exports = createClass({
             setSelectedNode,
             onHideBoxModelHighlighter,
             onSetGridOverlayColor,
             onShowBoxModelHighlighterForNode,
             onToggleGridHighlighter,
           }),
           GridDisplaySettings({
             highlighterSettings,
+            onToggleShowGridAreas,
             onToggleShowGridLineNumbers,
             onToggleShowInfiniteLines,
           })
         ),
         showGridOutline ?
           GridOutline({
             grids,
             onShowGridAreaHighlight,
--- a/devtools/client/inspector/grids/components/GridDisplaySettings.js
+++ b/devtools/client/inspector/grids/components/GridDisplaySettings.js
@@ -11,22 +11,32 @@ const Types = require("../types");
 const { getStr } = require("../utils/l10n");
 
 module.exports = createClass({
 
   displayName: "GridDisplaySettings",
 
   propTypes: {
     highlighterSettings: PropTypes.shape(Types.highlighterSettings).isRequired,
+    onToggleShowGridAreas: PropTypes.func.isRequired,
     onToggleShowGridLineNumbers: PropTypes.func.isRequired,
     onToggleShowInfiniteLines: PropTypes.func.isRequired,
   },
 
   mixins: [ addons.PureRenderMixin ],
 
+  onShowGridAreasCheckboxClick() {
+    let {
+      highlighterSettings,
+      onToggleShowGridAreas,
+    } = this.props;
+
+    onToggleShowGridAreas(!highlighterSettings.showGridAreasOverlay);
+  },
+
   onShowGridLineNumbersCheckboxClick() {
     let {
       highlighterSettings,
       onToggleShowGridLineNumbers,
     } = this.props;
 
     onToggleShowGridLineNumbers(!highlighterSettings.showGridLineNumbers);
   },
@@ -83,14 +93,31 @@ module.exports = createClass({
                 id: "grid-setting-show-grid-line-numbers",
                 type: "checkbox",
                 checked: highlighterSettings.showGridLineNumbers,
                 onChange: this.onShowGridLineNumbersCheckboxClick,
               }
             ),
             getStr("layout.displayNumbersOnLines")
           )
+        ),
+        dom.li(
+          {
+            className: "grid-settings-item",
+          },
+          dom.label(
+           {},
+           dom.input(
+             {
+               id: "grid-setting-show-grid-areas",
+               type: "checkbox",
+               checked: highlighterSettings.showGridAreasOverlay,
+               onChange: this.onShowGridAreasCheckboxClick,
+             }
+           ),
+           getStr("layout.displayGridAreas")
+          )
         )
       )
     );
   },
 
 });
--- a/devtools/client/inspector/grids/grid-inspector.js
+++ b/devtools/client/inspector/grids/grid-inspector.js
@@ -10,20 +10,22 @@ const { Task } = require("devtools/share
 const SwatchColorPickerTooltip = require("devtools/client/shared/widgets/tooltip/SwatchColorPickerTooltip");
 
 const {
   updateGridColor,
   updateGridHighlighted,
   updateGrids,
 } = require("./actions/grids");
 const {
+  updateShowGridAreas,
   updateShowGridLineNumbers,
   updateShowInfiniteLines,
 } = require("./actions/highlighter-settings");
 
+const SHOW_GRID_AREAS = "devtools.gridinspector.showGridAreas";
 const SHOW_GRID_LINE_NUMBERS = "devtools.gridinspector.showGridLineNumbers";
 const SHOW_INFINITE_LINES_PREF = "devtools.gridinspector.showInfiniteLines";
 
 // Default grid colors.
 const GRID_COLORS = [
   "#4B0082",
   "#BB9DFF",
   "#FFB53B",
@@ -49,16 +51,17 @@ function GridInspector(inspector, window
   this.onMarkupMutation = this.onMarkupMutation.bind(this);
   this.onReflow = this.onReflow.bind(this);
   this.onSetGridOverlayColor = this.onSetGridOverlayColor.bind(this);
   this.onShowGridAreaHighlight = this.onShowGridAreaHighlight.bind(this);
   this.onShowGridCellHighlight = this.onShowGridCellHighlight.bind(this);
   this.onShowGridLineNamesHighlight = this.onShowGridLineNamesHighlight.bind(this);
   this.onSidebarSelect = this.onSidebarSelect.bind(this);
   this.onToggleGridHighlighter = this.onToggleGridHighlighter.bind(this);
+  this.onToggleShowGridAreas = this.onToggleShowGridAreas.bind(this);
   this.onToggleShowGridLineNumbers = this.onToggleShowGridLineNumbers.bind(this);
   this.onToggleShowInfiniteLines = this.onToggleShowInfiniteLines.bind(this);
 
   this.init();
 }
 
 GridInspector.prototype = {
 
@@ -119,16 +122,17 @@ GridInspector.prototype = {
   getComponentProps() {
     return {
       getSwatchColorPickerTooltip: this.getSwatchColorPickerTooltip,
       onSetGridOverlayColor: this.onSetGridOverlayColor,
       onShowGridAreaHighlight: this.onShowGridAreaHighlight,
       onShowGridCellHighlight: this.onShowGridCellHighlight,
       onShowGridLineNamesHighlight: this.onShowGridLineNamesHighlight,
       onToggleGridHighlighter: this.onToggleGridHighlighter,
+      onToggleShowGridAreas: this.onToggleShowGridAreas,
       onToggleShowGridLineNumbers: this.onToggleShowGridLineNumbers,
       onToggleShowInfiniteLines: this.onToggleShowInfiniteLines,
     };
   },
 
   /**
    * Returns the initial color linked to a grid container. Will attempt to check the
    * current grid highlighter state and the store.
@@ -210,19 +214,21 @@ GridInspector.prototype = {
   },
 
   /**
    * Load the grid highligher display settings into the store from the stored preferences.
    */
   loadHighlighterSettings() {
     let { dispatch } = this.store;
 
+    let showGridAreas = Services.prefs.getBoolPref(SHOW_GRID_AREAS);
     let showGridLineNumbers = Services.prefs.getBoolPref(SHOW_GRID_LINE_NUMBERS);
     let showInfinteLines = Services.prefs.getBoolPref(SHOW_INFINITE_LINES_PREF);
 
+    dispatch(updateShowGridAreas(showGridAreas));
     dispatch(updateShowGridLineNumbers(showGridLineNumbers));
     dispatch(updateShowInfiniteLines(showInfinteLines));
   },
 
   showGridHighlighter(node, settings) {
     this.lastHighlighterColor = settings.color;
     this.lastHighlighterNode = node;
     this.lastHighlighterState = true;
@@ -491,16 +497,38 @@ GridInspector.prototype = {
     let highlighterSettings = this.getGridHighlighterSettings(node);
     this.toggleGridHighlighter(node, highlighterSettings);
 
     this.store.dispatch(updateGridHighlighted(node,
       node !== this.highlighters.gridHighlighterShown));
   },
 
   /**
+    * Handler for a change in the show grid areas checkbox in the GridDisplaySettings
+    * component. Toggles on/off the option to show the grid areas in the grid highlighter.
+    * Refreshes the shown grid highlighter for the grids currently highlighted.
+    *
+    * @param  {Boolean} enabled
+    *         Whether or not the grid highlighter should show the grid areas.
+    */
+  onToggleShowGridAreas(enabled) {
+    this.store.dispatch(updateShowGridAreas(enabled));
+    Services.prefs.setBoolPref(SHOW_GRID_AREAS, enabled);
+
+    let { grids } = this.store.getState();
+
+    for (let grid of grids) {
+      if (grid.highlighted) {
+        let highlighterSettings = this.getGridHighlighterSettings(grid.nodeFront);
+        this.highlighters.showGridHighlighter(grid.nodeFront, highlighterSettings);
+      }
+    }
+  },
+
+  /**
    * Handler for a change in the show grid line numbers checkbox in the
    * GridDisplaySettings component. Toggles on/off the option to show the grid line
    * numbers in the grid highlighter. Refreshes the shown grid highlighter for the
    * grids currently highlighted.
    *
    * @param  {Boolean} enabled
    *         Whether or not the grid highlighter should show the grid line numbers.
    */
--- a/devtools/client/inspector/grids/reducers/highlighter-settings.js
+++ b/devtools/client/inspector/grids/reducers/highlighter-settings.js
@@ -1,26 +1,34 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 const {
+  UPDATE_SHOW_GRID_AREAS,
   UPDATE_SHOW_GRID_LINE_NUMBERS,
   UPDATE_SHOW_INFINITE_LINES
 } = require("../actions/index");
 
 const INITIAL_HIGHLIGHTER_SETTINGS = {
+  showGridAreasOverlay: false,
   showGridLineNumbers: false,
   showInfiniteLines: false,
 };
 
 let reducers = {
 
+  [UPDATE_SHOW_GRID_AREAS](highlighterSettings, { enabled }) {
+    return Object.assign({}, highlighterSettings, {
+      showGridAreasOverlay: enabled,
+    });
+  },
+
   [UPDATE_SHOW_GRID_LINE_NUMBERS](highlighterSettings, { enabled }) {
     return Object.assign({}, highlighterSettings, {
       showGridLineNumbers: enabled,
     });
   },
 
   [UPDATE_SHOW_INFINITE_LINES](highlighterSettings, { enabled }) {
     return Object.assign({}, highlighterSettings, {
--- a/devtools/client/inspector/grids/test/browser.ini
+++ b/devtools/client/inspector/grids/test/browser.ini
@@ -8,16 +8,17 @@ support-files =
   !/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
   !/devtools/client/framework/test/shared-redux-head.js
 
 [browser_grids_display-setting-extend-grid-lines.js]
 [browser_grids_display-setting-show-grid-line-numbers.js]
+[browser_grids_display-setting-show-grid-areas.js]
 [browser_grids_grid-list-color-picker-on-ESC.js]
 [browser_grids_grid-list-color-picker-on-RETURN.js]
 [browser_grids_grid-list-element-rep.js]
 [browser_grids_grid-list-no-grids.js]
 [browser_grids_grid-list-on-mutation-element-added.js]
 [browser_grids_grid-list-on-mutation-element-removed.js]
 [browser_grids_grid-list-toggle-multiple-grids.js]
 [browser_grids_grid-list-toggle-single-grid.js]
new file mode 100644
--- /dev/null
+++ b/devtools/client/inspector/grids/test/browser_grids_display-setting-show-grid-areas.js
@@ -0,0 +1,51 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Tests that the 'Display grid areas' grid highlighter setting will update
+// the redux store and pref setting.
+
+const TEST_URI = `
+  <style type='text/css'>
+    #grid {
+      display: grid;
+    }
+  </style>
+  <div id="grid">
+    <div id="cell1">cell1</div>
+    <div id="cell2">cell2</div>
+  </div>
+`;
+
+const SHOW_GRID_AREAS_PREF = "devtools.gridinspector.showGridAreas";
+
+add_task(function* () {
+  yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
+  let { inspector, gridInspector } = yield openLayoutView();
+  let { document: doc } = gridInspector;
+  let { store } = inspector;
+
+  yield selectNode("#grid", inspector);
+  let checkbox = doc.getElementById("grid-setting-show-grid-areas");
+
+  ok(!Services.prefs.getBoolPref(SHOW_GRID_AREAS_PREF),
+    "'Display grid areas' is pref off by default.");
+
+  info("Toggling ON the 'Display grid areas' setting.");
+  let onCheckboxChange = waitUntilState(store, state =>
+    state.highlighterSettings.showGridAreasOverlay);
+  checkbox.click();
+  yield onCheckboxChange;
+
+  info("Toggling OFF the 'Display grid areas' setting.");
+  onCheckboxChange = waitUntilState(store, state =>
+    !state.highlighterSettings.showGridAreasOverlay);
+  checkbox.click();
+  yield onCheckboxChange;
+
+  ok(!Services.prefs.getBoolPref(SHOW_GRID_AREAS_PREF),
+    "'Display grid areas' is pref off.");
+
+  Services.prefs.clearUserPref(SHOW_GRID_AREAS_PREF);
+});
--- a/devtools/client/inspector/layout/layout.js
+++ b/devtools/client/inspector/layout/layout.js
@@ -46,16 +46,17 @@ LayoutView.prototype = {
 
     let {
       getSwatchColorPickerTooltip,
       onSetGridOverlayColor,
       onShowGridAreaHighlight,
       onShowGridCellHighlight,
       onShowGridLineNamesHighlight,
       onToggleGridHighlighter,
+      onToggleShowGridAreas,
       onToggleShowGridLineNumbers,
       onToggleShowInfiniteLines,
     } = this.inspector.gridInspector.getComponentProps();
 
     let app = App({
       getSwatchColorPickerTooltip,
       setSelectedNode,
       /**
@@ -75,16 +76,17 @@ LayoutView.prototype = {
       onShowBoxModelEditor,
       onShowBoxModelHighlighter,
       onShowBoxModelHighlighterForNode,
       onShowGridAreaHighlight,
       onShowGridCellHighlight,
       onShowGridLineNamesHighlight,
       onToggleGeometryEditor,
       onToggleGridHighlighter,
+      onToggleShowGridAreas,
       onToggleShowGridLineNumbers,
       onToggleShowInfiniteLines,
     });
 
     let provider = createElement(Provider, {
       store: this.store,
       id: "layoutview",
       title: INSPECTOR_L10N.getStr("inspector.sidebar.layoutViewTitle2"),
--- a/devtools/client/jsonview/converter-child.js
+++ b/devtools/client/jsonview/converter-child.js
@@ -46,125 +46,195 @@ Converter.prototype = {
   },
 
   /**
    * This component works as such:
    * 1. asyncConvertData captures the listener
    * 2. onStartRequest fires, initializes stuff, modifies the listener
    *    to match our output type
    * 3. onDataAvailable spits it back to the listener
-   * 4. onStopRequest spits it back to the listener and initializes
-        the JSON Viewer
+   * 4. onStopRequest spits it back to the listener
    * 5. convert does nothing, it's just the synchronous version
    *    of asyncConvertData
    */
   convert: function (fromStream, fromType, toType, ctx) {
     return fromStream;
   },
 
   asyncConvertData: function (fromType, toType, listener, ctx) {
     this.listener = listener;
   },
 
   onDataAvailable: function (request, context, inputStream, offset, count) {
     this.listener.onDataAvailable(...arguments);
   },
 
   onStartRequest: function (request, context) {
-    this.channel = request;
+    // Set the content type to HTML in order to parse the doctype, styles
+    // and scripts, but later a <plaintext> element will switch the tokenizer
+    // to the plaintext state in order to parse the JSON.
+    request.QueryInterface(Ci.nsIChannel);
+    request.contentType = "text/html";
 
-    // Let "save as" save the original JSON, not the viewer.
-    // To save with the proper extension we need the original content type,
-    // which has been replaced by application/vnd.mozilla.json.view
-    let originalType;
-    if (request instanceof Ci.nsIHttpChannel) {
-      try {
-        let header = request.getResponseHeader("Content-Type");
-        originalType = header.split(";")[0];
-      } catch (err) {
-        // Handled below
-      }
-    } else {
-      let uri = request.QueryInterface(Ci.nsIChannel).URI.spec;
-      let match = uri.match(/^data:(.*?)[,;]/);
-      if (match) {
-        originalType = match[1];
-      }
-    }
-    const JSON_TYPES = ["application/json", "application/manifest+json"];
-    if (!JSON_TYPES.includes(originalType)) {
-      originalType = JSON_TYPES[0];
-    }
-    request.QueryInterface(Ci.nsIWritablePropertyBag);
-    request.setProperty("contentType", originalType);
+    // JSON enforces UTF-8 charset (see bug 741776).
+    request.contentCharset = "UTF-8";
 
-    // Parse source as JSON. This is like text/plain, but enforcing
-    // UTF-8 charset (see bug 741776).
-    request.QueryInterface(Ci.nsIChannel);
-    request.contentType = JSON_TYPES[0];
-    this.charset = request.contentCharset = "UTF-8";
+    // Changing the content type breaks saving functionality. Fix it.
+    fixSave(request);
 
     // Because content might still have a reference to this window,
     // force setting it to a null principal to avoid it being same-
     // origin with (other) content.
     request.loadInfo.resetPrincipalToInheritToNullPrincipal();
 
+    // Start the request.
     this.listener.onStartRequest(request, context);
+
+    // Initialize stuff.
+    let win = NetworkHelper.getWindowForRequest(request);
+    exportData(win, request);
+    win.addEventListener("DOMContentLoaded", event => {
+      win.addEventListener("contentMessage", onContentMessage, false, true);
+    }, {once: true});
+
+    // Insert the initial HTML code.
+    let converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"]
+                      .createInstance(Ci.nsIScriptableUnicodeConverter);
+    converter.charset = "UTF-8";
+    let stream = converter.convertToInputStream(initialHTML(win.document));
+    this.listener.onDataAvailable(request, context, stream, 0, stream.available());
   },
 
   onStopRequest: function (request, context, statusCode) {
-    let Locale = {
-      $STR: key => {
-        try {
-          return jsonViewStrings.GetStringFromName(key);
-        } catch (err) {
-          console.error(err);
-          return undefined;
-        }
-      }
-    };
-
-    let headers = {
-      response: [],
-      request: []
-    };
-    // The request doesn't have to be always nsIHttpChannel
-    // (e.g. in case of data: URLs)
-    if (request instanceof Ci.nsIHttpChannel) {
-      request.visitResponseHeaders({
-        visitHeader: function (name, value) {
-          headers.response.push({name: name, value: value});
-        }
-      });
-      request.visitRequestHeaders({
-        visitHeader: function (name, value) {
-          headers.request.push({name: name, value: value});
-        }
-      });
-    }
-
-    let win = NetworkHelper.getWindowForRequest(request);
-    JsonViewUtils.exportIntoContentScope(win, Locale, "Locale");
-    JsonViewUtils.exportIntoContentScope(win, headers, "headers");
-
-    win.addEventListener("DOMContentLoaded", event => {
-      win.addEventListener("contentMessage",
-        onContentMessage.bind(this), false, true);
-      loadJsonViewer(win.document);
-    }, {once: true});
-
-    this.listener.onStopRequest(this.channel, context, statusCode);
+    this.listener.onStopRequest(request, context, statusCode);
     this.listener = null;
   }
 };
 
+// Lets "save as" save the original JSON, not the viewer.
+// To save with the proper extension we need the original content type,
+// which has been replaced by application/vnd.mozilla.json.view
+function fixSave(request) {
+  let originalType;
+  if (request instanceof Ci.nsIHttpChannel) {
+    try {
+      let header = request.getResponseHeader("Content-Type");
+      originalType = header.split(";")[0];
+    } catch (err) {
+      // Handled below
+    }
+  } else {
+    let uri = request.QueryInterface(Ci.nsIChannel).URI.spec;
+    let match = uri.match(/^data:(.*?)[,;]/);
+    if (match) {
+      originalType = match[1];
+    }
+  }
+  const JSON_TYPES = ["application/json", "application/manifest+json"];
+  if (!JSON_TYPES.includes(originalType)) {
+    originalType = JSON_TYPES[0];
+  }
+  request.QueryInterface(Ci.nsIWritablePropertyBag);
+  request.setProperty("contentType", originalType);
+}
+
+// Exports variables that will be accessed by the non-privileged scripts.
+function exportData(win, request) {
+  let Locale = {
+    $STR: key => {
+      try {
+        return jsonViewStrings.GetStringFromName(key);
+      } catch (err) {
+        console.error(err);
+        return undefined;
+      }
+    }
+  };
+  JsonViewUtils.exportIntoContentScope(win, Locale, "Locale");
+
+  let headers = {
+    response: [],
+    request: []
+  };
+  // The request doesn't have to be always nsIHttpChannel
+  // (e.g. in case of data: URLs)
+  if (request instanceof Ci.nsIHttpChannel) {
+    request.visitResponseHeaders({
+      visitHeader: function (name, value) {
+        headers.response.push({name: name, value: value});
+      }
+    });
+    request.visitRequestHeaders({
+      visitHeader: function (name, value) {
+        headers.request.push({name: name, value: value});
+      }
+    });
+  }
+  JsonViewUtils.exportIntoContentScope(win, headers, "headers");
+}
+
+// Serializes a qualifiedName and an optional set of attributes into an HTML
+// start tag. Be aware qualifiedName and attribute names are not validated.
+// Attribute values are escaped with escapingString algorithm in attribute mode
+// (https://html.spec.whatwg.org/multipage/syntax.html#escapingString).
+function startTag(qualifiedName, attributes = {}) {
+  return Object.entries(attributes).reduce(function (prev, [attr, value]) {
+    return prev + " " + attr + "=\"" +
+      value.replace(/&/g, "&amp;")
+           .replace(/\u00a0/g, "&nbsp;")
+           .replace(/"/g, "&quot;") +
+      "\"";
+  }, "<" + qualifiedName) + ">";
+}
+
+// Builds an HTML string that will be used to load stylesheets and scripts,
+// and switch the parser to plaintext state.
+function initialHTML(doc) {
+  let os;
+  let platform = Services.appinfo.OS;
+  if (platform.startsWith("WINNT")) {
+    os = "win";
+  } else if (platform.startsWith("Darwin")) {
+    os = "mac";
+  } else {
+    os = "linux";
+  }
+
+  let base = doc.createElement("base");
+  base.href = "resource://devtools/client/jsonview/";
+
+  let style = doc.createElement("link");
+  style.rel = "stylesheet";
+  style.type = "text/css";
+  style.href = "css/main.css";
+
+  let script = doc.createElement("script");
+  script.src = "lib/require.js";
+  script.dataset.main = "viewer-config";
+  script.defer = true;
+
+  let head = doc.createElement("head");
+  head.append(base, style, script);
+
+  return "<!DOCTYPE html>\n" +
+    startTag("html", {
+      "platform": os,
+      "class": "theme-" + JsonViewUtils.getCurrentTheme(),
+      "dir": Services.locale.isAppLocaleRTL ? "rtl" : "ltr"
+    }) +
+    head.outerHTML +
+    startTag("body") +
+    startTag("div", {"id": "content"}) +
+    startTag("plaintext", {"id": "json"});
+}
+
 // Chrome <-> Content communication
 function onContentMessage(e) {
   // Do not handle events from different documents.
-  let win = NetworkHelper.getWindowForRequest(this.channel);
+  let win = this;
   if (win != e.target) {
     return;
   }
 
   let value = e.detail.value;
   switch (e.detail.type) {
     case "copy":
       copyString(win, value);
@@ -178,63 +248,16 @@ function onContentMessage(e) {
       // The window ID is needed when the JSON Viewer is inside an iframe.
       let windowID = win.QueryInterface(Ci.nsIInterfaceRequestor)
         .getInterface(Ci.nsIDOMWindowUtils).outerWindowID;
       childProcessMessageManager.sendAsyncMessage(
         "devtools:jsonview:save", {url: value, windowID: windowID});
   }
 }
 
-// Loads the JSON Viewer into a text/plain document
-function loadJsonViewer(doc) {
-  function addStyleSheet(url) {
-    let link = doc.createElement("link");
-    link.rel = "stylesheet";
-    link.type = "text/css";
-    link.href = url;
-    doc.head.appendChild(link);
-  }
-
-  let os;
-  let platform = Services.appinfo.OS;
-  if (platform.startsWith("WINNT")) {
-    os = "win";
-  } else if (platform.startsWith("Darwin")) {
-    os = "mac";
-  } else {
-    os = "linux";
-  }
-
-  doc.documentElement.setAttribute("platform", os);
-  doc.documentElement.dataset.contentType = doc.contentType;
-  doc.documentElement.classList.add("theme-" + JsonViewUtils.getCurrentTheme());
-  doc.documentElement.dir = Services.locale.isAppLocaleRTL ? "rtl" : "ltr";
-
-  let base = doc.createElement("base");
-  base.href = "resource://devtools/client/jsonview/";
-  doc.head.appendChild(base);
-
-  addStyleSheet("../themes/variables.css");
-  addStyleSheet("../themes/common.css");
-  addStyleSheet("../themes/toolbars.css");
-  addStyleSheet("css/main.css");
-
-  let json = doc.querySelector("pre");
-  json.id = "json";
-  let content = doc.createElement("div");
-  content.id = "content";
-  content.appendChild(json);
-  doc.body.appendChild(content);
-
-  let script = doc.createElement("script");
-  script.src = "lib/require.js";
-  script.dataset.main = "viewer-config";
-  doc.body.appendChild(script);
-}
-
 function copyHeaders(win, headers) {
   let value = "";
   let eol = (Services.appinfo.OS !== "WINNT") ? "\n" : "\r\n";
 
   let responseHeaders = headers.response;
   for (let i = 0; i < responseHeaders.length; i++) {
     let header = responseHeaders[i];
     value += header.name + ": " + header.value + eol;
--- a/devtools/client/jsonview/css/general.css
+++ b/devtools/client/jsonview/css/general.css
@@ -28,28 +28,22 @@ pre {
 }
 
 #content {
   display: flow-root;
 }
 
 #json {
   margin: 8px;
+  white-space: pre-wrap;
 }
 
 /******************************************************************************/
 /* Dark Theme */
 
 body.theme-dark {
   color: var(--theme-body-color);
   background-color: var(--theme-body-background);
 }
 
 .theme-dark pre {
   background-color: var(--theme-body-background);
 }
-
-/******************************************************************************/
-/* Fixes for quirks mode */
-
-table {
-  font: inherit;
-}
--- a/devtools/client/jsonview/css/main.css
+++ b/devtools/client/jsonview/css/main.css
@@ -1,14 +1,17 @@
 /* vim:set ts=2 sw=2 sts=2 et: */
 /* 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/. */
 
-@import "resource://devtools/client/shared/components/reps/reps.css";
+@import "resource://devtools/client/themes/variables.css";
+@import "resource://devtools/client/themes/common.css";
+@import "resource://devtools/client/themes/toolbars.css";
+
 @import "resource://devtools/client/shared/components/tree/tree-view.css";
 @import "resource://devtools/client/shared/components/tabs/tabs.css";
 
 @import "general.css";
 @import "search-box.css";
 @import "toolbar.css";
 @import "json-panel.css";
 @import "text-panel.css";
@@ -48,9 +51,8 @@
 /******************************************************************************/
 /* Theme Firebug */
 
 /* JSON View is using bigger font-size for the main tabs so,
   let's overwrite the default value. */
 .theme-firebug .tabs .tabs-navigation {
   font-size: 14px;
 }
-
--- a/devtools/client/locales/en-US/layout.properties
+++ b/devtools/client/locales/en-US/layout.properties
@@ -7,16 +7,20 @@
 # The Layout Inspector may need to be enabled in about:config by setting
 # devtools.layoutview.enabled to true.
 
 # LOCALIZATION NOTE (layout.cannotShowGridOutline, layout.cannotSHowGridOutline.title):
 # In the case where the grid outline cannot be effectively displayed.
 layout.cannotShowGridOutline=Cannot show outline for this grid
 layout.cannotShowGridOutline.title=The selected grid’s outline cannot effectively fit inside the layout panel for it to be usable.
 
+# LOCALIZATION NOTE (layout.displayGridAreas): Label of the display grid areas setting
+# option in the CSS Grid pane.
+layout.displayGridAreas=Display grid areas
+
 # LOCALIZATION NOTE (layout.displayNumbersOnLines): Label of the display numbers on lines
 # setting option in the CSS Grid pane.
 layout.displayNumbersOnLines=Display numbers on lines
 
 # LOCALIZATION NOTE (layout.extendGridLinesInfinitely): Label of the extend grid lines
 # infinitely setting option in the CSS Grid pane.
 layout.extendGridLinesInfinitely=Extend grid lines infinitely
 
--- a/devtools/client/preferences/devtools.js
+++ b/devtools/client/preferences/devtools.js
@@ -68,16 +68,17 @@ pref("devtools.inspector.colorWidget.ena
 
 // Enable the Font Inspector
 pref("devtools.fontinspector.enabled", true);
 
 // Enable the Layout View
 pref("devtools.layoutview.enabled", false);
 
 // Grid highlighter preferences
+pref("devtools.gridinspector.showGridAreas", false);
 pref("devtools.gridinspector.showGridLineNumbers", false);
 pref("devtools.gridinspector.showGridOutline", false);
 pref("devtools.gridinspector.showInfiniteLines", false);
 
 // By how many times eyedropper will magnify pixels
 pref("devtools.eyedropper.zoom", 6);
 
 // Enable to collapse attributes that are too long.
--- a/devtools/server/actors/highlighters/css-grid.js
+++ b/devtools/server/actors/highlighters/css-grid.js
@@ -35,29 +35,35 @@ const CSS_GRID_ENABLED_PREF = "layout.cs
 
 const DEFAULT_GRID_COLOR = "#4B0082";
 
 const COLUMNS = "cols";
 const ROWS = "rows";
 
 const GRID_FONT_SIZE = 10;
 const GRID_FONT_FAMILY = "sans-serif";
+const GRID_AREA_NAME_FONT_SIZE = "20";
 
 const GRID_LINES_PROPERTIES = {
   "edge": {
     lineDash: [0, 0],
     alpha: 1,
   },
   "explicit": {
     lineDash: [5, 3],
     alpha: 0.75,
   },
   "implicit": {
     lineDash: [2, 2],
     alpha: 0.5,
+  },
+  "areaEdge": {
+    lineDash: [0, 0],
+    alpha: 1,
+    lineWidth: 3,
   }
 };
 
 const GRID_GAP_PATTERN_WIDTH = 14; // px
 const GRID_GAP_PATTERN_HEIGHT = 14; // px
 const GRID_GAP_PATTERN_LINE_DASH = [5, 3]; // px
 const GRID_GAP_ALPHA = 0.5;
 
@@ -192,16 +198,20 @@ function drawRoundedRect(ctx, x, y, widt
  *     @param  {String} colorValue
  *     The color that should be used to draw the highlighter for this grid.
  * - showAllGridAreas(isShown)
  *     @param  {Boolean} isShown
  *     Shows all the grid area highlights for the current grid if isShown is true.
  * - showGridArea(areaName)
  *     @param  {String} areaName
  *     Shows the grid area highlight for the given area name.
+ * - showGridAreasOverlay(isShown)
+ *     @param  {Boolean} isShown
+ *     Displays an overlay of all the grid areas for the current grid container if
+ *     isShown is true.
  * - showGridCell({ gridFragmentIndex: Number, rowNumber: Number, columnNumber: Number })
  *     @param  {Object} { gridFragmentIndex: Number, rowNumber: Number,
  *                        columnNumber: Number }
  *     An object containing the grid fragment index, row and column numbers to the
  *     corresponding grid cell to highlight for the current grid.
  * - showGridLineNames({ gridFragmentIndex: Number, lineNumber: Number,
  *                       type: String })
  *     @param  {Object} { gridFragmentIndex: Number, lineNumber: Number }
@@ -946,17 +956,17 @@ CssGridHighlighter.prototype = extend(Au
       this._canvasPosition.x = Math.max(rightThreshold, leftBoundary);
       hasUpdated = true;
     }
 
     return hasUpdated;
   },
 
   /**
-   *  Updates the <canvas> element's style in accordance with the current window's
+   * Updates the <canvas> element's style in accordance with the current window's
    * devicePixelRatio, and the position calculated in `calculateCanvasPosition`; it also
    * clears the drawing context.
    */
   updateCanvasElement() {
     let ratio = parseFloat((this.win.devicePixelRatio || 1).toFixed(2));
     let size = CANVAS_SIZE / ratio;
     let { x, y } = this._canvasPosition;
 
@@ -1061,26 +1071,125 @@ CssGridHighlighter.prototype = extend(Au
 
     this.renderLines(fragment.cols, COLUMNS, "left", "top", "height",
                      this.getFirstRowLinePos(fragment),
                      this.getLastRowLinePos(fragment));
     this.renderLines(fragment.rows, ROWS, "top", "left", "width",
                      this.getFirstColLinePos(fragment),
                      this.getLastColLinePos(fragment));
 
+    if (this.options.showGridAreasOverlay) {
+      this.renderGridAreaOverlay();
+    }
+
     // Line numbers are rendered in a 2nd step to avoid overlapping with existing lines.
     if (this.options.showGridLineNumbers) {
       this.renderLineNumbers(fragment.cols, COLUMNS, "left", "top",
                        this.getFirstRowLinePos(fragment));
       this.renderLineNumbers(fragment.rows, ROWS, "top", "left",
                        this.getFirstColLinePos(fragment));
     }
   },
 
   /**
+   * Renders the grid area overlay on the css grid highlighter canvas.
+   */
+  renderGridAreaOverlay() {
+    let padding = 1;
+
+    for (let i = 0; i < this.gridData.length; i++) {
+      let fragment = this.gridData[i];
+
+      for (let area of fragment.areas) {
+        let { rowStart, rowEnd, columnStart, columnEnd } = area;
+
+        // Draw the line edges for the grid area
+        const areaColStart = fragment.cols.lines[columnStart - 1];
+        const areaColEnd = fragment.cols.lines[columnEnd - 1];
+
+        const areaRowStart = fragment.rows.lines[rowStart - 1];
+        const areaRowEnd = fragment.rows.lines[rowEnd - 1];
+
+        const areaColStartLinePos = areaColStart.start + areaColStart.breadth;
+        const areaRowStartLinePos = areaRowStart.start + areaRowStart.breadth;
+
+        this.renderLine(areaColStartLinePos + padding,
+                        areaRowStartLinePos, areaRowEnd.start,
+                        COLUMNS, "areaEdge");
+        this.renderLine(areaColEnd.start - padding,
+                        areaRowStartLinePos, areaRowEnd.start,
+                        COLUMNS, "areaEdge");
+
+        this.renderLine(areaRowStartLinePos + padding,
+                        areaColStartLinePos, areaColEnd.start,
+                        ROWS, "areaEdge");
+        this.renderLine(areaRowEnd.start - padding,
+                        areaColStartLinePos, areaColEnd.start,
+                        ROWS, "areaEdge");
+
+        this.renderGridAreaName(fragment, area);
+      }
+    }
+
+    this.ctx.restore();
+  },
+
+  /**
+   * Render grid area name on the containing grid area cell.
+   *
+   * @param  {Object} fragment
+   *         The grid fragment of the grid container.
+   * @param  {Object} area
+   *         The area overlay to render on the CSS highlighter canvas.
+   */
+  renderGridAreaName(fragment, area) {
+    let { rowStart, rowEnd, columnStart, columnEnd } = area;
+    let { devicePixelRatio } = this.win;
+    let displayPixelRatio = getDisplayPixelRatio(this.win);
+    let currentZoom = getCurrentZoom(this.win);
+    let offset = (displayPixelRatio / 2) % 1;
+    let fontSize = (GRID_AREA_NAME_FONT_SIZE * displayPixelRatio);
+
+    this.ctx.save();
+
+    let canvasX = Math.round(this._canvasPosition.x * devicePixelRatio);
+    let canvasY = Math.round(this._canvasPosition.y * devicePixelRatio);
+    this.ctx.translate(offset - canvasX, offset - canvasY);
+
+    this.ctx.font = (fontSize * currentZoom) + "px " + GRID_FONT_FAMILY;
+    this.ctx.strokeStyle = this.color;
+    this.ctx.fillStyle = this.color;
+    this.ctx.textAlign = "center";
+    this.ctx.textBaseline = "middle";
+
+    // Draw the text for the grid area name.
+    for (let rowNumber = rowStart; rowNumber < rowEnd; rowNumber++) {
+      for (let columnNumber = columnStart; columnNumber < columnEnd; columnNumber++) {
+        let row = fragment.rows.tracks[rowNumber - 1];
+        let column = fragment.cols.tracks[columnNumber - 1];
+
+        // Check if the font size is exceeds the bounds of the containing grid cell.
+        if (fontSize > column.breadth || fontSize > row.breadth) {
+          fontSize = (column.breadth + row.breadth) / 2;
+          this.ctx.font = (fontSize * currentZoom) + "px " + GRID_FONT_FAMILY;
+        }
+
+        let x = column.start + column.breadth / 2;
+        let y = row.start + row.breadth / 2;
+
+        [x, y] = apply(this.currentMatrix, [x, y]);
+
+        this.ctx.fillText(area.name, x, y);
+      }
+    }
+
+    this.ctx.restore();
+  },
+
+  /**
    * Render the grid lines given the grid dimension information of the
    * column or row lines.
    *
    * @param  {GridDimension} gridDimension
    *         Column or row grid dimension object.
    * @param  {Object} quad.bounds
    *         The content bounds of the box model region quads.
    * @param  {String} dimensionType
@@ -1175,17 +1284,16 @@ CssGridHighlighter.prototype = extend(Au
 
     linePos = Math.round(linePos);
     startPos = Math.round(startPos);
 
     this.ctx.save();
     this.ctx.setLineDash(GRID_LINES_PROPERTIES[lineType].lineDash);
     this.ctx.beginPath();
     this.ctx.translate(offset - x, offset - y);
-    this.ctx.lineWidth = lineWidth;
 
     if (dimensionType === COLUMNS) {
       if (isFinite(endPos)) {
         endPos = Math.round(endPos);
       } else {
         endPos = CANVAS_INFINITY;
         startPos = -endPos;
       }
@@ -1198,16 +1306,22 @@ CssGridHighlighter.prototype = extend(Au
         startPos = -endPos;
       }
       drawLine(this.ctx, startPos, linePos, endPos, linePos, this.currentMatrix);
     }
 
     this.ctx.strokeStyle = this.color;
     this.ctx.globalAlpha = GRID_LINES_PROPERTIES[lineType].alpha;
 
+    if (GRID_LINES_PROPERTIES[lineType].lineWidth) {
+      this.ctx.lineWidth = GRID_LINES_PROPERTIES[lineType].lineWidth * devicePixelRatio;
+    } else {
+      this.ctx.lineWidth = lineWidth;
+    }
+
     this.ctx.stroke();
     this.ctx.restore();
   },
 
   /**
    * Render the grid line number on the css grid highlighter canvas.
    *
    * @param  {Number} lineNumber
--- a/dom/media/encoder/TrackEncoder.cpp
+++ b/dom/media/encoder/TrackEncoder.cpp
@@ -138,29 +138,31 @@ AudioTrackEncoder::AppendAudioSegment(co
 
 /*static*/
 void
 AudioTrackEncoder::InterleaveTrackData(AudioChunk& aChunk,
                                        int32_t aDuration,
                                        uint32_t aOutputChannels,
                                        AudioDataValue* aOutput)
 {
+  uint32_t numChannelsToCopy = std::min(aOutputChannels,
+                                        static_cast<uint32_t>(aChunk.mChannelData.Length()));
   switch(aChunk.mBufferFormat) {
     case AUDIO_FORMAT_S16: {
       AutoTArray<const int16_t*, 2> array;
-      array.SetLength(aOutputChannels);
+      array.SetLength(numChannelsToCopy);
       for (uint32_t i = 0; i < array.Length(); i++) {
         array[i] = static_cast<const int16_t*>(aChunk.mChannelData[i]);
       }
       InterleaveTrackData(array, aDuration, aOutputChannels, aOutput, aChunk.mVolume);
       break;
     }
     case AUDIO_FORMAT_FLOAT32: {
       AutoTArray<const float*, 2> array;
-      array.SetLength(aOutputChannels);
+      array.SetLength(numChannelsToCopy);
       for (uint32_t i = 0; i < array.Length(); i++) {
         array[i] = static_cast<const float*>(aChunk.mChannelData[i]);
       }
       InterleaveTrackData(array, aDuration, aOutputChannels, aOutput, aChunk.mVolume);
       break;
    }
    case AUDIO_FORMAT_SILENCE: {
       MOZ_ASSERT(false, "To implement.");
--- a/gfx/thebes/gfxPlatform.cpp
+++ b/gfx/thebes/gfxPlatform.cpp
@@ -2431,25 +2431,21 @@ gfxPlatform::DisableBufferRotation()
 
   sBufferRotationCheckPref = false;
 }
 
 already_AddRefed<ScaledFont>
 gfxPlatform::GetScaledFontForFontWithCairoSkia(DrawTarget* aTarget, gfxFont* aFont)
 {
     NativeFont nativeFont;
-    if (aTarget->GetBackendType() == BackendType::CAIRO || aTarget->GetBackendType() == BackendType::SKIA) {
-        nativeFont.mType = NativeFontType::CAIRO_FONT_FACE;
-        nativeFont.mFont = aFont->GetCairoScaledFont();
-        return Factory::CreateScaledFontForNativeFont(nativeFont,
-                                                      aFont->GetUnscaledFont(),
-                                                      aFont->GetAdjustedSize());
-    }
-
-    return nullptr;
+    nativeFont.mType = NativeFontType::CAIRO_FONT_FACE;
+    nativeFont.mFont = aFont->GetCairoScaledFont();
+    return Factory::CreateScaledFontForNativeFont(nativeFont,
+                                                  aFont->GetUnscaledFont(),
+                                                  aFont->GetAdjustedSize());
 }
 
 /* static */ bool
 gfxPlatform::UsesOffMainThreadCompositing()
 {
   if (XRE_GetProcessType() == GeckoProcessType_GPU) {
     return true;
   }
--- a/gfx/thebes/gfxPlatformGtk.cpp
+++ b/gfx/thebes/gfxPlatformGtk.cpp
@@ -590,31 +590,25 @@ gfxPlatformGtk::GetGdkDrawable(cairo_sur
 
     return nullptr;
 }
 #endif
 
 already_AddRefed<ScaledFont>
 gfxPlatformGtk::GetScaledFontForFont(DrawTarget* aTarget, gfxFont *aFont)
 {
-    switch (aTarget->GetBackendType()) {
-    case BackendType::CAIRO:
-    case BackendType::SKIA:
-        if (aFont->GetType() == gfxFont::FONT_TYPE_FONTCONFIG) {
-            gfxFontconfigFontBase* fcFont = static_cast<gfxFontconfigFontBase*>(aFont);
-            return Factory::CreateScaledFontForFontconfigFont(
-                    fcFont->GetCairoScaledFont(),
-                    fcFont->GetPattern(),
-                    fcFont->GetUnscaledFont(),
-                    fcFont->GetAdjustedSize());
-        }
-        MOZ_FALLTHROUGH;
-    default:
-        return GetScaledFontForFontWithCairoSkia(aTarget, aFont);
-    }
+  if (aFont->GetType() == gfxFont::FONT_TYPE_FONTCONFIG) {
+      gfxFontconfigFontBase* fcFont = static_cast<gfxFontconfigFontBase*>(aFont);
+      return Factory::CreateScaledFontForFontconfigFont(
+              fcFont->GetCairoScaledFont(),
+              fcFont->GetPattern(),
+              fcFont->GetUnscaledFont(),
+              fcFont->GetAdjustedSize());
+  }
+  return GetScaledFontForFontWithCairoSkia(aTarget, aFont);
 }
 
 #ifdef GL_PROVIDER_GLX
 
 class GLXVsyncSource final : public VsyncSource
 {
 public:
   GLXVsyncSource()
--- a/layout/style/nsCSSParser.cpp
+++ b/layout/style/nsCSSParser.cpp
@@ -17286,18 +17286,17 @@ CSSParserImpl::SetDefaultNamespaceOnSele
   }
 }
 
 bool
 CSSParserImpl::ParsePaint(nsCSSPropertyID aPropID)
 {
   nsCSSValue x, y;
 
-  if (ParseVariant(x, VARIANT_HC | VARIANT_NONE | VARIANT_URL |
-                      VARIANT_OPENTYPE_SVG_KEYWORD,
+  if (ParseVariant(x, VARIANT_HC | VARIANT_NONE | VARIANT_URL | VARIANT_KEYWORD,
                    nsCSSProps::kContextPatternKTable) != CSSParseResult::Ok) {
     return false;
   }
 
   bool hasFallback = false;
   bool canHaveFallback = x.GetUnit() == eCSSUnit_URL ||
                          x.GetUnit() == eCSSUnit_Enumerated;
   if (canHaveFallback) {
--- a/layout/style/test/stylo-failures.md
+++ b/layout/style/test/stylo-failures.md
@@ -75,17 +75,17 @@ to mochitest command.
     * test_value_storage.html `-moz-linear-gradient` [322]
     * ... `-moz-radial-gradient` [309]
     * ... `-moz-repeating-` [298]
   * -webkit-{flex,inline-flex} for display servo/servo#15400
     * test_webkit_flex_display.html [4]
 * Unsupported values
   * SVG-in-OpenType values not supported servo/servo#15211 bug 1355412
     * test_value_storage.html `context-` [7]
-    * test_bug798843_pref.html [7]
+    * test_bug798843_pref.html [5]
 * Incorrect parsing
   * different parsing bug 1364260
     * test_supports_rules.html [6]
     * test_condition_text.html [1]
 * Incorrect serialization
   * color value not canonicalized servo/servo#15397
     * test_shorthand_property_getters.html `should condense to canonical case` [2]
   * place-{content,items,self} shorthands bug 1363971
--- a/layout/style/test/test_bug798843_pref.html
+++ b/layout/style/test/test_bug798843_pref.html
@@ -8,18 +8,16 @@
   <title>Test that SVG glyph context-* values can be pref'ed off</title>
   <script src="/tests/SimpleTest/SimpleTest.js"></script>
 </head>
 <body>
 
 <script>
 
 var props = {
-  "fill" : "context-stroke none",
-  "stroke" : "context-fill none",
   "fillOpacity" : "context-stroke-opacity",
   "strokeOpacity" : "context-fill-opacity",
   "strokeDasharray" : "context-value",
   "strokeDashoffset" : "context-value",
   "strokeWidth" : "context-value"
 };
 
 function testDisabled() {
--- a/mobile/android/app/ua-update.json.in
+++ b/mobile/android/app/ua-update.json.in
@@ -6,10 +6,12 @@
   "weather.yahoo.co.jp": "Mozilla/5.0 (Linux; Android 5.0.2; Galaxy Nexus Build/IMM76B) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/43.0.2357.93 Mobile Safari/537.36",
   // bug 1177298, lohaco.jp
   "lohaco.jp": "Mozilla/5.0 (Linux; Android 5.0.2; Galaxy Nexus Build/IMM76B) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/43.0.2357.93 Mobile Safari/537.36",
   // bug 1177298, www.nhk.or.jp
   "nhk.or.jp": "\\)\\s# AppleWebKit ",
   // bug 1177298, uniqlo.com
   "uniqlo.com": "\\)\\s#) Mobile Safari ",
   // bug 1338260, directv.com
-  "directv.com": "Mozilla/5.0 (Linux; Android 6.0.1; SM-G920F Build/MMB29K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.91 Mobile Safari/537.36"
+  "directv.com": "Mozilla/5.0 (Linux; Android 6.0.1; SM-G920F Build/MMB29K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.91 Mobile Safari/537.36",
+  // bug 1310951, m.canadiantire.ca
+  "m.canadiantire.ca": "Mozilla/5.0 (Linux; Android 6.0.1; SM-G920F Build/MMB29K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.91 Mobile Safari/537.36"
 }
--- a/mobile/android/base/java/org/mozilla/gecko/GeckoApp.java
+++ b/mobile/android/base/java/org/mozilla/gecko/GeckoApp.java
@@ -1836,17 +1836,17 @@ public abstract class GeckoApp extends G
         // Check if launched from data reporting notification.
         if (ACTION_LAUNCH_SETTINGS.equals(action)) {
             Intent settingsIntent = new Intent(GeckoApp.this, GeckoPreferences.class);
             // Copy extras.
             settingsIntent.putExtras(intent.getUnsafe());
             startActivity(settingsIntent);
         }
 
-        mPromptService = new PromptService(this);
+        mPromptService = new PromptService(this, getAppEventDispatcher());
 
         // Trigger the completion of the telemetry timer that wraps activity startup,
         // then grab the duration to give to FHR.
         mJavaUiStartupTimer.stop();
         final long javaDuration = mJavaUiStartupTimer.getElapsed();
 
         ThreadUtils.getBackgroundHandler().postDelayed(new Runnable() {
             @Override
--- a/mobile/android/base/java/org/mozilla/gecko/GlobalHistory.java
+++ b/mobile/android/base/java/org/mozilla/gecko/GlobalHistory.java
@@ -177,17 +177,18 @@ class GlobalHistory {
                 getInstance().update(context.getContentResolver(), db, uri, title);
             }
         });
     }
 
     /* protected */ void checkVisited(final String uri) {
         final String storedURI = ReaderModeUtils.stripAboutReaderUrl(uri);
 
-        final NotifierRunnable runnable = new NotifierRunnable(GeckoAppShell.getContext());
+        final NotifierRunnable runnable =
+                new NotifierRunnable(GeckoAppShell.getApplicationContext());
         mHandler.post(new Runnable() {
             @Override
             public void run() {
                 // this runs on the same handler thread as the processing loop,
                 // so no synchronization needed
                 mPendingUris.add(storedURI);
                 if (mProcessing) {
                     // there's already a runnable queued up or working away, so
--- a/mobile/android/base/java/org/mozilla/gecko/prompts/PromptService.java
+++ b/mobile/android/base/java/org/mozilla/gecko/prompts/PromptService.java
@@ -1,50 +1,50 @@
 /* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
  * 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/. */
 
 package org.mozilla.gecko.prompts;
 
 import org.mozilla.gecko.EventDispatcher;
-import org.mozilla.gecko.GeckoApp;
-import org.mozilla.gecko.GeckoAppShell;
 import org.mozilla.gecko.util.BundleEventListener;
 import org.mozilla.gecko.util.EventCallback;
 import org.mozilla.gecko.util.GeckoBundle;
 import org.mozilla.gecko.util.ThreadUtils;
 
 import android.content.Context;
 import android.util.Log;
 
 public class PromptService implements BundleEventListener {
     private static final String LOGTAG = "GeckoPromptService";
 
-    private final GeckoApp mGeckoApp;
+    private final Context mContext;
+    private final EventDispatcher mDispatcher;
 
-    public PromptService(GeckoApp geckoApp) {
-        mGeckoApp = geckoApp;
-        mGeckoApp.getAppEventDispatcher().registerUiThreadListener(this,
+    public PromptService(final Context context, final EventDispatcher dispatcher) {
+        mContext = context;
+        mDispatcher = dispatcher;
+        mDispatcher.registerUiThreadListener(this,
             "Prompt:Show",
             "Prompt:ShowTop");
     }
 
     public void destroy() {
-        mGeckoApp.getAppEventDispatcher().unregisterUiThreadListener(this,
+        mDispatcher.unregisterUiThreadListener(this,
             "Prompt:Show",
             "Prompt:ShowTop");
     }
 
     // BundleEventListener implementation
     @Override
     public void handleMessage(final String event, final GeckoBundle message,
                               final EventCallback callback) {
         Prompt p;
-        p = new Prompt(mGeckoApp, new Prompt.PromptCallback() {
+        p = new Prompt(mContext, new Prompt.PromptCallback() {
             @Override
             public void onPromptFinished(final GeckoBundle result) {
                 callback.sendSuccess(result);
             }
         });
         p.show(message);
     }
 }
--- a/mobile/android/modules/Prompt.jsm
+++ b/mobile/android/modules/Prompt.jsm
@@ -2,32 +2,43 @@
  * This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 "use strict"
 
 var Cc = Components.classes;
 var Ci = Components.interfaces;
 
+Components.utils.import("resource://gre/modules/Messaging.jsm");
 Components.utils.import("resource://gre/modules/Services.jsm");
 
 this.EXPORTED_SYMBOLS = ["Prompt"];
 
 function log(msg) {
   Services.console.logStringMessage(msg);
 }
 
+function getRootWindow(win) {
+  // Get the root xul window.
+  return win.QueryInterface(Ci.nsIInterfaceRequestor)
+            .getInterface(Ci.nsIDocShell).QueryInterface(Ci.nsIDocShellTreeItem)
+            .rootTreeItem.QueryInterface(Ci.nsIInterfaceRequestor)
+            .getInterface(Ci.nsIDOMWindow);
+}
+
 function Prompt(aOptions) {
   this.window = "window" in aOptions ? aOptions.window : null;
 
   this.msg = { async: true };
 
   if (this.window) {
-    let window = Services.wm.getMostRecentWindow("navigator:browser");
-    var tab = window.BrowserApp.getTabForWindow(this.window);
+    let window = getRootWindow(this.window);
+    var tab = window &&
+              window.BrowserApp &&
+              window.BrowserApp.getTabForWindow(this.window);
     if (tab) {
       this.msg.tabId = tab.id;
     }
   }
 
   if (aOptions.priority === 1)
     this.msg.type = "Prompt:ShowTop"
   else
@@ -169,21 +180,34 @@ Prompt.prototype = {
   },
 
   show: function(callback) {
     this.callback = callback;
     log("Sending message");
     this._innerShow();
   },
 
+  _getDispatcher: function(win) {
+    let root = win && getRootWindow(win);
+    try {
+      return root && (root.WindowEventDispatcher || EventDispatcher.for(root));
+    } catch (e) {
+      // No EventDispatcher for this window.
+      return null;
+    }
+  },
+
   _innerShow: function() {
-    let window = Services.wm.getMostRecentWindow("navigator:browser");
-    window.WindowEventDispatcher.sendRequestForResult(this.msg).then((data) => {
-      if (this.callback)
+    let dispatcher =
+        this._getDispatcher(this.window) ||
+        this._getDispatcher(Services.wm.getMostRecentWindow("navigator:browser"));
+    dispatcher.sendRequestForResult(this.msg).then((data) => {
+      if (this.callback) {
         this.callback(data);
+      }
     });
   },
 
   _setListItems: function(aItems) {
     let hasSelected = false;
     this.msg.listitems = [];
 
     aItems.forEach(function(item) {
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -2081,21 +2081,25 @@ pref("network.auth.subresource-img-cross
 // for credentials from the user explicitly.
 // If set to true, and a server URL conforms other conditions for sending default
 // credentials, those will be sent automatically in Private Browsing windows.
 //
 // This preference has no effect when the browser is set to "Never Remember History",
 // in that case default credentials will always be used.
 pref("network.auth.private-browsing-sso", false);
 
-// Control how the throttling service works - number of ms that each
+// Control how throttling of http responses works - number of ms that each
 // suspend and resume period lasts (prefs named appropriately)
-pref("network.throttle.suspend-for", 3000);
-pref("network.throttle.resume-for", 200);
-pref("network.throttle.enable", true);
+pref("network.http.throttle.enable", true);
+pref("network.http.throttle.suspend-for", 3000);
+pref("network.http.throttle.resume-for", 200);
+// Delay we resume throttled background responses after the last unthrottled
+// response has finished.  Prevents resuming too soon during an active page load
+// at which sub-resource reqeusts quickly come and go.
+pref("network.http.throttle.resume-background-in", 400);
 
 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);
--- a/netwerk/protocol/http/Http2Session.cpp
+++ b/netwerk/protocol/http/Http2Session.cpp
@@ -399,16 +399,17 @@ Http2Session::AddStream(nsAHttpTransacti
               "transaction (%08x).\n", this, aHttpTransaction, trans,
               static_cast<uint32_t>(rv)));
       }
       return true;
     }
   }
 
   aHttpTransaction->SetConnection(this);
+  aHttpTransaction->OnActivated(true);
 
   if (aUseTunnel) {
     LOG3(("Http2Session::AddStream session=%p trans=%p OnTunnel",
           this, aHttpTransaction));
     DispatchOnTunnel(aHttpTransaction, aCallbacks);
     return true;
   }
 
--- a/netwerk/protocol/http/nsAHttpTransaction.h
+++ b/netwerk/protocol/http/nsAHttpTransaction.h
@@ -40,16 +40,20 @@ class SpdyConnectTransaction;
 class nsAHttpTransaction : public nsSupportsWeakReference
 {
 public:
     NS_DECLARE_STATIC_IID_ACCESSOR(NS_AHTTPTRANSACTION_IID)
 
     // called by the connection when it takes ownership of the transaction.
     virtual void SetConnection(nsAHttpConnection *) = 0;
 
+    // called by the connection after a successfull activation of this transaction
+    // in other words, tells the transaction it transitioned to the "active" state.
+    virtual void OnActivated(bool h2) {}
+
     // used to obtain the connection associated with this transaction
     virtual nsAHttpConnection *Connection() = 0;
 
     // called by the connection to get security callbacks to set on the
     // socket transport.
     virtual void GetSecurityCallbacks(nsIInterfaceRequestor **) = 0;
 
     // called to report socket status (see nsITransportEventSink)
--- a/netwerk/protocol/http/nsHttpChannel.cpp
+++ b/netwerk/protocol/http/nsHttpChannel.cpp
@@ -706,20 +706,16 @@ nsHttpChannel::ContinueConnect()
 
     rv = mTransactionPump->AsyncRead(this, nullptr);
     if (NS_FAILED(rv)) return rv;
 
     uint32_t suspendCount = mSuspendCount;
     while (suspendCount--)
         mTransactionPump->Suspend();
 
-    if (mClassOfService & nsIClassOfService::Throttleable) {
-        gHttpHandler->ThrottleTransaction(mTransaction, true);
-    }
-
     return NS_OK;
 }
 
 void
 nsHttpChannel::SpeculativeConnect()
 {
     // Before we take the latency hit of dealing with the cache, try and
     // get the TCP (and SSL) handshakes going so they can overlap.
@@ -6617,20 +6613,18 @@ nsHttpChannel::ContinueBeginConnect()
 
 //-----------------------------------------------------------------------------
 // HttpChannel::nsIClassOfService
 //-----------------------------------------------------------------------------
 
 void
 nsHttpChannel::OnClassOfServiceUpdated()
 {
-    bool throttleable = !!(mClassOfService & nsIClassOfService::Throttleable);
-
     if (mTransaction) {
-        gHttpHandler->ThrottleTransaction(mTransaction, throttleable);
+        gHttpHandler->UpdateClassOfServiceOnTransaction(mTransaction, mClassOfService);
     }
 }
 
 NS_IMETHODIMP
 nsHttpChannel::SetClassFlags(uint32_t inFlags)
 {
     uint32_t previous = mClassOfService;
     mClassOfService = inFlags;
@@ -8004,20 +7998,16 @@ nsHttpChannel::DoAuthRetry(nsAHttpConnec
 
     rv = mTransactionPump->AsyncRead(this, nullptr);
     if (NS_FAILED(rv)) return rv;
 
     uint32_t suspendCount = mSuspendCount;
     while (suspendCount--)
         mTransactionPump->Suspend();
 
-    if (mSuspendCount && mClassOfService & nsIClassOfService::Throttleable) {
-        gHttpHandler->ThrottleTransaction(mTransaction, true);
-    }
-
     return NS_OK;
 }
 
 //-----------------------------------------------------------------------------
 // nsHttpChannel::nsIApplicationCacheChannel
 //-----------------------------------------------------------------------------
 
 NS_IMETHODIMP
@@ -8714,20 +8704,16 @@ nsHttpChannel::SuspendInternal()
     NS_ENSURE_TRUE(mIsPending, NS_ERROR_NOT_AVAILABLE);
 
     LOG(("nsHttpChannel::SuspendInternal [this=%p]\n", this));
 
     ++mSuspendCount;
 
     if (mSuspendCount == 1) {
         mSuspendTimestamp = TimeStamp::NowLoRes();
-
-        if (mClassOfService & nsIClassOfService::Throttleable && mTransaction) {
-            gHttpHandler->ThrottleTransaction(mTransaction, true);
-        }
     }
 
     nsresult rvTransaction = NS_OK;
     if (mTransactionPump) {
         rvTransaction = mTransactionPump->Suspend();
     }
     nsresult rvCache = NS_OK;
     if (mCachePump) {
@@ -8743,20 +8729,16 @@ nsHttpChannel::ResumeInternal()
     NS_ENSURE_TRUE(mSuspendCount > 0, NS_ERROR_UNEXPECTED);
 
     LOG(("nsHttpChannel::ResumeInternal [this=%p]\n", this));
 
     if (--mSuspendCount == 0) {
         mSuspendTotalTime += (TimeStamp::NowLoRes() - mSuspendTimestamp).
                                ToMilliseconds();
 
-        if (mClassOfService & nsIClassOfService::Throttleable && mTransaction) {
-            gHttpHandler->ThrottleTransaction(mTransaction, false);
-        }
-
         if (mCallOnResume) {
             nsresult rv = AsyncCall(mCallOnResume);
             mCallOnResume = nullptr;
             NS_ENSURE_SUCCESS(rv, rv);
         }
     }
 
     nsresult rvTransaction = NS_OK;
--- a/netwerk/protocol/http/nsHttpConnection.cpp
+++ b/netwerk/protocol/http/nsHttpConnection.cpp
@@ -659,16 +659,18 @@ nsHttpConnection::Activate(nsAHttpTransa
     }
 
     if (mTLSFilter) {
         rv = mTLSFilter->SetProxiedTransaction(trans);
         NS_ENSURE_SUCCESS(rv, rv);
         mTransaction = mTLSFilter;
     }
 
+    trans->OnActivated(false);
+
     rv = OnOutputStreamReady(mSocketOut);
 
 failed_activation:
     if (NS_FAILED(rv)) {
         mTransaction = nullptr;
     }
 
     return rv;
--- a/netwerk/protocol/http/nsHttpConnectionMgr.cpp
+++ b/netwerk/protocol/http/nsHttpConnectionMgr.cpp
@@ -110,26 +110,34 @@ nsHttpConnectionMgr::InsertTransactionSo
 //-----------------------------------------------------------------------------
 
 nsHttpConnectionMgr::nsHttpConnectionMgr()
     : mReentrantMonitor("nsHttpConnectionMgr.mReentrantMonitor")
     , mMaxUrgentExcessiveConns(0)
     , mMaxConns(0)
     , mMaxPersistConnsPerHost(0)
     , mMaxPersistConnsPerProxy(0)
+    , mMaxRequestDelay(0)
+    , mThrottleEnabled(false)
+    , mThrottleSuspendFor(0)
+    , mThrottleResumeFor(0)
+    , mThrottleResumeIn(0)
     , mIsShuttingDown(false)
     , mNumActiveConns(0)
     , mNumIdleConns(0)
     , mNumSpdyActiveConns(0)
     , mNumHalfOpenConns(0)
     , mTimeOfNextWakeUp(UINT64_MAX)
     , mPruningNoTraffic(false)
     , mTimeoutTickArmed(false)
     , mTimeoutTickNext(1)
     , mCurrentTopLevelOuterContentWindowId(0)
+    , mThrottlingInhibitsReading(false)
+    , mActiveTabTransactionsExist(false)
+    , mActiveTabUnthrottledTransactionsExist(false)
 {
     LOG(("Creating nsHttpConnectionMgr @%p\n", this));
 }
 
 nsHttpConnectionMgr::~nsHttpConnectionMgr()
 {
     LOG(("Destroying nsHttpConnectionMgr @%p\n", this));
     MOZ_ASSERT(mCoalescingHash.Count() == 0);
@@ -159,29 +167,38 @@ nsHttpConnectionMgr::EnsureSocketThreadT
     return sts ? NS_OK : NS_ERROR_NOT_AVAILABLE;
 }
 
 nsresult
 nsHttpConnectionMgr::Init(uint16_t maxUrgentExcessiveConns,
                           uint16_t maxConns,
                           uint16_t maxPersistConnsPerHost,
                           uint16_t maxPersistConnsPerProxy,
-                          uint16_t maxRequestDelay)
+                          uint16_t maxRequestDelay,
+                          bool throttleEnabled,
+                          uint32_t throttleSuspendFor,
+                          uint32_t throttleResumeFor,
+                          uint32_t throttleResumeIn)
 {
     LOG(("nsHttpConnectionMgr::Init\n"));
 
     {
         ReentrantMonitorAutoEnter mon(mReentrantMonitor);
 
         mMaxUrgentExcessiveConns = maxUrgentExcessiveConns;
         mMaxConns = maxConns;
         mMaxPersistConnsPerHost = maxPersistConnsPerHost;
         mMaxPersistConnsPerProxy = maxPersistConnsPerProxy;
         mMaxRequestDelay = maxRequestDelay;
 
+        mThrottleEnabled = throttleEnabled;
+        mThrottleSuspendFor = throttleSuspendFor;
+        mThrottleResumeFor = throttleResumeFor;
+        mThrottleResumeIn = throttleResumeIn;
+
         mIsShuttingDown = false;
     }
 
     return EnsureSocketThreadTarget();
 }
 
 class BoolWrapper : public ARefBase
 {
@@ -339,23 +356,25 @@ nsHttpConnectionMgr::Observe(nsISupports
                              const char16_t *data)
 {
     LOG(("nsHttpConnectionMgr::Observe [topic=\"%s\"]\n", topic));
 
     if (0 == strcmp(topic, NS_TIMER_CALLBACK_TOPIC)) {
         nsCOMPtr<nsITimer> timer = do_QueryInterface(subject);
         if (timer == mTimer) {
             Unused << PruneDeadConnections();
-        }
-        else if (timer == mTimeoutTick) {
+        } else if (timer == mTimeoutTick) {
             TimeoutTick();
         } else if (timer == mTrafficTimer) {
             Unused << PruneNoTraffic();
-        }
-        else {
+        } else if (timer == mThrottleTicker) {
+            ThrottlerTick();
+        } else if (timer == mDelayedResumeReadTimer) {
+            ResumeBackgroundThrottledTransactions();
+        } else {
             MOZ_ASSERT(false, "unexpected timer-callback");
             LOG(("Unexpected timer object\n"));
             return NS_ERROR_UNEXPECTED;
         }
     }
 
     return NS_OK;
 }
@@ -373,22 +392,22 @@ nsHttpConnectionMgr::AddTransaction(nsHt
 nsresult
 nsHttpConnectionMgr::RescheduleTransaction(nsHttpTransaction *trans, int32_t priority)
 {
     LOG(("nsHttpConnectionMgr::RescheduleTransaction [trans=%p %d]\n", trans, priority));
     return PostEvent(&nsHttpConnectionMgr::OnMsgReschedTransaction, priority, trans);
 }
 
 void
-nsHttpConnectionMgr::ThrottleTransaction(nsHttpTransaction *trans, bool throttle)
+nsHttpConnectionMgr::UpdateClassOfServiceOnTransaction(nsHttpTransaction *trans, uint32_t classOfService)
 {
-    LOG(("nsHttpConnectionMgr::ThrottleTransaction [trans=%p throttle=%" PRIx32 "]\n",
-         trans, static_cast<uint32_t>(throttle)));
-    Unused << PostEvent(&nsHttpConnectionMgr::OnMsgThrottleTransaction,
-                        static_cast<int32_t>(throttle), trans);
+    LOG(("nsHttpConnectionMgr::UpdateClassOfServiceOnTransaction [trans=%p classOfService=%" PRIu32 "]\n",
+         trans, static_cast<uint32_t>(classOfService)));
+    Unused << PostEvent(&nsHttpConnectionMgr::OnMsgUpdateClassOfServiceOnTransaction,
+                        static_cast<int32_t>(classOfService), trans);
 }
 
 nsresult
 nsHttpConnectionMgr::CancelTransaction(nsHttpTransaction *trans, nsresult reason)
 {
     LOG(("nsHttpConnectionMgr::CancelTransaction [trans=%p reason=%" PRIx32 "]\n",
          trans, static_cast<uint32_t>(reason)));
     return PostEvent(&nsHttpConnectionMgr::OnMsgCancelTransaction,
@@ -2152,16 +2171,20 @@ nsHttpConnectionMgr::OnMsgShutdown(int32
     if (mTimer) {
       mTimer->Cancel();
       mTimer = nullptr;
     }
     if (mTrafficTimer) {
       mTrafficTimer->Cancel();
       mTrafficTimer = nullptr;
     }
+    DestroyThrottleTicker();
+    mActiveTransactions[false].Clear();
+    mActiveTransactions[true].Clear();
+
     mCoalescingHash.Clear();
 
     // signal shutdown complete
     nsCOMPtr<nsIRunnable> runnable =
         new ConnEvent(this, &nsHttpConnectionMgr::OnMsgShutdownConfirm,
                       0, param);
     NS_DispatchToMainThread(runnable);
 }
@@ -2219,25 +2242,25 @@ nsHttpConnectionMgr::OnMsgReschedTransac
         if (index >= 0) {
             RefPtr<PendingTransactionInfo> pendingTransInfo = (*pendingQ)[index];
             pendingQ->RemoveElementAt(index);
             InsertTransactionSorted(*pendingQ, pendingTransInfo);
         }
     }
 }
 
-void nsHttpConnectionMgr::OnMsgThrottleTransaction(int32_t arg, ARefBase *param)
+void nsHttpConnectionMgr::OnMsgUpdateClassOfServiceOnTransaction(int32_t arg, ARefBase *param)
 {
     MOZ_ASSERT(OnSocketThread(), "not on socket thread");
-    LOG(("nsHttpConnectionMgr::OnMsgThrottleTransaction [trans=%p]\n", param));
-
-    bool throttle = static_cast<bool>(arg);
+    LOG(("nsHttpConnectionMgr::OnMsgUpdateClassOfServiceOnTransaction [trans=%p]\n", param));
+
+    uint32_t cos = static_cast<uint32_t>(arg);
     nsHttpTransaction *trans = static_cast<nsHttpTransaction *>(param);
 
-    trans->ThrottleResponse(throttle);
+    trans->SetClassOfService(cos);
 }
 
 void
 nsHttpConnectionMgr::OnMsgCancelTransaction(int32_t reason, ARefBase *param)
 {
     MOZ_ASSERT(OnSocketThread(), "not on socket thread");
     LOG(("nsHttpConnectionMgr::OnMsgCancelTransaction [trans=%p]\n", param));
 
@@ -2725,16 +2748,28 @@ nsHttpConnectionMgr::OnMsgUpdateParam(in
         mMaxPersistConnsPerHost = value;
         break;
     case MAX_PERSISTENT_CONNECTIONS_PER_PROXY:
         mMaxPersistConnsPerProxy = value;
         break;
     case MAX_REQUEST_DELAY:
         mMaxRequestDelay = value;
         break;
+    case THROTTLING_ENABLED:
+        SetThrottlingEnabled(!!value);
+        break;
+    case THROTTLING_SUSPEND_FOR:
+        mThrottleSuspendFor = value;
+        break;
+    case THROTTLING_RESUME_FOR:
+        mThrottleResumeFor = value;
+        break;
+    case THROTTLING_RESUME_IN:
+        mThrottleResumeIn = value;
+        break;
     default:
         NS_NOTREACHED("unexpected parameter name");
     }
 }
 
 // nsHttpConnectionMgr::nsConnectionEntry
 nsHttpConnectionMgr::nsConnectionEntry::~nsConnectionEntry()
 {
@@ -2798,26 +2833,490 @@ nsHttpConnectionMgr::UpdateCurrentTopLev
 {
     RefPtr<UINT64Wrapper> windowIdWrapper = new UINT64Wrapper(aWindowId);
     return PostEvent(
         &nsHttpConnectionMgr::OnMsgUpdateCurrentTopLevelOuterContentWindowId,
         0,
         windowIdWrapper);
 }
 
+void nsHttpConnectionMgr::SetThrottlingEnabled(bool aEnable)
+{
+    LOG(("nsHttpConnectionMgr::SetThrottlingEnabled enable=%d", aEnable));
+
+    mThrottleEnabled = aEnable;
+
+    if (mThrottleEnabled) {
+        EnsureThrottleTickerIfNeeded();
+    } else {
+        DestroyThrottleTicker();
+        ResumeReadOf(mActiveTransactions[false]);
+        ResumeReadOf(mActiveTransactions[true]);
+    }
+}
+
+void nsHttpConnectionMgr::LogActiveTransactions(char operation)
+{
+    if (!LOG_ENABLED()) {
+        return;
+    }
+
+    nsTArray<RefPtr<nsHttpTransaction>> *trs = nullptr;
+    uint32_t au, at, bu = 0, bt = 0;
+
+    trs = mActiveTransactions[false].Get(mCurrentTopLevelOuterContentWindowId);
+    au = trs ? trs->Length() : 0;
+    trs = mActiveTransactions[true].Get(mCurrentTopLevelOuterContentWindowId);
+    at = trs ? trs->Length() : 0;
+
+    for (auto iter = mActiveTransactions[false].Iter(); !iter.Done(); iter.Next()) {
+        bu += iter.UserData()->Length();
+    }
+    bu -= au;
+    for (auto iter = mActiveTransactions[true].Iter(); !iter.Done(); iter.Next()) {
+        bt += iter.UserData()->Length();
+    }
+    bt -= at;
+
+    // Shows counts of:
+    // - unthrottled transaction for the active tab
+    // - throttled transaction for the active tab
+    // - unthrottled transaction for background tabs
+    // - throttled transaction for background tabs
+    LOG(("Active transactions %c[%u,%u,%u,%u]", operation, au, at, bu, bt));
+}
+
 void
-nsHttpConnectionMgr::OnMsgUpdateCurrentTopLevelOuterContentWindowId(
-    int32_t, ARefBase *param)
+nsHttpConnectionMgr::AddActiveTransaction(nsHttpTransaction * aTrans, bool aThrottled)
+{
+    MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+    uint64_t tabId = aTrans->TopLevelOuterContentWindowId();
+
+    nsTArray<RefPtr<nsHttpTransaction>> *transactions =
+        mActiveTransactions[aThrottled].LookupOrAdd(tabId);
+
+    MOZ_ASSERT(!transactions->Contains(aTrans));
+
+    transactions->AppendElement(aTrans);
+
+    LOG(("nsHttpConnectionMgr::AddActiveTransaction    t=%p tabid=%" PRIx64 "(%d) thr=%d",
+          aTrans, tabId, tabId == mCurrentTopLevelOuterContentWindowId, aThrottled));
+    LogActiveTransactions('+');
+
+    if (tabId == mCurrentTopLevelOuterContentWindowId) {
+        mActiveTabTransactionsExist = true;
+        if (!aThrottled) {
+            mActiveTabUnthrottledTransactionsExist = true;
+        }
+    }
+
+    if (!mThrottleEnabled) {
+        return;
+    }
+
+    EnsureThrottleTickerIfNeeded();
+}
+
+void
+nsHttpConnectionMgr::RemoveActiveTransaction(nsHttpTransaction * aTrans, bool aThrottled)
+{
+    MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+    uint64_t tabId = aTrans->TopLevelOuterContentWindowId();
+    bool forActiveTab = tabId == mCurrentTopLevelOuterContentWindowId;
+
+    nsTArray<RefPtr<nsHttpTransaction>> *transactions =
+        mActiveTransactions[aThrottled].Get(tabId);
+
+    if (!transactions || !transactions->RemoveElement(aTrans)) {
+        // Was not tracked as active, probably just ignore.
+        return;
+    }
+
+    LOG(("nsHttpConnectionMgr::RemoveActiveTransaction t=%p tabid=%" PRIx64 "(%d) thr=%d",
+          aTrans, tabId, forActiveTab, aThrottled));
+
+    if (!transactions->IsEmpty()) {
+        // There are still transactions of the type, hence nothing in the throttling conditions
+        // has changed and we don't need to update "Exists" caches nor we need to wake any now
+        // throttled transactions.
+        LogActiveTransactions('-');
+        return;
+    }
+
+    // To optimize the following logic, always remove the entry when the array is empty.
+    mActiveTransactions[aThrottled].Remove(tabId);
+    LogActiveTransactions('-');
+
+    if (forActiveTab) {
+        // Update caches of the active tab transaction existence, since it's now affected
+        if (!aThrottled) {
+            mActiveTabUnthrottledTransactionsExist = false;
+        }
+        if (mActiveTabTransactionsExist) {
+            mActiveTabTransactionsExist = mActiveTransactions[!aThrottled].Contains(tabId);
+        }
+    }
+
+    if (!mThrottleEnabled) {
+        return;
+    }
+
+    bool unthrottledExist = !mActiveTransactions[false].IsEmpty();
+    bool throttledExist = !mActiveTransactions[true].IsEmpty();
+
+    if (!unthrottledExist && !throttledExist) {
+        // Nothing active globally, just get rid of the timer completely and we are done.
+        MOZ_ASSERT(!mActiveTabUnthrottledTransactionsExist);
+        MOZ_ASSERT(!mActiveTabTransactionsExist);
+
+        DestroyThrottleTicker();
+        return;
+    }
+
+    if (!mThrottlingInhibitsReading) {
+        // There is then nothing to wake up.  Affected transactions will not be put
+        // to sleep automatically on next tick.
+        LOG(("  reading not currently inhibited"));
+        return;
+    }
+
+    if (mActiveTabUnthrottledTransactionsExist) {
+        // There are still unthrottled transactions for the active tab, hence the state
+        // is unaffected and we don't need to do anything (nothing to wake).
+        LOG(("  there are unthrottled for the active tab"));
+        return;
+    }
+
+    if (mActiveTabTransactionsExist) {
+        // There are only trottled transactions for the active tab.
+        // If the last transaction we just removed was a non-throttled for the active tab
+        // we can wake the throttled transactions for the active tab.
+        if (forActiveTab && !aThrottled) {
+            LOG(("  resuming throttled for active tab"));
+            ResumeReadOf(mActiveTransactions[true].Get(mCurrentTopLevelOuterContentWindowId));
+        }
+        return;
+    }
+
+    if (!unthrottledExist) {
+        // There are no unthrottled transactions for any tab.  Resume all throttled,
+        // all are only for background tabs.
+        LOG(("  delay resuming throttled for background tabs"));
+        DelayedResumeBackgroundThrottledTransactions();
+        return;
+    }
+
+    if (forActiveTab) {
+        // Removing the last transaction for the active tab frees up the unthrottled
+        // background tabs transactions.
+        LOG(("  resuming unthrottled for background tabs"));
+        ResumeReadOf(mActiveTransactions[false]);
+        return;
+    }
+
+    LOG(("  not resuming anything"));
+}
+
+void
+nsHttpConnectionMgr::MoveActiveTransaction(nsHttpTransaction * aTrans, bool aThrottled)
+{
+    LOG(("nsHttpConnectionMgr::MoveActiveTransaction ENTER t=%p", aTrans));
+    AddActiveTransaction(aTrans, aThrottled);
+    RemoveActiveTransaction(aTrans, !aThrottled);
+    LOG(("nsHttpConnectionMgr::MoveActiveTransaction EXIT t=%p", aTrans));
+}
+
+bool
+nsHttpConnectionMgr::ShouldStopReading(nsHttpTransaction * aTrans, bool aThrottled)
 {
     MOZ_ASSERT(OnSocketThread(), "not on socket thread");
-    mCurrentTopLevelOuterContentWindowId =
-        static_cast<UINT64Wrapper*>(param)->GetValue();
+
+    if (!mThrottlingInhibitsReading || !mThrottleEnabled) {
+        return false;
+    }
+
+    uint64_t tabId = aTrans->TopLevelOuterContentWindowId();
+
+    if (mActiveTabTransactionsExist) {
+        if (!tabId) {
+            // Chrome initiated and unidentified transactions just respect
+            // their throttle flag, when something for the active tab is happening.
+            return aThrottled;
+        }
+        if (tabId != mCurrentTopLevelOuterContentWindowId) {
+            // This is a background tab request, we want them to always throttle.
+            return true;
+        }
+        if (mActiveTabUnthrottledTransactionsExist) {
+            // Unthrottled transactions for the active tab take precedence
+            return aThrottled;
+        }
+        // This is a throttled transaction for the active tab and there are no
+        // unthrottled for the active tab, just let go on full fuel.
+        return false;
+    }
+
+    if (!mActiveTransactions[false].IsEmpty()) {
+        // This means there are unthrottled active transactions for background tabs.
+        // If we are here, there can't be any transactions for the active tab.
+        // (If there is no transaction for a tab id, there is no entry for it in the hashtable.)
+        return aThrottled;
+    }
+
+    // There are only unthrottled transactions for background tabs: don't throttle.
+    return false;
+}
+
+bool nsHttpConnectionMgr::IsThrottleTickerNeeded()
+{
+    LOG(("nsHttpConnectionMgr::IsThrottleTickerNeeded"));
+
+    if (mActiveTabUnthrottledTransactionsExist &&
+        mActiveTransactions[false].Count() > 1) {
+        LOG(("  there are unthrottled transactions for both active and bck"));
+        return true;
+    }
+
+    if (mActiveTabTransactionsExist &&
+        mActiveTransactions[true].Count() > 1) {
+        LOG(("  there are throttled transactions for both active and bck"));
+        return true;
+    }
+
+    if (!mActiveTransactions[true].IsEmpty() &&
+        !mActiveTransactions[false].IsEmpty()) {
+        LOG(("  there are both throttled and unthrottled transactions"));
+        return true;
+    }
+
+    LOG(("  nothing to throttle"));
+    return false;
+}
+
+void
+nsHttpConnectionMgr::EnsureThrottleTickerIfNeeded()
+{
+    MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+    LOG(("nsHttpConnectionMgr::EnsureThrottleTickerIfNeeded"));
+    if (!IsThrottleTickerNeeded()) {
+        return;
+    }
+
+    // There is a new demand to throttle, hence unschedule delayed resume
+    // of background throttled transastions.
+    CancelDelayedResumeBackgroundThrottledTransactions();
+
+    if (mThrottleTicker) {
+        return;
+    }
+
+    MOZ_ASSERT(!mThrottlingInhibitsReading);
+
+    mThrottleTicker = do_CreateInstance("@mozilla.org/timer;1");
+    if (mThrottleTicker) {
+        mThrottleTicker->Init(this, mThrottleSuspendFor, nsITimer::TYPE_ONE_SHOT);
+        mThrottlingInhibitsReading = true;
+    }
+
+    LogActiveTransactions('^');
+}
+
+void
+nsHttpConnectionMgr::DestroyThrottleTicker()
+{
+    MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+    // Nothing to throttle, hence no need for this timer anymore.
+    CancelDelayedResumeBackgroundThrottledTransactions();
+
+    MOZ_ASSERT(!mThrottleEnabled || !IsThrottleTickerNeeded());
+
+    if (!mThrottleTicker) {
+        return;
+    }
+
+    LOG(("nsHttpConnectionMgr::DestroyThrottleTicker"));
+    mThrottleTicker->Cancel();
+    mThrottleTicker = nullptr;
+    mThrottlingInhibitsReading = false;
+
+    LogActiveTransactions('v');
+}
+
+void
+nsHttpConnectionMgr::ThrottlerTick()
+{
+    MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+    mThrottlingInhibitsReading = !mThrottlingInhibitsReading;
+
+    LOG(("nsHttpConnectionMgr::ThrottlerTick inhibit=%d", mThrottlingInhibitsReading));
+
+    if (!mThrottlingInhibitsReading && !IsThrottleTickerNeeded()) {
+        mThrottleTicker = nullptr;
+    } else {
+        mThrottleTicker = do_CreateInstance("@mozilla.org/timer;1");
+    }
+
+    if (mThrottlingInhibitsReading) {
+        if (mThrottleTicker) {
+            mThrottleTicker->Init(this, mThrottleSuspendFor, nsITimer::TYPE_ONE_SHOT);
+        }
+    } else {
+        // Resume by the ticker happens sooner than delayed resume, no need
+        // for the delayed resume timer
+        CancelDelayedResumeBackgroundThrottledTransactions();
+
+        if (mThrottleTicker) {
+            mThrottleTicker->Init(this, mThrottleResumeFor, nsITimer::TYPE_ONE_SHOT);
+        }
+
+        ResumeReadOf(mActiveTransactions[false], true);
+        ResumeReadOf(mActiveTransactions[true]);
+    }
+}
+
+void
+nsHttpConnectionMgr::DelayedResumeBackgroundThrottledTransactions()
+{
+    if (mDelayedResumeReadTimer) {
+        return;
+    }
+
+    mDelayedResumeReadTimer = do_CreateInstance("@mozilla.org/timer;1");
+    if (!mDelayedResumeReadTimer) {
+        return;
+    }
+
+    LOG(("nsHttpConnectionMgr::DelayedResumeBackgroundThrottledTransactions"));
+    mDelayedResumeReadTimer->Init(this, mThrottleResumeIn, nsITimer::TYPE_ONE_SHOT);
+}
+
+void
+nsHttpConnectionMgr::CancelDelayedResumeBackgroundThrottledTransactions()
+{
+    if (!mDelayedResumeReadTimer) {
+        return;
+    }
+
+    LOG(("nsHttpConnectionMgr::CancelDelayedResumeBackgroundThrottledTransactions"));
+    mDelayedResumeReadTimer->Cancel();
+    mDelayedResumeReadTimer = nullptr;
+}
+
+void
+nsHttpConnectionMgr::ResumeBackgroundThrottledTransactions()
+{
+    MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+    LOG(("nsHttpConnectionMgr::ResumeBackgroundThrottledTransactions"));
+    mDelayedResumeReadTimer = nullptr;
+
+    // We can destroy the ticker, since only transactions to resume now
+    // are background throttled.  If 'higher class' transactions have
+    // been added, we don't get here - the ticker has been scheduled
+    // and hence the delayed resume timer canceled.
+    MOZ_ASSERT(mActiveTransactions[false].IsEmpty() &&
+                          !mActiveTabTransactionsExist);
+
+    DestroyThrottleTicker();
+    ResumeReadOf(mActiveTransactions[true], true);
+}
+
+void
+nsHttpConnectionMgr::ResumeReadOf(
+    nsClassHashtable<nsUint64HashKey, nsTArray<RefPtr<nsHttpTransaction>>>& hashtable,
+    bool excludeForActiveTab)
+{
+    for (auto iter = hashtable.Iter(); !iter.Done(); iter.Next()) {
+        if (excludeForActiveTab && iter.Key() == mCurrentTopLevelOuterContentWindowId) {
+            // These have never been throttled (never stopped reading)
+            continue;
+        }
+        ResumeReadOf(iter.UserData());
+    }
+}
+
+void
+nsHttpConnectionMgr::ResumeReadOf(nsTArray<RefPtr<nsHttpTransaction>>* transactions)
+{
+    MOZ_ASSERT(transactions);
+
+    for (auto trans : *transactions) {
+        trans->ResumeReading();
+    }
+}
+
+void
+nsHttpConnectionMgr::OnMsgUpdateCurrentTopLevelOuterContentWindowId(
+    int32_t aLoading, ARefBase *param)
+{
+    MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+    uint64_t winId = static_cast<UINT64Wrapper*>(param)->GetValue();
+
+    if (mCurrentTopLevelOuterContentWindowId == winId) {
+        // duplicate notification
+        return;
+    }
+
+    bool activeTabWasLoading = mActiveTabTransactionsExist;
+    bool activeTabIdChanged = mCurrentTopLevelOuterContentWindowId != winId;
+
+    mCurrentTopLevelOuterContentWindowId = winId;
+
     LOG(("nsHttpConnectionMgr::OnMsgUpdateCurrentTopLevelOuterContentWindowId"
-         " id=%" PRIu64 "\n",
+         " id=%" PRIx64 "\n",
          mCurrentTopLevelOuterContentWindowId));
+
+    nsTArray<RefPtr<nsHttpTransaction>> *transactions = nullptr;
+
+    if (activeTabIdChanged) {
+        // Update the "Exists" caches and resume any transactions that now deserve it,
+        // changing the active tab changes the conditions for throttling.
+        transactions = mActiveTransactions[false].Get(mCurrentTopLevelOuterContentWindowId);
+        mActiveTabUnthrottledTransactionsExist = !!transactions;
+
+        if (!mActiveTabUnthrottledTransactionsExist) {
+            transactions = mActiveTransactions[true].Get(mCurrentTopLevelOuterContentWindowId);
+        }
+        mActiveTabTransactionsExist = !!transactions;
+    }
+
+    if (transactions) {
+        // This means there are some transactions for this newly activated tab, resume them
+        // but anything else.
+        LOG(("  resuming newly activated tab transactions"));
+        ResumeReadOf(transactions);
+        return;
+    }
+
+    if (!activeTabWasLoading) {
+        // There were no transactions for the previously active tab, hence
+        // all remaning transactions, if there were, were all unthrottled,
+        // no need to wake them.
+        return;
+    }
+
+    if (!mActiveTransactions[false].IsEmpty()) {
+        LOG(("  resuming unthrottled background transactions"));
+        ResumeReadOf(mActiveTransactions[false]);
+        return;
+    }
+
+    if (!mActiveTransactions[true].IsEmpty()) {
+        LOG(("  delayed resuming throttled background transactions"));
+        DelayedResumeBackgroundThrottledTransactions();
+        return;
+    }
+
+    DestroyThrottleTicker();
 }
 
 void
 nsHttpConnectionMgr::TimeoutTick()
 {
     MOZ_ASSERT(OnSocketThread(), "not on socket thread");
     MOZ_ASSERT(mTimeoutTick, "no readtimeout tick");
 
--- a/netwerk/protocol/http/nsHttpConnectionMgr.h
+++ b/netwerk/protocol/http/nsHttpConnectionMgr.h
@@ -50,30 +50,38 @@ public:
     NS_DECL_NSIOBSERVER
 
     // parameter names
     enum nsParamName {
         MAX_URGENT_START_Q,
         MAX_CONNECTIONS,
         MAX_PERSISTENT_CONNECTIONS_PER_HOST,
         MAX_PERSISTENT_CONNECTIONS_PER_PROXY,
-        MAX_REQUEST_DELAY
+        MAX_REQUEST_DELAY,
+        THROTTLING_ENABLED,
+        THROTTLING_SUSPEND_FOR,
+        THROTTLING_RESUME_FOR,
+        THROTTLING_RESUME_IN
     };
 
     //-------------------------------------------------------------------------
     // NOTE: functions below may only be called on the main thread.
     //-------------------------------------------------------------------------
 
     nsHttpConnectionMgr();
 
     MOZ_MUST_USE nsresult Init(uint16_t maxUrgentExcessiveConns,
                                uint16_t maxConnections,
                                uint16_t maxPersistentConnectionsPerHost,
                                uint16_t maxPersistentConnectionsPerProxy,
-                               uint16_t maxRequestDelay);
+                               uint16_t maxRequestDelay,
+                               bool throttleEnabled,
+                               uint32_t throttleSuspendFor,
+                               uint32_t throttleResumeFor,
+                               uint32_t throttleResumeIn);
     MOZ_MUST_USE nsresult Shutdown();
 
     //-------------------------------------------------------------------------
     // NOTE: functions below may be called on any thread.
     //-------------------------------------------------------------------------
 
     // Schedules next pruning of dead connection to happen after
     // given time.
@@ -90,23 +98,19 @@ public:
     // adds a transaction to the list of managed transactions.
     MOZ_MUST_USE nsresult AddTransaction(nsHttpTransaction *, int32_t priority);
 
     // called to reschedule the given transaction.  it must already have been
     // added to the connection manager via AddTransaction.
     MOZ_MUST_USE nsresult RescheduleTransaction(nsHttpTransaction *,
                                                 int32_t priority);
 
-    // tells the transaction to stop receiving the response when |throttle|
-    // is true.  to start receiving again, this must be called with |throttle|
-    // set to false.  calling multiple times with the same value of |throttle|
-    // has no effect.  called by nsHttpChannels with the Throttleable class set
-    // and controlled by net::ThrottlingService.
-    // there is nothing to do when this fails, hence the void result.
-    void ThrottleTransaction(nsHttpTransaction *, bool throttle);
+    // TOOD
+    void UpdateClassOfServiceOnTransaction(nsHttpTransaction *,
+                                           uint32_t classOfService);
 
     // cancels a transaction w/ the given reason.
     MOZ_MUST_USE nsresult CancelTransaction(nsHttpTransaction *,
                                             nsresult reason);
     MOZ_MUST_USE nsresult CancelTransactions(nsHttpConnectionInfo *,
                                              nsresult reason);
 
     // called to force the connection manager to prune its list of idle
@@ -206,16 +210,27 @@ public:
 
     uint16_t MaxRequestDelay() { return mMaxRequestDelay; }
 
     // public, so that the SPDY/http2 seesions can activate
     void ActivateTimeoutTick();
 
     nsresult UpdateCurrentTopLevelOuterContentWindowId(uint64_t aWindowId);
 
+    // tracks and untracks active transactions according their throttle status
+    void AddActiveTransaction(nsHttpTransaction* aTrans, bool aThrottled);
+    void RemoveActiveTransaction(nsHttpTransaction* aTrans, bool aThrottled);
+    void MoveActiveTransaction(nsHttpTransaction* aTrans, bool aThrottled);
+
+    // called by nsHttpTransaction::WriteSegments.  decides whether the transaction
+    // should stop reading data based on: the throttling ticker status, overall
+    // status of all active transactions regarding active tab and respective
+    // throttling state.
+    bool ShouldStopReading(nsHttpTransaction* aTrans, bool aThrottled);
+
 private:
     virtual ~nsHttpConnectionMgr();
 
     class nsHalfOpenSocket;
     class PendingTransactionInfo;
 
     // nsConnectionEntry
     //
@@ -481,16 +496,20 @@ private:
     nsCOMPtr<nsIEventTarget>     mSocketThreadTarget;
 
     // connection limits
     uint16_t mMaxUrgentExcessiveConns;
     uint16_t mMaxConns;
     uint16_t mMaxPersistConnsPerHost;
     uint16_t mMaxPersistConnsPerProxy;
     uint16_t mMaxRequestDelay; // in seconds
+    bool mThrottleEnabled;
+    uint32_t mThrottleSuspendFor;
+    uint32_t mThrottleResumeFor;
+    uint32_t mThrottleResumeIn;
     Atomic<bool, mozilla::Relaxed> mIsShuttingDown;
 
     //-------------------------------------------------------------------------
     // NOTE: these members are only accessed on the socket transport thread
     //-------------------------------------------------------------------------
 
     MOZ_MUST_USE bool ProcessPendingQForEntry(nsConnectionEntry *,
                                               bool considerAll);
@@ -574,17 +593,17 @@ private:
         const nsConnectionEntry *ent,
         nsresult reason);
 
     // message handlers
     void OnMsgShutdown             (int32_t, ARefBase *);
     void OnMsgShutdownConfirm      (int32_t, ARefBase *);
     void OnMsgNewTransaction       (int32_t, ARefBase *);
     void OnMsgReschedTransaction   (int32_t, ARefBase *);
-    void OnMsgThrottleTransaction  (int32_t, ARefBase *);
+    void OnMsgUpdateClassOfServiceOnTransaction  (int32_t, ARefBase *);
     void OnMsgCancelTransaction    (int32_t, ARefBase *);
     void OnMsgCancelTransactions   (int32_t, ARefBase *);
     void OnMsgProcessPendingQ      (int32_t, ARefBase *);
     void OnMsgPruneDeadConnections (int32_t, ARefBase *);
     void OnMsgSpeculativeConnect   (int32_t, ARefBase *);
     void OnMsgReclaimConnection    (int32_t, ARefBase *);
     void OnMsgCompleteUpgrade      (int32_t, ARefBase *);
     void OnMsgUpdateParam          (int32_t, ARefBase *);
@@ -635,16 +654,68 @@ private:
     // Read Timeout Tick handlers
     void TimeoutTick();
 
     // For diagnostics
     void OnMsgPrintDiagnostics(int32_t, ARefBase *);
 
     nsCString mLogData;
     uint64_t mCurrentTopLevelOuterContentWindowId;
+
+    // Called on a pref change
+    void SetThrottlingEnabled(bool aEnable);
+
+    // Two hashtalbes keeping track of active transactions regarding window id and throttling.
+    // Used by the throttling algorithm to obtain number of transactions for the active tab
+    // and for inactive tabs according their throttle status.
+    // mActiveTransactions[0] are all unthrottled transactions, mActiveTransactions[1] throttled.
+    nsClassHashtable<nsUint64HashKey, nsTArray<RefPtr<nsHttpTransaction>>> mActiveTransactions[2];
+
+    // Whether we are inside the "stop reading" interval, altered by the throttle ticker
+    bool mThrottlingInhibitsReading;
+
+    // ticker for the 'stop reading'/'resume reading' signal
+    nsCOMPtr<nsITimer> mThrottleTicker;
+    // Checks if the combination of active transactions requires the ticker.
+    bool IsThrottleTickerNeeded();
+    // The method also unschedules the delayed resume of background tabs timer
+    // if the ticker was about to be scheduled.
+    void EnsureThrottleTickerIfNeeded();
+    // Drops also the mThrottlingInhibitsReading flag.  Immediate or delayed resume
+    // of currently throttled transactions is not affected by this method.
+    void DestroyThrottleTicker();
+    // Handler for the ticker: alters the mThrottlingInhibitsReading flag.
+    void ThrottlerTick();
+
+    // mechanism to delay immediate resume of background tabs and chrome initiated
+    // throttled transactions after the last transaction blocking their unthrottle
+    // has been removed.  Needs to be delayed because during a page load there is
+    // a number of intervals when there is no transaction that would cause throttling.
+    // Hence, throttling of long standing responses, like downloads, would be mostly
+    // ineffective if resumed during every such interval.
+    nsCOMPtr<nsITimer> mDelayedResumeReadTimer;
+    // Schedule the resume
+    void DelayedResumeBackgroundThrottledTransactions();
+    // Simply destroys the timer
+    void CancelDelayedResumeBackgroundThrottledTransactions();
+    // Handler for the timer: resumes all background throttled transactions
+    void ResumeBackgroundThrottledTransactions();
+
+    // Simple helpers, iterates the given hash/array and resume.
+    // @param excludeActive: skip active tabid transactions.
+    void ResumeReadOf(nsClassHashtable<nsUint64HashKey, nsTArray<RefPtr<nsHttpTransaction>>>&,
+                      bool excludeActive = false);
+    void ResumeReadOf(nsTArray<RefPtr<nsHttpTransaction>>*);
+
+    // Cached status of the active tab active transactions existence,
+    // saves a lot of hashtable lookups
+    bool mActiveTabTransactionsExist;
+    bool mActiveTabUnthrottledTransactionsExist;
+
+    void LogActiveTransactions(char);
 };
 
 NS_DEFINE_STATIC_IID_ACCESSOR(nsHttpConnectionMgr::nsHalfOpenSocket, NS_HALFOPENSOCKET_IID)
 
 } // namespace net
 } // namespace mozilla
 
 #endif // !nsHttpConnectionMgr_h__
--- a/netwerk/protocol/http/nsHttpHandler.cpp
+++ b/netwerk/protocol/http/nsHttpHandler.cpp
@@ -186,16 +186,20 @@ nsHttpHandler::nsHttpHandler()
     , mMaxRequestAttempts(6)
     , mMaxRequestDelay(10)
     , mIdleSynTimeout(250)
     , mH2MandatorySuiteEnabled(false)
     , mMaxUrgentExcessiveConns(3)
     , mMaxConnections(24)
     , mMaxPersistentConnectionsPerServer(2)
     , mMaxPersistentConnectionsPerProxy(4)
+    , mThrottleEnabled(true)
+    , mThrottleSuspendFor(3000)
+    , mThrottleResumeFor(200)
+    , mThrottleResumeIn(400)
     , mRedirectionLimit(10)
     , mPhishyUserPassLength(1)
     , mQoSBits(0x00)
     , mEnforceAssocReq(false)
     , mLastUniqueID(NowInSeconds())
     , mSessionStartTime(0)
     , mLegacyAppName("Mozilla")
     , mLegacyAppVersion("5.0")
@@ -544,17 +548,21 @@ nsHttpHandler::InitConnectionMgr()
     if (!mConnMgr) {
         mConnMgr = new nsHttpConnectionMgr();
     }
 
     rv = mConnMgr->Init(mMaxUrgentExcessiveConns,
                         mMaxConnections,
                         mMaxPersistentConnectionsPerServer,
                         mMaxPersistentConnectionsPerProxy,
-                        mMaxRequestDelay);
+                        mMaxRequestDelay,
+                        mThrottleEnabled,
+                        mThrottleSuspendFor,
+                        mThrottleResumeFor,
+                        mThrottleResumeIn);
     return rv;
 }
 
 nsresult
 nsHttpHandler::AddStandardRequestHeaders(nsHttpRequestHead *request, bool isSecure)
 {
     nsresult rv;
 
@@ -1541,16 +1549,51 @@ nsHttpHandler::PrefsChanged(nsIPrefBranc
 
     if (PREF_CHANGED(HTTP_PREF("max_response_header_size"))) {
         rv = prefs->GetIntPref(HTTP_PREF("max_response_header_size"), &val);
         if (NS_SUCCEEDED(rv)) {
             mMaxHttpResponseHeaderSize = val;
         }
     }
 
+    if (PREF_CHANGED("network.http.throttle.enable")) {
+        rv = prefs->GetBoolPref("network.http.throttle.enable", &mThrottleEnabled);
+        if (NS_SUCCEEDED(rv) && mConnMgr) {
+            Unused << mConnMgr->UpdateParam(nsHttpConnectionMgr::THROTTLING_ENABLED,
+                                            static_cast<int32_t>(mThrottleEnabled));
+        }
+    }
+
+    if (PREF_CHANGED("network.http.throttle.suspend-for")) {
+        rv = prefs->GetIntPref("network.http.throttle.suspend-for", &val);
+        mThrottleSuspendFor = (uint32_t)clamped(val, 0, 120000);
+        if (NS_SUCCEEDED(rv) && mConnMgr) {
+            Unused << mConnMgr->UpdateParam(nsHttpConnectionMgr::THROTTLING_SUSPEND_FOR,
+                                            mThrottleSuspendFor);
+        }
+    }
+
+    if (PREF_CHANGED("network.http.throttle.resume-for")) {
+        rv = prefs->GetIntPref("network.http.throttle.resume-for", &val);
+        mThrottleResumeFor = (uint32_t)clamped(val, 0, 120000);
+        if (NS_SUCCEEDED(rv) && mConnMgr) {
+            Unused << mConnMgr->UpdateParam(nsHttpConnectionMgr::THROTTLING_RESUME_FOR,
+                                            mThrottleResumeFor);
+        }
+    }
+
+    if (PREF_CHANGED("network.http.throttle.resume-background-in")) {
+        rv = prefs->GetIntPref("network.http.throttle.resume-background-in", &val);
+        mThrottleResumeIn = (uint32_t)clamped(val, 0, 120000);
+        if (NS_SUCCEEDED(rv) && mConnMgr) {
+            Unused << mConnMgr->UpdateParam(nsHttpConnectionMgr::THROTTLING_RESUME_IN,
+                                            mThrottleResumeIn);
+        }
+    }
+
     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
@@ -219,20 +219,20 @@ public:
     // Called to change the priority of an existing transaction that has
     // already been initiated.
     MOZ_MUST_USE nsresult RescheduleTransaction(nsHttpTransaction *trans,
                                                 int32_t priority)
     {
         return mConnMgr->RescheduleTransaction(trans, priority);
     }
 
-    void ThrottleTransaction(nsHttpTransaction *trans,
-                                              bool throttle)
+    void UpdateClassOfServiceOnTransaction(nsHttpTransaction *trans,
+                                           uint32_t classOfService)
     {
-        mConnMgr->ThrottleTransaction(trans, throttle);
+        mConnMgr->UpdateClassOfServiceOnTransaction(trans, classOfService);
     }
 
     // Called to cancel a transaction, which may or may not be assigned to
     // a connection.  Callable from any thread.
     MOZ_MUST_USE nsresult CancelTransaction(nsHttpTransaction *trans,
                                             nsresult reason)
     {
         return mConnMgr->CancelTransaction(trans, reason);
@@ -443,16 +443,21 @@ private:
     uint16_t mIdleSynTimeout;
 
     bool     mH2MandatorySuiteEnabled;
     uint16_t mMaxUrgentExcessiveConns;
     uint16_t mMaxConnections;
     uint8_t  mMaxPersistentConnectionsPerServer;
     uint8_t  mMaxPersistentConnectionsPerProxy;
 
+    bool mThrottleEnabled;
+    uint32_t mThrottleSuspendFor;
+    uint32_t mThrottleResumeFor;
+    uint32_t mThrottleResumeIn;
+
     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/netwerk/protocol/http/nsHttpTransaction.cpp
+++ b/netwerk/protocol/http/nsHttpTransaction.cpp
@@ -26,16 +26,17 @@
 #include "nsISeekableStream.h"
 #include "nsMultiplexInputStream.h"
 #include "nsStringStream.h"
 
 #include "nsComponentManagerUtils.h" // do_CreateInstance
 #include "nsIHttpActivityObserver.h"
 #include "nsSocketTransportService2.h"
 #include "nsICancelable.h"
+#include "nsIClassOfService.h"
 #include "nsIEventTarget.h"
 #include "nsIHttpChannelInternal.h"
 #include "nsIInputStream.h"
 #include "nsIThrottledInputChannel.h"
 #include "nsITransport.h"
 #include "nsIOService.h"
 #include "nsIRequestContext.h"
 #include "nsIHttpAuthenticator.h"
@@ -100,19 +101,21 @@ nsHttpTransaction::nsHttpTransaction()
     , mPriority(0)
     , mRestartCount(0)
     , mCaps(0)
     , mHttpVersion(NS_HTTP_VERSION_UNKNOWN)
     , mHttpResponseCode(0)
     , mCurrentHttpResponseHeaderSize(0)
     , mCapsToClear(0)
     , mResponseIsComplete(false)
-    , mThrottleResponse(false)
+    , mReadingStopped(false)
     , mClosed(false)
     , mConnected(false)
+    , mActivated(false)
+    , mActivatedAsH2(false)
     , mHaveStatusLine(false)
     , mHaveAllHeaders(false)
     , mTransactionDone(false)
     , mDidContentStart(false)
     , mNoContent(false)
     , mSentData(false)
     , mReceivedData(false)
     , mStatusEventPending(false)
@@ -146,21 +149,53 @@ nsHttpTransaction::nsHttpTransaction()
 #ifdef MOZ_VALGRIND
     memset(&mSelfAddr, 0, sizeof(NetAddr));
     memset(&mPeerAddr, 0, sizeof(NetAddr));
 #endif
     mSelfAddr.raw.family = PR_AF_UNSPEC;
     mPeerAddr.raw.family = PR_AF_UNSPEC;
 }
 
-void nsHttpTransaction::ThrottleResponse(bool aThrottle)
+void nsHttpTransaction::ResumeReading()
 {
     MOZ_ASSERT(OnSocketThread(), "not on socket thread");
 
-    mThrottleResponse = aThrottle;
+    if (!mReadingStopped) {
+        return;
+    }
+
+    LOG(("nsHttpTransaction::ResumeReading %p", this));
+
+    mReadingStopped = false;
+    if (mConnection) {
+        nsresult rv = mConnection->ResumeRecv();
+        if (NS_FAILED(rv)) {
+            LOG(("  resume failed with rv=%" PRIx32, static_cast<uint32_t>(rv)));
+        }
+    }
+}
+
+void nsHttpTransaction::SetClassOfService(uint32_t cos)
+{
+    bool wasThrottling = mClassOfService & nsIClassOfService::Throttleable;
+
+    mClassOfService = cos;
+
+    bool isThrottling = mClassOfService & nsIClassOfService::Throttleable;
+
+    if (mConnection && wasThrottling != isThrottling) {
+        // Do nothing until we are actually activated.  For now
+        // only remember the throttle flag.  Call to MoveActiveTransaction
+        // would add this transaction to the list too early.
+        gHttpHandler->ConnMgr()->MoveActiveTransaction(this, isThrottling);
+
+        if (mReadingStopped && !isThrottling) {
+            ResumeReading();
+        }
+    }
 }
 
 nsHttpTransaction::~nsHttpTransaction()
 {
     LOG(("Destroying nsHttpTransaction @%p\n", this));
     if (mTransactionObserver) {
         mTransactionObserver->Complete(this, NS_OK);
     }
@@ -468,16 +503,31 @@ nsHttpTransaction::SetConnection(nsAHttp
 {
     {
         MutexAutoLock lock(mLock);
         mConnection = conn;
     }
 }
 
 void
+nsHttpTransaction::OnActivated(bool h2)
+{
+    MOZ_ASSERT(OnSocketThread());
+
+    mActivatedAsH2 = h2;
+    if (mActivated) {
+        return;
+    }
+
+    mActivated = true;
+    gHttpHandler->ConnMgr()->AddActiveTransaction(
+        this, mClassOfService & nsIClassOfService::Throttleable);
+}
+
+void
 nsHttpTransaction::GetSecurityCallbacks(nsIInterfaceRequestor **cb)
 {
     MutexAutoLock lock(mLock);
     nsCOMPtr<nsIInterfaceRequestor> tmp(mCallbacks);
     tmp.forget(cb);
 }
 
 void
@@ -769,38 +819,49 @@ nsHttpTransaction::WritePipeSegment(nsIO
 
     return rv; // failure code only stops WriteSegments; it is not propagated.
 }
 
 nsresult
 nsHttpTransaction::WriteSegments(nsAHttpSegmentWriter *writer,
                                  uint32_t count, uint32_t *countWritten)
 {
-    static bool reentrantFlag = false;
-    LOG(("nsHttpTransaction::WriteSegments %p reentrantFlag=%d",
-         this, reentrantFlag));
-    MOZ_DIAGNOSTIC_ASSERT(!reentrantFlag);
-    reentrantFlag = true;
+    LOG(("nsHttpTransaction::WriteSegments %p", this));
+
     MOZ_ASSERT(OnSocketThread(), "not on socket thread");
 
     if (mTransactionDone) {
-        reentrantFlag = false;
         return NS_SUCCEEDED(mStatus) ? NS_BASE_STREAM_CLOSED : mStatus;
     }
 
+    // Throttling feature is now disabled for http/2 transactions
+    // because of bug 1367861.  The logic around mActivatedAsH2
+    // will be removed when that is fixed
+    if (!mActivatedAsH2 &&
+        gHttpHandler->ConnMgr()->ShouldStopReading(this,
+            mClassOfService & nsIClassOfService::Throttleable)) {
+        LOG(("nsHttpTransaction::WriteSegments %p response throttled", this));
+        // Must remember that we have to call ResumeRecv() on our connection when
+        // called back by the conn manager to resume reading.
+        mReadingStopped = true;
+        // This makes the underlaying connection or stream wait for explicit resume.
+        // For h1 this means we stop reading from the socket.
+        // For h2 this means we stop updating recv window for the stream.
+        return NS_BASE_STREAM_WOULD_BLOCK;
+    }
+
     mWriter = writer;
 
 #ifdef WIN32 // bug 1153929
     MOZ_DIAGNOSTIC_ASSERT(mPipeOut);
     uint32_t * vtable = (uint32_t *) mPipeOut.get();
     MOZ_DIAGNOSTIC_ASSERT(*vtable != 0);
 #endif // WIN32
 
     if (!mPipeOut) {
-        reentrantFlag = false;
         return NS_ERROR_UNEXPECTED;
     }
 
     nsresult rv = mPipeOut->WriteSegments(WritePipeSegment, this, count, countWritten);
 
     mWriter = nullptr;
 
     if (mForceRestart) {
@@ -822,26 +883,31 @@ nsHttpTransaction::WriteSegments(nsAHttp
             mPipeOut->AsyncWait(this, 0, 0, target);
             mWaitingOnPipeOut = true;
         } else {
             NS_ERROR("no socket thread event target");
             rv = NS_ERROR_UNEXPECTED;
         }
     }
 
-    reentrantFlag = false;
     return rv;
 }
 
 void
 nsHttpTransaction::Close(nsresult reason)
 {
     LOG(("nsHttpTransaction::Close [this=%p reason=%" PRIx32 "]\n",
          this, static_cast<uint32_t>(reason)));
 
+    if (!mClosed) {
+        gHttpHandler->ConnMgr()->RemoveActiveTransaction(
+            this, mClassOfService & nsIClassOfService::Throttleable);
+        mActivated = false;
+    }
+
     MOZ_ASSERT(OnSocketThread(), "not on socket thread");
     if (reason == NS_BINDING_RETARGETED) {
         LOG(("  close %p skipped due to ERETARGETED\n", this));
         return;
     }
 
     if (mClosed) {
         LOG(("  already closed\n"));
--- a/netwerk/protocol/http/nsHttpTransaction.h
+++ b/netwerk/protocol/http/nsHttpTransaction.h
@@ -84,16 +84,18 @@ public:
                                uint64_t               reqContentLength,
                                bool                   reqBodyIncludesHeaders,
                                nsIEventTarget        *consumerTarget,
                                nsIInterfaceRequestor *callbacks,
                                nsITransportEventSink *eventsink,
                                uint64_t               topLevelOuterContentWindowId,
                                nsIAsyncInputStream  **responseBody);
 
+    void OnActivated(bool h2) override;
+
     // attributes
     nsHttpResponseHead    *ResponseHead()   { return mHaveAllHeaders ? mResponseHead : nullptr; }
     nsISupports           *SecurityInfo()   { return mSecurityInfo; }
 
     nsIEventTarget        *ConsumerTarget() { return mConsumerTarget; }
     nsISupports           *HttpChannel()    { return mChannel; }
 
     void SetSecurityCallbacks(nsIInterfaceRequestor* aCallbacks);
@@ -302,24 +304,27 @@ private:
     // redundant requests on the network. The member itself is atomic, but
     // access to it from the networking thread may happen either before or
     // after the main thread modifies it. To deal with raciness, only unsetting
     // bitfields should be allowed: 'lost races' will thus err on the
     // conservative side, e.g. by going ahead with a 2nd DNS refresh.
     Atomic<uint32_t>                mCapsToClear;
     Atomic<bool, ReleaseAcquire>    mResponseIsComplete;
 
-    // If true, this transaction was asked to stop receiving the response.
-    // NOTE: this flag is currently unused.  A useful remnant of an old throttling algorithm.
-    bool                            mThrottleResponse;
+    // True iff WriteSegments was called while this transaction should be throttled (stop reading)
+    // Used to resume read on unblock of reading.  Conn manager is responsible for calling back
+    // to resume reading.
+    bool                            mReadingStopped;
 
     // state flags, all logically boolean, but not packed together into a
     // bitfield so as to avoid bitfield-induced races.  See bug 560579.
     bool                            mClosed;
     bool                            mConnected;
+    bool                            mActivated;
+    bool                            mActivatedAsH2;
     bool                            mHaveStatusLine;
     bool                            mHaveAllHeaders;
     bool                            mTransactionDone;
     bool                            mDidContentStart;
     bool                            mNoContent; // expecting an empty entity body
     bool                            mSentData;
     bool                            mReceivedData;
     bool                            mStatusEventPending;
@@ -368,27 +373,27 @@ public:
     void OnTokenBucketAdmitted() override; // ATokenBucketEvent
 
     // CancelPacing() can be used to tell the token bucket to remove this
     // transaction from the list of pending transactions. This is used when a
     // transaction is believed to be HTTP/1 (and thus subject to rate pacing)
     // but later can be dispatched via spdy (not subject to rate pacing).
     void CancelPacing(nsresult reason);
 
-    // Called only on the socket thread.  Updates the flag whether the transaction
-    // should make the underlying connection or session stop reading from the socket.
-    void ThrottleResponse(bool aThrottle);
+    // Called by the connetion manager on the socket thread when reading for this
+    // previously throttled transaction has to be resumed.
+    void ResumeReading();
 
 private:
     bool mSubmittedRatePacing;
     bool mPassedRatePacing;
     bool mSynchronousRatePaceRequest;
     nsCOMPtr<nsICancelable> mTokenBucketCancel;
 public:
-    void     SetClassOfService(uint32_t cos) { mClassOfService = cos; }
+    void     SetClassOfService(uint32_t cos);
     uint32_t ClassOfService() { return mClassOfService; }
 private:
     uint32_t mClassOfService;
 
 public:
     // setting TunnelProvider to non-null means the transaction should only
     // be dispatched on a specific ConnectionInfo Hash Key (as opposed to a
     // generic wild card one). That means in the specific case of carrying this