Merge m-c to fx-team, a=merge
authorWes Kocher <wkocher@mozilla.com>
Thu, 01 Sep 2016 17:55:15 -0700
changeset 312325 4f8bafdd88dc6cfdb5b5fe074dfb5d1abdb2f20c
parent 312272 3d376f300eac2ed52d331a47b4608c7719013bae (current diff)
parent 312324 475f0ee625239e06c5794d4cf34e88d6ee2fde31 (diff)
child 312327 4c4ce191fc9b444f1f47272293e001707538c105
push id20443
push userkwierso@gmail.com
push dateFri, 02 Sep 2016 00:55:23 +0000
treeherderfx-team@4f8bafdd88dc [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone51.0a1
Merge m-c to fx-team, a=merge
dom/tests/mochitest/bugs/test_bug411103.html
gfx/layers/ipc/TextureForwarder.cpp
--- a/accessible/atk/UtilInterface.cpp
+++ b/accessible/atk/UtilInterface.cpp
@@ -246,33 +246,33 @@ mai_key_snooper(GtkWidget *the_widget, G
     }
     g_free(info);
     return (consumed ? 1 : 0);
 }
 
 static guint sKey_snooper_id = 0;
 
 static guint
-mai_util_add_key_event_listener (AtkKeySnoopFunc listener,
-                                 gpointer data)
+mai_util_add_key_event_listener(AtkKeySnoopFunc listener, gpointer data)
 {
-  if (MOZ_UNLIKELY(!listener))
+  if (MOZ_UNLIKELY(!listener)) {
     return 0;
+  }
 
-    static guint key=0;
+  static guint key = 0;
 
-    if (!sKey_listener_list) {
-        sKey_listener_list = g_hash_table_new(nullptr, nullptr);
-        sKey_snooper_id = gtk_key_snooper_install(mai_key_snooper, data);
-    }
-    AtkKeySnoopFuncPointer atkKeySnoop;
-    atkKeySnoop.func_ptr = listener;
-    g_hash_table_insert(sKey_listener_list, GUINT_TO_POINTER (key++),
-                        atkKeySnoop.data);
-    return key;
+  if (!sKey_listener_list) {
+    sKey_listener_list = g_hash_table_new(nullptr, nullptr);
+    sKey_snooper_id = gtk_key_snooper_install(mai_key_snooper, data);
+  }
+  AtkKeySnoopFuncPointer atkKeySnoop;
+  atkKeySnoop.func_ptr = listener;
+  g_hash_table_insert(sKey_listener_list, GUINT_TO_POINTER(key++),
+                      atkKeySnoop.data);
+  return key;
 }
 
 static void
 mai_util_remove_key_event_listener (guint remove_listener)
 {
     if (!sKey_listener_list) {
         // atk-bridge is initialized with gail (e.g. yelp)
         // try gail_remove_key_event_listener
--- a/accessible/atk/nsMaiHyperlink.cpp
+++ b/accessible/atk/nsMaiHyperlink.cpp
@@ -183,28 +183,29 @@ getUriCB(AtkHyperlink *aLink, gint aLink
 
   return g_strdup(cautoStr.get());
 }
 
 AtkObject *
 getObjectCB(AtkHyperlink *aLink, gint aLinkIndex)
 {
   MaiHyperlink* maiLink = GetMaiHyperlink(aLink);
-  if (!maiLink)
+  if (!maiLink) {
     return nullptr;
+  }
 
-    if (Accessible* hyperlink = maiLink->GetAccHyperlink()) {
-      Accessible* anchor = hyperlink->AnchorAt(aLinkIndex);
-      NS_ENSURE_TRUE(anchor, nullptr);
+  if (Accessible* hyperlink = maiLink->GetAccHyperlink()) {
+    Accessible* anchor = hyperlink->AnchorAt(aLinkIndex);
+    NS_ENSURE_TRUE(anchor, nullptr);
 
-      return AccessibleWrap::GetAtkObject(anchor);
-    }
+    return AccessibleWrap::GetAtkObject(anchor);
+  }
 
-    ProxyAccessible* anchor = maiLink->Proxy()->AnchorAt(aLinkIndex);
-    return anchor ? GetWrapperFor(anchor) : nullptr;
+  ProxyAccessible* anchor = maiLink->Proxy()->AnchorAt(aLinkIndex);
+  return anchor ? GetWrapperFor(anchor) : nullptr;
 }
 
 gint
 getEndIndexCB(AtkHyperlink *aLink)
 {
   MaiHyperlink* maiLink = GetMaiHyperlink(aLink);
   if (!maiLink)
     return false;
--- a/accessible/atk/nsMaiInterfaceDocument.cpp
+++ b/accessible/atk/nsMaiInterfaceDocument.cpp
@@ -56,24 +56,25 @@ getDocumentLocaleCB(AtkDocument *aDocume
   }
 
   return locale.IsEmpty() ? nullptr : AccessibleWrap::ReturnString(locale);
 }
 
 static inline GSList *
 prependToList(GSList *aList, const char *const aName, const nsAutoString &aValue)
 {
-  if (aValue.IsEmpty())
+  if (aValue.IsEmpty()) {
     return aList;
+  }
 
-    // libspi will free these
-    AtkAttribute *atkAttr = (AtkAttribute *)g_malloc(sizeof(AtkAttribute));
-    atkAttr->name = g_strdup(aName);
-    atkAttr->value = g_strdup(NS_ConvertUTF16toUTF8(aValue).get());
-    return g_slist_prepend(aList, atkAttr);
+  // libspi will free these
+  AtkAttribute *atkAttr = (AtkAttribute *)g_malloc(sizeof(AtkAttribute));
+  atkAttr->name = g_strdup(aName);
+  atkAttr->value = g_strdup(NS_ConvertUTF16toUTF8(aValue).get());
+  return g_slist_prepend(aList, atkAttr);
 }
 
 AtkAttributeSet *
 getDocumentAttributesCB(AtkDocument *aDocument)
 {
   nsAutoString url;
   nsAutoString w3cDocType;
   nsAutoString mimeType;
--- a/browser/components/contextualidentity/test/browser/browser_windowName.js
+++ b/browser/components/contextualidentity/test/browser/browser_windowName.js
@@ -42,28 +42,28 @@ add_task(function* test() {
   info("Opening a window from the first tab...");
   yield ContentTask.spawn(browser1, { url: BASE_URI + '?new' }, function(opts) {
     yield (new content.window.wrappedJSObject.Promise(resolve => {
       let w = content.window.wrappedJSObject.open(opts.url, 'tab-2');
       w.onload = function() { resolve(); }
     }));
   });
 
-  is(browser1.contentDocument.title, '?old', "Tab1 title must be 'old'");
-  is(browser1.contentDocument.nodePrincipal.userContextId, 1, "Tab1 UCI must be 1");
+  is(browser1.contentTitle, '?old', "Tab1 title must be 'old'");
+  is(browser1.contentPrincipal.userContextId, 1, "Tab1 UCI must be 1");
 
-  is(browser2.contentDocument.title, '?old', "Tab2 title must be 'old'");
-  is(browser2.contentDocument.nodePrincipal.userContextId, 2, "Tab2 UCI must be 2");
+  is(browser2.contentTitle, '?old', "Tab2 title must be 'old'");
+  is(browser2.contentPrincipal.userContextId, 2, "Tab2 UCI must be 2");
 
   let found = false;
   for (let i = 0; i < gBrowser.tabContainer.childNodes.length; ++i) {
     let tab = gBrowser.tabContainer.childNodes[i];
     let browser = gBrowser.getBrowserForTab(tab);
-    if (browser.contentDocument.title == '?new') {
-      is(browser.contentDocument.nodePrincipal.userContextId, 1, "Tab3 UCI must be 1");
+    if (browser.contentTitle == '?new') {
+      is(browser.contentPrincipal.userContextId, 1, "Tab3 UCI must be 1");
       isnot(browser, browser1, "Tab3 is not browser 1");
       isnot(browser, browser2, "Tab3 is not browser 2");
       gBrowser.removeTab(tab);
       found = true;
       break;
     }
   }
 
--- a/devtools/client/debugger/test/mochitest/browser_dbg_addonactor.js
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_addonactor.js
@@ -23,17 +23,17 @@ function test() {
     is(aType, "browser",
       "Root actor should identify itself as a browser.");
 
     installAddon()
       .then(attachAddonActorForId.bind(null, gClient, ADDON3_ID))
       .then(attachAddonThread)
       .then(testDebugger)
       .then(testSources)
-      .then(closeConnection)
+      .then(() => gClient.close())
       .then(uninstallAddon)
       .then(finish)
       .then(null, aError => {
         ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
       });
   });
 }
 
@@ -83,19 +83,13 @@ function testSources() {
 
   return deferred.promise;
 }
 
 function uninstallAddon() {
   return removeAddon(gAddon);
 }
 
-function closeConnection() {
-  let deferred = promise.defer();
-  gClient.close(deferred.resolve);
-  return deferred.promise;
-}
-
 registerCleanupFunction(function () {
   gClient = null;
   gAddon = null;
   gThreadClient = null;
 });
--- a/devtools/client/debugger/test/mochitest/browser_dbg_break-on-dom-event-01.js
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_break-on-dom-event-01.js
@@ -26,17 +26,17 @@ function test() {
     addTab(TAB_URL)
       .then(() => attachThreadActorForUrl(gClient, TAB_URL))
       .then(setupGlobals)
       .then(pauseDebuggee)
       .then(testBreakOnAll)
       .then(testBreakOnDisabled)
       .then(testBreakOnNone)
       .then(testBreakOnClick)
-      .then(closeConnection)
+      .then(() => gClient.close())
       .then(finish)
       .then(null, aError => {
         ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
       });
   });
 }
 
 function setupGlobals(aThreadClient) {
@@ -179,22 +179,16 @@ function testBreakOnClick() {
     });
 
     triggerButtonClick();
   });
 
   return deferred.promise;
 }
 
-function closeConnection() {
-  let deferred = promise.defer();
-  gClient.close(deferred.resolve);
-  return deferred.promise;
-}
-
 function unexpectedListener() {
   gClient.removeListener("paused", unexpectedListener);
   ok(false, "An unexpected hidden breakpoint was hit.");
   gThreadClient.resume(testBreakOnClick);
 }
 
 function triggerInputKeyup() {
   // Make sure that the focus is not on the input box so that a focus event
--- a/devtools/client/debugger/test/mochitest/browser_dbg_break-on-dom-event-02.js
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_break-on-dom-event-02.js
@@ -24,17 +24,17 @@ function test() {
     is(aType, "browser",
       "Root actor should identify itself as a browser.");
 
     addTab(TAB_URL)
       .then(() => attachThreadActorForUrl(gClient, TAB_URL))
       .then(aThreadClient => gThreadClient = aThreadClient)
       .then(pauseDebuggee)
       .then(testBreakOnClick)
-      .then(closeConnection)
+      .then(() => gClient.close())
       .then(finish)
       .then(null, aError => {
         ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
       });
   });
 }
 
 function pauseDebuggee() {
@@ -94,18 +94,12 @@ function testBreakOnClick() {
   return deferred.promise;
 }
 
 function triggerButtonClick(aNodeId) {
   let button = content.document.getElementById(aNodeId);
   EventUtils.sendMouseEvent({ type: "click" }, button);
 }
 
-function closeConnection() {
-  let deferred = promise.defer();
-  gClient.close(deferred.resolve);
-  return deferred.promise;
-}
-
 registerCleanupFunction(function () {
   gClient = null;
   gThreadClient = null;
 });
--- a/devtools/client/debugger/test/mochitest/browser_dbg_break-on-dom-event-03.js
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_break-on-dom-event-03.js
@@ -23,17 +23,17 @@ function test() {
     is(aType, "browser",
       "Root actor should identify itself as a browser.");
 
     addTab(TAB_URL)
       .then(() => attachThreadActorForUrl(gClient, TAB_URL))
       .then(aThreadClient => gThreadClient = aThreadClient)
       .then(pauseDebuggee)
       .then(testBreakOnLoad)
-      .then(closeConnection)
+      .then(() => gClient.close())
       .then(finish)
       .then(null, aError => {
         ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
       });
   });
 }
 
 function pauseDebuggee() {
@@ -86,18 +86,12 @@ function testBreakOnLoad() {
   return deferred.promise;
 }
 
 function triggerButtonClick() {
   let button = content.document.querySelector("button");
   EventUtils.sendMouseEvent({ type: "click" }, button);
 }
 
-function closeConnection() {
-  let deferred = promise.defer();
-  gClient.close(deferred.resolve);
-  return deferred.promise;
-}
-
 registerCleanupFunction(function () {
   gClient = null;
   gThreadClient = null;
 });
--- a/devtools/client/debugger/test/mochitest/browser_dbg_chrome-debugging.js
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_chrome-debugging.js
@@ -81,17 +81,17 @@ function onNewSource(aEvent, aPacket) {
 
     gThreadClient.removeListener("newSource", onNewSource);
     gNewChromeSource.resolve();
   }
 }
 
 function resumeAndCloseConnection() {
   let deferred = promise.defer();
-  gThreadClient.resume(() => gClient.close(deferred.resolve));
+  gThreadClient.resume(() => deferred.resolve(gClient.close()));
   return deferred.promise;
 }
 
 registerCleanupFunction(function () {
   gClient = null;
   gThreadClient = null;
   gAttached = null;
   gNewGlobal = null;
--- a/devtools/client/debugger/test/mochitest/browser_dbg_debugger-statement.js
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_debugger-statement.js
@@ -26,17 +26,17 @@ function test() {
 
     addTab(TAB_URL)
       .then((aTab) => {
         gTab = aTab;
         return attachTabActorForUrl(gClient, TAB_URL);
       })
       .then(testEarlyDebuggerStatement)
       .then(testDebuggerStatement)
-      .then(closeConnection)
+      .then(() => gClient.close())
       .then(finish)
       .then(null, aError => {
         ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
       });
   });
 }
 
 function testEarlyDebuggerStatement([aGrip, aResponse]) {
@@ -77,17 +77,11 @@ function testDebuggerStatement([aGrip, a
   });
 
   // Reach around the debugging protocol and execute the debugger statement.
   callInTab(gTab, "runDebuggerStatement");
 
   return deferred.promise;
 }
 
-function closeConnection() {
-  let deferred = promise.defer();
-  gClient.close(deferred.resolve);
-  return deferred.promise;
-}
-
 registerCleanupFunction(function () {
   gClient = null;
 });
--- a/devtools/client/debugger/test/mochitest/browser_dbg_event-listeners-01.js
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_event-listeners-01.js
@@ -26,17 +26,17 @@ function test() {
 
     addTab(TAB_URL)
       .then((aTab) => {
         gTab = aTab;
         return attachThreadActorForUrl(gClient, TAB_URL);
       })
       .then(pauseDebuggee)
       .then(testEventListeners)
-      .then(closeConnection)
+      .then(() => gClient.close())
       .then(finish)
       .then(null, aError => {
         ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
       });
   });
 }
 
 function pauseDebuggee(aThreadClient) {
@@ -137,17 +137,11 @@ function testEventListeners(aThreadClien
 
       aThreadClient.resume(deferred.resolve);
     });
   });
 
   return deferred.promise;
 }
 
-function closeConnection() {
-  let deferred = promise.defer();
-  gClient.close(deferred.resolve);
-  return deferred.promise;
-}
-
 registerCleanupFunction(function () {
   gClient = null;
 });
--- a/devtools/client/debugger/test/mochitest/browser_dbg_event-listeners-02.js
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_event-listeners-02.js
@@ -27,17 +27,17 @@ function test() {
 
     addTab(TAB_URL)
       .then((aTab) => {
         gTab = aTab;
         return attachThreadActorForUrl(gClient, TAB_URL);
       })
       .then(pauseDebuggee)
       .then(testEventListeners)
-      .then(closeConnection)
+      .then(() => gClient.close())
       .then(finish)
       .then(null, aError => {
         ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
       });
   });
 }
 
 function pauseDebuggee(aThreadClient) {
@@ -113,17 +113,11 @@ function testEventListeners(aThreadClien
 
       aThreadClient.resume(deferred.resolve);
     });
   });
 
   return deferred.promise;
 }
 
-function closeConnection() {
-  let deferred = promise.defer();
-  gClient.close(deferred.resolve);
-  return deferred.promise;
-}
-
 registerCleanupFunction(function () {
   gClient = null;
 });
--- a/devtools/client/debugger/test/mochitest/browser_dbg_event-listeners-03.js
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_event-listeners-03.js
@@ -27,17 +27,17 @@ function test() {
 
     addTab(TAB_URL)
       .then((aTab) => {
         gTab = aTab;
         return attachThreadActorForUrl(gClient, TAB_URL);
       })
       .then(pauseDebuggee)
       .then(testEventListeners)
-      .then(closeConnection)
+      .then(() => gClient.close())
       .then(finish)
       .then(null, aError => {
         ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
       });
   });
 }
 
 function pauseDebuggee(aThreadClient) {
@@ -72,17 +72,11 @@ function testEventListeners(aThreadClien
     // and one more from the video element controls.
     is(aPacket.listeners.length, 3, "Found all event listeners.");
     aThreadClient.resume(deferred.resolve);
   });
 
   return deferred.promise;
 }
 
-function closeConnection() {
-  let deferred = promise.defer();
-  gClient.close(deferred.resolve);
-  return deferred.promise;
-}
-
 registerCleanupFunction(function () {
   gClient = null;
 });
--- a/devtools/client/debugger/test/mochitest/browser_dbg_globalactor.js
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_globalactor.js
@@ -48,14 +48,14 @@ function test() {
                 return e.startsWith(actorPrefix);
               }).length;
             }
           }
 
           is(count, 2,
             "Only two actor exists in all pools. One tab actor and one global.");
 
-          gClient.close(finish);
+          gClient.close().then(finish);
         });
       });
     });
   });
 }
--- a/devtools/client/debugger/test/mochitest/browser_dbg_listaddons.js
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_listaddons.js
@@ -25,17 +25,17 @@ function test() {
     is(aType, "browser",
       "Root actor should identify itself as a browser.");
 
     promise.resolve(null)
       .then(testFirstAddon)
       .then(testSecondAddon)
       .then(testRemoveFirstAddon)
       .then(testRemoveSecondAddon)
-      .then(closeConnection)
+      .then(() => gClient.close())
       .then(finish)
       .then(null, aError => {
         ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
       });
   });
 }
 
 function testFirstAddon() {
@@ -98,21 +98,15 @@ function testRemoveSecondAddon() {
   return removeAddon(gAddon2).then(() => {
     return getAddonActorForId(gClient, ADDON2_ID).then(aGrip => {
       ok(addonListChanged, "Should be notified that list of addons changed.");
       ok(!aGrip, "Shouldn't find a addon actor for the second addon anymore.");
     });
   });
 }
 
-function closeConnection() {
-  let deferred = promise.defer();
-  gClient.close(deferred.resolve);
-  return deferred.promise;
-}
-
 registerCleanupFunction(function () {
   gAddon1 = null;
   gAddon1Actor = null;
   gAddon2 = null;
   gAddon2Actor = null;
   gClient = null;
 });
--- a/devtools/client/debugger/test/mochitest/browser_dbg_listtabs-01.js
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_listtabs-01.js
@@ -24,17 +24,17 @@ function test() {
     is(aType, "browser",
       "Root actor should identify itself as a browser.");
 
     promise.resolve(null)
       .then(testFirstTab)
       .then(testSecondTab)
       .then(testRemoveTab)
       .then(testAttachRemovedTab)
-      .then(closeConnection)
+      .then(() => gClient.close())
       .then(finish)
       .then(null, aError => {
         ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
       });
   });
 }
 
 function testFirstTab() {
@@ -84,21 +84,15 @@ function testAttachRemovedTab() {
          "Connection is gone since the tab was removed.");
       deferred.resolve();
     });
 
     return deferred.promise;
   });
 }
 
-function closeConnection() {
-  let deferred = promise.defer();
-  gClient.close(deferred.resolve);
-  return deferred.promise;
-}
-
 registerCleanupFunction(function () {
   gTab1 = null;
   gTab1Actor = null;
   gTab2 = null;
   gTab2Actor = null;
   gClient = null;
 });
--- a/devtools/client/debugger/test/mochitest/browser_dbg_listtabs-03.js
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_listtabs-03.js
@@ -42,26 +42,20 @@ function test() {
     is(newGrip.actor, tabGrip.actor, "Should have the same actor for the same tab");
 
     response = yield gClient.request({ to: tabGrip.actor, type: "attach" });
     is(response.type, "tabAttached", "Should have attached");
     response = yield gClient.request({ to: tabGrip.actor, type: "detach" });
     is(response.type, "detached", "Should have detached");
 
     yield removeTab(tab);
-    yield closeConnection();
+    yield gClient.close();
     finish();
   }));
 }
 
-function closeConnection() {
-  let deferred = promise.defer();
-  gClient.close(deferred.resolve);
-  return deferred.promise;
-}
-
 registerCleanupFunction(function () {
   gTab1 = null;
   gTab1Actor = null;
   gTab2 = null;
   gTab2Actor = null;
   gClient = null;
 });
--- a/devtools/client/debugger/test/mochitest/browser_dbg_multiple-windows.js
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_multiple-windows.js
@@ -28,17 +28,17 @@ function test() {
 
     promise.resolve(null)
       .then(() => addTab(TAB1_URL))
       .then(testFirstTab)
       .then(() => addWindow(TAB2_URL))
       .then(testNewWindow)
       .then(testFocusFirst)
       .then(testRemoveTab)
-      .then(closeConnection)
+      .then(() => gClient.close())
       .then(finish)
       .then(null, aError => {
         ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
       });
   });
 }
 
 function testFirstTab(aTab) {
@@ -153,19 +153,13 @@ function continue_remove_tab(deferred)
 
     is(aResponse.selected, 0,
       "The original tab is selected.");
 
     deferred.resolve();
   });
 }
 
-function closeConnection() {
-  let deferred = promise.defer();
-  gClient.close(deferred.resolve);
-  return deferred.promise;
-}
-
 registerCleanupFunction(function () {
   gNewTab = null;
   gNewWindow = null;
   gClient = null;
 });
