merge mozilla-central to mozilla-inbound. r=merge a=merge
authorSebastian Hengst <archaeopteryx@coole-files.de>
Tue, 07 Nov 2017 02:47:30 +0200
changeset 434940 bee168acd12893a551d9a1e2b9204c667ce629a1
parent 434939 89513063a10631897ae55d14828e965c8ebe7aab (current diff)
parent 434930 c2fe4b3b1b930b3e7fdb84eae44cec165394f322 (diff)
child 434941 01af8ee498fcaf66a75feb59fcbcc3067f7138cc
push id117
push userfmarier@mozilla.com
push dateTue, 28 Nov 2017 20:17:16 +0000
reviewersmerge, merge
milestone58.0a1
merge mozilla-central to mozilla-inbound. r=merge a=merge
devtools/shim/aboutdevtools/images/feature-responsive-mode.svg
devtools/shim/aboutdevtools/images/feature-visual-editing.svg
layout/generic/nsFrame.cpp
layout/generic/nsIFrame.h
layout/generic/nsImageFrame.cpp
servo/components/script/dom/htmlappletelement.rs
servo/components/script/dom/webidls/HTMLAppletElement.webidl
taskcluster/taskgraph/try_option_syntax.py
--- a/.clang-format
+++ b/.clang-format
@@ -1,17 +1,10 @@
 BasedOnStyle: Mozilla
 
-# Ignore all comments because they aren't reflowed properly.
-CommentPragmas: "^"
-
-# Force pointers to the type for C++.
-DerivePointerAlignment: false
-PointerAlignment: Left
-
 # Prevent the loss of indentation with these macros
 MacroBlockBegin: "^\
 NS_INTERFACE_MAP_BEGIN|\
 NS_INTERFACE_TABLE_HEAD|\
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION|\
 NS_IMPL_CYCLE_COLLECTION_.*_BEGIN|\
 NS_INTERFACE_TABLE_HEAD_CYCLE_COLLECTION_INHERITED|\
 NS_INTERFACE_TABLE_BEGIN|\
@@ -23,25 +16,16 @@ NS_IMPL_CYCLE_COLLECTION_.*_END|\
 NS_INTERFACE_TABLE_END|\
 NS_INTERFACE_MAP_END_INHERITING|\
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END_INHERITED|\
 NS_IMPL_CYCLE_COLLECTION_UNLINK_END_INHERITED$"
 
 SortIncludes: false
 
 
-# All of the following items are default
-# in the Mozila coding style from clang format 4.0
-AlwaysBreakAfterReturnType: TopLevel
-BinPackArguments: false
-BinPackParameters: false
-SpaceAfterTemplateKeyword: false
-ReflowComments: false
-
-
 BreakBeforeBraces: Custom
 BraceWrapping:
   AfterEnum: true
   AfterStruct: true
   AfterFunction: true
   AfterClass: true
   SplitEmptyFunction: true
 
--- a/browser/base/content/browser.css
+++ b/browser/base/content/browser.css
@@ -1152,19 +1152,22 @@ toolbarpaletteitem[place="palette"] > #d
 }
 
 #BMB_bookmarksPopup[animate="cancel"] {
   -moz-window-transform: none;
 }
 
 %elifndef MOZ_WIDGET_GTK
 
+#BMB_bookmarksPopup {
+  will-change: transform, opacity; /* workaround for bug 1414033 */
+}
+
 #BMB_bookmarksPopup:not([animate="false"]) {
   opacity: 0;
-  will-change: transform; /* workaround for bug 1414033 */
   transform: translateY(-70px);
   transition-property: transform, opacity;
   transition-duration: 0.18s, 0.18s;
   transition-timing-function:
     var(--animation-easing-function), ease-out;
 }
 
 #BMB_bookmarksPopup[side="bottom"]:not([animate="false"]) {
--- a/browser/base/content/content.js
+++ b/browser/base/content/content.js
@@ -192,17 +192,17 @@ var AboutBlockedSiteListener = {
            "https://safebrowsing.google.com/safebrowsing/report_error/?tpl=mozilla"));
         doc.getElementById("learn_more_link").setAttribute("href",
           "https://www.antiphishing.org//");
         break;
     }
 
     // Set the firefox support url.
     doc.getElementById("firefox_support").setAttribute("href",
-      "https://support.mozilla.org/kb/how-does-phishing-and-malware-protection-work");
+      Services.urlFormatter.formatURLPref("app.support.baseURL") + "phishing-malware");
 
     // Show safe browsing details on load if the pref is set to true.
     let showDetails = Services.prefs.getBoolPref("browser.xul.error_pages.show_safe_browsing_details_on_load");
     if (showDetails) {
       let details = content.document.getElementById("errorDescriptionContainer");
       details.removeAttribute("hidden");
     }
 
--- a/browser/base/content/tabbrowser.xml
+++ b/browser/base/content/tabbrowser.xml
@@ -6435,18 +6435,16 @@
       <field name="tabbox" readonly="true">
         this.tabbrowser.mTabBox;
       </field>
 
       <field name="contextMenu" readonly="true">
         document.getElementById("tabContextMenu");
       </field>
 
-      <field name="mTabstripWidth">0</field>
-
       <field name="mTabstrip">
         document.getAnonymousElementByAttribute(this, "anonid", "arrowscrollbox");
       </field>
 
       <field name="_firstTab">null</field>
       <field name="_lastTab">null</field>
       <field name="_beforeSelectedTab">null</field>
       <field name="_beforeHoveredTab">null</field>
@@ -6468,39 +6466,41 @@
               .width;
           }
           return this._restoreTabsButtonWrapperWidth;
         </getter>
       </property>
 
       <method name="updateSessionRestoreVisibility">
         <body><![CDATA[
-          let {restoreTabsButton, restoreTabsButtonWrapperWidth, windowUtils, mTabstripWidth} = this;
+          let {restoreTabsButton, restoreTabsButtonWrapperWidth, windowUtils} = this;
           let restoreTabsButtonWrapper = restoreTabsButton.parentNode;
 
           if (!restoreTabsButtonWrapper.getAttribute("session-exists")) {
             restoreTabsButtonWrapper.removeAttribute("shown");
             return;
           }
 
+          let tabstripWidth = this.mTabstrip.clientWidth;
+
           let newTabButton = document.getAnonymousElementByAttribute(
             this, "anonid", "tabs-newtab-button");
 
           // If there are no pinned tabs it will multiply by 0 and result in 0
           let pinnedTabsWidth = windowUtils.getBoundsWithoutFlushing(this.firstChild).width * this._lastNumPinned;
 
           let numUnpinnedTabs = this.childNodes.length - this._lastNumPinned;
           let unpinnedTabsWidth = windowUtils.getBoundsWithoutFlushing(this.lastChild).width * numUnpinnedTabs;
 
           let tabbarUsedSpace = pinnedTabsWidth + unpinnedTabsWidth
             + windowUtils.getBoundsWithoutFlushing(newTabButton).width;
 
           // Subtract the elements' widths from the available space to ensure
           // that showing the restoreTabsButton won't cause any overflow.
-          if ((mTabstripWidth - tabbarUsedSpace) > restoreTabsButtonWrapperWidth) {
+          if (tabstripWidth - tabbarUsedSpace > restoreTabsButtonWrapperWidth) {
             restoreTabsButtonWrapper.setAttribute("shown", "true");
           } else {
             restoreTabsButtonWrapper.removeAttribute("shown");
           }
         ]]></body>
       </method>
 
       <method name="observe">
@@ -6995,24 +6995,19 @@
               this.updateVisibility();
               TabsInTitlebar.init();
               break;
             case "resize":
               if (aEvent.target != window)
                 break;
 
               TabsInTitlebar.updateAppearance();
-
-              var width = this.mTabstrip.boxObject.width;
-              if (width != this.mTabstripWidth) {
-                this.adjustTabstrip();
-                this._handleTabSelect(true);
-                this.mTabstripWidth = width;
-                this.updateSessionRestoreVisibility();
-              }
+              this.adjustTabstrip();
+              this._handleTabSelect(true);
+              this.updateSessionRestoreVisibility();
               break;
             case "mouseout":
               // If the "related target" (the node to which the pointer went) is not
               // a child of the current document, the mouse just left the window.
               let relatedTarget = aEvent.relatedTarget;
               if (relatedTarget && relatedTarget.ownerDocument == document)
                 break;
             case "mousemove":
--- a/browser/base/content/test/favicons/browser.ini
+++ b/browser/base/content/test/favicons/browser.ini
@@ -6,8 +6,9 @@ support-files =
   file_mask_icon.html
   moz.png
   rich_moz_1.png
   rich_moz_2.png
 
 [browser_multiple_icons_in_short_timeframe.js]
 [browser_rich_icons.js]
 [browser_icon_discovery.js]
+[browser_preferred_icons.js]
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/favicons/browser_preferred_icons.js
@@ -0,0 +1,97 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+const ROOT = "http://mochi.test:8888/browser/browser/base/content/test/favicons/";
+
+function waitIcon(url) {
+  // Because there is debounce logic in ContentLinkHandler.jsm to reduce the
+  // favicon loads, we have to wait some time before checking that icon was
+  // stored properly.
+  return BrowserTestUtils.waitForCondition(
+    () => {
+      let tabIcon = gBrowser.getIcon();
+      info("Found icon " + tabIcon);
+      return tabIcon == url;
+    },
+    "wait for icon load to finish", 200, 25);
+}
+
+function createLinks(linkInfos) {
+  return ContentTask.spawn(gBrowser.selectedBrowser, linkInfos, links => {
+    let doc = content.document;
+    let head = doc.getElementById("linkparent");
+    for (let l of links) {
+      let link = doc.createElement("link");
+      link.rel = "icon";
+      link.href = l.href;
+      link.type = l.type;
+      if (l.size)
+        link.setAttribute("sizes", `${l.size}x${l.size}`);
+      head.appendChild(link);
+    }
+  });
+}
+
+add_task(async function setup() {
+  const URL = ROOT + "discovery.html";
+  let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, URL);
+  registerCleanupFunction(async function() {
+    await BrowserTestUtils.removeTab(tab);
+  });
+});
+
+add_task(async function prefer_svg() {
+  let promise = waitIcon(ROOT + "icon.svg");
+  await createLinks([
+    { href: ROOT + "icon.ico",
+      type: "image/x-icon"
+    },
+    { href: ROOT + "icon.svg",
+      type: "image/svg+xml"
+    },
+    { href: ROOT + "icon.png",
+      type: "image/png",
+      size: 16 * Math.ceil(window.devicePixelRatio)
+    },
+  ]);
+  await promise;
+  // Must have at least one test.
+  Assert.ok(true, "The expected icon has been set");
+});
+
+add_task(async function prefer_sized() {
+  let promise = waitIcon(ROOT + "icon.png");
+  await createLinks([
+    { href: ROOT + "icon.ico",
+      type: "image/x-icon"
+    },
+    { href: ROOT + "icon.png",
+      type: "image/png",
+      size: 16 * Math.ceil(window.devicePixelRatio)
+    },
+    { href: ROOT + "icon2.ico",
+      type: "image/x-icon"
+    },
+  ]);
+  await promise;
+  // Must have at least one test.
+  Assert.ok(true, "The expected icon has been set");
+});
+
+add_task(async function prefer_ico() {
+  let promise = waitIcon(ROOT + "icon2.ico");
+  await createLinks([
+    { href: ROOT + "icon.ico",
+      type: "image/x-icon"
+    },
+    { href: ROOT + "icon.png",
+      type: "image/png",
+    },
+    { href: ROOT + "icon2.ico",
+    type: "image/x-icon"
+  },
+  ]);
+  await promise;
+  // Must have at least one test.
+  Assert.ok(true, "The expected icon has been set");
+});
--- a/browser/base/content/test/performance/browser_windowopen_reflows.js
+++ b/browser/base/content/test/performance/browser_windowopen_reflows.js
@@ -34,45 +34,27 @@ if (Services.appinfo.OS == "Linux") {
         "handleEvent@chrome://browser/content/tabbrowser.xml",
         "inferFromText@chrome://browser/content/browser.js",
         "handleEvent@chrome://browser/content/browser.js",
       ],
     });
   }
 }
 
