Merge mozilla-central to autoland on a CLOSED TREE
authorCarsten "Tomcat" Book <cbook@mozilla.com>
Fri, 15 Jul 2016 16:18:01 +0200
changeset 305161 058b26df0165c0cf78f3cde249bd10e8a474dcc0
parent 305160 82ae790de9889e8531cbc4d82b76259a1fd8d14d (current diff)
parent 305058 2f9e69c982f1e67887a1834b36ff0af4ababb3af (diff)
child 305162 38a0ffc8a956a49cd52c2c86e1cc7759f75b6db2
push id79518
push usercbook@mozilla.com
push dateSun, 17 Jul 2016 08:09:59 +0000
treeherdermozilla-inbound@711963e8daa3 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone50.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge mozilla-central to autoland on a CLOSED TREE
devtools/client/shared/components/reps/url.js
devtools/client/themes/images/firebug/filter.svg
devtools/client/themes/images/firebug/timeline-filter.svg
devtools/client/themes/images/magnifying-glass-light.png
devtools/client/themes/images/magnifying-glass-light@2x.png
dom/media/eme/CDMCallbackProxy.cpp
dom/media/eme/CDMCallbackProxy.h
--- a/accessible/xpcom/xpcAccessibleValue.cpp
+++ b/accessible/xpcom/xpcAccessibleValue.cpp
@@ -71,17 +71,17 @@ xpcAccessibleValue::GetCurrentValue(doub
 
   if (Intl().IsAccessible() && Intl().AsAccessible()->IsDefunct())
     return NS_ERROR_FAILURE;
 
   double value;
   if (Intl().IsAccessible()) {
     value = Intl().AsAccessible()->CurValue();
   } else { 
-    value = Intl().AsProxy()->MinValue();
+    value = Intl().AsProxy()->CurValue();
   }
 
   if (!IsNaN(value))
     *aValue = value;
 
   return NS_OK;
 }
 
@@ -110,17 +110,17 @@ xpcAccessibleValue::GetMinimumIncrement(
   *aValue = 0;
 
   if (Intl().IsNull())
     return NS_ERROR_FAILURE;
 
   if (Intl().IsAccessible() && Intl().AsAccessible()->IsDefunct())
     return NS_ERROR_FAILURE;
 
-  double value = Intl().AsAccessible()->Step();
+  double value;
   if (Intl().IsAccessible()) {
     value = Intl().AsAccessible()->Step();
   } else { 
     value = Intl().AsProxy()->Step();
   }
 
   if (!IsNaN(value))
     *aValue = value;
--- a/b2g/app/nsBrowserApp.cpp
+++ b/b2g/app/nsBrowserApp.cpp
@@ -45,17 +45,17 @@
 #include "mozilla/WindowsDllBlocklist.h"
 
 static void Output(const char *fmt, ... )
 {
   va_list ap;
   va_start(ap, fmt);
 
 #if defined(XP_WIN) && !MOZ_WINCONSOLE
-  char16_t msg[2048];
+  wchar_t msg[2048];
   _vsnwprintf(msg, sizeof(msg)/sizeof(msg[0]), NS_ConvertUTF8toUTF16(fmt).get(), ap);
   MessageBoxW(nullptr, msg, L"XULRunner", MB_OK | MB_ICONERROR);
 #else
   vfprintf(stderr, fmt, ap);
 #endif
 
   va_end(ap);
 }
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -6642,16 +6642,24 @@ var gIdentityHandler = {
   get _identityIcon () {
     delete this._identityIcon;
     return this._identityIcon = document.getElementById("identity-icon");
   },
   get _permissionList () {
     delete this._permissionList;
     return this._permissionList = document.getElementById("identity-popup-permission-list");
   },
+  get _permissionAnchors () {
+    delete this._permissionAnchors;
+    let permissionAnchors = {};
+    for (let anchor of document.getElementById("blocked-permissions-container").children) {
+      permissionAnchors[anchor.getAttribute("data-permission-id")] = anchor;
+    }
+    return this._permissionAnchors = permissionAnchors;
+  },
 
   /**
    * Handler for mouseclicks on the "More Information" button in the
    * "identity-popup" panel.
    */
   handleMoreInfoClick : function(event) {
     displaySecurityInfo();
     event.stopPropagation();
@@ -6899,17 +6907,42 @@ var gIdentityHandler = {
     }
 
     if (this._isCertUserOverridden) {
       this._identityBox.classList.add("certUserOverridden");
       // Cert is trusted because of a security exception, verifier is a special string.
       tooltip = gNavigatorBundle.getString("identity.identified.verified_by_you");
     }
 
-    if (SitePermissions.hasGrantedPermissions(this._uri)) {
+    let permissionAnchors = this._permissionAnchors;
+
+    // hide all permission icons
+    for (let icon of Object.values(permissionAnchors)) {
+      icon.removeAttribute("showing");
+    }
+
+    // keeps track if we should show an indicator that there are active permissions
+    let hasGrantedPermissions = false;
+
+    // show permission icons
+    for (let permission of SitePermissions.getAllByURI(this._uri)) {
+      if (permission.state === SitePermissions.BLOCK) {
+
+        let icon = permissionAnchors[permission.id];
+        if (icon) {
+          icon.setAttribute("showing", "true");
+        }
+
+      } else if (permission.state === SitePermissions.ALLOW ||
+                 permission.state === SitePermissions.SESSION) {
+        hasGrantedPermissions = true;
+      }
+    }
+
+    if (hasGrantedPermissions) {
       this._identityBox.classList.add("grantedPermissions");
     }
 
     // Push the appropriate strings out to the UI
     this._identityBox.tooltipText = tooltip;
     this._identityIcon.tooltipText = gNavigatorBundle.getString("identity.icon.tooltip");
     this._identityIconLabel.value = icon_label;
     this._identityIconCountryLabel.value = icon_country_label;
@@ -7219,17 +7252,17 @@ var gIdentityHandler = {
   },
 
   updateSitePermissions: function () {
     while (this._permissionList.hasChildNodes())
       this._permissionList.removeChild(this._permissionList.lastChild);
 
     let uri = gBrowser.currentURI;
 
-    for (let permission of SitePermissions.getPermissionsByURI(uri)) {
+    for (let permission of SitePermissions.getPermissionDetailsByURI(uri)) {
       let item = this._createPermissionItem(permission);
       this._permissionList.appendChild(item);
     }
   },
 
   setPermission: function (aPermission, aState) {
     if (aState == SitePermissions.getDefault(aPermission))
       SitePermissions.remove(gBrowser.currentURI, aPermission);
--- a/browser/base/content/browser.xul
+++ b/browser/base/content/browser.xul
@@ -720,16 +720,32 @@
                    align="center"
                    aria-label="&urlbar.viewSiteInfo.label;"
                    onclick="gIdentityHandler.handleIdentityButtonEvent(event);"
                    onkeypress="gIdentityHandler.handleIdentityButtonEvent(event);"
                    ondragstart="gIdentityHandler.onDragStart(event);">
                 <image id="identity-icon"
                        consumeanchor="identity-box"
                        onclick="PageProxyClickHandler(event);"/>
+                <box id="blocked-permissions-container" align="center">
+                  <image data-permission-id="geo" class="notification-anchor-icon geo-icon blocked" role="button"
+                         aria-label="&urlbar.geolocationNotificationAnchor.label;"/>
+                  <image data-permission-id="desktop-notification" class="notification-anchor-icon desktop-notification-icon blocked" role="button"
+                         aria-label="&urlbar.webNotsNotificationAnchor3.label;"/>
+                  <image data-permission-id="camera" class="notification-anchor-icon camera-icon blocked" role="button"
+                         aria-label="&urlbar.webRTCShareDevicesNotificationAnchor.label;"/>
+                  <image data-permission-id="indexedDB" class="notification-anchor-icon indexedDB-icon blocked" role="button"
+                         aria-label="&urlbar.indexedDBNotificationAnchor.label;"/>
+                  <image data-permission-id="microphone" class="notification-anchor-icon microphone-icon blocked" role="button"
+                         aria-label="&urlbar.webRTCShareMicrophoneNotificationAnchor.label;"/>
+                  <image data-permission-id="screen" class="notification-anchor-icon screen-icon blocked" role="button"
+                         aria-label="&urlbar.webRTCShareScreenNotificationAnchor.label;"/>
+                  <image data-permission-id="pointerLock" class="notification-anchor-icon pointerLock-icon blocked" role="button"
+                         aria-label="&urlbar.pointerLockNotificationAnchor.label;"/>
+                </box>
                 <box id="notification-popup-box"
                      hidden="true"
                      tooltiptext=""
                      onmouseover="document.getElementById('identity-icon').classList.add('no-hover');"
                      onmouseout="document.getElementById('identity-icon').classList.remove('no-hover');"
                      align="center">
                   <image id="default-notification-icon" class="notification-anchor-icon" role="button"
                          aria-label="&urlbar.defaultNotificationAnchor.label;"/>
--- a/browser/base/content/test/general/browser_permissions.js
+++ b/browser/base/content/test/general/browser_permissions.js
@@ -6,16 +6,17 @@ var {classes: Cc, interfaces: Ci, utils:
 const PERMISSIONS_PAGE = "http://example.com/browser/browser/base/content/test/general/permissions.html";
 var {SitePermissions} = Cu.import("resource:///modules/SitePermissions.jsm", {});
 
 registerCleanupFunction(function() {
   SitePermissions.remove(gBrowser.currentURI, "install");
   SitePermissions.remove(gBrowser.currentURI, "cookie");
   SitePermissions.remove(gBrowser.currentURI, "geo");
   SitePermissions.remove(gBrowser.currentURI, "camera");
+  SitePermissions.remove(gBrowser.currentURI, "microphone");
 
   while (gBrowser.tabs.length > 1) {
     gBrowser.removeCurrentTab();
   }
 });
 
 add_task(function* testMainViewVisible() {
   let {gIdentityHandler} = gBrowser.ownerGlobal;
@@ -59,25 +60,53 @@ add_task(function* testMainViewVisible()
 add_task(function* testIdentityIcon() {
   let {gIdentityHandler} = gBrowser.ownerGlobal;
   let tab = gBrowser.selectedTab = gBrowser.addTab();
   yield promiseTabLoadEvent(tab, PERMISSIONS_PAGE);
 
   gIdentityHandler.setPermission("geo", SitePermissions.ALLOW);
 
   ok(gIdentityHandler._identityBox.classList.contains("grantedPermissions"),
-    "identity-box signals granted permssions");
+    "identity-box signals granted permissions");
 
   gIdentityHandler.setPermission("geo", SitePermissions.getDefault("geo"));
 
   ok(!gIdentityHandler._identityBox.classList.contains("grantedPermissions"),
-    "identity-box doesn't signal granted permssions");
+    "identity-box doesn't signal granted permissions");
 
   gIdentityHandler.setPermission("camera", SitePermissions.BLOCK);
 
   ok(!gIdentityHandler._identityBox.classList.contains("grantedPermissions"),
-    "identity-box doesn't signal granted permssions");
+    "identity-box doesn't signal granted permissions");
 
   gIdentityHandler.setPermission("cookie", SitePermissions.SESSION);
 
   ok(gIdentityHandler._identityBox.classList.contains("grantedPermissions"),
-    "identity-box signals granted permssions");
+    "identity-box signals granted permissions");
 });
+
+add_task(function* testPermissionIcons() {
+  let {gIdentityHandler} = gBrowser.ownerGlobal;
+  let tab = gBrowser.selectedTab = gBrowser.addTab();
+  yield promiseTabLoadEvent(tab, PERMISSIONS_PAGE);
+
+  gIdentityHandler.setPermission("camera", SitePermissions.ALLOW);
+  gIdentityHandler.setPermission("geo", SitePermissions.BLOCK);
+  gIdentityHandler.setPermission("microphone", SitePermissions.SESSION);
+
+  let geoIcon = gIdentityHandler._identityBox.querySelector("[data-permission-id='geo']");
+  ok(geoIcon.hasAttribute("showing"), "blocked permission icon is shown");
+  ok(geoIcon.classList.contains("blocked"),
+    "blocked permission icon is shown as blocked");
+
+  let cameraIcon = gIdentityHandler._identityBox.querySelector("[data-permission-id='camera']");
+  ok(!cameraIcon.hasAttribute("showing"),
+    "allowed permission icon is not shown");
+
+  let microphoneIcon  = gIdentityHandler._identityBox.querySelector("[data-permission-id='microphone']");
+  ok(!microphoneIcon.hasAttribute("showing"),
+    "allowed permission icon is not shown");
+
+  gIdentityHandler.setPermission("geo", SitePermissions.getDefault("geo"));
+
+  ok(!geoIcon.hasAttribute("showing"),
+    "blocked permission icon is not shown after reset");
+});
--- a/browser/locales/en-US/chrome/browser/browser.dtd
+++ b/browser/locales/en-US/chrome/browser/browser.dtd
@@ -124,28 +124,33 @@ These should match what Safari and other
      fullscreenWarning.afterDomain.label): these two strings are used
      respectively before and after the domain requiring fullscreen.
      Localizers can use one of them, or both, to better adapt this
      sentence to their language. -->
 <!ENTITY fullscreenWarning.beforeDomain.label "">
 <!ENTITY fullscreenWarning.afterDomain.label "is now full screen">
 <!ENTITY fullscreenWarning.generic.label "This document is now full screen">
 
-<!ENTITY pointerlockWarning.beforeDomain.label "">
-<!ENTITY pointerlockWarning.afterDomain.label "has control of your pointer. Press Esc to take back control.">
-<!ENTITY pointerlockWarning.generic.label "This document has control of your pointer. Press Esc to take back control">
-
 <!-- LOCALIZATION NOTE (exitDOMFullscreen.button,
      exitDOMFullscreenMac.button): the "escape" button on PC keyboards
      is uppercase, while on Mac keyboards it is lowercase -->
 <!ENTITY exitDOMFullscreen.button "Exit Full Screen (Esc)">
 <!ENTITY exitDOMFullscreenMac.button "Exit Full Screen (esc)">
 <!ENTITY leaveDOMFullScreen.label "Exit Full Screen">
 <!ENTITY leaveDOMFullScreen.accesskey "u">
 
+<!-- LOCALIZATION NOTE (pointerlockWarning.beforeDomain.label,
+     pointerlockWarning.afterDomain.label): these two strings are used
+     respectively before and after the domain requiring pointerlock.
+     Localizers can use one of them, or both, to better adapt this
+     sentence to their language. -->
+<!ENTITY pointerlockWarning.beforeDomain.label "">
+<!ENTITY pointerlockWarning.afterDomain.label "has control of your pointer. Press Esc to take back control.">
+<!ENTITY pointerlockWarning.generic.label "This document has control of your pointer. Press Esc to take back control.">
+
 <!ENTITY closeWindow.label "Close Window">
 <!ENTITY closeWindow.accesskey "d">
 
 <!ENTITY bookmarksMenu.label "Bookmarks">
 <!ENTITY bookmarksMenu.accesskey "B">
 <!ENTITY bookmarkThisPageCmd.label "Bookmark This Page">
 <!ENTITY editThisBookmarkCmd.label "Edit This Bookmark">
 <!ENTITY bookmarkThisPageCmd.commandkey "d">
--- a/browser/modules/ReaderParent.jsm
+++ b/browser/modules/ReaderParent.jsm
@@ -40,17 +40,20 @@ var ReaderParent = {
       case "Reader:ArticleGet":
         this._getArticle(message.data.url, message.target).then((article) => {
           // Make sure the target browser is still alive before trying to send data back.
           if (message.target.messageManager) {
             message.target.messageManager.sendAsyncMessage("Reader:ArticleData", { article: article });
           }
         }, e => {
           if (e && e.newURL) {
-            message.target.loadURI("about:reader?url=" + encodeURIComponent(e.newURL));
+            // Make sure the target browser is still alive before trying to send data back.
+            if (message.target.messageManager) {
+              message.target.messageManager.sendAsyncMessage("Reader:ArticleData", { newURL: e.newURL });
+            }
           }
         });
         break;
 
       case "Reader:FaviconRequest": {
         if (message.target.messageManager) {
           let faviconUrl = PlacesUtils.promiseFaviconLinkUrl(message.data.url);
           faviconUrl.then(function onResolution(favicon) {
--- a/browser/modules/SitePermissions.jsm
+++ b/browser/modules/SitePermissions.jsm
@@ -11,73 +11,72 @@ var gStringBundle =
 
 this.SitePermissions = {
 
   UNKNOWN: Services.perms.UNKNOWN_ACTION,
   ALLOW: Services.perms.ALLOW_ACTION,
   BLOCK: Services.perms.DENY_ACTION,
   SESSION: Components.interfaces.nsICookiePermission.ACCESS_SESSION,
 
+  /* Returns all custom permissions for a given URI, the return
+   * type is a list of objects with the keys:
+   * - id: the permissionId of the permission
+   * - state: a constant representing the current permission state
+   *   (e.g. SitePermissions.ALLOW)
+   *
+   * To receive a more detailed, albeit less performant listing see
+   * SitePermissions.getPermissionDetailsByURI().
+   */
+  getAllByURI: function (aURI) {
+    let result = [];
+    if (!this.isSupportedURI(aURI)) {
+      return result;
+    }
+
+    let permissions = Services.perms.getAllForURI(aURI);
+    while (permissions.hasMoreElements()) {
+      let permission = permissions.getNext();
+
+      // filter out unknown permissions
+      if (gPermissionObject[permission.type]) {
+        result.push({
+          id: permission.type,
+          state: permission.capability,
+        });
+      }
+    }
+
+    return result;
+  },
+
   /* Returns a list of objects representing all permissions that are currently
    * set for the given URI. Each object contains the following keys:
    * - id: the permissionID of the permission
    * - label: the localized label
    * - state: a constant representing the current permission state
    *   (e.g. SitePermissions.ALLOW)
    * - availableStates: an array of all available states for that permission,
    *   represented as objects with the keys:
    *   - id: the state constant
    *   - label: the translated label of that state
    */
-  getPermissionsByURI: function (aURI) {
-    if (!this.isSupportedURI(aURI)) {
-      return [];
-    }
-
+  getPermissionDetailsByURI: function (aURI) {
     let permissions = [];
-    for (let permission of kPermissionIDs) {
-      let state = this.get(aURI, permission);
-      if (state === this.UNKNOWN) {
-        continue;
-      }
+    for (let {state, id} of this.getAllByURI(aURI)) {
+      let availableStates = this.getAvailableStates(id).map( state => {
+        return { id: state, label: this.getStateLabel(id, state) };
+      });
+      let label = this.getPermissionLabel(id);
 
-      let availableStates = this.getAvailableStates(permission).map( state => {
-        return { id: state, label: this.getStateLabel(permission, state) };
-      });
-      let label = this.getPermissionLabel(permission);
-
-      permissions.push({
-        id: permission,
-        label: label,
-        state: state,
-        availableStates: availableStates,
-      });
+      permissions.push({id, label, state, availableStates});
     }
 
     return permissions;
   },
 
-  /* Returns a boolean indicating whether there are any granted
-   * (meaning allowed or session-allowed) permissions for the given URI.
-   * Will return false for invalid URIs (such as file:// URLs).
-   */
-  hasGrantedPermissions: function (aURI) {
-    if (!this.isSupportedURI(aURI)) {
-      return false;
-    }
-
-    for (let permission of kPermissionIDs) {
-      let state = this.get(aURI, permission);
-      if (state === this.ALLOW || state === this.SESSION) {
-        return true;
-      }
-    }
-    return false;
-  },
-
   /* Checks whether a UI for managing permissions should be exposed for a given
    * URI. This excludes file URIs, for instance, as they don't have a host,
    * even though nsIPermissionManager can still handle them.
    */
   isSupportedURI: function (aURI) {
     return aURI.schemeIs("http") || aURI.schemeIs("https");
   },
 
--- a/browser/modules/test/xpcshell/test_SitePermissions.js
+++ b/browser/modules/test/xpcshell/test_SitePermissions.js
@@ -8,84 +8,79 @@ Components.utils.import("resource://gre/
 
 add_task(function* testPermissionsListing() {
   Assert.deepEqual(SitePermissions.listPermissions().sort(),
     ["camera","cookie","desktop-notification","geo","image",
      "indexedDB","install","microphone","popup"],
     "Correct list of all permissions");
 });
 
-add_task(function* testHasGrantedPermissions() {
-  // check that it returns false on an invalid URI
-  // like a file URI, which doesn't support site permissions
-  let wrongURI = Services.io.newURI("file:///example.js", null, null)
-  Assert.equal(SitePermissions.hasGrantedPermissions(wrongURI), false);
-
-  let uri = Services.io.newURI("https://example.com", null, null)
-  Assert.equal(SitePermissions.hasGrantedPermissions(uri), false);
-
-  // check that ALLOW states return true
-  SitePermissions.set(uri, "camera", SitePermissions.ALLOW);
-  Assert.equal(SitePermissions.hasGrantedPermissions(uri), true);
-
-  // removing the ALLOW state should revert to false
-  SitePermissions.remove(uri, "camera");
-  Assert.equal(SitePermissions.hasGrantedPermissions(uri), false);
-
-  // check that SESSION states return true
-  SitePermissions.set(uri, "microphone", SitePermissions.SESSION);
-  Assert.equal(SitePermissions.hasGrantedPermissions(uri), true);
-
-  // removing the SESSION state should revert to false
-  SitePermissions.remove(uri, "microphone");
-  Assert.equal(SitePermissions.hasGrantedPermissions(uri), false);
-
-  // check that a combination of ALLOW and BLOCK states returns true
-  SitePermissions.set(uri, "geo", SitePermissions.ALLOW);
-  Assert.equal(SitePermissions.hasGrantedPermissions(uri), true);
-
-  // check that a combination of SESSION and BLOCK states returns true
-  SitePermissions.set(uri, "geo", SitePermissions.SESSION);
-  Assert.equal(SitePermissions.hasGrantedPermissions(uri), true);
-
-  // check that only BLOCK states will not return true
-  SitePermissions.remove(uri, "geo");
-  Assert.equal(SitePermissions.hasGrantedPermissions(uri), false);
-
-});
-
-add_task(function* testGetPermissionsByURI() {
+add_task(function* testGetAllByURI() {
   // check that it returns an empty array on an invalid URI
   // like a file URI, which doesn't support site permissions
   let wrongURI = Services.io.newURI("file:///example.js", null, null)
-  Assert.deepEqual(SitePermissions.getPermissionsByURI(wrongURI), []);
+  Assert.deepEqual(SitePermissions.getAllByURI(wrongURI), []);
+
+  let uri = Services.io.newURI("https://example.com", null, null)
+  Assert.deepEqual(SitePermissions.getAllByURI(uri), []);
+
+  SitePermissions.set(uri, "camera", SitePermissions.ALLOW);
+  Assert.deepEqual(SitePermissions.getAllByURI(uri), [
+      { id: "camera", state: SitePermissions.ALLOW }
+  ]);
+
+  SitePermissions.set(uri, "microphone", SitePermissions.SESSION);
+  SitePermissions.set(uri, "desktop-notification", SitePermissions.BLOCK);
+
+  Assert.deepEqual(SitePermissions.getAllByURI(uri), [
+      { id: "camera", state: SitePermissions.ALLOW },
+      { id: "microphone", state: SitePermissions.SESSION },
+      { id: "desktop-notification", state: SitePermissions.BLOCK }
+  ]);
+
+  SitePermissions.remove(uri, "microphone");
+  Assert.deepEqual(SitePermissions.getAllByURI(uri), [
+      { id: "camera", state: SitePermissions.ALLOW },
+      { id: "desktop-notification", state: SitePermissions.BLOCK }
+  ]);
+
+  SitePermissions.remove(uri, "camera");
+  SitePermissions.remove(uri, "desktop-notification");
+  Assert.deepEqual(SitePermissions.getAllByURI(uri), []);
+});
+
+add_task(function* testGetPermissionDetailsByURI() {
+  // check that it returns an empty array on an invalid URI
+  // like a file URI, which doesn't support site permissions
+  let wrongURI = Services.io.newURI("file:///example.js", null, null)
+  Assert.deepEqual(SitePermissions.getPermissionDetailsByURI(wrongURI), []);
 
   let uri = Services.io.newURI("https://example.com", null, null)
 
   SitePermissions.set(uri, "camera", SitePermissions.ALLOW);
   SitePermissions.set(uri, "cookie", SitePermissions.SESSION);
   SitePermissions.set(uri, "popup", SitePermissions.BLOCK);
 
-  let permissions = SitePermissions.getPermissionsByURI(uri);
+  let permissions = SitePermissions.getPermissionDetailsByURI(uri);
 
   let camera = permissions.find(({id}) => id === "camera");
   Assert.deepEqual(camera, {
     id: "camera",
     label: "Use the Camera",
     state: SitePermissions.ALLOW,
     availableStates: [
       { id: SitePermissions.UNKNOWN, label: "Always Ask" },
       { id: SitePermissions.ALLOW, label: "Allow" },
       { id: SitePermissions.BLOCK, label: "Block" },
     ]
   });
 
   // check that removed permissions (State.UNKNOWN) are skipped
   SitePermissions.remove(uri, "camera");
-  permissions = SitePermissions.getPermissionsByURI(uri);
+  permissions = SitePermissions.getPermissionDetailsByURI(uri);
 
   camera = permissions.find(({id}) => id === "camera");
   Assert.equal(camera, undefined);
 
   // check that different available state values are represented
 
   let cookie = permissions.find(({id}) => id === "cookie");
   Assert.deepEqual(cookie, {
--- a/browser/themes/linux/searchbar.css
+++ b/browser/themes/linux/searchbar.css
@@ -206,18 +206,18 @@ menuitem[cmd="cmd_clearhistory"][disable
   display: -moz-box;
   margin-inline-end: 0;
   width: 16px;
   height: 16px;
 }
 
 .addengine-item {
   -moz-appearance: none;
-  background-color: Menu;
-  color: MenuText;
+  background-color: transparent;
+  color: inherit;
   border: none;
   height: 32px;
   margin: 0;
   padding: 0 10px;
 }
 
 .addengine-item > .button-box {
   -moz-box-pack: start;
--- a/devtools/client/animationinspector/components/animation-timeline.js
+++ b/devtools/client/animationinspector/components/animation-timeline.js
@@ -68,17 +68,17 @@ AnimationsTimeline.prototype = {
       parent: containerEl,
       attributes: {
         "class": "animation-timeline"
       }
     });
 
     let scrubberContainer = createNode({
       parent: this.rootWrapperEl,
-      attributes: {"class": "scrubber-wrapper track-container"}
+      attributes: {"class": "scrubber-wrapper"}
     });
 
     this.scrubberEl = createNode({
       parent: scrubberContainer,
       attributes: {
         "class": "scrubber"
       }
     });
@@ -87,25 +87,40 @@ AnimationsTimeline.prototype = {
       parent: this.scrubberEl,
       attributes: {
         "class": "scrubber-handle"
       }
     });
     this.scrubberHandleEl.addEventListener("mousedown",
       this.onScrubberMouseDown);
 
+    this.headerWrapper = createNode({
+      parent: this.rootWrapperEl,
+      attributes: {
+        "class": "header-wrapper"
+      }
+    });
+
     this.timeHeaderEl = createNode({
-      parent: this.rootWrapperEl,
+      parent: this.headerWrapper,
       attributes: {
         "class": "time-header track-container"
       }
     });
+
     this.timeHeaderEl.addEventListener("mousedown",
       this.onScrubberMouseDown);
 
+    this.timeTickEl = createNode({
+      parent: this.rootWrapperEl,
+      attributes: {
+        "class": "time-body track-container"
+      }
+    });
+
     this.animationsEl = createNode({
       parent: this.rootWrapperEl,
       nodeType: "ul",
       attributes: {
         "class": "animations"
       }
     });
 
@@ -449,24 +464,39 @@ AnimationsTimeline.prototype = {
     let animationDuration = TimeScale.maxEndTime - TimeScale.minStartTime;
     let minTimeInterval = TIME_GRADUATION_MIN_SPACING *
                           animationDuration / width;
     let intervalLength = findOptimalTimeInterval(minTimeInterval);
     let intervalWidth = intervalLength * width / animationDuration;
 
     // And the time graduation header.
     this.timeHeaderEl.innerHTML = "";
+    this.timeTickEl.innerHTML = "";
 
     for (let i = 0; i <= width / intervalWidth; i++) {
       let pos = 100 * i * intervalWidth / width;
 
+      // This element is the header of time tick for displaying animation
+      // duration time.
       createNode({
         parent: this.timeHeaderEl,
         nodeType: "span",
         attributes: {
-          "class": "time-tick",
+          "class": "header-item",
           "style": `left:${pos}%`
         },
         textContent: TimeScale.formatTime(TimeScale.distanceToRelativeTime(pos))
       });
+
+      // This element is displayed as a vertical line separator corresponding
+      // the header of time tick for indicating time slice for animation
+      // iterations.
+      createNode({
+        parent: this.timeTickEl,
+        nodeType: "span",
+        attributes: {
+          "class": "time-tick",
+          "style": `left:${pos}%`
+        }
+      });
     }
   }
 };
--- a/devtools/client/animationinspector/test/browser_animation_timeline_header.js
+++ b/devtools/client/animationinspector/test/browser_animation_timeline_header.js
@@ -11,33 +11,39 @@ requestLongerTimeout(2);
 const {findOptimalTimeInterval, TimeScale} = require("devtools/client/animationinspector/utils");
 
 // Should be kept in sync with TIME_GRADUATION_MIN_SPACING in
 // animation-timeline.js
 const TIME_GRADUATION_MIN_SPACING = 40;
 
 add_task(function* () {
   yield addTab(URL_ROOT + "doc_simple_animation.html");
+
+  // System scrollbar is enabled by default on our testing envionment and it
+  // would shrink width of inspector and affect number of time-ticks causing
+  // unexpected results. So, we set it wider to avoid this kind of edge case.
+  yield pushPref("devtools.toolsidebar-width.inspector", 350);
+
   let {panel} = yield openAnimationInspector();
 
   let timeline = panel.animationsTimelineComponent;
   let headerEl = timeline.timeHeaderEl;
 
   info("Find out how many time graduations should there be");
   let width = headerEl.offsetWidth;
 
   let animationDuration = TimeScale.maxEndTime - TimeScale.minStartTime;
   let minTimeInterval = TIME_GRADUATION_MIN_SPACING * animationDuration / width;
 
   // Note that findOptimalTimeInterval is tested separately in xpcshell test
   // test_findOptimalTimeInterval.js, so we assume that it works here.
   let interval = findOptimalTimeInterval(minTimeInterval);
   let nb = Math.ceil(animationDuration / interval);
 
-  is(headerEl.querySelectorAll(".time-tick").length, nb,
+  is(headerEl.querySelectorAll(".header-item").length, nb,
      "The expected number of time ticks were found");
 
   info("Make sure graduations are evenly distributed and show the right times");
   [...headerEl.querySelectorAll(".time-tick")].forEach((tick, i) => {
     let left = parseFloat(tick.style.left);
     let expectedPos = i * interval * 100 / animationDuration;
     is(Math.round(left), Math.round(expectedPos),
       `Graduation ${i} is positioned correctly`);
--- a/devtools/client/animationinspector/test/browser_animation_timeline_ui.js
+++ b/devtools/client/animationinspector/test/browser_animation_timeline_ui.js
@@ -12,17 +12,17 @@ add_task(function* () {
   yield addTab(URL_ROOT + "doc_simple_animation.html");
   let {panel} = yield openAnimationInspector();
 
   let timeline = panel.animationsTimelineComponent;
   let el = timeline.rootWrapperEl;
 
   ok(el.querySelector(".time-header"),
      "The header element is in the DOM of the timeline");
-  ok(el.querySelectorAll(".time-header .time-tick").length,
+  ok(el.querySelectorAll(".time-header .header-item").length,
      "The header has some time graduations");
 
   ok(el.querySelector(".animations"),
      "The animations container is in the DOM of the timeline");
   is(el.querySelectorAll(".animations .animation").length,
      timeline.animations.length,
      "The number of animations displayed matches the number of animations");
 
--- a/devtools/client/framework/test/browser.ini
+++ b/devtools/client/framework/test/browser.ini
@@ -59,16 +59,17 @@ skip-if = true # Bug 1177463 - Temporari
 [browser_toolbox_select_event.js]
 skip-if = e10s # Bug 1069044 - destroyInspector may hang during shutdown
 [browser_toolbox_selected_tool_unavailable.js]
 [browser_toolbox_sidebar.js]
 [browser_toolbox_sidebar_events.js]
 [browser_toolbox_sidebar_existing_tabs.js]
 [browser_toolbox_sidebar_overflow_menu.js]
 [browser_toolbox_split_console.js]
+[browser_toolbox_target.js]
 [browser_toolbox_tabsswitch_shortcuts.js]
 [browser_toolbox_textbox_context_menu.js]
 [browser_toolbox_theme_registration.js]
 [browser_toolbox_toggle.js]
 [browser_toolbox_tool_ready.js]
 [browser_toolbox_tool_remote_reopen.js]
 [browser_toolbox_transport_events.js]
 [browser_toolbox_view_source_01.js]
--- a/devtools/client/framework/test/browser_target_from_url.js
+++ b/devtools/client/framework/test/browser_target_from_url.js
@@ -33,20 +33,20 @@ add_task(function* () {
   assertIsTabTarget(target);
 
   info("Test tab with chrome privileges");
   target = yield targetFromURL(new URL("http://foo?type=tab&id=" + windowId + "&chrome"));
   assertIsTabTarget(target, true);
 
   info("Test invalid tab id");
   try {
-    yield targetFromURL(new URL("http://foo?type=tab&id=1"));
+    yield targetFromURL(new URL("http://foo?type=tab&id=10000"));
     ok(false, "Shouldn't pass");
   } catch (e) {
-    is(e.message, "targetFromURL, tab with outerWindowID:'1' doesn't exist");
+    is(e.message, "targetFromURL, tab with outerWindowID:'10000' doesn't exist");
   }
 
   info("Test parent process");
   target = yield targetFromURL(new URL("http://foo?type=process"));
   let topWindow = Services.wm.getMostRecentWindow("navigator:browser");
   is(target.url, topWindow.location.href);
   is(target.isLocalTab, false);
   is(target.chrome, true);
new file mode 100644
--- /dev/null
+++ b/devtools/client/framework/test/browser_toolbox_target.js
@@ -0,0 +1,60 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Test about:devtools-toolbox?target which allows opening a toolbox in an
+// iframe while defining which document to debug by setting a `target`
+// attribute refering to the document to debug.
+
+add_task(function *() {
+  // iframe loads the document to debug
+  let iframe = document.createElement("browser");
+  iframe.setAttribute("type", "content");
+  document.documentElement.appendChild(iframe);
+
+  let onLoad = once(iframe, "load", true);
+  iframe.setAttribute("src", "data:text/html,document to debug");
+  yield onLoad;
+  is(iframe.contentWindow.document.body.innerHTML, "document to debug");
+
+  // toolbox loads the toolbox document
+  let toolboxIframe = document.createElement("iframe");
+  document.documentElement.appendChild(toolboxIframe);
+
+  // Important step to define which target to debug
+  toolboxIframe.target = iframe;
+
+  let onToolboxReady = gDevTools.once("toolbox-ready");
+
+  onLoad = once(toolboxIframe, "load", true);
+  toolboxIframe.setAttribute("src", "about:devtools-toolbox?target");
+  yield onLoad;
+
+  // Also wait for toolbox-ready, as toolbox document load isn't enough, there
+  // is plenty of asynchronous steps during toolbox load
+  info("Waiting for toolbox-ready");
+  let toolbox = yield onToolboxReady;
+
+  let onToolboxDestroyed = gDevTools.once("toolbox-destroyed");
+  let onTabActorDetached = once(toolbox.target.client, "tabDetached");
+
+  info("Removing the iframes");
+  toolboxIframe.remove();
+
+  // And wait for toolbox-destroyed as toolbox unload is also full of
+  // asynchronous operation that outlast unload event
+  info("Waiting for toolbox-destroyed");
+  yield onToolboxDestroyed;
+  info("Toolbox destroyed");
+
+  // Also wait for tabDetached. Toolbox destroys the Target which calls
+  // TabActor.detach(). But Target doesn't wait for detach's end to resolve.
+  // Whereas it is quite important as it is a significant part of toolbox
+  // cleanup. If we do not wait for it and starts removing debugged document,
+  // the actor is still considered as being attached and continues processing
+  // events.
+  yield onTabActorDetached;
+
+  iframe.remove();
+});
--- a/devtools/client/inspector/rules/models/element-style.js
+++ b/devtools/client/inspector/rules/models/element-style.js
@@ -1,30 +1,21 @@
 /* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* vim: set ts=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/. */
 
 "use strict";
 
-const {Cc, Ci} = require("chrome");
 const promise = require("promise");
 const {Rule} = require("devtools/client/inspector/rules/models/rule");
 const {promiseWarn} = require("devtools/client/inspector/shared/utils");
 const {ELEMENT_STYLE} = require("devtools/shared/specs/styles");
-const {XPCOMUtils} = require("resource://gre/modules/XPCOMUtils.jsm");
-
-loader.lazyGetter(this, "PSEUDO_ELEMENTS", () => {
-  return domUtils.getCSSPseudoElementNames();
-});
-
-XPCOMUtils.defineLazyGetter(this, "domUtils", function () {
-  return Cc["@mozilla.org/inspector/dom-utils;1"].getService(Ci.inIDOMUtils);
-});
+const {getCssProperties} = require("devtools/shared/fronts/css-properties");
 
 /**
  * ElementStyle is responsible for the following:
  *   Keeps track of which properties are overridden.
  *   Maintains a list of Rule objects for a given element.
  *
  * @param {Element} element
  *        The element whose style we are viewing.
@@ -43,16 +34,17 @@ XPCOMUtils.defineLazyGetter(this, "domUt
 function ElementStyle(element, ruleView, store, pageStyle,
     showUserAgentStyles) {
   this.element = element;
   this.ruleView = ruleView;
   this.store = store || {};
   this.pageStyle = pageStyle;
   this.showUserAgentStyles = showUserAgentStyles;
   this.rules = [];
+  this.cssProperties = getCssProperties(this.ruleView.inspector.toolbox);
 
   // We don't want to overwrite this.store.userProperties so we only create it
   // if it doesn't already exist.
   if (!("userProperties" in this.store)) {
     this.store.userProperties = new UserProperties();
   }
 
   if (!("disabled" in this.store)) {
@@ -203,17 +195,17 @@ ElementStyle.prototype = {
     return true;
   },
 
   /**
    * Calls markOverridden with all supported pseudo elements
    */
   markOverriddenAll: function () {
     this.markOverridden();
-    for (let pseudo of PSEUDO_ELEMENTS) {
+    for (let pseudo of this.cssProperties.pseudoElements) {
       this.markOverridden(pseudo);
     }
   },
 
   /**
    * Mark the properties listed in this.rules for a given pseudo element
    * with an overridden flag if an earlier property overrides it.
    *
--- a/devtools/client/jar.mn
+++ b/devtools/client/jar.mn
@@ -205,20 +205,16 @@ devtools.jar:
     skin/shadereditor.css (themes/shadereditor.css)
     skin/storage.css (themes/storage.css)
     skin/splitview.css (themes/splitview.css)
     skin/styleeditor.css (themes/styleeditor.css)
     skin/webaudioeditor.css (themes/webaudioeditor.css)
     skin/components-frame.css (themes/components-frame.css)
     skin/components-h-split-box.css (themes/components-h-split-box.css)
     skin/jit-optimizations.css (themes/jit-optimizations.css)
-    skin/images/magnifying-glass.png (themes/images/magnifying-glass.png)
-    skin/images/magnifying-glass@2x.png (themes/images/magnifying-glass@2x.png)
-    skin/images/magnifying-glass-light.png (themes/images/magnifying-glass-light.png)
-    skin/images/magnifying-glass-light@2x.png (themes/images/magnifying-glass-light@2x.png)
     skin/images/filter.svg (themes/images/filter.svg)
     skin/images/search.svg (themes/images/search.svg)
     skin/images/itemToggle.svg (themes/images/itemToggle.svg)
     skin/images/itemArrow-dark-rtl.svg (themes/images/itemArrow-dark-rtl.svg)
     skin/images/itemArrow-dark-ltr.svg (themes/images/itemArrow-dark-ltr.svg)
     skin/images/itemArrow-rtl.svg (themes/images/itemArrow-rtl.svg)
     skin/images/itemArrow-ltr.svg (themes/images/itemArrow-ltr.svg)
     skin/images/noise.png (themes/images/noise.png)
@@ -338,21 +334,19 @@ devtools.jar:
     # Firebug Theme
     skin/images/firebug/read-only.svg (themes/images/firebug/read-only.svg)
     skin/images/firebug/spinner.png (themes/images/firebug/spinner.png)
     skin/images/firebug/twisty-closed-firebug.svg (themes/images/firebug/twisty-closed-firebug.svg)
     skin/images/firebug/twisty-open-firebug.svg (themes/images/firebug/twisty-open-firebug.svg)
     skin/images/firebug/arrow-down.svg (themes/images/firebug/arrow-down.svg)
     skin/images/firebug/arrow-up.svg (themes/images/firebug/arrow-up.svg)
     skin/images/firebug/close.svg (themes/images/firebug/close.svg)
-    skin/images/firebug/filter.svg (themes/images/firebug/filter.svg)
     skin/images/firebug/pause.svg (themes/images/firebug/pause.svg)
     skin/images/firebug/play.svg (themes/images/firebug/play.svg)
     skin/images/firebug/rewind.svg (themes/images/firebug/rewind.svg)
-    skin/images/firebug/timeline-filter.svg (themes/images/firebug/timeline-filter.svg)
     skin/images/firebug/disable.svg (themes/images/firebug/disable.svg)
     skin/images/firebug/breadcrumbs-divider.svg (themes/images/firebug/breadcrumbs-divider.svg)
     skin/images/firebug/breakpoint.svg (themes/images/firebug/breakpoint.svg)
     skin/images/firebug/tool-options.svg (themes/images/firebug/tool-options.svg)
     skin/images/firebug/debugger-step-in.svg (themes/images/firebug/debugger-step-in.svg)
     skin/images/firebug/debugger-step-out.svg (themes/images/firebug/debugger-step-out.svg)
     skin/images/firebug/debugger-step-over.svg (themes/images/firebug/debugger-step-over.svg)
     skin/images/firebug/pane-collapse.svg (themes/images/firebug/pane-collapse.svg)
--- a/devtools/client/jsonview/css/search-box.css
+++ b/devtools/client/jsonview/css/search-box.css
@@ -3,44 +3,22 @@
  * 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/. */
 
 /******************************************************************************/
 /* Search Box */
 
 .searchBox {
   height: 18px;
-  font-size: 12px;
-  margin-top: 0;
-  border: 1px solid rgb(170, 188, 207);
-  width: 200px;
-  position: fixed;
-  right: 5px;
-  background-image: url("search.svg");
+  font: message-box;
+  background-color: var(--theme-body-background);
+  background-image: url("chrome://devtools/skin/images/filter.svg");
   background-repeat: no-repeat;
   background-position: 2px center;
+  border: 1px solid var(--theme-splitter-color);
+  border-radius: 2px;
+  color: var(--theme-content-color1);
+  width: 200px;
+  margin-top: 0;
+  position: fixed;
+  right: 1px;
   padding-left: 20px;
 }
-
-/******************************************************************************/
-/* Light Theme & Dark Theme*/
-
-.theme-dark .searchBox,
-.theme-light .searchBox {
-  border: 1px solid rgb(170, 170, 170);
-  background-image: url("chrome://devtools/skin/images/magnifying-glass-light.png");
-  background-position: 8px center;
-  border-radius: 2px;
-  padding-left: 25px;
-  margin-top: 1px;
-  height: 16px;
-  font-style: italic;
-}
-
-/******************************************************************************/
-/* Dark Theme */
-
-.theme-dark .searchBox {
-  background-color: rgba(24, 29, 32, 1);
-  color: rgba(184, 200, 217, 1);
-  border-color: var(--theme-splitter-color);
-  background-image: url("chrome://devtools/skin/images/magnifying-glass.png");
-}
--- a/devtools/client/shared/components/reps/document.js
+++ b/devtools/client/shared/components/reps/document.js
@@ -6,19 +6,18 @@
 "use strict";
 
 // Make this available to both AMD and CJS environments
 define(function (require, exports, module) {
   // ReactJS
   const React = require("devtools/client/shared/vendor/react");
 
   // Reps
-  const { createFactories, isGrip } = require("./rep-utils");
+  const { createFactories, isGrip, getFileName } = require("./rep-utils");
   const { ObjectBox } = createFactories(require("./object-box"));
-  const { getFileName } = require("./url");
 
   // Shortcuts
   const { span } = React.DOM;
 
   /**
    * Renders DOM document object.
    */
   let Document = React.createClass({
--- a/devtools/client/shared/components/reps/function.js
+++ b/devtools/client/shared/components/reps/function.js
@@ -29,17 +29,17 @@ define(function (require, exports, modul
         return this.props.objectLink({
           object: grip
         }, "function");
       }
       return "";
     },
 
     summarizeFunction: function (grip) {
-      let name = grip.displayName || grip.name || "function";
+      let name = grip.userDisplayName || grip.displayName || grip.name || "function";
       return cropString(name + "()", 100);
     },
 
     render: function () {
       let grip = this.props.object;
 
       return (
         ObjectBox({className: "function"},
--- a/devtools/client/shared/components/reps/moz.build
+++ b/devtools/client/shared/components/reps/moz.build
@@ -26,11 +26,10 @@ DevToolsModules(
     'regexp.js',
     'rep-utils.js',
     'rep.js',
     'reps.css',
     'string.js',
     'stylesheet.js',
     'text-node.js',
     'undefined.js',
-    'url.js',
     'window.js',
 )
--- a/devtools/client/shared/components/reps/rep-utils.js
+++ b/devtools/client/shared/components/reps/rep-utils.js
@@ -1,8 +1,9 @@
+/* globals URLSearchParams */
 /* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* vim: set ft=javascript ts=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/. */
 
 "use strict";
 
@@ -68,14 +69,82 @@ define(function (require, exports, modul
     if (text.length > limit) {
       return text.substr(0, Math.ceil(halfLimit)) + alternativeText +
         text.substr(text.length - Math.floor(halfLimit));
     }
 
     return text;
   }
 
+  function parseURLParams(url) {
+    url = new URL(url);
+    return parseURLEncodedText(url.searchParams);
+  }
+
+  function parseURLEncodedText(text) {
+    let params = [];
+
+    // In case the text is empty just return the empty parameters
+    if (text == "") {
+      return params;
+    }
+
+    let searchParams = new URLSearchParams(text);
+    let entries = [...searchParams.entries()];
+    return entries.map(entry => {
+      return {
+        name: entry[0],
+        value: entry[1]
+      };
+    });
+  }
+
+  function getFileName(url) {
+    let split = splitURLBase(url);
+    return split.name;
+  }
+
+  function splitURLBase(url) {
+    if (!isDataURL(url)) {
+      return splitURLTrue(url);
+    }
+    return {};
+  }
+
+  function isDataURL(url) {
+    return (url && url.substr(0, 5) == "data:");
+  }
+
+  function splitURLTrue(url) {
+    const reSplitFile = /(.*?):\/{2,3}([^\/]*)(.*?)([^\/]*?)($|\?.*)/;
+    let m = reSplitFile.exec(url);
+
+    if (!m) {
+      return {
+        name: url,
+        path: url
+      };
+    } else if (m[4] == "" && m[5] == "") {
+      return {
+        protocol: m[1],
+        domain: m[2],
+        path: m[3],
+        name: m[3] != "/" ? m[3] : m[2]
+      };
+    }
+
+    return {
+      protocol: m[1],
+      domain: m[2],
+      path: m[2] + m[3],
+      name: m[4] + m[5]
+    };
+  }
+
   // Exports from this module
   exports.createFactories = createFactories;
   exports.isGrip = isGrip;
   exports.cropString = cropString;
   exports.cropMultipleLines = cropMultipleLines;
+  exports.parseURLParams = parseURLParams;
+  exports.parseURLEncodedText = parseURLEncodedText;
+  exports.getFileName = getFileName;
 });
--- a/devtools/client/shared/components/reps/stylesheet.js
+++ b/devtools/client/shared/components/reps/stylesheet.js
@@ -6,19 +6,18 @@
 "use strict";
 
 // Make this available to both AMD and CJS environments
 define(function (require, exports, module) {
   // ReactJS
   const React = require("devtools/client/shared/vendor/react");
 
   // Reps
-  const { createFactories, isGrip } = require("./rep-utils");
+  const { createFactories, isGrip, getFileName } = require("./rep-utils");
   const { ObjectBox } = createFactories(require("./object-box"));
-  const { getFileName } = require("./url");
 
   // Shortcuts
   const DOM = React.DOM;
 
   /**
    * Renders a grip representing CSSStyleSheet
    */
   let StyleSheet = React.createClass({
deleted file mode 100644
--- a/devtools/client/shared/components/reps/url.js
+++ /dev/null
@@ -1,81 +0,0 @@
-/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
-/* vim: set ft=javascript ts=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/. */
-/* global URLSearchParams */
-
-"use strict";
-
-// Make this available to both AMD and CJS environments
-define(function (require, exports, module) {
-  function parseURLParams(url) {
-    url = new URL(url);
-    return parseURLEncodedText(url.searchParams);
-  }
-
-  function parseURLEncodedText(text) {
-    let params = [];
-
-    // In case the text is empty just return the empty parameters
-    if (text == "") {
-      return params;
-    }
-
-    let searchParams = new URLSearchParams(text);
-    let entries = [...searchParams.entries()];
-    return entries.map(entry => {
-      return {
-        name: entry[0],
-        value: entry[1]
-      };
-    });
-  }
-
-  function getFileName(url) {
-    let split = splitURLBase(url);
-    return split.name;
-  }
-
-  function splitURLBase(url) {
-    if (!isDataURL(url)) {
-      return splitURLTrue(url);
-    }
-    return {};
-  }
-
-  function isDataURL(url) {
-    return (url && url.substr(0, 5) == "data:");
-  }
-
-  function splitURLTrue(url) {
-    const reSplitFile = /(.*?):\/{2,3}([^\/]*)(.*?)([^\/]*?)($|\?.*)/;
-    let m = reSplitFile.exec(url);
-
-    if (!m) {
-      return {
-        name: url,
-        path: url
-      };
-    } else if (m[4] == "" && m[5] == "") {
-      return {
-        protocol: m[1],
-        domain: m[2],
-        path: m[3],
-        name: m[3] != "/" ? m[3] : m[2]
-      };
-    }
-
-    return {
-      protocol: m[1],
-      domain: m[2],
-      path: m[2] + m[3],
-      name: m[4] + m[5]
-    };
-  }
-
-  // Exports from this module
-  exports.parseURLParams = parseURLParams;
-  exports.parseURLEncodedText = parseURLEncodedText;
-  exports.getFileName = getFileName;
-});
--- a/devtools/client/shared/components/test/mochitest/test_reps_function.html
+++ b/devtools/client/shared/components/test/mochitest/test_reps_function.html
@@ -47,16 +47,32 @@ window.onload = Task.async(function* () 
         mode: undefined,
         expectedOutput: defaultOutput,
       }
     ];
 
     testRepRenderModes(modeTests, testName, componentUnderTest, getGripStub(testName));
   }
 
+  function testUserNamed() {
+    // Test declaration: `function testName{ let innerVar = "foo" }`
+    const testName = "testUserNamed";
+
+    const defaultOutput = `testUserName()`;
+
+    const modeTests = [
+      {
+        mode: undefined,
+        expectedOutput: defaultOutput,
+      }
+    ];
+
+    testRepRenderModes(modeTests, testName, componentUnderTest, getGripStub(testName));
+  }
+
   function testVarNamed() {
     // Test declaration: `let testVarName = function() { }`
     const testName = "testVarNamed";
 
     const defaultOutput = `testVarName()`;
 
     const modeTests = [
       {
@@ -113,16 +129,33 @@ window.onload = Task.async(function* () 
           "name": "testName",
           "displayName": "testName",
           "location": {
             "url": "debugger eval code",
             "line": 1
           }
         };
 
+      case "testUserNamed":
+        return {
+          "type": "object",
+          "class": "Function",
+          "actor": "server1.conn6.obj35",
+          "extensible": true,
+          "frozen": false,
+          "sealed": false,
+          "name": "testName",
+          "userDisplayName": "testUserName",
+          "displayName": "testName",
+          "location": {
+            "url": "debugger eval code",
+            "line": 1
+          }
+        };
+
       case "testVarNamed":
         return {
           "type": "object",
           "class": "Function",
           "actor": "server1.conn7.obj41",
           "extensible": true,
           "frozen": false,
           "sealed": false,
--- a/devtools/client/shared/css-angle.js
+++ b/devtools/client/shared/css-angle.js
@@ -5,16 +5,18 @@
 "use strict";
 
 const SPECIALVALUES = new Set([
   "initial",
   "inherit",
   "unset"
 ]);
 
+const {getCSSLexer} = require("devtools/shared/css-lexer");
+
 /**
  * This module is used to convert between various angle units.
  *
  * Usage:
  *   let {require} = Cu.import("resource://devtools/shared/Loader.jsm", {});
  *   let {angleUtils} = require("devtools/client/shared/css-angle");
  *   let angle = new angleUtils.CssAngle("180deg");
  *
@@ -61,17 +63,22 @@ CssAngle.prototype = {
     return this._angleUnit;
   },
 
   set angleUnit(unit) {
     this._angleUnit = unit;
   },
 
   get valid() {
-    return /^-?\d+\.?\d*(deg|rad|grad|turn)$/gi.test(this.authored);
+    let token = getCSSLexer(this.authored).nextToken();
+    if (!token) {
+      return false;
+    }
+    return (token.tokenType === "dimension"
+      && token.text.toLowerCase() in CssAngle.ANGLEUNIT);
   },
 
   get specialValue() {
     return SPECIALVALUES.has(this.lowerCased) ? this.authored : null;
   },
 
   get deg() {
     let invalidOrSpecialValue = this._getInvalidOrSpecialValue();
--- a/devtools/client/shared/output-parser.js
+++ b/devtools/client/shared/output-parser.js
@@ -151,17 +151,17 @@ OutputParser.prototype = {
 
     let colorOK = function () {
       return options.supportsColor ||
         (options.expectFilter && parenDepth === 1 &&
          outerMostFunctionTakesColor);
     };
 
     let angleOK = function (angle) {
-      return /^-?\d+\.?\d*(deg|rad|grad|turn)$/gi.test(angle);
+      return (new angleUtils.CssAngle(angle)).valid;
     };
 
     while (true) {
       let token = tokenStream.nextToken();
       if (!token) {
         break;
       }
       if (token.tokenType === "comment") {
--- a/devtools/client/shared/test/browser_css_angle.js
+++ b/devtools/client/shared/test/browser_css_angle.js
@@ -8,16 +8,17 @@ const TEST_URI = "data:text/html;charset
 var {angleUtils} = require("devtools/client/shared/css-angle");
 
 add_task(function* () {
   yield addTab("about:blank");
   let [host] = yield createHost("bottom", TEST_URI);
 
   info("Starting the test");
   testAngleUtils();
+  testAngleValidity();
 
   host.destroy();
   gBrowser.removeCurrentTab();
 });
 
 function testAngleUtils() {
   let data = getTestData();
 
@@ -30,30 +31,77 @@ function testAngleUtils() {
     is(angle.rad, rad, "color.rad === rad");
     is(angle.grad, grad, "color.grad === grad");
     is(angle.turn, turn, "color.turn === turn");
 
     testToString(angle, deg, rad, grad, turn);
   }
 }
 
+function testAngleValidity() {
+  let data = getAngleValidityData();
+
+  for (let {angle, result} of data) {
+    let testAngle = new angleUtils.CssAngle(angle);
+
+    is(testAngle.valid, result, `Testing that "${angle}" is ${testAngle.valid ? " a valid" : "an invalid" } angle`);
+  }
+}
+
 function testToString(angle, deg, rad, grad, turn) {
   angle.angleUnit = angleUtils.CssAngle.ANGLEUNIT.deg;
   is(angle.toString(), deg, "toString() with deg type");
 
   angle.angleUnit = angleUtils.CssAngle.ANGLEUNIT.rad;
   is(angle.toString(), rad, "toString() with rad type");
 
   angle.angleUnit = angleUtils.CssAngle.ANGLEUNIT.grad;
   is(angle.toString(), grad, "toString() with grad type");
 
   angle.angleUnit = angleUtils.CssAngle.ANGLEUNIT.turn;
   is(angle.toString(), turn, "toString() with turn type");
 }
 
+function getAngleValidityData() {
+  return [{
+    angle: "0.2turn",
+    result: true
+  }, {
+    angle: "-0.2turn",
+    result: true
+  }, {
+    angle: "-.2turn",
+    result: true
+  }, {
+    angle: "1e02turn",
+    result: true
+  }, {
+    angle: "-2e2turn",
+    result: true
+  }, {
+    angle: ".2turn",
+    result: true
+  }, {
+    angle: "0.2aaturn",
+    result: false
+  }, {
+    angle: "2dega",
+    result: false
+  }, {
+    angle: "0.deg",
+    result: false
+  }, {
+    angle: ".deg",
+    result: false
+  }, {
+    angle: "..2turn",
+    result: false
+  }];
+}
+
 function getTestData() {
   return [{
     authored: "0deg",
     deg: "0deg",
     rad: "0rad",
     grad: "0grad",
     turn: "0turn"
   }, {
--- a/devtools/client/themes/animationinspector.css
+++ b/devtools/client/themes/animationinspector.css
@@ -102,17 +102,18 @@ body {
 [timeline] #timeline-toolbar {
   display: flex;
 }
 
 /* The main animations container */
 
 #players {
   height: calc(100% - var(--toolbar-height));
-  overflow: auto;
+  overflow-x: hidden;
+  overflow-y: auto;
 }
 
 [empty] #players {
   display: none;
 }
 
 /* The error message, shown when an invalid/unanimated element is selected */
 
@@ -195,18 +196,16 @@ body {
 #timeline-rate {
   position: relative;
   width: 4.5em;
 }
 
 /* Animation timeline component */
 
 .animation-timeline {
-  height: 100%;
-  overflow: hidden;
   position: relative;
   display: flex;
   flex-direction: column;
 }
 
 /* Useful for positioning animations or keyframes in the timeline */
 .animation-timeline .track-container {
   position: absolute;
@@ -214,77 +213,104 @@ body {
   left: var(--timeline-sidebar-width);
   /* Leave the width of a marker right of a track so the 100% markers can be
      selected easily */
   right: var(--keyframes-marker-size);
   height: var(--timeline-animation-height);
 }
 
 .animation-timeline .scrubber-wrapper {
-  z-index: 2;
-  pointer-events: none;
+  position: absolute;
+  left: var(--timeline-sidebar-width);
+  /* Leave the width of a marker right of a track so the 100% markers can be
+     selected easily */
+  right: var(--keyframes-marker-size);
   height: 100%;
 }
 
 .animation-timeline .scrubber {
+  z-index: 5;
+  pointer-events: none;
   position: absolute;
-  height: 100%;
+  /* Make the scrubber as tall as the viewport minus the toolbar height and the
+     header-wrapper's borders */
+  height: calc(100vh - var(--toolbar-height) - 1px);
+  min-height: 100%;
   width: 0;
   border-right: 1px solid red;
   box-sizing: border-box;
 }
 
-.animation-timeline .scrubber::before {
-  content: "";
-  position: absolute;
-  top: 0;
-  width: 1px;
-  right: -6px;
-  border-top: 5px solid red;
-  border-left: 5px solid transparent;
-  border-right: 5px solid transparent;
-}
-
 /* The scrubber handle is a transparent element displayed on top of the scrubber
    line that allows users to drag it */
 .animation-timeline .scrubber .scrubber-handle {
   position: absolute;
   height: 100%;
-  top: 0;
   /* Make it thick enough for easy dragging */
   width: 6px;
-  right: -3px;
+  right: -1.5px;
   cursor: col-resize;
   pointer-events: all;
 }
 
+.animation-timeline .scrubber .scrubber-handle::before {
+  content: "";
+  position: sticky;
+  top: 0;
+  width: 1px;
+  border-top: 5px solid red;
+  border-left: 5px solid transparent;
+  border-right: 5px solid transparent;
+}
+
 .animation-timeline .time-header {
-  min-height: var(--toolbar-height);
+  min-height: var(--timeline-animation-height);
   cursor: col-resize;
   -moz-user-select: none;
 }
 
-.animation-timeline .time-header .time-tick {
+.animation-timeline .time-header .header-item {
   position: absolute;
+  height: 100%;
+  padding-top: 3px;
+  border-left: 0.5px solid var(--time-graduation-border-color);
+}
+
+.animation-timeline .header-wrapper {
+  position: sticky;
   top: 0;
-  height: 100vh;
-  padding-top: 3px;
+  background-color: var(--theme-body-background);
+  border-bottom: 1px solid var(--time-graduation-border-color);
+  z-index: 3;
+  height: var(--timeline-animation-height);
+  overflow: hidden;
+}
+
+.animation-timeline .time-body {
+  height: 100%;
+}
+
+.animation-timeline .time-body .time-tick {
+  -moz-user-select: none;
+  position: absolute;
+  width: 0;
+  /* When scroll bar is shown, make it covers entire time-body */
+  height: 100%;
+  /* When scroll bar is hidden, make it as tall as the viewport minus the
+     timeline animation height and the header-wrapper's borders */
+  min-height: calc(100vh - var(--timeline-animation-height) - 1px);
   border-left: 0.5px solid var(--time-graduation-border-color);
 }
 
 .animation-timeline .animations {
   width: 100%;
   height: 100%;
-  overflow-y: auto;
-  overflow-x: hidden;
-  /* Leave some space for the header */
-  margin-top: var(--timeline-animation-height);
   padding: 0;
   list-style-type: none;
-  border-top: 1px solid var(--time-graduation-border-color);
+  margin-top: 0;
 }
 
 /* Animation block widgets */
 
 .animation-timeline .animation {
   margin: 2px 0;
   height: var(--timeline-animation-height);
   position: relative;
deleted file mode 100644
--- a/devtools/client/themes/images/firebug/filter.svg
+++ /dev/null
@@ -1,25 +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/. -->
-<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="12" height="12" viewBox="0 0 12 12">
-  <defs>
-    <linearGradient id="b">
-      <stop offset="0" stop-color="#646464"/>
-      <stop offset=".734" stop-color="#c8c8c8"/>
-      <stop offset="1" stop-color="#b4b4b4"/>
-    </linearGradient>
-    <linearGradient id="a">
-      <stop offset="0" stop-color="#787878"/>
-      <stop offset=".764" stop-color="#dcdcdc"/>
-      <stop offset="1" stop-color="#c8c8c8"/>
-    </linearGradient>
-    <linearGradient xlink:href="#a" id="e" x1="7.336" y1="8.379" x2="4.585" y2="8.379" gradientUnits="userSpaceOnUse" gradientTransform="matrix(.991 0 0 1 .083 1040.346)"/>
-    <linearGradient xlink:href="#b" id="f" x1="7.715" y1="7.526" x2="4.206" y2="7.526" gradientUnits="userSpaceOnUse" gradientTransform="matrix(.991 0 0 1 .083 1040.346)"/>
-    <linearGradient xlink:href="#b" id="d" x1="9.755" y1="1044.158" x2="2.188" y2="1044.158" gradientUnits="userSpaceOnUse" gradientTransform="scale(1.005 1)"/>
-    <linearGradient xlink:href="#a" id="c" x1="9.597" y1="1042.528" x2="2.261" y2="1042.528" gradientUnits="userSpaceOnUse" gradientTransform="scale(1.005 1)"/>
-  </defs>
-  <g stroke-width=".4" stroke-linejoin="round">
-    <path d="M.2 1041.536l3.975 5.244h3.65l3.975-5.242z" fill="url(#c)" stroke="url(#d)" transform="translate(0 -1040.362)"/>
-    <path d="M7.8 1046.764H4.2v3.898h3.6z" fill="url(#e)" stroke="url(#f)" transform="translate(0 -1040.362)"/>
-  </g>
-</svg>
deleted file mode 100644
--- a/devtools/client/themes/images/firebug/timeline-filter.svg
+++ /dev/null
@@ -1,6 +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/. -->
-<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
-  <path d="M2 2v3l5 4v6h2V9l5-4V2z" fill="#3bace5"/>
-</svg>
deleted file mode 100644
index e8c1841588a52bd11935724ef92112a58e2f3c7e..0000000000000000000000000000000000000000
GIT binary patch
literal 0
Hc$@<O00001
deleted file mode 100644
index d784870c380da76e4897aad41c4c87cc848dd50e..0000000000000000000000000000000000000000
GIT binary patch
literal 0
Hc$@<O00001
--- a/devtools/client/themes/performance.css
+++ b/devtools/client/themes/performance.css
@@ -5,35 +5,32 @@
 
 /* CSS Variables specific to this panel that aren't defined by the themes */
 .theme-dark {
   --cell-border-color: rgba(255,255,255,0.15);
   --cell-border-color-light: rgba(255,255,255,0.1);
   --focus-cell-border-color: rgba(255,255,255,0.5);
   --row-alt-background-color: rgba(86, 117, 185, 0.15);
   --row-hover-background-color: rgba(86, 117, 185, 0.25);
-  --filter-image: url(chrome://devtools/skin/images/filter.svg);
 }
 
 .theme-light {
   --cell-border-color: rgba(0,0,0,0.15);
   --cell-border-color-light: rgba(0,0,0,0.1);
   --focus-cell-border-color: rgba(0,0,0,0.3);
   --row-alt-background-color: rgba(76,158,217,0.1);
   --row-hover-background-color: rgba(76,158,217,0.2);
-  --filter-image: url(chrome://devtools/skin/images/filter.svg);
 }
 
 .theme-firebug {
   --cell-border-color: rgba(0,0,0,0.15);
   --cell-border-color-light: rgba(0,0,0,0.1);
   --focus-cell-border-color: rgba(0,0,0,0.3);
   --row-alt-background-color: rgba(76,158,217,0.1);
   --row-hover-background-color: rgba(76,158,217,0.2);
-  --filter-image: url(chrome://devtools/skin/images/firebug/timeline-filter.svg);
 }
 
 /**
  * A generic class to hide elements, replacing the `element.hidden` attribute
  * that we use to hide elements that can later be active
  */
 .hidden {
   display: none;
@@ -48,17 +45,17 @@
 }
 
 #performance-toolbar-controls-detail-views .toolbarbutton-text {
   padding-inline-start: 4px;
   padding-inline-end: 8px;
 }
 
 #filter-button {
-  list-style-image: var(--filter-image);
+  list-style-image: url(images/filter.svg);
 }
 
 #performance-filter-menupopup > menuitem .menu-iconic-left::after {
   content: "";
   display: block;
   width: 8px;
   height: 8px;
   margin: 0 8px;
--- a/devtools/client/themes/rules.css
+++ b/devtools/client/themes/rules.css
@@ -1,26 +1,26 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 /* CSS Variables specific to this panel that aren't defined by the themes */
 .theme-light {
   --rule-highlight-background-color: #ffee99;
-  --rule-filter-icon: url(images/magnifying-glass-light.png);
+  --rule-filter-icon: url(images/filter.svg);
 }
 
 .theme-dark {
   --rule-highlight-background-color: #594724;
-  --rule-filter-icon: url(images/magnifying-glass.png);
+  --rule-filter-icon: url(images/filter.svg);
 }
 
 .theme-firebug {
   --rule-highlight-background-color: #ffee99;
-  --rule-filter-icon: url(images/magnifying-glass-light.png);
+  --rule-filter-icon: url(images/filter.svg);
   --rule-property-name: darkgreen;
   --rule-property-value: darkblue;
 }
 
 /* Rule View Tabpanel */
 
 .theme-firebug .ruleview {
   font-family: monospace;
--- a/devtools/client/themes/toolbars.css
+++ b/devtools/client/themes/toolbars.css
@@ -9,91 +9,39 @@
   --toolbar-tab-hover-active: rgba(170, 170, 170, .4);
   --searchbox-background-color: #ffee99;
   --searchbox-border-color: #ffbf00;
   --searcbox-no-match-background-color: #ffe5e5;
   --searcbox-no-match-border-color: #e52e2e;
   --magnifying-glass-image: url(images/search.svg);
   --filter-image: url(images/filter.svg);
   --tool-options-image: url(images/tool-options.svg);
-  --close-button-image: url(chrome://devtools/skin/images/close.svg);
   --icon-filter: invert(1);
-  --dock-bottom-image: url(chrome://devtools/skin/images/dock-bottom.svg);
-  --dock-side-image: url(chrome://devtools/skin/images/dock-side.svg);
-  --dock-undock-image: url(chrome://devtools/skin/images/dock-undock.svg);
   --toolbar-button-border-color: rgba(170, 170, 170, .5);
-
-  /* Toolbox buttons */
-  --command-paintflashing-image: url(images/command-paintflashing.svg);
-  --command-screenshot-image: url(images/command-screenshot.svg);
-  --command-responsive-image: url(images/command-responsivemode.svg);
-  --command-scratchpad-image: url(images/command-scratchpad.svg);
-  --command-pick-image: url(images/command-pick.svg);
-  --command-frames-image: url(images/command-frames.svg);
-  --command-splitconsole-image: url(images/command-console.svg);
-  --command-noautohide-image: url(images/command-noautohide.svg);
-  --command-eyedropper-image: url(images/command-eyedropper.svg);
-  --command-rulers-image: url(images/command-rulers.svg);
-  --command-measure-image: url(images/command-measure.svg);
 }
 
 .theme-dark {
   --toolbar-tab-hover: hsla(206, 37%, 4%, .2);
   --toolbar-tab-hover-active: hsla(206, 37%, 4%, .4);
   --searchbox-background-color: #4d4222;
   --searchbox-border-color: #d99f2b;
   --searcbox-no-match-background-color: #402325;
   --searcbox-no-match-border-color: #cc3d3d;
   --magnifying-glass-image: url(images/search.svg);
   --filter-image: url(images/filter.svg);
   --tool-options-image: url(images/tool-options.svg);
-  --close-button-image: url(chrome://devtools/skin/images/close.svg);
   --icon-filter: none;
-  --dock-bottom-image: url(chrome://devtools/skin/images/dock-bottom.svg);
-  --dock-side-image: url(chrome://devtools/skin/images/dock-side.svg);
-  --dock-undock-image: url(chrome://devtools/skin/images/dock-undock.svg);
   --toolbar-button-border-color: rgba(0, 0, 0, .4);
-
-  /* Toolbox buttons */
-  --command-paintflashing-image: url(images/command-paintflashing.svg);
-  --command-screenshot-image: url(images/command-screenshot.svg);
-  --command-responsive-image: url(images/command-responsivemode.svg);
-  --command-scratchpad-image: url(images/command-scratchpad.svg);
-  --command-pick-image: url(images/command-pick.svg);
-  --command-frames-image: url(images/command-frames.svg);
-  --command-splitconsole-image: url(images/command-console.svg);
-  --command-noautohide-image: url(images/command-noautohide.svg);
-  --command-eyedropper-image: url(images/command-eyedropper.svg);
-  --command-rulers-image: url(images/command-rulers.svg);
-  --command-measure-image: url(images/command-measure.svg);
 }
 
 .theme-firebug {
-  --magnifying-glass-image: url(images/firebug/filter.svg);
-  --magnifying-glass-image-2x: url(images/firebug/filter.svg);
+  --magnifying-glass-image: url(images/search.svg);
   --tool-options-image: url(images/firebug/tool-options.svg);
-  --close-button-image: url(chrome://devtools/skin/images/firebug/close.svg);
   --icon-filter: invert(1);
-  --dock-bottom-image: url(chrome://devtools/skin/images/firebug/dock-bottom.svg);
-  --dock-side-image: url(chrome://devtools/skin/images/firebug/dock-side.svg);
-  --dock-undock-image: url(chrome://devtools/skin/images/firebug/dock-undock.svg);
   --toolbar-button-border-color: rgba(170, 170, 170, .5);
-
-  /* Toolbox buttons */
-  --command-paintflashing-image: url(images/firebug/command-paintflashing.svg);
-  --command-screenshot-image: url(images/firebug/command-screenshot.svg);
-  --command-responsive-image: url(images/firebug/command-responsivemode.svg);
-  --command-scratchpad-image: url(images/firebug/command-scratchpad.svg);
-  --command-pick-image: url(images/firebug/command-pick.svg);
-  --command-frames-image: url(images/firebug/command-frames.svg);
-  --command-splitconsole-image: url(images/firebug/command-console.svg);
-  --command-noautohide-image: url(images/firebug/command-noautohide.svg);
-  --command-eyedropper-image: url(images/firebug/command-eyedropper.svg);
-  --command-rulers-image: url(images/firebug/command-rulers.svg);
-  --command-measure-image: url(images/firebug/command-measure.svg);
 }
 
 
 /* Toolbars */
 .devtools-toolbar,
 .devtools-sidebar-tabs tabs {
   -moz-appearance: none;
   padding: 0;
@@ -122,16 +70,25 @@
 .devtools-toolbar checkbox .checkbox-label-box {
   border: none !important; /* overrides .checkbox-label-box from checkbox.css */
 }
 .devtools-toolbar checkbox .checkbox-label-box .checkbox-label {
   margin: 0 6px !important; /* overrides .checkbox-label from checkbox.css */
   padding: 0;
 }
 
+.devtools-separator {
+  margin: 0 2px;
+  width: 2px;
+  background-image: linear-gradient(transparent 15%, var(--theme-splitter-color) 15%, var(--theme-splitter-color) 85%, transparent 85%);
+  background-size: 1px 100%;
+  background-repeat: no-repeat;
+  background-position: 0, 1px, 2px;
+}
+
 /* Toolbar buttons */
 
 .devtools-menulist,
 .devtools-toolbarbutton,
 .devtools-button {
   -moz-appearance: none;
   background: transparent;
   min-height: 18px;
@@ -540,22 +497,16 @@
   margin-bottom: 0;
 }
 
 .devtools-searchinput > .textbox-input-box > .textbox-search-icons > .textbox-search-clear:hover,
 .devtools-filterinput > .textbox-input-box > .textbox-search-icons > .textbox-search-clear:hover {
   -moz-image-region: rect(0, 32px, 16px, 16px);
 }
 
-/* Close button */
-
-#toolbox-close::before {
-  background-image: var(--close-button-image);
-}
-
 /* In-tools sidebar */
 .devtools-sidebar-tabs {
   -moz-appearance: none;
   margin: 0;
 }
 
 .devtools-sidebar-tabs > tabpanels {
   -moz-appearance: none;
@@ -639,291 +590,16 @@
 }
 
 .devtools-sidebar-tabs tabs > tab[selected],
 .devtools-sidebar-tabs tabs > tab[selected]:hover:active {
   color: var(--theme-selection-color);
   background: var(--theme-selection-background);
 }
 
-/* Toolbox - moved from toolbox.css.
- * Rules that apply to the global toolbox like command buttons,
- * devtools tabs, docking buttons, etc. */
-
-#toolbox-controls > button,
-#toolbox-dock-buttons > button {
-  -moz-appearance: none;
-  -moz-user-focus: normal;
-  border: none;
-  margin: 0 4px;
-  min-width: 16px;
-  width: 16px;
-}
-
-/* Save space in Firebug theme */
-.theme-firebug #toolbox-controls button {
-  margin-inline-start: 0 !important;
-  min-width: 12px;
-  margin: 0 1px;
-}
-
-#toolbox-dock-bottom::before {
-  background-image: var(--dock-bottom-image);
-}
-
-#toolbox-dock-side::before {
-  background-image: var(--dock-side-image);
-}
-
-#toolbox-dock-window::before {
-  background-image: var(--dock-undock-image);
-}
-
-#toolbox-dock-bottom-minimize {
-  /* Bug 1177463 - The minimize button is currently hidden until we agree on
-     the UI for it, and until bug 1173849 is fixed too. */
-  display: none;
-}
-
-#toolbox-dock-bottom-minimize::before {
-  background-image: url("chrome://devtools/skin/images/dock-bottom-minimize@2x.png");
-}
-
-#toolbox-dock-bottom-minimize.minimized::before {
-  background-image: url("chrome://devtools/skin/images/dock-bottom-maximize@2x.png");
-}
-
-#toolbox-dock-window,
-#toolbox-dock-bottom,
-#toolbox-dock-side {
-  opacity: 0.8;
-}
-
-#toolbox-dock-window:hover,
-#toolbox-dock-bottom:hover,
-#toolbox-dock-side:hover {
-  opacity: 1;
-}
-
-.devtools-separator {
-  margin: 0 2px;
-  width: 2px;
-  background-image: linear-gradient(transparent 15%, var(--theme-splitter-color) 15%, var(--theme-splitter-color) 85%, transparent 85%);
-  background-size: 1px 100%;
-  background-repeat: no-repeat;
-  background-position: 0, 1px, 2px;
-}
-
-#toolbox-buttons:empty + .devtools-separator,
-.devtools-separator[invisible] {
-  visibility: hidden;
-}
-
-#toolbox-controls-separator {
-  margin: 0;
-}
-
-/* Command buttons */
-
-.command-button {
-  padding: 0;
-  margin: 0;
-  position: relative;
-  -moz-user-focus: normal;
-}
-
-.command-button::before {
-  opacity: 0.7;
-}
-
-.command-button:hover {
-  background-color: var(--toolbar-tab-hover);
-}
-
-.command-button:hover:active,
-.command-button[checked=true]:not(:hover) {
-  background-color: var(--toolbar-tab-hover-active)
-}
-
-.command-button:hover::before {
-  opacity: 0.85;
-}
-
-.command-button:hover:active::before,
-.command-button[checked=true]::before,
-.command-button[open=true]::before {
-  opacity: 1;
-}
-
-/* Tabs */
-
-.devtools-tabbar {
-  -moz-appearance: none;
-  min-height: 24px;
-  border: 0px solid;
-  border-bottom-width: 1px;
-  padding: 0;
-  background: var(--theme-tab-toolbar-background);
-  border-bottom-color: var(--theme-splitter-color);
-}
-
-#toolbox-tabs {
-  margin: 0;
-}
-
-.toolbox-panel {
-  display: -moz-box;
-  -moz-box-flex: 1;
-  visibility: collapse;
-}
-
-.toolbox-panel[selected] {
-  visibility: visible;
-}
-
-.devtools-tab {
-  -moz-appearance: none;
-  -moz-binding: url("chrome://global/content/bindings/general.xml#control-item");
-  -moz-box-align: center;
-  min-width: 32px;
-  min-height: 24px;
-  max-width: 100px;
-  margin: 0;
-  padding: 0;
-  border-style: solid;
-  border-width: 0;
-  border-inline-start-width: 1px;
-  -moz-box-align: center;
-  -moz-user-focus: normal;
-  -moz-box-flex: 1;
-}
-
-/* Save space on the tab-strip in Firebug theme */
-.theme-firebug .devtools-tab {
-  -moz-box-flex: initial;
-}
-
-.theme-dark .devtools-tab {
-  color: var(--theme-body-color-alt);
-  border-color: #42484f;
-}
-
-.theme-light .devtools-tab {
-  color: var(--theme-body-color);
-  border-color: var(--theme-splitter-color);
-}
-
-.theme-dark .devtools-tab:hover {
-  color: #ced3d9;
-}
-
-.devtools-tab:hover {
-  background-color: var(--toolbar-tab-hover);
-}
-
-.theme-dark .devtools-tab:hover:active {
-  color: var(--theme-selection-color);
-}
-
-.devtools-tab:hover:active {
-  background-color: var(--toolbar-tab-hover-active);
-}
-
-.theme-dark .devtools-tab:not([selected])[highlighted] {
-  background-color: hsla(99, 100%, 14%, .3);
-}
-
-.theme-light .devtools-tab:not([selected])[highlighted] {
-  background-color: rgba(44, 187, 15, .2);
-}
-
-/* Display execution pointer in the Debugger tab to indicate
-   that the debugger is paused. */
-.theme-firebug #toolbox-tab-jsdebugger.devtools-tab:not([selected])[highlighted] {
-  background-color: rgba(89, 178, 234, .2);
-  background-image: url(chrome://devtools/skin/images/firebug/tool-debugger-paused.svg);
-  background-repeat: no-repeat;
-  padding-left: 13px !important;
-  background-position: 3px 6px;
-}
-
-.devtools-tab > image {
-  border: none;
-  margin: 0;
-  margin-inline-start: 4px;
-  opacity: 0.6;
-  max-height: 16px;
-  width: 16px; /* Prevents collapse during theme switching */
-}
-
-.devtools-tab > label {
-  white-space: nowrap;
-  margin: 0 4px;
-}
-
-.devtools-tab:hover > image {
-  opacity: 0.8;
-}
-
-.devtools-tab:active > image,
-.devtools-tab[selected] > image {
-  opacity: 1;
-}
-
-.devtools-tabbar .devtools-tab[selected],
-.devtools-tabbar .devtools-tab[selected]:hover:active {
-  color: var(--theme-selection-color);
-  background-color: var(--theme-selection-background);
-}
-
-#toolbox-tabs .devtools-tab[selected],
-#toolbox-tabs .devtools-tab[highlighted] {
-  border-width: 0;
-  padding-inline-start: 1px;
-}
-
-#toolbox-tabs .devtools-tab[selected]:last-child,
-#toolbox-tabs .devtools-tab[highlighted]:last-child {
-  padding-inline-end: 1px;
-}
-
-#toolbox-tabs .devtools-tab[selected] + .devtools-tab,
-#toolbox-tabs .devtools-tab[highlighted] + .devtools-tab {
-  border-inline-start-width: 0;
-  padding-inline-start: 1px;
-}
-
-#toolbox-tabs .devtools-tab:first-child[selected] {
-  border-inline-start-width: 0;
-}
-
-#toolbox-tabs .devtools-tab:last-child {
-  border-inline-end-width: 1px;
-}
-
-.devtools-tab:not([highlighted]) > .highlighted-icon,
-.devtools-tab[selected] > .highlighted-icon,
-.devtools-tab:not([selected])[highlighted] > .default-icon {
-  visibility: collapse;
-}
-
-/* The options tab is special - it doesn't have the same parent
-   as the other tabs (toolbox-option-container vs toolbox-tabs) */
-#toolbox-option-container .devtools-tab:not([selected]) {
-  background-color: transparent;
-}
-#toolbox-option-container .devtools-tab {
-  border-color: transparent;
-  border-width: 0;
-  padding-inline-start: 1px;
-}
-#toolbox-tab-options > image {
-  margin: 0 8px;
-}
-
 /* Invert the colors of certain dark theme images for displaying
  * inside of the light theme.
  */
 .theme-light .devtools-tab[icon-invertable] > image,
 .theme-light .devtools-toolbarbutton > image,
 .theme-light .devtools-button::before,
 .theme-light #breadcrumb-separator-normal,
 .theme-light .scrollbutton-up > .toolbarbutton-icon,
@@ -943,41 +619,31 @@
 
 /* Since selected backgrounds are blue, we want to use the normal
  * (light) icons. */
 .theme-light .devtools-tab[icon-invertable][selected] > image,
 .theme-light .devtools-tab[icon-invertable][highlighted] > image {
   filter: none !important;
 }
 
-.theme-light .command-button:hover {
-  background-color: inherit;
-}
-
-.theme-light .command-button:hover:active,
-.theme-light .command-button[checked=true]:not(:hover) {
-  background-color: inherit;
-}
-
 .hidden-labels-box:not(.visible) > label,
 .hidden-labels-box.visible ~ .hidden-labels-box > label:last-child {
   display: none;
 }
 
 .devtools-invisible-splitter {
   border-color: transparent;
   background-color: transparent;
 }
 
 .devtools-horizontal-splitter,
 .devtools-side-splitter {
   background-color: var(--theme-splitter-color);
 }
 
-
 /* Throbbers */
 .devtools-throbber::before {
   content: "";
   display: inline-block;
   vertical-align: bottom;
   margin-inline-end: 0.5em;
   width: 1em;
   height: 1em;
--- a/devtools/client/themes/toolbox.css
+++ b/devtools/client/themes/toolbox.css
@@ -1,63 +1,370 @@
 /* vim:set ts=2 sw=2 sts=2 et: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
- /* Toolbox command buttons */
+:root {
+  --close-button-image: url(chrome://devtools/skin/images/close.svg);
+  --dock-bottom-image: url(chrome://devtools/skin/images/dock-bottom.svg);
+  --dock-side-image: url(chrome://devtools/skin/images/dock-side.svg);
+  --dock-undock-image: url(chrome://devtools/skin/images/dock-undock.svg);
+
+  --command-paintflashing-image: url(images/command-paintflashing.svg);
+  --command-screenshot-image: url(images/command-screenshot.svg);
+  --command-responsive-image: url(images/command-responsivemode.svg);
+  --command-scratchpad-image: url(images/command-scratchpad.svg);
+  --command-pick-image: url(images/command-pick.svg);
+  --command-frames-image: url(images/command-frames.svg);
+  --command-splitconsole-image: url(images/command-console.svg);
+  --command-noautohide-image: url(images/command-noautohide.svg);
+  --command-eyedropper-image: url(images/command-eyedropper.svg);
+  --command-rulers-image: url(images/command-rulers.svg);
+  --command-measure-image: url(images/command-measure.svg);
+}
+
+.theme-firebug {
+  --close-button-image: url(chrome://devtools/skin/images/firebug/close.svg);
+  --dock-bottom-image: url(chrome://devtools/skin/images/firebug/dock-bottom.svg);
+  --dock-side-image: url(chrome://devtools/skin/images/firebug/dock-side.svg);
+  --dock-undock-image: url(chrome://devtools/skin/images/firebug/dock-undock.svg);
+
+  --command-paintflashing-image: url(images/firebug/command-paintflashing.svg);
+  --command-screenshot-image: url(images/firebug/command-screenshot.svg);
+  --command-responsive-image: url(images/firebug/command-responsivemode.svg);
+  --command-scratchpad-image: url(images/firebug/command-scratchpad.svg);
+  --command-pick-image: url(images/firebug/command-pick.svg);
+  --command-frames-image: url(images/firebug/command-frames.svg);
+  --command-splitconsole-image: url(images/firebug/command-console.svg);
+  --command-noautohide-image: url(images/firebug/command-noautohide.svg);
+  --command-eyedropper-image: url(images/firebug/command-eyedropper.svg);
+  --command-rulers-image: url(images/firebug/command-rulers.svg);
+  --command-measure-image: url(images/firebug/command-measure.svg);
+}
+
+/* Toolbox tabbar */
+
+.devtools-tabbar {
+  -moz-appearance: none;
+  min-height: 24px;
+  border: 0px solid;
+  border-bottom-width: 1px;
+  padding: 0;
+  background: var(--theme-tab-toolbar-background);
+  border-bottom-color: var(--theme-splitter-color);
+}
+
+#toolbox-tabs {
+  margin: 0;
+}
+
+/* Toolbox tabs */
+
+.devtools-tab {
+  -moz-appearance: none;
+  -moz-binding: url("chrome://global/content/bindings/general.xml#control-item");
+  -moz-box-align: center;
+  min-width: 32px;
+  min-height: 24px;
+  max-width: 100px;
+  margin: 0;
+  padding: 0;
+  border-style: solid;
+  border-width: 0;
+  border-inline-start-width: 1px;
+  -moz-box-align: center;
+  -moz-user-focus: normal;
+  -moz-box-flex: 1;
+}
+
+/* Save space on the tab-strip in Firebug theme */
+.theme-firebug .devtools-tab {
+  -moz-box-flex: initial;
+}
+
+.theme-dark .devtools-tab {
+  color: var(--theme-body-color-alt);
+  border-color: #42484f;
+}
+
+.theme-light .devtools-tab {
+  color: var(--theme-body-color);
+  border-color: var(--theme-splitter-color);
+}
+
+.theme-dark .devtools-tab:hover {
+  color: #ced3d9;
+}
+
+.devtools-tab:hover {
+  background-color: var(--toolbar-tab-hover);
+}
+
+.theme-dark .devtools-tab:hover:active {
+  color: var(--theme-selection-color);
+}
+
+.devtools-tab:hover:active {
+  background-color: var(--toolbar-tab-hover-active);
+}
+
+.theme-dark .devtools-tab:not([selected])[highlighted] {
+  background-color: hsla(99, 100%, 14%, .3);
+}
+
+.theme-light .devtools-tab:not([selected])[highlighted] {
+  background-color: rgba(44, 187, 15, .2);
+}
+
+/* Display execution pointer in the Debugger tab to indicate
+   that the debugger is paused. */
+.theme-firebug #toolbox-tab-jsdebugger.devtools-tab:not([selected])[highlighted] {
+  background-color: rgba(89, 178, 234, .2);
+  background-image: url(chrome://devtools/skin/images/firebug/tool-debugger-paused.svg);
+  background-repeat: no-repeat;
+  padding-left: 13px !important;
+  background-position: 3px 6px;
+}
+
+.devtools-tab > image {
+  border: none;
+  margin: 0;
+  margin-inline-start: 4px;
+  opacity: 0.6;
+  max-height: 16px;
+  width: 16px; /* Prevents collapse during theme switching */
+}
+
+.devtools-tab > label {
+  white-space: nowrap;
+  margin: 0 4px;
+}
+
+.devtools-tab:hover > image {
+  opacity: 0.8;
+}
+
+.devtools-tab:active > image,
+.devtools-tab[selected] > image {
+  opacity: 1;
+}
+
+.devtools-tabbar .devtools-tab[selected],
+.devtools-tabbar .devtools-tab[selected]:hover:active {
+  color: var(--theme-selection-color);
+  background-color: var(--theme-selection-background);
+}
+
+#toolbox-tabs .devtools-tab[selected],
+#toolbox-tabs .devtools-tab[highlighted] {
+  border-width: 0;
+  padding-inline-start: 1px;
+}
+
+#toolbox-tabs .devtools-tab[selected]:last-child,
+#toolbox-tabs .devtools-tab[highlighted]:last-child {
+  padding-inline-end: 1px;
+}
+
+#toolbox-tabs .devtools-tab[selected] + .devtools-tab,
+#toolbox-tabs .devtools-tab[highlighted] + .devtools-tab {
+  border-inline-start-width: 0;
+  padding-inline-start: 1px;
+}
+
+#toolbox-tabs .devtools-tab:first-child[selected] {
+  border-inline-start-width: 0;
+}
+
+#toolbox-tabs .devtools-tab:last-child {
+  border-inline-end-width: 1px;
+}
+
+.devtools-tab:not([highlighted]) > .highlighted-icon,
+.devtools-tab[selected] > .highlighted-icon,
+.devtools-tab:not([selected])[highlighted] > .default-icon {
+  visibility: collapse;
+}
+
+/* The options tab is special - it doesn't have the same parent
+   as the other tabs (toolbox-option-container vs toolbox-tabs) */
+#toolbox-option-container .devtools-tab:not([selected]) {
+  background-color: transparent;
+}
+#toolbox-option-container .devtools-tab {
+  border-color: transparent;
+  border-width: 0;
+  padding-inline-start: 1px;
+}
+#toolbox-tab-options > image {
+  margin: 0 8px;
+}
+
+/* Toolbox controls */
+
+#toolbox-controls > button,
+#toolbox-dock-buttons > button {
+  -moz-appearance: none;
+  -moz-user-focus: normal;
+  border: none;
+  margin: 0 4px;
+  min-width: 16px;
+  width: 16px;
+}
+
+/* Save space in Firebug theme */
+.theme-firebug #toolbox-controls button {
+  margin-inline-start: 0 !important;
+  min-width: 12px;
+  margin: 0 1px;
+}
+
+#toolbox-close::before {
+  background-image: var(--close-button-image);
+}
+
+#toolbox-dock-bottom::before {
+  background-image: var(--dock-bottom-image);
+}
+
+#toolbox-dock-side::before {
+  background-image: var(--dock-side-image);
+}
+
+#toolbox-dock-window::before {
+  background-image: var(--dock-undock-image);
+}
+
+#toolbox-dock-bottom-minimize {
+  /* Bug 1177463 - The minimize button is currently hidden until we agree on
+     the UI for it, and until bug 1173849 is fixed too. */
+  display: none;
+}
+
+#toolbox-dock-bottom-minimize::before {
+  background-image: url("chrome://devtools/skin/images/dock-bottom-minimize@2x.png");
+}
+
+#toolbox-dock-bottom-minimize.minimized::before {
+  background-image: url("chrome://devtools/skin/images/dock-bottom-maximize@2x.png");
+}
+
+#toolbox-buttons:empty + .devtools-separator,
+.devtools-separator[invisible] {
+  visibility: hidden;
+}
+
+#toolbox-controls-separator {
+  margin: 0;
+}
+
+/* Command buttons */
+
+.command-button {
+  padding: 0;
+  margin: 0;
+  position: relative;
+  -moz-user-focus: normal;
+}
+
+.command-button::before {
+  opacity: 0.7;
+}
+
+.command-button:hover {
+  background-color: var(--toolbar-tab-hover);
+}
+
+.theme-light .command-button:hover {
+  background-color: inherit;
+}
+
+.command-button:hover:active,
+.command-button[checked=true]:not(:hover) {
+  background-color: var(--toolbar-tab-hover-active)
+}
+
+.theme-light .command-button:hover:active,
+.theme-light .command-button[checked=true]:not(:hover) {
+  background-color: inherit;
+}
+
+.command-button:hover::before {
+  opacity: 0.85;
+}
+
+.command-button:hover:active::before,
+.command-button[checked=true]::before,
+.command-button[open=true]::before {
+  opacity: 1;
+}
+
+/* Command button images */
 
 #command-button-paintflashing::before {
-   background-image: var(--command-paintflashing-image);
- }
+  background-image: var(--command-paintflashing-image);
+}
 
 #command-button-screenshot::before {
-   background-image: var(--command-screenshot-image);
- }
+  background-image: var(--command-screenshot-image);
+}
 
 #command-button-responsive::before {
- background-image: var(--command-responsive-image);
+  background-image: var(--command-responsive-image);
 }
 
 #command-button-scratchpad::before {
- background-image: var(--command-scratchpad-image);
+  background-image: var(--command-scratchpad-image);
 }
 
 #command-button-pick::before {
- background-image: var(--command-pick-image);
+  background-image: var(--command-pick-image);
 }
 
 #command-button-splitconsole::before {
- background-image: var(--command-splitconsole-image);
+  background-image: var(--command-splitconsole-image);
 }
 
 #command-button-noautohide::before {
- background-image: var(--command-noautohide-image);
+  background-image: var(--command-noautohide-image);
 }
 
 #command-button-eyedropper::before {
- background-image: var(--command-eyedropper-image);
+  background-image: var(--command-eyedropper-image);
 }
 
 #command-button-rulers::before {
- background-image: var(--command-rulers-image);
+  background-image: var(--command-rulers-image);
 }
 
 #command-button-measure::before {
- background-image: var(--command-measure-image);
+  background-image: var(--command-measure-image);
 }
 
 #command-button-frames::before {
- background-image: var(--command-frames-image);
+  background-image: var(--command-frames-image);
 }
 
 #command-button-frames {
- background: url("chrome://devtools/skin/images/dropmarker.svg") no-repeat right;
+  background: url("chrome://devtools/skin/images/dropmarker.svg") no-repeat right;
 
- /* Override background-size from the command-button.
+  /* Override background-size from the command-button.
    The drop down arrow is smaller */
- background-size: 8px 4px !important;
- min-width: 32px;
+  background-size: 8px 4px !important;
+  min-width: 32px;
 }
 
 #command-button-frames:-moz-dir(rtl) {
- background-position: left;
+  background-position: left;
 }
+
+/* Toolbox panels */
+
+.toolbox-panel {
+  display: -moz-box;
+  -moz-box-flex: 1;
+  visibility: collapse;
+}
+
+.toolbox-panel[selected] {
+  visibility: visible;
+}
\ No newline at end of file
--- a/devtools/client/themes/tooltips.css
+++ b/devtools/client/themes/tooltips.css
@@ -200,18 +200,23 @@
 .tooltip-arrow {
   position: relative;
   height: 16px;
   width: 32px;
   overflow: hidden;
   flex-shrink: 0;
 }
 
-.tooltip-arrow:-moz-locale-dir(rtl) {
-  align-self: flex-end;
+/* In RTL locales, only use RTL on the tooltip content, keep LTR for positioning */
+.tooltip-container:-moz-locale-dir(rtl) {
+  direction: ltr;
+}
+
+.tooltip-panel:-moz-locale-dir(rtl) {
+  direction: rtl;
 }
 
 .tooltip-top .tooltip-arrow {
   margin-top: -3px;
 }
 
 .tooltip-bottom .tooltip-arrow {
   margin-bottom: -3px;
--- a/devtools/client/webconsole/net/components/post-tab.js
+++ b/devtools/client/webconsole/net/components/post-tab.js
@@ -1,18 +1,17 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 "use strict";
 
 const React = require("devtools/client/shared/vendor/react");
 
 // Reps
-const { createFactories } = require("devtools/client/shared/components/reps/rep-utils");
-const { parseURLEncodedText } = require("devtools/client/shared/components/reps/url");
+const { createFactories, parseURLEncodedText } = require("devtools/client/shared/components/reps/rep-utils");
 const TreeView = React.createFactory(require("devtools/client/shared/components/tree/tree-view"));
 const { Rep } = createFactories(require("devtools/client/shared/components/reps/rep"));
 
 // Network
 const NetInfoParams = React.createFactory(require("./net-info-params"));
 const NetInfoGroupList = React.createFactory(require("./net-info-group-list"));
 const Spinner = React.createFactory(require("./spinner"));
 const SizeLimit = React.createFactory(require("./size-limit"));
--- a/devtools/client/webconsole/net/net-request.js
+++ b/devtools/client/webconsole/net/net-request.js
@@ -3,17 +3,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 "use strict";
 
 // React
 const React = require("devtools/client/shared/vendor/react");
 const ReactDOM = require("devtools/client/shared/vendor/react-dom");
 
 // Reps
-const { parseURLParams } = require("devtools/client/shared/components/reps/url");
+const { parseURLParams } = require("devtools/client/shared/components/reps/rep-utils");
 
 // Network
 const { cancelEvent, isLeftClick } = require("./utils/events");
 const NetInfoBody = React.createFactory(require("./components/net-info-body"));
 const DataProvider = require("./data-provider");
 
 // Constants
 const XHTML_NS = "http://www.w3.org/1999/xhtml";
--- a/devtools/server/actors/css-properties.js
+++ b/devtools/server/actors/css-properties.js
@@ -23,44 +23,57 @@ exports.CssPropertiesActor = ActorClassW
     this.parent = parent;
   },
 
   destroy() {
     Actor.prototype.destroy.call(this);
   },
 
   getCSSDatabase() {
-    const db = {};
-    const properties = DOMUtils.getCSSPropertyNames(DOMUtils.INCLUDE_ALIASES);
-
-    properties.forEach(name => {
-      // Get the list of CSS types this property supports.
-      let supports = [];
-      for (let type in CSS_TYPES) {
-        if (safeCssPropertySupportsType(name, DOMUtils["TYPE_" + type])) {
-          supports.push(CSS_TYPES[type]);
-        }
-      }
+    const properties = generateCssProperties();
+    const pseudoElements = DOMUtils.getCSSPseudoElementNames();
 
-      // In order to maintain any backwards compatible changes when debugging older
-      // clients, take the definition from the static CSS properties database, and fill it
-      // in with the most recent property definition from the server.
-      const clientDefinition = CSS_PROPERTIES[name] || {};
-      const serverDefinition = {
-        isInherited: DOMUtils.isInheritedProperty(name),
-        supports
-      };
-      db[name] = Object.assign(clientDefinition, serverDefinition);
-    });
-
-    return db;
+    return { properties, pseudoElements };
   }
 });
 
 /**
+ * Generate the CSS properties object. Every key is the property name, while
+ * the values are objects that contain information about that property.
+ *
+ * @return {Object}
+ */
+function generateCssProperties() {
+  const properties = {};
+  const propertyNames = DOMUtils.getCSSPropertyNames(DOMUtils.INCLUDE_ALIASES);
+
+  propertyNames.forEach(name => {
+    // Get the list of CSS types this property supports.
+    let supports = [];
+    for (let type in CSS_TYPES) {
+      if (safeCssPropertySupportsType(name, DOMUtils["TYPE_" + type])) {
+        supports.push(CSS_TYPES[type]);
+      }
+    }
+
+    // In order to maintain any backwards compatible changes when debugging older
+    // clients, take the definition from the static CSS properties database, and fill it
+    // in with the most recent property definition from the server.
+    const clientDefinition = CSS_PROPERTIES[name] || {};
+    const serverDefinition = {
+      isInherited: DOMUtils.isInheritedProperty(name),
+      supports
+    };
+    properties[name] = Object.assign(clientDefinition, serverDefinition);
+  });
+
+  return properties;
+}
+
+/**
  * Test if a CSS is property is known using server-code.
  *
  * @param {string} name
  * @return {Boolean}
  */
 function isCssPropertyKnown(name) {
   try {
     // If the property name is unknown, the cssPropertyIsShorthand
--- a/devtools/server/actors/highlighters.css
+++ b/devtools/server/actors/highlighters.css
@@ -166,31 +166,35 @@
 }
 
 /* Text container */
 
 :-moz-native-anonymous .box-model-nodeinfobar-text {
   overflow: hidden;
   white-space: nowrap;
   direction: ltr;
-  text-align: center;
   padding-bottom: 1px;
+  display: flex;
 }
 
 :-moz-native-anonymous .box-model-nodeinfobar-tagname {
   color: hsl(285,100%, 75%);
 }
 
 :-moz-native-anonymous .box-model-nodeinfobar-id {
   color: hsl(103, 46%, 54%);
+  overflow: hidden;
+  text-overflow: ellipsis;
 }
 
 :-moz-native-anonymous .box-model-nodeinfobar-classes,
 :-moz-native-anonymous .box-model-nodeinfobar-pseudo-classes {
   color: hsl(200, 74%, 57%);
+  overflow: hidden;
+  text-overflow: ellipsis;
 }
 
 :-moz-native-anonymous .box-model-nodeinfobar-dimensions {
   color: hsl(210, 30%, 85%);
   border-inline-start: 1px solid #5a6169;
   margin-inline-start: 6px;
   padding-inline-start: 6px;
 }
--- a/devtools/server/actors/webbrowser.js
+++ b/devtools/server/actors/webbrowser.js
@@ -377,16 +377,35 @@ BrowserTabList.prototype._getActorForBro
   actor = new BrowserTabActor(this._connection, browser);
   this._actorByBrowser.set(browser, actor);
   this._checkListening();
   return promise.resolve(actor);
 };
 
 BrowserTabList.prototype.getTab = function ({ outerWindowID, tabId }) {
   if (typeof outerWindowID == "number") {
+    // First look for in-process frames with this ID
+    let window = Services.wm.getOuterWindowWithId(outerWindowID);
+    // Safety check to prevent debugging top level window via getTab
+    if (window instanceof Ci.nsIDOMChromeWindow) {
+      return promise.reject({
+        error: "forbidden",
+        message: "Window with outerWindowID '" + outerWindowID + "' is chrome"
+      });
+    }
+    if (window) {
+      let iframe = window.QueryInterface(Ci.nsIInterfaceRequestor)
+                         .getInterface(Ci.nsIDOMWindowUtils)
+                         .containerElement;
+      if (iframe) {
+        return this._getActorForBrowser(iframe);
+      }
+    }
+    // Then also look on registered <xul:browsers> when using outerWindowID for
+    // OOP tabs
     for (let browser of this._getBrowsers()) {
       if (browser.outerWindowID == outerWindowID) {
         return this._getActorForBrowser(browser);
       }
     }
     return promise.reject({
       error: "noTab",
       message: "Unable to find tab with outerWindowID '" + outerWindowID + "'"
@@ -1120,20 +1139,17 @@ TabActor.prototype = {
     }
 
     // Tell the thread actor that the tab is closed, so that it may terminate
     // instead of resuming the debuggee script.
     if (this._attached) {
       this.threadActor._tabClosed = true;
     }
 
-    if (this._detach()) {
-      this.conn.send({ from: this.actorID,
-                       type: "tabDetached" });
-    }
+    this._detach();
 
     Object.defineProperty(this, "docShell", {
       value: null,
       configurable: true
     });
 
     this._extraActors = null;
 
@@ -1515,16 +1531,19 @@ TabActor.prototype = {
 
     if (this._workerActorPool !== null) {
       this.conn.removeActorPool(this._workerActorPool);
       this._workerActorPool = null;
     }
 
     this._attached = false;
 
+    this.conn.send({ from: this.actorID,
+                     type: "tabDetached" });
+
     return true;
   },
 
   // Protocol Request Handlers
 
   onAttach(request) {
     if (this.exited) {
       return { type: "exited" };
--- a/devtools/shared/css-properties-db.js
+++ b/devtools/shared/css-properties-db.js
@@ -40,16 +40,31 @@ exports.COLOR_TAKING_FUNCTIONS = ["linea
  */
 exports.ANGLE_TAKING_FUNCTIONS = ["linear-gradient", "-moz-linear-gradient",
                                   "repeating-linear-gradient",
                                   "-moz-repeating-linear-gradient", "rotate", "rotateX",
                                   "rotateY", "rotateZ", "rotate3d", "skew", "skewX",
                                   "skewY", "hue-rotate"];
 
 /**
+ * The list of all CSS Pseudo Elements. This list can be generated from:
+ *
+ * let domUtils = Cc["@mozilla.org/inspector/dom-utils;1"].getService(Ci.inIDOMUtils);
+ * domUtils.getCSSPseudoElementNames();
+ */
+exports.PSEUDO_ELEMENTS = [":after", ":before", ":backdrop", ":first-letter",
+                           ":first-line", ":-moz-selection", ":-moz-focus-inner",
+                           ":-moz-focus-outer", ":-moz-list-bullet",
+                           ":-moz-list-number", ":-moz-math-anonymous",
+                           ":-moz-progress-bar", ":-moz-range-track",
+                           ":-moz-range-progress", ":-moz-range-thumb",
+                           ":-moz-meter-bar", ":-moz-placeholder",
+                           ":-moz-color-swatch"];
+
+/**
  * This list is generated from the output of the CssPropertiesActor. If a server
  * does not support the actor, this is loaded as a backup. This list does not
  * guarantee that the server actually supports these CSS properties.
  */
 exports.CSS_PROPERTIES = {
   "align-content": {
     isInherited: false,
     supports: []
@@ -1738,8 +1753,13 @@ exports.CSS_PROPERTIES = {
     isInherited: false,
     supports: []
   },
   "-webkit-user-select": {
     isInherited: false,
     supports: []
   }
 };
+
+exports.CSS_PROPERTIES_DB = {
+  properties: exports.CSS_PROPERTIES,
+  pseudoElements: exports.PSEUDO_ELEMENTS
+};
--- a/devtools/shared/fronts/css-properties.js
+++ b/devtools/shared/fronts/css-properties.js
@@ -1,17 +1,17 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 "use strict";
 
 const { FrontClassWithSpec, Front } = require("devtools/shared/protocol");
 const { cssPropertiesSpec } = require("devtools/shared/specs/css-properties");
 const { Task } = require("devtools/shared/task");
-const { CSS_PROPERTIES } = require("devtools/shared/css-properties-db");
+const { CSS_PROPERTIES_DB } = require("devtools/shared/css-properties-db");
 
 /**
  * Build up a regular expression that matches a CSS variable token. This is an
  * ident token that starts with two dashes "--".
  *
  * https://www.w3.org/TR/css-syntax-3/#ident-token-diagram
  */
 var NON_ASCII = "[^\\x00-\\x7F]";
@@ -48,24 +48,25 @@ const CssPropertiesFront = FrontClassWit
 exports.CssPropertiesFront = CssPropertiesFront;
 
 /**
  * Ask questions to a CSS database. This class does not care how the database
  * gets loaded in, only the questions that you can ask to it.
  * Prototype functions are bound to 'this' so they can be passed around as helper
  * functions.
  *
- * @param {Array}  propertiesList
- *                 A list of known properties.
+ * @param {Object} db
+ *                 A database of CSS properties
  * @param {Object} inheritedList
  *                 The key is the property name, the value is whether or not
  *                 that property is inherited.
  */
-function CssProperties(properties) {
-  this.properties = properties;
+function CssProperties(db) {
+  this.properties = db.properties;
+  this.pseudoElements = db.pseudoElements;
 
   this.isKnown = this.isKnown.bind(this);
   this.isInherited = this.isInherited.bind(this);
   this.supportsType = this.supportsType.bind(this);
 }
 
 CssProperties.prototype = {
   /**
@@ -123,31 +124,41 @@ exports.initCssProperties = Task.async(f
 
   let db, front;
 
   // Get the list dynamically if the cssProperties actor exists.
   if (toolbox.target.hasActor("cssProperties")) {
     front = CssPropertiesFront(client, toolbox.target.form);
     db = yield front.getCSSDatabase();
 
-    // Even if the target has the cssProperties actor, it may not be the latest version.
-    // So, the "supports" data may be missing.
-    // Start with the server's list (because that's the correct one), and add the supports
-    // information if required.
-    if (!db.color.supports) {
-      for (let name in db) {
-        if (typeof CSS_PROPERTIES[name] === "object") {
-          db[name].supports = CSS_PROPERTIES[name].supports;
+    // Even if the target has the cssProperties actor, the returned data may
+    // not be in the same shape or have all of the data we need. The following
+    // code normalizes this data.
+
+    // Firefox 49's getCSSDatabase() just returned the properties object, but
+    // now it returns an object with multiple types of CSS information.
+    if (!db.properties) {
+      db = { properties: db };
+    }
+
+    // Fill in any missing DB information from the static database.
+    db = Object.assign({}, CSS_PROPERTIES_DB, db);
+
+    // Add "supports" information to the css properties if it's missing.
+    if (!db.properties.color.supports) {
+      for (let name in db.properties) {
+        if (typeof CSS_PROPERTIES_DB.properties[name] === "object") {
+          db.properties[name].supports = CSS_PROPERTIES_DB.properties[name].supports;
         }
       }
     }
   } else {
     // The target does not support this actor, so require a static list of supported
     // properties.
-    db = CSS_PROPERTIES;
+    db = CSS_PROPERTIES_DB;
   }
 
   const cssProperties = new CssProperties(db);
   cachedCssProperties.set(client, {cssProperties, front});
   return {cssProperties, front};
 });
 
 /**
new file mode 100644
--- /dev/null
+++ b/devtools/shared/tests/unit/test_css-properties-db.js
@@ -0,0 +1,36 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Test that css-properties-db matches platform.
+
+"use strict";
+
+const DOMUtils = Components.classes["@mozilla.org/inspector/dom-utils;1"]
+                           .getService(Components.interfaces.inIDOMUtils);
+
+const {PSEUDO_ELEMENTS} = require("devtools/shared/css-properties-db");
+
+function run_test() {
+  // Check that the platform and client match for pseudo elements.
+  let foundPseudoElements = 0;
+  const platformPseudoElements = DOMUtils.getCSSPseudoElementNames();
+  const instructions = "If this assertion fails then it means that pseudo elements " +
+                       "have been added, removed, or changed on the platform and need " +
+                       "to be updated on the static client-side list of pseudo " +
+                       "elements within the devtools. " +
+                       "See devtools/shared/css-properties-db.js and " +
+                       "exports.PSEUDO_ELEMENTS for information on how to update the " +
+                       "list of pseudo elements to fix this test.";
+
+  for (let element of PSEUDO_ELEMENTS) {
+    const hasElement = platformPseudoElements.includes(element);
+    ok(hasElement,
+       `"${element}" pseudo element from the client-side CSS properties database was ` +
+       `found on the platform. ${instructions}`);
+    foundPseudoElements += hasElement ? 1 : 0;
+  }
+
+  equal(foundPseudoElements, platformPseudoElements.length,
+        `The client side CSS properties database of psuedo element names should match ` +
+        `those found on the platform. ${instructions}`);
+}
--- a/devtools/shared/tests/unit/xpcshell.ini
+++ b/devtools/shared/tests/unit/xpcshell.ini
@@ -4,16 +4,17 @@ head = head_devtools.js
 tail =
 firefox-appdir = browser
 skip-if = toolkit == 'android' || toolkit == 'gonk'
 support-files =
   exposeLoader.js
 
 [test_assert.js]
 [test_csslexer.js]
+[test_css-properties-db.js]
 [test_fetch-chrome.js]
 [test_fetch-file.js]
 [test_fetch-http.js]
 [test_fetch-resource.js]
 [test_flatten.js]
 [test_indentation.js]
 [test_independent_loaders.js]
 [test_invisible_loader.js]
--- a/docshell/base/nsAboutRedirector.cpp
+++ b/docshell/base/nsAboutRedirector.cpp
@@ -60,17 +60,18 @@ static RedirEntry kRedirMap[] = {
 #ifdef MOZ_DEVTOOLS_ALL
   {
     "debugging", "chrome://devtools/content/aboutdebugging/aboutdebugging.xhtml",
     nsIAboutModule::ALLOW_SCRIPT
   },
 #endif
   {
     "license", "chrome://global/content/license.html",
-    nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT
+    nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT |
+      nsIAboutModule::MAKE_LINKABLE
   },
   {
     "logo", "chrome://branding/content/about.png",
     nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT |
     // Linkable for testing reasons.
     nsIAboutModule::MAKE_LINKABLE
   },
   {
--- a/docshell/base/nsDocShell.cpp
+++ b/docshell/base/nsDocShell.cpp
@@ -762,30 +762,29 @@ nsDocShell::nsDocShell()
   , mAppType(nsIDocShell::APP_TYPE_UNKNOWN)
   , mLoadType(0)
   , mMarginWidth(-1)
   , mMarginHeight(-1)
   , mItemType(typeContent)
   , mPreviousTransIndex(-1)
   , mLoadedTransIndex(-1)
   , mSandboxFlags(0)
+  , mOrientationLock(eScreenOrientation_None)
   , mFullscreenAllowed(CHECK_ATTRIBUTES)
-  , mOrientationLock(eScreenOrientation_None)
   , mCreated(false)
   , mAllowSubframes(true)
   , mAllowPlugins(true)
   , mAllowJavascript(true)
   , mAllowMetaRedirects(true)
   , mAllowImages(true)
   , mAllowMedia(true)
   , mAllowDNSPrefetch(true)
   , mAllowWindowControl(true)
   , mAllowContentRetargeting(true)
   , mAllowContentRetargetingOnChildren(true)
-  , mCreatingDocument(false)
   , mUseErrorPages(false)
   , mObserveErrorPages(true)
   , mAllowAuth(true)
   , mAllowKeywordFixup(false)
   , mIsOffScreenBrowser(false)
   , mIsActive(true)
   , mDisableMetaRefreshWhenInactive(false)
   , mIsPrerendered(false)
@@ -798,24 +797,25 @@ nsDocShell::nsDocShell()
   , mCanExecuteScripts(false)
   , mFiredUnloadEvent(false)
   , mEODForCurrentDocument(false)
   , mURIResultedInDocument(false)
   , mIsBeingDestroyed(false)
   , mIsExecutingOnLoadHandler(false)
   , mIsPrintingOrPP(false)
   , mSavingOldViewer(false)
+  , mAffectPrivateSessionLifetime(true)
+  , mInvisible(false)
+  , mHasLoadedNonBlankURI(false)
+  , mBlankTiming(false)
+  , mCreatingDocument(false)
 #ifdef DEBUG
   , mInEnsureScriptEnv(false)
 #endif
-  , mAffectPrivateSessionLifetime(true)
-  , mInvisible(false)
-  , mHasLoadedNonBlankURI(false)
   , mDefaultLoadFlags(nsIRequest::LOAD_NORMAL)
-  , mBlankTiming(false)
   , mFrameType(FRAME_TYPE_REGULAR)
   , mPrivateBrowsingId(0)
   , mParentCharsetSource(0)
   , mJSRunToCompletionDepth(0)
 {
   AssertOriginAttributesMatchPrivateBrowsing();
   mHistoryID = ++gDocshellIDCounter;
   if (gDocShellCount++ == 0) {
--- a/docshell/base/nsDocShell.h
+++ b/docshell/base/nsDocShell.h
@@ -893,120 +893,124 @@ protected:
   // Index into the SHTransaction list, indicating the previous and current
   // transaction at the time that this DocShell begins to load
   int32_t mPreviousTransIndex;
   int32_t mLoadedTransIndex;
 
   uint32_t mSandboxFlags;
   nsWeakPtr mOnePermittedSandboxedNavigator;
 
+  // The orientation lock as described by
+  // https://w3c.github.io/screen-orientation/
+  mozilla::dom::ScreenOrientationInternal mOrientationLock;
+
   // mFullscreenAllowed stores how we determine whether fullscreen is allowed
   // when GetFullscreenAllowed() is called. Fullscreen is allowed in a
   // docshell when all containing iframes have the allowfullscreen
   // attribute set to true. When mFullscreenAllowed is CHECK_ATTRIBUTES
   // we check this docshell's containing frame for the allowfullscreen
   // attribute, and recurse onto the parent docshell to ensure all containing
   // frames also have the allowfullscreen attribute. If we find an ancestor
   // docshell with mFullscreenAllowed not equal to CHECK_ATTRIBUTES, we've
   // reached a content boundary, and mFullscreenAllowed denotes whether the
   // parent across the content boundary has allowfullscreen=true in all its
   // containing iframes. mFullscreenAllowed defaults to CHECK_ATTRIBUTES and
   // is set otherwise when docshells which are content boundaries are created.
-  enum FullscreenAllowedState
+  enum FullscreenAllowedState : uint8_t
   {
     CHECK_ATTRIBUTES,
     PARENT_ALLOWS,
     PARENT_PROHIBITS
   };
   FullscreenAllowedState mFullscreenAllowed;
 
-  // The orientation lock as described by
-  // https://w3c.github.io/screen-orientation/
-  mozilla::dom::ScreenOrientationInternal mOrientationLock;
-
   // Cached value of the "browser.xul.error_pages.enabled" preference.
   static bool sUseErrorPages;
 
-  bool mCreated;
-  bool mAllowSubframes;
-  bool mAllowPlugins;
-  bool mAllowJavascript;
-  bool mAllowMetaRedirects;
-  bool mAllowImages;
-  bool mAllowMedia;
-  bool mAllowDNSPrefetch;
-  bool mAllowWindowControl;
-  bool mAllowContentRetargeting;
-  bool mAllowContentRetargetingOnChildren;
-  bool mCreatingDocument; // (should be) debugging only
-  bool mUseErrorPages;
-  bool mObserveErrorPages;
-  bool mAllowAuth;
-  bool mAllowKeywordFixup;
-  bool mIsOffScreenBrowser;
-  bool mIsActive;
-  bool mDisableMetaRefreshWhenInactive;
-  bool mIsPrerendered;
-  bool mIsAppTab;
-  bool mUseGlobalHistory;
-  bool mUseRemoteTabs;
-  bool mDeviceSizeIsPageSize;
-  bool mWindowDraggingAllowed;
-  bool mInFrameSwap;
+  bool mCreated : 1;
+  bool mAllowSubframes : 1;
+  bool mAllowPlugins : 1;
+  bool mAllowJavascript : 1;
+  bool mAllowMetaRedirects : 1;
+  bool mAllowImages : 1;
+  bool mAllowMedia : 1;
+  bool mAllowDNSPrefetch : 1;
+  bool mAllowWindowControl : 1;
+  bool mAllowContentRetargeting : 1;
+  bool mAllowContentRetargetingOnChildren : 1;
+  bool mUseErrorPages : 1;
+  bool mObserveErrorPages : 1;
+  bool mAllowAuth : 1;
+  bool mAllowKeywordFixup : 1;
+  bool mIsOffScreenBrowser : 1;
+  bool mIsActive : 1;
+  bool mDisableMetaRefreshWhenInactive : 1;
+  bool mIsPrerendered : 1;
+  bool mIsAppTab : 1;
+  bool mUseGlobalHistory : 1;
+  bool mUseRemoteTabs : 1;
+  bool mDeviceSizeIsPageSize : 1;
+  bool mWindowDraggingAllowed : 1;
+  bool mInFrameSwap : 1;
 
   // Because scriptability depends on the mAllowJavascript values of our
   // ancestors, we cache the effective scriptability and recompute it when
   // it might have changed;
-  bool mCanExecuteScripts;
+  bool mCanExecuteScripts : 1;
   void RecomputeCanExecuteScripts();
 
   // This boolean is set to true right before we fire pagehide and generally
   // unset when we embed a new content viewer. While it's true no navigation
   // is allowed in this docshell.
-  bool mFiredUnloadEvent;
+  bool mFiredUnloadEvent : 1;
 
   // this flag is for bug #21358. a docshell may load many urls
   // which don't result in new documents being created (i.e. a new
   // content viewer) we want to make sure we don't call a on load
   // event more than once for a given content viewer.
-  bool mEODForCurrentDocument;
-  bool mURIResultedInDocument;
+  bool mEODForCurrentDocument : 1;
+  bool mURIResultedInDocument : 1;
 
-  bool mIsBeingDestroyed;
+  bool mIsBeingDestroyed : 1;
 
-  bool mIsExecutingOnLoadHandler;
+  bool mIsExecutingOnLoadHandler : 1;
 
   // Indicates that a DocShell in this "docshell tree" is printing
-  bool mIsPrintingOrPP;
+  bool mIsPrintingOrPP : 1;
 
   // Indicates to CreateContentViewer() that it is safe to cache the old
   // presentation of the page, and to SetupNewViewer() that the old viewer
   // should be passed a SHEntry to save itself into.
-  bool mSavingOldViewer;
+  bool mSavingOldViewer : 1;
 
   // @see nsIDocShellHistory::createdDynamically
-  bool mDynamicallyCreated;
+  bool mDynamicallyCreated : 1;
+  bool mAffectPrivateSessionLifetime : 1;
+  bool mInvisible : 1;
+  bool mHasLoadedNonBlankURI : 1;
+
+  // This flag means that mTiming has been initialized but nulled out.
+  // We will check the innerWin's timing before creating a new one
+  // in MaybeInitTiming()
+  bool mBlankTiming : 1;
+
+  // The following two fields cannot be declared as bit fields
+  // because of uses with AutoRestore.
+  bool mCreatingDocument; // (should be) debugging only
 #ifdef DEBUG
   bool mInEnsureScriptEnv;
 #endif
-  bool mAffectPrivateSessionLifetime;
-  bool mInvisible;
-  bool mHasLoadedNonBlankURI;
+
   uint64_t mHistoryID;
   uint32_t mDefaultLoadFlags;
 
   static nsIURIFixup* sURIFixup;
 
   RefPtr<nsDOMNavigationTiming> mTiming;
 
-  // This flag means that mTiming has been initialized but nulled out.
-  // We will check the innerWin's timing before creating a new one
-  // in MaybeInitTiming()
-  bool mBlankTiming;
-
   // Are we a regular frame, a browser frame, or an app frame?
   uint32_t mFrameType;
 
   // This represents the state of private browsing in the docshell.
   // Currently treated as a binary value: 1 - in private mode, 0 - not private mode
   // On content docshells mPrivateBrowsingId == mOriginAttributes.mPrivateBrowsingId
   // On chrome docshells this value will be set, but not have the corresponding
   // origin attribute set.
--- a/dom/base/FormData.cpp
+++ b/dom/base/FormData.cpp
@@ -124,48 +124,54 @@ FormData::Append(const nsAString& aName,
   if (NS_WARN_IF(aRv.Failed())) {
     return;
   }
 
   AddNameBlobOrNullPair(aName, file);
 }
 
 void
+FormData::Append(const nsAString& aName, Directory* aDirectory)
+{
+  AddNameDirectoryPair(aName, aDirectory);
+}
+
+void
 FormData::Delete(const nsAString& aName)
 {
   // We have to use this slightly awkward for loop since uint32_t >= 0 is an
   // error for being always true.
   for (uint32_t i = mFormData.Length(); i-- > 0; ) {
     if (aName.Equals(mFormData[i].name)) {
       mFormData.RemoveElementAt(i);
     }
   }
 }
 
 void
 FormData::Get(const nsAString& aName,
-              Nullable<OwningBlobOrUSVString>& aOutValue)
+              Nullable<OwningBlobOrDirectoryOrUSVString>& aOutValue)
 {
   for (uint32_t i = 0; i < mFormData.Length(); ++i) {
     if (aName.Equals(mFormData[i].name)) {
       aOutValue.SetValue() = mFormData[i].value;
       return;
     }
   }
 
   aOutValue.SetNull();
 }
 
 void
 FormData::GetAll(const nsAString& aName,
-                 nsTArray<OwningBlobOrUSVString>& aValues)
+                 nsTArray<OwningBlobOrDirectoryOrUSVString>& aValues)
 {
   for (uint32_t i = 0; i < mFormData.Length(); ++i) {
     if (aName.Equals(mFormData[i].name)) {
-      OwningBlobOrUSVString* element = aValues.AppendElement();
+      OwningBlobOrDirectoryOrUSVString* element = aValues.AppendElement();
       *element = mFormData[i].value;
     }
   }
 }
 
 bool
 FormData::Has(const nsAString& aName)
 {
@@ -195,16 +201,26 @@ FormData::AddNameBlobOrNullPair(const ns
     return rv.StealNSResult();
   }
 
   FormDataTuple* data = mFormData.AppendElement();
   SetNameFilePair(data, aName, file);
   return NS_OK;
 }
 
+nsresult
+FormData::AddNameDirectoryPair(const nsAString& aName, Directory* aDirectory)
+{
+  MOZ_ASSERT(aDirectory);
+
+  FormDataTuple* data = mFormData.AppendElement();
+  SetNameDirectoryPair(data, aName, aDirectory);
+  return NS_OK;
+}
+
 FormData::FormDataTuple*
 FormData::RemoveAllOthersAndGetFirstFormDataTuple(const nsAString& aName)
 {
   FormDataTuple* lastFoundTuple = nullptr;
   uint32_t lastFoundIndex = mFormData.Length();
   // We have to use this slightly awkward for loop since uint32_t >= 0 is an
   // error for being always true.
   for (uint32_t i = mFormData.Length(); i-- > 0; ) {
@@ -260,17 +276,17 @@ FormData::GetIterableLength() const
 
 const nsAString&
 FormData::GetKeyAtIndex(uint32_t aIndex) const
 {
   MOZ_ASSERT(aIndex < mFormData.Length());
   return mFormData[aIndex].name;
 }
 
-const OwningBlobOrUSVString&
+const OwningBlobOrDirectoryOrUSVString&
 FormData::GetValueAtIndex(uint32_t aIndex) const
 {
   MOZ_ASSERT(aIndex < mFormData.Length());
   return mFormData[aIndex].value;
 }
 
 void
 FormData::SetNameValuePair(FormDataTuple* aData,
@@ -292,16 +308,29 @@ FormData::SetNameFilePair(FormDataTuple*
   MOZ_ASSERT(aData);
   MOZ_ASSERT(aFile);
 
   aData->name = aName;
   aData->wasNullBlob = false;
   aData->value.SetAsBlob() = aFile;
 }
 
+void
+FormData::SetNameDirectoryPair(FormDataTuple* aData,
+                               const nsAString& aName,
+                               Directory* aDirectory)
+{
+  MOZ_ASSERT(aData);
+  MOZ_ASSERT(aDirectory);
+
+  aData->name = aName;
+  aData->wasNullBlob = false;
+  aData->value.SetAsDirectory() = aDirectory;
+}
+
 // -------------------------------------------------------------------------
 // nsIDOMFormData
 
 NS_IMETHODIMP
 FormData::Append(const nsAString& aName, nsIVariant* aValue)
 {
   uint16_t dataType;
   nsresult rv = aValue->GetDataType(&dataType);
@@ -376,20 +405,23 @@ FormData::GetSendInfo(nsIInputStream** a
 
   for (uint32_t i = 0; i < mFormData.Length(); ++i) {
     if (mFormData[i].wasNullBlob) {
       MOZ_ASSERT(mFormData[i].value.IsUSVString());
       fs.AddNameBlobOrNullPair(mFormData[i].name, nullptr);
     } else if (mFormData[i].value.IsUSVString()) {
       fs.AddNameValuePair(mFormData[i].name,
                           mFormData[i].value.GetAsUSVString());
-    } else {
-      MOZ_ASSERT(mFormData[i].value.IsBlob());
+    } else if (mFormData[i].value.IsBlob()) {
       fs.AddNameBlobOrNullPair(mFormData[i].name,
                                mFormData[i].value.GetAsBlob());
+    } else {
+      MOZ_ASSERT(mFormData[i].value.IsDirectory());
+      fs.AddNameDirectoryPair(mFormData[i].name,
+                              mFormData[i].value.GetAsDirectory());
     }
   }
 
   fs.GetContentType(aContentType);
   aCharset.Truncate();
   *aContentLength = 0;
   NS_ADDREF(*aBody = fs.GetSubmissionBody(aContentLength));
 
--- a/dom/base/FormData.h
+++ b/dom/base/FormData.h
@@ -31,33 +31,37 @@ class FormData final : public nsIDOMForm
 {
 private:
   ~FormData() {}
 
   struct FormDataTuple
   {
     nsString name;
     bool wasNullBlob;
-    OwningBlobOrUSVString value;
+    OwningBlobOrDirectoryOrUSVString value;
   };
 
   // Returns the FormDataTuple to modify. This may be null, in which case
   // no element with aName was found.
   FormDataTuple*
   RemoveAllOthersAndGetFirstFormDataTuple(const nsAString& aName);
 
   void SetNameValuePair(FormDataTuple* aData,
                         const nsAString& aName,
                         const nsAString& aValue,
                         bool aWasNullBlob = false);
 
   void SetNameFilePair(FormDataTuple* aData,
                        const nsAString& aName,
                        File* aFile);
 
+  void SetNameDirectoryPair(FormDataTuple* aData,
+                            const nsAString& aName,
+                            Directory* aDirectory);
+
 public:
   explicit FormData(nsISupports* aOwner = nullptr);
 
   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
   NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_AMBIGUOUS(FormData,
                                                          nsIDOMFormData)
 
   NS_DECL_NSIDOMFORMDATA
@@ -75,57 +79,63 @@ public:
 
   static already_AddRefed<FormData>
   Constructor(const GlobalObject& aGlobal,
               const Optional<NonNull<HTMLFormElement> >& aFormElement,
               ErrorResult& aRv);
 
   void Append(const nsAString& aName, const nsAString& aValue,
               ErrorResult& aRv);
+
   void Append(const nsAString& aName, Blob& aBlob,
               const Optional<nsAString>& aFilename,
               ErrorResult& aRv);
 
+  void Append(const nsAString& aName, Directory* aDirectory);
+
   void Delete(const nsAString& aName);
 
-  void Get(const nsAString& aName, Nullable<OwningBlobOrUSVString>& aOutValue);
+  void Get(const nsAString& aName, Nullable<OwningBlobOrDirectoryOrUSVString>& aOutValue);
 
-  void GetAll(const nsAString& aName, nsTArray<OwningBlobOrUSVString>& aValues);
+  void GetAll(const nsAString& aName, nsTArray<OwningBlobOrDirectoryOrUSVString>& aValues);
 
   bool Has(const nsAString& aName);
 
   void Set(const nsAString& aName, Blob& aBlob,
            const Optional<nsAString>& aFilename,
            ErrorResult& aRv);
   void Set(const nsAString& aName, const nsAString& aValue,
            ErrorResult& aRv);
 
   uint32_t GetIterableLength() const;
 
   const nsAString& GetKeyAtIndex(uint32_t aIndex) const;
 
-  const OwningBlobOrUSVString& GetValueAtIndex(uint32_t aIndex) const;
+  const OwningBlobOrDirectoryOrUSVString& GetValueAtIndex(uint32_t aIndex) const;
 
   // HTMLFormSubmission
   virtual nsresult
   GetEncodedSubmission(nsIURI* aURI, nsIInputStream** aPostDataStream) override;
 
   virtual nsresult AddNameValuePair(const nsAString& aName,
                                     const nsAString& aValue) override
   {
     FormDataTuple* data = mFormData.AppendElement();
     SetNameValuePair(data, aName, aValue);
     return NS_OK;
   }
 
   virtual nsresult AddNameBlobOrNullPair(const nsAString& aName,
                                          Blob* aBlob) override;
 
+  virtual nsresult AddNameDirectoryPair(const nsAString& aName,
+                                        Directory* aDirectory) override;
+
   typedef bool (*FormDataEntryCallback)(const nsString& aName,
-                                        const OwningBlobOrUSVString& aValue,
+                                        const OwningBlobOrDirectoryOrUSVString& aValue,
                                         void* aClosure);
 
   uint32_t
   Length() const
   {
     return mFormData.Length();
   }
 
--- a/dom/base/IframeSandboxKeywordList.h
+++ b/dom/base/IframeSandboxKeywordList.h
@@ -18,8 +18,10 @@ SANDBOX_KEYWORD("allow-top-navigation", 
 		SANDBOXED_TOPLEVEL_NAVIGATION)
 SANDBOX_KEYWORD("allow-pointer-lock", allowpointerlock, SANDBOXED_POINTER_LOCK)
 SANDBOX_KEYWORD("allow-orientation-lock", alloworientationlock,
 		SANDBOXED_ORIENTATION_LOCK)
 SANDBOX_KEYWORD("allow-popups", allowpopups, SANDBOXED_AUXILIARY_NAVIGATION)
 SANDBOX_KEYWORD("allow-modals", allowmodals, SANDBOXED_MODALS)
 SANDBOX_KEYWORD("allow-popups-to-escape-sandbox", allowpopupstoescapesandbox,
                 SANDBOX_PROPAGATES_TO_AUXILIARY_BROWSING_CONTEXTS)
+SANDBOX_KEYWORD("allow-presentation", allowpresentation,
+                SANDBOXED_PRESENTATION)
--- a/dom/base/StructuredCloneHolder.cpp
+++ b/dom/base/StructuredCloneHolder.cpp
@@ -738,23 +738,21 @@ WriteDirectory(JSStructuredCloneWriter* 
   nsAutoString path;
   aDirectory->GetFullRealPath(path);
 
   size_t charSize = sizeof(nsString::char_type);
   return JS_WriteUint32Pair(aWriter, SCTAG_DOM_DIRECTORY, path.Length()) &&
          JS_WriteBytes(aWriter, path.get(), path.Length() * charSize);
 }
 
-JSObject*
-ReadDirectory(JSContext* aCx,
-              JSStructuredCloneReader* aReader,
-              uint32_t aPathLength,
-              StructuredCloneHolder* aHolder)
+already_AddRefed<Directory>
+ReadDirectoryInternal(JSStructuredCloneReader* aReader,
+                      uint32_t aPathLength,
+                      StructuredCloneHolder* aHolder)
 {
-  MOZ_ASSERT(aCx);
   MOZ_ASSERT(aReader);
   MOZ_ASSERT(aHolder);
 
   nsAutoString path;
   path.SetLength(aPathLength);
   size_t charSize = sizeof(nsString::char_type);
   if (!JS_ReadBytes(aReader, (void*) path.BeginWriting(),
                     aPathLength * charSize)) {
@@ -763,25 +761,43 @@ ReadDirectory(JSContext* aCx,
 
   nsCOMPtr<nsIFile> file;
   nsresult rv = NS_NewNativeLocalFile(NS_ConvertUTF16toUTF8(path), true,
                                       getter_AddRefs(file));
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return nullptr;
   }
 
+  RefPtr<Directory> directory =
+    Directory::Create(aHolder->ParentDuringRead(), file);
+  return directory.forget();
+}
+
+JSObject*
+ReadDirectory(JSContext* aCx,
+              JSStructuredCloneReader* aReader,
+              uint32_t aPathLength,
+              StructuredCloneHolder* aHolder)
+{
+  MOZ_ASSERT(aCx);
+  MOZ_ASSERT(aReader);
+  MOZ_ASSERT(aHolder);
+
   // RefPtr<Directory> needs to go out of scope before toObject() is
   // called because the static analysis thinks dereferencing XPCOM objects
   // can GC (because in some cases it can!), and a return statement with a
   // JSObject* type means that JSObject* is on the stack as a raw pointer
   // while destructors are running.
   JS::Rooted<JS::Value> val(aCx);
   {
     RefPtr<Directory> directory =
-      Directory::Create(aHolder->ParentDuringRead(), file);
+      ReadDirectoryInternal(aReader, aPathLength, aHolder);
+    if (!directory) {
+      return nullptr;
+    }
 
     if (!ToJSValue(aCx, directory, &val)) {
       return nullptr;
     }
   }
 
   return &val.toObject();
 }
@@ -921,16 +937,25 @@ ReadFormData(JSContext* aCx,
 
         ErrorResult rv;
         formData->Append(name, *blob, thirdArg, rv);
         if (NS_WARN_IF(rv.Failed())) {
           rv.SuppressException();
           return nullptr;
         }
 
+      } else if (tag == SCTAG_DOM_DIRECTORY) {
+        RefPtr<Directory> directory =
+          ReadDirectoryInternal(aReader, indexOrLengthOfString, aHolder);
+        if (!directory) {
+          return nullptr;
+        }
+
+        formData->Append(name, directory);
+
       } else {
         MOZ_ASSERT(tag == 0);
 
         nsAutoString value;
         value.SetLength(indexOrLengthOfString);
         size_t charSize = sizeof(nsString::char_type);
         if (!JS_ReadBytes(aReader, (void*) value.BeginWriting(),
                           indexOrLengthOfString * charSize)) {
@@ -956,16 +981,19 @@ ReadFormData(JSContext* aCx,
 
 // The format of the FormData serialization is:
 // - pair of ints: SCTAG_DOM_FORMDATA, Length of the FormData elements
 // - for each Element element:
 //   - name string
 //   - if it's a blob:
 //     - pair of ints: SCTAG_DOM_BLOB, index of the BlobImpl in the array
 //       mBlobImplArray.
+//   - if it's a directory (See WriteDirectory):
+//     - pair of ints: SCTAG_DOM_DIRECTORY, path length
+//     - path as string
 //   - else:
 //     - pair of ints: 0, string length
 //     - value string
 bool
 WriteFormData(JSStructuredCloneWriter* aWriter,
               FormData* aFormData,
               StructuredCloneHolder* aHolder)
 {
@@ -986,17 +1014,17 @@ WriteFormData(JSStructuredCloneWriter* a
   public:
     Closure(JSStructuredCloneWriter* aWriter,
             StructuredCloneHolder* aHolder)
       : mWriter(aWriter),
         mHolder(aHolder)
     { }
 
     static bool
-    Write(const nsString& aName, const OwningBlobOrUSVString& aValue,
+    Write(const nsString& aName, const OwningBlobOrDirectoryOrUSVString& aValue,
           void* aClosure)
     {
       Closure* closure = static_cast<Closure*>(aClosure);
       if (!WriteString(closure->mWriter, aName)) {
         return false;
       }
 
       if (aValue.IsBlob()) {
@@ -1005,16 +1033,28 @@ WriteFormData(JSStructuredCloneWriter* a
                                 closure->mHolder->BlobImpls().Length())) {
           return false;
         }
 
         closure->mHolder->BlobImpls().AppendElement(blobImpl);
         return true;
       }
 
+      if (aValue.IsDirectory()) {
+        Directory* directory = aValue.GetAsDirectory();
+
+        if (closure->mHolder->SupportedContext() !=
+              StructuredCloneHolder::SameProcessSameThread &&
+            !directory->ClonableToDifferentThreadOrProcess()) {
+          return false;
+        }
+
+        return WriteDirectory(closure->mWriter, directory);
+      }
+
       size_t charSize = sizeof(nsString::char_type);
       if (!JS_WriteUint32Pair(closure->mWriter, 0,
                               aValue.GetAsUSVString().Length()) ||
           !JS_WriteBytes(closure->mWriter, aValue.GetAsUSVString().get(),
                          aValue.GetAsUSVString().Length() * charSize)) {
         return false;
       }
 
--- a/dom/base/StructuredCloneHolder.h
+++ b/dom/base/StructuredCloneHolder.h
@@ -189,16 +189,21 @@ public:
   }
 
   nsTArray<RefPtr<BlobImpl>>& BlobImpls()
   {
     MOZ_ASSERT(mSupportsCloning, "Blobs cannot be taken/set if cloning is not supported.");
     return mBlobImplArray;
   }
 
+  ContextSupport SupportedContext() const
+  {
+    return mSupportedContext;
+  }
+
   // The parent object is set internally just during the Read(). This method
   // can be used by read functions to retrieve it.
   nsISupports* ParentDuringRead() const
   {
     return mParent;
   }
 
   // This must be called if the transferring has ports generated by Read().
--- a/dom/base/nsGkAtomList.h
+++ b/dom/base/nsGkAtomList.h
@@ -79,16 +79,17 @@ GK_ATOM(allowevents, "allowevents")
 GK_ATOM(allownegativeassertions, "allownegativeassertions")
 GK_ATOM(allowforms,"allow-forms")
 GK_ATOM(allowfullscreen, "allowfullscreen")
 GK_ATOM(allowmodals, "allow-modals")
 GK_ATOM(alloworientationlock,"allow-orientation-lock")
 GK_ATOM(allowpointerlock,"allow-pointer-lock")
 GK_ATOM(allowpopupstoescapesandbox,"allow-popups-to-escape-sandbox")
 GK_ATOM(allowpopups,"allow-popups")
+GK_ATOM(allowpresentation,"allow-presentation")
 GK_ATOM(allowsameorigin,"allow-same-origin")
 GK_ATOM(allowscripts,"allow-scripts")
 GK_ATOM(allowtopnavigation,"allow-top-navigation")
 GK_ATOM(allowuntrusted, "allowuntrusted")
 GK_ATOM(alt, "alt")
 GK_ATOM(alternate, "alternate")
 GK_ATOM(always, "always")
 GK_ATOM(ancestor, "ancestor")
--- a/dom/base/nsSandboxFlags.h
+++ b/dom/base/nsSandboxFlags.h
@@ -103,10 +103,15 @@ const unsigned long SANDBOXED_MODALS = 0
  */
 const unsigned long SANDBOX_PROPAGATES_TO_AUXILIARY_BROWSING_CONTEXTS = 0x1000;
 
 /**
  * This flag prevents locking screen orientation.
  */
 const unsigned long SANDBOXED_ORIENTATION_LOCK = 0x2000;
 
-const unsigned long SANDBOX_ALL_FLAGS = 0x3FFF;
+/**
+ * This flag disables the Presentation API.
+ */
+const unsigned long SANDBOXED_PRESENTATION = 0x4000;
+
+const unsigned long SANDBOX_ALL_FLAGS = 0x7FFF;
 #endif
--- a/dom/bindings/CallbackInterface.cpp
+++ b/dom/bindings/CallbackInterface.cpp
@@ -11,17 +11,17 @@
 
 namespace mozilla {
 namespace dom {
 
 bool
 CallbackInterface::GetCallableProperty(JSContext* cx, JS::Handle<jsid> aPropId,
                                        JS::MutableHandle<JS::Value> aCallable)
 {
-  if (!JS_GetPropertyById(cx, CallbackPreserveColor(), aPropId, aCallable)) {
+  if (!JS_GetPropertyById(cx, CallbackKnownNotGray(), aPropId, aCallable)) {
     return false;
   }
   if (!aCallable.isObject() ||
       !JS::IsCallable(&aCallable.toObject())) {
     char* propName =
       JS_EncodeString(cx, JS_FORGET_STRING_FLATNESS(JSID_TO_FLAT_STRING(aPropId)));
     nsPrintfCString description("Property '%s'", propName);
     JS_free(cx, propName);
--- a/dom/bindings/CallbackObject.h
+++ b/dom/bindings/CallbackObject.h
@@ -114,16 +114,27 @@ public:
    */
   JS::Handle<JSObject*> CallbackPreserveColor() const
   {
     // Calling fromMarkedLocation() is safe because we trace our mCallback, and
     // because the value of mCallback cannot change after if has been set.
     return JS::Handle<JSObject*>::fromMarkedLocation(mCallback.address());
   }
 
+  /*
+   * If the callback is known to be non-gray, then this method can be
+   * used instead of Callback() to avoid the overhead of
+   * ExposeObjectToActiveJS().
+   */
+  JS::Handle<JSObject*> CallbackKnownNotGray() const
+  {
+    MOZ_ASSERT(!JS::ObjectIsMarkedGray(mCallback.get()));
+    return CallbackPreserveColor();
+  }
+
   nsIGlobalObject* IncumbentGlobalOrNull() const
   {
     return mIncumbentGlobal;
   }
 
   enum ExceptionHandling {
     // Report any exception and don't throw it to the caller code.
     eReportExceptions,
--- a/dom/bindings/Codegen.py
+++ b/dom/bindings/Codegen.py
@@ -15042,22 +15042,22 @@ class FakeMember():
         # Claim to be a [NewObject] so we can avoid the "return a raw pointer"
         # comments CGNativeMember codegen would otherwise stick in.
         if name == "NewObject":
             return True
         return None
 
 
 class CallbackMember(CGNativeMember):
-    # XXXbz It's OK to use CallbackPreserveColor for wrapScope because
+    # XXXbz It's OK to use CallbackKnownNotGray for wrapScope because
     # CallSetup already handled the unmark-gray bits for us. we don't have
     # anything better to use for 'obj', really...
     def __init__(self, sig, name, descriptorProvider, needThisHandling,
                  rethrowContentException=False, typedArraysAreStructs=False,
-                 wrapScope='CallbackPreserveColor()'):
+                 wrapScope='CallbackKnownNotGray()'):
         """
         needThisHandling is True if we need to be able to accept a specified
         thisObj, False otherwise.
         """
         assert not rethrowContentException or not needThisHandling
 
         self.retvalType = sig[0]
         self.originalSig = sig
@@ -15498,17 +15498,17 @@ class CallbackSetter(CallbackAccessor):
         return ""
 
     def getCall(self):
         return fill(
             """
             MOZ_ASSERT(argv.length() == 1);
             ${atomCacheName}* atomsCache = GetAtomCache<${atomCacheName}>(cx);
             if ((!*reinterpret_cast<jsid**>(atomsCache) && !InitIds(cx, atomsCache)) ||
-                !JS_SetPropertyById(cx, CallbackPreserveColor(), atomsCache->${attrAtomName}, argv[0])) {
+                !JS_SetPropertyById(cx, CallbackKnownNotGray(), atomsCache->${attrAtomName}, argv[0])) {
               aRv.Throw(NS_ERROR_UNEXPECTED);
               return${errorReturn};
             }
             """,
             atomCacheName=self.descriptorProvider.interface.identifier.name + "Atoms",
             attrAtomName=CGDictionary.makeIdName(self.descriptorProvider.binaryNameFor(self.attrName)),
             errorReturn=self.getDefaultRetval())
 
--- a/dom/canvas/CanvasGradient.h
+++ b/dom/canvas/CanvasGradient.h
@@ -57,16 +57,18 @@ public:
   }
 
   CanvasRenderingContext2D* GetParentObject()
   {
     return mContext;
   }
 
 protected:
+  friend struct CanvasBidiProcessor;
+
   CanvasGradient(CanvasRenderingContext2D* aContext, Type aType)
     : mContext(aContext)
     , mType(aType)
   {
   }
 
   RefPtr<CanvasRenderingContext2D> mContext;
   nsTArray<mozilla::gfx::GradientStop> mRawStops;
--- a/dom/canvas/CanvasRenderingContext2D.cpp
+++ b/dom/canvas/CanvasRenderingContext2D.cpp
@@ -213,16 +213,17 @@ public:
                        const Point& aBegin, const Point& aEnd)
     : CanvasGradient(aContext, Type::LINEAR)
     , mBegin(aBegin)
     , mEnd(aEnd)
   {
   }
 
 protected:
+  friend struct CanvasBidiProcessor;
   friend class CanvasGeneralPattern;
 
   // Beginning of linear gradient.
   Point mBegin;
   // End of linear gradient.
   Point mEnd;
 };
 
@@ -3654,16 +3655,18 @@ CanvasRenderingContext2D::GetHitRegionRe
   return false;
 }
 
 /**
  * Used for nsBidiPresUtils::ProcessText
  */
 struct MOZ_STACK_CLASS CanvasBidiProcessor : public nsBidiPresUtils::BidiProcessor
 {
+  typedef CanvasRenderingContext2D::Style Style;
+
   CanvasBidiProcessor()
     : nsBidiPresUtils::BidiProcessor()
   {
     if (Preferences::GetBool(GFX_MISSING_FONTS_NOTIFY_PREF)) {
       mMissingFonts = new gfxMissingFontRecorder();
     }
   }
 
@@ -3706,22 +3709,81 @@ struct MOZ_STACK_CLASS CanvasBidiProcess
     if (mDoMeasureBoundingBox) {
       textRunMetrics.mBoundingBox.Scale(1.0 / mAppUnitsPerDevPixel);
       mBoundingBox = mBoundingBox.Union(textRunMetrics.mBoundingBox);
     }
 
     return NSToCoordRound(textRunMetrics.mAdvanceWidth);
   }
 
+  already_AddRefed<gfxPattern> GetGradientFor(Style aStyle)
+  {
+    RefPtr<gfxPattern> pattern;
+    CanvasGradient* gradient = mState->gradientStyles[aStyle];
+    CanvasGradient::Type type = gradient->GetType();
+
+    switch (type) {
+    case CanvasGradient::Type::RADIAL: {
+      CanvasRadialGradient* radial =
+        static_cast<CanvasRadialGradient*>(gradient);
+      pattern = new gfxPattern(radial->mCenter1.x, radial->mCenter1.y,
+                               radial->mRadius1, radial->mCenter2.x,
+                               radial->mCenter2.y, radial->mRadius2);
+      break;
+    }
+    case CanvasGradient::Type::LINEAR: {
+      CanvasLinearGradient* linear =
+        static_cast<CanvasLinearGradient*>(gradient);
+      pattern = new gfxPattern(linear->mBegin.x, linear->mBegin.y,
+                               linear->mEnd.x, linear->mEnd.y);
+      break;
+    }
+    default:
+      MOZ_ASSERT(false, "Should be linear or radial gradient.");
+      return nullptr;
+    }
+
+    for (auto stop : gradient->mRawStops) {
+      pattern->AddColorStop(stop.offset, stop.color);
+    }
+
+    return pattern.forget();
+  }
+
+  gfx::ExtendMode CvtCanvasRepeatToGfxRepeat(
+    CanvasPattern::RepeatMode aRepeatMode)
+  {
+    switch (aRepeatMode) {
+    case CanvasPattern::RepeatMode::REPEAT:
+      return gfx::ExtendMode::REPEAT;
+    case CanvasPattern::RepeatMode::REPEATX:
+      return gfx::ExtendMode::REPEAT_X;
+    case CanvasPattern::RepeatMode::REPEATY:
+      return gfx::ExtendMode::REPEAT_Y;
+    case CanvasPattern::RepeatMode::NOREPEAT:
+      return gfx::ExtendMode::CLAMP;
+    default:
+      return gfx::ExtendMode::CLAMP;
+    }
+  }
+
+  already_AddRefed<gfxPattern> GetPatternFor(Style aStyle)
+  {
+    const CanvasPattern* pat = mState->patternStyles[aStyle];
+    RefPtr<gfxPattern> pattern = new gfxPattern(pat->mSurface, Matrix());
+    pattern->SetExtend(CvtCanvasRepeatToGfxRepeat(pat->mRepeat));
+    return pattern.forget();
+  }
+
   virtual void DrawText(nscoord aXOffset, nscoord aWidth)
   {
     gfxPoint point = mPt;
     bool rtl = mTextRun->IsRightToLeft();
     bool verticalRun = mTextRun->IsVertical();
-    bool centerBaseline = mTextRun->UseCenterBaseline();
+    RefPtr<gfxPattern> pattern;
 
     gfxFloat& inlineCoord = verticalRun ? point.y : point.x;
     inlineCoord += aXOffset;
 
     // offset is given in terms of left side of string
     if (rtl) {
       // Bug 581092 - don't use rounded pixel width to advance to
       // right-hand end of run, because this will cause different
@@ -3737,215 +3799,72 @@ struct MOZ_STACK_CLASS CanvasBidiProcess
       // old code was:
       //   point.x += width * mAppUnitsPerDevPixel;
       // TODO: restore this if/when we move to fractional coords
       // throughout the text layout process
     }
 
     mCtx->EnsureTarget();
 
-    // If the operation is 'fill' with a simple color, we defer to gfxTextRun
-    // which will handle color/svg-in-ot fonts appropriately. Such fonts will
-    // not render well via the code below.
-    if (mOp == CanvasRenderingContext2D::TextDrawOperation::FILL &&
-        mState->StyleIsColor(CanvasRenderingContext2D::Style::FILL)) {
-      // TODO: determine if mCtx->mTarget is guaranteed to be non-null and valid
-      // here. If it's not, thebes will be null and we'll crash.
-      RefPtr<gfxContext> thebes =
-        gfxContext::CreatePreservingTransformOrNull(mCtx->mTarget);
-      nscolor fill = mState->colorStyles[CanvasRenderingContext2D::Style::FILL];
-      thebes->SetColor(Color::FromABGR(fill));
-      gfxTextRun::DrawParams params(thebes);
-      mTextRun->Draw(gfxTextRun::Range(mTextRun.get()), point, params);
-      return;
-    }
-
-    uint32_t numRuns;
-    const gfxTextRun::GlyphRun *runs = mTextRun->GetGlyphRuns(&numRuns);
-    const int32_t appUnitsPerDevUnit = mAppUnitsPerDevPixel;
-    const double devUnitsPerAppUnit = 1.0/double(appUnitsPerDevUnit);
-    Point baselineOrigin =
-      Point(point.x * devUnitsPerAppUnit, point.y * devUnitsPerAppUnit);
-
-    float advanceSum = 0;
-
-    for (uint32_t c = 0; c < numRuns; c++) {
-      gfxFont *font = runs[c].mFont;
-
-      bool verticalFont =
-        runs[c].mOrientation == gfxTextRunFactory::TEXT_ORIENT_VERTICAL_UPRIGHT;
-
-      const float& baselineOriginInline =
-        verticalFont ? baselineOrigin.y : baselineOrigin.x;
-      const float& baselineOriginBlock =
-        verticalFont ? baselineOrigin.x : baselineOrigin.y;
-
-      uint32_t endRun = 0;
-      if (c + 1 < numRuns) {
-        endRun = runs[c + 1].mCharacterOffset;
+    // Defer the tasks to gfxTextRun which will handle color/svg-in-ot fonts
+    // appropriately.
+    StrokeOptions strokeOpts;
+    DrawOptions drawOpts;
+    Style style = (mOp == CanvasRenderingContext2D::TextDrawOperation::FILL)
+                    ? Style::FILL
+                    : Style::STROKE;
+    RefPtr<gfxContext> thebes =
+      gfxContext::CreatePreservingTransformOrNull(mCtx->mTarget);
+    gfxTextRun::DrawParams params(thebes);
+
+    if (mState->StyleIsColor(style)) { // Color
+      nscolor fontColor = mState->colorStyles[style];
+      if (style == Style::FILL) {
+        params.context->SetColor(Color::FromABGR(fontColor));
       } else {
-        endRun = mTextRun->GetLength();
+        params.textStrokeColor = fontColor;
       }
-
-      const gfxTextRun::CompressedGlyph *glyphs = mTextRun->GetCharacterGlyphs();
-
-      RefPtr<ScaledFont> scaledFont =
-        gfxPlatform::GetPlatform()->GetScaledFontForFont(mCtx->mTarget, font);
-
-      if (!scaledFont) {
-        // This can occur when something switched DirectWrite off.
+    } else {
+      if (mState->gradientStyles[style]) { // Gradient
+        pattern = GetGradientFor(style);
+      } else if (mState->patternStyles[style]) { // Pattern
+        pattern = GetPatternFor(style);
+      } else {
+        MOZ_ASSERT(false, "Should never reach here.");
         return;
       }
-
-      AutoRestoreTransform sidewaysRestore;
-      if (runs[c].mOrientation ==
-          gfxTextRunFactory::TEXT_ORIENT_VERTICAL_SIDEWAYS_RIGHT) {
-        sidewaysRestore.Init(mCtx->mTarget);
-        const gfxFont::Metrics& metrics = mTextRun->GetFontGroup()->
-          GetFirstValidFont()->GetMetrics(gfxFont::eHorizontal);
-
-        gfx::Matrix mat = mCtx->mTarget->GetTransform().Copy().
-          PreTranslate(baselineOrigin).      // translate origin for rotation
-          PreRotate(gfx::Float(M_PI / 2.0)). // turn 90deg clockwise
-          PreTranslate(-baselineOrigin);     // undo the translation
-
-        if (centerBaseline) {
-          // TODO: The baseline adjustment here is kinda ad hoc; eventually
-          // perhaps we should check for horizontal and vertical baseline data
-          // in the font, and adjust accordingly.
-          // (The same will be true for HTML text layout.)
-          float offset = (metrics.emAscent - metrics.emDescent) / 2;
-          mat = mat.PreTranslate(Point(0, offset));
-                              // offset the (alphabetic) baseline of the
-                              // horizontally-shaped text from the (centered)
-                              // default baseline used for vertical
-        }
-
-        mCtx->mTarget->SetTransform(mat);
-      }
-
-      RefPtr<GlyphRenderingOptions> renderingOptions = font->GetGlyphRenderingOptions();
-
-      GlyphBuffer buffer;
-
-      std::vector<Glyph> glyphBuf;
-
-      // TODO:
-      // This more-or-less duplicates the code found in gfxTextRun::Draw
-      // and the gfxFont methods that uses (Draw, DrawGlyphs, DrawOneGlyph);
-      // it would be nice to refactor and share that code.
-      for (uint32_t i = runs[c].mCharacterOffset; i < endRun; i++) {
-        Glyph newGlyph;
-
-        float& inlinePos =
-          verticalFont ? newGlyph.mPosition.y : newGlyph.mPosition.x;
-        float& blockPos =
-          verticalFont ? newGlyph.mPosition.x : newGlyph.mPosition.y;
-
-        if (glyphs[i].IsSimpleGlyph()) {
-          newGlyph.mIndex = glyphs[i].GetSimpleGlyph();
-          if (rtl) {
-            inlinePos = baselineOriginInline - advanceSum -
-              glyphs[i].GetSimpleAdvance() * devUnitsPerAppUnit;
-          } else {
-            inlinePos = baselineOriginInline + advanceSum;
-          }
-          blockPos = baselineOriginBlock;
-          advanceSum += glyphs[i].GetSimpleAdvance() * devUnitsPerAppUnit;
-          glyphBuf.push_back(newGlyph);
-          continue;
-        }
-
-        if (!glyphs[i].GetGlyphCount()) {
-          continue;
-        }
-
-        const gfxTextRun::DetailedGlyph *d = mTextRun->GetDetailedGlyphs(i);
-
-        if (glyphs[i].IsMissing()) {
-          if (d->mAdvance > 0) {
-            // Perhaps we should render a hexbox here, but for now
-            // we just draw the font's .notdef glyph. (See bug 808288.)
-            newGlyph.mIndex = 0;
-            if (rtl) {
-              inlinePos = baselineOriginInline - advanceSum -
-                d->mAdvance * devUnitsPerAppUnit;
-            } else {
-              inlinePos = baselineOriginInline + advanceSum;
-            }
-            blockPos = baselineOriginBlock;
-            advanceSum += d->mAdvance * devUnitsPerAppUnit;
-            glyphBuf.push_back(newGlyph);
-          }
-          continue;
-        }
-
-        for (uint32_t c = 0; c < glyphs[i].GetGlyphCount(); c++, d++) {
-          newGlyph.mIndex = d->mGlyphID;
-          if (rtl) {
-            inlinePos = baselineOriginInline - advanceSum -
-              d->mAdvance * devUnitsPerAppUnit;
-          } else {
-            inlinePos = baselineOriginInline + advanceSum;
-          }
-          inlinePos += d->mXOffset * devUnitsPerAppUnit;
-          blockPos = baselineOriginBlock + d->mYOffset * devUnitsPerAppUnit;
-          glyphBuf.push_back(newGlyph);
-          advanceSum += d->mAdvance * devUnitsPerAppUnit;
-        }
-      }
-
-      if (!glyphBuf.size()) {
-        // This may happen for glyph runs for a 0 size font.
-        continue;
-      }
-
-      buffer.mGlyphs = &glyphBuf.front();
-      buffer.mNumGlyphs = glyphBuf.size();
-
-      Rect bounds = mCtx->mTarget->GetTransform().
-        TransformBounds(Rect(mBoundingBox.x, mBoundingBox.y,
-                             mBoundingBox.width, mBoundingBox.height));
-      if (mOp == CanvasRenderingContext2D::TextDrawOperation::FILL) {
-        AdjustedTarget(mCtx, &bounds)->
-          FillGlyphs(scaledFont, buffer,
-                     CanvasGeneralPattern().
-                       ForStyle(mCtx, CanvasRenderingContext2D::Style::FILL, mCtx->mTarget),
-                     DrawOptions(mState->globalAlpha, mCtx->UsedOperation()),
-                     renderingOptions);
-      } else if (mOp == CanvasRenderingContext2D::TextDrawOperation::STROKE) {
-        // stroke glyphs one at a time to avoid poor CoreGraphics performance
-        // when stroking a path with a very large number of points
-        buffer.mGlyphs = &glyphBuf.front();
-        buffer.mNumGlyphs = 1;
-        const ContextState& state = *mState;
-
-        const StrokeOptions strokeOpts(state.lineWidth, state.lineJoin,
-                                       state.lineCap, state.miterLimit,
-                                       state.dash.Length(),
-                                       state.dash.Elements(),
-                                       state.dashOffset);
-
-        // We need to adjust the bounds for the adjusted target
-        bounds.Inflate(MaxStrokeExtents(strokeOpts, mCtx->mTarget->GetTransform()));
-
-        AdjustedTarget target(mCtx, &bounds);
-
-        CanvasGeneralPattern cgp;
-        const Pattern& patForStyle
-          (cgp.ForStyle(mCtx, CanvasRenderingContext2D::Style::STROKE, mCtx->mTarget));
-        const DrawOptions drawOpts(state.globalAlpha, mCtx->UsedOperation());
-
-        for (unsigned i = glyphBuf.size(); i > 0; --i) {
-          RefPtr<Path> path = scaledFont->GetPathForGlyphs(buffer, mCtx->mTarget);
-          target->Stroke(path, patForStyle, strokeOpts, drawOpts);
-          buffer.mGlyphs++;
-        }
+      MOZ_ASSERT(pattern, "No valid pattern.");
+
+      if (style == Style::FILL) {
+        params.context->SetPattern(pattern);
+      } else {
+        params.textStrokePattern = pattern;
       }
     }
+
+    if (style == Style::STROKE) {
+      const ContextState& state = *mState;
+      drawOpts.mAlpha = state.globalAlpha;
+      drawOpts.mCompositionOp = mCtx->UsedOperation();
+
+      strokeOpts.mLineWidth = state.lineWidth;
+      strokeOpts.mLineJoin = state.lineJoin;
+      strokeOpts.mLineCap = state.lineCap;
+      strokeOpts.mMiterLimit = state.miterLimit;
+      strokeOpts.mDashLength = state.dash.Length();
+      strokeOpts.mDashPattern =
+        (strokeOpts.mDashLength > 0) ? state.dash.Elements() : 0;
+      strokeOpts.mDashOffset = state.dashOffset;
+
+      params.drawMode = DrawMode::GLYPH_STROKE;
+      params.strokeOpts = &strokeOpts;
+      params.drawOpts = &drawOpts;
+    }
+
+    mTextRun->Draw(gfxTextRun::Range(mTextRun.get()), point, params);
   }
 
   // current text run
   UniquePtr<gfxTextRun> mTextRun;
 
   // pointer to a screen reference context used to measure text and such
   RefPtr<DrawTarget> mDrawTarget;
 
--- a/dom/canvas/WebGL2ContextRenderbuffers.cpp
+++ b/dom/canvas/WebGL2ContextRenderbuffers.cpp
@@ -13,28 +13,53 @@ namespace mozilla {
 
 void
 WebGL2Context::GetInternalformatParameter(JSContext* cx, GLenum target,
                                           GLenum internalformat, GLenum pname,
                                           JS::MutableHandleValue retval,
                                           ErrorResult& out_rv)
 {
     const char funcName[] = "getInternalfomratParameter";
+    retval.setObjectOrNull(nullptr);
+
     if (IsContextLost())
         return;
 
     if (target != LOCAL_GL_RENDERBUFFER) {
         ErrorInvalidEnum("%s: `target` must be RENDERBUFFER, was: 0x%04x.", funcName,
                          target);
         return;
     }
 
-    // GL_INVALID_ENUM is generated if internalformat is not color-, depth-, or
-    // stencil-renderable.
-    // TODO: When format table queries lands.
+    // GLES 3.0.4 $4.4.4 p212:
+    // "An internal format is color-renderable if it is one of the formats from table 3.13
+    //  noted as color-renderable or if it is unsized format RGBA or RGB."
+
+    GLenum sizedFormat;
+    switch (internalformat) {
+    case LOCAL_GL_RGB:
+        sizedFormat = LOCAL_GL_RGB8;
+        break;
+    case LOCAL_GL_RGBA:
+        sizedFormat = LOCAL_GL_RGBA8;
+        break;
+    default:
+        sizedFormat = internalformat;
+        break;
+    }
+
+    // In RenderbufferStorage, we allow DEPTH_STENCIL. Therefore, it is accepted for
+    // internalformat as well. Please ignore the conformance test fail for DEPTH_STENCIL.
+
+    const auto usage = mFormatUsage->GetRBUsage(sizedFormat);
+    if (!usage) {
+        ErrorInvalidEnum("%s: `internalformat` must be color-, depth-, or stencil-renderable, was: 0x%04x.",
+                         funcName, internalformat);
+        return;
+    }
 
     if (pname != LOCAL_GL_SAMPLES) {
         ErrorInvalidEnumInfo("%s: `pname` must be SAMPLES, was 0x%04x.", funcName, pname);
         return;
     }
 
     GLint* samples = nullptr;
     GLint sampleCount = 0;
new file mode 100644
--- /dev/null
+++ b/dom/filesystem/GetFilesHelper.cpp
@@ -0,0 +1,511 @@
+/* -*- 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 "GetFilesHelper.h"
+#include "mozilla/dom/ContentChild.h"
+#include "mozilla/dom/ContentParent.h"
+
+namespace mozilla {
+namespace dom {
+
+///////////////////////////////////////////////////////////////////////////////
+// GetFilesHelper Base class
+
+already_AddRefed<GetFilesHelper>
+GetFilesHelper::Create(nsIGlobalObject* aGlobal,
+                       const nsTArray<OwningFileOrDirectory>& aFilesOrDirectory,
+                       bool aRecursiveFlag, ErrorResult& aRv)
+{
+  RefPtr<GetFilesHelper> helper;
+
+  if (XRE_IsParentProcess()) {
+    helper = new GetFilesHelper(aGlobal, aRecursiveFlag);
+  } else {
+    helper = new GetFilesHelperChild(aGlobal, aRecursiveFlag);
+  }
+
+  nsAutoString directoryPath;
+
+  for (uint32_t i = 0; i < aFilesOrDirectory.Length(); ++i) {
+    const OwningFileOrDirectory& data = aFilesOrDirectory[i];
+    if (data.IsFile()) {
+      if (!helper->mFiles.AppendElement(data.GetAsFile(), fallible)) {
+        aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
+        return nullptr;
+      }
+    } else {
+      MOZ_ASSERT(data.IsDirectory());
+
+      // We support the upload of only 1 top-level directory from our
+      // directory picker. This means that we cannot have more than 1
+      // Directory object in aFilesOrDirectory array.
+      MOZ_ASSERT(directoryPath.IsEmpty());
+
+      RefPtr<Directory> directory = data.GetAsDirectory();
+      MOZ_ASSERT(directory);
+
+      aRv = directory->GetFullRealPath(directoryPath);
+      if (NS_WARN_IF(aRv.Failed())) {
+        return nullptr;
+      }
+    }
+  }
+
+  // No directories to explore.
+  if (directoryPath.IsEmpty()) {
+    helper->mListingCompleted = true;
+    return helper.forget();
+  }
+
+  MOZ_ASSERT(helper->mFiles.IsEmpty());
+  helper->SetDirectoryPath(directoryPath);
+
+  helper->Work(aRv);
+  if (NS_WARN_IF(aRv.Failed())) {
+    return nullptr;
+  }
+
+  return helper.forget();
+}
+
+GetFilesHelper::GetFilesHelper(nsIGlobalObject* aGlobal, bool aRecursiveFlag)
+  : mGlobal(aGlobal)
+  , mRecursiveFlag(aRecursiveFlag)
+  , mListingCompleted(false)
+  , mErrorResult(NS_OK)
+  , mMutex("GetFilesHelper::mMutex")
+  , mCanceled(false)
+{
+}
+
+void
+GetFilesHelper::AddPromise(Promise* aPromise)
+{
+  MOZ_ASSERT(aPromise);
+
+  // Still working.
+  if (!mListingCompleted) {
+    mPromises.AppendElement(aPromise);
+    return;
+  }
+
+  MOZ_ASSERT(mPromises.IsEmpty());
+  ResolveOrRejectPromise(aPromise);
+}
+
+void
+GetFilesHelper::AddCallback(GetFilesCallback* aCallback)
+{
+  MOZ_ASSERT(aCallback);
+
+  // Still working.
+  if (!mListingCompleted) {
+    mCallbacks.AppendElement(aCallback);
+    return;
+  }
+
+  MOZ_ASSERT(mCallbacks.IsEmpty());
+  RunCallback(aCallback);
+}
+
+void
+GetFilesHelper::Unlink()
+{
+  mGlobal = nullptr;
+  mFiles.Clear();
+  mPromises.Clear();
+  mCallbacks.Clear();
+
+  {
+    MutexAutoLock lock(mMutex);
+    mCanceled = true;
+  }
+
+  Cancel();
+}
+
+void
+GetFilesHelper::Traverse(nsCycleCollectionTraversalCallback &cb)
+{
+  GetFilesHelper* tmp = this;
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mGlobal);
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mFiles);
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPromises);
+}
+
+void
+GetFilesHelper::Work(ErrorResult& aRv)
+{
+  nsCOMPtr<nsIEventTarget> target =
+    do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID);
+  MOZ_ASSERT(target);
+
+  aRv = target->Dispatch(this, NS_DISPATCH_NORMAL);
+}
+
+NS_IMETHODIMP
+GetFilesHelper::Run()
+{
+  MOZ_ASSERT(!mDirectoryPath.IsEmpty());
+  MOZ_ASSERT(!mListingCompleted);
+
+  // First step is to retrieve the list of file paths.
+  // This happens in the I/O thread.
+  if (!NS_IsMainThread()) {
+    RunIO();
+
+    // If this operation has been canceled, we don't have to go back to
+    // main-thread.
+    if (IsCanceled()) {
+      return NS_OK;
+    }
+
+    return NS_DispatchToMainThread(this);
+  }
+
+  // We are here, but we should not do anything on this thread because, in the
+  // meantime, the operation has been canceled.
+  if (IsCanceled()) {
+    return NS_OK;
+  }
+
+  RunMainThread();
+
+  OperationCompleted();
+  return NS_OK;
+}
+
+void
+GetFilesHelper::OperationCompleted()
+{
+  // We mark the operation as completed here.
+  mListingCompleted = true;
+
+  // Let's process the pending promises.
+  nsTArray<RefPtr<Promise>> promises;
+  promises.SwapElements(mPromises);
+
+  for (uint32_t i = 0; i < promises.Length(); ++i) {
+    ResolveOrRejectPromise(promises[i]);
+  }
+
+  // Let's process the pending callbacks.
+  nsTArray<RefPtr<GetFilesCallback>> callbacks;
+  callbacks.SwapElements(mCallbacks);
+
+  for (uint32_t i = 0; i < callbacks.Length(); ++i) {
+    RunCallback(callbacks[i]);
+  }
+}
+
+void
+GetFilesHelper::RunIO()
+{
+  MOZ_ASSERT(!NS_IsMainThread());
+  MOZ_ASSERT(!mDirectoryPath.IsEmpty());
+  MOZ_ASSERT(!mListingCompleted);
+
+  nsCOMPtr<nsIFile> file;
+  mErrorResult = NS_NewNativeLocalFile(NS_ConvertUTF16toUTF8(mDirectoryPath), true,
+                                       getter_AddRefs(file));
+  if (NS_WARN_IF(NS_FAILED(mErrorResult))) {
+    return;
+  }
+
+  nsAutoString path;
+  path.AssignLiteral(FILESYSTEM_DOM_PATH_SEPARATOR_LITERAL);
+
+  mErrorResult = ExploreDirectory(path, file);
+}
+
+void
+GetFilesHelper::RunMainThread()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(!mDirectoryPath.IsEmpty());
+  MOZ_ASSERT(!mListingCompleted);
+
+  // If there is an error, do nothing.
+  if (NS_FAILED(mErrorResult)) {
+    return;
+  }
+
+  // Create the sequence of Files.
+  for (uint32_t i = 0; i < mTargetPathArray.Length(); ++i) {
+    nsCOMPtr<nsIFile> file;
+    mErrorResult =
+      NS_NewNativeLocalFile(NS_ConvertUTF16toUTF8(mTargetPathArray[i].mRealPath),
+                            true, getter_AddRefs(file));
+    if (NS_WARN_IF(NS_FAILED(mErrorResult))) {
+      mFiles.Clear();
+      return;
+    }
+
+    RefPtr<File> domFile =
+      File::CreateFromFile(mGlobal, file);
+    MOZ_ASSERT(domFile);
+
+    domFile->SetPath(mTargetPathArray[i].mDomPath);
+
+    if (!mFiles.AppendElement(domFile, fallible)) {
+      mErrorResult = NS_ERROR_OUT_OF_MEMORY;
+      mFiles.Clear();
+      return;
+    }
+  }
+}
+
+nsresult
+GetFilesHelper::ExploreDirectory(const nsAString& aDOMPath, nsIFile* aFile)
+{
+  MOZ_ASSERT(!NS_IsMainThread());
+  MOZ_ASSERT(aFile);
+
+  // We check if this operation has to be terminated at each recursion.
+  if (IsCanceled()) {
+    return NS_OK;
+  }
+
+  nsCOMPtr<nsISimpleEnumerator> entries;
+  nsresult rv = aFile->GetDirectoryEntries(getter_AddRefs(entries));
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  for (;;) {
+    bool hasMore = false;
+    if (NS_WARN_IF(NS_FAILED(entries->HasMoreElements(&hasMore))) || !hasMore) {
+      break;
+    }
+
+    nsCOMPtr<nsISupports> supp;
+    if (NS_WARN_IF(NS_FAILED(entries->GetNext(getter_AddRefs(supp))))) {
+      break;
+    }
+
+    nsCOMPtr<nsIFile> currFile = do_QueryInterface(supp);
+    MOZ_ASSERT(currFile);
+
+    bool isLink, isSpecial, isFile, isDir;
+    if (NS_WARN_IF(NS_FAILED(currFile->IsSymlink(&isLink)) ||
+                   NS_FAILED(currFile->IsSpecial(&isSpecial))) ||
+        isLink || isSpecial) {
+      continue;
+    }
+
+    if (NS_WARN_IF(NS_FAILED(currFile->IsFile(&isFile)) ||
+                   NS_FAILED(currFile->IsDirectory(&isDir))) ||
+        !(isFile || isDir)) {
+      continue;
+    }
+
+    // The new domPath
+    nsAutoString domPath;
+    domPath.Assign(aDOMPath);
+    if (!aDOMPath.EqualsLiteral(FILESYSTEM_DOM_PATH_SEPARATOR_LITERAL)) {
+      domPath.AppendLiteral(FILESYSTEM_DOM_PATH_SEPARATOR_LITERAL);
+    }
+
+    nsAutoString leafName;
+    if (NS_WARN_IF(NS_FAILED(currFile->GetLeafName(leafName)))) {
+      continue;
+    }
+    domPath.Append(leafName);
+
+    if (isFile) {
+      FileData* data = mTargetPathArray.AppendElement(fallible);
+      if (!data) {
+        return NS_ERROR_OUT_OF_MEMORY;
+      }
+
+      if (NS_WARN_IF(NS_FAILED(currFile->GetPath(data->mRealPath)))) {
+        continue;
+      }
+
+      data->mDomPath = domPath;
+      continue;
+    }
+
+    MOZ_ASSERT(isDir);
+    if (!mRecursiveFlag) {
+      continue;
+    }
+
+    // Recursive.
+    rv = ExploreDirectory(domPath, currFile);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
+    }
+  }
+
+  return NS_OK;
+}
+
+void
+GetFilesHelper::ResolveOrRejectPromise(Promise* aPromise)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(mListingCompleted);
+  MOZ_ASSERT(aPromise);
+
+  // Error propagation.
+  if (NS_FAILED(mErrorResult)) {
+    aPromise->MaybeReject(mErrorResult);
+    return;
+  }
+
+  aPromise->MaybeResolve(mFiles);
+}
+
+void
+GetFilesHelper::RunCallback(GetFilesCallback* aCallback)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(mListingCompleted);
+  MOZ_ASSERT(aCallback);
+
+  aCallback->Callback(mErrorResult, mFiles);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// GetFilesHelperChild class
+
+void
+GetFilesHelperChild::Work(ErrorResult& aRv)
+{
+  ContentChild* cc = ContentChild::GetSingleton();
+  if (NS_WARN_IF(!cc)) {
+    aRv.Throw(NS_ERROR_FAILURE);
+    return;
+  }
+
+  aRv = nsContentUtils::GenerateUUIDInPlace(mUUID);
+  if (NS_WARN_IF(aRv.Failed())) {
+    return;
+  }
+
+  mPendingOperation = true;
+  cc->CreateGetFilesRequest(mDirectoryPath, mRecursiveFlag, mUUID, this);
+}
+
+void
+GetFilesHelperChild::Cancel()
+{
+  if (!mPendingOperation) {
+    return;
+  }
+
+  ContentChild* cc = ContentChild::GetSingleton();
+  if (NS_WARN_IF(!cc)) {
+    return;
+  }
+
+  mPendingOperation = false;
+  cc->DeleteGetFilesRequest(mUUID, this);
+}
+
+bool
+GetFilesHelperChild::AppendBlobImpl(BlobImpl* aBlobImpl)
+{
+  MOZ_ASSERT(mPendingOperation);
+  MOZ_ASSERT(aBlobImpl);
+  MOZ_ASSERT(aBlobImpl->IsFile());
+
+  RefPtr<File> file = File::Create(mGlobal, aBlobImpl);
+  MOZ_ASSERT(file);
+
+  return mFiles.AppendElement(file, fallible);
+}
+
+void
+GetFilesHelperChild::Finished(nsresult aError)
+{
+  MOZ_ASSERT(mPendingOperation);
+  MOZ_ASSERT(NS_SUCCEEDED(mErrorResult));
+
+  mPendingOperation = false;
+  mErrorResult = aError;
+
+  OperationCompleted();
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// GetFilesHelperParent class
+
+class GetFilesHelperParentCallback final : public GetFilesCallback
+{
+public:
+  explicit GetFilesHelperParentCallback(GetFilesHelperParent* aParent)
+    : mParent(aParent)
+  {
+    MOZ_ASSERT(aParent);
+  }
+
+  void
+  Callback(nsresult aStatus, const Sequence<RefPtr<File>>& aFiles) override
+  {
+    if (NS_FAILED(aStatus)) {
+      mParent->mContentParent->SendGetFilesResponseAndForget(mParent->mUUID,
+                                                             GetFilesResponseFailure(aStatus));
+      return;
+    }
+
+    GetFilesResponseSuccess success;
+    nsTArray<PBlobParent*>& blobsParent = success.blobsParent();
+    blobsParent.SetLength(aFiles.Length());
+
+    for (uint32_t i = 0; i < aFiles.Length(); ++i) {
+      blobsParent[i] =
+        mParent->mContentParent->GetOrCreateActorForBlob(aFiles[i]);
+      if (!blobsParent[i]) {
+        mParent->mContentParent->SendGetFilesResponseAndForget(mParent->mUUID,
+                                                               GetFilesResponseFailure(NS_ERROR_OUT_OF_MEMORY));
+        return;
+      }
+    }
+
+    mParent->mContentParent->SendGetFilesResponseAndForget(mParent->mUUID,
+                                                           success);
+  }
+
+private:
+  // Raw pointer because this callback is kept alive by this parent object.
+  GetFilesHelperParent* mParent;
+};
+
+GetFilesHelperParent::GetFilesHelperParent(const nsID& aUUID,
+                                           ContentParent* aContentParent,
+                                           bool aRecursiveFlag)
+  : GetFilesHelper(nullptr, aRecursiveFlag)
+  , mContentParent(aContentParent)
+  , mUUID(aUUID)
+{}
+
+/* static */ already_AddRefed<GetFilesHelperParent>
+GetFilesHelperParent::Create(const nsID& aUUID, const nsAString& aDirectoryPath,
+                             bool aRecursiveFlag, ContentParent* aContentParent,
+                             ErrorResult& aRv)
+{
+  MOZ_ASSERT(aContentParent);
+
+  RefPtr<GetFilesHelperParent> helper =
+    new GetFilesHelperParent(aUUID, aContentParent, aRecursiveFlag);
+  helper->SetDirectoryPath(aDirectoryPath);
+
+  helper->Work(aRv);
+  if (NS_WARN_IF(aRv.Failed())) {
+    return nullptr;
+  }
+
+  RefPtr<GetFilesHelperParentCallback> callback =
+    new GetFilesHelperParentCallback(helper);
+  helper->AddCallback(callback);
+
+  return helper.forget();
+}
+
+} // dom namespace
+} // mozilla namespace
new file mode 100644
--- /dev/null
+++ b/dom/filesystem/GetFilesHelper.h
@@ -0,0 +1,181 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_GetFilesHelper_h
+#define mozilla_dom_GetFilesHelper_h
+
+#include "mozilla/Mutex.h"
+#include "mozilla/RefPtr.h"
+#include "nsCycleCollectionTraversalCallback.h"
+#include "nsTArray.h"
+
+class nsIGlobalObject;
+
+namespace mozilla {
+namespace dom {
+
+class BlobImpl;
+class ContentParent;
+class File;
+class GetFilesHelperParent;
+class OwningFileOrDirectory;
+class Promise;
+
+class GetFilesCallback
+{
+public:
+  NS_INLINE_DECL_REFCOUNTING(GetFilesCallback);
+
+  virtual void
+  Callback(nsresult aStatus, const Sequence<RefPtr<File>>& aFiles) = 0;
+
+protected:
+  virtual ~GetFilesCallback() {}
+};
+
+// Retrieving the list of files can be very time/IO consuming. We use this
+// helper class to do it just once.
+class GetFilesHelper : public Runnable
+{
+  friend class GetFilesHelperParent;
+
+public:
+  static already_AddRefed<GetFilesHelper>
+  Create(nsIGlobalObject* aGlobal,
+         const nsTArray<OwningFileOrDirectory>& aFilesOrDirectory,
+         bool aRecursiveFlag, ErrorResult& aRv);
+
+  void
+  AddPromise(Promise* aPromise);
+
+  void
+  AddCallback(GetFilesCallback* aCallback);
+
+  // CC methods
+  void Unlink();
+  void Traverse(nsCycleCollectionTraversalCallback &cb);
+
+protected:
+  GetFilesHelper(nsIGlobalObject* aGlobal, bool aRecursiveFlag);
+
+  virtual ~GetFilesHelper() {}
+
+  void
+  SetDirectoryPath(const nsAString& aDirectoryPath)
+  {
+    mDirectoryPath = aDirectoryPath;
+  }
+
+  bool
+  IsCanceled()
+  {
+    MutexAutoLock lock(mMutex);
+    return mCanceled;
+  }
+
+  virtual void
+  Work(ErrorResult& aRv);
+
+  virtual void
+  Cancel() {};
+
+  NS_IMETHOD
+  Run() override;
+
+  void
+  RunIO();
+
+  void
+  RunMainThread();
+
+  void
+  OperationCompleted();
+
+  nsresult
+  ExploreDirectory(const nsAString& aDOMPath, nsIFile* aFile);
+  void
+  ResolveOrRejectPromise(Promise* aPromise);
+
+  void
+  RunCallback(GetFilesCallback* aCallback);
+
+  nsCOMPtr<nsIGlobalObject> mGlobal;
+
+  bool mRecursiveFlag;
+  bool mListingCompleted;
+  nsString mDirectoryPath;
+
+  // We populate this array in the I/O thread with the paths of the Files that
+  // we want to send as result to the promise objects.
+  struct FileData {
+    nsString mDomPath;
+    nsString mRealPath;
+  };
+  FallibleTArray<FileData> mTargetPathArray;
+
+  // This is the real File sequence that we expose via Promises.
+  Sequence<RefPtr<File>> mFiles;
+
+  // Error code to propagate.
+  nsresult mErrorResult;
+
+  nsTArray<RefPtr<Promise>> mPromises;
+  nsTArray<RefPtr<GetFilesCallback>> mCallbacks;
+
+  Mutex mMutex;
+
+  // This variable is protected by mutex.
+  bool mCanceled;
+};
+
+class GetFilesHelperChild final : public GetFilesHelper
+{
+public:
+  GetFilesHelperChild(nsIGlobalObject* aGlobal, bool aRecursiveFlag)
+    : GetFilesHelper(aGlobal, aRecursiveFlag)
+    , mPendingOperation(false)
+  {}
+
+  virtual void
+  Work(ErrorResult& aRv) override;
+
+  virtual void
+  Cancel() override;
+
+  bool
+  AppendBlobImpl(BlobImpl* aBlobImpl);
+
+  void
+  Finished(nsresult aResult);
+
+private:
+  nsID mUUID;
+  bool mPendingOperation;
+};
+
+class GetFilesHelperParentCallback;
+
+class GetFilesHelperParent final : public GetFilesHelper
+{
+  friend class GetFilesHelperParentCallback;
+
+public:
+  static already_AddRefed<GetFilesHelperParent>
+  Create(const nsID& aUUID, const nsAString& aDirectoryPath,
+         bool aRecursiveFlag, ContentParent* aContentParent, ErrorResult& aRv);
+
+private:
+  GetFilesHelperParent(const nsID& aUUID, ContentParent* aContentParent,
+                       bool aRecursiveFlag);
+
+  RefPtr<ContentParent> mContentParent;
+  nsID mUUID;
+};
+
+} // dom namespace
+} // mozilla namespace
+
+#endif // mozilla_dom_GetFilesHelper_h
--- a/dom/filesystem/compat/tests/mochitest.ini
+++ b/dom/filesystem/compat/tests/mochitest.ini
@@ -1,6 +1,8 @@
 [DEFAULT]
 support-files =
   script_entries.js
+  !/dom/html/test/form_submit_server.sjs
 
 [test_basic.html]
 [test_no_dnd.html]
+[test_formSubmission.html]
new file mode 100644
--- /dev/null
+++ b/dom/filesystem/compat/tests/test_formSubmission.html
@@ -0,0 +1,266 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>Test for Directory form submission</title>
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+  <meta http-equiv="Content-Type" content="text/html;charset=utf-8">
+</head>
+<body onload="return next();">
+
+<iframe name="target_iframe" id="target_iframe"></iframe>
+
+<form action="../../../html/test/form_submit_server.sjs" target="target_iframe" id="form"
+      method="POST" enctype="multipart/form-data">
+</form>
+
+<script class="testbody" type="text/javascript;version=1.8">
+
+var form;
+var iframe;
+var input;
+var xhr;
+
+function setup_tests() {
+  form = document.getElementById("form");
+
+  iframe = document.getElementById("target_iframe");
+  iframe.onload = function() {
+    info("Frame loaded!");
+    next();
+  }
+
+  SpecialPowers.pushPrefEnv({"set": [["dom.input.dirpicker", true],
+                                     ["dom.webkitBlink.dirPicker.enabled", true],
+                                     ["dom.webkitBlink.filesystem.enabled", true]]}, next);
+}
+
+function populate_entries(webkitDirectory) {
+  if (input) {
+    form.removeChild(input);
+  }
+
+  input = document.createElement('input');
+  input.setAttribute('id', 'input');
+  input.setAttribute('type', 'file');
+  input.setAttribute('name', 'input');
+
+  if (webkitDirectory) {
+    input.setAttribute('webkitdirectory', 'true');
+  }
+
+  form.appendChild(input);
+
+  var url = SimpleTest.getTestFileURL("script_entries.js");
+  var script = SpecialPowers.loadChromeScript(url);
+
+  function onOpened(message) {
+    input.addEventListener("change", function onChange() {
+      input.removeEventListener("change", onChange);
+      next();
+    });
+
+    SpecialPowers.wrap(input).mozSetDndFilesAndDirectories([message.data[0]]);
+    script.destroy();
+  }
+
+  script.addMessageListener("entries.opened", onOpened);
+  script.sendAsyncMessage("entries.open");
+}
+
+function setup_plain() {
+  info("Preparing for a plain text submission...");
+  form.action = "../../../html/test/form_submit_server.sjs?plain";
+  form.method = "POST";
+  form.enctype = "text/plain";
+  form.submit();
+}
+
+function test_plain() {
+  var content = iframe.contentDocument.documentElement.textContent;
+  var submission = JSON.parse(content);
+  input.getFilesAndDirectories().then(function(array) {
+    is(submission, array.map(function(v) {
+       return "input=" + v.name + "\r\n";
+    }).join(""), "Data match");
+
+    next();
+  });
+}
+
+function setup_urlencoded() {
+  info("Preparing for a urlencoded submission...");
+  form.action = "../../../html/test/form_submit_server.sjs?url";
+  form.method = "POST";
+  form.enctype = "application/x-www-form-urlencoded";
+  form.submit();
+}
+
+function setup_urlencoded_get() {
+  info("Preparing for a urlencoded+GET submission...");
+  form.action = "../../../html/test/form_submit_server.sjs?xxyy";
+  form.method = "GET";
+  form.enctype = "";
+  form.submit();
+}
+
+function setup_urlencoded_empty() {
+  info("Preparing for a urlencoded+default values submission...");
+  form.action = "../../../html/test/form_submit_server.sjs";
+  form.method = "";
+  form.enctype = "";
+  form.submit();
+}
+
+function test_urlencoded() {
+  var content = iframe.contentDocument.documentElement.textContent;
+  var submission = JSON.parse(content);
+  input.getFilesAndDirectories().then(function(array) {
+    is(submission, array.map(function(v) {
+       return "input=" + v.name;
+    }).join("&"), "Data match");
+
+    next();
+  });
+}
+
+function setup_formData() {
+  info("Preparing for a fromData submission...");
+
+  xhr = new XMLHttpRequest();
+  xhr.onload = next;
+  xhr.open("POST", "../../../html/test/form_submit_server.sjs");
+  xhr.send(new FormData(form));
+}
+
+function test_multipart() {
+  var submission = JSON.parse(xhr.responseText);
+  input.getFilesAndDirectories().then(function(array) {
+    is(submission.length, array.length, "Same length");
+
+    for (var i = 0; i < array.length; ++i) {
+      if (array[i] instanceof Directory) {
+        is(submission[i].headers["Content-Disposition"],
+           "form-data; name=\"input\"; filename=\"/" + array[i].name + "\"",
+           "Correct Content-Disposition");
+        is(submission[i].headers["Content-Type"], "application/octet-stream",
+           "Correct Content-Type");
+        is(submission[i].body, "", "Correct body");
+      } else {
+        ok(array[i] instanceof File);
+        is(submission[i].headers["Content-Disposition"],
+           "form-data; name=\"input\"; filename=\"" + array[i].name + "\"",
+           "Correct Content-Disposition");
+        is(submission[i].headers["Content-Type"], array[i].type,
+           "Correct Content-Type");
+        is(submission[i].body, "", "Correct body");
+      }
+    }
+    next();
+  });
+}
+
+function test_webkit_plain() {
+  var content = iframe.contentDocument.documentElement.textContent;
+  var submission = JSON.parse(content);
+
+  input.getFiles(true).then(function(array) {
+    is(submission, array.map(function(v) {
+       return "input=" + v.name + "\r\n";
+    }).join(""), "Data match");
+
+    next();
+  });
+}
+
+function test_webkit_urlencoded() {
+  var content = iframe.contentDocument.documentElement.textContent;
+  var submission = JSON.parse(content);
+  input.getFiles(true).then(function(array) {
+    is(submission, array.map(function(v) {
+       return "input=" + v.name;
+    }).join("&"), "Data match");
+
+    next();
+  });
+}
+
+function test_webkit_multipart() {
+  var submission = JSON.parse(xhr.responseText);
+  input.getFiles(true).then(function(array) {
+    is(submission.length, array.length, "Same length");
+
+    for (var i = 0; i < array.length; ++i) {
+      if (array[i] instanceof Directory) {
+        is(submission[i].headers["Content-Disposition"],
+           "form-data; name=\"input\"; filename=\"/" + array[i].name + "\"",
+           "Correct Content-Disposition");
+        is(submission[i].headers["Content-Type"], "application/octet-stream",
+           "Correct Content-Type");
+        is(submission[i].body, "", "Correct body");
+      } else {
+        ok(array[i] instanceof File);
+        is(submission[i].headers["Content-Disposition"],
+           "form-data; name=\"input\"; filename=\"" + array[i].webkitRelativePath + "\"",
+           "Correct Content-Disposition");
+        is(submission[i].headers["Content-Type"], array[i].type,
+           "Correct Content-Type");
+        is(submission[i].body, "", "Correct body");
+      }
+    }
+    next();
+  });
+}
+
+var tests = [
+  setup_tests,
+  function() { populate_entries(false); },
+
+  setup_plain,
+  test_plain,
+
+  setup_urlencoded,
+  test_urlencoded,
+
+  setup_urlencoded_get,
+  test_urlencoded,
+
+  setup_urlencoded_empty,
+  test_urlencoded,
+
+  setup_formData,
+  test_multipart,
+
+  function() { populate_entries(true); },
+
+  setup_plain,
+  test_webkit_plain,
+
+  setup_urlencoded,
+  test_webkit_urlencoded,
+
+  setup_urlencoded_get,
+  test_webkit_urlencoded,
+
+  setup_urlencoded_empty,
+  test_webkit_urlencoded,
+
+  setup_formData,
+  test_webkit_multipart,
+];
+
+function next() {
+  if (!tests.length) {
+    SimpleTest.finish();
+    return;
+  }
+
+  var test = tests.shift();
+  test();
+}
+
+SimpleTest.waitForExplicitFinish();
+
+</script>
+</body>
+</html>
--- a/dom/filesystem/moz.build
+++ b/dom/filesystem/moz.build
@@ -10,31 +10,33 @@ TEST_DIRS += ['tests']
 
 EXPORTS.mozilla.dom += [
     'DeviceStorageFileSystem.h',
     'Directory.h',
     'FileSystemBase.h',
     'FileSystemRequestParent.h',
     'FileSystemTaskBase.h',
     'FileSystemUtils.h',
+    'GetFilesHelper.h',
     'OSFileSystem.h',
 ]
 
 UNIFIED_SOURCES += [
     'CreateDirectoryTask.cpp',
     'CreateFileTask.cpp',
     'DeviceStorageFileSystem.cpp',
     'Directory.cpp',
     'FileSystemBase.cpp',
     'FileSystemPermissionRequest.cpp',
     'FileSystemRequestParent.cpp',
     'FileSystemTaskBase.cpp',
     'FileSystemUtils.cpp',
     'GetDirectoryListingTask.cpp',
     'GetFileOrDirectoryTask.cpp',
+    'GetFilesHelper.cpp',
     'GetFilesTask.cpp',
     'OSFileSystem.cpp',
     'RemoveTask.cpp',
 ]
 
 FINAL_LIBRARY = 'xul'
 
 IPDL_SOURCES += [
--- a/dom/html/HTMLFormSubmission.cpp
+++ b/dom/html/HTMLFormSubmission.cpp
@@ -65,16 +65,29 @@ RetrieveFileName(Blob* aBlob, nsAString&
   }
 
   RefPtr<File> file = aBlob->ToFile();
   if (file) {
     file->GetName(aFilename);
   }
 }
 
+void
+RetrieveDirectoryName(Directory* aDirectory, nsAString& aDirname)
+{
+  MOZ_ASSERT(aDirectory);
+
+  ErrorResult rv;
+  aDirectory->GetName(aDirname, rv);
+  if (NS_WARN_IF(rv.Failed())) {
+    rv.SuppressException();
+    aDirname.Truncate();
+  }
+}
+
 // --------------------------------------------------------------------------
 
 class FSURLEncoded : public EncodingFormSubmission
 {
 public:
   /**
    * @param aCharset the charset of the form as a string
    * @param aMethod the method of the submit (either NS_FORM_METHOD_GET or
@@ -93,16 +106,19 @@ public:
 
   virtual nsresult
   AddNameValuePair(const nsAString& aName, const nsAString& aValue) override;
 
   virtual nsresult
   AddNameBlobOrNullPair(const nsAString& aName, Blob* aBlob) override;
 
   virtual nsresult
+  AddNameDirectoryPair(const nsAString& aName, Directory* aDirectory) override;
+
+  virtual nsresult
   GetEncodedSubmission(nsIURI* aURI, nsIInputStream** aPostDataStream) override;
 
   virtual bool SupportsIsindexSubmission() override
   {
     return true;
   }
 
   virtual nsresult AddIsindex(const nsAString& aValue) override;
@@ -189,16 +205,27 @@ FSURLEncoded::AddNameBlobOrNullPair(cons
     mWarnedFileControl = true;
   }
 
   nsAutoString filename;
   RetrieveFileName(aBlob, filename);
   return AddNameValuePair(aName, filename);
 }
 
+nsresult
+FSURLEncoded::AddNameDirectoryPair(const nsAString& aName,
+                                   Directory* aDirectory)
+{
+  // No warning about because Directory objects are never sent via form.
+
+  nsAutoString dirname;
+  RetrieveDirectoryName(aDirectory, dirname);
+  return AddNameValuePair(aName, dirname);
+}
+
 void
 HandleMailtoSubject(nsCString& aPath)
 {
   // Walk through the string and see if we have a subject already.
   bool hasSubject = false;
   bool hasParams = false;
   int32_t paramSep = aPath.FindChar('?');
   while (paramSep != kNotFound && paramSep < (int32_t)aPath.Length()) {
@@ -463,16 +490,19 @@ FSMultipartFormData::AddNameValuePair(co
 nsresult
 FSMultipartFormData::AddNameBlobOrNullPair(const nsAString& aName, Blob* aBlob)
 {
   // Encode the control name
   nsAutoCString nameStr;
   nsresult rv = EncodeVal(aName, nameStr, true);
   NS_ENSURE_SUCCESS(rv, rv);
 
+  ErrorResult error;
+
+  uint64_t size = 0;
   nsAutoCString filename;
   nsAutoCString contentType;
   nsCOMPtr<nsIInputStream> fileStream;
 
   if (aBlob) {
     nsAutoString filename16;
 
     RefPtr<File> file = aBlob->ToFile();
@@ -500,72 +530,119 @@ FSMultipartFormData::AddNameBlobOrNullPa
     }
 
     contentType.Adopt(nsLinebreakConverter::
                       ConvertLineBreaks(NS_ConvertUTF16toUTF8(contentType16).get(),
                                         nsLinebreakConverter::eLinebreakAny,
                                         nsLinebreakConverter::eLinebreakSpace));
 
     // Get input stream
-    ErrorResult error;
     aBlob->GetInternalStream(getter_AddRefs(fileStream), error);
     if (NS_WARN_IF(error.Failed())) {
       return error.StealNSResult();
     }
 
+    // Get size
+    size = aBlob->GetSize(error);
+    if (error.Failed()) {
+      error.SuppressException();
+      fileStream = nullptr;
+    }
+
     if (fileStream) {
       // Create buffered stream (for efficiency)
       nsCOMPtr<nsIInputStream> bufferedStream;
       rv = NS_NewBufferedInputStream(getter_AddRefs(bufferedStream),
                                      fileStream, 8192);
       NS_ENSURE_SUCCESS(rv, rv);
 
       fileStream = bufferedStream;
     }
   } else {
     contentType.AssignLiteral("application/octet-stream");
   }
 
+  AddDataChunk(nameStr, filename, contentType, fileStream, size);
+  return NS_OK;
+}
+
+nsresult
+FSMultipartFormData::AddNameDirectoryPair(const nsAString& aName,
+                                          Directory* aDirectory)
+{
+  if (!Directory::WebkitBlinkDirectoryPickerEnabled(nullptr, nullptr)) {
+    return NS_OK;
+  }
+
+  // Encode the control name
+  nsAutoCString nameStr;
+  nsresult rv = EncodeVal(aName, nameStr, true);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  nsAutoCString dirname;
+  nsAutoString dirname16;
+
+  ErrorResult error;
+  nsAutoString path;
+  aDirectory->GetPath(path, error);
+  if (NS_WARN_IF(error.Failed())) {
+    error.SuppressException();
+  } else {
+    dirname16 = path;
+  }
+
+  if (dirname16.IsEmpty()) {
+    RetrieveDirectoryName(aDirectory, dirname16);
+  }
+
+  rv = EncodeVal(dirname16, dirname, true);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  AddDataChunk(nameStr, dirname,
+               NS_LITERAL_CSTRING("application/octet-stream"),
+               nullptr, 0);
+  return NS_OK;
+}
+
+void
+FSMultipartFormData::AddDataChunk(const nsACString& aName,
+                                  const nsACString& aFilename,
+                                  const nsACString& aContentType,
+                                  nsIInputStream* aInputStream,
+                                  uint64_t aInputStreamSize)
+{
   //
   // Make MIME block for name/value pair
   //
   // more appropriate than always using binary?
   mPostDataChunk += NS_LITERAL_CSTRING("--") + mBoundary
                  + NS_LITERAL_CSTRING(CRLF);
   // XXX: name/filename parameter should be encoded per RFC 2231
   // RFC 2388 specifies that RFC 2047 be used, but I think it's not
   // consistent with the MIME standard.
   mPostDataChunk +=
          NS_LITERAL_CSTRING("Content-Disposition: form-data; name=\"")
-       + nameStr + NS_LITERAL_CSTRING("\"; filename=\"")
-       + filename + NS_LITERAL_CSTRING("\"" CRLF)
+       + aName + NS_LITERAL_CSTRING("\"; filename=\"")
+       + aFilename + NS_LITERAL_CSTRING("\"" CRLF)
        + NS_LITERAL_CSTRING("Content-Type: ")
-       + contentType + NS_LITERAL_CSTRING(CRLF CRLF);
+       + aContentType + NS_LITERAL_CSTRING(CRLF CRLF);
 
   // We should not try to append an invalid stream. That will happen for example
   // if we try to update a file that actually do not exist.
-  if (fileStream) {
-    ErrorResult error;
-    uint64_t size = aBlob->GetSize(error);
-    if (error.Failed()) {
-      error.SuppressException();
-    } else {
-      // We need to dump the data up to this point into the POST data stream
-      // here, since we're about to add the file input stream
-      AddPostDataStream();
+  if (aInputStream) {
+    // We need to dump the data up to this point into the POST data stream
+    // here, since we're about to add the file input stream
+    AddPostDataStream();
 
-      mPostDataStream->AppendStream(fileStream);
-      mTotalLength += size;
-    }
+    mPostDataStream->AppendStream(aInputStream);
+    mTotalLength += aInputStreamSize;
   }
 
   // CRLF after file
   mPostDataChunk.AppendLiteral(CRLF);
-
-  return NS_OK;
 }
 
 nsresult
 FSMultipartFormData::GetEncodedSubmission(nsIURI* aURI,
                                           nsIInputStream** aPostDataStream)
 {
   nsresult rv;
 
@@ -619,16 +696,19 @@ public:
 
   virtual nsresult
   AddNameValuePair(const nsAString& aName, const nsAString& aValue) override;
 
   virtual nsresult
   AddNameBlobOrNullPair(const nsAString& aName, Blob* aBlob) override;
 
   virtual nsresult
+  AddNameDirectoryPair(const nsAString& aName, Directory* aDirectory) override;
+
+  virtual nsresult
   GetEncodedSubmission(nsIURI* aURI, nsIInputStream** aPostDataStream) override;
 
 private:
   nsString mBody;
 };
 
 nsresult
 FSTextPlain::AddNameValuePair(const nsAString& aName, const nsAString& aValue)
@@ -647,16 +727,26 @@ FSTextPlain::AddNameBlobOrNullPair(const
 {
   nsAutoString filename;
   RetrieveFileName(aBlob, filename);
   AddNameValuePair(aName, filename);
   return NS_OK;
 }
 
 nsresult
+FSTextPlain::AddNameDirectoryPair(const nsAString& aName,
+                                  Directory* aDirectory)
+{
+  nsAutoString dirname;
+  RetrieveDirectoryName(aDirectory, dirname);
+  AddNameValuePair(aName, dirname);
+  return NS_OK;
+}
+
+nsresult
 FSTextPlain::GetEncodedSubmission(nsIURI* aURI,
                                   nsIInputStream** aPostDataStream)
 {
   nsresult rv = NS_OK;
 
   // XXX HACK We are using the standard URL mechanism to give the body to the
   // mailer instead of passing the post data stream to it, since that sounds
   // hard.
--- a/dom/html/HTMLFormSubmission.h
+++ b/dom/html/HTMLFormSubmission.h
@@ -17,16 +17,17 @@ class nsIURI;
 class nsIInputStream;
 class nsGenericHTMLElement;
 class nsIMultiplexInputStream;
 
 namespace mozilla {
 namespace dom {
 
 class Blob;
+class Directory;
 
 /**
  * Class for form submissions; encompasses the function to call to submit as
  * well as the form submission name/value pairs
  */
 class HTMLFormSubmission
 {
 public:
@@ -64,16 +65,25 @@ public:
    * @param aBlob the blob to submit. The file's name will be used if the Blob
    * is actually a File, otherwise 'blob' string is used instead if the aBlob is
    * not null.
    */
   virtual nsresult
   AddNameBlobOrNullPair(const nsAString& aName, Blob* aBlob) = 0;
 
   /**
+   * Submit a name/directory pair
+   *
+   * @param aName the name of the parameter
+   * @param aBlob the directory to submit.
+   */
+  virtual nsresult AddNameDirectoryPair(const nsAString& aName,
+                                        Directory* aDirectory) = 0;
+
+  /**
    * Reports whether the instance supports AddIsindex().
    *
    * @return true if supported.
    */
   virtual bool SupportsIsindexSubmission()
   {
     return false;
   }
@@ -175,16 +185,19 @@ public:
  
   virtual nsresult
   AddNameValuePair(const nsAString& aName, const nsAString& aValue) override;
 
   virtual nsresult
   AddNameBlobOrNullPair(const nsAString& aName, Blob* aBlob) override;
 
   virtual nsresult
+  AddNameDirectoryPair(const nsAString& aName, Directory* aDirectory) override;
+
+  virtual nsresult
   GetEncodedSubmission(nsIURI* aURI, nsIInputStream** aPostDataStream) override;
 
   void GetContentType(nsACString& aContentType)
   {
     aContentType =
       NS_LITERAL_CSTRING("multipart/form-data; boundary=") + mBoundary;
   }
 
@@ -193,16 +206,21 @@ public:
 protected:
 
   /**
    * Roll up the data we have so far and add it to the multiplexed data stream.
    */
   nsresult AddPostDataStream();
 
 private:
+  void AddDataChunk(const nsACString& aName,
+                    const nsACString& aFilename,
+                    const nsACString& aContentType,
+                    nsIInputStream* aInputStream,
+                    uint64_t aInputStreamSize);
   /**
    * The post data stream as it is so far.  This is a collection of smaller
    * chunks--string streams and file streams interleaved to make one big POST
    * stream.
    */
   nsCOMPtr<nsIMultiplexInputStream> mPostDataStream;
 
   /**
--- a/dom/html/HTMLInputElement.cpp
+++ b/dom/html/HTMLInputElement.cpp
@@ -8,16 +8,17 @@
 
 #include "mozilla/ArrayUtils.h"
 #include "mozilla/AsyncEventDispatcher.h"
 #include "mozilla/DebugOnly.h"
 #include "mozilla/dom/Date.h"
 #include "mozilla/dom/Directory.h"
 #include "mozilla/dom/HTMLFormSubmission.h"
 #include "mozilla/dom/FileSystemUtils.h"
+#include "mozilla/dom/GetFilesHelper.h"
 #include "nsAttrValueInlines.h"
 #include "nsCRTGlue.h"
 
 #include "nsIDOMHTMLInputElement.h"
 #include "nsITextControlElement.h"
 #include "nsIDOMNSEditableElement.h"
 #include "nsIRadioVisitor.h"
 #include "nsIPhonetic.h"
@@ -221,414 +222,21 @@ const double HTMLInputElement::kMinimumY
   0x23e2,                                          \
   0x4479,                                          \
   {0xb5, 0x13, 0x7b, 0x36, 0x93, 0x43, 0xe3, 0xa0} \
 }
 
 #define PROGRESS_STR "progress"
 static const uint32_t kProgressEventInterval = 50; // ms
 
-class GetFilesCallback
-{
-public:
-  NS_INLINE_DECL_REFCOUNTING(GetFilesCallback);
-
-  virtual void
-  Callback(nsresult aStatus, const Sequence<RefPtr<File>>& aFiles) = 0;
-
-protected:
-  virtual ~GetFilesCallback() {}
-};
-
-// Retrieving the list of files can be very time/IO consuming. We use this
-// helper class to do it just once.
-class GetFilesHelper final : public Runnable
-{
-public:
-  static already_AddRefed<GetFilesHelper>
-  Create(nsIGlobalObject* aGlobal,
-         const nsTArray<OwningFileOrDirectory>& aFilesOrDirectory,
-         bool aRecursiveFlag, ErrorResult& aRv)
-  {
-    MOZ_ASSERT(aGlobal);
-
-    RefPtr<GetFilesHelper> helper = new GetFilesHelper(aGlobal, aRecursiveFlag);
-
-    nsAutoString directoryPath;
-
-    for (uint32_t i = 0; i < aFilesOrDirectory.Length(); ++i) {
-      const OwningFileOrDirectory& data = aFilesOrDirectory[i];
-      if (data.IsFile()) {
-        if (!helper->mFiles.AppendElement(data.GetAsFile(), fallible)) {
-          aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
-          return nullptr;
-        }
-      } else {
-        MOZ_ASSERT(data.IsDirectory());
-
-        // We support the upload of only 1 top-level directory from our
-        // directory picker. This means that we cannot have more than 1
-        // Directory object in aFilesOrDirectory array.
-        MOZ_ASSERT(directoryPath.IsEmpty());
-
-        RefPtr<Directory> directory = data.GetAsDirectory();
-        MOZ_ASSERT(directory);
-
-        aRv = directory->GetFullRealPath(directoryPath);
-        if (NS_WARN_IF(aRv.Failed())) {
-          return nullptr;
-        }
-      }
-    }
-
-    // No directories to explore.
-    if (directoryPath.IsEmpty()) {
-      helper->mListingCompleted = true;
-      return helper.forget();
-    }
-
-    MOZ_ASSERT(helper->mFiles.IsEmpty());
-    helper->SetDirectoryPath(directoryPath);
-
-    nsCOMPtr<nsIEventTarget> target =
-      do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID);
-    MOZ_ASSERT(target);
-
-    aRv = target->Dispatch(helper, NS_DISPATCH_NORMAL);
-    if (NS_WARN_IF(aRv.Failed())) {
-      return nullptr;
-    }
-
-    return helper.forget();
-  }
-
-  void
-  AddPromise(Promise* aPromise)
-  {
-    MOZ_ASSERT(aPromise);
-
-    // Still working.
-    if (!mListingCompleted) {
-      mPromises.AppendElement(aPromise);
-      return;
-    }
-
-    MOZ_ASSERT(mPromises.IsEmpty());
-    ResolveOrRejectPromise(aPromise);
-  }
-
-  void
-  AddCallback(GetFilesCallback* aCallback)
-  {
-    MOZ_ASSERT(aCallback);
-
-    // Still working.
-    if (!mListingCompleted) {
-      mCallbacks.AppendElement(aCallback);
-      return;
-    }
-
-    MOZ_ASSERT(mCallbacks.IsEmpty());
-    RunCallback(aCallback);
-  }
-
-  // CC methods
-  void Unlink()
-  {
-    mGlobal = nullptr;
-    mFiles.Clear();
-    mPromises.Clear();
-    mCallbacks.Clear();
-
-    MutexAutoLock lock(mMutex);
-    mCanceled = true;
-  }
-
-  void Traverse(nsCycleCollectionTraversalCallback &cb)
-  {
-    GetFilesHelper* tmp = this;
-    NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mGlobal);
-    NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mFiles);
-    NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPromises);
-  }
-
-private:
-  GetFilesHelper(nsIGlobalObject* aGlobal, bool aRecursiveFlag)
-    : mGlobal(aGlobal)
-    , mRecursiveFlag(aRecursiveFlag)
-    , mListingCompleted(false)
-    , mErrorResult(NS_OK)
-    , mMutex("GetFilesHelper::mMutex")
-    , mCanceled(false)
-  {
-    MOZ_ASSERT(aGlobal);
-  }
-
-  void
-  SetDirectoryPath(const nsAString& aDirectoryPath)
-  {
-    mDirectoryPath = aDirectoryPath;
-  }
-
-  bool
-  IsCanceled()
-  {
-    MutexAutoLock lock(mMutex);
-    return mCanceled;
-  }
-
-  NS_IMETHOD
-  Run() override
-  {
-    MOZ_ASSERT(!mDirectoryPath.IsEmpty());
-    MOZ_ASSERT(!mListingCompleted);
-
-    // First step is to retrieve the list of file paths.
-    // This happens in the I/O thread.
-    if (!NS_IsMainThread()) {
-      RunIO();
-
-      // If this operation has been canceled, we don't have to go back to
-      // main-thread.
-      if (IsCanceled()) {
-        return NS_OK;
-      }
-
-      return NS_DispatchToMainThread(this);
-    }
-
-    // We are here, but we should not do anything on this thread because, in the
-    // meantime, the operation has been canceled.
-    if (IsCanceled()) {
-      return NS_OK;
-    }
-
-    RunMainThread();
-
-    // We mark the operation as completed here.
-    mListingCompleted = true;
-
-    // Let's process the pending promises.
-    nsTArray<RefPtr<Promise>> promises;
-    promises.SwapElements(mPromises);
-
-    for (uint32_t i = 0; i < promises.Length(); ++i) {
-      ResolveOrRejectPromise(promises[i]);
-    }
-
-    // Let's process the pending callbacks.
-    nsTArray<RefPtr<GetFilesCallback>> callbacks;
-    callbacks.SwapElements(mCallbacks);
-
-    for (uint32_t i = 0; i < callbacks.Length(); ++i) {
-      RunCallback(callbacks[i]);
-    }
-
-    return NS_OK;
-  }
-
-  void
-  RunIO()
-  {
-    MOZ_ASSERT(!NS_IsMainThread());
-    MOZ_ASSERT(!mDirectoryPath.IsEmpty());
-    MOZ_ASSERT(!mListingCompleted);
-
-    nsCOMPtr<nsIFile> file;
-    mErrorResult = NS_NewNativeLocalFile(NS_ConvertUTF16toUTF8(mDirectoryPath), true,
-                                         getter_AddRefs(file));
-    if (NS_WARN_IF(NS_FAILED(mErrorResult))) {
-      return;
-    }
-
-    nsAutoString path;
-    path.AssignLiteral(FILESYSTEM_DOM_PATH_SEPARATOR_LITERAL);
-
-    mErrorResult = ExploreDirectory(path, file);
-  }
-
-  void
-  RunMainThread()
-  {
-    MOZ_ASSERT(NS_IsMainThread());
-    MOZ_ASSERT(!mDirectoryPath.IsEmpty());
-    MOZ_ASSERT(!mListingCompleted);
-
-    // If there is an error, do nothing.
-    if (NS_FAILED(mErrorResult)) {
-      return;
-    }
-
-    // Create the sequence of Files.
-    for (uint32_t i = 0; i < mTargetPathArray.Length(); ++i) {
-      nsCOMPtr<nsIFile> file;
-      mErrorResult =
-        NS_NewNativeLocalFile(NS_ConvertUTF16toUTF8(mTargetPathArray[i].mRealPath),
-                              true, getter_AddRefs(file));
-      if (NS_WARN_IF(NS_FAILED(mErrorResult))) {
-        mFiles.Clear();
-        return;
-      }
-
-      RefPtr<File> domFile =
-        File::CreateFromFile(mGlobal, file);
-      MOZ_ASSERT(domFile);
-
-      domFile->SetPath(mTargetPathArray[i].mDomPath);
-
-      if (!mFiles.AppendElement(domFile, fallible)) {
-        mErrorResult = NS_ERROR_OUT_OF_MEMORY;
-        mFiles.Clear();
-        return;
-      }
-    }
-  }
-
-  nsresult
-  ExploreDirectory(const nsAString& aDOMPath, nsIFile* aFile)
-  {
-    MOZ_ASSERT(!NS_IsMainThread());
-    MOZ_ASSERT(aFile);
-
-    // We check if this operation has to be terminated at each recursion.
-    if (IsCanceled()) {
-      return NS_OK;
-    }
-
-    nsCOMPtr<nsISimpleEnumerator> entries;
-    nsresult rv = aFile->GetDirectoryEntries(getter_AddRefs(entries));
-    if (NS_WARN_IF(NS_FAILED(rv))) {
-      return rv;
-    }
-
-    for (;;) {
-      bool hasMore = false;
-      if (NS_WARN_IF(NS_FAILED(entries->HasMoreElements(&hasMore))) || !hasMore) {
-        break;
-      }
-
-      nsCOMPtr<nsISupports> supp;
-      if (NS_WARN_IF(NS_FAILED(entries->GetNext(getter_AddRefs(supp))))) {
-        break;
-      }
-
-      nsCOMPtr<nsIFile> currFile = do_QueryInterface(supp);
-      MOZ_ASSERT(currFile);
-
-      bool isLink, isSpecial, isFile, isDir;
-      if (NS_WARN_IF(NS_FAILED(currFile->IsSymlink(&isLink)) ||
-                     NS_FAILED(currFile->IsSpecial(&isSpecial))) ||
-          isLink || isSpecial) {
-        continue;
-      }
-
-      if (NS_WARN_IF(NS_FAILED(currFile->IsFile(&isFile)) ||
-                     NS_FAILED(currFile->IsDirectory(&isDir))) ||
-          !(isFile || isDir)) {
-        continue;
-      }
-
-      // The new domPath
-      nsAutoString domPath;
-      domPath.Assign(aDOMPath);
-      if (!aDOMPath.EqualsLiteral(FILESYSTEM_DOM_PATH_SEPARATOR_LITERAL)) {
-        domPath.AppendLiteral(FILESYSTEM_DOM_PATH_SEPARATOR_LITERAL);
-      }
-
-      nsAutoString leafName;
-      if (NS_WARN_IF(NS_FAILED(currFile->GetLeafName(leafName)))) {
-        continue;
-      }
-      domPath.Append(leafName);
-
-      if (isFile) {
-        FileData* data = mTargetPathArray.AppendElement(fallible);
-        if (!data) {
-          return NS_ERROR_OUT_OF_MEMORY;
-        }
-
-        if (NS_WARN_IF(NS_FAILED(currFile->GetPath(data->mRealPath)))) {
-          continue;
-        }
-
-        data->mDomPath = domPath;
-        continue;
-      }
-
-      MOZ_ASSERT(isDir);
-      if (!mRecursiveFlag) {
-        continue;
-      }
-
-      // Recursive.
-      rv = ExploreDirectory(domPath, currFile);
-      if (NS_WARN_IF(NS_FAILED(rv))) {
-        return rv;
-      }
-    }
-
-    return NS_OK;
-  }
-
-  void
-  ResolveOrRejectPromise(Promise* aPromise)
-  {
-    MOZ_ASSERT(NS_IsMainThread());
-    MOZ_ASSERT(mListingCompleted);
-    MOZ_ASSERT(aPromise);
-
-    // Error propagation.
-    if (NS_FAILED(mErrorResult)) {
-      aPromise->MaybeReject(mErrorResult);
-      return;
-    }
-
-    aPromise->MaybeResolve(mFiles);
-  }
-
-  void
-  RunCallback(GetFilesCallback* aCallback)
-  {
-    MOZ_ASSERT(NS_IsMainThread());
-    MOZ_ASSERT(mListingCompleted);
-    MOZ_ASSERT(aCallback);
-
-    aCallback->Callback(mErrorResult, mFiles);
-  }
-
-  nsCOMPtr<nsIGlobalObject> mGlobal;
-
-  bool mRecursiveFlag;
-  bool mListingCompleted;
-  nsString mDirectoryPath;
-
-  // We populate this array in the I/O thread with the paths of the Files that
-  // we want to send as result to the promise objects.
-  struct FileData {
-    nsString mDomPath;
-    nsString mRealPath;
-  };
-  FallibleTArray<FileData> mTargetPathArray;
-
-  // This is the real File sequence that we expose via Promises.
-  Sequence<RefPtr<File>> mFiles;
-
-  // Error code to propagate.
-  nsresult mErrorResult;
-
-  nsTArray<RefPtr<Promise>> mPromises;
-  nsTArray<RefPtr<GetFilesCallback>> mCallbacks;
-
-  Mutex mMutex;
-
-  // This variable is protected by mutex.
-  bool mCanceled;
-};
-
 // An helper class for the dispatching of the 'change' event.
+// This class is used when the FilePicker finished its task (or when files and
+// directories are set by some chrome/test only method).
+// The task of this class is to postpone the dispatching of 'change' and 'input'
+// events at the end of the exploration of the directories.
 class DispatchChangeEventCallback final : public GetFilesCallback
 {
 public:
   explicit DispatchChangeEventCallback(HTMLInputElement* aInputElement)
     : mInputElement(aInputElement)
   {
     MOZ_ASSERT(aInputElement);
   }
@@ -663,42 +271,16 @@ public:
 
     return rv;
   }
 
 private:
   RefPtr<HTMLInputElement> mInputElement;
 };
 
-// This callback is used for postponing the calling of SetFilesOrDirectories
-// when the exploration of the directory is completed.
-class AfterSetFilesOrDirectoriesCallback : public GetFilesCallback
-{
-public:
-  AfterSetFilesOrDirectoriesCallback(HTMLInputElement* aInputElement,
-                                     bool aSetValueChanged)
-    : mInputElement(aInputElement)
-    , mSetValueChanged(aSetValueChanged)
-  {
-    MOZ_ASSERT(aInputElement);
-  }
-
-  void
-  Callback(nsresult aStatus, const Sequence<RefPtr<File>>& aFiles) override
-  {
-    if (NS_SUCCEEDED(aStatus)) {
-      mInputElement->AfterSetFilesOrDirectoriesInternal(mSetValueChanged);
-    }
-  }
-
-private:
-  RefPtr<HTMLInputElement> mInputElement;
-  bool mSetValueChanged;
-};
-
 class HTMLInputElementState final : public nsISupports
 {
   public:
     NS_DECLARE_STATIC_IID_ACCESSOR(NS_INPUT_ELEMENT_STATE_IID)
     NS_DECL_ISUPPORTS
 
     bool IsCheckedSet()
     {
@@ -3293,42 +2875,48 @@ HTMLInputElement::SetFiles(nsIDOMFileLis
       OwningFileOrDirectory* element = mFilesOrDirectories.AppendElement();
       element->SetAsFile() = files->Item(i);
     }
   }
 
   AfterSetFilesOrDirectories(aSetValueChanged);
 }
 
+// This method is used for testing only.
 void
 HTMLInputElement::MozSetDndFilesAndDirectories(const nsTArray<OwningFileOrDirectory>& aFilesOrDirectories)
 {
   if (Preferences::GetBool("dom.webkitBlink.filesystem.enabled", false)) {
     UpdateEntries(aFilesOrDirectories);
   }
 
   SetFilesOrDirectories(aFilesOrDirectories, true);
+
+  RefPtr<DispatchChangeEventCallback> dispatchChangeEventCallback =
+    new DispatchChangeEventCallback(this);
+
+  if (Preferences::GetBool("dom.webkitBlink.dirPicker.enabled", false) &&
+      HasAttr(kNameSpaceID_None, nsGkAtoms::webkitdirectory)) {
+    ErrorResult rv;
+    GetFilesHelper* helper = GetOrCreateGetFilesHelper(true /* recursionFlag */,
+                                                       rv);
+    if (NS_WARN_IF(rv.Failed())) {
+      rv.SuppressException();
+      return;
+    }
+
+    helper->AddCallback(dispatchChangeEventCallback);
+  } else {
+    dispatchChangeEventCallback->DispatchEvents();
+  }
 }
 
 void
 HTMLInputElement::AfterSetFilesOrDirectories(bool aSetValueChanged)
 {
-  if (Preferences::GetBool("dom.webkitBlink.dirPicker.enabled", false) &&
-      HasAttr(kNameSpaceID_None, nsGkAtoms::webkitdirectory)) {
-    // This will call AfterSetFilesOrDirectoriesInternal eventually.
-    ExploreDirectoryRecursively(aSetValueChanged);
-    return;
-  }
-
-  AfterSetFilesOrDirectoriesInternal(aSetValueChanged);
-}
-
-void
-HTMLInputElement::AfterSetFilesOrDirectoriesInternal(bool aSetValueChanged)
-{
   // No need to flush here, if there's no frame at this point we
   // don't need to force creation of one just to tell it about this
   // new value.  We just want the display to update as needed.
   nsIFormControlFrame* formControlFrame = GetFormControlFrame(false);
   if (formControlFrame) {
     nsAutoString readableValue;
     GetDisplayFileName(readableValue);
     formControlFrame->SetFormProperty(nsGkAtoms::value, readableValue);
@@ -6565,28 +6153,30 @@ HTMLInputElement::SubmitNamesValues(HTML
   // Submit file if its input type=file and this encoding method accepts files
   //
   if (mType == NS_FORM_INPUT_FILE) {
     // Submit files
 
     const nsTArray<OwningFileOrDirectory>& files =
       GetFilesOrDirectoriesInternal();
 
-    bool hasBlobs = false;
+    if (files.IsEmpty()) {
+      aFormSubmission->AddNameBlobOrNullPair(name, nullptr);
+      return NS_OK;
+    }
+
     for (uint32_t i = 0; i < files.Length(); ++i) {
       if (files[i].IsFile()) {
-        hasBlobs = true;
         aFormSubmission->AddNameBlobOrNullPair(name, files[i].GetAsFile());
+      } else {
+        MOZ_ASSERT(files[i].IsDirectory());
+        aFormSubmission->AddNameDirectoryPair(name, files[i].GetAsDirectory());
       }
     }
 
-    if (!hasBlobs) {
-      aFormSubmission->AddNameBlobOrNullPair(name, nullptr);
-    }
-
     return NS_OK;
   }
 
   if (mType == NS_FORM_INPUT_HIDDEN && name.EqualsLiteral("_charset_")) {
     nsCString charset;
     aFormSubmission->GetCharset(charset);
     return aFormSubmission->AddNameValuePair(name,
                                              NS_ConvertASCIItoUTF16(charset));
@@ -8509,32 +8099,16 @@ HTMLInputElement::GetOrCreateGetFilesHel
       return nullptr;
     }
   }
 
   return mGetFilesNonRecursiveHelper;
 }
 
 void
-HTMLInputElement::ExploreDirectoryRecursively(bool aSetValueChanged)
-{
-  ErrorResult rv;
-  GetFilesHelper* helper = GetOrCreateGetFilesHelper(true /* recursionFlag */,
-                                                     rv);
-  if (NS_WARN_IF(rv.Failed())) {
-    AfterSetFilesOrDirectoriesInternal(aSetValueChanged);
-    return;
-  }
-
-  RefPtr<AfterSetFilesOrDirectoriesCallback> callback =
-    new AfterSetFilesOrDirectoriesCallback(this, aSetValueChanged);
-  helper->AddCallback(callback);
-}
-
-void
 HTMLInputElement::UpdateEntries(const nsTArray<OwningFileOrDirectory>& aFilesOrDirectories)
 {
   mEntries.Clear();
 
   nsCOMPtr<nsIGlobalObject> global = OwnerDoc()->GetScopeObject();
   MOZ_ASSERT(global);
 
   RefPtr<DOMFileSystem> fs = DOMFileSystem::Create(global);
--- a/dom/html/HTMLInputElement.h
+++ b/dom/html/HTMLInputElement.h
@@ -238,16 +238,18 @@ public:
   {
     return mFilesOrDirectories;
   }
 
   void SetFilesOrDirectories(const nsTArray<OwningFileOrDirectory>& aFilesOrDirectories,
                              bool aSetValueChanged);
   void SetFiles(nsIDOMFileList* aFiles, bool aSetValueChanged);
 
+  // This method is used for test only. Onces the data is set, a 'change' event
+  // is dispatched.
   void MozSetDndFilesAndDirectories(const nsTArray<OwningFileOrDirectory>& aSequence);
 
   // Called when a nsIFilePicker or a nsIColorPicker terminate.
   void PickerClosed();
 
   void SetCheckedChangedInternal(bool aCheckedChanged);
   bool GetCheckedChanged() const {
     return mCheckedChanged;
@@ -966,17 +968,16 @@ protected:
 
   void UpdateEntries(const nsTArray<OwningFileOrDirectory>& aFilesOrDirectories);
 
   /**
    * Called after calling one of the SetFilesOrDirectories() functions.
    * This method can explore the directory recursively if needed.
    */
   void AfterSetFilesOrDirectories(bool aSetValueChanged);
-  void AfterSetFilesOrDirectoriesInternal(bool aSetValueChanged);
 
   /**
    * Recursively explore the directory and populate mFileOrDirectories correctly
    * for webkitdirectory.
    */
   void ExploreDirectoryRecursively(bool aSetValuechanged);
 
   /**
--- a/dom/ipc/Blob.cpp
+++ b/dom/ipc/Blob.cpp
@@ -901,16 +901,21 @@ CreateBlobImpl(const ParentBlobConstruct
       return nullptr;
     }
 
     if (NS_WARN_IF(params.modDate() == INT64_MAX)) {
       ASSERT_UNLESS_FUZZING();
       return nullptr;
     }
 
+    if (NS_WARN_IF(!params.path().IsEmpty())) {
+      ASSERT_UNLESS_FUZZING();
+      return nullptr;
+    }
+
     metadata.mContentType = params.contentType();
     metadata.mName = params.name();
     metadata.mLength = params.length();
     metadata.mLastModifiedDate = params.modDate();
   }
 
   RefPtr<BlobImpl> blobImpl =
     CreateBlobImplFromBlobData(aBlobData, metadata);
@@ -1708,16 +1713,17 @@ protected:
   const bool mIsSlice;
 
 public:
   // For File.
   RemoteBlobImpl(BlobChild* aActor,
                  BlobImpl* aRemoteBlobImpl,
                  const nsAString& aName,
                  const nsAString& aContentType,
+                 const nsAString& aPath,
                  uint64_t aLength,
                  int64_t aModDate,
                  bool aIsSameProcessBlob);
 
   // For Blob.
   RemoteBlobImpl(BlobChild* aActor,
                  BlobImpl* aRemoteBlobImpl,
                  const nsAString& aContentType,
@@ -2019,22 +2025,25 @@ private:
  * BlobChild::RemoteBlobImpl
  ******************************************************************************/
 
 BlobChild::
 RemoteBlobImpl::RemoteBlobImpl(BlobChild* aActor,
                                BlobImpl* aRemoteBlobImpl,
                                const nsAString& aName,
                                const nsAString& aContentType,
+                               const nsAString& aPath,
                                uint64_t aLength,
                                int64_t aModDate,
                                bool aIsSameProcessBlob)
   : BlobImplBase(aName, aContentType, aLength, aModDate)
   , mIsSlice(false)
 {
+  SetPath(aPath);
+
   if (aIsSameProcessBlob) {
     MOZ_ASSERT(aRemoteBlobImpl);
     mSameProcessBlobImpl = aRemoteBlobImpl;
     MOZ_ASSERT(gProcessType == GeckoProcessType_Default);
   } else {
     mDifferentProcessBlobImpl = aRemoteBlobImpl;
   }
 
@@ -2929,24 +2938,28 @@ BlobChild::CommonInit(BlobChild* aOther,
   otherImpl->GetType(contentType);
 
   ErrorResult rv;
   uint64_t length = otherImpl->GetSize(rv);
   MOZ_ASSERT(!rv.Failed());
 
   RemoteBlobImpl* remoteBlob = nullptr;
   if (otherImpl->IsFile()) {
-    nsString name;
+    nsAutoString name;
     otherImpl->GetName(name);
 
+    nsAutoString path;
+    otherImpl->GetPath(path);
+
     int64_t modDate = otherImpl->GetLastModified(rv);
     MOZ_ASSERT(!rv.Failed());
 
-    remoteBlob = new RemoteBlobImpl(this, otherImpl, name, contentType, length,
-                                    modDate, false /* SameProcessBlobImpl */);
+    remoteBlob =
+      new RemoteBlobImpl(this, otherImpl, name, contentType, path,
+                         length, modDate, false /* SameProcessBlobImpl */);
   } else {
     remoteBlob = new RemoteBlobImpl(this, otherImpl, contentType, length,
                                     false /* SameProcessBlobImpl */);
   }
 
   // This RemoteBlob must be kept alive untill RecvCreatedFromKnownBlob is
   // called because the parent will send this notification and we must be able
   // to manage it.
@@ -2986,16 +2999,17 @@ BlobChild::CommonInit(const ChildBlobCon
 
     case AnyBlobConstructorParams::TFileBlobConstructorParams: {
       const FileBlobConstructorParams& params =
         blobParams.get_FileBlobConstructorParams();
       remoteBlob = new RemoteBlobImpl(this,
                                       nullptr,
                                       params.name(),
                                       params.contentType(),
+                                      params.path(),
                                       params.length(),
                                       params.modDate(),
                                       false /* SameProcessBlobImpl */);
       break;
     }
 
     case AnyBlobConstructorParams::TSameProcessBlobConstructorParams: {
       MOZ_ASSERT(gProcessType == GeckoProcessType_Default);
@@ -3010,27 +3024,31 @@ BlobChild::CommonInit(const ChildBlobCon
       ErrorResult rv;
       uint64_t size = blobImpl->GetSize(rv);
       MOZ_ASSERT(!rv.Failed());
 
       nsString contentType;
       blobImpl->GetType(contentType);
 
       if (blobImpl->IsFile()) {
-        nsString name;
+        nsAutoString name;
         blobImpl->GetName(name);
 
+        nsAutoString path;
+        blobImpl->GetPath(path);
+
         int64_t lastModifiedDate = blobImpl->GetLastModified(rv);
         MOZ_ASSERT(!rv.Failed());
 
         remoteBlob =
           new RemoteBlobImpl(this,
                              blobImpl,
                              name,
                              contentType,
+                             path,
                              size,
                              lastModifiedDate,
                              true /* SameProcessBlobImpl */);
       } else {
         remoteBlob = new RemoteBlobImpl(this, blobImpl, contentType, size,
                                         true /* SameProcessBlobImpl */);
       }
 
@@ -3196,24 +3214,28 @@ BlobChild::GetOrCreateFromImpl(ChildMana
     nsString contentType;
     aBlobImpl->GetType(contentType);
 
     ErrorResult rv;
     uint64_t length = aBlobImpl->GetSize(rv);
     MOZ_ASSERT(!rv.Failed());
 
     if (aBlobImpl->IsFile()) {
-      nsString name;
+      nsAutoString name;
       aBlobImpl->GetName(name);
 
+      nsAutoString path;
+      aBlobImpl->GetPath(path);
+
       int64_t modDate = aBlobImpl->GetLastModified(rv);
       MOZ_ASSERT(!rv.Failed());
 
       blobParams =
-        FileBlobConstructorParams(name, contentType, length, modDate, blobData);
+        FileBlobConstructorParams(name, contentType, path, length, modDate,
+                                  blobData);
     } else {
       blobParams = NormalBlobConstructorParams(contentType, length, blobData);
     }
   }
 
   BlobChild* actor = new BlobChild(aManager, aBlobImpl);
 
   ParentBlobConstructorParams params(blobParams);
@@ -3387,16 +3409,17 @@ BlobChild::SetMysteryBlobInfo(const nsSt
   MOZ_ASSERT(mBlobImpl);
   MOZ_ASSERT(mRemoteBlobImpl);
   MOZ_ASSERT(aLastModifiedDate != INT64_MAX);
 
   mBlobImpl->SetLazyData(aName, aContentType, aLength, aLastModifiedDate);
 
   FileBlobConstructorParams params(aName,
                                    aContentType,
+                                   EmptyString(),
                                    aLength,
                                    aLastModifiedDate,
                                    void_t() /* optionalBlobData */);
   return SendResolveMystery(params);
 }
 
 bool
 BlobChild::SetMysteryBlobInfo(const nsString& aContentType, uint64_t aLength)
@@ -3743,24 +3766,27 @@ BlobParent::GetOrCreateFromImpl(ParentMa
       nsString contentType;
       aBlobImpl->GetType(contentType);
 
       ErrorResult rv;
       uint64_t length = aBlobImpl->GetSize(rv);
       MOZ_ASSERT(!rv.Failed());
 
       if (aBlobImpl->IsFile()) {
-        nsString name;
+        nsAutoString name;
         aBlobImpl->GetName(name);
 
+        nsAutoString path;
+        aBlobImpl->GetPath(path);
+
         int64_t modDate = aBlobImpl->GetLastModified(rv);
         MOZ_ASSERT(!rv.Failed());
 
         blobParams =
-          FileBlobConstructorParams(name, contentType, length, modDate,
+          FileBlobConstructorParams(name, contentType, path, length, modDate,
                                     void_t());
       } else {
         blobParams = NormalBlobConstructorParams(contentType, length, void_t());
       }
     }
   }
 
   nsID id;
--- a/dom/ipc/ContentChild.cpp
+++ b/dom/ipc/ContentChild.cpp
@@ -24,16 +24,17 @@
 #include "mozilla/docshell/OfflineCacheUpdateChild.h"
 #include "mozilla/dom/ContentBridgeChild.h"
 #include "mozilla/dom/ContentBridgeParent.h"
 #include "mozilla/dom/ContentParent.h"
 #include "mozilla/dom/DataTransfer.h"
 #include "mozilla/dom/DOMStorageIPC.h"
 #include "mozilla/dom/ExternalHelperAppChild.h"
 #include "mozilla/dom/FlyWebPublishedServerIPC.h"
+#include "mozilla/dom/GetFilesHelper.h"
 #include "mozilla/dom/PCrashReporterChild.h"
 #include "mozilla/dom/ProcessGlobal.h"
 #include "mozilla/dom/Promise.h"
 #include "mozilla/dom/workers/ServiceWorkerManager.h"
 #include "mozilla/dom/nsIContentChild.h"
 #include "mozilla/psm/PSMContentListener.h"
 #include "mozilla/hal_sandbox/PHalChild.h"
 #include "mozilla/ipc/BackgroundChild.h"
@@ -3367,10 +3368,66 @@ ContentChild::RecvNotifyPushSubscription
 {
 #ifndef MOZ_SIMPLEPUSH
   PushSubscriptionModifiedDispatcher dispatcher(aScope, aPrincipal);
   Unused << NS_WARN_IF(NS_FAILED(dispatcher.NotifyObservers()));
 #endif
   return true;
 }
 
+void
+ContentChild::CreateGetFilesRequest(const nsAString& aDirectoryPath,
+                                    bool aRecursiveFlag,
+                                    nsID& aUUID,
+                                    GetFilesHelperChild* aChild)
+{
+  MOZ_ASSERT(aChild);
+  MOZ_ASSERT(!mGetFilesPendingRequests.GetWeak(aUUID));
+
+  Unused << SendGetFilesRequest(aUUID, nsString(aDirectoryPath),
+                                aRecursiveFlag);
+  mGetFilesPendingRequests.Put(aUUID, aChild);
+}
+
+void
+ContentChild::DeleteGetFilesRequest(nsID& aUUID, GetFilesHelperChild* aChild)
+{
+  MOZ_ASSERT(aChild);
+  MOZ_ASSERT(mGetFilesPendingRequests.GetWeak(aUUID));
+
+  Unused << SendDeleteGetFilesRequest(aUUID);
+  mGetFilesPendingRequests.Remove(aUUID);
+}
+
+bool
+ContentChild::RecvGetFilesResponse(const nsID& aUUID,
+                                   const GetFilesResponseResult& aResult)
+{
+  GetFilesHelperChild* child = mGetFilesPendingRequests.GetWeak(aUUID);
+  // This object can already been deleted in case DeleteGetFilesRequest has
+  // been called when the response was sending by the parent.
+  if (!child) {
+    return true;
+  }
+
+  if (aResult.type() == GetFilesResponseResult::TGetFilesResponseFailure) {
+    child->Finished(aResult.get_GetFilesResponseFailure().errorCode());
+  } else {
+    MOZ_ASSERT(aResult.type() == GetFilesResponseResult::TGetFilesResponseSuccess);
+
+    const nsTArray<PBlobChild*>& blobs =
+      aResult.get_GetFilesResponseSuccess().blobsChild();
+
+    bool succeeded = true;
+    for (uint32_t i = 0; succeeded && i < blobs.Length(); ++i) {
+      RefPtr<BlobImpl> impl = static_cast<BlobChild*>(blobs[i])->GetBlobImpl();
+      succeeded = child->AppendBlobImpl(impl);
+    }
+
+    child->Finished(succeeded ? NS_OK : NS_ERROR_OUT_OF_MEMORY);
+  }
+
+  mGetFilesPendingRequests.Remove(aUUID);
+  return true;
+}
+
 } // namespace dom
 } // namespace mozilla
--- a/dom/ipc/ContentChild.h
+++ b/dom/ipc/ContentChild.h
@@ -11,16 +11,17 @@
 #include "mozilla/dom/ContentBridgeParent.h"
 #include "mozilla/dom/nsIContentChild.h"
 #include "mozilla/dom/PBrowserOrId.h"
 #include "mozilla/dom/PContentChild.h"
 #include "nsAutoPtr.h"
 #include "nsHashKeys.h"
 #include "nsIObserver.h"
 #include "nsTHashtable.h"
+#include "nsRefPtrHashtable.h"
 
 #include "nsWeakPtr.h"
 #include "nsIWindowProvider.h"
 
 
 struct ChromePackage;
 class nsIObserver;
 struct SubstitutionMapping;
@@ -42,16 +43,17 @@ class PCompositorBridgeChild;
 
 namespace dom {
 
 class AlertObserver;
 class ConsoleListener;
 class PStorageChild;
 class ClonedMessageData;
 class TabChild;
+class GetFilesHelperChild;
 
 class ContentChild final : public PContentChild
                          , public nsIWindowProvider
                          , public nsIContentChild
 {
   typedef mozilla::dom::ClonedMessageData ClonedMessageData;
   typedef mozilla::ipc::OptionalURIParams OptionalURIParams;
   typedef mozilla::ipc::PFileDescriptorSetChild PFileDescriptorSetChild;
@@ -622,16 +624,30 @@ public:
   DeallocPContentPermissionRequestChild(PContentPermissionRequestChild* actor) override;
 
   // Windows specific - set up audio session
   virtual bool
   RecvSetAudioSessionData(const nsID& aId,
                           const nsString& aDisplayName,
                           const nsString& aIconPath) override;
 
+
+  // GetFiles for WebKit/Blink FileSystem API and Directory API must run on the
+  // parent process.
+  void
+  CreateGetFilesRequest(const nsAString& aDirectoryPath, bool aRecursiveFlag,
+                        nsID& aUUID, GetFilesHelperChild* aChild);
+
+  void
+  DeleteGetFilesRequest(nsID& aUUID, GetFilesHelperChild* aChild);
+
+  virtual bool
+  RecvGetFilesResponse(const nsID& aUUID,
+                       const GetFilesResponseResult& aResult) override;
+
 private:
   static void ForceKillTimerCallback(nsITimer* aTimer, void* aClosure);
   void StartForceKillTimer();
 
   virtual void ActorDestroy(ActorDestroyReason why) override;
 
   virtual void ProcessingError(Result aCode, const char* aReason) override;
 
@@ -659,16 +675,21 @@ private:
   bool mIsAlive;
   nsString mProcessName;
 
   static ContentChild* sSingleton;
 
   nsCOMPtr<nsIDomainPolicy> mPolicy;
   nsCOMPtr<nsITimer> mForceKillTimer;
 
+  // Hashtable to keep track of the pending GetFilesHelper objects.
+  // This GetFilesHelperChild objects are removed when RecvGetFilesResponse is
+  // received.
+ nsRefPtrHashtable<nsIDHashKey, GetFilesHelperChild> mGetFilesPendingRequests;
+
   DISALLOW_EVIL_CONSTRUCTORS(ContentChild);
 };
 
 void
 InitOnContentProcessCreated();
 
 uint64_t
 NextWindowID();
--- a/dom/ipc/ContentParent.cpp
+++ b/dom/ipc/ContentParent.cpp
@@ -41,16 +41,17 @@
 #include "mozilla/DataStorage.h"
 #include "mozilla/devtools/HeapSnapshotTempFileHelperParent.h"
 #include "mozilla/docshell/OfflineCacheUpdateParent.h"
 #include "mozilla/dom/DataTransfer.h"
 #include "mozilla/dom/DOMStorageIPC.h"
 #include "mozilla/dom/Element.h"
 #include "mozilla/dom/File.h"
 #include "mozilla/dom/ExternalHelperAppParent.h"
+#include "mozilla/dom/GetFilesHelper.h"
 #include "mozilla/dom/GeolocationBinding.h"
 #ifdef MOZ_EME
 #include "mozilla/dom/MediaKeySystemAccess.h"
 #endif
 #include "mozilla/dom/Notification.h"
 #include "mozilla/dom/NuwaParent.h"
 #include "mozilla/dom/PContentBridgeParent.h"
 #include "mozilla/dom/PContentPermissionRequestParent.h"
@@ -5719,8 +5720,51 @@ ContentParent::HandleWindowsMessages(con
   // a11y message we can reenter the ipc message sending code.
   if (a11y::PDocAccessible::PDocAccessibleStart < aMsg.type() &&
       a11y::PDocAccessible::PDocAccessibleEnd > aMsg.type()) {
     return false;
   }
 
   return true;
 }
+
+bool
+ContentParent::RecvGetFilesRequest(const nsID& aUUID,
+                                   const nsString& aDirectoryPath,
+                                   const bool& aRecursiveFlag)
+{
+  MOZ_ASSERT(!mGetFilesPendingRequests.GetWeak(aUUID));
+
+  ErrorResult rv;
+  RefPtr<GetFilesHelper> helper =
+    GetFilesHelperParent::Create(aUUID, aDirectoryPath, aRecursiveFlag, this,
+                                 rv);
+
+  if (NS_WARN_IF(rv.Failed())) {
+    return SendGetFilesResponse(aUUID,
+                                GetFilesResponseFailure(rv.StealNSResult()));
+  }
+
+  mGetFilesPendingRequests.Put(aUUID, helper);
+  return true;
+}
+
+bool
+ContentParent::RecvDeleteGetFilesRequest(const nsID& aUUID)
+{
+  GetFilesHelper* helper = mGetFilesPendingRequests.GetWeak(aUUID);
+  if (helper) {
+    mGetFilesPendingRequests.Remove(aUUID);
+  }
+
+  return true;
+}
+
+void
+ContentParent::SendGetFilesResponseAndForget(const nsID& aUUID,
+                                             const GetFilesResponseResult& aResult)
+{
+  GetFilesHelper* helper = mGetFilesPendingRequests.GetWeak(aUUID);
+  if (helper) {
+    mGetFilesPendingRequests.Remove(aUUID);
+    Unused << SendGetFilesResponse(aUUID, aResult);
+  }
+}
--- a/dom/ipc/ContentParent.h
+++ b/dom/ipc/ContentParent.h
@@ -20,16 +20,17 @@
 
 #include "nsDataHashtable.h"
 #include "nsFrameMessageManager.h"
 #include "nsHashKeys.h"
 #include "nsIObserver.h"
 #include "nsIThreadInternal.h"
 #include "nsIDOMGeoPositionCallback.h"
 #include "nsIDOMGeoPositionErrorCallback.h"
+#include "nsRefPtrHashtable.h"
 #include "PermissionMessageUtils.h"
 #include "DriverCrashGuard.h"
 
 #define CHILD_PROCESS_SHUTDOWN_MESSAGE NS_LITERAL_STRING("child-process-shutdown")
 
 class mozIApplication;
 class nsConsoleService;
 class nsICycleCollectorLogSink;
@@ -78,16 +79,17 @@ namespace dom {
 
 class Element;
 class TabParent;
 class PStorageParent;
 class ClonedMessageData;
 class MemoryReport;
 class TabContext;
 class ContentBridgeParent;
+class GetFilesHelper;
 
 class ContentParent final : public PContentParent
                           , public nsIContentParent
                           , public nsIObserver
                           , public nsIDOMGeoPositionCallback
                           , public nsIDOMGeoPositionErrorCallback
                           , public mozilla::LinkedListElement<ContentParent>
 {
@@ -1163,16 +1165,28 @@ private:
   virtual bool RecvNotifyPushSubscriptionChangeObservers(const nsCString& aScope,
                                                          const IPC::Principal& aPrincipal) override;
 
   virtual bool RecvNotifyPushSubscriptionModifiedObservers(const nsCString& aScope,
                                                            const IPC::Principal& aPrincipal) override;
 
   virtual bool RecvNotifyLowMemory() override;
 
+  virtual bool RecvGetFilesRequest(const nsID& aID,
+                                   const nsString& aDirectoryPath,
+                                   const bool& aRecursiveFlag) override;
+
+  virtual bool RecvDeleteGetFilesRequest(const nsID& aID) override;
+
+public:
+  void SendGetFilesResponseAndForget(const nsID& aID,
+                                     const GetFilesResponseResult& aResult);
+
+private:
+
   // If you add strong pointers to cycle collected objects here, be sure to
   // release these objects in ShutDownProcess.  See the comment there for more
   // details.
 
   GeckoChildProcessHost* mSubprocess;
   ContentParent* mOpener;
 
   ContentParentId mChildID;
@@ -1258,16 +1272,20 @@ private:
   mozilla::UniquePtr<SandboxBroker> mSandboxBroker;
   static mozilla::UniquePtr<SandboxBrokerPolicyFactory>
       sSandboxBrokerPolicyFactory;
 #endif
 
 #ifdef NS_PRINTING
   RefPtr<embedding::PrintingParent> mPrintingParent;
 #endif
+
+  // This hashtable is used to run GetFilesHelper objects in the parent process.
+  // GetFilesHelper can be aborted by receiving RecvDeleteGetFilesRequest.
+  nsRefPtrHashtable<nsIDHashKey, GetFilesHelper> mGetFilesPendingRequests;
 };
 
 } // namespace dom
 } // namespace mozilla
 
 class ParentIdleListener : public nsIObserver
 {
   friend class mozilla::dom::ContentParent;
--- a/dom/ipc/DOMTypes.ipdlh
+++ b/dom/ipc/DOMTypes.ipdlh
@@ -66,16 +66,17 @@ struct NormalBlobConstructorParams
   // be of type void_t in a parent->child message.
   OptionalBlobData optionalBlobData;
 };
 
 struct FileBlobConstructorParams
 {
   nsString name;
   nsString contentType;
+  nsString path;
   uint64_t length;
   int64_t modDate;
 
   // This must be of type BlobData in a child->parent message, and will always
   // be of type void_t in a parent->child message.
   OptionalBlobData optionalBlobData;
 };
 
--- a/dom/ipc/PContent.ipdl
+++ b/dom/ipc/PContent.ipdl
@@ -352,16 +352,32 @@ struct AndroidSystemInfo
     nsString device;
     nsString manufacturer;
     nsString release_version;
     nsString hardware;
     uint32_t sdk_version;
     bool     isTablet;
 };
 
+struct GetFilesResponseSuccess
+{
+  PBlob[] blobs;
+};
+
+struct GetFilesResponseFailure
+{
+  nsresult errorCode;
+};
+
+union GetFilesResponseResult
+{
+  GetFilesResponseSuccess;
+  GetFilesResponseFailure;
+};
+
 prio(normal upto urgent) sync protocol PContent
 {
     parent spawns PPluginModule;
 
     parent opens PCompositorBridge;
     parent opens PProcessHangMonitor;
     parent opens PSharedBufferManager;
     parent opens PImageBridge;
@@ -652,16 +668,19 @@ child:
 
     /**
      * Windows specific: associate this content process with the browsers
      * audio session.
      */
     async SetAudioSessionData(nsID aID,
                               nsString aDisplayName,
                               nsString aIconPath);
+
+    async GetFilesResponse(nsID aID, GetFilesResponseResult aResult);
+
 parent:
     /**
      * Tell the content process some attributes of itself.  This is
      * among the first information queried by content processes after
      * startup.  (The message is sync to allow the content process to
      * control when it receives the information.)
      *
      * |id| is a unique ID among all subprocesses.  When |isForApp &&
@@ -1161,16 +1180,19 @@ parent:
 
     /**
      * Tell the parent process that the child process is low on memory. This
      * allows the parent process to save a memory report that can potentially be
      * sent with a crash report from the content process.
      */
      async NotifyLowMemory();
 
+     async GetFilesRequest(nsID aID, nsString aDirectory, bool aRecursiveFlag);
+     async DeleteGetFilesRequest(nsID aID);
+
 both:
      async AsyncMessage(nsString aMessage, CpowEntry[] aCpows,
                         Principal aPrincipal, ClonedMessageData aData);
 
     /**
      * Notify `push-subscription-modified` observers in the parent and child.
      */
     async NotifyPushSubscriptionModifiedObservers(nsCString scope,
--- a/dom/media/eme/CDMProxy.cpp
+++ b/dom/media/eme/CDMProxy.cpp
@@ -11,17 +11,17 @@
 #include "mozIGeckoMediaPluginService.h"
 #include "nsContentCID.h"
 #include "nsServiceManagerUtils.h"
 #include "MainThreadUtils.h"
 #include "mozilla/EMEUtils.h"
 #include "nsIConsoleService.h"
 #include "prenv.h"
 #include "mozilla/PodOperations.h"
-#include "mozilla/CDMCallbackProxy.h"
+#include "GMPCDMCallbackProxy.h"
 #include "MediaData.h"
 #include "nsPrintfCString.h"
 #include "GMPService.h"
 
 namespace mozilla {
 
 CDMProxy::CDMProxy(dom::MediaKeys* aKeys, const nsAString& aKeySystem)
   : mKeys(aKeys)
@@ -114,17 +114,17 @@ CDMProxy::gmp_InitDone(GMPDecryptorProxy
   }
   if (!aCDM) {
     RejectPromise(aData->mPromiseId, NS_ERROR_DOM_INVALID_STATE_ERR,
                   NS_LITERAL_CSTRING("GetGMPDecryptor failed to return a CDM"));
     return;
   }
 
   mCDM = aCDM;
-  mCallback = new CDMCallbackProxy(this);
+  mCallback = new GMPCDMCallbackProxy(this);
   mCDM->Init(mCallback);
   nsCOMPtr<nsIRunnable> task(
     NewRunnableMethod<uint32_t>(this,
                                 &CDMProxy::OnCDMCreated,
                                 aData->mPromiseId));
   NS_DispatchToMainThread(task);
 }
 
--- a/dom/media/eme/CDMProxy.h
+++ b/dom/media/eme/CDMProxy.h
@@ -15,17 +15,17 @@
 
 #include "nsIThread.h"
 #include "nsString.h"
 #include "nsAutoPtr.h"
 #include "GMPDecryptorProxy.h"
 
 namespace mozilla {
 class MediaRawData;
-class CDMCallbackProxy;
+class GMPCDMCallbackProxy;
 
 namespace dom {
 class MediaKeySession;
 } // namespace dom
 
 struct DecryptResult {
   DecryptResult(GMPErr aStatus, MediaRawData* aSample)
     : mStatus(aStatus)
@@ -333,17 +333,17 @@ private:
   // Gecko Media Plugin thread. All interactions with the out-of-process
   // EME plugin must come from this thread.
   RefPtr<nsIThread> mGMPThread;
 
   nsCString mNodeId;
 
   GMPDecryptorProxy* mCDM;
   CDMCaps mCapabilites;
-  nsAutoPtr<CDMCallbackProxy> mCallback;
+  nsAutoPtr<GMPCDMCallbackProxy> mCallback;
 
   // Decryption jobs sent to CDM, awaiting result.
   // GMP thread only.
   nsTArray<RefPtr<DecryptJob>> mDecryptionJobs;
 
   // Number of buffers we've decrypted. Used to uniquely identify
   // decryption jobs sent to CDM. Note we can't just use the length of
   // mDecryptionJobs as that shrinks as jobs are completed and removed
--- a/dom/media/eme/moz.build
+++ b/dom/media/eme/moz.build
@@ -11,25 +11,23 @@ EXPORTS.mozilla.dom += [
     'MediaKeys.h',
     'MediaKeySession.h',
     'MediaKeyStatusMap.h',
     'MediaKeySystemAccess.h',
     'MediaKeySystemAccessManager.h',
 ]
 
 EXPORTS.mozilla += [
-    'CDMCallbackProxy.h',
     'CDMCaps.h',
     'CDMProxy.h',
     'DetailedPromise.h',
     'EMEUtils.h',
 ]
 
 UNIFIED_SOURCES += [
-    'CDMCallbackProxy.cpp',
     'CDMCaps.cpp',
     'CDMProxy.cpp',
     'DetailedPromise.cpp',
     'EMEUtils.cpp',
     'MediaEncryptedEvent.cpp',
     'MediaKeyError.cpp',
     'MediaKeyMessageEvent.cpp',
     'MediaKeys.cpp',
rename from dom/media/eme/CDMCallbackProxy.cpp
rename to dom/media/gmp/GMPCDMCallbackProxy.cpp
--- a/dom/media/eme/CDMCallbackProxy.cpp
+++ b/dom/media/gmp/GMPCDMCallbackProxy.cpp
@@ -1,28 +1,28 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-#include "mozilla/CDMCallbackProxy.h"
+#include "GMPCDMCallbackProxy.h"
 #include "mozilla/CDMProxy.h"
 #include "nsString.h"
 #include "mozilla/dom/MediaKeys.h"
 #include "mozilla/dom/MediaKeySession.h"
 #include "mozIGeckoMediaPluginService.h"
 #include "nsContentCID.h"
 #include "nsServiceManagerUtils.h"
 #include "MainThreadUtils.h"
 #include "mozilla/EMEUtils.h"
 
 namespace mozilla {
 
-CDMCallbackProxy::CDMCallbackProxy(CDMProxy* aProxy)
+GMPCDMCallbackProxy::GMPCDMCallbackProxy(CDMProxy* aProxy)
   : mProxy(aProxy)
 {
 
 }
 
 class SetSessionIdTask : public Runnable {
 public:
   SetSessionIdTask(CDMProxy* aProxy,
@@ -40,18 +40,18 @@ public:
   }
 
   RefPtr<CDMProxy> mProxy;
   uint32_t mToken;
   nsString mSid;
 };
 
 void
-CDMCallbackProxy::SetSessionId(uint32_t aToken,
-                               const nsCString& aSessionId)
+GMPCDMCallbackProxy::SetSessionId(uint32_t aToken,
+                                  const nsCString& aSessionId)
 {
   MOZ_ASSERT(mProxy->IsOnGMPThread());
 
   nsCOMPtr<nsIRunnable> task(new SetSessionIdTask(mProxy,
                                                   aToken,
                                                   aSessionId));
   NS_DispatchToMainThread(task);
 }
@@ -73,28 +73,29 @@ public:
   }
 
   RefPtr<CDMProxy> mProxy;
   dom::PromiseId mPid;
   bool mSuccess;
 };
 
 void
-CDMCallbackProxy::ResolveLoadSessionPromise(uint32_t aPromiseId, bool aSuccess)
+GMPCDMCallbackProxy::ResolveLoadSessionPromise(uint32_t aPromiseId,
+                                               bool aSuccess)
 {
   MOZ_ASSERT(mProxy->IsOnGMPThread());
 
   nsCOMPtr<nsIRunnable> task(new LoadSessionTask(mProxy,
                                                  aPromiseId,
                                                  aSuccess));
   NS_DispatchToMainThread(task);
 }
 
 void
-CDMCallbackProxy::ResolvePromise(uint32_t aPromiseId)
+GMPCDMCallbackProxy::ResolvePromise(uint32_t aPromiseId)
 {
   MOZ_ASSERT(mProxy->IsOnGMPThread());
 
   // Note: CDMProxy proxies this from non-main threads to main thread.
   mProxy->ResolvePromise(aPromiseId);
 }
 
 class RejectPromiseTask : public Runnable {
@@ -118,19 +119,19 @@ public:
   RefPtr<CDMProxy> mProxy;
   dom::PromiseId mPid;
   nsresult mException;
   nsCString mMsg;
 };
 
 
 void
-CDMCallbackProxy::RejectPromise(uint32_t aPromiseId,
-                                nsresult aException,
-                                const nsCString& aMessage)
+GMPCDMCallbackProxy::RejectPromise(uint32_t aPromiseId,
+                                   nsresult aException,
+                                   const nsCString& aMessage)
 {
   MOZ_ASSERT(mProxy->IsOnGMPThread());
 
   nsCOMPtr<nsIRunnable> task;
   task = new RejectPromiseTask(mProxy,
                                aPromiseId,
                                aException,
                                aMessage);
@@ -158,19 +159,19 @@ public:
   RefPtr<CDMProxy> mProxy;
   dom::PromiseId mPid;
   nsString mSid;
   GMPSessionMessageType mMsgType;
   nsTArray<uint8_t> mMsg;
 };
 
 void
-CDMCallbackProxy::SessionMessage(const nsCString& aSessionId,
-                                 GMPSessionMessageType aMessageType,
-                                 const nsTArray<uint8_t>& aMessage)
+GMPCDMCallbackProxy::SessionMessage(const nsCString& aSessionId,
+                                    GMPSessionMessageType aMessageType,
+                                    const nsTArray<uint8_t>& aMessage)
 {
   MOZ_ASSERT(mProxy->IsOnGMPThread());
 
   nsCOMPtr<nsIRunnable> task;
   task = new SessionMessageTask(mProxy,
                                 aSessionId,
                                 aMessageType,
                                 aMessage);
@@ -193,30 +194,30 @@ public:
   }
 
   RefPtr<CDMProxy> mProxy;
   nsString mSid;
   GMPTimestamp mTimestamp;
 };
 
 void
-CDMCallbackProxy::ExpirationChange(const nsCString& aSessionId,
-                                   GMPTimestamp aExpiryTime)
+GMPCDMCallbackProxy::ExpirationChange(const nsCString& aSessionId,
+                                      GMPTimestamp aExpiryTime)
 {
   MOZ_ASSERT(mProxy->IsOnGMPThread());
 
   nsCOMPtr<nsIRunnable> task;
   task = new ExpirationChangeTask(mProxy,
                                   aSessionId,
                                   aExpiryTime);
   NS_DispatchToMainThread(task);
 }
 
 void
-CDMCallbackProxy::SessionClosed(const nsCString& aSessionId)
+GMPCDMCallbackProxy::SessionClosed(const nsCString& aSessionId)
 {
   MOZ_ASSERT(mProxy->IsOnGMPThread());
 
   bool keyStatusesChange = false;
   {
     CDMCaps::AutoLock caps(mProxy->Capabilites());
     keyStatusesChange = caps.RemoveKeysForSession(NS_ConvertUTF8toUTF16(aSessionId));
   }
@@ -258,36 +259,36 @@ public:
   dom::PromiseId mPid;
   nsString mSid;
   nsresult mException;
   uint32_t mSystemCode;
   nsString mMsg;
 };
 
 void
-CDMCallbackProxy::SessionError(const nsCString& aSessionId,
-                               nsresult aException,
-                               uint32_t aSystemCode,
-                               const nsCString& aMessage)
+GMPCDMCallbackProxy::SessionError(const nsCString& aSessionId,
+                                  nsresult aException,
+                                  uint32_t aSystemCode,
+                                  const nsCString& aMessage)
 {
   MOZ_ASSERT(mProxy->IsOnGMPThread());
 
   nsCOMPtr<nsIRunnable> task;
   task = new SessionErrorTask(mProxy,
                               aSessionId,
                               aException,
                               aSystemCode,
                               aMessage);
   NS_DispatchToMainThread(task);
 }
 
 void
-CDMCallbackProxy::KeyStatusChanged(const nsCString& aSessionId,
-                                   const nsTArray<uint8_t>& aKeyId,
-                                   GMPMediaKeyStatus aStatus)
+GMPCDMCallbackProxy::KeyStatusChanged(const nsCString& aSessionId,
+                                      const nsTArray<uint8_t>& aKeyId,
+                                      GMPMediaKeyStatus aStatus)
 {
   MOZ_ASSERT(mProxy->IsOnGMPThread());
 
   bool keyStatusesChange = false;
   {
     CDMCaps::AutoLock caps(mProxy->Capabilites());
     keyStatusesChange = caps.SetKeyStatus(aKeyId,
                                           NS_ConvertUTF8toUTF16(aSessionId),
@@ -298,26 +299,26 @@ CDMCallbackProxy::KeyStatusChanged(const
     task = NewRunnableMethod<nsString>(mProxy,
                                        &CDMProxy::OnKeyStatusesChange,
                                        NS_ConvertUTF8toUTF16(aSessionId));
     NS_DispatchToMainThread(task);
   }
 }
 
 void
-CDMCallbackProxy::Decrypted(uint32_t aId,
-                            GMPErr aResult,
-                            const nsTArray<uint8_t>& aDecryptedData)
+GMPCDMCallbackProxy::Decrypted(uint32_t aId,
+                               GMPErr aResult,
+                               const nsTArray<uint8_t>& aDecryptedData)
 {
   MOZ_ASSERT(mProxy->IsOnGMPThread());
 
   mProxy->gmp_Decrypted(aId, aResult, aDecryptedData);
 }
 
 void
-CDMCallbackProxy::Terminated()
+GMPCDMCallbackProxy::Terminated()
 {
   MOZ_ASSERT(mProxy->IsOnGMPThread());
   nsCOMPtr<nsIRunnable> task = NewRunnableMethod(mProxy, &CDMProxy::Terminated);
   NS_DispatchToMainThread(task);
 }
 
 } // namespace mozilla
rename from dom/media/eme/CDMCallbackProxy.h
rename to dom/media/gmp/GMPCDMCallbackProxy.h
--- a/dom/media/eme/CDMCallbackProxy.h
+++ b/dom/media/gmp/GMPCDMCallbackProxy.h
@@ -1,26 +1,26 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-#ifndef CDMCallbackProxy_h_
-#define CDMCallbackProxy_h_
+#ifndef GMPCDMCallbackProxy_h_
+#define GMPCDMCallbackProxy_h_
 
 #include "mozilla/CDMProxy.h"
 #include "gmp-decryption.h"
 #include "GMPDecryptorProxy.h"
 
 namespace mozilla {
 
 // Proxies call backs from the CDM on the GMP thread back to the MediaKeys
 // object on the main thread.
-class CDMCallbackProxy : public GMPDecryptorProxyCallback {
+class GMPCDMCallbackProxy : public GMPDecryptorProxyCallback {
 public:
   void SetSessionId(uint32_t aCreateSessionToken,
                     const nsCString& aSessionId) override;
 
   void ResolveLoadSessionPromise(uint32_t aPromiseId,
                                  bool aSuccess) override;
 
   void ResolvePromise(uint32_t aPromiseId) override;
@@ -48,21 +48,21 @@ public:
                         GMPMediaKeyStatus aStatus) override;
 
   void Decrypted(uint32_t aId,
                  GMPErr aResult,
                  const nsTArray<uint8_t>& aDecryptedData) override;
 
   void Terminated() override;
 
-  ~CDMCallbackProxy() {}
+  ~GMPCDMCallbackProxy() {}
 
 private:
   friend class CDMProxy;
-  explicit CDMCallbackProxy(CDMProxy* aProxy);
+  explicit GMPCDMCallbackProxy(CDMProxy* aProxy);
 
   // Warning: Weak ref.
   CDMProxy* mProxy;
 };
 
 } // namespace mozilla
 
-#endif // CDMCallbackProxy_h_
+#endif // GMPCDMCallbackProxy_h_
--- a/dom/media/gmp/moz.build
+++ b/dom/media/gmp/moz.build
@@ -30,16 +30,17 @@ EXPORTS += [
     'gmp-api/gmp-video-frame.h',
     'gmp-api/gmp-video-host.h',
     'gmp-api/gmp-video-plane.h',
     'GMPAudioDecoderChild.h',
     'GMPAudioDecoderParent.h',
     'GMPAudioDecoderProxy.h',
     'GMPAudioHost.h',
     'GMPCallbackBase.h',
+    'GMPCDMCallbackProxy.h',
     'GMPChild.h',
     'GMPContentChild.h',
     'GMPContentParent.h',
     'GMPCrashHelperHolder.h',
     'GMPDecryptorChild.h',
     'GMPDecryptorParent.h',
     'GMPDecryptorProxy.h',
     'GMPEncryptedBufferDataImpl.h',
@@ -80,16 +81,17 @@ if CONFIG['OS_TARGET'] == 'Android':
     USE_LIBS += [
         'rlz',
     ]
 
 UNIFIED_SOURCES += [
     'GMPAudioDecoderChild.cpp',
     'GMPAudioDecoderParent.cpp',
     'GMPAudioHost.cpp',
+    'GMPCDMCallbackProxy.cpp',
     'GMPChild.cpp',
     'GMPContentChild.cpp',
     'GMPContentParent.cpp',
     'GMPDecryptorChild.cpp',
     'GMPDecryptorParent.cpp',
     'GMPDiskStorage.cpp',
     'GMPEncryptedBufferDataImpl.cpp',
     'GMPMemoryStorage.cpp',
--- a/dom/presentation/Presentation.cpp
+++ b/dom/presentation/Presentation.cpp
@@ -1,23 +1,26 @@
 /* -*- 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 "Presentation.h"
+
 #include <ctype.h>
+
 #include "mozilla/dom/PresentationBinding.h"
 #include "mozilla/dom/Promise.h"
 #include "nsContentUtils.h"
 #include "nsCycleCollectionParticipant.h"
 #include "nsIDocShell.h"
 #include "nsIPresentationService.h"
+#include "nsSandboxFlags.h"
 #include "nsServiceManagerUtils.h"
-#include "Presentation.h"
 #include "PresentationReceiver.h"
 
 using namespace mozilla;
 using namespace mozilla::dom;
 
 NS_IMPL_CYCLE_COLLECTION_INHERITED(Presentation, DOMEventTargetHelper,
                                    mDefaultRequest, mReceiver)
 
@@ -52,16 +55,25 @@ Presentation::WrapObject(JSContext* aCx,
 
 void
 Presentation::SetDefaultRequest(PresentationRequest* aRequest)
 {
   if (IsInPresentedContent()) {
     return;
   }
 
+  nsCOMPtr<nsIDocument> doc = GetOwner() ? GetOwner()->GetExtantDoc() : nullptr;
+  if (NS_WARN_IF(!doc)) {
+    return;
+  }
+
+  if (doc->GetSandboxFlags() & SANDBOXED_PRESENTATION) {
+    return;
+  }
+
   mDefaultRequest = aRequest;
 }
 
 already_AddRefed<PresentationRequest>
 Presentation::GetDefaultRequest() const
 {
   if (IsInPresentedContent()) {
     return nullptr;
--- a/dom/presentation/PresentationRequest.cpp
+++ b/dom/presentation/PresentationRequest.cpp
@@ -1,25 +1,27 @@
 /* -*- 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 "PresentationRequest.h"
+
 #include "mozilla/dom/PresentationRequestBinding.h"
 #include "mozilla/dom/PresentationConnectionAvailableEvent.h"
 #include "mozilla/dom/Promise.h"
 #include "mozIThirdPartyUtil.h"
 #include "nsCycleCollectionParticipant.h"
 #include "nsIPresentationService.h"
 #include "nsIUUIDGenerator.h"
+#include "nsSandboxFlags.h"
 #include "nsServiceManagerUtils.h"
 #include "PresentationAvailability.h"
 #include "PresentationCallbacks.h"
-#include "PresentationRequest.h"
 
 using namespace mozilla;
 using namespace mozilla::dom;
 
 NS_IMPL_CYCLE_COLLECTION_INHERITED(PresentationRequest, DOMEventTargetHelper,
                                    mAvailability)
 
 NS_IMPL_ADDREF_INHERITED(PresentationRequest, DOMEventTargetHelper)
@@ -97,21 +99,32 @@ PresentationRequest::StartWithDevice(con
   // Get the origin.
   nsAutoString origin;
   nsresult rv = nsContentUtils::GetUTFOrigin(global->PrincipalOrNull(), origin);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     aRv.Throw(rv);
     return nullptr;
   }
 
+  nsCOMPtr<nsIDocument> doc = GetOwner()->GetExtantDoc();
+  if (NS_WARN_IF(!doc)) {
+    aRv.Throw(NS_ERROR_FAILURE);
+    return nullptr;
+  }
+
   RefPtr<Promise> promise = Promise::Create(global, aRv);
   if (NS_WARN_IF(aRv.Failed())) {
     return nullptr;
   }
 
+  if (doc->GetSandboxFlags() & SANDBOXED_PRESENTATION) {
+    promise->MaybeReject(NS_ERROR_DOM_SECURITY_ERR);
+    return promise.forget();
+  }
+
   // Generate a session ID.
   nsCOMPtr<nsIUUIDGenerator> uuidgen =
     do_GetService("@mozilla.org/uuid-generator;1");
   if(NS_WARN_IF(!uuidgen)) {
     promise->MaybeReject(NS_ERROR_DOM_OPERATION_ERR);
     return promise.forget();
   }
 
@@ -143,21 +156,32 @@ already_AddRefed<Promise>
 PresentationRequest::GetAvailability(ErrorResult& aRv)
 {
   nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(GetOwner());
   if (NS_WARN_IF(!global)) {
     aRv.Throw(NS_ERROR_UNEXPECTED);
     return nullptr;
   }
 
+  nsCOMPtr<nsIDocument> doc = GetOwner()->GetExtantDoc();
+  if (NS_WARN_IF(!doc)) {
+    aRv.Throw(NS_ERROR_FAILURE);
+    return nullptr;
+  }
+
   RefPtr<Promise> promise = Promise::Create(global, aRv);
   if (NS_WARN_IF(aRv.Failed())) {
     return nullptr;
   }
 
+  if (doc->GetSandboxFlags() & SANDBOXED_PRESENTATION) {
+    promise->MaybeReject(NS_ERROR_DOM_SECURITY_ERR);
+    return promise.forget();
+  }
+
   promise->MaybeResolve(mAvailability);
   return promise.forget();
 }
 
 nsresult
 PresentationRequest::DispatchConnectionAvailableEvent(PresentationConnection* aConnection)
 {
   PresentationConnectionAvailableEventInit init;
new file mode 100644
--- /dev/null
+++ b/dom/presentation/tests/mochitest/file_presentation_sandboxed_presentation.html
@@ -0,0 +1,96 @@
+
+<!DOCTYPE HTML>
+<html>
+<head>
+<meta charset="utf-8">
+<title>Test allow-presentation sandboxing flag</title>
+<script type="application/javascript;version=1.8">
+
+"use strict";
+
+function is(a, b, msg) {
+  window.parent.postMessage((a === b ? "OK " : "KO ") + msg, "*");
+}
+
+function ok(a, msg) {
+  window.parent.postMessage((a ? "OK " : "KO ") + msg, "*");
+}
+
+function info(msg) {
+  window.parent.postMessage("INFO " + msg, "*");
+}
+
+function command(msg) {
+  window.parent.postMessage("COMMAND " + JSON.stringify(msg), "*");
+}
+
+function finish() {
+  window.parent.postMessage("DONE", "*");
+}
+
+function testGetAvailability() {
+  return new Promise(function(aResolve, aReject) {
+    ok(navigator.presentation, "navigator.presentation should be available.");
+    var request = new PresentationRequest("http://example.com");
+
+    request.getAvailability().then(
+      function(aAvailability) {
+        ok(false, "Unexpected success, should get a security error.");
+        aReject();
+      },
+      function(aError) {
+        is(aError.name, "SecurityError", "Should get a security error.");
+        aResolve();
+      }
+    );
+  });
+}
+
+function testStartRequest() {
+  return new Promise(function(aResolve, aReject) {
+    var request = new PresentationRequest("http://example.com");
+
+    request.start().then(
+      function(aAvailability) {
+        ok(false, "Unexpected success, should get a security error.");
+        aReject();
+      },
+      function(aError) {
+        is(aError.name, "SecurityError", "Should get a security error.");
+        aResolve();
+      }
+    );
+  });
+}
+
+function testDefaultRequest() {
+  return new Promise(function(aResolve, aReject) {
+    navigator.presentation.defaultRequest = new PresentationRequest("http://example.com");
+    is(navigator.presentation.defaultRequest, null, "DefaultRequest shoud be null.");
+    aResolve();
+  });
+}
+
+function runTest() {
+  testGetAvailability()
+  .then(testStartRequest)
+  .then(testDefaultRequest)
+  .then(finish);
+}
+
+window.addEventListener("message", function onMessage(evt) {
+  window.removeEventListener("message", onMessage);
+  if (evt.data === "start") {
+    runTest();
+  }
+}, false);
+
+window.setTimeout(function() {
+  command("ready-to-start");
+}, 3000);
+
+</script>
+</head>
+<body>
+</body>
+</html>
--- a/dom/presentation/tests/mochitest/mochitest.ini
+++ b/dom/presentation/tests/mochitest/mochitest.ini
@@ -9,16 +9,17 @@ support-files =
   file_presentation_non_receiver.html
   file_presentation_receiver.html
   file_presentation_receiver_establish_connection_error.html
   file_presentation_receiver_inner_iframe.html
   file_presentation_1ua_wentaway.html
   test_presentation_1ua_connection_wentaway.js
   file_presentation_receiver_auxiliary_navigation.html
   test_presentation_receiver_auxiliary_navigation.js
+  file_presentation_sandboxed_presentation.html
 
 [test_presentation_dc_sender.html]
 [test_presentation_dc_receiver.html]
 skip-if = (e10s || toolkit == 'gonk' || toolkit == 'android') # Bug 1129785
 [test_presentation_dc_receiver_oop.html]
 skip-if = (e10s || toolkit == 'gonk' || toolkit == 'android') # Bug 1129785
 [test_presentation_1ua_sender_and_receiver.html]
 skip-if = (e10s || toolkit == 'gonk' || toolkit == 'android') # Bug 1129785
@@ -47,8 +48,9 @@ skip-if = (e10s || toolkit == 'gonk' || 
 [test_presentation_tcp_receiver.html]
 skip-if = (e10s || toolkit == 'gonk' || toolkit == 'android') # Bug 1129785
 [test_presentation_tcp_receiver_oop.html]
 skip-if = (e10s || toolkit == 'gonk' || toolkit == 'android') # Bug 1129785
 [test_presentation_receiver_auxiliary_navigation_inproc.html]
 skip-if = (e10s || toolkit == 'gonk')
 [test_presentation_receiver_auxiliary_navigation_oop.html]
 skip-if = (e10s || toolkit == 'gonk')
+[test_presentation_sandboxed_presentation.html]
new file mode 100644
--- /dev/null
+++ b/dom/presentation/tests/mochitest/test_presentation_sandboxed_presentation.html
@@ -0,0 +1,75 @@
+<!DOCTYPE HTML>
+<html>
+<!-- Any copyright is dedicated to the Public Domain.
+   - http://creativecommons.org/publicdomain/zero/1.0/ -->
+<head>
+  <meta charset="utf-8">
+  <title>Test default request for B2G Presentation API at sender side</title>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1268758">Test allow-presentation sandboxing flag</a>
+<iframe sandbox="allow-popups allow-scripts allow-same-origin" id="iframe" src="file_presentation_sandboxed_presentation.html"></iframe>
+<script type="application/javascript;version=1.8">
+
+"use strict";
+
+var iframe = document.getElementById("iframe");
+var readyToStart = false;
+var testSetuped = false;
+function setup() {
+  return new Promise(function(aResolve, aReject) {
+    addEventListener("message", function listener(event) {
+      var message = event.data;
+      if (/^OK /.exec(message)) {
+        ok(true, message.replace(/^OK /, ""));
+      } else if (/^KO /.exec(message)) {
+        ok(false, message.replace(/^KO /, ""));
+      } else if (/^INFO /.exec(message)) {
+        info(message.replace(/^INFO /, ""));
+      } else if (/^COMMAND /.exec(message)) {
+        var command = JSON.parse(message.replace(/^COMMAND /, ""));
+        if (command === "ready-to-start") {
+          readyToStart = true;
+          startTest();
+        }
+      } else if (/^DONE$/.exec(message)) {
+        window.removeEventListener('message', listener);
+        SimpleTest.finish();
+      }
+    }, false);
+
+    testSetuped = true;
+    aResolve();
+  });
+}
+
+iframe.onload = startTest();
+
+function startTest() {
+  if (!(testSetuped && readyToStart)) {
+    return;
+  }
+  iframe.contentWindow.postMessage("start", "*");
+}
+
+function runTests() {
+  ok(navigator.presentation, "navigator.presentation should be available.");
+  setup().then(startTest);
+}
+
+SimpleTest.waitForExplicitFinish();
+SpecialPowers.pushPermissions([
+  {type: "presentation-device-manage", allow: false, context: document},
+  {type: "presentation", allow: true, context: document},
+  {type: "presentation", allow: true, context: iframe.contentDocument},
+], function() {
+  SpecialPowers.pushPrefEnv({ "set": [["dom.presentation.enabled", true],
+                                      ["dom.presentation.session_transport.data_channel.enable", false]]},
+                            runTests);
+});
+
+</script>
+</body>
+</html>
--- a/dom/security/test/general/test_contentpolicytype_targeted_link_iframe.html
+++ b/dom/security/test/general/test_contentpolicytype_targeted_link_iframe.html
@@ -59,21 +59,21 @@ var policy = {
                        context, mimeTypeGuess, extra) {
 
     // make sure we get the right amount of content policy calls
     // e.g. about:blank also gets chrcked by content policies
     if (contentLocation.asciiSpec === EXPECTED_URL) {
       is(contentType, EXPECTED_CONTENT_TYPE,
          "content policy type should TYPESUBDOCUMENT");
       testCounter++;
-    }
-
-    if (testCounter === EXPECTED_RESULTS) {
-      categoryManager.deleteCategoryEntry("content-policy", POLICYNAME, false);
-      SimpleTest.finish();
+      
+      if (testCounter === EXPECTED_RESULTS) {
+        categoryManager.deleteCategoryEntry("content-policy", POLICYNAME, false);
+        SimpleTest.finish();
+      }
     }
     return Ci.nsIContentPolicy.ACCEPT;
   },
 
   shouldProcess: function(contentType, contentLocation, requestOrigin,
                           context, mimeTypeGuess, extra) {
     return Ci.nsIContentPolicy.ACCEPT;
   }
--- a/dom/webidl/FormData.webidl
+++ b/dom/webidl/FormData.webidl
@@ -2,17 +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/.
  *
  * The origin of this IDL file is
  * http://xhr.spec.whatwg.org
  */
 
-typedef (Blob or USVString) FormDataEntryValue;
+typedef (Blob or Directory or USVString) FormDataEntryValue;
 
 [Constructor(optional HTMLFormElement form),
  Exposed=(Window,Worker)]
 interface FormData {
   [Throws]
   void append(USVString name, Blob value, optional USVString filename);
   [Throws]
   void append(USVString name, USVString value);
--- a/editor/libeditor/HTMLAnonymousNodeEditor.cpp
+++ b/editor/libeditor/HTMLAnonymousNodeEditor.cpp
@@ -24,17 +24,17 @@
 #include "nsIDOMHTMLElement.h"
 #include "nsIDOMNode.h"
 #include "nsIDOMWindow.h"
 #include "nsIDocument.h"
 #include "nsIDocumentObserver.h"
 #include "nsIHTMLAbsPosEditor.h"
 #include "nsIHTMLInlineTableEditor.h"
 #include "nsIHTMLObjectResizer.h"
-#include "nsIMutationObserver.h"
+#include "nsStubMutationObserver.h"
 #include "nsINode.h"
 #include "nsIPresShell.h"
 #include "nsISupportsImpl.h"
 #include "nsISupportsUtils.h"
 #include "nsLiteralString.h"
 #include "nsPresContext.h"
 #include "nsReadableUtils.h"
 #include "nsString.h"
@@ -87,46 +87,77 @@ static int32_t GetCSSFloatValue(nsIDOMCS
         f = 5;
       break;
     }
   }
 
   return (int32_t) f;
 }
 
-class ElementDeletionObserver final : public nsIMutationObserver
+class ElementDeletionObserver final : public nsStubMutationObserver
 {
 public:
-  ElementDeletionObserver(nsINode* aNativeAnonNode, nsINode* aObservedNode)
+  ElementDeletionObserver(nsIContent* aNativeAnonNode,
+                          nsIContent* aObservedNode)
     : mNativeAnonNode(aNativeAnonNode)
     , mObservedNode(aObservedNode)
   {}
 
   NS_DECL_ISUPPORTS
-  NS_DECL_NSIMUTATIONOBSERVER
+  NS_DECL_NSIMUTATIONOBSERVER_PARENTCHAINCHANGED
+  NS_DECL_NSIMUTATIONOBSERVER_NODEWILLBEDESTROYED
 
 protected:
   ~ElementDeletionObserver() {}
-  nsINode* mNativeAnonNode;
-  nsINode* mObservedNode;
+  nsIContent* mNativeAnonNode;
+  nsIContent* mObservedNode;
 };
 
 NS_IMPL_ISUPPORTS(ElementDeletionObserver, nsIMutationObserver)
-NS_IMPL_NSIMUTATIONOBSERVER_CONTENT(ElementDeletionObserver)
+
+void
+ElementDeletionObserver::ParentChainChanged(nsIContent* aContent)
+{
+  // If the native anonymous content has been unbound already in
+  // DeleteRefToAnonymousNode, mNativeAnonNode's parentNode is null.
+  if (aContent == mObservedNode && mNativeAnonNode &&
+      mNativeAnonNode->GetParentNode() == aContent) {
+    // If the observed node has been moved to another document, there isn't much
+    // we can do easily. But at least be safe and unbind the native anonymous
+    // content and stop observing changes.
+    if (mNativeAnonNode->OwnerDoc() != mObservedNode->OwnerDoc()) {
+      mObservedNode->RemoveMutationObserver(this);
+      mObservedNode = nullptr;
+      mNativeAnonNode->RemoveMutationObserver(this);
+      mNativeAnonNode->UnbindFromTree();
+      mNativeAnonNode = nullptr;
+      NS_RELEASE_THIS();
+      return;
+    }
+
+    // We're staying in the same document, just rebind the native anonymous
+    // node so that the subtree root points to the right object etc.
+    mNativeAnonNode->UnbindFromTree();
+    mNativeAnonNode->BindToTree(mObservedNode->GetUncomposedDoc(), mObservedNode,
+                                mObservedNode, true);
+  }
+}
 
 void
 ElementDeletionObserver::NodeWillBeDestroyed(const nsINode* aNode)
 {
   NS_ASSERTION(aNode == mNativeAnonNode || aNode == mObservedNode,
                "Wrong aNode!");
   if (aNode == mNativeAnonNode) {
     mObservedNode->RemoveMutationObserver(this);
+    mObservedNode = nullptr;
   } else {
     mNativeAnonNode->RemoveMutationObserver(this);
-    static_cast<nsIContent*>(mNativeAnonNode)->UnbindFromTree();
+    mNativeAnonNode->UnbindFromTree();
+    mNativeAnonNode = nullptr;
   }
 
   NS_RELEASE_THIS();
 }
 
 // Returns in *aReturn an anonymous nsDOMElement of type aTag,
 // child of aParentNode. If aIsCreatedHidden is true, the class
 // "hidden" is added to the created element. If aAnonClass is not
@@ -237,30 +268,30 @@ HTMLEditor::DeleteRefToAnonymousNode(nsI
 
   if (aElement) {
     nsCOMPtr<nsIContent> content = do_QueryInterface(aElement);
     if (content) {
       nsAutoScriptBlocker scriptBlocker;
       // Need to check whether aShell has been destroyed (but not yet deleted).
       // In that case presContext->GetPresShell() returns nullptr.
       // See bug 338129.
-      if (aShell && aShell->GetPresContext() &&
+      if (content->IsInComposedDoc() && aShell && aShell->GetPresContext() &&
           aShell->GetPresContext()->GetPresShell() == aShell) {
         nsCOMPtr<nsIDocumentObserver> docObserver = do_QueryInterface(aShell);
         if (docObserver) {
           // Call BeginUpdate() so that the nsCSSFrameConstructor/PresShell
           // knows we're messing with the frame tree.
           nsCOMPtr<nsIDocument> document = GetDocument();
           if (document)
             docObserver->BeginUpdate(document, UPDATE_CONTENT_MODEL);
 
           // XXX This is wrong (bug 439258).  Once it's fixed, the NS_WARNING
           // in RestyleManager::RestyleForRemove should be changed back
           // to an assertion.
-          docObserver->ContentRemoved(content->GetUncomposedDoc(),
+          docObserver->ContentRemoved(content->GetComposedDoc(),
                                       aParentContent, content, -1,
                                       content->GetPreviousSibling());
           if (document)
             docObserver->EndUpdate(document, UPDATE_CONTENT_MODEL);
         }
       }
       content->UnbindFromTree();
     }
--- a/extensions/cookie/nsPermissionManager.cpp
+++ b/extensions/cookie/nsPermissionManager.cpp
@@ -2240,16 +2240,46 @@ NS_IMETHODIMP nsPermissionManager::GetEn
                          permEntry.mExpireType,
                          permEntry.mExpireTime));
     }
   }
 
   return NS_NewArrayEnumerator(aEnum, array);
 }
 
+NS_IMETHODIMP nsPermissionManager::GetAllForURI(nsIURI* aURI, nsISimpleEnumerator **aEnum)
+{
+  nsCOMArray<nsIPermission> array;
+
+  nsCOMPtr<nsIPrincipal> principal;
+  nsresult rv = GetPrincipal(aURI, getter_AddRefs(principal));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  RefPtr<PermissionKey> key = new PermissionKey(principal);
+  PermissionHashKey* entry = mPermissionTable.GetEntry(key);
+
+  if (entry) {
+    for (const auto& permEntry : entry->GetPermissions()) {
+      // Only return custom permissions
+      if (permEntry.mPermission == nsIPermissionManager::UNKNOWN_ACTION) {
+        continue;
+      }
+
+      array.AppendObject(
+        new nsPermission(principal,
+                         mTypeArray.ElementAt(permEntry.mType),
+                         permEntry.mPermission,
+                         permEntry.mExpireType,
+                         permEntry.mExpireTime));
+    }
+  }
+
+  return NS_NewArrayEnumerator(aEnum, array);
+}
+
 NS_IMETHODIMP nsPermissionManager::Observe(nsISupports *aSubject, const char *aTopic, const char16_t *someData)
 {
   ENSURE_NOT_CHILD_PROCESS;
 
   if (!nsCRT::strcmp(aTopic, "profile-before-change")) {
     // The profile is about to change,
     // or is going away because the application is shutting down.
     mIsShuttingDown = true;
new file mode 100644
--- /dev/null
+++ b/extensions/cookie/test/unit/test_permmanager_getAllForURI.js
@@ -0,0 +1,78 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+function check_enumerator(uri, permissions) {
+  let pm = Cc["@mozilla.org/permissionmanager;1"]
+           .getService(Ci.nsIPermissionManager);
+
+  let enumerator = pm.getAllForURI(uri);
+  for ([type, capability] of permissions) {
+    let perm = enumerator.getNext();
+    do_check_true(perm != null);
+    do_check_true(perm.principal.URI.equals(uri));
+    do_check_eq(perm.type, type);
+    do_check_eq(perm.capability, capability);
+    do_check_eq(perm.expireType, pm.EXPIRE_NEVER);
+  }
+  do_check_false(enumerator.hasMoreElements());
+}
+
+function run_test() {
+  let pm = Cc["@mozilla.org/permissionmanager;1"]
+           .getService(Ci.nsIPermissionManager);
+
+  let uri = NetUtil.newURI("http://example.com");
+  let sub = NetUtil.newURI("http://sub.example.com");
+
+  check_enumerator(uri, [ ]);
+
+  pm.add(uri, "test/getallforuri", pm.ALLOW_ACTION);
+  check_enumerator(uri, [
+    [ "test/getallforuri", pm.ALLOW_ACTION ]
+  ]);
+
+  // check that uris are matched exactly
+  check_enumerator(sub, [ ]);
+
+  pm.add(sub, "test/getallforuri", pm.PROMPT_ACTION);
+  pm.add(sub, "test/getallforuri2", pm.DENY_ACTION);
+
+  check_enumerator(sub, [
+    [ "test/getallforuri", pm.PROMPT_ACTION ],
+    [ "test/getallforuri2", pm.DENY_ACTION ]
+  ]);
+
+  // check that the original uri list has not changed
+  check_enumerator(uri, [
+    [ "test/getallforuri", pm.ALLOW_ACTION ]
+  ]);
+
+  // check that UNKNOWN_ACTION permissions are ignored
+  pm.add(uri, "test/getallforuri2", pm.UNKNOWN_ACTION);
+  pm.add(uri, "test/getallforuri3", pm.DENY_ACTION);
+
+  check_enumerator(uri, [
+    [ "test/getallforuri", pm.ALLOW_ACTION ],
+    [ "test/getallforuri3", pm.DENY_ACTION ]
+  ]);
+
+  // check that permission updates are reflected
+  pm.add(uri, "test/getallforuri", pm.PROMPT_ACTION);
+
+  check_enumerator(uri, [
+    [ "test/getallforuri", pm.PROMPT_ACTION ],
+    [ "test/getallforuri3", pm.DENY_ACTION ]
+  ]);
+
+  // check that permission removals are reflected
+  pm.remove(uri, "test/getallforuri");
+
+  check_enumerator(uri, [
+    [ "test/getallforuri3", pm.DENY_ACTION ]
+  ]);
+
+  pm.removeAll();
+  check_enumerator(uri, [ ]);
+  check_enumerator(sub, [ ]);
+}
+
--- a/extensions/cookie/test/unit/xpcshell.ini
+++ b/extensions/cookie/test/unit/xpcshell.ini
@@ -17,16 +17,17 @@ skip-if = true # Bug 863738
 [test_cookies_read.js]
 [test_cookies_sync_failure.js]
 [test_cookies_thirdparty.js]
 [test_cookies_thirdparty_session.js]
 [test_domain_eviction.js]
 [test_eviction.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_removesince.js]
 [test_permmanager_removeforapp.js]
 [test_permmanager_load_invalid_entries.js]
 skip-if = debug == true
 [test_permmanager_idn.js]
--- a/gfx/thebes/gfxFont.cpp
+++ b/gfx/thebes/gfxFont.cpp
@@ -1586,16 +1586,23 @@ public:
 private:
 #define GLYPH_BUFFER_SIZE (2048/sizeof(Glyph))
 
     Glyph *AppendGlyph()
     {
         return &mGlyphBuffer[mNumGlyphs++];
     }
 
+    static DrawMode
+    GetStrokeMode(DrawMode aMode)
+    {
+        return aMode & (DrawMode::GLYPH_STROKE |
+                        DrawMode::GLYPH_STROKE_UNDERNEATH);
+    }
+
     // Render the buffered glyphs to the draw target and clear the buffer.
     // This actually flushes the glyphs only if the buffer is full, or if the
     // aFinish parameter is true; otherwise it simply returns.
     void Flush(bool aFinish)
     {
         // Ensure there's enough room for a glyph to be added to the buffer
         if ((!aFinish && mNumGlyphs < GLYPH_BUFFER_SIZE) || !mNumGlyphs) {
             return;
@@ -1674,41 +1681,79 @@ private:
                                           mFontParams.renderingOptions);
             } else {
                 mRunParams.dt->FillGlyphs(mFontParams.scaledFont, buf,
                                           ColorPattern(state.color),
                                           mFontParams.drawOptions,
                                           mFontParams.renderingOptions);
             }
         }
-        if ((mRunParams.drawMode &
-             (DrawMode::GLYPH_STROKE | DrawMode::GLYPH_STROKE_UNDERNEATH)) ==
-            DrawMode::GLYPH_STROKE) {
-            state.color = gfx::Color::FromABGR(mRunParams.textStrokeColor);
-            state.strokeOptions.mLineWidth = mRunParams.textStrokeWidth;
-            FlushStroke(buf, state);
+        if (GetStrokeMode(mRunParams.drawMode) == DrawMode::GLYPH_STROKE &&
+            mRunParams.strokeOpts) {
+            Pattern *pat;
+            if (mRunParams.textStrokePattern) {
+                pat = mRunParams.textStrokePattern->GetPattern(
+                  mRunParams.dt, state.patternTransformChanged
+                                   ? &state.patternTransform
+                                   : nullptr);
+
+                if (pat) {
+                    Matrix saved;
+                    Matrix *mat = nullptr;
+                    if (mFontParams.passedInvMatrix) {
+                        // The brush matrix needs to be multiplied with the
+                        // inverted matrix as well, to move the brush into the
+                        // space of the glyphs.
+
+                        // This relies on the returned Pattern not to be reused
+                        // by others, but regenerated on GetPattern calls. This
+                        // is true!
+                        if (pat->GetType() == PatternType::LINEAR_GRADIENT) {
+                            mat = &static_cast<LinearGradientPattern*>(pat)->mMatrix;
+                        } else if (pat->GetType() == PatternType::RADIAL_GRADIENT) {
+                            mat = &static_cast<RadialGradientPattern*>(pat)->mMatrix;
+                        } else if (pat->GetType() == PatternType::SURFACE) {
+                            mat = &static_cast<SurfacePattern*>(pat)->mMatrix;
+                        }
+
+                        if (mat) {
+                            saved = *mat;
+                            *mat = (*mat) * (*mFontParams.passedInvMatrix);
+                        }
+                    }
+                    FlushStroke(buf, *pat);
+
+                    if (mat) {
+                        *mat = saved;
+                    }
+                }
+            } else {
+                FlushStroke(buf,
+                            ColorPattern(
+                              Color::FromABGR(mRunParams.textStrokeColor)));
+            }
         }
         if (mRunParams.drawMode & DrawMode::GLYPH_PATH) {
             mRunParams.context->EnsurePathBuilder();
             Matrix mat = mRunParams.dt->GetTransform();
             mFontParams.scaledFont->CopyGlyphsToBuilder(
                 buf, mRunParams.context->mPathBuilder,
                 mRunParams.dt->GetBackendType(), &mat);
         }
 
         mNumGlyphs = 0;
     }
 
-    void FlushStroke(gfx::GlyphBuffer& aBuf, gfxContext::AzureState& aState)
+    void FlushStroke(gfx::GlyphBuffer& aBuf, const Pattern& aPattern)
     {
         RefPtr<Path> path =
             mFontParams.scaledFont->GetPathForGlyphs(aBuf, mRunParams.dt);
-        mRunParams.dt->Stroke(path,
-                              ColorPattern(aState.color),
-                              aState.strokeOptions);
+        mRunParams.dt->Stroke(path, aPattern, *mRunParams.strokeOpts,
+                              (mRunParams.drawOpts) ? *mRunParams.drawOpts
+                                                    : DrawOptions());
     }
 
     Glyph        mGlyphBuffer[GLYPH_BUFFER_SIZE];
     unsigned int mNumGlyphs;
 
 #undef GLYPH_BUFFER_SIZE
 };
 
--- a/gfx/thebes/gfxFont.h
+++ b/gfx/thebes/gfxFont.h
@@ -6,16 +6,17 @@
 
 #ifndef GFX_FONT_H
 #define GFX_FONT_H
 
 #include "gfxTypes.h"
 #include "gfxFontEntry.h"
 #include "nsString.h"
 #include "gfxPoint.h"
+#include "gfxPattern.h"
 #include "nsTArray.h"
 #include "nsTHashtable.h"
 #include "nsHashKeys.h"
 #include "gfxRect.h"
 #include "nsExpirationTracker.h"
 #include "gfxPlatform.h"
 #include "nsIAtom.h"
 #include "mozilla/HashFunctions.h"
@@ -2183,18 +2184,20 @@ struct TextRunDrawParams {
     RefPtr<mozilla::gfx::DrawTarget> dt;
     gfxContext              *context;
     gfxFont::Spacing        *spacing;
     gfxTextRunDrawCallbacks *callbacks;
     gfxTextContextPaint     *runContextPaint;
     mozilla::gfx::Color      fontSmoothingBGColor;
     gfxFloat                 direction;
     double                   devPerApp;
-    float                    textStrokeWidth;
     nscolor                  textStrokeColor;
+    gfxPattern              *textStrokePattern;
+    const mozilla::gfx::StrokeOptions *strokeOpts;
+    const mozilla::gfx::DrawOptions   *drawOpts;
     DrawMode                 drawMode;
     bool                     isVerticalRun;
     bool                     isRTL;
     bool                     paintSVGGlyphs;
 };
 
 struct FontDrawParams {
     RefPtr<mozilla::gfx::ScaledFont>            scaledFont;
--- a/gfx/thebes/gfxTextRun.cpp
+++ b/gfx/thebes/gfxTextRun.cpp
@@ -624,18 +624,20 @@ gfxTextRun::Draw(Range aRange, gfxPoint 
     // Set up parameters that will be constant across all glyph runs we need
     // to draw, regardless of the font used.
     TextRunDrawParams params;
     params.context = aParams.context;
     params.devPerApp = 1.0 / double(GetAppUnitsPerDevUnit());
     params.isVerticalRun = IsVertical();
     params.isRTL = IsRightToLeft();
     params.direction = direction;
-    params.textStrokeWidth = aParams.textStrokeWidth;
+    params.strokeOpts = aParams.strokeOpts;
     params.textStrokeColor = aParams.textStrokeColor;
+    params.textStrokePattern = aParams.textStrokePattern;
+    params.drawOpts = aParams.drawOpts;
     params.drawMode = aParams.drawMode;
     params.callbacks = aParams.callbacks;
     params.runContextPaint = aParams.contextPaint;
     params.paintSVGGlyphs = !aParams.callbacks ||
         aParams.callbacks->mShouldPaintSVGGlyphs;
     params.dt = aParams.context->GetDrawTarget();
     params.fontSmoothingBGColor =
         aParams.context->GetFontSmoothingBackgroundColor();
--- a/gfx/thebes/gfxTextRun.h
+++ b/gfx/thebes/gfxTextRun.h
@@ -233,17 +233,19 @@ public:
         uint32_t    mCurrentChar;
     };
 
     struct DrawParams
     {
         gfxContext* context;
         DrawMode drawMode = DrawMode::GLYPH_FILL;
         nscolor textStrokeColor = 0;
-        float textStrokeWidth = 0.0f;
+        gfxPattern* textStrokePattern = nullptr;
+        const mozilla::gfx::StrokeOptions *strokeOpts = nullptr;
+        const mozilla::gfx::DrawOptions *drawOpts = nullptr;
         PropertyProvider* provider = nullptr;
         // If non-null, the advance width of the substring is set.
         gfxFloat* advanceWidth = nullptr;
         gfxTextContextPaint* contextPaint = nullptr;
         gfxTextRunDrawCallbacks* callbacks = nullptr;
         explicit DrawParams(gfxContext* aContext) : context(aContext) {}
     };
 
--- a/image/RasterImage.cpp
+++ b/image/RasterImage.cpp
@@ -55,20 +55,16 @@ namespace mozilla {
 using namespace gfx;
 using namespace layers;
 
 namespace image {
 
 using std::ceil;
 using std::min;
 
-// The maximum number of times any one RasterImage was decoded.  This is only
-// used for statistics.
-static int32_t sMaxDecodeCount = 0;
-
 #ifndef DEBUG
 NS_IMPL_ISUPPORTS(RasterImage, imgIContainer, nsIProperties)
 #else
 NS_IMPL_ISUPPORTS(RasterImage, imgIContainer, nsIProperties,
                   imgIContainerDebug)
 #endif
 
 //******************************************************************************
@@ -107,23 +103,16 @@ RasterImage::~RasterImage()
     mSourceBuffer->Complete(NS_ERROR_ABORT);
   }
 
   // Release all frames from the surface cache.
   SurfaceCache::RemoveImage(ImageKey(this));
 
   // Record Telemetry.
   Telemetry::Accumulate(Telemetry::IMAGE_DECODE_COUNT, mDecodeCount);
-
-  if (mDecodeCount > sMaxDecodeCount) {
-    sMaxDecodeCount = mDecodeCount;
-    // Clear out any previously collected data first.
-    Telemetry::ClearHistogram(Telemetry::IMAGE_MAX_DECODE_COUNT);
-    Telemetry::Accumulate(Telemetry::IMAGE_MAX_DECODE_COUNT, sMaxDecodeCount);
-  }
 }
 
 nsresult
 RasterImage::Init(const char* aMimeType,
                   uint32_t aFlags)
 {
   // We don't support re-initialization
   if (mInitialized) {
--- a/js/src/asmjs/WasmBaselineCompile.cpp
+++ b/js/src/asmjs/WasmBaselineCompile.cpp
@@ -24,23 +24,22 @@
  *
  * Unimplemented functionality:
  *
  *  - This is not actually a baseline compiler, as it performs no
  *    profiling and does not trigger ion compilation and function
  *    replacement (duh)
  *  - int64 load and store
  *  - SIMD
- *  - Atomics
+ *  - Atomics (very simple now, we have range checking)
  *  - current_memory, grow_memory
  *  - non-signaling interrupts
  *  - non-signaling bounds checks
  *  - profiler support (devtools)
  *  - Platform support:
- *      x86
  *      ARM-32
  *      ARM-64
  *
  * There are lots of machine dependencies here but they are pretty
  * well isolated to a segment of the compiler.  Many dependencies
  * will eventually be factored into the MacroAssembler layer and shared
  * with other code generators.
  *
@@ -120,16 +119,17 @@
 using mozilla::DebugOnly;
 using mozilla::FloatingPoint;
 using mozilla::SpecificNaN;
 
 namespace js {
 namespace wasm {
 
 using namespace js::jit;
+using JS::GenericNaN;
 
 struct BaseCompilePolicy : ExprIterPolicy
 {
     static const bool Output = true;
 
     // The baseline compiler tracks values on a stack of its own -- it
     // needs to scan that stack for spilling -- and thus has no need
     // for the values maintained by the iterator.
@@ -299,20 +299,49 @@ class BaseCompiler
     struct AnyReg
     {
         AnyReg() { tag = NONE; }
         explicit AnyReg(RegI32 r) { tag = I32; i32_ = r; }
         explicit AnyReg(RegI64 r) { tag = I64; i64_ = r; }
         explicit AnyReg(RegF32 r) { tag = F32; f32_ = r; }
         explicit AnyReg(RegF64 r) { tag = F64; f64_ = r; }
 
-        RegI32 i32() { MOZ_ASSERT(tag == I32); return i32_; }
-        RegI64 i64() { MOZ_ASSERT(tag == I64); return i64_; }
-        RegF32 f32() { MOZ_ASSERT(tag == F32); return f32_; }
-        RegF64 f64() { MOZ_ASSERT(tag == F64); return f64_; }
+        RegI32 i32() {
+            MOZ_ASSERT(tag == I32);
+            return i32_;
+        }
+        RegI64 i64() {
+            MOZ_ASSERT(tag == I64);
+            return i64_;
+        }
+        RegF32 f32() {
+            MOZ_ASSERT(tag == F32);
+            return f32_;
+        }
+        RegF64 f64() {
+            MOZ_ASSERT(tag == F64);
+            return f64_;
+        }
+        AnyRegister any() {
+            switch (tag) {
+              case F32: return AnyRegister(f32_.reg);
+              case F64: return AnyRegister(f64_.reg);
+              case I32: return AnyRegister(i32_.reg);
+              case I64:
+#ifdef JS_PUNBOX64
+                return AnyRegister(i64_.reg.reg);
+#else
+                MOZ_CRASH("WasmBaseline platform hook: AnyReg::any()");
+#endif
+              case NONE:
+                MOZ_CRASH("AnyReg::any() on NONE");
+            }
+            // Work around GCC 5 analysis/warning bug.
+            MOZ_CRASH("AnyReg::any(): impossible case");
+        }
 
         union {
             RegI32 i32_;
             RegI64 i64_;
             RegF32 f32_;
             RegF64 f64_;
         };
         enum { NONE, I32, I64, F32, F64 } tag;
@@ -452,16 +481,20 @@ class BaseCompiler
 #endif
 
 #if defined(JS_CODEGEN_X64) || defined(JS_CODEGEN_X86)
     RegI32 specific_eax;
     RegI32 specific_ecx;
     RegI32 specific_edx;
 #endif
 
+#if defined(JS_CODEGEN_X86)
+    AllocatableGeneralRegisterSet singleByteRegs_;
+#endif
+
     // The join registers are used to carry values out of blocks.
     // JoinRegI32 and joinRegI64 must overlap: emitBrIf and
     // emitBrTable assume that.
 
     RegI32 joinRegI32;
     RegI64 joinRegI64;
     RegF32 joinRegF32;
     RegF64 joinRegF64;
@@ -1678,16 +1711,19 @@ class BaseCompiler
     }
 
     Control& controlItem(uint32_t relativeDepth) {
         return ctl_[ctl_.length() - 1 - relativeDepth];
     }
 
     MOZ_MUST_USE
     PooledLabel* newLabel() {
+        // TODO / INVESTIGATE: allocate() is fallible, but we can
+        // probably rely on an infallible allocator here.  That would
+        // simplify code later.
         PooledLabel* candidate = labelPool_.allocate();
         if (!candidate)
             return nullptr;
         return new (candidate) PooledLabel(this);
     }
 
     void freeLabel(PooledLabel* label) {
         label->~PooledLabel();
@@ -1855,17 +1891,17 @@ class BaseCompiler
 #elif defined(JS_CODEGEN_X86)
             // Nothing
 #else
             MOZ_CRASH("BaseCompiler platform hook: beginCall");
 #endif
         }
 
         call.frameAlignAdjustment_ = ComputeByteAlignment(masm.framePushed() + sizeof(AsmJSFrame),
-                                                          ABIStackAlignment);
+                                                          JitStackAlignment);
     }
 
     void endCall(FunctionCall& call)
     {
         if (call.machineStateAreaSize_ || call.frameAlignAdjustment_) {
             int size = call.calleePopsArgs_ ? 0 : call.stackArgAreaSize_;
             if (call.callSavesMachineState_) {
 #if defined(JS_CODEGEN_X64)
@@ -2022,22 +2058,26 @@ class BaseCompiler
         }
 
 #if defined(JS_CODEGEN_X64)
         // CodeGeneratorX64::visitAsmJSLoadFuncPtr()
         {
             ScratchI32 scratch(*this);
             CodeOffset label = masm.loadRipRelativeInt64(scratch);
             masm.append(AsmJSGlobalAccess(label, globalDataOffset));
-            masm.loadPtr(Operand(scratch, ptrReg, TimesEight, 0), ptrReg);
+            masm.loadPtr(Operand(scratch, ptrReg, ScalePointer, 0), ptrReg);
         }
 #elif defined(JS_CODEGEN_X86)
         // CodeGeneratorX86::visitAsmJSLoadFuncPtr()
-        CodeOffset label = masm.movlWithPatch(PatchedAbsoluteAddress(), ptrReg, TimesFour, ptrReg);
-        masm.append(AsmJSGlobalAccess(label, globalDataOffset));
+        {
+            ScratchI32 scratch(*this);
+            CodeOffset label = masm.movlWithPatch(PatchedAbsoluteAddress(), scratch);
+            masm.append(AsmJSGlobalAccess(label, globalDataOffset));
+            masm.loadPtr(Operand(scratch, ptrReg, ScalePointer), ptrReg);
+        }
 #else
         MOZ_CRASH("BaseCompiler platform hook: funcPtrCall");
 #endif
 
         callDynamic(ptrReg, call);
     }
 
     // Precondition: sync()
@@ -2614,167 +2654,343 @@ class BaseCompiler
         masm.breakpoint();
 #endif
     }
 
     //////////////////////////////////////////////////////////////////////
     //
     // Global variable access.
 
-    // CodeGeneratorX64::visitAsmJSLoadGlobalVar()
+    // CodeGenerator{X86,X64}::visitAsmJSLoadGlobalVar()
 
     void loadGlobalVarI32(unsigned globalDataOffset, RegI32 r)
     {
-#ifdef JS_CODEGEN_X64
+#if defined(JS_CODEGEN_X64)
         CodeOffset label = masm.loadRipRelativeInt32(r.reg);
         masm.append(AsmJSGlobalAccess(label, globalDataOffset));
+#elif defined(JS_CODEGEN_X86)
+        CodeOffset label = masm.movlWithPatch(PatchedAbsoluteAddress(), r.reg);
+        masm.append(AsmJSGlobalAccess(label, globalDataOffset));
 #else
         MOZ_CRASH("BaseCompiler platform hook: loadGlobalVarI32");
 #endif
     }
 
     void loadGlobalVarI64(unsigned globalDataOffset, RegI64 r)
     {
-#ifdef JS_CODEGEN_X64
+#if defined(JS_CODEGEN_X64)
         CodeOffset label = masm.loadRipRelativeInt64(r.reg.reg);
         masm.append(AsmJSGlobalAccess(label, globalDataOffset));
 #else
         MOZ_CRASH("BaseCompiler platform hook: loadGlobalVarI64");
 #endif
     }
 
     void loadGlobalVarF32(unsigned globalDataOffset, RegF32 r)
     {
-#ifdef JS_CODEGEN_X64
+#if defined(JS_CODEGEN_X64)
         CodeOffset label = masm.loadRipRelativeFloat32(r.reg);
         masm.append(AsmJSGlobalAccess(label, globalDataOffset));
+#elif defined(JS_CODEGEN_X86)
+        CodeOffset label = masm.vmovssWithPatch(PatchedAbsoluteAddress(), r.reg);
+        masm.append(AsmJSGlobalAccess(label, globalDataOffset));
 #else
         MOZ_CRASH("BaseCompiler platform hook: loadGlobalVarF32");
 #endif
     }
 
     void loadGlobalVarF64(unsigned globalDataOffset, RegF64 r)
     {
-#ifdef JS_CODEGEN_X64
+#if defined(JS_CODEGEN_X64)
         CodeOffset label = masm.loadRipRelativeDouble(r.reg);
         masm.append(AsmJSGlobalAccess(label, globalDataOffset));
+#elif defined(JS_CODEGEN_X86)
+        CodeOffset label = masm.vmovsdWithPatch(PatchedAbsoluteAddress(), r.reg);
+        masm.append(AsmJSGlobalAccess(label, globalDataOffset));
 #else
         MOZ_CRASH("BaseCompiler platform hook: loadGlobalVarF32");
 #endif
     }
 
     // CodeGeneratorX64::visitAsmJSStoreGlobalVar()
 
     void storeGlobalVarI32(unsigned globalDataOffset, RegI32 r)
     {
-#ifdef JS_CODEGEN_X64
+#if defined(JS_CODEGEN_X64)
         CodeOffset label = masm.storeRipRelativeInt32(r.reg);
         masm.append(AsmJSGlobalAccess(label, globalDataOffset));
+#elif defined(JS_CODEGEN_X86)
+        CodeOffset label = masm.movlWithPatch(r.reg, PatchedAbsoluteAddress());
+        masm.append(AsmJSGlobalAccess(label, globalDataOffset));
 #else
         MOZ_CRASH("BaseCompiler platform hook: storeGlobalVarI32");
 #endif
     }
 
     void storeGlobalVarI64(unsigned globalDataOffset, RegI64 r)
     {
-#ifdef JS_CODEGEN_X64
+#if defined(JS_CODEGEN_X64)
         CodeOffset label = masm.storeRipRelativeInt64(r.reg.reg);
         masm.append(AsmJSGlobalAccess(label, globalDataOffset));
 #else
         MOZ_CRASH("BaseCompiler platform hook: storeGlobalVarI64");
 #endif
     }
 
     void storeGlobalVarF32(unsigned globalDataOffset, RegF32 r)
     {
-#ifdef JS_CODEGEN_X64
+#if defined(JS_CODEGEN_X64)
         CodeOffset label = masm.storeRipRelativeFloat32(r.reg);
         masm.append(AsmJSGlobalAccess(label, globalDataOffset));
+#elif defined(JS_CODEGEN_X86)
+        CodeOffset label = masm.vmovssWithPatch(r.reg, PatchedAbsoluteAddress());
+        masm.append(AsmJSGlobalAccess(label, globalDataOffset));
 #else
         MOZ_CRASH("BaseCompiler platform hook: storeGlobalVarF32");
 #endif
     }
 
     void storeGlobalVarF64(unsigned globalDataOffset, RegF64 r)
     {
-#ifdef JS_CODEGEN_X64
+#if defined(JS_CODEGEN_X64)
         CodeOffset label = masm.storeRipRelativeDouble(r.reg);
         masm.append(AsmJSGlobalAccess(label, globalDataOffset));
+#elif defined(JS_CODEGEN_X86)
+        CodeOffset label = masm.vmovsdWithPatch(r.reg, PatchedAbsoluteAddress());
+        masm.append(AsmJSGlobalAccess(label, globalDataOffset));
 #else
         MOZ_CRASH("BaseCompiler platform hook: storeGlobalVarF64");
 #endif
     }
 
     //////////////////////////////////////////////////////////////////////
     //
     // Heap access.
 
-#if defined(JS_CODEGEN_X64)
-    // Copied from CodeGenerator-x64.cpp
-    // TODO / CLEANUP - share with the code generator.
-    MemoryAccess
-    WasmMemoryAccess(uint32_t before)
-    {
-        if (isCompilingAsmJS())
-            return MemoryAccess(before, MemoryAccess::CarryOn, MemoryAccess::WrapOffset);
-        return MemoryAccess(before, MemoryAccess::Throw, MemoryAccess::DontWrapOffset);
-    }
-#endif
-
-    void memoryBarrier(MemoryBarrierBits barrier) {
-#if defined(JS_CODEGEN_X86) || defined(JS_CODEGEN_X64)
-        if (barrier & MembarStoreLoad)
-            masm.storeLoadFence();
-#else
-        MOZ_CRASH("BaseCompiler platform hook: memoryBarrier");
-#endif
-    }
-
-    // Cloned from MIRGraph.cpp, merge somehow?
+    // TODO / CLEANUP - cloned from MIRGraph.cpp, should share.
 
     bool needsBoundsCheckBranch(const MWasmMemoryAccess& access) const {
         // A heap access needs a bounds-check branch if we're not relying on signal
         // handlers to catch errors, and if it's not proven to be within bounds.
         // We use signal-handlers on x64, but on x86 there isn't enough address
         // space for a guard region.  Also, on x64 the atomic loads and stores
         // can't (yet) use the signal handlers.
 
 #if defined(ASMJS_MAY_USE_SIGNAL_HANDLERS_FOR_OOB)
         if (mg_.usesSignal.forOOB && !access.isAtomicAccess())
             return false;
 #endif
 
         return access.needsBoundsCheck();
     }
 
-#if defined(JS_CODEGEN_X64) || defined(JS_CODEGEN_X86)
-    void verifyHeapAccessDisassembly(uint32_t before, uint32_t after, bool isLoad,
-                                     Scalar::Type accessType, int nelems, Operand srcAddr,
-                                     AnyReg dest)
+    bool throwOnOutOfBounds(const MWasmMemoryAccess& access) {
+        return access.isAtomicAccess() || !isCompilingAsmJS();
+    }
+
+    // For asm.js code only: If we have a non-zero offset, it's possible that
+    // |ptr| itself is out of bounds, while adding the offset computes an
+    // in-bounds address. To catch this case, we need a second branch, which we
+    // emit out of line since it's unlikely to be needed in normal programs.
+    // For this, we'll generate an OffsetBoundsCheck OOL stub.
+
+    bool needsOffsetBoundsCheck(const MWasmMemoryAccess& access) const {
+        return isCompilingAsmJS() && access.offset() != 0;
+    }
+
+#if defined(JS_CODEGEN_X86) || defined(JS_CODEGEN_X64)
+
+# if defined(JS_CODEGEN_X64)
+    // TODO / CLEANUP - copied from CodeGenerator-x64.cpp, should share.
+
+    MemoryAccess
+    WasmMemoryAccess(uint32_t before)
+    {
+        if (isCompilingAsmJS())
+            return MemoryAccess(before, MemoryAccess::CarryOn, MemoryAccess::WrapOffset);
+        return MemoryAccess(before, MemoryAccess::Throw, MemoryAccess::DontWrapOffset);
+    }
+# endif
+
+    class OffsetBoundsCheck : public OutOfLineCode
     {
-#ifdef DEBUG
-        // TODO / MISSING: this needs to be adapted from what's in the
-        // platform's CodeGenerator; that code takes an LAllocation as
-        // the last arg now.
-#endif
-    }
-#endif
-
-    void loadHeap(const MWasmMemoryAccess& access, RegI32 ptr, AnyReg dest) {
+        Label* maybeOutOfBounds;
+        Register ptrReg;
+        int32_t offset;
+
+      public:
+        OffsetBoundsCheck(Label* maybeOutOfBounds, Register ptrReg, int32_t offset)
+          : maybeOutOfBounds(maybeOutOfBounds),
+            ptrReg(ptrReg),
+            offset(offset)
+        {}
+
+        void generate(MacroAssembler& masm) {
+            // asm.js code only:
+            //
+            // The access is heap[ptr + offset]. The inline code checks that
+            // ptr < heap.length - offset. We get here when that fails. We need to check
+            // for the case where ptr + offset >= 0, in which case the access is still
+            // in bounds.
+
+            MOZ_ASSERT(offset != 0,
+                       "An access without a constant offset doesn't need a separate "
+                       "OffsetBoundsCheck");
+            masm.cmp32(ptrReg, Imm32(-uint32_t(offset)));
+            if (maybeOutOfBounds)
+                masm.j(Assembler::Below, maybeOutOfBounds);
+            else
+                masm.j(Assembler::Below, wasm::JumpTarget::OutOfBounds);
+
+# ifdef JS_CODEGEN_X64
+            // In order to get the offset to wrap properly, we must sign-extend the
+            // pointer to 32-bits. We'll zero out the sign extension immediately
+            // after the access to restore asm.js invariants.
+            masm.movslq(ptrReg, ptrReg);
+# endif
+
+            masm.jmp(rejoin());
+        }
+    };
+
+    // CodeGeneratorX86Shared::emitAsmJSBoundsCheckBranch()
+
+    MOZ_MUST_USE
+    bool emitBoundsCheckBranch(const MWasmMemoryAccess& access, RegI32 ptr, Label* maybeFail) {
+        Label* pass = nullptr;
+
+        if (needsOffsetBoundsCheck(access)) {
+            auto* oolCheck = new(alloc_) OffsetBoundsCheck(maybeFail, ptr.reg, access.offset());
+            maybeFail = oolCheck->entry();
+            pass = oolCheck->rejoin();
+            if (!addOutOfLineCode(oolCheck))
+                return false;
+        }
+
+        // The bounds check is a comparison with an immediate value. The asm.js
+        // module linking process will add the length of the heap to the immediate
+        // field, so -access->endOffset() will turn into
+        // (heapLength - access->endOffset()), allowing us to test whether the end
+        // of the access is beyond the end of the heap.
+        MOZ_ASSERT(access.endOffset() >= 1,
+                   "need to subtract 1 to use JAE, see also AssemblerX86Shared::UpdateBoundsCheck");
+
+        uint32_t cmpOffset = masm.cmp32WithPatch(ptr.reg, Imm32(1 - access.endOffset())).offset();
+        if (maybeFail)
+            masm.j(Assembler::AboveOrEqual, maybeFail);
+        else
+            masm.j(Assembler::AboveOrEqual, wasm::JumpTarget::OutOfBounds);
+
+        if (pass)
+            masm.bind(pass);
+
+        masm.append(wasm::BoundsCheck(cmpOffset));
+        return true;
+    }
+
+    class OutOfLineLoadTypedArrayOOB : public OutOfLineCode
+    {
+        Scalar::Type viewType;
+        AnyRegister dest;
+      public:
+        OutOfLineLoadTypedArrayOOB(Scalar::Type viewType, AnyRegister dest)
+          : viewType(viewType),
+            dest(dest)
+        {}
+
+        void generate(MacroAssembler& masm) {
+            switch (viewType) {
+              case Scalar::Float32x4:
+              case Scalar::Int32x4:
+              case Scalar::Int8x16:
+              case Scalar::Int16x8:
+              case Scalar::MaxTypedArrayViewType:
+                MOZ_CRASH("unexpected array type");
+              case Scalar::Float32:
+                masm.loadConstantFloat32(float(GenericNaN()), dest.fpu());
+                break;
+              case Scalar::Float64:
+                masm.loadConstantDouble(GenericNaN(), dest.fpu());
+                break;
+              case Scalar::Int8:
+              case Scalar::Uint8:
+              case Scalar::Int16:
+              case Scalar::Uint16:
+              case Scalar::Int32:
+              case Scalar::Uint32:
+              case Scalar::Uint8Clamped:
+                masm.movePtr(ImmWord(0), dest.gpr());
+                break;
+              case Scalar::Int64:
+                MOZ_CRASH("unexpected array type");
+            }
+            masm.jump(rejoin());
+        }
+    };
+
+    MOZ_MUST_USE
+    bool maybeEmitLoadBoundsCheck(const MWasmMemoryAccess& access, RegI32 ptr, AnyRegister dest,
+                                  OutOfLineCode** ool)
+    {
+        *ool = nullptr;
+        if (!needsBoundsCheckBranch(access))
+            return true;
+
+        if (throwOnOutOfBounds(access))
+            return emitBoundsCheckBranch(access, ptr, nullptr);
+
+        // TODO / MEMORY: We'll allocate *a lot* of these OOL objects,
+        // thus risking OOM on a platform that is already
+        // memory-constrained.  We could opt to allocate this path
+        // in-line instead.
+        *ool = new (alloc_) OutOfLineLoadTypedArrayOOB(access.accessType(), dest);
+        if (!addOutOfLineCode(*ool))
+            return false;
+
+        return emitBoundsCheckBranch(access, ptr, (*ool)->entry());
+    }
+
+    MOZ_MUST_USE
+    bool maybeEmitStoreBoundsCheck(const MWasmMemoryAccess& access, RegI32 ptr, Label** rejoin) {
+        *rejoin = nullptr;
+        if (!needsBoundsCheckBranch(access))
+            return true;
+
+        if (throwOnOutOfBounds(access))
+            return emitBoundsCheckBranch(access, ptr, nullptr);
+
+        *rejoin = newLabel();
+        if (!*rejoin)
+            return false;
+
+        return emitBoundsCheckBranch(access, ptr, *rejoin);
+    }
+
+    void cleanupAfterBoundsCheck(const MWasmMemoryAccess& access, RegI32 ptr) {
+# ifdef JS_CODEGEN_X64
+        if (needsOffsetBoundsCheck(access)) {
+            // Zero out the high 32 bits, in case the OffsetBoundsCheck code had to
+            // sign-extend (movslq) the pointer value to get wraparound to work.
+            masm.movl(ptr.reg, ptr.reg);
+        }
+# endif
+    }
+
+    MOZ_MUST_USE
+    bool loadHeap(const MWasmMemoryAccess& access, RegI32 ptr, AnyReg dest) {
         if (access.offset() > INT32_MAX) {
             masm.jump(wasm::JumpTarget::OutOfBounds);
-            return;
+            return true;
         }
 
-#if defined(JS_CODEGEN_X64)
-        // CodeGeneratorX64::visitAsmJSLoadHeap()/visitWasmLoad()/visitWasmLoadI64()
-
-        if (needsBoundsCheckBranch(access))
-            MOZ_CRASH("BaseCompiler platform hook: bounds checking");
-
+        OutOfLineCode* ool = nullptr;
+        if (!maybeEmitLoadBoundsCheck(access, ptr, dest.any(), &ool))
+            return false;
+
+# if defined(JS_CODEGEN_X64)
         Operand srcAddr(HeapReg, ptr.reg, TimesOne, access.offset());
 
         uint32_t before = masm.size();
         if (dest.tag == AnyReg::I64) {
             Register out = dest.i64().reg.reg;
             switch (access.accessType()) {
               case Scalar::Int8:      masm.movsbq(srcAddr, out); break;
               case Scalar::Uint8:     masm.movzbq(srcAddr, out); break;
@@ -2796,32 +3012,74 @@ class BaseCompiler
               case Scalar::Int32:
               case Scalar::Uint32:    masm.movl(srcAddr, dest.i32().reg); break;
               case Scalar::Float32:   masm.loadFloat32(srcAddr, dest.f32().reg); break;
               case Scalar::Float64:   masm.loadDouble(srcAddr, dest.f64().reg); break;
               default:
                 MOZ_CRASH("Compiler bug: Unexpected array type");
             }
         }
-        uint32_t after = masm.size();
 
         masm.append(WasmMemoryAccess(before));
-        verifyHeapAccessDisassembly(before, after, IsLoad(true), access.accessType(), 0, srcAddr, dest);
-#else
-        MOZ_CRASH("BaseCompiler platform hook: loadHeap");
-#endif
-    }
-
-    void storeHeap(const MWasmMemoryAccess& access, RegI32 ptr, AnyReg src) {
-#if defined(JS_CODEGEN_X64)
-        // CodeGeneratorX64::visitAsmJSStoreHeap()
-
-        if (needsBoundsCheckBranch(access))
-            MOZ_CRASH("BaseCompiler platform hook: bounds checking");
-
+        // TODO: call verifyHeapAccessDisassembly somehow
+# elif defined(JS_CODEGEN_X86)
+        Operand srcAddr(ptr.reg, access.offset());
+
+        if (dest.tag == AnyReg::I64)
+            MOZ_CRASH("Not implemented: I64 support");
+
+        bool mustMove = access.byteSize() == 1 && !singleByteRegs_.has(dest.i32().reg);
+        switch (access.accessType()) {
+          case Scalar::Int8:
+          case Scalar::Uint8: {
+            Register rd = mustMove ? ScratchRegX86 : dest.i32().reg;
+            if (access.accessType() == Scalar::Int8)
+                masm.movsblWithPatch(srcAddr, rd);
+            else
+                masm.movzblWithPatch(srcAddr, rd);
+            break;
+          }
+          case Scalar::Int16:     masm.movswlWithPatch(srcAddr, dest.i32().reg); break;
+          case Scalar::Uint16:    masm.movzwlWithPatch(srcAddr, dest.i32().reg); break;
+          case Scalar::Int32:
+          case Scalar::Uint32:    masm.movlWithPatch(srcAddr, dest.i32().reg); break;
+          case Scalar::Float32:   masm.vmovssWithPatch(srcAddr, dest.f32().reg); break;
+          case Scalar::Float64:   masm.vmovsdWithPatch(srcAddr, dest.f64().reg); break;
+          default:
+            MOZ_CRASH("Compiler bug: Unexpected array type");
+        }
+        uint32_t after = masm.size();
+        if (mustMove)
+            masm.mov(ScratchRegX86, dest.i32().reg);
+
+        masm.append(wasm::MemoryAccess(after));
+        // TODO: call verifyHeapAccessDisassembly somehow
+# else
+        MOZ_CRASH("Compiler bug: Unexpected platform.");
+# endif
+
+        if (ool) {
+            cleanupAfterBoundsCheck(access, ptr);
+            masm.bind(ool->rejoin());
+        }
+        return true;
+    }
+
+    MOZ_MUST_USE
+    bool storeHeap(const MWasmMemoryAccess& access, RegI32 ptr, AnyReg src) {
+        if (access.offset() > INT32_MAX) {
+            masm.jump(wasm::JumpTarget::OutOfBounds);
+            return true;
+        }
+
+        Label* rejoin = nullptr;
+        if (!maybeEmitStoreBoundsCheck(access, ptr, &rejoin))
+            return false;
+
+# if defined(JS_CODEGEN_X64)
         Operand dstAddr(HeapReg, ptr.reg, TimesOne, access.offset());
 
         Register intReg;
         if (src.tag == AnyReg::I32)
             intReg = src.i32().reg;
         else if (src.tag == AnyReg::I64)
             intReg = src.i64().reg.reg;
 
@@ -2834,24 +3092,75 @@ class BaseCompiler
           case Scalar::Int32:
           case Scalar::Uint32:       masm.movl(intReg, dstAddr); break;
           case Scalar::Int64:        masm.movq(intReg, dstAddr); break;
           case Scalar::Float32:      masm.storeFloat32(src.f32().reg, dstAddr); break;
           case Scalar::Float64:      masm.storeDouble(src.f64().reg, dstAddr); break;
           default:
             MOZ_CRASH("Compiler bug: Unexpected array type");
         }
-        uint32_t after = masm.size();
 
         masm.append(WasmMemoryAccess(before));
-        verifyHeapAccessDisassembly(before, after, IsLoad(false), access.accessType(), 0, dstAddr, src);
+        // TODO: call verifyHeapAccessDisassembly somehow
+# elif defined(JS_CODEGEN_X86)
+        Operand dstAddr(ptr.reg, access.offset());
+
+        if (src.tag == AnyReg::I64)
+            MOZ_CRASH("Not implemented: I64 support");
+
+        bool didMove = false;
+        if (access.byteSize() == 1 && !singleByteRegs_.has(src.i32().reg)) {
+            didMove = true;
+            masm.mov(src.i32().reg, ScratchRegX86);
+        }
+        switch (access.accessType()) {
+          case Scalar::Int8:
+          case Scalar::Uint8: {
+            Register rs = src.i32().reg;
+            Register rt = didMove ? ScratchRegX86 : rs;
+            masm.movbWithPatch(rt, dstAddr);
+            break;
+          }
+          case Scalar::Int16:
+          case Scalar::Uint16:       masm.movwWithPatch(src.i32().reg, dstAddr); break;
+          case Scalar::Int32:
+          case Scalar::Uint32:       masm.movlWithPatch(src.i32().reg, dstAddr); break;
+          case Scalar::Float32:      masm.vmovssWithPatch(src.f32().reg, dstAddr); break;
+          case Scalar::Float64:      masm.vmovsdWithPatch(src.f64().reg, dstAddr); break;
+          default:
+              MOZ_CRASH("Compiler bug: Unexpected array type");
+        }
+        uint32_t after = masm.size();
+
+        masm.append(wasm::MemoryAccess(after));
+        // TODO: call verifyHeapAccessDisassembly somehow
+# else
+        MOZ_CRASH("Compiler bug: unexpected platform");
+# endif
+
+        if (rejoin) {
+            cleanupAfterBoundsCheck(access, ptr);
+            masm.bind(rejoin);
+        }
+        return true;
+    }
+
 #else
+
+    MOZ_MUST_USE
+    bool loadHeap(const MWasmMemoryAccess& access, RegI32 ptr, AnyReg dest) {
+        MOZ_CRASH("BaseCompiler platform hook: loadHeap");
+    }
+
+    MOZ_MUST_USE
+    bool storeHeap(const MWasmMemoryAccess& access, RegI32 ptr, AnyReg src) {
         MOZ_CRASH("BaseCompiler platform hook: storeHeap");
+    }
+
 #endif
-    }
 
     ////////////////////////////////////////////////////////////
 
     // Generally speaking, ABOVE this point there should be no value
     // stack manipulation (calls to popI32 etc).
 
     // Generally speaking, BELOW this point there should be no
     // platform dependencies.  We make an exception for x86 register
@@ -5239,40 +5548,44 @@ BaseCompiler::emitLoad(ValType type, Sca
     // TODO / OPTIMIZE: Disable bounds checking on constant accesses
     // below the minimum heap length.
 
     MWasmMemoryAccess access(viewType, addr.align, addr.offset);
 
     switch (type) {
       case ValType::I32: {
         RegI32 rp = popI32();
-        loadHeap(access, rp, AnyReg(rp));
+        if (!loadHeap(access, rp, AnyReg(rp)))
+            return false;
         pushI32(rp);
         break;
       }
       case ValType::I64: {
         RegI32 rp = popI32();
         RegI64 rv = needI64();
-        loadHeap(access, rp, AnyReg(rv));
+        if (!loadHeap(access, rp, AnyReg(rv)))
+            return false;
         pushI64(rv);
         freeI32(rp);
         break;
       }
       case ValType::F32: {
         RegI32 rp = popI32();
         RegF32 rv = needF32();
-        loadHeap(access, rp, AnyReg(rv));
+        if (!loadHeap(access, rp, AnyReg(rv)))
+            return false;
         pushF32(rv);
         freeI32(rp);
         break;
       }
       case ValType::F64: {
         RegI32 rp = popI32();
         RegF64 rv = needF64();
-        loadHeap(access, rp, AnyReg(rv));
+        if (!loadHeap(access, rp, AnyReg(rv)))
+            return false;
         pushF64(rv);
         freeI32(rp);
         break;
       }
       default:
         MOZ_CRASH("loadHeap type");
         break;
     }
@@ -5294,41 +5607,45 @@ BaseCompiler::emitStore(ValType resultTy
     // below the minimum heap length.
 
     MWasmMemoryAccess access(viewType, addr.align, addr.offset);
 
     switch (resultType) {
       case ValType::I32: {
         RegI32 rp, rv;
         pop2xI32(&rp, &rv);
-        storeHeap(access, rp, AnyReg(rv));
+        if (!storeHeap(access, rp, AnyReg(rv)))
+            return false;
         freeI32(rp);
         pushI32(rv);
         break;
       }
       case ValType::I64: {
         RegI64 rv = popI64();
         RegI32 rp = popI32();
-        storeHeap(access, rp, AnyReg(rv));
+        if (!storeHeap(access, rp, AnyReg(rv)))
+            return false;
         freeI32(rp);
         pushI64(rv);
         break;
       }
       case ValType::F32: {
         RegF32 rv = popF32();
         RegI32 rp = popI32();
-        storeHeap(access, rp, AnyReg(rv));
+        if (!storeHeap(access, rp, AnyReg(rv)))
+            return false;
         freeI32(rp);
         pushF32(rv);
         break;
       }
       case ValType::F64: {
         RegF64 rv = popF64();
         RegI32 rp = popI32();
-        storeHeap(access, rp, AnyReg(rv));
+        if (!storeHeap(access, rp, AnyReg(rv)))
+            return false;
         freeI32(rp);
         pushF64(rv);
         break;
       }
       default:
         MOZ_CRASH("storeHeap type");
         break;
     }
@@ -5579,27 +5896,29 @@ BaseCompiler::emitStoreWithCoercion(ValT
 
     MWasmMemoryAccess access(viewType, addr.align, addr.offset);
 
     if (resultType == ValType::F32 && viewType == Scalar::Float64) {
         RegF32 rv = popF32();
         RegF64 rw = needF64();
         masm.convertFloat32ToDouble(rv.reg, rw.reg);
         RegI32 rp = popI32();
-        storeHeap(access, rp, AnyReg(rw));
+        if (!storeHeap(access, rp, AnyReg(rw)))
+            return false;
         pushF32(rv);
         freeI32(rp);
         freeF64(rw);
     }
     else if (resultType == ValType::F64 && viewType == Scalar::Float32) {
         RegF64 rv = popF64();
         RegF32 rw = needF32();
         masm.convertDoubleToFloat32(rv.reg, rw.reg);
         RegI32 rp = popI32();
-        storeHeap(access, rp, AnyReg(rw));
+        if (!storeHeap(access, rp, AnyReg(rw)))
+            return false;
         pushF64(rv);
         freeI32(rp);
         freeF32(rw);
     }
     else
         MOZ_CRASH("unexpected coerced store");
 
     return true;
@@ -6253,16 +6572,19 @@ BaseCompiler::BaseCompiler(const ModuleG
       specific_rcx(RegI64(Register64(rcx))),
       specific_rdx(RegI64(Register64(rdx))),
 #endif
 #if defined(JS_CODEGEN_X64) || defined(JS_CODEGEN_X86)
       specific_eax(RegI32(eax)),
       specific_ecx(RegI32(ecx)),
       specific_edx(RegI32(edx)),
 #endif
+#ifdef JS_CODEGEN_X86
+      singleByteRegs_(GeneralRegisterSet(Registers::SingleByteRegs)),
+#endif
       joinRegI32(RegI32(ReturnReg)),
       joinRegI64(RegI64(Register64(ReturnReg))),
       joinRegF32(RegF32(ReturnFloat32Reg)),
       joinRegF64(RegF64(ReturnDoubleReg))
 {
     // jit/RegisterAllocator.h: RegisterAllocator::RegisterAllocator()
 
 #if defined(JS_CODEGEN_X64)
@@ -6394,17 +6716,17 @@ volatileReturnGPR()
 LiveRegisterSet BaseCompiler::VolatileReturnGPR = volatileReturnGPR();
 
 } // wasm
 } // js
 
 bool
 js::wasm::BaselineCanCompile(const FunctionGenerator* fg)
 {
-#if defined(JS_CODEGEN_X64)
+#if defined(JS_CODEGEN_X64) || defined(JS_CODEGEN_X86)
     if (!fg->usesSignalsForInterrupts())
         return false;
 
     if (fg->usesAtomics())
         return false;
 
     if (fg->usesSimd())
         return false;
--- a/js/src/gc/Marking.cpp
+++ b/js/src/gc/Marking.cpp
@@ -2473,17 +2473,16 @@ IsMarkedInternal(T* thingp)
     *thingp = DispatchTyped(IsMarkedFunctor<T>(), *thingp, &rv);
     return rv;
 }
 
 bool
 js::gc::IsAboutToBeFinalizedDuringSweep(TenuredCell& tenured)
 {
     MOZ_ASSERT(!IsInsideNursery(&tenured));
-    MOZ_ASSERT(!tenured.runtimeFromAnyThread()->isHeapMinorCollecting());
     MOZ_ASSERT(tenured.zoneFromAnyThread()->isGCSweeping());
     if (tenured.arena()->allocatedDuringIncremental)
         return false;
     return !tenured.isMarked();
 }
 
 template <typename T>
 static bool
@@ -2493,21 +2492,19 @@ IsAboutToBeFinalizedInternal(T** thingp)
     T* thing = *thingp;
     JSRuntime* rt = thing->runtimeFromAnyThread();
 
     /* Permanent atoms are never finalized by non-owning runtimes. */
     if (ThingIsPermanentAtomOrWellKnownSymbol(thing) && !TlsPerThreadData.get()->associatedWith(rt))
         return false;
 
     Nursery& nursery = rt->gc.nursery;
-    MOZ_ASSERT_IF(!rt->isHeapMinorCollecting(), !IsInsideNursery(thing));
-    if (rt->isHeapMinorCollecting()) {
-        if (IsInsideNursery(thing))
-            return !nursery.getForwardedPointer(reinterpret_cast<JSObject**>(thingp));
-        return false;
+    if (IsInsideNursery(thing)) {
+        MOZ_ASSERT(rt->isHeapMinorCollecting());
+        return !nursery.getForwardedPointer(reinterpret_cast<JSObject**>(thingp));
     }
 
     Zone* zone = thing->asTenured().zoneFromAnyThread();
     if (zone->isGCSweeping()) {
         return IsAboutToBeFinalizedDuringSweep(thing->asTenured());
     } else if (zone->isGCCompacting() && IsForwarded(thing)) {
         *thingp = Forwarded(thing);
         return false;
--- a/js/src/gc/Nursery.cpp
+++ b/js/src/gc/Nursery.cpp
@@ -496,22 +496,25 @@ js::Nursery::collect(JSRuntime* rt, JS::
     TIME_START(checkHashTables);
 #ifdef JS_GC_ZEAL
     if (rt->hasZealMode(ZealMode::CheckHashTablesOnMinorGC))
         CheckHashTablesAfterMovingGC(rt);
 #endif
     TIME_END(checkHashTables);
 
     // Resize the nursery.
+    static const double GrowThreshold   = 0.05;
+    static const double ShrinkThreshold = 0.01;
     TIME_START(resize);
     double promotionRate = mover.tenuredSize / double(allocationEnd() - start());
-    if (promotionRate > 0.05)
+    if (promotionRate > GrowThreshold)
         growAllocableSpace();
-    else if (promotionRate < 0.01)
+    else if (promotionRate < ShrinkThreshold && previousPromotionRate_ < ShrinkThreshold)
         shrinkAllocableSpace();
+    previousPromotionRate_ = promotionRate;
     TIME_END(resize);
 
     // If we are promoting the nursery, or exhausted the store buffer with
     // pointers to nursery things, which will force a collection well before
     // the nursery is full, look for object groups that are getting promoted
     // excessively and try to pretenure them.
     TIME_START(pretenure);
     if (pretenureGroups && (promotionRate > 0.8 || reason == JS::gcreason::FULL_STORE_BUFFER)) {
--- a/js/src/gc/Nursery.h
+++ b/js/src/gc/Nursery.h
@@ -99,16 +99,17 @@ class Nursery
         position_(0),
         currentStart_(0),
         currentEnd_(0),
         heapStart_(0),
         heapEnd_(0),
         currentChunk_(0),
         numActiveChunks_(0),
         numNurseryChunks_(0),
+        previousPromotionRate_(0),
         profileThreshold_(0),
         enableProfiling_(false),
         freeMallocedBuffersTask(nullptr)
     {}
     ~Nursery();
 
     MOZ_MUST_USE bool init(uint32_t maxNurseryBytes);
 
@@ -253,16 +254,19 @@ class Nursery
     int currentChunk_;
 
     /* The index after the last chunk that we will allocate from. */
     int numActiveChunks_;
 
     /* Number of chunks allocated for the nursery. */
     int numNurseryChunks_;
 
+    /* Promotion rate for the previous minor collection. */
+    double previousPromotionRate_;
+
     /* Report minor collections taking more than this many us, if enabled. */
     int64_t profileThreshold_;
     bool enableProfiling_;
 
     /*
      * The set of externally malloced buffers potentially kept live by objects
      * stored in the nursery. Any external buffers that do not belong to a
      * tenured thing at the end of a minor GC must be freed.
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/basic/bug1285227.js
@@ -0,0 +1,5 @@
+if (helperThreadCount() === 0)
+    quit();
+evalInWorker(`
+    (new WeakMap).set(FakeDOMObject.prototype, this)
+`);
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/gc/bug1285186.js
@@ -0,0 +1,6 @@
+if (helperThreadCount() === 0)
+    quit();
+gczeal(10);
+newGlobal();
+offThreadCompileScript("let x = 1;");
+abortgc();
--- a/js/src/jit/none/LIR-none.h
+++ b/js/src/jit/none/LIR-none.h
@@ -82,24 +82,16 @@ class LModI : public LBinaryMath<1>
           const LDefinition&)
     {
         MOZ_CRASH();
     }
 
     const LDefinition* callTemp() { MOZ_CRASH(); }
     MMod* mir() const { MOZ_CRASH(); }
 };
-class LAsmJSLoadFuncPtr : public LInstructionHelper<1, 1, 1>
-{
-  public:
-    LAsmJSLoadFuncPtr(const LAllocation&, const LDefinition&) { MOZ_CRASH(); }
-    const MAsmJSLoadFuncPtr* mir() const { MOZ_CRASH(); }
-    const LAllocation* index() { MOZ_CRASH(); }
-    const LDefinition* temp() { MOZ_CRASH(); }
-};
 class LAsmJSUInt32ToDouble : public LInstructionHelper<1, 1, 0>
 {
   public:
     LAsmJSUInt32ToDouble(const LAllocation&) { MOZ_CRASH(); }
 };
 class LModPowTwoI : public LInstructionHelper<1, 1, 0>
 {
 
--- a/js/src/jsgc.cpp
+++ b/js/src/jsgc.cpp
@@ -6325,17 +6325,17 @@ GCRuntime::collect(bool nonincrementalBy
         gcstats::AutoPhase ap(rt->gc.stats, gcstats::PHASE_TRACE_HEAP);
         CheckHeapAfterMovingGC(rt);
     }
 #endif
 }
 
 js::AutoEnqueuePendingParseTasksAfterGC::~AutoEnqueuePendingParseTasksAfterGC()
 {
-    if (!gc_.isIncrementalGCInProgress())
+    if (!OffThreadParsingMustWaitForGC(gc_.rt))
         EnqueuePendingParseTasksAfterGC(gc_.rt);
 }
 
 SliceBudget
 GCRuntime::defaultBudget(JS::gcreason::Reason reason, int64_t millis)
 {
     if (millis == 0) {
         if (reason == JS::gcreason::ALLOC_TRIGGER)
@@ -6393,16 +6393,17 @@ GCRuntime::finishGC(JS::gcreason::Reason
 
 void
 GCRuntime::abortGC()
 {
     checkCanCallAPI();
     MOZ_ASSERT(!rt->mainThread.suppressGC);
 
     AutoStopVerifyingBarriers av(rt, false);
+    AutoEnqueuePendingParseTasksAfterGC aept(*this);
 
     gcstats::AutoGCSlice agc(stats, scanZonesBeforeGC(), invocationKind,
                              SliceBudget::unlimited(), JS::gcreason::ABORT_GC);
 
     evictNursery(JS::gcreason::ABORT_GC);
     AutoTraceSession session(rt, JS::HeapState::MajorCollecting);
 
     number++;
--- a/js/src/shell/js.cpp
+++ b/js/src/shell/js.cpp
@@ -2637,16 +2637,23 @@ DisassWithSrc(JSContext* cx, unsigned ar
         fclose(file);
     }
     args.rval().setUndefined();
     return ok;
 }
 
 #endif /* DEBUG */
 
+/* Pretend we can always preserve wrappers for dummy DOM objects. */
+static bool
+DummyPreserveWrapperCallback(JSContext* cx, JSObject* obj)
+{
+    return true;
+}
+
 static bool
 Intern(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
 
     JSString* str = JS::ToString(cx, args.get(0));
     if (!str)
         return false;
@@ -2948,16 +2955,17 @@ WorkerMain(void* arg)
     }
 
     JSContext* cx = JS_GetContext(rt);
 
     sr->isWorker = true;
     JS_SetRuntimePrivate(rt, sr.get());
     JS_SetFutexCanWait(cx);
     JS::SetWarningReporter(cx, WarningReporter);
+    js::SetPreserveWrapperCallback(cx, DummyPreserveWrapperCallback);
     JS_InitDestroyPrincipalsCallback(cx, ShellPrincipals::destroy);
     SetWorkerContextOptions(cx);
 
     if (!JS::InitSelfHostedCode(cx)) {
         JS_DestroyRuntime(rt);
         js_delete(input);
         return;
     }
@@ -7171,23 +7179,16 @@ SetOutputFile(const char* const envVar,
         outFile = js_new<RCFile>(newfp);
     else
         outFile = defaultOut;
 
     outFile->acquire();
     *outFileP = outFile;
 }
 
-/* Pretend we can always preserve wrappers for dummy DOM objects. */
-static bool
-DummyPreserveWrapperCallback(JSContext* cx, JSObject* obj)
-{
-    return true;
-}
-
 static void
 PreInit()
 {
 #ifdef XP_WIN
     const char* crash_option = getenv("XRE_NO_WINDOWS_CRASH_DIALOG");
     if (crash_option && crash_option[0] == '1') {
         // Disable the segfault dialog. We want to fail the tests immediately
         // instead of hanging automation.
--- a/layout/base/tests/mochitest.ini
+++ b/layout/base/tests/mochitest.ini
@@ -285,8 +285,11 @@ support-files = bug1226904.html
 [test_bug1278021.html]
 [test_transformed_scrolling_repaints.html]
 skip-if = buildapp == 'b2g'
 [test_transformed_scrolling_repaints_2.html]
 skip-if = buildapp == 'b2g'
 [test_transformed_scrolling_repaints_3.html]
 skip-if = buildapp == 'b2g'
 [test_frame_reconstruction_scroll_restore.html]
+[test_resize_flush.html]
+support-files =
+  resize_flush_iframe.html
new file mode 100644
--- /dev/null
+++ b/layout/base/tests/resize_flush_iframe.html
@@ -0,0 +1,17 @@
+<html>
+<head>
+  <style>
+    body {
+      background-color: red;
+    }
+
+    @media (min-width: 250px) {
+      body {
+        background-color: green;
+      }
+    }
+  </style>
+</head>
+<body>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/base/tests/test_resize_flush.html
@@ -0,0 +1,51 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1279202
+-->
+<head>
+  <title>Test for Bug 1279202</title>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body onload="run()">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1279202">Mozilla Bug 1279202</a>
+<iframe src="resize_flush_iframe.html" id="iframe" height="200" width="200" style="border:none"></iframe>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 1279202 **/
+
+SimpleTest.waitForExplicitFinish();
+
+function run() {
+
+  var iframe = document.getElementById("iframe");
+  var doc = iframe.contentDocument.documentElement;
+  var win = iframe.contentWindow;
+  var body = iframe.contentDocument.body;
+
+  // Flush any pending layout changes before we start.
+  var width = doc.clientWidth;
+
+  // Resize the iframe
+  iframe.width = '300px';
+
+  // Flush pending style changes, but not layout ones. We do this twice because the first flush
+  // does a partial flush of the resize (setting the size on the pres context) which sets the
+  // need style flush flag again. The second call makes sure mNeedStyleFlush is false.
+  var color = win.getComputedStyle(body).getPropertyValue("background-color");
+  color = win.getComputedStyle(body).getPropertyValue("background-color");
+  is(color, "rgb(0, 128, 0)", "Style flush not completed when resizing an iframe!");
+
+  // Query the size of the inner document and make sure it has had a layout flush.
+  width = doc.clientWidth;
+
+  is(width, 300, "Layout flush not completed when resizing an iframe!");
+  SimpleTest.finish();
+}
+
+</script>
+</pre>
+</body>
+</html>
--- a/layout/generic/nsTextFrame.cpp
+++ b/layout/generic/nsTextFrame.cpp
@@ -6686,22 +6686,25 @@ DrawTextRun(gfxTextRun* aTextRun,
       // Default drawMode is DrawMode::GLYPH_FILL
       aParams.context->SetColor(Color::FromABGR(aParams.textColor));
     } else {
       params.drawMode = DrawMode::GLYPH_STROKE;
     }
 
     if (NS_GET_A(aParams.textStrokeColor) != 0 &&
         aParams.textStrokeWidth != 0.0f) {
+      StrokeOptions strokeOpts;
       params.drawMode |= DrawMode::GLYPH_STROKE;
-      params.textStrokeWidth = aParams.textStrokeWidth;
       params.textStrokeColor = aParams.textStrokeColor;
-    }
-
-    aTextRun->Draw(aRange, aTextBaselinePt, params);
+      strokeOpts.mLineWidth = aParams.textStrokeWidth;
+      params.strokeOpts = &strokeOpts;
+      aTextRun->Draw(aRange, aTextBaselinePt, params);
+    } else {
+      aTextRun->Draw(aRange, aTextBaselinePt, params);
+    }
   }
 }
 
 void
 nsTextFrame::DrawTextRun(Range aRange, const gfxPoint& aTextBaselinePt,
                          const DrawTextRunParams& aParams)
 {
   MOZ_ASSERT(aParams.advanceWidth, "Must provide advanceWidth");
--- a/layout/style/ServoBindings.cpp
+++ b/layout/style/ServoBindings.cpp
@@ -13,16 +13,17 @@
 #include "nsDOMTokenList.h"
 #include "nsIDOMNode.h"
 #include "nsIDocument.h"
 #include "nsINode.h"
 #include "nsIPrincipal.h"
 #include "nsNameSpaceManager.h"
 #include "nsString.h"
 #include "nsStyleStruct.h"
+#include "nsTArray.h"
 #include "nsStyleUtil.h"
 #include "StyleStructContext.h"
 
 #include "mozilla/EventStates.h"
 #include "mozilla/dom/Element.h"
 
 uint32_t
 Gecko_ChildrenCount(RawGeckoNode* aNode)
@@ -555,16 +556,31 @@ Gecko_CreateGradient(uint8_t aShape,
 
   for (uint32_t i = 0; i < aStopCount; i++) {
     result->mStops.AppendElement(dummyStop);
   }
 
   return result;
 }
 
+void
+Gecko_EnsureTArrayCapacity(void* aArray, size_t aCapacity, size_t aElemSize) {
+  auto base = reinterpret_cast<nsTArray_base<nsTArrayInfallibleAllocator, nsTArray_CopyWithMemutils> *>(aArray);
+  base->EnsureCapacity<nsTArrayInfallibleAllocator>(aCapacity, aElemSize);
+}
+
+void Gecko_EnsureImageLayersLength(nsStyleImageLayers* aLayers, size_t aLen) {
+  aLayers->mLayers.EnsureLengthAtLeast(aLen);
+}
+
+void Gecko_InitializeImageLayer(nsStyleImageLayers::Layer* aLayer,
+                                nsStyleImageLayers::LayerType aLayerType) {
+  aLayer->Initialize(aLayerType);
+}
+
 #define STYLE_STRUCT(name, checkdata_cb)                                      \
                                                                               \
 void                                                                          \
 Gecko_Construct_nsStyle##name(nsStyle##name* ptr)                             \
 {                                                                             \
   new (ptr) nsStyle##name(StyleStructContext::ServoContext());                \
 }                                                                             \
                                                                               \
--- a/layout/style/ServoBindings.h
+++ b/layout/style/ServoBindings.h
@@ -4,16 +4,17 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef mozilla_ServoBindings_h
 #define mozilla_ServoBindings_h
 
 #include "stdint.h"
 #include "nsColor.h"
+#include "nsStyleStruct.h"
 #include "mozilla/css/SheetParsingMode.h"
 #include "nsProxyRelease.h"
 
 /*
  * API for Servo to access Gecko data structures. This file must compile as valid
  * C code in order for the binding generator to parse it.
  *
  * Functions beginning with Gecko_ are implemented in Gecko and invoked from Servo.
@@ -176,16 +177,27 @@ void Gecko_SetMozBinding(nsStyleDisplay*
                          ThreadSafePrincipalHolder* principal);
 void Gecko_CopyMozBindingFrom(nsStyleDisplay* des, const nsStyleDisplay* src);
 
 // Dirtiness tracking.
 uint32_t Gecko_GetNodeFlags(RawGeckoNode* node);
 void Gecko_SetNodeFlags(RawGeckoNode* node, uint32_t flags);
 void Gecko_UnsetNodeFlags(RawGeckoNode* node, uint32_t flags);
 
+// `array` must be an nsTArray
+// If changing this signature, please update the
+// friend function declaration in nsTArray.h
+void Gecko_EnsureTArrayCapacity(void* array, size_t capacity, size_t elem_size);
+
+
+void Gecko_EnsureImageLayersLength(nsStyleImageLayers* layers, size_t len);
+
+void Gecko_InitializeImageLayer(nsStyleImageLayers::Layer* layer,
+                                nsStyleImageLayers::LayerType layer_type);
+
 // Styleset and Stylesheet management.
 //
 // TODO: Make these return already_AddRefed and UniquePtr when the binding
 // generator is smart enough to handle them.
 RawServoStyleSheet* Servo_StylesheetFromUTF8Bytes(
     const uint8_t* bytes, uint32_t length,
     mozilla::css::SheetParsingMode parsing_mode,
     ThreadSafeURIHolder* base,
--- a/mobile/android/base/java/org/mozilla/gecko/GeckoApp.java
+++ b/mobile/android/base/java/org/mozilla/gecko/GeckoApp.java
@@ -191,16 +191,97 @@ public abstract class GeckoApp
     private View mFullScreenPluginView;
 
     private final HashMap<String, PowerManager.WakeLock> mWakeLocks = new HashMap<String, PowerManager.WakeLock>();
 
     protected boolean mLastSessionCrashed;
     protected boolean mShouldRestore;
     private boolean mSessionRestoreParsingFinished = false;
 
+    private static final class LastSessionParser extends SessionParser {
+        private JSONArray tabs;
+        private JSONObject windowObject;
+        private boolean isExternalURL;
+
+        private boolean selectNextTab;
+        private boolean tabsWereSkipped;
+        private boolean tabsWereProcessed;
+
+        public LastSessionParser(JSONArray tabs, JSONObject windowObject, boolean isExternalURL) {
+            this.tabs = tabs;
+            this.windowObject = windowObject;
+            this.isExternalURL = isExternalURL;
+        }
+
+        public boolean allTabsSkipped() {
+            return tabsWereSkipped && !tabsWereProcessed;
+        }
+
+        @Override
+        public void onTabRead(final SessionTab sessionTab) {
+            if (sessionTab.isAboutHomeWithoutHistory()) {
+                // This is a tab pointing to about:home with no history. We won't restore
+                // this tab. If we end up restoring no tabs then the browser will decide
+                // whether it needs to open about:home or a different 'homepage'. If we'd
+                // always restore about:home only tabs then we'd never open the homepage.
+                // See bug 1261008.
+
+                if (sessionTab.isSelected()) {
+                    // Unfortunately this tab is the selected tab. Let's just try to select
+                    // the first tab. If we haven't restored any tabs so far then remember
+                    // to select the next tab that gets restored.
+
+                    if (!Tabs.getInstance().selectLastTab()) {
+                        selectNextTab = true;
+                    }
+                }
+
+                // Do not restore this tab.
+                tabsWereSkipped = true;
+                return;
+            }
+
+            tabsWereProcessed = true;
+
+            JSONObject tabObject = sessionTab.getTabObject();
+
+            int flags = Tabs.LOADURL_NEW_TAB;
+            flags |= ((isExternalURL || !sessionTab.isSelected()) ? Tabs.LOADURL_DELAY_LOAD : 0);
+            flags |= (tabObject.optBoolean("desktopMode") ? Tabs.LOADURL_DESKTOP : 0);
+            flags |= (tabObject.optBoolean("isPrivate") ? Tabs.LOADURL_PRIVATE : 0);
+
+            final Tab tab = Tabs.getInstance().loadUrl(sessionTab.getUrl(), flags);
+
+            if (selectNextTab) {
+                // We did not restore the selected tab previously. Now let's select this tab.
+                Tabs.getInstance().selectTab(tab.getId());
+                selectNextTab = false;
+            }
+
+            ThreadUtils.postToUiThread(new Runnable() {
+                @Override
+                public void run() {
+                    tab.updateTitle(sessionTab.getTitle());
+                }
+            });
+
+            try {
+                tabObject.put("tabId", tab.getId());
+            } catch (JSONException e) {
+                Log.e(LOGTAG, "JSON error", e);
+            }
+            tabs.put(tabObject);
+        }
+
+        @Override
+        public void onClosedTabsRead(final JSONArray closedTabData) throws JSONException {
+            windowObject.put("closedTabs", closedTabData);
+        }
+    };
+
     protected boolean mInitialized;
     protected boolean mWindowFocusInitialized;
     private Telemetry.Timer mJavaUiStartupTimer;
     private Telemetry.Timer mGeckoReadyStartupTimer;
 
     private String mPrivateBrowsingSession;
 
     private volatile HealthRecorder mHealthRecorder;
@@ -1283,16 +1364,27 @@ public abstract class GeckoApp
                         // of the tab stubs into the JSON data (which holds the session
                         // history). This JSON data is then sent to Gecko so session
                         // history can be restored for each tab.
                         final SafeIntent intent = new SafeIntent(getIntent());
                         restoreMessage = restoreSessionTabs(invokedWithExternalURL(getIntentURI(intent)));
                     } catch (SessionRestoreException e) {
                         // If restore failed, do a normal startup
                         Log.e(LOGTAG, "An error occurred during restore", e);
+                        // If mShouldRestore was already set to false in restoreSessionTabs(),
+                        // this means that we intentionally skipped all tabs read from the
+                        // session file, so we don't have to report this exception in telemetry
+                        // and can ignore the following bit.
+                        if (mShouldRestore && getProfile().sessionFileExistsAndNotEmptyWindow()) {
+                            // If we got a SessionRestoreException even though the file exists and its
+                            // length doesn't match the known length of an intentionally empty file,
+                            // it's very likely we've encountered a damaged/corrupt session store file.
+                            Log.d(LOGTAG, "Suspecting a damaged session store file.");
+                            Telemetry.addToHistogram("FENNEC_SESSIONSTORE_DAMAGED_SESSION_FILE", 1);
+                        }
                         mShouldRestore = false;
                     }
                 }
 
                 synchronized (GeckoApp.this) {
                     mSessionRestoreParsingFinished = true;
                     GeckoApp.this.notifyAll();
                 }
@@ -1675,88 +1767,35 @@ public abstract class GeckoApp
             }
 
             // If we are doing an OOM restore, parse the session data and
             // stub the restored tabs immediately. This allows the UI to be
             // updated before Gecko has restored.
             if (mShouldRestore) {
                 final JSONArray tabs = new JSONArray();
                 final JSONObject windowObject = new JSONObject();
-                SessionParser parser = new SessionParser() {
-                    private boolean selectNextTab;
-
-                    @Override
-                    public void onTabRead(final SessionTab sessionTab) {
-                        if (sessionTab.isAboutHomeWithoutHistory()) {
-                            // This is a tab pointing to about:home with no history. We won't restore
-                            // this tab. If we end up restoring no tabs then the browser will decide
-                            // whether it needs to open about:home or a different 'homepage'. If we'd
-                            // always restore about:home only tabs then we'd never open the homepage.
-                            // See bug 1261008.
-
-                            if (sessionTab.isSelected()) {
-                                // Unfortunately this tab is the selected tab. Let's just try to select
-                                // the first tab. If we haven't restored any tabs so far then remember
-                                // to select the next tab that gets restored.
-
-                                if (!Tabs.getInstance().selectLastTab()) {
-                                    selectNextTab = true;
-                                }
-                            }
-
-                            // Do not restore this tab.
-                            return;
-                        }
-
-                        JSONObject tabObject = sessionTab.getTabObject();
-
-                        int flags = Tabs.LOADURL_NEW_TAB;
-                        flags |= ((isExternalURL || !sessionTab.isSelected()) ? Tabs.LOADURL_DELAY_LOAD : 0);
-                        flags |= (tabObject.optBoolean("desktopMode") ? Tabs.LOADURL_DESKTOP : 0);
-                        flags |= (tabObject.optBoolean("isPrivate") ? Tabs.LOADURL_PRIVATE : 0);
-
-                        final Tab tab = Tabs.getInstance().loadUrl(sessionTab.getUrl(), flags);
-
-                        if (selectNextTab) {
-                            // We did not restore the selected tab previously. Now let's select this tab.
-                            Tabs.getInstance().selectTab(tab.getId());
-                            selectNextTab = false;
-                        }
-
-                        ThreadUtils.postToUiThread(new Runnable() {
-                            @Override
-                            public void run() {
-                                tab.updateTitle(sessionTab.getTitle());
-                            }
-                        });
-
-                        try {
-                            tabObject.put("tabId", tab.getId());
-                        } catch (JSONException e) {
-                            Log.e(LOGTAG, "JSON error", e);
-                        }
-                        tabs.put(tabObject);
-                    }
-
-                    @Override
-                    public void onClosedTabsRead(final JSONArray closedTabData) throws JSONException {
-                        windowObject.put("closedTabs", closedTabData);
-                    }
-                };
+
+                LastSessionParser parser = new LastSessionParser(tabs, windowObject, isExternalURL);
 
                 if (mPrivateBrowsingSession == null) {
                     parser.parse(sessionString);
                 } else {
                     parser.parse(sessionString, mPrivateBrowsingSession);
                 }
 
                 if (tabs.length() > 0) {
                     windowObject.put("tabs", tabs);
                     sessionString = new JSONObject().put("windows", new JSONArray().put(windowObject)).toString();
                 } else {
+                    if (parser.allTabsSkipped()) {
+                        // If we intentionally skipped all tabs we've read from the session file, we
+                        // set mShouldRestore back to false at this point already, so the calling code
+                        // can infer that the exception wasn't due to a damaged session store file.
+                        mShouldRestore = false;
+                    }
                     throw new SessionRestoreException("No tabs could be read from session file");
                 }
             }
 
             JSONObject restoreData = new JSONObject();
             restoreData.put("sessionString", sessionString);
             return restoreData.toString();
         } catch (JSONException e) {
--- a/mobile/android/base/java/org/mozilla/gecko/GeckoProfile.java
+++ b/mobile/android/base/java/org/mozilla/gecko/GeckoProfile.java
@@ -71,16 +71,17 @@ public final class GeckoProfile {
     // Profile is using a custom directory outside of the Mozilla directory.
     public static final String CUSTOM_PROFILE = "";
     public static final String GUEST_PROFILE_DIR = "guest";
 
     // Session store
     private static final String SESSION_FILE = "sessionstore.js";
     private static final String SESSION_FILE_BACKUP = "sessionstore.bak";
     private static final long MAX_BACKUP_FILE_AGE = 1000 * 3600 * 24; // 24 hours
+    private static final int SESSION_STORE_EMPTY_JSON_LENGTH = 14; // length of {"windows":[]}
 
     private boolean mOldSessionDataProcessed = false;
 
     private static final ConcurrentHashMap<String, GeckoProfile> sProfileCache =
             new ConcurrentHashMap<String, GeckoProfile>(
                     /* capacity */ 4, /* load factor */ 0.75f, /* concurrency */ 2);
     private static String sDefaultProfileName;
 
@@ -649,16 +650,29 @@ public final class GeckoProfile {
             }
         } catch (IOException ioe) {
             Log.e(LOGTAG, "Unable to read session file", ioe);
         }
         return null;
     }
 
     /**
+     * Checks whether the session store file exists and that its length
+     * doesn't match the known length of a session store file containing
+     * only an empty window.
+     */
+    public boolean sessionFileExistsAndNotEmptyWindow() {
+        File sessionFile = getFile(SESSION_FILE);
+
+        return sessionFile != null &&
+               sessionFile.exists() &&
+               sessionFile.length() != SESSION_STORE_EMPTY_JSON_LENGTH;
+    }
+
+    /**
      * Ensures the parent director(y|ies) of the given filename exist by making them
      * if they don't already exist..
      *
      * @param filename The path to the file whose parents should be made directories
      * @return true if the parent directory exists, false otherwise
      */
     @WorkerThread
     protected boolean ensureParentDirs(final String filename) {
--- a/mobile/android/base/java/org/mozilla/gecko/GeckoProfileDirectories.java
+++ b/mobile/android/base/java/org/mozilla/gecko/GeckoProfileDirectories.java
@@ -133,24 +133,24 @@ public class GeckoProfileDirectories {
      *
      * @return null if there is no "Default" entry in profiles.ini, or the profile
      *         name if there is.
      * @throws NoMozillaDirectoryException
      *             if the Mozilla directory did not exist and could not be created.
      */
     static String findDefaultProfileName(final Context context) throws NoMozillaDirectoryException {
       final INIParser parser = GeckoProfileDirectories.getProfilesINI(getMozillaDirectory(context));
-
-      for (Enumeration<INISection> e = parser.getSections().elements(); e.hasMoreElements();) {
-          final INISection section = e.nextElement();
-          if (section.getIntProperty("Default") == 1) {
-              return section.getStringProperty("Name");
+      if (parser.getSections() != null) {
+          for (Enumeration<INISection> e = parser.getSections().elements(); e.hasMoreElements(); ) {
+              final INISection section = e.nextElement();
+              if (section.getIntProperty("Default") == 1) {
+                  return section.getStringProperty("Name");
+              }
           }
       }
-
       return null;
     }
 
     static Map<String, String> getDefaultProfile(final File mozillaDir) {
         return getMatchingProfiles(mozillaDir, sectionIsDefault, true);
     }
 
     static Map<String, String> getProfilesNamed(final File mozillaDir, final String name) {
@@ -186,43 +186,45 @@ public class GeckoProfileDirectories {
      *            matches the predicate; if false, all matching results are
      *            included.
      * @return a {@link Map} from name to path.
      */
     public static Map<String, String> getMatchingProfiles(final File mozillaDir, INISectionPredicate predicate, boolean stopOnSuccess) {
         final HashMap<String, String> result = new HashMap<String, String>();
         final INIParser parser = GeckoProfileDirectories.getProfilesINI(mozillaDir);
 
-        for (Enumeration<INISection> e = parser.getSections().elements(); e.hasMoreElements();) {
-            final INISection section = e.nextElement();
-            if (predicate == null || predicate.matches(section)) {
-                final String name = section.getStringProperty("Name");
-                final String pathString = section.getStringProperty("Path");
-                final boolean isRelative = section.getIntProperty("IsRelative") == 1;
-                final File path = isRelative ? new File(mozillaDir, pathString) : new File(pathString);
-                result.put(name, path.getAbsolutePath());
+        if (parser.getSections() != null) {
+            for (Enumeration<INISection> e = parser.getSections().elements(); e.hasMoreElements(); ) {
+                final INISection section = e.nextElement();
+                if (predicate == null || predicate.matches(section)) {
+                    final String name = section.getStringProperty("Name");
+                    final String pathString = section.getStringProperty("Path");
+                    final boolean isRelative = section.getIntProperty("IsRelative") == 1;
+                    final File path = isRelative ? new File(mozillaDir, pathString) : new File(pathString);
+                    result.put(name, path.getAbsolutePath());
 
-                if (stopOnSuccess) {
-                    return result;
+                    if (stopOnSuccess) {
+                        return result;
+                    }
                 }
             }
         }
         return result;
     }
 
     public static File findProfileDir(final File mozillaDir, final String profileName) throws NoSuchProfileException {
         // Open profiles.ini to find the correct path.
         final INIParser parser = GeckoProfileDirectories.getProfilesINI(mozillaDir);
-
-        for (Enumeration<INISection> e = parser.getSections().elements(); e.hasMoreElements();) {
-            final INISection section = e.nextElement();
-            final String name = section.getStringProperty("Name");
-            if (name != null && name.equals(profileName)) {
-                if (section.getIntProperty("IsRelative") == 1) {
-                    return new File(mozillaDir, section.getStringProperty("Path"));
+        if (parser.getSections() != null) {
+            for (Enumeration<INISection> e = parser.getSections().elements(); e.hasMoreElements(); ) {
+                final INISection section = e.nextElement();
+                final String name = section.getStringProperty("Name");
+                if (name != null && name.equals(profileName)) {
+                    if (section.getIntProperty("IsRelative") == 1) {
+                        return new File(mozillaDir, section.getStringProperty("Path"));
+                    }
+                    return new File(section.getStringProperty("Path"));
                 }
-                return new File(section.getStringProperty("Path"));
             }
         }
-
         throw new NoSuchProfileException("No profile " + profileName);
     }
 }
--- a/mobile/android/base/locales/en-US/android_strings.dtd
+++ b/mobile/android/base/locales/en-US/android_strings.dtd
@@ -271,17 +271,17 @@
 
 <!ENTITY pref_whats_new_notification "What\'s new in &brandShortName;">
 <!ENTITY pref_whats_new_notification_summary "Learn about new features after an update">
 
 <!-- Custom Tabs is an Android API for allowing third-party apps to open URLs in a customized UI.
      Instead of switching to the browser it appears as if the user stays in the third-party app.
      For more see: https://developer.chrome.com/multidevice/android/customtabs -->
 <!ENTITY pref_custom_tabs "Custom Tabs">
-<!ENTITY pref_custom_tabs_summary "Allow third-party apps to open URLs with a customized look and feel. ">
+<!ENTITY pref_custom_tabs_summary "Allow third-party apps to open URLs with a customized look and feel.">
 
 <!ENTITY tracking_protection_prompt_title "Now with Tracking Protection">
 <!ENTITY tracking_protection_prompt_text "Actively block tracking elements so you don\'t have to worry.">
 <!ENTITY tracking_protection_prompt_tip_text "Visit Privacy settings to learn more">
 <!ENTITY tracking_protection_prompt_action_button "Got it!">
 
 <!ENTITY tab_queue_toast_message3 "Tab saved in &brandShortName;">
 <!ENTITY tab_queue_toast_action "Open now">
--- a/netwerk/base/nsIPermissionManager.idl
+++ b/netwerk/base/nsIPermissionManager.idl
@@ -89,16 +89,25 @@ interface nsIPermissionManager : nsISupp
    */
   void add(in nsIURI uri,
            in string type,
            in uint32_t permission,
            [optional] in uint32_t expireType,
            [optional] in int64_t expireTime);
 
   /**
+   * Get all custom permissions for a given URI. This will return
+   * an enumerator of all permissions which are not set to default
+   * and which belong to the matching prinicpal of the given URI.
+   *
+   * @param uri  the URI to get all permissions for
+   */
+  nsISimpleEnumerator getAllForURI(in nsIURI uri);
+
+  /**
    * Add permission information for a given principal.
    * It is internally calling the other add() method using the nsIURI from the
    * principal.
    * Passing a system principal will be a no-op because they will always be
    * granted permissions.
    */
   void addFromPrincipal(in nsIPrincipal principal, in string typed,
                         in uint32_t permission,
--- a/security/sandbox/linux/SandboxFilter.cpp
+++ b/security/sandbox/linux/SandboxFilter.cpp
@@ -462,16 +462,17 @@ public:
       // access.  But the graphics layer might not be using them
       // anymore; this needs to be studied.
     case SHMGET:
     case SHMCTL:
     case SHMAT:
     case SHMDT:
     case SEMGET:
     case SEMCTL:
+    case SEMOP:
       return Some(Allow());
     default:
       return SandboxPolicyCommon::EvaluateIpcCall(aCall);
     }
   }
 #endif
 
   virtual ResultExpr EvaluateSyscall(int sysno) const override {
@@ -518,17 +519,17 @@ public:
     CASES_FOR_fstatfs:
     case __NR_chmod:
     case __NR_rename:
     case __NR_symlink:
     case __NR_quotactl:
     case __NR_utimes:
     case __NR_link:
     case __NR_unlink:
-    case __NR_fchown:
+    CASES_FOR_fchown:
     case __NR_fchmod:
 #endif
       return Allow();
 
     case __NR_readlink:
     case __NR_readlinkat:
       // Workaround for bug 964455:
       return Error(EINVAL);
--- a/security/sandbox/linux/SandboxFilterUtil.h
+++ b/security/sandbox/linux/SandboxFilterUtil.h
@@ -69,16 +69,22 @@ public:
 // cases; see, e.g., the handling of RT vs. non-RT signal syscalls.
 
 #ifdef __NR_mmap2
 #define CASES_FOR_mmap   case __NR_mmap2
 #else
 #define CASES_FOR_mmap   case __NR_mmap
 #endif
 
+#ifdef __NR_fchown32
+#define CASES_FOR_fchown   case __NR_fchown32: case __NR_fchown
+#else
+#define CASES_FOR_fchown   case __NR_fchown
+#endif
+
 #ifdef __NR_getuid32
 #define CASES_FOR_getuid   case __NR_getuid32
 #define CASES_FOR_getgid   case __NR_getgid32
 #define CASES_FOR_geteuid   case __NR_geteuid32
 #define CASES_FOR_getegid   case __NR_getegid32
 #define CASES_FOR_getresuid   case __NR_getresuid32: case __NR_getresuid
 #define CASES_FOR_getresgid   case __NR_getresgid32: case __NR_getresgid
 // The set*id syscalls are omitted; we'll probably never need to allow them.
--- a/testing/mozharness/configs/balrog/production.py
+++ b/testing/mozharness/configs/balrog/production.py
@@ -11,24 +11,24 @@ config = {
                 'firefox': 'ffxbld',
                 'thunderbird': 'tbirdbld',
                 'mobile': 'ffxbld',
                 'Fennec': 'ffxbld',
                 'graphene': 'ffxbld',
                 'horizon': 'ffxbld',
             }
         },
-        # Bug 1275911 - get releng automation posting to cloudops balrog stage instance
-        {
-            'balrog_api_root': 'https://balrog-admin.stage.mozaws.net/api',
-            'ignore_failures': True,
-            'balrog_usernames': {
-                'b2g': 'stage-b2gbld',
-                'firefox': 'stage-ffxbld',
-                'thunderbird': 'stage-tbirdbld',
-                'mobile': 'stage-ffxbld',
-                'Fennec': 'stage-ffxbld',
-                'graphene': 'stage-ffxbld',
-                'horizon': 'stage-ffxbld',
-            }
-        }
+        # Bug 1261346 - temporarily disable staging balrog submissions
+        # {
+        #     'balrog_api_root': 'https://aus4-admin-dev.allizom.org/api',
+        #     'ignore_failures': True,
+        #     'balrog_usernames': {
+        #         'b2g': 'stage-b2gbld',
+        #         'firefox': 'stage-ffxbld',
+        #         'thunderbird': 'stage-tbirdbld',
+        #         'mobile': 'stage-ffxbld',
+        #         'Fennec': 'stage-ffxbld',
+        #         'graphene': 'stage-ffxbld',
+        #         'horizon': 'stage-ffxbld',
+        #     }
+        # }
     ]
 }
--- a/toolkit/components/reader/AboutReader.jsm
+++ b/toolkit/components/reader/AboutReader.jsm
@@ -609,39 +609,50 @@ AboutReader.prototype = {
   _loadArticle: Task.async(function* () {
     let url = this._getOriginalUrl();
     this._showProgressDelayed();
 
     let article;
     if (this._articlePromise) {
       article = yield this._articlePromise;
     } else {
-      article = yield this._getArticle(url);
+      try {
+        article = yield this._getArticle(url);
+      } catch (e) {
+        if (e && e.newURL) {
+          let readerURL = "about:reader?url=" + encodeURIComponent(e.newURL);
+          this._win.location.replace(readerURL);
+          return;
+        }
+      }
     }
 
     if (this._windowUnloaded) {
       return;
     }
 
-    if (article) {
-      this._showContent(article);
-    } else if (this._articlePromise) {
-      // If we were promised an article, show an error message if there's a failure.
+    // Replace the loading message with an error message if there's a failure.
+    // Users are supposed to navigate away by themselves (because we cannot
+    // remove ourselves from session history.)
+    if (!article) {
       this._showError();
-    } else {
-      // Otherwise, just load the original URL. We can encounter this case when
-      // loading an about:reader URL directly (e.g. opening a reading list item).
-      this._win.location.href = url;
+      return;
     }
+
+    this._showContent(article);
   }),
 
   _getArticle: function(url) {
     return new Promise((resolve, reject) => {
       let listener = (message) => {
         this._mm.removeMessageListener("Reader:ArticleData", listener);
+        if (message.data.newURL) {
+          reject({ newURL: message.data.newURL });
+          return;
+        }
         resolve(message.data.article);
       };
       this._mm.addMessageListener("Reader:ArticleData", listener);
       this._mm.sendAsyncMessage("Reader:ArticleGet", { url: url });
     });
   },
 
   _requestFavicon: function() {
--- a/toolkit/components/search/nsSearchService.js
+++ b/toolkit/components/search/nsSearchService.js
@@ -1618,88 +1618,83 @@ Engine.prototype = {
       Services.ww.getNewPrompter(null).alert(title, text);
     }
 
     if (!aBytes) {
       promptError();
       return;
     }
 
-    var engineToUpdate = null;
-    if (aEngine._engineToUpdate) {
-      engineToUpdate = aEngine._engineToUpdate.wrappedJSObject;
-
-      // Make this new engine use the old engine's shortName,
-      // to preserve user-set metadata.
-      aEngine._shortName = engineToUpdate._shortName;
-    }
-
     var parser = Cc["@mozilla.org/xmlextras/domparser;1"].
                  createInstance(Ci.nsIDOMParser);
     var doc = parser.parseFromBuffer(aBytes, aBytes.length, "text/xml");
     aEngine._data = doc.documentElement;
 
     try {
       // Initialize the engine from the obtained data
       aEngine._initFromData();
     } catch (ex) {
       LOG("_onLoad: Failed to init engine!\n" + ex);
       // Report an error to the user
       promptError();
       return;
     }
 
-    // Check that when adding a new engine (e.g., not updating an
-    // existing one), a duplicate engine does not already exist.
-    if (!engineToUpdate) {
+    if (aEngine._engineToUpdate) {
+      let engineToUpdate = aEngine._engineToUpdate.wrappedJSObject;
+
+      // Make this new engine use the old engine's shortName, and preserve
+      // metadata.
+      aEngine._shortName = engineToUpdate._shortName;
+      Object.keys(engineToUpdate._metaData).forEach(key => {
+        aEngine.setAttr(key, engineToUpdate.getAttr(key));
+      });
+      aEngine._loadPath = engineToUpdate._loadPath;
+
+      // Keep track of the last modified date, so that we can make conditional
+      // requests for future updates.
+      aEngine.setAttr("updatelastmodified", (new Date()).toUTCString());
+
+      // Set the new engine's icon, if it doesn't yet have one.
+      if (!aEngine._iconURI && engineToUpdate._iconURI)
+        aEngine._iconURI = engineToUpdate._iconURI;
+    } else {
+      // Check that when adding a new engine (e.g., not updating an
+      // existing one), a duplicate engine does not already exist.
       if (Services.search.getEngineByName(aEngine.name)) {
         // If we're confirming the engine load, then display a "this is a
         // duplicate engine" prompt; otherwise, fail silently.
         if (aEngine._confirm) {
           promptError({ error: "error_duplicate_engine_msg",
                         title: "error_invalid_engine_title"
                       }, Ci.nsISearchInstallCallback.ERROR_DUPLICATE_ENGINE);
         } else {
           onError(Ci.nsISearchInstallCallback.ERROR_DUPLICATE_ENGINE);
         }
         LOG("_onLoad: duplicate engine found, bailing");
         return;
       }
-    }
-
-    // If requested, confirm the addition now that we have the title.
-    // This property is only ever true for engines added via
-    // nsIBrowserSearchService::addEngine.
-    if (aEngine._confirm) {
-      var confirmation = aEngine._confirmAddEngine();
-      LOG("_onLoad: confirm is " + confirmation.confirmed +
-          "; useNow is " + confirmation.useNow);
-      if (!confirmation.confirmed) {
-        onError();
-        return;
+
+      // If requested, confirm the addition now that we have the title.
+      // This property is only ever true for engines added via