--- a/devtools/client/debugger/test/mochitest/browser_dbg_navigation.js
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_navigation.js
@@ -58,17 +58,17 @@ function testNavigate([aGrip, aResponse]
 }
 
 function testDetach(aActor) {
   let deferred = promise.defer();
 
   gClient.addOneTimeListener("tabDetached", (aType, aPacket) => {
     ok(true, "Got a tab detach notification.");
     is(aPacket.from, aActor, "tab detach message comes from the expected actor");
-    gClient.close(deferred.resolve);
+    deferred.resolve(gClient.close());
   });
 
   removeTab(gBrowser.selectedTab);
   return deferred.promise;
 }
 
 registerCleanupFunction(function () {
   gClient = null;
--- a/devtools/client/debugger/test/mochitest/browser_dbg_tabactor-01.js
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_tabactor-01.js
@@ -25,17 +25,17 @@ function test() {
   gClient.connect().then(([aType, aTraits]) => {
     is(aType, "browser",
       "Root actor should identify itself as a browser.");
 
     addTab(TAB_URL)
       .then(() => attachTabActorForUrl(gClient, TAB_URL))
       .then(testTabActor)
       .then(closeTab)
-      .then(closeConnection)
+      .then(() => gClient.close())
       .then(finish)
       .then(null, aError => {
         ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
       });
   });
 }
 
 function testTabActor([aGrip, aResponse]) {
@@ -55,17 +55,11 @@ function testTabActor([aGrip, aResponse]
 
   return deferred.promise;
 }
 
 function closeTab() {
   return removeTab(gBrowser.selectedTab);
 }
 
-function closeConnection() {
-  let deferred = promise.defer();
-  gClient.close(deferred.resolve);
-  return deferred.promise;
-}
-
 registerCleanupFunction(function () {
   gClient = null;
 });
--- a/devtools/client/debugger/test/mochitest/browser_dbg_tabactor-02.js
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_tabactor-02.js
@@ -25,17 +25,17 @@ function test() {
   gClient.connect().then(([aType, aTraits]) => {
     is(aType, "browser",
       "Root actor should identify itself as a browser.");
 
     addTab(TAB_URL)
       .then(() => attachTabActorForUrl(gClient, TAB_URL))
       .then(testTabActor)
       .then(closeTab)
-      .then(closeConnection)
+      .then(() => gClient.close())
       .then(finish)
       .then(null, aError => {
         ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
       });
   });
 }
 
 function testTabActor([aGrip, aResponse]) {
@@ -69,17 +69,11 @@ function closeTab(aTestActor) {
       is(e.message, "'ping' request packet has no destination.", "testTabActor1 went away.");
       deferred.resolve();
     }
 
     return deferred.promise;
   });
 }
 
-function closeConnection() {
-  let deferred = promise.defer();
-  gClient.close(deferred.resolve);
-  return deferred.promise;
-}
-
 registerCleanupFunction(function () {
   gClient = null;
 });
--- a/devtools/client/debugger/test/mochitest/head.js
+++ b/devtools/client/debugger/test/mochitest/head.js
@@ -657,19 +657,17 @@ AddonDebugger.prototype = {
     this.debuggerPanel = toolbox.getCurrentPanel();
     yield waitForSourceShown(this.debuggerPanel, "");
 
     prepareDebugger(this.debuggerPanel);
     yield this._attachConsole();
   }),
 
   destroy: Task.async(function* () {
-    let deferred = promise.defer();
-    this.client.close(deferred.resolve);
-    yield deferred.promise;
+    yield this.client.close();
     yield this.debuggerPanel._toolbox.destroy();
     this.frame.remove();
     window.removeEventListener("message", this._onMessage);
   }),
 
   _attachConsole: function () {
     let deferred = promise.defer();
     this.client.attachConsole(this.target.form.consoleActor, ["ConsoleAPI"], (aResponse, aWebConsoleClient) => {
@@ -1073,21 +1071,17 @@ function generateMouseClickInTab(tab, pa
 
 function connect(client) {
   info("Connecting client.");
   return client.connect();
 }
 
 function close(client) {
   info("Waiting for client to close.\n");
-  return new Promise(function (resolve) {
-    client.close(() => {
-      resolve();
-    });
-  });
+  return client.close();
 }
 
 function listTabs(client) {
   info("Listing tabs.");
   return client.listTabs();
 }
 
 function findTab(tabs, url) {
--- a/devtools/client/framework/target.js
+++ b/devtools/client/framework/target.js
@@ -581,17 +581,17 @@ TabTarget.prototype = {
     } else if (this._client) {
       // If, on the other hand, this target was remoted, the promise will be
       // resolved after the remote connection is closed.
       this._teardownRemoteListeners();
 
       if (this.isLocalTab) {
         // We started with a local tab and created the client ourselves, so we
         // should close it.
-        this._client.close(cleanupAndResolve);
+        this._client.close().then(cleanupAndResolve);
       } else if (this.activeTab) {
         // The client was handed to us, so we are not responsible for closing
         // it. We just need to detach from the tab, if already attached.
         // |detach| may fail if the connection is already dead, so proceed with
         // cleanup directly after this.
         this.activeTab.detach();
         cleanupAndResolve();
       } else {
--- a/devtools/client/framework/test/browser_two_tabs.js
+++ b/devtools/client/framework/test/browser_two_tabs.js
@@ -141,12 +141,12 @@ function checkFirstTabActor() {
   });
 }
 
 function cleanup() {
   let container = gBrowser.tabContainer;
   container.addEventListener("TabClose", function onTabClose() {
     container.removeEventListener("TabClose", onTabClose);
 
-    gClient.close(finish);
+    gClient.close().then(finish);
   });
   gBrowser.removeTab(gTab1);
 }
--- a/devtools/client/responsive.html/manager.js
+++ b/devtools/client/responsive.html/manager.js
@@ -370,23 +370,21 @@ ResponsiveUI.prototype = {
     let swap = this.swap;
     this.browserWindow = null;
     this.tab = null;
     this.inited = null;
     this.toolWindow = null;
     this.swap = null;
 
     // Close the debugger client used to speak with emulation actor
-    let clientClosed = new Promise((resolve, reject) => {
-      this.client.close(resolve);
-      this.client = this.emulationFront = null;
-    });
+    let clientClosed = this.client.close();
     if (!isTabClosing) {
       yield clientClosed;
     }
+    this.client = this.emulationFront = null;
 
     // Undo the swap and return the content back to a normal tab
     swap.stop();
 
     this.destroyed = true;
 
     return true;
   }),
--- a/devtools/client/responsivedesign/responsivedesign.jsm
+++ b/devtools/client/responsivedesign/responsivedesign.jsm
@@ -383,20 +383,18 @@ ResponsiveUI.prototype = {
     this.container.removeAttribute("responsivemode");
     this.stack.removeAttribute("responsivemode");
 
     ActiveTabs.delete(this.tab);
     if (this.touchEventSimulator) {
       this.touchEventSimulator.stop();
     }
 
-    yield new Promise((resolve, reject) => {
-      this.client.close(resolve);
-      this.client = this.emulationFront = null;
-    });
+    yield this.client.close();
+    this.client = this.emulationFront = null;
 
     this._telemetry.toolClosed("responsive");
 
     if (this.tab.linkedBrowser.messageManager) {
       let stopped = this.waitForMessage("ResponsiveMode:Stop:Done");
       this.tab.linkedBrowser.messageManager.sendAsyncMessage("ResponsiveMode:Stop");
       yield stopped;
     }
--- a/devtools/client/webconsole/hudservice.js
+++ b/devtools/client/webconsole/hudservice.js
@@ -693,17 +693,17 @@ BrowserConsole.prototype = extend(WebCon
     }
 
     this._telemetry.toolClosed("browserconsole");
 
     this._bc_destroyer = promise.defer();
 
     let chromeWindow = this.chromeWindow;
     this.$destroy().then(() =>
-      this.target.client.close(() => {
+      this.target.client.close().then(() => {
         HUDService._browserConsoleID = null;
         chromeWindow.close();
         this._bc_destroyer.resolve(null);
       }));
 
     return this._bc_destroyer.promise;
   },
 });
--- a/devtools/server/tests/browser/browser_animation_emitMutations.js
+++ b/devtools/server/tests/browser/browser_animation_emitMutations.js
@@ -52,11 +52,11 @@ add_task(function* () {
   ok(true, "The mutations event was emitted");
   is(changes.length, 2, "There are 2 changes in the mutation event");
   ok(changes.every(({type}) => type === "removed"), "Both are removals");
   ok(changes[0].player === p1 || changes[0].player === p2,
     "The first removed player was one of the previously added players");
   ok(changes[1].player === p1 || changes[1].player === p2,
     "The second removed player was one of the previously added players");
 
-  yield closeDebuggerClient(client);
+  yield client.close();
   gBrowser.removeCurrentTab();
 });
--- a/devtools/server/tests/browser/browser_animation_getFrames.js
+++ b/devtools/server/tests/browser/browser_animation_getFrames.js
@@ -22,11 +22,11 @@ add_task(function* () {
   let frames = yield player.getFrames();
   is(frames.length, 2, "The correct number of keyframes was retrieved");
   ok(frames[0].transform, "Frame 0 has the transform property");
   ok(frames[1].transform, "Frame 1 has the transform property");
   // Note that we don't really test the content of the frame object here on
   // purpose. This object comes straight out of the web animations API
   // unmodified.
 
-  yield closeDebuggerClient(client);
+  yield client.close();
   gBrowser.removeCurrentTab();
 });
--- a/devtools/server/tests/browser/browser_animation_getMultipleStates.js
+++ b/devtools/server/tests/browser/browser_animation_getMultipleStates.js
@@ -8,17 +8,17 @@
 // multiple animations.
 
 add_task(function* () {
   let {client, walker, animations} =
     yield initAnimationsFrontForUrl(MAIN_DOMAIN + "animation.html");
 
   yield playerHasAnInitialState(walker, animations);
 
-  yield closeDebuggerClient(client);
+  yield client.close();
   gBrowser.removeCurrentTab();
 });
 
 function* playerHasAnInitialState(walker, animations) {
   let state = yield getAnimationStateForNode(walker, animations,
     ".delayed-multiple-animations", 0);
 
   ok(state.duration, 50000,
--- a/devtools/server/tests/browser/browser_animation_getPlayers.js
+++ b/devtools/server/tests/browser/browser_animation_getPlayers.js
@@ -8,17 +8,17 @@
 
 add_task(function* () {
   let {client, walker, animations} =
     yield initAnimationsFrontForUrl(MAIN_DOMAIN + "animation.html");
 
   yield theRightNumberOfPlayersIsReturned(walker, animations);
   yield playersCanBePausedAndResumed(walker, animations);
 
-  yield closeDebuggerClient(client);
+  yield client.close();
   gBrowser.removeCurrentTab();
 });
 
 function* theRightNumberOfPlayersIsReturned(walker, animations) {
   let node = yield walker.querySelector(walker.rootNode, ".not-animated");
   let players = yield animations.getAnimationPlayersForNode(node);
   is(players.length, 0,
      "0 players were returned for the unanimated node");
--- a/devtools/server/tests/browser/browser_animation_getProperties.js
+++ b/devtools/server/tests/browser/browser_animation_getProperties.js
@@ -26,11 +26,11 @@ add_task(function* () {
 
   is(propertyObject.values.length, 2,
     "The correct number of property values was retrieved");
 
   // Note that we don't really test the content of the frame object here on
   // purpose. This object comes straight out of the web animations API
   // unmodified.
 
-  yield closeDebuggerClient(client);
+  yield client.close();
   gBrowser.removeCurrentTab();
 });
--- a/devtools/server/tests/browser/browser_animation_getStateAfterFinished.js
+++ b/devtools/server/tests/browser/browser_animation_getStateAfterFinished.js
@@ -45,11 +45,11 @@ add_task(function* () {
 
   is(players[1].state.duration, 300000,
      "The duration of the second animation is correct");
   is(players[1].state.delay, 1000,
      "The delay of the second animation is correct");
   is(players[1].state.iterationCount, 100,
      "The iterationCount of the second animation is correct");
 
-  yield closeDebuggerClient(client);
+  yield client.close();
   gBrowser.removeCurrentTab();
 });
--- a/devtools/server/tests/browser/browser_animation_getSubTreeAnimations.js
+++ b/devtools/server/tests/browser/browser_animation_getSubTreeAnimations.js
@@ -28,11 +28,11 @@ add_task(function* () {
   players = yield animations.getAnimationPlayersForNode(frameBody);
 
   // Testing for a hard-coded number of animations here would intermittently
   // fail depending on how fast or slow the test is (indeed, the test page
   // contains short transitions, and delayed animations). So just make sure we
   // at least have the infinitely running animations.
   ok(players.length >= 4, "All subtree animations were retrieved");
 
-  yield closeDebuggerClient(client);
+  yield client.close();
   gBrowser.removeCurrentTab();
 });
--- a/devtools/server/tests/browser/browser_animation_keepFinished.js
+++ b/devtools/server/tests/browser/browser_animation_keepFinished.js
@@ -38,17 +38,17 @@ add_task(function* () {
   players = yield animations.getAnimationPlayersForNode(node);
   is(players.length, 0, "The added animation is surely finished");
 
   is(reportedMutations.length, 1, "Only one mutation was reported");
   is(reportedMutations[0].type, "added", "The mutation was an addition");
 
   animations.off("mutations", onMutations);
 
-  yield closeDebuggerClient(client);
+  yield client.close();
   gBrowser.removeCurrentTab();
 });
 
 function wait(ms) {
   return new Promise(resolve => {
     setTimeout(resolve, ms);
   });
 }
--- a/devtools/server/tests/browser/browser_animation_playPauseIframe.js
+++ b/devtools/server/tests/browser/browser_animation_playPauseIframe.js
@@ -26,17 +26,17 @@ add_task(function* () {
   yield checkState(animations, nodeInFrame1, "paused");
   yield checkState(animations, nodeInFrame2, "paused");
 
   info("Play all animations in the test document");
   yield animations.playAll();
   yield checkState(animations, nodeInFrame1, "running");
   yield checkState(animations, nodeInFrame2, "running");
 
-  yield closeDebuggerClient(client);
+  yield client.close();
   gBrowser.removeCurrentTab();
 });
 
 function* checkState(animations, nodeFront, playState) {
   info("Getting the AnimationPlayerFront for the test node");
   let [player] = yield animations.getAnimationPlayersForNode(nodeFront);
   yield player.ready;
   let state = yield player.getCurrentState();
--- a/devtools/server/tests/browser/browser_animation_playPauseSeveral.js
+++ b/devtools/server/tests/browser/browser_animation_playPauseSeveral.js
@@ -59,17 +59,17 @@ add_task(function* () {
   yield animations.toggleSeveral([players[0]], false);
   state1 = yield players[0].getCurrentState();
   is(state1.playState, "running",
     "The playState of the first player is running");
   state2 = yield players[1].getCurrentState();
   is(state2.playState, "running",
     "The playState of the second player is running");
 
-  yield closeDebuggerClient(client);
+  yield client.close();
   gBrowser.removeCurrentTab();
 });
 
 function* checkStates(walker, animations, selectors, playState) {
   info("Checking the playState of all the nodes that have infinite running " +
        "animations");
 
   for (let selector of selectors) {
--- a/devtools/server/tests/browser/browser_animation_playerState.js
+++ b/devtools/server/tests/browser/browser_animation_playerState.js
@@ -8,17 +8,17 @@
 
 add_task(function* () {
   let {client, walker, animations} =
     yield initAnimationsFrontForUrl(MAIN_DOMAIN + "animation.html");
 
   yield playerHasAnInitialState(walker, animations);
   yield playerStateIsCorrect(walker, animations);
 
-  yield closeDebuggerClient(client);
+  yield client.close();
   gBrowser.removeCurrentTab();
 });
 
 function* playerHasAnInitialState(walker, animations) {
   let node = yield walker.querySelector(walker.rootNode, ".simple-animation");
   let [player] = yield animations.getAnimationPlayersForNode(node);
 
   ok(player.initialState, "The player front has an initial state");
--- a/devtools/server/tests/browser/browser_animation_reconstructState.js
+++ b/devtools/server/tests/browser/browser_animation_reconstructState.js
@@ -8,17 +8,17 @@
 // state that change, the front reconstructs the whole state everytime.
 
 add_task(function* () {
   let {client, walker, animations} =
     yield initAnimationsFrontForUrl(MAIN_DOMAIN + "animation.html");
 
   yield playerHasCompleteStateAtAllTimes(walker, animations);
 
-  yield closeDebuggerClient(client);
+  yield client.close();
   gBrowser.removeCurrentTab();
 });
 
 function* playerHasCompleteStateAtAllTimes(walker, animations) {
   let node = yield walker.querySelector(walker.rootNode, ".simple-animation");
   let [player] = yield animations.getAnimationPlayersForNode(node);
   yield player.ready();
 
--- a/devtools/server/tests/browser/browser_animation_refreshTransitions.js
+++ b/devtools/server/tests/browser/browser_animation_refreshTransitions.js
@@ -39,17 +39,17 @@ add_task(function* () {
   reportedMutations = yield onMutations;
 
   is(reportedMutations.length, 4, "4 new mutation events were received");
   is(reportedMutations.filter(m => m.type === "removed").length, 2,
     "2 'removed' events were sent (for the old transitions)");
   is(reportedMutations.filter(m => m.type === "added").length, 2,
     "2 'added' events were sent (for the new transitions)");
 
-  yield closeDebuggerClient(client);
+  yield client.close();
   gBrowser.removeCurrentTab();
 });
 
 function expectMutationEvents(animationsFront, nbOfEvents) {
   return new Promise(resolve => {
     let reportedMutations = [];
     function onMutations(mutations) {
       reportedMutations = [...reportedMutations, ...mutations];
--- a/devtools/server/tests/browser/browser_animation_setCurrentTime.js
+++ b/devtools/server/tests/browser/browser_animation_setCurrentTime.js
@@ -9,17 +9,17 @@
 
 add_task(function* () {
   let {client, walker, animations} =
     yield initAnimationsFrontForUrl(MAIN_DOMAIN + "animation.html");
 
   yield testSetCurrentTime(walker, animations);
   yield testSetCurrentTimes(walker, animations);
 
-  yield closeDebuggerClient(client);
+  yield client.close();
   gBrowser.removeCurrentTab();
 });
 
 function* testSetCurrentTime(walker, animations) {
   info("Retrieve an animated node");
   let node = yield walker.querySelector(walker.rootNode, ".simple-animation");
 
   info("Retrieve the animation player for the node");
--- a/devtools/server/tests/browser/browser_animation_setPlaybackRate.js
+++ b/devtools/server/tests/browser/browser_animation_setPlaybackRate.js
@@ -41,11 +41,11 @@ add_task(function* () {
   yield animations.setPlaybackRates(players, .5);
 
   info("Query their states and check they are correct");
   for (let player of players) {
     let state = yield player.getCurrentState();
     is(state.playbackRate, .5, "The playbackRate was updated");
   }
 
-  yield closeDebuggerClient(client);
+  yield client.close();
   gBrowser.removeCurrentTab();
 });
--- a/devtools/server/tests/browser/browser_animation_simple.js
+++ b/devtools/server/tests/browser/browser_animation_simple.js
@@ -25,11 +25,11 @@ add_task(function* () {
   }
   ok(didThrow, "An exception was thrown for a missing NodeActor");
 
   let invalidNode = yield walker.querySelector(walker.rootNode, "title");
   let players = yield animations.getAnimationPlayersForNode(invalidNode);
   ok(Array.isArray(players), "An array of players was returned");
   is(players.length, 0, "0 players have been returned for the invalid node");
 
-  yield closeDebuggerClient(client);
+  yield client.close();
   gBrowser.removeCurrentTab();
 });
--- a/devtools/server/tests/browser/browser_animation_updatedState.js
+++ b/devtools/server/tests/browser/browser_animation_updatedState.js
@@ -7,17 +7,17 @@
 // Check the animation player's updated state
 
 add_task(function* () {
   let {client, walker, animations} =
     yield initAnimationsFrontForUrl(MAIN_DOMAIN + "animation.html");
 
   yield playStateIsUpdatedDynamically(walker, animations);
 
-  yield closeDebuggerClient(client);
+  yield client.close();
   gBrowser.removeCurrentTab();
 });
 
 function* playStateIsUpdatedDynamically(walker, animations) {
   info("Getting the test node (which runs a very long animation)");
   // The animation lasts for 100s, to avoid intermittents.
   let node = yield walker.querySelector(walker.rootNode, ".long-animation");
 
--- a/devtools/server/tests/browser/browser_directorscript_actors.js
+++ b/devtools/server/tests/browser/browser_directorscript_actors.js
@@ -18,17 +18,17 @@ add_task(function* () {
   DirectorRegistry.clear();
   let directorManager = DirectorManagerFront(client, form);
 
   yield testDirectorScriptAttachEventAttributes(directorManager);
   yield testDirectorScriptMessagePort(directorManager);
   yield testDirectorScriptWindowEval(directorManager);
   yield testDirectorScriptUnloadOnDetach(directorManager);
 
-  yield closeDebuggerClient(client);
+  yield client.close();
   gBrowser.removeCurrentTab();
   DirectorRegistry.clear();
 });
 
 function* testDirectorScriptAttachEventAttributes(directorManager) {
   let attachEvent = yield installAndEnableDirectorScript(directorManager, {
     scriptId: "testDirectorScript_attachEventAttributes",
     scriptCode: "(" + (function () {
--- a/devtools/server/tests/browser/browser_directorscript_actors_error_events.js
+++ b/devtools/server/tests/browser/browser_directorscript_actors_error_events.js
@@ -18,17 +18,17 @@ add_task(function* () {
   DirectorRegistry.clear();
   let directorManager = DirectorManagerFront(client, form);
 
   yield testErrorOnRequire(directorManager);
   yield testErrorOnEvaluate(directorManager);
   yield testErrorOnAttach(directorManager);
   yield testErrorOnDetach(directorManager);
 
-  yield closeDebuggerClient(client);
+  yield client.close();
   gBrowser.removeCurrentTab();
   DirectorRegistry.clear();
 });
 
 function* testErrorOnRequire(directorManager) {
   // director script require method should raise a "not implemented" exception
   let errorOnRequire = yield installAndEnableDirectorScript(directorManager, {
     scriptId: "testDirectorScript_errorOnRequire",
--- a/devtools/server/tests/browser/browser_directorscript_actors_exports.js
+++ b/devtools/server/tests/browser/browser_directorscript_actors_exports.js
@@ -52,17 +52,17 @@ add_task(function* () {
     scriptOptions: {
       attachMethod: "attach"
     }
   });
   let { message } = errorUndefinedAttachMethod;
   ok(!!message, "testDirectorScript_undefinedAttachMethod error event received");
   assertIsDirectorScriptError(errorUndefinedAttachMethod);
 
-  yield closeDebuggerClient(client);
+  yield client.close();
   gBrowser.removeCurrentTab();
   DirectorRegistry.clear();
 });
 
 function assertIsDirectorScriptError(error) {
   ok(!!error.message, "errors should contain a message");
   ok(!!error.stack, "errors should contain a stack trace");
   ok(!!error.fileName, "errors should contain a fileName");
--- a/devtools/server/tests/browser/browser_markers-cycle-collection.js
+++ b/devtools/server/tests/browser/browser_markers-cycle-collection.js
@@ -23,11 +23,11 @@ add_task(function* () {
   let rec = yield front.startRecording({ withMarkers: true });
 
   let markers = yield waitForMarkerType(front, ["nsCycleCollector::Collect", "nsCycleCollector::ForgetSkippable"]);
   yield front.stopRecording(rec);
 
   ok(markers.some(m => m.name === "nsCycleCollector::Collect"), "got some nsCycleCollector::Collect markers");
   ok(markers.some(m => m.name === "nsCycleCollector::ForgetSkippable"), "got some nsCycleCollector::Collect markers");
 
-  yield closeDebuggerClient(client);
+  yield client.close();
   gBrowser.removeCurrentTab();
 });
--- a/devtools/server/tests/browser/browser_markers-docloading-01.js
+++ b/devtools/server/tests/browser/browser_markers-docloading-01.js
@@ -27,11 +27,11 @@ add_task(function* () {
   yield waitForMarkerType(front, MARKER_NAMES, () => true, e => e, "markers");
   yield front.stop(rec);
 
   ok(true, "Found the required marker names.");
 
   // Wait some more time to make sure the 'doc-loading' events never get fired.
   yield DevToolsUtils.waitForTime(1000);
 
-  yield closeDebuggerClient(client);
+  yield client.close();
   gBrowser.removeCurrentTab();
 });
--- a/devtools/server/tests/browser/browser_markers-docloading-02.js
+++ b/devtools/server/tests/browser/browser_markers-docloading-02.js
@@ -25,11 +25,11 @@ add_task(function* () {
 
   ok(true, "At least one doc-loading event got fired.");
 
   yield waitForMarkerType(front, MARKER_NAMES, () => true, e => e, "markers");
   yield front.stop(rec);
 
   ok(true, "Found the required marker names.");
 
-  yield closeDebuggerClient(client);
+  yield client.close();
   gBrowser.removeCurrentTab();
 });
--- a/devtools/server/tests/browser/browser_markers-docloading-03.js
+++ b/devtools/server/tests/browser/browser_markers-docloading-03.js
@@ -29,11 +29,11 @@ add_task(function* () {
 
   ok(true, "At least one doc-loading event got fired.");
 
   yield front.stop(rec);
 
   // Wait some more time to make sure the 'doc-loading' markers never get fired.
   yield DevToolsUtils.waitForTime(1000);
 
-  yield closeDebuggerClient(client);
+  yield client.close();
   gBrowser.removeCurrentTab();
 });
--- a/devtools/server/tests/browser/browser_markers-gc.js
+++ b/devtools/server/tests/browser/browser_markers-gc.js
@@ -40,11 +40,11 @@ add_task(function* () {
       ok(false, `markers must be in order. ${current.name} marker has later start time (${current.start}) thanprevious: ${previousStart}`);
       ordered = false;
     }
     return current.start;
   });
 
   is(ordered, true, "All GC and non-GC markers are in order by start time.");
 
-  yield closeDebuggerClient(client);
+  yield client.close();
   gBrowser.removeCurrentTab();
 });
--- a/devtools/server/tests/browser/browser_markers-minor-gc.js
+++ b/devtools/server/tests/browser/browser_markers-minor-gc.js
@@ -22,11 +22,11 @@ add_task(function* () {
   let rec = yield front.startRecording({ withMarkers: true });
 
   let markers = yield waitForMarkerType(front, ["MinorGC"]);
   yield front.stopRecording(rec);
 
   ok(markers.some(m => m.name === "MinorGC" && m.causeName),
      "got some MinorGC markers");
 
-  yield closeDebuggerClient(client);
+  yield client.close();
   gBrowser.removeCurrentTab();
 });
--- a/devtools/server/tests/browser/browser_markers-parse-html.js
+++ b/devtools/server/tests/browser/browser_markers-parse-html.js
@@ -19,11 +19,11 @@ add_task(function* () {
   yield front.connect();
   let rec = yield front.startRecording({ withMarkers: true });
 
   let markers = yield waitForMarkerType(front, MARKER_NAME);
   yield front.stopRecording(rec);
 
   ok(markers.some(m => m.name === MARKER_NAME), `got some ${MARKER_NAME} markers`);
 
-  yield closeDebuggerClient(client);
+  yield client.close();
   gBrowser.removeCurrentTab();
 });
--- a/devtools/server/tests/browser/browser_markers-styles.js
+++ b/devtools/server/tests/browser/browser_markers-styles.js
@@ -24,11 +24,11 @@ add_task(function* () {
   });
 
   yield front.stopRecording(rec);
 
   ok(markers.some(m => m.name === MARKER_NAME), `got some ${MARKER_NAME} markers`);
   ok(markers.some(({restyleHint}) => restyleHint != void 0),
     "Some markers have a restyleHint.");
 
-  yield closeDebuggerClient(client);
+  yield client.close();
   gBrowser.removeCurrentTab();
 });
--- a/devtools/server/tests/browser/browser_markers-timestamp.js
+++ b/devtools/server/tests/browser/browser_markers-timestamp.js
@@ -33,11 +33,11 @@ add_task(function* () {
   ok(markers.length === 2, "found 2 TimeStamp markers");
   ok(markers.every(({start, end}) => typeof start === "number" && start === end),
     "All markers have equal start and end times");
   is(markers[0].causeName, void 0, "Unlabeled timestamps have an empty causeName");
   is(markers[1].causeName, "myLabel", "Labeled timestamps have correct causeName");
 
   pmmClearFrameScripts();
 
-  yield closeDebuggerClient(client);
+  yield client.close();
   gBrowser.removeCurrentTab();
 });
--- a/devtools/server/tests/browser/browser_navigateEvents.js
+++ b/devtools/server/tests/browser/browser_navigateEvents.js
@@ -147,14 +147,14 @@ function test() {
 
   });
 }
 
 function cleanup() {
   let browser = gBrowser.selectedBrowser;
   browser.removeEventListener("DOMContentLoaded", onDOMContentLoaded);
   browser.removeEventListener("load", onLoad);
-  client.close(function () {
+  client.close().then(function () {
     Services.obs.addObserver(httpObserver, "http-on-modify-request", false);
     DebuggerServer.destroy();
     finish();
   });
 }
--- a/devtools/server/tests/browser/browser_perf-allocation-data.js
+++ b/devtools/server/tests/browser/browser_perf-allocation-data.js
@@ -28,11 +28,11 @@ add_task(function* () {
 
   let { frames, timestamps, sizes, sites } = rec.getAllocations();
 
   is(timestamps.length, sizes.length, "we have the same amount of timestamps and sizes");
   ok(timestamps.every(time => time > 0 && typeof time === "number"), "all timestamps have numeric values");
   ok(sizes.every(n => n > 0 && typeof n === "number"), "all sizes are positive numbers");
 
   yield front.destroy();
-  yield closeDebuggerClient(client);
+  yield client.close();
   gBrowser.removeCurrentTab();
 });
--- a/devtools/server/tests/browser/browser_perf-profiler-01.js
+++ b/devtools/server/tests/browser/browser_perf-profiler-01.js
@@ -31,15 +31,15 @@ add_task(function* () {
     "The built-in profiler module should still be active (1).");
 
   rec = yield front.startRecording();
   yield front.stopRecording(rec);
   ok((yield pmmIsProfilerActive()),
     "The built-in profiler module should still be active (2).");
 
   yield front.destroy();
-  yield closeDebuggerClient(client);
+  yield client.close();
 
   ok(!(yield pmmIsProfilerActive()),
     "The built-in profiler module should no longer be active.");
 
   gBrowser.removeCurrentTab();
 });
--- a/devtools/server/tests/browser/browser_perf-profiler-02.js
+++ b/devtools/server/tests/browser/browser_perf-profiler-02.js
@@ -27,20 +27,20 @@ add_task(function* () {
   let secondFront = PerformanceFront(client2, form2);
   yield secondFront.connect();
   pmmLoadFrameScripts(gBrowser);
 
   yield secondFront.startRecording();
 
   // Manually teardown the tabs so we can check profiler status
   yield secondFront.destroy();
-  yield closeDebuggerClient(client2);
+  yield client2.close();
   ok((yield pmmIsProfilerActive()),
     "The built-in profiler module should still be active.");
 
   yield firstFront.destroy();
-  yield closeDebuggerClient(client);
+  yield client.close();
   ok(!(yield pmmIsProfilerActive()),
     "The built-in profiler module should no longer be active.");
 
   gBrowser.removeCurrentTab();
   gBrowser.removeCurrentTab();
 });
--- a/devtools/server/tests/browser/browser_perf-profiler-03.js
+++ b/devtools/server/tests/browser/browser_perf-profiler-03.js
@@ -33,22 +33,22 @@ add_task(function* () {
 
   yield addTab(MAIN_DOMAIN + "doc_perf.html");
   let client2 = new DebuggerClient(DebuggerServer.connectPipe());
   let form2 = yield connectDebuggerClient(client2);
   let secondFront = PerformanceFront(client2, form2);
   yield secondFront.connect();
 
   yield secondFront.destroy();
-  yield closeDebuggerClient(client2);
+  yield client2.close();
   ok((yield pmmIsProfilerActive()),
     "The built-in profiler module should still be active.");
 
   yield firstFront.destroy();
-  yield closeDebuggerClient(client);
+  yield client.close();
   ok(!(yield pmmIsProfilerActive()),
     "The built-in profiler module should have been automatically stopped.");
 
   pmmClearFrameScripts();
 
   gBrowser.removeCurrentTab();
   gBrowser.removeCurrentTab();
 });
--- a/devtools/server/tests/browser/browser_perf-realtime-markers.js
+++ b/devtools/server/tests/browser/browser_perf-realtime-markers.js
@@ -39,17 +39,17 @@ add_task(function* () {
   yield front.stopRecording(rec);
   front.off("timeline-data", handler);
 
   is(counters.markers.length, 1, "one marker event fired.");
   is(counters.memory.length, 3, "three memory events fired.");
   is(counters.ticks.length, 3, "three ticks events fired.");
 
   yield front.destroy();
-  yield closeDebuggerClient(client);
+  yield client.close();
   gBrowser.removeCurrentTab();
 
   function handler(name, data) {
     if (name === "markers") {
       if (counters.markers.length >= 1) { return; }
       ok(data.markers[0].start, "received atleast one marker with `start`");
       ok(data.markers[0].end, "received atleast one marker with `end`");
       ok(data.markers[0].name, "received atleast one marker with `name`");
--- a/devtools/server/tests/browser/browser_perf-recording-actor-01.js
+++ b/devtools/server/tests/browser/browser_perf-recording-actor-01.js
@@ -63,17 +63,17 @@ add_task(function* () {
   ok(importedModel.isCompleted(), "All imported recordings should be completed");
   ok(!importedModel.isRecording(), "All imported recordings should not be recording");
   ok(importedModel.isImported(), "All imported recordings should be considerd imported");
 
   checkSystemInfo(importedModel, "Host");
   checkSystemInfo(importedModel, "Client");
 
   yield front.destroy();
-  yield closeDebuggerClient(client);
+  yield client.close();
   gBrowser.removeCurrentTab();
 });
 
 function checkSystemInfo(recording, type) {
   let data = recording[`get${type}SystemInfo`]();
   for (let field of ["appid", "apptype", "vendor", "name", "version"]) {
     ok(data[field], `get${type}SystemInfo() has ${field} property`);
   }
--- a/devtools/server/tests/browser/browser_perf-recording-actor-02.js
+++ b/devtools/server/tests/browser/browser_perf-recording-actor-02.js
@@ -44,11 +44,11 @@ add_task(function* () {
 
   ok(checkCount >= 1, "atleast 1 event were fired until the buffer was filled");
   is(lastBufferStatus, 1, "buffer usage cannot surpass 100%");
   yield front.stopRecording(model);
 
   is(front.getBufferUsageForRecording(model), null, "buffer usage should be null when no longer recording.");
 
   yield front.destroy();
-  yield closeDebuggerClient(client);
+  yield client.close();
   gBrowser.removeCurrentTab();
 });
--- a/devtools/server/tests/browser/browser_perf-samples-01.js
+++ b/devtools/server/tests/browser/browser_perf-samples-01.js
@@ -53,11 +53,11 @@ add_task(function* () {
     "The second recorded sample times were normalized.");
   ok(secondRecordingSamples[0][TIME_SLOT] > 0,
     "The second recorded sample times were normalized correctly.");
   ok(!secondRecordingSamples.find(e => e[TIME_SLOT] + secondRecordingStartTime <= firstRecording.getDuration()),
     "There should be no samples from the first recording in the second one, " +
     "even though the total number of frames did not overflow.");
 
   yield front.destroy();
-  yield closeDebuggerClient(client);
+  yield client.close();
   gBrowser.removeCurrentTab();
 });
--- a/devtools/server/tests/browser/browser_perf-samples-02.js
+++ b/devtools/server/tests/browser/browser_perf-samples-02.js
@@ -40,17 +40,17 @@ add_task(function* () {
       }
     }
   }
 
   ok(sampleCount > 0,
     "At least some samples have been iterated over, checking for root nodes.");
 
   yield front.destroy();
-  yield closeDebuggerClient(client);
+  yield client.close();
   gBrowser.removeCurrentTab();
 });
 
 /**
  * Inflate a particular sample's stack and return an array of strings.
  */
 function getInflatedStackLocations(thread, sample) {
   let stackTable = thread.stackTable;
--- a/devtools/server/tests/browser/browser_register_actor.js
+++ b/devtools/server/tests/browser/browser_register_actor.js
@@ -27,17 +27,17 @@ function test() {
           var tab = response.tabs[response.selected];
           ok(!!tab.helloActor, "Hello actor must exist");
 
           // Make sure actor's state is maintained across listTabs requests.
           checkActorState(tab.helloActor, () => {
 
             // Clean up
             actorFront.unregister().then(() => {
-              gClient.close(() => {
+              gClient.close().then(() => {
                 DebuggerServer.destroy();
                 gClient = null;
                 finish();
               });
             });
           });
         });
       });
--- a/devtools/server/tests/browser/browser_stylesheets_getTextEmpty.js
+++ b/devtools/server/tests/browser/browser_stylesheets_getTextEmpty.js
@@ -31,10 +31,10 @@ add_task(function* () {
      "getStyleSheets() returned the correct number of sheets");
 
   let sheet = sheets[0];
   yield sheet.update("", false);
   let longStr = yield sheet.getText();
   let source = yield longStr.string();
   is(source, "", "text is empty");
 
-  yield closeDebuggerClient(client);
+  yield client.close();
 });
--- a/devtools/server/tests/browser/browser_stylesheets_nested-iframes.js
+++ b/devtools/server/tests/browser/browser_stylesheets_nested-iframes.js
@@ -29,10 +29,10 @@ add_task(function* () {
 
   // Bug 285395 limits the number of nested iframes to 10. There's one sheet per
   // frame so we should get 10 sheets. However, the limit might change in the
   // future so it's better not to rely on the limit. Asserting > 2 ensures that
   // the test page is actually loading nested iframes and this test is doing
   // something sensible (if we got this far, the test has served its purpose).
   ok(sheets.length > 2, sheets.length + " sheets found (expected 3 or more).");
 
-  yield closeDebuggerClient(client);
+  yield client.close();
 });
--- a/devtools/server/tests/browser/browser_timeline.js
+++ b/devtools/server/tests/browser/browser_timeline.js
@@ -53,11 +53,11 @@ add_task(function* () {
 
   ok(markers.length > 0, "markers were returned");
 
   yield front.stop();
 
   isActive = yield front.isRecording();
   ok(!isActive, "Not recording after stop()");
 
-  yield closeDebuggerClient(client);
+  yield client.close();
   gBrowser.removeCurrentTab();
 });
--- a/devtools/server/tests/browser/browser_timeline_actors.js
+++ b/devtools/server/tests/browser/browser_timeline_actors.js
@@ -41,17 +41,17 @@ add_task(function* () {
 
   ok((yield waitUntil(() => updatedMemory > 1)),
     "Some memory measurements were emitted.");
   ok((yield waitUntil(() => updatedTicks > 1)),
     "Some refresh driver ticks were emitted.");
 
   info("Stop timeline marker recording");
   yield front.stop();
-  yield closeDebuggerClient(client);
+  yield client.close();
   gBrowser.removeCurrentTab();
 });
 
 /**
  * Waits until a predicate returns true.
  *
  * @param function predicate
  *        Invoked once in a while until it returns true.
--- a/devtools/server/tests/browser/browser_timeline_iframes.js
+++ b/devtools/server/tests/browser/browser_timeline_iframes.js
@@ -26,16 +26,16 @@ add_task(function* () {
   for (let i = 0; i < 3; i++) {
     yield wait(300); // That's the time the child frame waits before changing styles.
     let markers = yield once(front, "markers");
     ok(markers.length, "Markers were received for operations in the child frame");
   }
 
   info("Stop timeline marker recording");
   yield front.stop();
-  yield closeDebuggerClient(client);
+  yield client.close();
   gBrowser.removeCurrentTab();
 });
 
 function wait(ms) {
   return new Promise(resolve =>
     setTimeout(resolve, ms));
 }
--- a/devtools/server/tests/browser/head.js
+++ b/devtools/server/tests/browser/head.js
@@ -78,25 +78,16 @@ function connectDebuggerClient(client) {
   return client.connect()
     .then(() => client.listTabs())
     .then(tabs => {
       return tabs.tabs[tabs.selected];
     });
 }
 
 /**
- * Close a debugger client's connection.
- * @param {DebuggerClient}
- * @return {Promise} Resolves when the connection is closed.
- */
-function closeDebuggerClient(client) {
-  return new Promise(resolve => client.close(resolve));
-}
-
-/**
  * Wait for eventName on target.
  * @param {Object} target An observable object that either supports on/off or
  * addEventListener/removeEventListener
  * @param {String} eventName
  * @param {Boolean} useCapture Optional, for addEventListener/removeEventListener
  * @return A promise that resolves when the event has been handled
  */
 function once(target, eventName, useCapture = false) {
--- a/devtools/server/tests/mochitest/memory-helpers.js
+++ b/devtools/server/tests/mochitest/memory-helpers.js
@@ -27,17 +27,17 @@ function startServerAndGetSelectedTabMem
       var form = response.tabs[response.selected];
       var memory = MemoryFront(client, form, response);
 
       return { memory, client };
     });
 }
 
 function destroyServerAndFinish(client) {
-  client.close(() => {
+  client.close().then(() => {
     DebuggerServer.destroy();
     SimpleTest.finish();
   });
 }
 
 function waitForTime(ms) {
   return new Promise((resolve, reject) => {
     setTimeout(resolve, ms);
--- a/devtools/server/tests/mochitest/test_device.html
+++ b/devtools/server/tests/mochitest/test_device.html
@@ -72,17 +72,17 @@ window.onload = function() {
         var profileDir = currProfD.path;
         ok(profileDir.indexOf(desc.profile.length > 0 && desc.profile) != -1, "valid profile name");
 
         var a = JSON.stringify(PermissionsTable);
         var b = JSON.stringify(permissions.rawPermissionsTable);
 
         is(a, b, "Permissions Tables is valid");
 
-        client.close(() => {
+        client.close().then(() => {
           DebuggerServer.destroy();
           SimpleTest.finish()
         });
       }
 
 
       d.getDescription().then((v) => desc = v)
       .then(() => d.getRawPermissionsTable())
--- a/devtools/server/tests/mochitest/test_framerate_01.html
+++ b/devtools/server/tests/mochitest/test_framerate_01.html
@@ -124,17 +124,17 @@ window.onload = function() {
         "The start and end framerate values should be equal.");
 
       is(typeof currFramerateStart, "number", "All values should be numbers.");
       ok(currFramerateStart <= 60, "All values were correctly clamped.")
 
       prevFramerateValue = currFramerateStart;
     }
 
-    client.close(() => {
+    client.close().then(() => {
       DebuggerServer.destroy();
       SimpleTest.finish()
     });
   }
 }
 </script>
 </pre>
 </body>
--- a/devtools/server/tests/mochitest/test_framerate_02.html
+++ b/devtools/server/tests/mochitest/test_framerate_02.html
@@ -94,17 +94,17 @@ window.onload = function() {
         is(timeline[0].value, 0,
           "The first framerate value should be 0.");
 
         is(timeline[1].delta, 100,
           "The last time delta should be 100 (the default interval value).");
         is(timeline[1].value, 0,
           "The last framerate value should be 0.");
 
-        client.close(() => {
+        client.close().then(() => {
           DebuggerServer.destroy();
           SimpleTest.finish()
         });
       });
     });
   });
 }
 </script>
--- a/devtools/server/tests/mochitest/test_framerate_03.html
+++ b/devtools/server/tests/mochitest/test_framerate_03.html
@@ -65,17 +65,17 @@ window.onload = function() {
     ok(!rawData.find(e => e > STOP_TICK),
       "There should be no tick after 3000ms.");
 
     for (var tick of rawData) {
       info("Testing tick: " + tick);
       is(typeof tick, "number", "All values should be numbers.");
     }
 
-    client.close(() => {
+    client.close().then(() => {
       DebuggerServer.destroy();
       SimpleTest.finish()
     });
   }
 }
 </script>
 </pre>
 </body>
--- a/devtools/server/tests/mochitest/test_framerate_04.html
+++ b/devtools/server/tests/mochitest/test_framerate_04.html
@@ -54,17 +54,17 @@ window.onload = function() {
     info("Difference in ticks: " + diff);
     ok(diff > 0, "More ticks should be recorded in the second batch.");
 
     ok(firstBatch.every((e) => secondBatch.indexOf(e) != -1),
       "All the ticks in the first batch should be in the second batch as well.");
     ok(secondBatch.every((e, i, array) => i < array.length - 1 ? e < array[i + 1] : true),
       "All the ticks in the final batch should be ascending in value.");
 
-    client.close(() => {
+    client.close().then(() => {
       DebuggerServer.destroy();
       SimpleTest.finish()
     });
   }
 }
 </script>
 </pre>
 <a id="testContent" target="_blank" href="inspector_getImageData.html">Test Document</a>
--- a/devtools/server/tests/mochitest/test_framerate_05.html
+++ b/devtools/server/tests/mochitest/test_framerate_05.html
@@ -53,17 +53,17 @@ window.onload = function() {
                   "The returned pending ticks should be empty (2).");
 
                 front.stopRecording().then(rawData => {
                   ok(rawData,
                     "The returned raw data should be an empty array (1).");
                   is(rawData.length, 0,
                     "The returned raw data should be an empty array (2).");
 
-                  client.close(() => {
+                  client.close().then(() => {
                     DebuggerServer.destroy();
                     SimpleTest.finish()
                   });
                 });
               });
             }, 1000);
           });
         }, 1000);
--- a/devtools/server/tests/mochitest/test_framerate_06.html
+++ b/devtools/server/tests/mochitest/test_framerate_06.html
@@ -64,17 +64,17 @@ window.onload = function() {
     // when we get ticks from other frames they're usually at diffs of < 1. Sometimes
     // ticks can still be less than 16ms even on one frame (usually following a very slow
     // frame), so use a low number (2) to be our threshold
     var THRESHOLD = 2;
     ok(ticks.length >= 20, "we should have atleast 20 ticks over the course of two seconds.");
     var belowThreshold = diffs.filter(v => v <= THRESHOLD);
     ok(belowThreshold.length <= 10, "we should have very few frames less than the threshold");
 
-    client.close(() => {
+    client.close().then(() => {
       DebuggerServer.destroy();
       SimpleTest.finish()
     });
   }
 }
 </script>
 </pre>
 <a id="testContent" target="_blank" href="inspector-traversal-data.html">Test Document</a>
--- a/devtools/server/tests/mochitest/test_getProcess.html
+++ b/devtools/server/tests/mochitest/test_getProcess.html
@@ -99,17 +99,17 @@ function runTests() {
           ok(response.result, 42, "console.eval worked");
           cleanup();
         });
       });
     });
   }
 
   function cleanup() {
-    client.close(function () {
+    client.close().then(function () {
       DebuggerServer.destroy();
       iframe.remove();
       SimpleTest.finish()
     });
   }
 
   connect();
 }
--- a/devtools/server/tests/mochitest/test_preference.html
+++ b/devtools/server/tests/mochitest/test_preference.html
@@ -70,17 +70,17 @@ function runTests() {
           is(prefs.allPrefs[key].hasUserValue, Services.prefs.prefHasUserValue(key), "valid hasUserValue (" + key + ")");
         });
 
         ["test.bool", "test.int", "test.string"].forEach(function(key) {
           ok(!prefs.allPrefs.hasOwnProperty(key), "expect no pref (" + key + ")");
           is(Services.prefs.getPrefType(key), Ci.nsIPrefBranch.PREF_INVALID, "pref (" + key + ") is clear");
         });
 
-        client.close(() => {
+        client.close().then(() => {
           DebuggerServer.destroy();
           SimpleTest.finish()
         });
       }
 
 
       p.getAllPrefs().then((json) => prefs["allPrefs"]  = json)
       .then(() => p.setBoolPref("test.bool", localPref.boolPref))
--- a/devtools/server/tests/mochitest/test_settings.html
+++ b/devtools/server/tests/mochitest/test_settings.html
@@ -76,17 +76,17 @@ function runTests() {
         is(settings["app.reportCrashes"], localSetting["app.reportCrashes"], "updated string setting");
         is(JSON.stringify(settings["app.someObject"]), JSON.stringify(localSetting["app.someObject"]), "updated object as string setting");
 
         is(resetSettings["wifi.enabled"], fakeSettings["wifi.enabled"], "reset to original bool setting");
         is(resetSettings["audio.volume.alarm"], fakeSettings["audio.volume.alarm"], "reset to original int setting");
         is(resetSettings["app.reportCrashes"], fakeSettings["app.reportCrashes"], "reset to original string setting");
         is(JSON.stringify(resetSettings["app.someObject"]), JSON.stringify(fakeSettings["app.someObject"]), "reset to original object setting");
 
-        client.close(() => {
+        client.close().then(() => {
           DebuggerServer.destroy();
           SimpleTest.finish();
         });
       }
 
       // settings.json doesn't exist outside of b2g so we will fake it.
       _setDefaultSettings(fakeSettings);
       s.setSetting("wifi.enabled", fakeSettings["wifi.enabled"])
--- a/devtools/server/tests/mochitest/test_setupInParentChild.html
+++ b/devtools/server/tests/mochitest/test_setupInParentChild.html
@@ -91,17 +91,17 @@ function runTests() {
     DebuggerServer.setupInChild({
       module: "chrome://mochitests/content/chrome/devtools/server/tests/mochitest/setup-in-child.js",
       setupChild: "setupChild",
       args: [1, "two", {three: true}]
     });
   });
 
   function cleanup() {
-    client.close(function () {
+    client.close().then(function () {
       DebuggerServer.destroy();
       iframe.remove();
       SimpleTest.finish()
     });
   }
 
 }
 </script>
--- a/devtools/server/tests/unit/head_dbg.js
+++ b/devtools/server/tests/unit/head_dbg.js
@@ -159,21 +159,17 @@ function createTestGlobal(name) {
 
 function connect(client) {
   dump("Connecting client.\n");
   return client.connect();
 }
 
 function close(client) {
   dump("Closing client.\n");
-  return new Promise(function (resolve) {
-    client.close(function () {
-      resolve();
-    });
-  });
+  return client.close();
 }
 
 function listTabs(client) {
   dump("Listing tabs.\n");
   return client.listTabs();
 }
 
 function findTab(tabs, title) {
--- a/devtools/server/tests/unit/test_add_actors.js
+++ b/devtools/server/tests/unit/test_add_actors.js
@@ -98,10 +98,10 @@ function test_stable_global_actor_instan
   gClient.listTabs(function onListTabs(aResponse) {
     do_check_eq(postInitGlobalActor, getActorInstance(connID, aResponse.postInitGlobalActor));
     do_check_eq(preInitGlobalActor, getActorInstance(connID, aResponse.preInitGlobalActor));
     run_next_test();
   });
 }
 
 function close_client() {
-  gClient.close(() => run_next_test());
+  gClient.close().then(() => run_next_test());
 }
--- a/devtools/server/tests/unit/test_breakpoint-01.js
+++ b/devtools/server/tests/unit/test_breakpoint-01.js
@@ -49,17 +49,17 @@ function test_simple_breakpoint()
         do_check_eq(aPacket.why.actors[0], bpClient.actor);
         // Check that the breakpoint worked.
         do_check_eq(gDebuggee.a, 1);
         do_check_eq(gDebuggee.b, undefined);
 
         // Remove the breakpoint.
         bpClient.remove(function (aResponse) {
           gThreadClient.resume(function () {
-            gClient.close(gCallback);
+            gClient.close().then(gCallback);
           });
         });
 
       });
 
       // Continue until the breakpoint is hit.
       gThreadClient.resume();
     });
--- a/devtools/server/tests/unit/test_breakpoint-02.js
+++ b/devtools/server/tests/unit/test_breakpoint-02.js
@@ -47,17 +47,17 @@ function test_breakpoint_running()
 
     let source = gThreadClient.source(aPacket.frame.where.source);
     source.setBreakpoint(location, function (aResponse) {
       // Eval scripts don't stick around long enough for the breakpoint to be set,
       // so just make sure we got the expected response from the actor.
       do_check_neq(aResponse.error, "noScript");
 
       do_execute_soon(function () {
-        gClient.close(gCallback);
+        gClient.close().then(gCallback);
       });
     });
   });
 
   Cu.evalInSandbox(
     "var line0 = Error().lineNumber;\n" +
     "debugger;\n" +
     "var a = 1;\n" +  // line0 + 2
--- a/devtools/server/tests/unit/test_breakpoint-03.js
+++ b/devtools/server/tests/unit/test_breakpoint-03.js
@@ -57,17 +57,17 @@ function test_skip_breakpoint()
         do_check_eq(aPacket.why.actors[0], bpClient.actor);
         // Check that the breakpoint worked.
         do_check_eq(gDebuggee.a, 1);
         do_check_eq(gDebuggee.b, undefined);
 
         // Remove the breakpoint.
         bpClient.remove(function (aResponse) {
           gThreadClient.resume(function () {
-            gClient.close(gCallback);
+            gClient.close().then(gCallback);
           });
         });
       });
 
       gThreadClient.resume();
     });
   });
 
--- a/devtools/server/tests/unit/test_breakpoint-04.js
+++ b/devtools/server/tests/unit/test_breakpoint-04.js
@@ -51,17 +51,17 @@ function test_child_breakpoint()
         do_check_eq(aPacket.why.actors[0], bpClient.actor);
         // Check that the breakpoint worked.
         do_check_eq(gDebuggee.a, 1);
         do_check_eq(gDebuggee.b, undefined);
 
         // Remove the breakpoint.
         bpClient.remove(function (aResponse) {
           gThreadClient.resume(function () {
-            gClient.close(gCallback);
+            gClient.close().then(gCallback);
           });
         });
       });
 
       // Continue until the breakpoint is hit.
       gThreadClient.resume();
     });
 
--- a/devtools/server/tests/unit/test_breakpoint-05.js
+++ b/devtools/server/tests/unit/test_breakpoint-05.js
@@ -53,17 +53,17 @@ function test_child_skip_breakpoint()
         do_check_eq(aPacket.why.actors[0], bpClient.actor);
         // Check that the breakpoint worked.
         do_check_eq(gDebuggee.a, 1);
         do_check_eq(gDebuggee.b, undefined);
 
         // Remove the breakpoint.
         bpClient.remove(function (aResponse) {
           gThreadClient.resume(function () {
-            gClient.close(gCallback);
+            gClient.close().then(gCallback);
           });
         });
       });
 
       // Continue until the breakpoint is hit.
       gThreadClient.resume();
     });
   });
--- a/devtools/server/tests/unit/test_breakpoint-06.js
+++ b/devtools/server/tests/unit/test_breakpoint-06.js
@@ -53,17 +53,17 @@ function test_nested_breakpoint()
         do_check_eq(aPacket.why.actors[0], bpClient.actor);
         // Check that the breakpoint worked.
         do_check_eq(gDebuggee.a, 1);
         do_check_eq(gDebuggee.b, undefined);
 
         // Remove the breakpoint.
         bpClient.remove(function (aResponse) {
           gThreadClient.resume(function () {
-            gClient.close(gCallback);
+            gClient.close().then(gCallback);
           });
         });
       });
 
       // Continue until the breakpoint is hit.
       gThreadClient.resume();
     });
 
--- a/devtools/server/tests/unit/test_breakpoint-07.js
+++ b/devtools/server/tests/unit/test_breakpoint-07.js
@@ -53,17 +53,17 @@ function test_second_child_skip_breakpoi
         do_check_eq(aPacket.why.actors[0], bpClient.actor);
         // Check that the breakpoint worked.
         do_check_eq(gDebuggee.a, 1);
         do_check_eq(gDebuggee.b, undefined);
 
         // Remove the breakpoint.
         bpClient.remove(function (aResponse) {
           gThreadClient.resume(function () {
-            gClient.close(gCallback);
+            gClient.close().then(gCallback);
           });
         });
       });
 
       // Continue until the breakpoint is hit.
       gThreadClient.resume();
     });
   });
--- a/devtools/server/tests/unit/test_breakpoint-08.js
+++ b/devtools/server/tests/unit/test_breakpoint-08.js
@@ -61,17 +61,17 @@ function test_child_skip_breakpoint()
           do_check_eq(aPacket.why.actors[0], bpClient.actor);
           // Check that the breakpoint worked.
           do_check_eq(gDebuggee.a, 1);
           do_check_eq(gDebuggee.b, undefined);
 
           // Remove the breakpoint.
           bpClient.remove(function (aResponse) {
             gThreadClient.resume(function () {
-              gClient.close(gCallback);
+              gClient.close().then(gCallback);
             });
           });
         });
 
         // Continue until the breakpoint is hit.
         gThreadClient.resume();
       });
     }
