merge mozilla-inbound to mozilla-central a=merge
authorCarsten "Tomcat" Book <cbook@mozilla.com>
Wed, 27 Jan 2016 11:59:49 +0100
changeset 318172 211a4c710fb6af2cad10102c4cabc7cb525998b8
parent 317974 c8f9b2cd16885eb5db980b907fdfbd6807f19f2a (current diff)
parent 318171 ae1d72ffd535a25dd29454d4503ccfef80cb78fb (diff)
child 318177 60c5168a485cb166abd27b8eb0b83b919ac4b166
child 318249 1b65eca00113ce229887a819c8d9797eb73dad63
push id5913
push userjlund@mozilla.com
push dateMon, 25 Apr 2016 16:57:49 +0000
treeherdermozilla-beta@dcaf0a6fa115 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone47.0a1
first release with
nightly linux32
211a4c710fb6 / 47.0a1 / 20160127030236 / files
nightly linux64
211a4c710fb6 / 47.0a1 / 20160127030236 / files
nightly mac
211a4c710fb6 / 47.0a1 / 20160127030236 / files
nightly win32
211a4c710fb6 / 47.0a1 / 20160127030236 / files
nightly win64
211a4c710fb6 / 47.0a1 / 20160127030236 / files
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
releases
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
merge mozilla-inbound to mozilla-central a=merge
browser/app/profile/firefox.js
js/src/frontend/BytecodeEmitter.cpp
js/src/frontend/Parser.cpp
js/src/tests/ecma_6/extensions/ArrayBuffer-slice-arguments-neutering.js
js/src/tests/ecma_6/extensions/DataView-construct-arguments-neutering.js
js/src/tests/ecma_6/extensions/DataView-set-arguments-neutering.js
js/src/tests/ecma_6/extensions/TypedArray-set-object-funky-length-neuters.js
js/src/tests/ecma_6/extensions/TypedArray-subarray-arguments-neutering.js
js/src/tests/ecma_6/extensions/element-setting-ToNumber-neuters.js
js/src/tests/js1_8_5/extensions/typedarray-copyWithin-arguments-neutering.js
mobile/android/app/mobile.js
testing/web-platform/meta/selectors/attribute-selectors/attribute-case/semantics.html.ini
testing/web-platform/meta/selectors/attribute-selectors/attribute-case/syntax.html.ini
toolkit/crashreporter/Makefile.in
toolkit/crashreporter/breakpad-logging/moz.build
toolkit/crashreporter/breakpad-patches/00-module-api-extras.patch
toolkit/crashreporter/breakpad-patches/02-cfi-rule-repr.patch
toolkit/crashreporter/breakpad-patches/03-unique-string.patch
toolkit/crashreporter/breakpad-patches/04-uniquestringmap.patch
toolkit/crashreporter/breakpad-patches/08-dwarf2reader-dynamic-cast.patch
toolkit/crashreporter/breakpad-patches/10-logging.patch
toolkit/crashreporter/breakpad-patches/11-readsymboldatainternal-proto.patch
toolkit/crashreporter/breakpad-patches/16-sht-arm-exidx-define.patch
toolkit/crashreporter/google-breakpad/README
toolkit/crashreporter/google-breakpad/SVN-INFO
toolkit/crashreporter/google-breakpad/src/client/linux/crash_generation/moz.build
toolkit/crashreporter/google-breakpad/src/client/linux/handler/Makefile.in
toolkit/crashreporter/google-breakpad/src/client/linux/handler/moz.build
toolkit/crashreporter/google-breakpad/src/client/linux/minidump_writer/moz.build
toolkit/crashreporter/google-breakpad/src/client/mac/tests/SimpleStringDictionaryTest.h
toolkit/crashreporter/google-breakpad/src/client/windows/build/common.gypi
toolkit/crashreporter/google-breakpad/src/client/windows/build/external_code.gypi
toolkit/crashreporter/google-breakpad/src/client/windows/build/internal/release_defaults.gypi
toolkit/crashreporter/google-breakpad/src/common/logging.cc
toolkit/crashreporter/google-breakpad/src/common/logging.h
toolkit/crashreporter/google-breakpad/src/common/mac/SimpleStringDictionary.h
toolkit/crashreporter/google-breakpad/src/common/mac/SimpleStringDictionary.mm
toolkit/crashreporter/google-breakpad/src/common/pathname_stripper.cc
toolkit/crashreporter/google-breakpad/src/common/pathname_stripper.h
toolkit/crashreporter/google-breakpad/src/common/pathname_stripper_unittest.cc
toolkit/crashreporter/google-breakpad/src/common/unique_string.cc
toolkit/crashreporter/google-breakpad/src/common/unique_string.h
toolkit/crashreporter/google-breakpad/src/processor/binarystream.cc
toolkit/crashreporter/google-breakpad/src/processor/binarystream.h
toolkit/crashreporter/google-breakpad/src/processor/binarystream_unittest.cc
toolkit/crashreporter/google-breakpad/src/processor/testdata/linux_test_app.cc
toolkit/crashreporter/google-breakpad/src/processor/testdata/minidump2.dmp
toolkit/crashreporter/google-breakpad/src/processor/testdata/minidump2.dump.out
toolkit/crashreporter/google-breakpad/src/processor/testdata/minidump2.stackwalk.machine_readable.out
toolkit/crashreporter/google-breakpad/src/processor/testdata/minidump2.stackwalk.out
toolkit/crashreporter/google-breakpad/src/processor/testdata/module1.out
toolkit/crashreporter/google-breakpad/src/processor/testdata/module2.out
toolkit/crashreporter/google-breakpad/src/processor/testdata/module3_bad.out
toolkit/crashreporter/google-breakpad/src/processor/testdata/symbols/kernel32.pdb/BCE8785C57B44245A669896B6A19B9542/kernel32.sym
toolkit/crashreporter/google-breakpad/src/processor/testdata/symbols/test_app.pdb/5A9832E5287241C1838ED98914E9B7FF1/test_app.sym
toolkit/crashreporter/google-breakpad/src/processor/testdata/test_app.cc
toolkit/crashreporter/google-breakpad/src/third_party/glog/src/glog/log_severity.h
toolkit/crashreporter/google-breakpad/src/tools/windows/dump_syms/testdata/dump_syms_regtest.pdb
toolkit/crashreporter/google-breakpad/src/tools/windows/symupload/symupload.vcproj
tools/profiler/core/shim_mac_dump_syms.h
tools/profiler/core/shim_mac_dump_syms.mm
tools/profiler/gecko/local_debug_info_symbolizer.cc
tools/profiler/gecko/local_debug_info_symbolizer.h
--- a/CLOBBER
+++ b/CLOBBER
@@ -17,11 +17,10 @@
 #
 # Modifying this file will now automatically clobber the buildbot machines \o/
 #
 
 # Are you updating CLOBBER because you think it's needed for your WebIDL
 # changes to stick? As of bug 928195, this shouldn't be necessary! Please
 # don't change CLOBBER for WebIDL changes any more.
 
-Bug 1241975 - port B2G branding Makefiles to moz.build;
-Bug 1241974 - remove testing/mochitest/manifests/Makefile.in;
+to fix another bustage from bug 1069556 landing;
 
--- a/Makefile.in
+++ b/Makefile.in
@@ -109,17 +109,19 @@ backend: $(BUILD_BACKEND_FILES)
 $(subst .,%,$(BUILD_BACKEND_FILES)):
 	@echo 'Build configuration changed. Regenerating backend.'
 	$(PYTHON) config.status
 
 Makefile: $(BUILD_BACKEND_FILES)
 	@$(TOUCH) $@
 
 define build_backend_rule
-$(1): $$(shell cat $(1).in)
+$(1)_files := $$(shell cat $(1).in)
+$(1): $$($(1)_files)
+$$($(1)_files):
 
 endef
 $(foreach file,$(BUILD_BACKEND_FILES),$(eval $(call build_backend_rule,$(file))))
 
 default:: $(BUILD_BACKEND_FILES)
 endif
 
 install_manifests := \
--- a/accessible/atk/AccessibleWrap.cpp
+++ b/accessible/atk/AccessibleWrap.cpp
@@ -140,17 +140,17 @@ MaiAtkObject::GetAtkHyperlink()
   }
 
   return maiHyperlink->GetAtkHyperlink();
 }
 
 void
 MaiAtkObject::Shutdown()
 {
-  accWrap = 0;
+  accWrap.SetBits(0);
   MaiHyperlink* maiHyperlink =
     (MaiHyperlink*)g_object_get_qdata(G_OBJECT(this), quark_mai_hyperlink);
   if (maiHyperlink) {
     delete maiHyperlink;
     g_object_set_qdata(G_OBJECT(this), quark_mai_hyperlink, nullptr);
   }
 }
 
@@ -554,25 +554,25 @@ initializeCB(AtkObject *aAtkObj, gpointe
     /* AtkObjectClass has not a "initialize" function now,
      * maybe it has later
      */
 
     if (ATK_OBJECT_CLASS(parent_class)->initialize)
         ATK_OBJECT_CLASS(parent_class)->initialize(aAtkObj, aData);
 
   /* initialize object */
-  MAI_ATK_OBJECT(aAtkObj)->accWrap = reinterpret_cast<uintptr_t>(aData);
+  MAI_ATK_OBJECT(aAtkObj)->accWrap.SetBits(reinterpret_cast<uintptr_t>(aData));
 }
 
 void
 finalizeCB(GObject *aObj)
 {
     if (!IS_MAI_OBJECT(aObj))
         return;
-    NS_ASSERTION(MAI_ATK_OBJECT(aObj)->accWrap == 0, "AccWrap NOT null");
+    NS_ASSERTION(MAI_ATK_OBJECT(aObj)->accWrap.Bits() == 0, "AccWrap NOT null");
 
     // call parent finalize function
     // finalize of GObjectClass will unref the accessible parent if has
     if (G_OBJECT_CLASS (parent_class)->finalize)
         G_OBJECT_CLASS (parent_class)->finalize(aObj);
 }
 
 const gchar*
@@ -1059,23 +1059,23 @@ refRelationSetCB(AtkObject *aAtkObj)
 // for it.
 AccessibleWrap*
 GetAccessibleWrap(AtkObject* aAtkObj)
 {
   bool isMAIObject = IS_MAI_OBJECT(aAtkObj);
   NS_ENSURE_TRUE(isMAIObject || MAI_IS_ATK_SOCKET(aAtkObj),
                  nullptr);
 
-  uintptr_t accWrapPtr = isMAIObject ?
-    MAI_ATK_OBJECT(aAtkObj)->accWrap :
-    reinterpret_cast<uintptr_t>(MAI_ATK_SOCKET(aAtkObj)->accWrap);
-  if (accWrapPtr & IS_PROXY)
-    return nullptr;
-
-  AccessibleWrap* accWrap = reinterpret_cast<AccessibleWrap*>(accWrapPtr);
+  AccessibleWrap* accWrap = nullptr;
+  if (isMAIObject) {
+    Accessible* acc = MAI_ATK_OBJECT(aAtkObj)->accWrap.AsAccessible();
+    accWrap = static_cast<AccessibleWrap*>(acc);
+  } else {
+    accWrap = MAI_ATK_SOCKET(aAtkObj)->accWrap;
+  }
 
   // Check if the accessible was deconstructed.
   if (!accWrap)
     return nullptr;
 
   NS_ENSURE_TRUE(accWrap->GetAtkObject() == aAtkObj, nullptr);
 
   AccessibleWrap* appAccWrap = ApplicationAcc();
@@ -1084,21 +1084,20 @@ GetAccessibleWrap(AtkObject* aAtkObj)
 
   return accWrap;
 }
 
 ProxyAccessible*
 GetProxy(AtkObject* aObj)
 {
   if (!aObj || !IS_MAI_OBJECT(aObj) ||
-      !(MAI_ATK_OBJECT(aObj)->accWrap & IS_PROXY))
+      !MAI_ATK_OBJECT(aObj)->accWrap.IsProxy())
     return nullptr;
 
-  return reinterpret_cast<ProxyAccessible*>(MAI_ATK_OBJECT(aObj)->accWrap
-      & ~IS_PROXY);
+  return MAI_ATK_OBJECT(aObj)->accWrap.AsProxy();
 }
 
 AtkObject*
 GetWrapperFor(ProxyAccessible* aProxy)
 {
   return reinterpret_cast<AtkObject*>(aProxy->GetWrapper() & ~IS_PROXY);
 }
 
--- a/accessible/atk/nsMai.h
+++ b/accessible/atk/nsMai.h
@@ -6,16 +6,17 @@
 
 #ifndef __NS_MAI_H__
 #define __NS_MAI_H__
 
 #include <atk/atk.h>
 #include <glib.h>
 #include <glib-object.h>
 
+#include "AccessibleOrProxy.h"
 #include "AccessibleWrap.h"
 
 namespace mozilla {
 namespace a11y {
 class ProxyAccessible;
 }
 }
 
@@ -90,17 +91,17 @@ static const uintptr_t IS_PROXY = 1;
  */
 struct MaiAtkObject
 {
   AtkObject parent;
   /*
    * The AccessibleWrap whose properties and features are exported
    * via this object instance.
    */
-  uintptr_t accWrap;
+  mozilla::a11y::AccessibleOrProxy accWrap;
 
   /*
    * Get the AtkHyperlink for this atk object.
    */
   AtkHyperlink* GetAtkHyperlink();
 
   /*
    * Shutdown this AtkObject.
--- a/accessible/atk/nsMaiHyperlink.cpp
+++ b/accessible/atk/nsMaiHyperlink.cpp
@@ -95,17 +95,17 @@ mai_atk_hyperlink_get_type(void)
 
         type = g_type_register_static(ATK_TYPE_HYPERLINK,
                                       "MaiAtkHyperlink",
                                       &tinfo, GTypeFlags(0));
     }
     return type;
 }
 
-MaiHyperlink::MaiHyperlink(uintptr_t aHyperLink) :
+MaiHyperlink::MaiHyperlink(AccessibleOrProxy aHyperLink) :
     mHyperlink(aHyperLink),
     mMaiAtkHyperlink(nullptr)
 {
     mMaiAtkHyperlink =
         reinterpret_cast<AtkHyperlink *>
                         (g_object_new(mai_atk_hyperlink_get_type(), nullptr));
     NS_ASSERTION(mMaiAtkHyperlink, "OUT OF MEMORY");
     if (!mMaiAtkHyperlink)
--- a/accessible/atk/nsMaiHyperlink.h
+++ b/accessible/atk/nsMaiHyperlink.h
@@ -18,40 +18,38 @@ namespace a11y {
 
 /*
  * MaiHyperlink is a auxiliary class for MaiInterfaceHyperText.
  */
 
 class MaiHyperlink
 {
 public:
-  explicit MaiHyperlink(uintptr_t aHyperLink);
+  explicit MaiHyperlink(AccessibleOrProxy aHyperLink);
   ~MaiHyperlink();
 
 public:
   AtkHyperlink* GetAtkHyperlink() const { return mMaiAtkHyperlink; }
   Accessible* GetAccHyperlink()
     {
-      if (!mHyperlink || mHyperlink & IS_PROXY)
+      if (!mHyperlink.IsAccessible())
         return nullptr;
 
-      Accessible* link = reinterpret_cast<Accessible*>(mHyperlink);
+      Accessible* link = mHyperlink.AsAccessible();
+      if (!link) {
+        return nullptr;
+      }
+
       NS_ASSERTION(link->IsLink(), "Why isn't it a link!");
       return link;
     }
 
-  ProxyAccessible* Proxy() const
-  {
-    if (!(mHyperlink & IS_PROXY))
-      return nullptr;
-
-    return reinterpret_cast<ProxyAccessible*>(mHyperlink & ~IS_PROXY);
-  }
+  ProxyAccessible* Proxy() const { return mHyperlink.AsProxy(); }
 
 protected:
-  uintptr_t mHyperlink;
+  AccessibleOrProxy mHyperlink;
   AtkHyperlink* mMaiAtkHyperlink;
 };
 
 } // namespace a11y
 } // namespace mozilla
 
 #endif /* __MAI_HYPERLINK_H__ */
new file mode 100644
--- /dev/null
+++ b/accessible/base/AccessibleOrProxy.h
@@ -0,0 +1,63 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_a11y_AccessibleOrProxy_h
+#define mozilla_a11y_AccessibleOrProxy_h
+
+#include "mozilla/a11y/Accessible.h"
+#include "mozilla/a11y/ProxyAccessible.h"
+
+#include <stdint.h>
+
+namespace mozilla {
+namespace a11y {
+
+/**
+ * This class stores an Accessible* or a ProxyAccessible* in a safe manner
+ * with size sizeof(void*).
+ */
+class AccessibleOrProxy
+{
+public:
+  MOZ_IMPLICIT AccessibleOrProxy(Accessible* aAcc) :
+    mBits(reinterpret_cast<uintptr_t>(aAcc)) {}
+  MOZ_IMPLICIT AccessibleOrProxy(ProxyAccessible* aProxy) :
+    mBits(reinterpret_cast<uintptr_t>(aProxy) | IS_PROXY) {}
+  MOZ_IMPLICIT AccessibleOrProxy(decltype(nullptr)) : mBits(0) {}
+
+  bool IsProxy() const { return mBits & IS_PROXY; }
+  ProxyAccessible* AsProxy() const
+  {
+    if (IsProxy()) {
+      return reinterpret_cast<ProxyAccessible*>(mBits & ~IS_PROXY);
+    }
+
+    return nullptr;
+  }
+
+  bool IsAccessible() const { return !IsProxy(); }
+  Accessible* AsAccessible() const
+  {
+    if (IsAccessible()) {
+      return reinterpret_cast<Accessible*>(mBits);
+    }
+
+    return nullptr;
+  }
+
+  // XXX these are implementation details that ideally would not be exposed.
+  uintptr_t Bits() const { return mBits; }
+  void SetBits(uintptr_t aBits) { mBits = aBits; }
+
+private:
+  uintptr_t mBits;
+  static const uintptr_t IS_PROXY = 0x1;
+};
+
+}
+}
+
+#endif
--- a/accessible/base/DocManager.cpp
+++ b/accessible/base/DocManager.cpp
@@ -35,16 +35,18 @@
 #include "nsXULAppAPI.h"
 #include "mozilla/dom/TabChild.h"
 
 using namespace mozilla;
 using namespace mozilla::a11y;
 using namespace mozilla::dom;
 
 StaticAutoPtr<nsTArray<DocAccessibleParent*>> DocManager::sRemoteDocuments;
+nsRefPtrHashtable<nsPtrHashKey<const DocAccessibleParent>, xpcAccessibleDocument>*
+DocManager::sRemoteXPCDocumentCache = nullptr;
 
 ////////////////////////////////////////////////////////////////////////////////
 // DocManager
 ////////////////////////////////////////////////////////////////////////////////
 
 DocManager::DocManager()
   : mDocAccessibleCache(2), mXPCDocumentCache(0)
 {
@@ -96,30 +98,60 @@ DocManager::NotifyOfDocumentShutdown(Doc
     xpcDoc->Shutdown();
     mXPCDocumentCache.Remove(aDocument);
   }
 
   mDocAccessibleCache.Remove(aDOMDocument);
   RemoveListeners(aDOMDocument);
 }
 
+void
+DocManager::NotifyOfRemoteDocShutdown(DocAccessibleParent* aDoc)
+{
+  xpcAccessibleDocument* doc = GetCachedXPCDocument(aDoc);
+  if (doc) {
+    doc->Shutdown();
+    sRemoteXPCDocumentCache->Remove(aDoc);
+  }
+}
+
 xpcAccessibleDocument*
 DocManager::GetXPCDocument(DocAccessible* aDocument)
 {
   if (!aDocument)
     return nullptr;
 
   xpcAccessibleDocument* xpcDoc = mXPCDocumentCache.GetWeak(aDocument);
   if (!xpcDoc) {
     xpcDoc = new xpcAccessibleDocument(aDocument);
     mXPCDocumentCache.Put(aDocument, xpcDoc);
   }
   return xpcDoc;
 }
 
+xpcAccessibleDocument*
+DocManager::GetXPCDocument(DocAccessibleParent* aDoc)
+{
+  xpcAccessibleDocument* doc = GetCachedXPCDocument(aDoc);
+  if (doc) {
+    return doc;
+  }
+
+  if (!sRemoteXPCDocumentCache) {
+    sRemoteXPCDocumentCache =
+      new nsRefPtrHashtable<nsPtrHashKey<const DocAccessibleParent>, xpcAccessibleDocument>;
+  }
+
+  doc =
+    new xpcAccessibleDocument(aDoc, Interfaces::DOCUMENT | Interfaces::HYPERTEXT);
+  sRemoteXPCDocumentCache->Put(aDoc, doc);
+
+  return doc;
+}
+
 #ifdef DEBUG
 bool
 DocManager::IsProcessingRefreshDriverNotification() const
 {
   for (auto iter = mDocAccessibleCache.ConstIter(); !iter.Done(); iter.Next()) {
     DocAccessible* docAccessible = iter.UserData();
     NS_ASSERTION(docAccessible,
                  "No doc accessible for the object in doc accessible cache!");
--- a/accessible/base/DocManager.h
+++ b/accessible/base/DocManager.h
@@ -85,16 +85,31 @@ public:
   /*
    * Notify of a new top level document in a content process.
    */
   static void RemoteDocAdded(DocAccessibleParent* aDoc);
 
   static const nsTArray<DocAccessibleParent*>* TopLevelRemoteDocs()
     { return sRemoteDocuments; }
 
+  /**
+   * Remove the xpc document for a remote document if there is one.
+   */
+  static void NotifyOfRemoteDocShutdown(DocAccessibleParent* adoc);
+
+  /**
+   * Get a XPC document for a remote document.
+   */
+  static xpcAccessibleDocument* GetXPCDocument(DocAccessibleParent* aDoc);
+  static xpcAccessibleDocument* GetCachedXPCDocument(const DocAccessibleParent* aDoc)
+  {
+    return sRemoteXPCDocumentCache ? sRemoteXPCDocumentCache->GetWeak(aDoc)
+      : nullptr;
+  }
+
 #ifdef DEBUG
   bool IsProcessingRefreshDriverNotification() const;
 #endif
 
 protected:
   DocManager();
   virtual ~DocManager() { }
 
@@ -142,16 +157,18 @@ private:
 
   typedef nsRefPtrHashtable<nsPtrHashKey<const nsIDocument>, DocAccessible>
     DocAccessibleHashtable;
   DocAccessibleHashtable mDocAccessibleCache;
 
   typedef nsRefPtrHashtable<nsPtrHashKey<const DocAccessible>, xpcAccessibleDocument>
     XPCDocumentHashtable;
   XPCDocumentHashtable mXPCDocumentCache;
+  static nsRefPtrHashtable<nsPtrHashKey<const DocAccessibleParent>, xpcAccessibleDocument>*
+    sRemoteXPCDocumentCache;
 
   /*
    * The list of remote top level documents.
    */
   static StaticAutoPtr<nsTArray<DocAccessibleParent*>> sRemoteDocuments;
 };
 
 /**
--- a/accessible/base/nsCoreUtils.cpp
+++ b/accessible/base/nsCoreUtils.cpp
@@ -11,21 +11,23 @@
 #include "nsIDocShellTreeOwner.h"
 #include "nsIDocument.h"
 #include "nsIDOMHTMLDocument.h"
 #include "nsIDOMHTMLElement.h"
 #include "nsRange.h"
 #include "nsIBoxObject.h"
 #include "nsIDOMXULElement.h"
 #include "nsIDocShell.h"
+#include "nsIObserverService.h"
 #include "nsIPresShell.h"
 #include "nsPresContext.h"
 #include "nsIScrollableFrame.h"
 #include "nsISelectionPrivate.h"
 #include "nsISelectionController.h"
+#include "nsISimpleEnumerator.h"
 #include "mozilla/dom/TouchEvent.h"
 #include "mozilla/EventListenerManager.h"
 #include "mozilla/EventStateManager.h"
 #include "mozilla/MouseEvents.h"
 #include "mozilla/TouchEvents.h"
 #include "nsView.h"
 #include "nsGkAtoms.h"
 
@@ -653,8 +655,34 @@ nsCoreUtils::IsWhitespaceString(const ns
   aString.BeginReading(iterBegin);
   aString.EndReading(iterEnd);
 
   while (iterBegin != iterEnd && IsWhitespace(*iterBegin))
     ++iterBegin;
 
   return iterBegin == iterEnd;
 }
+
+bool
+nsCoreUtils::AccEventObserversExist()
+{
+  nsCOMPtr<nsIObserverService> obsService = services::GetObserverService();
+  NS_ENSURE_TRUE(obsService, false);
+
+  nsCOMPtr<nsISimpleEnumerator> observers;
+  obsService->EnumerateObservers(NS_ACCESSIBLE_EVENT_TOPIC,
+                                 getter_AddRefs(observers));
+  NS_ENSURE_TRUE(observers, false);
+
+  bool hasObservers = false;
+  observers->HasMoreElements(&hasObservers);
+
+  return hasObservers;
+}
+
+void
+nsCoreUtils::DispatchAccEvent(RefPtr<nsIAccessibleEvent> event)
+{
+  nsCOMPtr<nsIObserverService> obsService = services::GetObserverService();
+  NS_ENSURE_TRUE_VOID(obsService);
+
+  obsService->NotifyObservers(event, NS_ACCESSIBLE_EVENT_TOPIC, nullptr);
+}
--- a/accessible/base/nsCoreUtils.h
+++ b/accessible/base/nsCoreUtils.h
@@ -2,16 +2,17 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef nsCoreUtils_h_
 #define nsCoreUtils_h_
 
 #include "mozilla/EventForwards.h"
+#include "nsIAccessibleEvent.h"
 #include "nsIContent.h"
 #include "nsIDocument.h" // for GetShell()
 #include "nsIPresShell.h"
 
 #include "nsPoint.h"
 #include "nsTArray.h"
 
 class nsRange;
@@ -314,11 +315,21 @@ public:
   /**
    * Returns true if the given character is whitespace symbol.
    */
   static bool IsWhitespace(char16_t aChar)
   {
     return aChar == ' ' || aChar == '\n' ||
       aChar == '\r' || aChar == '\t' || aChar == 0xa0;
   }
+
+  /*
+   * Return true if there are any observers of accessible events.
+   */
+  static bool AccEventObserversExist();
+
+  /**
+   * Notify accessible event observers of an event.
+   */
+  static void DispatchAccEvent(RefPtr<nsIAccessibleEvent> aEvent);
 };
 
 #endif
--- a/accessible/generic/Accessible.cpp
+++ b/accessible/generic/Accessible.cpp
@@ -880,30 +880,18 @@ Accessible::HandleAccEvent(AccEvent* aEv
           break;
                                                      }
         default:
                                                          ipcDoc->SendEvent(id, aEvent->GetEventType());
       }
     }
   }
 
-  nsCOMPtr<nsIObserverService> obsService = services::GetObserverService();
-  NS_ENSURE_TRUE(obsService, NS_ERROR_FAILURE);
-
-  nsCOMPtr<nsISimpleEnumerator> observers;
-  obsService->EnumerateObservers(NS_ACCESSIBLE_EVENT_TOPIC,
-                                 getter_AddRefs(observers));
-
-  NS_ENSURE_STATE(observers);
-
-  bool hasObservers = false;
-  observers->HasMoreElements(&hasObservers);
-  if (hasObservers) {
-    nsCOMPtr<nsIAccessibleEvent> event = MakeXPCEvent(aEvent);
-    return obsService->NotifyObservers(event, NS_ACCESSIBLE_EVENT_TOPIC, nullptr);
+  if (nsCoreUtils::AccEventObserversExist()) {
+    nsCoreUtils::DispatchAccEvent(MakeXPCEvent(aEvent));
   }
 
   return NS_OK;
 }
 
 already_AddRefed<nsIPersistentProperties>
 Accessible::Attributes()
 {
--- a/accessible/ipc/DocAccessibleParent.cpp
+++ b/accessible/ipc/DocAccessibleParent.cpp
@@ -4,16 +4,20 @@
  * 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 "DocAccessibleParent.h"
 #include "nsAutoPtr.h"
 #include "mozilla/a11y/Platform.h"
 #include "ProxyAccessible.h"
 #include "mozilla/dom/TabParent.h"
+#include "xpcAccessibleDocument.h"
+#include "xpcAccEvents.h"
+#include "nsAccUtils.h"
+#include "nsCoreUtils.h"
 
 namespace mozilla {
 namespace a11y {
 
 bool
 DocAccessibleParent::RecvShowEvent(const ShowEventData& aData)
 {
   if (mShutdown)
@@ -139,44 +143,88 @@ DocAccessibleParent::RecvEvent(const uin
 {
   ProxyAccessible* proxy = GetAccessible(aID);
   if (!proxy) {
     NS_ERROR("no proxy for event!");
     return true;
   }
 
   ProxyEvent(proxy, aEventType);
+
+  if (!nsCoreUtils::AccEventObserversExist()) {
+    return true;
+  }
+
+  xpcAccessibleGeneric* xpcAcc = GetXPCAccessible(proxy);
+  xpcAccessibleDocument* doc = GetAccService()->GetXPCDocument(this);
+  nsIDOMNode* node = nullptr;
+  bool fromUser = true; // XXX fix me
+  RefPtr<xpcAccEvent> event = new xpcAccEvent(aEventType, xpcAcc, doc, node,
+                                              fromUser);
+  nsCoreUtils::DispatchAccEvent(Move(event));
+
   return true;
 }
 
 bool
 DocAccessibleParent::RecvStateChangeEvent(const uint64_t& aID,
                                           const uint64_t& aState,
                                           const bool& aEnabled)
 {
   ProxyAccessible* target = GetAccessible(aID);
   if (!target) {
     NS_ERROR("we don't know about the target of a state change event!");
     return true;
   }
 
   ProxyStateChangeEvent(target, aState, aEnabled);
+
+  if (!nsCoreUtils::AccEventObserversExist()) {
+    return true;
+  }
+
+  xpcAccessibleGeneric* xpcAcc = GetXPCAccessible(target);
+  xpcAccessibleDocument* doc = GetAccService()->GetXPCDocument(this);
+  uint32_t type = nsIAccessibleEvent::EVENT_STATE_CHANGE;
+  bool extra;
+  uint32_t state = nsAccUtils::To32States(aState, &extra);
+  bool fromUser = true; // XXX fix this
+  nsIDOMNode* node = nullptr; // XXX can we do better?
+  RefPtr<xpcAccStateChangeEvent> event =
+    new xpcAccStateChangeEvent(type, xpcAcc, doc, node, fromUser, state, extra,
+                               aEnabled);
+  nsCoreUtils::DispatchAccEvent(Move(event));
+
   return true;
 }
 
 bool
 DocAccessibleParent::RecvCaretMoveEvent(const uint64_t& aID, const int32_t& aOffset)
 {
   ProxyAccessible* proxy = GetAccessible(aID);
   if (!proxy) {
     NS_ERROR("unknown caret move event target!");
     return true;
   }
 
   ProxyCaretMoveEvent(proxy, aOffset);
+
+  if (!nsCoreUtils::AccEventObserversExist()) {
+    return true;
+  }
+
+  xpcAccessibleGeneric* xpcAcc = GetXPCAccessible(proxy);
+  xpcAccessibleDocument* doc = GetAccService()->GetXPCDocument(this);
+  nsIDOMNode* node = nullptr;
+  bool fromUser = true; // XXX fix me
+  uint32_t type = nsIAccessibleEvent::EVENT_TEXT_CARET_MOVED;
+  RefPtr<xpcAccCaretMoveEvent> event =
+    new xpcAccCaretMoveEvent(type, xpcAcc, doc, node, fromUser, aOffset);
+  nsCoreUtils::DispatchAccEvent(Move(event));
+
   return true;
 }
 
 bool
 DocAccessibleParent::RecvTextChangeEvent(const uint64_t& aID,
                                          const nsString& aStr,
                                          const int32_t& aStart,
                                          const uint32_t& aLen,
@@ -186,16 +234,29 @@ DocAccessibleParent::RecvTextChangeEvent
   ProxyAccessible* target = GetAccessible(aID);
   if (!target) {
     NS_ERROR("text change event target is unknown!");
     return true;
   }
 
   ProxyTextChangeEvent(target, aStr, aStart, aLen, aIsInsert, aFromUser);
 
+  if (!nsCoreUtils::AccEventObserversExist()) {
+    return true;
+  }
+
+  xpcAccessibleGeneric* xpcAcc = GetXPCAccessible(target);
+  xpcAccessibleDocument* doc = GetAccService()->GetXPCDocument(this);
+  uint32_t type = nsIAccessibleEvent::EVENT_TEXT_CHANGED;
+  nsIDOMNode* node = nullptr;
+  RefPtr<xpcAccTextChangeEvent> event =
+    new xpcAccTextChangeEvent(type, xpcAcc, doc, node, aFromUser, aStart, aLen,
+                              aIsInsert, aStr);
+  nsCoreUtils::DispatchAccEvent(Move(event));
+
   return true;
 }
 
 bool
 DocAccessibleParent::RecvBindChildDoc(PDocAccessibleParent* aChildDoc, const uint64_t& aID)
 {
   // One document should never directly be the child of another.
   // We should always have at least an outer doc accessible in between.
@@ -262,16 +323,18 @@ DocAccessibleParent::Destroy()
   for (uint32_t i = childDocCount - 1; i < childDocCount; i--)
     mChildDocs[i]->Destroy();
 
   for (auto iter = mAccessibles.Iter(); !iter.Done(); iter.Next()) {
     MOZ_ASSERT(iter.Get()->mProxy != this);
     ProxyDestroyed(iter.Get()->mProxy);
     iter.Remove();
   }
+
+  DocManager::NotifyOfRemoteDocShutdown(this);
   ProxyDestroyed(this);
   if (mParentDoc)
     mParentDoc->RemoveChildDoc(this);
   else if (IsTopLevel())
     GetAccService()->RemoteDocShutdown(this);
 }
 
 bool
@@ -285,10 +348,18 @@ DocAccessibleParent::CheckDocTree() cons
     if (!mChildDocs[i]->CheckDocTree()) {
       return false;
     }
   }
 
   return true;
 }
 
+xpcAccessibleGeneric*
+DocAccessibleParent::GetXPCAccessible(ProxyAccessible* aProxy)
+{
+  xpcAccessibleDocument* doc = GetAccService()->GetXPCDocument(this);
+  MOZ_ASSERT(doc);
+
+  return doc->GetXPCAccessible(aProxy);
+}
 } // a11y
 } // mozilla
--- a/accessible/ipc/DocAccessibleParent.h
+++ b/accessible/ipc/DocAccessibleParent.h
@@ -12,16 +12,18 @@
 #include "mozilla/a11y/PDocAccessibleParent.h"
 #include "nsClassHashtable.h"
 #include "nsHashKeys.h"
 #include "nsISupportsImpl.h"
 
 namespace mozilla {
 namespace a11y {
 
+class xpcAccessibleGeneric;
+
 /*
  * These objects live in the main process and comunicate with and represent
  * an accessible document in a content process.
  */
 class DocAccessibleParent : public ProxyAccessible,
     public PDocAccessibleParent
 {
 public:
@@ -154,16 +156,17 @@ private:
 
     ProxyAccessible* mProxy;
   };
 
   uint32_t AddSubtree(ProxyAccessible* aParent,
                       const nsTArray<AccessibleData>& aNewTree, uint32_t aIdx,
                       uint32_t aIdxInParent);
   MOZ_WARN_UNUSED_RESULT bool CheckDocTree() const;
+  xpcAccessibleGeneric* GetXPCAccessible(ProxyAccessible* aProxy);
 
   nsTArray<DocAccessibleParent*> mChildDocs;
   DocAccessibleParent* mParentDoc;
 
   /*
    * Conceptually this is a map from IDs to proxies, but we store the ID in the
    * proxy object so we can't use a real map.
    */
--- a/accessible/ipc/ProxyAccessible.cpp
+++ b/accessible/ipc/ProxyAccessible.cpp
@@ -9,25 +9,31 @@
 #include "DocAccessible.h"
 #include "mozilla/a11y/DocManager.h"
 #include "mozilla/dom/Element.h"
 #include "mozilla/dom/TabParent.h"
 #include "mozilla/unused.h"
 #include "mozilla/a11y/Platform.h"
 #include "RelationType.h"
 #include "mozilla/a11y/Role.h"
+#include "xpcAccessibleDocument.h"
 
 namespace mozilla {
 namespace a11y {
 
 void
 ProxyAccessible::Shutdown()
 {
   MOZ_DIAGNOSTIC_ASSERT(!IsDoc());
   NS_ASSERTION(!mOuterDoc, "Why do we still have a child doc?");
+  xpcAccessibleDocument* xpcDoc =
+    GetAccService()->GetCachedXPCDocument(Document());
+  if (xpcDoc) {
+    xpcDoc->NotifyOfShutdown(this);
+  }
 
   // XXX Ideally  this wouldn't be necessary, but it seems OuterDoc accessibles
   // can be destroyed before the doc they own.
   if (!mOuterDoc) {
     uint32_t childCount = mChildren.Length();
     for (uint32_t idx = 0; idx < childCount; idx++)
       mChildren[idx]->Shutdown();
   } else {
--- a/accessible/ipc/moz.build
+++ b/accessible/ipc/moz.build
@@ -19,16 +19,17 @@ if CONFIG['ACCESSIBILITY']:
         'DocAccessibleChild.cpp',
         'DocAccessibleParent.cpp',
         'ProxyAccessible.cpp'
     ]
 
     LOCAL_INCLUDES += [
         '../base',
         '../generic',
+        '../xpcom',
     ]
 
     if CONFIG['MOZ_ENABLE_GTK']:
         LOCAL_INCLUDES += [
             '/accessible/atk',
         ]
     elif CONFIG['MOZ_WIDGET_TOOLKIT'] == 'windows':
         LOCAL_INCLUDES += [
--- a/accessible/xpcom/moz.build
+++ b/accessible/xpcom/moz.build
@@ -59,8 +59,10 @@ xpc_acc_events_h = GENERATED_FILES['xpcA
 xpc_acc_events_h.script = 'AccEventGen.py:gen_header_file'
 xpc_acc_events_h.inputs += ['AccEvents.conf']
 
 xpc_acc_events_cpp = GENERATED_FILES['xpcAccEvents.cpp']
 xpc_acc_events_cpp.script = 'AccEventGen.py:gen_cpp_file'
 xpc_acc_events_cpp.inputs += ['AccEvents.conf']
 
 FINAL_LIBRARY = 'xul'
+
+include('/ipc/chromium/chromium-config.mozbuild')
--- a/accessible/xpcom/xpcAccessibleApplication.h
+++ b/accessible/xpcom/xpcAccessibleApplication.h
@@ -31,17 +31,17 @@ public:
   NS_IMETHOD GetAppVersion(nsAString& aVersion) final override;
   NS_IMETHOD GetPlatformName(nsAString& aName) final override;
   NS_IMETHOD GetPlatformVersion(nsAString& aVersion) final override;
 
 protected:
   virtual ~xpcAccessibleApplication() {}
 
 private:
-  ApplicationAccessible* Intl() { return mIntl->AsApplication(); }
+  ApplicationAccessible* Intl() { return mIntl.AsAccessible()->AsApplication(); }
 
   xpcAccessibleApplication(const xpcAccessibleApplication&) = delete;
   xpcAccessibleApplication& operator =(const xpcAccessibleApplication&) = delete;
 };
 
 } // namespace a11y
 } // namespace mozilla
 
--- a/accessible/xpcom/xpcAccessibleDocument.cpp
+++ b/accessible/xpcom/xpcAccessibleDocument.cpp
@@ -4,16 +4,17 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "xpcAccessibleDocument.h"
 #include "xpcAccessibleImage.h"
 #include "xpcAccessibleTable.h"
 #include "xpcAccessibleTableCell.h"
 
+#include "mozilla/a11y/DocAccessibleParent.h"
 #include "DocAccessible-inl.h"
 #include "nsIDOMDocument.h"
 
 using namespace mozilla::a11y;
 
 ////////////////////////////////////////////////////////////////////////////////
 // nsISupports and cycle collection
 
@@ -163,16 +164,17 @@ xpcAccessibleDocument::GetVirtualCursor(
 }
 
 ////////////////////////////////////////////////////////////////////////////////
 // xpcAccessibleDocument
 
 xpcAccessibleGeneric*
 xpcAccessibleDocument::GetAccessible(Accessible* aAccessible)
 {
+  MOZ_ASSERT(!mRemote);
   if (ToXPCDocument(aAccessible->Document()) != this) {
     NS_ERROR("This XPCOM document is not related with given internal accessible!");
     return nullptr;
   }
 
   if (aAccessible->IsDoc())
     return this;
 
@@ -190,16 +192,37 @@ xpcAccessibleDocument::GetAccessible(Acc
     xpcAcc = new xpcAccessibleHyperText(aAccessible);
   else
     xpcAcc = new xpcAccessibleGeneric(aAccessible);
 
   mCache.Put(aAccessible, xpcAcc);
   return xpcAcc;
 }
 
+xpcAccessibleGeneric*
+xpcAccessibleDocument::GetXPCAccessible(ProxyAccessible* aProxy)
+{
+  MOZ_ASSERT(mRemote);
+  MOZ_ASSERT(aProxy->Document() == mIntl.AsProxy());
+  if (aProxy->IsDoc()) {
+    return this;
+  }
+
+  xpcAccessibleGeneric* acc = mCache.GetWeak(aProxy);
+  if (acc) {
+    return acc;
+  }
+
+  // XXX support exposing optional interfaces.
+  acc = new xpcAccessibleGeneric(aProxy, 0);
+  mCache.Put(aProxy, acc);
+
+  return acc;
+}
+
 void
 xpcAccessibleDocument::Shutdown()
 {
   for (auto iter = mCache.Iter(); !iter.Done(); iter.Next()) {
     iter.Data()->Shutdown();
     iter.Remove();
   }
   xpcAccessibleGeneric::Shutdown();
--- a/accessible/xpcom/xpcAccessibleDocument.h
+++ b/accessible/xpcom/xpcAccessibleDocument.h
@@ -20,17 +20,21 @@ namespace a11y {
 /**
  * XPCOM wrapper around DocAccessible class.
  */
 class xpcAccessibleDocument : public xpcAccessibleHyperText,
                               public nsIAccessibleDocument
 {
 public:
   explicit xpcAccessibleDocument(DocAccessible* aIntl) :
-    xpcAccessibleHyperText(aIntl), mCache(kDefaultCacheLength) { }
+    xpcAccessibleHyperText(aIntl), mCache(kDefaultCacheLength), mRemote(false) { }
+
+  xpcAccessibleDocument(ProxyAccessible* aProxy, uint32_t aInterfaces) :
+    xpcAccessibleHyperText(aProxy, aInterfaces), mCache(kDefaultCacheLength),
+    mRemote(true) {}
 
   NS_DECL_ISUPPORTS_INHERITED
   NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(xpcAccessibleDocument,
                                            xpcAccessibleGeneric)
 
   // nsIAccessibleDocument
   NS_IMETHOD GetURL(nsAString& aURL) final override;
   NS_IMETHOD GetTitle(nsAString& aTitle) final override;
@@ -46,41 +50,63 @@ public:
     final override;
   NS_IMETHOD GetVirtualCursor(nsIAccessiblePivot** aVirtualCursor)
     final override;
 
   /**
    * Return XPCOM wrapper for the internal accessible.
    */
   xpcAccessibleGeneric* GetAccessible(Accessible* aAccessible);
+  xpcAccessibleGeneric* GetXPCAccessible(ProxyAccessible* aProxy);
 
   virtual void Shutdown() override;
 
 protected:
   virtual ~xpcAccessibleDocument() {}
 
 private:
-  DocAccessible* Intl() { return mIntl->AsDoc(); }
+  DocAccessible* Intl()
+  {
+    if (Accessible* acc = mIntl.AsAccessible()) {
+      return acc->AsDoc();
+    }
+
+    return nullptr;
+  }
 
   void NotifyOfShutdown(Accessible* aAccessible)
   {
+    MOZ_ASSERT(!mRemote);
     xpcAccessibleGeneric* xpcAcc = mCache.GetWeak(aAccessible);
     if (xpcAcc)
       xpcAcc->Shutdown();
 
     mCache.Remove(aAccessible);
   }
 
+  void NotifyOfShutdown(ProxyAccessible* aProxy)
+  {
+    MOZ_ASSERT(mRemote);
+    xpcAccessibleGeneric* acc = mCache.GetWeak(aProxy);
+    if (acc) {
+      acc->Shutdown();
+    }
+
+    mCache.Remove(aProxy);
+  }
+
   friend class DocManager;
   friend class DocAccessible;
+  friend class ProxyAccessible;
 
   xpcAccessibleDocument(const xpcAccessibleDocument&) = delete;
   xpcAccessibleDocument& operator =(const xpcAccessibleDocument&) = delete;
 
-  nsRefPtrHashtable<nsPtrHashKey<const Accessible>, xpcAccessibleGeneric> mCache;
+  nsRefPtrHashtable<nsPtrHashKey<const void>, xpcAccessibleGeneric> mCache;
+  bool mRemote;
 };
 
 inline xpcAccessibleGeneric*
 ToXPC(Accessible* aAccessible)
 {
   if (!aAccessible)
     return nullptr;
 
--- a/accessible/xpcom/xpcAccessibleGeneric.cpp
+++ b/accessible/xpcom/xpcAccessibleGeneric.cpp
@@ -28,17 +28,17 @@ NS_IMPL_CYCLE_COLLECTING_ADDREF(xpcAcces
 NS_IMPL_CYCLE_COLLECTING_RELEASE(xpcAccessibleGeneric)
 
 ////////////////////////////////////////////////////////////////////////////////
 // nsIAccessible
 
 Accessible*
 xpcAccessibleGeneric::ToInternalAccessible() const
 {
-  return mIntl;
+  return mIntl.AsAccessible();
 }
 
 ////////////////////////////////////////////////////////////////////////////////
 // xpcAccessibleGeneric
 
 void
 xpcAccessibleGeneric::Shutdown()
 {
--- a/accessible/xpcom/xpcAccessibleGeneric.h
+++ b/accessible/xpcom/xpcAccessibleGeneric.h
@@ -8,53 +8,68 @@
 #define mozilla_a11y_xpcAccessibleGeneric_h_
 
 #include "xpcAccessible.h"
 #include "xpcAccessibleHyperLink.h"
 #include "xpcAccessibleSelectable.h"
 #include "xpcAccessibleValue.h"
 
 #include "Accessible.h"
+#include "AccessibleOrProxy.h"
 
 namespace mozilla {
 namespace a11y {
 
 /**
  * XPCOM wrapper around Accessible class.
  */
 class xpcAccessibleGeneric : public xpcAccessible,
                              public xpcAccessibleHyperLink,
                              public xpcAccessibleSelectable,
                              public xpcAccessibleValue
 {
 public:
   explicit xpcAccessibleGeneric(Accessible* aInternal) :
     mIntl(aInternal), mSupportedIfaces(0)
   {
-    if (mIntl->IsSelect())
+    if (aInternal->IsSelect())
       mSupportedIfaces |= eSelectable;
-    if (mIntl->HasNumericValue())
+    if (aInternal->HasNumericValue())
       mSupportedIfaces |= eValue;
-    if (mIntl->IsLink())
+    if (aInternal->IsLink())
       mSupportedIfaces |= eHyperLink;
   }
 
+  xpcAccessibleGeneric(ProxyAccessible* aProxy, uint32_t aInterfaces) :
+    mIntl(aProxy)
+  {
+    if (aInterfaces & Interfaces::SELECTION) {
+      mSupportedIfaces |= eSelectable;
+    }
+      if (aInterfaces & Interfaces::VALUE) {
+        mSupportedIfaces |= eValue;
+      }
+      if (aInterfaces & Interfaces::HYPERLINK) {
+        mSupportedIfaces |= eHyperLink;
+      }
+    }
+
   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
   NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(xpcAccessibleGeneric, nsIAccessible)
 
   // nsIAccessible
   virtual Accessible* ToInternalAccessible() const final override;
 
   // xpcAccessibleGeneric
   virtual void Shutdown();
 
 protected:
   virtual ~xpcAccessibleGeneric() {}
 
-  Accessible* mIntl;
+  AccessibleOrProxy mIntl;
 
   enum {
     eSelectable = 1 << 0,
     eValue = 1 << 1,
     eHyperLink = 1 << 2,
     eText = 1 << 3
   };
   uint8_t mSupportedIfaces;
@@ -68,33 +83,33 @@ private:
 
   xpcAccessibleGeneric(const xpcAccessibleGeneric&) = delete;
   xpcAccessibleGeneric& operator =(const xpcAccessibleGeneric&) = delete;
 };
 
 inline Accessible*
 xpcAccessible::Intl()
 {
-  return static_cast<xpcAccessibleGeneric*>(this)->mIntl;
+  return static_cast<xpcAccessibleGeneric*>(this)->mIntl.AsAccessible();
 }
 
 inline Accessible*
 xpcAccessibleHyperLink::Intl()
 {
-  return static_cast<xpcAccessibleGeneric*>(this)->mIntl;
+  return static_cast<xpcAccessibleGeneric*>(this)->mIntl.AsAccessible();
 }
 
 inline Accessible*
 xpcAccessibleSelectable::Intl()
 {
-  return static_cast<xpcAccessibleGeneric*>(this)->mIntl;
+  return static_cast<xpcAccessibleGeneric*>(this)->mIntl.AsAccessible();
 }
 
 inline Accessible*
 xpcAccessibleValue::Intl()
 {
-  return static_cast<xpcAccessibleGeneric*>(this)->mIntl;
+  return static_cast<xpcAccessibleGeneric*>(this)->mIntl.AsAccessible();
 }
 
 } // namespace a11y
 } // namespace mozilla
 
 #endif
--- a/accessible/xpcom/xpcAccessibleHyperText.h
+++ b/accessible/xpcom/xpcAccessibleHyperText.h
@@ -21,31 +21,41 @@ class xpcAccessibleHyperText : public xp
                                public nsIAccessibleText,
                                public nsIAccessibleEditableText,
                                public nsIAccessibleHyperText
 {
 public:
   explicit xpcAccessibleHyperText(Accessible* aIntl) :
     xpcAccessibleGeneric(aIntl)
   {
-    if (mIntl->IsHyperText() && mIntl->AsHyperText()->IsTextRole())
+    if (aIntl->IsHyperText() && aIntl->AsHyperText()->IsTextRole())
       mSupportedIfaces |= eText;
   }
 
+  xpcAccessibleHyperText(ProxyAccessible* aProxy, uint32_t aInterfaces) :
+    xpcAccessibleGeneric(aProxy, aInterfaces) { mSupportedIfaces |= eText; }
+
   NS_DECL_ISUPPORTS_INHERITED
 
   NS_DECL_NSIACCESSIBLETEXT
   NS_DECL_NSIACCESSIBLEHYPERTEXT
   NS_DECL_NSIACCESSIBLEEDITABLETEXT
 
 protected:
   virtual ~xpcAccessibleHyperText() {}
 
 private:
-  HyperTextAccessible* Intl() { return mIntl->AsHyperText(); }
+  HyperTextAccessible* Intl()
+  {
+    if (Accessible* acc = mIntl.AsAccessible()) {
+      return acc->AsHyperText();
+    }
+
+    return nullptr;
+  }
 
   xpcAccessibleHyperText(const xpcAccessibleHyperText&) = delete;
   xpcAccessibleHyperText& operator =(const xpcAccessibleHyperText&) = delete;
 };
 
 } // namespace a11y
 } // namespace mozilla
 
--- a/accessible/xpcom/xpcAccessibleImage.h
+++ b/accessible/xpcom/xpcAccessibleImage.h
@@ -16,27 +16,31 @@ namespace a11y {
 
 class xpcAccessibleImage : public xpcAccessibleGeneric,
                            public nsIAccessibleImage
 {
 public:
   explicit xpcAccessibleImage(Accessible* aIntl) :
     xpcAccessibleGeneric(aIntl) { }
 
+  xpcAccessibleImage(ProxyAccessible* aProxy, uint32_t aInterfaces) :
+    xpcAccessibleGeneric(aProxy, aInterfaces) {}
+
   NS_DECL_ISUPPORTS_INHERITED
 
   NS_IMETHOD GetImagePosition(uint32_t aCoordType,
                               int32_t* aX, int32_t* aY) final override;
   NS_IMETHOD GetImageSize(int32_t* aWidth, int32_t* aHeight) final override;
 
 protected:
   virtual ~xpcAccessibleImage() {}
 
 private:
-  ImageAccessible* Intl() { return mIntl->AsImage(); }
+  ImageAccessible* Intl()
+  { return mIntl.IsAccessible() ? mIntl.AsAccessible()->AsImage() : nullptr; }
 
   xpcAccessibleImage(const xpcAccessibleImage&) = delete;
   xpcAccessibleImage& operator =(const xpcAccessibleImage&) = delete;
 };
 
 } // namespace a11y
 } // namespace mozilla
 
--- a/accessible/xpcom/xpcAccessibleTable.h
+++ b/accessible/xpcom/xpcAccessibleTable.h
@@ -18,16 +18,19 @@ namespace a11y {
  */
 class xpcAccessibleTable : public xpcAccessibleGeneric,
                            public nsIAccessibleTable
 {
 public:
   explicit xpcAccessibleTable(Accessible* aIntl) :
     xpcAccessibleGeneric(aIntl) { }
 
+  xpcAccessibleTable(ProxyAccessible* aProxy, uint32_t aInterfaces) :
+    xpcAccessibleGeneric(aProxy, aInterfaces) {}
+
   NS_DECL_ISUPPORTS_INHERITED
 
   // nsIAccessibleTable
   NS_IMETHOD GetCaption(nsIAccessible** aCaption) final override;
   NS_IMETHOD GetSummary(nsAString& aSummary) final override;
   NS_IMETHOD GetColumnCount(int32_t* aColumnCount) final override;
   NS_IMETHOD GetRowCount(int32_t* aRowCount) final override;
   NS_IMETHOD GetCellAt(int32_t aRowIndex, int32_t aColumnIndex,
@@ -75,17 +78,18 @@ public:
   NS_IMETHOD UnselectColumn(int32_t aColIdx) final override;
   NS_IMETHOD UnselectRow(int32_t aRowIdx) final override;
   NS_IMETHOD IsProbablyForLayout(bool* aIsForLayout) final override;
 
 protected:
   virtual ~xpcAccessibleTable() {}
 
 private:
-  TableAccessible* Intl() { return mIntl->AsTable(); }
+  TableAccessible* Intl()
+  { return mIntl.IsAccessible() ? mIntl.AsAccessible()->AsTable() : nullptr; }
 
   xpcAccessibleTable(const xpcAccessibleTable&) = delete;
   xpcAccessibleTable& operator =(const xpcAccessibleTable&) = delete;
 };
 
 } // namespace a11y
 } // namespace mozilla
 
--- a/accessible/xpcom/xpcAccessibleTableCell.h
+++ b/accessible/xpcom/xpcAccessibleTableCell.h
@@ -19,33 +19,43 @@ namespace a11y {
  */
 class xpcAccessibleTableCell : public xpcAccessibleHyperText,
                                public nsIAccessibleTableCell
 {
 public:
   explicit xpcAccessibleTableCell(Accessible* aIntl) :
     xpcAccessibleHyperText(aIntl) { }
 
+  xpcAccessibleTableCell(ProxyAccessible* aProxy, uint32_t aInterfaces) :
+    xpcAccessibleHyperText(aProxy, aInterfaces) {}
+
   NS_DECL_ISUPPORTS_INHERITED
 
   // nsIAccessibleTableCell
   NS_IMETHOD GetTable(nsIAccessibleTable** aTable) final override;
   NS_IMETHOD GetColumnIndex(int32_t* aColIdx) final override;
   NS_IMETHOD GetRowIndex(int32_t* aRowIdx) final override;
   NS_IMETHOD GetColumnExtent(int32_t* aExtent) final override;
   NS_IMETHOD GetRowExtent(int32_t* aExtent) final override;
   NS_IMETHOD GetColumnHeaderCells(nsIArray** aHeaderCells) final override;
   NS_IMETHOD GetRowHeaderCells(nsIArray** aHeaderCells) final override;
   NS_IMETHOD IsSelected(bool* aSelected) final override;
 
 protected:
   virtual ~xpcAccessibleTableCell() {}
 
 private:
-  TableCellAccessible* Intl() { return mIntl->AsTableCell(); }
+  TableCellAccessible* Intl()
+  {
+    if (Accessible* acc = mIntl.AsAccessible()) {
+      return acc->AsTableCell();
+    }
+
+    return nullptr;
+}
 
   xpcAccessibleTableCell(const xpcAccessibleTableCell&) = delete;
   xpcAccessibleTableCell& operator =(const xpcAccessibleTableCell&) = delete;
 };
 
 } // namespace a11y
 } // namespace mozilla
 
--- a/browser/base/content/test/newtab/browser.ini
+++ b/browser/base/content/test/newtab/browser.ini
@@ -1,35 +1,32 @@
 [DEFAULT]
-skip-if = buildapp == 'mulet' || e10s # Bug ?????? - about:newtab tests don't work in e10s
+skip-if = buildapp == 'mulet'
 support-files =
   head.js
 
 [browser_newtab_background_captures.js]
 [browser_newtab_block.js]
 [browser_newtab_bug721442.js]
 [browser_newtab_bug722273.js]
 skip-if = true # Bug 1119906
 [browser_newtab_bug723102.js]
 [browser_newtab_bug723121.js]
 [browser_newtab_bug725996.js]
 [browser_newtab_bug734043.js]
 [browser_newtab_bug735987.js]
-skip-if = (os == 'mac' && os_version == '10.10') # bug 1122478 - newtab drag-drop tests fail on OS X 10.10
 [browser_newtab_bug752841.js]
 [browser_newtab_bug765628.js]
 [browser_newtab_bug876313.js]
 [browser_newtab_bug991111.js]
 [browser_newtab_bug991210.js]
 [browser_newtab_bug998387.js]
 [browser_newtab_disable.js]
 [browser_newtab_drag_drop.js]
-skip-if = os == "win" || (os == 'mac' && os_version == '10.10') # Bug 1152810 - can't do simulateDrop(0,0) on Windows; Bug 1122478 - newtab drag-drop tests fail on OS X 10.10
 [browser_newtab_drag_drop_ext.js]
-skip-if = (os == 'mac' && os_version == '10.10') # bug 1122478 - newtab drag-drop tests fail on OS X 10.10
 [browser_newtab_drop_preview.js]
 [browser_newtab_enhanced.js]
 [browser_newtab_focus.js]
 [browser_newtab_perwindow_private_browsing.js]
 [browser_newtab_reportLinkAction.js]
 [browser_newtab_reflow_load.js]
 support-files =
   content-reflows.js
--- a/browser/base/content/test/newtab/browser_newtab_1188015.js
+++ b/browser/base/content/test/newtab/browser_newtab_1188015.js
@@ -1,28 +1,26 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
-const PRELOAD_PREF = "browser.newtab.preload";
-
 gDirectorySource = "data:application/json," + JSON.stringify({
   "directory": [{
     url: "http://example1.com/",
     enhancedImageURI: "data:image/png;base64,helloWORLD2",
     title: "title1",
     type: "affiliate",
     titleBgColor: "green"
   }]
 });
 
-function runTests() {
-  registerCleanupFunction(() => {
-    Services.prefs.clearUserPref(PRELOAD_PREF);
-  });
-
-  Services.prefs.setBoolPref(PRELOAD_PREF, false);
+add_task(function* () {
+  yield pushPrefs(["browser.newtab.preload", false]);
 
   // Make the page have a directory link
   yield setLinks([]);
-  yield addNewTabPageTab();
-  let titleNode = getCell(0).node.querySelector(".newtab-title");
-  is(titleNode.style.backgroundColor, "green", "title bg color is green");
-}
+  yield* addNewTabPageTab();
+
+  let color = yield performOnCell(0, cell => {
+    return cell.node.querySelector(".newtab-title").style.backgroundColor;
+  });
+
+  is(color, "green", "title bg color is green");
+});
--- a/browser/base/content/test/newtab/browser_newtab_background_captures.js
+++ b/browser/base/content/test/newtab/browser_newtab_background_captures.js
@@ -3,23 +3,22 @@
 
 /**
  * Verifies that hidden, pre-loaded newtabs don't allow background captures, and
  * when unhidden, do allow background captures.
  */
 
 const CAPTURE_PREF = "browser.pagethumbnails.capturing_disabled";
 
-function runTests() {
+add_task(function* () {
   let imports = {};
   Cu.import("resource://gre/modules/PageThumbs.jsm", imports);
 
   // Disable captures.
-  let originalDisabledState = Services.prefs.getBoolPref(CAPTURE_PREF);
-  Services.prefs.setBoolPref(CAPTURE_PREF, true);
+  yield pushPrefs([CAPTURE_PREF, false]);
 
   // Make sure the thumbnail doesn't exist yet.
   let url = "http://example.com/";
   let path = imports.PageThumbsStorage.getFilePathForURL(url);
   let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
   file.initWithPath(path);
   try {
     file.remove(false);
@@ -29,35 +28,37 @@ function runTests() {
   // Add a top site.
   yield setLinks("-1");
 
   // We need a handle to a hidden, pre-loaded newtab so we can verify that it
   // doesn't allow background captures. Ensure we have a preloaded browser.
   gBrowser._createPreloadBrowser();
 
   // Wait for the preloaded browser to load.
-  yield waitForBrowserLoad(gBrowser._preloadedBrowser);
+  if (gBrowser._preloadedBrowser.contentDocument.readyState != "complete") {
+    yield BrowserTestUtils.waitForEvent(gBrowser._preloadedBrowser, "load", true);
+  }
 
   // We're now ready to use the preloaded browser.
   BrowserOpenTab();
   let tab = gBrowser.selectedTab;
-  let doc = tab.linkedBrowser.contentDocument;
+
+  let thumbnailCreatedPromise = new Promise(resolve => {
+    // Showing the preloaded tab should trigger thumbnail capture.
+    Services.obs.addObserver(function onCreate(subj, topic, data) {
+      if (data != url)
+        return;
+      Services.obs.removeObserver(onCreate, "page-thumbnail:create");
+      ok(true, "thumbnail created after preloaded tab was shown");
+
+      resolve();
+    }, "page-thumbnail:create", false);
+  });
 
   // Enable captures.
-  Services.prefs.setBoolPref(CAPTURE_PREF, false);
+  yield pushPrefs([CAPTURE_PREF, false]);
 
-  // Showing the preloaded tab should trigger thumbnail capture.
-  Services.obs.addObserver(function onCreate(subj, topic, data) {
-    if (data != url)
-      return;
-    Services.obs.removeObserver(onCreate, "page-thumbnail:create");
-    ok(true, "thumbnail created after preloaded tab was shown");
+  yield thumbnailCreatedPromise;
 
-    // Test finished!
-    Services.prefs.setBoolPref(CAPTURE_PREF, originalDisabledState);
-    gBrowser.removeTab(tab);
-    file.remove(false);
-    TestRunner.next();
-  }, "page-thumbnail:create", false);
-
-  info("Waiting for thumbnail capture");
-  yield true;
-}
+  // Test finished!
+  gBrowser.removeTab(tab);
+  file.remove(false);
+});
--- a/browser/base/content/test/newtab/browser_newtab_block.js
+++ b/browser/base/content/test/newtab/browser_newtab_block.js
@@ -15,80 +15,81 @@ gDirectorySource = "data:application/jso
     imageURI: "data:image/png;base64,helloWORLD3",
     title: "title",
     type: "affiliate",
     adgroup_name: "test",
     frecent_sites: ["example0.com"]
   }]
 });
 
-function runTests() {
+add_task(function* () {
   let origGetFrecentSitesName = DirectoryLinksProvider.getFrecentSitesName;
   DirectoryLinksProvider.getFrecentSitesName = () => "";
   let origIsTopPlacesSite = NewTabUtils.isTopPlacesSite;
   NewTabUtils.isTopPlacesSite = (site) => false;
 
   // we remove sites and expect the gaps to be filled as long as there still
   // are some sites available
   yield setLinks("0,1,2,3,4,5,6,7,8,9");
   setPinnedLinks("");
 
-  yield addNewTabPageTab();
+  yield* addNewTabPageTab();
   yield customizeNewTabPage("enhanced"); // Toggle enhanced off
-  checkGrid("0,1,2,3,4,5,6,7,8");
+  yield* checkGrid("0,1,2,3,4,5,6,7,8");
 
   yield blockCell(4);
-  checkGrid("0,1,2,3,5,6,7,8,9");
+  yield* checkGrid("0,1,2,3,5,6,7,8,9");
 
   yield blockCell(4);
-  checkGrid("0,1,2,3,6,7,8,9,");
+  yield* checkGrid("0,1,2,3,6,7,8,9,");
 
   yield blockCell(4);
-  checkGrid("0,1,2,3,7,8,9,,");
+  yield* checkGrid("0,1,2,3,7,8,9,,");
 
   // we removed a pinned site
   yield restore();
   yield setLinks("0,1,2,3,4,5,6,7,8");
   setPinnedLinks(",1");
 
-  yield addNewTabPageTab();
-  checkGrid("0,1p,2,3,4,5,6,7,8");
+  yield* addNewTabPageTab();
+  yield* checkGrid("0,1p,2,3,4,5,6,7,8");
 
   yield blockCell(1);
-  checkGrid("0,2,3,4,5,6,7,8,");
+  yield* checkGrid("0,2,3,4,5,6,7,8,");
 
   // we remove the last site on the grid (which is pinned) and expect the gap
   // to be re-filled and the new site to be unpinned
   yield restore();
   yield setLinks("0,1,2,3,4,5,6,7,8,9");
   setPinnedLinks(",,,,,,,,8");
 
-  yield addNewTabPageTab();
-  checkGrid("0,1,2,3,4,5,6,7,8p");
+  yield* addNewTabPageTab();
+  yield* checkGrid("0,1,2,3,4,5,6,7,8p");
 
   yield blockCell(8);
-  checkGrid("0,1,2,3,4,5,6,7,9");
+  yield* checkGrid("0,1,2,3,4,5,6,7,9");
 
   // we remove the first site on the grid with the last one pinned. all cells
   // but the last one should shift to the left and a new site fades in
   yield restore();
   yield setLinks("0,1,2,3,4,5,6,7,8,9");
   setPinnedLinks(",,,,,,,,8");
 
-  yield addNewTabPageTab();
-  checkGrid("0,1,2,3,4,5,6,7,8p");
+  yield* addNewTabPageTab();
+  yield* checkGrid("0,1,2,3,4,5,6,7,8p");
 
   yield blockCell(0);
-  checkGrid("1,2,3,4,5,6,7,9,8p");
+  yield* checkGrid("1,2,3,4,5,6,7,9,8p");
 
   // Test that blocking the targeted site also removes its associated suggested tile
   NewTabUtils.isTopPlacesSite = origIsTopPlacesSite;
   yield restore();
   yield setLinks("0,1,2,3,4,5,6,7,8,9");
   yield customizeNewTabPage("enhanced"); // Toggle enhanced on
-  yield addNewTabPageTab();
-  checkGrid("http://suggested.com/,0,1,2,3,4,5,6,7,8,9");
+  yield* addNewTabPageTab();
+
+  yield* checkGrid("http://suggested.com/,0,1,2,3,4,5,6,7,8,9");
 
   yield blockCell(1);
-  yield addNewTabPageTab();
-  checkGrid("1,2,3,4,5,6,7,8,9");
+  yield* addNewTabPageTab();
+  yield* checkGrid("1,2,3,4,5,6,7,8,9");
   DirectoryLinksProvider.getFrecentSitesName = origGetFrecentSitesName;
-}
+});
--- a/browser/base/content/test/newtab/browser_newtab_bug1145428.js
+++ b/browser/base/content/test/newtab/browser_newtab_bug1145428.js
@@ -15,75 +15,73 @@ gDirectorySource = "data:application/jso
     enhancedImageURI: "data:image/png;base64,helloWORLD2",
     title: "title",
     type: "affiliate",
     adgroup_name: "example",
     frecent_sites: ["example0.com"],
   }]
 });
 
-function runTests() {
+add_task(function* () {
   let origGetFrecentSitesName = DirectoryLinksProvider.getFrecentSitesName;
   DirectoryLinksProvider.getFrecentSitesName = () => "";
 
   function getData(cellNum) {
-    let cell = getCell(cellNum);
-    if (!cell.site)
-      return null;
-    let siteNode = cell.site.node;
-    return {
-      type: siteNode.getAttribute("type"),
-      thumbnail: siteNode.querySelector(".newtab-thumbnail").style.backgroundImage,
-      enhanced: siteNode.querySelector(".enhanced-content").style.backgroundImage,
-      title: siteNode.querySelector(".newtab-title").textContent,
-      suggested: siteNode.getAttribute("suggested"),
-      url: siteNode.querySelector(".newtab-link").getAttribute("href"),
-    };
+    return performOnCell(cellNum, cell => {
+      if (!cell.site)
+        return null;
+      let siteNode = cell.site.node;
+      return {
+        type: siteNode.getAttribute("type"),
+        thumbnail: siteNode.querySelector(".newtab-thumbnail").style.backgroundImage,
+        enhanced: siteNode.querySelector(".enhanced-content").style.backgroundImage,
+        title: siteNode.querySelector(".newtab-title").textContent,
+        suggested: siteNode.getAttribute("suggested"),
+        url: siteNode.querySelector(".newtab-link").getAttribute("href"),
+      };
+    });
   }
 
   yield setLinks("0,1,2,3,4,5,6,7,8,9");
   setPinnedLinks("");
 
-  yield addNewTabPageTab();
+  yield* addNewTabPageTab();
   // load another newtab since the first may not get suggested tile
-  yield addNewTabPageTab();
-  checkGrid("http://example.com/landing/page.html,0,1,2,3,4,5,6,7,8,9");
+  yield* addNewTabPageTab();
+  yield* checkGrid("http://example.com/landing/page.html,0,1,2,3,4,5,6,7,8,9");
   // evaluate suggested tile
-  let tileData = getData(0);
+  let tileData = yield getData(0);
   is(tileData.type, "affiliate", "unpinned type");
   is(tileData.thumbnail, "url(\"data:image/png;base64,helloWORLD3\")", "unpinned thumbnail");
   is(tileData.enhanced, "url(\"data:image/png;base64,helloWORLD2\")", "unpinned enhanced");
   is(tileData.suggested, "true", "has suggested set", "unpinned suggested exists");
   is(tileData.url, "http://example.com/landing/page.html", "unpinned landing page");
 
   // suggested tile should not be pinned
   is(NewTabUtils.pinnedLinks.isPinned({url: "http://example.com/landing/page.html"}), false, "suggested tile is not pinned");
 
   // pin suggested tile
-  whenPagesUpdated();
-  let siteNode = getCell(0).node.querySelector(".newtab-site");
-  let pinButton = siteNode.querySelector(".newtab-control-pin");
-  EventUtils.synthesizeMouseAtCenter(pinButton, {}, getContentWindow());
-  // wait for whenPagesUpdated
-  yield null;
+  let updatedPromise = whenPagesUpdated();
+  yield BrowserTestUtils.synthesizeMouseAtCenter(".newtab-site > .newtab-control-pin", {}, gBrowser.selectedBrowser);
+  yield updatedPromise;
 
   // tile should be pinned and turned into history tile
   is(NewTabUtils.pinnedLinks.isPinned({url: "http://example.com/landing/page.html"}), true, "suggested tile is pinned");
-  tileData = getData(0);
+  tileData = yield getData(0);
   is(tileData.type, "history", "pinned type");
   is(tileData.suggested, null, "no suggested attribute");
   is(tileData.url, "http://example.com/landing/page.html", "original landing page");
 
   // set pinned tile endTime into past and reload the page
   NewTabUtils.pinnedLinks._links[0].endTime = Date.now() - 1000;
-  yield addNewTabPageTab();
+  yield* addNewTabPageTab();
 
   // check that url is reset to base domain and thumbnail points to moz-page-thumb service
   is(NewTabUtils.pinnedLinks.isPinned({url: "http://example.com/"}), true, "baseDomain url is pinned");
-  tileData = getData(0);
+  tileData = yield getData(0);
   is(tileData.type, "history", "type is history");
   is(tileData.title, "example.com", "title changed to baseDomain");
   is(tileData.thumbnail.indexOf("moz-page-thumb") != -1, true, "thumbnail contains moz-page-thumb");
   is(tileData.enhanced, "", "no enhanced image");
   is(tileData.url, "http://example.com/", "url points to baseDomian");
 
   DirectoryLinksProvider.getFrecentSitesName = origGetFrecentSitesName;
-}
+});
--- a/browser/base/content/test/newtab/browser_newtab_bug1178586.js
+++ b/browser/base/content/test/newtab/browser_newtab_bug1178586.js
@@ -13,74 +13,71 @@ gDirectorySource = "data:application/jso
     enhancedImageURI: "data:image/png;base64,helloWORLD2",
     title: "title",
     type: "affiliate",
     adgroup_name: "example",
     frecent_sites: ["example0.com"],
   }]
 });
 
-function runTests() {
+add_task(function* () {
   let origGetFrecentSitesName = DirectoryLinksProvider.getFrecentSitesName;
   DirectoryLinksProvider.getFrecentSitesName = () => "";
 
   function getData(cellNum) {
-    let cell = getCell(cellNum);
-    if (!cell.site)
-      return null;
-    let siteNode = cell.site.node;
-    return {
-      type: siteNode.getAttribute("type"),
-      thumbnail: siteNode.querySelector(".newtab-thumbnail").style.backgroundImage,
-      enhanced: siteNode.querySelector(".enhanced-content").style.backgroundImage,
-      title: siteNode.querySelector(".newtab-title").textContent,
-      suggested: siteNode.getAttribute("suggested"),
-      url: siteNode.querySelector(".newtab-link").getAttribute("href"),
-    };
+    return performOnCell(cellNum, cell => {
+      if (!cell.site)
+        return null;
+      let siteNode = cell.site.node;
+      return {
+        type: siteNode.getAttribute("type"),
+        thumbnail: siteNode.querySelector(".newtab-thumbnail").style.backgroundImage,
+        enhanced: siteNode.querySelector(".enhanced-content").style.backgroundImage,
+        title: siteNode.querySelector(".newtab-title").textContent,
+        suggested: siteNode.getAttribute("suggested"),
+        url: siteNode.querySelector(".newtab-link").getAttribute("href"),
+      };
+    });
   }
 
   yield setLinks("0,1,2,3,4,5,6,7,8,9");
   setPinnedLinks("");
 
-  yield addNewTabPageTab();
+  yield* addNewTabPageTab();
   // load another newtab since the first may not get suggested tile
-  yield addNewTabPageTab();
-  checkGrid("http://example.com/hardlanding/page.html,0,1,2,3,4,5,6,7,8,9");
+  yield* addNewTabPageTab();
+  yield* checkGrid("http://example.com/hardlanding/page.html,0,1,2,3,4,5,6,7,8,9");
   // evaluate suggested tile
-  let tileData = getData(0);
+  let tileData = yield getData(0);
   is(tileData.type, "affiliate", "unpinned type");
   is(tileData.thumbnail, "url(\"data:image/png;base64,helloWORLD3\")", "unpinned thumbnail");
   is(tileData.enhanced, "url(\"data:image/png;base64,helloWORLD2\")", "unpinned enhanced");
   is(tileData.suggested, "true", "has suggested set", "unpinned suggested exists");
   is(tileData.url, "http://example.com/hardlanding/page.html", "unpinned landing page");
 
   // suggested tile should not be pinned
   is(NewTabUtils.pinnedLinks.isPinned({url: "http://example.com/hardlanding/page.html"}), false, "suggested tile is not pinned");
 
   // pin suggested tile
-  whenPagesUpdated();
-  let siteNode = getCell(0).node.querySelector(".newtab-site");
-  let pinButton = siteNode.querySelector(".newtab-control-pin");
-  EventUtils.synthesizeMouseAtCenter(pinButton, {}, getContentWindow());
-  // wait for whenPagesUpdated
-  yield null;
+  let updatedPromise = whenPagesUpdated();
+  yield BrowserTestUtils.synthesizeMouseAtCenter(".newtab-site > .newtab-control-pin", {}, gBrowser.selectedBrowser);
+  yield updatedPromise;
 
   // tile should be pinned and turned into history tile
   is(NewTabUtils.pinnedLinks.isPinned({url: "http://example.com/hardlanding/page.html"}), true, "suggested tile is pinned");
-  tileData = getData(0);
+  tileData = yield getData(0);
   is(tileData.type, "history", "pinned type");
   is(tileData.suggested, null, "no suggested attribute");
   is(tileData.url, "http://example.com/hardlanding/page.html", "original landing page");
 
   // click the pinned tile
-  siteNode = getCell(0).node.querySelector(".newtab-site");
-  EventUtils.synthesizeMouseAtCenter(siteNode, {}, getContentWindow());
+  yield BrowserTestUtils.synthesizeMouseAtCenter(".newtab-site", {}, gBrowser.selectedBrowser);
   // add new page twice to avoid using cached version
-  yield addNewTabPageTab();
-  yield addNewTabPageTab();
+  yield* addNewTabPageTab();
+  yield* addNewTabPageTab();
 
   // check that type and suggested did not change
-  tileData = getData(0);
+  tileData = yield getData(0);
   is(tileData.type, "history", "pinned type");
   is(tileData.suggested, null, "no suggested attribute");
 
   DirectoryLinksProvider.getFrecentSitesName = origGetFrecentSitesName;
-}
+});
--- a/browser/base/content/test/newtab/browser_newtab_bug1194895.js
+++ b/browser/base/content/test/newtab/browser_newtab_bug1194895.js
@@ -20,102 +20,108 @@ function populateDirectoryTiles() {
   return directoryTiles;
 }
 
 gDirectorySource = "data:application/json," + JSON.stringify({
   "directory": populateDirectoryTiles()
 });
 
 
-function runTests() {
+add_task(function* () {
   requestLongerTimeout(4);
   let origEnhanced = NewTabUtils.allPages.enhanced;
   let origCompareLinks = NewTabUtils.links.compareLinks;
   registerCleanupFunction(() => {
-    Services.prefs.clearUserPref(PRELOAD_PREF);
-    Services.prefs.clearUserPref(PREF_NEWTAB_ROWS);
-    Services.prefs.clearUserPref(PREF_NEWTAB_COLUMNS);
     NewTabUtils.allPages.enhanced = origEnhanced;
     NewTabUtils.links.compareLinks = origCompareLinks;
   });
 
   // turn off preload to ensure grid updates on every setLinks
-  Services.prefs.setBoolPref(PRELOAD_PREF, false);
+  yield pushPrefs([PRELOAD_PREF, false]);
   // set newtab to have three columns only
-  Services.prefs.setIntPref(PREF_NEWTAB_COLUMNS, 3);
-  Services.prefs.setIntPref(PREF_NEWTAB_ROWS, 5);
+  yield pushPrefs([PREF_NEWTAB_COLUMNS, 3]);
+  yield pushPrefs([PREF_NEWTAB_ROWS, 5]);
 
-  yield addNewTabPageTab();
+  yield* addNewTabPageTab();
   yield customizeNewTabPage("enhanced"); // Toggle enhanced off
 
   // Testing history tiles
 
   // two rows of tiles should always fit on any screen
   yield setLinks("0,1,2,3,4,5");
-  yield addNewTabPageTab();
+  yield* addNewTabPageTab();
 
   // should do not see scrollbar since tiles fit into visible space
-  checkGrid("0,1,2,3,4,5");
-  ok(!hasScrollbar(), "no scrollbar");
+  yield* checkGrid("0,1,2,3,4,5");
+  let scrolling = yield hasScrollbar();
+  ok(!scrolling, "no scrollbar");
 
   // add enough tiles to cause extra two rows and observe scrollbar
   yield setLinks("0,1,2,3,4,5,6,7,8,9");
-  yield addNewTabPageTab();
-  checkGrid("0,1,2,3,4,5,6,7,8,9");
-  ok(hasScrollbar(), "document has scrollbar");
+  yield* addNewTabPageTab();
+  yield* checkGrid("0,1,2,3,4,5,6,7,8,9");
+  scrolling = yield hasScrollbar();
+  ok(scrolling, "document has scrollbar");
 
   // pin the last tile to make it stay at the bottom of the newtab
-  pinCell(9);
+  yield pinCell(9);
   // block first 6 tiles, which should not remove the scroll bar
   // since the last tile is pinned in the nineth position
   for (let i = 0; i < 6; i++) {
     yield blockCell(0);
   }
-  yield addNewTabPageTab();
-  checkGrid("6,7,8,,,,,,,9p");
-  ok(hasScrollbar(), "document has scrollbar when tile is pinned to the last row");
+  yield* addNewTabPageTab();
+  yield* checkGrid("6,7,8,,,,,,,9p");
+  scrolling = yield hasScrollbar();
+  ok(scrolling, "document has scrollbar when tile is pinned to the last row");
 
   // unpin the site: this will move tile up and make scrollbar disappear
   yield unpinCell(9);
-  yield addNewTabPageTab();
-  checkGrid("6,7,8,9");
-  ok(!hasScrollbar(), "no scrollbar when bottom row tile is unpinned");
+  yield* addNewTabPageTab();
+  yield* checkGrid("6,7,8,9");
+  scrolling = yield hasScrollbar();
+  ok(!scrolling, "no scrollbar when bottom row tile is unpinned");
 
   // reset everything to clean slate
   NewTabUtils.restore();
 
   // Testing directory tiles
   yield customizeNewTabPage("enhanced"); // Toggle enhanced on
 
   // setup page with no history tiles to test directory only display
   yield setLinks([]);
-  yield addNewTabPageTab();
-  ok(!hasScrollbar(), "no scrollbar for directory tiles");
+  yield* addNewTabPageTab();
+  ok(!scrolling, "no scrollbar for directory tiles");
 
   // introduce one history tile - it should occupy the last
   // available slot at the bottom of newtab and cause scrollbar
   yield setLinks("41");
-  yield addNewTabPageTab();
-  ok(hasScrollbar(), "adding low frecency history site causes scrollbar");
+  yield* addNewTabPageTab();
+  scrolling = yield hasScrollbar();
+  ok(scrolling, "adding low frecency history site causes scrollbar");
 
   // set PREF_NEWTAB_ROWS to 4, that should clip off the history tile
   // and remove scroll bar
-  Services.prefs.setIntPref(PREF_NEWTAB_ROWS, 4);
-  yield addNewTabPageTab();
-  ok(!hasScrollbar(), "no scrollbar if history tiles falls past max rows");
+  yield pushPrefs([PREF_NEWTAB_ROWS, 4]);
+  yield* addNewTabPageTab();
+
+  scrolling = yield hasScrollbar();
+  ok(!scrolling, "no scrollbar if history tiles falls past max rows");
 
   // restore max rows and watch scrollbar re-appear
-  Services.prefs.setIntPref(PREF_NEWTAB_ROWS, 5);
-  yield addNewTabPageTab();
-  ok(hasScrollbar(), "scrollbar is back when max rows allow for bottom history tile");
+  yield pushPrefs([PREF_NEWTAB_ROWS, 5]);
+  yield* addNewTabPageTab();
+  scrolling = yield hasScrollbar();
+  ok(scrolling, "scrollbar is back when max rows allow for bottom history tile");
 
   // block that history tile, and watch scrollbar disappear
   yield blockCell(14);
-  yield addNewTabPageTab();
-  ok(!hasScrollbar(), "no scrollbar after bottom history tiles is blocked");
+  yield* addNewTabPageTab();
+  scrolling = yield hasScrollbar();
+  ok(!scrolling, "no scrollbar after bottom history tiles is blocked");
 
   // Test well-populated user history - newtab has highly-frecent history sites
   // redefine compareLinks to always choose history tiles first
   NewTabUtils.links.compareLinks = function (aLink1, aLink2) {
     if (aLink1.type == aLink2.type) {
       return aLink2.frecency - aLink1.frecency ||
              aLink2.lastVisitDate - aLink1.lastVisitDate;
     }
@@ -126,17 +132,19 @@ function runTests() {
       else {
         return -1;
       }
     }
   };
 
   // add a row of history tiles, directory tiles will be clipped off, hence no scrollbar
   yield setLinks("31,32,33");
-  yield addNewTabPageTab();
-  ok(!hasScrollbar(), "no scrollbar when directory tiles follow history tiles");
+  yield* addNewTabPageTab();
+  scrolling = yield hasScrollbar();
+  ok(!scrolling, "no scrollbar when directory tiles follow history tiles");
 
   // fill first four rows with history tiles and observer scrollbar
   yield setLinks("30,31,32,33,34,35,36,37,38,39");
-  yield addNewTabPageTab();
-  ok(hasScrollbar(), "scrollbar appears when history tiles need extra row");
+  yield* addNewTabPageTab();
+  scrolling = yield hasScrollbar();
+  ok(scrolling, "scrollbar appears when history tiles need extra row");
+});
 
-}
--- a/browser/base/content/test/newtab/browser_newtab_bug721442.js
+++ b/browser/base/content/test/newtab/browser_newtab_bug721442.js
@@ -1,23 +1,28 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
-function runTests() {
+add_task(function* () {
   yield setLinks("0,1,2,3,4,5,6,7,8");
   setPinnedLinks([
     {url: "http://example7.com/", title: ""},
     {url: "http://example8.com/", title: "title"},
     {url: "http://example9.com/", title: "http://example9.com/"}
   ]);
 
-  yield addNewTabPageTab();
-  checkGrid("7p,8p,9p,0,1,2,3,4,5");
+  yield* addNewTabPageTab();
+  yield* checkGrid("7p,8p,9p,0,1,2,3,4,5");
+
+  yield ContentTask.spawn(gBrowser.selectedBrowser, {}, function* () {
+    function checkTooltip(aIndex, aExpected, aMessage) {
+      let cell = content.gGrid.cells[aIndex];
 
-  checkTooltip(0, "http://example7.com/", "1st tooltip is correct");
-  checkTooltip(1, "title\nhttp://example8.com/", "2nd tooltip is correct");
-  checkTooltip(2, "http://example9.com/", "3rd tooltip is correct");
-}
+      let link = cell.node.querySelector(".newtab-link");
+      is(link.getAttribute("title"), aExpected, aMessage);
+    }
 
-function checkTooltip(aIndex, aExpected, aMessage) {
-  let link = getCell(aIndex).node.querySelector(".newtab-link");
-  is(link.getAttribute("title"), aExpected, aMessage);
-}
+    checkTooltip(0, "http://example7.com/", "1st tooltip is correct");
+    checkTooltip(1, "title\nhttp://example8.com/", "2nd tooltip is correct");
+    checkTooltip(2, "http://example9.com/", "3rd tooltip is correct");
+  });
+});
+
--- a/browser/base/content/test/newtab/browser_newtab_bug722273.js
+++ b/browser/base/content/test/newtab/browser_newtab_bug722273.js
@@ -6,34 +6,30 @@ const URL = "http://fake-site.com/";
 
 var tmp = {};
 Cc["@mozilla.org/moz/jssubscript-loader;1"]
   .getService(Ci.mozIJSSubScriptLoader)
   .loadSubScript("chrome://browser/content/sanitize.js", tmp);
 
 var {Sanitizer} = tmp;
 
-add_task(function*() {
+add_task(function* () {
   yield promiseSanitizeHistory();
   yield promiseAddFakeVisits();
-  yield addNewTabPageTabPromise();
-  is(getCell(0).site.url, URL, "first site is our fake site");
+  yield* addNewTabPageTab();
 
-  whenPagesUpdated(() => {});
-  yield promiseSanitizeHistory();
+  let cellUrl = yield performOnCell(0, cell => { return cell.site.url; });
+  is(cellUrl, URL, "first site is our fake site");
 
-  // Now wait until the grid is updated
-  while (true) {
-    if (!getCell(0).site) {
-      break;
-    }
-    info("the fake site is still present");
-    yield new Promise(resolve => setTimeout(resolve, 1000));
-  }
-  ok(!getCell(0).site, "fake site is gone");
+  let updatedPromise = whenPagesUpdated();
+  yield promiseSanitizeHistory();
+  yield updatedPromise;
+
+  let isGone = yield performOnCell(0, cell => { return cell.site == null; });
+  ok(isGone, "fake site is gone");
 });
 
 function promiseAddFakeVisits() {
   let visits = [];
   for (let i = 59; i > 0; i--) {
     visits.push({
       visitDate: NOW - i * 60 * 1000000,
       transitionType: Ci.nsINavHistoryService.TRANSITION_LINK
--- a/browser/base/content/test/newtab/browser_newtab_bug723102.js
+++ b/browser/base/content/test/newtab/browser_newtab_bug723102.js
@@ -1,19 +1,25 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
-function runTests() {
+add_task(function* () {
   // create a new tab page and hide it.
   yield setLinks("0,1,2,3,4,5,6,7,8");
   setPinnedLinks("");
 
-  yield addNewTabPageTab();
+  yield* addNewTabPageTab();
   let firstTab = gBrowser.selectedTab;
 
-  yield addNewTabPageTab();
-  gBrowser.removeTab(firstTab);
+  yield* addNewTabPageTab();
+  yield BrowserTestUtils.removeTab(firstTab);
 
   ok(NewTabUtils.allPages.enabled, "page is enabled");
   NewTabUtils.allPages.enabled = false;
-  ok(getGrid().node.hasAttribute("page-disabled"), "page is disabled");
+
+  let disabled = yield ContentTask.spawn(gBrowser.selectedBrowser, {}, function* () {
+    return content.gGrid.node.hasAttribute("page-disabled");
+  });
+  ok(disabled, "page is disabled");
+
   NewTabUtils.allPages.enabled = true;
-}
+});
+
--- a/browser/base/content/test/newtab/browser_newtab_bug723121.js
+++ b/browser/base/content/test/newtab/browser_newtab_bug723121.js
@@ -1,30 +1,42 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
-function runTests() {
+add_task(function* () {
   yield setLinks("0,1,2,3,4,5,6,7,8");
   setPinnedLinks("");
 
-  yield addNewTabPageTab();
-  checkGridLocked(false, "grid is unlocked");
+  yield* addNewTabPageTab();
 
-  let cell = getCell(0).node;
-  let site = getCell(0).site.node;
-  let link = site.querySelector(".newtab-link");
+  yield ContentTask.spawn(gBrowser.selectedBrowser, {}, function*() {
+    let grid = content.gGrid;
+    let cell = grid.cells[0];
+    let site = cell.site.node;
+    let link = site.querySelector(".newtab-link");
 
-  sendDragEvent("dragstart", link);
-  checkGridLocked(true, "grid is now locked");
+    function checkGridLocked(aLocked, aMessage) {
+      is(grid.node.hasAttribute("locked"), aLocked, aMessage);
+    }
 
-  sendDragEvent("dragend", link);
-  checkGridLocked(false, "grid isn't locked anymore");
+    function sendDragEvent(aEventType, aTarget) {
+      let dataTransfer = new content.DataTransfer(aEventType, false);
+      let event = content.document.createEvent("DragEvents");
+      event.initDragEvent(aEventType, true, true, content, 0, 0, 0, 0, 0,
+                          false, false, false, false, 0, null, dataTransfer);
+      aTarget.dispatchEvent(event);
+    }
 
-  sendDragEvent("dragstart", cell);
-  checkGridLocked(false, "grid isn't locked - dragstart was ignored");
+    checkGridLocked(false, "grid is unlocked");
 
-  sendDragEvent("dragstart", site);
-  checkGridLocked(false, "grid isn't locked - dragstart was ignored");
-}
+    sendDragEvent("dragstart", link);
+    checkGridLocked(true, "grid is now locked");
+
+    sendDragEvent("dragend", link);
+    checkGridLocked(false, "grid isn't locked anymore");
 
-function checkGridLocked(aLocked, aMessage) {
-  is(getGrid().node.hasAttribute("locked"), aLocked, aMessage);
-}
+    sendDragEvent("dragstart", cell.node);
+    checkGridLocked(false, "grid isn't locked - dragstart was ignored");
+
+    sendDragEvent("dragstart", site);
+    checkGridLocked(false, "grid isn't locked - dragstart was ignored");
+  });
+});
--- a/browser/base/content/test/newtab/browser_newtab_bug725996.js
+++ b/browser/base/content/test/newtab/browser_newtab_bug725996.js
@@ -1,23 +1,35 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
-function runTests() {
+add_task(function* () {
   yield setLinks("0,1,2,3,4,5,6,7,8");
   setPinnedLinks("");
 
-  yield addNewTabPageTab();
-  checkGrid("0,1,2,3,4,5,6,7,8");
+  yield* addNewTabPageTab();
+  yield* checkGrid("0,1,2,3,4,5,6,7,8");
 
-  let cell = getCell(0).node;
+  function doDrop(data) {
+    return ContentTask.spawn(gBrowser.selectedBrowser, { data: data }, function*(args) {
+      let dataTransfer = new content.DataTransfer("dragstart", false);
+      dataTransfer.mozSetDataAt("text/x-moz-url", args.data, 0);
+      let event = content.document.createEvent("DragEvents");
+      event.initDragEvent("drop", true, true, content, 0, 0, 0, 0, 0,
+                          false, false, false, false, 0, null, dataTransfer);
 
-  sendDragEvent("drop", cell, "http://example99.com/\nblank");
+      let target = content.gGrid.cells[0].node;
+      target.dispatchEvent(event);
+    });
+  }
+
+  yield doDrop("http://example99.com/\nblank");
   is(NewTabUtils.pinnedLinks.links[0].url, "http://example99.com/",
      "first cell is pinned and contains the dropped site");
 
   yield whenPagesUpdated();
-  checkGrid("99p,0,1,2,3,4,5,6,7");
+  yield* checkGrid("99p,0,1,2,3,4,5,6,7");
 
-  sendDragEvent("drop", cell, "");
+  yield doDrop("");
   is(NewTabUtils.pinnedLinks.links[0].url, "http://example99.com/",
      "first cell is still pinned with the site we dropped before");
-}
+});
+
--- a/browser/base/content/test/newtab/browser_newtab_bug734043.js
+++ b/browser/base/content/test/newtab/browser_newtab_bug734043.js
@@ -1,27 +1,34 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
-function runTests() {
+add_task(function* () {
   yield setLinks("0,1,2,3,4,5,6,7,8");
   setPinnedLinks("");
 
-  yield addNewTabPageTab();
-  checkGrid("0,1,2,3,4,5,6,7,8");
+  yield* addNewTabPageTab();
+  yield* checkGrid("0,1,2,3,4,5,6,7,8");
+
+  yield ContentTask.spawn(gBrowser.selectedBrowser, {}, function* () {
+    content.addEventListener("error", function () {
+      sendAsyncMessage("test:newtab-error", {});
+    });
+  });
 
   let receivedError = false;
-  let block = getContentDocument().querySelector(".newtab-control-block");
+  let mm = gBrowser.selectedBrowser.messageManager;
+  mm.addMessageListener("test:newtab-error", function onResponse(message) {
+    mm.removeMessageListener("test:newtab-error", onResponse);
+    ok(false, "Error event happened");
+    receivedError = true;
+  });
 
-  function onError() {
-    receivedError = true;
+  let pagesUpdatedPromise = whenPagesUpdated();
+
+  for (let i = 0; i < 3; i++) {
+    yield BrowserTestUtils.synthesizeMouseAtCenter(".newtab-control-block", {}, gBrowser.selectedBrowser);
   }
 
-  let cw = getContentWindow();
-  cw.addEventListener("error", onError);
+  yield pagesUpdatedPromise;
 
-  for (let i = 0; i < 3; i++)
-    EventUtils.synthesizeMouseAtCenter(block, {}, cw);
-
-  yield whenPagesUpdated();
   ok(!receivedError, "we got here without any errors");
-  cw.removeEventListener("error", onError);
-}
+});
--- a/browser/base/content/test/newtab/browser_newtab_bug735987.js
+++ b/browser/base/content/test/newtab/browser_newtab_bug735987.js
@@ -1,32 +1,32 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
-function runTests() {
+add_task(function* () {
   yield setLinks("0,1,2,3,4,5,6,7,8");
   setPinnedLinks("");
 
-  yield addNewTabPageTab();
-  checkGrid("0,1,2,3,4,5,6,7,8");
+  yield* addNewTabPageTab();
+  yield* checkGrid("0,1,2,3,4,5,6,7,8");
 
-  yield simulateExternalDrop(1);
-  checkGrid("0,99p,1,2,3,4,5,6,7");
+  yield* simulateExternalDrop(1);
+  yield* checkGrid("0,99p,1,2,3,4,5,6,7");
 
   yield blockCell(1);
-  checkGrid("0,1,2,3,4,5,6,7,8");
+  yield* checkGrid("0,1,2,3,4,5,6,7,8");
 
-  yield simulateExternalDrop(1);
-  checkGrid("0,99p,1,2,3,4,5,6,7");
+  yield* simulateExternalDrop(1);
+  yield* checkGrid("0,99p,1,2,3,4,5,6,7");
 
   // Simulate a restart and force the next about:newtab
   // instance to read its data from the storage again.
   NewTabUtils.blockedLinks.resetCache();
 
   // Update all open pages, e.g. preloaded ones.
   NewTabUtils.allPages.update();
 
-  yield addNewTabPageTab();
-  checkGrid("0,99p,1,2,3,4,5,6,7");
+  yield* addNewTabPageTab();
+  yield* checkGrid("0,99p,1,2,3,4,5,6,7");
 
   yield blockCell(1);
-  checkGrid("0,1,2,3,4,5,6,7,8");
-}
+  yield* checkGrid("0,1,2,3,4,5,6,7,8");
+});
--- a/browser/base/content/test/newtab/browser_newtab_bug752841.js
+++ b/browser/base/content/test/newtab/browser_newtab_bug752841.js
@@ -1,57 +1,56 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 const PREF_NEWTAB_ROWS = "browser.newtabpage.rows";
 const PREF_NEWTAB_COLUMNS = "browser.newtabpage.columns";
 
-function runTests() {
+function getCellsCount()
+{
+  return ContentTask.spawn(gBrowser.selectedBrowser, {}, function* () {
+    return content.gGrid.cells.length;
+  });
+}
+
+add_task(function* () {
   let testValues = [
     {row: 0, column: 0},
     {row: -1, column: -1},
     {row: -1, column: 0},
     {row: 0, column: -1},
     {row: 2, column: 4},
     {row: 2, column: 5},
   ];
 
   // Expected length of grid
   let expectedValues = [1, 1, 1, 1, 8, 10];
 
    // Values before setting new pref values (15 is the default value -> 5 x 3)
   let previousValues = [15, 1, 1, 1, 1, 8];
 
-  let existingTab, existingTabGridLength, newTab, newTabGridLength;
-  yield addNewTabPageTab();
-  existingTab = gBrowser.selectedTab;
+  yield* addNewTabPageTab();
+  let existingTab = gBrowser.selectedTab;
 
   for (let i = 0; i < expectedValues.length; i++) {
-    gBrowser.selectedTab = existingTab;
-    existingTabGridLength = getGrid().cells.length;
+    let existingTabGridLength = yield getCellsCount();
     is(existingTabGridLength, previousValues[i],
       "Grid length of existing page before update is correctly.");
 
-    Services.prefs.setIntPref(PREF_NEWTAB_ROWS, testValues[i].row);
-    Services.prefs.setIntPref(PREF_NEWTAB_COLUMNS, testValues[i].column);
+    yield pushPrefs([PREF_NEWTAB_ROWS, testValues[i].row]);
+    yield pushPrefs([PREF_NEWTAB_COLUMNS, testValues[i].column]);
 
-    existingTabGridLength = getGrid().cells.length;
+    existingTabGridLength = yield getCellsCount();
     is(existingTabGridLength, expectedValues[i],
       "Existing page grid is updated correctly.");
 
-    yield addNewTabPageTab();
-    newTab = gBrowser.selectedTab;
-    newTabGridLength = getGrid().cells.length;
+    yield* addNewTabPageTab();
+    let newTab = gBrowser.selectedTab;
+    let newTabGridLength = yield getCellsCount();
     is(newTabGridLength, expectedValues[i],
       "New page grid is updated correctly.");
 
-    gBrowser.removeTab(newTab);
-
-    // Wait until the original tab is visible again.
-    let doc = existingTab.linkedBrowser.contentDocument;
-    yield waitForCondition(() => !doc.hidden).then(TestRunner.next);
+    yield BrowserTestUtils.removeTab(newTab);
   }
 
   gBrowser.removeTab(existingTab);
+});
 
-  Services.prefs.clearUserPref(PREF_NEWTAB_ROWS);
-  Services.prefs.clearUserPref(PREF_NEWTAB_COLUMNS);
-}
--- a/browser/base/content/test/newtab/browser_newtab_bug765628.js
+++ b/browser/base/content/test/newtab/browser_newtab_bug765628.js
@@ -1,27 +1,32 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
-const BAD_DRAG_DATA = "javascript:alert('h4ck0rz');\nbad stuff";
-const GOOD_DRAG_DATA = "http://example99.com/\nsite 99";
-
-function runTests() {
+add_task(function* () {
   yield setLinks("0,1,2,3,4,5,6,7,8");
   setPinnedLinks("");
 
-  yield addNewTabPageTab();
-  checkGrid("0,1,2,3,4,5,6,7,8");
+  yield* addNewTabPageTab();
+  yield checkGrid("0,1,2,3,4,5,6,7,8");
+
+  yield ContentTask.spawn(gBrowser.selectedBrowser, {}, function*() {
+    const BAD_DRAG_DATA = "javascript:alert('h4ck0rz');\nbad stuff";
+    const GOOD_DRAG_DATA = "http://example99.com/\nsite 99";
 
-  sendDropEvent(0, BAD_DRAG_DATA);
-  sendDropEvent(1, GOOD_DRAG_DATA);
+    function sendDropEvent(aCellIndex, aDragData) {
+      let dataTransfer = new content.DataTransfer("dragstart", false);
+      dataTransfer.mozSetDataAt("text/x-moz-url", aDragData, 0);
+      let event = content.document.createEvent("DragEvents");
+      event.initDragEvent("drop", true, true, content, 0, 0, 0, 0, 0,
+                          false, false, false, false, 0, null, dataTransfer);
+
+      let target = content.gGrid.cells[aCellIndex].node;
+      target.dispatchEvent(event);
+    }
+
+    sendDropEvent(0, BAD_DRAG_DATA);
+    sendDropEvent(1, GOOD_DRAG_DATA);
+  });
 
   yield whenPagesUpdated();
-  checkGrid("0,99p,1,2,3,4,5,6,7");
-}
-
-function sendDropEvent(aCellIndex, aDragData) {
-  let ifaceReq = getContentWindow().QueryInterface(Ci.nsIInterfaceRequestor);
-  let windowUtils = ifaceReq.getInterface(Ci.nsIDOMWindowUtils);
-
-  let event = createDragEvent("drop", aDragData);
-  windowUtils.dispatchDOMEventViaPresShell(getCell(aCellIndex).node, event, true);
-}
+  yield* checkGrid("0,99p,1,2,3,4,5,6,7");
+});
--- a/browser/base/content/test/newtab/browser_newtab_bug876313.js
+++ b/browser/base/content/test/newtab/browser_newtab_bug876313.js
@@ -1,24 +1,24 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 /*
  * This test makes sure that the changes made by unpinning
  * a site are actually written to NewTabUtils' storage.
  */
-function runTests() {
+add_task(function* () {
   // Second cell is pinned with page #99.
   yield setLinks("0,1,2,3,4,5,6,7,8");
   setPinnedLinks(",99");
 
-  yield addNewTabPageTab();
-  checkGrid("0,99p,1,2,3,4,5,6,7");
+  yield* addNewTabPageTab();
+  yield* checkGrid("0,99p,1,2,3,4,5,6,7");
 
   // Unpin the second cell's site.
   yield unpinCell(1);
-  checkGrid("0,1,2,3,4,5,6,7,8");
+  yield* checkGrid("0,1,2,3,4,5,6,7,8");
 
   // Clear the pinned cache to force NewTabUtils to read the pref again.
   NewTabUtils.pinnedLinks.resetCache();
   NewTabUtils.allPages.update();
-  checkGrid("0,1,2,3,4,5,6,7,8");
-}
+  yield* checkGrid("0,1,2,3,4,5,6,7,8");
+});
--- a/browser/base/content/test/newtab/browser_newtab_bug991111.js
+++ b/browser/base/content/test/newtab/browser_newtab_bug991111.js
@@ -1,26 +1,36 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
-const PREF_NEWTAB_ROWS = "browser.newtabpage.rows";
-
-function runTests() {
+add_task(function* () {
   // set max rows to 1, to avoid scroll events by clicking middle button
-  Services.prefs.setIntPref(PREF_NEWTAB_ROWS, 1);
+  yield pushPrefs(["browser.newtabpage.rows", 1]);
   yield setLinks("-1");
-  yield addNewTabPageTab();
+  yield* addNewTabPageTab();
   // we need a second newtab to honor max rows
-  yield addNewTabPageTab();
+  yield* addNewTabPageTab();
+
+  yield ContentTask.spawn(gBrowser.selectedBrowser, {index: 0}, function* (args) {
+    let {site} = content.wrappedJSObject.gGrid.cells[args.index];
 
-  // Remember if the click handler was triggered
-  let cell = getCell(0);
-  let clicked = false;
-  cell.site.onClick = e => {
-    clicked = true;
-    executeSoon(TestRunner.next);
-  };
+    let origOnClick = site.onClick;
+    let clicked = false;
+    site.onClick = e => {
+      origOnClick.call(site, e);
+      sendAsyncMessage("test:clicked-on-cell", {});
+    };
+  });
+
+  let mm = gBrowser.selectedBrowser.messageManager;
+  let messagePromise = new Promise(resolve => {
+    mm.addMessageListener("test:clicked-on-cell", function onResponse(message) {
+      mm.removeMessageListener("test:clicked-on-cell", onResponse);
+      resolve();
+    });
+  });
 
   // Send a middle-click and make sure it happened
-  yield EventUtils.synthesizeMouseAtCenter(cell.node, {button: 1}, getContentWindow());
-  ok(clicked, "middle click triggered click listener");
-  Services.prefs.clearUserPref(PREF_NEWTAB_ROWS);
-}
+  yield BrowserTestUtils.synthesizeMouseAtCenter(".newtab-cell",
+                                                 {button: 1}, gBrowser.selectedBrowser);
+  yield messagePromise;
+  ok(true, "middle click triggered click listener");
+});
--- a/browser/base/content/test/newtab/browser_newtab_bug991210.js
+++ b/browser/base/content/test/newtab/browser_newtab_bug991210.js
@@ -1,41 +1,35 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
-const PRELOAD_PREF = "browser.newtab.preload";
-
-function runTests() {
+add_task(function* () {
   // turn off preload to ensure that a newtab page loads
-  Services.prefs.setBoolPref(PRELOAD_PREF, false);
+  yield pushPrefs(["browser.newtab.preload", false]);
 
   // add a test provider that waits for load
   let afterLoadProvider = {
     getLinks: function(callback) {
       this.callback = callback;
     },
     addObserver: function() {},
   };
   NewTabUtils.links.addProvider(afterLoadProvider);
 
   // wait until about:newtab loads before calling provider callback
-  addNewTabPageTab();
-  let browser = gWindow.gBrowser.selectedBrowser;
-  yield browser.addEventListener("load", function onLoad() {
-    browser.removeEventListener("load", onLoad, true);
-    // afterLoadProvider.callback has to be called asynchronously to make grid
-    // initilize after "load" event was handled
-    executeSoon(() => afterLoadProvider.callback([]));
-  }, true);
+  let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, "about:newtab");
+
+  afterLoadProvider.callback([]);
 
-  let {_cellMargin, _cellHeight, _cellWidth, node} = getGrid();
-  isnot(_cellMargin, null, "grid has a computed cell margin");
-  isnot(_cellHeight, null, "grid has a computed cell height");
-  isnot(_cellWidth, null, "grid has a computed cell width");
-  let {height, maxHeight, maxWidth} = node.style;
-  isnot(height, "", "grid has a computed grid height");
-  isnot(maxHeight, "", "grid has a computed grid max-height");
-  isnot(maxWidth, "", "grid has a computed grid max-width");
+  yield ContentTask.spawn(gBrowser.selectedBrowser, {}, function* () {
+    let {_cellMargin, _cellHeight, _cellWidth, node} = content.gGrid;
+    isnot(_cellMargin, null, "grid has a computed cell margin");
+    isnot(_cellHeight, null, "grid has a computed cell height");
+    isnot(_cellWidth, null, "grid has a computed cell width");
+    let {height, maxHeight, maxWidth} = node.style;
+    isnot(height, "", "grid has a computed grid height");
+    isnot(maxHeight, "", "grid has a computed grid max-height");
+    isnot(maxWidth, "", "grid has a computed grid max-width");
+  });
 
   // restore original state
   NewTabUtils.links.removeProvider(afterLoadProvider);
-  Services.prefs.clearUserPref(PRELOAD_PREF);
-}
+});
--- a/browser/base/content/test/newtab/browser_newtab_bug998387.js
+++ b/browser/base/content/test/newtab/browser_newtab_bug998387.js
@@ -1,32 +1,41 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
-const PREF_NEWTAB_ROWS = "browser.newtabpage.rows";
-
-function runTests() {
+add_task(function* () {
   // set max rows to 1, to avoid scroll events by clicking middle button
-  Services.prefs.setIntPref(PREF_NEWTAB_ROWS, 1);
+  yield pushPrefs(["browser.newtabpage.rows", 1]);
   yield setLinks("0");
-  yield addNewTabPageTab();
+  yield* addNewTabPageTab();
   // we need a second newtab to honor max rows
-  yield addNewTabPageTab();
+  yield* addNewTabPageTab();
+
+  yield ContentTask.spawn(gBrowser.selectedBrowser, {index: 0}, function* (args) {
+    let {site} = content.wrappedJSObject.gGrid.cells[args.index];
 
-  // Remember if the click handler was triggered
-  let {site} = getCell(0);
-  let origOnClick = site.onClick;
-  let clicked = false;
-  site.onClick = e => {
-    origOnClick.call(site, e);
-    clicked = true;
-    executeSoon(TestRunner.next);
-  };
+    let origOnClick = site.onClick;
+    let clicked = false;
+    site.onClick = e => {
+      origOnClick.call(site, e);
+      sendAsyncMessage("test:clicked-on-cell", {});
+    };
+  });
+
+  let mm = gBrowser.selectedBrowser.messageManager;
+  let messagePromise = new Promise(resolve => {
+    mm.addMessageListener("test:clicked-on-cell", function onResponse(message) {
+      mm.removeMessageListener("test:clicked-on-cell", onResponse);
+      resolve();
+    });
+  });
 
   // Send a middle-click and make sure it happened
-  let block = getContentDocument().querySelector(".newtab-control-block");
-  yield EventUtils.synthesizeMouseAtCenter(block, {button: 1}, getContentWindow());
-  ok(clicked, "middle click triggered click listener");
+  yield BrowserTestUtils.synthesizeMouseAtCenter(".newtab-control-block",
+                                                 {button: 1}, gBrowser.selectedBrowser);
+
+  yield messagePromise;
+  ok(true, "middle click triggered click listener");
 
   // Make sure the cell didn't actually get blocked
-  checkGrid("0");
-  Services.prefs.clearUserPref(PREF_NEWTAB_ROWS);
-}
+  yield* checkGrid("0");
+});
+
--- a/browser/base/content/test/newtab/browser_newtab_disable.js
+++ b/browser/base/content/test/newtab/browser_newtab_disable.js
@@ -1,34 +1,49 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 /*
  * These tests make sure that the 'New Tab Page' feature can be disabled if the
  * decides not to use it.
  */
-function runTests() {
+add_task(function* () {
   // create a new tab page and hide it.
   yield setLinks("0,1,2,3,4,5,6,7,8");
   setPinnedLinks("");
 
-  yield addNewTabPageTab();
-  let gridNode = getGrid().node;
+  let firstTab = yield* addNewTabPageTab();
 
-  ok(!gridNode.hasAttribute("page-disabled"), "page is not disabled");
+  function isGridDisabled(browser = gBrowser.selectedBrowser)
+  {
+    return ContentTask.spawn(browser, {}, function*() {
+      return content.gGrid.node.hasAttribute("page-disabled");
+    });
+  }
+
+  let isDisabled = yield isGridDisabled();
+  ok(!isDisabled, "page is not disabled");
 
   NewTabUtils.allPages.enabled = false;
-  ok(gridNode.hasAttribute("page-disabled"), "page is disabled");
 
-  let oldGridNode = gridNode;
+  isDisabled = yield isGridDisabled();
+  ok(isDisabled, "page is disabled");
 
-  // create a second new tage page and make sure it's disabled. enable it
+  // create a second new tab page and make sure it's disabled. enable it
   // again and check if the former page gets enabled as well.
-  yield addNewTabPageTab();
-  ok(gridNode.hasAttribute("page-disabled"), "page is disabled");
+  yield* addNewTabPageTab();
+  isDisabled = yield isGridDisabled(firstTab.linkedBrowser);
+  ok(isDisabled, "page is disabled");
 
   // check that no sites have been rendered
-  is(0, getContentDocument().querySelectorAll(".site").length, "no sites have been rendered");
+  let sitesLength = yield ContentTask.spawn(gBrowser.selectedBrowser, {}, function*() {
+    return content.document.querySelectorAll(".site").length;
+  });
+  is(0, sitesLength, "no sites have been rendered");
 
   NewTabUtils.allPages.enabled = true;
-  ok(!gridNode.hasAttribute("page-disabled"), "page is not disabled");
-  ok(!oldGridNode.hasAttribute("page-disabled"), "old page is not disabled");
-}
+
+  isDisabled = yield isGridDisabled();
+  ok(!isDisabled, "page is not disabled");
+
+  isDisabled = yield isGridDisabled(firstTab.linkedBrowser);
+  ok(!isDisabled, "old page is not disabled");
+});
--- a/browser/base/content/test/newtab/browser_newtab_drag_drop.js
+++ b/browser/base/content/test/newtab/browser_newtab_drag_drop.js
@@ -2,74 +2,94 @@
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 /*
  * These tests make sure that dragging and dropping sites works as expected.
  * Sites contained in the grid need to shift around to indicate the result
  * of the drag-and-drop operation. If the grid is full and we're dragging
  * a new site into it another one gets pushed out.
  */
-function runTests() {
+add_task(function* () {
   requestLongerTimeout(2);
-  yield addNewTabPageTab();
+  yield* addNewTabPageTab();
 
   // test a simple drag-and-drop scenario
   yield setLinks("0,1,2,3,4,5,6,7,8");
   setPinnedLinks("");
 
-  yield addNewTabPageTab();
-  checkGrid("0,1,2,3,4,5,6,7,8");
+  yield* addNewTabPageTab();
+  yield* checkGrid("0,1,2,3,4,5,6,7,8");
 
-  yield simulateDrop(0, 1);
-  checkGrid("1,0p,2,3,4,5,6,7,8");
+  yield doDragEvent(0, 1);
+  yield* checkGrid("1,0p,2,3,4,5,6,7,8");
 
   // drag a cell to its current cell and make sure it's not pinned afterwards
   yield setLinks("0,1,2,3,4,5,6,7,8");
   setPinnedLinks("");
 
-  yield addNewTabPageTab();
-  checkGrid("0,1,2,3,4,5,6,7,8");
+  yield* addNewTabPageTab();
+  yield* checkGrid("0,1,2,3,4,5,6,7,8");
 
-  yield simulateDrop(0, 0);
-  checkGrid("0,1,2,3,4,5,6,7,8");
+  yield doDragEvent(0, 0);
+  yield* checkGrid("0,1,2,3,4,5,6,7,8");
 
   // ensure that pinned pages aren't moved if that's not necessary
   yield setLinks("0,1,2,3,4,5,6,7,8");
   setPinnedLinks(",1,2");
 
-  yield addNewTabPageTab();
-  checkGrid("0,1p,2p,3,4,5,6,7,8");
+  yield* addNewTabPageTab();
+  yield* checkGrid("0,1p,2p,3,4,5,6,7,8");
 
-  yield simulateDrop(0, 3);
-  checkGrid("3,1p,2p,0p,4,5,6,7,8");
+  yield doDragEvent(0, 3);
+  yield* checkGrid("3,1p,2p,0p,4,5,6,7,8");
 
   // pinned sites should always be moved around as blocks. if a pinned site is
   // moved around, neighboring pinned are affected as well
   yield setLinks("0,1,2,3,4,5,6,7,8");
   setPinnedLinks("0,1");
 
-  yield addNewTabPageTab();
-  checkGrid("0p,1p,2,3,4,5,6,7,8");
+  yield* addNewTabPageTab();
+  yield* checkGrid("0p,1p,2,3,4,5,6,7,8");
 
-  yield simulateDrop(2, 0);
-  checkGrid("2p,0p,1p,3,4,5,6,7,8");
+  yield doDragEvent(2, 0);
+  yield* checkGrid("2p,0p,1p,3,4,5,6,7,8");
 
   // pinned sites should not be pushed out of the grid (unless there are only
   // pinned ones left on the grid)
   yield setLinks("0,1,2,3,4,5,6,7,8");
   setPinnedLinks(",,,,,,,7,8");
 
-  yield addNewTabPageTab();
-  checkGrid("0,1,2,3,4,5,6,7p,8p");
+  yield* addNewTabPageTab();
+  yield* checkGrid("0,1,2,3,4,5,6,7p,8p");
 
-  yield simulateDrop(2, 5);
-  checkGrid("0,1,3,4,5,2p,6,7p,8p");
+  yield doDragEvent(2, 5);
+  yield* checkGrid("0,1,3,4,5,2p,6,7p,8p");
 
   // make sure that pinned sites are re-positioned correctly
   yield setLinks("0,1,2,3,4,5,6,7,8");
   setPinnedLinks("0,1,2,,,5");
 
-  yield addNewTabPageTab();
-  checkGrid("0p,1p,2p,3,4,5p,6,7,8");
+  yield* addNewTabPageTab();
+  yield* checkGrid("0p,1p,2p,3,4,5p,6,7,8");
+
+  yield doDragEvent(0, 4);
+  yield* checkGrid("3,1p,2p,4,0p,5p,6,7,8");
+});
 
-  yield simulateDrop(0, 4);
-  checkGrid("3,1p,2p,4,0p,5p,6,7,8");
+function doDragEvent(sourceIndex, dropIndex) {
+  return ContentTask.spawn(gBrowser.selectedBrowser,
+                           { sourceIndex: sourceIndex, dropIndex: dropIndex }, function*(args) {
+    let dataTransfer = new content.DataTransfer("dragstart", false);
+    let event = content.document.createEvent("DragEvents");
+    event.initDragEvent("dragstart", true, true, content, 0, 0, 0, 0, 0,
+                        false, false, false, false, 0, null, dataTransfer);
+
+    let target = content.gGrid.cells[args.sourceIndex].site.node;
+    target.dispatchEvent(event);
+
+    event = content.document.createEvent("DragEvents");
+    event.initDragEvent("drop", true, true, content, 0, 0, 0, 0, 0,
+                        false, false, false, false, 0, null, dataTransfer);
+
+    target = content.gGrid.cells[args.dropIndex].node;
+    target.dispatchEvent(event);
+  });
 }
--- a/browser/base/content/test/newtab/browser_newtab_drag_drop_ext.js
+++ b/browser/base/content/test/newtab/browser_newtab_drag_drop_ext.js
@@ -8,57 +8,56 @@ const PREF_NEWTAB_COLUMNS = "browser.new
 /*
  * These tests make sure that dragging and dropping sites works as expected.
  * Sites contained in the grid need to shift around to indicate the result
  * of the drag-and-drop operation. If the grid is full and we're dragging
  * a new site into it another one gets pushed out.
  * This is a continuation of browser_newtab_drag_drop.js
  * to decrease test run time, focusing on external sites.
  */
-function runTests() {
-  registerCleanupFunction(_ => Services.prefs.clearUserPref(PREF_NEWTAB_COLUMNS));
-  yield addNewTabPageTab();
+ add_task(function* () {
+  yield* addNewTabPageTab();
 
   // drag a new site onto the very first cell
   yield setLinks("0,1,2,3,4,5,6,7,8");
   setPinnedLinks(",,,,,,,7,8");
 
-  yield addNewTabPageTab();
-  checkGrid("0,1,2,3,4,5,6,7p,8p");
+  yield* addNewTabPageTab();
+  yield* checkGrid("0,1,2,3,4,5,6,7p,8p");
 
-  yield simulateExternalDrop(0);
-  checkGrid("99p,0,1,2,3,4,5,7p,8p");
+  yield* simulateExternalDrop(0);
+  yield* checkGrid("99p,0,1,2,3,4,5,7p,8p");
 
   // drag a new site onto the grid and make sure that pinned cells don't get
   // pushed out
   yield setLinks("0,1,2,3,4,5,6,7,8");
   setPinnedLinks(",,,,,,,7,8");
 
-  yield addNewTabPageTab();
-  checkGrid("0,1,2,3,4,5,6,7p,8p");
+  yield* addNewTabPageTab();
+  yield* checkGrid("0,1,2,3,4,5,6,7p,8p");
 
   // force the grid to be small enough that a pinned cell could be pushed out
-  Services.prefs.setIntPref(PREF_NEWTAB_COLUMNS, 3);
-  yield simulateExternalDrop(5);
-  checkGrid("0,1,2,3,4,99p,5,7p,8p");
+  yield pushPrefs([PREF_NEWTAB_COLUMNS, 3]);
+  yield* simulateExternalDrop(5);
+  yield* checkGrid("0,1,2,3,4,99p,5,7p,8p");
 
   // drag a new site beneath a pinned cell and make sure the pinned cell is
   // not moved
   yield setLinks("0,1,2,3,4,5,6,7,8");
   setPinnedLinks(",,,,,,,,8");
 
-  yield addNewTabPageTab();
-  checkGrid("0,1,2,3,4,5,6,7,8p");
+  yield* addNewTabPageTab();
+  yield* checkGrid("0,1,2,3,4,5,6,7,8p");
 
-  yield simulateExternalDrop(5);
-  checkGrid("0,1,2,3,4,99p,5,6,8p");
+  yield* simulateExternalDrop(5);
+  yield* checkGrid("0,1,2,3,4,99p,5,6,8p");
 
   // drag a new site onto a block of pinned sites and make sure they're shifted
   // around accordingly
   yield setLinks("0,1,2,3,4,5,6,7,8");
   setPinnedLinks("0,1,2,,,,,,");
 
-  yield addNewTabPageTab();
-  checkGrid("0p,1p,2p");
+  yield* addNewTabPageTab();
+  yield* checkGrid("0p,1p,2p");
 
-  yield simulateExternalDrop(1);
-  checkGrid("0p,99p,1p,2p,3,4,5,6,7");
-}
+  yield* simulateExternalDrop(1);
+  yield* checkGrid("0p,99p,1p,2p,3,4,5,6,7");
+});
--- a/browser/base/content/test/newtab/browser_newtab_drop_preview.js
+++ b/browser/base/content/test/newtab/browser_newtab_drop_preview.js
@@ -1,24 +1,41 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 /*
  * These tests ensure that the drop preview correctly arranges sites when
  * dragging them around.
  */
-function runTests() {
-  yield addNewTabPageTab();
+add_task(function* () {
+  yield* addNewTabPageTab();
 
   // the first three sites are pinned - make sure they're re-arranged correctly
   yield setLinks("0,1,2,3,4,5,6,7,8");
   setPinnedLinks("0,1,2,,,5");
 
-  yield addNewTabPageTab();
-  checkGrid("0p,1p,2p,3,4,5p,6,7,8");
+  yield* addNewTabPageTab();
+  yield* checkGrid("0p,1p,2p,3,4,5p,6,7,8");
+
+  let foundSites = yield ContentTask.spawn(gWindow.gBrowser.selectedBrowser, {}, function*() {
+    let cells = content.gGrid.cells;
+    content.gDrag._draggedSite = cells[0].site;
+    let sites = content.gDropPreview.rearrange(cells[4]);
+    content.gDrag._draggedSite = null;
+
+    sites = sites.slice(0, 9);
+    return sites.map(function (aSite) {
+      if (!aSite)
+        return "";
 
-  let cw = getContentWindow();
-  cw.gDrag._draggedSite = getCell(0).site;
-  let sites = cw.gDropPreview.rearrange(getCell(4));
-  cw.gDrag._draggedSite = null;
+      let pinned = aSite.isPinned();
+      if (pinned != aSite.node.hasAttribute("pinned")) {
+        ok(false, "invalid state (site.isPinned() != site[pinned])");
+      }
 
-  checkGrid("3,1p,2p,4,0p,5p,6,7,8", sites);
-}
+      return aSite.url.replace(/^http:\/\/example(\d+)\.com\/$/, "$1") + (pinned ? "p" : "");
+    });
+  });
+
+  let expectedSites = "3,1p,2p,4,0p,5p,6,7,8"
+  is(foundSites, expectedSites, "grid status = " + expectedSites);
+});
+
--- a/browser/base/content/test/newtab/browser_newtab_enhanced.js
+++ b/browser/base/content/test/newtab/browser_newtab_enhanced.js
@@ -25,193 +25,205 @@ gDirectorySource = "data:application/jso
     url: "http://example1.com/",
     enhancedImageURI: "data:image/png;base64,helloWORLD2",
     title: "title1",
     type: "organic"
   }],
   "suggested": [suggestedLink]
 });
 
-function runTests() {
+add_task(function* () {
   let origEnhanced = NewTabUtils.allPages.enhanced;
   registerCleanupFunction(() => {
-    Services.prefs.clearUserPref(PRELOAD_PREF);
     NewTabUtils.allPages.enhanced = origEnhanced;
   });
 
-  Services.prefs.setBoolPref(PRELOAD_PREF, false);
+  yield pushPrefs([PRELOAD_PREF, false]);
 
   function getData(cellNum) {
-    let cell = getCell(cellNum);
-    if (!cell.site)
-      return null;
-    let siteNode = cell.site.node;
-    return {
-      type: siteNode.getAttribute("type"),
-      enhanced: siteNode.querySelector(".enhanced-content").style.backgroundImage,
-      title: siteNode.querySelector(".newtab-title").textContent,
-      suggested: siteNode.querySelector(".newtab-suggested").innerHTML
-    };
+    return performOnCell(cellNum, cell => {
+      if (!cell.site)
+        return null;
+      let siteNode = cell.site.node;
+      return {
+        type: siteNode.getAttribute("type"),
+        enhanced: siteNode.querySelector(".enhanced-content").style.backgroundImage,
+        title: siteNode.querySelector(".newtab-title").textContent,
+        suggested: siteNode.querySelector(".newtab-suggested").innerHTML
+      };
+    });
   }
 
   // Make the page have a directory link, enhanced link, and history link
   yield setLinks("-1");
 
   // Test with enhanced = false
-  yield addNewTabPageTab();
+  yield* addNewTabPageTab();
   yield customizeNewTabPage("classic");
   yield customizeNewTabPage("enhanced"); // Toggle enhanced off
-  let {type, enhanced, title, suggested} = getData(0);
+  let {type, enhanced, title, suggested} = yield getData(0);
   isnot(type, "enhanced", "history link is not enhanced");
   is(enhanced, "", "history link has no enhanced image");
   is(title, "example.com");
   is(suggested, "", "There is no suggested explanation");
 
-  is(getData(1), null, "there is only one link and it's a history link");
+  let data = yield getData(1);
+  is(data, null, "there is only one link and it's a history link");
 
   // Test with enhanced = true
-  yield addNewTabPageTab();
+  yield* addNewTabPageTab();
   yield customizeNewTabPage("enhanced"); // Toggle enhanced on
-  ({type, enhanced, title, suggested} = getData(0));
+  ({type, enhanced, title, suggested} = yield getData(0));
   is(type, "organic", "directory link is organic");
   isnot(enhanced, "", "directory link has enhanced image");
   is(title, "title1");
   is(suggested, "", "There is no suggested explanation");
 
-  ({type, enhanced, title, suggested} = getData(1));
+  ({type, enhanced, title, suggested} = yield getData(1));
   is(type, "enhanced", "history link is enhanced");
   isnot(enhanced, "", "history link has enhanced image");
   is(title, "title");
   is(suggested, "", "There is no suggested explanation");
 
-  is(getData(2), null, "there are only 2 links, directory and enhanced history");
+  data = yield getData(2);
+  is(data, null, "there are only 2 links, directory and enhanced history");
 
   // Test with a pinned link
   setPinnedLinks("-1");
-  yield addNewTabPageTab();
-  ({type, enhanced, title, suggested} = getData(0));
+  yield* addNewTabPageTab();
+  ({type, enhanced, title, suggested} = yield getData(0));
   is(type, "enhanced", "pinned history link is enhanced");
   isnot(enhanced, "", "pinned history link has enhanced image");
   is(title, "title");
   is(suggested, "", "There is no suggested explanation");
 
-  ({type, enhanced, title, suggested} = getData(1));
+  ({type, enhanced, title, suggested} = yield getData(1));
   is(type, "organic", "directory link is organic");
   isnot(enhanced, "", "directory link has enhanced image");
   is(title, "title1");
   is(suggested, "", "There is no suggested explanation");
 
-  is(getData(2), null, "directory link pushed out by pinned history link");
+  data = yield getData(2);
+  is(data, null, "directory link pushed out by pinned history link");
 
   // Test pinned link with enhanced = false
-  yield addNewTabPageTab();
+  yield* addNewTabPageTab();
   yield customizeNewTabPage("enhanced"); // Toggle enhanced off
-  ({type, enhanced, title, suggested} = getData(0));
+  ({type, enhanced, title, suggested} = yield getData(0));
   isnot(type, "enhanced", "history link is not enhanced");
   is(enhanced, "", "history link has no enhanced image");
   is(title, "example.com");
   is(suggested, "", "There is no suggested explanation");
 
-  is(getData(1), null, "directory link still pushed out by pinned history link");
+  data = yield getData(1);
+  is(data, null, "directory link still pushed out by pinned history link");
 
   yield unpinCell(0);
 
 
 
   // Test that a suggested tile is not enhanced by a directory tile
   let origIsTopPlacesSite = NewTabUtils.isTopPlacesSite;
   NewTabUtils.isTopPlacesSite = () => true;
   yield setLinks("-1,2,3,4,5,6,7,8");
 
   // Test with enhanced = false
-  yield addNewTabPageTab();
-  ({type, enhanced, title, suggested} = getData(0));
+  yield* addNewTabPageTab();
+  ({type, enhanced, title, suggested} = yield getData(0));
   isnot(type, "enhanced", "history link is not enhanced");
   is(enhanced, "", "history link has no enhanced image");
   is(title, "example.com");
   is(suggested, "", "There is no suggested explanation");
 
-  isnot(getData(7), null, "there are 8 history links");
-  is(getData(8), null, "there are 8 history links");
+  data = yield getData(7);
+  isnot(data, null, "there are 8 history links");
+  data = yield getData(8);
+  is(data, null, "there are 8 history links");
 
 
   // Test with enhanced = true
-  yield addNewTabPageTab();
+  yield* addNewTabPageTab();
   yield customizeNewTabPage("enhanced");
 
   // Suggested link was not enhanced by directory link with same domain
-  ({type, enhanced, title, suggested} = getData(0));
+  ({type, enhanced, title, suggested} = yield getData(0));
   is(type, "affiliate", "suggested link is affiliate");
   is(enhanced, "", "suggested link has no enhanced image");
   is(title, "title2");
   ok(suggested.indexOf("Suggested for <strong> Technology </strong> visitors") > -1, "Suggested for 'Technology'");
 
   // Enhanced history link shows up second
-  ({type, enhanced, title, suggested} = getData(1));
+  ({type, enhanced, title, suggested} = yield getData(1));
   is(type, "enhanced", "pinned history link is enhanced");
   isnot(enhanced, "", "pinned history link has enhanced image");
   is(title, "title");
   is(suggested, "", "There is no suggested explanation");
 
-  is(getData(9), null, "there is a suggested link followed by an enhanced history link and the remaining history links");
+  data = yield getData(9);
+  is(data, null, "there is a suggested link followed by an enhanced history link and the remaining history links");
 
 
 
   // Test no override category/adgroup name.
-  Services.prefs.setCharPref(PREF_NEWTAB_DIRECTORYSOURCE,
-    "data:application/json," + JSON.stringify({"suggested": [suggestedLink]}));
-  yield watchLinksChangeOnce().then(TestRunner.next);
+  let linksChangedPromise = watchLinksChangeOnce();
+  yield pushPrefs([PREF_NEWTAB_DIRECTORYSOURCE,
+                  "data:application/json," + JSON.stringify({"suggested": [suggestedLink]})]);
+  yield linksChangedPromise;
 
-  yield addNewTabPageTab();
-  ({type, enhanced, title, suggested} = getData(0));
+  yield* addNewTabPageTab();
+  ({type, enhanced, title, suggested} = yield getData(0));
   Cu.reportError("SUGGEST " + suggested);
   ok(suggested.indexOf("Suggested for <strong> Technology </strong> visitors") > -1, "Suggested for 'Technology'");
 
 
   // Test server provided explanation string.
   suggestedLink.explanation = "Suggested for %1$S enthusiasts who visit sites like %2$S";
-  Services.prefs.setCharPref(PREF_NEWTAB_DIRECTORYSOURCE,
-    "data:application/json," + encodeURIComponent(JSON.stringify({"suggested": [suggestedLink]})));
-  yield watchLinksChangeOnce().then(TestRunner.next);
+  linksChangedPromise = watchLinksChangeOnce();
+  yield pushPrefs([PREF_NEWTAB_DIRECTORYSOURCE,
+                  "data:application/json," + encodeURIComponent(JSON.stringify({"suggested": [suggestedLink]}))]);
+  yield linksChangedPromise;
 
-  yield addNewTabPageTab();
-  ({type, enhanced, title, suggested} = getData(0));
+  yield* addNewTabPageTab();
+  ({type, enhanced, title, suggested} = yield getData(0));
   Cu.reportError("SUGGEST " + suggested);
   ok(suggested.indexOf("Suggested for <strong> Technology </strong> enthusiasts who visit sites like <strong> classroom.google.com </strong>") > -1, "Suggested for 'Technology' enthusiasts");
 
 
   // Test server provided explanation string with category override.
   suggestedLink.adgroup_name = "webdev education";
-  Services.prefs.setCharPref(PREF_NEWTAB_DIRECTORYSOURCE,
-    "data:application/json," + encodeURIComponent(JSON.stringify({"suggested": [suggestedLink]})));
-  yield watchLinksChangeOnce().then(TestRunner.next);
+  linksChangedPromise = watchLinksChangeOnce();
+  yield pushPrefs([PREF_NEWTAB_DIRECTORYSOURCE,
+                  "data:application/json," + encodeURIComponent(JSON.stringify({"suggested": [suggestedLink]}))]);
+  yield linksChangedPromise;
 
-  yield addNewTabPageTab();
-  ({type, enhanced, title, suggested} = getData(0));
+  yield* addNewTabPageTab();
+  ({type, enhanced, title, suggested} = yield getData(0));
   Cu.reportError("SUGGEST " + suggested);
   ok(suggested.indexOf("Suggested for <strong> webdev education </strong> enthusiasts who visit sites like <strong> classroom.google.com </strong>") > -1, "Suggested for 'webdev education' enthusiasts");
 
 
 
   // Test with xml entities in category name
   suggestedLink.url = "http://example1.com/3";
   suggestedLink.adgroup_name = ">angles< & \"quotes\'";
-  Services.prefs.setCharPref(PREF_NEWTAB_DIRECTORYSOURCE,
-    "data:application/json," + encodeURIComponent(JSON.stringify({"suggested": [suggestedLink]})));
-  yield watchLinksChangeOnce().then(TestRunner.next);
+  linksChangedPromise = watchLinksChangeOnce();
+  yield pushPrefs([PREF_NEWTAB_DIRECTORYSOURCE,
+                  "data:application/json," + encodeURIComponent(JSON.stringify({"suggested": [suggestedLink]}))]);
+  yield linksChangedPromise;
 
-  yield addNewTabPageTab();
-  ({type, enhanced, title, suggested} = getData(0));
+  yield* addNewTabPageTab();
+  ({type, enhanced, title, suggested} = yield getData(0));
   Cu.reportError("SUGGEST " + suggested);
   ok(suggested.indexOf("Suggested for <strong> &gt;angles&lt; &amp; \"quotes\' </strong> enthusiasts who visit sites like <strong> classroom.google.com </strong>") > -1, "Suggested for 'xml entities' enthusiasts");
 
 
   // Test with xml entities in explanation.
   suggestedLink.explanation = "Testing junk explanation &<>\"'";
-  Services.prefs.setCharPref(PREF_NEWTAB_DIRECTORYSOURCE,
-    "data:application/json," + encodeURIComponent(JSON.stringify({"suggested": [suggestedLink]})));
-  yield watchLinksChangeOnce().then(TestRunner.next);
+  linksChangedPromise = watchLinksChangeOnce();
+  yield pushPrefs([PREF_NEWTAB_DIRECTORYSOURCE,
+                  "data:application/json," + encodeURIComponent(JSON.stringify({"suggested": [suggestedLink]}))]);
+  yield linksChangedPromise;
 
-  yield addNewTabPageTab();
-  ({type, enhanced, title, suggested} = getData(0));
+  yield* addNewTabPageTab();
+  ({type, enhanced, title, suggested} = yield getData(0));
   Cu.reportError("SUGGEST " + suggested);
   ok(suggested.indexOf("Testing junk explanation &amp;&lt;&gt;\"'") > -1, "Junk test");
-}
+});
--- a/browser/base/content/test/newtab/browser_newtab_external_resource.js
+++ b/browser/base/content/test/newtab/browser_newtab_external_resource.js
@@ -9,67 +9,66 @@
  *     as the nodePrincipal match the URL in the URL bar.
  */
 
 /* globals Cc, Ci, ok, is, content, TestRunner, addNewTabPageTab, gWindow, Services, info */
 /* exported runTests */
 
 "use strict";
 
-var browser = null;
 var aboutNewTabService = Cc["@mozilla.org/browser/aboutnewtab-service;1"]
                            .getService(Ci.nsIAboutNewTabService);
 
 const ABOUT_NEWTAB_URI = "about:newtab";
 const PREF_URI = "http://example.com/browser/browser/base/content/test/newtab/external_newtab.html";
 const DEFAULT_URI = aboutNewTabService.newTabURL;
 
-function testPref() {
+function* loadNewPageAndVerify(browser, uri) {
+  let browserLoadedPromise = BrowserTestUtils.waitForEvent(browser, "load", true);
+  browser.loadURI("about:newtab");
+  yield browserLoadedPromise;
+
+  yield ContentTask.spawn(gBrowser.selectedBrowser, { uri: uri }, function* (args) {
+    let uri = args.uri;
+
+    is(String(content.document.location), uri, "document.location should match " + uri);
+    is(content.document.documentURI, uri, "document.documentURI should match " + uri);
+
+    if (uri == "about:newtab") {
+      is(content.document.nodePrincipal,
+         Services.scriptSecurityManager.getSystemPrincipal(),
+         "nodePrincipal should match systemPrincipal");
+    }
+    else {
+      is(content.document.nodePrincipal.URI.spec, uri,
+         "nodePrincipal should match " + uri);
+    }
+  }, true);
+}
+
+add_task(function* () {
+  // test the default behavior
+  yield* addNewTabPageTab();
+
+  let browser = gBrowser.selectedBrowser;
+
+  ok(!aboutNewTabService.overridden,
+     "sanity check: default URL for about:newtab should not be overriden");
+
+  yield* loadNewPageAndVerify(browser, ABOUT_NEWTAB_URI);
+
   // set the pref for about:newtab to point to an exteranl resource
   aboutNewTabService.newTabURL = PREF_URI;
   ok(aboutNewTabService.overridden,
      "sanity check: default URL for about:newtab should be overriden");
   is(aboutNewTabService.newTabURL, PREF_URI,
      "sanity check: default URL for about:newtab should return the new URL");
 
-  browser.contentWindow.location = ABOUT_NEWTAB_URI;
-
-  browser.addEventListener("load", function onLoad() {
-    browser.removeEventListener("load", onLoad, true);
-    is(content.document.location, PREF_URI, "document.location should match the external resource");
-    is(content.document.documentURI, PREF_URI, "document.documentURI should match the external resource");
-    is(content.document.nodePrincipal.URI.spec, PREF_URI, "nodePrincipal should match the external resource");
-
-    // reset to about:newtab and perform sanity check
-    aboutNewTabService.resetNewTabURL();
-    is(aboutNewTabService.newTabURL, DEFAULT_URI,
-       "sanity check: resetting the URL to about:newtab should return about:newtab");
-
-    // remove the tab and move on
-    gBrowser.removeCurrentTab();
-    TestRunner.next();
-  }, true);
-}
+  yield* loadNewPageAndVerify(browser, PREF_URI);
 
-function runTests() {
-  // test the default behavior
-  yield addNewTabPageTab();
-  browser = gWindow.gBrowser.selectedBrowser;
-
-  ok(!aboutNewTabService.overridden,
-     "sanity check: default URL for about:newtab should not be overriden");
-  browser.contentWindow.location = ABOUT_NEWTAB_URI;
+  // reset to about:newtab and perform sanity check
+  aboutNewTabService.resetNewTabURL();
+  is(aboutNewTabService.newTabURL, DEFAULT_URI,
+     "sanity check: resetting the URL to about:newtab should return about:newtab");
 
-  browser.addEventListener("load", function onLoad() {
-    browser.removeEventListener("load", onLoad, true);
-    is(content.document.location, ABOUT_NEWTAB_URI, "document.location should match about:newtab");
-    is(content.document.documentURI, ABOUT_NEWTAB_URI, "document.documentURI should match about:newtab");
-    is(content.document.nodePrincipal,
-       Services.scriptSecurityManager.getSystemPrincipal(),
-       "nodePrincipal should match systemPrincipal");
-
-    // also test the pref
-    testPref();
-  }, true);
-
-  info("Waiting for about:newtab to load ...");
-  yield true;
-}
+  // remove the tab and move on
+  gBrowser.removeCurrentTab();
+});
--- a/browser/base/content/test/newtab/browser_newtab_focus.js
+++ b/browser/base/content/test/newtab/browser_newtab_focus.js
@@ -1,60 +1,48 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 /*
- * These tests make sure that focusing the 'New Tage Page' works as expected.
+ * These tests make sure that focusing the 'New Tab Page' works as expected.
  */
-function runTests() {
-  // Handle the OSX full keyboard access setting
-  Services.prefs.setIntPref("accessibility.tabfocus", 7);
+add_task(function* () {
+  yield pushPrefs(["accessibility.tabfocus", 7]);
 
   // Focus count in new tab page.
   // 30 = 9 * 3 + 3 = 9 sites, each with link, pin and remove buttons; search
   // bar; search button; and toggle button. Additionaly there may or may not be
   // a scroll bar caused by fix to 1180387, which will eat an extra focus
   let FOCUS_COUNT = 30;
 
   // Create a new tab page.
   yield setLinks("0,1,2,3,4,5,6,7,8");
   setPinnedLinks("");
 
-  yield addNewTabPageTab();
+  yield* addNewTabPageTab();
   gURLBar.focus();
 
   // Count the focus with the enabled page.
-  yield countFocus(FOCUS_COUNT);
+  countFocus(FOCUS_COUNT);
 
   // Disable page and count the focus with the disabled page.
   NewTabUtils.allPages.enabled = false;
-  yield countFocus(1);
+
+  countFocus(4);
 
-  Services.prefs.clearUserPref("accessibility.tabfocus");
   NewTabUtils.allPages.enabled = true;
-}
+});
 
 /**
  * Focus the urlbar and count how many focus stops to return again to the urlbar.
  */
 function countFocus(aExpectedCount) {
   let focusCount = 0;
-  let contentDoc = getContentDocument();
+  do {
+    EventUtils.synthesizeKey("VK_TAB", {});
+    if (document.activeElement == gBrowser.selectedBrowser) {
+      focusCount++;
+    }
+  } while (document.activeElement != gURLBar.inputField);
 
-  window.addEventListener("focus", function onFocus() {
-    let focusedElement = document.commandDispatcher.focusedElement;
-    if (focusedElement && focusedElement.classList.contains("urlbar-input")) {
-      window.removeEventListener("focus", onFocus, true);
-      // account for a potential presence of a scroll bar
-      ok(focusCount == aExpectedCount || focusCount == (aExpectedCount + 1),
-         "Validate focus count in the new tab page.");
-      executeSoon(TestRunner.next);
-    } else {
-      if (focusedElement && focusedElement.ownerDocument == contentDoc &&
-          focusedElement instanceof HTMLElement) {
-        focusCount++;
-      }
-      document.commandDispatcher.advanceFocus();
-    }
-  }, true);
-
-  document.commandDispatcher.advanceFocus();
+  ok(focusCount == aExpectedCount || focusCount == (aExpectedCount + 1),
+     "Validate focus count in the new tab page.");
 }
--- a/browser/base/content/test/newtab/browser_newtab_perwindow_private_browsing.js
+++ b/browser/base/content/test/newtab/browser_newtab_perwindow_private_browsing.js
@@ -3,65 +3,53 @@
 
 /*
  * These tests ensure that all changes made to the new tab page in private
  * browsing mode are discarded after switching back to normal mode again.
  * The private browsing mode should start with the current grid shown in normal
  * mode.
  */
 
-function runTests() {
+add_task(function* () {
   // prepare the grid
   yield testOnWindow(undefined);
   yield setLinks("0,1,2,3,4,5,6,7,8,9");
 
-  yield addNewTabPageTab();
-  pinCell(0);
-  checkGrid("0p,1,2,3,4,5,6,7,8");
+  yield* addNewTabPageTab();
+  yield pinCell(0);
+  yield* checkGrid("0p,1,2,3,4,5,6,7,8");
 
   // open private window
   yield testOnWindow({private: true});
 
-  yield addNewTabPageTab();
-  checkGrid("0p,1,2,3,4,5,6,7,8");
+  yield* addNewTabPageTab();
+  yield* checkGrid("0p,1,2,3,4,5,6,7,8");
 
   // modify the grid while we're in pb mode
   yield blockCell(1);
-  checkGrid("0p,2,3,4,5,6,7,8");
+  yield* checkGrid("0p,2,3,4,5,6,7,8");
 
   yield unpinCell(0);
-  checkGrid("0,2,3,4,5,6,7,8");
+  yield* checkGrid("0,2,3,4,5,6,7,8");
 
   // open normal window
   yield testOnWindow(undefined);
 
   // check that the grid is the same as before entering pb mode
-  yield addNewTabPageTab();
-  checkGrid("0,2,3,4,5,6,7,8")
-}
+  yield* addNewTabPageTab();
+  yield* checkGrid("0,2,3,4,5,6,7,8")
+});
 
 var windowsToClose = [];
 function testOnWindow(options) {
+  let newWindowPromise = BrowserTestUtils.waitForNewWindow();
   var win = OpenBrowserWindow(options);
-  win.addEventListener("load", function onLoad() {
-    win.removeEventListener("load", onLoad, false);
-    windowsToClose.push(win);
-    gWindow = win;
-    whenDelayedStartupFinished(win, TestRunner.next);
-  }, false);
-}
-
-function whenDelayedStartupFinished(win, callback) {
-  const topic = "browser-delayed-startup-finished";
-  Services.obs.addObserver(function onStartup(subject) {
-    if (win == subject) {
-      Services.obs.removeObserver(onStartup, topic);
-      executeSoon(callback);
-    }
-  }, topic, false);
+  windowsToClose.push(win);
+  gWindow = win;
+  yield newWindowPromise;
 }
 
 registerCleanupFunction(function () {
   gWindow = window;
   windowsToClose.forEach(function(win) {
     win.close();
   });
 });
--- a/browser/base/content/test/newtab/browser_newtab_reflow_load.js
+++ b/browser/base/content/test/newtab/browser_newtab_reflow_load.js
@@ -4,37 +4,34 @@
 "use strict";
 
 const FRAME_SCRIPT = getRootDirectory(gTestPath) + "content-reflows.js";
 const ADDITIONAL_WAIT_MS = 2000;
 
 /*
  * Ensure that loading about:newtab doesn't cause uninterruptible reflows.
  */
-function runTests() {
-  gBrowser.selectedTab = gBrowser.addTab("about:blank", {animate: false});
+add_task(function* () {
+  yield BrowserTestUtils.openNewForegroundTab(gBrowser, () => {
+    return gBrowser.selectedTab = gBrowser.addTab("about:blank", {animate: false});
+  }, false);
+
   let browser = gBrowser.selectedBrowser;
-  yield whenBrowserLoaded(browser);
-
   let mm = browser.messageManager;
   mm.loadFrameScript(FRAME_SCRIPT, true);
   mm.addMessageListener("newtab-reflow", ({data: stack}) => {
     ok(false, `unexpected uninterruptible reflow ${stack}`);
   });
 
+  let browserLoadedPromise = BrowserTestUtils.waitForEvent(browser, "load", true);
   browser.loadURI("about:newtab");
-  yield whenBrowserLoaded(browser);
+  yield browserLoadedPromise;
 
   // Wait some more to catch sync reflows after the page has loaded.
-  yield setTimeout(TestRunner.next, ADDITIONAL_WAIT_MS);
+  yield new Promise(resolve => {
+    setTimeout(resolve, ADDITIONAL_WAIT_MS);
+  });
 
   // Clean up.
   gBrowser.removeCurrentTab({animate: false});
 
   ok(true, "Each test requires at least one pass, fail or todo so here is a pass.");
-}
-
-function whenBrowserLoaded(browser) {
-  browser.addEventListener("load", function onLoad() {
-    browser.removeEventListener("load", onLoad, true);
-    executeSoon(TestRunner.next);
-  }, true);
-}
+});
--- a/browser/base/content/test/newtab/browser_newtab_reportLinkAction.js
+++ b/browser/base/content/test/newtab/browser_newtab_reportLinkAction.js
@@ -8,84 +8,76 @@ gDirectorySource = "data:application/jso
     url: "http://example.com/organic",
     type: "organic"
   }, {
     url: "http://localhost/sponsored",
     type: "sponsored"
   }]
 });
 
-function runTests() {
-  Services.prefs.setBoolPref(PRELOAD_PREF, false);
+add_task(function* () {
+  yield pushPrefs([PRELOAD_PREF, false]);
 
   let originalReportSitesAction  = DirectoryLinksProvider.reportSitesAction;
   registerCleanupFunction(() => {
-    Services.prefs.clearUserPref(PRELOAD_PREF);
     DirectoryLinksProvider.reportSitesAction = originalReportSitesAction;
   });
 
   let expected = {};
-  DirectoryLinksProvider.reportSitesAction = function(sites, action, siteIndex) {
-    let {link} = sites[siteIndex];
-    is(link.type, expected.type, "got expected type");
-    is(action, expected.action, "got expected action");
-    is(NewTabUtils.pinnedLinks.isPinned(link), expected.pinned, "got expected pinned");
-    executeSoon(TestRunner.next);
+
+  function expectReportSitesAction() {
+    return new Promise(resolve => {
+      DirectoryLinksProvider.reportSitesAction = function(sites, action, siteIndex) {
+        let {link} = sites[siteIndex];
+        is(link.type, expected.type, "got expected type");
+        is(action, expected.action, "got expected action");
+        is(NewTabUtils.pinnedLinks.isPinned(link), expected.pinned, "got expected pinned");
+        resolve();
+      }
+    });
   }
 
   // Test that the last visible site (index 1) is reported
+  let reportSitesPromise = expectReportSitesAction();
   expected.type = "sponsored";
   expected.action = "view";
   expected.pinned = false;
-  addNewTabPageTab();
+  yield* addNewTabPageTab();
+  yield reportSitesPromise;
 
-  // Wait for addNewTabPageTab and reportSitesAction
-  yield null;
-  yield null;
-
-  whenPagesUpdated();
   // Click the pin button on the link in the 1th tile spot
-  let siteNode = getCell(1).node.querySelector(".newtab-site");
-  let pinButton = siteNode.querySelector(".newtab-control-pin");
   expected.action = "pin";
   // tiles become "history" when pinned
   expected.type = "history";
   expected.pinned = true;
-  EventUtils.synthesizeMouseAtCenter(pinButton, {}, getContentWindow());
+  let pagesUpdatedPromise = whenPagesUpdated();
+  reportSitesPromise = expectReportSitesAction();
 
-  // Wait for whenPagesUpdated and reportSitesAction
-  yield null;
-  yield null;
+  yield BrowserTestUtils.synthesizeMouseAtCenter(".newtab-cell + .newtab-cell .newtab-control-pin", {}, gBrowser.selectedBrowser);
+  yield pagesUpdatedPromise;
+  yield reportSitesPromise;
 
   // Unpin that link
   expected.action = "unpin";
   expected.pinned = false;
-  whenPagesUpdated();
-  // need to reget siteNode for it could have been re-rendered after pin
-  siteNode = getCell(1).node.querySelector(".newtab-site");
-  pinButton = siteNode.querySelector(".newtab-control-pin");
-  EventUtils.synthesizeMouseAtCenter(pinButton, {}, getContentWindow());
-
-  // Wait for whenPagesUpdated and reportSitesAction
-  yield null;
-  yield null;
+  pagesUpdatedPromise = whenPagesUpdated();
+  reportSitesPromise = expectReportSitesAction();
+  yield BrowserTestUtils.synthesizeMouseAtCenter(".newtab-cell + .newtab-cell .newtab-control-pin", {}, gBrowser.selectedBrowser);
+  yield pagesUpdatedPromise;
+  yield reportSitesPromise;
 
   // Block the site in the 0th tile spot
-  let blockedSite = getCell(0).node.querySelector(".newtab-site");
-  let blockButton = blockedSite.querySelector(".newtab-control-block");
   expected.type = "organic";
   expected.action = "block";
   expected.pinned = false;
-  whenPagesUpdated();
-  EventUtils.synthesizeMouseAtCenter(blockButton, {}, getContentWindow());
-
-  // Wait for whenPagesUpdated and reportSitesAction
-  yield null;
-  yield null;
+  pagesUpdatedPromise = whenPagesUpdated();
+  reportSitesPromise = expectReportSitesAction();
+  yield BrowserTestUtils.synthesizeMouseAtCenter(".newtab-site .newtab-control-block", {}, gBrowser.selectedBrowser);
+  yield pagesUpdatedPromise;
+  yield reportSitesPromise;
 
   // Click the 1th link now in the 0th tile spot
   expected.type = "history";
   expected.action = "click";
-  EventUtils.synthesizeMouseAtCenter(siteNode, {}, getContentWindow());
-
-  // Wait for reportSitesAction
-  yield null;
-}
+  reportSitesPromise = expectReportSitesAction();
+  yield BrowserTestUtils.synthesizeMouseAtCenter(".newtab-site", {}, gBrowser.selectedBrowser);
+  yield reportSitesPromise;
+});
--- a/browser/base/content/test/newtab/browser_newtab_search.js
+++ b/browser/base/content/test/newtab/browser_newtab_search.js
@@ -39,317 +39,266 @@ const ENGINE_1X_2X_LOGO = {
   numLogos: 2,
 };
 
 const ENGINE_SUGGESTIONS = {
   name: "searchSuggestionEngine.xml",
   numLogos: 0,
 };
 
-const SERVICE_EVENT_NAME = "ContentSearchService";
-
-const LOGO_1X_DPI_SIZE = [65, 26];
-const LOGO_2X_DPI_SIZE = [130, 52];
-
 // The test has an expected search event queue and a search event listener.
 // Search events that are expected to happen are added to the queue, and the
 // listener consumes the queue and ensures that each event it receives is at
 // the head of the queue.
-//
-// Each item in the queue is an object { type, deferred }.  type is the
-// expected search event type.  deferred is a Promise.defer() value that is
-// resolved when the event is consumed.
-var gExpectedSearchEventQueue = [];
+let gExpectedSearchEventQueue = [];
+let gExpectedSearchEventResolver = null;
 
-var gNewEngines = [];
+let gNewEngines = [];
 
-function runTests() {
-  runTaskifiedTests().then(TestRunner.next, TestRunner.next);
-  yield;
-}
-
-var runTaskifiedTests = Task.async(function* () {
+add_task(function* () {
   let oldCurrentEngine = Services.search.currentEngine;
 
-  yield addNewTabPageTabPromise();
+  yield* addNewTabPageTab();
 
   // The tab is removed at the end of the test, so there's no need to remove
   // this listener at the end of the test.
   info("Adding search event listener");
-  getContentWindow().addEventListener(SERVICE_EVENT_NAME, searchEventListener);
+  yield ContentTask.spawn(gBrowser.selectedBrowser, {}, function* () {
+    const SERVICE_EVENT_NAME = "ContentSearchService";
+    content.addEventListener(SERVICE_EVENT_NAME, function (event) {
+      sendAsyncMessage("test:search-event", { eventType: event.detail.type });
+    });
+  });
+
+  let mm = gBrowser.selectedBrowser.messageManager;
+  mm.addMessageListener("test:search-event", function (message) {
+    let eventType = message.data.eventType;
+    if (!gExpectedSearchEventResolver) {
+      ok(false, "Got search event " + eventType + " with no promise assigned");
+    }
+
+    let expectedEventType = gExpectedSearchEventQueue.shift();
+    is(eventType, expectedEventType, "Got expected search event " + expectedEventType);
+    if (!gExpectedSearchEventQueue.length) {
+      gExpectedSearchEventResolver();
+      gExpectedSearchEventResolver = null;
+    }
+  });
 
   // Add the engine without any logos and switch to it.
   let noLogoEngine = yield promiseNewSearchEngine(ENGINE_NO_LOGO);
+  let searchEventsPromise = promiseSearchEvents(["CurrentEngine"]);
   Services.search.currentEngine = noLogoEngine;
-  yield promiseSearchEvents(["CurrentEngine"]);
-  yield checkCurrentEngine(ENGINE_NO_LOGO);
+  yield searchEventsPromise;
+  yield* checkCurrentEngine(ENGINE_NO_LOGO);
 
   // Add the engine with favicon and switch to it.
   let faviconEngine = yield promiseNewSearchEngine(ENGINE_FAVICON);
+  searchEventsPromise = promiseSearchEvents(["CurrentEngine"]);
   Services.search.currentEngine = faviconEngine;
-  yield promiseSearchEvents(["CurrentEngine"]);
-  yield checkCurrentEngine(ENGINE_FAVICON);
+  yield searchEventsPromise;
+  yield* checkCurrentEngine(ENGINE_FAVICON);
 
   // Add the engine with a 1x-DPI logo and switch to it.
   let logo1xEngine = yield promiseNewSearchEngine(ENGINE_1X_LOGO);
+  searchEventsPromise = promiseSearchEvents(["CurrentEngine"]);
   Services.search.currentEngine = logo1xEngine;
-  yield promiseSearchEvents(["CurrentEngine"]);
-  yield checkCurrentEngine(ENGINE_1X_LOGO);
+  yield searchEventsPromise;
+  yield* checkCurrentEngine(ENGINE_1X_LOGO);
 
   // Add the engine with a 2x-DPI logo and switch to it.
   let logo2xEngine = yield promiseNewSearchEngine(ENGINE_2X_LOGO);
+  searchEventsPromise = promiseSearchEvents(["CurrentEngine"]);
   Services.search.currentEngine = logo2xEngine;
-  yield promiseSearchEvents(["CurrentEngine"]);
-  yield checkCurrentEngine(ENGINE_2X_LOGO);
+  yield searchEventsPromise;
+  yield* checkCurrentEngine(ENGINE_2X_LOGO);
 
   // Add the engine with 1x- and 2x-DPI logos and switch to it.
   let logo1x2xEngine = yield promiseNewSearchEngine(ENGINE_1X_2X_LOGO);
+  searchEventsPromise = promiseSearchEvents(["CurrentEngine"]);
   Services.search.currentEngine = logo1x2xEngine;
-  yield promiseSearchEvents(["CurrentEngine"]);
-  yield checkCurrentEngine(ENGINE_1X_2X_LOGO);
+  yield searchEventsPromise;
+  yield* checkCurrentEngine(ENGINE_1X_2X_LOGO);
 
   // Add the engine that provides search suggestions and switch to it.
   let suggestionEngine = yield promiseNewSearchEngine(ENGINE_SUGGESTIONS);
+  searchEventsPromise = promiseSearchEvents(["CurrentEngine"]);
   Services.search.currentEngine = suggestionEngine;
-  yield promiseSearchEvents(["CurrentEngine"]);
-  yield checkCurrentEngine(ENGINE_SUGGESTIONS);
+  yield searchEventsPromise;
+  yield* checkCurrentEngine(ENGINE_SUGGESTIONS);
 
   // Avoid intermittent failures.
-  gSearch().remoteTimeout = 5000;
+  yield ContentTask.spawn(gBrowser.selectedBrowser, {}, function* () {
+    content.gSearch._contentSearchController.remoteTimeout = 5000;
+  });
 
   // Type an X in the search input.  This is only a smoke test.  See
   // browser_searchSuggestionUI.js for comprehensive content search suggestion
   // UI tests.
-  let input = $("text");
-  input.focus();
+  let suggestionsOpenPromise = new Promise(resolve => {
+    mm.addMessageListener("test:newtab-suggestions-open", function onResponse(message) {
+      mm.removeMessageListener("test:newtab-suggestions-open", onResponse);
+      resolve();
+    });
+  });
+
+  yield ContentTask.spawn(gBrowser.selectedBrowser, {}, function* () {
+    let table = content.document.getElementById("searchSuggestionTable");
+
+    let input = content.document.getElementById("newtab-search-text");
+    input.focus();
+
+    info("Waiting for suggestions table to open");
+    let observer = new content.MutationObserver(() => {
+      if (input.getAttribute("aria-expanded") == "true") {
+        observer.disconnect();
+        ok(!table.hidden, "Search suggestion table unhidden");
+        sendAsyncMessage("test:newtab-suggestions-open", {});
+      }
+    });
+    observer.observe(input, {
+      attributes: true,
+      attributeFilter: ["aria-expanded"],
+    });
+  });
+
+  let suggestionsPromise = promiseSearchEvents(["Suggestions"]);
+
   EventUtils.synthesizeKey("x", {});
-  let suggestionsPromise = promiseSearchEvents(["Suggestions"]);
 
   // Wait for the search suggestions to become visible and for the Suggestions
   // message.
-  let suggestionsUnhiddenDefer = Promise.defer();
-  let table = getContentDocument().getElementById("searchSuggestionTable");
-  info("Waiting for suggestions table to open");
-  let observer = new MutationObserver(() => {
-    if (input.getAttribute("aria-expanded") == "true") {
-      observer.disconnect();
-      ok(!table.hidden, "Search suggestion table unhidden");
-      suggestionsUnhiddenDefer.resolve();
-    }
-  });
-  observer.observe(input, {
-    attributes: true,
-    attributeFilter: ["aria-expanded"],
-  });
-  yield suggestionsUnhiddenDefer.promise;
+  yield suggestionsOpenPromise;
   yield suggestionsPromise;
 
   // Empty the search input, causing the suggestions to be hidden.
   EventUtils.synthesizeKey("a", { accelKey: true });
   EventUtils.synthesizeKey("VK_DELETE", {});
-  ok(table.hidden, "Search suggestion table hidden");
+
+  let tableHidden = yield ContentTask.spawn(gBrowser.selectedBrowser, {}, function* () {
+    return content.document.getElementById("searchSuggestionTable").hidden;
+  });
+  ok(tableHidden, "Search suggestion table hidden");
 
   // Remove the search bar from toolbar
   CustomizableUI.removeWidgetFromArea("search-container");
   // Focus a different element than the search input from the page.
-  let btn = getContentDocument().getElementById("newtab-customize-button");
-  yield promiseClick(btn);
+  yield BrowserTestUtils.synthesizeMouseAtCenter("#newtab-customize-button", { }, gBrowser.selectedBrowser);
+
+  yield ContentTask.spawn(gBrowser.selectedBrowser, { }, function* () {
+    let input = content.document.getElementById("newtab-search-text");
+    isnot(input, content.document.activeElement, "Search input should not be focused");
+  });
 
-  isnot(input, getContentDocument().activeElement, "Search input should not be focused");
   // Test that Ctrl/Cmd + K will focus the input field from the page.
+  let focusPromise = promiseSearchEvents(["FocusInput"]);
   EventUtils.synthesizeKey("k", { accelKey: true });
-  yield promiseSearchEvents(["FocusInput"]);
-  is(input, getContentDocument().activeElement, "Search input should be focused");
+  yield focusPromise;
+
+  yield ContentTask.spawn(gBrowser.selectedBrowser, { }, function* () {
+    let input = content.document.getElementById("newtab-search-text");
+    is(input, content.document.activeElement, "Search input should be focused");
+  });
+
   // Reset changes made to toolbar
   CustomizableUI.reset();
 
   // Test that Ctrl/Cmd + K will focus the search bar from toolbar.
-  let searchBar = gWindow.document.getElementById("searchbar");
   EventUtils.synthesizeKey("k", { accelKey: true });
-  is(searchBar.textbox.inputField, gWindow.document.activeElement, "Toolbar's search bar should be focused");
+  let searchBar = document.getElementById("searchbar");
+  is(searchBar.textbox.inputField, document.activeElement, "Toolbar's search bar should be focused");
 
   // Test that Ctrl/Cmd + K will focus the search bar from a new about:home page if
   // the newtab is disabled from `NewTabUtils.allPages.enabled`.
-  yield addNewTabPageTabPromise();
+  let tab = yield* addNewTabPageTab();
   // Remove the search bar from toolbar
   CustomizableUI.removeWidgetFromArea("search-container");
   NewTabUtils.allPages.enabled = false;
   EventUtils.synthesizeKey("k", { accelKey: true });
-  let waitEvent = "AboutHomeLoadSnippetsCompleted";
-  yield promiseTabLoadEvent(gWindow.gBrowser.selectedTab, "about:home", waitEvent);
+
+  
+  let aboutHomeLoaded = new Promise(resolve => {
+    tab.linkedBrowser.addEventListener("AboutHomeLoadSnippetsCompleted", function loadListener(event) {
+      tab.linkedBrowser.removeEventListener("AboutHomeLoadSnippetsCompleted", loadListener, true);
+      resolve();
+    }, true, true);
+  });
 
-  is(getContentDocument().documentURI.toLowerCase(), "about:home", "New tab's uri should be about:home");
-  let searchInput = getContentDocument().getElementById("searchText");
-  is(searchInput, getContentDocument().activeElement, "Search input must be the selected element");
+  tab.linkedBrowser.loadURI("about:home");
+  yield aboutHomeLoaded;
+
+  yield ContentTask.spawn(gBrowser.selectedBrowser, { }, function* () {
+    is(content.document.documentURI.toLowerCase(), "about:home", "New tab's uri should be about:home");
+    let searchInput = content.document.getElementById("searchText");
+    is(searchInput, content.document.activeElement, "Search input must be the selected element");
+  });
 
   NewTabUtils.allPages.enabled = true;
   CustomizableUI.reset();
-  gBrowser.removeCurrentTab();
+  yield BrowserTestUtils.removeTab(gBrowser.selectedTab);
 
   // Done.  Revert the current engine and remove the new engines.
+  searchEventsPromise = promiseSearchEvents(["CurrentEngine"]);
   Services.search.currentEngine = oldCurrentEngine;
-  yield promiseSearchEvents(["CurrentEngine"]);
+  yield searchEventsPromise;
 
-  let events = [];
+  let events = Array(gNewEngines.length).fill("CurrentState", 0, gNewEngines.length);
+  searchEventsPromise = promiseSearchEvents(events);
+
   for (let engine of gNewEngines) {
     Services.search.removeEngine(engine);
-    events.push("CurrentState");
   }
-  yield promiseSearchEvents(events);
+  yield searchEventsPromise;
 });
 
-function searchEventListener(event) {
-  info("Got search event " + event.detail.type);
-  let nonempty = gExpectedSearchEventQueue.length > 0;
-  ok(nonempty, "Expected search event queue should be nonempty");
-  if (nonempty) {
-    let { type, deferred } = gExpectedSearchEventQueue.shift();
-    is(event.detail.type, type, "Got expected search event " + type);
-    if (event.detail.type == type) {
-      deferred.resolve();
-    } else {
-      deferred.reject();
-    }
-  }
-}
-
-function $(idSuffix) {
-  return getContentDocument().getElementById("newtab-search-" + idSuffix);
-}
-
 function promiseSearchEvents(events) {
   info("Expecting search events: " + events);
-  events = events.map(e => ({ type: e, deferred: Promise.defer() }));
-  gExpectedSearchEventQueue.push(...events);
-  return Promise.all(events.map(e => e.deferred.promise));
+  return new Promise(resolve => {
+    gExpectedSearchEventQueue.push(...events);
+    gExpectedSearchEventResolver = resolve;
+  });
 }
 
 function promiseNewSearchEngine({name: basename, numLogos}) {
   info("Waiting for engine to be added: " + basename);
 
   // Wait for the search events triggered by adding the new engine.
   // engine-added engine-loaded
   let expectedSearchEvents = ["CurrentState", "CurrentState"];
   // engine-changed for each of the logos
   for (let i = 0; i < numLogos; i++) {
     expectedSearchEvents.push("CurrentState");
   }
   let eventPromise = promiseSearchEvents(expectedSearchEvents);
 
   // Wait for addEngine().
-  let addDeferred = Promise.defer();
-  let url = getRootDirectory(gTestPath) + basename;
-  Services.search.addEngine(url, null, "", false, {
-    onSuccess: function (engine) {
-      info("Search engine added: " + basename);
-      gNewEngines.push(engine);
-      addDeferred.resolve(engine);
-    },
-    onError: function (errCode) {
-      ok(false, "addEngine failed with error code " + errCode);
-      addDeferred.reject();
-    },
+  let addEnginePromise = new Promise((resolve, reject) => {
+    let url = getRootDirectory(gTestPath) + basename;
+    Services.search.addEngine(url, null, "", false, {
+      onSuccess: function (engine) {
+        info("Search engine added: " + basename);
+        gNewEngines.push(engine);
+        resolve(engine);
+      },
+      onError: function (errCode) {
+        ok(false, "addEngine failed with error code " + errCode);
+        reject();
+      },
+    });
   });
 
-  return Promise.all([addDeferred.promise, eventPromise]).then(([newEngine, _]) => {
+  return Promise.all([addEnginePromise, eventPromise]).then(([newEngine, _]) => {
     return newEngine;
   });
 }
 
-function objectURLToBlob(url) {
-  return new Promise(function (resolve, reject) {
-    let xhr = new XMLHttpRequest();
-    xhr.open("get", url, true);
-    xhr.responseType = "blob";
-    xhr.overrideMimeType("image/png");
-    xhr.onload = function(e) {
-      if (this.status == 200) {
-        return resolve(this.response);
-      }
-      reject("Failed to get logo, xhr returned status: " + this.status);
-    };
-    xhr.onerror = reject;
-    xhr.send();
-  });
-}
-
-function blobToBase64(blob) {
-  return new Promise(function (resolve, reject) {
-    var reader = new FileReader();
-    reader.onload = function() {
-      resolve(reader.result);
-    }
-    reader.onerror = reject;
-    reader.readAsDataURL(blob);
-  });
-}
-
-var checkCurrentEngine = Task.async(function* ({name: basename, logoPrefix1x, logoPrefix2x}) {
+function* checkCurrentEngine(engineInfo)
+{
   let engine = Services.search.currentEngine;
-  ok(engine.name.includes(basename),
+  ok(engine.name.includes(engineInfo.name),
      "Sanity check: current engine: engine.name=" + engine.name +
-     " basename=" + basename);
-
-  // gSearch.currentEngineName
-  is(gSearch().defaultEngine.name, engine.name,
-     "currentEngineName: " + engine.name);
-});
-
-function promiseClick(node) {
-  let deferred = Promise.defer();
-  let win = getContentWindow();
-  SimpleTest.waitForFocus(() => {
-    EventUtils.synthesizeMouseAtCenter(node, {}, win);
-    deferred.resolve();
-  }, win);
-  return deferred.promise;
-}
+     " basename=" + engineInfo.name);
 
-function logoImg() {
-  return $("logo");
-}
-
-function gSearch() {
-  return getContentWindow().gSearch._contentSearchController;
+  let engineName = yield ContentTask.spawn(gBrowser.selectedBrowser, {}, function* () {
+    return content.gSearch._contentSearchController.defaultEngine.name;
+  });
+  is(engineName, engine.name, "currentEngineName: " + engine.name);
 }
-
-/**
- * Waits for a load (or custom) event to finish in a given tab. If provided
- * load an uri into the tab.
- *
- * @param tab
- *        The tab to load into.
- * @param [optional] url
- *        The url to load, or the current url.
- * @param [optional] event
- *        The load event type to wait for.  Defaults to "load".
- * @return {Promise} resolved when the event is handled.
- * @resolves to the received event
- * @rejects if a valid load event is not received within a meaningful interval
- */
-function promiseTabLoadEvent(tab, url, eventType="load") {
-  let deferred = Promise.defer();
-  info("Wait tab event: " + eventType);
-
-  function handle(event) {
-    if (event.originalTarget != tab.linkedBrowser.contentDocument ||
-        event.target.location.href == "about:blank" ||
-        (url && event.target.location.href != url)) {
-      info("Skipping spurious '" + eventType + "'' event" +
-           " for " + event.target.location.href);
-      return;
-    }
-    clearTimeout(timeout);
-    tab.linkedBrowser.removeEventListener(eventType, handle, true);
-    info("Tab event received: " + eventType);
-    deferred.resolve(event);
-  }
-
-  let timeout = setTimeout(() => {
-    tab.linkedBrowser.removeEventListener(eventType, handle, true);
-    deferred.reject(new Error("Timed out while waiting for a '" + eventType + "'' event"));
-  }, 20000);
-
-  tab.linkedBrowser.addEventListener(eventType, handle, true, true);
-  if (url)
-    tab.linkedBrowser.loadURI(url);
-  return deferred.promise;
-}
--- a/browser/base/content/test/newtab/browser_newtab_sponsored_icon_click.js
+++ b/browser/base/content/test/newtab/browser_newtab_sponsored_icon_click.js
@@ -1,36 +1,47 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
-function runTests() {
+add_task(function* () {
   yield setLinks("0");
-  yield addNewTabPageTab();
-
-  let site = getCell(0).node.querySelector(".newtab-site");
-  site.setAttribute("type", "sponsored");
+  yield* addNewTabPageTab();
 
-  // test explain text appearing upon a click
-  let sponsoredButton = site.querySelector(".newtab-sponsored");
-  EventUtils.synthesizeMouseAtCenter(sponsoredButton, {}, getContentWindow());
-  let explain = site.querySelector(".sponsored-explain");
-  isnot(explain, null, "Sponsored explanation shown");
-  ok(explain.querySelector("input").classList.contains("newtab-control-block"), "sponsored tiles show blocked image");
-  ok(sponsoredButton.hasAttribute("active"), "Sponsored button has active attribute");
+  yield ContentTask.spawn(gBrowser.selectedBrowser, {}, function* () {
+    var EventUtils = {};
+    EventUtils.window = {};
+    EventUtils.parent = EventUtils.window;
+    EventUtils._EU_Ci = Components.interfaces;
+
+    Services.scriptloader.loadSubScript("chrome://mochikit/content/tests/SimpleTest/EventUtils.js", EventUtils);
+
+    let cell = content.gGrid.cells[0];
+
+    let site = cell.node.querySelector(".newtab-site");
+    site.setAttribute("type", "sponsored");
 
-  // test dismissing sponsored explain
-  EventUtils.synthesizeMouseAtCenter(sponsoredButton, {}, getContentWindow());
-  is(site.querySelector(".sponsored-explain"), null, "Sponsored explanation no longer shown");
-  ok(!sponsoredButton.hasAttribute("active"), "Sponsored button does not have active attribute");
+    // test explain text appearing upon a click
+    let sponsoredButton = site.querySelector(".newtab-sponsored");
+    EventUtils.synthesizeMouseAtCenter(sponsoredButton, {}, content);
+    let explain = site.querySelector(".sponsored-explain");
+    isnot(explain, null, "Sponsored explanation shown");
+    ok(explain.querySelector("input").classList.contains("newtab-control-block"), "sponsored tiles show blocked image");
+    ok(sponsoredButton.hasAttribute("active"), "Sponsored button has active attribute");
+
+    // test dismissing sponsored explain
+    EventUtils.synthesizeMouseAtCenter(sponsoredButton, {}, content);
+    is(site.querySelector(".sponsored-explain"), null, "Sponsored explanation no longer shown");
+    ok(!sponsoredButton.hasAttribute("active"), "Sponsored button does not have active attribute");
 
-  // test with enhanced tile
-  site.setAttribute("type", "enhanced");
-  EventUtils.synthesizeMouseAtCenter(sponsoredButton, {}, getContentWindow());
-  explain = site.querySelector(".sponsored-explain");
-  isnot(explain, null, "Sponsored explanation shown");
-  ok(explain.querySelector("input").classList.contains("newtab-customize"), "enhanced tiles show customize image");
-  ok(sponsoredButton.hasAttribute("active"), "Sponsored button has active attribute");
+    // test with enhanced tile
+    site.setAttribute("type", "enhanced");
+    EventUtils.synthesizeMouseAtCenter(sponsoredButton, {}, content);
+    explain = site.querySelector(".sponsored-explain");
+    isnot(explain, null, "Sponsored explanation shown");
+    ok(explain.querySelector("input").classList.contains("newtab-customize"), "enhanced tiles show customize image");
+    ok(sponsoredButton.hasAttribute("active"), "Sponsored button has active attribute");
 
-  // test dismissing enhanced explain
-  EventUtils.synthesizeMouseAtCenter(sponsoredButton, {}, getContentWindow());
-  is(site.querySelector(".sponsored-explain"), null, "Sponsored explanation no longer shown");
-  ok(!sponsoredButton.hasAttribute("active"), "Sponsored button does not have active attribute");
-}
+    // test dismissing enhanced explain
+    EventUtils.synthesizeMouseAtCenter(sponsoredButton, {}, content);
+    is(site.querySelector(".sponsored-explain"), null, "Sponsored explanation no longer shown");
+    ok(!sponsoredButton.hasAttribute("active"), "Sponsored button does not have active attribute");
+  });
+});
--- a/browser/base/content/test/newtab/browser_newtab_undo.js
+++ b/browser/base/content/test/newtab/browser_newtab_undo.js
@@ -1,49 +1,47 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 /*
  * These tests make sure that the undo dialog works as expected.
  */
-function runTests() {
+add_task(function* () {
   // remove unpinned sites and undo it
   yield setLinks("0,1,2,3,4,5,6,7,8");
   setPinnedLinks("5");
 
-  yield addNewTabPageTab();
-  checkGrid("5p,0,1,2,3,4,6,7,8");
+  yield* addNewTabPageTab();
+  yield* checkGrid("5p,0,1,2,3,4,6,7,8");
 
   yield blockCell(4);
   yield blockCell(4);
-  checkGrid("5p,0,1,2,6,7,8");
+  yield* checkGrid("5p,0,1,2,6,7,8");
 
-  yield undo();
-  checkGrid("5p,0,1,2,4,6,7,8");
+  yield* undo();
+  yield* checkGrid("5p,0,1,2,4,6,7,8");
 
   // now remove a pinned site and undo it
   yield blockCell(0);
-  checkGrid("0,1,2,4,6,7,8");
+  yield* checkGrid("0,1,2,4,6,7,8");
 
-  yield undo();
-  checkGrid("5p,0,1,2,4,6,7,8");
+  yield* undo();
+  yield* checkGrid("5p,0,1,2,4,6,7,8");
 
   // remove a site and restore all
   yield blockCell(1);
-  checkGrid("5p,1,2,4,6,7,8");
+  yield* checkGrid("5p,1,2,4,6,7,8");
 
-  yield undoAll();
-  checkGrid("5p,0,1,2,3,4,6,7,8");
+  yield* undoAll();
+  yield* checkGrid("5p,0,1,2,3,4,6,7,8");
+});
+
+function* undo() {
+  let updatedPromise = whenPagesUpdated();
+  yield BrowserTestUtils.synthesizeMouseAtCenter("#newtab-undo-button", {}, gBrowser.selectedBrowser);
+  yield updatedPromise; 
 }
 
-function undo() {
-  let cw = getContentWindow();
-  let target = cw.document.getElementById("newtab-undo-button");
-  EventUtils.synthesizeMouseAtCenter(target, {}, cw);
-  whenPagesUpdated();
-}
-
-function undoAll() {
-  let cw = getContentWindow();
-  let target = cw.document.getElementById("newtab-undo-restore-button");
-  EventUtils.synthesizeMouseAtCenter(target, {}, cw);
-  whenPagesUpdated();
+function* undoAll() {
+  let updatedPromise = whenPagesUpdated();
+  yield BrowserTestUtils.synthesizeMouseAtCenter("#newtab-undo-restore-button", {}, gBrowser.selectedBrowser);
+  yield updatedPromise; 
 }
\ No newline at end of file
--- a/browser/base/content/test/newtab/browser_newtab_unpin.js
+++ b/browser/base/content/test/newtab/browser_newtab_unpin.js
@@ -1,56 +1,56 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 /*
  * These tests make sure that when a site gets unpinned it is either moved to
  * its actual place in the grid or removed in case it's not on the grid anymore.
  */
-function runTests() {
+add_task(function* () {
   // we have a pinned link that didn't change its position since it was pinned.
-  // nothing should happend when we unpin it.
+  // nothing should happen when we unpin it.
   yield setLinks("0,1,2,3,4,5,6,7,8");
   setPinnedLinks(",1");
 
-  yield addNewTabPageTab();
-  checkGrid("0,1p,2,3,4,5,6,7,8");
+  yield* addNewTabPageTab();
+  yield* checkGrid("0,1p,2,3,4,5,6,7,8");
 
   yield unpinCell(1);
-  checkGrid("0,1,2,3,4,5,6,7,8");
+  yield* checkGrid("0,1,2,3,4,5,6,7,8");
 
   // we have a pinned link that is not anymore in the list of the most-visited
   // links. this should disappear, the remaining links adjust their positions
   // and a new link will appear at the end of the grid.
   yield setLinks("0,1,2,3,4,5,6,7,8");
   setPinnedLinks(",99");
 
-  yield addNewTabPageTab();
-  checkGrid("0,99p,1,2,3,4,5,6,7");
+  yield* addNewTabPageTab();
+  yield* checkGrid("0,99p,1,2,3,4,5,6,7");
 
   yield unpinCell(1);
-  checkGrid("0,1,2,3,4,5,6,7,8");
+  yield* checkGrid("0,1,2,3,4,5,6,7,8");
 
   // we have a pinned link that changed its position since it was pinned. it
   // should be moved to its new position after being unpinned.
   yield setLinks("0,1,2,3,4,5,6,7");
   setPinnedLinks(",1,,,,,,,0");
 
-  yield addNewTabPageTab();
-  checkGrid("2,1p,3,4,5,6,7,,0p");
+  yield* addNewTabPageTab();
+  yield* checkGrid("2,1p,3,4,5,6,7,,0p");
 
   yield unpinCell(1);
-  checkGrid("1,2,3,4,5,6,7,,0p");
+  yield* checkGrid("1,2,3,4,5,6,7,,0p");
 
   yield unpinCell(8);
-  checkGrid("0,1,2,3,4,5,6,7,");
+  yield* checkGrid("0,1,2,3,4,5,6,7,");
 
   // we have pinned link that changed its position since it was pinned. the
   // link will disappear from the grid because it's now a much lower priority
   yield setLinks("0,1,2,3,4,5,6,7,8,9");
   setPinnedLinks("9");
 
-  yield addNewTabPageTab();
-  checkGrid("9p,0,1,2,3,4,5,6,7");
+  yield* addNewTabPageTab();
+  yield* checkGrid("9p,0,1,2,3,4,5,6,7");
 
   yield unpinCell(0);
-  checkGrid("0,1,2,3,4,5,6,7,8");
-}
+  yield* checkGrid("0,1,2,3,4,5,6,7,8");
+});
--- a/browser/base/content/test/newtab/browser_newtab_update.js
+++ b/browser/base/content/test/newtab/browser_newtab_update.js
@@ -1,49 +1,48 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 "use strict";
 
 /**
  * Checks that newtab is updated as its links change.
  */
-function runTests() {
+add_task(function* () {
   // First, start with an empty page.  setLinks will trigger a hidden page
   // update because it calls clearHistory.  We need to wait for that update to
   // happen so that the next time we wait for a page update below, we catch the
   // right update and not the one triggered by setLinks.
-  yield whenPagesUpdatedAnd(resolve => setLinks([], resolve));
+  let updatedPromise = whenPagesUpdated();
+  let setLinksPromise = setLinks([]);
+  yield Promise.all([updatedPromise, setLinksPromise]);
 
   // Strategy: Add some visits, open a new page, check the grid, repeat.
   yield fillHistoryAndWaitForPageUpdate([1]);
-  yield addNewTabPageTab();
-  checkGrid("1,,,,,,,,");
+  yield* addNewTabPageTab();
+  yield* checkGrid("1,,,,,,,,");
 
   yield fillHistoryAndWaitForPageUpdate([2]);
-  yield addNewTabPageTab();
-  checkGrid("2,1,,,,,,,");
+  yield* addNewTabPageTab();
+  yield* checkGrid("2,1,,,,,,,");
 
   yield fillHistoryAndWaitForPageUpdate([1]);
-  yield addNewTabPageTab();
-  checkGrid("1,2,,,,,,,");
+  yield* addNewTabPageTab();
+  yield* checkGrid("1,2,,,,,,,");
 
   yield fillHistoryAndWaitForPageUpdate([2, 3, 4]);
-  yield addNewTabPageTab();
-  checkGrid("2,1,3,4,,,,,");
+  yield* addNewTabPageTab();
+  yield* checkGrid("2,1,3,4,,,,,");
 
   // Make sure these added links have the right type
-  is(getCell(1).site.link.type, "history", "added link is history");
-}
+  let type = yield performOnCell(1, cell => { return cell.site.link.type });
+  is(type, "history", "added link is history");
+});
 
 function fillHistoryAndWaitForPageUpdate(links) {
-  return whenPagesUpdatedAnd(resolve => fillHistory(links.map(link), resolve));
-}
-
-function whenPagesUpdatedAnd(promiseConstructor) {
-  let promise1 = new Promise(whenPagesUpdated);
-  let promise2 = new Promise(promiseConstructor);
-  return Promise.all([promise1, promise2]).then(TestRunner.next);
+  let updatedPromise = whenPagesUpdated;
+  let fillHistoryPromise = fillHistory(links.map(link));
+  return Promise.all([updatedPromise, fillHistoryPromise]);
 }
 
 function link(id) {
   return { url: "http://example" + id + ".com/", title: "site#" + id };
 }
--- a/browser/base/content/test/newtab/head.js
+++ b/browser/base/content/test/newtab/head.js
@@ -2,32 +2,24 @@
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 const PREF_NEWTAB_ENABLED = "browser.newtabpage.enabled";
 const PREF_NEWTAB_DIRECTORYSOURCE = "browser.newtabpage.directory.source";
 
 Services.prefs.setBoolPref(PREF_NEWTAB_ENABLED, true);
 
 var tmp = {};
-Cu.import("resource://gre/modules/Promise.jsm", tmp);
 Cu.import("resource://gre/modules/NewTabUtils.jsm", tmp);
 Cu.import("resource:///modules/DirectoryLinksProvider.jsm", tmp);
 Cu.import("resource://testing-common/PlacesTestUtils.jsm", tmp);
 Cc["@mozilla.org/moz/jssubscript-loader;1"]
   .getService(Ci.mozIJSSubScriptLoader)
   .loadSubScript("chrome://browser/content/sanitize.js", tmp);
-Cu.import("resource://gre/modules/Timer.jsm", tmp);
-var {Promise, NewTabUtils, Sanitizer, clearTimeout, setTimeout, DirectoryLinksProvider, PlacesTestUtils} = tmp;
+var {NewTabUtils, Sanitizer, DirectoryLinksProvider, PlacesTestUtils} = tmp;
 
-var uri = Services.io.newURI("about:newtab", null, null);
-var principal = Services.scriptSecurityManager.createCodebasePrincipal(uri, {});
-
-var isMac = ("nsILocalFileMac" in Ci);
-var isLinux = ("@mozilla.org/gnome-gconf-service;1" in Cc);
-var isWindows = ("@mozilla.org/windows-registry-key;1" in Cc);
 var gWindow = window;
 
 // Default to dummy/empty directory links
 var gDirectorySource = 'data:application/json,{"test":1}';
 var gOrigDirectorySource;
 
 // The tests assume all 3 rows and all 3 columns of sites are shown, but the
 // window may be too small to actually show everything.  Resize it if necessary.
@@ -93,236 +85,156 @@ registerCleanupFunction(function () {
   }
 
   Services.prefs.clearUserPref(PREF_NEWTAB_ENABLED);
   Services.prefs.setCharPref(PREF_NEWTAB_DIRECTORYSOURCE, gOrigDirectorySource);
 
   return watchLinksChangeOnce();
 });
 
+function pushPrefs(...aPrefs) {
+  return new Promise(resolve =>
+                     SpecialPowers.pushPrefEnv({"set": aPrefs}, resolve));
+}
+
 /**
  * Resolves promise when directory links are downloaded and written to disk
  */
 function watchLinksChangeOnce() {
-  let deferred = Promise.defer();
-  let observer = {
-    onManyLinksChanged: () => {
-      DirectoryLinksProvider.removeObserver(observer);
-      deferred.resolve();
-    }
-  };
-  observer.onDownloadFail = observer.onManyLinksChanged;
-  DirectoryLinksProvider.addObserver(observer);
-  return deferred.promise;
+  return new Promise(resolve => {
+    let observer = {
+      onManyLinksChanged: () => {
+        DirectoryLinksProvider.removeObserver(observer);
+        resolve();
+      }
+    };
+    observer.onDownloadFail = observer.onManyLinksChanged;
+    DirectoryLinksProvider.addObserver(observer);
+  });
 };
 
-/**
- * Provide the default test function to start our test runner.
- *
- * We need different code paths for tests that are still wired for
- * `TestRunner` and tests that have been ported to `add_task` as
- * we cannot have both in the same file.
- */
-function isTestPortedToAddTask() {
-  return gTestPath.endsWith("browser_newtab_bug722273.js");
-}
-if (!isTestPortedToAddTask()) {
-  this.test = function() {
-    waitForExplicitFinish();
-    // start TestRunner.run() after directory links is downloaded and written to disk
-    watchLinksChangeOnce().then(() => {
-      // Wait for hidden page to update with the desired links
-      whenPagesUpdated(() => TestRunner.run(), true);
-    });
+add_task(function* setup() {
+  registerCleanupFunction(function() {
+    return new Promise(resolve => {
+      function cleanupAndFinish() {
+        PlacesTestUtils.clearHistory().then(() => {
+          whenPagesUpdated().then(resolve);
+          NewTabUtils.restore();
+        });
+      }
 
-    // Save the original directory source (which is set globally for tests)
-    gOrigDirectorySource = Services.prefs.getCharPref(PREF_NEWTAB_DIRECTORYSOURCE);
-    Services.prefs.setCharPref(PREF_NEWTAB_DIRECTORYSOURCE, gDirectorySource);
-  }
-} else {
-  add_task(function* setup() {
-    registerCleanupFunction(function() {
-      return new Promise(resolve => {
-        function cleanupAndFinish() {
-          PlacesTestUtils.clearHistory().then(() => {
-            whenPagesUpdated(resolve);
-            NewTabUtils.restore();
-          });
-        }
-
-        let callbacks = NewTabUtils.links._populateCallbacks;
-        let numCallbacks = callbacks.length;
+      let callbacks = NewTabUtils.links._populateCallbacks;
+      let numCallbacks = callbacks.length;
 
-        if (numCallbacks)
-          callbacks.splice(0, numCallbacks, cleanupAndFinish);
-        else
-          cleanupAndFinish();
-      });
+      if (numCallbacks)
+        callbacks.splice(0, numCallbacks, cleanupAndFinish);
+      else
+        cleanupAndFinish();
     });
-
-    let promiseReady = Task.spawn(function*() {
-      yield watchLinksChangeOnce();
-      yield new Promise(resolve => whenPagesUpdated(resolve, true));
-    });
-
-    // Save the original directory source (which is set globally for tests)
-    gOrigDirectorySource = Services.prefs.getCharPref(PREF_NEWTAB_DIRECTORYSOURCE);
-    Services.prefs.setCharPref(PREF_NEWTAB_DIRECTORYSOURCE, gDirectorySource);
-    yield promiseReady;
   });
-}
 
-/**
- * The test runner that controls the execution flow of our tests.
- */
-var TestRunner = {
-  /**
-   * Starts the test runner.
-   */
-  run: function () {
-    this._iter = runTests();
-    this.next();
-  },
+  let promiseReady = Task.spawn(function*() {
+    yield watchLinksChangeOnce();
+    yield whenPagesUpdated();
+  });
 
-  /**
-   * Runs the next available test or finishes if there's no test left.
-   */
-  next: function () {
-    try {
-      TestRunner._iter.next();
-    } catch (e if e instanceof StopIteration) {
-      TestRunner.finish();
-    }
-  },
-
-  /**
-   * Finishes all tests and cleans up.
-   */
-  finish: function () {
-    function cleanupAndFinish() {
-      PlacesTestUtils.clearHistory().then(() => {
-        whenPagesUpdated(finish);
-        NewTabUtils.restore();
-      });
-    }
-
-    let callbacks = NewTabUtils.links._populateCallbacks;
-    let numCallbacks = callbacks.length;
+  // Save the original directory source (which is set globally for tests)
+  gOrigDirectorySource = Services.prefs.getCharPref(PREF_NEWTAB_DIRECTORYSOURCE);
+  Services.prefs.setCharPref(PREF_NEWTAB_DIRECTORYSOURCE, gDirectorySource);
+  yield promiseReady;
+});
 
-    if (numCallbacks)
-      callbacks.splice(0, numCallbacks, cleanupAndFinish);
-    else
-      cleanupAndFinish();
-  }
-};
-
-/**
- * Returns the selected tab's content window.
- * @return The content window.
- */
-function getContentWindow() {
-  return gWindow.gBrowser.selectedBrowser.contentWindow;
-}
-
-/**
- * Returns the selected tab's content document.
- * @return The content document.
- */
-function getContentDocument() {
-  return gWindow.gBrowser.selectedBrowser.contentDocument;
-}
-
-/**
- * Returns the newtab grid of the selected tab.
- * @return The newtab grid.
- */
-function getGrid() {
-  return getContentWindow().gGrid;
-}
-
-/**
- * Returns the cell at the given index of the selected tab's newtab grid.
- * @param aIndex The cell index.
- * @return The newtab cell.
- */
-function getCell(aIndex) {
-  return getGrid().cells[aIndex];
+/** Perform an action on a cell within the newtab page.
+  * @param aIndex index of cell
+  * @param aFn function to call in child process or tab.
+  * @returns result of calling the function.
+  */
+function performOnCell(aIndex, aFn) {
+  return ContentTask.spawn(gWindow.gBrowser.selectedBrowser,
+                           { index: aIndex, fn: aFn.toString() }, function* (args) {
+    let cell = content.gGrid.cells[args.index];
+    return eval("(" + args.fn + ")(cell)");
+  });
 }
 
 /**
  * Allows to provide a list of links that is used to construct the grid.
  * @param aLinksPattern the pattern (see below)
  *
  * Example: setLinks("-1,0,1,2,3")
  * Result: [{url: "http://example.com/", title: "site#-1"},
  *          {url: "http://example0.com/", title: "site#0"},
  *          {url: "http://example1.com/", title: "site#1"},
  *          {url: "http://example2.com/", title: "site#2"},
  *          {url: "http://example3.com/", title: "site#3"}]
  */
-function setLinks(aLinks, aCallback = TestRunner.next) {
-  let links = aLinks;
+function setLinks(aLinks) {
+  return new Promise(resolve => {
+    let links = aLinks;
 
-  if (typeof links == "string") {
-    links = aLinks.split(/\s*,\s*/).map(function (id) {
-      return {url: "http://example" + (id != "-1" ? id : "") + ".com/",
-              title: "site#" + id};
-    });
-  }
+    if (typeof links == "string") {
+      links = aLinks.split(/\s*,\s*/).map(function (id) {
+        return {url: "http://example" + (id != "-1" ? id : "") + ".com/",
+                title: "site#" + id};
+      });
+    }
 
-  // Call populateCache() once to make sure that all link fetching that is
-  // currently in progress has ended. We clear the history, fill it with the
-  // given entries and call populateCache() now again to make sure the cache
-  // has the desired contents.
-  NewTabUtils.links.populateCache(function () {
-    PlacesTestUtils.clearHistory().then(() => {
-      fillHistory(links, function () {
-        NewTabUtils.links.populateCache(function () {
-          NewTabUtils.allPages.update();
-          aCallback();
-        }, true);
+    // Call populateCache() once to make sure that all link fetching that is
+    // currently in progress has ended. We clear the history, fill it with the
+    // given entries and call populateCache() now again to make sure the cache
+    // has the desired contents.
+    NewTabUtils.links.populateCache(function () {
+      PlacesTestUtils.clearHistory().then(() => {
+        fillHistory(links).then(() => {
+          NewTabUtils.links.populateCache(function () {
+            NewTabUtils.allPages.update();
+            resolve();
+          }, true);
+        });
       });
     });
   });
 }
 
-function fillHistory(aLinks, aCallback = TestRunner.next) {
-  let numLinks = aLinks.length;
-  if (!numLinks) {
-    if (aCallback)
-      executeSoon(aCallback);
-    return;
-  }
+function fillHistory(aLinks) {
+  return new Promise(resolve => {
+    let numLinks = aLinks.length;
+    if (!numLinks) {
+      executeSoon(resolve);
+      return;
+    }
 
-  let transitionLink = Ci.nsINavHistoryService.TRANSITION_LINK;
+    let transitionLink = Ci.nsINavHistoryService.TRANSITION_LINK;
 
-  // Important: To avoid test failures due to clock jitter on Windows XP, call
-  // Date.now() once here, not each time through the loop.
-  let now = Date.now() * 1000;
+    // Important: To avoid test failures due to clock jitter on Windows XP, call
+    // Date.now() once here, not each time through the loop.
+    let now = Date.now() * 1000;
 
-  for (let i = 0; i < aLinks.length; i++) {
-    let link = aLinks[i];
-    let place = {
-      uri: makeURI(link.url),
-      title: link.title,
-      // Links are secondarily sorted by visit date descending, so decrease the
-      // visit date as we progress through the array so that links appear in the
-      // grid in the order they're present in the array.
-      visits: [{visitDate: now - i, transitionType: transitionLink}]
-    };
+    for (let i = 0; i < aLinks.length; i++) {
+      let link = aLinks[i];
+      let place = {
+        uri: makeURI(link.url),
+        title: link.title,
+        // Links are secondarily sorted by visit date descending, so decrease the
+        // visit date as we progress through the array so that links appear in the
+        // grid in the order they're present in the array.
+        visits: [{visitDate: now - i, transitionType: transitionLink}]
+      };
 
-    PlacesUtils.asyncHistory.updatePlaces(place, {
-      handleError: () => ok(false, "couldn't add visit to history"),
-      handleResult: function () {},
-      handleCompletion: function () {
-        if (--numLinks == 0 && aCallback)
-          aCallback();
-      }
-    });
-  }
+      PlacesUtils.asyncHistory.updatePlaces(place, {
+        handleError: () => ok(false, "couldn't add visit to history"),
+        handleResult: function () {},
+        handleCompletion: function () {
+          if (--numLinks == 0) {
+            resolve();
+          }
+        }
+      });
+    }
+  });
 }
 
 /**
  * Allows to specify the list of pinned links (that have a fixed position in
  * the grid.
  * @param aLinksPattern the pattern (see below)
  *
  * Example: setPinnedLinks("3,,1")
@@ -350,18 +262,20 @@ function setPinnedLinks(aLinks) {
   NewTabUtils.pinnedLinks.resetCache();
   NewTabUtils.allPages.update();
 }
 
 /**
  * Restore the grid state.
  */
 function restore() {
-  whenPagesUpdated();
-  NewTabUtils.restore();
+  return new Promise(resolve => {
+    whenPagesUpdated().then(resolve);
+    NewTabUtils.restore();
+  });
 }
 
 /**
  * Wait until a given condition becomes true.
  */
 function waitForCondition(aConditionFn, aMaxTries=50, aCheckInterval=100) {
   return new Promise((resolve, reject) => {
     let tries = 0;
@@ -384,420 +298,225 @@ function waitForCondition(aConditionFn, 
 
     tryAgain();
   });
 }
 
 /**
  * Creates a new tab containing 'about:newtab'.
  */
-function addNewTabPageTab() {
-  addNewTabPageTabPromise().then(TestRunner.next);
-}
-
-function addNewTabPageTabPromise() {
-  let deferred = Promise.defer();
-
-  let tab = gWindow.gBrowser.selectedTab = gWindow.gBrowser.addTab("about:newtab");
+function* addNewTabPageTab() {
+  let tab = yield BrowserTestUtils.openNewForegroundTab(gWindow.gBrowser, "about:newtab", false);
   let browser = tab.linkedBrowser;
 
-  function whenNewTabLoaded() {
+  // Wait for the document to become visible in case it was preloaded.
+  yield waitForCondition(() => !browser.contentDocument.hidden)
+
+  yield new Promise(resolve => {
     if (NewTabUtils.allPages.enabled) {
       // Continue when the link cache has been populated.
       NewTabUtils.links.populateCache(function () {
-        deferred.resolve(whenSearchInitDone());
+        whenSearchInitDone().then(resolve);
       });
     } else {
-      deferred.resolve();
+      resolve();
     }
-  }
-
-  // Wait for the new tab page to be loaded.
-  waitForBrowserLoad(browser, function () {
-    // Wait for the document to become visible in case it was preloaded.
-    waitForCondition(() => !browser.contentDocument.hidden).then(whenNewTabLoaded);
   });
 
-  return deferred.promise;
-}
-
-function waitForBrowserLoad(browser, callback = TestRunner.next) {
-  if (browser.contentDocument.readyState == "complete") {
-    executeSoon(callback);
-    return;
-  }
-
-  browser.addEventListener("load", function onLoad() {
-    browser.removeEventListener("load", onLoad, true);
-    executeSoon(callback);
-  }, true);
+  return tab;
 }
 
 /**
  * Compares the current grid arrangement with the given pattern.
  * @param the pattern (see below)
- * @param the array of sites to compare with (optional)
  *
  * Example: checkGrid("3p,2,,1p")
  * Result: We expect the first cell to contain the pinned site 'http://example3.com/'.
  *         The second cell contains 'http://example2.com/'. The third cell is empty.
  *         The fourth cell contains the pinned site 'http://example4.com/'.
  */
-function checkGrid(aSitesPattern, aSites) {
+function* checkGrid(aSitesPattern) {
   let length = aSitesPattern.split(",").length;
-  let sites = (aSites || getGrid().sites).slice(0, length);
-  let current = sites.map(function (aSite) {
-    if (!aSite)
-      return "";
+
+  let foundPattern = 
+    yield ContentTask.spawn(gWindow.gBrowser.selectedBrowser,
+                            { length: length }, function* (args) {
+      let grid = content.wrappedJSObject.gGrid;
 
-    let pinned = aSite.isPinned();
-    let hasPinnedAttr = aSite.node.hasAttribute("pinned");
+      let sites = grid.sites.slice(0, args.length);
+      return sites.map(function (aSite) {
+        if (!aSite)
+          return "";
 
-    if (pinned != hasPinnedAttr)
-      ok(false, "invalid state (site.isPinned() != site[pinned])");
+        let pinned = aSite.isPinned();
+        let hasPinnedAttr = aSite.node.hasAttribute("pinned");
 
-    return aSite.url.replace(/^http:\/\/example(\d+)\.com\/$/, "$1") + (pinned ? "p" : "");
+        if (pinned != hasPinnedAttr)
+          ok(false, "invalid state (site.isPinned() != site[pinned])");
+
+        return aSite.url.replace(/^http:\/\/example(\d+)\.com\/$/, "$1") + (pinned ? "p" : "");
+      });
   });
 
-  is(current, aSitesPattern, "grid status = " + aSitesPattern);
+  is(foundPattern, aSitesPattern, "grid status = " + aSitesPattern);
 }
 
 /**
  * Blocks a site from the grid.
  * @param aIndex The cell index.
  */
 function blockCell(aIndex) {
-  whenPagesUpdated();
-  getCell(aIndex).site.block();
+  return new Promise(resolve => {
+    whenPagesUpdated().then(resolve);
+    performOnCell(aIndex, cell => {
+      return cell.site.block();
+    });
+  });
 }
 
 /**
  * Pins a site on a given position.
  * @param aIndex The cell index.
  * @param aPinIndex The index the defines where the site should be pinned.
  */
-function pinCell(aIndex, aPinIndex) {
-  getCell(aIndex).site.pin(aPinIndex);
+function pinCell(aIndex) {
+  performOnCell(aIndex, cell => {
+    cell.site.pin();
+  });
 }
 
 /**
  * Unpins the given cell's site.
  * @param aIndex The cell index.
  */
 function unpinCell(aIndex) {
-  whenPagesUpdated();
-  getCell(aIndex).site.unpin();
-}
-
-/**
- * Simulates a drag and drop operation.
- * @param aSourceIndex The cell index containing the dragged site.
- * @param aDestIndex The cell index of the drop target.
- */
-function simulateDrop(aSourceIndex, aDestIndex) {
-  let src = getCell(aSourceIndex).site.node;
-  let dest = getCell(aDestIndex).node;
-
-  // Drop 'src' onto 'dest' and continue testing when all newtab
-  // pages have been updated (i.e. the drop operation is completed).
-  startAndCompleteDragOperation(src, dest, whenPagesUpdated);
+  return new Promise(resolve => {
+    whenPagesUpdated().then(resolve);
+    performOnCell(aIndex, cell => {
+      cell.site.unpin();
+    });
+  });
 }
 
 /**
  * Simulates a drag and drop operation. Instead of rearranging a site that is
  * is already contained in the newtab grid, this is used to simulate dragging
  * an external link onto the grid e.g. the text from the URL bar.
  * @param aDestIndex The cell index of the drop target.
  */
-function simulateExternalDrop(aDestIndex) {
-  let dest = getCell(aDestIndex).node;
+function* simulateExternalDrop(aDestIndex) {
+  let pagesUpdatedPromise = whenPagesUpdated();
+
+  yield ContentTask.spawn(gWindow.gBrowser.selectedBrowser, aDestIndex, function*(dropIndex) {
+    return new Promise(resolve => {
+      const url = "data:text/html;charset=utf-8," +
+                  "<a id='link' href='http://example99.com/'>link</a>";
 
-  // Create an iframe that contains the external link we'll drag.
-  createExternalDropIframe().then(iframe => {
-    let link = iframe.contentDocument.getElementById("link");
+      let doc = content.document;
+      let iframe = doc.createElement("iframe");
+
+      function iframeLoaded() {
+        let link = iframe.contentDocument.getElementById("link");
+
+        let dataTransfer = new iframe.contentWindow.DataTransfer("dragstart", false);
+        dataTransfer.mozSetDataAt("text/x-moz-url", "http://example99.com/", 0);
+
+        let event = content.document.createEvent("DragEvents");
+        event.initDragEvent("drop", true, true, content, 0, 0, 0, 0, 0,
+                            false, false, false, false, 0, null, dataTransfer);
 
-    // Drop 'link' onto 'dest'.
-    startAndCompleteDragOperation(link, dest, () => {
-      // Wait until the drop operation is complete
-      // and all newtab pages have been updated.
-      whenPagesUpdated(() => {
-        // Clean up and remove the iframe.
+        let target = content.gGrid.cells[dropIndex].node;
+        target.dispatchEvent(event);
+
         iframe.remove();
-        // Continue testing.
-        TestRunner.next();
+
+        resolve();
+      }
+
+      iframe.addEventListener("load", function onLoad() {
+        iframe.removeEventListener("load", onLoad);
+        content.setTimeout(iframeLoaded, 0);
       });
+
+      iframe.setAttribute("src", url);
+      iframe.style.width = "50px";
+      iframe.style.height = "50px";
+      iframe.style.position = "absolute";
+      iframe.style.zIndex = 50;
+
+      // the frame has to be attached to a visible element
+      let margin = doc.getElementById("newtab-search-container");
+      margin.appendChild(iframe);
     });
   });
-}
 
-/**
- * Starts and complete a drag-and-drop operation.
- * @param aSource The node that is being dragged.
- * @param aDest The node we're dragging aSource onto.
- * @param aCallback The function that is called when we're done.
- */
-function startAndCompleteDragOperation(aSource, aDest, aCallback) {
-  // The implementation of this function varies by platform because each
-  // platform has particular quirks that we need to deal with
-
-  if (isMac) {
-    // On OS X once the drag starts, Cocoa manages the drag session and
-    // gives us a limited amount of time to complete the drag operation. In
-    // some cases as soon as the first mouse-move event is received (the one
-    // that starts the drag session), Cocoa becomes blind to subsequent mouse
-    // events and completes the drag session all by itself. Therefore it is
-    // important that the first mouse-move we send is already positioned at
-    // the destination.
-    synthesizeNativeMouseLDown(aSource);
-    synthesizeNativeMouseDrag(aDest);
-    // In some tests, aSource and aDest are at the same position, so to ensure
-    // a drag session is created (instead of it just turning into a click) we
-    // move the mouse 10 pixels away and then back.
-    synthesizeNativeMouseDrag(aDest, 10);
-    synthesizeNativeMouseDrag(aDest);
-    // Finally, release the drag and have it run the callback when done.
-    synthesizeNativeMouseLUp(aDest).then(aCallback, Cu.reportError);
-  } else if (isWindows) {
-    // on Windows once the drag is initiated, Windows doesn't spin our
-    // message loop at all, so with async event synthesization the async
-    // messages never get processed while a drag is in progress. So if
-    // we did a mousedown followed by a mousemove, we would never be able
-    // to successfully dispatch the mouseup. Instead, we just skip the move
-    // entirely, so and just generate the up at the destination. This way
-    // Windows does the drag and also terminates it right away. Note that
-    // this only works for tests where aSource and aDest are sufficiently
-    // far to trigger a drag, otherwise it may just end up doing a click.
-    synthesizeNativeMouseLDown(aSource);
-    synthesizeNativeMouseLUp(aDest).then(aCallback, Cu.reportError);
-  } else if (isLinux) {
-    // Start by pressing the left mouse button.
-    synthesizeNativeMouseLDown(aSource);
-
-    // Move the mouse in 5px steps until the drag operation starts.
-    // Note that we need to do this with pauses in between otherwise the
-    // synthesized events get coalesced somewhere in the guts of GTK. In order
-    // to successfully initiate a drag session in the case where aSource and
-    // aDest are at the same position, we synthesize a bunch of drags until
-    // we know the drag session has started, and then move to the destination.
-    let offset = 0;
-    let interval = setInterval(() => {
-      synthesizeNativeMouseDrag(aSource, offset += 5);
-    }, 10);
-
-    // When the drag operation has started we'll move
-    // the dragged element to its target position.
-    aSource.addEventListener("dragstart", function onDragStart() {
-      aSource.removeEventListener("dragstart", onDragStart);
-      clearInterval(interval);
-
-      // Place the cursor above the drag target.
-      synthesizeNativeMouseMove(aDest);
-    });
-
-    // As soon as the dragged element hovers the target, we'll drop it.
-    // Note that we need to actually wait for the dragenter event here, because
-    // the mousemove synthesization is "more async" than the mouseup
-    // synthesization - they use different gdk APIs. If we don't wait, the
-    // up could get processed before the moves, dropping the item in the
-    // wrong position.
-    aDest.addEventListener("dragenter", function onDragEnter() {
-      aDest.removeEventListener("dragenter", onDragEnter);
-
-      // Finish the drop operation.
-      synthesizeNativeMouseLUp(aDest).then(aCallback, Cu.reportError);
-    });
-  } else {
-    throw "Unsupported platform";
-  }
-}
-
-/**
- * Helper function that creates a temporary iframe in the about:newtab
- * document. This will contain a link we can drag to the test the dropping
- * of links from external documents.
- */
-function createExternalDropIframe() {
-  const url = "data:text/html;charset=utf-8," +
-              "<a id='link' href='http://example99.com/'>link</a>";
-
-  let deferred = Promise.defer();
-  let doc = getContentDocument();
-  let iframe = doc.createElement("iframe");
-  iframe.setAttribute("src", url);
-  iframe.style.width = "50px";
-  iframe.style.height = "50px";
-  iframe.style.position = "absolute";
-  iframe.style.zIndex = 50;
-
-  // the frame has to be attached to a visible element
-  let margin = doc.getElementById("newtab-search-container");
-  margin.appendChild(iframe);
-
-  iframe.addEventListener("load", function onLoad() {
-    iframe.removeEventListener("load", onLoad);
-    executeSoon(() => deferred.resolve(iframe));
-  });
-
-  return deferred.promise;
-}
-
-/**
- * Fires a synthetic 'mousedown' event on the current about:newtab page.
- * @param aElement The element used to determine the cursor position.
- */
-function synthesizeNativeMouseLDown(aElement) {
-  if (isLinux) {
-    let win = aElement.ownerDocument.defaultView;
-    EventUtils.synthesizeMouseAtCenter(aElement, {type: "mousedown"}, win);
-  } else {
-    let msg = isWindows ? 2 : 1;
-    synthesizeNativeMouseEvent(aElement, msg);
-  }
-}
-
-/**
- * Fires a synthetic 'mouseup' event on the current about:newtab page.
- * @param aElement The element used to determine the cursor position.
- */
-function synthesizeNativeMouseLUp(aElement) {
-  let msg = isWindows ? 4 : (isMac ? 2 : 7);
-  return synthesizeNativeMouseEvent(aElement, msg);
-}
-
-/**
- * Fires a synthetic mouse drag event on the current about:newtab page.
- * @param aElement The element used to determine the cursor position.
- * @param aOffsetX The left offset that is added to the position.
- */
-function synthesizeNativeMouseDrag(aElement, aOffsetX) {
-  let msg = isMac ? 6 : 1;
-  synthesizeNativeMouseEvent(aElement, msg, aOffsetX);
-}
-
-/**
- * Fires a synthetic 'mousemove' event on the current about:newtab page.
- * @param aElement The element used to determine the cursor position.
- */
-function synthesizeNativeMouseMove(aElement) {
-  let msg = isMac ? 5 : 1;
-  synthesizeNativeMouseEvent(aElement, msg);
-}
-
-/**
- * Fires a synthetic mouse event on the current about:newtab page.
- * @param aElement The element used to determine the cursor position.
- * @param aOffsetX The left offset that is added to the position (optional).
- * @param aOffsetY The top offset that is added to the position (optional).
- */
-function synthesizeNativeMouseEvent(aElement, aMsg, aOffsetX = 0, aOffsetY = 0) {
-  return new Promise((resolve, reject) => {
-    let rect = aElement.getBoundingClientRect();
-    let win = aElement.ownerDocument.defaultView;
-    let x = aOffsetX + win.mozInnerScreenX + rect.left + rect.width / 2;
-    let y = aOffsetY + win.mozInnerScreenY + rect.top + rect.height / 2;
-
-    let utils = win.QueryInterface(Ci.nsIInterfaceRequestor)
-                   .getInterface(Ci.nsIDOMWindowUtils);
-
-    let scale = utils.screenPixelsPerCSSPixel;
-    let observer = {
-      observe: function(aSubject, aTopic, aData) {
-        if (aTopic == "mouseevent") {
-          resolve();
-        }
-      }
-    };
-    utils.sendNativeMouseEvent(x * scale, y * scale, aMsg, 0, null, observer);
-  });
-}
-
-/**
- * Sends a custom drag event to a given DOM element.
- * @param aEventType The drag event's type.
- * @param aTarget The DOM element that the event is dispatched to.
- * @param aData The event's drag data (optional).
- */
-function sendDragEvent(aEventType, aTarget, aData) {
-  let event = createDragEvent(aEventType, aData);
-  let ifaceReq = getContentWindow().QueryInterface(Ci.nsIInterfaceRequestor);
-  let windowUtils = ifaceReq.getInterface(Ci.nsIDOMWindowUtils);
-  windowUtils.dispatchDOMEventViaPresShell(aTarget, event, true);
-}
-
-/**
- * Creates a custom drag event.
- * @param aEventType The drag event's type.
- * @param aData The event's drag data (optional).
- * @return The drag event.
- */
-function createDragEvent(aEventType, aData) {
-  let dataTransfer = new (getContentWindow()).DataTransfer("dragstart", false);
-  dataTransfer.mozSetDataAt("text/x-moz-url", aData, 0);
-  let event = getContentDocument().createEvent("DragEvents");
-  event.initDragEvent(aEventType, true, true, getContentWindow(), 0, 0, 0, 0, 0,
-                      false, false, false, false, 0, null, dataTransfer);
-
-  return event;
+  yield pagesUpdatedPromise;
 }
 
 /**
  * Resumes testing when all pages have been updated.
- * @param aCallback Called when done. If not specified, TestRunner.next is used.
  */
-function whenPagesUpdated(aCallback = TestRunner.next) {
-  let page = {
-    observe: _ => _,
+function whenPagesUpdated() {
+  return new Promise(resolve => {
+    let page = {
+      observe: _ => _,
 
-    update() {
-      NewTabUtils.allPages.unregister(this);
-      executeSoon(aCallback);
-    }
-  };
+      update() {
+        NewTabUtils.allPages.unregister(this);
+        executeSoon(resolve);
+      }
+    };
 
-  NewTabUtils.allPages.register(page);
-  registerCleanupFunction(function () {
-    NewTabUtils.allPages.unregister(page);
+    NewTabUtils.allPages.register(page);
+    registerCleanupFunction(function () {
+      NewTabUtils.allPages.unregister(page);
+    });
   });
 }
 
 /**
  * Waits for the response to the page's initial search state request.
  */
 function whenSearchInitDone() {
-  let deferred = Promise.defer();
-  let searchController = getContentWindow().gSearch._contentSearchController;
-  if (searchController.defaultEngine) {
-    return Promise.resolve();
-  }
-  let eventName = "ContentSearchService";
-  getContentWindow().addEventListener(eventName, function onEvent(event) {
-    if (event.detail.type == "State") {
-      getContentWindow().removeEventListener(eventName, onEvent);
-      // Wait for the search controller to receive the event, then resolve.
-      let resolver = function() {
+  return ContentTask.spawn(gWindow.gBrowser.selectedBrowser, {}, function*() {
+    return new Promise(resolve => {
+      if (content.gSearch) {
+        let searchController = content.gSearch._contentSearchController;
         if (searchController.defaultEngine) {
-          deferred.resolve();
+          resolve();
           return;
         }
-        executeSoon(resolver);
       }
-      executeSoon(resolver);
-    }
+
+      let eventName = "ContentSearchService";
+      content.addEventListener(eventName, function onEvent(event) {
+        if (event.detail.type == "State") {
+          content.removeEventListener(eventName, onEvent);
+          let resolver = function() {
+            // Wait for the search controller to receive the event, then resolve.
+            if (content.gSearch._contentSearchController.defaultEngine) {
+              resolve();
+              return;
+            }
+          }
+          content.setTimeout(resolver, 0);
+        }
+      });
+    });
   });
-  return deferred.promise;
 }
 
 /**
  * Changes the newtab customization option and waits for the panel to open and close
  *
  * @param {string} aTheme
  *        Can be any of("blank"|"classic"|"enhanced")
  */
 function customizeNewTabPage(aTheme) {
-  let promise = ContentTask.spawn(gBrowser.selectedBrowser, aTheme, function*(aTheme) {
+  return ContentTask.spawn(gWindow.gBrowser.selectedBrowser, aTheme, function*(aTheme) {
 
     let document = content.document;
     let panel = document.getElementById("newtab-customize-panel");
     let customizeButton = document.getElementById("newtab-customize-button");
 
     function panelOpened(opened) {
       return new Promise( (resolve) => {
         let options = {attributes: true, oldValue: true};
@@ -817,19 +536,19 @@ function customizeNewTabPage(aTheme) {
     let opened = panelOpened(true);
     customizeButton.click();
     yield opened;
 
     let closed = panelOpened(false);
     customizeButton.click();
     yield closed;
   });
-
-  promise.then(TestRunner.next);
 }
 
 /**
  * Reports presence of a scrollbar
  */
 function hasScrollbar() {
-  let docElement = getContentDocument().documentElement;
-  return docElement.scrollHeight > docElement.clientHeight;
+  return ContentTask.spawn(gWindow.gBrowser.selectedBrowser, {}, function* () {
+    let docElement = content.document.documentElement;
+    return docElement.scrollHeight > docElement.clientHeight;
+  });
 }
--- a/browser/components/sessionstore/content/content-sessionStore.js
+++ b/browser/components/sessionstore/content/content-sessionStore.js
@@ -705,17 +705,17 @@ var MessageQueue = {
     Services.prefs.addObserver(TIMEOUT_DISABLED_PREF, this, false);
   },
 
   uninit() {
     Services.prefs.removeObserver(TIMEOUT_DISABLED_PREF, this);
   },
 
   observe(subject, topic, data) {
-    if (topic == TIMEOUT_DISABLED_PREF) {
+    if (topic == "nsPref:changed" && data == TIMEOUT_DISABLED_PREF) {
       this.timeoutDisabled =
         Services.prefs.getBoolPref(TIMEOUT_DISABLED_PREF);
     }
   },
 
   /**
    * Pushes a given |value| onto the queue. The given |key| represents the type
    * of data that is stored and can override data that has been queued before
--- a/build/clang-plugin/clang-plugin.cpp
+++ b/build/clang-plugin/clang-plugin.cpp
@@ -1267,17 +1267,17 @@ void DiagnosticsMatcher::RefCountedInsid
   unsigned errorID = Diag.getDiagnosticIDs()->getCustomDiagID(
       DiagnosticIDs::Error,
       "Refcounted variable %0 of type %1 cannot be captured by a lambda");
   unsigned noteID = Diag.getDiagnosticIDs()->getCustomDiagID(
       DiagnosticIDs::Note, "Please consider using a smart pointer");
   const LambdaExpr *Lambda = Result.Nodes.getNodeAs<LambdaExpr>("lambda");
 
   for (const LambdaCapture Capture : Lambda->captures()) {
-    if (Capture.capturesVariable()) {
+    if (Capture.capturesVariable() && Capture.getCaptureKind() != LCK_ByRef) {
       QualType Pointee = Capture.getCapturedVar()->getType()->getPointeeType();
 
       if (!Pointee.isNull() && isClassRefCounted(Pointee)) {
         Diag.Report(Capture.getLocation(), errorID)
           << Capture.getCapturedVar() << Pointee;
         Diag.Report(Capture.getLocation(), noteID);
       }
     }
--- a/build/clang-plugin/tests/TestNoRefcountedInsideLambdas.cpp
+++ b/build/clang-plugin/tests/TestNoRefcountedInsideLambdas.cpp
@@ -16,29 +16,29 @@ struct R : RefCountedBase {
 };
 
 void take(...);
 void foo() {
   R* ptr;
   SmartPtr<R> sp;
   take([&](R* argptr) {
     R* localptr;
-    ptr->method(); // expected-error{{Refcounted variable 'ptr' of type 'R' cannot be captured by a lambda}} expected-note{{Please consider using a smart pointer}}
+    ptr->method();
     argptr->method();
     localptr->method();
   });
   take([&](SmartPtr<R> argsp) {
     SmartPtr<R> localsp;
     sp->method();
     argsp->method();
     localsp->method();
   });
   take([&](R* argptr) {
     R* localptr;
-    take(ptr); // expected-error{{Refcounted variable 'ptr' of type 'R' cannot be captured by a lambda}} expected-note{{Please consider using a smart pointer}}
+    take(ptr);
     take(argptr);
     take(localptr);
   });
   take([&](SmartPtr<R> argsp) {
     SmartPtr<R> localsp;
     take(sp);
     take(argsp);
     take(localsp);
@@ -86,9 +86,33 @@ void foo() {
     take(localptr);
   });
   take([sp](SmartPtr<R> argsp) {
     SmartPtr<R> localsp;
     take(sp);
     take(argsp);
     take(localsp);
   });
+  take([&ptr](R* argptr) {
+      R* localptr;
+      ptr->method();
+      argptr->method();
+      localptr->method();
+    });
+  take([&sp](SmartPtr<R> argsp) {
+      SmartPtr<R> localsp;
+      sp->method();
+      argsp->method();
+      localsp->method();
+    });
+  take([&ptr](R* argptr) {
+      R* localptr;
+      take(ptr);
+      take(argptr);
+      take(localptr);
+    });
+  take([&sp](SmartPtr<R> argsp) {
+      SmartPtr<R> localsp;
+      take(sp);
+      take(argsp);
+      take(localsp);
+    });
 }
--- a/config/external/nss/Makefile.in
+++ b/config/external/nss/Makefile.in
@@ -252,20 +252,16 @@ DEFAULT_GMAKE_FLAGS += MODULE_INCLUDES='
 
 # Work around NSS's MAKE_OBJDIR being racy. See bug #836220
 DEFAULT_GMAKE_FLAGS += MAKE_OBJDIR='$$(INSTALL) -D $$(OBJDIR)'
 
 # Work around NSS adding IMPORT_LIBRARY to TARGETS with no rule for
 # it, creating race conditions. See bug #836220
 DEFAULT_GMAKE_FLAGS += TARGETS='$$(LIBRARY) $$(SHARED_LIBRARY) $$(PROGRAM)'
 
-ifeq ($(MOZ_FOLD_LIBS),1)
-MOZ_FOLD_LIBS_FLAGS += -D_NSPR_BUILD_=1
-endif
-
 ifdef MOZ_FOLD_LIBS_FLAGS
 DEFAULT_GMAKE_FLAGS += XCFLAGS='$(MOZ_FOLD_LIBS_FLAGS)'
 endif
 
 ifndef WARNINGS_AS_ERRORS
 DEFAULT_GMAKE_FLAGS += NSS_ENABLE_WERROR=0
 endif
 ifeq ($(OS_TARGET),Android)
--- a/config/external/nss/nss.symbols
+++ b/config/external/nss/nss.symbols
@@ -400,16 +400,17 @@ PK11_KeyGenWithTemplate
 PK11_ListCerts
 PK11_ListCertsInSlot
 PK11_ListPrivateKeysInSlot
 PK11_ListPrivKeysInSlot
 PK11_LoadPrivKey
 PK11_Logout
 PK11_LogoutAll
 PK11_MakeIDFromPubKey
+PK11_MapSignKeyType
 PK11_MechanismToAlgtag
 PK11_MergeTokens
 PK11_NeedLogin
 PK11_NeedUserInit
 PK11_ParamFromIV
 PK11_PBEKeyGen
 PK11_PrivDecrypt
 PK11_PrivDecryptPKCS1
@@ -424,21 +425,23 @@ PK11_ReadRawAttribute
 PK11_ReferenceSlot
 PK11_ResetToken
 PK11SDR_Decrypt
 PK11SDR_Encrypt
 PK11_SetPasswordFunc
 PK11_SetSlotPWValues
 PK11_Sign
 PK11_SignatureLen
+PK11_SignWithMechanism
 PK11_UnwrapPrivKey
 PK11_UnwrapSymKey
 PK11_UpdateSlotAttribute
 PK11_UserDisableSlot
 PK11_UserEnableSlot
+PK11_VerifyWithMechanism
 PK11_WrapPrivKey
 PK11_WrapSymKey
 PORT_Alloc
 PORT_Alloc_Util
 PORT_ArenaAlloc
 PORT_ArenaAlloc_Util
 PORT_ArenaGrow_Util
 PORT_ArenaMark_Util
@@ -626,19 +629,21 @@ SEC_QuickDERDecodeItem
 SEC_QuickDERDecodeItem_Util
 SEC_RegisterDefaultHttpClient
 SEC_SignData
 SEC_SignedCertificateTemplate @DATA@
 SEC_StringToOID
 SEC_UTF8StringTemplate @DATA@
 SEC_UTF8StringTemplate_Util @DATA@
 SGN_Begin
+SGN_CreateDigestInfo
 SGN_CreateDigestInfo_Util
 SGN_DecodeDigestInfo
 SGN_DestroyContext
+SGN_DestroyDigestInfo
 SGN_DestroyDigestInfo_Util
 SGN_End
 SGN_NewContext
 SGN_Update
 SSL_AuthCertificateComplete
 SSL_AuthCertificateHook
 SSL_CipherPrefGet
 SSL_CipherPrefSet
--- a/devtools/shared/security/tests/unit/xpcshell.ini
+++ b/devtools/shared/security/tests/unit/xpcshell.ini
@@ -5,12 +5,15 @@ tail =
 firefox-appdir = browser
 skip-if = toolkit == 'gonk' && debug # Bug 1206586
 
 support-files=
   testactors.js
 
 [test_cert.js]
 [test_encryption.js]
+# Failures on B2G emulator debug, B2G emulator-x86-kk
+# See bug 1234972 and bug 1199472
+skip-if = toolkit == 'gonk' && (debug || android_version > '15')
 [test_oob_cert_auth.js]
-# Failures on B2G emulator debug and Android opt
-# See bug 1141544, bug 1163052, and bug 1166032
-skip-if = (toolkit == 'gonk' && debug) || (toolkit == 'android' && !debug)
+# Failures on B2G emulator debug, B2G emulator-x86-kk and Android opt
+# See bug 1141544, bug 1163052, bug 1166032 and bug 1241831
+skip-if = (toolkit == 'gonk' && (debug || android_version > '15')) || (toolkit == 'android' && !debug)
--- a/docshell/base/nsDocShell.cpp
+++ b/docshell/base/nsDocShell.cpp
@@ -12536,23 +12536,19 @@ bool
 nsDocShell::ShouldDiscardLayoutState(nsIHttpChannel* aChannel)
 {
   // By default layout State will be saved.
   if (!aChannel) {
     return false;
   }
 
   // figure out if SH should be saving layout state
-  nsCOMPtr<nsISupports> securityInfo;
-  bool noStore = false, noCache = false;
-  aChannel->GetSecurityInfo(getter_AddRefs(securityInfo));
+  bool noStore = false;
   aChannel->IsNoStoreResponse(&noStore);
-  aChannel->IsNoCacheResponse(&noCache);
-
-  return (noStore || (noCache && securityInfo));
+  return noStore;
 }
 
 NS_IMETHODIMP
 nsDocShell::GetEditor(nsIEditor** aEditor)
 {
   NS_ENSURE_ARG_POINTER(aEditor);
 
   if (!mEditorData) {
--- a/docshell/base/timeline/LayerTimelineMarker.h
+++ b/docshell/base/timeline/LayerTimelineMarker.h
@@ -18,23 +18,23 @@ class LayerTimelineMarker : public Timel
 public:
   explicit LayerTimelineMarker(const nsIntRegion& aRegion)
     : TimelineMarker("Layer", MarkerTracingType::HELPER_EVENT)
     , mRegion(aRegion)
   {}
 
   void AddLayerRectangles(dom::Sequence<dom::ProfileTimelineLayerRect>& aRectangles)
   {
-    nsIntRegionRectIterator it(mRegion);
-    while (const nsIntRect* iterRect = it.Next()) {
+    for (auto iter = mRegion.RectIter(); !iter.Done(); iter.Next()) {
+      const nsIntRect& iterRect = iter.Get();
       dom::ProfileTimelineLayerRect rect;
-      rect.mX = iterRect->X();
-      rect.mY = iterRect->Y();
-      rect.mWidth = iterRect->Width();
-      rect.mHeight = iterRect->Height();
+      rect.mX = iterRect.X();
+      rect.mY = iterRect.Y();
+      rect.mWidth = iterRect.Width();
+      rect.mHeight = iterRect.Height();
       aRectangles.AppendElement(rect, fallible);
     }
   }
 
 private:
   nsIntRegion mRegion;
 };
 
--- a/docshell/test/chrome/bug112564_window.xul
+++ b/docshell/test/chrome/bug112564_window.xul
@@ -79,40 +79,39 @@
       try {
         gTestsIterator.next();
       } catch (err if err instanceof StopIteration) {
         finish();
       }
     }
 
     function testsIterator() {
-      // Load a secure page with a no-store header, followed by a simple page
-      // On pagehide, first page should report it is not being persisted
+      // Load a secure page with a no-cache header, followed by a simple page.
+      // no-cache should not interfere with the bfcache in the way no-store
+      // does.
       var test1DocURI = "https://example.com:443/tests/docshell/test/chrome/112564_nocache.html";
 
       gExpected = [{type: "pagehide", persisted: true},
                    {type: "load", title: "test1"},
                    {type: "pageshow", title: "test1", persisted: false}];
       gBrowser.loadURI(test1DocURI);
       yield undefined;
 
       var test2Doc = "data:text/html,<html><head><title>test2</title></head>" +
                      "<body>test2</body></html>";
 
-      gExpected = [{type: "pagehide", title: "test1", persisted: false},
-                   {type: "unload", title: "test1"},
+      gExpected = [{type: "pagehide", title: "test1", persisted: true},
                    {type: "load", title: "test2"},
                    {type: "pageshow", title: "test2", persisted: false}];
       gBrowser.loadURI(test2Doc);
       yield undefined;
 
-      // Now go back in history. First page should not have been cached.
+      // Now go back in history. First page has been cached.
       // Check persisted property to confirm
       gExpected = [{type: "pagehide", title: "test2", persisted: true},
-                   {type: "load", title: "test1"},
-                   {type: "pageshow", title: "test1", persisted: false}];
+                   {type: "pageshow", title: "test1", persisted: true}];
       gBrowser.goBack();
       yield undefined;
     }
   ]]></script>
 
   <browser type="content-primary" flex="1" id="content" src="about:blank"/>
 </window>
--- a/docshell/test/chrome/bug215405_window.xul
+++ b/docshell/test/chrome/bug215405_window.xul
@@ -108,17 +108,18 @@
          " vertical axis scroll position not correctly restored");
       var formValue = gBrowser.contentDocument.getElementById("inp").value;
       isnot(formValue, text, testName + " form value incorrectly restored");
 
     
       // https no-cache
       testName = "[nocache]";
 
-      // Load a page with a no-cache header
+      // Load a page with a no-cache header. This should not be
+      // restricted like no-store (bug 567365)
       gBrowser.loadURI(nocacheURI);
       yield undefined;
 
 
       // Now that the page has loaded, amend the form contents
       form = gBrowser.contentDocument.getElementById("inp");
       form.value = text;
 
@@ -136,29 +137,29 @@
             testName + " failed to scroll window horizontally");
       isnot(scrollY, originalYPosition,
             testName + " failed to scroll window vertically");
 
       gBrowser.loadURI(simple);
       yield undefined;
 
 
-      // Now go back in history. First page should not have been cached.
+      // Now go back in history to the cached page.
       gBrowser.goBack();
       yield undefined;
 
 
-      // First uncacheable page will now be reloaded. Check scroll position
-      // restored, and form contents not
+      // First page will now be reloaded. Check scroll position
+      // and form contents are restored
       is(gBrowser.contentWindow.scrollX, scrollX, testName +
          " horizontal axis scroll position not correctly restored");
       is(gBrowser.contentWindow.scrollY, scrollY, testName +
          " vertical axis scroll position not correctly restored");
       var formValue = gBrowser.contentDocument.getElementById("inp").value;
-      isnot(formValue, text, testName + " form value incorrectly restored");
+      is(formValue, text, testName + " form value not correctly restored");
       
       // nextTest has to be called from here, as no events are fired in this
       // step
       setTimeout(nextTest, 0);
       yield undefined;
     }
   ]]></script>
 
--- a/dom/base/FileReader.cpp
+++ b/dom/base/FileReader.cpp
@@ -8,16 +8,17 @@
 
 #include "nsIEventTarget.h"
 #include "nsIGlobalObject.h"
 #include "nsITimer.h"
 #include "nsITransport.h"
 #include "nsIStreamTransportService.h"
 
 #include "mozilla/Base64.h"
+#include "mozilla/CheckedInt.h"
 #include "mozilla/dom/DOMError.h"
 #include "mozilla/dom/EncodingUtils.h"
 #include "mozilla/dom/File.h"
 #include "mozilla/dom/FileReaderBinding.h"
 #include "mozilla/dom/ProgressEvent.h"
 #include "nsContentUtils.h"
 #include "nsCycleCollectionParticipant.h"
 #include "nsDOMJSUtils.h"
@@ -310,21 +311,27 @@ FileReader::DoReadData(uint64_t aCount)
     NS_ENSURE_TRUE(buf, NS_ERROR_OUT_OF_MEMORY);
 
     uint32_t bytesRead = 0;
     mAsyncStream->ReadSegments(ReadFuncBinaryString, buf + oldLen, aCount,
                                &bytesRead);
     NS_ASSERTION(bytesRead == aCount, "failed to read data");
   }
   else {
+    CheckedInt<uint64_t> size = mDataLen;
+    size += aCount;
+
     //Update memory buffer to reflect the contents of the file
-    if (mDataLen + aCount > UINT32_MAX) {
-      // PR_Realloc doesn't support over 4GB memory size even if 64-bit OS
+    if (!size.isValid() ||
+        // PR_Realloc doesn't support over 4GB memory size even if 64-bit OS
+        size.value() > UINT32_MAX ||
+        size.value() > mTotal) {
       return NS_ERROR_OUT_OF_MEMORY;
     }
+
     if (mDataFormat != FILE_AS_ARRAYBUFFER) {
       mFileData = (char *) realloc(mFileData, mDataLen + aCount);
       NS_ENSURE_TRUE(mFileData, NS_ERROR_OUT_OF_MEMORY);
     }
 
     uint32_t bytesRead = 0;
     mAsyncStream->Read(mFileData + mDataLen, aCount, &bytesRead);
     NS_ASSERTION(bytesRead == aCount, "failed to read data");
--- a/dom/base/FormData.cpp
+++ b/dom/base/FormData.cpp
@@ -338,30 +338,17 @@ FormData::Constructor(const GlobalObject
 NS_IMETHODIMP
 FormData::GetSendInfo(nsIInputStream** aBody, uint64_t* aContentLength,
                       nsACString& aContentType, nsACString& aCharset)
 {
   nsFSMultipartFormData fs(NS_LITERAL_CSTRING("UTF-8"), nullptr);
 
   for (uint32_t i = 0; i < mFormData.Length(); ++i) {
     if (mFormData[i].value.IsBlob()) {
-      RefPtr<File> file = mFormData[i].value.GetAsBlob()->ToFile();
-      if (file) {
-        fs.AddNameBlobPair(mFormData[i].name, file);
-        continue;
-      }
-
-      ErrorResult rv;
-      file =
-        mFormData[i].value.GetAsBlob()->ToFile(NS_LITERAL_STRING("blob"), rv);
-      if (NS_WARN_IF(rv.Failed())) {
-        return rv.StealNSResult();
-      }
-
-      fs.AddNameBlobPair(mFormData[i].name, file);
+      fs.AddNameBlobPair(mFormData[i].name, mFormData[i].value.GetAsBlob());
     } else if (mFormData[i].value.IsUSVString()) {
       fs.AddNameValuePair(mFormData[i].name,
                           mFormData[i].value.GetAsUSVString());
     } else {
       MOZ_CRASH("This should no be possible.");
     }
   }
 
--- a/dom/base/test/browser.ini
+++ b/dom/base/test/browser.ini
@@ -19,8 +19,10 @@ tags = mcb
 [browser_pagehide_on_tab_close.js]
 skip-if = e10s # this tests non-e10s behavior. it's not expected to work in e10s.
 [browser_messagemanager_unload.js]
 [browser_state_notifications.js]
 # skip-if = e10s # Bug ?????? - content-document-* notifications come while document's URI is still about:blank, but test expects real URL.
 skip-if = true # Intermittent failures - bug 987493. Restore the skip-if above once fixed
 [browser_bug1058164.js]
 [browser_use_counters.js]
+[browser_bug1238440.js]
+skip-if = e10s
new file mode 100644
--- /dev/null
+++ b/dom/base/test/browser_bug1238440.js
@@ -0,0 +1,76 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+Components.utils.import("resource://gre/modules/NetUtil.jsm");
+Components.utils.import("resource://gre/modules/FileUtils.jsm");
+
+waitForExplicitFinish();
+
+const PAGE = "data:text/html,<html><body><input type=\"file\"/></body></html>";
+
+function writeFile(file, text) {
+  return new Promise((resolve) => {
+    let converter = Components.classes["@mozilla.org/intl/scriptableunicodeconverter"]
+                              .createInstance(Components.interfaces.nsIScriptableUnicodeConverter);
+    converter.charset = "UTF-8";
+
+    let ostream = FileUtils.openSafeFileOutputStream(file);
+    let istream = converter.convertToInputStream(text);
+    NetUtil.asyncCopy(istream, ostream, function(status) {
+      if (!Components.isSuccessCode(status)) throw 'fail';
+      resolve();
+    });
+  });
+}
+
+function runFileReader(input, status) {
+  return new Promise((resolve) => {
+    let fr = new FileReader();
+    fr.onload = function() {
+      ok(status, "FileReader called onload");
+      resolve();
+    }
+
+    fr.onerror = function(e) {
+      e.preventDefault();
+      ok(!status, "FileReader called onerror");
+      resolve();
+    }
+
+    fr.readAsArrayBuffer(input);
+  });
+}
+
+add_task(function() {
+  info("Creating a temporary file...");
+  let file = FileUtils.getFile("TmpD", ["bug1238440.txt"]);
+  yield writeFile(file, "hello world");
+
+  info("Opening a tab...");
+  let tab = gBrowser.addTab(PAGE);
+  gBrowser.selectedTab = tab;
+  let browser = tab.linkedBrowser;
+  yield BrowserTestUtils.browserLoaded(browser);
+
+  info("Populating the form...");
+  let doc = browser.contentDocument;
+  let input = doc.querySelector('input');
+  input.value = file.path;
+
+  info("Running the FileReader...");
+  yield runFileReader(input.files[0], true);
+
+  info("Writing the temporary file again...");
+  yield writeFile(file, "hello world-----------------------------");
+
+  info("Running the FileReader again...");
+  yield runFileReader(input.files[0], false);
+
+  info("Closing the tab...");
+  gBrowser.removeTab(gBrowser.selectedTab);
+
+  ok(true, "we didn't crash.");
+});
--- a/dom/base/test/fileutils.js
+++ b/dom/base/test/fileutils.js
@@ -31,17 +31,17 @@ function testFile(file, contents, test) 
 
   // Send file to server using FormData and XMLHttpRequest
   xhr = new XMLHttpRequest();
   xhr.onload = function(event) {
     checkMPSubmission(JSON.parse(event.target.responseText),
                       [{ name: "hello", value: "world"},
                        { name: "myfile",
                          value: contents,
-                         fileName: file.name || "blob",
+                         fileName: file.name || "",
                          contentType: file.type || "application/octet-stream" }]);
     testHasRun();
   }
   xhr.open("POST", "../../../dom/html/test/form_submit_server.sjs");
   var fd = new FormData;
   fd.append("hello", "world");
   fd.append("myfile", file);
   xhr.send(fd);
--- a/dom/crypto/KeyAlgorithmProxy.cpp
+++ b/dom/crypto/KeyAlgorithmProxy.cpp
@@ -188,16 +188,29 @@ KeyAlgorithmProxy::JwkAlg() const
       return NS_LITERAL_STRING(JWK_ALG_RSA_OAEP_256);
     } else if (hashName.EqualsLiteral(WEBCRYPTO_ALG_SHA384)) {
       return NS_LITERAL_STRING(JWK_ALG_RSA_OAEP_384);
     } else if (hashName.EqualsLiteral(WEBCRYPTO_ALG_SHA512)) {
       return NS_LITERAL_STRING(JWK_ALG_RSA_OAEP_512);
     }
   }
 
+  if (mName.EqualsLiteral(WEBCRYPTO_ALG_RSA_PSS)) {
+    nsString hashName = mRsa.mHash.mName;
+    if (hashName.EqualsLiteral(WEBCRYPTO_ALG_SHA1)) {
+      return NS_LITERAL_STRING(JWK_ALG_PS1);
+    } else if (hashName.EqualsLiteral(WEBCRYPTO_ALG_SHA256)) {
+      return NS_LITERAL_STRING(JWK_ALG_PS256);
+    } else if (hashName.EqualsLiteral(WEBCRYPTO_ALG_SHA384)) {
+      return NS_LITERAL_STRING(JWK_ALG_PS384);
+    } else if (hashName.EqualsLiteral(WEBCRYPTO_ALG_SHA512)) {
+      return NS_LITERAL_STRING(JWK_ALG_PS512);
+    }
+  }
+
   return nsString();
 }
 
 CK_MECHANISM_TYPE
 KeyAlgorithmProxy::GetMechanism(const KeyAlgorithm& aAlgorithm)
 {
   // For everything but HMAC, the name determines the mechanism
   // HMAC is handled by the specialization below
--- a/dom/crypto/WebCryptoCommon.h
+++ b/dom/crypto/WebCryptoCommon.h
@@ -18,19 +18,21 @@
 #define WEBCRYPTO_ALG_AES_CTR       "AES-CTR"
 #define WEBCRYPTO_ALG_AES_GCM       "AES-GCM"
 #define WEBCRYPTO_ALG_AES_KW        "AES-KW"
 #define WEBCRYPTO_ALG_SHA1          "SHA-1"
 #define WEBCRYPTO_ALG_SHA256        "SHA-256"
 #define WEBCRYPTO_ALG_SHA384        "SHA-384"
 #define WEBCRYPTO_ALG_SHA512        "SHA-512"
 #define WEBCRYPTO_ALG_HMAC          "HMAC"
+#define WEBCRYPTO_ALG_HKDF          "HKDF"
 #define WEBCRYPTO_ALG_PBKDF2        "PBKDF2"
 #define WEBCRYPTO_ALG_RSASSA_PKCS1  "RSASSA-PKCS1-v1_5"
 #define WEBCRYPTO_ALG_RSA_OAEP      "RSA-OAEP"
+#define WEBCRYPTO_ALG_RSA_PSS       "RSA-PSS"
 #define WEBCRYPTO_ALG_ECDH          "ECDH"
 #define WEBCRYPTO_ALG_ECDSA         "ECDSA"
 #define WEBCRYPTO_ALG_DH            "DH"
 
 // WebCrypto key formats
 #define WEBCRYPTO_KEY_FORMAT_RAW    "raw"
 #define WEBCRYPTO_KEY_FORMAT_PKCS8  "pkcs8"
 #define WEBCRYPTO_KEY_FORMAT_SPKI   "spki"
@@ -81,16 +83,20 @@
 #define JWK_ALG_RS1                 "RS1"      // RSASSA-PKCS1
 #define JWK_ALG_RS256               "RS256"
 #define JWK_ALG_RS384               "RS384"
 #define JWK_ALG_RS512               "RS512"
 #define JWK_ALG_RSA_OAEP            "RSA-OAEP" // RSA-OAEP
 #define JWK_ALG_RSA_OAEP_256        "RSA-OAEP-256"
 #define JWK_ALG_RSA_OAEP_384        "RSA-OAEP-384"
 #define JWK_ALG_RSA_OAEP_512        "RSA-OAEP-512"
+#define JWK_ALG_PS1                 "PS1"      // RSA-PSS
+#define JWK_ALG_PS256               "PS256"
+#define JWK_ALG_PS384               "PS384"
+#define JWK_ALG_PS512               "PS512"
 #define JWK_ALG_ECDSA_P_256         "ES256"
 #define JWK_ALG_ECDSA_P_384         "ES384"
 #define JWK_ALG_ECDSA_P_521         "ES521"
 
 // JWK usages
 #define JWK_USE_ENC                 "enc"
 #define JWK_USE_SIG                 "sig"
 
@@ -199,16 +205,18 @@ MapAlgorithmNameToMechanism(const nsStri
   } else if (aName.EqualsLiteral(WEBCRYPTO_ALG_SHA512)) {
     mechanism = CKM_SHA512;
   } else if (aName.EqualsLiteral(WEBCRYPTO_ALG_PBKDF2)) {
     mechanism = CKM_PKCS5_PBKD2;
   } else if (aName.EqualsLiteral(WEBCRYPTO_ALG_RSASSA_PKCS1)) {
     mechanism = CKM_RSA_PKCS;
   } else if (aName.EqualsLiteral(WEBCRYPTO_ALG_RSA_OAEP)) {
     mechanism = CKM_RSA_PKCS_OAEP;
+  } else if (aName.EqualsLiteral(WEBCRYPTO_ALG_RSA_PSS)) {
+    mechanism = CKM_RSA_PKCS_PSS;
   } else if (aName.EqualsLiteral(WEBCRYPTO_ALG_ECDH)) {
     mechanism = CKM_ECDH1_DERIVE;
   } else if (aName.EqualsLiteral(WEBCRYPTO_ALG_DH)) {
     mechanism = CKM_DH_PKCS_DERIVE;
   }
 
   return mechanism;
 }
@@ -233,22 +241,26 @@ NormalizeToken(const nsString& aName, ns
   } else if (NORMALIZED_EQUALS(aName, WEBCRYPTO_ALG_SHA256)) {
     aDest.AssignLiteral(WEBCRYPTO_ALG_SHA256);
   } else if (NORMALIZED_EQUALS(aName, WEBCRYPTO_ALG_SHA384)) {
     aDest.AssignLiteral(WEBCRYPTO_ALG_SHA384);
   } else if (NORMALIZED_EQUALS(aName, WEBCRYPTO_ALG_SHA512)) {
     aDest.AssignLiteral(WEBCRYPTO_ALG_SHA512);
   } else if (NORMALIZED_EQUALS(aName, WEBCRYPTO_ALG_HMAC)) {
     aDest.AssignLiteral(WEBCRYPTO_ALG_HMAC);
+  } else if (NORMALIZED_EQUALS(aName, WEBCRYPTO_ALG_HKDF)) {
+    aDest.AssignLiteral(WEBCRYPTO_ALG_HKDF);
   } else if (NORMALIZED_EQUALS(aName, WEBCRYPTO_ALG_PBKDF2)) {
     aDest.AssignLiteral(WEBCRYPTO_ALG_PBKDF2);
   } else if (NORMALIZED_EQUALS(aName, WEBCRYPTO_ALG_RSASSA_PKCS1)) {
     aDest.AssignLiteral(WEBCRYPTO_ALG_RSASSA_PKCS1);
   } else if (NORMALIZED_EQUALS(aName, WEBCRYPTO_ALG_RSA_OAEP)) {
     aDest.AssignLiteral(WEBCRYPTO_ALG_RSA_OAEP);
+  } else if (NORMALIZED_EQUALS(aName, WEBCRYPTO_ALG_RSA_PSS)) {
+    aDest.AssignLiteral(WEBCRYPTO_ALG_RSA_PSS);
   } else if (NORMALIZED_EQUALS(aName, WEBCRYPTO_ALG_ECDH)) {
     aDest.AssignLiteral(WEBCRYPTO_ALG_ECDH);
   } else if (NORMALIZED_EQUALS(aName, WEBCRYPTO_ALG_ECDSA)) {
     aDest.AssignLiteral(WEBCRYPTO_ALG_ECDSA);
   } else if (NORMALIZED_EQUALS(aName, WEBCRYPTO_ALG_DH)) {
     aDest.AssignLiteral(WEBCRYPTO_ALG_DH);
   // Named curve values
   } else if (NORMALIZED_EQUALS(aName, WEBCRYPTO_NAMED_CURVE_P256)) {
--- a/dom/crypto/WebCryptoTask.cpp
+++ b/dom/crypto/WebCryptoTask.cpp
@@ -15,16 +15,30 @@
 #include "mozilla/dom/CryptoBuffer.h"
 #include "mozilla/dom/CryptoKey.h"
 #include "mozilla/dom/KeyAlgorithmProxy.h"
 #include "mozilla/dom/TypedArray.h"
 #include "mozilla/dom/WebCryptoCommon.h"
 #include "mozilla/dom/WebCryptoTask.h"
 #include "mozilla/dom/WebCryptoThreadPool.h"
 
+// Template taken from security/nss/lib/util/templates.c
+// This (or SGN_EncodeDigestInfo) would ideally be exported
+// by NSS and until that happens we have to keep our own copy.
+const SEC_ASN1Template SGN_DigestInfoTemplate[] = {
+    { SEC_ASN1_SEQUENCE,
+      0, NULL, sizeof(SGNDigestInfo) },
+    { SEC_ASN1_INLINE,
+      offsetof(SGNDigestInfo,digestAlgorithm),
+      SEC_ASN1_GET(SECOID_AlgorithmIDTemplate) },
+    { SEC_ASN1_OCTET_STRING,
+      offsetof(SGNDigestInfo,digest) },
+    { 0, }
+};
+
 namespace mozilla {
 namespace dom {
 
 // Pre-defined identifiers for telemetry histograms
 
 enum TelemetryMethod {
   TM_ENCRYPT      = 0,
   TM_DECRYPT      = 1,
@@ -65,16 +79,17 @@ enum TelemetryAlgorithm {
   TA_SHA_256         = 16,
   TA_SHA_384         = 17,
   TA_SHA_512         = 18,
   // Later additions
   TA_AES_KW          = 19,
   TA_ECDH            = 20,
   TA_PBKDF2          = 21,
   TA_ECDSA           = 22,
+  TA_HKDF            = 23,
 };
 
 // Convenience functions for extracting / converting information
 
 // OOM-safe CryptoBuffer initialization, suitable for constructors
 #define ATTEMPT_BUFFER_INIT(dst, src) \
   if (!dst.Assign(src)) { \
     mEarlyRv = NS_ERROR_DOM_UNKNOWN_ERR; \
@@ -259,16 +274,51 @@ MapOIDTagToNamedCurve(SECOidTag aOIDTag,
       break;
     default:
       return false;
   }
 
   return true;
 }
 
+inline SECOidTag
+MapHashAlgorithmNameToOID(const nsString& aName)
+{
+  SECOidTag hashOID(SEC_OID_UNKNOWN);
+
+  if (aName.EqualsLiteral(WEBCRYPTO_ALG_SHA1)) {
+    hashOID = SEC_OID_SHA1;
+  } else if (aName.EqualsLiteral(WEBCRYPTO_ALG_SHA256)) {
+    hashOID = SEC_OID_SHA256;
+  } else if (aName.EqualsLiteral(WEBCRYPTO_ALG_SHA384)) {
+    hashOID = SEC_OID_SHA384;
+  } else if (aName.EqualsLiteral(WEBCRYPTO_ALG_SHA512)) {
+    hashOID = SEC_OID_SHA512;
+  }
+
+  return hashOID;
+}
+
+inline CK_MECHANISM_TYPE
+MapHashAlgorithmNameToMgfMechanism(const nsString& aName) {
+  CK_MECHANISM_TYPE mech(UNKNOWN_CK_MECHANISM);
+
+  if (aName.EqualsLiteral(WEBCRYPTO_ALG_SHA1)) {
+    mech = CKG_MGF1_SHA1;
+  } else if (aName.EqualsLiteral(WEBCRYPTO_ALG_SHA256)) {
+    mech = CKG_MGF1_SHA256;
+  } else if (aName.EqualsLiteral(WEBCRYPTO_ALG_SHA384)) {
+    mech = CKG_MGF1_SHA384;
+  } else if (aName.EqualsLiteral(WEBCRYPTO_ALG_SHA512)) {
+    mech = CKG_MGF1_SHA512;
+  }
+
+  return mech;
+}
+
 // Helper function to clone data from an ArrayBuffer or ArrayBufferView object
 inline bool
 CloneData(JSContext* aCx, CryptoBuffer& aDst, JS::Handle<JSObject*> aSrc)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   // Try ArrayBuffer
   RootedTypedArray<ArrayBuffer> ab(aCx);
@@ -833,34 +883,25 @@ public:
       }
 
       if (params.mLabel.WasPassed()) {
         ATTEMPT_BUFFER_INIT(mLabel, params.mLabel.Value());
       }
     }
     // Otherwise mLabel remains the empty octet string, as intended
 
-    // Look up the MGF based on the KeyAlgorithm.
-    // static_cast is safe because we only get here if the algorithm name
-    // is RSA-OAEP, and that only happens if we've constructed
-    // an RsaHashedKeyAlgorithm.
-    mHashMechanism = KeyAlgorithmProxy::GetMechanism(aKey.Algorithm().mRsa.mHash);
-
-    switch (mHashMechanism) {
-      case CKM_SHA_1:
-        mMgfMechanism = CKG_MGF1_SHA1; break;
-      case CKM_SHA256:
-        mMgfMechanism = CKG_MGF1_SHA256; break;
-      case CKM_SHA384:
-        mMgfMechanism = CKG_MGF1_SHA384; break;
-      case CKM_SHA512:
-        mMgfMechanism = CKG_MGF1_SHA512; break;
-      default:
-        mEarlyRv = NS_ERROR_DOM_NOT_SUPPORTED_ERR;
-        return;
+    KeyAlgorithm& hashAlg = aKey.Algorithm().mRsa.mHash;
+    mHashMechanism = KeyAlgorithmProxy::GetMechanism(hashAlg);
+    mMgfMechanism = MapHashAlgorithmNameToMgfMechanism(hashAlg.mName);
+
+    // Check we found appropriate mechanisms.
+    if (mHashMechanism == UNKNOWN_CK_MECHANISM ||
+        mMgfMechanism == UNKNOWN_CK_MECHANISM) {
+      mEarlyRv = NS_ERROR_DOM_NOT_SUPPORTED_ERR;
+      return;
     }
   }
 
 private:
   CK_MECHANISM_TYPE mHashMechanism;
   CK_MECHANISM_TYPE mMgfMechanism;
   ScopedSECKEYPrivateKey mPrivKey;
   ScopedSECKEYPublicKey mPubKey;
@@ -1035,165 +1076,192 @@ class AsymmetricSignVerifyTask : public 
 public:
   AsymmetricSignVerifyTask(JSContext* aCx,
                            const ObjectOrString& aAlgorithm,
                            CryptoKey& aKey,
                            const CryptoOperationData& aSignature,
                            const CryptoOperationData& aData,
                            bool aSign)
     : mOidTag(SEC_OID_UNKNOWN)
+    , mHashMechanism(UNKNOWN_CK_MECHANISM)
+    , mMgfMechanism(UNKNOWN_CK_MECHANISM)
     , mPrivKey(aKey.GetPrivateKey())
     , mPubKey(aKey.GetPublicKey())
+    , mSaltLength(0)
     , mSign(aSign)
     , mVerified(false)
-    , mEcdsa(false)
+    , mAlgorithm(Algorithm::UNKNOWN)
   {
     ATTEMPT_BUFFER_INIT(mData, aData);
     if (!aSign) {
       ATTEMPT_BUFFER_INIT(mSignature, aSignature);
     }
 
     nsString algName;
+    nsString hashAlgName;
     mEarlyRv = GetAlgorithmName(aCx, aAlgorithm, algName);
     if (NS_FAILED(mEarlyRv)) {
       return;
     }
 
-    // Look up the SECOidTag
     if (algName.EqualsLiteral(WEBCRYPTO_ALG_RSASSA_PKCS1)) {
-      mEcdsa = false;
+      mAlgorithm = Algorithm::RSA_PKCS1;
       Telemetry::Accumulate(Telemetry::WEBCRYPTO_ALG, TA_RSASSA_PKCS1);
       CHECK_KEY_ALGORITHM(aKey.Algorithm(), WEBCRYPTO_ALG_RSASSA_PKCS1);
-
-      // For RSA, the hash name comes from the key algorithm
-      nsString hashName = aKey.Algorithm().mRsa.mHash.mName;
-      switch (MapAlgorithmNameToMechanism(hashName)) {
-        case CKM_SHA_1:
-          mOidTag = SEC_OID_PKCS1_SHA1_WITH_RSA_ENCRYPTION; break;
-        case CKM_SHA256:
-          mOidTag = SEC_OID_PKCS1_SHA256_WITH_RSA_ENCRYPTION; break;
-        case CKM_SHA384:
-          mOidTag = SEC_OID_PKCS1_SHA384_WITH_RSA_ENCRYPTION; break;
-        case CKM_SHA512:
-          mOidTag = SEC_OID_PKCS1_SHA512_WITH_RSA_ENCRYPTION; break;
-        default:
-          mEarlyRv = NS_ERROR_DOM_NOT_SUPPORTED_ERR;
-          return;
+      hashAlgName = aKey.Algorithm().mRsa.mHash.mName;
+    } else if (algName.EqualsLiteral(WEBCRYPTO_ALG_RSA_PSS)) {
+      mAlgorithm = Algorithm::RSA_PSS;
+      Telemetry::Accumulate(Telemetry::WEBCRYPTO_ALG, TA_RSA_PSS);
+      CHECK_KEY_ALGORITHM(aKey.Algorithm(), WEBCRYPTO_ALG_RSA_PSS);
+
+      KeyAlgorithm& hashAlg = aKey.Algorithm().mRsa.mHash;
+      hashAlgName = hashAlg.mName;
+      mHashMechanism = KeyAlgorithmProxy::GetMechanism(hashAlg);
+      mMgfMechanism = MapHashAlgorithmNameToMgfMechanism(hashAlgName);
+
+      // Check we found appropriate mechanisms.
+      if (mHashMechanism == UNKNOWN_CK_MECHANISM ||
+          mMgfMechanism == UNKNOWN_CK_MECHANISM) {
+        mEarlyRv = NS_ERROR_DOM_NOT_SUPPORTED_ERR;
+        return;
       }
+
+      RootedDictionary<RsaPssParams> params(aCx);
+      mEarlyRv = Coerce(aCx, params, aAlgorithm);
+      if (NS_FAILED(mEarlyRv)) {
+        mEarlyRv = NS_ERROR_DOM_SYNTAX_ERR;
+        return;
+      }
+
+      mSaltLength = params.mSaltLength;
     } else if (algName.EqualsLiteral(WEBCRYPTO_ALG_ECDSA)) {
-      mEcdsa = true;
+      mAlgorithm = Algorithm::ECDSA;
       Telemetry::Accumulate(Telemetry::WEBCRYPTO_ALG, TA_ECDSA);
       CHECK_KEY_ALGORITHM(aKey.Algorithm(), WEBCRYPTO_ALG_ECDSA);
 
       // For ECDSA, the hash name comes from the algorithm parameter
       RootedDictionary<EcdsaParams> params(aCx);
       mEarlyRv = Coerce(aCx, params, aAlgorithm);
       if (NS_FAILED(mEarlyRv)) {
         mEarlyRv = NS_ERROR_DOM_SYNTAX_ERR;
         return;
       }
 
-      nsString hashName;
-      mEarlyRv = GetAlgorithmName(aCx, params.mHash, hashName);
+      mEarlyRv = GetAlgorithmName(aCx, params.mHash, hashAlgName);
       if (NS_FAILED(mEarlyRv)) {
         mEarlyRv = NS_ERROR_DOM_SYNTAX_ERR;
         return;
       }
-
-      CK_MECHANISM_TYPE hashMechanism = MapAlgorithmNameToMechanism(hashName);
-      if (hashMechanism == UNKNOWN_CK_MECHANISM) {
-        mEarlyRv = NS_ERROR_DOM_SYNTAX_ERR;
-        return;
-      }
-
-      switch (hashMechanism) {
-        case CKM_SHA_1:
-          mOidTag = SEC_OID_ANSIX962_ECDSA_SHA1_SIGNATURE; break;
-        case CKM_SHA256:
-          mOidTag = SEC_OID_ANSIX962_ECDSA_SHA256_SIGNATURE; break;
-        case CKM_SHA384:
-          mOidTag = SEC_OID_ANSIX962_ECDSA_SHA384_SIGNATURE; break;
-        case CKM_SHA512:
-          mOidTag = SEC_OID_ANSIX962_ECDSA_SHA512_SIGNATURE; break;
-        default:
-          mEarlyRv = NS_ERROR_DOM_NOT_SUPPORTED_ERR;
-          return;
-      }
     } else {
       // This shouldn't happen; CreateSignVerifyTask shouldn't create
       // one of these unless it's for the above algorithms.
       MOZ_ASSERT(false);
     }
 
+    // Must have a valid algorithm by now.
+    MOZ_ASSERT(mAlgorithm != Algorithm::UNKNOWN);
+
+    // Determine hash algorithm to use.
+    mOidTag = MapHashAlgorithmNameToOID(hashAlgName);
+    if (mOidTag == SEC_OID_UNKNOWN) {
+      mEarlyRv = NS_ERROR_DOM_NOT_SUPPORTED_ERR;
+      return;
+    }
+
     // Check that we have the appropriate key
     if ((mSign && !mPrivKey) || (!mSign && !mPubKey)) {
       mEarlyRv = NS_ERROR_DOM_INVALID_ACCESS_ERR;
       return;
     }
   }
 
 private:
   SECOidTag mOidTag;
+  CK_MECHANISM_TYPE mHashMechanism;
+  CK_MECHANISM_TYPE mMgfMechanism;
   ScopedSECKEYPrivateKey mPrivKey;
   ScopedSECKEYPublicKey mPubKey;
   CryptoBuffer mSignature;
   CryptoBuffer mData;
+  uint32_t mSaltLength;
   bool mSign;
   bool mVerified;
-  bool mEcdsa;
+
+  // The signature algorithm to use.
+  enum class Algorithm: uint8_t {ECDSA, RSA_PKCS1, RSA_PSS, UNKNOWN};
+  Algorithm mAlgorithm;
 
   virtual nsresult DoCrypto() override
   {
-    nsresult rv;
-    if (mSign) {
-      ScopedSECItem signature(::SECITEM_AllocItem(nullptr, nullptr, 0));
-      if (!signature.get()) {
+    SECStatus rv;
+    ScopedSECItem hash(::SECITEM_AllocItem(nullptr, nullptr,
+                                           HASH_ResultLenByOidTag(mOidTag)));
+    if (!hash) {
+      return NS_ERROR_DOM_OPERATION_ERR;
+    }
+
+    // Compute digest over given data.
+    rv = PK11_HashBuf(mOidTag, hash->data, mData.Elements(), mData.Length());
+    NS_ENSURE_SUCCESS(MapSECStatus(rv), NS_ERROR_DOM_OPERATION_ERR);
+
+    // Wrap hash in a digest info template (RSA-PKCS1 only).
+    if (mAlgorithm == Algorithm::RSA_PKCS1) {
+      ScopedSGNDigestInfo di(SGN_CreateDigestInfo(mOidTag, hash->data, hash->len));
+      if (!di) {
+        return NS_ERROR_DOM_OPERATION_ERR;
+      }
+
+      // Reuse |hash|.
+      SECITEM_FreeItem(hash, false);
+      if (!SEC_ASN1EncodeItem(nullptr, hash, di, SGN_DigestInfoTemplate)) {
         return NS_ERROR_DOM_OPERATION_ERR;
       }
-
-      rv = MapSECStatus(SEC_SignData(signature, mData.Elements(),
-                                     mData.Length(), mPrivKey, mOidTag));
-
-      if (mEcdsa) {
-        // DER-decode the signature
-        int signatureLength = PK11_SignatureLen(mPrivKey);
-        ScopedSECItem rawSignature(DSAU_DecodeDerSigToLen(signature.get(),
-                                                          signatureLength));
-        if (!rawSignature.get()) {
-          return NS_ERROR_DOM_OPERATION_ERR;
-        }
-
-        ATTEMPT_BUFFER_ASSIGN(mSignature, rawSignature);
-      } else {
-        ATTEMPT_BUFFER_ASSIGN(mSignature, signature);
-      }
-
+    }
+
+    SECItem* params = nullptr;
+    CK_MECHANISM_TYPE mech = PK11_MapSignKeyType((mSign ? mPrivKey->keyType :
+                                                          mPubKey->keyType));
+
+    CK_RSA_PKCS_PSS_PARAMS rsaPssParams;
+    SECItem rsaPssParamsItem = { siBuffer, };
+
+    // Set up parameters for RSA-PSS.
+    if (mAlgorithm == Algorithm::RSA_PSS) {
+      rsaPssParams.hashAlg = mHashMechanism;
+      rsaPssParams.mgf = mMgfMechanism;
+      rsaPssParams.sLen = mSaltLength;
+
+      rsaPssParamsItem.data = (unsigned char*)&rsaPssParams;
+      rsaPssParamsItem.len = sizeof(rsaPssParams);
+      params = &rsaPssParamsItem;
+
+      mech = CKM_RSA_PKCS_PSS;
+    }
+
+    // Allocate SECItem to hold the signature.
+    uint32_t len = mSign ? PK11_SignatureLen(mPrivKey) : 0;
+    ScopedSECItem sig(::SECITEM_AllocItem(nullptr, nullptr, len));
+    if (!sig) {
+      return NS_ERROR_DOM_OPERATION_ERR;
+    }
+
+    if (mSign) {
+      // Sign the hash.
+      rv = PK11_SignWithMechanism(mPrivKey, mech, params, sig, hash);
+      NS_ENSURE_SUCCESS(MapSECStatus(rv), NS_ERROR_DOM_OPERATION_ERR);
+      ATTEMPT_BUFFER_ASSIGN(mSignature, sig);
     } else {
-      ScopedSECItem signature(::SECITEM_AllocItem(nullptr, nullptr, 0));
-      if (!signature.get()) {
-        return NS_ERROR_DOM_UNKNOWN_ERR;
+      // Copy the given signature to the SECItem.
+      if (!mSignature.ToSECItem(nullptr, sig)) {
+        return NS_ERROR_DOM_OPERATION_ERR;
       }
 
-      if (mEcdsa) {
-        // DER-encode the signature
-        ScopedSECItem rawSignature(::SECITEM_AllocItem(nullptr, nullptr, 0));
-        if (!rawSignature || !mSignature.ToSECItem(nullptr, rawSignature)) {
-          return NS_ERROR_DOM_UNKNOWN_ERR;
-        }
-
-        rv = MapSECStatus(DSAU_EncodeDerSigWithLen(signature, rawSignature,
-                                                   rawSignature->len));
-        NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_OPERATION_ERR);
-      } else if (!mSignature.ToSECItem(nullptr, signature)) {
-        return NS_ERROR_DOM_UNKNOWN_ERR;
-      }
-
-      rv = MapSECStatus(VFY_VerifyData(mData.Elements(), mData.Length(),
-                                       mPubKey, signature, mOidTag, nullptr));
-      mVerified = NS_SUCCEEDED(rv);
+      // Verify the signature.
+      rv = PK11_VerifyWithMechanism(mPubKey, mech, params, sig, hash, nullptr);
+      mVerified = NS_SUCCEEDED(MapSECStatus(rv));
     }
 
     return NS_OK;
   }
 
   virtual void Resolve() override
   {
     if (mSign) {
@@ -1218,32 +1286,29 @@ public:
     mEarlyRv = GetAlgorithmName(aCx, aAlgorithm, algName);
     if (NS_FAILED(mEarlyRv)) {
       mEarlyRv = NS_ERROR_DOM_SYNTAX_ERR;
       return;
     }
 
     TelemetryAlgorithm telemetryAlg;
     if (algName.EqualsLiteral(WEBCRYPTO_ALG_SHA1))   {
-      mOidTag = SEC_OID_SHA1;
       telemetryAlg = TA_SHA_1;
     } else if (algName.EqualsLiteral(WEBCRYPTO_ALG_SHA256)) {
-      mOidTag = SEC_OID_SHA256;
       telemetryAlg = TA_SHA_224;
     } else if (algName.EqualsLiteral(WEBCRYPTO_ALG_SHA384)) {
-      mOidTag = SEC_OID_SHA384;
       telemetryAlg = TA_SHA_256;
     } else if (algName.EqualsLiteral(WEBCRYPTO_ALG_SHA512)) {
-      mOidTag = SEC_OID_SHA512;
       telemetryAlg = TA_SHA_384;
     } else {
       mEarlyRv = NS_ERROR_DOM_SYNTAX_ERR;
       return;
     }
     Telemetry::Accumulate(Telemetry::WEBCRYPTO_ALG, telemetryAlg);
+    mOidTag = MapHashAlgorithmNameToOID(algName);
   }
 
 private:
   SECOidTag mOidTag;
   CryptoBuffer mData;
 
   virtual nsresult DoCrypto() override
   {
@@ -1438,16 +1503,23 @@ public:
       const ObjectOrString& aAlgorithm, bool aExtractable,
       const Sequence<nsString>& aKeyUsages)
   {
     ImportKeyTask::Init(aCx, aFormat, aAlgorithm, aExtractable, aKeyUsages);
     if (NS_FAILED(mEarlyRv)) {
       return;
     }
 
+    // This task only supports raw and JWK format.
+    if (!mFormat.EqualsLiteral(WEBCRYPTO_KEY_FORMAT_JWK) &&
+        !mFormat.EqualsLiteral(WEBCRYPTO_KEY_FORMAT_RAW)) {
+      mEarlyRv = NS_ERROR_DOM_NOT_SUPPORTED_ERR;
+      return;
+    }
+
     // If this is an HMAC key, import the hash name
     if (mAlgName.EqualsLiteral(WEBCRYPTO_ALG_HMAC)) {
       RootedDictionary<HmacImportParams> params(aCx);
       mEarlyRv = Coerce(aCx, params, aAlgorithm);
       if (NS_FAILED(mEarlyRv)) {
         mEarlyRv = NS_ERROR_DOM_SYNTAX_ERR;
         return;
       }
@@ -1502,24 +1574,25 @@ public:
         return NS_ERROR_DOM_DATA_ERR;
       }
       mKey->Algorithm().MakeAes(mAlgName, length);
 
       if (mDataIsJwk && mJwk.mUse.WasPassed() &&
           !mJwk.mUse.Value().EqualsLiteral(JWK_USE_ENC)) {
         return NS_ERROR_DOM_DATA_ERR;
       }
-    } else if (mAlgName.EqualsLiteral(WEBCRYPTO_ALG_PBKDF2)) {
+    } else if (mAlgName.EqualsLiteral(WEBCRYPTO_ALG_HKDF) ||
+               mAlgName.EqualsLiteral(WEBCRYPTO_ALG_PBKDF2)) {
       if (mKey->HasUsageOtherThan(CryptoKey::DERIVEKEY | CryptoKey::DERIVEBITS)) {
         return NS_ERROR_DOM_DATA_ERR;
       }
       mKey->Algorithm().MakeAes(mAlgName, length);
 
       if (mDataIsJwk && mJwk.mUse.WasPassed()) {
-        // There is not a 'use' value consistent with PBKDF
+        // There is not a 'use' value consistent with PBKDF or HKDF
         return NS_ERROR_DOM_DATA_ERR;
       };
     } else if (mAlgName.EqualsLiteral(WEBCRYPTO_ALG_HMAC)) {
       if (mKey->HasUsageOtherThan(CryptoKey::SIGN | CryptoKey::VERIFY)) {
         return NS_ERROR_DOM_DATA_ERR;
       }
 
       mKey->Algorithm().MakeHmac(length, mHashName);
@@ -1590,17 +1663,18 @@ public:
   {
     ImportKeyTask::Init(aCx, aFormat, aAlgorithm, aExtractable, aKeyUsages);
     if (NS_FAILED(mEarlyRv)) {
       return;
     }
 
     // If this is RSA with a hash, cache the hash name
     if (mAlgName.EqualsLiteral(WEBCRYPTO_ALG_RSASSA_PKCS1) ||
-        mAlgName.EqualsLiteral(WEBCRYPTO_ALG_RSA_OAEP)) {
+        mAlgName.EqualsLiteral(WEBCRYPTO_ALG_RSA_OAEP) ||
+        mAlgName.EqualsLiteral(WEBCRYPTO_ALG_RSA_PSS)) {
       RootedDictionary<RsaHashedImportParams> params(aCx);
       mEarlyRv = Coerce(aCx, params, aAlgorithm);
       if (NS_FAILED(mEarlyRv)) {
         mEarlyRv = NS_ERROR_DOM_DATA_ERR;
         return;
       }
 
       mEarlyRv = GetAlgorithmName(aCx, params.mHash, mHashName);
@@ -1692,17 +1766,18 @@ private:
     // Check permissions for the requested operation
     if (mAlgName.EqualsLiteral(WEBCRYPTO_ALG_RSA_OAEP)) {
       if ((mKey->GetKeyType() == CryptoKey::PUBLIC &&
            mKey->HasUsageOtherThan(CryptoKey::ENCRYPT | CryptoKey::WRAPKEY)) ||
           (mKey->GetKeyType() == CryptoKey::PRIVATE &&
            mKey->HasUsageOtherThan(CryptoKey::DECRYPT | CryptoKey::UNWRAPKEY))) {
         return NS_ERROR_DOM_DATA_ERR;
       }
-    } else if (mAlgName.EqualsLiteral(WEBCRYPTO_ALG_RSASSA_PKCS1)) {
+    } else if (mAlgName.EqualsLiteral(WEBCRYPTO_ALG_RSASSA_PKCS1) ||
+               mAlgName.EqualsLiteral(WEBCRYPTO_ALG_RSA_PSS)) {
       if ((mKey->GetKeyType() == CryptoKey::PUBLIC &&
            mKey->HasUsageOtherThan(CryptoKey::VERIFY)) ||
           (mKey->GetKeyType() == CryptoKey::PRIVATE &&
            mKey->HasUsageOtherThan(CryptoKey::SIGN))) {
         return NS_ERROR_DOM_DATA_ERR;
       }
     }
 
@@ -2278,17 +2353,18 @@ GenerateAsymmetricKeyTask::GenerateAsymm
   if (NS_FAILED(mEarlyRv)) {
     mEarlyRv = NS_ERROR_DOM_SYNTAX_ERR;
     return;
   }
 
   // Construct an appropriate KeyAlorithm
   uint32_t privateAllowedUsages = 0, publicAllowedUsages = 0;
   if (mAlgName.EqualsLiteral(WEBCRYPTO_ALG_RSASSA_PKCS1) ||
-      mAlgName.EqualsLiteral(WEBCRYPTO_ALG_RSA_OAEP)) {
+      mAlgName.EqualsLiteral(WEBCRYPTO_ALG_RSA_OAEP) ||
+      mAlgName.EqualsLiteral(WEBCRYPTO_ALG_RSA_PSS)) {
     RootedDictionary<RsaHashedKeyGenParams> params(aCx);
     mEarlyRv = Coerce(aCx, params, aAlgorithm);
     if (NS_FAILED(mEarlyRv)) {
       mEarlyRv = NS_ERROR_DOM_SYNTAX_ERR;
       return;
     }
 
     // Pull relevant info
@@ -2381,26 +2457,29 @@ GenerateAsymmetricKeyTask::GenerateAsymm
     mMechanism = CKM_DH_PKCS_KEY_PAIR_GEN;
   } else {
     mEarlyRv = NS_ERROR_DOM_NOT_SUPPORTED_ERR;
     return;
   }
 
   // Set key usages.
   if (mAlgName.EqualsLiteral(WEBCRYPTO_ALG_RSASSA_PKCS1) ||
+      mAlgName.EqualsLiteral(WEBCRYPTO_ALG_RSA_PSS) ||
       mAlgName.EqualsLiteral(WEBCRYPTO_ALG_ECDSA)) {
     privateAllowedUsages = CryptoKey::SIGN;
     publicAllowedUsages = CryptoKey::VERIFY;
   } else if (mAlgName.EqualsLiteral(WEBCRYPTO_ALG_RSA_OAEP)) {
     privateAllowedUsages = CryptoKey::DECRYPT | CryptoKey::UNWRAPKEY;
     publicAllowedUsages = CryptoKey::ENCRYPT | CryptoKey::WRAPKEY;
   } else if (mAlgName.EqualsLiteral(WEBCRYPTO_ALG_ECDH) ||
              mAlgName.EqualsLiteral(WEBCRYPTO_ALG_DH)) {
     privateAllowedUsages = CryptoKey::DERIVEKEY | CryptoKey::DERIVEBITS;
     publicAllowedUsages = 0;
+  } else {
+    MOZ_ASSERT(false); // This shouldn't happen.
   }
 
   mKeyPair->mPrivateKey.get()->SetExtractable(aExtractable);
   mKeyPair->mPrivateKey.get()->SetType(CryptoKey::PRIVATE);
 
   mKeyPair->mPublicKey.get()->SetExtractable(true);
   mKeyPair->mPublicKey.get()->SetType(CryptoKey::PUBLIC);
 
@@ -2492,16 +2571,166 @@ GenerateAsymmetricKeyTask::Resolve()
 }
 
 void
 GenerateAsymmetricKeyTask::Cleanup()
 {
   mKeyPair = nullptr;
 }
 
+class DeriveHkdfBitsTask : public ReturnArrayBufferViewTask
+{
+public:
+  DeriveHkdfBitsTask(JSContext* aCx,
+      const ObjectOrString& aAlgorithm, CryptoKey& aKey, uint32_t aLength)
+    : mSymKey(aKey.GetSymKey())
+  {
+    Init(aCx, aAlgorithm, aKey, aLength);
+  }
+
+  DeriveHkdfBitsTask(JSContext* aCx, const ObjectOrString& aAlgorithm,
+                      CryptoKey& aKey, const ObjectOrString& aTargetAlgorithm)
+    : mSymKey(aKey.GetSymKey())
+  {
+    size_t length;
+    mEarlyRv = GetKeyLengthForAlgorithm(aCx, aTargetAlgorithm, length);
+
+    if (NS_SUCCEEDED(mEarlyRv)) {
+      Init(aCx, aAlgorithm, aKey, length);
+    }
+  }
+
+  void Init(JSContext* aCx, const ObjectOrString& aAlgorithm, CryptoKey& aKey,
+            uint32_t aLength)
+  {
+    Telemetry::Accumulate(Telemetry::WEBCRYPTO_ALG, TA_HKDF);
+    CHECK_KEY_ALGORITHM(aKey.Algorithm(), WEBCRYPTO_ALG_HKDF);
+
+    // Check that we have a key.
+    if (mSymKey.Length() == 0) {
+      mEarlyRv = NS_ERROR_DOM_INVALID_ACCESS_ERR;
+      return;
+    }
+
+    RootedDictionary<HkdfParams> params(aCx);
+    mEarlyRv = Coerce(aCx, params, aAlgorithm);
+    if (NS_FAILED(mEarlyRv)) {
+      mEarlyRv = NS_ERROR_DOM_SYNTAX_ERR;
+      return;
+    }
+
+    // length must be greater than zero.
+    if (aLength == 0) {
+      mEarlyRv = NS_ERROR_DOM_DATA_ERR;
+      return;
+    }
+
+    // Extract the hash algorithm.
+    nsString hashName;
+    mEarlyRv = GetAlgorithmName(aCx, params.mHash, hashName);
+    if (NS_FAILED(mEarlyRv)) {
+      return;
+    }
+
+    // Check the given hash algorithm.
+    switch (MapAlgorithmNameToMechanism(hashName)) {
+      case CKM_SHA_1: mMechanism = CKM_NSS_HKDF_SHA1; break;
+      case CKM_SHA256: mMechanism = CKM_NSS_HKDF_SHA256; break;
+      case CKM_SHA384: mMechanism = CKM_NSS_HKDF_SHA384; break;
+      case CKM_SHA512: mMechanism = CKM_NSS_HKDF_SHA512; break;
+      default:
+        mEarlyRv = NS_ERROR_DOM_NOT_SUPPORTED_ERR;
+        return;
+    }
+
+    ATTEMPT_BUFFER_INIT(mSalt, params.mSalt)
+    ATTEMPT_BUFFER_INIT(mInfo, params.mInfo)
+    mLengthInBytes = ceil((double)aLength / 8);
+    mLengthInBits = aLength;
+  }
+
+private:
+  size_t mLengthInBits;
+  size_t mLengthInBytes;
+  CryptoBuffer mSalt;
+  CryptoBuffer mInfo;
+  CryptoBuffer mSymKey;
+  CK_MECHANISM_TYPE mMechanism;
+
+  virtual nsresult DoCrypto() override
+  {
+    ScopedPLArenaPool arena(PORT_NewArena(DER_DEFAULT_CHUNKSIZE));
+    if (!arena) {
+      return NS_ERROR_DOM_OPERATION_ERR;
+    }
+
+    // Import the key
+    SECItem keyItem = { siBuffer, nullptr, 0 };
+    ATTEMPT_BUFFER_TO_SECITEM(arena, &keyItem, mSymKey);
+
+    ScopedPK11SlotInfo slot(PK11_GetInternalSlot());
+    if (!slot.get()) {
+      return NS_ERROR_DOM_OPERATION_ERR;
+    }
+
+    ScopedPK11SymKey baseKey(PK11_ImportSymKey(slot, mMechanism,
+                                               PK11_OriginUnwrap, CKA_WRAP,
+                                               &keyItem, nullptr));
+    if (!baseKey) {
+      return NS_ERROR_DOM_INVALID_ACCESS_ERR;
+    }
+
+    SECItem salt = { siBuffer, nullptr, 0 };
+    SECItem info = { siBuffer, nullptr, 0 };
+    ATTEMPT_BUFFER_TO_SECITEM(arena, &salt, mSalt);
+    ATTEMPT_BUFFER_TO_SECITEM(arena, &info, mInfo);
+
+    CK_NSS_HKDFParams hkdfParams = { true, salt.data, salt.len,
+                                     true, info.data, info.len };
+    SECItem params = { siBuffer, (unsigned char*)&hkdfParams,
+                       sizeof(hkdfParams) };
+
+    // CKM_SHA512_HMAC and CKA_SIGN are key type and usage attributes of the
+    // derived symmetric key and don't matter because we ignore them anyway.
+    ScopedPK11SymKey symKey(PK11_Derive(baseKey, mMechanism, &params,
+                                        CKM_SHA512_HMAC, CKA_SIGN,
+                                        mLengthInBytes));
+
+    if (!symKey.get()) {
+      return NS_ERROR_DOM_OPERATION_ERR;
+    }
+
+    nsresult rv = MapSECStatus(PK11_ExtractKeyValue(symKey));
+    if (NS_FAILED(rv)) {
+      return NS_ERROR_DOM_OPERATION_ERR;
+    }
+
+    // This doesn't leak, because the SECItem* returned by PK11_GetKeyData
+    // just refers to a buffer managed by symKey. The assignment copies the
+    // data, so mResult manages one copy, while symKey manages another.
+    ATTEMPT_BUFFER_ASSIGN(mResult, PK11_GetKeyData(symKey));
+
+    if (mLengthInBytes > mResult.Length()) {
+      return NS_ERROR_DOM_DATA_ERR;
+    }
+
+    if (!mResult.SetLength(mLengthInBytes, fallible)) {
+      return NS_ERROR_DOM_UNKNOWN_ERR;
+    }
+
+    // If the number of bits to derive is not a multiple of 8 we need to
+    // zero out the remaining bits that were derived but not requested.
+    if (mLengthInBits % 8) {
+      mResult[mResult.Length() - 1] &= 0xff << (mLengthInBits % 8);
+    }
+
+    return NS_OK;
+  }
+};
+
 class DerivePbkdfBitsTask : public ReturnArrayBufferViewTask
 {
 public:
   DerivePbkdfBitsTask(JSContext* aCx,
       const ObjectOrString& aAlgorithm, CryptoKey& aKey, uint32_t aLength)
     : mSymKey(aKey.GetSymKey())
   {
     Init(aCx, aAlgorithm, aKey, aLength);
@@ -3030,16 +3259,17 @@ WebCryptoTask::CreateSignVerifyTask(JSCo
   nsresult rv = GetAlgorithmName(aCx, aAlgorithm, algName);
   if (NS_FAILED(rv)) {
     return new FailureTask(rv);
   }
 
   if (algName.EqualsLiteral(WEBCRYPTO_ALG_HMAC)) {
     return new HmacTask(aCx, aAlgorithm, aKey, aSignature, aData, aSign);
   } else if (algName.EqualsLiteral(WEBCRYPTO_ALG_RSASSA_PKCS1) ||
+             algName.EqualsLiteral(WEBCRYPTO_ALG_RSA_PSS) ||
              algName.EqualsLiteral(WEBCRYPTO_ALG_ECDSA)) {
     return new AsymmetricSignVerifyTask(aCx, aAlgorithm, aKey, aSignature,
                                         aData, aSign);
   }
 
   return new FailureTask(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
 }
 
@@ -3098,21 +3328,23 @@ WebCryptoTask::CreateImportKeyTask(JSCon
 
   // SPEC-BUG: PBKDF2 is not supposed to be supported for this operation.
   // However, the spec should be updated to allow it.
   if (algName.EqualsLiteral(WEBCRYPTO_ALG_AES_CBC) ||
       algName.EqualsLiteral(WEBCRYPTO_ALG_AES_CTR) ||
       algName.EqualsLiteral(WEBCRYPTO_ALG_AES_GCM) ||
       algName.EqualsLiteral(WEBCRYPTO_ALG_AES_KW) ||
       algName.EqualsLiteral(WEBCRYPTO_ALG_PBKDF2) ||
+      algName.EqualsLiteral(WEBCRYPTO_ALG_HKDF) ||
       algName.EqualsLiteral(WEBCRYPTO_ALG_HMAC)) {
     return new ImportSymmetricKeyTask(aCx, aFormat, aKeyData, aAlgorithm,
                                       aExtractable, aKeyUsages);
   } else if (algName.EqualsLiteral(WEBCRYPTO_ALG_RSASSA_PKCS1) ||
-             algName.EqualsLiteral(WEBCRYPTO_ALG_RSA_OAEP)) {
+             algName.EqualsLiteral(WEBCRYPTO_ALG_RSA_OAEP) ||
+             algName.EqualsLiteral(WEBCRYPTO_ALG_RSA_PSS)) {
     return new ImportRsaKeyTask(aCx, aFormat, aKeyData, aAlgorithm,
                                 aExtractable, aKeyUsages);
   } else if (algName.EqualsLiteral(WEBCRYPTO_ALG_ECDH) ||
              algName.EqualsLiteral(WEBCRYPTO_ALG_ECDSA)) {
     return new ImportEcKeyTask(aCx, aFormat, aKeyData, aAlgorithm,
                                aExtractable, aKeyUsages);
   } else if (algName.EqualsLiteral(WEBCRYPTO_ALG_DH)) {
     return new ImportDhKeyTask(aCx, aFormat, aKeyData, aAlgorithm,
@@ -3148,16 +3380,17 @@ WebCryptoTask::CreateExportKeyTask(const
   if (algName.EqualsLiteral(WEBCRYPTO_ALG_AES_CBC) ||
       algName.EqualsLiteral(WEBCRYPTO_ALG_AES_CTR) ||
       algName.EqualsLiteral(WEBCRYPTO_ALG_AES_GCM) ||
       algName.EqualsLiteral(WEBCRYPTO_ALG_AES_KW) ||
       algName.EqualsLiteral(WEBCRYPTO_ALG_PBKDF2) ||
       algName.EqualsLiteral(WEBCRYPTO_ALG_HMAC) ||
       algName.EqualsLiteral(WEBCRYPTO_ALG_RSASSA_PKCS1) ||
       algName.EqualsLiteral(WEBCRYPTO_ALG_RSA_OAEP) ||
+      algName.EqualsLiteral(WEBCRYPTO_ALG_RSA_PSS) ||
       algName.EqualsLiteral(WEBCRYPTO_ALG_ECDSA) ||
       algName.EqualsLiteral(WEBCRYPTO_ALG_ECDH) ||
       algName.EqualsLiteral(WEBCRYPTO_ALG_DH)) {
     return new ExportKeyTask(aFormat, aKey);
   }
 
   return new FailureTask(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
 }
@@ -3187,16 +3420,17 @@ WebCryptoTask::CreateGenerateKeyTask(JSC
   if (algName.EqualsASCII(WEBCRYPTO_ALG_AES_CBC) ||
       algName.EqualsASCII(WEBCRYPTO_ALG_AES_CTR) ||
       algName.EqualsASCII(WEBCRYPTO_ALG_AES_GCM) ||
       algName.EqualsASCII(WEBCRYPTO_ALG_AES_KW) ||
       algName.EqualsASCII(WEBCRYPTO_ALG_HMAC)) {
     return new GenerateSymmetricKeyTask(aCx, aAlgorithm, aExtractable, aKeyUsages);
   } else if (algName.EqualsASCII(WEBCRYPTO_ALG_RSASSA_PKCS1) ||
              algName.EqualsASCII(WEBCRYPTO_ALG_RSA_OAEP) ||
+             algName.EqualsASCII(WEBCRYPTO_ALG_RSA_PSS) ||
              algName.EqualsASCII(WEBCRYPTO_ALG_ECDH) ||
              algName.EqualsASCII(WEBCRYPTO_ALG_ECDSA) ||
              algName.EqualsASCII(WEBCRYPTO_ALG_DH)) {
     return new GenerateAsymmetricKeyTask(aCx, aAlgorithm, aExtractable, aKeyUsages);
   } else {
     return new FailureTask(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
   }
 }
@@ -3222,16 +3456,22 @@ WebCryptoTask::CreateDeriveKeyTask(JSCon
   }
 
   nsString algName;
   nsresult rv = GetAlgorithmName(aCx, aAlgorithm, algName);
   if (NS_FAILED(rv)) {
     return new FailureTask(rv);
   }
 
+  if (algName.EqualsASCII(WEBCRYPTO_ALG_HKDF)) {
+    return new DeriveKeyTask<DeriveHkdfBitsTask>(aCx, aAlgorithm, aBaseKey,
+                                                 aDerivedKeyType, aExtractable,
+                                                 aKeyUsages);
+  }
+
   if (algName.EqualsASCII(WEBCRYPTO_ALG_PBKDF2)) {
     return new DeriveKeyTask<DerivePbkdfBitsTask>(aCx, aAlgorithm, aBaseKey,
                                                   aDerivedKeyType, aExtractable,
                                                   aKeyUsages);
   }
 
   if (algName.EqualsASCII(WEBCRYPTO_ALG_ECDH)) {
     return new DeriveKeyTask<DeriveEcdhBitsTask>(aCx, aAlgorithm, aBaseKey,
@@ -3268,16 +3508,20 @@ WebCryptoTask::CreateDeriveBitsTask(JSCo
   if (algName.EqualsASCII(WEBCRYPTO_ALG_ECDH)) {
     return new DeriveEcdhBitsTask(aCx, aAlgorithm, aKey, aLength);
   }
 
   if (algName.EqualsASCII(WEBCRYPTO_ALG_DH)) {
     return new DeriveDhBitsTask(aCx, aAlgorithm, aKey, aLength);
   }
 
+  if (algName.EqualsASCII(WEBCRYPTO_ALG_HKDF)) {
+    return new DeriveHkdfBitsTask(aCx, aAlgorithm, aKey, aLength);
+  }
+
   return new FailureTask(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
 }
 
 WebCryptoTask*
 WebCryptoTask::CreateWrapKeyTask(JSContext* aCx,
                                  const nsAString& aFormat,
                                  CryptoKey& aKey,
                                  CryptoKey& aWrappingKey,
@@ -3353,22 +3597,24 @@ WebCryptoTask::CreateUnwrapKeyTask(JSCon
     return new FailureTask(rv);
   }
 
   CryptoOperationData dummy;
   RefPtr<ImportKeyTask> importTask;
   if (keyAlgName.EqualsASCII(WEBCRYPTO_ALG_AES_CBC) ||
       keyAlgName.EqualsASCII(WEBCRYPTO_ALG_AES_CTR) ||
       keyAlgName.EqualsASCII(WEBCRYPTO_ALG_AES_GCM) ||
+      keyAlgName.EqualsASCII(WEBCRYPTO_ALG_HKDF) ||
       keyAlgName.EqualsASCII(WEBCRYPTO_ALG_HMAC)) {
     importTask = new ImportSymmetricKeyTask(aCx, aFormat,
                                             aUnwrappedKeyAlgorithm,
                                             aExtractable, aKeyUsages);
   } else if (keyAlgName.EqualsASCII(WEBCRYPTO_ALG_RSASSA_PKCS1) ||
-             keyAlgName.EqualsASCII(WEBCRYPTO_ALG_RSA_OAEP)) {
+             keyAlgName.EqualsASCII(WEBCRYPTO_ALG_RSA_OAEP) ||
+             keyAlgName.EqualsASCII(WEBCRYPTO_ALG_RSA_PSS)) {
     importTask = new ImportRsaKeyTask(aCx, aFormat,
                                       aUnwrappedKeyAlgorithm,
                                       aExtractable, aKeyUsages);
   } else {
     return new FailureTask(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
   }
 
   nsString unwrapAlgName;
--- a/dom/crypto/test/mochitest.ini
+++ b/dom/crypto/test/mochitest.ini
@@ -9,14 +9,16 @@ support-files =
   util.js
 
 [test_indexedDB.html]
 skip-if = toolkit == 'android' # bug 1200570
 [test_WebCrypto.html]
 [test_WebCrypto_DH.html]
 [test_WebCrypto_ECDH.html]
 [test_WebCrypto_ECDSA.html]
+[test_WebCrypto_HKDF.html]
 [test_WebCrypto_JWK.html]
 [test_WebCrypto_Normalize.html]
 [test_WebCrypto_PBKDF2.html]
 [test_WebCrypto_Reject_Generating_Keys_Without_Usages.html]
 [test_WebCrypto_RSA_OAEP.html]
+[test_WebCrypto_RSA_PSS.html]
 [test_WebCrypto_Wrap_Unwrap.html]
--- a/dom/crypto/test/test-vectors.js
+++ b/dom/crypto/test/test-vectors.js
@@ -413,16 +413,192 @@ tv = {
     result: util.hex2abv(
       "354fe67b4a126d5d35fe36c777791a3f7ba13def484e2d3908aff722fad468fb" +
       "21696de95d0be911c2d3174f8afcc201035f7b6d8e69402de5451618c21a535f" +
       "a9d7bfc5b8dd9fc243f8cf927db31322d6e881eaa91a996170e657a05a266426" +
       "d98c88003f8477c1227094a0d9fa1e8c4024309ce1ecccb5210035d47ac72e8a"
     ),
   },
 
+  // [pss-vect.txt] Example 1.1 from
+  // <ftp://ftp.rsasecurity.com/pub/pkcs/pkcs-1/pkcs-1v2-1-vec.zip>
+  rsapss: {
+    pkcs8: util.hex2abv(
+      "30820275020100300d06092a864886f70d01010105000482025f3082025b0201" +
+      "0002818100a56e4a0e701017589a5187dc7ea841d156f2ec0e36ad52a44dfeb1" +
+      "e61f7ad991d8c51056ffedb162b4c0f283a12a88a394dff526ab7291cbb307ce" +
+      "abfce0b1dfd5cd9508096d5b2b8b6df5d671ef6377c0921cb23c270a70e2598e" +
+      "6ff89d19f105acc2d3f0cb35f29280e1386b6f64c4ef22e1e1f20d0ce8cffb22" +
+      "49bd9a2137020301000102818033a5042a90b27d4f5451ca9bbbd0b44771a101" +
+      "af884340aef9885f2a4bbe92e894a724ac3c568c8f97853ad07c0266c8c6a3ca" +
+      "0929f1e8f11231884429fc4d9ae55fee896a10ce707c3ed7e734e44727a39574" +
+      "501a532683109c2abacaba283c31b4bd2f53c3ee37e352cee34f9e503bd80c06" +
+      "22ad79c6dcee883547c6a3b325024100e7e8942720a877517273a356053ea2a1" +
+      "bc0c94aa72d55c6e86296b2dfc967948c0a72cbccca7eacb35706e09a1df55a1" +
+      "535bd9b3cc34160b3b6dcd3eda8e6443024100b69dca1cf7d4d7ec81e75b90fc" +
+      "ca874abcde123fd2700180aa90479b6e48de8d67ed24f9f19d85ba275874f542" +
+      "cd20dc723e6963364a1f9425452b269a6799fd024028fa13938655be1f8a159c" +
+      "baca5a72ea190c30089e19cd274a556f36c4f6e19f554b34c077790427bbdd8d" +
+      "d3ede2448328f385d81b30e8e43b2fffa02786197902401a8b38f398fa712049" +
+      "898d7fb79ee0a77668791299cdfa09efc0e507acb21ed74301ef5bfd48be455e" +
+      "aeb6e1678255827580a8e4e8e14151d1510a82a3f2e729024027156aba4126d2" +
+      "4a81f3a528cbfb27f56886f840a9f6e86e17a44b94fe9319584b8e22fdde1e5a" +
+      "2e3bd8aa5ba8d8584194eb2190acf832b847f13a3d24a79f4d"
+    ),
+    spki: util.hex2abv(
+      "30819f300d06092a864886f70d010101050003818d0030818902818100a56e4a" +
+      "0e701017589a5187dc7ea841d156f2ec0e36ad52a44dfeb1e61f7ad991d8c510" +
+      "56ffedb162b4c0f283a12a88a394dff526ab7291cbb307ceabfce0b1dfd5cd95" +
+      "08096d5b2b8b6df5d671ef6377c0921cb23c270a70e2598e6ff89d19f105acc2" +
+      "d3f0cb35f29280e1386b6f64c4ef22e1e1f20d0ce8cffb2249bd9a2137020301" +
+      "0001"
+    ),
+    data: util.hex2abv(
+      "cdc87da223d786df3b45e0bbbc721326d1ee2af806cc315475cc6f0d9c66e1b6" +
+      "2371d45ce2392e1ac92844c310102f156a0d8d52c1f4c40ba3aa65095786cb76" +
+      "9757a6563ba958fed0bcc984e8b517a3d5f515b23b8a41e74aa867693f90dfb0" +
+      "61a6e86dfaaee64472c00e5f20945729cbebe77f06ce78e08f4098fba41f9d61" +
+      "93c0317e8b60d4b6084acb42d29e3808a3bc372d85e331170fcbf7cc72d0b71c" +
+      "296648b3a4d10f416295d0807aa625cab2744fd9ea8fd223c42537029828bd16" +
+      "be02546f130fd2e33b936d2676e08aed1b73318b750a0167d0"
+    ),
+    sig: util.hex2abv(
+      "9074308fb598e9701b2294388e52f971faac2b60a5145af185df5287b5ed2887" +
+      "e57ce7fd44dc8634e407c8e0e4360bc226f3ec227f9d9e54638e8d31f5051215" +
+      "df6ebb9c2f9579aa77598a38f914b5b9c1bd83c4e2f9f382a0d0aa3542ffee65" +
+      "984a601bc69eb28deb27dca12c82c2d4c3f66cd500f1ff2b994d8a4e30cbb33c"
+    ),
+    saltLength: 20,
+    jwk_priv: {
+      kty: "RSA",
+      n: "pW5KDnAQF1iaUYfcfqhB0Vby7A42rVKkTf6x5h962ZHYxRBW_-2xYrTA8oOhK" +
+         "oijlN_1JqtykcuzB86r_OCx39XNlQgJbVsri2311nHvY3fAkhyyPCcKcOJZjm" +
+         "_4nRnxBazC0_DLNfKSgOE4a29kxO8i4eHyDQzoz_siSb2aITc",
+      e: "AQAB",
+      d: "M6UEKpCyfU9UUcqbu9C0R3GhAa-IQ0Cu-YhfKku-kuiUpySsPFaMj5eFOtB8A" +
+         "mbIxqPKCSnx6PESMYhEKfxNmuVf7olqEM5wfD7X5zTkRyejlXRQGlMmgxCcKr" +
+         "rKuig8MbS9L1PD7jfjUs7jT55QO9gMBiKtecbc7og1R8ajsyU",
+      p: "5-iUJyCod1Fyc6NWBT6iobwMlKpy1VxuhilrLfyWeUjApyy8zKfqyzVwbgmh3" +
+         "1WhU1vZs8w0Fgs7bc0-2o5kQw",
+      q: "tp3KHPfU1-yB51uQ_MqHSrzeEj_ScAGAqpBHm25I3o1n7ST58Z2FuidYdPVCz" +
+         "SDccj5pYzZKH5QlRSsmmmeZ_Q",
+      dp: "KPoTk4ZVvh-KFZy6ylpy6hkMMAieGc0nSlVvNsT24Z9VSzTAd3kEJ7vdjdPt" +
+          "4kSDKPOF2Bsw6OQ7L_-gJ4YZeQ",
+      dq: "Gos485j6cSBJiY1_t57gp3ZoeRKZzfoJ78DlB6yyHtdDAe9b_Ui-RV6utuFn" +
+          "glWCdYCo5OjhQVHRUQqCo_LnKQ",
+      qi: "JxVqukEm0kqB86Uoy_sn9WiG-ECp9uhuF6RLlP6TGVhLjiL93h5aLjvYqluo" +
+          "2FhBlOshkKz4MrhH8To9JKefTQ",
+    },
+    jwk_pub: {
+      kty: "RSA",
+      n: "pW5KDnAQF1iaUYfcfqhB0Vby7A42rVKkTf6x5h962ZHYxRBW_-2xYrTA8oOhK" +
+         "oijlN_1JqtykcuzB86r_OCx39XNlQgJbVsri2311nHvY3fAkhyyPCcKcOJZjm" +
+         "_4nRnxBazC0_DLNfKSgOE4a29kxO8i4eHyDQzoz_siSb2aITc",
+      e: "AQAB",
+    },
+  },
+
+  // [pss-vect.txt] Example 1.4 from
+  // <ftp://ftp.rsasecurity.com/pub/pkcs/pkcs-1/pkcs-1v2-1-vec.zip>
+  rsapss2: {
+    spki: util.hex2abv(
+      "30819f300d06092a864886f70d010101050003818d0030818902818100a56e4a" +
+      "0e701017589a5187dc7ea841d156f2ec0e36ad52a44dfeb1e61f7ad991d8c510" +
+      "56ffedb162b4c0f283a12a88a394dff526ab7291cbb307ceabfce0b1dfd5cd95" +
+      "08096d5b2b8b6df5d671ef6377c0921cb23c270a70e2598e6ff89d19f105acc2" +
+      "d3f0cb35f29280e1386b6f64c4ef22e1e1f20d0ce8cffb2249bd9a2137020301" +
+      "0001"
+    ),
+    data: util.hex2abv(
+      "bc656747fa9eafb3f0"
+    ),
+    sig: util.hex2abv(
+      "4609793b23e9d09362dc21bb47da0b4f3a7622649a47d464019b9aeafe53359c" +
+      "178c91cd58ba6bcb78be0346a7bc637f4b873d4bab38ee661f199634c547a1ad" +
+      "8442e03da015b136e543f7ab07c0c13e4225b8de8cce25d4f6eb8400f81f7e18" +
+      "33b7ee6e334d370964ca79fdb872b4d75223b5eeb08101591fb532d155a6de87"
+    ),
+    saltLength: 20
+  },
+
+  // [SigVerPSS_186-3.rsp] from
+  // <http://csrc.nist.gov/groups/STM/cavp/documents/dss/186-2rsatestvectors.zip>
+  rsapss3: {
+    spki: util.hex2abv(
+      "30819d300d06092a864886f70d010101050003818b0030818702818100be499b" +
+      "5e7f06c83fa0293e31465c8eb6b58af920bae52a7b5b9bfeb7aa72db1264112e" +
+      "b3fd431d31a2a7e50941566929494a0e891ed5613918b4b51b0d1fb97783b26a" +
+      "cf7d0f384cfb35f4d2824f5dd380623a26bf180b63961c619dcdb20cae406f22" +
+      "f6e276c80a37259490cfeb72c1a71a84f1846d330877ba3e3101ec9c7b020111"
+    ),
+    data: util.hex2abv(
+      "c7f5270fca725f9bd19f519a8d7cca3cc5c079024029f3bae510f9b02140fe23" +
+      "8908e4f6c18f07a89c687c8684669b1f1db2baf9251a3c829faccb493084e16e" +
+      "c9e28d58868074a5d6221667dd6e528d16fe2c9f3db4cfaf6c4dce8c8439af38" +
+      "ceaaaa9ce2ecae7bc8f4a5a55e3bf96df9cd575c4f9cb327951b8cdfe4087168"
+    ),
+    sig: util.hex2abv(
+      "11e169f2fd40b07641b9768a2ab19965fb6c27f10fcf0323fcc6d12eb4f1c06b" +
+      "330ddaa1ea504407afa29de9ebe0374fe9d1e7d0ffbd5fc1cf3a3446e4145415" +
+      "d2ab24f789b3464c5c43a256bbc1d692cf7f04801dac5bb401a4a03ab7d5728a" +
+      "860c19e1a4dc797ca542c8203cec2e601eb0c51f567f2eda022b0b9ebddeeefa"
+    ),
+    saltLength: 10
+  },
+
+  // [SigVerPSS_186-3.rsp] from
+  // <http://csrc.nist.gov/groups/STM/cavp/documents/dss/186-2rsatestvectors.zip>
+  rsapss4: {
+    spki: util.hex2abv(
+      "30819d300d06092a864886f70d010101050003818b0030818702818100be499b" +
+      "5e7f06c83fa0293e31465c8eb6b58af920bae52a7b5b9bfeb7aa72db1264112e" +
+      "b3fd431d31a2a7e50941566929494a0e891ed5613918b4b51b0d1fb97783b26a" +
+      "cf7d0f384cfb35f4d2824f5dd380623a26bf180b63961c619dcdb20cae406f22" +
+      "f6e276c80a37259490cfeb72c1a71a84f1846d330877ba3e3101ec9c7b020111"
+    ),
+    data: util.hex2abv(
+      "c7f5270fca725f9bd19f519a8d7cca3cc5c079024029f3bae510f9b02140fe23" +
+      "8908e4f6c18f07a89c687c8684669b1f1db2baf9251a3c829faccb493084e16e" +
+      "c9e28d58868074a5d6221667dd6e528d16fe2c9f3db4cfaf6c4dce8c8439af38" +
+      "ceaaaa9ce2ecae7bc8f4a5a55e3bf96df9cd575c4f9cb327951b8cdfe4087168"
+    ),
+    sig: util.hex2abv(
+      "b281ad934b2775c0cba5fb10aa574d2ed85c7f99b942b78e49702480069362ed" +
+      "394baded55e56cfcbe7b0b8d2217a05a60e1acd725cb09060dfac585bc2132b9" +
+      "9b41cdbd530c69d17cdbc84bc6b9830fc7dc8e1b2412cfe06dcf8c1a0cc3453f" +
+      "93f25ebf10cb0c90334fac573f449138616e1a194c67f44efac34cc07a526267"
+    ),
+    saltLength: 10
+  },
+
+  // [SigVerPSS_186-3.rsp] from
+  // <http://csrc.nist.gov/groups/STM/cavp/documents/dss/186-2rsatestvectors.zip>
+  rsapss5: {
+    spki: util.hex2abv(
+      "30819d300d06092a864886f70d010101050003818b0030818702818100be499b" +
+      "5e7f06c83fa0293e31465c8eb6b58af920bae52a7b5b9bfeb7aa72db1264112e" +
+      "b3fd431d31a2a7e50941566929494a0e891ed5613918b4b51b0d1fb97783b26a" +
+      "cf7d0f384cfb35f4d2824f5dd380623a26bf180b63961c619dcdb20cae406f22" +
+      "f6e276c80a37259490cfeb72c1a71a84f1846d330877ba3e3101ec9c7b020111"
+    ),
+    data: util.hex2abv(
+      "c7f5270fca725f9bd19f519a8d7cca3cc5c079024029f3bae510f9b02140fe23" +
+      "8908e4f6c18f07a89c687c8684669b1f1db2baf9251a3c829faccb493084e16e" +
+      "c9e28d58868074a5d6221667dd6e528d16fe2c9f3db4cfaf6c4dce8c8439af38" +
+      "ceaaaa9ce2ecae7bc8f4a5a55e3bf96df9cd575c4f9cb327951b8cdfe4087168"
+    ),
+    sig: util.hex2abv(
+      "8ffc38f9b820ef6b080fd2ec7de5626c658d79056f3edf610a295b7b0546f73e" +
+      "01ffdf4d0070ebf79c33fd86c2d608be9438b3d420d09535b97cd3d846ecaf8f" +
+      "6551cdf93197e9f8fb048044473ab41a801e9f7fc983c62b324361dade9f71a6" +
+      "5952bd35c59faaa4d6ff462f68a6c4ec0b428aa47336f2178aeb276136563b7d"
+    ),
+    saltLength: 10
+  },
+
   key_wrap_known_answer: {
     key:          util.hex2abv("0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a"),
     wrapping_key: util.hex2abv("0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b"),
     wrapping_iv:  util.hex2abv("0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c"),
     wrapped_key:  util.hex2abv("9ed0283a9a2b7e4292ebc5135e6342cc" +
                                "8a7f65802a1f6fd41bd3251c4da0c138")
   },
 
@@ -447,17 +623,22 @@ tv = {
   pbkdf2_sha1: {
     password: new TextEncoder("utf-8").encode("passwordPASSWORDpassword"),
     salt: new TextEncoder("utf-8").encode("saltSALTsaltSALTsaltSALTsaltSALTsalt"),
     iterations: 4096,
     length: 25 * 8,
 
     derived: util.hex2abv(
       "3d2eec4fe41c849b80c8d83662c0e44a8b291a964cf2f07038"
-    )
+    ),
+
+    jwk: {
+      kty: "oct",
+      k: "cGFzc3dvcmRQQVNTV09SRHBhc3N3b3Jk"
+    }
   },
 
   // https://stackoverflow.com/questions/5130513/pbkdf2-hmac-sha2-test-vectors
   pbkdf2_sha256: {
     password: new TextEncoder("utf-8").encode("passwordPASSWORDpassword"),
     salt: new TextEncoder("utf-8").encode("saltSALTsaltSALTsaltSALTsaltSALTsalt"),
     iterations: 4096,
     length: 40 * 8,
@@ -748,9 +929,114 @@ tv = {
       "3826dcef3a1586956105ebea805d871f34c46c25bc30fc66b2db26cb0a930000" +
       "038184000281804fc9904887ac7fabff87f054003547c2d9458c1f6f584c140d" +
       "7271f8b266bb390af7e3f625a629bec9c6a057a4cbe1a556d5e3eb2ff1c6ff67" +
       "7a08b0c7c509110b9e7c6dbc961ca4360362d3dbcffc5bf2bb7207e0a5922f77" +
       "cf5464b316aa49fb62b338ebcdb30bf573d07b663bb7777b69d6317df0a4f636" +
       "ba3d9acbf9e8ac"
     )
   },
+
+  // Taken from appendix A of RFC 5869.
+  // <https://tools.ietf.org/html/rfc5869>
+  hkdf: [
+    {
+      prf: "SHA-256",
+      key: util.hex2abv("0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b"),
+      salt: util.hex2abv("000102030405060708090a0b0c"),
+      info: util.hex2abv("f0f1f2f3f4f5f6f7f8f9"),
+      data: util.hex2abv(
+        "3cb25f25faacd57a90434f64d0362f2a2d2d0a90cf1a5a4c5db02d56ecc4c5bf" +
+        "34007208d5b887185865"
+      ),
+      jwk: {
+        kty: "oct",
+        k: "CwsLCwsLCwsLCwsLCwsLCwsLCwsLCw"
+      }
+    },
+    {
+      prf: "SHA-256",
+      key: util.hex2abv(
+        "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f" +
+        "202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f" +
+        "404142434445464748494a4b4c4d4e4f"
+      ),
+      salt: util.hex2abv(
+        "606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f" +
+        "808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9f" +
+        "a0a1a2a3a4a5a6a7a8a9aaabacadaeaf"
+      ),
+      info: util.hex2abv(
+        "b0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecf" +
+        "d0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeef" +
+        "f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff"
+      ),
+      data: util.hex2abv(
+        "b11e398dc80327a1c8e7f78c596a49344f012eda2d4efad8a050cc4c19afa97c" +
+        "59045a99cac7827271cb41c65e590e09da3275600c2f09b8367793a9aca3db71" +
+        "cc30c58179ec3e87c14c01d5c1f3434f1d87"
+      )
+    },
+    {
+      prf: "SHA-256",
+      key: util.hex2abv("0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b"),
+      salt: util.hex2abv(""),
+      info: util.hex2abv(""),
+      data: util.hex2abv(
+        "8da4e775a563c18f715f802a063c5a31b8a11f5c5ee1879ec3454e5f3c738d2d" +
+        "9d201395faa4b61a96c8"
+      )
+    },
+    {
+      prf: "SHA-1",
+      key: util.hex2abv("0b0b0b0b0b0b0b0b0b0b0b"),
+      salt: util.hex2abv("000102030405060708090a0b0c"),
+      info: util.hex2abv("f0f1f2f3f4f5f6f7f8f9"),
+      data: util.hex2abv(
+        "085a01ea1b10f36933068b56efa5ad81a4f14b822f5b091568a9cdd4f155fda2" +
+        "c22e422478d305f3f896"
+      )
+    },
+    {
+      prf: "SHA-1",
+      key: util.hex2abv(
+        "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f" +
+        "202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f" +
+        "404142434445464748494a4b4c4d4e4f"
+      ),
+      salt: util.hex2abv(
+        "606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f" +
+        "808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9f" +
+        "a0a1a2a3a4a5a6a7a8a9aaabacadaeaf"
+      ),
+      info: util.hex2abv(
+        "b0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecf" +
+        "d0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeef" +
+        "f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff"
+      ),
+      data: util.hex2abv(
+        "0bd770a74d1160f7c9f12cd5912a06ebff6adcae899d92191fe4305673ba2ffe" +
+        "8fa3f1a4e5ad79f3f334b3b202b2173c486ea37ce3d397ed034c7f9dfeb15c5e" +
+        "927336d0441f4c4300e2cff0d0900b52d3b4"
+      )
+    },
+    {
+      prf: "SHA-1",
+      key: util.hex2abv("0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b"),
+      salt: util.hex2abv(""),
+      info: util.hex2abv(""),
+      data: util.hex2abv(
+        "0ac1af7002b3d761d1e55298da9d0506b9ae52057220a306e07b6b87e8df21d0" +
+        "ea00033de03984d34918"
+      )
+    },
+    {
+      prf: "SHA-1",
+      key: util.hex2abv("0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c"),
+      salt: util.hex2abv(""),
+      info: util.hex2abv(""),
+      data: util.hex2abv(
+        "2c91117204d745f3500d636a62f64f0ab3bae548aa53d423b0d1f27ebba6f5e5" +
+        "673a081d70cce7acfc48"
+      )
+    }
+  ]
 }
new file mode 100644
--- /dev/null
+++ b/dom/crypto/test/test_WebCrypto_HKDF.html
@@ -0,0 +1,351 @@
+<!DOCTYPE html>
+<html>
+
+<head>
+<title>WebCrypto Test Suite</title>
+<meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
+<link rel="stylesheet" href="./test_WebCrypto.css"/>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+
+<!-- Utilities for manipulating ABVs -->
+<script src="util.js"></script>
+
+<!-- A simple wrapper around IndexedDB -->
+<script src="simpledb.js"></script>
+
+<!-- Test vectors drawn from the literature -->
+<script src="./test-vectors.js"></script>
+
+<!-- General testing framework -->
+<script src="./test-array.js"></script>
+
+<script>/*<![CDATA[*/
+"use strict";
+
+// -----------------------------------------------------------------------------
+TestArray.addTest(
+  "Deriving zero bits should fail",
+  function() {
+    var that = this;
+    var key = util.hex2abv("0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b");
+
+    var alg = {
+      name: "HKDF",
+      hash: "SHA-256",
+      salt: new Uint8Array(),
+      info: new Uint8Array()
+    };
+
+    crypto.subtle.importKey("raw", key, "HKDF", false, ["deriveBits"])
+      .then(x => crypto.subtle.deriveBits(alg, x, 0), error(that))
+      .then(error(that), complete(that));
+  }
+);
+
+// -----------------------------------------------------------------------------
+TestArray.addTest(
+  "Derive four bits with HKDF, no salt or info given",
+  function() {
+    var that = this;
+    var key = util.hex2abv("0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b");
+
+    var alg = {
+      name: "HKDF",
+      hash: "SHA-256",
+      salt: new Uint8Array(),
+      info: new Uint8Array()
+    };
+
+    crypto.subtle.importKey("raw", key, "HKDF", false, ["deriveBits"])
+      .then(x => crypto.subtle.deriveBits(alg, x, 4))
+      // The last 4 bits should be zeroes (1000 1101 => 1000 0000).
+      .then(memcmp_complete(that, new Uint8Array([0x80])))
+      .catch(error(that));
+  }
+);
+
+// -----------------------------------------------------------------------------
+TestArray.addTest(
+  "Deriving too many bits should fail",
+  function() {
+    var that = this;
+    var key = util.hex2abv("0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b");
+
+    var alg = {
+      name: "HKDF",
+      hash: "SHA-256",
+      salt: new Uint8Array(),
+      info: new Uint8Array()
+    };
+
+    function deriveBits(x) {
+      // The maximum length (in bytes) of output material for HKDF is 255 times
+      // the digest length. In this case, the digest length (in bytes) of
+      // SHA-256 is 32; 32*255 = 8160. deriveBits expects the length to be in
+      // bits, so 8160*8=65280 and add 1 to exceed the maximum length.
+      return crypto.subtle.deriveBits(alg, x, 65281);
+    }
+
+    crypto.subtle.importKey("raw", key, "HKDF", false, ["deriveBits"])
+      .then(deriveBits, error(that))
+      .then(error(that), complete(that));
+  }
+);
+
+// -----------------------------------------------------------------------------
+TestArray.addTest(
+  "Deriving with an unsupported PRF should fail",
+  function() {
+    var that = this;
+    var key = util.hex2abv("0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b");
+
+    var alg = {
+      name: "HKDF",
+      hash: "HMAC",
+      salt: new Uint8Array(),
+      info: new Uint8Array()
+    };
+
+    function deriveBits(x) {
+      return crypto.subtle.deriveBits(alg, x, 4);
+    }
+
+    crypto.subtle.importKey("raw", key, "HKDF", false, ["deriveBits"])
+      .then(deriveBits, error(that))
+      .then(error(that), complete(that));
+  }
+);
+
+// -----------------------------------------------------------------------------
+TestArray.addTest(
+  "Deriving with a non-HKDF key should fail",
+  function() {
+    var that = this;
+
+    var alg = {
+      name: "HKDF",
+      hash: "HMAC",
+      salt: new Uint8Array(),
+      info: new Uint8Array()
+    };
+
+    function deriveBits(x) {
+      return crypto.subtle.deriveBits(alg, x, 4);
+    }
+
+    var ecAlg = {name: "ECDH", namedCurve: "P-256"};
+    crypto.subtle.generateKey(ecAlg, false, ["deriveBits"])
+      .then(deriveBits, error(that))
+      .then(error(that), complete(that));
+  }
+);
+
+// -----------------------------------------------------------------------------
+TestArray.addTest(
+  "Derive known values from test vectors (SHA-1 and SHA-256)",
+  function() {
+    var that = this;
+    var tests = tv.hkdf.slice();
+
+    function next() {
+      if (!tests.length) {
+        return;
+      }
+
+      var test = tests.shift();
+      var {key, data} = test;
+
+      return crypto.subtle.importKey("raw", key, "HKDF", false, ["deriveBits"])
+        .then(function (key) {
+          return crypto.subtle.deriveBits({
+            name: "HKDF",
+            hash: test.prf,
+            salt: test.salt,
+            info: test.info
+          }, key, test.data.byteLength * 8);
+        })
+        .then(function (data) {
+          if (!util.memcmp(data, test.data)) {
+            throw new Error("derived bits don't match expected value");
+          }
+
+          // Next test vector.
+          return next();
+        });
+    }
+
+    next().then(complete(that), error(that));
+  }
+);
+
+// -----------------------------------------------------------------------------
+TestArray.addTest(
+  "Derive known values from test vectors (JWK, SHA-256)",
+  function() {
+    var that = this;
+    var test = tv.hkdf[0];
+    var alg = {
+      name: "HKDF",
+      hash: test.prf,
+      salt: test.salt,
+      info: test.info
+    };
+
+    crypto.subtle.importKey("jwk", test.jwk, "HKDF", false, ["deriveBits"])
+      .then(x => crypto.subtle.deriveBits(alg, x, test.data.byteLength * 8))
+      .then(memcmp_complete(that, test.data), error(that));
+  }
+);
+
+// -----------------------------------------------------------------------------
+TestArray.addTest(
+  "Test wrapping/unwrapping an HKDF key",
+  function() {
+    var that = this;
+    var hkdfKey = util.hex2abv("0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b");
+    var alg = {name: "AES-GCM", length: 256, iv: new Uint8Array(16)};
+    var wrappingKey;
+
+    function wrap(x) {
+      wrappingKey = x;
+      return crypto.subtle.encrypt(alg, wrappingKey, hkdfKey);
+    }
+
+    function unwrap(wrappedKey) {
+      return crypto.subtle.unwrapKey(
+        "raw", wrappedKey, wrappingKey, alg, "HKDF", false, ["deriveBits"])
+        .then(rawKey => {
+          return crypto.subtle.deriveBits({
+            name: "HKDF",
+            hash: "SHA-256",
+            salt: new Uint8Array(),
+            info: new Uint8Array()
+          }, rawKey, 4);
+        })
+        .then(derivedBits => {
+          if (!util.memcmp(derivedBits, new Uint8Array([0x80]))) {
+            throw new Error("deriving bits failed");
+          }
+
+          // Forward to reuse.
+          return wrappedKey;
+        });
+    }
+
+    crypto.subtle.generateKey(alg, false, ["encrypt", "unwrapKey"])
+      .then(wrap)
+      .then(unwrap)
+      .then(complete(that), error(that));
+  }
+);
+
+// -----------------------------------------------------------------------------
+TestArray.addTest(
+  "Unwrapping an HKDF key in PKCS8 format should fail",
+  function() {
+    var that = this;
+    var hkdfKey = util.hex2abv("0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b");
+    var alg = {name: "AES-GCM", length: 256, iv: new Uint8Array(16)};
+    var wrappingKey;
+
+    function wrap(x) {
+      wrappingKey = x;
+      return crypto.subtle.encrypt(alg, wrappingKey, hkdfKey);
+    }
+
+    function unwrap(x) {
+      return crypto.subtle.unwrapKey(
+        "pkcs8", x, wrappingKey, alg, "HKDF", false, ["deriveBits"]);
+    }
+
+    crypto.subtle.generateKey(alg, false, ["encrypt", "unwrapKey"])
+      .then(wrap, error(that))
+      .then(unwrap, error(that))
+      .then(error(that), complete(that));
+  }
+);
+
+// -----------------------------------------------------------------------------
+TestArray.addTest(
+  "Derive an AES key using with HKDF",
+  function() {
+    var that = this;
+    var key = util.hex2abv("0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b");
+
+    var alg = {
+      name: "HKDF",
+      hash: "SHA-256",
+      salt: new Uint8Array(),
+      info: new Uint8Array()
+    };
+
+    function deriveKey(x) {
+      var targetAlg = {name: "AES-GCM", length: 256};
+      return crypto.subtle.deriveKey(alg, x, targetAlg, false, ["encrypt"]);
+    }
+
+    crypto.subtle.importKey("raw", key, "HKDF", false, ["deriveKey"])
+      .then(deriveKey)
+      .then(complete(that), error(that))
+  }
+);
+
+// -----------------------------------------------------------------------------
+TestArray.addTest(
+  "Deriving an HKDF key with HKDF should fail",
+  function() {
+    var that = this;
+    var key = util.hex2abv("0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b");
+
+    var alg = {
+      name: "HKDF",
+      hash: "SHA-256",
+      salt: new Uint8Array(),
+      info: new Uint8Array()
+    };
+
+    function deriveKey(x) {
+      return crypto.subtle.deriveKey(alg, x, "HKDF", false, ["deriveBits"]);
+    }
+
+    crypto.subtle.importKey("raw", key, "HKDF", false, ["deriveKey"])
+      .then(deriveKey)
+      .then(error(that), complete(that))
+  }
+);
+
+/*]]>*/</script>
+</head>
+
+<body>
+
+<div id="content">
+	<div id="head">
+		<b>Web</b>Crypto<br>
+	</div>
+
+    <div id="start" onclick="start();">RUN ALL</div>
+
+    <div id="resultDiv" class="content">
+    Summary:
+    <span class="pass"><span id="passN">0</span> passed, </span>
+    <span class="fail"><span id="failN">0</span> failed, </span>
+    <span class="pending"><span id="pendingN">0</span> pending.</span>
+    <br/>
+    <br/>
+
+    <table id="results">
+        <tr>
+            <th>Test</th>
+            <th>Result</th>
+            <th>Time</th>
+        </tr>
+    </table>
+
+    </div>
+
+    <div id="foot"></div>
+</div>
+
+</body>
+</html>
--- a/dom/crypto/test/test_WebCrypto_PBKDF2.html
+++ b/dom/crypto/test/test_WebCrypto_PBKDF2.html
@@ -34,16 +34,42 @@ TestArray.addTest(
       complete(that, hasKeyFields),
       error(that)
     );
   }
 );
 
 // -----------------------------------------------------------------------------
 TestArray.addTest(
+  "Unwrapping a PBKDF2 key in PKCS8 format should fail",
+  function() {
+    var that = this;
+    var pbkdf2Key = new TextEncoder("utf-8").encode("password");
+    var alg = {name: "AES-GCM", length: 256, iv: new Uint8Array(16)};
+    var wrappingKey;
+
+    function wrap(x) {
+      wrappingKey = x;
+      return crypto.subtle.encrypt(alg, wrappingKey, pbkdf2Key);
+    }
+
+    function unwrap(x) {
+      return crypto.subtle.unwrapKey(
+        "pkcs8", x, wrappingKey, alg, "PBKDF2", false, ["deriveBits"]);
+    }
+
+    crypto.subtle.generateKey(alg, false, ["encrypt", "unwrapKey"])
+      .then(wrap, error(that))
+      .then(unwrap, error(that))
+      .then(error(that), complete(that));
+  }
+);
+
+// -----------------------------------------------------------------------------
+TestArray.addTest(
   "Import raw PBKDF2 key and derive bits using HMAC-SHA-1",
   function() {
     var that = this;
     var alg = "PBKDF2";
     var key = tv.pbkdf2_sha1.password;
 
     function doDerive(x) {
       if (!hasKeyFields(x)) {
@@ -63,16 +89,44 @@ TestArray.addTest(
     crypto.subtle.importKey("raw", key, alg, false, ["deriveBits"])
       .then( doDerive, fail )
       .then( memcmp_complete(that, tv.pbkdf2_sha1.derived), fail );
   }
 );
 
 // -----------------------------------------------------------------------------
 TestArray.addTest(
+  "Import a PBKDF2 key in JWK format and derive bits using HMAC-SHA-1",
+  function() {
+    var that = this;
+    var alg = "PBKDF2";
+
+    function doDerive(x) {
+      if (!hasKeyFields(x)) {
+        throw "Invalid key; missing field(s)";
+      }
+
+      var alg = {
+        name: "PBKDF2",
+        hash: "SHA-1",
+        salt: tv.pbkdf2_sha1.salt,
+        iterations: tv.pbkdf2_sha1.iterations
+      };
+      return crypto.subtle.deriveBits(alg, x, tv.pbkdf2_sha1.length);
+    }
+    function fail(x) { console.log("failing"); error(that)(x); }
+
+    crypto.subtle.importKey("jwk", tv.pbkdf2_sha1.jwk, alg, false, ["deriveBits"])
+      .then( doDerive, fail )
+      .then( memcmp_complete(that, tv.pbkdf2_sha1.derived), fail );
+  }
+);
+
+// -----------------------------------------------------------------------------
+TestArray.addTest(
   "Import raw PBKDF2 key and derive a new key using HMAC-SHA-1",
   function() {
     var that = this;
     var alg = "PBKDF2";
     var key = tv.pbkdf2_sha1.password;
 
     function doDerive(x) {
       if (!hasKeyFields(x)) {
new file mode 100644
--- /dev/null
+++ b/dom/crypto/test/test_WebCrypto_RSA_PSS.html
@@ -0,0 +1,404 @@
+<!DOCTYPE html>
+<html>
+
+<head>
+<title>WebCrypto Test Suite</title>
+<meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
+<link rel="stylesheet" href="./test_WebCrypto.css"/>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+
+<!-- Utilities for manipulating ABVs -->
+<script src="util.js"></script>
+
+<!-- A simple wrapper around IndexedDB -->
+<script src="simpledb.js"></script>
+
+<!-- Test vectors drawn from the literature -->
+<script src="./test-vectors.js"></script>
+
+<!-- General testing framework -->
+<script src="./test-array.js"></script>
+
+<script>/*<![CDATA[*/
+"use strict";
+
+// Generating 2048-bit keys takes some time.
+SimpleTest.requestLongerTimeout(2);
+
+// -----------------------------------------------------------------------------
+TestArray.addTest(
+  "RSA-PSS key generation (SHA-1, 1024-bit)",
+  function () {
+    var that = this;
+    var alg = {
+      name: "RSA-PSS",
+      hash: "SHA-1",
+      modulusLength: 1024,
+      publicExponent: new Uint8Array([0x01, 0x00, 0x01])
+    };
+
+    crypto.subtle.generateKey(alg, false, ["sign", "verify"])
+      .then(complete(that), error(that));
+  }
+);
+
+// -----------------------------------------------------------------------------
+TestArray.addTest(
+  "RSA-PSS key generation and sign/verify round-trip (SHA-256, 2048-bit)",
+  function () {
+    var that = this;
+    var alg = {
+      name: "RSA-PSS",
+      hash: "SHA-256",
+      modulusLength: 2048,
+      publicExponent: new Uint8Array([0x01, 0x00, 0x01])
+    };
+
+    var privKey, pubKey;
+    var data = crypto.getRandomValues(new Uint8Array(128));
+    function setKey(x) { pubKey = x.publicKey; privKey = x.privateKey; }
+    function doSign() {
+      var alg = {name: "RSA-PSS", saltLength: 32};
+      return crypto.subtle.sign(alg, privKey, data);
+    }
+    function doVerify(x) {
+      var alg = {name: "RSA-PSS", saltLength: 32};
+      return crypto.subtle.verify(alg, pubKey, x, data);
+    }
+
+    crypto.subtle.generateKey(alg, false, ["sign", "verify"])
+      .then(setKey, error(that))
+      .then(doSign, error(that))
+      .then(doVerify, error(that))
+      .then(complete(that, x => x), error(that))
+  }
+);
+
+// -----------------------------------------------------------------------------
+TestArray.addTest(
+  "RSA-PSS verify known signature (SHA-1, 1024-bit)",
+  function () {
+    var that = this;
+    var alg = {name: "RSA-PSS", hash: "SHA-1"};
+    var vec = tv.rsapss;
+
+    function doVerify(x) {
+      var alg = {name: "RSA-PSS", saltLength: vec.saltLength};
+      return crypto.subtle.verify(alg, x, vec.sig, vec.data);
+    }
+
+    crypto.subtle.importKey("spki", vec.spki, alg, false, ["verify"])
+      .then(doVerify, error(that))
+      .then(complete(that, x => x), error(that));
+  }
+);
+
+// -----------------------------------------------------------------------------
+TestArray.addTest(
+  "Test invalid RSA-PSS signatures",
+  function () {
+    var that = this;
+    var alg = {name: "RSA-PSS", hash: "SHA-1"};
+    var vec = tv.rsapss;
+
+    function doVerify(x) {
+      var alg = {name: "RSA-PSS", saltLength: vec.saltLength};
+      var clone1 = new Uint8Array(vec.data);
+      var clone2 = new Uint8Array(vec.data);
+      clone1[clone1.byteLength - 1] ^= 1;
+      clone2[0] ^= 1;
+
+      return Promise.all([
+        crypto.subtle.verify(alg, x, vec.sig, clone1),
+        crypto.subtle.verify(alg, x, vec.sig, clone2),
+        crypto.subtle.verify(alg, x, vec.sig, vec.data.slice(1)),
+        crypto.subtle.verify(alg, x, vec.sig, vec.data.slice(0, vec.data.byteLength - 1)),
+      ]);
+    }
+
+    crypto.subtle.importKey("spki", vec.spki, alg, false, ["verify"])
+      .then(doVerify, error(that))
+      .then(results => results.every(x => !x))
+      .then(complete(that, x => x), error(that));
+  }
+);
+
+// -----------------------------------------------------------------------------
+TestArray.addTest(
+  "RSA-PSS verify known signature (SHA-1, 1024-bit, JWK)",
+  function () {
+    var that = this;
+    var alg = {name: "RSA-PSS", hash: "SHA-1"};
+
+    function doVerify(x) {
+      var alg = {name: "RSA-PSS", saltLength: tv.rsapss.saltLength};
+      return crypto.subtle.verify(alg, x, tv.rsapss.sig, tv.rsapss.data);
+    }
+
+    crypto.subtle.importKey("jwk", tv.rsapss.jwk_pub, alg, false, ["verify"])
+      .then(doVerify, error(that))
+      .then(complete(that, x => x), error(that));
+  }
+);
+
+// -----------------------------------------------------------------------------
+TestArray.addTest(
+  "RSA-PSS verify known signatures (SHA-1 to SHA-512, 1024-bit)",
+  function () {
+    var that = this;
+
+    function verifyCase(hash, tv) {
+      var alg = {name: "RSA-PSS", hash, saltLength: tv.saltLength};
+      return crypto.subtle.importKey("spki", tv.spki, alg, false, ["verify"])
+        .then(x => crypto.subtle.verify(alg, x, tv.sig, tv.data));
+    }
+
+    Promise.all([
+      verifyCase("SHA-1", tv.rsapss2),
+      verifyCase("SHA-256", tv.rsapss3),
+      verifyCase("SHA-384", tv.rsapss4),
+      verifyCase("SHA-512", tv.rsapss5),
+    ]).then(complete(that, x => x.every(y => y)), error(that));
+  }
+);
+
+// -----------------------------------------------------------------------------
+TestArray.addTest(
+  "RSA-PSS import SPKI/PKCS#8 keys and sign/verify (SHA-1, 1024-bit)",
+  function () {
+    var that = this;
+    var alg = {name: "RSA-PSS", hash: "SHA-1"};
+
+    var privKey, pubKey;
+    function setKeys([pub, priv]) { pubKey = pub; privKey = priv; }
+    function doSign() {
+      var alg = {name: "RSA-PSS", saltLength: tv.rsapss.saltLength};
+      return crypto.subtle.sign(alg, privKey, tv.rsapss.data);
+    }
+    function doVerify(x) {
+      var alg = {name: "RSA-PSS", saltLength: tv.rsapss.saltLength};
+      return crypto.subtle.verify(alg, pubKey, x, tv.rsapss.data);
+    }
+
+    var spki =
+      crypto.subtle.importKey("spki", tv.rsapss.spki, alg, false, ["verify"]);
+    var pkcs8 =
+      crypto.subtle.importKey("pkcs8", tv.rsapss.pkcs8, alg, false, ["sign"]);
+
+    Promise.all([spki, pkcs8])
+      .then(setKeys, error(that))
+      .then(doSign, error(that))
+      .then(doVerify, error(that))
+      .then(complete(that, x => x), error(that));
+  }
+);
+
+// -----------------------------------------------------------------------------
+TestArray.addTest(
+  "RSA-PSS import JWK keys and sign/verify (SHA-1, 1024-bit)",
+  function () {
+    var that = this;
+    var alg = {name: "RSA-PSS", hash: "SHA-1"};
+
+    var privKey, pubKey;
+    function setKeys([pub, priv]) { pubKey = pub; privKey = priv; }
+    function doSign() {
+      var alg = {name: "RSA-PSS", saltLength: tv.rsapss.saltLength};
+      return crypto.subtle.sign(alg, privKey, tv.rsapss.data);
+    }
+    function doVerify(x) {
+      var alg = {name: "RSA-PSS", saltLength: tv.rsapss.saltLength};
+      return crypto.subtle.verify(alg, pubKey, x, tv.rsapss.data);
+    }
+
+    var spki =
+      crypto.subtle.importKey("jwk", tv.rsapss.jwk_pub, alg, false, ["verify"]);
+    var pkcs8 =
+      crypto.subtle.importKey("jwk", tv.rsapss.jwk_priv, alg, false, ["sign"]);
+
+    Promise.all([spki, pkcs8])
+      .then(setKeys, error(that))
+      .then(doSign, error(that))
+      .then(doVerify, error(that))
+      .then(complete(that, x => x), error(that));
+  }
+);
+
+// -----------------------------------------------------------------------------
+TestArray.addTest(
+  "RSA-PSS SPKI import/export (SHA-1, 1024-bit)",
+  function () {
+    var that = this;
+    var alg = {name: "RSA-PSS", hash: "SHA-1"};
+
+    function doExport(x) {
+      return crypto.subtle.exportKey("spki", x);
+    }
+
+    crypto.subtle.importKey("spki", tv.rsapss.spki, alg, true, ["verify"])
+      .then(doExport, error(that))
+      .then(memcmp_complete(that, tv.rsapss.spki), error(that));
+  }
+);
+
+// -----------------------------------------------------------------------------
+TestArray.addTest(
+  "RSA-PSS PKCS#8 import/export (SHA-1, 1024-bit)",
+  function () {
+    var that = this;
+    var alg = {name: "RSA-PSS", hash: "SHA-1"};
+
+    function doExport(x) {
+      return crypto.subtle.exportKey("pkcs8", x);
+    }
+
+    crypto.subtle.importKey("pkcs8", tv.rsapss.pkcs8, alg, true, ["sign"])
+      .then(doExport, error(that))
+      .then(memcmp_complete(that, tv.rsapss.pkcs8), error(that));
+  }
+);
+
+// -----------------------------------------------------------------------------
+TestArray.addTest(
+  "RSA-PSS JWK export a public key",
+  function () {
+    var that = this;
+    var alg = {name: "RSA-PSS", hash: "SHA-1"};
+    var jwk = tv.rsapss.jwk_pub;
+
+    function doExport(x) {
+      return crypto.subtle.exportKey("jwk", x);
+    }
+
+    crypto.subtle.importKey("jwk", jwk, alg, true, ["verify"])
+      .then(doExport)
+      .then(
+        complete(that, function(x) {
+          return hasBaseJwkFields(x) &&
+                 hasFields(x, ["n", "e"]) &&
+                 x.kty == "RSA" &&
+                 x.alg == "PS1" &&
+                 x.ext &&
+                 shallowArrayEquals(x.key_ops, ["verify"]) &&
+                 x.n == jwk.n &&
+                 x.e == jwk.e;
+          }),
+        error(that)
+      );
+  }
+);
+
+// -----------------------------------------------------------------------------
+TestArray.addTest(
+  "RSA-PSS JWK export a private key",
+  function () {
+    var that = this;
+    var alg = {name: "RSA-PSS", hash: "SHA-1"};
+    var jwk = tv.rsapss.jwk_priv;
+
+    function doExport(x) {
+      return crypto.subtle.exportKey("jwk", x);
+    }
+
+    crypto.subtle.importKey("jwk", jwk, alg, true, ["sign"])
+      .then(doExport)
+      .then(
+        complete(that, function(x) {
+          return hasBaseJwkFields(x) &&
+                 hasFields(x, ["n", "e", "d", "p", "q", "dp", "dq", "qi"]) &&
+                 x.kty == "RSA" &&
+                 x.alg == "PS1" &&
+                 x.ext &&
+                 shallowArrayEquals(x.key_ops, ["sign"]) &&
+                 x.n == jwk.n &&
+                 x.e == jwk.e &&
+                 x.d == jwk.d &&
+                 x.p == jwk.p &&
+                 x.q == jwk.q &&
+                 x.dp == jwk.dp &&
+                 x.dq == jwk.dq &&
+                 x.qi == jwk.qi;
+          }),
+        error(that)
+      );
+  }
+);
+
+// -----------------------------------------------------------------------------
+TestArray.addTest(
+  "Deterministic RSA-PSS signatures with saltLength=0 (SHA-256, 2048-bit)",
+  function () {
+    var that = this;
+    var alg = {
+      name: "RSA-PSS",
+      hash: "SHA-256",
+      modulusLength: 2048,
+      publicExponent: new Uint8Array([0x01, 0x00, 0x01])
+    };
+
+    var privKey, pubKey;
+    var data = crypto.getRandomValues(new Uint8Array(128));
+    function setKey(x) { pubKey = x.publicKey; privKey = x.privateKey; }
+
+    function doSignTwice() {
+      var alg = {name: "RSA-PSS", saltLength: 0};
+      return Promise.all([
+        crypto.subtle.sign(alg, privKey, data),
+        crypto.subtle.sign(alg, privKey, data)
+      ]);
+    }
+
+    function doVerify(x) {
+      var alg = {name: "RSA-PSS", saltLength: 0};
+      return crypto.subtle.verify(alg, pubKey, x, data);
+    }
+
+    crypto.subtle.generateKey(alg, false, ["sign", "verify"])
+      .then(setKey, error(that))
+      .then(doSignTwice, error(that))
+      .then(([sig1, sig2]) => {
+        if (!util.memcmp(sig1, sig2)) {
+          throw new Error("sig1 must be equal to sig2");
+        }
+
+        return sig1;
+      }, error(that))
+      .then(doVerify, error(that))
+      .then(complete(that, x => x), error(that))
+  }
+);
+/*]]>*/</script>
+</head>
+
+<body>
+
+<div id="content">
+	<div id="head">
+		<b>Web</b>Crypto<br>
+	</div>
+
+    <div id="start" onclick="start();">RUN ALL</div>
+
+    <div id="resultDiv" class="content">
+    Summary:
+    <span class="pass"><span id="passN">0</span> passed, </span>
+    <span class="fail"><span id="failN">0</span> failed, </span>
+    <span class="pending"><span id="pendingN">0</span> pending.</span>
+    <br/>
+    <br/>
+
+    <table id="results">
+        <tr>
+            <th>Test</th>
+            <th>Result</th>
+            <th>Time</th>
+        </tr>
+    </table>
+
+    </div>
+
+    <div id="foot"></div>
+</div>
+
+</body>
+</html>
--- a/dom/crypto/test/util.js
+++ b/dom/crypto/test/util.js
@@ -16,17 +16,17 @@ var util = {
         return false;
       }
     }
     return true;
   },
 
   // Convert an ArrayBufferView to a hex string
   abv2hex: function util_abv2hex(abv) {
-    var b = new Uint8Array(abv.buffer, abv.byteOffset, abv.byteLength);
+    var b = new Uint8Array(abv);
     var hex = "";
     for (var i=0; i <b.length; ++i) {
       var zeropad = (b[i] < 0x10) ? "0" : "";
       hex += zeropad + b[i].toString(16);
     }
     return hex;
   },
 
--- a/dom/events/NotifyPaintEvent.cpp
+++ b/dom/events/NotifyPaintEvent.cpp
@@ -79,21 +79,19 @@ NotifyPaintEvent::GetClientRects(nsIDOMC
 
 already_AddRefed<DOMRectList>
 NotifyPaintEvent::ClientRects()
 {
   nsISupports* parent = ToSupports(this);
   RefPtr<DOMRectList> rectList = new DOMRectList(parent);
 
   nsRegion r = GetRegion();
-  nsRegionRectIterator iter(r);
-  for (const nsRect* rgnRect = iter.Next(); rgnRect; rgnRect = iter.Next()) {
+  for (auto iter = r.RectIter(); !iter.Done(); iter.Next()) {
     RefPtr<DOMRect> rect = new DOMRect(parent);
-    
-    rect->SetLayoutRect(*rgnRect);
+    rect->SetLayoutRect(iter.Get());
     rectList->Append(rect);
   }
 
   return rectList.forget();
 }
 
 NS_IMETHODIMP
 NotifyPaintEvent::GetPaintRequests(nsISupports** aResult)
--- a/dom/html/test/formData_test.js
+++ b/dom/html/test/formData_test.js
@@ -74,17 +74,16 @@ function testSet() {
 
   f.set("other", "value4");
   is(f.getAll("other").length, 1, "set() should replace existing entries.");
   is(f.getAll("other")[0], "value4", "set() should replace existing entries.");
 }
 
 function testFilename() {
   var f = new FormData();
-  // Spec says if a Blob (which is not a File) is added, the name parameter is set to "blob".
   f.append("blob", new Blob(["hi"]));
   ok(f.get("blob") instanceof Blob, "We should have a blob back.");
 
   // If a filename is passed, that should replace the original.
   f.append("blob2", new Blob(["hi"]), "blob2.txt");
   is(f.get("blob2").name, "blob2.txt", "Explicit filename should override \"blob\".");
 
   var file = new File(["hi"], "file1.txt");
@@ -158,17 +157,17 @@ function testSend(doneCb) {
     var response = xhr.response;
 
     for (var entry of response) {
       is(entry.body, 'hey');
       is(entry.headers['Content-Type'], 'text/plain');
     }
 
     is(response[0].headers['Content-Disposition'],
-        'form-data; name="empty"; filename="blob"');
+        'form-data; name="empty"; filename=""');
 
     is(response[1].headers['Content-Disposition'],
         'form-data; name="explicit"; filename="explicit-file-name"');
 
     is(response[2].headers['Content-Disposition'],
         'form-data; name="explicit-empty"; filename=""');
 
     is(response[3].headers['Content-Disposition'],
--- a/dom/html/test/test_formSubmission.html
+++ b/dom/html/test/test_formSubmission.html
@@ -585,17 +585,17 @@ var expectedAugment = [
   { name: "aNameNum", value: "9.2" },
   { name: "aNameFile1", value: placeholder_myFile1 },
   { name: "aNameFile2", value: placeholder_myFile2 },
   //{ name: "aNameObj", value: "[object XMLHttpRequest]" },
   //{ name: "aNameNull", value: "null" },
   //{ name: "aNameUndef", value: "undefined" },
 ];
 
-function checkMPSubmission(sub, expected, test, isFormData = false) {
+function checkMPSubmission(sub, expected, test) {
   function getPropCount(o) {
     var x, l = 0;
     for (x in o) ++l;
     return l;
   }
   function mpquote(s) {
     return s.replace(/\r\n/g, " ")
             .replace(/\r/g, " ")
@@ -620,17 +620,17 @@ function checkMPSubmission(sub, expected
           "Wrong number of headers in " + test);
       is(sub[i].body,
          expected[i].value.replace(/\r\n|\r|\n/, "\r\n"),
          "Correct value in " + test);
     }
     else {
       is(sub[i].headers["Content-Disposition"],
          "form-data; name=\"" + mpquote(expected[i].name) + "\"; filename=\"" +
-           mpquote(expected[i].fileName != "" || !isFormData ? expected[i].fileName : "blob") + "\"",
+           mpquote(expected[i].fileName) + "\"",
          "Correct name in " + test);
       is(sub[i].headers["Content-Type"],
          expected[i].contentType,
          "Correct content type in " + test);
       is (getPropCount(sub[i].headers), 2,
           "Wrong number of headers in " + test);
       is(sub[i].body,
          expected[i].value,
@@ -777,48 +777,48 @@ function runTest() {
   checkURLSubmission(submission, expectedSub);
 
   // Send form using XHR and FormData
   xhr = new XMLHttpRequest();
   xhr.onload = function() { gen.next(); };
   xhr.open("POST", "form_submit_server.sjs");
   xhr.send(new FormData(form));
   yield undefined; // Wait for XHR load
-  checkMPSubmission(JSON.parse(xhr.responseText), expectedSub, "send form using XHR and FormData", true);
+  checkMPSubmission(JSON.parse(xhr.responseText), expectedSub, "send form using XHR and FormData");
   
   // Send disabled form using XHR and FormData
   setDisabled(document.querySelectorAll("input, select, textarea"), true);
   xhr.open("POST", "form_submit_server.sjs");
   xhr.send(new FormData(form));
   yield undefined;
-  checkMPSubmission(JSON.parse(xhr.responseText), [], "send disabled form using XHR and FormData", true);
+  checkMPSubmission(JSON.parse(xhr.responseText), [], "send disabled form using XHR and FormData");
   setDisabled(document.querySelectorAll("input, select, textarea"), false);
   
   // Send FormData
   function addToFormData(fd) {
     fd.append("aName", "aValue");
     fd.append("aNameNum", 9.2);
     fd.append("aNameFile1", myFile1);
     fd.append("aNameFile2", myFile2);
   }
   var fd = new FormData();
   addToFormData(fd);
   xhr.open("POST", "form_submit_server.sjs");
   xhr.send(fd);
   yield undefined;
-  checkMPSubmission(JSON.parse(xhr.responseText), expectedAugment, "send FormData", true);
+  checkMPSubmission(JSON.parse(xhr.responseText), expectedAugment, "send FormData");
 
   // Augment <form> using FormData
   fd = new FormData(form);
   addToFormData(fd);
   xhr.open("POST", "form_submit_server.sjs");
   xhr.send(fd);
   yield undefined;
   checkMPSubmission(JSON.parse(xhr.responseText),
-                    expectedSub.concat(expectedAugment), "send augmented FormData", true);
+                    expectedSub.concat(expectedAugment), "send augmented FormData");
 
   SimpleTest.finish();
   yield undefined;
 }
 
 </script>
 </pre>
 </body>
--- a/dom/identity/nsDOMIdentity.js
+++ b/dom/identity/nsDOMIdentity.js
@@ -645,24 +645,24 @@ nsDOMIdentity.prototype = {
   },
 
  /**
   * Internal methods that are not exposed to content.
   * See dom/webidl/Identity.webidl for the public interface.
   */
   // nsIObserver
   observe: function nsDOMIdentityInternal_observe(aSubject, aTopic, aData) {
-    let window = aSubject.QueryInterface(Ci.nsIDOMWindow);
-    if (window != this._window) {
+    let wId = aSubject.QueryInterface(Ci.nsISupportsPRUint64).data;
+    if (wId != this._innerWindowID) {
       return;
     }
 
     this.uninit();
 
-    Services.obs.removeObserver(this, "dom-window-destroyed");
+    Services.obs.removeObserver(this, "inner-window-destroyed");
     this._initializeState();
 
     // TODO: Also send message to DOMIdentity notifiying window is no longer valid
     // ie. in the case that the user closes the auth. window and we need to know.
 
     try {
       for (let msgName of this._messages) {
         this._mm.removeMessageListener(msgName, this);
@@ -724,17 +724,17 @@ nsDOMIdentity.prototype = {
       "Identity:IDP:CallGenKeyPairCallback",
       "Identity:IDP:CallBeginAuthenticationCallback"
     ];
     this._messages.forEach(function(msgName) {
       this._mm.addMessageListener(msgName, this);
     }, this);
 
     // Setup observers so we can remove message listeners.
-    Services.obs.addObserver(this, "dom-window-destroyed", false);
+    Services.obs.addObserver(this, "inner-window-destroyed", false);
   },
 
   uninit: function DOMIdentity_uninit() {
     this._log("nsDOMIdentity uninit() " + this._id);
     this._mm.sendAsyncMessage(
       "Identity:RP:Unwatch",
       { id: this._id }
     );
--- a/dom/media/DOMMediaStream.cpp
+++ b/dom/media/DOMMediaStream.cpp
@@ -1047,57 +1047,60 @@ DOMAudioNodeMediaStream::CreateTrackUnio
 {
   RefPtr<DOMAudioNodeMediaStream> stream = new DOMAudioNodeMediaStream(aNode);
   stream->InitTrackUnionStream(aWindow, aGraph);
   return stream.forget();
 }
 
 DOMHwMediaStream::DOMHwMediaStream()
 {
-#ifdef MOZ_WIDGET_GONK
-  mImageContainer = LayerManager::CreateImageContainer(ImageContainer::ASYNCHRONOUS);
-  mOverlayImage = mImageContainer->CreateOverlayImage();
-  nsAutoTArray<ImageContainer::NonOwningImage,1> images;
-  images.AppendElement(ImageContainer::NonOwningImage(mOverlayImage));
-  mImageContainer->SetCurrentImages(images);
-#endif
 }
 
 DOMHwMediaStream::~DOMHwMediaStream()
 {
 }
 
 already_AddRefed<DOMHwMediaStream>
-DOMHwMediaStream::CreateHwStream(nsIDOMWindow* aWindow)
+DOMHwMediaStream::CreateHwStream(nsIDOMWindow* aWindow, OverlayImage* aImage)
 {
   RefPtr<DOMHwMediaStream> stream = new DOMHwMediaStream();
 
   MediaStreamGraph* graph =
     MediaStreamGraph::GetInstance(MediaStreamGraph::SYSTEM_THREAD_DRIVER,
                                   AudioChannel::Normal);
   stream->InitSourceStream(aWindow, graph);
-  stream->Init(stream->GetInputStream());
+  stream->Init(stream->GetInputStream(), aImage);
 
   return stream.forget();
 }
 
 void
-DOMHwMediaStream::Init(MediaStream* stream)
+DOMHwMediaStream::Init(MediaStream* stream, OverlayImage* aImage)
 {
   SourceMediaStream* srcStream = stream->AsSourceStream();
 
+#ifdef MOZ_WIDGET_GONK
+  if (aImage) {
+    mOverlayImage = aImage;
+  } else {
+    Data imageData;
+    imageData.mOverlayId = DEFAULT_IMAGE_ID;
+    imageData.mSize.width = DEFAULT_IMAGE_WIDTH;
+    imageData.mSize.height = DEFAULT_IMAGE_HEIGHT;
+
+    mOverlayImage = new OverlayImage();
+    mOverlayImage->SetData(imageData);
+  }
+#endif
+
   if (srcStream) {
     VideoSegment segment;
 #ifdef MOZ_WIDGET_GONK
     const StreamTime delta = STREAM_TIME_MAX; // Because MediaStreamGraph will run out frames in non-autoplay mode,
                                               // we must give it bigger frame length to cover this situation.
-    mImageData.mOverlayId = DEFAULT_IMAGE_ID;
-    mImageData.mSize.width = DEFAULT_IMAGE_WIDTH;
-    mImageData.mSize.height = DEFAULT_IMAGE_HEIGHT;
-    mOverlayImage->SetData(mImageData);
 
     RefPtr<Image> image = static_cast<Image*>(mOverlayImage.get());
     mozilla::gfx::IntSize size = image->GetSize();
 
     segment.AppendFrame(image.forget(), delta, size);
 #endif
     srcStream->AddTrack(TRACK_VIDEO_PRIMARY, 0, new VideoSegment());
     srcStream->AppendToTrack(TRACK_VIDEO_PRIMARY, &segment);
@@ -1115,21 +1118,27 @@ DOMHwMediaStream::RequestOverlayId()
   return -1;
 #endif
 }
 
 void
 DOMHwMediaStream::SetImageSize(uint32_t width, uint32_t height)
 {
 #ifdef MOZ_WIDGET_GONK
-  OverlayImage::Data imgData;
-
-  imgData.mOverlayId = mOverlayImage->GetOverlayId();
-  imgData.mSize = IntSize(width, height);
-  mOverlayImage->SetData(imgData);
+  if (mOverlayImage->GetSidebandStream().IsValid()) {
+    OverlayImage::SidebandStreamData imgData;
+    imgData.mStream = mOverlayImage->GetSidebandStream();
+    imgData.mSize = IntSize(width, height);
+    mOverlayImage->SetData(imgData);
+  } else {
+    OverlayImage::Data imgData;
+    imgData.mOverlayId = mOverlayImage->GetOverlayId();
+    imgData.mSize = IntSize(width, height);
+    mOverlayImage->SetData(imgData);
+  }
 #endif
 
   SourceMediaStream* srcStream = GetInputStream()->AsSourceStream();
   StreamBuffer::Track* track = srcStream->FindTrack(TRACK_VIDEO_PRIMARY);
 
   if (!track || !track->GetSegment()) {
     return;
   }
@@ -1147,16 +1156,53 @@ DOMHwMediaStream::SetImageSize(uint32_t 
   RefPtr<Image> image = static_cast<Image*>(mOverlayImage.get());
   mozilla::gfx::IntSize size = image->GetSize();
   VideoSegment segment;
 
   segment.AppendFrame(image.forget(), delta, size);
   srcStream->AppendToTrack(TRACK_VIDEO_PRIMARY, &segment);
 #endif
 }
+
+void
+DOMHwMediaStream::SetOverlayImage(OverlayImage* aImage)
+{
+  if (!aImage) {
+    return;
+  }
+#ifdef MOZ_WIDGET_GONK
+  mOverlayImage = aImage;
+#endif
+
+  SourceMediaStream* srcStream = GetInputStream()->AsSourceStream();
+  StreamBuffer::Track* track = srcStream->FindTrack(TRACK_VIDEO_PRIMARY);
+
+  if (!track || !track->GetSegment()) {
+    return;
+  }
+
+#ifdef MOZ_WIDGET_GONK
+  // Clear the old segment.
+  // Changing the existing content of segment is a Very BAD thing, and this way will
+  // confuse consumers of MediaStreams.
+  // It is only acceptable for DOMHwMediaStream
+  // because DOMHwMediaStream doesn't have consumers of TV streams currently.
+  track->GetSegment()->Clear();
+
+  // Change the image size.
+  const StreamTime delta = STREAM_TIME_MAX;
+  RefPtr<Image> image = static_cast<Image*>(mOverlayImage.get());
+  mozilla::gfx::IntSize size = image->GetSize();
+  VideoSegment segment;
+
+  segment.AppendFrame(image.forget(), delta, size);
+  srcStream->AppendToTrack(TRACK_VIDEO_PRIMARY, &segment);
+#endif
+}
+
 void
 DOMHwMediaStream::SetOverlayId(int32_t aOverlayId)
 {
 #ifdef MOZ_WIDGET_GONK
   OverlayImage::Data imgData;
 
   imgData.mOverlayId = aOverlayId;
   imgData.mSize = mOverlayImage->GetSize();
--- a/dom/media/DOMMediaStream.h
+++ b/dom/media/DOMMediaStream.h
@@ -693,42 +693,40 @@ private:
   // If this object wraps a stream owned by an AudioNode, we need to ensure that
   // the node isn't cycle-collected too early.
   RefPtr<AudioNode> mStreamNode;
 };
 
 class DOMHwMediaStream : public DOMLocalMediaStream
 {
   typedef mozilla::gfx::IntSize IntSize;
-  typedef layers::ImageContainer ImageContainer;
+  typedef layers::OverlayImage OverlayImage;
 #ifdef MOZ_WIDGET_GONK
-  typedef layers::OverlayImage OverlayImage;
   typedef layers::OverlayImage::Data Data;
 #endif
 
 public:
   DOMHwMediaStream();
 
-  static already_AddRefed<DOMHwMediaStream> CreateHwStream(nsIDOMWindow* aWindow);
+  static already_AddRefed<DOMHwMediaStream> CreateHwStream(nsIDOMWindow* aWindow, OverlayImage* aImage = nullptr);
   virtual DOMHwMediaStream* AsDOMHwMediaStream() override { return this; }
   int32_t RequestOverlayId();
   void SetOverlayId(int32_t aOverlayId);
   void SetImageSize(uint32_t width, uint32_t height);
+  void SetOverlayImage(OverlayImage* aImage);
 
 protected:
   ~DOMHwMediaStream();
 
 private:
-  void Init(MediaStream* aStream);
+  void Init(MediaStream* aStream, OverlayImage* aImage);
 
 #ifdef MOZ_WIDGET_GONK
-  RefPtr<ImageContainer> mImageContainer;
   const int DEFAULT_IMAGE_ID = 0x01;
   const int DEFAULT_IMAGE_WIDTH = 400;
   const int DEFAULT_IMAGE_HEIGHT = 300;
   RefPtr<OverlayImage> mOverlayImage;
-  Data mImageData;
 #endif
 };
 
 } // namespace mozilla
 
 #endif /* NSDOMMEDIASTREAM_H_ */
--- a/dom/media/platforms/android/AndroidDecoderModule.cpp
+++ b/dom/media/platforms/android/AndroidDecoderModule.cpp
@@ -244,31 +244,27 @@ public:
     return NS_OK;
   }
 };
 
 bool
 AndroidDecoderModule::SupportsMimeType(const nsACString& aMimeType) const
 {
   if (!AndroidBridge::Bridge() ||
-      (AndroidBridge::Bridge()->GetAPIVersion() < 16)) {
+      AndroidBridge::Bridge()->GetAPIVersion() < 16) {
     return false;
   }
 
   if (aMimeType.EqualsLiteral("video/mp4") ||
       aMimeType.EqualsLiteral("video/avc")) {
     return true;
   }
 
-  MediaCodec::LocalRef ref = mozilla::CreateDecoder(aMimeType);
-  if (!ref) {
-    return false;
-  }
-  ref->Release();
-  return true;
+  return widget::HardwareCodecCapabilityUtils::FindDecoderCodecInfoForMimeType(
+      nsCString(TranslateMimeType(aMimeType)));
 }
 
 already_AddRefed<MediaDataDecoder>
 AndroidDecoderModule::CreateVideoDecoder(
     const VideoInfo& aConfig, layers::LayersBackend aLayersBackend,
     layers::ImageContainer* aImageContainer, FlushableTaskQueue* aVideoTaskQueue,
     MediaDataDecoderCallback* aCallback)
 {
--- a/dom/plugins/ipc/PluginInstanceChild.cpp
+++ b/dom/plugins/ipc/PluginInstanceChild.cpp
@@ -3866,20 +3866,19 @@ PluginInstanceChild::ReadbackDifferenceR
 
     // Read back previous content
     RefPtr<DrawTarget> dt = CreateDrawTargetForSurface(mCurrentSurface);
     RefPtr<SourceSurface> source =
         gfxPlatform::GetSourceSurfaceForSurface(dt, mBackSurface);
     // Subtract from mSurfaceDifferenceRect area which is overlapping with rect
     nsIntRegion result;
     result.Sub(mSurfaceDifferenceRect, nsIntRegion(rect));
-    nsIntRegionRectIterator iter(result);
-    const nsIntRect* r;
-    while ((r = iter.Next()) != nullptr) {
-        dt->CopySurface(source, *r, r->TopLeft());
+    for (auto iter = result.RectIter(); !iter.Done(); iter.Next()) {
+        const nsIntRect& r = iter.Get();
+        dt->CopySurface(source, r, r.TopLeft());
     }
 
     return true;
 #else
     return false;
 #endif
 }
 
--- a/dom/promise/Promise.cpp
+++ b/dom/promise/Promise.cpp
@@ -926,20 +926,20 @@ Promise::NewPromiseCapability(JSContext*
 
   // Step 5 doesn't need to be done, since we're not actually storing a
   // PromiseCapability in the executor; see the comments in
   // GetCapabilitiesExecutor above.
 
   // Step 6 and step 7.
   JS::Rooted<JS::Value> getCapabilities(aCx,
                                         JS::ObjectValue(*getCapabilitiesObj));
-  JS::Rooted<JS::Value> promiseVal(aCx);
+  JS::Rooted<JSObject*> promiseObj(aCx);
   if (!JS::Construct(aCx, aConstructor,
                      JS::HandleValueArray(getCapabilities),
-                     &promiseVal)) {
+                     &promiseObj)) {
     aRv.NoteJSContextException();
     return;
   }
 
   // Step 8 plus copying over the value to the PromiseCapability.
   JS::Rooted<JS::Value> v(aCx);
   v = js::GetFunctionNativeReserved(getCapabilitiesObj,
                                     GET_CAPABILITIES_EXECUTOR_RESOLVE_SLOT);
@@ -954,17 +954,17 @@ Promise::NewPromiseCapability(JSContext*
                                     GET_CAPABILITIES_EXECUTOR_REJECT_SLOT);
   if (!v.isObject() || !JS::IsCallable(&v.toObject())) {
     aRv.ThrowTypeError<MSG_PROMISE_REJECT_FUNCTION_NOT_CALLABLE>();
     return;
   }
   aCapability.mReject = v;
 
   // Step 10.
-  aCapability.mPromise = promiseVal;
+  aCapability.mPromise.setObject(*promiseObj);
 
   // Step 11 doesn't need anything, since the PromiseCapability was passed in.
 }
 
 /* static */ void
 Promise::Resolve(const GlobalObject& aGlobal, JS::Handle<JS::Value> aThisv,
                  JS::Handle<JS::Value> aValue,
                  JS::MutableHandle<JS::Value> aRetval, ErrorResult& aRv)
--- a/dom/svg/SVGContentUtils.cpp
+++ b/dom/svg/SVGContentUtils.cpp
@@ -183,29 +183,31 @@ SVGContentUtils::GetStrokeOptions(AutoSt
   }
 
   if (!styleContext) {
     return;
   }
 
   const nsStyleSVG* styleSVG = styleContext->StyleSVG();
 
+  bool checkedDashAndStrokeIsDashed = false;
   if (aFlags != eIgnoreStrokeDashing) {
     DashState dashState =
       GetStrokeDashData(aStrokeOptions, aElement, styleSVG, aContextPaint);
 
     if (dashState == eNoStroke) {
       // Hopefully this will shortcircuit any stroke operations:
       aStrokeOptions->mLineWidth = 0;
       return;
     }
     if (dashState == eContinuousStroke && aStrokeOptions->mDashPattern) {
       // Prevent our caller from wasting time looking at a pattern without gaps:
       aStrokeOptions->DiscardDashPattern();
     }
+    checkedDashAndStrokeIsDashed = (dashState == eDashedStroke);
   }
 
   aStrokeOptions->mLineWidth =
     GetStrokeWidth(aElement, styleContext, aContextPaint);
 
   aStrokeOptions->mMiterLimit = Float(styleSVG->mStrokeMiterlimit);
 
   switch (styleSVG->mStrokeLinejoin) {
@@ -215,20 +217,22 @@ SVGContentUtils::GetStrokeOptions(AutoSt
   case NS_STYLE_STROKE_LINEJOIN_ROUND:
     aStrokeOptions->mLineJoin = JoinStyle::ROUND;
     break;
   case NS_STYLE_STROKE_LINEJOIN_BEVEL:
     aStrokeOptions->mLineJoin = JoinStyle::BEVEL;
     break;
   }
 
-  if (ShapeTypeHasNoCorners(aElement)) {
+  if (ShapeTypeHasNoCorners(aElement) && !checkedDashAndStrokeIsDashed) {
+    // Note: if aFlags == eIgnoreStrokeDashing then we may be returning the
+    // wrong linecap value here, since the actual linecap used on render in this
+    // case depends on whether the stroke is dashed or not.
     aStrokeOptions->mLineCap = CapStyle::BUTT;
-  }
-  else {
+  } else {
     switch (styleSVG->mStrokeLinecap) {
       case NS_STYLE_STROKE_LINECAP_BUTT:
         aStrokeOptions->mLineCap = CapStyle::BUTT;
         break;
       case NS_STYLE_STROKE_LINECAP_ROUND:
         aStrokeOptions->mLineCap = CapStyle::ROUND;
         break;
       case NS_STYLE_STROKE_LINECAP_SQUARE:
--- a/dom/svg/SVGContentUtils.h
+++ b/dom/svg/SVGContentUtils.h
@@ -158,16 +158,22 @@ public:
     // Most dasharrays will fit in this and save us allocating
     Float mSmallArray[16];
   };
 
   enum StrokeOptionFlags {
     eAllStrokeOptions,
     eIgnoreStrokeDashing
   };
+  /**
+   * Note: the linecap style returned in aStrokeOptions is not valid when
+   * ShapeTypeHasNoCorners(aElement) == true && aFlags == eIgnoreStrokeDashing,
+   * since when aElement has no corners the rendered linecap style depends on
+   * whether or not the stroke is dashed.
+   */
   static void GetStrokeOptions(AutoStrokeOptions* aStrokeOptions,
                                nsSVGElement* aElement,
                                nsStyleContext* aStyleContext,
                                gfxTextContextPaint *aContextPaint,
                                StrokeOptionFlags aFlags = eAllStrokeOptions);
 
   /**
    * Returns the current computed value of the CSS property 'stroke-width' for
--- a/dom/tests/mochitest/fetch/test_fetch_basic_http.js
+++ b/dom/tests/mochitest/fetch/test_fetch_basic_http.js
@@ -164,17 +164,17 @@ function testFormDataSend() {
         if (entry.headers['Content-Disposition'] != 'form-data; name="string"') {
           is(entry.headers['Content-Type'], 'text/plain');
         }
 
         is(entry.body, 'hey');
       }
 
       is(response[1].headers['Content-Disposition'],
-          'form-data; name="empty"; filename="blob"');
+          'form-data; name="empty"; filename=""');
 
       is(response[2].headers['Content-Disposition'],
           'form-data; name="explicit"; filename="explicit-file-name"');
 
       is(response[3].headers['Content-Disposition'],
           'form-data; name="explicit-empty"; filename=""');
 
       is(response[4].headers['Content-Disposition'],
--- a/dom/tests/mochitest/fetch/test_request.js
+++ b/dom/tests/mochitest/fetch/test_request.js
@@ -412,28 +412,28 @@ function testFormDataBodyExtraction() {
   f1.append("blob", new Blob([text]));
   var r2 = new Request("", { method: 'post', body: f1 });
   var p2 = r2.formData().then(function(fd) {
     ok(fd.has("key"), "Has entry 'key'.");
     ok(fd.has("foo"), "Has entry 'foo'.");
     ok(fd.has("blob"), "Has entry 'blob'.");
     var entries = fd.getAll("blob");
     is(entries.length, 1, "getAll returns all items.");
-    is(entries[0].name, "blob", "Filename should be blob.");
+    ok(entries[0] instanceof Blob, "getAll returns blobs.");
   });
 
   var ws = "\r\n\r\n\r\n\r\n";
   f1.set("key", new File([ws], 'file name has spaces.txt', { type: 'new/lines' }));
   var r3 = new Request("", { method: 'post', body: f1 });
   var p3 = r3.formData().then(function(fd) {
     ok(fd.has("foo"), "Has entry 'foo'.");
     ok(fd.has("blob"), "Has entry 'blob'.");
     var entries = fd.getAll("blob");
     is(entries.length, 1, "getAll returns all items.");
-    is(entries[0].name, "blob", "Filename should be blob.");
+    ok(entries[0] instanceof Blob, "getAll returns blobs.");
 
     ok(fd.has("key"), "Has entry 'key'.");
     var f = fd.get("key");
     ok(f instanceof File, "entry should be a File.");
     is(f.name, "file name has spaces.txt", "File name should match.");
     is(f.type, "new/lines", "File type should match.");
     is(f.size, ws.length, "File size should match.");
     return readAsText(f).then(function(text) {
--- a/dom/webidl/SubtleCrypto.webidl
+++ b/dom/webidl/SubtleCrypto.webidl
@@ -14,36 +14,36 @@ typedef Uint8Array BigInteger;
 
 /***** Algorithm dictionaries *****/
 
 dictionary Algorithm {
   required DOMString name;
 };
 
 dictionary AesCbcParams : Algorithm {
-  required CryptoOperationData iv;
+  required BufferSource iv;
 };
 
 dictionary AesCtrParams : Algorithm {
-  required CryptoOperationData counter;
+  required BufferSource counter;
   [EnforceRange] required octet length;
 };
 
 dictionary AesGcmParams : Algorithm {
-  required CryptoOperationData iv;
-  CryptoOperationData additionalData;
+  required BufferSource iv;
+  BufferSource additionalData;
   [EnforceRange] octet tagLength;
 };
 
 dictionary HmacImportParams : Algorithm {
   required AlgorithmIdentifier hash;
 };
 
 dictionary Pbkdf2Params : Algorithm {
-  required CryptoOperationData salt;
+  required BufferSource salt;
   [EnforceRange] required unsigned long iterations;
   required AlgorithmIdentifier hash;
 };
 
 dictionary RsaHashedImportParams {
   required AlgorithmIdentifier hash;
 };
 
@@ -58,17 +58,21 @@ dictionary HmacKeyGenParams : Algorithm 
 
 dictionary RsaHashedKeyGenParams : Algorithm {
   [EnforceRange] required unsigned long modulusLength;
   required BigInteger publicExponent;
   required AlgorithmIdentifier hash;
 };
 
 dictionary RsaOaepParams : Algorithm {
-  CryptoOperationData label;
+  BufferSource label;
+};
+
+dictionary RsaPssParams : Algorithm {
+  [EnforceRange] required unsigned long saltLength;
 };
 
 dictionary DhKeyGenParams : Algorithm {
   required BigInteger prime;
   required BigInteger generator;
 };
 
 dictionary EcKeyGenParams : Algorithm {
@@ -99,16 +103,22 @@ dictionary DhImportKeyParams : Algorithm
 dictionary EcdsaParams : Algorithm {
   required AlgorithmIdentifier hash;
 };
 
 dictionary EcKeyImportParams : Algorithm {
   NamedCurve namedCurve;
 };
 
+dictionary HkdfParams : Algorithm {
+  required AlgorithmIdentifier hash;
+  required BufferSource salt;
+  required BufferSource info;
+};
+
 /***** JWK *****/
 
 dictionary RsaOtherPrimesInfo {
   // The following fields are defined in Section 6.3.2.7 of JSON Web Algorithms
   required DOMString r;
   required DOMString d;
   required DOMString t;
 };
@@ -150,40 +160,39 @@ interface CryptoKey {
 };
 
 dictionary CryptoKeyPair {
   required CryptoKey publicKey;
   required CryptoKey privateKey;
 };
 
 typedef DOMString KeyFormat;
-typedef (ArrayBufferView or ArrayBuffer) CryptoOperationData;
 typedef (object or DOMString) AlgorithmIdentifier;
 
 interface SubtleCrypto {
   [Throws]
   Promise<any> encrypt(AlgorithmIdentifier algorithm,
                        CryptoKey key,
-                       CryptoOperationData data);
+                       BufferSource data);
   [Throws]
   Promise<any> decrypt(AlgorithmIdentifier algorithm,
                        CryptoKey key,
-                       CryptoOperationData data);
+                       BufferSource data);
   [Throws]
   Promise<any> sign(AlgorithmIdentifier algorithm,
                      CryptoKey key,
-                     CryptoOperationData data);
+                     BufferSource data);
   [Throws]
   Promise<any> verify(AlgorithmIdentifier algorithm,
                       CryptoKey key,
-                      CryptoOperationData signature,
-                      CryptoOperationData data);
+                      BufferSource signature,
+                      BufferSource data);
   [Throws]
   Promise<any> digest(AlgorithmIdentifier algorithm,
-                      CryptoOperationData data);
+                      BufferSource data);
 
   [Throws]
   Promise<any> generateKey(AlgorithmIdentifier algorithm,
                            boolean extractable,
                            sequence<KeyUsage> keyUsages );
   [Throws]
   Promise<any> deriveKey(AlgorithmIdentifier algorithm,
                          CryptoKey baseKey,
@@ -207,16 +216,16 @@ interface SubtleCrypto {
   [Throws]
   Promise<any> wrapKey(KeyFormat format,
                        CryptoKey key,
                        CryptoKey wrappingKey,
                        AlgorithmIdentifier wrapAlgorithm);
 
   [Throws]
   Promise<any> unwrapKey(KeyFormat format,
-                         CryptoOperationData wrappedKey,
+                         BufferSource wrappedKey,
                          CryptoKey unwrappingKey,
                          AlgorithmIdentifier unwrapAlgorithm,
                          AlgorithmIdentifier unwrappedKeyAlgorithm,
                          boolean extractable,
                          sequence<KeyUsage> keyUsages );
 };
 
--- a/dom/wifi/WifiCertService.cpp
+++ b/dom/wifi/WifiCertService.cpp
@@ -434,16 +434,22 @@ WifiCertService::WifiCertService()
 {
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(!gWifiCertService);
 }
 
 WifiCertService::~WifiCertService()
 {
   MOZ_ASSERT(!gWifiCertService);
+
+  nsNSSShutDownPreventionLock locker;
+  if (isAlreadyShutDown()) {
+    return;
+  }
+  shutdown(calledFromObject);
 }
 
 already_AddRefed<WifiCertService>
 WifiCertService::FactoryCreate()
 {
   if (!XRE_IsParentProcess()) {
     return nullptr;
   }
--- a/dom/workers/XMLHttpRequest.cpp
+++ b/dom/workers/XMLHttpRequest.cpp
@@ -1207,21 +1207,21 @@ EventRunnable::PreDispatch(JSContext* aC
       } else {
         bool doClone = true;
         JS::Rooted<JS::Value> transferable(aCx);
         JS::Rooted<JSObject*> obj(aCx, response.isObjectOrNull() ?
                                   response.toObjectOrNull() : nullptr);
         if (obj && JS_IsArrayBufferObject(obj)) {
           // Use cached response if the arraybuffer has been transfered.
           if (mProxy->mArrayBufferResponseWasTransferred) {
-            MOZ_ASSERT(JS_IsNeuteredArrayBufferObject(obj));
+            MOZ_ASSERT(JS_IsDetachedArrayBufferObject(obj));
             mUseCachedArrayBufferResponse = true;
             doClone = false;
           } else {
-            MOZ_ASSERT(!JS_IsNeuteredArrayBufferObject(obj));
+            MOZ_ASSERT(!JS_IsDetachedArrayBufferObject(obj));
             JS::AutoValueArray<1> argv(aCx);
             argv[0].set(response);
             obj = JS_NewArrayObject(aCx, argv);
             if (obj) {
               transferable.setObject(*obj);
               // Only cache the response when the readyState is DONE.
               if (xhr->ReadyState() == nsIXMLHttpRequest::DONE) {
                 mProxy->mArrayBufferResponseWasTransferred = true;
--- a/extensions/spellcheck/hunspell/glue/moz.build
+++ b/extensions/spellcheck/hunspell/glue/moz.build
@@ -30,8 +30,12 @@ IPDL_SOURCES = [
 
 EXPORTS.mozilla += [
      'RemoteSpellCheckEngineChild.h',
      'RemoteSpellCheckEngineParent.h',
 ]
 
 if CONFIG['GNU_CXX']:
     CXXFLAGS += ['-Wshadow']
+
+# This variable is referenced in configure.in.  Make sure to change that file
+# too if you need to change this variable.
+DEFINES['HUNSPELL_STATIC'] = True
--- a/gfx/2d/DrawTargetRecording.cpp
+++ b/gfx/2d/DrawTargetRecording.cpp
@@ -553,16 +553,40 @@ DrawTargetRecording::PushClipRect(const 
 
 void
 DrawTargetRecording::PopClip()
 {
   mRecorder->RecordEvent(RecordedPopClip(this));
   mFinalDT->PopClip();
 }
 
+void
+DrawTargetRecording::PushLayer(bool aOpaque, Float aOpacity,
+                               SourceSurface* aMask,
+                               const Matrix& aMaskTransform,
+                               const IntRect& aBounds, bool aCopyBackground)
+{
+  if (aMask) {
+    EnsureSurfaceStored(mRecorder, aMask, "PushLayer");
+  }
+
+  mRecorder->RecordEvent(RecordedPushLayer(this, aOpacity, aOpacity, aMask,
+                                           aMaskTransform, aBounds,
+                                           aCopyBackground));
+  mFinalDT->PushLayer(aOpacity, aOpacity, aMask, aMaskTransform, aBounds,
+                      aCopyBackground);
+}
+
+void
+DrawTargetRecording::PopLayer()
+{
+  mRecorder->RecordEvent(RecordedPopLayer(this));
+  mFinalDT->PopLayer();
+}
+
 already_AddRefed<SourceSurface>
 DrawTargetRecording::CreateSourceSurfaceFromData(unsigned char *aData,
                                                  const IntSize &aSize,
                                                  int32_t aStride,
                                                  SurfaceFormat aFormat) const
 {
   RefPtr<SourceSurface> surf = mFinalDT->CreateSourceSurfaceFromData(aData, aSize, aStride, aFormat);
 
--- a/gfx/2d/DrawTargetRecording.h
+++ b/gfx/2d/DrawTargetRecording.h
@@ -212,16 +212,45 @@ public:
    */
   virtual void PushClipRect(const Rect &aRect) override;
 
   /* Pop a clip from the DrawTarget. A pop without a corresponding push will
    * be ignored.
    */
   virtual void PopClip() override;
 
+  /**
+   * Push a 'layer' to the DrawTarget, a layer is a temporary surface that all
+   * drawing will be redirected to, this is used for example to support group
+   * opacity or the masking of groups. Clips must be balanced within a layer,
+   * i.e. between a matching PushLayer/PopLayer pair there must be as many
+   * PushClip(Rect) calls as there are PopClip calls.
+   *
+   * @param aOpaque Whether the layer will be opaque
+   * @param aOpacity Opacity of the layer
+   * @param aMask Mask applied to the layer
+   * @param aMaskTransform Transform applied to the layer mask
+   * @param aBounds Optional bounds in device space to which the layer is
+   *                limited in size.
+   * @param aCopyBackground Whether to copy the background into the layer, this
+   *                        is only supported when aOpaque is true.
+   */
+  virtual void PushLayer(bool aOpaque, Float aOpacity,
+                         SourceSurface* aMask,
+                         const Matrix& aMaskTransform,
+                         const IntRect& aBounds = IntRect(),
+                         bool aCopyBackground = false) override;
+
+  /**
+   * This balances a call to PushLayer and proceeds to blend the layer back
+   * onto the background. This blend will blend the temporary surface back
+   * onto the target in device space using POINT sampling and operator over.
+   */
+  virtual void PopLayer() override;
+
   /*
    * Create a SourceSurface optimized for use with this DrawTarget from
    * existing bitmap data in memory.
    *
    * The SourceSurface does not take ownership of aData, and may be freed at any time.
    */
   virtual already_AddRefed<SourceSurface> CreateSourceSurfaceFromData(unsigned char *aData,
                                                                   const IntSize &aSize,
@@ -281,16 +310,20 @@ public:
    */
   virtual void SetTransform(const Matrix &aTransform) override;
 
   /* Tries to get a native surface for a DrawTarget, this may fail if the
    * draw target cannot convert to this surface type.
    */
   virtual void *GetNativeSurface(NativeSurfaceType aType) override { return mFinalDT->GetNativeSurface(aType); }
 
+  virtual bool IsCurrentGroupOpaque() override {
+    return mFinalDT->IsCurrentGroupOpaque();
+  }
+
 private:
   Path *GetPathForPathRecording(const Path *aPath) const;
   already_AddRefed<PathRecording> EnsurePathStored(const Path *aPath);
   void EnsurePatternDependenciesStored(const Pattern &aPattern);
 
   RefPtr<DrawEventRecorderPrivate> mRecorder;
   RefPtr<DrawTarget> mFinalDT;
 };
--- a/gfx/2d/RecordedEvent.cpp
+++ b/gfx/2d/RecordedEvent.cpp
@@ -75,16 +75,18 @@ RecordedEvent::LoadEventFromStream(std::
     LOAD_EVENT_TYPE(SNAPSHOT, RecordedSnapshot);
     LOAD_EVENT_TYPE(SCALEDFONTCREATION, RecordedScaledFontCreation);
     LOAD_EVENT_TYPE(SCALEDFONTDESTRUCTION, RecordedScaledFontDestruction);
     LOAD_EVENT_TYPE(MASKSURFACE, RecordedMaskSurface);
     LOAD_EVENT_TYPE(FILTERNODESETATTRIBUTE, RecordedFilterNodeSetAttribute);
     LOAD_EVENT_TYPE(FILTERNODESETINPUT, RecordedFilterNodeSetInput);
     LOAD_EVENT_TYPE(CREATESIMILARDRAWTARGET, RecordedCreateSimilarDrawTarget);
     LOAD_EVENT_TYPE(FONTDATA, RecordedFontData);
+    LOAD_EVENT_TYPE(PUSHLAYER, RecordedPushLayer);
+    LOAD_EVENT_TYPE(POPLAYER, RecordedPopLayer);
   default:
     return nullptr;
   }
 }
 
 string
 RecordedEvent::GetEventName(EventType aType)
 {
@@ -152,16 +154,20 @@ RecordedEvent::GetEventName(EventType aT
   case FILTERNODESETATTRIBUTE:
     return "SetAttribute";
   case FILTERNODESETINPUT:
     return "SetInput";
   case CREATESIMILARDRAWTARGET:
     return "CreateSimilarDrawTarget";
   case FONTDATA:
     return "FontData";
+  case PUSHLAYER:
+    return "PushLayer";
+  case POPLAYER:
+    return "PopLayer";
   default:
     return "Unknown";
   }
 }
 
 void
 RecordedEvent::RecordPatternData(std::ostream &aStream, const PatternStorage &aPattern) const
 {
@@ -923,16 +929,78 @@ RecordedPopClip::RecordedPopClip(istream
 
 void
 RecordedPopClip::OutputSimpleEventInfo(stringstream &aStringStream) const
 {
   aStringStream << "[" << mDT << "] PopClip";
 }
 
 void
+RecordedPushLayer::PlayEvent(Translator *aTranslator) const
+{
+  SourceSurface* mask = mMask ? aTranslator->LookupSourceSurface(mMask)
+                              : nullptr;
+  aTranslator->LookupDrawTarget(mDT)->
+    PushLayer(mOpaque, mOpacity, mask, mMaskTransform, mBounds, mCopyBackground);
+}
+
+void
+RecordedPushLayer::RecordToStream(ostream &aStream) const
+{
+  RecordedDrawingEvent::RecordToStream(aStream);
+  WriteElement(aStream, mOpaque);
+  WriteElement(aStream, mOpacity);
+  WriteElement(aStream, mMask);
+  WriteElement(aStream, mMaskTransform);
+  WriteElement(aStream, mBounds);
+  WriteElement(aStream, mCopyBackground);
+}
+
+RecordedPushLayer::RecordedPushLayer(istream &aStream)
+  : RecordedDrawingEvent(PUSHLAYER, aStream)
+{
+  ReadElement(aStream, mOpaque);
+  ReadElement(aStream, mOpacity);
+  ReadElement(aStream, mMask);
+  ReadElement(aStream, mMaskTransform);
+  ReadElement(aStream, mBounds);
+  ReadElement(aStream, mCopyBackground);
+}
+
+void
+RecordedPushLayer::OutputSimpleEventInfo(stringstream &aStringStream) const
+{
+  aStringStream << "[" << mDT << "] PushPLayer (Opaque=" << mOpaque <<
+    ", Opacity=" << mOpacity << ", Mask Ref=" << mMask << ") ";
+}
+
+void
+RecordedPopLayer::PlayEvent(Translator *aTranslator) const
+{
+  aTranslator->LookupDrawTarget(mDT)->PopLayer();
+}
+
+void
+RecordedPopLayer::RecordToStream(ostream &aStream) const
+{
+  RecordedDrawingEvent::RecordToStream(aStream);
+}
+
+RecordedPopLayer::RecordedPopLayer(istream &aStream)
+  : RecordedDrawingEvent(POPLAYER, aStream)
+{
+}
+
+void
+RecordedPopLayer::OutputSimpleEventInfo(stringstream &aStringStream) const
+{
+  aStringStream << "[" << mDT << "] PopLayer";
+}
+
+void
 RecordedSetTransform::PlayEvent(Translator *aTranslator) const
 {
   aTranslator->LookupDrawTarget(mDT)->SetTransform(mTransform);
 }
 
 void
 RecordedSetTransform::RecordToStream(ostream &aStream) const
 {
--- a/gfx/2d/RecordedEvent.h
+++ b/gfx/2d/RecordedEvent.h
@@ -22,17 +22,17 @@ const uint32_t kMagicInt = 0xc001feed;
 
 // A change in major revision means a change in event binary format, causing
 // loss of backwards compatibility. Old streams will not work in a player
 // using a newer major revision. And new streams will not work in a player
 // using an older major revision.
 const uint16_t kMajorRevision = 4;
 // A change in minor revision means additions of new events. New streams will
 // not play in older players.
-const uint16_t kMinorRevision = 0;
+const uint16_t kMinorRevision = 1;
 
 struct ReferencePtr
 {
   ReferencePtr()
     : mLongPtr(0)
   {}
 
   MOZ_IMPLICIT ReferencePtr(const void* aLongPtr)
@@ -187,16 +187,18 @@ public:
     MASKSURFACE,
     FILTERNODECREATION,
     FILTERNODEDESTRUCTION,
     DRAWFILTER,
     FILTERNODESETATTRIBUTE,
     FILTERNODESETINPUT,
     CREATESIMILARDRAWTARGET,
     FONTDATA,
+    PUSHLAYER,
+    POPLAYER,
   };
   static const uint32_t kTotalEventTypes = RecordedEvent::FILTERNODESETINPUT + 1;
 
   virtual ~RecordedEvent() {}
 
   static std::string GetEventName(EventType aType);
 
   virtual void PlayEvent(Translator *aTranslator) const {}
@@ -619,16 +621,65 @@ public:
 
   virtual std::string GetName() const { return "PopClip"; }
 private:
   friend class RecordedEvent;
 
   MOZ_IMPLICIT RecordedPopClip(std::istream &aStream);
 };
 
+class RecordedPushLayer : public RecordedDrawingEvent {
+public:
+  RecordedPushLayer(DrawTarget* aDT, bool aOpaque, Float aOpacity,
+                    SourceSurface* aMask, const Matrix& aMaskTransform,
+                    const IntRect& aBounds, bool aCopyBackground)
+    : RecordedDrawingEvent(PUSHLAYER, aDT), mOpaque(aOpaque)
+    , mOpacity(aOpacity), mMask(aMask), mMaskTransform(aMaskTransform)
+    , mBounds(aBounds), mCopyBackground(aCopyBackground)
+  {
+  }
+
+  virtual void PlayEvent(Translator *aTranslator) const;
+
+  virtual void RecordToStream(std::ostream &aStream) const;
+  virtual void OutputSimpleEventInfo(std::stringstream &aStringStream) const;
+
+  virtual std::string GetName() const { return "PushLayer"; }
+private:
+  friend class RecordedEvent;
+
+  MOZ_IMPLICIT RecordedPushLayer(std::istream &aStream);
+
+  bool mOpaque;
+  Float mOpacity;
+  ReferencePtr mMask;
+  Matrix mMaskTransform;
+  IntRect mBounds;
+  bool mCopyBackground;
+};
+
+class RecordedPopLayer : public RecordedDrawingEvent {
+public:
+  MOZ_IMPLICIT RecordedPopLayer(DrawTarget* aDT)
+    : RecordedDrawingEvent(POPLAYER, aDT)
+  {
+  }
+
+  virtual void PlayEvent(Translator *aTranslator) const;
+
+  virtual void RecordToStream(std::ostream &aStream) const;
+  virtual void OutputSimpleEventInfo(std::stringstream &aStringStream) const;
+
+  virtual std::string GetName() const { return "PopLayer"; }
+private:
+  friend class RecordedEvent;
+
+  MOZ_IMPLICIT RecordedPopLayer(std::istream &aStream);
+};
+
 class RecordedSetTransform : public RecordedDrawingEvent {
 public:
   RecordedSetTransform(DrawTarget *aDT, const Matrix &aTransform)
     : RecordedDrawingEvent(SETTRANSFORM, aDT), mTransform(aTransform)
   {
   }
 
   virtual void PlayEvent(Translator *aTranslator) const;
--- a/gfx/gl/GLScreenBuffer.cpp
+++ b/gfx/gl/GLScreenBuffer.cpp
@@ -109,16 +109,22 @@ GLScreenBuffer::CreateFactory(GLContext*
                   factory = SurfaceFactory_D3D11Interop::Create(gl, caps, forwarder, flags);
                 }
 #endif
               break;
             }
             default:
               break;
         }
+
+#ifdef GL_PROVIDER_GLX
+        if (!factory && sGLXLibrary.UseSurfaceSharing()) {
+            factory = SurfaceFactory_GLXDrawable::Create(gl, caps, forwarder, flags);
+        }
+#endif
     }
 
     return factory;
 }
 
 GLScreenBuffer::GLScreenBuffer(GLContext* gl,
                                const SurfaceCaps& caps,
                                UniquePtr<SurfaceFactory> factory)
--- a/gfx/gl/GLUploadHelpers.cpp
+++ b/gfx/gl/GLUploadHelpers.cpp
@@ -496,52 +496,51 @@ UploadImageDataToTexture(GLContext* gl,
             type = LOCAL_GL_UNSIGNED_BYTE;
             // We don't have a specific luminance shader
             surfaceFormat = SurfaceFormat::A8;
             break;
         default:
             NS_ASSERTION(false, "Unhandled image surface format!");
     }
 
-    nsIntRegionRectIterator iter(paintRegion);
-    const IntRect *iterRect;
 
     // Top left point of the region's bounding rectangle.
     IntPoint topLeft = paintRegion.GetBounds().TopLeft();
 
-    while ((iterRect = iter.Next())) {
+    for (auto iter = paintRegion.RectIter(); !iter.Done(); iter.Next()) {
+        const IntRect& rect = iter.Get();
         // The inital data pointer is at the top left point of the region's
         // bounding rectangle. We need to find the offset of this rect
         // within the region and adjust the data pointer accordingly.
         unsigned char *rectData =
-            aData + DataOffset(iterRect->TopLeft() - topLeft, aStride, aFormat);
+            aData + DataOffset(rect.TopLeft() - topLeft, aStride, aFormat);
 
-        NS_ASSERTION(textureInited || (iterRect->x == 0 && iterRect->y == 0),
+        NS_ASSERTION(textureInited || (rect.x == 0 && rect.y == 0),
                      "Must be uploading to the origin when we don't have an existing texture");
 
         if (textureInited && CanUploadSubTextures(gl)) {
             TexSubImage2DHelper(gl,
                                 aTextureTarget,
                                 0,
-                                iterRect->x,
-                                iterRect->y,
-                                iterRect->width,
-                                iterRect->height,
+                                rect.x,
+                                rect.y,
+                                rect.width,
+                                rect.height,
                                 aStride,
                                 pixelSize,
                                 format,
                                 type,
                                 rectData);
         } else {
             TexImage2DHelper(gl,
                              aTextureTarget,
                              0,
                              internalFormat,
-                             iterRect->width,
-                             iterRect->height,
+                             rect.width,
+                             rect.height,
                              aStride,
                              pixelSize,
                              0,
                              format,
                              type,
                              rectData);
         }
 
--- a/gfx/ipc/GfxMessageUtils.h
+++ b/gfx/ipc/GfxMessageUtils.h
@@ -362,20 +362,20 @@ struct ParamTraits<mozilla::gfx::IntSize
 
 template<typename Region, typename Rect, typename Iter>
 struct RegionParamTraits
 {
   typedef Region paramType;
 
   static void Write(Message* msg, const paramType& param)
   {
-    Iter it(param);
-    while (const Rect* r = it.Next()) {
-      MOZ_RELEASE_ASSERT(!r->IsEmpty());
-      WriteParam(msg, *r);
+    for (auto iter = param.RectIter(); !iter.Done(); iter.Next()) {
+      const Rect& r = iter.Get();
+      MOZ_RELEASE_ASSERT(!r.IsEmpty());
+      WriteParam(msg, r);
     }
     // empty rects are sentinel values because nsRegions will never
     // contain them
     WriteParam(msg, Rect());
   }
 
   static bool Read(const Message* msg, void** iter, paramType* result)
   {
@@ -661,17 +661,17 @@ struct ParamTraits<nsRect>
             ReadParam(msg, iter, &result->y) &&
             ReadParam(msg, iter, &result->width) &&
             ReadParam(msg, iter, &result->height));
   }
 };
 
 template<>
 struct ParamTraits<nsRegion>
-  : RegionParamTraits<nsRegion, nsRect, nsRegionRectIterator>
+  : RegionParamTraits<nsRegion, nsRect, nsRegion::RectIterator>
 {};
 
 template <>
 struct ParamTraits<mozilla::layers::FrameMetrics>
 {
   typedef mozilla::layers::FrameMetrics paramType;
 
   static void Write(Message* aMsg, const paramType& aParam)
--- a/gfx/layers/Compositor.cpp
+++ b/gfx/layers/Compositor.cpp
@@ -53,22 +53,19 @@ Compositor::DrawDiagnostics(DiagnosticFl
                             const gfx::Matrix4x4& aTransform,
                             uint32_t aFlashCounter)
 {
   if (!ShouldDrawDiagnostics(aFlags)) {
     return;
   }
 
   if (aVisibleRegion.GetNumRects() > 1) {
-    nsIntRegionRectIterator screenIter(aVisibleRegion);
-
-    while (const gfx::IntRect* rect = screenIter.Next())
-    {
+    for (auto iter = aVisibleRegion.RectIter(); !iter.Done(); iter.Next()) {
       DrawDiagnostics(aFlags | DiagnosticFlags::REGION_RECT,
-                      IntRectToRect(*rect), aClipRect, aTransform,
+                      IntRectToRect(iter.Get()), aClipRect, aTransform,
                       aFlashCounter);
     }
   }
 
   DrawDiagnostics(aFlags, IntRectToRect(aVisibleRegion.GetBounds()),
                   aClipRect, aTransform, aFlashCounter);
 }
 
--- a/gfx/layers/ImageContainer.h
+++ b/gfx/layers/ImageContainer.h
@@ -852,22 +852,24 @@ public:
   };
 
   OverlayImage() : Image(nullptr, ImageFormat::OVERLAY_IMAGE) { mOverlayId = INVALID_OVERLAY; }
 
   void SetData(const Data& aData)
   {
     mOverlayId = aData.mOverlayId;
     mSize = aData.mSize;
+    mSidebandStream = GonkNativeHandle();
   }
 
   void SetData(const SidebandStreamData& aData)
   {
     mSidebandStream = aData.mStream;
     mSize = aData.mSize;
+    mOverlayId = INVALID_OVERLAY;
   }
 
   already_AddRefed<gfx::SourceSurface> GetAsSourceSurface() { return nullptr; } ;
   int32_t GetOverlayId() { return mOverlayId; }
   GonkNativeHandle& GetSidebandStream() { return mSidebandStream; }
 
   gfx::IntSize GetSize() { return mSize; }
 
--- a/gfx/layers/LayerTreeInvalidation.cpp
+++ b/gfx/layers/LayerTreeInvalidation.cpp
@@ -78,20 +78,18 @@ TransformRect(const IntRect& aRect, cons
   }
 
   return intRect;
 }
 
 static void
 AddTransformedRegion(nsIntRegion& aDest, const nsIntRegion& aSource, const Matrix4x4& aTransform)
 {
-  nsIntRegionRectIterator iter(aSource);
-  const IntRect *r;
-  while ((r = iter.Next())) {
-    aDest.Or(aDest, TransformRect(*r, aTransform));
+  for (auto iter = aSource.RectIter(); !iter.Done(); iter.Next()) {
+    aDest.Or(aDest, TransformRect(iter.Get(), aTransform));
   }
   aDest.SimplifyOutward(20);
 }
 
 static void
 AddRegion(nsIntRegion& aDest, const nsIntRegion& aSource)
 {
   aDest.Or(aDest, aSource);
--- a/gfx/layers/Layers.cpp
+++ b/gfx/layers/Layers.cpp
@@ -1992,19 +1992,18 @@ DumpRect(layerscope::LayersPacket::Layer
   aLayerRect->set_w(aRect.width);
   aLayerRect->set_h(aRect.height);
 }
 
 // The static helper function sets the nsIntRegion into the packet
 static void
 DumpRegion(layerscope::LayersPacket::Layer::Region* aLayerRegion, const nsIntRegion& aRegion)
 {
-  nsIntRegionRectIterator it(aRegion);
-  while (const IntRect* sr = it.Next()) {
-    DumpRect(aLayerRegion->add_r(), *sr);
+  for (auto iter = aRegion.RectIter(); !iter.Done(); iter.Next()) {
+    DumpRect(aLayerRegion->add_r(), iter.Get());
   }
 }
 
 void
 Layer::DumpPacket(layerscope::LayersPacket* aPacket, const void* aParent)
 {
   // Add a new layer (UnknownLayer)
   using namespace layerscope;
--- a/gfx/layers/LayersLogging.cpp
+++ b/gfx/layers/LayersLogging.cpp
@@ -90,37 +90,35 @@ AppendToString(std::stringstream& aStrea
 }
 
 void
 AppendToString(std::stringstream& aStream, const nsRegion& r,
                const char* pfx, const char* sfx)
 {
   aStream << pfx;
 
-  nsRegionRectIterator it(r);
   aStream << "< ";
-  while (const nsRect* sr = it.Next()) {
-    AppendToString(aStream, *sr);
+  for (auto iter = r.RectIter(); !iter.Done(); iter.Next()) {
+    AppendToString(aStream, iter.Get());
     aStream << "; ";
   }
   aStream << ">";
 
   aStream << sfx;
 }
 
 void
 AppendToString(std::stringstream& aStream, const nsIntRegion& r,
                const char* pfx, const char* sfx)
 {
   aStream << pfx;
 
-  nsIntRegionRectIterator it(r);
   aStream << "< ";
-  while (const IntRect* sr = it.Next()) {
-    AppendToString(aStream, *sr);
+  for (auto iter = r.RectIter(); !iter.Done(); iter.Next()) {
+    AppendToString(aStream, iter.Get());
     aStream << "; ";
   }
   aStream << ">";
 
   aStream << sfx;
 }
 
 void
--- a/gfx/layers/LayersLogging.h
+++ b/gfx/layers/LayersLogging.h
@@ -93,24 +93,21 @@ void
 AppendToString(std::stringstream& aStream, const nsIntRegion& r,
                const char* pfx="", const char* sfx="");
 
 template <typename units>
 void
 AppendToString(std::stringstream& aStream, const mozilla::gfx::IntRegionTyped<units>& r,
                const char* pfx="", const char* sfx="")
 {
-  typedef mozilla::gfx::IntRegionTyped<units> RegionType;
-
   aStream << pfx;
 
-  typename RegionType::RectIterator it(r);
   aStream << "< ";
-  while (const typename RegionType::RectType* sr = it.Next()) {
-    AppendToString(aStream, *sr);
+  for (auto iter = r.RectIter(); !iter.Done(); iter.Next()) {
+    AppendToString(aStream, iter.Get());
     aStream << "; ";
   }
   aStream << ">";
 
   aStream << sfx;
 }
 
 void
--- a/gfx/layers/RotatedBuffer.cpp
+++ b/gfx/layers/RotatedBuffer.cpp
@@ -754,30 +754,28 @@ RotatedContentBuffer::BorrowDrawTargetFo
 
   if (aPaintState.mMode == SurfaceMode::SURFACE_COMPONENT_ALPHA) {
     if (!mDTBuffer || !mDTBufferOnWhite) {
       // This can happen in release builds if allocating one of the two buffers
       // failed. This in turn can happen if unreasonably large textures are
       // requested.
       return nullptr;
     }
-    nsIntRegionRectIterator iter(*drawPtr);
-    const IntRect *iterRect;
-    while ((iterRect = iter.Next())) {
-      mDTBuffer->FillRect(Rect(iterRect->x, iterRect->y, iterRect->width, iterRect->height),
+    for (auto iter = drawPtr->RectIter(); !iter.Done(); iter.Next()) {
+      const IntRect& rect = iter.Get();
+      mDTBuffer->FillRect(Rect(rect.x, rect.y, rect.width, rect.height),
                           ColorPattern(Color(0.0, 0.0, 0.0, 1.0)));
-      mDTBufferOnWhite->FillRect(Rect(iterRect->x, iterRect->y, iterRect->width, iterRect->height),
+      mDTBufferOnWhite->FillRect(Rect(rect.x, rect.y, rect.width, rect.height),
                                  ColorPattern(Color(1.0, 1.0, 1.0, 1.0)));
     }
   } else if (aPaintState.mContentType == gfxContentType::COLOR_ALPHA && HaveBuffer()) {
     // HaveBuffer() => we have an existing buffer that we must clear
-    nsIntRegionRectIterator iter(*drawPtr);
-    const IntRect *iterRect;
-    while ((iterRect = iter.Next())) {
-      result->ClearRect(Rect(iterRect->x, iterRect->y, iterRect->width, iterRect->height));
+    for (auto iter = drawPtr->RectIter(); !iter.Done(); iter.Next()) {
+      const IntRect& rect = iter.Get();
+      result->ClearRect(Rect(rect.x, rect.y, rect.width, rect.height));
     }
   }
 
   return result;
 }
 
 already_AddRefed<SourceSurface>