Merge mozilla-central to autoland on a CLOSED TREE
authorCarsten "Tomcat" Book <cbook@mozilla.com>
Fri, 15 Jul 2016 16:18:01 +0200
changeset 330126 058b26df0165c0cf78f3cde249bd10e8a474dcc0
parent 330125 82ae790de9889e8531cbc4d82b76259a1fd8d14d (current diff)
parent 330112 2f9e69c982f1e67887a1834b36ff0af4ababb3af (diff)
child 330127 38a0ffc8a956a49cd52c2c86e1cc7759f75b6db2
push id9858
push userjlund@mozilla.com
push dateMon, 01 Aug 2016 14:37:10 +0000
treeherdermozilla-aurora@203106ef6cb6 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone50.0a1
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..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
GIT binary patch
literal 0
Hc$@<O00001
deleted file mode 100644
index d784870c380da76e4897aad41c4c87cc848dd50e..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
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
+      // nsIBrowserSearchService::addEngine.
+      if (aEngine._confirm) {
+        var confirmation = aEngine._confirmAddEngine();
+        LOG("_onLoad: confirm is " + confirmation.confirmed +
+            "; useNow is " + confirmation.useNow);
+        if (!confirmation.confirmed) {
+          onError();
+          return;
+        }
+        aEngine._useNow = confirmation.useNow;
       }
-      aEngine._useNow = confirmation.useNow;
-    }
-
-    // If we don't yet have a shortName, get one now. We would already have one
-    // if this is an update and _file was set above.
-    if (!aEngine._shortName)
+
       aEngine._shortName = sanitizeName(aEngine.name);
-
-    aEngine._loadPath = aEngine.getAnonymizedLoadPath(null, aEngine._uri);
-    aEngine.setAttr("loadPathHash", getVerificationHash(aEngine._loadPath));
-
-    if (engineToUpdate) {
-      // 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;
+      aEngine._loadPath = aEngine.getAnonymizedLoadPath(null, aEngine._uri);
+      aEngine.setAttr("loadPathHash", getVerificationHash(aEngine._loadPath));
     }
 
     // Notify the search service of the successful load. It will deal with
     // updates by checking aEngine._engineToUpdate.
     notifyAction(aEngine, SEARCH_ENGINE_LOADED);
 
     // Notify the callback if needed
     if (aEngine._installCallback) {
new file mode 100644
--- /dev/null
+++ b/toolkit/components/search/tests/xpcshell/test_engineUpdate.js
@@ -0,0 +1,50 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/* Test that user-set metadata isn't lost on engine update */