--- a/devtools/server/tests/unit/test_breakpoint-09.js
+++ b/devtools/server/tests/unit/test_breakpoint-09.js
@@ -79,10 +79,10 @@ function test_remove_breakpoint()
                    "  foo(true);\n" +         // line0 + 5
                    "}\n" +                    // line0 + 6
                    "debugger;\n" +            // line1 + 7
                    "foo();\n",                // line1 + 8
                    gDebuggee);
   if (!done) {
     do_check_true(false);
   }
-  gClient.close(gCallback);
+  gClient.close().then(gCallback);
 }
--- a/devtools/server/tests/unit/test_breakpoint-10.js
+++ b/devtools/server/tests/unit/test_breakpoint-10.js
@@ -57,17 +57,17 @@ function test_child_breakpoint()
           do_check_eq(aPacket.why.type, "breakpoint");
           do_check_eq(aPacket.why.actors[0], bpClient.actor);
           // Check that the breakpoint worked.
           do_check_eq(gDebuggee.i, 1);
 
           // Remove the breakpoint.
           bpClient.remove(function (aResponse) {
             gThreadClient.resume(function () {
-              gClient.close(gCallback);
+              gClient.close().then(gCallback);
             });
           });
         });
 
         // Continue until the breakpoint is hit again.
         gThreadClient.resume();
 
       });
--- a/devtools/server/tests/unit/test_breakpoint-11.js
+++ b/devtools/server/tests/unit/test_breakpoint-11.js
@@ -58,17 +58,17 @@ function test_child_breakpoint()
           do_check_eq(aPacket.why.actors[0], bpClient.actor);
           // Check that the breakpoint worked.
           do_check_eq(gDebuggee.a.b, 1);
           do_check_eq(gDebuggee.res, undefined);
 
           // Remove the breakpoint.
           bpClient.remove(function (aResponse) {
             gThreadClient.resume(function () {
-              gClient.close(gCallback);
+              gClient.close().then(gCallback);
             });
           });
         });
 
         // Continue until the breakpoint is hit again.
         gThreadClient.resume();
 
       });
--- a/devtools/server/tests/unit/test_breakpoint-12.js
+++ b/devtools/server/tests/unit/test_breakpoint-12.js
@@ -96,17 +96,17 @@ function set_breakpoints(source, locatio
 
       gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) {
         // We don't expect any more pauses after the breakpoint was hit once.
         do_check_true(false);
       });
       gThreadClient.resume(function () {
         // Give any remaining breakpoints a chance to trigger.
         do_timeout(1000, function () {
-          gClient.close(gCallback);
+          gClient.close().then(gCallback);
         });
       });
 
     });
     // Continue until the breakpoint is hit.
     gThreadClient.resume();
   });
 
--- a/devtools/server/tests/unit/test_breakpoint-13.js
+++ b/devtools/server/tests/unit/test_breakpoint-13.js
@@ -94,17 +94,17 @@ function test_simple_breakpoint()
         let packet = yield waiter;
         callback(packet);
       }
 
       // Remove the breakpoint and finish.
       let waiter = waitForPause(gThreadClient);
       gThreadClient.stepIn();
       yield waiter;
-      bpClient.remove(() => gThreadClient.resume(() => gClient.close(gCallback)));
+      bpClient.remove(() => gThreadClient.resume(() => gClient.close().then(gCallback)));
     }));
   });
 
   Cu.evalInSandbox("var line0 = Error().lineNumber;\n" +
                    "function foo() {\n" + // line0 + 1
                    "  this.a = 1;\n" +    // line0 + 2 <-- Breakpoint is set here.
                    "}\n" +                // line0 + 3
                    "debugger;\n" +        // line0 + 4
--- a/devtools/server/tests/unit/test_breakpoint-14.js
+++ b/devtools/server/tests/unit/test_breakpoint-14.js
@@ -92,17 +92,17 @@ function test_simple_breakpoint()
         let packet = yield waiter;
         callback(packet);
       }
 
       // Remove the breakpoint and finish.
       let waiter = waitForPause(gThreadClient);
       gThreadClient.stepOver();
       yield waiter;
-      bpClient.remove(() => gThreadClient.resume(() => gClient.close(gCallback)));
+      bpClient.remove(() => gThreadClient.resume(() => gClient.close().then(gCallback)));
     }));
   });
 
   Cu.evalInSandbox("var line0 = Error().lineNumber;\n" +
                    "function foo() {\n" + // line0 + 1
                    "  this.a = 1;\n" +    // line0 + 2 <-- Breakpoint is set here.
                    "}\n" +                // line0 + 3
                    "debugger;\n" +        // line0 + 4
--- a/devtools/server/tests/unit/test_breakpoint-16.js
+++ b/devtools/server/tests/unit/test_breakpoint-16.js
@@ -56,17 +56,17 @@ function test_column_breakpoint()
 
         do_check_eq(gDebuggee.acc, timesBreakpointHit);
         do_check_eq(aPacket.frame.environment.bindings.variables.i.value,
                     timesBreakpointHit);
 
         if (++timesBreakpointHit === 3) {
           gThreadClient.removeListener("paused", onPaused);
           bpClient.remove(function (aResponse) {
-            gThreadClient.resume(() => gClient.close(gCallback));
+            gThreadClient.resume(() => gClient.close().then(gCallback));
           });
         } else {
           gThreadClient.resume();
         }
       });
 
       // Continue until the breakpoint is hit.
       gThreadClient.resume();
--- a/devtools/server/tests/unit/test_breakpoint-17.js
+++ b/devtools/server/tests/unit/test_breakpoint-17.js
@@ -103,17 +103,17 @@ function test_remove_one(aFirst, aSecond
         return;
       }
 
       if (why.type == "debuggerStatement") {
         gClient.removeListener("paused", _onPaused);
         do_check_true(hitSecond,
                       "We should still hit `second`, but not `first`.");
 
-        gClient.close(gCallback);
+        gClient.close().then(gCallback);
         return;
       }
 
       do_check_true(false, "Should never get here");
     });
 
     gThreadClient.resume(() => gDebuggee.foo());
   });
--- a/devtools/server/tests/unit/test_breakpoint-18.js
+++ b/devtools/server/tests/unit/test_breakpoint-18.js
@@ -73,10 +73,10 @@ function testBPHit(event, { why }) {
 }
 
 function testDbgStatement(event, { why }) {
   // Should continue to the debugger statement.
   do_check_eq(why.type, "debuggerStatement");
   // Not break on another offset from the same line (that isn't an entry point
   // to the line)
   do_check_neq(why.type, "breakpoint");
-  gClient.close(gCallback);
+  gClient.close().then(gCallback);
 }
--- a/devtools/server/tests/unit/test_client_close.js
+++ b/devtools/server/tests/unit/test_client_close.js
@@ -24,16 +24,16 @@ function test_close(aTransport)
 {
   // Check that, if we fake a transport shutdown
   // (like if a device is unplugged)
   // the client is automatically closed,
   // and we can still call client.close.
   let onClosed = function () {
     gClient.removeListener("closed", onClosed);
     ok(true, "Client emitted 'closed' event");
-    gClient.close(function () {
+    gClient.close().then(function () {
       ok(true, "client.close() successfully called its callback");
       do_test_finished();
     });
   };
   gClient.addListener("closed", onClosed);
   aTransport.close();
 }
--- a/devtools/server/tests/unit/test_client_request.js
+++ b/devtools/server/tests/unit/test_client_request.js
@@ -155,17 +155,17 @@ function test_close_client_while_sending
 
   let expectReply = promise.defer();
   gClient.expectReply("root", function (response) {
     do_check_eq(response.error, "connectionClosed");
     do_check_eq(response.message, "server side packet can't be received as the connection just closed.");
     expectReply.resolve();
   });
 
-  gClient.close(() => {
+  gClient.close().then(() => {
     activeRequest.then(() => {
       ok(false, "First request unexpectedly succeed while closing the connection");
     }, response => {
       do_check_eq(response.error, "connectionClosed");
       do_check_eq(response.message, "'hello' active request packet to '" + gActorId + "' can't be sent as the connection just closed.");
     })
     .then(() => pendingRequest)
     .then(() => {
--- a/devtools/server/tests/unit/test_memory_footprint.js
+++ b/devtools/server/tests/unit/test_memory_footprint.js
@@ -46,10 +46,10 @@ function connect_client() {
 
 function list_tabs() {
   gClient.listTabs(function onListTabs(aResponse) {
     check_footprint("DebuggerClient.listTabs()", 3800);
   });
 }
 
 function close_client() {
-  gClient.close(run_next_test);
+  gClient.close().then(run_next_test);
 }
--- a/devtools/server/tests/unit/test_objectgrips-01.js
+++ b/devtools/server/tests/unit/test_objectgrips-01.js
@@ -42,17 +42,17 @@ function test_object_grip()
     let objClient = gThreadClient.pauseGrip(args[0]);
     objClient.getOwnPropertyNames(function (aResponse) {
       do_check_eq(aResponse.ownPropertyNames.length, 3);
       do_check_eq(aResponse.ownPropertyNames[0], "a");
       do_check_eq(aResponse.ownPropertyNames[1], "b");
       do_check_eq(aResponse.ownPropertyNames[2], "c");
 
       gThreadClient.resume(function () {
-        gClient.close(gCallback);
+        gClient.close().then(gCallback);
       });
     });
 
   });
 
   gDebuggee.eval("stopMe({ a: 1, b: true, c: 'foo' })");
 }
 
--- a/devtools/server/tests/unit/test_objectgrips-02.js
+++ b/devtools/server/tests/unit/test_objectgrips-02.js
@@ -45,17 +45,17 @@ function test_object_grip()
 
       let protoClient = gThreadClient.pauseGrip(aResponse.prototype);
       protoClient.getOwnPropertyNames(function (aResponse) {
         do_check_eq(aResponse.ownPropertyNames.length, 2);
         do_check_eq(aResponse.ownPropertyNames[0], "b");
         do_check_eq(aResponse.ownPropertyNames[1], "c");
 
         gThreadClient.resume(function () {
-          gClient.close(gCallback);
+          gClient.close().then(gCallback);
         });
       });
     });
 
   });
 
   gDebuggee.eval(function Constr() {
     this.a = 1;
--- a/devtools/server/tests/unit/test_objectgrips-03.js
+++ b/devtools/server/tests/unit/test_objectgrips-03.js
@@ -55,17 +55,17 @@ function test_object_grip()
         objClient.getProperty("a", function (aResponse) {
           do_check_eq(aResponse.descriptor.configurable, true);
           do_check_eq(aResponse.descriptor.enumerable, true);
           do_check_eq(aResponse.descriptor.get.type, "object");
           do_check_eq(aResponse.descriptor.get.class, "Function");
           do_check_eq(aResponse.descriptor.set.type, "undefined");
 
           gThreadClient.resume(function () {
-            gClient.close(gCallback);
+            gClient.close().then(gCallback);
           });
         });
       });
     });
 
   });
 
   gDebuggee.eval("stopMe({ x: 10, y: 'kaiju', get a() { return 42; } })");
--- a/devtools/server/tests/unit/test_objectgrips-04.js
+++ b/devtools/server/tests/unit/test_objectgrips-04.js
@@ -59,17 +59,17 @@ function test_object_grip()
 
       do_check_true(aResponse.prototype != undefined);
 
       let protoClient = gThreadClient.pauseGrip(aResponse.prototype);
       protoClient.getOwnPropertyNames(function (aResponse) {
         do_check_true(aResponse.ownPropertyNames.toString != undefined);
 
         gThreadClient.resume(function () {
-          gClient.close(gCallback);
+          gClient.close().then(gCallback);
         });
       });
     });
 
   });
 
   gDebuggee.eval("stopMe({ x: 10, y: 'kaiju', get a() { return 42; } })");
 }
--- a/devtools/server/tests/unit/test_objectgrips-05.js
+++ b/devtools/server/tests/unit/test_objectgrips-05.js
@@ -48,17 +48,17 @@ function test_object_grip()
 
     let obj2 = aPacket.frame.arguments[1];
     do_check_false(obj2.frozen);
 
     let obj2Client = gThreadClient.pauseGrip(obj2);
     do_check_false(obj2Client.isFrozen);
 
     gThreadClient.resume(_ => {
-      gClient.close(gCallback);
+      gClient.close().then(gCallback);
     });
   });
 
   gDebuggee.eval("(" + function () {
     let obj1 = {};
     Object.freeze(obj1);
     stopMe(obj1, {});
   } + "())");
--- a/devtools/server/tests/unit/test_objectgrips-06.js
+++ b/devtools/server/tests/unit/test_objectgrips-06.js
@@ -48,17 +48,17 @@ function test_object_grip()
 
     let obj2 = aPacket.frame.arguments[1];
     do_check_false(obj2.sealed);
 
     let obj2Client = gThreadClient.pauseGrip(obj2);
     do_check_false(obj2Client.isSealed);
 
     gThreadClient.resume(_ => {
-      gClient.close(gCallback);
+      gClient.close().then(gCallback);
     });
   });
 
   gDebuggee.eval("(" + function () {
     let obj1 = {};
     Object.seal(obj1);
     stopMe(obj1, {});
   } + "())");
--- a/devtools/server/tests/unit/test_objectgrips-07.js
+++ b/devtools/server/tests/unit/test_objectgrips-07.js
@@ -52,17 +52,17 @@ function test_object_grip()
 
     do_check_false(ne.extensible);
     do_check_false(neClient.isExtensible);
 
     do_check_true(e.extensible);
     do_check_true(eClient.isExtensible);
 
     gThreadClient.resume(_ => {
-      gClient.close(gCallback);
+      gClient.close().then(gCallback);
     });
   });
 
   gDebuggee.eval("(" + function () {
     let f = {};
     Object.freeze(f);
     let s = {};
     Object.seal(s);
--- a/devtools/server/tests/unit/test_objectgrips-08.js
+++ b/devtools/server/tests/unit/test_objectgrips-08.js
@@ -57,16 +57,16 @@ function test_object_grip()
       do_check_eq(aResponse.ownProperties.c.value.type, "NaN");
 
       do_check_eq(aResponse.ownProperties.d.configurable, true);
       do_check_eq(aResponse.ownProperties.d.enumerable, true);
       do_check_eq(aResponse.ownProperties.d.writable, true);
       do_check_eq(aResponse.ownProperties.d.value.type, "-0");
 
       gThreadClient.resume(function () {
-        gClient.close(gCallback);
+        gClient.close().then(gCallback);
       });
     });
   });
 
   gDebuggee.eval("stopMe({ a: Infinity, b: -Infinity, c: NaN, d: -0 })");
 }
 
--- a/devtools/server/tests/unit/test_objectgrips-09.js
+++ b/devtools/server/tests/unit/test_objectgrips-09.js
@@ -58,17 +58,17 @@ function test_object_grip()
       do_check_eq(obj2.ownProperties.z.enumerable, true);
       do_check_eq(obj2.ownProperties.z.writable, true);
       do_check_eq(obj2.ownProperties.z.value, 123);
 
       do_check_true(obj1.prototype != undefined);
       do_check_true(obj2.prototype != undefined);
 
       gThreadClient.resume(function () {
-        gClient.close(gCallback);
+        gClient.close().then(gCallback);
       });
     });
 
   });
 
   gDebuggee.eval("stopMe({ x: 10, y: 'kaiju'}, { z: 123 })");
 }
 
--- a/devtools/server/tests/unit/test_profiler_activation-02.js
+++ b/devtools/server/tests/unit/test_profiler_activation-02.js
@@ -16,17 +16,17 @@ function run_test()
   // Ensure the profiler is already running when the test starts.
   Profiler.StartProfiler(1000000, 1, ["js"], 1);
 
   DevToolsUtils.waitForTime(WAIT_TIME).then(() => {
 
     get_chrome_actors((client, form) => {
       let actor = form.profilerActor;
       test_start_time(client, actor, () => {
-        client.close(do_test_finished);
+        client.close().then(do_test_finished);
       });
     });
   });
 
   do_test_pending();
 }
 
 function test_start_time(client, actor, callback) {
--- a/devtools/server/tests/unit/test_profiler_bufferstatus.js
+++ b/devtools/server/tests/unit/test_profiler_bufferstatus.js
@@ -20,17 +20,17 @@ function run_test()
 
   get_chrome_actors((client, form) => {
     let actor = form.profilerActor;
     check_empty_buffer(client, actor, () => {
       activate_profiler(client, actor, startTime => {
         wait_for_samples(client, actor, () => {
           check_buffer(client, actor, () => {
             deactivate_profiler(client, actor, () => {
-              client.close(do_test_finished);
+              client.close().then(do_test_finished);
             });
           });
         });
       });
     });
   });
 
   do_test_pending();
--- a/devtools/server/tests/unit/test_profiler_data.js
+++ b/devtools/server/tests/unit/test_profiler_data.js
@@ -14,17 +14,17 @@ const MAX_WAIT_TIME = 20000; // ms
 
 function run_test()
 {
   get_chrome_actors((client, form) => {
     let actor = form.profilerActor;
     activate_profiler(client, actor, startTime => {
       test_data(client, actor, startTime, () => {
         deactivate_profiler(client, actor, () => {
-          client.close(do_test_finished);
+          client.close().then(do_test_finished);
         });
       });
     });
   });
 
   do_test_pending();
 }
 
--- a/devtools/server/tests/unit/test_profiler_getbufferinfo.js
+++ b/devtools/server/tests/unit/test_profiler_getbufferinfo.js
@@ -20,17 +20,17 @@ function run_test()
 
   get_chrome_actors((client, form) => {
     let actor = form.profilerActor;
     check_empty_buffer(client, actor, () => {
       activate_profiler(client, actor, startTime => {
         wait_for_samples(client, actor, () => {
           check_buffer(client, actor, () => {
             deactivate_profiler(client, actor, () => {
-              client.close(do_test_finished);
+              client.close().then(do_test_finished);
             });
           });
         });
       });
     });
   });
 
   do_test_pending();
--- a/devtools/server/tests/unit/test_profiler_getfeatures.js
+++ b/devtools/server/tests/unit/test_profiler_getfeatures.js
@@ -9,17 +9,17 @@
 
 const Profiler = Cc["@mozilla.org/tools/profiler;1"].getService(Ci.nsIProfiler);
 
 function run_test()
 {
   get_chrome_actors((client, form) => {
     let actor = form.profilerActor;
     test_getfeatures(client, actor, () => {
-      client.close(() => {
+      client.close().then(() => {
         do_test_finished();
       });
     });
   });
 
   do_test_pending();
 }
 
--- a/devtools/server/tests/unit/test_profiler_getsharedlibraryinformation.js
+++ b/devtools/server/tests/unit/test_profiler_getsharedlibraryinformation.js
@@ -9,17 +9,17 @@
 
 const Profiler = Cc["@mozilla.org/tools/profiler;1"].getService(Ci.nsIProfiler);
 
 function run_test()
 {
   get_chrome_actors((client, form) => {
     let actor = form.profilerActor;
     test_getsharedlibraryinformation(client, actor, () => {
-      client.close(() => {
+      client.close().then(() => {
         do_test_finished();
       });
     });
   });
 
   do_test_pending();
 }
 
--- a/devtools/server/tests/unit/test_protocol_async.js
+++ b/devtools/server/tests/unit/test_protocol_async.js
@@ -170,15 +170,15 @@ function run_test()
     calls.push(rootClient.simpleReturn().then(ret => {
       return deferAfterRejection.promise.then(function () {
         do_check_eq(sequence, 7); // Check right return order
         do_check_eq(ret, sequence++); // Check request handling order
       });
     }));
 
     promise.all(calls).then(() => {
-      client.close(() => {
+      client.close().then(() => {
         do_test_finished();
       });
     });
   });
   do_test_pending();
 }
--- a/devtools/server/tests/unit/test_protocol_children.js
+++ b/devtools/server/tests/unit/test_protocol_children.js
@@ -543,17 +543,17 @@ function run_test()
       };
       return rootFront.getChildren2(f());
     }).then(ret => {
       do_check_eq(ret.length, 2);
       do_check_true(ret[0] === childFront);
       do_check_true(ret[1] !== childFront);
       do_check_true(ret[1] instanceof ChildFront);
     }).then(() => {
-      client.close(() => {
+      client.close().then(() => {
         do_test_finished();
       });
     }).then(null, err => {
       do_report_unexpected_exception(err, "Failure executing test");
     });
   });
   do_test_pending();
 }
--- a/devtools/server/tests/unit/test_protocol_longstring.js
+++ b/devtools/server/tests/unit/test_protocol_longstring.js
@@ -202,17 +202,17 @@ function run_test()
       do_check_eq(value, LONG_STR);
     }).then(() => {
       return strfront.release();
     }).then(() => {
       trace.expectSend({"type":"release", "to":"<actorid>"});
       trace.expectReceive({"from":"<actorid>"});
       expectRootChildren(0);
     }).then(() => {
-      client.close(() => {
+      client.close().then(() => {
         do_test_finished();
       });
     }).then(null, err => {
       do_report_unexpected_exception(err, "Failure executing test");
     });
   });
   do_test_pending();
 }
--- a/devtools/server/tests/unit/test_protocol_simple.js
+++ b/devtools/server/tests/unit/test_protocol_simple.js
@@ -303,17 +303,17 @@ function run_test()
 
         do_check_true(res.zero === 0);
         do_check_true(res.farce === false);
         deferred.resolve();
       });
       rootClient.emitFalsyOptions();
       return deferred.promise;
     }).then(() => {
-      client.close(() => {
+      client.close().then(() => {
         do_test_finished();
       });
     }).then(null, err => {
       do_report_unexpected_exception(err, "Failure executing test");
     });
   });
   do_test_pending();
 }
--- a/devtools/server/tests/unit/test_protocol_stack.js
+++ b/devtools/server/tests/unit/test_protocol_stack.js
@@ -83,16 +83,16 @@ function run_test() {
           return;
         }
         stack = stack.asyncCaller || stack.caller;
       }
       ok(false, "Incomplete stack");
     }, () => {
       ok(false, "Request failed unexpectedly");
     }).then(() => {
-      client.close(() => {
+      client.close().then(() => {
         do_test_finished();
       });
     });
   });
 
   do_test_pending();
 }
--- a/devtools/server/tests/unit/test_reattach-thread.js
+++ b/devtools/server/tests/unit/test_reattach-thread.js
@@ -49,10 +49,10 @@ function test_reattach()
     do_check_eq(aThreadClient.state, "paused");
     do_check_eq(gTabClient.thread, aThreadClient);
     aThreadClient.resume(cleanup);
   });
 }
 
 function cleanup()
 {
-  gClient.close(do_test_finished);
+  gClient.close().then(do_test_finished);
 }
--- a/devtools/server/tests/unit/test_registerClient.js
+++ b/devtools/server/tests/unit/test_registerClient.js
@@ -81,15 +81,15 @@ function test_client_events()
     do_check_eq(type, "foo");
     do_check_eq(data.hello, "world");
     run_next_test();
   });
   gTestClient.start();
 }
 
 function close_client() {
-  gClient.close(() => {
+  gClient.close().then(() => {
     // Check that client.detach method is call on client destruction
     do_check_true(gTestClient.detached);
     run_next_test();
   });
 }
 
--- a/devtools/server/tests/unit/test_register_actor.js
+++ b/devtools/server/tests/unit/test_register_actor.js
@@ -91,17 +91,17 @@ function test_lazy_api() {
   function onRequest(aResponse) {
     do_check_eq(aResponse, "world");
 
     // Finally, the actor is loaded on the first request being made to it
     do_check_true(isActorLoaded);
     do_check_true(isActorInstanciated);
 
     Services.obs.removeObserver(onActorEvent, "actor", false);
-    client.close(() => run_next_test());
+    client.close().then(() => run_next_test());
   }
 }
 
 function cleanup() {
   DebuggerServer.destroy();
 
   // Check that all actors are unregistered on server destruction
   check_actors(false);
--- a/devtools/server/tests/unit/test_requestTypes.js
+++ b/devtools/server/tests/unit/test_requestTypes.js
@@ -11,17 +11,17 @@ function test_requestTypes_request(aClie
     var expectedRequestTypes = Object.keys(RootActor.
                                            prototype.
                                            requestTypes);
 
     do_check_true(Array.isArray(aResponse.requestTypes));
     do_check_eq(JSON.stringify(aResponse.requestTypes),
                 JSON.stringify(expectedRequestTypes));
 
-    aClient.close(() => {
+    aClient.close().then(() => {
       do_test_finished();
     });
   });
 }
 
 function run_test()
 {
   DebuggerServer.init();
--- a/devtools/server/tests/unit/test_stepping-01.js
+++ b/devtools/server/tests/unit/test_stepping-01.js
@@ -59,17 +59,17 @@ function test_simple_stepping()
           // When leaving a stack frame the line number doesn't change.
           do_check_eq(aPacket.frame.where.line, gDebuggee.line0 + 3);
           do_check_eq(aPacket.why.type, "resumeLimit");
           // Check that stepping worked.
           do_check_eq(gDebuggee.a, 1);
           do_check_eq(gDebuggee.b, 2);
 
           gThreadClient.resume(function () {
-            gClient.close(gCallback);
+            gClient.close().then(gCallback);
           });
         });
         gThreadClient.stepOver();
       });
       gThreadClient.stepOver();
 
     });
     gThreadClient.stepOver();
--- a/devtools/server/tests/unit/test_stepping-02.js
+++ b/devtools/server/tests/unit/test_stepping-02.js
@@ -59,17 +59,17 @@ function test_simple_stepping()
           // When leaving a stack frame the line number doesn't change.
           do_check_eq(aPacket.frame.where.line, gDebuggee.line0 + 3);
           do_check_eq(aPacket.why.type, "resumeLimit");
           // Check that stepping worked.
           do_check_eq(gDebuggee.a, 1);
           do_check_eq(gDebuggee.b, 2);
 
           gThreadClient.resume(function () {
-            gClient.close(gCallback);
+            gClient.close().then(gCallback);
           });
         });
         gThreadClient.stepIn();
       });
       gThreadClient.stepIn();
 
     });
     gThreadClient.stepIn();
--- a/devtools/server/tests/unit/test_stepping-03.js
+++ b/devtools/server/tests/unit/test_stepping-03.js
@@ -40,17 +40,17 @@ function test_simple_stepping()
       do_check_eq(aPacket.type, "paused");
       do_check_eq(aPacket.frame.where.line, gDebuggee.line0 + 5);
       do_check_eq(aPacket.why.type, "resumeLimit");
       // Check that stepping worked.
       do_check_eq(gDebuggee.a, 1);
       do_check_eq(gDebuggee.b, 2);
 
       gThreadClient.resume(function () {
-        gClient.close(gCallback);
+        gClient.close().then(gCallback);
       });
     });
     gThreadClient.stepOut();
 
   });
 
   gDebuggee.eval("var line0 = Error().lineNumber;\n" +
                  "function f() {\n" + // line0 + 1
--- a/devtools/server/tests/unit/test_stepping-04.js
+++ b/devtools/server/tests/unit/test_stepping-04.js
@@ -49,17 +49,17 @@ function test_simple_stepping()
         do_check_eq(aPacket.type, "paused");
         do_check_eq(aPacket.frame.where.line, gDebuggee.line0 + 6);
         do_check_eq(aPacket.why.type, "resumeLimit");
         // Check that stepping worked.
         do_check_eq(gDebuggee.a, 1);
         do_check_eq(gDebuggee.b, undefined);
 
         gThreadClient.resume(function () {
-          gClient.close(gCallback);
+          gClient.close().then(gCallback);
         });
       });
       gThreadClient.stepOver();
 
     });
     gThreadClient.stepOver();
 
   });
--- a/devtools/server/tests/unit/test_stepping-05.js
+++ b/devtools/server/tests/unit/test_stepping-05.js
@@ -88,14 +88,14 @@ function test_next_pause()
 {
   gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) {
     // Check the return value.
     do_check_eq(aPacket.type, "paused");
     // Before fixing bug 785689, the type was resumeLimit.
     do_check_eq(aPacket.why.type, "debuggerStatement");
 
     gThreadClient.resume(function () {
-      gClient.close(gCallback);
+      gClient.close().then(gCallback);
     });
   });
 
   gDebuggee.eval("debugger;");
 }
--- a/devtools/server/tests/unit/test_stepping-06.js
+++ b/devtools/server/tests/unit/test_stepping-06.js
@@ -59,17 +59,17 @@ function test_simple_stepping()
             gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) {
               // Check that the exception was thrown.
               do_check_eq(aPacket.type, "paused");
               do_check_eq(aPacket.frame.where.line, gDebuggee.line0 + 11);
               do_check_eq(aPacket.why.type, "resumeLimit");
               do_check_eq(aPacket.why.frameFinished.throw, "ah");
 
               gThreadClient.resume(function () {
-                gClient.close(gCallback);
+                gClient.close().then(gCallback);
               });
             });
             gThreadClient.stepOut();
           });
           gThreadClient.resume();
         });
         gThreadClient.stepOut();
       });
--- a/devtools/shared/client/main.js
+++ b/devtools/shared/client/main.js
@@ -349,44 +349,46 @@ DebuggerClient.prototype = {
     return deferred.promise;
   },
 
   /**
    * Shut down communication with the debugging server.
    *
    * @param aOnClosed function
    *        If specified, will be called when the debugging connection
-   *        has been closed.
+   *        has been closed. This parameter is deprecated - please use
+   *        the returned Promise.
+   * @return Promise
+   *         Resolves after the underlying transport is closed.
    */
   close: function (aOnClosed) {
+    let deferred = promise.defer();
+    if (aOnClosed) {
+      deferred.promise.then(aOnClosed);
+    }
+
     // Disable detach event notifications, because event handlers will be in a
     // cleared scope by the time they run.
     this._eventsEnabled = false;
 
     let cleanup = () => {
       this._transport.close();
       this._transport = null;
     };
 
     // If the connection is already closed,
     // there is no need to detach client
     // as we won't be able to send any message.
     if (this._closed) {
       cleanup();
-      if (aOnClosed) {
-        aOnClosed();
-      }
-      return;
+      deferred.resolve();
+      return deferred.promise;
     }
 
-    if (aOnClosed) {
-      this.addOneTimeListener("closed", function (aEvent) {
-        aOnClosed();
-      });
-    }
+    this.addOneTimeListener("closed", deferred.resolve);
 
     // Call each client's `detach` method by calling
     // lastly registered ones first to give a chance
     // to detach child clients first.
     let clients = [...this._clients.values()];
     this._clients.clear();
     const detachClients = () => {
       let client = clients.pop();
@@ -397,16 +399,18 @@ DebuggerClient.prototype = {
       }
       if (client.detach) {
         client.detach(detachClients);
         return;
       }
       detachClients();
     };
     detachClients();
+
+    return deferred.promise;
   },
 
   /*
    * This function exists only to preserve DebuggerClient's interface;
    * new code should say 'client.mainRoot.listTabs()'.
    */
   listTabs: function (aOnResponse) { return this.mainRoot.listTabs(aOnResponse); },
 
--- a/devtools/shared/transport/tests/unit/head_dbg.js
+++ b/devtools/shared/transport/tests/unit/head_dbg.js
@@ -180,17 +180,17 @@ function initTestDebuggerServer() {
     type: { global: true, tab: true }
   });
   DebuggerServer.registerModule("xpcshell-test/testactors");
   // Allow incoming connections.
   DebuggerServer.init();
 }
 
 function finishClient(aClient) {
-  aClient.close(function () {
+  aClient.close().then(function () {
     do_test_finished();
   });
 }
 
 /**
  * Takes a relative file path and returns the absolute file url for it.
  */
 function getFileUrl(aName, aAllowMissing = false) {
--- a/devtools/shared/webconsole/test/common.js
+++ b/devtools/shared/webconsole/test/common.js
@@ -140,17 +140,17 @@ function _attachConsole(aListeners, aCal
         });
       });
     }
   });
 }
 
 function closeDebugger(aState, aCallback)
 {
-  aState.dbgClient.close(aCallback);
+  aState.dbgClient.close().then(aCallback);
   aState.dbgClient = null;
   aState.client = null;
 }
 
 function checkConsoleAPICalls(consoleCalls, expectedConsoleCalls)
 {
   is(consoleCalls.length, expectedConsoleCalls.length,
     "received correct number of console calls");
--- a/dom/canvas/CanvasRenderingContext2D.cpp
+++ b/dom/canvas/CanvasRenderingContext2D.cpp
@@ -1541,30 +1541,38 @@ CanvasRenderingContext2D::RestoreClipsAn
 }
 
 CanvasRenderingContext2D::RenderingMode
 CanvasRenderingContext2D::EnsureTarget(const gfx::Rect* aCoveredRect,
                                        RenderingMode aRenderingMode)
 {
   if (AlreadyShutDown()) {
     gfxCriticalError() << "Attempt to render into a Canvas2d after shutdown.";
-    EnsureErrorTarget();
-    mTarget = sErrorTarget;
+    SetErrorState();
     return aRenderingMode;
   }
 
   // This would make no sense, so make sure we don't get ourselves in a mess
   MOZ_ASSERT(mRenderingMode != RenderingMode::DefaultBackendMode);
 
   RenderingMode mode = (aRenderingMode == RenderingMode::DefaultBackendMode) ? mRenderingMode : aRenderingMode;
 
   if (mTarget && mode == mRenderingMode) {
     return mRenderingMode;
   }
 
+  // Check that the dimensions are sane
+  if (mWidth > gfxPrefs::MaxCanvasSize() ||
+      mHeight > gfxPrefs::MaxCanvasSize() ||
+      mWidth < 0 || mHeight < 0) {
+
+    SetErrorState();
+    return aRenderingMode;
+  }
+
   // If the next drawing command covers the entire canvas, we can skip copying
   // from the previous frame and/or clearing the canvas.
   gfx::Rect canvasRect(0, 0, mWidth, mHeight);
   bool canDiscardContent = aCoveredRect &&
     CurrentState().transform.TransformBounds(*aCoveredRect).Contains(canvasRect);
 
   // If a clip is active we don't know for sure that the next drawing command
   // will really cover the entire canvas.
@@ -1577,138 +1585,234 @@ CanvasRenderingContext2D::EnsureTarget(c
         canDiscardContent = false;
         break;
       }
     }
   }
 
   ScheduleStableStateCallback();
 
-  // we'll do a few extra things at the end of this method if we changed the
-  // buffer provider.
-  RefPtr<PersistentBufferProvider> oldBufferProvider = mBufferProvider;
+  IntRect persistedRect = canDiscardContent ? IntRect()
+                                            : IntRect(0, 0, mWidth, mHeight);
 
   if (mBufferProvider && mode == mRenderingMode) {
-    auto persistedRect = canDiscardContent ? IntRect()
-                                           : IntRect(0, 0, mWidth, mHeight);
     mTarget = mBufferProvider->BorrowDrawTarget(persistedRect);
 
     if (mTarget && !mBufferProvider->PreservesDrawingState()) {
       RestoreClipsAndTransformToTarget();
     }
 
     if (mTarget) {
       return mode;
     }
   }
 
