Merge m-c to graphics
authorKartikaya Gupta <kgupta@mozilla.com>
Tue, 25 Apr 2017 08:32:48 -0400
changeset 405653 0fd98cd60c05480d81c3f7d48d017535b8bbd1c0
parent 405652 5fd5cf125a540be927531d609d08bbd11e331606 (current diff)
parent 405212 a30dc237c3a600a5231f2974fc2b85dfb5513414 (diff)
child 405654 708b9a30040654c6ead5d03a18966d11181cee1d
push id1490
push usermtabara@mozilla.com
push dateMon, 31 Jul 2017 14:08:16 +0000
treeherdermozilla-release@70e32e6bf15e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
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 m-c to graphics MozReview-Commit-ID: GdyXEYZsVuX
dom/base/test/browser_bug1307747.js
gfx/ipc/GfxMessageUtils.h
gfx/layers/LayerMetricsWrapper.h
gfx/layers/Layers.h
gfx/layers/apz/src/APZCTreeManager.cpp
js/src/wasm/WasmRuntime.cpp
js/src/wasm/WasmRuntime.h
layout/generic/nsImageFrame.cpp
layout/reftests/webkit-gradient/webkit-gradient-approx-linear-1-ref.html
layout/reftests/webkit-gradient/webkit-gradient-approx-linear-1.html
layout/reftests/xul/reftest.list
modules/libpref/init/all.js
servo/components/script/dom/domrectlist.rs
servo/components/script/dom/webidls/DOMRectList.webidl
testing/web-platform/meta/dom/lists/DOMTokenList-Iterable.html.ini
testing/web-platform/meta/dom/lists/DOMTokenList-iteration.html.ini
testing/web-platform/meta/dom/lists/DOMTokenList-value.html.ini
testing/web-platform/meta/dom/nodes/Element-classlist.html.ini
testing/web-platform/meta/webvtt/rendering/cues-with-video/processing-model/align_middle.html.ini
testing/web-platform/meta/webvtt/rendering/cues-with-video/processing-model/align_middle_position_50.html.ini
testing/web-platform/meta/webvtt/rendering/cues-with-video/processing-model/basic.html.ini
testing/web-platform/meta/webvtt/rendering/cues-with-video/processing-model/bidi/bidi_ruby.html.ini
testing/web-platform/meta/webvtt/rendering/cues-with-video/processing-model/bidi/u0041_first.html.ini
testing/web-platform/meta/webvtt/rendering/cues-with-video/processing-model/bidi/u06E9_no_strong_dir.html.ini
testing/web-platform/meta/webvtt/rendering/cues-with-video/processing-model/evil/size_90.html.ini
testing/web-platform/meta/webvtt/rendering/cues-with-video/processing-model/evil/size_99.html.ini
testing/web-platform/meta/webvtt/rendering/cues-with-video/processing-model/selectors/__dir__.ini
testing/web-platform/meta/webvtt/rendering/cues-with-video/processing-model/selectors/cue/font_properties.html.ini
testing/web-platform/meta/webvtt/rendering/cues-with-video/processing-model/selectors/cue/font_shorthand.html.ini
testing/web-platform/mozilla/meta/dom/classList.html.ini
toolkit/library/gtest/rust/Cargo.lock
toolkit/library/rust/Cargo.lock
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -994,16 +994,23 @@ pref("security.sandbox.content.level", 1
 #endif
 
 // This controls the depth of stack trace that is logged when Windows sandbox
 // logging is turned on.  This is only currently available for the content
 // process because the only other sandbox (for GMP) has too strict a policy to
 // allow stack tracing.  This does not require a restart to take effect.
 pref("security.sandbox.windows.log.stackTraceDepth", 0);
 #endif
+
+// This controls the strength of the Windows GPU process sandbox.  Changes
+// will require restart.
+// For information on what the level number means, see
+// SetSecurityLevelForGPUProcess() in
+// security/sandbox/win/src/sandboxbroker/sandboxBroker.cpp
+pref("security.sandbox.gpu.level", 1);
 #endif
 
 #if defined(XP_MACOSX) && defined(MOZ_SANDBOX) && defined(MOZ_CONTENT_SANDBOX)
 // This pref is discussed in bug 1083344, the naming is inspired from its
 // Windows counterpart, but on Mac it's an integer which means:
 // 0 -> "no sandbox"
 // 1 -> "preliminary content sandboxing enabled: write access to
 //       home directory is prevented"
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -5098,17 +5098,18 @@ var TabsProgressListener = {
 function nsBrowserAccess() { }
 
 nsBrowserAccess.prototype = {
   QueryInterface: XPCOMUtils.generateQI([Ci.nsIBrowserDOMWindow, Ci.nsISupports]),
 
   _openURIInNewTab(aURI, aReferrer, aReferrerPolicy, aIsPrivate,
                              aIsExternal, aForceNotRemote = false,
                              aUserContextId = Ci.nsIScriptSecurityManager.DEFAULT_USER_CONTEXT_ID,
-                             aOpener = null, aTriggeringPrincipal = null) {
+                             aOpener = null, aTriggeringPrincipal = null,
+                             aNextTabParentId = 0) {
     let win, needToFocusWin;
 
     // try the current window.  if we're in a popup, fall back on the most recent browser window
     if (window.toolbar.visible)
       win = window;
     else {
       win = RecentWindow.getMostRecentBrowserWindow({private: aIsPrivate});
       needToFocusWin = true;
@@ -5131,16 +5132,17 @@ nsBrowserAccess.prototype = {
                                       triggeringPrincipal: aTriggeringPrincipal,
                                       referrerURI: aReferrer,
                                       referrerPolicy: aReferrerPolicy,
                                       userContextId: aUserContextId,
                                       fromExternal: aIsExternal,
                                       inBackground: loadInBackground,
                                       forceNotRemote: aForceNotRemote,
                                       opener: aOpener,
+                                      nextTabParentId: aNextTabParentId,
                                       });
     let browser = win.gBrowser.getBrowserForTab(tab);
 
     if (needToFocusWin || (!loadInBackground && aIsExternal))
       win.focus();
 
     return browser;
   },
@@ -5233,17 +5235,18 @@ nsBrowserAccess.prototype = {
                                     });
         }
         if (!gPrefService.getBoolPref("browser.tabs.loadDivertedInBackground"))
           window.focus();
     }
     return newWindow;
   },
 
-  openURIInFrame: function browser_openURIInFrame(aURI, aParams, aWhere, aFlags) {
+  openURIInFrame: function browser_openURIInFrame(aURI, aParams, aWhere, aFlags,
+                                                  aNextTabParentId) {
     if (aWhere != Ci.nsIBrowserDOMWindow.OPEN_NEWTAB) {
       dump("Error: openURIInFrame can only open in new tabs");
       return null;
     }
 
     var isExternal = !!(aFlags & Ci.nsIBrowserDOMWindow.OPEN_EXTERNAL);
 
     var userContextId = aParams.openerOriginAttributes &&
@@ -5252,17 +5255,18 @@ nsBrowserAccess.prototype = {
                           : Ci.nsIScriptSecurityManager.DEFAULT_USER_CONTEXT_ID
 
     let referrer = aParams.referrer ? makeURI(aParams.referrer) : null;
     let browser = this._openURIInNewTab(aURI, referrer,
                                         aParams.referrerPolicy,
                                         aParams.isPrivate,
                                         isExternal, false,
                                         userContextId, null,
-                                        aParams.triggeringPrincipal);
+                                        aParams.triggeringPrincipal,
+                                        aNextTabParentId);
     if (browser)
       return browser.QueryInterface(Ci.nsIFrameLoaderOwner);
 
     return null;
   },
 
   isTabContentWindow(aWindow) {
     return gBrowser.browsers.some(browser => browser.contentWindow == aWindow);
--- a/browser/base/content/browser.xul
+++ b/browser/base/content/browser.xul
@@ -160,16 +160,17 @@
            level="parent"
            overflowpadding="30" />
 
     <panel id="DateTimePickerPanel"
            type="arrow"
            hidden="true"
            orient="vertical"
            noautofocus="true"
+           noautohide="true"
            consumeoutsideclicks="false"
            level="parent"
            tabspecific="true">
       <iframe id="dateTimePopupFrame"/>
     </panel>
 
     <!-- for select dropdowns. The menupopup is what shows the list of options,
          and the popuponly menulist makes things like the menuactive attributes
@@ -1066,36 +1067,37 @@
     </toolbarpalette>
   </toolbox>
 
   <hbox id="fullscr-toggler" hidden="true"/>
 
   <deck id="content-deck" flex="1">
     <hbox flex="1" id="browser">
       <vbox id="browser-border-start" hidden="true" layer="true"/>
+      <vbox id="sidebar-box" hidden="true" class="chromeclass-extrachrome">
+        <sidebarheader id="sidebar-header" align="center">
+          <label id="sidebar-title" persist="value" flex="1" crop="end" control="sidebar"/>
+          <image id="sidebar-throbber"/>
+          <toolbarbutton class="close-icon tabbable" tooltiptext="&sidebarCloseButton.tooltip;" oncommand="SidebarUI.hide();"/>
+        </sidebarheader>
+        <browser id="sidebar" flex="1" autoscroll="false" disablehistory="true" disablefullscreen="true"
+                  style="min-width: 14em; width: 18em; max-width: 36em;" tooltip="aHTMLTooltip"/>
+      </vbox>
+
+      <splitter id="sidebar-splitter" class="chromeclass-extrachrome sidebar-splitter" hidden="true"/>
       <vbox id="appcontent" flex="1">
         <notificationbox id="high-priority-global-notificationbox" notificationside="top"/>
         <tabbrowser id="content"
                     flex="1" contenttooltip="aHTMLTooltip"
                     tabcontainer="tabbrowser-tabs"
                     contentcontextmenu="contentAreaContextMenu"
                     autocompletepopup="PopupAutoComplete"
                     selectmenulist="ContentSelectDropdown"
                     datetimepicker="DateTimePickerPanel"/>
       </vbox>
-      <splitter id="sidebar-splitter" class="chromeclass-extrachrome sidebar-splitter" hidden="true"/>
-      <vbox id="sidebar-box" hidden="true" class="chromeclass-extrachrome">
-        <sidebarheader id="sidebar-header" align="center">
-          <label id="sidebar-title" persist="value" flex="1" crop="end" control="sidebar"/>
-          <image id="sidebar-throbber"/>
-          <toolbarbutton class="close-icon tabbable" tooltiptext="&sidebarCloseButton.tooltip;" oncommand="SidebarUI.hide();"/>
-        </sidebarheader>
-        <browser id="sidebar" flex="1" autoscroll="false" disablehistory="true" disablefullscreen="true"
-                  style="min-width: 14em; width: 18em; max-width: 36em;" tooltip="aHTMLTooltip"/>
-      </vbox>
       <vbox id="browser-border-end" hidden="true" layer="true"/>
     </hbox>
 #include ../../components/customizableui/content/customizeMode.inc.xul
   </deck>
 
   <html:div id="fullscreen-warning" class="pointerlockfswarning" hidden="true">
     <html:div class="pointerlockfswarning-domain-text">
       &fullscreenWarning.beforeDomain.label;
--- a/browser/base/content/tabbrowser.xml
+++ b/browser/base/content/tabbrowser.xml
@@ -1483,16 +1483,17 @@
             var aForceNotRemote;
             var aPreferredRemoteType;
             var aNoReferrer;
             var aUserContextId;
             var aSameProcessAsFrameLoader;
             var aOriginPrincipal;
             var aOpener;
             var aCreateLazyBrowser;
+            var aNextTabParentId;
             if (arguments.length == 2 &&
                 typeof arguments[1] == "object" &&
                 !(arguments[1] instanceof Ci.nsIURI)) {
               let params = arguments[1];
               aTriggeringPrincipal      = params.triggeringPrincipal
               aReferrerURI              = params.referrerURI;
               aReferrerPolicy           = params.referrerPolicy;
               aCharset                  = params.charset;
@@ -1507,16 +1508,17 @@
               aPreferredRemoteType      = params.preferredRemoteType;
               aNoReferrer               = params.noReferrer;
               aUserContextId            = params.userContextId;
               aSameProcessAsFrameLoader = params.sameProcessAsFrameLoader;
               aOriginPrincipal          = params.originPrincipal;
               aOpener                   = params.opener;
               aIsPrerendered            = params.isPrerendered;
               aCreateLazyBrowser        = params.createLazyBrowser;
+              aNextTabParentId          = params.nextTabParentId;
             }
 
             var bgLoad = (aLoadInBackground != null) ? aLoadInBackground :
                          Services.prefs.getBoolPref("browser.tabs.loadInBackground");
             var owner = bgLoad ? null : this.selectedTab;
 
             var tab = this.addTab(aURI, {
                                   triggeringPrincipal: aTriggeringPrincipal,
@@ -1533,17 +1535,18 @@
                                   forceNotRemote: aForceNotRemote,
                                   createLazyBrowser: aCreateLazyBrowser,
                                   preferredRemoteType: aPreferredRemoteType,
                                   noReferrer: aNoReferrer,
                                   userContextId: aUserContextId,
                                   originPrincipal: aOriginPrincipal,
                                   sameProcessAsFrameLoader: aSameProcessAsFrameLoader,
                                   opener: aOpener,
-                                  isPrerendered: aIsPrerendered });
+                                  isPrerendered: aIsPrerendered,
+                                  nextTabParentId: aNextTabParentId });
             if (!bgLoad)
               this.selectedTab = tab;
 
             return tab;
          ]]>
         </body>
       </method>
 
@@ -1969,16 +1972,21 @@
               b.setAttribute("selectmenulist", this.getAttribute("selectmenulist"));
 
             if (this.hasAttribute("datetimepicker")) {
               b.setAttribute("datetimepicker", this.getAttribute("datetimepicker"));
             }
 
             b.setAttribute("autoscrollpopup", this._autoScrollPopup.id);
 
+            if (aParams.nextTabParentId) {
+              // Gecko is going to read this attribute and use it.
+              b.setAttribute("nextTabParentId", aParams.nextTabParentId.toString());
+            }
+
             if (aParams.sameProcessAsFrameLoader) {
               b.sameProcessAsFrameLoader = aParams.sameProcessAsFrameLoader;
             }
 
             // Create the browserStack container
             var stack = document.createElementNS(NS_XUL, "stack");
             stack.className = "browserStack";
             stack.appendChild(b);
@@ -2213,16 +2221,17 @@
             var aUserContextId;
             var aEventDetail;
             var aSameProcessAsFrameLoader;
             var aOriginPrincipal;
             var aDisallowInheritPrincipal;
             var aOpener;
             var aCreateLazyBrowser;
             var aSkipBackgroundNotify;
+            var aNextTabParentId;
             if (arguments.length == 2 &&
                 typeof arguments[1] == "object" &&
                 !(arguments[1] instanceof Ci.nsIURI)) {
               let params = arguments[1];
               aTriggeringPrincipal      = params.triggeringPrincipal;
               aReferrerURI              = params.referrerURI;
               aReferrerPolicy           = params.referrerPolicy;
               aCharset                  = params.charset;
@@ -2240,16 +2249,17 @@
               aEventDetail              = params.eventDetail;
               aSameProcessAsFrameLoader = params.sameProcessAsFrameLoader;
               aOriginPrincipal          = params.originPrincipal;
               aDisallowInheritPrincipal = params.disallowInheritPrincipal;
               aOpener                   = params.opener;
               aIsPrerendered            = params.isPrerendered;
               aCreateLazyBrowser        = params.createLazyBrowser;
               aSkipBackgroundNotify     = params.skipBackgroundNotify;
+              aNextTabParentId          = params.nextTabParentId;
             }
 
             // if we're adding tabs, we're past interrupt mode, ditch the owner
             if (this.mCurrentTab.owner)
               this.mCurrentTab.owner = null;
 
             var t = document.createElementNS(NS_XUL, "tab");
 
@@ -2348,17 +2358,18 @@
 
             if (!b) {
               // No preloaded browser found, create one.
               b = this._createBrowser({ remoteType,
                                         uriIsAboutBlank,
                                         userContextId: aUserContextId,
                                         sameProcessAsFrameLoader: aSameProcessAsFrameLoader,
                                         opener: aOpener,
-                                        isPrerendered: aIsPrerendered });
+                                        isPrerendered: aIsPrerendered,
+                                        nextTabParentId: aNextTabParentId });
             }
 
             t.linkedBrowser = b;
             this._tabForBrowser.set(b, t);
             t.permanentKey = b.permanentKey;
             t._browserParams = { uriIsAboutBlank,
                                  remoteType,
                                  usingPreloadedContent };
--- a/browser/base/content/test/general/browser_documentnavigation.js
+++ b/browser/base/content/test/general/browser_documentnavigation.js
@@ -140,37 +140,35 @@ add_task(function* () {
                                false, "back focus content page urlbar");
 });
 
 // Open the sidebar and navigate between the sidebar, content and top-level window
 add_task(function* () {
   let sidebar = document.getElementById("sidebar");
 
   let loadPromise = BrowserTestUtils.waitForEvent(sidebar, "load", true);
-  let focusPromise = BrowserTestUtils.waitForEvent(sidebar, "focus", true);
   SidebarUI.toggle("viewBookmarksSidebar");
   yield loadPromise;
-  yield focusPromise;
+
 
   gURLBar.focus();
-
-  yield* expectFocusOnF6(false, "html1", "html1",
-                                true, "focus with sidebar open content");
   yield* expectFocusOnF6(false, "bookmarksPanel",
                                 sidebar.contentDocument.getElementById("search-box").inputField,
                                 false, "focus with sidebar open sidebar");
+  yield* expectFocusOnF6(false, "html1", "html1",
+                                true, "focus with sidebar open content");
   yield* expectFocusOnF6(false, "main-window", gURLBar.inputField,
                                 false, "focus with sidebar urlbar");
 
   // Now go backwards
+  yield* expectFocusOnF6(true, "html1", "html1",
+                               true, "back focus with sidebar open content");
   yield* expectFocusOnF6(true, "bookmarksPanel",
                                sidebar.contentDocument.getElementById("search-box").inputField,
                                false, "back focus with sidebar open sidebar");
-  yield* expectFocusOnF6(true, "html1", "html1",
-                               true, "back focus with sidebar open content");
   yield* expectFocusOnF6(true, "main-window", gURLBar.inputField,
                                false, "back focus with sidebar urlbar");
 
   SidebarUI.toggle("viewBookmarksSidebar");
 });
 
 // Navigate when the downloads panel is open
 add_task(function* () {
--- a/browser/base/content/test/popupNotifications/browser_popupNotification_keyboard.js
+++ b/browser/base/content/test/popupNotifications/browser_popupNotification_keyboard.js
@@ -196,13 +196,13 @@ var tests = [
         }
 
         // Check that the input field is still focused inside the browser.
         yield ContentTask.spawn(browser, {}, function() {
           is(content.document.activeElement, content.document.getElementById("test-input"));
         });
 
         notification.remove();
-        goNext();
       });
+      goNext();
     },
   },
 ];
--- a/browser/components/extensions/test/browser/browser_ext_omnibox.js
+++ b/browser/components/extensions/test/browser/browser_ext_omnibox.js
@@ -113,16 +113,36 @@ add_task(function* () {
 
     // Start an input session by typing in <keyword><space>.
     for (let letter of keyword) {
       EventUtils.synthesizeKey(letter, {});
     }
     EventUtils.synthesizeKey(" ", {});
     yield expectEvent("on-input-started-fired");
 
+    // Test canceling the input before any changed events fire.
+    EventUtils.synthesizeKey("VK_BACK_SPACE", {});
+    yield expectEvent("on-input-cancelled-fired");
+
+    EventUtils.synthesizeKey(" ", {});
+    yield expectEvent("on-input-started-fired");
+
+    // Test submitting the input before any changed events fire.
+    EventUtils.synthesizeKey("VK_RETURN", {});
+    yield expectEvent("on-input-entered-fired");
+
+    gURLBar.focus();
+
+    // Start an input session by typing in <keyword><space>.
+    for (let letter of keyword) {
+      EventUtils.synthesizeKey(letter, {});
+    }
+    EventUtils.synthesizeKey(" ", {});
+    yield expectEvent("on-input-started-fired");
+
     // We should expect input changed events now that the keyword is active.
     EventUtils.synthesizeKey("b", {});
     yield expectEvent("on-input-changed-fired", {text: "b"});
 
     EventUtils.synthesizeKey("c", {});
     yield expectEvent("on-input-changed-fired", {text: "bc"});
 
     EventUtils.synthesizeKey("VK_BACK_SPACE", {});
--- a/browser/components/nsBrowserGlue.js
+++ b/browser/components/nsBrowserGlue.js
@@ -775,34 +775,20 @@ BrowserGlue.prototype = {
     ];
 
     let nb = win.document.getElementById("high-priority-global-notificationbox");
     nb.appendNotification(message, "non-mpc-addons-disabled", "",
                           nb.PRIORITY_WARNING_MEDIUM, buttons);
   },
 
   _firstWindowTelemetry(aWindow) {
-    let SCALING_PROBE_NAME = "";
-    switch (AppConstants.platform) {
-      case "win":
-        SCALING_PROBE_NAME = "DISPLAY_SCALING_MSWIN";
-        break;
-      case "macosx":
-        SCALING_PROBE_NAME = "DISPLAY_SCALING_OSX";
-        break;
-      case "linux":
-        SCALING_PROBE_NAME = "DISPLAY_SCALING_LINUX";
-        break;
-    }
-    if (SCALING_PROBE_NAME) {
-      let scaling = aWindow.devicePixelRatio * 100;
-      try {
-        Services.telemetry.getHistogramById(SCALING_PROBE_NAME).add(scaling);
-      } catch (ex) {}
-    }
+    let scaling = aWindow.devicePixelRatio * 100;
+    try {
+      Services.telemetry.getHistogramById("DISPLAY_SCALING").add(scaling);
+    } catch (ex) {}
   },
 
   // the first browser window has finished initializing
   _onFirstWindowLoaded: function BG__onFirstWindowLoaded(aWindow) {
     // Initialize PdfJs when running in-process and remote. This only
     // happens once since PdfJs registers global hooks. If the PdfJs
     // extension is installed the init method below will be overridden
     // leaving initialization to the extension.
--- a/browser/components/originattributes/test/browser/browser.ini
+++ b/browser/components/originattributes/test/browser/browser.ini
@@ -60,16 +60,17 @@ support-files =
 
 [browser_broadcastChannel.js]
 [browser_cache.js]
 [browser_cookieIsolation.js]
 [browser_favicon_firstParty.js]
 [browser_favicon_userContextId.js]
 [browser_firstPartyIsolation.js]
 [browser_firstPartyIsolation_aboutPages.js]
+[browser_firstPartyIsolation_blobURI.js]
 [browser_firstPartyIsolation_js_uri.js]
 [browser_localStorageIsolation.js]
 [browser_blobURLIsolation.js]
 [browser_imageCacheIsolation.js]
 [browser_sharedworker.js]
 [browser_httpauth.js]
 [browser_clientAuth.js]
 [browser_cacheAPI.js]
--- a/browser/components/originattributes/test/browser/browser_firstPartyIsolation.js
+++ b/browser/components/originattributes/test/browser/browser_firstPartyIsolation.js
@@ -1,15 +1,16 @@
 const BASE_URL = "http://mochi.test:8888/browser/browser/components/originattributes/test/browser/";
 const BASE_DOMAIN = "mochi.test";
 
 add_task(function* setup() {
   Services.prefs.setBoolPref("privacy.firstparty.isolate", true);
   registerCleanupFunction(function() {
     Services.prefs.clearUserPref("privacy.firstparty.isolate");
+    Services.cookies.removeAll();
   });
 });
 
 /**
  * Test for the top-level document and child iframes should have the
  * firstPartyDomain attribute.
  */
 add_task(function* principal_test() {
new file mode 100644
--- /dev/null
+++ b/browser/components/originattributes/test/browser/browser_firstPartyIsolation_blobURI.js
@@ -0,0 +1,76 @@
+add_task(function* setup() {
+  Services.prefs.setBoolPref("privacy.firstparty.isolate", true);
+
+  registerCleanupFunction(function() {
+    Services.prefs.clearUserPref("privacy.firstparty.isolate");
+    Services.cookies.removeAll();
+  });
+});
+
+/**
+ * First we generate a Blob URI by using URL.createObjectURL(new Blob(..));
+ * then we navigate to this Blob URI, hence to make the top-level document URI
+ * is Blob URI.
+ * Later we create an iframe on this Blob: document, and we test that the iframe
+ * has correct firstPartyDomain.
+ */
+add_task(function* test_blob_uri_inherit_oa_from_content() {
+  const BASE_URI = "http://mochi.test:8888/browser/browser/components/" +
+                   "originattributes/test/browser/dummy.html";
+  const BASE_DOMAIN = "mochi.test";
+
+  // First we load a normal web page.
+  let win = yield BrowserTestUtils.openNewBrowserWindow({ remote: true });
+  let browser = win.gBrowser.selectedBrowser;
+  browser.loadURI(BASE_URI);
+  yield BrowserTestUtils.browserLoaded(browser);
+
+  // Then navigate to the blob: URI.
+  yield ContentTask.spawn(browser, { firstPartyDomain: BASE_DOMAIN }, function* (attrs) {
+    info("origin " + content.document.nodePrincipal.origin);
+    Assert.equal(content.document.nodePrincipal.originAttributes.firstPartyDomain,
+                 attrs.firstPartyDomain, "The document should have firstPartyDomain");
+
+    // Now we use createObjectURL to generate a blob URI and navigate to it.
+    let url = content.window.URL.createObjectURL(new content.window.Blob([
+      `<script src="http://mochi.test:8888/browser/browser/components/originattributes/test/browser/test.js"></script>`],
+      {"type": "text/html"}));
+    content.document.location = url;
+  });
+
+  // Wait for the Blob: URI to be loaded.
+  yield BrowserTestUtils.browserLoaded(browser, false, function(url) {
+    info("BrowserTestUtils.browserLoaded url=" + url);
+    return url.startsWith("blob:http://mochi.test:8888/");
+  });
+
+  // We verify the blob document has correct origin attributes.
+  // Then we inject an iframe to it.
+  yield ContentTask.spawn(browser, { firstPartyDomain: BASE_DOMAIN }, function* (attrs) {
+    Assert.ok(content.document.documentURI.startsWith("blob:http://mochi.test:8888/"),
+              "the document URI should be a blob URI.");
+    info("origin " + content.document.nodePrincipal.origin);
+    Assert.equal(content.document.nodePrincipal.originAttributes.firstPartyDomain,
+                 attrs.firstPartyDomain, "The document should have firstPartyDomain");
+
+    let iframe = content.document.createElement("iframe");
+    iframe.src = "http://example.com";
+    iframe.id = "iframe1";
+    content.document.body.appendChild(iframe);
+  });
+
+  // Wait for the iframe to be loaded.
+//  yield BrowserTestUtils.browserLoaded(browser, true, function(url) {
+//    info("BrowserTestUtils.browserLoaded iframe url=" + url);
+//    return url == "http://example.com/";
+//  });
+
+  // Finally we verify the iframe has correct origin attributes.
+  yield ContentTask.spawn(browser, { firstPartyDomain: BASE_DOMAIN }, function* (attrs) {
+    let iframe = content.document.getElementById("iframe1");
+    Assert.equal(iframe.contentDocument.nodePrincipal.originAttributes.firstPartyDomain,
+                 attrs.firstPartyDomain, "iframe should inherit firstPartyDomain from blob: URI");
+  });
+
+  win.close();
+});
--- a/browser/components/preferences/in-content/privacy.xul
+++ b/browser/components/preferences/in-content/privacy.xul
@@ -745,16 +745,19 @@
                  class="indent">&telemetryDesc.label;</description>
   </vbox>
 </groupbox>
 #endif
 
 #ifdef MOZ_DATA_REPORTING
 #ifdef MOZ_CRASHREPORTER
 <groupbox id="crashReporterGroup" data-category="panePrivacy" data-subcategory="reports" hidden="true">
+#ifndef MOZ_TELEMETRY_REPORTING
+<caption><label>&reports.label;</label></caption>
+#endif
   <hbox align="center">
     <checkbox id="automaticallySubmitCrashesBox"
               preference="browser.crashReports.unsubmittedCheck.autoSubmit"
               label="&alwaysSubmitCrashReports.label;"
               accesskey="&alwaysSubmitCrashReports.accesskey;"/>
     <label id="crashReporterLearnMore"
            class="learnMore text-link">&crashReporterLearnMore.label;</label>
   </hbox>
--- a/browser/components/shell/moz.build
+++ b/browser/components/shell/moz.build
@@ -25,17 +25,16 @@ elif 'gtk' in CONFIG['MOZ_WIDGET_TOOLKIT
     XPIDL_SOURCES += [
         'nsIGNOMEShellService.idl',
     ]
 
 XPIDL_MODULE = 'shellservice'
 
 if CONFIG['OS_ARCH'] == 'WINNT':
     SOURCES += [
-        '../../../other-licenses/nsis/Contrib/CityHash/cityhash/city.cpp',
         'nsWindowsShellService.cpp',
     ]
     LOCAL_INCLUDES += [
         '../../../other-licenses/nsis/Contrib/CityHash/cityhash',
     ]
 elif CONFIG['MOZ_WIDGET_TOOLKIT'] == 'cocoa':
     SOURCES += [
         'nsMacShellService.cpp',
--- a/browser/extensions/formautofill/skin/linux/autocomplete-item.css
+++ b/browser/extensions/formautofill/skin/linux/autocomplete-item.css
@@ -1,14 +1,14 @@
 /* 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/. */
 
 @namespace url("http://www.w3.org/1999/xhtml");
 
 
 .profile-item-box > .profile-item-col > .profile-label {
-  font-size: .9em;
+  font-size: .84em;
 }
 
 .profile-item-box > .profile-item-col > .profile-comment {
   font-size: .7em;
 }
--- a/browser/extensions/formautofill/skin/osx/autocomplete-item.css
+++ b/browser/extensions/formautofill/skin/osx/autocomplete-item.css
@@ -1,14 +1,14 @@
 /* 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/. */
 
 @namespace url("http://www.w3.org/1999/xhtml");
 
 
 .profile-item-box > .profile-item-col > .profile-label {
-  font-size: 1.18em;
+  font-size: 1.09em;
 }
 
 .profile-item-box > .profile-item-col > .profile-comment {
   font-size: .9em;
 }
--- a/browser/extensions/formautofill/skin/shared/autocomplete-item.css
+++ b/browser/extensions/formautofill/skin/shared/autocomplete-item.css
@@ -6,61 +6,65 @@
 @namespace xul url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");
 
 
 xul|richlistitem[originaltype="autofill-profile"][selected="true"] > .profile-item-box {
   background-color: #F2F2F2;
 }
 
 .profile-item-box {
+  --item-padding-vertical: 6px;
+  --item-padding-horizontal: 10px;
+  --col-spacer: 7px;
+  --item-width: calc(50% - (var(--col-spacer) / 2));
+}
+
+.profile-item-box[size="small"] {
+  --item-padding-vertical: 7px;
+  --col-spacer: 0px;
+  --row-spacer: 3px;
+  --item-width: 100%;
+}
+
+.profile-item-box {
   box-sizing: border-box;
+  margin: 0;
   border-bottom: 1px solid rgba(38,38,38,.15);
-  padding-top: 6px;
-  padding-bottom: 6px;
-  padding-inline-start: 16px;
-  padding-inline-end: 10px;
+  padding: var(--item-padding-vertical) 0;
+  padding-inline-start: var(--item-padding-horizontal);
+  padding-inline-end: var(--item-padding-horizontal);
   display: flex;
   flex-direction: row;
   flex-wrap: wrap;
   align-items: center;
   color: -moz-FieldText
 }
 
 .profile-item-box:last-child {
   border-bottom: 0;
 }
 
 .profile-item-box > .profile-item-col {
   box-sizing: border-box;
   overflow: hidden;
   text-overflow: ellipsis;
   white-space: nowrap;
-  width: 50%;
+  width: var(--item-width);
 }
 
 .profile-item-box > .profile-label-col {
-  padding-inline-end: 10px;
   text-align: start;
 }
 
 .profile-item-box > .profile-comment-col {
+  margin-inline-start: var(--col-spacer);
   text-align: end;
   color: GrayText;
 }
 
 .profile-item-box[size="small"] {
-  padding-top: 10px;
-  padding-bottom: 10px;
   flex-direction: column;
 }
 
-.profile-item-box[size="small"] > .profile-item-col {
-  width: 100%;
-}
-
-.profile-item-box[size="small"] > .profile-label-col {
-  padding-inline-end: 0;
-}
-
 .profile-item-box[size="small"] > .profile-comment-col {
-  margin-top: 7px;
+  margin-top: var(--row-spacer);
   text-align: start;
 }
--- a/browser/extensions/formautofill/skin/windows/autocomplete-item.css
+++ b/browser/extensions/formautofill/skin/windows/autocomplete-item.css
@@ -1,20 +1,16 @@
 /* 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/. */
 
 @namespace url("http://www.w3.org/1999/xhtml");
 @namespace xul url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");
 
 
-.profile-item-box > .profile-item-col > .profile-label {
-  font-size: 1.08em;
-}
-
 .profile-item-box > .profile-item-col > .profile-comment {
   font-size: .83em;
 }
 
 @media (-moz-windows-default-theme: 0) {
   xul|richlistitem[originaltype="autofill-profile"][selected="true"] > .profile-item-box {
     background-color: Highlight;
   }
--- a/browser/extensions/pocket/bootstrap.js
+++ b/browser/extensions/pocket/bootstrap.js
@@ -11,17 +11,17 @@ Cu.import("resource://gre/modules/XPCOMU
 Cu.import("resource://services-common/utils.js");
 Cu.import("resource://gre/modules/Preferences.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "Services",
                                   "resource://gre/modules/Services.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "RecentWindow",
                                   "resource:///modules/RecentWindow.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "CustomizableUI",
                                   "resource:///modules/CustomizableUI.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "AddonManager",
+XPCOMUtils.defineLazyModuleGetter(this, "AddonManagerPrivate",
                                   "resource://gre/modules/AddonManager.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "ReaderMode",
                                   "resource://gre/modules/ReaderMode.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "Pocket",
                                   "chrome://pocket/content/Pocket.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "AboutPocket",
                                   "chrome://pocket/content/AboutPocket.jsm");
 XPCOMUtils.defineLazyGetter(this, "gPocketBundle", function() {
@@ -472,31 +472,30 @@ function prefObserver(aSubject, aTopic, 
   let enabled = Services.prefs.getBoolPref("extensions.pocket.enabled");
   if (enabled)
     PocketOverlay.startup(ADDON_ENABLE);
   else
     PocketOverlay.shutdown(ADDON_DISABLE);
 }
 
 function startup(data, reason) {
-  AddonManager.getAddonByID("isreaditlater@ideashower.com", addon => {
-    if (addon && addon.isActive)
-      return;
-    setDefaultPrefs();
-    // migrate enabled pref
-    if (Services.prefs.prefHasUserValue("browser.pocket.enabled")) {
-      Services.prefs.setBoolPref("extensions.pocket.enabled", Services.prefs.getBoolPref("browser.pocket.enabled"));
-      Services.prefs.clearUserPref("browser.pocket.enabled");
-    }
-    // watch pref change and enable/disable if necessary
-    Services.prefs.addObserver("extensions.pocket.enabled", prefObserver);
-    if (!Services.prefs.getBoolPref("extensions.pocket.enabled"))
-      return;
-    PocketOverlay.startup(reason);
-  });
+  if (AddonManagerPrivate.addonIsActive("isreaditlater@ideashower.com"))
+    return;
+
+  setDefaultPrefs();
+  // migrate enabled pref
+  if (Services.prefs.prefHasUserValue("browser.pocket.enabled")) {
+    Services.prefs.setBoolPref("extensions.pocket.enabled", Services.prefs.getBoolPref("browser.pocket.enabled"));
+    Services.prefs.clearUserPref("browser.pocket.enabled");
+  }
+  // watch pref change and enable/disable if necessary
+  Services.prefs.addObserver("extensions.pocket.enabled", prefObserver);
+  if (!Services.prefs.getBoolPref("extensions.pocket.enabled"))
+    return;
+  PocketOverlay.startup(reason);
 }
 
 function shutdown(data, reason) {
   // For speed sake, we should only do a shutdown if we're being disabled.
   // On an app shutdown, just let it fade away...
   if (reason != APP_SHUTDOWN) {
     Services.prefs.removeObserver("extensions.pocket.enabled", prefObserver);
     PocketOverlay.shutdown(reason);
--- a/browser/installer/Makefile.in
+++ b/browser/installer/Makefile.in
@@ -63,24 +63,26 @@ DEFINES += -DMOZ_D3DCOMPILER_VISTA_DLL=$
 endif
 endif
 
 DEFINES += -DMOZ_CHILD_PROCESS_NAME=$(MOZ_CHILD_PROCESS_NAME)
 
 # Set MSVC dlls version to package, if any.
 ifdef MOZ_NO_DEBUG_RTL
 ifdef WIN32_REDIST_DIR
+ifndef MOZ_ARTIFACT_BUILDS
 DEFINES += -DMOZ_PACKAGE_MSVC_DLLS=1
 DEFINES += -DMSVC_C_RUNTIME_DLL=$(MSVC_C_RUNTIME_DLL)
 DEFINES += -DMSVC_CXX_RUNTIME_DLL=$(MSVC_CXX_RUNTIME_DLL)
 endif
 ifdef WIN_UCRT_REDIST_DIR
 DEFINES += -DMOZ_PACKAGE_WIN_UCRT_DLLS=1
 endif
 endif
+endif
 
 ifneq (,$(filter WINNT Darwin Android,$(OS_TARGET)))
 DEFINES += -DMOZ_SHARED_MOZGLUE=1
 endif
 
 ifdef NECKO_WIFI
 DEFINES += -DNECKO_WIFI
 endif
--- a/browser/locales/searchplugins/rakuten.xml
+++ b/browser/locales/searchplugins/rakuten.xml
@@ -2,15 +2,15 @@
    - 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/. -->
 
 <SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
 <ShortName>楽天市場</ShortName>
 <Description>楽天市場 商品検索</Description>
 <InputEncoding>EUC-JP</InputEncoding>
 <Image width="16" height="16">data:image/x-icon;base64,R0lGODlhEAAQALMOAOefn9psbMswMMMQEPjj4/PPz++/v+OPj99/f9+AgMcgIM9AQOuvr////wAAAL8AACH5BAEAAA4ALAAAAAAQABAAAARf0EnJ0FqIze2YemCoaBsQniEwMeIloOQHJk1NvKDSnTRQNIZThddYPAiNk4UYCDQIpwux1ghEEUTGc6BkhWiLA1DokD3Ag5/1odvlFlzFAkdymFAn1caDH3FWFhh1EhEAOw==</Image>
-<Url type="text/html" method="GET" template="http://pt.afl.rakuten.co.jp/c/013ca98b.cd7c5f0c/" resultdomain="rakuten.co.jp">
+<Url type="text/html" method="GET" template="https://pt.afl.rakuten.co.jp/c/013ca98b.cd7c5f0c/" resultdomain="rakuten.co.jp">
   <Param name="sitem" value="{searchTerms}"/>
   <Param name="sv" value="2"/>
   <Param name="p" value="0"/>
 </Url>
 <SearchForm>http://www.rakuten.co.jp/</SearchForm>
 </SearchPlugin>
--- a/browser/modules/ExtensionsUI.jsm
+++ b/browser/modules/ExtensionsUI.jsm
@@ -393,26 +393,30 @@ this.ExtensionsUI = {
     };
 
     let win = browser.ownerGlobal;
     return new Promise(resolve => {
       let action = {
         label: strings.acceptText,
         accessKey: strings.acceptKey,
         callback: () => {
-          this.histogram.add(histkey + "Accepted");
+          if (histkey) {
+            this.histogram.add(histkey + "Accepted");
+          }
           resolve(true);
         },
       };
       let secondaryActions = [
         {
           label: strings.cancelText,
           accessKey: strings.cancelKey,
           callback: () => {
-            this.histogram.add(histkey + "Rejected");
+            if (histkey) {
+              this.histogram.add(histkey + "Rejected");
+            }
             resolve(false);
           },
         },
       ];
 
       // Get the text value of strings.header to pre-populate the header. This will get
       // overwritten with the HTML version later.
       let escapeHeader = browser.ownerDocument.createElement("div");
--- a/caps/OriginAttributes.cpp
+++ b/caps/OriginAttributes.cpp
@@ -5,16 +5,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "mozilla/OriginAttributes.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/dom/URLSearchParams.h"
 #include "mozilla/dom/quota/QuotaManager.h"
 #include "nsIEffectiveTLDService.h"
 #include "nsIURI.h"
+#include "nsIURIWithPrincipal.h"
 
 namespace mozilla {
 
 using dom::URLParams;
 
 bool OriginAttributes::sFirstPartyIsolation = false;
 bool OriginAttributes::sRestrictedOpenerAccess = false;
 
@@ -47,26 +48,39 @@ OriginAttributes::SetFirstPartyDomain(co
     do_GetService(NS_EFFECTIVETLDSERVICE_CONTRACTID);
   MOZ_ASSERT(tldService);
   if (!tldService) {
     return;
   }
 
   nsAutoCString baseDomain;
   nsresult rv = tldService->GetBaseDomain(aURI, 0, baseDomain);
-  if (NS_FAILED(rv)) {
-    nsAutoCString scheme;
-    rv = aURI->GetScheme(scheme);
-    NS_ENSURE_SUCCESS_VOID(rv);
-    if (scheme.EqualsLiteral("about")) {
-      baseDomain.AssignLiteral(ABOUT_URI_FIRST_PARTY_DOMAIN);
+  if (NS_SUCCEEDED(rv)) {
+    mFirstPartyDomain = NS_ConvertUTF8toUTF16(baseDomain);
+    return;
+  }
+
+  nsAutoCString scheme;
+  rv = aURI->GetScheme(scheme);
+  NS_ENSURE_SUCCESS_VOID(rv);
+  if (scheme.EqualsLiteral("about")) {
+    mFirstPartyDomain.AssignLiteral(ABOUT_URI_FIRST_PARTY_DOMAIN);
+  } else if (scheme.EqualsLiteral("blob")) {
+    nsCOMPtr<nsIURIWithPrincipal> uriPrinc = do_QueryInterface(aURI);
+    if (uriPrinc) {
+      nsCOMPtr<nsIPrincipal> principal;
+      rv = uriPrinc->GetPrincipal(getter_AddRefs(principal));
+      NS_ENSURE_SUCCESS_VOID(rv);
+
+      MOZ_ASSERT(principal, "blob URI but no principal.");
+      if (principal) {
+        mFirstPartyDomain = principal->OriginAttributesRef().mFirstPartyDomain;
+      }
     }
   }
-
-  mFirstPartyDomain = NS_ConvertUTF8toUTF16(baseDomain);
 }
 
 void
 OriginAttributes::SetFirstPartyDomain(const bool aIsTopLevelDocument,
                                       const nsACString& aDomain)
 {
   bool isFirstPartyEnabled = IsFirstPartyEnabled();
 
--- a/config/rules.mk
+++ b/config/rules.mk
@@ -41,17 +41,17 @@ endif
 
 endif
 
 EXEC			= exec
 
 # ELOG prints out failed command when building silently (gmake -s). Pymake
 # prints out failed commands anyway, so ELOG just makes things worse by
 # forcing shell invocations.
-ifneq (,$(findstring s, $(filter-out --%, $(MAKEFLAGS))))
+ifneq (,$(findstring -s, $(filter-out --%, $(MAKEFLAGS))))
   ELOG := $(EXEC) sh $(BUILD_TOOLS)/print-failed-commands.sh
 else
   ELOG :=
 endif # -s
 
 _VPATH_SRCS = $(abspath $<)
 
 ################################################################################
@@ -948,19 +948,22 @@ rustflags_override = RUSTFLAGS='$(rustfl
 endif
 
 ifdef MOZ_MSVCBITS
 # If we are building a MozillaBuild shell, we want to clear out the
 # vcvars.bat environment variables for cargo builds. This is because
 # a 32-bit MozillaBuild shell on a 64-bit machine will try to use
 # the 32-bit compiler/linker for everything, while cargo/rustc wants
 # to use the 64-bit linker for build.rs scripts. This conflict results
-# in a build failure (see bug 1350001). Clearing out *just* the changes
-# from vcvars.bat is hard, so we just clear out the whole environment.
-environment_cleaner = -i
+# in a build failure (see bug 1350001). So we clear out the environment
+# variables that are actually relevant to 32- vs 64-bit builds.
+environment_cleaner = PATH='' LIB='' LIBPATH=''
+# The servo build needs to know where python is, and we're removing the PATH
+# so we tell it explicitly via the PYTHON env var.
+environment_cleaner += PYTHON='$(shell which $(PYTHON))'
 else
 environment_cleaner =
 endif
 
 CARGO_BUILD = env $(environment_cleaner) $(rustflags_override) \
 	CARGO_TARGET_DIR=$(CARGO_TARGET_DIR) \
 	RUSTC=$(RUSTC) \
 	MOZ_DIST=$(ABS_DIST) \
--- a/devtools/client/aboutdebugging/aboutdebugging.css
+++ b/devtools/client/aboutdebugging/aboutdebugging.css
@@ -244,16 +244,35 @@ button {
 }
 
 .addon-target-container .name {
   align-self: center;
   font-size: 16px;
   font-weight: 600;
 }
 
+.addon-target-info {
+  display: grid;
+  font-size: 14px;
+  grid-template-columns: 128px 1fr;
+}
+
+.addon-target-info-label {
+  padding-inline-end: 8px;
+  padding-bottom: 8px;
+}
+
+.addon-target-info-label:last-of-type {
+  padding-bottom: 16px;
+}
+
+.addon-target-info-content {
+  margin: 0;
+}
+
 .addon-target-button {
   background: none;
   border: none;
   color: #0087ff;
   font-size: 14px;
   margin: 12px;
   min-width: auto;
   padding: 4px;
@@ -284,8 +303,23 @@ button {
   text-decoration: none;
 }
 
 .addon-target-button:first-of-type {
   /* Subtract the start padding so the button is still a bigger click target but
    * lines up with the icon. */
   margin-inline-start: -4px;
 }
+
+/* We want the ellipsis on the left-hand-side, so make the parent RTL
+ * with an ellipsis and the child can be LTR. */
+.file-path {
+  direction: rtl;
+  max-width: 100%;
+  overflow: hidden;
+  text-overflow: ellipsis;
+  white-space: nowrap;
+}
+
+.file-path-inner {
+  direction: ltr;
+  unicode-bidi: plaintext;
+}
--- a/devtools/client/aboutdebugging/components/addons/panel.js
+++ b/devtools/client/aboutdebugging/components/addons/panel.js
@@ -71,17 +71,18 @@ module.exports = createClass({
     this.props.client.listAddons()
       .then(({addons}) => {
         let extensions = addons.filter(addon => addon.debuggable).map(addon => {
           return {
             name: addon.name,
             icon: addon.iconURL || ExtensionIcon,
             addonID: addon.id,
             addonActor: addon.actor,
-            temporarilyInstalled: addon.temporarilyInstalled
+            temporarilyInstalled: addon.temporarilyInstalled,
+            url: addon.url,
           };
         });
 
         this.setState({ extensions });
       }, error => {
         throw new Error("Client error while listing addons: " + error);
       });
   },
--- a/devtools/client/aboutdebugging/components/addons/target.js
+++ b/devtools/client/aboutdebugging/components/addons/target.js
@@ -15,28 +15,48 @@ loader.lazyImporter(this, "BrowserToolbo
   "resource://devtools/client/framework/ToolboxProcess.jsm");
 
 loader.lazyRequireGetter(this, "DebuggerClient",
   "devtools/shared/client/main", true);
 
 const Strings = Services.strings.createBundle(
   "chrome://devtools/locale/aboutdebugging.properties");
 
+function filePathForTarget(target) {
+  // Only show file system paths, and only for temporarily installed add-ons.
+  if (!target.temporarilyInstalled || !target.url || !target.url.startsWith("file://")) {
+    return [];
+  }
+  let path = target.url.slice("file://".length);
+  return [
+    dom.dt(
+      { className: "addon-target-info-label" },
+      Strings.GetStringFromName("location")),
+    // Wrap the file path in a span so we can do some RTL/LTR swapping to get
+    // the ellipsis on the left.
+    dom.dd(
+      { className: "addon-target-info-content file-path" },
+      dom.span({ className: "file-path-inner", title: path }, path),
+    ),
+  ];
+}
+
 module.exports = createClass({
   displayName: "AddonTarget",
 
   propTypes: {
     client: PropTypes.instanceOf(DebuggerClient).isRequired,
     debugDisabled: PropTypes.bool,
     target: PropTypes.shape({
       addonActor: PropTypes.string.isRequired,
       addonID: PropTypes.string.isRequired,
       icon: PropTypes.string,
       name: PropTypes.string.isRequired,
-      temporarilyInstalled: PropTypes.bool
+      temporarilyInstalled: PropTypes.bool,
+      url: PropTypes.string,
     }).isRequired
   },
 
   debug() {
     let { target } = this.props;
     debugAddon(target.addonID);
   },
 
@@ -63,16 +83,20 @@ module.exports = createClass({
       dom.div({ className: "target" },
         dom.img({
           className: "target-icon",
           role: "presentation",
           src: target.icon
         }),
         dom.span({ className: "target-name", title: target.name }, target.name)
       ),
+      dom.dl(
+        { className: "addon-target-info" },
+        ...filePathForTarget(target),
+      ),
       dom.div({className: "addon-target-actions"},
         dom.button({
           className: "debug-button addon-target-button",
           onClick: this.debug,
           disabled: debugDisabled,
         }, Strings.GetStringFromName("debug")),
         dom.button({
           className: "reload-button addon-target-button",
--- a/devtools/client/aboutdebugging/test/browser.ini
+++ b/devtools/client/aboutdebugging/test/browser.ini
@@ -15,16 +15,17 @@ support-files =
   service-workers/empty-sw.js
   service-workers/fetch-sw.html
   service-workers/fetch-sw.js
   service-workers/push-sw.html
   service-workers/push-sw.js
   !/devtools/client/framework/test/shared-head.js
 
 [browser_addons_debug_bootstrapped.js]
+[browser_addons_debug_info.js]
 [browser_addons_debug_webextension.js]
 tags = webextensions
 [browser_addons_debug_webextension_inspector.js]
 tags = webextensions
 [browser_addons_debug_webextension_nobg.js]
 tags = webextensions
 [browser_addons_debug_webextension_popup.js]
 tags = webextensions
new file mode 100644
--- /dev/null
+++ b/devtools/client/aboutdebugging/test/browser_addons_debug_info.js
@@ -0,0 +1,28 @@
+"use strict";
+
+const ADDON_ID = "test-devtools@mozilla.org";
+const ADDON_NAME = "test-devtools";
+
+add_task(function* () {
+  let { tab, document } = yield openAboutDebugging("addons");
+  yield waitForInitialAddonList(document);
+
+  yield installAddon({
+    document,
+    path: "addons/unpacked/install.rdf",
+    name: ADDON_NAME,
+  });
+
+  let container = document.querySelector(`[data-addon-id="${ADDON_ID}"]`);
+  let filePath = container.querySelector(".file-path");
+  let expectedFilePath = "browser/devtools/client/aboutdebugging/test/addons/unpacked/";
+
+  // Verify that the path to the install location is shown next to its label.
+  ok(filePath, "file path is in DOM");
+  ok(filePath.textContent.endsWith(expectedFilePath), "file path is set correctly");
+  is(filePath.previousElementSibling.textContent, "Location", "file path has label");
+
+  yield uninstallAddon({document, id: ADDON_ID, name: ADDON_NAME});
+
+  yield closeAboutDebugging(tab);
+});
--- a/devtools/client/commandline/test/browser_cmd_qsa.js
+++ b/devtools/client/commandline/test/browser_cmd_qsa.js
@@ -1,33 +1,33 @@
-/* Any copyright is dedicated to the Public Domain.
-* http://creativecommons.org/publicdomain/zero/1.0/ */
-
-"use strict";
-
-// Tests that the qsa commands work as they should.
-
-const TEST_URI = "data:text/html;charset=utf-8,<body></body>";
-
-function test() {
-  helpers.addTabWithToolbar(TEST_URI, function (options) {
-    return helpers.audit(options, [
-      {
-        setup: "qsa",
-        check: {
-          input:  "qsa",
-          hints:  " [query]",
-          markup: "VVV",
-          status: "VALID"
-        }
-      },
-      {
-        setup: "qsa body",
-        check: {
-          input:  "qsa body",
-          hints:  "",
-          markup: "VVVVVVVV",
-          status: "VALID"
-        }
-      }
-    ]);
-  }).then(finish, helpers.handleError);
-}
+/* Any copyright is dedicated to the Public Domain.
+* http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Tests that the qsa commands work as they should.
+
+const TEST_URI = "data:text/html;charset=utf-8,<body></body>";
+
+function test() {
+  helpers.addTabWithToolbar(TEST_URI, function (options) {
+    return helpers.audit(options, [
+      {
+        setup: "qsa",
+        check: {
+          input:  "qsa",
+          hints:  " [query]",
+          markup: "VVV",
+          status: "VALID"
+        }
+      },
+      {
+        setup: "qsa body",
+        check: {
+          input:  "qsa body",
+          hints:  "",
+          markup: "VVVVVVVV",
+          status: "VALID"
+        }
+      }
+    ]);
+  }).then(finish, helpers.handleError);
+}
--- a/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-edit-cancel.js
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-edit-cancel.js
@@ -1,58 +1,58 @@
 /* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* vim: set ft=javascript ts=2 et sw=2 tw=80: */
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
-
-/**
- * Make sure that canceling a name change correctly unhides the separator and
- * value elements.
- */
-
-const TAB_URL = EXAMPLE_URL + "doc_watch-expressions.html";
-
-function test() {
-  Task.spawn(function* () {
+
+/**
+ * Make sure that canceling a name change correctly unhides the separator and
+ * value elements.
+ */
+
+const TAB_URL = EXAMPLE_URL + "doc_watch-expressions.html";
+
+function test() {
+  Task.spawn(function* () {
     let options = {
       source: TAB_URL,
       line: 1
     };
-    let [tab,, panel] = yield initDebugger(TAB_URL, options);
-    let win = panel.panelWin;
-    let vars = win.DebuggerView.Variables;
-
-    win.DebuggerView.WatchExpressions.addExpression("this");
-
-    callInTab(tab, "ermahgerd");
-    yield waitForDebuggerEvents(panel, win.EVENTS.FETCHED_WATCH_EXPRESSIONS);
-
-    let exprScope = vars.getScopeAtIndex(0);
-    let {target} = exprScope.get("this");
-
-    let name = target.querySelector(".title > .name");
-    let separator = target.querySelector(".separator");
-    let value = target.querySelector(".value");
-
-    is(separator.hidden, false,
-      "The separator element should not be hidden.");
-    is(value.hidden, false,
-      "The value element should not be hidden.");
-
-    for (let key of ["ESCAPE", "RETURN"]) {
-      EventUtils.sendMouseEvent({ type: "dblclick" }, name, win);
-
-      is(separator.hidden, true,
-        "The separator element should be hidden.");
-      is(value.hidden, true,
-        "The value element should be hidden.");
-
-      EventUtils.sendKey(key, win);
-
-      is(separator.hidden, false,
-        "The separator element should not be hidden.");
-      is(value.hidden, false,
-        "The value element should not be hidden.");
-    }
-
-    yield resumeDebuggerThenCloseAndFinish(panel);
-  });
-}
+    let [tab,, panel] = yield initDebugger(TAB_URL, options);
+    let win = panel.panelWin;
+    let vars = win.DebuggerView.Variables;
+
+    win.DebuggerView.WatchExpressions.addExpression("this");
+
+    callInTab(tab, "ermahgerd");
+    yield waitForDebuggerEvents(panel, win.EVENTS.FETCHED_WATCH_EXPRESSIONS);
+
+    let exprScope = vars.getScopeAtIndex(0);
+    let {target} = exprScope.get("this");
+
+    let name = target.querySelector(".title > .name");
+    let separator = target.querySelector(".separator");
+    let value = target.querySelector(".value");
+
+    is(separator.hidden, false,
+      "The separator element should not be hidden.");
+    is(value.hidden, false,
+      "The value element should not be hidden.");
+
+    for (let key of ["ESCAPE", "RETURN"]) {
+      EventUtils.sendMouseEvent({ type: "dblclick" }, name, win);
+
+      is(separator.hidden, true,
+        "The separator element should be hidden.");
+      is(value.hidden, true,
+        "The value element should be hidden.");
+
+      EventUtils.sendKey(key, win);
+
+      is(separator.hidden, false,
+        "The separator element should not be hidden.");
+      is(value.hidden, false,
+        "The value element should not be hidden.");
+    }
+
+    yield resumeDebuggerThenCloseAndFinish(panel);
+  });
+}
--- a/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-popup-17.js
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-popup-17.js
@@ -1,80 +1,80 @@
-/* Any copyright is dedicated to the Public Domain.
-   http://creativecommons.org/publicdomain/zero/1.0/ */
-
-/**
- * Tests opening the variable inspection popup while stopped at a debugger statement,
- * clicking "step in" and verifying that the popup is gone.
- */
-
-const TAB_URL = EXAMPLE_URL + "doc_with-frame.html";
-
-let gTab, gPanel, gDebugger;
-let actions, gSources, gVariables;
-
-function test() {
-  let options = {
-    source: TAB_URL,
-    line: 1
-  };
-  initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => {
-    gTab = aTab;
-    gPanel = aPanel;
-    gDebugger = gPanel.panelWin;
-    actions = bindActionCreators(gPanel);
-    gSources = gDebugger.DebuggerView.Sources;
-    gVariables = gDebugger.DebuggerView.Variables;
-    let bubble = gDebugger.DebuggerView.VariableBubble;
-    let tooltip = bubble._tooltip.panel;
-    let testPopupHiding = Task.async(function* () {
-      yield addBreakpoint();
-      yield ensureThreadClientState(gPanel, "resumed");
-      yield pauseDebuggee();
-      yield openVarPopup(gPanel, { line: 20, ch: 17 });
-      is(tooltip.querySelectorAll(".devtools-tooltip-simple-text").length, 1,
-          "The popup should be open with a simple text entry");
-      // Now we're stopped at a breakpoint with an open popup
-      // we'll send a keypress and check if the popup closes
-      executeSoon(() => EventUtils.synthesizeKey("VK_F11", {}));
-      // The keypress should cause one resumed event and one paused event
-      yield waitForThreadEvents(gPanel, "resumed");
-      yield waitForThreadEvents(gPanel, "paused");
-      // Here's the state we're actually interested in checking..
-      checkVariablePopupClosed(bubble);
-      yield resumeDebuggerThenCloseAndFinish(gPanel);
-    });
-    testPopupHiding();
-  });
-}
-
-function addBreakpoint() {
-  return actions.addBreakpoint({ actor: gSources.selectedValue, line: 21 });
-}
-
-function pauseDebuggee() {
-  generateMouseClickInTab(gTab, "content.document.querySelector('button')");
-
-  // The first 'with' scope should be expanded by default, but the
-  // variables haven't been fetched yet. This is how 'with' scopes work.
-  return promise.all([
-    waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FETCHED_SCOPES),
-    waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FETCHED_VARIABLES)
-  ]);
-}
-
-function checkVariablePopupClosed(bubble) {
-  ok(!bubble.contentsShown(),
-    "When stepping, popup should close and be hidden.");
-  ok(bubble._tooltip.isEmpty(),
-    "The variable inspection popup should now be empty.");
-  ok(!bubble._markedText,
-    "The marked text in the editor was removed.");
-}
-
-registerCleanupFunction(function () {
-  gTab = null;
-  gPanel = null;
-  gDebugger = null;
-  actions = null;
-  gSources = null;
-  gVariables = null;
-});
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests opening the variable inspection popup while stopped at a debugger statement,
+ * clicking "step in" and verifying that the popup is gone.
+ */
+
+const TAB_URL = EXAMPLE_URL + "doc_with-frame.html";
+
+let gTab, gPanel, gDebugger;
+let actions, gSources, gVariables;
+
+function test() {
+  let options = {
+    source: TAB_URL,
+    line: 1
+  };
+  initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => {
+    gTab = aTab;
+    gPanel = aPanel;
+    gDebugger = gPanel.panelWin;
+    actions = bindActionCreators(gPanel);
+    gSources = gDebugger.DebuggerView.Sources;
+    gVariables = gDebugger.DebuggerView.Variables;
+    let bubble = gDebugger.DebuggerView.VariableBubble;
+    let tooltip = bubble._tooltip.panel;
+    let testPopupHiding = Task.async(function* () {
+      yield addBreakpoint();
+      yield ensureThreadClientState(gPanel, "resumed");
+      yield pauseDebuggee();
+      yield openVarPopup(gPanel, { line: 20, ch: 17 });
+      is(tooltip.querySelectorAll(".devtools-tooltip-simple-text").length, 1,
+          "The popup should be open with a simple text entry");
+      // Now we're stopped at a breakpoint with an open popup
+      // we'll send a keypress and check if the popup closes
+      executeSoon(() => EventUtils.synthesizeKey("VK_F11", {}));
+      // The keypress should cause one resumed event and one paused event
+      yield waitForThreadEvents(gPanel, "resumed");
+      yield waitForThreadEvents(gPanel, "paused");
+      // Here's the state we're actually interested in checking..
+      checkVariablePopupClosed(bubble);
+      yield resumeDebuggerThenCloseAndFinish(gPanel);
+    });
+    testPopupHiding();
+  });
+}
+
+function addBreakpoint() {
+  return actions.addBreakpoint({ actor: gSources.selectedValue, line: 21 });
+}
+
+function pauseDebuggee() {
+  generateMouseClickInTab(gTab, "content.document.querySelector('button')");
+
+  // The first 'with' scope should be expanded by default, but the
+  // variables haven't been fetched yet. This is how 'with' scopes work.
+  return promise.all([
+    waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FETCHED_SCOPES),
+    waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FETCHED_VARIABLES)
+  ]);
+}
+
+function checkVariablePopupClosed(bubble) {
+  ok(!bubble.contentsShown(),
+    "When stepping, popup should close and be hidden.");
+  ok(bubble._tooltip.isEmpty(),
+    "The variable inspection popup should now be empty.");
+  ok(!bubble._markedText,
+    "The marked text in the editor was removed.");
+}
+
+registerCleanupFunction(function () {
+  gTab = null;
+  gPanel = null;
+  gDebugger = null;
+  actions = null;
+  gSources = null;
+  gVariables = null;
+});
--- a/devtools/client/locales/en-US/aboutdebugging.properties
+++ b/devtools/client/locales/en-US/aboutdebugging.properties
@@ -74,16 +74,20 @@ selectAddonFromFile2 = Select Manifest F
 # This string is displayed as a label of the button that reloads a given addon.
 reload = Reload
 
 # LOCALIZATION NOTE (reloadDisabledTooltip):
 # This string is displayed in a tooltip that appears when hovering over a
 # disabled 'reload' button.
 reloadDisabledTooltip = Only temporarily installed add-ons can be reloaded
 
+# LOCALIZATION NOTE (location):
+# This string is displayed as a label for the filesystem location of an extension.
+location = Location
+
 # LOCALIZATION NOTE (workers):
 # This string is displayed as a header of the about:debugging#workers page.
 workers = Workers
 
 serviceWorkers = Service Workers
 sharedWorkers = Shared Workers
 otherWorkers = Other Workers
 
--- a/devtools/client/netmonitor/src/assets/styles/netmonitor.css
+++ b/devtools/client/netmonitor/src/assets/styles/netmonitor.css
@@ -45,87 +45,81 @@
 
 :root.theme-firebug {
   --sort-ascending-image: url(chrome://devtools/skin/images/firebug/arrow-up.svg);
   --sort-descending-image: url(chrome://devtools/skin/images/firebug/arrow-down.svg);
 }
 
 /* General */
 
+* {
+  box-sizing: border-box;
+}
+
 html,
 body,
 #mount,
 .launchpad-root,
 .network-monitor,
 .monitor-panel {
   flex: initial;
   display: flex;
   flex-direction: column;
   height: 100%;
   overflow: hidden;
 }
 
-* {
-  box-sizing: border-box;
-}
-
 .split-box {
   overflow: hidden;
 }
 
-.toolbar-labels {
-  overflow: hidden;
-  display: flex;
-  flex: auto;
-}
+/* Toolbar */
 
 .devtools-toolbar {
   display: flex;
 }
 
-.devtools-toolbar-container.devtools-toolbar {
-  height: auto !important;
+.devtools-toolbar-container {
+  height: auto;
   flex-wrap: wrap;
   justify-content: space-between;
 }
 
 .devtools-toolbar-group {
   display: flex;
   flex: 0 0 auto;
   flex-wrap: nowrap;
   align-items: center;
 }
 
-#response-content-image-box {
-  overflow: auto;
-}
-
-.cropped-textbox .textbox-input {
-  /* workaround for textbox not supporting the @crop attribute */
-  text-overflow: ellipsis;
+.requests-list-filter-buttons {
+  display: flex;
+  flex-wrap: nowrap;
 }
 
 .learn-more-link {
   color: var(--theme-highlight-blue);
   cursor: pointer;
   margin: 0 5px;
   white-space: nowrap;
 }
 
 .learn-more-link:hover {
   text-decoration: underline;
 }
 
 /* Status bar */
 
+.devtools-status-bar-label {
+  flex: 0;
+}
+
 .status-bar-label {
   display: inline-flex;
-  align-content: stretch;
   margin-inline-end: 10px;
-
   /* Status bar has just one line so, don't wrap labels */
   white-space: nowrap;
 }
 
 .status-bar-label::before {
   content: "";
   display: inline-block;
   margin-inline-end: 10px;
@@ -138,25 +132,17 @@ body,
 .status-bar-label.dom-content-loaded {
   color: blue;
 }
 
 .status-bar-label.load {
   color: red;
 }
 
-/* Request list */
-
-.request-list-container {
-  display: flex;
-  flex-direction: column;
-  width: 100%;
-  height: 100%;
-  overflow: hidden;
-}
+/* Request list empty panel */
 
 .request-list-empty-notice {
   margin: 0;
   padding: 12px;
   font-size: 120%;
   flex: 1;
   overflow: auto;
 }
@@ -179,141 +165,146 @@ body,
 }
 
 .requests-list-reload-notice-button {
   font-size: inherit;
   min-height: 26px;
   margin: 0 5px;
 }
 
-/* Network requests table */
+/* Requests list table */
 
-.requests-list-toolbar {
+.request-list-container {
   display: flex;
-  padding: 0;
+  flex-direction: column;
+  width: 100%;
+  height: 100%;
+  overflow-x: hidden;
 }
 
-.requests-list-filter-buttons {
-  display: flex;
-  flex-wrap: nowrap;
+.requests-list-wrapper {
+  width: 100%;
+  height: 100%;
 }
 
-.theme-firebug .requests-list-toolbar {
-  height: 19px !important;
+.requests-list-table {
+  display: table;
+  position: relative;
+  width: 100%;
+  height: 100%;
 }
 
 .requests-list-contents {
-  height: 100%;
+  display: table-row-group;
+  position: absolute;
+  top: 0;
+  bottom: 0;
+  left: 0;
+  right: 0;
   overflow-x: hidden;
   overflow-y: auto;
   --timings-scale: 1;
   --timings-rev-scale: 1;
 }
 
-.requests-list-subitem {
-  display: flex;
-  flex: none;
-  box-sizing: border-box;
-  align-items: center;
-  padding: 3px;
+.requests-list-column {
+  display: table-cell;
   cursor: default;
-}
-
-.subitem-label {
+  text-align: center;
   white-space: nowrap;
   overflow: hidden;
   text-overflow: ellipsis;
+  vertical-align: middle;
+  max-width: 50px;
+  min-width: 50px;
+}
+
+.requests-list-column > * {
+  display: inline-block;
 }
 
-/* Requests list header */
+.theme-firebug .requests-list-column {
+  padding: 1px;
+}
+
+/* Requests list headers */
 
-.requests-list-header {
-  display: flex;
-  flex: none;
+.requests-list-headers {
+  display: table-header-group;
+  height: 24px;
+  padding: 0;
+}
+
+.requests-list-headers .requests-list-column:first-child .requests-list-header-button {
+  border-width: 0;
 }
 
 .requests-list-header-button {
-  display: flex;
-  align-items: center;
-  flex: auto;
-  -moz-appearance: none; appearance: none;
   background-color: transparent;
   border-image: linear-gradient(transparent 15%,
                                 var(--theme-splitter-color) 15%,
                                 var(--theme-splitter-color) 85%,
                                 transparent 85%) 1 1;
-  border-style: solid;
   border-width: 0;
   border-inline-start-width: 1px;
-  min-width: 1px;
-  min-height: 24px;
-  margin: 0;
-  padding-top: 2px;
-  padding-bottom: 2px;
   padding-inline-start: 16px;
-  padding-inline-end: 0;
+  width: 100%;
+  min-height: 23px;
   text-align: center;
   color: inherit;
-  font-weight: inherit !important;
 }
 
 .requests-list-header-button::-moz-focus-inner {
   border: 0;
   padding: 0;
 }
 
-.requests-list-header:first-child .requests-list-header-button {
-  border-width: 0;
-}
-
 .requests-list-header-button:hover {
   background-color: rgba(0, 0, 0, 0.1);
 }
 
 .requests-list-header-button > .button-text {
-  flex: auto;
-  white-space: nowrap;
+  display: inline-block;
+  text-align: center;
+  vertical-align: middle;
+  /* Align button text to center */
+  width: calc(100% - 8px);
   overflow: hidden;
   text-overflow: ellipsis;
 }
 
 .requests-list-header-button > .button-icon {
-  flex: none;
+  display: inline-block;
+  width: 7px;
   height: 4px;
   margin-inline-start: 3px;
   margin-inline-end: 6px;
-  width: 7px;
+  vertical-align: middle;
 }
 
 .requests-list-header-button[data-sorted=ascending] > .button-icon {
   background-image: var(--sort-ascending-image);
 }
 
 .requests-list-header-button[data-sorted=descending] > .button-icon {
   background-image: var(--sort-descending-image);
 }
 
-.requests-list-waterfall-label-wrapper {
-  display: flex;
-}
-
 .requests-list-header-button[data-sorted],
 .requests-list-header-button[data-sorted]:hover {
   background-color: var(--theme-selection-background);
   color: var(--theme-selection-color);
 }
 
 .requests-list-header-button[data-sorted],
-.requests-list-header[data-active] + .requests-list-header .requests-list-header-button {
+.requests-list-column[data-active] + .requests-list-column .requests-list-header-button {
   border-image: linear-gradient(var(--theme-splitter-color), var(--theme-splitter-color)) 1 1;
 }
 
-/* Firebug theme support for Network panel header */
-
-.theme-firebug .requests-list-header {
+.theme-firebug .requests-list-headers {
   padding: 0 !important;
   font-weight: bold;
   background: linear-gradient(rgba(255, 255, 255, 0.05),
                               rgba(0, 0, 0, 0.05)),
                               #C8D2DC;
 }
 
 .theme-firebug .requests-list-header-button {
@@ -328,147 +319,46 @@ body,
   background-color: #AAC3DC;
 }
 
 :root[platform="linux"].theme-firebug .requests-list-header-button[data-sorted] {
   background-color: #FAC8AF !important;
   color: inherit !important;
 }
 
-.theme-firebug .requests-list-header:hover:active {
+.theme-firebug .requests-list-header-button:hover:active {
   background-image: linear-gradient(rgba(0, 0, 0, 0.1),
                                     transparent);
 }
 
+/* Requests list column */
 
-/* Network requests table: specific column dimensions */
+/* Status column */
 
 .requests-list-status {
-  max-width: 6em;
-  text-align: center;
-  width: 8vw;
-}
-
-.requests-list-method,
-.requests-list-method-box {
-  max-width: 7em;
-  text-align: center;
-  width: 10vw;
-}
-
-.requests-list-icon-and-file {
-  width: 22vw;
-}
-
-.requests-list-icon {
-  background: transparent;
-  width: 15px;
-  height: 15px;
-  margin-inline-end: 4px;
-}
-
-.requests-list-icon {
-  outline: 1px solid var(--table-splitter-color);
-}
-
-.requests-list-security-and-domain {
-  width: 14vw;
-}
-
-.requests-list-remoteip {
-  width: 8vw;
-}
-
-.requests-list-protocol {
-  width: 7vw;
-}
-
-.requests-security-state-icon {
-  flex: none;
-  width: 16px;
-  height: 16px;
-  margin-inline-end: 4px;
-}
-
-.request-list-item.selected .requests-security-state-icon {
-  filter: brightness(1.3);
-}
-
-.security-state-insecure {
-  background-image: url(chrome://devtools/skin/images/security-state-insecure.svg);
+  width: 8%;
 }
 
-.security-state-secure {
-  background-image: url(chrome://devtools/skin/images/security-state-secure.svg);
-}
-
-.security-state-weak {
-  background-image: url(chrome://devtools/skin/images/security-state-weak.svg);
-}
-
-.security-state-broken {
-  background-image: url(chrome://devtools/skin/images/security-state-broken.svg);
-}
-
-.security-state-local {
-  background-image: url(chrome://devtools/skin/images/globe.svg);
-}
-
-.requests-list-type,
-.requests-list-size {
-  max-width: 6em;
-  width: 8vw;
-  justify-content: center;
-}
-
-.requests-list-transferred {
-  max-width: 8em;
-  width: 8vw;
-  justify-content: center;
+.theme-firebug .requests-list-status {
+  font-weight: bold;
 }
 
-.requests-list-cause {
-  max-width: 8em;
-  width: 8vw;
-}
-
-.requests-list-cause-stack {
-  background-color: var(--theme-body-color-alt);
-  color: var(--theme-body-background);
-  font-size: 8px;
-  font-weight: bold;
-  line-height: 10px;
-  border-radius: 3px;
-  padding: 0 2px;
-  margin: 0;
-  margin-inline-end: 3px;
-  -moz-user-select: none;
-}
-
-.request-list-item.selected .requests-list-transferred.theme-comment {
-  color: var(--theme-selection-color);
-}
-
-/* Network requests table: status codes */
-
 .requests-list-status-code {
-  margin-inline-start: 3px !important;
+  margin-inline-start: 3px;
   width: 3em;
-  margin-inline-end: -3em !important;
 }
 
 .requests-list-status-icon {
   background: #fff;
   height: 10px;
   width: 10px;
   margin-inline-start: 5px;
   margin-inline-end: 5px;
   border-radius: 10px;
   transition: box-shadow 0.5s ease-in-out;
-  box-sizing: border-box;
 }
 
 .request-list-item.selected .requests-list-status-icon {
   filter: brightness(1.3);
 }
 
 .requests-list-status-icon:not([data-code]) {
   background-color: var(--theme-content-color2);
@@ -495,47 +385,191 @@ body,
   border-left: 5px solid transparent;
   border-right: 5px solid transparent;
   border-bottom: 10px solid var(--theme-highlight-lightorange);
   border-radius: 0;
 }
 
 /* 4xx and 5xx are squares - error codes */
 .requests-list-status-icon[data-code^="4"] {
- background-color: var(--theme-highlight-red);
+  background-color: var(--theme-highlight-red);
   border-radius: 0; /* squares */
 }
 
 .requests-list-status-icon[data-code^="5"] {
   background-color: var(--theme-highlight-pink);
   border-radius: 0;
   transform: rotate(45deg);
 }
 
-/* Network requests table: waterfall header */
+/* Method column */
+
+.requests-list-method {
+  width: 8%;
+}
+
+.theme-firebug .requests-list-method {
+  color: rgb(128, 128, 128);
+}
+
+/* File column */
+
+.requests-list-file {
+  width: 22%;
+}
+
+.requests-list-file.requests-list-column {
+  text-align: start;
+}
+
+.requests-list-icon {
+  background: transparent;
+  width: 15px;
+  height: 15px;
+  margin: 0 4px;
+  outline: 1px solid var(--table-splitter-color);
+  vertical-align: top;
+}
+
+/* Protocol column */
+
+.requests-list-protocol {
+  width: 8%;
+}
+
+/* Domain column */
+
+.requests-list-domain {
+  width: 13%;
+}
+
+.requests-list-domain.requests-list-column {
+  text-align: start;
+}
+
+.requests-security-state-icon {
+  display: inline-block;
+  width: 16px;
+  height: 16px;
+  margin: 0 4px;
+  vertical-align: top;
+}
+
+.request-list-item.selected .requests-security-state-icon {
+  filter: brightness(1.3);
+}
+
+.security-state-insecure {
+  background-image: url(chrome://devtools/skin/images/security-state-insecure.svg);
+}
+
+.security-state-secure {
+  background-image: url(chrome://devtools/skin/images/security-state-secure.svg);
+}
+
+.security-state-weak {
+  background-image: url(chrome://devtools/skin/images/security-state-weak.svg);
+}
+
+.security-state-broken {
+  background-image: url(chrome://devtools/skin/images/security-state-broken.svg);
+}
+
+.security-state-local {
+  background-image: url(chrome://devtools/skin/images/globe.svg);
+}
+
+/* RemoteIP column */
+
+.requests-list-remoteip {
+  width: 9%;
+}
+
+/* Cause column */
+
+.requests-list-cause {
+  width: 9%;
+}
+
+.requests-list-cause-stack {
+  display: inline-block;
+  background-color: var(--theme-body-color-alt);
+  color: var(--theme-body-background);
+  font-size: 8px;
+  font-weight: bold;
+  line-height: 10px;
+  border-radius: 3px;
+  padding: 0 2px;
+  margin: 0;
+  margin-inline-end: 3px;
+}
+
+/* Type column */
+
+.requests-list-type {
+  width: 6%;
+}
+
+/* Transferred column */
+
+.requests-list-transferred {
+  width: 9%;
+}
+
+/* Size column */
+
+.requests-list-size {
+  width: 7%;
+}
+
+.theme-firebug .requests-list-size {
+  justify-content: end;
+  padding-inline-end: 4px;
+}
+
+/* Waterfall column */
 
 .requests-list-waterfall {
-  flex: auto;
+  width: 20vw;
+  max-width: 20vw;
+  min-width: 20vw;
+  background-repeat: repeat-y;
+  background-position: left center;
+  /* Background created on a <canvas> in js. */
+  /* @see devtools/client/netmonitor/src/waterfall-background.js */
+  background-image: -moz-element(#waterfall-background);
+}
+
+.requests-list-waterfall:dir(rtl) {
+  background-position: right center;
+}
+
+.requests-list-waterfall > .requests-list-header-button {
   padding-inline-start: 0;
 }
 
+.requests-list-waterfall > .requests-list-header-button > .button-text {
+  width: auto;
+}
+
 .requests-list-waterfall-label-wrapper:not(.requests-list-waterfall-visible) {
   padding-inline-start: 16px;
 }
 
 .requests-list-timings-division {
-  padding-top: 2px;
+  display: inline-block;
   padding-inline-start: 4px;
   font-size: 75%;
   pointer-events: none;
-  box-sizing: border-box;
   text-align: start;
-  /* Allow the timing label to shrink if the container gets too narrow.
-   * The container width then is not limited by the content. */
-  flex: initial;
+}
+
+:root[platform="win"] .requests-list-timings-division {
+  padding-top: 1px;
+  font-size: 90%;
 }
 
 .requests-list-timings-division:not(:first-child) {
   border-inline-start: 1px dashed;
 }
 
 .requests-list-timings-division:dir(ltr) {
   transform-origin: left center;
@@ -553,65 +587,31 @@ body,
   border-inline-start-color: #585959 !important;
 }
 
 .requests-list-timings-division[data-division-scale=second],
 .requests-list-timings-division[data-division-scale=minute] {
   font-weight: 600;
 }
 
-/* Network requests table: waterfall items */
-
-.requests-list-subitem.requests-list-waterfall {
-  padding-inline-start: 0;
-  padding-inline-end: 4px;
-  /* Background created on a <canvas> in js. */
-  /* @see devtools/client/netmonitor/netmonitor-view.js */
-  background-image: -moz-element(#waterfall-background);
-  background-repeat: repeat-y;
-  background-position: left center;
-}
-
-.requests-list-subitem.requests-list-waterfall:dir(rtl) {
-  background-position: right center;
-}
-
 .requests-list-timings {
   display: flex;
   flex: none;
   align-items: center;
   transform: scaleX(var(--timings-scale));
 }
 
 .requests-list-timings:dir(ltr) {
   transform-origin: left center;
 }
 
 .requests-list-timings:dir(rtl) {
   transform-origin: right center;
 }
 
-.requests-list-timings-total:dir(ltr) {
-  transform-origin: left center;
-}
-
-.requests-list-timings-total:dir(rtl) {
-  transform-origin: right center;
-}
-
-.requests-list-timings-total {
-  display: inline-block;
-  padding-inline-start: 4px;
-  font-size: 85%;
-  font-weight: 600;
-  white-space: nowrap;
-  /* This node should not be scaled - apply a reversed transformation */
-  transform: scaleX(var(--timings-rev-scale));
-}
-
 .requests-list-timings-box {
   display: inline-block;
   height: 9px;
 }
 
 .theme-firebug .requests-list-timings-box {
   background-image: linear-gradient(rgba(255, 255, 255, 0.3), rgba(0, 0, 0, 0.2));
   height: 16px;
@@ -636,92 +636,79 @@ body,
 .requests-list-timings-box.wait {
   background-color: var(--timing-wait-color);
 }
 
 .requests-list-timings-box.receive {
   background-color: var(--timing-receive-color);
 }
 
-/* SideMenuWidget */
-#network-table .request-list-empty-notice,
-#network-table .request-list-container {
-  background-color: var(--theme-body-background);
+.requests-list-timings-total {
+  display: inline-block;
+  padding-inline-start: 4px;
+  font-size: 85%;
+  font-weight: 600;
+  white-space: nowrap;
+  /* This node should not be scaled - apply a reversed transformation */
+  transform: scaleX(var(--timings-rev-scale));
 }
 
+.requests-list-timings-total:dir(ltr) {
+  transform-origin: left center;
+}
+
+.requests-list-timings-total:dir(rtl) {
+  transform-origin: right center;
+}
+
+/* Request list item */
+
 .request-list-item {
-  display: flex;
-  border-top-color: transparent;
-  border-bottom-color: transparent;
-  padding: 0;
+  display: table-row;
+  height: 24px;
 }
 
 .request-list-item.selected {
   background-color: var(--theme-selection-background);
   color: var(--theme-selection-color);
 }
 
 .request-list-item:not(.selected).odd {
   background-color: var(--table-zebra-background);
 }
 
 .request-list-item:not(.selected):hover {
   background-color: var(--theme-selection-background-semitransparent);
 }
 
-.request-list-item.fromCache > .requests-list-subitem:not(.requests-list-waterfall) {
-    opacity: 0.6;
+.request-list-item.fromCache > .requests-list-column:not(.requests-list-waterfall) {
+  opacity: 0.6;
 }
 
 .theme-firebug .request-list-item:not(.selected):hover {
   background: #EFEFEF;
 }
 
-.theme-firebug .requests-list-subitem {
-  padding: 1px;
-}
-
-/* HTTP Status Column */
-.theme-firebug .requests-list-subitem.requests-list-status {
-  font-weight: bold;
-}
-
-/* Method Column */
-
-.theme-firebug .requests-list-subitem.requests-list-method-box {
-  color: rgb(128, 128, 128);
-}
-
-.request-list-item.selected .requests-list-method {
-  color: var(--theme-selection-color);
-}
-
-/* Size Column */
-.theme-firebug .requests-list-subitem.requests-list-size {
-  justify-content: end;
-  padding-inline-end: 4px;
-}
-
-/* Network details panel */
+/* Network details panel toggle */
 
 .network-details-panel-toggle[disabled] {
   display: none;
 }
 
 .network-details-panel-toggle:dir(ltr)::before,
 .network-details-panel-toggle.pane-collapsed:dir(rtl)::before {
   background-image: var(--theme-pane-collapse-image);
 }
 
 .network-details-panel-toggle.pane-collapsed:dir(ltr)::before,
 .network-details-panel-toggle:dir(rtl)::before {
   background-image: var(--theme-pane-expand-image);
 }
 
-/* Network request details tabpanels */
+/* Network details panel */
 
 .network-details-panel {
   width: 100%;
   height: 100%;
   overflow: hidden;
 }
 
 .panel-container {
@@ -911,17 +898,16 @@ body,
   padding: 0 4px;
 }
 
 .headers-summary .raw-headers textarea {
   width: 100%;
   height: 50vh;
   font: message-box;
   resize: none;
-  box-sizing: border-box;
 }
 
 .headers-summary .raw-headers .tabpanel-summary-label {
   padding: 0 0 4px 0;
 }
 
 .headers-summary .textbox-input {
   margin-inline-end: 2px;
@@ -1047,17 +1033,17 @@ body,
 }
 
 @media (min-resolution: 1.1dppx) {
   .security-warning-icon {
     background-image: url(chrome://devtools/skin/images/alerticon-warning@2x.png);
   }
 }
 
-/* Custom request view */
+/* Custom request panel */
 
 .custom-request-panel {
   height: 100%;
   overflow: auto;
   padding: 0 4px;
   background-color: var(--theme-sidebar-background);
 }
 
@@ -1096,43 +1082,44 @@ body,
   width: 4.5em;
 }
 
 .custom-url-value {
   flex-grow: 1;
   margin-inline-start: 6px;
 }
 
-/* Performance analysis buttons */
+/* Statistics panel buttons */
 
 .requests-list-network-summary-button {
-  display: flex;
-  flex-wrap: nowrap;
-  align-items: center;
+  display: inline-flex;
+  cursor: pointer;
+  height: 18px;
   background: none;
   box-shadow: none;
   border-color: transparent;
   padding-inline-end: 0;
-  cursor: pointer;
+  margin-top: 3px;
+  margin-bottom: 3px;
   margin-inline-end: 1em;
 }
 
 .requests-list-network-summary-button > .summary-info-icon {
-  background-image: url(chrome://devtools/skin/images/profiler-stopwatch.svg);
+  background: url(chrome://devtools/skin/images/profiler-stopwatch.svg) no-repeat;
   filter: var(--icon-filter);
   width: 16px;
   height: 16px;
   opacity: 0.8;
 }
 
 .requests-list-network-summary-button:hover > .summary-info-icon {
   opacity: 1;
 }
 
-/* Performance analysis view */
+/* Statistics panel */
 
 .statistics-panel {
   display: flex;
   height: 100vh;
 }
 
 .statistics-panel .devtools-toolbarbutton.back-button {
   min-width: 4em;
@@ -1258,17 +1245,17 @@ body,
   text-align: start;
 }
 
 .table-chart-totals {
   display: flex;
   flex-direction: column;
 }
 
-/* Firebug theme support for network charts */
+/* Firebug theme support for statistics panel charts */
 
 .theme-firebug .chart-colored-blob[name=html] {
   fill: rgba(94, 136, 176, 0.8); /* Blue-Grey highlight */
   background: rgba(94, 136, 176, 0.8);
 }
 
 .theme-firebug .chart-colored-blob[name=css] {
   fill: rgba(70, 175, 227, 0.8); /* light blue */
@@ -1300,90 +1287,44 @@ body,
   background: rgba(235, 235, 84, 0.8);
 }
 
 .theme-firebug .chart-colored-blob[name=flash] {
   fill: rgba(84, 235, 159, 0.8); /* cyan */
   background: rgba(84, 235, 159, 0.8);
 }
 
-/* Responsive sidebar */
+/* Responsive web design support */
+
 @media (max-width: 700px) {
-  .requests-list-toolbar {
-    height: 22px;
-  }
-
   .requests-list-header-button {
-    min-height: 22px;
-    padding-left: 8px;
-  }
-
-  .requests-list-status {
-    max-width: none;
+    padding-inline-start: 8px;
   }
 
   .requests-list-status-code {
     width: auto;
   }
 
-  .requests-list-method,
-  .requests-list-method-box {
-    max-width: none;
-    width: 12vw;
-  }
-
-  .requests-list-icon-and-file {
-    width: 22vw;
-  }
-
-  .requests-list-security-and-domain {
-    width: 16vw;
-  }
-
-  .requests-list-remoteip {
-    width: 8vw;
-  }
-
-  .requests-list-cause,
-  .requests-list-type,
-  .requests-list-transferred,
   .requests-list-size {
-    max-width: none;
-    width: 8vw;
+    /* Given a fix max-width to display all columns in RWD mode */
+    max-width: 7%;
   }
 
   .requests-list-waterfall {
     display: none;
   }
 
   .statistics-panel .charts-container {
     flex-direction: column;
     /* Minus 4em for statistics back button width */
     width: calc(100% - 4em);
   }
 
   .statistics-panel .splitter {
     width: 100%;
     height: 1px;
   }
-}
 
-/* Platform overrides (copied in from the old platform specific files) */
-
-:root[platform="win"] .requests-list-timings-division {
-  padding-top: 1px;
-  font-size: 90%;
-}
-
-:root[platform="linux"] #headers-summary-resend {
-  padding: 4px;
-}
-
-:root[platform="linux"] #toggle-raw-headers {
-  padding: 4px;
-}
-
-/* Responsive sidebar */
-@media (max-width: 700px) {
   :root[platform="linux"] .requests-list-header-button {
     font-size: 85%;
   }
 }
+
--- a/devtools/client/netmonitor/src/components/request-list-column-cause.js
+++ b/devtools/client/netmonitor/src/components/request-list-column-cause.js
@@ -5,58 +5,50 @@
 "use strict";
 
 const {
   createClass,
   DOM,
   PropTypes,
 } = require("devtools/client/shared/vendor/react");
 
-const { div, span } = DOM;
+const { div } = DOM;
 
 const RequestListColumnCause = createClass({
   displayName: "RequestListColumnCause",
 
   propTypes: {
     item: PropTypes.object.isRequired,
     onCauseBadgeClick: PropTypes.func.isRequired,
   },
 
   shouldComponentUpdate(nextProps) {
     return this.props.item.cause !== nextProps.item.cause;
   },
 
   render() {
-    const {
-      item,
+    let {
+      item: { cause },
       onCauseBadgeClick,
     } = this.props;
 
-    const { cause } = item;
-
-    let causeType = "";
-    let causeUri = undefined;
+    let causeType = "unknown";
     let causeHasStack = false;
 
     if (cause) {
       // Legacy server might send a numeric value. Display it as "unknown"
       causeType = typeof cause.type === "string" ? cause.type : "unknown";
-      causeUri = cause.loadingDocumentUri;
       causeHasStack = cause.stacktrace && cause.stacktrace.length > 0;
     }
 
     return (
-      div({
-        className: "requests-list-subitem requests-list-cause",
-        title: causeUri,
-      },
-        span({
+      div({ className: "requests-list-column requests-list-cause", title: causeType },
+        causeHasStack && div({
           className: "requests-list-cause-stack",
-          hidden: !causeHasStack,
           onClick: onCauseBadgeClick,
         }, "JS"),
-        span({ className: "subitem-label" }, causeType),
+        causeType
       )
     );
   }
 });
 
 module.exports = RequestListColumnCause;
--- a/devtools/client/netmonitor/src/components/request-list-column-content-size.js
+++ b/devtools/client/netmonitor/src/components/request-list-column-content-size.js
@@ -6,41 +6,31 @@
 
 const {
   createClass,
   DOM,
   PropTypes,
 } = require("devtools/client/shared/vendor/react");
 const { getFormattedSize } = require("../utils/format-utils");
 
-const { div, span } = DOM;
+const { div } = DOM;
 
 const RequestListColumnContentSize = createClass({
   displayName: "RequestListColumnContentSize",
 
   propTypes: {
     item: PropTypes.object.isRequired,
   },
 
   shouldComponentUpdate(nextProps) {
     return this.props.item.contentSize !== nextProps.item.contentSize;
   },
 
   render() {
-    const { contentSize } = this.props.item;
-
-    let text;
-    if (typeof contentSize == "number") {
-      text = getFormattedSize(contentSize);
-    }
-
+    let { contentSize } = this.props.item;
+    let size = typeof contentSize === "number" ? getFormattedSize(contentSize) : null;
     return (
-      div({
-        className: "requests-list-subitem subitem-label requests-list-size",
-        title: text,
-      },
-        span({ className: "subitem-label" }, text),
-      )
+      div({ className: "requests-list-column requests-list-size", title: size }, size)
     );
   }
 });
 
 module.exports = RequestListColumnContentSize;
--- a/devtools/client/netmonitor/src/components/request-list-column-domain.js
+++ b/devtools/client/netmonitor/src/components/request-list-column-domain.js
@@ -7,58 +7,57 @@
 const {
   createClass,
   DOM,
   PropTypes,
 } = require("devtools/client/shared/vendor/react");
 const { L10N } = require("../utils/l10n");
 const { propertiesEqual } = require("../utils/request-utils");
 
-const { div, span } = DOM;
+const { div } = DOM;
 
 const UPDATED_DOMAIN_PROPS = [
-  "urlDetails",
   "remoteAddress",
   "securityState",
+  "urlDetails",
 ];
 
 const RequestListColumnDomain = createClass({
   displayName: "RequestListColumnDomain",
 
   propTypes: {
     item: PropTypes.object.isRequired,
     onSecurityIconClick: PropTypes.func.isRequired,
   },
 
   shouldComponentUpdate(nextProps) {
     return !propertiesEqual(UPDATED_DOMAIN_PROPS, this.props.item, nextProps.item);
   },
 
   render() {
-    const { item, onSecurityIconClick } = this.props;
-    const { urlDetails, remoteAddress, securityState } = item;
-
+    let { item, onSecurityIconClick } = this.props;
+    let { remoteAddress, securityState, urlDetails: { host, isLocal } } = item;
     let iconClassList = ["requests-security-state-icon"];
     let iconTitle;
-    if (urlDetails.isLocal) {
+    let title = host + (remoteAddress ? ` (${remoteAddress})` : "");
+
+    if (isLocal) {
       iconClassList.push("security-state-local");
       iconTitle = L10N.getStr("netmonitor.security.state.secure");
     } else if (securityState) {
       iconClassList.push(`security-state-${securityState}`);
       iconTitle = L10N.getStr(`netmonitor.security.state.${securityState}`);
     }
 
-    let title = urlDetails.host + (remoteAddress ? ` (${remoteAddress})` : "");
-
     return (
-      div({ className: "requests-list-subitem requests-list-security-and-domain" },
+      div({ className: "requests-list-column requests-list-domain", title },
         div({
           className: iconClassList.join(" "),
+          onMouseDown: onSecurityIconClick,
           title: iconTitle,
-          onClick: onSecurityIconClick,
         }),
-        span({ className: "subitem-label requests-list-domain", title }, urlDetails.host),
+        host,
       )
     );
   }
 });
 
 module.exports = RequestListColumnDomain;
--- a/devtools/client/netmonitor/src/components/request-list-column-file.js
+++ b/devtools/client/netmonitor/src/components/request-list-column-file.js
@@ -9,46 +9,42 @@ const {
   DOM,
   PropTypes,
 } = require("devtools/client/shared/vendor/react");
 const { propertiesEqual } = require("../utils/request-utils");
 
 const { div, img } = DOM;
 
 const UPDATED_FILE_PROPS = [
+  "responseContentDataUri",
   "urlDetails",
-  "responseContentDataUri",
 ];
 
 const RequestListColumnFile = createClass({
   displayName: "RequestListColumnFile",
 
   propTypes: {
     item: PropTypes.object.isRequired,
   },
 
   shouldComponentUpdate(nextProps) {
     return !propertiesEqual(UPDATED_FILE_PROPS, this.props.item, nextProps.item);
   },
 
   render() {
-    const { urlDetails, responseContentDataUri } = this.props.item;
+    let { responseContentDataUri, urlDetails } = this.props.item;
 
     return (
-      div({ className: "requests-list-subitem requests-list-icon-and-file" },
+      div({
+        className: "requests-list-column requests-list-file",
+        title: urlDetails.unicodeUrl,
+      },
         img({
           className: "requests-list-icon",
           src: responseContentDataUri,
-          hidden: !responseContentDataUri,
-          "data-type": responseContentDataUri ? "thumbnail" : undefined,
         }),
-        div({
-          className: "subitem-label requests-list-file",
-          title: urlDetails.unicodeUrl,
-        },
-          urlDetails.baseNameWithQuery,
-        ),
+        urlDetails.baseNameWithQuery
       )
     );
   }
 });
 
 module.exports = RequestListColumnFile;
--- a/devtools/client/netmonitor/src/components/request-list-column-method.js
+++ b/devtools/client/netmonitor/src/components/request-list-column-method.js
@@ -5,32 +5,28 @@
 "use strict";
 
 const {
   createClass,
   DOM,
   PropTypes,
 } = require("devtools/client/shared/vendor/react");
 
-const { div, span } = DOM;
+const { div } = DOM;
 
 const RequestListColumnMethod = createClass({
   displayName: "RequestListColumnMethod",
 
   propTypes: {
     item: PropTypes.object.isRequired,
   },
 
   shouldComponentUpdate(nextProps) {
     return this.props.item.method !== nextProps.item.method;
   },
 
   render() {
-    const { method } = this.props.item;
-    return (
-      div({ className: "requests-list-subitem requests-list-method-box" },
-        span({ className: "subitem-label requests-list-method" }, method)
-      )
-    );
+    let { method } = this.props.item;
+    return div({ className: "requests-list-column requests-list-method" }, method);
   }
 });
 
 module.exports = RequestListColumnMethod;
--- a/devtools/client/netmonitor/src/components/request-list-column-protocol.js
+++ b/devtools/client/netmonitor/src/components/request-list-column-protocol.js
@@ -5,32 +5,35 @@
 "use strict";
 
 const {
   createClass,
   DOM,
   PropTypes,
 } = require("devtools/client/shared/vendor/react");
 
-const { div, span } = DOM;
+const { div } = DOM;
 
 const RequestListColumnProtocol = createClass({
   displayName: "RequestListColumnProtocol",
 
   propTypes: {
     item: PropTypes.object.isRequired,
   },
 
   shouldComponentUpdate(nextProps) {
     return this.props.item.httpVersion !== nextProps.item.httpVersion;
   },
 
   render() {
-    const { httpVersion } = this.props.item;
+    let { httpVersion = "" } = this.props.item;
     return (
-      div({ className: "requests-list-subitem requests-list-protocol" },
-        span({ className: "subitem-label", title: httpVersion }, httpVersion),
+      div({
+        className: "requests-list-column requests-list-protocol",
+        title: httpVersion,
+      },
+        httpVersion
       )
     );
   }
 });
 
 module.exports = RequestListColumnProtocol;
--- a/devtools/client/netmonitor/src/components/request-list-column-remote-ip.js
+++ b/devtools/client/netmonitor/src/components/request-list-column-remote-ip.js
@@ -5,34 +5,34 @@
 "use strict";
 
 const {
   createClass,
   DOM,
   PropTypes,
 } = require("devtools/client/shared/vendor/react");
 
-const { div, span } = DOM;
+const { div } = DOM;
 
 const RequestListColumnRemoteIP = createClass({
   displayName: "RequestListColumnRemoteIP",
 
   propTypes: {
     item: PropTypes.object.isRequired,
   },
 
   shouldComponentUpdate(nextProps) {
     return this.props.item.remoteAddress !== nextProps.item.remoteAddress;
   },
 
   render() {
-    const { remoteAddress, remotePort } = this.props.item;
-    let remoteSummary = remoteAddress ? `${remoteAddress}:${remotePort}` : "";
+    let { remoteAddress, remotePort } = this.props.item;
+    let remoteIP = remoteAddress ? `${remoteAddress}:${remotePort}` : "unknown";
 
     return (
-      div({ className: "requests-list-subitem requests-list-remoteip" },
-        span({ className: "subitem-label", title: remoteSummary }, remoteSummary),
+      div({ className: "requests-list-column requests-list-remoteip", title: remoteIP },
+        remoteIP
       )
     );
   }
 });
 
 module.exports = RequestListColumnRemoteIP;
--- a/devtools/client/netmonitor/src/components/request-list-column-status.js
+++ b/devtools/client/netmonitor/src/components/request-list-column-status.js
@@ -7,17 +7,17 @@
 const {
   createClass,
   DOM,
   PropTypes,
 } = require("devtools/client/shared/vendor/react");
 const { L10N } = require("../utils/l10n");
 const { propertiesEqual } = require("../utils/request-utils");
 
-const { div, span } = DOM;
+const { div } = DOM;
 
 const UPDATED_STATUS_PROPS = [
   "fromCache",
   "fromServiceWorker",
   "status",
   "statusText",
 ];
 
@@ -28,18 +28,17 @@ const RequestListColumnStatus = createCl
     item: PropTypes.object.isRequired,
   },
 
   shouldComponentUpdate(nextProps) {
     return !propertiesEqual(UPDATED_STATUS_PROPS, this.props.item, nextProps.item);
   },
 
   render() {
-    const { status, statusText, fromCache, fromServiceWorker } = this.props.item;
-
+    let { fromCache, fromServiceWorker, status, statusText } = this.props.item;
     let code, title;
 
     if (status) {
       if (fromCache) {
         code = "cached";
       } else if (fromServiceWorker) {
         code = "service worker";
       } else {
@@ -59,17 +58,17 @@ const RequestListColumnStatus = createCl
         } else {
           title = L10N.getFormatStr("netmonitor.status.tooltip.simple",
             status, statusText);
         }
       }
     }
 
     return (
-        div({ className: "requests-list-subitem requests-list-status", title },
+      div({ className: "requests-list-column requests-list-status", title },
         div({ className: "requests-list-status-icon", "data-code": code }),
-        span({ className: "subitem-label requests-list-status-code" }, status)
+        div({ className: "requests-list-status-code" }, status)
       )
     );
   }
 });
 
 module.exports = RequestListColumnStatus;
--- a/devtools/client/netmonitor/src/components/request-list-column-transferred-size.js
+++ b/devtools/client/netmonitor/src/components/request-list-column-transferred-size.js
@@ -8,17 +8,17 @@ const {
   createClass,
   DOM,
   PropTypes,
 } = require("devtools/client/shared/vendor/react");
 const { getFormattedSize } = require("../utils/format-utils");
 const { L10N } = require("../utils/l10n");
 const { propertiesEqual } = require("../utils/request-utils");
 
-const { div, span } = DOM;
+const { div } = DOM;
 
 const UPDATED_TRANSFERRED_PROPS = [
   "transferredSize",
   "fromCache",
   "fromServiceWorker",
 ];
 
 const RequestListColumnTransferredSize = createClass({
@@ -28,36 +28,30 @@ const RequestListColumnTransferredSize =
     item: PropTypes.object.isRequired,
   },
 
   shouldComponentUpdate(nextProps) {
     return !propertiesEqual(UPDATED_TRANSFERRED_PROPS, this.props.item, nextProps.item);
   },
 
   render() {
-    const { transferredSize, fromCache, fromServiceWorker, status } = this.props.item;
+    let { fromCache, fromServiceWorker, status, transferredSize } = this.props.item;
+    let text;
 
-    let text;
-    let className = "subitem-label";
     if (fromCache || status === "304") {
       text = L10N.getStr("networkMenu.sizeCached");
-      className += " theme-comment";
     } else if (fromServiceWorker) {
       text = L10N.getStr("networkMenu.sizeServiceWorker");
-      className += " theme-comment";
     } else if (typeof transferredSize == "number") {
       text = getFormattedSize(transferredSize);
     } else if (transferredSize === null) {
       text = L10N.getStr("networkMenu.sizeUnavailable");
     }
 
     return (
-      div({
-        className: "requests-list-subitem requests-list-transferred",
-        title: text,
-      },
-        span({ className }, text),
+      div({ className: "requests-list-column requests-list-transferred", title: text },
+        text
       )
     );
   }
 });
 
 module.exports = RequestListColumnTransferredSize;
--- a/devtools/client/netmonitor/src/components/request-list-column-type.js
+++ b/devtools/client/netmonitor/src/components/request-list-column-type.js
@@ -6,40 +6,41 @@
 
 const {
   createClass,
   DOM,
   PropTypes,
 } = require("devtools/client/shared/vendor/react");
 const { getAbbreviatedMimeType } = require("../utils/request-utils");
 
-const { div, span } = DOM;
+const { div } = DOM;
 
 const RequestListColumnType = createClass({
   displayName: "RequestListColumnType",
 
   propTypes: {
     item: PropTypes.object.isRequired,
   },
 
   shouldComponentUpdate(nextProps) {
     return this.props.item.mimeType !== nextProps.item.mimeType;
   },
 
   render() {
-    const { mimeType } = this.props.item;
+    let { mimeType } = this.props.item;
     let abbrevType;
+
     if (mimeType) {
       abbrevType = getAbbreviatedMimeType(mimeType);
     }
 
     return (
       div({
-        className: "requests-list-subitem requests-list-type",
+        className: "requests-list-column requests-list-type",
         title: mimeType,
       },
-        span({ className: "subitem-label" }, abbrevType),
+        abbrevType
       )
     );
   }
 });
 
 module.exports = RequestListColumnType;
--- a/devtools/client/netmonitor/src/components/request-list-column-waterfall.js
+++ b/devtools/client/netmonitor/src/components/request-list-column-waterfall.js
@@ -11,85 +11,88 @@ const {
 } = require("devtools/client/shared/vendor/react");
 const { L10N } = require("../utils/l10n");
 const { propertiesEqual } = require("../utils/request-utils");
 
 const { div } = DOM;
 
 const UPDATED_WATERFALL_PROPS = [
   "eventTimings",
-  "totalTime",
   "fromCache",
   "fromServiceWorker",
+  "totalTime",
 ];
+// List of properties of the timing info we want to create boxes for
+const TIMING_KEYS = ["blocked", "dns", "connect", "send", "wait", "receive"];
 
 const RequestListColumnWaterfall = createClass({
   displayName: "RequestListColumnWaterfall",
 
   propTypes: {
     firstRequestStartedMillis: PropTypes.number.isRequired,
     item: PropTypes.object.isRequired,
   },
 
   shouldComponentUpdate(nextProps) {
-    return this.props.firstRequestStartedMillis !== nextProps.firstRequestStartedMillis ||
-      !propertiesEqual(UPDATED_WATERFALL_PROPS, this.props.item, nextProps.item);
+    return !propertiesEqual(UPDATED_WATERFALL_PROPS, this.props.item, nextProps.item) ||
+      this.props.firstRequestStartedMillis !== nextProps.firstRequestStartedMillis;
   },
 
   render() {
-    const { item, firstRequestStartedMillis } = this.props;
+    let { firstRequestStartedMillis, item } = this.props;
 
     return (
-      div({ className: "requests-list-subitem requests-list-waterfall" },
+      div({ className: "requests-list-column requests-list-waterfall" },
         div({
           className: "requests-list-timings",
           style: {
             paddingInlineStart: `${item.startedMillis - firstRequestStartedMillis}px`,
           },
         },
           timingBoxes(item),
         )
       )
     );
   }
 });
 
-// List of properties of the timing info we want to create boxes for
-const TIMING_KEYS = ["blocked", "dns", "connect", "send", "wait", "receive"];
-
 function timingBoxes(item) {
-  const { eventTimings, totalTime, fromCache, fromServiceWorker } = item;
+  let { eventTimings, fromCache, fromServiceWorker, totalTime } = item;
   let boxes = [];
 
   if (fromCache || fromServiceWorker) {
     return boxes;
   }
 
   if (eventTimings) {
     // Add a set of boxes representing timing information.
     for (let key of TIMING_KEYS) {
       let width = eventTimings.timings[key];
 
       // Don't render anything if it surely won't be visible.
       // One millisecond == one unscaled pixel.
       if (width > 0) {
-        boxes.push(div({
-          key,
-          className: "requests-list-timings-box " + key,
-          style: { width }
-        }));
+        boxes.push(
+          div({
+            key,
+            className: `requests-list-timings-box ${key}`,
+            style: { width },
+          })
+        );
       }
     }
   }
 
   if (typeof totalTime === "number") {
-    let text = L10N.getFormatStr("networkMenu.totalMS", totalTime);
-    boxes.push(div({
-      key: "total",
-      className: "requests-list-timings-total",
-      title: text
-    }, text));
+    let title = L10N.getFormatStr("networkMenu.totalMS", totalTime);
+    boxes.push(
+      div({
+        key: "total",
+        className: "requests-list-timings-total",
+        title,
+      }, title)
+    );
   }
 
   return boxes;
 }
 
 module.exports = RequestListColumnWaterfall;
--- a/devtools/client/netmonitor/src/components/request-list-content.js
+++ b/devtools/client/netmonitor/src/components/request-list-content.js
@@ -149,17 +149,17 @@ const RequestListContent = createClass({
     if (!itemId) {
       return false;
     }
     let requestItem = this.props.displayedRequests.find(r => r.id == itemId);
     if (!requestItem) {
       return false;
     }
 
-    if (requestItem.responseContent && target.closest(".requests-list-icon-and-file")) {
+    if (requestItem.responseContent && target.closest(".requests-list-icon")) {
       return setTooltipImageContent(tooltip, itemEl, requestItem);
     }
 
     return false;
   },
 
   /**
    * Scroll listener for the requests menu view.
@@ -219,43 +219,47 @@ const RequestListContent = createClass({
     this.shouldScrollBottom = false;
   },
 
   render() {
     const {
       columns,
       displayedRequests,
       firstRequestStartedMillis,
-      selectedRequestId,
       onCauseBadgeClick,
       onItemMouseDown,
       onSecurityIconClick,
+      selectedRequestId,
     } = this.props;
 
     return (
-      div({
-        ref: "contentEl",
-        className: "requests-list-contents",
-        tabIndex: 0,
-        onKeyDown: this.onKeyDown,
-      },
-        displayedRequests.map((item, index) => RequestListItem({
-          firstRequestStartedMillis,
-          fromCache: item.status === "304" || item.fromCache,
-          columns,
-          item,
-          index,
-          isSelected: item.id === selectedRequestId,
-          key: item.id,
-          onContextMenu: this.onContextMenu,
-          onFocusedNodeChange: this.onFocusedNodeChange,
-          onMouseDown: () => onItemMouseDown(item.id),
-          onCauseBadgeClick: () => onCauseBadgeClick(item.cause),
-          onSecurityIconClick: () => onSecurityIconClick(item.securityState),
-        }))
+      div({ className: "requests-list-wrapper"},
+        div({ className: "requests-list-table"},
+          div({
+            ref: "contentEl",
+            className: "requests-list-contents",
+            tabIndex: 0,
+            onKeyDown: this.onKeyDown,
+          },
+            displayedRequests.map((item, index) => RequestListItem({
+              firstRequestStartedMillis,
+              fromCache: item.status === "304" || item.fromCache,
+              columns,
+              item,
+              index,
+              isSelected: item.id === selectedRequestId,
+              key: item.id,
+              onContextMenu: this.onContextMenu,
+              onFocusedNodeChange: this.onFocusedNodeChange,
+              onMouseDown: () => onItemMouseDown(item.id),
+              onCauseBadgeClick: () => onCauseBadgeClick(item.cause),
+              onSecurityIconClick: () => onSecurityIconClick(item.securityState),
+            }))
+          )
+        )
       )
     );
   },
 });
 
 module.exports = connect(
   (state) => ({
     columns: state.ui.columns,
--- a/devtools/client/netmonitor/src/components/request-list-header.js
+++ b/devtools/client/netmonitor/src/components/request-list-header.js
@@ -6,51 +6,49 @@
 
 const {
   createClass,
   PropTypes,
   DOM,
 } = require("devtools/client/shared/vendor/react");
 const { connect } = require("devtools/client/shared/vendor/react-redux");
 const Actions = require("../actions/index");
+const { HEADERS, REQUESTS_WATERFALL } = require("../constants");
 const { getWaterfallScale } = require("../selectors/index");
 const { getFormattedTime } = require("../utils/format-utils");
-const { HEADERS } = require("../constants");
 const { L10N } = require("../utils/l10n");
 const WaterfallBackground = require("../waterfall-background");
 const RequestListHeaderContextMenu = require("../request-list-header-context-menu");
 
 const { div, button } = DOM;
 
-const REQUESTS_WATERFALL_HEADER_TICKS_MULTIPLE = 5; // ms
-const REQUESTS_WATERFALL_HEADER_TICKS_SPACING_MIN = 60; // px
-
 /**
  * Render the request list header with sorting arrows for columns.
  * Displays tick marks in the waterfall column header.
  * Also draws the waterfall background canvas and updates it when needed.
  */
 const RequestListHeader = createClass({
   displayName: "RequestListHeader",
 
   propTypes: {
     columns: PropTypes.object.isRequired,
-    dispatch: PropTypes.func.isRequired,
-    sort: PropTypes.object,
+    resetColumns: PropTypes.func.isRequired,
+    resizeWaterfall: PropTypes.func.isRequired,
     scale: PropTypes.number,
+    sort: PropTypes.object,
+    sortBy: PropTypes.func.isRequired,
+    toggleColumn: PropTypes.func.isRequired,
     waterfallWidth: PropTypes.number,
-    onHeaderClick: PropTypes.func.isRequired,
-    resizeWaterfall: PropTypes.func.isRequired,
   },
 
   componentWillMount() {
-    const { dispatch } = this.props;
+    const { resetColumns, toggleColumn } = this.props;
     this.contextMenu = new RequestListHeaderContextMenu({
-      toggleColumn: (column) => dispatch(Actions.toggleColumn(column)),
-      resetColumns: () => dispatch(Actions.resetColumns()),
+      resetColumns,
+      toggleColumn,
     });
   },
 
   componentDidMount() {
     // Create the object that takes care of drawing the waterfall canvas background
     this.background = new WaterfallBackground(document);
     this.background.draw(this.props);
     this.resizeWaterfall();
@@ -68,85 +66,87 @@ const RequestListHeader = createClass({
   },
 
   onContextMenu(evt) {
     evt.preventDefault();
     this.contextMenu.open(evt);
   },
 
   resizeWaterfall() {
-    // Measure its width and update the 'waterfallWidth' property in the store.
-    // The 'waterfallWidth' will be further updated on every window resize.
-    setTimeout(() => {
-      let { width } = this.refs.header.getBoundingClientRect();
-      this.props.resizeWaterfall(width);
-    }, 50);
+    let waterfallHeader = this.refs.waterfallHeader;
+    if (waterfallHeader) {
+      // Measure its width and update the 'waterfallWidth' property in the store.
+      // The 'waterfallWidth' will be further updated on every window resize.
+      setTimeout(() => {
+        this.props.resizeWaterfall(waterfallHeader.getBoundingClientRect().width);
+      }, 500);
+    }
   },
 
   render() {
-    const { sort, scale, waterfallWidth, onHeaderClick, columns } = this.props;
+    let { columns, scale, sort, sortBy, waterfallWidth } = this.props;
 
-    return div(
-      { className: "devtools-toolbar requests-list-toolbar" },
-      div({ className: "toolbar-labels" },
-        HEADERS.filter(h => columns.get(h.name)).map(header => {
-          const name = header.name;
-          const boxName = header.boxName || name;
-          const label = L10N.getStr(`netmonitor.toolbar.${header.label || name}`);
+    return (
+      div({ className: "devtools-toolbar requests-list-headers" },
+        HEADERS.filter((header) => columns.get(header.name)).map((header) => {
+          let name = header.name;
+          let boxName = header.boxName || name;
+          let label = L10N.getStr(`netmonitor.toolbar.${header.label || name}`);
+          let sorted, sortedTitle;
+          let active = sort.type == name ? true : undefined;
 
-          let sorted, sortedTitle;
-          const active = sort.type == name ? true : undefined;
           if (active) {
             sorted = sort.ascending ? "ascending" : "descending";
             sortedTitle = L10N.getStr(sort.ascending
               ? "networkMenu.sortedAsc"
               : "networkMenu.sortedDesc");
           }
 
-          return div(
-            {
+          return (
+            div({
               id: `requests-list-${boxName}-header-box`,
-              className: `requests-list-header requests-list-${boxName}`,
+              className: `requests-list-column requests-list-${boxName}`,
               key: name,
-              ref: "header",
+              ref: `${name}Header`,
               // Used to style the next column.
               "data-active": active,
               onContextMenu: this.onContextMenu,
             },
-            button(
-              {
+              button({
                 id: `requests-list-${name}-button`,
-                className: `requests-list-header-button requests-list-${name}`,
+                className: `requests-list-header-button`,
                 "data-sorted": sorted,
                 title: sortedTitle,
-                onClick: () => onHeaderClick(name),
+                onClick: () => sortBy(name),
               },
-              name == "waterfall" ? WaterfallLabel(waterfallWidth, scale, label)
-                                  : div({ className: "button-text" }, label),
-              div({ className: "button-icon" })
+                name === "waterfall"
+                  ? WaterfallLabel(waterfallWidth, scale, label)
+                  : div({ className: "button-text" }, label),
+                div({ className: "button-icon" })
+              )
             )
           );
         })
       )
     );
   }
 });
 
 /**
  * Build the waterfall header - timing tick marks with the right spacing
  */
 function waterfallDivisionLabels(waterfallWidth, scale) {
   let labels = [];
 
   // Build new millisecond tick labels...
-  let timingStep = REQUESTS_WATERFALL_HEADER_TICKS_MULTIPLE;
+  let timingStep = REQUESTS_WATERFALL.HEADER_TICKS_MULTIPLE;
   let scaledStep = scale * timingStep;
 
   // Ignore any divisions that would end up being too close to each other.
-  while (scaledStep < REQUESTS_WATERFALL_HEADER_TICKS_SPACING_MIN) {
+  while (scaledStep < REQUESTS_WATERFALL.HEADER_TICKS_SPACING_MIN) {
     scaledStep *= 2;
   }
 
   // Insert one label for each division on the current scale.
   for (let x = 0; x < waterfallWidth; x += scaledStep) {
     let millisecondTime = x / scale;
     let divisionScale = "millisecond";
 
@@ -180,31 +180,32 @@ function waterfallDivisionLabels(waterfa
   }
 
   return labels;
 }
 
 function WaterfallLabel(waterfallWidth, scale, label) {
   let className = "button-text requests-list-waterfall-label-wrapper";
 
-  if (waterfallWidth != null && scale != null) {
+  if (waterfallWidth !== null && scale !== null) {
     label = waterfallDivisionLabels(waterfallWidth, scale);
     className += " requests-list-waterfall-visible";
   }
 
   return div({ className }, label);
 }
 
 module.exports = connect(
-  state => ({
+  (state) => ({
     columns: state.ui.columns,
-    sort: state.sort,
-    scale: getWaterfallScale(state),
-    waterfallWidth: state.ui.waterfallWidth,
     firstRequestStartedMillis: state.requests.firstStartedMillis,
+    scale: getWaterfallScale(state),
+    sort: state.sort,
     timingMarkers: state.timingMarkers,
+    waterfallWidth: state.ui.waterfallWidth,
   }),
-  dispatch => ({
-    dispatch,
-    onHeaderClick: type => dispatch(Actions.sortBy(type)),
-    resizeWaterfall: width => dispatch(Actions.resizeWaterfall(width)),
+  (dispatch) => ({
+    resetColumns: () => dispatch(Actions.resetColumns()),
+    resizeWaterfall: (width) => dispatch(Actions.resizeWaterfall(width)),
+    sortBy: (type) => dispatch(Actions.sortBy(type)),
+    toggleColumn: (column) => dispatch(Actions.toggleColumn(column)),
   })
 )(RequestListHeader);
--- a/devtools/client/netmonitor/src/components/request-list-item.js
+++ b/devtools/client/netmonitor/src/components/request-list-item.js
@@ -6,17 +6,16 @@
 
 const {
   createClass,
   createFactory,
   DOM,
   PropTypes,
 } = require("devtools/client/shared/vendor/react");
 const I = require("devtools/client/shared/vendor/immutable");
-
 const { propertiesEqual } = require("../utils/request-utils");
 
 // Components
 const RequestListColumnCause = createFactory(require("./request-list-column-cause"));
 const RequestListColumnContentSize = createFactory(require("./request-list-column-content-size"));
 const RequestListColumnDomain = createFactory(require("./request-list-column-domain"));
 const RequestListColumnFile = createFactory(require("./request-list-column-file"));
 const RequestListColumnMethod = createFactory(require("./request-list-column-method"));
@@ -50,19 +49,20 @@ const UPDATED_REQ_ITEM_PROPS = [
   "cause",
   "contentSize",
   "transferredSize",
   "startedMillis",
   "totalTime",
 ];
 
 const UPDATED_REQ_PROPS = [
+  "firstRequestStartedMillis",
   "index",
   "isSelected",
-  "firstRequestStartedMillis",
+  "waterfallWidth",
 ];
 
 /**
  * Render one row in the request list.
  */
 const RequestListItem = createClass({
   displayName: "RequestListItem",
 
@@ -73,67 +73,61 @@ const RequestListItem = createClass({
     isSelected: PropTypes.bool.isRequired,
     firstRequestStartedMillis: PropTypes.number.isRequired,
     fromCache: PropTypes.bool,
     onCauseBadgeClick: PropTypes.func.isRequired,
     onContextMenu: PropTypes.func.isRequired,
     onFocusedNodeChange: PropTypes.func,
     onMouseDown: PropTypes.func.isRequired,
     onSecurityIconClick: PropTypes.func.isRequired,
+    waterfallWidth: PropTypes.number,
   },
 
   componentDidMount() {
     if (this.props.isSelected) {
-      this.refs.el.focus();
+      this.refs.listItem.focus();
     }
   },
 
   shouldComponentUpdate(nextProps) {
     return !propertiesEqual(UPDATED_REQ_ITEM_PROPS, this.props.item, nextProps.item) ||
       !propertiesEqual(UPDATED_REQ_PROPS, this.props, nextProps) ||
       !I.is(this.props.columns, nextProps.columns);
   },
 
   componentDidUpdate(prevProps) {
     if (!prevProps.isSelected && this.props.isSelected) {
-      this.refs.el.focus();
+      this.refs.listItem.focus();
       if (this.props.onFocusedNodeChange) {
         this.props.onFocusedNodeChange();
       }
     }
   },
 
   render() {
-    const {
+    let {
       columns,
       item,
       index,
       isSelected,
       firstRequestStartedMillis,
       fromCache,
       onContextMenu,
       onMouseDown,
       onCauseBadgeClick,
-      onSecurityIconClick
+      onSecurityIconClick,
     } = this.props;
 
-    let classList = ["request-list-item"];
-    if (isSelected) {
-      classList.push("selected");
-    }
-
-    if (fromCache) {
-      classList.push("fromCache");
-    }
-
-    classList.push(index % 2 ? "odd" : "even");
+    let classList = ["request-list-item", index % 2 ? "odd" : "even"];
+    isSelected && classList.push("selected");
+    fromCache && classList.push("fromCache");
 
     return (
       div({
-        ref: "el",
+        ref: "listItem",
         className: classList.join(" "),
         "data-id": item.id,
         tabIndex: 0,
         onContextMenu,
         onMouseDown,
       },
         columns.get("status") && RequestListColumnStatus({ item }),
         columns.get("method") && RequestListColumnMethod({ item }),
--- a/devtools/client/netmonitor/src/components/source-editor.js
+++ b/devtools/client/netmonitor/src/components/source-editor.js
@@ -5,19 +5,19 @@
 "use strict";
 
 const {
   createClass,
   DOM,
   PropTypes,
 } = require("devtools/client/shared/vendor/react");
 const Editor = require("devtools/client/sourceeditor/editor");
+const { SOURCE_EDITOR_SYNTAX_HIGHLIGHT_MAX_SIZE } = require("../constants");
 
 const { div } = DOM;
-const SYNTAX_HIGHLIGHT_MAX_SIZE = 51200;
 
 /**
  * CodeMirror editor as a React component
  */
 const SourceEditor = createClass({
   displayName: "SourceEditor",
 
   propTypes: {
@@ -28,32 +28,33 @@ const SourceEditor = createClass({
   },
 
   componentDidMount() {
     const { mode, text } = this.props;
 
     this.editor = new Editor({
       lineNumbers: true,
       lineWrapping: false,
-      mode: text.length < SYNTAX_HIGHLIGHT_MAX_SIZE ? mode : null,
+      mode: text.length < SOURCE_EDITOR_SYNTAX_HIGHLIGHT_MAX_SIZE ? mode : null,
       readOnly: true,
       theme: "mozilla",
       value: text,
     });
 
     // Delay to CodeMirror initialization content to prevent UI freezed
     this.editorTimeout = setTimeout(() => {
       this.editor.appendToLocalElement(this.refs.editorElement);
     });
   },
 
   componentDidUpdate(prevProps) {
     const { mode, text } = this.props;
 
-    if (prevProps.mode !== mode && text.length < SYNTAX_HIGHLIGHT_MAX_SIZE) {
+    if (prevProps.mode !== mode &&
+        text.length < SOURCE_EDITOR_SYNTAX_HIGHLIGHT_MAX_SIZE) {
       this.editor.setMode(mode);
     }
 
     if (prevProps.text !== text) {
       this.editor.setText(text);
     }
   },
 
--- a/devtools/client/netmonitor/src/components/status-bar.js
+++ b/devtools/client/netmonitor/src/components/status-bar.js
@@ -5,30 +5,28 @@
 "use strict";
 
 const {
   DOM,
   PropTypes,
 } = require("devtools/client/shared/vendor/react");
 const { connect } = require("devtools/client/shared/vendor/react-redux");
 const { PluralForm } = require("devtools/shared/plural-form");
-
 const Actions = require("../actions/index");
 const {
   getDisplayedRequestsSummary,
   getDisplayedTimingMarker,
 } = require("../selectors/index");
 const {
   getFormattedSize,
-  getFormattedTime
+  getFormattedTime,
 } = require("../utils/format-utils");
 const { L10N } = require("../utils/l10n");
 
-// Components
-const { div, button, span } = DOM;
+const { button, div } = DOM;
 
 function StatusBar({ summary, openStatistics, timingMarkers }) {
   let { count, contentSize, transferredSize, millis } = summary;
   let {
     DOMContentLoaded,
     load,
   } = timingMarkers;
 
@@ -37,39 +35,42 @@ function StatusBar({ summary, openStatis
       count, L10N.getFormatStrWithNumbers("networkMenu.summary.requestsCount", count)
   );
   let transferText = L10N.getFormatStrWithNumbers("networkMenu.summary.transferred",
     getFormattedSize(contentSize), getFormattedSize(transferredSize));
   let finishText = L10N.getFormatStrWithNumbers("networkMenu.summary.finish",
     getFormattedTime(millis));
 
   return (
-    div({ className: "devtools-toolbar devtools-toolbar-bottom" },
+    div({ className: "devtools-toolbar devtools-status-bottom" },
       button({
         className: "devtools-button requests-list-network-summary-button",
         onClick: openStatistics,
       },
-        span({ className: "summary-info-icon" }),
+        div({ className: "summary-info-icon" }),
+      ),
+      div({ className: "status-bar-label requests-list-network-summary-count" },
+        countText
       ),
-      span({ className: "status-bar-label requests-list-network-summary-count" },
-        countText),
       count !== 0 &&
-        span({ className: "status-bar-label requests-list-network-summary-transfer" },
-          transferText),
+        div({ className: "status-bar-label requests-list-network-summary-transfer" },
+          transferText
+        ),
       count !== 0 &&
-        span({ className: "status-bar-label requests-list-network-summary-finish" },
-          finishText),
-
+        div({ className: "status-bar-label requests-list-network-summary-finish" },
+          finishText
+        ),
       DOMContentLoaded > -1 &&
-      span({ className: "status-bar-label dom-content-loaded" },
-        `DOMContentLoaded: ${getFormattedTime(DOMContentLoaded)}`),
-
+        div({ className: "status-bar-label dom-content-loaded" },
+          `DOMContentLoaded: ${getFormattedTime(DOMContentLoaded)}`
+        ),
       load > -1 &&
-      span({ className: "status-bar-label load" },
-        `load: ${getFormattedTime(load)}`),
+        div({ className: "status-bar-label load" },
+          `load: ${getFormattedTime(load)}`
+        ),
     )
   );
 }
 
 StatusBar.displayName = "StatusBar";
 
 StatusBar.propTypes = {
   openStatistics: PropTypes.func.isRequired,
--- a/devtools/client/netmonitor/src/constants.js
+++ b/devtools/client/netmonitor/src/constants.js
@@ -88,28 +88,16 @@ const EVENTS = {
   UPDATING_EVENT_TIMINGS: "NetMonitor:NetworkEventUpdating:EventTimings",
   RECEIVED_EVENT_TIMINGS: "NetMonitor:NetworkEventUpdated:EventTimings",
 
   // When response content begins, updates and finishes receiving.
   STARTED_RECEIVING_RESPONSE: "NetMonitor:NetworkEventUpdating:ResponseStart",
   UPDATING_RESPONSE_CONTENT: "NetMonitor:NetworkEventUpdating:ResponseContent",
   RECEIVED_RESPONSE_CONTENT: "NetMonitor:NetworkEventUpdated:ResponseContent",
 
-  // When the request post params are displayed in the UI.
-  REQUEST_POST_PARAMS_DISPLAYED: "NetMonitor:RequestPostParamsAvailable",
-
-  // When the image response thumbnail is displayed in the UI.
-  RESPONSE_IMAGE_THUMBNAIL_DISPLAYED:
-    "NetMonitor:ResponseImageThumbnailAvailable",
-
-  // Fired when charts have been displayed in the PerformanceStatisticsView.
-  PLACEHOLDER_CHARTS_DISPLAYED: "NetMonitor:PlaceholderChartsDisplayed",
-  PRIMED_CACHE_CHART_DISPLAYED: "NetMonitor:PrimedChartsDisplayed",
-  EMPTY_CACHE_CHART_DISPLAYED: "NetMonitor:EmptyChartsDisplayed",
-
   // Fired once the NetMonitorController establishes a connection to the debug
   // target.
   CONNECTED: "connected",
 };
 
 const HEADERS = [
   {
     name: "status",
@@ -118,26 +106,24 @@ const HEADERS = [
     filterKey: "status-code"
   },
   {
     name: "method",
     canFilter: true,
   },
   {
     name: "file",
-    boxName: "icon-and-file",
     canFilter: false,
   },
   {
     name: "protocol",
     canFilter: true,
   },
   {
     name: "domain",
-    boxName: "security-and-domain",
     canFilter: true,
   },
   {
     name: "remoteip",
     canFilter: true,
     filterKey: "remote-ip",
   },
   {
@@ -159,19 +145,36 @@ const HEADERS = [
     canFilter: true,
   },
   {
     name: "waterfall",
     canFilter: false,
   }
 ];
 
+const REQUESTS_WATERFALL = {
+  BACKGROUND_TICKS_MULTIPLE: 5, // ms
+  BACKGROUND_TICKS_SCALES: 3,
+  BACKGROUND_TICKS_SPACING_MIN: 10, // px
+  BACKGROUND_TICKS_COLOR_RGB: [128, 136, 144],
+  // 8-bit value of the alpha component of the tick color
+  BACKGROUND_TICKS_OPACITY_MIN: 32,
+  BACKGROUND_TICKS_OPACITY_ADD: 32,
+  // RGBA colors for the timing markers
+  DOMCONTENTLOADED_TICKS_COLOR_RGBA: [0, 0, 255, 128],
+  HEADER_TICKS_MULTIPLE: 5, // ms
+  HEADER_TICKS_SPACING_MIN: 60, // px
+  LOAD_TICKS_COLOR_RGBA: [255, 0, 0, 128],
+  // Reserve extra space for rendering waterfall time label
+  LABEL_WIDTH: 50, // px
+};
+
 const general = {
   ACTIVITY_TYPE,
   EVENTS,
   FILTER_SEARCH_DELAY: 200,
   HEADERS,
-  // 100 KB in bytes
-  SOURCE_SYNTAX_HIGHLIGHT_MAX_FILE_SIZE: 102400,
+  SOURCE_EDITOR_SYNTAX_HIGHLIGHT_MAX_SIZE: 51200, // 50 KB in bytes
+  REQUESTS_WATERFALL,
 };
 
 // flatten constants
 module.exports = Object.assign({}, general, actionTypes);
--- a/devtools/client/netmonitor/src/netmonitor-controller.js
+++ b/devtools/client/netmonitor/src/netmonitor-controller.js
@@ -2,20 +2,17 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 const { TimelineFront } = require("devtools/shared/fronts/timeline");
 const { CurlUtils } = require("devtools/client/shared/curl");
 const { ACTIVITY_TYPE, EVENTS } = require("./constants");
-const {
-  getRequestById,
-  getDisplayedRequestById,
-} = require("./selectors/index");
+const { getDisplayedRequestById } = require("./selectors/index");
 const {
   fetchHeaders,
   formDataURI,
 } = require("./utils/request-utils");
 const {
   getLongString,
   getWebConsoleClient,
   onFirefoxConnect,
@@ -302,17 +299,16 @@ function NetworkEventsHandler() {
   this._onNetworkEvent = this._onNetworkEvent.bind(this);
   this._onNetworkEventUpdate = this._onNetworkEventUpdate.bind(this);
   this._onDocLoadingMarker = this._onDocLoadingMarker.bind(this);
   this._onRequestHeaders = this._onRequestHeaders.bind(this);
   this._onRequestCookies = this._onRequestCookies.bind(this);
   this._onRequestPostData = this._onRequestPostData.bind(this);
   this._onResponseHeaders = this._onResponseHeaders.bind(this);
   this._onResponseCookies = this._onResponseCookies.bind(this);
-  this._onResponseContent = this._onResponseContent.bind(this);
   this._onSecurityInfo = this._onSecurityInfo.bind(this);
   this._onEventTimings = this._onEventTimings.bind(this);
 }
 
 NetworkEventsHandler.prototype = {
   get client() {
     return NetMonitorController._target.client;
   },
@@ -423,125 +419,151 @@ NetworkEventsHandler.prototype = {
         fromCache,
         fromServiceWorker,
       },
       true
     )
     .then(() => window.emit(EVENTS.REQUEST_ADDED, id));
   },
 
-  async updateRequest(id, data) {
-    await this.actions.updateRequest(id, data, true);
-    let {
-      responseContent,
-      responseCookies,
-      responseHeaders,
-      requestCookies,
-      requestHeaders,
-      requestPostData,
-    } = data;
-    let request = getRequestById(window.gStore.getState(), id);
-
-    if (requestHeaders && requestHeaders.headers && requestHeaders.headers.length) {
-      let headers = await fetchHeaders(requestHeaders, getLongString);
-      if (headers) {
-        await this.actions.updateRequest(
-          id,
-          { requestHeaders: headers },
-          true,
-        );
-      }
-    }
-
-    if (responseHeaders && responseHeaders.headers && responseHeaders.headers.length) {
-      let headers = await fetchHeaders(responseHeaders, getLongString);
-      if (headers) {
-        await this.actions.updateRequest(
-          id,
-          { responseHeaders: headers },
-          true,
-        );
-      }
-    }
-
-    if (request && responseContent && responseContent.content) {
-      let { mimeType } = request;
-      let { text, encoding } = responseContent.content;
+  async fetchImage(mimeType, responseContent) {
+    let payload = {};
+    if (mimeType && responseContent && responseContent.content) {
+      let { encoding, text } = responseContent.content;
       let response = await getLongString(text);
-      let payload = {};
 
       if (mimeType.includes("image/")) {
         payload.responseContentDataUri = formDataURI(mimeType, encoding, response);
       }
 
       responseContent.content.text = response;
       payload.responseContent = responseContent;
-
-      await this.actions.updateRequest(id, payload, true);
+    }
+    return payload;
+  },
 
-      if (mimeType.includes("image/")) {
-        window.emit(EVENTS.RESPONSE_IMAGE_THUMBNAIL_DISPLAYED);
+  async fetchRequestHeaders(requestHeaders) {
+    let payload = {};
+    if (requestHeaders && requestHeaders.headers && requestHeaders.headers.length) {
+      let headers = await fetchHeaders(requestHeaders, getLongString);
+      if (headers) {
+        payload.requestHeaders = headers;
       }
     }
+    return payload;
+  },
 
-    // Search the POST data upload stream for request headers and add
-    // them as a separate property, different from the classic headers.
+  async fetchResponseHeaders(responseHeaders) {
+    let payload = {};
+    if (responseHeaders && responseHeaders.headers && responseHeaders.headers.length) {
+      let headers = await fetchHeaders(responseHeaders, getLongString);
+      if (headers) {
+        payload.responseHeaders = headers;
+      }
+    }
+    return payload;
+  },
+
+  // Search the POST data upload stream for request headers and add
+  // them as a separate property, different from the classic headers.
+  async fetchPostData(requestPostData) {
+    let payload = {};
     if (requestPostData && requestPostData.postData) {
       let { text } = requestPostData.postData;
       let postData = await getLongString(text);
       const headers = CurlUtils.getHeadersFromMultipartText(postData);
       const headersSize = headers.reduce((acc, { name, value }) => {
         return acc + name.length + value.length + 2;
       }, 0);
-      let payload = {};
       requestPostData.postData.text = postData;
       payload.requestPostData = Object.assign({}, requestPostData);
       payload.requestHeadersFromUploadStream = { headers, headersSize };
-
-      await this.actions.updateRequest(id, payload, true);
     }
+    return payload;
+  },
 
-    // Fetch request and response cookies long value.
-    // Actor does not provide full sized cookie value when the value is too long
-    // To display values correctly, we need fetch them in each request.
+  async fetchResponseCookies(responseCookies) {
+    let payload = {};
+    if (responseCookies) {
+      let resCookies = [];
+      // response store cookies in responseCookies or responseCookies.cookies
+      let cookies = responseCookies.cookies ?
+        responseCookies.cookies : responseCookies;
+      // make sure cookies is iterable
+      if (typeof cookies[Symbol.iterator] === "function") {
+        for (let cookie of cookies) {
+          resCookies.push(Object.assign({}, cookie, {
+            value: await getLongString(cookie.value),
+          }));
+        }
+        if (resCookies.length) {
+          payload.responseCookies = resCookies;
+        }
+      }
+    }
+    return payload;
+  },
+
+  // Fetch request and response cookies long value.
+  // Actor does not provide full sized cookie value when the value is too long
+  // To display values correctly, we need fetch them in each request.
+  async fetchRequestCookies(requestCookies) {
+    let payload = {};
     if (requestCookies) {
       let reqCookies = [];
       // request store cookies in requestCookies or requestCookies.cookies
       let cookies = requestCookies.cookies ?
         requestCookies.cookies : requestCookies;
       // make sure cookies is iterable
       if (typeof cookies[Symbol.iterator] === "function") {
         for (let cookie of cookies) {
           reqCookies.push(Object.assign({}, cookie, {
             value: await getLongString(cookie.value),
           }));
         }
         if (reqCookies.length) {
-          await this.actions.updateRequest(id, { requestCookies: reqCookies }, true);
+          payload.requestCookies = reqCookies;
         }
       }
     }
+    return payload;
+  },
 
-    if (responseCookies) {
-      let resCookies = [];
-      // response store cookies in responseCookies or responseCookies.cookies
-      let cookies = responseCookies.cookies ?
-        responseCookies.cookies : responseCookies;
-      // make sure cookies is iterable
-      if (typeof cookies[Symbol.iterator] === "function") {
-        for (let cookie of cookies) {
-          resCookies.push(Object.assign({}, cookie, {
-            value: await getLongString(cookie.value),
-          }));
-        }
-        if (resCookies.length) {
-          await this.actions.updateRequest(id, { responseCookies: resCookies }, true);
-        }
-      }
-    }
+  async updateRequest(id, data) {
+    let {
+      mimeType,
+      responseContent,
+      responseCookies,
+      responseHeaders,
+      requestCookies,
+      requestHeaders,
+      requestPostData,
+    } = data;
+
+    // fetch request detail contents in parallel
+    let [
+      imageObj,
+      requestHeadersObj,
+      responseHeadersObj,
+      postDataObj,
+      requestCookiesObj,
+      responseCookiesObj,
+    ] = await Promise.all([
+      this.fetchImage(mimeType, responseContent),
+      this.fetchRequestHeaders(requestHeaders),
+      this.fetchResponseHeaders(responseHeaders),
+      this.fetchPostData(requestPostData),
+      this.fetchRequestCookies(requestCookies),
+      this.fetchResponseCookies(responseCookies),
+    ]);
+
+    let payload = Object.assign({}, data,
+                                    imageObj, requestHeadersObj, responseHeadersObj,
+                                    postDataObj, requestCookiesObj, responseCookiesObj);
+    await this.actions.updateRequest(id, payload, true);
   },
 
   /**
    * The "networkEventUpdate" message type handler.
    *
    * @param string type
    *        Message type.
    * @param object packet
@@ -563,19 +585,20 @@ NetworkEventsHandler.prototype = {
       case "requestPostData":
         this.webConsoleClient.getRequestPostData(actor,
           this._onRequestPostData);
         window.emit(EVENTS.UPDATING_REQUEST_POST_DATA, actor);
         break;
       case "securityInfo":
         this.updateRequest(actor, {
           securityState: networkInfo.securityInfo,
+        }).then(() => {
+          this.webConsoleClient.getSecurityInfo(actor, this._onSecurityInfo);
+          window.emit(EVENTS.UPDATING_SECURITY_INFO, actor);
         });
-        this.webConsoleClient.getSecurityInfo(actor, this._onSecurityInfo);
-        window.emit(EVENTS.UPDATING_SECURITY_INFO, actor);
         break;
       case "responseHeaders":
         this.webConsoleClient.getResponseHeaders(actor,
           this._onResponseHeaders);
         window.emit(EVENTS.UPDATING_RESPONSE_HEADERS, actor);
         break;
       case "responseCookies":
         this.webConsoleClient.getResponseCookies(actor,
@@ -585,35 +608,36 @@ NetworkEventsHandler.prototype = {
       case "responseStart":
         this.updateRequest(actor, {
           httpVersion: networkInfo.response.httpVersion,
           remoteAddress: networkInfo.response.remoteAddress,
           remotePort: networkInfo.response.remotePort,
           status: networkInfo.response.status,
           statusText: networkInfo.response.statusText,
           headersSize: networkInfo.response.headersSize
+        }).then(() => {
+          window.emit(EVENTS.STARTED_RECEIVING_RESPONSE, actor);
         });
-        window.emit(EVENTS.STARTED_RECEIVING_RESPONSE, actor);
         break;
       case "responseContent":
-        this.updateRequest(actor, {
-          contentSize: networkInfo.response.bodySize,
-          transferredSize: networkInfo.response.transferredSize,
-          mimeType: networkInfo.response.content.mimeType
-        });
         this.webConsoleClient.getResponseContent(actor,
-          this._onResponseContent);
+          this._onResponseContent.bind(this, {
+            contentSize: networkInfo.response.bodySize,
+            transferredSize: networkInfo.response.transferredSize,
+            mimeType: networkInfo.response.content.mimeType
+          }));
         window.emit(EVENTS.UPDATING_RESPONSE_CONTENT, actor);
         break;
       case "eventTimings":
         this.updateRequest(actor, {
           totalTime: networkInfo.totalTime
+        }).then(() => {
+          this.webConsoleClient.getEventTimings(actor, this._onEventTimings);
+          window.emit(EVENTS.UPDATING_EVENT_TIMINGS, actor);
         });
-        this.webConsoleClient.getEventTimings(actor, this._onEventTimings);
-        window.emit(EVENTS.UPDATING_EVENT_TIMINGS, actor);
         break;
     }
   },
 
   /**
    * Handles additional information received for a "requestHeaders" packet.
    *
    * @param object response
@@ -695,23 +719,24 @@ NetworkEventsHandler.prototype = {
     }).then(() => {
       window.emit(EVENTS.RECEIVED_RESPONSE_COOKIES, response.from);
     });
   },
 
   /**
    * Handles additional information received for a "responseContent" packet.
    *
+   * @param object data
+   *        The message received from the server event.
    * @param object response
    *        The message received from the server.
    */
-  _onResponseContent: function (response) {
-    this.updateRequest(response.from, {
-      responseContent: response
-    }).then(() => {
+  _onResponseContent: function (data, response) {
+    let payload = Object.assign({ responseContent: response }, data);
+    this.updateRequest(response.from, payload).then(() => {
       window.emit(EVENTS.RECEIVED_RESPONSE_CONTENT, response.from);
     });
   },
 
   /**
    * Handles additional information received for a "eventTimings" packet.
    *
    * @param object response
--- a/devtools/client/netmonitor/src/reducers/ui.js
+++ b/devtools/client/netmonitor/src/reducers/ui.js
@@ -35,25 +35,22 @@ const Columns = I.Record({
 const UI = I.Record({
   columns: new Columns(),
   detailsPanelSelectedTab: "headers",
   networkDetailsOpen: false,
   statisticsOpen: false,
   waterfallWidth: null,
 });
 
-// Safe bounds for waterfall width (px)
-const REQUESTS_WATERFALL_SAFE_BOUNDS = 90;
-
 function resetColumns(state) {
   return state.set("columns", new Columns());
 }
 
 function resizeWaterfall(state, action) {
-  return state.set("waterfallWidth", action.width - REQUESTS_WATERFALL_SAFE_BOUNDS);
+  return state.set("waterfallWidth", action.width);
 }
 
 function openNetworkDetails(state, action) {
   return state.set("networkDetailsOpen", action.open);
 }
 
 function openStatistics(state, action) {
   return state.set("statisticsOpen", action.open);
--- a/devtools/client/netmonitor/src/selectors/ui.js
+++ b/devtools/client/netmonitor/src/selectors/ui.js
@@ -1,36 +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 { REQUESTS_WATERFALL } = require("../constants");
 const { getDisplayedRequests } = require("./requests");
 
 function isNetworkDetailsToggleButtonDisabled(state) {
   return getDisplayedRequests(state).isEmpty();
 }
 
 const EPSILON = 0.001;
 
 function getWaterfallScale(state) {
   const { requests, timingMarkers, ui } = state;
 
-  if (requests.firstStartedMillis == +Infinity) {
-    return null;
-  }
-
-  if (ui.waterfallWidth == null) {
+  if (requests.firstStartedMillis === +Infinity || ui.waterfallWidth === null) {
     return null;
   }
 
   const lastEventMillis = Math.max(requests.lastEndedMillis,
                                    timingMarkers.firstDocumentDOMContentLoadedTimestamp,
                                    timingMarkers.firstDocumentLoadTimestamp);
   const longestWidth = lastEventMillis - requests.firstStartedMillis;
-  return Math.min(Math.max(ui.waterfallWidth / longestWidth, EPSILON), 1);
+  return Math.min(Math.max(
+    (ui.waterfallWidth - REQUESTS_WATERFALL.LABEL_WIDTH) / longestWidth, EPSILON), 1);
 }
 
 module.exports = {
   isNetworkDetailsToggleButtonDisabled,
   getWaterfallScale,
 };
--- a/devtools/client/netmonitor/src/waterfall-background.js
+++ b/devtools/client/netmonitor/src/waterfall-background.js
@@ -1,31 +1,22 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
+const { REQUESTS_WATERFALL } = require("./constants");
+
 const HTML_NS = "http://www.w3.org/1999/xhtml";
-const REQUESTS_WATERFALL_BACKGROUND_TICKS_MULTIPLE = 5; // ms
-const REQUESTS_WATERFALL_BACKGROUND_TICKS_SCALES = 3;
-const REQUESTS_WATERFALL_BACKGROUND_TICKS_SPACING_MIN = 10; // px
-const REQUESTS_WATERFALL_BACKGROUND_TICKS_COLOR_RGB = [128, 136, 144];
-// 8-bit value of the alpha component of the tick color
-const REQUESTS_WATERFALL_BACKGROUND_TICKS_OPACITY_MIN = 32;
-const REQUESTS_WATERFALL_BACKGROUND_TICKS_OPACITY_ADD = 32;
-// RGBA colors for the timing markers
-const REQUESTS_WATERFALL_DOMCONTENTLOADED_TICKS_COLOR_RGBA = [0, 0, 255, 128];
-const REQUESTS_WATERFALL_LOAD_TICKS_COLOR_RGBA = [255, 0, 0, 128];
-
 const STATE_KEYS = [
+  "firstRequestStartedMillis",
   "scale",
+  "timingMarkers",
   "waterfallWidth",
-  "firstRequestStartedMillis",
-  "timingMarkers",
 ];
 
 /**
  * Creates the background displayed on each waterfall view in this container.
  */
 function WaterfallBackground() {
   this.canvas = document.createElementNS(HTML_NS, "canvas");
   this.ctx = this.canvas.getContext("2d");
@@ -57,76 +48,77 @@ WaterfallBackground.prototype = {
     this.prevState = state;
 
     if (state.waterfallWidth === null || state.scale === null) {
       setImageElement("waterfall-background", null);
       return;
     }
 
     // Nuke the context.
-    let canvasWidth = this.canvas.width = state.waterfallWidth;
+    let canvasWidth = this.canvas.width =
+      state.waterfallWidth - REQUESTS_WATERFALL.LABEL_WIDTH;
     // Awww yeah, 1px, repeats on Y axis.
     let canvasHeight = this.canvas.height = 1;
 
     // Start over.
     let imageData = this.ctx.createImageData(canvasWidth, canvasHeight);
     let pixelArray = imageData.data;
 
     let buf = new ArrayBuffer(pixelArray.length);
     let view8bit = new Uint8ClampedArray(buf);
     let view32bit = new Uint32Array(buf);
 
     // Build new millisecond tick lines...
-    let timingStep = REQUESTS_WATERFALL_BACKGROUND_TICKS_MULTIPLE;
+    let timingStep = REQUESTS_WATERFALL.BACKGROUND_TICKS_MULTIPLE;
     let optimalTickIntervalFound = false;
     let scaledStep;
 
     while (!optimalTickIntervalFound) {
       // Ignore any divisions that would end up being too close to each other.
       scaledStep = state.scale * timingStep;
-      if (scaledStep < REQUESTS_WATERFALL_BACKGROUND_TICKS_SPACING_MIN) {
+      if (scaledStep < REQUESTS_WATERFALL.BACKGROUND_TICKS_SPACING_MIN) {
         timingStep <<= 1;
         continue;
       }
       optimalTickIntervalFound = true;
     }
 
     const isRTL = isDocumentRTL(document);
-    const [r, g, b] = REQUESTS_WATERFALL_BACKGROUND_TICKS_COLOR_RGB;
-    let alphaComponent = REQUESTS_WATERFALL_BACKGROUND_TICKS_OPACITY_MIN;
+    const [r, g, b] = REQUESTS_WATERFALL.BACKGROUND_TICKS_COLOR_RGB;
+    let alphaComponent = REQUESTS_WATERFALL.BACKGROUND_TICKS_OPACITY_MIN;
 
     function drawPixelAt(offset, color) {
       let position = (isRTL ? canvasWidth - offset : offset - 1) | 0;
       let [rc, gc, bc, ac] = color;
       view32bit[position] = (ac << 24) | (bc << 16) | (gc << 8) | rc;
     }
 
     // Insert one pixel for each division on each scale.
-    for (let i = 1; i <= REQUESTS_WATERFALL_BACKGROUND_TICKS_SCALES; i++) {
+    for (let i = 1; i <= REQUESTS_WATERFALL.BACKGROUND_TICKS_SCALES; i++) {
       let increment = scaledStep * Math.pow(2, i);
       for (let x = 0; x < canvasWidth; x += increment) {
         drawPixelAt(x, [r, g, b, alphaComponent]);
       }
-      alphaComponent += REQUESTS_WATERFALL_BACKGROUND_TICKS_OPACITY_ADD;
+      alphaComponent += REQUESTS_WATERFALL.BACKGROUND_TICKS_OPACITY_ADD;
     }
 
     function drawTimestamp(timestamp, color) {
       if (timestamp === -1) {
         return;
       }
 
       let delta = Math.floor((timestamp - state.firstRequestStartedMillis) * state.scale);
       drawPixelAt(delta, color);
     }
 
     drawTimestamp(state.timingMarkers.firstDocumentDOMContentLoadedTimestamp,
-                  REQUESTS_WATERFALL_DOMCONTENTLOADED_TICKS_COLOR_RGBA);
+                  REQUESTS_WATERFALL.DOMCONTENTLOADED_TICKS_COLOR_RGBA);
 
     drawTimestamp(state.timingMarkers.firstDocumentLoadTimestamp,
-                  REQUESTS_WATERFALL_LOAD_TICKS_COLOR_RGBA);
+                  REQUESTS_WATERFALL.LOAD_TICKS_COLOR_RGBA);
 
     // Flush the image data and cache the waterfall background.
     pixelArray.set(view8bit);
     this.ctx.putImageData(imageData, 0, 0);
 
     setImageElement("waterfall-background", this.canvas);
   },
 
--- a/devtools/client/netmonitor/test/browser_net_icon-preview.js
+++ b/devtools/client/netmonitor/test/browser_net_icon-preview.js
@@ -4,74 +4,66 @@
 "use strict";
 
 /**
  * Tests if image responses show a thumbnail in the requests menu.
  */
 
 add_task(function* () {
   let { tab, monitor } = yield initNetMonitor(CONTENT_TYPE_WITHOUT_CACHE_URL);
+  const SELECTOR = ".requests-list-icon[src]";
   info("Starting test... ");
 
   let { document, gStore, windowRequire } = monitor.panelWin;
   let Actions = windowRequire("devtools/client/netmonitor/src/actions/index");
   let { NetMonitorController } =
     windowRequire("devtools/client/netmonitor/src/netmonitor-controller");
-  let {
-    ACTIVITY_TYPE,
-    EVENTS,
-  } = windowRequire("devtools/client/netmonitor/src/constants");
+  let { ACTIVITY_TYPE } = windowRequire("devtools/client/netmonitor/src/constants");
 
   gStore.dispatch(Actions.batchEnable(false));
 
-  let wait = waitForEvents();
+  let wait = waitForNetworkEvents(monitor, CONTENT_TYPE_WITHOUT_CACHE_REQUESTS);
   yield performRequests();
   yield wait;
+  yield waitUntil(() => !!document.querySelector(SELECTOR));
 
   info("Checking the image thumbnail when all items are shown.");
   checkImageThumbnail();
 
   gStore.dispatch(Actions.sortBy("contentSize"));
   info("Checking the image thumbnail when all items are sorted.");
   checkImageThumbnail();
 
   gStore.dispatch(Actions.toggleRequestFilterType("images"));
   info("Checking the image thumbnail when only images are shown.");
   checkImageThumbnail();
 
   info("Reloading the debuggee and performing all requests again...");
-  wait = waitForEvents();
+  wait = waitForNetworkEvents(monitor, CONTENT_TYPE_WITHOUT_CACHE_REQUESTS);
   yield reloadAndPerformRequests();
   yield wait;
+  yield waitUntil(() => !!document.querySelector(SELECTOR));
 
   info("Checking the image thumbnail after a reload.");
   checkImageThumbnail();
 
   yield teardown(monitor);
 
-  function waitForEvents() {
-    return promise.all([
-      waitForNetworkEvents(monitor, CONTENT_TYPE_WITHOUT_CACHE_REQUESTS),
-      monitor.panelWin.once(EVENTS.RESPONSE_IMAGE_THUMBNAIL_DISPLAYED)
-    ]);
-  }
-
   function performRequests() {
     return ContentTask.spawn(tab.linkedBrowser, {}, function* () {
       content.wrappedJSObject.performRequests();
     });
   }
 
   function* reloadAndPerformRequests() {
     yield NetMonitorController.triggerActivity(ACTIVITY_TYPE.RELOAD.WITH_CACHE_ENABLED);
     yield performRequests();
   }
 
   function checkImageThumbnail() {
-    is(document.querySelectorAll(".requests-list-icon[data-type=thumbnail]").length, 1,
+    is(document.querySelectorAll(SELECTOR).length, 1,
       "There should be only one image request with a thumbnail displayed.");
-    is(document.querySelector(".requests-list-icon[data-type=thumbnail]").src,
-      TEST_IMAGE_DATA_URI,
+    is(document.querySelector(SELECTOR).src, TEST_IMAGE_DATA_URI,
       "The image requests-list-icon thumbnail is displayed correctly.");
-    is(document.querySelector(".requests-list-icon[data-type=thumbnail]").hidden, false,
+    is(document.querySelector(SELECTOR).hidden, false,
       "The image requests-list-icon thumbnail should not be hidden.");
   }
 });
--- a/devtools/client/netmonitor/test/browser_net_image-tooltip.js
+++ b/devtools/client/netmonitor/test/browser_net_image-tooltip.js
@@ -6,53 +6,49 @@
 const IMAGE_TOOLTIP_URL = EXAMPLE_URL + "html_image-tooltip-test-page.html";
 const IMAGE_TOOLTIP_REQUESTS = 1;
 
 /**
  * Tests if image responses show a popup in the requests menu when hovered.
  */
 add_task(function* test() {
   let { tab, monitor } = yield initNetMonitor(IMAGE_TOOLTIP_URL);
+  const SELECTOR = ".requests-list-icon[src]";
   info("Starting test... ");
 
   let { document, gStore, windowRequire } = monitor.panelWin;
   let Actions = windowRequire("devtools/client/netmonitor/src/actions/index");
   let { NetMonitorController } =
     windowRequire("devtools/client/netmonitor/src/netmonitor-controller");
-  let {
-    ACTIVITY_TYPE,
-    EVENTS,
-  } = windowRequire("devtools/client/netmonitor/src/constants");
+  let { ACTIVITY_TYPE } = windowRequire("devtools/client/netmonitor/src/constants");
   let toolboxDoc = monitor.panelWin.parent.document;
 
   gStore.dispatch(Actions.batchEnable(false));
 
   let onEvents = waitForNetworkEvents(monitor, IMAGE_TOOLTIP_REQUESTS);
-  let onThumbnail = monitor.panelWin.once(EVENTS.RESPONSE_IMAGE_THUMBNAIL_DISPLAYED);
   yield performRequests();
   yield onEvents;
-  yield onThumbnail;
+  yield waitUntil(() => !!document.querySelector(SELECTOR));
 
   info("Checking the image thumbnail after a few requests were made...");
   yield showTooltipAndVerify(document.querySelectorAll(".request-list-item")[0]);
 
   // Hide tooltip before next test, to avoid the situation that tooltip covers
   // the icon for the request of the next test.
   info("Checking the image thumbnail gets hidden...");
   yield hideTooltipAndVerify(document.querySelectorAll(".request-list-item")[0]);
 
   // +1 extra document reload
   onEvents = waitForNetworkEvents(monitor, IMAGE_TOOLTIP_REQUESTS + 1);
-  onThumbnail = monitor.panelWin.once(EVENTS.RESPONSE_IMAGE_THUMBNAIL_DISPLAYED);
 
   info("Reloading the debuggee and performing all requests again...");
   yield NetMonitorController.triggerActivity(ACTIVITY_TYPE.RELOAD.WITH_CACHE_ENABLED);
   yield performRequests();
   yield onEvents;
-  yield onThumbnail;
+  yield waitUntil(() => !!document.querySelector(SELECTOR));
 
   info("Checking the image thumbnail after a reload.");
   yield showTooltipAndVerify(document.querySelectorAll(".request-list-item")[1]);
 
   info("Checking if the image thumbnail is hidden when mouse leaves the menu widget");
   let requestsListContents = document.querySelector(".requests-list-contents");
   EventUtils.synthesizeMouse(requestsListContents, 0, 0, { type: "mouseout" },
                              monitor.panelWin);
@@ -66,17 +62,17 @@ add_task(function* test() {
     });
   }
 
   /**
    * Show a tooltip on the {target} and verify that it was displayed
    * with the expected content.
    */
   function* showTooltipAndVerify(target) {
-    let anchor = target.querySelector(".requests-list-file");
+    let anchor = target.querySelector(".requests-list-icon");
     yield showTooltipOn(anchor);
 
     info("Tooltip was successfully opened for the image request.");
     is(toolboxDoc.querySelector(".tooltip-panel img").src, TEST_IMAGE_DATA_URI,
       "The tooltip's image content is displayed correctly.");
   }
 
   /**
--- a/devtools/client/netmonitor/test/browser_net_security-state.js
+++ b/devtools/client/netmonitor/test/browser_net_security-state.js
@@ -20,17 +20,17 @@ add_task(function* () {
   let { document, gStore, windowRequire } = monitor.panelWin;
   let Actions = windowRequire("devtools/client/netmonitor/src/actions/index");
 
   gStore.dispatch(Actions.batchEnable(false));
 
   yield performRequests();
 
   for (let subitemNode of Array.from(document.querySelectorAll(
-    "requests-list-subitem.requests-list-security-and-domain"))) {
+    "requests-list-column.requests-list-security-and-domain"))) {
     let domain = subitemNode.querySelector(".requests-list-domain").textContent;
 
     info("Found a request to " + domain);
     ok(domain in EXPECTED_SECURITY_STATES, "Domain " + domain + " was expected.");
 
     let classes = subitemNode.querySelector(".requests-security-state-icon").classList;
     let expectedClass = EXPECTED_SECURITY_STATES[domain];
 
--- a/devtools/client/netmonitor/test/browser_net_simple-request-data.js
+++ b/devtools/client/netmonitor/test/browser_net_simple-request-data.js
@@ -188,44 +188,26 @@ function test() {
         SIMPLE_SJS,
         {
           status: "200",
           statusText: "Och Aye"
         }
       );
     });
 
-    monitor.panelWin.once(EVENTS.UPDATING_RESPONSE_CONTENT, () => {
+    monitor.panelWin.once(EVENTS.RECEIVED_RESPONSE_CONTENT, () => {
       let requestItem = getSortedRequests(gStore.getState()).get(0);
 
       is(requestItem.transferredSize, "12",
         "The transferredSize data has an incorrect value.");
       is(requestItem.contentSize, "12",
         "The contentSize data has an incorrect value.");
       is(requestItem.mimeType, "text/plain; charset=utf-8",
         "The mimeType data has an incorrect value.");
 
-      verifyRequestItemTarget(
-        document,
-        getDisplayedRequests(gStore.getState()),
-        requestItem,
-        "GET",
-        SIMPLE_SJS,
-        {
-          type: "plain",
-          fullMimeType: "text/plain; charset=utf-8",
-          transferred: L10N.getFormatStrWithNumbers("networkMenu.sizeB", 12),
-          size: L10N.getFormatStrWithNumbers("networkMenu.sizeB", 12),
-        }
-      );
-    });
-
-    monitor.panelWin.once(EVENTS.RECEIVED_RESPONSE_CONTENT, () => {
-      let requestItem = getSortedRequests(gStore.getState()).get(0);
-
       ok(requestItem.responseContent,
         "There should be a responseContent data available.");
       // eslint-disable-next-line mozilla/no-cpows-in-tests
       is(requestItem.responseContent.content.mimeType,
         "text/plain; charset=utf-8",
         "The responseContent data has an incorrect |content.mimeType| property.");
       // eslint-disable-next-line mozilla/no-cpows-in-tests
       is(requestItem.responseContent.content.text,
--- a/devtools/client/netmonitor/test/browser_net_timing-division.js
+++ b/devtools/client/netmonitor/test/browser_net_timing-division.js
@@ -3,16 +3,23 @@
 
 "use strict";
 
 /**
  * Tests if timing intervals are divided againts seconds when appropriate.
  */
 
 add_task(function* () {
+  // Hide file, protocol, remoteip columns to make sure timing division
+  // can render properly
+  Services.prefs.setCharPref(
+    "devtools.netmonitor.hiddenColumns",
+    "[\"file\",\"protocol\",\"remoteip\"]"
+  );
+
   let { tab, monitor } = yield initNetMonitor(CUSTOM_GET_URL);
   info("Starting test... ");
 
   let { document, gStore, windowRequire } = monitor.panelWin;
   let Actions = windowRequire("devtools/client/netmonitor/src/actions/index");
   let {
     getSortedRequests,
   } = windowRequire("devtools/client/netmonitor/src/selectors/index");
--- a/devtools/client/netmonitor/test/head.js
+++ b/devtools/client/netmonitor/test/head.js
@@ -81,16 +81,19 @@ const gEnableLogging = Services.prefs.ge
 Services.prefs.setBoolPref("devtools.debugger.log", false);
 
 // Uncomment this pref to dump all devtools emitted events to the console.
 // Services.prefs.setBoolPref("devtools.dump.emit", true);
 
 // Always reset some prefs to their original values after the test finishes.
 const gDefaultFilters = Services.prefs.getCharPref("devtools.netmonitor.filters");
 
+// Reveal all hidden columns for test
+Services.prefs.setCharPref("devtools.netmonitor.hiddenColumns", "[]");
+
 registerCleanupFunction(() => {
   info("finish() was called, cleaning up...");
 
   Services.prefs.setBoolPref("devtools.debugger.log", gEnableLogging);
   Services.prefs.setCharPref("devtools.netmonitor.filters", gDefaultFilters);
   Services.prefs.clearUserPref("devtools.cache.disabled");
 });
 
@@ -369,17 +372,18 @@ function verifyRequestItemTarget(documen
   let { fuzzyUrl, status, statusText, cause, type, fullMimeType,
         transferred, size, time, displayedStatus } = data;
 
   let target = document.querySelectorAll(".request-list-item")[visibleIndex];
   let unicodeUrl = decodeUnicodeUrl(url);
   let name = getUrlBaseName(url);
   let query = getUrlQuery(url);
   let hostPort = getUrlHost(url);
-  let remoteAddress = requestItem.remoteAddress;
+  let { httpVersion = "", remoteAddress, remotePort } = requestItem;
+  let remoteIP = remoteAddress ? `${remoteAddress}:${remotePort}` : "unknown";
 
   if (fuzzyUrl) {
     ok(requestItem.method.startsWith(method), "The attached method is correct.");
     ok(requestItem.url.startsWith(url), "The attached url is correct.");
   } else {
     is(requestItem.method, method, "The attached method is correct.");
     is(requestItem.url, url, "The attached url is correct.");
   }
@@ -395,43 +399,56 @@ function verifyRequestItemTarget(documen
       "The tooltip file is correct.");
   } else {
     is(target.querySelector(".requests-list-file").textContent,
       name + (query ? "?" + query : ""), "The displayed file is correct.");
     is(target.querySelector(".requests-list-file").getAttribute("title"),
       unicodeUrl, "The tooltip file is correct.");
   }
 
+  is(target.querySelector(".requests-list-protocol").textContent,
+    httpVersion, "The displayed protocol is correct.");
+
+  is(target.querySelector(".requests-list-protocol").getAttribute("title"),
+    httpVersion, "The tooltip protocol is correct.");
+
   is(target.querySelector(".requests-list-domain").textContent,
     hostPort, "The displayed domain is correct.");
 
   let domainTooltip = hostPort + (remoteAddress ? " (" + remoteAddress + ")" : "");
   is(target.querySelector(".requests-list-domain").getAttribute("title"),
     domainTooltip, "The tooltip domain is correct.");
 
+  is(target.querySelector(".requests-list-remoteip").textContent,
+    remoteIP, "The displayed remote IP is correct.");
+
+  is(target.querySelector(".requests-list-remoteip").getAttribute("title"),
+    remoteIP, "The tooltip remote IP is correct.");
+
   if (status !== undefined) {
     let value = target.querySelector(".requests-list-status-icon")
                       .getAttribute("data-code");
     let codeValue = target.querySelector(".requests-list-status-code").textContent;
     let tooltip = target.querySelector(".requests-list-status").getAttribute("title");
     info("Displayed status: " + value);
     info("Displayed code: " + codeValue);
     info("Tooltip status: " + tooltip);
     is(value, displayedStatus ? displayedStatus : status,
       "The displayed status is correct.");
     is(codeValue, status, "The displayed status code is correct.");
     is(tooltip, status + " " + statusText, "The tooltip status is correct.");
   }
   if (cause !== undefined) {
-    let value = target.querySelector(".requests-list-cause > .subitem-label").textContent;
+    let value = Array.from(target.querySelector(".requests-list-cause").childNodes)
+      .filter((node) => node.nodeType === Node.TEXT_NODE)[0].textContent;
     let tooltip = target.querySelector(".requests-list-cause").getAttribute("title");
     info("Displayed cause: " + value);
     info("Tooltip cause: " + tooltip);
     is(value, cause.type, "The displayed cause is correct.");
-    is(tooltip, cause.loadingDocumentUri, "The tooltip cause is correct.");
+    is(tooltip, cause.type, "The tooltip cause is correct.");
   }
   if (type !== undefined) {
     let value = target.querySelector(".requests-list-type").textContent;
     let tooltip = target.querySelector(".requests-list-type").getAttribute("title");
     info("Displayed type: " + value);
     info("Tooltip type: " + tooltip);
     is(value, type, "The displayed type is correct.");
     is(tooltip, fullMimeType, "The tooltip type is correct.");
--- a/devtools/client/shared/components/notification-box.js
+++ b/devtools/client/shared/components/notification-box.js
@@ -24,17 +24,17 @@ const PriorityLevels = {
   PRIORITY_CRITICAL_LOW: 7,
   PRIORITY_CRITICAL_MEDIUM: 8,
   PRIORITY_CRITICAL_HIGH: 9,
   PRIORITY_CRITICAL_BLOCK: 10,
 };
 
 /**
  * This component represents Notification Box - HTML alternative for
- * <xul:notifictionbox> binding.
+ * <xul:notificationbox> binding.
  *
  * See also MDN for more info about <xul:notificationbox>:
  * https://developer.mozilla.org/en-US/docs/Mozilla/Tech/XUL/notificationbox
  */
 var NotificationBox = createClass({
   displayName: "NotificationBox",
 
   propTypes: {
--- a/devtools/client/themes/webconsole.css
+++ b/devtools/client/themes/webconsole.css
@@ -795,16 +795,27 @@ a.learn-more-link.webconsole-learn-more-
   margin-inline-end: 5px;
 }
 
 .message.startGroup .icon,
 .message.startGroupCollapsed .icon {
   display: none;
 }
 
+/*
+ * Open DOMNode in inspector button. Style need to be reset in the new
+ * console since its style is already defined in reps.css .
+ */
+.webconsole-output-wrapper .open-inspector {
+  background: unset;
+  padding-left: unset;
+  margin-left: unset;
+  cursor: unset;
+}
+
 /* console.table() */
 .new-consoletable {
   width: 100%;
   border-collapse: collapse;
   --consoletable-border: 1px solid var(--table-splitter-color);
 }
 
 .new-consoletable thead,
--- a/devtools/client/webconsole/new-console-output/components/grip-message-body.js
+++ b/devtools/client/webconsole/new-console-output/components/grip-message-body.js
@@ -56,19 +56,25 @@ function GripMessageBody(props) {
 
   let styleObject;
   if (userProvidedStyle && userProvidedStyle !== "") {
     styleObject = cleanupStyle(userProvidedStyle, serviceContainer.createElement);
   }
 
   let onDOMNodeMouseOver;
   let onDOMNodeMouseOut;
+  let onInspectIconClick;
   if (serviceContainer) {
-    onDOMNodeMouseOver = (object) => serviceContainer.highlightDomElement(object);
+    onDOMNodeMouseOver = serviceContainer.highlightDomElement
+      ? (object) => serviceContainer.highlightDomElement(object)
+      : null;
     onDOMNodeMouseOut = serviceContainer.unHighlightDomElement;
+    onInspectIconClick = serviceContainer.openNodeInInspector
+      ? (object) => serviceContainer.openNodeInInspector(object)
+      : null;
   }
 
   return (
     // @TODO once there is a longString rep, also turn off quotes for those.
     typeof grip === "string"
       ? StringRep({
         object: grip,
         useQuotes: useQuotes,
@@ -76,16 +82,17 @@ function GripMessageBody(props) {
         mode: props.mode,
         style: styleObject
       })
       : Rep({
         object: grip,
         objectLink: VariablesViewLink,
         onDOMNodeMouseOver,
         onDOMNodeMouseOut,
+        onInspectIconClick,
         defaultRep: Grip,
         mode: props.mode,
       })
   );
 }
 
 function cleanupStyle(userProvidedStyle, createElement) {
   // Regular expression that matches the allowed CSS property names.
--- a/devtools/client/webconsole/new-console-output/components/message-types/network-event-message.js
+++ b/devtools/client/webconsole/new-console-output/components/message-types/network-event-message.js
@@ -52,19 +52,19 @@ function NetworkEventMessage({
 
   const topLevelClasses = [ "cm-s-mozilla" ];
   let statusInfo;
 
   if (httpVersion && status && statusText && totalTime !== undefined) {
     statusInfo = `[${httpVersion} ${status} ${statusText} ${totalTime}ms]`;
   }
 
-  function openNetworkMonitor() {
-    serviceContainer.openNetworkPanel(actor);
-  }
+  const openNetworkMonitor = serviceContainer.openNetworkPanel
+    ? () => serviceContainer.openNetworkPanel(actor)
+    : null;
 
   const method = dom.span({className: "method" }, request.method);
   const xhr = isXHR
     ? dom.span({ className: "xhr" }, l10n.getStr("webConsoleXhrIndicator"))
     : null;
   const url = dom.a({ className: "url", title: request.url, onClick: openNetworkMonitor },
     request.url.replace(/\?.+/, ""));
   const statusBody = statusInfo
--- a/devtools/client/webconsole/new-console-output/components/message.js
+++ b/devtools/client/webconsole/new-console-output/components/message.js
@@ -44,19 +44,19 @@ const Message = createClass({
     scrollToMessage: PropTypes.bool,
     exceptionDocURL: PropTypes.string,
     parameters: PropTypes.object,
     request: PropTypes.object,
     dispatch: PropTypes.func,
     timeStamp: PropTypes.number,
     serviceContainer: PropTypes.shape({
       emitNewMessage: PropTypes.func.isRequired,
-      onViewSourceInDebugger: PropTypes.func.isRequired,
-      onViewSourceInScratchpad: PropTypes.func.isRequired,
-      onViewSourceInStyleEditor: PropTypes.func.isRequired,
+      onViewSourceInDebugger: PropTypes.func,
+      onViewSourceInScratchpad: PropTypes.func,
+      onViewSourceInStyleEditor: PropTypes.func,
       openContextMenu: PropTypes.func.isRequired,
       openLink: PropTypes.func.isRequired,
       sourceMapService: PropTypes.any,
     }),
     notes: PropTypes.arrayOf(PropTypes.shape({
       messageBody: PropTypes.string.isRequired,
       frame: PropTypes.any,
     })),
--- a/devtools/client/webconsole/new-console-output/new-console-output-wrapper.js
+++ b/devtools/client/webconsole/new-console-output/new-console-output-wrapper.js
@@ -33,81 +33,100 @@ function NewConsoleOutputWrapper(parentN
 }
 
 NewConsoleOutputWrapper.prototype = {
   init: function () {
     const attachRefToHud = (id, node) => {
       this.jsterm.hud[id] = node;
     };
 
-    let childComponent = ConsoleOutput({
-      serviceContainer: {
-        attachRefToHud,
-        emitNewMessage: (node, messageId) => {
-          this.jsterm.hud.emit("new-messages", new Set([{
-            node,
-            messageId,
-          }]));
-        },
-        hudProxyClient: this.jsterm.hud.proxy.client,
+    const serviceContainer = {
+      attachRefToHud,
+      emitNewMessage: (node, messageId) => {
+        this.jsterm.hud.emit("new-messages", new Set([{
+          node,
+          messageId,
+        }]));
+      },
+      hudProxyClient: this.jsterm.hud.proxy.client,
+      openContextMenu: (e, message) => {
+        let { screenX, screenY, target } = e;
+
+        let messageEl = target.closest(".message");
+        let clipboardText = messageEl ? messageEl.textContent : null;
+
+        // Retrieve closes actor id from the DOM.
+        let actorEl = target.closest("[data-link-actor-id]");
+        let actor = actorEl ? actorEl.dataset.linkActorId : null;
+
+        let menu = createContextMenu(this.jsterm, this.parentNode,
+          { actor, clipboardText, message });
+
+        // Emit the "menu-open" event for testing.
+        menu.once("open", () => this.emit("menu-open"));
+        menu.popup(screenX, screenY, this.toolbox);
+
+        return menu;
+      },
+      openLink: url => this.jsterm.hud.owner.openLink(url),
+      createElement: nodename => {
+        return this.document.createElementNS("http://www.w3.org/1999/xhtml", nodename);
+      },
+    };
+
+    if (this.toolbox) {
+      Object.assign(serviceContainer, {
         onViewSourceInDebugger: frame => {
           this.toolbox.viewSourceInDebugger(frame.url, frame.line).then(() =>
             this.jsterm.hud.emit("source-in-debugger-opened")
           );
         },
         onViewSourceInScratchpad: frame => this.toolbox.viewSourceInScratchpad(
           frame.url,
           frame.line
         ),
         onViewSourceInStyleEditor: frame => this.toolbox.viewSourceInStyleEditor(
           frame.url,
           frame.line
         ),
-        openContextMenu: (e, message) => {
-          let { screenX, screenY, target } = e;
-
-          let messageEl = target.closest(".message");
-          let clipboardText = messageEl ? messageEl.textContent : null;
-
-          // Retrieve closes actor id from the DOM.
-          let actorEl = target.closest("[data-link-actor-id]");
-          let actor = actorEl ? actorEl.dataset.linkActorId : null;
-
-          let menu = createContextMenu(this.jsterm, this.parentNode,
-            { actor, clipboardText, message });
-
-          // Emit the "menu-open" event for testing.
-          menu.once("open", () => this.emit("menu-open"));
-          menu.popup(screenX, screenY, this.toolbox);
-
-          return menu;
-        },
         openNetworkPanel: (requestId) => {
           return this.toolbox.selectTool("netmonitor").then(panel => {
             return panel.panelWin.NetMonitorController.inspectRequest(requestId);
           });
         },
         sourceMapService:
           this.toolbox ? this.toolbox._deprecatedServerSourceMapService : null,
-        openLink: url => this.jsterm.hud.owner.openLink(url),
-        createElement: nodename => {
-          return this.document.createElementNS("http://www.w3.org/1999/xhtml", nodename);
-        },
         highlightDomElement: (grip, options = {}) => {
-          return this.toolbox && this.toolbox.highlighterUtils
+          return this.toolbox.highlighterUtils
             ? this.toolbox.highlighterUtils.highlightDomValueGrip(grip, options)
             : null;
         },
         unHighlightDomElement: (forceHide = false) => {
-          return this.toolbox && this.toolbox.highlighterUtils
+          return this.toolbox.highlighterUtils
             ? this.toolbox.highlighterUtils.unhighlight(forceHide)
             : null;
         },
-      }
-    });
+        openNodeInInspector: async (grip) => {
+          let onSelectInspector = this.toolbox.selectTool("inspector");
+          let onGripNodeToFront = this.toolbox.highlighterUtils.gripToNodeFront(grip);
+          let [
+            front,
+            inspector
+          ] = await Promise.all([onGripNodeToFront, onSelectInspector]);
+
+          let onInspectorUpdated = inspector.once("inspector-updated");
+          let onNodeFrontSet = this.toolbox.selection.setNodeFront(front, "console");
+
+          return Promise.all([onNodeFrontSet, onInspectorUpdated]);
+        }
+      });
+    }
+
+    let childComponent = ConsoleOutput({serviceContainer});
+
     let filterBar = FilterBar({
       serviceContainer: {
         attachRefToHud
       }
     });
 
     let provider = React.createElement(
       Provider,
--- a/devtools/client/webconsole/new-console-output/test/mochitest/browser.ini
+++ b/devtools/client/webconsole/new-console-output/test/mochitest/browser.ini
@@ -33,14 +33,15 @@ skip-if = (os == 'linux' && bits == 32 &
 [browser_webconsole_filters_persist.js]
 [browser_webconsole_init.js]
 [browser_webconsole_input_focus.js]
 [browser_webconsole_keyboard_accessibility.js]
 [browser_webconsole_location_debugger_link.js]
 [browser_webconsole_location_scratchpad_link.js]
 [browser_webconsole_location_styleeditor_link.js]
 [browser_webconsole_nodes_highlight.js]
+[browser_webconsole_nodes_select.js]
 [browser_webconsole_observer_notifications.js]
 [browser_webconsole_stacktrace_location_debugger_link.js]
 [browser_webconsole_stacktrace_location_scratchpad_link.js]
 [browser_webconsole_string.js]
 [browser_webconsole_timestamps.js]
 [browser_webconsole_vview_close_on_esc_key.js]
new file mode 100644
--- /dev/null
+++ b/devtools/client/webconsole/new-console-output/test/mochitest/browser_webconsole_nodes_select.js
@@ -0,0 +1,58 @@
+/* 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";
+
+// Check clicking on open-in-inspector icon does select the node in the inspector.
+
+const HTML = `
+  <!DOCTYPE html>
+  <html>
+    <body>
+      <h1>Select node in inspector test</h1>
+    </body>
+    <script>
+      function logNode(selector) {
+        console.log(document.querySelector(selector));
+      }
+    </script>
+  </html>
+`;
+const TEST_URI = "data:text/html;charset=utf-8," + encodeURI(HTML);
+
+add_task(async function () {
+  const hud = await openNewTabAndConsole(TEST_URI);
+  const toolbox = gDevTools.getToolbox(hud.target);
+
+  // Loading the inspector panel at first, to make it possible to listen for
+  // new node selections
+  await toolbox.loadTool("inspector");
+  const inspector = toolbox.getPanel("inspector");
+
+  await ContentTask.spawn(gBrowser.selectedBrowser, null, () => {
+    content.wrappedJSObject.logNode("h1");
+  });
+
+  let msg = await waitFor(() => findMessage(hud, "<h1>"));
+  let node = msg.querySelector(".objectBox-node");
+  ok(node !== null, "Node was logged as expected");
+
+  let openInInspectorIcon = node.querySelector(".open-inspector");
+  ok(openInInspectorIcon !== null, "The is an open in inspector icon");
+
+  info("Clicking on the inspector icon and waiting for the " +
+       "inspector to be selected");
+  let onInspectorSelected = toolbox.once("inspector-selected");
+  let onInspectorUpdated = inspector.once("inspector-updated");
+  let onNewNode = toolbox.selection.once("new-node-front");
+
+  openInInspectorIcon.click();
+
+  await onInspectorSelected;
+  await onInspectorUpdated;
+  let nodeFront = await onNewNode;
+
+  ok(true, "Inspector selected and new node got selected");
+  is(nodeFront.displayName, "h1", "The expected node was selected");
+});
--- a/devtools/client/webconsole/new-console-output/test/mochitest/head.js
+++ b/devtools/client/webconsole/new-console-output/test/mochitest/head.js
@@ -96,21 +96,19 @@ function waitForMessages({ hud, messages
  *        true in order to return the value.
  * @param string message [optional]
  *        A message to output if the condition failes.
  * @param number interval [optional]
  *        How often the predicate is invoked, in milliseconds.
  * @return object
  *         A promise that is resolved with the result of the condition.
  */
-function* waitFor(condition, message = "waitFor", interval = 10, maxTries = 500) {
-  return new Promise(resolve => {
-    BrowserTestUtils.waitForCondition(condition, message, interval, maxTries)
-      .then(() => resolve(condition()));
-  });
+async function waitFor(condition, message = "waitFor", interval = 10, maxTries = 500) {
+  await BrowserTestUtils.waitForCondition(condition, message, interval, maxTries);
+  return condition();
 }
 
 /**
  * Find a message in the output.
  *
  * @param object hud
  *        The web console.
  * @param string text
--- a/devtools/docs/SUMMARY.md
+++ b/devtools/docs/SUMMARY.md
@@ -25,8 +25,14 @@
   * Actors
     * [Actors Organization](backend/actor-hierarchy.md)
     * [Handling Multi-Processes in Actors](backend/actor-e10s-handling.md)
     * [Writing Actors With protocol.js](backend/protocol.js.md)
     * [Registering A New Actor](backend/actor-registration.md)
     * [Actor Best Practices](backend/actor-best-practices.md)
 * [Files and directories](files/README.md)
   * [Adding New Files](files/adding-files.md)
+* [Automated tests](tests/README.md)
+  * Running tests
+    * [`xpcshell`](tests/xpcshell.md)
+    * [Chrome mochitests](tests/mochitest-chrome.md)
+    * [DevTools mochitests](tests/mochitest-devtools.md)
+  * [Writing tests](tests/writing-tests.md)
new file mode 100644
--- /dev/null
+++ b/devtools/docs/tests/README.md
@@ -0,0 +1,23 @@
+# Automated tests
+
+When working on a patch for DevTools, there's almost never a reason not to add a new test. If you are fixing a bug, you probably should write a new test to prevent this bug from occurring again. If you're implementing a new feature, you should write new tests to cover the aspects of this new feature.
+
+Ask yourself:
+* Are there enough tests for my patch?
+* Are they the right types of tests?
+
+We use three suites of tests:
+
+* [`xpcshell`](xpcshell.md): Unit-test style of tests. No browser window, only a JavaScript shell. Mostly testing APIs directly.
+* [Chrome mochitests](mochitest-chrome.md): Unit-test style of tests, but with a browser window. Mostly testing APIs that interact with the DOM.
+* [DevTools mochitests](mochitest-devtools.md): Integration style of tests. Fires up a whole browser window with every test and you can test clicking on buttons, etc.
+
+More information about the different types of tests can be found on the [automated testing page](https://developer.mozilla.org/en-US/docs/Mozilla/QA/Automated_testing) at MDN.
+
+To run all DevTools tests, regardless of suite type:
+
+```bash
+./mach test devtools/*
+```
+
+Have a look at the child pages for more specific commands for running only a single suite or single test in a suite.
new file mode 100644
--- /dev/null
+++ b/devtools/docs/tests/mochitest-chrome.md
@@ -0,0 +1,14 @@
+# Automated tests: chrome mochitests
+
+To run the whole suite of chrome mochitests:
+
+```bash
+./mach mochitest -f chrome --tag devtools
+```
+
+To run a specific chrome mochitest:
+
+```bash
+./mach mochitest devtools/path/to/the/test_you_want_to_run.html
+```
+
new file mode 100644
--- /dev/null
+++ b/devtools/docs/tests/mochitest-devtools.md
@@ -0,0 +1,25 @@
+# Automated tests: DevTools mochitests
+
+To run the whole suite of browser mochitests for DevTools (sit back and relax):
+
+```bash
+./mach mochitest --subsuite devtools --tag devtools
+```
+To run a specific tool's suite of browser mochitests:
+
+```bash
+./mach mochitest devtools/client/<tool>
+```
+
+For example, run all of the debugger browser mochitests:
+
+```bash
+./mach mochitest devtools/client/debugger
+```
+To run a specific DevTools mochitest:
+
+```bash
+./mach mochitest devtools/client/path/to/the/test_you_want_to_run.js
+```
+Note that the mochitests *must* have focus while running.
+
new file mode 100644
--- /dev/null
+++ b/devtools/docs/tests/writing-tests.md
@@ -0,0 +1,255 @@
+# Automated tests: writing tests
+
+<!--TODO this file might benefit from being split in other various files. For now it's just taken from the wiki with some edits-->
+
+## Adding a new browser chrome test
+
+It's almost always a better idea to create a new test file rather than to add new test cases to an existing one.
+
+This prevents test files from growing up to the point where they timeout for running too long. Test systems may be under lots of stress at time and run a lot slower than your regular local environment.
+
+It also helps with making tests more maintainable: with many small files, it's easier to track a problem rather than in one huge file.
+
+### Creating the new file
+
+The first thing you need to do is create a file. This file should go next to the code it's testing, in the `tests` directory. For example, an inspector test would go into `devtools/inspector/test/`.
+
+### Naming the new file
+
+Naming your file is pretty important to help other people get a feeling of what it is supposed to test.
+Having said that, the name shouldn't be too long either.
+
+A good naming convention is `browser_<panel>_<short-description>[_N].js`
+
+where:
+
+* `<panel>` is one of `debugger`, `markupview`, `inspector`, `ruleview`, etc.
+* `<short-description>` should be about 3 to 4 words, separated by hyphens (-)
+* and optionally add a number at the end if you have several files testing the same thing
+
+For example: `browser_ruleview_completion-existing-property_01.js`
+
+Note that not all existing tests are consistently named. So the rule we try to follow is to **be consistent with how other tests in the same test folder are named**.
+
+### Basic structure of a test
+
+```javascript
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+// A detailed description of what the test is supposed to test
+
+const TEST_URL = TEST_URL_ROOT + "doc_some_test_page.html";
+
+add_task(function*() {
+yield addTab(TEST_URL_ROOT);
+let {toolbox, inspector, view} = yield openRuleView();
+yield selectNode("#testNode", inspector);
+yield checkSomethingFirst(view);
+yield checkSomethingElse(view);
+});
+
+function* checkSomethingFirst(view) {
+/* ... do something ... this function can yield */
+}
+
+function* checkSomethingElse(view) {
+/* ... do something ... this function can yield */
+}
+```
+
+### Referencing the new file
+
+For your test to be run, it needs to be referenced in the `browser.ini` file that you'll find in the same directory. For example: `browser/devtools/debugger/test/browser.ini`
+
+Add a line with your file name between square brackets, and make sure that the list of files **is always sorted by alphabetical order** (some lists can be really long, so the alphabetical order helps in finding and reasoning about things).
+
+For example, if you were to add the test from the previous section, you'd add this to `browser.ini`:
+
+```ini
+[browser_ruleview_completion-existing-property_01.js]
+```
+
+### Adding support files
+
+Sometimes your test may need to open an HTML file in a tab, and it may also need to load CSS or JavaScript. For this to work, you'll need to...
+
+1. place these files in the same directory, and also 
+2. reference them in the `browser.ini` file.
+
+There's a naming convention for support files: `doc_<support-some-test>.html`
+
+But again, often names do not follow this convention, so try to follow the style of the other support files currently in the same test directory.
+
+To reference your new support file, add its filename in the `support-files` section of `browser.ini`, also making sure this section is in alphabetical order.
+
+Support files can be accessed via a local server that is started while tests are running. This server is accessible at [http://example.com/browser/](http://example.com/browser/). See the `head.js section` below for more information.
+
+## Leveraging helpers in `head.js`
+
+`head.js` is a special support file that is loaded in the scope the test runs in, before the test starts. It contains global helpers that are useful for most tests. Read through the head.js file in your test directory to see what functions are there and therefore avoid duplicating code.
+
+Each panel in DevTools has its own test directory with its own `head.js`, so you'll find different things in each panel's `head.js` file.
+
+For example, the head.js files in the `markupview` and `styleinspector` test folders contain these useful functions and constants:
+
+* Base URLs for support files: `TEST_URL_ROOT`. This avoids having to duplicate the http://example.com/browser/browser/devtools/styleinspector/ URL fragment in all tests,
+* `waitForExplicitFinish()` is called in `head.js` once and for all<!--TODO: what does this even mean?-->. All tests are asynchronous, so there's no need to call it again in each and every test,
+* `auto-cleanup`: the toolbox is closed automatically and all tabs are closed,
+* `tab addTab(url)`
+* `{toolbox, inspector} openInspector()`
+* `{toolbox, inspector, view} openRuleView()`
+* `selectNode(selectorOrNode, inspector)`
+* `node getNode(selectorOrNode)`
+* ...
+
+## Shared head.js file
+
+A [shared-head.js](https://dxr.mozilla.org/mozilla-central/source/devtools/client/framework/test/shared-head.js) file has been introduced to avoid duplicating code in various `head.js` files.
+
+It's important to know whether or not the `shared.js` in your test directory already imports `shared-head.js` (look for a <code>Services.scriptloader.loadSubScript</code> call), as common helpers in `shared-head.js` might be useful for your test.
+
+If you're planning to work on a lot of new tests, it might be worth the time actually importing `shared-head.js` in your `head.js` if it isn't here already.
+
+## E10S (Electrolysis)
+
+E10S is the codename for Firefox multi-process, and what that means for us is that the process in which the test runs isn't the same as the one in which the test content page runs.
+
+You can learn more about E10S [from this blog post](https://timtaubert.de/blog/2011/08/firefox-electrolysis-101/) and [the Electrolysis wiki page](https://wiki.mozilla.org/Electrolysis).
+
+One of the direct consequences of E10S on tests is that you cannot retrieve and manipulate objects from the content page as you'd do without E10S.
+
+Well this isn't entirely true, because with [cross-process object wrappers](https://developer.mozilla.org/en-US/Firefox/Multiprocess_Firefox/Cross_Process_Object_Wrappers CPOWs) you can somehow access the page, get to DOM nodes, and read their attributes for instance, but a lot of other things you'd expect to work without E10S won't work exactly the same or at all.
+
+Using CPOWs is discouraged; they are only temporarily allowed in mochitests, and their use is forbidden in browser code.
+
+So when creating a new test, if this test needs to access the content page in any way, you can use [the message manager](https://developer.mozilla.org/en-US/docs/The_message_manager) to communicate with a script loaded in the content process to do things for you instead of accessing objects in the page directly.
+
+You can use the helper `ContentTask.spawn()` for this. See [this list of DevTools tests that use that helper for examples](https://dxr.mozilla.org/mozilla-central/search?q=ContentTask.spawn%28+path%3Adevtools%2Fclient&redirect=false&case=false).
+
+Note that a lot of tests only need to access the DevTools UI anyway, and don't need to interact with the content process at all. Since the UI lives in the same process as the test, you won't need to use the message manager to access it.
+
+## Asynchronous tests
+
+Most browser chrome DevTools tests are asynchronous. One of the reasons why they are asynchronous is that the code needs to register event handlers for various user interactions in the tools and then simulate these interactions. Another reason is that most DevTools operations are done asynchronously via the debugger protocol.
+
+Here are a few things to keep in mind with regards to asynchronous testing:
+
+* `head.js` already calls `waitForExplicitFinish()` so there's no need for your new test to do it too.
+* Using `add_task` with a generator function means that you can yield calls to functions that return promises. It also means your main test function can be written to almost look like synchronous code, by adding `yield` before calls to asynchronous functions. For example:
+
+```javascript
+for (let test of testData) {
+	yield testCompletion(test, editor, view);
+}
+```
+
+Each call to `testCompletion` is asynchronous, but the code doesn't need to rely on nested callbacks and maintain an index, a standard for loop can be used.
+
+<!--TODO: I think the following paragraph might be slightly outdated-->
+
+* Define your test functions as generators that yield, no need for them to be tasks since they are called from one already. In some cases you'll need to return promises anyway (if you're adding a new helper function to head.js for example). If this is the case, it sometimes is best to define your function like so:
+
+```javascript
+let myHelperFunction = Task.async(function*() {
+	...
+});
+```
+
+## Writing clean, maintainable test code
+
+Test code is as important as feature code itself, it helps avoiding regressions of course, but it also helps understanding complex parts of the code that would be otherwise hard to grasp.
+
+Since we find ourselves working with test code a large portion of our time, we should spend the time and energy it takes to make this time enjoyable.
+
+### Logs and comments
+
+Reading test output logs isn't exactly fun and it takes time but is needed at times. Make sure your test generates enough logs by using:
+
+```
+info("doing something now")
+```
+
+it helps a lot knowing around which lines the test fails, if it fails.
+
+One good rule of thumb is if you're about to add a JS line comment in your test to explain what the code below is about to test, write the same comment in an `info()` instead.
+
+Also add a description at the top of the file to help understand what this test is about. The file name is often not long enough to convey everything you need to know about the test. Understanding a test often teaches you about the feature itself.
+
+Not really a comment, but don't forget to "use strict";
+
+### Callbacks and promises
+
+Avoid multiple nested callbacks or chained promises. They make it hard to read the code.
+
+Thanks to `add_task`, it's easy to write asynchronous code that looks like flat, synchronous, code.
+
+### Clean up after yourself
+
+Do not expose global variables in your test file, they may end up causing bugs that are hard to track. Most functions in `head.js` return useful instances of the DevTools panels, and you can pass these as arguments to your sub functions, no need to store them in the global scope.
+This avoids having to remember nullifying them at the end.
+
+If your test needs to toggle user preferences, make sure you reset these preferences when the test ends. Do not reset them at the end of the test function though because if your test fails, the preferences will never be reset. Use the `registerCleanupFunction` helper instead.
+
+It may be a good idea to do the reset in `head.js`.
+
+### Write small, maintainable code
+
+Split your main test function into smaller test functions with self explanatory names.
+
+Make sure your test files are small. If you are working on a new feature, you can create a new test each time you add a new functionality, a new button to the UI for instance. This helps having small, incremental tests and can also help writing test while coding.
+
+If your test is just a sequence of functions being called to do the same thing over and over again, it may be better to describe the test steps in an array instead and just have one function that runs each item of the array. See the following example
+
+```javascript
+const TESTS = [
+	{desc: "add a class", cssSelector: "#id1", makeChanges: function*() {...}},
+	{desc: "change href", cssSelector: "a.the-link", makeChanges: function*() {...}},
+	...
+];
+
+add_task(function*() {
+	yield addTab("...");
+	let {toolbox, inspector} = yield openInspector();
+	for (let step of TESTS) {
+	  info("Testing step: " + step.desc);
+	  yield selectNode(step.cssSelector, inspector);
+	  yield step.makeChanges();
+	}
+});
+```
+
+As shown in this code example, you can add as many test cases as you want in the TESTS array and the actual test code will remain very short, and easy to understand and maintain (note that when looping through test arrays, it's always a good idea to add a "desc" property that will be used in an info() log output).
+
+### Avoid exceptions
+
+Even when they're not failing the test, exceptions are bad because they pollute the logs and make them harder to read.
+They're also bad because when your test is run as part of a test suite and if an other, unrelated, test fails then the exceptions may give wrong information to the person fixing the unrelated test.
+
+After your test has run locally, just make sure it doesn't output exceptions by scrolling through the logs.
+
+Often, non-blocking exceptions may be caused by hanging protocol requests that haven't been responded to yet when the tools get closed at the end of the test. Make sure you register to the right events and give time to the tools to update themselves before moving on.
+
+### Avoid test timeouts
+
+<!--TODO: this recommendation is conflicting with the above recommendation. What? -->
+When tests fail, it's far better to have them fail and end immediately with an exception that will help fix it rather than have them hang until they hit the timeout and get killed.
+
+## Adding new helpers
+
+In some cases, you may want to extract some common code from your test to use it another another test. 
+
+* If this is very common code that all tests could use, then add it to `devtools/client/framework/test/shared-head.js`.
+* If this is common code specific to a given tool, then add it to the corresponding `head.js` file.
+* If it isn't common enough to live in `head.js`, then it may be a good idea to create a helper file to avoid duplication anyway. Here's how to create a helper file:
+ * Create a new file in your test director. The naming convention should be `helper_<description_of_the_helper>.js`
+ * Add it to the browser.ini support-files section, making sure it is sorted alphabetically
+ * Load the helper file in the tests
+ * `browser/devtools/markupview/test/head.js` has a handy `loadHelperScript(fileName)` function that you can use.
+ * The file will be loaded in the test global scope, so any global function or variables it defines will be available (just like `head.js`).
+ * Use the special ESLint comment `/* import-globals-from helper_file.js */` to prevent ESLint errors for undefined variables.
+
+In all cases, new helper functions should be properly commented with an jsdoc comment block.
+
new file mode 100644
--- /dev/null
+++ b/devtools/docs/tests/xpcshell.md
@@ -0,0 +1,14 @@
+# Automated tests: `xpcshell` tests
+
+To run all of the xpcshell tests:
+
+```bash
+./mach xpcshell-test --tag devtools
+```
+
+To run a specific xpcshell test:
+
+```bash
+./mach xpcshell-test devtools/path/to/the/test_you_want_to_run.js
+```
+
--- a/devtools/server/tests/unit/test_frameactor_wasm-01.js
+++ b/devtools/server/tests/unit/test_frameactor_wasm-01.js
@@ -42,17 +42,17 @@ function test_pause_frame() {
 
       let wasmFrame = frameResponse.frames[1];
       do_check_eq(wasmFrame.type, "wasmcall");
       do_check_eq(wasmFrame.this, undefined);
 
       let location = wasmFrame.where;
       do_check_eq(location.line > 0, true);
       do_check_eq(location.column > 0, true);
-      do_check_eq(location.source.url.endsWith(" > wasm"), true);
+      do_check_eq(/^wasm:(?:[^:]*:)*?[0-9a-f]{16}$/.test(location.source.url), true);
 
       finishClient(gClient);
     });
   });
 
   /* eslint-disable comma-spacing, max-len */
   gDebuggee.eval("(" + function () {
     // WebAssembly bytecode was generated by running:
--- a/devtools/shared/acorn/tests/unit/test_import_acorn.js
+++ b/devtools/shared/acorn/tests/unit/test_import_acorn.js
@@ -1,18 +1,18 @@
-/* Any copyright is dedicated to the Public Domain.
-   http://creativecommons.org/publicdomain/zero/1.0/ */
-
-/**
- * Test that we can require acorn.
- */
-
-function run_test() {
-  const acorn = require("acorn/acorn");
-  const acorn_loose = require("acorn/acorn_loose");
-  const walk = require("acorn/util/walk");
-  do_check_true(isObject(acorn));
-  do_check_true(isObject(acorn_loose));
-  do_check_true(isObject(walk));
-  do_check_eq(typeof acorn.parse, "function");
-  do_check_eq(typeof acorn_loose.parse_dammit, "function");
-  do_check_eq(typeof walk.simple, "function");
-}
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Test that we can require acorn.
+ */
+
+function run_test() {
+  const acorn = require("acorn/acorn");
+  const acorn_loose = require("acorn/acorn_loose");
+  const walk = require("acorn/util/walk");
+  do_check_true(isObject(acorn));
+  do_check_true(isObject(acorn_loose));
+  do_check_true(isObject(walk));
+  do_check_eq(typeof acorn.parse, "function");
+  do_check_eq(typeof acorn_loose.parse_dammit, "function");
+  do_check_eq(typeof walk.simple, "function");
+}
--- a/devtools/shared/acorn/tests/unit/test_lenient_parser.js
+++ b/devtools/shared/acorn/tests/unit/test_lenient_parser.js
@@ -1,62 +1,62 @@
-/* Any copyright is dedicated to the Public Domain.
-   http://creativecommons.org/publicdomain/zero/1.0/ */
-
-/**
- * Test that acorn's lenient parser gives something usable.
- */
-
-const acorn_loose = require("acorn/acorn_loose");
-
-function run_test() {
-  let actualAST = acorn_loose.parse_dammit("let x = 10", {});
-
-  do_print("Actual AST:");
-  do_print(JSON.stringify(actualAST, null, 2));
-  do_print("Expected AST:");
-  do_print(JSON.stringify(expectedAST, null, 2));
-
-  checkEquivalentASTs(expectedAST, actualAST);
-}
-
-const expectedAST = {
-  "type": "Program",
-  "start": 0,
-  "end": 10,
-  "body": [
-    {
-      "type": "ExpressionStatement",
-      "start": 0,
-      "end": 3,
-      "expression": {
-        "type": "Identifier",
-        "start": 0,
-        "end": 3,
-        "name": "let"
-      }
-    },
-    {
-      "type": "ExpressionStatement",
-      "start": 4,
-      "end": 10,
-      "expression": {
-        "type": "AssignmentExpression",
-        "start": 4,
-        "end": 10,
-        "operator": "=",
-        "left": {
-          "type": "Identifier",
-          "start": 4,
-          "end": 5,
-          "name": "x"
-        },
-        "right": {
-          "type": "Literal",
-          "start": 8,
-          "end": 10,
-          "value": 10,
-          "raw": "10"
-        }
-      }
-    }
-  ]
-};
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Test that acorn's lenient parser gives something usable.
+ */
+
+const acorn_loose = require("acorn/acorn_loose");
+
+function run_test() {
+  let actualAST = acorn_loose.parse_dammit("let x = 10", {});
+
+  do_print("Actual AST:");
+  do_print(JSON.stringify(actualAST, null, 2));
+  do_print("Expected AST:");
+  do_print(JSON.stringify(expectedAST, null, 2));
+
+  checkEquivalentASTs(expectedAST, actualAST);
+}
+
+const expectedAST = {
+  "type": "Program",
+  "start": 0,
+  "end": 10,
+  "body": [
+    {
+      "type": "ExpressionStatement",
+      "start": 0,
+      "end": 3,
+      "expression": {
+        "type": "Identifier",
+        "start": 0,
+        "end": 3,
+        "name": "let"
+      }
+    },
+    {
+      "type": "ExpressionStatement",
+      "start": 4,
+      "end": 10,
+      "expression": {
+        "type": "AssignmentExpression",
+        "start": 4,
+        "end": 10,
+        "operator": "=",
+        "left": {
+          "type": "Identifier",
+          "start": 4,
+          "end": 5,
+          "name": "x"
+        },
+        "right": {
+          "type": "Literal",
+          "start": 8,
+          "end": 10,
+          "value": 10,
+          "raw": "10"
+        }
+      }
+    }
+  ]
+};
--- a/devtools/shared/acorn/tests/unit/test_same_ast.js
+++ b/devtools/shared/acorn/tests/unit/test_same_ast.js
@@ -1,37 +1,37 @@
-/* Any copyright is dedicated to the Public Domain.
-   http://creativecommons.org/publicdomain/zero/1.0/ */
-
-/**
- * Test that Reflect and acorn create the same AST for ES5.
- */
-
-const acorn = require("acorn/acorn");
-const { Reflect } = require("resource://gre/modules/reflect.jsm");
-
-const testCode = "" + function main () {
-  function makeAcc(n) {
-    return function () {
-      return ++n;
-    };
-  }
-
-  var acc = makeAcc(10);
-
-  for (var i = 0; i < 10; i++) {
-    acc();
-  }
-
-  console.log(acc());
-};
-
-function run_test() {
-  const reflectAST = Reflect.parse(testCode);
-  const acornAST = acorn.parse(testCode);
-
-  do_print("Reflect AST:");
-  do_print(JSON.stringify(reflectAST, null, 2));
-  do_print("acorn AST:");
-  do_print(JSON.stringify(acornAST, null, 2));
-
-  checkEquivalentASTs(reflectAST, acornAST);
-}
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Test that Reflect and acorn create the same AST for ES5.
+ */
+
+const acorn = require("acorn/acorn");
+const { Reflect } = require("resource://gre/modules/reflect.jsm");
+
+const testCode = "" + function main () {
+  function makeAcc(n) {
+    return function () {
+      return ++n;
+    };
+  }
+
+  var acc = makeAcc(10);
+
+  for (var i = 0; i < 10; i++) {
+    acc();
+  }
+
+  console.log(acc());
+};
+
+function run_test() {
+  const reflectAST = Reflect.parse(testCode);
+  const acornAST = acorn.parse(testCode);
+
+  do_print("Reflect AST:");
+  do_print(JSON.stringify(reflectAST, null, 2));
+  do_print("acorn AST:");
+  do_print(JSON.stringify(acornAST, null, 2));
+
+  checkEquivalentASTs(reflectAST, acornAST);
+}
--- a/dom/base/domerr.msg
+++ b/dom/base/domerr.msg
@@ -140,16 +140,17 @@ DOM4_MSG_DEF(QuotaExceededError, "The cu
 
 /* Push API errors. */
 DOM4_MSG_DEF(InvalidStateError, "Invalid service worker registration.", NS_ERROR_DOM_PUSH_INVALID_REGISTRATION_ERR)
 DOM4_MSG_DEF(NotAllowedError, "User denied permission to use the Push API.", NS_ERROR_DOM_PUSH_DENIED_ERR)
 DOM4_MSG_DEF(AbortError, "Error retrieving push subscription.", NS_ERROR_DOM_PUSH_ABORT_ERR)
 DOM4_MSG_DEF(NetworkError, "Push service unreachable.", NS_ERROR_DOM_PUSH_SERVICE_UNREACHABLE)
 DOM4_MSG_DEF(InvalidAccessError, "Invalid raw ECDSA P-256 public key.", NS_ERROR_DOM_PUSH_INVALID_KEY_ERR)
 DOM4_MSG_DEF(InvalidStateError, "A subscription with a different application server key already exists.", NS_ERROR_DOM_PUSH_MISMATCHED_KEY_ERR)
+DOM4_MSG_DEF(InvalidStateError, "GCM service disabled.", NS_ERROR_DOM_PUSH_GCM_DISABLED)
 
 /* Media errors */
 DOM4_MSG_DEF(AbortError,        "The fetching process for the media resource was aborted by the user agent at the user's request.", NS_ERROR_DOM_MEDIA_ABORT_ERR)
 DOM4_MSG_DEF(NotAllowedError,   "The play method is not allowed by the user agent or the platform in the current context, possibly because the user denied permission.", NS_ERROR_DOM_MEDIA_NOT_ALLOWED_ERR)
 DOM4_MSG_DEF(NotSupportedError, "The media resource indicated by the src attribute or assigned media provider object was not suitable.", NS_ERROR_DOM_MEDIA_NOT_SUPPORTED_ERR)
 
 DOM4_MSG_DEF(SyntaxError, "The URI is malformed.", NS_ERROR_DOM_MALFORMED_URI)
 DOM4_MSG_DEF(SyntaxError, "Invalid header name.", NS_ERROR_DOM_INVALID_HEADER_NAME)
--- a/dom/base/nsDOMTokenList.cpp
+++ b/dom/base/nsDOMTokenList.cpp
@@ -5,19 +5,23 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 /*
  * Implementation of DOMTokenList specified by HTML5.
  */
 
 #include "nsDOMTokenList.h"
 #include "nsAttrValue.h"
+#include "nsAttrValueInlines.h"
+#include "nsDataHashtable.h"
 #include "nsError.h"
+#include "nsHashKeys.h"
 #include "mozilla/dom/Element.h"
 #include "mozilla/dom/DOMTokenListBinding.h"
+#include "mozilla/BloomFilter.h"
 #include "mozilla/ErrorResult.h"
 
 using namespace mozilla;
 using namespace mozilla::dom;
 
 nsDOMTokenList::nsDOMTokenList(Element* aElement, nsIAtom* aAttrAtom,
                                const DOMTokenListSupportedTokenArray aSupportedTokens)
   : mElement(aElement),
@@ -45,32 +49,79 @@ const nsAttrValue*
 nsDOMTokenList::GetParsedAttr()
 {
   if (!mElement) {
     return nullptr;
   }
   return mElement->GetAttrInfo(kNameSpaceID_None, mAttrAtom).mValue;
 }
 
+void
+nsDOMTokenList::RemoveDuplicates(const nsAttrValue* aAttr)
+{
+  if (!aAttr || aAttr->Type() != nsAttrValue::eAtomArray) {
+    return;
+  }
+
+  BloomFilter<8, nsIAtom> filter;
+  nsAttrValue::AtomArray* array = aAttr->GetAtomArrayValue();
+  for (uint32_t i = 0; i < array->Length(); i++) {
+    nsIAtom* atom = array->ElementAt(i);
+    if (filter.mightContain(atom)) {
+      // Start again, with a hashtable
+      RemoveDuplicatesInternal(array, i);
+      return;
+    } else {
+      filter.add(atom);
+    }
+  }
+}
+
+void
+nsDOMTokenList::RemoveDuplicatesInternal(nsAttrValue::AtomArray* aArray,
+                                         uint32_t aStart)
+{
+  nsDataHashtable<nsPtrHashKey<nsIAtom>, bool> tokens;
+
+  for (uint32_t i = 0; i < aArray->Length(); i++) {
+    nsIAtom* atom = aArray->ElementAt(i);
+    // No need to check the hashtable below aStart
+    if (i >= aStart && tokens.Get(atom)) {
+      aArray->RemoveElementAt(i);
+      i--;
+    } else {
+      tokens.Put(atom, true);
+    }
+  }
+}
+
 uint32_t
 nsDOMTokenList::Length()
 {
   const nsAttrValue* attr = GetParsedAttr();
   if (!attr) {
     return 0;
   }
 
+  RemoveDuplicates(attr);
   return attr->GetAtomCount();
 }
 
 void
 nsDOMTokenList::IndexedGetter(uint32_t aIndex, bool& aFound, nsAString& aResult)
 {
   const nsAttrValue* attr = GetParsedAttr();
 
+  if (!attr || aIndex >= static_cast<uint32_t>(attr->GetAtomCount())) {
+    aFound = false;
+    return;
+  }
+
+  RemoveDuplicates(attr);
+
   if (attr && aIndex < static_cast<uint32_t>(attr->GetAtomCount())) {
     aFound = true;
     attr->AtomAt(aIndex)->ToString(aResult);
   } else {
     aFound = false;
   }
 }
 
@@ -130,40 +181,40 @@ nsDOMTokenList::AddInternal(const nsAttr
 {
   if (!mElement) {
     return;
   }
 
   nsAutoString resultStr;
 
   if (aAttr) {
-    aAttr->ToString(resultStr);
+    RemoveDuplicates(aAttr);
+    for (uint32_t i = 0; i < aAttr->GetAtomCount(); i++) {
+      if (i != 0) {
+        resultStr.AppendLiteral(" ");
+      }
+      resultStr.Append(nsDependentAtomString(aAttr->AtomAt(i)));
+    }
   }
 
-  bool oneWasAdded = false;
   AutoTArray<nsString, 10> addedClasses;
 
   for (uint32_t i = 0, l = aTokens.Length(); i < l; ++i) {
     const nsString& aToken = aTokens[i];
 
     if ((aAttr && aAttr->Contains(aToken)) ||
         addedClasses.Contains(aToken)) {
       continue;
     }
 
-    if (oneWasAdded ||
-        (!resultStr.IsEmpty() &&
-        !nsContentUtils::IsHTMLWhitespace(resultStr.Last()))) {
+    if (!resultStr.IsEmpty()) {
       resultStr.Append(' ');
-      resultStr.Append(aToken);
-    } else {
-      resultStr.Append(aToken);
     }
+    resultStr.Append(aToken);
 
-    oneWasAdded = true;
     addedClasses.AppendElement(aToken);
   }
 
   mElement->SetAttr(kNameSpaceID_None, mAttrAtom, resultStr, true);
 }
 
 void
 nsDOMTokenList::Add(const nsTArray<nsString>& aTokens, ErrorResult& aError)
@@ -186,33 +237,30 @@ nsDOMTokenList::Add(const nsAString& aTo
 }
 
 void
 nsDOMTokenList::RemoveInternal(const nsAttrValue* aAttr,
                                const nsTArray<nsString>& aTokens)
 {
   MOZ_ASSERT(aAttr, "Need an attribute");
 
-  nsAutoString input;
-  aAttr->ToString(input);
-
-  WhitespaceTokenizer tokenizer(input);
-  nsAutoString output;
+  RemoveDuplicates(aAttr);
 
-  while (tokenizer.hasMoreTokens()) {
-    auto& currentToken = tokenizer.nextToken();
-    if (!aTokens.Contains(currentToken)) {
-      if (!output.IsEmpty()) {
-        output.Append(char16_t(' '));
-      }
-      output.Append(currentToken);
+  nsAutoString resultStr;
+  for (uint32_t i = 0; i < aAttr->GetAtomCount(); i++) {
+    if (aTokens.Contains(nsDependentAtomString(aAttr->AtomAt(i)))) {
+      continue;
     }
+    if (!resultStr.IsEmpty()) {
+      resultStr.AppendLiteral(" ");
+    }
+    resultStr.Append(nsDependentAtomString(aAttr->AtomAt(i)));
   }
 
-  mElement->SetAttr(kNameSpaceID_None, mAttrAtom, output, true);
+  mElement->SetAttr(kNameSpaceID_None, mAttrAtom, resultStr, true);
 }
 
 void
 nsDOMTokenList::Remove(const nsTArray<nsString>& aTokens, ErrorResult& aError)
 {
   aError = CheckTokens(aTokens);
   if (aError.Failed()) {
     return;
@@ -298,43 +346,42 @@ nsDOMTokenList::Replace(const nsAString&
   ReplaceInternal(attr, aToken, aNewToken);
 }
 
 void
 nsDOMTokenList::ReplaceInternal(const nsAttrValue* aAttr,
                                 const nsAString& aToken,
                                 const nsAString& aNewToken)
 {
-  nsAutoString attribute;
-  aAttr->ToString(attribute);
-
-  nsAutoString result;
-  WhitespaceTokenizer tokenizer(attribute);
+  RemoveDuplicates(aAttr);
 
   bool sawIt = false;
-  while (tokenizer.hasMoreTokens()) {
-    auto currentToken = tokenizer.nextToken();
-    if (currentToken.Equals(aToken) || currentToken.Equals(aNewToken)) {
-      if (!sawIt) {
-        sawIt = true;
-        if (!result.IsEmpty()) {
-          result.Append(char16_t(' '));
-        }
-        result.Append(aNewToken);
+  nsAutoString resultStr;
+  for (uint32_t i = 0; i < aAttr->GetAtomCount(); i++) {
+    if (aAttr->AtomAt(i)->Equals(aToken) ||
+        aAttr->AtomAt(i)->Equals(aNewToken)) {
+      if (sawIt) {
+        // We keep only the first
+        continue;
       }
-    } else {
-      if (!result.IsEmpty()) {
-        result.Append(char16_t(' '));
+      sawIt = true;
+      if (!resultStr.IsEmpty()) {
+        resultStr.AppendLiteral(" ");
       }
-      result.Append(currentToken);
+      resultStr.Append(aNewToken);
+      continue;
     }
+    if (!resultStr.IsEmpty()) {
+      resultStr.AppendLiteral(" ");
+    }
+    resultStr.Append(nsDependentAtomString(aAttr->AtomAt(i)));
   }
 
   if (sawIt) {
-    mElement->SetAttr(kNameSpaceID_None, mAttrAtom, result, true);
+    mElement->SetAttr(kNameSpaceID_None, mAttrAtom, resultStr, true);
   }
 }
 
 bool
 nsDOMTokenList::Supports(const nsAString& aToken,
                          ErrorResult& aError)
 {
   if (!mSupportedTokens) {
--- a/dom/base/nsDOMTokenList.h
+++ b/dom/base/nsDOMTokenList.h
@@ -47,16 +47,17 @@ public:
 
   virtual JSObject* WrapObject(JSContext *cx, JS::Handle<JSObject*> aGivenProto) override;
 
   Element* GetParentObject()
   {
     return mElement;
   }
 
+  void RemoveDuplicates(const nsAttrValue* aAttr);
   uint32_t Length();
   void Item(uint32_t aIndex, nsAString& aResult)
   {
     bool found;
     IndexedGetter(aIndex, found, aResult);
     if (!found) {
       SetDOMStringToNull(aResult);
     }
@@ -82,16 +83,18 @@ public:
   void SetValue(const nsAString& aValue, mozilla::ErrorResult& rv);
   void Stringify(nsAString& aResult);
 
 protected:
   virtual ~nsDOMTokenList();
 
   nsresult CheckToken(const nsAString& aStr);
   nsresult CheckTokens(const nsTArray<nsString>& aStr);
+  void RemoveDuplicatesInternal(nsTArray<nsCOMPtr<nsIAtom>>* aArray,
+                                uint32_t aStart);
   void AddInternal(const nsAttrValue* aAttr,
                    const nsTArray<nsString>& aTokens);
   void RemoveInternal(const nsAttrValue* aAttr,
                       const nsTArray<nsString>& aTokens);
   void ReplaceInternal(const nsAttrValue* aAttr,
                        const nsAString& aToken,
                        const nsAString& aNewToken);
   inline const nsAttrValue* GetParsedAttr();
--- a/dom/base/nsDOMWindowUtils.cpp
+++ b/dom/base/nsDOMWindowUtils.cpp
@@ -3044,23 +3044,30 @@ nsDOMWindowUtils::IsPartOfOpaqueLayer(ns
   nsCOMPtr<nsIContent> content = do_QueryInterface(aElement, &rv);
   NS_ENSURE_SUCCESS(rv, rv);
 
   nsIFrame* frame = content->GetPrimaryFrame();
   if (!frame) {
     return NS_ERROR_FAILURE;
   }
 
-  PaintedLayer* layer = FrameLayerBuilder::GetDebugSingleOldPaintedLayerForFrame(frame);
-  if (!layer) {
-    return NS_ERROR_FAILURE;
+  ColorLayer* colorLayer = FrameLayerBuilder::GetDebugSingleOldLayerForFrame<ColorLayer>(frame);
+  if (colorLayer) {
+    auto color = colorLayer->GetColor();
+    *aResult = color.a == 1.0f;
+    return NS_OK;
   }
 
-  *aResult = (layer->GetContentFlags() & Layer::CONTENT_OPAQUE);
-  return NS_OK;
+  PaintedLayer* paintedLayer = FrameLayerBuilder::GetDebugSingleOldLayerForFrame<PaintedLayer>(frame);
+  if (paintedLayer) {
+    *aResult = paintedLayer->IsOpaque();
+    return NS_OK;
+  }
+
+  return NS_ERROR_FAILURE;
 }
 
 NS_IMETHODIMP
 nsDOMWindowUtils::NumberOfAssignedPaintedLayers(nsIDOMElement** aElements,
                                                 uint32_t aCount,
                                                 uint32_t* aResult)
 {
   if (!aElements) {
@@ -3073,17 +3080,17 @@ nsDOMWindowUtils::NumberOfAssignedPainte
     nsCOMPtr<nsIContent> content = do_QueryInterface(aElements[i], &rv);
     NS_ENSURE_SUCCESS(rv, rv);
 
     nsIFrame* frame = content->GetPrimaryFrame();
     if (!frame) {
       return NS_ERROR_FAILURE;
     }
 
-    PaintedLayer* layer = FrameLayerBuilder::GetDebugSingleOldPaintedLayerForFrame(frame);
+    PaintedLayer* layer = FrameLayerBuilder::GetDebugSingleOldLayerForFrame<PaintedLayer>(frame);
     if (!layer) {
       return NS_ERROR_FAILURE;
     }
 
     layers.PutEntry(layer);
   }
 
   *aResult = layers.Count();
--- a/dom/base/nsFrameLoader.cpp
+++ b/dom/base/nsFrameLoader.cpp
@@ -2952,19 +2952,36 @@ nsFrameLoader::TryRemoteBrowser()
 
   PROFILER_LABEL("nsFrameLoader", "CreateRemoteBrowser",
     js::ProfileEntry::Category::OTHER);
 
   MutableTabContext context;
   nsresult rv = GetNewTabContext(&context);
   NS_ENSURE_SUCCESS(rv, false);
 
+  uint64_t nextTabParentId = 0;
+  if (mOwnerContent) {
+    nsAutoString nextTabParentIdAttr;
+    mOwnerContent->GetAttr(kNameSpaceID_None, nsGkAtoms::nextTabParentId,
+                           nextTabParentIdAttr);
+    nextTabParentId = strtoull(NS_ConvertUTF16toUTF8(nextTabParentIdAttr).get(),
+                               nullptr, 10);
+
+    // We may be in a window that was just opened, so try the
+    // nsIBrowserDOMWindow API as a backup.
+    if (!nextTabParentId && window) {
+      Unused << window->GetNextTabParentId(&nextTabParentId);
+    }
+  }
+
   nsCOMPtr<Element> ownerElement = mOwnerContent;
   mRemoteBrowser = ContentParent::CreateBrowser(context, ownerElement,
-                                                openerContentParent, sameTabGroupAs);
+                                                openerContentParent,
+                                                sameTabGroupAs,
+                                                nextTabParentId);
   if (!mRemoteBrowser) {
     return false;
   }
   // Now that mRemoteBrowser is set, we can initialize the RenderFrameParent
   mRemoteBrowser->InitRenderFrame();
 
   MaybeUpdatePrimaryTabParent(eTabParentChanged);
 
--- a/dom/base/nsGkAtomList.h
+++ b/dom/base/nsGkAtomList.h
@@ -647,16 +647,17 @@ GK_ATOM(namespaceUri, "namespace-uri")
 GK_ATOM(NaN, "NaN")
 GK_ATOM(nativeAnonymousChildList, "nativeAnonymousChildList")
 GK_ATOM(nav, "nav")
 GK_ATOM(negate, "negate")
 GK_ATOM(never, "never")
 GK_ATOM(_new, "new")
 GK_ATOM(newline, "newline")
 GK_ATOM(nextBidi, "NextBidi")
+GK_ATOM(nextTabParentId, "nextTabParentId")
 GK_ATOM(no, "no")
 GK_ATOM(noautofocus, "noautofocus")
 GK_ATOM(noautohide, "noautohide")
 GK_ATOM(norolluponanchor, "norolluponanchor")
 GK_ATOM(nobr, "nobr")
 GK_ATOM(node, "node")
 GK_ATOM(nodefaultsrc, "nodefaultsrc")
 GK_ATOM(nodeSet, "node-set")
--- a/dom/base/nsGlobalWindow.cpp
+++ b/dom/base/nsGlobalWindow.cpp
@@ -11558,34 +11558,40 @@ nsGlobalWindow::ShowSlowScriptDialog()
   // If our document is not active, just kill the script: we've been unloaded
   if (!AsInner()->HasActiveDocument()) {
     return KillSlowScript;
   }
 
   // Check if we should offer the option to debug
   JS::AutoFilename filename;
   unsigned lineno;
-  bool hasFrame = JS::DescribeScriptedCaller(cx, &filename, &lineno);
+  // Computing the line number can be very expensive (see bug 1330231 for
+  // example), and we don't use the line number anywhere except than in the
+  // parent process, so we avoid computing it elsewhere.  This gives us most of
+  // the wins we are interested in, since the source of the slowness here is
+  // minified scripts which is more common in Web content that is loaded in the
+  // content process.
+  unsigned* linenop = XRE_IsParentProcess() ? &lineno : nullptr;
+  bool hasFrame = JS::DescribeScriptedCaller(cx, &filename, linenop);
 
   // Record the slow script event if we haven't done so already for this inner window
   // (which represents a particular page to the user).
   if (!mHasHadSlowScript) {
     Telemetry::Accumulate(Telemetry::SLOW_SCRIPT_PAGE_COUNT, 1);
   }
   mHasHadSlowScript = true;
 
   if (XRE_IsContentProcess() &&
       ProcessHangMonitor::Get()) {
     ProcessHangMonitor::SlowScriptAction action;
     RefPtr<ProcessHangMonitor> monitor = ProcessHangMonitor::Get();
     nsIDocShell* docShell = GetDocShell();
     nsCOMPtr<nsITabChild> child = docShell ? docShell->GetTabChild() : nullptr;
     action = monitor->NotifySlowScript(child,
-                                       filename.get(),
-                                       lineno);
+                                       filename.get());
     if (action == ProcessHangMonitor::Terminate) {
       return KillSlowScript;
     }
 
     if (action == ProcessHangMonitor::StartDebugger) {
       // Spin a nested event loop so that the debugger in the parent can fetch
       // any information it needs. Once the debugger has started, return to the
       // script.
--- a/dom/base/test/browser.ini
+++ b/dom/base/test/browser.ini
@@ -29,12 +29,11 @@ tags = mcb
 [browser_messagemanager_loadprocessscript.js]
 [browser_messagemanager_targetframeloader.js]
 [browser_messagemanager_unload.js]
 [browser_pagehide_on_tab_close.js]
 skip-if = e10s # this tests non-e10s behavior. it's not expected to work in e10s.
 [browser_state_notifications.js]
 skip-if = true # Bug 1271028
 [browser_use_counters.js]
-[browser_bug1307747.js]
 [browser_timeout_throttling_with_audio_playback.js]
 [browser_bug1303838.js]
 [browser_inputStream_structuredClone.js]
new file mode 100644
--- /dev/null
+++ b/dom/base/test/file_domwindowutils_animation.html
@@ -0,0 +1,151 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <meta charset="UTF-8">
+  <title>DOMWindowUtils test with animation</title>
+</head>
+<body>
+<script type="application/javascript">
+
+const SimpleTest = window.opener.SimpleTest;
+const utils = SpecialPowers.getDOMWindowUtils(window);
+const next = window.opener.next;
+const is = window.opener.is;
+const ok = window.opener.ok;
+
+function addStyle(rules) {
+  const extraStyle = document.createElement("style");
+  document.head.appendChild(extraStyle);
+  rules.forEach(rule => {
+    extraStyle.sheet.insertRule(rule, extraStyle.sheet.cssRules.length);
+  });
+}
+
+function deleteStyle() {
+  document.head.querySelector("style").remove();
+}
+
+
+function test_getUnanimatedComputedStyle() {
+  [
+    {
+      property: "opacity",
+      keyframes: [1, 0],
+      expectedInitialStyle: "1",
+      expectedDuringTransitionStyle: "0",
+      isDiscrete: false,
+    },
+    {
+      property: "clear",
+      keyframes: ["left", "inline-end"],
+      expectedInitialStyle: "none",
+      expectedDuringTransitionStyle: "inline-end",
+      isDiscrete: true,
+    },
+  ].forEach(testcase => {
+    const { property, keyframes, expectedInitialStyle,
+            expectedDuringTransitionStyle, isDiscrete } = testcase;
+
+    [null, "unset", "initial", "inherit"].forEach(initialStyle => {
+      const scriptAnimation = target => {
+        return target.animate({ [property]: keyframes }, 1000);
+      }
+      checkUnanimatedComputedStyle(property, initialStyle, null,
+                                   expectedInitialStyle, expectedInitialStyle,
+                                   scriptAnimation, "script animation");
+
+      const cssAnimationStyle = `@keyframes cssanimation {`
+                                + ` from { ${property}: ${ keyframes[0] }; }`
+                                + ` to { ${property}: ${ keyframes[1] }; } }`;
+      addStyle([cssAnimationStyle]);
+      const cssAnimation = target => {
+        target.style.animation = "cssanimation 1s";
+        return target.getAnimations()[0];
+      }
+      checkUnanimatedComputedStyle(property, initialStyle, null,
+                                   expectedInitialStyle, expectedInitialStyle,
+                                   cssAnimation, "CSS Animations");
+      deleteStyle();
+
+      // We don't support discrete animations for CSS Transitions yet.
+      // (bug 1320854)
+      if (!isDiscrete) {
+        const cssTransition = target => {
+          target.style[property] = keyframes[0];
+          target.style.transition =
+            `${ property } 1s`;
+          window.getComputedStyle(target)[property];
+          target.style[property] = keyframes[1];
+          return target.getAnimations()[0];
+        }
+        checkUnanimatedComputedStyle(property, initialStyle, null,
+                                     expectedInitialStyle,
+                                     expectedDuringTransitionStyle,
+                                     cssTransition, "CSS Transitions");
+      }
+
+      addStyle([cssAnimationStyle,
+                ".pseudo::before { animation: cssanimation 1s; }"]);
+      const pseudoAnimation = target => {
+        target.classList.add("pseudo");
+        return target.getAnimations({ subtree: true })[0];
+      }
+      checkUnanimatedComputedStyle(property, initialStyle, "::before",
+                                   expectedInitialStyle, expectedInitialStyle,
+                                   pseudoAnimation, "Animation at pseudo");
+      deleteStyle();
+    });
+  });
+
+  SimpleTest.doesThrow(
+    () => utils.getUnanimatedComputedStyle(div, null, "background"),
+    "NS_ERROR_INVALID_ARG",
+    "Shorthand property should throw");
+
+  SimpleTest.doesThrow(
+    () => utils.getUnanimatedComputedStyle(div, null, "invalid"),
+    "NS_ERROR_INVALID_ARG",
+    "Invalid property should throw");
+
+  SimpleTest.doesThrow(
+    () => utils.getUnanimatedComputedStyle(null, null, "opacity"),
+    "NS_ERROR_INVALID_ARG",
+    "Null element should throw");
+
+  next();
+  window.close();
+}
+
+function checkUnanimatedComputedStyle(property, initialStyle, pseudoType,
+                                      expectedBeforeAnimation,
+                                      expectedDuringAnimation,
+                                      animate, animationType) {
+  const div = document.createElement("div");
+  document.body.appendChild(div);
+
+  if (initialStyle) {
+    div.style[property] = initialStyle;
+  }
+
+  is(utils.getUnanimatedComputedStyle(div, pseudoType, property),
+     expectedBeforeAnimation,
+     `'${ property }' property with '${ initialStyle }' style `
+     + `should be '${ expectedBeforeAnimation }' `
+     + `before animating by ${ animationType }`);
+
+  const animation = animate(div);
+  animation.currentTime = 500;
+  is(utils.getUnanimatedComputedStyle(div, pseudoType, property),
+     expectedDuringAnimation,
+     `'${ property }' property with '${ initialStyle }' style `
+     + `should be '${ expectedDuringAnimation }' `
+     + `even while animating by ${ animationType }`);
+
+  div.remove();
+}
+
+window.addEventListener("load", test_getUnanimatedComputedStyle);
+
+</script>
+</body>
+</html>
--- a/dom/base/test/mochitest.ini
+++ b/dom/base/test/mochitest.ini
@@ -121,16 +121,17 @@ support-files =
   file_bug804395.jar
   file_bug869432.eventsource
   file_bug869432.eventsource^headers^
   file_bug902350.html
   file_bug902350_frame.html
   file_bug907892.html
   file_bug945152.jar
   file_bug1274806.html
+  file_domwindowutils_animation.html
   file_general_document.html
   file_htmlserializer_1.html
   file_htmlserializer_1_bodyonly.html
   file_htmlserializer_1_format.html
   file_htmlserializer_1_linebreak.html
   file_htmlserializer_1_links.html
   file_htmlserializer_1_nested_body.html
   file_htmlserializer_1_no_body.html
--- a/dom/base/test/test_domwindowutils.html
+++ b/dom/base/test/test_domwindowutils.html
@@ -147,132 +147,22 @@ function test_getAnimationType() {
     () => utils.getAnimationTypeForLonghand("invalid"),
     "NS_ERROR_ILLEGAL_VALUE",
     "Invalid property should throw");
 
   next();
 }
 
 function test_getUnanimatedComputedStyle() {
-  [
-    {
-      property: "opacity",
-      keyframes: [1, 0],
-      expectedInitialStyle: "1",
-      expectedDuringTransitionStyle: "0",
-      isDiscrete: false,
-    },
-    {
-      property: "clear",
-      keyframes: ["left", "inline-end"],
-      expectedInitialStyle: "none",
-      expectedDuringTransitionStyle: "inline-end",
-      isDiscrete: true,
-    },
-  ].forEach(testcase => {
-    const { property, keyframes, expectedInitialStyle,
-            expectedDuringTransitionStyle, isDiscrete } = testcase;
-
-    [null, "unset", "initial", "inherit"].forEach(initialStyle => {
-      const scriptAnimation = target => {
-        return target.animate({ [property]: keyframes }, 1000);
-      }
-      checkUnanimatedComputedStyle(property, initialStyle, null,
-                                   expectedInitialStyle, expectedInitialStyle,
-                                   scriptAnimation, "script animation");
-
-      const cssAnimationStyle = `@keyframes cssanimation {`
-                                + ` from { ${property}: ${ keyframes[0] }; }`
-                                + ` to { ${property}: ${ keyframes[1] }; } }`;
-      document.styleSheets[0].insertRule(cssAnimationStyle, 0);
-      const cssAnimation = target => {
-        target.style.animation = "cssanimation 1s";
-        return target.getAnimations()[0];
-      }
-      checkUnanimatedComputedStyle(property, initialStyle, null,
-                                   expectedInitialStyle, expectedInitialStyle,
-                                   cssAnimation, "CSS Animations");
-      document.styleSheets[0].deleteRule(0);
-
-      // We don't support discrete animations for CSS Transitions yet.
-      // (bug 1320854)
-      if (!isDiscrete) {
-        const cssTransition = target => {
-          target.style[property] = keyframes[0];
-          target.style.transition =
-            `${ property } 1s`;
-          window.getComputedStyle(target)[property];
-          target.style[property] = keyframes[1];
-          return target.getAnimations()[0];
-        }
-        checkUnanimatedComputedStyle(property, initialStyle, null,
-                                     expectedInitialStyle,
-                                     expectedDuringTransitionStyle,
-                                     cssTransition, "CSS Transitions");
-      }
-
-      document.styleSheets[0].insertRule(cssAnimationStyle, 0);
-      document.styleSheets[0].insertRule(
-        ".pseudo::before { animation: cssanimation 1s; }", 0);
-      const pseudoAnimation = target => {
-        target.classList.add("pseudo");
-        return target.getAnimations({ subtree: true })[0];
-      }
-      checkUnanimatedComputedStyle(property, initialStyle, "::before",
-                                   expectedInitialStyle, expectedInitialStyle,
-                                   pseudoAnimation, "Animation at pseudo");
-      document.styleSheets[0].deleteRule(0);
-      document.styleSheets[0].deleteRule(0);
-    });
-  });
-
-  SimpleTest.doesThrow(
-    () => utils.getUnanimatedComputedStyle(div, null, "background"),
-    "NS_ERROR_INVALID_ARG",
-    "Shorthand property should throw");
-
-  SimpleTest.doesThrow(
-    () => utils.getUnanimatedComputedStyle(div, null, "invalid"),
-    "NS_ERROR_INVALID_ARG",
-    "Invalid property should throw");
-
-  SimpleTest.doesThrow(
-    () => utils.getUnanimatedComputedStyle(null, null, "opacity"),
-    "NS_ERROR_INVALID_ARG",
-    "Null element should throw");
-
-  next();
-}
-
-function checkUnanimatedComputedStyle(property, initialStyle, pseudoType,
-                                      expectedBeforeAnimation,
-                                      expectedDuringAnimation,
-                                      animate, animationType) {
-  const div = document.createElement("div");
-  document.body.appendChild(div);
-
-  if (initialStyle) {
-    div.style[property] = initialStyle;
-  }
-
-  is(utils.getUnanimatedComputedStyle(div, pseudoType, property),
-     expectedBeforeAnimation,
-     `'${ property }' property with '${ initialStyle }' style `
-     + `should be '${ expectedBeforeAnimation }' `
-     + `before animating by ${ animationType }`);
-
-  const animation = animate(div);
-  animation.currentTime = 500;
-  is(utils.getUnanimatedComputedStyle(div, pseudoType, property),
-     expectedDuringAnimation,
-     `'${ property }' property with '${ initialStyle }' style `
-     + `should be '${ expectedDuringAnimation }' `
-     + `even while animating by ${ animationType }`);
-
-  div.remove();
+  SpecialPowers.pushPrefEnv(
+    { set: [["dom.animations-api.core.enabled", true]] },
+    () => {
+      window.open("file_domwindowutils_animation.html");
+    }
+  );
 }
 
 var tests = [
   test_sendMouseEventDefaults,
   test_sendMouseEventOptionals,
   test_getAnimationType,
   test_getUnanimatedComputedStyle
 ];
--- a/dom/broadcastchannel/BroadcastChannelParent.cpp
+++ b/dom/broadcastchannel/BroadcastChannelParent.cpp
@@ -2,18 +2,19 @@
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "BroadcastChannelParent.h"
 #include "BroadcastChannelService.h"
 #include "mozilla/dom/File.h"
-#include "mozilla/dom/ipc/BlobParent.h"
+#include "mozilla/dom/IPCBlobUtils.h"
 #include "mozilla/ipc/BackgroundParent.h"
+#include "mozilla/ipc/IPCStreamUtils.h"
 #include "mozilla/Unused.h"
 #include "nsIScriptSecurityManager.h"
 
 namespace mozilla {
 
 using namespace ipc;
 
 namespace dom {
@@ -68,35 +69,10 @@ BroadcastChannelParent::ActorDestroy(Act
 
   if (mService) {
     // This object is about to be released and with it, also mService will be
     // released too.
     mService->UnregisterActor(this, mOriginChannelKey);
   }
 }
 
-void
-BroadcastChannelParent::Deliver(const ClonedMessageData& aData)
-{
-  AssertIsOnBackgroundThread();
-
-  // Duplicate the data for this parent.
-  ClonedMessageData newData(aData);
-
-  // Create new BlobParent objects for this message.
-  for (uint32_t i = 0, len = newData.blobsParent().Length(); i < len; ++i) {
-    RefPtr<BlobImpl> impl =
-      static_cast<BlobParent*>(newData.blobsParent()[i])->GetBlobImpl();
-
-    PBlobParent* blobParent =
-      BackgroundParent::GetOrCreateActorForBlobImpl(Manager(), impl);
-    if (!blobParent) {
-      return;
-    }
-
-    newData.blobsParent()[i] = blobParent;
-  }
-
-  Unused << SendNotify(newData);
-}
-
 } // namespace dom
 } // namespace mozilla
--- a/dom/broadcastchannel/BroadcastChannelParent.h
+++ b/dom/broadcastchannel/BroadcastChannelParent.h
@@ -21,19 +21,16 @@ namespace dom {
 class BroadcastChannelService;
 
 class BroadcastChannelParent final : public PBroadcastChannelParent
 {
   friend class mozilla::ipc::BackgroundParentImpl;
 
   typedef mozilla::ipc::PrincipalInfo PrincipalInfo;
 
-public:
-  void Deliver(const ClonedMessageData& aData);
-
 private:
   explicit BroadcastChannelParent(const nsAString& aOriginChannelKey);
   ~BroadcastChannelParent();
 
   virtual mozilla::ipc::IPCResult
   RecvPostMessage(const ClonedMessageData& aData) override;
 
   virtual mozilla::ipc::IPCResult RecvClose() override;
--- a/dom/broadcastchannel/BroadcastChannelService.cpp
+++ b/dom/broadcastchannel/BroadcastChannelService.cpp
@@ -2,17 +2,17 @@
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "BroadcastChannelService.h"
 #include "BroadcastChannelParent.h"
 #include "mozilla/dom/File.h"
-#include "mozilla/dom/ipc/BlobParent.h"
+#include "mozilla/dom/IPCBlobUtils.h"
 #include "mozilla/ipc/BackgroundParent.h"
 
 #ifdef XP_WIN
 #undef PostMessage
 #endif
 
 namespace mozilla {
 
@@ -101,32 +101,50 @@ BroadcastChannelService::PostMessage(Bro
   MOZ_ASSERT(aParent);
 
   nsTArray<BroadcastChannelParent*>* parents;
   if (!mAgents.Get(aOriginChannelKey, &parents)) {
     MOZ_CRASH("Invalid state");
   }
 
   // We need to keep the array alive for the life-time of this operation.
-  nsTArray<RefPtr<BlobImpl>> blobs;
-  if (!aData.blobsParent().IsEmpty()) {
-    blobs.SetCapacity(aData.blobsParent().Length());
+  nsTArray<RefPtr<BlobImpl>> blobImpls;
+  if (!aData.blobs().IsEmpty()) {
+    blobImpls.SetCapacity(aData.blobs().Length());
 
-    for (uint32_t i = 0, len = aData.blobsParent().Length(); i < len; ++i) {
-      RefPtr<BlobImpl> impl =
-        static_cast<BlobParent*>(aData.blobsParent()[i])->GetBlobImpl();
-     MOZ_ASSERT(impl);
-     blobs.AppendElement(impl);
+    for (uint32_t i = 0, len = aData.blobs().Length(); i < len; ++i) {
+      RefPtr<BlobImpl> impl = IPCBlobUtils::Deserialize(aData.blobs()[i]);
+
+      MOZ_ASSERT(impl);
+      blobImpls.AppendElement(impl);
     }
   }
 
+  // For each parent actor, we notify the message.
   for (uint32_t i = 0; i < parents->Length(); ++i) {
     BroadcastChannelParent* parent = parents->ElementAt(i);
     MOZ_ASSERT(parent);
 
-    if (parent != aParent) {
-      parent->Deliver(aData);
+    if (parent == aParent) {
+      continue;
     }
+
+    // We need to have a copy of the data for this parent.
+    ClonedMessageData newData(aData);
+    MOZ_ASSERT(blobImpls.Length() == newData.blobs().Length());
+
+    if (!blobImpls.IsEmpty()) {
+      // Serialize Blob objects for this message.
+      for (uint32_t i = 0, len = blobImpls.Length(); i < len; ++i) {
+        nsresult rv = IPCBlobUtils::Serialize(blobImpls[i], parent->Manager(),
+                                              newData.blobs()[i]);
+        if (NS_WARN_IF(NS_FAILED(rv))) {
+          return;
+        }
+      }
+    }
+
+    Unused << parent->SendNotify(newData);
   }
 }
 
 } // namespace dom
 } // namespace mozilla
--- a/dom/broadcastchannel/PBroadcastChannel.ipdl
+++ b/dom/broadcastchannel/PBroadcastChannel.ipdl
@@ -1,16 +1,17 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 include protocol PBackground;
 include protocol PBlob;
 include protocol PChildToParentStream;
 include protocol PFileDescriptorSet;
+include protocol PIPCBlobInputStream;
 include protocol PParentToChildStream;
 include DOMTypes;
 
 using struct mozilla::SerializedStructuredCloneBuffer from "ipc/IPCMessageUtils.h";
 
 namespace mozilla {
 namespace dom {
 
--- a/dom/canvas/CanvasRenderingContext2D.cpp
+++ b/dom/canvas/CanvasRenderingContext2D.cpp
@@ -5574,25 +5574,28 @@ CanvasRenderingContext2D::DrawWindow(nsG
 
   // protect against too-large surfaces that will cause allocation
   // or overflow issues
   if (!Factory::CheckSurfaceSize(IntSize(int32_t(aW), int32_t(aH)), 0xffff)) {
     aError.Throw(NS_ERROR_FAILURE);
     return;
   }
 
+  // Flush layout updates
+  if (!(aFlags & nsIDOMCanvasRenderingContext2D::DRAWWINDOW_DO_NOT_FLUSH)) {
+    nsContentUtils::FlushLayoutForTree(aWindow.AsInner()->GetOuterWindow());
+  }
+
   CompositionOp op = UsedOperation();
   bool discardContent = GlobalAlpha() == 1.0f
     && (op == CompositionOp::OP_OVER || op == CompositionOp::OP_SOURCE);
   const gfx::Rect drawRect(aX, aY, aW, aH);
   EnsureTarget(discardContent ? &drawRect : nullptr);
-
-  // Flush layout updates
-  if (!(aFlags & nsIDOMCanvasRenderingContext2D::DRAWWINDOW_DO_NOT_FLUSH)) {
-    nsContentUtils::FlushLayoutForTree(aWindow.AsInner()->GetOuterWindow());
+  if (!IsTargetValid()) {
+    return;
   }
 
   RefPtr<nsPresContext> presContext;
   nsIDocShell* docshell = aWindow.GetDocShell();
   if (docshell) {
     docshell->GetPresContext(getter_AddRefs(presContext));
   }
   if (!presContext) {
--- a/dom/events/TouchEvent.cpp
+++ b/dom/events/TouchEvent.cpp
@@ -218,18 +218,18 @@ TouchEvent::PrefEnabled(nsIDocShell* aDo
         // into the cached state. If APZ is enabled, we need to further check
         // based on the widget, which we do below (and don't cache that result).
         sIsTouchDeviceSupportPresent &= gfxPlatform::AsyncPanZoomEnabled();
       }
       enabled = sIsTouchDeviceSupportPresent;
       if (enabled && aDocShell) {
         // APZ might be disabled on this particular widget, in which case
         // TouchEvent support will also be disabled. Try to detect that.
-        nsPresContext* pc = nullptr;
-        aDocShell->GetPresContext(&pc);
+        RefPtr<nsPresContext> pc;
+        aDocShell->GetPresContext(getter_AddRefs(pc));
         if (pc && pc->GetRootWidget()) {
           enabled &= pc->GetRootWidget()->AsyncPanZoomEnabled();
         }
       }
 #else
       enabled = false;
 #endif
     } else {
--- a/dom/events/test/pointerevents/test_trigger_fullscreen_by_pointer_events.html
+++ b/dom/events/test/pointerevents/test_trigger_fullscreen_by_pointer_events.html
@@ -3,51 +3,54 @@
 <head>
   <meta charset="utf-8">
   <title>Test for triggering Fullscreen by pointer events</title>
   <script src="/tests/SimpleTest/SimpleTest.js"></script>
   <script src="/tests/SimpleTest/EventUtils.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
 </head>
 <body>
-<div id="target" style="width: 50px; height: 50px; background: green"></div>
 <script>
-
 SimpleTest.waitForExplicitFinish();
 
-var target = document.getElementById("target");
-target.addEventListener("pointerdown", () => {
-  target.requestFullscreen();
-  target.addEventListener("pointerdown", () => {
-    document.exitFullscreen();
-  }, {once: true});
-}, {once: true});
+function startTest() {
+  let win = window.open("data:text/html,<body><div id='target' style='width: 50px; height: 50px; background: green'></div></body>", "_blank");
+  win.addEventListener("load", () => {
+    let target = win.document.getElementById("target");
+    target.addEventListener("pointerdown", () => {
+      target.requestFullscreen();
+      target.addEventListener("pointerdown", () => {
+        win.document.exitFullscreen();
+      }, {once: true});
+    }, {once: true});
 
-document.addEventListener("fullscreenchange", () => {
-  if (document.fullscreenElement) {
-    ok(document.fullscreenElement, target, "fullscreenElement should be the div element");
-    // synthesize mouse events to generate pointer events and leave full screen.
-    synthesizeMouseAtCenter(target, { type: "mousedown" });
-    synthesizeMouseAtCenter(target, { type: "mouseup" });
-  } else {
-    SimpleTest.finish();
-  }
-});
-
-function startTest() {
-  // synthesize mouse events to generate pointer events and enter full screen.
-  synthesizeMouseAtCenter(target, { type: "mousedown" });
-  synthesizeMouseAtCenter(target, { type: "mouseup" });
+    win.document.addEventListener("fullscreenchange", () => {
+      if (win.document.fullscreenElement) {
+        ok(win.document.fullscreenElement, target, "fullscreenElement should be the div element");
+        // synthesize mouse events to generate pointer events and leave full screen.
+        synthesizeMouseAtCenter(target, { type: "mousedown" }, win);
+        synthesizeMouseAtCenter(target, { type: "mouseup" }, win);
+      } else {
+        win.close();
+        SimpleTest.finish();
+      }
+    });
+    // Make sure our window is focused before starting the test
+    SimpleTest.waitForFocus(() => {
+      // synthesize mouse events to generate pointer events and enter full screen.
+      synthesizeMouseAtCenter(target, { type: "mousedown" }, win);
+      synthesizeMouseAtCenter(target, { type: "mouseup" }, win);
+    }, win);
+  });
 }
 
 SimpleTest.waitForFocus(() => {
   SpecialPowers.pushPrefEnv({
     "set": [
       ["full-screen-api.unprefix.enabled", true],
       ["full-screen-api.allow-trusted-requests-only", false],
       ["dom.w3c_pointer_events.enabled", true]
     ]
   }, startTest);
 });
-
 </script>
 </body>
 </html>
--- a/dom/file/FileReader.cpp
+++ b/dom/file/FileReader.cpp
@@ -285,20 +285,30 @@ FileReader::DoReadData(uint64_t aCount)
     uint32_t oldLen = mResult.Length();
     MOZ_ASSERT(mResult.Length() == mDataLen, "unexpected mResult length");
     if (uint64_t(oldLen) + aCount > UINT32_MAX)
       return NS_ERROR_OUT_OF_MEMORY;
     char16_t *buf = nullptr;
     mResult.GetMutableData(&buf, oldLen + aCount, fallible);
     NS_ENSURE_TRUE(buf, NS_ERROR_OUT_OF_MEMORY);
 
+    nsresult rv;
+
+    // nsFileStreams do not implement ReadSegment. In case here we are dealing
+    // with a nsIAsyncInputStream, in content process, we need to wrap a
+    // nsIBufferedInputStream around it.
+    if (!mBufferedStream) {
+      rv = NS_NewBufferedInputStream(getter_AddRefs(mBufferedStream),
+                                     mAsyncStream, 8192);
+      NS_ENSURE_SUCCESS(rv, rv);
+    }
+
     uint32_t bytesRead = 0;
-    nsresult rv =
-      mAsyncStream->ReadSegments(ReadFuncBinaryString, buf + oldLen, aCount,
-                                 &bytesRead);
+    rv = mBufferedStream->ReadSegments(ReadFuncBinaryString, buf + oldLen,
+                                       aCount, &bytesRead);
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return rv;
     }
 
     MOZ_ASSERT(bytesRead == aCount, "failed to read data");
   }
   else {
     CheckedInt<uint64_t> size = mDataLen;
@@ -345,16 +355,17 @@ FileReader::ReadFileContent(Blob& aBlob,
   }
 
   mError = nullptr;
 
   SetDOMStringToNull(mResult);
   mResultArrayBuffer = nullptr;
 
   mAsyncStream = nullptr;
+  mBufferedStream = nullptr;
 
   mTransferred = 0;
   mTotal = 0;
   mReadyState = EMPTY;
   FreeFileData();
 
   mBlob = &aBlob;
   mDataFormat = aDataFormat;
@@ -369,37 +380,40 @@ FileReader::ReadFileContent(Blob& aBlob,
   }
 
   nsCOMPtr<nsIInputStream> stream;
   mBlob->GetInternalStream(getter_AddRefs(stream), aRv);
   if (NS_WARN_IF(aRv.Failed())) {
     return;
   }
 
-  nsCOMPtr<nsITransport> transport;
-  aRv = sts->CreateInputTransport(stream,
-                                  /* aStartOffset */ 0,
-                                  /* aReadLimit */ -1,
-                                  /* aCloseWhenDone */ true,
-                                  getter_AddRefs(transport));
-  if (NS_WARN_IF(aRv.Failed())) {
-    return;
+  mAsyncStream = do_QueryInterface(stream);
+  if (!mAsyncStream) {
+    nsCOMPtr<nsITransport> transport;
+    aRv = sts->CreateInputTransport(stream,
+                                    /* aStartOffset */ 0,
+                                    /* aReadLimit */ -1,
+                                    /* aCloseWhenDone */ true,
+                                    getter_AddRefs(transport));
+    if (NS_WARN_IF(aRv.Failed())) {
+      return;
+    }
+
+    nsCOMPtr<nsIInputStream> wrapper;
+    aRv = transport->OpenInputStream(/* aFlags */ 0,
+                                     /* aSegmentSize */ 0,
+                                     /* aSegmentCount */ 0,
+                                     getter_AddRefs(wrapper));
+    if (NS_WARN_IF(aRv.Failed())) {
+      return;
+    }
+
+    mAsyncStream = do_QueryInterface(wrapper);
   }
 
-  nsCOMPtr<nsIInputStream> wrapper;
-  aRv = transport->OpenInputStream(/* aFlags */ 0,
-                                   /* aSegmentSize */ 0,
-                                   /* aSegmentCount */ 0,
-                                   getter_AddRefs(wrapper));
-  if (NS_WARN_IF(aRv.Failed())) {
-    return;
-  }
-
-  MOZ_ASSERT(!mAsyncStream);
-  mAsyncStream = do_QueryInterface(wrapper);
   MOZ_ASSERT(mAsyncStream);
 
   mTotal = mBlob->GetSize(aRv);
   if (NS_WARN_IF(aRv.Failed())) {
     return;
   }
 
   if (mDataFormat == FILE_AS_ARRAYBUFFER) {
@@ -524,31 +538,33 @@ FileReader::ClearProgressEventTimer()
 }
 
 void
 FileReader::FreeDataAndDispatchSuccess()
 {
   FreeFileData();
   mResult.SetIsVoid(false);
   mAsyncStream = nullptr;
+  mBufferedStream = nullptr;
   mBlob = nullptr;
 
   // Dispatch event to signify end of a successful operation
   DispatchProgressEvent(NS_LITERAL_STRING(LOAD_STR));
   DispatchProgressEvent(NS_LITERAL_STRING(LOADEND_STR));
 }
 
 void
 FileReader::FreeDataAndDispatchError()
 {
   MOZ_ASSERT(mError);
 
   FreeFileData();
   mResult.SetIsVoid(true);
   mAsyncStream = nullptr;
+  mBufferedStream = nullptr;
   mBlob = nullptr;
 
   // Dispatch error event to signify load failure
   DispatchProgressEvent(NS_LITERAL_STRING(ERROR_STR));
   DispatchProgressEvent(NS_LITERAL_STRING(LOADEND_STR));
 }
 
 void
@@ -724,16 +740,17 @@ FileReader::Abort()
   // XXX The spec doesn't say this
   mError = new DOMError(GetOwner(), NS_LITERAL_STRING("AbortError"));
 
   // Revert status and result attributes
   SetDOMStringToNull(mResult);
   mResultArrayBuffer = nullptr;
 
   mAsyncStream = nullptr;
+  mBufferedStream = nullptr;
   mBlob = nullptr;
 
   //Clean up memory buffer
   FreeFileData();
 
   // Dispatch the events
   DispatchProgressEvent(NS_LITERAL_STRING(ABORT_STR));
   DispatchProgressEvent(NS_LITERAL_STRING(LOADEND_STR));
@@ -777,16 +794,21 @@ FileReader::Shutdown()
 {
   mReadyState = DONE;
 
   if (mAsyncStream) {
     mAsyncStream->Close();
     mAsyncStream = nullptr;
   }
 
+  if (mBufferedStream) {
+    mBufferedStream->Close();
+    mBufferedStream = nullptr;
+  }
+
   FreeFileData();
   mResultArrayBuffer = nullptr;
 
   if (mWorkerPrivate && mBusyCount != 0) {
     ReleaseWorker();
     mWorkerPrivate = nullptr;
     mBusyCount = 0;
   }
--- a/dom/file/FileReader.h
+++ b/dom/file/FileReader.h
@@ -175,16 +175,17 @@ private:
 
   JS::Heap<JSObject*> mResultArrayBuffer;
 
   nsCOMPtr<nsITimer> mProgressNotifier;
   bool mProgressEventWasDelayed;
   bool mTimerIsActive;
 
   nsCOMPtr<nsIAsyncInputStream> mAsyncStream;
+  nsCOMPtr<nsIInputStream> mBufferedStream;
 
   RefPtr<DOMError> mError;
 
   eReadyState mReadyState;
 
   uint64_t mTotal;
   uint64_t mTransferred;
 
--- a/dom/file/ipc/Blob.cpp
+++ b/dom/file/ipc/Blob.cpp
@@ -666,19 +666,22 @@ SerializeInputStreamInChunks(nsIInputStr
   }
 
   return child;
 }
 
 void
 DeleteStreamMemoryFromBlobDataStream(BlobDataStream& aStream)
 {
-  PMemoryStreamChild* actor = aStream.streamChild();
-  if (actor) {
-    actor->Send__delete__(actor);
+  if (aStream.type() == BlobDataStream::TMemoryBlobDataStream) {
+    PMemoryStreamChild* actor =
+      aStream.get_MemoryBlobDataStream().streamChild();
+    if (actor) {
+      actor->Send__delete__(actor);
+    }
   }
 }
 
 void
 DeleteStreamMemoryFromBlobData(BlobData& aBlobData)
 {
   switch (aBlobData.type()) {
     case BlobData::TBlobDataStream:
@@ -748,28 +751,47 @@ CreateBlobImpl(const nsID& aKnownBlobIDD
 }
 
 already_AddRefed<BlobImpl>
 CreateBlobImpl(const BlobDataStream& aStream,
                const CreateBlobImplMetadata& aMetadata)
 {
   MOZ_ASSERT(gProcessType == GeckoProcessType_Default);
 
-  MemoryStreamParent* actor =
-    static_cast<MemoryStreamParent*>(aStream.streamParent());
-
   nsCOMPtr<nsIInputStream> inputStream;
-  actor->GetStream(getter_AddRefs(inputStream));
-  if (!inputStream) {
-    ASSERT_UNLESS_FUZZING();
-    return nullptr;
+  uint64_t length;
+
+  if (aStream.type() == BlobDataStream::TMemoryBlobDataStream) {
+    const MemoryBlobDataStream& memoryBlobDataStream =
+      aStream.get_MemoryBlobDataStream();
+
+    MemoryStreamParent* actor =
+      static_cast<MemoryStreamParent*>(memoryBlobDataStream.streamParent());
+
+    actor->GetStream(getter_AddRefs(inputStream));
+    if (!inputStream) {
+      ASSERT_UNLESS_FUZZING();
+      return nullptr;
+    }
+
+    length = memoryBlobDataStream.length();
+  } else {
+    MOZ_ASSERT(aStream.type() == BlobDataStream::TIPCStream);
+    inputStream = DeserializeIPCStream(aStream.get_IPCStream());
+    if (!inputStream) {
+      ASSERT_UNLESS_FUZZING();
+      return nullptr;
+    }
+
+    nsresult rv = inputStream->Available(&length);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return nullptr;
+    }
   }
 
-  uint64_t length = aStream.length();
-
   RefPtr<BlobImpl> blobImpl;
   if (!aMetadata.mHasRecursed && aMetadata.IsFile()) {
     if (length) {
       blobImpl =
         StreamBlobImpl::Create(inputStream,
                                aMetadata.mName,
                                aMetadata.mContentType,
                                aMetadata.mLastModifiedDate,
@@ -946,17 +968,18 @@ CreateBlobImpl(const ParentBlobConstruct
   RefPtr<BlobImpl> blobImpl =
     CreateBlobImplFromBlobData(aBlobData, metadata);
   return blobImpl.forget();
 }
 
 template <class ChildManagerType>
 bool
 BlobDataFromBlobImpl(ChildManagerType* aManager, BlobImpl* aBlobImpl,
-                     BlobData& aBlobData)
+                     BlobData& aBlobData,
+                     nsTArray<UniquePtr<AutoIPCStream>>& aIPCStreams)
 {
   MOZ_ASSERT(gProcessType != GeckoProcessType_Default);
   MOZ_ASSERT(aBlobImpl);
 
   const nsTArray<RefPtr<BlobImpl>>* subBlobs = aBlobImpl->GetSubBlobImpls();
 
   if (subBlobs) {
     MOZ_ASSERT(subBlobs->Length());
@@ -965,17 +988,17 @@ BlobDataFromBlobImpl(ChildManagerType* a
 
     nsTArray<BlobData>& subBlobDatas = aBlobData.get_ArrayOfBlobData();
     subBlobDatas.SetLength(subBlobs->Length());
 
     for (uint32_t count = subBlobs->Length(), index = 0;
          index < count;
          index++) {
       if (!BlobDataFromBlobImpl(aManager, subBlobs->ElementAt(index),
-                                subBlobDatas[index])) {
+                                subBlobDatas[index], aIPCStreams)) {
         return false;
       }
     }
 
     return true;
   }
 
   nsCOMPtr<nsIRemoteBlob> remoteBlob = do_QueryInterface(aBlobImpl);
@@ -990,23 +1013,43 @@ BlobDataFromBlobImpl(ChildManagerType* a
   ErrorResult rv;
   uint64_t length = aBlobImpl->GetSize(rv);
   MOZ_ALWAYS_TRUE(!rv.Failed());
 
   nsCOMPtr<nsIInputStream> inputStream;
   aBlobImpl->GetInternalStream(getter_AddRefs(inputStream), rv);
   MOZ_ALWAYS_TRUE(!rv.Failed());
 
+  nsCOMPtr<nsIIPCSerializableInputStream> serializable =
+    do_QueryInterface(inputStream);
+
+  // ExpectedSerializedLength() returns the length of the stream if serialized.
+  // This is useful to decide if we want to continue using the serialization
+  // directly, or if it's better to use IPCStream.
+  uint64_t expectedLength =
+    serializable ? serializable->ExpectedSerializedLength().valueOr(0) : 0;
+
+  // If a stream is known to be larger than 1MB, prefer sending it in chunks.
+  const uint64_t kTooLargeStream = 1024 * 1024;
+  if (serializable && expectedLength < kTooLargeStream) {
+    UniquePtr<AutoIPCStream> autoStream(new AutoIPCStream());
+    autoStream->Serialize(inputStream, aManager);
+    aBlobData = autoStream->TakeValue();
+
+    aIPCStreams.AppendElement(Move(autoStream));
+    return true;
+  }
+
   PMemoryStreamChild* streamActor =
     SerializeInputStreamInChunks(inputStream, length, aManager);
   if (!streamActor) {
     return false;
   }
 
-  aBlobData = BlobDataStream(nullptr, streamActor, length);
+  aBlobData = MemoryBlobDataStream(nullptr, streamActor, length);
   return true;
 }
 
 RemoteInputStream::RemoteInputStream(BlobImpl* aBlobImpl,
                                      uint64_t aStart,
                                      uint64_t aLength)
   : mMonitor("RemoteInputStream.mMonitor")
   , mActor(nullptr)
@@ -3592,28 +3635,30 @@ BlobChild::GetOrCreateFromImpl(ChildMana
   if (NS_WARN_IF(NS_FAILED(aBlobImpl->SetMutable(false)))) {
     return nullptr;
   }
 
   MOZ_ASSERT(!aBlobImpl->IsSizeUnknown());
   MOZ_ASSERT(!aBlobImpl->IsDateUnknown());
 
   AnyBlobConstructorParams blobParams;
+  nsTArray<UniquePtr<AutoIPCStream>> autoIPCStreams;
 
   if (gProcessType == GeckoProcessType_Default) {
     RefPtr<BlobImpl> sameProcessImpl = aBlobImpl;
     auto addRefedBlobImpl =
       reinterpret_cast<intptr_t>(sameProcessImpl.forget().take());
 
     blobParams = SameProcessBlobConstructorParams(addRefedBlobImpl);
   } else {
     // BlobData is going to be populate here and it _must_ be send via IPC in
     // order to avoid leaks.
     BlobData blobData;
-    if (NS_WARN_IF(!BlobDataFromBlobImpl(aManager, aBlobImpl, blobData))) {
+    if (NS_WARN_IF(!BlobDataFromBlobImpl(aManager, aBlobImpl, blobData,
+                                         autoIPCStreams))) {
       return nullptr;
     }
 
     nsString contentType;
     aBlobImpl->GetType(contentType);
 
     ErrorResult rv;
     uint64_t length = aBlobImpl->GetSize(rv);
@@ -3642,16 +3687,17 @@ BlobChild::GetOrCreateFromImpl(ChildMana
   ParentBlobConstructorParams params(blobParams);
 
   if (NS_WARN_IF(!aManager->SendPBlobConstructor(actor, params))) {
     return nullptr;
   }
 
   DeleteStreamMemory(params.blobParams());
 
+  autoIPCStreams.Clear();
   return actor;
 }
 
 // static
 template <class ChildManagerType>
 BlobChild*
 BlobChild::CreateFromParams(ChildManagerType* aManager,
                             const ChildBlobConstructorParams& aParams)
new file mode 100644
--- /dev/null
+++ b/dom/file/ipc/IPCBlob.ipdlh
@@ -0,0 +1,60 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+include protocol PChildToParentStream;
+include protocol PParentToChildStream;
+include protocol PIPCBlobInputStream;
+
+include IPCStream;
+include ProtocolTypes;
+
+using struct mozilla::void_t
+  from "ipc/IPCMessageUtils.h";
+
+namespace mozilla {
+namespace dom {
+
+// This contains any extra bit for making a File out of a Blob.
+// For more information about Blobs and IPC, please read the comments in
+// IPCBlobUtils.h
+
+struct IPCFile
+{
+  nsString name;
+  int64_t lastModified;
+  nsString DOMPath;
+};
+
+// Union for blob vs file.
+union IPCFileUnion
+{
+  // For Blob.
+  void_t;
+
+  // For File.
+  IPCFile;
+};
+
+union IPCBlobStream
+{
+  // Parent to Child: The child will receive a IPCBlobInputStream. Nothing
+  // can be done with it except retrieving the size.
+  PIPCBlobInputStream;
+
+  // Child to Parent: Normal serialization.
+  IPCStream;
+};
+
+struct IPCBlob
+{
+  nsString type;
+  uint64_t size;
+
+  IPCBlobStream inputStream;
+
+  IPCFileUnion file;
+};
+
+} // namespace dom
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/file/ipc/IPCBlobInputStream.cpp
@@ -0,0 +1,357 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "IPCBlobInputStream.h"
+#include "IPCBlobInputStreamChild.h"
+#include "nsIAsyncInputStream.h"
+#include "mozilla/SystemGroup.h"
+
+namespace mozilla {
+namespace dom {
+
+namespace {
+
+class CallbackRunnable final : public CancelableRunnable
+{
+public:
+  static void
+  Execute(nsIInputStreamCallback* aCallback,
+          nsIEventTarget* aEventTarget,
+          IPCBlobInputStream* aStream)
+  {
+    RefPtr<CallbackRunnable> runnable =
+      new CallbackRunnable(aCallback, aStream);
+
+    nsCOMPtr<nsIEventTarget> target = aEventTarget;
+    if (!target) {
+      target = SystemGroup::EventTargetFor(TaskCategory::Other);
+    }
+
+    target->Dispatch(runnable, NS_DISPATCH_NORMAL);
+  }
+
+  NS_IMETHOD
+  Run() override
+  {
+    mCallback->OnInputStreamReady(mStream);
+    mCallback = nullptr;
+    mStream = nullptr;
+    return NS_OK;
+  }
+
+private:
+  CallbackRunnable(nsIInputStreamCallback* aCallback,
+                   IPCBlobInputStream* aStream)
+    : mCallback(aCallback)
+    , mStream(aStream)
+  {
+    MOZ_ASSERT(mCallback);
+    MOZ_ASSERT(mStream);
+  }
+
+  nsCOMPtr<nsIInputStreamCallback> mCallback;
+  RefPtr<IPCBlobInputStream> mStream;
+};
+
+} // anonymous
+
+NS_IMPL_ADDREF(IPCBlobInputStream);
+NS_IMPL_RELEASE(IPCBlobInputStream);
+
+NS_INTERFACE_MAP_BEGIN(IPCBlobInputStream)
+  NS_INTERFACE_MAP_ENTRY(nsIInputStream)
+  NS_INTERFACE_MAP_ENTRY(nsIAsyncInputStream)
+  NS_INTERFACE_MAP_ENTRY(nsIInputStreamCallback)
+  NS_INTERFACE_MAP_ENTRY(nsICloneableInputStream)
+  NS_INTERFACE_MAP_ENTRY(nsIIPCSerializableInputStream)
+  NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIInputStream)
+NS_INTERFACE_MAP_END
+
+IPCBlobInputStream::IPCBlobInputStream(IPCBlobInputStreamChild* aActor)
+  : mActor(aActor)
+  , mState(eInit)
+{
+  MOZ_ASSERT(aActor);
+}
+
+IPCBlobInputStream::~IPCBlobInputStream()
+{
+  Close();
+}
+
+// nsIInputStream interface
+
+NS_IMETHODIMP
+IPCBlobInputStream::Available(uint64_t* aLength)
+{
+  // We don't have a remoteStream yet. Let's return the full known size.
+  if (mState == eInit || mState == ePending) {
+    *aLength = mActor->Size();
+    return NS_OK;
+  }
+
+  if (mState == eRunning) {
+    MOZ_ASSERT(mRemoteStream);
+    return mRemoteStream->Available(aLength);
+  }
+
+  MOZ_ASSERT(mState == eClosed);
+  return NS_BASE_STREAM_CLOSED;
+}
+
+NS_IMETHODIMP
+IPCBlobInputStream::Read(char* aBuffer, uint32_t aCount, uint32_t* aReadCount)
+{
+  // Read is not available is we don't have a remoteStream.
+  if (mState == eInit || mState == ePending) {
+    return NS_BASE_STREAM_WOULD_BLOCK;
+  }
+
+  if (mState == eRunning) {
+    return mRemoteStream->Read(aBuffer, aCount, aReadCount);
+  }
+
+  MOZ_ASSERT(mState == eClosed);
+  return NS_BASE_STREAM_CLOSED;
+}
+
+NS_IMETHODIMP
+IPCBlobInputStream::ReadSegments(nsWriteSegmentFun aWriter, void* aClosure,
+                                 uint32_t aCount, uint32_t *aResult)
+{
+  // ReadSegments is not available is we don't have a remoteStream.
+  if (mState == eInit || mState == ePending) {
+    return NS_BASE_STREAM_WOULD_BLOCK;
+  }
+
+  if (mState == eRunning) {
+    return mRemoteStream->ReadSegments(aWriter, aClosure, aCount, aResult);
+  }
+
+  MOZ_ASSERT(mState == eClosed);
+  return NS_BASE_STREAM_CLOSED;
+}
+
+NS_IMETHODIMP
+IPCBlobInputStream::IsNonBlocking(bool* aNonBlocking)
+{
+  *aNonBlocking = true;
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+IPCBlobInputStream::Close()
+{
+  if (mActor) {
+    mActor->ForgetStream(this);
+    mActor = nullptr;
+  }
+
+  if (mRemoteStream) {
+    mRemoteStream->Close();
+    mRemoteStream = nullptr;
+  }
+
+  mCallback = nullptr;
+
+  mState = eClosed;
+  return NS_OK;
+}
+
+// nsICloneableInputStream interface
+
+NS_IMETHODIMP
+IPCBlobInputStream::GetCloneable(bool* aCloneable)
+{
+  *aCloneable = mState != eClosed;
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+IPCBlobInputStream::Clone(nsIInputStream** aResult)
+{
+  if (mState == eClosed) {
+    return NS_BASE_STREAM_CLOSED;
+  }
+
+  MOZ_ASSERT(mActor);
+
+  nsCOMPtr<nsIInputStream> stream = mActor->CreateStream();
+  stream.forget(aResult);
+  return NS_OK;
+}
+
+// nsIAsyncInputStream interface
+
+NS_IMETHODIMP
+IPCBlobInputStream::CloseWithStatus(nsresult aStatus)
+{
+  return Close();
+}
+
+NS_IMETHODIMP
+IPCBlobInputStream::AsyncWait(nsIInputStreamCallback* aCallback,
+                              uint32_t aFlags, uint32_t aRequestedCount,
+                              nsIEventTarget* aEventTarget)
+{
+  // See IPCBlobInputStream.h for more information about this state machine.
+
+  switch (mState) {
+  // First call, we need to retrieve the stream from the parent actor.
+  case eInit:
+    MOZ_ASSERT(mActor);
+
+    mCallback = aCallback;
+    mCallbackEventTarget = aEventTarget;
+    mState = ePending;
+
+    mActor->StreamNeeded(this);
+    return NS_OK;
+
+  // We are still waiting for the remote inputStream
+  case ePending:
+    if (mCallback && aCallback) {
+      return NS_ERROR_FAILURE;
+    }
+
+    mCallback = aCallback;
+    mCallbackEventTarget = aEventTarget;
+    return NS_OK;
+
+  // We have the remote inputStream, let's check if we can execute the callback.
+  case eRunning:
+    return MaybeExecuteCallback(aCallback, aEventTarget);
+
+  // Stream is closed.
+  default:
+    MOZ_ASSERT(mState == eClosed);
+    return NS_BASE_STREAM_CLOSED;
+  }
+}
+
+void
+IPCBlobInputStream::StreamReady(nsIInputStream* aInputStream)
+{
+  // We have been closed in the meantime.
+  if (mState == eClosed) {
+    if (aInputStream) {
+      aInputStream->Close();
+    }
+    return;
+  }
+
+  // If aInputStream is null, it means that the serialization went wrong or the
+  // stream is not available anymore. We keep the state as pending just to block
+  // any additional operation.
+
+  nsCOMPtr<nsIInputStreamCallback> callback;
+  callback.swap(mCallback);
+
+  nsCOMPtr<nsIEventTarget> callbackEventTarget;
+  callbackEventTarget.swap(mCallbackEventTarget);
+
+  if (aInputStream && callback) {
+    MOZ_ASSERT(mState == ePending);
+
+    mRemoteStream = aInputStream;
+    mState = eRunning;
+
+    MaybeExecuteCallback(callback, callbackEventTarget);
+  }
+}
+
+nsresult
+IPCBlobInputStream::MaybeExecuteCallback(nsIInputStreamCallback* aCallback,
+                                         nsIEventTarget* aCallbackEventTarget)
+{
+  MOZ_ASSERT(mState == eRunning);
+  MOZ_ASSERT(mRemoteStream);
+
+  // If the stream supports nsIAsyncInputStream, we need to call its AsyncWait
+  // and wait for OnInputStreamReady.
+  nsCOMPtr<nsIAsyncInputStream> asyncStream = do_QueryInterface(mRemoteStream);
+  if (asyncStream) {
+    // If the callback has been already set, we return an error.
+    if (mCallback && aCallback) {
+      return NS_ERROR_FAILURE;
+    }
+
+    mCallback = aCallback;
+    mCallbackEventTarget = aCallbackEventTarget;
+
+    if (!mCallback) {
+      return NS_OK;
+    }
+
+    nsCOMPtr<nsIEventTarget> target = NS_GetCurrentThread();
+    return asyncStream->AsyncWait(this, 0, 0, target);
+  }
+
+  MOZ_ASSERT(!mCallback);
+  MOZ_ASSERT(!mCallbackEventTarget);
+
+  if (!aCallback) {
+    return NS_OK;
+  }
+
+  CallbackRunnable::Execute(aCallback, aCallbackEventTarget, this);
+  return NS_OK;
+}
+
+// nsIInputStreamCallback
+
+NS_IMETHODIMP
+IPCBlobInputStream::OnInputStreamReady(nsIAsyncInputStream* aStream)
+{
+  // We have been closed in the meantime.
+  if (mState == eClosed) {
+    return NS_OK;
+  }
+
+  MOZ_ASSERT(mState == eRunning);
+  MOZ_ASSERT(mRemoteStream == aStream);
+
+  // The callback has been canceled in the meantime.
+  if (!mCallback) {
+    return NS_OK;
+  }
+
+  CallbackRunnable::Execute(mCallback, mCallbackEventTarget, this);
+
+  mCallback = nullptr;
+  mCallbackEventTarget = nullptr;
+
+  return NS_OK;
+}
+
+// nsIIPCSerializableInputStream
+
+void
+IPCBlobInputStream::Serialize(mozilla::ipc::InputStreamParams& aParams,
+                              FileDescriptorArray& aFileDescriptors)
+{
+  IPCBlobInputStreamParams params;
+  params.id() = mActor->ID();
+
+  aParams = params;
+}
+
+bool
+IPCBlobInputStream::Deserialize(const mozilla::ipc::InputStreamParams& aParams,
+                                const FileDescriptorArray& aFileDescriptors)
+{
+  MOZ_CRASH("This should never be called.");
+  return false;
+}
+
+mozilla::Maybe<uint64_t>
+IPCBlobInputStream::ExpectedSerializedLength()
+{
+  return mozilla::Nothing();
+}
+
+} // namespace dom
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/file/ipc/IPCBlobInputStream.h
@@ -0,0 +1,77 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_ipc_IPCBlobInputStream_h
+#define mozilla_dom_ipc_IPCBlobInputStream_h
+
+#include "nsIAsyncInputStream.h"
+#include "nsICloneableInputStream.h"
+#include "nsIIPCSerializableInputStream.h"
+
+namespace mozilla {
+namespace dom {
+
+class IPCBlobInputStreamChild;
+
+class IPCBlobInputStream final : public nsIAsyncInputStream
+                               , public nsIInputStreamCallback
+                               , public nsICloneableInputStream
+                               , public nsIIPCSerializableInputStream
+{
+public:
+  NS_DECL_THREADSAFE_ISUPPORTS
+  NS_DECL_NSIINPUTSTREAM
+  NS_DECL_NSIASYNCINPUTSTREAM
+  NS_DECL_NSIINPUTSTREAMCALLBACK
+  NS_DECL_NSICLONEABLEINPUTSTREAM
+  NS_DECL_NSIIPCSERIALIZABLEINPUTSTREAM
+
+  explicit IPCBlobInputStream(IPCBlobInputStreamChild* aActor);
+
+  void
+  StreamReady(nsIInputStream* aInputStream);
+
+private:
+  ~IPCBlobInputStream();
+
+  nsresult
+  MaybeExecuteCallback(nsIInputStreamCallback* aCallback,
+                       nsIEventTarget* aEventTarget);
+
+  RefPtr<IPCBlobInputStreamChild> mActor;
+
+  // This is the list of possible states.
+  enum {
+    // The initial state. Only ::Available() can be used without receiving an
+    // error. The available size is known by the actor.
+    eInit,
+
+    // AsyncWait() has been called for the first time. SendStreamNeeded() has
+    // been called and we are waiting for the 'real' inputStream.
+    ePending,
+
+    // When the child receives the stream from the parent, we move to this
+    // state. The received stream is stored in mRemoteStream. From now on, any
+    // method call will be forwared to mRemoteStream.
+    eRunning,
+
+    // If Close() or CloseWithStatus() is called, we move to this state.
+    // mRemoveStream is released and any method will return
+    // NS_BASE_STREAM_CLOSED.
+    eClosed,
+  } mState;
+
+  nsCOMPtr<nsIInputStream> mRemoteStream;
+
+  // These 2 values are set only if mState is ePending.
+  nsCOMPtr<nsIInputStreamCallback> mCallback;
+  nsCOMPtr<nsIEventTarget> mCallbackEventTarget;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_ipc_IPCBlobInputStream_h
new file mode 100644
--- /dev/null
+++ b/dom/file/ipc/IPCBlobInputStreamChild.cpp
@@ -0,0 +1,183 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "IPCBlobInputStreamChild.h"
+
+namespace mozilla {
+namespace dom {
+
+namespace {
+
+// This runnable is used in case the last stream is forgotten on the 'wrong'
+// thread.
+class DeleteRunnable final : public Runnable
+{
+public:
+  explicit DeleteRunnable(IPCBlobInputStreamChild* aActor)
+    : mActor(aActor)
+  {}
+
+  NS_IMETHOD
+  Run() override
+  {
+    if (mActor->IsAlive()) {
+      mActor->Send__delete__(mActor);
+    }
+    return NS_OK;
+  }
+
+private:
+  RefPtr<IPCBlobInputStreamChild> mActor;
+};
+
+// This runnable is used in case StreamNeeded() has been called on a non-owning
+// thread.
+class StreamNeededRunnable final : public Runnable
+{
+public:
+  explicit StreamNeededRunnable(IPCBlobInputStreamChild* aActor)
+    : mActor(aActor)
+  {}
+
+  NS_IMETHOD
+  Run() override
+  {
+    if (mActor->IsAlive()) {
+      mActor->SendStreamNeeded();
+    }
+    return NS_OK;
+  }
+
+private:
+  RefPtr<IPCBlobInputStreamChild> mActor;
+};
+
+// When the stream has been received from the parent, we inform the
+// IPCBlobInputStream.
+class StreamReadyRunnable final : public CancelableRunnable
+{
+public:
+  StreamReadyRunnable(IPCBlobInputStream* aDestinationStream,
+                      nsIInputStream* aCreatedStream)
+    : mDestinationStream(aDestinationStream)
+    , mCreatedStream(aCreatedStream)
+  {
+    MOZ_ASSERT(mDestinationStream);
+    // mCreatedStream can be null.
+  }
+
+  NS_IMETHOD
+  Run() override
+  {
+    mDestinationStream->StreamReady(mCreatedStream);
+    return NS_OK;
+  }
+
+private:
+  RefPtr<IPCBlobInputStream> mDestinationStream;
+  nsCOMPtr<nsIInputStream> mCreatedStream;
+};
+
+} // anonymous
+
+IPCBlobInputStreamChild::IPCBlobInputStreamChild(const nsID& aID,
+                                                 uint64_t aSize)
+  : mMutex("IPCBlobInputStreamChild::mMutex")
+  , mID(aID)
+  , mSize(aSize)
+  , mActorAlive(true)
+  , mOwningThread(NS_GetCurrentThread())
+{}
+
+IPCBlobInputStreamChild::~IPCBlobInputStreamChild()
+{}
+
+void
+IPCBlobInputStreamChild::ActorDestroy(IProtocol::ActorDestroyReason aReason)
+{
+  MutexAutoLock lock(mMutex);
+  mActorAlive = false;
+}
+
+bool
+IPCBlobInputStreamChild::IsAlive()
+{
+  MutexAutoLock lock(mMutex);
+  return mActorAlive;
+}
+
+already_AddRefed<nsIInputStream>
+IPCBlobInputStreamChild::CreateStream()
+{
+  MutexAutoLock lock(mMutex);
+
+  RefPtr<IPCBlobInputStream> stream = new IPCBlobInputStream(this);
+  mStreams.AppendElement(stream);
+  return stream.forget();
+}
+
+void
+IPCBlobInputStreamChild::ForgetStream(IPCBlobInputStream* aStream)
+{
+  MOZ_ASSERT(aStream);
+
+  RefPtr<IPCBlobInputStreamChild> kungFoDeathGrip = this;
+
+  {
+    MutexAutoLock lock(mMutex);
+    mStreams.RemoveElement(aStream);
+
+    if (!mStreams.IsEmpty() || !mActorAlive) {
+      return;
+    }
+  }
+
+  if (mOwningThread == NS_GetCurrentThread()) {
+    Send__delete__(this);
+    return;
+  }
+
+  RefPtr<DeleteRunnable> runnable = new DeleteRunnable(this);
+  mOwningThread->Dispatch(runnable, NS_DISPATCH_NORMAL);
+}
+
+void
+IPCBlobInputStreamChild::StreamNeeded(IPCBlobInputStream* aStream)
+{
+  MutexAutoLock lock(mMutex);
+  MOZ_ASSERT(mStreams.Contains(aStream));
+
+  PendingOperation* opt = mPendingOperations.AppendElement();
+  opt->mStream = aStream;
+  opt->mThread = NS_GetCurrentThread();
+
+  if (mOwningThread == NS_GetCurrentThread()) {
+    SendStreamNeeded();
+    return;
+  }
+
+  RefPtr<StreamNeededRunnable> runnable = new StreamNeededRunnable(this);
+  mOwningThread->Dispatch(runnable, NS_DISPATCH_NORMAL);
+}
+
+mozilla::ipc::IPCResult
+IPCBlobInputStreamChild::RecvStreamReady(const OptionalIPCStream& aStream)
+{
+  MOZ_ASSERT(!mPendingOperations.IsEmpty());
+
+  nsCOMPtr<nsIInputStream> stream = DeserializeIPCStream(aStream);
+
+  RefPtr<StreamReadyRunnable> runnable =
+    new StreamReadyRunnable(mPendingOperations[0].mStream, stream);
+  mPendingOperations[0].mThread->Dispatch(runnable, NS_DISPATCH_NORMAL);
+
+  mPendingOperations.RemoveElementAt(0);
+
+  return IPC_OK();
+}
+
+} // namespace dom
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/file/ipc/IPCBlobInputStreamChild.h
@@ -0,0 +1,89 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_ipc_IPCBlobInputStreamChild_h
+#define mozilla_dom_ipc_IPCBlobInputStreamChild_h
+
+#include "mozilla/ipc/PIPCBlobInputStreamChild.h"
+#include "mozilla/Mutex.h"
+#include "nsIThread.h"
+#include "nsTArray.h"
+
+namespace mozilla {
+namespace dom {
+
+class IPCBlobInputStream;
+
+class IPCBlobInputStreamChild final
+  : public mozilla::ipc::PIPCBlobInputStreamChild
+{
+public:
+  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(IPCBlobInputStreamChild)
+
+  IPCBlobInputStreamChild(const nsID& aID, uint64_t aSize);
+
+  void
+  ActorDestroy(IProtocol::ActorDestroyReason aReason) override;
+
+  bool
+  IsAlive();
+
+  already_AddRefed<nsIInputStream>
+  CreateStream();
+
+  void
+  ForgetStream(IPCBlobInputStream* aStream);
+
+  const nsID&
+  ID() const
+  {
+    return mID;
+  }
+
+  uint64_t
+  Size() const
+  {
+    return mSize;
+  }
+
+  void
+  StreamNeeded(IPCBlobInputStream* aStream);
+
+  mozilla::ipc::IPCResult
+  RecvStreamReady(const OptionalIPCStream& aStream) override;
+
+private:
+  ~IPCBlobInputStreamChild();
+
+  // Raw pointers because these streams keep this actor alive. When the last
+  // stream is unregister, the actor will be deleted. This list is protected by
+  // mutex.
+  nsTArray<IPCBlobInputStream*> mStreams;
+
+  // This mutex protects mStreams because that can be touched in any thread.
+  Mutex mMutex;
+
+  const nsID mID;
+  const uint64_t mSize;
+
+  // false when ActorDestroy() is called.
+  bool mActorAlive;
+
+  // This struct and the array are used for creating streams when needed.
+  struct PendingOperation
+  {
+    RefPtr<IPCBlobInputStream> mStream;
+    nsCOMPtr<nsIThread> mThread;
+  };
+  nsTArray<PendingOperation> mPendingOperations;
+
+  nsCOMPtr<nsIThread> mOwningThread;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_ipc_IPCBlobInputStreamChild_h
new file mode 100644
--- /dev/null
+++ b/dom/file/ipc/IPCBlobInputStreamParent.cpp
@@ -0,0 +1,100 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "IPCBlobInputStreamParent.h"
+#include "IPCBlobInputStreamStorage.h"
+#include "nsContentUtils.h"
+
+namespace mozilla {
+namespace dom {
+
+template<typename M>
+/* static */ IPCBlobInputStreamParent*
+IPCBlobInputStreamParent::Create(nsIInputStream* aInputStream, uint64_t aSize,
+                                 nsresult* aRv, M* aManager)
+{
+  MOZ_ASSERT(aInputStream);
+  MOZ_ASSERT(aRv);
+
+  nsID id;
+  *aRv = nsContentUtils::GenerateUUIDInPlace(id);
+  if (NS_WARN_IF(NS_FAILED(*aRv))) {
+    return nullptr;
+  }
+
+  IPCBlobInputStreamStorage::Get()->AddStream(aInputStream, id);
+
+  return new IPCBlobInputStreamParent(id, aSize, aManager);
+}
+
+IPCBlobInputStreamParent::IPCBlobInputStreamParent(const nsID& aID,
+                                                   uint64_t aSize,
+                                                   nsIContentParent* aManager)
+  : mID(aID)
+  , mSize(aSize)
+  , mContentManager(aManager)
+  , mPBackgroundManager(nullptr)
+{}
+
+IPCBlobInputStreamParent::IPCBlobInputStreamParent(const nsID& aID,
+                                                   uint64_t aSize,
+                                                   PBackgroundParent* aManager)
+  : mID(aID)
+  , mSize(aSize)
+  , mContentManager(nullptr)
+  , mPBackgroundManager(aManager)
+{}
+
+void
+IPCBlobInputStreamParent::ActorDestroy(IProtocol::ActorDestroyReason aReason)
+{
+  MOZ_ASSERT(mContentManager || mPBackgroundManager);
+
+  mContentManager = nullptr;
+  mPBackgroundManager = nullptr;
+
+  IPCBlobInputStreamStorage::Get()->ForgetStream(mID);
+}
+
+mozilla::ipc::IPCResult
+IPCBlobInputStreamParent::RecvStreamNeeded()
+{
+  MOZ_ASSERT(mContentManager || mPBackgroundManager);
+
+  nsCOMPtr<nsIInputStream> stream;
+  IPCBlobInputStreamStorage::Get()->GetStream(mID, getter_AddRefs(stream));
+  if (!stream) {
+    if (!SendStreamReady(void_t())) {
+      return IPC_FAIL(this, "SendStreamReady failed");
+    }
+
+    return IPC_OK();
+  }
+
+  AutoIPCStream ipcStream;
+  bool ok = false;
+
+  if (mContentManager) {
+    MOZ_ASSERT(NS_IsMainThread());
+    ok = ipcStream.Serialize(stream, mContentManager);
+  } else {
+    MOZ_ASSERT(mPBackgroundManager);
+    ok = ipcStream.Serialize(stream, mPBackgroundManager);
+  }
+
+  if (NS_WARN_IF(!ok)) {
+    return IPC_FAIL(this, "SendStreamReady failed");
+  }
+
+  if (!SendStreamReady(ipcStream.TakeValue())) {
+    return IPC_FAIL(this, "SendStreamReady failed");
+  }
+
+  return IPC_OK();
+}
+
+} // namespace dom
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/file/ipc/IPCBlobInputStreamParent.h
@@ -0,0 +1,66 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_ipc_IPCBlobInputStreamParent_h
+#define mozilla_dom_ipc_IPCBlobInputStreamParent_h
+
+#include "mozilla/ipc/PIPCBlobInputStreamParent.h"
+
+class nsIInputStream;
+
+namespace mozilla {
+namespace dom {
+
+class IPCBlobInputStreamParent final
+  : public mozilla::ipc::PIPCBlobInputStreamParent
+{
+public:
+  // The size of the inputStream must be passed as argument in order to avoid
+  // the use of nsIInputStream::Available() which could open a fileDescriptor in
+  // case the stream is a nsFileStream.
+  template<typename M>
+  static IPCBlobInputStreamParent*
+  Create(nsIInputStream* aInputStream, uint64_t aSize, nsresult* aRv,
+         M* aManager);
+
+  void
+  ActorDestroy(IProtocol::ActorDestroyReason aReason) override;
+
+  const nsID&
+  ID() const
+  {
+    return mID;
+  }
+
+  uint64_t
+  Size() const
+  {
+    return mSize;
+  }
+
+  mozilla::ipc::IPCResult
+  RecvStreamNeeded() override;
+
+private:
+  IPCBlobInputStreamParent(const nsID& aID, uint64_t aSize,
+                           nsIContentParent* aManager);
+
+  IPCBlobInputStreamParent(const nsID& aID, uint64_t aSize,
+                           mozilla::ipc::PBackgroundParent* aManager);
+
+  const nsID mID;
+  const uint64_t mSize;
+
+  // Only 1 of these 2 is set. Raw pointer because these 2 managers are keeping
+  // the parent actor alive. The pointers will be nullified in ActorDestroyed.
+  nsIContentParent* mContentManager;
+  mozilla::ipc::PBackgroundParent* mPBackgroundManager;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_ipc_IPCBlobInputStreamParent_h
new file mode 100644
--- /dev/null
+++ b/dom/file/ipc/IPCBlobInputStreamStorage.cpp
@@ -0,0 +1,89 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "IPCBlobInputStreamStorage.h"
+
+#include "mozilla/ClearOnShutdown.h"
+#include "mozilla/StaticMutex.h"
+#include "mozilla/StaticPtr.h"
+#include "nsStreamUtils.h"
+
+namespace mozilla {
+namespace dom {
+
+namespace {
+StaticMutex gMutex;
+StaticRefPtr<IPCBlobInputStreamStorage> gStorage;
+}
+
+IPCBlobInputStreamStorage::~IPCBlobInputStreamStorage()
+{}
+
+/* static */ IPCBlobInputStreamStorage*
+IPCBlobInputStreamStorage::Get()
+{
+  return gStorage;
+}
+
+/* static */ void
+IPCBlobInputStreamStorage::Initialize()
+{
+  MOZ_ASSERT(!gStorage);
+
+  gStorage = new IPCBlobInputStreamStorage();
+  ClearOnShutdown(&gStorage);
+}
+
+void
+IPCBlobInputStreamStorage::AddStream(nsIInputStream* aInputStream,
+                                     const nsID& aID)
+{
+  MOZ_ASSERT(aInputStream);
+
+  mozilla::StaticMutexAutoLock lock(gMutex);
+  mStorage.Put(aID, aInputStream);
+}
+
+void
+IPCBlobInputStreamStorage::ForgetStream(const nsID& aID)
+{
+  mozilla::StaticMutexAutoLock lock(gMutex);
+  mStorage.Remove(aID);
+}
+
+void
+IPCBlobInputStreamStorage::GetStream(const nsID& aID,
+                                     nsIInputStream** aInputStream)
+{
+  mozilla::StaticMutexAutoLock lock(gMutex);
+  nsCOMPtr<nsIInputStream> stream = mStorage.Get(aID);
+  if (!stream) {
+    *aInputStream = nullptr;
+    return;
+  }
+
+  // We cannot return always the same inputStream because not all of them are
+  // able to be reused. Better to clone them.
+
+  nsCOMPtr<nsIInputStream> clonedStream;
+  nsCOMPtr<nsIInputStream> replacementStream;
+
+  nsresult rv =
+    NS_CloneInputStream(stream, getter_AddRefs(clonedStream),
+                        getter_AddRefs(replacementStream));
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return;
+  }
+
+  if (replacementStream) {
+    mStorage.Put(aID, replacementStream);
+  }
+
+  clonedStream.forget(aInputStream);
+}
+
+} // namespace dom
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/file/ipc/IPCBlobInputStreamStorage.h
@@ -0,0 +1,50 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_ipc_IPCBlobInputStreamStorage_h
+#define mozilla_dom_ipc_IPCBlobInputStreamStorage_h
+
+#include "mozilla/RefPtr.h"
+#include "nsInterfaceHashtable.h"
+#include "nsISupportsImpl.h"
+
+class nsIInputStream;
+struct nsID;
+
+namespace mozilla {
+namespace dom {
+
+class IPCBlobInputStreamStorage final
+{
+public:
+  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(IPCBlobInputStreamStorage);
+
+  // This initializes the singleton and it must be called on the main-thread.
+  static void
+  Initialize();
+
+  static IPCBlobInputStreamStorage*
+  Get();
+
+  void
+  AddStream(nsIInputStream* aInputStream, const nsID& aID);
+
+  void
+  ForgetStream(const nsID& aID);
+
+  void
+  GetStream(const nsID& aID, nsIInputStream** aInputStream);
+
+private:
+  ~IPCBlobInputStreamStorage();
+
+  nsInterfaceHashtable<nsIDHashKey, nsIInputStream> mStorage;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_ipc_IPCBlobInputStreamStorage_h
new file mode 100644
--- /dev/null
+++ b/dom/file/ipc/IPCBlobUtils.cpp
@@ -0,0 +1,211 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "IPCBlobUtils.h"
+#include "IPCBlobInputStream.h"
+#include "IPCBlobInputStreamChild.h"
+#include "IPCBlobInputStreamParent.h"
+#include "IPCBlobInputStreamStorage.h"
+#include "mozilla/dom/IPCBlob.h"
+#include "prtime.h"
+
+namespace mozilla {
+namespace dom {
+namespace IPCBlobUtils {
+
+already_AddRefed<BlobImpl>
+Deserialize(const IPCBlob& aIPCBlob)
+{
+  nsCOMPtr<nsIInputStream> inputStream;
+
+  const IPCBlobStream& stream = aIPCBlob.inputStream();
+  switch (stream.type()) {
+
+  // Parent to child: when an nsIInputStream is sent from parent to child, the
+  // child receives a IPCBlobInputStream actor.
+  case IPCBlobStream::TPIPCBlobInputStreamChild: {
+    IPCBlobInputStreamChild* actor =
+      static_cast<IPCBlobInputStreamChild*>(stream.get_PIPCBlobInputStreamChild());
+    inputStream = actor->CreateStream();
+    break;
+  }
+
+  // Child to Parent: when a blob is created on the content process send it's
+  // sent to the parent, we have an IPCStream object.
+  case IPCBlobStream::TIPCStream:
+    MOZ_ASSERT(XRE_IsParentProcess());
+    inputStream = DeserializeIPCStream(stream.get_IPCStream());
+    break;
+
+  default:
+    MOZ_CRASH("Unknown type.");
+    break;
+  }
+
+  MOZ_ASSERT(inputStream);
+
+  RefPtr<BlobImpl> blobImpl;
+
+  if (aIPCBlob.file().type() == IPCFileUnion::Tvoid_t) {
+    blobImpl = StreamBlobImpl::Create(inputStream,
+                                      aIPCBlob.type(),
+                                      aIPCBlob.size());
+  } else {
+    const IPCFile& file = aIPCBlob.file().get_IPCFile();
+    blobImpl = StreamBlobImpl::Create(inputStream,
+                                      file.name(),
+                                      aIPCBlob.type(),
+                                      file.lastModified(),
+                                      aIPCBlob.size());
+    blobImpl->SetDOMPath(file.DOMPath());
+  }
+
+  return blobImpl.forget();
+}
+
+template<typename M>
+nsresult
+SerializeInputStreamParent(nsIInputStream* aInputStream, uint64_t aSize,
+                           IPCBlob& aIPCBlob, M* aManager)
+{
+  // Parent to Child we always send a IPCBlobInputStream.
+  MOZ_ASSERT(XRE_IsParentProcess());
+
+  nsresult rv;
+  IPCBlobInputStreamParent* parentActor =
+    IPCBlobInputStreamParent::Create(aInputStream, aSize, &rv, aManager);
+  if (!parentActor) {
+    return rv;
+  }
+
+  if (!aManager->SendPIPCBlobInputStreamConstructor(parentActor,
+                                                    parentActor->ID(),
+                                                    parentActor->Size())) {
+    return NS_ERROR_FAILURE;
+  }
+
+  aIPCBlob.inputStream() = parentActor;
+  return NS_OK;
+}
+
+template<typename M>
+nsresult
+SerializeInputStreamChild(nsIInputStream* aInputStream, IPCBlob& aIPCBlob,
+                          M* aManager)
+{
+  AutoIPCStream ipcStream(true /* delayed start */);
+  if (!ipcStream.Serialize(aInputStream, aManager)) {
+    return NS_ERROR_FAILURE;
+  }
+
+  aIPCBlob.inputStream() = ipcStream.TakeValue();
+  return NS_OK;
+}
+
+nsresult
+SerializeInputStream(nsIInputStream* aInputStream, uint64_t aSize,
+                     IPCBlob& aIPCBlob, nsIContentParent* aManager)
+{
+  return SerializeInputStreamParent(aInputStream, aSize, aIPCBlob, aManager);
+}
+
+nsresult
+SerializeInputStream(nsIInputStream* aInputStream, uint64_t aSize,
+                     IPCBlob& aIPCBlob, PBackgroundParent* aManager)
+{
+  return SerializeInputStreamParent(aInputStream, aSize, aIPCBlob, aManager);
+}
+
+nsresult
+SerializeInputStream(nsIInputStream* aInputStream, uint64_t aSize,
+                     IPCBlob& aIPCBlob, nsIContentChild* aManager)
+{
+  return SerializeInputStreamChild(aInputStream, aIPCBlob, aManager);
+}
+
+nsresult
+SerializeInputStream(nsIInputStream* aInputStream, uint64_t aSize,
+                     IPCBlob& aIPCBlob, PBackgroundChild* aManager)
+{
+  return SerializeInputStreamChild(aInputStream, aIPCBlob, aManager);
+}
+
+template<typename M>
+nsresult
+SerializeInternal(BlobImpl* aBlobImpl, M* aManager, IPCBlob& aIPCBlob)
+{
+  MOZ_ASSERT(aBlobImpl);
+
+  nsAutoString value;
+  aBlobImpl->GetType(value);
+  aIPCBlob.type() = value;
+
+  ErrorResult rv;
+  aIPCBlob.size() = aBlobImpl->GetSize(rv);
+  if (NS_WARN_IF(rv.Failed())) {
+    return rv.StealNSResult();
+  }
+
+  if (!aBlobImpl->IsFile()) {
+    aIPCBlob.file() = void_t();
+  } else {
+    IPCFile file;
+
+    aBlobImpl->GetName(value);
+    file.name() = value;
+
+    file.lastModified() = aBlobImpl->GetLastModified(rv) * PR_USEC_PER_MSEC;
+    if (NS_WARN_IF(rv.Failed())) {
+      return rv.StealNSResult();
+    }
+
+    aBlobImpl->GetDOMPath(value);
+    file.DOMPath() = value;
+
+    aIPCBlob.file() = file;
+  }
+
+  nsCOMPtr<nsIInputStream> inputStream;
+  aBlobImpl->GetInternalStream(getter_AddRefs(inputStream), rv);
+  if (NS_WARN_IF(rv.Failed())) {
+    return rv.StealNSResult();
+  }
+
+  rv = SerializeInputStream(inputStream, aIPCBlob.size(), aIPCBlob, aManager);
+  if (NS_WARN_IF(rv.Failed())) {
+    return rv.StealNSResult();
+  }
+
+  return NS_OK;
+}
+
+nsresult
+Serialize(BlobImpl* aBlobImpl, nsIContentChild* aManager, IPCBlob& aIPCBlob)
+{
+  return SerializeInternal(aBlobImpl, aManager, aIPCBlob);
+}
+
+nsresult
+Serialize(BlobImpl* aBlobImpl, PBackgroundChild* aManager, IPCBlob& aIPCBlob)
+{
+  return SerializeInternal(aBlobImpl, aManager, aIPCBlob);
+}
+
+nsresult
+Serialize(BlobImpl* aBlobImpl, nsIContentParent* aManager, IPCBlob& aIPCBlob)
+{
+  return SerializeInternal(aBlobImpl, aManager, aIPCBlob);
+}
+
+nsresult
+Serialize(BlobImpl* aBlobImpl, PBackgroundParent* aManager, IPCBlob& aIPCBlob)
+{
+  return SerializeInternal(aBlobImpl, aManager, aIPCBlob);
+}
+
+} // IPCBlobUtils namespace
+} // dom namespace
+} // mozilla namespace
new file mode 100644
--- /dev/null
+++ b/dom/file/ipc/IPCBlobUtils.h
@@ -0,0 +1,169 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_IPCBlobUtils_h
+#define mozilla_dom_IPCBlobUtils_h
+
+#include "mozilla/dom/File.h"
+
+/*
+ * Blobs and IPC
+ * ~~~~~~~~~~~~~
+ * 
+ * Simplifying, DOM Blob objects are chunks of data with a content type and a
+ * size. DOM Files are Blobs with a name. They are are used in many APIs and
+ * they can be cloned and sent cross threads and cross processes.
+ * 
+ * If we see Blobs from a platform point of view, the main (and often, the only)
+ * interesting part is how to retrieve data from it. This is done via
+ * nsIInputStream and, except for a couple of important details, this stream is
+ * used in the parent process.
+ * 
+ * For this reason, when we consider the serialization of a blob via IPC
+ * messages, the biggest effort is put in how to manage the nsInputStream
+ * correctly. To serialize, we use the IPCBlob data struct: basically, the blob
+ * properties (size, type, name if it's a file) and the nsIInputStream.
+ * 
+ * Before talking about the nsIInputStream it's important to say that we have
+ * different kinds of Blobs, based on the different kinds of sources. A non
+ * exaustive list is:
+ * - a memory buffer: MemoryBlobImpl
+ * - a string: StringBlobImpl
+ * - a real OS file: FileBlobImpl
+ * - a temporary OS file: TemporaryBlobImpl
+ * - a generic nsIInputStream: StreamBlobImpl
+ * - an empty blob: EmptyBlobImpl
+ * - more blobs combined together: MultipartBlobImpl
+ * Each one of these implementations has a custom ::GetInternalStream method.
+ * So, basically, each one has a different kind of nsIInputStream (nsFileStream,
+ * nsIStringInputStream, SlicedInputStream, and so on).
+ * 
+ * Another important point to keep in mind is that a Blob can be created on the
+ * content process (for example: |new Blob([123])|) or it can be created on the
+ * parent process and sent to content (a FilePicker creates Blobs and it runs on
+ * the parent process).
+ * 
+ * Child to Parent Blob Serialization
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ * 
+ * When a document creates a blob, this can be sent, for different reasons to
+ * the parent process. For instance it can be sent as part of a FormData, or it
+ * can be converted to a BlobURL and broadcasted to any other existing
+ * processes.
+ * 
+ * When this happens, we use the IPCStream data struct for the serialization
+ * of the nsIInputStream. This means that, if the stream is fully serializable
+ * and its size is lower than 1Mb, we are able to recreate the stream completely
+ * on the parent side. This happens, basically with any kind of child-to-parent
+ * stream except for huge memory streams. In this case we end up using
+ * PChildToParentStream. See more information in IPCStreamUtils.h.
+ *
+ * In order to populate IPCStream correctly, we use AutoIPCStream as documented
+ * in IPCStreamUtils.h. Note that we use the 'delayed start' feature because,
+ * often, the stream doesn't need to be read on the parent side.
+ * 
+ * Parent to Child Blob Serialization
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ * 
+ * This scenario is common when we talk about Blobs pointing to real files:
+ * HTMLInputElement (type=file), or Entries API, DataTransfer and so on. But we
+ * also have this scenario when a content process creates a Blob and it
+ * broadcasts it because of a BlobURL or because BroadcastChannel API is used.
+ * 
+ * The approach here is this: normally, the content process doesn't really read
+ * data from the blob nsIInputStream. The content process needs to have the
+ * nsIInputStream and be able to send it back to the parent process when the
+ * "real" work needs to be done. This is true except for 2 usecases: FileReader
+ * API and BlobURL usage. So, if we ignore these 2, normally, the parent sends a
+ * blob nsIInputStream to a content process, and then, it will receive it back
+ * in order to do some networking, or whatever.
+ * 
+ * For this reason, IPCBlobUtils uses a particular protocol for serializing
+ * nsIInputStream parent to child: PIPCBlobInputStream. This protocol keeps the
+ * original nsIInputStream alive on the parent side, and gives its size and a
+ * UUID to the child side. The child side creates a IPCBlobInputStream and that
+ * is incapsulated into a StreamBlobImpl.
+ * 
+ * The UUID is useful when the content process sends the same nsIInputStream
+ * back to the parent process because, the only information it has to share is
+ * the UUID. Each nsIInputStream sent via PIPCBlobInputStream, is registered
+ * into the IPCBlobInputStreamStorage.
+ * 
+ * On the content process side, IPCBlobInputStream is a special inputStream:
+ * the only reliable methods are:
+ * - nsIInputStream.available() - the size is shared by PIPCBlobInputStream
+ *   actor.
+ * - nsIIPCSerializableInputStream.serialize() - we can give back this stream to
+ *   the parent because we know its UUID.
+ * - nsICloneableInputStream.cloneable() and nsICloneableInputStream.clone() -
+ *   this stream can be cloned. We just need to have a reference of the
+ *   PIPCBlobInputStream actor and its UUID.
+ * - nsIAsyncInputStream.asyncWait() - see next section.
+ * 
+ * Any other method (read, readSegment and so on) will fail if asyncWait() is
+ * not previously called (see the next section). Basically, this inputStream
+ * cannot be used synchronously for any 'real' reading operation.
+ * 
+ * When the parent receives the serialization of a IPCBlobInputStream, it is
+ * able to retrieve the correct nsIInputStream using the UUID and
+ * IPCBlobInputStreamStorage.
+ * 
+ * Parent to Child Streams, FileReader and BlobURL
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ * 
+ * The FileReader and BlobURL scenarios are described here.
+ *
+ * When content process needs to read data from a Blob sent from the parent
+ * process, it must do it asynchronously using IPCBlobInputStream as a
+ * nsIAsyncInputStream stream. This happens calling
+ * IPCBlobInputStream.asyncWait(). At that point, the child actor will send a
+ * StreamNeeded() IPC message to the parent side. When this is received, the
+ * parent retrieves the 'real' stream from IPCBlobInputStreamStorage using the
+ * UUID, it will serialize the 'real' stream, and it will send it to the child
+ * side.
+ * 
+ * When the 'real' stream is received (RecvStreamReady()), the asyncWait
+ * callback will be executed and, from that moment, any IPCBlobInputStream
+ * method will be forwarded to the 'real' stream ones. This means that the
+ * reading will be available.
+ */ 
+
+namespace mozilla {
+
+namespace ipc {
+class PBackgroundChild;
+class PBackgroundParent;
+}
+
+namespace dom {
+
+class nsIContentChild;
+class nsIContentParent;
+
+namespace IPCBlobUtils {
+
+already_AddRefed<BlobImpl>
+Deserialize(const IPCBlob& aIPCBlob);
+
+// These 4 methods serialize aBlobImpl into aIPCBlob using the right manager.
+
+nsresult
+Serialize(BlobImpl* aBlobImpl, nsIContentChild* aManager, IPCBlob& aIPCBlob);
+
+nsresult
+Serialize(BlobImpl* aBlobImpl, PBackgroundChild* aManager, IPCBlob& aIPCBlob);
+
+nsresult
+Serialize(BlobImpl* aBlobImpl, nsIContentParent* aManager, IPCBlob& aIPCBlob);
+
+nsresult
+Serialize(BlobImpl* aBlobImpl, PBackgroundParent* aManager, IPCBlob& aIPCBlob);
+
+} // IPCBlobUtils
+} // dom namespace
+} // mozilla namespace
+
+#endif // mozilla_dom_IPCBlobUtils_h
new file mode 100644
--- /dev/null
+++ b/dom/file/ipc/PIPCBlobInputStream.ipdl
@@ -0,0 +1,32 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+include protocol PBackground;
+include protocol PChildToParentStream;
+include protocol PContent;
+include protocol PContentBridge;
+include protocol PFileDescriptorSet;
+include protocol PParentToChildStream;
+
+include IPCStream;
+
+namespace mozilla {
+namespace ipc {
+
+protocol PIPCBlobInputStream
+{
+  manager PBackground or PContent or PContentBridge;
+
+parent:
+  async StreamNeeded();
+
+  async __delete__();
+
+child:
+  async StreamReady(OptionalIPCStream aStream);
+};
+
+} // namespace dom
+} // namespace mozilla
+
--- a/dom/file/ipc/moz.build
+++ b/dom/file/ipc/moz.build
@@ -2,30 +2,45 @@
 # vim: set filetype=python:
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 EXPORTS.mozilla.dom.ipc += [
     'BlobChild.h',
     'BlobParent.h',
+    'IPCBlobInputStream.h',
+    'IPCBlobInputStreamChild.h',
+    'IPCBlobInputStreamParent.h',
+    'IPCBlobInputStreamStorage.h',
     'MemoryStreamChild.h',
     'MemoryStreamParent.h',
     'nsIRemoteBlob.h',
 ]
 
+EXPORTS.mozilla.dom += [
+    'IPCBlobUtils.h',
+]
+
 UNIFIED_SOURCES += [
     'Blob.cpp',
+    'IPCBlobInputStream.cpp',
+    'IPCBlobInputStreamChild.cpp',
+    'IPCBlobInputStreamParent.cpp',
+    'IPCBlobInputStreamStorage.cpp',
+    'IPCBlobUtils.cpp',
     'MemoryStreamParent.cpp',
 ]
 
 IPDL_SOURCES += [
     'BlobTypes.ipdlh',
+    'IPCBlob.ipdlh',
     'PBlob.ipdl',
     'PBlobStream.ipdl',
+    'PIPCBlobInputStream.ipdl',
     'PMemoryStream.ipdl',
 ]
 
 LOCAL_INCLUDES += [
     '/dom/file',
     '/dom/ipc',
     '/dom/workers',
 ]
@@ -33,8 +48,10 @@ LOCAL_INCLUDES += [
 include('/ipc/chromium/chromium-config.mozbuild')
 
 FINAL_LIBRARY = 'xul'
 
 CXXFLAGS += CONFIG['TK_CFLAGS']
 
 if CONFIG['GNU_CXX']:
     CXXFLAGS += ['-Wno-error=shadow']
+
+BROWSER_CHROME_MANIFESTS += ['tests/browser.ini']
new file mode 100644
--- /dev/null
+++ b/dom/file/ipc/tests/browser.ini
@@ -0,0 +1,5 @@
+[DEFAULT]
+support-files =
+  empty.html
+
+[browser_ipcBlob.js]
rename from dom/base/test/browser_bug1307747.js
rename to dom/file/ipc/tests/browser_ipcBlob.js
--- a/dom/base/test/browser_bug1307747.js
+++ b/dom/file/ipc/tests/browser_ipcBlob.js
@@ -1,32 +1,173 @@
 /* -*- Mode: javascript; tab-width: 2; indent-tabs-mode: nil; js-indent-level: 2 -*- */
 
+requestLongerTimeout(3);
+
 var {Promise: promise} = Cu.import("resource://gre/modules/Promise.jsm", {});
 
-const BASE_URI = "http://mochi.test:8888/browser/dom/base/test/empty.html";
+const BASE_URI = "http://mochi.test:8888/browser/dom/file/ipc/tests/empty.html";
 
-add_task(function* test_initialize() {
-  let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, BASE_URI);
-  let browser = gBrowser.getBrowserForTab(tab);
+// More than 1mb memory blob childA-parent-childB.
+add_task(function* test_CtoPtoC_big() {
+  let tab1 = yield BrowserTestUtils.openNewForegroundTab(gBrowser, BASE_URI);
+  let browser1 = gBrowser.getBrowserForTab(tab1);
 
-  let blob = yield ContentTask.spawn(browser, null, function() {
+  let blob = yield ContentTask.spawn(browser1, null, function() {
     let blob = new Blob([new Array(1024*1024).join('123456789ABCDEF')]);
     return blob;
   });
 
-  ok(blob, "We have a blob!");
-  is(blob.size, new Array(1024*1024).join('123456789ABCDEF').length, "The size matches");
+  ok(blob, "CtoPtoC-big: We have a blob!");
+  is(blob.size, new Array(1024*1024).join('123456789ABCDEF').length, "CtoPtoC-big: The size matches");
 
-  let status = yield ContentTask.spawn(browser, blob, function(blob) {
+  let tab2 = yield BrowserTestUtils.openNewForegroundTab(gBrowser, BASE_URI);
+  let browser2 = gBrowser.getBrowserForTab(tab2);
+
+  let status = yield ContentTask.spawn(browser2, blob, function(blob) {
     return new Promise(resolve => {
       let fr = new content.FileReader();
       fr.readAsText(blob);
       fr.onloadend = function() {
         resolve(fr.result == new Array(1024*1024).join('123456789ABCDEF'));
       }
     });
   });
 
-  ok(status, "Data match!");
+  ok(status, "CtoPtoC-big: Data match!");
+
+  yield BrowserTestUtils.removeTab(tab1);
+  yield BrowserTestUtils.removeTab(tab2);
+});
+
+// Less than 1mb memory blob childA-parent-childB.
+add_task(function* test_CtoPtoC_small() {
+  let tab1 = yield BrowserTestUtils.openNewForegroundTab(gBrowser, BASE_URI);
+  let browser1 = gBrowser.getBrowserForTab(tab1);
+
+  let blob = yield ContentTask.spawn(browser1, null, function() {
+    let blob = new Blob(["hello world!"]);
+    return blob;
+  });
+
+  ok(blob, "CtoPtoC-small: We have a blob!");
+  is(blob.size, "hello world!".length, "CtoPtoC-small: The size matches");
+
+  let tab2 = yield BrowserTestUtils.openNewForegroundTab(gBrowser, BASE_URI);
+  let browser2 = gBrowser.getBrowserForTab(tab2);
+
+  let status = yield ContentTask.spawn(browser2, blob, function(blob) {
+    return new Promise(resolve => {
+      let fr = new content.FileReader();
+      fr.readAsText(blob);
+      fr.onloadend = function() {
+        resolve(fr.result == "hello world!");
+      }
+    });
+  });
+
+  ok(status, "CtoPtoC-small: Data match!");
+
+  yield BrowserTestUtils.removeTab(tab1);
+  yield BrowserTestUtils.removeTab(tab2);
+});
+
+// More than 1mb memory blob childA-parent-childB: BroadcastChannel
+add_task(function* test_CtoPtoC_bc_big() {
+  let tab1 = yield BrowserTestUtils.openNewForegroundTab(gBrowser, BASE_URI);
+  let browser1 = gBrowser.getBrowserForTab(tab1);
+
+  yield ContentTask.spawn(browser1, null, function() {
+    var bc = new content.BroadcastChannel('test');
+    bc.onmessage = function() {
+      bc.postMessage(new Blob([new Array(1024*1024).join('123456789ABCDEF')]));
+    }
+  });
+
+  let tab2 = yield BrowserTestUtils.openNewForegroundTab(gBrowser, BASE_URI);
+  let browser2 = gBrowser.getBrowserForTab(tab2);
+
+  let status = yield ContentTask.spawn(browser2, null, function() {
+    return new Promise(resolve => {
+      var bc = new content.BroadcastChannel('test');
+      bc.onmessage = function(e) {
+        let fr = new content.FileReader();
+        fr.readAsText(e.data);
+        fr.onloadend = function() {
+          resolve(fr.result == new Array(1024*1024).join('123456789ABCDEF'));
+        }
+      }
+
+      bc.postMessage("GO!");
+    });
+  });
+
+  ok(status, "CtoPtoC-broadcastChannel-big: Data match!");
 
-  yield BrowserTestUtils.removeTab(tab);
+  yield BrowserTestUtils.removeTab(tab1);
+  yield BrowserTestUtils.removeTab(tab2);
 });
+
+// Less than 1mb memory blob childA-parent-childB: BroadcastChannel
+add_task(function* test_CtoPtoC_bc_small() {
+  let tab1 = yield BrowserTestUtils.openNewForegroundTab(gBrowser, BASE_URI);
+  let browser1 = gBrowser.getBrowserForTab(tab1);
+
+  yield ContentTask.spawn(browser1, null, function() {
+    var bc = new content.BroadcastChannel('test');
+    bc.onmessage = function() {
+      bc.postMessage(new Blob(["hello world!"]));
+    }
+  });
+
+  let tab2 = yield BrowserTestUtils.openNewForegroundTab(gBrowser, BASE_URI);
+  let browser2 = gBrowser.getBrowserForTab(tab2);
+
+  let status = yield ContentTask.spawn(browser2, null, function() {
+    return new Promise(resolve => {
+      var bc = new content.BroadcastChannel('test');
+      bc.onmessage = function(e) {
+        let fr = new content.FileReader();
+        fr.readAsText(e.data);
+        fr.onloadend = function() {
+          resolve(fr.result == "hello world!");
+        }
+      }
+
+      bc.postMessage("GO!");
+    });
+  });
+
+  ok(status, "CtoPtoC-broadcastChannel-small: Data match!");
+
+  yield BrowserTestUtils.removeTab(tab1);
+  yield BrowserTestUtils.removeTab(tab2);
+});
+
+// blob URL childA-parent-childB
+add_task(function* test_CtoPtoC_bc_small() {
+  let tab1 = yield BrowserTestUtils.openNewForegroundTab(gBrowser, BASE_URI);
+  let browser1 = gBrowser.getBrowserForTab(tab1);
+
+  let blobURL = yield ContentTask.spawn(browser1, null, function() {
+    return content.URL.createObjectURL(new content.Blob(["hello world!"]));
+  });
+
+  let tab2 = yield BrowserTestUtils.openNewForegroundTab(gBrowser, BASE_URI);
+  let browser2 = gBrowser.getBrowserForTab(tab2);
+
+  let status = yield ContentTask.spawn(browser2, blobURL, function(blobURL) {
+    return new Promise(resolve => {
+      var xhr = new content.XMLHttpRequest();
+      xhr.open("GET", blobURL);
+      xhr.onloadend = function() {
+        resolve(xhr.response == "hello world!");
+      }
+
+      xhr.send();
+    });
+  });
+
+  ok(status, "CtoPtoC-blobURL: Data match!");
+
+  yield BrowserTestUtils.removeTab(tab1);
+  yield BrowserTestUtils.removeTab(tab2);
+});
new file mode 100644
--- a/dom/interfaces/base/nsIBrowserDOMWindow.idl
+++ b/dom/interfaces/base/nsIBrowserDOMWindow.idl
@@ -104,17 +104,18 @@ interface nsIBrowserDOMWindow : nsISuppo
           in short aWhere, in long aFlags);
 
   /**
    * As above, but return the nsIFrameLoaderOwner for the new window.
    // XXXbz is this the right API?
    // See bug 537428
    */
   nsIFrameLoaderOwner openURIInFrame(in nsIURI aURI, in nsIOpenURIInFrameParams params,
-                                     in short aWhere, in long aFlags);
+                                     in short aWhere, in long aFlags,
+                                     in unsigned long long aNextTabParentId);
 
   /**
    * @param  aWindow the window to test.
    * @return whether the window is the main content window for any
    *         currently open tab in this toplevel browser window.
    */
   boolean isTabContentWindow(in nsIDOMWindow aWindow);
 
--- a/dom/ipc/ContentBridgeChild.cpp
+++ b/dom/ipc/ContentBridgeChild.cpp
@@ -187,16 +187,29 @@ ContentBridgeChild::AllocPMemoryStreamCh
 }
 
 bool
 ContentBridgeChild::DeallocPMemoryStreamChild(PMemoryStreamChild* aActor)
 {
   return nsIContentChild::DeallocPMemoryStreamChild(aActor);
 }
 
+PIPCBlobInputStreamChild*
+ContentBridgeChild::AllocPIPCBlobInputStreamChild(const nsID& aID,
+                                                  const uint64_t& aSize)
+{
+  return nsIContentChild::AllocPIPCBlobInputStreamChild(aID, aSize);
+}
+
+bool
+ContentBridgeChild::DeallocPIPCBlobInputStreamChild(PIPCBlobInputStreamChild* aActor)
+{
+  return nsIContentChild::DeallocPIPCBlobInputStreamChild(aActor);
+}
+
 PChildToParentStreamChild*
 ContentBridgeChild::AllocPChildToParentStreamChild()
 {
   return nsIContentChild::AllocPChildToParentStreamChild();
 }
 
 bool
 ContentBridgeChild::DeallocPChildToParentStreamChild(PChildToParentStreamChild* aActor)
--- a/dom/ipc/ContentBridgeChild.h
+++ b/dom/ipc/ContentBridgeChild.h
@@ -87,16 +87,23 @@ protected:
 
   virtual PBlobChild* AllocPBlobChild(const BlobConstructorParams& aParams) override;
   virtual bool DeallocPBlobChild(PBlobChild*) override;
 
   virtual PMemoryStreamChild*
   AllocPMemoryStreamChild(const uint64_t& aSize) override;
   virtual bool DeallocPMemoryStreamChild(PMemoryStreamChild*) override;
 
+  virtual PIPCBlobInputStreamChild*
+  AllocPIPCBlobInputStreamChild(const nsID& aID,
+                                const uint64_t& aSize) override;
+
+  virtual bool
+  DeallocPIPCBlobInputStreamChild(PIPCBlobInputStreamChild*) override;
+
   virtual mozilla::ipc::PChildToParentStreamChild*
   AllocPChildToParentStreamChild() override;
 
   virtual bool
   DeallocPChildToParentStreamChild(mozilla::ipc::PChildToParentStreamChild* aActor) override;
 
   virtual PParentToChildStreamChild* AllocPParentToChildStreamChild() override;
 
--- a/dom/ipc/ContentBridgeParent.cpp
+++ b/dom/ipc/ContentBridgeParent.cpp
@@ -126,28 +126,50 @@ ContentBridgeParent::AllocPBlobParent(co
 }
 
 bool
 ContentBridgeParent::DeallocPBlobParent(PBlobParent* aActor)
 {
   return nsIContentParent::DeallocPBlobParent(aActor);
 }
 
+PIPCBlobInputStreamParent*
+ContentBridgeParent::SendPIPCBlobInputStreamConstructor(PIPCBlobInputStreamParent* aActor,
+                                                        const nsID& aID,
+                                                        const uint64_t& aSize)
+{
+  return
+    PContentBridgeParent::SendPIPCBlobInputStreamConstructor(aActor, aID, aSize);
+}
+
 PMemoryStreamParent*
 ContentBridgeParent::AllocPMemoryStreamParent(const uint64_t& aSize)
 {
   return nsIContentParent::AllocPMemoryStreamParent(aSize);
 }
 
 bool
 ContentBridgeParent::DeallocPMemoryStreamParent(PMemoryStreamParent* aActor)
 {
   return nsIContentParent::DeallocPMemoryStreamParent(aActor);
 }
 
+PIPCBlobInputStreamParent*
+ContentBridgeParent::AllocPIPCBlobInputStreamParent(const nsID& aID,
+                                                    const uint64_t& aSize)
+{
+  return nsIContentParent::AllocPIPCBlobInputStreamParent(aID, aSize);
+}
+
+bool
+ContentBridgeParent::DeallocPIPCBlobInputStreamParent(PIPCBlobInputStreamParent* aActor)
+{
+  return nsIContentParent::DeallocPIPCBlobInputStreamParent(aActor);
+}
+
 mozilla::jsipc::PJavaScriptParent *
 ContentBridgeParent::AllocPJavaScriptParent()
 {
   return nsIContentParent::AllocPJavaScriptParent();
 }
 
 bool
 ContentBridgeParent::DeallocPJavaScriptParent(PJavaScriptParent *parent)
--- a/dom/ipc/ContentBridgeParent.h
+++ b/dom/ipc/ContentBridgeParent.h
@@ -138,16 +138,28 @@ protected:
 
   virtual bool DeallocPBlobParent(PBlobParent*) override;
 
   virtual PMemoryStreamParent*
   AllocPMemoryStreamParent(const uint64_t& aSize) override;
 
   virtual bool DeallocPMemoryStreamParent(PMemoryStreamParent*) override;
 
+  virtual PIPCBlobInputStreamParent*
+  SendPIPCBlobInputStreamConstructor(PIPCBlobInputStreamParent* aActor,
+                                     const nsID& aID,
+                                     const uint64_t& aSize) override;
+
+  virtual PIPCBlobInputStreamParent*
+  AllocPIPCBlobInputStreamParent(const nsID& aID,
+                                 const uint64_t& aSize) override;
+
+  virtual bool
+  DeallocPIPCBlobInputStreamParent(PIPCBlobInputStreamParent*) override;
+
   virtual PChildToParentStreamParent* AllocPChildToParentStreamParent() override;
 
   virtual bool
   DeallocPChildToParentStreamParent(PChildToParentStreamParent* aActor) override;
 
   virtual mozilla::ipc::PParentToChildStreamParent*
   AllocPParentToChildStreamParent() override;
 
--- a/dom/ipc/ContentChild.cpp
+++ b/dom/ipc/ContentChild.cpp
@@ -1579,16 +1579,29 @@ ContentChild::AllocPMemoryStreamChild(co
 }
 
 bool
 ContentChild::DeallocPMemoryStreamChild(PMemoryStreamChild* aActor)
 {
   return nsIContentChild::DeallocPMemoryStreamChild(aActor);
 }
 
+PIPCBlobInputStreamChild*
+ContentChild::AllocPIPCBlobInputStreamChild(const nsID& aID,
+                                            const uint64_t& aSize)
+{
+  return nsIContentChild::AllocPIPCBlobInputStreamChild(aID, aSize);
+}
+
+bool
+ContentChild::DeallocPIPCBlobInputStreamChild(PIPCBlobInputStreamChild* aActor)
+{
+  return nsIContentChild::DeallocPIPCBlobInputStreamChild(aActor);
+}
+
 PBlobChild*
 ContentChild::AllocPBlobChild(const BlobConstructorParams& aParams)
 {
   return nsIContentChild::AllocPBlobChild(aParams);
 }
 
 mozilla::PRemoteSpellcheckEngineChild *
 ContentChild::AllocPRemoteSpellcheckEngineChild()
--- a/dom/ipc/ContentChild.h
+++ b/dom/ipc/ContentChild.h
@@ -186,16 +186,23 @@ public:
   virtual bool DeallocPBlobChild(PBlobChild* aActor) override;
 
   virtual PMemoryStreamChild*
   AllocPMemoryStreamChild(const uint64_t& aSize) override;
 
   virtual bool
   DeallocPMemoryStreamChild(PMemoryStreamChild* aActor) override;
 
+  virtual PIPCBlobInputStreamChild*
+  AllocPIPCBlobInputStreamChild(const nsID& aID,
+                                const uint64_t& aSize) override;
+
+  virtual bool
+  DeallocPIPCBlobInputStreamChild(PIPCBlobInputStreamChild* aActor) override;
+
   virtual PHalChild* AllocPHalChild() override;
   virtual bool DeallocPHalChild(PHalChild*) override;
 
   virtual PHeapSnapshotTempFileHelperChild*
   AllocPHeapSnapshotTempFileHelperChild() override;
 
   virtual bool
   DeallocPHeapSnapshotTempFileHelperChild(PHeapSnapshotTempFileHelperChild*) override;
--- a/dom/ipc/ContentParent.cpp
+++ b/dom/ipc/ContentParent.cpp
@@ -530,16 +530,18 @@ ScriptableCPInfo::GetMessageManager(nsIM
 
 } // anonymous namespace
 
 nsTArray<ContentParent*>* ContentParent::sPrivateContent;
 StaticAutoPtr<LinkedList<ContentParent> > ContentParent::sContentParents;
 #if defined(XP_LINUX) && defined(MOZ_CONTENT_SANDBOX)
 UniquePtr<SandboxBrokerPolicyFactory> ContentParent::sSandboxBrokerPolicyFactory;
 #endif
+uint64_t ContentParent::sNextTabParentId = 0;
+nsDataHashtable<nsUint64HashKey, TabParent*> ContentParent::sNextTabParents;
 
 // This is true when subprocess launching is enabled.  This is the
 // case between StartUp() and ShutDown() or JoinAllSubprocesses().
 static bool sCanLaunchSubprocesses;
 
 // Set to true if the DISABLE_UNSAFE_CPOW_WARNINGS environment variable is
 // set.
 static bool sDisableUnsafeCPOWWarnings = false;
@@ -1147,27 +1149,33 @@ ContentParent::RecvFindPlugins(const uin
   *aRv = mozilla::plugins::FindPluginsForContent(aPluginEpoch, aPlugins, aNewPluginEpoch);
   return IPC_OK();
 }
 
 /*static*/ TabParent*
 ContentParent::CreateBrowser(const TabContext& aContext,
                              Element* aFrameElement,
                              ContentParent* aOpenerContentParent,
-                             TabParent* aSameTabGroupAs)
+                             TabParent* aSameTabGroupAs,
+                             uint64_t aNextTabParentId)
 {
   PROFILER_LABEL_FUNC(js::ProfileEntry::Category::OTHER);
 
   if (!sCanLaunchSubprocesses) {
     return nullptr;
   }
 
-  if (TabParent* parent = TabParent::GetNextTabParent()) {
-    parent->SetOwnerElement(aFrameElement);
-    return parent;
+  if (aNextTabParentId) {
+    if (TabParent* parent =
+          sNextTabParents.GetAndRemove(aNextTabParentId).valueOr(nullptr)) {
+      MOZ_ASSERT(!parent->GetOwnerElement(),
+                 "Shouldn't have an owner elemnt before");
+      parent->SetOwnerElement(aFrameElement);
+      return parent;
+    }
   }
 
   ProcessPriority initialPriority = GetInitialProcessPriority(aFrameElement);
   bool isInContentProcess = !XRE_IsParentProcess();
   TabId tabId;
 
   nsIDocShell* docShell = GetOpenerDocShellHelper(aFrameElement);
   TabId openerTabId;
@@ -2833,16 +2841,29 @@ ContentParent::AllocPMemoryStreamParent(
 }
 
 bool
 ContentParent::DeallocPMemoryStreamParent(PMemoryStreamParent* aActor)
 {
   return nsIContentParent::DeallocPMemoryStreamParent(aActor);
 }
 
+PIPCBlobInputStreamParent*
+ContentParent::AllocPIPCBlobInputStreamParent(const nsID& aID,
+                                              const uint64_t& aSize)
+{
+  return nsIContentParent::AllocPIPCBlobInputStreamParent(aID, aSize);
+}
+
+bool
+ContentParent::DeallocPIPCBlobInputStreamParent(PIPCBlobInputStreamParent* aActor)
+{
+  return nsIContentParent::DeallocPIPCBlobInputStreamParent(aActor);
+}
+
 mozilla::ipc::IPCResult
 ContentParent::RecvPBlobConstructor(PBlobParent* aActor,
                                     const BlobConstructorParams& aParams)
 {
   const ParentBlobConstructorParams& params = aParams.get_ParentBlobConstructorParams();
   if (params.blobParams().type() == AnyBlobConstructorParams::TKnownBlobConstructorParams) {
     if (!aActor->SendCreatedFromKnownBlob()) {
       return IPC_FAIL_NO_REASON(this);
@@ -3830,16 +3851,24 @@ ContentParent::DoSendAsyncMessage(JSCont
 
 PBlobParent*
 ContentParent::SendPBlobConstructor(PBlobParent* aActor,
                                     const BlobConstructorParams& aParams)
 {
   return PContentParent::SendPBlobConstructor(aActor, aParams);
 }
 
+PIPCBlobInputStreamParent*
+ContentParent::SendPIPCBlobInputStreamConstructor(PIPCBlobInputStreamParent* aActor,
+                                                  const nsID& aID,
+                                                  const uint64_t& aSize)
+{
+  return PContentParent::SendPIPCBlobInputStreamConstructor(aActor, aID, aSize);
+}
+
 PBrowserParent*
 ContentParent::SendPBrowserConstructor(PBrowserParent* aActor,
                                        const TabId& aTabId,
                                        const TabId& aSameTabGroupAs,
                                        const IPCTabContext& aContext,
                                        const uint32_t& aChromeFlags,
                                        const ContentParentId& aCpId,
                                        const bool& aIsForBrowser)
@@ -4436,16 +4465,17 @@ ContentParent::CommonCreateWindow(PBrows
                                   const bool& aCalledFromJS,
                                   const bool& aPositionSpecified,
                                   const bool& aSizeSpecified,
                                   nsIURI* aURIToLoad,
                                   const nsCString& aFeatures,
                                   const nsCString& aBaseURI,
                                   const OriginAttributes& aOpenerOriginAttributes,
                                   const float& aFullZoom,
+                                  uint64_t aNextTabParentId,
                                   nsresult& aResult,
                                   nsCOMPtr<nsITabParent>& aNewTabParent,
                                   bool* aWindowIsNew)
 
 {
   // The content process should never be in charge of computing whether or
   // not a window should be private or remote - the parent will do that.
   const uint32_t badFlags = nsIWebBrowserChrome::CHROME_PRIVATE_WINDOW |
@@ -4518,16 +4548,17 @@ ContentParent::CommonCreateWindow(PBrows
     nsCOMPtr<nsIOpenURIInFrameParams> params =
       new nsOpenURIInFrameParams(aOpenerOriginAttributes);
     params->SetReferrer(NS_ConvertUTF8toUTF16(aBaseURI));
     params->SetIsPrivate(isPrivate);
 
     nsCOMPtr<nsIFrameLoaderOwner> frameLoaderOwner;
     aResult = browserDOMWin->OpenURIInFrame(aURIToLoad, params, openLocation,
                                             nsIBrowserDOMWindow::OPEN_NEW,
+                                            aNextTabParentId,
                                             getter_AddRefs(frameLoaderOwner));
     if (NS_SUCCEEDED(aResult) && frameLoaderOwner) {
       RefPtr<nsFrameLoader> frameLoader = frameLoaderOwner->GetFrameLoader();
       if (frameLoader) {
         frameLoader->GetTabParent(getter_AddRefs(aNewTabParent));
       }
     } else {
       *aWindowIsNew = false;
@@ -4539,16 +4570,17 @@ ContentParent::CommonCreateWindow(PBrows
   nsCOMPtr<nsPIWindowWatcher> pwwatch =
     do_GetService(NS_WINDOWWATCHER_CONTRACTID, &aResult);
   if (NS_WARN_IF(NS_FAILED(aResult))) {
     return IPC_OK();
   }
 
   aResult = pwwatch->OpenWindowWithTabParent(aSetOpener ? thisTabParent : nullptr,
                                              aFeatures, aCalledFromJS, aFullZoom,
+                                             aNextTabParentId,
                                              getter_AddRefs(aNewTabParent));
   if (NS_WARN_IF(NS_FAILED(aResult))) {
     return IPC_OK();
   }
 
   if (aURIToLoad) {
     nsCOMPtr<mozIDOMWindowProxy> openerWindow;
     if (aSetOpener && thisTabParent) {
@@ -4604,32 +4636,38 @@ ContentParent::RecvCreateWindow(PBrowser
       }
     }
   });
 
   // Content has requested that we open this new content window, so
   // we must have an opener.
   newTab->SetHasContentOpener(true);
 
-  TabParent::AutoUseNewTab aunt(newTab, aWindowIsNew, aURLToLoad);
+  TabParent::AutoUseNewTab aunt(newTab, aURLToLoad);
+  const uint64_t nextTabParentId = ++sNextTabParentId;
+  sNextTabParents.Put(nextTabParentId, newTab);
 
   nsCOMPtr<nsITabParent> newRemoteTab;
   mozilla::ipc::IPCResult ipcResult =
     CommonCreateWindow(aThisTab, /* aSetOpener = */ true, aChromeFlags,
                        aCalledFromJS, aPositionSpecified, aSizeSpecified,
                        nullptr, aFeatures, aBaseURI, aOpenerOriginAttributes,
-                       aFullZoom, *aResult, newRemoteTab, aWindowIsNew);
+                       aFullZoom, nextTabParentId, *aResult,
+                       newRemoteTab, aWindowIsNew);
   if (!ipcResult) {
     return ipcResult;
   }
 
   if (NS_WARN_IF(NS_FAILED(*aResult))) {
     return IPC_OK();
   }
 
+  if (sNextTabParents.GetAndRemove(nextTabParentId).valueOr(nullptr)) {
+    *aWindowIsNew = false;
+  }
   MOZ_ASSERT(TabParent::GetFrom(newRemoteTab) == newTab);
 
   newTab->SwapFrameScriptsFrom(*aFrameScripts);
 
   RenderFrameParent* rfp = static_cast<RenderFrameParent*>(aRenderFrame);
   if (!newTab->SetRenderFrame(rfp) ||
       !newTab->GetRenderFrameInfo(aTextureFactoryIdentifier, aLayersId)) {
     *aResult = NS_ERROR_FAILURE;
@@ -4655,17 +4693,18 @@ ContentParent::RecvCreateWindowInDiffere
   nsCOMPtr<nsITabParent> newRemoteTab;
   bool windowIsNew;
   nsCOMPtr<nsIURI> uriToLoad = DeserializeURI(aURIToLoad);
   nsresult rv;
   mozilla::ipc::IPCResult ipcResult =
     CommonCreateWindow(aThisTab, /* aSetOpener = */ false, aChromeFlags,
                        aCalledFromJS, aPositionSpecified, aSizeSpecified,
                        uriToLoad, aFeatures, aBaseURI, aOpenerOriginAttributes,
-                       aFullZoom, rv, newRemoteTab, &windowIsNew);
+                       aFullZoom, /* aNextTabParentId = */ 0, rv,
+                       newRemoteTab, &windowIsNew);
   if (!ipcResult) {
     return ipcResult;
   }
 
   if (NS_FAILED(rv)) {
     NS_WARNING("Call to CommonCreateWindow failed.");
   }
 
--- a/dom/ipc/ContentParent.h
+++ b/dom/ipc/ContentParent.h
@@ -184,17 +184,18 @@ public:
    * Get or create a content process for the given TabContext.  aFrameElement
    * should be the frame/iframe element with which this process will
    * associated.
    */
   static TabParent*
   CreateBrowser(const TabContext& aContext,
                 Element* aFrameElement,
                 ContentParent* aOpenerContentParent,
-                TabParent* aSameTabGroupAs);
+                TabParent* aSameTabGroupAs,
+                uint64_t aNextTabParentId);
 
   static void GetAll(nsTArray<ContentParent*>& aArray);
 
   static void GetAllEvenIfDead(nsTArray<ContentParent*>& aArray);
 
   const nsAString& GetRemoteType() const;
 
   enum CPIteratorPolicy {
@@ -714,16 +715,17 @@ private:
                      const bool& aCalledFromJS,
                      const bool& aPositionSpecified,
                      const bool& aSizeSpecified,
                      nsIURI* aURIToLoad,
                      const nsCString& aFeatures,
                      const nsCString& aBaseURI,
                      const OriginAttributes& aOpenerOriginAttributes,
                      const float& aFullZoom,
+                     uint64_t aNextTabParentId,
                      nsresult& aResult,
                      nsCOMPtr<nsITabParent>& aNewTabParent,
                      bool* aWindowIsNew);
 
   FORWARD_SHMEM_ALLOCATOR_TO(PContentParent)
 
   ContentParent(ContentParent* aOpener,
                 const nsAString& aRemoteType);
@@ -846,16 +848,28 @@ private:
 
   virtual bool DeallocPBlobParent(PBlobParent* aActor) override;
 
   virtual PMemoryStreamParent*
   AllocPMemoryStreamParent(const uint64_t& aSize) override;
 
   virtual bool DeallocPMemoryStreamParent(PMemoryStreamParent* aActor) override;
 
+  virtual PIPCBlobInputStreamParent*
+  SendPIPCBlobInputStreamConstructor(PIPCBlobInputStreamParent* aActor,
+                                     const nsID& aID,
+                                     const uint64_t& aSize) override;
+
+  virtual PIPCBlobInputStreamParent*
+  AllocPIPCBlobInputStreamParent(const nsID& aID,
+                                 const uint64_t& aSize) override;
+
+  virtual bool
+  DeallocPIPCBlobInputStreamParent(PIPCBlobInputStreamParent* aActor) override;
+
   virtual mozilla::ipc::IPCResult
   RecvPBlobConstructor(PBlobParent* aActor,
                        const BlobConstructorParams& params) override;
 
   virtual mozilla::ipc::IPCResult RecvNSSU2FTokenIsCompatibleVersion(const nsString& aVersion,
                                                                      bool* aIsCompatible) override;
 
   virtual mozilla::ipc::IPCResult RecvNSSU2FTokenIsRegistered(nsTArray<uint8_t>&& aKeyHandle,
@@ -1257,16 +1271,19 @@ private:
   nsRefPtrHashtable<nsIDHashKey, GetFilesHelper> mGetFilesPendingRequests;
 
   nsTHashtable<nsCStringHashKey> mActivePermissionKeys;
 
   nsTArray<nsCString> mBlobURLs;
 #ifdef MOZ_CRASHREPORTER
   UniquePtr<mozilla::ipc::CrashReporterHost> mCrashReporter;
 #endif
+
+  static uint64_t sNextTabParentId;
+  static nsDataHashtable<nsUint64HashKey, TabParent*> sNextTabParents;
 };
 
 } // namespace dom
 } // namespace mozilla
 
 class ParentIdleListener : public nsIObserver
 {
   friend class mozilla::dom::ContentParent;
--- a/dom/ipc/DOMTypes.ipdlh
+++ b/dom/ipc/DOMTypes.ipdlh
@@ -2,16 +2,17 @@
 /* vim: set sw=4 ts=8 et tw=80 ft=cpp : */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 include protocol PBlob;
 include protocol PMemoryStream;
 
+include IPCBlob;
 include IPCStream;
 include ProtocolTypes;
 
 using struct mozilla::void_t
   from "ipc/IPCMessageUtils.h";
 
 using struct mozilla::SerializedStructuredCloneBuffer
   from "ipc/IPCMessageUtils.h";
@@ -36,27 +37,36 @@ struct MessagePortIdentifier
  * Cross-process representation for postMessage() style payloads where Blobs may
  * be referenced/"cloned" and (optionally) messageports transferred.  Use
  * StructuredCloneData in your code to convert between this wire representation
  * and the StructuredCloneData StructuredCloneHolder-subclass.
  */
 struct ClonedMessageData
 {
   SerializedStructuredCloneBuffer data;
-  PBlob[] blobs;
+  IPCBlob[] blobs;
   IPCStream[] inputStreams;
   MessagePortIdentifier[] identfiers;
 };
 
-struct BlobDataStream
+struct MemoryBlobDataStream
 {
   PMemoryStream stream;
   uint64_t length;
 };
 
+union BlobDataStream
+{
+  MemoryBlobDataStream;
+
+  // InputStreamParams is used only when we are _sure_ that the serialized size
+  // is lower than 1 Mb. Otherwise we use MemoryBlobDataStream.
+  IPCStream;
+};
+
 union BlobData
 {
   // For remote blobs.
   nsID;
 
   // For memory-backed blobs.
   BlobDataStream;
 
--- a/dom/ipc/PBrowser.ipdl
+++ b/dom/ipc/PBrowser.ipdl
@@ -14,17 +14,21 @@ include protocol PDocumentRenderer;
 include protocol PFilePicker;
 include protocol PIndexedDBPermissionRequest;
 include protocol PRenderFrame;
 include protocol PPluginWidget;
 include protocol PRemotePrintJob;
 include protocol PChildToParentStream;
 include protocol PParentToChildStream;
 include protocol PFileDescriptorSet;
+include protocol PIPCBlobInputStream;
+
 include DOMTypes;
+include IPCBlob;
+include IPCStream;
 include JavaScriptTypes;
 include URIParams;
 include PPrintingTypes;
 include PTabContext;
 
 
 using class IPC::Principal from "mozilla/dom/PermissionMessageUtils.h";
 using class mozilla::gfx::Matrix from "mozilla/gfx/Matrix.h";
--- a/dom/ipc/PContent.ipdl
+++ b/dom/ipc/PContent.ipdl
@@ -14,16 +14,17 @@ include protocol PCycleCollectWithLogs;
 include protocol PPSMContentDownloader;
 include protocol PExternalHelperApp;
 include protocol PHandlerService;
 include protocol PFileDescriptorSet;
 include protocol PHal;
 include protocol PHeapSnapshotTempFileHelper;
 include protocol PProcessHangMonitor;
 include protocol PImageBridge;
+include protocol PIPCBlobInputStream;
 include protocol PMedia;
 include protocol PMemoryStream;
 include protocol PNecko;
 include protocol PGMPContent;
 include protocol PGMPService;
 include protocol PPluginModule;
 include protocol PGMP;
 include protocol PPrinting;
@@ -282,16 +283,17 @@ nested(upto inside_cpow) sync protocol P
     manages PContentPermissionRequest;
     manages PCycleCollectWithLogs;
     manages PPSMContentDownloader;
     manages PExternalHelperApp;
     manages PFileDescriptorSet;
     manages PHal;
     manages PHandlerService;
     manages PHeapSnapshotTempFileHelper;
+    manages PIPCBlobInputStream;
     manages PMedia;
     manages PMemoryStream;
     manages PNecko;
     manages POfflineCacheUpdate;
     manages PPrinting;
     manages PChildToParentStream;
     manages PParentToChildStream;
     manages PSpeechSynthesis;
@@ -604,16 +606,18 @@ child:
     async PParentToChildStream();
 
     async ProvideAnonymousTemporaryFile(uint64_t aID, FileDescOrError aFD);
 
     async SetPermissionsWithKey(nsCString aPermissionKey, Permission[] aPermissions);
 
     async RefreshScreens(ScreenDetails[] aScreens);
 
+    async PIPCBlobInputStream(nsID aID, uint64_t aSize);
+
 parent:
     async InitBackground(Endpoint<PBackgroundParent> aEndpoint);
 
     sync CreateChildProcess(IPCTabContext context,
                             ProcessPriority priority,
                             TabId openerTabId)
         returns (ContentParentId cpId, bool isForBrowser, TabId tabId);
     sync BridgeToChildProcess(ContentParentId cpId)
--- a/dom/ipc/PContentBridge.ipdl
+++ b/dom/ipc/PContentBridge.ipdl
@@ -7,19 +7,21 @@
 include protocol PBlob;
 include protocol PBrowser;
 include protocol PContent;
 include protocol PJavaScript;
 include protocol PFileDescriptorSet;
 include protocol PChildToParentStream;
 include protocol PMemoryStream;
 include protocol PParentToChildStream;
+include protocol PIPCBlobInputStream;
 
 include DOMTypes;
 include JavaScriptTypes;
+include ProtocolTypes;
 include PTabContext;
 
 using class IPC::Principal from "mozilla/dom/PermissionMessageUtils.h";
 using mozilla::dom::TabId from "mozilla/dom/ipc/IdType.h";
 using mozilla::dom::ContentParentId from "mozilla/dom/ipc/IdType.h";
 using class mozilla::dom::MessagePort from "mozilla/dom/MessagePort.h";
 using class mozilla::dom::ipc::StructuredCloneData from "mozilla/dom/ipc/StructuredCloneData.h";
 
@@ -38,30 +40,33 @@ nested(upto inside_cpow) sync protocol P
 {
     manages PBlob;
     manages PBrowser;
     manages PFileDescriptorSet;
     manages PJavaScript;
     manages PChildToParentStream;
     manages PMemoryStream;
     manages PParentToChildStream;
+    manages PIPCBlobInputStream;
 
 child:
     async PParentToChildStream();
 
 child:
    /**
      * Sending an activate message moves focus to the child.
      */
     async Activate(PBrowser aTab);
 
     async Deactivate(PBrowser aTab);
 
     async ParentActivated(PBrowser aTab, bool aActivated);
 
+    async PIPCBlobInputStream(nsID aID, uint64_t aSize);
+
 parent:
     sync SyncMessage(nsString aMessage, ClonedMessageData aData,
                      CpowEntry[] aCpows, Principal aPrincipal)
       returns (StructuredCloneData[] retval);
 
     async PJavaScript();
 
     async PChildToParentStream();
--- a/dom/ipc/PProcessHangMonitor.ipdl
+++ b/dom/ipc/PProcessHangMonitor.ipdl
@@ -9,17 +9,16 @@ using base::ProcessId from "base/process
 using mozilla::dom::TabId from "mozilla/dom/ipc/IdType.h";
 
 namespace mozilla {
 
 struct SlowScriptData
 {
   TabId tabId;
   nsCString filename;
-  uint32_t lineno;
 };
 
 struct PluginHangData
 {
   uint32_t pluginId;
   ProcessId contentProcessId;
 };
 
--- a/dom/ipc/ProcessHangMonitor.cpp
+++ b/dom/ipc/ProcessHangMonitor.cpp
@@ -80,21 +80,19 @@ class HangMonitorChild
  public:
   explicit HangMonitorChild(ProcessHangMonitor* aMonitor);
   ~HangMonitorChild() override;
 
   void Bind(Endpoint<PProcessHangMonitorChild>&& aEndpoint);
 
   typedef ProcessHangMonitor::SlowScriptAction SlowScriptAction;
   SlowScriptAction NotifySlowScript(nsITabChild* aTabChild,
-                                    const char* aFileName,
-                                    unsigned aLineNo);
+                                    const char* aFileName);
   void NotifySlowScriptAsync(TabId aTabId,
-                             const nsCString& aFileName,
-                             unsigned aLineNo);
+                             const nsCString& aFileName);
 
   bool IsDebuggerStartupComplete();
 
   void NotifyPluginHang(uint32_t aPluginId);
   void NotifyPluginHangAsync(uint32_t aPluginId);
 
   void ClearHang();
   void ClearHangAsync();
@@ -155,17 +153,16 @@ public:
 
   HangMonitoredProcess(HangMonitorParent* aActor,
                        ContentParent* aContentParent)
     : mActor(aActor), mContentParent(aContentParent) {}
 
   NS_IMETHOD GetHangType(uint32_t* aHangType) override;
   NS_IMETHOD GetScriptBrowser(nsIDOMElement** aBrowser) override;
   NS_IMETHOD GetScriptFileName(nsACString& aFileName) override;
-  NS_IMETHOD GetScriptLineNo(uint32_t* aLineNo) override;
 
   NS_IMETHOD GetPluginName(nsACString& aPluginName) override;
 
   NS_IMETHOD TerminateScript() override;
   NS_IMETHOD BeginStartingDebugger() override;
   NS_IMETHOD EndStartingDebugger() override;
   NS_IMETHOD TerminatePlugin() override;
   NS_IMETHOD UserCanceled() override;
@@ -453,28 +450,26 @@ HangMonitorChild::Bind(Endpoint<PProcess
   DebugOnly<bool> ok = aEndpoint.Bind(this);
   MOZ_ASSERT(ok);
 
   Unused << SendReady();
 }
 
 void
 HangMonitorChild::NotifySlowScriptAsync(TabId aTabId,
-                                        const nsCString& aFileName,
-                                        unsigned aLineNo)
+                                        const nsCString& aFileName)
 {
   if (mIPCOpen) {
-    Unused << SendHangEvidence(SlowScriptData(aTabId, aFileName, aLineNo));
+    Unused << SendHangEvidence(SlowScriptData(aTabId, aFileName));
   }
 }
 
 HangMonitorChild::SlowScriptAction
 HangMonitorChild::NotifySlowScript(nsITabChild* aTabChild,
-                                   const char* aFileName,
-                                   unsigned aLineNo)
+                                   const char* aFileName)
 {
   MOZ_RELEASE_ASSERT(NS_IsMainThread());
 
   mSentReport = true;
 
   {
     MonitorAutoLock lock(mMonitor);
 
@@ -492,19 +487,19 @@ HangMonitorChild::NotifySlowScript(nsITa
   TabId id;
   if (aTabChild) {
     RefPtr<TabChild> tabChild = static_cast<TabChild*>(aTabChild);
     id = tabChild->GetTabId();
   }
   nsAutoCString filename(aFileName);
 
   MonitorLoop()->PostTask(NewNonOwningRunnableMethod
-                          <TabId, nsCString, unsigned>(this,
-                                                       &HangMonitorChild::NotifySlowScriptAsync,
-                                                       id, filename, aLineNo));
+                          <TabId, nsCString>(this,
+                                             &HangMonitorChild::NotifySlowScriptAsync,
+                                             id, filename));
   return SlowScriptAction::Continue;
 }
 
 bool
 HangMonitorChild::IsDebuggerStartupComplete()
 {
   MOZ_RELEASE_ASSERT(NS_IsMainThread());
 
@@ -958,28 +953,16 @@ HangMonitoredProcess::GetScriptFileName(
     return NS_ERROR_NOT_AVAILABLE;
   }
 
   aFileName = mHangData.get_SlowScriptData().filename();
   return NS_OK;
 }
 
 NS_IMETHODIMP
-HangMonitoredProcess::GetScriptLineNo(uint32_t* aLineNo)
-{
-  MOZ_RELEASE_ASSERT(NS_IsMainThread());
-  if (mHangData.type() != HangData::TSlowScriptData) {
-    return NS_ERROR_NOT_AVAILABLE;
-  }
-
-  *aLineNo = mHangData.get_SlowScriptData().lineno();
-  return NS_OK;
-}
-
-NS_IMETHODIMP
 HangMonitoredProcess::GetPluginName(nsACString& aPluginName)
 {
   MOZ_RELEASE_ASSERT(NS_IsMainThread());
   if (mHangData.type() != HangData::TPluginHangData) {
     return NS_ERROR_NOT_AVAILABLE;
   }
 
   uint32_t id = mHangData.get_PluginHangData().pluginId();
@@ -1177,21 +1160,20 @@ ProcessHangMonitor::Observe(nsISupports*
       obs->RemoveObserver(this, "xpcom-shutdown");
     }
   }
   return NS_OK;
 }
 
 ProcessHangMonitor::SlowScriptAction
 ProcessHangMonitor::NotifySlowScript(nsITabChild* aTabChild,
-                                     const char* aFileName,
-                                     unsigned aLineNo)
+                                     const char* aFileName)
 {
   MOZ_RELEASE_ASSERT(NS_IsMainThread());
-  return HangMonitorChild::Get()->NotifySlowScript(aTabChild, aFileName, aLineNo);
+  return HangMonitorChild::Get()->NotifySlowScript(aTabChild, aFileName);
 }
 
 bool
 ProcessHangMonitor::IsDebuggerStartupComplete()
 {
   MOZ_RELEASE_ASSERT(NS_IsMainThread());
   return HangMonitorChild::Get()->IsDebuggerStartupComplete();
 }
--- a/dom/ipc/ProcessHangMonitor.h
+++ b/dom/ipc/ProcessHangMonitor.h
@@ -52,18 +52,17 @@ class ProcessHangMonitor final
   static void ClearForcePaint();
 
   enum SlowScriptAction {
     Continue,
     Terminate,
     StartDebugger
   };
   SlowScriptAction NotifySlowScript(nsITabChild* aTabChild,
-                                    const char* aFileName,
-                                    unsigned aLineNo);
+                                    const char* aFileName);
 
   void NotifyPluginHang(uint32_t aPluginId);
 
   bool IsDebuggerStartupComplete();
 
   void InitiateCPOWTimeout();
   bool ShouldTimeOutCPOWs();
 
--- a/dom/ipc/StructuredCloneData.cpp
+++ b/dom/ipc/StructuredCloneData.cpp
@@ -8,16 +8,17 @@
 
 #include "nsIDOMDOMException.h"
 #include "nsIMutable.h"
 #include "nsIXPConnect.h"
 
 #include "ipc/IPCMessageUtils.h"
 #include "mozilla/dom/BindingUtils.h"
 #include "mozilla/dom/BlobBinding.h"
+#include "mozilla/dom/IPCBlobUtils.h"
 #include "mozilla/dom/File.h"
 #include "mozilla/ipc/IPCStreamUtils.h"
 #include "nsContentUtils.h"
 #include "nsJSEnvironment.h"
 #include "MainThreadUtils.h"
 #include "StructuredCloneTags.h"
 #include "jsapi.h"
 
@@ -137,102 +138,19 @@ enum ActorFlavorEnum {
   Child,
 };
 
 enum ManagerFlavorEnum {
   ContentProtocol = 0,
   BackgroundProtocol
 };
 
-template <ActorFlavorEnum>
-struct BlobTraits
-{ };
-
-template <>
-struct BlobTraits<Parent>
-{
-  typedef mozilla::dom::BlobParent BlobType;
-  typedef mozilla::dom::PBlobParent ProtocolType;
-};
-
-template <>
-struct BlobTraits<Child>
-{
-  typedef mozilla::dom::BlobChild BlobType;
-  typedef mozilla::dom::PBlobChild ProtocolType;
-};
-
-template <ActorFlavorEnum, ManagerFlavorEnum>
-struct ParentManagerTraits
-{ };
-
-template<>
-struct ParentManagerTraits<Parent, ContentProtocol>
-{
-  typedef mozilla::dom::nsIContentParent ConcreteContentManagerType;
-};
-
-template<>
-struct ParentManagerTraits<Child, ContentProtocol>
-{
-  typedef mozilla::dom::nsIContentChild ConcreteContentManagerType;
-};
-
-template<>
-struct ParentManagerTraits<Parent, BackgroundProtocol>
-{
-  typedef mozilla::ipc::PBackgroundParent ConcreteContentManagerType;
-};
-
-template<>
-struct ParentManagerTraits<Child, BackgroundProtocol>
-{
-  typedef mozilla::ipc::PBackgroundChild ConcreteContentManagerType;
-};
-
-template<ActorFlavorEnum>
-struct DataBlobs
-{ };
-
-template<>
-struct DataBlobs<Parent>
-{
-  typedef BlobTraits<Parent>::ProtocolType ProtocolType;
-
-  static InfallibleTArray<ProtocolType*>& Blobs(ClonedMessageData& aData)
-  {
-    return aData.blobsParent();
-  }
-
-  static const InfallibleTArray<ProtocolType*>& Blobs(const ClonedMessageData& aData)
-  {
-    return aData.blobsParent();
-  }
-};
-
-template<>
-struct DataBlobs<Child>
-{
-  typedef BlobTraits<Child>::ProtocolType ProtocolType;
-
-  static InfallibleTArray<ProtocolType*>& Blobs(ClonedMessageData& aData)
-  {
-    return aData.blobsChild();
-  }
-
-  static const InfallibleTArray<ProtocolType*>& Blobs(const ClonedMessageData& aData)
-  {
-    return aData.blobsChild();
-  }
-};
-
-template<ActorFlavorEnum Flavor, ManagerFlavorEnum ManagerFlavor>
-static bool
-BuildClonedMessageData(typename ParentManagerTraits<Flavor, ManagerFlavor>::ConcreteContentManagerType* aManager,
-                       StructuredCloneData& aData,
+template<typename M>
+bool
+BuildClonedMessageData(M* aManager, StructuredCloneData& aData,
                        ClonedMessageData& aClonedData)
 {
   SerializedStructuredCloneBuffer& buffer = aClonedData.data();
   auto iter = aData.Data().Iter();
   size_t size = aData.Data().Size();
   bool success;
   buffer.data = aData.Data().Borrow<js::SystemAllocPolicy>(iter, size, &success);
   if (NS_WARN_IF(!success)) {
@@ -240,33 +158,36 @@ BuildClonedMessageData(typename ParentMa
   }
   if (aData.SupportsTransferring()) {
     aClonedData.identfiers().AppendElements(aData.PortIdentifiers());
   }
 
   const nsTArray<RefPtr<BlobImpl>>& blobImpls = aData.BlobImpls();
 
   if (!blobImpls.IsEmpty()) {
-    typedef typename BlobTraits<Flavor>::BlobType BlobType;
-    typedef typename BlobTraits<Flavor>::ProtocolType ProtocolType;
-    InfallibleTArray<ProtocolType*>& blobList = DataBlobs<Flavor>::Blobs(aClonedData);
-    uint32_t length = blobImpls.Length();
-    blobList.SetCapacity(length);
-    for (uint32_t i = 0; i < length; ++i) {
-      BlobType* protocolActor = BlobType::GetOrCreate(aManager, blobImpls[i]);
-      if (!protocolActor) {
+    if (NS_WARN_IF(!aClonedData.blobs().SetLength(blobImpls.Length(), fallible))) {
+      return false;
+    }
+
+    for (uint32_t i = 0; i < blobImpls.Length(); ++i) {
+      nsresult rv = IPCBlobUtils::Serialize(blobImpls[i], aManager,
+                                            aClonedData.blobs()[i]);
+      if (NS_WARN_IF(NS_FAILED(rv))) {
         return false;
       }
-      blobList.AppendElement(protocolActor);
     }
   }
 
   const nsTArray<nsCOMPtr<nsIInputStream>>& inputStreams = aData.InputStreams();
+  if (!inputStreams.IsEmpty()) {
+    if (NS_WARN_IF(!aData.IPCStreams().SetCapacity(inputStreams.Length(),
+                                                   fallible))) {
+      return false;
+    }
 
-  if (!inputStreams.IsEmpty()) {
     InfallibleTArray<IPCStream>& streams = aClonedData.inputStreams();
     uint32_t length = inputStreams.Length();
     streams.SetCapacity(length);
     for (uint32_t i = 0; i < length; ++i) {
       AutoIPCStream* stream = aData.IPCStreams().AppendElement(fallible);
       if (NS_WARN_IF(!stream)) {
         return false;
       }
@@ -281,41 +202,41 @@ BuildClonedMessageData(typename ParentMa
   return true;
 }
 
 bool
 StructuredCloneData::BuildClonedMessageDataForParent(
   nsIContentParent* aParent,
   ClonedMessageData& aClonedData)
 {
-  return BuildClonedMessageData<Parent, ContentProtocol>(aParent, *this, aClonedData);
+  return BuildClonedMessageData(aParent, *this, aClonedData);
 }
 
 bool
 StructuredCloneData::BuildClonedMessageDataForChild(
   nsIContentChild* aChild,
   ClonedMessageData& aClonedData)
 {
-  return BuildClonedMessageData<Child, ContentProtocol>(aChild, *this, aClonedData);
+  return BuildClonedMessageData(aChild, *this, aClonedData);
 }
 
 bool
 StructuredCloneData::BuildClonedMessageDataForBackgroundParent(
   PBackgroundParent* aParent,
   ClonedMessageData& aClonedData)
 {
-  return BuildClonedMessageData<Parent, BackgroundProtocol>(aParent, *this, aClonedData);
+  return BuildClonedMessageData(aParent, *this, aClonedData);
 }
 
 bool
 StructuredCloneData::BuildClonedMessageDataForBackgroundChild(
   PBackgroundChild* aChild,
   ClonedMessageData& aClonedData)
 {
-  return BuildClonedMessageData<Child, BackgroundProtocol>(aChild, *this, aClonedData);
+  return BuildClonedMessageData(aChild, *this, aClonedData);
 }
 
 // See the StructuredCloneData class block comment for the meanings of each val.
 enum MemoryFlavorEnum {
   BorrowMemory = 0,
   CopyMemory,
   StealMemory
 };
@@ -368,35 +289,30 @@ struct MemoryTraits<StealMemory>
 // and Child/BackgroundChild in this implementation.  The calling methods,
 // however, do maintain the distinction for code-reading purposes and are backed
 // by assertions to enforce there is no misuse.
 template<MemoryFlavorEnum MemoryFlavor, ActorFlavorEnum Flavor>
 static void
 UnpackClonedMessageData(typename MemoryTraits<MemoryFlavor>::ClonedMessageType& aClonedData,
                         StructuredCloneData& aData)
 {
-  typedef typename BlobTraits<Flavor>::ProtocolType ProtocolType;
-  const InfallibleTArray<ProtocolType*>& blobs = DataBlobs<Flavor>::Blobs(aClonedData);
   const InfallibleTArray<MessagePortIdentifier>& identifiers = aClonedData.identfiers();
 
   MemoryTraits<MemoryFlavor>::ProvideBuffer(aClonedData, aData);
 
   if (aData.SupportsTransferring()) {
     aData.PortIdentifiers().AppendElements(identifiers);
   }
 
+  const nsTArray<IPCBlob>& blobs = aClonedData.blobs();
   if (!blobs.IsEmpty()) {
     uint32_t length = blobs.Length();
     aData.BlobImpls().SetCapacity(length);
     for (uint32_t i = 0; i < length; ++i) {
-      auto* blob =
-        static_cast<typename BlobTraits<Flavor>::BlobType*>(blobs[i]);
-      MOZ_ASSERT(blob);
-
-      RefPtr<BlobImpl> blobImpl = blob->GetBlobImpl();
+      RefPtr<BlobImpl> blobImpl = IPCBlobUtils::Deserialize(blobs[i]);
       MOZ_ASSERT(blobImpl);
 
       aData.BlobImpls().AppendElement(blobImpl);
     }
   }
 
   const InfallibleTArray<IPCStream>& streams = aClonedData.inputStreams();
   if (!streams.IsEmpty()) {
--- a/dom/ipc/StructuredCloneData.h
+++ b/dom/ipc/StructuredCloneData.h
@@ -198,18 +198,18 @@ public:
 
   void Write(JSContext* aCx,
              JS::Handle<JS::Value> aValue,
              JS::Handle<JS::Value> aTransfers,
              ErrorResult &aRv);
 
   // Actor-varying methods to convert the structured clone stored in this holder
   // by a previous call to Write() into ClonedMessageData IPC representation.
-  // (Blobs are represented in IPC by PBlob actors, so we need the parent to be
-  // able to create them.)
+  // (Blobs are represented in IPC by IPCBlob actors, so we need the parent to
+  // be able to create them.)
   bool BuildClonedMessageDataForParent(nsIContentParent* aParent,
                                        ClonedMessageData& aClonedData);
   bool BuildClonedMessageDataForChild(nsIContentChild* aChild,
                                       ClonedMessageData& aClonedData);
   bool BuildClonedMessageDataForBackgroundParent(mozilla::ipc::PBackgroundParent* aParent,
                                                  ClonedMessageData& aClonedData);
   bool BuildClonedMessageDataForBackgroundChild(mozilla::ipc::PBackgroundChild* aChild,
                                                 ClonedMessageData& aClonedData);
--- a/dom/ipc/TabParent.cpp
+++ b/dom/ipc/TabParent.cpp
@@ -560,26 +560,16 @@ TabParent::RecvEvent(const RemoteDOMEven
 
   event->SetOwner(target);
 
   bool dummy;
   target->DispatchEvent(event, &dummy);
   return IPC_OK();
 }
 
-TabParent* TabParent::sNextTabParent;
-
-/* static */ TabParent*
-TabParent::GetNextTabParent()
-{
-  TabParent* result = sNextTabParent;
-  sNextTabParent = nullptr;
-  return result;
-}
-
 bool
 TabParent::SendLoadRemoteScript(const nsString& aURL,
                                 const bool& aRunInGlobalScope)
 {
   if (mCreatingWindow) {
     mDelayedFrameScripts.AppendElement(FrameScriptInfo(aURL, aRunInGlobalScope));
     return true;
   }
--- a/dom/ipc/TabParent.h
+++ b/dom/ipc/TabParent.h
@@ -548,18 +548,16 @@ public:
 
   virtual bool
   DeallocPPluginWidgetParent(PPluginWidgetParent* aActor) override;
 
   void SetInitedByParent() { mInitedByParent = true; }
 
   bool IsInitedByParent() const { return mInitedByParent; }
 
-  static TabParent* GetNextTabParent();
-
   bool SendLoadRemoteScript(const nsString& aURL,
                             const bool& aRunInGlobalScope);
 
   void LayerTreeUpdate(uint64_t aEpoch, bool aActive);
 
   virtual mozilla::ipc::IPCResult
   RecvInvokeDragSession(nsTArray<IPCDataTransfer>&& aTransfers,
                         const uint32_t& aAction,
@@ -693,23 +691,16 @@ private:
 
   // We keep a strong reference to the frameloader after we've sent the
   // Destroy message and before we've received __delete__. This allows us to
   // dispatch message manager messages during this time.