Merge inbound to mozilla-central. a=merge
authorTiberius Oros <toros@mozilla.com>
Wed, 22 Aug 2018 00:56:31 +0300
changeset 432732 7c96ad3ab6731287646616610e159447c69d6beb
parent 432731 00a5c0b3923abfcef338f7698c7d73e634395db8 (current diff)
parent 432650 4d48052fa86864972d109d0c096f895dd846ecb7 (diff)
child 432733 9dcb4b6cb5104adb3bb4f576daeea3d63e30f4b7
push id106850
push userapavel@mozilla.com
push dateWed, 22 Aug 2018 03:39:20 +0000
treeherdermozilla-inbound@d91e84fc8bb6 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone63.0a1
first release with
nightly linux32
7c96ad3ab673 / 63.0a1 / 20180821220101 / files
nightly linux64
7c96ad3ab673 / 63.0a1 / 20180821220101 / files
nightly mac
7c96ad3ab673 / 63.0a1 / 20180821220101 / files
nightly win32
7c96ad3ab673 / 63.0a1 / 20180821220101 / files
nightly win64
7c96ad3ab673 / 63.0a1 / 20180821220101 / files
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
releases
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge inbound to mozilla-central. a=merge
testing/web-platform/meta/webdriver/tests/new_session/merge.py.ini
third_party/rust/rustc-serialize/.cargo-checksum.json
third_party/rust/rustc-serialize/Cargo.toml
third_party/rust/rustc-serialize/LICENSE-APACHE
third_party/rust/rustc-serialize/LICENSE-MIT
third_party/rust/rustc-serialize/README.md
third_party/rust/rustc-serialize/src/base64.rs
third_party/rust/rustc-serialize/src/hex.rs
third_party/rust/rustc-serialize/src/json.rs
third_party/rust/rustc-serialize/src/lib.rs
xpcom/ds/nsINIProcessor.js
xpcom/ds/nsINIProcessor.manifest
xpcom/tests/unit/test_iniProcessor.js
--- a/.gitignore
+++ b/.gitignore
@@ -1,16 +1,17 @@
 # .gitignore - List of filenames git should ignore
 
 # Filenames that should be ignored wherever they appear
 *~
 *.pyc
 *.pyo
 TAGS
 tags
+compile_commands.json
 # Ignore ID generated by idutils and un-ignore id directory (for Indonesian locale)
 ID
 !id/
 .DS_Store*
 *.pdb
 *.egg-info
 
 # Vim swap files.
@@ -36,18 +37,20 @@ security/manager/.nss.checkout
 
 # Build directories
 /obj*/
 
 # gecko.log is generated by various test harnesses
 /gecko.log
 
 # Build directories for js shell
-_DBG.OBJ/
-_OPT.OBJ/
+*_DBG.OBJ/
+*_OPT.OBJ/
+/js/src/*-obj/
+/js/src/obj-*/
 
 # SpiderMonkey configury
 js/src/configure
 js/src/old-configure
 js/src/autom4te.cache
 # SpiderMonkey test result logs
 js/src/tests/results-*.html
 js/src/tests/results-*.txt
@@ -139,11 +142,11 @@ tps_result.json
 
 # Ignore files created when running a reftest.
 lextab.py
 
 # tup database
 /.tup
 
 # Ignore Visual Studio Code workspace files.
-.vscode/*
+.vscode/
 !.vscode/extensions.json
 !.vscode/tasks.json
--- a/.hgignore
+++ b/.hgignore
@@ -6,16 +6,17 @@
 (?i)(^|/)TAGS$
 (^|/)ID$
 (^|/)\.DS_Store$
 \.pdb
 \.egg-info
 \.gcda
 \.gcno
 \.gcov
+compile_commands\.json
 
 # Vim swap files.
 ^\.sw.$
 .[^/]*\.sw.$
 
 # Emacs directory variable files.
 \.dir-locals\.el
 
@@ -37,16 +38,17 @@
 
 # gecko.log is generated by various test harnesses
 ^gecko\.log
 
 # Build directories for js shell
 _DBG\.OBJ/
 _OPT\.OBJ/
 ^js/src/.*-obj/
+^js/src/obj-.*/
 
 # SpiderMonkey configury
 ^js/src/configure$
 ^js/src/old-configure$
 ^js/src/autom4te.cache$
 # SpiderMonkey test result logs
 ^js/src/tests/results-.*\.(html|txt)$
 ^js/src/devtools/rootAnalysis/t/out
--- a/browser/actors/LightweightThemeChild.jsm
+++ b/browser/actors/LightweightThemeChild.jsm
@@ -6,16 +6,18 @@
 
 var EXPORTED_SYMBOLS = ["LightweightThemeChild"];
 
 ChromeUtils.import("resource://gre/modules/ActorChild.jsm");
 ChromeUtils.import("resource://gre/modules/Services.jsm");
 
 /**
  * LightweightThemeChild forwards theme data to in-content pages.
+ * It is both instantiated by the traditional Actor mechanism,
+ * and also manually within the sidebar JS global (which has no message manager)
  */
 class LightweightThemeChild extends ActorChild {
   constructor(mm) {
     super(mm);
 
     this.init();
   }
 
--- a/browser/base/content/browser.css
+++ b/browser/base/content/browser.css
@@ -3,33 +3,30 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 @namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");
 @namespace html url("http://www.w3.org/1999/xhtml");
 
 :root {
   --panelui-subview-transition-duration: 150ms;
   --lwt-additional-images: none;
-  --lwt-background-alignment: right top;
   --lwt-background-tiling: no-repeat;
 }
 
 :root:-moz-lwtheme {
   color: var(--lwt-text-color) !important;
 }
 
 :root:-moz-lwtheme {
   background-color: var(--lwt-accent-color) !important;
-  background-image: var(--lwt-additional-images) !important;
-  background-position: var(--lwt-background-alignment) !important;
-  background-repeat: var(--lwt-background-tiling) !important;
 }
 
 :root:-moz-lwtheme[lwtheme-image] {
-  background-image: var(--lwt-header-image), var(--lwt-additional-images) !important;
+  background-image: var(--lwt-header-image) !important;
+  background-position: right top !important;
 }
 
 :root:-moz-lwtheme:-moz-window-inactive {
   background-color: var(--lwt-accent-color-inactive, var(--lwt-accent-color)) !important;
 }
 
 #main-window:not([chromehidden~="toolbar"]) {
 %ifdef XP_MACOSX
--- a/browser/base/content/contentTheme.js
+++ b/browser/base/content/contentTheme.js
@@ -27,16 +27,53 @@ const inContentVariableMap = [
       const {r, g, b, a} = rgbaChannels;
       if (!_isTextColorDark(r, g, b)) {
         element.setAttribute("lwt-newtab-brighttext", "true");
       }
 
       return `rgba(${r}, ${g}, ${b}, ${a})`;
     },
   }],
+  ["--lwt-sidebar-background-color", {
+    lwtProperty: "sidebar",
+  }],
+  ["--lwt-sidebar-text-color", {
+    lwtProperty: "sidebar_text",
+    processColor(rgbaChannels, element) {
+      if (!rgbaChannels) {
+        element.removeAttribute("lwt-sidebar");
+        element.removeAttribute("lwt-sidebar-brighttext");
+        return null;
+      }
+
+      element.setAttribute("lwt-sidebar", "true");
+      const {r, g, b, a} = rgbaChannels;
+      if (!_isTextColorDark(r, g, b)) {
+        element.setAttribute("lwt-sidebar-brighttext", "true");
+      }
+
+      return `rgba(${r}, ${g}, ${b}, ${a})`;
+    },
+  }],
+  ["--lwt-sidebar-highlight-background-color", {
+    lwtProperty: "sidebar_highlight",
+  }],
+  ["--lwt-sidebar-highlight-text-color", {
+    lwtProperty: "sidebar_highlight_text",
+    processColor(rgbaChannels, element) {
+      if (!rgbaChannels) {
+        element.removeAttribute("lwt-sidebar-highlight");
+        return null;
+      }
+      element.setAttribute("lwt-sidebar-highlight", "true");
+
+      const {r, g, b, a} = rgbaChannels;
+      return `rgba(${r}, ${g}, ${b}, ${a})`;
+    },
+  }],
 ];
 
 /**
  * ContentThemeController handles theme updates sent by the frame script.
  * To be able to use ContentThemeController, you must add your page to the whitelist
  * in LightweightThemeChildListener.jsm
  */
 const ContentThemeController = {
@@ -53,17 +90,19 @@ const ContentThemeController = {
    * @param {Object} event object containing the theme update.
    */
   handleEvent({ type, detail }) {
     if (type == "LightweightTheme:Set") {
       let {data} = detail;
       if (!data) {
         data = {};
       }
-      this._setProperties(document.body, data);
+      // XUL documents don't have a body
+      const element = document.body ? document.body : document.documentElement;
+      this._setProperties(element, data);
     }
   },
 
   /**
    * Set a CSS variable to a given value
    * @param {Element} elem The element where the CSS variable should be added.
    * @param {string} variableName The CSS variable to set.
    * @param {string} value The new value of the CSS variable.
@@ -85,17 +124,17 @@ const ContentThemeController = {
     for (let [cssVarName, definition] of inContentVariableMap) {
       const {
         lwtProperty,
         processColor,
       } = definition;
       let value = themeData[lwtProperty];
 
       if (processColor) {
-        value = processColor(value, document.body);
+        value = processColor(value, elem);
       } else if (value) {
         const {r, g, b, a} = value;
         value = `rgba(${r}, ${g}, ${b}, ${a})`;
       }
 
       this._setProperty(elem, cssVarName, value);
     }
   },
--- a/browser/base/content/test/favicons/browser.ini
+++ b/browser/base/content/test/favicons/browser.ini
@@ -46,8 +46,12 @@ support-files =
 [browser_redirect.js]
 support-files =
   file_favicon_redirect.html
   file_favicon_redirect.ico
   file_favicon_redirect.ico^headers^
 [browser_rooticon.js]
 support-files =
   blank.html
+[browser_mixed_content.js]
+support-files =
+  file_insecure_favicon.html
+  file_favicon.png
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/favicons/browser_mixed_content.js
@@ -0,0 +1,17 @@
+add_task(async () => {
+  const testPath = "https://example.com/browser/browser/base/content/test/favicons/file_insecure_favicon.html";
+  const expectedIcon = "http://example.com/browser/browser/base/content/test/favicons/file_favicon.png";
+
+  let tab = BrowserTestUtils.addTab(gBrowser, testPath);
+  gBrowser.selectedTab = tab;
+  let browser = tab.linkedBrowser;
+
+  let faviconPromise = waitForLinkAvailable(browser);
+  await BrowserTestUtils.browserLoaded(browser);
+  let iconURI = await faviconPromise;
+  is(iconURI, expectedIcon, "Got correct icon.");
+
+  ok(gIdentityHandler._isMixedPassiveContentLoaded, "Should have seen mixed content.");
+
+  BrowserTestUtils.removeTab(tab);
+});
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/favicons/file_insecure_favicon.html
@@ -0,0 +1,11 @@
+<!DOCTYPE HTML>
+<html>
+  <head>
+    <meta charset='utf-8'>
+    <title>Favicon Test for mixed content</title>
+    <link rel="icon" type="image/png" href="http://example.com/browser/browser/base/content/test/favicons/file_favicon.png" />
+  </head>
+  <body>
+    Favicon!!
+  </body>
+</html>
--- a/browser/base/content/test/general/browser.ini
+++ b/browser/base/content/test/general/browser.ini
@@ -137,16 +137,17 @@ skip-if = toolkit == "cocoa"
 [browser_bug484315.js]
 # DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_bug491431.js]
 # DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_bug495058.js]
 skip-if = coverage # Bug 1439493
 # DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_bug519216.js]
+skip-if = true # Bug 1478159
 # DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_bug520538.js]
 # DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_bug521216.js]
 # DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_bug533232.js]
 # DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_bug537013.js]
--- a/browser/base/content/test/performance/browser_startup_content.js
+++ b/browser/base/content/test/performance/browser_startup_content.js
@@ -51,17 +51,16 @@ const whitelist = {
     "resource:///actors/AboutReaderChild.jsm",
     "resource:///actors/BrowserTabChild.jsm",
     "resource:///modules/ContentMetaHandler.jsm",
     "resource:///actors/LinkHandlerChild.jsm",
     "resource:///actors/PageStyleChild.jsm",
     "resource://gre/modules/ActorChild.jsm",
     "resource://gre/modules/ActorManagerChild.jsm",
     "resource://gre/modules/E10SUtils.jsm",
-    "resource://gre/modules/PrivateBrowsingUtils.jsm",
     "resource://gre/modules/ReaderMode.jsm",
     "resource://gre/modules/WebProgressChild.jsm",
 
     // Pocket
     "chrome://pocket/content/AboutPocket.jsm",
 
     // Telemetry
     "resource://gre/modules/TelemetryController.jsm", // bug 1470339
--- a/browser/base/content/test/sidebar/browser_sidebar_keys.js
+++ b/browser/base/content/test/sidebar/browser_sidebar_keys.js
@@ -1,12 +1,13 @@
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
 async function testSidebarKeyToggle(key, options, expectedSidebarId) {
+  EventUtils.synthesizeMouseAtCenter(gURLBar, {});
   let promiseShown = BrowserTestUtils.waitForEvent(window, "SidebarShown");
   EventUtils.synthesizeKey(key, options);
   await promiseShown;
   Assert.equal(document.getElementById("sidebar-box")
                        .getAttribute("sidebarcommand"), expectedSidebarId);
   EventUtils.synthesizeKey(key, options);
   Assert.ok(!SidebarUI.isOpen);
 }
--- a/browser/components/places/content/bookmarksSidebar.js
+++ b/browser/components/places/content/bookmarksSidebar.js
@@ -2,16 +2,17 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 /* Shared Places Import - change other consumers if you change this: */
 ChromeUtils.import("resource://gre/modules/Services.jsm");
 ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
 XPCOMUtils.defineLazyModuleGetters(this, {
+  LightweightThemeChild: "resource:///actors/LightweightThemeChild.jsm",
   PlacesUtils: "resource://gre/modules/PlacesUtils.jsm",
   PlacesUIUtils: "resource:///modules/PlacesUIUtils.jsm",
   PlacesTransactions: "resource://gre/modules/PlacesTransactions.jsm",
   PrivateBrowsingUtils: "resource://gre/modules/PrivateBrowsingUtils.jsm",
 });
 XPCOMUtils.defineLazyScriptGetter(this, "PlacesTreeView",
                                   "chrome://browser/content/places/treeView.js");
 XPCOMUtils.defineLazyScriptGetter(this, ["PlacesInsertionPoint", "PlacesController",
@@ -20,16 +21,27 @@ XPCOMUtils.defineLazyScriptGetter(this, 
 /* End Shared Places Import */
 
 function init() {
   let uidensity = window.top.document.documentElement.getAttribute("uidensity");
   if (uidensity) {
     document.documentElement.setAttribute("uidensity", uidensity);
   }
 
+  /* Listen for sidebar theme changes */
+  let themeListener = new LightweightThemeChild({
+    content: window,
+    chromeOuterWindowID: window.top.windowUtils.outerWindowID,
+    docShell: window.docShell,
+  });
+
+  window.addEventListener("unload", () => {
+    themeListener.cleanup();
+  });
+
   document.getElementById("bookmarks-view").place =
     "place:type=" + Ci.nsINavHistoryQueryOptions.RESULTS_AS_ROOTS_QUERY;
 }
 
 function searchBookmarks(aSearchString) {
   var tree = document.getElementById("bookmarks-view");
   if (!aSearchString) {
     // eslint-disable-next-line no-self-assign
--- a/browser/components/places/content/bookmarksSidebar.xul
+++ b/browser/components/places/content/bookmarksSidebar.xul
@@ -26,16 +26,18 @@
       aria-label="&bookmarksButton.label;">
 
   <script type="application/javascript"
           src="chrome://browser/content/places/bookmarksSidebar.js"/>
   <script type="application/javascript"
           src="chrome://global/content/globalOverlay.js"/>
   <script type="application/javascript"
           src="chrome://browser/content/utilityOverlay.js"/>
+  <script type="application/javascript"
+          src="chrome://browser/content/contentTheme.js"/>
 
 #include placesCommands.inc.xul
 #include ../../../../toolkit/content/editMenuCommands.inc.xul
 #include placesContextMenu.inc.xul
 #include bookmarksHistoryTooltip.inc.xul
 
   <hbox id="sidebar-search-container" align="center">
     <textbox id="search-box" flex="1" type="search"
--- a/browser/components/places/content/historySidebar.js
+++ b/browser/components/places/content/historySidebar.js
@@ -2,16 +2,17 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 /* Shared Places Import - change other consumers if you change this: */
 ChromeUtils.import("resource://gre/modules/Services.jsm");
 ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
 XPCOMUtils.defineLazyModuleGetters(this, {
+  LightweightThemeChild: "resource:///actors/LightweightThemeChild.jsm",
   PlacesUtils: "resource://gre/modules/PlacesUtils.jsm",
   PlacesUIUtils: "resource:///modules/PlacesUIUtils.jsm",
   PlacesTransactions: "resource://gre/modules/PlacesTransactions.jsm",
   PrivateBrowsingUtils: "resource://gre/modules/PrivateBrowsingUtils.jsm",
 });
 XPCOMUtils.defineLazyScriptGetter(this, "PlacesTreeView",
                                   "chrome://browser/content/places/treeView.js");
 XPCOMUtils.defineLazyScriptGetter(this, ["PlacesInsertionPoint", "PlacesController",
@@ -27,16 +28,27 @@ var gHistoryGrouping = "";
 var gSearching = false;
 
 function HistorySidebarInit() {
   let uidensity = window.top.document.documentElement.getAttribute("uidensity");
   if (uidensity) {
     document.documentElement.setAttribute("uidensity", uidensity);
   }
 
+  /* Listen for sidebar theme changes */
+  let themeListener = new LightweightThemeChild({
+    content: window,
+    chromeOuterWindowID: window.top.windowUtils.outerWindowID,
+    docShell: window.docShell,
+  });
+
+  window.addEventListener("unload", () => {
+    themeListener.cleanup();
+  });
+
   gHistoryTree = document.getElementById("historyTree");
   gSearchBox = document.getElementById("search-box");
 
   gHistoryGrouping = document.getElementById("viewButton").
                               getAttribute("selectedsort");
 
   if (gHistoryGrouping == "site")
     document.getElementById("bysite").setAttribute("checked", "true");
--- a/browser/components/places/content/historySidebar.xul
+++ b/browser/components/places/content/historySidebar.xul
@@ -26,16 +26,18 @@
       aria-label="&historyButton.label;">
 
   <script type="application/javascript"
           src="chrome://browser/content/places/historySidebar.js"/>
   <script type="application/javascript"
           src="chrome://global/content/globalOverlay.js"/>
   <script type="application/javascript"
           src="chrome://browser/content/utilityOverlay.js"/>
+  <script type="application/javascript"
+          src="chrome://browser/content/contentTheme.js"/>
 
 #include ../../../../toolkit/content/editMenuCommands.inc.xul
 
 #include placesCommands.inc.xul
 
 #include ../../../../toolkit/content/editMenuKeys.inc.xul
 #ifdef XP_MACOSX
   <keyset id="editMenuKeysExtra">
--- a/browser/extensions/formautofill/FormAutofillContent.jsm
+++ b/browser/extensions/formautofill/FormAutofillContent.jsm
@@ -9,17 +9,16 @@
 /* eslint-disable no-use-before-define */
 
 "use strict";
 
 var EXPORTED_SYMBOLS = ["FormAutofillContent"];
 
 const Cm = Components.manager;
 
-ChromeUtils.import("resource://gre/modules/PrivateBrowsingUtils.jsm");
 ChromeUtils.import("resource://gre/modules/Services.jsm");
 ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
 
 ChromeUtils.defineModuleGetter(this, "AddressResult",
                                "resource://formautofill/ProfileAutoCompleteResult.jsm");
 ChromeUtils.defineModuleGetter(this, "CreditCardResult",
                                "resource://formautofill/ProfileAutoCompleteResult.jsm");
 ChromeUtils.defineModuleGetter(this, "FormAutofill",
@@ -27,16 +26,18 @@ ChromeUtils.defineModuleGetter(this, "Fo
 ChromeUtils.defineModuleGetter(this, "FormAutofillHandler",
                                "resource://formautofill/FormAutofillHandler.jsm");
 ChromeUtils.defineModuleGetter(this, "FormAutofillUtils",
                                "resource://formautofill/FormAutofillUtils.jsm");
 ChromeUtils.defineModuleGetter(this, "FormLikeFactory",
                                "resource://gre/modules/FormLikeFactory.jsm");
 ChromeUtils.defineModuleGetter(this, "InsecurePasswordUtils",
                                "resource://gre/modules/InsecurePasswordUtils.jsm");
+ChromeUtils.defineModuleGetter(this, "PrivateBrowsingUtils",
+                               "resource://gre/modules/PrivateBrowsingUtils.jsm");
 
 const formFillController = Cc["@mozilla.org/satchel/form-fill-controller;1"]
                              .getService(Ci.nsIFormFillController);
 const autocompleteController = Cc["@mozilla.org/autocomplete/controller;1"]
                              .getService(Ci.nsIAutoCompleteController);
 
 XPCOMUtils.defineLazyGetter(this, "ADDRESSES_COLLECTION_NAME",
                             () => FormAutofillUtils.ADDRESSES_COLLECTION_NAME);
--- a/browser/installer/package-manifest.in
+++ b/browser/installer/package-manifest.in
@@ -280,18 +280,16 @@
 #ifdef XP_WIN
 @RESPATH@/browser/components/360seProfileMigrator.js
 @RESPATH@/browser/components/EdgeProfileMigrator.js
 @RESPATH@/browser/components/IEProfileMigrator.js
 #endif
 #ifdef XP_MACOSX
 @RESPATH@/browser/components/SafariProfileMigrator.js
 #endif
-@RESPATH@/components/nsINIProcessor.manifest
-@RESPATH@/components/nsINIProcessor.js
 @RESPATH@/components/nsPrompter.manifest
 @RESPATH@/components/nsPrompter.js
 @RESPATH@/components/SyncComponents.manifest
 @RESPATH@/components/Weave.js
 @RESPATH@/components/FxAccountsComponents.manifest
 @RESPATH@/components/FxAccountsPush.js
 @RESPATH@/components/CaptivePortalDetectComponents.manifest
 @RESPATH@/components/captivedetect.js
--- a/browser/modules/ThemeVariableMap.jsm
+++ b/browser/modules/ThemeVariableMap.jsm
@@ -79,9 +79,13 @@ const ThemeVariableMap = [
   ["--autocomplete-popup-highlight-color", {
     lwtProperty: "popup_highlight_text"
   }],
 ];
 
 const ThemeContentPropertyList = [
   "ntp_background",
   "ntp_text",
+  "sidebar",
+  "sidebar_highlight",
+  "sidebar_highlight_text",
+  "sidebar_text",
 ];
--- a/browser/themes/linux/browser.css
+++ b/browser/themes/linux/browser.css
@@ -149,18 +149,16 @@ menuitem.bookmark-item {
 %include ../shared/menupanel.inc.css
 
 /* Fullscreen window controls */
 
 #minimize-button,
 #restore-button,
 #close-button {
   -moz-appearance: none;
-  border: none;
-  margin: 0 !important;
   padding: 6px 12px;
   -moz-context-properties: stroke;
   stroke: currentColor;
   color: inherit;
 }
 
 #minimize-button {
   list-style-image: url(chrome://browser/skin/window-controls/minimize.svg);
--- a/browser/themes/linux/places/sidebar.css
+++ b/browser/themes/linux/places/sidebar.css
@@ -18,23 +18,16 @@
   margin: 1px 0;
   margin-inline-start: 4px;
 }
 
 #viewButton:-moz-focusring:not(:hover):not([open]) {
   outline: 1px dotted -moz-DialogText;
 }
 
-.sidebar-placesTree {
-  margin: 0;
-  color: inherit;
-  -moz-appearance: none;
-  background: transparent;
-}
-
 :root[uidensity=touch] #search-box,
 :root[uidensity=touch] .sidebar-placesTreechildren::-moz-tree-row {
   min-height: 32px;
 }
 
 .sidebar-placesTreechildren::-moz-tree-cell(leaf) ,
 .sidebar-placesTreechildren::-moz-tree-image(leaf) {
   cursor: pointer;
--- a/browser/themes/osx/browser.css
+++ b/browser/themes/osx/browser.css
@@ -42,19 +42,35 @@
   --panel-separator-color: rgba(249,249,250,.1);
 
   --arrowpanel-dimmed: rgba(249,249,250,.1);
   --arrowpanel-dimmed-further: rgba(249,249,250,.15);
   --arrowpanel-dimmed-even-further: rgba(249,249,250,.2);
 }
 
 #navigator-toolbox {
+  -moz-appearance: none;
   --tabs-border-color: rgba(0,0,0,.3);
 }
 
+/*
+  This is a workaround for Bug 1482157
+  -moz-appearance: toolbox; makes the macOS sheets attached to the element's
+  bottom border. We cannot put this property on the toolbox itself as it
+  cancels all backgrounds that are there, so we set it on the toolbox bottom
+  border.
+*/
+#navigator-toolbox::after {
+  -moz-appearance: toolbox;
+  height: 1px;
+  /* use inset box-shadow instead of border because -moz-appearance hides the border */
+  border: none;
+  box-shadow: inset 0 -1px var(--toolbox-border-bottom-color);
+}
+
 #tabbrowser-tabs {
   --tab-line-color: #0a84ff;
 }
 
 #navigator-toolbox toolbarbutton:-moz-lwtheme {
   color: inherit;
   text-shadow: inherit;
 }
--- a/browser/themes/osx/places/sidebar.css
+++ b/browser/themes/osx/places/sidebar.css
@@ -5,16 +5,19 @@
 /* Sidebars */
 
 %include ../../shared/places/sidebar.inc.css
 
 .sidebar-placesTree {
   margin: 0;
   /* Default font size is 11px on mac, so this is 12px */
   font-size: 1.0909rem;
+}
+
+.sidebar-panel:not([lwt-sidebar]) .sidebar-placesTree {
   -moz-appearance: -moz-mac-source-list;
   -moz-font-smoothing-background-color: -moz-mac-source-list;
 }
 
 :root[uidensity=touch] .sidebar-placesTreechildren::-moz-tree-row {
   min-height: 32px;
 }
 
--- a/browser/themes/shared/browser.inc.css
+++ b/browser/themes/shared/browser.inc.css
@@ -19,20 +19,29 @@
   --space-above-tabbar: 8px;
 }
 
 :root[sessionrestored]:-moz-lwtheme {
   transition: @themeTransition@;
 }
 
 /* Increase contrast of UI links on dark themes */
+
 :root[lwt-popup-brighttext] panel .text-link {
   color: @lwtPopupBrighttextLinkColor@;
 }
 
+/* Set additional backgrounds alignment relative to toolbox*/
+
+#navigator-toolbox:-moz-lwtheme {
+  background-image: var(--lwt-additional-images);
+  background-position: var(--lwt-background-alignment);
+  background-repeat: var(--lwt-background-tiling);
+}
+
 /* Toolbar / content area border */
 
 #navigator-toolbox::after {
   content: "";
   display: -moz-box;
   border-bottom: 1px solid var(--toolbox-border-bottom-color);
 }
 
--- a/browser/themes/shared/places/sidebar.inc.css
+++ b/browser/themes/shared/places/sidebar.inc.css
@@ -2,16 +2,61 @@
  * 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/. */
 
 .sidebar-panel {
   -moz-appearance: none;
   background-color: transparent;
 }
 
+/* Themed sidebars */
+
+.sidebar-panel[lwt-sidebar] {
+  background-color: var(--lwt-sidebar-background-color);
+  color: var(--lwt-sidebar-text-color);
+}
+
+.sidebar-panel[lwt-sidebar] .sidebar-placesTreechildren::-moz-tree-row(selected) {
+  background-color: hsla(0,0%,80%,.3);
+}
+
+.sidebar-panel[lwt-sidebar-brighttext] .sidebar-placesTreechildren::-moz-tree-row(selected) {
+  -moz-appearance: none;
+  background-color: rgba(249,249,250,.1);
+}
+
+.sidebar-panel[lwt-sidebar-brighttext] .sidebar-placesTreechildren::-moz-tree-image(selected),
+.sidebar-panel[lwt-sidebar-brighttext] .sidebar-placesTreechildren::-moz-tree-twisty(selected),
+.sidebar-panel[lwt-sidebar-brighttext] .sidebar-placesTreechildren::-moz-tree-cell-text(selected) {
+  color: var(--lwt-sidebar-text-color);
+}
+
+.sidebar-panel[lwt-sidebar-highlight] .sidebar-placesTreechildren::-moz-tree-row(selected,focus) {
+  -moz-appearance: none;
+  background-color: var(--lwt-sidebar-highlight-background-color);
+}
+
+.sidebar-panel[lwt-sidebar-highlight] .sidebar-placesTreechildren::-moz-tree-image(selected, focus),
+.sidebar-panel[lwt-sidebar-highlight] .sidebar-placesTreechildren::-moz-tree-twisty(selected, focus),
+.sidebar-panel[lwt-sidebar-highlight] .sidebar-placesTreechildren::-moz-tree-cell-text(selected, focus) {
+  color: var(--lwt-sidebar-highlight-text-color);
+}
+
+/* Sidebar tree */
+
+.sidebar-placesTree {
+  -moz-appearance: none;
+  background-color: transparent;
+  color: inherit;
+  border: 0;
+  margin: 0;
+}
+
+/* View button */
+
 #viewButton {
   -moz-appearance: none;
   border-radius: 4px;
   padding: 2px 4px;
   color: inherit;
 }
 
 #viewButton:hover {
--- a/browser/themes/windows/browser.css
+++ b/browser/themes/windows/browser.css
@@ -397,18 +397,16 @@ menuitem.bookmark-item {
 %include ../shared/menupanel.inc.css
 
 /* ::::: fullscreen window controls ::::: */
 
 #minimize-button,
 #restore-button,
 #close-button {
   -moz-appearance: none;
-  border: none;
-  margin: 0 !important;
   padding: 6px 12px;
   -moz-context-properties: stroke;
   stroke: currentColor;
   color: inherit;
 }
 
 #minimize-button {
   list-style-image: url(chrome://browser/skin/window-controls/minimize.svg);
--- a/browser/themes/windows/places/sidebar.css
+++ b/browser/themes/windows/places/sidebar.css
@@ -1,24 +1,16 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 /* Sidebars */
 
 %include ../../shared/places/sidebar.inc.css
 
-.sidebar-placesTree {
-  -moz-appearance: none;
-  background-color: transparent;
-  color: inherit;
-  border: 0;
-  margin: 0;
-}
-
 :root[uidensity=touch] #search-box,
 :root[uidensity=touch] .sidebar-placesTreechildren::-moz-tree-row {
   min-height: 32px;
 }
 
 .sidebar-placesTreechildren::-moz-tree-cell,
 .sidebar-placesTreechildren::-moz-tree-twisty {
   padding: 0 4px;
--- a/build/autoconf/compiler-opts.m4
+++ b/build/autoconf/compiler-opts.m4
@@ -16,16 +16,19 @@ case "$target" in
     if test -z "$AS"; then
         case "${target_cpu}" in
         i*86)
             AS=ml;
             ;;
         x86_64)
             AS=ml64;
             ;;
+        aarch64)
+            AS=armasm64;
+            ;;
         esac
     fi
     if test -z "$MIDL"; then MIDL=midl; fi
 
     # need override this flag since we don't use $(LDFLAGS) for this.
     if test -z "$HOST_LDFLAGS" ; then
         HOST_LDFLAGS=" "
     fi
--- a/build/autoconf/icu.m4
+++ b/build/autoconf/icu.m4
@@ -86,16 +86,23 @@ AC_SUBST(USE_ICU)
 AC_SUBST(ICU_DATA_FILE)
 
 if test -n "$USE_ICU"; then
     dnl Source files that use ICU should have control over which parts of the ICU
     dnl namespace they want to use.
     AC_DEFINE(U_USING_ICU_NAMESPACE,0)
 
     if test -z "$MOZ_SYSTEM_ICU"; then
-        if test -z "$YASM" -a -z "$GNU_AS" -a "$COMPILE_ENVIRONMENT"; then
-            AC_MSG_ERROR([Building ICU requires either yasm or a GNU assembler. If you do not have either of those available for this platform you must use --without-intl-api])
-        fi
+        case "$OS_TARGET:$CPU_ARCH" in
+        WINNT:aarch64)
+            dnl we use non-yasm, non-GNU as solutions here.
+            ;;
+        *)
+            if test -z "$YASM" -a -z "$GNU_AS" -a "$COMPILE_ENVIRONMENT"; then
+                AC_MSG_ERROR([Building ICU requires either yasm or a GNU assembler. If you do not have either of those available for this platform you must use --without-intl-api])
+            fi
+            ;;
+        esac
         dnl We build ICU as a static library.
         AC_DEFINE(U_STATIC_IMPLEMENTATION)
     fi
 fi
 ])
--- a/build/moz.configure/toolchain.configure
+++ b/build/moz.configure/toolchain.configure
@@ -684,17 +684,17 @@ def default_c_compilers(host_or_target):
             gcc = tuple('%sgcc' % p for p in toolchain_prefix) + gcc
         # Android sets toolchain_prefix and android_clang_compiler, but
         # we want the latter to take precedence, because the latter can
         # point at clang, which is what we want to use.
         if android_clang_compiler and host_or_target is target:
             return (android_clang_compiler,) + gcc
 
         if host_or_target.kernel == 'WINNT':
-            return ('cl', 'clang-cl') + gcc + ('clang',)
+            return ('clang-cl', 'cl') + gcc + ('clang',)
         if host_or_target.kernel == 'Darwin':
             return ('clang',)
         if developer_options:
             return ('clang',) + gcc
         return gcc + ('clang',)
 
     return default_c_compilers
 
--- a/build/moz.configure/windows.configure
+++ b/build/moz.configure/windows.configure
@@ -454,17 +454,18 @@ def valid_mt(path):
     except subprocess.CalledProcessError:
         pass
     raise FatalCheckError('%s is not Microsoft Manifest Tool')
 
 
 set_config('MSMANIFEST_TOOL', depends(valid_mt)(lambda x: bool(x)))
 
 
-link = check_prog('LINKER', ('link.exe',), paths=toolchain_search_path)
+link = check_prog('LINKER', ('lld-link.exe', 'link.exe'),
+                  paths=toolchain_search_path)
 
 add_old_configure_assignment('LINKER', link)
 
 
 # Normally, we'd just have CC, etc. set to absolute paths, but the build system
 # doesn't currently handle properly the case where the paths contain spaces.
 # Additionally, there's the issue described in toolchain.configure, in
 # valid_compiler().
new file mode 100644
--- /dev/null
+++ b/config/external/icu/data/genicudata.py
@@ -0,0 +1,14 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+
+def main(output, data_file, data_symbol):
+    output.write('''    AREA .rdata,ALIGN=4,DATA,READONLY
+    EXPORT _{data_symbol}[DATA]
+_{data_symbol}
+    INCBIN {data_file}
+    END
+'''.format(data_file=data_file, data_symbol=data_symbol))
--- a/config/external/icu/data/moz.build
+++ b/config/external/icu/data/moz.build
@@ -9,19 +9,30 @@
 Library('icudata')
 
 if CONFIG['OS_ARCH'] == 'WINNT':
     if CONFIG['CPU_ARCH'] == 'x86':
         ASFLAGS += ['-DPREFIX']
 elif CONFIG['OS_ARCH'] == 'Darwin':
     ASFLAGS += ['-DPREFIX']
 
-ASFLAGS += [
+data_symbol = 'icudt%s_dat' % CONFIG['MOZ_ICU_VERSION']
+asflags = [
     '-DICU_DATA_FILE="%s"' % CONFIG['ICU_DATA_FILE'],
-    '-DICU_DATA_SYMBOL=icudt%s_dat' % CONFIG['MOZ_ICU_VERSION'],
+    '-DICU_DATA_SYMBOL=%s' % data_symbol,
 ]
 LOCAL_INCLUDES += ['.']
 
-if CONFIG['HAVE_YASM']:
+if CONFIG['OS_TARGET'] == 'WINNT' and CONFIG['CPU_ARCH'] == 'aarch64':
+    icudata = 'icudata.asm'
+    GENERATED_FILES += [icudata]
+    SOURCES += ['!%s' % icudata]
+    icudata = GENERATED_FILES[icudata]
+    icudata.script = 'genicudata.py'
+    icudata.inputs = [CONFIG['ICU_DATA_FILE']]
+    icudata.flags = [data_symbol]
+elif CONFIG['HAVE_YASM']:
     USE_YASM = True
     SOURCES += ['icudata.s']
+    ASFLAGS += asflags
 elif CONFIG['GNU_AS']:
     SOURCES += ['icudata_gas.S']
+    ASFLAGS += asflags
--- a/devtools/client/responsive.html/components/Toolbar.js
+++ b/devtools/client/responsive.html/components/Toolbar.js
@@ -2,144 +2,162 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 const { PureComponent, createFactory } = require("devtools/client/shared/vendor/react");
 const dom = require("devtools/client/shared/vendor/react-dom-factories");
 const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
+const { connect } = require("devtools/client/shared/vendor/react-redux");
 
 const DevicePixelRatioMenu = createFactory(require("./DevicePixelRatioMenu"));
 const DeviceSelector = createFactory(require("./DeviceSelector"));
 const NetworkThrottlingMenu = createFactory(require("devtools/client/shared/components/throttling/NetworkThrottlingMenu"));
 const SettingsMenu = createFactory(require("./SettingsMenu"));
 const ViewportDimension = createFactory(require("./ViewportDimension"));
 
 const { getStr } = require("../utils/l10n");
 const Types = require("../types");
 
 class Toolbar extends PureComponent {
   static get propTypes() {
     return {
       devices: PropTypes.shape(Types.devices).isRequired,
       displayPixelRatio: Types.pixelRatio.value.isRequired,
+      leftAlignmentEnabled: PropTypes.bool.isRequired,
       networkThrottling: PropTypes.shape(Types.networkThrottling).isRequired,
-      reloadConditions: PropTypes.shape(Types.reloadConditions).isRequired,
-      screenshot: PropTypes.shape(Types.screenshot).isRequired,
-      selectedDevice: PropTypes.string.isRequired,
-      selectedPixelRatio: PropTypes.shape(Types.pixelRatio).isRequired,
-      touchSimulation: PropTypes.shape(Types.touchSimulation).isRequired,
-      viewport: PropTypes.shape(Types.viewport).isRequired,
       onChangeDevice: PropTypes.func.isRequired,
       onChangeNetworkThrottling: PropTypes.func.isRequired,
       onChangePixelRatio: PropTypes.func.isRequired,
       onChangeReloadCondition: PropTypes.func.isRequired,
       onChangeTouchSimulation: PropTypes.func.isRequired,
       onExit: PropTypes.func.isRequired,
       onRemoveDeviceAssociation: PropTypes.func.isRequired,
       onResizeViewport: PropTypes.func.isRequired,
       onRotateViewport: PropTypes.func.isRequired,
       onScreenshot: PropTypes.func.isRequired,
       onToggleLeftAlignment: PropTypes.func.isRequired,
       onUpdateDeviceModal: PropTypes.func.isRequired,
+      reloadConditions: PropTypes.shape(Types.reloadConditions).isRequired,
+      screenshot: PropTypes.shape(Types.screenshot).isRequired,
+      selectedDevice: PropTypes.string.isRequired,
+      selectedPixelRatio: PropTypes.shape(Types.pixelRatio).isRequired,
+      touchSimulation: PropTypes.shape(Types.touchSimulation).isRequired,
+      viewport: PropTypes.shape(Types.viewport).isRequired,
     };
   }
 
   render() {
     const {
       devices,
       displayPixelRatio,
+      leftAlignmentEnabled,
       networkThrottling,
-      reloadConditions,
-      screenshot,
-      selectedDevice,
-      selectedPixelRatio,
-      touchSimulation,
-      viewport,
       onChangeDevice,
       onChangeNetworkThrottling,
       onChangePixelRatio,
       onChangeReloadCondition,
       onChangeTouchSimulation,
       onExit,
       onRemoveDeviceAssociation,
       onResizeViewport,
       onRotateViewport,
       onScreenshot,
       onToggleLeftAlignment,
       onUpdateDeviceModal,
+      reloadConditions,
+      screenshot,
+      selectedDevice,
+      selectedPixelRatio,
+      touchSimulation,
+      viewport,
     } = this.props;
 
-    return dom.header(
-      { id: "toolbar" },
-      DeviceSelector({
-        devices,
-        selectedDevice,
-        viewportId: viewport.id,
-        onChangeDevice,
-        onResizeViewport,
-        onUpdateDeviceModal,
-      }),
-      dom.div(
-        { id: "toolbar-center-controls" },
-        ViewportDimension({
-          viewport,
-          onRemoveDeviceAssociation,
+    return (
+      dom.header(
+        {
+          id: "toolbar",
+          className: leftAlignmentEnabled ? "left-aligned" : "",
+        },
+        DeviceSelector({
+          devices,
+          onChangeDevice,
           onResizeViewport,
-        }),
-        dom.button({
-          id: "rotate-button",
-          className: "devtools-button",
-          onClick: () => onRotateViewport(viewport.id),
-          title: getStr("responsive.rotate"),
-        }),
-        dom.div({ className: "devtools-separator" }),
-        DevicePixelRatioMenu({
-          devices,
-          displayPixelRatio,
+          onUpdateDeviceModal,
           selectedDevice,
-          selectedPixelRatio,
-          onChangePixelRatio,
+          viewportId: viewport.id,
         }),
-        dom.div({ className: "devtools-separator" }),
-        NetworkThrottlingMenu({
-          networkThrottling,
-          onChangeNetworkThrottling,
-          useTopLevelWindow: true,
-        }),
-        dom.div({ className: "devtools-separator" }),
-        dom.button({
-          id: "touch-simulation-button",
-          className: "devtools-button" +
-                     (touchSimulation.enabled ? " checked" : ""),
-          title: (touchSimulation.enabled ?
-            getStr("responsive.disableTouch") : getStr("responsive.enableTouch")),
-          onClick: () => onChangeTouchSimulation(!touchSimulation.enabled),
-        })
-      ),
-      dom.div(
-        { id: "toolbar-end-controls" },
-        dom.button({
-          id: "screenshot-button",
-          className: "devtools-button",
-          title: getStr("responsive.screenshot"),
-          onClick: onScreenshot,
-          disabled: screenshot.isCapturing,
-        }),
-        SettingsMenu({
-          reloadConditions,
-          onChangeReloadCondition,
-          onToggleLeftAlignment,
-        }),
-        dom.div({ className: "devtools-separator" }),
-        dom.button({
-          id: "exit-button",
-          className: "devtools-button",
-          title: getStr("responsive.exit"),
-          onClick: onExit,
-        })
+        leftAlignmentEnabled ?
+          dom.div({ className: "devtools-separator" })
+          :
+          null,
+        dom.div(
+          { id: "toolbar-center-controls" },
+          ViewportDimension({
+            onRemoveDeviceAssociation,
+            onResizeViewport,
+            viewport,
+          }),
+          dom.button({
+            id: "rotate-button",
+            className: "devtools-button",
+            onClick: () => onRotateViewport(viewport.id),
+            title: getStr("responsive.rotate"),
+          }),
+          dom.div({ className: "devtools-separator" }),
+          DevicePixelRatioMenu({
+            devices,
+            displayPixelRatio,
+            onChangePixelRatio,
+            selectedDevice,
+            selectedPixelRatio,
+          }),
+          dom.div({ className: "devtools-separator" }),
+          NetworkThrottlingMenu({
+            networkThrottling,
+            onChangeNetworkThrottling,
+            useTopLevelWindow: true,
+          }),
+          dom.div({ className: "devtools-separator" }),
+          dom.button({
+            id: "touch-simulation-button",
+            className: "devtools-button" +
+                       (touchSimulation.enabled ? " checked" : ""),
+            title: (touchSimulation.enabled ?
+              getStr("responsive.disableTouch") : getStr("responsive.enableTouch")),
+            onClick: () => onChangeTouchSimulation(!touchSimulation.enabled),
+          })
+        ),
+        dom.div(
+          { id: "toolbar-end-controls" },
+          dom.button({
+            id: "screenshot-button",
+            className: "devtools-button",
+            title: getStr("responsive.screenshot"),
+            onClick: onScreenshot,
+            disabled: screenshot.isCapturing,
+          }),
+          SettingsMenu({
+            reloadConditions,
+            onChangeReloadCondition,
+            onToggleLeftAlignment,
+          }),
+          dom.div({ className: "devtools-separator" }),
+          dom.button({
+            id: "exit-button",
+            className: "devtools-button",
+            title: getStr("responsive.exit"),
+            onClick: onExit,
+          })
+        )
       )
     );
   }
 }
 
-module.exports = Toolbar;
+const mapStateToProps = state => {
+  return {
+    leftAlignmentEnabled: state.ui.leftAlignmentEnabled,
+  };
+};
+
+module.exports = connect(mapStateToProps)(Toolbar);
--- a/devtools/client/responsive.html/index.css
+++ b/devtools/client/responsive.html/index.css
@@ -67,24 +67,33 @@ body,
   border-bottom: 1px solid var(--theme-splitter-color);
   display: grid;
   grid-template-columns: min-content auto min-content;
   width: 100%;
   min-height: 29px;
   -moz-user-select: none;
 }
 
+#toolbar.left-aligned {
+  grid-template-columns: min-content min-content min-content auto;
+}
+
 #toolbar-center-controls,
 #toolbar-end-controls {
   display: flex;
   align-items: center;
 }
 
 #toolbar-center-controls {
   justify-self: center;
+  margin: 1px;
+}
+
+#toolbar.left-aligned #toolbar-end-controls {
+  justify-self: end;
 }
 
 #rotate-button::before {
   background-image: url("./images/rotate-viewport.svg");
 }
 
 #touch-simulation-button::before {
   background-image: url("./images/touch-events.svg");
--- a/dom/ipc/ContentParent.cpp
+++ b/dom/ipc/ContentParent.cpp
@@ -768,21 +768,22 @@ ContentParent::MinTabSelect(const nsTArr
   }
 
   return candidate.forget();
 }
 
 static bool
 CreateTemporaryRecordingFile(nsAString& aResult)
 {
-  unsigned long elapsed = (TimeStamp::Now() - TimeStamp::ProcessCreation()).ToMilliseconds();
-
+  static int sNumTemporaryRecordings;
   nsCOMPtr<nsIFile> file;
   return !NS_FAILED(NS_GetSpecialDirectory(NS_OS_TEMP_DIR, getter_AddRefs(file)))
-      && !NS_FAILED(file->AppendNative(nsPrintfCString("Recording%lu", elapsed)))
+      && !NS_FAILED(file->AppendNative(nsPrintfCString("TempRecording.%d.%d",
+                                                       base::GetCurrentProcId(),
+                                                       ++sNumTemporaryRecordings)))
       && !NS_FAILED(file->GetPath(aResult));
 }
 
 /*static*/ already_AddRefed<ContentParent>
 ContentParent::GetNewOrUsedBrowserProcess(Element* aFrameElement,
                                           const nsAString& aRemoteType,
                                           ProcessPriority aPriority,
                                           ContentParent* aOpener,
@@ -1444,21 +1445,23 @@ ContentParent::ShutDownProcess(ShutDownM
   }
 
   // Shutting down by sending a shutdown message works differently than the
   // other methods. We first call Shutdown() in the child. After the child is
   // ready, it calls FinishShutdown() on us. Then we close the channel.
   if (aMethod == SEND_SHUTDOWN_MESSAGE) {
     if (const char* directory = recordreplay::parent::SaveAllRecordingsDirectory()) {
       // Save a recording for the child process before it shuts down.
-      unsigned long elapsed = (TimeStamp::Now() - TimeStamp::ProcessCreation()).ToMilliseconds();
+      static int sNumSavedRecordings;
       nsCOMPtr<nsIFile> file;
       if (!NS_FAILED(NS_NewNativeLocalFile(nsDependentCString(directory), false,
                                            getter_AddRefs(file))) &&
-          !NS_FAILED(file->AppendNative(nsPrintfCString("Recording%lu", elapsed)))) {
+          !NS_FAILED(file->AppendNative(nsPrintfCString("Recording.%d.%d",
+                                                        base::GetCurrentProcId(),
+                                                        ++sNumSavedRecordings)))) {
         bool unused;
         SaveRecording(file, &unused);
       }
     }
 
     if (mIPCOpen && !mShutdownPending) {
       // Stop sending input events with input priority when shutting down.
       SetInputPriorityEventEnabled(false);
--- a/dom/ipc/TabChild.cpp
+++ b/dom/ipc/TabChild.cpp
@@ -39,16 +39,17 @@
 #include "mozilla/layers/ImageBridgeChild.h"
 #include "mozilla/layers/InputAPZContext.h"
 #include "mozilla/layers/LayerTransactionChild.h"
 #include "mozilla/layers/ShadowLayers.h"
 #include "mozilla/layers/WebRenderLayerManager.h"
 #include "mozilla/layout/RenderFrameChild.h"
 #include "mozilla/layout/RenderFrameParent.h"
 #include "mozilla/plugins/PPluginWidgetChild.h"
+#include "mozilla/recordreplay/ParentIPC.h"
 #include "mozilla/LookAndFeel.h"
 #include "mozilla/MouseEvents.h"
 #include "mozilla/Move.h"
 #include "mozilla/PresShell.h"
 #include "mozilla/ProcessHangMonitor.h"
 #include "mozilla/ScopeExit.h"
 #include "mozilla/Services.h"
 #include "mozilla/StaticPtr.h"
@@ -335,17 +336,20 @@ private:
         if (!mReadyToDelete) {
           // This time run this runnable at input priority.
           mReadyToDelete = true;
           MOZ_ALWAYS_SUCCEEDS(NS_DispatchToCurrentThread(this));
           return NS_OK;
         }
 
         // Check in case ActorDestroy was called after RecvDestroy message.
-        if (mTabChild->IPCOpen()) {
+        // Middleman processes with their own recording child process avoid
+        // sending a delete message, so that the parent process does not
+        // receive two deletes for the same actor.
+        if (mTabChild->IPCOpen() && !recordreplay::parent::IsMiddlemanWithRecordingChild()) {
           Unused << PBrowserChild::Send__delete__(mTabChild);
         }
 
         mTabChild = nullptr;
         return NS_OK;
     }
 };
 
--- a/dom/media/MediaFormatReader.cpp
+++ b/dom/media/MediaFormatReader.cpp
@@ -1,35 +1,33 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* 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/. */
 
 #include "MediaFormatReader.h"
 
+#include "AllocationPolicy.h"
 #include "MediaData.h"
 #include "MediaInfo.h"
 #include "VideoFrameContainer.h"
 #include "VideoUtils.h"
 #include "mozilla/AbstractThread.h"
 #include "mozilla/CDMProxy.h"
 #include "mozilla/ClearOnShutdown.h"
 #include "mozilla/NotNull.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/SharedThreadPool.h"
 #include "mozilla/StaticPrefs.h"
 #include "mozilla/TaskQueue.h"
 #include "mozilla/Telemetry.h"
 #include "mozilla/Unused.h"
 #include "nsContentUtils.h"
 #include "nsPrintfCString.h"
-#ifdef MOZ_WIDGET_ANDROID
-#include "mozilla/jni/Utils.h"
-#endif
 
 #include <algorithm>
 #include <map>
 #include <queue>
 
 using namespace mozilla::media;
 
 static mozilla::LazyLogModule sFormatDecoderLog("MediaFormatReader");
@@ -133,172 +131,16 @@ private:
   static StaticMutex sGPUCrashMapMutex;
 };
 
 std::map<MediaDecoderOwnerID, GPUProcessCrashTelemetryLogger::GPUCrashData>
 GPUProcessCrashTelemetryLogger::sGPUCrashDataMap;
 StaticMutex GPUProcessCrashTelemetryLogger::sGPUCrashMapMutex;
 
 /**
- * This is a singleton which controls the number of decoders that can be
- * created concurrently. Before calling PDMFactory::CreateDecoder(), Alloc()
- * must be called to get a token object as a permission to create a decoder.
- * The token should stay alive until Shutdown() is called on the decoder.
- * The destructor of the token will restore the decoder count so it is available
- * for next calls of Alloc().
- */
-class GlobalAllocPolicy
-{
-  using TrackType = TrackInfo::TrackType;
-
-public:
-  class Token
-  {
-    NS_INLINE_DECL_THREADSAFE_REFCOUNTING(Token)
-  protected:
-    virtual ~Token() {}
-  };
-
-  using Promise = MozPromise<RefPtr<Token>, bool, true>;
-
-  // Acquire a token for decoder creation. Thread-safe.
-  auto Alloc() -> RefPtr<Promise>;
-
-  // Called by ClearOnShutdown() to delete the singleton.
-  void operator=(decltype(nullptr));
-
-  // Get the singleton for the given track type. Thread-safe.
-  static GlobalAllocPolicy& Instance(TrackType aTrack);
-
-private:
-  class AutoDeallocToken;
-  using PromisePrivate = Promise::Private;
-  GlobalAllocPolicy();
-  ~GlobalAllocPolicy();
-  // Called by the destructor of TokenImpl to restore the decoder limit.
-  void Dealloc();
-  // Decrement the decoder limit and resolve a promise if available.
-  void ResolvePromise(ReentrantMonitorAutoEnter& aProofOfLock);
-
-  // Protect access to Instance().
-  static StaticMutex sMutex;
-
-  ReentrantMonitor mMonitor;
-  // The number of decoders available for creation.
-  int mDecoderLimit;
-  // Requests to acquire tokens.
-  std::queue<RefPtr<PromisePrivate>> mPromises;
-};
-
-StaticMutex GlobalAllocPolicy::sMutex;
-
-class GlobalAllocPolicy::AutoDeallocToken : public Token
-{
-public:
-  explicit AutoDeallocToken(GlobalAllocPolicy& aPolicy) : mPolicy(aPolicy) { }
-
-private:
-  ~AutoDeallocToken()
-  {
-    mPolicy.Dealloc();
-  }
-
-  GlobalAllocPolicy& mPolicy; // reference to a singleton object.
-};
-
-static int32_t
-MediaDecoderLimitDefault()
-{
-#ifdef MOZ_WIDGET_ANDROID
-  if (jni::GetAPIVersion() < 18) {
-    // Older Android versions have broken support for multiple simultaneous
-    // decoders, see bug 1278574.
-    return 1;
-  }
-#endif
-  // Otherwise, set no decoder limit.
-  return -1;
-}
-
-GlobalAllocPolicy::GlobalAllocPolicy()
-  : mMonitor("DecoderAllocPolicy::mMonitor")
-  , mDecoderLimit(MediaDecoderLimitDefault())
-{
-  SystemGroup::Dispatch(
-    TaskCategory::Other,
-    NS_NewRunnableFunction("GlobalAllocPolicy::GlobalAllocPolicy", [this]() {
-      ClearOnShutdown(this, ShutdownPhase::ShutdownThreads);
-    }));
-}
-
-GlobalAllocPolicy::~GlobalAllocPolicy()
-{
-  while (!mPromises.empty()) {
-    RefPtr<PromisePrivate> p = mPromises.front().forget();
-    mPromises.pop();
-    p->Reject(true, __func__);
-  }
-}
-
-GlobalAllocPolicy&
-GlobalAllocPolicy::Instance(TrackType aTrack)
-{
-  StaticMutexAutoLock lock(sMutex);
-  if (aTrack == TrackType::kAudioTrack) {
-    static auto sAudioPolicy = new GlobalAllocPolicy();
-    return *sAudioPolicy;
-  } else {
-    static auto sVideoPolicy = new GlobalAllocPolicy();
-    return *sVideoPolicy;
-  }
-}
-
-auto
-GlobalAllocPolicy::Alloc() -> RefPtr<Promise>
-{
-  // No decoder limit set.
-  if (mDecoderLimit < 0) {
-    return Promise::CreateAndResolve(new Token(), __func__);
-  }
-
-  ReentrantMonitorAutoEnter mon(mMonitor);
-  RefPtr<PromisePrivate> p = new PromisePrivate(__func__);
-  mPromises.push(p);
-  ResolvePromise(mon);
-  return p.forget();
-}
-
-void
-GlobalAllocPolicy::Dealloc()
-{
-  ReentrantMonitorAutoEnter mon(mMonitor);
-  ++mDecoderLimit;
-  ResolvePromise(mon);
-}
-
-void
-GlobalAllocPolicy::ResolvePromise(ReentrantMonitorAutoEnter& aProofOfLock)
-{
-  MOZ_ASSERT(mDecoderLimit >= 0);
-
-  if (mDecoderLimit > 0 && !mPromises.empty()) {
-    --mDecoderLimit;
-    RefPtr<PromisePrivate> p = mPromises.front().forget();
-    mPromises.pop();
-    p->Resolve(new AutoDeallocToken(*this), __func__);
-  }
-}
-
-void
-GlobalAllocPolicy::operator=(std::nullptr_t)
-{
-  delete this;
-}
-
-/**
  * This class addresses the concern of bug 1339310 comment 4 where the Widevine
  * CDM doesn't support running multiple instances of a video decoder at once per
  * CDM instance by sequencing the order of decoder creation and shutdown. Note
  * this class addresses a different concern from that of GlobalAllocPolicy which
  * controls a system-wide number of decoders while this class control a per-MFR
  * number (which is one per CDM requirement).
  */
 class LocalAllocPolicy
@@ -616,18 +458,16 @@ public:
     if (data.mDecoder) {
       mOwner->mShutdownPromisePool->ShutdownDecoder(data.mDecoder.forget());
     }
     data.mStage = Stage::None;
     MOZ_ASSERT(!data.mToken);
   }
 
 private:
-  class Wrapper;
-
   enum class Stage : int8_t
   {
     None,
     WaitForToken,
     CreateDecoder,
     WaitForInit
   };
 
@@ -658,82 +498,16 @@ private:
 void
 MediaFormatReader::DecoderFactory::CreateDecoder(TrackType aTrack)
 {
   MOZ_ASSERT(aTrack == TrackInfo::kAudioTrack ||
              aTrack == TrackInfo::kVideoTrack);
   RunStage(aTrack == TrackInfo::kAudioTrack ? mAudio : mVideo);
 }
 
-class MediaFormatReader::DecoderFactory::Wrapper : public MediaDataDecoder
-{
-  using Token = GlobalAllocPolicy::Token;
-
-public:
-  Wrapper(already_AddRefed<MediaDataDecoder> aDecoder,
-          already_AddRefed<Token> aToken)
-    : mDecoder(aDecoder)
-    , mToken(aToken)
-  {
-    DecoderDoctorLogger::LogConstructionAndBase(
-      "MediaFormatReader::DecoderFactory::Wrapper",
-      this,
-      static_cast<const MediaDataDecoder*>(this));
-    DecoderDoctorLogger::LinkParentAndChild(
-      "MediaFormatReader::DecoderFactory::Wrapper",
-      this,
-      "decoder",
-      mDecoder.get());
-  }
-
-  ~Wrapper()
-  {
-    DecoderDoctorLogger::LogDestruction(
-      "MediaFormatReader::DecoderFactory::Wrapper", this);
-  }
-
-  RefPtr<InitPromise> Init() override { return mDecoder->Init(); }
-  RefPtr<DecodePromise> Decode(MediaRawData* aSample) override
-  {
-    return mDecoder->Decode(aSample);
-  }
-  RefPtr<DecodePromise> Drain() override { return mDecoder->Drain(); }
-  RefPtr<FlushPromise> Flush() override { return mDecoder->Flush(); }
-  bool IsHardwareAccelerated(nsACString& aFailureReason) const override
-  {
-    return mDecoder->IsHardwareAccelerated(aFailureReason);
-  }
-  nsCString GetDescriptionName() const override
-  {
-    return mDecoder->GetDescriptionName();
-  }
-  void SetSeekThreshold(const TimeUnit& aTime) override
-  {
-    mDecoder->SetSeekThreshold(aTime);
-  }
-  bool SupportDecoderRecycling() const override
-  {
-    return mDecoder->SupportDecoderRecycling();
-  }
-  RefPtr<ShutdownPromise> Shutdown() override
-  {
-    RefPtr<MediaDataDecoder> decoder = mDecoder.forget();
-    RefPtr<Token> token = mToken.forget();
-    return decoder->Shutdown()->Then(
-      AbstractThread::GetCurrent(), __func__,
-      [token]() {
-        return ShutdownPromise::CreateAndResolve(true, __func__);
-      });
-  }
-
-private:
-  RefPtr<MediaDataDecoder> mDecoder;
-  RefPtr<Token> mToken;
-};
-
 void
 MediaFormatReader::DecoderFactory::RunStage(Data& aData)
 {
   switch (aData.mStage) {
     case Stage::None: {
       MOZ_ASSERT(!aData.mToken);
       aData.mPolicy->Alloc()->Then(
         mOwner->OwnerThread(), __func__,
@@ -772,17 +546,18 @@ MediaFormatReader::DecoderFactory::RunSt
                  this,
                  DDLogCategory::Log,
                  "create_decoder_error",
                  rv);
         mOwner->NotifyError(aData.mTrack, rv);
         return;
       }
 
-      aData.mDecoder = new Wrapper(aData.mDecoder.forget(), aData.mToken.forget());
+      aData.mDecoder =
+        new AllocationWrapper(aData.mDecoder.forget(), aData.mToken.forget());
       DecoderDoctorLogger::LinkParentAndChild(
         aData.mDecoder.get(),
         "decoder",
         "MediaFormatReader::DecoderFactory",
         this);
 
       DoInitDecoder(aData);
       aData.mStage = Stage::WaitForInit;
--- a/dom/media/mediacapabilities/MediaCapabilities.cpp
+++ b/dom/media/mediacapabilities/MediaCapabilities.cpp
@@ -1,15 +1,16 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "MediaCapabilities.h"
+#include "AllocationPolicy.h"
 #include "Benchmark.h"
 #include "DecoderTraits.h"
 #include "Layers.h"
 #include "MediaInfo.h"
 #include "MediaRecorder.h"
 #include "PDMFactory.h"
 #include "VPXDecoder.h"
 #include "mozilla/Move.h"
@@ -323,78 +324,85 @@ MediaCapabilities::DecodingInfo(
         // MediaDataDecoder keeps a reference to the config object, so we must
         // keep it alive until the decoder has been shutdown.
         CreateDecoderParams params{ *config,
                                     taskQueue,
                                     compositor,
                                     CreateDecoderParams::VideoFrameRate(
                                       frameRate),
                                     TrackInfo::kVideoTrack };
-
-        RefPtr<PDMFactory> pdm = new PDMFactory();
-        RefPtr<MediaDataDecoder> decoder = pdm->CreateDecoder(params);
-        if (!decoder) {
-          return CapabilitiesPromise::CreateAndReject(
-            MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR, "Can't create decoder"),
-            __func__);
-        }
-        // We now query the decoder to determine if it's power efficient.
-        return decoder->Init()->Then(
+        return AllocationWrapper::CreateDecoder(params)->Then(
           taskQueue,
           __func__,
-          [taskQueue, decoder, frameRate, config = std::move(config)](
-            const MediaDataDecoder::InitPromise::ResolveOrRejectValue&
-              aValue) mutable {
-            RefPtr<CapabilitiesPromise> p;
+          [taskQueue, frameRate, config = std::move(config)](
+            const AllocationWrapper::AllocateDecoderPromise::
+              ResolveOrRejectValue& aValue) mutable {
             if (aValue.IsReject()) {
-              p = CapabilitiesPromise::CreateAndReject(aValue.RejectValue(),
-                                                       __func__);
-            } else {
-              MOZ_ASSERT(config->IsVideo());
-              nsAutoCString reason;
-              bool powerEfficient = true;
-              bool smooth = true;
-              if (config->GetAsVideoInfo()->mImage.height > 480) {
-                // Assume that we can do stuff at 480p or less in a power
-                // efficient manner and smoothly. If greater than 480p we assume
-                // that if the video decoding is hardware accelerated it will be
-                // smooth and power efficient, otherwise we use the benchmark to
-                // estimate
-                powerEfficient = decoder->IsHardwareAccelerated(reason);
-                if (!powerEfficient && VPXDecoder::IsVP9(config->mMimeType)) {
-                  smooth = VP9Benchmark::IsVP9DecodeFast(true /* default */);
-                  uint32_t fps = VP9Benchmark::MediaBenchmarkVp9Fps();
-                  if (!smooth && fps > 0) {
-                    // The VP9 estimizer decode a 1280x720 video. Let's adjust
-                    // the result for the resolution and frame rate of what we
-                    // actually want. If the result is twice that we need we
-                    // assume it will be smooth.
-                    const auto& videoConfig = *config->GetAsVideoInfo();
-                    double needed =
-                      ((1280.0 * 720.0) /
-                       (videoConfig.mImage.width * videoConfig.mImage.height) *
-                       fps) /
-                      frameRate;
-                    smooth = needed > 2;
-                  }
-                }
-              }
-              p = CapabilitiesPromise::CreateAndResolve(
-                MediaCapabilitiesInfo(
-                  true /* supported */, smooth, powerEfficient),
-                __func__);
+              return CapabilitiesPromise::CreateAndReject(aValue.RejectValue(),
+                                                          __func__);
             }
-            MOZ_ASSERT(p.get(), "the promise has been created");
-            // Let's keep alive the decoder and the config object until the
-            // decoder has shutdown.
-            decoder->Shutdown()->Then(
+            RefPtr<MediaDataDecoder> decoder = aValue.ResolveValue();
+            // We now query the decoder to determine if it's power efficient.
+            RefPtr<CapabilitiesPromise> p = decoder->Init()->Then(
               taskQueue,
               __func__,
-              [taskQueue, decoder, config = std::move(config)](
-                const ShutdownPromise::ResolveOrRejectValue& aValue) {});
+              [taskQueue, decoder, frameRate, config = std::move(config)](
+                const MediaDataDecoder::InitPromise::ResolveOrRejectValue&
+                  aValue) mutable {
+                RefPtr<CapabilitiesPromise> p;
+                if (aValue.IsReject()) {
+                  p = CapabilitiesPromise::CreateAndReject(aValue.RejectValue(),
+                                                           __func__);
+                } else {
+                  MOZ_ASSERT(config->IsVideo());
+                  nsAutoCString reason;
+                  bool powerEfficient = true;
+                  bool smooth = true;
+                  if (config->GetAsVideoInfo()->mImage.height > 480) {
+                    // Assume that we can do stuff at 480p or less in a power
+                    // efficient manner and smoothly. If greater than 480p we
+                    // assume that if the video decoding is hardware accelerated
+                    // it will be smooth and power efficient, otherwise we use
+                    // the benchmark to estimate
+                    powerEfficient = decoder->IsHardwareAccelerated(reason);
+                    if (!powerEfficient &&
+                        VPXDecoder::IsVP9(config->mMimeType)) {
+                      smooth =
+                        VP9Benchmark::IsVP9DecodeFast(true /* default */);
+                      uint32_t fps = VP9Benchmark::MediaBenchmarkVp9Fps();
+                      if (!smooth && fps > 0) {
+                        // The VP9 estimizer decode a 1280x720 video. Let's
+                        // adjust the result for the resolution and frame rate
+                        // of what we actually want. If the result is twice that
+                        // we need we assume it will be smooth.
+                        const auto& videoConfig = *config->GetAsVideoInfo();
+                        double needed = ((1280.0 * 720.0) /
+                                         (videoConfig.mImage.width *
+                                          videoConfig.mImage.height) *
+                                         fps) /
+                                        frameRate;
+                        smooth = needed > 2;
+                      }
+                    }
+                  }
+                  p = CapabilitiesPromise::CreateAndResolve(
+                    MediaCapabilitiesInfo(
+                      true /* supported */, smooth, powerEfficient),
+                    __func__);
+                }
+                MOZ_ASSERT(p.get(), "the promise has been created");
+                // Let's keep alive the decoder and the config object until the
+                // decoder has shutdown.
+                decoder->Shutdown()->Then(
+                  taskQueue,
+                  __func__,
+                  [taskQueue, decoder, config = std::move(config)](
+                    const ShutdownPromise::ResolveOrRejectValue& aValue) {});
+                return p;
+              });
             return p;
           });
       }));
   }
 
   auto holder =
     MakeRefPtr<DOMMozPromiseRequestHolder<CapabilitiesPromise::AllPromiseType>>(
       mParent);
new file mode 100644
--- /dev/null
+++ b/dom/media/platforms/AllocationPolicy.cpp
@@ -0,0 +1,216 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+
+#include "AllocationPolicy.h"
+
+#include "PDMFactory.h"
+#include "MediaInfo.h"
+#include "mozilla/ClearOnShutdown.h"
+#include "mozilla/SystemGroup.h"
+#ifdef MOZ_WIDGET_ANDROID
+#include "mozilla/jni/Utils.h"
+#endif
+
+namespace mozilla {
+
+using TrackType = TrackInfo::TrackType;
+
+StaticMutex GlobalAllocPolicy::sMutex;
+
+class GlobalAllocPolicy::AutoDeallocToken : public Token
+{
+public:
+  explicit AutoDeallocToken(GlobalAllocPolicy& aPolicy) : mPolicy(aPolicy) { }
+
+private:
+  ~AutoDeallocToken()
+  {
+    mPolicy.Dealloc();
+  }
+
+  GlobalAllocPolicy& mPolicy; // reference to a singleton object.
+};
+
+static int32_t
+MediaDecoderLimitDefault()
+{
+#ifdef MOZ_WIDGET_ANDROID
+  if (jni::GetAPIVersion() < 18) {
+    // Older Android versions have broken support for multiple simultaneous
+    // decoders, see bug 1278574.
+    return 1;
+  }
+#endif
+  // Otherwise, set no decoder limit.
+  return -1;
+}
+
+GlobalAllocPolicy::GlobalAllocPolicy()
+  : mMonitor("DecoderAllocPolicy::mMonitor")
+  , mDecoderLimit(MediaDecoderLimitDefault())
+{
+  SystemGroup::Dispatch(
+    TaskCategory::Other,
+    NS_NewRunnableFunction("GlobalAllocPolicy::GlobalAllocPolicy", [this]() {
+      ClearOnShutdown(this, ShutdownPhase::ShutdownThreads);
+    }));
+}
+
+GlobalAllocPolicy::~GlobalAllocPolicy()
+{
+  while (!mPromises.empty()) {
+    RefPtr<PromisePrivate> p = mPromises.front().forget();
+    mPromises.pop();
+    p->Reject(true, __func__);
+  }
+}
+
+GlobalAllocPolicy&
+GlobalAllocPolicy::Instance(TrackType aTrack)
+{
+  StaticMutexAutoLock lock(sMutex);
+  if (aTrack == TrackType::kAudioTrack) {
+    static auto sAudioPolicy = new GlobalAllocPolicy();
+    return *sAudioPolicy;
+  } else {
+    static auto sVideoPolicy = new GlobalAllocPolicy();
+    return *sVideoPolicy;
+  }
+}
+
+auto
+GlobalAllocPolicy::Alloc() -> RefPtr<Promise>
+{
+  // No decoder limit set.
+  if (mDecoderLimit < 0) {
+    return Promise::CreateAndResolve(new Token(), __func__);
+  }
+
+  ReentrantMonitorAutoEnter mon(mMonitor);
+  RefPtr<PromisePrivate> p = new PromisePrivate(__func__);
+  mPromises.push(p);
+  ResolvePromise(mon);
+  return p.forget();
+}
+
+void
+GlobalAllocPolicy::Dealloc()
+{
+  ReentrantMonitorAutoEnter mon(mMonitor);
+  ++mDecoderLimit;
+  ResolvePromise(mon);
+}
+
+void
+GlobalAllocPolicy::ResolvePromise(ReentrantMonitorAutoEnter& aProofOfLock)
+{
+  MOZ_ASSERT(mDecoderLimit >= 0);
+
+  if (mDecoderLimit > 0 && !mPromises.empty()) {
+    --mDecoderLimit;
+    RefPtr<PromisePrivate> p = mPromises.front().forget();
+    mPromises.pop();
+    p->Resolve(new AutoDeallocToken(*this), __func__);
+  }
+}
+
+void
+GlobalAllocPolicy::operator=(std::nullptr_t)
+{
+  delete this;
+}
+
+AllocationWrapper::AllocationWrapper(
+  already_AddRefed<MediaDataDecoder> aDecoder,
+  already_AddRefed<Token> aToken)
+  : mDecoder(aDecoder)
+  , mToken(aToken)
+{
+  DecoderDoctorLogger::LogConstructionAndBase(
+    "AllocationWrapper", this, static_cast<const MediaDataDecoder*>(this));
+  DecoderDoctorLogger::LinkParentAndChild(
+    "AllocationWrapper", this, "decoder", mDecoder.get());
+}
+
+AllocationWrapper::~AllocationWrapper()
+{
+  DecoderDoctorLogger::LogDestruction("AllocationWrapper", this);
+}
+
+RefPtr<ShutdownPromise>
+AllocationWrapper::Shutdown()
+{
+  RefPtr<MediaDataDecoder> decoder = mDecoder.forget();
+  RefPtr<Token> token = mToken.forget();
+  return decoder->Shutdown()->Then(
+    AbstractThread::GetCurrent(), __func__, [token]() {
+      return ShutdownPromise::CreateAndResolve(true, __func__);
+    });
+}
+/* static */ RefPtr<AllocationWrapper::AllocateDecoderPromise>
+AllocationWrapper::CreateDecoder(const CreateDecoderParams& aParams)
+{
+  // aParams.mConfig is guaranteed to stay alive during the lifetime of the
+  // MediaDataDecoder, so keeping a pointer to the object is safe.
+  const TrackInfo* config = &aParams.mConfig;
+  RefPtr<TaskQueue> taskQueue = aParams.mTaskQueue;
+  DecoderDoctorDiagnostics* diagnostics = aParams.mDiagnostics;
+  RefPtr<layers::ImageContainer> imageContainer = aParams.mImageContainer;
+  RefPtr<layers::KnowsCompositor> knowsCompositor = aParams.mKnowsCompositor;
+  RefPtr<GMPCrashHelper> crashHelper = aParams.mCrashHelper;
+  CreateDecoderParams::UseNullDecoder useNullDecoder = aParams.mUseNullDecoder;
+  CreateDecoderParams::NoWrapper noWrapper = aParams.mNoWrapper;
+  TrackInfo::TrackType type = aParams.mType;
+  MediaEventProducer<TrackInfo::TrackType>* onWaitingForKeyEvent =
+    aParams.mOnWaitingForKeyEvent;
+  CreateDecoderParams::OptionSet options = aParams.mOptions;
+  CreateDecoderParams::VideoFrameRate rate = aParams.mRate;
+
+  RefPtr<AllocateDecoderPromise> p =
+    GlobalAllocPolicy::Instance(aParams.mType)
+      .Alloc()
+      ->Then(
+        AbstractThread::GetCurrent(),
+        __func__,
+        [=](RefPtr<Token> aToken) {
+          // result may not always be updated by PDMFactory::CreateDecoder
+          // either when the creation succeeded or failed, as such it must be
+          // initialized to a fatal error by default.
+          MediaResult result = MediaResult(
+            NS_ERROR_DOM_MEDIA_FATAL_ERR,
+            nsPrintfCString("error creating %s decoder", TrackTypeToStr(type)));
+          RefPtr<PDMFactory> pdm = new PDMFactory();
+          CreateDecoderParams params{ *config,
+                                      taskQueue,
+                                      diagnostics,
+                                      imageContainer,
+                                      &result,
+                                      knowsCompositor,
+                                      crashHelper,
+                                      useNullDecoder,
+                                      noWrapper,
+                                      type,
+                                      onWaitingForKeyEvent,
+                                      options,
+                                      rate };
+          RefPtr<MediaDataDecoder> decoder = pdm->CreateDecoder(params);
+          if (decoder) {
+            RefPtr<AllocationWrapper> wrapper =
+              new AllocationWrapper(decoder.forget(), aToken.forget());
+            return AllocateDecoderPromise::CreateAndResolve(wrapper, __func__);
+          }
+          return AllocateDecoderPromise::CreateAndReject(result, __func__);
+        },
+        []() {
+          return AllocateDecoderPromise::CreateAndReject(
+            MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR,
+                        "Allocation policy expired"),
+            __func__);
+        });
+  return p;
+}
+
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/media/platforms/AllocationPolicy.h
@@ -0,0 +1,121 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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 AllocationPolicy_h_
+#define AllocationPolicy_h_
+
+#include "MediaInfo.h"
+#include "PlatformDecoderModule.h"
+#include "TimeUnits.h"
+#include "mozilla/MozPromise.h"
+#include "mozilla/StaticMutex.h"
+#include "mozilla/ReentrantMonitor.h"
+#include <queue>
+
+namespace mozilla {
+
+/**
+ * This is a singleton which controls the number of decoders that can be
+ * created concurrently. Before calling PDMFactory::CreateDecoder(), Alloc()
+ * must be called to get a token object as a permission to create a decoder.
+ * The token should stay alive until Shutdown() is called on the decoder.
+ * The destructor of the token will restore the decoder count so it is available
+ * for next calls of Alloc().
+ */
+class GlobalAllocPolicy
+{
+public:
+  class Token
+  {
+    NS_INLINE_DECL_THREADSAFE_REFCOUNTING(Token)
+  protected:
+    virtual ~Token() {}
+  };
+
+  using Promise = MozPromise<RefPtr<Token>, bool, true>;
+
+  // Acquire a token for decoder creation. Thread-safe.
+  RefPtr<Promise> Alloc();
+
+  // Called by ClearOnShutdown() to delete the singleton.
+  void operator=(decltype(nullptr));
+
+  // Get the singleton for the given track type. Thread-safe.
+  static GlobalAllocPolicy& Instance(TrackInfo::TrackType aTrack);
+
+private:
+  class AutoDeallocToken;
+  using PromisePrivate = Promise::Private;
+  GlobalAllocPolicy();
+  ~GlobalAllocPolicy();
+  // Called by the destructor of TokenImpl to restore the decoder limit.
+  void Dealloc();
+  // Decrement the decoder limit and resolve a promise if available.
+  void ResolvePromise(ReentrantMonitorAutoEnter& aProofOfLock);
+
+  // Protect access to Instance().
+  static StaticMutex sMutex;
+
+  ReentrantMonitor mMonitor;
+  // The number of decoders available for creation.
+  int mDecoderLimit;
+  // Requests to acquire tokens.
+  std::queue<RefPtr<PromisePrivate>> mPromises;
+};
+
+class AllocationWrapper : public MediaDataDecoder
+{
+  using Token = GlobalAllocPolicy::Token;
+
+public:
+  AllocationWrapper(already_AddRefed<MediaDataDecoder> aDecoder,
+                    already_AddRefed<Token> aToken);
+  ~AllocationWrapper();
+
+  RefPtr<InitPromise> Init() override { return mDecoder->Init(); }
+  RefPtr<DecodePromise> Decode(MediaRawData* aSample) override
+  {
+    return mDecoder->Decode(aSample);
+  }
+  RefPtr<DecodePromise> Drain() override { return mDecoder->Drain(); }
+  RefPtr<FlushPromise> Flush() override { return mDecoder->Flush(); }
+  bool IsHardwareAccelerated(nsACString& aFailureReason) const override
+  {
+    return mDecoder->IsHardwareAccelerated(aFailureReason);
+  }
+  nsCString GetDescriptionName() const override
+  {
+    return mDecoder->GetDescriptionName();
+  }
+  void SetSeekThreshold(const media::TimeUnit& aTime) override
+  {
+    mDecoder->SetSeekThreshold(aTime);
+  }
+  bool SupportDecoderRecycling() const override
+  {
+    return mDecoder->SupportDecoderRecycling();
+  }
+  RefPtr<ShutdownPromise> Shutdown() override;
+
+  typedef MozPromise<RefPtr<MediaDataDecoder>,
+                     MediaResult,
+                     /* IsExclusive = */ true>
+    AllocateDecoderPromise;
+  // Will create a decoder has soon as one can be created according to the
+  // GlobalAllocPolicy.
+  // Warning: all aParams members must be valid until the promise has been
+  // resolved, as some contains raw pointers to objects.
+  static RefPtr<AllocateDecoderPromise> CreateDecoder(
+    const CreateDecoderParams& aParams);
+
+private:
+  RefPtr<MediaDataDecoder> mDecoder;
+  RefPtr<Token> mToken;
+};
+
+} // namespace mozilla
+
+#endif
--- a/dom/media/platforms/moz.build
+++ b/dom/media/platforms/moz.build
@@ -6,16 +6,17 @@
 
 EXPORTS += [
     'agnostic/AgnosticDecoderModule.h',
     'agnostic/DummyMediaDataDecoder.h',
     'agnostic/OpusDecoder.h',
     'agnostic/TheoraDecoder.h',
     'agnostic/VorbisDecoder.h',
     'agnostic/VPXDecoder.h',
+    'AllocationPolicy.h',
     'MediaTelemetryConstants.h',
     'PDMFactory.h',
     'PlatformDecoderModule.h',
     'ReorderQueue.h',
     'SimpleMap.h',
     'wrappers/H264Converter.h',
     'wrappers/MediaDataDecoderProxy.h'
 
@@ -26,16 +27,17 @@ UNIFIED_SOURCES += [
     'agnostic/BlankDecoderModule.cpp',
     'agnostic/DummyMediaDataDecoder.cpp',
     'agnostic/NullDecoderModule.cpp',
     'agnostic/OpusDecoder.cpp',
     'agnostic/TheoraDecoder.cpp',
     'agnostic/VorbisDecoder.cpp',
     'agnostic/VPXDecoder.cpp',
     'agnostic/WAVDecoder.cpp',
+    'AllocationPolicy.cpp',
     'PDMFactory.cpp',
     'wrappers/H264Converter.cpp',
     'wrappers/MediaDataDecoderProxy.cpp'
 ]
 
 DIRS += [
     'agnostic/bytestreams',
     'agnostic/eme',
--- a/dom/plugins/base/nsPluginHost.cpp
+++ b/dom/plugins/base/nsPluginHost.cpp
@@ -788,16 +788,22 @@ nsPluginHost::InstantiatePluginInstance(
   PR_LogFlush();
 #endif
 
   if (aMimeType.IsEmpty()) {
     MOZ_ASSERT_UNREACHABLE("Attempting to spawn a plugin with no mime type");
     return NS_ERROR_FAILURE;
   }
 
+  // Plugins are not supported when recording or replaying executions.
+  // See bug 1483232.
+  if (recordreplay::IsRecordingOrReplaying()) {
+    return NS_ERROR_FAILURE;
+  }
+
   RefPtr<nsPluginInstanceOwner> instanceOwner = new nsPluginInstanceOwner();
   if (!instanceOwner) {
     return NS_ERROR_OUT_OF_MEMORY;
   }
 
   nsCOMPtr<nsIContent> ourContent = do_QueryInterface(static_cast<nsIImageLoadingContent*>(aContent));
   nsresult rv = instanceOwner->Init(ourContent);
   if (NS_FAILED(rv)) {
--- a/dom/storage/LocalStorageCache.cpp
+++ b/dom/storage/LocalStorageCache.cpp
@@ -438,17 +438,19 @@ LocalStorageCache::SetItem(const LocalSt
   }
 
   data.mKeys.Put(aKey, aValue);
 
   if (aSource != ContentMutation) {
     return NS_OK;
   }
 
+#if !defined(MOZ_WIDGET_ANDROID)
   NotifyObservers(aStorage, nsString(aKey), aOld, aValue);
+#endif
 
   if (Persist(aStorage)) {
     StorageDBChild* storageChild = StorageDBChild::Get();
     if (!storageChild) {
       NS_ERROR("Writing to localStorage after the database has been shut down"
                ", data lose!");
       return NS_ERROR_NOT_INITIALIZED;
     }
@@ -486,17 +488,19 @@ LocalStorageCache::RemoveItem(const Loca
                           static_cast<int64_t>(aKey.Length()));
   Unused << ProcessUsageDelta(aStorage, delta, aSource);
   data.mKeys.Remove(aKey);
 
   if (aSource != ContentMutation) {
     return NS_OK;
   }
 
+#if !defined(MOZ_WIDGET_ANDROID)
   NotifyObservers(aStorage, nsString(aKey), aOld, VoidString());
+#endif
 
   if (Persist(aStorage)) {
     StorageDBChild* storageChild = StorageDBChild::Get();
     if (!storageChild) {
       NS_ERROR("Writing to localStorage after the database has been shut down"
                ", data lose!");
       return NS_ERROR_NOT_INITIALIZED;
     }
@@ -534,19 +538,21 @@ LocalStorageCache::Clear(const LocalStor
     Unused << ProcessUsageDelta(aStorage, -data.mOriginQuotaUsage, aSource);
     data.mKeys.Clear();
   }
 
   if (aSource != ContentMutation) {
     return hadData ? NS_OK : NS_SUCCESS_DOM_NO_OPERATION;
   }
 
+#if !defined(MOZ_WIDGET_ANDROID)
   if (hadData) {
     NotifyObservers(aStorage, VoidString(), VoidString(), VoidString());
   }
+#endif
 
   if (Persist(aStorage) && (refresh || hadData)) {
     StorageDBChild* storageChild = StorageDBChild::Get();
     if (!storageChild) {
       NS_ERROR("Writing to localStorage after the database has been shut down"
                ", data lose!");
       return NS_ERROR_NOT_INITIALIZED;
     }
--- a/dom/storage/LocalStorageManager.cpp
+++ b/dom/storage/LocalStorageManager.cpp
@@ -241,16 +241,17 @@ LocalStorageManager::GetStorageInternal(
         }
       } else {
         if (originKey.EqualsLiteral("knalb.:about")) {
           return NS_OK;
         }
       }
     }
 
+#if !defined(MOZ_WIDGET_ANDROID)
     PBackgroundChild* backgroundActor =
       BackgroundChild::GetOrCreateForCurrentThread();
     if (NS_WARN_IF(!backgroundActor)) {
       return NS_ERROR_FAILURE;
     }
 
     PrincipalInfo principalInfo;
     rv = mozilla::ipc::PrincipalToPrincipalInfo(aPrincipal, &principalInfo);
@@ -258,31 +259,34 @@ LocalStorageManager::GetStorageInternal(
       return rv;
     }
 
     uint32_t privateBrowsingId;
     rv = aPrincipal->GetPrivateBrowsingId(&privateBrowsingId);
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return rv;
     }
+#endif
 
     // There is always a single instance of a cache per scope
     // in a single instance of a DOM storage manager.
     cache = PutCache(originAttrSuffix, originKey, aPrincipal);
 
+#if !defined(MOZ_WIDGET_ANDROID)
     LocalStorageCacheChild* actor = new LocalStorageCacheChild(cache);
 
     MOZ_ALWAYS_TRUE(
       backgroundActor->SendPBackgroundLocalStorageCacheConstructor(
                                                             actor,
                                                             principalInfo,
                                                             originKey,
                                                             privateBrowsingId));
 
     cache->SetActor(actor);
+#endif
   }
 
   if (aRetval) {
     nsCOMPtr<nsPIDOMWindowInner> inner = nsPIDOMWindowInner::From(aWindow);
 
     nsCOMPtr<nsIDOMStorage> storage = new LocalStorage(
       inner, this, cache, aDocumentURI, aPrincipal, aPrivate);
     storage.forget(aRetval);
--- a/extensions/cookie/nsPermissionManager.cpp
+++ b/extensions/cookie/nsPermissionManager.cpp
@@ -2096,16 +2096,97 @@ nsPermissionManager::RemoveAll()
 
 NS_IMETHODIMP
 nsPermissionManager::RemoveAllSince(int64_t aSince)
 {
   ENSURE_NOT_CHILD_PROCESS;
   return RemoveAllModifiedSince(aSince);
 }
 
+template<class T>
+nsresult
+nsPermissionManager::RemovePermissionEntries(T aCondition)
+{
+  nsCOMArray<nsIPermission> array;
+  for (auto iter = mPermissionTable.Iter(); !iter.Done(); iter.Next()) {
+    PermissionHashKey* entry = iter.Get();
+    for (const auto& permEntry : entry->GetPermissions()) {
+      if (!aCondition(permEntry)) {
+        continue;
+      }
+
+      nsCOMPtr<nsIPrincipal> principal;
+      nsresult rv = GetPrincipalFromOrigin(entry->GetKey()->mOrigin,
+                                           getter_AddRefs(principal));
+      if (NS_FAILED(rv)) {
+        continue;
+      }
+
+      nsCOMPtr<nsIPermission> permission =
+        nsPermission::Create(principal,
+                             mTypeArray.ElementAt(permEntry.mType),
+                             permEntry.mPermission,
+                             permEntry.mExpireType,
+                             permEntry.mExpireTime);
+      if (NS_WARN_IF(!permission)) {
+        continue;
+      }
+      array.AppendObject(permission);
+    }
+  }
+
+  for (int32_t i = 0; i<array.Count(); ++i) {
+    nsCOMPtr<nsIPrincipal> principal;
+    nsAutoCString type;
+
+    nsresult rv = array[i]->GetPrincipal(getter_AddRefs(principal));
+    if (NS_FAILED(rv)) {
+      NS_ERROR("GetPrincipal() failed!");
+      continue;
+    }
+
+    rv = array[i]->GetType(type);
+    if (NS_FAILED(rv)) {
+      NS_ERROR("GetType() failed!");
+      continue;
+    }
+
+    // AddInternal handles removal, so let it do the work...
+    AddInternal(
+      principal,
+      type,
+      nsIPermissionManager::UNKNOWN_ACTION,
+      0,
+      nsIPermissionManager::EXPIRE_NEVER, 0, 0,
+      nsPermissionManager::eNotify,
+      nsPermissionManager::eWriteToDB);
+  }
+  // now re-import any defaults as they may now be required if we just deleted
+  // an override.
+  ImportDefaults();
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPermissionManager::RemoveByType(const char* aType)
+{
+  ENSURE_NOT_CHILD_PROCESS;
+
+  int32_t typeIndex = GetTypeIndex(aType, false);
+  // If type == -1, the type isn't known,
+  // so just return NS_OK
+  if (typeIndex == -1) {
+    return NS_OK;
+  }
+
+  return RemovePermissionEntries([typeIndex] (const PermissionEntry& aPermEntry) {
+    return static_cast<uint32_t> (typeIndex) == aPermEntry.mType;
+  });
+}
+
 void
 nsPermissionManager::CloseDB(bool aRebuildOnSuccess)
 {
   // Null the statements, this will finalize them.
   mStmtInsert = nullptr;
   mStmtDelete = nullptr;
   mStmtUpdate = nullptr;
   if (mDBConn) {
@@ -2622,74 +2703,19 @@ NS_IMETHODIMP nsPermissionManager::Obser
   return NS_OK;
 }
 
 nsresult
 nsPermissionManager::RemoveAllModifiedSince(int64_t aModificationTime)
 {
   ENSURE_NOT_CHILD_PROCESS;
 
-  nsCOMArray<nsIPermission> array;
-  for (auto iter = mPermissionTable.Iter(); !iter.Done(); iter.Next()) {
-    PermissionHashKey* entry = iter.Get();
-    for (const auto& permEntry : entry->GetPermissions()) {
-      if (aModificationTime > permEntry.mModificationTime) {
-        continue;
-      }
-
-      nsCOMPtr<nsIPrincipal> principal;
-      nsresult rv = GetPrincipalFromOrigin(entry->GetKey()->mOrigin,
-                                           getter_AddRefs(principal));
-      if (NS_FAILED(rv)) {
-        continue;
-      }
-
-      nsCOMPtr<nsIPermission> permission =
-        nsPermission::Create(principal,
-                             mTypeArray.ElementAt(permEntry.mType),
-                             permEntry.mPermission,
-                             permEntry.mExpireType,
-                             permEntry.mExpireTime);
-      if (NS_WARN_IF(!permission)) {
-        continue;
-      }
-      array.AppendObject(permission);
-    }
-  }
-
-  for (int32_t i = 0; i<array.Count(); ++i) {
-    nsCOMPtr<nsIPrincipal> principal;
-    nsAutoCString type;
-
-    nsresult rv = array[i]->GetPrincipal(getter_AddRefs(principal));
-    if (NS_FAILED(rv)) {
-      NS_ERROR("GetPrincipal() failed!");
-      continue;
-    }
-
-    rv = array[i]->GetType(type);
-    if (NS_FAILED(rv)) {
-      NS_ERROR("GetType() failed!");
-      continue;
-    }
-
-    // AddInternal handles removal, so let it do the work...
-    AddInternal(
-      principal,
-      type,
-      nsIPermissionManager::UNKNOWN_ACTION,
-      0,
-      nsIPermissionManager::EXPIRE_NEVER, 0, 0,
-      nsPermissionManager::eNotify,
-      nsPermissionManager::eWriteToDB);
-  }
-  // now re-import any defaults as they may now be required if we just deleted
-  // an override.
-  ImportDefaults();
-  return NS_OK;
+  return RemovePermissionEntries([aModificationTime] (const PermissionEntry& aPermEntry) {
+    return aModificationTime <= aPermEntry.mModificationTime;
+  });
 }
 
 NS_IMETHODIMP
 nsPermissionManager::RemovePermissionsWithAttributes(const nsAString& aPattern)
 {
   ENSURE_NOT_CHILD_PROCESS;
   mozilla::OriginAttributesPattern pattern;
   if (!pattern.Init(aPattern)) {
--- a/extensions/cookie/nsPermissionManager.h
+++ b/extensions/cookie/nsPermissionManager.h
@@ -359,16 +359,20 @@ private:
                        int64_t aModificationTime);
 
   /**
    * This method removes all permissions modified after the specified time.
    */
   nsresult
   RemoveAllModifiedSince(int64_t aModificationTime);
 
+  template<class T>
+  nsresult
+  RemovePermissionEntries(T aCondition);
+
   /**
    * Returns false if this permission manager wouldn't have the permission
    * requested available.
    *
    * If aType is nullptr, checks that the permission manager would have all
    * permissions available for the given principal.
    */
   bool PermissionAvailable(nsIPrincipal* aPrincipal, const char* aType);
new file mode 100644
--- /dev/null
+++ b/extensions/cookie/test/unit/test_permmanager_removebytype.js
@@ -0,0 +1,77 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+function run_test() {
+  // initialize the permission manager service
+  let pm = Cc["@mozilla.org/permissionmanager;1"].
+        getService(Ci.nsIPermissionManager);
+
+  Assert.equal(perm_count(), 0);
+
+  // add some permissions
+  let uri = NetUtil.newURI("http://amazon.com:8080/foobarbaz");
+  let uri2 = NetUtil.newURI("http://google.com:2048/quxx");
+  let uri3 = NetUtil.newURI("https://google.com/search");
+
+  pm.add(uri, "apple", 3);
+  pm.add(uri, "pear", 1);
+  pm.add(uri, "cucumber", 1);
+
+  pm.add(uri2, "apple", 2);
+  pm.add(uri2, "pear", 2);
+
+  pm.add(uri3, "cucumber", 3);
+  pm.add(uri3, "apple", 1);
+
+  Assert.equal(perm_count(), 7);
+
+  pm.removeByType("apple");
+  Assert.equal(perm_count(), 4);
+
+  Assert.equal(pm.testPermission(uri, "pear"), 1);
+  Assert.equal(pm.testPermission(uri2, "pear"), 2);
+
+  Assert.equal(pm.testPermission(uri, "apple"), 0);
+  Assert.equal(pm.testPermission(uri2, "apple"), 0);
+  Assert.equal(pm.testPermission(uri3, "apple"), 0);
+
+  Assert.equal(pm.testPermission(uri, "cucumber"), 1);
+  Assert.equal(pm.testPermission(uri3, "cucumber"), 3);
+
+  pm.removeByType("cucumber");
+  Assert.equal(perm_count(), 2);
+
+  Assert.equal(pm.testPermission(uri, "pear"), 1);
+  Assert.equal(pm.testPermission(uri2, "pear"), 2);
+
+  Assert.equal(pm.testPermission(uri, "apple"), 0);
+  Assert.equal(pm.testPermission(uri2, "apple"), 0);
+  Assert.equal(pm.testPermission(uri3, "apple"), 0);
+
+  Assert.equal(pm.testPermission(uri, "cucumber"), 0);
+  Assert.equal(pm.testPermission(uri3, "cucumber"), 0);
+
+  pm.removeByType("pear");
+  Assert.equal(perm_count(), 0);
+
+  Assert.equal(pm.testPermission(uri, "pear"), 0);
+  Assert.equal(pm.testPermission(uri2, "pear"), 0);
+
+  Assert.equal(pm.testPermission(uri, "apple"), 0);
+  Assert.equal(pm.testPermission(uri2, "apple"), 0);
+  Assert.equal(pm.testPermission(uri3, "apple"), 0);
+
+  Assert.equal(pm.testPermission(uri, "cucumber"), 0);
+  Assert.equal(pm.testPermission(uri3, "cucumber"), 0);
+
+  function perm_count() {
+    let enumerator = pm.enumerator;
+    let count = 0;
+    while (enumerator.hasMoreElements()) {
+      count++;
+      enumerator.getNext();
+    }
+
+    return count;
+  }
+}
--- a/extensions/cookie/test/unit/xpcshell.ini
+++ b/extensions/cookie/test/unit/xpcshell.ini
@@ -19,16 +19,17 @@ skip-if = true # Bug 863738
 [test_eviction.js]
 [test_permmanager_default_pref.js]
 [test_permmanager_defaults.js]
 [test_permmanager_expiration.js]
 [test_permmanager_getAllForURI.js]
 [test_permmanager_getPermissionObject.js]
 [test_permmanager_notifications.js]
 [test_permmanager_removeall.js]
+[test_permmanager_removebytype.js]
 [test_permmanager_removesince.js]
 [test_permmanager_removeforapp.js]
 [test_permmanager_load_invalid_entries.js]
 skip-if = debug == true
 [test_permmanager_idn.js]
 [test_permmanager_subdomains.js]
 [test_permmanager_local_files.js]
 [test_permmanager_cleardata.js]
--- a/gfx/2d/UserData.h
+++ b/gfx/2d/UserData.h
@@ -73,17 +73,19 @@ public:
     return nullptr;
   }
 
   /* Remove and destroy a given key */
   void RemoveAndDestroy(UserDataKey *key)
   {
     for (int i=0; i<count; i++) {
       if (key == entries[i].key) {
-        entries[i].destroy(entries[i].userData);
+        if (entries[i].destroy) {
+          entries[i].destroy(entries[i].userData);
+        }
         // decrement before looping so entries[i+1] doesn't read past the end:
         --count;
         for (;i<count; i++) {
           entries[i] = entries[i+1];
         }
       }
     }
   }
--- a/gfx/layers/client/TextureClient.cpp
+++ b/gfx/layers/client/TextureClient.cpp
@@ -1465,17 +1465,17 @@ public:
   virtual int32_t GetReadCount() override;
 
   virtual LockType GetType() override { return TYPE_NONBLOCKING_MEMORY; }
 
   virtual bool IsValid() const override { return true; };
 
   virtual bool Serialize(ReadLockDescriptor& aOutput, base::ProcessId aOther) override;
 
-  int32_t mReadCount;
+  Atomic<int32_t> mReadCount;
 };
 
 // The cross-prcess implementation of TextureReadLock.
 //
 // Since we don't use cross-process reference counting for the ReadLock objects,
 // we use the lock's internal counter as a way to know when to deallocate the
 // underlying shmem section: when the counter is equal to 1, it means that the
 // lock is not "held" (the texture is writable), when the counter is equal to 0
@@ -1646,24 +1646,24 @@ MemoryTextureReadLock::Serialize(ReadLoc
   return true;
 }
 
 bool
 MemoryTextureReadLock::ReadLock()
 {
   NS_ASSERT_OWNINGTHREAD(MemoryTextureReadLock);
 
-  PR_ATOMIC_INCREMENT(&mReadCount);
+  ++mReadCount;
   return true;
 }
 
 int32_t
 MemoryTextureReadLock::ReadUnlock()
 {
-  int32_t readCount = PR_ATOMIC_DECREMENT(&mReadCount);
+  int32_t readCount = --mReadCount;
   MOZ_ASSERT(readCount >= 0);
 
   return readCount;
 }
 
 int32_t
 MemoryTextureReadLock::GetReadCount()
 {
--- a/gfx/thebes/gfxFontMissingGlyphs.cpp
+++ b/gfx/thebes/gfxFontMissingGlyphs.cpp
@@ -4,117 +4,41 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "gfxFontMissingGlyphs.h"
 
 #include "gfxUtils.h"
 #include "mozilla/gfx/2D.h"
 #include "mozilla/gfx/Helpers.h"
 #include "mozilla/gfx/PathHelpers.h"
+#include "mozilla/LinkedList.h"
 #include "mozilla/RefPtr.h"
 #include "nsDeviceContext.h"
 #include "nsLayoutUtils.h"
+#include "TextDrawTarget.h"
 
 using namespace mozilla;
 using namespace mozilla::gfx;
 
-#define CHAR_BITS(b00, b01, b02, b10, b11, b12, b20, b21, b22, b30, b31, b32, b40, b41, b42) \
-  ((b00 << 0) | (b01 << 1) | (b02 << 2) | (b10 << 3) | (b11 << 4) | (b12 << 5) | \
-   (b20 << 6) | (b21 << 7) | (b22 << 8) | (b30 << 9) | (b31 << 10) | (b32 << 11) | \
-   (b40 << 12) | (b41 << 13) | (b42 << 14))
-
-static const uint16_t glyphMicroFont[16] = {
-  CHAR_BITS(0, 1, 0,
-            1, 0, 1,
-            1, 0, 1,
-            1, 0, 1,
-            0, 1, 0),
-  CHAR_BITS(0, 1, 0,
-            0, 1, 0,
-            0, 1, 0,
-            0, 1, 0,
-            0, 1, 0),
-  CHAR_BITS(1, 1, 1,
-            0, 0, 1,
-            1, 1, 1,
-            1, 0, 0,
-            1, 1, 1),
-  CHAR_BITS(1, 1, 1,
-            0, 0, 1,
-            1, 1, 1,
-            0, 0, 1,
-            1, 1, 1),
-  CHAR_BITS(1, 0, 1,
-            1, 0, 1,
-            1, 1, 1,
-            0, 0, 1,
-            0, 0, 1),
-  CHAR_BITS(1, 1, 1,
-            1, 0, 0,
-            1, 1, 1,
-            0, 0, 1,
-            1, 1, 1),
-  CHAR_BITS(1, 1, 1,
-            1, 0, 0,
-            1, 1, 1,
-            1, 0, 1,
-            1, 1, 1),
-  CHAR_BITS(1, 1, 1,
-            0, 0, 1,
-            0, 0, 1,
-            0, 0, 1,
-            0, 0, 1),
-  CHAR_BITS(0, 1, 0,
-            1, 0, 1,
-            0, 1, 0,
-            1, 0, 1,
-            0, 1, 0),
-  CHAR_BITS(1, 1, 1,
-            1, 0, 1,
-            1, 1, 1,
-            0, 0, 1,
-            0, 0, 1),
-  CHAR_BITS(1, 1, 1,
-            1, 0, 1,
-            1, 1, 1,
-            1, 0, 1,
-            1, 0, 1),
-  CHAR_BITS(1, 1, 0,
-            1, 0, 1,
-            1, 1, 0,
-            1, 0, 1,
-            1, 1, 0),
-  CHAR_BITS(0, 1, 1,
-            1, 0, 0,
-            1, 0, 0,
-            1, 0, 0,
-            0, 1, 1),
-  CHAR_BITS(1, 1, 0,
-            1, 0, 1,
-            1, 0, 1,
-            1, 0, 1,
-            1, 1, 0),
-  CHAR_BITS(1, 1, 1,
-            1, 0, 0,
-            1, 1, 1,
-            1, 0, 0,
-            1, 1, 1),
-  CHAR_BITS(1, 1, 1,
-            1, 0, 0,
-            1, 1, 1,
-            1, 0, 0,
-            1, 0, 0)
+#define X 255
+static const uint8_t gMiniFontData[] = {
+    0,X,0, 0,X,0, X,X,X, X,X,X, X,0,X, X,X,X, X,X,X, X,X,X, X,X,X, X,X,X, X,X,X, X,X,0, 0,X,X, X,X,0, X,X,X, X,X,X,
+    X,0,X, 0,X,0, 0,0,X, 0,0,X, X,0,X, X,0,0, X,0,0, 0,0,X, X,0,X, X,0,X, X,0,X, X,0,X, X,0,0, X,0,X, X,0,0, X,0,0,
+    X,0,X, 0,X,0, X,X,X, X,X,X, X,X,X, X,X,X, X,X,X, 0,0,X, X,X,X, X,X,X, X,X,X, X,X,0, X,0,0, X,0,X, X,X,X, X,X,X,
+    X,0,X, 0,X,0, X,0,0, 0,0,X, 0,0,X, 0,0,X, X,0,X, 0,0,X, X,0,X, 0,0,X, X,0,X, X,0,X, X,0,0, X,0,X, X,0,0, X,0,0,
+    0,X,0, 0,X,0, X,X,X, X,X,X, 0,0,X, X,X,X, X,X,X, 0,0,X, X,X,X, 0,0,X, X,0,X, X,X,0, 0,X,X, X,X,0, X,X,X, X,0,0,
 };
+#undef X
 
 /* Parameters that control the rendering of hexboxes. They look like this:
 
         BMP codepoints           non-BMP codepoints
       (U+0000 - U+FFFF)         (U+10000 - U+10FFFF)
 
-         +---------+              +-------------+ 
+         +---------+              +-------------+
          |         |              |             |
          | HHH HHH |              | HHH HHH HHH |
          | HHH HHH |              | HHH HHH HHH |
          | HHH HHH |              | HHH HHH HHH |
          | HHH HHH |              | HHH HHH HHH |
          | HHH HHH |              | HHH HHH HHH |
          |         |              |             |
          | HHH HHH |              | HHH HHH HHH |
@@ -143,77 +67,327 @@ static const int HEX_CHAR_GAP = 1;
 static const int BOX_HORIZONTAL_INSET = 1;
 /** The width of the border */
 static const int BOX_BORDER_WIDTH = 1;
 /**
  * The scaling factor for the border opacity; this is multiplied by the current
  * opacity being used to draw the text.
  */
 static const Float BOX_BORDER_OPACITY = 0.5;
+
+#ifndef MOZ_GFX_OPTIMIZE_MOBILE
+
+static RefPtr<DrawTarget> gGlyphDrawTarget;
+static RefPtr<SourceSurface> gGlyphMask;
+static RefPtr<SourceSurface> gGlyphAtlas;
+static Color gGlyphColor;
+
 /**
- * Draw a single hex character using the current color. A nice way to do this
- * would be to fill in an A8 image surface and then use it as a mask
- * to paint the current color. Tragically this doesn't currently work with the
- * Quartz cairo backend which doesn't generally support masking with surfaces.
- * So for now we just paint a bunch of rectangles...
+ * Generates a new colored mini-font atlas from the mini-font mask.
  */
-#ifndef MOZ_GFX_OPTIMIZE_MOBILE
-static void
-DrawHexChar(uint32_t aDigit, const Point& aPt, DrawTarget& aDrawTarget,
-            const Pattern &aPattern, const Matrix* aMat)
+static bool
+MakeGlyphAtlas(const Color& aColor)
 {
-    uint32_t glyphBits = glyphMicroFont[aDigit];
+    gGlyphAtlas = nullptr;
+    if (!gGlyphDrawTarget) {
+        gGlyphDrawTarget = gfxPlatform::GetPlatform()->CreateOffscreenContentDrawTarget(
+            IntSize(MINIFONT_WIDTH * 16, MINIFONT_HEIGHT), SurfaceFormat::B8G8R8A8);
+        if (!gGlyphDrawTarget) {
+            return false;
+        }
+    }
+    if (!gGlyphMask) {
+        gGlyphMask = gGlyphDrawTarget->CreateSourceSurfaceFromData(
+            const_cast<uint8_t*>(gMiniFontData), IntSize(MINIFONT_WIDTH * 16, MINIFONT_HEIGHT),
+            MINIFONT_WIDTH * 16, SurfaceFormat::A8);
+        if (!gGlyphMask) {
+            return false;
+        }
+    }
+    gGlyphDrawTarget->MaskSurface(ColorPattern(aColor), gGlyphMask, Point(0, 0),
+                                  DrawOptions(1.0f, CompositionOp::OP_SOURCE));
+    gGlyphAtlas = gGlyphDrawTarget->Snapshot();
+    if (!gGlyphAtlas) {
+        return false;
+    }
+    gGlyphColor = aColor;
+    return true;
+}
 
-    if (aMat) {
-        // If using an orientation matrix instead of a DT transform, step
-        // with the matrix basis vectors, filling individual rectangles of
-        // the size indicated by the matrix.
-        Point stepX(aMat->_11, aMat->_12);
-        Point stepY(aMat->_21, aMat->_22);
-        Point corner = stepX + stepY;
-        // Get the rectangle at the origin that will be stepped into place.
-        Rect startRect(std::min(corner.x, 0.0f), std::min(corner.y, 0.0f),
-                       fabs(corner.x), fabs(corner.y));
-        startRect.MoveBy(aMat->TransformPoint(aPt));
-        for (int y = 0; y < MINIFONT_HEIGHT; ++y) {
-            Rect curRect = startRect;
-            for (int x = 0; x < MINIFONT_WIDTH; ++x) {
-                if (glyphBits & 1) {
-                    aDrawTarget.FillRect(curRect, aPattern);
-                }
-                glyphBits >>= 1;
-                curRect.MoveBy(stepX);
-            }
-            startRect.MoveBy(stepY);
+/**
+ * Reuse the current mini-font atlas if the color matches, otherwise regenerate it.
+ */
+static inline already_AddRefed<SourceSurface>
+GetGlyphAtlas(const Color& aColor)
+{
+    // Get the opaque color, ignoring any transparency which will be handled later.
+    Color color(aColor.r, aColor.g, aColor.b);
+    if ((gGlyphAtlas && gGlyphColor == color) || MakeGlyphAtlas(color)) {
+        return do_AddRef(gGlyphAtlas);
+    }
+    return nullptr;
+}
+
+/**
+ * Clear any cached glyph atlas resources.
+ */
+static void
+PurgeGlyphAtlas()
+{
+    gGlyphAtlas = nullptr;
+    gGlyphDrawTarget = nullptr;
+    gGlyphMask = nullptr;
+}
+
+// WebRender layer manager user data that will get signaled when the layer
+// manager is destroyed.
+class WRUserData : public layers::LayerUserData, public LinkedListElement<WRUserData>
+{
+public:
+    explicit WRUserData(layers::WebRenderLayerManager* aManager);
+
+    ~WRUserData();
+
+    static void
+    Assign(layers::WebRenderLayerManager* aManager)
+    {
+        if (!aManager->HasUserData(&sWRUserDataKey)) {
+            aManager->SetUserData(&sWRUserDataKey, new WRUserData(aManager));
         }
-        return;
+    }
+
+    void
+    Remove()
+    {
+        remove();
+        mManager->RemoveUserData(&sWRUserDataKey);
     }
 
-    // To avoid the potential for seams showing between rects when we're under
-    // a transform we concat all the rects into a PathBuilder and fill the
-    // resulting Path (rather than using DrawTarget::FillRect).
-    RefPtr<PathBuilder> builder = aDrawTarget.CreatePathBuilder();
-    for (int y = 0; y < MINIFONT_HEIGHT; ++y) {
-        for (int x = 0; x < MINIFONT_WIDTH; ++x) {
-            if (glyphBits & 1) {
-                Rect r(aPt.x + x, aPt.y + y, 1, 1);
-                MaybeSnapToDevicePixels(r, aDrawTarget, true);
-                builder->MoveTo(r.TopLeft());
-                builder->LineTo(r.TopRight());
-                builder->LineTo(r.BottomRight());
-                builder->LineTo(r.BottomLeft());
-                builder->Close();
+    layers::WebRenderLayerManager* mManager;
+
+    static UserDataKey sWRUserDataKey;
+};
+
+static RefPtr<SourceSurface> gWRGlyphAtlas[8];
+static LinkedList<WRUserData> gWRUsers;
+UserDataKey WRUserData::sWRUserDataKey;
+
+/**
+ * Generates a transformed WebRender mini-font atlas for a given orientation.
+ */
+static already_AddRefed<SourceSurface>
+MakeWRGlyphAtlas(const Matrix* aMat)
+{
+    IntSize size(MINIFONT_WIDTH * 16, MINIFONT_HEIGHT);
+    // If the orientation is transposed, width/height are swapped.
+    if (aMat && aMat->_11 == 0) {
+        std::swap(size.width, size.height);
+    }
+    RefPtr<DrawTarget> ref = gfxPlatform::GetPlatform()->ScreenReferenceDrawTarget();
+    RefPtr<DrawTarget> dt = gfxPlatform::GetPlatform()->CreateSimilarSoftwareDrawTarget(
+            ref, size, SurfaceFormat::B8G8R8A8);
+    if (!dt) {
+        return nullptr;
+    }
+    if (aMat) {
+        // Select appropriate transform matrix based on whether the
+        // orientation is transposed.
+        dt->SetTransform(aMat->_11 == 0 ?
+            Matrix(0.0f, copysign(1.0f, aMat->_12),
+                   copysign(1.0f, aMat->_21), 0.0f,
+                   aMat->_21 < 0 ? MINIFONT_HEIGHT : 0.0f,
+                   aMat->_12 < 0 ? MINIFONT_WIDTH * 16 : 0.0f) :
+            Matrix(copysign(1.0f, aMat->_11), 0.0f,
+                   0.0f, copysign(1.0f, aMat->_22),
+                   aMat->_11 < 0 ? MINIFONT_WIDTH * 16 : 0.0f,
+                   aMat->_22 < 0 ? MINIFONT_HEIGHT : 0.0f));
+    }
+    RefPtr<SourceSurface> mask = dt->CreateSourceSurfaceFromData(
+        const_cast<uint8_t*>(gMiniFontData), IntSize(MINIFONT_WIDTH * 16, MINIFONT_HEIGHT),
+        MINIFONT_WIDTH * 16, SurfaceFormat::A8);
+    if (!mask) {
+        return nullptr;
+    }
+    dt->MaskSurface(ColorPattern(Color(1.0f, 1.0f, 1.0f)), mask, Point(0, 0));
+    return dt->Snapshot();
+}
+
+/**
+ * Clear any cached WebRender glyph atlas resources.
+ */
+static void
+PurgeWRGlyphAtlas()
+{
+    // For each WR layer manager, we need go through each atlas orientation
+    // and see if it has a stashed image key. If it does, remove the image
+    // from the layer manager.
+    for (WRUserData* user : gWRUsers) {
+        auto* manager = user->mManager;
+        for (size_t i = 0; i < 8; i++) {
+            if (gWRGlyphAtlas[i]) {
+                uint32_t handle =
+                    (uint32_t)(uintptr_t)gWRGlyphAtlas[i]->GetUserData(
+                        reinterpret_cast<UserDataKey*>(manager));
+                if (handle) {
+                    manager->AddImageKeyForDiscard(
+                        wr::ImageKey{manager->WrBridge()->GetNamespace(), handle});
+                }
             }
-            glyphBits >>= 1;
+        }
+        // Remove the layer manager's destroy notification.
+        user->Remove();
+    }
+    // Finally, clear out the atlases.
+    for (size_t i = 0; i < 8; i++) {
+        gWRGlyphAtlas[i] = nullptr;
+    }
+}
+
+WRUserData::WRUserData(layers::WebRenderLayerManager* aManager)
+    : mManager(aManager)
+{
+    gWRUsers.insertFront(this);
+}
+
+WRUserData::~WRUserData()
+{
+    // When the layer manager is destroyed, we need go through each
+    // atlas and remove any assigned image keys.
+    if (isInList()) {
+        for (size_t i = 0; i < 8; i++) {
+            if (gWRGlyphAtlas[i]) {
+                gWRGlyphAtlas[i]->RemoveUserData(reinterpret_cast<UserDataKey*>(mManager));
+            }
+        }
+    }
+}
+
+static already_AddRefed<SourceSurface>
+GetWRGlyphAtlas(DrawTarget& aDrawTarget, const Matrix* aMat)
+{
+    uint32_t key = 0;
+    // Encode orientation in the key.
+    if (aMat) {
+        if (aMat->_11 == 0) {
+            key |= 4 | (aMat->_12 < 0 ? 1 : 0) | (aMat->_21 < 0 ? 2 : 0);
+        } else {
+            key |= (aMat->_11 < 0 ? 1 : 0) | (aMat->_22 < 0 ? 2 : 0);
         }
     }
-    RefPtr<Path> path = builder->Finish();
-    aDrawTarget.Fill(path, aPattern);
+    // Check if an atlas was already created, or create one if necessary.
+    RefPtr<SourceSurface> atlas = gWRGlyphAtlas[key];
+    if (!atlas) {
+        atlas = MakeWRGlyphAtlas(aMat);
+        gWRGlyphAtlas[key] = atlas;
+    }
+    // The atlas may exist, but an image key may not be assigned for it to
+    // the given layer manager.
+    auto* tdt = static_cast<layout::TextDrawTarget*>(&aDrawTarget);
+    auto* manager = tdt->WrLayerManager();
+    if (!atlas->GetUserData(reinterpret_cast<UserDataKey*>(manager))) {
+        // No image key, so we need to map the atlas' data for transfer to WR.
+        RefPtr<DataSourceSurface> dataSurface = atlas->GetDataSurface();
+        if (!dataSurface) {
+            return nullptr;
+        }
+        DataSourceSurface::ScopedMap map(dataSurface, DataSourceSurface::READ);
+        if (!map.IsMapped()) {
+            return nullptr;
+        }
+        // Transfer the data and get an image key for it.
+        Maybe<wr::ImageKey> result =
+            tdt->DefineImage(atlas->GetSize(),
+                             map.GetStride(),
+                             atlas->GetFormat(),
+                             map.GetData());
+        if (!result.isSome()) {
+            return nullptr;
+        }
+        // Assign the image key to the atlas.
+        atlas->AddUserData(reinterpret_cast<UserDataKey*>(manager),
+                           (void*)(uintptr_t)result.value().mHandle,
+                           nullptr);
+        // Create a user data notification for when the layer manager is
+        // destroyed so we can clean up any assigned image keys.
+        WRUserData::Assign(manager);
+    }
+    return atlas.forget();
 }
-#endif // MOZ_GFX_OPTIMIZE_MOBILE
+
+static void
+DrawHexChar(uint32_t aDigit, Float aLeft, Float aTop, DrawTarget& aDrawTarget,
+            SourceSurface* aAtlas, const Color& aColor,
+            const Matrix* aMat = nullptr)
+{
+    Rect dest(aLeft, aTop, MINIFONT_WIDTH, MINIFONT_HEIGHT);
+    if (aDrawTarget.GetBackendType() == BackendType::WEBRENDER_TEXT) {
+        // For WR, we need to get the image key assigned to the given WR layer manager
+        // for referencing the image.
+        auto* tdt = static_cast<layout::TextDrawTarget*>(&aDrawTarget);
+        auto* manager = tdt->WrLayerManager();
+        wr::ImageKey key = {
+            manager->WrBridge()->GetNamespace(),
+            (uint32_t)(uintptr_t)aAtlas->GetUserData(reinterpret_cast<UserDataKey*>(manager))
+        };
+        // Transform the bounds of the atlas into the given orientation, and then also transform
+        // a small clip rect which will be used to select the given digit from the atlas.
+        Rect bounds(aLeft - aDigit * MINIFONT_WIDTH, aTop, MINIFONT_WIDTH * 16, MINIFONT_HEIGHT);
+        if (aMat) {
+            // Width and height may be negative after the transform, so move the rect
+            // if necessary and fix size.
+            bounds = aMat->TransformRect(bounds);
+            bounds.x += std::min(bounds.width, 0.0f);
+            bounds.y += std::min(bounds.height, 0.0f);
+            bounds.width = fabs(bounds.width);
+            bounds.height = fabs(bounds.height);
+            dest = aMat->TransformRect(dest);
+            dest.x += std::min(dest.width, 0.0f);
+            dest.y += std::min(dest.height, 0.0f);
+            dest.width = fabs(dest.width);
+            dest.height = fabs(dest.height);
+        }
+        // Finally, push the colored image with point filtering.
+        tdt->PushImage(key,
+                       wr::ToLayoutRect(bounds),
+                       wr::ToLayoutRect(dest),
+                       wr::ImageRendering::Pixelated,
+                       wr::ToColorF(aColor));
+    } else {
+        // For the normal case, just draw the given digit from the atlas. Point filtering is used
+        // to ensure the mini-font rectangles stay sharp with any scaling. Handle any transparency
+        // here as well.
+        aDrawTarget.DrawSurface(aAtlas,
+                                dest,
+                                Rect(aDigit * MINIFONT_WIDTH, 0, MINIFONT_WIDTH, MINIFONT_HEIGHT),
+                                DrawSurfaceOptions(SamplingFilter::POINT),
+                                DrawOptions(aColor.a, CompositionOp::OP_OVER, AntialiasMode::NONE));
+    }
+}
+
+void
+gfxFontMissingGlyphs::Purge()
+{
+    PurgeGlyphAtlas();
+    PurgeWRGlyphAtlas();
+}
+
+#else // MOZ_GFX_OPTIMIZE_MOBILE
+
+void
+gfxFontMissingGlyphs::Purge()
+{
+}
+
+#endif
+
+void
+gfxFontMissingGlyphs::Shutdown()
+{
+    Purge();
+}
 
 void
 gfxFontMissingGlyphs::DrawMissingGlyph(uint32_t aChar,
                                        const Rect& aRect,
                                        DrawTarget& aDrawTarget,
                                        const Pattern& aPattern,
                                        uint32_t aAppUnitsPerDevPixel,
                                        const Matrix* aMat)
@@ -223,41 +397,49 @@ gfxFontMissingGlyphs::DrawMissingGlyph(u
     if (aMat) {
         rect.MoveBy(-aRect.BottomLeft());
         rect = aMat->TransformBounds(rect);
         rect.MoveBy(aRect.BottomLeft());
     }
 
     // If we're currently drawing with some kind of pattern, we just draw the
     // missing-glyph data in black.
-    ColorPattern color = aPattern.GetType() == PatternType::COLOR ?
-        static_cast<const ColorPattern&>(aPattern) :
-        ColorPattern(ToDeviceColor(Color(0.f, 0.f, 0.f, 1.f)));
+    Color color = aPattern.GetType() == PatternType::COLOR ?
+        static_cast<const ColorPattern&>(aPattern).mColor :
+        ToDeviceColor(Color(0.f, 0.f, 0.f, 1.f));
 
     // Stroke a rectangle so that the stroke's left edge is inset one pixel
     // from the left edge of the glyph box and the stroke's right edge
     // is inset one pixel from the right edge of the glyph box.
     Float halfBorderWidth = BOX_BORDER_WIDTH / 2.0;
     Float borderLeft = rect.X() + BOX_HORIZONTAL_INSET + halfBorderWidth;
     Float borderRight = rect.XMost() - BOX_HORIZONTAL_INSET - halfBorderWidth;
     Rect borderStrokeRect(borderLeft, rect.Y() + halfBorderWidth,
                           borderRight - borderLeft,
                           rect.Height() - 2.0 * halfBorderWidth);
     if (!borderStrokeRect.IsEmpty()) {
-        ColorPattern adjustedColor = color;
+        ColorPattern adjustedColor(color);
         adjustedColor.mColor.a *= BOX_BORDER_OPACITY;
 #ifdef MOZ_GFX_OPTIMIZE_MOBILE
         aDrawTarget.FillRect(borderStrokeRect, adjustedColor);
 #else
         StrokeOptions strokeOptions(BOX_BORDER_WIDTH);
         aDrawTarget.StrokeRect(borderStrokeRect, adjustedColor, strokeOptions);
 #endif
     }
 
 #ifndef MOZ_GFX_OPTIMIZE_MOBILE
+    RefPtr<SourceSurface> atlas =
+        aDrawTarget.GetBackendType() == BackendType::WEBRENDER_TEXT ?
+            GetWRGlyphAtlas(aDrawTarget, aMat) :
+            GetGlyphAtlas(color);
+    if (!atlas) {
+        return;
+    }
+
     Point center = rect.Center();
     Float halfGap = HEX_CHAR_GAP / 2.f;
     Float top = -(MINIFONT_HEIGHT + halfGap);
     // We always want integer scaling, otherwise the "bitmap" glyphs will look
     // even uglier than usual when zoomed
     int32_t devPixelsPerCSSPx =
         std::max<int32_t>(1, AppUnitsPerCSSPixel() /
                              aAppUnitsPerDevPixel);
@@ -279,44 +461,34 @@ gfxFontMissingGlyphs::DrawMissingGlyph(u
                            .PreScale(devPixelsPerCSSPx, devPixelsPerCSSPx));
     }
 
     if (aChar < 0x10000) {
         if (rect.Width() >= 2 * (MINIFONT_WIDTH + HEX_CHAR_GAP) &&
             rect.Height() >= 2 * MINIFONT_HEIGHT + HEX_CHAR_GAP) {
             // Draw 4 digits for BMP
             Float left = -(MINIFONT_WIDTH + halfGap);
-            DrawHexChar((aChar >> 12) & 0xF,
-                        Point(left, top), aDrawTarget, color, aMat);
-            DrawHexChar((aChar >> 8) & 0xF,
-                        Point(halfGap, top), aDrawTarget, color, aMat);
-            DrawHexChar((aChar >> 4) & 0xF,
-                        Point(left, halfGap), aDrawTarget, color, aMat);
-            DrawHexChar(aChar & 0xF,
-                        Point(halfGap, halfGap), aDrawTarget, color, aMat);
+            DrawHexChar((aChar >> 12) & 0xF, left, top, aDrawTarget, atlas, color, aMat);
+            DrawHexChar((aChar >> 8) & 0xF, halfGap, top, aDrawTarget, atlas, color, aMat);
+            DrawHexChar((aChar >> 4) & 0xF, left, halfGap, aDrawTarget, atlas, color, aMat);
+            DrawHexChar(aChar & 0xF, halfGap, halfGap, aDrawTarget, atlas, color, aMat);
         }
     } else {
         if (rect.Width() >= 3 * (MINIFONT_WIDTH + HEX_CHAR_GAP) &&
             rect.Height() >= 2 * MINIFONT_HEIGHT + HEX_CHAR_GAP) {
             // Draw 6 digits for non-BMP
             Float first = -(MINIFONT_WIDTH * 1.5 + HEX_CHAR_GAP);
             Float second = -(MINIFONT_WIDTH / 2.0);
             Float third = (MINIFONT_WIDTH / 2.0 + HEX_CHAR_GAP);
-            DrawHexChar((aChar >> 20) & 0xF,
-                        Point(first, top), aDrawTarget, color, aMat);
-            DrawHexChar((aChar >> 16) & 0xF,
-                        Point(second, top), aDrawTarget, color, aMat);
-            DrawHexChar((aChar >> 12) & 0xF,
-                        Point(third, top), aDrawTarget, color, aMat);
-            DrawHexChar((aChar >> 8) & 0xF,
-                        Point(first, halfGap), aDrawTarget, color, aMat);
-            DrawHexChar((aChar >> 4) & 0xF,
-                        Point(second, halfGap), aDrawTarget, color, aMat);
-            DrawHexChar(aChar & 0xF,
-                        Point(third, halfGap), aDrawTarget, color, aMat);
+            DrawHexChar((aChar >> 20) & 0xF, first, top, aDrawTarget, atlas, color, aMat);
+            DrawHexChar((aChar >> 16) & 0xF, second, top, aDrawTarget, atlas, color, aMat);
+            DrawHexChar((aChar >> 12) & 0xF, third, top, aDrawTarget, atlas, color, aMat);
+            DrawHexChar((aChar >> 8) & 0xF, first, halfGap, aDrawTarget, atlas, color, aMat);
+            DrawHexChar((aChar >> 4) & 0xF, second, halfGap, aDrawTarget, atlas, color, aMat);
+            DrawHexChar(aChar & 0xF, third, halfGap, aDrawTarget, atlas, color, aMat);
         }
     }
 
     if (!aMat) {
         // The draw target transform was changed, so it must be restored to
         // the original value.
         aDrawTarget.SetTransform(tempMat);
     }
--- a/gfx/thebes/gfxFontMissingGlyphs.h
+++ b/gfx/thebes/gfxFontMissingGlyphs.h
@@ -49,11 +49,15 @@ public:
                                  uint32_t aAppUnitsPerDevPixel,
                                  const Matrix* aMat = nullptr);
     /**
      * @return the desired minimum width for a glyph-box that will allow
      * the hexboxes to be drawn reasonably.
      */
     static Float GetDesiredMinWidth(uint32_t aChar,
                                     uint32_t aAppUnitsPerDevUnit);
+
+    static void Purge();
+
+    static void Shutdown();
 };
 
 #endif
--- a/gfx/thebes/gfxPlatform.cpp
+++ b/gfx/thebes/gfxPlatform.cpp
@@ -68,16 +68,17 @@
 #include "gfxContext.h"
 #include "gfxImageSurface.h"
 #include "nsUnicodeProperties.h"
 #include "harfbuzz/hb.h"
 #include "gfxGraphiteShaper.h"
 #include "gfx2DGlue.h"
 #include "gfxGradientCache.h"
 #include "gfxUtils.h" // for NextPowerOfTwo
+#include "gfxFontMissingGlyphs.h"
 
 #include "nsExceptionHandler.h"
 #include "nsUnicodeRange.h"
 #include "nsServiceManagerUtils.h"
 #include "nsTArray.h"
 #include "nsIObserverService.h"
 #include "nsIScreenManager.h"
 #include "FrameMetrics.h"
@@ -465,16 +466,17 @@ FontPrefChanged(const char* aPref, void*
     gfxPlatform::GetPlatform()->FontsPrefsChanged(aPref);
 }
 
 void
 gfxPlatform::OnMemoryPressure(layers::MemoryPressureReason aWhy)
 {
     Factory::PurgeAllCaches();
     gfxGradientCache::PurgeAllCaches();
+    gfxFontMissingGlyphs::Purge();
     PurgeSkiaFontCache();
     PurgeSkiaGPUCache();
     if (XRE_IsParentProcess()) {
       layers::CompositorManagerChild* manager = CompositorManagerChild::GetInstance();
       if (manager) {
         manager->SendNotifyMemoryPressure();
       }
     }
@@ -959,16 +961,17 @@ gfxPlatform::Shutdown()
 
     // These may be called before the corresponding subsystems have actually
     // started up. That's OK, they can handle it.
     gfxFontCache::Shutdown();
     gfxGradientCache::Shutdown();
     gfxAlphaBoxBlur::ShutdownBlurCache();
     gfxGraphiteShaper::Shutdown();
     gfxPlatformFontList::Shutdown();
+    gfxFontMissingGlyphs::Shutdown();
     ShutdownTileCache();
 
     // Free the various non-null transforms and loaded profiles
     ShutdownCMS();
 
     /* Unregister our CMS Override callback. */
     NS_ASSERTION(gPlatform->mSRGBOverrideObserver, "mSRGBOverrideObserver has alreay gone");
     Preferences::RemoveObserver(gPlatform->mSRGBOverrideObserver, GFX_PREF_CMS_FORCE_SRGB);
--- a/gfx/webrender_bindings/WebRenderAPI.cpp
+++ b/gfx/webrender_bindings/WebRenderAPI.cpp
@@ -1042,39 +1042,41 @@ DisplayListBuilder::PushRadialGradient(c
 }
 
 void
 DisplayListBuilder::PushImage(const wr::LayoutRect& aBounds,
                               const wr::LayoutRect& aClip,
                               bool aIsBackfaceVisible,
                               wr::ImageRendering aFilter,
                               wr::ImageKey aImage,
-                              bool aPremultipliedAlpha)
+                              bool aPremultipliedAlpha,
+                              const wr::ColorF& aColor)
 {
   wr::LayoutSize size;
   size.width = aBounds.size.width;
   size.height = aBounds.size.height;
-  PushImage(aBounds, aClip, aIsBackfaceVisible, size, size, aFilter, aImage, aPremultipliedAlpha);
+  PushImage(aBounds, aClip, aIsBackfaceVisible, size, size, aFilter, aImage, aPremultipliedAlpha, aColor);
 }
 
 void
 DisplayListBuilder::PushImage(const wr::LayoutRect& aBounds,
                               const wr::LayoutRect& aClip,
                               bool aIsBackfaceVisible,
                               const wr::LayoutSize& aStretchSize,
                               const wr::LayoutSize& aTileSpacing,
                               wr::ImageRendering aFilter,
                               wr::ImageKey aImage,
-                              bool aPremultipliedAlpha)
+                              bool aPremultipliedAlpha,
+                              const wr::ColorF& aColor)
 {
   WRDL_LOG("PushImage b=%s cl=%s s=%s t=%s\n", mWrState,
       Stringify(aBounds).c_str(),
       Stringify(aClip).c_str(), Stringify(aStretchSize).c_str(),
       Stringify(aTileSpacing).c_str());
-  wr_dp_push_image(mWrState, aBounds, aClip, aIsBackfaceVisible, aStretchSize, aTileSpacing, aFilter, aImage, aPremultipliedAlpha);
+  wr_dp_push_image(mWrState, aBounds, aClip, aIsBackfaceVisible, aStretchSize, aTileSpacing, aFilter, aImage, aPremultipliedAlpha, aColor);
 }
 
 void
 DisplayListBuilder::PushYCbCrPlanarImage(const wr::LayoutRect& aBounds,
                                          const wr::LayoutRect& aClip,
                                          bool aIsBackfaceVisible,
                                          wr::ImageKey aImageChannel0,
                                          wr::ImageKey aImageChannel1,
--- a/gfx/webrender_bindings/WebRenderAPI.h
+++ b/gfx/webrender_bindings/WebRenderAPI.h
@@ -366,26 +366,28 @@ public:
                           const wr::LayoutSize aTileSize,
                           const wr::LayoutSize aTileSpacing);
 
   void PushImage(const wr::LayoutRect& aBounds,
                  const wr::LayoutRect& aClip,
                  bool aIsBackfaceVisible,
                  wr::ImageRendering aFilter,
                  wr::ImageKey aImage,
-                 bool aPremultipliedAlpha = true);
+                 bool aPremultipliedAlpha = true,
+                 const wr::ColorF& aColor = wr::ColorF{1.0f, 1.0f, 1.0f, 1.0f});
 
   void PushImage(const wr::LayoutRect& aBounds,
                  const wr::LayoutRect& aClip,
                  bool aIsBackfaceVisible,
                  const wr::LayoutSize& aStretchSize,
                  const wr::LayoutSize& aTileSpacing,
                  wr::ImageRendering aFilter,
                  wr::ImageKey aImage,
-                 bool aPremultipliedAlpha = true);
+                 bool aPremultipliedAlpha = true,
+                 const wr::ColorF& aColor = wr::ColorF{1.0f, 1.0f, 1.0f, 1.0f});
 
   void PushYCbCrPlanarImage(const wr::LayoutRect& aBounds,
                             const wr::LayoutRect& aClip,
                             bool aIsBackfaceVisible,
                             wr::ImageKey aImageChannel0,
                             wr::ImageKey aImageChannel1,
                             wr::ImageKey aImageChannel2,
                             wr::WrYuvColorSpace aColorSpace,
--- a/gfx/webrender_bindings/src/bindings.rs
+++ b/gfx/webrender_bindings/src/bindings.rs
@@ -1955,17 +1955,18 @@ pub extern "C" fn wr_dp_push_clear_rect(
 pub extern "C" fn wr_dp_push_image(state: &mut WrState,
                                    bounds: LayoutRect,
                                    clip: LayoutRect,
                                    is_backface_visible: bool,
                                    stretch_size: LayoutSize,
                                    tile_spacing: LayoutSize,
                                    image_rendering: ImageRendering,
                                    key: WrImageKey,
-                                   premultiplied_alpha: bool) {
+                                   premultiplied_alpha: bool,
+                                   color: ColorF) {
     debug_assert!(unsafe { is_in_main_thread() || is_in_compositor_thread() });
 
     let mut prim_info = LayoutPrimitiveInfo::with_clip_rect(bounds, clip.into());
     prim_info.is_backface_visible = is_backface_visible;
     prim_info.tag = state.current_tag;
     let alpha_type = if premultiplied_alpha {
         AlphaType::PremultipliedAlpha
     } else {
@@ -1974,17 +1975,17 @@ pub extern "C" fn wr_dp_push_image(state
     state.frame_builder
          .dl_builder
          .push_image(&prim_info,
                      stretch_size,
                      tile_spacing,
                      image_rendering,
                      alpha_type,
                      key,
-                     ColorF::WHITE);
+                     color);
 }
 
 /// Push a 3 planar yuv image.
 #[no_mangle]
 pub extern "C" fn wr_dp_push_yuv_planar_image(state: &mut WrState,
                                               bounds: LayoutRect,
                                               clip: LayoutRect,
                                               is_backface_visible: bool,
--- a/gfx/webrender_bindings/webrender_ffi_generated.h
+++ b/gfx/webrender_bindings/webrender_ffi_generated.h
@@ -1290,17 +1290,18 @@ WR_INLINE
 void wr_dp_push_image(WrState *aState,
                       LayoutRect aBounds,
                       LayoutRect aClip,
                       bool aIsBackfaceVisible,
                       LayoutSize aStretchSize,
                       LayoutSize aTileSpacing,
                       ImageRendering aImageRendering,
                       WrImageKey aKey,
-                      bool aPremultipliedAlpha)
+                      bool aPremultipliedAlpha,
+                      ColorF color)
 WR_FUNC;
 
 WR_INLINE
 void wr_dp_push_line(WrState *aState,
                      const LayoutRect *aClip,
                      bool aIsBackfaceVisible,
                      const LayoutRect *aBounds,
                      float aWavyLineThickness,
--- a/js/src/jit/ProcessExecutableMemory.cpp
+++ b/js/src/jit/ProcessExecutableMemory.cpp
@@ -39,16 +39,21 @@
 #ifdef MOZ_VALGRIND
 # include <valgrind/valgrind.h>
 #endif
 
 using namespace js;
 using namespace js::jit;
 
 #ifdef XP_WIN
+// TODO: implement the necessary support for AArch64.
+# if defined(HAVE_64BIT_BUILD) && defined(_M_X64)
+#  define NEED_JIT_UNWIND_HANDLING
+# endif
+
 static void*
 ComputeRandomAllocationAddress()
 {
     /*
      * Inspiration is V8's OS::Allocate in platform-win32.cc.
      *
      * VirtualAlloc takes 64K chunks out of the virtual address space, so we
      * keep 16b alignment.
@@ -67,17 +72,17 @@ ComputeRandomAllocationAddress()
 # else
 #  error "Unsupported architecture"
 # endif
 
     uint64_t rand = js::GenerateRandomSeed();
     return (void*) (base | (rand & mask));
 }
 
-# ifdef HAVE_64BIT_BUILD
+# ifdef NEED_JIT_UNWIND_HANDLING
 static js::JitExceptionHandler sJitExceptionHandler;
 
 JS_FRIEND_API(void)
 js::SetJitExceptionHandler(JitExceptionHandler handler)
 {
     MOZ_ASSERT(!sJitExceptionHandler);
     sJitExceptionHandler = handler;
 }
@@ -178,17 +183,17 @@ UnregisterExecutableMemory(void* p, size
     AutoSuppressStackWalking suppress;
     RtlDeleteFunctionTable(&r->runtimeFunction);
 }
 # endif
 
 static void*
 ReserveProcessExecutableMemory(size_t bytes)
 {
-# ifdef HAVE_64BIT_BUILD
+# ifdef NEED_JIT_UNWIND_HANDLING
     size_t pageSize = gc::SystemPageSize();
     if (sJitExceptionHandler)
         bytes += pageSize;
 # endif
 
     void* p = nullptr;
     for (size_t i = 0; i < 10; i++) {
         void* randomAddr = ComputeRandomAllocationAddress();
@@ -199,17 +204,17 @@ ReserveProcessExecutableMemory(size_t by
 
     if (!p) {
         // Try again without randomization.
         p = VirtualAlloc(nullptr, bytes, MEM_RESERVE, PAGE_NOACCESS);
         if (!p)
             return nullptr;
     }
 
-# ifdef HAVE_64BIT_BUILD
+# ifdef NEED_JIT_UNWIND_HANDLING
     if (sJitExceptionHandler) {
         if (!RegisterExecutableMemory(p, bytes, pageSize)) {
             VirtualFree(p, 0, MEM_RELEASE);
             return nullptr;
         }
 
         p = (uint8_t*)p + pageSize;
         bytes -= pageSize;
@@ -219,17 +224,17 @@ ReserveProcessExecutableMemory(size_t by
 # endif
 
     return p;
 }
 
 static void
 DeallocateProcessExecutableMemory(void* addr, size_t bytes)
 {
-# ifdef HAVE_64BIT_BUILD
+# ifdef NEED_JIT_UNWIND_HANDLING
     UnregisterJitCodeRegion((uint8_t*)addr, bytes);
 
     if (sJitExceptionHandler) {
         size_t pageSize = gc::SystemPageSize();
         addr = (uint8_t*)addr - pageSize;
         UnregisterExecutableMemory(addr, bytes, pageSize);
     }
 # endif
--- a/js/src/jit/mips32/Simulator-mips32.cpp
+++ b/js/src/jit/mips32/Simulator-mips32.cpp
@@ -1368,37 +1368,31 @@ class Redirection
 Simulator::~Simulator()
 {
     js_free(stack_);
 }
 
 SimulatorProcess::SimulatorProcess()
   : cacheLock_(mutexid::SimulatorCacheLock)
   , redirection_(nullptr)
-{}
+{
+    if (getenv("MIPS_SIM_ICACHE_CHECKS"))
+        ICacheCheckingDisableCount = 0;
+}
 
 SimulatorProcess::~SimulatorProcess()
 {
     Redirection* r = redirection_;
     while (r) {
         Redirection* next = r->next_;
         js_delete(r);
         r = next;
     }
 }
 
-bool
-SimulatorProcess::init()
-{
-    if (getenv("MIPS_SIM_ICACHE_CHECKS"))
-        ICacheCheckingDisableCount = 0;
-
-    return icache_.init();
-}
-
 /* static */ void*
 Simulator::RedirectNativeFunction(void* nativeFunction, ABIFunctionType type)
 {
     Redirection* redirection = Redirection::Get(nativeFunction, type);
     return redirection->addressOfSwiInstruction();
 }
 
 // Get the active Simulator for the current thread.
--- a/js/src/jit/mips32/Simulator-mips32.h
+++ b/js/src/jit/mips32/Simulator-mips32.h
@@ -415,29 +415,27 @@ class SimulatorProcess
 
     static mozilla::Atomic<size_t, mozilla::ReleaseAcquire> ICacheCheckingDisableCount;
     static void FlushICache(void* start, size_t size);
 
     static void checkICacheLocked(SimInstruction* instr);
 
     static bool initialize() {
         singleton_ = js_new<SimulatorProcess>();
-        return singleton_ && singleton_->init();
+        return singleton_;
     }
     static void destroy() {
         js_delete(singleton_);
         singleton_ = nullptr;
     }
 
     SimulatorProcess();
     ~SimulatorProcess();
 
   private:
-    bool init();
-
     static SimulatorProcess* singleton_;
 
     // This lock creates a critical section around 'redirection_' and
     // 'icache_', which are referenced both by the execution engine
     // and by the off-thread compiler (see Redirection::Get in the cpp file).
     Mutex cacheLock_;
 
     Redirection* redirection_;
--- a/js/src/jit/mips64/Simulator-mips64.cpp
+++ b/js/src/jit/mips64/Simulator-mips64.cpp
@@ -1376,37 +1376,31 @@ class Redirection
 Simulator::~Simulator()
 {
     js_free(stack_);
 }
 
 SimulatorProcess::SimulatorProcess()
   : cacheLock_(mutexid::SimulatorCacheLock)
   , redirection_(nullptr)
-{}
+{
+    if (getenv("MIPS_SIM_ICACHE_CHECKS"))
+        ICacheCheckingDisableCount = 0;
+}
 
 SimulatorProcess::~SimulatorProcess()
 {
     Redirection* r = redirection_;
     while (r) {
         Redirection* next = r->next_;
         js_delete(r);
         r = next;
     }
 }
 
-bool
-SimulatorProcess::init()
-{
-    if (getenv("MIPS_SIM_ICACHE_CHECKS"))
-        ICacheCheckingDisableCount = 0;
-
-    return icache_.init();
-}
-
 /* static */ void*
 Simulator::RedirectNativeFunction(void* nativeFunction, ABIFunctionType type)
 {
     Redirection* redirection = Redirection::Get(nativeFunction, type);
     return redirection->addressOfSwiInstruction();
 }
 
 // Get the active Simulator for the current thread.
--- a/js/src/jit/mips64/Simulator-mips64.h
+++ b/js/src/jit/mips64/Simulator-mips64.h
@@ -422,29 +422,27 @@ class SimulatorProcess
 
     static mozilla::Atomic<size_t, mozilla::ReleaseAcquire> ICacheCheckingDisableCount;
     static void FlushICache(void* start, size_t size);
 
     static void checkICacheLocked(SimInstruction* instr);
 
     static bool initialize() {
         singleton_ = js_new<SimulatorProcess>();
-        return singleton_ && singleton_->init();
+        return singleton_;
     }
     static void destroy() {
         js_delete(singleton_);
         singleton_ = nullptr;
     }
 
     SimulatorProcess();
     ~SimulatorProcess();
 
   private:
-    bool init();
-
     static SimulatorProcess* singleton_;
 
     // This lock creates a critical section around 'redirection_' and
     // 'icache_', which are referenced both by the execution engine
     // and by the off-thread compiler (see Redirection::Get in the cpp file).
     Mutex cacheLock_;
 
     Redirection* redirection_;
--- a/js/src/vm/JSScript.cpp
+++ b/js/src/vm/JSScript.cpp
@@ -4443,18 +4443,30 @@ JSScript::updateJitCodeRaw(JSRuntime* rt
 bool
 JSScript::hasLoops()
 {
     if (!hasTrynotes())
         return false;
     JSTryNote* tn = trynotes()->vector;
     JSTryNote* tnlimit = tn + trynotes()->length;
     for (; tn < tnlimit; tn++) {
-        if (tn->kind == JSTRY_FOR_IN || tn->kind == JSTRY_LOOP)
+        switch (tn->kind) {
+          case JSTRY_FOR_IN:
+          case JSTRY_FOR_OF:
+          case JSTRY_LOOP:
             return true;
+          case JSTRY_CATCH:
+          case JSTRY_FINALLY:
+          case JSTRY_FOR_OF_ITERCLOSE:
+          case JSTRY_DESTRUCTURING_ITERCLOSE:
+            break;
+          default:
+            MOZ_ASSERT(false, "Add new try note type to JSScript::hasLoops");
+            break;
+        }
     }
     return false;
 }
 
 bool
 JSScript::mayReadFrameArgsDirectly()
 {
     return argumentsHasVarBinding() || hasRest();
--- a/layout/generic/TextDrawTarget.h
+++ b/layout/generic/TextDrawTarget.h
@@ -7,16 +7,17 @@
 #ifndef TextDrawTarget_h
 #define TextDrawTarget_h
 
 #include "mozilla/gfx/2D.h"
 #include "mozilla/layers/WebRenderLayerManager.h"
 #include "mozilla/layers/WebRenderBridgeChild.h"
 #include "mozilla/webrender/WebRenderAPI.h"
 #include "mozilla/layers/StackingContextHelper.h"
+#include "mozilla/layers/IpcResourceUpdateQueue.h"
 
 namespace mozilla {
 namespace layout {
 
 using namespace gfx;
 
 // This class is fake DrawTarget, used to intercept text draw calls, while
 // also collecting up the other aspects of text natively.
@@ -45,21 +46,22 @@ using namespace gfx;
 // while we further develop the design.
 //
 // TextDrawTarget doesn't yet support all features. See mHasUnsupportedFeatures
 // for details.
 class TextDrawTarget : public DrawTarget
 {
 public:
   explicit TextDrawTarget(wr::DisplayListBuilder& aBuilder,
+                          wr::IpcResourceUpdateQueue& aResources,
                           const layers::StackingContextHelper& aSc,
                           layers::WebRenderLayerManager* aManager,
                           nsDisplayItem* aItem,
                           nsRect& aBounds)
-    : mBuilder(aBuilder), mSc(aSc), mManager(aManager)
+    : mBuilder(aBuilder), mResources(aResources), mSc(aSc), mManager(aManager)
   {
     SetPermitSubpixelAA(!aItem->IsSubpixelAADisabled());
 
     // Compute clip/bounds
     auto appUnitsPerDevPixel = aItem->Frame()->PresContext()->AppUnitsPerDevPixel();
     LayoutDeviceRect layoutBoundsRect = LayoutDeviceRect::FromAppUnits(
         aBounds, appUnitsPerDevPixel);
     LayoutDeviceRect layoutClipRect = layoutBoundsRect;
@@ -297,16 +299,43 @@ public:
     decoration.orientation = aVertical
       ? wr::LineOrientation::Vertical
       : wr::LineOrientation::Horizontal;
     decoration.style = wr::LineStyle::Wavy;
 
     mBuilder.PushLine(ClipRect(), mBackfaceVisible, decoration);
   }
 
+  layers::WebRenderBridgeChild* WrBridge() { return mManager->WrBridge(); }
+  layers::WebRenderLayerManager* WrLayerManager() { return mManager; }
+
+  Maybe<wr::ImageKey>
+  DefineImage(const IntSize& aSize,
+              uint32_t aStride,
+              SurfaceFormat aFormat,
+              const uint8_t* aData)
+  {
+    wr::ImageKey key = mManager->WrBridge()->GetNextImageKey();
+    wr::ImageDescriptor desc(aSize, aStride, aFormat);
+    Range<uint8_t> bytes(const_cast<uint8_t*>(aData), aStride * aSize.height);
+    if (mResources.AddImage(key, desc, bytes)) {
+        return Some(key);
+    }
+    return Nothing();
+  }
+
+  void PushImage(wr::ImageKey aKey,
+                 const wr::LayoutRect& aBounds,
+                 const wr::LayoutRect& aClip,
+                 wr::ImageRendering aFilter,
+                 const wr::ColorF& aColor)
+  {
+    mBuilder.PushImage(aBounds, aClip, true, aFilter, aKey, true, aColor);
+  }
+
 private:
   wr::LayoutRect ClipRect()
   {
     return wr::ToRoundedLayoutRect(mClipStack.LastElement());
   }
   // Whether anything unsupported was encountered. Currently:
   //
   // * Synthetic bold/italics
@@ -318,16 +347,17 @@ private:
   // * Text stroke
   bool mHasUnsupportedFeatures = false;
 
   // Whether PopAllShadows needs to be called
   bool mHasShadows = false;
 
   // Things used to push to webrender
   wr::DisplayListBuilder& mBuilder;
+  wr::IpcResourceUpdateQueue& mResources;
   const layers::StackingContextHelper& mSc;
   layers::WebRenderLayerManager* mManager;
 
   // Computed facts
   IntSize mSize;
   wr::LayoutRect mBoundsRect;
   nsTArray<LayoutDeviceRect> mClipStack;
   bool mBackfaceVisible;
@@ -413,44 +443,39 @@ public:
 
   void StrokeRect(const Rect &aRect,
                   const Pattern &aPattern,
                   const StrokeOptions &aStrokeOptions,
                   const DrawOptions &aOptions) override {
     MOZ_RELEASE_ASSERT(aPattern.GetType() == PatternType::COLOR &&
                        aStrokeOptions.mDashLength == 0);
 
-    wr::Line line;
-    line.wavyLineThickness = 0; // dummy value, unused
-    line.color = wr::ToColorF(static_cast<const ColorPattern&>(aPattern).mColor);
-    line.style = wr::LineStyle::Solid;
-
-    // Top horizontal line
-    LayoutDevicePoint top(aRect.x, aRect.y - aStrokeOptions.mLineWidth / 2);
-    LayoutDeviceSize horiSize(aRect.width, aStrokeOptions.mLineWidth);
-    line.bounds = wr::ToRoundedLayoutRect(LayoutDeviceRect(top, horiSize));
-    line.orientation = wr::LineOrientation::Horizontal;
-    mBuilder.PushLine(ClipRect(), mBackfaceVisible, line);
-
-    // Bottom horizontal line
-    LayoutDevicePoint bottom(aRect.x, aRect.YMost() - aStrokeOptions.mLineWidth / 2);
-    line.bounds = wr::ToRoundedLayoutRect(LayoutDeviceRect(bottom, horiSize));
-    mBuilder.PushLine(ClipRect(), mBackfaceVisible, line);
-
-    // Left vertical line
-    LayoutDevicePoint left(aRect.x + aStrokeOptions.mLineWidth / 2, aRect.y + aStrokeOptions.mLineWidth / 2);
-    LayoutDeviceSize vertSize(aStrokeOptions.mLineWidth, aRect.height - aStrokeOptions.mLineWidth);
-    line.bounds = wr::ToRoundedLayoutRect(LayoutDeviceRect(left, vertSize));
-    line.orientation = wr::LineOrientation::Vertical;
-    mBuilder.PushLine(ClipRect(), mBackfaceVisible, line);
-
-    // Right vertical line
-    LayoutDevicePoint right(aRect.XMost() - aStrokeOptions.mLineWidth / 2, aRect.y + aStrokeOptions.mLineWidth / 2);
-    line.bounds = wr::ToRoundedLayoutRect(LayoutDeviceRect(right, vertSize));
-    mBuilder.PushLine(ClipRect(), mBackfaceVisible, line);
+    wr::BorderWidths widths = {
+        aStrokeOptions.mLineWidth,
+        aStrokeOptions.mLineWidth,
+        aStrokeOptions.mLineWidth,
+        aStrokeOptions.mLineWidth
+    };
+    wr::ColorF color = wr::ToColorF(static_cast<const ColorPattern&>(aPattern).mColor);
+    wr::BorderSide sides[4] = {
+        { color, wr::BorderStyle::Solid },
+        { color, wr::BorderStyle::Solid },
+        { color, wr::BorderStyle::Solid },
+        { color, wr::BorderStyle::Solid }
+    };
+    wr::BorderRadius radius = {
+        { 0, 0 },
+        { 0, 0 },
+        { 0, 0 },
+        { 0, 0 }
+    };
+    Rect rect(aRect);
+    rect.Inflate(aStrokeOptions.mLineWidth / 2);
+    wr::LayoutRect bounds = wr::ToRoundedLayoutRect(LayoutDeviceRect::FromUnknownRect(rect));
+    mBuilder.PushBorder(bounds, ClipRect(), true, widths, Range<const wr::BorderSide>(sides, 4), radius);
   }
 
   void StrokeLine(const Point &aStart,
                   const Point &aEnd,
                   const Pattern &aPattern,
                   const StrokeOptions &aStrokeOptions,
                   const DrawOptions &aOptions) override {
     MOZ_CRASH("TextDrawTarget: Method shouldn't be called");
--- a/layout/generic/TextOverflow.cpp
+++ b/layout/generic/TextOverflow.cpp
@@ -299,17 +299,17 @@ nsDisplayTextOverflowMarker::CreateWebRe
 {
   bool snap;
   nsRect bounds = GetBounds(aDisplayListBuilder, &snap);
   if (bounds.IsEmpty()) {
     return true;
   }
 
   // Run the rendering algorithm to capture the glyphs and shadows
-  RefPtr<TextDrawTarget> textDrawer = new TextDrawTarget(aBuilder, aSc, aManager, this, bounds);
+  RefPtr<TextDrawTarget> textDrawer = new TextDrawTarget(aBuilder, aResources, aSc, aManager, this, bounds);
   RefPtr<gfxContext> captureCtx = gfxContext::CreateOrNull(textDrawer);
   Paint(aDisplayListBuilder, captureCtx);
   textDrawer->TerminateShadows();
 
   return !textDrawer->HasUnsupportedFeatures();
 }
 
 
--- a/layout/generic/nsBulletFrame.cpp
+++ b/layout/generic/nsBulletFrame.cpp
@@ -565,17 +565,17 @@ BulletRenderer::CreateWebRenderCommandsF
 
   bool dummy;
   nsRect bounds = aItem->GetBounds(aDisplayListBuilder, &dummy);
 
   if (bounds.IsEmpty()) {
     return true;
   }
 
-  RefPtr<TextDrawTarget> textDrawer = new TextDrawTarget(aBuilder, aSc, aManager, aItem, bounds);
+  RefPtr<TextDrawTarget> textDrawer = new TextDrawTarget(aBuilder, aResources, aSc, aManager, aItem, bounds);
   RefPtr<gfxContext> captureCtx = gfxContext::CreateOrNull(textDrawer);
   PaintTextToContext(aItem->Frame(), captureCtx, aItem->IsSubpixelAADisabled());
   textDrawer->TerminateShadows();
 
   return !textDrawer->HasUnsupportedFeatures();
 }
 
 class nsDisplayBullet final : public nsDisplayItem {
--- a/layout/generic/nsTextFrame.cpp
+++ b/layout/generic/nsTextFrame.cpp
@@ -5083,17 +5083,17 @@ nsDisplayText::CreateWebRenderCommands(m
     return true;
   }
 
 
   auto appUnitsPerDevPixel = Frame()->PresContext()->AppUnitsPerDevPixel();
   gfx::Point deviceOffset = LayoutDevicePoint::FromAppUnits(
       mBounds.TopLeft(), appUnitsPerDevPixel).ToUnknownPoint();
 
-  RefPtr<TextDrawTarget> textDrawer = new TextDrawTarget(aBuilder, aSc, aManager, this, mBounds);
+  RefPtr<TextDrawTarget> textDrawer = new TextDrawTarget(aBuilder, aResources, aSc, aManager, this, mBounds);
   RefPtr<gfxContext> captureCtx = gfxContext::CreateOrNull(textDrawer, deviceOffset);
 
   RenderToContext(captureCtx, aDisplayListBuilder, true);
 
   return !textDrawer->HasUnsupportedFeatures();
 }
 
 void
--- a/layout/reftests/bugs/reftest.list
+++ b/layout/reftests/bugs/reftest.list
@@ -1384,17 +1384,17 @@ random-if(/^Windows\x20NT\x206\.1/.test(
 random-if(/^Windows\x20NT\x206\.1/.test(http.oscpu)) == 495385-3.html 495385-3-ref.html # Bug 1392106
 random-if(/^Windows\x20NT\x206\.1/.test(http.oscpu)) == 495385-4.html 495385-4-ref.html # Bug 1392106
 == 496032-1.html 496032-1-ref.html
 == 496840-1.html 496840-1-ref.html
 == 501037.html 501037-ref.html
 == 501257-1a.html 501257-1-ref.html
 == 501257-1b.html 501257-1-ref.html
 == 501257-1.xhtml 501257-1-ref.xhtml
-fuzzy-if(webrender,5-6,83244-97456) == 501627-1.html 501627-1-ref.html
+fuzzy-if(webrender,2-6,39042-97456) == 501627-1.html 501627-1-ref.html # Bug 1481664
 == 502288-1.html 502288-1-ref.html
 fuzzy-if(gtkWidget,0-1,0-2) == 502447-1.html 502447-1-ref.html #Bug 1315834
 == 502795-1.html 502795-1-ref.html
 == 502942-1.html 502942-1-ref.html
 == 503364-1a.html 503364-1-ref.html
 == 503364-1b.html 503364-1-ref.html
 # Reftest for bug 503531 marked as failing; should be re-enabled when
 # bug 607548 gets resolved.
--- a/mobile/android/base/java/org/mozilla/gecko/customtabs/CustomTabsActivity.java
+++ b/mobile/android/base/java/org/mozilla/gecko/customtabs/CustomTabsActivity.java
@@ -146,28 +146,16 @@ public class CustomTabsActivity extends 
             finish();
         }
 
         sendTelemetry();
         recordCustomTabUsage(getReferrerHost());
     }
 
     @Override
-    public void onResume() {
-        mGeckoSession.setActive(true);
-        super.onResume();
-    }
-
-    @Override
-    public void onPause() {
-        mGeckoSession.setActive(false);
-        super.onPause();
-    }
-
-    @Override
     public void onDestroy() {
         mGeckoSession.close();
         mTextSelection.destroy();
         mFormAssistPopup.destroy();
         mDoorHangerPopup.destroy();
         mPromptService.destroy();
 
         super.onDestroy();
--- a/mobile/android/base/java/org/mozilla/gecko/webapps/WebAppActivity.java
+++ b/mobile/android/base/java/org/mozilla/gecko/webapps/WebAppActivity.java
@@ -192,28 +192,16 @@ public class WebAppActivity extends AppC
         if (android.os.Build.VERSION.SDK_INT >= 21) {
             finishAndRemoveTask();
         } else {
             finish();
         }
     }
 
     @Override
-    public void onResume() {
-        mGeckoSession.setActive(true);
-        super.onResume();
-    }
-
-    @Override
-    public void onPause() {
-        mGeckoSession.setActive(false);
-        super.onPause();
-    }
-
-    @Override
     public void onDestroy() {
         mGeckoSession.close();
         mTextSelection.destroy();
         mFormAssistPopup.destroy();
         mDoorHangerPopup.destroy();
         mPromptService.destroy();
         super.onDestroy();
     }
--- a/mobile/android/chrome/geckoview/GeckoViewContent.js
+++ b/mobile/android/chrome/geckoview/GeckoViewContent.js
@@ -2,21 +2,23 @@
 /* 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/. */
 
 ChromeUtils.import("resource://gre/modules/GeckoViewContentModule.jsm");
 ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
 
 XPCOMUtils.defineLazyModuleGetters(this, {
+  DeferredTask: "resource://gre/modules/DeferredTask.jsm",
+  FormData: "resource://gre/modules/FormData.jsm",
+  FormLikeFactory: "resource://gre/modules/FormLikeFactory.jsm",
+  PrivacyFilter: "resource://gre/modules/sessionstore/PrivacyFilter.jsm",
+  ScrollPosition: "resource://gre/modules/ScrollPosition.jsm",
   Services: "resource://gre/modules/Services.jsm",
   SessionHistory: "resource://gre/modules/sessionstore/SessionHistory.jsm",
-  FormData: "resource://gre/modules/FormData.jsm",
-  PrivacyFilter: "resource://gre/modules/sessionstore/PrivacyFilter.jsm",
-  ScrollPosition: "resource://gre/modules/ScrollPosition.jsm",
 });
 
 class GeckoViewContent extends GeckoViewContentModule {
   onInit() {
     debug `onInit`;
 
     // We don't load this in the global namespace because
     // a Utils.jsm in a11y will clobber us.
@@ -30,16 +32,27 @@ class GeckoViewContent extends GeckoView
                                            this);
     this.messageManager.addMessageListener("GeckoView:DOMFullscreenEntered",
                                            this);
     this.messageManager.addMessageListener("GeckoView:DOMFullscreenExited",
                                            this);
     this.messageManager.addMessageListener("GeckoView:ZoomToInput",
                                            this);
 
+    const options = {
+        mozSystemGroup: true,
+        capture: false,
+    };
+    addEventListener("DOMFormHasPassword", this, options);
+    addEventListener("DOMInputPasswordAdded", this, options);
+    addEventListener("pagehide", this, options);
+    addEventListener("pageshow", this, options);
+    addEventListener("focusin", this, options);
+    addEventListener("focusout", this, options);
+
     // Notify WebExtension process script that this tab is ready for extension content to load.
     Services.obs.notifyObservers(this.messageManager, "tab-content-frameloader-created");
   }
 
   onEnable() {
     debug `onEnable`;
 
     addEventListener("DOMTitleChanged", this, false);
@@ -174,18 +187,30 @@ class GeckoViewContent extends GeckoView
         break;
 
       case "GeckoView:RestoreState":
         this._savedState = JSON.parse(aMsg.data.state);
 
         if (this._savedState.history) {
           let restoredHistory = SessionHistory.restore(docShell, this._savedState.history);
 
-          addEventListener("load", this, {capture: true, mozSystemGroup: true, once: true});
-          addEventListener("pageshow", this, {capture: true, mozSystemGroup: true, once: true});
+          addEventListener("load", _ => {
+            const formdata = this._savedState.formdata;
+            if (formdata) {
+              FormData.restoreTree(content, formdata);
+            }
+          }, {capture: true, mozSystemGroup: true, once: true});
+
+          addEventListener("pageshow", _ => {
+            const scrolldata = this._savedState.scrolldata;
+            if (scrolldata) {
+              ScrollPosition.restoreTree(content, scrolldata);
+            }
+            delete this._savedState;
+          }, {capture: true, mozSystemGroup: true, once: true});
 
           if (!this.progressFilter) {
             this.progressFilter =
               Cc["@mozilla.org/appshell/component/browser-status-filter;1"]
               .createInstance(Ci.nsIWebProgress);
             this.flags = Ci.nsIWebProgress.NOTIFY_LOCATION;
           }
 
@@ -228,16 +253,27 @@ class GeckoViewContent extends GeckoView
             elementType,
             elementSrc: (isImage || isMedia)
                         ? node.currentSrc || node.src
                         : null
           });
           aEvent.preventDefault();
         }
         break;
+      case "DOMFormHasPassword":
+        this._addAutoFillElement(
+            FormLikeFactory.createFromForm(aEvent.composedTarget));
+        break;
+      case "DOMInputPasswordAdded": {
+        const input = aEvent.composedTarget;
+        if (!input.form) {
+          this._addAutoFillElement(FormLikeFactory.createFromField(input));
+        }
+        break;
+      }
       case "MozDOMFullscreen:Request":
         sendAsyncMessage("GeckoView:DOMFullscreenRequest");
         break;
       case "MozDOMFullscreen:Entered":
       case "MozDOMFullscreen:Exited":
         // Content may change fullscreen state by itself, and we should ensure
         // that the parent always exits fullscreen when content has left
         // full screen mode.
@@ -264,31 +300,36 @@ class GeckoViewContent extends GeckoView
           return;
         }
 
         aEvent.preventDefault();
         this.eventDispatcher.sendRequest({
           type: "GeckoView:DOMWindowClose"
         });
         break;
-      case "load": {
-        const formdata = this._savedState.formdata;
-        if (formdata) {
-          FormData.restoreTree(content, formdata);
+      case "focusin":
+        if (aEvent.composedTarget instanceof content.HTMLInputElement) {
+          this._onAutoFillFocus(aEvent.composedTarget);
+        }
+        break;
+      case "focusout":
+        if (aEvent.composedTarget instanceof content.HTMLInputElement) {
+          this._onAutoFillFocus(null);
         }
         break;
-      }
-      case "pageshow": {
-        const scrolldata = this._savedState.scrolldata;
-        if (scrolldata) {
-          ScrollPosition.restoreTree(content, scrolldata);
+      case "pagehide":
+        if (aEvent.target === content.document) {
+          this._clearAutoFillElements();
         }
-        delete this._savedState;
         break;
-      }
+      case "pageshow":
+        if (aEvent.target === content.document && aEvent.persisted) {
+          this._scanAutoFillDocument(aEvent.target);
+        }
+        break;
     }
   }
 
   // WebProgress event handler.
   onLocationChange(aWebProgress, aRequest, aLocationURI, aFlags) {
     debug `onLocationChange`;
 
     if (this._savedState) {
@@ -302,12 +343,206 @@ class GeckoViewContent extends GeckoView
       }
     }
 
     this.progressFilter.removeProgressListener(this);
     let webProgress = docShell.QueryInterface(Ci.nsIInterfaceRequestor)
                               .getInterface(Ci.nsIWebProgress);
     webProgress.removeProgressListener(this.progressFilter);
   }
+
+  /**
+   * Process an auto-fillable form and send the relevant details of the form
+   * to Java. Multiple calls within a short time period for the same form are
+   * coalesced, so that, e.g., if multiple inputs are added to a form in
+   * succession, we will only perform one processing pass. Note that for inputs
+   * without forms, FormLikeFactory treats the document as the "form", but
+   * there is no difference in how we process them.
+   *
+   * @param aFormLike A FormLike object produced by FormLikeFactory.
+   * @param aFromDeferredTask Signal that this call came from our internal
+   *                          coalescing task. Other caller must not use this
+   *                          parameter.
+   */
+  _addAutoFillElement(aFormLike, aFromDeferredTask) {
+    let task = this._autoFillTasks &&
+               this._autoFillTasks.get(aFormLike.rootElement);
+    if (task && !aFromDeferredTask) {
+      // We already have a pending task; cancel that and start a new one.
+      debug `Canceling previous auto-fill task`;
+      task.disarm();
+      task = null;
+    }
+
+    if (!task) {
+      if (aFromDeferredTask) {
+        // Canceled before we could run the task.
+        debug `Auto-fill task canceled`;
+        return;
+      }
+      // Start a new task so we can coalesce adding elements in one batch.
+      debug `Deferring auto-fill task`;
+      task = new DeferredTask(
+          () => this._addAutoFillElement(aFormLike, true), 100);
+      task.arm();
+      if (!this._autoFillTasks) {
+        this._autoFillTasks = new WeakMap();
+      }
+      this._autoFillTasks.set(aFormLike.rootElement, task);
+      return;
+    }
+
+    debug `Adding auto-fill ${aFormLike}`;
+
+    this._autoFillTasks.delete(aFormLike.rootElement);
+    this._autoFillId = this._autoFillId || 0;
+
+    if (!this._autoFillInfos) {
+      this._autoFillInfos = new WeakMap();
+      this._autoFillElements = new Map();
+    }
+
+    let sendFocusEvent = false;
+    const getInfo = (element, parent) => {
+      let info = this._autoFillInfos.get(element);
+      if (info) {
+        return info;
+      }
+      info = {
+        id: ++this._autoFillId,
+        parent,
+        tag: element.tagName,
+        type: element instanceof content.HTMLInputElement ? element.type : null,
+        editable: (element instanceof content.HTMLInputElement) &&
+                  ["color", "date", "datetime-local", "email", "month",
+                   "number", "password", "range", "search", "tel", "text",
+                   "time", "url", "week"].includes(element.type),
+        disabled: element instanceof content.HTMLInputElement ? element.disabled
+                                                              : null,
+        attributes: Object.assign({}, ...Array.from(element.attributes)
+            .filter(attr => attr.localName !== "value")
+            .map(attr => ({[attr.localName]: attr.value}))),
+        origin: element.ownerDocument.location.origin,
+      };
+      this._autoFillInfos.set(element, info);
+      this._autoFillElements.set(info.id, Cu.getWeakReference(element));
+      sendFocusEvent |= (element === element.ownerDocument.activeElement);
+      return info;
+    };
+
+    const rootInfo = getInfo(aFormLike.rootElement, null);
+    rootInfo.children = aFormLike.elements.map(
+        element => getInfo(element, rootInfo.id));
+
+    this.eventDispatcher.dispatch("GeckoView:AddAutoFill", rootInfo, {
+      onSuccess: responses => {
+        // `responses` is an object with IDs as keys.
+        debug `Performing auto-fill ${responses}`;
+
+        const AUTOFILL_STATE = "-moz-autofill";
+        const winUtils = content.windowUtils;
+
+        for (let id in responses) {
+          const entry = this._autoFillElements &&
+                        this._autoFillElements.get(+id);
+          const element = entry && entry.get();
+          const value = responses[id] || "";
+
+          if (element instanceof content.HTMLInputElement &&
+              !element.disabled && element.parentElement) {
+            element.value = value;
+
+            // Fire both "input" and "change" events.
+            element.dispatchEvent(new element.ownerGlobal.Event(
+                "input", { bubbles: true }));
+            element.dispatchEvent(new element.ownerGlobal.Event(
+                "change", { bubbles: true }));
+
+            if (winUtils && element.value === value) {
+              // Add highlighting for autofilled fields.
+              winUtils.addManuallyManagedState(element, AUTOFILL_STATE);
+
+              // Remove highlighting when the field is changed.
+              element.addEventListener("input", _ =>
+                  winUtils.removeManuallyManagedState(element, AUTOFILL_STATE),
+                  { mozSystemGroup: true, once: true });
+            }
+
+          } else if (element) {
+            warn `Don't know how to auto-fill ${element.tagName}`;
+          }
+        }
+      },
+      onError: error => {
+        warn `Cannot perform autofill ${error}`;
+      },
+    });
+
+    if (sendFocusEvent) {
+      // We might have missed sending a focus event for the active element.
+      this._onAutoFillFocus(aFormLike.ownerDocument.activeElement);
+    }
+  }
+
+  /**
+   * Called when an auto-fillable field is focused or blurred.
+   *
+   * @param aTarget Focused element, or null if an element has lost focus.
+   */
+  _onAutoFillFocus(aTarget) {
+    debug `Auto-fill focus on ${aTarget && aTarget.tagName}`;
+
+    let info = aTarget && this._autoFillInfos &&
+               this._autoFillInfos.get(aTarget);
+    if (!aTarget || info) {
+      this.eventDispatcher.dispatch("GeckoView:OnAutoFillFocus", info);
+    }
+  }
+
+  /**
+   * Clear all tracked auto-fill forms and notify Java.
+   */
+  _clearAutoFillElements() {
+    debug `Clearing auto-fill`;
+
+    this._autoFillTasks = undefined;
+    this._autoFillInfos = undefined;
+    this._autoFillElements = undefined;
+
+    this.eventDispatcher.sendRequest({
+      type: "GeckoView:ClearAutoFill",
+    });
+  }
+
+  /**
+   * Scan for auto-fillable forms and add them if necessary. Called when a page
+   * is navigated to through history, in which case we don't get our typical
+   * "input added" notifications.
+   *
+   * @param aDoc Document to scan.
+   */
+  _scanAutoFillDocument(aDoc) {
+    // Add forms first; only check forms with password inputs.
+    const inputs = aDoc.querySelectorAll("input[type=password]");
+    let inputAdded = false;
+    for (let i = 0; i < inputs.length; i++) {
+      if (inputs[i].form) {
+        // Let _addAutoFillElement coalesce multiple calls for the same form.
+        this._addAutoFillElement(
+            FormLikeFactory.createFromForm(inputs[i].form));
+      } else if (!inputAdded) {
+        // Treat inputs without forms as one unit, and process them only once.
+        inputAdded = true;
+        this._addAutoFillElement(
+            FormLikeFactory.createFromField(inputs[i]));
+      }
+    }
+
+    // Finally add frames.
+    const frames = aDoc.defaultView.frames;
+    for (let i = 0; i < frames.length; i++) {
+      this._scanAutoFillDocument(frames[i].document);
+    }
+  }
 }
 
 let {debug, warn} = GeckoViewContent.initLogging("GeckoViewContent");
 let module = GeckoViewContent.create(this);
new file mode 100644
--- /dev/null
+++ b/mobile/android/geckoview/src/androidTest/assets/www/forms.html
@@ -0,0 +1,26 @@
+<html>
+    <head><title>Forms</title></head>
+    <body>
+        <form>
+            <input type="text" id="user1" value="foo">
+            <input type="password" id="pass1", value="foo">
+            <input type="email" id="email1", value="@">
+            <input type="number" id="number1", value="0">
+            <input type="tel" id="tel1", value="0">
+        </form>
+        <input type="Text" id="user2" value="foo">
+        <input type="PassWord" id="pass2" maxlength="8" value="foo">
+        <input type="button" id="button1" value="foo"/>
+        <input type="checkbox" id="checkbox1"/>
+        <input type="hidden" id="hidden1" value="foo"/>
+
+        <iframe id="iframe"></iframe>
+    </body>
+    <script>
+        addEventListener("load", function(e) {
+            if (window.parent === window) {
+                document.getElementById("iframe").contentWindow.location.href = window.location.href;
+            }
+        });
+    </script>
+</html>
--- a/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/AccessibilityTest.kt
+++ b/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/AccessibilityTest.kt
@@ -1,33 +1,37 @@
 /* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
  * Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 package org.mozilla.geckoview.test
 
 import org.mozilla.geckoview.test.rule.GeckoSessionTestRule.AssertCalled
+import org.mozilla.geckoview.test.rule.GeckoSessionTestRule.ReuseSession
 import org.mozilla.geckoview.test.rule.GeckoSessionTestRule.WithDisplay
 import org.mozilla.geckoview.test.rule.GeckoSessionTestRule.WithDevToolsAPI
 
 import android.graphics.Rect
 
 import android.os.Build
 import android.os.Bundle
 
 import android.support.test.filters.MediumTest
 import android.support.test.InstrumentationRegistry
 import android.support.test.runner.AndroidJUnit4
+import android.text.InputType
+import android.util.SparseLongArray
 
 import android.view.accessibility.AccessibilityNodeInfo
 import android.view.accessibility.AccessibilityNodeProvider
 import android.view.accessibility.AccessibilityEvent
 import android.view.accessibility.AccessibilityRecord
 import android.view.View
 import android.view.ViewGroup
+import android.widget.EditText
 
 import android.widget.FrameLayout
 
 import org.hamcrest.Matchers.*
 import org.junit.Test
 import org.junit.Before
 import org.junit.After
 import org.junit.runner.RunWith
@@ -61,16 +65,26 @@ class AccessibilityTest : BaseSessionTes
             val getSourceIdMethod =
                 AccessibilityRecord::class.java.getMethod("getSourceNodeId")
             return getVirtualDescendantId(getSourceIdMethod.invoke(event) as Long)
         } catch (ex: Exception) {
             return 0
         }
     }
 
+    // Get a child ID by index.
+    private fun AccessibilityNodeInfo.getChildId(index: Int): Int =
+            getVirtualDescendantId(
+                    if (Build.VERSION.SDK_INT >= 21)
+                        AccessibilityNodeInfo::class.java.getMethod(
+                                "getChildId", Int::class.java).invoke(this, index) as Long
+                    else
+                        (AccessibilityNodeInfo::class.java.getMethod("getChildNodeIds")
+                                .invoke(this) as SparseLongArray).get(index))
+
     private interface EventDelegate {
         fun onAccessibilityFocused(event: AccessibilityEvent) { }
         fun onClicked(event: AccessibilityEvent) { }
         fun onFocused(event: AccessibilityEvent) { }
         fun onSelected(event: AccessibilityEvent) { }
         fun onScrolled(event: AccessibilityEvent) { }
         fun onTextSelectionChanged(event: AccessibilityEvent) { }
         fun onTextChanged(event: AccessibilityEvent) { }
@@ -557,9 +571,161 @@ class AccessibilityTest : BaseSessionTes
 
             @AssertCalled(count = 1, order = [3])
             override fun onWinContentChanged(event: AccessibilityEvent) {
                 nodeId = getSourceId(event)
                 assertThat("Focused node is onscreen", screenContainsNode(nodeId), equalTo(true))
             }
         })
     }
+
+    @ReuseSession(false) // XXX automation crash fix (bug 1485107)
+    @WithDevToolsAPI
+    @Test fun autoFill() {
+        // Wait for the accessibility nodes to populate.
+        mainSession.loadTestPath(FORMS_HTML_PATH)
+        sessionRule.waitUntilCalled(object : EventDelegate {
+            // For the root document and the iframe document, each has a form group and
+            // a group for inputs outside of forms, so the total count is 4.
+            @AssertCalled(count = 4)
+            override fun onWinContentChanged(event: AccessibilityEvent) {
+            }
+        })
+
+        val autoFills = mapOf(
+                "#user1" to "bar", "#pass1" to "baz", "#user2" to "bar", "#pass2" to "baz") +
+                if (Build.VERSION.SDK_INT >= 19) mapOf(
+                        "#email1" to "a@b.c", "#number1" to "24", "#tel1" to "42")
+                else mapOf(
+                        "#email1" to "bar", "#number1" to "", "#tel1" to "bar")
+
+        // Set up promises to monitor the values changing.
+        val promises = autoFills.flatMap { entry ->
+            // Repeat each test with both the top document and the iframe document.
+            arrayOf("document", "$('#iframe').contentDocument").map { doc ->
+                mainSession.evaluateJS("""new Promise(resolve =>
+                    $doc.querySelector('${entry.key}').addEventListener(
+                        'input', event => resolve([event.target.value, '${entry.value}']),
+                        { once: true }))""").asJSPromise()
+            }
+        }
+
+        // Perform auto-fill and return number of auto-fills performed.
+        fun autoFillChild(id: Int, child: AccessibilityNodeInfo) {
+            // Seal the node info instance so we can perform actions on it.
+            if (child.childCount > 0) {
+                for (i in 0 until child.childCount) {
+                    val childId = child.getChildId(i)
+                    autoFillChild(childId, provider.createAccessibilityNodeInfo(childId))
+                }
+            }
+
+            if (EditText::class.java.name == child.className) {
+                assertThat("Input should be enabled", child.isEnabled, equalTo(true))
+                assertThat("Input should be focusable", child.isFocusable, equalTo(true))
+                if (Build.VERSION.SDK_INT >= 19) {
+                    assertThat("Password type should match", child.isPassword, equalTo(
+                            child.inputType == InputType.TYPE_CLASS_TEXT or
+                                    InputType.TYPE_TEXT_VARIATION_WEB_PASSWORD))
+                }
+
+                val args = Bundle(1)
+                val value = if (child.isPassword) "baz" else
+                    if (Build.VERSION.SDK_INT < 19) "bar" else
+                        when (child.inputType) {
+                            InputType.TYPE_CLASS_TEXT or
+                                    InputType.TYPE_TEXT_VARIATION_WEB_EMAIL_ADDRESS -> "a@b.c"
+                            InputType.TYPE_CLASS_NUMBER -> "24"
+                            InputType.TYPE_CLASS_PHONE -> "42"
+                            else -> "bar"
+                        }
+
+                val ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE = if (Build.VERSION.SDK_INT >= 21)
+                    AccessibilityNodeInfo.ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE else
+                    "ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE"
+                val ACTION_SET_TEXT = if (Build.VERSION.SDK_INT >= 21)
+                    AccessibilityNodeInfo.ACTION_SET_TEXT else 0x200000
+
+                args.putCharSequence(ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE, value)
+                assertThat("Can perform auto-fill",
+                           provider.performAction(id, ACTION_SET_TEXT, args), equalTo(true))
+            }
+            child.recycle()
+        }
+
+        autoFillChild(View.NO_ID, provider.createAccessibilityNodeInfo(View.NO_ID))
+
+        // Wait on the promises and check for correct values.
+        for ((actual, expected) in promises.map { it.value.asJSList<String>() }) {
+            assertThat("Auto-filled value must match", actual, equalTo(expected))
+        }
+    }
+
+    @ReuseSession(false) // XXX automation crash fix (bug 1485107)
+    @Test fun autoFill_navigation() {
+        fun countAutoFillNodes(cond: (AccessibilityNodeInfo) -> Boolean =
+                                       { it.className == "android.widget.EditText" },
+                               id: Int = View.NO_ID): Int {
+            val info = provider.createAccessibilityNodeInfo(id)
+            try {
+                return (if (cond(info)) 1 else 0) + (if (info.childCount > 0)
+                    (0 until info.childCount).sumBy {
+                        countAutoFillNodes(cond, info.getChildId(it))
+                    } else 0)
+            } finally {
+                info.recycle()
+            }
+        }
+
+        // Wait for the accessibility nodes to populate.
+        mainSession.loadTestPath(FORMS_HTML_PATH)
+        sessionRule.waitUntilCalled(object : EventDelegate {
+            @AssertCalled(count = 4)
+            override fun onWinContentChanged(event: AccessibilityEvent) {
+            }
+        })
+        assertThat("Initial auto-fill count should match",
+                   countAutoFillNodes(), equalTo(14))
+        assertThat("Password auto-fill count should match",
+                   countAutoFillNodes({ it.isPassword }), equalTo(4))
+
+        // Now wait for the nodes to clear.
+        mainSession.loadTestPath(HELLO_HTML_PATH)
+        mainSession.waitForPageStop()
+        assertThat("Should not have auto-fill fields",
+                   countAutoFillNodes(), equalTo(0))
+
+        // Now wait for the nodes to reappear.
+        mainSession.goBack()
+        sessionRule.waitUntilCalled(object : EventDelegate {
+            @AssertCalled(count = 4)
+            override fun onWinContentChanged(event: AccessibilityEvent) {
+            }
+        })
+        assertThat("Should have auto-fill fields again",
+                   countAutoFillNodes(), equalTo(14))
+        assertThat("Should not have focused field",
+                   countAutoFillNodes({ it.isFocused }), equalTo(0))
+
+        mainSession.evaluateJS("$('#pass1').focus()")
+        sessionRule.waitUntilCalled(object : EventDelegate {
+            @AssertCalled
+            override fun onFocused(event: AccessibilityEvent) {
+            }
+        })
+        assertThat("Should have one focused field",
+                   countAutoFillNodes({ it.isFocused }), equalTo(1))
+        // The focused field, its siblings, and its parent should be visible.
+        assertThat("Should have at least six visible fields",
+                   countAutoFillNodes({ node -> node.isVisibleToUser &&
+                           !(Rect().also({ node.getBoundsInScreen(it) }).isEmpty) }),
+                   greaterThanOrEqualTo(6))
+
+        mainSession.evaluateJS("$('#pass1').blur()")
+        sessionRule.waitUntilCalled(object : EventDelegate {
+            @AssertCalled
+            override fun onFocused(event: AccessibilityEvent) {
+            }
+        })
+        assertThat("Should not have focused field",
+                   countAutoFillNodes({ it.isFocused }), equalTo(0))
+    }
 }
--- a/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/BaseSessionTest.kt
+++ b/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/BaseSessionTest.kt
@@ -23,16 +23,17 @@ import kotlin.reflect.KClass
  * Common base class for tests using GeckoSessionTestRule,
  * providing the test rule and other utilities.
  */
 open class BaseSessionTest(noErrorCollector: Boolean = false) {
     companion object {
         const val CLICK_TO_RELOAD_HTML_PATH = "/assets/www/clickToReload.html"
         const val CONTENT_CRASH_URL = "about:crashcontent"
         const val DOWNLOAD_HTML_PATH = "/assets/www/download.html"
+        const val FORMS_HTML_PATH = "/assets/www/forms.html"
         const val HELLO_HTML_PATH = "/assets/www/hello.html"
         const val HELLO2_HTML_PATH = "/assets/www/hello2.html"
         const val INPUTS_PATH = "/assets/www/inputs.html"
         const val INVALID_URI = "http://www.test.invalid/"
         const val LOREM_IPSUM_HTML_PATH = "/assets/www/loremIpsum.html"
         const val NEW_SESSION_HTML_PATH = "/assets/www/newSession.html"
         const val NEW_SESSION_CHILD_HTML_PATH = "/assets/www/newSession_child.html"
         const val SAVE_STATE_PATH = "/assets/www/saveState.html"
--- a/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/util/Callbacks.kt
+++ b/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/util/Callbacks.kt
@@ -160,10 +160,13 @@ class Callbacks private constructor() {
         override fun updateSelection(session: GeckoSession, selStart: Int, selEnd: Int, compositionStart: Int, compositionEnd: Int) {
         }
 
         override fun updateExtractedText(session: GeckoSession, request: ExtractedTextRequest, text: ExtractedText) {
         }
 
         override fun updateCursorAnchorInfo(session: GeckoSession, info: CursorAnchorInfo) {
         }
+
+        override fun notifyAutoFill(session: GeckoSession, notification: Int, virtualId: Int) {
+        }
     }
 }
--- a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoSession.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoSession.java
@@ -1003,16 +1003,22 @@ public class GeckoSession extends LayerS
 
         onWindowChanged(WINDOW_CLOSE, /* inProgress */ false);
     }
 
     private void onWindowChanged(int change, boolean inProgress) {
         if ((change == WINDOW_OPEN || change == WINDOW_TRANSFER_IN) && !inProgress) {
             mTextInput.onWindowChanged(mWindow);
         }
+        if ((change == WINDOW_CLOSE || change == WINDOW_TRANSFER_OUT) && !inProgress) {
+            if (mAccessibility != null) {
+                mAccessibility.clearAutoFill();
+            }
+            mTextInput.clearAutoFill();
+        }
     }
 
     /**
      * Get the SessionTextInput instance for this session. May be called on any thread.
      *
      * @return SessionTextInput instance.
      */
     public @NonNull SessionTextInput getTextInput() {
@@ -1292,28 +1298,51 @@ public class GeckoSession extends LayerS
     public SessionFinder getFinder() {
         if (mFinder == null) {
             mFinder = new SessionFinder(getEventDispatcher());
         }
         return mFinder;
     }
 
     /**
-    * Set this GeckoSession as active or inactive. Setting a GeckoSession to inactive will
-    * significantly reduce its memory footprint, but should only be done if the
-    * GeckoSession is not currently visible.
-    * @param active A boolean determining whether the GeckoSession is active
-    */
+     * Set this GeckoSession as active or inactive, which represents if the session is currently
+     * visible or not. Setting a GeckoSession to inactive will significantly reduce its memory
+     * footprint, but should only be done if the GeckoSession is not currently visible. Note that
+     * a session can be active (i.e. visible) but not focused.
+     *
+     * @param active A boolean determining whether the GeckoSession is active.
+     *
+     * @see #setFocused
+     */
     public void setActive(boolean active) {
-        final GeckoBundle msg = new GeckoBundle();
+        final GeckoBundle msg = new GeckoBundle(1);
         msg.putBoolean("active", active);
         mEventDispatcher.dispatch("GeckoView:SetActive", msg);
     }
 
     /**
+     * Move focus to this session or away from this session. Only one session has focus at
+     * a given time. Note that a session can be unfocused but still active (i.e. visible).
+     *
+     * @param focused True if the session should gain focus or
+     *                false if the session should lose focus.
+     *
+     * @see #setActive
+     */
+    public void setFocused(boolean focused) {
+        final GeckoBundle msg = new GeckoBundle(1);
+        msg.putBoolean("focused", focused);
+        mEventDispatcher.dispatch("GeckoView:SetFocused", msg);
+
+        if (focused && mAccessibility != null) {
+            mAccessibility.onWindowFocus();
+        }
+    }
+
+    /**
      * Class representing a saved session state.
      */
     public static class SessionState implements Parcelable {
         private String mState;
 
         /**
          * Construct a SessionState from a String.
          *
@@ -3338,10 +3367,50 @@ public class GeckoSession extends LayerS
          * Update the cursor-anchor information as requested through
          * {@link android.view.inputmethod.InputConnection#requestCursorUpdates}.
          * Consequently, this method is <i>not</i> called in viewless mode.
          *
          * @param session Session instance.
          * @param info Cursor-anchor information.
          */
         void updateCursorAnchorInfo(@NonNull GeckoSession session, @NonNull CursorAnchorInfo info);
+
+        @Retention(RetentionPolicy.SOURCE)
+        @IntDef({AUTO_FILL_NOTIFY_STARTED, AUTO_FILL_NOTIFY_COMMITTED, AUTO_FILL_NOTIFY_CANCELED,
+                AUTO_FILL_NOTIFY_VIEW_ADDED, AUTO_FILL_NOTIFY_VIEW_REMOVED,
+                AUTO_FILL_NOTIFY_VIEW_UPDATED, AUTO_FILL_NOTIFY_VIEW_ENTERED,
+                AUTO_FILL_NOTIFY_VIEW_EXITED})
+        /* package */ @interface AutoFillNotification {}
+
+        /** An auto-fill session has started, usually as a result of loading a page. */
+        int AUTO_FILL_NOTIFY_STARTED = 0;
+        /** An auto-fill session has been committed, usually as a result of submitting a form. */
+        int AUTO_FILL_NOTIFY_COMMITTED = 1;
+        /** An auto-fill session has been canceled, usually as a result of unloading a page. */
+        int AUTO_FILL_NOTIFY_CANCELED = 2;
+        /** A view within the auto-fill session has been added. */
+        int AUTO_FILL_NOTIFY_VIEW_ADDED = 3;
+        /** A view within the auto-fill session has been removed. */
+        int AUTO_FILL_NOTIFY_VIEW_REMOVED = 4;
+        /** A view within the auto-fill session has been updated (e.g. change in state). */
+        int AUTO_FILL_NOTIFY_VIEW_UPDATED = 5;
+        /** A view within the auto-fill session has gained focus. */
+        int AUTO_FILL_NOTIFY_VIEW_ENTERED = 6;
+        /** A view within the auto-fill session has lost focus. */
+        int AUTO_FILL_NOTIFY_VIEW_EXITED = 7;
+
+        /**
+         * Notify that an auto-fill event has occurred. The default implementation forwards the
+         * notification to the system {@link android.view.autofill.AutofillManager}. This method is
+         * only called on Android 6.0 and above, and it is called in viewless mode as well.
+         *
+         * @param session Session instance.
+         * @param notification Notification type as one of the {@link #AUTO_FILL_NOTIFY_STARTED
+         *                     AUTO_FILL_NOTIFY_*} constants.
+         * @param virtualId Virtual ID of the target, or {@link android.view.View#NO_ID} if not
+         *                  applicable. The ID matches one of the virtual IDs provided by {@link
+         *                  SessionTextInput#onProvideAutofillVirtualStructure} and can be used
+         *                  with {@link SessionTextInput#autofill}.
+         */
+        void notifyAutoFill(@NonNull GeckoSession session, @AutoFillNotification int notification,
+                            int virtualId);
     }
 }
--- a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoView.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoView.java
@@ -9,38 +9,42 @@ package org.mozilla.geckoview;
 import org.mozilla.gecko.AndroidGamepadManager;
 import org.mozilla.gecko.EventDispatcher;
 import org.mozilla.gecko.gfx.DynamicToolbarAnimator;
 import org.mozilla.gecko.gfx.PanZoomController;
 import org.mozilla.gecko.gfx.GeckoDisplay;
 import org.mozilla.gecko.InputMethods;
 import org.mozilla.gecko.util.ActivityUtils;
 
+import android.annotation.TargetApi;
 import android.app.Activity;
 import android.content.Context;
 import android.content.res.Configuration;
 import android.graphics.Canvas;
 import android.graphics.Color;
 import android.graphics.Rect;
 import android.graphics.Region;
 import android.os.Build;
 import android.os.Handler;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.support.annotation.Nullable;
 import android.support.annotation.NonNull;
 import android.util.AttributeSet;
 import android.util.DisplayMetrics;
+import android.util.SparseArray;
 import android.util.TypedValue;
 import android.view.KeyEvent;
 import android.view.MotionEvent;
 import android.view.SurfaceHolder;
 import android.view.SurfaceView;
 import android.view.View;
 import android.view.ViewGroup;
+import android.view.ViewStructure;
+import android.view.autofill.AutofillValue;
 import android.view.inputmethod.EditorInfo;
 import android.view.inputmethod.InputConnection;
 import android.view.inputmethod.InputMethodManager;
 import android.widget.FrameLayout;
 
 public class GeckoView extends FrameLayout {
     private static final String LOGTAG = "GeckoView";
     private static final boolean DEBUG = false;
@@ -103,21 +107,23 @@ public class GeckoView extends FrameLayo
 
             // Tell display there is already a surface.
             onGlobalLayout();
             if (GeckoView.this.mSurfaceView != null) {
                 final SurfaceHolder holder = GeckoView.this.mSurfaceView.getHolder();
                 final Rect frame = holder.getSurfaceFrame();
                 mDisplay.surfaceChanged(holder.getSurface(), frame.right, frame.bottom);
             }
+            GeckoView.this.setActive(true);
         }
 
         public GeckoDisplay release() {
             if (mValid) {
                 mDisplay.surfaceDestroyed();
+                GeckoView.this.setActive(false);
             }
 
             final GeckoDisplay display = mDisplay;
             mDisplay = null;
             return display;
         }
 
         @Override // SurfaceHolder.Callback
@@ -125,24 +131,26 @@ public class GeckoView extends FrameLayo
         }
 
         @Override // SurfaceHolder.Callback
         public void surfaceChanged(final SurfaceHolder holder, final int format,
                                    final int width, final int height) {
             if (mDisplay != null) {
                 mDisplay.surfaceChanged(holder.getSurface(), width, height);
             }
+            GeckoView.this.setActive(true);
             mValid = true;
         }
 
         @Override // SurfaceHolder.Callback
         public void surfaceDestroyed(final SurfaceHolder holder) {
             if (mDisplay != null) {
                 mDisplay.surfaceDestroyed();
             }
+            GeckoView.this.setActive(false);
             mValid = false;
         }
 
         public void onGlobalLayout() {
             if (mDisplay == null) {
                 return;
             }
             if (GeckoView.this.mSurfaceView != null) {
@@ -198,16 +206,22 @@ public class GeckoView extends FrameLayo
      * @param color Cover color.
      */
     public void coverUntilFirstPaint(final int color) {
         if (mSurfaceView != null) {
             mSurfaceView.setBackgroundColor(color);
         }
     }
 
+    /* package */ void setActive(final boolean active) {
+        if (mSession != null) {
+            mSession.setActive(active);
+        }
+    }
+
     public GeckoSession releaseSession() {
         if (mSession == null) {
             return null;
         }
 
         // Cover the view while we are not drawing to the surface.
         coverUntilFirstPaint(Color.WHITE);
 
@@ -219,20 +233,23 @@ public class GeckoView extends FrameLayo
         if (mSession.getAccessibility().getView() == this) {
             mSession.getAccessibility().setView(null);
         }
 
         if (mSession.getTextInput().getView() == this) {
             mSession.getTextInput().setView(null);
         }
 
-        if (session.getSelectionActionDelegate() == mSelectionActionDelegate) {
+        if (mSession.getSelectionActionDelegate() == mSelectionActionDelegate) {
             mSession.setSelectionActionDelegate(null);
         }
 
+        if (isFocused()) {
+            mSession.setFocused(false);
+        }
         mSession = null;
         return session;
     }
 
     /**
      * Attach a session to this view. The session should be opened before
      * attaching.
      *
@@ -310,16 +327,20 @@ public class GeckoView extends FrameLayo
 
         if (session.getAccessibility().getView() == null) {
             session.getAccessibility().setView(this);
         }
 
         if (session.getSelectionActionDelegate() == null && mSelectionActionDelegate != null) {
             session.setSelectionActionDelegate(mSelectionActionDelegate);
         }
+
+        if (isFocused()) {
+            session.setFocused(true);
+        }
     }
 
     public GeckoSession getSession() {
         return mSession;
     }
 
     public EventDispatcher getEventDispatcher() {
         return mSession.getEventDispatcher();
@@ -411,20 +432,37 @@ public class GeckoView extends FrameLayo
             setSession(ss.session, ss.session.getRuntime());
         } else if (ss.session != null) {
             mSession.transferFrom(ss.session);
             mRuntime = mSession.getRuntime();
         }
     }
 
     @Override
+    public void onWindowFocusChanged(boolean hasWindowFocus) {
+        super.onWindowFocusChanged(hasWindowFocus);
+
+        if (mSession != null) {
+            mSession.setFocused(hasWindowFocus && isFocused());
+        }
+    }
+
+    @Override
     public void onFocusChanged(boolean gainFocus, int direction, Rect previouslyFocusedRect) {
         super.onFocusChanged(gainFocus, direction, previouslyFocusedRect);
 
-        if (!gainFocus || mIsResettingFocus) {
+        if (mIsResettingFocus) {
+            return;
+        }
+
+        if (mSession != null) {
+            mSession.setFocused(gainFocus);
+        }
+
+        if (!gainFocus) {
             return;
         }
 
         post(new Runnable() {
             @Override
             public void run() {
                 if (!isFocused()) {
                     return;
@@ -547,9 +585,37 @@ public class GeckoView extends FrameLayo
 
         if (mSession == null) {
             return false;
         }
 
         return mSession.getAccessibility().onMotionEvent(event) ||
                mSession.getPanZoomController().onMotionEvent(event);
     }
+
+    @Override
+    public void onProvideAutofillVirtualStructure(final ViewStructure structure, int flags) {
+        super.onProvideAutofillVirtualStructure(structure, flags);
+
+        if (mSession != null) {
+            mSession.getTextInput().onProvideAutofillVirtualStructure(structure, flags);
+        }
+    }
+
+    @Override
+    @TargetApi(26)
+    public void autofill(@NonNull final SparseArray<AutofillValue> values) {
+        super.autofill(values);
+
+        if (mSession == null) {
+            return;
+        }
+        final SparseArray<CharSequence> strValues = new SparseArray<>(values.size());
+        for (int i = 0; i < values.size(); i++) {
+            final AutofillValue value = values.valueAt(i);
+            if (value.isText()) {
+                // Only text is currently supported.
+                strValues.put(values.keyAt(i), value.getTextValue());
+            }
+        }
+        mSession.getTextInput().autofill(strValues);
+    }
 }
--- a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/SessionAccessibility.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/SessionAccessibility.java
@@ -12,56 +12,265 @@ import org.mozilla.gecko.util.BundleEven
 import org.mozilla.gecko.util.EventCallback;
 import org.mozilla.gecko.util.GeckoBundle;
 
 import android.content.Context;
 import android.graphics.Matrix;
 import android.graphics.Rect;
 import android.os.Build;
 import android.os.Bundle;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.text.InputType;
 import android.util.Log;
+import android.util.SparseArray;
 import android.view.InputDevice;
 import android.view.MotionEvent;
 import android.view.View;
 import android.view.ViewParent;
 import android.view.accessibility.AccessibilityEvent;
 import android.view.accessibility.AccessibilityManager;
 import android.view.accessibility.AccessibilityNodeInfo;
 import android.view.accessibility.AccessibilityNodeProvider;
 
 public class SessionAccessibility {
     private static final String LOGTAG = "GeckoAccessibility";
-    // This is a special ID we use for nodes that are eent sources.
+    private static final boolean DEBUG = false;
+
+    // This is a special ID we use for nodes that are event sources.
     // We expose it as a fragment and not an actual child of the View node.
     private static final int VIRTUAL_CONTENT_ID = -2;
 
     // This is the number BrailleBack uses to start indexing routing keys.
     private static final int BRAILLE_CLICK_BASE_INDEX = -275000000;
 
+    private static final int ACTION_SET_TEXT = 0x200000;
+    private static final String ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE =
+            "ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE";
+
+    /* package */ final class NodeProvider extends AccessibilityNodeProvider {
+        @Override
+        public AccessibilityNodeInfo createAccessibilityNodeInfo(int virtualDescendantId) {
+            AccessibilityNodeInfo info = getAutoFillNode(virtualDescendantId);
+            if (info != null) {
+                // Try auto-fill nodes first.
+                return info;
+            }
+
+            info = (virtualDescendantId == VIRTUAL_CONTENT_ID && mVirtualContentNode != null)
+                   ? AccessibilityNodeInfo.obtain(mVirtualContentNode)
+                   : AccessibilityNodeInfo.obtain(mView, virtualDescendantId);
+
+            switch (virtualDescendantId) {
+            case View.NO_ID:
+                // This is the parent View node.
+                // We intentionally don't add VIRTUAL_CONTENT_ID
+                // as a child. It is a source for events,
+                // but not a member of the tree you
+                // can get to by traversing down.
+                if (Build.VERSION.SDK_INT < 17 || mView.getDisplay() != null) {
+                    // When running junit tests we don't have a display
+                    mView.onInitializeAccessibilityNodeInfo(info);
+                }
+                info.setPackageName(GeckoAppShell.getApplicationContext().getPackageName());
+                info.setClassName("android.webkit.WebView"); // TODO: WTF
+
+                if (Build.VERSION.SDK_INT >= 19) {
+                    Bundle bundle = info.getExtras();
+                    bundle.putCharSequence(
+                        "ACTION_ARGUMENT_HTML_ELEMENT_STRING_VALUES",
+                        "ARTICLE,BUTTON,CHECKBOX,COMBOBOX,CONTROL," +
+                        "FOCUSABLE,FRAME,GRAPHIC,H1,H2,H3,H4,H5,H6," +
+                        "HEADING,LANDMARK,LINK,LIST,LIST_ITEM,MAIN," +
+                        "MEDIA,RADIO,SECTION,TABLE,TEXT_FIELD," +
+                        "UNVISITED_LINK,VISITED_LINK");
+                }
+                info.addAction(AccessibilityNodeInfo.ACTION_NEXT_HTML_ELEMENT);
+
+                if (mAutoFillRoots != null) {
+                    // Add auto-fill nodes.
+                    if (DEBUG) {
+                        Log.d(LOGTAG, "Adding roots " + mAutoFillRoots);
+                    }
+                    for (int i = 0; i < mAutoFillRoots.size(); i++) {
+                        info.addChild(mView, mAutoFillRoots.keyAt(i));
+                    }
+                }
+                break;
+            default:
+                info.setParent(mView);
+                info.setSource(mView, virtualDescendantId);
+                info.setVisibleToUser(mView.isShown());
+                info.setPackageName(GeckoAppShell.getApplicationContext().getPackageName());
+                info.setEnabled(true);
+                info.addAction(AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY);
+                info.addAction(AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY);
+                info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD);
+                info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_FORWARD);
+                info.addAction(AccessibilityNodeInfo.ACTION_NEXT_HTML_ELEMENT);
+                info.addAction(AccessibilityNodeInfo.ACTION_PREVIOUS_HTML_ELEMENT);
+                info.setMovementGranularities(AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER |
+                                              AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD |
+                                              AccessibilityNodeInfo.MOVEMENT_GRANULARITY_LINE);
+                break;
+            }
+            return info;
+        }
+
+        @Override
+        public boolean performAction(int virtualViewId, int action, Bundle arguments) {
+            if (virtualViewId == View.NO_ID) {
+                return performRootAction(action, arguments);
+            }
+            if (action == AccessibilityNodeInfo.ACTION_SET_TEXT) {
+                final String value = arguments.getString(Build.VERSION.SDK_INT >= 21
+                        ? AccessibilityNodeInfo.ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE
+                        : ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE);
+                return performAutoFill(virtualViewId, value);
+            }
+            return performContentAction(action, arguments);
+        }
+
+        private boolean performRootAction(int action, Bundle arguments) {
+            switch (action) {
+            case AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS:
+            case AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS:
+                final GeckoBundle data = new GeckoBundle(1);
+                data.putBoolean("gainFocus", action == AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS);
+                mSession.getEventDispatcher().dispatch("GeckoView:AccessibilityViewFocused", data);
+                return true;
+            }
+
+            return mView.performAccessibilityAction(action, arguments);
+        }
+
+        @SuppressWarnings("fallthrough")
+        private boolean performContentAction(int action, Bundle arguments) {
+            final GeckoBundle data;
+            switch (action) {
+            case AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS:
+                final AccessibilityEvent event = obtainEvent(AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED, VIRTUAL_CONTENT_ID);
+                ((ViewParent) mView).requestSendAccessibilityEvent(mView, event);
+                return true;
+            case AccessibilityNodeInfo.ACTION_CLICK:
+                mSession.getEventDispatcher().dispatch("GeckoView:AccessibilityActivate", null);
+                return true;
+            case AccessibilityNodeInfo.ACTION_LONG_CLICK:
+                mSession.getEventDispatcher().dispatch("GeckoView:AccessibilityLongPress", null);
+                return true;
+            case AccessibilityNodeInfo.ACTION_SCROLL_FORWARD:
+                mSession.getEventDispatcher().dispatch("GeckoView:AccessibilityScrollForward", null);
+                return true;
+            case AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD:
+                mSession.getEventDispatcher().dispatch("GeckoView:AccessibilityScrollBackward", null);
+                return true;
+            case AccessibilityNodeInfo.ACTION_SELECT:
+                mSession.getEventDispatcher().dispatch("GeckoView:AccessibilitySelect", null);
+                return true;
+            case AccessibilityNodeInfo.ACTION_NEXT_HTML_ELEMENT:
+                if (mLastItem) {
+                    return false;
+                }
+                // fall-through
+            case AccessibilityNodeInfo.ACTION_PREVIOUS_HTML_ELEMENT:
+                if (arguments != null) {
+                    data = new GeckoBundle(1);
+                    data.putString("rule", arguments.getString(AccessibilityNodeInfo.ACTION_ARGUMENT_HTML_ELEMENT_STRING));
+                } else {
+                    data = null;
+                }
+                mSession.getEventDispatcher().dispatch(action == AccessibilityNodeInfo.ACTION_NEXT_HTML_ELEMENT ?
+                                                       "GeckoView:AccessibilityNext" : "GeckoView:AccessibilityPrevious", data);
+                return true;
+            case AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY:
+            case AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY:
+                // XXX: Self brailling gives this action with a bogus argument instead of an actual click action;
+                // the argument value is the BRAILLE_CLICK_BASE_INDEX - the index of the routing key that was hit.
+                // Other negative values are used by ChromeVox, but we don't support them.
+                // FAKE_GRANULARITY_READ_CURRENT = -1
+                // FAKE_GRANULARITY_READ_TITLE = -2
+                // FAKE_GRANULARITY_STOP_SPEECH = -3
+                // FAKE_GRANULARITY_CHANGE_SHIFTER = -4
+                int granularity = arguments.getInt(AccessibilityNodeInfo.ACTION_ARGUMENT_MOVEMENT_GRANULARITY_INT);
+                if (granularity <= BRAILLE_CLICK_BASE_INDEX) {
+                    int keyIndex = BRAILLE_CLICK_BASE_INDEX - granularity;
+                    data = new GeckoBundle(1);
+                    data.putInt("keyIndex", keyIndex);
+                    mSession.getEventDispatcher().dispatch("GeckoView:AccessibilityActivate", data);
+                } else if (granularity > 0) {
+                    boolean extendSelection = arguments.getBoolean(AccessibilityNodeInfo.ACTION_ARGUMENT_EXTEND_SELECTION_BOOLEAN);
+                    data = new GeckoBundle(3);
+                    data.putString("direction", action == AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY ? "Next" : "Previous");
+                    data.putInt("granularity", granularity);
+                    data.putBoolean("select", extendSelection);
+                    mSession.getEventDispatcher().dispatch("GeckoView:AccessibilityByGranularity", data);
+                }
+                return true;
+            case AccessibilityNodeInfo.ACTION_SET_SELECTION:
+                if (arguments == null) {
+                    return false;
+                }
+                int selectionStart = arguments.getInt(AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_START_INT);
+                int selectionEnd = arguments.getInt(AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_END_INT);
+                data = new GeckoBundle(2);
+                data.putInt("start", selectionStart);
+                data.putInt("end", selectionEnd);
+                mSession.getEventDispatcher().dispatch("GeckoView:AccessibilitySetSelection", data);
+                return true;
+            case AccessibilityNodeInfo.ACTION_CUT:
+            case AccessibilityNodeInfo.ACTION_COPY:
+            case AccessibilityNodeInfo.ACTION_PASTE:
+                data = new GeckoBundle(1);
+                data.putInt("action", action);
+                mSession.getEventDispatcher().dispatch("GeckoView:AccessibilityClipboard", data);
+                return true;
+            }
+
+            return mView.performAccessibilityAction(action, arguments);
+        }
+    };
+
     // Gecko session we are proxying
     /* package */  final GeckoSession mSession;
     // This is the view that delegates accessibility to us. We also sends event through it.
     private View mView;
-    // Aave we reached the last item in content?
+    // Have we reached the last item in content?
     private boolean mLastItem;
     // Used to store the JSON message and populate the event later in the code path.
     private AccessibilityNodeInfo mVirtualContentNode;
+    // Auto-fill nodes.
+    private SparseArray<GeckoBundle> mAutoFillNodes;
+    private SparseArray<EventCallback> mAutoFillRoots;
+    private int mAutoFillFocusedId = View.NO_ID;
 
     /* package */ SessionAccessibility(final GeckoSession session) {
         mSession = session;
 
-        Settings.getInstance().dispatch();
+        Settings.updateAccessibilitySettings();
 
         session.getEventDispatcher().registerUiThreadListener(new BundleEventListener() {
-            @Override
-            public void handleMessage(final String event, final GeckoBundle message,
-                                      final EventCallback callback) {
-                sendAccessibilityEvent(message);
-            }
-        }, "GeckoView:AccessibilityEvent", null);
+                @Override
+                public void handleMessage(final String event, final GeckoBundle message,
+                                          final EventCallback callback) {
+                    if ("GeckoView:AccessibilityEvent".equals(event)) {
+                        sendAccessibilityEvent(message);
+                    } else if ("GeckoView:AddAutoFill".equals(event)) {
+                        addAutoFill(message, callback);
+                    } else if ("GeckoView:ClearAutoFill".equals(event)) {
+                        clearAutoFill();
+                    } else if ("GeckoView:OnAutoFillFocus".equals(event)) {
+                        onAutoFillFocus(message);
+                    }
+                }
+            },
+            "GeckoView:AccessibilityEvent",
+            "GeckoView:AddAutoFill",
+            "GeckoView:ClearAutoFill",
+            "GeckoView:OnAutoFillFocus",
+            null);
     }
 
     /**
       * Get the View instance that delegates accessibility to this session.
       *
       * @return View instance.
       */
     public View getView() {
@@ -81,212 +290,43 @@ public class SessionAccessibility {
         mView = view;
         mLastItem = false;
 
         if (mView == null) {
             return;
         }
 
         mView.setAccessibilityDelegate(new View.AccessibilityDelegate() {
-            private AccessibilityNodeProvider mAccessibilityNodeProvider;
+            private NodeProvider mProvider;
 
             @Override
             public AccessibilityNodeProvider getAccessibilityNodeProvider(final View hostView) {
-
-                if (mAccessibilityNodeProvider == null)
-                    mAccessibilityNodeProvider = new AccessibilityNodeProvider() {
-                    @Override
-                    public AccessibilityNodeInfo createAccessibilityNodeInfo(int virtualDescendantId) {
-                        assertAttachedView(hostView);
-
-                        AccessibilityNodeInfo info = (virtualDescendantId == VIRTUAL_CONTENT_ID && mVirtualContentNode != null) ?
-                                                     AccessibilityNodeInfo.obtain(mVirtualContentNode) :
-                                                     AccessibilityNodeInfo.obtain(mView, virtualDescendantId);
-
-                        switch (virtualDescendantId) {
-                        case View.NO_ID:
-                            // This is the parent View node.
-                            // We intentionally don't add VIRTUAL_CONTENT_ID
-                            // as a child. It is a source for events,
-                            // but not a member of the tree you
-                            // can get to by traversing down.
-                            if (mView.getDisplay() != null) {
-                                // When running junit tests we don't have a display
-                                onInitializeAccessibilityNodeInfo(mView, info);
-                            }
-                            info.setClassName("android.webkit.WebView"); // TODO: WTF
-                            if (Build.VERSION.SDK_INT >= 19) {
-                                Bundle bundle = info.getExtras();
-                                bundle.putCharSequence(
-                                    "ACTION_ARGUMENT_HTML_ELEMENT_STRING_VALUES",
-                                    "ARTICLE,BUTTON,CHECKBOX,COMBOBOX,CONTROL," +
-                                    "FOCUSABLE,FRAME,GRAPHIC,H1,H2,H3,H4,H5,H6," +
-                                    "HEADING,LANDMARK,LINK,LIST,LIST_ITEM,MAIN," +
-                                    "MEDIA,RADIO,SECTION,TABLE,TEXT_FIELD," +
-                                    "UNVISITED_LINK,VISITED_LINK");
-                            }
-                            info.addAction(AccessibilityNodeInfo.ACTION_NEXT_HTML_ELEMENT);
-                            info.addChild(hostView, VIRTUAL_CONTENT_ID);
-                            break;
-                        default:
-                            info.setParent(mView);
-                            info.setSource(mView, virtualDescendantId);
-                            info.setVisibleToUser(mView.isShown());
-                            info.setPackageName(GeckoAppShell.getApplicationContext().getPackageName());
-                            info.setEnabled(true);
-                            info.addAction(AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY);
-                            info.addAction(AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY);
-                            info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD);
-                            info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_FORWARD);
-                            info.addAction(AccessibilityNodeInfo.ACTION_NEXT_HTML_ELEMENT);
-                            info.addAction(AccessibilityNodeInfo.ACTION_PREVIOUS_HTML_ELEMENT);
-                            info.setMovementGranularities(AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER |
-                                                          AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD |
-                                                          AccessibilityNodeInfo.MOVEMENT_GRANULARITY_LINE);
-                            break;
-                        }
-                        return info;
-                    }
-
-                    @Override
-                    public boolean performAction(int virtualViewId, int action, Bundle arguments) {
-                        assertAttachedView(hostView);
-
-                        if (virtualViewId == View.NO_ID) {
-                            return performRootAction(action, arguments);
-                        }
-                        return performContentAction(action, arguments);
-                    }
-
-                    private boolean performRootAction(int action, Bundle arguments) {
-                        switch (action) {
-                        case AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS:
-                        case AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS:
-                            final GeckoBundle data = new GeckoBundle(1);
-                            data.putBoolean("gainFocus", action == AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS);
-                            mSession.getEventDispatcher().dispatch("GeckoView:AccessibilityViewFocused", data);
-                            return true;
-                        }
-
-                        return mView.performAccessibilityAction(action, arguments);
-                    }
-
-                    @SuppressWarnings("fallthrough")
-                    private boolean performContentAction(int action, Bundle arguments) {
-                        final GeckoBundle data;
-                        switch (action) {
-                        case AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS:
-                            final AccessibilityEvent event = obtainEvent(AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED, VIRTUAL_CONTENT_ID);
-                            ((ViewParent) mView).requestSendAccessibilityEvent(mView, event);
-                            return true;
-                        case AccessibilityNodeInfo.ACTION_CLICK:
-                            mSession.getEventDispatcher().dispatch("GeckoView:AccessibilityActivate", null);
-                            return true;
-                        case AccessibilityNodeInfo.ACTION_LONG_CLICK:
-                            mSession.getEventDispatcher().dispatch("GeckoView:AccessibilityLongPress", null);
-                            return true;
-                        case AccessibilityNodeInfo.ACTION_SCROLL_FORWARD:
-                            mSession.getEventDispatcher().dispatch("GeckoView:AccessibilityScrollForward", null);
-                            return true;
-                        case AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD:
-                            mSession.getEventDispatcher().dispatch("GeckoView:AccessibilityScrollBackward", null);
-                            return true;
-                        case AccessibilityNodeInfo.ACTION_SELECT:
-                            mSession.getEventDispatcher().dispatch("GeckoView:AccessibilitySelect", null);
-                            return true;
-                        case AccessibilityNodeInfo.ACTION_NEXT_HTML_ELEMENT:
-                            if (mLastItem) {
-                                return false;
-                            }
-                            // fall-through
-                        case AccessibilityNodeInfo.ACTION_PREVIOUS_HTML_ELEMENT:
-                            if (arguments != null) {
-                                data = new GeckoBundle(1);
-                                data.putString("rule", arguments.getString(AccessibilityNodeInfo.ACTION_ARGUMENT_HTML_ELEMENT_STRING));
-                            } else {
-                                data = null;
-                            }
-                            mSession.getEventDispatcher().dispatch(action == AccessibilityNodeInfo.ACTION_NEXT_HTML_ELEMENT ?
-                                                                   "GeckoView:AccessibilityNext" : "GeckoView:AccessibilityPrevious", data);
-                            return true;
-                        case AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY:
-                        case AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY:
-                            // XXX: Self brailling gives this action with a bogus argument instead of an actual click action;
-                            // the argument value is the BRAILLE_CLICK_BASE_INDEX - the index of the routing key that was hit.
-                            // Other negative values are used by ChromeVox, but we don't support them.
-                            // FAKE_GRANULARITY_READ_CURRENT = -1
-                            // FAKE_GRANULARITY_READ_TITLE = -2
-                            // FAKE_GRANULARITY_STOP_SPEECH = -3
-                            // FAKE_GRANULARITY_CHANGE_SHIFTER = -4
-                            int granularity = arguments.getInt(AccessibilityNodeInfo.ACTION_ARGUMENT_MOVEMENT_GRANULARITY_INT);
-                            if (granularity <= BRAILLE_CLICK_BASE_INDEX) {
-                                int keyIndex = BRAILLE_CLICK_BASE_INDEX - granularity;
-                                data = new GeckoBundle(1);
-                                data.putInt("keyIndex", keyIndex);
-                                mSession.getEventDispatcher().dispatch("GeckoView:AccessibilityActivate", data);
-                            } else if (granularity > 0) {
-                                boolean extendSelection = arguments.getBoolean(AccessibilityNodeInfo.ACTION_ARGUMENT_EXTEND_SELECTION_BOOLEAN);
-                                data = new GeckoBundle(3);
-                                data.putString("direction", action == AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY ? "Next" : "Previous");
-                                data.putInt("granularity", granularity);
-                                data.putBoolean("select", extendSelection);
-                                mSession.getEventDispatcher().dispatch("GeckoView:AccessibilityByGranularity", data);
-                            }
-                            return true;
-                        case AccessibilityNodeInfo.ACTION_SET_SELECTION:
-                            if (arguments == null) {
-                                return false;
-                            }
-                            int selectionStart = arguments.getInt(AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_START_INT);
-                            int selectionEnd = arguments.getInt(AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_END_INT);
-                            data = new GeckoBundle(2);
-                            data.putInt("start", selectionStart);
-                            data.putInt("end", selectionEnd);
-                            mSession.getEventDispatcher().dispatch("GeckoView:AccessibilitySetSelection", data);
-                            return true;
-                        case AccessibilityNodeInfo.ACTION_CUT:
-                        case AccessibilityNodeInfo.ACTION_COPY:
-                        case AccessibilityNodeInfo.ACTION_PASTE:
-                            data = new GeckoBundle(1);
-                            data.putInt("action", action);
-                            mSession.getEventDispatcher().dispatch("GeckoView:AccessibilityClipboard", data);
-                            return true;
-                        }
-
-                        return mView.performAccessibilityAction(action, arguments);
-                    }
-
-                    private void assertAttachedView(final View view) {
-                        if (view != mView) {
-                            throw new AssertionError("delegate used with wrong view.");
-                        }
-                    }
-                };
-
-                return mAccessibilityNodeProvider;
+                if (hostView != mView) {
+                    return null;
+                }
+                if (mProvider == null) {
+                    mProvider = new NodeProvider();
+                }
+                return mProvider;
             }
-
         });
     }
 
     private static class Settings {
-        private static final Settings INSTANCE = new Settings();
         private static final String FORCE_ACCESSIBILITY_PREF = "accessibility.force_disabled";
 
-        private volatile boolean mEnabled;
-        /* package */ volatile boolean mForceEnabled;
+        private static volatile boolean sEnabled;
+        private static volatile boolean sTouchExplorationEnabled;
+        /* package */ static volatile boolean sForceEnabled;
 
-        public Settings() {
+        static {
             final Context context = GeckoAppShell.getApplicationContext();
             AccessibilityManager accessibilityManager =
                 (AccessibilityManager) context.getSystemService(Context.ACCESSIBILITY_SERVICE);
 
-            mEnabled = accessibilityManager.isEnabled() &&
-                       accessibilityManager.isTouchExplorationEnabled();
-
             accessibilityManager.addAccessibilityStateChangeListener(
             new AccessibilityManager.AccessibilityStateChangeListener() {
                 @Override
                 public void onAccessibilityStateChanged(boolean enabled) {
                     updateAccessibilitySettings();
                 }
             }
             );
@@ -301,43 +341,43 @@ public class SessionAccessibility {
                 }
                 );
             }
 
             PrefsHelper.PrefHandler prefHandler = new PrefsHelper.PrefHandlerBase() {
                 @Override
                 public void prefValue(String pref, int value) {
                     if (pref.equals(FORCE_ACCESSIBILITY_PREF)) {
-                        mForceEnabled = value < 0;
+                        sForceEnabled = value < 0;
                         dispatch();
                     }
                 }
             };
             PrefsHelper.addObserver(new String[]{ FORCE_ACCESSIBILITY_PREF }, prefHandler);
         }
 
-        public static Settings getInstance() {
-            return INSTANCE;
+        public static boolean isEnabled() {
+            return sEnabled || sForceEnabled;
         }
 
-        public static boolean isEnabled() {
-            return INSTANCE.mEnabled || INSTANCE.mForceEnabled;
+        public static boolean isTouchExplorationEnabled() {
+            return sTouchExplorationEnabled || sForceEnabled;
         }
 
-        private void updateAccessibilitySettings() {
+        public static void updateAccessibilitySettings() {
             final AccessibilityManager accessibilityManager = (AccessibilityManager)
                     GeckoAppShell.getApplicationContext().getSystemService(Context.ACCESSIBILITY_SERVICE);
-            mEnabled = accessibilityManager.isEnabled() && accessibilityManager.isTouchExplorationEnabled();
-
+            sEnabled = accessibilityManager.isEnabled();
+            sTouchExplorationEnabled = sEnabled && accessibilityManager.isTouchExplorationEnabled();
             dispatch();
         }
 
-        private void dispatch() {
+        /* package */ static void dispatch() {
             final GeckoBundle ret = new GeckoBundle(1);
-            ret.putBoolean("enabled", mEnabled || mForceEnabled);
+            ret.putBoolean("enabled", isTouchExplorationEnabled());
             // "GeckoView:AccessibilitySettings" is dispatched to the Gecko thread.
             EventDispatcher.getInstance().dispatch("GeckoView:AccessibilitySettings", ret);
             // "GeckoView:AccessibilityEnabled" is dispatched to the UI thread.
             EventDispatcher.getInstance().dispatch("GeckoView:AccessibilityEnabled", ret);
         }
     }
 
     private AccessibilityEvent obtainEvent(final int eventType, final int sourceId) {
@@ -440,17 +480,17 @@ public class SessionAccessibility {
             node.setChecked(message.getBoolean("checked"));
         }
         if (message.containsKey("selected")) {
             node.setSelected(message.getBoolean("selected"));
         }
     }
 
     private void sendAccessibilityEvent(final GeckoBundle message) {
-        if (mView == null || !Settings.isEnabled())
+        if (mView == null || !Settings.isTouchExplorationEnabled())
             return;
 
         final int eventType = message.getInt("eventType", -1);
         if (eventType < 0) {
             Log.e(LOGTAG, "No accessibility event type provided");
             return;
         }
 
@@ -494,17 +534,17 @@ public class SessionAccessibility {
         }
 
         final AccessibilityEvent accessibilityEvent = obtainEvent(eventType, eventSource);
         populateEventFromJSON(accessibilityEvent, message);
         ((ViewParent) mView).requestSendAccessibilityEvent(mView, accessibilityEvent);
     }
 
     public boolean onMotionEvent(final MotionEvent event) {
-        if (!Settings.isEnabled()) {
+        if (!Settings.isTouchExplorationEnabled()) {
             return false;
         }
 
         if (event.getSource() != InputDevice.SOURCE_TOUCHSCREEN) {
             return false;
         }
 
         final int action = event.getActionMasked();
@@ -514,9 +554,253 @@ public class SessionAccessibility {
             return false;
         }
 
         final GeckoBundle data = new GeckoBundle(2);
         data.putDoubleArray("coordinates", new double[] {event.getRawX(), event.getRawY()});
         mSession.getEventDispatcher().dispatch("GeckoView:AccessibilityExploreByTouch", data);
         return true;
     }
+
+    private int getAutoFillRootId(final int id) {
+        int root = View.NO_ID;
+        for (int newId = id; newId != View.NO_ID;) {
+            root = newId;
+            newId = mAutoFillNodes.get(newId).getInt("parent", View.NO_ID);
+        }
+        return root;
+    }
+
+    /* package */ AccessibilityNodeInfo getAutoFillNode(final int id) {
+        if (mView == null || mAutoFillRoots == null) {
+            return null;
+        }
+
+        final GeckoBundle bundle = mAutoFillNodes.get(id);
+        if (bundle == null) {
+            return null;
+        }
+
+        if (DEBUG) {
+            Log.d(LOGTAG, "getAutoFillNode(" + id + ')');
+        }
+
+        final AccessibilityNodeInfo node = AccessibilityNodeInfo.obtain(mView, id);
+        node.setPackageName(GeckoAppShell.getApplicationContext().getPackageName());
+        node.setParent(mView, bundle.getInt("parent", View.NO_ID));
+        node.setEnabled(true);
+
+        if (getAutoFillRootId(mAutoFillFocusedId) == getAutoFillRootId(id)) {
+            // Some auto-fill clients require a dummy rect for the focused View.
+            final Rect rect = new Rect();
+            mSession.getSurfaceBounds(rect);
+            node.setVisibleToUser(!rect.isEmpty());
+            node.setBoundsInParent(rect);
+
+            final int[] offset = new int[2];
+            mView.getLocationOnScreen(offset);
+            rect.offset(offset[0], offset[1]);
+            node.setBoundsInScreen(rect);
+        }
+
+        final GeckoBundle[] children = bundle.getBundleArray("children");
+        if (children != null) {
+            for (final GeckoBundle child : children) {
+                final int childId = child.getInt("id");
+                node.addChild(mView, childId);
+                mAutoFillNodes.append(childId, child);
+            }
+        }
+
+        String tag = bundle.getString("tag", "");
+        final String type = bundle.getString("type", "text");
+        final GeckoBundle attrs = bundle.getBundle("attributes");
+
+        if ("INPUT".equals(tag) && !bundle.getBoolean("editable", false)) {
+            tag = ""; // Don't process non-editable inputs (e.g. type="button").
+        }
+        switch (tag) {
+            case "INPUT":
+            case "TEXTAREA": {
+                final boolean disabled = bundle.getBoolean("disabled");
+                node.setClassName("android.widget.EditText");
+                node.setEnabled(!disabled);
+                node.setFocusable(!disabled);
+                node.setFocused(id == mAutoFillFocusedId);
+
+                if ("password".equals(type)) {
+                    node.setPassword(true);
+                }
+                if (Build.VERSION.SDK_INT >= 18) {
+                    node.setEditable(!disabled);
+                }
+                if (Build.VERSION.SDK_INT >= 19) {
+                    node.setMultiLine("TEXTAREA".equals(tag));
+                }
+                if (Build.VERSION.SDK_INT >= 21) {
+                    try {
+                        node.setMaxTextLength(Integer.parseInt(
+                                String.valueOf(attrs.get("maxlength"))));
+                    } catch (final NumberFormatException ignore) {
+                    }
+                }
+
+                if (!disabled) {
+                    if (Build.VERSION.SDK_INT >= 21) {
+                        node.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_SET_TEXT);
+                    } else {
+                        node.addAction(ACTION_SET_TEXT);
+                    }
+                }
+                break;
+            }
+            default:
+                if (children != null) {
+                    node.setClassName("android.view.ViewGroup");
+                } else {
+                    node.setClassName("android.view.View");
+                }
+                break;
+        }
+
+        if (Build.VERSION.SDK_INT >= 19 && "INPUT".equals(tag)) {
+            switch (type) {
+                case "email":
+                    node.setInputType(InputType.TYPE_CLASS_TEXT |
+                                      InputType.TYPE_TEXT_VARIATION_WEB_EMAIL_ADDRESS);
+                    break;
+                case "number":
+                    node.setInputType(InputType.TYPE_CLASS_NUMBER);
+                    break;
+                case "password":
+                    node.setInputType(InputType.TYPE_CLASS_TEXT |
+                                      InputType.TYPE_TEXT_VARIATION_WEB_PASSWORD);
+                    break;
+                case "tel":
+                    node.setInputType(InputType.TYPE_CLASS_PHONE);
+                    break;
+                case "text":
+                    node.setInputType(InputType.TYPE_CLASS_TEXT |
+                                      InputType.TYPE_TEXT_VARIATION_WEB_EDIT_TEXT);
+                    break;
+                case "url":
+                    node.setInputType(InputType.TYPE_CLASS_TEXT |
+                                      InputType.TYPE_TEXT_VARIATION_URI);
+                    break;
+            }
+        }
+        return node;
+    }
+
+    /* package */ boolean performAutoFill(final int id, final String value) {
+        if (mAutoFillRoots == null) {
+            return false;
+        }
+
+        int rootId = id;
+        for (int currentId = id; currentId != View.NO_ID;) {
+            final GeckoBundle bundle = mAutoFillNodes.get(currentId);
+            if (bundle == null) {
+                return false;
+            }
+            rootId = currentId;
+            currentId = bundle.getInt("parent", View.NO_ID);
+        }
+
+        if (DEBUG) {
+            Log.d(LOGTAG, "performAutoFill(" + id + ", " + value + ')');
+        }
+
+        final EventCallback callback = mAutoFillRoots.get(rootId);
+        if (callback == null) {
+            return false;
+        }
+
+        final GeckoBundle response = new GeckoBundle(1);
+        response.putString(String.valueOf(id), value);
+        callback.sendSuccess(response);
+        return true;
+    }
+
+    private void fireWindowChangedEvent(final int id) {
+        if (mView instanceof ViewParent) {
+            final AccessibilityEvent event = obtainEvent(
+                    AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED, id);
+            if (Build.VERSION.SDK_INT >= 19) {
+                event.setContentChangeTypes(AccessibilityEvent.CONTENT_CHANGE_TYPE_SUBTREE);
+            }
+            ((ViewParent) mView).requestSendAccessibilityEvent(mView, event);
+        }
+    }
+
+    /* package */ void addAutoFill(final GeckoBundle message, final EventCallback callback) {
+        if (!Settings.isEnabled()) {
+            return;
+        }
+
+        if (mAutoFillRoots == null) {
+            mAutoFillRoots = new SparseArray<>();
+            mAutoFillNodes = new SparseArray<>();
+        }
+
+        final int id = message.getInt("id");
+        if (DEBUG) {
+            Log.d(LOGTAG, "addAutoFill(" + id + ')');
+        }
+
+        mAutoFillRoots.append(id, callback);
+        mAutoFillNodes.append(id, message);
+        fireWindowChangedEvent(id);
+    }
+
+    /* package */ void clearAutoFill() {
+        if (mAutoFillRoots != null) {
+            if (DEBUG) {
+                Log.d(LOGTAG, "clearAutoFill()");
+            }
+            mAutoFillRoots = null;
+            mAutoFillNodes = null;
+            mAutoFillFocusedId = View.NO_ID;
+            fireWindowChangedEvent(View.NO_ID);
+        }
+    }
+
+    /* package */ void onAutoFillFocus(final GeckoBundle message) {
+        if (!Settings.isEnabled() || !(mView instanceof ViewParent) || mAutoFillNodes == null) {
+            return;
+        }
+
+        final int id;
+        if (message != null) {
+            id = message.getInt("id");
+            mAutoFillNodes.put(id, message);
+        } else {
+            id = View.NO_ID;
+        }
+
+        if (DEBUG) {
+            Log.d(LOGTAG, "onAutoFillFocus(" + id + ')');
+        }
+        if (mAutoFillFocusedId == id) {
+            return;
+        }
+        mAutoFillFocusedId = id;
+
+        // We already send "TYPE_VIEW_FOCUSED" in touch exploration mode,
+        // so in that case don't send it here.
+        if (!Settings.isTouchExplorationEnabled()) {
+            AccessibilityEvent event = obtainEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED, id);
+            ((ViewParent) mView).requestSendAccessibilityEvent(mView, event);
+        }
+    }
+
+    /* package */ void onWindowFocus() {
+        // Auto-fill clients expect a state change event on focus.
+        if (Settings.isEnabled() && mView instanceof ViewParent) {
+            if (DEBUG) {
+                Log.d(LOGTAG, "onWindowFocus()");
+            }
+            final AccessibilityEvent event = obtainEvent(
+                    AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED, View.NO_ID);
+            ((ViewParent) mView).requestSendAccessibilityEvent(mView, event);
+        }
+    }
 }
--- a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/SessionTextInput.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/SessionTextInput.java
@@ -6,50 +6,62 @@
 package org.mozilla.geckoview;
 
 import org.mozilla.gecko.InputMethods;
 import org.mozilla.gecko.annotation.WrapForJNI;
 import org.mozilla.gecko.GeckoEditableChild;
 import org.mozilla.gecko.IGeckoEditableParent;
 import org.mozilla.gecko.NativeQueue;
 import org.mozilla.gecko.util.ActivityUtils;
+import org.mozilla.gecko.util.BundleEventListener;
+import org.mozilla.gecko.util.EventCallback;
+import org.mozilla.gecko.util.GeckoBundle;
 import org.mozilla.gecko.util.ThreadUtils;
 
 import android.annotation.TargetApi;
 import android.app.Activity;
 import android.content.Context;
+import android.graphics.Rect;
 import android.graphics.RectF;
+import android.os.Build;
 import android.os.Handler;
 import android.support.annotation.NonNull;
 import android.support.annotation.Nullable;
 import android.text.Editable;
+import android.text.InputType;
 import android.util.Log;
+import android.util.SparseArray;
 import android.view.KeyEvent;
 import android.view.View;
+import android.view.ViewStructure;
+import android.view.autofill.AutofillManager;
 import android.view.inputmethod.CursorAnchorInfo;
 import android.view.inputmethod.EditorInfo;
 import android.view.inputmethod.ExtractedText;
 import android.view.inputmethod.ExtractedTextRequest;
 import android.view.inputmethod.InputConnection;
 import android.view.inputmethod.InputMethodManager;
 
+import java.util.Locale;
+
 /**
  * {@code SessionTextInput} handles text input for {@code GeckoSession} through key events or input
  * methods. It is typically used to implement certain methods in {@link android.view.View}
  * such as {@link android.view.View#onCreateInputConnection}, by forwarding such calls to
  * corresponding methods in {@code SessionTextInput}.
  * <p>
  * For full functionality, {@code SessionTextInput} requires a {@link android.view.View} to be set
  * first through {@link #setView}. When a {@link android.view.View} is not set or set to null,
  * {@code SessionTextInput} will operate in a reduced functionality mode. See {@link
  * #onCreateInputConnection} and methods in {@link GeckoSession.TextInputDelegate} for changes in
  * behavior in this viewless mode.
  */
 public final class SessionTextInput {
     /* package */ static final String LOGTAG = "GeckoSessionTextInput";
+    private static final boolean DEBUG = false;
 
     // Interface to access GeckoInputConnection from SessionTextInput.
     /* package */ interface InputConnectionClient {
         View getView();
         Handler getHandler(Handler defHandler);
         InputConnection onCreateInputConnection(EditorInfo attrs);
     }
 
@@ -213,32 +225,93 @@ public final class SessionTextInput {
                                            @NonNull final CursorAnchorInfo info) {
             ThreadUtils.assertOnUiThread();
             final View view = session.getTextInput().getView();
             final InputMethodManager imm = getInputMethodManager(view);
             if (imm != null) {
                 imm.updateCursorAnchorInfo(view, info);
             }
         }
+
+        @Override
+        public void notifyAutoFill(@NonNull final GeckoSession session,
+                                   @AutoFillNotification final int notification,
+                                   final int virtualId) {
+            ThreadUtils.assertOnUiThread();
+            final View view = session.getTextInput().getView();
+            if (Build.VERSION.SDK_INT < 26 || view == null) {
+                return;
+            }
+
+            final AutofillManager manager =
+                    view.getContext().getSystemService(AutofillManager.class);
+            if (manager == null) {
+                return;
+            }
+
+            switch (notification) {
+                case AUTO_FILL_NOTIFY_STARTED:
+                    // This line seems necessary for auto-fill to work on the initial page.
+                    manager.cancel();
+                    break;
+                case AUTO_FILL_NOTIFY_COMMITTED:
+                    manager.commit();
+                    break;
+                case AUTO_FILL_NOTIFY_CANCELED:
+                    manager.cancel();
+                    break;
+                case AUTO_FILL_NOTIFY_VIEW_ENTERED:
+                    // Use a dummy rect for the View.
+                    manager.notifyViewEntered(view, virtualId, getDummyAutoFillRect(
+                            session, /* screen */ true, view));
+                    break;
+                case AUTO_FILL_NOTIFY_VIEW_EXITED:
+                    manager.notifyViewExited(view, virtualId);
+                    break;
+            }
+        }
     }
 
     private final GeckoSession mSession;
     private final NativeQueue mQueue;
     private final GeckoEditable mEditable;
     private final GeckoEditableChild mEditableChild;
     private InputConnectionClient mInputConnection;
     private GeckoSession.TextInputDelegate mDelegate;
+    // Auto-fill nodes.
+    private SparseArray<GeckoBundle> mAutoFillNodes;
+    private SparseArray<EventCallback> mAutoFillRoots;
+    private int mAutoFillFocusedId = View.NO_ID;
 
     /* package */ SessionTextInput(final @NonNull GeckoSession session,
                                    final @NonNull NativeQueue queue) {
         mSession = session;
         mQueue = queue;
         mEditable = new GeckoEditable(session);
         mEditableChild = new GeckoEditableChild(mEditable);
         mEditable.setDefaultEditableChild(mEditableChild);
+
+        session.getEventDispatcher().registerUiThreadListener(
+                new BundleEventListener() {
+                    @Override
+                    public void handleMessage(final String event, final GeckoBundle message,
+                                              final EventCallback callback) {
+                        if ("GeckoView:AddAutoFill".equals(event)) {
+                            addAutoFill(message, callback);
+                        } else if ("GeckoView:ClearAutoFill".equals(event)) {
+                            clearAutoFill();
+                        } else if ("GeckoView:OnAutoFillFocus".equals(event)) {
+                            onAutoFillFocus(message);
+                        }
+                    }
+                },
+                "GeckoView:AddAutoFill",
+                "GeckoView:ClearAutoFill",
+                "GeckoView:OnAutoFillFocus",
+                null);
     }
 
     /* package */ void onWindowChanged(final GeckoSession.Window window) {
         if (mQueue.isReady()) {
             window.attachEditable(mEditable, mEditableChild);
         } else {
             mQueue.queueUntilReady(window, "attachEditable",
                                    IGeckoEditableParent.class, mEditable,
@@ -402,9 +475,302 @@ public final class SessionTextInput {
      */
     public GeckoSession.TextInputDelegate getDelegate() {
         ThreadUtils.assertOnUiThread();
         if (mDelegate == null) {
             mDelegate = DefaultDelegate.INSTANCE;
         }
         return mDelegate;
     }
+
+    /**
+     * Fill the specified {@link ViewStructure} with auto-fill fields from the current page.
+     *
+     * @param structure Structure to be filled.
+     * @param flags Zero or a combination of {@link View#AUTOFILL_FLAG_INCLUDE_NOT_IMPORTANT_VIEWS
+     *              AUTOFILL_FLAG_*} constants.
+     */
+    @TargetApi(23)
+    public void onProvideAutofillVirtualStructure(@NonNull final ViewStructure structure,
+                                                  final int flags) {
+        final View view = getView();
+        if (view != null) {
+            structure.setClassName(view.getClass().getName());
+        }
+        structure.setEnabled(true);
+        structure.setVisibility(View.VISIBLE);
+
+        final Rect rect = getDummyAutoFillRect(mSession, /* screen */ false,
+                                               /* view */ null);
+        structure.setDimens(rect.left, rect.top, 0, 0, rect.width(), rect.height());
+
+        if (mAutoFillRoots == null) {
+            structure.setChildCount(0);
+            return;
+        }
+
+        final int size = mAutoFillRoots.size();
+        structure.setChildCount(size);
+
+        int focusedRoot = View.NO_ID;
+        for (int newId = mAutoFillFocusedId; newId != View.NO_ID;) {
+            focusedRoot = newId;
+            newId = mAutoFillNodes.get(newId).getInt("parent", View.NO_ID);
+        }
+
+        for (int i = 0; i < size; i++) {
+            final int id = mAutoFillRoots.keyAt(i);
+            final GeckoBundle root = mAutoFillNodes.get(id);
+            fillAutoFillStructure(view, id, root, structure.newChild(i),
+                                  (focusedRoot == id) ? rect : null);
+        }
+    }
+
+    /**
+     * Perform auto-fill using the specified values.
+     *
+     * @param values Map of auto-fill IDs to values.
+     */
+    public void autofill(final SparseArray<CharSequence> values) {
+        if (mAutoFillRoots == null) {
+            return;
+        }
+
+        GeckoBundle response = null;
+        EventCallback callback = null;
+
+        for (int i = 0; i < values.size(); i++) {
+            final int id = values.keyAt(i);
+            final CharSequence value = values.valueAt(i);
+
+            if (DEBUG) {
+                Log.d(LOGTAG,
+                      "performAutoFill(" + id + ", " + values + ')');
+            }
+            int rootId = id;
+            for (int currentId = id; currentId != View.NO_ID; ) {
+                final GeckoBundle bundle = mAutoFillNodes.get(currentId);
+                if (bundle == null) {
+                    return;
+                }
+                rootId = currentId;
+                currentId = bundle.getInt("parent", View.NO_ID);
+            }
+
+            final EventCallback newCallback = mAutoFillRoots.get(rootId);
+            if (callback == null || newCallback != callback) {
+                if (callback != null) {
+                    callback.sendSuccess(response);
+                }
+                response = new GeckoBundle(values.size() - i);
+                callback = newCallback;
+            }
+            response.putString(String.valueOf(id), String.valueOf(value));
+        }
+
+        if (callback != null) {
+            callback.sendSuccess(response);
+        }
+    }
+
+    @TargetApi(23)
+    private void fillAutoFillStructure(@Nullable final View view, final int id,
+                                       @NonNull final GeckoBundle bundle,
+                                       @NonNull final ViewStructure structure,
+                                       @Nullable final Rect rect) {
+        if (mAutoFillRoots == null) {
+            return;
+        }
+
+        if (DEBUG) {
+            Log.d(LOGTAG, "fillAutoFillStructure(" + id + ')');
+        }
+
+        if (Build.VERSION.SDK_INT >= 26) {
+            if (view != null) {
+                structure.setAutofillId(view.getAutofillId(), id);
+            }
+            structure.setWebDomain(bundle.getString("origin"));
+        }
+        structure.setId(id, null, null, null);
+
+        if (rect != null) {
+            structure.setDimens(0, 0, 0, 0, rect.width(), rect.height());
+        }
+
+        final GeckoBundle[] children = bundle.getBundleArray("children");
+        if (children != null) {
+            structure.setChildCount(children.length);
+            for (int i = 0; i < children.length; i++) {
+                final GeckoBundle childBundle = children[i];
+                final int childId = childBundle.getInt("id");
+                final ViewStructure childStructure = structure.newChild(i);
+                fillAutoFillStructure(view, childId, childBundle, childStructure, rect);
+                mAutoFillNodes.append(childId, childBundle);
+            }
+        }
+
+        String tag = bundle.getString("tag", "");
+        final String type = bundle.getString("type", "text");
+
+        if (Build.VERSION.SDK_INT >= 26) {
+            final GeckoBundle attrs = bundle.getBundle("attributes");
+            final ViewStructure.HtmlInfo.Builder builder =
+                    structure.newHtmlInfoBuilder(tag.toLowerCase(Locale.US));
+            for (final String key : attrs.keys()) {
+                builder.addAttribute(key, String.valueOf(attrs.get(key)));
+            }
+            structure.setHtmlInfo(builder.build());
+        }
+
+        if ("INPUT".equals(tag) && !bundle.getBoolean("editable", false)) {
+            tag = ""; // Don't process non-editable inputs (e.g. type="button").
+        }
+        switch (tag) {
+            case "INPUT":
+            case "TEXTAREA": {
+                final boolean disabled = bundle.getBoolean("disabled");
+                structure.setClassName("android.widget.EditText");
+                structure.setEnabled(!disabled);
+                structure.setFocusable(!disabled);
+                structure.setFocused(id == mAutoFillFocusedId);
+                structure.setVisibility(View.VISIBLE);
+
+                if (Build.VERSION.SDK_INT >= 26) {
+                    structure.setAutofillType(View.AUTOFILL_TYPE_TEXT);
+                }
+                break;
+            }
+            default:
+                if (children != null) {
+                    structure.setClassName("android.view.ViewGroup");
+                } else {
+                    structure.setClassName("android.view.View");
+                }
+                break;
+        }
+
+        if (Build.VERSION.SDK_INT >= 26 && "INPUT".equals(tag)) {
+            switch (type) {
+                case "email":
+                    structure.setAutofillHints(new String[] { View.AUTOFILL_HINT_EMAIL_ADDRESS });
+                    structure.setInputType(InputType.TYPE_CLASS_TEXT |
+                                              InputType.TYPE_TEXT_VARIATION_WEB_EMAIL_ADDRESS);
+                    break;
+                case "number":
+                    structure.setInputType(InputType.TYPE_CLASS_NUMBER);
+                    break;
+                case "password":
+                    structure.setAutofillHints(new String[] { View.AUTOFILL_HINT_PASSWORD });
+                    structure.setInputType(InputType.TYPE_CLASS_TEXT |
+                                           InputType.TYPE_TEXT_VARIATION_WEB_PASSWORD);
+                    break;
+                case "tel":
+                    structure.setAutofillHints(new String[] { View.AUTOFILL_HINT_PHONE });
+                    structure.setInputType(InputType.TYPE_CLASS_PHONE);
+                    break;
+                case "text":
+                    structure.setInputType(InputType.TYPE_CLASS_TEXT |
+                                           InputType.TYPE_TEXT_VARIATION_WEB_EDIT_TEXT);
+                    break;
+                case "url":
+                    structure.setInputType(InputType.TYPE_CLASS_TEXT |
+                                           InputType.TYPE_TEXT_VARIATION_URI);
+                    break;
+            }
+        }
+    }
+
+    /* package */ void addAutoFill(@NonNull final GeckoBundle message,
+                                   @NonNull final EventCallback callback) {
+        if (Build.VERSION.SDK_INT < 23) {
+            return;
+        }
+
+        final boolean initializing;
+        if (mAutoFillRoots == null) {
+            mAutoFillRoots = new SparseArray<>();
+            mAutoFillNodes = new SparseArray<>();
+            initializing = true;
+        } else {
+            initializing = false;
+        }
+
+        final int id = message.getInt("id");
+        if (DEBUG) {
+            Log.d(LOGTAG, "addAutoFill(" + id + ')');
+        }
+
+        mAutoFillRoots.append(id, callback);
+        mAutoFillNodes.append(id, message);
+
+        if (initializing) {
+            getDelegate().notifyAutoFill(
+                    mSession, GeckoSession.TextInputDelegate.AUTO_FILL_NOTIFY_STARTED, id);
+        } else {
+            getDelegate().notifyAutoFill(
+                    mSession, GeckoSession.TextInputDelegate.AUTO_FILL_NOTIFY_VIEW_ADDED, id);
+        }
+    }
+
+    /* package */ void clearAutoFill() {
+        if (mAutoFillRoots == null) {
+            return;
+        }
+        if (DEBUG) {
+            Log.d(LOGTAG, "clearAutoFill()");
+        }
+        mAutoFillRoots = null;
+        mAutoFillNodes = null;
+        mAutoFillFocusedId = View.NO_ID;
+
+        getDelegate().notifyAutoFill(
+                mSession, GeckoSession.TextInputDelegate.AUTO_FILL_NOTIFY_CANCELED, View.NO_ID);
+    }
+
+    /* package */ void onAutoFillFocus(@Nullable final GeckoBundle message) {
+        if (mAutoFillRoots == null) {
+            return;
+        }
+
+        final int id;
+        if (message != null) {
+            id = message.getInt("id");
+            mAutoFillNodes.put(id, message);
+        } else {
+            id = View.NO_ID;
+        }
+
+        if (DEBUG) {
+            Log.d(LOGTAG, "onAutoFillFocus(" + id + ')');
+        }
+        if (mAutoFillFocusedId == id) {
+            return;
+        }
+        if (mAutoFillFocusedId != View.NO_ID) {
+            getDelegate().notifyAutoFill(
+                    mSession, GeckoSession.TextInputDelegate.AUTO_FILL_NOTIFY_VIEW_EXITED,
+                    mAutoFillFocusedId);
+        }
+
+        mAutoFillFocusedId = id;
+        if (id != View.NO_ID) {
+            getDelegate().notifyAutoFill(
+                    mSession, GeckoSession.TextInputDelegate.AUTO_FILL_NOTIFY_VIEW_ENTERED, id);
+        }
+    }
+
+    /* package */ static Rect getDummyAutoFillRect(@NonNull final GeckoSession session,
+                                                   final boolean screen,
+                                                   @Nullable final View view) {
+        final Rect rect = new Rect();
+        session.getSurfaceBounds(rect);
+        if (screen) {
+            if (view == null) {
+                throw new IllegalArgumentException();
+            }
+            final int[] offset = new int[2];
+            view.getLocationOnScreen(offset);
+            rect.offset(offset[0], offset[1]);
+        }
+        return rect;
+    }
 }
--- a/mobile/android/geckoview_example/src/main/java/org/mozilla/geckoview_example/GeckoViewActivity.java
+++ b/mobile/android/geckoview_example/src/main/java/org/mozilla/geckoview_example/GeckoViewActivity.java
@@ -189,28 +189,16 @@ public class GeckoViewActivity extends A
     }
 
     private void updateTrackingProtection(GeckoSession session) {
         session.getSettings().setBoolean(
             GeckoSessionSettings.USE_TRACKING_PROTECTION, mUseTrackingProtection);
     }
 
     @Override
-    protected void onPause() {
-        mGeckoSession.setActive(false);
-        super.onPause();
-    }
-
-    @Override
-    protected void onResume() {
-        mGeckoSession.setActive(true);
-        super.onResume();
-    }
-
-    @Override
     public void onBackPressed() {
         if (mFullScreen) {
             mGeckoSession.exitFullScreen();
             return;
         }
 
         if (mCanGoBack && mGeckoSession != null) {
             mGeckoSession.goBack();
--- a/mobile/android/modules/geckoview/GeckoViewContent.jsm
+++ b/mobile/android/modules/geckoview/GeckoViewContent.jsm
@@ -18,16 +18,17 @@ class GeckoViewContent extends GeckoView
     this.registerListener([
         "GeckoViewContent:ExitFullScreen",
         "GeckoView:ClearMatches",
         "GeckoView:DisplayMatches",
         "GeckoView:FindInPage",
         "GeckoView:RestoreState",
         "GeckoView:SaveState",
         "GeckoView:SetActive",
+        "GeckoView:SetFocused",
         "GeckoView:ZoomToInput",
     ]);
 
     this.messageManager.addMessageListener("GeckoView:SaveStateFinish", this);
   }
 
   onEnable() {
     this.window.addEventListener("MozDOMFullscreen:Entered", this,
@@ -73,22 +74,27 @@ class GeckoViewContent extends GeckoView
         this._findInPage(aData, aCallback);
         break;
       }
       case "GeckoView:ZoomToInput":
         this.messageManager.sendAsyncMessage(aEvent);
         break;
       case "GeckoView:SetActive":
         if (aData.active) {
-          this.browser.setAttribute("primary", "true");
-          this.browser.focus();
           this.browser.docShellIsActive = true;
         } else {
+          this.browser.docShellIsActive = false;
+        }
+        break;
+      case "GeckoView:SetFocused":
+        if (aData.focused) {
+          this.browser.focus();
+          this.browser.setAttribute("primary", "true");
+        } else {
           this.browser.removeAttribute("primary");
-          this.browser.docShellIsActive = false;
           this.browser.blur();
         }
         break;
       case "GeckoView:SaveState":
         if (!this._saveStateCallbacks) {
           this._saveStateCallbacks = new Map();
           this._saveStateNextId = 0;
         }
--- a/netwerk/base/nsIPermissionManager.idl
+++ b/netwerk/base/nsIPermissionManager.idl
@@ -171,16 +171,21 @@ interface nsIPermissionManager : nsISupp
   void removeAll();
 
   /**
    * Clear all permission information added since the specified time.
    */
   void removeAllSince(in int64_t since);
 
   /**
+   * Clear all permissions of the passed type.
+   */
+  void removeByType(in string type);
+
+  /**
    * Test whether a website has permission to perform the given action.
    * This function will perform a pref lookup to permissions.default.<type>
    * if the specific permission type is part of the whitelist for that functionality.
    * @param uri     the uri to be tested
    * @param type    a case-sensitive ASCII string, identifying the consumer
    * @param return  see add(), param permission. returns UNKNOWN_ACTION when
    *                there is no stored permission for this uri and / or type.
    */
--- a/old-configure.in
+++ b/old-configure.in
@@ -859,16 +859,20 @@ case "$target" in
             # Silence problematic clang warnings
             CXXFLAGS="$CXXFLAGS -Wno-incompatible-ms-struct"
         fi
     else
         TARGET_COMPILER_ABI=msvc
         if test "$AS_BIN"; then
             AS="$(basename "$AS_BIN")"
         fi
+        # armasm64 doesn't understand -c.
+        if test "$CPU_ARCH" = "aarch64"; then
+            AS_DASH_C_FLAG=
+        fi
         case "$LINKER" in
         *lld*)
             AR='llvm-lib'
             AR_FLAGS='-llvmlibthin -out:$@'
             ;;
         *)
             AR='lib'
             AR_FLAGS='-NOLOGO -OUT:$@'
--- a/python/mozbuild/mozbuild/frontend/context.py
+++ b/python/mozbuild/mozbuild/frontend/context.py
@@ -368,16 +368,20 @@ class AsmFlags(BaseCompileFlags):
         if (self._context.config.substs.get('MOZ_DEBUG') or
             self._context.config.substs.get('MOZ_DEBUG_SYMBOLS')):
             if self._context.get('USE_YASM'):
                 if (self._context.config.substs.get('OS_ARCH') == 'WINNT' and
                     not self._context.config.substs.get('GNU_CC')):
                     debug_flags += ['-g', 'cv8']
                 elif self._context.config.substs.get('OS_ARCH') != 'Darwin':
                     debug_flags += ['-g', 'dwarf2']
+            elif (self._context.config.substs.get('OS_ARCH') == 'WINNT' and
+                  self._context.config.substs.get('CPU_ARCH') == 'aarch64'):
+                # armasm64 accepts a paucity of options compared to ml/ml64.
+                pass
             else:
                 debug_flags += self._context.config.substs.get('MOZ_DEBUG_FLAGS', '').split()
         return debug_flags
 
 
 class LinkFlags(BaseCompileFlags):
     def __init__(self, context):
         self._context = context
@@ -402,29 +406,16 @@ class LinkFlags(BaseCompileFlags):
             flags += self._context.config.substs.get('MOZ_DEBUG_LDFLAGS', [])
 
         # TODO: This is pretty convoluted, and isn't really a per-context thing,
         # configure would be a better place to aggregate these.
         if all([self._context.config.substs.get('OS_ARCH') == 'WINNT',
                 not self._context.config.substs.get('GNU_CC'),
                 not self._context.config.substs.get('MOZ_DEBUG')]):
 
-            # MOZ_DEBUG_SYMBOLS generates debug symbols in separate PDB files.
-            # Used for generating an optimized build with debugging symbols.
-            # Used in the Windows nightlies to generate symbols for crash reporting.
-            if self._context.config.substs.get('MOZ_DEBUG_SYMBOLS'):
-                flags.append('-DEBUG')
-
-
-            if self._context.config.substs.get('MOZ_DMD'):
-                # On Windows Opt DMD builds we actually override everything
-                # from OS_LDFLAGS. Bug 1413728 is on file to figure out whether
-                # this is necessary.
-                flags = ['-DEBUG']
-
             if self._context.config.substs.get('MOZ_OPTIMIZE'):
                 flags.append('-OPT:REF,ICF')
 
         return flags
 
 
 class CompileFlags(BaseCompileFlags):
     def __init__(self, context):
--- a/python/mozbuild/mozbuild/mach_commands.py
+++ b/python/mozbuild/mozbuild/mach_commands.py
@@ -900,16 +900,20 @@ class RunProgram(MachCommandBase):
                 args.extend(params)
 
             if not remote:
                 args.append('-no-remote')
 
             if not background and sys.platform == 'darwin':
                 args.append('-foreground')
 
+            if sys.platform.startswith('win') and \
+               'MOZ_LAUNCHER_PROCESS' in self.defines:
+                args.append('-wait-for-browser')
+
             no_profile_option_given = \
                 all(p not in params for p in ['-profile', '--profile', '-P'])
             if no_profile_option_given and not noprofile:
                 prefs = {
                    'browser.shell.checkDefaultBrowser': False,
                    'general.warnOnAboutConfig': False,
                 }
                 prefs.update(self._mach_context.settings.runprefs)
--- a/python/mozbuild/mozbuild/test/configure/test_toolchain_configure.py
+++ b/python/mozbuild/mozbuild/test/configure/test_toolchain_configure.py
@@ -962,17 +962,22 @@ class WindowsToolchainTest(BaseToolchain
     GXX_5_RESULT = LinuxToolchainTest.GXX_5_RESULT
     GCC_6_RESULT = LinuxToolchainTest.GCC_6_RESULT
     GXX_6_RESULT = LinuxToolchainTest.GXX_6_RESULT
     DEFAULT_GCC_RESULT = LinuxToolchainTest.DEFAULT_GCC_RESULT
     DEFAULT_GXX_RESULT = LinuxToolchainTest.DEFAULT_GXX_RESULT
 
     # VS2017u6 or greater is required.
     def test_msvc(self):
-        self.do_toolchain_test(self.PATHS, {
+        # We'll pick msvc if clang-cl can't be found.
+        paths = {
+            k: v for k, v in self.PATHS.iteritems()
+            if os.path.basename(k) != 'clang-cl'
+        }
+        self.do_toolchain_test(paths, {
             'c_compiler': self.VS_2017u6_RESULT,
             'cxx_compiler': self.VSXX_2017u6_RESULT,
         })
 
     def test_unsupported_msvc(self):
         self.do_toolchain_test(self.PATHS, {
             'c_compiler': self.VS_2017u4_RESULT,
         }, environ={
@@ -1011,22 +1016,17 @@ class WindowsToolchainTest(BaseToolchain
 
         self.do_toolchain_test(self.PATHS, {
             'c_compiler': self.VS_2013u2_RESULT,
         }, environ={
             'CC': '/opt/VS_2013u2/bin/cl',
         })
 
     def test_clang_cl(self):
-        # We'll pick clang-cl if msvc can't be found.
-        paths = {
-            k: v for k, v in self.PATHS.iteritems()
-            if os.path.basename(k) != 'cl'
-        }
-        self.do_toolchain_test(paths, {
+        self.do_toolchain_test(self.PATHS, {
             'c_compiler': self.CLANG_CL_3_9_RESULT,
             'cxx_compiler': self.CLANGXX_CL_3_9_RESULT,
         })
 
     def test_gcc(self):
         # We'll pick GCC if msvc and clang-cl can't be found.
         paths = {
             k: v for k, v in self.PATHS.iteritems()
--- a/python/mozbuild/mozbuild/test/frontend/test_emitter.py
+++ b/python/mozbuild/mozbuild/test/frontend/test_emitter.py
@@ -262,34 +262,35 @@ class TestEmitterBasic(unittest.TestCase
         self.assertEqual(ldflags.flags['OS'],
                          reader.config.substs['MOZ_DEBUG_LDFLAGS'])
 
     def test_windows_opt_link_flags(self):
         reader = self.reader('link-flags', extra_substs={
             'OS_ARCH': 'WINNT',
             'GNU_CC': '',
             'MOZ_OPTIMIZE': '1',
+            'MOZ_DEBUG_LDFLAGS': ['-DEBUG'],
             'MOZ_DEBUG_SYMBOLS': '1',
             'MOZ_OPTIMIZE_FLAGS': [],
             'MOZ_OPTIMIZE_LDFLAGS': [],
         })
         sources, ldflags, lib, compile_flags = self.read_topsrcdir(reader)
         self.assertIsInstance(ldflags, ComputedFlags)
         self.assertIn('-DEBUG', ldflags.flags['OS'])
         self.assertIn('-OPT:REF,ICF', ldflags.flags['OS'])
 
     def test_windows_dmd_link_flags(self):
         reader = self.reader('link-flags', extra_substs={
             'OS_ARCH': 'WINNT',
             'GNU_CC': '',
             'MOZ_DMD': '1',
+            'MOZ_DEBUG_LDFLAGS': ['-DEBUG'],
             'MOZ_DEBUG_SYMBOLS': '1',
             'MOZ_OPTIMIZE': '1',
             'MOZ_OPTIMIZE_FLAGS': [],
-            'OS_LDFLAGS': ['-Wl,-U_foo'],
         })
         sources, ldflags, lib, compile_flags = self.read_topsrcdir(reader)
         self.assertIsInstance(ldflags, ComputedFlags)
         self.assertEqual(ldflags.flags['OS'],
                          ['-DEBUG', '-OPT:REF,ICF'])
 
     def test_host_compile_flags(self):
         reader = self.reader('host-compile-flags', extra_substs={
--- a/testing/firefox-ui/tests/functional/security/test_ssl_disabled_error_page.py
+++ b/testing/firefox-ui/tests/functional/security/test_ssl_disabled_error_page.py
@@ -46,15 +46,21 @@ class TestSSLDisabledErrorPage(Puppeteer
             self.assertEquals(title.get_property('textContent'), nss_failure2title)
 
             # Verify the error message is correct
             short_description = self.marionette.find_element(By.ID, 'errorShortDescText')
             self.assertIn('SSL_ERROR_UNSUPPORTED_VERSION',
                           short_description.get_property('textContent'))
 
             # Verify that the "Restore" button appears and works
-            reset_button = self.marionette.find_element(By.ID, 'prefResetButton')
-            reset_button.click()
+            restore_button = self.marionette.find_element(By.ID, 'prefResetButton')
+            restore_button.click()
+            Wait(self.marionette).until(
+                expected.element_not_present(By.ID, "prefResetButton"),
+                message="Click on the restore button didn't trigger a page load"
+            )
 
             # With the preferences reset, the page has to load correctly
             el = Wait(self.marionette, timeout=self.marionette.timeout.page_load).until(
-                expected.element_present(By.TAG_NAME, 'h1'))
+                expected.element_present(By.TAG_NAME, 'h1'),
+                message="Expected target page has not been loaded"
+            )
             self.assertIn('tls-v1-0', el.get_property('innerText'))
--- a/testing/web-platform/meta/MANIFEST.json
+++ b/testing/web-platform/meta/MANIFEST.json
@@ -422104,16 +422104,22 @@
     ]
    ],
    "webdriver/tests/accept_alert/accept.py": [
     [
      "/webdriver/tests/accept_alert/accept.py",
      {}
     ]
    ],
+   "webdriver/tests/actions/bounds.py": [
+    [
+     "/webdriver/tests/actions/bounds.py",
+     {}
+    ]
+   ],
    "webdriver/tests/actions/control_click.py": [
     [
      "/webdriver/tests/actions/control_click.py",
      {}
     ]
    ],
    "webdriver/tests/actions/key.py": [
     [
@@ -554864,17 +554870,17 @@
    "85dd7324815b8f8ef1a1d0496224c1a0661db9d8",
    "support"
   ],
   "css/css-transitions/support/generalParallelTest.js": [
    "f6e14128fc07f08984ac705b2f6ec03f02bc2215",
    "support"
   ],
   "css/css-transitions/support/helper.js": [
-   "eb5959ce5e1a13bf51b4228f36fe851618a12697",
+   "aa3efe8012268115734bd91bff8f82ceb8d26eea",
    "support"
   ],
   "css/css-transitions/support/import-green.css": [
    "537104e663364492c6ef388e4afce190e9c5bc58",
    "support"
   ],
   "css/css-transitions/support/import-red.css": [
    "9945ef47114c2841a746c99a2fb1e93e050aac8b",
@@ -642620,17 +642626,17 @@
    "c473961cb64f424e3402b265746a612b09cb7dfe",
    "support"
   ],
   "tools/webdriver/webdriver/__init__.py": [
    "b29b95e376344271b5b942d548d9e6d55ea275db",
    "support"
   ],
   "tools/webdriver/webdriver/client.py": [
-   "a8c9a061ccf50b1b870283871e2e4ea55bc4e8cc",
+   "34d1a90dde2d91e70a6c1b1bb1db6b2e07127d70",
    "support"
   ],
   "tools/webdriver/webdriver/error.py": [
    "b2337ff3b38f57828c72d76e49ef8893d30b578c",
    "support"
   ],
   "tools/webdriver/webdriver/protocol.py": [
    "18a3d52c8a3b537ba75d487fd7f6c5cde206ec3e",
@@ -647443,16 +647449,20 @@
   "webdriver/tests/accept_alert/accept.py": [
    "a111f103bf142b4041f2a8d1c6017de79b54560e",
    "wdspec"
   ],
   "webdriver/tests/actions/__init__.py": [
    "e69de29bb2d1d6434b8b29ae775ad8c2e48c5391",
    "support"
   ],
+  "webdriver/tests/actions/bounds.py": [
+   "e218c851e75220cea0c296461dad1d8427619afb",
+   "wdspec"
+  ],
   "webdriver/tests/actions/conftest.py": [
    "47aad72ba05d4e0c7afe989030c91c7824cb0b07",
    "support"
   ],
   "webdriver/tests/actions/control_click.py": [
    "2ec819b772fcfa4d0a37395aa64fb89ad7686bda",
    "wdspec"
   ],
@@ -647572,17 +647582,17 @@
    "1ed7db6e8e320575ffa99ec2f7b7cf2cfeb0ee6a",
    "wdspec"
   ],
   "webdriver/tests/delete_session/__init__.py": [
    "e69de29bb2d1d6434b8b29ae775ad8c2e48c5391",
    "support"
   ],
   "webdriver/tests/delete_session/delete.py": [
-   "835f2525792136e7aa0082e9b32165b662e8cdd8",
+   "d0b4d796308e20d057df0a02edc3a9ed428d21eb",
    "wdspec"
   ],
   "webdriver/tests/dismiss_alert/__init__.py": [
    "e69de29bb2d1d6434b8b29ae775ad8c2e48c5391",
    "support"
   ],
   "webdriver/tests/dismiss_alert/dismiss.py": [
    "c0efc38b47fd8b5d3d9fcf89a4dd803a9d38ec2f",
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/webdriver/tests/actions/bounds.py
@@ -0,0 +1,51 @@
+import pytest
+
+from tests.support.asserts import assert_error, assert_success
+
+
+def perform_actions(session, actions):
+    return session.transport.send(
+        "POST",
+        "/session/{session_id}/actions".format(session_id=session.session_id),
+        {"actions": actions})
+
+
+@pytest.mark.parametrize("action_type", ["none", "key", "pointer"])
+def test_pause_positive_integer(session, action_type):
+    for valid_duration in [0, 1]:
+        actions = [{
+            "type": action_type,
+            "id": "foobar",
+            "actions": [{
+                "type": "pause",
+                "duration": valid_duration
+            }]
+        }]
+        response = perform_actions(session, actions)
+        assert_success(response)
+
+    actions = [{
+        "type": action_type,
+        "id": "foobar",
+        "actions": [{
+            "type": "pause",
+            "duration": -1
+        }]
+    }]
+    response = perform_actions(session, actions)
+    assert_error(response, "invalid argument")
+
+
+@pytest.mark.parametrize("action_type", ["none", "key", "pointer"])
+def test_pause_invalid_types(session, action_type):
+    for invalid_type in [0.0, None, "foo", True, [], {}]:
+        actions = [{
+            "type": action_type,
+            "id": "foobar",
+            "actions": [{
+                "type": "pause",
+                "duration": invalid_type
+            }]
+        }]
+        response = perform_actions(session, actions)
+        assert_error(response, "invalid argument")
--- a/toolkit/components/downloads/DownloadIntegration.jsm
+++ b/toolkit/components/downloads/DownloadIntegration.jsm
@@ -550,21 +550,23 @@ var DownloadIntegration = {
       }
     }
 
     let aReferrer = null;
     if (aDownload.source.referrer) {
       aReferrer = NetUtil.newURI(aDownload.source.referrer);
     }
 
-    gDownloadPlatform.downloadDone(NetUtil.newURI(aDownload.source.url),
-                                   aReferrer,
-                                   new FileUtils.File(aDownload.target.path),
-                                   aDownload.contentType,
-                                   aDownload.source.isPrivate);
+    await gDownloadPlatform.downloadDone(
+      NetUtil.newURI(aDownload.source.url),
+      aReferrer,
+      new FileUtils.File(aDownload.target.path),
+      aDownload.contentType,
+      aDownload.source.isPrivate
+    );
   },
 
   /**
    * Launches a file represented by the target of a download. This can
    * open the file with the default application for the target MIME type
    * or file extension, or with a custom application if
    * aDownload.launcherPath is set.
    *
--- a/toolkit/components/downloads/DownloadPlatform.cpp
+++ b/toolkit/components/downloads/DownloadPlatform.cpp
@@ -8,22 +8,29 @@
 #include "nsString.h"
 #include "nsINestedURI.h"
 #include "nsIProtocolHandler.h"
 #include "nsIURI.h"
 #include "nsIFile.h"
 #include "nsIObserverService.h"
 #include "nsISupportsPrimitives.h"
 #include "nsDirectoryServiceDefs.h"
+#include "nsThreadUtils.h"
+#include "xpcpublic.h"
 
+#include "mozilla/dom/Promise.h"
+#include "mozilla/LazyIdleThread.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/Services.h"
 
 #define PREF_BDM_ADDTORECENTDOCS "browser.download.manager.addToRecentDocs"
 
+// The amount of time, in milliseconds, that our IO thread will stay alive after the last event it processes.
+#define DEFAULT_THREAD_TIMEOUT_MS 10000
+
 #ifdef XP_WIN
 #include <shlobj.h>
 #include <urlmon.h>
 #include "nsILocalFileWin.h"
 #endif
 
 #ifdef XP_MACOSX
 #include <CoreFoundation/CoreFoundation.h>
@@ -34,16 +41,17 @@
 #include "FennecJNIWrappers.h"
 #endif
 
 #ifdef MOZ_WIDGET_GTK
 #include <gtk/gtk.h>
 #endif
 
 using namespace mozilla;
+using dom::Promise;
 
 DownloadPlatform *DownloadPlatform::gDownloadPlatformService = nullptr;
 
 NS_IMPL_ISUPPORTS(DownloadPlatform, mozIDownloadPlatform);
 
 DownloadPlatform* DownloadPlatform::GetDownloadPlatform()
 {
   if (!gDownloadPlatformService) {
@@ -93,19 +101,44 @@ CFURLRef CreateCFURLFromNSIURI(nsIURI *a
                                          NULL);
 
   ::CFRelease(urlStr);
 
   return url;
 }
 #endif
 
+DownloadPlatform::DownloadPlatform()
+{
+  mIOThread = new LazyIdleThread(DEFAULT_THREAD_TIMEOUT_MS,
+                                 NS_LITERAL_CSTRING("DownloadPlatform"));
+}
+
 nsresult DownloadPlatform::DownloadDone(nsIURI* aSource, nsIURI* aReferrer, nsIFile* aTarget,
-                                        const nsACString& aContentType, bool aIsPrivate)
+                                        const nsACString& aContentType, bool aIsPrivate,
+                                        JSContext* aCx, Promise** aPromise)
 {
+
+  nsIGlobalObject* globalObject =
+    xpc::NativeGlobal(JS::CurrentGlobalOrNull(aCx));
+
+  if (NS_WARN_IF(!globalObject)) {
+    return NS_ERROR_FAILURE;
+  }
+
+  ErrorResult result;
+  RefPtr<Promise> promise = Promise::Create(globalObject, result);
+
+  if (NS_WARN_IF(result.Failed())) {
+    return result.StealNSResult();
+  }
+
+  nsresult rv = NS_OK;
+  bool pendingAsyncOperations = false;
+
 #if defined(XP_WIN) || defined(XP_MACOSX) || defined(MOZ_WIDGET_ANDROID) \
  || defined(MOZ_WIDGET_GTK)
 
   nsAutoString path;
   if (aTarget && NS_SUCCEEDED(aTarget->GetPath(path))) {
 #if defined(XP_WIN) || defined(MOZ_WIDGET_GTK) || defined(MOZ_WIDGET_ANDROID)
     // On Windows and Gtk, add the download to the system's "recent documents"
     // list, with a pref to disable.
@@ -164,42 +197,68 @@ nsresult DownloadPlatform::DownloadDone(
     CFStringRef pathCFStr = NULL;
     if (!path.IsEmpty()) {
       pathCFStr = ::CFStringCreateWithCharacters(kCFAllocatorDefault,
                                                  (const UniChar*)path.get(),
                                                  path.Length());
     }
     if (pathCFStr && !aIsPrivate) {
       bool isFromWeb = IsURLPossiblyFromWeb(aSource);
-
-      CFURLRef sourceCFURL = CreateCFURLFromNSIURI(aSource);
-      CFURLRef referrerCFURL = CreateCFURLFromNSIURI(aReferrer);
+      nsCOMPtr<nsIURI> source(aSource);
+      nsCOMPtr<nsIURI> referrer(aReferrer);
 
-      CocoaFileUtils::AddOriginMetadataToFile(pathCFStr,
-                                              sourceCFURL,
-                                              referrerCFURL);
-      CocoaFileUtils::AddQuarantineMetadataToFile(pathCFStr,
+      rv = mIOThread->Dispatch(NS_NewRunnableFunction(
+        "DownloadPlatform::DownloadDone",
+        [pathCFStr, isFromWeb, source, referrer, promise]() mutable {
+          CFURLRef sourceCFURL = CreateCFURLFromNSIURI(source);
+          CFURLRef referrerCFURL = CreateCFURLFromNSIURI(referrer);
+
+          CocoaFileUtils::AddOriginMetadataToFile(pathCFStr,
                                                   sourceCFURL,
-                                                  referrerCFURL,
-                                                  isFromWeb);
+                                                  referrerCFURL);
+          CocoaFileUtils::AddQuarantineMetadataToFile(pathCFStr,
+                                                      sourceCFURL,
+                                                      referrerCFURL,
+                                                      isFromWeb);
+          ::CFRelease(pathCFStr);
+          if (sourceCFURL) {
+            ::CFRelease(sourceCFURL);
+          }
+          if (referrerCFURL) {
+            ::CFRelease(referrerCFURL);
+          }
 
-      ::CFRelease(pathCFStr);
-      if (sourceCFURL) {
-        ::CFRelease(sourceCFURL);
-      }
-      if (referrerCFURL) {
-        ::CFRelease(referrerCFURL);
+          DebugOnly<nsresult> rv = NS_DispatchToMainThread(NS_NewRunnableFunction(
+            "DownloadPlatform::DownloadDoneResolve",
+            [promise = std::move(promise)]() {
+              promise->MaybeResolveWithUndefined();
+            }
+          ));
+          MOZ_ASSERT(NS_SUCCEEDED(rv));
+          // In non-debug builds, if we've for some reason failed to dispatch
+          // a runnable to the main thread to resolve the Promise, then it's
+          // unlikely we can reject it either. At that point, the Promise
+          // is going to remain in pending limbo until its global goes away.
+        }
+      ));
+
+      if (NS_SUCCEEDED(rv)) {
+        pendingAsyncOperations = true;
       }
     }
 #endif
   }
 
 #endif
 
-  return NS_OK;
+  if (!pendingAsyncOperations) {
+    promise->MaybeResolveWithUndefined();
+  }
+  promise.forget(aPromise);
+  return rv;
 }
 
 nsresult DownloadPlatform::MapUrlToZone(const nsAString& aURL,
                                         uint32_t* aZone)
 {
 #ifdef XP_WIN
   RefPtr<IInternetSecurityManager> inetSecMgr;
   if (FAILED(CoCreateInstance(CLSID_InternetSecurityManager, NULL,
--- a/toolkit/components/downloads/DownloadPlatform.h
+++ b/toolkit/components/downloads/DownloadPlatform.h
@@ -3,32 +3,35 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef __DownloadPlatform_h__
 #define __DownloadPlatform_h__
 
 #include "mozIDownloadPlatform.h"
 
 #include "nsCOMPtr.h"
+#include "nsIThread.h"
+
 class nsIURI;
 
 class DownloadPlatform : public mozIDownloadPlatform
 {
 protected:
 
   virtual ~DownloadPlatform() { }
 
 public:
 
   NS_DECL_ISUPPORTS
   NS_DECL_MOZIDOWNLOADPLATFORM
 
-  DownloadPlatform() { }
+  DownloadPlatform();
 
   static DownloadPlatform *gDownloadPlatformService;
 
   static DownloadPlatform* GetDownloadPlatform();
 
 private:
+  nsCOMPtr<nsIThread> mIOThread;
   static bool IsURLPossiblyFromWeb(nsIURI* aURI);
 };
 
 #endif
--- a/toolkit/components/downloads/mozIDownloadPlatform.idl
+++ b/toolkit/components/downloads/mozIDownloadPlatform.idl
@@ -29,20 +29,21 @@ interface mozIDownloadPlatform : nsISupp
    * @param aReferrer
    *        Referrer URI of the download
    * @param aTarget
    *        Downloaded file
    * @param aContentType
    *        The source's content type
    * @param aIsPrivate
    *        True for private downloads
-   * @return none
+   * @return Promise that resolves once operations have completed.
    */
-  void downloadDone(in nsIURI aSource, in nsIURI aReferrer, in nsIFile aTarget,
-                    in ACString aContentType, in boolean aIsPrivate);
+  [implicit_jscontext]
+  Promise downloadDone(in nsIURI aSource, in nsIURI aReferrer, in nsIFile aTarget,
+                       in ACString aContentType, in boolean aIsPrivate);
 
   /**
    * Security Zone constants. Used by mapUrlToZone().
    */
   const unsigned long ZONE_MY_COMPUTER = 0;
   const unsigned long ZONE_INTRANET = 1;
   const unsigned long ZONE_TRUSTED = 2;
   const unsigned long ZONE_INTERNET = 3;
--- a/toolkit/components/extensions/parent/ext-theme.js
+++ b/toolkit/components/extensions/parent/ext-theme.js
@@ -1,12 +1,14 @@
 "use strict";
 
 /* global windowTracker, EventManager, EventEmitter */
 
+/* eslint-disable complexity */
+
 ChromeUtils.import("resource://gre/modules/Services.jsm");
 
 ChromeUtils.defineModuleGetter(this, "LightweightThemeManager",
                                "resource://gre/modules/LightweightThemeManager.jsm");
 
 var {
   getWinUtils,
 } = ExtensionUtils;
@@ -179,16 +181,20 @@ class Theme {
         case "button_background_active":
         case "popup":
         case "popup_text":
         case "popup_border":
         case "popup_highlight":
         case "popup_highlight_text":
         case "ntp_background":
         case "ntp_text":
+        case "sidebar":
+        case "sidebar_text":
+        case "sidebar_highlight":
+        case "sidebar_highlight_text":
           this.lwtStyles[color] = cssColor;
           break;
         default:
           if (this.experiment && this.experiment.colors && color in this.experiment.colors) {
             this.lwtStyles.experimental.colors[color] = cssColor;
           } else {
             const {logger} = this.extension;
             logger.warn(`Unrecognized theme property found: colors.${color}`);
@@ -297,32 +303,25 @@ class Theme {
       }
 
       switch (property) {
         case "additional_backgrounds_alignment": {
           if (!assertValidAdditionalBackgrounds(property, val.length)) {
             break;
           }
 
-          let alignment = [];
-          if (this.lwtStyles.headerURL) {
-            alignment.push("right top");
-          }
-          this.lwtStyles.backgroundsAlignment = alignment.concat(val).join(",");
+          this.lwtStyles.backgroundsAlignment = val.join(",");
           break;
         }
         case "additional_backgrounds_tiling": {
           if (!assertValidAdditionalBackgrounds(property, val.length)) {
             break;
           }
 
           let tiling = [];
-          if (this.lwtStyles.headerURL) {
-            tiling.push("no-repeat");
-          }
           for (let i = 0, l = this.lwtStyles.additionalBackgrounds.length; i < l; ++i) {
             tiling.push(val[i] || "no-repeat");
           }
           this.lwtStyles.backgroundsTiling = tiling.join(",");
           break;
         }
         default: {
           if (this.experiment && this.experiment.properties && property in this.experiment.properties) {
--- a/toolkit/components/extensions/schemas/theme.json
+++ b/toolkit/components/extensions/schemas/theme.json
@@ -231,16 +231,32 @@
               },
               "ntp_background": {
                 "$ref": "ThemeColor",
                 "optional": true
               },
               "ntp_text": {
                 "$ref": "ThemeColor",
                 "optional": true
+              },
+              "sidebar": {
+                "$ref": "ThemeColor",
+                "optional": true
+              },
+              "sidebar_text": {
+                "$ref": "ThemeColor",
+                "optional": true
+              },
+              "sidebar_highlight": {
+                "$ref": "ThemeColor",
+                "optional": true
+              },
+              "sidebar_highlight_text": {
+                "$ref": "ThemeColor",
+                "optional": true
               }
             },
             "additionalProperties": { "$ref": "ThemeColor" }
           },
           "icons": {
             "type": "object",
             "optional": true,
             "properties": {
--- a/toolkit/components/extensions/test/browser/browser.ini
+++ b/toolkit/components/extensions/test/browser/browser.ini
@@ -1,14 +1,15 @@
 [DEFAULT]
 support-files =
   head.js
 
 [browser_ext_management_themes.js]
 skip-if = verify
+[browser_ext_themes_additional_backgrounds_alignment.js]
 [browser_ext_themes_alpha_accentcolor.js]
 [browser_ext_themes_chromeparity.js]
 [browser_ext_themes_dynamic_getCurrent.js]
 [browser_ext_themes_dynamic_onUpdated.js]
 [browser_ext_themes_dynamic_updates.js]
 [browser_ext_themes_experiment.js]
 [browser_ext_themes_getCurrent_differentExt.js]
 [browser_ext_themes_lwtsupport.js]
@@ -27,10 +28,11 @@ skip-if = verify
 [browser_ext_themes_toolbars.js]
 [browser_ext_themes_toolbarbutton_icons.js]
 [browser_ext_themes_toolbarbutton_colors.js]
 [browser_ext_themes_theme_transition.js]
 [browser_ext_themes_arrowpanels.js]
 [browser_ext_themes_tab_selected.js]
 [browser_ext_themes_autocomplete_popup.js]
 [browser_ext_themes_sanitization.js]
+[browser_ext_themes_sidebars.js]
 [browser_ext_themes_findbar.js]
 [browser_ext_themes_warnings.js]
new file mode 100644
--- /dev/null
+++ b/toolkit/components/extensions/test/browser/browser_ext_themes_additional_backgrounds_alignment.js
@@ -0,0 +1,105 @@
+"use strict";
+
+/* globals InspectorUtils */
+
+// Case 1 - When there is a headerURL image and additional_backgrounds_alignment is not specified.
+// So background-position should default to "left top"
+add_task(async function test_default_additional_backgrounds_alignment() {
+  const LEFT_TOP = "0% 0%";
+  const RIGHT_TOP = "100% 0%";
+
+  let extension = ExtensionTestUtils.loadExtension({
+    manifest: {
+      "theme": {
+        "images": {
+          "headerURL": "image1.png",
+          "additional_backgrounds": ["image1.png", "image1.png"],
+        },
+        "colors": {
+          "accentcolor": ACCENT_COLOR,
+          "textcolor": TEXT_COLOR,
+        },
+      },
+    },
+    files: {
+      "image1.png": BACKGROUND,
+    },
+
+  });
+
+  await extension.startup();
+
+  let docEl = document.documentElement;
+  let rootCS = window.getComputedStyle(docEl);
+
+  Assert.equal(
+    rootCS.getPropertyValue("background-position"),
+    RIGHT_TOP,
+    "root only contains headerURL alignment property"
+  );
+
+
+  let toolbox = document.querySelector("#navigator-toolbox");
+  let toolboxCS = window.getComputedStyle(toolbox);
+
+  Assert.equal(
+    toolboxCS.getPropertyValue("background-position"),
+    LEFT_TOP,
+    toolbox.id + " only contains default additional backgrounds alignment property"
+  );
+
+  await extension.unload();
+});
+
+
+// Case 2 - When there is a headerURL image and additional_backgrounds_alignment is specified.
+add_task(async function test_additional_backgrounds_alignment() {
+  const LEFT_BOTTOM = "0% 100%";
+  const CENTER_CENTER = "50% 50%";
+  const RIGHT_TOP = "100% 0%";
+
+  let extension = ExtensionTestUtils.loadExtension({
+    manifest: {
+      "theme": {
+        "images": {
+          "headerURL": "image1.png",
+          "additional_backgrounds": ["image1.png", "image1.png", "image1.png"],
+        },
+        "colors": {
+          "accentcolor": ACCENT_COLOR,
+          "textcolor": TEXT_COLOR,
+        },
+        "properties": {
+          additional_backgrounds_alignment: ["left bottom", "center center", "right top"],
+        },
+      },
+    },
+    files: {
+      "image1.png": BACKGROUND,
+    },
+
+  });
+
+  await extension.startup();
+
+  let docEl = document.documentElement;
+  let rootCS = window.getComputedStyle(docEl);
+
+  Assert.equal(
+    rootCS.getPropertyValue("background-position"),
+    RIGHT_TOP,
+    "root only contains headerURL alignment property"
+  );
+
+
+  let toolbox = document.querySelector("#navigator-toolbox");
+  let toolboxCS = window.getComputedStyle(toolbox);
+
+  Assert.equal(
+    toolboxCS.getPropertyValue("background-position"),
+    LEFT_BOTTOM + ", " + CENTER_CENTER + ", " + RIGHT_TOP,
+    toolbox.id + " contains additional backgrounds alignment properties"
+  );
+
+  await extension.unload();
+});
--- a/toolkit/components/extensions/test/browser/browser_ext_themes_multiple_backgrounds.js
+++ b/toolkit/components/extensions/test/browser/browser_ext_themes_multiple_backgrounds.js
@@ -1,59 +1,67 @@
 "use strict";
 
 add_task(async function test_support_backgrounds_position() {
   let extension = ExtensionTestUtils.loadExtension({
     manifest: {
       "theme": {
         "images": {
-          "headerURL": "face.png",
-          "additional_backgrounds": ["face.png", "face.png", "face.png"],
+          "headerURL": "face1.png",
+          "additional_backgrounds": ["face2.png", "face2.png", "face2.png"],
         },
         "colors": {
           "accentcolor": `rgb(${FRAME_COLOR.join(",")})`,
           "textcolor": `rgb(${TAB_BACKGROUND_TEXT_COLOR.join(",")})`,
         },
         "properties": {
           "additional_backgrounds_alignment": ["left top", "center top", "right bottom"],
         },
       },
     },
     files: {
-      "face.png": imageBufferFromDataURI(ENCODED_IMAGE_DATA),
+      "face1.png": imageBufferFromDataURI(ENCODED_IMAGE_DATA),
+      "face2.png": imageBufferFromDataURI(ENCODED_IMAGE_DATA),
     },
   });
 
   await extension.startup();
 
   let docEl = window.document.documentElement;
+  let toolbox = document.querySelector("#navigator-toolbox");
 
   Assert.ok(docEl.hasAttribute("lwtheme"), "LWT attribute should be set");
   Assert.equal(docEl.getAttribute("lwthemetextcolor"), "bright",
                "LWT text color attribute should be set");
 
-  let style = window.getComputedStyle(docEl);
-  let bgImage = style.backgroundImage.split(",")[0].trim();
-  Assert.ok(bgImage.includes("face.png"),
-            `The backgroundImage should use face.png. Actual value is: ${bgImage}`);
-  Assert.equal(Array(4).fill(bgImage).join(", "), style.backgroundImage,
-               "The backgroundImage should use face.png four times.");
-  Assert.equal(style.backgroundPosition, "100% 0%, 0% 0%, 50% 0%, 100% 100%",
-               "The backgroundPosition should use the four values provided.");
-  Assert.equal(style.backgroundRepeat, "no-repeat",
+  let toolboxCS = window.getComputedStyle(toolbox);
+  let rootCS = window.getComputedStyle(docEl);
+  let rootBgImage = rootCS.backgroundImage.split(",")[0].trim();
+  let bgImage = toolboxCS.backgroundImage.split(",")[0].trim();
+  Assert.ok(rootBgImage.includes("face1.png"),
+            `The backgroundImage should use face1.png. Actual value is: ${rootBgImage}`);
+  Assert.equal(toolboxCS.backgroundImage, Array(3).fill(bgImage).join(", "),
+               "The backgroundImage should use face2.png three times.");
+  Assert.equal(toolboxCS.backgroundPosition, "0% 0%, 50% 0%, 100% 100%",
+               "The backgroundPosition should use the three values provided.");
+  Assert.equal(toolboxCS.backgroundRepeat, "no-repeat",
                "The backgroundPosition should use the default value.");
 
   await extension.unload();
 
   Assert.ok(!docEl.hasAttribute("lwtheme"), "LWT attribute should not be set");
-  style = window.getComputedStyle(docEl);
+  toolboxCS = window.getComputedStyle(toolbox);
+
   // Styles should've reverted to their initial values.
-  Assert.equal(style.backgroundImage, "none");
-  Assert.equal(style.backgroundPosition, "0% 0%");
-  Assert.equal(style.backgroundRepeat, "repeat");
+  Assert.equal(rootCS.backgroundImage, "none");
+  Assert.equal(rootCS.backgroundPosition, "0% 0%");
+  Assert.equal(rootCS.backgroundRepeat, "repeat");
+  Assert.equal(toolboxCS.backgroundImage, "none");
+  Assert.equal(toolboxCS.backgroundPosition, "0% 0%");
+  Assert.equal(toolboxCS.backgroundRepeat, "repeat");
 });
 
 add_task(async function test_support_backgrounds_repeat() {
   let extension = ExtensionTestUtils.loadExtension({
     manifest: {
       "theme": {
         "images": {
           "theme_frame": "face0.png",
@@ -74,31 +82,37 @@ add_task(async function test_support_bac
       "face2.png": imageBufferFromDataURI(ENCODED_IMAGE_DATA),
       "face3.png": imageBufferFromDataURI(ENCODED_IMAGE_DATA),
     },
   });
 
   await extension.startup();
 
   let docEl = window.document.documentElement;
+  let toolbox = document.querySelector("#navigator-toolbox");
 
   Assert.ok(docEl.hasAttribute("lwtheme"), "LWT attribute should be set");
   Assert.equal(docEl.getAttribute("lwthemetextcolor"), "bright",
                "LWT text color attribute should be set");
 
-  let style = window.getComputedStyle(docEl);
-  let bgImage = style.backgroundImage.split(",")[0].trim();
+  let rootCS = window.getComputedStyle(docEl);
+  let toolboxCS = window.getComputedStyle(toolbox);
+  let bgImage = rootCS.backgroundImage.split(",")[0].trim();
   Assert.ok(bgImage.includes("face0.png"),
             `The backgroundImage should use face.png. Actual value is: ${bgImage}`);
-  Assert.equal([0, 1, 2, 3].map(num => bgImage.replace(/face[\d]*/, `face${num}`)).join(", "),
-               style.backgroundImage, "The backgroundImage should use face.png four times.");
-  Assert.equal(style.backgroundPosition, "100% 0%",
-               "The backgroundPosition should use the default value.");
-  Assert.equal(style.backgroundRepeat, "no-repeat, repeat-x, repeat-y, repeat",
-               "The backgroundPosition should use the four values provided.");
+  Assert.equal([1, 2, 3].map(num => bgImage.replace(/face[\d]*/, `face${num}`)).join(", "),
+               toolboxCS.backgroundImage, "The backgroundImage should use face.png three times.");
+  Assert.equal(rootCS.backgroundPosition, "100% 0%",
+               "The backgroundPosition should use the default value for root.");
+  Assert.equal(toolboxCS.backgroundPosition, "0% 0%",
+               "The backgroundPosition should use the default value for navigator-toolbox.");
+  Assert.equal(rootCS.backgroundRepeat, "no-repeat",
+               "The backgroundRepeat should use the default values for root.");
+  Assert.equal(toolboxCS.backgroundRepeat, "repeat-x, repeat-y, repeat",
+               "The backgroundRepeat should use the three values provided for navigator-toolbox.");
 
   await extension.unload();
 
   Assert.ok(!docEl.hasAttribute("lwtheme"), "LWT attribute should not be set");
 });
 
 add_task(async function test_additional_images_check() {
   let extension = ExtensionTestUtils.loadExtension({
@@ -119,28 +133,30 @@ add_task(async function test_additional_
     files: {
       "face.png": imageBufferFromDataURI(ENCODED_IMAGE_DATA),
     },
   });
 
   await extension.startup();
 
   let docEl = window.document.documentElement;
+  let toolbox = document.querySelector("#navigator-toolbox");
 
   Assert.ok(docEl.hasAttribute("lwtheme"), "LWT attribute should be set");
   Assert.equal(docEl.getAttribute("lwthemetextcolor"), "bright",
                "LWT text color attribute should be set");
 
-  let style = window.getComputedStyle(docEl);
-  let bgImage = style.backgroundImage.split(",")[0];
+  let rootCS = window.getComputedStyle(docEl);
+  let toolboxCS = window.getComputedStyle(toolbox);
+  let bgImage = rootCS.backgroundImage.split(",")[0];
   Assert.ok(bgImage.includes("face.png"),
             `The backgroundImage should use face.png. Actual value is: ${bgImage}`);
-  Assert.equal(bgImage + ", none", style.backgroundImage,
-               "The backgroundImage should use face.png only once.");
-  Assert.equal(style.backgroundPosition, "100% 0%",
+  Assert.equal("none", toolboxCS.backgroundImage,
+               "The backgroundImage should not use face.png.");
+  Assert.equal(rootCS.backgroundPosition, "100% 0%",
                "The backgroundPosition should use the default value.");
-  Assert.equal(style.backgroundRepeat, "no-repeat",
+  Assert.equal(rootCS.backgroundRepeat, "no-repeat",
                "The backgroundPosition should use only one (default) value.");
 
   await extension.unload();
 
   Assert.ok(!docEl.hasAttribute("lwtheme"), "LWT attribute should not be set");
 });
--- a/toolkit/components/extensions/test/browser/browser_ext_themes_sanitization.js
+++ b/toolkit/components/extensions/test/browser/browser_ext_themes_sanitization.js
@@ -5,78 +5,77 @@
 add_task(async function test_sanitization_invalid() {
   // This test checks that invalid values are sanitized
   let extension = ExtensionTestUtils.loadExtension({
     manifest: {
       "theme": {
         "colors": {
           "accentcolor": ACCENT_COLOR,
           "textcolor": TEXT_COLOR,
-          "toolbar_bottom_separator": "ntimsfavoriteblue",
+          "toolbar_text": "ntimsfavoriteblue",
         },
       },
     },
   });
 
   let docEl = document.documentElement;
 
   let transitionPromise = waitForTransition(docEl, "background-color");
   await extension.startup();
   await transitionPromise;
 
-  let toolbox = document.querySelector("#navigator-toolbox");
+  let navbar = document.querySelector("#nav-bar");
   Assert.equal(
-    window.getComputedStyle(toolbox, "::after").borderBottomColor,
+    window.getComputedStyle(navbar).color,
     "rgb(0, 0, 0)",
     "All invalid values should always compute to black."
   );
 
   await extension.unload();
 });
 
 add_task(async function test_sanitization_css_variables() {
   // This test checks that CSS variables are sanitized
   let extension = ExtensionTestUtils.loadExtension({
     manifest: {
       "theme": {
         "colors": {
           "accentcolor": ACCENT_COLOR,
           "textcolor": TEXT_COLOR,
-          "toolbar_bottom_separator": "var(--arrowpanel-dimmed)",
+          "toolbar_text": "var(--arrowpanel-dimmed)",
         },
       },
     },
   });
 
   let docEl = document.documentElement;
 
   let transitionPromise = waitForTransition(docEl, "background-color");
   await extension.startup();
   await transitionPromise;
 
-  let toolbox = document.querySelector("#navigator-toolbox");
+  let navbar = document.querySelector("#nav-bar");
   Assert.equal(
-    window.getComputedStyle(toolbox, "::after").borderBottomColor,
+    window.getComputedStyle(navbar).color,
     "rgb(0, 0, 0)",
     "All CSS variables should always compute to black."
   );
 
   await extension.unload();
 });
 
 add_task(async function test_sanitization_transparent() {
   // This test checks whether transparent values are applied properly
   let extension = ExtensionTestUtils.loadExtension({
     manifest: {
       "theme": {
         "colors": {
           "accentcolor": ACCENT_COLOR,
           "textcolor": TEXT_COLOR,
           "toolbar_top_separator": "transparent",
-          "toolbar_bottom_separator": "transparent",
         },
       },
     },
   });
 
   let docEl = document.documentElement;
 
   let transitionPromise = waitForTransition(docEl, "background-color");
@@ -84,23 +83,16 @@ add_task(async function test_sanitizatio
   await transitionPromise;
 
   let navbar = document.querySelector("#nav-bar");
   Assert.ok(
     window.getComputedStyle(navbar).boxShadow.includes("rgba(0, 0, 0, 0)"),
     "Top separator should be transparent"
   );
 
-  let toolbox = document.querySelector("#navigator-toolbox");
-  Assert.equal(
-    window.getComputedStyle(toolbox, "::after").borderBottomColor,
-    "rgba(0, 0, 0, 0)",
-    "Bottom separator should be transparent"
-  );
-
   await extension.unload();
 });
 
 add_task(async function test_sanitization_transparent_accentcolor() {
   // This test checks whether transparent accentcolor falls back to white.
   let extension = ExtensionTestUtils.loadExtension({
     manifest: {
       "theme": {
--- a/toolkit/components/extensions/test/browser/browser_ext_themes_separators.js
+++ b/toolkit/components/extensions/test/browser/browser_ext_themes_separators.js
@@ -51,16 +51,24 @@ add_task(async function test_support_sep
   Assert.ok(
     window.getComputedStyle(panelUIButton)
           .getPropertyValue("border-image-source")
           .includes(`rgb(${hexToRGB(SEPARATOR_VERTICAL_COLOR).join(", ")})`),
     "Vertical separator color properly set"
   );
 
   let toolbox = document.querySelector("#navigator-toolbox");
-  Assert.equal(
-    window.getComputedStyle(toolbox, "::after").borderBottomColor,
-    `rgb(${hexToRGB(SEPARATOR_BOTTOM_COLOR).join(", ")})`,
-    "Bottom separator color properly set"
-  );
+  if (AppConstants.platform == "macosx") {
+    Assert.ok(
+      window.getComputedStyle(toolbox, "::after").boxShadow
+            .includes(`rgb(${hexToRGB(SEPARATOR_BOTTOM_COLOR).join(", ")})`),
+      "Bottom separator color properly set"
+    );
+  } else {
+    Assert.equal(
+      window.getComputedStyle(toolbox, "::after").borderBottomColor,
+      `rgb(${hexToRGB(SEPARATOR_BOTTOM_COLOR).join(", ")})`,
+      "Bottom separator color properly set"
+    );
+  }
 
   await extension.unload();
 });
new file mode 100644
--- /dev/null
+++ b/toolkit/components/extensions/test/browser/browser_ext_themes_sidebars.js
@@ -0,0 +1,136 @@
+"use strict";
+
+// This test checks whether the sidebar color properties work.
+
+/**
+ * Test whether the selected browser has the sidebar theme applied
+ * @param {Object} theme that is applied
+ * @param {boolean} isBrightText whether the brighttext attribute should be set
+ */
+async function test_sidebar_theme(theme, isBrightText) {
+  let extension = ExtensionTestUtils.loadExtension({
+    manifest: {
+      theme,
+    },
+  });
+
+  const content = SidebarUI.browser.contentWindow;
+  const root = content.document.documentElement;
+
+  ok(!root.hasAttribute("lwt-sidebar"),
+     "Sidebar should not have lwt-sidebar attribute");
+  ok(!root.hasAttribute("lwt-sidebar-brighttext"),
+     "Sidebar should not have lwt-sidebar-brighttext attribute");
+  ok(!root.hasAttribute("lwt-sidebar-highlight"),
+     "Sidebar should not have lwt-sidebar-highlight attribute");
+
+  const rootCS = content.getComputedStyle(root);
+  const originalBackground = rootCS.backgroundColor;
+  const originalColor = rootCS.color;
+
+  // ::-moz-tree-row(selected, focus) computed style can't be accessed, so we create a fake one.
+  const highlightCS = {
+    get backgroundColor() {
+      // Standardize to rgb like other computed style.
+      let color = rootCS.getPropertyValue("--lwt-sidebar-highlight-background-color");
+      let [r, g, b] = color.replace("rgba(", "")
+        .split(",")
+        .map(channel => parseInt(channel, 10));
+      return `rgb(${r}, ${g}, ${b})`;
+    },
+
+    get color() {
+      let color = rootCS.getPropertyValue("--lwt-sidebar-highlight-text-color");
+      let [r, g, b] = color.replace("rgba(", "")
+        .split(",")
+        .map(channel => parseInt(channel, 10));
+      return `rgb(${r}, ${g}, ${b})`;
+    },
+  };
+  const originalHighlightBackground = highlightCS.backgroundColor;
+  const originalHighlightColor = highlightCS.color;
+
+  await extension.startup();
+
+  Services.ppmm.sharedData.flush();
+
+  const actualBackground = hexToCSS(theme.colors.sidebar) || originalBackground;
+  const actualColor = hexToCSS(theme.colors.sidebar_text) || originalColor;
+  const actualHighlightBackground = hexToCSS(theme.colors.sidebar_highlight) || originalHighlightBackground;
+  const actualHighlightColor = hexToCSS(theme.colors.sidebar_highlight_text) || originalHighlightColor;
+  const isCustomHighlight = !!theme.colors.sidebar_highlight_text;
+  const isCustomSidebar = !!theme.colors.sidebar_text;
+
+  is(root.hasAttribute("lwt-sidebar"), isCustomSidebar,
+     `Sidebar should${!isCustomSidebar ? " not" : ""} have lwt-sidebar attribute`);
+  is(root.hasAttribute("lwt-sidebar-brighttext"), isBrightText,
+     `Sidebar should${!isBrightText ? " not" : ""} have lwt-sidebar-brighttext attribute`);
+  is(root.hasAttribute("lwt-sidebar-highlight"), isCustomHighlight,
+     `Sidebar should${!isCustomHighlight ? " not" : ""} have lwt-sidebar-highlight attribute`);
+
+  is(rootCS.backgroundColor, actualBackground, "Sidebar background should be set.");
+  is(rootCS.color, actualColor, "Sidebar text color should be set.");
+
+  is(highlightCS.backgroundColor, actualHighlightBackground,
+     "Sidebar highlight background color should be set.");
+  is(highlightCS.color, actualHighlightColor,
+     "Sidebar highlight text color should be set.");
+
+  await extension.unload();
+
+  Services.ppmm.sharedData.flush();
+
+  ok(!root.hasAttribute("lwt-sidebar"),
+     "Sidebar should not have lwt-sidebar attribute");
+  ok(!root.hasAttribute("lwt-sidebar-brighttext"),
+     "Sidebar should not have lwt-sidebar-brighttext attribute");
+  ok(!root.hasAttribute("lwt-sidebar-highlight"),
+     "Sidebar should not have lwt-sidebar-highlight attribute");
+
+  is(rootCS.backgroundColor, originalBackground,
+     "Sidebar background should be reset.");
+  is(rootCS.color, originalColor,
+     "Sidebar text color should be reset.");
+  is(highlightCS.backgroundColor, originalHighlightBackground,
+     "Sidebar highlight background color should be reset.");
+  is(highlightCS.color, originalHighlightColor,
+     "Sidebar highlight text color should be reset.");
+}
+
+add_task(async function test_support_sidebar_colors() {
+  for (let command of ["viewBookmarksSidebar", "viewHistorySidebar"]) {
+    info("Executing command: " + command);
+
+    await SidebarUI.show(command);
+
+    await test_sidebar_theme({
+      colors: {
+        sidebar: "#fafad2", // lightgoldenrodyellow
+        sidebar_text: "#2f4f4f", // darkslategrey
+      },
+    }, false);
+
+    await test_sidebar_theme({
+      colors: {
+        sidebar: "#8b4513", // saddlebrown
+        sidebar_text: "#ffa07a", // lightsalmon
+      },
+    }, true);
+
+    await test_sidebar_theme({
+      colors: {
+        sidebar: "#fffafa", // snow
+        sidebar_text: "#663399", // rebeccapurple
+        sidebar_highlight: "#7cfc00", // lawngreen
+        sidebar_highlight_text: "#ffefd5", // papayawhip
+      },
+    }, false);
+
+    await test_sidebar_theme({
+      colors: {
+        sidebar_highlight: "#a0522d", // sienna
+        sidebar_highlight_text: "#fff5ee", // seashell
+      },
+    }, false);
+  }
+});
--- a/toolkit/components/extensions/test/browser/head.js
+++ b/toolkit/components/extensions/test/browser/head.js
@@ -33,25 +33,31 @@ const ENCODED_IMAGE_DATA = "iVBORw0KGgoA
   "eG+mgfrcLHh3C79bx6wttGEqERiH/AjPohWMouv2ZAAAAAElFTkSuQmCC";
 const ACCENT_COLOR = "#a14040";
 const TEXT_COLOR = "#fac96e";
 // For testing aliases of the colors above:
 const FRAME_COLOR = [71, 105, 91];
 const TAB_BACKGROUND_TEXT_COLOR = [207, 221, 192, .9];
 
 function hexToRGB(hex) {
+  if (!hex) {
+    return null;
+  }
   hex = parseInt((hex.indexOf("#") > -1 ? hex.substring(1) : hex), 16);
   return [hex >> 16, (hex & 0x00FF00) >> 8, (hex & 0x0000FF)];
 }
 
 function rgbToCSS(rgb) {
   return `rgb(${rgb.join(", ")})`;
 }
 
 function hexToCSS(hex) {
+  if (!hex) {
+    return null;
+  }
   return rgbToCSS(hexToRGB(hex));
 }
 
 function imageBufferFromDataURI(encodedImageData) {
   let decodedImageData = atob(encodedImageData);
   return Uint8Array.from(decodedImageData, byte => byte.charCodeAt(0)).buffer;
 }
 
--- a/toolkit/components/satchel/formSubmitListener.js
+++ b/toolkit/components/satchel/formSubmitListener.js
@@ -1,20 +1,21 @@
 /* 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/. */
 
 /* eslint-env mozilla/frame-script */
 
 ChromeUtils.defineModuleGetter(this, "CreditCard",
                                "resource://gre/modules/CreditCard.jsm");
+ChromeUtils.defineModuleGetter(this, "PrivateBrowsingUtils",
+                               "resource://gre/modules/PrivateBrowsingUtils.jsm");
 
 (function() {
 ChromeUtils.import("resource://gre/modules/Services.jsm");
-ChromeUtils.import("resource://gre/modules/PrivateBrowsingUtils.jsm");
 
 let satchelFormListener = {
   QueryInterface: ChromeUtils.generateQI([
     Ci.nsIFormSubmitObserver,
     Ci.nsIObserver,
     Ci.nsISupportsWeakReference,
   ]),
 
--- a/toolkit/crashreporter/nsExceptionHandler.cpp
+++ b/toolkit/crashreporter/nsExceptionHandler.cpp
@@ -2563,17 +2563,17 @@ static nsresult PrefSubmitReports(bool* 
         return NS_OK;
     }
     // Create the file so the INI processor can write to it.
     rv = reporterINI->Create(nsIFile::NORMAL_FILE_TYPE, 0600);
     NS_ENSURE_SUCCESS(rv, rv);
   }
 
   nsCOMPtr<nsIINIParserFactory> iniFactory =
-    do_GetService("@mozilla.org/xpcom/ini-processor-factory;1", &rv);
+    do_GetService("@mozilla.org/xpcom/ini-parser-factory;1", &rv);
   NS_ENSURE_SUCCESS(rv, rv);
 
   nsCOMPtr<nsIINIParser> iniParser;
   rv = iniFactory->CreateINIParser(reporterINI,
                                    getter_AddRefs(iniParser));
   NS_ENSURE_SUCCESS(rv, rv);
 
   // If we're writing the pref, just set and we're done.
@@ -2581,17 +2581,17 @@ static nsresult PrefSubmitReports(bool* 
     nsCOMPtr<nsIINIParserWriter> iniWriter = do_QueryInterface(iniParser);
     NS_ENSURE_TRUE(iniWriter, NS_ERROR_FAILURE);
 
     rv = iniWriter->SetString(NS_LITERAL_CSTRING("Crash Reporter"),
                               NS_LITERAL_CSTRING("SubmitReport"),
                               *aSubmitReports ?  NS_LITERAL_CSTRING("1") :
                                                  NS_LITERAL_CSTRING("0"));
     NS_ENSURE_SUCCESS(rv, rv);
-    rv = iniWriter->WriteFile(nullptr, 0);
+    rv = iniWriter->WriteFile(reporterINI);
     return rv;
   }
 
   nsAutoCString submitReportValue;
   rv = iniParser->GetString(NS_LITERAL_CSTRING("Crash Reporter"),
                             NS_LITERAL_CSTRING("SubmitReport"),
                             submitReportValue);
 
--- a/toolkit/recordreplay/ipc/DisabledIPC.cpp
+++ b/toolkit/recordreplay/ipc/DisabledIPC.cpp
@@ -141,12 +141,18 @@ GetArgumentsForChildProcess(base::Proces
 }
 
 base::ProcessId
 ParentProcessId()
 {
   MOZ_CRASH();
 }
 
+bool
+IsMiddlemanWithRecordingChild()
+{
+  return false;
+}
+
 } // namespace parent
 
 } // namespace recordreplay
 } // namespace mozilla
--- a/toolkit/recordreplay/ipc/ParentForwarding.cpp
+++ b/toolkit/recordreplay/ipc/ParentForwarding.cpp
@@ -13,89 +13,22 @@
 #include "mozilla/dom/PBrowserChild.h"
 #include "mozilla/dom/ContentChild.h"
 #include "mozilla/layers/CompositorBridgeChild.h"
 
 namespace mozilla {
 namespace recordreplay {
 namespace parent {
 
-// Known associations between managee and manager routing IDs.
-static StaticInfallibleVector<std::pair<int32_t, int32_t>> gProtocolManagers;
-
-// The routing IDs of actors in the parent process that have been destroyed.
-static StaticInfallibleVector<int32_t> gDeadRoutingIds;
-
-static void
-NoteProtocolManager(int32_t aManagee, int32_t aManager)
-{
-  gProtocolManagers.emplaceBack(aManagee, aManager);
-  for (auto id : gDeadRoutingIds) {
-    if (id == aManager) {
-      gDeadRoutingIds.emplaceBack(aManagee);
-    }
-  }
-}
-
-static void
-DestroyRoutingId(int32_t aId)
-{
-  gDeadRoutingIds.emplaceBack(aId);
-  for (auto manager : gProtocolManagers) {
-    if (manager.second == aId) {
-      DestroyRoutingId(manager.first);
-    }
-  }
-}
-
-// Return whether a message from the child process to the UI process is being
-// sent to a target that is being destroyed, and should be suppressed.
-static bool
-MessageTargetIsDead(const IPC::Message& aMessage)
-{
-  // After the parent process destroys a browser, we handle the destroy in
-  // both the middleman and child processes. Both processes will respond to
-  // the destroy by sending additional messages to the UI process indicating
-  // the browser has been destroyed, but we need to ignore such messages from
-  // the child process (if it is still recording) to avoid confusing the UI
-  // process.
-  for (int32_t id : gDeadRoutingIds) {
-    if (id == aMessage.routing_id()) {
-      return true;
-    }
-  }
-  return false;
-}
-
 static bool
 HandleMessageInMiddleman(ipc::Side aSide, const IPC::Message& aMessage)
 {
   IPC::Message::msgid_t type = aMessage.type();
 
-  // Ignore messages sent from the child to dead UI process targets.
   if (aSide == ipc::ParentSide) {
-    // When the browser is destroyed in the UI process all its children will
-    // also be destroyed. Figure out the routing IDs of children which we need
-    // to recognize as dead once the browser is destroyed. This is not a
-    // complete list of all the browser's children, but only includes ones
-    // where crashes have been seen as a result.
-    if (type == dom::PBrowser::Msg_PDocAccessibleConstructor__ID) {
-      PickleIterator iter(aMessage);
-      ipc::ActorHandle handle;
-
-      if (!IPC::ReadParam(&aMessage, &iter, &handle))
-        MOZ_CRASH("IPC::ReadParam failed");
-
-      NoteProtocolManager(handle.mId, aMessage.routing_id());
-    }
-
-    if (MessageTargetIsDead(aMessage)) {
-      PrintSpew("Suppressing %s message to dead target\n", IPC::StringFromIPCMessageType(type));
-      return true;
-    }
     return false;
   }
 
   // Handle messages that should be sent to both the middleman and the
   // child process.
   if (// Initialization that must be performed in both processes.
       type == dom::PContent::Msg_PBrowserConstructor__ID ||
       type == dom::PContent::Msg_RegisterChrome__ID ||
@@ -112,26 +45,43 @@ HandleMessageInMiddleman(ipc::Side aSide
       type == dom::PBrowser::Msg_LoadURL__ID ||
       type == dom::PBrowser::Msg_Show__ID ||
       // May be loading devtools code that runs in the middleman process.
       type == dom::PBrowser::Msg_LoadRemoteScript__ID ||
       // May be sending a message for receipt by devtools code.
       type == dom::PBrowser::Msg_AsyncMessage__ID ||
       // Teardown that must be performed in both processes.
       type == dom::PBrowser::Msg_Destroy__ID) {
-    ipc::IProtocol::Result r =
-      dom::ContentChild::GetSingleton()->PContentChild::OnMessageReceived(aMessage);
+    dom::ContentChild* contentChild = dom::ContentChild::GetSingleton();
+
+    if (type >= dom::PBrowser::PBrowserStart && type <= dom::PBrowser::PBrowserEnd) {
+      // Ignore messages sent from the parent to browsers that do not have an
+      // actor in the middleman process. PBrowser may be allocated on either
+      // side of the IPDL channel, and when allocated by the recording child
+      // there will not be a corresponding actor in the middleman.
+      nsTArray<dom::PBrowserChild*> browsers;
+      contentChild->ManagedPBrowserChild(browsers);
+      bool found = false;
+      for (ipc::IProtocol* child : browsers) {
+        if (child->Id() == aMessage.routing_id()) {
+          found = true;
+          break;
+        }
+      }
+      if (!found) {
+        return false;
+      }
+    }
+
+    ipc::IProtocol::Result r = contentChild->PContentChild::OnMessageReceived(aMessage);
     MOZ_RELEASE_ASSERT(r == ipc::IProtocol::MsgProcessed);
     if (type == dom::PContent::Msg_SetXPCOMProcessAttributes__ID) {
       // Preferences are initialized via the SetXPCOMProcessAttributes message.
       PreferencesLoaded();
     }
-    if (type == dom::PBrowser::Msg_Destroy__ID) {
-      DestroyRoutingId(aMessage.routing_id());
-    }
     if (type == dom::PBrowser::Msg_RenderLayers__ID) {
       // Graphics are being loaded or unloaded for a tab, so update what we are
       // showing to the UI process according to the last paint performed.
       UpdateGraphicsInUIProcess(nullptr);
     }
     return false;
   }
 
@@ -155,16 +105,28 @@ HandleMessageInMiddleman(ipc::Side aSide
     ipc::IProtocol::Result r = compositorChild->OnMessageReceived(aMessage);
     MOZ_RELEASE_ASSERT(r == ipc::IProtocol::MsgProcessed);
     return true;
   }
 
   return false;
 }
 
+// Return whether a message should be sent to the recording child, even if it
+// is not currently active.
+static bool
+AlwaysForwardMessage(const IPC::Message& aMessage)
+{
+  IPC::Message::msgid_t type = aMessage.type();
+
+  // Forward close messages so that the tab shuts down properly even if it is
+  // currently replaying.
+  return type == dom::PBrowser::Msg_Destroy__ID;
+}
+
 static bool gMainThreadIsWaitingForIPDLReply = false;
 
 bool
 MainThreadIsWaitingForIPDLReply()
 {
   return gMainThreadIsWaitingForIPDLReply;
 }
 
@@ -199,17 +161,17 @@ public:
     , mOppositeMessageLoop(nullptr)
   {}
 
   virtual void RemoveManagee(int32_t, IProtocol*) override {
     MOZ_CRASH("MiddlemanProtocol::RemoveManagee");
   }
 
   static void ForwardMessageAsync(MiddlemanProtocol* aProtocol, Message* aMessage) {
-    if (ActiveChildIsRecording()) {
+    if (ActiveChildIsRecording() || AlwaysForwardMessage(*aMessage)) {
       PrintSpew("ForwardAsyncMsg %s %s %d\n",
                 (aProtocol->mSide == ipc::ChildSide) ? "Child" : "Parent",
                 IPC::StringFromIPCMessageType(aMessage->type()),
                 (int) aMessage->routing_id());
       if (!aProtocol->GetIPCChannel()->Send(aMessage)) {
         MOZ_CRASH("MiddlemanProtocol::ForwardMessageAsync");
       }
     } else {
@@ -252,17 +214,16 @@ public:
 
     MonitorAutoLock lock(*gMonitor);
     *aReply = nReply;
     gMonitor->Notify();
   }
 
   virtual Result OnMessageReceived(const Message& aMessage, Message*& aReply) override {
     MOZ_RELEASE_ASSERT(mOppositeMessageLoop);
-    MOZ_RELEASE_ASSERT(mSide == ipc::ChildSide || !MessageTargetIsDead(aMessage));
 
     Message* nMessage = new Message();
     nMessage->CopyFrom(aMessage);
     mOppositeMessageLoop->PostTask(NewRunnableFunction("ForwardMessageSync", ForwardMessageSync,
                                                        mOpposite, nMessage, &aReply));
 
     if (mSide == ipc::ChildSide) {
       AutoMarkMainThreadWaitingForIPDLReply blocked;
@@ -289,17 +250,16 @@ public:
 
     MonitorAutoLock lock(*gMonitor);
     *aReply = nReply;
     gMonitor->Notify();
   }
 
   virtual Result OnCallReceived(const Message& aMessage, Message*& aReply) override {
     MOZ_RELEASE_ASSERT(mOppositeMessageLoop);
-    MOZ_RELEASE_ASSERT(mSide == ipc::ChildSide || !MessageTargetIsDead(aMessage));
 
     Message* nMessage = new Message();
     nMessage->CopyFrom(aMessage);
     mOppositeMessageLoop->PostTask(NewRunnableFunction("ForwardCallMessage", ForwardCallMessage,
                                                        mOpposite, nMessage, &aReply));
 
     if (mSide == ipc::ChildSide) {
       AutoMarkMainThreadWaitingForIPDLReply blocked;
--- a/toolkit/recordreplay/ipc/ParentIPC.cpp
+++ b/toolkit/recordreplay/ipc/ParentIPC.cpp
@@ -182,16 +182,22 @@ void
 Shutdown()
 {
   delete gRecordingChild;
   delete gFirstReplayingChild;
   delete gSecondReplayingChild;
   _exit(0);
 }
 
+bool
+IsMiddlemanWithRecordingChild()
+{
+  return IsMiddleman() && gRecordingChild;
+}
+
 static ChildProcessInfo*
 OtherReplayingChild(ChildProcessInfo* aChild)
 {
   MOZ_RELEASE_ASSERT(!aChild->IsRecording() && gFirstReplayingChild && gSecondReplayingChild);
   return aChild == gFirstReplayingChild ? gSecondReplayingChild : gFirstReplayingChild;
 }
 
 static void
--- a/toolkit/recordreplay/ipc/ParentIPC.h
+++ b/toolkit/recordreplay/ipc/ParentIPC.h
@@ -35,16 +35,20 @@ void InitializeUIProcess(int aArgc, char
 // Get any directory where content process recordings should be saved.
 const char* SaveAllRecordingsDirectory();
 
 // Middleman process API
 
 // Get the pid of the UI process.
 base::ProcessId ParentProcessId();
 
+// Return whether this is a middleman process that forwards IPDL messages to
+// a recording child process.
+bool IsMiddlemanWithRecordingChild();
+
 // Save the recording up to the current point in execution.
 void SaveRecording(const ipc::FileDescriptor& aFile);
 
 // Get the message channel used to communicate with the UI process.
 ipc::MessageChannel* ChannelToUIProcess();
 
 // Initialize state in a middleman process.
 void InitializeMiddleman(int aArgc, char* aArgv[], base::ProcessId aParentPid,
--- a/xpcom/base/nsCycleCollector.cpp
+++ b/xpcom/base/nsCycleCollector.cpp
@@ -2723,17 +2723,19 @@ public:
       }
       aObject.mParticipant->DeleteCycleCollectable(aObject.mPointer);
     }
   }
 
   bool
   Visit(nsPurpleBuffer& aBuffer, nsPurpleBufferEntry* aEntry)
   {
-    if (mBudget) {
+    // Ignore any slice budget we have when recording/replaying, as it behaves
+    // non-deterministically.
+    if (mBudget && !recordreplay::IsRecordingOrReplaying()) {
       if (mBudget->isOverBudget()) {
         return false;
       }
       mBudget->step();
     }
 
     MOZ_ASSERT(aEntry->mObject, "Null object in purple buffer");
     if (!aEntry->mRefCnt->get()) {
--- a/xpcom/base/nsINIParser.cpp
+++ b/xpcom/base/nsINIParser.cpp
@@ -4,65 +4,21 @@
  * 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/. */
 
 // Moz headers (alphabetical)
 #include "nsCRTGlue.h"
 #include "nsError.h"
 #include "nsIFile.h"
 #include "nsINIParser.h"
-#include "mozilla/FileUtils.h" // AutoFILE
 #include "mozilla/ResultExtensions.h"
 #include "mozilla/URLPreloader.h"
 
-// System headers (alphabetical)
-#include <stdio.h>
-#include <stdlib.h>
-#ifdef XP_WIN
-#include <windows.h>
-#endif
-
 using namespace mozilla;
 
-#ifdef XP_WIN
-inline FILE*
-TS_tfopen(const char* aPath, const wchar_t* aMode)
-{
-  wchar_t wPath[MAX_PATH];
-  MultiByteToWideChar(CP_UTF8, 0, aPath, -1, wPath, MAX_PATH);
-  return _wfopen(wPath, aMode);
-}
-#else
-inline FILE*
-TS_tfopen(const char* aPath, const char* aMode)
-{
-  return fopen(aPath, aMode);
-}
-#endif
-
-// Stack based FILE wrapper to ensure that fclose is called, copied from
-// toolkit/mozapps/update/updater/readstrings.cpp
-
-class AutoFILE
-{
-public:
-  explicit AutoFILE(FILE* aFp = nullptr) : fp_(aFp) {}
-  ~AutoFILE()
-  {
-    if (fp_) {
-      fclose(fp_);
-    }
-  }
-  operator FILE*() { return fp_; }
-  FILE** operator&() { return &fp_; }
-  void operator=(FILE* aFp) { fp_ = aFp; }
-private:
-  FILE* fp_;
-};
-
 nsresult
 nsINIParser::Init(nsIFile* aFile)
 {
   nsCString result;
   MOZ_TRY_VAR(result, URLPreloader::ReadFile(aFile));
 
   return InitFromString(result);
 }
@@ -70,36 +26,37 @@ nsINIParser::Init(nsIFile* aFile)
 static const char kNL[] = "\r\n";
 static const char kEquals[] = "=";
 static const char kWhitespace[] = " \t";
 static const char kRBracket[] = "]";
 
 nsresult
 nsINIParser::InitFromString(const nsCString& aStr)
 {
+  nsCString fileContents;
   char* buffer;
 
   if (StringHead(aStr, 3) == "\xEF\xBB\xBF") {
     // Someone set us up the Utf-8 BOM
     // This case is easy, since we assume that BOM-less
     // files are Utf-8 anyway.  Just skip the BOM and process as usual.
-    mFileContents.Append(aStr);
-    buffer = mFileContents.BeginWriting() + 3;
+    fileContents.Append(aStr);
+    buffer = fileContents.BeginWriting() + 3;
   } else {
     if (StringHead(aStr, 2) == "\xFF\xFE") {
       // Someone set us up the Utf-16LE BOM
       nsDependentSubstring str(reinterpret_cast<const char16_t*>(aStr.get()),
                                aStr.Length() / 2);
 
-      AppendUTF16toUTF8(Substring(str, 1), mFileContents);
+      AppendUTF16toUTF8(Substring(str, 1), fileContents);
     } else {
-      mFileContents.Append(aStr);
+      fileContents.Append(aStr);
     }
 
-    buffer = mFileContents.BeginWriting();
+    buffer = fileContents.BeginWriting();
   }
 
   char* currSection = nullptr;
 
   // outer loop tokenizes into lines
   while (char* token = NS_strtok(kNL, &buffer)) {
     if (token[0] == '#' || token[0] == ';') { // it's a comment
       continue;
@@ -133,53 +90,59 @@ nsINIParser::InitFromString(const nsCStr
     }
 
     char* key = token;
     char* e = NS_strtok(kEquals, &token);
     if (!e || !token) {
       continue;
     }
 
-    INIValue* v;
-    if (!mSections.Get(currSection, &v)) {
-      v = new INIValue(key, token);
-      if (!v) {
-        return NS_ERROR_OUT_OF_MEMORY;
-      }
-
-      mSections.Put(currSection, v);
-      continue;
-    }
-
-    // Check whether this key has already been specified; overwrite
-    // if so, or append if not.
-    while (v) {
-      if (!strcmp(key, v->key)) {
-        v->value = token;
-        break;
-      }
-      if (!v->next) {
-        v->next = MakeUnique<INIValue>(key, token);
-        if (!v->next) {
-          return NS_ERROR_OUT_OF_MEMORY;
-        }
-        break;
-      }
-      v = v->next.get();
-    }
-    NS_ASSERTION(v, "v should never be null coming out of this loop");
+    SetString(currSection, key, token);
   }
 
   return NS_OK;
 }
 
+bool
+nsINIParser::IsValidSection(const char* aSection)
+{
+  if (aSection[0] == '\0') {
+    return false;
+  }
+
+  const char* found = strpbrk(aSection, "\r\n[]");
+  return found == nullptr;
+}
+
+bool
+nsINIParser::IsValidKey(const char* aKey)
+{
+  if (aKey[0] == '\0') {
+    return false;
+  }
+
+  const char* found = strpbrk(aKey, "\r\n=");
+  return found == nullptr;
+}
+
+bool
+nsINIParser::IsValidValue(const char* aValue)
+{
+  const char* found = strpbrk(aValue, "\r\n");
+  return found == nullptr;
+}
+
 nsresult
 nsINIParser::GetString(const char* aSection, const char* aKey,
                        nsACString& aResult)
 {
+  if (!IsValidSection(aSection) || !IsValidKey(aKey)) {
+    return NS_ERROR_INVALID_ARG;
+  }
+
   INIValue* val;
   mSections.Get(aSection, &val);
 
   while (val) {
     if (strcmp(val->key, aKey) == 0) {
       aResult.Assign(val->value);
       return NS_OK;
     }
@@ -189,16 +152,20 @@ nsINIParser::GetString(const char* aSect
 
   return NS_ERROR_FAILURE;
 }
 
 nsresult
 nsINIParser::GetString(const char* aSection, const char* aKey,
                        char* aResult, uint32_t aResultLen)
 {
+  if (!IsValidSection(aSection) || !IsValidKey(aKey)) {
+    return NS_ERROR_INVALID_ARG;
+  }
+
   INIValue* val;
   mSections.Get(aSection, &val);
 
   while (val) {
     if (strcmp(val->key, aKey) == 0) {
       strncpy(aResult, val->value, aResultLen);
       aResult[aResultLen - 1] = '\0';
       if (strlen(val->value) >= aResultLen) {
@@ -224,21 +191,136 @@ nsINIParser::GetSections(INISectionCallb
   }
   return NS_OK;
 }
 
 nsresult
 nsINIParser::GetStrings(const char* aSection,
                         INIStringCallback aCB, void* aClosure)
 {
+  if (!IsValidSection(aSection)) {
+    return NS_ERROR_INVALID_ARG;
+  }
+
   INIValue* val;
 
   for (mSections.Get(aSection, &val);
        val;
        val = val->next.get()) {
 
     if (!aCB(val->key, val->value, aClosure)) {
       return NS_OK;
     }
   }
 
   return NS_OK;
 }
+
+nsresult
+nsINIParser::SetString(const char* aSection, const char* aKey, const char* aValue)
+{
+  if (!IsValidSection(aSection) || !IsValidKey(aKey) || !IsValidValue(aValue)) {
+    return NS_ERROR_INVALID_ARG;
+  }
+
+  INIValue* v;
+  if (!mSections.Get(aSection, &v)) {
+    v = new INIValue(aKey, aValue);
+
+    mSections.Put(aSection, v);
+    return NS_OK;
+  }
+
+  // Check whether this key has already been specified; overwrite
+  // if so, or append if not.
+  while (v) {
+    if (!strcmp(aKey, v->key)) {
+      v->SetValue(aValue);
+      break;
+    }
+    if (!v->next) {
+      v->next = MakeUnique<INIValue>(aKey, aValue);
+      break;
+    }
+    v = v->next.get();
+  }
+  NS_ASSERTION(v, "v should never be null coming out of this loop");
+
+  return NS_OK;
+}
+
+nsresult
+nsINIParser::DeleteString(const char* aSection, const char* aKey)
+{
+  if (!IsValidSection(aSection) || !IsValidKey(aKey)) {
+    return NS_ERROR_INVALID_ARG;
+  }
+
+  INIValue* val;
+  if (!mSections.Get(aSection, &val)) {
+    return NS_ERROR_FAILURE;
+  }
+
+  // Special case the first result
+  if (strcmp(val->key, aKey) == 0) {
+    if (!val->next) {
+      mSections.Remove(aSection);
+    } else {
+      mSections.Put(aSection, val->next.release());
+      delete val;
+    }
+    return NS_OK;
+  }
+
+  while (val->next) {
+    if (strcmp(val->next->key, aKey) == 0) {
+      val->next = std::move(val->next->next);
+
+      return NS_OK;
+    }
+
+    val = val->next.get();
+  }
+
+  return NS_ERROR_FAILURE;
+}
+
+nsresult
+nsINIParser::DeleteSection(const char* aSection)
+{
+  if (!IsValidSection(aSection)) {
+    return NS_ERROR_INVALID_ARG;
+  }
+
+  if (!mSections.Remove(aSection)) {
+    return NS_ERROR_FAILURE;
+  }
+  return NS_OK;
+}
+
+nsresult
+nsINIParser::WriteToFile(nsIFile *aFile) {
+  nsCString buffer;
+
+  for (auto iter = mSections.Iter(); !iter.Done(); iter.Next()) {
+    buffer.AppendPrintf("[%s]\n", iter.Key());
+    INIValue* val = iter.Data();
+    while (val) {
+      buffer.AppendPrintf("%s=%s\n", val->key, val->value);
+      val = val->next.get();
+    }
+    buffer.AppendLiteral("\n");
+  }
+
+  FILE* writeFile;
+  nsresult rv = aFile->OpenANSIFileDesc("w", &writeFile);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  unsigned int length = buffer.Length();
+
+  if (fwrite(buffer.get(), sizeof(char), length, writeFile) != length) {
+      fclose(writeFile);
+      return NS_ERROR_UNEXPECTED;
+  }
+
+  fclose(writeFile);
+  return NS_OK;
+}
--- a/xpcom/base/nsINIParser.h
+++ b/xpcom/base/nsINIParser.h
@@ -82,29 +82,79 @@ public:
    *         large enough for the data. aResult will be filled with as
    *         much data as possible.
    *
    * @see GetString [1]
    */
   nsresult GetString(const char* aSection, const char* aKey,
                      char* aResult, uint32_t aResultLen);
 
+  /**
+   * Sets the value of the specified key in the specified section. The section
+   * is created if it does not already exist.
+   *
+   * @oaram aSection      section name
+   * @param aKey          key name
+   * @param aValue        the value to set
+   */
+  nsresult SetString(const char* aSection, const char* aKey, const char* aValue);
+
+  /**
+   * Deletes the value of the specified key in the specified section.
+   *
+   * @param aSection      section name
+   * @param aKey          key name
+   *
+   * @throws NS_ERROR_FAILURE if the string was not set.
+   */
+  nsresult DeleteString(const char* aSection, const char* aKey);
+
+  /**
+   * Deletes the specified section.
+   *
+   * @param aSection      section name
+   *
+   * @throws NS_ERROR_FAILURE if the section did not exist.
+   */
+  nsresult DeleteSection(const char* aSection);
+
+  /**
+   * Writes the ini data to disk.
+   * @param aFile         the file to write to
+   * @throws NS_ERROR_FAILURE on failure.
+   */
+  nsresult WriteToFile(nsIFile *aFile);
+
 private:
   struct INIValue
   {
     INIValue(const char* aKey, const char* aValue)
-      : key(aKey)
-      , value(aValue)
+      : key(strdup(aKey))
+      , value(strdup(aValue))
+    {
+    }
+
+    ~INIValue()
     {
+      delete key;
+      delete value;
+    }
+
+    void SetValue(const char* aValue) {
+      delete value;
+      value = strdup(aValue);
     }
 
     const char* key;
     const char* value;
     mozilla::UniquePtr<INIValue> next;
   };
 
-  nsClassHashtable<nsDepCharHashKey, INIValue> mSections;
-  nsCString mFileContents;
+  nsClassHashtable<nsCharPtrHashKey, INIValue> mSections;
 
   nsresult InitFromString(const nsCString& aStr);
+
+  bool IsValidSection(const char* aSection);
+  bool IsValidKey(const char* aKey);
+  bool IsValidValue(const char* aValue);
 };
 
 #endif /* nsINIParser_h__ */
--- a/xpcom/ds/moz.build
+++ b/xpcom/ds/moz.build
@@ -124,21 +124,16 @@ SOURCES += [
 ]
 if CONFIG['CC_TYPE'] == 'msvc':
     # Needed for gGkAtoms.
     SOURCES['nsGkAtoms.cpp'].flags += [
         '-constexpr:steps300000',
         '-Zc:externConstexpr',
     ]
 
-EXTRA_COMPONENTS += [
-    'nsINIProcessor.js',
-    'nsINIProcessor.manifest',
-]
-
 LOCAL_INCLUDES += [
     '../io',
 ]
 
 GENERATED_FILES += ['nsGkAtomList.h']
 GENERATED_FILES['nsGkAtomList.h'].script = 'StaticAtoms.py:generate_nsgkatomlist_h'
 GENERATED_FILES['nsGkAtomList.h'].inputs = ['Atom.py', 'HTMLAtoms.py']
 
--- a/xpcom/ds/nsIINIParser.idl
+++ b/xpcom/ds/nsIINIParser.idl
@@ -25,34 +25,26 @@ interface nsIINIParser : nsISupports
    */
   AUTF8String getString(in AUTF8String aSection, in AUTF8String aKey);
 };
 
 [scriptable, uuid(b67bb24b-31a3-4a6a-a5d9-0485c9af5a04)]
 interface nsIINIParserWriter : nsISupports
 {
   /**
-   * Windows and the NSIS installer code sometimes expect INI files to be in
-   * UTF-16 encoding. On Windows only, this flag to writeFile can be used to
-   * change the encoding from its default UTF-8.
-   */
-  const unsigned long WRITE_UTF16 = 0x1;
-
-  /**
    * Set the value of a string for a particular section and key.
    */
   void setString(in AUTF8String aSection, in AUTF8String aKey, in AUTF8String aValue);
 
   /**
    * Write to the INI file.
    */
-  void writeFile([optional] in nsIFile aINIFile,
-                 [optional] in unsigned long aFlags);
+  void writeFile(in nsIFile aINIFile);
 };
 
 [scriptable, uuid(ccae7ea5-1218-4b51-aecb-c2d8ecd46af9)]
 interface nsIINIParserFactory : nsISupports
 {
   /**
    * Create an iniparser instance from a local file.
    */
-  nsIINIParser createINIParser(in nsIFile aINIFile);
+  nsIINIParser createINIParser([optional] in nsIFile aINIFile);
 };
--- a/xpcom/ds/nsINIParserImpl.cpp
+++ b/xpcom/ds/nsINIParserImpl.cpp
@@ -8,27 +8,30 @@
 
 #include "nsINIParser.h"
 #include "nsStringEnumerator.h"
 #include "nsTArray.h"
 #include "mozilla/Attributes.h"
 
 class nsINIParserImpl final
   : public nsIINIParser
+  , public nsIINIParserWriter
 {
   ~nsINIParserImpl() {}
 
 public:
   NS_DECL_ISUPPORTS
   NS_DECL_NSIINIPARSER
+  NS_DECL_NSIINIPARSERWRITER
 
   nsresult Init(nsIFile* aINIFile) { return mParser.Init(aINIFile); }
 
 private:
   nsINIParser mParser;
+  bool ContainsNull(const nsACString& aStr);
 };
 
 NS_IMPL_ISUPPORTS(nsINIParserFactory,
                   nsIINIParserFactory,
                   nsIFactory)
 
 NS_IMETHODIMP
 nsINIParserFactory::CreateINIParser(nsIFile* aINIFile,
@@ -36,23 +39,25 @@ nsINIParserFactory::CreateINIParser(nsIF
 {
   *aResult = nullptr;
 
   RefPtr<nsINIParserImpl> p(new nsINIParserImpl());
   if (!p) {
     return NS_ERROR_OUT_OF_MEMORY;
   }
 
-  nsresult rv = p->Init(aINIFile);
-
-  if (NS_SUCCEEDED(rv)) {
-    NS_ADDREF(*aResult = p);
+  if (aINIFile) {
+    nsresult rv = p->Init(aINIFile);
+    if (NS_FAILED(rv)) {
+      return rv;
+    }
   }
 
-  return rv;
+   p.forget(aResult);
+  return NS_OK;
 }
 
 NS_IMETHODIMP
 nsINIParserFactory::CreateInstance(nsISupports* aOuter,
                                    REFNSIID aIID,
                                    void** aResult)
 {
   if (NS_WARN_IF(aOuter)) {
@@ -65,17 +70,23 @@ nsINIParserFactory::CreateInstance(nsISu
 
 NS_IMETHODIMP
 nsINIParserFactory::LockFactory(bool aLock)
 {
   return NS_OK;
 }
 
 NS_IMPL_ISUPPORTS(nsINIParserImpl,
-                  nsIINIParser)
+                  nsIINIParser,
+                  nsIINIParserWriter)
+
+bool
+nsINIParserImpl::ContainsNull(const nsACString& aStr) {
+  return aStr.CountChar('\0') > 0;
+}
 
 static bool
 SectionCB(const char* aSection, void* aClosure)
 {
   nsTArray<nsCString>* strings = static_cast<nsTArray<nsCString>*>(aClosure);
   strings->AppendElement()->Assign(aSection);
   return true;
 }
@@ -107,16 +118,20 @@ KeyCB(const char* aKey, const char* aVal
   strings->AppendElement()->Assign(aKey);
   return true;
 }
 
 NS_IMETHODIMP
 nsINIParserImpl::GetKeys(const nsACString& aSection,
                          nsIUTF8StringEnumerator** aResult)
 {
+  if (ContainsNull(aSection)) {
+    return NS_ERROR_INVALID_ARG;
+  }
+
   nsTArray<nsCString>* strings = new nsTArray<nsCString>;
   if (!strings) {
     return NS_ERROR_OUT_OF_MEMORY;
   }
 
   nsresult rv = mParser.GetStrings(PromiseFlatCString(aSection).get(),
                                    KeyCB, strings);
   if (NS_SUCCEEDED(rv)) {
@@ -131,12 +146,36 @@ nsINIParserImpl::GetKeys(const nsACStrin
 
 }
 
 NS_IMETHODIMP
 nsINIParserImpl::GetString(const nsACString& aSection,
                            const nsACString& aKey,
                            nsACString& aResult)
 {
+  if (ContainsNull(aSection) || ContainsNull(aKey)) {
+    return NS_ERROR_INVALID_ARG;
+  }
+
   return mParser.GetString(PromiseFlatCString(aSection).get(),
                            PromiseFlatCString(aKey).get(),
                            aResult);
 }
+
+NS_IMETHODIMP
+nsINIParserImpl::SetString(const nsACString& aSection,
+                           const nsACString& aKey,
+                           const nsACString& aValue)
+{
+  if (ContainsNull(aSection) || ContainsNull(aKey) || ContainsNull(aValue)) {
+    return NS_ERROR_INVALID_ARG;
+  }
+
+  return mParser.SetString(PromiseFlatCString(aSection).get(),
+                           PromiseFlatCString(aKey).get(),
+                           PromiseFlatCString(aValue).get());
+}
+
+NS_IMETHODIMP
+nsINIParserImpl::WriteFile(nsIFile* aINIFile)
+{
+  return mParser.WriteToFile(aINIFile);
+}
deleted file mode 100644
--- a/xpcom/ds/nsINIProcessor.js
+++ /dev/null
@@ -1,187 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-
-ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
-
-function INIProcessorFactory() {
-}
-
-INIProcessorFactory.prototype = {
-    classID: Components.ID("{6ec5f479-8e13-4403-b6ca-fe4c2dca14fd}"),
-    QueryInterface: ChromeUtils.generateQI([Ci.nsIINIParserFactory]),
-
-    createINIParser(aINIFile) {
-        return new INIProcessor(aINIFile);
-    }
-
-}; // end of INIProcessorFactory implementation
-
-const MODE_WRONLY = 0x02;
-const MODE_CREATE = 0x08;
-const MODE_TRUNCATE = 0x20;
-
-// nsIINIParser implementation
-function INIProcessor(aFile) {
-    this._iniFile = aFile;
-    this._iniData = {};
-    this._readFile();
-}
-
-INIProcessor.prototype = {
-    QueryInterface: ChromeUtils.generateQI([Ci.nsIINIParser, Ci.nsIINIParserWriter]),
-
-    __utf8Converter: null, // UCS2 <--> UTF8 string conversion
-    get _utf8Converter() {
-        if (!this.__utf8Converter) {
-            this.__utf8Converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"].
-                                  createInstance(Ci.nsIScriptableUnicodeConverter);
-            this.__utf8Converter.charset = "UTF-8";
-        }
-        return this.__utf8Converter;
-    },
-
-    __utf16leConverter: null, // UCS2 <--> UTF16LE string conversion
-    get _utf16leConverter() {
-        if (!this.__utf16leConverter) {
-            this.__utf16leConverter = Cc["@mozilla.org/intl/scriptableunicodeconverter"].
-                                  createInstance(Ci.nsIScriptableUnicodeConverter);
-            this.__utf16leConverter.charset = "UTF-16LE";
-        }
-        return this.__utf16leConverter;
-    },
-
-    _utfConverterReset() {
-        this.__utf8Converter = null;
-        this.__utf16leConverter = null;
-    },
-
-    _iniFile: null,
-    _iniData: null,
-
-    /*
-     * Reads the INI file and stores the data internally.
-     */
-    _readFile() {
-        // If file doesn't exist, there's nothing to do.
-        if (!this._iniFile.exists() || 0 == this._iniFile.fileSize)
-            return;
-
-        let iniParser = Cc["@mozilla.org/xpcom/ini-parser-factory;1"]
-            .getService(Ci.nsIINIParserFactory).createINIParser(this._iniFile);
-        for (let section of XPCOMUtils.IterStringEnumerator(iniParser.getSections())) {
-            this._iniData[section] = {};
-            for (let key of XPCOMUtils.IterStringEnumerator(iniParser.getKeys(section))) {
-                this._iniData[section][key] = iniParser.getString(section, key);
-            }
-        }
-    },
-
-    // nsIINIParser
-
-    getSections() {
-        let sections = [];
-        for (let section in this._iniData)
-            sections.push(section);
-        return new stringEnumerator(sections);
-    },
-
-    getKeys(aSection) {
-        let keys = [];
-        if (aSection in this._iniData)
-            for (let key in this._iniData[aSection])
-                keys.push(key);
-        return new stringEnumerator(keys);
-    },
-
-    getString(aSection, aKey) {
-        if (!(aSection in this._iniData))
-            throw Cr.NS_ERROR_FAILURE;
-        if (!(aKey in this._iniData[aSection]))
-            throw Cr.NS_ERROR_FAILURE;
-        return this._iniData[aSection][aKey];
-    },
-
-
-    // nsIINIParserWriter
-
-    setString(aSection, aKey, aValue) {
-        const isSectionIllegal = /[\0\r\n\[\]]/;
-        const isKeyValIllegal  = /[\0\r\n=]/;
-
-        if (isSectionIllegal.test(aSection))
-            throw Components.Exception("bad character in section name",
-                                       Cr.ERROR_ILLEGAL_VALUE);
-        if (isKeyValIllegal.test(aKey) || isKeyValIllegal.test(aValue))
-            throw Components.Exception("bad character in key/value",
-                                       Cr.ERROR_ILLEGAL_VALUE);
-
-        if (!(aSection in this._iniData))
-            this._iniData[aSection] = {};
-
-        this._iniData[aSection][aKey] = aValue;
-    },
-
-    writeFile(aFile, aFlags) {
-
-        let converter;
-        function writeLine(data) {
-            data += "\n";
-            data = converter.ConvertFromUnicode(data);
-            data += converter.Finish();
-            outputStream.write(data, data.length);
-        }
-
-        if (!aFile)
-            aFile = this._iniFile;
-
-        let safeStream = Cc["@mozilla.org/network/safe-file-output-stream;1"].
-                         createInstance(Ci.nsIFileOutputStream);
-        safeStream.init(aFile, MODE_WRONLY | MODE_CREATE | MODE_TRUNCATE,
-                        0o600, null);
-
-        var outputStream = Cc["@mozilla.org/network/buffered-output-stream;1"].
-                           createInstance(Ci.nsIBufferedOutputStream);
-        outputStream.init(safeStream, 8192);
-        outputStream.QueryInterface(Ci.nsISafeOutputStream); // for .finish()
-
-        if (Ci.nsIINIParserWriter.WRITE_UTF16 == aFlags
-         && "nsIWindowsRegKey" in Ci) {
-            outputStream.write("\xFF\xFE", 2);
-            converter = this._utf16leConverter;
-        } else {
-            converter = this._utf8Converter;
-        }
-
-        for (let section in this._iniData) {
-            writeLine("[" + section + "]");
-            for (let key in this._iniData[section]) {
-                writeLine(key + "=" + this._iniData[section][key]);
-            }
-        }
-
-        outputStream.finish();
-    }
-};
-
-function stringEnumerator(stringArray) {
-    this._strings = stringArray;
-}
-stringEnumerator.prototype = {
-    QueryInterface: ChromeUtils.generateQI([Ci.nsIUTF8StringEnumerator]),
-
-    _strings: null,
-    _enumIndex: 0,
-
-    hasMore() {
-        return (this._enumIndex < this._strings.length);
-    },
-
-    getNext() {
-        return this._strings[this._enumIndex++];
-    }
-};
-
-var component = [INIProcessorFactory];
-this.NSGetFactory = XPCOMUtils.generateNSGetFactory(component);
deleted file mode 100644
--- a/xpcom/ds/nsINIProcessor.manifest
+++ /dev/null
@@ -1,2 +0,0 @@
-component {6ec5f479-8e13-4403-b6ca-fe4c2dca14fd} nsINIProcessor.js
-contract @mozilla.org/xpcom/ini-processor-factory;1 {6ec5f479-8e13-4403-b6ca-fe4c2dca14fd}
--- a/xpcom/io/CocoaFileUtils.h
+++ b/xpcom/io/CocoaFileUtils.h
@@ -17,23 +17,28 @@
 namespace CocoaFileUtils {
 
 nsresult RevealFileInFinder(CFURLRef aUrl);
 nsresult OpenURL(CFURLRef aUrl);
 nsresult GetFileCreatorCode(CFURLRef aUrl, OSType* aCreatorCode);
 nsresult SetFileCreatorCode(CFURLRef aUrl, OSType aCreatorCode);
 nsresult GetFileTypeCode(CFURLRef aUrl, OSType* aTypeCode);
 nsresult SetFileTypeCode(CFURLRef aUrl, OSType aTypeCode);
+
+// Can be called off of the main thread.
 void     AddOriginMetadataToFile(const CFStringRef filePath,
                                  const CFURLRef sourceURL,
                                  const CFURLRef referrerURL);
+// Can be called off of the main thread.
 void     AddQuarantineMetadataToFile(const CFStringRef filePath,
                                      const CFURLRef sourceURL,
                                      const CFURLRef referrerURL,
                                      const bool isFromWeb,
                                      const bool createProps=false);
+// Can be called off of the main thread.
 void CopyQuarantineReferrerUrl(const CFStringRef aFilePath,
                                nsAString& aReferrer);
+
 CFURLRef GetTemporaryFolderCFURLRef();
 
 } // namespace CocoaFileUtils
 
 #endif
--- a/xpcom/io/CocoaFileUtils.mm
+++ b/xpcom/io/CocoaFileUtils.mm
@@ -139,16 +139,17 @@ nsresult SetFileTypeCode(CFURLRef url, O
   NSDictionary* dict = [NSDictionary dictionaryWithObject:[NSNumber numberWithUnsignedLong:typeCode] forKey:NSFileHFSTypeCode];
   BOOL success = [[NSFileManager defaultManager] setAttributes:dict ofItemAtPath:[(NSURL*)url path] error:nil];
   [ap release];
   return (success ? NS_OK : NS_ERROR_FAILURE);
 
   NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
 }
 
+// Can be called off of the main thread.
 void AddOriginMetadataToFile(const CFStringRef filePath,
                              const CFURLRef sourceURL,
                              const CFURLRef referrerURL) {
   typedef OSStatus (*MDItemSetAttribute_type)(MDItemRef, CFStringRef, CFTypeRef);
   static MDItemSetAttribute_type mdItemSetAttributeFunc = NULL;
 
   static bool did_symbol_lookup = false;
   if (!did_symbol_lookup) {
@@ -188,23 +189,25 @@ void AddOriginMetadataToFile(const CFStr
   }
 
   mdItemSetAttributeFunc(mdItem, kMDItemWhereFroms, list);
 
   ::CFRelease(list);
   ::CFRelease(mdItem);
 }
 
+// Can be called off of the main thread.
 CFStringRef GetQuarantinePropKey() {
   if (nsCocoaFeatures::OnYosemiteOrLater()) {
     return kCFURLQuarantinePropertiesKey;
   }
   return kLSItemQuarantineProperties;
 }
 
+// Can be called off of the main thread.
 CFMutableDictionaryRef CreateQuarantineDictionary(const CFURLRef aFileURL,
                                                   const bool aCreateProps) {
   // The properties key changed in 10.10:
   CFDictionaryRef quarantineProps = NULL;
   if (aCreateProps) {
     quarantineProps = ::CFDictionaryCreate(NULL, NULL, NULL, 0,
                                            &kCFTypeDictionaryKeyCallBacks,
                                            &kCFTypeDictionaryValueCallBacks);
@@ -230,16 +233,17 @@ CFMutableDictionaryRef CreateQuarantineD
   CFMutableDictionaryRef mutQuarantineProps =
     ::CFDictionaryCreateMutableCopy(kCFAllocatorDefault, 0,
                                     (CFDictionaryRef)quarantineProps);
   ::CFRelease(quarantineProps);
 
   return mutQuarantineProps;
 }
 
+// Can be called off of the main thread.
 void AddQuarantineMetadataToFile(const CFStringRef filePath,
                                  const CFURLRef sourceURL,
                                  const CFURLRef referrerURL,
                                  const bool isFromWeb,
                                  const bool createProps /* = false */) {
   CFURLRef fileURL = ::CFURLCreateWithFileSystemPath(kCFAllocatorDefault,
                                                      filePath,
                                                      kCFURLPOSIXPathStyle,
@@ -279,16 +283,17 @@ void AddQuarantineMetadataToFile(const C
                                    GetQuarantinePropKey(),
                                    mutQuarantineProps,
                                    NULL);
 
   ::CFRelease(fileURL);
   ::CFRelease(mutQuarantineProps);
 }
 
+// Can be called off of the main thread.
 void CopyQuarantineReferrerUrl(const CFStringRef aFilePath,
                                nsAString& aReferrer)
 {
   CFURLRef fileURL = ::CFURLCreateWithFileSystemPath(kCFAllocatorDefault,
                                                      aFilePath,
                                                      kCFURLPOSIXPathStyle,
                                                      false);
 
new file mode 100644
--- /dev/null
+++ b/xpcom/tests/unit/data/iniparser17.ini
@@ -0,0 +1,7 @@
+[section]
+key=
+
+[]
+
+[empty]
+=foo
rename from xpcom/tests/unit/test_iniProcessor.js
rename to xpcom/tests/unit/test_iniParser.js
--- a/xpcom/tests/unit/test_iniProcessor.js
+++ b/xpcom/tests/unit/test_iniParser.js
@@ -84,17 +84,17 @@ var testdata = [
                     { section1: { name1: "value1", name2: "value2" },
                       section2: { name1: "value1", name2: "foopy"  }} },
     { filename: "data/iniparser15.ini", reference:
                     { section1: { name1: "newValue1" },
                       section2: { name1: "foopy"     }} },
     { filename: "data/iniparser16.ini", reference:
                     { "☺♫": { "♫": "☻", "♪": "♥"  },
                        "☼": { "♣": "♠", "♦": "♥"  }} },
-
+    { filename: "data/iniparser17.ini", reference: { section: { key: "" } } },
     ];
 
     testdata.push( { filename: "data/iniparser01-utf8BOM.ini",
                      reference: testdata[0].reference } );
     testdata.push( { filename: "data/iniparser02-utf8BOM.ini",
                      reference: testdata[1].reference } );
     testdata.push( { filename: "data/iniparser03-utf8BOM.ini",
                      reference: testdata[2].reference } );
@@ -160,17 +160,17 @@ var testdata = [
                          reference: testdata[13].reference } );
         testdata.push( { filename: "data/iniparser15-utf16leBOM.ini",
                          reference: testdata[14].reference } );
         testdata.push( { filename: "data/iniparser16-utf16leBOM.ini",
                          reference: testdata[15].reference } );
     }
 
 /* ========== 0 ========== */
-factory = Cc["@mozilla.org/xpcom/ini-processor-factory;1"].
+factory = Cc["@mozilla.org/xpcom/ini-parser-factory;1"].
           getService(Ci.nsIINIParserFactory);
 Assert.ok(!!factory);
 
 // Test reading from a variety of files. While we're at it, write out each one
 // and read it back to ensure that nothing changed.
 while (testnum < testdata.length) {
     dump("\nINFO | test #" + ++testnum);
     let filename = testdata[testnum - 1].filename;
@@ -196,31 +196,38 @@ dump("INFO | test #" + ++testnum + "\n")
 
 // test writing to a new file.
 var newfile = do_get_file("data/");
 newfile.append("nonexistent-file.ini");
 if (newfile.exists())
     newfile.remove(false);
 Assert.ok(!newfile.exists());
 
-var parser = factory.createINIParser(newfile);
+try {
+    var parser = factory.createINIParser(newfile);
+    Assert.ok(false, "Should have thrown an exception");
+} catch (e) {
+    Assert.equal(e.result, Cr.NS_ERROR_FILE_NOT_FOUND, "Caught a file not found exception");
+}
+parser = factory.createINIParser();
 Assert.ok(!!parser);
 Assert.ok(parser instanceof Ci.nsIINIParserWriter);
 checkParserOutput(parser, {});
-parser.writeFile();
+parser.writeFile(newfile);
 Assert.ok(newfile.exists());
 
 // test adding a new section and new key
 parser.setString("section", "key", "value");
-parser.writeFile();
+parser.setString("section", "key2", "");
+parser.writeFile(newfile);
 Assert.ok(newfile.exists());
-checkParserOutput(parser, {section: {key: "value"} });
+checkParserOutput(parser, {section: {key: "value", key2: ""} });
 // read it in again, check for same data.
 parser = parserForFile("data/nonexistent-file.ini");
-checkParserOutput(parser, {section: {key: "value"} });
+checkParserOutput(parser, {section: {key: "value", key2: ""} });
 // cleanup after the test
 newfile.remove(false);
 
 dump("INFO | test #" + ++testnum + "\n");
 
 // test modifying a existing key's value (in an existing section)
 parser = parserForFile("data/iniparser09.ini");
 checkParserOutput(parser, {section1: {name1: "value1"} });
@@ -228,59 +235,80 @@ checkParserOutput(parser, {section1: {na
 Assert.ok(parser instanceof Ci.nsIINIParserWriter);
 parser.setString("section1", "name1", "value2");
 checkParserOutput(parser, {section1: {name1: "value2"} });
 
 dump("INFO | test #" + ++testnum + "\n");
 
 // test trying to set illegal characters
 var caughtError;
-caughtError = false;
+caughtError = null;
 checkParserOutput(parser, {section1: {name1: "value2"} });
 
 // Bad characters in section name
-try { parser.SetString("bad\0", "ok", "ok"); } catch (e) { caughtError = true; }
+try { parser.setString("bad\0", "ok", "ok"); } catch (e) { caughtError = e; }
 Assert.ok(caughtError);
-caughtError = false;
-try { parser.SetString("bad\r", "ok", "ok"); } catch (e) { caughtError = true; }
+Assert.equal(caughtError.result, Cr.NS_ERROR_INVALID_ARG);
+caughtError = null;
+try { parser.setString("bad\r", "ok", "ok"); } catch (e) { caughtError = e; }
+Assert.ok(caughtError);
+Assert.equal(caughtError.result, Cr.NS_ERROR_INVALID_ARG);
+caughtError = null;
+try { parser.setString("bad\n", "ok", "ok"); } catch (e) { caughtError = e; }
 Assert.ok(caughtError);
-caughtError = false;
-try { parser.SetString("bad\n", "ok", "ok"); } catch (e) { caughtError = true; }
+Assert.equal(caughtError.result, Cr.NS_ERROR_INVALID_ARG);
+caughtError = null;
+try { parser.setString("bad[", "ok", "ok"); } catch (e) { caughtError = e; }
 Assert.ok(caughtError);
-caughtError = false;
-try { parser.SetString("bad[", "ok", "ok"); } catch (e) { caughtError = true; }
+Assert.equal(caughtError.result, Cr.NS_ERROR_INVALID_ARG);
+caughtError = null;
+try { parser.setString("bad]", "ok", "ok"); } catch (e) { caughtError = e; }
 Assert.ok(caughtError);
-caughtError = false;
-try { parser.SetString("bad]", "ok", "ok"); } catch (e) { caughtError = true; }
+Assert.equal(caughtError.result, Cr.NS_ERROR_INVALID_ARG);
+caughtError = null;
+try { parser.setString("", "ok", "ok"); } catch (e) { caughtError = e; }
 Assert.ok(caughtError);
+Assert.equal(caughtError.result, Cr.NS_ERROR_INVALID_ARG);
 
 // Bad characters in key name
-caughtError = false;
-try { parser.SetString("ok", "bad\0", "ok"); } catch (e) { caughtError = true; }
+caughtError = null;
+try { parser.setString("ok", "bad\0", "ok"); } catch (e) { caughtError = e; }
 Assert.ok(caughtError);
-caughtError = false;
-try { parser.SetString("ok", "bad\r", "ok"); } catch (e) { caughtError = true; }
+Assert.equal(caughtError.result, Cr.NS_ERROR_INVALID_ARG);
+caughtError = null;
+try { parser.setString("ok", "bad\r", "ok"); } catch (e) { caughtError = e; }
 Assert.ok(caughtError);
-caughtError = false;
-try { parser.SetString("ok", "bad\n", "ok"); } catch (e) { caughtError = true; }
+Assert.equal(caughtError.result, Cr.NS_ERROR_INVALID_ARG);
+caughtError = null;
+try { parser.setString("ok", "bad\n", "ok"); } catch (e) { caughtError = e; }
 Assert.ok(caughtError);
-caughtError = false;
-try { parser.SetString("ok", "bad=", "ok"); } catch (e) { caughtError = true; }
+Assert.equal(caughtError.result, Cr.NS_ERROR_INVALID_ARG);
+caughtError = null;
+try { parser.setString("ok", "bad=", "ok"); } catch (e) { caughtError = e; }
 Assert.ok(caughtError);
+Assert.equal(caughtError.result, Cr.NS_ERROR_INVALID_ARG);
+caughtError = null;
+try { parser.setString("ok", "", "ok"); } catch (e) { caughtError = e; }
+Assert.ok(caughtError);
+Assert.equal(caughtError.result, Cr.NS_ERROR_INVALID_ARG);
 
 // Bad characters in value
-caughtError = false;
-try { parser.SetString("ok", "ok", "bad\0"); } catch (e) { caughtError = true; }
+caughtError = null;
+try { parser.setString("ok", "ok", "bad\0"); } catch (e) { caughtError = e; }
 Assert.ok(caughtError);
-caughtError = false;
-try { parser.SetString("ok", "ok", "bad\r"); } catch (e) { caughtError = true; }
+Assert.equal(caughtError.result, Cr.NS_ERROR_INVALID_ARG);
+caughtError = null;
+try { parser.setString("ok", "ok", "bad\r"); } catch (e) { caughtError = e; }
 Assert.ok(caughtError);
-caughtError = false;
-try { parser.SetString("ok", "ok", "bad\n"); } catch (e) { caughtError = true; }
+Assert.equal(caughtError.result, Cr.NS_ERROR_INVALID_ARG);
+caughtError = null;
+try { parser.setString("ok", "ok", "bad\n"); } catch (e) { caughtError = e; }
 Assert.ok(caughtError);
-caughtError = false;
-try { parser.SetString("ok", "ok", "bad="); } catch (e) { caughtError = true; }
-Assert.ok(caughtError);
+Assert.equal(caughtError.result, Cr.NS_ERROR_INVALID_ARG);
+caughtError = null;
+try { parser.setString("ok", "ok", "good="); } catch (e) { caughtError = e; }
+Assert.ok(!caughtError);
+caughtError = null;
 
 } catch (e) {
     throw "FAILED in test #" + testnum + " -- " + e;
 }
 }
--- a/xpcom/tests/unit/xpcshell.ini
+++ b/xpcom/tests/unit/xpcshell.ini
@@ -25,17 +25,17 @@ fail-if = os == "android"
 [test_bug1434856.js]
 [test_debugger_malloc_size_of.js]
 [test_file_createUnique.js]
 [test_file_equality.js]
 [test_hidden_files.js]
 [test_home.js]
 # Bug 676998: test fails consistently on Android
 fail-if = os == "android"
-[test_iniProcessor.js]
+[test_iniParser.js]
 [test_ioutil.js]
 [test_localfile.js]
 [test_mac_bundle.js]
 [test_nsIMutableArray.js]
 [test_nsIProcess.js]
 skip-if = os == "win" || os == "linux" # bug 582821, bug 1325609
 # Bug 676998: test fails consistently on Android
 fail-if = os == "android"