+  RefPtr<DrawTarget> newTarget;
+  RefPtr<PersistentBufferProvider> newProvider;
+
+  if (mode == RenderingMode::OpenGLBackendMode &&
+      !TrySkiaGLTarget(newTarget, newProvider)) {
+    // Fall back to software.
+    mode = RenderingMode::SoftwareBackendMode;
+  }
+
+  if (mode == RenderingMode::SoftwareBackendMode &&
+      !TrySharedTarget(newTarget, newProvider) &&
+      !TryBasicTarget(newTarget, newProvider)) {
+    SetErrorState();
+    return mode;
+  }
+
+  MOZ_ASSERT(newTarget);
+  MOZ_ASSERT(newProvider);
+
+  mTarget = newTarget.forget();
+  mBufferProvider = newProvider.forget();
+
+  RegisterAllocation();
+
+  // Skia expects the unused X channel to contains 0 even for opaque operations
+  // so we can't skip clearing in that case, even if we are going to cover the
+  // entire canvas in the next drawing operation.
+  if (!canDiscardContent || mTarget->GetBackendType() == gfx::BackendType::SKIA) {
+    mTarget->ClearRect(canvasRect);
+  }
+
+  RestoreClipsAndTransformToTarget();
+
+  // Force a full layer transaction since we didn't have a layer before
+  // and now we might need one.
+  if (mCanvasElement) {
+    mCanvasElement->InvalidateCanvas();
+  }
+  // Calling Redraw() tells our invalidation machinery that the entire
+  // canvas is already invalid, which can speed up future drawing.
+  Redraw();
+
+  return mode;
+}
+
+void
+CanvasRenderingContext2D::SetInitialState()
+{
+  // Set up the initial canvas defaults
+  mPathBuilder = nullptr;
+  mPath = nullptr;
+  mDSPathBuilder = nullptr;
+
+  mStyleStack.Clear();
+  ContextState *state = mStyleStack.AppendElement();
+  state->globalAlpha = 1.0;
+
+  state->colorStyles[Style::FILL] = NS_RGB(0,0,0);
+  state->colorStyles[Style::STROKE] = NS_RGB(0,0,0);
+  state->shadowColor = NS_RGBA(0,0,0,0);
+}
+
+void
+CanvasRenderingContext2D::SetErrorState()
+{
+  EnsureErrorTarget();
+
+  if (mTarget && mTarget != sErrorTarget) {
+    gCanvasAzureMemoryUsed -= mWidth * mHeight * 4;
+  }
+
+  mTarget = sErrorTarget;
+  mBufferProvider = nullptr;
+
+  // clear transforms, clips, etc.
+  SetInitialState();
+}
+
+void
+CanvasRenderingContext2D::RegisterAllocation()
+{
+  // XXX - It would make more sense to track the allocation in
+  // PeristentBufferProvider, rather than here.
+  static bool registered = false;
+  if (!registered) {
+    registered = true;
+    RegisterStrongMemoryReporter(new Canvas2dPixelsReporter());
+  }
+
+  gCanvasAzureMemoryUsed += mWidth * mHeight * 4;
+  JSContext* context = nsContentUtils::GetCurrentJSContext();
+  if (context) {
+    JS_updateMallocCounter(context, mWidth * mHeight * 4);
+  }
+}
+
+static already_AddRefed<LayerManager>
+LayerManagerFromCanvasElement(nsINode* aCanvasElement)
+{
+  if (!aCanvasElement || !aCanvasElement->OwnerDoc()) {
+    return nullptr;
+  }
+
+  return nsContentUtils::PersistentLayerManagerForDocument(aCanvasElement->OwnerDoc());
+}
+
+bool
+CanvasRenderingContext2D::TrySkiaGLTarget(RefPtr<gfx::DrawTarget>& aOutDT,
+                                          RefPtr<layers::PersistentBufferProvider>& aOutProvider)
+{
+  aOutDT = nullptr;
+  aOutProvider = nullptr;
+
+
   mIsSkiaGL = false;
 
-   // Check that the dimensions are sane
   IntSize size(mWidth, mHeight);
-  if (!mTarget &&
-      size.width <= gfxPrefs::MaxCanvasSize() &&
-      size.height <= gfxPrefs::MaxCanvasSize() &&
-      size.width >= 0 && size.height >= 0) {
-    SurfaceFormat format = GetSurfaceFormat();
-    nsIDocument* ownerDoc = nullptr;
-    if (mCanvasElement) {
-      ownerDoc = mCanvasElement->OwnerDoc();
-    }
-
-    RefPtr<LayerManager> layerManager = nullptr;
-
-    if (ownerDoc) {
-      layerManager =
-        nsContentUtils::PersistentLayerManagerForDocument(ownerDoc);
-    }
-
-    if (layerManager) {
-      if (mode == RenderingMode::OpenGLBackendMode &&
-          gfxPlatform::GetPlatform()->UseAcceleratedCanvas() &&
-          CheckSizeForSkiaGL(size)) {
-        DemoteOldestContextIfNecessary();
-        mBufferProvider = nullptr;
+  if (!gfxPlatform::GetPlatform()->UseAcceleratedCanvas() ||
+      !CheckSizeForSkiaGL(size)) {
+
+    return false;
+  }
+
+
+  RefPtr<LayerManager> layerManager = LayerManagerFromCanvasElement(mCanvasElement);
+
+  if (!layerManager) {
+    return false;
+  }
+
+  DemoteOldestContextIfNecessary();
+  mBufferProvider = nullptr;
 
 #if USE_SKIA_GPU
-        SkiaGLGlue* glue = gfxPlatform::GetPlatform()->GetSkiaGLGlue();
-
-        if (glue && glue->GetGrContext() && glue->GetGLContext()) {
-          mTarget = Factory::CreateDrawTargetSkiaWithGrContext(glue->GetGrContext(), size, format);
-          if (mTarget) {
-            AddDemotableContext(this);
-            mBufferProvider = new PersistentBufferProviderBasic(mTarget);
-            mIsSkiaGL = true;
-          } else {
-            gfxCriticalNote << "Failed to create a SkiaGL DrawTarget, falling back to software " << size << ", " << format;
-            mode = RenderingMode::SoftwareBackendMode;
-          }
-        }
+  SkiaGLGlue* glue = gfxPlatform::GetPlatform()->GetSkiaGLGlue();
+  if (!glue || !glue->GetGrContext() || !glue->GetGLContext()) {
+    return false;
+  }
+
+  SurfaceFormat format = GetSurfaceFormat();
+  aOutDT = Factory::CreateDrawTargetSkiaWithGrContext(glue->GetGrContext(),
+                                                      size, format);
+  if (!aOutDT) {
+    return false;
+    gfxCriticalNote << "Failed to create a SkiaGL DrawTarget, falling back to software\n";
+  }
+
+  MOZ_ASSERT(aOutDT->GetType() == DrawTargetType::HARDWARE_RASTER);
+
+  AddDemotableContext(this);
+  aOutProvider = new PersistentBufferProviderBasic(aOutDT);
+  mIsSkiaGL = true;
+  // Drop a note in the debug builds if we ever use accelerated Skia canvas.
+  gfxWarningOnce() << "Using SkiaGL canvas.";
 #endif
-      }
-
-      if (!mBufferProvider) {
-        mTarget = nullptr;
-        mBufferProvider = layerManager->CreatePersistentBufferProvider(size, format);
-      }
-    }
-
-    if (mBufferProvider) {
-      mTarget = mBufferProvider->BorrowDrawTarget(IntRect(IntPoint(), IntSize(mWidth, mHeight)));
-    } else if (!mTarget) {
-      mTarget = gfxPlatform::GetPlatform()->CreateOffscreenCanvasDrawTarget(size, format);
-      mode = RenderingMode::SoftwareBackendMode;
-    }
-  }
-
-  if (mTarget) {
-    // We changed the buffer provider.
-    // XXX - It would make more sense to track the allocation in
-    // PeristentBufferProvider, rather than here.
-    if (mBufferProvider != oldBufferProvider) {
-      static bool registered = false;
-      if (!registered) {
-        registered = true;
-        RegisterStrongMemoryReporter(new Canvas2dPixelsReporter());
-      }
-
-      gCanvasAzureMemoryUsed += mWidth * mHeight * 4;
-      JSContext* context = nsContentUtils::GetCurrentJSContext();
-      if (context) {
-        JS_updateMallocCounter(context, mWidth * mHeight * 4);
-      }
-
-      mTarget->ClearRect(canvasRect);
-
-      // Force a full layer transaction since we didn't have a layer before
-      // and now we might need one.
-      if (mCanvasElement) {
-        mCanvasElement->InvalidateCanvas();
-      }
-      // Calling Redraw() tells our invalidation machinery that the entire
-      // canvas is already invalid, which can speed up future drawing.
-      Redraw();
-    }
-
-    if (mBufferProvider != oldBufferProvider || !mBufferProvider ||
-        !mBufferProvider->PreservesDrawingState()) {
-      RestoreClipsAndTransformToTarget();
-    }
-  } else {
-    EnsureErrorTarget();
-    mTarget = sErrorTarget;
-    mBufferProvider = nullptr;
-  }
-
-  // Drop a note in the debug builds if we ever use accelerated Skia canvas.
-  if (mIsSkiaGL && mTarget && mTarget->GetType() == DrawTargetType::HARDWARE_RASTER) {
-    gfxWarningOnce() << "Using SkiaGL canvas.";
-  }
-
-  return mode;
+
+  // could still be null if USE_SKIA_GPU is not #defined.
+  return !!aOutDT;
+}
+
+bool
+CanvasRenderingContext2D::TrySharedTarget(RefPtr<gfx::DrawTarget>& aOutDT,
+                                          RefPtr<layers::PersistentBufferProvider>& aOutProvider)
+{
+  aOutDT = nullptr;
+  aOutProvider = nullptr;
+
+  if (!mCanvasElement || !mCanvasElement->OwnerDoc()) {
+    return false;
+  }
+
+  RefPtr<LayerManager> layerManager = LayerManagerFromCanvasElement(mCanvasElement);
+
+  if (!layerManager) {
+    return false;
+  }
+
+  aOutProvider = layerManager->CreatePersistentBufferProvider(GetSize(), GetSurfaceFormat());
+
+  if (!aOutProvider) {
+    return false;
+  }
+
+  // We can pass an empty persisted rect since we just created the buffer
+  // provider (nothing to restore).
+  aOutDT = aOutProvider->BorrowDrawTarget(IntRect());
+  MOZ_ASSERT(aOutDT);
+
+  return !!aOutDT;
+}
+
+bool
+CanvasRenderingContext2D::TryBasicTarget(RefPtr<gfx::DrawTarget>& aOutDT,
+                                         RefPtr<layers::PersistentBufferProvider>& aOutProvider)
+{
+  aOutDT = gfxPlatform::GetPlatform()->CreateOffscreenCanvasDrawTarget(GetSize(),
+                                                                       GetSurfaceFormat());
+  if (!aOutDT) {
+    return false;
+  }
+
+  aOutProvider = new PersistentBufferProviderBasic(aOutDT);
+  return true;
 }
 
 int32_t
 CanvasRenderingContext2D::GetWidth() const
 {
   return mWidth;
 }
 
@@ -1758,43 +1862,32 @@ CanvasRenderingContext2D::ClearTarget(bo
   Reset();
 
   if (aRetainBuffer) {
     mBufferProvider = provider;
   }
 
   mResetLayer = true;
 
-  // set up the initial canvas defaults
-  mStyleStack.Clear();
-  mPathBuilder = nullptr;
-  mPath = nullptr;
-  mDSPathBuilder = nullptr;
-
-  ContextState *state = mStyleStack.AppendElement();
-  state->globalAlpha = 1.0;
-
-  state->colorStyles[Style::FILL] = NS_RGB(0,0,0);
-  state->colorStyles[Style::STROKE] = NS_RGB(0,0,0);
-  state->shadowColor = NS_RGBA(0,0,0,0);
+  SetInitialState();
 
   // For vertical writing-mode, unless text-orientation is sideways,
   // we'll modify the initial value of textBaseline to 'middle'.
   RefPtr<nsStyleContext> canvasStyle;
   if (mCanvasElement && mCanvasElement->IsInUncomposedDoc()) {
     nsCOMPtr<nsIPresShell> presShell = GetPresShell();
     if (presShell) {
       canvasStyle =
         nsComputedDOMStyle::GetStyleContextForElement(mCanvasElement,
                                                       nullptr,
                                                       presShell);
       if (canvasStyle) {
         WritingMode wm(canvasStyle);
         if (wm.IsVertical() && !wm.IsSideways()) {
-          state->textBaseline = TextBaseline::MIDDLE;
+          CurrentState().textBaseline = TextBaseline::MIDDLE;
         }
       }
     }
   }
 }
 
 void
 CanvasRenderingContext2D::ReturnTarget(bool aForceReset)
