Merge m-c to graphics
authorKartikaya Gupta <kgupta@mozilla.com>
Wed, 01 Mar 2017 09:45:49 -0500
changeset 374819 9059ef02d91ba49bb3cf3b9cdd4c92bc99a4d4b0
parent 374818 7d88bb71fefb36157f16769e87658a39248a2a91 (current diff)
parent 374304 34c6c2f302e7b48e3ad2cec575cbd34d423a9d32 (diff)
child 374820 9503f5f64ba0a99e38f6cb16df350805f770945d
push id10863
push userjlorenzo@mozilla.com
push dateMon, 06 Mar 2017 23:02:23 +0000
treeherdermozilla-aurora@0931190cd725 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone54.0a1
Merge m-c to graphics MozReview-Commit-ID: 6TMc1hGNQVA
security/nss/fuzz/hash.options
security/nss/fuzz/hash_target.cc
taskcluster/ci/test/tests.yml
testing/mozharness/mozharness/mozilla/repo_manupulation.py
third_party/rust/cssparser/src/macros/match_byte.rs
third_party/rust/cssparser/src/macros/mod.rs
toolkit/library/gtest/rust/Cargo.lock
toolkit/library/rust/Cargo.lock
--- a/.clang-format-ignore
+++ b/.clang-format-ignore
@@ -1,15 +1,18 @@
 ^build/clang-plugin/tests/.*
 ^config/gcc-stl-wrapper.template.h
 ^config/msvc-stl-wrapper.template.h
 ^js/src/jsapi-tests/.*
 ^widget/android/GeneratedJNINatives.h
 ^widget/android/GeneratedJNIWrappers.cpp
 ^widget/android/GeneratedJNIWrappers.h
+^widget/android/fennec/FennecJNINatives.h
+^widget/android/fennec/FennecJNIWrappers.cpp
+^widget/android/fennec/FennecJNIWrappers.h
 
 # Generated from ./tools/rewriting/ThirdPartyPaths.txt
 # awk '{print "^"$1".*"}' ./tools/rewriting/ThirdPartyPaths.txt
 ^browser/components/translation/cld2/.*
 ^build/stlport/.*
 ^db/sqlite3/src/.*
 ^dom/media/platforms/ffmpeg/libav.*
 ^extensions/spellcheck/hunspell/src/.*
--- a/.eslintignore
+++ b/.eslintignore
@@ -82,18 +82,16 @@ devtools/client/commandline/**
 devtools/client/debugger/**
 devtools/client/framework/**
 !devtools/client/framework/selection.js
 !devtools/client/framework/target*
 !devtools/client/framework/toolbox*
 devtools/client/inspector/markup/test/doc_markup_events_*.html
 devtools/client/inspector/rules/test/doc_media_queries.html
 devtools/client/memory/test/chrome/*.html
-devtools/client/netmonitor/test/**
-devtools/client/netmonitor/har/test/**
 devtools/client/performance/components/test/test_jit_optimizations_01.html
 devtools/client/projecteditor/**
 devtools/client/responsive.html/test/browser/touch.html
 devtools/client/responsivedesign/**
 devtools/client/scratchpad/**
 devtools/client/shadereditor/**
 devtools/client/shared/*.jsm
 devtools/client/shared/components/reps/reps.js
--- a/browser/base/content/test/general/browser.ini
+++ b/browser/base/content/test/general/browser.ini
@@ -37,16 +37,17 @@ support-files =
   file_bug970276_popup2.html
   file_bug970276_favicon1.ico
   file_bug970276_favicon2.ico
   file_documentnavigation_frameset.html
   file_double_close_tab.html
   file_favicon_change.html
   file_favicon_change_not_in_document.html
   file_fullscreen-window-open.html
+  file_with_link_to_http.html
   head.js
   healthreport_pingData.js
   healthreport_testRemoteCommands.html
   moz.png
   navigating_window_with_download.html
   offlineQuotaNotification.cacheManifest
   offlineQuotaNotification.html
   page_style_sample.html
@@ -266,16 +267,17 @@ skip-if = e10s # Bug 863514 - no gesture
 skip-if = toolkit == "windows" # Disabled on Windows due to frequent failures (bug 969405)
 [browser_menuButtonFitts.js]
 skip-if = os != "win" # The Fitts Law menu button is only supported on Windows (bug 969376)
 [browser_middleMouse_noJSPaste.js]
 subsuite = clipboard
 [browser_minimize.js]
 [browser_misused_characters_in_strings.js]
 [browser_modifiedclick_inherit_principal.js]
+[browser_new_http_window_opened_from_file_tab.js]
 [browser_offlineQuotaNotification.js]
 skip-if = os == "linux" && !debug # bug 1304273
 [browser_feed_discovery.js]
 support-files = feed_discovery.html
 [browser_gZipOfflineChild.js]
 support-files = test_offline_gzip.html gZipOfflineChild.cacheManifest gZipOfflineChild.cacheManifest^headers^ gZipOfflineChild.html gZipOfflineChild.html^headers^
 [browser_overflowScroll.js]
 [browser_pageInfo.js]
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/general/browser_new_http_window_opened_from_file_tab.js
@@ -0,0 +1,54 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+
+const TEST_FILE = "file_with_link_to_http.html";
+const TEST_HTTP = "http://example.org/";
+
+// Test for bug 1338375.
+add_task(function* () {
+  // Open file:// page.
+  let dir = getChromeDir(getResolvedURI(gTestPath));
+  dir.append(TEST_FILE);
+  const uriString = Services.io.newFileURI(dir).spec;
+  let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, uriString);
+  registerCleanupFunction(function* () {
+    yield BrowserTestUtils.removeTab(tab);
+  });
+  let browser = tab.linkedBrowser;
+
+  // Set pref to open in new window.
+  Services.prefs.setIntPref("browser.link.open_newwindow", 2);
+  registerCleanupFunction(function() {
+    Services.prefs.clearUserPref("browser.link.open_newwindow");
+  });
+
+  // Open new http window from JavaScript in file:// page and check that we get
+  // a new window with the correct page and features.
+  let promiseNewWindow = BrowserTestUtils.waitForNewWindow(true, TEST_HTTP);
+  yield ContentTask.spawn(browser, TEST_HTTP, uri => {
+    content.open(uri, "_blank");
+  });
+  let win = yield promiseNewWindow;
+  registerCleanupFunction(function* () {
+    yield BrowserTestUtils.closeWindow(win);
+  });
+  ok(win, "Check that an http window loaded when using window.open.");
+  ok(win.menubar.visible,
+     "Check that the menu bar on the new window is visible.")
+  ok(win.toolbar.visible,
+     "Check that the tool bar on the new window is visible.")
+
+  // Open new http window from a link in file:// page and check that we get a
+  // new window with the correct page and features.
+  promiseNewWindow = BrowserTestUtils.waitForNewWindow(true, TEST_HTTP);
+  yield BrowserTestUtils.synthesizeMouseAtCenter("#linkToExample", {}, browser);
+  let win2 = yield promiseNewWindow;
+  registerCleanupFunction(function* () {
+    yield BrowserTestUtils.closeWindow(win2);
+  });
+  ok(win2, "Check that an http window loaded when using link.");
+  ok(win2.menubar.visible,
+     "Check that the menu bar on the new window is visible.")
+  ok(win2.toolbar.visible,
+     "Check that the tool bar on the new window is visible.")
+});
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/general/file_with_link_to_http.html
@@ -0,0 +1,9 @@
+<html>
+<head>
+  <meta charset="utf-8">
+  <title>Test page for Bug 1338375</title>
+</head>
+<body>
+  <a id="linkToExample" href="http://example.org" target="_blank">example.org</a>
+</body>
+</html>
--- a/browser/base/content/test/webextensions/head.js
+++ b/browser/base/content/test/webextensions/head.js
@@ -114,16 +114,47 @@ function is_visible(element) {
   // Hiding a parent element will hide all its children
   if (element.parentNode != element.ownerDocument)
     return is_visible(element.parentNode);
 
   return true;
 }
 
 /**
+ * Check the contents of an individual permission string.
+ * This function is fairly specific to the use here and probably not
+ * suitable for re-use elsewhere...
+ *
+ * @param {string} string
+ *        The string value to check (i.e., pulled from the DOM)
+ * @param {string} key
+ *        The key in browser.properties for the localized string to
+ *        compare with.
+ * @param {string|null} param
+ *        Optional string to substitute for %S in the localized string.
+ * @param {string} msg
+ *        The message to be emitted as part of the actual test.
+ */
+function checkPermissionString(string, key, param, msg) {
+  let localizedString = param ?
+                        gBrowserBundle.formatStringFromName(key, [param], 1) :
+                        gBrowserBundle.GetStringFromName(key);
+
+  // If this is a parameterized string and the parameter isn't given,
+  // just do a simple comparison of the text before and after the %S
+  if (localizedString.includes("%S")) {
+    let i = localizedString.indexOf("%S");
+    ok(string.startsWith(localizedString.slice(0, i)), msg);
+    ok(string.endsWith(localizedString.slice(i + 2)), msg);
+  } else {
+    is(string, localizedString, msg);
+  }
+}
+
+/**
  * Test that install-time permission prompts work for a given
  * installation method.
  *
  * @param {Function} installFn
  *        Callable that takes the name of an xpi file to install and
  *        starts to install it.  Should return a Promise that resolves
  *        when the install is finished or rejects if the install is canceled.
  *
@@ -191,17 +222,34 @@ async function testInstallMethod(install
       // The icon should come from the extension, don't bother with the precise
       // path, just make sure we've got a jar url pointing to the right path
       // inside the jar.
       ok(icon.startsWith("jar:file://"), "Icon is a jar url");
       ok(icon.endsWith("/icon.png"), "Icon is icon.png inside a jar");
 
       is(header.getAttribute("hidden"), "", "Permission list header is visible");
       is(ul.childElementCount, 5, "Permissions list has 5 entries");
-      // Real checking of the contents here is deferred until bug 1316996 lands
+
+      checkPermissionString(ul.children[0].textContent,
+                            "webextPerms.hostDescription.wildcard",
+                            "wildcard.domain",
+                            "First permission is domain permission");
+      checkPermissionString(ul.children[1].textContent,
+                            "webextPerms.hostDescription.oneSite",
+                            "singlehost.domain",
+                            "Second permission is single host permission");
+      checkPermissionString(ul.children[2].textContent,
+                            "webextPerms.description.nativeMessaging", null,
+                            "Third permission is nativeMessaging");
+      checkPermissionString(ul.children[3].textContent,
+                            "webextPerms.description.tabs", null,
+                            "Fourth permission is tabs");
+      checkPermissionString(ul.children[4].textContent,
+                            "webextPerms.description.history", null,
+                            "Fifth permission is history");
     } else if (filename == NO_PERMS_XPI) {
       // This extension has no icon, it should have the default
       ok(isDefaultIcon(icon), "Icon is the default extension icon");
 
       is(header.getAttribute("hidden"), "true", "Permission list header is hidden");
       is(ul.childElementCount, 0, "Permissions list has 0 entries");
     }
 
--- a/browser/components/preferences/in-content/applications.js
+++ b/browser/components/preferences/in-content/applications.js
@@ -1343,19 +1343,20 @@ var gApplicationsPane = {
     var menu =
       document.getAnonymousElementByAttribute(typeItem, "class", "actionsMenu");
     var menuPopup = menu.menupopup;
 
     // Clear out existing items.
     while (menuPopup.hasChildNodes())
       menuPopup.removeChild(menuPopup.lastChild);
 
+    let internalMenuItem;
     // Add the "Preview in Firefox" option for optional internal handlers.
     if (handlerInfo instanceof InternalHandlerInfoWrapper) {
-      let internalMenuItem = document.createElement("menuitem");
+      internalMenuItem = document.createElement("menuitem");
       internalMenuItem.setAttribute("action", Ci.nsIHandlerInfo.handleInternally);
       let label = this._prefsBundle.getFormattedString("previewInApp",
                                                        [this._brandShortName]);
       internalMenuItem.setAttribute("label", label);
       internalMenuItem.setAttribute("tooltiptext", label);
       internalMenuItem.setAttribute(APP_ICON_ATTR_NAME, "ask");
       menuPopup.appendChild(internalMenuItem);
     }
@@ -1387,17 +1388,17 @@ var gApplicationsPane = {
       saveMenuItem.setAttribute("label", label);
       saveMenuItem.setAttribute("tooltiptext", label);
       saveMenuItem.setAttribute(APP_ICON_ATTR_NAME, "save");
       menuPopup.appendChild(saveMenuItem);
     }
 
     // If this is the feed type, add a Live Bookmarks item.
     if (isFeedType(handlerInfo.type)) {
-      let internalMenuItem = document.createElement("menuitem");
+      internalMenuItem = document.createElement("menuitem");
       internalMenuItem.setAttribute("action", Ci.nsIHandlerInfo.handleInternally);
       let label = this._prefsBundle.getFormattedString("addLiveBookmarksInApp",
                                                        [this._brandShortName]);
       internalMenuItem.setAttribute("label", label);
       internalMenuItem.setAttribute("tooltiptext", label);
       internalMenuItem.setAttribute(APP_ICON_ATTR_NAME, "feed");
       menuPopup.appendChild(internalMenuItem);
     }
@@ -1499,17 +1500,21 @@ var gApplicationsPane = {
     // Select the item corresponding to the preferred action.  If the always
     // ask flag is set, it overrides the preferred action.  Otherwise we pick
     // the item identified by the preferred action (when the preferred action
     // is to use a helper app, we have to pick the specific helper app item).
     if (handlerInfo.alwaysAskBeforeHandling)
       menu.selectedItem = askMenuItem;
     else switch (handlerInfo.preferredAction) {
       case Ci.nsIHandlerInfo.handleInternally:
-        menu.selectedItem = internalMenuItem;
+        if (internalMenuItem) {
+          menu.selectedItem = internalMenuItem;
+        } else {
+          Cu.reportError("No menu item defined to set!")
+        }
         break;
       case Ci.nsIHandlerInfo.useSystemDefault:
         menu.selectedItem = defaultMenuItem;
         break;
       case Ci.nsIHandlerInfo.useHelperApp:
         if (preferredApp)
           menu.selectedItem =
             possibleAppMenuItems.filter(v => v.handlerApp.equals(preferredApp))[0];
--- a/browser/components/preferences/in-content/tests/browser.ini
+++ b/browser/components/preferences/in-content/tests/browser.ini
@@ -1,14 +1,15 @@
 [DEFAULT]
 support-files =
   head.js
   privacypane_tests_perwindow.js
   site_data_test.html
 
+[browser_applications_selection.js]
 [browser_advanced_siteData.js]
 [browser_advanced_update.js]
 skip-if = !updater
 [browser_basic_rebuild_fonts_test.js]
 [browser_bug410900.js]
 [browser_bug705422.js]
 [browser_bug731866.js]
 [browser_bug795764_cachedisabled.js]
new file mode 100644
--- /dev/null
+++ b/browser/components/preferences/in-content/tests/browser_applications_selection.js
@@ -0,0 +1,79 @@
+var win;
+var feedItem;
+var container;
+
+SimpleTest.requestCompleteLog();
+
+add_task(function* setup() {
+  yield openPreferencesViaOpenPreferencesAPI("applications", null, {leaveOpen: true});
+  info("Preferences page opened on the applications pane.");
+
+  registerCleanupFunction(() => {
+    gBrowser.removeCurrentTab();
+  });
+});
+
+add_task(function* getFeedItem() {
+  win = gBrowser.selectedBrowser.contentWindow;
+
+  container = win.document.getElementById("handlersView");
+  feedItem = container.querySelector("richlistitem[type='application/vnd.mozilla.maybe.feed']");
+  Assert.ok(feedItem, "feedItem is present in handlersView.");
+})
+
+add_task(function* selectInternalOptionForFeed() {
+  // Select the item.
+  feedItem.scrollIntoView();
+  container.selectItem(feedItem);
+  Assert.ok(feedItem.selected, "Should be able to select our item.");
+
+  // Wait for the menu.
+  let list = yield waitForCondition(() =>
+    win.document.getAnonymousElementByAttribute(feedItem, "class", "actionsMenu"));
+  info("Got list after item was selected");
+
+  // Find the "Add Live bookmarks option".
+  let chooseItems = list.getElementsByAttribute("action", Ci.nsIHandlerInfo.handleInternally);
+  Assert.equal(chooseItems.length, 1, "Should only be one action to handle internally");
+
+  // Select the option.
+  let cmdEvent = win.document.createEvent("xulcommandevent");
+  cmdEvent.initCommandEvent("command", true, true, win, 0, false, false, false, false, null);
+  chooseItems[0].dispatchEvent(cmdEvent);
+
+  // Check that we display the correct result.
+  list = yield waitForCondition(() =>
+    win.document.getAnonymousElementByAttribute(feedItem, "class", "actionsMenu"));
+  info("Got list after item was selected");
+  Assert.ok(list.selectedItem, "Should have a selected item.");
+  Assert.equal(list.selectedItem.getAttribute("action"),
+               Ci.nsIHandlerInfo.handleInternally,
+               "Newly selected item should be the expected one.");
+});
+
+// This builds on the previous selectInternalOptionForFeed task.
+add_task(function* reselectInternalOptionForFeed() {
+  // Now select a different option in the list - use the pdf item as that doesn't
+  // need to load any favicons.
+  let anotherItem = container.querySelector("richlistitem[type='application/pdf']");
+
+  container.selectItem(anotherItem);
+
+  // Wait for the menu so that we don't hit race conditions.
+  yield waitForCondition(() =>
+    win.document.getAnonymousElementByAttribute(anotherItem, "class", "actionsMenu"));
+  info("Got list after item was selected");
+
+  // Now select the feed item again, and check what it is displaying.
+  container.selectItem(feedItem);
+
+  let list = yield waitForCondition(() =>
+    win.document.getAnonymousElementByAttribute(feedItem, "class", "actionsMenu"));
+  info("Got list after item was selected");
+
+  Assert.ok(list.selectedItem,
+            "Should have a selected item");
+  Assert.equal(list.selectedItem.getAttribute("action"),
+               Ci.nsIHandlerInfo.handleInternally,
+               "Selected item should still be the same as the previously selected item.");
+});
--- a/browser/locales/search/list.json
+++ b/browser/locales/search/list.json
@@ -93,16 +93,23 @@
     },
     "az": {
       "default": {
         "visibleDefaultEngines": [
           "google", "amazondotcom", "azerdict", "bing", "ddg", "wikipedia-az", "yandex-az"
         ]
       }
     },
+    "be": {
+      "default": {
+        "visibleDefaultEngines": [
+          "yandex-by", "google", "ddg", "wikipedia-be", "wikipedia-be-tarask"
+        ]
+      }
+    },
     "bg": {
       "default": {
         "visibleDefaultEngines": [
           "google", "diribg", "amazondotcom", "ddg", "portalbgdict", "wikipedia-bg"
         ]
       }
     },
     "bn-BD": {
new file mode 100644
--- /dev/null
+++ b/browser/locales/searchplugins/wikipedia-be-tarask.xml
@@ -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/. -->
+
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>Вікіпэдыя (be-tarask)</ShortName>
+<Description>Вікіпэдыя, вольная энцыкляпэдыя</Description>
+<InputEncoding>UTF-8</InputEncoding>
+<Image width="16" height="16">resource://search-plugins/images/wikipedia.ico</Image>
+<Url type="application/x-suggestions+json" method="GET" template="https://be-tarask.wikipedia.org/w/api.php">
+  <Param name="action" value="opensearch"/>
+  <Param name="search" value="{searchTerms}"/>
+</Url>
+<Url type="text/html" method="GET" template="https://be-tarask.wikipedia.org/wiki/Спэцыяльныя:Пошук"
+     resultdomain="wikipedia.org" rel="searchform">
+  <Param name="search" value="{searchTerms}"/>
+  <Param name="sourceid" value="Mozilla-search"/>
+</Url>
+</SearchPlugin>
new file mode 100644
--- /dev/null
+++ b/browser/locales/searchplugins/wikipedia-be.xml
@@ -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/. -->
+
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>Вікіпедыя (be)</ShortName>
+<Description>Вікіпедыя, свабодная энцыклапедыя</Description>
+<InputEncoding>UTF-8</InputEncoding>
+<Image width="16" height="16">resource://search-plugins/images/wikipedia.ico</Image>
+<Url type="application/x-suggestions+json" method="GET" template="https://be.wikipedia.org/w/api.php">
+  <Param name="action" value="opensearch"/>
+  <Param name="search" value="{searchTerms}"/>
+</Url>
+<Url type="text/html" method="GET" template="https://be.wikipedia.org/wiki/Адмысловае:Search"
+     resultdomain="wikipedia.org" rel="searchform">
+  <Param name="search" value="{searchTerms}"/>
+  <Param name="sourceid" value="Mozilla-search"/>
+</Url>
+</SearchPlugin>
new file mode 100644
--- /dev/null
+++ b/browser/locales/searchplugins/yandex-by.xml
@@ -0,0 +1,22 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+   - License, v. 2.0. If a copy of the MPL was not distributed with this
+   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>Яндекс</ShortName>
+<Description>Пошук з дапамогаю Яндекс</Description>
+<InputEncoding>UTF-8</InputEncoding>
+<Image width="16" height="16">data:image/x-icon;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABmJLR0QA/wD/AP+gvaeTAAAB50lEQVQ4T43SvWsacRgH8OcPCITSLRQ6BSRL1gxZTNZQsgYC3QKunVoMnTKFhBQSnDqIUa5gxNbBCgriC4pv50sJ1lIR5SROpRVbz+O4b5/flbvedekJH+557nvfH8chdbvdIFMYPAgBICdqt9uKpmnw8huNRuDnX8J5QKvVgmEYnqiqClmWwZ113kmger0OXdf/Wv6EIb0BTo+AgB94ceDKJ5MJuHPOMwlUqVSwWq1sevQaxqvn0O5l6HdvgaMdV75cLtFoNMC9Xd6JisWiedNiPNuB9l4yZ+1jEvBvuXJBURRwL8YzUT6fx2KxsGHrKdQPCXNW794Bvieu3CLegrsnlM1mMZ/PbfqeH6vToDkvb2+Bx49cuWU2m4G7bUqn0xiPx7ZpqYRf29v4cXyMxf4+tLU1V24ZDAbgbptSqRSGw6HL9OwM37n4bXPTvP6bC7lcDtw9oWQyiX6/b/vMH1XZ2MAoEMDXqytM+QBnLtRqNXAvxjNRPB5Hr9ez9Q8PMfD50OM/2P3FBb7wAc680+mIMri3yzuRJEloNpsmORTCJy7INzd/9stLc7dyIZPJgDvnPJNA0WgU1WrVkxJ/4FgsBu6s804CRSKRh0KhgHK5/F+JRAL8fJBnslA4HH7NHhg8CDnLwm8IYz560xw92AAAAABJRU5ErkJggg==</Image>
+<Url type="application/x-suggestions+json" method="GET" template="https://suggest.yandex.net/suggest-ff.cgi">
+  <Param name="part" value="{searchTerms}"/>
+</Url>
+<Url type="text/html" method="GET" template="https://yandex.by/yandsearch" resultdomain="yandex.by">
+  <MozParam name="clid" condition="purpose" purpose="searchbar"   value="2186618"/>
+  <MozParam name="clid" condition="purpose" purpose="keyword"     value="2186621"/>
+  <MozParam name="clid" condition="purpose" purpose="contextmenu" value="2186623"/>
+  <MozParam name="clid" condition="purpose" purpose="homepage"    value="2186617"/>
+  <MozParam name="clid" condition="purpose" purpose="newtab"      value="2186620"/>
+  <Param    name="text" value="{searchTerms}"/>
+</Url>
+<SearchForm>https://www.yandex.by/</SearchForm>
+</SearchPlugin>
--- a/browser/modules/ExtensionsUI.jsm
+++ b/browser/modules/ExtensionsUI.jsm
@@ -215,33 +215,17 @@ this.ExtensionsUI = {
       result.header = "";
       result.text = bundle.formatStringFromName("webextPerms.updateText", [addonName], 1);
       result.acceptText = bundle.GetStringFromName("webextPerms.updateAccept.label");
       result.acceptKey = bundle.GetStringFromName("webextPerms.updateAccept.accessKey");
     }
 
     let perms = info.permissions || {hosts: [], permissions: []};
 
-    result.msgs = [];
-    for (let permission of perms.permissions) {
-      let key = `webextPerms.description.${permission}`;
-      if (permission == "nativeMessaging") {
-        let brandBundle = Services.strings.createBundle(BRAND_PROPERTIES);
-        let appName = brandBundle.GetStringFromName("brandShortName");
-        result.msgs.push(bundle.formatStringFromName(key, [appName], 1));
-      } else {
-        try {
-          result.msgs.push(bundle.GetStringFromName(key));
-        } catch (err) {
-          // We deliberately do not include all permissions in the prompt.
-          // So if we don't find one then just skip it.
-        }
-      }
-    }
-
+    // First classify our host permissions
     let allUrls = false, wildcards = [], sites = [];
     for (let permission of perms.hosts) {
       if (permission == "<all_urls>") {
         allUrls = true;
         break;
       }
       let match = /^[htps*]+:\/\/([^/]+)\//.exec(permission);
       if (!match) {
@@ -251,16 +235,20 @@ this.ExtensionsUI = {
         allUrls = true;
       } else if (match[1].startsWith("*.")) {
         wildcards.push(match[1].slice(2));
       } else {
         sites.push(match[1]);
       }
     }
 
+    // Format the host permissions.  If we have a wildcard for all urls,
+    // a single string will suffice.  Otherwise, show domain wildcards
+    // first, then individual host permissions.
+    result.msgs = [];
     if (allUrls) {
       result.msgs.push(bundle.GetStringFromName("webextPerms.hostDescription.allUrls"));
     } else {
       // Formats a list of host permissions.  If we have 4 or fewer, display
       // them all, otherwise display the first 3 followed by an item that
       // says "...plus N others"
       function format(list, itemKey, moreKey) {
         function formatItems(items) {
@@ -278,16 +266,40 @@ this.ExtensionsUI = {
       }
 
       format(wildcards, "webextPerms.hostDescription.wildcard",
              "webextPerms.hostDescription.tooManyWildcards");
       format(sites, "webextPerms.hostDescription.oneSite",
              "webextPerms.hostDescription.tooManySites");
     }
 
+    let permissionKey = perm => `webextPerms.description.${perm}`;
+
+    // Next, show the native messaging permission if it is present.
+    const NATIVE_MSG_PERM = "nativeMessaging";
+    if (perms.permissions.includes(NATIVE_MSG_PERM)) {
+      let brandBundle = Services.strings.createBundle(BRAND_PROPERTIES);
+      let appName = brandBundle.GetStringFromName("brandShortName");
+      result.msgs.push(bundle.formatStringFromName(permissionKey(NATIVE_MSG_PERM), [appName], 1));
+    }
+
+    // Finally, show remaining permissions, in any order.
+    for (let permission of perms.permissions) {
+      // Handled above
+      if (permission == "nativeMessaging") {
+        continue;
+      }
+      try {
+        result.msgs.push(bundle.GetStringFromName(permissionKey(permission)));
+      } catch (err) {
+        // We deliberately do not include all permissions in the prompt.
+        // So if we don't find one then just skip it.
+      }
+    }
+
     return result;
   },
 
   showPermissionsPrompt(browser, strings, icon) {
     function eventCallback(topic) {
       if (topic == "showing") {
         let doc = this.browser.ownerDocument;
         doc.getElementById("addon-webext-perm-header").innerHTML = strings.header;
--- a/browser/themes/shared/customizableui/panelUI.inc.css
+++ b/browser/themes/shared/customizableui/panelUI.inc.css
@@ -585,16 +585,18 @@ toolbarpaletteitem[place="palette"] > to
 
 #PanelUI-update-status[update-status="failed"]::after {
   background-image: url(chrome://browser/skin/update-badge-failed.svg);
   background-color: #D90000;
 }
 
 #PanelUI-footer-addons > toolbarbutton {
   background-color: #FFEFBF;
+  /* Force border to override `#PanelUI-footer-addons > toolbarbutton` selector below */
+  border-top: 1px solid hsl(45, 100%, 77%) !important;
   display: flex;
   flex: 1 1 0%;
   width: calc(@menuPanelWidth@ + 30px);
   padding-inline-start: 15px;
   border-inline-start-style: none;
 }
 
 #PanelUI-footer-addons > toolbarbutton:hover {
--- a/devtools/client/inspector/markup/test/browser_markup_events_04.js
+++ b/devtools/client/inspector/markup/test/browser_markup_events_04.js
@@ -51,33 +51,33 @@ const TEST_DATA = [ // eslint-disable-li
       },
     ]
   },
   {
     selector: "#constructed-function",
     expected: [
       {
         type: "click",
-        filename: TEST_URL,
+        filename: TEST_URL + ":1",
         attributes: [
           "Bubbling",
           "DOM2"
         ],
         handler: "function anonymous() {\n" +
                  "\n" +
                  "}"
       }
     ]
   },
   {
     selector: "#constructed-function-with-body-string",
     expected: [
       {
         type: "click",
-        filename: TEST_URL,
+        filename: TEST_URL + ":1",
         attributes: [
           "Bubbling",
           "DOM2"
         ],
         handler: "function anonymous(a, b, c) {\n" +
                  "  alert(\"constructedFuncWithBodyString\");\n" +
         "}"
       }
--- a/devtools/client/inspector/rules/test/browser.ini
+++ b/devtools/client/inspector/rules/test/browser.ini
@@ -139,16 +139,17 @@ skip-if = os == "mac" # Bug 1245996 : cl
 [browser_rules_editable-field-focus_01.js]
 [browser_rules_editable-field-focus_02.js]
 [browser_rules_eyedropper.js]
 [browser_rules_filtereditor-appears-on-swatch-click.js]
 [browser_rules_filtereditor-commit-on-ENTER.js]
 [browser_rules_filtereditor-revert-on-ESC.js]
 skip-if = (os == "win" && debug) # bug 963492: win.
 [browser_rules_grid-highlighter-on-navigate.js]
+[browser_rules_grid-highlighter-on-reload.js]
 [browser_rules_grid-toggle_01.js]
 [browser_rules_grid-toggle_01b.js]
 [browser_rules_grid-toggle_02.js]
 [browser_rules_grid-toggle_03.js]
 [browser_rules_guessIndentation.js]
 [browser_rules_inherited-properties_01.js]
 [browser_rules_inherited-properties_02.js]
 [browser_rules_inherited-properties_03.js]
new file mode 100644
--- /dev/null
+++ b/devtools/client/inspector/rules/test/browser_rules_grid-highlighter-on-reload.js
@@ -0,0 +1,53 @@
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test that a grid highlighter showing grid gaps can be displayed after reloading the
+// page (Bug 1342051).
+
+const TEST_URI = `
+  <style type='text/css'>
+    #grid {
+      display: grid;
+      grid-gap: 10px;
+    }
+  </style>
+  <div id="grid">
+    <div id="cell1">cell1</div>
+    <div id="cell2">cell2</div>
+  </div>
+`;
+
+add_task(function* () {
+  yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
+
+  info("Check that the grid highlighter can be displayed");
+  yield checkGridHighlighter();
+
+  info("Close the toolbox before reloading the tab");
+  let target = TargetFactory.forTab(gBrowser.selectedTab);
+  yield gDevTools.closeToolbox(target);
+
+  yield refreshTab(gBrowser.selectedTab);
+
+  info("Check that the grid highlighter can be displayed after reloading the page");
+  yield checkGridHighlighter();
+});
+
+function* checkGridHighlighter() {
+  let {inspector, view} = yield openRuleView();
+  let {highlighters} = view;
+
+  yield selectNode("#grid", inspector);
+  let container = getRuleViewProperty(view, "#grid", "display").valueSpan;
+  let gridToggle = container.querySelector(".ruleview-grid");
+
+  info("Toggling ON the CSS grid highlighter from the rule-view.");
+  let onHighlighterShown = highlighters.once("grid-highlighter-shown");
+  gridToggle.click();
+  yield onHighlighterShown;
+
+  ok(highlighters.gridHighlighterShown, "CSS grid highlighter is shown.");
+}
--- a/devtools/client/netmonitor/har/test/browser_net_har_copy_all_as_har.js
+++ b/devtools/client/netmonitor/har/test/browser_net_har_copy_all_as_har.js
@@ -6,17 +6,17 @@
 /**
  * Basic tests for exporting Network panel content into HAR format.
  */
 add_task(function* () {
   let { tab, monitor } = yield initNetMonitor(SIMPLE_URL);
 
   info("Starting test... ");
 
-  let { document, gStore, windowRequire } = monitor.panelWin;
+  let { gStore, windowRequire } = monitor.panelWin;
   let Actions = windowRequire("devtools/client/netmonitor/actions/index");
   let RequestListContextMenu = windowRequire(
     "devtools/client/netmonitor/request-list-context-menu");
 
   gStore.dispatch(Actions.batchEnable(false));
 
   let wait = waitForNetworkEvents(monitor, 1);
   tab.linkedBrowser.reload();
--- a/devtools/client/netmonitor/har/test/browser_net_har_post_data.js
+++ b/devtools/client/netmonitor/har/test/browser_net_har_post_data.js
@@ -7,17 +7,17 @@
  * Tests for exporting POST data into HAR format.
  */
 add_task(function* () {
   let { tab, monitor } = yield initNetMonitor(
     HAR_EXAMPLE_URL + "html_har_post-data-test-page.html");
 
   info("Starting test... ");
 
-  let { document, gStore, windowRequire } = monitor.panelWin;
+  let { gStore, windowRequire } = monitor.panelWin;
   let Actions = windowRequire("devtools/client/netmonitor/actions/index");
   let RequestListContextMenu = windowRequire(
     "devtools/client/netmonitor/request-list-context-menu");
 
   gStore.dispatch(Actions.batchEnable(false));
 
   // Execute one POST request on the page and wait till its done.
   let wait = waitForNetworkEvents(monitor, 0, 1);
--- a/devtools/client/netmonitor/har/test/browser_net_har_throttle_upload.js
+++ b/devtools/client/netmonitor/har/test/browser_net_har_throttle_upload.js
@@ -11,17 +11,17 @@ add_task(function* () {
 });
 
 function* throttleUploadTest(actuallyThrottle) {
   let { tab, monitor } = yield initNetMonitor(
     HAR_EXAMPLE_URL + "html_har_post-data-test-page.html");
 
   info("Starting test... (actuallyThrottle = " + actuallyThrottle + ")");
 
-  let { document, gStore, windowRequire, NetMonitorController } = monitor.panelWin;
+  let { gStore, windowRequire, NetMonitorController } = monitor.panelWin;
   let Actions = windowRequire("devtools/client/netmonitor/actions/index");
   let RequestListContextMenu = windowRequire(
     "devtools/client/netmonitor/request-list-context-menu");
 
   gStore.dispatch(Actions.batchEnable(false));
 
   const size = 4096;
   const uploadSize = actuallyThrottle ? size / 3 : 0;
--- a/devtools/client/netmonitor/har/test/html_har_post-data-test-page.html
+++ b/devtools/client/netmonitor/har/test/html_har_post-data-test-page.html
@@ -11,30 +11,33 @@
     <meta http-equiv="Expires" content="0" />
     <title>Network Monitor Test Page</title>
   </head>
 
   <body>
     <p>HAR POST data test</p>
 
     <script type="text/javascript">
-      function post(aAddress, aData) {
-        var xhr = new XMLHttpRequest();
-        xhr.open("POST", aAddress, true);
+      /* exported executeTest, executeTest2 */
+      "use strict";
+
+      function post(address, data) {
+        let xhr = new XMLHttpRequest();
+        xhr.open("POST", address, true);
         xhr.setRequestHeader("Content-Type", "application/json");
-        xhr.send(aData);
+        xhr.send(data);
       }
 
       function executeTest() {
-        var url = "html_har_post-data-test-page.html";
-        var data = "{'first': 'John', 'last': 'Doe'}";
+        const url = "html_har_post-data-test-page.html";
+        const data = "{'first': 'John', 'last': 'Doe'}";
         post(url, data);
       }
 
       function executeTest2(size) {
-        var url = "html_har_post-data-test-page.html";
-        var data = "x".repeat(size);
+        const url = "html_har_post-data-test-page.html";
+        const data = "x".repeat(size);
         post(url, data);
       }
     </script>
   </body>
 
 </html>
--- a/devtools/client/netmonitor/shared/components/headers-panel.js
+++ b/devtools/client/netmonitor/shared/components/headers-panel.js
@@ -7,17 +7,20 @@
 const {
   createClass,
   createFactory,
   DOM,
   PropTypes,
 } = require("devtools/client/shared/vendor/react");
 const { L10N } = require("../../utils/l10n");
 const { writeHeaderText } = require("../../utils/request-utils");
-const { getHeadersURL } = require("../../utils/mdn-utils");
+const {
+  getHeadersURL,
+  getHTTPStatusCodeURL,
+} = require("../../utils/mdn-utils");
 const { getFormattedSize } = require("../../utils/format-utils");
 const { REPS, MODE } = require("devtools/client/shared/components/reps/reps");
 const Rep = createFactory(REPS.Rep);
 
 // Components
 const MDNLink = createFactory(require("./mdn-link"));
 const PropertiesView = createFactory(require("./properties-view"));
 
@@ -167,30 +170,38 @@ const HeadersPanel = createClass({
       if (fromCache) {
         code = "cached";
       } else if (fromServiceWorker) {
         code = "service worker";
       } else {
         code = status;
       }
 
+      let statusCodeDocURL = getHTTPStatusCodeURL(code);
+      let inputWidth = status.length + statusText.length + 1;
+
       summaryStatus = (
         div({ className: "tabpanel-summary-container headers-summary" },
           div({
             className: "tabpanel-summary-label headers-summary-label",
           }, SUMMARY_STATUS),
           div({
             className: "requests-list-status-icon",
             "data-code": code,
           }),
           input({
-            className: "tabpanel-summary-value textbox-input devtools-monospace",
+            className: "tabpanel-summary-value textbox-input devtools-monospace"
+              + " status-text",
             readOnly: true,
             value: `${status} ${statusText}`,
+            size: `${inputWidth}`,
           }),
+          statusCodeDocURL ? MDNLink({
+            url: statusCodeDocURL,
+          }) : null,
           window.NetMonitorController.supportsCustomRequest && button({
             className: "devtools-button",
             onClick: cloneSelectedRequest,
           }, EDIT_AND_RESEND),
           button({
             className: "devtools-button",
             onClick: this.toggleRawHeaders,
           }, RAW_HEADERS),
--- a/devtools/client/netmonitor/test/browser_net_autoscroll.js
+++ b/devtools/client/netmonitor/test/browser_net_autoscroll.js
@@ -61,17 +61,17 @@ add_task(function* () {
   }
 
   function* waitForRequestsToOverflowContainer() {
     info("Waiting for enough requests to overflow the container");
     while (true) {
       info("Waiting for one network request");
       yield waitForNetworkEvents(monitor, 1);
       console.log(requestsContainer.scrollHeight);
-      console.log(requestsContainer.clientHeight)
+      console.log(requestsContainer.clientHeight);
       if (requestsContainer.scrollHeight > requestsContainer.clientHeight) {
         info("The list is long enough, returning");
         return;
       }
     }
   }
 
   function scrolledToBottom(element) {
--- a/devtools/client/netmonitor/test/browser_net_cause.js
+++ b/devtools/client/netmonitor/test/browser_net_cause.js
@@ -33,52 +33,52 @@ const EXPECTED_REQUESTS = [
     causeUri: CAUSE_URL,
     stack: false
   },
   {
     method: "GET",
     url: EXAMPLE_URL + "xhr_request",
     causeType: "xhr",
     causeUri: CAUSE_URL,
-    stack: [{ fn: "performXhrRequest", file: CAUSE_FILE_NAME, line: 22 }]
+    stack: [{ fn: "performXhrRequest", file: CAUSE_FILE_NAME, line: 24 }]
   },
   {
     method: "GET",
     url: EXAMPLE_URL + "fetch_request",
     causeType: "fetch",
     causeUri: CAUSE_URL,
-    stack: [{ fn: "performFetchRequest", file: CAUSE_FILE_NAME, line: 26 }]
+    stack: [{ fn: "performFetchRequest", file: CAUSE_FILE_NAME, line: 28 }]
   },
   {
     method: "GET",
     url: EXAMPLE_URL + "promise_fetch_request",
     causeType: "fetch",
     causeUri: CAUSE_URL,
     stack: [
-      { fn: "performPromiseFetchRequest", file: CAUSE_FILE_NAME, line: 38 },
-      { fn: null, file: CAUSE_FILE_NAME, line: 37, asyncCause: "promise callback" },
+      { fn: "performPromiseFetchRequest", file: CAUSE_FILE_NAME, line: 40 },
+      { fn: null, file: CAUSE_FILE_NAME, line: 39, asyncCause: "promise callback" },
     ]
   },
   {
     method: "GET",
     url: EXAMPLE_URL + "timeout_fetch_request",
     causeType: "fetch",
     causeUri: CAUSE_URL,
     stack: [
-      { fn: "performTimeoutFetchRequest", file: CAUSE_FILE_NAME, line: 40 },
-      { fn: "performPromiseFetchRequest", file: CAUSE_FILE_NAME, line: 39,
+      { fn: "performTimeoutFetchRequest", file: CAUSE_FILE_NAME, line: 42 },
+      { fn: "performPromiseFetchRequest", file: CAUSE_FILE_NAME, line: 41,
         asyncCause: "setTimeout handler" },
     ]
   },
   {
     method: "POST",
     url: EXAMPLE_URL + "beacon_request",
     causeType: "beacon",
     causeUri: CAUSE_URL,
-    stack: [{ fn: "performBeaconRequest", file: CAUSE_FILE_NAME, line: 30 }]
+    stack: [{ fn: "performBeaconRequest", file: CAUSE_FILE_NAME, line: 32 }]
   },
 ];
 
 add_task(function* () {
   // Async stacks aren't on by default in all builds
   yield SpecialPowers.pushPrefEnv({ set: [["javascript.options.asyncstack", true]] });
 
   // the initNetMonitor function clears the network request list after the
--- a/devtools/client/netmonitor/test/browser_net_charts-03.js
+++ b/devtools/client/netmonitor/test/browser_net_charts-03.js
@@ -62,17 +62,16 @@ add_task(function* () {
     "The first column of the header exists.");
   is(rows[0].querySelectorAll("span")[1].getAttribute("name"), "label2",
     "The second column of the header exists.");
   is(rows[0].querySelectorAll("span")[0].textContent, "label1header",
     "The first column of the header displays the correct text.");
   is(rows[0].querySelectorAll("span")[1].textContent, "label2header",
     "The second column of the header displays the correct text.");
 
-
   ok(rows[1].querySelector(".table-chart-row-box.chart-colored-blob"),
     "A colored blob exists for the firt row.");
   is(rows[1].querySelectorAll("span")[0].getAttribute("name"), "label1",
     "The first column of the first row exists.");
   is(rows[1].querySelectorAll("span")[1].getAttribute("name"), "label2",
     "The second column of the first row exists.");
   is(rows[1].querySelectorAll("span")[0].textContent, "1",
     "The first column of the first row displays the correct text.");
--- a/devtools/client/netmonitor/test/browser_net_clear.js
+++ b/devtools/client/netmonitor/test/browser_net_clear.js
@@ -9,17 +9,16 @@
 
 add_task(function* () {
   let { tab, monitor } = yield initNetMonitor(SIMPLE_URL);
   info("Starting test... ");
 
   let { document, gStore, windowRequire } = monitor.panelWin;
   let Actions = windowRequire("devtools/client/netmonitor/actions/index");
   let { EVENTS } = windowRequire("devtools/client/netmonitor/constants");
-  let detailsPane = document.querySelector("#details-pane");
   let detailsPanelToggleButton = document.querySelector(".network-details-panel-toggle");
   let clearButton = document.querySelector(".requests-list-clear-button");
 
   gStore.dispatch(Actions.batchEnable(false));
 
   // Make sure we start in a sane state
   assertNoRequestState();
 
--- a/devtools/client/netmonitor/test/browser_net_content-type.js
+++ b/devtools/client/netmonitor/test/browser_net_content-type.js
@@ -36,100 +36,110 @@ add_task(function* () {
     CONTENT_TYPE_SJS + "?fmt=xml",
     {
       status: 200,
       statusText: "OK",
       type: "xml",
       fullMimeType: "text/xml; charset=utf-8",
       size: L10N.getFormatStrWithNumbers("networkMenu.sizeB", 42),
       time: true
-    });
-   verifyRequestItemTarget(
+    }
+  );
+  verifyRequestItemTarget(
     document,
     getDisplayedRequests(gStore.getState()),
     getSortedRequests(gStore.getState()).get(1),
     "GET",
     CONTENT_TYPE_SJS + "?fmt=css",
     {
       status: 200,
       statusText: "OK",
       type: "css",
       fullMimeType: "text/css; charset=utf-8",
       size: L10N.getFormatStrWithNumbers("networkMenu.sizeB", 34),
       time: true
-    });
-   verifyRequestItemTarget(
+    }
+  );
+  verifyRequestItemTarget(
     document,
     getDisplayedRequests(gStore.getState()),
     getSortedRequests(gStore.getState()).get(2),
     "GET",
     CONTENT_TYPE_SJS + "?fmt=js",
     {
       status: 200,
       statusText: "OK",
       type: "js",
       fullMimeType: "application/javascript; charset=utf-8",
       size: L10N.getFormatStrWithNumbers("networkMenu.sizeB", 34),
       time: true
-    });
-   verifyRequestItemTarget(
+    }
+  );
+  verifyRequestItemTarget(
     document,
     getDisplayedRequests(gStore.getState()),
     getSortedRequests(gStore.getState()).get(3),
     "GET",
     CONTENT_TYPE_SJS + "?fmt=json",
     {
       status: 200,
       statusText: "OK",
       type: "json",
       fullMimeType: "application/json; charset=utf-8",
       size: L10N.getFormatStrWithNumbers("networkMenu.sizeB", 29),
       time: true
-    });
-   verifyRequestItemTarget(
+    }
+  );
+  verifyRequestItemTarget(
     document,
     getDisplayedRequests(gStore.getState()),
     getSortedRequests(gStore.getState()).get(4),
     "GET",
-    CONTENT_TYPE_SJS + "?fmt=bogus", {
+    CONTENT_TYPE_SJS + "?fmt=bogus",
+    {
       status: 404,
       statusText: "Not Found",
       type: "html",
       fullMimeType: "text/html; charset=utf-8",
       size: L10N.getFormatStrWithNumbers("networkMenu.sizeB", 24),
       time: true
-    });
-   verifyRequestItemTarget(
+    }
+  );
+  verifyRequestItemTarget(
     document,
     getDisplayedRequests(gStore.getState()),
     getSortedRequests(gStore.getState()).get(5),
     "GET",
-    TEST_IMAGE, {
+    TEST_IMAGE,
+    {
       fuzzyUrl: true,
       status: 200,
       statusText: "OK",
       type: "png",
       fullMimeType: "image/png",
       size: L10N.getFormatStrWithNumbers("networkMenu.sizeB", 580),
       time: true
-    });
-   verifyRequestItemTarget(
+    }
+  );
+  verifyRequestItemTarget(
     document,
     getDisplayedRequests(gStore.getState()),
     getSortedRequests(gStore.getState()).get(6),
     "GET",
-    CONTENT_TYPE_SJS + "?fmt=gzip", {
+    CONTENT_TYPE_SJS + "?fmt=gzip",
+    {
       status: 200,
       statusText: "OK",
       type: "plain",
       fullMimeType: "text/plain",
       transferred: L10N.getFormatStrWithNumbers("networkMenu.sizeB", 73),
       size: L10N.getFormatStrWithNumbers("networkMenu.sizeKB", 10.73),
       time: true
-    });
+    }
+  );
 
   yield selectIndexAndWaitForEditor(0);
   yield testResponseTab("xml");
 
   yield selectIndexAndWaitForEditor(1);
   yield testResponseTab("css");
 
   yield selectIndexAndWaitForEditor(2);
--- a/devtools/client/netmonitor/test/browser_net_copy_as_curl.js
+++ b/devtools/client/netmonitor/test/browser_net_copy_as_curl.js
@@ -36,18 +36,17 @@ add_task(function* () {
     header("X-Custom-Header-2: 8.8.8.8"),
     header("X-Custom-Header-3: Mon, 3 Mar 2014 11:11:11 GMT"),
     header("Referer: " + CURL_URL),
     header("Connection: keep-alive"),
     header("Pragma: no-cache"),
     header("Cache-Control: no-cache")
   ];
 
-  let { document, gStore, windowRequire } = monitor.panelWin;
-  let Actions = windowRequire("devtools/client/netmonitor/actions/index");
+  let { document } = monitor.panelWin;
 
   let wait = waitForNetworkEvents(monitor, 1);
   yield ContentTask.spawn(tab.linkedBrowser, SIMPLE_SJS, function* (url) {
     content.wrappedJSObject.performRequest(url);
   });
   yield wait;
 
   EventUtils.sendMouseEvent({ type: "mousedown" },
--- a/devtools/client/netmonitor/test/browser_net_copy_image_as_data_uri.js
+++ b/devtools/client/netmonitor/test/browser_net_copy_image_as_data_uri.js
@@ -6,17 +6,17 @@
 /**
  * Tests if copying an image as data uri works.
  */
 
 add_task(function* () {
   let { tab, monitor } = yield initNetMonitor(CONTENT_TYPE_WITHOUT_CACHE_URL);
   info("Starting test... ");
 
-  let { document, gStore, windowRequire } = monitor.panelWin;
+  let { document } = monitor.panelWin;
 
   let wait = waitForNetworkEvents(monitor, CONTENT_TYPE_WITHOUT_CACHE_REQUESTS);
   yield ContentTask.spawn(tab.linkedBrowser, {}, function* () {
     content.wrappedJSObject.performRequests();
   });
   yield wait;
 
   EventUtils.sendMouseEvent({ type: "mousedown" },
@@ -24,14 +24,14 @@ add_task(function* () {
   EventUtils.sendMouseEvent({ type: "contextmenu" },
     document.querySelectorAll(".request-list-item")[5]);
 
   yield waitForClipboardPromise(function setup() {
     // Context menu is appending in XUL document, we must select it from
     // toolbox.doc
     monitor.toolbox.doc
       .querySelector("#request-list-context-copy-image-as-data-uri").click();
- }, TEST_IMAGE_DATA_URI);
+  }, TEST_IMAGE_DATA_URI);
 
   ok(true, "Clipboard contains the currently selected image as data uri.");
 
   yield teardown(monitor);
 });
--- a/devtools/client/netmonitor/test/browser_net_curl-utils.js
+++ b/devtools/client/netmonitor/test/browser_net_curl-utils.js
@@ -8,17 +8,17 @@
  */
 
 const { CurlUtils } = require("devtools/client/shared/curl");
 
 add_task(function* () {
   let { tab, monitor } = yield initNetMonitor(CURL_UTILS_URL);
   info("Starting test... ");
 
-  let { document, gStore, windowRequire, gNetwork } = monitor.panelWin;
+  let { gStore, windowRequire, gNetwork } = monitor.panelWin;
   let Actions = windowRequire("devtools/client/netmonitor/actions/index");
   let { getSortedRequests } = windowRequire("devtools/client/netmonitor/selectors/index");
 
   gStore.dispatch(Actions.batchEnable(false));
 
   let wait = waitForNetworkEvents(monitor, 1, 3);
   yield ContentTask.spawn(tab.linkedBrowser, SIMPLE_SJS, function* (url) {
     content.wrappedJSObject.performRequests(url);
--- a/devtools/client/netmonitor/test/browser_net_cyrillic-02.js
+++ b/devtools/client/netmonitor/test/browser_net_cyrillic-02.js
@@ -8,17 +8,16 @@
  * when loaded directly from an HTML page.
  */
 
 add_task(function* () {
   let { tab, monitor } = yield initNetMonitor(CYRILLIC_URL);
   info("Starting test... ");
 
   let { document, gStore, windowRequire } = monitor.panelWin;
-  let Actions = windowRequire("devtools/client/netmonitor/actions/index");
   let {
     getDisplayedRequests,
     getSortedRequests,
   } = windowRequire("devtools/client/netmonitor/selectors/index");
 
   let wait = waitForNetworkEvents(monitor, 1);
   tab.linkedBrowser.reload();
   yield wait;
--- a/devtools/client/netmonitor/test/browser_net_filter-04.js
+++ b/devtools/client/netmonitor/test/browser_net_filter-04.js
@@ -25,17 +25,18 @@ const REQUESTS_WITH_MEDIA_AND_FLASH = RE
 ]);
 
 const REQUESTS_WITH_MEDIA_AND_FLASH_AND_WS = REQUESTS_WITH_MEDIA_AND_FLASH.concat([
   /* "Upgrade" is a reserved header and can not be set on XMLHttpRequest */
   { url: "sjs_content-type-test-server.sjs?fmt=ws" },
 ]);
 
 add_task(function* () {
-  Services.prefs.setCharPref("devtools.netmonitor.filters", '["bogus", "js", "alsobogus"]');
+  Services.prefs.setCharPref("devtools.netmonitor.filters",
+                             '["bogus", "js", "alsobogus"]');
 
   let { monitor } = yield initNetMonitor(FILTERING_URL);
   info("Starting test... ");
 
   let { gStore, windowRequire } = monitor.panelWin;
   let Actions = windowRequire("devtools/client/netmonitor/actions/index");
   let { Prefs } = windowRequire("devtools/client/netmonitor/utils/prefs");
 
--- a/devtools/client/netmonitor/test/browser_net_footer-summary.js
+++ b/devtools/client/netmonitor/test/browser_net_footer-summary.js
@@ -44,18 +44,19 @@ add_task(function* () {
       EventUtils.sendMouseEvent({ type: "click" }, buttonEl);
       testStatus();
     }
   }
 
   yield teardown(monitor);
 
   function testStatus() {
-    let value = document.querySelector(".requests-list-network-summary-button").textContent;
-   info("Current summary: " + value);
+    let value = document.querySelector(".requests-list-network-summary-button")
+                        .textContent;
+    info("Current summary: " + value);
 
     let state = gStore.getState();
     let totalRequestsCount = state.requests.requests.size;
     let requestsSummary = getDisplayedRequestsSummary(state);
     info(`Current requests: ${requestsSummary.count} of ${totalRequestsCount}.`);
 
     if (!totalRequestsCount || !requestsSummary.count) {
       is(value, L10N.getStr("networkMenu.empty"),
--- a/devtools/client/netmonitor/test/browser_net_frame.js
+++ b/devtools/client/netmonitor/test/browser_net_frame.js
@@ -34,52 +34,52 @@ const EXPECTED_REQUESTS_TOP = [
     causeUri: TOP_URL,
     stack: false
   },
   {
     method: "GET",
     url: EXAMPLE_URL + "xhr_request",
     causeType: "xhr",
     causeUri: TOP_URL,
-    stack: [{ fn: "performXhrRequest", file: TOP_FILE_NAME, line: 23 }]
+    stack: [{ fn: "performXhrRequest", file: TOP_FILE_NAME, line: 25 }]
   },
   {
     method: "GET",
     url: EXAMPLE_URL + "fetch_request",
     causeType: "fetch",
     causeUri: TOP_URL,
-    stack: [{ fn: "performFetchRequest", file: TOP_FILE_NAME, line: 27 }]
+    stack: [{ fn: "performFetchRequest", file: TOP_FILE_NAME, line: 29 }]
   },
   {
     method: "GET",
     url: EXAMPLE_URL + "promise_fetch_request",
     causeType: "fetch",
     causeUri: TOP_URL,
     stack: [
-      { fn: "performPromiseFetchRequest", file: TOP_FILE_NAME, line: 39 },
-      { fn: null, file: TOP_FILE_NAME, line: 38, asyncCause: "promise callback" },
+      { fn: "performPromiseFetchRequest", file: TOP_FILE_NAME, line: 41 },
+      { fn: null, file: TOP_FILE_NAME, line: 40, asyncCause: "promise callback" },
     ]
   },
   {
     method: "GET",
     url: EXAMPLE_URL + "timeout_fetch_request",
     causeType: "fetch",
     causeUri: TOP_URL,
     stack: [
-      { fn: "performTimeoutFetchRequest", file: TOP_FILE_NAME, line: 41 },
-      { fn: "performPromiseFetchRequest", file: TOP_FILE_NAME, line: 40,
+      { fn: "performTimeoutFetchRequest", file: TOP_FILE_NAME, line: 43 },
+      { fn: "performPromiseFetchRequest", file: TOP_FILE_NAME, line: 42,
         asyncCause: "setTimeout handler" },
     ]
   },
   {
     method: "POST",
     url: EXAMPLE_URL + "beacon_request",
     causeType: "beacon",
     causeUri: TOP_URL,
-    stack: [{ fn: "performBeaconRequest", file: TOP_FILE_NAME, line: 31 }]
+    stack: [{ fn: "performBeaconRequest", file: TOP_FILE_NAME, line: 33 }]
   },
 ];
 
 const EXPECTED_REQUESTS_SUB = [
   {
     method: "GET",
     url: SUB_URL,
     causeType: "subdocument",
@@ -100,52 +100,52 @@ const EXPECTED_REQUESTS_SUB = [
     causeUri: SUB_URL,
     stack: false
   },
   {
     method: "GET",
     url: EXAMPLE_URL + "xhr_request",
     causeType: "xhr",
     causeUri: SUB_URL,
-    stack: [{ fn: "performXhrRequest", file: SUB_FILE_NAME, line: 22 }]
+    stack: [{ fn: "performXhrRequest", file: SUB_FILE_NAME, line: 24 }]
   },
   {
     method: "GET",
     url: EXAMPLE_URL + "fetch_request",
     causeType: "fetch",
     causeUri: SUB_URL,
-    stack: [{ fn: "performFetchRequest", file: SUB_FILE_NAME, line: 26 }]
+    stack: [{ fn: "performFetchRequest", file: SUB_FILE_NAME, line: 28 }]
   },
   {
     method: "GET",
     url: EXAMPLE_URL + "promise_fetch_request",
     causeType: "fetch",
     causeUri: SUB_URL,
     stack: [
-      { fn: "performPromiseFetchRequest", file: SUB_FILE_NAME, line: 38 },
-      { fn: null, file: SUB_FILE_NAME, line: 37, asyncCause: "promise callback" },
+      { fn: "performPromiseFetchRequest", file: SUB_FILE_NAME, line: 40 },
+      { fn: null, file: SUB_FILE_NAME, line: 39, asyncCause: "promise callback" },
     ]
   },
   {
     method: "GET",
     url: EXAMPLE_URL + "timeout_fetch_request",
     causeType: "fetch",
     causeUri: SUB_URL,
     stack: [
-      { fn: "performTimeoutFetchRequest", file: SUB_FILE_NAME, line: 40 },
-      { fn: "performPromiseFetchRequest", file: SUB_FILE_NAME, line: 39,
+      { fn: "performTimeoutFetchRequest", file: SUB_FILE_NAME, line: 42 },
+      { fn: "performPromiseFetchRequest", file: SUB_FILE_NAME, line: 41,
         asyncCause: "setTimeout handler" },
     ]
   },
   {
     method: "POST",
     url: EXAMPLE_URL + "beacon_request",
     causeType: "beacon",
     causeUri: SUB_URL,
-    stack: [{ fn: "performBeaconRequest", file: SUB_FILE_NAME, line: 30 }]
+    stack: [{ fn: "performBeaconRequest", file: SUB_FILE_NAME, line: 32 }]
   },
 ];
 
 const REQUEST_COUNT = EXPECTED_REQUESTS_TOP.length + EXPECTED_REQUESTS_SUB.length;
 
 add_task(function* () {
   // Async stacks aren't on by default in all builds
   yield SpecialPowers.pushPrefEnv({ set: [["javascript.options.asyncstack", true]] });
--- a/devtools/client/netmonitor/test/browser_net_header-docs.js
+++ b/devtools/client/netmonitor/test/browser_net_header-docs.js
@@ -11,17 +11,16 @@ const { getHeadersURL } = require("devto
  */
 add_task(function* () {
   let { tab, monitor } = yield initNetMonitor(POST_DATA_URL);
   info("Starting test... ");
 
   let { document, gStore, windowRequire } = monitor.panelWin;
   let Actions = windowRequire("devtools/client/netmonitor/actions/index");
   let {
-    getDisplayedRequests,
     getSortedRequests,
   } = windowRequire("devtools/client/netmonitor/selectors/index");
 
   gStore.dispatch(Actions.batchEnable(false));
 
   let wait = waitForNetworkEvents(monitor, 0, 2);
   yield ContentTask.spawn(tab.linkedBrowser, {}, function* () {
     content.wrappedJSObject.performRequests();
@@ -35,23 +34,25 @@ add_task(function* () {
 
   return teardown(monitor);
 
   /*
    * Tests that a "Learn More" button is only shown if
    * and only if a header is documented in MDN.
    */
   function testShowLearnMore(data) {
-    document.querySelectorAll(".properties-view .treeRow.stringRow").forEach((rowEl, index) => {
-      let headerName = rowEl.querySelectorAll(".treeLabelCell .treeLabel")[0].textContent;
+    let selector = ".properties-view .treeRow.stringRow";
+    document.querySelectorAll(selector).forEach((rowEl, index) => {
+      let headerName = rowEl.querySelectorAll(".treeLabelCell .treeLabel")[0]
+                            .textContent;
       let headerDocURL = getHeadersURL(headerName);
       let learnMoreEl = rowEl.querySelectorAll(".treeValueCell .learn-more-link");
 
       if (headerDocURL === null) {
         ok(learnMoreEl.length === 0,
-          "undocumented header does not include a \"Learn More\" button");
+           "undocumented header does not include a \"Learn More\" button");
       } else {
         ok(learnMoreEl[0].getAttribute("title") === headerDocURL,
-          "documented header includes a \"Learn More\" button with a link to MDN");
+           "documented header includes a \"Learn More\" button with a link to MDN");
       }
     });
   }
 });
--- a/devtools/client/netmonitor/test/browser_net_icon-preview.js
+++ b/devtools/client/netmonitor/test/browser_net_icon-preview.js
@@ -59,14 +59,15 @@ add_task(function* () {
   function* reloadAndPerformRequests() {
     yield NetMonitorController.triggerActivity(ACTIVITY_TYPE.RELOAD.WITH_CACHE_ENABLED);
     yield performRequests();
   }
 
   function checkImageThumbnail() {
     is(document.querySelectorAll(".requests-list-icon[data-type=thumbnail]").length, 1,
       "There should be only one image request with a thumbnail displayed.");
-    is(document.querySelector(".requests-list-icon[data-type=thumbnail]").src, TEST_IMAGE_DATA_URI,
+    is(document.querySelector(".requests-list-icon[data-type=thumbnail]").src,
+      TEST_IMAGE_DATA_URI,
       "The image requests-list-icon thumbnail is displayed correctly.");
     is(document.querySelector(".requests-list-icon[data-type=thumbnail]").hidden, false,
       "The image requests-list-icon thumbnail should not be hidden.");
   }
 });
--- a/devtools/client/netmonitor/test/browser_net_image-tooltip.js
+++ b/devtools/client/netmonitor/test/browser_net_image-tooltip.js
@@ -11,95 +11,90 @@ const IMAGE_TOOLTIP_REQUESTS = 1;
  */
 add_task(function* test() {
   let { tab, monitor } = yield initNetMonitor(IMAGE_TOOLTIP_URL);
   info("Starting test... ");
 
   let { document, gStore, windowRequire, NetMonitorController } = monitor.panelWin;
   let Actions = windowRequire("devtools/client/netmonitor/actions/index");
   let { ACTIVITY_TYPE, EVENTS } = windowRequire("devtools/client/netmonitor/constants");
-  let {
-    getDisplayedRequests,
-    getSortedRequests,
-  } = windowRequire("devtools/client/netmonitor/selectors/index");
   let toolboxDoc = monitor.toolbox.doc;
 
   gStore.dispatch(Actions.batchEnable(false));
 
   let onEvents = waitForNetworkEvents(monitor, IMAGE_TOOLTIP_REQUESTS);
   let onThumbnail = monitor.panelWin.once(EVENTS.RESPONSE_IMAGE_THUMBNAIL_DISPLAYED);
   yield performRequests();
   yield onEvents;
   yield onThumbnail;
 
   info("Checking the image thumbnail after a few requests were made...");
-  yield showTooltipAndVerify(toolboxDoc,
-    document.querySelectorAll(".request-list-item")[0]);
+  yield showTooltipAndVerify(document.querySelectorAll(".request-list-item")[0]);
 
   // Hide tooltip before next test, to avoid the situation that tooltip covers
   // the icon for the request of the next test.
   info("Checking the image thumbnail gets hidden...");
-  yield hideTooltipAndVerify(monitor.toolbox.doc,
-    document.querySelectorAll(".request-list-item")[0]);
+  yield hideTooltipAndVerify(document.querySelectorAll(".request-list-item")[0]);
 
   // +1 extra document reload
   onEvents = waitForNetworkEvents(monitor, IMAGE_TOOLTIP_REQUESTS + 1);
   onThumbnail = monitor.panelWin.once(EVENTS.RESPONSE_IMAGE_THUMBNAIL_DISPLAYED);
 
   info("Reloading the debuggee and performing all requests again...");
   yield NetMonitorController.triggerActivity(ACTIVITY_TYPE.RELOAD.WITH_CACHE_ENABLED);
   yield performRequests();
   yield onEvents;
   yield onThumbnail;
 
   info("Checking the image thumbnail after a reload.");
-  yield showTooltipAndVerify(toolboxDoc,
-    document.querySelectorAll(".request-list-item")[1]);
+  yield showTooltipAndVerify(document.querySelectorAll(".request-list-item")[1]);
 
   info("Checking if the image thumbnail is hidden when mouse leaves the menu widget");
   let requestsListContents = document.querySelector(".requests-list-contents");
-  EventUtils.synthesizeMouse(requestsListContents, 0, 0, { type: "mouseout" }, monitor.panelWin);
+  EventUtils.synthesizeMouse(requestsListContents, 0, 0, { type: "mouseout" },
+                             monitor.panelWin);
   yield waitUntil(() => !toolboxDoc.querySelector(".tooltip-container.tooltip-visible"));
 
   yield teardown(monitor);
 
   function performRequests() {
     return ContentTask.spawn(tab.linkedBrowser, {}, function* () {
       content.wrappedJSObject.performRequests();
     });
   }
 
   /**
    * Show a tooltip on the {target} and verify that it was displayed
    * with the expected content.
    */
-  function* showTooltipAndVerify(toolboxDoc, target) {
+  function* showTooltipAndVerify(target) {
     let anchor = target.querySelector(".requests-list-file");
-    yield showTooltipOn(toolboxDoc, anchor);
+    yield showTooltipOn(anchor);
 
     info("Tooltip was successfully opened for the image request.");
     is(toolboxDoc.querySelector(".tooltip-panel img").src, TEST_IMAGE_DATA_URI,
       "The tooltip's image content is displayed correctly.");
   }
 
   /**
    * Trigger a tooltip over an element by sending mousemove event.
    * @return a promise that resolves when the tooltip is shown
    */
-  function* showTooltipOn(toolboxDoc, element) {
+  function* showTooltipOn(element) {
     let win = element.ownerDocument.defaultView;
     EventUtils.synthesizeMouseAtCenter(element, { type: "mousemove" }, win);
     yield waitUntil(() => toolboxDoc.querySelector(".tooltip-panel img"));
   }
 
   /**
    * Hide a tooltip on the {target} and verify that it was closed.
    */
-  function* hideTooltipAndVerify(toolboxDoc, target) {
+  function* hideTooltipAndVerify(target) {
     // Hovering over the "method" column hides the tooltip.
     let anchor = target.querySelector(".requests-list-method");
     let win = anchor.ownerDocument.defaultView;
     EventUtils.synthesizeMouseAtCenter(anchor, { type: "mousemove" }, win);
 
-    yield waitUntil(() => !toolboxDoc.querySelector(".tooltip-container.tooltip-visible"));
+    yield waitUntil(
+      () => !toolboxDoc.querySelector(".tooltip-container.tooltip-visible"));
     info("Tooltip was successfully closed.");
   }
 });
--- a/devtools/client/netmonitor/test/browser_net_json-null.js
+++ b/devtools/client/netmonitor/test/browser_net_json-null.js
@@ -10,20 +10,16 @@ const { L10N } = require("devtools/clien
  */
 
 add_task(function* () {
   let { tab, monitor } = yield initNetMonitor(JSON_BASIC_URL + "?name=null");
   info("Starting test... ");
 
   let { document, gStore, windowRequire } = monitor.panelWin;
   let Actions = windowRequire("devtools/client/netmonitor/actions/index");
-  let {
-    getDisplayedRequests,
-    getSortedRequests,
-  } = windowRequire("devtools/client/netmonitor/selectors/index");
 
   gStore.dispatch(Actions.batchEnable(false));
 
   let wait = waitForNetworkEvents(monitor, 1);
   yield ContentTask.spawn(tab.linkedBrowser, {}, function* () {
     content.wrappedJSObject.performRequests();
   });
   yield wait;
--- a/devtools/client/netmonitor/test/browser_net_open_request_in_tab.js
+++ b/devtools/client/netmonitor/test/browser_net_open_request_in_tab.js
@@ -8,20 +8,16 @@
  */
 
 add_task(function* () {
   let { tab, monitor } = yield initNetMonitor(CUSTOM_GET_URL);
   info("Starting test...");
 
   let { document, gStore, windowRequire } = monitor.panelWin;
   let Actions = windowRequire("devtools/client/netmonitor/actions/index");
-  let {
-    getDisplayedRequests,
-    getSortedRequests,
-  } = windowRequire("devtools/client/netmonitor/selectors/index");
 
   gStore.dispatch(Actions.batchEnable(false));
 
   let wait = waitForNetworkEvents(monitor, 1);
   yield ContentTask.spawn(tab.linkedBrowser, {}, function* () {
     content.wrappedJSObject.performRequests(1);
   });
   yield wait;
--- a/devtools/client/netmonitor/test/browser_net_persistent_logs.js
+++ b/devtools/client/netmonitor/test/browser_net_persistent_logs.js
@@ -7,17 +7,17 @@
  * Tests if the network monitor leaks on initialization and sudden destruction.
  * You can also use this initialization format as a template for other tests.
  */
 
 add_task(function* () {
   let { tab, monitor } = yield initNetMonitor(SINGLE_GET_URL);
   info("Starting test... ");
 
-  let { document, windowRequire } = monitor.panelWin;
+  let { document } = monitor.panelWin;
 
   Services.prefs.setBoolPref("devtools.webconsole.persistlog", false);
 
   yield reloadAndWait();
 
   is(document.querySelectorAll(".request-list-item").length, 2,
     "The request list should have two items at this point.");
 
--- a/devtools/client/netmonitor/test/browser_net_post-data-01.js
+++ b/devtools/client/netmonitor/test/browser_net_post-data-01.js
@@ -39,31 +39,33 @@ add_task(function* () {
     SIMPLE_SJS + "?foo=bar&baz=42&type=urlencoded",
     {
       status: 200,
       statusText: "Och Aye",
       type: "plain",
       fullMimeType: "text/plain; charset=utf-8",
       size: L10N.getFormatStrWithNumbers("networkMenu.sizeB", 12),
       time: true
-    });
-   verifyRequestItemTarget(
+    }
+  );
+  verifyRequestItemTarget(
     document,
     getDisplayedRequests(gStore.getState()),
     getSortedRequests(gStore.getState()).get(1),
     "POST",
     SIMPLE_SJS + "?foo=bar&baz=42&type=multipart",
     {
       status: 200,
       statusText: "Och Aye",
       type: "plain",
       fullMimeType: "text/plain; charset=utf-8",
       size: L10N.getFormatStrWithNumbers("networkMenu.sizeB", 12),
       time: true
-    });
+    }
+  );
 
   // Wait for all tree sections updated by react
   wait = waitForDOM(document, "#params-panel .tree-section", 2);
   EventUtils.sendMouseEvent({ type: "mousedown" },
     document.querySelectorAll(".request-list-item")[0]);
   EventUtils.sendMouseEvent({ type: "click" },
     document.querySelector("#params-tab"));
   yield wait;
@@ -102,39 +104,43 @@ add_task(function* () {
     is(treeSections[0].querySelector(".treeLabel").textContent,
       L10N.getStr("paramsQueryString"),
       "The query section doesn't have the correct title.");
 
     is(treeSections[1].querySelector(".treeLabel").textContent,
       L10N.getStr(type == "urlencoded" ? "paramsFormData" : "paramsPostPayload"),
       "The post section doesn't have the correct title.");
 
-    let labels = tabpanel.querySelectorAll("tr:not(.tree-section) .treeLabelCell .treeLabel");
-    let values = tabpanel.querySelectorAll("tr:not(.tree-section) .treeValueCell .objectBox");
+    let labels = tabpanel
+      .querySelectorAll("tr:not(.tree-section) .treeLabelCell .treeLabel");
+    let values = tabpanel
+      .querySelectorAll("tr:not(.tree-section) .treeValueCell .objectBox");
 
     is(labels[0].textContent, "foo", "The first query param name was incorrect.");
     is(values[0].textContent, "\"bar\"", "The first query param value was incorrect.");
     is(labels[1].textContent, "baz", "The second query param name was incorrect.");
     is(values[1].textContent, "\"42\"", "The second query param value was incorrect.");
     is(labels[2].textContent, "type", "The third query param name was incorrect.");
-    is(values[2].textContent, "\"" + type + "\"", "The third query param value was incorrect.");
+    is(values[2].textContent, "\"" + type + "\"",
+      "The third query param value was incorrect.");
 
     if (type == "urlencoded") {
       checkVisibility("params");
       is(labels.length, 5, "There should be 5 param values displayed in this tabpanel.");
       is(labels[3].textContent, "foo", "The first post param name was incorrect.");
       is(values[3].textContent, "\"bar\"", "The first post param value was incorrect.");
       is(labels[4].textContent, "baz", "The second post param name was incorrect.");
       is(values[4].textContent, "\"123\"", "The second post param value was incorrect.");
     } else {
       checkVisibility("params editor");
 
       is(labels.length, 3, "There should be 3 param values displayed in this tabpanel.");
 
-      let text = editorFrames[0].contentDocument.querySelector(".CodeMirror-code").textContent;
+      let text = editorFrames[0].contentDocument.querySelector(".CodeMirror-code")
+                                                .textContent;
 
       ok(text.includes("Content-Disposition: form-data; name=\"text\""),
         "The text shown in the source editor is incorrect (1.1).");
       ok(text.includes("Content-Disposition: form-data; name=\"email\""),
         "The text shown in the source editor is incorrect (2.1).");
       ok(text.includes("Content-Disposition: form-data; name=\"range\""),
         "The text shown in the source editor is incorrect (3.1).");
       ok(text.includes("Content-Disposition: form-data; name=\"Custom field\""),
--- a/devtools/client/netmonitor/test/browser_net_post-data-02.js
+++ b/devtools/client/netmonitor/test/browser_net_post-data-02.js
@@ -11,20 +11,16 @@
 add_task(function* () {
   let { L10N } = require("devtools/client/netmonitor/utils/l10n");
 
   let { tab, monitor } = yield initNetMonitor(POST_RAW_URL);
   info("Starting test... ");
 
   let { document, gStore, windowRequire } = monitor.panelWin;
   let Actions = windowRequire("devtools/client/netmonitor/actions/index");
-  let {
-    getDisplayedRequests,
-    getSortedRequests,
-  } = windowRequire("devtools/client/netmonitor/selectors/index");
 
   gStore.dispatch(Actions.batchEnable(false));
 
   let wait = waitForNetworkEvents(monitor, 0, 1);
   yield ContentTask.spawn(tab.linkedBrowser, {}, function* () {
     content.wrappedJSObject.performRequests();
   });
   yield wait;
--- a/devtools/client/netmonitor/test/browser_net_prefs-reload.js
+++ b/devtools/client/netmonitor/test/browser_net_prefs-reload.js
@@ -6,28 +6,30 @@
 /**
  * Tests if the prefs that should survive across tool reloads work.
  */
 
 add_task(function* () {
   let { monitor } = yield initNetMonitor(SIMPLE_URL);
   let { getRequestFilterTypes } = monitor.panelWin
     .windowRequire("devtools/client/netmonitor/selectors/index");
-  let Actions = monitor.panelWin.windowRequire("devtools/client/netmonitor/actions/index");
+  let Actions = monitor.panelWin
+    .windowRequire("devtools/client/netmonitor/actions/index");
   info("Starting test... ");
 
   // This test reopens the network monitor a bunch of times, for different
   // hosts (bottom, side, window). This seems to be slow on debug builds.
   requestLongerTimeout(3);
 
   // Use these getters instead of caching instances inside the panel win,
   // since the tool is reopened a bunch of times during this test
   // and the instances will differ.
   let getDoc = () => monitor.panelWin.document;
-  let getPrefs = () => monitor.panelWin.windowRequire("devtools/client/netmonitor/utils/prefs").Prefs;
+  let getPrefs = () => monitor.panelWin
+    .windowRequire("devtools/client/netmonitor/utils/prefs").Prefs;
   let getStore = () => monitor.panelWin.gStore;
   let getState = () => getStore().getState();
 
   let prefsToCheck = {
     filters: {
       // A custom new value to be used for the verified preference.
       newValue: ["html", "css"],
       // Getter used to retrieve the current value from the frontend, in order
@@ -39,25 +41,29 @@ add_task(function* () {
       // before trying to validate the changes.
       modifyFrontend: (value) => value.forEach(e =>
         getStore().dispatch(Actions.toggleRequestFilterType(e)))
     },
     networkDetailsWidth: {
       newValue: ~~(Math.random() * 200 + 100),
       validateValue: () =>
         getDoc().querySelector(".monitor-panel .split-box .controlled").clientWidth,
-      modifyFrontend: (value) =>
-        getDoc().querySelector(".monitor-panel .split-box .controlled").style.width = `${value}px`,
+      modifyFrontend: function (value) {
+        getDoc().querySelector(".monitor-panel .split-box .controlled")
+                .style.width = `${value}px`;
+      }
     },
     networkDetailsHeight: {
       newValue: ~~(Math.random() * 300 + 100),
       validateValue: () =>
         getDoc().querySelector(".monitor-panel .split-box .controlled").clientHeight,
-      modifyFrontend: (value) =>
-        getDoc().querySelector(".monitor-panel .split-box .controlled").style.height = `${value}px`
+      modifyFrontend: function (value) {
+        getDoc().querySelector(".monitor-panel .split-box .controlled")
+                .style.height = `${value}px`;
+      }
     }
     /* add more prefs here... */
   };
 
   yield testBottom();
   yield testSide();
   yield testWindow();
 
@@ -75,17 +81,17 @@ add_task(function* () {
   }
 
   function validateFirstPrefValues(isVerticalSplitter) {
     info("Validating current pref values to the UI elements.");
 
     for (let name in prefsToCheck) {
       if ((isVerticalSplitter && name === "networkDetailsHeight") ||
           (!isVerticalSplitter && name === "networkDetailsWidth")) {
-        continue
+        continue;
       }
 
       let currentValue = getPrefs()[name];
       let firstValue = prefsToCheck[name].firstValue;
       let validateValue = prefsToCheck[name].validateValue;
 
       is(firstValue.toSource(), currentValue.toSource(),
         "Pref " + name + " should be equal to first value: " + currentValue);
@@ -95,17 +101,17 @@ add_task(function* () {
   }
 
   function modifyFrontend(isVerticalSplitter) {
     info("Modifying UI elements to the specified new values.");
 
     for (let name in prefsToCheck) {
       if ((isVerticalSplitter && name === "networkDetailsHeight") ||
           (!isVerticalSplitter && name === "networkDetailsWidth")) {
-        continue
+        continue;
       }
 
       let currentValue = getPrefs()[name];
       let firstValue = prefsToCheck[name].firstValue;
       let newValue = prefsToCheck[name].newValue;
       let validateValue = prefsToCheck[name].validateValue;
       let modFrontend = prefsToCheck[name].modifyFrontend;
 
@@ -122,17 +128,17 @@ add_task(function* () {
   }
 
   function validateNewPrefValues(isVerticalSplitter) {
     info("Invalidating old pref values to the modified UI elements.");
 
     for (let name in prefsToCheck) {
       if ((isVerticalSplitter && name === "networkDetailsHeight") ||
           (!isVerticalSplitter && name === "networkDetailsWidth")) {
-        continue
+        continue;
       }
 
       let currentValue = getPrefs()[name];
       let firstValue = prefsToCheck[name].firstValue;
       let newValue = prefsToCheck[name].newValue;
       let validateValue = prefsToCheck[name].validateValue;
 
       isnot(firstValue.toSource(), currentValue.toSource(),
@@ -145,17 +151,17 @@ add_task(function* () {
   }
 
   function resetFrontend(isVerticalSplitter) {
     info("Resetting UI elements to the cached initial pref values.");
 
     for (let name in prefsToCheck) {
       if ((isVerticalSplitter && name === "networkDetailsHeight") ||
           (!isVerticalSplitter && name === "networkDetailsWidth")) {
-        continue
+        continue;
       }
 
       let currentValue = getPrefs()[name];
       let firstValue = prefsToCheck[name].firstValue;
       let newValue = prefsToCheck[name].newValue;
       let validateValue = prefsToCheck[name].validateValue;
       let modFrontend = prefsToCheck[name].modifyFrontend;
 
--- a/devtools/client/netmonitor/test/browser_net_reload-markers.js
+++ b/devtools/client/netmonitor/test/browser_net_reload-markers.js
@@ -6,17 +6,17 @@
 /**
  * Tests if the empty-requests reload button works.
  */
 
 add_task(function* () {
   let { monitor } = yield initNetMonitor(SIMPLE_URL);
   info("Starting test... ");
 
-  let { document, windowRequire } = monitor.panelWin;
+  let { document } = monitor.panelWin;
   let button = document.querySelector(".requests-list-reload-notice-button");
   button.click();
 
   let markers = [];
 
   monitor.panelWin.on(EVENTS.TIMELINE_EVENT, (_, marker) => {
     markers.push(marker);
   });
--- a/devtools/client/netmonitor/test/browser_net_resend.js
+++ b/devtools/client/netmonitor/test/browser_net_resend.js
@@ -69,17 +69,17 @@ add_task(function* () {
     let expectedUrl = orig.url + "&" + ADD_QUERY;
 
     is(url, expectedUrl, "menu item is updated to reflect url entered in form");
   }
 
   /*
    * Test that the New Request form was populated correctly
    */
-  function testCustomForm(data) {
+  function* testCustomForm(data) {
     yield waitUntil(() => document.querySelector(".custom-request-panel"));
     is(document.getElementById("custom-method-value").value, data.method,
        "new request form showing correct method");
 
     is(document.getElementById("custom-url-value").value, data.url,
        "new request form showing correct url");
 
     let query = document.getElementById("custom-query-value");
--- a/devtools/client/netmonitor/test/browser_net_resend_cors.js
+++ b/devtools/client/netmonitor/test/browser_net_resend_cors.js
@@ -7,20 +7,19 @@
  * Tests if resending a CORS request avoids the security checks and doesn't send
  * a preflight OPTIONS request (bug 1270096 and friends)
  */
 
 add_task(function* () {
   let { tab, monitor } = yield initNetMonitor(CORS_URL);
   info("Starting test... ");
 
-  let { document, gStore, windowRequire } = monitor.panelWin;
+  let { gStore, windowRequire } = monitor.panelWin;
   let Actions = windowRequire("devtools/client/netmonitor/actions/index");
   let {
-    getDisplayedRequests,
     getSortedRequests,
   } = windowRequire("devtools/client/netmonitor/selectors/index");
 
   gStore.dispatch(Actions.batchEnable(false));
 
   let requestUrl = "http://test1.example.com" + CORS_SJS_PATH;
 
   info("Waiting for OPTIONS, then POST");
@@ -39,17 +38,17 @@ add_task(function* () {
     is(item.url, requestUrl, `The ${item.method} request has the right URL`);
   });
 
   // Resend both requests without modification. Wait for resent OPTIONS, then POST.
   // POST is supposed to have no preflight OPTIONS request this time (CORS is disabled)
   let onRequests = waitForNetworkEvents(monitor, 1, 0);
   ITEMS.forEach((item) => {
     info(`Selecting the ${item.method} request`);
-    gStore.dispatch(Actions.selectRequest(item.id))
+    gStore.dispatch(Actions.selectRequest(item.id));
 
     info("Cloning the selected request into a custom clone");
     gStore.dispatch(Actions.cloneSelectedRequest());
 
     info("Sending the cloned request (without change)");
     gStore.dispatch(Actions.sendCustomRequest());
   });
 
--- a/devtools/client/netmonitor/test/browser_net_resend_headers.js
+++ b/devtools/client/netmonitor/test/browser_net_resend_headers.js
@@ -6,20 +6,19 @@
 /**
  * Test if custom request headers are not ignored (bug 1270096 and friends)
  */
 
 add_task(function* () {
   let { monitor } = yield initNetMonitor(SIMPLE_SJS);
   info("Starting test... ");
 
-  let { document, gStore, windowRequire, NetMonitorController } = monitor.panelWin;
+  let { gStore, windowRequire, NetMonitorController } = monitor.panelWin;
   let Actions = windowRequire("devtools/client/netmonitor/actions/index");
   let {
-    getDisplayedRequests,
     getSortedRequests,
   } = windowRequire("devtools/client/netmonitor/selectors/index");
 
   gStore.dispatch(Actions.batchEnable(false));
 
   let requestUrl = SIMPLE_SJS;
   let requestHeaders = [
     { name: "Host", value: "fakehost.example.com" },
--- a/devtools/client/netmonitor/test/browser_net_security-details.js
+++ b/devtools/client/netmonitor/test/browser_net_security-details.js
@@ -41,27 +41,28 @@ add_task(function* () {
   // The cipher suite used by the test server example.com might change at any
   // moment but all of them should start with "TLS_".
   // http://www.iana.org/assignments/tls-parameters/tls-parameters.xhtml
   let suite = textboxes[1].value;
   ok(suite.startsWith("TLS_"), "The suite " + suite + " seems valid.");
 
   // Host
   is(tabpanel.querySelectorAll(".treeLabel.objectLabel")[1].textContent,
-    "Host example.com:",
-    "Label has the expected value.");
+     "Host example.com:",
+     "Label has the expected value.");
   is(textboxes[2].value, "Disabled", "Label has the expected value.");
   is(textboxes[3].value, "Disabled", "Label has the expected value.");
 
   // Cert
   is(textboxes[4].value, "example.com", "Label has the expected value.");
   is(textboxes[5].value, "<Not Available>", "Label has the expected value.");
   is(textboxes[6].value, "<Not Available>", "Label has the expected value.");
 
-  is(textboxes[7].value, "Temporary Certificate Authority", "Label has the expected value.");
+  is(textboxes[7].value, "Temporary Certificate Authority",
+     "Label has the expected value.");
   is(textboxes[8].value, "Mozilla Testing", "Label has the expected value.");
   is(textboxes[9].value, "Profile Guided Optimization", "Label has the expected value.");
 
   // Locale sensitive and varies between timezones. Cant't compare equality or
   // the test fails depending on which part of the world the test is executed.
 
   // cert validity begins
   isnot(textboxes[10].value, "", "Label was not empty.");
--- a/devtools/client/netmonitor/test/browser_net_security-icon-click.js
+++ b/devtools/client/netmonitor/test/browser_net_security-icon-click.js
@@ -6,20 +6,16 @@
 /**
  * Test that clicking on the security indicator opens the security details tab.
  */
 
 add_task(function* () {
   let { tab, monitor } = yield initNetMonitor(CUSTOM_GET_URL);
   let { document, gStore, windowRequire } = monitor.panelWin;
   let Actions = windowRequire("devtools/client/netmonitor/actions/index");
-  let {
-    getDisplayedRequests,
-    getSortedRequests,
-  } = windowRequire("devtools/client/netmonitor/selectors/index");
 
   gStore.dispatch(Actions.batchEnable(false));
 
   info("Requesting a resource over HTTPS.");
   yield performRequestAndWait("https://example.com" + CORS_SJS_PATH + "?request_2");
   yield performRequestAndWait("https://example.com" + CORS_SJS_PATH + "?request_1");
 
   is(gStore.getState().requests.requests.size, 2, "Two events event logged.");
@@ -49,11 +45,12 @@ add_task(function* () {
   }
 
   function* clickAndTestSecurityIcon() {
     let icon = document.querySelector(".requests-security-state-icon");
 
     info("Clicking security icon of the first request and waiting for panel update.");
     EventUtils.synthesizeMouseAtCenter(icon, {}, monitor.panelWin);
 
-    ok(document.querySelector("#security-tab[aria-selected=true]"), "Security tab is selected.");
+    ok(document.querySelector("#security-tab[aria-selected=true]"),
+       "Security tab is selected.");
   }
 });
--- a/devtools/client/netmonitor/test/browser_net_security-redirect.js
+++ b/devtools/client/netmonitor/test/browser_net_security-redirect.js
@@ -7,36 +7,32 @@
  * Test a http -> https redirect shows secure icon only for redirected https
  * request.
  */
 
 add_task(function* () {
   let { tab, monitor } = yield initNetMonitor(CUSTOM_GET_URL);
   let { document, gStore, windowRequire } = monitor.panelWin;
   let Actions = windowRequire("devtools/client/netmonitor/actions/index");
-  let {
-    getDisplayedRequests,
-    getSortedRequests,
-  } = windowRequire("devtools/client/netmonitor/selectors/index");
 
   gStore.dispatch(Actions.batchEnable(false));
 
   let wait = waitForNetworkEvents(monitor, 2);
   yield ContentTask.spawn(tab.linkedBrowser, HTTPS_REDIRECT_SJS, function* (url) {
     content.wrappedJSObject.performRequests(1, url);
   });
   yield wait;
 
-  is(gStore.getState().requests.requests.size, 2, "There were two requests due to redirect.");
+  is(gStore.getState().requests.requests.size, 2,
+     "There were two requests due to redirect.");
 
-  let initial = getSortedRequests(gStore.getState()).get(0);
-  let redirect = getSortedRequests(gStore.getState()).get(1);
-
-  let initialSecurityIcon = document.querySelectorAll(".requests-security-state-icon")[0];
-  let redirectSecurityIcon = document.querySelectorAll(".requests-security-state-icon")[1];
+  let [
+    initialSecurityIcon,
+    redirectSecurityIcon,
+  ] = document.querySelectorAll(".requests-security-state-icon");
 
   ok(initialSecurityIcon.classList.contains("security-state-insecure"),
      "Initial request was marked insecure.");
 
   ok(redirectSecurityIcon.classList.contains("security-state-secure"),
      "Redirected request was marked secure.");
 
   yield teardown(monitor);
--- a/devtools/client/netmonitor/test/browser_net_security-state.js
+++ b/devtools/client/netmonitor/test/browser_net_security-state.js
@@ -14,20 +14,16 @@ add_task(function* () {
     "example.com": "security-state-secure",
     "nocert.example.com": "security-state-broken",
     "localhost": "security-state-local",
   };
 
   let { tab, monitor } = yield initNetMonitor(CUSTOM_GET_URL);
   let { document, gStore, windowRequire } = monitor.panelWin;
   let Actions = windowRequire("devtools/client/netmonitor/actions/index");
-  let {
-    getDisplayedRequests,
-    getSortedRequests,
-  } = windowRequire("devtools/client/netmonitor/selectors/index");
 
   gStore.dispatch(Actions.batchEnable(false));
 
   yield performRequests();
 
   for (let subitemNode of Array.from(document.querySelectorAll(
     "requests-list-subitem.requests-list-security-and-domain"))) {
     let domain = subitemNode.querySelector(".requests-list-domain").textContent;
--- a/devtools/client/netmonitor/test/browser_net_security-tab-visibility.js
+++ b/devtools/client/netmonitor/test/browser_net_security-tab-visibility.js
@@ -29,17 +29,19 @@ add_task(function* () {
       visibleOnSecurityInfo: true,
       visibleOnceComplete: true,
     }
   ];
 
   let { tab, monitor } = yield initNetMonitor(CUSTOM_GET_URL);
   let { document, gStore, windowRequire } = monitor.panelWin;
   let Actions = windowRequire("devtools/client/netmonitor/actions/index");
-  let { getSelectedRequest } = windowRequire("devtools/client/netmonitor/selectors/index");
+  let {
+    getSelectedRequest,
+  } = windowRequire("devtools/client/netmonitor/selectors/index");
 
   gStore.dispatch(Actions.batchEnable(false));
 
   for (let testcase of TEST_DATA) {
     info("Testing Security tab visibility for " + testcase.desc);
     let onNewItem = monitor.panelWin.once(EVENTS.NETWORK_EVENT);
     let onSecurityInfo = monitor.panelWin.once(EVENTS.RECEIVED_SECURITY_INFO);
     let onComplete = testcase.isBroken ?
--- a/devtools/client/netmonitor/test/browser_net_send-beacon.js
+++ b/devtools/client/netmonitor/test/browser_net_send-beacon.js
@@ -4,17 +4,17 @@
 "use strict";
 
 /**
  * Tests if beacons are handled correctly.
  */
 
 add_task(function* () {
   let { tab, monitor } = yield initNetMonitor(SEND_BEACON_URL);
- let { gStore, windowRequire } = monitor.panelWin;
+  let { gStore, windowRequire } = monitor.panelWin;
   let Actions = windowRequire("devtools/client/netmonitor/actions/index");
   let { getSortedRequests } = windowRequire("devtools/client/netmonitor/selectors/index");
 
   gStore.dispatch(Actions.batchEnable(false));
 
   is(gStore.getState().requests.requests.size, 0, "The requests menu should be empty.");
 
   let wait = waitForNetworkEvents(monitor, 1);
--- a/devtools/client/netmonitor/test/browser_net_simple-request-data.js
+++ b/devtools/client/netmonitor/test/browser_net_simple-request-data.js
@@ -218,22 +218,25 @@ function test() {
       );
     });
 
     monitor.panelWin.once(EVENTS.RECEIVED_RESPONSE_CONTENT, () => {
       let requestItem = getSortedRequests(gStore.getState()).get(0);
 
       ok(requestItem.responseContent,
         "There should be a responseContent data available.");
+      // eslint-disable-next-line mozilla/no-cpows-in-tests
       is(requestItem.responseContent.content.mimeType,
         "text/plain; charset=utf-8",
         "The responseContent data has an incorrect |content.mimeType| property.");
+      // eslint-disable-next-line mozilla/no-cpows-in-tests
       is(requestItem.responseContent.content.text,
         "Hello world!",
         "The responseContent data has an incorrect |content.text| property.");
+      // eslint-disable-next-line mozilla/no-cpows-in-tests
       is(requestItem.responseContent.content.size,
         12,
         "The responseContent data has an incorrect |content.size| property.");
 
       verifyRequestItemTarget(
         document,
         getDisplayedRequests(gStore.getState()),
         requestItem,
--- a/devtools/client/netmonitor/test/browser_net_simple-request-details.js
+++ b/devtools/client/netmonitor/test/browser_net_simple-request-details.js
@@ -8,23 +8,24 @@
  */
 
 add_task(function* () {
   let { L10N } = require("devtools/client/netmonitor/utils/l10n");
 
   let { tab, monitor } = yield initNetMonitor(SIMPLE_SJS);
   info("Starting test... ");
 
-  let { document, gStore, windowRequire } = monitor.panelWin;
+  let { document, gStore, windowRequire, NetMonitorView } = monitor.panelWin;
   let Actions = windowRequire("devtools/client/netmonitor/actions/index");
   let { EVENTS } = windowRequire("devtools/client/netmonitor/constants");
   let {
-    getDisplayedRequests,
+    getSelectedRequest,
     getSortedRequests,
   } = windowRequire("devtools/client/netmonitor/selectors/index");
+  let Editor = require("devtools/client/sourceeditor/editor");
 
   gStore.dispatch(Actions.batchEnable(false));
 
   let wait = waitForNetworkEvents(monitor, 1);
   tab.linkedBrowser.reload();
   yield wait;
 
   is(getSelectedRequest(gStore.getState()), undefined,
--- a/devtools/client/netmonitor/test/browser_net_simple-request.js
+++ b/devtools/client/netmonitor/test/browser_net_simple-request.js
@@ -13,21 +13,16 @@
  * 4) Number of requests displayed
  */
 add_task(function* () {
   let { tab, monitor } = yield initNetMonitor(SIMPLE_URL);
   info("Starting test... ");
 
   let { document, gStore, windowRequire } = monitor.panelWin;
   let Actions = windowRequire("devtools/client/netmonitor/actions/index");
-  let { EVENTS } = windowRequire("devtools/client/netmonitor/constants");
-  let {
-    getDisplayedRequests,
-    getSortedRequests,
-  } = windowRequire("devtools/client/netmonitor/selectors/index");
 
   gStore.dispatch(Actions.batchEnable(false));
 
   is(document.querySelector(".network-details-panel-toggle").hasAttribute("disabled"),
     true,
     "The pane toggle button should be disabled when the frontend is opened.");
   ok(document.querySelector(".request-list-empty-notice"),
     "An empty notice should be displayed when the frontend is opened.");
--- a/devtools/client/netmonitor/test/browser_net_statistics-01.js
+++ b/devtools/client/netmonitor/test/browser_net_statistics-01.js
@@ -22,27 +22,31 @@ add_task(function* () {
   gStore.dispatch(Actions.openStatistics(true));
 
   ok(document.querySelector(".statistics-panel"),
     "The current main panel is correct.");
 
   info("Waiting for placeholder to display");
 
   yield waitUntil(
-    () => document.querySelectorAll(".pie-chart-container[placeholder=true]").length == 2);
+    () => document.querySelectorAll(".pie-chart-container[placeholder=true]")
+                  .length == 2);
   ok(true, "Two placeholder pie charts appear to be rendered correctly.");
 
   yield waitUntil(
-    () => document.querySelectorAll(".table-chart-container[placeholder=true]").length == 2);
+    () => document.querySelectorAll(".table-chart-container[placeholder=true]")
+                  .length == 2);
   ok(true, "Two placeholde table charts appear to be rendered correctly.");
 
   info("Waiting for chart to display");
 
   yield waitUntil(
-    () => document.querySelectorAll(".pie-chart-container:not([placeholder=true])").length == 2);
+    () => document.querySelectorAll(".pie-chart-container:not([placeholder=true])")
+                  .length == 2);
   ok(true, "Two real pie charts appear to be rendered correctly.");
 
   yield waitUntil(
-    () => document.querySelectorAll(".table-chart-container:not([placeholder=true])").length == 2);
+    () => document.querySelectorAll(".table-chart-container:not([placeholder=true])")
+                  .length == 2);
   ok(true, "Two real table charts appear to be rendered correctly.");
 
   yield teardown(monitor);
 });
--- a/devtools/client/netmonitor/test/browser_net_statistics-02.js
+++ b/devtools/client/netmonitor/test/browser_net_statistics-02.js
@@ -30,17 +30,18 @@ add_task(function* () {
   info("The correct filtering predicates are used before entering perf. analysis mode.");
 
   gStore.dispatch(Actions.openStatistics(true));
 
   ok(document.querySelector(".statistics-panel"),
     "The main panel is switched to the statistics panel.");
 
   yield waitUntil(
-    () => document.querySelectorAll(".pie-chart-container:not([placeholder=true])").length == 2);
+    () => document.querySelectorAll(".pie-chart-container:not([placeholder=true])")
+                  .length == 2);
   ok(true, "Two real pie charts appear to be rendered correctly.");
 
   EventUtils.sendMouseEvent({ type: "click" },
     document.querySelector(".pie-chart-slice"));
 
   ok(document.querySelector(".monitor-panel"),
     "The main panel is switched back to the monitor panel.");
 
--- a/devtools/client/netmonitor/test/browser_net_throttle.js
+++ b/devtools/client/netmonitor/test/browser_net_throttle.js
@@ -9,18 +9,17 @@ add_task(function* () {
   yield throttleTest(true);
   yield throttleTest(false);
 });
 
 function* throttleTest(actuallyThrottle) {
   requestLongerTimeout(2);
 
   let { monitor } = yield initNetMonitor(SIMPLE_URL);
-  let { document, gStore, windowRequire, NetMonitorController } = monitor.panelWin;
-  let Actions = windowRequire("devtools/client/netmonitor/actions/index");
+  let { gStore, windowRequire, NetMonitorController } = monitor.panelWin;
   let { ACTIVITY_TYPE } = windowRequire("devtools/client/netmonitor/constants");
   let { EVENTS } = windowRequire("devtools/client/netmonitor/constants");
   let {
     getSortedRequests,
   } = windowRequire("devtools/client/netmonitor/selectors/index");
 
   info("Starting test... (actuallyThrottle = " + actuallyThrottle + ")");
 
--- a/devtools/client/netmonitor/test/browser_net_timing-division.js
+++ b/devtools/client/netmonitor/test/browser_net_timing-division.js
@@ -34,25 +34,26 @@ add_task(function* () {
   info("Number of millisecond divisions: " + milDivs.length);
   info("Number of second divisions: " + secDivs.length);
   info("Number of minute divisions: " + minDivs.length);
 
   milDivs.forEach(div => info(`Millisecond division: ${div.textContent}`));
   secDivs.forEach(div => info(`Second division: ${div.textContent}`));
   minDivs.forEach(div => info(`Minute division: ${div.textContent}`));
 
-  is(gStore.getState().requests.requests.size, 2, "There should be only two requests made.");
+  is(gStore.getState().requests.requests.size, 2,
+     "There should be only two requests made.");
 
   let firstRequest = getSortedRequests(gStore.getState()).get(0);
   let lastRequest = getSortedRequests(gStore.getState()).get(1);
 
   info("First request happened at: " +
-    firstRequest.responseHeaders.headers.find(e => e.name == "Date").value);
+       firstRequest.responseHeaders.headers.find(e => e.name == "Date").value);
   info("Last request happened at: " +
-    lastRequest.responseHeaders.headers.find(e => e.name == "Date").value);
+       lastRequest.responseHeaders.headers.find(e => e.name == "Date").value);
 
   ok(secDivs.length,
-    "There should be at least one division on the seconds time scale.");
+     "There should be at least one division on the seconds time scale.");
   ok(secDivs[0].textContent.match(/\d+\.\d{2}\s\w+/),
-    "The division on the seconds time scale looks legit.");
+     "The division on the seconds time scale looks legit.");
 
   return teardown(monitor);
 });
--- a/devtools/client/netmonitor/test/head.js
+++ b/devtools/client/netmonitor/test/head.js
@@ -1,29 +1,33 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 /* import-globals-from ../../framework/test/shared-head.js */
+/* exported Toolbox, restartNetMonitor, teardown, waitForExplicitFinish,
+   verifyRequestItemTarget, waitFor, testFilterButtons, loadCommonFrameScript,
+   performRequestsInContent, waitForNetworkEvents */
 
 "use strict";
 
 // shared-head.js handles imports, constants, and utility functions
 Services.scriptloader.loadSubScript(
   "chrome://mochitests/content/browser/devtools/client/framework/test/shared-head.js",
   this);
 
 const { EVENTS } = require("devtools/client/netmonitor/constants");
-var { Toolbox } = require("devtools/client/framework/toolbox");
+let { Toolbox } = require("devtools/client/framework/toolbox");
 const {
   decodeUnicodeUrl,
   getUrlBaseName,
   getUrlQuery,
   getUrlHost,
 } = require("devtools/client/netmonitor/utils/request-utils");
 
+/* eslint-disable no-unused-vars, max-len */
 const EXAMPLE_URL = "http://example.com/browser/devtools/client/netmonitor/test/";
 const HTTPS_EXAMPLE_URL = "https://example.com/browser/devtools/client/netmonitor/test/";
 
 const API_CALLS_URL = EXAMPLE_URL + "html_api-calls-test-page.html";
 const SIMPLE_URL = EXAMPLE_URL + "html_simple-test-page.html";
 const NAVIGATE_URL = EXAMPLE_URL + "html_navigate-test-page.html";
 const CONTENT_TYPE_URL = EXAMPLE_URL + "html_content-type-test-page.html";
 const CONTENT_TYPE_WITHOUT_CACHE_URL = EXAMPLE_URL + "html_content-type-without-cache-test-page.html";
@@ -64,16 +68,17 @@ const HSTS_SJS = EXAMPLE_URL + "sjs_hsts
 
 const HSTS_BASE_URL = EXAMPLE_URL;
 const HSTS_PAGE_URL = CUSTOM_GET_URL;
 
 const TEST_IMAGE = EXAMPLE_URL + "test-image.png";
 const TEST_IMAGE_DATA_URI = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAHWSURBVHjaYvz//z8DJQAggJiQOe/fv2fv7Oz8rays/N+VkfG/iYnJfyD/1+rVq7ffu3dPFpsBAAHEAHIBCJ85c8bN2Nj4vwsDw/8zQLwKiO8CcRoQu0DxqlWrdsHUwzBAAIGJmTNnPgYa9j8UqhFElwPxf2MIDeIrKSn9FwSJoRkAEEAM0DD4DzMAyPi/G+QKY4hh5WAXGf8PDQ0FGwJ22d27CjADAAIIrLmjo+MXA9R2kAHvGBA2wwx6B8W7od6CeQcggKCmCEL8bgwxYCbUIGTDVkHDBia+CuotgACCueD3TDQN75D4xmAvCoK9ARMHBzAw0AECiBHkAlC0Mdy7x9ABNA3obAZXIAa6iKEcGlMVQHwWyjYuL2d4v2cPg8vZswx7gHyAAAK7AOif7SAbOqCmn4Ha3AHFsIDtgPq/vLz8P4MSkJ2W9h8ggBjevXvHDo4FQUQg/kdypqCg4H8lUIACnQ/SOBMYI8bAsAJFPcj1AAEEjwVQqLpAbXmH5BJjqI0gi9DTAAgDBBCcAVLkgmQ7yKCZxpCQxqUZhAECCJ4XgMl493ug21ZD+aDAXH0WLM4A9MZPXJkJIIAwTAR5pQMalaCABQUULttBGCCAGCnNzgABBgAMJ5THwGvJLAAAAABJRU5ErkJggg==";
 
 const FRAME_SCRIPT_UTILS_URL = "chrome://devtools/content/shared/frame-script-utils.js";
+/* eslint-enable no-unused-vars, max-len */
 
 // All tests are asynchronous.
 waitForExplicitFinish();
 
 const gEnableLogging = Services.prefs.getBoolPref("devtools.debugger.log");
 // To enable logging for try runs, just set the pref to true.
 Services.prefs.setBoolPref("devtools.debugger.log", false);
 
@@ -86,55 +91,55 @@ const gDefaultFilters = Services.prefs.g
 registerCleanupFunction(() => {
   info("finish() was called, cleaning up...");
 
   Services.prefs.setBoolPref("devtools.debugger.log", gEnableLogging);
   Services.prefs.setCharPref("devtools.netmonitor.filters", gDefaultFilters);
   Services.prefs.clearUserPref("devtools.cache.disabled");
 });
 
-function waitForNavigation(aTarget) {
+function waitForNavigation(target) {
   let deferred = promise.defer();
-  aTarget.once("will-navigate", () => {
-    aTarget.once("navigate", () => {
+  target.once("will-navigate", () => {
+    target.once("navigate", () => {
       deferred.resolve();
     });
   });
   return deferred.promise;
 }
 
-function reconfigureTab(aTarget, aOptions) {
+function reconfigureTab(target, options) {
   let deferred = promise.defer();
-  aTarget.activeTab.reconfigure(aOptions, deferred.resolve);
+  target.activeTab.reconfigure(options, deferred.resolve);
   return deferred.promise;
 }
 
-function toggleCache(aTarget, aDisabled) {
-  let options = { cacheDisabled: aDisabled, performReload: true };
-  let navigationFinished = waitForNavigation(aTarget);
+function toggleCache(target, disabled) {
+  let options = { cacheDisabled: disabled, performReload: true };
+  let navigationFinished = waitForNavigation(target);
 
   // Disable the cache for any toolbox that it is opened from this point on.
-  Services.prefs.setBoolPref("devtools.cache.disabled", aDisabled);
+  Services.prefs.setBoolPref("devtools.cache.disabled", disabled);
 
-  return reconfigureTab(aTarget, options).then(() => navigationFinished);
+  return reconfigureTab(target, options).then(() => navigationFinished);
 }
 
-function initNetMonitor(aUrl, aWindow, aEnableCache) {
+function initNetMonitor(url, window, enableCache) {
   info("Initializing a network monitor pane.");
 
   return Task.spawn(function* () {
-    let tab = yield addTab(aUrl);
-    info("Net tab added successfully: " + aUrl);
+    let tab = yield addTab(url);
+    info("Net tab added successfully: " + url);
 
     let target = TargetFactory.forTab(tab);
 
     yield target.makeRemote();
     info("Target remoted.");
 
-    if (!aEnableCache) {
+    if (!enableCache) {
       info("Disabling cache and reloading page.");
       yield toggleCache(target, true);
       info("Cache disabled when the current and all future toolboxes are open.");
       // Remove any requests generated by the reload while toggling the cache to
       // avoid interfering with the test.
       isnot([...target.activeConsole.getNetworkEvents()].length, 0,
          "Request to reconfigure the tab was recorded.");
       target.activeConsole.clearNetworkRequests();
@@ -170,19 +175,19 @@ function teardown(monitor) {
     let tab = monitor.toolbox.target.tab;
 
     let onDestroyed = monitor.once("destroyed");
     yield removeTab(tab);
     yield onDestroyed;
   });
 }
 
-function waitForNetworkEvents(aMonitor, aGetRequests, aPostRequests = 0) {
+function waitForNetworkEvents(monitor, getRequests, postRequests = 0) {
   let deferred = promise.defer();
-  let panel = aMonitor.panelWin;
+  let panel = monitor.panelWin;
   let progress = {};
   let genericEvents = 0;
   let postEvents = 0;
   let awaitedEventsToListeners = [
     ["UPDATING_REQUEST_HEADERS", onGenericEvent],
     ["RECEIVED_REQUEST_HEADERS", onGenericEvent],
     ["UPDATING_REQUEST_COOKIES", onGenericEvent],
     ["RECEIVED_REQUEST_COOKIES", onGenericEvent],
@@ -195,19 +200,23 @@ function waitForNetworkEvents(aMonitor, 
     ["STARTED_RECEIVING_RESPONSE", onGenericEvent],
     ["UPDATING_RESPONSE_CONTENT", onGenericEvent],
     ["RECEIVED_RESPONSE_CONTENT", onGenericEvent],
     ["UPDATING_EVENT_TIMINGS", onGenericEvent],
     ["RECEIVED_EVENT_TIMINGS", onGenericEvent]
   ];
 
   function initProgressForURL(url) {
-    if (progress[url]) return;
+    if (progress[url]) {
+      return;
+    }
     progress[url] = {};
-    awaitedEventsToListeners.forEach(([e]) => progress[url][e] = 0);
+    awaitedEventsToListeners.forEach(function ([e]) {
+      progress[url][e] = 0;
+    });
   }
 
   function updateProgressForURL(url, event) {
     initProgressForURL(url);
     progress[url][Object.keys(EVENTS).find(e => EVENTS[e] == event)] = 1;
   }
 
   function onGenericEvent(event, actor) {
@@ -217,137 +226,141 @@ function waitForNetworkEvents(aMonitor, 
 
   function onPostEvent(event, actor) {
     postEvents++;
     maybeResolve(event, actor);
   }
 
   function maybeResolve(event, actor) {
     info("> Network events progress: " +
-      genericEvents + "/" + ((aGetRequests + aPostRequests) * 13) + ", " +
-      postEvents + "/" + (aPostRequests * 2) + ", " +
+      genericEvents + "/" + ((getRequests + postRequests) * 13) + ", " +
+      postEvents + "/" + (postRequests * 2) + ", " +
       "got " + event + " for " + actor);
 
     let networkInfo =
       panel.NetMonitorController.webConsoleClient.getNetworkRequest(actor);
     let url = networkInfo.request.url;
     updateProgressForURL(url, event);
 
     // Uncomment this to get a detailed progress logging (when debugging a test)
     // info("> Current state: " + JSON.stringify(progress, null, 2));
 
     // There are 15 updates which need to be fired for a request to be
     // considered finished. The "requestPostData" packet isn't fired for
     // non-POST requests.
-    if (genericEvents >= (aGetRequests + aPostRequests) * 13 &&
-        postEvents >= aPostRequests * 2) {
-
+    if (genericEvents >= (getRequests + postRequests) * 13 &&
+        postEvents >= postRequests * 2) {
       awaitedEventsToListeners.forEach(([e, l]) => panel.off(EVENTS[e], l));
       executeSoon(deferred.resolve);
     }
   }
 
   awaitedEventsToListeners.forEach(([e, l]) => panel.on(EVENTS[e], l));
   return deferred.promise;
 }
 
-function verifyRequestItemTarget(document, requestList, requestItem, aMethod,
-                                 aUrl, aData = {}) {
-  info("> Verifying: " + aMethod + " " + aUrl + " " + aData.toSource());
+function verifyRequestItemTarget(document, requestList, requestItem, method,
+                                 url, data = {}) {
+  info("> Verifying: " + method + " " + url + " " + data.toSource());
 
   let visibleIndex = requestList.indexOf(requestItem);
 
   info("Visible index of item: " + visibleIndex);
 
   let { fuzzyUrl, status, statusText, cause, type, fullMimeType,
-        transferred, size, time, displayedStatus } = aData;
+        transferred, size, time, displayedStatus } = data;
 
   let target = document.querySelectorAll(".request-list-item")[visibleIndex];
-  let unicodeUrl = decodeUnicodeUrl(aUrl);
-  let name = getUrlBaseName(aUrl);
-  let query = getUrlQuery(aUrl);
-  let hostPort = getUrlHost(aUrl);
+  let unicodeUrl = decodeUnicodeUrl(url);
+  let name = getUrlBaseName(url);
+  let query = getUrlQuery(url);
+  let hostPort = getUrlHost(url);
   let remoteAddress = requestItem.remoteAddress;
 
   if (fuzzyUrl) {
-    ok(requestItem.method.startsWith(aMethod), "The attached method is correct.");
-    ok(requestItem.url.startsWith(aUrl), "The attached url is correct.");
+    ok(requestItem.method.startsWith(method), "The attached method is correct.");
+    ok(requestItem.url.startsWith(url), "The attached url is correct.");
   } else {
-    is(requestItem.method, aMethod, "The attached method is correct.");
-    is(requestItem.url, aUrl, "The attached url is correct.");
+    is(requestItem.method, method, "The attached method is correct.");
+    is(requestItem.url, url, "The attached url is correct.");
   }
 
   is(target.querySelector(".requests-list-method").textContent,
-    aMethod, "The displayed method is correct.");
+    method, "The displayed method is correct.");
 
   if (fuzzyUrl) {
     ok(target.querySelector(".requests-list-file").textContent.startsWith(
       name + (query ? "?" + query : "")), "The displayed file is correct.");
-    ok(target.querySelector(".requests-list-file").getAttribute("title").startsWith(unicodeUrl),
+    ok(target.querySelector(".requests-list-file").getAttribute("title")
+                                                  .startsWith(unicodeUrl),
       "The tooltip file is correct.");
   } else {
     is(target.querySelector(".requests-list-file").textContent,
       name + (query ? "?" + query : ""), "The displayed file is correct.");
     is(target.querySelector(".requests-list-file").getAttribute("title"),
       unicodeUrl, "The tooltip file is correct.");
   }
 
   is(target.querySelector(".requests-list-domain").textContent,
     hostPort, "The displayed domain is correct.");
 
   let domainTooltip = hostPort + (remoteAddress ? " (" + remoteAddress + ")" : "");
   is(target.querySelector(".requests-list-domain").getAttribute("title"),
     domainTooltip, "The tooltip domain is correct.");
 
   if (status !== undefined) {
-    let value = target.querySelector(".requests-list-status-icon").getAttribute("data-code");
+    let value = target.querySelector(".requests-list-status-icon")
+                      .getAttribute("data-code");
     let codeValue = target.querySelector(".requests-list-status-code").textContent;
     let tooltip = target.querySelector(".requests-list-status").getAttribute("title");
     info("Displayed status: " + value);
     info("Displayed code: " + codeValue);
     info("Tooltip status: " + tooltip);
-    is(value, displayedStatus ? displayedStatus : status, "The displayed status is correct.");
+    is(value, displayedStatus ? displayedStatus : status,
+      "The displayed status is correct.");
     is(codeValue, status, "The displayed status code is correct.");
     is(tooltip, status + " " + statusText, "The tooltip status is correct.");
   }
   if (cause !== undefined) {
     let value = target.querySelector(".requests-list-cause > .subitem-label").textContent;
     let tooltip = target.querySelector(".requests-list-cause").getAttribute("title");
     info("Displayed cause: " + value);
     info("Tooltip cause: " + tooltip);
     is(value, cause.type, "The displayed cause is correct.");
-    is(tooltip, cause.loadingDocumentUri, "The tooltip cause is correct.")
+    is(tooltip, cause.loadingDocumentUri, "The tooltip cause is correct.");
   }
   if (type !== undefined) {
     let value = target.querySelector(".requests-list-type").textContent;
     let tooltip = target.querySelector(".requests-list-type").getAttribute("title");
     info("Displayed type: " + value);
     info("Tooltip type: " + tooltip);
     is(value, type, "The displayed type is correct.");
     is(tooltip, fullMimeType, "The tooltip type is correct.");
   }
   if (transferred !== undefined) {
     let value = target.querySelector(".requests-list-transferred").textContent;
-    let tooltip = target.querySelector(".requests-list-transferred").getAttribute("title");
+    let tooltip = target.querySelector(".requests-list-transferred")
+                        .getAttribute("title");
     info("Displayed transferred size: " + value);
     info("Tooltip transferred size: " + tooltip);
     is(value, transferred, "The displayed transferred size is correct.");
     is(tooltip, transferred, "The tooltip transferred size is correct.");
   }
   if (size !== undefined) {
     let value = target.querySelector(".requests-list-size").textContent;
     let tooltip = target.querySelector(".requests-list-size").getAttribute("title");
     info("Displayed size: " + value);
     info("Tooltip size: " + tooltip);
     is(value, size, "The displayed size is correct.");
     is(tooltip, size, "The tooltip size is correct.");
   }
   if (time !== undefined) {
     let value = target.querySelector(".requests-list-timings-total").textContent;
-    let tooltip = target.querySelector(".requests-list-timings-total").getAttribute("title");
+    let tooltip = target.querySelector(".requests-list-timings-total")
+                        .getAttribute("title");
     info("Displayed time: " + value);
     info("Tooltip time: " + tooltip);
     ok(~~(value.match(/[0-9]+/)) >= 0, "The displayed time is correct.");
     ok(~~(tooltip.match(/[0-9]+/)) >= 0, "The tooltip time is correct.");
   }
 
   if (visibleIndex !== -1) {
     if (visibleIndex % 2 === 0) {
@@ -398,22 +411,22 @@ function testFilterButtons(monitor, filt
 /**
  * Tests if filter buttons have 'checked' attributes set correctly.
  *
  * @param array aIsChecked
  *        An array specifying if a button at given index should have a
  *        'checked' attribute. For example, if the third item of the array
  *        evaluates to true, the third button should be checked.
  */
-function testFilterButtonsCustom(aMonitor, aIsChecked) {
-  let doc = aMonitor.panelWin.document;
+function testFilterButtonsCustom(monitor, isChecked) {
+  let doc = monitor.panelWin.document;
   let buttons = doc.querySelectorAll("#requests-list-filter-buttons button");
-  for (let i = 0; i < aIsChecked.length; i++) {
+  for (let i = 0; i < isChecked.length; i++) {
     let button = buttons[i];
-    if (aIsChecked[i]) {
+    if (isChecked[i]) {
       is(button.classList.contains("checked"), true,
         "The " + button.id + " button should have a 'checked' class.");
       is(button.getAttribute("aria-pressed"), "true",
         "The " + button.id + " button should set 'aria-pressed' = true.");
     } else {
       is(button.classList.contains("checked"), false,
         "The " + button.id + " button should not have a 'checked' class.");
       is(button.getAttribute("aria-pressed"), "false",
@@ -468,19 +481,18 @@ function performRequestsInContent(reques
  *         resolves otherwise
  */
 function executeInContent(name, data = {}, objects = {}, expectResponse = true) {
   let mm = gBrowser.selectedBrowser.messageManager;
 
   mm.sendAsyncMessage(name, data, objects);
   if (expectResponse) {
     return waitForContentMessage(name);
-  } else {
-    return promise.resolve();
   }
+  return promise.resolve();
 }
 
 /**
  * Wait for a content -> chrome message on the message manager (the window
  * messagemanager is used).
  * @param {String} name The message name
  * @return {Promise} A promise that resolves to the response data when the
  * message has been received
--- a/devtools/client/netmonitor/test/html_api-calls-test-page.html
+++ b/devtools/client/netmonitor/test/html_api-calls-test-page.html
@@ -10,37 +10,42 @@
     <meta http-equiv="Expires" content="0" />
     <title>Network Monitor test page</title>
   </head>
 
   <body>
     <p>API calls request test</p>
 
     <script type="text/javascript">
-      function get(aAddress, aCallback) {
-        var xhr = new XMLHttpRequest();
-        xhr.open("GET", aAddress, true);
+      /* exported performRequests */
+      "use strict";
 
-        xhr.onreadystatechange = function() {
+      function get(address, callback) {
+        let xhr = new XMLHttpRequest();
+        xhr.open("GET", address, true);
+
+        xhr.onreadystatechange = function () {
           if (this.readyState == this.DONE) {
-            aCallback();
+            callback();
           }
         };
         xhr.send();
       }
 
       function performRequests() {
-        get("/api/fileName.xml", function() {
-          get("/api/file%E2%98%A2.xml", function() {
-            get("/api/ascii/get/", function() {
-              get("/api/unicode/%E2%98%A2/", function() {
-                get("/api/search/?q=search%E2%98%A2", function() {
+        /* eslint-disable max-nested-callbacks */
+        get("/api/fileName.xml", function () {
+          get("/api/file%E2%98%A2.xml", function () {
+            get("/api/ascii/get/", function () {
+              get("/api/unicode/%E2%98%A2/", function () {
+                get("/api/search/?q=search%E2%98%A2", function () {
                   // Done.
                 });
               });
             });
           });
         });
+        /* eslint-enable max-nested-callbacks */
       }
     </script>
   </body>
 
 </html>
--- a/devtools/client/netmonitor/test/html_brotli-test-page.html
+++ b/devtools/client/netmonitor/test/html_brotli-test-page.html
@@ -10,29 +10,32 @@
     <meta http-equiv="Expires" content="0" />
     <title>Network Monitor test page</title>
   </head>
 
   <body>
     <p>Brotli test</p>
 
     <script type="text/javascript">
-      function get(aAddress, aCallback) {
-        var xhr = new XMLHttpRequest();
-        xhr.open("GET", aAddress, true);
+      /* exported performRequests */
+      "use strict";
 
-        xhr.onreadystatechange = function() {
+      function get(address, callback) {
+        let xhr = new XMLHttpRequest();
+        xhr.open("GET", address, true);
+
+        xhr.onreadystatechange = function () {
           if (this.readyState == this.DONE) {
-            aCallback();
+            callback();
           }
         };
         xhr.send(null);
       }
 
       function performRequests() {
-        get("sjs_content-type-test-server.sjs?fmt=br", function() {
+        get("sjs_content-type-test-server.sjs?fmt=br", function () {
           // Done.
         });
       }
     </script>
   </body>
 
 </html>
--- a/devtools/client/netmonitor/test/html_cause-test-page.html
+++ b/devtools/client/netmonitor/test/html_cause-test-page.html
@@ -11,18 +11,20 @@
     <title>Network Monitor test page</title>
     <link rel="stylesheet" type="text/css" href="stylesheet_request" />
   </head>
 
   <body>
     <p>Request cause test</p>
     <img src="img_request" />
     <script type="text/javascript">
+      "use strict";
+
       function performXhrRequest() {
-        var xhr = new XMLHttpRequest();
+        let xhr = new XMLHttpRequest();
         xhr.open("GET", "xhr_request", true);
         xhr.send();
       }
 
       function performFetchRequest() {
         fetch("fetch_request");
       }
 
--- a/devtools/client/netmonitor/test/html_content-type-test-page.html
+++ b/devtools/client/netmonitor/test/html_content-type-test-page.html
@@ -10,39 +10,44 @@
     <meta http-equiv="Expires" content="0" />
     <title>Network Monitor test page</title>
   </head>
 
   <body>
     <p>Content type test</p>
 
     <script type="text/javascript">
-      function get(aAddress, aCallback) {
-        var xhr = new XMLHttpRequest();
-        xhr.open("GET", aAddress, true);
+      /* exported performRequests */
+      "use strict";
 
-        xhr.onreadystatechange = function() {
+      function get(address, callback) {
+        let xhr = new XMLHttpRequest();
+        xhr.open("GET", address, true);
+
+        xhr.onreadystatechange = function () {
           if (this.readyState == this.DONE) {
-            aCallback();
+            callback();
           }
         };
         xhr.send(null);
       }
 
       function performRequests() {
-        get("sjs_content-type-test-server.sjs?fmt=xml", function() {
-          get("sjs_content-type-test-server.sjs?fmt=css", function() {
-            get("sjs_content-type-test-server.sjs?fmt=js", function() {
-              get("sjs_content-type-test-server.sjs?fmt=json", function() {
-                get("sjs_content-type-test-server.sjs?fmt=bogus", function() {
-                  get("test-image.png", function() {
+        /* eslint-disable max-nested-callbacks */
+        get("sjs_content-type-test-server.sjs?fmt=xml", function () {
+          get("sjs_content-type-test-server.sjs?fmt=css", function () {
+            get("sjs_content-type-test-server.sjs?fmt=js", function () {
+              get("sjs_content-type-test-server.sjs?fmt=json", function () {
+                get("sjs_content-type-test-server.sjs?fmt=bogus", function () {
+                  get("test-image.png", function () {
                     // Done.
                   });
                 });
               });
             });
           });
         });
+        /* eslint-enable max-nested-callbacks */
       }
     </script>
   </body>
 
 </html>
--- a/devtools/client/netmonitor/test/html_content-type-without-cache-test-page.html
+++ b/devtools/client/netmonitor/test/html_content-type-without-cache-test-page.html
@@ -10,43 +10,48 @@
     <meta http-equiv="Expires" content="0" />
     <title>Network Monitor test page</title>
   </head>
 
   <body>
     <p>Content type test</p>
 
     <script type="text/javascript">
-      function get(aAddress, aCallback) {
-        var xhr = new XMLHttpRequest();
-        xhr.open("GET", aAddress, true);
+      /* exported performRequests */
+      "use strict";
 
-        xhr.onreadystatechange = function() {
+      function get(address, callback) {
+        let xhr = new XMLHttpRequest();
+        xhr.open("GET", address, true);
+
+        xhr.onreadystatechange = function () {
           if (this.readyState == this.DONE) {
-            aCallback();
+            callback();
           }
         };
         xhr.send(null);
       }
 
       function performRequests() {
-        get("sjs_content-type-test-server.sjs?fmt=xml", function() {
-          get("sjs_content-type-test-server.sjs?fmt=css", function() {
-            get("sjs_content-type-test-server.sjs?fmt=js", function() {
-              get("sjs_content-type-test-server.sjs?fmt=json", function() {
-                get("sjs_content-type-test-server.sjs?fmt=bogus", function() {
-                  get("test-image.png?v=" + Math.random(), function() {
-                    get("sjs_content-type-test-server.sjs?fmt=gzip", function() {
-                      get("sjs_content-type-test-server.sjs?fmt=br", function() {
+        /* eslint-disable max-nested-callbacks */
+        get("sjs_content-type-test-server.sjs?fmt=xml", function () {
+          get("sjs_content-type-test-server.sjs?fmt=css", function () {
+            get("sjs_content-type-test-server.sjs?fmt=js", function () {
+              get("sjs_content-type-test-server.sjs?fmt=json", function () {
+                get("sjs_content-type-test-server.sjs?fmt=bogus", function () {
+                  get("test-image.png?v=" + Math.random(), function () {
+                    get("sjs_content-type-test-server.sjs?fmt=gzip", function () {
+                      get("sjs_content-type-test-server.sjs?fmt=br", function () {
                         // Done.
                       });
                     });
                   });
                 });
               });
             });
           });
         });
+        /* eslint-enable max-nested-callbacks */
       }
     </script>
   </body>
 
 </html>
--- a/devtools/client/netmonitor/test/html_copy-as-curl.html
+++ b/devtools/client/netmonitor/test/html_copy-as-curl.html
@@ -10,19 +10,22 @@
     <meta http-equiv="Expires" content="0" />
     <title>Network Monitor test page</title>
   </head>
 
   <body>
     <p>Performing a GET request</p>
 
     <script type="text/javascript">
-      function performRequest(aUrl) {
-        var xhr = new XMLHttpRequest();
-        xhr.open("GET", aUrl, true);
+      /* exported performRequest */
+      "use strict";
+
+      function performRequest(url) {
+        let xhr = new XMLHttpRequest();
+        xhr.open("GET", url, true);
         xhr.setRequestHeader("Accept-Language", window.navigator.language);
         xhr.setRequestHeader("X-Custom-Header-1", "Custom value");
         xhr.setRequestHeader("X-Custom-Header-2", "8.8.8.8");
         xhr.setRequestHeader("X-Custom-Header-3", "Mon, 3 Mar 2014 11:11:11 GMT");
         xhr.send(null);
       }
     </script>
   </body>
--- a/devtools/client/netmonitor/test/html_cors-test-page.html
+++ b/devtools/client/netmonitor/test/html_cors-test-page.html
@@ -10,18 +10,21 @@
     <meta http-equiv="Expires" content="0" />
     <title>Network Monitor test page</title>
   </head>
 
   <body>
     <p>POST with CORS test page</p>
 
     <script type="text/javascript">
+      /* exported performRequests */
+      "use strict";
+
       function post(url, contentType, postData) {
-        var xhr = new XMLHttpRequest();
+        let xhr = new XMLHttpRequest();
         xhr.open("POST", url, true);
         xhr.setRequestHeader("Content-Type", contentType);
         xhr.send(postData);
       }
 
       function performRequests(url, contentType, postData) {
         post(url, contentType, postData);
       }
--- a/devtools/client/netmonitor/test/html_curl-utils.html
+++ b/devtools/client/netmonitor/test/html_curl-utils.html
@@ -24,76 +24,78 @@
       <input type="text" name="param1" value="value1"/>
       <input type="text" name="param2" value="value2"/>
       <input type="text" name="param3" value="value3"/>
       <input type="submit"/>
     </form>
     <iframe name="target"></iframe>
 
     <script type="text/javascript">
+      /* exported performRequests */
+      "use strict";
 
-      function ajaxGet(aUrl, aCallback) {
-        var xhr = new XMLHttpRequest();
-        xhr.open("GET", aUrl + "?param1=value1&param2=value2&param3=value3", true);
+      function ajaxGet(url, callback) {
+        let xhr = new XMLHttpRequest();
+        xhr.open("GET", url + "?param1=value1&param2=value2&param3=value3", true);
         xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest");
-        xhr.onload = function() {
-          aCallback();
+        xhr.onload = function () {
+          callback();
         };
         xhr.send();
       }
 
-      function ajaxPost(aUrl, aCallback) {
-        var xhr = new XMLHttpRequest();
-        xhr.open("POST", aUrl, true);
+      function ajaxPost(url, callback) {
+        let xhr = new XMLHttpRequest();
+        xhr.open("POST", url, true);
         xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
         xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest");
-        xhr.onload = function() {
-          aCallback();
+        xhr.onload = function () {
+          callback();
         };
-        var params = "param1=value1&param2=value2&param3=value3";
+        const params = "param1=value1&param2=value2&param3=value3";
         xhr.send(params);
       }
 
-      function ajaxMultipart(aUrl, aCallback) {
-        var xhr = new XMLHttpRequest();
-        xhr.open("POST", aUrl, true);
+      function ajaxMultipart(url, callback) {
+        let xhr = new XMLHttpRequest();
+        xhr.open("POST", url, true);
         xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest");
-        xhr.onload = function() {
-          aCallback();
+        xhr.onload = function () {
+          callback();
         };
 
         getCanvasElem().toBlob((blob) => {
-          var formData = new FormData();
+          let formData = new FormData();
           formData.append("param1", "value1");
           formData.append("file", blob, "filename.png");
           xhr.send(formData);
         });
       }
 
       function submitForm() {
-        var form = document.querySelector("#post-form");
+        let form = document.querySelector("#post-form");
         form.submit();
       }
 
       function getCanvasElem() {
         return document.querySelector("canvas");
       }
 
       function initCanvas() {
-        var canvas = getCanvasElem();
-        var ctx = canvas.getContext("2d");
-        ctx.fillRect(0,0,100,100);
-        ctx.clearRect(20,20,60,60);
-        ctx.strokeRect(25,25,50,50);
+        let canvas = getCanvasElem();
+        let ctx = canvas.getContext("2d");
+        ctx.fillRect(0, 0, 100, 100);
+        ctx.clearRect(20, 20, 60, 60);
+        ctx.strokeRect(25, 25, 50, 50);
       }
 
-      function performRequests(aUrl) {
-        ajaxGet(aUrl, () => {
-          ajaxPost(aUrl, () => {
-            ajaxMultipart(aUrl, () => {
+      function performRequests(url) {
+        ajaxGet(url, () => {
+          ajaxPost(url, () => {
+            ajaxMultipart(url, () => {
               submitForm();
             });
           });
         });
       }
 
       initCanvas();
     </script>
--- a/devtools/client/netmonitor/test/html_custom-get-page.html
+++ b/devtools/client/netmonitor/test/html_custom-get-page.html
@@ -10,35 +10,38 @@
     <meta http-equiv="Expires" content="0" />
     <title>Network Monitor test page</title>
   </head>
 
   <body>
     <p>Performing a custom number of GETs</p>
 
     <script type="text/javascript">
-      function get(aAddress, aCallback) {
-        var xhr = new XMLHttpRequest();
-        xhr.open("GET", aAddress, true);
+      /* exported performRequests */
+      "use strict";
 
-        xhr.onreadystatechange = function() {
+      function get(address, callback) {
+        let xhr = new XMLHttpRequest();
+        xhr.open("GET", address, true);
+
+        xhr.onreadystatechange = function () {
           if (this.readyState == this.DONE) {
-            aCallback();
+            callback();
           }
         };
         xhr.send(null);
       }
 
       // Use a count parameter to defeat caching.
-      var count = 0;
+      let count = 0;
 
-      function performRequests(aTotal, aUrl, aTimeout = 0) {
-        if (!aTotal) {
+      function performRequests(total, url, timeout = 0) {
+        if (!total) {
           return;
         }
-        get(aUrl || "request_" + (count++), function() {
-          setTimeout(performRequests.bind(this, --aTotal, aUrl, aTimeout), aTimeout);
+        get(url || "request_" + (count++), function () {
+          setTimeout(performRequests.bind(this, --total, url, timeout), timeout);
         });
       }
     </script>
   </body>
 
 </html>
--- a/devtools/client/netmonitor/test/html_cyrillic-test-page.html
+++ b/devtools/client/netmonitor/test/html_cyrillic-test-page.html
@@ -11,29 +11,32 @@
     <title>Network Monitor test page</title>
   </head>
 
   <body>
     <p>Cyrillic type test</p>
     <p>Братан, ты вообще качаешься?</p>
 
     <script type="text/javascript">
-      function get(aAddress, aCallback) {
-        var xhr = new XMLHttpRequest();
-        xhr.open("GET", aAddress, true);
+      /* exported performRequests */
+      "use strict";
 
-        xhr.onreadystatechange = function() {
+      function get(address, callback) {
+        let xhr = new XMLHttpRequest();
+        xhr.open("GET", address, true);
+
+        xhr.onreadystatechange = function () {
           if (this.readyState == this.DONE) {
-            aCallback();
+            callback();
           }
         };
         xhr.send(null);
       }
 
       function performRequests() {
-        get("sjs_content-type-test-server.sjs?fmt=txt", function() {
+        get("sjs_content-type-test-server.sjs?fmt=txt", function () {
           // Done.
         });
       }
     </script>
   </body>
 
 </html>
--- a/devtools/client/netmonitor/test/html_filter-test-page.html
+++ b/devtools/client/netmonitor/test/html_filter-test-page.html
@@ -10,51 +10,59 @@
     <meta http-equiv="Expires" content="0" />
     <title>Network Monitor test page</title>
   </head>
 
   <body>
     <p>Filtering test</p>
 
     <script type="text/javascript">
-      function get(aAddress, aCallback) {
-        var xhr = new XMLHttpRequest();
+      /* exported performRequests */
+      "use strict";
+
+      function get(address, callback) {
+        let xhr = new XMLHttpRequest();
         // Use a random parameter to defeat caching.
-        xhr.open("GET", aAddress + "&" + Math.random(), true);
+        xhr.open("GET", address + "&" + Math.random(), true);
 
-        xhr.onreadystatechange = function() {
+        xhr.onreadystatechange = function () {
           if (this.readyState == this.DONE) {
-            aCallback();
+            callback();
           }
         };
         xhr.send(null);
       }
 
-      function performRequests(aOptions) {
-        var options = JSON.parse(aOptions);
-        get("sjs_content-type-test-server.sjs?fmt=html&res=" + options.htmlContent, function() {
-          get("sjs_content-type-test-server.sjs?fmt=css", function() {
-            get("sjs_content-type-test-server.sjs?fmt=js", function() {
-              if (!options.getMedia) {
-                return;
-              }
-              get("sjs_content-type-test-server.sjs?fmt=font", function() {
-                get("sjs_content-type-test-server.sjs?fmt=image", function() {
-                  get("sjs_content-type-test-server.sjs?fmt=audio", function() {
-                    get("sjs_content-type-test-server.sjs?fmt=video", function() {
-                      if (!options.getFlash) {
-                        return;
-                      }
-                      get("sjs_content-type-test-server.sjs?fmt=flash", function() {
-                        // Done.
+      function performRequests(optionsText) {
+        const options = JSON.parse(optionsText);
+
+        /* eslint-disable max-nested-callbacks */
+        get("sjs_content-type-test-server.sjs?fmt=html&res=" + options.htmlContent,
+            function () {
+              get("sjs_content-type-test-server.sjs?fmt=css", function () {
+                get("sjs_content-type-test-server.sjs?fmt=js", function () {
+                  if (!options.getMedia) {
+                    return;
+                  }
+                  get("sjs_content-type-test-server.sjs?fmt=font", function () {
+                    get("sjs_content-type-test-server.sjs?fmt=image", function () {
+                      get("sjs_content-type-test-server.sjs?fmt=audio", function () {
+                        get("sjs_content-type-test-server.sjs?fmt=video", function () {
+                          if (!options.getFlash) {
+                            return;
+                          }
+                          get("sjs_content-type-test-server.sjs?fmt=flash", function () {
+                            // Done.
+                          });
+                        });
                       });
                     });
                   });
                 });
               });
-            });
-          });
-        });
+            }
+        );
+        /* eslint-enable max-nested-callbacks */
       }
     </script>
   </body>
 
 </html>
--- a/devtools/client/netmonitor/test/html_frame-subdocument.html
+++ b/devtools/client/netmonitor/test/html_frame-subdocument.html
@@ -11,18 +11,20 @@
     <title>Network Monitor test page</title>
     <link rel="stylesheet" type="text/css" href="stylesheet_request" />
   </head>
 
   <body>
     <p>Request frame test</p>
     <img src="img_request" />
     <script type="text/javascript">
+      "use strict";
+
       function performXhrRequest() {
-        var xhr = new XMLHttpRequest();
+        let xhr = new XMLHttpRequest();
         xhr.open("GET", "xhr_request", true);
         xhr.send();
       }
 
       function performFetchRequest() {
         fetch("fetch_request");
       }
 
--- a/devtools/client/netmonitor/test/html_frame-test-page.html
+++ b/devtools/client/netmonitor/test/html_frame-test-page.html
@@ -12,18 +12,20 @@
     <link rel="stylesheet" type="text/css" href="stylesheet_request" />
   </head>
 
   <body>
     <p>Request frame test</p>
     <img src="img_request" />
     <iframe src="html_frame-subdocument.html"></iframe>
     <script type="text/javascript">
+      "use strict";
+
       function performXhrRequest() {
-        var xhr = new XMLHttpRequest();
+        let xhr = new XMLHttpRequest();
         xhr.open("GET", "xhr_request", true);
         xhr.send();
       }
 
       function performFetchRequest() {
         fetch("fetch_request");
       }
 
--- a/devtools/client/netmonitor/test/html_image-tooltip-test-page.html
+++ b/devtools/client/netmonitor/test/html_image-tooltip-test-page.html
@@ -10,17 +10,20 @@
     <meta http-equiv="Expires" content="0" />
     <title>Network Monitor test page</title>
   </head>
 
   <body>
     <p>tooltip test</p>
 
     <script type="text/javascript">
+      /* exported performRequests */
+      "use strict";
+
       function performRequests() {
-        var xhr = new XMLHttpRequest();
+        let xhr = new XMLHttpRequest();
         xhr.open("GET", "test-image.png?v=" + Math.random(), true);
         xhr.send(null);
       }
     </script>
   </body>
 
 </html>
--- a/devtools/client/netmonitor/test/html_infinite-get-page.html
+++ b/devtools/client/netmonitor/test/html_infinite-get-page.html
@@ -10,32 +10,34 @@
     <meta http-equiv="Expires" content="0" />
     <title>Network Monitor test page</title>
   </head>
 
   <body>
     <p>Infinite GETs</p>
 
     <script type="text/javascript">
-      function get(aAddress, aCallback) {
-        var xhr = new XMLHttpRequest();
-        xhr.open("GET", aAddress, true);
+      "use strict";
 
-        xhr.onreadystatechange = function() {
+      function get(address, callback) {
+        let xhr = new XMLHttpRequest();
+        xhr.open("GET", address, true);
+
+        xhr.onreadystatechange = function () {
           if (this.readyState == this.DONE) {
-            aCallback();
+            callback();
           }
         };
         xhr.send(null);
       }
 
       // Use a count parameter to defeat caching.
-      var count = 0;
+      let count = 0;
 
       (function performRequests() {
-        get("request_" + (count++), function() {
+        get("request_" + (count++), function () {
           setTimeout(performRequests, 50);
         });
       })();
     </script>
   </body>
 
 </html>
--- a/devtools/client/netmonitor/test/html_json-b64.html
+++ b/devtools/client/netmonitor/test/html_json-b64.html
@@ -10,29 +10,32 @@
     <meta http-equiv="Expires" content="0" />
     <title>Network Monitor test page</title>
   </head>
 
   <body>
     <p>JSON b64 test</p>
 
     <script type="text/javascript">
-      function get(aAddress, aCallback) {
-        var xhr = new XMLHttpRequest();
-        xhr.open("GET", aAddress, true);
+      /* exported performRequests */
+      "use strict";
 
-        xhr.onreadystatechange = function() {
+      function get(address, callback) {
+        let xhr = new XMLHttpRequest();
+        xhr.open("GET", address, true);
+
+        xhr.onreadystatechange = function () {
           if (this.readyState == this.DONE) {
-            aCallback();
+            callback();
           }
         };
         xhr.send(null);
       }
 
       function performRequests() {
-        get("sjs_content-type-test-server.sjs?fmt=json-b64", function() {
+        get("sjs_content-type-test-server.sjs?fmt=json-b64", function () {
           // Done.
         });
       }
     </script>
   </body>
 
 </html>
--- a/devtools/client/netmonitor/test/html_json-basic.html
+++ b/devtools/client/netmonitor/test/html_json-basic.html
@@ -11,30 +11,33 @@
     <title>Network Monitor test page</title>
   </head>
 
   <body>
     <p>JSON request test page</p>
     <p>Pass the JSON name (as supported by sjs_json-test-server.sjs) as a query parameter</p>
 
     <script type="text/javascript">
-      function get(aAddress, aCallback) {
-        var xhr = new XMLHttpRequest();
-        xhr.open("GET", aAddress, true);
+      /* exported performRequests */
+      "use strict";
 
-        xhr.onreadystatechange = function() {
+      function get(address, callback) {
+        let xhr = new XMLHttpRequest();
+        xhr.open("GET", address, true);
+
+        xhr.onreadystatechange = function () {
           if (this.readyState == this.DONE) {
-            aCallback();
+            callback();
           }
         };
         xhr.send(null);
       }
 
       function performRequests() {
         // Forward the query parameter for this page to sjs_json-test-server
-        get("sjs_json-test-server.sjs" + window.location.search, function() {
+        get("sjs_json-test-server.sjs" + window.location.search, function () {
           // Done.
         });
       }
     </script>
   </body>
 
 </html>
--- a/devtools/client/netmonitor/test/html_json-custom-mime-test-page.html
+++ b/devtools/client/netmonitor/test/html_json-custom-mime-test-page.html
@@ -10,29 +10,32 @@
     <meta http-equiv="Expires" content="0" />
     <title>Network Monitor test page</title>
   </head>
 
   <body>
     <p>JSONP test</p>
 
     <script type="text/javascript">
-      function get(aAddress, aCallback) {
-        var xhr = new XMLHttpRequest();
-        xhr.open("GET", aAddress, true);
+      /* exported performRequests */
+      "use strict";
 
-        xhr.onreadystatechange = function() {
+      function get(address, callback) {
+        let xhr = new XMLHttpRequest();
+        xhr.open("GET", address, true);
+
+        xhr.onreadystatechange = function () {
           if (this.readyState == this.DONE) {
-            aCallback();
+            callback();
           }
         };
         xhr.send(null);
       }
 
       function performRequests() {
-        get("sjs_content-type-test-server.sjs?fmt=json-custom-mime", function() {
+        get("sjs_content-type-test-server.sjs?fmt=json-custom-mime", function () {
           // Done.
         });
       }
     </script>
   </body>
 
 </html>
--- a/devtools/client/netmonitor/test/html_json-long-test-page.html
+++ b/devtools/client/netmonitor/test/html_json-long-test-page.html
@@ -10,29 +10,32 @@
     <meta http-equiv="Expires" content="0" />
     <title>Network Monitor test page</title>
   </head>
 
   <body>
     <p>JSON long string test</p>
 
     <script type="text/javascript">
-      function get(aAddress, aCallback) {
-        var xhr = new XMLHttpRequest();
-        xhr.open("GET", aAddress, true);
+      /* exported performRequests */
+      "use strict";
 
-        xhr.onreadystatechange = function() {
+      function get(address, callback) {
+        let xhr = new XMLHttpRequest();
+        xhr.open("GET", address, true);
+
+        xhr.onreadystatechange = function () {
           if (this.readyState == this.DONE) {
-            aCallback();
+            callback();
           }
         };
         xhr.send(null);
       }
 
       function performRequests() {
-        get("sjs_content-type-test-server.sjs?fmt=json-long", function() {
+        get("sjs_content-type-test-server.sjs?fmt=json-long", function () {
           // Done.
         });
       }
     </script>
   </body>
 
 </html>
--- a/devtools/client/netmonitor/test/html_json-malformed-test-page.html
+++ b/devtools/client/netmonitor/test/html_json-malformed-test-page.html
@@ -10,29 +10,32 @@
     <meta http-equiv="Expires" content="0" />
     <title>Network Monitor test page</title>
   </head>
 
   <body>
     <p>JSON malformed test</p>
 
     <script type="text/javascript">
-      function get(aAddress, aCallback) {
-        var xhr = new XMLHttpRequest();
-        xhr.open("GET", aAddress, true);
+      /* exported performRequests */
+      "use strict";
 
-        xhr.onreadystatechange = function() {
+      function get(address, callback) {
+        let xhr = new XMLHttpRequest();
+        xhr.open("GET", address, true);
+
+        xhr.onreadystatechange = function () {
           if (this.readyState == this.DONE) {
-            aCallback();
+            callback();
           }
         };
         xhr.send(null);
       }
 
       function performRequests() {
-        get("sjs_content-type-test-server.sjs?fmt=json-malformed", function() {
+        get("sjs_content-type-test-server.sjs?fmt=json-malformed", function () {
           // Done.
         });
       }
     </script>
   </body>
 
 </html>
--- a/devtools/client/netmonitor/test/html_json-text-mime-test-page.html
+++ b/devtools/client/netmonitor/test/html_json-text-mime-test-page.html
@@ -10,29 +10,32 @@
     <meta http-equiv="Expires" content="0" />
     <title>Network Monitor test page</title>
   </head>
 
   <body>
     <p>JSON text test</p>
 
     <script type="text/javascript">
-      function get(aAddress, aCallback) {
-        var xhr = new XMLHttpRequest();
-        xhr.open("GET", aAddress, true);
+      /* exported performRequests */
+      "use strict";
 
-        xhr.onreadystatechange = function() {
+      function get(address, callback) {
+        let xhr = new XMLHttpRequest();
+        xhr.open("GET", address, true);
+
+        xhr.onreadystatechange = function () {
           if (this.readyState == this.DONE) {
-            aCallback();
+            callback();
           }
         };
         xhr.send(null);
       }
 
       function performRequests() {
-        get("sjs_content-type-test-server.sjs?fmt=json-text-mime", function() {
+        get("sjs_content-type-test-server.sjs?fmt=json-text-mime", function () {
           // Done.
         });
       }
     </script>
   </body>
 
 </html>
--- a/devtools/client/netmonitor/test/html_jsonp-test-page.html
+++ b/devtools/client/netmonitor/test/html_jsonp-test-page.html
@@ -10,31 +10,34 @@
     <meta http-equiv="Expires" content="0" />
     <title>Network Monitor test page</title>
   </head>
 
   <body>
     <p>JSONP test</p>
 
     <script type="text/javascript">
-      function get(aAddress, aCallback) {
-        var xhr = new XMLHttpRequest();
-        xhr.open("GET", aAddress, true);
+      /* exported performRequests */
+      "use strict";
 
-        xhr.onreadystatechange = function() {
+      function get(address, callback) {
+        let xhr = new XMLHttpRequest();
+        xhr.open("GET", address, true);
+
+        xhr.onreadystatechange = function () {
           if (this.readyState == this.DONE) {
-            aCallback();
+            callback();
           }
         };
         xhr.send(null);
       }
 
       function performRequests() {
-        get("sjs_content-type-test-server.sjs?fmt=jsonp&jsonp=$_0123Fun", function() {
-          get("sjs_content-type-test-server.sjs?fmt=jsonp2&jsonp=$_4567Sad", function() {
+        get("sjs_content-type-test-server.sjs?fmt=jsonp&jsonp=$_0123Fun", function () {
+          get("sjs_content-type-test-server.sjs?fmt=jsonp2&jsonp=$_4567Sad", function () {
             // Done.
           });
         });
       }
     </script>
   </body>
 
 </html>
--- a/devtools/client/netmonitor/test/html_params-test-page.html
+++ b/devtools/client/netmonitor/test/html_params-test-page.html
@@ -10,58 +10,63 @@
     <meta http-equiv="Expires" content="0" />
     <title>Network Monitor test page</title>
   </head>
 
   <body>
     <p>Request params type test</p>
 
     <script type="text/javascript">
-      function get(aAddress, aQuery) {
-        var xhr = new XMLHttpRequest();
-        xhr.open("GET", aAddress + aQuery, true);
+      /* exported performRequests */
+      "use strict";
+
+      function get(address, query) {
+        let xhr = new XMLHttpRequest();
+        xhr.open("GET", address + query, true);
         xhr.send();
       }
 
-      function post(aAddress, aQuery, aContentType, aPostBody) {
-        var xhr = new XMLHttpRequest();
-        xhr.open("POST", aAddress + aQuery, true);
-        xhr.setRequestHeader("content-type", aContentType);
-        xhr.send(aPostBody);
+      function post(address, query, contentType, postBody) {
+        let xhr = new XMLHttpRequest();
+        xhr.open("POST", address + query, true);
+        xhr.setRequestHeader("content-type", contentType);
+        xhr.send(postBody);
       }
 
       function performRequests() {
-        var urlencoded = "application/x-www-form-urlencoded";
+        const urlencoded = "application/x-www-form-urlencoded";
 
-        setTimeout(function() {
+        /* eslint-disable max-nested-callbacks */
+        setTimeout(function () {
           post("baz", "?a", urlencoded, '{ "foo": "bar" }');
 
-          setTimeout(function() {
+          setTimeout(function () {
             post("baz", "?a=b", urlencoded, '{ "foo": "bar" }');
 
-            setTimeout(function() {
-              post("baz", "?a=b", urlencoded, '?foo=bar');
+            setTimeout(function () {
+              post("baz", "?a=b", urlencoded, "?foo=bar");
 
-              setTimeout(function() {
+              setTimeout(function () {
                 post("baz", "?a", undefined, '{ "foo": "bar" }');
 
-                setTimeout(function() {
+                setTimeout(function () {
                   post("baz", "?a=b", undefined, '{ "foo": "bar" }');
 
-                  setTimeout(function() {
-                    post("baz", "?a=b", undefined, '?foo=bar');
+                  setTimeout(function () {
+                    post("baz", "?a=b", undefined, "?foo=bar");
 
-                    setTimeout(function() {
+                    setTimeout(function () {
                       get("baz", "");
 
                       // Done.
                     }, 10);
                   }, 10);
                 }, 10);
               }, 10);
             }, 10);
           }, 10);
         }, 10);
+        /* eslint-enable max-nested-callbacks */
       }
     </script>
   </body>
 
 </html>
--- a/devtools/client/netmonitor/test/html_post-data-test-page.html
+++ b/devtools/client/netmonitor/test/html_post-data-test-page.html
@@ -22,56 +22,59 @@
     <form enctype="multipart/form-data" method="post" name="form-name">
       <input type="text" name="text" placeholder="text" value="Some text..."/>
       <input type="email" name="email" placeholder="email"/>
       <input type="range" name="range" value="42"/>
       <input type="button" value="Post me!" onclick="window.form()">
     </form>
 
     <script type="text/javascript">
-      function post(aAddress, aMessage, aCallback) {
-        var xhr = new XMLHttpRequest();
-        xhr.open("POST", aAddress, true);
+      /* exported performRequests */
+      "use strict";
+
+      function post(address, message, callback) {
+        let xhr = new XMLHttpRequest();
+        xhr.open("POST", address, true);
         xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
 
-        var data = "";
-        for (var i in aMessage) {
-          data += "&" + i + "=" + aMessage[i];
+        let data = "";
+        for (let i in message) {
+          data += "&" + i + "=" + message[i];
         }
 
-        xhr.onreadystatechange = function() {
+        xhr.onreadystatechange = function () {
           if (this.readyState == this.DONE) {
-            aCallback();
+            callback();
           }
         };
         xhr.send(data);
       }
 
-      function form(aAddress, aForm, aCallback) {
-        var formData = new FormData(document.forms.namedItem(aForm));
+      function form(address, formName, callback) {
+        let formData = new FormData(document.forms.namedItem(formName));
         formData.append("Custom field", "Extra data");
 
-        var xhr = new XMLHttpRequest();
-        xhr.open("POST", aAddress, true);
+        let xhr = new XMLHttpRequest();
+        xhr.open("POST", address, true);
 
-        xhr.onreadystatechange = function() {
+        xhr.onreadystatechange = function () {
           if (this.readyState == this.DONE) {
-            aCallback();
+            callback();
           }
         };
         xhr.send(formData);
       }
 
       function performRequests() {
-        var url = "sjs_simple-test-server.sjs";
-        var url1 = url + "?foo=bar&baz=42&type=urlencoded";
-        var url2 = url + "?foo=bar&baz=42&type=multipart";
+        const url = "sjs_simple-test-server.sjs";
+        const url1 = url + "?foo=bar&baz=42&type=urlencoded";
+        const url2 = url + "?foo=bar&baz=42&type=multipart";
 
-        post(url1, { foo: "bar", baz: 123 }, function() {
-          form(url2, "form-name", function() {
+        post(url1, { foo: "bar", baz: 123 }, function () {
+          form(url2, "form-name", function () {
             // Done.
           });
         });
       }
     </script>
   </body>
 
 </html>
--- a/devtools/client/netmonitor/test/html_post-json-test-page.html
+++ b/devtools/client/netmonitor/test/html_post-json-test-page.html
@@ -10,16 +10,19 @@
     <meta http-equiv="Expires" content="0" />
     <title>Network Monitor test page</title>
   </head>
 
   <body>
     <p>POST raw test</p>
 
     <script type="text/javascript">
+      /* exported performRequests */
+      "use strict";
+
       function post(address, message, callback) {
         let xhr = new XMLHttpRequest();
         xhr.open("POST", address, true);
         xhr.setRequestHeader("Content-Type", "application/json");
 
         xhr.onreadystatechange = function () {
           if (this.readyState == this.DONE) {
             callback();
--- a/devtools/client/netmonitor/test/html_post-raw-test-page.html
+++ b/devtools/client/netmonitor/test/html_post-raw-test-page.html
@@ -10,31 +10,34 @@
     <meta http-equiv="Expires" content="0" />
     <title>Network Monitor test page</title>
   </head>
 
   <body>
     <p>POST raw test</p>
 
     <script type="text/javascript">
-      function post(aAddress, aMessage, aCallback) {
-        var xhr = new XMLHttpRequest();
-        xhr.open("POST", aAddress, true);
+      /* exported performRequests */
+      "use strict";
+
+      function post(address, message, callback) {
+        let xhr = new XMLHttpRequest();
+        xhr.open("POST", address, true);
         xhr.setRequestHeader("content-type", "application/x-www-form-urlencoded");
 
-        xhr.onreadystatechange = function() {
+        xhr.onreadystatechange = function () {
           if (this.readyState == this.DONE) {
-            aCallback();
+            callback();
           }
         };
-        xhr.send(aMessage);
+        xhr.send(message);
       }
 
       function performRequests() {
-        var rawData = "foo=bar&baz=123";
-        post("sjs_simple-test-server.sjs", rawData, function() {
+        const rawData = "foo=bar&baz=123";
+        post("sjs_simple-test-server.sjs", rawData, function () {
           // Done.
         });
       }
     </script>
   </body>
 
 </html>
--- a/devtools/client/netmonitor/test/html_post-raw-with-headers-test-page.html
+++ b/devtools/client/netmonitor/test/html_post-raw-with-headers-test-page.html
@@ -10,36 +10,39 @@
     <meta http-equiv="Expires" content="0" />
     <title>Network Monitor test page</title>
   </head>
 
   <body>
     <p>POST raw with headers test</p>
 
     <script type="text/javascript">
-      function post(aAddress, aMessage, aCallback) {
-        var xhr = new XMLHttpRequest();
-        xhr.open("POST", aAddress, true);
+      /* exported performRequests */
+      "use strict";
 
-        xhr.onreadystatechange = function() {
+      function post(address, message, callback) {
+        let xhr = new XMLHttpRequest();
+        xhr.open("POST", address, true);
+
+        xhr.onreadystatechange = function () {
           if (this.readyState == this.DONE) {
-            aCallback();
+            callback();
           }
         };
-        xhr.send(aMessage);
+        xhr.send(message);
       }
 
       function performRequests() {
-        var rawData = [
+        let rawData = [
           "content-type: application/x-www-form-urlencoded\r",
           "custom-header: hello world!\r",
           "\r",
           "\r",
           "foo=bar&baz=123"
         ];
-        post("sjs_simple-test-server.sjs", rawData.join("\n"), function() {
+        post("sjs_simple-test-server.sjs", rawData.join("\n"), function () {
           // Done.
         });
       }
     </script>
   </body>
 
 </html>
--- a/devtools/client/netmonitor/test/html_send-beacon.html
+++ b/devtools/client/netmonitor/test/html_send-beacon.html
@@ -10,14 +10,17 @@
     <meta http-equiv="Expires" content="0" />
     <title>Network Monitor test page</title>
   </head>
 
   <body>
     <p>Send beacon test</p>
 
     <script type="text/javascript">
+    /* exported performRequest */
+    "use strict";
+
     function performRequest() {
       navigator.sendBeacon("beacon_request");
     }
     </script>
   </body>
 </html>
--- a/devtools/client/netmonitor/test/html_single-get-page.html
+++ b/devtools/client/netmonitor/test/html_single-get-page.html
@@ -10,27 +10,29 @@
     <meta http-equiv="Expires" content="0" />
     <title>Network Monitor test page</title>
   </head>
 
   <body>
     <p>Performing a custom number of GETs</p>
 
     <script type="text/javascript">
-      function get(aAddress, aCallback) {
-        var xhr = new XMLHttpRequest();
-        xhr.open("GET", aAddress, true);
+      "use strict";
 
-        xhr.onreadystatechange = function() {
+      function get(address, callback) {
+        let xhr = new XMLHttpRequest();
+        xhr.open("GET", address, true);
+
+        xhr.onreadystatechange = function () {
           if (this.readyState == this.DONE) {
-            aCallback();
+            callback();
           }
         };
         xhr.send(null);
       }
 
       (function performRequests() {
-        get("request_0", function() {});
+        get("request_0", function () {});
       })();
     </script>
   </body>
 
 </html>
--- a/devtools/client/netmonitor/test/html_statistics-test-page.html
+++ b/devtools/client/netmonitor/test/html_statistics-test-page.html
@@ -10,19 +10,21 @@
     <meta http-equiv="Expires" content="0" />
     <title>Network Monitor test page</title>
   </head>
 
   <body>
     <p>Statistics test</p>
 
     <script type="text/javascript">
-      function get(aAddress) {
-        var xhr = new XMLHttpRequest();
-        xhr.open("GET", aAddress, true);
+      "use strict";
+
+      function get(address) {
+        let xhr = new XMLHttpRequest();
+        xhr.open("GET", address, true);
         xhr.send(null);
       }
 
       get("sjs_content-type-test-server.sjs?sts=304&fmt=txt");
       get("sjs_content-type-test-server.sjs?sts=304&fmt=xml");
       get("sjs_content-type-test-server.sjs?sts=304&fmt=html");
       get("sjs_content-type-test-server.sjs?sts=304&fmt=css");
       get("sjs_content-type-test-server.sjs?sts=304&fmt=js");
--- a/devtools/client/netmonitor/test/html_status-codes-test-page.html
+++ b/devtools/client/netmonitor/test/html_status-codes-test-page.html
@@ -10,45 +10,50 @@
     <meta http-equiv="Expires" content="0" />
     <title>Network Monitor test page</title>
   </head>
 
   <body>
     <p>Status codes test</p>
 
     <script type="text/javascript">
-      function get(aAddress, aCallback) {
-        var xhr = new XMLHttpRequest();
-        xhr.open("GET", aAddress, true);
+      /* exported performRequests, performCachedRequests */
+      "use strict";
 
-        xhr.onreadystatechange = function() {
+      function get(address, callback) {
+        let xhr = new XMLHttpRequest();
+        xhr.open("GET", address, true);
+
+        xhr.onreadystatechange = function () {
           if (this.readyState == this.DONE) {
-            aCallback();
+            callback();
           }
         };
         xhr.send(null);
       }
 
       function performRequests() {
-        get("sjs_status-codes-test-server.sjs?sts=100", function() {
-          get("sjs_status-codes-test-server.sjs?sts=200", function() {
-            get("sjs_status-codes-test-server.sjs?sts=300", function() {
-              get("sjs_status-codes-test-server.sjs?sts=400", function() {
-                get("sjs_status-codes-test-server.sjs?sts=500", function() {
+        /* eslint-disable max-nested-callbacks */
+        get("sjs_status-codes-test-server.sjs?sts=100", function () {
+          get("sjs_status-codes-test-server.sjs?sts=200", function () {
+            get("sjs_status-codes-test-server.sjs?sts=300", function () {
+              get("sjs_status-codes-test-server.sjs?sts=400", function () {
+                get("sjs_status-codes-test-server.sjs?sts=500", function () {
                   // Done.
                 });
               });
             });
           });
         });
+        /* eslint-enable max-nested-callbacks */
       }
 
       function performCachedRequests() {
-        get("sjs_status-codes-test-server.sjs?sts=ok&cached", function() {
-          get("sjs_status-codes-test-server.sjs?sts=redirect&cached", function() {
+        get("sjs_status-codes-test-server.sjs?sts=ok&cached", function () {
+          get("sjs_status-codes-test-server.sjs?sts=redirect&cached", function () {
             // Done.
           });
         });
       }
 
     </script>
   </body>
 
--- a/devtools/client/netmonitor/test/require-helper.js
+++ b/devtools/client/netmonitor/test/require-helper.js
@@ -26,9 +26,11 @@ requireHacker.global_hook("default", pat
   // Some modules depend on Chrome APIs which don't work in mocha. When such a module
   // is required, replace it with a mock version.
   switch (path) {
     case "devtools/shared/l10n":
       return `module.exports = require("devtools/client/netmonitor/test/fixtures/localization-helper")`;
     case "devtools/client/shared/redux/create-store":
       return `module.exports = require("devtools/client/netmonitor/test/fixtures/create-store")`;
   }
+
+  return null;
 });
--- a/devtools/client/netmonitor/test/service-workers/status-codes.html
+++ b/devtools/client/netmonitor/test/service-workers/status-codes.html
@@ -11,32 +11,35 @@
     <meta http-equiv="Expires" content="0" />
     <title>Network Monitor test page</title>
   </head>
 
   <body>
     <p>Status codes test</p>
 
     <script type="text/javascript">
+      /* exported registerServiceWorker, unregisterServiceWorker, performRequests */
+      "use strict";
+
       let swRegistration;
 
       function registerServiceWorker() {
         let sw = navigator.serviceWorker;
         return sw.register("status-codes-service-worker.js")
           .then(registration => {
             swRegistration = registration;
             console.log("Registered, scope is:", registration.scope);
             return sw.ready;
           }).then(() => {
             // wait until the page is controlled
             return new Promise(resolve => {
               if (sw.controller) {
                 resolve();
               } else {
-                sw.addEventListener('controllerchange', function () {
+                sw.addEventListener("controllerchange", function () {
                   resolve();
                 }, {once: true});
               }
             });
           }).catch(err => {
             console.error("Registration failed");
           });
       }
--- a/devtools/client/netmonitor/utils/mdn-utils.js
+++ b/devtools/client/netmonitor/utils/mdn-utils.js
@@ -71,25 +71,66 @@ const SUPPORTED_HEADERS = [
   "Warning",
   "X-Content-Type-Options",
   "X-DNS-Prefetch-Control",
   "X-Frame-Options",
   "X-XSS-Protection"
 ];
 
 /**
+ * A mapping of HTTP status codes to external documentation. Any code included
+ * here will show a MDN link alongside it.
+ */
+const SUPPORTED_HTTP_CODES = [
+    "100",
+    "200",
+    "201",
+    "204",
+    "206",
+    "301",
+    "302",
+    "303",
+    "304",
+    "307",
+    "308",
+    "404",
+    "406",
+    "410",
+    "412",
+    "451",
+    "500",
+    "501",
+    "502",
+    "503",
+    "504"
+];
+
+/**
  * Get the MDN URL for the specified header.
  *
  * @param {string} header Name of the header for the baseURL to use.
  *
  * @return {string} The MDN URL for the header, or null if not available.
  */
 function getHeadersURL(header) {
   const lowerCaseHeader = header.toLowerCase();
   let idx = SUPPORTED_HEADERS.findIndex(item =>
     item.toLowerCase() === lowerCaseHeader);
   return idx > -1 ?
     `https://developer.mozilla.org/docs/Web/HTTP/Headers/${SUPPORTED_HEADERS[idx]}?utm_source=mozilla&utm_medium=devtools-netmonitor&utm_campaign=default` : null;
 }
 
+/**
+ * Get the MDN URL for the specified HTTP status code.
+ *
+ * @param {string} HTTP status code for the baseURL to use.
+ *
+ * @return {string} The MDN URL for the HTTP status code, or null if not available.
+ */
+function getHTTPStatusCodeURL(statusCode) {
+  let idx = SUPPORTED_HTTP_CODES.indexOf(statusCode);
+  return idx > -1 ? `https://developer.mozilla.org/docs/Web/HTTP/Status/${SUPPORTED_HTTP_CODES[idx]}` : null;
+}
+
 module.exports = {
   getHeadersURL,
+  getHTTPStatusCodeURL,
 };
--- a/devtools/client/themes/markup.css
+++ b/devtools/client/themes/markup.css
@@ -272,26 +272,34 @@ ul.children + .tag-line::before {
 
 /* Whitespace only text nodes are sometimes shown in the markup-view, and when they do
    they get a greyed-out whitespace symbol so users know what they are */
 .editor.text .whitespace {
   padding: 0 .5em;
 }
 
 .editor.text .whitespace::before {
-  content: "";
+  /* black circle (full) character */
+  content: "\25cf";
   display: inline-block;
-  height: 4px;
-  width: 4px;
-  border: 1px solid var(--theme-body-color-inactive);
-  border-radius: 50%;
+  width: 12px;
+  height: 8px;
+  margin: 0 2px;
+  line-height: 7px;
+  font-size: 0.6em;
+  color: var(--theme-body-color-inactive);
+  border-radius: 3px;
+  border-style: solid;
+  border-width: 1px;
+  text-align: center;
+  vertical-align: middle;
 }
 
 .tag-line[selected] .editor.text .whitespace::before {
-  border-color: white;
+  color: white;
 }
 
 .more-nodes {
   padding-left: 16px;
 }
 
 .styleinspector-propertyeditor {
   border: 1px solid #CCC;
--- a/devtools/client/themes/netmonitor.css
+++ b/devtools/client/themes/netmonitor.css
@@ -665,16 +665,32 @@
 }
 
 /* Headers tabpanel */
 
 .headers-overview {
   background: var(--theme-toolbar-background);
 }
 
+.headers-summary .status-text {
+    width: auto!important;
+}
+
+.headers-summary .learn-more-link {
+  color: var(--theme-highlight-blue);
+  cursor: pointer;
+  margin: 0 5px;
+  white-space: nowrap;
+  flex-grow: 1;
+}
+
+.headers-summary .learn-more-link:hover {
+  text-decoration: underline;
+}
+
 /* Response tabpanel */
 
 .response-error-header {
   margin: 0;
   padding: 3px 8px;
   background-color: var(--theme-highlight-red);
   color: var(--theme-selection-color);
 }
--- a/devtools/server/actors/highlighters/css-grid.js
+++ b/devtools/server/actors/highlighters/css-grid.js
@@ -236,16 +236,19 @@ CssGridHighlighter.prototype = extend(Au
     let { highlighterEnv } = this;
     highlighterEnv.off("navigate", this.onNavigate);
     highlighterEnv.off("will-navigate", this.onWillNavigate);
 
     let { pageListenerTarget } = highlighterEnv;
     pageListenerTarget.removeEventListener("pagehide", this.onPageHide);
 
     this.markup.destroy();
+
+    // Clear the pattern cache to avoid dead object exceptions (Bug 1342051).
+    this._clearCache();
     AutoRefreshHighlighter.prototype.destroy.call(this);
   },
 
   getElement(id) {
     return this.markup.getElement(this.ID_CLASS_PREFIX + id);
   },
 
   get ctx() {
--- a/dom/base/ScriptSettings.cpp
+++ b/dom/base/ScriptSettings.cpp
@@ -650,16 +650,19 @@ AutoJSAPI::IsStackTop() const
 }
 #endif // DEBUG
 
 AutoEntryScript::AutoEntryScript(nsIGlobalObject* aGlobalObject,
                                  const char *aReason,
                                  bool aIsMainThread)
   : AutoJSAPI(aGlobalObject, aIsMainThread, eEntryScript)
   , mWebIDLCallerPrincipal(nullptr)
+  // This relies on us having a cx() because the AutoJSAPI constructor already
+  // ran.
+  , mCallerOverride(cx())
 {
   MOZ_ASSERT(aGlobalObject);
 
   if (aIsMainThread && gRunToCompletionListeners > 0) {
     mDocShellEntryMonitor.emplace(cx(), aReason);
   }
 }
 
--- a/dom/base/ScriptSettings.h
+++ b/dom/base/ScriptSettings.h
@@ -374,16 +374,17 @@ private:
   // the aIsJSImplementedWebIDL case.  And in that case, the subject principal
   // is the principal of the callee function that is part of the CallArgs just a
   // bit up the stack, and which will outlive us.  So we know the principal
   // can't go away until then either.
   nsIPrincipal* MOZ_NON_OWNING_REF mWebIDLCallerPrincipal;
   friend nsIPrincipal* GetWebIDLCallerPrincipal();
 
   Maybe<DocshellEntryMonitor> mDocShellEntryMonitor;
+  JS::AutoHideScriptedCaller mCallerOverride;
 };
 
 /*
  * A class that can be used to force a particular incumbent script on the stack.
  */
 class AutoIncumbentScript : protected ScriptSettingsStackEntry {
 public:
   explicit AutoIncumbentScript(nsIGlobalObject* aGlobalObject);
--- a/dom/base/nsContentUtils.cpp
+++ b/dom/base/nsContentUtils.cpp
@@ -7064,33 +7064,40 @@ nsContentUtils::GetSelectionInTextContro
   MOZ_ASSERT(aSelection && aRoot);
 
   if (!aSelection->RangeCount()) {
     // Nothing selected
     aOutStartOffset = aOutEndOffset = 0;
     return;
   }
 
-  nsCOMPtr<nsINode> anchorNode = aSelection->GetAnchorNode();
+  // All the node pointers here are raw pointers for performance.  We shouldn't
+  // be doing anything in this function that invalidates the node tree.
+  nsINode* anchorNode = aSelection->GetAnchorNode();
   uint32_t anchorOffset = aSelection->AnchorOffset();
-  nsCOMPtr<nsINode> focusNode = aSelection->GetFocusNode();
+  nsINode* focusNode = aSelection->GetFocusNode();
   uint32_t focusOffset = aSelection->FocusOffset();
 
   // We have at most two children, consisting of an optional text node followed
   // by an optional <br>.
   NS_ASSERTION(aRoot->GetChildCount() <= 2, "Unexpected children");
-  nsCOMPtr<nsIContent> firstChild = aRoot->GetFirstChild();
+  nsIContent* firstChild = aRoot->GetFirstChild();
 #ifdef DEBUG
   nsCOMPtr<nsIContent> lastChild = aRoot->GetLastChild();
   NS_ASSERTION(anchorNode == aRoot || anchorNode == firstChild ||
                anchorNode == lastChild, "Unexpected anchorNode");
   NS_ASSERTION(focusNode == aRoot || focusNode == firstChild ||
                focusNode == lastChild, "Unexpected focusNode");
+  // firstChild is either text or a <br> (hence an element).
+  MOZ_ASSERT_IF(firstChild,
+                firstChild->IsNodeOfType(nsINode::eTEXT) || firstChild->IsElement());
 #endif
-  if (!firstChild || !firstChild->IsNodeOfType(nsINode::eTEXT)) {
+  // Testing IsElement() is faster than testing IsNodeOfType(), since it's
+  // non-virtual.
+  if (!firstChild || firstChild->IsElement()) {
     // No text node, so everything is 0
     anchorOffset = focusOffset = 0;
   } else {
     // First child is text.  If the anchor/focus is already in the text node,
     // or the start of the root node, no change needed.  If it's in the root
     // node but not the start, or in the trailing <br>, we need to set the
     // offset to the end.
     if ((anchorNode == aRoot && anchorOffset != 0) ||
--- a/dom/base/nsDocument.cpp
+++ b/dom/base/nsDocument.cpp
@@ -3249,16 +3249,31 @@ nsIDocument::HasFocus(ErrorResult& rv) c
       // Yes, we are an ancestor
       return true;
     }
   }
 
   return false;
 }
 
+TimeStamp
+nsIDocument::LastFocusTime() const
+{
+  return mLastFocusTime;
+}
+
+void
+nsIDocument::SetLastFocusTime(const TimeStamp& aFocusTime)
+{
+  MOZ_DIAGNOSTIC_ASSERT(!aFocusTime.IsNull());
+  MOZ_DIAGNOSTIC_ASSERT(mLastFocusTime.IsNull() ||
+                        aFocusTime >= mLastFocusTime);
+  mLastFocusTime = aFocusTime;
+}
+
 NS_IMETHODIMP
 nsDocument::GetReferrer(nsAString& aReferrer)
 {
   nsIDocument::GetReferrer(aReferrer);
   return NS_OK;
 }
 
 void
@@ -4748,16 +4763,24 @@ nsDocument::SetScriptGlobalObject(nsIScr
 #endif
         bool allowDNSPrefetch;
         docShell->GetAllowDNSPrefetch(&allowDNSPrefetch);
         mAllowDNSPrefetch = allowDNSPrefetch;
       }
     }
 
     MaybeRescheduleAnimationFrameNotifications();
+
+    // If we are set in a window that is already focused we should remember this
+    // as the time the document gained focus.
+    bool focused = false;
+    Unused << HasFocus(&focused);
+    if (focused) {
+      SetLastFocusTime(TimeStamp::Now());
+    }
   }
 
   // Remember the pointer to our window (or lack there of), to avoid
   // having to QI every time it's asked for.
   nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(mScriptGlobalObject);
   mWindow = window;
 
   // Now that we know what our window is, we can flush the CSP errors to the
--- a/dom/base/nsFocusManager.cpp
+++ b/dom/base/nsFocusManager.cpp
@@ -3606,16 +3606,27 @@ void
 nsFocusManager::SetFocusedWindowInternal(nsPIDOMWindowOuter* aWindow)
 {
   if (!PointerUnlocker::sActiveUnlocker &&
       nsContentUtils::IsInPointerLockContext(mFocusedWindow) &&
       !nsContentUtils::IsInPointerLockContext(aWindow)) {
     nsCOMPtr<nsIRunnable> runnable = new PointerUnlocker();
     NS_DispatchToCurrentThread(runnable);
   }
+
+  // Update the last focus time on any affected documents
+  if (aWindow && aWindow != mFocusedWindow) {
+    const TimeStamp now(TimeStamp::Now());
+    for (nsIDocument* doc = aWindow->GetExtantDoc();
+         doc;
+         doc = doc->GetParentDocument()) {
+      doc->SetLastFocusTime(now);
+    }
+  }
+
   mFocusedWindow = aWindow;
 }
 
 void
 nsFocusManager::MarkUncollectableForCCGeneration(uint32_t aGeneration)
 {
   if (!sInstance) {
     return;
--- a/dom/base/nsIDocument.h
+++ b/dom/base/nsIDocument.h
@@ -2651,16 +2651,18 @@ public:
   void GetDir(nsAString& aDirection) const;
   void SetDir(const nsAString& aDirection);
   nsPIDOMWindowOuter* GetDefaultView() const
   {
     return GetWindow();
   }
   Element* GetActiveElement();
   bool HasFocus(mozilla::ErrorResult& rv) const;
+  mozilla::TimeStamp LastFocusTime() const;
+  void SetLastFocusTime(const mozilla::TimeStamp& aFocusTime);
   // Event handlers are all on nsINode already
   bool MozSyntheticDocument() const
   {
     return IsSyntheticDocument();
   }
   Element* GetCurrentScript();
   void ReleaseCapture() const;
   virtual void MozSetImageElement(const nsAString& aImageElementId,
@@ -3020,16 +3022,20 @@ protected:
   nsTArray<nsAutoPtr<nsPropertyTable> > mExtraPropertyTables;
 
   // Our cached .children collection
   nsCOMPtr<nsIHTMLCollection> mChildrenCollection;
 
   // container for per-context fonts (downloadable, SVG, etc.)
   RefPtr<mozilla::dom::FontFaceSet> mFontFaceSet;
 
+  // Last time this document or a one of its sub-documents was focused.  If
+  // focus has never occurred then mLastFocusTime.IsNull() will be true.
+  mozilla::TimeStamp mLastFocusTime;
+
   // True if BIDI is enabled.
   bool mBidiEnabled : 1;
   // True if a MathML element has ever been owned by this document.
   bool mMathMLEnabled : 1;
 
   // True if this document is the initial document for a window.  This should
   // basically be true only for documents that exist in newly-opened windows or
   // documents created to satisfy a GetDocument() on a window when there's no
--- a/dom/base/nsScriptLoader.cpp
+++ b/dom/base/nsScriptLoader.cpp
@@ -732,17 +732,17 @@ nsScriptLoader::WaitForModuleFetch(nsMod
       promise = new GenericPromise::Private(__func__);
       mFetchingModules.Put(aRequest->mURI, promise);
     }
     return promise;
   }
 
   RefPtr<nsModuleScript> ms;
   MOZ_ALWAYS_TRUE(mFetchedModules.Get(aRequest->mURI, getter_AddRefs(ms)));
-  if (!ms) {
+  if (!ms || ms->InstantiationFailed()) {
     return GenericPromise::CreateAndReject(NS_ERROR_FAILURE, __func__);
   }
 
   return GenericPromise::CreateAndResolve(true, __func__);
 }
 
 nsModuleScript*
 nsScriptLoader::GetFetchedModule(nsIURI* aURL) const
@@ -994,16 +994,17 @@ ResolveRequestedModules(nsModuleLoadRequ
 
   return NS_OK;
 }
 
 void
 nsScriptLoader::StartFetchingModuleDependencies(nsModuleLoadRequest* aRequest)
 {
   MOZ_ASSERT(aRequest->mModuleScript);
+  MOZ_ASSERT(!aRequest->mModuleScript->InstantiationFailed());
   MOZ_ASSERT(!aRequest->IsReadyToRun());
   aRequest->mProgress = nsModuleLoadRequest::Progress::FetchingImports;
 
   nsCOMArray<nsIURI> urls;
   nsresult rv = ResolveRequestedModules(aRequest, urls);
   if (NS_FAILED(rv)) {
     aRequest->LoadFailed();
     return;
--- a/dom/canvas/CanvasRenderingContext2D.cpp
+++ b/dom/canvas/CanvasRenderingContext2D.cpp
@@ -52,17 +52,16 @@
 #include "gfxContext.h"
 #include "gfxPlatform.h"
 #include "gfxFont.h"
 #include "gfxBlur.h"
 #include "gfxPrefs.h"
 #include "gfxUtils.h"
 
 #include "nsFrameLoader.h"
-#include "nsBidi.h"
 #include "nsBidiPresUtils.h"
 #include "Layers.h"
 #include "LayerUserData.h"
 #include "CanvasUtils.h"
 #include "nsIMemoryReporter.h"
 #include "nsStyleUtil.h"
 #include "CanvasImageCache.h"
 
@@ -4271,27 +4270,26 @@ CanvasRenderingContext2D::DrawOrMeasureT
   processor.mDoMeasureBoundingBox = doCalculateBounds || !mIsEntireFrameInvalid;
   processor.mState = &CurrentState();
   processor.mFontgrp = currentFontStyle;
 
   nscoord totalWidthCoord;
 
   // calls bidi algo twice since it needs the full text width and the
   // bounding boxes before rendering anything
-  nsBidi bidiEngine;
   rv = nsBidiPresUtils::ProcessText(textToDraw.get(),
                                     textToDraw.Length(),
                                     isRTL ? NSBIDI_RTL : NSBIDI_LTR,
                                     presShell->GetPresContext(),
                                     processor,
                                     nsBidiPresUtils::MODE_MEASURE,
                                     nullptr,
                                     0,
                                     &totalWidthCoord,
-                                    &bidiEngine);
+                                    &mBidiEngine);
   if (NS_FAILED(rv)) {
     return rv;
   }
 
   float totalWidth = float(totalWidthCoord) / processor.mAppUnitsPerDevPixel;
   if (aWidth) {
     *aWidth = totalWidth;
   }
@@ -4396,17 +4394,17 @@ CanvasRenderingContext2D::DrawOrMeasureT
                                     textToDraw.Length(),
                                     isRTL ? NSBIDI_RTL : NSBIDI_LTR,
                                     presShell->GetPresContext(),
                                     processor,
                                     nsBidiPresUtils::MODE_DRAW,
                                     nullptr,
                                     0,
                                     nullptr,
-                                    &bidiEngine);
+                                    &mBidiEngine);
 
 
   mTarget->SetTransform(oldTransform);
 
   if (aOp == CanvasRenderingContext2D::TextDrawOperation::FILL &&
       !doCalculateBounds) {
     RedrawUser(boundingBox);
     return NS_OK;
--- a/dom/canvas/CanvasRenderingContext2D.h
+++ b/dom/canvas/CanvasRenderingContext2D.h
@@ -24,16 +24,17 @@
 #include "mozilla/UniquePtr.h"
 #include "gfx2DGlue.h"
 #include "imgIEncoder.h"
 #include "nsLayoutUtils.h"
 #include "mozilla/EnumeratedArray.h"
 #include "FilterSupport.h"
 #include "nsSVGEffects.h"
 #include "Layers.h"
+#include "nsBidi.h"
 
 class nsGlobalWindow;
 class nsXULElement;
 
 namespace mozilla {
 namespace gl {
 class SourceSurface;
 } // namespace gl
@@ -859,16 +860,18 @@ protected:
     // fallback element for a11y
     RefPtr<Element> mElement;
     // Path of the hit region in the 2d context coordinate space (not user space)
     RefPtr<gfx::Path> mPath;
   };
 
   nsTArray<RegionInfo> mHitRegionsOptions;
 
+  nsBidi mBidiEngine;
+
   /**
     * Returns true if a shadow should be drawn along with a
     * drawing operation.
     */
   bool NeedToDrawShadow()
   {
     const ContextState& state = CurrentState();
 
--- a/dom/html/HTMLInputElement.cpp
+++ b/dom/html/HTMLInputElement.cpp
@@ -2945,17 +2945,17 @@ HTMLInputElement::CreateEditor()
 {
   nsTextEditorState* state = GetEditorState();
   if (state) {
     return state->PrepareEditor();
   }
   return NS_ERROR_FAILURE;
 }
 
-NS_IMETHODIMP_(nsIContent*)
+NS_IMETHODIMP_(Element*)
 HTMLInputElement::GetRootEditorNode()
 {
   nsTextEditorState* state = GetEditorState();
   if (state) {
     return state->GetRootNode();
   }
   return nullptr;
 }
@@ -6602,27 +6602,40 @@ HTMLInputElement::SetSelectionEnd(int32_
 NS_IMETHODIMP
 HTMLInputElement::GetFiles(nsIDOMFileList** aFileList)
 {
   RefPtr<FileList> list = GetFiles();
   list.forget(aFileList);
   return NS_OK;
 }
 
-nsresult
+NS_IMETHODIMP
 HTMLInputElement::GetSelectionRange(int32_t* aSelectionStart,
                                     int32_t* aSelectionEnd)
 {
-  nsIFormControlFrame* formControlFrame = GetFormControlFrame(true);
-  nsITextControlFrame* textControlFrame = do_QueryFrame(formControlFrame);
-  if (textControlFrame) {
-    return textControlFrame->GetSelectionRange(aSelectionStart, aSelectionEnd);
-  }
-
-  return NS_ERROR_FAILURE;
+  // Flush frames, because our editor state will want to work with the frame.
+  if (IsInComposedDoc()) {
+    GetComposedDoc()->FlushPendingNotifications(FlushType::Frames);
+  }
+  if (!GetPrimaryFrame()) {
+    // Can we return a selection range anyway here, now that it lives on our
+    // state?  In fact, could we make this behave more like
+    // GetSelectionDirection, in the sense of working even when we have no
+    // frame, by just delegating entirely to mState?  And then, do we really
+    // need the flush?
+    return NS_ERROR_FAILURE;
+  }
+
+  nsTextEditorState* state = GetEditorState();
+  if (!state) {
+    // Not a text control.
+    return NS_ERROR_FAILURE;
+  }
+
+  return state->GetSelectionRange(aSelectionStart, aSelectionEnd);
 }
 
 static void
 DirectionToName(nsITextControlFrame::SelectionDirection dir, nsAString& aDirection)
 {
   if (dir == nsITextControlFrame::eNone) {
     aDirection.AssignLiteral("none");
   } else if (dir == nsITextControlFrame::eForward) {
@@ -6637,38 +6650,39 @@ DirectionToName(nsITextControlFrame::Sel
 void
 HTMLInputElement::GetSelectionDirection(nsAString& aDirection, ErrorResult& aRv)
 {
   if (!SupportsTextSelection()) {
     aDirection.SetIsVoid(true);
     return;
   }
 
+  nsIFormControlFrame* formControlFrame = GetFormControlFrame(true);
+  nsTextEditorState* state = GetEditorState();
+  if (!state) {
+    aRv.Throw(NS_ERROR_FAILURE);
+    return;
+  }
+
   nsresult rv = NS_ERROR_FAILURE;
-  nsIFormControlFrame* formControlFrame = GetFormControlFrame(true);
-  nsITextControlFrame* textControlFrame = do_QueryFrame(formControlFrame);
-  if (textControlFrame) {
+  if (formControlFrame) {
     nsITextControlFrame::SelectionDirection dir;
-    rv = textControlFrame->GetSelectionRange(nullptr, nullptr, &dir);
+    rv = state->GetSelectionDirection(&dir);
     if (NS_SUCCEEDED(rv)) {
       DirectionToName(dir, aDirection);
-    }
-  }
-
-  if (NS_FAILED(rv)) {
-    nsTextEditorState* state = GetEditorState();
-    if (state && state->IsSelectionCached()) {
-      DirectionToName(state->GetSelectionProperties().GetDirection(), aDirection);
       return;
     }
   }
-
-  if (NS_FAILED(rv)) {
-    aRv.Throw(rv);
-  }
+  
+  if (state->IsSelectionCached()) {
+    DirectionToName(state->GetSelectionProperties().GetDirection(), aDirection);
+    return;
+  }
+
+  aRv.Throw(rv);
 }
 
 NS_IMETHODIMP
 HTMLInputElement::GetSelectionDirection(nsAString& aDirection)
 {
   ErrorResult rv;
   GetSelectionDirection(aDirection, rv);
   return rv.StealNSResult();
--- a/dom/html/HTMLInputElement.h
+++ b/dom/html/HTMLInputElement.h
@@ -227,24 +227,26 @@ public:
   NS_IMETHOD_(bool) ValueChanged() const override;
   NS_IMETHOD_(void) GetTextEditorValue(nsAString& aValue, bool aIgnoreWrap) const override;
   NS_IMETHOD_(nsIEditor*) GetTextEditor() override;
   NS_IMETHOD_(nsISelectionController*) GetSelectionController() override;
   NS_IMETHOD_(nsFrameSelection*) GetConstFrameSelection() override;
   NS_IMETHOD BindToFrame(nsTextControlFrame* aFrame) override;
   NS_IMETHOD_(void) UnbindFromFrame(nsTextControlFrame* aFrame) override;
   NS_IMETHOD CreateEditor() override;
-  NS_IMETHOD_(nsIContent*) GetRootEditorNode() override;
+  NS_IMETHOD_(Element*) GetRootEditorNode() override;
   NS_IMETHOD_(Element*) CreatePlaceholderNode() override;
   NS_IMETHOD_(Element*) GetPlaceholderNode() override;
   NS_IMETHOD_(void) UpdatePlaceholderVisibility(bool aNotify) override;
   NS_IMETHOD_(bool) GetPlaceholderVisibility() override;
   NS_IMETHOD_(void) InitializeKeyboardEventListeners() override;
   NS_IMETHOD_(void) OnValueChanged(bool aNotify, bool aWasInteractiveUserChange) override;
   NS_IMETHOD_(bool) HasCachedSelection() override;
+  NS_IMETHOD GetSelectionRange(int32_t* aSelectionStart,
+                               int32_t* aSelectionEnd) override;
 
   void GetDisplayFileName(nsAString& aFileName) const;
 
   const nsTArray<OwningFileOrDirectory>& GetFilesOrDirectoriesInternal() const
   {
     return mFilesOrDirectories;
   }
 
@@ -951,18 +953,16 @@ protected:
    */
   bool IsValueEmpty() const;
 
   void ClearFiles(bool aSetValueChanged);
 
   void SetIndeterminateInternal(bool aValue,
                                 bool aShouldInvalidate);
 
-  nsresult GetSelectionRange(int32_t* aSelectionStart, int32_t* aSelectionEnd);
-
   /**
    * Called when an attribute is about to be changed
    */
   virtual nsresult BeforeSetAttr(int32_t aNameSpaceID, nsIAtom* aName,
                                  nsAttrValueOrString* aValue,
                                  bool aNotify) override;
   /**
    * Called when an attribute has just been changed
--- a/dom/html/HTMLMediaElement.cpp
+++ b/dom/html/HTMLMediaElement.cpp
@@ -5261,16 +5261,19 @@ void HTMLMediaElement::NetworkError()
 
 void HTMLMediaElement::DecodeError(const MediaResult& aError)
 {
   nsAutoString src;
   GetCurrentSrc(src);
   const char16_t* params[] = { src.get() };
   ReportLoadError("MediaLoadDecodeError", params, ArrayLength(params));
 
+  DecoderDoctorDiagnostics diagnostics;
+  diagnostics.StoreDecodeError(OwnerDoc(), aError, src, __func__);
+
   AudioTracks()->EmptyTracks();
   VideoTracks()->EmptyTracks();
   if (mIsLoadingFromSourceChildren) {
     mErrorSink->ResetError();
     if (mSourceLoadCandidate) {
       DispatchAsyncSourceError(mSourceLoadCandidate);
       QueueLoadFromSourceTask();
     } else {
@@ -5278,16 +5281,24 @@ void HTMLMediaElement::DecodeError(const
     }
   } else if (mReadyState == nsIDOMHTMLMediaElement::HAVE_NOTHING) {
     NoSupportedMediaSourceError(aError.Description());
   } else {
     Error(MEDIA_ERR_DECODE, aError.Description());
   }
 }
 
+void HTMLMediaElement::DecodeWarning(const MediaResult& aError)
+{
+  nsAutoString src;
+  GetCurrentSrc(src);
+  DecoderDoctorDiagnostics diagnostics;
+  diagnostics.StoreDecodeError(OwnerDoc(), aError, src, __func__);
+}
+
 bool HTMLMediaElement::HasError() const
 {
   return GetError();
 }
 
 void HTMLMediaElement::LoadAborted()
 {
   Error(MEDIA_ERR_ABORTED);
--- a/dom/html/HTMLMediaElement.h
+++ b/dom/html/HTMLMediaElement.h
@@ -170,16 +170,21 @@ public:
   // Called by the video decoder object, on the main thread,
   // when the resource has a network error during loading.
   virtual void NetworkError() final override;
 
   // Called by the video decoder object, on the main thread, when the
   // resource has a decode error during metadata loading or decoding.
   virtual void DecodeError(const MediaResult& aError) final override;
 
+  // Called by the decoder object, on the main thread, when the
+  // resource has a decode issue during metadata loading or decoding, but can
+  // continue decoding.
+  virtual void DecodeWarning(const MediaResult& aError) final override;
+
   // Return true if error attribute is not null.
   virtual bool HasError() const final override;
 
   // Called by the video decoder object, on the main thread, when the
   // resource load has been cancelled.
   virtual void LoadAborted() final override;
 
   // Called by the video decoder object, on the main thread,
--- a/dom/html/HTMLTextAreaElement.cpp
+++ b/dom/html/HTMLTextAreaElement.cpp
@@ -284,17 +284,17 @@ HTMLTextAreaElement::UnbindFromFrame(nsT
 }
 
 NS_IMETHODIMP
 HTMLTextAreaElement::CreateEditor()
 {
   return mState.PrepareEditor();
 }
 
-NS_IMETHODIMP_(nsIContent*)
+NS_IMETHODIMP_(Element*)
 HTMLTextAreaElement::GetRootEditorNode()
 {
   return mState.GetRootNode();
 }
 
 NS_IMETHODIMP_(Element*)
 HTMLTextAreaElement::CreatePlaceholderNode()
 {
@@ -835,27 +835,34 @@ HTMLTextAreaElement::SetSelectionEnd(con
     start = end;
   }
   rv = SetSelectionRange(start, end, direction);
   if (NS_FAILED(rv)) {
     aError.Throw(rv);
   }
 }
 
-nsresult
+NS_IMETHODIMP
 HTMLTextAreaElement::GetSelectionRange(int32_t* aSelectionStart,
                                        int32_t* aSelectionEnd)
 {
-  nsIFormControlFrame* formControlFrame = GetFormControlFrame(true);
-  nsITextControlFrame* textControlFrame = do_QueryFrame(formControlFrame);
-  if (textControlFrame) {
-    return textControlFrame->GetSelectionRange(aSelectionStart, aSelectionEnd);
+  // Flush frames, because our editor state will want to work with the frame.
+  if (IsInComposedDoc()) {
+    GetComposedDoc()->FlushPendingNotifications(FlushType::Frames);
+  }
+  if (!GetPrimaryFrame()) {
+    // Can we return a selection range anyway here, now that it lives on our
+    // state?  In fact, could we make this behave more like
+    // GetSelectionDirection, in the sense of working even when we have no
+    // frame, by just delegating entirely to mState?  And then, do we really
+    // need the flush?
+    return NS_ERROR_FAILURE;
   }
 
-  return NS_ERROR_FAILURE;
+  return mState.GetSelectionRange(aSelectionStart, aSelectionEnd);
 }
 
 static void
 DirectionToName(nsITextControlFrame::SelectionDirection dir, nsAString& aDirection)
 {
   if (dir == nsITextControlFrame::eNone) {
     aDirection.AssignLiteral("none");
   } else if (dir == nsITextControlFrame::eForward) {
@@ -875,32 +882,31 @@ HTMLTextAreaElement::GetSelectionDirecti
   return error.StealNSResult();
 }
 
 void
 HTMLTextAreaElement::GetSelectionDirection(nsAString& aDirection, ErrorResult& aError)
 {
   nsresult rv = NS_ERROR_FAILURE;
   nsIFormControlFrame* formControlFrame = GetFormControlFrame(true);
-  nsITextControlFrame* textControlFrame = do_QueryFrame(formControlFrame);
-  if (textControlFrame) {
+  if (formControlFrame) {
     nsITextControlFrame::SelectionDirection dir;
-    rv = textControlFrame->GetSelectionRange(nullptr, nullptr, &dir);
+    rv = mState.GetSelectionDirection(&dir);
     if (NS_SUCCEEDED(rv)) {
       DirectionToName(dir, aDirection);
+      return;
     }
   }
 
-  if (NS_FAILED(rv)) {
-    if (mState.IsSelectionCached()) {
-      DirectionToName(mState.GetSelectionProperties().GetDirection(), aDirection);
-      return;
-    }
-    aError.Throw(rv);
+  if (mState.IsSelectionCached()) {
+    DirectionToName(mState.GetSelectionProperties().GetDirection(), aDirection);
+    return;
   }
+
+  aError.Throw(rv);
 }
 
 NS_IMETHODIMP
 HTMLTextAreaElement::SetSelectionDirection(const nsAString& aDirection)
 {
   ErrorResult error;
   SetSelectionDirection(aDirection, error);
   return error.StealNSResult();
--- a/dom/html/HTMLTextAreaElement.h
+++ b/dom/html/HTMLTextAreaElement.h
@@ -96,24 +96,27 @@ public:
   NS_IMETHOD_(bool) ValueChanged() const override;
   NS_IMETHOD_(void) GetTextEditorValue(nsAString& aValue, bool aIgnoreWrap) const override;
   NS_IMETHOD_(nsIEditor*) GetTextEditor() override;
   NS_IMETHOD_(nsISelectionController*) GetSelectionController() override;
   NS_IMETHOD_(nsFrameSelection*) GetConstFrameSelection() override;
   NS_IMETHOD BindToFrame(nsTextControlFrame* aFrame) override;
   NS_IMETHOD_(void) UnbindFromFrame(nsTextControlFrame* aFrame) override;
   NS_IMETHOD CreateEditor() override;
-  NS_IMETHOD_(nsIContent*) GetRootEditorNode() override;
+  NS_IMETHOD_(Element*) GetRootEditorNode() override;
   NS_IMETHOD_(Element*) CreatePlaceholderNode() override;
   NS_IMETHOD_(Element*) GetPlaceholderNode() override;
   NS_IMETHOD_(void) UpdatePlaceholderVisibility(bool aNotify) override;
   NS_IMETHOD_(bool) GetPlaceholderVisibility() override;
   NS_IMETHOD_(void) InitializeKeyboardEventListeners() override;
   NS_IMETHOD_(void) OnValueChanged(bool aNotify, bool aWasInteractiveUserChange) override;
   NS_IMETHOD_(bool) HasCachedSelection() override;
+  NS_IMETHOD GetSelectionRange(int32_t* aSelectionStart,
+                               int32_t* aSelectionEnd) override;
+
 
   // nsIContent
   virtual nsresult BindToTree(nsIDocument* aDocument, nsIContent* aParent,
                                nsIContent* aBindingParent,
                                bool aCompileEventHandlers) override;
   virtual void UnbindFromTree(bool aDeep = true,
                               bool aNullParent = true) override;
   virtual bool ParseAttribute(int32_t aNamespaceID,
@@ -342,18 +345,16 @@ protected:
   /**
    * Setting the value.
    *
    * @param aValue      String to set.
    * @param aFlags      See nsTextEditorState::SetValueFlags.
    */
   nsresult SetValueInternal(const nsAString& aValue, uint32_t aFlags);
 
-  nsresult GetSelectionRange(int32_t* aSelectionStart, int32_t* aSelectionEnd);
-
   /**
    * Common method to call from the various mutation observer methods.
    * aContent is a content node that's either the one that changed or its
    * parent; we should only respond to the change if aContent is non-anonymous.
    */
   void ContentChanged(nsIContent* aContent);
 
   virtual nsresult AfterSetAttr(int32_t aNamespaceID, nsIAtom *aName,
--- a/dom/html/nsITextControlElement.h
+++ b/dom/html/nsITextControlElement.h
@@ -134,17 +134,17 @@ public:
    * a frame has been created for the text control element, but the created
    * editor may outlive the frame itself.
    */
   NS_IMETHOD CreateEditor() = 0;
 
   /**
    * Get the anonymous root node for the text control.
    */
-  NS_IMETHOD_(nsIContent*) GetRootEditorNode() = 0;
+  NS_IMETHOD_(mozilla::dom::Element*) GetRootEditorNode() = 0;
 
   /**
    * Create the placeholder anonymous node for the text control and returns it.
    */
   NS_IMETHOD_(mozilla::dom::Element*) CreatePlaceholderNode() = 0;
 
   /**
    * Get the placeholder anonymous node for the text control.
@@ -191,15 +191,21 @@ public:
    *
    * Note that this function has the side effect of making the editor for input
    * elements be initialized eagerly.
    */
   NS_IMETHOD_(bool) HasCachedSelection() = 0;
 
   static already_AddRefed<nsITextControlElement>
   GetTextControlElementFromEditingHost(nsIContent* aHost);
+
+  /**
+   * Get the selection range start and end points.
+   */
+  NS_IMETHOD GetSelectionRange(int32_t* aSelectionStart,
+                               int32_t* aSelectionEnd) = 0;
 };
 
 NS_DEFINE_STATIC_IID_ACCESSOR(nsITextControlElement,
                               NS_ITEXTCONTROLELEMENT_IID)
 
 #endif // nsITextControlElement_h___
 
--- a/dom/html/nsTextEditorState.cpp
+++ b/dom/html/nsTextEditorState.cpp
@@ -1544,16 +1544,83 @@ nsTextEditorState::SetSelectionPropertie
     mBoundFrame->SetSelectionRange(aProps.GetStart(),
                                    aProps.GetEnd(),
                                    aProps.GetDirection());
   } else {
     mSelectionProperties = aProps;
   }
 }
 
+nsresult
+nsTextEditorState::GetSelectionRange(int32_t* aSelectionStart,
+                                     int32_t* aSelectionEnd)
+{
+  MOZ_ASSERT(mBoundFrame,
+             "Caller didn't flush out frames and check for a frame?");
+  MOZ_ASSERT(aSelectionStart);
+  MOZ_ASSERT(aSelectionEnd);
+
+  // It's not clear that all the checks here are needed, but the previous
+  // version of this code in nsTextControlFrame was doing them, so we keep them
+  // for now.
+
+  nsresult rv = mBoundFrame->EnsureEditorInitialized();
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  nsISelectionController* selCon = GetSelectionController();
+  NS_ENSURE_TRUE(selCon, NS_ERROR_FAILURE);
+
+  nsCOMPtr<nsISelection> selection;
+  rv = selCon->GetSelection(nsISelectionController::SELECTION_NORMAL,
+                            getter_AddRefs(selection));
+  NS_ENSURE_SUCCESS(rv, rv);
+  NS_ENSURE_TRUE(selection, NS_ERROR_FAILURE);
+
+  dom::Selection* sel = selection->AsSelection();
+  mozilla::dom::Element* root = GetRootNode();
+  NS_ENSURE_STATE(root);
+  nsContentUtils::GetSelectionInTextControl(sel, root,
+                                            *aSelectionStart, *aSelectionEnd);
+  return NS_OK;
+}
+
+nsresult
+nsTextEditorState::GetSelectionDirection(nsITextControlFrame::SelectionDirection* aDirection)
+{
+  MOZ_ASSERT(mBoundFrame,
+             "Caller didn't flush out frames and check for a frame?");
+  MOZ_ASSERT(aDirection);
+
+  // It's not clear that all the checks here are needed, but the previous
+  // version of this code in nsTextControlFrame was doing them, so we keep them
+  // for now.
+
+  nsresult rv = mBoundFrame->EnsureEditorInitialized();
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  nsISelectionController* selCon = GetSelectionController();
+  NS_ENSURE_TRUE(selCon, NS_ERROR_FAILURE);
+
+  nsCOMPtr<nsISelection> selection;
+  rv = selCon->GetSelection(nsISelectionController::SELECTION_NORMAL,
+                            getter_AddRefs(selection));
+  NS_ENSURE_SUCCESS(rv, rv);
+  NS_ENSURE_TRUE(selection, NS_ERROR_FAILURE);
+
+  dom::Selection* sel = selection->AsSelection();
+  nsDirection direction = sel->GetSelectionDirection();
+  if (direction == eDirNext) {
+    *aDirection = nsITextControlFrame::eForward;
+  } else if (direction == eDirPrevious) {
+    *aDirection = nsITextControlFrame::eBackward;
+  } else {
+    NS_NOTREACHED("Invalid nsDirection enum value");
+  }
+  return NS_OK;
+}
 
 HTMLInputElement*
 nsTextEditorState::GetParentNumberControl(nsFrame* aFrame) const
 {
   MOZ_ASSERT(aFrame);
   nsIContent* content = aFrame->GetContent();
   MOZ_ASSERT(content);
   nsIContent* parent = content->GetParent();
@@ -1616,38 +1683,40 @@ nsTextEditorState::UnbindFromFrame(nsTex
   GetValue(value, true);
 
   if (mRestoringSelection) {
     mRestoringSelection->Revoke();
     mRestoringSelection = nullptr;
   }
 
   // Save our selection state if needed.
-  // Note that nsTextControlFrame::GetSelectionRange attempts to initialize the
+  // Note that GetSelectionRange attempts to initialize the
   // editor before grabbing the range, and because this is not an acceptable
   // side effect for unbinding from a text control frame, we need to call
   // GetSelectionRange before calling DestroyEditor, and only if
   // mEditorInitialized indicates that we actually have an editor available.
   int32_t start = 0, end = 0;
   nsITextControlFrame::SelectionDirection direction =
     nsITextControlFrame::eForward;
   if (mEditorInitialized) {
     HTMLInputElement* number = GetParentNumberControl(aFrame);
     if (number) {
       // If we are inside a number control, cache the selection on the
       // parent control, because this text editor state will be destroyed
       // together with the native anonymous text control.
       SelectionProperties props;
-      mBoundFrame->GetSelectionRange(&start, &end, &direction);
+      GetSelectionRange(&start, &end);
+      GetSelectionDirection(&direction);
       props.SetStart(start);
       props.SetEnd(end);
       props.SetDirection(direction);
       number->SetSelectionProperties(props);
     } else {
-      mBoundFrame->GetSelectionRange(&start, &end, &direction);
+      GetSelectionRange(&start, &end);
+      GetSelectionDirection(&direction);
       mSelectionProperties.SetStart(start);
       mSelectionProperties.SetEnd(end);
       mSelectionProperties.SetDirection(direction);
       mSelectionCached = true;
     }
   }
 
   // Destroy our editor
--- a/dom/html/nsTextEditorState.h
+++ b/dom/html/nsTextEditorState.h
@@ -257,16 +257,22 @@ public:
   };
 
   bool IsSelectionCached() const;
   SelectionProperties& GetSelectionProperties();
   void SetSelectionProperties(SelectionProperties& aProps);
   void WillInitEagerly() { mSelectionRestoreEagerInit = true; }
   bool HasNeverInitializedBefore() const { return !mEverInited; }
 
+  // Get the selection range start and end points in our text.
+  nsresult GetSelectionRange(int32_t* aSelectionStart, int32_t* aSelectionEnd);
+
+  // Get the selection direction
+  nsresult GetSelectionDirection(nsITextControlFrame::SelectionDirection* aDirection);
+
   void UpdateEditableState(bool aNotify) {
     if (mRootNode) {
       mRootNode->UpdateEditableState(aNotify);
     }
   }
 
 private:
   friend class RestoreSelectionState;
--- a/dom/ipc/ContentParent.cpp
+++ b/dom/ipc/ContentParent.cpp
@@ -4533,22 +4533,39 @@ ContentParent::CommonCreateWindow(PBrows
   }
 
   nsCOMPtr<nsPIWindowWatcher> pwwatch =
     do_GetService(NS_WINDOWWATCHER_CONTRACTID, &aResult);
   if (NS_WARN_IF(NS_FAILED(aResult))) {
     return IPC_OK();
   }
 
-  if (aSetOpener && thisTabParent) {
-    aResult = pwwatch->OpenWindowWithTabParent(thisTabParent, aFeatures,
-                                               aCalledFromJS, aFullZoom,
-                                               getter_AddRefs(aNewTabParent));
-  } else {
-    aResult = pwwatch->OpenWindowWithoutParent(getter_AddRefs(aNewTabParent));
+  aResult = pwwatch->OpenWindowWithTabParent(aSetOpener ? thisTabParent : nullptr,
+                                             aFeatures, aCalledFromJS, aFullZoom,
+                                             getter_AddRefs(aNewTabParent));
+  if (NS_WARN_IF(NS_FAILED(aResult))) {
+    return IPC_OK();
+  }
+
+  if (aURIToLoad) {
+    nsCOMPtr<mozIDOMWindowProxy> openerWindow;
+    if (aSetOpener && thisTabParent) {
+      openerWindow = thisTabParent->GetParentWindowOuter();
+    }
+    nsCOMPtr<nsIBrowserDOMWindow> newBrowserDOMWin =
+      TabParent::GetFrom(aNewTabParent)->GetBrowserDOMWindow();
+    if (NS_WARN_IF(!newBrowserDOMWin)) {
+      aResult = NS_ERROR_ABORT;
+      return IPC_OK();
+    }
+    nsCOMPtr<mozIDOMWindowProxy> win;
+    aResult = newBrowserDOMWin->OpenURI(aURIToLoad, openerWindow,
+                                        nsIBrowserDOMWindow::OPEN_CURRENTWINDOW,
+                                        nsIBrowserDOMWindow::OPEN_NEW,
+                                        getter_AddRefs(win));
   }
 
   return IPC_OK();
 }
 
 mozilla::ipc::IPCResult
 ContentParent::RecvCreateWindow(PBrowserParent* aThisTab,
                                 PBrowserParent* aNewTab,
--- a/dom/media/DecoderDoctorDiagnostics.cpp
+++ b/dom/media/DecoderDoctorDiagnostics.cpp
@@ -449,21 +449,24 @@ DecoderDoctorDocumentWatcher::Synthesize
           if (issue != DecoderDoctorDiagnostics::eUnset) {
             lastKeySystemIssue = issue;
           }
         }
         break;
       case DecoderDoctorDiagnostics::eEvent:
         MOZ_ASSERT_UNREACHABLE("Events shouldn't be stored for processing.");
         break;
+      case DecoderDoctorDiagnostics::eDecodeError:
+        // TODO
+        break;
+      case DecoderDoctorDiagnostics::eDecodeWarning:
+        // TODO
+        break;
       default:
-        MOZ_ASSERT(diag.mDecoderDoctorDiagnostics.Type()
-                     == DecoderDoctorDiagnostics::eFormatSupportCheck
-                   || diag.mDecoderDoctorDiagnostics.Type()
-                        == DecoderDoctorDiagnostics::eMediaKeySystemAccessRequest);
+        MOZ_ASSERT_UNREACHABLE("Unhandled DecoderDoctorDiagnostics type");
         break;
     }
   }
 
   // Check if issues have been solved, by finding if some now-playable
   // key systems or formats were previously recorded as having issues.
   if (!supportedKeySystems.IsEmpty() || !playableFormats.IsEmpty()) {
     DD_DEBUG("DecoderDoctorDocumentWatcher[%p, doc=%p]::SynthesizeAnalysis() - supported key systems '%s', playable formats '%s'; See if they show issues have been solved...",
@@ -774,16 +777,98 @@ DecoderDoctorDiagnostics::StoreEvent(nsI
         ReportAnalysis(aDocument, sCannotInitializePulseAudio,
                        true, NS_LITERAL_STRING("*"));
       }
       break;
   }
 #endif // MOZ_PULSEAUDIO
 }
 
+void
+DecoderDoctorDiagnostics::StoreDecodeError(nsIDocument* aDocument,
+                                           const MediaResult& aError,
+                                           const nsString& aMediaSrc,
+                                           const char* aCallSite)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  // Make sure we only store once.
+  MOZ_ASSERT(mDiagnosticsType == eUnsaved);
+  mDiagnosticsType = eDecodeError;
+
+  if (NS_WARN_IF(!aDocument)) {
+    DD_WARN("DecoderDoctorDiagnostics[%p]::StoreDecodeError("
+            "nsIDocument* aDocument=nullptr, aError=%s,"
+            " aMediaSrc=<provided>, call site '%s')",
+            this, aError.Description().get(), aCallSite);
+    return;
+  }
+
+  RefPtr<DecoderDoctorDocumentWatcher> watcher =
+    DecoderDoctorDocumentWatcher::RetrieveOrCreate(aDocument);
+
+  if (NS_WARN_IF(!watcher)) {
+    DD_WARN("DecoderDoctorDiagnostics[%p]::StoreDecodeError("
+            "nsIDocument* aDocument=%p, aError='%s', aMediaSrc=<provided>,"
+            " call site '%s') - Could not create document watcher",
+            this, aDocument, aError.Description().get(), aCallSite);
+    return;
+  }
+
+  mDecodeIssue = aError;
+  mDecodeIssueMediaSrc = aMediaSrc;
+
+  // StoreDecodeError should only be called once, after all data is
+  // available, so it is safe to Move() from this object.
+  watcher->AddDiagnostics(Move(*this), aCallSite);
+  // Even though it's moved-from, the type should stay set
+  // (Only used to ensure that we do store only once.)
+  MOZ_ASSERT(mDiagnosticsType == eDecodeError);
+}
+
+void
+DecoderDoctorDiagnostics::StoreDecodeWarning(nsIDocument* aDocument,
+                                             const MediaResult& aWarning,
+                                             const nsString& aMediaSrc,
+                                             const char* aCallSite)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  // Make sure we only store once.
+  MOZ_ASSERT(mDiagnosticsType == eUnsaved);
+  mDiagnosticsType = eDecodeWarning;
+
+  if (NS_WARN_IF(!aDocument)) {
+    DD_WARN("DecoderDoctorDiagnostics[%p]::StoreDecodeWarning("
+            "nsIDocument* aDocument=nullptr, aWarning=%s,"
+            " aMediaSrc=<provided>, call site '%s')",
+            this, aWarning.Description().get(), aCallSite);
+    return;
+  }
+
+  RefPtr<DecoderDoctorDocumentWatcher> watcher =
+    DecoderDoctorDocumentWatcher::RetrieveOrCreate(aDocument);
+
+  if (NS_WARN_IF(!watcher)) {
+    DD_WARN("DecoderDoctorDiagnostics[%p]::StoreDecodeWarning("
+            "nsIDocument* aDocument=%p, aWarning='%s', aMediaSrc=<provided>,"
+            " call site '%s') - Could not create document watcher",
+            this, aDocument, aWarning.Description().get(), aCallSite);
+    return;
+  }
+
+  mDecodeIssue = aWarning;
+  mDecodeIssueMediaSrc = aMediaSrc;
+
+  // StoreDecodeWarning should only be called once, after all data is
+  // available, so it is safe to Move() from this object.
+  watcher->AddDiagnostics(Move(*this), aCallSite);
+  // Even though it's moved-from, the type should stay set
+  // (Only used to ensure that we do store only once.)
+  MOZ_ASSERT(mDiagnosticsType == eDecodeWarning);
+}
+
 static const char*
 EventDomainString(DecoderDoctorEvent::Domain aDomain)
 {
   switch (aDomain) {
     case DecoderDoctorEvent::eAudioSinkStartup:
       return "audio-sink-startup";
   }
   return "?";
@@ -832,16 +917,30 @@ DecoderDoctorDiagnostics::GetDescription
           s += ", Widevine with no WMF";
           break;
       }
       break;
     case eEvent:
       s = nsPrintfCString("event domain %s result=%" PRIu32,
                           EventDomainString(mEvent.mDomain), static_cast<uint32_t>(mEvent.mResult));
       break;
+    case eDecodeError:
+      s = "decode error: ";
+      s += mDecodeIssue.Description();
+      s += ", src='";
+      s += mDecodeIssueMediaSrc.IsEmpty() ? "<none>" : "<provided>";
+      s += "'";
+      break;
+    case eDecodeWarning:
+      s = "decode warning: ";
+      s += mDecodeIssue.Description();
+      s += ", src='";
+      s += mDecodeIssueMediaSrc.IsEmpty() ? "<none>" : "<provided>";
+      s += "'";
+      break;
     default:
       MOZ_ASSERT_UNREACHABLE("Unexpected DiagnosticsType");
       s = "?";
       break;
   }
   return s;
 }
 
--- a/dom/media/DecoderDoctorDiagnostics.h
+++ b/dom/media/DecoderDoctorDiagnostics.h
@@ -2,16 +2,17 @@
 /* vim:set ts=2 sw=2 sts=2 et cindent: */
 /* 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 DecoderDoctorDiagnostics_h_
 #define DecoderDoctorDiagnostics_h_
 
+#include "MediaResult.h"
 #include "nsString.h"
 
 class nsIDocument;
 
 namespace mozilla {
 
 struct DecoderDoctorEvent {
   enum Domain {
@@ -52,21 +53,34 @@ public:
                                  const nsAString& aKeySystem,
                                  bool aIsSupported,
                                  const char* aCallSite);
 
   void StoreEvent(nsIDocument* aDocument,
                   const DecoderDoctorEvent& aEvent,
                   const char* aCallSite);
 
-  enum DiagnosticsType {
+  void StoreDecodeError(nsIDocument* aDocument,
+                        const MediaResult& aError,
+                        const nsString& aMediaSrc,
+                        const char* aCallSite);
+
+  void StoreDecodeWarning(nsIDocument* aDocument,
+                          const MediaResult& aWarning,
+                          const nsString& aMediaSrc,
+                          const char* aCallSite);
+
+  enum DiagnosticsType
+  {
     eUnsaved,
     eFormatSupportCheck,
     eMediaKeySystemAccessRequest,
-    eEvent
+    eEvent,
+    eDecodeError,
+    eDecodeWarning
   };
   DiagnosticsType Type() const { return mDiagnosticsType; }
 
   // Description string, for logging purposes; only call on stored diags.
   nsCString GetDescription() const;
 
   // Methods to record diagnostic information:
 
@@ -103,16 +117,22 @@ public:
     return mKeySystemIssue;
   }
 
   DecoderDoctorEvent event() const
   {
     return mEvent;
   }
 
+  const MediaResult& DecodeIssue() const { return mDecodeIssue; }
+  const nsString& DecodeIssueMediaSrc() const
+  {
+    return mDecodeIssueMediaSrc;
+  }
+
 private:
   // Currently-known type of diagnostics. Set from one of the 'Store...' methods.
   // This helps ensure diagnostics are only stored once,
   // and makes it easy to know what information they contain.
   DiagnosticsType mDiagnosticsType = eUnsaved;
 
   nsString mFormat;
   // True if there is at least one decoder that can play that format.
@@ -125,13 +145,16 @@ private:
   bool mAudioNotSupported = false;
   nsCString mGMP;
 
   nsString mKeySystem;
   bool mIsKeySystemSupported = false;
   KeySystemIssue mKeySystemIssue = eUnset;
 
   DecoderDoctorEvent mEvent;
+
+  MediaResult mDecodeIssue = NS_OK;
+  nsString mDecodeIssueMediaSrc;
 };
 
 } // namespace mozilla
 
 #endif
--- a/dom/media/MediaDecoderOwner.h
+++ b/dom/media/MediaDecoderOwner.h
@@ -69,16 +69,21 @@ public:
   virtual void NetworkError() = 0;
 
   // Called by the decoder object, on the main thread, when the
   // resource has a decode error during metadata loading or decoding.
   // The decoder owner should call Shutdown() on the decoder and drop the
   // reference to the decoder to prevent further calls into the decoder.
   virtual void DecodeError(const MediaResult& aError) = 0;
 
+  // Called by the decoder object, on the main thread, when the
+  // resource has a decode issue during metadata loading or decoding, but can
+  // continue decoding.
+  virtual void DecodeWarning(const MediaResult& aError) = 0;
+
   // Return true if media element error attribute is not null.
   virtual bool HasError() const = 0;
 
   // Called by the video decoder object, on the main thread, when the
   // resource load has been cancelled.
   virtual void LoadAborted() = 0;
 
   // Called by the video decoder object, on the main thread,
--- a/dom/media/MediaFormatReader.cpp
+++ b/dom/media/MediaFormatReader.cpp
@@ -979,34 +979,16 @@ MediaFormatReader::DemuxerProxy::NotifyD
     }
     if (data->mVideoDemuxer) {
       data->mVideoDemuxer->UpdateBuffered();
     }
     return NotifyDataArrivedPromise::CreateAndResolve(true, __func__);
   });
 }
 
-static const char*
-TrackTypeToStr(TrackInfo::TrackType aTrack)
-{
-  MOZ_ASSERT(aTrack == TrackInfo::kAudioTrack
-             || aTrack == TrackInfo::kVideoTrack
-             || aTrack == TrackInfo::kTextTrack);
-  switch (aTrack) {
-  case TrackInfo::kAudioTrack:
-    return "Audio";
-  case TrackInfo::kVideoTrack:
-    return "Video";
-  case TrackInfo::kTextTrack:
-    return "Text";
-  default:
-    return "Unknown";
-  }
-}
-
 MediaFormatReader::MediaFormatReader(AbstractMediaDecoder* aDecoder,
                                      MediaDataDemuxer* aDemuxer,
                                      VideoFrameContainer* aVideoFrameContainer)
   : MediaDecoderReader(aDecoder)
   , mAudio(this, MediaData::AUDIO_DATA,
            Preferences::GetUint("media.audio-max-decode-error", 3))
   , mVideo(this, MediaData::VIDEO_DATA,
            Preferences::GetUint("media.video-max-decode-error", 2))
--- a/dom/media/MediaInfo.cpp
+++ b/dom/media/MediaInfo.cpp
@@ -3,16 +3,34 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "MediaInfo.h"
 
 namespace mozilla {
 
+const char*
+TrackTypeToStr(TrackInfo::TrackType aTrack)
+{
+  MOZ_ASSERT(aTrack == TrackInfo::kAudioTrack
+             || aTrack == TrackInfo::kVideoTrack
+             || aTrack == TrackInfo::kTextTrack);
+  switch (aTrack) {
+  case TrackInfo::kAudioTrack:
+    return "Audio";
+  case TrackInfo::kVideoTrack:
+    return "Video";
+  case TrackInfo::kTextTrack:
+    return "Text";
+  default:
+    return "Unknown";
+  }
+}
+
 typedef AudioConfig::ChannelLayout ChannelLayout;
 
 /**
  * AudioConfig::ChannelLayout
  */
 
 /*
  SMPTE channel layout (also known as wave order)
--- a/dom/media/MediaInfo.h
+++ b/dom/media/MediaInfo.h
@@ -173,16 +173,19 @@ protected:
     mTags = aOther.mTags;
     MOZ_COUNT_CTOR(TrackInfo);
   }
 
 private:
   TrackType mType;
 };
 
+// String version of track type. Don't use with kUndefinedTrack.
+const char* TrackTypeToStr(TrackInfo::TrackType aTrack);
+
 // Stores info relevant to presenting media frames.
 class VideoInfo : public TrackInfo
 {
 public:
   enum Rotation
   {
     kDegree_0 = 0,
     kDegree_90 = 90,
--- a/dom/media/fmp4/MP4Demuxer.cpp
+++ b/dom/media/fmp4/MP4Demuxer.cpp
@@ -119,24 +119,16 @@ MP4Demuxer::MP4Demuxer(MediaResource* aR
 {
 }
 
 RefPtr<MP4Demuxer::InitPromise>
 MP4Demuxer::Init()
 {
   AutoPinned<mp4_demuxer::ResourceStream> stream(mStream);
 
-  // Check that we have enough data to read the metadata.
-  if (!mp4_demuxer::MP4Metadata::HasCompleteMetadata(stream)) {
-    return InitPromise::CreateAndReject(
-      MediaResult(NS_ERROR_DOM_MEDIA_DEMUXER_ERR,
-                  RESULT_DETAIL("Incomplete MP4 metadata")),
-      __func__);
-  }
-
   RefPtr<MediaByteBuffer> initData = mp4_demuxer::MP4Metadata::Metadata(stream);
   if (!initData) {
     return InitPromise::CreateAndReject(
       MediaResult(NS_ERROR_DOM_MEDIA_DEMUXER_ERR,
                   RESULT_DETAIL("Invalid MP4 metadata or OOM")),
       __func__);
   }
 
--- a/dom/media/test/external/external_media_tests/media_utils/video_puppeteer.py
+++ b/dom/media/test/external/external_media_tests/media_utils/video_puppeteer.py
@@ -55,17 +55,17 @@ class VideoPuppeteer(object):
      of the video.
     :param stall_wait_time: The amount of time to wait to see if a stall has
      cleared. If 0, do not check for stalls.
     :param timeout: The amount of time to wait until the video starts.
     """
 
     _video_var_script = (
         'var video = arguments[0];'
-        'var baseURI = arguments[0].baseURI;'
+        'var baseURI = video.baseURI;'
         'var currentTime = video.wrappedJSObject.currentTime;'
         'var duration = video.wrappedJSObject.duration;'
         'var buffered = video.wrappedJSObject.buffered;'
         'var bufferedRanges = [];'
         'for (var i = 0; i < buffered.length; i++) {'
         'bufferedRanges.push([buffered.start(i), buffered.end(i)]);'
         '}'
         'var played = video.wrappedJSObject.played;'
@@ -200,17 +200,17 @@ class VideoPuppeteer(object):
         if self._last_seen_video_state.remaining_time < self.interval:
             return True
 
         # Check to see if the video has stalled. Accumulate the amount of lag
         # since the video started, and if it is too high, then raise.
         if (self.stall_wait_time and
                 self._last_seen_video_state.lag > self.stall_wait_time):
             raise VideoException('Video {} stalled.\n{}'
-                                 .format(self._last_seen_video_state.video_uri,
+                                 .format(self._last_seen_video_state.base_uri,
                                          self))
 
         # We are cruising, so we are not done.
         return False
 
     def _update_expected_duration(self):
         """
         Update the duration of the target video at self.test_url (in seconds).
--- a/dom/permission/tests/test_permissions_api.html
+++ b/dom/permission/tests/test_permissions_api.html
@@ -169,17 +169,17 @@
   function createIframe() {
     return new Promise((resolve) => {
       const iframe = document.createElement('iframe');
       iframe.src = 'file_empty.html';
       iframe.onload = () => resolve(iframe.contentWindow);
       document.body.appendChild(iframe);
     });
   }
-  debugger;
+
   window.onload = () => {
     enablePrefs()
       .then(createIframe)
       .then(createPermissionTester)
       .then((tester) => {
         return tester
           .checkUnsupportedPermissions()
           .then(() => tester.setPermissions(UNKNOWN_ACTION))
--- a/dom/workers/ServiceWorkerClient.cpp
+++ b/dom/workers/ServiceWorkerClient.cpp
@@ -28,18 +28,19 @@ NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(Se
 NS_IMPL_CYCLE_COLLECTING_ADDREF(ServiceWorkerClient)
 NS_IMPL_CYCLE_COLLECTING_RELEASE(ServiceWorkerClient)
 
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ServiceWorkerClient)
   NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
   NS_INTERFACE_MAP_ENTRY(nsISupports)
 NS_INTERFACE_MAP_END
 
-ServiceWorkerClientInfo::ServiceWorkerClientInfo(nsIDocument* aDoc)
+ServiceWorkerClientInfo::ServiceWorkerClientInfo(nsIDocument* aDoc, uint32_t aOrdinal)
   : mType(ClientType::Window)
+  , mOrdinal(aOrdinal)
   , mWindowId(0)
   , mFrameType(FrameType::None)
 {
   MOZ_ASSERT(aDoc);
   nsresult rv = aDoc->GetOrCreateId(mClientId);
   if (NS_FAILED(rv)) {
     NS_WARNING("Failed to get the UUID of the document.");
   }
@@ -54,34 +55,69 @@ ServiceWorkerClientInfo::ServiceWorkerCl
   nsCOMPtr<nsIURI> originalURI = aDoc->GetOriginalURI();
   if (originalURI) {
     nsAutoCString spec;
     originalURI->GetSpec(spec);
     CopyUTF8toUTF16(spec, mUrl);
   }
   mVisibilityState = aDoc->VisibilityState();
 
+  mLastFocusTime = aDoc->LastFocusTime();
+
   ErrorResult result;
   mFocused = aDoc->HasFocus(result);
   if (result.Failed()) {
     NS_WARNING("Failed to get focus information.");
   }
 
+  MOZ_ASSERT_IF(mLastFocusTime.IsNull(), !mFocused);
+  MOZ_ASSERT_IF(mFocused, !mLastFocusTime.IsNull());
+
   RefPtr<nsGlobalWindow> outerWindow = nsGlobalWindow::Cast(aDoc->GetWindow());
   if (!outerWindow) {
     MOZ_ASSERT(mFrameType == FrameType::None);
   } else if (!outerWindow->IsTopLevelWindow()) {
     mFrameType = FrameType::Nested;
   } else if (outerWindow->HadOriginalOpener()) {
     mFrameType = FrameType::Auxiliary;
   } else {
     mFrameType = FrameType::Top_level;
   }
 }
 
+bool
+ServiceWorkerClientInfo::operator<(const ServiceWorkerClientInfo& aRight) const
+{
+  // Note: the mLastFocusTime comparisons are reversed because we need to
+  // put most recently focused values first.  The mOrdinal comparison is
+  // normal, though, because otherwise we want normal creation order.
+
+  if (mLastFocusTime == aRight.mLastFocusTime) {
+    return mOrdinal < aRight.mOrdinal;
+  }
+
+  if (mLastFocusTime.IsNull()) {
+    return false;
+  }
+
+  if (aRight.mLastFocusTime.IsNull()) {
+    return true;
+  }
+
+  return mLastFocusTime > aRight.mLastFocusTime;
+}
+
+bool
+ServiceWorkerClientInfo::operator==(const ServiceWorkerClientInfo& aRight) const
+{
+  return mLastFocusTime == aRight.mLastFocusTime &&
+         mOrdinal == aRight.mOrdinal &&
+         mClientId == aRight.mClientId;
+}
+
 JSObject*
 ServiceWorkerClient::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
 {
   return ClientBinding::Wrap(aCx, this, aGivenProto);
 }
 
 ClientType
 ServiceWorkerClient::Type() const
--- a/dom/workers/ServiceWorkerClient.h
+++ b/dom/workers/ServiceWorkerClient.h
@@ -26,33 +26,38 @@ class ServiceWorkerWindowClient;
 // Used as a container object for information needed to create
 // client objects.
 class ServiceWorkerClientInfo final
 {
   friend class ServiceWorkerClient;
   friend class ServiceWorkerWindowClient;
 
 public:
-  explicit ServiceWorkerClientInfo(nsIDocument* aDoc);
+  explicit ServiceWorkerClientInfo(nsIDocument* aDoc, uint32_t aOrdinal = 0);
 
   const nsString& ClientId() const
   {
     return mClientId;
   }
 
+  bool operator<(const ServiceWorkerClientInfo& aRight) const;
+  bool operator==(const ServiceWorkerClientInfo& aRight) const;
+
 private:
   const mozilla::dom::ClientType mType;
+  const uint32_t mOrdinal;
   nsString mClientId;
   uint64_t mWindowId;
   nsString mUrl;
 
   // Window Clients
   VisibilityState mVisibilityState;
+  FrameType mFrameType;
+  TimeStamp mLastFocusTime;
   bool mFocused;
-  FrameType mFrameType;
 };
 
 class ServiceWorkerClient : public nsISupports,
                             public nsWrapperCache
 {
 public:
   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
   NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(ServiceWorkerClient)
--- a/dom/workers/ServiceWorkerManager.cpp
+++ b/dom/workers/ServiceWorkerManager.cpp
@@ -2969,71 +2969,74 @@ ServiceWorkerManager::GetAllClients(nsIP
 
   nsCOMPtr<nsISimpleEnumerator> enumerator;
   nsresult rv = obs->EnumerateObservers("service-worker-get-client",
                                         getter_AddRefs(enumerator));
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return;
   }
 
-  auto ProcessDocument = [&aDocuments](nsIPrincipal* aPrincipal, nsIDocument* aDoc) {
-    if (!aDoc || !aDoc->GetWindow()) {
-      return;
+  // Get a list of Client documents out of the observer service
+  AutoTArray<nsCOMPtr<nsIDocument>, 32> docList;
+  bool loop = true;
+  while (NS_SUCCEEDED(enumerator->HasMoreElements(&loop)) && loop) {
+    nsCOMPtr<nsISupports> ptr;
+    rv = enumerator->GetNext(getter_AddRefs(ptr));
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      continue;
+    }
+
+    nsCOMPtr<nsIDocument> doc = do_QueryInterface(ptr);
+    if (!doc || !doc->GetWindow()) {
+      continue;
     }
 
     bool equals = false;
-    aPrincipal->Equals(aDoc->NodePrincipal(), &equals);
+    Unused << aPrincipal->Equals(doc->NodePrincipal(), &equals);
     if (!equals) {
-      return;
+      continue;
     }
 
     // Treat http windows with devtools opened as secure if the correct devtools
     // setting is enabled.
-    if (!aDoc->GetWindow()->GetServiceWorkersTestingEnabled() &&
+    if (!doc->GetWindow()->GetServiceWorkersTestingEnabled() &&
         !Preferences::GetBool("dom.serviceWorkers.testing.enabled") &&
-        !IsFromAuthenticatedOrigin(aDoc)) {
-      return;
+        !IsFromAuthenticatedOrigin(doc)) {
+      continue;
     }
 
-    ServiceWorkerClientInfo clientInfo(aDoc);
-    aDocuments.AppendElement(aDoc);
-  };
-
-  // Since it's not simple to check whether a document is in
-  // mControlledDocuments, we take different code paths depending on whether we
-  // need to look at all documents.  The common parts of the two loops are
-  // factored out into the ProcessDocument lambda.
-  if (aIncludeUncontrolled) {
-    bool loop = true;
-    while (NS_SUCCEEDED(enumerator->HasMoreElements(&loop)) && loop) {
-      nsCOMPtr<nsISupports> ptr;
-      rv = enumerator->GetNext(getter_AddRefs(ptr));
-      if (NS_WARN_IF(NS_FAILED(rv))) {
+    // If we are only returning controlled Clients then skip any documents
+    // that are for different registrations.
+    if (!aIncludeUncontrolled) {
+      ServiceWorkerRegistrationInfo* reg = mControlledDocuments.GetWeak(doc);
+      if (!reg || reg->mScope != registration->mScope) {
         continue;
       }
-
-      nsCOMPtr<nsIDocument> doc = do_QueryInterface(ptr);
-      ProcessDocument(aPrincipal, doc);
+    }
+
+    if (!aIncludeUncontrolled && !mControlledDocuments.Contains(doc)) {
+      continue;
     }
-  } else {
-    for (auto iter = mControlledDocuments.Iter(); !iter.Done(); iter.Next()) {
-      ServiceWorkerRegistrationInfo* thisRegistration = iter.UserData();
-      MOZ_ASSERT(thisRegistration);
-      if (!registration->mScope.Equals(thisRegistration->mScope)) {
-        continue;
-      }
-
-      nsCOMPtr<nsIDocument> doc = do_QueryInterface(iter.Key());
-
-      // All controlled documents must have an outer window.
-      MOZ_ASSERT(doc->GetWindow());
-
-      ProcessDocument(aPrincipal, doc);
-    }
-  }
+
+    docList.AppendElement(doc.forget());
+  }
+
+  // The observer service gives us the list in reverse creation order.
+  // We need to maintain creation order, so reverse the list before
+  // processing.
+  docList.Reverse();
+
+  // Finally convert to the list of ServiceWorkerClientInfo objects.
+  uint32_t ordinal = 0;
+  for (uint32_t i = 0; i < docList.Length(); ++i) {
+    aDocuments.AppendElement(ServiceWorkerClientInfo(docList[i], ordinal));
+    ordinal += 1;
+  }
+
+  aDocuments.Sort();
 }
 
 void
 ServiceWorkerManager::MaybeClaimClient(nsIDocument* aDocument,
                                        ServiceWorkerRegistrationInfo* aWorkerRegistration)
 {
   MOZ_ASSERT(aWorkerRegistration);
   MOZ_ASSERT(aWorkerRegistration->GetActive());
--- a/dom/workers/test/serviceworkers/claim_worker_2.js
+++ b/dom/workers/test/serviceworkers/claim_worker_2.js
@@ -16,12 +16,15 @@ onactivate = function(e) {
 
     return claimPromise.then(self.clients.matchAll().then(function(matched) {
       // should be 1
       result.match_count_after = matched.length;
       if (result.match_count_after === 1) {
         matched[0].postMessage(result);
       } else {
         dump("ERROR: claim_worker_2 failed to capture clients.\n");
+        for (let i = 0; i < matched.length; ++i) {
+          dump('### ### matched[' + i + ']: ' + matched[i].url + '\n');
+        }
       }
     }));
   });
 }
--- a/gfx/2d/DrawCommand.h
+++ b/gfx/2d/DrawCommand.h
@@ -367,16 +367,21 @@ PathExtentsToMaxStrokeExtents(const Stro
     styleExpansionFactor = M_SQRT2 * aStrokeOptions.mMiterLimit;
   }
 
   styleExpansionFactor *= aStrokeOptions.mLineWidth;
 
   double dx = styleExpansionFactor * hypot(aTransform._11, aTransform._21);
   double dy = styleExpansionFactor * hypot(aTransform._22, aTransform._12);
 
+  // Even if the stroke only partially covers a pixel, it must still render to
+  // full pixels. Round up to compensate for this.
+  dx = ceil(dx);
+  dy = ceil(dy);
+
   Rect result = aRect;
   result.Inflate(dx, dy);
   return result;
 }
 
 class StrokeCommand : public DrawingCommand
 {
 public:
--- a/gfx/2d/DrawTargetRecording.cpp
+++ b/gfx/2d/DrawTargetRecording.cpp
@@ -387,17 +387,17 @@ DrawTargetRecording::FillGlyphs(ScaledFo
     RecordingFontUserData *userData = new RecordingFontUserData;
     userData->refPtr = aFont;
     userData->recorder = mRecorder;
     aFont->AddUserData(reinterpret_cast<UserDataKey*>(mRecorder.get()), userData, 
                        &RecordingFontUserDataDestroyFunc);
   }
 
   mRecorder->RecordEvent(RecordedFillGlyphs(this, aFont, aPattern, aOptions, aBuffer.mGlyphs, aBuffer.mNumGlyphs));
-  mFinalDT->FillGlyphs(aFont, aBuffer, aPattern, aOptions, aRenderingOptions);
+  mFinalDT->FillGlyphs(aFont, aBuffer, *AdjustedPattern(aPattern), aOptions, aRenderingOptions);
 }
 
 void
 DrawTargetRecording::Mask(const Pattern &aSource,
                           const Pattern &aMask,
                           const DrawOptions &aOptions)
 {
   EnsurePatternDependenciesStored(aSource);
--- a/gfx/2d/DrawTargetSkia.cpp
+++ b/gfx/2d/DrawTargetSkia.cpp
@@ -19,16 +19,17 @@
 #include "skia/include/core/SkColorFilter.h"
 #include "skia/include/effects/SkBlurImageFilter.h"
 #include "skia/include/effects/SkLayerRasterizer.h"
 #include "skia/src/core/SkSpecialImage.h"
 #include "Blur.h"
 #include "Logging.h"
 #include "Tools.h"
 #include "DataSurfaceHelpers.h"
+#include "PathHelpers.h"
 #include "Swizzle.h"
 #include <algorithm>
 
 #ifdef USE_SKIA_GPU
 #include "GLDefs.h"
 #include "skia/include/gpu/SkGr.h"
 #include "skia/include/gpu/GrContext.h"
 #include "skia/include/gpu/GrDrawContext.h"
@@ -800,29 +801,109 @@ DrawTargetSkia::Stroke(const Path *aPath
 
   if (!skiaPath->GetPath().isFinite()) {
     return;
   }
 
   mCanvas->drawPath(skiaPath->GetPath(), paint.mPaint);
 }
 
+static Float
+DashPeriodLength(const StrokeOptions& aStrokeOptions)
+{
+  Float length = 0;
+  for (size_t i = 0; i < aStrokeOptions.mDashLength; i++) {
+    length += aStrokeOptions.mDashPattern[i];
+  }
+  if (aStrokeOptions.mDashLength & 1) {
+    // "If an odd number of values is provided, then the list of values is
+    // repeated to yield an even number of values."
+    // Double the length.
+    length += length;
+  }
+  return length;
+}
+
+static inline Float
+RoundDownToMultiple(Float aValue, Float aFactor)
+{
+  return floorf(aValue / aFactor) * aFactor;
+}
+
+static Rect
+UserSpaceStrokeClip(const IntRect &aDeviceClip,
+                    const Matrix &aTransform,
+                    const StrokeOptions &aStrokeOptions)
+{
+  Matrix inverse = aTransform;
+  if (!inverse.Invert()) {
+    return Rect();
+  }
+  Rect deviceClip(aDeviceClip);
+  deviceClip.Inflate(MaxStrokeExtents(aStrokeOptions, aTransform));
+  return inverse.TransformBounds(deviceClip);
+}
+
+static Rect
+ShrinkClippedStrokedRect(const Rect &aStrokedRect, const IntRect &aDeviceClip,
+                         const Matrix &aTransform,
+                         const StrokeOptions &aStrokeOptions)
+{
+  Rect userSpaceStrokeClip =
+    UserSpaceStrokeClip(aDeviceClip, aTransform, aStrokeOptions);
+
+  Rect intersection = aStrokedRect.Intersect(userSpaceStrokeClip);
+  Float dashPeriodLength = DashPeriodLength(aStrokeOptions);
+  if (intersection.IsEmpty() || dashPeriodLength == 0.0f) {
+    return intersection;
+  }
+
+  // Reduce the rectangle side lengths in multiples of the dash period length
+  // so that the visible dashes stay in the same place.
+  Margin insetBy = aStrokedRect - intersection;
+  insetBy.top = RoundDownToMultiple(insetBy.top, dashPeriodLength);
+  insetBy.right = RoundDownToMultiple(insetBy.right, dashPeriodLength);
+  insetBy.bottom = RoundDownToMultiple(insetBy.bottom, dashPeriodLength);
+  insetBy.left = RoundDownToMultiple(insetBy.left, dashPeriodLength);
+
+  Rect shrunkRect = aStrokedRect;
+  shrunkRect.Deflate(insetBy);
+  return shrunkRect;
+}
+
 void
 DrawTargetSkia::StrokeRect(const Rect &aRect,
                            const Pattern &aPattern,
                            const StrokeOptions &aStrokeOptions,
                            const DrawOptions &aOptions)
 {
+  // Stroking large rectangles with dashes is expensive with Skia (fixed
+  // overhead based on the number of dashes, regardless of whether the dashes
+  // are visible), so we try to reduce the size of the stroked rectangle as
+  // much as possible before passing it on to Skia.
+  Rect rect = aRect;
+  if (aStrokeOptions.mDashLength > 0 && !rect.IsEmpty()) {
+    IntRect deviceClip(IntPoint(0, 0), mSize);
+    SkIRect clipBounds;
+    if (mCanvas->getClipDeviceBounds(&clipBounds)) {
+      deviceClip = SkIRectToIntRect(clipBounds);
+    }
+    rect = ShrinkClippedStrokedRect(rect, deviceClip, mTransform, aStrokeOptions);
+    if (rect.IsEmpty()) {
+      return;
+    }
+  }
+
   MarkChanged();
   AutoPaintSetup paint(mCanvas.get(), aOptions, aPattern);
   if (!StrokeOptionsToPaint(paint.mPaint, aStrokeOptions)) {
     return;
   }
 
-  mCanvas->drawRect(RectToSkRect(aRect), paint.mPaint);
+  mCanvas->drawRect(RectToSkRect(rect), paint.mPaint);
 }
 
 void
 DrawTargetSkia::StrokeLine(const Point &aStart,
                            const Point &aEnd,
                            const Pattern &aPattern,
                            const StrokeOptions &aStrokeOptions,
                            const DrawOptions &aOptions)
--- a/gfx/2d/HelpersSkia.h
+++ b/gfx/2d/HelpersSkia.h
@@ -292,16 +292,22 @@ RectToSkIRect(const Rect& aRect)
 }
 
 static inline SkIRect
 IntRectToSkIRect(const IntRect& aRect)
 {
   return SkIRect::MakeXYWH(aRect.x, aRect.y, aRect.width, aRect.height);
 }
 
+static inline IntRect
+SkIRectToIntRect(const SkIRect& aRect)
+{
+  return IntRect(aRect.x(), aRect.y(), aRect.width(), aRect.height());
+}
+
 static inline Point
 SkPointToPoint(const SkPoint &aPoint)
 {
   return Point(SkScalarToFloat(aPoint.x()), SkScalarToFloat(aPoint.y()));
 }
 
 static inline Rect
 SkRectToRect(const SkRect &aRect)
--- a/gfx/2d/PathHelpers.cpp
+++ b/gfx/2d/PathHelpers.cpp
@@ -265,13 +265,19 @@ MaxStrokeExtents(const StrokeOptions& aS
       styleExpansionFactor < M_SQRT2 * aStrokeOptions.mMiterLimit) {
     styleExpansionFactor = M_SQRT2 * aStrokeOptions.mMiterLimit;
   }
 
   styleExpansionFactor *= aStrokeOptions.mLineWidth;
 
   double dx = styleExpansionFactor * hypot(aTransform._11, aTransform._21);
   double dy = styleExpansionFactor * hypot(aTransform._22, aTransform._12);
+
+  // Even if the stroke only partially covers a pixel, it must still render to
+  // full pixels. Round up to compensate for this.
+  dx = ceil(dx);
+  dy = ceil(dy);
+
   return Margin(dy, dx, dy, dx);
 }
 
 } // namespace gfx
 } // namespace mozilla
--- a/gfx/cairo/cairo/src/cairo-scaled-font.c
+++ b/gfx/cairo/cairo/src/cairo-scaled-font.c
@@ -1504,17 +1504,19 @@ cairo_scaled_font_glyph_extents (cairo_s
     for (i = 0; i < num_glyphs; i++) {
 	double			left, top, right, bottom;
 
 	status = _cairo_scaled_glyph_lookup (scaled_font,
 					     glyphs[i].index,
 					     CAIRO_SCALED_GLYPH_INFO_METRICS,
 					     &scaled_glyph);
 	if (unlikely (status)) {
-	    status = _cairo_scaled_font_set_error (scaled_font, status);
+	    if (status != CAIRO_INT_STATUS_UNSUPPORTED) {
+		status = _cairo_scaled_font_set_error (scaled_font, status);
+	    }
 	    goto UNLOCK;
 	}
 
 	/* "Ink" extents should skip "invisible" glyphs */
 	if (scaled_glyph->metrics.width == 0 || scaled_glyph->metrics.height == 0)
 	    continue;
 
 	left = scaled_glyph->metrics.x_bearing + glyphs[i].x;
--- a/gfx/config/gfxFeature.cpp
+++ b/gfx/config/gfxFeature.cpp
@@ -185,16 +185,19 @@ void
 FeatureState::DisableByDefault(FeatureStatus aStatus, const char* aMessage,
                                const nsACString& aFailureId)
 {
   // User/runtime decisions should not have been made yet.
   MOZ_ASSERT(!mUser.IsInitialized());
   MOZ_ASSERT(!mEnvironment.IsInitialized());
   MOZ_ASSERT(!mRuntime.IsInitialized());
 
+  // Make sure that when disabling we actually use a failure status.
+  MOZ_ASSERT(IsFeatureStatusFailure(aStatus));
+
   mDefault.Set(aStatus, aMessage);
   SetFailureId(aFailureId);
 }
 
 void
 FeatureState::SetUser(FeatureStatus aStatus, const char* aMessage)
 {
   // Default decision must have been made, but not runtime or environment.
--- a/gfx/layers/apz/util/APZEventState.cpp
+++ b/gfx/layers/apz/util/APZEventState.cpp
@@ -363,17 +363,17 @@ APZEventState::ProcessTouchEvent(const W
   default:
     NS_WARNING("Unknown touch event type");
   }
 
   if (sentContentResponse &&
         aApzResponse == nsEventStatus_eConsumeDoDefault &&
         gfxPrefs::PointerEventsEnabled()) {
     WidgetTouchEvent cancelEvent(aEvent);
-    cancelEvent.mMessage = eTouchCancel;
+    cancelEvent.mMessage = eTouchPointerCancel;
     cancelEvent.mFlags.mCancelable = false; // mMessage != eTouchCancel;
     for (uint32_t i = 0; i < cancelEvent.mTouches.Length(); ++i) {
       if (mozilla::dom::Touch* touch = cancelEvent.mTouches[i]) {
         touch->convertToPointer = true;
       }
     }
     nsEventStatus status;
     cancelEvent.mWidget->DispatchEvent(&cancelEvent, status);
new file mode 100644
--- /dev/null
+++ b/gfx/layers/composite/AnimationMetricsTracker.cpp
@@ -0,0 +1,63 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/layers/AnimationMetricsTracker.h"
+
+#include <algorithm>
+#include <inttypes.h>
+#include "mozilla/Telemetry.h"
+
+#define AMT_LOG(...)
+// #define AMT_LOG(...) printf_stderr("AMT: " __VA_ARGS__)
+
+namespace mozilla {
+namespace layers {
+
+AnimationMetricsTracker::AnimationMetricsTracker()
+{
+}
+
+AnimationMetricsTracker::~AnimationMetricsTracker()
+{
+}
+
+void
+AnimationMetricsTracker::UpdateAnimationInProgress(bool aInProgress,
+                                                   uint64_t aLayerArea)
+{
+  MOZ_ASSERT(aInProgress || aLayerArea == 0);
+  if (mCurrentAnimationStart && !aInProgress) {
+    AnimationEnded();
+    mCurrentAnimationStart = TimeStamp();
+    mMaxLayerAreaAnimated = 0;
+  } else if (aInProgress) {
+    if (!mCurrentAnimationStart) {
+      mCurrentAnimationStart = TimeStamp::Now();
+      mMaxLayerAreaAnimated = aLayerArea;
+      AnimationStarted();
+    } else {
+      mMaxLayerAreaAnimated = std::max(mMaxLayerAreaAnimated, aLayerArea);
+    }
+  }
+}
+
+void
+AnimationMetricsTracker::AnimationStarted()
+{
+}
+
+void
+AnimationMetricsTracker::AnimationEnded()
+{
+  MOZ_ASSERT(mCurrentAnimationStart);
+  Telemetry::AccumulateTimeDelta(Telemetry::COMPOSITOR_ANIMATION_DURATION, mCurrentAnimationStart);
+  Telemetry::Accumulate(Telemetry::COMPOSITOR_ANIMATION_MAX_LAYER_AREA, mMaxLayerAreaAnimated);
+  AMT_LOG("Ended animation; duration: %f ms, area: %" PRIu64 "\n",
+    (TimeStamp::Now() - mCurrentAnimationStart).ToMilliseconds(),
+    mMaxLayerAreaAnimated);
+}
+
+} // namespace layers
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/gfx/layers/composite/AnimationMetricsTracker.h
@@ -0,0 +1,40 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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_layers_AnimationMetricsTracker_h
+#define mozilla_layers_AnimationMetricsTracker_h
+
+#include "mozilla/TimeStamp.h"
+
+namespace mozilla {
+namespace layers {
+
+/**
+ * Tracks the start and end of compositor animations.
+ */
+class AnimationMetricsTracker {
+public:
+  AnimationMetricsTracker();
+  ~AnimationMetricsTracker();
+
+  /**
+   * This function should be called per composite, to inform the metrics
+   * tracker if any animation is in progress, and if so, what area is
+   * being animated. The aLayerArea is in Layer pixels squared.
+   */
+  void UpdateAnimationInProgress(bool aInProgress, uint64_t aLayerArea);
+
+private:
+  void AnimationStarted();
+  void AnimationEnded();
+
+  TimeStamp mCurrentAnimationStart;
+  uint64_t mMaxLayerAreaAnimated;
+};
+
+} // namespace layers
+} // namespace mozilla
+
+#endif // mozilla_layers_AnimationMetricsTracker_h
--- a/gfx/layers/composite/AsyncCompositionManager.cpp
+++ b/gfx/layers/composite/AsyncCompositionManager.cpp
@@ -629,38 +629,41 @@ ApplyAnimatedValue(Layer* aLayer,
       break;
     }
     default:
       MOZ_ASSERT_UNREACHABLE("Unhandled animated property");
   }
 }
 
 static bool
-SampleAnimations(Layer* aLayer, TimeStamp aPoint)
+SampleAnimations(Layer* aLayer, TimeStamp aPoint, uint64_t* aLayerAreaAnimated)
 {
   bool activeAnimations = false;
 
   ForEachNode<ForwardIterator>(
       aLayer,
-      [&activeAnimations, &aPoint] (Layer* layer)
+      [&activeAnimations, &aPoint, &aLayerAreaAnimated] (Layer* layer)
       {
         bool hasInEffectAnimations = false;
         StyleAnimationValue animationValue = layer->GetBaseAnimationStyle();
         activeAnimations |=
           AnimationHelper::SampleAnimationForEachNode(aPoint,
                                                       layer->GetAnimations(),
                                                       layer->GetAnimationData(),
                                                       animationValue,
                                                       hasInEffectAnimations);
         if (hasInEffectAnimations) {
           Animation& animation = layer->GetAnimations().LastElement();
           ApplyAnimatedValue(layer,
                              animation.property(),
                              animation.data(),
                              animationValue);
+          if (aLayerAreaAnimated) {
+            *aLayerAreaAnimated += (layer->GetVisibleRegion().Area());
+          }
         }
       });
   return activeAnimations;
 }
 
 static bool
 SampleAPZAnimations(const LayerMetricsWrapper& aLayer, TimeStamp aSampleTime)
 {
@@ -1306,19 +1309,23 @@ AsyncCompositionManager::TransformShadow
 
   // First, compute and set the shadow transforms from OMT animations.
   // NB: we must sample animations *before* sampling pan/zoom
   // transforms.
   // Use a previous vsync time to make main thread animations and compositor
   // more in sync with each other.
   // On the initial frame we use aVsyncTimestamp here so the timestamp on the
   // second frame are the same as the initial frame, but it does not matter.
+  uint64_t layerAreaAnimated = 0;
   bool wantNextFrame = SampleAnimations(root,
     !mPreviousFrameTimeStamp.IsNull() ?
-      mPreviousFrameTimeStamp : aCurrentFrame);
+      mPreviousFrameTimeStamp : aCurrentFrame,
+    &layerAreaAnimated);
+  mAnimationMetricsTracker.UpdateAnimationInProgress(
+    wantNextFrame, layerAreaAnimated);
 
   // Reset the previous time stamp if we don't already have any running
   // animations to avoid using the time which is far behind for newly
   // started animations.
   mPreviousFrameTimeStamp = wantNextFrame ? aCurrentFrame : TimeStamp();
 
   if (!(aSkip & TransformsToSkip::APZ)) {
     // FIXME/bug 775437: unify this interface with the ~native-fennec
--- a/gfx/layers/composite/AsyncCompositionManager.h
+++ b/gfx/layers/composite/AsyncCompositionManager.h
@@ -9,16 +9,17 @@
 #include "Units.h"                      // for ScreenPoint, etc
 #include "mozilla/layers/LayerManagerComposite.h"  // for LayerManagerComposite
 #include "mozilla/Attributes.h"         // for final, etc
 #include "mozilla/RefPtr.h"             // for RefCounted
 #include "mozilla/TimeStamp.h"          // for TimeStamp
 #include "mozilla/dom/ScreenOrientation.h"  // for ScreenOrientation
 #include "mozilla/gfx/BasePoint.h"      // for BasePoint
 #include "mozilla/gfx/Matrix.h"         // for Matrix4x4
+#include "mozilla/layers/AnimationMetricsTracker.h" // for AnimationMetricsTracker
 #include "mozilla/layers/FrameUniformityData.h" // For FrameUniformityData
 #include "mozilla/layers/LayersMessages.h"  // for TargetConfig
 #include "mozilla/RefPtr.h"                   // for nsRefPtr
 #include "nsISupportsImpl.h"            // for LayerManager::AddRef, etc
 
 namespace mozilla {
 namespace layers {
 
@@ -233,16 +234,17 @@ private:
   int32_t mPaintSyncId;
 
   bool mReadyForCompose;
 
   gfx::Matrix mWorldTransform;
   LayerTransformRecorder mLayerTransformRecorder;
 
   TimeStamp mPreviousFrameTimeStamp;
+  AnimationMetricsTracker mAnimationMetricsTracker;
 
 #ifdef MOZ_WIDGET_ANDROID
   // The following two fields are only needed on Fennec with C++ APZ, because
   // then we need to reposition the gecko scrollbar to deal with the
   // dynamic toolbar shifting content around.
   FrameMetrics::ViewID mRootScrollableId;
   ScreenMargin mFixedLayerMargins;
 #endif
--- a/gfx/layers/moz.build
+++ b/gfx/layers/moz.build
@@ -137,16 +137,17 @@ EXPORTS.mozilla.layers += [
     'client/GPUVideoTextureClient.h',
     'client/ImageClient.h',
     'client/SingleTiledContentClient.h',
     'client/TextureClient.h',
     'client/TextureClientPool.h',
     'client/TextureClientRecycleAllocator.h',
     'client/TextureClientSharedSurface.h',
     'client/TiledContentClient.h',
+    'composite/AnimationMetricsTracker.h',
     'composite/AsyncCompositionManager.h',
     'composite/CanvasLayerComposite.h',
     'composite/ColorLayerComposite.h',
     'composite/ContainerLayerComposite.h',
     'composite/ContentHost.h',
     'composite/FrameUniformityData.h',
     'composite/GPUVideoTextureHost.h',
     'composite/ImageComposite.h',
@@ -317,16 +318,17 @@ UNIFIED_SOURCES += [
     'client/GPUVideoTextureClient.cpp',
     'client/ImageClient.cpp',
     'client/SingleTiledContentClient.cpp',
     'client/TextureClient.cpp',
     'client/TextureClientPool.cpp',
     'client/TextureClientRecycleAllocator.cpp',
     'client/TextureClientSharedSurface.cpp',
     'client/TiledContentClient.cpp',
+    'composite/AnimationMetricsTracker.cpp',
     'composite/AsyncCompositionManager.cpp',
     'composite/CanvasLayerComposite.cpp',
     'composite/ColorLayerComposite.cpp',
     'composite/CompositableHost.cpp',
     'composite/ContainerLayerComposite.cpp',
     'composite/ContentHost.cpp',
     'composite/FPSCounter.cpp',
     'composite/FrameUniformityData.cpp',
--- a/gfx/src/gfxTelemetry.cpp
+++ b/gfx/src/gfxTelemetry.cpp
@@ -1,59 +1,61 @@
-/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
- *
- * 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 "gfxTelemetry.h"
-
-namespace mozilla {
-namespace gfx {
-
-const char*
-FeatureStatusToString(FeatureStatus aStatus)
-{
-  switch (aStatus) {
-    case FeatureStatus::Unused:
-      return "unused";
-    case FeatureStatus::Unavailable:
-      return "unavailable";
-    case FeatureStatus::CrashedInHandler:
-      return "crashed";
-    case FeatureStatus::Blocked:
-      return "blocked";
-    case FeatureStatus::Blacklisted:
-      return "blacklisted";
-    case FeatureStatus::Failed:
-      return "failed";
-    case FeatureStatus::Disabled:
-      return "disabled";
-    case FeatureStatus::Available:
-      return "available";
-    case FeatureStatus::ForceEnabled:
-      return "force_enabled";
-    case FeatureStatus::CrashedOnStartup:
-      return "crashed_on_startup";
-    case FeatureStatus::Broken:
-      return "broken";
-    default:
-      MOZ_ASSERT_UNREACHABLE("missing status case");
-      return "unknown";
-  }
-}
-
-bool
-IsFeatureStatusFailure(FeatureStatus aStatus)
-{
-  return !(aStatus == FeatureStatus::Unused ||
-           aStatus == FeatureStatus::Available ||
-           aStatus == FeatureStatus::ForceEnabled);
-}
-
-bool
-IsFeatureStatusSuccess(FeatureStatus aStatus)
-{
-  return aStatus == FeatureStatus::Available ||
-         aStatus == FeatureStatus::ForceEnabled;
-}
-
-} // namespace gfx
-} // namespace mozilla
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * 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 "gfxTelemetry.h"
+
+namespace mozilla {
+namespace gfx {
+
+const char*
+FeatureStatusToString(FeatureStatus aStatus)
+{
+  switch (aStatus) {
+    case FeatureStatus::Unused:
+      return "unused";
+    case FeatureStatus::Unavailable:
+      return "unavailable";
+    case FeatureStatus::CrashedInHandler:
+      return "crashed";
+    case FeatureStatus::Blocked:
+      return "blocked";
+    case FeatureStatus::Blacklisted:
+      return "blacklisted";
+    case FeatureStatus::OptIn:
+      return "opt-in";
+    case FeatureStatus::Failed:
+      return "failed";
+    case FeatureStatus::Disabled:
+      return "disabled";
+    case FeatureStatus::Available:
+      return "available";
+    case FeatureStatus::ForceEnabled:
+      return "force_enabled";
+    case FeatureStatus::CrashedOnStartup:
+      return "crashed_on_startup";
+    case FeatureStatus::Broken:
+      return "broken";
+    default:
+      MOZ_ASSERT_UNREACHABLE("missing status case");
+      return "unknown";
+  }
+}
+
+bool
+IsFeatureStatusFailure(FeatureStatus aStatus)
+{
+  return !(aStatus == FeatureStatus::Unused ||
+           aStatus == FeatureStatus::Available ||
+           aStatus == FeatureStatus::ForceEnabled);
+}
+
+bool
+IsFeatureStatusSuccess(FeatureStatus aStatus)
+{
+  return aStatus == FeatureStatus::Available ||
+         aStatus == FeatureStatus::ForceEnabled;
+}
+
+} // namespace gfx
+} // namespace mozilla
--- a/gfx/src/gfxTelemetry.h
+++ b/gfx/src/gfxTelemetry.h
@@ -1,70 +1,74 @@
-/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
- *
- * 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 gfx_src_gfxTelemetry_h__
-#define gfx_src_gfxTelemetry_h__
-
-namespace mozilla {
-namespace gfx {
-
-// Describes the status of a graphics feature, in terms of whether or not we've
-// attempted to initialize the feature, and if so, whether or not it succeeded
-// (and if not, why).
-enum class FeatureStatus
-{
-  // This feature has not been requested.
-  Unused,
-
-  // This feature is unavailable due to Safe Mode, not being included with
-  // the operating system, or a dependent feature being disabled.
-  Unavailable,
-
-  // This feature crashed immediately when we tried to initialize it, but we
-  // were able to recover via SEH (or something similar).
-  CrashedInHandler,
-
-  // This feature was blocked for reasons outside the blacklist, such as a
-  // runtime test failing.
-  Blocked,
-
-  // This feature has been blocked by the graphics blacklist.
-  Blacklisted,
-
-  // This feature was attempted but failed to activate.
-  Failed,
-
-  // This feature was explicitly disabled by the user.
-  Disabled,
-
-  // This feature is available for use.
-  Available,
-
-  // This feature was explicitly force-enabled by the user.
-  ForceEnabled,
-
-  // This feature was disabled due to the startup crash guard.
-  CrashedOnStartup,
-
-  // This feature was attempted but later determined to be broken.
-  Broken,
-
-  // Add new entries above here.
-  LAST
-};
-
-const char* FeatureStatusToString(FeatureStatus aStatus);
-bool IsFeatureStatusFailure(FeatureStatus aStatus);
-bool IsFeatureStatusSuccess(FeatureStatus aStatus);
-
-enum class TelemetryDeviceCode : uint32_t {
-  Content = 0,
-  Image = 1,
-  D2D1 = 2
-};
-
-} // namespace gfx
-} // namespace mozilla
-
-#endif // gfx_src_gfxTelemetry_h__
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * 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 gfx_src_gfxTelemetry_h__
+#define gfx_src_gfxTelemetry_h__
+
+namespace mozilla {
+namespace gfx {
+
+// Describes the status of a graphics feature, in terms of whether or not we've
+// attempted to initialize the feature, and if so, whether or not it succeeded
+// (and if not, why).
+enum class FeatureStatus
+{
+  // This feature has not been requested.
+  Unused,
+
+  // This feature is unavailable due to Safe Mode, not being included with
+  // the operating system, or a dependent feature being disabled.
+  Unavailable,
+
+  // This feature crashed immediately when we tried to initialize it, but we
+  // were able to recover via SEH (or something similar).
+  CrashedInHandler,
+
+  // This feature was blocked for reasons outside the blacklist, such as a
+  // runtime test failing.
+  Blocked,
+
+  // This feature has been blocked by the graphics blacklist.
+  Blacklisted,
+
+  // This feature is disabled by default, and so activation isn't attempted
+  // unless something explicitly enables it.
+  OptIn,
+
+  // This feature was attempted but failed to activate.
+  Failed,
+
+  // This feature was explicitly disabled by the user.
+  Disabled,
+
+  // This feature is available for use.
+  Available,
+
+  // This feature was explicitly force-enabled by the user.
+  ForceEnabled,
+
+  // This feature was disabled due to the startup crash guard.
+  CrashedOnStartup,
+
+  // This feature was attempted but later determined to be broken.
+  Broken,
+
+  // Add new entries above here.
+  LAST
+};
+
+const char* FeatureStatusToString(FeatureStatus aStatus);
+bool IsFeatureStatusFailure(FeatureStatus aStatus);
+bool IsFeatureStatusSuccess(FeatureStatus aStatus);
+
+enum class TelemetryDeviceCode : uint32_t {
+  Content = 0,
+  Image = 1,
+  D2D1 = 2
+};
+
+} // namespace gfx
+} // namespace mozilla
+
+#endif // gfx_src_gfxTelemetry_h__
--- a/gfx/thebes/gfxWindowsPlatform.cpp
+++ b/gfx/thebes/gfxWindowsPlatform.cpp
@@ -707,18 +707,25 @@ gfxWindowsPlatform::GetCommonFallbackFon
             aFontList.AppendElement(kFontMVBoli);
             aFontList.AppendElement(kFontEbrima);
             break;
         case 0x09:
             aFontList.AppendElement(kFontNirmalaUI);
             aFontList.AppendElement(kFontUtsaah);
             aFontList.AppendElement(kFontAparajita);
             break;
+        case 0x0a:
+        case 0x0b:
+        case 0x0c:
+        case 0x0d:
+            aFontList.AppendElement(kFontNirmalaUI);
+            break;
         case 0x0e:
             aFontList.AppendElement(kFontLaoUI);
+            aFontList.AppendElement(kFontLeelawadeeUI);
             break;
         case 0x10:
             aFontList.AppendElement(kFontMyanmarText);
             break;
         case 0x11:
             aFontList.AppendElement(kFontMalgunGothic);
             break;
         case 0x12:
@@ -729,26 +736,27 @@ gfxWindowsPlatform::GetCommonFallbackFon
         case 0x14:
         case 0x15:
         case 0x16:
             aFontList.AppendElement(kFontEuphemia);
             aFontList.AppendElement(kFontSegoeUISymbol);
             break;
         case 0x17:
             aFontList.AppendElement(kFontKhmerUI);
+            aFontList.AppendElement(kFontLeelawadeeUI);
             break;
         case 0x18:  // Mongolian
             aFontList.AppendElement(kFontMongolianBaiti);
             aFontList.AppendElement(kFontEuphemia);
             break;
         case 0x19:
             aFontList.AppendElement(kFontMicrosoftTaiLe);
             aFontList.AppendElement(kFontMicrosoftNewTaiLue);
             aFontList.AppendElement(kFontKhmerUI);
-            break;
+            aFontList.AppendElement(kFontLeelawadeeUI);
             break;
         case 0x1a:
             aFontList.AppendElement(kFontLeelawadeeUI);
             break;
         case 0x1c:
             aFontList.AppendElement(kFontNirmalaUI);
             break;
         case 0x20:  // Symbol ranges
@@ -814,16 +822,17 @@ gfxWindowsPlatform::GetCommonFallbackFon
             break;
         case 0xa8:
              aFontList.AppendElement(kFontMicrosoftPhagsPa);
              aFontList.AppendElement(kFontNirmalaUI);
              break;
         case 0xa9:
              aFontList.AppendElement(kFontMalgunGothic);
              aFontList.AppendElement(kFontJavaneseText);
+             aFontList.AppendElement(kFontLeelawadeeUI);
              break;
         case 0xaa:
              aFontList.AppendElement(kFontMyanmarText);
              break;
         case 0xab:
              aFontList.AppendElement(kFontEbrima);
              aFontList.AppendElement(kFontNyala);
              break;
--- a/ipc/mscom/WeakRef.h
+++ b/ipc/mscom/WeakRef.h
@@ -100,30 +100,32 @@ protected:
 
 private:
   RefPtr<detail::SharedRef> mSharedRef;
   ULONG                     mRefCnt;
   Flags                     mFlags;
   CRITICAL_SECTION          mCSForQI;
 };
 
-class WeakRef : public IWeakReference
+class WeakRef final : public IWeakReference
 {
 public:
   // IUnknown
   STDMETHODIMP QueryInterface(REFIID riid, void** ppv) override;
   STDMETHODIMP_(ULONG) AddRef() override;
   STDMETHODIMP_(ULONG) Release() override;
 
   // IWeakReference
   STDMETHODIMP Resolve(REFIID aIid, void** aOutStrongReference) override;
 
   explicit WeakRef(RefPtr<detail::SharedRef>& aSharedRef);
 
 private:
+  ~WeakRef() = default;
+
   Atomic<ULONG>             mRefCnt;
   RefPtr<detail::SharedRef> mSharedRef;
 };
 
 } // namespace mscom
 } // namespace mozilla
 
 #endif // mozilla_mscom_WeakRef_h
--- a/js/src/builtin/Intl.cpp
+++ b/js/src/builtin/Intl.cpp
@@ -650,16 +650,17 @@ enum UDateFormatField {
 };
 
 enum UDateFormatStyle {
     UDAT_FULL,
     UDAT_LONG,
     UDAT_MEDIUM,
     UDAT_SHORT,
     UDAT_DEFAULT = UDAT_MEDIUM,
+    UDAT_NONE = -1,
     UDAT_PATTERN = -2,
     UDAT_IGNORE = UDAT_PATTERN
 };
 
 enum UDateFormatSymbolType {
     UDAT_ERAS,
     UDAT_MONTHS,
     UDAT_SHORT_MONTHS,
@@ -691,16 +692,23 @@ enum UDateFormatSymbolType {
 };
 
 int32_t
 udat_countAvailable()
 {
     MOZ_CRASH("udat_countAvailable: Intl API disabled");
 }
 
+int32_t
+udat_toPattern(const UDateFormat* fmt, UBool localized, UChar* result,
+               int32_t resultLength, UErrorCode* status)
+{
+    MOZ_CRASH("udat_toPattern: Intl API disabled");
+}
+
 const char*
 udat_getAvailable(int32_t localeIndex)
 {
     MOZ_CRASH("udat_getAvailable: Intl API disabled");
 }
 
 UDateFormat*
 udat_open(UDateFormatStyle timeStyle, UDateFormatStyle dateStyle, const char* locale,
@@ -816,27 +824,34 @@ IntlInitialize(JSContext* cx, HandleObje
     if (!js::CallSelfHostedFunction(cx, initializer, thisv, args, &ignored))
         return false;
 
     MOZ_ASSERT(ignored.isUndefined(),
                "Unexpected return value from non-legacy Intl object initializer");
     return true;
 }
 
+enum class DateTimeFormatOptions
+{
+    Standard,
+    EnableMozExtensions,
+};
+
 static bool
 LegacyIntlInitialize(JSContext* cx, HandleObject obj, Handle<PropertyName*> initializer,
                      HandleValue thisValue, HandleValue locales, HandleValue options,
-                     MutableHandleValue result)
+                     DateTimeFormatOptions dtfOptions, MutableHandleValue result)
 {
-    FixedInvokeArgs<4> args(cx);
+    FixedInvokeArgs<5> args(cx);
 
     args[0].setObject(*obj);
     args[1].set(thisValue);
     args[2].set(locales);
     args[3].set(options);
+    args[4].setBoolean(dtfOptions == DateTimeFormatOptions::EnableMozExtensions);
 
     RootedValue thisv(cx, NullValue());
     if (!js::CallSelfHostedFunction(cx, initializer, thisv, args, result))
         return false;
 
     MOZ_ASSERT(result.isObject(), "Legacy Intl object initializer must return an object");
     return true;
 }
@@ -1486,17 +1501,17 @@ NumberFormat(JSContext* cx, const CallAr
     numberFormat->setReservedSlot(NumberFormatObject::UNUMBER_FORMAT_SLOT, PrivateValue(nullptr));
 
     RootedValue thisValue(cx, construct ? ObjectValue(*numberFormat) : args.thisv());
     RootedValue locales(cx, args.get(0));
     RootedValue options(cx, args.get(1));
 
     // Step 3.
     return LegacyIntlInitialize(cx, numberFormat, cx->names().InitializeNumberFormat, thisValue,
-                                locales, options, args.rval());
+                                locales, options, DateTimeFormatOptions::Standard, args.rval());
 }
 
 static bool
 NumberFormat(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
     return NumberFormat(cx, args, args.isConstructing());
 }
@@ -2351,17 +2366,17 @@ static const JSPropertySpec dateTimeForm
 };
 
 /**
  * 12.2.1 Intl.DateTimeFormat([ locales [, options]])
  *
  * ES2017 Intl draft rev 94045d234762ad107a3d09bb6f7381a65f1a2f9b
  */
 static bool
-DateTimeFormat(JSContext* cx, const CallArgs& args, bool construct)
+DateTimeFormat(JSContext* cx, const CallArgs& args, bool construct, DateTimeFormatOptions dtfOptions)
 {
     // Step 1 (Handled by OrdinaryCreateFromConstructor fallback code).
 
     // Step 2 (Inlined 9.1.14, OrdinaryCreateFromConstructor).
     RootedObject proto(cx);
     if (args.isConstructing() && !GetPrototypeFromCallableConstructor(cx, args, &proto))
         return false;
 
@@ -2381,55 +2396,71 @@ DateTimeFormat(JSContext* cx, const Call
                                     PrivateValue(nullptr));
 
     RootedValue thisValue(cx, construct ? ObjectValue(*dateTimeFormat) : args.thisv());
     RootedValue locales(cx, args.get(0));
     RootedValue options(cx, args.get(1));
 
     // Step 3.
     return LegacyIntlInitialize(cx, dateTimeFormat, cx->names().InitializeDateTimeFormat,
-                                thisValue, locales, options, args.rval());
+                                thisValue, locales, options, dtfOptions, args.rval());
 }
 
 static bool
 DateTimeFormat(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
-    return DateTimeFormat(cx, args, args.isConstructing());
+    return DateTimeFormat(cx, args, args.isConstructing(), DateTimeFormatOptions::Standard);
+}
+
+static bool
+MozDateTimeFormat(JSContext* cx, unsigned argc, Value* vp)
+{
+    CallArgs args = CallArgsFromVp(argc, vp);
+
+    // Don't allow to call mozIntl.DateTimeFormat as a function. That way we
+    // don't need to worry how to handle the legacy initialization semantics
+    // when applied on mozIntl.DateTimeFormat.
+    if (!ThrowIfNotConstructing(cx, args, "mozIntl.DateTimeFormat"))
+        return false;
+
+    return DateTimeFormat(cx, args, true, DateTimeFormatOptions::EnableMozExtensions);
 }
 
 bool
 js::intl_DateTimeFormat(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
     MOZ_ASSERT(args.length() == 2);
     MOZ_ASSERT(!args.isConstructing());
     // intl_DateTimeFormat is an intrinsic for self-hosted JavaScript, so it
     // cannot be used with "new", but it still has to be treated as a
     // constructor.
-    return DateTimeFormat(cx, args, true);
+    return DateTimeFormat(cx, args, true, DateTimeFormatOptions::Standard);
 }
 
 void
 DateTimeFormatObject::finalize(FreeOp* fop, JSObject* obj)
 {
     MOZ_ASSERT(fop->onActiveCooperatingThread());
 
     const Value& slot =
         obj->as<DateTimeFormatObject>().getReservedSlot(DateTimeFormatObject::UDATE_FORMAT_SLOT);
     if (UDateFormat* df = static_cast<UDateFormat*>(slot.toPrivate()))
         udat_close(df);
 }
 
 static JSObject*
 CreateDateTimeFormatPrototype(JSContext* cx, HandleObject Intl, Handle<GlobalObject*> global,
-                              MutableHandleObject constructor)
+                              MutableHandleObject constructor, DateTimeFormatOptions dtfOptions)
 {
     RootedFunction ctor(cx);
-    ctor = GlobalObject::createConstructor(cx, &DateTimeFormat, cx->names().DateTimeFormat, 0);
+    ctor = dtfOptions == DateTimeFormatOptions::EnableMozExtensions
+           ? GlobalObject::createConstructor(cx, MozDateTimeFormat, cx->names().DateTimeFormat, 0)
+           : GlobalObject::createConstructor(cx, DateTimeFormat, cx->names().DateTimeFormat, 0);
     if (!ctor)
         return nullptr;
 
     RootedObject proto(cx, GlobalObject::createBlankPrototype<PlainObject>(cx, global));
     if (!proto)
         return nullptr;
 
     if (!LinkConstructorAndPrototype(cx, ctor, proto))
@@ -2452,16 +2483,27 @@ CreateDateTimeFormatPrototype(JSContext*
     if (!DefineProperty(cx, Intl, cx->names().DateTimeFormat, ctorValue, nullptr, nullptr, 0))
         return nullptr;
 
     constructor.set(ctor);
     return proto;
 }
 
 bool
+js::AddMozDateTimeFormatConstructor(JSContext* cx, JS::Handle<JSObject*> intl)
+{
+    Handle<GlobalObject*> global = cx->global();
+
+    RootedObject mozDateTimeFormat(cx);
+    JSObject* mozDateTimeFormatProto =
+        CreateDateTimeFormatPrototype(cx, intl, global, &mozDateTimeFormat, DateTimeFormatOptions::EnableMozExtensions);
+    return mozDateTimeFormatProto != nullptr;
+}
+
+bool
 js::intl_DateTimeFormat_availableLocales(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
     MOZ_ASSERT(args.length() == 0);
 
     RootedValue result(cx);
     if (!intl_availableLocales(cx, udat_countAvailable, udat_getAvailable, &result))
         return false;
@@ -2969,16 +3011,89 @@ js::intl_patternForSkeleton(JSContext* c
     });
     if (!str)
         return false;
 
     args.rval().setString(str);
     return true;
 }
 
+bool
+js::intl_patternForStyle(JSContext* cx, unsigned argc, Value* vp)
+{
+    CallArgs args = CallArgsFromVp(argc, vp);
+    MOZ_ASSERT(args.length() == 4);
+    MOZ_ASSERT(args[0].isString());
+
+    JSAutoByteString locale(cx, args[0].toString());
+    if (!locale)
+        return false;
+
+    UDateFormatStyle dateStyle = UDAT_NONE;
+    UDateFormatStyle timeStyle = UDAT_NONE;
+
+    if (args[1].isString()) {
+        JSLinearString* dateStyleStr = args[1].toString()->ensureLinear(cx);
+        if (!dateStyleStr)
+            return false;
+
+        if (StringEqualsAscii(dateStyleStr, "full"))
+            dateStyle = UDAT_FULL;
+        else if (StringEqualsAscii(dateStyleStr, "long"))
+            dateStyle = UDAT_LONG;
+        else if (StringEqualsAscii(dateStyleStr, "medium"))
+            dateStyle = UDAT_MEDIUM;
+        else if (StringEqualsAscii(dateStyleStr, "short"))
+            dateStyle = UDAT_SHORT;
+        else
+            MOZ_ASSERT_UNREACHABLE("unexpected dateStyle");
+    }
+
+    if (args[2].isString()) {
+        JSLinearString* timeStyleStr = args[2].toString()->ensureLinear(cx);
+        if (!timeStyleStr)
+            return false;
+
+        if (StringEqualsAscii(timeStyleStr, "full"))
+            timeStyle = UDAT_FULL;
+        else if (StringEqualsAscii(timeStyleStr, "long"))
+            timeStyle = UDAT_LONG;
+        else if (StringEqualsAscii(timeStyleStr, "medium"))
+            timeStyle = UDAT_MEDIUM;
+        else if (StringEqualsAscii(timeStyleStr, "short"))
+            timeStyle = UDAT_SHORT;
+        else
+            MOZ_ASSERT_UNREACHABLE("unexpected timeStyle");
+    }
+
+    AutoStableStringChars timeZone(cx);
+    if (!timeZone.initTwoByte(cx, args[3].toString()))
+        return false;
+
+    mozilla::Range<const char16_t> timeZoneChars = timeZone.twoByteRange();
+
+    UErrorCode status = U_ZERO_ERROR;
+    UDateFormat* df = udat_open(timeStyle, dateStyle, icuLocale(locale.ptr()),
+                                Char16ToUChar(timeZoneChars.begin().get()),
+                                timeZoneChars.length(), nullptr, -1, &status);
+    if (U_FAILURE(status)) {
+        JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR);
+        return false;
+    }
+    ScopedICUObject<UDateFormat, udat_close> toClose(df);
+
+    JSString* str = Call(cx, [df](UChar* chars, uint32_t size, UErrorCode* status) {
+        return udat_toPattern(df, false, chars, size, status);
+    });
+    if (!str)
+        return false;
+    args.rval().setString(str);
+    return true;
+}
+
 /**
  * Returns a new UDateFormat with the locale and date-time formatting options
  * of the given DateTimeFormat.
  */
 static UDateFormat*
 NewUDateFormat(JSContext* cx, Handle<DateTimeFormatObject*> dateTimeFormat)
 {
     RootedValue value(cx);
@@ -4111,17 +4226,17 @@ GlobalObject::initIntlObject(JSContext* 
         return false;
 
     // Add the constructor properties, computing and returning the relevant
     // prototype objects needed below.
     RootedObject collatorProto(cx, CreateCollatorPrototype(cx, intl, global));
     if (!collatorProto)
         return false;
     RootedObject dateTimeFormatProto(cx), dateTimeFormat(cx);
-    dateTimeFormatProto = CreateDateTimeFormatPrototype(cx, intl, global, &dateTimeFormat);
+    dateTimeFormatProto = CreateDateTimeFormatPrototype(cx, intl, global, &dateTimeFormat, DateTimeFormatOptions::Standard);
     if (!dateTimeFormatProto)
         return false;
     RootedObject numberFormatProto(cx), numberFormat(cx);
     numberFormatProto = CreateNumberFormatPrototype(cx, intl, global, &numberFormat);
     if (!numberFormatProto)
         return false;
 
     // The |Intl| object is fully set up now, so define the global property.
--- a/js/src/builtin/Intl.h
+++ b/js/src/builtin/Intl.h
@@ -407,16 +407,45 @@ intl_defaultTimeZoneOffset(JSContext* cx
  * given locale.
  *
  * Usage: pattern = intl_patternForSkeleton(locale, skeleton)
  */
 extern MOZ_MUST_USE bool
 intl_patternForSkeleton(JSContext* cx, unsigned argc, Value* vp);
 
 /**
+ * Return a pattern in the date-time format pattern language of Unicode
+ * Technical Standard 35, Unicode Locale Data Markup Language, for the
+ * best-fit date-time style for the given locale.
+ * The function takes four arguments:
+ *
+ *   locale
+ *     BCP47 compliant locale string
+ *   dateStyle
+ *     A string with values: full or long or medium or short, or `undefined`
+ *   timeStyle
+ *     A string with values: full or long or medium or short, or `undefined`
+ *   timeZone
+ *     IANA time zone name
+ *
+ * Date and time style categories map to CLDR time/date standard
+ * format patterns.
+ *
+ * For the definition of a pattern string, see LDML 4.8:
+ * http://unicode.org/reports/tr35/tr35-dates.html#Date_Format_Patterns
+ *
+ * If `undefined` is passed to `dateStyle` or `timeStyle`, the respective
+ * portions of the pattern will not be included in the result.
+ *
+ * Usage: pattern = intl_patternForStyle(locale, dateStyle, timeStyle, timeZone)
+ */
+extern MOZ_MUST_USE bool
+intl_patternForStyle(JSContext* cx, unsigned argc, Value* vp);
+
+/**
  * Returns a String value representing x (which must be a Number value)
  * according to the effective locale and the formatting options of the
  * given DateTimeFormat.
  *
  * Spec: ECMAScript Internationalization API Specification, 12.3.2.
  *
  * Usage: formatted = intl_FormatDateTime(dateTimeFormat, x, formatToParts)
  */
--- a/js/src/builtin/Intl.js
+++ b/js/src/builtin/Intl.js
@@ -2192,16 +2192,28 @@ function resolveDateTimeFormatInternals(
     //
     //     formatOpt: // *second* opt computed in InitializeDateTimeFormat
     //       {
     //         // all the properties/values listed in Table 3
     //         // (weekday, era, year, month, day, &c.)
     //       }
     //
     //     formatMatcher: "basic" / "best fit",
+    //
+    //     mozExtensions: true / false,
+    //
+    //
+    //     // If mozExtensions is true:
+    //
+    //     dateStyle: "full" / "long" / "medium" / "short" / undefined,
+    //
+    //     timeStyle: "full" / "long" / "medium" / "short" / undefined,
+    //
+    //     patternOption:
+    //       String representing LDML Date Format pattern or undefined
     //   }
     //
     // Note that lazy data is only installed as a final step of initialization,
     // so every DateTimeFormat lazy data object has *all* these properties,
     // never a subset of them.
 
     var internalProps = std_Object_create(null);
 
@@ -2230,17 +2242,36 @@ function resolveDateTimeFormatInternals(
 
     // Steps 15-17.
     internalProps.timeZone = lazyDateTimeFormatData.timeZone;
 
     // Step 18.
     var formatOpt = lazyDateTimeFormatData.formatOpt;
 
     // Steps 27-28, more or less - see comment after this function.
-    var pattern = toBestICUPattern(dataLocale, formatOpt);
+    var pattern;
+    if (lazyDateTimeFormatData.mozExtensions) {
+        if (lazyDateTimeFormatData.patternOption !== undefined) {
+            pattern = lazyDateTimeFormatData.patternOption;
+
+            internalProps.patternOption = lazyDateTimeFormatData.patternOption;
+        } else if (lazyDateTimeFormatData.dateStyle || lazyDateTimeFormatData.timeStyle) {
+            pattern = intl_patternForStyle(dataLocale,
+              lazyDateTimeFormatData.dateStyle, lazyDateTimeFormatData.timeStyle,
+              lazyDateTimeFormatData.timeZone);
+
+            internalProps.dateStyle = lazyDateTimeFormatData.dateStyle;
+            internalProps.timeStyle = lazyDateTimeFormatData.timeStyle;
+        } else {
+            pattern = toBestICUPattern(dataLocale, formatOpt);
+        }
+        internalProps.mozExtensions = true;
+    } else {
+      pattern = toBestICUPattern(dataLocale, formatOpt);
+    }
 
     // Step 29.
     internalProps.pattern = pattern;
 
     // Step 30.
     internalProps.boundFormat = undefined;
 
     // The caller is responsible for associating |internalProps| with the right
@@ -2296,17 +2327,17 @@ function UnwrapDateTimeFormat(dtf, metho
  * This method is complicated a moderate bit by its implementing initialization
  * as a *lazy* concept.  Everything that must happen now, does -- but we defer
  * all the work we can until the object is actually used as a DateTimeFormat.
  * This later work occurs in |resolveDateTimeFormatInternals|; steps not noted
  * here occur there.
  *
  * Spec: ECMAScript Internationalization API Specification, 12.1.1.
  */
-function InitializeDateTimeFormat(dateTimeFormat, thisValue, locales, options) {
+function InitializeDateTimeFormat(dateTimeFormat, thisValue, locales, options, mozExtensions) {
     assert(IsObject(dateTimeFormat), "InitializeDateTimeFormat called with non-Object");
     assert(IsDateTimeFormat(dateTimeFormat),
            "InitializeDateTimeFormat called with non-DateTimeFormat");
 
     // Steps 1-2 (These steps are no longer required and should be removed
     // from the spec; https://github.com/tc39/ecma402/issues/115).
 
     // Lazy DateTimeFormat data has the following structure:
@@ -2373,16 +2404,28 @@ function InitializeDateTimeFormat(dateTi
         tz = DefaultTimeZone();
     }
     lazyDateTimeFormatData.timeZone = tz;
 
     // Step 18.
     var formatOpt = new Record();
     lazyDateTimeFormatData.formatOpt = formatOpt;
 
+    lazyDateTimeFormatData.mozExtensions = mozExtensions;
+
+    if (mozExtensions) {
+        let pattern = GetOption(options, "pattern", "string", undefined, undefined);
+        lazyDateTimeFormatData.patternOption = pattern;
+
+        let dateStyle = GetOption(options, "dateStyle", "string", ["full", "long", "medium", "short"], undefined);
+        lazyDateTimeFormatData.dateStyle = dateStyle;
+        let timeStyle = GetOption(options, "timeStyle", "string", ["full", "long", "medium", "short"], undefined);
+        lazyDateTimeFormatData.timeStyle = timeStyle;
+    }
+
     // Step 19.
     // 12.1, Table 4: Components of date and time formats.
     formatOpt.weekday = GetOption(options, "weekday", "string", ["narrow", "short", "long"],
                                   undefined);
     formatOpt.era = GetOption(options, "era", "string", ["narrow", "short", "long"], undefined);
     formatOpt.year = GetOption(options, "year", "string", ["2-digit", "numeric"], undefined);
     formatOpt.month = GetOption(options, "month", "string",
                                 ["2-digit", "numeric", "narrow", "short", "long"], undefined);
@@ -2788,18 +2831,28 @@ function Intl_DateTimeFormat_resolvedOpt
     var dtf = UnwrapDateTimeFormat(this, "resolvedOptions");
 
     var internals = getDateTimeFormatInternals(dtf);
 
     var result = {
         locale: internals.locale,
         calendar: internals.calendar,
         numberingSystem: internals.numberingSystem,
-        timeZone: internals.timeZone
+        timeZone: internals.timeZone,
     };
+
+    if (internals.mozExtensions) {
+        if (internals.patternOption !== undefined) {
+            result.pattern = internals.pattern;
+        } else if (internals.dateStyle || internals.timeStyle) {
+            result.dateStyle = internals.dateStyle;
+            result.timeStyle = internals.timeStyle;
+        }
+    }
+
     resolveICUPattern(internals.pattern, result);
     return result;
 }
 
 
 // Table mapping ICU pattern characters back to the corresponding date-time
 // components of DateTimeFormat. See
 // http://unicode.org/reports/tr35/tr35-dates.html#Date_Field_Symbol_Table
--- a/js/src/builtin/TypedArray.js
+++ b/js/src/builtin/TypedArray.js
@@ -980,17 +980,17 @@ function TypedArraySlice(start, end) {
     // Step 1.
     var O = this;
 
     // Step 2.
     if (!IsObject(O) || !IsTypedArray(O)) {
         return callFunction(CallTypedArrayMethodIfWrapped, O, start, end, "TypedArraySlice");
     }
 
-    GetAttachedArrayBuffer(O);
+    var buffer = GetAttachedArrayBuffer(O);
 
     // Step 3.
     var len = TypedArrayLength(O);
 
     // Step 4.
     var relativeStart = ToInteger(start);
 
     // Step 5.
@@ -1007,27 +1007,42 @@ function TypedArraySlice(start, end) {
                 : std_Math_min(relativeEnd, len);
 
     // Step 8.
     var count = std_Math_max(final - k, 0);
 
     // Step 9.
     var A = TypedArraySpeciesCreateWithLength(O, count);
 
-    // Step 14.a.
-    var n = 0;
+    // Steps 10-13 (Not implemented, bug 1140152).
+
+    // Steps 14-15.
+    if (count > 0) {
+        // Step 14.a.
+        var n = 0;
 
-    // Step 14.b.
-    while (k < final) {
-        // Steps 14.b.i-v.
-        A[n++] = O[k++];
+        // Steps 14.b.ii, 15.b.
+        if (buffer === null) {
+            // A typed array previously using inline storage may acquire a
+            // buffer, so we must check with the source.
+            buffer = ViewedArrayBufferIfReified(O);
+        }
+
+        if (IsDetachedBuffer(buffer))
+            ThrowTypeError(JSMSG_TYPED_ARRAY_DETACHED);
+
+        // Step 14.b.
+        while (k < final) {
+            // Steps 14.b.i-v.
+            A[n++] = O[k++];
+        }
+
+        // FIXME: Implement step 15 (bug 1140152).
     }
 
-    // FIXME: Implement step 15 (bug 1140152).
-
     // Step 16.
     return A;
 }
 
 // ES6 draft rev30 (2014/12/24) 22.2.3.25 %TypedArray%.prototype.some(callbackfn[, thisArg]).
 function TypedArraySome(callbackfn/*, thisArg*/) {
     // Steps 1-2.
     var O = this;
--- a/js/src/devtools/automation/autospider.py
+++ b/js/src/devtools/automation/autospider.py
@@ -41,19 +41,23 @@ parser.add_argument('--dep', action='sto
 parser.add_argument('--platform', '-p', type=str, metavar='PLATFORM',
                     default='', help='build platform')
 parser.add_argument('--timeout', '-t', type=int, metavar='TIMEOUT',
                     default=10800,
                     help='kill job after TIMEOUT seconds')
 parser.add_argument('--objdir', type=str, metavar='DIR',
                     default=env.get('OBJDIR', 'obj-spider'),
                     help='object directory')
-parser.add_argument('--optimize', type=bool, metavar='OPT',
-                    default=None,
-                    help='whether to generate an optimized build. Overrides variant setting.')
+group = parser.add_mutually_exclusive_group()
+group.add_argument('--optimize', action='store_true',
+                   help='generate an optimized build. Overrides variant setting.')
+group.add_argument('--no-optimize', action='store_false',
+                   dest='optimize',
+                   help='generate a non-optimized build. Overrides variant setting.')
+group.set_defaults(optimize=None)
 parser.add_argument('--run-tests', '--tests', type=str, metavar='TESTSUITE',
                     default='',
                     help="comma-separated set of test suites to add to the variant's default set")
 parser.add_argument('--skip-tests', '--skip', type=str, metavar='TESTSUITE',
                     default='',
                     help="comma-separated set of test suites to remove from the variant's default set")
 parser.add_argument('--build-only', '--build',
                     dest='skip_tests', action='store_const', const='all',
--- a/js/src/gc/Nursery.cpp
+++ b/js/src/gc/Nursery.cpp
@@ -139,19 +139,16 @@ js::Nursery::init(uint32_t maxNurseryByt
 
     /* If no chunks are specified then the nursery is permanently disabled. */
     if (maxNurseryChunks_ == 0)
         return true;
 
     if (!mallocedBuffers.init())
         return false;
 
-    if (!cellsWithUid_.init())
-        return false;
-
     freeMallocedBuffersTask = js_new<FreeMallocedBuffersTask>(runtime()->defaultFreeOp());
     if (!freeMallocedBuffersTask || !freeMallocedBuffersTask->init())
         return false;
 
     AutoMaybeStartBackgroundAllocation maybeBgAlloc;
     updateNumChunksLocked(1, maybeBgAlloc, lock);
     if (numChunks() == 0)
         return false;
@@ -817,18 +814,18 @@ js::Nursery::waitBackgroundFreeEnd()
     MOZ_ASSERT(freeMallocedBuffersTask);
     freeMallocedBuffersTask->join();
 }
 
 void
 js::Nursery::sweep()
 {
     /* Sweep unique id's in all in-use chunks. */
-    for (CellsWithUniqueIdSet::Enum e(cellsWithUid_); !e.empty(); e.popFront()) {
-        JSObject* obj = static_cast<JSObject*>(e.front());
+    for (Cell* cell : cellsWithUid_) {
+        JSObject* obj = static_cast<JSObject*>(cell);
         if (!IsForwarded(obj))
             obj->zone()->removeUniqueId(obj);
         else
             MOZ_ASSERT(Forwarded(obj)->zone()->hasUniqueId(Forwarded(obj)));
     }
     cellsWithUid_.clear();
 
     runSweepActions();
--- a/js/src/gc/Nursery.h
+++ b/js/src/gc/Nursery.h
@@ -218,19 +218,17 @@ class Nursery
         mallocedBuffers.remove(buffer);
     }
 
     void waitBackgroundFreeEnd();
 
     MOZ_MUST_USE bool addedUniqueIdToCell(gc::Cell* cell) {
         MOZ_ASSERT(IsInsideNursery(cell));
         MOZ_ASSERT(isEnabled());
-        MOZ_ASSERT(cellsWithUid_.initialized());
-        MOZ_ASSERT(!cellsWithUid_.has(cell));
-        return cellsWithUid_.put(cell);
+        return cellsWithUid_.append(cell);
     }
 
     using SweepThunk = void (*)(void *data);
     void queueSweepAction(SweepThunk thunk, void* data);
 
     MOZ_MUST_USE bool queueDictionaryModeObjectToSweep(NativeObject* obj);
 
     size_t sizeOfHeapCommitted() const {
@@ -383,18 +381,18 @@ class Nursery
      * tenured. It is possible, if rare, for an object that acquired a uid to
      * be dead before the next collection, in which case we need to know to
      * remove it when we sweep.
      *
      * Note: we store the pointers as Cell* here, resulting in an ugly cast in
      *       sweep. This is because this structure is used to help implement
      *       stable object hashing and we have to break the cycle somehow.
      */
-    using CellsWithUniqueIdSet = HashSet<gc::Cell*, PointerHasher<gc::Cell*, 3>, SystemAllocPolicy>;
-    CellsWithUniqueIdSet cellsWithUid_;
+    using CellsWithUniqueIdVector = Vector<gc::Cell*, 8, SystemAllocPolicy>;
+    CellsWithUniqueIdVector cellsWithUid_;
 
     struct SweepAction;
     SweepAction* sweepActions_;
 
     using NativeObjectVector = Vector<NativeObject*, 0, SystemAllocPolicy>;
     NativeObjectVector dictionaryModeObjects_;
 
 #ifdef JS_GC_ZEAL
--- a/js/src/jit-test/tests/basic/dumpStringRepresentation.js
+++ b/js/src/jit-test/tests/basic/dumpStringRepresentation.js
@@ -63,8 +63,11 @@ print("\nr2: now mutated into a dependen
 dumpStringRepresentation(r2);
 
 print("\nr1: now a doubly-dependent string, because of r2's mutation:");
 dumpStringRepresentation(r1);
 
 print("\nt, s: Original leaves, representation unchanged:");
 dumpStringRepresentation(t);
 dumpStringRepresentation(s);
+
+for (var str of representativeStringArray())
+    dumpStringRepresentation(str);
--- a/js/src/jit/BaselineCacheIRCompiler.cpp
+++ b/js/src/jit/BaselineCacheIRCompiler.cpp
@@ -992,17 +992,17 @@ BaselineCacheIRCompiler::emitStoreTypedO
     if (!addFailurePath(&failure))
         return false;
 
     // Compute the address being written to.
     LoadTypedThingData(masm, layout, obj, scratch1);
     masm.addPtr(offsetAddr, scratch1);
     Address dest(scratch1, 0);
 
-    BaselineStoreToTypedArray(cx_, masm, type, val, dest, scratch2, failure->label());
+    StoreToTypedArray(cx_, masm, type, val, dest, scratch2, failure->label());
     return true;
 }
 
 bool
 BaselineCacheIRCompiler::emitStoreDenseElement()
 {
     ObjOperandId objId = reader.objOperandId();
     Int32OperandId indexId = reader.int32OperandId();
@@ -1219,17 +1219,17 @@ BaselineCacheIRCompiler::emitStoreTypedE
 
     // Use ICStubReg as second scratch register. TODO: consider doing the RHS
     // type check/conversion as a separate IR instruction so we can simplify
     // this.
     Register scratch2 = ICStubReg;
     masm.push(scratch2);
 
     Label fail;
-    BaselineStoreToTypedArray(cx_, masm, type, val, dest, scratch2, &fail);
+    StoreToTypedArray(cx_, masm, type, val, dest, scratch2, &fail);
     masm.pop(scratch2);
     masm.jump(&done);
 
     masm.bind(&fail);
     masm.pop(scratch2);
     masm.jump(failure->label());
 
     masm.bind(&done);
--- a/js/src/jit/BaselineIC.cpp
+++ b/js/src/jit/BaselineIC.cpp
@@ -1104,19 +1104,19 @@ EmitICUnboxedPreBarrier(MacroAssembler& 
 template void
 EmitICUnboxedPreBarrier(MacroAssembler& masm, const Address& address, JSValueType type);
 
 template void
 EmitICUnboxedPreBarrier(MacroAssembler& masm, const BaseIndex& address, JSValueType type);
 
 template <typename T>
 void
-BaselineStoreToTypedArray(JSContext* cx, MacroAssembler& masm, Scalar::Type type,
-                          const ValueOperand& value, const T& dest, Register scratch,
-                          Label* failure)
+StoreToTypedArray(JSContext* cx, MacroAssembler& masm, Scalar::Type type,
+                  const ValueOperand& value, const T& dest, Register scratch,
+                  Label* failure)
 {
     Label done;
 
     if (type == Scalar::Float32 || type == Scalar::Float64) {
         masm.ensureDouble(value, FloatReg0, failure);
         if (type == Scalar::Float32) {
             masm.convertDoubleToFloat32(FloatReg0, ScratchFloat32Reg);
             masm.storeToTypedFloatArray(type, ScratchFloat32Reg, dest);
@@ -1167,24 +1167,24 @@ BaselineStoreToTypedArray(JSContext* cx,
             masm.jump(failure);
         }
     }
 
     masm.bind(&done);
 }
 
 template void
-BaselineStoreToTypedArray(JSContext* cx, MacroAssembler& masm, Scalar::Type type,
-                          const ValueOperand& value, const Address& dest, Register scratch,
-                          Label* failure);
+StoreToTypedArray(JSContext* cx, MacroAssembler& masm, Scalar::Type type,
+                  const ValueOperand& value, const Address& dest, Register scratch,
+                  Label* failure);
 
 template void
-BaselineStoreToTypedArray(JSContext* cx, MacroAssembler& masm, Scalar::Type type,
-                          const ValueOperand& value, const BaseIndex& dest, Register scratch,
-                          Label* failure);
+StoreToTypedArray(JSContext* cx, MacroAssembler& masm, Scalar::Type type,
+                  const ValueOperand& value, const BaseIndex& dest, Register scratch,
+                  Label* failure);
 
 //
 // In_Fallback
 //
 
 static bool
 DoInFallback(JSContext* cx, BaselineFrame* frame, ICIn_Fallback* stub_,
              HandleValue key, HandleValue objValue, MutableHandleValue res)
--- a/js/src/jit/BaselineIC.h
+++ b/js/src/jit/BaselineIC.h
@@ -1713,16 +1713,16 @@ struct IonOsrTempData;
 
 template <typename T>
 void EmitICUnboxedPreBarrier(MacroAssembler &masm, const T& address, JSValueType type);
 
 // Write an arbitrary value to a typed array or typed object address at dest.
 // If the value could not be converted to the appropriate format, jump to
 // failure.
 template <typename T>
-void BaselineStoreToTypedArray(JSContext* cx, MacroAssembler& masm, Scalar::Type type,
-                               const ValueOperand& value, const T& dest, Register scratch,
-                               Label* failure);
+void StoreToTypedArray(JSContext* cx, MacroAssembler& masm, Scalar::Type type,
+                       const ValueOperand& value, const T& dest, Register scratch,
+                       Label* failure);
 
 } // namespace jit
 } // namespace js
 
 #endif /* jit_BaselineIC_h */
--- a/js/src/jit/IonBuilder.cpp
+++ b/js/src/jit/IonBuilder.cpp
@@ -6627,22 +6627,24 @@ void
 IonBuilder::maybeMarkEmpty(MDefinition* ins)
 {
     MOZ_ASSERT(ins->type() == MIRType::Value);
 
     // When one of the operands has no type information, mark the output
     // as having no possible types too. This is to avoid degrading
     // subsequent analysis.
     for (size_t i = 0; i < ins->numOperands(); i++) {
-        if (!ins->emptyResultTypeSet())
+        if (!ins->getOperand(i)->emptyResultTypeSet())
             continue;
 
         TemporaryTypeSet* types = alloc().lifoAlloc()->new_<TemporaryTypeSet>();
-        if (types)
+        if (types) {
             ins->setResultTypeSet(types);
+            return;
+        }
     }
 }
 
 // Return whether property lookups can be performed effectlessly on clasp.
 static bool
 ClassHasEffectlessLookup(const Class* clasp)
 {
     return (clasp == &UnboxedPlainObject::class_) ||
--- a/js/src/jit/IonCacheIRCompiler.cpp
+++ b/js/src/jit/IonCacheIRCompiler.cpp
@@ -385,17 +385,17 @@ IonCacheIRCompiler::init()
         if (numInputs > 1)
             allocator.initInputLocation(1, ic->id());
         break;
       }
       case CacheKind::SetProp:
       case CacheKind::SetElem: {
         IonSetPropertyIC* ic = ic_->asSetPropertyIC();
 
-        available.add(ic->temp1());
+        available.add(ic->temp());
 
         liveRegs_.emplace(ic->liveRegs());
 
         allocator.initInputLocation(0, ic->object(), JSVAL_TYPE_OBJECT);
 
         if (ic->kind() == CacheKind::SetProp) {
             MOZ_ASSERT(numInputs == 2);
             allocator.initInputLocation(1, ic->rhs());
@@ -1235,17 +1235,17 @@ IonCacheIRCompiler::emitStoreTypedObject
     FailurePath* failure;
     if (!addFailurePath(&failure))
         return false;
 
     // Compute the address being written to.
     LoadTypedThingData(masm, layout, obj, scratch1);
     Address dest(scratch1, offset);
 
-    BaselineStoreToTypedArray(cx_, masm, type, val, dest, scratch2, failure->label());
+    StoreToTypedArray(cx_, masm, type, val, dest, scratch2, failure->label());
     return true;
 }
 
 bool
 IonCacheIRCompiler::emitStoreDenseElement()
 {
     Register obj = allocator.useRegister(masm, reader.objOperandId());
     Register index = allocator.useRegister(masm, reader.int32OperandId());
--- a/js/src/jit/IonIC.cpp
+++ b/js/src/jit/IonIC.cpp
@@ -38,17 +38,17 @@ IonIC::scratchRegisterForEntryJump()
         Register temp = asGetPropertyIC()->maybeTemp();
         if (temp != InvalidReg)
             return temp;
         TypedOrValueRegister output = asGetPropertyIC()->output();
         return output.hasValue() ? output.valueReg().scratchReg() : output.typedReg().gpr();
       }
       case CacheKind::SetProp:
       case CacheKind::SetElem:
-        return asSetPropertyIC()->temp1();
+        return asSetPropertyIC()->temp();
       case CacheKind::GetName:
       case CacheKind::In:
         MOZ_CRASH("Baseline-specific for now");
     }
 
     MOZ_CRASH("Invalid kind");
 }
 
--- a/js/src/jit/IonIC.h
+++ b/js/src/jit/IonIC.h
@@ -203,49 +203,49 @@ class IonGetPropertyIC : public IonIC
                                     HandleValue val, HandleValue idVal, MutableHandleValue res);
 };
 
 class IonSetPropertyIC : public IonIC
 {
     LiveRegisterSet liveRegs_;
 
     Register object_;
-    Register temp1_;
+    Register temp_;
     FloatRegister maybeTempDouble_;
     FloatRegister maybeTempFloat32_;
     ConstantOrRegister id_;
     ConstantOrRegister rhs_;
     bool strict_ : 1;
     bool needsTypeBarrier_ : 1;
     bool guardHoles_ : 1;
 
   public:
-    IonSetPropertyIC(CacheKind kind, LiveRegisterSet liveRegs, Register object, Register temp1,
+    IonSetPropertyIC(CacheKind kind, LiveRegisterSet liveRegs, Register object, Register temp,
                      FloatRegister maybeTempDouble, FloatRegister maybeTempFloat32,
                      const ConstantOrRegister& id, const ConstantOrRegister& rhs, bool strict,
                      bool needsTypeBarrier, bool guardHoles)
       : IonIC(kind),
         liveRegs_(liveRegs),
         object_(object),
-        temp1_(temp1),
+        temp_(temp),
         maybeTempDouble_(maybeTempDouble),
         maybeTempFloat32_(maybeTempFloat32),
         id_(id),
         rhs_(rhs),
         strict_(strict),
         needsTypeBarrier_(needsTypeBarrier),
         guardHoles_(guardHoles)
     { }
 
     LiveRegisterSet liveRegs() const { return liveRegs_; }
     Register object() const { return object_; }
     ConstantOrRegister id() const { return id_; }
     ConstantOrRegister rhs() const { return rhs_; }
 
-    Register temp1() const { return temp1_; }
+    Register temp() const { return temp_; }
     FloatRegister maybeTempDouble() const { return maybeTempDouble_; }
     FloatRegister maybeTempFloat32() const { return maybeTempFloat32_; }
 
     bool strict() const { return strict_; }
     bool needsTypeBarrier() const { return needsTypeBarrier_; }
     bool guardHoles() const { return guardHoles_; }
 
     static MOZ_MUST_USE bool update(JSContext* cx, HandleScript outerScript, IonSetPropertyIC* ic,
--- a/js/src/jsdate.cpp
+++ b/js/src/jsdate.cpp
@@ -581,86 +581,92 @@ RegionMatches(const char* s1, int s1off,
         s1off++;
         s2off++;
         count--;
     }
 
     return count == 0;
 }
 
-/* ES6 20.3.3.4. */
+// ES2017 draft rev (TODO: Add git hash when PR 642 is merged.)
+// 20.3.3.4
+// Date.UTC(year [, month [, date [, hours [, minutes [, seconds [, ms]]]]]])
 static bool
 date_UTC(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
 
-    // Steps 1-2.
+    // Step 1.
     double y;
     if (!ToNumber(cx, args.get(0), &y))
         return false;
 
-    // Steps 3-4.
+    // Step 2.
     double m;
-    if (!ToNumber(cx, args.get(1), &m))
-        return false;
-
-    // Steps 5-6.
+    if (args.length() >= 2) {
+        if (!ToNumber(cx, args[1], &m))
+            return false;
+    } else {
+        m = 0;
+    }
+
+    // Step 3.
     double dt;
     if (args.length() >= 3) {
         if (!ToNumber(cx, args[2], &dt))
             return false;
     } else {
         dt = 1;
     }
 
-    // Steps 7-8.
+    // Step 4.
     double h;
     if (args.length() >= 4) {
         if (!ToNumber(cx, args[3], &h))
             return false;
     } else {
         h = 0;
     }
 
-    // Steps 9-10.
+    // Step 5.
     double min;
     if (args.length() >= 5) {
         if (!ToNumber(cx, args[4], &min))
             return false;
     } else {
         min = 0;
     }
 
-    // Steps 11-12.
+    // Step 6.
     double s;
     if (args.length() >= 6) {
         if (!ToNumber(cx, args[5], &s))
             return false;
     } else {
         s = 0;
     }
 
-    // Steps 13-14.
+    // Step 7.
     double milli;
     if (args.length() >= 7) {
         if (!ToNumber(cx, args[6], &milli))
             return false;
     } else {
         milli = 0;
     }
 
-    // Step 15.
+    // Step 8.
     double yr = y;
     if (!IsNaN(y)) {
         double yint = ToInteger(y);
         if (0 <= yint && yint <= 99)
             yr = 1900 + yint;
     }
 
-    // Step 16.
+    // Step 9.
     ClippedTime time = TimeClip(MakeDate(MakeDay(yr, m, dt), MakeTime(h, min, s, milli)));
     args.rval().set(TimeValue(time));
     return true;
 }
 
 /*
  * Read and convert decimal digits from s[*i] into *result
  * while *i < limit.
--- a/js/src/jsfriendapi.h
+++ b/js/src/jsfriendapi.h
@@ -2905,16 +2905,31 @@ extern JS_FRIEND_API(JSObject*)
 ToWindowIfWindowProxy(JSObject* obj);
 
 // Create and add the Intl.PluralRules constructor function to the provided
 // object.  This function throws if called more than once per realm/global
 // object.
 extern bool
 AddPluralRulesConstructor(JSContext* cx, JS::Handle<JSObject*> intl);
 
+// Create and add the Intl.MozDateTimeFormat constructor function to the provided
+// object.
+//
+// This custom date/time formatter constructor gives users the ability
+// to specify a custom format pattern. This pattern is passed *directly*
+// to ICU with NO SYNTAX PARSING OR VALIDATION WHATSOEVER. ICU appears to
+// have a a modicum of testing of this, and it won't fall over completely
+// if passed bad input. But the current behavior is entirely under-specified
+// and emphatically not shippable on the web, and it *must* be fixed before
+// this functionality can be exposed in the real world. (There are also some
+// questions about whether the format exposed here is the *right* one to
+// standardize, that will also need to be resolved to ship this.)
+extern bool
+AddMozDateTimeFormatConstructor(JSContext* cx, JS::Handle<JSObject*> intl);
+
 class MOZ_STACK_CLASS JS_FRIEND_API(AutoAssertNoContentJS)
 {
   public:
     explicit AutoAssertNoContentJS(JSContext* cx);
     ~AutoAssertNoContentJS();
 
   private:
     JSContext* context_;
--- a/js/src/jsfun.cpp
+++ b/js/src/jsfun.cpp
@@ -1618,19 +1618,18 @@ FunctionConstructor(JSContext* cx, const
     else if (generatorKind != NotGenerator)
         introductionType = "GeneratorFunction";
 
     const char* introducerFilename = filename;
     if (maybeScript && maybeScript->scriptSource()->introducerFilename())
         introducerFilename = maybeScript->scriptSource()->introducerFilename();
 
     CompileOptions options(cx);
-    // Use line 0 to make the function body starts from line 1.
     options.setMutedErrors(mutedErrors)
-           .setFileAndLine(filename, 0)
+           .setFileAndLine(filename, 1)
            .setNoScriptRval(false)
            .setIntroductionInfo(introducerFilename, introductionType, lineno, maybeScript, pcOffset);
 
     StringBuffer sb(cx);
 
     if (!sb.append('('))
         return false;
 
--- a/js/src/proxy/CrossCompartmentWrapper.cpp
+++ b/js/src/proxy/CrossCompartmentWrapper.cpp
@@ -261,25 +261,28 @@ static bool
 CanReify(HandleObject obj)
 {
     return obj->is<PropertyIteratorObject>() &&
            (obj->as<PropertyIteratorObject>().getNativeIterator()->flags & JSITER_ENUMERATE);
 }
 
 struct AutoCloseIterator
 {
-    AutoCloseIterator(JSContext* cx, JSObject* obj) : cx(cx), obj(cx, obj) {}
+    AutoCloseIterator(JSContext* cx, PropertyIteratorObject* obj) : cx(cx), obj(cx, obj) {}
 
-    ~AutoCloseIterator() { if (obj) CloseIterator(cx, obj); }
+    ~AutoCloseIterator() {
+        if (obj)
+            MOZ_ALWAYS_TRUE(CloseIterator(cx, obj));
+    }
 
     void clear() { obj = nullptr; }
 
   private:
     JSContext* cx;
-    RootedObject obj;
+    Rooted<PropertyIteratorObject*> obj;
 };
 
 static bool
 Reify(JSContext* cx, JSCompartment* origin, MutableHandleObject objp)
 {
     Rooted<PropertyIteratorObject*> iterObj(cx, &objp->as<PropertyIteratorObject>());
     NativeIterator* ni = iterObj->getNativeIterator();
 
@@ -305,18 +308,17 @@ Reify(JSContext* cx, JSCompartment* orig
             RootedValue v(cx, StringValue(ni->begin()[i]));
             if (!ValueToId<CanGC>(cx, v, &id))
                 return false;
             keys.infallibleAppend(id);
         }
     }
 
     close.clear();
-    if (!CloseIterator(cx, iterObj))
-        return false;
+    MOZ_ALWAYS_TRUE(CloseIterator(cx, iterObj));
 
     return EnumeratedIdVectorToIterator(cx, obj, ni->flags, keys, objp);
 }
 
 bool
 CrossCompartmentWrapper::enumerate(JSContext* cx, HandleObject wrapper,
                                    MutableHandleObject objp) const
 {
--- a/js/src/shell/js.cpp
+++ b/js/src/shell/js.cpp
@@ -957,16 +957,19 @@ AddIntlExtras(JSContext* cx, unsigned ar
     };
 
     if (!JS_DefineFunctions(cx, intl, funcs))
         return false;
 
     if (!js::AddPluralRulesConstructor(cx, intl))
         return false;
 
+    if (!js::AddMozDateTimeFormatConstructor(cx, intl))
+        return false;
+
     args.rval().setUndefined();
     return true;
 }
 #endif // ENABLE_INTL_API
 
 static bool
 EvalAndPrint(JSContext* cx, const char* bytes, size_t length,
              int lineno, bool compileOnly)
new file mode 100644
--- /dev/null
+++ b/js/src/tests/Intl/DateTimeFormat/mozExtensions.js
@@ -0,0 +1,58 @@
+// |reftest| skip-if(!this.hasOwnProperty("Intl")||!this.hasOwnProperty("addIntlExtras"))
+/* 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/. */
+
+// Tests the format function with a diverse set of locales and options.
+// Always use UTC to avoid dependencies on test environment.
+
+let mozIntl = {};
+addIntlExtras(mozIntl);
+
+// Pattern
+
+var dtf = new Intl.DateTimeFormat("en-US", {pattern: "HH:mm MM/dd/YYYY"});
+var mozDtf = new mozIntl.DateTimeFormat("en-US", {pattern: "HH:mm MM/dd/YYYY"});
+
+assertEq(dtf.resolvedOptions().hasOwnProperty('pattern'), false);
+assertEq(mozDtf.resolvedOptions().pattern, "HH:mm MM/dd/YYYY");
+
+// Date style
+
+var dtf = new Intl.DateTimeFormat("en-US", {dateStyle: 'long'});
+assertEq(mozDtf.resolvedOptions().hasOwnProperty('dateStyle'), false);
+
+var mozDtf = new mozIntl.DateTimeFormat("en-US", {dateStyle: 'long'});
+assertEq(mozDtf.resolvedOptions().dateStyle, 'long');
+assertEq(mozDtf.resolvedOptions().hasOwnProperty('year'), true);
+assertEq(mozDtf.resolvedOptions().hasOwnProperty('month'), true);
+assertEq(mozDtf.resolvedOptions().hasOwnProperty('day'), true);
+
+// Time style
+
+var dtf = new Intl.DateTimeFormat("en-US", {timeStyle: 'long'});
+assertEq(dtf.resolvedOptions().hasOwnProperty('dateStyle'), false);
+
+var mozDtf = new mozIntl.DateTimeFormat("en-US", {timeStyle: 'long'});
+assertEq(mozDtf.resolvedOptions().timeStyle, 'long');
+assertEq(mozDtf.resolvedOptions().hasOwnProperty('hour'), true);
+assertEq(mozDtf.resolvedOptions().hasOwnProperty('minute'), true);
+assertEq(mozDtf.resolvedOptions().hasOwnProperty('second'), true);
+
+// Date/Time style
+
+var dtf = new Intl.DateTimeFormat("en-US", {timeStyle: 'medium', dateStyle: 'medium'});
+assertEq(dtf.resolvedOptions().hasOwnProperty('dateStyle'), false);
+assertEq(dtf.resolvedOptions().hasOwnProperty('timeStyle'), false);
+
+var mozDtf = new mozIntl.DateTimeFormat("en-US", {dateStyle: 'medium', timeStyle: 'medium'});
+assertEq(mozDtf.resolvedOptions().timeStyle, 'medium');
+assertEq(mozDtf.resolvedOptions().dateStyle, 'medium');
+assertEq(mozDtf.resolvedOptions().hasOwnProperty('hour'), true);
+assertEq(mozDtf.resolvedOptions().hasOwnProperty('minute'), true);
+assertEq(mozDtf.resolvedOptions().hasOwnProperty('second'), true);
+assertEq(mozDtf.resolvedOptions().hasOwnProperty('year'), true);
+assertEq(mozDtf.resolvedOptions().hasOwnProperty('month'), true);
+assertEq(mozDtf.resolvedOptions().hasOwnProperty('day'), true);
+
+reportCompare(0, 0, 'ok');
new file mode 100644
--- /dev/null
+++ b/js/src/tests/ecma_6/TypedArray/slice-detached.js
@@ -0,0 +1,103 @@
+// Tests for detached ArrayBuffer checks in %TypedArray%.prototype.slice ( start, end ).
+
+function* createTypedArrays(lengths = [0, 1, 4, 4096]) {
+    // Test with eagerly created ArrayBuffer.
+    for (let length of lengths) {
+        let buffer = new ArrayBuffer(length * Int32Array.BYTES_PER_ELEMENT);
+        let typedArray = new Int32Array(buffer);
+
+        yield {typedArray, length, buffer() { return buffer; }};
+    }
+
+    // Test with lazily created ArrayBuffer.
+    for (let length of lengths) {
+        let typedArray = new Int32Array(length);
+
+        yield {typedArray, length, buffer() { return typedArray.buffer; }};
+    }
+}
+
+if (typeof detachArrayBuffer === "function") {
+    // ArrayBuffer is detached when entering slice().
+    for (let {typedArray, buffer} of createTypedArrays()) {
+        detachArrayBuffer(buffer());
+        assertThrowsInstanceOf(() => {
+            typedArray.slice(0);
+        }, TypeError, "ArrayBuffer is detached on function entry");
+    }
+
+    // ArrayBuffer is detached when computing ToInteger(start).
+    for (let {typedArray, length, buffer} of createTypedArrays()) {
+        let detached = false;
+        let start = {
+            valueOf() {
+                assertEq(detached, false);
+                detachArrayBuffer(buffer());
+                assertEq(detached, false);
+                detached = true;
+                return 0;
+            }
+        };
+
+        // Doesn't throw an error when no bytes are copied.
+        if (length === 0) {
+            typedArray.slice(start);
+        } else {
+            assertThrowsInstanceOf(() => {
+                typedArray.slice(start);
+            }, TypeError, "ArrayBuffer is detached in ToInteger(start)");
+        }
+        assertEq(detached, true, "detachArrayBuffer was called");
+    }
+
+    // ArrayBuffer is detached when computing ToInteger(end).
+    for (let {typedArray, length, buffer} of createTypedArrays()) {
+        let detached = false;
+        let end = {
+            valueOf() {
+                assertEq(detached, false);
+                detachArrayBuffer(buffer());
+                assertEq(detached, false);
+                detached = true;
+                return length;
+            }
+        };
+
+        // Doesn't throw an error when no bytes are copied.
+        if (length === 0) {
+            typedArray.slice(0, end);
+        } else {
+            assertThrowsInstanceOf(() => {
+                typedArray.slice(0, end);
+            }, TypeError, "ArrayBuffer is detached in ToInteger(end)");
+        }
+        assertEq(detached, true, "detachArrayBuffer was called");
+    }
+
+    // ArrayBuffer is detached in species constructor.
+    for (let {typedArray, length, buffer} of createTypedArrays()) {
+        let detached = false;
+        typedArray.constructor = {
+            [Symbol.species]: function(...args) {
+                assertEq(detached, false);
+                detachArrayBuffer(buffer());
+                assertEq(detached, false);
+                detached = true;
+                return new Int32Array(...args);
+            }
+        };
+
+        // Doesn't throw an error when no bytes are copied.
+        if (length === 0) {
+            typedArray.slice(0);
+        } else {
+            assertThrowsInstanceOf(() => {
+                typedArray.slice(0);
+            }, TypeError, "ArrayBuffer is detached in TypedArraySpeciesCreate(...)");
+        }
+        assertEq(detached, true, "detachArrayBuffer was called");
+    }
+}
+
+if (typeof reportCompare === "function")
+    reportCompare(true, true);
--- a/js/src/tests/jstests.list
+++ b/js/src/tests/jstests.list
@@ -163,19 +163,16 @@ skip script test262/built-ins/global/glo
 skip script test262/built-ins/TypedArray/prototype/every/callbackfn-detachbuffer.js
 skip script test262/built-ins/TypedArray/prototype/filter/callbackfn-detachbuffer.js
 skip script test262/built-ins/TypedArray/prototype/find/predicate-may-detach-buffer.js
 skip script test262/built-ins/TypedArray/prototype/findIndex/predicate-may-detach-buffer.js
 skip script test262/built-ins/TypedArray/prototype/forEach/callbackfn-detachbuffer.js
 skip script test262/built-ins/TypedArray/prototype/map/callbackfn-detachbuffer.js
 skip script test262/built-ins/TypedArray/prototype/reduceRight/callbackfn-detachbuffer.js
 skip script test262/built-ins/TypedArray/prototype/reduce/callbackfn-detachbuffer.js
-skip script test262/built-ins/TypedArray/prototype/slice/detached-buffer-custom-ctor-same-targettype.js
-skip script test262/built-ins/TypedArray/prototype/slice/detached-buffer-custom-ctor-other-targettype.js
-skip script test262/built-ins/TypedArray/prototype/slice/detached-buffer-get-ctor.js
 skip script test262/built-ins/TypedArray/prototype/some/callbackfn-detachbuffer.js
 skip script test262/built-ins/TypedArrays/internals/DefineOwnProperty/detached-buffer-realm.js
 skip script test262/built-ins/TypedArrays/internals/DefineOwnProperty/detached-buffer.js
 skip script test262/built-ins/TypedArrays/internals/Get/detached-buffer-realm.js
 skip script test262/built-ins/TypedArrays/internals/Get/detached-buffer.js
 skip script test262/built-ins/TypedArrays/internals/GetOwnProperty/detached-buffer-realm.js
 skip script test262/built-ins/TypedArrays/internals/GetOwnProperty/detached-buffer.js
 skip script test262/built-ins/TypedArrays/internals/HasProperty/detached-buffer-realm.js
@@ -536,19 +533,16 @@ skip script test262/annexB/language/eval
 skip script test262/language/expressions/async-generators/expression-yield-as-statement.js
 skip script test262/language/expressions/async-generators/expression-await-thenable-as-yield-operand.js
 skip script test262/language/expressions/async-generators/expression-yield-as-operand.js
 skip script test262/language/expressions/async-generators/expression-await-as-yield-operand.js
 skip script test262/language/expressions/async-generators/expression-yield-newline.js
 skip script test262/language/expressions/async-generators/expression-await-promise-as-yield-operand.js
 skip script test262/language/expressions/async-generators/expression-yield-star-before-newline.js
 
-# https://bugzilla.mozilla.org/show_bug.cgi?id=1050755
-skip script test262/built-ins/Date/UTC/return-value.js
-
 # SIMD.
 skip script test262/built-ins/Simd/check.js
 skip script test262/built-ins/Simd/from.js
 skip script test262/built-ins/Simd/operators.js
 skip script test262/built-ins/Simd/replace_lane.js
 skip script test262/built-ins/Simd/shuffle.js
 skip script test262/built-ins/Simd/swizzle.js
 
--- a/js/src/vm/SelfHosting.cpp
+++ b/js/src/vm/SelfHosting.cpp
@@ -2617,16 +2617,17 @@ static const JSFunctionSpec intrinsic_fu
     JS_FN("intl_GetCalendarInfo", intl_GetCalendarInfo, 1,0),
     JS_FN("intl_GetLocaleInfo", intl_GetLocaleInfo, 1,0),
     JS_FN("intl_ComputeDisplayNames", intl_ComputeDisplayNames, 3,0),
     JS_FN("intl_IsValidTimeZoneName", intl_IsValidTimeZoneName, 1,0),
     JS_FN("intl_NumberFormat", intl_NumberFormat, 2,0),
     JS_FN("intl_NumberFormat_availableLocales", intl_NumberFormat_availableLocales, 0,0),
     JS_FN("intl_numberingSystem", intl_numberingSystem, 1,0),
     JS_FN("intl_patternForSkeleton", intl_patternForSkeleton, 2,0),
+    JS_FN("intl_patternForStyle", intl_patternForStyle, 3,0),
     JS_FN("intl_PluralRules_availableLocales", intl_PluralRules_availableLocales, 0,0),
     JS_FN("intl_GetPluralCategories", intl_GetPluralCategories, 2, 0),
     JS_FN("intl_SelectPluralRule", intl_SelectPluralRule, 2,0),
 
     JS_INLINABLE_FN("IsCollator",
                     intrinsic_IsInstanceOfBuiltin<CollatorObject>, 1,0,
                     IntlIsCollator),
     JS_INLINABLE_FN("IsDateTimeFormat",
--- a/js/src/vm/String.cpp
+++ b/js/src/vm/String.cpp
@@ -1107,18 +1107,17 @@ JSAtom::dump()
 
 void
 JSExternalString::dumpRepresentation(FILE* fp, int indent) const
 {
     dumpRepresentationHeader(fp, indent, "JSExternalString");
     indent += 2;
 
     fprintf(fp, "%*sfinalizer: ((JSStringFinalizer*) %p)\n", indent, "", externalFinalizer());
-    fprintf(fp, "%*sbase: ", indent, "");
-    base()->dumpRepresentation(fp, indent);
+    dumpRepresentationChars(fp, indent);
 }
 #endif /* DEBUG */
 
 JSLinearString*
 js::NewDependentString(JSContext* cx, JSString* baseArg, size_t start, size_t length)
 {
     if (length == 0)
         return cx->emptyString();
--- a/layout/base/PresShell.cpp
+++ b/layout/base/PresShell.cpp
@@ -7021,25 +7021,26 @@ DispatchPointerFromMouseOrTouch(PresShel
     case eTouchEnd:
       pointerMessage = ePointerUp;
       buttons = WidgetMouseEvent::eNoButtonFlag;
       break;
     case eTouchStart:
       pointerMessage = ePointerDown;
       break;
     case eTouchCancel:
+    case eTouchPointerCancel:
       pointerMessage = ePointerCancel;
       break;
     default:
       return NS_OK;
     }
 
     for (uint32_t i = 0; i < touchEvent->mTouches.Length(); ++i) {
       mozilla::dom::Touch* touch = touchEvent->mTouches[i];
-      if (!touch || !touch->convertToPointer) {
+      if (!TouchManager::ShouldConvertTouchToPointer(touch, touchEvent)) {
         continue;
       }
 
       WidgetPointerEvent event(touchEvent->IsTrusted(), pointerMessage,
                                touchEvent->mWidget);
       event.mIsPrimary = i == 0;
       event.pointerId = touch->Identifier();
       event.mRefPoint = touch->mRefPoint;
--- a/layout/base/ServoRestyleManager.cpp
+++ b/layout/base/ServoRestyleManager.cpp
@@ -4,16 +4,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/. */
 
 #include "mozilla/ServoRestyleManager.h"
 
 #include "mozilla/DocumentStyleRootIterator.h"
 #include "mozilla/ServoBindings.h"
 #include "mozilla/ServoStyleSet.h"
+#include "mozilla/Unused.h"
 #include "mozilla/dom/ChildIterator.h"
 #include "nsContentUtils.h"
 #include "nsCSSFrameConstructor.h"
 #include "nsPrintfCString.h"
 #include "nsRefreshDriver.h"
 #include "nsStyleChangeList.h"
 
 using namespace mozilla::dom;
@@ -112,26 +113,32 @@ ServoRestyleManager::ClearServoDataFromS
     if (n->IsElement()) {
       ClearServoDataFromSubtree(n->AsElement());
     }
   }
 
   aElement->UnsetHasDirtyDescendantsForServo();
 }
 
-/* static */ void
-ServoRestyleManager::ClearDirtyDescendantsFromSubtree(Element* aElement)
+
+// Clears HasDirtyDescendants and RestyleData from all elements in the
+// subtree rooted at aElement.
+static void
+ClearRestyleStateFromSubtree(Element* aElement)
 {
-  StyleChildrenIterator it(aElement);
-  for (nsIContent* n = it.GetNextChild(); n; n = it.GetNextChild()) {
-    if (n->IsElement()) {
-      ClearDirtyDescendantsFromSubtree(n->AsElement());
+  if (aElement->HasDirtyDescendantsForServo()) {
+    StyleChildrenIterator it(aElement);
+    for (nsIContent* n = it.GetNextChild(); n; n = it.GetNextChild()) {
+      if (n->IsElement()) {
+        ClearRestyleStateFromSubtree(n->AsElement());
+      }
     }
   }
 
+  Unused << Servo_TakeChangeHint(aElement);
   aElement->UnsetHasDirtyDescendantsForServo();
 }
 
 static void
 UpdateStyleContextForTableWrapper(nsIFrame* aTableWrapper,
                                   nsStyleContext* aTableStyleContext,
                                   ServoStyleSet* aStyleSet)
 {
@@ -178,17 +185,17 @@ ServoRestyleManager::RecreateStyleContex
   }
 
   // If our change hint is reconstruct, we delegate to the frame constructor,
   // which consumes the new style and expects the old style to be on the frame.
   //
   // XXXbholley: We should teach the frame constructor how to clear the dirty
   // descendants bit to avoid the traversal here.
   if (changeHint & nsChangeHint_ReconstructFrame) {
-    ClearDirtyDescendantsFromSubtree(aElement);
+    ClearRestyleStateFromSubtree(aElement);
     return;
   }
 
   // TODO(emilio): We could avoid some refcount traffic here, specially in the
   // ServoComputedValues case, which uses atomic refcounting.
   //
   // Hold the old style context alive, because it could become a dangling
   // pointer during the replacement. In practice it's not a huge deal (on
--- a/layout/base/ServoRestyleManager.h
+++ b/layout/base/ServoRestyleManager.h
@@ -87,22 +87,16 @@ public:
                                          nsIAtom* aPseudoTagOrNull);
 
   /**
    * Clears the ServoElementData and HasDirtyDescendants from all elements
    * in the subtree rooted at aElement.
    */
   static void ClearServoDataFromSubtree(Element* aElement);
 
-  /**
-   * Clears HasDirtyDescendants from all elements in the subtree rooted at
-   * aElement.
-   */
-  static void ClearDirtyDescendantsFromSubtree(Element* aElement);
-
 protected:
   ~ServoRestyleManager() override
   {
     MOZ_ASSERT(!mReentrantChanges);
   }
 
 private:
   /**
--- a/layout/base/TouchManager.cpp
+++ b/layout/base/TouchManager.cpp
@@ -135,17 +135,18 @@ TouchManager::PreHandleEvent(WidgetEvent
       for (uint32_t i = 0; i < touchEvent->mTouches.Length(); ++i) {
         Touch* touch = touchEvent->mTouches[i];
         int32_t id = touch->Identifier();
         if (!sCaptureTouchList->Get(id, nullptr)) {
           // If it is not already in the queue, it is a new touch
           touch->mChanged = true;
         }
         touch->mMessage = aEvent->mMessage;
-        TouchInfo info = { touch, GetNonAnonymousAncestor(touch->mTarget) };
+        TouchInfo info = { touch, GetNonAnonymousAncestor(touch->mTarget),
+                           true };
         sCaptureTouchList->Put(id, info);
       }
       break;
     }
     case eTouchMove: {
       // Check for touches that changed. Mark them add to queue
       WidgetTouchEvent* touchEvent = aEvent->AsTouchEvent();
       WidgetTouchEvent::TouchArray& touches = touchEvent->mTouches;
@@ -242,16 +243,36 @@ TouchManager::PreHandleEvent(WidgetEvent
         aCurrentEventContent = do_QueryInterface(targetPtr);
         touch->SetTarget(targetPtr);
         sCaptureTouchList->Remove(id);
       }
       // add any touches left in the touch list, but ensure changed=false
       AppendToTouchList(&touches);
       break;
     }
+    case eTouchPointerCancel: {
+      // Don't generate pointer events by touch events after eTouchPointerCancel
+      // is received.
+      WidgetTouchEvent* touchEvent = aEvent->AsTouchEvent();
+      WidgetTouchEvent::TouchArray& touches = touchEvent->mTouches;
+      for (uint32_t i = 0; i < touches.Length(); ++i) {
+        Touch* touch = touches[i];
+        if (!touch) {
+          continue;
+        }
+        int32_t id = touch->Identifier();
+        TouchInfo info;
+        if (!sCaptureTouchList->Get(id, &info)) {
+          continue;
+        }
+        info.mConvertToPointer = false;
+        sCaptureTouchList->Put(id, info);
+      }
+      break;
+    }
     default:
       break;
   }
   return true;
 }
 
 /*static*/ already_AddRefed<nsIContent>
 TouchManager::GetAnyCapturedTouchTarget()
@@ -285,9 +306,29 @@ TouchManager::GetCapturedTouch(int32_t a
   RefPtr<Touch> touch;
   TouchInfo info;
   if (sCaptureTouchList->Get(aId, &info)) {
     touch = info.mTouch;
   }
   return touch.forget();
 }
 
+/*static*/ bool
+TouchManager::ShouldConvertTouchToPointer(const Touch* aTouch,
+                                          const WidgetTouchEvent* aEvent)
+{
+  if (!aTouch || !aTouch->convertToPointer) {
+    return false;
+  }
+  TouchInfo info;
+  if (!sCaptureTouchList->Get(aTouch->Identifier(), &info)) {
+    // This check runs before the TouchManager has the touch registered in its
+    // touch list. It's because we dispatching pointer events before handling
+    // touch events. So we convert eTouchStart to pointerdown even it's not
+    // registered.
+    // Check WidgetTouchEvent::mMessage because Touch::mMessage is assigned when
+    // pre-handling touch events.
+    return aEvent->mMessage == eTouchStart;
+  }
+  return info.mConvertToPointer;
+}
+
 } // namespace mozilla
--- a/layout/base/TouchManager.h
+++ b/layout/base/TouchManager.h
@@ -35,29 +35,31 @@ public:
                       nsEventStatus* aStatus,
                       bool& aTouchIsNew,
                       bool& aIsHandlingUserInput,
                       nsCOMPtr<nsIContent>& aCurrentEventContent);
 
   static already_AddRefed<nsIContent> GetAnyCapturedTouchTarget();
   static bool HasCapturedTouch(int32_t aId);
   static already_AddRefed<dom::Touch> GetCapturedTouch(int32_t aId);
-
+  static bool ShouldConvertTouchToPointer(const dom::Touch* aTouch,
+                                          const WidgetTouchEvent* aEvent);
 private:
   void EvictTouches();
   static void EvictTouchPoint(RefPtr<dom::Touch>& aTouch,
                               nsIDocument* aLimitToDocument = nullptr);
   static void AppendToTouchList(WidgetTouchEvent::TouchArray* aTouchList);
 
   RefPtr<PresShell>   mPresShell;
   nsCOMPtr<nsIDocument> mDocument;
 
   struct TouchInfo
   {
     RefPtr<mozilla::dom::Touch> mTouch;
     nsCOMPtr<nsIContent> mNonAnonymousTarget;
+    bool mConvertToPointer;
   };
   static nsDataHashtable<nsUint32HashKey, TouchInfo>* sCaptureTouchList;
 };
 
 } // namespace mozilla
 
 #endif /* !defined(TouchManager_h_) */
--- a/layout/base/nsFrameManager.cpp
+++ b/layout/base/nsFrameManager.cpp
@@ -722,17 +722,17 @@ nsFrameManagerBase::UndisplayedMap::GetE
   if (parentContent && nsContentUtils::IsContentInsertionPoint(parentContent)) {
     parentContent = parentContent->GetParent();
     // Change the caller's pointer for the parent content to be the insertion parent.
     *aParentContent = parentContent;
   }
 
   PLHashNumber hashCode = NS_PTR_TO_INT32(parentContent);
   PLHashEntry** entry = PL_HashTableRawLookup(mTable, hashCode, parentContent);
-  if (*entry) {
+  if (*entry && !ServoStyleSet::IsInServoTraversal()) {
     mLastLookup = entry;
   }
   return entry;
 }
 
 UndisplayedNode* 
 nsFrameManagerBase::UndisplayedMap::GetFirstNode(nsIContent* aParentContent)
 {
--- a/layout/base/tests/bug970964_inner.html
+++ b/layout/base/tests/bug970964_inner.html
@@ -109,54 +109,68 @@ function runTests() {
   // Test Pointer firing before any mouse/touch original source
 
   var mouseDownTriggered = 0;
   var pointerDownTriggered = 0;
   var touchDownTriggered = 0;
   var touchCancelTriggered = 0;
   var pointerCancelTriggered = 0;
 
-  d0.onmousedown = function(e) {
-    mouseDownTriggered = 1;
-    is(pointerDownTriggered , 1, "Mouse event must be triggered after pointer event!");
-  };
-  d0.ontouchstart = function(e) {
-    touchDownTriggered = 1;
-    is(touchDownTriggered , 1, "Touch event must be triggered after pointer event!");
-  }
-  d0.ontouchcancel = function(e) {
-    touchCancelTriggered = 1;
-    is(pointerCancelTriggered, 1, "Touch cancel event must be triggered after pointer event!");
-  }
-  d0.onpointerdown = function(e) {
-    pointerDownTriggered = 1;
-    is(mouseDownTriggered, 0, "Pointer event must be triggered before mouse event!");
-    is(touchDownTriggered, 0, "Pointer event must be triggered before touch event!");
-  };
-  d0.addEventListener("pointercancel", function(ev) {
-    is(ev.pointerId, 0, "Correct default pointerId");
-    is(ev.bubbles, true, "bubbles should be true");
-    is(ev.cancelable, false, "pointercancel cancelable should be false ");
-    pointerCancelTriggered = 1;
-    is(touchCancelTriggered, 0, "Pointer event must be triggered before touch event!");
+  // Test pointer event generated from mouse event
+  d0.addEventListener("mousedown", (e) => {
+    ++mouseDownTriggered;
+    is(pointerDownTriggered , mouseDownTriggered, "Mouse event must be triggered after pointer event!");
   }, {once: true});
 
-  // Test pointer event generated from mouse event
+  d0.addEventListener("pointerdown", (e) => {
+    ++pointerDownTriggered;
+    is(pointerDownTriggered, mouseDownTriggered + 1, "Pointer event must be triggered before mouse event!");
+  }, {once: true});
+
   synthesizeMouse(d1, 3, 3, { type: "mousemove"});
   synthesizeMouse(d1, 3, 3, { type: "mousedown"});
   synthesizeMouse(d1, 3, 3, { type: "mouseup"});
 
   // Test pointer event generated from touch event
+  mouseDownTriggered = 0;
   pointerDownTriggered = 0;
-  mouseDownTriggered = 0;
+
+  d0.addEventListener("touchstart", (e) => {
+    ++touchDownTriggered;
+    is(pointerDownTriggered, touchDownTriggered,  "Touch event must be triggered after pointer event!");
+  }, {once: true});
+
+  d0.addEventListener("mousedown", (e) => {
+    ++mouseDownTriggered;
+    is(pointerDownTriggered , mouseDownTriggered, "Mouse event must be triggered after pointer event!");
+  }, {once: true});
+
+  d0.addEventListener("pointerdown", (e) => {
+    ++pointerDownTriggered;
+    is(pointerDownTriggered, touchDownTriggered + 1, "Pointer event must be triggered before mouse event!");
+    is(pointerDownTriggered, mouseDownTriggered + 1, "Pointer event must be triggered before mouse event!");
+  }, {once: true});
+
+  d0.addEventListener("touchcancel", (e) => {
+    ++touchCancelTriggered;
+    is(pointerCancelTriggered, touchCancelTriggered, "Touch cancel event must be triggered after pointer event!");
+  }, {once: true});
+
+  d0.addEventListener("pointercancel", function(ev) {
+    is(ev.pointerId, 0, "Correct default pointerId");
+    is(ev.bubbles, true, "bubbles should be true");
+    is(ev.cancelable, false, "pointercancel cancelable should be false ");
+    ++pointerCancelTriggered;
+    is(pointerCancelTriggered, touchCancelTriggered + 1, "Pointer event must be triggered before touch event!");
+  }, {once: true});
 
   var cwu = SpecialPowers.getDOMWindowUtils(window);
   var event1 = getTouchEventForTarget(d1, cwu, 0);
+  sendTouchEvent(cwu, "touchstart", event1, 0);
   sendTouchEvent(cwu, "touchmove", event1, 0);
-  sendTouchEvent(cwu, "touchstart", event1, 0);
   // Test Touch to Pointer Cancel
   sendTouchEvent(cwu, "touchcancel", event1, 0);
 
   // Check Pointer enter/leave from mouse generated event
   var mouseEnterTriggered = 0;
   var pointerEnterTriggered = 0;
   d2.onpointerenter = function(e) {
     pointerEnterTriggered = 1;
@@ -223,16 +237,17 @@ function runTests() {
   d3.onpointerleave = function(e) {
     ++d3leaveCount;
     is(e.bubbles, false, "bubbles should be false");
     is(e.cancelable, false, "cancelable should be false");
     checkPointerType(e.pointerType);
   };
 
   synthesizeMouse(d1, 3, 3, { type: "mousemove"});
+  sendTouchEvent(cwu, "touchstart", getTouchEventForTarget(d3, cwu, 3), 0);
   sendTouchEvent(cwu, "touchmove", getTouchEventForTarget(d3, cwu, 3), 0);
   is(touchPointerEnterLeaveCount, 1, "Wrong touch enterLeave count for!");
   is(mousePointerEnterLeaveCount, 2, "Wrong mouse enterLeave count for!");
 
   is(d1enterCount, 1, "Wrong enter count for! d1");
   is(d2leaveCount, 1, "Wrong leave count for! d2");
   is(d3enterCount, 1, "Wrong enter count for! d3");
 
@@ -286,18 +301,18 @@ function runTests() {
     if (pointerOutTriggeredForCancelEvent == 0) {
       is(e.pointerId, 3, "Wrong Pointer type, should be id from Touch event");
       is(e.pointerType, "touch", "Wrong Pointer type, should be touch type");
     } else {
       is(e.pointerId, 0, "Wrong Pointer type, should be id from mouse event");
       is(e.pointerType, "mouse", "Wrong Pointer type, should be mouse type");
     }
     pointerOutTriggeredForCancelEvent = 1;
- };
- d1.onpointerleave = function(e) {
+  };
+  d1.onpointerleave = function(e) {
     is(pointerOutTriggeredForCancelEvent, 1, "Pointer Out must be dispatched bedore Pointer leave");
     if (pointerLeaveTriggeredForCancelEvent == 0) {
       is(e.pointerId, 3, "Wrong Pointer type, should be id from Touch event");
       is(e.pointerType, "touch", "Wrong Pointer type, should be touch type");
     } else {
       is(e.pointerId, 0, "Wrong Pointer type, should be id from mouse event");
       is(e.pointerType, "mouse", "Wrong Pointer type, should be mouse type");
     }
--- a/layout/forms/nsITextControlFrame.h
+++ b/layout/forms/nsITextControlFrame.h
@@ -26,19 +26,16 @@ public:
   NS_IMETHOD    GetEditor(nsIEditor **aEditor) = 0;
 
   NS_IMETHOD    SetSelectionStart(int32_t aSelectionStart) = 0;
   NS_IMETHOD    SetSelectionEnd(int32_t aSelectionEnd) = 0;
   
   NS_IMETHOD    SetSelectionRange(int32_t aSelectionStart,
                                   int32_t aSelectionEnd,
                                   SelectionDirection aDirection = eNone) = 0;
-  NS_IMETHOD    GetSelectionRange(int32_t* aSelectionStart,
-                                  int32_t* aSelectionEnd,
-                                  SelectionDirection* aDirection = nullptr) = 0;
 
   NS_IMETHOD    GetOwnedSelectionController(nsISelectionController** aSelCon) = 0;
   virtual nsFrameSelection* GetOwnedFrameSelection() = 0;
 
   virtual nsresult GetPhonetic(nsAString& aPhonetic) = 0;
 
   /**
    * Ensure editor is initialized with the proper flags and the default value.
--- a/layout/forms/nsTextControlFrame.cpp
+++ b/layout/forms/nsTextControlFrame.cpp
@@ -798,25 +798,16 @@ nsTextControlFrame::ScrollSelectionIntoV
     return selCon->ScrollSelectionIntoView(nsISelectionController::SELECTION_NORMAL,
                                            nsISelectionController::SELECTION_FOCUS_REGION,
                                            nsISelectionController::SCROLL_FIRST_ANCESTOR_ONLY);
   }
 
   return NS_ERROR_FAILURE;
 }
 
-mozilla::dom::Element*
-nsTextControlFrame::GetRootNodeAndInitializeEditor()
-{
-  nsCOMPtr<nsIDOMElement> root;
-  GetRootNodeAndInitializeEditor(getter_AddRefs(root));
-  nsCOMPtr<mozilla::dom::Element> rootElem = do_QueryInterface(root);
-  return rootElem;
-}
-
 nsresult
 nsTextControlFrame::GetRootNodeAndInitializeEditor(nsIDOMElement **aRootElement)
 {
   NS_ENSURE_ARG_POINTER(aRootElement);
 
   nsCOMPtr<nsIEditor> editor;
   GetEditor(getter_AddRefs(editor));
   if (!editor)
@@ -920,17 +911,19 @@ nsTextControlFrame::SetSelectionRange(in
 NS_IMETHODIMP
 nsTextControlFrame::SetSelectionStart(int32_t aSelectionStart)
 {
   nsresult rv = EnsureEditorInitialized();
   NS_ENSURE_SUCCESS(rv, rv);
 
   int32_t selStart = 0, selEnd = 0;
 
-  rv = GetSelectionRange(&selStart, &selEnd);
+  nsCOMPtr<nsITextControlElement> txtCtrl = do_QueryInterface(GetContent());
+  MOZ_ASSERT(txtCtrl, "Content not a text control element");
+  rv = txtCtrl->GetSelectionRange(&selStart, &selEnd);
   NS_ENSURE_SUCCESS(rv, rv);
 
   if (aSelectionStart > selEnd) {
     // Collapse to the new start point.
     selEnd = aSelectionStart;
   }
 
   selStart = aSelectionStart;
@@ -941,17 +934,19 @@ nsTextControlFrame::SetSelectionStart(in
 NS_IMETHODIMP
 nsTextControlFrame::SetSelectionEnd(int32_t aSelectionEnd)
 {
   nsresult rv = EnsureEditorInitialized();
   NS_ENSURE_SUCCESS(rv, rv);
 
   int32_t selStart = 0, selEnd = 0;
 
-  rv = GetSelectionRange(&selStart, &selEnd);
+  nsCOMPtr<nsITextControlElement> txtCtrl = do_QueryInterface(GetContent());
+  MOZ_ASSERT(txtCtrl, "Content not a text control element");
+  rv = txtCtrl->GetSelectionRange(&selStart, &selEnd);
   NS_ENSURE_SUCCESS(rv, rv);
 
   if (aSelectionEnd < selStart) {
     // Collapse to the new end point.
     selStart = aSelectionEnd;
   }
 
   selEnd = aSelectionEnd;
@@ -1013,68 +1008,16 @@ nsTextControlFrame::OffsetToDOMPoint(int
   } else {
     NS_IF_ADDREF(*aResult = rootNode);
     *aPosition = 0;
   }
 
   return NS_OK;
 }
 
-NS_IMETHODIMP
-nsTextControlFrame::GetSelectionRange(int32_t* aSelectionStart,
-                                      int32_t* aSelectionEnd,
-                                      SelectionDirection* aDirection)
-{
-  // make sure we have an editor
-  nsresult rv = EnsureEditorInitialized();
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  if (aSelectionStart) {
-    *aSelectionStart = 0;
-  }
-  if (aSelectionEnd) {
-    *aSelectionEnd = 0;
-  }
-  if (aDirection) {
-    *aDirection = eNone;
-  }
-
-  nsCOMPtr<nsITextControlElement> txtCtrl = do_QueryInterface(GetContent());
-  NS_ASSERTION(txtCtrl, "Content not a text control element");
-  nsISelectionController* selCon = txtCtrl->GetSelectionController();
-  NS_ENSURE_TRUE(selCon, NS_ERROR_FAILURE);
-  nsCOMPtr<nsISelection> selection;
-  rv = selCon->GetSelection(nsISelectionController::SELECTION_NORMAL, getter_AddRefs(selection));
-  NS_ENSURE_SUCCESS(rv, rv);
-  NS_ENSURE_TRUE(selection, NS_ERROR_FAILURE);
-
-  dom::Selection* sel = selection->AsSelection();
-  if (aDirection) {
-    nsDirection direction = sel->GetSelectionDirection();
-    if (direction == eDirNext) {
-      *aDirection = eForward;
-    } else if (direction == eDirPrevious) {
-      *aDirection = eBackward;
-    } else {
-      NS_NOTREACHED("Invalid nsDirection enum value");
-    }
-  }
-
-  if (!aSelectionStart || !aSelectionEnd) {
-    return NS_OK;
-  }
-
-  mozilla::dom::Element* root = GetRootNodeAndInitializeEditor();
-  NS_ENSURE_STATE(root);
-  nsContentUtils::GetSelectionInTextControl(sel, root,
-                                            *aSelectionStart, *aSelectionEnd);
-
-  return NS_OK;
-}
-
 /////END INTERFACE IMPLEMENTATIONS
 
 ////NSIFRAME
 nsresult
 nsTextControlFrame::AttributeChanged(int32_t         aNameSpaceID,
                                      nsIAtom*        aAttribute,
                                      int32_t         aModType)
 {
--- a/layout/forms/nsTextControlFrame.h
+++ b/layout/forms/nsTextControlFrame.h
@@ -142,19 +142,16 @@ public:
 //==== NSITEXTCONTROLFRAME
 
   NS_IMETHOD    GetEditor(nsIEditor **aEditor) override;
   NS_IMETHOD    SetSelectionStart(int32_t aSelectionStart) override;
   NS_IMETHOD    SetSelectionEnd(int32_t aSelectionEnd) override;
   NS_IMETHOD    SetSelectionRange(int32_t aSelectionStart,
                                   int32_t aSelectionEnd,
                                   SelectionDirection aDirection = eNone) override;
-  NS_IMETHOD    GetSelectionRange(int32_t* aSelectionStart,
-                                  int32_t* aSelectionEnd,
-                                  SelectionDirection* aDirection = nullptr) override;
   NS_IMETHOD    GetOwnedSelectionController(nsISelectionController** aSelCon) override;
   virtual nsFrameSelection* GetOwnedFrameSelection() override;
 
   nsresult GetPhonetic(nsAString& aPhonetic) override;
 
   /**
    * Ensure mEditor is initialized with the proper flags and the default value.
    * @throws NS_ERROR_NOT_INITIALIZED if mEditor has not been created
@@ -316,19 +313,23 @@ private:
   nsresult SetSelectionInternal(nsIDOMNode *aStartNode, int32_t aStartOffset,
                                 nsIDOMNode *aEndNode, int32_t aEndOffset,
                                 SelectionDirection aDirection = eNone);
   nsresult SelectAllOrCollapseToEndOfText(bool aSelect);
   nsresult SetSelectionEndPoints(int32_t aSelStart, int32_t aSelEnd,
                                  SelectionDirection aDirection = eNone);
 
   /**
-   * Return the root DOM element, and implicitly initialize the editor if needed.
+   * Return the root DOM element, and implicitly initialize the editor if
+   * needed.
+   *
+   * XXXbz This function is slow.  Very slow.  Consider using
+   * EnsureEditorInitialized() if you need that, and
+   * nsITextControlElement::GetRootEditorNode on our content if you need that.
    */
-  mozilla::dom::Element* GetRootNodeAndInitializeEditor();
   nsresult GetRootNodeAndInitializeEditor(nsIDOMElement **aRootElement);
 
   void FinishedInitializer() {
     Properties().Delete(TextControlInitializer());
   }
 
 private:
   // Our first baseline, or NS_INTRINSIC_WIDTH_UNKNOWN if we have a pending
--- a/layout/generic/ViewportFrame.h
+++ b/layout/generic/ViewportFrame.h
@@ -33,25 +33,16 @@ public:
     : nsContainerFrame(aContext)
   {}
   virtual ~ViewportFrame() { } // useful for debugging
 
   virtual void Init(nsIContent*       aContent,
                     nsContainerFrame* aParent,
                     nsIFrame*         aPrevInFlow) override;
 
-  virtual mozilla::WritingMode GetWritingMode() const override
-  {
-    nsIFrame* firstChild = mFrames.FirstChild();
-    if (firstChild) {
-      return firstChild->GetWritingMode();
-    }
-    return nsIFrame::GetWritingMode();
-  }
-
 #ifdef DEBUG
   virtual void AppendFrames(ChildListID     aListID,
                             nsFrameList&    aFrameList) override;
   virtual void InsertFrames(ChildListID     aListID,
                             nsIFrame*       aPrevFrame,
                             nsFrameList&    aFrameList) override;
   virtual void RemoveFrame(ChildListID     aListID,
                            nsIFrame*       aOldFrame) override;
--- a/layout/generic/nsCanvasFrame.cpp
+++ b/layout/generic/nsCanvasFrame.cpp
@@ -177,55 +177,60 @@ nsCanvasFrame::SetHasFocus(bool aHasFocu
         sf->AddScrollPositionListener(this);
         mAddedScrollPositionListener = true;
       }
     }
   }
   return NS_OK;
 }
 
-#ifdef DEBUG
 void
 nsCanvasFrame::SetInitialChildList(ChildListID     aListID,
                                    nsFrameList&    aChildList)
 {
   NS_ASSERTION(aListID != kPrincipalList ||
                aChildList.IsEmpty() || aChildList.OnlyChild(),
                "Primary child list can have at most one frame in it");
   nsContainerFrame::SetInitialChildList(aListID, aChildList);
+  MaybePropagateRootElementWritingMode();
 }
 
 void
 nsCanvasFrame::AppendFrames(ChildListID     aListID,
                             nsFrameList&    aFrameList)
 {
+#ifdef DEBUG
   MOZ_ASSERT(aListID == kPrincipalList, "unexpected child list");
   if (!mFrames.IsEmpty()) {
     for (nsFrameList::Enumerator e(aFrameList); !e.AtEnd(); e.Next()) {
       // We only allow native anonymous child frames to be in principal child
       // list in canvas frame.
       MOZ_ASSERT(e.get()->GetContent()->IsInNativeAnonymousSubtree(),
                  "invalid child list");
     }
   }
   nsFrame::VerifyDirtyBitSet(aFrameList);
+#endif
   nsContainerFrame::AppendFrames(aListID, aFrameList);
+  MaybePropagateRootElementWritingMode();
 }
 
 void
 nsCanvasFrame::InsertFrames(ChildListID     aListID,
                             nsIFrame*       aPrevFrame,
                             nsFrameList&    aFrameList)
 {
   // Because we only support a single child frame inserting is the same
   // as appending
   MOZ_ASSERT(!aPrevFrame, "unexpected previous sibling frame");
   AppendFrames(aListID, aFrameList);
+  MaybePropagateRootElementWritingMode();
 }
 
+#ifdef DEBUG
 void
 nsCanvasFrame::RemoveFrame(ChildListID     aListID,
                            nsIFrame*       aOldFrame)
 {
   MOZ_ASSERT(aListID == kPrincipalList, "unexpected child list");
   nsContainerFrame::RemoveFrame(aListID, aOldFrame);
 }
 #endif
@@ -797,15 +802,26 @@ nsCanvasFrame::GetContentForEvent(Widget
       rv = kid->GetContentForEvent(aEvent,
                                    aContent);
     }
   }
 
   return rv;
 }
 
+void
+nsCanvasFrame::MaybePropagateRootElementWritingMode()
+{
+  nsIFrame* child = PrincipalChildList().FirstChild();
+  if (child && child->GetContent() &&
+      child->GetContent() == PresContext()->Document()->GetRootElement()) {
+    nsIFrame* childPrimary = child->GetContent()->GetPrimaryFrame();
+    PropagateRootElementWritingMode(childPrimary->GetWritingMode());
+  }
+}
+
 #ifdef DEBUG_FRAME_DUMP
 nsresult
 nsCanvasFrame::GetFrameName(nsAString& aResult) const
 {
   return MakeFrameName(NS_LITERAL_STRING("Canvas"), aResult);
 }
 #endif
--- a/layout/generic/nsCanvasFrame.h
+++ b/layout/generic/nsCanvasFrame.h
@@ -39,29 +39,24 @@ public:
 
   NS_DECL_QUERYFRAME_TARGET(nsCanvasFrame)
   NS_DECL_QUERYFRAME
   NS_DECL_FRAMEARENA_HELPERS
 
 
   virtual void DestroyFrom(nsIFrame* aDestructRoot) override;
 
-  mozilla::WritingMode GetWritingMode() const override
-  {
-    return nsFrame::GetWritingModeDeferringToRootElem();
-  }
-
-#ifdef DEBUG
   virtual void SetInitialChildList(ChildListID     aListID,
                                    nsFrameList&    aChildList) override;
   virtual void AppendFrames(ChildListID     aListID,
                             nsFrameList&    aFrameList) override;
   virtual void InsertFrames(ChildListID     aListID,
                             nsIFrame*       aPrevFrame,
                             nsFrameList&    aFrameList) override;
+#ifdef DEBUG
   virtual void RemoveFrame(ChildListID     aListID,
                            nsIFrame*       aOldFrame) override;
 #endif
 
   virtual nscoord GetMinISize(nsRenderingContext *aRenderingContext) override;
   virtual nscoord GetPrefISize(nsRenderingContext *aRenderingContext) override;
   virtual void Reflow(nsPresContext*           aPresContext,
                       ReflowOutput&     aDesiredSize,
@@ -120,16 +115,20 @@ public:
   virtual nsresult GetFrameName(nsAString& aResult) const override;
 #endif
   virtual nsresult GetContentForEvent(mozilla::WidgetEvent* aEvent,
                                       nsIContent** aContent) override;
 
   nsRect CanvasArea() const;
 
 protected:
+  // Utility function to propagate the WritingMode from our first child to
+  // 'this' and all its ancestors.
+  void MaybePropagateRootElementWritingMode();
+
   // Data members
   bool                      mDoPaintFocus;
   bool                      mAddedScrollPositionListener;
 
   nsCOMPtr<mozilla::dom::Element> mCustomContentContainer;
 };
 
 /**
--- a/layout/generic/nsFrame.cpp
+++ b/layout/generic/nsFrame.cpp
@@ -429,16 +429,17 @@ NS_NewEmptyFrame(nsIPresShell* aPresShel
 }
 
 nsFrame::nsFrame(nsStyleContext* aContext)
 {
   MOZ_COUNT_CTOR(nsFrame);
 
   mState = NS_FRAME_FIRST_REFLOW | NS_FRAME_IS_DIRTY;
   mStyleContext = aContext;
+  mWritingMode = WritingMode(mStyleContext);
   mStyleContext->AddRef();
 #ifdef DEBUG
   mStyleContext->FrameAddRef();
 #endif
 }
 
 nsFrame::~nsFrame()
 {
@@ -554,16 +555,18 @@ nsFrame::Init(nsIContent*       aContent
   mContent = aContent;
   mParent = aParent;
 
   if (aContent) {
     NS_ADDREF(aContent);
   }
 
   if (aPrevInFlow) {
+    mWritingMode = aPrevInFlow->GetWritingMode();
+
     // Make sure the general flags bits are the same
     nsFrameState state = aPrevInFlow->GetStateBits();
 
     // Make bits that are currently off (see constructor) the same:
     mState |= state & (NS_FRAME_INDEPENDENT_SELECTION |
                        NS_FRAME_PART_OF_IBSPLIT |
                        NS_FRAME_MAY_BE_TRANSFORMED |
                        NS_FRAME_MAY_HAVE_GENERATED_CONTENT |
@@ -3078,29 +3081,16 @@ nsFrame::FireDOMEvent(const nsAString& a
   if (target) {
     RefPtr<AsyncEventDispatcher> asyncDispatcher =
       new AsyncEventDispatcher(target, aDOMEventName, true, false);
     DebugOnly<nsresult> rv = asyncDispatcher->PostDOMEvent();
     NS_ASSERTION(NS_SUCCEEDED(rv), "AsyncEventDispatcher failed to dispatch");
   }
 }
 
-WritingMode
-nsFrame::GetWritingModeDeferringToRootElem() const
-{
-  Element* rootElem = PresContext()->Document()->GetRootElement();
-  if (rootElem) {
-    nsIFrame* primaryFrame = rootElem->GetPrimaryFrame();
-    if (primaryFrame) {
-      return primaryFrame->GetWritingMode();
-    }
-  }
-  return nsIFrame::GetWritingMode();
-}
-
 nsresult
 nsFrame::HandleEvent(nsPresContext* aPresContext, 
                      WidgetGUIEvent* aEvent,
                      nsEventStatus* aEventStatus)
 {
 
   if (aEvent->mMessage == eMouseMove) {
     // XXX If the second argument of HandleDrag() is WidgetMouseEvent,
--- a/layout/generic/nsFrame.h
+++ b/layout/generic/nsFrame.h
@@ -683,18 +683,16 @@ protected:
   void GetBoxName(nsAutoString& aName) override;
 #endif
 
   nsBoxLayoutMetrics* BoxMetrics() const;
 
   // Fire DOM event. If no aContent argument use frame's mContent.
   void FireDOMEvent(const nsAString& aDOMEventName, nsIContent *aContent = nullptr);
 
-  mozilla::WritingMode GetWritingModeDeferringToRootElem() const;
-
 private:
   void BoxReflow(nsBoxLayoutState& aState,
                  nsPresContext*    aPresContext,
                  ReflowOutput&     aDesiredSize,
                  nsRenderingContext* aRenderingContext,
                  nscoord aX,
                  nscoord aY,
                  nscoord aWidth,
--- a/layout/generic/nsGfxScrollFrame.cpp
+++ b/layout/generic/nsGfxScrollFrame.cpp
@@ -152,48 +152,48 @@ nsHTMLScrollFrame::DestroyFrom(nsIFrame*
   nsContainerFrame::DestroyFrom(aDestructRoot);
 }
 
 void
 nsHTMLScrollFrame::SetInitialChildList(ChildListID  aListID,
                                        nsFrameList& aChildList)
 {
   nsContainerFrame::SetInitialChildList(aListID, aChildList);
-  mHelper.ReloadChildFrames();
+  ReloadChildFrames();
 }
 
 
 void
 nsHTMLScrollFrame::AppendFrames(ChildListID  aListID,
                                 nsFrameList& aFrameList)
 {
   NS_ASSERTION(aListID == kPrincipalList, "Only main list supported");
   mFrames.AppendFrames(nullptr, aFrameList);
-  mHelper.ReloadChildFrames();
+  ReloadChildFrames();
 }
 
 void
 nsHTMLScrollFrame::InsertFrames(ChildListID aListID,
                                 nsIFrame* aPrevFrame,
                                 nsFrameList& aFrameList)
 {
   NS_ASSERTION(aListID == kPrincipalList, "Only main list supported");
   NS_ASSERTION(!aPrevFrame || aPrevFrame->GetParent() == this,
                "inserting after sibling frame with different parent");
   mFrames.InsertFrames(nullptr, aPrevFrame, aFrameList);
-  mHelper.ReloadChildFrames();
+  ReloadChildFrames();
 }
 
 void
 nsHTMLScrollFrame::RemoveFrame(ChildListID aListID,
                                nsIFrame* aOldFrame)
 {
   NS_ASSERTION(aListID == kPrincipalList, "Only main list supported");
   mFrames.DestroyFrame(aOldFrame);
-  mHelper.ReloadChildFrames();
+  ReloadChildFrames();
 }
 
 nsSplittableType
 nsHTMLScrollFrame::GetSplittableType() const
 {
   return NS_FRAME_NOT_SPLITTABLE;
 }
 
--- a/layout/generic/nsGfxScrollFrame.h
+++ b/layout/generic/nsGfxScrollFrame.h
@@ -671,24 +671,16 @@ public:
   typedef mozilla::ScrollReflowInput ScrollReflowInput;
   friend nsHTMLScrollFrame* NS_NewHTMLScrollFrame(nsIPresShell* aPresShell,
                                                   nsStyleContext* aContext,
                                                   bool aIsRoot);
 
   NS_DECL_QUERYFRAME
   NS_DECL_FRAMEARENA_HELPERS
 
-  virtual mozilla::WritingMode GetWritingMode() const override
-  {
-    if (mHelper.mScrolledFrame) {
-      return mHelper.mScrolledFrame->GetWritingMode();
-    }
-    return nsIFrame::GetWritingMode();
-  }
-
   virtual void BuildDisplayList(nsDisplayListBuilder*   aBuilder,
                                 const nsRect&           aDirtyRect,
                                 const nsDisplayListSet& aLists) override {
     mHelper.BuildDisplayList(aBuilder, aDirtyRect, aLists);
   }
 
   bool TryLayout(ScrollReflowInput* aState,
                    ReflowOutput* aKidMetrics,
@@ -1071,16 +1063,24 @@ protected:
   
   /**
    * Override this to return false if computed bsize/min-bsize/max-bsize
    * should NOT be propagated to child content.
    * nsListControlFrame uses this.
    */
   virtual bool ShouldPropagateComputedBSizeToScrolledContent() const { return true; }
 
+  void ReloadChildFrames()
+  {
+    mHelper.ReloadChildFrames();
+    if (mHelper.mScrolledFrame) {
+      mWritingMode = mHelper.mScrolledFrame->GetWritingMode();
+    }
+  }
+
 private:
   friend class mozilla::ScrollFrameHelper;
   ScrollFrameHelper mHelper;
 };
 
 /**
  * The scroll frame creates and manages the scrolling view
  *
--- a/layout/generic/nsIFrame.h
+++ b/layout/generic/nsIFrame.h
@@ -782,20 +782,25 @@ public:
    * If the frame may have moved into or out of a scrollframe's
    * frame subtree, StickyScrollContainer::NotifyReparentedFrameAcrossScrollFrameBoundary
    * must also be called.
    */
   void SetParent(nsContainerFrame* aParent);
 
   /**
    * The frame's writing-mode, used for logical layout computations.
+   * It's usually the 'writing-mode' computed value, but there are exceptions:
+   *   * inner table frames copy the value from the table frame
+   *     (@see nsTableRowGroupFrame::Init, nsTableRowFrame::Init etc)
+   *   * the root element frame propagates its value to its ancestors
+   *     (@see nsCanvasFrame::MaybePropagateRootElementWritingMode)
+   *   * a scrolled frame propagates its value to its ancestor scroll frame
+   *     (@see nsHTMLScrollFrame::ReloadChildFrames)
    */
-  virtual mozilla::WritingMode GetWritingMode() const {
-    return mozilla::WritingMode(StyleContext());
-  }
+  mozilla::WritingMode GetWritingMode() const { return mWritingMode; }
 
   /**
    * Construct a writing mode for line layout in this frame.  This is
    * the writing mode of this frame, except that if this frame is styled with
    * unicode-bidi:plaintext, we reset the direction to the resolved paragraph
    * level of the given subframe (typically the first frame on the line),
    * because the container frame could be split by hard line breaks into
    * multiple paragraphs with different base direction.
@@ -3584,16 +3589,21 @@ private:
       list = new nsTArray<nsWeakPtr>();
       Properties().Set(PaintedPresShellsProperty(), list);
     }
     
     return list;
   }
 
 protected:
+  /**
+   * Copies aRootElemWM to mWritingMode on 'this' and all its ancestors.
+   */
+  inline void PropagateRootElementWritingMode(mozilla::WritingMode aRootElemWM);
+
   void MarkInReflow() {
 #ifdef DEBUG_dbaron_off
     // bug 81268
     NS_ASSERTION(!(mState & NS_FRAME_IN_REFLOW), "frame is already in reflow");
 #endif
     mState |= NS_FRAME_IN_REFLOW;
   }
 
@@ -3623,16 +3633,19 @@ protected:
       return !(*this == aOther);
     }
   };
   union {
     uint32_t     mType;
     VisualDeltas mVisualDeltas;
   } mOverflow;
 
+  /** @see GetWritingMode() */
+  mozilla::WritingMode mWritingMode;
+
   // Helpers
   /**
    * Can we stop inside this frame when we're skipping non-rendered whitespace?
    * @param  aForward [in] Are we moving forward (or backward) in content order.
    * @param  aOffset [in/out] At what offset into the frame to start looking.
    *         on output - what offset was reached (whether or not we found a place to stop).
    * @return STOP: An appropriate offset was found within this frame,
    *         and is given by aOffset.
--- a/layout/generic/nsIFrameInlines.h
+++ b/layout/generic/nsIFrameInlines.h
@@ -155,9 +155,18 @@ nsIFrame::BaselineBOffset(mozilla::Writi
   }
   if (aAlignmentContext == AlignmentContext::eInline) {
     return SynthesizeBaselineBOffsetFromMarginBox(aWM, aBaselineGroup);
   }
   // XXX AlignmentContext::eTable should use content box?
   return SynthesizeBaselineBOffsetFromBorderBox(aWM, aBaselineGroup);
 }
 
+void
+nsIFrame::PropagateRootElementWritingMode(mozilla::WritingMode aRootElemWM)
+{
+  MOZ_ASSERT(GetType() == nsGkAtoms::canvasFrame);
+  for (auto f = this; f; f = f->GetParent()) {
+    f->mWritingMode = aRootElemWM;
+  }
+}
+
 #endif
--- a/layout/generic/nsPageContentFrame.h
+++ b/layout/generic/nsPageContentFrame.h
@@ -39,21 +39,16 @@ public:
 
   /**
    * Get the "type" of the frame
    *
    * @see nsGkAtoms::pageContentFrame
    */
   virtual nsIAtom* GetType() const override;
 
-  mozilla::WritingMode GetWritingMode() const override
-  {
-    return nsFrame::GetWritingModeDeferringToRootElem();
-  }
-
 #ifdef DEBUG_FRAME_DUMP
   // Debugging
   virtual nsresult  GetFrameName(nsAString& aResult) const override;
 #endif
 
 protected:
   explicit nsPageContentFrame(nsStyleContext* aContext) : ViewportFrame(aContext) {}
 
--- a/layout/generic/nsPageFrame.h
+++ b/layout/generic/nsPageFrame.h
@@ -27,21 +27,16 @@ public:
                       ReflowOutput& aDesiredSize,
                       const ReflowInput& aMaxSize,
                       nsReflowStatus&      aStatus) override;
 
   virtual void BuildDisplayList(nsDisplayListBuilder*   aBuilder,
                                 const nsRect&           aDirtyRect,
                                 const nsDisplayListSet& aLists) override;
 
-  mozilla::WritingMode GetWritingMode() const override
-  {
-    return nsFrame::GetWritingModeDeferringToRootElem();
-  }
-
   /**
    * Get the "type" of the frame
    *
    * @see nsGkAtoms::pageFrame
    */
   virtual nsIAtom* GetType() const override;
 
 #ifdef DEBUG_FRAME_DUMP
--- a/layout/generic/nsSimplePageSequenceFrame.h
+++ b/layout/generic/nsSimplePageSequenceFrame.h
@@ -55,21 +55,16 @@ class nsSimplePageSequenceFrame : public
                                   public nsIPageSequenceFrame {
 public:
   friend nsSimplePageSequenceFrame* NS_NewSimplePageSequenceFrame(nsIPresShell* aPresShell,
                                                                   nsStyleContext* aContext);
 
   NS_DECL_QUERYFRAME
   NS_DECL_FRAMEARENA_HELPERS
 
-  mozilla::WritingMode GetWritingMode() const override
-  {
-    return nsFrame::GetWritingModeDeferringToRootElem();
-  }
-
   // nsIFrame
   void Reflow(nsPresContext* aPresContext,
               ReflowOutput& aDesiredSize,
               const ReflowInput& aMaxSize,
               nsReflowStatus& aStatus) override;
 
   void BuildDisplayList(nsDisplayListBuilder*   aBuilder,
                         const nsRect&           aDirtyRect,
--- a/layout/reftests/backgrounds/reftest-stylo.list
+++ b/layout/reftests/backgrounds/reftest-stylo.list
@@ -147,19 +147,19 @@ fails == table-background-print.html tab
 == background-repeat-1-ref.html background-repeat-1-ref.html
 
 == multi-background-clip-content-border.html multi-background-clip-content-border.html
 
 fails HTTP == background-referrer.html background-referrer.html
 
 == attachment-scroll-positioning-1.html attachment-scroll-positioning-1.html
 == attachment-local-positioning-1.html attachment-local-positioning-1.html
-fails == attachment-local-positioning-2.html attachment-local-positioning-2.html
-fails == attachment-local-positioning-3.html attachment-local-positioning-3.html
-fails == attachment-local-positioning-4.html attachment-local-positioning-4.html
+== attachment-local-positioning-2.html attachment-local-positioning-2.html
+== attachment-local-positioning-3.html attachment-local-positioning-3.html
+== attachment-local-positioning-4.html attachment-local-positioning-4.html
 == attachment-local-positioning-5.html attachment-local-positioning-5.html
 
 == attachment-local-clipping-color-1.html attachment-local-clipping-color-1.html
 == attachment-local-clipping-color-2.html attachment-local-clipping-color-2.html
 == attachment-local-clipping-color-3.html attachment-local-clipping-color-3.html
 == attachment-local-clipping-color-4.html attachment-local-clipping-color-4.html
 == attachment-local-clipping-color-5.html attachment-local-clipping-color-5.html
 == attachment-local-clipping-color-6.html attachment-local-clipping-color-6.html
--- a/layout/reftests/bugs/reftest-stylo.list
+++ b/layout/reftests/bugs/reftest-stylo.list
@@ -87,17 +87,17 @@ fails == 82711-1.html 82711-1.html
 fails == 82711-2.html 82711-2.html
 fails == 82711-3.html 82711-3.html
 fails == 82711-1-ref.html 82711-1-ref.html # Bug 1341697, Bug 1341637
 fails == 82711-1-ref.html 82711-1-ref.html # Bug 1341697, Bug 1341637
 fails == 82711-2-ref.html 82711-2-ref.html
 fails == 84400-1.html 84400-1.html
 fails == 84400-2.html 84400-2.html
 == 97777-1.html 97777-1.html
-fails == 97777-2.html 97777-2.html
+== 97777-2.html 97777-2.html
 == 98223-1.html 98223-1.html
 == 98223-2.html 98223-2.html
 skip-if(stylo) == 99850-1a.html 99850-1a.html # bug 1337695
 skip-if(stylo) == 99850-1b.html 99850-1b.html # bug 1337695
 fails == 99850-1c.html 99850-1c.html
 skip-if(stylo) == 99850-1d.html 99850-1d.html # bug 1337695
 == 105030-1.html 105030-1.html
 == 109735-1.html 109735-1.html
@@ -686,22 +686,22 @@ fails == 378933-1.html 378933-1.html
 fails == 378935-1.html 378935-1.html
 fails == 378937-1.html 378937-1.html
 == 379178-xhtml.xhtml 379178-xhtml.xhtml
 fails == 379178-html.html 379178-html.html
 == 379178-svg.svg 379178-svg.svg
 == 379316-1.html 379316-1.html
 == 379316-2.html 379316-2.html
 == 379328-1.html 379328-1.html
-fails == 379349-1a.xhtml 379349-1a.xhtml
+== 379349-1a.xhtml 379349-1a.xhtml
 # fuzzy because of different border rendering approach in bug 1185636
-fails == 379349-1b.xhtml 379349-1b.xhtml
-fails == 379349-1c.xhtml 379349-1c.xhtml
-fails == 379349-2a.xhtml 379349-2a.xhtml
-fails == 379349-2b.xhtml 379349-2b.xhtml
+== 379349-1b.xhtml 379349-1b.xhtml
+== 379349-1c.xhtml 379349-1c.xhtml
+== 379349-2a.xhtml 379349-2a.xhtml
+== 379349-2b.xhtml 379349-2b.xhtml
 fails == 379349-3a.xhtml 379349-3a.xhtml
 fails == 379349-3b.xhtml 379349-3b.xhtml
 fails == 379361-1.html 379361-1.html
 fails == 379361-2.html 379361-2.html
 fails == 379361-3.html 379361-3.html
 == 379461-1.xhtml 379461-1.xhtml
 == 379461-2.xhtml 379461-2.xhtml
 fails == 379461-3-container-xhtml.html 379461-3-container-xhtml.html # Bug 1341095
@@ -714,22 +714,22 @@ fails == 380825-1.html 380825-1.html
 fails == 381507-1.html 381507-1.html
 == 381746-1.html 381746-1.html
 fails == 382600-1.html 382600-1.html
 fails == 382916-1.html 382916-1.html
 == 383035-1.html 383035-1.html
 == 383035-2.html 383035-2.html
 == 383488-1.html 383488-1.html
 == 383551-1.html 383551-1.html
-fails == 383883-1.html 383883-1.html
-fails == 383883-2.html 383883-2.html
-fails == 383883-3.html 383883-3.html
-fails == 383883-4.html 383883-4.html
-fails == 383884-1.html 383884-1.html
-fails == 383885-1.html 383885-1.html
+== 383883-1.html 383883-1.html
+== 383883-2.html 383883-2.html
+== 383883-3.html 383883-3.html
+== 383883-4.html 383883-4.html
+== 383884-1.html 383884-1.html
+== 383885-1.html 383885-1.html
 fails == 384322-1.html 384322-1.html
 == 384576-1.html 384576-1.html
 == 384762-1.html 384762-1.html
 == 384876-1.html 384876-1.html
 == 385533-1.html 385533-1.html
 fails HTTP(..) == 385569-1a.html 385569-1a.html
 fails HTTP(..) == 385569-1b.html 385569-1b.html
 == 385607-1.html 385607-1.html
@@ -816,17 +816,17 @@ fails == 395107-4.html 395107-4.html
 fails == 395107-5.html 395107-5.html
 fails == 395130-1.html 395130-1.html
 fails == 395130-2.html 395130-2.html
 == 395331-1.xml 395331-1.xml
 fails == 395390-1.html 395390-1.html
 == 396286-1.html 396286-1.html
 fails == 397428-1.html 397428-1.html
 == 397844-1.xhtml 397844-1.xhtml
-fails == 398092-1.html 398092-1.html
+== 398092-1.html 398092-1.html
 fails == 398101-1.html 398101-1.html
 == 398144-1.html 398144-1.html
 == 398682-1.html 398682-1.html
 == 398797-1a.html 398797-1a.html
 == 398797-1b.html 398797-1b.html
 == 398797-1c.html 398797-1c.html
 == 398797-1d.html 398797-1d.html
 == 399209-1.html 399209-1.html
@@ -982,17 +982,17 @@ fails == 413840-pushed-line-bullet.html 
 == 413982.html 413982.html
 fails == 414123.xhtml 414123.xhtml
 == 414638.html 414638.html
 fails == 414851-1.html 414851-1.html
 fails == 416106-1.xhtml 416106-1.xhtml
 == 416752-1.html 416752-1.html
 == 417178-1.html 417178-1.html
 == 417246-1.html 417246-1.html
-fails == 417676.html 417676.html
+== 417676.html 417676.html
 fails asserts(1) == 418574-1.html 418574-1.html
 fails asserts(1) == 418574-2.html 418574-2.html
 == 418766-1a.html 418766-1a.html
 == 418766-1b.html 418766-1b.html
 == 419060.html 419060.html
 == 419285-1.html 419285-1.html
 fails == 419531-1.html 419531-1.html
 == 420069-1.html 420069-1.html
--- a/layout/reftests/columns/reftest-stylo.list
+++ b/layout/reftests/columns/reftest-stylo.list
@@ -1,28 +1,28 @@
 # DO NOT EDIT! This is a auto-generated temporary list for Stylo testing
 == basic-1.html basic-1.html
 == pref-width-1a.html pref-width-1a.html
 == pref-width-1b.html pref-width-1b.html
 == pref-width-1c.html pref-width-1c.html
 fails == min-width-1a.html min-width-1a.html
 == min-width-1b.html min-width-1b.html
 == min-width-1c.html min-width-1c.html
-fails == min-width-2.html min-width-2.html
-fails == column-balancing-overflow-000.html column-balancing-overflow-000.html
-fails == column-balancing-overflow-001.html column-balancing-overflow-001.html
-fails == column-balancing-overflow-002.html column-balancing-overflow-002.html
-fails == column-balancing-overflow-003.html column-balancing-overflow-003.html
+== min-width-2.html min-width-2.html
+== column-balancing-overflow-000.html column-balancing-overflow-000.html
+== column-balancing-overflow-001.html column-balancing-overflow-001.html
+== column-balancing-overflow-002.html column-balancing-overflow-002.html
+== column-balancing-overflow-003.html column-balancing-overflow-003.html
 == column-balancing-overflow-004.html column-balancing-overflow-004.html
 == column-balancing-overflow-005.html column-balancing-overflow-005.html
-fails == column-balancing-000.html column-balancing-000.html
-fails == column-balancing-001.html column-balancing-001.html
-fails == column-balancing-002.html column-balancing-002.html
-fails == column-balancing-003.html column-balancing-003.html
-fails == column-balancing-004.html column-balancing-004.html
+== column-balancing-000.html column-balancing-000.html
+== column-balancing-001.html column-balancing-001.html
+== column-balancing-002.html column-balancing-002.html
+== column-balancing-003.html column-balancing-003.html
+== column-balancing-004.html column-balancing-004.html
 == column-box-alignment-rtl.html column-box-alignment-rtl.html
 fails HTTP(..) == columnfill-balance.html columnfill-balance.html
 fails == columnfill-auto.html columnfill-auto.html
 fails HTTP(..) == columnfill-auto-2.html columnfill-auto-2.html
 fails HTTP(..) == columnfill-auto-3.html columnfill-auto-3.html
 fails == columnrule-basic.html columnrule-basic.html
 fails == columnrule-complex.html columnrule-complex.html
 == columnrule-linestyles.html columnrule-linestyles.html
--- a/layout/reftests/css-break/reftest-stylo.list
+++ b/layout/reftests/css-break/reftest-stylo.list
@@ -5,9 +5,9 @@ fails == box-decoration-break-1.html box
 fails == box-decoration-break-with-inset-box-shadow-1.html box-decoration-break-with-inset-box-shadow-1.html
 fails == box-decoration-break-with-outset-box-shadow-1.html box-decoration-break-with-outset-box-shadow-1.html
 fails == box-decoration-break-border-image.html box-decoration-break-border-image.html
 == box-decoration-break-block-border-padding.html box-decoration-break-block-border-padding.html
 == box-decoration-break-block-margin.html box-decoration-break-block-margin.html
 fails == box-decoration-break-first-letter.html box-decoration-break-first-letter.html
 == box-decoration-break-with-bidi.html box-decoration-break-with-bidi.html
 == box-decoration-break-bug-1235152.html box-decoration-break-bug-1235152.html
-fails == box-decoration-break-bug-1249913.html box-decoration-break-bug-1249913.html
+== box-decoration-break-bug-1249913.html box-decoration-break-bug-1249913.html
--- a/layout/reftests/invalidation/reftest-stylo.list
+++ b/layout/reftests/invalidation/reftest-stylo.list
@@ -83,12 +83,12 @@ fails pref(layers.single-tile.enabled,fa
 fails == mask-invalidation-1a.html mask-invalidation-1a.html
 fails == mask-invalidation-1b.html mask-invalidation-1b.html
 
 fails == mask-invalidation-2a.html mask-invalidation-2a.html
 fails == mask-invalidation-2b.html mask-invalidation-2b.html
 fails == mask-invalidation-2c.html mask-invalidation-2c.html
 fails == mask-invalidation-2d.html mask-invalidation-2d.html
 
-fails == clip-path-invalidation-1a.html clip-path-invalidation-1a.html
-fails == clip-path-invalidation-1b.html clip-path-invalidation-1b.html
+== clip-path-invalidation-1a.html clip-path-invalidation-1a.html
+== clip-path-invalidation-1b.html clip-path-invalidation-1b.html
 == clip-path-invalidation-1c.html clip-path-invalidation-1c.html
 == clip-path-invalidation-1d.html clip-path-invalidation-1d.html
--- a/layout/reftests/pagination/reftest-stylo.list
+++ b/layout/reftests/pagination/reftest-stylo.list
@@ -11,45 +11,45 @@ fails == abspos-breaking-002.xhtml abspo
 == abspos-breaking-006.html abspos-breaking-006.html
 pref(layout.css.box-decoration-break.enabled,true) == abspos-breaking-007.html abspos-breaking-007.html
 pref(layout.css.box-decoration-break.enabled,true) == abspos-breaking-008.html abspos-breaking-008.html
 pref(layout.css.box-decoration-break.enabled,true) == abspos-breaking-009.html abspos-breaking-009.html
 pref(layout.css.box-decoration-break.enabled,true) == abspos-breaking-010.html abspos-breaking-010.html
 == abspos-breaking-011.html abspos-breaking-011.html
 == abspos-overflow-01.xhtml abspos-overflow-01.xhtml
 == abspos-overflow-01-cols.xhtml abspos-overflow-01-cols.xhtml
-fails == border-breaking-000-cols.xhtml border-breaking-000-cols.xhtml
-fails == border-breaking-001-cols.xhtml border-breaking-001-cols.xhtml
-fails == border-breaking-002-cols.xhtml border-breaking-002-cols.xhtml
-fails == border-breaking-003-cols.xhtml border-breaking-003-cols.xhtml
-fails == border-breaking-004-cols.xhtml border-breaking-004-cols.xhtml
+== border-breaking-000-cols.xhtml border-breaking-000-cols.xhtml
+== border-breaking-001-cols.xhtml border-breaking-001-cols.xhtml
+== border-breaking-002-cols.xhtml border-breaking-002-cols.xhtml
+== border-breaking-003-cols.xhtml border-breaking-003-cols.xhtml
+== border-breaking-004-cols.xhtml border-breaking-004-cols.xhtml
 == content-inserted-000.xhtml content-inserted-000.xhtml
 == content-inserted-001.xhtml content-inserted-001.xhtml
 == content-inserted-002.xhtml content-inserted-002.xhtml
 == content-inserted-003.xhtml content-inserted-003.xhtml
 == content-inserted-004.xhtml content-inserted-004.xhtml
 == content-inserted-005.xhtml content-inserted-005.xhtml
 == content-inserted-006.xhtml content-inserted-006.xhtml
 == content-inserted-007.xhtml content-inserted-007.xhtml
 == content-inserted-008.xhtml content-inserted-008.xhtml
 == content-inserted-009.xhtml content-inserted-009.xhtml
 == dynamic-abspos-overflow-01-cols.xhtml dynamic-abspos-overflow-01-cols.xhtml
-fails pref(layout.float-fragments-inside-column.enabled,false) == float-clear-000.html float-clear-000.html
-fails pref(layout.float-fragments-inside-column.enabled,true) == float-clear-000.html float-clear-000.html
+pref(layout.float-fragments-inside-column.enabled,false) == float-clear-000.html float-clear-000.html
+pref(layout.float-fragments-inside-column.enabled,true) == float-clear-000.html float-clear-000.html
 fails == float-clear-001.html float-clear-001.html
-fails pref(layout.float-fragments-inside-column.enabled,false) == float-clear-002.html float-clear-002.html
-fails pref(layout.float-fragments-inside-column.enabled,true) == float-clear-002.html float-clear-002.html
-fails == float-clear-003.html float-clear-003.html
-fails == float-clear-000-print.html float-clear-000-print.html
-fails == float-clear-001-print.html float-clear-001-print.html
-fails == float-clear-002-print.html float-clear-002-print.html
-fails == float-clear-003-print.html float-clear-003-print.html
-fails == float-continuations-000.html float-continuations-000.html
-fails == resize-reflow-000.html resize-reflow-000.html
-fails == resize-reflow-001.html resize-reflow-001.html
+pref(layout.float-fragments-inside-column.enabled,false) == float-clear-002.html float-clear-002.html
+pref(layout.float-fragments-inside-column.enabled,true) == float-clear-002.html float-clear-002.html
+== float-clear-003.html float-clear-003.html
+== float-clear-000-print.html float-clear-000-print.html
+== float-clear-001-print.html float-clear-001-print.html
+== float-clear-002-print.html float-clear-002-print.html
+== float-clear-003-print.html float-clear-003-print.html
+== float-continuations-000.html float-continuations-000.html
+== resize-reflow-000.html resize-reflow-000.html
+== resize-reflow-001.html resize-reflow-001.html
 == table-page-break-before-auto-1.html table-page-break-before-auto-1.html
 fails == table-page-break-before-auto-2.html table-page-break-before-auto-2-ref.html
 == table-page-break-before-always-1.html table-page-break-before-always-1.html
 == table-page-break-before-left-1.html table-page-break-before-left-1.html
 == table-page-break-before-right-1.html table-page-break-before-right-1.html
 == table-page-break-after-always-1.html table-page-break-after-always-1.html
 == table-page-break-after-left-1.html table-page-break-after-left-1.html
 == table-page-break-after-right-1.html table-page-break-after-right-1.html
--- a/layout/reftests/printing/reftest-stylo.list
+++ b/layout/reftests/printing/reftest-stylo.list
@@ -25,12 +25,12 @@ fails == 115199-1.html 115199-1.html
 == 115199-2a.html 115199-2a.html
 == 115199-2b.html 115199-2b.html
 == 652178-1.html 652178-1.html
 == 745025-1.html 745025-1.html
 fails == 820496-1.html 820496-1.html
 
 # NOTE: These tests don't yet rigorously test what they're
 # trying to test (shrink-to-fit behavior), due to bug 967311.
-fails == 960822.html 960822.html
-fails == 966419-1.html 966419-1.html
-fails == 966419-2.html 966419-2.html
+== 960822.html 960822.html
+== 966419-1.html 966419-1.html
+== 966419-2.html 966419-2.html
 fails HTTP(..) == 1108104.html 1108104-ref.html
--- a/layout/reftests/table-anonymous-boxes/reftest-stylo.list
+++ b/layout/reftests/table-anonymous-boxes/reftest-stylo.list
@@ -31,17 +31,17 @@
 == 394402-1a.html 394402-1a.html
 == 394402-1b.html 394402-1b.html
 == 398095-1.html 398095-1.html
 == 407115-1.html 407115-1.html
 fails == 443616-1a.xhtml 443616-1a.xhtml
 fails == 443616-1b.html 443616-1b.html
 == 448111-1.html 448111-1.html
 fails == 490174-1.html 490174-1.html
-fails == 695538-1.html 695538-1.html
+== 695538-1.html 695538-1.html
 fails == infer-first-row.html infer-first-row.html
 fails == infer-first-row-and-table.html infer-first-row-and-table.html
 fails == infer-second-row.html infer-second-row.html
 fails == infer-second-row-and-table.html infer-second-row-and-table.html
 fails == infer-table-around-headers-footers-1.html infer-table-around-headers-footers-1.html
 fails == infer-table-around-headers-footers-2.html infer-table-around-headers-footers-2.html
 fails == infer-table-around-headers-footers-3.html infer-table-around-headers-footers-3.html
 fails == infer-rows-inside-rowgroups.html infer-rows-inside-rowgroups.html
--- a/layout/reftests/w3c-css/submitted/multicol3/reftest-stylo.list
+++ b/layout/reftests/w3c-css/submitted/multicol3/reftest-stylo.list
@@ -1,3 +1,3 @@
 # DO NOT EDIT! This is a auto-generated temporary list for Stylo testing
-fails == moz-multicol3-column-balancing-break-inside-avoid-1.html moz-multicol3-column-balancing-break-inside-avoid-1.html
+== moz-multicol3-column-balancing-break-inside-avoid-1.html moz-multicol3-column-balancing-break-inside-avoid-1.html
 fails == multicol-height-002.xht multicol-height-002.xht
--- a/layout/reftests/w3c-css/submitted/variables/reftest-stylo.list
+++ b/layout/reftests/w3c-css/submitted/variables/reftest-stylo.list
@@ -98,17 +98,17 @@ fails == variable-font-face-02.html vari
 == variable-reference-30.html variable-reference-30.html
 == variable-reference-31.html variable-reference-31.html
 == variable-reference-32.html variable-reference-32.html
 == variable-reference-33.html variable-reference-33.html
 == variable-reference-34.html variable-reference-34.html
 == variable-reference-35.html variable-reference-35.html
 fails == variable-reference-36.html variable-reference-36.html
 == variable-reference-37.html variable-reference-37.html
-fails == variable-reference-38.html variable-reference-38.html
+== variable-reference-38.html variable-reference-38.html
 == variable-reference-39.html variable-reference-39.html
 == variable-reference-40.html variable-reference-40.html
 == variable-supports-01.html variable-supports-01.html
 == variable-supports-02.html variable-supports-02.html
 == variable-supports-03.html variable-supports-03.html
 == variable-supports-04.html variable-supports-04.html
 == variable-supports-05.html variable-supports-05.html
 == variable-supports-06.html variable-supports-06.html
--- a/layout/reftests/writing-mode/tables/reftest-stylo.list
+++ b/layout/reftests/writing-mode/tables/reftest-stylo.list
@@ -81,14 +81,14 @@ fails == wm-row-progression-007.xht wm-r
 == table-caption-right-1.html table-caption-right-1.html
 
 fuzzy-if(stylo,1,7400) == border-collapse-bevels-1a.html border-collapse-bevels-1a.html
 fuzzy-if(stylo,1,7400) fuzzy-if(cocoaWidget,23,162) == border-collapse-bevels-1b.html border-collapse-bevels-1b.html
 fuzzy-if(stylo,1,7400) fuzzy-if(cocoaWidget,23,162) == border-collapse-bevels-1c.html border-collapse-bevels-1c.html
 fuzzy-if(stylo,1,7400) fuzzy-if(cocoaWidget,23,162) == border-collapse-bevels-1d.html border-collapse-bevels-1d.html
 fuzzy-if(stylo,1,7400) fuzzy-if(cocoaWidget,23,162) == border-collapse-bevels-1e.html border-collapse-bevels-1e.html
 
-fails == vertical-rl-row-progression-1a.html vertical-rl-row-progression-1a.html
-fails == vertical-rl-row-progression-1b.html vertical-rl-row-progression-1b.html
+== vertical-rl-row-progression-1a.html vertical-rl-row-progression-1a.html
+== vertical-rl-row-progression-1b.html vertical-rl-row-progression-1b.html
 fails == sideways-lr-row-progression-1a.html sideways-lr-row-progression-1a.html
 fails == sideways-lr-row-progression-1b.html sideways-lr-row-progression-1b.html
 fails == sideways-rl-row-progression-1a.html sideways-rl-row-progression-1a.html
 fails == sideways-rl-row-progression-1b.html sideways-rl-row-progression-1b.html
--- a/layout/style/Declaration.cpp
+++ b/layout/style/Declaration.cpp
@@ -7,16 +7,17 @@
  * representation of a declaration block (or style attribute) in a CSS
  * stylesheet
  */
 
 #include "mozilla/css/Declaration.h"
 
 #include "mozilla/ArrayUtils.h"
 #include "mozilla/MemoryReporting.h"
+#include "mozilla/ServoStyleSet.h"
 
 #include "mozilla/css/Rule.h"
 #include "nsPrintfCString.h"
 #include "gfxFontConstants.h"
 #include "nsStyleUtil.h"
 
 namespace mozilla {
 namespace css {
@@ -1697,16 +1698,20 @@ Declaration::AppendVariableAndValueToStr
     aResult.AppendLiteral("!important");
   }
   aResult.AppendLiteral("; ");
 }
 
 void
 Declaration::ToString(nsAString& aString) const
 {
+  // Tell the static analysis not to worry about thread-unsafe things here
+  // because because this function isn't reached during parallel style traversal.
+  MOZ_ASSERT(!ServoStyleSet::IsInServoTraversal());
+
   nsCSSCompressedDataBlock *systemFontData =
     GetPropertyIsImportantByID(eCSSProperty__x_system_font) ? mImportantData
                                                             : mData;
   const nsCSSValue *systemFont =
     systemFontData->ValueFor(eCSSProperty__x_system_font);
   const bool haveSystemFont = systemFont &&
                                 systemFont->GetUnit() != eCSSUnit_None &&
                                 systemFont->GetUnit() != eCSSUnit_Null;
--- a/layout/style/nsCSSPseudoClassList.h
+++ b/layout/style/nsCSSPseudoClassList.h
@@ -115,17 +115,18 @@ CSS_PSEUDO_CLASS(mozLWThemeBrightText, "
 // -moz-lwtheme-darktext matches a document that has a bright lightweight theme
 CSS_PSEUDO_CLASS(mozLWThemeDarkText, ":-moz-lwtheme-darktext", 0, "")
 
 // Matches anything when the containing window is inactive
 CSS_PSEUDO_CLASS(mozWindowInactive, ":-moz-window-inactive", 0, "")
 
 // Matches any table elements that have a nonzero border attribute,
 // according to HTML integer attribute parsing rules.
-CSS_PSEUDO_CLASS(mozTableBorderNonzero, ":-moz-table-border-nonzero", 0, "")
+CSS_PSEUDO_CLASS(mozTableBorderNonzero, ":-moz-table-border-nonzero",
+                 CSS_PSEUDO_CLASS_ENABLED_IN_UA_SHEETS, "")
 
 // Matches HTML frame/iframe elements which are mozbrowser.
 CSS_PSEUDO_CLASS(mozBrowserFrame, ":-moz-browser-frame",
                  CSS_PSEUDO_CLASS_ENABLED_IN_UA_SHEETS_AND_CHROME, "")
 
 // Matches whatever the contextual reference elements are for the
 // matching operation.
 CSS_PSEUDO_CLASS(scope, ":scope", 0, "layout.css.scope-pseudo.enabled")
--- a/layout/style/nsStyleContext.cpp
+++ b/layout/style/nsStyleContext.cpp
@@ -1197,16 +1197,21 @@ nsStyleContext::CalcStyleDifferenceInter
   // call GetVisitedDependentColor.
   nsStyleContext *thisVis = GetStyleIfVisited(),
                 *otherVis = aNewContext->GetStyleIfVisited();
   if (!thisVis != !otherVis) {
     // One style context has a style-if-visited and the other doesn't.
     // Presume a difference.
     hint |= nsChangeHint_RepaintFrame;
   } else if (thisVis && !NS_IsHintSubset(nsChangeHint_RepaintFrame, hint)) {
+    // We'll handle visited style differently in servo. Assert against being
+    // in the parallel traversal to avoid static analysis hazards when calling
+    // StyleFoo() below.
+    MOZ_ASSERT(!ServoStyleSet::IsInServoTraversal());
+
     // Both style contexts have a style-if-visited.
     bool change = false;
 
     // NB: Calling Peek on |this|, not |thisVis|, since callers may look
     // at a struct on |this| without looking at the same struct on
     // |thisVis| (including this function if we skip one of these checks
     // due to change being true already or due to the old style context
     // not having a style-if-visited), but not the other way around.
--- a/layout/style/nsStyleStruct.cpp
+++ b/layout/style/nsStyleStruct.cpp
@@ -3605,16 +3605,18 @@ nsChangeHint
 nsStyleVisibility::CalcDifference(const nsStyleVisibility& aNewData) const
 {
   nsChangeHint hint = nsChangeHint(0);
 
   if (mDirection != aNewData.mDirection || mWritingMode != aNewData.mWritingMode) {
     // It's important that a change in mWritingMode results in frame
     // reconstruction, because it may affect intrinsic size (see
     // nsSubDocumentFrame::GetIntrinsicISize/BSize).
+    // Also, the used writing-mode value is now a field on nsIFrame and some
+    // classes (e.g. table rows/cells) copy their value from an ancestor.
     hint |= nsChangeHint_ReconstructFrame;
   } else {
     if ((mImageOrientation != aNewData.mImageOrientation)) {
       hint |= nsChangeHint_AllReflowHints |
               nsChangeHint_RepaintFrame;
     }
     if (mVisible != aNewData.mVisible) {
       if ((NS_STYLE_VISIBILITY_COLLAPSE == mVisible) ||
--- a/layout/style/nsStyleStructInlines.h
+++ b/layout/style/nsStyleStructInlines.h
@@ -146,17 +146,17 @@ nsStyleDisplay::HasTransform(const nsIFr
 
 template<class StyleContextLike>
 bool
 nsStyleDisplay::HasFixedPosContainingBlockStyleInternal(
                   StyleContextLike* aStyleContext) const
 {
   // NOTE: Any CSS properties that influence the output of this function
   // should have the CSS_PROPERTY_FIXPOS_CB set on them.
-  NS_ASSERTION(aStyleContext->StyleDisplay() == this,
+  NS_ASSERTION(aStyleContext->ThreadsafeStyleDisplay() == this,
                "unexpected aStyleContext");
 
   if (IsContainPaint() || HasPerspectiveStyle()) {
     return true;
   }
 
   if (mWillChangeBitField & NS_STYLE_WILL_CHANGE_FIXPOS_CB) {
     return true;
--- a/layout/style/test/mochitest.ini
+++ b/layout/style/test/mochitest.ini
@@ -297,20 +297,21 @@ support-files = file_transitions_with_di
 [test_unicode_range_loading.html]
 skip-if = stylo # timeout bug 1328507
 support-files = ../../reftests/fonts/markA.woff ../../reftests/fonts/markB.woff ../../reftests/fonts/markC.woff ../../reftests/fonts/markD.woff
 [test_units_angle.html]
 [test_units_frequency.html]
 [test_units_length.html]
 [test_units_time.html]
 [test_value_cloning.html]
-skip-if = toolkit == 'android' # bug 775227 for android
+skip-if = (toolkit == 'android' || stylo) # bug 775227 for android, bug 1343168 for stylo
 [test_value_computation.html]
-skip-if = toolkit == 'android'
+skip-if = (toolkit == 'android' || stylo) # bug 1343168 for stylo
 [test_value_storage.html]
+skip-if = stylo # bug 1343168
 [test_variable_serialization_computed.html]
 [test_variable_serialization_specified.html]
 [test_variables.html]
 support-files = support/external-variable-url.css
 [test_video_object_fit.html]
 [test_viewport_units.html]
 [test_visited_image_loading.html]
 skip-if = (toolkit == 'android' || stylo) # TIMED_OUT for android, timeout bug 1328511 for stylo
--- a/layout/style/test/test_selectors.html
+++ b/layout/style/test/test_selectors.html
@@ -1068,16 +1068,17 @@ function run() {
     test_unbalanced_unparseable(":-moz-handler-vulnerable-updatable");
     test_unbalanced_unparseable(":-moz-handler-vulnerable-no-update");
     test_unbalanced_unparseable(":-moz-handler-disabled");
     test_unbalanced_unparseable(":-moz-handler-blocked");
     test_unbalanced_unparseable(":-moz-handler-crashed");
 
     // We're not in a UA sheet, so this should be invalid.
     test_balanced_unparseable(":-moz-native-anonymous");
+    test_balanced_unparseable(":-moz-table-border-nonzero");
 
     // Case sensitivity of tag selectors
     function setup_cased_spans(body) {
       var data = [
         { tag: "span" },
         { tag: "sPaN" },
         { tag: "Span" },
         { tag: "SPAN" },
@@ -1202,29 +1203,16 @@ function run() {
     test_selector_in_html(":-moz-any(:link,:not(a))", any_elts,
                           bodychildset([0, 1, 2]), bodychildset([3]));
     test_selector_in_html(":-moz-any([href],input[type],input[name])", any_elts,
                           bodychildset([0, 1]), bodychildset([2, 3]));
     test_selector_in_html(":-moz-any(div,a):-moz-any([type],[href],[name])",
                           any_elts,
                           bodychildset([1, 3]), bodychildset([0, 2]));
 
-    test_selector_in_html(":-moz-table-border-nonzero",
-                          "<p></p>" +
-                          "<p border='2'></p>" +
-                          "<table border='2'></table>" +
-                          "<table border></table>" +
-                          "<table></table>" +
-                          "<table frame='border'></table>" +
-                          "<table border='0'></table>" +
-                          "<table border='0pt'></table>" +
-                          "<table border='3pt'></table>",
-                          bodychildset([2, 3, 8]),
-                          bodychildset([0, 1, 4, 5, 6, 7]));
-
     // Test that we don't tokenize an empty HASH.
     test_balanced_unparseable("#");
     test_balanced_unparseable("# ");
     test_balanced_unparseable("#, p");
     test_balanced_unparseable("# , p");
     test_balanced_unparseable("p #");
     test_balanced_unparseable("p # ");
     test_balanced_unparseable("p #, p");
--- a/layout/tables/nsTableCellFrame.cpp
+++ b/layout/tables/nsTableCellFrame.cpp
@@ -87,16 +87,23 @@ nsTableCellFrame::Init(nsIContent*      
   }
 
   if (aPrevInFlow) {
     // Set the column index
     nsTableCellFrame* cellFrame = (nsTableCellFrame*)aPrevInFlow;
     int32_t           colIndex;
     cellFrame->GetColIndex(colIndex);
     SetColIndex(colIndex);
+  } else {
+    // Although the spec doesn't say that writing-mode is not applied to
+    // table-cells, we still override style value here because we want to
+    // make effective writing mode of table structure frames consistent
+    // within a table. The content inside table cells is reflowed by an
+    // anonymous block, hence their writing mode is not affected.
+    mWritingMode = GetTableFrame()->GetWritingMode();
   }
 }
 
 void
 nsTableCellFrame::DestroyFrom(nsIFrame* aDestructRoot)
 {
   if (HasAnyStateBits(NS_FRAME_CAN_HAVE_ABSPOS_CHILDREN)) {
     nsTableFrame::UnregisterPositionedTablePart(this, aDestructRoot);
--- a/layout/tables/nsTableCellFrame.h
+++ b/layout/tables/nsTableCellFrame.h
@@ -131,24 +131,16 @@ public:
    * @see nsLayoutAtoms::tableCellFrame
    */
   virtual nsIAtom* GetType() const override;
 
 #ifdef DEBUG_FRAME_DUMP
   virtual nsresult GetFrameName(nsAString& aResult) const override;
 #endif
 
-  // Although the spec doesn't say that writing-mode is not applied to
-  // table-cells, we still override this method here because we want to
-  // make effective writing mode of table structure frames consistent
-  // within a table. The content inside table cells is reflowed by an
-  // anonymous block, hence their writing mode is not affected.
-  virtual mozilla::WritingMode GetWritingMode() const override
-    { return GetTableFrame()->GetWritingMode(); }
-
   void BlockDirAlignChild(mozilla::WritingMode aWM, nscoord aMaxAscent);
 
   /*
    * Get the value of vertical-align adjusted for CSS 2's rules for a
    * table cell, which means the result is always
    * NS_STYLE_VERTICAL_ALIGN_{TOP,MIDDLE,BOTTOM,BASELINE}.
    */
   virtual uint8_t GetVerticalAlign() const;
--- a/layout/tables/nsTableColFrame.h
+++ b/layout/tables/nsTableColFrame.h
@@ -28,37 +28,30 @@ public:
   /** instantiate a new instance of nsTableRowFrame.
     * @param aPresShell the pres shell for this frame
     *
     * @return           the frame that was created
     */
   friend nsTableColFrame* NS_NewTableColFrame(nsIPresShell* aPresShell,
                                               nsStyleContext*  aContext);
 
-  nsTableColGroupFrame* GetTableColGroupFrame() const
+  // nsIFrame overrides
+  virtual void Init(nsIContent*       aContent,
+                    nsContainerFrame* aParent,
+                    nsIFrame*         aPrevInFlow) override
   {
-    nsIFrame* parent = GetParent();
-    MOZ_ASSERT(parent && parent->GetType() == nsGkAtoms::tableColGroupFrame);
-    return static_cast<nsTableColGroupFrame*>(parent);
-  }
-
-  nsTableFrame* GetTableFrame() const
-  {
-    return GetTableColGroupFrame()->GetTableFrame();
+    nsSplittableFrame::Init(aContent, aParent, aPrevInFlow);
+    if (!aPrevInFlow) {
+      mWritingMode = GetTableFrame()->GetWritingMode();
+    }
   }
 
   /** @see nsIFrame::DidSetStyleContext */
   virtual void DidSetStyleContext(nsStyleContext* aOldStyleContext) override;
 
-  int32_t GetColIndex() const;
-
-  void SetColIndex (int32_t aColIndex);
-
-  nsTableColFrame* GetNextCol() const;
-
   virtual void Reflow(nsPresContext*           aPresContext,
                       ReflowOutput&     aDesiredSize,
                       const ReflowInput& aReflowInput,
                       nsReflowStatus&          aStatus) override;
 
   /**
    * Table columns never paint anything, nor receive events.
    */
@@ -74,18 +67,33 @@ public:
   virtual nsIAtom* GetType() const override;
 
 #ifdef DEBUG_FRAME_DUMP
   virtual nsresult GetFrameName(nsAString& aResult) const override;
 #endif
 
   virtual nsSplittableType GetSplittableType() const override;
 
-  virtual mozilla::WritingMode GetWritingMode() const override
-    { return GetTableFrame()->GetWritingMode(); }
+  nsTableColGroupFrame* GetTableColGroupFrame() const
+  {
+    nsIFrame* parent = GetParent();
+    MOZ_ASSERT(parent && parent->GetType() == nsGkAtoms::tableColGroupFrame);
+    return static_cast<nsTableColGroupFrame*>(parent);
+  }
+
+  nsTableFrame* GetTableFrame() const
+  {
+    return GetTableColGroupFrame()->GetTableFrame();
+  }
+
+  int32_t GetColIndex() const;
+
+  void SetColIndex (int32_t aColIndex);
+
+  nsTableColFrame* GetNextCol() const;
 
   /** return the number of the columns the col represents.  always >= 1 */
   int32_t GetSpan();
 
   /** convenience method, calls into cellmap */
   int32_t Count() const;
 
   nscoord GetIStartBorderWidth() const { return mIStartBorderWidth; }
--- a/layout/tables/nsTableColGroupFrame.h
+++ b/layout/tables/nsTableColGroupFrame.h
@@ -11,34 +11,41 @@
 #include "nsTableFrame.h"
 #include "mozilla/WritingModes.h"
 
 class nsTableColFrame;
 
 /**
  * nsTableColGroupFrame
  * data structure to maintain information about a single table cell's frame
- *
- * @author  sclark
  */
 class nsTableColGroupFrame final : public nsContainerFrame
 {
 public:
   NS_DECL_FRAMEARENA_HELPERS
 
-  // default constructor supplied by the compiler
-
   /** instantiate a new instance of nsTableRowFrame.
     * @param aPresShell the pres shell for this frame
     *
     * @return           the frame that was created
     */
   friend nsTableColGroupFrame* NS_NewTableColGroupFrame(nsIPresShell* aPresShell,
                                                         nsStyleContext* aContext);
 
+  // nsIFrame overrides
+  virtual void Init(nsIContent*       aContent,
+                    nsContainerFrame* aParent,
+                    nsIFrame*         aPrevInFlow) override
+  {
+    nsContainerFrame::Init(aContent, aParent, aPrevInFlow);
+    if (!aPrevInFlow) {
+      mWritingMode = GetTableFrame()->GetWritingMode();
+    }
+  }
+
   nsTableFrame* GetTableFrame() const
   {
     nsIFrame* parent = GetParent();
     MOZ_ASSERT(parent && parent->GetType() == nsGkAtoms::tableFrame);
     MOZ_ASSERT(!parent->GetPrevInFlow(),
                "Col group should always be in a first-in-flow table frame");
     return static_cast<nsTableFrame*>(parent);
   }
@@ -111,19 +118,16 @@ public:
 
   /**
    * Get the "type" of the frame
    *
    * @see nsGkAtoms::tableColGroupFrame
    */
   virtual nsIAtom* GetType() const override;
 
-  virtual mozilla::WritingMode GetWritingMode() const override
-    { return GetTableFrame()->GetWritingMode(); }
-
   /** Add column frames to the table storages: colframe cache and cellmap
     * this doesn't change the mFrames of the colgroup frame.
     * @param aFirstColIndex - the index at which aFirstFrame should be inserted
     *                         into the colframe cache.
     * @param aResetSubsequentColIndices - the indices of the col frames
     *                                     after the insertion might need
     *                                     an update
     * @param aCols - an iterator that can be used to iterate over the col
--- a/layout/tables/nsTableRowFrame.cpp
+++ b/layout/tables/nsTableRowFrame.cpp
@@ -164,16 +164,18 @@ nsTableRowFrame::Init(nsIContent*       
   NS_ASSERTION(mozilla::StyleDisplay::TableRow == StyleDisplay()->mDisplay,
                "wrong display on table row frame");
 
   if (aPrevInFlow) {
     // Set the row index
     nsTableRowFrame* rowFrame = (nsTableRowFrame*)aPrevInFlow;
 
     SetRowIndex(rowFrame->GetRowIndex());
+  } else {
+    mWritingMode = GetTableFrame()->GetWritingMode();
   }
 }
 
 void
 nsTableRowFrame::DestroyFrom(nsIFrame* aDestructRoot)
 {
   if (HasAnyStateBits(NS_FRAME_CAN_HAVE_ABSPOS_CHILDREN)) {
     nsTableFrame::UnregisterPositionedTablePart(this, aDestructRoot);
--- a/layout/tables/nsTableRowFrame.h
+++ b/layout/tables/nsTableRowFrame.h
@@ -111,19 +111,16 @@ public:
    * @see nsGkAtoms::tableRowFrame
    */
   virtual nsIAtom* GetType() const override;
 
 #ifdef DEBUG_FRAME_DUMP
   virtual nsresult GetFrameName(nsAString& aResult) const override;
 #endif
 
-  virtual mozilla::WritingMode GetWritingMode() const override
-    { return GetTableFrame()->GetWritingMode(); }
- 
   void UpdateBSize(nscoord           aBSize,
                    nscoord           aAscent,
                    nscoord           aDescent,
                    nsTableFrame*     aTableFrame = nullptr,
                    nsTableCellFrame* aCellFrame  = nullptr);
 
   void ResetBSize(nscoord aRowStyleBSize);
 
--- a/layout/tables/nsTableRowGroupFrame.h
+++ b/layout/tables/nsTableRowGroupFrame.h
@@ -46,21 +46,25 @@ public:
     * @param aPresShell the pres shell for this frame
     *
     * @return           the frame that was created
     */
   friend nsTableRowGroupFrame* NS_NewTableRowGroupFrame(nsIPresShell* aPresShell,
                                                         nsStyleContext* aContext);
   virtual ~nsTableRowGroupFrame();
 
-  nsTableFrame* GetTableFrame() const
+  // nsIFrame overrides
+  virtual void Init(nsIContent*       aContent,
+                    nsContainerFrame* aParent,
+                    nsIFrame*         aPrevInFlow) override
   {
-    nsIFrame* parent = GetParent();
-    MOZ_ASSERT(parent && parent->GetType() == nsGkAtoms::tableFrame);
-    return static_cast<nsTableFrame*>(parent);
+    nsContainerFrame::Init(aContent, aParent, aPrevInFlow);
+    if (!aPrevInFlow) {
+      mWritingMode = GetTableFrame()->GetWritingMode();
+    }
   }
 
   virtual void DestroyFrom(nsIFrame* aDestructRoot) override;
 
   /** @see nsIFrame::DidSetStyleContext */
   virtual void DidSetStyleContext(nsStyleContext* aOldStyleContext) override;
 
   virtual void AppendFrames(ChildListID     aListID,
@@ -97,25 +101,29 @@ public:
 
   /**
    * Get the "type" of the frame
    *
    * @see nsGkAtoms::tableRowGroupFrame
    */
   virtual nsIAtom* GetType() const override;
 
-  nsTableRowFrame* GetFirstRow();
-  nsTableRowFrame* GetLastRow();
-
 #ifdef DEBUG_FRAME_DUMP
   virtual nsresult GetFrameName(nsAString& aResult) const override;
 #endif
 
-  virtual mozilla::WritingMode GetWritingMode() const override
-    { return GetTa