-if (Services.appinfo.OS == "Darwin") {
-  EXPECTED_REFLOWS.push({
-    stack: [
-      "handleEvent@chrome://browser/content/tabbrowser.xml",
-      "inferFromText@chrome://browser/content/browser.js",
-      "handleEvent@chrome://browser/content/browser.js",
-    ],
-  });
-}
-
 if (Services.appinfo.OS == "WINNT") {
   EXPECTED_REFLOWS.push(
     {
       stack: [
         "verticalMargins@chrome://browser/content/browser-tabsintitlebar.js",
         "_update@chrome://browser/content/browser-tabsintitlebar.js",
         "init@chrome://browser/content/browser-tabsintitlebar.js",
         "handleEvent@chrome://browser/content/tabbrowser.xml",
       ],
       times: 2, // This number should only ever go down - never up.
     },
-
-    {
-      stack: [
-        "handleEvent@chrome://browser/content/tabbrowser.xml",
-        "inferFromText@chrome://browser/content/browser.js",
-        "handleEvent@chrome://browser/content/browser.js",
-      ],
-    },
   );
 }
 
 if (Services.appinfo.OS == "WINNT" || Services.appinfo.OS == "Darwin") {
   EXPECTED_REFLOWS.push(
     {
       stack: [
         "rect@chrome://browser/content/browser-tabsintitlebar.js",
@@ -80,26 +62,16 @@ if (Services.appinfo.OS == "WINNT" || Se
         "init@chrome://browser/content/browser-tabsintitlebar.js",
         "handleEvent@chrome://browser/content/tabbrowser.xml",
       ],
       times: 4, // This number should only ever go down - never up.
     },
   );
 }
 
-if (Services.appinfo.OS == "WINNT" && screen.width <= 1280) {
-  EXPECTED_REFLOWS.push(
-    {
-      stack: [
-        "handleEvent@chrome://browser/content/tabbrowser.xml",
-      ],
-    },
-  );
-}
-
 /*
  * This test ensures that there are no unexpected
  * uninterruptible reflows when opening new windows.
  */
 add_task(async function() {
   // Flushing all caches helps to ensure that we get consistent
   // behaviour when opening a new window, even if windows have been
   // opened in previous tests.
--- a/browser/components/customizableui/CustomizableUI.jsm
+++ b/browser/components/customizableui/CustomizableUI.jsm
@@ -2764,16 +2764,20 @@ var CustomizableUIInternal = {
    * @return {Boolean} whether the widget is removable
    */
   isWidgetRemovable(aWidget) {
     let widgetId;
     let widgetNode;
     if (typeof aWidget == "string") {
       widgetId = aWidget;
     } else {
+      // Skipped items could just not have ids.
+      if (!aWidget.id && aWidget.getAttribute("skipintoolbarset") == "true") {
+        return false;
+      }
       if (!aWidget.id &&
           !["toolbarspring", "toolbarspacer", "toolbarseparator"].includes(aWidget.nodeName)) {
         throw new Error("No nodes without ids that aren't special widgets should ever come into contact with CUI");
       }
       // Use "spring" / "spacer" / "separator" for special widgets without ids
       widgetId = aWidget.id || aWidget.nodeName.substring(7 /* "toolbar".length */);
       widgetNode = aWidget;
     }
--- a/browser/components/preferences/in-content/privacy.js
+++ b/browser/components/preferences/in-content/privacy.js
@@ -1051,17 +1051,17 @@ var gPrivacyPane = {
 
     let blockDownloadsPref = document.getElementById("browser.safebrowsing.downloads.enabled");
     let malwareTable = document.getElementById("urlclassifier.malwareTable");
 
     let blockUnwantedPref = document.getElementById("browser.safebrowsing.downloads.remote.block_potentially_unwanted");
     let blockUncommonPref = document.getElementById("browser.safebrowsing.downloads.remote.block_uncommon");
 
     let learnMoreLink = document.getElementById("enableSafeBrowsingLearnMore");
-    let phishingUrl = "https://support.mozilla.org/kb/how-does-phishing-and-malware-protection-work";
+    let phishingUrl = Services.urlFormatter.formatURLPref("app.support.baseURL") + "phishing-malware";
     learnMoreLink.setAttribute("href", phishingUrl);
 
     enableSafeBrowsing.addEventListener("command", function() {
       safeBrowsingPhishingPref.value = enableSafeBrowsing.checked;
       safeBrowsingMalwarePref.value = enableSafeBrowsing.checked;
 
       if (enableSafeBrowsing.checked) {
         if (blockDownloads) {
--- a/browser/extensions/formautofill/FormAutofillHandler.jsm
+++ b/browser/extensions/formautofill/FormAutofillHandler.jsm
@@ -334,17 +334,17 @@ FormAutofillHandler.prototype = {
       }
       return _pattern.test(str);
     };
     if (element.pattern) {
       if (testPattern(profile.tel)) {
         return;
       }
     } else if (element.maxLength) {
-      if (profile.tel.length <= element.maxLength) {
+      if (detail._reason == "autocomplete" && profile.tel.length <= element.maxLength) {
         return;
       }
     }
 
     if (detail._reason != "autocomplete") {
       // Since we only target people living in US and using en-US websites in
       // MVP, it makes more sense to fill `tel-national` instead of `tel`
       // if the field is identified by heuristics and no other clues to
--- a/browser/extensions/formautofill/bootstrap.js
+++ b/browser/extensions/formautofill/bootstrap.js
@@ -13,16 +13,18 @@ const CACHED_STYLESHEETS = new WeakMap()
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "AddonManager", "resource://gre/modules/AddonManager.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "AddonManagerPrivate",
                                   "resource://gre/modules/AddonManager.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "FormAutofillParent",
                                   "resource://formautofill/FormAutofillParent.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "FormAutofillUtils",
+                                  "resource://formautofill/FormAutofillUtils.jsm");
 
 function insertStyleSheet(domWindow, url) {
   let doc = domWindow.document;
   let styleSheetAttr = `href="${url}" type="text/css"`;
   let styleSheet = doc.createProcessingInstruction("xml-stylesheet", styleSheetAttr);
 
   doc.insertBefore(styleSheet, doc.documentElement);
 
@@ -63,16 +65,17 @@ function isAvailable() {
 }
 
 function startup(data) {
   if (!isAvailable()) {
     Services.prefs.clearUserPref("dom.forms.autocomplete.formautofill");
     // reset the sync related prefs incase the feature was previously available
     // but isn't now.
     Services.prefs.clearUserPref("services.sync.engine.addresses.available");
+    Services.prefs.clearUserPref("services.sync.engine.creditcards.available");
     Services.telemetry.scalarSet("formautofill.availability", false);
     return;
   }
 
   if (data.hasOwnProperty("instanceID") && data.instanceID) {
     if (AddonManagerPrivate.isDBLoaded()) {
       addUpgradeListener(data.instanceID);
     } else {
@@ -87,20 +90,25 @@ function startup(data) {
   }
 
   // This pref is used for web contents to detect the autocomplete feature.
   // When it's true, "element.autocomplete" will return tokens we currently
   // support -- otherwise it'll return an empty string.
   Services.prefs.setBoolPref("dom.forms.autocomplete.formautofill", true);
   Services.telemetry.scalarSet("formautofill.availability", true);
 
-  // This pref determines whether the "addresses" sync engine is available
-  // (ie, whether it is shown in any UI etc) - it *does not* determine whether
-  // the engine is actually enabled or not.
+  // This pref determines whether the "addresses"/"creditcards" sync engine is
+  // available (ie, whether it is shown in any UI etc) - it *does not* determine
+  // whether the engine is actually enabled or not.
   Services.prefs.setBoolPref("services.sync.engine.addresses.available", true);
+  if (FormAutofillUtils.isAutofillCreditCardsAvailable) {
+    Services.prefs.setBoolPref("services.sync.engine.creditcards.available", true);
+  } else {
+    Services.prefs.clearUserPref("services.sync.engine.creditcards.available");
+  }
 
   // Listen for the autocomplete popup message to lazily append our stylesheet related to the popup.
   Services.mm.addMessageListener("FormAutoComplete:MaybeOpenPopup", onMaybeOpenPopup);
 
   let parent = new FormAutofillParent();
   parent.init().catch(Cu.reportError);
   Services.ppmm.loadProcessScript("data:,new " + function() {
     Components.utils.import("resource://formautofill/FormAutofillContent.jsm");
--- a/browser/extensions/formautofill/test/browser/browser_creditCard_doorhanger.js
+++ b/browser/extensions/formautofill/test/browser/browser_creditCard_doorhanger.js
@@ -15,16 +15,20 @@ add_task(async function test_submit_cred
         let number = form.querySelector("#cc-number");
         number.setUserInput("1111222233334444");
 
         // Wait 1000ms before submission to make sure the input value applied
         await new Promise(resolve => setTimeout(resolve, 1000));
         form.querySelector("input[type=submit]").click();
       });
 
+      ok(!SpecialPowers.Services.prefs.prefHasUserValue(SYNC_USERNAME_PREF),
+         "Sync account should not exist by default");
+      let cb = getDoorhangerCheckbox();
+      ok(cb.hidden, "Sync checkbox should be hidden");
       await promiseShown;
       await clickDoorhangerButton(SECONDARY_BUTTON);
     }
   );
 
   await sleep(1000);
   let creditCards = await getCreditCards();
   is(creditCards.length, 0, "No credit card saved");
@@ -158,52 +162,16 @@ add_task(async function test_submit_cred
   );
 
   await sleep(1000);
   let creditCards = await getCreditCards();
   is(creditCards.length, 2, "Still 2 credit cards in storage");
   LoginTestUtils.masterPassword.disable();
 });
 
-add_task(async function test_submit_creditCard_unavailable_with_sync_account() {
-  await SpecialPowers.pushPrefEnv({
-    "set": [
-      [SYNC_USERNAME_PREF, "foo@bar.com"],
-    ],
-  });
-
-  await BrowserTestUtils.withNewTab({gBrowser, url: CREDITCARD_FORM_URL},
-    async function(browser) {
-      let promiseShown = BrowserTestUtils.waitForEvent(PopupNotifications.panel,
-                                                       "popupshown");
-      is(SpecialPowers.getBoolPref(SYNC_CREDITCARDS_AVAILABLE_PREF), false,
-         "creditCards sync should be unavailable by default");
-      await ContentTask.spawn(browser, null, async function() {
-        let form = content.document.getElementById("form");
-        let name = form.querySelector("#cc-name");
-        name.focus();
-        name.setUserInput("User 2");
-
-        let number = form.querySelector("#cc-number");
-        number.setUserInput("1234123412341234");
-
-        // Wait 500ms before submission to make sure the input value applied
-        await new Promise(resolve => setTimeout(resolve, 500));
-        form.querySelector("input[type=submit]").click();
-      });
-
-      await promiseShown;
-      let cb = getDoorhangerCheckbox();
-      ok(cb.hidden, "Sync checkbox should be hidden");
-
-      await clickDoorhangerButton(SECONDARY_BUTTON);
-    }
-  );
-});
-
 add_task(async function test_submit_creditCard_with_sync_account() {
   await SpecialPowers.pushPrefEnv({
     "set": [
       [SYNC_USERNAME_PREF, "foo@bar.com"],
       [SYNC_CREDITCARDS_AVAILABLE_PREF, true],
     ],
   });
 
--- a/browser/extensions/formautofill/test/unit/test_getAdaptedProfiles.js
+++ b/browser/extensions/formautofill/test/unit/test_getAdaptedProfiles.js
@@ -311,33 +311,54 @@ const TESTCASES = [
       "address-line3": "line3",
       "address-level1": "CA",
       "country": "US",
       "tel": "+19876543210",
       "tel-national": "9876543210",
     }],
   },
   {
-    description: "`tel` field with `maxlength` can be filled with `tel` value.",
+    description: "autocomplete=\"tel\" field with `maxlength` can be filled with `tel` value.",
+    document: `<form>
+               <input id="telephone" autocomplete="tel" maxlength="12">
+               <input id="line1" autocomplete="address-line1">
+               <input id="line2" autocomplete="address-line2">
+               </form>`,
+    profileData: [Object.assign({}, DEFAULT_ADDRESS_RECORD)],
+    expectedResult: [{
+      "guid": "123",
+      "street-address": "2 Harrison St\nline2\nline3",
+      "-moz-street-address-one-line": "2 Harrison St line2 line3",
+      "address-line1": "2 Harrison St",
+      "address-line2": "line2 line3",
+      "address-line3": "line3",
+      "address-level1": "CA",
+      "country": "US",
+      "tel": "+19876543210",
+      "tel-national": "9876543210",
+    }],
+  },
+  {
+    description: "Still fill `tel-national` in a `tel` field with `maxlength` can be filled with `tel` value.",
     document: `<form>
                <input id="telephone" maxlength="12">
                <input id="line1" autocomplete="address-line1">
                <input id="line2" autocomplete="address-line2">
                </form>`,
     profileData: [Object.assign({}, DEFAULT_ADDRESS_RECORD)],
     expectedResult: [{
       "guid": "123",
       "street-address": "2 Harrison St\nline2\nline3",
       "-moz-street-address-one-line": "2 Harrison St line2 line3",
       "address-line1": "2 Harrison St",
       "address-line2": "line2 line3",
       "address-line3": "line3",
       "address-level1": "CA",
       "country": "US",
-      "tel": "+19876543210",
+      "tel": "9876543210",
       "tel-national": "9876543210",
     }],
   },
   {
     description: "`tel` field with `maxlength` can be filled with `tel-national` value.",
     document: `<form>
                <input id="telephone" maxlength="10">
                <input id="line1" autocomplete="address-line1">
--- a/browser/modules/ContentLinkHandler.jsm
+++ b/browser/modules/ContentLinkHandler.jsm
@@ -114,46 +114,61 @@ function setIconForLink(aIconInfo, aChro
     "Link:SetIcon",
     { url: aIconInfo.iconUri.spec,
       loadingPrincipal: aIconInfo.loadingPrincipal,
       requestContextID: aIconInfo.requestContextID,
       canUseForTab: !aIconInfo.isRichIcon,
     });
 }
 
+/**
+ * Checks whether the icon info represents an ICO image.
+ */
+function isICO(icon) {
+  return icon.type == "image/x-icon" || icon.type == "image/vnd.microsoft.icon";
+}
+
 /*
  * Timeout callback function for loading favicon.
  *
  * @param {Map} aFaviconLoads A map of page URL and FaviconLoad object pairs,
  *   where the FaviconLoad object looks like {
  *     timer: a nsITimer object,
  *     iconInfos: an array of IconInfo objects
  *   }
  * @param {String} aPageUrl A page URL string for this callback.
  * @param {Object} aChromeGlobal A global chrome object.
  */
 function faviconTimeoutCallback(aFaviconLoads, aPageUrl, aChromeGlobal) {
   let load = aFaviconLoads.get(aPageUrl);
   if (!load)
     return;
 
-  // SVG and ico are the preferred icons
-  let preferredIcon;
+  let preferredIcon = {
+    type: null
+  };
+  let preferredWidth = 16 * Math.ceil(aChromeGlobal.content.devicePixelRatio);
   // Other links with the "icon" tag are the default icons
   let defaultIcon;
   // Rich icons are either apple-touch or fluid icons, or the ones of the
   // dimension 96x96 or greater
   let largestRichIcon;
 
   for (let icon of load.iconInfos) {
-    if (icon.type === "image/svg+xml" ||
-      icon.type === "image/x-icon" ||
-      icon.type === "image/vnd.microsoft.icon") {
-      preferredIcon = icon;
-      continue;
+    if (!icon.isRichIcon) {
+      // First check for svg. If it's not available check for an icon with a
+      // size adapt to the current resolution. If both are not available, prefer
+      // ico files. When multiple icons are in the same set, the latest wins.
+      if (icon.type == "image/svg+xml") {
+        preferredIcon = icon;
+      } else if (icon.width == preferredWidth && preferredIcon.type != "image/svg+xml") {
+        preferredIcon = icon;
+      } else if (isICO(icon) && (preferredIcon.type == null || isICO(preferredIcon))) {
+        preferredIcon = icon;
+      }
     }
 
     // Note that some sites use hi-res icons without specifying them as
     // apple-touch or fluid icons.
     if (icon.isRichIcon || icon.width >= FAVICON_RICH_ICON_MIN_WIDTH) {
       if (!largestRichIcon || largestRichIcon.width < icon.width) {
         largestRichIcon = icon;
       }
@@ -165,17 +180,17 @@ function faviconTimeoutCallback(aFavicon
   // Now set the favicons for the page in the following order:
   // 1. Set the best rich icon if any.
   // 2. Set the preferred one if any, otherwise use the default one.
   // This order allows smaller icon frames to eventually override rich icon
   // frames.
   if (largestRichIcon) {
     setIconForLink(largestRichIcon, aChromeGlobal);
   }
-  if (preferredIcon) {
+  if (preferredIcon.type) {
     setIconForLink(preferredIcon, aChromeGlobal);
   } else if (defaultIcon) {
     setIconForLink(defaultIcon, aChromeGlobal);
   }
 
   load.timer = null;
   aFaviconLoads.delete(aPageUrl);
 }
--- a/browser/themes/linux/preferences/in-content/dialog.css
+++ b/browser/themes/linux/preferences/in-content/dialog.css
@@ -3,17 +3,17 @@
    - You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 %include ../../../shared/incontentprefs/dialog.inc.css
 
 label:not(.menu-text),
 textbox,
 description,
 .tab-text,
-caption > label {
+caption label {
   font-size: 1.11rem;
 }
 
 /* Create a separate rule to unset these styles on .tree-input instead of
    using :not(.tree-input) so the selector specifity doesn't change. */
 textbox.tree-input {
   font-size: unset;
 }
--- a/browser/themes/linux/preferences/in-content/preferences.css
+++ b/browser/themes/linux/preferences/in-content/preferences.css
@@ -5,17 +5,17 @@
 %include ../../../shared/incontentprefs/preferences.inc.css
 
 html *,
 page *,
 window * {
   font-size: 1.11rem;
 }
 
-caption > label:not(.dialogTitle) {
+caption label:not(.dialogTitle) {
   font-size: 1.27rem;
 }
 
 .tip-caption,
 .help-label {
   font-size: 1rem;
 }
 
--- a/browser/themes/osx/browser.css
+++ b/browser/themes/osx/browser.css
@@ -224,19 +224,18 @@
 .findbar-button {
   background: none;
   box-shadow: none;
   border: none;
 }
 
 /* On Mac, native buttons keep their full opacity when they become disabled
  * and only the glyph or text on top of them becomes less opaque. */
-#main-window:not([customizing]) #back-button[disabled="true"] > .toolbarbutton-icon {
+:root:not([customizing]) #back-button[disabled="true"] {
   opacity: 1 !important;
-  -moz-context-properties: fill, fill-opacity;
   /* Disabled toolbar buttons get an opacity of 0.4 which multiplies
    * their fill-opacity of 0.7. calc() doesn't work here - we'd need
    * to multiply two unitless numbers and that's invalid in CSS, so
    * we need to hard code the value for now. */
   fill-opacity: 0.28;
 }
 
 /* Inactive elements are faded out on OSX */
--- a/browser/themes/osx/preferences/in-content/dialog.css
+++ b/browser/themes/osx/preferences/in-content/dialog.css
@@ -8,17 +8,17 @@ prefwindow,
 .windowDialog {
   font: message-box !important;
 }
 
 label:not(.menu-text),
 textbox,
 description,
 .tab-text,
-caption > label {
+caption label {
   font-size: 1.36rem;
 }
 
 /* Create a separate rule to unset these styles on .tree-input instead of
    using :not(.tree-input) so the selector specifity doesn't change. */
 textbox.tree-input {
   font-size: unset;
 }
--- a/browser/themes/osx/preferences/in-content/preferences.css
+++ b/browser/themes/osx/preferences/in-content/preferences.css
@@ -5,17 +5,17 @@
 %include ../../../shared/incontentprefs/preferences.inc.css
 
 html *,
 page *,
 window * {
   font-size: 1.36rem;
 }
 
-caption > label:not(.dialogTitle) {
+caption label:not(.dialogTitle) {
   font-size: 1.55rem;
 }
 
 .tip-caption,
 .help-label {
   font-size: 1.18rem;
 }
 
--- a/browser/themes/shared/tabs.inc.css
+++ b/browser/themes/shared/tabs.inc.css
@@ -194,17 +194,17 @@ tabbrowser {
 }
 
 :root[sessionrestored] .tab-throbber[progress]::before {
   fill: var(--tab-loading-fill);
   opacity: 1;
 }
 
 #TabsToolbar[brighttext] .tabbrowser-tab:not([visuallyselected=true]) {
-  --tab-loading-fill: #fff;
+  --tab-loading-fill: #bbbbff;
 }
 
 #tabbrowser-tabs[schedulepressure] .tab-throbber,
 #tabbrowser-tabs:not([schedulepressure]) .tab-throbber-fallback {
   display: none;
 }
 
 .tab-icon-image {
--- a/browser/themes/windows/preferences/in-content/dialog.css
+++ b/browser/themes/windows/preferences/in-content/dialog.css
@@ -3,17 +3,17 @@
    - You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 %include ../../../shared/incontentprefs/dialog.inc.css
 
 label:not(.menu-text),
 textbox,
 description,
 .tab-text,
-caption > label {
+caption label {
   font-size: 1.25rem;
 }
 
 /* Create a separate rule to unset these styles on .tree-input instead of
    using :not(.tree-input) so the selector specifity doesn't change. */
 textbox.tree-input {
   font-size: unset;
 }
--- a/browser/themes/windows/preferences/in-content/preferences.css
+++ b/browser/themes/windows/preferences/in-content/preferences.css
@@ -5,17 +5,17 @@
 %include ../../../shared/incontentprefs/preferences.inc.css
 
 html *,
 page *,
 window * {
   font-size: 1.25rem;
 }
 
-caption > label:not(.dialogTitle) {
+caption label:not(.dialogTitle) {
   font-size: 1.42rem;
 }
 
 .tip-caption,
 .help-label {
   font-size: 1.08rem;
 }
 
--- a/browser/tools/mozscreenshots/mozscreenshots/extension/configurations/DevTools.jsm
+++ b/browser/tools/mozscreenshots/mozscreenshots/extension/configurations/DevTools.jsm
@@ -16,48 +16,52 @@ let { devtools } = Cu.import("resource:/
 let TargetFactory = devtools.TargetFactory;
 
 function getTargetForSelectedTab() {
   let browserWindow = Services.wm.getMostRecentWindow("navigator:browser");
   let target = TargetFactory.forTab(browserWindow.gBrowser.selectedTab);
   return target;
 }
 
+function selectToolbox() {
+  return gDevTools.getToolbox(getTargetForSelectedTab()).win.document.querySelector("#toolbox-container");
+}
+
 this.DevTools = {
   init(libDir) {
     let panels = ["options", "webconsole", "jsdebugger", "styleeditor",
                   "performance", "netmonitor"];
 
     panels.forEach(panel => {
       this.configurations[panel] = {};
-      this.configurations[panel].selectors = ["#toolbox-container"];
+      this.configurations[panel].selectors = [selectToolbox];
       this.configurations[panel].applyConfig = async function() {
         await gDevTools.showToolbox(getTargetForSelectedTab(), panel, "bottom");
         await new Promise(resolve => setTimeout(resolve, 500));
       };
     });
   },
 
   configurations: {
     bottomToolbox: {
-      selectors: ["#toolbox-container"],
+      selectors: [selectToolbox],
       async applyConfig() {
         await gDevTools.showToolbox(getTargetForSelectedTab(), "inspector", "bottom");
         await new Promise(resolve => setTimeout(resolve, 1000));
       },
     },
     sideToolbox: {
-      selectors: ["#toolbox-container"],
+      selectors: [selectToolbox],
       async applyConfig() {
         await gDevTools.showToolbox(getTargetForSelectedTab(), "inspector", "side");
         await new Promise(resolve => setTimeout(resolve, 500));
       },
     },
     undockedToolbox: {
-      selectors: ["#toolbox-container"],
+      selectors: [selectToolbox],
       windowType: "devtools:toolbox",
       async applyConfig() {
         await gDevTools.showToolbox(getTargetForSelectedTab(), "inspector", "window");
         await new Promise(resolve => setTimeout(resolve, 500));
       },
     }
   },
 };
new file mode 100644
--- /dev/null
+++ b/browser/tools/mozscreenshots/mozscreenshots/extension/configurations/UIDensities.jsm
@@ -0,0 +1,42 @@
+/* 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";
+
+this.EXPORTED_SYMBOLS = ["UIDensities"];
+
+const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
+
+Cu.import("resource://gre/modules/Services.jsm");
+
+this.UIDensities = {
+
+  init(libDir) {},
+
+  configurations: {
+    compactDensity: {
+      selectors: ["#navigator-toolbox, #appMenu-popup, #widget-overflow"],
+      async applyConfig() {
+        let browserWindow = Services.wm.getMostRecentWindow("navigator:browser");
+        browserWindow.gCustomizeMode.setUIDensity(browserWindow.gUIDensity.MODE_COMPACT);
+      },
+    },
+
+    normalDensity: {
+      selectors: ["#navigator-toolbox, #appMenu-popup, #widget-overflow"],
+      async applyConfig() {
+        let browserWindow = Services.wm.getMostRecentWindow("navigator:browser");
+        browserWindow.gCustomizeMode.setUIDensity(browserWindow.gUIDensity.MODE_NORMAL);
+      },
+    },
+
+    touchDensity: {
+      selectors: ["#navigator-toolbox, #appMenu-popup, #widget-overflow"],
+      async applyConfig() {
+        let browserWindow = Services.wm.getMostRecentWindow("navigator:browser");
+        browserWindow.gCustomizeMode.setUIDensity(browserWindow.gUIDensity.MODE_TOUCH);
+      },
+    },
+  },
+};
--- a/build/moz.configure/toolchain.configure
+++ b/build/moz.configure/toolchain.configure
@@ -1503,18 +1503,18 @@ def select_linker(linker, c_compiler, de
     linker = linker[0] if linker else 'other'
     if linker in ('gold', 'bfd', 'other'):
         return enable_gnu_linker(linker == 'gold', c_compiler, developer_options,
                                  build_env, toolchain_flags, linker)
     if linker == 'lld':
         version_check = ['-Wl,--version']
         cmd_base = c_compiler.wrapper + \
             [c_compiler.compiler] + c_compiler.flags
-        lld = "-fuse-ld=" + linker
-        cmd = cmd_base + [lld] + version_check
+        lld = ["-fuse-ld=" + linker]
+        cmd = cmd_base + lld + version_check
         if 'LLD' in check_cmd_output(*cmd).decode('utf-8'):
             return namespace(
                 KIND='lld',
                 LINKER_FLAG=lld,
             )
         else:
             die("Could not use lld as linker")
 
--- a/devtools/client/jar.mn
+++ b/devtools/client/jar.mn
@@ -2,17 +2,25 @@
 # 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/.
 
 devtools.jar:
 %   content devtools %content/
     content/shared/vendor/d3.js (shared/vendor/d3.js)
     content/shared/vendor/dagre-d3.js (shared/vendor/dagre-d3.js)
     content/shared/widgets/widgets.css (shared/widgets/widgets.css)
+    content/netmonitor/src/assets/styles/httpi.css (netmonitor/src/assets/styles/httpi.css)
+    content/netmonitor/src/assets/styles/MdnLink.css (netmonitor/src/assets/styles/MdnLink.css)
     content/netmonitor/src/assets/styles/netmonitor.css (netmonitor/src/assets/styles/netmonitor.css)
+    content/netmonitor/src/assets/styles/NetworkDetailsPanel.css (netmonitor/src/assets/styles/NetworkDetailsPanel.css)
+    content/netmonitor/src/assets/styles/RequestList.css (netmonitor/src/assets/styles/RequestList.css)
+    content/netmonitor/src/assets/styles/StatisticsPanel.css (netmonitor/src/assets/styles/StatisticsPanel.css)
+    content/netmonitor/src/assets/styles/StatusBar.css (netmonitor/src/assets/styles/StatusBar.css)
+    content/netmonitor/src/assets/styles/Toolbar.css (netmonitor/src/assets/styles/Toolbar.css)
+    content/netmonitor/src/assets/styles/variables.css (netmonitor/src/assets/styles/variables.css)
     content/shared/widgets/VariablesView.xul (shared/widgets/VariablesView.xul)
     content/netmonitor/index.html (netmonitor/index.html)
     content/webconsole/webconsole.html (webconsole/webconsole.html)
     content/webconsole/webconsole.xul (webconsole/webconsole.xul)
     content/scratchpad/scratchpad.xul (scratchpad/scratchpad.xul)
     content/scratchpad/scratchpad.js (scratchpad/scratchpad.js)
     content/shared/splitview.css (shared/splitview.css)
     content/shared/theme-switching.js (shared/theme-switching.js)
new file mode 100644
--- /dev/null
+++ b/devtools/client/netmonitor/src/assets/styles/MdnLink.css
@@ -0,0 +1,20 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* Learn more links */
+
+.network-monitor .learn-more-link::before {
+  background-image: url(chrome://devtools/skin/images/help.svg);
+}
+
+.network-monitor .tree-container .treeTable tr .learn-more-link {
+  position: absolute;
+  top: 0;
+  left: 0;
+  padding: 0;
+}
+
+.network-monitor .tree-container .treeTable tr:not(:hover) .learn-more-link {
+  opacity: 0.1;
+}
new file mode 100644
--- /dev/null
+++ b/devtools/client/netmonitor/src/assets/styles/NetworkDetailsPanel.css
@@ -0,0 +1,396 @@
+/* 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/. */
+
+/* Network details panel */
+
+.network-monitor .network-details-panel {
+  width: 100%;
+  height: 100%;
+  overflow: hidden;
+}
+
+.network-monitor .panel-container {
+  height: 100%;
+}
+
+.network-monitor .panel-container,
+.network-monitor .properties-view {
+  display: flex;
+  flex-direction: column;
+  overflow-x: hidden;
+  overflow-y: auto;
+}
+
+.network-monitor .panel-container .tree-container .objectBox {
+  white-space: normal;
+  word-wrap: break-word;
+}
+
+.network-monitor .properties-view {
+  flex-grow: 1;
+}
+
+.network-monitor .properties-view .searchbox-section {
+  flex: 0 1 auto;
+}
+
+.network-monitor .properties-view .devtools-searchbox {
+  padding: 0;
+}
+
+.network-monitor .properties-view .devtools-searchbox input {
+  margin: 1px 3px;
+}
+
+/* Empty notices in tab panels */
+
+.network-monitor .empty-notice {
+  color: var(--theme-body-color-inactive);
+  padding: 3px 8px;
+  text-align: center;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  height: 100%;
+  font-size: 24px;
+}
+
+/* Text inputs in tab panels */
+
+.network-monitor .textbox-input {
+  text-overflow: ellipsis;
+  border: none;
+  background: none;
+  color: inherit;
+  width: 100%;
+}
+
+.network-monitor .textbox-input:focus {
+  outline: 0;
+  box-shadow: var(--theme-focus-box-shadow-textbox);
+}
+
+/* Tree table in tab panels */
+
+.network-monitor .tree-container, .tree-container .treeTable {
+  position: relative;
+  height: 100%;
+  width: 100%;
+  overflow: auto;
+  flex: 1;
+}
+
+.network-monitor .tree-container .treeTable,
+.network-monitor .tree-container .treeTable tbody {
+  display: flex;
+  flex-direction: column;
+}
+
+.network-monitor .tree-container .treeTable tbody {
+  /* Apply flex to table will create an anonymous table element outside of tbody
+   * See also http://stackoverflow.com/a/30851678
+   * Therefore, we set height with this magic number in order to remove the
+   * redundant scrollbar when source editor appears.
+   */
+  height: calc(100% - 4px);
+}
+
+.network-monitor .tree-container .treeTable tr {
+  display: block;
+  position: relative;
+}
+
+/* Make right td fill available horizontal space */
+.network-monitor .tree-container .treeTable td:last-child {
+  width: 100%;
+}
+
+.network-monitor .properties-view .devtools-searchbox,
+.network-monitor .tree-container .treeTable .tree-section {
+  width: 100%;
+  background-color: var(--theme-toolbar-background);
+}
+
+.network-monitor .tree-container .treeTable tr.tree-section:not(:first-child) td:not([class=""]) {
+  border-top: 1px solid var(--theme-splitter-color);
+}
+
+.network-monitor .properties-view .devtools-searchbox,
+.network-monitor .tree-container .treeTable tr.tree-section:not(:last-child) td:not([class=""]) {
+  border-bottom: 1px solid var(--theme-splitter-color);
+}
+
+.network-monitor .tree-container .treeTable .tree-section > * {
+  vertical-align: middle;
+}
+
+.network-monitor .tree-container .treeTable .treeRow.tree-section > .treeLabelCell > .treeLabel,
+.network-monitor .tree-container .treeTable .treeRow.tree-section > .treeLabelCell > .treeLabel:hover,
+.network-monitor .tree-container .treeTable .treeRow.tree-section > .treeValueCell:not(:hover) * {
+  color: var(--theme-toolbar-color);
+}
+
+.network-monitor .tree-container .treeTable .treeValueCell {
+  /* FIXME: Make value cell can be reduced to shorter width */
+  max-width: 0;
+  padding-inline-end: 5px;
+}
+
+/* Source editor in tab panels */
+
+/* If there is a source editor shows up in the last row of TreeView,
+ * it should occupy the available vertical space.
+ */
+.network-monitor .tree-container .treeTable .editor-row-container,
+.network-monitor .tree-container .treeTable tr:last-child td[colspan="2"] {
+  display: block;
+  height: 100%;
+}
+
+.network-monitor .source-editor-mount {
+  width: 100%;
+  height: 100%;
+}
+
+.network-monitor .headers-summary-label,
+.network-monitor .tree-container .objectBox {
+  white-space: nowrap;
+}
+
+/* Summary tabpanel */
+
+.network-monitor .tabpanel-summary-container {
+  padding: 1px;
+}
+
+.network-monitor .tabpanel-summary-label {
+  display: inline-block;
+  padding-inline-start: 4px;
+  padding-inline-end: 3px;
+  font-weight: 600;
+}
+
+.network-monitor .tabpanel-summary-value {
+  color: inherit;
+  padding-inline-start: 3px;
+}
+
+.theme-dark .network-monitor .tabpanel-summary-value {
+  color: var(--theme-selection-color);
+}
+
+/* Headers tabpanel */
+
+.network-monitor .headers-overview {
+  background: var(--theme-toolbar-background);
+}
+
+.network-monitor .headers-summary,
+.network-monitor .response-summary {
+  display: flex;
+  align-items: center;
+}
+
+.network-monitor .headers-summary .devtools-button {
+  margin-inline-end: 6px;
+}
+
+.network-monitor .headers-summary .requests-list-status-icon {
+  min-width: 10px;
+}
+
+.network-monitor .headers-summary .raw-headers-container {
+  display: flex;
+  width: 100%;
+}
+
+.network-monitor .headers-summary .raw-headers {
+  width: 50%;
+  padding: 0 4px;
+}
+
+.network-monitor .headers-summary .raw-headers textarea {
+  width: 100%;
+  height: 50vh;
+  font: message-box;
+  resize: none;
+}
+
+.network-monitor .headers-summary .raw-headers .tabpanel-summary-label {
+  padding: 0 0 4px 0;
+}
+
+.headers-summary .textbox-input {
+  margin-inline-end: 2px;
+}
+
+.network-monitor .headers-summary .status-text {
+    width: auto!important;
+}
+
+/* Response tabpanel */
+
+.network-monitor .response-error-header {
+  margin: 0;
+  padding: 3px 8px;
+  background-color: var(--theme-highlight-red);
+  color: var(--theme-selection-color);
+}
+
+.network-monitor .response-image-box {
+  display: flex;
+  flex-direction: column;
+  justify-content: center;
+  align-items: center;
+  overflow-y: auto;
+  padding: 10px;
+}
+
+.network-monitor .response-image {
+  background: #fff;
+  border: 1px dashed GrayText;
+  margin-bottom: 10px;
+  max-width: 300px;
+  max-height: 100px;
+}
+
+/* Timings tabpanel */
+
+.network-monitor .timings-container {
+  display: flex;
+}
+
+.network-monitor .timings-label {
+  width: 10em;
+}
+
+.network-monitor .requests-list-timings-container {
+  display: flex;
+  flex: 1;
+  align-items: center;
+}
+
+.network-monitor .requests-list-timings-offset {
+  transition: width 0.2s ease-out;
+}
+
+.network-monitor .requests-list-timings-box {
+  border: none;
+  min-width: 1px;
+  transition: width 0.2s ease-out;
+}
+
+.theme-firebug .network-monitor .requests-list-timings-total {
+  color: var(--theme-body-color);
+}
+
+/* Stack trace panel */
+
+.network-monitor .stack-trace {
+  font-family: var(--monospace-font-family);
+  /* The markup contains extra whitespace to improve formatting of clipboard text.
+     Make sure this whitespace doesn't affect the HTML rendering */
+  white-space: normal;
+  padding: 5px;
+}
+
+.network-monitor .stack-trace .frame-link-source,
+.network-monitor .message-location .frame-link-source {
+  /* Makes the file name truncated (and ellipsis shown) on the left side */
+  direction: rtl;
+  white-space: nowrap;
+  overflow: hidden;
+  text-overflow: ellipsis;
+}
+
+.network-monitor .stack-trace .frame-link-source-inner,
+.network-monitor .message-location .frame-link-source-inner {
+  /* Enforce LTR direction for the file name - fixes bug 1290056 */
+  direction: ltr;
+  unicode-bidi: embed;
+}
+
+.network-monitor .stack-trace .frame-link-function-display-name {
+  white-space: nowrap;
+  overflow: hidden;
+  text-overflow: ellipsis;
+  margin-inline-end: 1ch;
+}
+
+/* Security tabpanel */
+
+/* Overwrite tree-view cell colon `:` for security panel and tree section */
+.network-monitor .security-panel .treeTable .treeLabelCell::after,
+.network-monitor .treeTable .tree-section .treeLabelCell::after {
+  content: "";
+}
+
+/* Layout additional warning icon in tree value cell  */
+.network-monitor .security-info-value {
+  display: flex;
+}
+
+.network-monitor .security-warning-icon {
+  background-image: url(chrome://devtools/skin/images/alerticon-warning.png);
+  background-size: 13px 12px;
+  margin-inline-start: 5px;
+  vertical-align: top;
+  width: 13px;
+  height: 12px;
+}
+
+@media (min-resolution: 1.1dppx) {
+  .network-monitor .security-warning-icon {
+    background-image: url(chrome://devtools/skin/images/alerticon-warning@2x.png);
+  }
+}
+
+/* Custom request panel */
+
+.network-monitor .custom-request-panel {
+  height: 100%;
+  overflow: auto;
+  padding: 0 4px;
+  background-color: var(--theme-sidebar-background);
+}
+
+.theme-dark .network-monitor .custom-request-panel {
+  color: var(--theme-selection-color);
+}
+
+.network-monitor .custom-request-label {
+  font-weight: 600;
+}
+
+.network-monitor .custom-request-panel textarea {
+  resize: none;
+  font: message-box;
+}
+
+.network-monitor .custom-header,
+.network-monitor .custom-method-and-url,
+.network-monitor .custom-request,
+.network-monitor .custom-section {
+  display: flex;
+}
+
+.network-monitor .custom-header {
+  flex-grow: 1;
+  font-size: 1.1em;
+  padding-top: 4px;
+}
+
+.network-monitor .custom-section {
+  flex-direction: column;
+  margin-top: 0.5em;
+}
+
+.network-monitor .custom-method-value {
+  width: 4.5em;
+}
+
+.network-monitor .custom-url-value {
+  flex-grow: 1;
+  margin-inline-start: 6px;
+}
new file mode 100644
--- /dev/null
+++ b/devtools/client/netmonitor/src/assets/styles/RequestList.css
@@ -0,0 +1,651 @@
+/* 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/. */
+
+/* Request list empty panel */
+
+.request-list-empty-notice {
+  margin: 0;
+  flex: 1;
+  overflow: auto;
+}
+
+.empty-notice-element {
+  padding-top: 12px;
+  padding-left: 12px;
+  padding-right: 12px;
+  font-size: 1.2rem;
+}
+
+.notice-perf-message {
+  margin-top: 2px;
+  display: flex;
+  align-items: center;
+}
+
+.requests-list-perf-notice-button {
+  min-width: 30px;
+  min-height: 26px;
+  margin: 0 5px;
+  vertical-align: middle;
+}
+
+.requests-list-perf-notice-button::before {
+  background-image: url(chrome://devtools/skin/images/profiler-stopwatch.svg);
+}
+
+.requests-list-reload-notice-button {
+  font-size: inherit;
+  min-height: 26px;
+  margin: 0 5px;
+}
+
+/* Requests list table */
+
+.request-list-container {
+  display: flex;
+  flex-direction: column;
+  width: 100%;
+  height: 100%;
+  overflow: hidden;
+}
+
+.requests-list-wrapper {
+  width: 100%;
+  height: 100%;
+}
+
+.requests-list-table {
+  display: table;
+  position: relative;
+  width: 100%;
+  height: 100%;
+}
+
+.requests-list-contents {
+  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-column {
+  display: table-cell;
+  cursor: default;
+  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;
+}
+
+.theme-firebug .requests-list-column {
+  padding: 1px;
+}
+
+/* Requests list headers */
+
+.requests-list-headers-wrapper {
+  position: sticky;
+  top: 0;
+  z-index: 1000;
+  width: 100%;
+  padding: 0;
+}
+
+.requests-list-headers {
+  display: table-header-group;
+  height: 24px;
+  padding: 0;
+  width: 100%;
+}
+
+.requests-list-headers .requests-list-column:first-child .requests-list-header-button {
+  border-width: 0;
+}
+
+.requests-list-header-button {
+  background-color: transparent;
+  border-image: linear-gradient(transparent 15%,
+                                var(--theme-splitter-color) 15%,
+                                var(--theme-splitter-color) 85%,
+                                transparent 85%) 1 1;
+  border-width: 0;
+  border-inline-start-width: 1px;
+  padding-inline-start: 16px;
+  width: 100%;
+  min-height: 23px;
+  text-align: center;
+  color: inherit;
+}
+
+.requests-list-header-button::-moz-focus-inner {
+  border: 0;
+  padding: 0;
+}
+
+.requests-list-header-button:hover {
+  background-color: rgba(0, 0, 0, 0.1);
+}
+
+.requests-list-header-button > .button-text {
+  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 {
+  display: inline-block;
+  width: 7px;
+  height: 4px;
+  margin-inline-start: 3px;
+  margin-inline-end: 6px;
+  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-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-column[data-active] + .requests-list-column .requests-list-header-button {
+  border-image: linear-gradient(var(--theme-splitter-color), var(--theme-splitter-color)) 1 1;
+}
+
+.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 {
+  min-height: 17px;
+}
+
+.theme-firebug .requests-list-header-button > .button-icon {
+  height: 7px;
+}
+
+.theme-firebug .requests-list-header-button[data-sorted] {
+  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-button:hover:active {
+  background-image: linear-gradient(rgba(0, 0, 0, 0.1),
+                                    transparent);
+}
+
+/* Requests list column */
+
+/* Status column */
+
+.requests-list-status {
+  width: 8%;
+   /* Don't ellipsize status codes */
+  text-overflow: initial;
+}
+
+.theme-firebug .requests-list-status {
+  font-weight: bold;
+}
+
+.requests-list-status-code {
+  margin-inline-start: 3px;
+  width: 3em;
+}
+
+.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;
+}
+
+.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);
+}
+
+.requests-list-status-icon[data-code="cached"] {
+  border: 2px solid var(--theme-content-color2);
+  background-color: transparent;
+}
+
+.requests-list-status-icon[data-code^="1"] {
+  background-color: var(--theme-highlight-blue);
+}
+
+.requests-list-status-icon[data-code^="2"] {
+  background-color: var(--theme-highlight-green);
+}
+
+/* 3xx are triangles */
+.requests-list-status-icon[data-code^="3"] {
+  background-color: transparent;
+  width: 0;
+  height: 0;
+  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);
+  border-radius: 0; /* squares */
+}
+
+.requests-list-status-icon[data-code^="5"] {
+  background-color: var(--theme-highlight-pink);
+  border-radius: 0;
+  transform: rotate(45deg);
+}
+
+/* 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%;
+}
+
+/* Cookies column */
+
+.requests-list-cookies {
+  width: 6%;
+}
+
+/* Set Cookies column */
+
+.requests-list-set-cookies {
+  width: 8%;
+}
+
+/* Scheme column */
+
+.requests-list-scheme {
+  width: 8%;
+}
+
+/* Domain column */
+
+.requests-list-domain {
+  width: 13%;
+}
+
+/* Start Time column */
+
+.requests-list-start-time {
+  width: 8%;
+}
+
+/* End Time column */
+
+.requests-list-end-time {
+  width: 8%;
+}
+
+/* Response Time column */
+
+.requests-list-response-time {
+  width: 10%;
+}
+
+/* Duration column */
+
+.requests-list-duration {
+  width: 8%;
+}
+
+/* Latency column */
+
+.requests-list-latency {
+  width: 8%;
+}
+
+.requests-list-response-header {
+  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 {
+  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 {
+  display: inline-block;
+  padding-inline-start: 4px;
+  font-size: 75%;
+  pointer-events: none;
+  text-align: start;
+}
+
+: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;
+}
+
+.requests-list-timings-division:dir(rtl) {
+  transform-origin: right center;
+}
+
+.theme-dark .requests-list-timings-division {
+  border-inline-start-color: #5a6169 !important;
+}
+
+.theme-light .requests-list-timings-division {
+  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;
+}
+
+.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-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;
+}
+
+.requests-list-timings-box.blocked {
+  background-color: var(--timing-blocked-color);
+}
+
+.requests-list-timings-box.dns {
+  background-color: var(--timing-dns-color);
+}
+
+.requests-list-timings-box.connect {
+  background-color: var(--timing-connect-color);
+}
+
+.requests-list-timings-box.ssl {
+  background-color: var(--timing-ssl-color);
+}
+
+.requests-list-timings-box.send {
+  background-color: var(--timing-send-color);
+}
+
+.requests-list-timings-box.wait {
+  background-color: var(--timing-wait-color);
+}
+
+.requests-list-timings-box.receive {
+  background-color: var(--timing-receive-color);
+}
+
+.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 {
+  position: relative;
+  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-hover);
+}
+
+.request-list-item.fromCache > .requests-list-column:not(.requests-list-waterfall) {
+  opacity: 0.6;
+}
+
+.theme-firebug .request-list-item:not(.selected):hover {
+  background: #EFEFEF;
+}
+
+/* Responsive web design support */
+
+@media (max-width: 700px) {
+  .requests-list-header-button {
+    padding-inline-start: 8px;
+  }
+
+  .requests-list-status-code {
+    width: auto;
+  }
+
+  .requests-list-size {
+    /* Given a fix max-width to display all columns in RWD mode */
+    max-width: 7%;
+  }
+
+  .requests-list-waterfall {
+    display: none;
+  }
+
+  :root[platform="linux"] .requests-list-header-button {
+    font-size: 85%;
+  }
+}
new file mode 100644
--- /dev/null
+++ b/devtools/client/netmonitor/src/assets/styles/StatisticsPanel.css
@@ -0,0 +1,197 @@
+/* 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/. */
+
+/* Statistics panel */
+
+.statistics-panel {
+  display: flex;
+  height: 100vh;
+}
+
+.statistics-panel .devtools-toolbarbutton.back-button {
+  min-width: 4em;
+  margin: 0;
+  padding: 0;
+  border-radius: 0;
+  border-top: none;
+  border-bottom: none;
+  border-inline-start: none;
+}
+
+.statistics-panel .splitter {
+  border-color: rgba(0,0,0,0.2);
+  cursor: default;
+  pointer-events: none;
+  height: 100%;
+}
+
+.statistics-panel .charts-container {
+  display: flex;
+  width: 100%;
+}
+
+.statistics-panel .charts,
+.statistics-panel .pie-table-chart-container {
+  width: 100%;
+  height: 100%;
+}
+
+.statistics-panel .learn-more-link {
+  font-weight: 400;
+}
+
+.statistics-panel .table-chart-title {
+  display: flex;
+}
+
+.pie-table-chart-container {
+  display: flex;
+  justify-content: center;
+  align-items: center;
+}
+
+.statistics-panel .pie-chart-container {
+  margin-inline-start: 3vw;
+  margin-inline-end: 1vw;
+}
+
+.statistics-panel .table-chart-container {
+  display: flex;
+  flex-direction: column;
+  flex: 1 1 auto;
+  min-width: 240px;
+  margin-inline-start: 1vw;
+  margin-inline-end: 3vw;
+}
+
+.chart-colored-blob[name=html] {
+  fill: var(--theme-highlight-bluegrey);
+  background: var(--theme-highlight-bluegrey);
+}
+
+.chart-colored-blob[name=css] {
+  fill: var(--theme-highlight-blue);
+  background: var(--theme-highlight-blue);
+}
+
+.chart-colored-blob[name=js] {
+  fill: var(--theme-highlight-lightorange);
+  background: var(--theme-highlight-lightorange);
+}
+
+.chart-colored-blob[name=xhr] {
+  fill: var(--theme-highlight-orange);
+  background: var(--theme-highlight-orange);
+}
+
+.chart-colored-blob[name=fonts] {
+  fill: var(--theme-highlight-purple);
+  background: var(--theme-highlight-purple);
+}
+
+.chart-colored-blob[name=images] {
+  fill: var(--theme-highlight-pink);
+  background: var(--theme-highlight-pink);
+}
+
+.chart-colored-blob[name=media] {
+  fill: var(--theme-highlight-green);
+  background: var(--theme-highlight-green);
+}
+
+.chart-colored-blob[name=flash] {
+  fill: var(--theme-highlight-red);
+  background: var(--theme-highlight-red);
+}
+
+.table-chart-row {
+  display: flex;
+}
+
+.table-chart-row-label[name=cached] {
+  display: none;
+}
+
+.table-chart-row-label[name=count] {
+  width: 3em;
+  text-align: end;
+}
+
+.table-chart-row-label[name=label] {
+  width: 7em;
+  text-align: start;
+}
+
+.table-chart-row-label[name=size] {
+  width: 7em;
+  text-align: start;
+}
+
+.table-chart-row-label[name=time] {
+  width: 7em;
+  text-align: start;
+}
+
+.table-chart-totals {
+  display: flex;
+  flex-direction: column;
+}
+
+/* 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 */
+  background: rgba(70, 175, 227, 0.8);
+}
+
+.theme-firebug .chart-colored-blob[name=js] {
+  fill: rgba(235, 83, 104, 0.8); /* red */
+  background: rgba(235, 83, 104, 0.8);
+}
+
+.theme-firebug .chart-colored-blob[name=xhr] {
+  fill: rgba(217, 102, 41, 0.8); /* orange  */
+  background: rgba(217, 102, 41, 0.8);
+}
+
+.theme-firebug .chart-colored-blob[name=fonts] {
+  fill: rgba(223, 128, 255, 0.8); /* pink */
+  background: rgba(223, 128, 255, 0.8);
+}
+
+.theme-firebug .chart-colored-blob[name=images] {
+  fill: rgba(112, 191, 83, 0.8); /* pink */
+  background: rgba(112, 191, 83, 0.8);
+}
+
+.theme-firebug .chart-colored-blob[name=media] {
+  fill: rgba(235, 235, 84, 0.8); /* yellow */
+  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 web design support */
+
+@media (max-width: 700px) {
+  .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;
+  }
+}
+
new file mode 100644
--- /dev/null
+++ b/devtools/client/netmonitor/src/assets/styles/StatusBar.css
@@ -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/. */
+
+/* Status bar */
+
+.devtools-status-bar-label {
+  flex: 0;
+}
+
+.status-bar-label {
+  display: inline-flex;
+  margin-inline-end: 10px;
+  /* Status bar has just one line so, don't wrap labels */
+  white-space: nowrap;
+}
+
+.status-bar-label:not(:first-of-type)::before {
+  content: "";
+  display: inline-block;
+  margin-inline-end: 10px;
+  margin-top: 4px;
+  margin-bottom: 4px;
+  width: 1px;
+  background: var(--theme-splitter-color);
+}
+
+.status-bar-label.dom-content-loaded {
+  color: var(--theme-highlight-blue);
+}
+
+.status-bar-label.load {
+  color: var(--theme-highlight-red);
+}
+
+.requests-list-network-summary-button {
+  display: inline-flex;
+  cursor: pointer;
+  height: 18px;
+  background: none;
+  box-shadow: none;
+  border-color: transparent;
+  padding-inline-end: 0;
+  margin-top: 3px;
+  margin-bottom: 3px;
+  margin-inline-end: 1em;
+}
+
+.requests-list-network-summary-button > .summary-info-icon {
+  background: url(chrome://devtools/skin/images/profiler-stopwatch.svg) no-repeat;
+  -moz-context-properties: fill;
+  fill: var(--theme-toolbar-color);
+  width: 16px;
+  height: 16px;
+  opacity: 0.8;
+}
+
+.requests-list-network-summary-button:hover > .summary-info-icon {
+  opacity: 1;
+}
new file mode 100644
--- /dev/null
+++ b/devtools/client/netmonitor/src/assets/styles/Toolbar.css
@@ -0,0 +1,71 @@
+/* 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/. */
+
+/* Toolbar */
+
+.devtools-toolbar {
+  display: flex;
+}
+
+.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;
+}
+
+.requests-list-filter-buttons {
+  display: flex;
+  flex-wrap: wrap;
+}
+
+.devtools-button.devtools-pause-icon::before {
+  background-image: var(--pause-icon-url);
+}
+
+.devtools-button.devtools-play-icon::before {
+  background-image: var(--play-icon-url);
+}
+
+.devtools-checkbox {
+  position: relative;
+  vertical-align: middle;
+  bottom: 1px;
+}
+
+.devtools-checkbox-label {
+  margin-inline-start: 10px;
+  margin-inline-end: 3px;
+  white-space: nowrap;
+}
+
+/* Network details panel toggle */
+
+.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);
+}
+
+/* Responsive web design support */
+
+@media (max-width: 700px) {
+  .network-details-panel-toggle:dir(ltr)::before {
+    transform: rotate(90deg);
+  }
+
+  .network-details-panel-toggle:dir(rtl)::before {
+    transform: rotate(-90deg);
+  }
+}
new file mode 100644
--- /dev/null
+++ b/devtools/client/netmonitor/src/assets/styles/httpi.css
@@ -0,0 +1,19 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+@import "chrome://devtools/skin/widgets.css";
+@import "resource://devtools/client/themes/light-theme.css";
+@import "resource://devtools/client/shared/components/splitter/SplitBox.css";
+@import "resource://devtools/client/shared/components/tree/TreeView.css";
+@import "resource://devtools/client/shared/components/tabs/Tabs.css";
+@import "resource://devtools/client/shared/components/tabs/TabBar.css";
+@import "chrome://devtools/skin/components-frame.css";
+@import "chrome://devtools/content/sourceeditor/codemirror/lib/codemirror.css";
+@import "chrome://devtools/content/sourceeditor/codemirror/addon/dialog/dialog.css";
+@import "chrome://devtools/content/sourceeditor/codemirror/mozilla.css";
+
+/* Network panel components & styles */
+@import "chrome://devtools/content/netmonitor/src/assets/styles/variables.css";
+@import "chrome://devtools/content/netmonitor/src/assets/styles/MdnLink.css";
+@import "chrome://devtools/content/netmonitor/src/assets/styles/NetworkDetailsPanel.css";
--- a/devtools/client/netmonitor/src/assets/styles/netmonitor.css
+++ b/devtools/client/netmonitor/src/assets/styles/netmonitor.css
@@ -8,58 +8,24 @@
 @import "resource://devtools/client/shared/components/tree/TreeView.css";
 @import "resource://devtools/client/shared/components/tabs/Tabs.css";
 @import "resource://devtools/client/shared/components/tabs/TabBar.css";
 @import "chrome://devtools/skin/components-frame.css";
 @import "chrome://devtools/content/sourceeditor/codemirror/lib/codemirror.css";
 @import "chrome://devtools/content/sourceeditor/codemirror/addon/dialog/dialog.css";
 @import "chrome://devtools/content/sourceeditor/codemirror/mozilla.css";
 
-:root.theme-dark {
-  --table-splitter-color: rgba(255,255,255,0.15);
-  --table-zebra-background: rgba(255,255,255,0.05);
-
-  --timing-blocked-color: rgba(235, 83, 104, 0.8);
-  --timing-dns-color: rgba(223, 128, 255, 0.8); /* pink */
-  --timing-ssl-color: rgba(217, 102, 41, 0.8); /* orange */
-  --timing-connect-color: rgba(217, 102, 41, 0.8); /* orange */
-  --timing-send-color: rgba(70, 175, 227, 0.8); /* light blue */
-  --timing-wait-color: rgba(94, 136, 176, 0.8); /* blue grey */
-  --timing-receive-color: rgba(112, 191, 83, 0.8); /* green */
-
-  --sort-ascending-image: url(chrome://devtools/skin/images/sort-ascending-arrow.svg);
-  --sort-descending-image: url(chrome://devtools/skin/images/sort-descending-arrow.svg);
-}
-
-:root.theme-light {
-  --table-splitter-color: rgba(0,0,0,0.15);
-  --table-zebra-background: rgba(0,0,0,0.05);
-
-  --timing-blocked-color: rgba(235, 83, 104, 0.8);
-  --timing-dns-color: rgba(223, 128, 255, 0.8); /* pink */
-  --timing-ssl-color: rgba(217, 102, 41, 0.8); /* orange */
-  --timing-connect-color: rgba(217, 102, 41, 0.8); /* orange */
-  --timing-send-color: rgba(0, 136, 204, 0.8); /* blue */
-  --timing-wait-color: rgba(95, 136, 176, 0.8); /* blue grey */
-  --timing-receive-color: rgba(44, 187, 15, 0.8); /* green */
-
-  --sort-ascending-image: url(chrome://devtools/skin/images/sort-ascending-arrow.svg);
-  --sort-descending-image: url(chrome://devtools/skin/images/sort-descending-arrow.svg);
-}
-
-: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);
-}
-
-/* Icons */
-:root {
-  --play-icon-url: url("chrome://devtools/skin/images/play.svg");
-  --pause-icon-url: url("chrome://devtools/skin/images/pause.svg");
-}
+/* Network panel components & styles */
+@import "chrome://devtools/content/netmonitor/src/assets/styles/variables.css";
+@import "chrome://devtools/content/netmonitor/src/assets/styles/MdnLink.css";
+@import "chrome://devtools/content/netmonitor/src/assets/styles/Toolbar.css";
+@import "chrome://devtools/content/netmonitor/src/assets/styles/StatusBar.css";
+@import "chrome://devtools/content/netmonitor/src/assets/styles/RequestList.css";
+@import "chrome://devtools/content/netmonitor/src/assets/styles/NetworkDetailsPanel.css";
+@import "chrome://devtools/content/netmonitor/src/assets/styles/StatisticsPanel.css";
 
 /* General */
 
 * {
   box-sizing: border-box;
 }
 
 html,
@@ -73,1379 +39,8 @@ body,
   flex-direction: column;
   height: 100%;
   overflow: hidden;
 }
 
 .split-box {
   overflow: hidden;
 }
-
-/* Toolbar */
-
-.devtools-toolbar {
-  display: flex;
-}
-
-.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;
-}
-
-.requests-list-filter-buttons {
-  display: flex;
-  flex-wrap: wrap;
-}
-
-.devtools-button.devtools-pause-icon::before {
-  background-image: var(--pause-icon-url);
-}
-
-.devtools-button.devtools-play-icon::before {
-  background-image: var(--play-icon-url);
-}
-
-/* Learn more links */
-
-.learn-more-link::before {
-  background-image: url(chrome://devtools/skin/images/help.svg);
-}
-
-.tree-container .treeTable tr .learn-more-link {
-  position: absolute;
-  top: 0;
-  left: 0;
-  padding: 0;
-}
-
-.tree-container .treeTable tr:not(:hover) .learn-more-link {
-  opacity: 0.1;
-}
-
-/* Status bar */
-
-.devtools-status-bar-label {
-  flex: 0;
-}
-
-.status-bar-label {
-  display: inline-flex;
-  margin-inline-end: 10px;
-  /* Status bar has just one line so, don't wrap labels */
-  white-space: nowrap;
-}
-
-.status-bar-label:not(:first-of-type)::before {
-  content: "";
-  display: inline-block;
-  margin-inline-end: 10px;
-  margin-top: 4px;
-  margin-bottom: 4px;
-  width: 1px;
-  background: var(--theme-splitter-color);
-}
-
-.status-bar-label.dom-content-loaded {
-  color: var(--theme-highlight-blue);
-}
-
-.status-bar-label.load {
-  color: var(--theme-highlight-red);
-}
-
-/* Request list empty panel */
-
-.request-list-empty-notice {
-  margin: 0;
-  flex: 1;
-  overflow: auto;
-}
-
-.empty-notice-element {
-  padding-top: 12px;
-  padding-left: 12px;
-  padding-right: 12px;
-  font-size: 1.2rem;
-}
-
-.notice-perf-message {
-  margin-top: 2px;
-  display: flex;
-  align-items: center;
-}
-
-.requests-list-perf-notice-button {
-  min-width: 30px;
-  min-height: 26px;
-  margin: 0 5px;
-  vertical-align: middle;
-}
-
-.requests-list-perf-notice-button::before {
-  background-image: url(chrome://devtools/skin/images/profiler-stopwatch.svg);
-}
-
-.requests-list-reload-notice-button {
-  font-size: inherit;
-  min-height: 26px;
-  margin: 0 5px;
-}
-
-/* Requests list table */
-
-.request-list-container {
-  display: flex;
-  flex-direction: column;
-  width: 100%;
-  height: 100%;
-  overflow: hidden;
-}
-
-.requests-list-wrapper {
-  width: 100%;
-  height: 100%;
-}
-
-.requests-list-table {
-  display: table;
-  position: relative;
-  width: 100%;
-  height: 100%;
-}
-
-.requests-list-contents {
-  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-column {
-  display: table-cell;
-  cursor: default;
-  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;
-}
-
-.theme-firebug .requests-list-column {
-  padding: 1px;
-}
-
-/* Requests list headers */
-
-.requests-list-headers-wrapper {
-  position: sticky;
-  top: 0;
-  z-index: 1000;
-  width: 100%;
-  padding: 0;
-}
-
-.requests-list-headers {
-  display: table-header-group;
-  height: 24px;
-  padding: 0;
-  width: 100%;
-}
-
-.requests-list-headers .requests-list-column:first-child .requests-list-header-button {
-  border-width: 0;
-}
-
-.requests-list-header-button {
-  background-color: transparent;
-  border-image: linear-gradient(transparent 15%,
-                                var(--theme-splitter-color) 15%,
-                                var(--theme-splitter-color) 85%,
-                                transparent 85%) 1 1;
-  border-width: 0;
-  border-inline-start-width: 1px;
-  padding-inline-start: 16px;
-  width: 100%;
-  min-height: 23px;
-  text-align: center;
-  color: inherit;
-}
-
-.requests-list-header-button::-moz-focus-inner {
-  border: 0;
-  padding: 0;
-}
-
-.requests-list-header-button:hover {
-  background-color: rgba(0, 0, 0, 0.1);
-}
-
-.requests-list-header-button > .button-text {
-  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 {
-  display: inline-block;
-  width: 7px;
-  height: 4px;
-  margin-inline-start: 3px;
-  margin-inline-end: 6px;
-  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-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-column[data-active] + .requests-list-column .requests-list-header-button {
-  border-image: linear-gradient(var(--theme-splitter-color), var(--theme-splitter-color)) 1 1;
-}
-
-.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 {
-  min-height: 17px;
-}
-
-.theme-firebug .requests-list-header-button > .button-icon {
-  height: 7px;
-}
-
-.theme-firebug .requests-list-header-button[data-sorted] {
-  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-button:hover:active {
-  background-image: linear-gradient(rgba(0, 0, 0, 0.1),
-                                    transparent);
-}
-
-/* Requests list column */
-
-/* Status column */
-
-.requests-list-status {
-  width: 8%;
-   /* Don't ellipsize status codes */
-  text-overflow: initial;
-}
-
-.theme-firebug .requests-list-status {
-  font-weight: bold;
-}
-
-.requests-list-status-code {
-  margin-inline-start: 3px;
-  width: 3em;
-}
-
-.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;
-}
-
-.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);
-}
-
-.requests-list-status-icon[data-code="cached"] {
-  border: 2px solid var(--theme-content-color2);
-  background-color: transparent;
-}
-
-.requests-list-status-icon[data-code^="1"] {
-  background-color: var(--theme-highlight-blue);
-}
-
-.requests-list-status-icon[data-code^="2"] {
-  background-color: var(--theme-highlight-green);
-}
-
-/* 3xx are triangles */
-.requests-list-status-icon[data-code^="3"] {
-  background-color: transparent;
-  width: 0;
-  height: 0;
-  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);
-  border-radius: 0; /* squares */
-}
-
-.requests-list-status-icon[data-code^="5"] {
-  background-color: var(--theme-highlight-pink);
-  border-radius: 0;
-  transform: rotate(45deg);
-}
-
-/* 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%;
-}
-
-/* Cookies column */
-
-.requests-list-cookies {
-  width: 6%;
-}
-
-/* Set Cookies column */
-
-.requests-list-set-cookies {
-  width: 8%;
-}
-
-/* Scheme column */
-
-.requests-list-scheme {
-  width: 8%;
-}
-
-/* Domain column */
-
-.requests-list-domain {
-  width: 13%;
-}
-
-/* Start Time column */
-
-.requests-list-start-time {
-  width: 8%;
-}
-
-/* End Time column */
-
-.requests-list-end-time {
-  width: 8%;
-}
-
-/* Response Time column */
-
-.requests-list-response-time {
-  width: 10%;
-}
-
-/* Duration column */
-
-.requests-list-duration {
-  width: 8%;
-}
-
-/* Latency column */
-
-.requests-list-latency {
-  width: 8%;
-}
-
-.requests-list-response-header {
-  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 {
-  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 {
-  display: inline-block;
-  padding-inline-start: 4px;
-  font-size: 75%;
-  pointer-events: none;
-  text-align: start;
-}
-
-: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;
-}
-
-.requests-list-timings-division:dir(rtl) {
-  transform-origin: right center;
-}
-
-.theme-dark .requests-list-timings-division {
-  border-inline-start-color: #5a6169 !important;
-}
-
-.theme-light .requests-list-timings-division {
-  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;
-}
-
-.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-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;
-}
-
-.requests-list-timings-box.blocked {
-  background-color: var(--timing-blocked-color);
-}
-
-.requests-list-timings-box.dns {
-  background-color: var(--timing-dns-color);
-}
-
-.requests-list-timings-box.connect {
-  background-color: var(--timing-connect-color);
-}
-
-.requests-list-timings-box.ssl {
-  background-color: var(--timing-ssl-color);
-}
-
-.requests-list-timings-box.send {
-  background-color: var(--timing-send-color);
-}
-
-.requests-list-timings-box.wait {
-  background-color: var(--timing-wait-color);
-}
-
-.requests-list-timings-box.receive {
-  background-color: var(--timing-receive-color);
-}
-
-.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 {
-  position: relative;
-  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-hover);
-}
-
-.request-list-item.fromCache > .requests-list-column:not(.requests-list-waterfall) {
-  opacity: 0.6;
-}
-
-.theme-firebug .request-list-item:not(.selected):hover {
-  background: #EFEFEF;
-}
-
-/* Network details panel toggle */
-
-.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 details panel */
-
-.network-details-panel {
-  width: 100%;
-  height: 100%;
-  overflow: hidden;
-}
-
-.panel-container {
-  height: 100%;
-}
-
-.panel-container,
-.properties-view {
-  display: flex;
-  flex-direction: column;
-  overflow-x: hidden;
-  overflow-y: auto;
-}
-
-.panel-container .tree-container .objectBox {
-  white-space: normal;
-  word-wrap: break-word;
-}
-
-.properties-view {
-  flex-grow: 1;
-}
-
-.properties-view .searchbox-section {
-  flex: 0 1 auto;
-}
-
-.properties-view .devtools-searchbox {
-  padding: 0;
-}
-
-.properties-view .devtools-searchbox input {
-  margin: 1px 3px;
-}
-
-.devtools-checkbox {
-  position: relative;
-  vertical-align: middle;
-  bottom: 1px;
-}
-
-.devtools-checkbox-label {
-  margin-inline-start: 10px;
-  margin-inline-end: 3px;
-  white-space: nowrap;
-}
-
-/* Empty notices in tab panels */
-
-.empty-notice {
-  color: var(--theme-body-color-inactive);
-  padding: 3px 8px;
-  text-align: center;
-  display: flex;
-  align-items: center;
-  justify-content: center;
-  height: 100%;
-  font-size: 24px;
-}
-
-/* Text inputs in tab panels */
-
-.textbox-input {
-  text-overflow: ellipsis;
-  border: none;
-  background: none;
-  color: inherit;
-  width: 100%;
-}
-
-.textbox-input:focus {
-  outline: 0;
-  box-shadow: var(--theme-focus-box-shadow-textbox);
-}
-
-/* Tree table in tab panels */
-
-.tree-container, .tree-container .treeTable {
-  position: relative;
-  height: 100%;
-  width: 100%;
-  overflow: auto;
-  flex: 1;
-}
-
-.tree-container .treeTable,
-.tree-container .treeTable tbody {
-  display: flex;
-  flex-direction: column;
-}
-
-.tree-container .treeTable tbody {
-  /* Apply flex to table will create an anonymous table element outside of tbody
-   * See also http://stackoverflow.com/a/30851678
-   * Therefore, we set height with this magic number in order to remove the
-   * redundant scrollbar when source editor appears.
-   */
-  height: calc(100% - 4px);
-}
-
-.tree-container .treeTable tr {
-  display: block;
-  position: relative;
-}
-
-/* Make right td fill available horizontal space */
-.tree-container .treeTable td:last-child {
-  width: 100%;
-}
-
-.properties-view .devtools-searchbox,
-.tree-container .treeTable .tree-section {
-  width: 100%;
-  background-color: var(--theme-toolbar-background);
-}
-
-.tree-container .treeTable tr.tree-section:not(:first-child) td:not([class=""]) {
-  border-top: 1px solid var(--theme-splitter-color);
-}
-
-.properties-view .devtools-searchbox,
-.tree-container .treeTable tr.tree-section:not(:last-child) td:not([class=""]) {
-  border-bottom: 1px solid var(--theme-splitter-color);
-}
-
-.tree-container .treeTable .tree-section > * {
-  vertical-align: middle;
-}
-
-.tree-container .treeTable .treeRow.tree-section > .treeLabelCell > .treeLabel,
-.tree-container .treeTable .treeRow.tree-section > .treeLabelCell > .treeLabel:hover,
-.tree-container .treeTable .treeRow.tree-section > .treeValueCell:not(:hover) * {
-  color: var(--theme-toolbar-color);
-}
-
-.tree-container .treeTable .treeValueCell {
-  /* FIXME: Make value cell can be reduced to shorter width */
-  max-width: 0;
-  padding-inline-end: 5px;
-}
-
-/* Source editor in tab panels */
-
-/* If there is a source editor shows up in the last row of TreeView,
- * it should occupy the available vertical space.
- */
-.tree-container .treeTable .editor-row-container,
-.tree-container .treeTable tr:last-child td[colspan="2"] {
-  display: block;
-  height: 100%;
-}
-
-.source-editor-mount {
-  width: 100%;
-  height: 100%;
-}
-
-.headers-summary-label,
-.tree-container .objectBox {
-  white-space: nowrap;
-}
-
-/* Summary tabpanel */
-
-.tabpanel-summary-container {
-  padding: 1px;
-}
-
-.tabpanel-summary-label {
-  display: inline-block;
-  padding-inline-start: 4px;
-  padding-inline-end: 3px;
-  font-weight: 600;
-}
-
-.tabpanel-summary-value {
-  color: inherit;
-  padding-inline-start: 3px;
-}
-
-.theme-dark .tabpanel-summary-value {
-  color: var(--theme-selection-color);
-}
-
-/* Headers tabpanel */
-
-.headers-overview {
-  background: var(--theme-toolbar-background);
-}
-
-.headers-summary,
-.response-summary {
-  display: flex;
-  align-items: center;
-}
-
-.headers-summary .devtools-button {
-  margin-inline-end: 6px;
-}
-
-.headers-summary .requests-list-status-icon {
-  min-width: 10px;
-}
-
-.headers-summary .raw-headers-container {
-  display: flex;
-  width: 100%;
-}
-
-.headers-summary .raw-headers {
-  width: 50%;
-  padding: 0 4px;
-}
-
-.headers-summary .raw-headers textarea {
-  width: 100%;
-  height: 50vh;
-  font: message-box;
-  resize: none;
-}
-
-.headers-summary .raw-headers .tabpanel-summary-label {
-  padding: 0 0 4px 0;
-}
-
-.headers-summary .textbox-input {
-  margin-inline-end: 2px;
-}
-
-.headers-summary .status-text {
-    width: auto!important;
-}
-
-/* Response tabpanel */
-
-.response-error-header {
-  margin: 0;
-  padding: 3px 8px;
-  background-color: var(--theme-highlight-red);
-  color: var(--theme-selection-color);
-}
-
-.response-image-box {
-  display: flex;
-  flex-direction: column;
-  justify-content: center;
-  align-items: center;
-  overflow-y: auto;
-  padding: 10px;
-}
-
-.response-image {
-  background: #fff;
-  border: 1px dashed GrayText;
-  margin-bottom: 10px;
-  max-width: 300px;
-  max-height: 100px;
-}
-
-/* Timings tabpanel */
-
-.timings-container {
-  display: flex;
-}
-
-.timings-label {
-  width: 10em;
-}
-
-.requests-list-timings-container {
-  display: flex;
-  flex: 1;
-  align-items: center;
-}
-
-.requests-list-timings-offset {
-  transition: width 0.2s ease-out;
-}
-
-.requests-list-timings-box {
-  border: none;
-  min-width: 1px;
-  transition: width 0.2s ease-out;
-}
-
-.theme-firebug .requests-list-timings-total {
-  color: var(--theme-body-color);
-}
-
-/* Stack trace panel */
-
-.stack-trace {
-  font-family: var(--monospace-font-family);
-  /* The markup contains extra whitespace to improve formatting of clipboard text.
-     Make sure this whitespace doesn't affect the HTML rendering */
-  white-space: normal;
-  padding: 5px;
-}
-
-.stack-trace .frame-link-source,
-.message-location .frame-link-source {
-  /* Makes the file name truncated (and ellipsis shown) on the left side */
-  direction: rtl;
-  white-space: nowrap;
-  overflow: hidden;
-  text-overflow: ellipsis;
-}
-
-.stack-trace .frame-link-source-inner,
-.message-location .frame-link-source-inner {
-  /* Enforce LTR direction for the file name - fixes bug 1290056 */
-  direction: ltr;
-  unicode-bidi: embed;
-}
-
-.stack-trace .frame-link-function-display-name {
-  white-space: nowrap;
-  overflow: hidden;
-  text-overflow: ellipsis;
-  margin-inline-end: 1ch;
-}
-
-/* Security tabpanel */
-
-/* Overwrite tree-view cell colon `:` for security panel and tree section */
-.security-panel .treeTable .treeLabelCell::after,
-.treeTable .tree-section .treeLabelCell::after {
-  content: "";
-}
-
-/* Layout additional warning icon in tree value cell  */
-.security-info-value {
-  display: flex;
-}
-
-.security-warning-icon {
-  background-image: url(chrome://devtools/skin/images/alerticon-warning.png);
-  background-size: 13px 12px;
-  margin-inline-start: 5px;
-  vertical-align: top;
-  width: 13px;
-  height: 12px;
-}
-
-@media (min-resolution: 1.1dppx) {
-  .security-warning-icon {
-    background-image: url(chrome://devtools/skin/images/alerticon-warning@2x.png);
-  }
-}
-
-/* Custom request panel */
-
-.custom-request-panel {
-  height: 100%;
-  overflow: auto;
-  padding: 0 4px;
-  background-color: var(--theme-sidebar-background);
-}
-
-.theme-dark .custom-request-panel {
-  color: var(--theme-selection-color);
-}
-
-.custom-request-label {
-  font-weight: 600;
-}
-
-.custom-request-panel textarea {
-  resize: none;
-  font: message-box;
-}
-
-.custom-header,
-.custom-method-and-url,
-.custom-request,
-.custom-section {
-  display: flex;
-}
-
-.custom-header {
-  flex-grow: 1;
-  font-size: 1.1em;
-  padding-top: 4px;
-}
-
-.custom-section {
-  flex-direction: column;
-  margin-top: 0.5em;
-}
-
-.custom-method-value {
-  width: 4.5em;
-}
-
-.custom-url-value {
-  flex-grow: 1;
-  margin-inline-start: 6px;
-}
-
-/* Statistics panel buttons */
-
-.requests-list-network-summary-button {
-  display: inline-flex;
-  cursor: pointer;
-  height: 18px;
-  background: none;
-  box-shadow: none;
-  border-color: transparent;
-  padding-inline-end: 0;
-  margin-top: 3px;
-  margin-bottom: 3px;
-  margin-inline-end: 1em;
-}
-
-.requests-list-network-summary-button > .summary-info-icon {
-  background: url(chrome://devtools/skin/images/profiler-stopwatch.svg) no-repeat;
-  -moz-context-properties: fill;
-  fill: var(--theme-toolbar-color);
-  width: 16px;
-  height: 16px;
-  opacity: 0.8;
-}
-
-.requests-list-network-summary-button:hover > .summary-info-icon {
-  opacity: 1;
-}
-
-/* Statistics panel */
-
-.statistics-panel {
-  display: flex;
-  height: 100vh;
-}
-
-.statistics-panel .devtools-toolbarbutton.back-button {
-  min-width: 4em;
-  margin: 0;
-  padding: 0;
-  border-radius: 0;
-  border-top: none;
-  border-bottom: none;
-  border-inline-start: none;
-}
-
-.statistics-panel .splitter {
-  border-color: rgba(0,0,0,0.2);
-  cursor: default;
-  pointer-events: none;
-  height: 100%;
-}
-
-.statistics-panel .charts-container {
-  display: flex;
-  width: 100%;
-}
-
-.statistics-panel .charts,
-.statistics-panel .pie-table-chart-container {
-  width: 100%;
-  height: 100%;
-}
-
-.statistics-panel .learn-more-link {
-  font-weight: 400;
-}
-
-.statistics-panel .table-chart-title {
-  display: flex;
-}
-
-.pie-table-chart-container {
-  display: flex;
-  justify-content: center;
-  align-items: center;
-}
-
-.statistics-panel .pie-chart-container {
-  margin-inline-start: 3vw;
-  margin-inline-end: 1vw;
-}
-
-.statistics-panel .table-chart-container {
-  display: flex;
-  flex-direction: column;
-  flex: 1 1 auto;
-  min-width: 240px;
-  margin-inline-start: 1vw;
-  margin-inline-end: 3vw;
-}
-
-.chart-colored-blob[name=html] {
-  fill: var(--theme-highlight-bluegrey);
-  background: var(--theme-highlight-bluegrey);
-}
-
-.chart-colored-blob[name=css] {
-  fill: var(--theme-highlight-blue);
-  background: var(--theme-highlight-blue);
-}
-
-.chart-colored-blob[name=js] {
-  fill: var(--theme-highlight-lightorange);
-  background: var(--theme-highlight-lightorange);
-}
-
-.chart-colored-blob[name=xhr] {
-  fill: var(--theme-highlight-orange);
-  background: var(--theme-highlight-orange);
-}
-
-.chart-colored-blob[name=fonts] {
-  fill: var(--theme-highlight-purple);
-  background: var(--theme-highlight-purple);
-}
-
-.chart-colored-blob[name=images] {
-  fill: var(--theme-highlight-pink);
-  background: var(--theme-highlight-pink);
-}
-
-.chart-colored-blob[name=media] {
-  fill: var(--theme-highlight-green);
-  background: var(--theme-highlight-green);
-}
-
-.chart-colored-blob[name=flash] {
-  fill: var(--theme-highlight-red);
-  background: var(--theme-highlight-red);
-}
-
-.table-chart-row {
-  display: flex;
-}
-
-.table-chart-row-label[name=cached] {
-  display: none;
-}
-
-.table-chart-row-label[name=count] {
-  width: 3em;
-  text-align: end;
-}
-
-.table-chart-row-label[name=label] {
-  width: 7em;
-  text-align: start;
-}
-
-.table-chart-row-label[name=size] {
-  width: 7em;
-  text-align: start;
-}
-
-.table-chart-row-label[name=time] {
-  width: 7em;
-  text-align: start;
-}
-
-.table-chart-totals {
-  display: flex;
-  flex-direction: column;
-}
-
-/* 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 */
-  background: rgba(70, 175, 227, 0.8);
-}
-
-.theme-firebug .chart-colored-blob[name=js] {
-  fill: rgba(235, 83, 104, 0.8); /* red */
-  background: rgba(235, 83, 104, 0.8);
-}
-
-.theme-firebug .chart-colored-blob[name=xhr] {
-  fill: rgba(217, 102, 41, 0.8); /* orange  */
-  background: rgba(217, 102, 41, 0.8);
-}
-
-.theme-firebug .chart-colored-blob[name=fonts] {
-  fill: rgba(223, 128, 255, 0.8); /* pink */
-  background: rgba(223, 128, 255, 0.8);
-}
-
-.theme-firebug .chart-colored-blob[name=images] {
-  fill: rgba(112, 191, 83, 0.8); /* pink */
-  background: rgba(112, 191, 83, 0.8);
-}
-
-.theme-firebug .chart-colored-blob[name=media] {
-  fill: rgba(235, 235, 84, 0.8); /* yellow */
-  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 web design support */
-
-@media (max-width: 700px) {
-  .requests-list-header-button {
-    padding-inline-start: 8px;
-  }
-
-  .requests-list-status-code {
-    width: auto;
-  }
-
-  .requests-list-size {
-    /* 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;
-  }
-
-  :root[platform="linux"] .requests-list-header-button {
-    font-size: 85%;
-  }
-
-  .network-details-panel-toggle:dir(ltr)::before {
-    transform: rotate(90deg);
-  }
-
-  .network-details-panel-toggle:dir(rtl)::before {
-    transform: rotate(-90deg);
-  }
-}
-
new file mode 100644
--- /dev/null
+++ b/devtools/client/netmonitor/src/assets/styles/variables.css
@@ -0,0 +1,46 @@
+/* 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/. */
+
+:root.theme-dark {
+  --table-splitter-color: rgba(255,255,255,0.15);
+  --table-zebra-background: rgba(255,255,255,0.05);
+
+  --timing-blocked-color: rgba(235, 83, 104, 0.8);
+  --timing-dns-color: rgba(223, 128, 255, 0.8); /* pink */
+  --timing-ssl-color: rgba(217, 102, 41, 0.8); /* orange */
+  --timing-connect-color: rgba(217, 102, 41, 0.8); /* orange */
+  --timing-send-color: rgba(70, 175, 227, 0.8); /* light blue */
+  --timing-wait-color: rgba(94, 136, 176, 0.8); /* blue grey */
+  --timing-receive-color: rgba(112, 191, 83, 0.8); /* green */
+
+  --sort-ascending-image: url(chrome://devtools/skin/images/sort-ascending-arrow.svg);
+  --sort-descending-image: url(chrome://devtools/skin/images/sort-descending-arrow.svg);
+}
+
+:root.theme-light {
+  --table-splitter-color: rgba(0,0,0,0.15);
+  --table-zebra-background: rgba(0,0,0,0.05);
+
+  --timing-blocked-color: rgba(235, 83, 104, 0.8);
+  --timing-dns-color: rgba(223, 128, 255, 0.8); /* pink */
+  --timing-ssl-color: rgba(217, 102, 41, 0.8); /* orange */
+  --timing-connect-color: rgba(217, 102, 41, 0.8); /* orange */
+  --timing-send-color: rgba(0, 136, 204, 0.8); /* blue */
+  --timing-wait-color: rgba(95, 136, 176, 0.8); /* blue grey */
+  --timing-receive-color: rgba(44, 187, 15, 0.8); /* green */
+
+  --sort-ascending-image: url(chrome://devtools/skin/images/sort-ascending-arrow.svg);
+  --sort-descending-image: url(chrome://devtools/skin/images/sort-descending-arrow.svg);
+}
+
+: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);
+}
+
+/* Icons */
+:root {
+  --play-icon-url: url("chrome://devtools/skin/images/play.svg");
+  --pause-icon-url: url("chrome://devtools/skin/images/pause.svg");
+}
--- a/devtools/client/themes/webconsole.css
+++ b/devtools/client/themes/webconsole.css
@@ -411,16 +411,17 @@ html #webconsole-notificationbox {
   /* Always allow scrolling on input - it auto expands in js by setting height,
      but don't want it to get bigger than the window. 24px = toolbar height. */
   max-height: calc(90vh - 24px);
   background-image: var(--theme-command-line-image);
   background-repeat: no-repeat;
   background-size: 16px 16px;
   background-position: 4px 50%;
   color: var(--theme-content-color1);
+  box-sizing: border-box;
 }
 
 .jsterm-complete-node {
   color: var(--theme-comment);
 }
 
 .theme-light .jsterm-input-container {
   /* For light theme use a white background for the input - it looks better
@@ -1129,17 +1130,16 @@ a.learn-more-link.webconsole-learn-more-
   direction: ltr;
   overflow: auto;
   -moz-user-select: text;
   position: relative;
 }
 
 html,
 body {
-  display: block !important; /* Until Bug 1409413 removes the accidental display: flex */
   height: 100%;
   margin: 0;
   padding: 0;
 }
 
 body {
   overflow: hidden;
 }
--- a/devtools/client/webconsole/local-dev/index.js
+++ b/devtools/client/webconsole/local-dev/index.js
@@ -23,17 +23,17 @@ EventEmitter.decorate(window);
 
 require("../../themes/widgets.css");
 require("../../themes/webconsole.css");
 require("../../themes/components-frame.css");
 require("../../themes/light-theme.css");
 require("../../shared/components/reps/reps.css");
 require("../../shared/components/tabs/Tabs.css");
 require("../../shared/components/tabs/TabBar.css");
-require("../../netmonitor/src/assets/styles/netmonitor.css");
+require("../../netmonitor/src/assets/styles/httpi.css");
 
 pref("devtools.debugger.remote-timeout", 10000);
 pref("devtools.hud.loglimit", 10000);
 pref("devtools.webconsole.filter.error", true);
 pref("devtools.webconsole.filter.warn", true);
 pref("devtools.webconsole.filter.info", true);
 pref("devtools.webconsole.filter.log", true);
 pref("devtools.webconsole.filter.debug", true);
--- a/devtools/client/webconsole/new-console-output/components/message-types/NetworkEventMessage.js
+++ b/devtools/client/webconsole/new-console-output/components/message-types/NetworkEventMessage.js
@@ -115,17 +115,18 @@ function NetworkEventMessage({
     getNetworkRequest: () => {},
     sendHTTPRequest: () => {},
     setPreferences: () => {},
     triggerActivity: () => {},
   };
 
   // Only render the attachment if the network-event is
   // actually opened (performance optimization).
-  const attachment = open && dom.div({className: "network-info devtools-monospace"},
+  const attachment = open && dom.div({
+    className: "network-info network-monitor devtools-monospace"},
     TabboxPanel({
       connector,
       activeTabId: networkMessageActiveTabId,
       request: networkMessageUpdate,
       sourceMapService: serviceContainer.sourceMapService,
       openLink: serviceContainer.openLink,
       selectTab: (tabId) => {
         dispatch(actions.selectNetworkMessageTab(tabId));
--- a/devtools/client/webconsole/webconsole.html
+++ b/devtools/client/webconsole/webconsole.html
@@ -8,17 +8,17 @@
     <link rel="stylesheet" href="chrome://devtools/skin/widgets.css"/>
     <link rel="stylesheet" href="resource://devtools/client/themes/light-theme.css"/>
     <link rel="stylesheet" href="chrome://devtools/skin/webconsole.css"/>
     <link rel="stylesheet" href="chrome://devtools/skin/components-frame.css"/>
     <link rel="stylesheet" href="resource://devtools/client/shared/components/reps/reps.css"/>
     <link rel="stylesheet" href="resource://devtools/client/shared/components/tabs/Tabs.css"/>
     <link rel="stylesheet" href="resource://devtools/client/shared/components/tabs/TabBar.css"/>
     <link rel="stylesheet" href="resource://devtools/client/shared/components/NotificationBox.css"/>
-    <link rel="stylesheet" href="chrome://devtools/content/netmonitor/src/assets/styles/netmonitor.css"/>
+    <link rel="stylesheet" href="chrome://devtools/content/netmonitor/src/assets/styles/httpi.css"/>
 
     <script src="chrome://devtools/content/shared/theme-switching.js"></script>
     <script type="application/javascript"
             src="resource://devtools/client/webconsole/new-console-output/main.js"></script>
   </head>
   <body class="theme-sidebar" role="application">
     <div id="app-wrapper" class="theme-body">
       <div id="output-container" role="document" aria-live="polite"></div>
--- a/devtools/shim/aboutdevtools/aboutdevtools.css
+++ b/devtools/shim/aboutdevtools/aboutdevtools.css
@@ -79,35 +79,43 @@ p {
   font-weight: 300;
   margin: 10px 0;
 }
 
 .feature-desc {
   margin: 1em 20px;
 }
 
-.installpage-link {
+.feature-link {
+  display: block;
+}
+
+.external,
+.external:hover,
+.external:visited,
+.external:hover:active {
   color: var(--blue-60);
-  -moz-context-properties: fill;
-  fill: var(--blue-60);
 }
 
 .external::after {
   content: "";
 
   display: inline-block;
   height: 16px;
   width: 16px;
 
   margin: -.3rem .15rem 0 0.25rem;
   vertical-align: middle;
 
   background-image: url(images/external-link.svg);
   background-repeat: no-repeat;
   background-size: 16px 16px;
+
+  -moz-context-properties: fill;
+  fill: var(--blue-60);
 }
 
 .title {
   font-weight: 300;
   font-size: 32px;
   margin-top: 16px;
   line-height: 44px;
 }
@@ -196,17 +204,19 @@ footer {
 
 .footer-message-title {
   color: var(--white);
 }
 
 .footer-link {
   display: block;
   margin-top: 10px;
-  -moz-context-properties: fill;
+}
+
+.footer-link::after {
   fill: var(--white);
 }
 
 .footer-link,
 .footer-link:hover,
 .footer-link:visited,
 .footer-link:hover:active {
   color: var(--white);
--- a/devtools/shim/aboutdevtools/aboutdevtools.js
+++ b/devtools/shim/aboutdevtools/aboutdevtools.js
@@ -12,27 +12,31 @@ const DEVTOOLS_ENABLED_PREF = "devtools.
 const MESSAGES = {
   AboutDebugging: "about-debugging-message",
   ContextMenu: "inspect-element-message",
   HamburgerMenu: "menu-message",
   KeyShortcut: "key-shortcut-message",
   SystemMenu: "menu-message",
 };
 
+const ABOUTDEVTOOLS_STRINGS = "chrome://devtools-shim/locale/aboutdevtools.properties";
+const aboutDevtoolsBundle = Services.strings.createBundle(ABOUTDEVTOOLS_STRINGS);
+
+const KEY_SHORTCUTS_STRINGS = "chrome://devtools-shim/locale/key-shortcuts.properties";
+const keyShortcutsBundle = Services.strings.createBundle(KEY_SHORTCUTS_STRINGS);
+
 // URL constructor doesn't support about: scheme,
 // we have to use http in order to have working searchParams.
 let url = new URL(window.location.href.replace("about:", "http://"));
 let reason = url.searchParams.get("reason");
 let tabid = parseInt(url.searchParams.get("tabid"), 10);
 
 function getToolboxShortcut() {
-  const bundleUrl = "chrome://devtools-shim/locale/key-shortcuts.properties";
-  const bundle = Services.strings.createBundle(bundleUrl);
   const modifier = Services.appinfo.OS == "Darwin" ? "Cmd+Opt+" : "Ctrl+Shift+";
-  return modifier + bundle.GetStringFromName("toggleToolbox.commandkey");
+  return modifier + keyShortcutsBundle.GetStringFromName("toggleToolbox.commandkey");
 }
 
 function onInstallButtonClick() {
   Services.prefs.setBoolPref("devtools.enabled", true);
 }
 
 function onCloseButtonClick() {
   window.close();
@@ -46,16 +50,98 @@ function updatePage() {
     installPage.setAttribute("hidden", "true");
     welcomePage.removeAttribute("hidden");
   } else {
     welcomePage.setAttribute("hidden", "true");
     installPage.removeAttribute("hidden");
   }
 }
 
+/**
+ * Array of descriptors for features displayed on about:devtools.
+ * Each feature should contain:
+ * - icon: the name of the image to use
+ * - title: the key of the localized title (from aboutdevtools.properties)
+ * - desc: the key of the localized description (from aboutdevtools.properties)
+ * - link: the MDN documentation link
+ */
+const features = [
+  {
+    icon: "chrome://devtools-shim/content/aboutdevtools/images/feature-inspector.svg",
+    title: "features.inspector.title",
+    desc: "features.inspector.desc",
+    link: "https://developer.mozilla.org/docs/Tools/Page_Inspector",
+  }, {
+    icon: "chrome://devtools-shim/content/aboutdevtools/images/feature-console.svg",
+    title: "features.console.title",
+    desc: "features.console.desc",
+    link: "https://developer.mozilla.org/docs/Tools/Web_Console",
+  }, {
+    icon: "chrome://devtools-shim/content/aboutdevtools/images/feature-debugger.svg",
+    title: "features.debugger.title",
+    desc: "features.debugger.desc",
+    link: "https://developer.mozilla.org/docs/Tools/Debugger",
+  }, {
+    icon: "chrome://devtools-shim/content/aboutdevtools/images/feature-network.svg",
+    title: "features.network.title",
+    desc: "features.network.desc",
+    link: "https://developer.mozilla.org/docs/Tools/Network_Monitor",
+  }, {
+    icon: "chrome://devtools-shim/content/aboutdevtools/images/feature-storage.svg",
+    title: "features.storage.title",
+    desc: "features.storage.desc",
+    link: "https://developer.mozilla.org/docs/Tools/Storage_Inspector",
+  }, {
+    icon: "chrome://devtools-shim/content/aboutdevtools/images/feature-responsive.svg",
+    title: "features.responsive.title",
+    desc: "features.responsive.desc",
+    link: "https://developer.mozilla.org/docs/Tools/Responsive_Design_Mode",
+  }, {
+    icon: "chrome://devtools-shim/content/aboutdevtools/images/feature-visualediting.svg",
+    title: "features.visualediting.title",
+    desc: "features.visualediting.desc",
+    link: "https://developer.mozilla.org/docs/Tools/Style_Editor",
+  }, {
+    icon: "chrome://devtools-shim/content/aboutdevtools/images/feature-performance.svg",
+    title: "features.performance.title",
+    desc: "features.performance.desc",
+    link: "https://developer.mozilla.org/docs/Tools/Performance",
+  }, {
+    icon: "chrome://devtools-shim/content/aboutdevtools/images/feature-memory.svg",
+    title: "features.memory.title",
+    desc: "features.memory.desc",
+    link: "https://developer.mozilla.org/docs/Tools/Memory",
+  },
+];
+
+/**
+ * Helper to create a DOM element to represent a DevTools feature.
+ */
+function createFeatureEl(feature) {
+  let li = document.createElement("li");
+  li.classList.add("feature");
+  let learnMore = aboutDevtoolsBundle.GetStringFromName("features.learnMore");
+
+  let {icon, link, title, desc} = feature;
+  title = aboutDevtoolsBundle.GetStringFromName(title);
+  desc = aboutDevtoolsBundle.GetStringFromName(desc);
+  // eslint-disable-next-line no-unsanitized/property
+  li.innerHTML =
+    `<a class="feature-link" href="${link}" target="_blank">
+       <img class="feature-icon" src="${icon}"/>
+     </a>
+     <h3 class="feature-name">${title}</h3>
+     <p class="feature-desc">
+       ${desc}
+       <a class="external feature-link" href="${link}" target="_blank">${learnMore}</a>
+     </p>`;
+
+  return li;
+}
+
 window.addEventListener("load", function () {
   const inspectorShortcut = getToolboxShortcut();
   const welcomeMessage = document.getElementById("welcome-message");
   welcomeMessage.textContent = welcomeMessage.textContent.replace(
     "##INSPECTOR_SHORTCUT##", inspectorShortcut);
 
   // Set the appropriate title message.
   if (reason == "ContextMenu") {
@@ -71,16 +157,21 @@ window.addEventListener("load", function
     message.removeAttribute("hidden");
   }
 
   // Attach event listeners
   document.getElementById("install").addEventListener("click", onInstallButtonClick);
   document.getElementById("close").addEventListener("click", onCloseButtonClick);
   Services.prefs.addObserver(DEVTOOLS_ENABLED_PREF, updatePage);
 
+  let featuresContainer = document.querySelector(".features-list");
+  for (let feature of features) {
+    featuresContainer.appendChild(createFeatureEl(feature));
+  }
+
   // Update the current page based on the current value of DEVTOOLS_ENABLED_PREF.
   updatePage();
 }, { once: true });
 
 window.addEventListener("beforeunload", function () {
   // Focus the tab that triggered the DevTools onboarding.
   if (document.visibilityState != "visible") {
     // Only try to focus the correct tab if the current tab is the about:devtools page.
--- a/devtools/shim/aboutdevtools/aboutdevtools.xhtml
+++ b/devtools/shim/aboutdevtools/aboutdevtools.xhtml
@@ -48,69 +48,16 @@
       <div class="right-pane">
         <h1 class="title" >&aboutDevtools.welcome.title;</h1>
         <p id="welcome-message">&aboutDevtools.welcome.message;</p>
       </div>
     </div>
 
     <div class="features">
       <ul class="features-list">
-        <li class="feature">
-          <img class="feature-icon" src="chrome://devtools-shim/content/aboutdevtools/images/feature-inspector.svg" alt=""/>
-          <h3 class="feature-name">Inspector</h3>
-          <p class="feature-desc">Inspect and refine code to build pixel-perfect layouts.</p>
-        </li>
-
-        <li class="feature">
-          <img class="feature-icon" src="chrome://devtools-shim/content/aboutdevtools/images/feature-console.svg" alt=""/>
-          <h3 class="feature-name">Console</h3>
-          <p class="feature-desc">Track CSS, JavaScript, security and network issues.</p>
-        </li>
-
-        <li class="feature">
-          <img class="feature-icon" src="chrome://devtools-shim/content/aboutdevtools/images/feature-debugger.svg" alt=""/>
-          <h3 class="feature-name">Debugger</h3>
-          <p class="feature-desc">Powerful JavaScript debugger with support for your framework.</p>
-        </li>
-
-        <li class="feature">
-          <img class="feature-icon" src="chrome://devtools-shim/content/aboutdevtools/images/feature-network.svg" alt=""/>
-          <h3 class="feature-name">Network</h3>
-          <p class="feature-desc">Monitor network requests that can slow or block your site.</p>
-        </li>
-
-        <li class="feature">
-          <img class="feature-icon" src="chrome://devtools-shim/content/aboutdevtools/images/feature-storage.svg" alt=""/>
-          <h3 class="feature-name">Storage panel</h3>
-          <p class="feature-desc">Add, modify and remove cache, cookies, databases and session data.</p>
-        </li>
-
-        <li class="feature">
-          <img class="feature-icon" src="chrome://devtools-shim/content/aboutdevtools/images/feature-responsive-mode.svg" alt=""/>
-          <h3 class="feature-name">Responsive Design Mode</h3>
-          <p class="feature-desc">Test sites on emulated devices in your browser.</p>
-        </li>
-
-        <li class="feature">
-          <img class="feature-icon" src="chrome://devtools-shim/content/aboutdevtools/images/feature-visual-editing.svg" alt=""/>
-          <h3 class="feature-name">Visual Editing</h3>
-          <p class="feature-desc">Fine-tune animations, alignment and padding.</p>
-        </li>
-
-        <li class="feature">
-          <img class="feature-icon" src="chrome://devtools-shim/content/aboutdevtools/images/feature-performance.svg" alt=""/>
-          <h3 class="feature-name">Performance</h3>
-          <p class="feature-desc">Unblock bottlenecks, streamline processes, optimize assets.</p>
-        </li>
-
-        <li class="feature">
-          <img class="feature-icon" src="chrome://devtools-shim/content/aboutdevtools/images/feature-memory.svg" alt=""/>
-          <h3 class="feature-name">Memory</h3>
-          <p class="feature-desc">Find memory leaks and make your application zippy.</p>
-        </li>
       </ul>
     </div>
 
     <footer>
       <img class="dev-edition-logo"
            src="chrome://devtools-shim/content/aboutdevtools/images/dev-edition-logo.svg"
            alt="Firefox Developer Edition logo"/>
       <div class="footer-message">
rename from devtools/shim/aboutdevtools/images/feature-responsive-mode.svg
rename to devtools/shim/aboutdevtools/images/feature-responsive.svg
rename from devtools/shim/aboutdevtools/images/feature-visual-editing.svg
rename to devtools/shim/aboutdevtools/images/feature-visualediting.svg
--- a/devtools/shim/jar.mn
+++ b/devtools/shim/jar.mn
@@ -16,14 +16,14 @@ devtools-shim.jar:
 
     content/aboutdevtools/images/dev-edition-logo.svg (aboutdevtools/images/dev-edition-logo.svg)
     content/aboutdevtools/images/external-link.svg (aboutdevtools/images/external-link.svg)
     content/aboutdevtools/images/feature-inspector.svg (aboutdevtools/images/feature-inspector.svg)
     content/aboutdevtools/images/feature-console.svg (aboutdevtools/images/feature-console.svg)
     content/aboutdevtools/images/feature-debugger.svg (aboutdevtools/images/feature-debugger.svg)
     content/aboutdevtools/images/feature-network.svg (aboutdevtools/images/feature-network.svg)
     content/aboutdevtools/images/feature-memory.svg (aboutdevtools/images/feature-memory.svg)
-    content/aboutdevtools/images/feature-visual-editing.svg (aboutdevtools/images/feature-visual-editing.svg)
-    content/aboutdevtools/images/feature-responsive-mode.svg (aboutdevtools/images/feature-responsive-mode.svg)
+    content/aboutdevtools/images/feature-visualediting.svg (aboutdevtools/images/feature-visualediting.svg)
+    content/aboutdevtools/images/feature-responsive.svg (aboutdevtools/images/feature-responsive.svg)
     content/aboutdevtools/images/feature-storage.svg (aboutdevtools/images/feature-storage.svg)
     content/aboutdevtools/images/feature-performance.svg (aboutdevtools/images/feature-performance.svg)
 
     content/DevToolsShim.jsm  (DevToolsShim.jsm)
new file mode 100644
--- /dev/null
+++ b/devtools/shim/locales/en-US/aboutdevtools.properties
@@ -0,0 +1,37 @@
+# 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/.
+
+# LOCALIZATION NOTE These strings are used in the about:devtools page.
+
+# LOCALIZATION NOTE (features.learnMore): The text of the learn more link displayed below
+# each feature section of about:devtools. Each section presents a quick description of a
+# DevTools panel/feature. The learn more link points to the associated MDN page.
+features.learnMore=Learn more
+
+features.inspector.title=Inspector
+features.inspector.desc=Inspect and refine code to build pixel-perfect layouts.
+
+features.console.title=Console
+features.console.desc=Track CSS, JavaScript, security and network issues.
+
+features.debugger.title=Debugger
+features.debugger.desc=Powerful JavaScript debugger with support for your framework.
+
+features.network.title=Network
+features.network.desc=Monitor network requests that can slow or block your site.
+
+features.storage.title=Storage
+features.storage.desc=Add, modify and remove cache, cookies, databases and session data.
+
+features.responsive.title=Responsive Design Mode
+features.responsive.desc=Test sites on emulated devices in your browser.
+
+features.visualediting.title=Visual Editing
+features.visualediting.desc=Fine-tune animations, alignment and padding.
+
+features.performance.title=Performance
+features.performance.desc=Unblock bottlenecks, streamline processes, optimize assets.
+
+features.memory.title=Memory
+features.memory.desc=Find memory leaks and make your application zippy.
--- a/devtools/templates.mozbuild
+++ b/devtools/templates.mozbuild
@@ -9,21 +9,17 @@ def DevToolsModules(*modules):
     '''Installs JS modules at a resource:// path that corresponds directly to
     their source tree location.
 
     For this to work as intended, a moz.build file should be placed in each
     source directory which uses this template to install only the JS files in
     its own directory.  Subdirectories should use their own moz.build.
 
     By following this pattern, there's less magic to require() and resource://
-    paths, since they now match the source tree.
-
-    Currently `DevToolsModules` can only be called once per moz.build, so we
-    build a list manually above.  Bug 1198013 tracks fixing this to make it more
-    like other moz.build constructs.'''
+    paths, since they now match the source tree.'''
 
     for m in modules:
         if '/' in m:
             error('DevToolsModules must be used from the same directory as ' +
                   'the files to be installed.')
 
     # jar.mn manifest files are typically used to install files to chrome
     # locations.  Instead of doing this, use this DevToolsModules syntax via
--- a/dom/base/nsGkAtomList.h
+++ b/dom/base/nsGkAtomList.h
@@ -2266,16 +2266,20 @@ GK_ATOM(windows_accent_color_in_titlebar
 GK_ATOM(windows_default_theme, "windows-default-theme")
 GK_ATOM(mac_graphite_theme, "mac-graphite-theme")
 GK_ATOM(mac_yosemite_theme, "mac-yosemite-theme")
 GK_ATOM(windows_compositor, "windows-compositor")
 GK_ATOM(windows_glass, "windows-glass")
 GK_ATOM(touch_enabled, "touch-enabled")
 GK_ATOM(menubar_drag, "menubar-drag")
 GK_ATOM(swipe_animation_enabled, "swipe-animation-enabled")
+GK_ATOM(gtk_csd_available, "gtk-csd-available")
+GK_ATOM(gtk_csd_minimize_button, "gtk-csd-minimize-button")
+GK_ATOM(gtk_csd_maximize_button, "gtk-csd-maximize-button")
+GK_ATOM(gtk_csd_close_button, "gtk-csd-close-button")
 
 // windows theme selector metrics
 GK_ATOM(windows_classic, "windows-classic")
 GK_ATOM(windows_theme_aero, "windows-theme-aero")
 GK_ATOM(windows_theme_aero_lite, "windows-theme-aero-lite")
 GK_ATOM(windows_theme_luna_blue, "windows-theme-luna-blue")
 GK_ATOM(windows_theme_luna_olive, "windows-theme-luna-olive")
 GK_ATOM(windows_theme_luna_silver, "windows-theme-luna-silver")
@@ -2300,16 +2304,20 @@ GK_ATOM(_moz_windows_glass, "-moz-window
 GK_ATOM(_moz_windows_theme, "-moz-windows-theme")
 GK_ATOM(_moz_os_version, "-moz-os-version")
 GK_ATOM(_moz_touch_enabled, "-moz-touch-enabled")
 GK_ATOM(_moz_menubar_drag, "-moz-menubar-drag")
 GK_ATOM(_moz_device_pixel_ratio, "-moz-device-pixel-ratio")
 GK_ATOM(_moz_device_orientation, "-moz-device-orientation")
 GK_ATOM(_moz_is_resource_document, "-moz-is-resource-document")
 GK_ATOM(_moz_swipe_animation_enabled, "-moz-swipe-animation-enabled")
+GK_ATOM(_moz_gtk_csd_available, "-moz-gtk-csd-available")
+GK_ATOM(_moz_gtk_csd_minimize_button, "-moz-gtk-csd-minimize-button")
+GK_ATOM(_moz_gtk_csd_maximize_button, "-moz-gtk-csd-maximize-button")
+GK_ATOM(_moz_gtk_csd_close_button, "-moz-gtk-csd-close-button")
 
 // application commands
 GK_ATOM(Back, "Back")
 GK_ATOM(Forward, "Forward")
 GK_ATOM(Reload, "Reload")
 GK_ATOM(Stop, "Stop")
 GK_ATOM(Search, "Search")
 GK_ATOM(Bookmarks, "Bookmarks")
--- a/dom/canvas/WebGLContext.cpp
+++ b/dom/canvas/WebGLContext.cpp
@@ -2002,17 +2002,16 @@ WebGLContext::GetSurfaceSnapshot(gfxAlph
     }
 
     if (out_alphaType) {
         *out_alphaType = alphaType;
     } else {
         // Expects Opaque or Premult
         if (alphaType == gfxAlphaType::NonPremult) {
             gfxUtils::PremultiplyDataSurface(surf, surf);
-            alphaType = gfxAlphaType::Premult;
         }
     }
 
     RefPtr<DrawTarget> dt =
         Factory::CreateDrawTarget(gfxPlatform::GetPlatform()->GetSoftwareBackend(),
                                   IntSize(mWidth, mHeight),
                                   SurfaceFormat::B8G8R8A8);
     if (!dt)
--- a/dom/media/ChannelMediaResource.cpp
+++ b/dom/media/ChannelMediaResource.cpp
@@ -433,17 +433,19 @@ ChannelMediaResource::CopySegmentToCache
                                          void* aClosure,
                                          const char* aFromSegment,
                                          uint32_t aToOffset,
                                          uint32_t aCount,
                                          uint32_t* aWriteCount)
 {
   Closure* closure = static_cast<Closure*>(aClosure);
   closure->mResource->mCacheStream.NotifyDataReceived(
-    closure->mLoadID, aCount, aFromSegment);
+    closure->mLoadID,
+    aCount,
+    reinterpret_cast<const uint8_t*>(aFromSegment));
   *aWriteCount = aCount;
   return NS_OK;
 }
 
 nsresult
 ChannelMediaResource::OnDataAvailable(uint32_t aLoadID,
                                       nsIInputStream* aStream,
                                       uint32_t aCount)
--- a/dom/media/MediaCache.cpp
+++ b/dom/media/MediaCache.cpp
@@ -1985,72 +1985,76 @@ MediaCacheStream::UpdatePrincipal(nsIPri
                                                   aPrincipal)) {
       stream->mClient->CacheClientNotifyPrincipalChanged();
     }
   }
 }
 
 void
 MediaCacheStream::NotifyDataReceived(uint32_t aLoadID,
-                                     int64_t aSize,
-                                     const char* aData)
+                                     uint32_t aCount,
+                                     const uint8_t* aData)
 {
   MOZ_ASSERT(aLoadID > 0);
   // This might happen off the main thread.
 
   ReentrantMonitorAutoEnter mon(mMediaCache->GetReentrantMonitor());
   if (mClosed) {
     // Nothing to do if the stream is closed.
     return;
   }
 
-  LOG("Stream %p DataReceived at %" PRId64 " count=%" PRId64 " aLoadID=%u",
+  LOG("Stream %p DataReceived at %" PRId64 " count=%u aLoadID=%u",
       this,
       mChannelOffset,
-      aSize,
+      aCount,
       aLoadID);
 
   if (mLoadID != aLoadID) {
     // mChannelOffset is updated to a new position when loading a new channel.
     // We should discard the data coming from the old channel so it won't be
     // stored to the wrong positoin.
     return;
   }
-  int64_t size = aSize;
-  const char* data = aData;
+
+  auto source = MakeSpan<const uint8_t>(aData, aCount);
 
   // We process the data one block (or part of a block) at a time
-  while (size > 0) {
-    uint32_t blockIndex = OffsetToBlockIndexUnchecked(mChannelOffset);
-    int32_t blockOffset = int32_t(mChannelOffset - blockIndex*BLOCK_SIZE);
-    int32_t chunkSize = std::min<int64_t>(BLOCK_SIZE - blockOffset, size);
+  while (!source.IsEmpty()) {
+    // The data we've collected so far in the partial block.
+    auto partial = MakeSpan<const uint8_t>(mPartialBlockBuffer.get(),
+                                           OffsetInBlock(mChannelOffset));
 
-    if (blockOffset == 0) {
+    if (partial.IsEmpty()) {
       // We've just started filling this buffer so now is a good time
       // to clear this flag.
       mMetadataInPartialBlockBuffer = false;
     }
 
-    ReadMode mode = mMetadataInPartialBlockBuffer
-      ? MODE_METADATA : MODE_PLAYBACK;
+    // The number of bytes needed to complete the partial block.
+    size_t remaining = BLOCK_SIZE - partial.Length();
 
-    if (blockOffset + chunkSize == BLOCK_SIZE) {
+    if (source.Length() >= remaining) {
       // We have a whole block now to write it out.
-      auto data1 = MakeSpan<const uint8_t>(
-        mPartialBlockBuffer.get(), blockOffset);
-      auto data2 = MakeSpan<const uint8_t>(
-        reinterpret_cast<const uint8_t*>(data), chunkSize);
-      mMediaCache->AllocateAndWriteBlock(this, blockIndex, mode, data1, data2);
+      mMediaCache->AllocateAndWriteBlock(
+        this,
+        OffsetToBlockIndexUnchecked(mChannelOffset),
+        mMetadataInPartialBlockBuffer ? MODE_METADATA : MODE_PLAYBACK,
+        partial,
+        source.First(remaining));
+      source = source.From(remaining);
+      mChannelOffset += remaining;
     } else {
-      memcpy(mPartialBlockBuffer.get() + blockOffset, data, chunkSize);
+      // The buffer to be filled in the partial block.
+      auto buf = MakeSpan<uint8_t>(mPartialBlockBuffer.get() + partial.Length(),
+                                   remaining);
+      memcpy(buf.Elements(), source.Elements(), source.Length());
+      mChannelOffset += source.Length();
+      break;
     }
-
-    mChannelOffset += chunkSize;
-    size -= chunkSize;
-    data += chunkSize;
   }
 
   MediaCache::ResourceStreamIterator iter(mMediaCache, mResourceID);
   while (MediaCacheStream* stream = iter.Next()) {
     if (stream->mStreamLength >= 0) {
       // The stream is at least as long as what we've read
       stream->mStreamLength = std::max(stream->mStreamLength, mChannelOffset);
     }
--- a/dom/media/MediaCache.h
+++ b/dom/media/MediaCache.h
@@ -259,17 +259,19 @@ public:
   // we do an HTTP load the seekability may be different (and sometimes
   // is, in practice, due to the effects of caching proxies).
   void NotifyDataStarted(uint32_t aLoadID, int64_t aOffset, bool aSeekable);
   // Notifies the cache that data has been received. The stream already
   // knows the offset because data is received in sequence and
   // the starting offset is known via NotifyDataStarted or because
   // the cache requested the offset in
   // ChannelMediaResource::CacheClientSeek, or because it defaulted to 0.
-  void NotifyDataReceived(uint32_t aLoadID, int64_t aSize, const char* aData);
+  void NotifyDataReceived(uint32_t aLoadID,
+                          uint32_t aCount,
+                          const uint8_t* aData);
   // Notifies the cache that the current bytes should be written to disk.
   // Called on the main thread.
   void FlushPartialBlock();
   // Notifies the cache that the channel has closed with the given status.
   void NotifyDataEnded(nsresult aStatus);
 
   // Notifies the stream that the channel is reopened. The stream should
   // reset variables such as |mDidNotifyDataEnded|.
--- a/dom/media/platforms/android/AndroidDecoderModule.cpp
+++ b/dom/media/platforms/android/AndroidDecoderModule.cpp
@@ -28,17 +28,17 @@ using namespace mozilla;
 using namespace mozilla::gl;
 using namespace mozilla::java::sdk;
 using media::TimeUnit;
 
 namespace mozilla {
 
 mozilla::LazyLogModule sAndroidDecoderModuleLog("AndroidDecoderModule");
 
-static const char*
+const char*
 TranslateMimeType(const nsACString& aMimeType)
 {
   if (VPXDecoder::IsVPX(aMimeType, VPXDecoder::VP8)) {
     return "video/x-vnd.on2.vp8";
   } else if (VPXDecoder::IsVPX(aMimeType, VPXDecoder::VP9)) {
     return "video/x-vnd.on2.vp9";
   }
   return PromiseFlatCString(aMimeType).get();
@@ -52,76 +52,16 @@ GetFeatureStatus(int32_t aFeature)
   nsCString discardFailureId;
   if (!gfxInfo || NS_FAILED(gfxInfo->GetFeatureStatus(
                     aFeature, discardFailureId, &status))) {
     return false;
   }
   return status == nsIGfxInfo::FEATURE_STATUS_OK;
 };
 
-CryptoInfo::LocalRef
-GetCryptoInfoFromSample(const MediaRawData* aSample)
-{
-  auto& cryptoObj = aSample->mCrypto;
-
-  if (!cryptoObj.mValid) {
-    return nullptr;
-  }
-
-  CryptoInfo::LocalRef cryptoInfo;
-  nsresult rv = CryptoInfo::New(&cryptoInfo);
-  NS_ENSURE_SUCCESS(rv, nullptr);
-
-  uint32_t numSubSamples = std::min<uint32_t>(
-    cryptoObj.mPlainSizes.Length(), cryptoObj.mEncryptedSizes.Length());
-
-  uint32_t totalSubSamplesSize = 0;
-  for (auto& size : cryptoObj.mEncryptedSizes) {
-    totalSubSamplesSize += size;
-  }
-
-  // mPlainSizes is uint16_t, need to transform to uint32_t first.
-  nsTArray<uint32_t> plainSizes;
-  for (auto& size : cryptoObj.mPlainSizes) {
-    totalSubSamplesSize += size;
-    plainSizes.AppendElement(size);
-  }
-
-  uint32_t codecSpecificDataSize = aSample->Size() - totalSubSamplesSize;
-  // Size of codec specific data("CSD") for Android MediaCodec usage should be
-  // included in the 1st plain size.
-  plainSizes[0] += codecSpecificDataSize;
-
-  static const int kExpectedIVLength = 16;
-  auto tempIV(cryptoObj.mIV);
-  auto tempIVLength = tempIV.Length();
-  MOZ_ASSERT(tempIVLength <= kExpectedIVLength);
-  for (size_t i = tempIVLength; i < kExpectedIVLength; i++) {
-    // Padding with 0
-    tempIV.AppendElement(0);
-  }
-
-  auto numBytesOfPlainData = mozilla::jni::IntArray::New(
-                              reinterpret_cast<int32_t*>(&plainSizes[0]),
-                              plainSizes.Length());
-
-  auto numBytesOfEncryptedData = mozilla::jni::IntArray::New(
-    reinterpret_cast<const int32_t*>(&cryptoObj.mEncryptedSizes[0]),
-    cryptoObj.mEncryptedSizes.Length());
-  auto iv = mozilla::jni::ByteArray::New(reinterpret_cast<int8_t*>(&tempIV[0]),
-                                         tempIV.Length());
-  auto keyId = mozilla::jni::ByteArray::New(
-    reinterpret_cast<const int8_t*>(&cryptoObj.mKeyId[0]),
-    cryptoObj.mKeyId.Length());
-  cryptoInfo->Set(numSubSamples, numBytesOfPlainData, numBytesOfEncryptedData,
-                  keyId, iv, MediaCodec::CRYPTO_MODE_AES_CTR);
-
-  return cryptoInfo;
-}
-
 AndroidDecoderModule::AndroidDecoderModule(CDMProxy* aProxy)
 {
   mProxy = static_cast<MediaDrmCDMProxy*>(aProxy);
 }
 
 bool
 AndroidDecoderModule::SupportsMimeType(
   const nsACString& aMimeType,
--- a/dom/media/platforms/android/AndroidDecoderModule.h
+++ b/dom/media/platforms/android/AndroidDecoderModule.h
@@ -26,11 +26,14 @@ public:
 
 private:
   virtual ~AndroidDecoderModule() { }
   RefPtr<MediaDrmCDMProxy> mProxy;
 };
 
 extern LazyLogModule sAndroidDecoderModuleLog;
 
+const char*
+TranslateMimeType(const nsACString& aMimeType);
+
 } // namespace mozilla
 
 #endif
--- a/dom/media/platforms/android/RemoteDataDecoder.cpp
+++ b/dom/media/platforms/android/RemoteDataDecoder.cpp
@@ -538,16 +538,79 @@ RemoteDataDecoder::ProcessShutdown()
     mJavaCallbacks = nullptr;
   }
 
   mFormat = nullptr;
 
   return ShutdownPromise::CreateAndResolve(true, __func__);
 }
 
+static CryptoInfo::LocalRef
+GetCryptoInfoFromSample(const MediaRawData* aSample)
+{
+  auto& cryptoObj = aSample->mCrypto;
+
+  if (!cryptoObj.mValid) {
+    return nullptr;
+  }
+
+  CryptoInfo::LocalRef cryptoInfo;
+  nsresult rv = CryptoInfo::New(&cryptoInfo);
+  NS_ENSURE_SUCCESS(rv, nullptr);
+
+  uint32_t numSubSamples = std::min<uint32_t>(
+    cryptoObj.mPlainSizes.Length(), cryptoObj.mEncryptedSizes.Length());
+
+  uint32_t totalSubSamplesSize = 0;
+  for (auto& size : cryptoObj.mEncryptedSizes) {
+    totalSubSamplesSize += size;
+  }
+
+  // mPlainSizes is uint16_t, need to transform to uint32_t first.
+  nsTArray<uint32_t> plainSizes;
+  for (auto& size : cryptoObj.mPlainSizes) {
+    totalSubSamplesSize += size;
+    plainSizes.AppendElement(size);
+  }
+
+  uint32_t codecSpecificDataSize = aSample->Size() - totalSubSamplesSize;
+  // Size of codec specific data("CSD") for Android MediaCodec usage should be
+  // included in the 1st plain size.
+  plainSizes[0] += codecSpecificDataSize;
+
+  static const int kExpectedIVLength = 16;
+  auto tempIV(cryptoObj.mIV);
+  auto tempIVLength = tempIV.Length();
+  MOZ_ASSERT(tempIVLength <= kExpectedIVLength);
+  for (size_t i = tempIVLength; i < kExpectedIVLength; i++) {
+    // Padding with 0
+    tempIV.AppendElement(0);
+  }
+
+  auto numBytesOfPlainData = mozilla::jni::IntArray::New(
+    reinterpret_cast<int32_t*>(&plainSizes[0]), plainSizes.Length());
+
+  auto numBytesOfEncryptedData = mozilla::jni::IntArray::New(
+    reinterpret_cast<const int32_t*>(&cryptoObj.mEncryptedSizes[0]),
+    cryptoObj.mEncryptedSizes.Length());
+  auto iv = mozilla::jni::ByteArray::New(reinterpret_cast<int8_t*>(&tempIV[0]),
+                                         tempIV.Length());
+  auto keyId = mozilla::jni::ByteArray::New(
+    reinterpret_cast<const int8_t*>(&cryptoObj.mKeyId[0]),
+    cryptoObj.mKeyId.Length());
+  cryptoInfo->Set(numSubSamples,
+                  numBytesOfPlainData,
+                  numBytesOfEncryptedData,
+                  keyId,
+                  iv,
+                  MediaCodec::CRYPTO_MODE_AES_CTR);
+
+  return cryptoInfo;
+}
+
 RefPtr<MediaDataDecoder::DecodePromise>
 RemoteDataDecoder::Decode(MediaRawData* aSample)
 {
   MOZ_ASSERT(aSample != nullptr);
 
   RefPtr<RemoteDataDecoder> self = this;
   RefPtr<MediaRawData> sample = aSample;
   return InvokeAsync(mTaskQueue, __func__, [self, sample]() {
--- a/dom/webidl/Document.webidl
+++ b/dom/webidl/Document.webidl
@@ -50,19 +50,19 @@ interface Document : Node {
   HTMLCollection getElementsByTagName(DOMString localName);
   [Pure, Throws]
   HTMLCollection getElementsByTagNameNS(DOMString? namespace, DOMString localName);
   [Pure]
   HTMLCollection getElementsByClassName(DOMString classNames);
   [Pure]
   Element? getElementById(DOMString elementId);
 
-  [NewObject, Throws]
+  [CEReactions, NewObject, Throws]
   Element createElement(DOMString localName, optional (ElementCreationOptions or DOMString) options);
-  [NewObject, Throws]
+  [CEReactions, NewObject, Throws]
   Element createElementNS(DOMString? namespace, DOMString qualifiedName, optional (ElementCreationOptions or DOMString) options);
   [NewObject]
   DocumentFragment createDocumentFragment();
   [NewObject]
   Text createTextNode(DOMString data);
   [NewObject]
   Comment createComment(DOMString data);
   [NewObject, Throws]
--- a/intl/locale/LocaleService.cpp
+++ b/intl/locale/LocaleService.cpp
@@ -90,16 +90,26 @@ ReadRequestedLocales(nsTArray<nsCString>
   // Otherwise, we'll try to get the requested locale from the prefs.
   if (!NS_SUCCEEDED(Preferences::GetCString(SELECTED_LOCALE_PREF, locale))) {
     return false;
   }
 
   // At the moment we just take a single locale, but in the future
   // we'll want to allow user to specify a list of requested locales.
   aRetVal.AppendElement(locale);
+
+  // en-US is a LastResort locale. LastResort locale is a fallback locale
+  // for the requested locale chain. In the future we'll want to make the
+  // fallback chain differ per-locale. For now, it'll always fallback on en-US.
+  //
+  // Notice: This is not the same as DefaultLocale,
+  // which follows the default locale the build is in.
+  if (!locale.Equals("en-US")) {
+    aRetVal.AppendElement("en-US");
+  }
   return true;
 }
 
 static bool
 ReadAvailableLocales(nsTArray<nsCString>& aRetVal)
 {
   nsCOMPtr<nsIToolkitChromeRegistry> cr =
     mozilla::services::GetToolkitChromeRegistryService();
@@ -288,25 +298,16 @@ LocaleService::AssignRequestedLocales(co
 }
 
 bool
 LocaleService::GetRequestedLocales(nsTArray<nsCString>& aRetVal)
 {
   if (mRequestedLocales.IsEmpty()) {
     ReadRequestedLocales(mRequestedLocales);
 
-    // en-US is a LastResort locale. LastResort locale is a fallback locale
-    // for the requested locale chain. In the future we'll want to make the
-    // fallback chain differ per-locale. For now, it'll always fallback on en-US.
-    //
-    // Notice: This is not the same as DefaultLocale,
-    // which follows the default locale the build is in.
-    if (!mRequestedLocales.Contains("en-US")) {
-      mRequestedLocales.AppendElement("en-US");
-    }
   }
 
   aRetVal = mRequestedLocales;
   return true;
 }
 
 bool
 LocaleService::GetAvailableLocales(nsTArray<nsCString>& aRetVal)
--- a/layout/generic/nsFrame.cpp
+++ b/layout/generic/nsFrame.cpp
@@ -3537,17 +3537,16 @@ nsIFrame::BuildDisplayListForChild(nsDis
                            (aFlags & DISPLAY_CHILD_FORCE_STACKING_CONTEXT);
 
   if (isVisuallyAtomic || isPositioned || (!isSVG && disp->IsFloating(child)) ||
       ((effects->mClipFlags & NS_STYLE_CLIP_RECT) &&
        IsSVGContentWithCSSClip(child)) ||
        disp->mIsolation != NS_STYLE_ISOLATION_AUTO ||
        (disp->mWillChangeBitField & NS_STYLE_WILL_CHANGE_STACKING_CONTEXT) ||
       (aFlags & DISPLAY_CHILD_FORCE_STACKING_CONTEXT)) {
-    // If you change this, also change IsPseudoStackingContextFromStyle()
     pseudoStackingContext = true;
     awayFromCommonPath = true;
   }
   NS_ASSERTION(!isStackingContext || pseudoStackingContext,
                "Stacking contexts must also be pseudo-stacking-contexts");
 
   nsDisplayListBuilder::AutoBuildingDisplayList
     buildingForChild(aBuilder, child, visible, dirty, pseudoStackingContext);
@@ -10851,29 +10850,16 @@ nsIFrame::DestroyWebRenderUserDataTable(
 {
   for (auto iter = aTable->Iter(); !iter.Done(); iter.Next()) {
     iter.UserData()->RemoveFromTable();
   }
   delete aTable;
 }
 
 bool
-nsIFrame::IsPseudoStackingContextFromStyle() {
-  // If you change this, also change the computation of pseudoStackingContext
-  // in BuildDisplayListForChild()
-  if (StyleEffects()->mOpacity != 1.0f) {
-    return true;
-  }
-  const nsStyleDisplay* disp = StyleDisplay();
-  return disp->IsAbsPosContainingBlock(this) ||
-         disp->IsFloating(this) ||
-         (disp->mWillChangeBitField & NS_STYLE_WILL_CHANGE_STACKING_CONTEXT);
-}
-
-bool
 nsIFrame::IsVisuallyAtomic(EffectSet* aEffectSet,
                            const nsStyleDisplay* aStyleDisplay,
                            const nsStyleEffects* aStyleEffects) {
   return HasOpacity(aEffectSet) ||
          IsTransformed(aStyleDisplay) ||
          // strictly speaking, 'perspective' doesn't require visual atomicity,
          // but the spec says it acts like the rest of these
          aStyleDisplay->mChildPerspective.GetUnit() == eStyleUnit_Coord ||
--- a/layout/generic/nsIFrame.h
+++ b/layout/generic/nsIFrame.h
@@ -3489,24 +3489,16 @@ public:
 
   /**
    * Overridable function to determine whether this frame should be considered
    * "in" the given non-null aSelection for visibility purposes.
    */
   virtual bool IsVisibleInSelection(nsISelection* aSelection);
 
   /**
-   * Determines whether this frame is a pseudo stacking context, looking
-   * only as style --- i.e., assuming that it's in-flow and not a replaced
-   * element and not an SVG element.
-   * XXX maybe check IsTransformed()?
-   */
-  bool IsPseudoStackingContextFromStyle();
-
-  /**
    * Determines if this frame has a container effect that requires
    * it to paint as a visually atomic unit.
    */
   bool IsVisuallyAtomic(mozilla::EffectSet* aEffectSet,
                         const nsStyleDisplay* aStyleDisplay,
                         const nsStyleEffects* aStyleEffects);
 
   /**
--- a/layout/generic/nsImageFrame.cpp
+++ b/layout/generic/nsImageFrame.cpp
@@ -1438,17 +1438,18 @@ nsImageFrame::DisplayAltFeedback(gfxCont
     WritingMode wm = GetWritingMode();
     bool flushRight =
       (!wm.IsVertical() && !wm.IsBidiLTR()) || wm.IsVerticalRL();
 
     // If the icon in question is loaded, draw it.
     uint32_t imageStatus = 0;
     if (request)
       request->GetImageStatus(&imageStatus);
-    if (imageStatus & imgIRequest::STATUS_LOAD_COMPLETE) {
+    if (imageStatus & imgIRequest::STATUS_LOAD_COMPLETE &&
+        !(imageStatus & imgIRequest::STATUS_ERROR)) {
       nsCOMPtr<imgIContainer> imgCon;
       request->GetImage(getter_AddRefs(imgCon));
       MOZ_ASSERT(imgCon, "Load complete, but no image container?");
       nsRect dest(flushRight ? inner.XMost() - size : inner.x,
                   inner.y, size, size);
       result = nsLayoutUtils::DrawSingleImage(aRenderingContext, PresContext(), imgCon,
         nsLayoutUtils::GetSamplingFilterForFrame(this), dest, aDirtyRect,
         /* no SVGImageContext */ Nothing(), aFlags);
--- a/layout/style/nsCSSRuleProcessor.cpp
+++ b/layout/style/nsCSSRuleProcessor.cpp
@@ -1154,16 +1154,40 @@ nsCSSRuleProcessor::InitSystemMetrics()
   }
 
   rv = LookAndFeel::GetInt(LookAndFeel::eIntID_SwipeAnimationEnabled,
                            &metricResult);
   if (NS_SUCCEEDED(rv) && metricResult) {
     sSystemMetrics->AppendElement(nsGkAtoms::swipe_animation_enabled);
   }
 
+  rv = LookAndFeel::GetInt(LookAndFeel::eIntID_GTKCSDAvailable,
+                           &metricResult);
+  if (NS_SUCCEEDED(rv) && metricResult) {
+    sSystemMetrics->AppendElement(nsGkAtoms::gtk_csd_available);
+  }
+
+  rv = LookAndFeel::GetInt(LookAndFeel::eIntID_GTKCSDMinimizeButton,
+                           &metricResult);
+  if (NS_SUCCEEDED(rv) && metricResult) {
+    sSystemMetrics->AppendElement(nsGkAtoms::gtk_csd_minimize_button);
+  }
+
+  rv = LookAndFeel::GetInt(LookAndFeel::eIntID_GTKCSDMaximizeButton,
+                           &metricResult);
+  if (NS_SUCCEEDED(rv) && metricResult) {
+    sSystemMetrics->AppendElement(nsGkAtoms::gtk_csd_maximize_button);
+  }
+
+  rv = LookAndFeel::GetInt(LookAndFeel::eIntID_GTKCSDCloseButton,
+                           &metricResult);
+  if (NS_SUCCEEDED(rv) && metricResult) {
+    sSystemMetrics->AppendElement(nsGkAtoms::gtk_csd_close_button);
+  }
+
 #ifdef XP_WIN
   if (NS_SUCCEEDED(
         LookAndFeel::GetInt(LookAndFeel::eIntID_WindowsThemeIdentifier,
                             &metricResult))) {
     nsCSSRuleProcessor::SetWindowsThemeIdentifier(static_cast<uint8_t>(metricResult));
     switch(metricResult) {
       case LookAndFeel::eWindowsTheme_Aero:
         sSystemMetrics->AppendElement(nsGkAtoms::windows_theme_aero);
--- a/layout/style/nsMediaFeatures.cpp
+++ b/layout/style/nsMediaFeatures.cpp
@@ -790,16 +790,52 @@ nsMediaFeatures::features[] = {
     &nsGkAtoms::_moz_swipe_animation_enabled,
     nsMediaFeature::eMinMaxNotAllowed,
     nsMediaFeature::eBoolInteger,
     nsMediaFeature::eUserAgentAndChromeOnly,
     { &nsGkAtoms::swipe_animation_enabled },
     GetSystemMetric
   },
 
+  {
+    &nsGkAtoms::_moz_gtk_csd_available,
+    nsMediaFeature::eMinMaxNotAllowed,
+    nsMediaFeature::eBoolInteger,
+    nsMediaFeature::eUserAgentAndChromeOnly,
+    { &nsGkAtoms::gtk_csd_available },
+    GetSystemMetric
+  },
+
+  {
+    &nsGkAtoms::_moz_gtk_csd_minimize_button,
+    nsMediaFeature::eMinMaxNotAllowed,
+    nsMediaFeature::eBoolInteger,
+    nsMediaFeature::eUserAgentAndChromeOnly,
+    { &nsGkAtoms::gtk_csd_minimize_button },
+    GetSystemMetric
+  },
+
+  {
+    &nsGkAtoms::_moz_gtk_csd_maximize_button,
+    nsMediaFeature::eMinMaxNotAllowed,
+    nsMediaFeature::eBoolInteger,
+    nsMediaFeature::eUserAgentAndChromeOnly,
+    { &nsGkAtoms::gtk_csd_maximize_button },
+    GetSystemMetric
+  },
+
+  {
+    &nsGkAtoms::_moz_gtk_csd_close_button,
+    nsMediaFeature::eMinMaxNotAllowed,
+    nsMediaFeature::eBoolInteger,
+    nsMediaFeature::eUserAgentAndChromeOnly,
+    { &nsGkAtoms::gtk_csd_close_button },
+    GetSystemMetric
+  },
+
   // Internal -moz-is-glyph media feature: applies only inside SVG glyphs.
   // Internal because it is really only useful in the user agent anyway
   //  and therefore not worth standardizing.
   {
     &nsGkAtoms::_moz_is_glyph,
     nsMediaFeature::eMinMaxNotAllowed,
     nsMediaFeature::eBoolInteger,
     nsMediaFeature::eUserAgentAndChromeOnly,
--- a/layout/style/test/chrome/bug418986-2.js
+++ b/layout/style/test/chrome/bug418986-2.js
@@ -46,16 +46,20 @@ var suppressed_toggles = [
   "-moz-scrollbar-end-forward",
   "-moz-scrollbar-start-backward",
   "-moz-scrollbar-start-forward",
   "-moz-scrollbar-thumb-proportional",
   "-moz-touch-enabled",
   "-moz-windows-compositor",
   "-moz-windows-default-theme",
   "-moz-windows-glass",
+  "-moz-gtk-csd-available",
+  "-moz-gtk-csd-minimize-button",
+  "-moz-gtk-csd-maximize-button",
+  "-moz-gtk-csd-close-button",
 ];
 
 var toggles_enabled_in_content = [
   "-moz-touch-enabled",
 ];
 
 // Possible values for '-moz-os-version'
 var windows_versions = [
--- a/layout/style/test/test_media_queries.html
+++ b/layout/style/test/test_media_queries.html
@@ -642,48 +642,60 @@ function run() {
   expression_should_not_be_parseable("-moz-windows-default-theme");
   expression_should_not_be_parseable("-moz-mac-graphite-theme");
   expression_should_not_be_parseable("-moz-mac-yosemite-theme");
   expression_should_not_be_parseable("-moz-windows-accent-color-in-titlebar");
   expression_should_not_be_parseable("-moz-windows-compositor");
   expression_should_not_be_parseable("-moz-windows-classic");
   expression_should_not_be_parseable("-moz-windows-glass");
   expression_should_not_be_parseable("-moz-swipe-animation-enabled");
+  expression_should_not_be_parseable("-moz-gtk-csd-available");
+  expression_should_not_be_parseable("-moz-gtk-csd-minimize-button");
+  expression_should_not_be_parseable("-moz-gtk-csd-maximize-button");
+  expression_should_not_be_parseable("-moz-gtk-csd-close-button");
   expression_should_be_parseable("-moz-touch-enabled");
 
   expression_should_not_be_parseable("-moz-scrollbar-start-backward: 0");
   expression_should_not_be_parseable("-moz-scrollbar-start-forward: 0");
   expression_should_not_be_parseable("-moz-scrollbar-end-backward: 0");
   expression_should_not_be_parseable("-moz-scrollbar-end-forward: 0");
   expression_should_not_be_parseable("-moz-scrollbar-thumb-proportional: 0");
   expression_should_not_be_parseable("-moz-overlay-scrollbars: 0");
   expression_should_not_be_parseable("-moz-windows-default-theme: 0");
   expression_should_not_be_parseable("-moz-mac-graphite-theme: 0");
   expression_should_not_be_parseable("-moz-mac-yosemite-theme: 0");
   expression_should_not_be_parseable("-moz-windows-accent-color-in-titlebar: 0");
   expression_should_not_be_parseable("-moz-windows-compositor: 0");
   expression_should_not_be_parseable("-moz-windows-classic: 0");
   expression_should_not_be_parseable("-moz-windows-glass: 0");
   expression_should_not_be_parseable("-moz-swipe-animation-enabled: 0");
+  expression_should_not_be_parseable("-moz-gtk-csd-available: 0");
+  expression_should_not_be_parseable("-moz-gtk-csd-minimize-button: 0");
+  expression_should_not_be_parseable("-moz-gtk-csd-maximize-button: 0");
+  expression_should_not_be_parseable("-moz-gtk-csd-close-button: 0");
   expression_should_be_parseable("-moz-touch-enabled: 0");
 
   expression_should_not_be_parseable("-moz-scrollbar-start-backward: 1");
   expression_should_not_be_parseable("-moz-scrollbar-start-forward: 1");
   expression_should_not_be_parseable("-moz-scrollbar-end-backward: 1");
   expression_should_not_be_parseable("-moz-scrollbar-end-forward: 1");
   expression_should_not_be_parseable("-moz-scrollbar-thumb-proportional: 1");
   expression_should_not_be_parseable("-moz-overlay-scrollbars: 1");
   expression_should_not_be_parseable("-moz-windows-default-theme: 1");
   expression_should_not_be_parseable("-moz-mac-graphite-theme: 1");
   expression_should_not_be_parseable("-moz-mac-yosemite-theme: 1");
   expression_should_not_be_parseable("-moz-windows-accent-color-in-titlebar: 1");
   expression_should_not_be_parseable("-moz-windows-compositor: 1");
   expression_should_not_be_parseable("-moz-windows-classic: 1");
   expression_should_not_be_parseable("-moz-windows-glass: 1");
   expression_should_not_be_parseable("-moz-swipe-animation-enabled: 1");
+  expression_should_not_be_parseable("-moz-gtk-csd-available: 1");
+  expression_should_not_be_parseable("-moz-gtk-csd-minimize-button: 1");
+  expression_should_not_be_parseable("-moz-gtk-csd-maximize-button: 1");
+  expression_should_not_be_parseable("-moz-gtk-csd-close-button: 1");
   expression_should_be_parseable("-moz-touch-enabled: 1");
 
   expression_should_not_be_parseable("-moz-scrollbar-start-backward: -1");
   expression_should_not_be_parseable("-moz-scrollbar-start-forward: -1");
   expression_should_not_be_parseable("-moz-scrollbar-end-backward: -1");
   expression_should_not_be_parseable("-moz-scrollbar-end-forward: -1");
   expression_should_not_be_parseable("-moz-scrollbar-thumb-proportional: -1");
   expression_should_not_be_parseable("-moz-overlay-scrollbars: -1");
@@ -691,32 +703,40 @@ function run() {
   expression_should_not_be_parseable("-moz-mac-graphite-theme: -1");
   expression_should_not_be_parseable("-moz-mac-yosemite-theme: -1");
   expression_should_not_be_parseable("-moz-windows-accent-color-in-titlebar: -1");
   expression_should_not_be_parseable("-moz-windows-compositor: -1");
   expression_should_not_be_parseable("-moz-windows-classic: -1");
   expression_should_not_be_parseable("-moz-windows-glass: -1");
   expression_should_not_be_parseable("-moz-touch-enabled: -1");
   expression_should_not_be_parseable("-moz-swipe-animation-enabled: -1");
+  expression_should_not_be_parseable("-moz-gtk-csd-available: -1");
+  expression_should_not_be_parseable("-moz-gtk-csd-minimize-button: -1");
+  expression_should_not_be_parseable("-moz-gtk-csd-maximize-button: -1");
+  expression_should_not_be_parseable("-moz-gtk-csd-close-button: -1");
 
   expression_should_not_be_parseable("-moz-scrollbar-start-backward: true");
   expression_should_not_be_parseable("-moz-scrollbar-start-forward: true");
   expression_should_not_be_parseable("-moz-scrollbar-end-backward: true");
   expression_should_not_be_parseable("-moz-scrollbar-end-forward: true");
   expression_should_not_be_parseable("-moz-scrollbar-thumb-proportional: true");
   expression_should_not_be_parseable("-moz-overlay-scrollbars: true");
   expression_should_not_be_parseable("-moz-windows-default-theme: true");
   expression_should_not_be_parseable("-moz-mac-graphite-theme: true");
   expression_should_not_be_parseable("-moz-mac-yosemite-theme: true");
   expression_should_not_be_parseable("-moz-windows-accent-color-in-titlebar: true");
   expression_should_not_be_parseable("-moz-windows-compositor: true");
   expression_should_not_be_parseable("-moz-windows-classic: true");
   expression_should_not_be_parseable("-moz-windows-glass: true");
   expression_should_not_be_parseable("-moz-touch-enabled: true");
   expression_should_not_be_parseable("-moz-swipe-animation-enabled: true");
+  expression_should_not_be_parseable("-moz-gtk-csd-available: true");
+  expression_should_not_be_parseable("-moz-gtk-csd-minimize-button: true");
+  expression_should_not_be_parseable("-moz-gtk-csd-maximize-button: true");
+  expression_should_not_be_parseable("-moz-gtk-csd-close-button: true");
 
   // windows theme media queries
   expression_should_not_be_parseable("-moz-windows-theme: aero");
   expression_should_not_be_parseable("-moz-windows-theme: aero-lite");
   expression_should_not_be_parseable("-moz-windows-theme: luna-blue");
   expression_should_not_be_parseable("-moz-windows-theme: luna-olive");
   expression_should_not_be_parseable("-moz-windows-theme: luna-silver");
   expression_should_not_be_parseable("-moz-windows-theme: royale");
--- a/media/webrtc/signaling/src/peerconnection/PeerConnectionImpl.cpp
+++ b/media/webrtc/signaling/src/peerconnection/PeerConnectionImpl.cpp
@@ -824,17 +824,16 @@ class CompareCodecPriority {
 
   private:
     std::string mPreferredCodec;
 };
 
 class ConfigureCodec {
   public:
     explicit ConfigureCodec(nsCOMPtr<nsIPrefBranch>& branch) :
-      mHardwareH264Enabled(false),
       mHardwareH264Supported(false),
       mSoftwareH264Enabled(false),
       mH264Enabled(false),
       mVP9Enabled(true),
       mVP9Preferred(false),
       mH264Level(13), // minimum suggested for WebRTC spec
       mH264MaxBr(0), // Unlimited
       mH264MaxMbps(0), // Unlimited
@@ -958,17 +957,16 @@ class ConfigureCodec {
         case SdpMediaSection::kText:
         case SdpMediaSection::kApplication:
         case SdpMediaSection::kMessage:
           {} // Nothing to configure for these.
       }
     }
 
   private:
-    bool mHardwareH264Enabled;
     bool mHardwareH264Supported;
     bool mSoftwareH264Enabled;
     bool mH264Enabled;
     bool mVP9Enabled;
     bool mVP9Preferred;
     int32_t mH264Level;
     int32_t mH264MaxBr;
     int32_t mH264MaxMbps;
--- a/netwerk/protocol/http/nsHttpChannel.cpp
+++ b/netwerk/protocol/http/nsHttpChannel.cpp
@@ -2259,27 +2259,26 @@ nsHttpChannel::ProcessAltService()
 nsresult
 nsHttpChannel::ProcessResponse()
 {
     uint32_t httpStatus = mResponseHead->Status();
 
     LOG(("nsHttpChannel::ProcessResponse [this=%p httpStatus=%u]\n",
         this, httpStatus));
 
-    // do some telemetry
-    if (gHttpHandler->IsTelemetryEnabled()) {
-        // Gather data on whether the transaction and page (if this is
-        // the initial page load) is being loaded with SSL.
-        Telemetry::Accumulate(Telemetry::HTTP_TRANSACTION_IS_SSL,
+    // Gather data on whether the transaction and page (if this is
+    // the initial page load) is being loaded with SSL.
+    Telemetry::Accumulate(Telemetry::HTTP_TRANSACTION_IS_SSL,
+                          mConnectionInfo->EndToEndSSL());
+    if (mLoadFlags & LOAD_INITIAL_DOCUMENT_URI) {
+        Telemetry::Accumulate(Telemetry::HTTP_PAGELOAD_IS_SSL,
                               mConnectionInfo->EndToEndSSL());
-        if (mLoadFlags & LOAD_INITIAL_DOCUMENT_URI) {
-            Telemetry::Accumulate(Telemetry::HTTP_PAGELOAD_IS_SSL,
-                                  mConnectionInfo->EndToEndSSL());
-        }
-
+    }
+
+    if (gHttpHandler->IsTelemetryEnabled()) {
         // how often do we see something like Alt-Svc: "443:quic,p=1"
         nsAutoCString alt_service;
         Unused << mResponseHead->GetHeader(nsHttp::Alternate_Service, alt_service);
         bool saw_quic = (!alt_service.IsEmpty() &&
                          PL_strstr(alt_service.get(), "quic")) ? 1 : 0;
         Telemetry::Accumulate(Telemetry::HTTP_SAW_QUIC_ALT_PROTOCOL, saw_quic);
 
         // Gather data on how many URLS get redirected
--- a/python/mozbuild/mozbuild/configure/__init__.py
+++ b/python/mozbuild/mozbuild/configure/__init__.py
@@ -1,14 +1,15 @@
 # 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/.
 
 from __future__ import absolute_import, print_function, unicode_literals
 
+import __builtin__
 import inspect
 import logging
 import os
 import re
 import sys
 import types
 from collections import OrderedDict
 from contextlib import contextmanager
@@ -247,17 +248,17 @@ class ConfigureSandbox(dict):
         sandbox = ConfigureSandbox(config)
         sandbox.run(path)
         do_stuff(config)
     """
 
     # The default set of builtins. We expose unicode as str to make sandboxed
     # files more python3-ready.
     BUILTINS = ReadOnlyDict({
-        b: __builtins__[b]
+        b: getattr(__builtin__, b)
         for b in ('None', 'False', 'True', 'int', 'bool', 'any', 'all', 'len',
                   'list', 'tuple', 'set', 'dict', 'isinstance', 'getattr',
                   'hasattr', 'enumerate', 'range', 'zip')
     }, __import__=forbidden_import, str=unicode)
 
     # Expose a limited set of functions from os.path
     OS = ReadOnlyNamespace(path=ReadOnlyNamespace(**{
         k: getattr(mozpath, k, getattr(os.path, k))
--- a/python/mozbuild/mozbuild/frontend/emitter.py
+++ b/python/mozbuild/mozbuild/frontend/emitter.py
@@ -127,16 +127,17 @@ class TreeMetadataEmitter(LoggingMixin):
             if isinstance(k, unicode):
                 k = k.encode('ascii')
             self.info[k] = v
 
         self._libs = OrderedDefaultDict(list)
         self._binaries = OrderedDict()
         self._compile_dirs = set()
         self._host_compile_dirs = set()
+        self._rust_compile_dirs = set()
         self._compile_flags = dict()
         self._linkage = []
         self._static_linking_shared = set()
         self._crate_verified_local = set()
         self._crate_directories = dict()
 
         # Keep track of external paths (third party build systems), starting
         # from what we run a subconfigure in. We'll eliminate some directories
@@ -769,19 +770,23 @@ class TreeMetadataEmitter(LoggingMixin):
         # Only emit sources if we have linkables defined in the same context.
         # Note the linkables are not emitted in this function, but much later,
         # after aggregation (because of e.g. USE_LIBS processing).
         if not (linkables or host_linkables):
             return
 
         # Avoid emitting compile flags for directories only containing rust
         # libraries. Emitted compile flags are only relevant to C/C++ sources
-        # for the time being.
+        # for the time being, however ldflags must be emitted for the benefit
+        # of cargo.
         if not all(isinstance(l, (RustLibrary)) for l in linkables):
             self._compile_dirs.add(context.objdir)
+        elif linkables:
+            self._rust_compile_dirs.add(context.objdir)
+
         if host_linkables and not all(isinstance(l, HostRustLibrary) for l in host_linkables):
             self._host_compile_dirs.add(context.objdir)
             # TODO: objdirs with only host things in them shouldn't need target
             # flags, but there's at least one Makefile.in (in
             # build/unix/elfhack) that relies on the value of LDFLAGS being
             # passed to one-off rules.
             self._compile_dirs.add(context.objdir)
 
@@ -1196,16 +1201,19 @@ class TreeMetadataEmitter(LoggingMixin):
             yield AndroidExtraPackages(context, android_extra_packages)
 
         if passthru.variables:
             yield passthru
 
         if context.objdir in self._compile_dirs:
             self._compile_flags[context.objdir] = computed_flags
             yield computed_link_flags
+        elif context.objdir in self._rust_compile_dirs:
+            yield computed_link_flags
+
         if context.objdir in self._host_compile_dirs:
             yield computed_host_flags
 
 
     def _create_substitution(self, cls, context, path):
         sub = cls(context)
         sub.input_path = '%s.in' % path.full_path
         sub.output_path = path.translated
--- a/python/mozbuild/mozbuild/test/frontend/test_emitter.py
+++ b/python/mozbuild/mozbuild/test/frontend/test_emitter.py
@@ -1260,18 +1260,18 @@ class TestEmitterBasic(unittest.TestCase
             self.read_topsrcdir(reader)
 
     def test_rust_library_dash_folding(self):
         '''Test that on-disk names of RustLibrary objects convert dashes to underscores.'''
         reader = self.reader('rust-library-dash-folding',
                              extra_substs=dict(RUST_TARGET='i686-pc-windows-msvc'))
         objs = self.read_topsrcdir(reader)
 
-        self.assertEqual(len(objs), 1)
-        lib = objs[0]
+        ldflags, lib = objs
+        self.assertIsInstance(ldflags, ComputedFlags)
         self.assertIsInstance(lib, RustLibrary)
         self.assertRegexpMatches(lib.lib_name, "random_crate")
         self.assertRegexpMatches(lib.import_name, "random_crate")
         self.assertRegexpMatches(lib.basename, "random-crate")
 
     def test_multiple_rust_libraries(self):
         '''Test that linking multiple Rust libraries throws an error'''
         reader = self.reader('multiple-rust-libraries',
@@ -1280,18 +1280,18 @@ class TestEmitterBasic(unittest.TestCase
              'Cannot link multiple Rust libraries'):
             self.read_topsrcdir(reader)
 
     def test_rust_library_features(self):
         '''Test that RustLibrary features are correctly emitted.'''
         reader = self.reader('rust-library-features',
                              extra_substs=dict(RUST_TARGET='i686-pc-windows-msvc'))
         objs = self.read_topsrcdir(reader)
-        self.assertEqual(len(objs), 1)
-        lib = objs[0]
+        ldflags, lib = objs
+        self.assertIsInstance(ldflags, ComputedFlags)
         self.assertIsInstance(lib, RustLibrary)
         self.assertEqual(lib.features, ['musthave', 'cantlivewithout'])
 
     def test_rust_library_duplicate_features(self):
         '''Test that duplicate RustLibrary features are rejected.'''
         reader = self.reader('rust-library-duplicate-features')
         with self.assertRaisesRegexp(SandboxValidationError,
              'features for .* should not contain duplicates'):
@@ -1361,18 +1361,19 @@ class TestEmitterBasic(unittest.TestCase
         self.assertRegexpMatches(objs[0].import_name, 'host_lib')
 
     def test_crate_dependency_path_resolution(self):
         '''Test recursive dependencies resolve with the correct paths.'''
         reader = self.reader('crate-dependency-path-resolution',
                              extra_substs=dict(RUST_TARGET='i686-pc-windows-msvc'))
         objs = self.read_topsrcdir(reader)
 
-        self.assertEqual(len(objs), 1)
-        self.assertIsInstance(objs[0], RustLibrary)
+        ldflags, lib = objs
+        self.assertIsInstance(ldflags, ComputedFlags)
+        self.assertIsInstance(lib, RustLibrary)
 
     def test_android_res_dirs(self):
         """Test that ANDROID_RES_DIRS works properly."""
         reader = self.reader('android-res-dirs')
         objs = self.read_topsrcdir(reader)
 
         self.assertEqual(len(objs), 1)
         self.assertIsInstance(objs[0], AndroidResDirs)
--- a/security/sandbox/linux/SandboxFilter.cpp
+++ b/security/sandbox/linux/SandboxFilter.cpp
@@ -10,16 +10,17 @@
 #include "SandboxBrokerClient.h"
 #include "SandboxInfo.h"
 #include "SandboxInternal.h"
 #include "SandboxLogging.h"
 #ifdef MOZ_GMP_SANDBOX
 #include "SandboxOpenedFiles.h"
 #endif
 #include "mozilla/PodOperations.h"
+#include "mozilla/TemplateLib.h"
 #include "mozilla/UniquePtr.h"
 
 #include <errno.h>
 #include <fcntl.h>
 #include <linux/ioctl.h>
 #include <linux/ipc.h>
 #include <linux/net.h>
 #include <linux/prctl.h>
@@ -100,27 +101,30 @@ protected:
   // Convert Unix-style "return -1 and set errno" APIs back into the
   // Linux ABI "return -err" style.
   static intptr_t ConvertError(long rv) {
     return rv < 0 ? -errno : rv;
   }
 
   template<typename... Args>
   static intptr_t DoSyscall(long nr, Args... args) {
+    static_assert(tl::And<(sizeof(Args) <= sizeof(void*))...>::value,
+                  "each syscall arg is at most one word");
     return ConvertError(syscall(nr, args...));
   }
 
 private:
   // Bug 1093893: Translate tkill to tgkill for pthread_kill; fixed in
   // bionic commit 10c8ce59a (in JB and up; API level 16 = Android 4.1).
   // Bug 1376653: musl also needs this, and security-wise it's harmless.
-  static intptr_t TKillCompatTrap(const sandbox::arch_seccomp_data& aArgs,
-                                  void *aux)
+  static intptr_t TKillCompatTrap(ArgsRef aArgs, void *aux)
   {
-    return DoSyscall(__NR_tgkill, getpid(), aArgs.args[0], aArgs.args[1]);
+    auto tid = static_cast<pid_t>(aArgs.args[0]);
+    auto sig = static_cast<int>(aArgs.args[1]);
+    return DoSyscall(__NR_tgkill, getpid(), tid, sig);
   }
 
   static intptr_t SetNoNewPrivsTrap(ArgsRef& aArgs, void* aux) {
     if (gSetSandboxFilter == nullptr) {
       // Called after BroadcastSetThreadSandbox finished, therefore
       // not our doing and not expected.
       return BlockedSyscallTrap(aArgs, nullptr);
     }
@@ -918,23 +922,16 @@ public:
     case __NR_wait4:
 #ifdef __NR_waitpid
     case __NR_waitpid:
 #endif
       // NSPR will start a thread to wait for child processes even if
       // fork() fails; see bug 227246 and bug 1299581.
       return Error(ECHILD);
 
-      // inotify_{add,rm}_watch take filesystem paths.  Pretend the
-      // kernel doesn't support inotify; note that this could make
-      // libgio attempt network connections for FAM.
-    case __NR_inotify_init:
-    case __NR_inotify_init1:
-      return Error(ENOSYS);
-
     case __NR_eventfd2:
       return Allow();
 
 #ifdef __NR_memfd_create
     case __NR_memfd_create:
       return Allow();
 #endif
 
@@ -1050,28 +1047,27 @@ class GMPSandboxPolicy : public SandboxP
     int fd = files->GetDesc(path);
     if (fd < 0) {
       // SandboxOpenedFile::GetDesc already logged about this, if appropriate.
       return -ENOENT;
     }
     return fd;
   }
 
-  static intptr_t SchedTrap(const sandbox::arch_seccomp_data& aArgs,
-                            void* aux)
+  static intptr_t SchedTrap(ArgsRef aArgs, void* aux)
   {
     const pid_t tid = syscall(__NR_gettid);
     if (aArgs.args[0] == static_cast<uint64_t>(tid)) {
       return DoSyscall(aArgs.nr,
                        0,
-                       aArgs.args[1],
-                       aArgs.args[2],
-                       aArgs.args[3],
-                       aArgs.args[4],
-                       aArgs.args[5]);
+                       static_cast<uintptr_t>(aArgs.args[1]),
+                       static_cast<uintptr_t>(aArgs.args[2]),
+                       static_cast<uintptr_t>(aArgs.args[3]),
+                       static_cast<uintptr_t>(aArgs.args[4]),
+                       static_cast<uintptr_t>(aArgs.args[5]));
     }
     SANDBOX_LOG_ERROR("unsupported tid in SchedTrap");
     return BlockedSyscallTrap(aArgs, nullptr);
   }
 
   static intptr_t UnameTrap(const sandbox::arch_seccomp_data& aArgs,
                             void* aux)
   {
@@ -1129,20 +1125,21 @@ public:
 #ifdef MOZ_ASAN
         .ElseIf(advice == MADV_DONTDUMP, Allow())
 #endif
         .Else(InvalidSyscall());
     }
     case __NR_brk:
     CASES_FOR_geteuid:
       return Allow();
+    case __NR_sched_get_priority_min:
+    case __NR_sched_get_priority_max:
+      return Allow();
     case __NR_sched_getparam:
     case __NR_sched_getscheduler:
-    case __NR_sched_get_priority_min:
-    case __NR_sched_get_priority_max:
     case __NR_sched_setscheduler: {
       Arg<pid_t> pid(0);
       return If(pid == 0, Allow())
         .Else(Trap(SchedTrap, nullptr));
     }
 
     // For clock(3) on older glibcs; bug 1304220.
     case __NR_times:
--- a/security/sandbox/linux/SandboxHooks.cpp
+++ b/security/sandbox/linux/SandboxHooks.cpp
@@ -1,21 +1,22 @@
 /* -*- 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 "mozilla/Types.h"
+
 #include <dlfcn.h>
 #include <signal.h>
 #include <errno.h>
-
-#include "mozilla/Types.h"
-
 #include <stdio.h>
 #include <stdlib.h>
+#include <sys/inotify.h>
 
 // Signal number used to enable seccomp on each thread.
 extern int gSeccompTsyncBroadcastSignum;
 
 // This file defines a hook for sigprocmask() and pthread_sigmask().
 // Bug 1176099: some threads block SIGSYS signal which breaks our seccomp-bpf
 // sandbox. To avoid this, we intercept the call and remove SIGSYS.
 //
@@ -65,8 +66,21 @@ sigprocmask(int how, const sigset_t* set
 extern "C" MOZ_EXPORT int
 pthread_sigmask(int how, const sigset_t* set, sigset_t* oldset)
 {
   static auto sRealFunc = (int (*)(int, const sigset_t*, sigset_t*))
     dlsym(RTLD_NEXT, "pthread_sigmask");
 
   return HandleSigset(sRealFunc, how, set, oldset, false);
 }
+
+extern "C" MOZ_EXPORT int
+inotify_init(void)
+{
+  return inotify_init1(0);
+}
+
+extern "C" MOZ_EXPORT int
+inotify_init1(int flags)
+{
+  errno = ENOSYS;
+  return -1;
+}
--- a/services/sync/modules/constants.js
+++ b/services/sync/modules/constants.js
@@ -127,16 +127,17 @@ ENGINE_BATCH_INTERRUPTED:              "
 
 // Ways that a sync can be disabled (messages only to be printed in debug log)
 kSyncMasterPasswordLocked:             "User elected to leave Master Password locked",
 kSyncWeaveDisabled:                    "Weave is disabled",
 kSyncNetworkOffline:                   "Network is offline",
 kSyncBackoffNotMet:                    "Trying to sync before the server said it's okay",
 kFirstSyncChoiceNotMade:               "User has not selected an action for first sync",
 kSyncNotConfigured:                    "Sync is not configured",
+kFirefoxShuttingDown:                  "Firefox is about to shut down",
 
 DEVICE_TYPE_DESKTOP:                   "desktop",
 DEVICE_TYPE_MOBILE:                    "mobile",
 
 SQLITE_MAX_VARIABLE_NUMBER:            999,
 
 })) {
   this[key] = val;
--- a/services/sync/modules/engines.js
+++ b/services/sync/modules/engines.js
@@ -1756,22 +1756,26 @@ SyncEngine.prototype = {
       await this.trackRemainingChanges();
     } finally {
       this._modified.clear();
     }
   },
 
   async _sync() {
     try {
+      Async.checkAppReady();
       await this._syncStartup();
+      Async.checkAppReady();
       Observers.notify("weave:engine:sync:status", "process-incoming");
       await this._processIncoming();
+      Async.checkAppReady();
       Observers.notify("weave:engine:sync:status", "upload-outgoing");
       try {
         await this._uploadOutgoing();
+        Async.checkAppReady();
         await this._syncFinish();
       } catch (ex) {
         if (!ex.status || ex.status != 412) {
           throw ex;
         }
         // a 412 posting just means another client raced - but we don't want
         // to treat that as a sync error - the next sync is almost certain
         // to work.
--- a/services/sync/modules/engines/forms.js
+++ b/services/sync/modules/engines/forms.js
@@ -9,16 +9,17 @@ var Ci = Components.interfaces;
 var Cu = Components.utils;
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://services-sync/engines.js");
 Cu.import("resource://services-sync/record.js");
 Cu.import("resource://services-sync/util.js");
 Cu.import("resource://services-sync/constants.js");
 Cu.import("resource://services-sync/collection_validator.js");
+Cu.import("resource://services-common/async.js");
 Cu.import("resource://gre/modules/Log.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "FormHistory",
                                   "resource://gre/modules/FormHistory.jsm");
 
 const FORMS_TTL = 3 * 365 * 24 * 60 * 60; // Three years in seconds.
 
 this.FormRec = function FormRec(collection, id) {
   CryptoWrapper.call(this, collection, id);
@@ -133,16 +134,17 @@ FormStore.prototype = {
       return;
     }
 
     // Otherwise we must handle the change right now.
     await FormWrapper._update(change);
   },
 
   async applyIncomingBatch(records) {
+    Async.checkAppReady();
     // We collect all the changes to be made then apply them all at once.
     this._changes = [];
     let failures = await Store.prototype.applyIncomingBatch.call(this, records);
     if (this._changes.length) {
       await FormWrapper._update(this._changes);
     }
     delete this._changes;
     return failures;
--- a/services/sync/modules/engines/history.js
+++ b/services/sync/modules/engines/history.js
@@ -177,17 +177,19 @@ HistoryStore.prototype = {
   },
 
   async applyIncomingBatch(records) {
     let failed = [];
 
     // Convert incoming records to mozIPlaceInfo objects. Some records can be
     // ignored or handled directly, so we're rewriting the array in-place.
     let i, k;
+    let maybeYield = Async.jankYielder();
     for (i = 0, k = 0; i < records.length; i++) {
+      await maybeYield();
       let record = records[k] = records[i];
       let shouldApply;
 
       try {
         if (record.deleted) {
           await this.remove(record);
 
           // No further processing needed. Remove it from the list.
--- a/services/sync/modules/resource.js
+++ b/services/sync/modules/resource.js
@@ -168,16 +168,18 @@ Resource.prototype = {
     return channel;
   },
 
   _onProgress(channel) {},
 
   _doRequest(action, data) {
     this._log.trace("In _doRequest.");
     return new Promise((resolve, reject) => {
+      // Don't bother starting a network request if we're shutting down.
+      Async.checkAppReady();
       this._deferred = { resolve, reject };
       let channel = this._createRequest(action);
 
       if ("undefined" != typeof(data))
         this._data = data;
 
       // PUT and POST are treated differently because they have payload data.
       if ("PUT" == action || "POST" == action) {
--- a/services/sync/modules/service.js
+++ b/services/sync/modules/service.js
@@ -1029,17 +1029,18 @@ Sync11Service.prototype = {
    *
    * Note that this function has strong ties to _checkSync: callers
    * of this function should typically use _checkSync to verify that
    * any necessary login took place.
    */
   _shouldLogin: function _shouldLogin() {
     return this.enabled &&
            !Services.io.offline &&
-           !this.isLoggedIn;
+           !this.isLoggedIn &&
+           Async.isAppReady();
   },
 
   /**
    * Determine if a sync should run.
    *
    * @param ignore [optional]
    *        array of reasons to ignore when checking
    *
@@ -1056,16 +1057,18 @@ Sync11Service.prototype = {
       reason = kSyncNetworkOffline;
     else if (this.status.minimumNextSync > Date.now())
       reason = kSyncBackoffNotMet;
     else if ((this.status.login == MASTER_PASSWORD_LOCKED) &&
              Utils.mpLocked())
       reason = kSyncMasterPasswordLocked;
     else if (Svc.Prefs.get("firstSync") == "notReady")
       reason = kFirstSyncChoiceNotMade;
+    else if (!Async.isAppReady())
+      reason = kFirefoxShuttingDown;
 
     if (ignore && ignore.indexOf(reason) != -1)
       return "";
 
     return reason;
   },
 
   async sync(engineNamesToSync) {
--- a/servo/components/script/dom/create.rs
+++ b/servo/components/script/dom/create.rs
@@ -5,17 +5,16 @@
 use dom::bindings::error::{report_pending_exception, throw_dom_exception};
 use dom::bindings::reflector::DomObject;
 use dom::bindings::root::DomRoot;
 use dom::customelementregistry::{is_valid_custom_element_name, upgrade_element};
 use dom::document::Document;
 use dom::element::{CustomElementCreationMode, CustomElementState, Element, ElementCreator};
 use dom::globalscope::GlobalScope;
 use dom::htmlanchorelement::HTMLAnchorElement;
-use dom::htmlappletelement::HTMLAppletElement;
 use dom::htmlareaelement::HTMLAreaElement;
 use dom::htmlaudioelement::HTMLAudioElement;
 use dom::htmlbaseelement::HTMLBaseElement;
 use dom::htmlbodyelement::HTMLBodyElement;
 use dom::htmlbrelement::HTMLBRElement;
 use dom::htmlbuttonelement::HTMLButtonElement;
 use dom::htmlcanvaselement::HTMLCanvasElement;
 use dom::htmldataelement::HTMLDataElement;
@@ -186,22 +185,23 @@ fn create_html_element(name: QualName,
     // Step 7.3
     if is_valid_custom_element_name(&*name.local) || is.is_some() {
         result.set_custom_element_state(CustomElementState::Undefined);
     }
 
     result
 }
 
-pub fn create_native_html_element(name: QualName,
-                                  prefix: Option<Prefix>,
-                                  document: &Document,
-                                  creator: ElementCreator)
-                                  -> DomRoot<Element> {
-    assert!(name.ns == ns!(html));
+pub fn create_native_html_element(
+    name: QualName,
+    prefix: Option<Prefix>,
+    document: &Document,
+    creator: ElementCreator,
+) -> DomRoot<Element> {
+    assert_eq!(name.ns, ns!(html));
 
     macro_rules! make(
         ($ctor:ident) => ({
             let obj = $ctor::new(name.local, prefix, document);
             DomRoot::upcast(obj)
         });
         ($ctor:ident, $($arg:expr),+) => ({
             let obj = $ctor::new(name.local, prefix, document, $($arg),+);
@@ -212,17 +212,16 @@ pub fn create_native_html_element(name: 
     // This is a big match, and the IDs for inline-interned atoms are not very structured.
     // Perhaps we should build a perfect hash from those IDs instead.
     // https://html.spec.whatwg.org/multipage/#elements-in-the-dom
     match name.local {
         local_name!("a")          => make!(HTMLAnchorElement),
         local_name!("abbr")       => make!(HTMLElement),
         local_name!("acronym")    => make!(HTMLElement),
         local_name!("address")    => make!(HTMLElement),
-        local_name!("applet")     => make!(HTMLAppletElement),
         local_name!("area")       => make!(HTMLAreaElement),
         local_name!("article")    => make!(HTMLElement),
         local_name!("aside")      => make!(HTMLElement),
         local_name!("audio")      => make!(HTMLAudioElement),
         local_name!("b")          => make!(HTMLElement),
         local_name!("base")       => make!(HTMLBaseElement),
         local_name!("bdi")        => make!(HTMLElement),
         local_name!("bdo")        => make!(HTMLElement),
--- a/servo/components/script/dom/document.rs
+++ b/servo/components/script/dom/document.rs
@@ -42,17 +42,16 @@ use dom::element::CustomElementCreationM
 use dom::errorevent::ErrorEvent;
 use dom::event::{Event, EventBubbles, EventCancelable, EventDefault, EventStatus};
 use dom::eventtarget::EventTarget;
 use dom::focusevent::FocusEvent;
 use dom::forcetouchevent::ForceTouchEvent;
 use dom::globalscope::GlobalScope;
 use dom::hashchangeevent::HashChangeEvent;
 use dom::htmlanchorelement::HTMLAnchorElement;
-use dom::htmlappletelement::HTMLAppletElement;
 use dom::htmlareaelement::HTMLAreaElement;
 use dom::htmlbaseelement::HTMLBaseElement;
 use dom::htmlbodyelement::HTMLBodyElement;
 use dom::htmlcollection::{CollectionFilter, HTMLCollection};
 use dom::htmlelement::HTMLElement;
 use dom::htmlembedelement::HTMLEmbedElement;
 use dom::htmlformelement::{FormControl, FormControlElementHelpers, HTMLFormElement};
 use dom::htmlheadelement::HTMLHeadElement;
@@ -408,24 +407,16 @@ impl CollectionFilter for ScriptsFilter 
 #[derive(JSTraceable, MallocSizeOf)]
 struct AnchorsFilter;
 impl CollectionFilter for AnchorsFilter {
     fn filter(&self, elem: &Element, _root: &Node) -> bool {
         elem.is::<HTMLAnchorElement>() && elem.has_attribute(&local_name!("href"))
     }
 }
 
-#[derive(JSTraceable, MallocSizeOf)]
-struct AppletsFilter;
-impl CollectionFilter for AppletsFilter {
-    fn filter(&self, elem: &Element, _root: &Node) -> bool {
-        elem.is::<HTMLAppletElement>()
-    }
-}
-
 impl Document {
     #[inline]
     pub fn loader(&self) -> Ref<DocumentLoader> {
         self.loader.borrow()
     }
 
     #[inline]
     pub fn loader_mut(&self) -> RefMut<DocumentLoader> {
@@ -3368,20 +3359,18 @@ impl DocumentMethods for Document {
         self.anchors.or_init(|| {
             let filter = Box::new(AnchorsFilter);
             HTMLCollection::create(&self.window, self.upcast(), filter)
         })
     }
 
     // https://html.spec.whatwg.org/multipage/#dom-document-applets
     fn Applets(&self) -> DomRoot<HTMLCollection> {
-        // FIXME: This should be return OBJECT elements containing applets.
         self.applets.or_init(|| {
-            let filter = Box::new(AppletsFilter);
-            HTMLCollection::create(&self.window, self.upcast(), filter)
+            HTMLCollection::always_empty(&self.window, self.upcast())
         })
     }
 
     // https://html.spec.whatwg.org/multipage/#dom-document-location
     fn GetLocation(&self) -> Option<DomRoot<Location>> {
         if self.is_fully_active() {
             Some(self.window.Location())
         } else {
@@ -3525,27 +3514,16 @@ impl DocumentMethods for Document {
                 NodeTypeId::Element(ElementTypeId::HTMLElement(type_)) => type_,
                 _ => return false,
             };
             let elem = match node.downcast::<Element>() {
                 Some(elem) => elem,
                 None => return false,
             };
             match html_elem_type {
-                HTMLElementTypeId::HTMLAppletElement => {
-                    match elem.get_attribute(&ns!(), &local_name!("name")) {
-                        Some(ref attr) if attr.value().as_atom() == name => true,
-                        _ => {
-                            match elem.get_attribute(&ns!(), &local_name!("id")) {
-                                Some(ref attr) => attr.value().as_atom() == name,
-                                None => false,
-                            }
-                        },
-                    }
-                },
                 HTMLElementTypeId::HTMLFormElement => {
                     match elem.get_attribute(&ns!(), &local_name!("name")) {
                         Some(ref attr) => attr.value().as_atom() == name,
                         None => false,
                     }
                 },
                 HTMLElementTypeId::HTMLImageElement => {
                     match elem.get_attribute(&ns!(), &local_name!("name")) {
--- a/servo/components/script/dom/element.rs
+++ b/servo/components/script/dom/element.rs
@@ -104,17 +104,18 @@ use std::str::FromStr;
 use style::CaseSensitivityExt;
 use style::applicable_declarations::ApplicableDeclarationBlock;
 use style::attr::{AttrValue, LengthOrPercentageOrAuto};
 use style::context::QuirksMode;
 use style::dom_apis;
 use style::element_state::ElementState;
 use style::invalidation::element::restyle_hints::RestyleHint;
 use style::properties::{Importance, PropertyDeclaration, PropertyDeclarationBlock, parse_style_attribute};
-use style::properties::longhands::{self, background_image, border_spacing, font_family, font_size, overflow_x};
+use style::properties::longhands::{self, background_image, border_spacing, font_family, font_size};
+use style::properties::longhands::{overflow_x, overflow_y};
 use style::rule_tree::CascadeLevel;
 use style::selector_parser::{NonTSPseudoClass, PseudoElement, RestyleDamage, SelectorImpl, SelectorParser};
 use style::selector_parser::extended_filtering;
 use style::shared_lock::{SharedRwLock, Locked};
 use style::thread_state;
 use style::values::{CSSFloat, Either};
 use style::values::{specified, computed};
 use stylesheet_loader::StylesheetOwner;
@@ -368,17 +369,17 @@ impl Element {
         let overflow_pair = window.overflow_query(self.upcast::<Node>().to_trusted_node_address());
         overflow_pair.x == overflow_x::computed_value::T::visible
     }
 
     // used value of overflow-y is "visible"
     fn overflow_y_is_visible(&self) -> bool {
         let window = window_from_node(self);
         let overflow_pair = window.overflow_query(self.upcast::<Node>().to_trusted_node_address());
-        overflow_pair.y != overflow_x::computed_value::T::visible
+        overflow_pair.y == overflow_y::computed_value::T::visible
     }
 }
 
 #[allow(unsafe_code)]
 pub trait RawLayoutElementHelpers {
     unsafe fn get_attr_for_layout<'a>(&'a self, namespace: &Namespace, name: &LocalName)
                                       -> Option<&'a AttrValue>;
     unsafe fn get_attr_val_for_layout<'a>(&'a self, namespace: &Namespace, name: &LocalName)
deleted file mode 100644
--- a/servo/components/script/dom/htmlappletelement.rs
+++ /dev/null
@@ -1,62 +0,0 @@
-/* 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 dom::bindings::codegen::Bindings::HTMLAppletElementBinding;
-use dom::bindings::codegen::Bindings::HTMLAppletElementBinding::HTMLAppletElementMethods;
-use dom::bindings::inheritance::Castable;
-use dom::bindings::root::DomRoot;
-use dom::bindings::str::DOMString;
-use dom::document::Document;
-use dom::htmlelement::HTMLElement;
-use dom::node::Node;
-use dom::virtualmethods::VirtualMethods;
-use dom_struct::dom_struct;
-use html5ever::{LocalName, Prefix};
-use style::attr::AttrValue;
-
-#[dom_struct]
-pub struct HTMLAppletElement {
-    htmlelement: HTMLElement
-}
-
-impl HTMLAppletElement {
-    fn new_inherited(local_name: LocalName,
-                     prefix: Option<Prefix>,
-                     document: &Document) -> HTMLAppletElement {
-        HTMLAppletElement {
-            htmlelement:
-                HTMLElement::new_inherited(local_name, prefix, document)
-        }
-    }
-
-    #[allow(unrooted_must_root)]
-    pub fn new(local_name: LocalName,
-               prefix: Option<Prefix>,
-               document: &Document) -> DomRoot<HTMLAppletElement> {
-        Node::reflect_node(Box::new(HTMLAppletElement::new_inherited(local_name, prefix, document)),
-                           document,
-                           HTMLAppletElementBinding::Wrap)
-    }
-}
-
-impl HTMLAppletElementMethods for HTMLAppletElement {
-    // https://html.spec.whatwg.org/multipage/#the-applet-element:dom-applet-name
-    make_getter!(Name, "name");
-
-    // https://html.spec.whatwg.org/multipage/#the-applet-element:dom-applet-name
-    make_atomic_setter!(SetName, "name");
-}
-
-impl VirtualMethods for HTMLAppletElement {
-    fn super_type(&self) -> Option<&VirtualMethods> {
-        Some(self.upcast::<HTMLElement>() as &VirtualMethods)
-    }
-
-    fn parse_plain_attribute(&self, name: &LocalName, value: DOMString) -> AttrValue {
-        match name {
-            &local_name!("name") => AttrValue::from_atomic(value.into()),
-            _ => self.super_type().unwrap().parse_plain_attribute(name, value),
-        }
-    }
-}
--- a/servo/components/script/dom/htmlcollection.rs
+++ b/servo/components/script/dom/htmlcollection.rs
@@ -75,16 +75,29 @@ impl HTMLCollection {
             // Default values for the cache
             cached_version: Cell::new(root.inclusive_descendants_version()),
             cached_cursor_element: MutNullableDom::new(None),
             cached_cursor_index: Cell::new(OptionU32::none()),
             cached_length: Cell::new(OptionU32::none()),
         }
     }
 
+    /// Returns a collection which is always empty.
+    pub fn always_empty(window: &Window, root: &Node) -> DomRoot<Self> {
+        #[derive(JSTraceable)]
+        struct NoFilter;
+        impl CollectionFilter for NoFilter {
+            fn filter<'a>(&self, _: &'a Element, _: &'a Node) -> bool {
+                false
+            }
+        }
+
+        Self::new(window, root, Box::new(NoFilter))
+    }
+
     #[allow(unrooted_must_root)]
     pub fn new(window: &Window, root: &Node, filter: Box<CollectionFilter + 'static>) -> DomRoot<HTMLCollection> {
         reflect_dom_object(Box::new(HTMLCollection::new_inherited(root, filter)),
                            window, HTMLCollectionBinding::Wrap)
     }
 
     pub fn create(window: &Window, root: &Node,
                   filter: Box<CollectionFilter + 'static>) -> DomRoot<HTMLCollection> {
--- a/servo/components/script/dom/mod.rs
+++ b/servo/components/script/dom/mod.rs
@@ -296,17 +296,16 @@ pub mod gamepadbutton;
 pub mod gamepadbuttonlist;
 pub mod gamepadevent;
 pub mod gamepadlist;
 pub mod globalscope;
 pub mod hashchangeevent;
 pub mod headers;
 pub mod history;
 pub mod htmlanchorelement;
-pub mod htmlappletelement;
 pub mod htmlareaelement;
 pub mod htmlaudioelement;
 pub mod htmlbaseelement;
 pub mod htmlbodyelement;
 pub mod htmlbrelement;
 pub mod htmlbuttonelement;
 pub mod htmlcanvaselement;
 pub mod htmlcollection;
--- a/servo/components/script/dom/virtualmethods.rs
+++ b/servo/components/script/dom/virtualmethods.rs
@@ -9,17 +9,16 @@ use dom::bindings::inheritance::HTMLElem
 use dom::bindings::inheritance::NodeTypeId;
 use dom::bindings::inheritance::SVGElementTypeId;
 use dom::bindings::inheritance::SVGGraphicsElementTypeId;
 use dom::bindings::str::DOMString;
 use dom::document::Document;
 use dom::element::{AttributeMutation, Element};
 use dom::event::Event;
 use dom::htmlanchorelement::HTMLAnchorElement;
-use dom::htmlappletelement::HTMLAppletElement;
 use dom::htmlareaelement::HTMLAreaElement;
 use dom::htmlbaseelement::HTMLBaseElement;
 use dom::htmlbodyelement::HTMLBodyElement;
 use dom::htmlbuttonelement::HTMLButtonElement;
 use dom::htmlcanvaselement::HTMLCanvasElement;
 use dom::htmldetailselement::HTMLDetailsElement;
 use dom::htmlelement::HTMLElement;
 use dom::htmlfieldsetelement::HTMLFieldSetElement;
@@ -149,19 +148,16 @@ pub trait VirtualMethods {
 /// method call on the trait object will invoke the corresponding method on the
 /// concrete type, propagating up the parent hierarchy unless otherwise
 /// interrupted.
 pub fn vtable_for(node: &Node) -> &VirtualMethods {
     match node.type_id() {
         NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLAnchorElement)) => {
             node.downcast::<HTMLAnchorElement>().unwrap() as &VirtualMethods
         }
-        NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLAppletElement)) => {
-            node.downcast::<HTMLAppletElement>().unwrap() as &VirtualMethods
-        }
         NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLAreaElement)) => {
             node.downcast::<HTMLAreaElement>().unwrap() as &VirtualMethods
         }
         NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLBaseElement)) => {
             node.downcast::<HTMLBaseElement>().unwrap() as &VirtualMethods
         }
         NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLBodyElement)) => {
             node.downcast::<HTMLBodyElement>().unwrap() as &VirtualMethods
--- a/servo/components/script/dom/webidls/Document.webidl
+++ b/servo/components/script/dom/webidls/Document.webidl
@@ -166,16 +166,17 @@ partial interface Document {
   // [CEReactions, TreatNullAs=EmptyString]
   // attribute DOMString alinkColor;
 
   [CEReactions, TreatNullAs=EmptyString]
   attribute DOMString bgColor;
 
   [SameObject]
   readonly attribute HTMLCollection anchors;
+
   [SameObject]
   readonly attribute HTMLCollection applets;
 
   void clear();
   void captureEvents();
   void releaseEvents();
 
   // Tracking issue for document.all: https://github.com/servo/servo/issues/7396
deleted file mode 100644
--- a/servo/components/script/dom/webidls/HTMLAppletElement.webidl
+++ /dev/null
@@ -1,19 +0,0 @@
-/* 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/. */
-
-// https://html.spec.whatwg.org/multipage/#htmlappletelement
-// Note: intentionally not [HTMLConstructor]
-interface HTMLAppletElement : HTMLElement {
-  //         attribute DOMString align;
-  //         attribute DOMString alt;
-  //         attribute DOMString archive;
-  //         attribute DOMString code;
-  //         attribute DOMString codeBase;
-  //         attribute DOMString height;
-  //         attribute unsigned long hspace;
-             attribute DOMString name;
-  //         attribute DOMString _object; // the underscore is not part of the identifier
-  //         attribute unsigned long vspace;
-  //         attribute DOMString width;
-};
--- a/servo/components/style/gecko/media_queries.rs
+++ b/servo/components/style/gecko/media_queries.rs
@@ -5,31 +5,28 @@
 //! Gecko's media-query device and expression representation.
 
 use app_units::AU_PER_PX;
 use app_units::Au;
 use context::QuirksMode;
 use cssparser::{CssStringWriter, Parser, RGBA, Token, BasicParseErrorKind};
 use euclid::ScaleFactor;
 use euclid::Size2D;
-use font_metrics::get_metrics_provider_for_product;
 use gecko::values::{convert_nscolor_to_rgba, convert_rgba_to_nscolor};
 use gecko_bindings::bindings;
 use gecko_bindings::structs;
 use gecko_bindings::structs::{nsCSSKeyword, nsCSSProps_KTableEntry, nsCSSValue, nsCSSUnit};
 use gecko_bindings::structs::{nsMediaExpression_Range, nsMediaFeature};
 use gecko_bindings::structs::{nsMediaFeature_ValueType, nsMediaFeature_RangeType};
 use gecko_bindings::structs::{nsPresContext, RawGeckoPresContextOwned};
 use media_queries::MediaType;
 use parser::ParserContext;
-use properties::{ComputedValues, StyleBuilder};
+use properties::ComputedValues;
 use properties::longhands::font_size;
-use rule_cache::RuleCacheConditions;
 use servo_arc::Arc;
-use std::cell::RefCell;
 use std::fmt::{self, Write};
 use std::sync::atomic::{AtomicBool, AtomicIsize, AtomicUsize, Ordering};
 use str::starts_with_ignore_ascii_case;
 use string_cache::Atom;
 use style_traits::{CSSPixel, DevicePixel};
 use style_traits::{ToCss, ParseError, StyleParseErrorKind};
 use style_traits::viewport::ViewportConstraints;
 use stylesheets::Origin;
@@ -717,55 +714,45 @@ impl Expression {
     ) -> bool {
         use self::MediaExpressionValue::*;
         use std::cmp::Ordering;
 
         debug_assert!(self.range == nsMediaExpression_Range::eEqual ||
                       self.feature.mRangeType == nsMediaFeature_RangeType::eMinMaxAllowed,
                       "Whoops, wrong range");
 
-        let default_values = device.default_computed_values();
-
-
-        let provider = get_metrics_provider_for_product();
-
         // http://dev.w3.org/csswg/mediaqueries3/#units
         // em units are relative to the initial font-size.
-        let mut conditions = RuleCacheConditions::default();
-        let context = computed::Context {
-            is_root_element: false,
-            builder: StyleBuilder::for_derived_style(device, default_values, None, None),
-            font_metrics_provider: &provider,
-            cached_system_font: None,
-            in_media_query: true,
-            quirks_mode,
-            for_smil_animation: false,
-            for_non_inherited_property: None,
-            rule_cache_conditions: RefCell::new(&mut conditions),
-        };
-
         let required_value = match self.value {
             Some(ref v) => v,
             None => {
                 // If there's no value, always match unless it's a zero length
                 // or a zero integer or boolean.
                 return match *actual_value {
                     BoolInteger(v) => v,
                     Integer(v) => v != 0,
-                    Length(ref l) => l.to_computed_value(&context).px() != 0.,
+                    Length(ref l) => {
+                        computed::Context::for_media_query_evaluation(
+                            device,
+                            quirks_mode,
+                            |context| l.to_computed_value(&context).px() != 0.,
+                        )
+                    },
                     _ => true,
                 }
             }
         };
 
         // FIXME(emilio): Handle the possible floating point errors?
         let cmp = match (required_value, actual_value) {
             (&Length(ref one), &Length(ref other)) => {
-                one.to_computed_value(&context).to_i32_au()
-                    .cmp(&other.to_computed_value(&context).to_i32_au())
+                computed::Context::for_media_query_evaluation(device, quirks_mode, |context| {
+                    one.to_computed_value(&context).to_i32_au()
+                        .cmp(&other.to_computed_value(&context).to_i32_au())
+                })
             }
             (&Integer(one), &Integer(ref other)) => one.cmp(other),
             (&BoolInteger(one), &BoolInteger(ref other)) => one.cmp(other),
             (&Float(one), &Float(ref other)) => one.partial_cmp(other).unwrap(),
             (&IntRatio(one_num, one_den), &IntRatio(other_num, other_den)) => {
                 // Extend to avoid overflow.
                 (one_num as u64 * other_den as u64).cmp(
                     &(other_num as u64 * one_den as u64))
--- a/servo/components/style/media_queries.rs
+++ b/servo/components/style/media_queries.rs
@@ -72,27 +72,20 @@ pub struct MediaQuery {
     /// The set of expressions that this media query contains.
     pub expressions: Vec<Expression>,
 }
 
 impl MediaQuery {
     /// Return a media query that never matches, used for when we fail to parse
     /// a given media query.
     fn never_matching() -> Self {
-        Self::new(Some(Qualifier::Not), MediaQueryType::All, vec![])
-    }
-
-    /// Trivially constructs a new media query.
-    pub fn new(qualifier: Option<Qualifier>,
-               media_type: MediaQueryType,
-               expressions: Vec<Expression>) -> MediaQuery {
-        MediaQuery {
-            qualifier: qualifier,
-            media_type: media_type,
-            expressions: expressions,
+        Self {
+            qualifier: Some(Qualifier::Not),
+            media_type: MediaQueryType::All,
+            expressions: vec![],
         }
     }
 }
 
 impl ToCss for MediaQuery {
     fn to_css<W>(&self, dest: &mut W) -> fmt::Result
         where W: fmt::Write,
     {
@@ -204,19 +197,22 @@ impl MediaQuery {
         } else if input.try(|input| input.expect_ident_matching("not")).is_ok() {
             Some(Qualifier::Not)
         } else {
             None
         };
 
         let media_type = match input.try(|i| i.expect_ident_cloned()) {
             Ok(ident) => {
-                let result: Result<_, ParseError> = MediaQueryType::parse(&*ident)
-                    .map_err(|()| input.new_custom_error(SelectorParseErrorKind::UnexpectedIdent(ident.clone())));
-                result?
+                MediaQueryType::parse(&*ident)
+                    .map_err(|()| {
+                        input.new_custom_error(
+                            SelectorParseErrorKind::UnexpectedIdent(ident.clone())
+                        )
+                    })?
             }
             Err(_) => {
                 // Media type is only optional if qualifier is not specified.
                 if qualifier.is_some() {
                     return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError))
                 }
 
                 // Without a media type, require at least one expression.
@@ -224,17 +220,17 @@ impl MediaQuery {
 
                 MediaQueryType::All
             }
         };
 
         // Parse any subsequent expressions
         loop {
             if input.try(|input| input.expect_ident_matching("and")).is_err() {
-                return Ok(MediaQuery::new(qualifier, media_type, expressions))
+                return Ok(MediaQuery { qualifier, media_type, expressions })
             }
             expressions.push(Expression::parse(context, input)?)
         }
     }
 }
 
 /// Parse a media query list from CSS.
 ///
--- a/servo/components/style/servo/media_queries.rs
+++ b/servo/components/style/servo/media_queries.rs
@@ -3,24 +3,21 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 //! Servo's media-query device and expression representation.
 
 use app_units::Au;
 use context::QuirksMode;
 use cssparser::{Parser, RGBA};
 use euclid::{ScaleFactor, Size2D, TypedSize2D};
-use font_metrics::ServoMetricsProvider;
 use media_queries::MediaType;
 use parser::ParserContext;
-use properties::{ComputedValues, StyleBuilder};
+use properties::ComputedValues;
 use properties::longhands::font_size;
-use rule_cache::RuleCacheConditions;
 use selectors::parser::SelectorParseErrorKind;
-use std::cell::RefCell;
 use std::fmt;
 use std::sync::atomic::{AtomicBool, AtomicIsize, Ordering};
 use style_traits::{CSSPixel, DevicePixel, ToCss, ParseError};
 use style_traits::viewport::ViewportConstraints;
 use values::computed::{self, ToComputedValue};
 use values::specified;
 
 /// A device is a structure that represents the current media a given document
@@ -247,34 +244,17 @@ pub enum Range<T> {
     /// At most the inner value.
     Max(T),
     /// Exactly the inner value.
     Eq(T),
 }
 
 impl Range<specified::Length> {
     fn to_computed_range(&self, device: &Device, quirks_mode: QuirksMode) -> Range<Au> {
-        let default_values = device.default_computed_values();
-        let mut conditions = RuleCacheConditions::default();
-        // http://dev.w3.org/csswg/mediaqueries3/#units
-        // em units are relative to the initial font-size.
-        let context = computed::Context {
-            is_root_element: false,
-            builder: StyleBuilder::for_derived_style(device, default_values, None, None),
-            // Servo doesn't support font metrics
-            // A real provider will be needed here once we do; since
-            // ch units can exist in media queries.
-            font_metrics_provider: &ServoMetricsProvider,
-            in_media_query: true,
-            cached_system_font: None,
-            quirks_mode,
-            for_smil_animation: false,
-            for_non_inherited_property: None,
-            rule_cache_conditions: RefCell::new(&mut conditions),
-        };
-
-        match *self {
-            Range::Min(ref width) => Range::Min(Au::from(width.to_computed_value(&context))),
-            Range::Max(ref width) => Range::Max(Au::from(width.to_computed_value(&context))),
-            Range::Eq(ref width) => Range::Eq(Au::from(width.to_computed_value(&context)))
-        }
+        computed::Context::for_media_query_evaluation(device, quirks_mode, |context| {
+            match *self {
+                Range::Min(ref width) => Range::Min(Au::from(width.to_computed_value(&context))),
+                Range::Max(ref width) => Range::Max(Au::from(width.to_computed_value(&context))),
+                Range::Eq(ref width) => Range::Eq(Au::from(width.to_computed_value(&context)))
+            }
+        })
     }
 }
--- a/servo/components/style/values/computed/mod.rs
+++ b/servo/components/style/values/computed/mod.rs
@@ -2,17 +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/. */
 
 //! Computed values.
 
 use {Atom, Namespace};
 use context::QuirksMode;
 use euclid::Size2D;
-use font_metrics::FontMetricsProvider;
+use font_metrics::{FontMetricsProvider, get_metrics_provider_for_product};
 use media_queries::Device;
 #[cfg(feature = "gecko")]
 use properties;
 use properties::{ComputedValues, LonghandId, StyleBuilder};
 use rule_cache::RuleCacheConditions;
 #[cfg(feature = "servo")]
 use servo_url::ServoUrl;
 use std::{f32, fmt};
@@ -131,16 +131,46 @@ pub struct Context<'a> {
 
     /// The conditions to cache a rule node on the rule cache.
     ///
     /// FIXME(emilio): Drop the refcell.
     pub rule_cache_conditions: RefCell<&'a mut RuleCacheConditions>,
 }
 
 impl<'a> Context<'a> {
+    /// Creates a suitable context for media query evaluation, in which
+    /// font-relative units compute against the system_font, and executes `f`
+    /// with it.
+    pub fn for_media_query_evaluation<F, R>(
+        device: &Device,
+        quirks_mode: QuirksMode,
+        f: F,
+    ) -> R
+    where
+        F: FnOnce(&Context) -> R
+    {
+        let mut conditions = RuleCacheConditions::default();
+        let default_values = device.default_computed_values();
+        let provider = get_metrics_provider_for_product();
+
+        let context = Context {
+            is_root_element: false,
+            builder: StyleBuilder::for_derived_style(device, default_values, None, None),
+            font_metrics_provider: &provider,
+            cached_system_font: None,
+            in_media_query: true,
+            quirks_mode,
+            for_smil_animation: false,
+            for_non_inherited_property: None,
+            rule_cache_conditions: RefCell::new(&mut conditions),
+        };
+
+        f(&context)
+    }
+
     /// Whether the current element is the root element.
     pub fn is_root_element(&self) -> bool {
         self.is_root_element
     }
 
     /// The current device.
     pub fn device(&self) -> &Device {
         self.builder.device
--- a/servo/components/style/values/specified/mod.rs
+++ b/servo/components/style/values/specified/mod.rs
@@ -71,16 +71,17 @@ pub mod font;
 #[cfg(feature = "gecko")]
 pub mod gecko;
 pub mod grid;
 pub mod image;
 pub mod length;
 pub mod percentage;
 pub mod position;
 pub mod rect;
+pub mod source_size_list;
 pub mod svg;
 pub mod table;
 pub mod text;
 pub mod time;
 pub mod transform;
 
 /// Common handling for the specified value CSS url() values.
 pub mod url {
new file mode 100644
--- /dev/null
+++ b/servo/components/style/values/specified/source_size_list.rs
@@ -0,0 +1,79 @@
+/* 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/. */
+
+//! https://html.spec.whatwg.org/multipage/#source-size-list
+
+use app_units::Au;
+use cssparser::Parser;
+use media_queries::{Device, Expression as MediaExpression};
+use parser::{Parse, ParserContext};
+use selectors::context::QuirksMode;
+use style_traits::ParseError;
+use values::computed::{self, ToComputedValue};
+use values::specified::Length;
+
+/// A value for a `<source-size>`:
+///
+/// https://html.spec.whatwg.org/multipage/#source-size
+pub struct SourceSize {
+    condition: MediaExpression,
+    value: Length,
+}
+
+impl Parse for SourceSize {
+    fn parse<'i, 't>(
+        context: &ParserContext,
+        input: &mut Parser<'i, 't>,
+    ) -> Result<Self, ParseError<'i>> {
+        let condition = MediaExpression::parse(context, input)?;
+        let value = Length::parse_non_negative(context, input)?;
+
+        Ok(Self { condition, value })
+    }
+}
+
+/// A value for a `<source-size-list>`:
+///
+/// https://html.spec.whatwg.org/multipage/#source-size-list
+pub struct SourceSizeList {
+    source_sizes: Vec<SourceSize>,
+    value: Length,
+}
+
+impl SourceSizeList {
+    /// Evaluate this <source-size-list> to get the final viewport length.
+    pub fn evaluate(&self, device: &Device, quirks_mode: QuirksMode) -> Au {
+        let matching_source_size = self.source_sizes.iter().find(|source_size| {
+            source_size.condition.matches(device, quirks_mode)
+        });
+
+        computed::Context::for_media_query_evaluation(device, quirks_mode, |context| {
+            match matching_source_size {
+                Some(source_size) => {
+                    source_size.value.to_computed_value(context)
+                }
+                None => {
+                    self.value.to_computed_value(context)
+                }
+            }
+        }).into()
+    }
+}
+
+impl Parse for SourceSizeList {
+    fn parse<'i, 't>(
+        context: &ParserContext,
+        input: &mut Parser<'i, 't>,
+    ) -> Result<Self, ParseError<'i>> {
+        let source_sizes = input.try(|input| {
+            input.parse_comma_separated(|input| {
+                SourceSize::parse(context, input)
+            })
+        }).unwrap_or(vec![]);
+
+        let value = Length::parse_non_negative(context, input)?;
+
+        Ok(Self { source_sizes, value })
+    }
+}
--- a/servo/resources/presentational-hints.css
+++ b/servo/resources/presentational-hints.css
@@ -134,41 +134,41 @@ hr[align=left] { margin-left: 0; margin-
 hr[align=right] { margin-left: auto; margin-right: 0; }
 hr[align=center] { margin-left: auto; margin-right: auto; }
 hr[color], hr[noshade] { border-style: solid; }
 
 
 
 iframe[frameborder="0"], iframe[frameborder=no i] { border: none; }
 
-applet[align=left i], embed[align=left i], iframe[align=left i], img[type=image i][align=left i], object[align=left i] {
+embed[align=left i], iframe[align=left i], img[type=image i][align=left i], object[align=left i] {
   float: left;
 }
-applet[align=right i], embed[align=right i], iframe[align=right i], img[type=image i][align=right i], object[align=right i] {
+embed[align=right i], iframe[align=right i], img[type=image i][align=right i], object[align=right i] {
   float: right;
 }
-applet[align=top i], embed[align=top i], iframe[align=top i], img[type=image i][align=top i], object[align=top i] {
+embed[align=top i], iframe[align=top i], img[type=image i][align=top i], object[align=top i] {
   vertical-align: top;
 }
-applet[align=baseline i], embed[align=baseline i], iframe[align=baseline i], img[type=image i][align=baseline i], object[align=baseline i] {
+embed[align=baseline i], iframe[align=baseline i], img[type=image i][align=baseline i], object[align=baseline i] {
   vertical-align: baseline;
 }
-applet[align=texttop i], embed[align=texttop i], iframe[align=texttop i], img[type=image i][align=texttop i], object[align=texttop i] {
+embed[align=texttop i], iframe[align=texttop i], img[type=image i][align=texttop i], object[align=texttop i] {
   vertical-align: text-top;
 }
-applet[align=absmiddle i], embed[align=absmiddle i], iframe[align=absmiddle i], img[type=image i][align=absmiddle i], object[align=absmiddle i],
-applet[align=abscenter i], embed[align=abscenter i], iframe[align=abscenter i], img[type=image i][align=abscenter i], object[align=abscenter i] {
+embed[align=absmiddle i], iframe[align=absmiddle i], img[type=image i][align=absmiddle i], object[align=absmiddle i],
+embed[align=abscenter i], iframe[align=abscenter i], img[type=image i][align=abscenter i], object[align=abscenter i] {
   vertical-align: middle;
 }
-applet[align=bottom i], embed[align=bottom i], iframe[align=bottom i], img[type=image i][align=bottom i], object[align=bottom i] {
+embed[align=bottom i], iframe[align=bottom i], img[type=image i][align=bottom i], object[align=bottom i] {
   vertical-align: bottom;
 }
 /*
 FIXME:
-:matches(applet, embed, iframe, img, input[type=image i], object):matches([align=center i], [align=middle i]) {
+:matches(embed, iframe, img, input[type=image i], object):matches([align=center i], [align=middle i]) {
   vertical-align: "aligns the vertical middle of the element with the parent element's baseline."
 }
 */
 
 /*
 
 Presentational attributes which can not currently be expressed in CSS.
 FIXME: Deal with them with attr(foo dimension) and the like?
@@ -230,24 +230,24 @@ hr
   color
   noshade
   size
   width
 
 legend
   align
 
-applet, embed, iframe, img, input[type=image i], object
+embed, iframe, img, input[type=image i], object
   hspace
   vspace
 
 img, input[type=image i], object
   border
 
-applet, embed, iframe, img, input[type=image i], object, video
+embed, iframe, img, input[type=image i], object, video
   width
   height
 
 */
 
 /*
 
 Extra
new file mode 100644
--- /dev/null
+++ b/taskcluster/ci/config.yml
@@ -0,0 +1,80 @@
+treeherder:
+    group-names:
+        'cram': 'Cram tests'
+        'mocha': 'Mocha unit tests'
+        'py': 'Python unit tests'
+        'tc': 'Executed by TaskCluster'
+        'tc-A': 'Android Gradle tests executed by TaskCluster'
+        'tc-e10s': 'Executed by TaskCluster with e10s'
+        'tc-Fxfn-l': 'Firefox functional tests (local) executed by TaskCluster'
+        'tc-Fxfn-l-e10s': 'Firefox functional tests (local) executed by TaskCluster with e10s'
+        'tc-Fxfn-r': 'Firefox functional tests (remote) executed by TaskCluster'
+        'tc-Fxfn-r-e10s': 'Firefox functional tests (remote) executed by TaskCluster with e10s'
+        'tc-M': 'Mochitests executed by TaskCluster'
+        'tc-M-e10s': 'Mochitests executed by TaskCluster with e10s'
+        'tc-M-V': 'Mochitests on Valgrind executed by TaskCluster'
+        'tc-R': 'Reftests executed by TaskCluster'
+        'tc-R-e10s': 'Reftests executed by TaskCluster with e10s'
+        'tc-T': 'Talos performance tests executed by TaskCluster'
+        'tc-Tsd': 'Talos performance tests executed by TaskCluster with Stylo disabled'
+        'tc-Tss': 'Talos performance tests executed by TaskCluster with Stylo sequential'
+        'tc-T-e10s': 'Talos performance tests executed by TaskCluster with e10s'
+        'tc-Tsd-e10s': 'Talos performance tests executed by TaskCluster with e10s, Stylo disabled'
+        'tc-Tss-e10s': 'Talos performance tests executed by TaskCluster with e10s, Stylo sequential'
+        'tc-tt-c': 'Telemetry client marionette tests'
+        'tc-tt-c-e10s': 'Telemetry client marionette tests with e10s'
+        'tc-SY-e10s': 'Are we slim yet tests by TaskCluster with e10s'
+        'tc-SYsd-e10s': 'Are we slim yet tests by TaskCluster with e10s, Stylo disabled'
+        'tc-SYss-e10s': 'Are we slim yet tests by TaskCluster with e10s, Stylo sequential'
+        'tc-VP': 'VideoPuppeteer tests executed by TaskCluster'
+        'tc-W': 'Web platform tests executed by TaskCluster'
+        'tc-W-e10s': 'Web platform tests executed by TaskCluster with e10s'
+        'tc-X': 'Xpcshell tests executed by TaskCluster'
+        'tc-X-e10s': 'Xpcshell tests executed by TaskCluster with e10s'
+        'tc-L10n': 'Localised Repacks executed by Taskcluster'
+        'tc-L10n-Rpk': 'Localized Repackaged Repacks executed by Taskcluster'
+        'tc-BM-L10n': 'Beetmover for locales executed by Taskcluster'
+        'tc-BMR-L10n': 'Beetmover repackages for locales executed by Taskcluster'
+        'c-Up': 'Balrog submission of complete updates'
+        'tc-cs': 'Checksum signing executed by Taskcluster'
+        'tc-rs': 'Repackage signing executed by Taskcluster'
+        'tc-BMcs': 'Beetmover checksums, executed by Taskcluster'
+        'Aries': 'Aries Device Image'
+        'Nexus 5-L': 'Nexus 5-L Device Image'
+        'I': 'Docker Image Builds'
+        'TL': 'Toolchain builds for Linux 64-bits'
+        'TM': 'Toolchain builds for OSX'
+        'TMW': 'Toolchain builds for Windows MinGW'
+        'TW32': 'Toolchain builds for Windows 32-bits'
+        'TW64': 'Toolchain builds for Windows 64-bits'
+        'SM-tc': 'Spidermonkey builds'
+        'pub': 'APK publishing'
+        'p': 'Partial generation'
+        'ps': 'Partials signing'
+        'Rel': 'Release promotion'
+
+try:
+    # We have a few platforms for which we want to do some "extra" builds, or at
+    # least build-ish things.  Sort of.  Anyway, these other things are implemented
+    # as different "platforms".  These do *not* automatically ride along with "-p
+    # all"
+    ridealong-builds:
+        'android-api-16':
+            - 'android-api-16-l10n'
+        'linux':
+            - 'linux-l10n'
+        'linux64':
+            - 'linux64-l10n'
+            - 'sm-plain'
+            - 'sm-nonunified'
+            - 'sm-arm-sim'
+            - 'sm-arm64-sim'
+            - 'sm-compacting'
+            - 'sm-rootanalysis'
+            - 'sm-package'
+            - 'sm-tsan'
+            - 'sm-asan'
+            - 'sm-mozjs-sys'
+            - 'sm-msan'
+            - 'sm-fuzzing'
+            - 'sm-rust-bindings'
new file mode 100644
--- /dev/null
+++ b/taskcluster/taskgraph/config.py
@@ -0,0 +1,26 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+from __future__ import absolute_import, print_function, unicode_literals
+
+from .util.schema import validate_schema, Schema
+from voluptuous import Required
+
+graph_config_schema = Schema({
+    Required('treeherder'): {
+        # Mapping of treeherder group symbols to descriptive names
+        Required('group-names'): {basestring: basestring}
+    },
+    Required('try'): {
+        # We have a few platforms for which we want to do some "extra" builds, or at
+        # least build-ish things.  Sort of.  Anyway, these other things are implemented
+        # as different "platforms".  These do *not* automatically ride along with "-p
+        # all"
+        Required('ridealong-builds', default={}): {basestring: [basestring]},
+    },
+})
+
+
+def validate_graph_config(config):
+    return validate_schema(graph_config_schema, config, "Invalid graph configuration:")
--- a/taskcluster/taskgraph/decision.py
+++ b/taskcluster/taskgraph/decision.py
@@ -34,16 +34,20 @@ PER_PROJECT_PARAMETERS = {
         # `--include-nightly` is specified in the commit message.
         # We're setting the `include_nightly` parameter to True here for when
         # we submit decision tasks against Try that use other
         # `target_task_method`s, like `nightly_fennec` or `mozilla_beta_tasks`,
         # which reference the `include_nightly` parameter.
         'include_nightly': True,
     },
 
+    'try-comm-central': {
+        'target_tasks_method': 'try_tasks',
+    },
+
     'ash': {
         'target_tasks_method': 'ash_tasks',
         'optimize_target_tasks': True,
         'include_nightly': False,
     },
 
     'cedar': {
         'target_tasks_method': 'cedar_tasks',
@@ -196,17 +200,17 @@ def get_decision_parameters(options):
     parameters.setdefault('release_history', dict())
     if 'nightly' in parameters.get('target_tasks_method', ''):
         parameters['release_history'] = populate_release_history('Firefox', project)
 
     # if try_task_config.json is present, load it
     task_config_file = os.path.join(os.getcwd(), 'try_task_config.json')
 
     # load try settings
-    if project == 'try':
+    if 'try' in project:
         parameters['try_mode'] = None
         if os.path.isfile(task_config_file):
             parameters['try_mode'] = 'try_task_config'
             with open(task_config_file, 'r') as fh:
                 parameters['try_task_config'] = json.load(fh)
         else:
             parameters['try_task_config'] = None
 
--- a/taskcluster/taskgraph/filter_tasks.py
+++ b/taskcluster/taskgraph/filter_tasks.py
@@ -19,29 +19,29 @@ def filter_task(name):
     """Generator to declare a task filter function."""
     def wrap(func):
         filter_task_functions[name] = func
         return func
     return wrap
 
 
 @filter_task('target_tasks_method')
-def filter_target_tasks(graph, parameters):
+def filter_target_tasks(graph, parameters, graph_config):
     """Proxy filter to use legacy target tasks code.
 
     This should go away once target_tasks are converted to filters.
     """
 
     attr = parameters.get('target_tasks_method', 'all_tasks')
     fn = target_tasks.get_method(attr)
-    return fn(graph, parameters)
+    return fn(graph, parameters, graph_config)
 
 
 @filter_task('check_servo')
-def filter_servo(graph, parameters):
+def filter_servo(graph, parameters, graph_config):
     """Filter out tasks for Servo vendoring changesets.
 
     If the change triggering is related to Servo vendoring, impact is minimal
     because not all platforms use Servo code.
 
     We filter out tests on platforms that don't run Servo tests because running
     tests will accomplish little for these changes.
     """
--- a/taskcluster/taskgraph/generator.py
+++ b/taskcluster/taskgraph/generator.py
@@ -15,26 +15,28 @@ from .task import Task
 from .optimize import optimize_task_graph
 from .morph import morph
 from .util.python_path import find_object
 from .transforms.base import TransformSequence, TransformConfig
 from .util.verify import (
     verify_docs,
     verifications,
 )
+from .config import validate_graph_config
 
 logger = logging.getLogger(__name__)
 
 
 class Kind(object):
 
-    def __init__(self, name, path, config):
+    def __init__(self, name, path, config, graph_config):
         self.name = name
         self.path = path
         self.config = config
+        self.graph_config = graph_config
 
     def _get_loader(self):
         try:
             loader = self.config['loader']
         except KeyError:
             raise KeyError("{!r} does not define `loader`".format(self.path))
         return find_object(loader)
 
@@ -50,17 +52,17 @@ class Kind(object):
 
         transforms = TransformSequence()
         for xform_path in config['transforms']:
             transform = find_object(xform_path)
             transforms.add(transform)
 
         # perform the transformations on the loaded inputs
         trans_config = TransformConfig(self.name, self.path, config, parameters,
-                                       kind_dependencies_tasks)
+                                       kind_dependencies_tasks, self.graph_config)
         tasks = [Task(self.name,
                       label=task_dict['label'],
                       attributes=task_dict['attributes'],
                       task=task_dict['task'],
                       optimization=task_dict.get('optimization'),
                       dependencies=task_dict.get('dependencies'))
                  for task_dict in transforms(trans_config, inputs)]
         return tasks
@@ -174,38 +176,52 @@ class TaskGraphGenerator(object):
         The optimized task graph, with any subsequent morphs applied. This graph
         will have the same meaning as the optimized task graph, but be in a form
         more palatable to TaskCluster.
 
         @type: TaskGraph
         """
         return self._run_until('morphed_task_graph')
 
-    def _load_kinds(self):
+    def _load_kinds(self, graph_config):
         for path in os.listdir(self.root_dir):
             path = os.path.join(self.root_dir, path)
             if not os.path.isdir(path):
                 continue
             kind_name = os.path.basename(path)
 
             kind_yml = os.path.join(path, 'kind.yml')
             if not os.path.exists(kind_yml):
                 continue
 
             logger.debug("loading kind `{}` from `{}`".format(kind_name, path))
             with open(kind_yml) as f:
                 config = yaml.load(f)
 
-            yield Kind(kind_name, path, config)
+            yield Kind(kind_name, path, config, graph_config)
+
+    def _load_graph_config(self):
+        config_yml = os.path.join(self.root_dir, "config.yml")
+        if not os.path.exists(config_yml):
+            raise Exception("Couldn't find taskgraph configuration: {}".format(config_yml))
+
+        logger.debug("loading config from `{}`".format(config_yml))
+        with open(config_yml) as f:
+            config = yaml.load(f)
+
+        return validate_graph_config(config)
 
     def _run(self):
+        logger.info("Loading graph configuration.")
+        graph_config = self._load_graph_config()
+
         logger.info("Loading kinds")
         # put the kinds into a graph and sort topologically so that kinds are loaded
         # in post-order
-        kinds = {kind.name: kind for kind in self._load_kinds()}
+        kinds = {kind.name: kind for kind in self._load_kinds(graph_config)}
         self.verify_kinds(kinds)
 
         edges = set()
         for kind in kinds.itervalues():
             for dep in kind.config.get('kind-dependencies', []):
                 edges.add((kind.name, dep, 'kind-dependency'))
         kind_graph = Graph(set(kinds), edges)
 
@@ -237,17 +253,17 @@ class TaskGraphGenerator(object):
             len(full_task_set.graph.nodes), len(edges)))
         yield verifications('full_task_graph', full_task_graph)
 
         logger.info("Generating target task set")
         target_task_set = TaskGraph(dict(all_tasks),
                                     Graph(set(all_tasks.keys()), set()))
         for fltr in self.filters:
             old_len = len(target_task_set.graph.nodes)
-            target_tasks = set(fltr(target_task_set, self.parameters))
+            target_tasks = set(fltr(target_task_set, self.parameters, graph_config))
             target_task_set = TaskGraph(
                 {l: all_tasks[l] for l in target_tasks},
                 Graph(target_tasks, set()))
             logger.info('Filter %s pruned %d tasks (%d remain)' % (
                 fltr.__name__,
                 old_len - len(target_tasks),
                 len(target_tasks)))
 
--- a/taskcluster/taskgraph/target_tasks.py
+++ b/taskcluster/taskgraph/target_tasks.py
@@ -88,25 +88,25 @@ def filter_beta_release_tasks(task, para
 
 def standard_filter(task, parameters):
     return all(
         filter_func(task, parameters) for filter_func in
         (filter_on_nightly, filter_for_project, filter_upload_symbols)
     )
 
 
-def _try_task_config(full_task_graph, parameters):
+def _try_task_config(full_task_graph, parameters, graph_config):
     requested_tasks = parameters['try_task_config']['tasks']
     return list(set(requested_tasks) & full_task_graph.graph.nodes)
 
 
-def _try_option_syntax(full_task_graph, parameters):
+def _try_option_syntax(full_task_graph, parameters, graph_config):
     """Generate a list of target tasks based on try syntax in
     parameters['message'] and, for context, the full task graph."""
-    options = try_option_syntax.TryOptionSyntax(parameters, full_task_graph)
+    options = try_option_syntax.TryOptionSyntax(parameters, full_task_graph, graph_config)
     target_tasks_labels = [t.label for t in full_task_graph.tasks.itervalues()
                            if options.task_matches(t)]
 
     attributes = {
         k: getattr(options, k) for k in [
             'env',
             'no_retry',
             'tag',
@@ -142,38 +142,38 @@ def _try_option_syntax(full_task_graph, 
             elif options.notifications == 'failure':
                 routes.append("notify.email.{}.on-failed".format(owner))
                 routes.append("notify.email.{}.on-exception".format(owner))
 
     return target_tasks_labels
 
 
 @_target_task('try_tasks')
-def target_tasks_try(full_task_graph, parameters):
+def target_tasks_try(full_task_graph, parameters, graph_config):
     try_mode = parameters['try_mode']
     if try_mode == 'try_task_config':
-        return _try_task_config(full_task_graph, parameters)
+        return _try_task_config(full_task_graph, parameters, graph_config)
     elif try_mode == 'try_option_syntax':
-        return _try_option_syntax(full_task_graph, parameters)
+        return _try_option_syntax(full_task_graph, parameters, graph_config)
     else:
         # With no try mode, we schedule nothing, allowing the user to add tasks
         # later via treeherder.
         return []
 
 
 @_target_task('default')
-def target_tasks_default(full_task_graph, parameters):
+def target_tasks_default(full_task_graph, parameters, graph_config):
     """Target the tasks which have indicated they should be run on this project
     via the `run_on_projects` attributes."""
     return [l for l, t in full_task_graph.tasks.iteritems()
             if standard_filter(t, parameters)]
 
 
 @_target_task('ash_tasks')
-def target_tasks_ash(full_task_graph, parameters):
+def target_tasks_ash(full_task_graph, parameters, graph_config):
     """Target tasks that only run on the ash branch."""
     def filter(task):
         platform = task.attributes.get('build_platform')
         # Early return if platform is None
         if not platform:
             return False
         # Only on Linux platforms
         if 'linux' not in platform:
@@ -199,64 +199,64 @@ def target_tasks_ash(full_task_graph, pa
         # don't upload symbols
         if task.attributes['kind'] == 'upload-symbols':
             return False
         return True
     return [l for l, t in full_task_graph.tasks.iteritems() if filter(t)]
 
 
 @_target_task('cedar_tasks')
-def target_tasks_cedar(full_task_graph, parameters):
+def target_tasks_cedar(full_task_graph, parameters, graph_config):
     """Target tasks that only run on the cedar branch."""
     def filter(task):
         platform = task.attributes.get('build_platform')
         # only select platforms
         if platform not in ('linux64', 'macosx64'):
             return False
         if task.attributes.get('unittest_suite'):
             if not (task.attributes['unittest_suite'].startswith('mochitest') or
                     'xpcshell' in task.attributes['unittest_suite']):
                 return False
         return True
     return [l for l, t in full_task_graph.tasks.iteritems() if filter(t)]
 
 
 @_target_task('graphics_tasks')
-def target_tasks_graphics(full_task_graph, parameters):
+def target_tasks_graphics(full_task_graph, parameters, graph_config):
     """In addition to doing the filtering by project that the 'default'
        filter does, also remove artifact builds because we have csets on
        the graphics branch that aren't on the candidate branches of artifact
        builds"""
     filtered_for_project = target_tasks_default(full_task_graph, parameters)
 
     def filter(task):
         if task.attributes['kind'] == 'artifact-build':
             return False
         return True
     return [l for l in filtered_for_project if filter(full_task_graph[l])]
 
 
 @_target_task('mochitest_valgrind')
-def target_tasks_valgrind(full_task_graph, parameters):
+def target_tasks_valgrind(full_task_graph, parameters, graph_config):
     """Target tasks that only run on the cedar branch."""
     def filter(task):
         platform = task.attributes.get('test_platform', '').split('/')[0]
         if platform not in ['linux64']:
             return False
 
         if task.attributes.get('unittest_suite', '').startswith('mochitest') and \
            task.attributes.get('unittest_flavor', '').startswith('valgrind-plain'):
             return True
         return False
 
     return [l for l, t in full_task_graph.tasks.iteritems() if filter(t)]
 
 
 @_target_task('nightly_fennec')
-def target_tasks_nightly_fennec(full_task_graph, parameters):
+def target_tasks_nightly_fennec(full_task_graph, parameters, graph_config):
     """Select the set of tasks required for a nightly build of fennec. The
     nightly build process involves a pipeline of builds, signing,
     and, eventually, uploading the tasks to balrog."""
     def filter(task):
         platform = task.attributes.get('build_platform')
         if platform in ('android-aarch64-nightly',
                         'android-api-16-nightly',
                         'android-api-16-old-id-nightly',
@@ -265,51 +265,51 @@ def target_tasks_nightly_fennec(full_tas
                         'android-x86-old-id-nightly'):
             if not task.attributes.get('nightly', False):
                 return False
             return filter_for_project(task, parameters)
     return [l for l, t in full_task_graph.tasks.iteritems() if filter(t)]
 
 
 @_target_task('nightly_linux')
-def target_tasks_nightly_linux(full_task_graph, parameters):
+def target_tasks_nightly_linux(full_task_graph, parameters, graph_config):
     """Select the set of tasks required for a nightly build of linux. The
     nightly build process involves a pipeline of builds, signing,
     and, eventually, uploading the tasks to balrog."""
     def filter(task):
         platform = task.attributes.get('build_platform')
         if platform in ('linux64-nightly', 'linux-nightly'):
             return task.attributes.get('nightly', False)
     return [l for l, t in full_task_graph.tasks.iteritems() if filter(t)]
 
 
 @_target_task('mozilla_beta_tasks')
-def target_tasks_mozilla_beta(full_task_graph, parameters):
+def target_tasks_mozilla_beta(full_task_graph, parameters, graph_config):
     """Select the set of tasks required for a promotable beta or release build
     of desktop, plus android CI. The candidates build process involves a pipeline
     of builds and signing, but does not include beetmover or balrog jobs."""
 
     return [l for l, t in full_task_graph.tasks.iteritems() if
             filter_beta_release_tasks(t, parameters)]
 
 
 @_target_task('mozilla_release_tasks')
-def target_tasks_mozilla_release(full_task_graph, parameters):
+def target_tasks_mozilla_release(full_task_graph, parameters, graph_config):
     """Select the set of tasks required for a promotable beta or release build
     of desktop, plus android CI. The candidates build process involves a pipeline
     of builds and signing, but does not include beetmover or balrog jobs."""
 
     return [l for l, t in full_task_graph.tasks.iteritems() if
             filter_beta_release_tasks(t, parameters)]
 
 
 @_target_task('maple_desktop_promotion')
 @_target_task('mozilla-beta_desktop_promotion')
 @_target_task('mozilla-release_desktop_promotion')
-def target_tasks_mozilla_beta_desktop_promotion(full_task_graph, parameters):
+def target_tasks_mozilla_beta_desktop_promotion(full_task_graph, parameters, graph_config):
     """Select the superset of tasks required to promote a beta or release build
     of desktop. This should include all non-android mozilla_beta tasks, plus
     l10n, beetmover, balrog, etc."""
 
     beta_tasks = [l for l, t in full_task_graph.tasks.iteritems() if
                   filter_beta_release_tasks(t, parameters,
                                             ignore_kinds=[],
                                             allow_l10n=True)]
@@ -344,17 +344,17 @@ def target_tasks_mozilla_beta_desktop_pr
         # TODO: bouncer sub
         # TODO: snap
         # TODO: recompression tasks
 
     return [l for l, t in full_task_graph.tasks.iteritems() if filter(t)]
 
 
 @_target_task('publish_firefox')
-def target_tasks_publish_firefox(full_task_graph, parameters):
+def target_tasks_publish_firefox(full_task_graph, parameters, graph_config):
     """Select the set of tasks required to publish a candidates build of firefox.
     Previous build deps will be optimized out via action task."""
     filtered_for_candidates = target_tasks_mozilla_beta_desktop_promotion(
         full_task_graph, parameters
     )
 
     def filter(task):
         # Include promotion tasks; these will be optimized out
@@ -370,17 +370,17 @@ def target_tasks_publish_firefox(full_ta
         # TODO: bouncer aliases
         # TODO: checksums
         # TODO: shipit mark as shipped
 
     return [l for l, t in full_task_graph.iteritems() if filter(t)]
 
 
 @_target_task('candidates_fennec')
-def target_tasks_candidates_fennec(full_task_graph, parameters):
+def target_tasks_candidates_fennec(full_task_graph, parameters, graph_config):
     """Select the set of tasks required for a candidates build of fennec. The
     nightly build process involves a pipeline of builds, signing,
     and, eventually, uploading the tasks to balrog."""
     filtered_for_project = target_tasks_nightly_fennec(full_task_graph, parameters)
 
     def filter(task):
         attr = task.attributes.get
         # Don't ship single locale fennec anymore - Bug 1408083
@@ -398,17 +398,17 @@ def target_tasks_candidates_fennec(full_
             if task.kind in ('release-notify-promote',
                              ):
                 return True
 
     return [l for l, t in full_task_graph.tasks.iteritems() if filter(full_task_graph[l])]
 
 
 @_target_task('publish_fennec')
-def target_tasks_publish_fennec(full_task_graph, parameters):
+def target_tasks_publish_fennec(full_task_graph, parameters, graph_config):
     """Select the set of tasks required to publish a candidates build of fennec.
     Previous build deps will be optimized out via action task."""
     filtered_for_candidates = target_tasks_candidates_fennec(full_task_graph, parameters)
 
     def filter(task):
         # Include candidates build tasks; these will be optimized out
         if task.label in filtered_for_candidates:
             return True
@@ -430,95 +430,95 @@ def target_tasks_publish_fennec(full_tas
 
         if task.kind in ('push-apk', 'push-apk-breakpoint'):
             return True
 
     return [l for l, t in full_task_graph.tasks.iteritems() if filter(full_task_graph[l])]
 
 
 @_target_task('pine_tasks')
-def target_tasks_pine(full_task_graph, parameters):
+def target_tasks_pine(full_task_graph, parameters, graph_config):
     """Bug 1339179 - no mobile automation needed on pine"""
     def filter(task):
         platform = task.attributes.get('build_platform')
         # disable mobile jobs
         if str(platform).startswith('android'):
             return False
         # disable asan
         if platform == 'linux64-asan':
             return False
         # disable non-pine and nightly tasks
         if standard_filter(task, parameters):
             return True
     return [l for l, t in full_task_graph.tasks.iteritems() if filter(t)]
 
 
 @_target_task('nightly_macosx')
-def target_tasks_nightly_macosx(full_task_graph, parameters):
+def target_tasks_nightly_macosx(full_task_graph, parameters, graph_config):
     """Select the set of tasks required for a nightly build of macosx. The
     nightly build process involves a pipeline of builds, signing,
     and, eventually, uploading the tasks to balrog."""
     def filter(task):
         platform = task.attributes.get('build_platform')
         if platform in ('macosx64-nightly', ):
             return task.attributes.get('nightly', False)
     return [l for l, t in full_task_graph.tasks.iteritems() if filter(t)]
 
 
 @_target_task('nightly_win32')
-def target_tasks_nightly_win32(full_task_graph, parameters):
+def target_tasks_nightly_win32(full_task_graph, parameters, graph_config):
     """Select the set of tasks required for a nightly build of win32 and win64.
     The nightly build process involves a pipeline of builds, signing,
     and, eventually, uploading the tasks to balrog."""
     def filter(task):
         platform = task.attributes.get('build_platform')
         if not filter_for_project(task, parameters):
             return False
         if platform in ('win32-nightly', ):
             return task.attributes.get('nightly', False)
     return [l for l, t in full_task_graph.tasks.iteritems() if filter(t)]
 
 
 @_target_task('nightly_win64')
-def target_tasks_nightly_win64(full_task_graph, parameters):
+def target_tasks_nightly_win64(full_task_graph, parameters, graph_config):
     """Select the set of tasks required for a nightly build of win32 and win64.
     The nightly build process involves a pipeline of builds, signing,
     and, eventually, uploading the tasks to balrog."""
     def filter(task):
         platform = task.attributes.get('build_platform')
         if not filter_for_project(task, parameters):
             return False
         if platform in ('win64-nightly', ):
             return task.attributes.get('nightly', False)
     return [l for l, t in full_task_graph.tasks.iteritems() if filter(t)]
 
 
 @_target_task('nightly_desktop')
-def target_tasks_nightly_desktop(full_task_graph, parameters):
+def target_tasks_nightly_desktop(full_task_graph, parameters, graph_config):
     """Select the set of tasks required for a nightly build of linux, mac,
     windows."""
     # Avoid duplicate tasks.
     return list(
         set(target_tasks_nightly_win32(full_task_graph, parameters))
         | set(target_tasks_nightly_win64(full_task_graph, parameters))
         | set(target_tasks_nightly_macosx(full_task_graph, parameters))
         | set(target_tasks_nightly_linux(full_task_graph, parameters))
     )
 
 
 # Opt DMD builds should only run nightly
 @_target_task('nightly_dmd')
-def target_tasks_dmd(full_task_graph, parameters):
+def target_tasks_dmd(full_task_graph, parameters, graph_config):
     """Target DMD that run nightly on the m-c branch."""
     def filter(task):
         platform = task.attributes.get('build_platform', '')
         return platform.endswith('-dmd')
     return [l for l, t in full_task_graph.tasks.iteritems() if filter(t)]
 
 
 @_target_task('file_update')
-def target_tasks_file_update(full_task_graph, parameters):
+def target_tasks_file_update(full_task_graph, parameters, graph_config):
     """Select the set of tasks required to perform nightly in-tree file updates
     """
     def filter(task):
         # For now any task in the repo-update kind is ok
         return task.kind in ['repo-update']
     return [l for l, t in full_task_graph.tasks.iteritems() if filter(t)]
--- a/taskcluster/taskgraph/test/test_generator.py
+++ b/taskcluster/taskgraph/test/test_generator.py
@@ -43,24 +43,27 @@ class FakeKind(Kind):
 
     def load_tasks(self, parameters, loaded_tasks):
         FakeKind.loaded_kinds.append(self.name)
         return super(FakeKind, self).load_tasks(parameters, loaded_tasks)
 
 
 class WithFakeKind(TaskGraphGenerator):
 
-    def _load_kinds(self):
+    def _load_kinds(self, graph_config):
         for kind_name, cfg in self.parameters['_kinds']:
             config = {
                 'transforms': [],
             }
             if cfg:
                 config.update(cfg)
-            yield FakeKind(kind_name, '/fake', config)
+            yield FakeKind(kind_name, '/fake', config, graph_config)
+
+    def _load_graph_config(self):
+        return {}
 
 
 class FakeParameters(dict):
     strict = True
 
 
 class FakeOptimization(OptimizationStrategy):
     def __init__(self, mode, *args, **kwargs):
@@ -83,17 +86,17 @@ class TestGenerator(unittest.TestCase):
     def patch(self, monkeypatch):
         self.patch = monkeypatch
 
     def maketgg(self, target_tasks=None, kinds=[('_fake', [])], params=None):
         params = params or {}
         FakeKind.loaded_kinds = []
         self.target_tasks = target_tasks or []
 
-        def target_tasks_method(full_task_graph, parameters):
+        def target_tasks_method(full_task_graph, parameters, graph_config):
             return self.target_tasks
 
         def make_fake_strategies():
             return {mode: FakeOptimization(mode)
                     for mode in ('always', 'never', 'even', 'odd')}
 
         target_tasks_mod._target_task_methods['test_method'] = target_tasks_method
         self.patch.setattr(optimize_mod, '_make_default_strategies', make_fake_strategies)
--- a/taskcluster/taskgraph/test/test_target_tasks.py
+++ b/taskcluster/taskgraph/test/test_target_tasks.py
@@ -12,17 +12,17 @@ from taskgraph import try_option_syntax
 from taskgraph.graph import Graph
 from taskgraph.taskgraph import TaskGraph
 from taskgraph.task import Task
 from mozunit import main
 
 
 class FakeTryOptionSyntax(object):
 
-    def __init__(self, message, task_graph):
+    def __init__(self, message, task_graph, graph_config):
         self.trigger_tests = 0
         self.talos_trigger_tests = 0
         self.notifications = None
         self.env = []
         self.profile = False
         self.tag = None
         self.no_retry = False
 
@@ -35,17 +35,17 @@ class TestTargetTasks(unittest.TestCase)
     def default_matches(self, run_on_projects, project):
         method = target_tasks.get_method('default')
         graph = TaskGraph(tasks={
             'a': Task(kind='build', label='a',
                       attributes={'run_on_projects': run_on_projects},
                       task={}),
         }, graph=Graph(nodes={'a'}, edges=set()))
         parameters = {'project': project}
-        return 'a' in method(graph, parameters)
+        return 'a' in method(graph, parameters, {})
 
     def test_default_all(self):
         """run_on_projects=[all] includes release, integration, and other projects"""
         self.assertTrue(self.default_matches(['all'], 'mozilla-central'))
         self.assertTrue(self.default_matches(['all'], 'mozilla-inbound'))
         self.assertTrue(self.default_matches(['all'], 'baobab'))
 
     def test_default_integration(self):
@@ -89,34 +89,34 @@ class TestTargetTasks(unittest.TestCase)
         tg = self.make_task_graph()
         method = target_tasks.get_method('try_tasks')
         params = {
             'try_mode': None,
             'project': 'try',
             'message': '',
         }
         # only runs the task with run_on_projects: try
-        self.assertEqual(method(tg, params), [])
+        self.assertEqual(method(tg, params, {}), [])
 
     def test_try_option_syntax(self):
         "try_mode = try_option_syntax uses TryOptionSyntax"
         tg = self.make_task_graph()
         method = target_tasks.get_method('try_tasks')
         with self.fake_TryOptionSyntax():
             params = {
                 'try_mode': 'try_option_syntax',
                 'message': 'try: -p all',
             }
-            self.assertEqual(method(tg, params), ['b'])
+            self.assertEqual(method(tg, params, {}), ['b'])
 
     def test_try_task_config(self):
         "try_mode = try_task_config uses the try config"
         tg = self.make_task_graph()
         method = target_tasks.get_method('try_tasks')
         params = {
             'try_mode': 'try_task_config',
             'try_task_config': {'tasks': ['a']},
         }
-        self.assertEqual(method(tg, params), ['a'])
+        self.assertEqual(method(tg, params, {}), ['a'])
 
 
 if __name__ == '__main__':
     main()
--- a/taskcluster/taskgraph/test/test_try_option_syntax.py
+++ b/taskcluster/taskgraph/test/test_try_option_syntax.py
@@ -2,17 +2,16 @@
 # 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/.
 
 from __future__ import absolute_import, print_function, unicode_literals
 
 import unittest
 
 from taskgraph.try_option_syntax import TryOptionSyntax, parse_message
-from taskgraph.try_option_syntax import RIDEALONG_BUILDS
 from taskgraph.graph import Graph
 from taskgraph.taskgraph import TaskGraph
 from taskgraph.task import Task
 from mozunit import main
 
 
 def unittest_task(n, tp, bt='opt'):
     return (n, Task('test', n, {
@@ -44,16 +43,25 @@ tasks = {k: v for k, v in [
     unittest_task('extra4', 'linux64/debug', 'debug'),
     unittest_task('extra5', 'linux/this'),
     unittest_task('extra6', 'linux/that'),
     unittest_task('extra7', 'linux/other'),
     unittest_task('extra8', 'linux64/asan'),
     talos_task('extra9', 'linux64/psan'),
 ]}
 
+RIDEALONG_BUILDS = {
+    'linux': ['linux-ridealong'],
+    'linux64': ['linux64-ridealong'],
+}
+
+GRAPH_CONFIG = {
+    'try': {'ridealong-builds': RIDEALONG_BUILDS},
+}
+
 for r in RIDEALONG_BUILDS.values():
     tasks.update({k: v for k, v in [
         unittest_task(n + '-test', n) for n in r
     ]})
 
 unittest_tasks = {k: v for k, v in tasks.iteritems()
                   if 'unittest_try_name' in v.attributes}
 talos_tasks = {k: v for k, v in tasks.iteritems()
@@ -61,283 +69,283 @@ talos_tasks = {k: v for k, v in tasks.it
 graph_with_jobs = TaskGraph(tasks, Graph(set(tasks), set()))
 
 
 class TestTryOptionSyntax(unittest.TestCase):
 
     def test_unknown_args(self):
         "unknown arguments are ignored"
         parameters = {'try_options': parse_message('try: --doubledash -z extra')}
-        tos = TryOptionSyntax(parameters, graph_with_jobs)
+        tos = TryOptionSyntax(parameters, graph_with_jobs, GRAPH_CONFIG)
         # equilvant to "try:"..
         self.assertEqual(tos.build_types, [])
         self.assertEqual(tos.jobs, [])
 
     def test_apostrophe_in_message(self):
         "apostrophe does not break parsing"
         parameters = {'try_options': parse_message('Increase spammy log\'s log level. try: -b do')}
-        tos = TryOptionSyntax(parameters, graph_with_jobs)
+        tos = TryOptionSyntax(parameters, graph_with_jobs, GRAPH_CONFIG)
         self.assertEqual(sorted(tos.build_types), ['debug', 'opt'])
 
     def test_b_do(self):
         "-b do should produce both build_types"
         parameters = {'try_options': parse_message('try: -b do')}
-        tos = TryOptionSyntax(parameters, graph_with_jobs)
+        tos = TryOptionSyntax(parameters, graph_with_jobs, GRAPH_CONFIG)
         self.assertEqual(sorted(tos.build_types), ['debug', 'opt'])
 
     def test_b_d(self):
         "-b d should produce build_types=['debug']"
         parameters = {'try_options': parse_message('try: -b d')}
-        tos = TryOptionSyntax(parameters, graph_with_jobs)
+        tos = TryOptionSyntax(parameters, graph_with_jobs, GRAPH_CONFIG)
         self.assertEqual(sorted(tos.build_types), ['debug'])
 
     def test_b_o(self):
         "-b o should produce build_types=['opt']"
         parameters = {'try_options': parse_message('try: -b o')}
-        tos = TryOptionSyntax(parameters, graph_with_jobs)
+        tos = TryOptionSyntax(parameters, graph_with_jobs, GRAPH_CONFIG)
         self.assertEqual(sorted(tos.build_types), ['opt'])
 
     def test_build_o(self):
         "--build o should produce build_types=['opt']"
         parameters = {'try_options': parse_message('try: --build o')}
-        tos = TryOptionSyntax(parameters, graph_with_jobs)
+        tos = TryOptionSyntax(parameters, graph_with_jobs, GRAPH_CONFIG)
         self.assertEqual(sorted(tos.build_types), ['opt'])
 
     def test_b_dx(self):
         "-b dx should produce build_types=['debug'], silently ignoring the x"
         parameters = {'try_options': parse_message('try: -b dx')}
-        tos = TryOptionSyntax(parameters, graph_with_jobs)
+        tos = TryOptionSyntax(parameters, graph_with_jobs, GRAPH_CONFIG)
         self.assertEqual(sorted(tos.build_types), ['debug'])
 
     def test_j_job(self):
         "-j somejob sets jobs=['somejob']"
         parameters = {'try_options': parse_message('try: -j somejob')}
-        tos = TryOptionSyntax(parameters, graph_with_jobs)
+        tos = TryOptionSyntax(parameters, graph_with_jobs, GRAPH_CONFIG)
         self.assertEqual(sorted(tos.jobs), ['somejob'])
 
     def test_j_jobs(self):
         "-j job1,job2 sets jobs=['job1', 'job2']"
         parameters = {'try_options': parse_message('try: -j job1,job2')}
-        tos = TryOptionSyntax(parameters, graph_with_jobs)
+        tos = TryOptionSyntax(parameters, graph_with_jobs, GRAPH_CONFIG)
         self.assertEqual(sorted(tos.jobs), ['job1', 'job2'])
 
     def test_j_all(self):
         "-j all sets jobs=None"
         parameters = {'try_options': parse_message('try: -j all')}
-        tos = TryOptionSyntax(parameters, graph_with_jobs)
+        tos = TryOptionSyntax(parameters, graph_with_jobs, GRAPH_CONFIG)
         self.assertEqual(tos.jobs, None)
 
     def test_j_twice(self):
         "-j job1 -j job2 sets jobs=job1, job2"
         parameters = {'try_options': parse_message('try: -j job1 -j job2')}
-        tos = TryOptionSyntax(parameters, graph_with_jobs)
+        tos = TryOptionSyntax(parameters, graph_with_jobs, GRAPH_CONFIG)
         self.assertEqual(sorted(tos.jobs), sorted(['job1', 'job2']))
 
     def test_p_all(self):
         "-p all sets platforms=None"
         parameters = {'try_options': parse_message('try: -p all')}
-        tos = TryOptionSyntax(parameters, graph_with_jobs)
+        tos = TryOptionSyntax(parameters, graph_with_jobs, GRAPH_CONFIG)
         self.assertEqual(tos.platforms, None)
 
     def test_p_linux(self):
-        "-p linux sets platforms=['linux', 'linux-l10n']"
+        "-p linux sets platforms=['linux', 'linux-ridealong']"
         parameters = {'try_options': parse_message('try: -p linux')}
-        tos = TryOptionSyntax(parameters, graph_with_jobs)
-        self.assertEqual(tos.platforms, ['linux', 'linux-l10n'])
+        tos = TryOptionSyntax(parameters, graph_with_jobs, GRAPH_CONFIG)
+        self.assertEqual(tos.platforms, ['linux', 'linux-ridealong'])
 
     def test_p_linux_win32(self):
-        "-p linux,win32 sets platforms=['linux', 'linux-l10n', 'win32']"
+        "-p linux,win32 sets platforms=['linux', 'linux-ridealong', 'win32']"
         parameters = {'try_options': parse_message('try: -p linux,win32')}
-        tos = TryOptionSyntax(parameters, graph_with_jobs)
-        self.assertEqual(sorted(tos.platforms), ['linux', 'linux-l10n', 'win32'])
+        tos = TryOptionSyntax(parameters, graph_with_jobs, GRAPH_CONFIG)
+        self.assertEqual(sorted(tos.platforms), ['linux', 'linux-ridealong', 'win32'])
 
     def test_p_expands_ridealongs(self):
         "-p linux,linux64 includes the RIDEALONG_BUILDS"
         parameters = {'try_options': parse_message('try: -p linux,linux64')}
-        tos = TryOptionSyntax(parameters, graph_with_jobs)
+        tos = TryOptionSyntax(parameters, graph_with_jobs, GRAPH_CONFIG)
         platforms = set(['linux'] + RIDEALONG_BUILDS['linux'])
         platforms |= set(['linux64'] + RIDEALONG_BUILDS['linux64'])
         self.assertEqual(sorted(tos.platforms), sorted(platforms))
 
     def test_u_none(self):
         "-u none sets unittests=[]"
         parameters = {'try_options': parse_message('try: -u none')}
-        tos = TryOptionSyntax(parameters, graph_with_jobs)
+        tos = TryOptionSyntax(parameters, graph_with_jobs, GRAPH_CONFIG)
         self.assertEqual(sorted(tos.unittests), [])
 
     def test_u_all(self):
         "-u all sets unittests=[..whole list..]"
         parameters = {'try_options': parse_message('try: -u all')}
-        tos = TryOptionSyntax(parameters, graph_with_jobs)
+        tos = TryOptionSyntax(parameters, graph_with_jobs, GRAPH_CONFIG)
         self.assertEqual(sorted(tos.unittests), sorted([{'test': t} for t in unittest_tasks]))
 
     def test_u_single(self):
         "-u mochitest-webgl sets unittests=[mochitest-webgl]"
         parameters = {'try_options': parse_message('try: -u mochitest-webgl')}
-        tos = TryOptionSyntax(parameters, graph_with_jobs)
+        tos = TryOptionSyntax(parameters, graph_with_jobs, GRAPH_CONFIG)
         self.assertEqual(sorted(tos.unittests), sorted([{'test': 'mochitest-webgl'}]))
 
     def test_u_alias(self):
         "-u mochitest-gl sets unittests=[mochitest-webgl]"
         parameters = {'try_options': parse_message('try: -u mochitest-gl')}
-        tos = TryOptionSyntax(parameters, graph_with_jobs)
+        tos = TryOptionSyntax(parameters, graph_with_jobs, GRAPH_CONFIG)
         self.assertEqual(sorted(tos.unittests), sorted([{'test': 'mochitest-webgl'}]))
 
     def test_u_multi_alias(self):
         "-u e10s sets unittests=[all e10s unittests]"
         parameters = {'try_options': parse_message('try: -u e10s')}
-        tos = TryOptionSyntax(parameters, graph_with_jobs)
+        tos = TryOptionSyntax(parameters, graph_with_jobs, GRAPH_CONFIG)
         self.assertEqual(sorted(tos.unittests), sorted([
             {'test': t} for t in unittest_tasks if 'e10s' in t
         ]))
 
     def test_u_commas(self):
         "-u mochitest-webgl,gtest sets unittests=both"
         parameters = {'try_options': parse_message('try: -u mochitest-webgl,gtest')}
-        tos = TryOptionSyntax(parameters, graph_with_jobs)
+        tos = TryOptionSyntax(parameters, graph_with_jobs, GRAPH_CONFIG)
         self.assertEqual(sorted(tos.unittests), sorted([
             {'test': 'mochitest-webgl'},
             {'test': 'gtest'},
         ]))
 
     def test_u_chunks(self):
         "-u gtest-3,gtest-4 selects the third and fourth chunk of gtest"
         parameters = {'try_options': parse_message('try: -u gtest-3,gtest-4')}
-        tos = TryOptionSyntax(parameters, graph_with_jobs)
+        tos = TryOptionSyntax(parameters, graph_with_jobs, GRAPH_CONFIG)
         self.assertEqual(sorted(tos.unittests), sorted([
             {'test': 'gtest', 'only_chunks': set('34')},
         ]))
 
     def test_u_platform(self):
         "-u gtest[linux] selects the linux platform for gtest"
         parameters = {'try_options': parse_message('try: -u gtest[linux]')}
-        tos = TryOptionSyntax(parameters, graph_with_jobs)
+        tos = TryOptionSyntax(parameters, graph_with_jobs, GRAPH_CONFIG)
         self.assertEqual(sorted(tos.unittests), sorted([
             {'test': 'gtest', 'platforms': ['linux']},
         ]))
 
     def test_u_platforms(self):
         "-u gtest[linux,win32] selects the linux and win32 platforms for gtest"
         parameters = {'try_options': parse_message('try: -u gtest[linux,win32]')}
-        tos = TryOptionSyntax(parameters, graph_with_jobs)
+        tos = TryOptionSyntax(parameters, graph_with_jobs, GRAPH_CONFIG)
         self.assertEqual(sorted(tos.unittests), sorted([
             {'test': 'gtest', 'platforms': ['linux', 'win32']},
         ]))
 
     def test_u_platforms_pretty(self):
         """-u gtest[Ubuntu] selects the linux, linux64, linux64-asan, linux64-stylo-disabled,
         and linux64-stylo-sequential platforms for gtest"""
         parameters = {'try_options': parse_message('try: -u gtest[Ubuntu]')}
-        tos = TryOptionSyntax(parameters, graph_with_jobs)
+        tos = TryOptionSyntax(parameters, graph_with_jobs, GRAPH_CONFIG)
         self.assertEqual(sorted(tos.unittests), sorted([
             {'test': 'gtest', 'platforms': ['linux32', 'linux64', 'linux64-asan',
                                             'linux64-stylo-disabled', 'linux64-stylo-sequential']},
         ]))
 
     def test_u_platforms_negated(self):
         "-u gtest[-linux] selects all platforms but linux for gtest"
         parameters = {'try_options': parse_message('try: -u gtest[-linux]')}
-        tos = TryOptionSyntax(parameters, graph_with_jobs)
+        tos = TryOptionSyntax(parameters, graph_with_jobs, GRAPH_CONFIG)
         all_platforms = set([x.attributes['test_platform'] for x in unittest_tasks.values()])
         self.assertEqual(sorted(tos.unittests[0]['platforms']), sorted(
             [x for x in all_platforms if x != 'linux']
         ))
 
     def test_u_platforms_negated_pretty(self):
         "-u gtest[Ubuntu,-x64] selects just linux for gtest"
         parameters = {'try_options': parse_message('try: -u gtest[Ubuntu,-x64]')}
-        tos = TryOptionSyntax(parameters, graph_with_jobs)
+        tos = TryOptionSyntax(parameters, graph_with_jobs, GRAPH_CONFIG)
         self.assertEqual(sorted(tos.unittests), sorted([
             {'test': 'gtest', 'platforms': ['linux32']},
         ]))
 
     def test_u_chunks_platforms(self):
         "-u gtest-1[linux,win32] selects the linux and win32 platforms for chunk 1 of gtest"
         parameters = {'try_options': parse_message('try: -u gtest-1[linux,win32]')}
-        tos = TryOptionSyntax(parameters, graph_with_jobs)
+        tos = TryOptionSyntax(parameters, graph_with_jobs, GRAPH_CONFIG)
         self.assertEqual(sorted(tos.unittests), sorted([
             {'test': 'gtest', 'platforms': ['linux', 'win32'], 'only_chunks': set('1')},
         ]))
 
     def test_t_none(self):
         "-t none sets talos=[]"
         parameters = {'try_options': parse_message('try: -t none')}
-        tos = TryOptionSyntax(parameters, graph_with_jobs)
+        tos = TryOptionSyntax(parameters, graph_with_jobs, GRAPH_CONFIG)
         self.assertEqual(sorted(tos.talos), [])
 
     def test_t_all(self):
         "-t all sets talos=[..whole list..]"
         parameters = {'try_options': parse_message('try: -t all')}
-        tos = TryOptionSyntax(parameters, graph_with_jobs)
+        tos = TryOptionSyntax(parameters, graph_with_jobs, GRAPH_CONFIG)
         self.assertEqual(sorted(tos.talos), sorted([{'test': t} for t in talos_tasks]))
 
     def test_t_single(self):
         "-t mochitest-webgl sets talos=[mochitest-webgl]"
         parameters = {'try_options': parse_message('try: -t mochitest-webgl')}
-        tos = TryOptionSyntax(parameters, graph_with_jobs)
+        tos = TryOptionSyntax(parameters, graph_with_jobs, GRAPH_CONFIG)
         self.assertEqual(sorted(tos.talos), sorted([{'test': 'mochitest-webgl'}]))
 
     # -t shares an implementation with -u, so it's not tested heavily
 
     def test_trigger_tests(self):
         "--rebuild 10 sets trigger_tests"
         parameters = {'try_options': parse_message('try: --rebuild 10')}
-        tos = TryOptionSyntax(parameters, graph_with_jobs)
+        tos = TryOptionSyntax(parameters, graph_with_jobs, GRAPH_CONFIG)
         self.assertEqual(tos.trigger_tests, 10)
 
     def test_talos_trigger_tests(self):
         "--rebuild-talos 10 sets talos_trigger_tests"
         parameters = {'try_options': parse_message('try: --rebuild-talos 10')}
-        tos = TryOptionSyntax(parameters, graph_with_jobs)
+        tos = TryOptionSyntax(parameters, graph_with_jobs, GRAPH_CONFIG)
         self.assertEqual(tos.talos_trigger_tests, 10)
 
     def test_interactive(self):
         "--interactive sets interactive"
         parameters = {'try_options': parse_message('try: --interactive')}
-        tos = TryOptionSyntax(parameters, graph_with_jobs)
+        tos = TryOptionSyntax(parameters, graph_with_jobs, GRAPH_CONFIG)
         self.assertEqual(tos.interactive, True)
 
     def test_all_email(self):
         "--all-emails sets notifications"
         parameters = {'try_options': parse_message('try: --all-emails')}
-        tos = TryOptionSyntax(parameters, graph_with_jobs)
+        tos = TryOptionSyntax(parameters, graph_with_jobs, GRAPH_CONFIG)
         self.assertEqual(tos.notifications, 'all')
 
     def test_fail_email(self):
         "--failure-emails sets notifications"
         parameters = {'try_options': parse_message('try: --failure-emails')}
-        tos = TryOptionSyntax(parameters, graph_with_jobs)
+        tos = TryOptionSyntax(parameters, graph_with_jobs, GRAPH_CONFIG)
         self.assertEqual(tos.notifications, 'failure')
 
     def test_no_email(self):
         "no email settings don't set notifications"
         parameters = {'try_options': parse_message('try:')}
-        tos = TryOptionSyntax(parameters, graph_with_jobs)
+        tos = TryOptionSyntax(parameters, graph_with_jobs, GRAPH_CONFIG)
         self.assertEqual(tos.notifications, None)
 
     def test_setenv(self):
         "--setenv VAR=value adds a environment variables setting to env"
         parameters = {'try_options': parse_message(
             'try: --setenv VAR1=value1 --setenv VAR2=value2')}
-        tos = TryOptionSyntax(parameters, graph_with_jobs)
+        tos = TryOptionSyntax(parameters, graph_with_jobs, GRAPH_CONFIG)
         self.assertEqual(tos.env, ['VAR1=value1', 'VAR2=value2'])
 
     def test_profile(self):
         "--geckoProfile sets profile to true"
         parameters = {'try_options': parse_message('try: --geckoProfile')}
-        tos = TryOptionSyntax(parameters, graph_with_jobs)
+        tos = TryOptionSyntax(parameters, graph_with_jobs, GRAPH_CONFIG)
         self.assertTrue(tos.profile)
 
     def test_tag(self):
         "--tag TAG sets tag to TAG value"
         parameters = {'try_options': parse_message('try: --tag tagName')}
-        tos = TryOptionSyntax(parameters, graph_with_jobs)
+        tos = TryOptionSyntax(parameters, graph_with_jobs, GRAPH_CONFIG)
         self.assertEqual(tos.tag, 'tagName')
 
     def test_no_retry(self):
         "--no-retry sets no_retry to true"
         parameters = {'try_options': parse_message('try: --no-retry')}
-        tos = TryOptionSyntax(parameters, graph_with_jobs)
+        tos = TryOptionSyntax(parameters, graph_with_jobs, GRAPH_CONFIG)
         self.assertTrue(tos.no_retry)
 
 
 if __name__ == '__main__':
     main()
--- a/taskcluster/taskgraph/transforms/base.py
+++ b/taskcluster/taskgraph/transforms/base.py
@@ -5,33 +5,36 @@
 from __future__ import absolute_import, print_function, unicode_literals
 
 
 class TransformConfig(object):
     """A container for configuration affecting transforms.  The `config`
     argument to transforms is an instance of this class, possibly with
     additional kind-specific attributes beyond those set here."""
     def __init__(self, kind, path, config, params,
-                 kind_dependencies_tasks=None):
+                 kind_dependencies_tasks=None, graph_config=None):
         # the name of the current kind
         self.kind = kind
 
         # the path to the kind configuration directory
         self.path = path
 
         # the parsed contents of kind.yml
         self.config = config
 
         # the parameters for this task-graph generation run
         self.params = params
 
         # a list of all the tasks associated with the kind dependencies of the
         # current kind
         self.kind_dependencies_tasks = kind_dependencies_tasks
 
+        # Global configuration of the taskgraph
+        self.graph_config = graph_config or {}
+
 
 class TransformSequence(object):
     """
     Container for a sequence of transforms.  Each transform is represented as a
     callable taking (config, items) and returning a generator which will yield
     transformed items.  The resulting sequence has the same interface.
 
     This is convenient to use in a file full of transforms, as it provides a
--- a/taskcluster/taskgraph/transforms/task.py
+++ b/taskcluster/taskgraph/transforms/task.py
@@ -536,70 +536,16 @@ task_description_schema = Schema({
         Required('dry-run', default=True): bool,
         Optional('rollout-percentage'): int,
     }),
 })
 
 TC_TREEHERDER_SCHEMA_URL = 'https://github.com/taskcluster/taskcluster-treeherder/' \
                            'blob/master/schemas/task-treeherder-config.yml'
 
-GROUP_NAMES = {
-    'cram': 'Cram tests',
-    'mocha': 'Mocha unit tests',
-    'py': 'Python unit tests',
-    'tc': 'Executed by TaskCluster',
-    'tc-A': 'Android Gradle tests executed by TaskCluster',
-    'tc-e10s': 'Executed by TaskCluster with e10s',
-    'tc-Fxfn-l': 'Firefox functional tests (local) executed by TaskCluster',
-    'tc-Fxfn-l-e10s': 'Firefox functional tests (local) executed by TaskCluster with e10s',
-    'tc-Fxfn-r': 'Firefox functional tests (remote) executed by TaskCluster',
-    'tc-Fxfn-r-e10s': 'Firefox functional tests (remote) executed by TaskCluster with e10s',
-    'tc-M': 'Mochitests executed by TaskCluster',
-    'tc-M-e10s': 'Mochitests executed by TaskCluster with e10s',
-    'tc-M-V': 'Mochitests on Valgrind executed by TaskCluster',
-    'tc-R': 'Reftests executed by TaskCluster',
-    'tc-R-e10s': 'Reftests executed by TaskCluster with e10s',
-    'tc-T': 'Talos performance tests executed by TaskCluster',
-    'tc-Tsd': 'Talos performance tests executed by TaskCluster with Stylo disabled',
-    'tc-Tss': 'Talos performance tests executed by TaskCluster with Stylo sequential',
-    'tc-T-e10s': 'Talos performance tests executed by TaskCluster with e10s',
-    'tc-Tsd-e10s': 'Talos performance tests executed by TaskCluster with e10s, Stylo disabled',
-    'tc-Tss-e10s': 'Talos performance tests executed by TaskCluster with e10s, Stylo sequential',
-    'tc-tt-c': 'Telemetry client marionette tests',
-    'tc-tt-c-e10s': 'Telemetry client marionette tests with e10s',
-    'tc-SY-e10s': 'Are we slim yet tests by TaskCluster with e10s',
-    'tc-SYsd-e10s': 'Are we slim yet tests by TaskCluster with e10s, Stylo disabled',
-    'tc-SYss-e10s': 'Are we slim yet tests by TaskCluster with e10s, Stylo sequential',
-    'tc-VP': 'VideoPuppeteer tests executed by TaskCluster',
-    'tc-W': 'Web platform tests executed by TaskCluster',
-    'tc-W-e10s': 'Web platform tests executed by TaskCluster with e10s',
-    'tc-X': 'Xpcshell tests executed by TaskCluster',
-    'tc-X-e10s': 'Xpcshell tests executed by TaskCluster with e10s',
-    'tc-L10n': 'Localised Repacks executed by Taskcluster',
-    'tc-L10n-Rpk': 'Localized Repackaged Repacks executed by Taskcluster',
-    'tc-BM-L10n': 'Beetmover for locales executed by Taskcluster',
-    'tc-BMR-L10n': 'Beetmover repackages for locales executed by Taskcluster',
-    'c-Up': 'Balrog submission of complete updates',
-    'tc-cs': 'Checksum signing executed by Taskcluster',
-    'tc-rs': 'Repackage signing executed by Taskcluster',
-    'tc-BMcs': 'Beetmover checksums, executed by Taskcluster',
-    'Aries': 'Aries Device Image',
-    'Nexus 5-L': 'Nexus 5-L Device Image',
-    'I': 'Docker Image Builds',
-    'TL': 'Toolchain builds for Linux 64-bits',
-    'TM': 'Toolchain builds for OSX',
-    'TMW': 'Toolchain builds for Windows MinGW',
-    'TW32': 'Toolchain builds for Windows 32-bits',
-    'TW64': 'Toolchain builds for Windows 64-bits',
-    'SM-tc': 'Spidermonkey builds',
-    'pub': 'APK publishing',
-    'p': 'Partial generation',
-    'ps': 'Partials signing',
-    'Rel': 'Release promotion',
-}
 
 UNKNOWN_GROUP_NAME = "Treeherder group {} has no name; add it to " + __file__
 
 V2_ROUTE_TEMPLATES = [
     "index.gecko.v2.{project}.latest.{product}.{job-name}",
     "index.gecko.v2.{project}.pushdate.{build_date_long}.{product}.{job-name}",
     "index.gecko.v2.{project}.pushlog-id.{pushlog_id}.{product}.{job-name}",
     "index.gecko.v2.{project}.revision.{head_rev}.{product}.{job-name}",
@@ -1239,22 +1185,23 @@ def build_task(config, tasks):
             extra['treeherderEnv'] = task_th['environments']
 
             treeherder = extra.setdefault('treeherder', {})
 
             machine_platform, collection = task_th['platform'].split('/', 1)
             treeherder['machine'] = {'platform': machine_platform}
             treeherder['collection'] = {collection: True}
 
+            group_names = config.graph_config['treeherder']['group-names']
             groupSymbol, symbol = split_symbol(task_th['symbol'])
             if groupSymbol != '?':
                 treeherder['groupSymbol'] = groupSymbol
-                if groupSymbol not in GROUP_NAMES:
+                if groupSymbol not in group_names:
                     raise Exception(UNKNOWN_GROUP_NAME.format(groupSymbol))
-                treeherder['groupName'] = GROUP_NAMES[groupSymbol]
+                treeherder['groupName'] = group_names[groupSymbol]
             treeherder['symbol'] = symbol
             if len(symbol) > 25 or len(groupSymbol) > 25:
                 raise RuntimeError("Treeherder group and symbol names must not be longer than "
                                    "25 characters: {} (see {})".format(
                                        task_th['symbol'],
                                        TC_TREEHERDER_SCHEMA_URL,
                                        ))
             treeherder['jobKind'] = task_th['kind']
--- a/taskcluster/taskgraph/transforms/tests.py
+++ b/taskcluster/taskgraph/transforms/tests.py
@@ -867,22 +867,26 @@ def set_worker_type(config, tests):
         test_platform = test['test-platform']
         try_options = config.params['try_options'] if config.params['try_options'] else {}
         if test.get('worker-type'):
             # This test already has its worker type defined, so just use that (yields below)
             pass
         elif test_platform.startswith('macosx'):
             test['worker-type'] = MACOSX_WORKER_TYPES['macosx64']
         elif test_platform.startswith('win'):
-            if test.get('suite', '') == 'talos' and \
-                    not any('taskcluster' in cfg for cfg in test['mozharness']['config']):
-                test['worker-type'] = 'buildbot-bridge/buildbot-bridge'
+            win_worker_type_platform = WINDOWS_WORKER_TYPES[
+                test_platform.split('/')[0]
+            ]
+            if test.get('suite', '') == 'talos':
+                if try_options.get('taskcluster_worker'):
+                    test['worker-type'] = win_worker_type_platform['hardware']
+                else:
+                    test['worker-type'] = 'buildbot-bridge/buildbot-bridge'
             else:
-                test['worker-type'] = \
-                    WINDOWS_WORKER_TYPES[test_platform.split('/')[0]][test['virtualization']]
+                test['worker-type'] = win_worker_type_platform[test['virtualization']]
         elif test_platform.startswith('linux') or test_platform.startswith('android'):
             if test.get('suite', '') == 'talos' and test['build-platform'] != 'linux64-ccov/opt':
                 if try_options.get('taskcluster_worker'):
                     test['worker-type'] = 'releng-hardware/gecko-t-linux-talos'
                 else:
                     test['worker-type'] = 'buildbot-bridge/buildbot-bridge'
             else:
                 test['worker-type'] = LINUX_WORKER_TYPES[test['instance-size']]
--- a/taskcluster/taskgraph/try_option_syntax.py
+++ b/taskcluster/taskgraph/try_option_syntax.py
@@ -147,45 +147,16 @@ UNITTEST_PLATFORM_PRETTY_NAMES = {
     'Windows 7 VM':  ['windows7-32-vm'],
     'Windows 8':  ['windows8-64'],
     'Windows 10':  ['windows10-64'],
     # 'Windows XP': [..TODO..],
     # 'win32': [..TODO..],
     # 'win64': [..TODO..],
 }
 
-# We have a few platforms for which we want to do some "extra" builds, or at
-# least build-ish things.  Sort of.  Anyway, these other things are implemented
-# as different "platforms".  These do *not* automatically ride along with "-p
-# all"
-RIDEALONG_BUILDS = {
-    'android-api-16': [
-        'android-api-16-l10n',
-    ],
-    'linux': [
-        'linux-l10n',
-    ],
-    'linux64': [
-        'linux64-l10n',
-        'sm-plain',
-        'sm-nonunified',
-        'sm-arm-sim',
-        'sm-arm64-sim',
-        'sm-compacting',
-        'sm-rootanalysis',
-        'sm-package',
-        'sm-tsan',
-        'sm-asan',
-        'sm-mozjs-sys',
-        'sm-msan',
-        'sm-fuzzing',
-        'sm-rust-bindings',
-    ],
-}
-
 TEST_CHUNK_SUFFIX = re.compile('(.*)-([0-9]+)$')
 
 
 def escape_whitespace_in_brackets(input_str):
     '''
     In tests you may restrict them by platform [] inside of the brackets
     whitespace may occur this is typically invalid shell syntax so we escape it
     with backslash sequences    .
@@ -257,17 +228,17 @@ def parse_message(message):
     # In order to run test jobs multiple times
     parser.add_argument('--rebuild', dest='trigger_tests', type=int, default=1)
     args, _ = parser.parse_known_args(parts)
     return vars(args)
 
 
 class TryOptionSyntax(object):
 
-    def __init__(self, parameters, full_task_graph):
+    def __init__(self, parameters, full_task_graph, graph_config):
         """
         Apply the try options in parameters.
 
         The resulting object has attributes:
 
         - build_types: a list containing zero or more of 'opt' and 'debug'
         - platforms: a list of selected platform names, or None for all
         - unittests: a list of tests, of the form given below, or None for all
@@ -284,16 +255,17 @@ class TryOptionSyntax(object):
         The unittests and talos lists contain dictionaries of the form:
 
         {
             'test': '<suite name>',
             'platforms': [..platform names..], # to limit to only certain platforms
             'only_chunks': set([..chunk numbers..]), # to limit only to certain chunks
         }
         """
+        self.graph_config = graph_config
         self.jobs = []
         self.build_types = []
         self.platforms = []
         self.unittests = []
         self.talos = []
         self.trigger_tests = 0
         self.interactive = False
         self.notifications = None
@@ -347,16 +319,17 @@ class TryOptionSyntax(object):
             raise Exception("Unknown build type(s) [%s] specified for try" % ','.join(bad_types))
 
         return build_types
 
     def parse_platforms(self, platform_arg, full_task_graph):
         if platform_arg == 'all':
             return None
 
+        RIDEALONG_BUILDS = self.graph_config['try']['ridealong-builds']
         results = []
         for build in platform_arg.split(','):
             results.append(build)
             if build in RIDEALONG_BUILDS:
                 results.extend(RIDEALONG_BUILDS[build])
                 logger.info("platform %s triggers ridealong builds %s" %
                             (build, ', '.join(RIDEALONG_BUILDS[build])))
 
--- a/testing/marionette/harness/marionette_harness/tests/unit/test_quit_restart.py
+++ b/testing/marionette/harness/marionette_harness/tests/unit/test_quit_restart.py
@@ -193,18 +193,18 @@ class TestQuitRestart(MarionetteTestCase
             self.assertEqual(self.marionette.process_id, self.pid)
         else:
             self.assertNotEqual(self.marionette.process_id, self.pid)
 
         self.assertNotEqual(self.marionette.get_pref("startup.homepage_welcome_url"),
                             "about:")
 
     def test_in_app_restart_safe_mode(self):
-        if self.marionette.session_capabilities["platformName"] != "linux":
-            raise unittest.SkipTest("Bug 1397612 - Hang of Marionette client after the restart.")
+        if self.marionette.session_capabilities["moz:headless"]:
+            raise unittest.SkipTest("Bug 1390848 - Hang of Marionette client after the restart.")
 
         def restart_in_safe_mode():
             with self.marionette.using_context("chrome"):
                 self.marionette.execute_script("""
                   Components.utils.import("resource://gre/modules/Services.jsm");
 
                   let cancelQuit = Cc["@mozilla.org/supports-PRBool;1"]
                                      .createInstance(Ci.nsISupportsPRBool);
--- a/testing/marionette/listener.js
+++ b/testing/marionette/listener.js
@@ -128,24 +128,21 @@ const loadListener = {
     timeout = startTime + timeout - new Date().getTime();
 
     if (timeout <= 0) {
       this.notify(this.timerPageLoad);
       return;
     }
 
     if (waitForUnloaded) {
-      addEventListener("hashchange", this, false);
-      addEventListener("pagehide", this, false);
-      addEventListener("popstate", this, false);
-
-      // The events can only be received when the event listeners are
-      // added to the currently selected frame.
-      curContainer.frame.addEventListener("beforeunload", this);
-      curContainer.frame.addEventListener("unload", this);
+      addEventListener("beforeunload", this, true);
+      addEventListener("hashchange", this, true);
+      addEventListener("pagehide", this, true);
+      addEventListener("popstate", this, true);
+      addEventListener("unload", this, true);
 
       Services.obs.addObserver(this, "outer-window-destroyed");
 
     } else {
       // The frame script got reloaded due to a new content process.
       // Due to the time it takes to re-register the browser in Marionette,
       // it can happen that page load events are missed before the listeners
       // are getting attached again. By checking the document readyState the
@@ -155,18 +152,18 @@ const loadListener = {
       logger.debug(`Check readyState "${readyState} for "${documentURI}"`);
 
       // If the page load has already finished, don't setup listeners and
       // timers but return immediatelly.
       if (this.handleReadyState(readyState, documentURI)) {
         return;
       }
 
-      addEventListener("DOMContentLoaded", loadListener);
-      addEventListener("pageshow", loadListener);
+      addEventListener("DOMContentLoaded", loadListener, true);
+      addEventListener("pageshow", loadListener, true);
     }
 
     this.timerPageLoad.initWithCallback(
         this, timeout, Ci.nsITimer.TYPE_ONE_SHOT);
   },
 
   /**
    * Stop listening for page unload/load events.
@@ -175,32 +172,23 @@ const loadListener = {
     if (this.timerPageLoad) {
       this.timerPageLoad.cancel();
     }
 
     if (this.timerPageUnload) {
       this.timerPageUnload.cancel();
     }
 
-    removeEventListener("hashchange", this);
-    removeEventListener("pagehide", this);
-    removeEventListener("popstate", this);
-    removeEventListener("DOMContentLoaded", this);
-    removeEventListener("pageshow", this);
-
-    // If the original content window, where the navigation was triggered,
-    // doesn't exist anymore, exceptions can be silently ignored.
-    try {
-      curContainer.frame.removeEventListener("beforeunload", this);
-      curContainer.frame.removeEventListener("unload", this);
-    } catch (e) {
-      if (e.name != "TypeError") {
-        throw e;
-      }
-    }
+    removeEventListener("beforeunload", this, true);
+    removeEventListener("hashchange", this, true);
+    removeEventListener("pagehide", this, true);
+    removeEventListener("popstate", this, true);
+    removeEventListener("DOMContentLoaded", this, true);
+    removeEventListener("pageshow", this, true);
+    removeEventListener("unload", this, true);
 
     // In case the observer was added before the frame script has been
     // reloaded, it will no longer be available. Exceptions can be ignored.
     try {
       Services.obs.removeObserver(this, "outer-window-destroyed");
     } catch (e) {}
   },
 
@@ -225,23 +213,23 @@ const loadListener = {
 
       case "unload":
         this.seenUnload = true;
         break;
 
       case "pagehide":
         this.seenUnload = true;
 
-        removeEventListener("hashchange", this);
-        removeEventListener("pagehide", this);
-        removeEventListener("popstate", this);
+        removeEventListener("hashchange", this, true);
+        removeEventListener("pagehide", this, true);
+        removeEventListener("popstate", this, true);
 
         // Now wait until the target page has been loaded
-        addEventListener("DOMContentLoaded", this, false);
-        addEventListener("pageshow", this, false);
+        addEventListener("DOMContentLoaded", this, true);
+        addEventListener("pageshow", this, true);
         break;
 
       case "hashchange":
       case "popstate":
         this.stop();
         sendOk(this.commandID);
         break;
 
--- a/testing/webdriver/src/capabilities.rs
+++ b/testing/webdriver/src/capabilities.rs
@@ -105,17 +105,17 @@ impl SpecNewSessionParameters {
                     try!(SpecNewSessionParameters::validate_timeouts(value))
                 },
                 "unhandledPromptBehavior" => {
                     try!(SpecNewSessionParameters::validate_unhandled_prompt_behaviour(value))
                 }
                 x => {
                     if !x.contains(":") {
                         return Err(WebDriverError::new(ErrorStatus::InvalidArgument,
-                                                       format!("{} is not a the name of a known capability or a valid extension capability", x)))
+                                                       format!("{} is not the name of a known capability or a valid extension capability", x)))
                     } else {
                         try!(browser_capabilities.validate_custom(x, value));
                     }
                 }
             }
         }
         Ok(capabilities)
     }
@@ -125,17 +125,17 @@ impl SpecNewSessionParameters {
             &Json::String(ref x) => {
                 match &**x {
                     "normal" |
                     "eager" |
                     "none" => {},
                     x => {
                         return Err(WebDriverError::new(
                             ErrorStatus::InvalidArgument,
-                            format!("\"{}\" not a valid page load strategy", x)))
+                            format!("\"{}\" is not a valid page load strategy", x)))
                     }
                 }
             }
             _ => return Err(WebDriverError::new(ErrorStatus::InvalidArgument,
                                                 "pageLoadStrategy is not a string"))
         }
         Ok(())
     }
--- a/toolkit/components/extensions/ext-theme.js
+++ b/toolkit/components/extensions/ext-theme.js
@@ -130,16 +130,19 @@ class Theme {
         case "textcolor":
         case "tab_text":
           this.lwtStyles.textcolor = cssColor;
           break;
         case "toolbar":
           this.lwtStyles.toolbarColor = cssColor;
           break;
         case "toolbar_text":
+        case "bookmark_text":
+          this.lwtStyles.toolbar_text = cssColor;
+          break;
         case "toolbar_field":
         case "toolbar_field_text":
         case "toolbar_top_separator":
         case "toolbar_bottom_separator":
         case "toolbar_vertical_separator":
           this.lwtStyles[color] = cssColor;
           break;
       }
--- a/toolkit/components/extensions/schemas/theme.json
+++ b/toolkit/components/extensions/schemas/theme.json
@@ -68,16 +68,20 @@
               "toolbar": {
                 "type": "string",
                 "optional": true
               },
               "toolbar_text": {
                 "type": "string",
                 "optional": true
               },
+              "bookmark_text": {
+                "type": "string",
+                "optional": true
+              },
               "toolbar_field": {
                 "type": "string",
                 "optional": true
               },
               "toolbar_field_text": {
                 "type": "string",
                 "optional": true
               },
--- a/toolkit/components/extensions/test/browser/browser_ext_themes_toolbars.js
+++ b/toolkit/components/extensions/test/browser/browser_ext_themes_toolbars.js
@@ -50,8 +50,54 @@ add_task(async function test_support_too
 
   info("Checking selected tab colors");
   let selectedTab = document.querySelector(".tabbrowser-tab[selected]");
   Assert.equal(window.getComputedStyle(selectedTab).color,
     "rgb(" + hexToRGB(TOOLBAR_TEXT_COLOR).join(", ") + ")", "Selected tab text color should be set.");
 
   await extension.unload();
 });
+
+add_task(async function test_bookmark_text_property() {
+  const TOOLBAR_COLOR = "#ff00ff";
+  const TOOLBAR_TEXT_COLOR = "#9400ff";
+  let extension = ExtensionTestUtils.loadExtension({
+    manifest: {
+      "theme": {
+        "images": {
+          "headerURL": "image1.png",
+        },
+        "colors": {
+          "accentcolor": ACCENT_COLOR,
+          "textcolor": TEXT_COLOR,
+          "toolbar": TOOLBAR_COLOR,
+          "bookmark_text": TOOLBAR_TEXT_COLOR,
+        },
+      },
+    },
+    files: {
+      "image1.png": BACKGROUND,
+    },
+  });
+
+  await extension.startup();
+
+  let toolbox = document.querySelector("#navigator-toolbox");
+  let toolbars = [...toolbox.querySelectorAll("toolbar:not(#TabsToolbar)")].filter(toolbar => {
+    let bounds = toolbar.getBoundingClientRect();
+    return bounds.width > 0 && bounds.height > 0;
+  });
+
+  info(`Checking toolbar colors for ${toolbars.length} toolbars.`);
+  for (let toolbar of toolbars) {
+    info(`Testing ${toolbar.id}`);
+    Assert.equal(window.getComputedStyle(toolbar).color,
+      "rgb(" + hexToRGB(TOOLBAR_TEXT_COLOR).join(", ") + ")",
+      "bookmark_text should be an alias for toolbar_text");
+  }
+
+  info("Checking selected tab colors");
+  let selectedTab = document.querySelector(".tabbrowser-tab[selected]");
+  Assert.equal(window.getComputedStyle(selectedTab).color,
+    "rgb(" + hexToRGB(TOOLBAR_TEXT_COLOR).join(", ") + ")", "Selected tab text color should be set.");
+
+  await extension.unload();
+});
--- a/toolkit/components/osfile/NativeOSFileInternals.cpp
+++ b/toolkit/components/osfile/NativeOSFileInternals.cpp
@@ -1,8 +1,10 @@
+/* -*- 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/. */
 
 /**
  * Native implementation of some OS.File operations.
  */
 
--- a/toolkit/components/osfile/NativeOSFileInternals.h
+++ b/toolkit/components/osfile/NativeOSFileInternals.h
@@ -1,8 +1,10 @@
+/* -*- 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_nativeosfileinternalservice_h__
 #define mozilla_nativeosfileinternalservice_h__
 
 #include "nsINativeOSFileInternals.h"
--- a/toolkit/content/xul.css
+++ b/toolkit/content/xul.css
@@ -468,19 +468,22 @@ panel[type="arrow"][side][animate="open"
 }
 
 panel[type="arrow"][side][animate="cancel"] {
   -moz-window-transform: none;
 }
 
 %elifndef MOZ_WIDGET_GTK
 
+panel[type="arrow"][side] {
+  will-change: transform, opacity; /* workaround for bug 1414033 */
+}
+
 panel[type="arrow"][side]:not([animate="false"]) {
   opacity: 0;
-  will-change: transform; /* workaround for bug 1414033 */
   transform: translateY(-70px);
   transition-property: transform, opacity;
   transition-duration: 0.18s, 0.18s;
   transition-timing-function:
     var(--animation-easing-function), ease-out;
 }
 
 panel[type="arrow"][side="bottom"]:not([animate="false"]) {
--- a/toolkit/themes/shared/in-content/common.inc.css
+++ b/toolkit/themes/shared/in-content/common.inc.css
@@ -72,24 +72,24 @@ html|hr {
 }
 
 xul|caption {
   -moz-appearance: none;
   margin: 0;
 }
 
 html|h2,
-xul|caption > xul|checkbox,
-xul|caption > xul|label {
+xul|caption xul|checkbox,
+xul|caption xul|label {
   font-weight: 600;
   line-height: 22px;
 }
 
-xul|caption > xul|checkbox,
-xul|caption > xul|label {
+xul|caption xul|checkbox,
+xul|caption xul|label {
   margin: 0 !important;
 }
 
 *|*.main-content {
   padding: 40px 28px;
   overflow: auto;
 }
 
--- a/tools/fuzzing/libfuzzer/FuzzerIO.cpp
+++ b/tools/fuzzing/libfuzzer/FuzzerIO.cpp
@@ -4,16 +4,17 @@
 //
 // This file is distributed under the University of Illinois Open Source
 // License. See LICENSE.TXT for details.
 //
 //===----------------------------------------------------------------------===//
 // IO functions.
 //===----------------------------------------------------------------------===//
 
+#include "mozilla/Unused.h"
 #include "FuzzerIO.h"
 #include "FuzzerDefs.h"
 #include "FuzzerExtFunctions.h"
 #include <algorithm>
 #include <cstdarg>
 #include <fstream>
 #include <iterator>
 #include <sys/stat.h>
@@ -57,17 +58,17 @@ std::string FileToString(const std::stri
 void CopyFileToErr(const std::string &Path) {
   Printf("%s", FileToString(Path).c_str());
 }
 
 void WriteToFile(const Unit &U, const std::string &Path) {
   // Use raw C interface because this function may be called from a sig handler.
   FILE *Out = fopen(Path.c_str(), "w");
   if (!Out) return;
-  fwrite(U.data(), sizeof(U[0]), U.size(), Out);
+  mozilla::Unused << fwrite(U.data(), sizeof(U[0]), U.size(), Out);
   fclose(Out);
 }
 
 void ReadDirToVectorOfUnits(const char *Path, std::vector<Unit> *V,
                             long *Epoch, size_t MaxSize, bool ExitOnError) {
   long E = Epoch ? *Epoch : 0;
   std::vector<std::string> Files;
   ListFilesInDirRecursive(Path, Epoch, &Files, /*TopDir*/true);
--- a/tools/fuzzing/libfuzzer/FuzzerIOPosix.cpp
+++ b/tools/fuzzing/libfuzzer/FuzzerIOPosix.cpp
@@ -6,16 +6,17 @@
 // License. See LICENSE.TXT for details.
 //
 //===----------------------------------------------------------------------===//
 // IO functions implementation using Posix API.
 //===----------------------------------------------------------------------===//
 #include "FuzzerDefs.h"
 #if LIBFUZZER_POSIX
 
+#include "mozilla/Unused.h"
 #include "FuzzerExtFunctions.h"
 #include "FuzzerIO.h"
 #include <cstdarg>
 #include <cstdio>
 #include <dirent.h>
 #include <fstream>
 #include <iterator>
 #include <libgen.h>
@@ -110,14 +111,14 @@ bool IsInterestingCoverageFile(const std
     return false;
   if (FileName == "<null>")
     return false;
   return true;
 }
 
 
 void RawPrint(const char *Str) {
-  write(2, Str, strlen(Str));
+  mozilla::Unused << write(2, Str, strlen(Str));
 }
 
 }  // namespace fuzzer
 
 #endif // LIBFUZZER_POSIX
--- a/tools/lint/eslint/modules.json
+++ b/tools/lint/eslint/modules.json
@@ -26,17 +26,17 @@
   "BootstrapMonitor.jsm": ["monitor"],
   "browser-loader.js": ["BrowserLoader"],
   "browserid_identity.js": ["BrowserIDManager", "AuthenticationError"],
   "CertUtils.jsm": ["BadCertHandler", "checkCert", "readCertPrefs", "validateCert"],
   "clients.js": ["ClientEngine", "ClientsRec"],
   "collection_repair.js": ["getRepairRequestor", "getAllRepairRequestors", "CollectionRepairRequestor", "getRepairResponder", "CollectionRepairResponder"],
   "collection_validator.js": ["CollectionValidator", "CollectionProblemData"],
   "Console.jsm": ["console", "ConsoleAPI"],
-  "constants.js": ["WEAVE_VERSION", "SYNC_API_VERSION", "USER_API_VERSION", "MISC_API_VERSION", "STORAGE_VERSION", "PREFS_BRANCH", "PWDMGR_HOST", "PWDMGR_PASSWORD_REALM", "PWDMGR_PASSPHRASE_REALM", "PWDMGR_KEYBUNDLE_REALM", "DEFAULT_KEYBUNDLE_NAME", "HMAC_INPUT", "SYNC_KEY_ENCODED_LENGTH", "SYNC_KEY_DECODED_LENGTH", "SYNC_KEY_HYPHENATED_LENGTH", "NO_SYNC_NODE_INTERVAL", "MAX_ERROR_COUNT_BEFORE_BACKOFF", "MAX_IGNORE_ERROR_COUNT", "MINIMUM_BACKOFF_INTERVAL", "MAXIMUM_BACKOFF_INTERVAL", "HMAC_EVENT_INTERVAL", "MASTER_PASSWORD_LOCKED_RETRY_INTERVAL", "DEFAULT_BLOCK_PERIOD", "DEFAULT_GUID_FETCH_BATCH_SIZE", "DEFAULT_MOBILE_GUID_FETCH_BATCH_SIZE", "DEFAULT_STORE_BATCH_SIZE", "HISTORY_STORE_BATCH_SIZE", "FORMS_STORE_BATCH_SIZE", "PASSWORDS_STORE_BATCH_SIZE", "ADDONS_STORE_BATCH_SIZE", "APPS_STORE_BATCH_SIZE", "DEFAULT_DOWNLOAD_BATCH_SIZE", "DEFAULT_MAX_RECORD_PAYLOAD_BYTES", "SINGLE_USER_THRESHOLD", "MULTI_DEVICE_THRESHOLD", "SCORE_INCREMENT_SMALL", "SCORE_INCREMENT_MEDIUM", "SCORE_INCREMENT_XLARGE", "SCORE_UPDATE_DELAY", "IDLE_OBSERVER_BACK_DELAY", "URI_LENGTH_MAX", "MAX_HISTORY_UPLOAD", "MAX_HISTORY_DOWNLOAD", "STATUS_OK", "SYNC_FAILED", "LOGIN_FAILED", "SYNC_FAILED_PARTIAL", "CLIENT_NOT_CONFIGURED", "STATUS_DISABLED", "MASTER_PASSWORD_LOCKED", "LOGIN_SUCCEEDED", "SYNC_SUCCEEDED", "ENGINE_SUCCEEDED", "LOGIN_FAILED_NO_USERNAME", "LOGIN_FAILED_NO_PASSWORD", "LOGIN_FAILED_NO_PASSPHRASE", "LOGIN_FAILED_NETWORK_ERROR", "LOGIN_FAILED_SERVER_ERROR", "LOGIN_FAILED_INVALID_PASSPHRASE", "LOGIN_FAILED_LOGIN_REJECTED", "METARECORD_DOWNLOAD_FAIL", "VERSION_OUT_OF_DATE", "DESKTOP_VERSION_OUT_OF_DATE", "SETUP_FAILED_NO_PASSPHRASE", "CREDENTIALS_CHANGED", "ABORT_SYNC_COMMAND", "NO_SYNC_NODE_FOUND", "OVER_QUOTA", "PROLONGED_SYNC_FAILURE", "SERVER_MAINTENANCE", "RESPONSE_OVER_QUOTA", "ENGINE_UPLOAD_FAIL", "ENGINE_DOWNLOAD_FAIL", "ENGINE_UNKNOWN_FAIL", "ENGINE_APPLY_FAIL", "ENGINE_METARECORD_DOWNLOAD_FAIL", "ENGINE_METARECORD_UPLOAD_FAIL", "ENGINE_BATCH_INTERRUPTED", "JPAKE_ERROR_CHANNEL", "JPAKE_ERROR_NETWORK", "JPAKE_ERROR_SERVER", "JPAKE_ERROR_TIMEOUT", "JPAKE_ERROR_INTERNAL", "JPAKE_ERROR_INVALID", "JPAKE_ERROR_NODATA", "JPAKE_ERROR_KEYMISMATCH", "JPAKE_ERROR_WRONGMESSAGE", "JPAKE_ERROR_USERABORT", "JPAKE_ERROR_DELAYUNSUPPORTED", "kSyncNotConfigured", "kSyncMasterPasswordLocked", "kSyncWeaveDisabled", "kSyncNetworkOffline", "kSyncBackoffNotMet", "kFirstSyncChoiceNotMade", "FIREFOX_ID", "FENNEC_ID", "SEAMONKEY_ID", "TEST_HARNESS_ID", "MIN_PP_LENGTH", "MIN_PASS_LENGTH", "DEVICE_TYPE_DESKTOP", "DEVICE_TYPE_MOBILE", "SQLITE_MAX_VARIABLE_NUMBER"],
+  "constants.js": ["WEAVE_VERSION", "SYNC_API_VERSION", "USER_API_VERSION", "MISC_API_VERSION", "STORAGE_VERSION", "PREFS_BRANCH", "PWDMGR_HOST", "PWDMGR_PASSWORD_REALM", "PWDMGR_PASSPHRASE_REALM", "PWDMGR_KEYBUNDLE_REALM", "DEFAULT_KEYBUNDLE_NAME", "HMAC_INPUT", "SYNC_KEY_ENCODED_LENGTH", "SYNC_KEY_DECODED_LENGTH", "SYNC_KEY_HYPHENATED_LENGTH", "NO_SYNC_NODE_INTERVAL", "MAX_ERROR_COUNT_BEFORE_BACKOFF", "MAX_IGNORE_ERROR_COUNT", "MINIMUM_BACKOFF_INTERVAL", "MAXIMUM_BACKOFF_INTERVAL", "HMAC_EVENT_INTERVAL", "MASTER_PASSWORD_LOCKED_RETRY_INTERVAL", "DEFAULT_BLOCK_PERIOD", "DEFAULT_GUID_FETCH_BATCH_SIZE", "DEFAULT_MOBILE_GUID_FETCH_BATCH_SIZE", "DEFAULT_STORE_BATCH_SIZE", "HISTORY_STORE_BATCH_SIZE", "FORMS_STORE_BATCH_SIZE", "PASSWORDS_STORE_BATCH_SIZE", "ADDONS_STORE_BATCH_SIZE", "APPS_STORE_BATCH_SIZE", "DEFAULT_DOWNLOAD_BATCH_SIZE", "DEFAULT_MAX_RECORD_PAYLOAD_BYTES", "SINGLE_USER_THRESHOLD", "MULTI_DEVICE_THRESHOLD", "SCORE_INCREMENT_SMALL", "SCORE_INCREMENT_MEDIUM", "SCORE_INCREMENT_XLARGE", "SCORE_UPDATE_DELAY", "IDLE_OBSERVER_BACK_DELAY", "URI_LENGTH_MAX", "MAX_HISTORY_UPLOAD", "MAX_HISTORY_DOWNLOAD", "STATUS_OK", "SYNC_FAILED", "LOGIN_FAILED", "SYNC_FAILED_PARTIAL", "CLIENT_NOT_CONFIGURED", "STATUS_DISABLED", "MASTER_PASSWORD_LOCKED", "LOGIN_SUCCEEDED", "SYNC_SUCCEEDED", "ENGINE_SUCCEEDED", "LOGIN_FAILED_NO_USERNAME", "LOGIN_FAILED_NO_PASSWORD", "LOGIN_FAILED_NO_PASSPHRASE", "LOGIN_FAILED_NETWORK_ERROR", "LOGIN_FAILED_SERVER_ERROR", "LOGIN_FAILED_INVALID_PASSPHRASE", "LOGIN_FAILED_LOGIN_REJECTED", "METARECORD_DOWNLOAD_FAIL", "VERSION_OUT_OF_DATE", "DESKTOP_VERSION_OUT_OF_DATE", "SETUP_FAILED_NO_PASSPHRASE", "CREDENTIALS_CHANGED", "ABORT_SYNC_COMMAND", "NO_SYNC_NODE_FOUND", "OVER_QUOTA", "PROLONGED_SYNC_FAILURE", "SERVER_MAINTENANCE", "RESPONSE_OVER_QUOTA", "ENGINE_UPLOAD_FAIL", "ENGINE_DOWNLOAD_FAIL", "ENGINE_UNKNOWN_FAIL", "ENGINE_APPLY_FAIL", "ENGINE_METARECORD_DOWNLOAD_FAIL", "ENGINE_METARECORD_UPLOAD_FAIL", "ENGINE_BATCH_INTERRUPTED", "JPAKE_ERROR_CHANNEL", "JPAKE_ERROR_NETWORK", "JPAKE_ERROR_SERVER", "JPAKE_ERROR_TIMEOUT", "JPAKE_ERROR_INTERNAL", "JPAKE_ERROR_INVALID", "JPAKE_ERROR_NODATA", "JPAKE_ERROR_KEYMISMATCH", "JPAKE_ERROR_WRONGMESSAGE", "JPAKE_ERROR_USERABORT", "JPAKE_ERROR_DELAYUNSUPPORTED", "kSyncNotConfigured", "kSyncMasterPasswordLocked", "kSyncWeaveDisabled", "kSyncNetworkOffline", "kSyncBackoffNotMet", "kFirstSyncChoiceNotMade", "kFirefoxShuttingDown", "FIREFOX_ID", "FENNEC_ID", "SEAMONKEY_ID", "TEST_HARNESS_ID", "MIN_PP_LENGTH", "MIN_PASS_LENGTH", "DEVICE_TYPE_DESKTOP", "DEVICE_TYPE_MOBILE", "SQLITE_MAX_VARIABLE_NUMBER"],
   "Constants.jsm": ["Roles", "Events", "Relations", "Filters", "States", "Prefilters"],
   "ContactDB.jsm": ["ContactDB", "DB_NAME", "STORE_NAME", "SAVED_GETALL_STORE_NAME", "REVISION_STORE", "DB_VERSION"],
   "content-server.jsm": ["init"],
   "content.jsm": ["registerContentFrame"],
   "ContentCrashHandlers.jsm": ["TabCrashHandler", "PluginCrashReporter", "UnsubmittedCrashHandler"],
   "ContentObservers.js": [],
   "ContentPrefUtils.jsm": ["ContentPref", "cbHandleResult", "cbHandleError", "cbHandleCompletion", "safeCallback", "_methodsCallableFromChild"],
   "controller.js": ["MozMillController", "globalEventRegistry", "sleep", "windowMap"],
--- a/widget/gtk/nsLookAndFeel.cpp
+++ b/widget/gtk/nsLookAndFeel.cpp
@@ -1079,16 +1079,22 @@ nsLookAndFeel::EnsureInit()
     // Require GTK 3.20 for client-side decoration support.
     sCSDAvailable = gtk_check_version(3, 20, 0) == nullptr;
     if (sCSDAvailable) {
         sCSDAvailable =
             mozilla::Preferences::GetBool("widget.allow-client-side-decoration",
                                           false);
     }
 
+    // We need to initialize whole CSD config explicitly because it's queried
+    // as -moz-gtk* media features.
+    sCSDCloseButton = false;
+    sCSDMaximizeButton = false;
+    sCSDMinimizeButton = false;
+
     if (sCSDAvailable) {
         static auto sGtkHeaderBarGetDecorationLayoutPtr =
           (const gchar* (*)(GtkWidget*))
           dlsym(RTLD_DEFAULT, "gtk_header_bar_get_decoration_layout");
 
         GtkWidget* headerBar = GetWidget(MOZ_GTK_HEADER_BAR);
         const gchar* decorationLayout =
             sGtkHeaderBarGetDecorationLayoutPtr(headerBar);