@@ -1915,17 +2008,17 @@ CanvasRenderingContext2D::GetImageBuffer
     snapshot = mBufferProvider->BorrowSnapshot();
   } else {
     EnsureTarget();
     snapshot = mTarget->Snapshot();
   }
 
   if (snapshot) {
     RefPtr<DataSourceSurface> data = snapshot->GetDataSurface();
-    if (data && data->GetSize() == IntSize(mWidth, mHeight)) {
+    if (data && data->GetSize() == GetSize()) {
       *aFormat = imgIEncoder::INPUT_FORMAT_HOSTARGB;
       ret = SurfaceToPackedBGRA(data);
     }
   }
 
   if (!mTarget && mBufferProvider) {
     mBufferProvider->ReturnSnapshot(snapshot.forget());
   }
@@ -2757,17 +2850,17 @@ CanvasRenderingContext2D::UpdateFilter()
   // The filter might reference an SVG filter that is declared inside this
   // document. Flush frames so that we'll have an nsSVGFilterFrame to work
   // with.
   presShell->FlushPendingNotifications(Flush_Frames);
 
   CurrentState().filter =
     nsFilterInstance::GetFilterDescription(mCanvasElement,
       CurrentState().filterChain,
-      CanvasUserSpaceMetrics(IntSize(mWidth, mHeight),
+      CanvasUserSpaceMetrics(GetSize(),
                              CurrentState().fontFont,
                              CurrentState().fontLanguage,
                              CurrentState().fontExplicitLanguage,
                              presShell->GetPresContext()),
       gfxRect(0, 0, mWidth, mHeight),
       CurrentState().filterAdditionalImages);
 }
 
@@ -5793,40 +5886,16 @@ CanvasRenderingContext2D::SkiaGLTex() co
 void CanvasRenderingContext2D::RemoveDrawObserver()
 {
   if (mDrawObserver) {
     delete mDrawObserver;
     mDrawObserver = nullptr;
   }
 }
 
-PersistentBufferProvider*
-CanvasRenderingContext2D::GetBufferProvider(LayerManager* aManager)
-{
-  if (AlreadyShutDown()) {
-    return nullptr;
-  }
-
-  if (mBufferProvider) {
-    return mBufferProvider;
-  }
-
-  if (mTarget) {
-    mBufferProvider = new PersistentBufferProviderBasic(mTarget);
-    return mBufferProvider;
-  }
-
-  if (aManager && !mIsSkiaGL) {
-    mBufferProvider = aManager->CreatePersistentBufferProvider(gfx::IntSize(mWidth, mHeight),
-                                                               GetSurfaceFormat());
-  }
-
-  return mBufferProvider;
-}
-
 already_AddRefed<Layer>
 CanvasRenderingContext2D::GetCanvasLayer(nsDisplayListBuilder* aBuilder,
                                          Layer *aOldLayer,
                                          LayerManager *aManager,
                                          bool aMirror /* = false */)
 {
   if (aMirror) {
     // Not supported for CanvasRenderingContext2D
@@ -5864,18 +5933,17 @@ CanvasRenderingContext2D::GetCanvasLayer
       if (skiaGLTex) {
         SkiaGLGlue* glue = gfxPlatform::GetPlatform()->GetSkiaGLGlue();
         MOZ_ASSERT(glue);
         data.mGLContext = glue->GetGLContext();
         data.mFrontbufferGLTex = skiaGLTex;
       }
     }
 
-    PersistentBufferProvider *provider = GetBufferProvider(aManager);
-    data.mBufferProvider = provider;
+    data.mBufferProvider = mBufferProvider;
 
     if (userData &&
         userData->IsForContext(this) &&
         static_cast<CanvasLayer*>(aOldLayer)->IsDataValid(data)) {
       RefPtr<Layer> ret = aOldLayer;
       return ret.forget();
     }
   }
@@ -5901,35 +5969,34 @@ CanvasRenderingContext2D::GetCanvasLayer
   // The userData will receive DidTransactionCallbacks, which flush the
   // the invalidation state to indicate that the canvas is up to date.
   userData = new CanvasRenderingContext2DUserData(this);
   canvasLayer->SetDidTransactionCallback(
           CanvasRenderingContext2DUserData::DidTransactionCallback, userData);
   canvasLayer->SetUserData(&g2DContextLayerUserData, userData);
 
   CanvasLayer::Data data;
-  data.mSize = nsIntSize(mWidth, mHeight);
+  data.mSize = GetSize();
   data.mHasAlpha = !mOpaque;
 
   canvasLayer->SetPreTransactionCallback(
           CanvasRenderingContext2DUserData::PreTransactionCallback, userData);
 
 
   if (mIsSkiaGL) {
       GLuint skiaGLTex = SkiaGLTex();
       if (skiaGLTex) {
         SkiaGLGlue* glue = gfxPlatform::GetPlatform()->GetSkiaGLGlue();
         MOZ_ASSERT(glue);
         data.mGLContext = glue->GetGLContext();
         data.mFrontbufferGLTex = skiaGLTex;
       }
   }
 
-  PersistentBufferProvider *provider = GetBufferProvider(aManager);
-  data.mBufferProvider = provider;
+  data.mBufferProvider = mBufferProvider;
 
   canvasLayer->Initialize(data);
   uint32_t flags = mOpaque ? Layer::CONTENT_OPAQUE : 0;
   canvasLayer->SetContentFlags(flags);
   canvasLayer->Updated();
 
   mResetLayer = false;
 
@@ -5956,17 +6023,17 @@ bool
 CanvasRenderingContext2D::IsContextCleanForFrameCapture()
 {
   return !mIsCapturedFrameInvalid;
 }
 
 bool
 CanvasRenderingContext2D::ShouldForceInactiveLayer(LayerManager* aManager)
 {
-  return !aManager->CanUseCanvasLayerForSize(IntSize(mWidth, mHeight));
+  return !aManager->CanUseCanvasLayerForSize(GetSize());
 }
 
 NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(CanvasPath, AddRef)
 NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(CanvasPath, Release)
 
 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(CanvasPath, mParent)
 
 CanvasPath::CanvasPath(nsISupports* aParent)
--- a/dom/canvas/CanvasRenderingContext2D.h
+++ b/dom/canvas/CanvasRenderingContext2D.h
@@ -424,16 +424,17 @@ public:
 
   // Eventually this should be deprecated. Keeping for now to keep the binding functional.
   void Demote();
 
   nsresult Redraw();
 
   virtual int32_t GetWidth() const override;
   virtual int32_t GetHeight() const override;
+  gfx::IntSize GetSize() const { return gfx::IntSize(mWidth, mHeight); }
 
   // nsICanvasRenderingContextInternal
   /**
     * Gets the pres shell from either the canvas element or the doc shell
     */
   virtual nsIPresShell *GetPresShell() override {
     if (mCanvasElement) {
       return mCanvasElement->OwnerDoc()->GetShell();
@@ -458,17 +459,16 @@ public:
       *aPremultAlpha = true;
     }
     return mTarget->Snapshot();
   }
 
   NS_IMETHOD SetIsOpaque(bool aIsOpaque) override;
   bool GetIsOpaque() override { return mOpaque; }
   NS_IMETHOD Reset() override;
-  mozilla::layers::PersistentBufferProvider* GetBufferProvider(mozilla::layers::LayerManager* aManager);
   already_AddRefed<Layer> GetCanvasLayer(nsDisplayListBuilder* aBuilder,
                                          Layer* aOldLayer,
                                          LayerManager* aManager,
                                          bool aMirror = false) override;
   virtual bool ShouldForceInactiveLayer(LayerManager* aManager) override;
   void MarkContextClean() override;
   void MarkContextCleanForFrameCapture() override;
   bool IsContextCleanForFrameCapture() override;
@@ -640,16 +640,31 @@ protected:
    *
    * Returns the actual rendering mode being used by the created target.
    */
   RenderingMode EnsureTarget(const gfx::Rect* aCoveredRect = nullptr,
                              RenderingMode aRenderMode = RenderingMode::DefaultBackendMode);
 
   void RestoreClipsAndTransformToTarget();
 
+  bool TrySkiaGLTarget(RefPtr<gfx::DrawTarget>& aOutDT,
+                       RefPtr<layers::PersistentBufferProvider>& aOutProvider);
+
+  bool TrySharedTarget(RefPtr<gfx::DrawTarget>& aOutDT,
+                       RefPtr<layers::PersistentBufferProvider>& aOutProvider);
+
+  bool TryBasicTarget(RefPtr<gfx::DrawTarget>& aOutDT,
+                      RefPtr<layers::PersistentBufferProvider>& aOutProvider);
+
+  void RegisterAllocation();
+
+  void SetInitialState();
+
+  void SetErrorState();
+
   /**
    * This method is run at the end of the event-loop spin where
    * ScheduleStableStateCallback was called.
    *
    * We use it to unlock resources that need to be locked while drawing.
    */
   void OnStableState();
 
--- a/dom/canvas/test/webgl-mochitest/ensure-exts/test_EXT_disjoint_timer_query.html
+++ b/dom/canvas/test/webgl-mochitest/ensure-exts/test_EXT_disjoint_timer_query.html
@@ -6,14 +6,14 @@
     <link rel='stylesheet' href='/tests/SimpleTest/test.css'>
     <script src='ensure-ext.js'></script>
   </head>
   <body>
     <script>
 
 'use strict';
 Lastly_WithDraftExtsEnabled(function() {
-    EnsureExt('EXT_disjoint_timer_query');
+    EnsureExtFor('webgl', 'EXT_disjoint_timer_query');
 });
 
     </script>
   </body>
 </html>
--- a/dom/events/DataTransfer.cpp
+++ b/dom/events/DataTransfer.cpp
@@ -278,34 +278,36 @@ DataTransfer::SetEffectAllowedInt(uint32
 
 NS_IMETHODIMP
 DataTransfer::GetMozUserCancelled(bool* aUserCancelled)
 {
   *aUserCancelled = MozUserCancelled();
   return NS_OK;
 }
 
-FileList*
+already_AddRefed<FileList>
 DataTransfer::GetFiles(ErrorResult& aRv)
 {
-  return mItems->Files();
+  return mItems->Files(nsContentUtils::SubjectPrincipal());
 }
 
 NS_IMETHODIMP
 DataTransfer::GetFiles(nsIDOMFileList** aFileList)
 {
   if (!aFileList) {
     return NS_ERROR_FAILURE;
   }
 
-  ErrorResult rv;
-  RefPtr<FileList> files = GetFiles(rv);
-  if (NS_WARN_IF(rv.Failed())) {
-    return rv.StealNSResult();
-  }
+  // The XPCOM interface is only avaliable to system code, and thus we can
+  // assume the system principal. This is consistent with the previous behavour
+  // of this function, which also assumed the system principal.
+  //
+  // This code is also called from C++ code, which expects it to have a System
+  // Principal, and thus the SubjectPrincipal cannot be used.
+  RefPtr<FileList> files = mItems->Files(nsContentUtils::GetSystemPrincipal());
 
   files.forget(aFileList);
   return NS_OK;
 }
 
 already_AddRefed<DOMStringList>
 DataTransfer::GetTypes(ErrorResult& aRv) const
 {
@@ -661,16 +663,37 @@ DataTransfer::MozGetDataAt(JSContext* aC
 
   JS::Rooted<JS::Value> result(aCx);
   if (!VariantToJsval(aCx, data, aRetval)) {
     aRv = NS_ERROR_FAILURE;
     return;
   }
 }
 
+/* static */ bool
+DataTransfer::PrincipalMaySetData(const nsAString& aType,
+                                  nsIVariant* aData,
+                                  nsIPrincipal* aPrincipal)
+{
+  if (!nsContentUtils::IsSystemPrincipal(aPrincipal)) {
+    DataTransferItem::eKind kind = DataTransferItem::KindFromData(aData);
+    if (kind == DataTransferItem::KIND_OTHER) {
+      NS_WARNING("Disallowing adding non string/file types to DataTransfer");
+      return false;
+    }
+
+    if (aType.EqualsASCII(kFileMime) ||
+        aType.EqualsASCII(kFilePromiseMime)) {
+      NS_WARNING("Disallowing adding x-moz-file or x-moz-file-promize types to DataTransfer");
+      return false;
+    }
+  }
+  return true;
+}
+
 nsresult
 DataTransfer::SetDataAtInternal(const nsAString& aFormat, nsIVariant* aData,
                                 uint32_t aIndex,
                                 nsIPrincipal* aSubjectPrincipal)
 {
   if (aFormat.IsEmpty()) {
     return NS_OK;
   }
@@ -692,30 +715,18 @@ DataTransfer::SetDataAtInternal(const ns
     return NS_ERROR_DOM_INDEX_SIZE_ERR;
   }
 
   // Don't allow the custom type to be assigned.
   if (aFormat.EqualsLiteral(kCustomTypesMime)) {
     return NS_ERROR_TYPE_ERR;
   }
 
-  // Don't allow non-chrome to add non-string or file data. We'll block file
-  // promises as well which are used internally for drags to the desktop.
-  if (!nsContentUtils::IsSystemPrincipal(aSubjectPrincipal)) {
-    if (aFormat.EqualsLiteral(kFilePromiseMime) ||
-        aFormat.EqualsLiteral(kFileMime)) {
-      return NS_ERROR_DOM_SECURITY_ERR;
-    }
-
-    uint16_t type;
-    aData->GetDataType(&type);
-    if (type == nsIDataType::VTYPE_INTERFACE ||
-        type == nsIDataType::VTYPE_INTERFACE_IS) {
-      return NS_ERROR_DOM_SECURITY_ERR;
-    }
+  if (!PrincipalMaySetData(aFormat, aData, aSubjectPrincipal)) {
+    return NS_ERROR_DOM_SECURITY_ERR;
   }
 
   return SetDataWithPrincipal(aFormat, aData, aIndex, aSubjectPrincipal);
 }
 
 void
 DataTransfer::MozSetDataAt(JSContext* aCx, const nsAString& aFormat,
                            JS::Handle<JS::Value> aData,
@@ -831,17 +842,17 @@ DataTransfer::GetFilesAndDirectories(Err
     return nullptr;
   }
 
   RefPtr<Promise> p = Promise::Create(global, aRv);
   if (NS_WARN_IF(aRv.Failed())) {
     return nullptr;
   }
 
-  RefPtr<FileList> files = mItems->Files();
+  RefPtr<FileList> files = mItems->Files(nsContentUtils::SubjectPrincipal());
   if (NS_WARN_IF(!files)) {
     return nullptr;
   }
 
   Sequence<RefPtr<File>> filesSeq;
   files->ToSequence(filesSeq, aRv);
   if (NS_WARN_IF(aRv.Failed())) {
     return nullptr;
--- a/dom/events/DataTransfer.h
+++ b/dom/events/DataTransfer.h
@@ -141,17 +141,17 @@ public:
   void GetData(const nsAString& aFormat, nsAString& aData, ErrorResult& aRv);
 
   void SetData(const nsAString& aFormat, const nsAString& aData,
                ErrorResult& aRv);
 
   void ClearData(const mozilla::dom::Optional<nsAString>& aFormat,
                  mozilla::ErrorResult& aRv);
 
-  FileList* GetFiles(mozilla::ErrorResult& aRv);
+  already_AddRefed<FileList> GetFiles(mozilla::ErrorResult& aRv);
 
   already_AddRefed<Promise> GetFilesAndDirectories(ErrorResult& aRv);
 
   already_AddRefed<Promise> GetFiles(bool aRecursiveFlag, ErrorResult& aRv);
 
 
   void AddElement(Element& aElement, mozilla::ErrorResult& aRv);
 
@@ -266,16 +266,20 @@ public:
   nsresult Clone(nsISupports* aParent, EventMessage aEventMessage,
                  bool aUserCancelled, bool aIsCrossDomainSubFrameDrop,
                  DataTransfer** aResult);
 
   // converts some formats used for compatibility in aInFormat into aOutFormat.
   // Text and text/unicode become text/plain, and URL becomes text/uri-list
   void GetRealFormat(const nsAString& aInFormat, nsAString& aOutFormat) const;
 
+  static bool PrincipalMaySetData(const nsAString& aFormat,
+                                  nsIVariant* aData,
+                                  nsIPrincipal* aPrincipal);
+
 protected:
 
   // caches text and uri-list data formats that exist in the drag service or
   // clipboard for retrieval later.
   nsresult CacheExternalData(const char* aFormat, uint32_t aIndex,
                              nsIPrincipal* aPrincipal, bool aHidden);
 
   // caches the formats that exist in the drag service that were added by an
--- a/dom/events/DataTransferItem.cpp
+++ b/dom/events/DataTransferItem.cpp
@@ -98,41 +98,46 @@ DataTransferItem::SetData(nsIVariant* aD
         break;
       }
     }
 
     mData = nullptr;
     return;
   }
 
-  mKind = KIND_OTHER;
   mData = aData;
+  mKind = KindFromData(mData);
+}
 
+/* static */ DataTransferItem::eKind
+DataTransferItem::KindFromData(nsIVariant* aData)
+{
   nsCOMPtr<nsISupports> supports;
   nsresult rv = aData->GetAsISupports(getter_AddRefs(supports));
   if (NS_SUCCEEDED(rv) && supports) {
     // Check if we have one of the supported file data formats
     if (nsCOMPtr<nsIDOMBlob>(do_QueryInterface(supports)) ||
         nsCOMPtr<BlobImpl>(do_QueryInterface(supports)) ||
         nsCOMPtr<nsIFile>(do_QueryInterface(supports))) {
-      mKind = KIND_FILE;
-      return;
+      return KIND_FILE;
     }
   }
 
   nsAutoString string;
   // If we can't get the data type as a string, that means that the object
   // should be considered to be of the "other" type. This is impossible
   // through the APIs defined by the spec, but we provide extra Moz* APIs,
   // which allow setting of non-string data. We determine whether we can
   // consider it a string, by calling GetAsAString, and checking for success.
   rv = aData->GetAsAString(string);
   if (NS_SUCCEEDED(rv)) {
-    mKind = KIND_STRING;
+    return KIND_STRING;
   }
+
+  return KIND_OTHER;
 }
 
 void
 DataTransferItem::FillInExternalData()
 {
   if (mData) {
     return;
   }
@@ -230,36 +235,44 @@ DataTransferItem::FillInExternalData()
     NS_WARNING("Clipboard data provided by the OS does not match predicted kind");
   }
 #endif
 }
 
 already_AddRefed<File>
 DataTransferItem::GetAsFile(ErrorResult& aRv)
 {
+  return GetAsFileWithPrincipal(nsContentUtils::SubjectPrincipal(), aRv);
+}
+
+already_AddRefed<File>
+DataTransferItem::GetAsFileWithPrincipal(nsIPrincipal* aPrincipal, ErrorResult& aRv)
+{
   if (mKind != KIND_FILE) {
     return nullptr;
   }
 
-  nsCOMPtr<nsIVariant> data = Data(nsContentUtils::SubjectPrincipal(), aRv);
+  // This is done even if we have an mCachedFile, as it performs the necessary
+  // permissions checks to ensure that we are allowed to access this type.
+  nsCOMPtr<nsIVariant> data = Data(aPrincipal, aRv);
   if (NS_WARN_IF(!data || aRv.Failed())) {
     return nullptr;
   }
 
-  nsCOMPtr<nsISupports> supports;
-  aRv = data->GetAsISupports(getter_AddRefs(supports));
-  MOZ_ASSERT(!aRv.Failed() && supports,
-             "File objects should be stored as nsISupports variants");
-  if (aRv.Failed() || !supports) {
-    return nullptr;
-  }
-
   // Generate the dom::File from the stored data, caching it so that the
   // same object is returned in the future.
   if (!mCachedFile) {
+    nsCOMPtr<nsISupports> supports;
+    aRv = data->GetAsISupports(getter_AddRefs(supports));
+    MOZ_ASSERT(!aRv.Failed() && supports,
+               "File objects should be stored as nsISupports variants");
+    if (aRv.Failed() || !supports) {
+      return nullptr;
+    }
+
     if (nsCOMPtr<nsIDOMBlob> domBlob = do_QueryInterface(supports)) {
       Blob* blob = static_cast<Blob*>(domBlob.get());
       mCachedFile = blob->ToFile();
     } else if (nsCOMPtr<BlobImpl> blobImpl = do_QueryInterface(supports)) {
       MOZ_ASSERT(blobImpl->IsFile());
       mCachedFile = File::Create(mDataTransfer, blobImpl);
     } else if (nsCOMPtr<nsIFile> ifile = do_QueryInterface(supports)) {
       mCachedFile = File::CreateFromFile(mDataTransfer, ifile);
@@ -270,17 +283,24 @@ DataTransferItem::GetAsFile(ErrorResult&
 
   RefPtr<File> file = mCachedFile;
   return file.forget();
 }
 
 already_AddRefed<FileSystemEntry>
 DataTransferItem::GetAsEntry(ErrorResult& aRv)
 {
-  RefPtr<File> file = GetAsFile(aRv);
+  return GetAsEntryWithPrincipal(nsContentUtils::SubjectPrincipal(), aRv);
+}
+
+already_AddRefed<FileSystemEntry>
+DataTransferItem::GetAsEntryWithPrincipal(nsIPrincipal* aPrincipal,
+                                          ErrorResult& aRv)
+{
+  RefPtr<File> file = GetAsFileWithPrincipal(aPrincipal, aRv);
   if (NS_WARN_IF(aRv.Failed()) || !file) {
     return nullptr;
   }
 
   nsCOMPtr<nsIGlobalObject> global;
   // This is annoying, but DataTransfer may have various things as parent.
   nsCOMPtr<EventTarget> target =
     do_QueryInterface(mDataTransfer->GetParentObject());
--- a/dom/events/DataTransferItem.h
+++ b/dom/events/DataTransferItem.h
@@ -43,17 +43,20 @@ public:
     , mDataTransfer(aDataTransfer)
   {
     MOZ_ASSERT(mDataTransfer, "Must be associated with a DataTransfer");
   }
 
   already_AddRefed<DataTransferItem> Clone(DataTransfer* aDataTransfer) const;
 
   virtual JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
+
+  // NOTE: This accesses the subject principal, and should not be called from C++
   void GetAsString(FunctionStringCallback* aCallback, ErrorResult& aRv);
+
   void GetKind(nsAString& aKind) const
   {
     switch (mKind) {
     case KIND_FILE:
       aKind = NS_LITERAL_STRING("file");
       return;
     case KIND_STRING:
       aKind = NS_LITERAL_STRING("string");
@@ -74,19 +77,25 @@ public:
   {
     return mKind;
   }
   void SetKind(eKind aKind)
   {
     mKind = aKind;
   }
 
+  // NOTE: This accesses the subject principal, and should not be called from C++
   already_AddRefed<File> GetAsFile(ErrorResult& aRv);
+  already_AddRefed<File> GetAsFileWithPrincipal(nsIPrincipal* aPrincipal,
+                                                ErrorResult& aRv);
 
+  // NOTE: This accesses the subject principal, and should not be called from C++
   already_AddRefed<FileSystemEntry> GetAsEntry(ErrorResult& aRv);
+  already_AddRefed<FileSystemEntry> GetAsEntryWithPrincipal(nsIPrincipal* aPrincipal,
+                                                            ErrorResult& aRv);
 
   DataTransfer* GetParentObject() const
   {
     return mDataTransfer;
   }
 
   nsIPrincipal* Principal() const
   {
@@ -115,16 +124,18 @@ public:
   {
     return mChromeOnly;
   }
   void SetChromeOnly(bool aChromeOnly)
   {
     mChromeOnly = aChromeOnly;
   }
 
+  static eKind KindFromData(nsIVariant* aData);
+
 private:
   ~DataTransferItem() {}
   already_AddRefed<File> CreateFileFromInputStream(nsIInputStream* aStream);
 
   // The index in the 2d mIndexedItems array
   uint32_t mIndex;
 
   bool mChromeOnly;
--- a/dom/events/DataTransferItemList.cpp
+++ b/dom/events/DataTransferItemList.cpp
@@ -148,21 +148,27 @@ DataTransferItemList::Add(const nsAStrin
     return nullptr;
   }
 
   nsCOMPtr<nsIVariant> data(new storage::TextVariant(aData));
 
   nsAutoString format;
   mDataTransfer->GetRealFormat(aType, format);
 
+  nsIPrincipal* subjectPrincipal = nsContentUtils::SubjectPrincipal();
+
+  if (!DataTransfer::PrincipalMaySetData(format, data, subjectPrincipal)) {
+    aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
+    return nullptr;
+  }
+
   // We add the textual data to index 0. We set aInsertOnly to true, as we don't
   // want to update an existing entry if it is already present, as per the spec.
   RefPtr<DataTransferItem> item =
-    SetDataWithPrincipal(format, data, 0,
-                         nsContentUtils::SubjectPrincipal(),
+    SetDataWithPrincipal(format, data, 0, subjectPrincipal,
                          /* aInsertOnly = */ true,
                          /* aHidden = */ false,
                          aRv);
   if (NS_WARN_IF(aRv.Failed())) {
     return nullptr;
   }
   MOZ_ASSERT(item->Kind() != DataTransferItem::KIND_FILE);
 
@@ -178,42 +184,82 @@ DataTransferItemList::Add(File& aData, E
 
   nsCOMPtr<nsISupports> supports = do_QueryObject(&aData);
   nsCOMPtr<nsIWritableVariant> data = new nsVariant();
   data->SetAsISupports(supports);
 
   nsAutoString type;
   aData.GetType(type);
 
+  nsIPrincipal* subjectPrincipal = nsContentUtils::SubjectPrincipal();
+
+  if (!DataTransfer::PrincipalMaySetData(type, data, subjectPrincipal)) {
+    aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
+    return nullptr;
+  }
 
   // We need to add this as a new item, as multiple files can't exist in the
   // same item in the Moz DataTransfer layout. It will be appended at the end of
   // the internal specced layout.
   uint32_t index = mIndexedItems.Length();
   RefPtr<DataTransferItem> item =
-    SetDataWithPrincipal(type, data, index,
-                         nsContentUtils::SubjectPrincipal(),
-                         true, false, aRv);
+    SetDataWithPrincipal(type, data, index, subjectPrincipal,
+                         /* aInsertOnly = */ true,
+                         /* aHidden = */ false,
+                         aRv);
   if (NS_WARN_IF(aRv.Failed())) {
     return nullptr;
   }
   MOZ_ASSERT(item->Kind() == DataTransferItem::KIND_FILE);
 
   return item;
 }
 
-FileList*
-DataTransferItemList::Files()
+already_AddRefed<FileList>
+DataTransferItemList::Files(nsIPrincipal* aPrincipal)
 {
+  // The DataTransfer can hold data with varying principals, coming from
+  // different windows. This means that permissions checks need to be made when
+  // accessing data from the DataTransfer. With the accessor methods, this is
+  // checked by DataTransferItem::Data(), however with files, we keep a cached
+  // live copy of the files list for spec compliance.
+  //
+  // A DataTransfer is only exposed to one webpage, and chrome code. The chrome
+  // code should be able to see all files on the DataTransfer, while the webpage
+  // should only be able to see the files it can see. As chrome code doesn't
+  // need as strict spec compliance as web visible code, we generate a new
+  // FileList object every time you access the Files list from chrome code, but
+  // re-use the cached one when accessing from non-chrome code.
+  //
+  // It is not legal to expose an identical DataTransfer object is to multiple
+  // different principals without using the `Clone` method or similar to copy it
+  // first. If that happens, this method will assert, and return nullptr in
+  // release builds. If this functionality is required in the future, a more
+  // advanced caching mechanism for the FileList objects will be required.
+  RefPtr<FileList> files;
+  if (nsContentUtils::IsSystemPrincipal(aPrincipal)) {
+    files = new FileList(static_cast<nsIDOMDataTransfer*>(mDataTransfer));
+    GenerateFiles(files, aPrincipal);
+    return files.forget();
+  }
+
   if (!mFiles) {
     mFiles = new FileList(static_cast<nsIDOMDataTransfer*>(mDataTransfer));
+    mFilesPrincipal = aPrincipal;
     RegenerateFiles();
   }
 
-  return mFiles;
+  if (!aPrincipal->Subsumes(mFilesPrincipal)) {
+    MOZ_ASSERT(false, "This DataTransfer should only be accessed by the system "
+               "and a single principal");
+    return nullptr;
+  }
+
+  files = mFiles;
+  return files.forget();
 }
 
 void
 DataTransferItemList::MozRemoveByTypeAt(const nsAString& aType,
                                         uint32_t aIndex,
                                         ErrorResult& aRv)
 {
   if (NS_WARN_IF(mDataTransfer->IsReadOnly() ||
@@ -486,30 +532,39 @@ DataTransferItemList::RegenerateFiles()
   // list. That way we can avoid the unnecessary work if the user never touches
   // the files list.
   if (mFiles) {
     // We clear the list rather than performing smaller updates, because it
     // simplifies the logic greatly on this code path, which should be very
     // infrequently used.
     mFiles->Clear();
 
-    uint32_t count = Length();
-    for (uint32_t i = 0; i < count; i++) {
-      ErrorResult rv;
-      bool found;
-      RefPtr<DataTransferItem> item = IndexedGetter(i, found, rv);
-      if (NS_WARN_IF(!found || rv.Failed())) {
+    DataTransferItemList::GenerateFiles(mFiles, mFilesPrincipal);
+  }
+}
+
+void
+DataTransferItemList::GenerateFiles(FileList* aFiles,
+                                    nsIPrincipal* aFilesPrincipal)
+{
+  MOZ_ASSERT(aFiles);
+  MOZ_ASSERT(aFilesPrincipal);
+  uint32_t count = Length();
+  for (uint32_t i = 0; i < count; i++) {
+    ErrorResult rv;
+    bool found;
+    RefPtr<DataTransferItem> item = IndexedGetter(i, found, rv);
+    if (NS_WARN_IF(!found || rv.Failed())) {
+      continue;
+    }
+
+    if (item->Kind() == DataTransferItem::KIND_FILE) {
+      RefPtr<File> file = item->GetAsFileWithPrincipal(aFilesPrincipal, rv);
+      if (NS_WARN_IF(rv.Failed() || !file)) {
         continue;
       }
-
-      if (item->Kind() == DataTransferItem::KIND_FILE) {
-        RefPtr<File> file = item->GetAsFile(rv);
-        if (NS_WARN_IF(rv.Failed() || !file)) {
-          continue;
-        }
-        mFiles->Append(file);
-      }
+      aFiles->Append(file);
     }
   }
 }
 
 } // namespace mozilla
 } // namespace dom
--- a/dom/events/DataTransferItemList.h
+++ b/dom/events/DataTransferItemList.h
@@ -66,17 +66,17 @@ public:
 
   void Clear(ErrorResult& aRv);
 
   already_AddRefed<DataTransferItem>
   SetDataWithPrincipal(const nsAString& aType, nsIVariant* aData,
                        uint32_t aIndex, nsIPrincipal* aPrincipal,
                        bool aInsertOnly, bool aHidden, ErrorResult& aRv);
 
-  FileList* Files();
+  already_AddRefed<FileList> Files(nsIPrincipal* aPrincipal);
 
   // Moz-style helper methods for interacting with the stored data
   void MozRemoveByTypeAt(const nsAString& aType, uint32_t aIndex,
                          ErrorResult& aRv);
   DataTransferItem* MozItemByTypeAt(const nsAString& aType, uint32_t aIndex);
   const nsTArray<RefPtr<DataTransferItem>>* MozItemsAt(uint32_t aIndex);
   uint32_t MozItemCount() const;
 
@@ -89,22 +89,25 @@ public:
 
 private:
   void ClearDataHelper(DataTransferItem* aItem, uint32_t aIndexHint,
                        uint32_t aMozOffsetHint, ErrorResult& aRv);
   DataTransferItem* AppendNewItem(uint32_t aIndex, const nsAString& aType,
                                   nsIVariant* aData, nsIPrincipal* aPrincipal,
                                   bool aHidden);
   void RegenerateFiles();
+  void GenerateFiles(FileList* aFiles, nsIPrincipal* aFilesPrincipal);
 
   ~DataTransferItemList() {}
 
   RefPtr<DataTransfer> mDataTransfer;
   bool mIsExternal;
   RefPtr<FileList> mFiles;
+  // The principal for which mFiles is cached
+  nsCOMPtr<nsIPrincipal> mFilesPrincipal;
   nsTArray<RefPtr<DataTransferItem>> mItems;
   nsTArray<nsTArray<RefPtr<DataTransferItem>>> mIndexedItems;
 };
 
 } // namespace dom
 } // namespace mozilla
 
 #endif // mozilla_dom_DataTransferItemList_h
--- a/dom/events/test/test_DataTransferItemList.html
+++ b/dom/events/test/test_DataTransferItemList.html
@@ -74,16 +74,18 @@
       is(files[0], file, "It should be the same file as the file we originally created");
       is(file, e.dataTransfer.mozGetDataAt("text/html", 1),
          "It should be stored in index 1 for mozGetDataAt");
 
       var file2 = new File(['<a id="c"><b id="d">yo!</b></a>'], "myotherfile.html",
                            {type: "text/html"});
       dtList.add(file2);
 
+      todo(files.length == 2, "This test has chrome privileges, so the FileList objects aren't updated live");
+      files = e.dataTransfer.files;
       is(files.length, 2, "The files property should have been updated in place");
       is(files[1], file2, "It should be the same file as the file we originally created");
       is(file2, e.dataTransfer.mozGetDataAt("text/html", 2),
          "It should be stored in index 2 for mozGetDataAt");
 
       var oldLength = dtList.length;
       var randomString = "foo!";
       e.dataTransfer.mozSetDataAt("random/string", randomString, 3);
@@ -93,16 +95,19 @@
       var file3 = new File(['<a id="e"><b id="f">heya!</b></a>'], "yetanotherfile.html",
                            {type: "text/html"});
       e.dataTransfer.mozSetDataAt("random/string", file3, 3);
       is(oldLength + 1, dtList.length,
          "Replacing the entry with a file should add it to the list!");
       is(dtList[oldLength].getAsFile(), file3, "It should be stored in the last index as a file");
       is(dtList[oldLength].type, "random/string", "It should have the correct type");
       is(dtList[oldLength].kind, "file", "It should have the correct kind");
+
+      todo(files.length == 3, "This test has chrome privileges, so the FileList objects aren't updated live");
+      files = e.dataTransfer.files;
       is(files[files.length - 1], file3, "It should also be in the files list");
 
       oldLength = dtList.length;
       var nonstring = {};
       e.dataTransfer.mozSetDataAt("jsobject", nonstring, 0);
       is(oldLength + 1, dtList.length,
          "Adding a non-string object using the mozAPIs to index 0 should add an item to the dataTransfer");
       is(dtList[oldLength].type, "jsobject", "It should have the correct type");
--- a/dom/svg/test/test_tabindex.html
+++ b/dom/svg/test/test_tabindex.html
@@ -9,17 +9,17 @@
 </head>
 <body>
 <svg xmlns="http://www.w3.org/2000/svg" overflow="visible">
   <foreignObject id="f" x="0" y="0" width="200" height="60" tabindex="0">
     <body xmlns="http://www.w3.org/1999/xhtml">
       <p>Here is a paragraph that requires word wrap</p>
     </body>
   </foreignObject>
-  <rect id="r" x="0" y="70" width="100" height="100" fill="yellow" tabIndex="1"/>
+  <rect id="r" x="0" y="70" width="100" height="100" fill="yellow" tabindex="1"/>
   <text id="t" x="0" y="200" tabindex="2">
         This is SVG text
   </text>
 </svg>
 <pre id="test">
 <script class="testbody" type="text/javascript">
 SimpleTest.waitForExplicitFinish();
 
--- a/dom/tests/mochitest/bugs/mochitest.ini
+++ b/dom/tests/mochitest/bugs/mochitest.ini
@@ -71,17 +71,16 @@ skip-if = (buildapp == 'b2g' && toolkit 
 [test_bug393974.html]
 [test_bug394769.html]
 [test_bug396843.html]
 skip-if = (buildapp == 'b2g' && (toolkit != 'gonk' || debug))
 [test_bug400204.html]
 [test_bug404748.html]
 [test_bug406375.html]
 skip-if = buildapp == 'mulet' || buildapp == 'b2g' || toolkit == 'android'
-[test_bug411103.html]
 [test_bug414291.html]
 tags = openwindow
 [test_bug427744.html]
 skip-if = buildapp == 'mulet' || buildapp == 'b2g' || toolkit == 'android'
 [test_bug42976.html]
 [test_bug430276.html]
 [test_bug437361.html]
 skip-if = buildapp == 'mulet' || buildapp == 'b2g' || toolkit == 'android' # b2g(dom.disable_open_during_load not implemented in b2g, showmodaldialog) b2g-debug(dom.disable_open_during_load not implemented in b2g, showmodaldialog) b2g-desktop(dom.disable_open_during_load not implemented in b2g, showmodaldialog)
deleted file mode 100644
--- a/dom/tests/mochitest/bugs/test_bug411103.html
+++ /dev/null
@@ -1,188 +0,0 @@
-<!DOCTYPE html>
-<html>
-<!--
-https://bugzilla.mozilla.org/show_bug.cgi?id=411103
--->
-<head>
-  <title>Test for Bug 411103</title>
-  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
-  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
-</head>
-<body>
-<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=411103">Mozilla Bug 411103</a>
-<p id="display"></p>
-<div id="content" style="display: none"></div>
-
-<!-- XML's createElement and createElementNS aren't HTML's, of course -->
-<iframe src="data:application/xml,%3Cfoo%3EXML%3C/foo%3E" name="xmlWindow"></iframe>
-
-<!-- for good measure... -->
-<iframe src="data:application/xhtml+xml,%3Chtml%20xmlns=%22http://www.w3.org/1999/xhtml%22%3E%3Cbody%3E%3Cp%3EXHTML%3C/p%3E%3C/body%3E%3C/html%3E"
-        name="xhtmlWindow"></iframe>
-
-<pre id="test">
-<script class="testbody" type="text/javascript">
-
-SimpleTest.waitForExplicitFinish();
-
-/** Test for Bug 411103 **/
-var allNSTests =
-  [
-   { args: [undefined, undefined] },
-   { args: [null, undefined] },
-   { args: [undefined, null] },
-   { args: [null, null] },
-   { args: [null, ""], code: 5 },
-   { args: ["", null] },
-   { args: ["", ""], code: 5 },
-   { args: [null, "<div>"], code: 5 },
-   { args: [null, "0div"], code: 5 },
-   { args: [null, "di v"], code: 5 },
-   { args: [null, "di<v"], code: 5 },
-   { args: [null, "-div"], code: 5 },
-   { args: [null, ".div"], code: 5 },
-   { args: ["http://example.com/", "<div>"], code: 5 },
-   { args: ["http://example.com/", "0div"], code: 5 },
-   { args: ["http://example.com/", "di<v"], code: 5 },
-   { args: ["http://example.com/", "-div"], code: 5 },
-   { args: ["http://example.com/", ".div"], code: 5 },
-   { args: [null, ":div"], code: 14 },
-   { args: [null, "div:"], code: 14 },
-   { args: ["http://example.com/", ":div"], code: 14 },
-   { args: ["http://example.com/", "div:"], code: 14 },
-   { args: [null, "d:iv"], code: 14 },
-   { args: [null, "a:b:c"], code: 14, message: "valid XML name, invalid QName" },
-   { args: ["http://example.com/", "a:b:c"], code: 14, message: "valid XML name, invalid QName" },
-   { args: [null, "a::c"], code: 14, message: "valid XML name, invalid QName" },
-   { args: ["http://example.com/", "a::c"], code: 14, message: "valid XML name, invalid QName" },
-   { args: ["http://example.com/", "a:0"], code: 5, message: "valid XML name, not a valid QName" },
-   { args: ["http://example.com/", "0:a"], code: 5, message: "0 at start makes it not a valid XML name" },
-   { args: ["http://example.com/", "a:_"] },
-   { args: ["http://example.com/", "a:\u0BC6"], code: 14,
-     message: "non-ASCII character after colon is CombiningChar, which is " +
-              "NCNameChar but not (Letter | \"_\") so invalid at start of " +
-              "NCName (but still a valid XML name, hence not 5)" },
-   { args: ["http://example.com/", "\u0BC6:a"], code: 14,
-     message: "non-ASCII character after colon is CombiningChar, which is " +
-              "NCNameChar but not (Letter | \"_\") so invalid at start of " +
-              "NCName (Gecko chooses to throw 14 here, but either is valid " +
-              "as this is both an invalid XML name and an invalid QName)" },
-   { args: ["http://example.com/", "a:a\u0BC6"] },
-   { args: ["http://example.com/", "a\u0BC6:a"] },
-   { args: ["http://example.com/", "xml:test"], code: 14, message: "binding xml prefix wrong" },
-   { args: ["http://example.com/", "xmlns:test"], code: 14, message: "binding xmlns prefix wrong" },
-   { args: ["http://www.w3.org/2000/xmlns/", "x:test"], code: 14, message: "binding namespace namespace to wrong prefix" },
-   { args: ["http://www.w3.org/2000/xmlns/", "xmlns:test"] },
-   { args: ["http://www.w3.org/XML/1998/namespace", "xml:test"] },
-   { args: ["http://www.w3.org/XML/1998/namespace", "x:test"] },
-  ];
-
-var allNoNSTests =
-  [
-   { args: [undefined] },
-   { args: [null] },
-   { args: [""], code: 5 },
-   { args: ["<div>"], code: 5 },
-   { args: ["0div"], code: 5 },
-   { args: ["di v"], code: 5 },
-   { args: ["di<v"], code: 5 },
-   { args: ["-div"], code: 5 },
-   { args: [".div"], code: 5 },
-   { args: [":"], message: "valid XML name, invalid QName" },
-   { args: [":div"], message: "valid XML name, invalid QName" },
-   { args: ["div:"], message: "valid XML name, invalid QName" },
-   { args: ["d:iv"] },
-   { args: ["a:b:c"], message: "valid XML name, invalid QName" },
-   { args: ["a::c"], message: "valid XML name, invalid QName" },
-   { args: ["a::c:"], message: "valid XML name, invalid QName" },
-   { args: ["a:0"], message: "valid XML name, not a valid QName" },
-   { args: ["0:a"], code: 5, message: "0 at start makes it not a valid XML name" },
-   { args: ["a:_"] },
-   { args: ["a:\u0BC6"],
-     message: "non-ASCII character after colon is CombiningChar, which is " +
-              "valid in pre-namespace XML" },
-   { args: ["\u0BC6:a"], code: 5, message: "not a valid start character" },
-   { args: ["a:a\u0BC6"] },
-   { args: ["a\u0BC6:a"] },
-   { args: ["xml:test"] },
-   { args: ["xmlns:test"] },
-   { args: ["x:test"] },
-   { args: ["xmlns:test"] },
-  ];
-
-function sourceify(v)
-{
-  switch (typeof v)
-  {
-    case "undefined":
-      return v;
-
-    case "string":
-      return '"' + v.replace('"', '\\"') + '"';
-
-    default:
-      return String(v);
-  }
-}
-
-function sourceifyArgs(args)
-{
-  var copy = new Array(args.length);
-  for (var i = 0, sz = args.length; i < sz; i++)
-    copy[i] = sourceify(args[i]);
-
-  return copy.join(", ");
-}
-
-function runTests(tests, methodName, document)
-{
-  for (var i = 0, sz = tests.length; i < sz; i++)
-  {
-    var test = tests[i];
-  
-    var argStr = sourceifyArgs(test.args);
-    try
-    {
-      document[methodName].apply(document, test.args);
-      var msg = "expected no exception for " +
-                "document." + methodName + "(" + argStr + ")";
-      if ("message" in test)
-        msg += "; " + test.message;
-      ok(!("code" in test), msg);
-    }
-    catch (e)
-    {
-      msg = "exception code for document." + methodName + "(" + argStr + ")";
-      if ("message" in test)
-        msg += "; " + test.message;
-      is(e.code, test.code || "no exception", msg);
-    }
-  }
-}
-
-
-function run()
-{
-  // HTML document
-  runTests(allNSTests, "createElementNS", document);
-  runTests(allNoNSTests, "createElement", document);
-
-  // XML document
-  var xmlDocument = window.frames.xmlWindow.document;
-  runTests(allNSTests, "createElementNS", xmlDocument);
-  runTests(allNoNSTests, "createElement", xmlDocument);
-
-  // XHTML document, for good measure
-  var xhtmlDocument = window.frames.xhtmlWindow.document;
-  runTests(allNSTests, "createElementNS", xhtmlDocument);
-  runTests(allNoNSTests, "createElement", xhtmlDocument);
-
-  SimpleTest.finish();
-}
-
-window.addEventListener("load", run, false);
-
-</script>
-</pre>
-</body>
-</html>
--- a/gfx/layers/ipc/CompositorBridgeChild.cpp
+++ b/gfx/layers/ipc/CompositorBridgeChild.cpp
@@ -54,16 +54,17 @@ static StaticRefPtr<CompositorBridgeChil
 
 Atomic<int32_t> CompositableForwarder::sSerialCounter(0);
 
 CompositorBridgeChild::CompositorBridgeChild(ClientLayerManager *aLayerManager)
   : mLayerManager(aLayerManager)
   , mCanSend(false)
   , mFwdTransactionId(0)
   , mMessageLoop(MessageLoop::current())
+  , mSectionAllocator(nullptr)
 {
   MOZ_ASSERT(NS_IsMainThread());
 }
 
 CompositorBridgeChild::~CompositorBridgeChild()
 {
   if (mCanSend) {
     gfxCriticalError() << "CompositorBridgeChild was not deinitialized";
@@ -95,16 +96,21 @@ CompositorBridgeChild::Destroy()
   if (!mCanSend) {
     return;
   }
 
   for (size_t i = 0; i < mTexturePools.Length(); i++) {
     mTexturePools[i]->Destroy();
   }
 
+  if (mSectionAllocator) {
+    delete mSectionAllocator;
+    mSectionAllocator = nullptr;
+  }
+
   // Destroying the layer manager may cause all sorts of things to happen, so
   // let's make sure there is still a reference to keep this alive whatever
   // happens.
   RefPtr<CompositorBridgeChild> selfRef = this;
 
   if (mLayerManager) {
     mLayerManager->Destroy();
     mLayerManager = nullptr;
@@ -955,16 +961,31 @@ CompositorBridgeChild::HandleMemoryPress
 void
 CompositorBridgeChild::ClearTexturePool()
 {
   for (size_t i = 0; i < mTexturePools.Length(); i++) {
     mTexturePools[i]->Clear();
   }
 }
 
+FixedSizeSmallShmemSectionAllocator*
+CompositorBridgeChild::GetTileLockAllocator()
+{
+  MOZ_ASSERT(IPCOpen());
+  if (!IPCOpen()) {
+    return nullptr;
+  }
+
+  if (!mSectionAllocator) {
+    mSectionAllocator = new FixedSizeSmallShmemSectionAllocator(this);
+  }
+  return mSectionAllocator;
+}
+
+
 PTextureChild*
 CompositorBridgeChild::CreateTexture(const SurfaceDescriptor& aSharedData,
                                      LayersBackend aLayersBackend,
                                      TextureFlags aFlags,
                                      uint64_t aSerial)
 {
   return PCompositorBridgeChild::SendPTextureConstructor(aSharedData, aLayersBackend, aFlags, 0 /* FIXME? */, aSerial);
 }
--- a/gfx/layers/ipc/CompositorBridgeChild.h
+++ b/gfx/layers/ipc/CompositorBridgeChild.h
@@ -189,16 +189,18 @@ public:
 
   virtual void CancelWaitForRecycle(uint64_t aTextureId) override;
 
   TextureClientPool* GetTexturePool(LayersBackend aBackend,
                                     gfx::SurfaceFormat aFormat,
                                     TextureFlags aFlags);
   void ClearTexturePool();
 
+  virtual FixedSizeSmallShmemSectionAllocator* GetTileLockAllocator() override;
+
   void HandleMemoryPressure();
 
   virtual MessageLoop* GetMessageLoop() const override { return mMessageLoop; }
 
   virtual base::ProcessId GetParentPid() const override { return OtherPid(); }
 
   virtual bool AllocUnsafeShmem(size_t aSize,
                                 mozilla::ipc::SharedMemory::SharedMemoryType aShmType,
@@ -307,14 +309,16 @@ private:
    */
   nsDataHashtable<nsUint64HashKey, RefPtr<TextureClient> > mTexturesWaitingRecycled;
 
   MessageLoop* mMessageLoop;
 
   AutoTArray<RefPtr<TextureClientPool>,2> mTexturePools;
 
   uint64_t mProcessToken;
+
+  FixedSizeSmallShmemSectionAllocator* mSectionAllocator;
 };
 
 } // namespace layers
 } // namespace mozilla
 
 #endif // mozilla_layers_CompositorBrigedChild_h
--- a/gfx/layers/ipc/ISurfaceAllocator.h
+++ b/gfx/layers/ipc/ISurfaceAllocator.h
@@ -311,36 +311,17 @@ public:
 
   // can be called on the compositor process.
   static void FreeShmemSection(ShmemSection& aShmemSection);
 
   void ShrinkShmemSectionHeap();
 
   ShmemAllocator* GetShmAllocator() { return mShmProvider->AsShmemAllocator(); }
 
-  /**
-    * In order to avoid shutdown crashes, we need to test for mShmProvider->AsShmemAllocator()
-    * here. Basically, there's a case where we have the following class hierarchy:
-    *
-    * ClientIPCAllocator -> TextureForwarder -> CompositableForwarder -> ShadowLayerForwarder
-    *
-    * In ShadowLayerForwarder's dtor, we tear down the actor and close the IPC channel.
-    * In TextureForwarder's dtor, we destroy the FixedSizeSmallShmemAllocator and that in turn calls
-    * ClientIPCAllocator::IPCOpen() to determine whether we can dealloc some shmem regions.
-    *
-    * This does not work. In the above class diagram, as the ShadowLayerForwarder's dtor has run
-    * its course, the ClientIPCAllocator object we're holding on to is now just a plain
-    * ClientIPCAllocator and so we call ClientIPCAllocator's IPCOpen() which unconditionally
-    * returns true. We therefore have to rely on AsShmemAllocator() to determine whether we can
-    * do these deallocs as ClientIPCAllocator::AsShmemAllocator() returns nullptr.
-    *
-    * Ideally, we should move a lot of this destruction work into non-destructor Destroy() methods
-    * which do cleanup before we destroy the objects.
-    */
-  bool IPCOpen() const { return mShmProvider->AsShmemAllocator() && mShmProvider->IPCOpen(); }
+  bool IPCOpen() const { return mShmProvider->IPCOpen(); }
 
 protected:
   std::vector<mozilla::ipc::Shmem> mUsedShmems;
   ClientIPCAllocator* mShmProvider;
 };
 
 } // namespace layers
 } // namespace mozilla
deleted file mode 100644
--- a/gfx/layers/ipc/TextureForwarder.cpp
+++ /dev/null
@@ -1,40 +0,0 @@
-/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
- * vim: sw=2 ts=8 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/. */
-
-#include "mozilla/layers/TextureForwarder.h"
-
-namespace mozilla {
-namespace layers {
-
-TextureForwarder::TextureForwarder()
-  : mSectionAllocator(nullptr)
-{
-}
-
-TextureForwarder::~TextureForwarder()
-{
-  if (mSectionAllocator) {
-    delete mSectionAllocator;
-  }
-}
-
-FixedSizeSmallShmemSectionAllocator*
-TextureForwarder::GetTileLockAllocator()
-{
-  MOZ_ASSERT(IPCOpen());
-  if (!IPCOpen()) {
-    return nullptr;
-  }
-
-  if (!mSectionAllocator) {
-    mSectionAllocator = new FixedSizeSmallShmemSectionAllocator(this);
-  }
-  return mSectionAllocator;
-}
-
-} // namespace layers
-} // namespace mozilla
--- a/gfx/layers/ipc/TextureForwarder.h
+++ b/gfx/layers/ipc/TextureForwarder.h
@@ -17,33 +17,26 @@
 #include "mozilla/gfx/Rect.h"
 
 namespace mozilla {
 namespace layers {
 
 class TextureForwarder : public ClientIPCAllocator
 {
 public:
-  TextureForwarder();
-
-  virtual ~TextureForwarder();
-
   /**
    * Create a TextureChild/Parent pair as as well as the TextureHost on the parent side.
    */
   virtual PTextureChild* CreateTexture(
     const SurfaceDescriptor& aSharedData,
     LayersBackend aLayersBackend,
     TextureFlags aFlags,
     uint64_t aSerial) = 0;
 
   virtual TextureForwarder* AsTextureForwarder() override { return this; }
 
-  virtual FixedSizeSmallShmemSectionAllocator* GetTileLockAllocator();
-
-private:
-  FixedSizeSmallShmemSectionAllocator* mSectionAllocator;
+  virtual FixedSizeSmallShmemSectionAllocator* GetTileLockAllocator() { return nullptr; }
 };
 
 } // namespace layers
 } // namespace mozilla
 
 #endif
--- a/gfx/layers/moz.build
+++ b/gfx/layers/moz.build
@@ -369,17 +369,16 @@ UNIFIED_SOURCES += [
     'ipc/RemoteContentController.cpp',
     'ipc/ShadowLayerChild.cpp',
     'ipc/ShadowLayerParent.cpp',
     'ipc/ShadowLayers.cpp',
     'ipc/SharedBufferManagerChild.cpp',
     'ipc/SharedBufferManagerParent.cpp',
     'ipc/SharedPlanarYCbCrImage.cpp',
     'ipc/SharedRGBImage.cpp',
-    'ipc/TextureForwarder.cpp',
     'LayerScope.cpp',
     'LayersLogging.cpp',
     'LayerSorter.cpp',
     'LayersTypes.cpp',
     'opengl/CompositingRenderTargetOGL.cpp',
     'opengl/CompositorOGL.cpp',
     'opengl/GLBlitTextureImageHelper.cpp',
     'opengl/OGLShaderProgram.cpp',
--- a/layout/generic/nsBlockFrame.cpp
+++ b/layout/generic/nsBlockFrame.cpp
@@ -211,20 +211,16 @@ nsBlockFrame::InitDebugFlags()
         ShowDebugFlags();
       }
     }
   }
 }
 
 #endif
 
-// add in a sanity check for absurdly deep frame trees.  See bug 42138
-// can't just use IsFrameTreeTooDeep() because that method has side effects we don't want
-#define MAX_DEPTH_FOR_LIST_RENUMBERING 200  // 200 open displayable tags is pretty unrealistic
-
 //----------------------------------------------------------------------
 
 // Debugging support code
 
 #ifdef DEBUG
 const char* nsBlockFrame::kReflowCommandType[] = {
   "ContentChanged",
   "StyleChanged",
@@ -673,17 +669,17 @@ nsBlockFrame::GetMinISize(nsRenderingCon
   AutoNoisyIndenter indenter(gNoisyIntrinsic);
 #endif
 
   for (nsBlockFrame* curFrame = this; curFrame;
        curFrame = static_cast<nsBlockFrame*>(curFrame->GetNextContinuation())) {
     curFrame->LazyMarkLinesDirty();
   }
 
-  if (RenumberLists(PresContext())) {
+  if (RenumberList()) {
     AddStateBits(NS_FRAME_HAS_DIRTY_CHILDREN);
   }
   if (GetStateBits() & NS_BLOCK_NEEDS_BIDI_RESOLUTION)
     ResolveBidi();
   InlineMinISizeData data;
   for (nsBlockFrame* curFrame = this; curFrame;
        curFrame = static_cast<nsBlockFrame*>(curFrame->GetNextContinuation())) {
     for (line_iterator line = curFrame->begin_lines(), line_end = curFrame->end_lines();
@@ -761,17 +757,17 @@ nsBlockFrame::GetPrefISize(nsRenderingCo
   AutoNoisyIndenter indenter(gNoisyIntrinsic);
 #endif
 
   for (nsBlockFrame* curFrame = this; curFrame;
        curFrame = static_cast<nsBlockFrame*>(curFrame->GetNextContinuation())) {
     curFrame->LazyMarkLinesDirty();
   }
 
-  if (RenumberLists(PresContext())) {
+  if (RenumberList()) {
     AddStateBits(NS_FRAME_HAS_DIRTY_CHILDREN);
   }
   if (GetStateBits() & NS_BLOCK_NEEDS_BIDI_RESOLUTION)
     ResolveBidi();
   InlinePrefISizeData data;
   for (nsBlockFrame* curFrame = this; curFrame;
        curFrame = static_cast<nsBlockFrame*>(curFrame->GetNextContinuation())) {
     for (line_iterator line = curFrame->begin_lines(), line_end = curFrame->end_lines();
@@ -1107,17 +1103,17 @@ nsBlockFrame::Reflow(nsPresContext*     
   // to continually recompute it.
   BlockReflowInput state(*reflowInput, aPresContext, this,
                            blockStartMarginRoot, blockEndMarginRoot,
                            needFloatManager, consumedBSize);
 
   if (GetStateBits() & NS_BLOCK_NEEDS_BIDI_RESOLUTION)
     static_cast<nsBlockFrame*>(FirstContinuation())->ResolveBidi();
 
-  if (RenumberLists(aPresContext)) {
+  if (RenumberList()) {
     AddStateBits(NS_FRAME_HAS_DIRTY_CHILDREN);
   }
 
 #ifdef DEBUG
   // Between when we drain pushed floats and when we complete reflow,
   // we're allowed to have multiple continuations of the same float on
   // our floats list, since a first-in-flow might get pushed to a later
   // continuation of its containing block.  But it's not permitted
@@ -2924,55 +2920,46 @@ nsBlockFrame::MoveChildFramesOfLine(nsLi
 
 nsresult 
 nsBlockFrame::AttributeChanged(int32_t         aNameSpaceID,
                                nsIAtom*        aAttribute,
                                int32_t         aModType)
 {
   nsresult rv = nsContainerFrame::AttributeChanged(aNameSpaceID,
                                                    aAttribute, aModType);
-
   if (NS_FAILED(rv)) {
     return rv;
   }
-  if (nsGkAtoms::start == aAttribute ||
-      (nsGkAtoms::reversed == aAttribute &&
-       mContent->IsHTMLElement(nsGkAtoms::ol))) {
-    nsPresContext* presContext = PresContext();
-
-    // XXX Not sure if this is necessary anymore
-    if (RenumberLists(presContext)) {
-      presContext->PresShell()->
-        FrameNeedsReflow(this, nsIPresShell::eStyleChange,
-                         NS_FRAME_HAS_DIRTY_CHILDREN);
-    }
-  }
-  else if (nsGkAtoms::value == aAttribute) {
+  if (nsGkAtoms::value == aAttribute) {
     const nsStyleDisplay* styleDisplay = StyleDisplay();
     if (NS_STYLE_DISPLAY_LIST_ITEM == styleDisplay->mDisplay) {
-      // Search for the closest ancestor that's a block frame. We
-      // make the assumption that all related list items share a
-      // common block parent.
+      // Search for the closest ancestor that's a block/grid/flex frame.
+      // We make the assumption that all related list items share a
+      // common block/grid/flex ancestor.
       // XXXldb I think that's a bad assumption.
-      nsBlockFrame* blockParent = nsLayoutUtils::FindNearestBlockAncestor(this);
-
-      // Tell the enclosing block frame to renumber list items within
-      // itself
-      if (nullptr != blockParent) {
-        nsPresContext* presContext = PresContext();
+      nsContainerFrame* ancestor = GetParent();
+      for (; ancestor; ancestor = ancestor->GetParent()) {
+        auto frameType = ancestor->GetType();
+        if (frameType == nsGkAtoms::blockFrame ||
+            frameType == nsGkAtoms::flexContainerFrame ||
+            frameType == nsGkAtoms::gridContainerFrame) {
+          break;
+        }
+      }
+      // Tell the ancestor to renumber list items within itself.
+      if (ancestor) {
         // XXX Not sure if this is necessary anymore
-        if (blockParent->RenumberLists(presContext)) {
-          presContext->PresShell()->
-            FrameNeedsReflow(blockParent, nsIPresShell::eStyleChange,
+        if (ancestor->RenumberList()) {
+          PresContext()->PresShell()->
+            FrameNeedsReflow(ancestor, nsIPresShell::eStyleChange,
                              NS_FRAME_HAS_DIRTY_CHILDREN);
         }
       }
     }
   }
-
   return rv;
 }
 
 static inline bool
 IsNonAutoNonZeroBSize(const nsStyleCoord& aCoord)
 {
   nsStyleUnit unit = aCoord.GetUnit();
   if (unit == eStyleUnit_Auto ||
@@ -6965,225 +6952,57 @@ nsBlockFrame::GetSpokenBulletText(nsAStr
     if (bullet) {
       bullet->GetSpokenText(aText);
     } else {
       aText.Truncate();
     }
   }
 }
 
-// static
 bool
-nsBlockFrame::FrameStartsCounterScope(nsIFrame* aFrame)
-{
-  nsIContent* content = aFrame->GetContent();
-  if (!content || !content->IsHTMLElement())
-    return false;
-
-  nsIAtom *localName = content->NodeInfo()->NameAtom();
-  return localName == nsGkAtoms::ol ||
-         localName == nsGkAtoms::ul ||
-         localName == nsGkAtoms::dir ||
-         localName == nsGkAtoms::menu;
-}
-
-bool
-nsBlockFrame::RenumberLists(nsPresContext* aPresContext)
-{
-  if (!FrameStartsCounterScope(this)) {
-    // If this frame doesn't start a counter scope then we don't need
-    // to renumber child list items.
-    return false;
-  }
-
-  MOZ_ASSERT(mContent->IsHTMLElement(),
-             "FrameStartsCounterScope should only return true for HTML elements");
-
-  // Setup initial list ordinal value
-  // XXX Map html's start property to counter-reset style
-  int32_t ordinal = 1;
-  int32_t increment;
-  if (mContent->IsHTMLElement(nsGkAtoms::ol) &&
-      mContent->HasAttr(kNameSpaceID_None, nsGkAtoms::reversed)) {
-    increment = -1;
-  } else {
-    increment = 1;
-  }
-
-  nsGenericHTMLElement *hc = nsGenericHTMLElement::FromContent(mContent);
-  // Must be non-null, since FrameStartsCounterScope only returns true
-  // for HTML elements.
-  MOZ_ASSERT(hc, "How is mContent not HTML?");
-  const nsAttrValue* attr = hc->GetParsedAttr(nsGkAtoms::start);
-  if (attr && attr->Type() == nsAttrValue::eInteger) {
-    ordinal = attr->GetIntegerValue();
-  } else if (increment < 0) {
-    // <ol reversed> case, or some other case with a negative increment: count
-    // up the child list
-    ordinal = 0;
-    nsBlockFrame* block = static_cast<nsBlockFrame*>(FirstInFlow());
-    RenumberListsInBlock(aPresContext, block, &ordinal, 0, -increment, true);
-  }
-
-  // Get to first-in-flow
-  nsBlockFrame* block = static_cast<nsBlockFrame*>(FirstInFlow());
-  return RenumberListsInBlock(aPresContext, block, &ordinal, 0, increment, false);
-}
-
-bool
-nsBlockFrame::RenumberListsInBlock(nsPresContext* aPresContext,
-                                   nsBlockFrame* aBlockFrame,
-                                   int32_t* aOrdinal,
-                                   int32_t aDepth,
-                                   int32_t aIncrement,
-                                   bool aForCounting)
+nsBlockFrame::RenumberChildFrames(int32_t* aOrdinal,
+                                  int32_t aDepth,
+                                  int32_t aIncrement,
+                                  bool aForCounting)
 {
   // Examine each line in the block
   bool foundValidLine;
-  nsBlockInFlowLineIterator bifLineIter(aBlockFrame, &foundValidLine);
-  
-  if (!foundValidLine)
+  nsBlockInFlowLineIterator bifLineIter(this, &foundValidLine);
+  if (!foundValidLine) {
     return false;
+  }
 
   bool renumberedABullet = false;
-
   do {
     nsLineList::iterator line = bifLineIter.GetLine();
     nsIFrame* kid = line->mFirstChild;
     int32_t n = line->GetChildCount();
     while (--n >= 0) {
-      bool kidRenumberedABullet = RenumberListsFor(aPresContext, kid, aOrdinal,
-                                                   aDepth, aIncrement,
-                                                   aForCounting);
+      bool kidRenumberedABullet =
+        kid->RenumberFrameAndDescendants(aOrdinal, aDepth, aIncrement, aForCounting);
       if (!aForCounting && kidRenumberedABullet) {
         line->MarkDirty();
         renumberedABullet = true;
       }
       kid = kid->GetNextSibling();
     }
   } while (bifLineIter.Next());
 
   // We need to set NS_FRAME_HAS_DIRTY_CHILDREN bits up the tree between
   // the bullet and the caller of RenumberLists.  But the caller itself
   // has to be responsible for setting the bit itself, since that caller
   // might be making a FrameNeedsReflow call, which requires that the
   // bit not be set yet.
   if (renumberedABullet && aDepth != 0) {
-    aBlockFrame->AddStateBits(NS_FRAME_HAS_DIRTY_CHILDREN);
+    AddStateBits(NS_FRAME_HAS_DIRTY_CHILDREN);
   }
 
   return renumberedABullet;
 }
 
-bool
-nsBlockFrame::RenumberListsFor(nsPresContext* aPresContext,
-                               nsIFrame* aKid,
-                               int32_t* aOrdinal,
-                               int32_t aDepth,
-                               int32_t aIncrement,
-                               bool aForCounting)
-{
-  NS_PRECONDITION(aPresContext && aKid && aOrdinal, "null params are immoral!");
-
-  // add in a sanity check for absurdly deep frame trees.  See bug 42138
-  if (MAX_DEPTH_FOR_LIST_RENUMBERING < aDepth)
-    return false;
-
-  // if the frame is a placeholder, then get the out of flow frame
-  nsIFrame* kid = nsPlaceholderFrame::GetRealFrameFor(aKid);
-  const nsStyleDisplay* display = kid->StyleDisplay();
-
-  // drill down through any wrappers to the real frame
-  kid = kid->GetContentInsertionFrame();
-
-  // possible there is no content insertion frame
-  if (!kid)
-    return false;
-
-  // Do not renumber list for summary elements.
-  if (HTMLDetailsElement::IsDetailsEnabled()) {
-    HTMLSummaryElement* summary =
-      HTMLSummaryElement::FromContent(kid->GetContent());
-    if (summary && summary->IsMainSummary()) {
-      return false;
-    }
-  }
-
-  bool kidRenumberedABullet = false;
-
-  // If the frame is a list-item and the frame implements our
-  // block frame API then get its bullet and set the list item
-  // ordinal.
-  if (NS_STYLE_DISPLAY_LIST_ITEM == display->mDisplay) {
-    // Make certain that the frame is a block frame in case
-    // something foreign has crept in.
-    nsBlockFrame* listItem = nsLayoutUtils::GetAsBlock(kid);
-    if (listItem) {
-      nsBulletFrame* bullet = listItem->GetBullet();
-      if (bullet) {
-        if (!aForCounting) {
-          bool changed;
-          *aOrdinal = bullet->SetListItemOrdinal(*aOrdinal, &changed, aIncrement);
-          if (changed) {
-            kidRenumberedABullet = true;
-
-            // The ordinal changed - mark the bullet frame, and any
-            // intermediate frames between it and the block (are there
-            // ever any?), dirty.
-            // The calling code will make the necessary FrameNeedsReflow
-            // call for the list ancestor.
-            bullet->AddStateBits(NS_FRAME_IS_DIRTY);
-            nsIFrame *f = bullet;
-            do {
-              nsIFrame *parent = f->GetParent();
-              parent->ChildIsDirty(f);
-              f = parent;
-            } while (f != listItem);
-          }
-        } else {
-          // We're only counting the number of children,
-          // not restyling them. Don't take |value|
-          // into account when incrementing the ordinal
-          // or dirty the bullet.
-          *aOrdinal += aIncrement;
-        }
-      }
-
-      // XXX temporary? if the list-item has child list-items they
-      // should be numbered too; especially since the list-item is
-      // itself (ASSUMED!) not to be a counter-resetter.
-      bool meToo = RenumberListsInBlock(aPresContext, listItem, aOrdinal,
-                                        aDepth + 1, aIncrement,
-                                        aForCounting);
-      if (meToo) {
-        kidRenumberedABullet = true;
-      }
-    }
-  }
-  else if (NS_STYLE_DISPLAY_BLOCK == display->mDisplay) {
-    if (FrameStartsCounterScope(kid)) {
-      // Don't bother recursing into a block frame that is a new
-      // counter scope. Any list-items in there will be handled by
-      // it.
-    }
-    else {
-      // If the display=block element is a block frame then go ahead
-      // and recurse into it, as it might have child list-items.
-      nsBlockFrame* kidBlock = nsLayoutUtils::GetAsBlock(kid);
-      if (kidBlock) {
-        kidRenumberedABullet = RenumberListsInBlock(aPresContext, kidBlock,
-                                                    aOrdinal, aDepth + 1,
-                                                    aIncrement,
-                                                    aForCounting);
-      }
-    }
-  }
-  return kidRenumberedABullet;
-}
-
 void
 nsBlockFrame::ReflowBullet(nsIFrame* aBulletFrame,
                            BlockReflowInput& aState,
                            ReflowOutput& aMetrics,
                            nscoord aLineTop)
 {
   const ReflowInput &rs = aState.mReflowInput;
 
--- a/layout/generic/nsBlockFrame.h
+++ b/layout/generic/nsBlockFrame.h
@@ -514,16 +514,20 @@ public:
       nsIFrame* f = e.get();
       NS_ASSERTION(!(f->GetStateBits() & NS_FRAME_IS_PUSHED_FLOAT),
         "pushed floats must be at the beginning of the float list");
     }
 #endif
     return false;
   }
 
+  virtual bool RenumberChildFrames(int32_t* aOrdinal,
+                                   int32_t aDepth,
+                                   int32_t aIncrement,
+                                   bool aForCounting) override;
 protected:
 
   /** grab overflow lines from this block's prevInFlow, and make them
     * part of this block's mLines list.
     * @return true if any lines were drained.
     */
   bool DrainOverflowLines();
 
@@ -776,63 +780,16 @@ protected:
                             nsLineBox* aLine,
                             nscoord aDeltaBCoord);
 
   void CheckFloats(BlockReflowInput& aState);
 
   //----------------------------------------
   // List handling kludge
 
-  // If this returns true, the block it's called on should get the
-  // NS_FRAME_HAS_DIRTY_CHILDREN bit set on it by the caller; either directly
-  // if it's already in reflow, or via calling FrameNeedsReflow() to schedule a
-  // reflow.
-  bool RenumberLists(nsPresContext* aPresContext);
-
-  /**
-   * Renumber lists for a single block frame
-   * @param aOrdinal Ordinal number to start counting at.
-   *        Modifies this number for each associated list
-   *        item. Changes in the numbering due to setting
-   *        the |value| attribute are included if |aForCounting|
-   *        is false. This value is both an input and output
-   *        of this function, with the output value being the
-   *        next ordinal number to be used.
-   * @param aIncrement Amount to increase by after visiting each associated
-   *        list item, unless overridden by |value|.
-   * @param aForCounting Whether we are counting the elements or actually
-   *        restyling them. When true, this simply visits all children,
-   *        ignoring |<li value="..">| changes, effectively counting them
-   *        and storing the result in |aOrdinal|. This is useful for
-   *        |<ol reversed>|, where we need to count the number of
-   *        applicable child list elements before numbering. When false,
-   *        this will restyle all applicable descendants, and the next
-   *        ordinal value will be stored in |aOrdinal|, taking into account
-   *        any changes from |<li value="..">|.
-   * @param aDepth Current depth in frame tree from root list element.
-   */
-  static bool RenumberListsInBlock(nsPresContext* aPresContext,
-                                   nsBlockFrame* aBlockFrame,
-                                   int32_t* aOrdinal,
-                                   int32_t aDepth,
-                                   int32_t aIncrement,
-                                   bool aForCounting);
-
-  /**
-   * Renumber the lists for a single frame.
-   * May recurse into RenumberListsInBlock.
-   * See RenumberListsInBlock for description of parameters.
-   */
-  static bool RenumberListsFor(nsPresContext* aPresContext, nsIFrame* aKid,
-                               int32_t* aOrdinal, int32_t aDepth,
-                               int32_t aIncrement,
-                               bool aForCounting);
-
-  static bool FrameStartsCounterScope(nsIFrame* aFrame);
-
   void ReflowBullet(nsIFrame* aBulletFrame,
                     BlockReflowInput& aState,
                     ReflowOutput& aMetrics,
                     nscoord aLineTop);
 
   //----------------------------------------
 
   virtual nsILineIterator* GetLineIterator() override;
--- a/layout/generic/nsContainerFrame.cpp
+++ b/layout/generic/nsContainerFrame.cpp
@@ -2,16 +2,18 @@
 /* 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/. */
 
 /* base class #1 for rendering objects that have child lists */
 
 #include "nsContainerFrame.h"
 
+#include "mozilla/dom/HTMLDetailsElement.h"
+#include "mozilla/dom/HTMLSummaryElement.h"
 #include "nsAbsoluteContainingBlock.h"
 #include "nsIDocument.h"
 #include "nsPresContext.h"
 #include "nsStyleContext.h"
 #include "nsRect.h"
 #include "nsPoint.h"
 #include "nsStyleConsts.h"
 #include "nsView.h"
@@ -1791,16 +1793,223 @@ nsContainerFrame::ResolvedOrientationIsV
       return GetWritingMode().IsVertical();
     case NS_STYLE_ORIENT_BLOCK:
       return !GetWritingMode().IsVertical();
   }
   NS_NOTREACHED("unexpected -moz-orient value");
   return false;
 }
 
+// static
+bool
+nsContainerFrame::FrameStartsCounterScope(nsIFrame* aFrame)
+{
+  nsIContent* content = aFrame->GetContent();
+  if (!content || !content->IsHTMLElement())
+    return false;
+
+  nsIAtom* localName = content->NodeInfo()->NameAtom();
+  return localName == nsGkAtoms::ol ||
+         localName == nsGkAtoms::ul ||
+         localName == nsGkAtoms::dir ||
+         localName == nsGkAtoms::menu;
+}
+
+bool
+nsContainerFrame::RenumberList()
+{
+  if (!FrameStartsCounterScope(this)) {
+    // If this frame doesn't start a counter scope then we don't need
+    // to renumber child list items.
+    return false;
+  }
+
+  MOZ_ASSERT(mContent->IsHTMLElement(),
+             "FrameStartsCounterScope should only return true for HTML elements");
+
+  // Setup initial list ordinal value
+  // XXX Map html's start property to counter-reset style
+  int32_t ordinal = 1;
+  int32_t increment;
+  if (mContent->IsHTMLElement(nsGkAtoms::ol) &&
+      mContent->HasAttr(kNameSpaceID_None, nsGkAtoms::reversed)) {
+    increment = -1;
+  } else {
+    increment = 1;
+  }
+
+  nsGenericHTMLElement* hc = nsGenericHTMLElement::FromContent(mContent);
+  // Must be non-null, since FrameStartsCounterScope only returns true
+  // for HTML elements.
+  MOZ_ASSERT(hc, "How is mContent not HTML?");
+  const nsAttrValue* attr = hc->GetParsedAttr(nsGkAtoms::start);
+  nsContainerFrame* fif = static_cast<nsContainerFrame*>(FirstInFlow());
+  if (attr && attr->Type() == nsAttrValue::eInteger) {
+    ordinal = attr->GetIntegerValue();
+  } else if (increment < 0) {
+    // <ol reversed> case, or some other case with a negative increment: count
+    // up the child list
+    ordinal = 0;
+    fif->RenumberChildFrames(&ordinal, 0, -increment, true);
+  }
+
+  return fif->RenumberChildFrames(&ordinal, 0, increment, false);
+}
+
+// add in a sanity check for absurdly deep frame trees.  See bug 42138
+// can't just use IsFrameTreeTooDeep() because that method has side effects we don't want
+#define MAX_DEPTH_FOR_LIST_RENUMBERING 200  // 200 open displayable tags is pretty unrealistic
+
+bool
+nsContainerFrame::RenumberFrameAndDescendants(int32_t* aOrdinal,
+                                              int32_t aDepth,
+                                              int32_t aIncrement,
+                                              bool aForCounting)
+{
+  NS_PRECONDITION(aOrdinal, "null params are immoral!");
+
+  // add in a sanity check for absurdly deep frame trees.  See bug 42138
+  if (MAX_DEPTH_FOR_LIST_RENUMBERING < aDepth) {
+    return false;
+  }
+  const nsStyleDisplay* display = StyleDisplay();
+
+  // drill down through any wrappers to the real frame
+  nsIFrame* kid = GetContentInsertionFrame();
+  if (!kid) {
+    return false;
+  }
+
+  // Do not renumber list for summary elements.
+  if (HTMLDetailsElement::IsDetailsEnabled()) {
+    HTMLSummaryElement* summary =
+      HTMLSummaryElement::FromContent(kid->GetContent());
+    if (summary && summary->IsMainSummary()) {
+      return false;
+    }
+  }
+
+  bool kidRenumberedABullet = false;
+
+  // If the frame is a list-item and the frame implements our
+  // block frame API then get its bullet and set the list item
+  // ordinal.
+  if (NS_STYLE_DISPLAY_LIST_ITEM == display->mDisplay) {
+    // Make certain that the frame is a block frame in case
+    // something foreign has crept in.
+    nsBlockFrame* listItem = nsLayoutUtils::GetAsBlock(kid);
+    if (listItem) {
+      nsBulletFrame* bullet = listItem->GetBullet();
+      if (bullet) {
+        if (!aForCounting) {
+          bool changed;
+          *aOrdinal = bullet->SetListItemOrdinal(*aOrdinal, &changed, aIncrement);
+          if (changed) {
+            kidRenumberedABullet = true;
+
+            // The ordinal changed - mark the bullet frame, and any
+            // intermediate frames between it and the block (are there
+            // ever any?), dirty.
+            // The calling code will make the necessary FrameNeedsReflow
+            // call for the list ancestor.
+            bullet->AddStateBits(NS_FRAME_IS_DIRTY);
+            nsIFrame *f = bullet;
+            do {
+              nsIFrame *parent = f->GetParent();
+              parent->ChildIsDirty(f);
+              f = parent;
+            } while (f != listItem);
+          }
+        } else {
+          // We're only counting the number of children,
+          // not restyling them. Don't take |value|
+          // into account when incrementing the ordinal
+          // or dirty the bullet.
+          *aOrdinal += aIncrement;
+        }
+      }
+
+      // XXX temporary? if the list-item has child list-items they
+      // should be numbered too; especially since the list-item is
+      // itself (ASSUMED!) not to be a counter-resetter.
+      bool meToo = listItem->RenumberChildFrames(aOrdinal, aDepth + 1,
+                                                 aIncrement, aForCounting);
+      if (meToo) {
+        kidRenumberedABullet = true;
+      }
+    }
+  } else if (display->mDisplay == NS_STYLE_DISPLAY_BLOCK ||
+             display->mDisplay == NS_STYLE_DISPLAY_FLEX ||
+             display->mDisplay == NS_STYLE_DISPLAY_GRID) {
+    if (FrameStartsCounterScope(kid)) {
+      // Don't bother recursing into a frame that is a new counter scope.
+      // Any list-items in there will be handled by it.
+    } else {
+      nsContainerFrame* container = do_QueryFrame(kid);
+      if (container) {
+        kidRenumberedABullet =
+          container->RenumberChildFrames(aOrdinal, aDepth + 1,
+                                         aIncrement, aForCounting);
+      }
+    }
+  }
+  return kidRenumberedABullet;
+}
+
+bool
+nsContainerFrame::RenumberChildFrames(int32_t* aOrdinal,
+                                      int32_t aDepth,
+                                      int32_t aIncrement,
+                                      bool aForCounting)
+{
+  bool renumbered = false;
+  for (auto kid : mFrames) {
+    bool kidRenumbered =
+      kid->RenumberFrameAndDescendants(aOrdinal, aDepth, aIncrement, aForCounting);
+    if (!aForCounting && kidRenumbered) {
+      renumbered = true;
+    }
+  }
+
+  // We need to set NS_FRAME_HAS_DIRTY_CHILDREN bits up the tree between
+  // the bullet and the caller of RenumberLists.  But the caller itself
+  // has to be responsible for setting the bit itself, since that caller
+  // might be making a FrameNeedsReflow call, which requires that the
+  // bit not be set yet.
+  if (renumbered && aDepth != 0) {
+    AddStateBits(NS_FRAME_HAS_DIRTY_CHILDREN);
+  }
+
+  return renumbered;
+}
+
+nsresult
+nsContainerFrame::AttributeChanged(int32_t         aNameSpaceID,
+                                   nsIAtom*        aAttribute,
+                                   int32_t         aModType)
+{
+  nsresult rv = nsSplittableFrame::AttributeChanged(aNameSpaceID,
+                                                    aAttribute, aModType);
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+  if (nsGkAtoms::start == aAttribute ||
+      (nsGkAtoms::reversed == aAttribute &&
+       mContent->IsHTMLElement(nsGkAtoms::ol))) {
+
+    // XXX Not sure if this is necessary anymore
+    if (RenumberList()) {
+      PresContext()->PresShell()->
+        FrameNeedsReflow(this, nsIPresShell::eStyleChange,
+                         NS_FRAME_HAS_DIRTY_CHILDREN);
+    }
+  }
+  return rv;
+}
+
 nsOverflowContinuationTracker::nsOverflowContinuationTracker(nsContainerFrame* aFrame,
                                                              bool              aWalkOOFFrames,
                                                              bool              aSkipOverflowContainerChildren)
   : mOverflowContList(nullptr),
     mPrevOverflowCont(nullptr),
     mSentry(nullptr),
     mParent(aFrame),
     mSkipOverflowContainerChildren(aSkipOverflowContainerChildren),
--- a/layout/generic/nsContainerFrame.h
+++ b/layout/generic/nsContainerFrame.h
@@ -64,17 +64,21 @@ public:
   virtual void GetChildLists(nsTArray<ChildList>* aLists) const override;
   virtual void DestroyFrom(nsIFrame* aDestructRoot) override;
   virtual void ChildIsDirty(nsIFrame* aChild) override;
 
   virtual bool IsLeaf() const override;
   virtual FrameSearchResult PeekOffsetNoAmount(bool aForward, int32_t* aOffset) override;
   virtual FrameSearchResult PeekOffsetCharacter(bool aForward, int32_t* aOffset,
                                      bool aRespectClusters = true) override;
-  
+
+  virtual nsresult AttributeChanged(int32_t         aNameSpaceID,
+                                    nsIAtom*        aAttribute,
+                                    int32_t         aModType) override;
+
 #ifdef DEBUG_FRAME_DUMP
   void List(FILE* out = stderr, const char* aPrefix = "", uint32_t aFlags = 0) const override;
 #endif  
 
   // nsContainerFrame methods
 
   /**
    * Called to set the initial list of frames. This happens after the frame
@@ -455,16 +459,62 @@ public:
   static void PlaceFrameView(nsIFrame* aFrame)
   {
     if (aFrame->HasView())
       nsContainerFrame::PositionFrameView(aFrame);
     else
       nsContainerFrame::PositionChildViews(aFrame);
   }
 
+  static bool FrameStartsCounterScope(nsIFrame* aFrame);
+
+  /**
+   * Renumber the list of the counter scope started by this frame, if any.
+   * If this returns true, the frame it's called on should get the
+   * NS_FRAME_HAS_DIRTY_CHILDREN bit set on it by the caller; either directly
+   * if it's already in reflow, or via calling FrameNeedsReflow() to schedule
+   * a reflow.
+   */
+  bool RenumberList();
+
+  /**
+   * Renumber this frame if it's a list-item, then call RenumberChildFrames.
+   * @param aOrdinal Ordinal number to start counting at.
+   *        Modifies this number for each associated list
+   *        item. Changes in the numbering due to setting
+   *        the |value| attribute are included if |aForCounting|
+   *        is false. This value is both an input and output
+   *        of this function, with the output value being the
+   *        next ordinal number to be used.
+   * @param aDepth Current depth in frame tree from root list element.
+   * @param aIncrement Amount to increase by after visiting each associated
+   *        list item, unless overridden by |value|.
+   * @param aForCounting Whether we are counting the elements or actually
+   *        restyling them. When true, this simply visits all children,
+   *        ignoring |<li value="..">| changes, effectively counting them
+   *        and storing the result in |aOrdinal|. This is useful for
+   *        |<ol reversed>|, where we need to count the number of
+   *        applicable child list elements before numbering. When false,
+   *        this will restyle all applicable descendants, and the next
+   *        ordinal value will be stored in |aOrdinal|, taking into account
+   *        any changes from |<li value="..">|.
+   */
+  bool RenumberFrameAndDescendants(int32_t* aOrdinal,
+                                   int32_t aDepth,
+                                   int32_t aIncrement,
+                                   bool aForCounting) override;
+  /**
+   * Renumber the child frames using RenumberFrameAndDescendants.
+   * See RenumberFrameAndDescendants for description of parameters.
+   */
+  virtual bool RenumberChildFrames(int32_t* aOrdinal,
+                                   int32_t aDepth,
+                                   int32_t aIncrement,
+                                   bool aForCounting);
+
 #define NS_DECLARE_FRAME_PROPERTY_FRAMELIST(prop) \
   NS_DECLARE_FRAME_PROPERTY_WITH_DTOR_NEVER_CALLED(prop, nsFrameList)
 
   typedef PropertyDescriptor<nsFrameList> FrameListPropertyDescriptor;
 
   NS_DECLARE_FRAME_PROPERTY_FRAMELIST(OverflowProperty)
   NS_DECLARE_FRAME_PROPERTY_FRAMELIST(OverflowContainersProperty)
   NS_DECLARE_FRAME_PROPERTY_FRAMELIST(ExcessOverflowContainersProperty)
--- a/layout/generic/nsFlexContainerFrame.cpp
+++ b/layout/generic/nsFlexContainerFrame.cpp
@@ -3845,16 +3845,18 @@ nsFlexContainerFrame::Reflow(nsPresConte
   if (!HasAnyStateBits(NS_STATE_FLEX_CHILDREN_REORDERED)) {
     if (SortChildrenIfNeeded<IsOrderLEQ>()) {
       AddStateBits(NS_STATE_FLEX_CHILDREN_REORDERED);
     }
   } else {
     SortChildrenIfNeeded<IsOrderLEQWithDOMFallback>();
   }
 
+  RenumberList();
+
   const FlexboxAxisTracker axisTracker(this, aReflowInput.GetWritingMode());
 
   // If we're being fragmented into a constrained BSize, then subtract off
   // borderpadding BStart from that constrained BSize, to get the available
   // BSize for our content box. (No need to subtract the borderpadding BStart
   // if we're already skipping it via GetLogicalSkipSides, though.)
   nscoord availableBSizeForContent = aReflowInput.AvailableBSize();
   if (availableBSizeForContent != NS_UNCONSTRAINEDSIZE &&
@@ -4384,16 +4386,18 @@ nsFlexContainerFrame::ReflowFlexItem(nsP
 }
 
 /* virtual */ nscoord
 nsFlexContainerFrame::GetMinISize(nsRenderingContext* aRenderingContext)
 {
   nscoord minWidth = 0;
   DISPLAY_MIN_WIDTH(this, minWidth);
 
+  RenumberList();
+
   const nsStylePosition* stylePos = StylePosition();
   const FlexboxAxisTracker axisTracker(this, GetWritingMode());
 
   for (nsIFrame* childFrame : mFrames) {
     nscoord childMinWidth =
       nsLayoutUtils::IntrinsicForContainer(aRenderingContext, childFrame,
                                            nsLayoutUtils::MIN_ISIZE);
     // For a horizontal single-line flex container, the intrinsic min width is
@@ -4411,16 +4415,18 @@ nsFlexContainerFrame::GetMinISize(nsRend
 }
 
 /* virtual */ nscoord
 nsFlexContainerFrame::GetPrefISize(nsRenderingContext* aRenderingContext)
 {
   nscoord prefWidth = 0;
   DISPLAY_PREF_WIDTH(this, prefWidth);
 
+  RenumberList();
+
   // XXXdholbert Optimization: We could cache our intrinsic widths like
   // nsBlockFrame does (and return it early from this function if it's set).
   // Whenever anything happens that might change it, set it to
   // NS_INTRINSIC_WIDTH_UNKNOWN (like nsBlockFrame::MarkIntrinsicISizesDirty
   // does)
   const FlexboxAxisTracker axisTracker(this, GetWritingMode());
 
   for (nsIFrame* childFrame : mFrames) {
--- a/layout/generic/nsGfxScrollFrame.cpp
+++ b/layout/generic/nsGfxScrollFrame.cpp
@@ -5887,28 +5887,36 @@ ScrollFrameHelper::SaveState() const
 {
   nsIScrollbarMediator* mediator = do_QueryFrame(GetScrolledFrame());
   if (mediator) {
     // child handles its own scroll state, so don't bother saving state here
     return nullptr;
   }
 
   // Don't store a scroll state if we never have been scrolled or restored
-  // a previous scroll state.
-  if (!mHasBeenScrolled && !mDidHistoryRestore) {
+  // a previous scroll state, and we're not in the middle of a smooth scroll.
+  bool isInSmoothScroll = IsProcessingAsyncScroll() || mLastSmoothScrollOrigin;
+  if (!mHasBeenScrolled && !mDidHistoryRestore && !isInSmoothScroll) {
     return nullptr;
   }
 
   nsPresState* state = new nsPresState();
   // Save mRestorePos instead of our actual current scroll position, if it's
   // valid and we haven't moved since the last update of mLastPos (same check
   // that ScrollToRestoredPosition uses). This ensures if a reframe occurs
   // while we're in the process of loading content to scroll to a restored
-  // position, we'll keep trying after the reframe.
+  // position, we'll keep trying after the reframe. Similarly, if we're in the
+  // middle of a smooth scroll, store the destination so that when we restore
+  // we'll jump straight to the end of the scroll animation, rather than
+  // effectively dropping it. Note that the mRestorePos will override the
+  // smooth scroll destination if both are present.
   nsPoint pt = GetLogicalScrollPosition();
+  if (isInSmoothScroll) {
+    pt = mDestination;
+  }
   if (mRestorePos.y != -1 && pt == mLastPos) {
     pt = mRestorePos;
   }
   state->SetScrollState(pt);
   if (mIsRoot) {
     // Only save resolution properties for root scroll frames
     nsIPresShell* shell = mOuter->PresContext()->PresShell();
     state->SetResolution(shell->GetResolution());
--- a/layout/generic/nsGridContainerFrame.cpp
+++ b/layout/generic/nsGridContainerFrame.cpp
@@ -5594,16 +5594,18 @@ nsGridContainerFrame::Reflow(nsPresConte
       firstNIF->MergeSortedExcessOverflowContainers(childOCNIFs);
     }
 
     MOZ_ASSERT(foundOwnPushedChild || !items.IsEmpty() || mDidPushItemsBitMayLie,
                "NS_STATE_GRID_DID_PUSH_ITEMS lied");
     ::MergeSortedFrameLists(mFrames, items, GetContent());
   }
 
+  RenumberList();
+
 #ifdef DEBUG
   mDidPushItemsBitMayLie = false;
   SanityCheckGridItemsBeforeReflow();
 #endif // DEBUG
 
   const nsStylePosition* stylePos = aReflowInput.mStylePosition;
   if (!prevInFlow) {
     InitImplicitNamedAreas(stylePos);
@@ -5897,16 +5899,18 @@ nsGridContainerFrame::Reflow(nsPresConte
   FinishAndStoreOverflow(&aDesiredSize);
   NS_FRAME_SET_TRUNCATION(aStatus, aReflowInput, aDesiredSize);
 }
 
 nscoord
 nsGridContainerFrame::IntrinsicISize(nsRenderingContext* aRenderingContext,
                                      IntrinsicISizeType  aConstraint)
 {
+  RenumberList();
+
   // Calculate the sum of column sizes under aConstraint.
   // http://dev.w3.org/csswg/css-grid/#intrinsic-sizes
   GridReflowInput state(this, *aRenderingContext);
   InitImplicitNamedAreas(state.mGridStyle); // XXX optimize
   LogicalSize indefinite(state.mWM, NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE);
   Grid grid;
   grid.PlaceGridItems(state, indefinite, indefinite, indefinite);  // XXX optimize
   if (grid.mGridColEnd == 0) {
--- a/layout/generic/nsIFrame.h
+++ b/layout/generic/nsIFrame.h
@@ -3269,16 +3269,27 @@ public:
     return StyleDisplay()->BackfaceIsHidden();
   }
 
   /**
    * Returns true if the frame is scrolled out of view.
    */
   bool IsScrolledOutOfView();
 
+  /**
+   * If this returns true, the frame it's called on should get the
+   * NS_FRAME_HAS_DIRTY_CHILDREN bit set on it by the caller; either directly
+   * if it's already in reflow, or via calling FrameNeedsReflow() to schedule a
+   * reflow.
+   */
+  virtual bool RenumberFrameAndDescendants(int32_t* aOrdinal,
+                                           int32_t aDepth,
+                                           int32_t aIncrement,
+                                           bool aForCounting) { return false; }
+
 protected:
   // Members
   nsRect           mRect;
   nsIContent*      mContent;
   nsStyleContext*  mStyleContext;
 private:
   nsContainerFrame* mParent;
   nsIFrame*        mNextSibling;  // doubly-linked list of frames
--- a/layout/generic/nsPlaceholderFrame.h
+++ b/layout/generic/nsPlaceholderFrame.h
@@ -134,16 +134,25 @@ public:
     nsIFrame* realFrame = GetRealFrameForPlaceholder(this);
     return realFrame ? realFrame->AccessibleType() :
                        nsFrame::AccessibleType();
   }
 #endif
 
   virtual nsStyleContext* GetParentStyleContext(nsIFrame** aProviderFrame) const override;
 
+  bool RenumberFrameAndDescendants(int32_t* aOrdinal,
+                                   int32_t aDepth,
+                                   int32_t aIncrement,
+                                   bool aForCounting) override
+  {
+    return mOutOfFlowFrame->
+      RenumberFrameAndDescendants(aOrdinal, aDepth, aIncrement, aForCounting);
+  }
+
   /**
    * @return the out-of-flow for aFrame if aFrame is a placeholder; otherwise
    * aFrame
    */
   static nsIFrame* GetRealFrameFor(nsIFrame* aFrame) {
     NS_PRECONDITION(aFrame, "Must have a frame to work with");
     if (aFrame->GetType() == nsGkAtoms::placeholderFrame) {
       return GetRealFrameForPlaceholder(aFrame);
--- a/layout/generic/test/mochitest.ini
+++ b/layout/generic/test/mochitest.ini
@@ -141,8 +141,9 @@ support-files = selection_expanding_xbl.
 [test_selection_preventDefault.html]
 skip-if = buildapp == 'mulet'
 [test_selection_splitText-normalize.html]
 [test_selection_touchevents.html]
 [test_taintedfilters.html]
 support-files = file_taintedfilters_feDisplacementMap-tainted-1.svg file_taintedfilters_feDisplacementMap-tainted-2.svg file_taintedfilters_feDisplacementMap-tainted-3.svg file_taintedfilters_feDisplacementMap-tainted-ref.svg file_taintedfilters_feDisplacementMap-untainted-ref.svg file_taintedfilters_feDisplacementMap-untainted-1.svg file_taintedfilters_feDisplacementMap-untainted-2.svg file_taintedfilters_red-flood-for-feImage-cors.svg file_taintedfilters_red-flood-for-feImage-cors.svg^headers^ file_taintedfilters_red-flood-for-feImage.svg
 [test_scroll_position_restore.html]
 support-files = file_scroll_position_restore.html
+[test_scroll_animation_restore.html]
new file mode 100644
--- /dev/null
+++ b/layout/generic/test/test_scroll_animation_restore.html
@@ -0,0 +1,128 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1247074
+-->
+<head>
+  <meta charset="utf-8">
+  <title>Test for Bug 1247074</title>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="text/javascript" src="/tests/SimpleTest/paint_listener.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+  <style>
+  .outer {
+      direction: ltr;
+      height: 400px;
+      width: 415px;
+      overflow: hidden;
+      position: relative;
+  }
+  .inner {
+      height: 100%;
+      outline: none;
+      overflow-x: hidden;
+      overflow-y: scroll;
+      position: relative;
+      scroll-behavior: smooth;
+  }
+  .outer.contentBefore::before {
+      top: 0;
+      content: '';
+      display: block;
+      height: 2px;
+      position: absolute;
+      width: 100%;
+      z-index: 99;
+  }
+  </style>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1247074">Mozilla Bug 1247074</a>
+<p id="display"></p>
+<div class="outer">
+  <div class="inner">
+   <ol>
+    <li>Some text</li>
+    <li>Some text</li>
+    <li>Some text</li>
+    <li>Some text</li>
+    <li>Some text</li>
+    <li>Some text</li>
+    <li>Some text</li>
+    <li>Some text</li>
+    <li>Some text</li>
+    <li>Some text</li>
+    <li>Some text</li>
+    <li>Some text</li>
+    <li>Some text</li>
+    <li>Some text</li>
+    <li>Some text</li>
+    <li>Some text</li>
+    <li>Some text</li>
+    <li>Some text</li>
+    <li>Some text</li>
+    <li>Some text</li>
+    <li>Some text</li>
+    <li>Some text</li>
+    <li>Some text</li>
+    <li>Some text</li>
+    <li>Some text</li>
+    <li>Some text</li>
+    <li>Some text</li>
+    <li>Some text</li>
+    <li>Some text</li>
+    <li>Some text</li>
+    <li>Some text</li>
+    <li>Some text</li>
+    <li>Some text</li>
+    <li>Some text</li>
+    <li>Some text</li>
+    <li>Some text</li>
+    <li>Some text</li>
+    <li>Some text</li>
+    <li>Some text</li>
+    <li>Some text</li>
+    <li>Some text</li>
+    <li>Some text</li>
+    <li>Some text</li>
+    <li>Some text</li>
+    <li>Some text</li>
+    <li>Some text</li>
+    <li>Some text</li>
+    <li>Some text</li>
+    <li>Some text</li>
+    <li>Some text</li>
+    <li>Some text</li>
+    <li>Some text</li>
+   </ol>
+  </div>
+</div>
+<script>
+SimpleTest.waitForExplicitFinish();
+window.onload = function() {
+  var elm = document.getElementsByClassName('inner')[0];
+
+  // Take control of the refresh driver
+  var utils = SpecialPowers.DOMWindowUtils;
+  utils.advanceTimeAndRefresh(0);
+
+  // Start a smooth scroll and advance a couple of frames so we're in the
+  // middle of the scroll animation
+  elm.scrollTop = 500;
+  utils.advanceTimeAndRefresh(16);
+  utils.advanceTimeAndRefresh(16);
+
+  // Trigger a frame reconstruction
+  elm.parentNode.classList.add('contentBefore');
+
+  // Reach a stable state and verify the scroll position is 500
+  utils.restoreNormalRefresh();
+  waitForAllPaintsFlushed(function() {
+    SimpleTest.is(elm.scrollTop, 500, "Scroll position ended up at 500");
+    SimpleTest.finish();
+  });
+}
+
+</script>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/list-item/numbering-3-ref.html
@@ -0,0 +1,52 @@
+<!DOCTYPE HTML>
+<html><head>
+    <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
+    <title>Reference for testcase #1 for bug 1171419</title>
+<style>
+l { display:block; list-style: decimal inside; }
+</style>
+</head>
+<body>
+
+<ol style="display:grid; list-style-type:none">
+    <l start="1"><li style="overflow: hidden">List item 1</li></l>
+    <l start="2"><li style="overflow: hidden">List item 2</li></l>
+    <l start="3"><li>List item 3</li></l>
+    <l start="4"><li style="overflow: hidden">List item 4</li></l>
+    <l start="5"><li style="overflow: hidden">List item 5</li></l>
+    <l start="6"><li>List item 6</li></l>
+    <l start="7"><li>List item 7</li></l>
+</ol>
+
+<ol style="display:grid; list-style-type:none">
+<div>
+    <l start="1"><li style="overflow: hidden">List item 1</li></l>
+    <l start="2"><li style="overflow: hidden">List item 2</li></l>
+    <l start="3"><li>List item 3</li></l>
+</div>
+</ol>
+
+<ol style="display:grid; list-style-type:none">
+<div style="display:grid">
+    <l start="1"><li style="overflow: hidden">List item 1</li></l>
+    <l start="2"><li style="overflow: hidden">List item 2</li></l>
+    <l start="3"><li>List item 3</li></l>
+</div>
+</ol>
+
+<ol style="display:inline-grid; list-style-type:none">
+<div style="display:grid">
+    <l start="1"><li style="overflow: hidden">List item 1</li></l>
+    <l start="2"><li style="overflow: hidden">List item 2</li></l>
+    <l start="3"><li>List item 3</li></l>
+</div>
+</ol>
+
+<ol style="display:grid; list-style-type:none">
+    <l start="1"><li style="-moz-column-width:1em; column-width:1em;">item1</li></l>
+    <l start="2"><li style="-moz-column-width:1em; column-width:1em;">item2</li></l>
+    <l start="3"><li style="-moz-column-width:1em; column-width:1em;">item3</li></l>
+</ol>
+
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/list-item/numbering-3.html
@@ -0,0 +1,49 @@
+<!DOCTYPE HTML>
+<html><head>
+    <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
+    <title>Testcase #1 for bug 1171419</title>
+</head>
+<body>
+
+<ol style="list-style: decimal inside; display:grid">
+    <li style="overflow: hidden">List item 1</li>
+    <li style="overflow: hidden">List item 2</li>
+    <li>List item 3</li>
+    <li style="overflow: hidden">List item 4</li>
+    <li style="overflow: hidden">List item 5</li>
+    <li>List item 6</li>
+    <div style="overflow: hidden"><li>List item 7</li></div>
+</ol>
+
+<ol style="list-style: decimal inside; display:grid">
+<div>
+    <li style="overflow: hidden">List item 1</li>
+    <li style="overflow: hidden">List item 2</li>
+    <li>List item 3</li>
+</div>
+</ol>
+
+<ol style="list-style: decimal inside; display:grid">
+<div style="display:grid">
+    <li style="overflow: hidden">List item 1</li>
+    <li style="overflow: hidden">List item 2</li>
+    <li>List item 3</li>
+</div>
+</ol>
+
+<ol style="list-style: decimal inside; display:inline-grid">
+<div style="display:grid">
+    <li style="overflow: hidden">List item 1</li>
+    <li style="overflow: hidden">List item 2</li>
+    <li>List item 3</li>
+</div>
+</ol>
+
+<ol style="list-style: decimal inside; display:grid">
+    <li style="-moz-column-width:1em; column-width:1em;">item1</li>
+    <li style="-moz-column-width:1em; column-width:1em;">item2</li>
+    <li style="-moz-column-width:1em; column-width:1em;">item3</li>
+</ol>
+
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/list-item/numbering-4-ref.html
@@ -0,0 +1,52 @@
+<!DOCTYPE HTML>
+<html><head>
+    <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
+    <title>Reference for testcase #2 for bug 1171419</title>
+<style>
+l { display:block; list-style: decimal inside; }
+</style>
+</head>
+<body>
+
+<ol style="display:flex; list-style-type:none">
+    <l start="1"><li style="overflow: hidden">List item 1</li></l>
+    <l start="2"><li style="overflow: hidden">List item 2</li></l>
+    <l start="3"><li>List item 3</li></l>
+    <l start="4"><li style="overflow: hidden">List item 4</li></l>
+    <l start="5"><li style="overflow: hidden">List item 5</li></l>
+    <l start="6"><li>List item 6</li></l>
+    <l start="7"><li>List item 7</li></l>
+</ol>
+
+<ol style="display:flex; list-style-type:none">
+<div>
+    <l start="1"><li style="overflow: hidden">List item 1</li></l>
+    <l start="2"><li style="overflow: hidden">List item 2</li></l>
+    <l start="3"><li>List item 3</li></l>
+</div>
+</ol>
+
+<ol style="display:flex; list-style-type:none">
+<div style="display:flex">
+    <l start="1"><li style="overflow: hidden">List item 1</li></l>
+    <l start="2"><li style="overflow: hidden">List item 2</li></l>
+    <l start="3"><li>List item 3</li></l>
+</div>
+</ol>
+
+<ol style="display:inline-flex; list-style-type:none">
+<div style="display:flex">
+    <l start="1"><li style="overflow: hidden">List item 1</li></l>
+    <l start="2"><li style="overflow: hidden">List item 2</li></l>
+    <l start="3"><li>List item 3</li></l>
+</div>
+</ol>
+
+<ol style="display:flex; list-style-type:none">
+    <l start="1"><li style="-moz-column-width:1em; column-width:1em;">item1</li></l>
+    <l start="2"><li style="-moz-column-width:1em; column-width:1em;">item2</li></l>
+    <l start="3"><li style="-moz-column-width:1em; column-width:1em;">item3</li></l>
+</ol>
+
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/list-item/numbering-4.html
@@ -0,0 +1,49 @@
+<!DOCTYPE HTML>
+<html><head>
+    <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
+    <title>Testcase #2 for bug 1171419</title>
+</head>
+<body>
+
+<ol style="list-style: decimal inside; display:flex">
+    <li style="overflow: hidden">List item 1</li>
+    <li style="overflow: hidden">List item 2</li>
+    <li>List item 3</li>
+    <li style="overflow: hidden">List item 4</li>
+    <li style="overflow: hidden">List item 5</li>
+    <li>List item 6</li>
+    <div style="overflow: hidden"><li>List item 7</li></div>
+</ol>
+
+<ol style="list-style: decimal inside; display:flex">
+<div>
+    <li style="overflow: hidden">List item 1</li>
+    <li style="overflow: hidden">List item 2</li>
+    <li>List item 3</li>
+</div>
+</ol>
+
+<ol style="list-style: decimal inside; display:flex">
+<div style="display:flex">
+    <li style="overflow: hidden">List item 1</li>
+    <li style="overflow: hidden">List item 2</li>
+    <li>List item 3</li>
+</div>
+</ol>
+
+<ol style="list-style: decimal inside; display:inline-flex">
+<div style="display:flex">
+    <li style="overflow: hidden">List item 1</li>
+    <li style="overflow: hidden">List item 2</li>
+    <li>List item 3</li>
+</div>
+</ol>
+
+<ol style="list-style: decimal inside; display:flex">
+    <li style="-moz-column-width:1em; column-width:1em;">item1</li>
+    <li style="-moz-column-width:1em; column-width:1em;">item2</li>
+    <li style="-moz-column-width:1em; column-width:1em;">item3</li>
+</ol>
+
+</body>
+</html>
--- a/layout/reftests/list-item/reftest.list
+++ b/layout/reftests/list-item/reftest.list
@@ -1,10 +1,12 @@
 fuzzy-if(OSX>=1008,55,4) == numbering-1.html numbering-1-ref.html
 == numbering-2.html numbering-2-ref.html
+pref(layout.css.grid.enabled,true) fuzzy-if(OSX>=1008,8,1) == numbering-3.html numbering-3-ref.html
+fuzzy-if(OSX>=1008,72,2) == numbering-4.html numbering-4-ref.html
 == ol-reversed-1a.html ol-reversed-1-ref.html
 asserts(1) == ol-reversed-1b.html ol-reversed-1-ref.html # bug 478135
 == ol-reversed-1c.html ol-reversed-1-ref.html
 == ol-reversed-2.html ol-reversed-2-ref.html
 == ol-reversed-3.html ol-reversed-3-ref.html
 == bullet-space-1.html bullet-space-1-ref.html
 == bullet-space-2.html bullet-space-2-ref.html
 == bullet-intrinsic-isize-1.html bullet-intrinsic-isize-1-ref.html
--- a/mobile/android/base/moz.build
+++ b/mobile/android/base/moz.build
@@ -221,16 +221,17 @@ geckoview_java_files = [
     'GeckoScreenOrientation.java',
     'GeckoService.java',
     'GeckoSharedPrefs.java',
     'GeckoSmsManager.java',
     'GeckoThread.java',
     'GeckoView.java',
     'GeckoViewChrome.java',
     'GeckoViewContent.java',
+    'GeckoViewFragment.java',
     'gfx/Axis.java',
     'gfx/BitmapUtils.java',
     'gfx/BufferedImage.java',
     'gfx/BufferedImageGLInfo.java',
     'gfx/DisplayPortCalculator.java',
     'gfx/DisplayPortMetrics.java',
     'gfx/DrawTimingQueue.java',
     'gfx/DynamicToolbarAnimator.java',
--- a/mobile/android/base/resources/layout/customtabs_activity.xml
+++ b/mobile/android/base/resources/layout/customtabs_activity.xml
@@ -21,17 +21,18 @@
         android:background="@android:color/transparent">
 
         <RelativeLayout android:id="@+id/gecko_layout"
             android:layout_width="match_parent"
             android:layout_height="match_parent"
             android:layout_below="@+id/tablet_tab_strip"
             android:layout_above="@+id/find_in_page">
 
-            <org.mozilla.gecko.GeckoView android:id="@+id/layer_view"
+            <fragment class="org.mozilla.gecko.GeckoViewFragment"
+                android:id="@+id/layer_view"
                 android:layout_width="match_parent"
                 android:layout_height="match_parent"
                 android:scrollbars="none"/>
 
         </RelativeLayout>
 
     </view>
 
--- a/mobile/android/base/resources/layout/gecko_app.xml
+++ b/mobile/android/base/resources/layout/gecko_app.xml
@@ -21,20 +21,21 @@
          android:background="@android:color/transparent">
 
         <RelativeLayout android:id="@+id/gecko_layout"
                         android:layout_width="match_parent"
                         android:layout_height="match_parent"
                         android:layout_below="@+id/tablet_tab_strip"
                         android:layout_above="@+id/find_in_page">
 
-            <org.mozilla.gecko.GeckoView android:id="@+id/layer_view"
-                                         android:layout_width="match_parent"
-                                         android:layout_height="match_parent"
-                                         android:scrollbars="none"/>
+            <fragment class="org.mozilla.gecko.GeckoViewFragment"
+                      android:id="@+id/layer_view"
+                      android:layout_width="match_parent"
+                      android:layout_height="match_parent"
+                      android:scrollbars="none"/>
 
             <AbsoluteLayout android:id="@+id/plugin_container"
                             android:background="@android:color/transparent"
                             android:layout_width="match_parent"
                             android:layout_height="match_parent"/>
 
             <org.mozilla.gecko.FormAssistPopup android:id="@+id/form_assist_popup"
                                                android:layout_width="match_parent"
--- a/mobile/android/config/tooltool-manifests/android/releng.manifest
+++ b/mobile/android/config/tooltool-manifests/android/releng.manifest
@@ -82,10 +82,18 @@
 {
 "version": "rust stdlib repack 1.8.0 (db2939409 2016-04-11)",
 "size": 17874585,
 "visibility": "public",
 "digest": "bea72d352a70411240d95c7ab33e97d85be9cf0548807968dc721b14d816c70f4fd77a0ca1b43b851772ec06c0c1395b474ad342d506f8987fdbb070393c81f6",
 "algorithm": "sha512",
 "filename": "rust-std-lib.tar.bz2",
 "unpack": true
+},
+{
+"algorithm": "sha512",
+"visibility": "public",
+"filename": "dotgradle.tar.xz",
+"unpack": true,
+"digest": "9f082ccd71ad18991eb71fcad355c6990f50a72a09ab9b79696521485656083a72faf5a8d4714de9c4b901ee2319b6786a51964846bb7075061642a8505501c2",
+"size": 512
 }
 ]
--- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoView.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoView.java
@@ -42,16 +42,18 @@ public class GeckoView extends LayerView
     private static final String DEFAULT_SHARED_PREFERENCES_FILE = "GeckoView";
     private static final String LOGTAG = "GeckoView";
 
     private ChromeDelegate mChromeDelegate;
     private ContentDelegate mContentDelegate;
 
     private InputConnectionListener mInputConnectionListener;
 
+    private boolean onAttachedToWindowCalled;
+
     @Override
     public void handleMessage(final String event, final JSONObject message) {
         ThreadUtils.postToUiThread(new Runnable() {
             @Override
             public void run() {
                 try {
                     if (event.equals("Gecko:Ready")) {
                         handleReady(message);
@@ -202,54 +204,71 @@ public class GeckoView extends LayerView
         stateSaved = true;
         return new StateBinder(superState, this.window);
     }
 
     @Override
     protected void onRestoreInstanceState(final Parcelable state)
     {
         final StateBinder stateBinder = (StateBinder) state;
-        // We have to always call super.onRestoreInstanceState because View keeps
-        // track of these calls and throws an exception when we don't call it.
-        super.onRestoreInstanceState(stateBinder.superState);
 
         if (stateBinder.window != null) {
             this.window = stateBinder.window;
         }
         stateSaved = false;
+
+        if (onAttachedToWindowCalled) {
+            reattachWindow();
+        }
+
+        // We have to always call super.onRestoreInstanceState because View keeps
+        // track of these calls and throws an exception when we don't call it.
+        super.onRestoreInstanceState(stateBinder.superState);
+    }
+
+    private void openWindow() {
+        final DisplayMetrics metrics = getContext().getResources().getDisplayMetrics();
+
+        final String chromeURI = getGeckoInterface().getDefaultChromeURI();
+
+        if (GeckoThread.isStateAtLeast(GeckoThread.State.PROFILE_READY)) {
+            Window.open(window, this, getCompositor(),
+                        chromeURI, metrics.widthPixels, metrics.heightPixels);
+        } else {
+            GeckoThread.queueNativeCallUntil(GeckoThread.State.PROFILE_READY, Window.class,
+                    "open", window, GeckoView.class, this, Object.class, getCompositor(),
+                    String.class, chromeURI, metrics.widthPixels, metrics.heightPixels);
+        }
+    }
+
+    private void reattachWindow() {
+        if (GeckoThread.isStateAtLeast(GeckoThread.State.PROFILE_READY)) {
+            window.reattach(this, getCompositor());
+        } else {
+            GeckoThread.queueNativeCallUntil(GeckoThread.State.PROFILE_READY,
+                    window, "reattach", GeckoView.class, this, Object.class, getCompositor());
+        }
     }
 
     @Override
     public void onAttachedToWindow()
     {
         final DisplayMetrics metrics = getContext().getResources().getDisplayMetrics();
 
         if (window == null) {
             // Open a new nsWindow if we didn't have one from before.
             window = new Window();
-            final String chromeURI = getGeckoInterface().getDefaultChromeURI();
-
-            if (GeckoThread.isStateAtLeast(GeckoThread.State.PROFILE_READY)) {
-                Window.open(window, this, getCompositor(),
-                            chromeURI, metrics.widthPixels, metrics.heightPixels);
-            } else {
-                GeckoThread.queueNativeCallUntil(GeckoThread.State.PROFILE_READY, Window.class,
-                        "open", window, GeckoView.class, this, Object.class, getCompositor(),
-                        String.class, chromeURI, metrics.widthPixels, metrics.heightPixels);
-            }
+            openWindow();
         } else {
-            if (GeckoThread.isStateAtLeast(GeckoThread.State.PROFILE_READY)) {
-                window.reattach(this, getCompositor());
-            } else {
-                GeckoThread.queueNativeCallUntil(GeckoThread.State.PROFILE_READY,
-                        window, "reattach", GeckoView.class, this, Object.class, getCompositor());
-            }
+            reattachWindow();
         }
 
         super.onAttachedToWindow();
+
+        onAttachedToWindowCalled = true;
     }
 
     @Override
     public void onDetachedFromWindow()
     {
         super.onDetachedFromWindow();
         super.destroy();
 
@@ -262,16 +281,18 @@ public class GeckoView extends LayerView
             window.close();
             window.disposeNative();
         } else {
             GeckoThread.queueNativeCallUntil(GeckoThread.State.PROFILE_READY,
                     window, "close");
             GeckoThread.queueNativeCallUntil(GeckoThread.State.PROFILE_READY,
                     window, "disposeNative");
         }
+
+        onAttachedToWindowCalled = false;
     }
 
     @WrapForJNI public static final int LOAD_DEFAULT = 0;
     @WrapForJNI public static final int LOAD_NEW_TAB = 1;
     @WrapForJNI public static final int LOAD_SWITCH_TAB = 2;
 
     public void loadUri(String uri, int flags) {
         if (window == null) {
new file mode 100644
--- /dev/null
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoViewFragment.java
@@ -0,0 +1,52 @@
+/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
+ * 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/. */
+
+package org.mozilla.gecko;
+
+import android.support.v4.app.Fragment;
+import android.os.Bundle;
+import android.os.Parcelable;
+import android.util.Log;
+import android.util.SparseArray;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+public class GeckoViewFragment extends android.support.v4.app.Fragment {
+    private static final String LOGTAG = "GeckoViewFragment";
+
+    private static Parcelable state = null;
+    private static GeckoViewFragment lastUsed = null;
+    private GeckoView geckoView = null;
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        setRetainInstance(true);
+        super.onCreate(savedInstanceState);
+    }
+
+    @Override
+    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
+        geckoView = new GeckoView(getContext());
+        return geckoView;
+    }
+
+    @Override
+    public void onResume() {
+        if (state != null && lastUsed != this) {
+            // "Restore" the window from the previously used GeckoView to this GeckoView and attach it
+            geckoView.onRestoreInstanceState(state);
+            state = null;
+        }
+        super.onResume();
+    }
+
+    @Override
+    public void onPause() {
+        state = geckoView.onSaveInstanceState();
+        lastUsed = this;
+        super.onPause();
+    }
+}
--- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/gfx/GeckoLayerClient.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/gfx/GeckoLayerClient.java
@@ -86,19 +86,19 @@ class GeckoLayerClient implements LayerV
      * 3) whenever reading multiple fields from mViewportMetrics without synchronization (i.e. in
      *    case 1 above) you should always first grab a local copy of the reference, and then use
      *    that because mViewportMetrics might get reassigned in between reading the different
      *    fields. */
     private volatile ImmutableViewportMetrics mViewportMetrics;
 
     private volatile boolean mGeckoIsReady;
 
-    private final PanZoomController mPanZoomController;
+    /* package */ final PanZoomController mPanZoomController;
     private final DynamicToolbarAnimator mToolbarAnimator;
-    private final LayerView mView;
+    /* package */ final LayerView mView;
 
     /* This flag is true from the time that browser.js detects a first-paint is about to start,
      * to the time that we receive the first-paint composite notification from the compositor.
      * Note that there is a small race condition with this; if there are two paints that both
      * have the first-paint flag set, and the second paint happens concurrently with the
      * composite for the first paint, then this flag may be set to true prematurely. Fixing this
      * is possible but risky; see https://bugzilla.mozilla.org/show_bug.cgi?id=797615#c751
      */
@@ -137,16 +137,20 @@ class GeckoLayerClient implements LayerV
         mView.setListener(this);
         mContentDocumentIsDisplayed = true;
     }
 
     public void setOverscrollHandler(final Overscroll listener) {
         mPanZoomController.setOverscrollHandler(listener);
     }
 
+    public void setGeckoReady(boolean ready) {
+        mGeckoIsReady = ready;
+    }
+
     @Override // PanZoomTarget
     public boolean isGeckoReady() {
         return mGeckoIsReady;
     }
 
     /** Attaches to root layer so that Gecko appears. */
     @WrapForJNI(calledFrom = "gecko")
     private void onGeckoReady() {
@@ -161,16 +165,17 @@ class GeckoLayerClient implements LayerV
         // Gecko being ready is one of the two conditions (along with having an available
         // surface) that cause us to create the compositor. So here, now that we know gecko
         // is ready, call updateCompositor() to see if we can actually do the creation.
         // This needs to run on the UI thread so that the surface validity can't change on
         // us while we're in the middle of creating the compositor.
         mView.post(new Runnable() {
             @Override
             public void run() {
+                mPanZoomController.attach();
                 mView.updateCompositor();
             }
         });
     }
 
     public void destroy() {
         mPanZoomController.destroy();
         mToolbarAnimator.destroy();
--- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/gfx/LayerView.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/gfx/LayerView.java
@@ -26,16 +26,17 @@ import org.mozilla.gecko.ZoomConstraints
 import android.content.Context;
 import android.graphics.Canvas;
 import android.graphics.Color;
 import android.graphics.Matrix;
 import android.graphics.Point;
 import android.graphics.PointF;
 import android.graphics.Rect;
 import android.graphics.SurfaceTexture;
+import android.os.Parcelable;
 import android.util.AttributeSet;
 import android.view.KeyEvent;
 import android.view.MotionEvent;
 import android.view.SurfaceHolder;
 import android.view.SurfaceView;
 import android.view.TextureView;
 import android.view.View;
 import android.view.ViewGroup;
@@ -66,16 +67,18 @@ public class LayerView extends ScrollVie
     private float mSurfaceTranslation;
 
     /* This should only be modified on the Java UI thread. */
     private final Overscroll mOverscroll;
 
     private boolean mServerSurfaceValid;
     private int mWidth, mHeight;
 
+    private boolean onAttachedToWindowCalled;
+
     /* This is written by the Gecko thread and the UI thread, and read by the UI thread. */
     @WrapForJNI(stubName = "CompositorCreated", calledFrom = "ui")
     /* package */ volatile boolean mCompositorCreated;
 
     private class Compositor extends JNIObject {
         public Compositor() {
         }
 
@@ -111,16 +114,18 @@ public class LayerView extends ScrollVie
             mCompositorCreated = true;
         }
 
         @WrapForJNI(calledFrom = "gecko")
         private void destroy() {
             // The nsWindow has been closed. First mark our compositor as destroyed.
             LayerView.this.mCompositorCreated = false;
 
+            LayerView.this.mLayerClient.setGeckoReady(false);
+
             // Then clear out any pending calls on the UI thread by disposing on the UI thread.
             ThreadUtils.postToUiThread(new Runnable() {
                 @Override
                 public void run() {
                     disposeNative();
                 }
             });
         }
@@ -294,16 +299,24 @@ public class LayerView extends ScrollVie
         }
         if (mPanZoomController != null && mPanZoomController.onMotionEvent(event)) {
             return true;
         }
         return false;
     }
 
     @Override
+    protected void onRestoreInstanceState(final Parcelable state) {
+        if (onAttachedToWindowCalled) {
+            attachCompositor();
+        }
+        super.onRestoreInstanceState(state);
+    }
+
+    @Override
     protected void onAttachedToWindow() {
         super.onAttachedToWindow();
 
         // We are adding descendants to this LayerView, but we don't want the
         // descendants to affect the way LayerView retains its focus.
         setDescendantFocusability(FOCUS_BLOCK_DESCENDANTS);
 
         // This check should not be done before the view is attached to a window
@@ -347,16 +360,25 @@ public class LayerView extends ScrollVie
             container.addView(mSurfaceView, new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
             addView(container, new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
 
             SurfaceHolder holder = mSurfaceView.getHolder();
             holder.addCallback(new SurfaceListener());
         }
 
         attachCompositor();
+
+        onAttachedToWindowCalled = true;
+    }
+
+    @Override
+    protected void onDetachedFromWindow() {
+        super.onDetachedFromWindow();
+
+        onAttachedToWindowCalled = false;
     }
 
     // Don't expose GeckoLayerClient to things outside this package; only expose it as an Object
     GeckoLayerClient getLayerClient() { return mLayerClient; }
 
     public PanZoomController getPanZoomController() { return mPanZoomController; }
     public DynamicToolbarAnimator getDynamicToolbarAnimator() { return mToolbarAnimator; }
 
--- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/gfx/NativePanZoomController.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/gfx/NativePanZoomController.java
@@ -138,16 +138,17 @@ class NativePanZoomController extends JN
 
         return handleMouseEvent(event.getActionMasked(), event.getEventTime(), event.getMetaState(), x, y, event.getButtonState());
     }
 
 
     NativePanZoomController(PanZoomTarget target, View view) {
         mTarget = target;
         mView = (LayerView) view;
+        mDestroyed = true;
 
         String[] prefs = { "ui.scrolling.negate_wheel_scroll" };
         mPrefsObserver = new PrefsHelper.PrefHandlerBase() {
             @Override public void prefValue(String pref, boolean value) {
                 if (pref.equals("ui.scrolling.negate_wheel_scroll")) {
                     mNegateWheelScroll = value;
                 }
             }
@@ -203,16 +204,21 @@ class NativePanZoomController extends JN
         }
         if (mDestroyed || !mTarget.isGeckoReady()) {
             return;
         }
         mDestroyed = true;
         disposeNative();
     }
 
+    @Override
+    public void attach() {
+        mDestroyed = false;
+    }
+
     @WrapForJNI(calledFrom = "ui", dispatchTo = "gecko") @Override // JNIObject
     protected native void disposeNative();
 
     @Override
     public void setOverscrollHandler(final Overscroll handler) {
         mOverscroll = handler;
     }
 
--- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/gfx/PanZoomController.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/gfx/PanZoomController.java
@@ -19,16 +19,17 @@ public interface PanZoomController {
 
     static class Factory {
         static PanZoomController create(PanZoomTarget target, View view, EventDispatcher dispatcher) {
             return new NativePanZoomController(target, view);
         }
     }
 
     public void destroy();
+    public void attach();
 
     public boolean onTouchEvent(MotionEvent event);
     public boolean onMotionEvent(MotionEvent event);
     public void onMotionEventVelocity(final long aEventTime, final float aSpeedY);
 
     public void setOverscrollHandler(final Overscroll controller);
 
     public void setIsLongpressEnabled(boolean isLongpressEnabled);
--- a/parser/expat/lib/moz_extensions.c
+++ b/parser/expat/lib/moz_extensions.c
@@ -40,27 +40,24 @@ int MOZ_XMLCheckQName(const char* ptr, c
           or we've already seen a colon. */
       if (ns_aware && (nmstrt || *colon || ptr + 2 == end)) {
         return MOZ_EXPAT_MALFORMED;
       }
       *colon = ptr;
       nmstrt = ns_aware; /* e.g. "a:0" should be valid if !ns_aware */
       break;
     case BT_NONASCII:
-      if (nmstrt && !IS_NMSTRT_CHAR_MINBPC(ptr)) {
-        /* If this is a valid name character and we're namespace-aware, the
-           QName is malformed.  Otherwise, this character's invalid at the
-           start of a name (or, if we're namespace-aware, at the start of a
-           localpart). */
-        return (IS_NAME_CHAR_MINBPC(ptr) && ns_aware) ?
-               MOZ_EXPAT_MALFORMED :
-               MOZ_EXPAT_INVALID_CHARACTER;
+      if (!IS_NAME_CHAR_MINBPC(ptr) ||
+          (nmstrt && !*colon && !IS_NMSTRT_CHAR_MINBPC(ptr))) {
+        return MOZ_EXPAT_INVALID_CHARACTER;
       }
-      if (!IS_NAME_CHAR_MINBPC(ptr)) {
-        return MOZ_EXPAT_INVALID_CHARACTER;
+      if (nmstrt && *colon && !IS_NMSTRT_CHAR_MINBPC(ptr)) {
+        /* If a non-starting character like a number is right after the colon,
+           this is a namespace error, not invalid character */
+        return MOZ_EXPAT_MALFORMED;
       }
       nmstrt = 0;
       break;
     case BT_NMSTRT:
     case BT_HEX:
       nmstrt = 0;
       break;
     case BT_DIGIT:
--- a/services/common/kinto-offline-client.js
+++ b/services/common/kinto-offline-client.js
@@ -15,17 +15,17 @@
 
 /*
  * This file is generated from kinto.js - do not modify directly.
  */
 
 this.EXPORTED_SYMBOLS = ["loadKinto"];
 
 /*
- * Version 4.0.2 - ef8a96f
+ * Version 4.0.3 - 8100433
  */
 
 (function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.loadKinto = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
 "use strict";
 
 Object.defineProperty(exports, "__esModule", {
   value: true
 });
@@ -2639,17 +2639,17 @@ function pFinally(promise, fn) {
  */
 function deepEqual(a, b) {
   if (a === b) {
     return true;
   }
   if (typeof a !== typeof b) {
     return false;
   }
-  if (!(a instanceof Object) || !(b instanceof Object)) {
+  if (!(a && typeof a == "object") || !(b && typeof b == "object")) {
     return false;
   }
   if (Object.keys(a).length !== Object.keys(b).length) {
     return false;
   }
   for (let k in a) {
     if (!deepEqual(a[k], b[k])) {
       return false;
--- a/services/sync/tests/unit/test_bookmark_order.js
+++ b/services/sync/tests/unit/test_bookmark_order.js
@@ -1,75 +1,64 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 _("Making sure after processing incoming bookmarks, they show up in the right order");
-Cu.import("resource://gre/modules/PlacesUtils.jsm", this);
+Cu.import("resource://gre/modules/PlacesUtils.jsm");
+Cu.import("resource://gre/modules/Task.jsm");
 Cu.import("resource://services-sync/engines/bookmarks.js");
 Cu.import("resource://services-sync/service.js");
 Cu.import("resource://services-sync/util.js");
 
-function getBookmarks(folderId) {
-  let bookmarks = [];
-
-  let pos = 0;
-  while (true) {
-    let itemId = PlacesUtils.bookmarks.getIdForItemAt(folderId, pos);
-    _("Got itemId", itemId, "under", folderId, "at", pos);
-    if (itemId == -1)
-      break;
+var check = Task.async(function* (expected, message) {
+  let root = yield PlacesUtils.promiseBookmarksTree();
 
-    let isOrphan = PlacesUtils.annotations.itemHasAnnotation(itemId,
-      "sync/parent");
-    switch (PlacesUtils.bookmarks.getItemType(itemId)) {
-      case PlacesUtils.bookmarks.TYPE_BOOKMARK:
-        let title = PlacesUtils.bookmarks.getItemTitle(itemId);
-        if (isOrphan) {
-          let requestedParent = PlacesUtils.annotations.getItemAnnotation(
-            itemId, "sync/parent");
-          bookmarks.push({ title, requestedParent });
-        } else {
-          bookmarks.push(title);
+  let bookmarks = (function mapTree(children) {
+    return children.map(child => {
+      let result = {
+        guid: child.guid,
+        index: child.index,
+      };
+      if (child.children) {
+        result.children = mapTree(child.children);
+      }
+      if (child.annos) {
+        let orphanAnno = child.annos.find(
+          anno => anno.name == "sync/parent");
+        if (orphanAnno) {
+          result.requestedParent = orphanAnno.value;
         }
-        break;
-      case PlacesUtils.bookmarks.TYPE_FOLDER:
-        let titles = getBookmarks(itemId);
-        if (isOrphan) {
-          let requestedParent = PlacesUtils.annotations.getItemAnnotation(
-            itemId, "sync/parent");
-          bookmarks.push({ titles, requestedParent });
-        } else {
-          bookmarks.push(titles);
-        }
-        break;
-      default:
-        _("Unsupported item type..");
-    }
-
-    pos++;
-  }
-
-  return bookmarks;
-}
-
-function check(expected) {
-  let bookmarks = getBookmarks(PlacesUtils.bookmarks.unfiledBookmarksFolder);
+      }
+      return result;
+    });
+  }(root.children));
 
   _("Checking if the bookmark structure is", JSON.stringify(expected));
   _("Got bookmarks:", JSON.stringify(bookmarks));
-  do_check_true(Utils.deepEquals(bookmarks, expected));
-}
+  deepEqual(bookmarks, expected);
+});
 
-function run_test() {
+add_task(function* test_bookmark_order() {
   let store = new BookmarksEngine(Service)._store;
   initTestLogging("Trace");
 
   _("Starting with a clean slate of no bookmarks");
   store.wipe();
-  check([]);
+  yield check([{
+    guid: PlacesUtils.bookmarks.menuGuid,
+    index: 0,
+  }, {
+    guid: PlacesUtils.bookmarks.toolbarGuid,
+    index: 1,
+  }, {
+    // Index 2 is the tags root. (Root indices depend on the order of the
+    // `CreateRoot` calls in `Database::CreateBookmarkRoots`).
+    guid: PlacesUtils.bookmarks.unfiledGuid,
+    index: 3,
+  }], "clean slate");
 
   function bookmark(name, parent) {
     let bookmark = new Bookmark("http://weave.server/my-bookmark");
     bookmark.id = name;
     bookmark.title = name;
     bookmark.bmkUri = "http://uri/";
     bookmark.parentid = parent || "unfiled";
     bookmark.tags = [];
@@ -89,73 +78,413 @@ function run_test() {
     store._childrenToOrder = {};
     store.applyIncoming(record);
     store._orderChildren();
     delete store._childrenToOrder;
   }
   let id10 = "10_aaaaaaaaa";
   _("basic add first bookmark");
   apply(bookmark(id10, ""));
-  check([id10]);
+  yield check([{
+    guid: PlacesUtils.bookmarks.menuGuid,
+    index: 0,
+  }, {
+    guid: PlacesUtils.bookmarks.toolbarGuid,
+    index: 1,
+  }, {
+    guid: PlacesUtils.bookmarks.unfiledGuid,
+    index: 3,
+    children: [{
+      guid: id10,
+      index: 0,
+    }],
+  }], "basic add first bookmark");
   let id20 = "20_aaaaaaaaa";
   _("basic append behind 10");
   apply(bookmark(id20, ""));
-  check([id10, id20]);
+  yield check([{
+    guid: PlacesUtils.bookmarks.menuGuid,
+    index: 0,
+  }, {
+    guid: PlacesUtils.bookmarks.toolbarGuid,
+    index: 1,
+  }, {
+    guid: PlacesUtils.bookmarks.unfiledGuid,
+    index: 3,
+    children: [{
+      guid: id10,
+      index: 0,
+    }, {
+      guid: id20,
+      index: 1,
+    }],
+  }], "basic append behind 10");
 
   let id31 = "31_aaaaaaaaa";
   let id30 = "f30_aaaaaaaa";
   _("basic create in folder");
   apply(bookmark(id31, id30));
   let f30 = folder(id30, "", [id31]);
   apply(f30);
-  check([id10, id20, [id31]]);
+  yield check([{
+    guid: PlacesUtils.bookmarks.menuGuid,
+    index: 0,
+  }, {
+    guid: PlacesUtils.bookmarks.toolbarGuid,
+    index: 1,
+  }, {
+    guid: PlacesUtils.bookmarks.unfiledGuid,
+    index: 3,
+    children: [{
+      guid: id10,
+      index: 0,
+    }, {
+      guid: id20,
+      index: 1,
+    }, {
+      guid: id30,
+      index: 2,
+      children: [{
+        guid: id31,
+        index: 0,
+      }],
+    }],
+  }], "basic create in folder");
 
   let id41 = "41_aaaaaaaaa";
   let id40 = "f40_aaaaaaaa";
   _("insert missing parent -> append to unfiled");
   apply(bookmark(id41, id40));
-  check([id10, id20, [id31], { title: id41, requestedParent: id40 }]);
+  yield check([{
+    guid: PlacesUtils.bookmarks.menuGuid,
+    index: 0,
+  }, {
+    guid: PlacesUtils.bookmarks.toolbarGuid,
+    index: 1,
+  }, {
+    guid: PlacesUtils.bookmarks.unfiledGuid,
+    index: 3,
+    children: [{
+      guid: id10,
+      index: 0,
+    }, {
+      guid: id20,
+      index: 1,
+    }, {
+      guid: id30,
+      index: 2,
+      children: [{
+        guid: id31,
+        index: 0,
+      }],
+    }, {
+      guid: id41,
+      index: 3,
+      requestedParent: id40,
+    }],
+  }], "insert missing parent -> append to unfiled");
 
   let id42 = "42_aaaaaaaaa";
 
   _("insert another missing parent -> append");
   apply(bookmark(id42, id40));
-  check([id10, id20, [id31], { title: id41, requestedParent: id40 },
-    { title: id42, requestedParent: id40 }]);
+  yield check([{
+    guid: PlacesUtils.bookmarks.menuGuid,
+    index: 0,
+  }, {
+    guid: PlacesUtils.bookmarks.toolbarGuid,
+    index: 1,
+  }, {
+    guid: PlacesUtils.bookmarks.unfiledGuid,
+    index: 3,
+    children: [{
+      guid: id10,
+      index: 0,
+    }, {
+      guid: id20,
+      index: 1,
+    }, {
+      guid: id30,
+      index: 2,
+      children: [{
+        guid: id31,
+        index: 0,
+      }],
+    }, {
+      guid: id41,
+      index: 3,
+      requestedParent: id40,
+    }, {
+      guid: id42,
+      index: 4,
+      requestedParent: id40,
+    }],
+  }], "insert another missing parent -> append");
 
   _("insert folder -> move children and followers");
   let f40 = folder(id40, "", [id41, id42]);
   apply(f40);
-  check([id10, id20, [id31], [id41, id42]]);
+  yield check([{
+    guid: PlacesUtils.bookmarks.menuGuid,
+    index: 0,
+  }, {
+    guid: PlacesUtils.bookmarks.toolbarGuid,
+    index: 1,
+  }, {
+    guid: PlacesUtils.bookmarks.unfiledGuid,
+    index: 3,
+    children: [{
+      guid: id10,
+      index: 0,
+    }, {
+      guid: id20,
+      index: 1,
+    }, {
+      guid: id30,
+      index: 2,
+      children: [{
+        guid: id31,
+        index: 0,
+      }],
+    }, {
+      guid: id40,
+      index: 3,
+      children: [{
+        guid: id41,
+        index: 0,
+      }, {
+        guid: id42,
+        index: 1,
+      }]
+    }],
+  }], "insert folder -> move children and followers");
 
   _("Moving 41 behind 42 -> update f40");
   f40.children = [id42, id41];
   apply(f40);
-  check([id10, id20, [id31], [id42, id41]]);
+  yield check([{
+    guid: PlacesUtils.bookmarks.menuGuid,
+    index: 0,
+  }, {
+    guid: PlacesUtils.bookmarks.toolbarGuid,
+    index: 1,
+  }, {
+    guid: PlacesUtils.bookmarks.unfiledGuid,
+    index: 3,
+    children: [{
+      guid: id10,
+      index: 0,
+    }, {
+      guid: id20,
+      index: 1,
+    }, {
+      guid: id30,
+      index: 2,
+      children: [{
+        guid: id31,
+        index: 0,
+      }],
+    }, {
+      guid: id40,
+      index: 3,
+      children: [{
+        guid: id42,
+        index: 0,
+      }, {
+        guid: id41,
+        index: 1,
+      }]
+    }],
+  }], "Moving 41 behind 42 -> update f40");
 
   _("Moving 10 back to front -> update 10, 20");
   f40.children = [id41, id42];
   apply(f40);
-  check([id10, id20, [id31], [id41, id42]]);
+  yield check([{
+    guid: PlacesUtils.bookmarks.menuGuid,
+    index: 0,
+  }, {
+    guid: PlacesUtils.bookmarks.toolbarGuid,
+    index: 1,
+  }, {
+    guid: PlacesUtils.bookmarks.unfiledGuid,
+    index: 3,
+    children: [{
+      guid: id10,
+      index: 0,
+    }, {
+      guid: id20,
+      index: 1,
+    }, {
+      guid: id30,
+      index: 2,
+      children: [{
+        guid: id31,
+        index: 0,
+      }],
+    }, {
+      guid: id40,
+      index: 3,
+      children: [{
+        guid: id41,
+        index: 0,
+      }, {
+        guid: id42,
+        index: 1,
+      }]
+    }],
+  }], "Moving 10 back to front -> update 10, 20");
 
   _("Moving 20 behind 42 in f40 -> update 50");
   apply(bookmark(id20, id40));
-  check([id10, [id31], [id41, id42, id20]]);
+  yield check([{
+    guid: PlacesUtils.bookmarks.menuGuid,
+    index: 0,
+  }, {
+    guid: PlacesUtils.bookmarks.toolbarGuid,
+    index: 1,
+  }, {
+    guid: PlacesUtils.bookmarks.unfiledGuid,
+    index: 3,
+    children: [{
+      guid: id10,
+      index: 0,
+    }, {
+      guid: id30,
+      index: 1,
+      children: [{
+        guid: id31,
+        index: 0,
+      }],
+    }, {
+      guid: id40,
+      index: 2,
+      children: [{
+        guid: id41,
+        index: 0,
+      }, {
+        guid: id42,
+        index: 1,
+      }, {
+        guid: id20,
+        index: 2,
+      }]
+    }],
+  }], "Moving 20 behind 42 in f40 -> update 50");
 
   _("Moving 10 in front of 31 in f30 -> update 10, f30");
   apply(bookmark(id10, id30));
   f30.children = [id10, id31];
   apply(f30);
-  check([[id10, id31], [id41, id42, id20]]);
+  yield check([{
+    guid: PlacesUtils.bookmarks.menuGuid,
+    index: 0,
+  }, {
+    guid: PlacesUtils.bookmarks.toolbarGuid,
+    index: 1,
+  }, {
+    guid: PlacesUtils.bookmarks.unfiledGuid,
+    index: 3,
+    children: [{
+      guid: id30,
+      index: 0,
+      children: [{
+        guid: id10,
+        index: 0,
+      }, {
+        guid: id31,
+        index: 1,
+      }],
+    }, {
+      guid: id40,
+      index: 1,
+      children: [{
+        guid: id41,
+        index: 0,
+      }, {
+        guid: id42,
+        index: 1,
+      }, {
+        guid: id20,
+        index: 2,
+      }]
+    }],
+  }], "Moving 10 in front of 31 in f30 -> update 10, f30");
 
   _("Moving 20 from f40 to f30 -> update 20, f30");
   apply(bookmark(id20, id30));
   f30.children = [id10, id20, id31];
   apply(f30);
-  check([[id10, id20, id31], [id41, id42]]);
+  yield check([{
+    guid: PlacesUtils.bookmarks.menuGuid,
+    index: 0,
+  }, {
+    guid: PlacesUtils.bookmarks.toolbarGuid,
+    index: 1,
+  }, {
+    guid: PlacesUtils.bookmarks.unfiledGuid,
+    index: 3,
+    children: [{
+      guid: id30,
+      index: 0,
+      children: [{
+        guid: id10,
+        index: 0,
+      }, {
+        guid: id20,
+        index: 1,
+      }, {
+        guid: id31,
+        index: 2,
+      }],
+    }, {
+      guid: id40,
+      index: 1,
+      children: [{
+        guid: id41,
+        index: 0,
+      }, {
+        guid: id42,
+        index: 1,
+      }]
+    }],
+  }], "Moving 20 from f40 to f30 -> update 20, f30");
 
   _("Move 20 back to front -> update 20, f30");
   apply(bookmark(id20, ""));
   f30.children = [id10, id31];
   apply(f30);
-  check([[id10, id31], [id41, id42], id20]);
+  yield check([{
+    guid: PlacesUtils.bookmarks.menuGuid,
+    index: 0,
+  }, {
+    guid: PlacesUtils.bookmarks.toolbarGuid,
+    index: 1,
+  }, {
+    guid: PlacesUtils.bookmarks.unfiledGuid,
+    index: 3,
+    children: [{
+      guid: id30,
+      index: 0,
+      children: [{
+        guid: id10,
+        index: 0,
+      }, {
+        guid: id31,
+        index: 1,
+      }],
+    }, {
+      guid: id40,
+      index: 1,
+      children: [{
+        guid: id41,
+        index: 0,
+      }, {
+        guid: id42,
+        index: 1,
+      }],
+    }, {
+      guid: id20,
+      index: 2,
+    }],
+  }], "Move 20 back to front -> update 20, f30");
 
-}
+});
--- a/taskcluster/ci/legacy/tasks/builds/android_api_15_gradle.yml
+++ b/taskcluster/ci/legacy/tasks/builds/android_api_15_gradle.yml
@@ -35,16 +35,17 @@ task:
       MOZHARNESS_CONFIG: >
           builds/releng_base_android_64_builds.py
           disable_signing.py
           platform_supports_post_upload_to_latest.py
       MOZHARNESS_ACTIONS: "get-secrets build multi-l10n update"
       MH_CUSTOM_BUILD_VARIANT_CFG: api-15-gradle
       MH_BRANCH: {{project}}
       MH_BUILD_POOL: taskcluster
+      GRADLE_USER_HOME: '/home/worker/workspace/build/src/dotgradle'
 
     command: ["/bin/bash", "bin/build.sh"]
 
   extra:
     treeherderEnv:
       - production
       - staging
     treeherder:
--- a/testing/docker/android-gradle-build/Dockerfile
+++ b/testing/docker/android-gradle-build/Dockerfile
@@ -1,18 +1,30 @@
-FROM          taskcluster/centos6-build-upd:0.1.3.20160222133000
+# TODO remove VOLUME below when the base image is updated next.
+FROM          taskcluster/centos6-build-upd:0.1.6.20160329195300
 MAINTAINER    Nick Alexander <nalexander@mozilla.com>
 
 # BEGIN ../desktop-build/Dockerfile
 
+# TODO remove when base image is updated
+VOLUME /home/worker/workspace
+VOLUME /home/worker/tooltool-cache
+
 # Add build scripts; these are the entry points from the taskcluster worker, and
 # operate on environment variables
 ADD             bin /home/worker/bin
 RUN             chmod +x /home/worker/bin/*
 
+# Add wrapper scripts for xvfb allowing tasks to easily retry starting up xvfb
+# %include testing/docker/recipes/xvfb.sh
+ADD topsrcdir/testing/docker/recipes/xvfb.sh /home/worker/scripts/xvfb.sh
+
+# Add configuration
+COPY            dot-config                    /home/worker/.config
+
 # Generate machine uuid file
 RUN dbus-uuidgen --ensure=/var/lib/dbus/machine-id
 
 # Stubbed out credentials; mozharness looks for this file an issues a WARNING
 # if it's not found, which causes the build to fail.  Note that this needs to
 # be in the parent of the workspace directory and in the directory where
 # mozharness is run (not its --work-dir).  See Bug 1169652.
 ADD           oauth.txt /home/worker/
@@ -34,21 +46,24 @@ WORKDIR /
 USER root
 
 # Update base.
 RUN yum upgrade -y
 
 # Install JDK and Sonatype Nexus.  Cribbed directly from
 # https://github.com/sonatype/docker-nexus/blob/fffd2c61b2368292040910c055cf690c8e76a272/oss/Dockerfile.
 
+# Install the screen package here to use with xvfb.
+# Move installation to base centos6-build image once Bug 1272629 is fixed
 RUN yum install -y \
   createrepo \
   curl \
   java-1.7.0-openjdk-devel \
   java-1.7.0-openjdk \
+  screen \
   sudo \
   tar \
   unzip \
   wget \
   zip \
   && yum clean all
 
 ENV NEXUS_VERSION 2.12.0-01
@@ -72,8 +87,11 @@ RUN tar zxf nexus-${NEXUS_VERSION}-bundl
 
 # Install tooltool directly from github.
 RUN mkdir /build
 ADD https://raw.githubusercontent.com/mozilla/build-tooltool/master/tooltool.py /build/tooltool.py
 RUN chmod +rx /build/tooltool.py
 
 # Back to the centos6-build workdir, matching desktop-build.
 WORKDIR /home/worker
+
+# Set a default command useful for debugging
+CMD ["/bin/bash", "--login"]
copy from testing/docker/desktop-build/dot-config/pip/pip.conf
copy to testing/docker/android-gradle-build/dot-config/pip/pip.conf
--- a/testing/web-platform/meta/MANIFEST.json
+++ b/testing/web-platform/meta/MANIFEST.json
@@ -1,12 +1,20 @@
 {
   "items": {
     "manual": [
       {
+        "path": "dummy.xml",
+        "url": "/dummy.xml"
+      },
+      {
+        "path": "dummy.xhtml",
+        "url": "/dummy.xhtml"
+      },
+      {
         "path": "2dcontext/conformance-requirements/2d.coordinatespace-manual.html",
         "url": "/2dcontext/conformance-requirements/2d.coordinatespace-manual.html"
       },
       {
         "path": "2dcontext/drawing-paths-to-the-canvas/canvas_focus_drawFocusIfNeeded_AAPI_001-manual.html",
         "url": "/2dcontext/drawing-paths-to-the-canvas/canvas_focus_drawFocusIfNeeded_AAPI_001-manual.html"
       },
       {
--- a/testing/web-platform/meta/dom/historical.html.ini
+++ b/testing/web-platform/meta/dom/historical.html.ini
@@ -1,14 +1,18 @@
 [historical.html]
   type: testharness
   [Historical DOM features must be removed: CDATASection]
     expected: FAIL
+    bug: 660660
 
   [Historical DOM features must be removed: DOMError]
     expected: FAIL
+    bug: 1120178
 
   [Historical DOM features must be removed: createCDATASection]
     expected: FAIL
+    bug: 660660
 
   [Node member must be nuked: rootNode]
     expected: FAIL
+    bug: 1269155
 
--- a/testing/web-platform/meta/dom/interfaces.html.ini
+++ b/testing/web-platform/meta/dom/interfaces.html.ini
@@ -1,25 +1,28 @@
 [interfaces.html]
   type: testharness
   prefs: [dom.node.rootNode.enabled:true]
   [MutationObserver interface: operation observe(Node,MutationObserverInit)]
     expected: FAIL
+    bug: https://github.com/whatwg/dom/issues/316
 
   [Document interface: attribute origin]
     expected: FAIL
+    bug: 931884
 
   [Document interface: operation query(DOMString)]
     expected: FAIL
 
   [Document interface: operation queryAll(DOMString)]
     expected: FAIL
 
   [Document interface: xmlDoc must inherit property "origin" with the proper type (3)]
     expected: FAIL
+    bug: 931884
 
   [Document interface: xmlDoc must inherit property "query" with the proper type (34)]
     expected: FAIL
 
   [Document interface: calling query(DOMString) on xmlDoc with too few arguments must throw TypeError]
     expected: FAIL
 
   [Document interface: xmlDoc must inherit property "queryAll" with the proper type (35)]
@@ -73,31 +76,33 @@
   [Attr interface: attribute nodeValue]
     expected: FAIL
 
   [Attr interface: attribute textContent]
     expected: FAIL
 
   [NodeFilter interface: existence and properties of interface object]
     expected: FAIL
+    bug: https://github.com/heycam/webidl/issues/96
 
   [Document interface: xmlDoc must inherit property "query" with the proper type (35)]
     expected: FAIL
 
   [Document interface: xmlDoc must inherit property "queryAll" with the proper type (36)]
     expected: FAIL
 
   [Element interface: element must inherit property "query" with the proper type (36)]
     expected: FAIL
 
   [Element interface: element must inherit property "queryAll" with the proper type (37)]
     expected: FAIL
 
   [Document interface: new Document() must inherit property "origin" with the proper type (3)]
     expected: FAIL
+    bug: 931884
 
   [Document interface: new Document() must inherit property "query" with the proper type (35)]
     expected: FAIL
 
   [Document interface: calling query(DOMString) on new Document() with too few arguments must throw TypeError]
     expected: FAIL
 
   [Document interface: new Document() must inherit property "queryAll" with the proper type (36)]
--- a/testing/web-platform/meta/dom/nodes/DOMImplementation-createDocument.html.ini
+++ b/testing/web-platform/meta/dom/nodes/DOMImplementation-createDocument.html.ini
@@ -1,17 +1,22 @@
 [DOMImplementation-createDocument.html]
   type: testharness
-  [createDocument test 6: null,"̀foo",null,"INVALID_CHARACTER_ERR"]
+  [createDocument test 67: "http://example.com/","a:0",null,"NAMESPACE_ERR"]
     expected: FAIL
+    bug: https://github.com/whatwg/dom/issues/319
 
-  [createDocument test 172: metadata for null,null,DocumentType node]
+  [createDocument test 197: metadata for null,null,DocumentType node]
     expected: FAIL
+    bug: 520969
 
-  [createDocument test 172: null,null,DocumentType node,null]
+  [createDocument test 197: null,null,DocumentType node,null]
     expected: FAIL
-
-  [createDocument test 185: null,"",DocumentType node]
-    expected: FAIL
+    bug: 520969
 
-  [createDocument test 186: null,"",DocumentType node]
+  [createDocument test 210: null,"",DocumentType node]
     expected: FAIL
+    bug: 520969
 
+  [createDocument test 211: null,"",DocumentType node]
+    expected: FAIL
+    bug: 520969
+
--- a/testing/web-platform/meta/dom/nodes/Document-createElementNS.html.ini
+++ b/testing/web-platform/meta/dom/nodes/Document-createElementNS.html.ini
@@ -1,5 +1,14 @@
 [Document-createElementNS.html]
   type: testharness
-  [createElementNS test 6: null,"̀foo","INVALID_CHARACTER_ERR"]
+  [createElementNS test 67 in HTML document: "http://example.com/","a:0","NAMESPACE_ERR"]
+    bug: https://github.com/whatwg/dom/issues/319
     expected: FAIL
 
+  [createElementNS test 67 in XML document: "http://example.com/","a:0","NAMESPACE_ERR"]
+    bug: https://github.com/whatwg/dom/issues/319
+    expected: FAIL
+
+  [createElementNS test 67 in XHTML document: "http://example.com/","a:0","NAMESPACE_ERR"]
+    bug: https://github.com/whatwg/dom/issues/319
+    expected: FAIL
+
--- a/testing/web-platform/meta/dom/nodes/Element-classlist.html.ini
+++ b/testing/web-platform/meta/dom/nodes/Element-classlist.html.ini
@@ -1,28 +1,36 @@
 [Element-classlist.html]
   type: testharness
   [classList must be correct for an element that has classes]
     expected: FAIL
+    bug: https://github.com/whatwg/dom/issues/105
+    bug: 869788
 
   [empty classList should return the empty string since the ordered set parser skip the whitespaces]
     expected: FAIL
 
   [classList.remove must collapse whitespaces around each token and remove duplicates]
     expected: FAIL
+    bug: https://github.com/whatwg/dom/issues/105
+    bug: 869788
 
   [classList.add must collapse whitespaces and remove duplicates when adding a token that already exists]
     expected: FAIL
+    bug: https://github.com/whatwg/dom/issues/105
+    bug: 869788
 
   [classList.add should treat \\t as a space]
     expected: FAIL
 
   [classList.add should treat \\r as a space]
     expected: FAIL
 
   [classList.add should treat \\n as a space]
     expected: FAIL
 
   [classList.add should treat \\f as a space]
     expected: FAIL
 
   [classList.replace must collapse whitespaces around each token and remove duplicates]
     expected: FAIL
+    bug: https://github.com/whatwg/dom/issues/105
+    bug: 869788
--- a/testing/web-platform/tests/dom/nodes/Document-createElement.html
+++ b/testing/web-platform/tests/dom/nodes/Document-createElement.html
@@ -4,82 +4,154 @@
 <link rel=help href="https://dom.spec.whatwg.org/#dom-document-createelement">
 <link rel=help href="https://dom.spec.whatwg.org/#dom-element-localname">
 <link rel=help href="https://dom.spec.whatwg.org/#dom-element-tagname">
 <link rel=help href="https://dom.spec.whatwg.org/#dom-element-prefix">
 <link rel=help href="https://dom.spec.whatwg.org/#dom-element-namespaceuri">
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
 <div id="log"></div>
+<iframe src="/dummy.xml"></iframe>
+<iframe src="/dummy.xhtml"></iframe>
 <script>
 function toASCIIUppercase(str) {
   var diff = "a".charCodeAt(0) - "A".charCodeAt(0);
   var res = "";
   for (var i = 0; i < str.length; ++i) {
     if ("a" <= str[i] && str[i] <= "z") {
       res += String.fromCharCode(str.charCodeAt(i) - diff);
     } else {
       res += str[i];
     }
   }
   return res;
 }
-test(function() {
-  var HTMLNS = "http://www.w3.org/1999/xhtml",
-      valid = [
-        //[input, localName],
-        [undefined, "undefined"],
-        [null, "null"],
-        ["foo", "foo"],
-        ["f1oo", "f1oo"],
-        ["foo1", "foo1"],
-        ["f\u0300oo", "f\u0300oo"],
-        ["foo\u0300", "foo\u0300"],
-        [":foo", ":foo"],
-        ["f:oo", "f:oo"],
-        ["foo:", "foo:"],
-        ["xml", "xml"],
-        ["xmlns", "xmlns"],
-        ["xmlfoo", "xmlfoo"],
-        ["xml:foo", "xml:foo"],
-        ["xmlns:foo", "xmlns:foo"],
-        ["xmlfoo:bar", "xmlfoo:bar"],
-        ["svg", "svg"],
-        ["math", "math"],
-        ["FOO", "foo"],
-        ["mar\u212a", "mar\u212a"],
-        ["\u0130nput", "\u0130nput"],
-        ["\u0131nput", "\u0131nput"]
-     ],
-     invalid = [
-       "",
-       "1foo",
-       "\u0300foo",
-       "}foo",
-       "f}oo",
-       "foo}",
-       "\ufffffoo",
-       "f\uffffoo",
-       "foo\uffff",
-       "<foo",
-       "foo>",
-       "<foo>",
-       "f<oo"
-     ]
+function toASCIILowercase(str) {
+  var diff = "a".charCodeAt(0) - "A".charCodeAt(0);
+  var res = "";
+  for (var i = 0; i < str.length; ++i) {
+    if ("A" <= str[i] && str[i] <= "Z") {
+      res += String.fromCharCode(str.charCodeAt(i) + diff);
+    } else {
+      res += str[i];
+    }
+  }
+  return res;
+}
+var HTMLNS = "http://www.w3.org/1999/xhtml",
+    valid = [
+      undefined,
+      null,
+      "foo",
+      "f1oo",
+      "foo1",
+      "f\u0BC6",
+      "foo\u0BC6",
+      ":",
+      ":foo",
+      "f:oo",
+      "foo:",
+      "f:o:o",
+      "f::oo",
+      "f::oo:",
+      "foo:0",
+      "foo:_",
+      // combining char after :, invalid QName but valid Name
+      "foo:\u0BC6",
+      "foo:foo\u0BC6",
+      "foo\u0BC6:foo",
+      "xml",
+      "xmlns",
+      "xmlfoo",
+      "xml:foo",
+      "xmlns:foo",
+      "xmlfoo:bar",
+      "svg",
+      "math",
+      "FOO",
+      // Test that non-ASCII chars don't get uppercased/lowercased
+      "mar\u212a",
+      "\u0130nput",
+      "\u0131nput",
+    ],
+    invalid = [
+      "",
+      "1foo",
+      "1:foo",
+      "fo o",
+      "\u0BC6foo",
+      "}foo",
+      "f}oo",
+      "foo}",
+      "\ufffffoo",
+      "f\uffffoo",
+      "foo\uffff",
+      "<foo",
+      "foo>",
+      "<foo>",
+      "f<oo",
+      "-foo",
+      ".foo",
+      "\u0BC6",
+    ]
 
-  valid.forEach(function(t) {
-    test(function() {
-      var elt = document.createElement(t[0])
-      assert_true(elt instanceof Element)
-      assert_true(elt instanceof Node)
-      assert_equals(elt.localName, t[1])
-      assert_equals(elt.tagName, toASCIIUppercase(t[1]))
-      assert_equals(elt.prefix, null)
-      assert_equals(elt.namespaceURI, HTMLNS)
-    }, "createElement(" + format_value(t[0]) + ")");
+var xmlIframe = document.querySelector('[src="/dummy.xml"]');
+var xhtmlIframe = document.querySelector('[src="/dummy.xhtml"]');
+
+function getWin(desc) {
+  if (desc == "HTML document") {
+    return window;
+  }
+  if (desc == "XML document") {
+    assert_equals(xmlIframe.contentDocument.documentElement.textContent,
+                  "Dummy XML document", "XML document didn't load");
+    return xmlIframe.contentWindow;
+  }
+  if (desc == "XHTML document") {
+    assert_equals(xhtmlIframe.contentDocument.documentElement.textContent,
+                  "Dummy XHTML document", "XHTML document didn't load");
+    return xhtmlIframe.contentWindow;
+  }
+}
+
+
+valid.forEach(function(t) {
+  ["HTML document", "XML document", "XHTML document"].forEach(function(desc) {
+    async_test(function(testObj) {
+      window.addEventListener("load", function() {
+        testObj.step(function() {
+          var win = getWin(desc);
+          var doc = win.document;
+          var elt = doc.createElement(t)
+          assert_true(elt instanceof win.Element, "instanceof Element")
+          assert_true(elt instanceof win.Node, "instanceof Node")
+          assert_equals(elt.localName,
+                        desc == "HTML document" ? toASCIILowercase(String(t))
+                                                : String(t),
+                        "localName")
+          assert_equals(elt.tagName,
+                        desc == "HTML document" ? toASCIIUppercase(String(t))
+                                                : String(t),
+                        "tagName")
+          assert_equals(elt.prefix, null, "prefix")
+          assert_equals(elt.namespaceURI,
+                        desc == "XML document" ? null : HTMLNS, "namespaceURI")
+        });
+        testObj.done();
+      });
+    }, "createElement(" + format_value(t) + ") in " + desc);
   });
-  invalid.forEach(function(arg) {
-    test(function() {
-      assert_throws("INVALID_CHARACTER_ERR", function() { document.createElement(arg) })
-    }, "createElement(" + format_value(arg) + ")");
+});
+invalid.forEach(function(arg) {
+  ["HTML document", "XML document", "XHTML document"].forEach(function(desc) {
+    async_test(function(testObj) {
+      window.addEventListener("load", function() {
+        testObj.step(function() {
+          var doc = getWin(desc).document;
+          assert_throws("InvalidCharacterError",
+                        function() { doc.createElement(arg) })
+        });
+        testObj.done();
+      });
+    }, "createElement(" + format_value(arg) + ") in " + desc);
   });
-})
+});
 </script>
--- a/testing/web-platform/tests/dom/nodes/Document-createElementNS.html
+++ b/testing/web-platform/tests/dom/nodes/Document-createElementNS.html
@@ -1,70 +1,98 @@
 <!DOCTYPE html>
 <meta charset=utf-8>
 <title>Document.createElementNS</title>
 <link rel=help href="https://dom.spec.whatwg.org/#dom-document-createelementns">
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
 <script src="Document-createElementNS.js"></script>
 <div id="log"></div>
+<iframe src="/dummy.xml"></iframe>
+<iframe src="/dummy.xhtml"></iframe>
 <script>
-test(function() {
-  var tests = createElementNS_tests.concat([
-    /* Arrays with three elements:
-     *   the namespace argument
-     *   the qualifiedName argument
-     *   the expected exception, or null if none
-     */
-    ["", "", "INVALID_CHARACTER_ERR"],
-    [null, null, null],
-    [null, "", "INVALID_CHARACTER_ERR"],
-    [undefined, null, null],
-    [undefined, undefined, null],
-    [undefined, "", "INVALID_CHARACTER_ERR"],
-    ["http://example.com/", null, null],
-    ["http://example.com/", "", "INVALID_CHARACTER_ERR"],
-    ["/", null, null],
-    ["/", "", "INVALID_CHARACTER_ERR"],
-    ["http://www.w3.org/XML/1998/namespace", null, null],
-    ["http://www.w3.org/XML/1998/namespace", "", "INVALID_CHARACTER_ERR"],
-    ["http://www.w3.org/2000/xmlns/", null, "NAMESPACE_ERR"],
-    ["http://www.w3.org/2000/xmlns/", "", "INVALID_CHARACTER_ERR"],
-    ["foo:", null, null],
-    ["foo:", "", "INVALID_CHARACTER_ERR"],
-  ])
+var tests = createElementNS_tests.concat([
+  /* Arrays with three elements:
+   *   the namespace argument
+   *   the qualifiedName argument
+   *   the expected exception, or null if none
+   */
+  ["", "", "INVALID_CHARACTER_ERR"],
+  [null, null, null],
+  [null, "", "INVALID_CHARACTER_ERR"],
+  [undefined, null, null],
+  [undefined, undefined, null],
+  [undefined, "", "INVALID_CHARACTER_ERR"],
+  ["http://example.com/", null, null],
+  ["http://example.com/", "", "INVALID_CHARACTER_ERR"],
+  ["/", null, null],
+  ["/", "", "INVALID_CHARACTER_ERR"],
+  ["http://www.w3.org/XML/1998/namespace", null, null],
+  ["http://www.w3.org/XML/1998/namespace", "", "INVALID_CHARACTER_ERR"],
+  ["http://www.w3.org/2000/xmlns/", null, "NAMESPACE_ERR"],
+  ["http://www.w3.org/2000/xmlns/", "", "INVALID_CHARACTER_ERR"],
+  ["foo:", null, null],
+  ["foo:", "", "INVALID_CHARACTER_ERR"],
+])
+
+var xmlIframe = document.querySelector('[src="/dummy.xml"]');
+var xhtmlIframe = document.querySelector('[src="/dummy.xhtml"]');
 
-  tests.forEach(function(t, i) {
-    test(function() {
-      var namespace = t[0], qualifiedName = t[1], expected = t[2]
-      if (expected != null) {
-        assert_throws(expected, function() { document.createElementNS(namespace, qualifiedName) })
-      } else {
-        var element = document.createElementNS(namespace, qualifiedName)
-        assert_not_equals(element, null)
-        assert_equals(element.nodeType, Node.ELEMENT_NODE)
-        assert_equals(element.nodeType, element.ELEMENT_NODE)
-        assert_equals(element.nodeValue, null)
-        assert_equals(element.ownerDocument, document)
-        var qualified = String(qualifiedName), names = []
-        if (qualified.indexOf(":") >= 0) {
-          names = qualified.split(":", 2)
+function runTest(t, i, desc) {
+  async_test(function(testObj) {
+    window.addEventListener("load", function() {
+      testObj.step(function() {
+        var doc;
+        if (desc == "HTML document") {
+          doc = document;
+        } else if (desc == "XML document") {
+          doc = xmlIframe.contentDocument;
+          // Make sure we're testing the right document
+          assert_equals(doc.documentElement.textContent, "Dummy XML document");
+        } else if (desc == "XHTML document") {
+          doc = xhtmlIframe.contentDocument;
+          assert_equals(doc.documentElement.textContent, "Dummy XHTML document");
+        }
+        var namespace = t[0], qualifiedName = t[1], expected = t[2]
+        if (expected != null) {
+          assert_throws(expected, function() { doc.createElementNS(namespace, qualifiedName) })
         } else {
-          names = [null, qualified]
+          var element = doc.createElementNS(namespace, qualifiedName)
+          assert_not_equals(element, null)
+          assert_equals(element.nodeType, Node.ELEMENT_NODE)
+          assert_equals(element.nodeType, element.ELEMENT_NODE)
+          assert_equals(element.nodeValue, null)
+          assert_equals(element.ownerDocument, doc)
+          var qualified = String(qualifiedName), names = []
+          if (qualified.indexOf(":") >= 0) {
+            names = qualified.split(":", 2)
+          } else {
+            names = [null, qualified]
+          }
+          assert_equals(element.prefix, names[0])
+          assert_equals(element.localName, names[1])
+          assert_equals(element.tagName, qualified)
+          assert_equals(element.nodeName, qualified)
+          assert_equals(element.namespaceURI,
+                        namespace === undefined || namespace === "" ? null
+                                                                    : namespace)
         }
-        assert_equals(element.prefix, names[0])
-        assert_equals(element.localName, names[1])
-        assert_equals(element.tagName, qualified)
-        assert_equals(element.nodeName, qualified)
-        assert_equals(element.namespaceURI, namespace === undefined ? null : namespace)
-      }
-    }, "createElementNS test " + i + ": " + t.map(function(el) { return format_value(el) }))
-  })
+      });
+      testObj.done();
+    });
+  }, "createElementNS test " + i + " in " + desc + ": " + t.map(format_value))
+}
+
+tests.forEach(function(t, i) {
+  runTest(t, i, "HTML document")
+  runTest(t, i, "XML document")
+  runTest(t, i, "XHTML document")
 })
 
+
 test(function() {
   var HTMLNS = "http://www.w3.org/1999/xhtml";
   var element = document.createElementNS(HTMLNS, "span");
   assert_equals(element.namespaceURI, HTMLNS);
   assert_equals(element.prefix, null);
   assert_equals(element.localName, "span");
   assert_equals(element.tagName, "SPAN");
   assert_true(element instanceof Node, "Should be a Node");
--- a/testing/web-platform/tests/dom/nodes/Document-createElementNS.js
+++ b/testing/web-platform/tests/dom/nodes/Document-createElementNS.js
@@ -1,67 +1,92 @@
 var createElementNS_tests = [
   /* Arrays with three elements:
    *   the namespace argument
    *   the qualifiedName argument
    *   the expected exception, or null if none
    */
+  [null, null, null],
   [null, undefined, null],
   [null, "foo", null],
   [null, "1foo", "INVALID_CHARACTER_ERR"],
   [null, "f1oo", null],
   [null, "foo1", null],
   [null, "1foo", "INVALID_CHARACTER_ERR"],
-  [null, "\u0300foo", "INVALID_CHARACTER_ERR"],
+  [null, "\u0BC6foo", "INVALID_CHARACTER_ERR"],
   [null, "}foo", "INVALID_CHARACTER_ERR"],
   [null, "f}oo", "INVALID_CHARACTER_ERR"],
   [null, "foo}", "INVALID_CHARACTER_ERR"],
   [null, "\uFFFFfoo", "INVALID_CHARACTER_ERR"],
   [null, "f\uFFFFoo", "INVALID_CHARACTER_ERR"],
   [null, "foo\uFFFF", "INVALID_CHARACTER_ERR"],
   [null, "<foo", "INVALID_CHARACTER_ERR"],
   [null, "foo>", "INVALID_CHARACTER_ERR"],
   [null, "<foo>", "INVALID_CHARACTER_ERR"],
   [null, "f<oo", "INVALID_CHARACTER_ERR"],
   [null, "^^", "INVALID_CHARACTER_ERR"],
+  [null, "fo o", "INVALID_CHARACTER_ERR"],
+  [null, "-foo", "INVALID_CHARACTER_ERR"],
+  [null, ".foo", "INVALID_CHARACTER_ERR"],
   [null, ":foo", "NAMESPACE_ERR"],
   [null, "f:oo", "NAMESPACE_ERR"],
   [null, "foo:", "NAMESPACE_ERR"],
+  [null, "f:o:o", "NAMESPACE_ERR"],
   [null, ":", "NAMESPACE_ERR"],
   [null, "xml", null],
   [null, "xmlns", "NAMESPACE_ERR"],
   [null, "xmlfoo", null],
   [null, "xml:foo", "NAMESPACE_ERR"],
   [null, "xmlns:foo", "NAMESPACE_ERR"],
   [null, "xmlfoo:bar", "NAMESPACE_ERR"],
   [null, "null:xml", "NAMESPACE_ERR"],
+  ["", null, null],
   ["", ":foo", "NAMESPACE_ERR"],
   ["", "f:oo", "NAMESPACE_ERR"],
   ["", "foo:", "NAMESPACE_ERR"],
+  [undefined, null, null],
   [undefined, undefined, null],
   [undefined, "foo", null],
   [undefined, "1foo", "INVALID_CHARACTER_ERR"],
   [undefined, "f1oo", null],
   [undefined, "foo1", null],
   [undefined, ":foo", "NAMESPACE_ERR"],
   [undefined, "f:oo", "NAMESPACE_ERR"],
   [undefined, "foo:", "NAMESPACE_ERR"],
+  [undefined, "f::oo", "NAMESPACE_ERR"],
   [undefined, "xml", null],
   [undefined, "xmlns", "NAMESPACE_ERR"],
   [undefined, "xmlfoo", null],
   [undefined, "xml:foo", "NAMESPACE_ERR"],
   [undefined, "xmlns:foo", "NAMESPACE_ERR"],
   [undefined, "xmlfoo:bar", "NAMESPACE_ERR"],
   ["http://example.com/", "foo", null],
   ["http://example.com/", "1foo", "INVALID_CHARACTER_ERR"],
+  ["http://example.com/", "<foo>", "INVALID_CHARACTER_ERR"],
+  ["http://example.com/", "fo<o", "INVALID_CHARACTER_ERR"],
+  ["http://example.com/", "-foo", "INVALID_CHARACTER_ERR"],
+  ["http://example.com/", ".foo", "INVALID_CHARACTER_ERR"],
   ["http://example.com/", "f1oo", null],
   ["http://example.com/", "foo1", null],
   ["http://example.com/", ":foo", "NAMESPACE_ERR"],
   ["http://example.com/", "f:oo", null],
+  ["http://example.com/", "f:o:o", "NAMESPACE_ERR"],
   ["http://example.com/", "foo:", "NAMESPACE_ERR"],
+  ["http://example.com/", "f::oo", "NAMESPACE_ERR"],
+  ["http://example.com/", "a:0", "NAMESPACE_ERR"],
+  ["http://example.com/", "0:a", "INVALID_CHARACTER_ERR"],
+  ["http://example.com/", "a:_", null],
+  ["http://example.com/", "a:\u0BC6", "NAMESPACE_ERR"],
+  ["http://example.com/", "\u0BC6:a", "INVALID_CHARACTER_ERR"],
+  ["http://example.com/", "a:a\u0BC6", null],
+  ["http://example.com/", "a\u0BC6:a", null],
+  ["http://example.com/", "xml:test", "NAMESPACE_ERR"],
+  ["http://example.com/", "xmlns:test", "NAMESPACE_ERR"],
+  ["http://example.com/", "test:xmlns", null],
+  ["http://example.com/", "xmlns", "NAMESPACE_ERR"],
   ["http://example.com/", "_:_", null],
   ["http://example.com/", "_:h0", null],
   ["http://example.com/", "_:test", null],
   ["http://example.com/", "l_:_", null],
   ["http://example.com/", "ns:_0", null],
   ["http://example.com/", "ns:a0", null],
   ["http://example.com/", "ns0:test", null],
   ["http://example.com/", "a.b:c", null],
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/dummy.xhtml
@@ -0,0 +1,2 @@
+<!DOCTYPE html>
+<html><head><title>Dummy XHTML document</title></head><body /></html>
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/dummy.xml
@@ -0,0 +1,1 @@
+<foo>Dummy XML document</foo>
--- a/toolkit/components/places/Bookmarks.jsm
+++ b/toolkit/components/places/Bookmarks.jsm
@@ -1178,24 +1178,25 @@ function reorderChildren(parent, ordered
       // when no more existing GUIDs have been provided.
       let valuesTable = children.map((child, i) => `("${child.guid}", ${i})`)
                                 .join();
       yield db.execute(
         `WITH sorting(g, p) AS (
            VALUES ${valuesTable}
          )
          UPDATE moz_bookmarks SET position = (
-           SELECT CASE count(a.g) WHEN 0 THEN -position
-                                  ELSE count(a.g) - 1
+           SELECT CASE count(*) WHEN 0 THEN -position
+                                       ELSE count(*) - 1
                   END
            FROM sorting a
            JOIN sorting b ON b.p <= a.p
            WHERE a.g = guid
-             AND parent = :parentId
-        )`, { parentId: parent._id});
+         )
+         WHERE parent = :parentId
+        `, { parentId: parent._id});
 
       // Update position of items that could have been inserted in the meanwhile.
       // Since this can happen rarely and it's only done for schema coherence
       // resonds, we won't notify about these changes.
       yield db.executeCached(
         `CREATE TEMP TRIGGER moz_bookmarks_reorder_trigger
            AFTER UPDATE OF position ON moz_bookmarks
            WHEN NEW.position = -1
--- a/toolkit/components/places/PlacesSyncUtils.jsm
+++ b/toolkit/components/places/PlacesSyncUtils.jsm
@@ -96,75 +96,44 @@ const BookmarkSyncUtils = PlacesSyncUtil
 
     if (parentGuid == PlacesUtils.bookmarks.rootGuid) {
       // Reordering roots doesn't make sense, but Sync will do this on the
       // first sync.
       return Promise.resolve();
     }
     return PlacesUtils.withConnectionWrapper("BookmarkSyncUtils: order",
       Task.async(function* (db) {
-        let children;
-
-        yield db.executeTransaction(function* () {
-          children = yield fetchAllChildren(db, parentGuid);
-          if (!children.length) {
-            return;
-          }
-          for (let child of children) {
-            // Note the current index for notifying observers. This can
-            // be removed once we switch to `reorder`.
-            child.oldIndex = child.index;
-          }
-
-          // Reorder the list, ignoring missing children.
-          let delta = 0;
-          for (let i = 0; i < childGuids.length; ++i) {
-            let guid = childGuids[i];
-            let child = findChildByGuid(children, guid);
-            if (!child) {
-              delta++;
-              BookmarkSyncLog.trace(`order: Ignoring missing child ${guid}`);
-              continue;
-            }
-            let newIndex = i - delta;
-            updateChildIndex(children, child, newIndex);
-          }
-          children.sort((a, b) => a.index - b.index);
+        let children = yield fetchAllChildren(db, parentGuid);
+        if (!children.length) {
+          return;
+        }
+        for (let child of children) {
+          // Note the current index for notifying observers. This can
+          // be removed once we switch to `reorder`.
+          child.oldIndex = child.index;
+        }
 
-          // Update positions. We use a custom query instead of
-          // `PlacesUtils.bookmarks.reorder` because `reorder` introduces holes
-          // (bug 1293365). Once it's fixed, we can uncomment this code and
-          // remove the transaction, query, and observer notification code.
-
-          /*
-          let orderedChildrenGuids = children.map(({ guid }) => guid);
-          yield PlacesUtils.bookmarks.reorder(parentGuid, orderedChildrenGuids);
-          */
+        // Reorder the list, ignoring missing children.
+        let delta = 0;
+        for (let i = 0; i < childGuids.length; ++i) {
+          let guid = childGuids[i];
+          let child = findChildByGuid(children, guid);
+          if (!child) {
+            delta++;
+            BookmarkSyncLog.trace(`order: Ignoring missing child ${guid}`);
+            continue;
+          }
+          let newIndex = i - delta;
+          updateChildIndex(children, child, newIndex);
+        }
+        children.sort((a, b) => a.index - b.index);
 
-          yield db.executeCached(`WITH sorting(g, p) AS (
-            VALUES ${children.map(
-              (child, i) => `("${child.guid}", ${i})`
-            ).join()}
-          ) UPDATE moz_bookmarks SET position = (
-            SELECT p FROM sorting WHERE g = guid
-          ) WHERE parent = (
-            SELECT id FROM moz_bookmarks WHERE guid = :parentGuid
-          )`,
-          { parentGuid });
-        });
-
-        // Notify observers.
-        let observers = PlacesUtils.bookmarks.getObservers();
-        for (let child of children) {
-          notify(observers, "onItemMoved", [ child.id, child.parentId,
-                                             child.oldIndex, child.parentId,
-                                             child.index, child.type,
-                                             child.guid, parentGuid,
-                                             parentGuid, SOURCE_SYNC ]);
-        }
+        // Update positions.
+        let orderedChildrenGuids = children.map(({ guid }) => guid);
+        yield PlacesUtils.bookmarks.reorder(parentGuid, orderedChildrenGuids);
       })
     );
   }),
 
   /**
    * Removes an item from the database.
    */
   remove: Task.async(function* (guid) {
--- a/toolkit/components/places/tests/bookmarks/test_bookmarks_reorder.js
+++ b/toolkit/components/places/tests/bookmarks/test_bookmarks_reorder.js
@@ -37,32 +37,29 @@ add_task(function* invalid_input_throws(
 add_task(function* reorder_nonexistent_guid() {
   yield Assert.rejects(PlacesUtils.bookmarks.reorder("123456789012", [ "012345678901" ]),
                        /No folder found for the provided GUID/,
                        "Should throw for nonexisting guid");
 });
 
 add_task(function* reorder() {
   let bookmarks = [
-    { type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
-      url: "http://example1.com/",
+    { url: "http://example1.com/",
       parentGuid: PlacesUtils.bookmarks.unfiledGuid
     },
     { type: PlacesUtils.bookmarks.TYPE_FOLDER,
       parentGuid: PlacesUtils.bookmarks.unfiledGuid
     },
     { type: PlacesUtils.bookmarks.TYPE_SEPARATOR,
       parentGuid: PlacesUtils.bookmarks.unfiledGuid
     },
-    { type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
-      url: "http://example2.com/",
+    { url: "http://example2.com/",
       parentGuid: PlacesUtils.bookmarks.unfiledGuid
     },
-    { type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
-      url: "http://example3.com/",
+    { url: "http://example3.com/",
       parentGuid: PlacesUtils.bookmarks.unfiledGuid
     }
   ];
 
   let sorted = [];
   for (let bm of bookmarks) {
     sorted.push(yield PlacesUtils.bookmarks.insert(bm));
   }
@@ -104,11 +101,77 @@ add_task(function* reorder() {
   let rows = yield db.execute(
     `SELECT parent
      FROM moz_bookmarks
      GROUP BY parent
      HAVING (SUM(DISTINCT position + 1) - (count(*) * (count(*) + 1) / 2)) <> 0`);
   Assert.equal(rows.length, 0, "All the bookmarks should have consistent positions");
 });
 
-function run_test() {
-  run_next_test();
-}
+add_task(function* move_and_reorder() {
+  // Start clean.
+  yield PlacesUtils.bookmarks.eraseEverything();
+
+  let bm1 = yield PlacesUtils.bookmarks.insert({
+    url: "http://example1.com/",
+    parentGuid: PlacesUtils.bookmarks.unfiledGuid
+  });
+  let f1 = yield PlacesUtils.bookmarks.insert({
+    type: PlacesUtils.bookmarks.TYPE_FOLDER,
+    parentGuid: PlacesUtils.bookmarks.unfiledGuid
+  });
+  let bm2 = yield PlacesUtils.bookmarks.insert({
+    url: "http://example2.com/",
+    parentGuid: f1.guid
+  });
+  let f2 = yield PlacesUtils.bookmarks.insert({
+    type: PlacesUtils.bookmarks.TYPE_FOLDER,
+    parentGuid: PlacesUtils.bookmarks.unfiledGuid
+  });
+  let bm3 = yield PlacesUtils.bookmarks.insert({
+    url: "http://example3.com/",
+    parentGuid: f2.guid
+  });
+  let bm4 = yield PlacesUtils.bookmarks.insert({
+    url: "http://example4.com/",
+    parentGuid: f2.guid
+  });
+  let bm5 = yield PlacesUtils.bookmarks.insert({
+    url: "http://example5.com/",
+    parentGuid: f2.guid
+  });
+
+  // Invert f2 children.
+  // This is critical to reproduce the bug, cause it inverts the position
+  // compared to the natural insertion order.
+  yield PlacesUtils.bookmarks.reorder(f2.guid, [bm5.guid, bm4.guid, bm3.guid]);
+
+  bm1.parentGuid = f1.guid;
+  bm1.index = 0;
+  yield PlacesUtils.bookmarks.update(bm1);
+
+  bm1 = yield PlacesUtils.bookmarks.fetch(bm1.guid);
+  Assert.equal(bm1.index, 0);
+  bm2 = yield PlacesUtils.bookmarks.fetch(bm2.guid);
+  Assert.equal(bm2.index, 1);
+  bm3 = yield PlacesUtils.bookmarks.fetch(bm3.guid);
+  Assert.equal(bm3.index, 2);
+  bm4 = yield PlacesUtils.bookmarks.fetch(bm4.guid);
+  Assert.equal(bm4.index, 1);
+  bm5 = yield PlacesUtils.bookmarks.fetch(bm5.guid);
+  Assert.equal(bm5.index, 0);
+
+  // No-op reorder on f1 children.
+  // Nothing should change. Though, due to bug 1293365 this was causing children
+  // of other folders to get messed up.
+  yield PlacesUtils.bookmarks.reorder(f1.guid, [bm1.guid, bm2.guid]);
+
+  bm1 = yield PlacesUtils.bookmarks.fetch(bm1.guid);
+  Assert.equal(bm1.index, 0);
+  bm2 = yield PlacesUtils.bookmarks.fetch(bm2.guid);
+  Assert.equal(bm2.index, 1);
+  bm3 = yield PlacesUtils.bookmarks.fetch(bm3.guid);
+  Assert.equal(bm3.index, 2);
+  bm4 = yield PlacesUtils.bookmarks.fetch(bm4.guid);
+  Assert.equal(bm4.index, 1);
+  bm5 = yield PlacesUtils.bookmarks.fetch(bm5.guid);
+  Assert.equal(bm5.index, 0);
+});
--- a/toolkit/moz.configure
+++ b/toolkit/moz.configure
@@ -707,17 +707,17 @@ add_old_configure_assignment('NECKO_PROT
 
 # Graphics
 # ==============================================================
 option('--disable-skia', help='Disable use of Skia')
 
 @depends('--disable-skia', target)
 def skia(value, target):
     if value.origin == 'default' and target.endianness == 'big':
-        return False
+        return None
     if value:
         return True
 
 set_config('MOZ_ENABLE_SKIA', skia)
 set_define('MOZ_ENABLE_SKIA', skia)
 set_define('USE_SKIA', skia)
 
 @depends(skia, target)
@@ -728,20 +728,20 @@ def skia_android(skia, target):
 set_define('SK_BUILD_FOR_ANDROID_NDK', skia_android)
 
 option('--disable-skia-gpu', help='Disable use of Skia-GPU')
 
 @depends('--disable-skia-gpu', skia, target)
 def skia_gpu(value, skia, target):
     if value.origin == 'default':
         if not skia:
-            return False
+            return None
         # Skia GPU support may not reliably build on certain *BSDs (see bug 1234494)
         if target.os in ('NetBSD', 'OpenBSD'):
-            return False
+            return None
     elif value and not skia:
         die('Cannot enable Skia-GPU without enabling Skia')
     if skia and value:
         return True
 
 set_config('MOZ_ENABLE_SKIA_GPU', skia_gpu)
 set_define('USE_SKIA_GPU', skia_gpu)
 
--- a/toolkit/mozapps/extensions/internal/XPIProvider.jsm
+++ b/toolkit/mozapps/extensions/internal/XPIProvider.jsm
@@ -924,16 +924,21 @@ var loadManifestFromWebManifest = Task.a
     throw new Error("Extension is invalid");
 
   let bss = (manifest.browser_specific_settings && manifest.browser_specific_settings.gecko)
       || (manifest.applications && manifest.applications.gecko) || {};
   if (manifest.browser_specific_settings && manifest.applications) {
     logger.warn("Ignoring applications property in manifest");
   }
 
+  // A * is illegal in strict_min_version
+  if (bss.strict_min_version && bss.strict_min_version.split(".").some(part => part == "*")) {
+    logger.warn("The use of '*' in strict_min_version is deprecated");
+  }
+
   let addon = new AddonInternal();
   addon.id = bss.id;
   addon.version = manifest.version;
   addon.type = "webextension";
   addon.unpack = false;
   addon.strictCompatibility = true;
   addon.bootstrap = true;
   addon.hasBinaryComponents = false;
--- a/toolkit/mozapps/extensions/test/xpcshell/test_webextension_install.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_webextension_install.js
@@ -358,9 +358,40 @@ add_task(function* test_strict_min_max()
   yield AddonManager.installTemporaryAddon(addonDir);
   addon = yield promiseAddonByID(newId);
 
   do_check_neq(addon, null);
   do_check_eq(addon.id, newId);
 
   addon.uninstall();
   flushAndRemove(addonDir);
+
+  // * in min will generate a warning
+  for (let version of ["0.*", "0.*.0"]) {
+    newId = "strict_min_star@tests.mozilla.org";
+    let apps = {
+      applications: {
+        gecko: {
+          id: newId,
+          strict_min_version: version,
+        },
+      },
+    }
+    let testManifest = Object.assign(apps, MANIFEST);
+
+    let addonDir = yield promiseWriteWebManifestForExtension(testManifest, gTmpD,
+                                            "strict_min_star");
+
+    let { messages } = yield promiseConsoleOutput(function* () {
+      yield AddonManager.installTemporaryAddon(addonDir);
+    });
+    ok(messages.some(msg => msg.message.includes("The use of '*' in strict_min_version is deprecated")),
+       "Deprecation warning for strict_min_version with '*' was generated");
+
+    let addon = yield promiseAddonByID(newId);
+
+    notEqual(addon, null, "Add-on is installed");
+    equal(addon.id, newId, "Add-on has the expected id");
+
+    addon.uninstall();
+    flushAndRemove(addonDir);
+  }
 });
--- a/tools/lint/eslint.lint
+++ b/tools/lint/eslint.lint
@@ -304,18 +304,23 @@ def lint(paths, binary=None, fix=None, s
                 '--ext', '[{}]'.format(','.join(EXTENSIONS)),
                 '--format', 'json',
                 ] + extra_args + paths
 
     # eslint requires that --fix be set before the --ext argument.
     if fix:
         cmd_args.insert(1, '--fix')
 
+    shell = False
+    if os.environ.get('MSYSTEM') in ('MINGW32', 'MINGW64'):
+        # The eslint binary needs to be run from a shell with msys
+        shell = True
+
     orig = signal.signal(signal.SIGINT, signal.SIG_IGN)
-    proc = ProcessHandler(cmd_args, env=os.environ, stream=None)
+    proc = ProcessHandler(cmd_args, env=os.environ, stream=None, shell=shell)
     proc.run()
     signal.signal(signal.SIGINT, orig)
 
     try:
         proc.wait()
     except KeyboardInterrupt:
         proc.kill()
         return []