Merge mozilla-central to fx-team
authorCarsten "Tomcat" Book <cbook@mozilla.com>
Tue, 20 Sep 2016 12:05:02 +0200
changeset 314472 14705f779a46da3dbbc41af098209911479cff01
parent 314410 8a1efb73e7429df1dd35a4588c038b079bdf6018 (current diff)
parent 314471 62f79d676e0e11b3ad59a5425b3ebb3ec5bbefb5 (diff)
child 314473 0147246bc5f671e860c4e64f8e76d1156fbb449c
push id20574
push usercbook@mozilla.com
push dateTue, 20 Sep 2016 10:05:16 +0000
treeherderfx-team@14705f779a46 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone52.0a1
Merge mozilla-central to fx-team
--- a/accessible/ipc/DocAccessibleChildBase.cpp
+++ b/accessible/ipc/DocAccessibleChildBase.cpp
@@ -77,23 +77,61 @@ DocAccessibleChildBase::SerializeTree(Ac
   aTree.AppendElement(AccessibleData(id, role, childCount, interfaces));
 #endif
 
   for (uint32_t i = 0; i < childCount; i++) {
     SerializeTree(aRoot->GetChildAt(i), aTree);
   }
 }
 
+#if defined(XP_WIN)
+/* static */ void
+DocAccessibleChildBase::SetMsaaIds(Accessible* aRoot,
+                                   uint32_t& aMsaaIdIndex,
+                                   const nsTArray<MsaaMapping>& aNewMsaaIds)
+{
+  const MsaaMapping& mapping = aNewMsaaIds[aMsaaIdIndex];
+#if defined(DEBUG)
+  uint64_t id = reinterpret_cast<uint64_t>(aRoot->UniqueID());
+  MOZ_ASSERT(mapping.ID() == id);
+#endif // defined(DEBUG)
+  static_cast<AccessibleWrap*>(aRoot)->SetID(mapping.MsaaID());
+  ++aMsaaIdIndex;
+  if (aRoot->IsOuterDoc()) {
+    // This needs to match the tree traversal in SerializeTree
+    return;
+  }
+  for (uint32_t i = 0, n = aRoot->ChildCount(); i < n; ++i) {
+    SetMsaaIds(aRoot->GetChildAt(i), aMsaaIdIndex, aNewMsaaIds);
+  }
+}
+#endif // defined(XP_WIN)
+
 void
 DocAccessibleChildBase::ShowEvent(AccShowEvent* aShowEvent)
 {
   Accessible* parent = aShowEvent->Parent();
   uint64_t parentID = parent->IsDoc() ? 0 : reinterpret_cast<uint64_t>(parent->UniqueID());
   uint32_t idxInParent = aShowEvent->InsertionIndex();
   nsTArray<AccessibleData> shownTree;
   ShowEventData data(parentID, idxInParent, shownTree);
   SerializeTree(aShowEvent->GetAccessible(), data.NewTree());
+#if defined(XP_WIN)
+  nsTArray<MsaaMapping> newMsaaIds;
+  SendShowEventInfo(data, &newMsaaIds);
+  // newMsaaIds could be empty if something went wrong in SendShowEvent()
+  if (!newMsaaIds.IsEmpty()) {
+    uint32_t index = 0;
+    SetMsaaIds(aShowEvent->GetAccessible(), index, newMsaaIds);
+  }
+  // NB: On Windows, SendShowEvent attaches the subtree and generates new IDs,
+  //     but does *NOT* fire the native event. We need to do that after
+  //     we've called SetMsaaIds.
+  SendEvent(reinterpret_cast<uint64_t>(aShowEvent->GetAccessible()->UniqueID()),
+            nsIAccessibleEvent::EVENT_SHOW);
+#else
   SendShowEvent(data, aShowEvent->IsFromUserInput());
+#endif // defined(XP_WIN)
 }
 
 } // namespace a11y
 } // namespace mozilla
 
--- a/accessible/ipc/DocAccessibleChildBase.h
+++ b/accessible/ipc/DocAccessibleChildBase.h
@@ -55,16 +55,20 @@ public:
 
     mDoc->SetIPCDoc(nullptr);
     mDoc = nullptr;
   }
 
 protected:
   static uint32_t InterfacesFor(Accessible* aAcc);
   static void SerializeTree(Accessible* aRoot, nsTArray<AccessibleData>& aTree);
+#if defined(XP_WIN)
+  static void SetMsaaIds(Accessible* aRoot, uint32_t& aMsaaIdIndex,
+                         const nsTArray<MsaaMapping>& aNewMsaaIds);
+#endif
 
   DocAccessible*  mDoc;
 };
 
 } // namespace a11y
 } // namespace mozilla
 
 #endif // mozilla_a11y_DocAccessibleChildBase_h
--- a/accessible/ipc/DocAccessibleParent.cpp
+++ b/accessible/ipc/DocAccessibleParent.cpp
@@ -11,18 +11,23 @@
 #include "xpcAccEvents.h"
 #include "nsAccUtils.h"
 #include "nsCoreUtils.h"
 
 namespace mozilla {
 namespace a11y {
 
 bool
+#if defined(XP_WIN)
+DocAccessibleParent::RecvShowEventInfo(const ShowEventData& aData,
+                                       nsTArray<MsaaMapping>* aNewMsaaIds)
+#else
 DocAccessibleParent::RecvShowEvent(const ShowEventData& aData,
                                    const bool& aFromUser)
+#endif // defined(XP_WIN)
 {
   if (mShutdown)
     return true;
 
   MOZ_DIAGNOSTIC_ASSERT(CheckDocTree());
 
   if (aData.NewTree().IsEmpty()) {
     NS_ERROR("no children being added");
@@ -39,17 +44,23 @@ DocAccessibleParent::RecvShowEvent(const
   }
 
   uint32_t newChildIdx = aData.Idx();
   if (newChildIdx > parent->ChildrenCount()) {
     NS_ERROR("invalid index to add child at");
     return true;
   }
 
+#if defined(XP_WIN)
+  aNewMsaaIds->SetCapacity(aData.NewTree().Length());
+  uint32_t consumed = AddSubtree(parent, aData.NewTree(), 0, newChildIdx,
+                                 aNewMsaaIds);
+#else
   uint32_t consumed = AddSubtree(parent, aData.NewTree(), 0, newChildIdx);
+#endif
   MOZ_ASSERT(consumed == aData.NewTree().Length());
 
   // XXX This shouldn't happen, but if we failed to add children then the below
   // is pointless and can crash.
   if (!consumed) {
     return true;
   }
 
@@ -57,38 +68,46 @@ DocAccessibleParent::RecvShowEvent(const
   for (uint32_t i = 0; i < consumed; i++) {
     uint64_t id = aData.NewTree()[i].ID();
     MOZ_ASSERT(mAccessibles.GetEntry(id));
   }
 #endif
 
   MOZ_DIAGNOSTIC_ASSERT(CheckDocTree());
 
+  // NB: On Windows we dispatch the native event via a subsequent call to
+  // RecvEvent().
+#if !defined(XP_WIN)
   ProxyAccessible* target = parent->ChildAt(newChildIdx);
   ProxyShowHideEvent(target, parent, true, aFromUser);
 
   if (!nsCoreUtils::AccEventObserversExist()) {
     return true;
   }
 
   uint32_t type = nsIAccessibleEvent::EVENT_SHOW;
   xpcAccessibleGeneric* xpcAcc = GetXPCAccessible(target);
   xpcAccessibleDocument* doc = GetAccService()->GetXPCDocument(this);
   nsIDOMNode* node = nullptr;
   RefPtr<xpcAccEvent> event = new xpcAccEvent(type, xpcAcc, doc, node,
                                               aFromUser);
   nsCoreUtils::DispatchAccEvent(Move(event));
+#endif
 
   return true;
 }
 
 uint32_t
 DocAccessibleParent::AddSubtree(ProxyAccessible* aParent,
                                 const nsTArray<a11y::AccessibleData>& aNewTree,
-                                uint32_t aIdx, uint32_t aIdxInParent)
+                                uint32_t aIdx, uint32_t aIdxInParent
+#if defined(XP_WIN)
+                                , nsTArray<MsaaMapping>* aNewMsaaIds
+#endif
+                                )
 {
   if (aNewTree.Length() <= aIdx) {
     NS_ERROR("bad index in serialized tree!");
     return 0;
   }
 
   const AccessibleData& newChild = aNewTree[aIdx];
   if (newChild.Role() > roles::LAST_ROLE) {
@@ -119,20 +138,32 @@ DocAccessibleParent::AddSubtree(ProxyAcc
     new ProxyAccessible(newChild.ID(), aParent, this, role,
                         newChild.Interfaces());
 #endif
 
   aParent->AddChildAt(aIdxInParent, newProxy);
   mAccessibles.PutEntry(newChild.ID())->mProxy = newProxy;
   ProxyCreated(newProxy, newChild.Interfaces());
 
+#if defined(XP_WIN)
+  Accessible* idForAcc = WrapperFor(newProxy);
+  MOZ_ASSERT(idForAcc);
+  uint32_t newMsaaId = AccessibleWrap::GetChildIDFor(idForAcc);
+  MOZ_ASSERT(newMsaaId);
+  aNewMsaaIds->AppendElement(MsaaMapping(newChild.ID(), newMsaaId));
+#endif // defined(XP_WIN)
+
   uint32_t accessibles = 1;
   uint32_t kids = newChild.ChildrenCount();
   for (uint32_t i = 0; i < kids; i++) {
-    uint32_t consumed = AddSubtree(newProxy, aNewTree, aIdx + accessibles, i);
+    uint32_t consumed = AddSubtree(newProxy, aNewTree, aIdx + accessibles, i
+#if defined(XP_WIN)
+                                   , aNewMsaaIds
+#endif
+                                  );
     if (!consumed)
       return 0;
 
     accessibles += consumed;
   }
 
   MOZ_ASSERT(newProxy->ChildrenCount() == kids);
 
@@ -471,26 +502,29 @@ DocAccessibleParent::GetXPCAccessible(Pr
 /**
  * @param aCOMProxy COM Proxy to the document in the content process.
  * @param aParentCOMProxy COM Proxy to the OuterDocAccessible that is
  *        the parent of the document. The content process will use this
  *        proxy when traversing up across the content/chrome boundary.
  */
 bool
 DocAccessibleParent::RecvCOMProxy(const IAccessibleHolder& aCOMProxy,
-                                  IAccessibleHolder* aParentCOMProxy)
+                                  IAccessibleHolder* aParentCOMProxy,
+                                  uint32_t* aMsaaID)
 {
   RefPtr<IAccessible> ptr(aCOMProxy.Get());
   SetCOMInterface(ptr);
 
   Accessible* outerDoc = OuterDocOfRemoteBrowser();
   IAccessible* rawNative = nullptr;
   if (outerDoc) {
     outerDoc->GetNativeInterface((void**) &rawNative);
   }
 
   aParentCOMProxy->Set(IAccessibleHolder::COMPtrType(rawNative));
+  Accessible* wrapper = WrapperFor(this);
+  *aMsaaID = AccessibleWrap::GetChildIDFor(wrapper);
   return true;
 }
 #endif // defined(XP_WIN)
 
 } // a11y
 } // mozilla
--- a/accessible/ipc/DocAccessibleParent.h
+++ b/accessible/ipc/DocAccessibleParent.h
@@ -45,18 +45,23 @@ public:
 
   /*
    * Called when a message from a document in a child process notifies the main
    * process it is firing an event.
    */
   virtual bool RecvEvent(const uint64_t& aID, const uint32_t& aType)
     override;
 
+#if defined(XP_WIN)
+  virtual bool RecvShowEventInfo(const ShowEventData& aData,
+                                 nsTArray<MsaaMapping>* aNewMsaaIds) override;
+#else
   virtual bool RecvShowEvent(const ShowEventData& aData, const bool& aFromUser)
     override;
+#endif // defined(XP_WIN)
   virtual bool RecvHideEvent(const uint64_t& aRootID, const bool& aFromUser)
     override;
   virtual bool RecvStateChangeEvent(const uint64_t& aID,
                                     const uint64_t& aState,
                                     const bool& aEnabled) override final;
 
   virtual bool RecvCaretMoveEvent(const uint64_t& aID, const int32_t& aOffset)
     override final;
@@ -139,17 +144,18 @@ public:
     { return const_cast<DocAccessibleParent*>(this)->GetAccessible(aID); }
 
   size_t ChildDocCount() const { return mChildDocs.Length(); }
   const DocAccessibleParent* ChildDocAt(size_t aIdx) const
     { return mChildDocs[aIdx]; }
 
 #if defined(XP_WIN)
   virtual bool RecvCOMProxy(const IAccessibleHolder& aCOMProxy,
-                            IAccessibleHolder* aParentCOMProxy) override;
+                            IAccessibleHolder* aParentCOMProxy,
+                            uint32_t* aMsaaID) override;
 #endif
 
 private:
 
   class ProxyEntry : public PLDHashEntryHdr
   {
   public:
     explicit ProxyEntry(const void*) : mProxy(nullptr) {}
@@ -169,17 +175,21 @@ private:
 
     enum { ALLOW_MEMMOVE = true };
 
     ProxyAccessible* mProxy;
   };
 
   uint32_t AddSubtree(ProxyAccessible* aParent,
                       const nsTArray<AccessibleData>& aNewTree, uint32_t aIdx,
-                      uint32_t aIdxInParent);
+                      uint32_t aIdxInParent
+#if defined(XP_WIN)
+                      , nsTArray<MsaaMapping>* aNewMsaaIds
+#endif // defined(XP_WIN)
+                      );
   MOZ_MUST_USE 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
--- a/accessible/ipc/win/DocAccessibleChild.cpp
+++ b/accessible/ipc/win/DocAccessibleChild.cpp
@@ -29,15 +29,17 @@ DocAccessibleChild::~DocAccessibleChild(
 {
   MOZ_COUNT_DTOR_INHERITED(DocAccessibleChild, DocAccessibleChildBase);
 }
 
 void
 DocAccessibleChild::SendCOMProxy(const IAccessibleHolder& aProxy)
 {
   IAccessibleHolder parentProxy;
-  PDocAccessibleChild::SendCOMProxy(aProxy, &parentProxy);
+  uint32_t msaaID = AccessibleWrap::kNoID;
+  PDocAccessibleChild::SendCOMProxy(aProxy, &parentProxy, &msaaID);
   mParentProxy.reset(parentProxy.Release());
+  mDoc->SetID(msaaID);
 }
 
 } // namespace a11y
 } // namespace mozilla
 
--- a/accessible/ipc/win/PDocAccessible.ipdl
+++ b/accessible/ipc/win/PDocAccessible.ipdl
@@ -22,16 +22,22 @@ struct AccessibleData
 
 struct ShowEventData
 {
   uint64_t ID;
   uint32_t Idx;
   AccessibleData[] NewTree;
 };
 
+struct MsaaMapping
+{
+  uint64_t ID;
+  uint32_t MsaaID;
+};
+
 struct Attribute
 {
   nsCString Name;
   nsString Value;
 };
 
 sync protocol PDocAccessible
 {
@@ -40,17 +46,17 @@ sync protocol PDocAccessible
 parent:
   async Shutdown();
 
   /*
    * Notify the parent process the document in the child process is firing an
    * event.
    */
   async Event(uint64_t aID, uint32_t type);
-  async ShowEvent(ShowEventData data, bool aFromuser);
+  sync ShowEventInfo(ShowEventData data) returns (MsaaMapping[] aNewMsaaIds);
   async HideEvent(uint64_t aRootID, bool aFromUser);
   async StateChangeEvent(uint64_t aID, uint64_t aState, bool aEnabled);
   async CaretMoveEvent(uint64_t aID, int32_t aOffset);
   async TextChangeEvent(uint64_t aID, nsString aStr, int32_t aStart, uint32_t aLen,
                         bool aIsInsert, bool aFromUser);
   async SelectionEvent(uint64_t aID, uint64_t aWidgetID, uint32_t aType);
   async RoleChangedEvent(uint32_t aRole);
 
@@ -58,16 +64,16 @@ parent:
    * Tell the parent document to bind the existing document as a new child
    * document.
    */
   async BindChildDoc(PDocAccessible aChildDoc, uint64_t aID);
 
   // For now we'll add the command to send the proxy here. This might move to
   // PDocAccessible constructor in PBrowser.
   sync COMProxy(IAccessibleHolder aDocCOMProxy)
-    returns(IAccessibleHolder aParentCOMProxy);
+    returns(IAccessibleHolder aParentCOMProxy, uint32_t aMsaaID);
 
 child:
   async __delete__();
 };
 
 }
 }
--- a/accessible/windows/msaa/AccessibleWrap.cpp
+++ b/accessible/windows/msaa/AccessibleWrap.cpp
@@ -4,16 +4,18 @@
  * 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 "AccessibleWrap.h"
 #include "Accessible-inl.h"
 
 #include "Compatibility.h"
 #include "DocAccessible-inl.h"
+#include "mozilla/dom/TabChild.h"
+#include "mozilla/a11y/DocAccessibleChild.h"
 #include "mozilla/a11y/DocAccessibleParent.h"
 #include "EnumVariant.h"
 #include "nsAccUtils.h"
 #include "nsCoreUtils.h"
 #include "nsIAccessibleEvent.h"
 #include "nsWinUtils.h"
 #include "mozilla/a11y/ProxyAccessible.h"
 #include "ProxyWrappers.h"
@@ -64,46 +66,42 @@ IDSet AccessibleWrap::sIDGen;
 
 static const int32_t kIEnumVariantDisconnected = -1;
 
 ////////////////////////////////////////////////////////////////////////////////
 // AccessibleWrap
 ////////////////////////////////////////////////////////////////////////////////
 AccessibleWrap::AccessibleWrap(nsIContent* aContent, DocAccessible* aDoc) :
   Accessible(aContent, aDoc)
-#ifdef _WIN64
   , mID(kNoID)
-#endif
 {
 }
 
 AccessibleWrap::~AccessibleWrap()
 {
 #ifdef _WIN64
-  if (mID != kNoID)
+  if (mID != kNoID && XRE_IsParentProcess())
     sIDGen.ReleaseID(mID);
 #endif
 }
 
 ITypeInfo* AccessibleWrap::gTypeInfo = nullptr;
 
 NS_IMPL_ISUPPORTS_INHERITED0(AccessibleWrap, Accessible)
 
 void
 AccessibleWrap::Shutdown()
 {
-#ifdef _WIN64
   if (mID != kNoID) {
     auto doc = static_cast<DocAccessibleWrap*>(mDoc);
     MOZ_ASSERT(doc);
     if (doc) {
       doc->RemoveID(mID);
     }
   }
-#endif
 
   Accessible::Shutdown();
 }
 
 //-----------------------------------------------------
 // IUnknown interface methods - see iunknown.h for documentation
 //-----------------------------------------------------
 
@@ -1143,18 +1141,31 @@ AccessibleWrap::Invoke(DISPID dispIdMemb
 void
 AccessibleWrap::GetNativeInterface(void** aOutAccessible)
 {
   *aOutAccessible = static_cast<IAccessible*>(this);
   NS_ADDREF_THIS();
 }
 
 void
+AccessibleWrap::SetID(uint32_t aID)
+{
+  MOZ_ASSERT(XRE_IsContentProcess());
+  mID = aID;
+  DocAccessibleWrap* doc = static_cast<DocAccessibleWrap*>(Document());
+  DebugOnly<AccessibleWrap*> checkAcc = nullptr;
+  MOZ_ASSERT(!(checkAcc = doc->GetAccessibleByID(aID)) ||
+             checkAcc->GetExistingID() == aID);
+  doc->AddID(aID, this);
+}
+
+void
 AccessibleWrap::FireWinEvent(Accessible* aTarget, uint32_t aEventType)
 {
+  MOZ_ASSERT(XRE_IsParentProcess());
   static_assert(sizeof(gWinEventMap)/sizeof(gWinEventMap[0]) == nsIAccessibleEvent::EVENT_LAST_ENTRY,
                 "MSAA event map skewed");
 
   NS_ASSERTION(aEventType > 0 && aEventType < ArrayLength(gWinEventMap), "invalid event type");
 
   uint32_t winEvent = gWinEventMap[aEventType];
   if (!winEvent)
     return;
@@ -1241,16 +1252,23 @@ AccessibleWrap::GetChildIDFor(Accessible
   // A child ID of the window is required, when we use NotifyWinEvent,
   // so that the 3rd party application can call back and get the IAccessible
   // the event occurred on.
 
   if (!aAccessible) {
     return 0;
   }
 
+  // Content should use mID which has been generated by the chrome process.
+  if (XRE_IsContentProcess() && !aAccessible->IsApplication()) {
+    const uint32_t id = static_cast<AccessibleWrap*>(aAccessible)->mID;
+    MOZ_ASSERT(id != kNoID);
+    return id;
+  }
+
 #ifdef _WIN64
   if (!aAccessible->Document() && !aAccessible->IsProxy())
     return 0;
 
   uint32_t* id = & static_cast<AccessibleWrap*>(aAccessible)->mID;
   if (*id != kNoID)
     return *id;
 
@@ -1281,16 +1299,32 @@ AccessibleWrap::GetChildIDFor(Accessible
 
 HWND
 AccessibleWrap::GetHWNDFor(Accessible* aAccessible)
 {
   if (!aAccessible) {
     return nullptr;
   }
 
+  if (XRE_IsContentProcess()) {
+    DocAccessible* doc = aAccessible->Document();
+    if (!doc) {
+      return nullptr;
+    }
+
+    DocAccessibleChild* ipcDoc = doc->IPCDoc();
+    if (!ipcDoc) {
+      return nullptr;
+    }
+
+    auto tab = static_cast<dom::TabChild*>(ipcDoc->Manager());
+    MOZ_ASSERT(tab);
+    return reinterpret_cast<HWND>(tab->GetNativeWindowHandle());
+  }
+
   // Accessibles in child processes are said to have the HWND of the window
   // their tab is within.  Popups are always in the parent process, and so
   // never proxied, which means this is basically correct.
   if (aAccessible->IsProxy()) {
     ProxyAccessible* proxy = aAccessible->Proxy();
     if (!proxy) {
       return nullptr;
     }
@@ -1340,34 +1374,32 @@ AccessibleWrap::NativeAccessible(Accessi
     return nullptr;
   }
 
   IAccessible* msaaAccessible = nullptr;
   aAccessible->GetNativeInterface(reinterpret_cast<void**>(&msaaAccessible));
   return static_cast<IDispatch*>(msaaAccessible);
 }
 
-#ifdef _WIN64
 static Accessible*
 GetAccessibleInSubtree(DocAccessible* aDoc, uint32_t aID)
 {
   Accessible* child = static_cast<DocAccessibleWrap*>(aDoc)->GetAccessibleByID(aID);
   if (child)
     return child;
 
   uint32_t childDocCount = aDoc->ChildDocumentCount();
   for (uint32_t i = 0; i < childDocCount; i++) {
     child = GetAccessibleInSubtree(aDoc->GetChildDocumentAt(i), aID);
     if (child)
       return child;
   }
 
     return nullptr;
   }
-#endif
 
 static AccessibleWrap*
 GetProxiedAccessibleInSubtree(const DocAccessibleParent* aDoc, uint32_t aID)
 {
   auto wrapper = static_cast<DocProxyAccessibleWrap*>(WrapperFor(aDoc));
   AccessibleWrap* child = wrapper->GetAccessibleByID(aID);
   if (child) {
     return child;
@@ -1421,17 +1453,19 @@ AccessibleWrap::GetXPAccessibleFor(const
   if (!IsProxy()) {
     void* uniqueID = reinterpret_cast<void*>(intptr_t(-aVarChild.lVal));
 
     DocAccessible* document = Document();
     Accessible* child =
 #ifdef _WIN64
       GetAccessibleInSubtree(document, static_cast<uint32_t>(aVarChild.lVal));
 #else
-      document->GetAccessibleByUniqueIDInSubtree(uniqueID);
+      XRE_IsContentProcess() ?
+        GetAccessibleInSubtree(document, static_cast<uint32_t>(aVarChild.lVal)) :
+        document->GetAccessibleByUniqueIDInSubtree(uniqueID);
 #endif
 
     // If it is a document then just return an accessible.
     if (child && IsDoc())
       return child;
 
     // Otherwise check whether the accessible is a child (this path works for
     // ARIA documents and popups).
--- a/accessible/windows/msaa/AccessibleWrap.h
+++ b/accessible/windows/msaa/AccessibleWrap.h
@@ -174,27 +174,25 @@ public: // construction, destruction
    * Find an accessible by the given child ID in cached documents.
    */
   Accessible* GetXPAccessibleFor(const VARIANT& aVarChild);
 
   virtual void GetNativeInterface(void **aOutAccessible) override;
 
   static IDispatch* NativeAccessible(Accessible* aAccessible);
 
-#ifdef _WIN64
   uint32_t GetExistingID() const { return mID; }
   static const uint32_t kNoID = 0;
-#endif
+  // This is only valid to call in content
+  void SetID(uint32_t aID);
 
 protected:
   virtual ~AccessibleWrap();
 
-#ifdef _WIN64
   uint32_t mID;
-#endif
 
   /**
    * Return the wrapper for the document's proxy.
    */
   DocProxyAccessibleWrap* DocProxyWrapper() const;
 
   /**
    * Creates ITypeInfo for LIBID_Accessibility if it's needed and returns it.
--- a/accessible/windows/msaa/DocAccessibleWrap.cpp
+++ b/accessible/windows/msaa/DocAccessibleWrap.cpp
@@ -43,25 +43,20 @@ IMPL_IUNKNOWN_QUERY_HEAD(DocAccessibleWr
     return S_OK;
   }
 IMPL_IUNKNOWN_QUERY_TAIL_INHERITED(HyperTextAccessibleWrap)
 
 STDMETHODIMP
 DocAccessibleWrap::get_accParent(
       /* [retval][out] */ IDispatch __RPC_FAR *__RPC_FAR *ppdispParent)
 {
-  HRESULT hr = DocAccessible::get_accParent(ppdispParent);
-  if (*ppdispParent) {
-    return hr;
-  }
-
   // We might be a top-level document in a content process.
   DocAccessibleChild* ipcDoc = IPCDoc();
   if (!ipcDoc) {
-    return S_FALSE;
+    return DocAccessible::get_accParent(ppdispParent);
   }
   IAccessible* dispParent = ipcDoc->GetParentIAccessible();
   if (!dispParent) {
     return S_FALSE;
   }
 
   dispParent->AddRef();
   *ppdispParent = static_cast<IDispatch*>(dispParent);
--- a/accessible/windows/msaa/DocAccessibleWrap.h
+++ b/accessible/windows/msaa/DocAccessibleWrap.h
@@ -35,35 +35,31 @@ public:
   virtual void Shutdown();
 
   // DocAccessible
   virtual void* GetNativeWindow() const;
 
   /**
    * Manage the mapping from id to Accessible.
    */
-#ifdef _WIN64
   void AddID(uint32_t aID, AccessibleWrap* aAcc)
     { mIDToAccessibleMap.Put(aID, aAcc); }
   void RemoveID(uint32_t aID) { mIDToAccessibleMap.Remove(aID); }
   AccessibleWrap* GetAccessibleByID(uint32_t aID) const
     { return mIDToAccessibleMap.Get(aID); }
-#endif
 
 protected:
   // DocAccessible
   virtual void DoInitialUpdate();
 
 protected:
   void* mHWND;
 
   /*
    * This provides a mapping from 32 bit id to accessible objects.
    */
-#ifdef _WIN64
   nsDataHashtable<nsUint32HashKey, AccessibleWrap*> mIDToAccessibleMap;
-#endif
 };
 
 } // namespace a11y
 } // namespace mozilla
 
 #endif
--- a/addon-sdk/moz.build
+++ b/addon-sdk/moz.build
@@ -27,16 +27,17 @@ addons = [
     'contributors',
     'curly-id',
     'developers',
     'e10s-content',
     'e10s-l10n',
     'e10s-remote',
     'e10s-tabs',
     'e10s',
+    'embedded-webextension',
     'l10n-properties',
     'l10n',
     'layout-change',
     'main',
     'name-in-numbers-plus',
     'name-in-numbers',
     'packaging',
     'packed',
@@ -178,16 +179,20 @@ if CONFIG['MOZ_WIDGET_TOOLKIT'] != "gonk
         'source/lib/sdk/windows/tabs-fennec.js',
     ]
 
 EXTRA_JS_MODULES.commonjs += [
     'source/lib/index.js',
     'source/lib/test.js',
 ]
 
+EXTRA_JS_MODULES.commonjs.sdk += [
+    'source/lib/sdk/webextension.js',
+]
+
 EXTRA_JS_MODULES.commonjs.dev += [
     'source/lib/dev/debuggee.js',
     'source/lib/dev/frame-script.js',
     'source/lib/dev/panel.js',
     'source/lib/dev/ports.js',
     'source/lib/dev/theme.js',
     'source/lib/dev/toolbox.js',
     'source/lib/dev/utils.js',
--- a/addon-sdk/source/app-extension/bootstrap.js
+++ b/addon-sdk/source/app-extension/bootstrap.js
@@ -252,16 +252,19 @@ function startup(data, reasonCode) {
           checkMemory: options.check_memory,
         }
       }
     });
 
     let module = cuddlefish.Module('sdk/loader/cuddlefish', cuddlefishURI);
     let require = cuddlefish.Require(loader, module);
 
+    // Init the 'sdk/webextension' module from the bootstrap addon parameter.
+    require("sdk/webextension").initFromBootstrapAddonParam(data);
+
     require('sdk/addon/runner').startup(reason, {
       loader: loader,
       main: main,
       prefsURI: rootURI + 'defaults/preferences/prefs.js'
     });
   } catch (error) {
     dump('Bootstrap error: ' +
          (error.message ? error.message : String(error)) + '\n' +
--- a/addon-sdk/source/lib/sdk/addon/bootstrap.js
+++ b/addon-sdk/source/lib/sdk/addon/bootstrap.js
@@ -118,27 +118,30 @@ Bootstrap.prototype = {
         paths: Object.assign({
           "": "resource://gre/modules/commonjs/",
           "devtools/": "resource://devtools/",
           "./": baseURI
         }, readPaths(id)),
         manifest: metadata,
         metadata: metadata,
         modules: {
-          "@test/options": {}
+          "@test/options": {},
         },
         noQuit: prefs.get(`extensions.${id}.sdk.test.no-quit`, false)
       });
       self.loader = loader;
 
       const module = Module("package.json", `${baseURI}package.json`);
       const require = Require(loader, module);
       const main = command === "test" ? "sdk/test/runner" : null;
       const prefsURI = `${baseURI}defaults/preferences/prefs.js`;
 
+      // Init the 'sdk/webextension' module from the bootstrap addon parameter.
+      require("sdk/webextension").initFromBootstrapAddonParam(addon);
+
       const { startup } = require("sdk/addon/runner");
       startup(reason, {loader, main, prefsURI});
     }.bind(this)).catch(error => {
       console.error(`Failed to start ${id} addon`, error);
       throw error;
     });
   },
   shutdown(addon, code) {
new file mode 100644
--- /dev/null
+++ b/addon-sdk/source/lib/sdk/webextension.js
@@ -0,0 +1,43 @@
+/* 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";
+
+module.metadata = {
+  "stability": "experimental"
+};
+
+let webExtension;
+let waitForWebExtensionAPI;
+
+module.exports = {
+  initFromBootstrapAddonParam(data) {
+    if (webExtension) {
+      throw new Error("'sdk/webextension' module has been already initialized");
+    }
+
+    webExtension = data.webExtension;
+  },
+
+  startup() {
+    if (!webExtension) {
+      return Promise.reject(new Error(
+        "'sdk/webextension' module is currently disabled. " +
+        "('hasEmbeddedWebExtension' option is missing or set to false)"
+      ));
+    }
+
+    // NOTE: calling `startup` more than once raises an "Embedded Extension already started"
+    // error, but given that SDK addons are going to have access to the startup method through
+    // an SDK module that can be required in any part of the addon, it will be nicer if any
+    // additional startup calls return the startup promise instead of raising an exception,
+    // so that the SDK addon can access the API object in the other addon modules without the
+    // need to manually pass this promise around.
+    if (!waitForWebExtensionAPI) {
+      waitForWebExtensionAPI = webExtension.startup();
+    }
+
+    return waitForWebExtensionAPI;
+  }
+};
--- a/addon-sdk/source/python-lib/cuddlefish/rdf.py
+++ b/addon-sdk/source/python-lib/cuddlefish/rdf.py
@@ -128,16 +128,21 @@ def gen_manifest(template_root_dir, targ
     manifest.set("em:creator",
                  target_cfg.get("author", ""))
     manifest.set("em:bootstrap", str(bootstrap).lower())
     # XPIs remain packed by default, but package.json can override that. The
     # RDF format accepts "true" as True, anything else as False. We expect
     # booleans in the .json file, not strings.
     manifest.set("em:unpack", "true" if target_cfg.get("unpack") else "false")
 
+    if target_cfg.get('hasEmbeddedWebExtension', False):
+        elem = dom.createElement("em:hasEmbeddedWebExtension");
+        elem.appendChild(dom.createTextNode("true"))
+        dom.documentElement.getElementsByTagName("Description")[0].appendChild(elem)
+
     for translator in target_cfg.get("translators", [ ]):
         elem = dom.createElement("em:translator");
         elem.appendChild(dom.createTextNode(translator))
         dom.documentElement.getElementsByTagName("Description")[0].appendChild(elem)
 
     for developer in target_cfg.get("developers", [ ]):
         elem = dom.createElement("em:developer");
         elem.appendChild(dom.createTextNode(developer))
--- a/addon-sdk/source/python-lib/cuddlefish/xpi.py
+++ b/addon-sdk/source/python-lib/cuddlefish/xpi.py
@@ -43,37 +43,42 @@ def build_xpi(template_root_dir, manifes
     if 'icon64' in harness_options:
         zf.write(os.path.join(str(harness_options['icon64'])), 'icon64.png')
         del harness_options['icon64']
 
     # chrome.manifest
     if os.path.isfile(os.path.join(pkgdir, 'chrome.manifest')):
       files_to_copy['chrome.manifest'] = os.path.join(pkgdir, 'chrome.manifest')
 
-    # chrome folder (would contain content, skin, and locale folders typically)
-    folder = 'chrome'
-    if os.path.exists(os.path.join(pkgdir, folder)):
-      dirs_to_create.add('chrome')
-      # cp -r folder
-      abs_dirname = os.path.join(pkgdir, folder)
-      for dirpath, dirnames, filenames in os.walk(abs_dirname):
+    def add_special_dir(folder):
+      if os.path.exists(os.path.join(pkgdir, folder)):
+        dirs_to_create.add(folder)
+        # cp -r folder
+        abs_dirname = os.path.join(pkgdir, folder)
+        for dirpath, dirnames, filenames in os.walk(abs_dirname):
           goodfiles = list(filter_filenames(filenames, IGNORED_FILES))
           dirnames[:] = filter_dirnames(dirnames)
           for dirname in dirnames:
             arcpath = make_zipfile_path(template_root_dir,
                                         os.path.join(dirpath, dirname))
             dirs_to_create.add(arcpath)
           for filename in goodfiles:
               abspath = os.path.join(dirpath, filename)
               arcpath = ZIPSEP.join(
                   [folder,
                    make_zipfile_path(abs_dirname, os.path.join(dirpath, filename)),
                    ])
               files_to_copy[str(arcpath)] = str(abspath)
 
+
+    # chrome folder (would contain content, skin, and locale folders typically)
+    add_special_dir('chrome')
+    # optionally include a `webextension/` dir from the add-on dir.
+    add_special_dir('webextension')
+
     for dirpath, dirnames, filenames in os.walk(template_root_dir):
         if template_root_dir == dirpath:
             filenames = list(filter_filenames(filenames, IGNORED_TOP_LVL_FILES))
         filenames = list(filter_filenames(filenames, IGNORED_FILES))
         dirnames[:] = filter_dirnames(dirnames)
         for dirname in dirnames:
             arcpath = make_zipfile_path(template_root_dir,
                                         os.path.join(dirpath, dirname))
new file mode 100644
--- /dev/null
+++ b/addon-sdk/source/test/addons/embedded-webextension/main.js
@@ -0,0 +1,159 @@
+const tabs = require("sdk/tabs");
+const webExtension = require('sdk/webextension');
+
+exports.testEmbeddedWebExtensionModuleInitializedException = function (assert) {
+  let actualErr;
+
+  assert.throws(
+    () => webExtension.initFromBootstrapAddonParam({webExtension: null}),
+    /'sdk\/webextension' module has been already initialized/,
+    "Got the expected exception if the module is initialized twice"
+  );
+};
+
+exports.testEmbeddedWebExtensionBackgroungPage = function* (assert) {
+  try {
+    const api = yield webExtension.startup();
+    assert.ok(api, `webextension waitForStartup promise successfully resolved`);
+
+    const apiSecondStartup = yield webExtension.startup();
+    assert.equal(api, apiSecondStartup, "Got the same API object from the second startup call");
+
+    const {browser} = api;
+
+    let messageListener;
+    let waitForBackgroundPageMessage = new Promise((resolve, reject) => {
+      let numExpectedMessage = 2;
+      messageListener = (msg, sender, sendReply) => {
+        numExpectedMessage -= 1;
+        if (numExpectedMessage == 1) {
+          assert.equal(msg, "bg->sdk message",
+                       "Got the expected message from the background page");
+          sendReply("sdk reply");
+        } else if (numExpectedMessage == 0) {
+          assert.equal(msg, "sdk reply",
+                       "The background page received the expected reply message");
+          resolve();
+        } else {
+          console.error("Unexpected message received", {msg,sender, numExpectedMessage});
+          assert.ok(false, `unexpected message received`);
+          reject();
+        }
+      };
+      browser.runtime.onMessage.addListener(messageListener);
+    });
+
+    let portListener;
+    let waitForBackgroundPagePort = new Promise((resolve, reject) => {
+      portListener = (port) => {
+        let numExpectedMessages = 2;
+        port.onMessage.addListener((msg) => {
+          numExpectedMessages -= 1;
+
+          if (numExpectedMessages == 1) {
+            // Check that the legacy context has been able to receive the first port message
+            // and reply with a port message to the background page.
+            assert.equal(msg, "bg->sdk port message",
+                         "Got the expected port message from the background page");
+            port.postMessage("sdk->bg port message");
+          } else if (numExpectedMessages == 0) {
+            // Check that the background page has received the above port message.
+            assert.equal(msg, "bg received sdk->bg port message",
+                         "The background page received the expected port message");
+          }
+        });
+
+        port.onDisconnect.addListener(() => {
+          assert.equal(numExpectedMessages, 0, "Got the expected number of port messages");
+          resolve();
+        });
+      };
+      browser.runtime.onConnect.addListener(portListener);
+    });
+
+    yield Promise.all([
+      waitForBackgroundPageMessage,
+      waitForBackgroundPagePort,
+    ]).then(() => {
+      browser.runtime.onMessage.removeListener(messageListener);
+      browser.runtime.onConnect.removeListener(portListener);
+    });
+
+  } catch (err) {
+    assert.fail(`Unexpected webextension startup exception: ${err} - ${err.stack}`);
+  }
+};
+
+exports.testEmbeddedWebExtensionContentScript = function* (assert, done) {
+  try {
+    const {browser} = yield webExtension.startup();
+    assert.ok(browser, `webextension startup promise resolved successfully to the API object`);
+
+    let messageListener;
+    let waitForContentScriptMessage = new Promise((resolve, reject) => {
+      let numExpectedMessage = 2;
+      messageListener = (msg, sender, sendReply) => {
+        numExpectedMessage -= 1;
+        if (numExpectedMessage == 1) {
+          assert.equal(msg, "content script->sdk message",
+                       "Got the expected message from the content script");
+          sendReply("sdk reply");
+        } else if (numExpectedMessage == 0) {
+          assert.equal(msg, "sdk reply",
+                       "The content script received the expected reply message");
+          resolve();
+        } else {
+          console.error("Unexpected message received", {msg,sender, numExpectedMessage});
+          assert.ok(false, `unexpected message received`);
+          reject();
+        }
+      };
+      browser.runtime.onMessage.addListener(messageListener);
+    });
+
+    let portListener;
+    let waitForContentScriptPort = new Promise((resolve, reject) => {
+      portListener = (port) => {
+        let numExpectedMessages = 2;
+        port.onMessage.addListener((msg) => {
+          numExpectedMessages -= 1;
+
+          if (numExpectedMessages == 1) {
+            assert.equal(msg, "content script->sdk port message",
+                         "Got the expected message from the content script port");
+            port.postMessage("sdk->content script port message");
+          } else if (numExpectedMessages == 0) {
+            assert.equal(msg, "content script received sdk->content script port message",
+                         "The content script received the expected port message");
+          }
+        });
+        port.onDisconnect.addListener(() => {
+          assert.equal(numExpectedMessages, 0, "Got the epected number of port messages");
+          resolve();
+        });
+      };
+      browser.runtime.onConnect.addListener(portListener);
+    });
+
+    let url = "data:text/html;charset=utf-8,<h1>Test Page</h1>";
+
+    var openedTab;
+    tabs.once('open', function onOpen(tab) {
+      openedTab = tab;
+    });
+    tabs.open(url);
+
+    yield Promise.all([
+      waitForContentScriptMessage,
+      waitForContentScriptPort,
+    ]).then(() => {
+      browser.runtime.onMessage.removeListener(messageListener);
+      browser.runtime.onConnect.removeListener(portListener);
+      openedTab.close();
+    });
+  } catch (err) {
+    assert.fail(`Unexpected webextension startup exception: ${err} - ${err.stack}`);
+  }
+};
+
+require("sdk/test/runner").runTestsFromModule(module);
new file mode 100644
--- /dev/null
+++ b/addon-sdk/source/test/addons/embedded-webextension/package.json
@@ -0,0 +1,6 @@
+{
+  "id": "embedded-webextension@jetpack",
+  "version": "0.1.0",
+  "main": "./main.js",
+  "hasEmbeddedWebExtension": true
+}
new file mode 100644
--- /dev/null
+++ b/addon-sdk/source/test/addons/embedded-webextension/webextension/background-page.js
@@ -0,0 +1,10 @@
+browser.runtime.sendMessage("bg->sdk message", (reply) => {
+  browser.runtime.sendMessage(reply);
+});
+
+let port = browser.runtime.connect();
+port.onMessage.addListener((msg) => {
+  port.postMessage(`bg received ${msg}`);
+  port.disconnect();
+});
+port.postMessage("bg->sdk port message");
new file mode 100644
--- /dev/null
+++ b/addon-sdk/source/test/addons/embedded-webextension/webextension/content-script.js
@@ -0,0 +1,10 @@
+browser.runtime.sendMessage("content script->sdk message", (reply) => {
+  browser.runtime.sendMessage(reply);
+});
+
+let port = browser.runtime.connect();
+port.onMessage.addListener((msg) => {
+  port.postMessage(`content script received ${msg}`);
+  port.disconnect();
+});
+port.postMessage("content script->sdk port message");
new file mode 100644
--- /dev/null
+++ b/addon-sdk/source/test/addons/embedded-webextension/webextension/manifest.json
@@ -0,0 +1,18 @@
+{
+  "name": "Test SDK Embedded WebExtension",
+  "description": "",
+  "version": "0.1.0",
+  "applications": {
+    "gecko": {
+      "id": "embedded-webextension@jetpack"
+    }
+  },
+  "manifest_version": 2,
+  "permissions": ["tabs"],
+  "background": {
+    "scripts": ["background-page.js"]
+  },
+  "content_scripts": [
+    {"matches": ["<all_urls>"], "js": ["content-script.js"]}
+  ]
+}
--- a/addon-sdk/source/test/addons/jetpack-addon.ini
+++ b/addon-sdk/source/test/addons/jetpack-addon.ini
@@ -12,16 +12,17 @@ skip-if = true
 [e10s-content.xpi]
 skip-if = true
 [e10s-l10n.xpi]
 skip-if = true
 [e10s-remote.xpi]
 skip-if = true
 [e10s-tabs.xpi]
 skip-if = true
+[embedded-webextension.xpi]
 [l10n.xpi]
 [l10n-properties.xpi]
 [layout-change.xpi]
 [main.xpi]
 [name-in-numbers.xpi]
 [name-in-numbers-plus.xpi]
 [packaging.xpi]
 [packed.xpi]
--- a/browser/components/nsBrowserGlue.js
+++ b/browser/components/nsBrowserGlue.js
@@ -926,17 +926,19 @@ BrowserGlue.prototype = {
         SCALING_PROBE_NAME = "DISPLAY_SCALING_OSX";
         break;
       case "linux":
         SCALING_PROBE_NAME = "DISPLAY_SCALING_LINUX";
         break;
     }
     if (SCALING_PROBE_NAME) {
       let scaling = aWindow.devicePixelRatio * 100;
-      Services.telemetry.getHistogramById(SCALING_PROBE_NAME).add(scaling);
+      try {
+        Services.telemetry.getHistogramById(SCALING_PROBE_NAME).add(scaling);
+      } catch (ex) {}
     }
   },
 
   // the first browser window has finished initializing
   _onFirstWindowLoaded: function BG__onFirstWindowLoaded(aWindow) {
     // Initialize PdfJs when running in-process and remote. This only
     // happens once since PdfJs registers global hooks. If the PdfJs
     // extension is installed the init method below will be overridden
--- a/browser/components/search/content/search.xml
+++ b/browser/components/search/content/search.xml
@@ -1285,18 +1285,18 @@
               this.query = event.target.oneOffSearchQuery || event.target.value;
               break;
             case "popupshowing":
               this._rebuild();
               break;
             case "popuphidden":
               Services.tm.mainThread.dispatch(() => {
                 this.selectedButton = null;
+                this._contextEngine = null;
               }, Ci.nsIThread.DISPATCH_NORMAL);
-              this._contextEngine = null;
               break;
           }
         ]]></body>
       </method>
 
       <method name="showSettings">
         <body><![CDATA[
           BrowserUITelemetry.countSearchSettingsEvent(this.telemetryOrigin);
--- a/browser/components/search/test/browser.ini
+++ b/browser/components/search/test/browser.ini
@@ -24,16 +24,17 @@ support-files =
 [browser_contextSearchTabPosition.js]
 skip-if = os == "mac" # bug 967013
 [browser_google.js]
 [browser_google_codes.js]
 [browser_google_behavior.js]
 [browser_healthreport.js]
 [browser_hiddenOneOffs_cleanup.js]
 [browser_hiddenOneOffs_diacritics.js]
+[browser_oneOffContextMenu.js]
 [browser_oneOffHeader.js]
 [browser_private_search_perwindowpb.js]
 [browser_yahoo.js]
 [browser_yahoo_behavior.js]
 [browser_abouthome_behavior.js]
 skip-if = true # Bug ??????, Bug 1100301 - leaks windows until shutdown when --run-by-dir
 [browser_aboutSearchReset.js]
 [browser_searchbar_openpopup.js]
new file mode 100644
--- /dev/null
+++ b/browser/components/search/test/browser_oneOffContextMenu.js
@@ -0,0 +1,99 @@
+"use strict";
+
+const TEST_ENGINE_NAME = "Foo";
+const TEST_ENGINE_BASENAME = "testEngine.xml";
+
+const searchbar = document.getElementById("searchbar");
+const searchPopup = document.getElementById("PopupSearchAutoComplete");
+const searchIcon = document.getAnonymousElementByAttribute(
+  searchbar, "anonid", "searchbar-search-button"
+);
+const oneOffBinding = document.getAnonymousElementByAttribute(
+  searchPopup, "anonid", "search-one-off-buttons"
+);
+const contextMenu = document.getAnonymousElementByAttribute(
+  oneOffBinding, "anonid", "search-one-offs-context-menu"
+);
+const oneOffButtons = document.getAnonymousElementByAttribute(
+  oneOffBinding, "anonid", "search-panel-one-offs"
+);
+const searchInNewTabMenuItem = document.getAnonymousElementByAttribute(
+  oneOffBinding, "anonid", "search-one-offs-context-open-in-new-tab"
+);
+
+add_task(function* init() {
+  yield promiseNewEngine(TEST_ENGINE_BASENAME, {
+    setAsCurrent: false,
+  });
+});
+
+add_task(function* extendedTelemetryDisabled() {
+  yield SpecialPowers.pushPrefEnv({set: [["toolkit.telemetry.enabled", false]]});
+  yield doTest();
+  checkTelemetry("other");
+});
+
+add_task(function* extendedTelemetryEnabled() {
+  yield SpecialPowers.pushPrefEnv({set: [["toolkit.telemetry.enabled", true]]});
+  yield doTest();
+  checkTelemetry("other-" + TEST_ENGINE_NAME);
+});
+
+function* doTest() {
+  // Open the popup.
+  let promise = promiseEvent(searchPopup, "popupshown");
+  info("Opening search panel");
+  EventUtils.synthesizeMouseAtCenter(searchIcon, {});
+  yield promise;
+
+  // Get the one-off button for the test engine.
+  let oneOffButton;
+  for (let node of oneOffButtons.childNodes) {
+    if (node.engine && node.engine.name == TEST_ENGINE_NAME) {
+      oneOffButton = node;
+      break;
+    }
+  }
+  Assert.notEqual(oneOffButton, undefined,
+                  "One-off for test engine should exist");
+
+  // Open the context menu on the one-off.
+  promise = BrowserTestUtils.waitForEvent(contextMenu, "popupshown");
+  EventUtils.synthesizeMouseAtCenter(oneOffButton, {
+    type: "contextmenu",
+    button: 2,
+  });
+  yield promise;
+
+  // Click the Search in New Tab menu item.
+  promise = BrowserTestUtils.waitForNewTab(gBrowser);
+  EventUtils.synthesizeMouseAtCenter(searchInNewTabMenuItem, {});
+  let tab = yield promise;
+
+  // Check the loaded tab.
+  Assert.equal(tab.linkedBrowser.currentURI.spec,
+               "http://mochi.test:8888/browser/browser/components/search/test/",
+               "Expected search tab should have loaded");
+
+  yield BrowserTestUtils.removeTab(tab);
+
+  // Move the cursor out of the panel area to avoid messing with other tests.
+  yield EventUtils.synthesizeNativeMouseMove(searchbar);
+}
+
+function checkTelemetry(expectedEngineName) {
+  let propertyPath = [
+    "countableEvents",
+    "__DEFAULT__",
+    "search-oneoff",
+    expectedEngineName + ".oneoff-context-searchbar",
+    "unknown",
+    "tab-background",
+  ];
+  let telem = BrowserUITelemetry.getToolbarMeasures();
+  for (let prop of propertyPath) {
+    Assert.ok(prop in telem, "Property " + prop + " should be in the telemetry");
+    telem = telem[prop];
+  }
+  Assert.equal(telem, 1, "Click count");
+}
--- a/devtools/client/framework/devtools-browser.js
+++ b/devtools/client/framework/devtools-browser.js
@@ -629,17 +629,17 @@ var gDevToolsBrowser = exports.gDevTools
     }
     gDevToolsBrowser._trackedBrowserWindows.delete(win);
     win.removeEventListener("unload", this);
 
     BrowserMenus.removeMenus(win.document);
 
     // Destroy toolboxes for closed window
     for (let [target, toolbox] of gDevTools._toolboxes) {
-      if (toolbox.win == win) {
+      if (toolbox.win.top == win) {
         toolbox.destroy();
       }
     }
 
     // Destroy the Developer toolbar if it has been accessed
     let desc = Object.getOwnPropertyDescriptor(win, "DeveloperToolbar");
     if (desc && !desc.get) {
       win.DeveloperToolbar.destroy();
--- a/devtools/client/framework/target.js
+++ b/devtools/client/framework/target.js
@@ -614,16 +614,19 @@ TabTarget.prototype = {
 
     this.activeTab = null;
     this.activeConsole = null;
     this._client = null;
     this._tab = null;
     this._form = null;
     this._remote = null;
     this._root = null;
+    this._title = null;
+    this._url = null;
+    this.threadActor = null;
   },
 
   toString: function () {
     let id = this._tab ? this._tab : (this._form && this._form.actor);
     return `TabTarget:${id}`;
   },
 
   /**
--- a/devtools/client/framework/test/browser_toolbox_theme_registration.js
+++ b/devtools/client/framework/test/browser_toolbox_theme_registration.js
@@ -73,24 +73,26 @@ add_task(function* themeInOptionsPanel()
 
   onThemeSwithComplete = once(panelWin, "theme-switch-complete");
   // Select test theme again.
   testThemeOption.click();
   yield onThemeSwithComplete;
 });
 
 add_task(function* themeUnregistration() {
+  let panelWin = toolbox.getCurrentPanel().panelWin;
   let onUnRegisteredTheme = once(gDevTools, "theme-unregistered");
+  let onThemeSwitchComplete = once(panelWin, "theme-switch-complete");
   gDevTools.unregisterTheme("test-theme");
   yield onUnRegisteredTheme;
+  yield onThemeSwitchComplete;
 
   ok(!gDevTools.getThemeDefinitionMap().has("test-theme"),
     "theme removed from map");
 
-  let panelWin = toolbox.getCurrentPanel().panelWin;
   let doc = panelWin.frameElement.contentDocument;
   let themeBox = doc.getElementById("devtools-theme-box");
 
   // The default light theme must be selected now.
   is(themeBox.querySelector("#devtools-theme-box [value=light]").checked, true,
     "light theme must be selected");
 });
 
--- a/devtools/client/framework/toolbox-hosts.js
+++ b/devtools/client/framework/toolbox-hosts.js
@@ -168,16 +168,19 @@ BottomHost.prototype = {
    */
   destroy: function () {
     if (!this._destroyed) {
       this._destroyed = true;
 
       Services.prefs.setIntPref(this.heightPref, this.frame.height);
       this._nbox.removeChild(this._splitter);
       this._nbox.removeChild(this.frame);
+      this.frame = null;
+      this._nbox = null;
+      this._splitter = null;
     }
 
     return promise.resolve(null);
   }
 };
 
 /**
  * Host object for the in-browser sidebar
--- a/devtools/client/framework/toolbox-options.js
+++ b/devtools/client/framework/toolbox-options.js
@@ -93,34 +93,36 @@ OptionsPanel.prototype = {
     this.setupThemeList();
     yield this.populatePreferences();
     this.isReady = true;
     this.emit("ready");
     return this;
   }),
 
   _addListeners: function () {
-    gDevTools.on("pref-changed", this._prefChanged);
+    Services.prefs.addObserver("devtools.cache.disabled", this._prefChanged, false);
+    Services.prefs.addObserver("devtools.theme", this._prefChanged, false);
     gDevTools.on("theme-registered", this._themeRegistered);
     gDevTools.on("theme-unregistered", this._themeUnregistered);
   },
 
   _removeListeners: function () {
-    gDevTools.off("pref-changed", this._prefChanged);
+    Services.prefs.removeObserver("devtools.cache.disabled", this._prefChanged);
+    Services.prefs.removeObserver("devtools.theme", this._prefChanged);
     gDevTools.off("theme-registered", this._themeRegistered);
     gDevTools.off("theme-unregistered", this._themeUnregistered);
   },
 
-  _prefChanged: function (event, data) {
-    if (data.pref === "devtools.cache.disabled") {
+  _prefChanged: function (subject, topic, prefName) {
+    if (prefName === "devtools.cache.disabled") {
       let cacheDisabled = data.newValue;
       let cbx = this.panelDoc.getElementById("devtools-disable-cache");
 
       cbx.checked = cacheDisabled;
-    } else if (data.pref === "devtools.theme") {
+    } else if (prefName === "devtools.theme") {
       this.updateCurrentTheme();
     }
   },
 
   _themeRegistered: function (event, themeId) {
     this.setupThemeList();
   },
 
@@ -249,16 +251,17 @@ OptionsPanel.prototype = {
       toolsNotSupportedLabel.style.display = "none";
     }
 
     this.panelWin.focus();
   },
 
   setupThemeList: function () {
     let themeBox = this.panelDoc.getElementById("devtools-theme-box");
+    themeBox.innerHTML = "";
 
     let createThemeOption = theme => {
       let inputLabel = this.panelDoc.createElement("label");
       let inputRadio = this.panelDoc.createElement("input");
       inputRadio.setAttribute("type", "radio");
       inputRadio.setAttribute("value", theme.id);
       inputRadio.setAttribute("name", "devtools-theme-item");
       inputRadio.addEventListener("change", function (e) {
@@ -345,21 +348,21 @@ OptionsPanel.prototype = {
   },
 
   updateCurrentTheme: function () {
     let currentTheme = GetPref("devtools.theme");
     let themeBox = this.panelDoc.getElementById("devtools-theme-box");
     let themeRadioInput = themeBox.querySelector(`[value=${currentTheme}]`);
 
     if (themeRadioInput) {
-      themeRadioInput.click();
+      themeRadioInput.checked = true;
     } else {
       // If the current theme does not exist anymore, switch to light theme
       let lightThemeInputRadio = themeBox.querySelector("[value=light]");
-      lightThemeInputRadio.click();
+      lightThemeInputRadio.checked = true;
     }
   },
 
   /**
    * Disables JavaScript for the currently loaded tab. We force a page refresh
    * here because setting docShell.allowJavascript to true fails to block JS
    * execution from event listeners added using addEventListener(), AJAX calls
    * and timers. The page refresh prevents these things from being added in the
--- a/devtools/client/framework/toolbox.js
+++ b/devtools/client/framework/toolbox.js
@@ -818,19 +818,17 @@ Toolbox.prototype = {
         continue;
       }
 
       let button = this.doc.createElementNS(HTML_NS, "button");
       button.id = "toolbox-dock-" + position;
       button.className = "toolbox-dock-button devtools-button";
       button.setAttribute("title", L10N.getStr("toolboxDockButtons." +
                                                   position + ".tooltip"));
-      button.addEventListener("click", () => {
-        this.switchHost(position);
-      });
+      button.addEventListener("click", this.switchHost.bind(this, position));
 
       dockBox.appendChild(button);
     }
   },
 
   _getMinimizeButtonShortcutTooltip: function () {
     let str = L10N.getStr("toolbox.minimize.key");
     let key = KeyShortcuts.parseElectronKey(this.win, str);
@@ -1131,19 +1129,17 @@ Toolbox.prototype = {
     radio.setAttribute("ordinal", toolDefinition.ordinal);
     radio.setAttribute("tooltiptext", toolDefinition.tooltip);
     if (toolDefinition.invertIconForLightTheme) {
       radio.setAttribute("icon-invertable", "light-theme");
     } else if (toolDefinition.invertIconForDarkTheme) {
       radio.setAttribute("icon-invertable", "dark-theme");
     }
 
-    radio.addEventListener("command", () => {
-      this.selectTool(id);
-    });
+    radio.addEventListener("command", this.selectTool.bind(this, id));
 
     // spacer lets us center the image and label, while allowing cropping
     let spacer = this.doc.createElement("spacer");
     spacer.setAttribute("flex", "1");
     radio.appendChild(spacer);
 
     if (toolDefinition.icon) {
       let image = this.doc.createElement("image");
@@ -2047,16 +2043,18 @@ Toolbox.prototype = {
    * Remove all UI elements, detach from target and clear up
    */
   destroy: function () {
     // If several things call destroy then we give them all the same
     // destruction promise so we're sure to destroy only once
     if (this._destroyer) {
       return this._destroyer;
     }
+    let deferred = defer();
+    this._destroyer = deferred.promise;
 
     this.emit("destroy");
 
     this._target.off("navigate", this._refreshHostTitle);
     this._target.off("frame-update", this._updateFrames);
     this.off("select", this._refreshHostTitle);
     this.off("host-changed", this._refreshHostTitle);
     this.off("ready", this._showDevEditionPromo);
@@ -2071,23 +2069,33 @@ Toolbox.prototype = {
       this._sourceMapService.destroy();
       this._sourceMapService = null;
     }
 
     if (this.webconsolePanel) {
       this._saveSplitConsoleHeight();
       this.webconsolePanel.removeEventListener("resize",
         this._saveSplitConsoleHeight);
+      this.webconsolePanel = null;
     }
-    this.closeButton.removeEventListener("click", this.destroy, true);
-    this.textboxContextMenuPopup.removeEventListener("popupshowing",
-      this._updateTextboxMenuItems, true);
-    this.tabbar.removeEventListener("focus", this._onTabbarFocus, true);
-    this.tabbar.removeEventListener("click", this._onTabbarFocus, true);
-    this.tabbar.removeEventListener("keypress", this._onTabbarArrowKeypress);
+    if (this.closeButton) {
+      this.closeButton.removeEventListener("click", this.destroy, true);
+      this.closeButton = null;
+    }
+    if (this.textboxContextMenuPopup) {
+      this.textboxContextMenuPopup.removeEventListener("popupshowing",
+        this._updateTextboxMenuItems, true);
+      this.textboxContextMenuPopup = null;
+    }
+    if (this.tabbar) {
+      this.tabbar.removeEventListener("focus", this._onTabbarFocus, true);
+      this.tabbar.removeEventListener("click", this._onTabbarFocus, true);
+      this.tabbar.removeEventListener("keypress", this._onTabbarArrowKeypress);
+      this.tabbar = null;
+    }
 
     let outstanding = [];
     for (let [id, panel] of this._toolPanels) {
       try {
         gDevTools.emit(id + "-destroy", this, panel);
         this.emit(id + "-destroy", panel);
 
         outstanding.push(panel.destroy());
@@ -2132,17 +2140,17 @@ Toolbox.prototype = {
       CommandUtils.destroyRequisition(this._requisition, this.target);
     }
     this._telemetry.toolClosed("toolbox");
     this._telemetry.destroy();
 
     // Finish all outstanding tasks (which means finish destroying panels and
     // then destroying the host, successfully or not) before destroying the
     // target.
-    this._destroyer = settleAll(outstanding)
+    deferred.resolve(settleAll(outstanding)
         .catch(console.error)
         .then(() => this.destroyHost())
         .catch(console.error)
         .then(() => {
           this._win = null;
 
           // Targets need to be notified that the toolbox is being torn down.
           // This is done after other destruction tasks since it may tear down
@@ -2166,17 +2174,17 @@ Toolbox.prototype = {
 
           // Force GC to prevent long GC pauses when running tests and to free up
           // memory in general when the toolbox is closed.
           if (flags.testing) {
             win.QueryInterface(Ci.nsIInterfaceRequestor)
               .getInterface(Ci.nsIDOMWindowUtils)
               .garbageCollect();
           }
-        }).then(null, console.error);
+        }).then(null, console.error));
 
     let leakCheckObserver = ({wrappedJSObject: barrier}) => {
       // Make the leak detector wait until this toolbox is properly destroyed.
       barrier.client.addBlocker("DevTools: Wait until toolbox is destroyed",
                                 this._destroyer);
     };
 
     let topic = "shutdown-leaks-before-check";
--- a/devtools/client/shared/developer-toolbar.js
+++ b/devtools/client/shared/developer-toolbar.js
@@ -100,38 +100,38 @@ var CommandUtils = {
 
         if (command.tooltipText != null) {
           button.setAttribute("title", command.tooltipText);
         }
         else if (command.description != null) {
           button.setAttribute("title", command.description);
         }
 
-        button.addEventListener("click", () => {
-          requisition.updateExec(typed);
-        }, false);
+        button.addEventListener("click",
+          requisition.updateExec.bind(requisition, typed));
 
         button.addEventListener("keypress", (event) => {
           if (ViewHelpers.isSpaceOrReturn(event)) {
             event.preventDefault();
             requisition.updateExec(typed);
           }
         }, false);
 
         // Allow the command button to be toggleable
+        let onChange = null;
         if (command.state) {
           button.setAttribute("autocheck", false);
 
           /**
            * The onChange event should be called with an event object that
            * contains a target property which specifies which target the event
            * applies to. For legacy reasons the event object can also contain
            * a tab property.
            */
-          let onChange = (eventName, ev) => {
+          onChange = (eventName, ev) => {
             if (ev.target == target || ev.tab == target.tab) {
 
               let updateChecked = (checked) => {
                 if (checked) {
                   button.setAttribute("checked", true);
                 }
                 else if (button.hasAttribute("checked")) {
                   button.removeAttribute("checked");
@@ -150,22 +150,24 @@ var CommandUtils = {
               else {
                 updateChecked(reply);
               }
             }
           };
 
           command.state.onChange(target, onChange);
           onChange("", { target: target });
-          document.defaultView.addEventListener("unload", () => {
-            if (command.state.offChange) {
-              command.state.offChange(target, onChange);
-            }
-          }, false);
-        }
+        };
+        document.defaultView.addEventListener("unload", function (event) {
+          if (onChange && command.state.offChange) {
+            command.state.offChange(target, onChange);
+          }
+          button.remove();
+          button = null;
+        }, { once: true });
 
         requisition.clear();
 
         return button;
       });
     });
   },
 
--- a/devtools/client/shared/theme-switching.js
+++ b/devtools/client/shared/theme-switching.js
@@ -22,16 +22,17 @@
 
   // no-theme attributes allows to just est the platform attribute
   // to have per-platform CSS working correctly.
   if (documentElement.getAttribute("no-theme") === "true") {
     return;
   }
 
   let devtoolsStyleSheets = new WeakMap();
+  let gOldTheme = "";
 
   function forceStyle() {
     let computedStyle = window.getComputedStyle(documentElement);
     if (!computedStyle) {
       // Null when documentElement is not ready. This method is anyways not
       // required then as scrollbars would be in their state without flushing.
       return;
     }
@@ -78,20 +79,22 @@
   function notifyWindow() {
     window.dispatchEvent(new CustomEvent("theme-switch-complete", {}));
   }
 
   /*
    * Apply all the sheets from `newTheme` and remove all of the sheets
    * from `oldTheme`
    */
-  function switchTheme(newTheme, oldTheme) {
-    if (newTheme === oldTheme) {
+  function switchTheme(newTheme) {
+    if (newTheme === gOldTheme) {
       return;
     }
+    let oldTheme = gOldTheme;
+    gOldTheme = newTheme;
 
     let oldThemeDef = gDevTools.getThemeDefinition(oldTheme);
     let newThemeDef = gDevTools.getThemeDefinition(newTheme);
 
     // The theme might not be available anymore (e.g. uninstalled)
     // Use the default one.
     if (!newThemeDef) {
       newThemeDef = gDevTools.getThemeDefinition("light");
@@ -151,34 +154,32 @@
       }
 
       // Final notification for further theme-switching related logic.
       gDevTools.emit("theme-switched", window, newTheme, oldTheme);
       notifyWindow();
     }, console.error.bind(console));
   }
 
-  function handlePrefChange(event, data) {
-    if (data.pref == "devtools.theme") {
-      switchTheme(data.newValue, data.oldValue);
-    }
+  function handlePrefChange() {
+    switchTheme(Services.prefs.getCharPref("devtools.theme"));
   }
 
   const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
   const { require } = Cu.import("resource://devtools/shared/Loader.jsm", {});
   const Services = require("Services");
   const { gDevTools } = require("devtools/client/framework/devtools");
   const StylesheetUtils = require("sdk/stylesheet/utils");
   const { watchCSS } = require("devtools/client/shared/css-reload");
 
   if (documentElement.hasAttribute("force-theme")) {
     switchTheme(documentElement.getAttribute("force-theme"));
   } else {
     switchTheme(Services.prefs.getCharPref("devtools.theme"));
 
-    gDevTools.on("pref-changed", handlePrefChange);
+    Services.prefs.addObserver("devtools.theme", handlePrefChange, false);
     window.addEventListener("unload", function () {
-      gDevTools.off("pref-changed", handlePrefChange);
-    });
+      Services.prefs.removeObserver("devtools.theme", handlePrefChange);
+    }, { once: true });
   }
 
   watchCSS(window);
 })();
--- a/devtools/client/webconsole/panel.js
+++ b/devtools/client/webconsole/panel.js
@@ -102,13 +102,17 @@ WebConsolePanel.prototype = {
   },
 
   destroy: function () {
     if (this._destroyer) {
       return this._destroyer;
     }
 
     this._destroyer = this.hud.destroy();
-    this._destroyer.then(() => this.emit("destroyed"));
+    this._destroyer.then(() => {
+      this._frameWindow = null;
+      this._toolbox = null;
+      this.emit("destroyed");
+    });
 
     return this._destroyer;
   },
 };
--- a/dom/base/test/browser_use_counters.js
+++ b/dom/base/test/browser_use_counters.js
@@ -1,21 +1,27 @@
 /* -*- Mode: javascript; tab-width: 2; indent-tabs-mode: nil; js-indent-level: 2 -*- */
 
 requestLongerTimeout(2);
 
 var {Promise: promise} = Cu.import("resource://gre/modules/Promise.jsm", {});
+Cu.import("resource://gre/modules/Services.jsm");
 
 const gHttpTestRoot = "http://example.com/browser/dom/base/test/";
 
 /**
  * Enable local telemetry recording for the duration of the tests.
  */
 var gOldContentCanRecord = false;
+var gOldParentCanRecord = false;
 add_task(function* test_initialize() {
+  let Telemetry = Cc["@mozilla.org/base/telemetry;1"].getService(Ci.nsITelemetry);
+  gOldParentCanRecord = Telemetry.canRecordExtended
+  Telemetry.canRecordExtended = true;
+
   // Because canRecordExtended is a per-process variable, we need to make sure
   // that all of the pages load in the same content process. Limit the number
   // of content processes to at most 1 (or 0 if e10s is off entirely).
   yield SpecialPowers.pushPrefEnv({ set: [[ "dom.ipc.processCount", 1 ]] });
 
   gOldContentCanRecord = yield ContentTask.spawn(gBrowser.selectedBrowser, {}, function () {
     let telemetry = Cc["@mozilla.org/base/telemetry;1"].getService(Ci.nsITelemetry);
     let old = telemetry.canRecordExtended;
@@ -69,16 +75,19 @@ add_task(function* () {
   // document counters, because we won't be re-parsing the SVG documents.
   yield check_use_counter_iframe("file_use_counter_svg_background.html",
                                  "PROPERTY_FILL", false);
   yield check_use_counter_iframe("file_use_counter_svg_list_style_image.html",
                                  "PROPERTY_FILL", false);
 });
 
 add_task(function* () {
+  let Telemetry = Cc["@mozilla.org/base/telemetry;1"].getService(Ci.nsITelemetry);
+  Telemetry.canRecordExtended = gOldParentCanRecord;
+
   yield ContentTask.spawn(gBrowser.selectedBrowser, { oldCanRecord: gOldContentCanRecord }, function (arg) {
     Cu.import("resource://gre/modules/PromiseUtils.jsm");
     yield new Promise(resolve => {
       let telemetry = Cc["@mozilla.org/base/telemetry;1"].getService(Ci.nsITelemetry);
       telemetry.canRecordExtended = arg.oldCanRecord;
       resolve();
     });
   });
@@ -99,46 +108,42 @@ function waitForPageLoad(browser) {
         removeEventListener("load", listener, true);
         resolve();
       }
       addEventListener("load", listener, true);
     });
   });
 }
 
-function grabHistogramsFromContent(browser, use_counter_middlefix) {
-  return ContentTask.spawn(browser, { middlefix: use_counter_middlefix }, function* (arg) {
-    let telemetry = Cc["@mozilla.org/base/telemetry;1"].getService(Ci.nsITelemetry);
-    function snapshot_histogram(name) {
-      return telemetry.getHistogramById(name).snapshot();
-    }
-
-    let histogram_page_name = "USE_COUNTER2_" + arg.middlefix + "_PAGE";
-    let histogram_document_name = "USE_COUNTER2_" + arg.middlefix + "_DOCUMENT";
-    let histogram_page = snapshot_histogram(histogram_page_name);
-    let histogram_document = snapshot_histogram(histogram_document_name);
-    let histogram_docs = snapshot_histogram("CONTENT_DOCUMENTS_DESTROYED");
-    let histogram_toplevel_docs = snapshot_histogram("TOP_LEVEL_CONTENT_DOCUMENTS_DESTROYED");
-    return [histogram_page.sum, histogram_document.sum,
-            histogram_docs.sum, histogram_toplevel_docs.sum];
-  });
+function grabHistogramsFromContent(use_counter_middlefix, page_before = null) {
+  let telemetry = Cc["@mozilla.org/base/telemetry;1"].getService(Ci.nsITelemetry);
+  let suffix = Services.appinfo.browserTabsRemoteAutostart ? "#content" : "";
+  let gather = () => [
+    telemetry.getHistogramById("USE_COUNTER2_" + use_counter_middlefix + "_PAGE" + suffix).snapshot().sum,
+    telemetry.getHistogramById("USE_COUNTER2_" + use_counter_middlefix + "_DOCUMENT" + suffix).snapshot().sum,
+    telemetry.getHistogramById("CONTENT_DOCUMENTS_DESTROYED" + suffix).snapshot().sum,
+    telemetry.getHistogramById("TOP_LEVEL_CONTENT_DOCUMENTS_DESTROYED" + suffix).snapshot().sum,
+  ];
+  return BrowserTestUtils.waitForCondition(() => {
+    return page_before != telemetry.getHistogramById("USE_COUNTER2_" + use_counter_middlefix + "_PAGE" + suffix).snapshot().sum;
+  }).then(gather, gather);
 }
 
 var check_use_counter_iframe = Task.async(function* (file, use_counter_middlefix, check_documents=true) {
   info("checking " + file + " with histogram " + use_counter_middlefix);
 
   let newTab = gBrowser.addTab( "about:blank");
   gBrowser.selectedTab = newTab;
   newTab.linkedBrowser.stop();
 
   // Hold on to the current values of the telemetry histograms we're
   // interested in.
   let [histogram_page_before, histogram_document_before,
        histogram_docs_before, histogram_toplevel_docs_before] =
-      yield grabHistogramsFromContent(gBrowser.selectedBrowser, use_counter_middlefix);
+      yield grabHistogramsFromContent(use_counter_middlefix);
 
   gBrowser.selectedBrowser.loadURI(gHttpTestRoot + "file_use_counter_outer.html");
   yield waitForPageLoad(gBrowser.selectedBrowser);
 
   // Inject our desired file into the iframe of the newly-loaded page.
   yield ContentTask.spawn(gBrowser.selectedBrowser, { file: file }, function(opts) {
     Cu.import("resource://gre/modules/PromiseUtils.jsm");
     let deferred = PromiseUtils.defer();
@@ -146,17 +151,17 @@ var check_use_counter_iframe = Task.asyn
     let wu = content.window.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils);
 
     let iframe = content.document.getElementById('content');
     iframe.src = opts.file;
     let listener = (event) => {
       event.target.removeEventListener("load", listener, true);
 
       // We flush the main document first, then the iframe's document to
-      // ensure any propagation that might happen from child->parent should
+      // ensure any propagation that might happen from content->parent should
       // have already happened when counters are reported to telemetry.
       wu.forceUseCounterFlush(content.document);
       wu.forceUseCounterFlush(iframe.contentDocument);
 
       deferred.resolve();
     };
     iframe.addEventListener("load", listener, true);
 
@@ -169,17 +174,17 @@ var check_use_counter_iframe = Task.asyn
   // The histograms only get recorded when the document actually gets
   // destroyed, which might not have happened yet due to GC/CC effects, etc.
   // Try to force document destruction.
   yield waitForDestroyedDocuments();
 
   // Grab histograms again and compare.
   let [histogram_page_after, histogram_document_after,
        histogram_docs_after, histogram_toplevel_docs_after] =
-      yield grabHistogramsFromContent(gBrowser.selectedBrowser, use_counter_middlefix);
+      yield grabHistogramsFromContent(use_counter_middlefix, histogram_page_before);
 
   is(histogram_page_after, histogram_page_before + 1,
      "page counts for " + use_counter_middlefix + " after are correct");
   ok(histogram_toplevel_docs_after >= histogram_toplevel_docs_before + 1,
      "top level document counts are correct");
   if (check_documents) {
     is(histogram_document_after, histogram_document_before + 1,
        "document counts for " + use_counter_middlefix + " after are correct");
@@ -192,17 +197,17 @@ var check_use_counter_img = Task.async(f
   let newTab = gBrowser.addTab("about:blank");
   gBrowser.selectedTab = newTab;
   newTab.linkedBrowser.stop();
 
   // Hold on to the current values of the telemetry histograms we're
   // interested in.
   let [histogram_page_before, histogram_document_before,
        histogram_docs_before, histogram_toplevel_docs_before] =
-      yield grabHistogramsFromContent(gBrowser.selectedBrowser, use_counter_middlefix);
+      yield grabHistogramsFromContent(use_counter_middlefix);
 
   gBrowser.selectedBrowser.loadURI(gHttpTestRoot + "file_use_counter_outer.html");
   yield waitForPageLoad(gBrowser.selectedBrowser);
 
   // Inject our desired file into the img of the newly-loaded page.
   yield ContentTask.spawn(gBrowser.selectedBrowser, { file: file }, function(opts) {
     Cu.import("resource://gre/modules/PromiseUtils.jsm");
     let deferred = PromiseUtils.defer();
@@ -234,17 +239,17 @@ var check_use_counter_img = Task.async(f
   // The histograms only get recorded when the document actually gets
   // destroyed, which might not have happened yet due to GC/CC effects, etc.
   // Try to force document destruction.
   yield waitForDestroyedDocuments();
 
   // Grab histograms again and compare.
   let [histogram_page_after, histogram_document_after,
        histogram_docs_after, histogram_toplevel_docs_after] =
-      yield grabHistogramsFromContent(gBrowser.selectedBrowser, use_counter_middlefix);
+      yield grabHistogramsFromContent(use_counter_middlefix, histogram_page_before);
   is(histogram_page_after, histogram_page_before + 1,
      "page counts for " + use_counter_middlefix + " after are correct");
   is(histogram_document_after, histogram_document_before + 1,
      "document counts for " + use_counter_middlefix + " after are correct");
   ok(histogram_toplevel_docs_after >= histogram_toplevel_docs_before + 1,
      "top level document counts are correct");
   // 2 documents: one for the outer html page containing the <img> element, and
   // one for the SVG image itself.
@@ -258,17 +263,17 @@ var check_use_counter_direct = Task.asyn
   let newTab = gBrowser.addTab( "about:blank");
   gBrowser.selectedTab = newTab;
   newTab.linkedBrowser.stop();
 
   // Hold on to the current values of the telemetry histograms we're
   // interested in.
   let [histogram_page_before, histogram_document_before,
        histogram_docs_before, histogram_toplevel_docs_before] =
-      yield grabHistogramsFromContent(gBrowser.selectedBrowser, use_counter_middlefix);
+      yield grabHistogramsFromContent(use_counter_middlefix);
 
   gBrowser.selectedBrowser.loadURI(gHttpTestRoot + file);
   yield ContentTask.spawn(gBrowser.selectedBrowser, null, function*() {
     Cu.import("resource://gre/modules/PromiseUtils.jsm");
     yield new Promise(resolve => {
       let listener = () => {
         removeEventListener("load", listener, true);
 
@@ -287,17 +292,17 @@ var check_use_counter_direct = Task.asyn
   // The histograms only get recorded when the document actually gets
   // destroyed, which might not have happened yet due to GC/CC effects, etc.
   // Try to force document destruction.
   yield waitForDestroyedDocuments();
 
   // Grab histograms again and compare.
   let [histogram_page_after, histogram_document_after,
        histogram_docs_after, histogram_toplevel_docs_after] =
-      yield grabHistogramsFromContent(gBrowser.selectedBrowser, use_counter_middlefix);
+      yield grabHistogramsFromContent(use_counter_middlefix, histogram_page_before);
   (xfail ? todo_is : is)(histogram_page_after, histogram_page_before + 1,
                          "page counts for " + use_counter_middlefix + " after are correct");
   (xfail ? todo_is : is)(histogram_document_after, histogram_document_before + 1,
                          "document counts for " + use_counter_middlefix + " after are correct");
   ok(histogram_toplevel_docs_after >= histogram_toplevel_docs_before + 1,
      "top level document counts are correct");
   ok(histogram_docs_after >= histogram_docs_before + 1,
      "document counts are correct");
--- a/dom/ipc/ContentParent.cpp
+++ b/dom/ipc/ContentParent.cpp
@@ -5408,8 +5408,24 @@ ContentParent::SendGetFilesResponseAndFo
 void
 ContentParent::ForceTabPaint(TabParent* aTabParent, uint64_t aLayerObserverEpoch)
 {
   if (!mHangMonitorActor) {
     return;
   }
   ProcessHangMonitor::ForcePaint(mHangMonitorActor, aTabParent, aLayerObserverEpoch);
 }
+
+bool
+ContentParent::RecvAccumulateChildHistogram(
+                InfallibleTArray<Accumulation>&& aAccumulations)
+{
+  Telemetry::AccumulateChild(aAccumulations);
+  return true;
+}
+
+bool
+ContentParent::RecvAccumulateChildKeyedHistogram(
+                InfallibleTArray<KeyedAccumulation>&& aAccumulations)
+{
+  Telemetry::AccumulateChildKeyed(aAccumulations);
+  return true;
+}
--- a/dom/ipc/ContentParent.h
+++ b/dom/ipc/ContentParent.h
@@ -1132,16 +1132,20 @@ private:
   virtual bool RecvNotifyLowMemory() override;
 
   virtual bool RecvGetFilesRequest(const nsID& aID,
                                    const nsString& aDirectoryPath,
                                    const bool& aRecursiveFlag) override;
 
   virtual bool RecvDeleteGetFilesRequest(const nsID& aID) override;
 
+  virtual bool RecvAccumulateChildHistogram(
+                  InfallibleTArray<Accumulation>&& aAccumulations) override;
+  virtual bool RecvAccumulateChildKeyedHistogram(
+                  InfallibleTArray<KeyedAccumulation>&& aAccumulations) override;
 public:
   void SendGetFilesResponseAndForget(const nsID& aID,
                                      const GetFilesResponseResult& aResult);
 
 private:
 
   // If you add strong pointers to cycle collected objects here, be sure to
   // release these objects in ShutDownProcess.  See the comment there for more
--- a/dom/ipc/PBrowser.ipdl
+++ b/dom/ipc/PBrowser.ipdl
@@ -832,16 +832,24 @@ child:
     /**
      * Tell the child to print the current page with the given settings.
      *
      * @param aOuterWindowID the ID of the outer window to print
      * @param aPrintData the serialized settings to print with
      */
     async Print(uint64_t aOuterWindowID, PrintData aPrintData);
 
+    /**
+     * Update the child with the tab's current top-level native window handle.
+     * This is used by a11y objects who must expose their native window.
+     *
+     * @param aNewHandle The native window handle of the tab's top-level window.
+     */
+    async UpdateNativeWindowHandle(uintptr_t aNewHandle);
+
 /*
  * FIXME: write protocol!
 
 state LIVE:
     send LoadURL goto LIVE;
 //etc.
     send Destroy goto DYING;
 
--- a/dom/ipc/PContent.ipdl
+++ b/dom/ipc/PContent.ipdl
@@ -95,16 +95,18 @@ using mozilla::dom::ContentParentId from
 using mozilla::LayoutDeviceIntPoint from "Units.h";
 using struct LookAndFeelInt from "mozilla/widget/WidgetMessageUtils.h";
 using class mozilla::dom::MessagePort from "mozilla/dom/MessagePort.h";
 using class mozilla::dom::ipc::StructuredCloneData from "mozilla/dom/ipc/StructuredCloneData.h";
 using mozilla::DataStorageType from "ipc/DataStorageIPCUtils.h";
 using mozilla::DocShellOriginAttributes from "mozilla/ipc/BackgroundUtils.h";
 using struct mozilla::layers::TextureFactoryIdentifier from "mozilla/layers/CompositorTypes.h";
 using struct mozilla::dom::FlyWebPublishOptions from "mozilla/dom/FlyWebPublishOptionsIPCSerializer.h";
+using mozilla::Telemetry::Accumulation from "mozilla/TelemetryComms.h";
+using mozilla::Telemetry::KeyedAccumulation from "mozilla/TelemetryComms.h";
 
 union ChromeRegistryItem
 {
     ChromePackage;
     OverrideMapping;
     SubstitutionMapping;
 };
 
@@ -1211,16 +1213,22 @@ parent:
      async GetFilesRequest(nsID aID, nsString aDirectory, bool aRecursiveFlag);
      async DeleteGetFilesRequest(nsID aID);
 
      async StoreAndBroadcastBlobURLRegistration(nsCString url, PBlob blob,
                                                 Principal principal);
 
      async UnstoreAndBroadcastBlobURLUnregistration(nsCString url);
 
+    /**
+     * Messages for communicating child Telemetry to the parent process
+     */
+    async AccumulateChildHistogram(Accumulation[] accumulations);
+    async AccumulateChildKeyedHistogram(KeyedAccumulation[] accumulations);
+
 both:
      async AsyncMessage(nsString aMessage, CpowEntry[] aCpows,
                         Principal aPrincipal, ClonedMessageData aData);
 
     /**
      * Notify `push-subscription-modified` observers in the parent and child.
      */
     async NotifyPushSubscriptionModifiedObservers(nsCString scope,
--- a/dom/ipc/TabChild.cpp
+++ b/dom/ipc/TabChild.cpp
@@ -544,16 +544,19 @@ TabChild::TabChild(nsIContentChild* aMan
   , mDefaultScale(0)
   , mIsTransparent(false)
   , mIPCOpen(true)
   , mParentIsActive(false)
   , mDidSetRealShowInfo(false)
   , mDidLoadURLInit(false)
   , mAPZChild(nullptr)
   , mLayerObserverEpoch(0)
+#if defined(XP_WIN) && defined(ACCESSIBILITY)
+  , mNativeWindowHandle(0)
+#endif
 {
   // In the general case having the TabParent tell us if APZ is enabled or not
   // doesn't really work because the TabParent itself may not have a reference
   // to the owning widget during initialization. Instead we assume that this
   // TabChild corresponds to a widget type that would have APZ enabled, and just
   // check the other conditions necessary for enabling APZ.
   mAsyncPanZoomEnabled = gfxPlatform::AsyncPanZoomEnabled();
 
@@ -2530,16 +2533,27 @@ TabChild::RecvPrint(const uint64_t& aOut
     return true;
   }
 
 #endif
   return true;
 }
 
 bool
+TabChild::RecvUpdateNativeWindowHandle(const uintptr_t& aNewHandle)
+{
+#if defined(XP_WIN) && defined(ACCESSIBILITY)
+  mNativeWindowHandle = aNewHandle;
+  return true;
+#else
+  return false;
+#endif
+}
+
+bool
 TabChild::RecvDestroy()
 {
   MOZ_ASSERT(mDestroyed == false);
   mDestroyed = true;
 
   nsTArray<PContentPermissionRequestChild*> childArray =
       nsContentPermissionUtils::GetContentPermissionRequestChildById(GetTabId());
 
--- a/dom/ipc/TabChild.h
+++ b/dom/ipc/TabChild.h
@@ -585,16 +585,18 @@ public:
 
   virtual bool RecvHandledWindowedPluginKeyEvent(
                  const mozilla::NativeEventData& aKeyEventData,
                  const bool& aIsConsumed) override;
 
   virtual bool RecvPrint(const uint64_t& aOuterWindowID,
                          const PrintData& aPrintData) override;
 
+  virtual bool RecvUpdateNativeWindowHandle(const uintptr_t& aNewHandle) override;
+
   /**
    * Native widget remoting protocol for use with windowed plugins with e10s.
    */
   PPluginWidgetChild* AllocPPluginWidgetChild() override;
 
   bool DeallocPPluginWidgetChild(PPluginWidgetChild* aActor) override;
 
   nsresult CreatePluginWidget(nsIWidget* aParent, nsIWidget** aOut);
@@ -646,16 +648,20 @@ public:
   void SetAPZChild(layers::APZChild* aAPZChild)
   {
       mAPZChild = aAPZChild;
   }
 
   // Request that the docshell be marked as active.
   void ForcePaint(uint64_t aLayerObserverEpoch);
 
+#if defined(XP_WIN) && defined(ACCESSIBILITY)
+  uintptr_t GetNativeWindowHandle() const { return mNativeWindowHandle; }
+#endif
+
 protected:
   virtual ~TabChild();
 
   virtual PRenderFrameChild* AllocPRenderFrameChild() override;
 
   virtual bool DeallocPRenderFrameChild(PRenderFrameChild* aFrame) override;
 
   virtual bool RecvDestroy() override;
@@ -784,15 +790,20 @@ private:
   RefPtr<layers::IAPZCTreeManager> mApzcTreeManager;
   // APZChild clears this pointer from its destructor, so it shouldn't be a
   // dangling pointer.
   layers::APZChild* mAPZChild;
 
   // The most recently seen layer observer epoch in RecvSetDocShellIsActive.
   uint64_t mLayerObserverEpoch;
 
+#if defined(XP_WIN) && defined(ACCESSIBILITY)
+  // The handle associated with the native window that contains this tab
+  uintptr_t mNativeWindowHandle;
+#endif // defined(XP_WIN)
+
   DISALLOW_EVIL_CONSTRUCTORS(TabChild);
 };
 
 } // namespace dom
 } // namespace mozilla
 
 #endif // mozilla_dom_TabChild_h
--- a/dom/ipc/TabParent.cpp
+++ b/dom/ipc/TabParent.cpp
@@ -395,16 +395,27 @@ TabParent::SetOwnerElement(Element* aEle
   }
 
   if (mFrameElement) {
     bool useGlobalHistory =
       !mFrameElement->HasAttr(kNameSpaceID_None, nsGkAtoms::disableglobalhistory);
     Unused << SendSetUseGlobalHistory(useGlobalHistory);
   }
 
+#if defined(XP_WIN) && defined(ACCESSIBILITY)
+  if (!mIsDestroyed) {
+    uintptr_t newWindowHandle = 0;
+    if (nsCOMPtr<nsIWidget> widget = GetWidget()) {
+      newWindowHandle =
+        reinterpret_cast<uintptr_t>(widget->GetNativeData(NS_NATIVE_WINDOW));
+    }
+    Unused << SendUpdateNativeWindowHandle(newWindowHandle);
+  }
+#endif
+
   AddWindowListeners();
   TryCacheDPIAndScale();
 }
 
 void
 TabParent::AddWindowListeners()
 {
   if (mFrameElement && mFrameElement->OwnerDoc()) {
--- a/dom/media/MediaDecoderStateMachine.cpp
+++ b/dom/media/MediaDecoderStateMachine.cpp
@@ -218,17 +218,33 @@ public:
   virtual ~StateObject() {}
   virtual void Enter() {}; // Entry action.
   virtual void Exit() {};  // Exit action.
   virtual void Step() {}   // Perform a 'cycle' of this state object.
   virtual State GetState() const = 0;
 
   // Event handlers for various events.
   // Return true if the event is handled by this state object.
-  virtual bool HandleDormant(bool aDormant) { return false; }
+  virtual bool HandleDormant(bool aDormant)
+  {
+    if (!aDormant) {
+      return true;
+    }
+    mMaster->mQueuedSeek.mTarget =
+      SeekTarget(mMaster->mCurrentPosition,
+                 SeekTarget::Accurate,
+                 MediaDecoderEventVisibility::Suppressed);
+    // SeekJob asserts |mTarget.IsValid() == !mPromise.IsEmpty()| so we
+    // need to create the promise even it is not used at all.
+    RefPtr<MediaDecoder::SeekPromise> unused =
+      mMaster->mQueuedSeek.mPromise.Ensure(__func__);
+    SetState(DECODER_STATE_DORMANT);
+    return true;
+  }
+
   virtual bool HandleCDMProxyReady() { return false; }
 
 protected:
   using Master = MediaDecoderStateMachine;
   explicit StateObject(Master* aPtr) : mMaster(aPtr) {}
   TaskQueue* OwnerThread() const { return mMaster->mTaskQueue; }
   MediaResource* Resource() const { return mMaster->mResource; }
   MediaDecoderReaderWrapper* Reader() const { return mMaster->mReader; }
@@ -403,16 +419,25 @@ public:
     mMaster->Reset();
     mMaster->mReader->ReleaseResources();
   }
 
   State GetState() const override
   {
     return DECODER_STATE_DORMANT;
   }
+
+  bool HandleDormant(bool aDormant) override
+  {
+    if (!aDormant) {
+      // Exit dormant state.
+      SetState(DECODER_STATE_DECODING_METADATA);
+    }
+    return true;
+  }
 };
 
 class MediaDecoderStateMachine::DecodingFirstFrameState
   : public MediaDecoderStateMachine::StateObject
 {
 public:
   explicit DecodingFirstFrameState(Master* aPtr) : StateObject(aPtr) {}
 
@@ -454,16 +479,36 @@ class MediaDecoderStateMachine::SeekingS
 {
 public:
   explicit SeekingState(Master* aPtr) : StateObject(aPtr) {}
 
   State GetState() const override
   {
     return DECODER_STATE_SEEKING;
   }
+
+  bool HandleDormant(bool aDormant) override
+  {
+    if (!aDormant) {
+      return true;
+    }
+    MOZ_ASSERT(!mMaster->mQueuedSeek.Exists());
+    MOZ_ASSERT(mMaster->mCurrentSeek.Exists());
+    // Because both audio and video decoders are going to be reset in this
+    // method later, we treat a VideoOnly seek task as a normal Accurate
+    // seek task so that while it is resumed, both audio and video playback
+    // are handled.
+    if (mMaster->mCurrentSeek.mTarget.IsVideoOnly()) {
+      mMaster->mCurrentSeek.mTarget.SetType(SeekTarget::Accurate);
+      mMaster->mCurrentSeek.mTarget.SetVideoOnly(false);
+    }
+    mMaster->mQueuedSeek = Move(mMaster->mCurrentSeek);
+    SetState(DECODER_STATE_DORMANT);
+    return true;
+  }
 };
 
 class MediaDecoderStateMachine::BufferingState
   : public MediaDecoderStateMachine::StateObject
 {
 public:
   explicit BufferingState(Master* aPtr) : StateObject(aPtr) {}
 
@@ -525,16 +570,21 @@ public:
   {
     MOZ_DIAGNOSTIC_ASSERT(false, "Shouldn't escape the SHUTDOWN state.");
   }
 
   State GetState() const override
   {
     return DECODER_STATE_SHUTDOWN;
   }
+
+  bool HandleDormant(bool aDormant) override
+  {
+    return true;
+  }
 };
 
 #define INIT_WATCHABLE(name, val) \
   name(val, "MediaDecoderStateMachine::" #name)
 #define INIT_MIRROR(name, val) \
   name(mTaskQueue, val, "MediaDecoderStateMachine::" #name " (Mirror)")
 #define INIT_CANONICAL(name, val) \
   name(mTaskQueue, val, "MediaDecoderStateMachine::" #name " (Canonical)")
@@ -1312,30 +1362,43 @@ void MediaDecoderStateMachine::MaybeStar
 void
 MediaDecoderStateMachine::MaybeStartBuffering()
 {
   MOZ_ASSERT(OnTaskQueue());
   // Buffering makes senses only after decoding first frames.
   MOZ_ASSERT(mSentFirstFrameLoadedEvent);
   MOZ_ASSERT(mState == DECODER_STATE_DECODING);
 
-  if (mPlayState == MediaDecoder::PLAY_STATE_PLAYING &&
-      mResource->IsExpectingMoreData()) {
-    bool shouldBuffer;
-    if (mReader->UseBufferingHeuristics()) {
-      shouldBuffer = HasLowDecodedData(EXHAUSTED_DATA_MARGIN_USECS) &&
-                     (JustExitedQuickBuffering() || HasLowUndecodedData());
-    } else {
-      MOZ_ASSERT(mReader->IsWaitForDataSupported());
-      shouldBuffer = (OutOfDecodedAudio() && mReader->IsWaitingAudioData()) ||
-                     (OutOfDecodedVideo() && mReader->IsWaitingVideoData());
-    }
-    if (shouldBuffer) {
-      SetState(DECODER_STATE_BUFFERING);
-    }
+  // Don't enter buffering when MediaDecoder is not playing.
+  if (mPlayState != MediaDecoder::PLAY_STATE_PLAYING) {
+    return;
+  }
+
+  // Don't enter buffering while prerolling so that the decoder has a chance to
+  // enqueue some decoded data before we give up and start buffering.
+  if (!IsPlaying()) {
+    return;
+  }
+
+  // No more data to download. No need to enter buffering.
+  if (!mResource->IsExpectingMoreData()) {
+    return;
+  }
+
+  bool shouldBuffer;
+  if (mReader->UseBufferingHeuristics()) {
+    shouldBuffer = HasLowDecodedData(EXHAUSTED_DATA_MARGIN_USECS) &&
+                   (JustExitedQuickBuffering() || HasLowUndecodedData());
+  } else {
+    MOZ_ASSERT(mReader->IsWaitForDataSupported());
+    shouldBuffer = (OutOfDecodedAudio() && mReader->IsWaitingAudioData()) ||
+                   (OutOfDecodedVideo() && mReader->IsWaitingVideoData());
+  }
+  if (shouldBuffer) {
+    SetState(DECODER_STATE_BUFFERING);
   }
 }
 
 void MediaDecoderStateMachine::UpdatePlaybackPositionInternal(int64_t aTime)
 {
   MOZ_ASSERT(OnTaskQueue());
   SAMPLE_LOG("UpdatePlaybackPositionInternal(%lld)", aTime);
 
@@ -1483,61 +1546,17 @@ MediaDecoderStateMachine::DispatchSetDor
     this, &MediaDecoderStateMachine::SetDormant, aDormant);
   OwnerThread()->Dispatch(r.forget());
 }
 
 void
 MediaDecoderStateMachine::SetDormant(bool aDormant)
 {
   MOZ_ASSERT(OnTaskQueue());
-
-  if (IsShutdown()) {
-    return;
-  }
-
-  if (mStateObj->HandleDormant(aDormant)) {
-    return;
-  }
-
-  bool wasDormant = mState == DECODER_STATE_DORMANT;
-  if (wasDormant == aDormant) {
-    return;
-  }
-
-  DECODER_LOG("SetDormant=%d", aDormant);
-
-  // Enter dormant state.
-  if (aDormant) {
-    if (mState == DECODER_STATE_SEEKING) {
-      MOZ_ASSERT(!mQueuedSeek.Exists());
-      MOZ_ASSERT(mCurrentSeek.Exists());
-      // Because both audio and video decoders are going to be reset in this
-      // method later, we treat a VideoOnly seek task as a normal Accurate
-      // seek task so that while it is resumed, both audio and video playback
-      // are handled.
-      if (mCurrentSeek.mTarget.IsVideoOnly()) {
-        mCurrentSeek.mTarget.SetType(SeekTarget::Accurate);
-        mCurrentSeek.mTarget.SetVideoOnly(false);
-      }
-      mQueuedSeek = Move(mCurrentSeek);
-    } else {
-      mQueuedSeek.mTarget = SeekTarget(mCurrentPosition,
-                                       SeekTarget::Accurate,
-                                       MediaDecoderEventVisibility::Suppressed);
-      // SeekJob asserts |mTarget.IsValid() == !mPromise.IsEmpty()| so we
-      // need to create the promise even it is not used at all.
-      RefPtr<MediaDecoder::SeekPromise> unused = mQueuedSeek.mPromise.Ensure(__func__);
-    }
-
-    SetState(DECODER_STATE_DORMANT);
-    return;
-  }
-
-  // Exit dormant state.
-  SetState(DECODER_STATE_DECODING_METADATA);
+  mStateObj->HandleDormant(aDormant);
 }
 
 RefPtr<ShutdownPromise>
 MediaDecoderStateMachine::Shutdown()
 {
   MOZ_ASSERT(OnTaskQueue());
 
   SetState(DECODER_STATE_SHUTDOWN);
--- a/gfx/tests/reftest/reftest.list
+++ b/gfx/tests/reftest/reftest.list
@@ -1,9 +1,9 @@
 # 468496-1 will also detect bugs in video drivers.
 == 468496-1.html 468496-1-ref.html
-fuzzy-if(winWidget,175,443) == 611498-1.html 611498-ref.html
+fuzzy(175,443) == 611498-1.html 611498-ref.html
 skip-if(B2G) fuzzy-if(Android,8,1000) == 709477-1.html 709477-1-ref.html # bug 773482
 skip-if(!asyncPan) == 1086723.html 1086723-ref.html
 == 853889-1.html 853889-1-ref.html
 skip-if(Android) fuzzy-if(skiaContent,1,587) == 1143303-1.svg pass.svg
 fuzzy(100,30) == 1149923.html 1149923-ref.html # use fuzzy due to few distorted pixels caused by border-radius
 == 1131264-1.svg pass.svg
--- a/gfx/thebes/gfxPrefs.h
+++ b/gfx/thebes/gfxPrefs.h
@@ -533,16 +533,20 @@ private:
 
   // These affect how line scrolls from wheel events will be accelerated.
   DECL_GFX_PREF(Live, "mousewheel.acceleration.factor",        MouseWheelAccelerationFactor, int32_t, -1);
   DECL_GFX_PREF(Live, "mousewheel.acceleration.start",         MouseWheelAccelerationStart, int32_t, -1);
 
   // This affects whether events will be routed through APZ or not.
   DECL_GFX_PREF(Live, "mousewheel.system_scroll_override_on_root_content.enabled",
                                                                MouseWheelHasRootScrollDeltaOverride, bool, false);
+  DECL_GFX_PREF(Live, "mousewheel.system_scroll_override_on_root_content.horizontal.factor",
+                                                               MouseWheelRootScrollHorizontalFactor, int32_t, 0);
+  DECL_GFX_PREF(Live, "mousewheel.system_scroll_override_on_root_content.vertical.factor",
+                                                               MouseWheelRootScrollVerticalFactor, int32_t, 0);
   DECL_GFX_PREF(Live, "mousewheel.transaction.ignoremovedelay",MouseWheelIgnoreMoveDelayMs, int32_t, (int32_t)100);
   DECL_GFX_PREF(Live, "mousewheel.transaction.timeout",        MouseWheelTransactionTimeoutMs, int32_t, (int32_t)1500);
 
   DECL_GFX_PREF(Live, "nglayout.debug.widget_update_flashing", WidgetUpdateFlashing, bool, false);
 
   DECL_GFX_PREF(Live, "test.events.async.enabled",             TestEventsAsyncEnabled, bool, false);
   DECL_GFX_PREF(Live, "test.mousescroll",                      MouseScrollTestingEnabled, bool, false);
 
--- a/image/test/reftest/reftest-stylo.list
+++ b/image/test/reftest/reftest-stylo.list
@@ -32,17 +32,17 @@ skip-if(B2G) include pngsuite-transparen
 skip-if(B2G) include pngsuite-zlib/reftest-stylo.list        
 # bug 783632
 
 # Disabled, lots of intermittents here
 # BMP tests
 #skip-if(Android) include bmp/reftest-stylo.list
 
 # ICO tests
-skip-if(Android) include ico/reftest-stylo.list
+#skip-if(Android) include ico/reftest-stylo.list
 
 # JPEG tests
 # include jpeg/reftest-stylo.list
 
 # GIF tests
 # include gif/reftest-stylo.list
 
 # APNG tests
--- a/ipc/glue/MessageChannel.cpp
+++ b/ipc/glue/MessageChannel.cpp
@@ -483,16 +483,17 @@ MessageChannel::MessageChannel(MessageLi
     mDispatchingAsyncMessagePriority(0),
     mTransactionStack(nullptr),
     mTimedOutMessageSeqno(0),
     mTimedOutMessagePriority(0),
     mRemoteStackDepthGuess(false),
     mSawInterruptOutMsg(false),
     mIsWaitingForIncoming(false),
     mAbortOnError(false),
+    mNotifiedChannelDone(false),
     mFlags(REQUIRE_DEFAULT),
     mPeerPidSet(false),
     mPeerPid(-1)
 #if defined(MOZ_CRASHREPORTER) && defined(OS_WIN)
     , mPending(AnnotateAllocator<Message>(*this))
 #endif
 {
     MOZ_COUNT_CTOR(ipc::MessageChannel);
@@ -2076,16 +2077,23 @@ MessageChannel::NotifyMaybeChannelError(
         return;
     }
 
     Clear();
 
     // Oops, error!  Let the listener know about it.
     mChannelState = ChannelError;
 
+    // IPDL assumes these notifications do not fire twice, so we do not let
+    // that happen.
+    if (mNotifiedChannelDone) {
+      return;
+    }
+    mNotifiedChannelDone = true;
+
     // After this, the channel may be deleted.  Based on the premise that
     // mListener owns this channel, any calls back to this class that may
     // work with mListener should still work on living objects.
     mListener->OnChannelError();
 }
 
 void
 MessageChannel::OnNotifyMaybeChannelError()
@@ -2233,16 +2241,23 @@ MessageChannel::NotifyChannelClosed()
 {
     mMonitor->AssertNotCurrentThreadOwns();
 
     if (ChannelClosed != mChannelState)
         NS_RUNTIMEABORT("channel should have been closed!");
 
     Clear();
 
+    // IPDL assumes these notifications do not fire twice, so we do not let
+    // that happen.
+    if (mNotifiedChannelDone) {
+      return;
+    }
+    mNotifiedChannelDone = true;
+
     // OK, the IO thread just closed the channel normally.  Let the
     // listener know about it. After this point the channel may be
     // deleted.
     mListener->OnChannelClose();
 }
 
 void
 MessageChannel::DebugAbort(const char* file, int line, const char* cond,
--- a/ipc/glue/MessageChannel.h
+++ b/ipc/glue/MessageChannel.h
@@ -778,16 +778,20 @@ class MessageChannel : HasResultCodes
 #ifdef OS_WIN
     HANDLE mEvent;
 #endif
 
     // Should the channel abort the process from the I/O thread when
     // a channel error occurs?
     bool mAbortOnError;
 
+    // True if the listener has already been notified of a channel close or
+    // error.
+    bool mNotifiedChannelDone;
+
     // See SetChannelFlags
     ChannelFlags mFlags;
 
     // Task and state used to asynchronously notify channel has been connected
     // safely.  This is necessary to be able to cancel notification if we are
     // closed at the same time.
     RefPtr<RefCountedTask> mOnChannelConnectedTask;
     bool mPeerPidSet;
--- a/ipc/glue/ProtocolUtils.h
+++ b/ipc/glue/ProtocolUtils.h
@@ -504,16 +504,19 @@ public:
                           mMode == Transport::MODE_SERVER ? ParentSide : ChildSide)) {
             return false;
         }
         mValid = false;
         aActor->SetTransport(Move(t));
         return true;
     }
 
+    bool IsValid() const {
+        return mValid;
+    }
 
 private:
     friend struct IPC::ParamTraits<Endpoint<PFooSide>>;
 
     Endpoint(const Endpoint&) = delete;
     Endpoint& operator=(const Endpoint&) = delete;
 
     bool mValid;
@@ -644,17 +647,20 @@ struct ParamTraits<mozilla::ipc::ActorHa
 
 template<class PFooSide>
 struct ParamTraits<mozilla::ipc::Endpoint<PFooSide>>
 {
     typedef mozilla::ipc::Endpoint<PFooSide> paramType;
 
     static void Write(Message* aMsg, const paramType& aParam)
     {
-        MOZ_RELEASE_ASSERT(aParam.mValid);
+        IPC::WriteParam(aMsg, aParam.mValid);
+        if (!aParam.mValid) {
+            return;
+        }
 
         IPC::WriteParam(aMsg, static_cast<uint32_t>(aParam.mMode));
 
         // We duplicate the descriptor so that our own file descriptor remains
         // valid after the write. An alternative would be to set
         // aParam.mTransport.mValid to false, but that won't work because aParam
         // is const.
         mozilla::ipc::TransportDescriptor desc = mozilla::ipc::DuplicateDescriptor(aParam.mTransport);
@@ -663,17 +669,25 @@ struct ParamTraits<mozilla::ipc::Endpoin
         IPC::WriteParam(aMsg, aParam.mMyPid);
         IPC::WriteParam(aMsg, aParam.mOtherPid);
         IPC::WriteParam(aMsg, static_cast<uint32_t>(aParam.mProtocolId));
     }
 
     static bool Read(const Message* aMsg, PickleIterator* aIter, paramType* aResult)
     {
         MOZ_RELEASE_ASSERT(!aResult->mValid);
-        aResult->mValid = true;
+
+        if (!IPC::ReadParam(aMsg, aIter, &aResult->mValid)) {
+            return false;
+        }
+        if (!aResult->mValid) {
+            // Object is empty, but read succeeded.
+            return true;
+        }
+
         uint32_t mode, protocolId;
         if (!IPC::ReadParam(aMsg, aIter, &mode) ||
             !IPC::ReadParam(aMsg, aIter, &aResult->mTransport) ||
             !IPC::ReadParam(aMsg, aIter, &aResult->mMyPid) ||
             !IPC::ReadParam(aMsg, aIter, &aResult->mOtherPid) ||
             !IPC::ReadParam(aMsg, aIter, &protocolId)) {
             return false;
         }
--- a/ipc/ipdl/ipdl/lower.py
+++ b/ipc/ipdl/ipdl/lower.py
@@ -2379,17 +2379,20 @@ def _generateCxxUnion(ud):
         StmtExpr(callAssertSanity(uvar=othervar)),
         copyswitch,
         StmtExpr(ExprAssn(mtypevar, othertype))
     ])
     cls.addstmts([ copyctor, Whitespace.NL ])
 
     # ~Union()
     dtor = DestructorDefn(DestructorDecl(ud.name))
-    dtor.addstmt(StmtExpr(callMaybeDestroy(tnonevar)))
+    # The void cast prevents Coverity from complaining about missing return
+    # value checks.
+    dtor.addstmt(StmtExpr(ExprCast(callMaybeDestroy(tnonevar), Type.VOID,
+                                   static=1)))
     cls.addstmts([ dtor, Whitespace.NL ])
 
     # type()
     typemeth = MethodDefn(MethodDecl('type', ret=typetype,
                                      const=1, force_inline=1))
     typemeth.addstmt(StmtReturn(mtypevar))
     cls.addstmts([ typemeth, Whitespace.NL ])
 
@@ -2420,19 +2423,24 @@ def _generateCxxUnion(ud):
         case = StmtBlock()
         case.addstmts([
             maybeReconstruct(c, rhstypevar),
             StmtExpr(c.callOperatorEq(
                 ExprCall(ExprSelect(rhsvar, '.', c.getConstTypeName())))),
             StmtBreak()
         ])
         opeqswitch.addcase(CaseLabel(c.enum()), case)
-    opeqswitch.addcase(CaseLabel(tnonevar.name),
-                       StmtBlock([ StmtExpr(callMaybeDestroy(rhstypevar)),
-                                   StmtBreak() ]))
+    opeqswitch.addcase(
+        CaseLabel(tnonevar.name),
+        # The void cast prevents Coverity from complaining about missing return
+        # value checks.
+        StmtBlock([ StmtExpr(ExprCast(callMaybeDestroy(rhstypevar), Type.VOID,
+                                      static=1)),
+                    StmtBreak() ])
+    )
     opeqswitch.addcase(
         DefaultLabel(),
         StmtBlock([ _logicError('unreached'), StmtBreak() ]))
     opeq.addstmts([
         StmtExpr(callAssertSanity(uvar=rhsvar)),
         StmtDecl(Decl(typetype, rhstypevar.name), init=ud.callType(rhsvar)),
         opeqswitch,
         StmtExpr(ExprAssn(mtypevar, rhstypevar)),
--- a/js/src/jit/CodeGenerator.cpp
+++ b/js/src/jit/CodeGenerator.cpp
@@ -6069,38 +6069,44 @@ CodeGenerator::visitCreateArgumentsObjec
     Register callObj = ToRegister(lir->getCallObject());
     Register temp = ToRegister(lir->temp0());
     Label done;
 
     if (ArgumentsObject* templateObj = lir->mir()->templateObject()) {
         Register objTemp = ToRegister(lir->temp1());
         Register cxTemp = ToRegister(lir->temp2());
 
+        masm.Push(callObj);
+
         // Try to allocate an arguments object. This will leave the reserved
         // slots uninitialized, so it's important we don't GC until we
         // initialize these slots in ArgumentsObject::finishForIon.
         Label failure;
         masm.createGCObject(objTemp, temp, templateObj, gc::DefaultHeap, &failure,
                             /* initContents = */ false);
 
         masm.moveStackPtrTo(temp);
-        masm.addPtr(Imm32(frameSize()), temp);
+        masm.addPtr(Imm32(masm.framePushed()), temp);
 
         masm.setupUnalignedABICall(cxTemp);
         masm.loadJSContext(cxTemp);
         masm.passABIArg(cxTemp);
         masm.passABIArg(temp);
         masm.passABIArg(callObj);
         masm.passABIArg(objTemp);
 
         masm.callWithABI(JS_FUNC_TO_DATA_PTR(void*, ArgumentsObject::finishForIon));
-        masm.branchTestPtr(Assembler::Zero, ReturnReg, ReturnReg, masm.exceptionLabel());
+        masm.branchTestPtr(Assembler::Zero, ReturnReg, ReturnReg, &failure);
+
+        // Discard saved callObj on the stack.
+        masm.addToStackPtr(Imm32(sizeof(uintptr_t)));
         masm.jump(&done);
 
         masm.bind(&failure);
+        masm.Pop(callObj);
     }
 
     masm.moveStackPtrTo(temp);
     masm.addPtr(Imm32(frameSize()), temp);
 
     pushArg(callObj);
     pushArg(temp);
     callVM(NewIonArgumentsObjectInfo, lir);
--- a/js/src/vm/ArgumentsObject.cpp
+++ b/js/src/vm/ArgumentsObject.cpp
@@ -378,17 +378,19 @@ ArgumentsObject::finishForIon(JSContext*
     unsigned numActuals = frame->numActualArgs();
     unsigned numFormals = callee->nargs();
     unsigned numArgs = Max(numActuals, numFormals);
     unsigned numBytes = ArgumentsData::bytesRequired(numArgs);
 
     ArgumentsData* data =
         reinterpret_cast<ArgumentsData*>(AllocateObjectBuffer<uint8_t>(cx, obj, numBytes));
     if (!data) {
-        // Make the object safe for GC.
+        // Make the object safe for GC. Don't report OOM, the slow path will
+        // retry the allocation.
+        cx->recoverFromOutOfMemory();
         obj->initFixedSlot(DATA_SLOT, PrivateValue(nullptr));
         return nullptr;
     }
 
     data->numArgs = numArgs;
     data->rareData = nullptr;
 
     obj->initFixedSlot(INITIAL_LENGTH_SLOT, Int32Value(numActuals << PACKED_BITS_COUNT));
--- a/js/xpconnect/src/XPCCallContext.cpp
+++ b/js/xpconnect/src/XPCCallContext.cpp
@@ -194,16 +194,17 @@ void
 XPCCallContext::SystemIsBeingShutDown()
 {
     // XXX This is pretty questionable since the per thread cleanup stuff
     // can be making this call on one thread for call contexts on another
     // thread.
     NS_WARNING("Shutting Down XPConnect even through there is a live XPCCallContext");
     mXPCJSContext = nullptr;
     mState = SYSTEM_SHUTDOWN;
+    mSet = nullptr;
     mInterface = nullptr;
 
     if (mPrevCallContext)
         mPrevCallContext->SystemIsBeingShutDown();
 }
 
 XPCCallContext::~XPCCallContext()
 {
--- a/js/xpconnect/src/XPCInlines.h
+++ b/js/xpconnect/src/XPCInlines.h
@@ -441,28 +441,16 @@ XPCNativeSet::MatchesSetUpToInterface(co
         if (cur != (*pp2))
             return false;
         if (cur == iface)
             return true;
     }
     return false;
 }
 
-inline void XPCNativeSet::Mark()
-{
-    mMarked = 1;
-}
-
-#ifdef DEBUG
-inline void XPCNativeSet::ASSERT_NotMarked()
-{
-    MOZ_ASSERT(!IsMarked(), "bad");
-}
-#endif
-
 /***************************************************************************/
 
 inline
 JSObject* XPCWrappedNativeTearOff::GetJSObjectPreserveColor() const
 {
     return mJSObject.getPtr();
 }
 
--- a/js/xpconnect/src/XPCJSContext.cpp
+++ b/js/xpconnect/src/XPCJSContext.cpp
@@ -779,87 +779,25 @@ XPCJSContext::FinalizeCallback(JSFreeOp*
 
             break;
         }
         case JSFINALIZE_COLLECTION_END:
         {
             MOZ_ASSERT(!self->mGCIsRunning, "bad state");
             self->mGCIsRunning = true;
 
-            // We use this occasion to mark and sweep NativeInterfaces,
-            // NativeSets, and the WrappedNativeJSClasses...
-
-            // Do the marking...
-            XPCWrappedNativeScope::MarkAllWrappedNativesAndProtos();
-
-            // Mark the sets used in the call contexts. There is a small
-            // chance that a wrapper's set will change *while* a call is
-            // happening which uses that wrapper's old interfface set. So,
-            // we need to do this marking to avoid collecting those sets
-            // that might no longer be otherwise reachable from the wrappers
-            // or the wrapperprotos.
-
             // Skip this part if XPConnect is shutting down. We get into
             // bad locking problems with the thread iteration otherwise.
             if (!nsXPConnect::XPConnect()->IsShuttingDown()) {
 
                 // Mark those AutoMarkingPtr lists!
                 if (AutoMarkingPtr* roots = Get()->mAutoRoots)
                     roots->MarkAfterJSFinalizeAll();
-
-                XPCCallContext* ccxp = XPCJSContext::Get()->GetCallContext();
-                while (ccxp) {
-                    // Deal with the strictness of callcontext that
-                    // complains if you ask for a set when
-                    // it is in a state where the set could not
-                    // possibly be valid.
-                    if (ccxp->CanGetSet()) {
-                        XPCNativeSet* set = ccxp->GetSet();
-                        if (set)
-                            set->Mark();
-                    }
-                    ccxp = ccxp->GetPrevCallContext();
-                }
             }
 
-            // Do the sweeping. During a zone GC, only WrappedNativeProtos in
-            // collected zones will be marked. Therefore, some reachable
-            // NativeInterfaces will not be marked, so it is not safe to sweep
-            // them. We still need to unmark them, since the ones pointed to by
-            // WrappedNativeProtos in a zone being collected will be marked.
-            //
-            // Ideally, if NativeInterfaces from different zones were kept
-            // separate, we could sweep only the ones belonging to zones being
-            // collected. Currently, though, NativeInterfaces are shared between
-            // zones. This ought to be fixed.
-            bool doSweep = !isZoneGC;
-
-            if (doSweep) {
-                for (auto i = self->mClassInfo2NativeSetMap->Iter(); !i.Done(); i.Next()) {
-                    auto entry = static_cast<ClassInfo2NativeSetMap::Entry*>(i.Get());
-                    if (!entry->value->IsMarked())
-                        i.Remove();
-                }
-            }
-
-            for (auto i = self->mNativeSetMap->Iter(); !i.Done(); i.Next()) {
-                auto entry = static_cast<NativeSetMap::Entry*>(i.Get());
-                XPCNativeSet* set = entry->key_value;
-                if (set->IsMarked()) {
-                    set->Unmark();
-                } else if (doSweep) {
-                    XPCNativeSet::DestroyInstance(set);
-                    i.Remove();
-                }
-            }
-
-#ifdef DEBUG
-            XPCWrappedNativeScope::ASSERT_NoInterfaceSetsAreMarked();
-#endif
-
             // Now we are going to recycle any unused WrappedNativeTearoffs.
             // We do this by iterating all the live callcontexts
             // and marking the tearoffs in use. And then we
             // iterate over all the WrappedNative wrappers and sweep their
             // tearoffs.
             //
             // This allows us to perhaps minimize the growth of the
             // tearoffs. And also makes us not hold references to interfaces
--- a/js/xpconnect/src/XPCMaps.cpp
+++ b/js/xpconnect/src/XPCMaps.cpp
@@ -197,24 +197,51 @@ IID2NativeInterfaceMap::SizeOfIncludingT
     }
     return n;
 }
 
 /***************************************************************************/
 // implement ClassInfo2NativeSetMap...
 
 // static
+bool ClassInfo2NativeSetMap::Entry::Match(const PLDHashEntryHdr* aEntry,
+                                          const void* aKey)
+{
+    return static_cast<const Entry*>(aEntry)->key == aKey;
+}
+
+// static
+void ClassInfo2NativeSetMap::Entry::Clear(PLDHashTable* aTable,
+                                          PLDHashEntryHdr* aEntry)
+{
+    auto entry = static_cast<Entry*>(aEntry);
+    NS_RELEASE(entry->value);
+
+    entry->key = nullptr;
+    entry->value = nullptr;
+}
+
+const PLDHashTableOps ClassInfo2NativeSetMap::Entry::sOps =
+{
+    PLDHashTable::HashVoidPtrKeyStub,
+    Match,
+    PLDHashTable::MoveEntryStub,
+    Clear,
+    nullptr
+};
+
+// static
 ClassInfo2NativeSetMap*
 ClassInfo2NativeSetMap::newMap(int length)
 {
     return new ClassInfo2NativeSetMap(length);
 }
 
 ClassInfo2NativeSetMap::ClassInfo2NativeSetMap(int length)
-  : mTable(PLDHashTable::StubOps(), sizeof(Entry), length)
+  : mTable(&ClassInfo2NativeSetMap::Entry::sOps, sizeof(Entry), length)
 {
 }
 
 size_t
 ClassInfo2NativeSetMap::ShallowSizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf)
 {
     size_t n = mallocSizeOf(this);
     n += mTable.ShallowSizeOfExcludingThis(mallocSizeOf);
--- a/js/xpconnect/src/XPCMaps.h
+++ b/js/xpconnect/src/XPCMaps.h
@@ -273,17 +273,22 @@ private:
 /*************************/
 
 class ClassInfo2NativeSetMap
 {
 public:
     struct Entry : public PLDHashEntryHdr
     {
         nsIClassInfo* key;
-        XPCNativeSet* value;
+        XPCNativeSet* value; // strong reference
+        static const PLDHashTableOps sOps;
+
+    private:
+        static bool Match(const PLDHashEntryHdr* aEntry, const void* aKey);
+        static void Clear(PLDHashTable* aTable, PLDHashEntryHdr* aEntry);
     };
 
     static ClassInfo2NativeSetMap* newMap(int length);
 
     inline XPCNativeSet* Find(nsIClassInfo* info)
     {
         auto entry = static_cast<Entry*>(mTable.Search(info));
         return entry ? entry->value : nullptr;
@@ -293,30 +298,28 @@ public:
     {
         NS_PRECONDITION(info,"bad param");
         auto entry = static_cast<Entry*>(mTable.Add(info, mozilla::fallible));
         if (!entry)
             return nullptr;
         if (entry->key)
             return entry->value;
         entry->key = info;
-        entry->value = set;
+        NS_ADDREF(entry->value = set);
         return set;
     }
 
     inline void Remove(nsIClassInfo* info)
     {
         NS_PRECONDITION(info,"bad param");
         mTable.Remove(info);
     }
 
     inline uint32_t Count() { return mTable.EntryCount(); }
 
-    PLDHashTable::Iterator Iter() { return mTable.Iter(); }
-
     // ClassInfo2NativeSetMap holds pointers to *some* XPCNativeSets.
     // So we don't want to count those XPCNativeSets, because they are better
     // counted elsewhere (i.e. in XPCJSContext::mNativeSetMap, which holds
     // pointers to *all* XPCNativeSets).  Hence the "Shallow".
     size_t ShallowSizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf);
 
 private:
     ClassInfo2NativeSetMap();    // no implementation
--- a/js/xpconnect/src/XPCWrappedNative.cpp
+++ b/js/xpconnect/src/XPCWrappedNative.cpp
@@ -426,25 +426,25 @@ XPCWrappedNative::GetNewOrUsed(xpcObject
             return NS_ERROR_FAILURE;
 
         wrapper = new XPCWrappedNative(helper.forgetCanonical(), proto);
     } else {
         RefPtr<XPCNativeInterface> iface = Interface;
         if (!iface)
             iface = XPCNativeInterface::GetISupports();
 
-        AutoMarkingNativeSetPtr set(cx);
         XPCNativeSetKey key(iface);
-        set = XPCNativeSet::GetNewOrUsed(&key);
+        RefPtr<XPCNativeSet> set =
+            XPCNativeSet::GetNewOrUsed(&key);
 
         if (!set)
             return NS_ERROR_FAILURE;
 
-        wrapper =
-            new XPCWrappedNative(helper.forgetCanonical(), Scope, set);
+        wrapper = new XPCWrappedNative(helper.forgetCanonical(), Scope,
+                                       set.forget());
     }
 
     MOZ_ASSERT(!xpc::WrapperFactory::IsXrayWrapper(parent),
                "Xray wrapper being used to parent XPCWrappedNative?");
 
     // We use an AutoMarkingPtr here because it is possible for JS gc to happen
     // after we have Init'd the wrapper but *before* we add it to the hashtable.
     // This would cause the mSet to get collected and we'd later crash. I've
@@ -561,29 +561,29 @@ XPCWrappedNative::XPCWrappedNative(alrea
 
     MOZ_ASSERT(mMaybeProto, "bad ctor param");
     MOZ_ASSERT(mSet, "bad ctor param");
 }
 
 // This ctor is used if this object will NOT have a proto.
 XPCWrappedNative::XPCWrappedNative(already_AddRefed<nsISupports>&& aIdentity,
                                    XPCWrappedNativeScope* aScope,
-                                   XPCNativeSet* aSet)
+                                   already_AddRefed<XPCNativeSet>&& aSet)
 
     : mMaybeScope(TagScope(aScope)),
       mSet(aSet),
       mScriptableInfo(nullptr)
 {
     MOZ_ASSERT(NS_IsMainThread());
 
     mIdentity = aIdentity;
     mFlatJSObject.setFlags(FLAT_JS_OBJECT_VALID);
 
     MOZ_ASSERT(aScope, "bad ctor param");
-    MOZ_ASSERT(aSet, "bad ctor param");
+    MOZ_ASSERT(mSet, "bad ctor param");
 }
 
 XPCWrappedNative::~XPCWrappedNative()
 {
     Destroy();
 }
 
 void
@@ -1009,26 +1009,24 @@ public:
 private:
     RootedObject mOldReflector;
     RootedObject mNewReflector;
 };
 
 bool
 XPCWrappedNative::ExtendSet(XPCNativeInterface* aInterface)
 {
-    AutoJSContext cx;
-
     if (!mSet->HasInterface(aInterface)) {
-        AutoMarkingNativeSetPtr newSet(cx);
         XPCNativeSetKey key(mSet, aInterface);
-        newSet = XPCNativeSet::GetNewOrUsed(&key);
+        RefPtr<XPCNativeSet> newSet =
+            XPCNativeSet::GetNewOrUsed(&key);
         if (!newSet)
             return false;
 
-        mSet = newSet;
+        mSet = newSet.forget();
     }
     return true;
 }
 
 XPCWrappedNativeTearOff*
 XPCWrappedNative::FindTearOff(XPCNativeInterface* aInterface,
                               bool needJSObject /* = false */,
                               nsresult* pError /* = nullptr */)
@@ -2159,17 +2157,17 @@ NS_IMETHODIMP XPCWrappedNative::DebugDum
             else
                 XPC_LOG_ALWAYS(("mMaybeProto @ %x", proto));
         } else
             XPC_LOG_ALWAYS(("Scope @ %x", GetScope()));
 
         if (depth && mSet)
             mSet->DebugDump(depth);
         else
-            XPC_LOG_ALWAYS(("mSet @ %x", mSet));
+            XPC_LOG_ALWAYS(("mSet @ %x", mSet.get()));
 
         XPC_LOG_ALWAYS(("mFlatJSObject of %x", mFlatJSObject.getPtr()));
         XPC_LOG_ALWAYS(("mIdentity of %x", mIdentity.get()));
         XPC_LOG_ALWAYS(("mScriptableInfo @ %x", mScriptableInfo));
 
         if (depth && mScriptableInfo) {
             XPC_LOG_INDENT();
             XPC_LOG_ALWAYS(("mScriptable @ %x", mScriptableInfo->GetCallback()));
--- a/js/xpconnect/src/XPCWrappedNativeInfo.cpp
+++ b/js/xpconnect/src/XPCWrappedNativeInfo.cpp
@@ -457,69 +457,73 @@ XPCNativeSetKey::Hash() const
     }
 
     return h;
 }
 
 /***************************************************************************/
 // XPCNativeSet
 
+XPCNativeSet::~XPCNativeSet()
+{
+    // Remove |this| before we clear the interfaces to ensure that the
+    // hashtable look up is correct.
+    XPCJSContext::Get()->GetNativeSetMap()->Remove(this);
+
+    for (int i = 0; i < mInterfaceCount; i++) {
+        NS_RELEASE(mInterfaces[i]);
+    }
+}
+
 // static
-XPCNativeSet*
+already_AddRefed<XPCNativeSet>
 XPCNativeSet::GetNewOrUsed(const nsIID* iid)
 {
-    AutoJSContext cx;
-    AutoMarkingNativeSetPtr set(cx);
-
     RefPtr<XPCNativeInterface> iface =
         XPCNativeInterface::GetNewOrUsed(iid);
     if (!iface)
         return nullptr;
 
     XPCNativeSetKey key(iface);
 
     XPCJSContext* xpccx = XPCJSContext::Get();
     NativeSetMap* map = xpccx->GetNativeSetMap();
     if (!map)
         return nullptr;
 
-    set = map->Find(&key);
+    RefPtr<XPCNativeSet> set = map->Find(&key);
 
     if (set)
-        return set;
+        return set.forget();
 
     set = NewInstance({iface.forget()});
     if (!set)
         return nullptr;
 
     if (!map->AddNew(&key, set)) {
         NS_ERROR("failed to add our set!");
-        DestroyInstance(set);
         set = nullptr;
     }
 
-    return set;
+    return set.forget();
 }
 
 // static
-XPCNativeSet*
+already_AddRefed<XPCNativeSet>
 XPCNativeSet::GetNewOrUsed(nsIClassInfo* classInfo)
 {
-    AutoJSContext cx;
-    AutoMarkingNativeSetPtr set(cx);
     XPCJSContext* xpccx = XPCJSContext::Get();
-
     ClassInfo2NativeSetMap* map = xpccx->GetClassInfo2NativeSetMap();
     if (!map)
         return nullptr;
 
-    set = map->Find(classInfo);
+    RefPtr<XPCNativeSet> set = map->Find(classInfo);
 
     if (set)
-        return set;
+        return set.forget();
 
     nsIID** iidArray = nullptr;
     uint32_t iidCount = 0;
 
     if (NS_FAILED(classInfo->GetInterfaces(&iidCount, &iidArray))) {
         // Note: I'm making it OK for this call to fail so that one can add
         // nsIClassInfo to classes implemented in script without requiring this
         // method to be implemented.
@@ -562,24 +566,22 @@ XPCNativeSet::GetNewOrUsed(nsIClassInfo*
                 if (!map2)
                     goto out;
 
                 XPCNativeSetKey key(set);
 
                 XPCNativeSet* set2 = map2->Add(&key, set);
                 if (!set2) {
                     NS_ERROR("failed to add our set!");
-                    DestroyInstance(set);
                     set = nullptr;
                     goto out;
                 }
                 // It is okay to find an existing entry here because
                 // we did not look for one before we called Add().
                 if (set2 != set) {
-                    DestroyInstance(set);
                     set = set2;
                 }
             }
         } else
             set = GetNewOrUsed(&NS_GET_IID(nsISupports));
     } else
         set = GetNewOrUsed(&NS_GET_IID(nsISupports));
 
@@ -591,111 +593,107 @@ XPCNativeSet::GetNewOrUsed(nsIClassInfo*
         MOZ_ASSERT(set2, "failed to add our set!");
         MOZ_ASSERT(set2 == set, "hashtables inconsistent!");
     }
 
 out:
     if (iidArray)
         NS_FREE_XPCOM_ALLOCATED_POINTER_ARRAY(iidCount, iidArray);
 
-    return set;
+    return set.forget();
 }
 
 // static
 void
 XPCNativeSet::ClearCacheEntryForClassInfo(nsIClassInfo* classInfo)
 {
     XPCJSContext* xpccx = nsXPConnect::GetContextInstance();
     ClassInfo2NativeSetMap* map = xpccx->GetClassInfo2NativeSetMap();
     if (map)
         map->Remove(classInfo);
 }
 
 // static
-XPCNativeSet*
+already_AddRefed<XPCNativeSet>
 XPCNativeSet::GetNewOrUsed(XPCNativeSetKey* key)
 {
-    AutoJSContext cx;
-    AutoMarkingNativeSetPtr set(cx);
-    XPCJSContext* xpccx = XPCJSContext::Get();
-    NativeSetMap* map = xpccx->GetNativeSetMap();
+    NativeSetMap* map = XPCJSContext::Get()->GetNativeSetMap();
     if (!map)
         return nullptr;
 
-    set = map->Find(key);
+    RefPtr<XPCNativeSet> set = map->Find(key);
 
     if (set)
-        return set;
+        return set.forget();
 
     if (key->GetBaseSet())
         set = NewInstanceMutate(key);
     else
         set = NewInstance({key->GetAddition()});
 
     if (!set)
         return nullptr;
 
     if (!map->AddNew(key, set)) {
         NS_ERROR("failed to add our set!");
-        DestroyInstance(set);
         set = nullptr;
     }
 
-    return set;
+    return set.forget();
 }
 
 // static
-XPCNativeSet*
+already_AddRefed<XPCNativeSet>
 XPCNativeSet::GetNewOrUsed(XPCNativeSet* firstSet,
                            XPCNativeSet* secondSet,
                            bool preserveFirstSetOrder)
 {
     // Figure out how many interfaces we'll need in the new set.
     uint32_t uniqueCount = firstSet->mInterfaceCount;
     for (uint32_t i = 0; i < secondSet->mInterfaceCount; ++i) {
         if (!firstSet->HasInterface(secondSet->mInterfaces[i]))
             uniqueCount++;
     }
 
     // If everything in secondSet was a duplicate, we can just use the first
     // set.
     if (uniqueCount == firstSet->mInterfaceCount)
-        return firstSet;
+        return RefPtr<XPCNativeSet>(firstSet).forget();
 
     // If the secondSet is just a superset of the first, we can use it provided
     // that the caller doesn't care about ordering.
     if (!preserveFirstSetOrder && uniqueCount == secondSet->mInterfaceCount)
-        return secondSet;
+        return RefPtr<XPCNativeSet>(secondSet).forget();
 
     // Ok, darn. Now we have to make a new set.
     //
     // It would be faster to just create the new set all at once, but that
     // would involve wrangling with some pretty hairy code - especially since
     // a lot of stuff assumes that sets are created by adding one interface to an
     // existing set. So let's just do the slow and easy thing and hope that the
     // above optimizations handle the common cases.
-    XPCNativeSet* currentSet = firstSet;
+    RefPtr<XPCNativeSet> currentSet = firstSet;
     for (uint32_t i = 0; i < secondSet->mInterfaceCount; ++i) {
         XPCNativeInterface* iface = secondSet->mInterfaces[i];
         if (!currentSet->HasInterface(iface)) {
             // Create a new augmented set, inserting this interface at the end.
             XPCNativeSetKey key(currentSet, iface);
             currentSet = XPCNativeSet::GetNewOrUsed(&key);
             if (!currentSet)
                 return nullptr;
         }
     }
 
     // We've got the union set. Hand it back to the caller.
     MOZ_ASSERT(currentSet->mInterfaceCount == uniqueCount);
-    return currentSet;
+    return currentSet.forget();
 }
 
 // static
-XPCNativeSet*
+already_AddRefed<XPCNativeSet>
 XPCNativeSet::NewInstance(nsTArray<RefPtr<XPCNativeInterface>>&& array)
 {
     if (array.Length() == 0)
         return nullptr;
 
     // We impose the invariant:
     // "All sets have exactly one nsISupports interface and it comes first."
     // This is the place where we impose that rule - even if given inputs
@@ -710,17 +708,17 @@ XPCNativeSet::NewInstance(nsTArray<RefPt
     }
 
     // Use placement new to create an object with the right amount of space
     // to hold the members array
     int size = sizeof(XPCNativeSet);
     if (slots > 1)
         size += (slots - 1) * sizeof(XPCNativeInterface*);
     void* place = new char[size];
-    XPCNativeSet* obj = new(place) XPCNativeSet();
+    RefPtr<XPCNativeSet> obj = new(place) XPCNativeSet();
 
     // Stick the nsISupports in front and skip additional nsISupport(s)
     XPCNativeInterface** outp = (XPCNativeInterface**) &obj->mInterfaces;
     uint16_t memberCount = 1;   // for the one member in nsISupports
 
     NS_ADDREF(*(outp++) = isup);
 
     for (auto key = array.begin(); key != array.end(); key++) {
@@ -728,50 +726,50 @@ XPCNativeSet::NewInstance(nsTArray<RefPt
         if (isup == cur)
             continue;
         memberCount += cur->GetMemberCount();
         *(outp++) = cur.forget().take();
     }
     obj->mMemberCount = memberCount;
     obj->mInterfaceCount = slots;
 
-    return obj;
+    return obj.forget();
 }
 
 // static
-XPCNativeSet*
+already_AddRefed<XPCNativeSet>
 XPCNativeSet::NewInstanceMutate(XPCNativeSetKey* key)
 {
     XPCNativeSet* otherSet = key->GetBaseSet();
     XPCNativeInterface* newInterface = key->GetAddition();
 
     MOZ_ASSERT(otherSet);
 
     if (!newInterface)
         return nullptr;
 
     // Use placement new to create an object with the right amount of space
     // to hold the members array
     int size = sizeof(XPCNativeSet);
     size += otherSet->mInterfaceCount * sizeof(XPCNativeInterface*);
     void* place = new char[size];
-    XPCNativeSet* obj = new(place) XPCNativeSet();
+    RefPtr<XPCNativeSet> obj = new(place) XPCNativeSet();
 
     obj->mMemberCount = otherSet->GetMemberCount() +
         newInterface->GetMemberCount();
     obj->mInterfaceCount = otherSet->mInterfaceCount + 1;
 
     XPCNativeInterface** src = otherSet->mInterfaces;
     XPCNativeInterface** dest = obj->mInterfaces;
     for (uint16_t i = 0; i < otherSet->mInterfaceCount; i++) {
         NS_ADDREF(*dest++ = *src++);
     }
     NS_ADDREF(*dest++ = newInterface);
 
-    return obj;
+    return obj.forget();
 }
 
 // static
 void
 XPCNativeSet::DestroyInstance(XPCNativeSet* inst)
 {
     inst->~XPCNativeSet();
     delete [] (char*) inst;
--- a/js/xpconnect/src/XPCWrappedNativeProto.cpp
+++ b/js/xpconnect/src/XPCWrappedNativeProto.cpp
@@ -12,17 +12,17 @@
 using namespace mozilla;
 
 #ifdef DEBUG
 int32_t XPCWrappedNativeProto::gDEBUG_LiveProtoCount = 0;
 #endif
 
 XPCWrappedNativeProto::XPCWrappedNativeProto(XPCWrappedNativeScope* Scope,
                                              nsIClassInfo* ClassInfo,
-                                             XPCNativeSet* Set)
+                                             already_AddRefed<XPCNativeSet>&& Set)
     : mScope(Scope),
       mJSProtoObject(nullptr),
       mClassInfo(ClassInfo),
       mSet(Set),
       mScriptableInfo(nullptr)
 {
     // This native object lives as long as its associated JSObject - killed
     // by finalization of the JSObject (or explicitly if Init fails).
@@ -161,22 +161,21 @@ XPCWrappedNativeProto::GetNewOrUsed(XPCW
     AutoMarkingWrappedNativeProtoPtr proto(cx);
     ClassInfo2WrappedNativeProtoMap* map = nullptr;
 
     map = scope->GetWrappedNativeProtoMap();
     proto = map->Find(classInfo);
     if (proto)
         return proto;
 
-    AutoMarkingNativeSetPtr set(cx);
-    set = XPCNativeSet::GetNewOrUsed(classInfo);
+    RefPtr<XPCNativeSet> set = XPCNativeSet::GetNewOrUsed(classInfo);
     if (!set)
         return nullptr;
 
-    proto = new XPCWrappedNativeProto(scope, classInfo, set);
+    proto = new XPCWrappedNativeProto(scope, classInfo, set.forget());
 
     if (!proto || !proto->Init(scriptableCreateInfo, callPostCreatePrototype)) {
         delete proto.get();
         return nullptr;
     }
 
     map->Add(classInfo, proto);
 
@@ -188,17 +187,17 @@ XPCWrappedNativeProto::DebugDump(int16_t
 {
 #ifdef DEBUG
     depth-- ;
     XPC_LOG_ALWAYS(("XPCWrappedNativeProto @ %x", this));
     XPC_LOG_INDENT();
         XPC_LOG_ALWAYS(("gDEBUG_LiveProtoCount is %d", gDEBUG_LiveProtoCount));
         XPC_LOG_ALWAYS(("mScope @ %x", mScope));
         XPC_LOG_ALWAYS(("mJSProtoObject @ %x", mJSProtoObject.get()));
-        XPC_LOG_ALWAYS(("mSet @ %x", mSet));
+        XPC_LOG_ALWAYS(("mSet @ %x", mSet.get()));
         XPC_LOG_ALWAYS(("mScriptableInfo @ %x", mScriptableInfo));
         if (depth && mScriptableInfo) {
             XPC_LOG_INDENT();
             XPC_LOG_ALWAYS(("mScriptable @ %x", mScriptableInfo->GetCallback()));
             XPC_LOG_ALWAYS(("mFlags of %x", (uint32_t)mScriptableInfo->GetFlags()));
             XPC_LOG_ALWAYS(("mJSClass @ %x", mScriptableInfo->GetJSClass()));
             XPC_LOG_OUTDENT();
         }
--- a/js/xpconnect/src/XPCWrappedNativeScope.cpp
+++ b/js/xpconnect/src/XPCWrappedNativeScope.cpp
@@ -571,52 +571,16 @@ XPCWrappedNativeScope::UpdateWeakPointer
         if (cur)
             prev = cur;
         cur = next;
     }
 }
 
 // static
 void
-XPCWrappedNativeScope::MarkAllWrappedNativesAndProtos()
-{
-    for (XPCWrappedNativeScope* cur = gScopes; cur; cur = cur->mNext) {
-        for (auto i = cur->mWrappedNativeMap->Iter(); !i.Done(); i.Next()) {
-            auto entry = static_cast<Native2WrappedNativeMap::Entry*>(i.Get());
-            entry->value->Mark();
-        }
-        // We need to explicitly mark all the protos too because some protos may be
-        // alive in the hashtable but not currently in use by any wrapper
-        for (auto i = cur->mWrappedNativeProtoMap->Iter(); !i.Done(); i.Next()) {
-            auto entry = static_cast<ClassInfo2WrappedNativeProtoMap::Entry*>(i.Get());
-            entry->value->Mark();
-        }
-    }
-}
-
-#ifdef DEBUG
-// static
-void
-XPCWrappedNativeScope::ASSERT_NoInterfaceSetsAreMarked()
-{
-    for (XPCWrappedNativeScope* cur = gScopes; cur; cur = cur->mNext) {
-        for (auto i = cur->mWrappedNativeMap->Iter(); !i.Done(); i.Next()) {
-            auto entry = static_cast<Native2WrappedNativeMap::Entry*>(i.Get());
-            entry->value->ASSERT_SetsNotMarked();
-        }
-        for (auto i = cur->mWrappedNativeProtoMap->Iter(); !i.Done(); i.Next()) {
-            auto entry = static_cast<ClassInfo2WrappedNativeProtoMap::Entry*>(i.Get());
-            entry->value->ASSERT_SetNotMarked();
-        }
-    }
-}
-#endif
-
-// static
-void
 XPCWrappedNativeScope::SweepAllWrappedNativeTearOffs()
 {
     for (XPCWrappedNativeScope* cur = gScopes; cur; cur = cur->mNext) {
         for (auto i = cur->mWrappedNativeMap->Iter(); !i.Done(); i.Next()) {
             auto entry = static_cast<Native2WrappedNativeMap::Entry*>(i.Get());
             entry->value->SweepTearOffs();
         }
     }
--- a/js/xpconnect/src/xpcprivate.h
+++ b/js/xpconnect/src/xpcprivate.h
@@ -788,17 +788,17 @@ private:
 
     XPCCallContext*                 mPrevCallContext;
 
     XPCWrappedNative*               mWrapper;
     XPCWrappedNativeTearOff*        mTearOff;
 
     XPCNativeScriptableInfo*        mScriptableInfo;
 
-    XPCNativeSet*                   mSet;
+    RefPtr<XPCNativeSet>            mSet;
     RefPtr<XPCNativeInterface>      mInterface;
     XPCNativeMember*                mMember;
 
     JS::RootedId                    mName;
     bool                            mStaticMemberIsLocal;
 
     unsigned                        mArgc;
     JS::Value*                      mArgv;
@@ -924,24 +924,16 @@ public:
         if (mXrayExpandos.initialized())
             mXrayExpandos.trace(trc);
     }
 
     static void
     SuspectAllWrappers(XPCJSContext* cx, nsCycleCollectionNoteRootCallback& cb);
 
     static void
-    MarkAllWrappedNativesAndProtos();
-
-#ifdef DEBUG
-    static void
-    ASSERT_NoInterfaceSetsAreMarked();
-#endif
-
-    static void
     SweepAllWrappedNativeTearOffs();
 
     static void
     UpdateWeakPointersAfterGC(XPCJSContext* cx);
 
     static void
     KillDyingScopes();
 
@@ -1276,17 +1268,17 @@ private:
     XPCNativeMember            mMembers[1]; // always last - object sized for array
 };
 
 /***************************************************************************/
 // XPCNativeSetKey is used to key a XPCNativeSet in a NativeSetMap.
 // It represents a new XPCNativeSet we are considering constructing, without
 // requiring that the set actually be built.
 
-class XPCNativeSetKey final
+class MOZ_STACK_CLASS XPCNativeSetKey final
 {
 public:
     // This represents an existing set |baseSet|.
     explicit XPCNativeSetKey(XPCNativeSet* baseSet)
         : mBaseSet(baseSet), mAddition(nullptr)
     {
         MOZ_ASSERT(baseSet);
     }
@@ -1309,40 +1301,43 @@ public:
     XPCNativeSet* GetBaseSet() const {return mBaseSet;}
     XPCNativeInterface* GetAddition() const {return mAddition;}
 
     PLDHashNumber Hash() const;
 
     // Allow shallow copy
 
 private:
-    XPCNativeSet* mBaseSet;
-    XPCNativeInterface* mAddition;
+    RefPtr<XPCNativeSet> mBaseSet;
+    RefPtr<XPCNativeInterface> mAddition;
 };
 
 /***************************************************************************/
 // XPCNativeSet represents an ordered collection of XPCNativeInterface pointers.
 
 class XPCNativeSet final
 {
   public:
-    static XPCNativeSet* GetNewOrUsed(const nsIID* iid);
-    static XPCNativeSet* GetNewOrUsed(nsIClassInfo* classInfo);
-    static XPCNativeSet* GetNewOrUsed(XPCNativeSetKey* key);
+    NS_INLINE_DECL_REFCOUNTING_WITH_DESTROY(XPCNativeSet,
+                                            DestroyInstance(this))
+
+    static already_AddRefed<XPCNativeSet> GetNewOrUsed(const nsIID* iid);
+    static already_AddRefed<XPCNativeSet> GetNewOrUsed(nsIClassInfo* classInfo);
+    static already_AddRefed<XPCNativeSet> GetNewOrUsed(XPCNativeSetKey* key);
 
     // This generates a union set.
     //
     // If preserveFirstSetOrder is true, the elements from |firstSet| come first,
     // followed by any non-duplicate items from |secondSet|. If false, the same
     // algorithm is applied; but if we detect that |secondSet| is a superset of
     // |firstSet|, we return |secondSet| without worrying about whether the
     // ordering might differ from |firstSet|.
-    static XPCNativeSet* GetNewOrUsed(XPCNativeSet* firstSet,
-                                      XPCNativeSet* secondSet,
-                                      bool preserveFirstSetOrder);
+    static already_AddRefed<XPCNativeSet> GetNewOrUsed(XPCNativeSet* firstSet,
+                                                       XPCNativeSet* secondSet,
+                                                       bool preserveFirstSetOrder);
 
     static void ClearCacheEntryForClassInfo(nsIClassInfo* classInfo);
 
     inline bool FindMember(jsid name, XPCNativeMember** pMember,
                            uint16_t* pInterfaceIndex) const;
 
     inline bool FindMember(jsid name, XPCNativeMember** pMember,
                            RefPtr<XPCNativeInterface>* pInterface) const;
@@ -1372,60 +1367,35 @@ class XPCNativeSet final
     }
 
     XPCNativeInterface* GetInterfaceAt(uint16_t i)
         {MOZ_ASSERT(i < mInterfaceCount, "bad index"); return mInterfaces[i];}
 
     inline bool MatchesSetUpToInterface(const XPCNativeSet* other,
                                           XPCNativeInterface* iface) const;
 
-    inline void Mark();
-
-    // NOP. This is just here to make the AutoMarkingPtr code compile.
-    inline void TraceJS(JSTracer* trc) {}
-    inline void AutoTrace(JSTracer* trc) {}
-
-  public:
-    void Unmark() {
-        mMarked = 0;
-    }
-    bool IsMarked() const {
-        return !!mMarked;
-    }
-
-#ifdef DEBUG
-    inline void ASSERT_NotMarked();
-#endif
-
     void DebugDump(int16_t depth);
 
-    static void DestroyInstance(XPCNativeSet* inst);
-
     size_t SizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf);
 
   protected:
-    static XPCNativeSet* NewInstance(nsTArray<RefPtr<XPCNativeInterface>>&& array);
-    static XPCNativeSet* NewInstanceMutate(XPCNativeSetKey* key);
+    static already_AddRefed<XPCNativeSet> NewInstance(nsTArray<RefPtr<XPCNativeInterface>>&& array);
+    static already_AddRefed<XPCNativeSet> NewInstanceMutate(XPCNativeSetKey* key);
+
     XPCNativeSet()
-      : mMemberCount(0), mInterfaceCount(0), mMarked(0)
-    {
-        MOZ_COUNT_CTOR(XPCNativeSet);
-    }
-    ~XPCNativeSet() {
-        for (int i = 0; i < mInterfaceCount; i++) {
-            NS_RELEASE(mInterfaces[i]);
-        }
-        MOZ_COUNT_DTOR(XPCNativeSet);
-    }
+      : mMemberCount(0), mInterfaceCount(0)
+    {}
+    ~XPCNativeSet();
     void* operator new(size_t, void* p) CPP_THROW_NEW {return p;}
 
+    static void DestroyInstance(XPCNativeSet* inst);
+
   private:
     uint16_t                mMemberCount;
-    uint16_t                mInterfaceCount : 15;
-    uint16_t                mMarked : 1;
+    uint16_t                mInterfaceCount;
     // Always last - object sized for array.
     // These are strong references.
     XPCNativeInterface*     mInterfaces[1];
 };
 
 /***************************************************************************/
 // XPCNativeScriptableFlags is a wrapper class that holds the flags returned
 // from calls to nsIXPCScriptable::GetScriptableFlags(). It has convenience
@@ -1648,68 +1618,59 @@ public:
     void DebugDump(int16_t depth);
 
     void TraceSelf(JSTracer* trc) {
         if (mJSProtoObject)
             mJSProtoObject.trace(trc, "XPCWrappedNativeProto::mJSProtoObject");
     }
 
     void TraceInside(JSTracer* trc) {
-        if (trc->isMarkingTracer()) {
-            mSet->Mark();
-        }
-
         GetScope()->TraceSelf(trc);
     }
 
     void TraceJS(JSTracer* trc) {
         TraceSelf(trc);
         TraceInside(trc);
     }
 
     void WriteBarrierPre(JSContext* cx)
     {
         if (JS::IsIncrementalBarrierNeeded(cx) && mJSProtoObject)
             mJSProtoObject.writeBarrierPre(cx);
     }
 
     // NOP. This is just here to make the AutoMarkingPtr code compile.
+    void Mark() const {}
     inline void AutoTrace(JSTracer* trc) {}
 
-    void Mark() const {mSet->Mark();}
-
-#ifdef DEBUG
-    void ASSERT_SetNotMarked() const {mSet->ASSERT_NotMarked();}
-#endif
-
     ~XPCWrappedNativeProto();
 
 protected:
     // disable copy ctor and assignment
     XPCWrappedNativeProto(const XPCWrappedNativeProto& r) = delete;
     XPCWrappedNativeProto& operator= (const XPCWrappedNativeProto& r) = delete;
 
     // hide ctor
     XPCWrappedNativeProto(XPCWrappedNativeScope* Scope,
                           nsIClassInfo* ClassInfo,
-                          XPCNativeSet* Set);
+                          already_AddRefed<XPCNativeSet>&& Set);
 
     bool Init(const XPCNativeScriptableCreateInfo* scriptableCreateInfo,
               bool callPostCreatePrototype);
 
 private:
 #ifdef DEBUG
     static int32_t gDEBUG_LiveProtoCount;
 #endif
 
 private:
     XPCWrappedNativeScope*   mScope;
     JS::ObjectPtr            mJSProtoObject;
     nsCOMPtr<nsIClassInfo>   mClassInfo;
-    XPCNativeSet*            mSet;
+    RefPtr<XPCNativeSet>     mSet;
     XPCNativeScriptableInfo* mScriptableInfo;
 };
 
 /***********************************************/
 // XPCWrappedNativeTearOff represents the info needed to make calls to one
 // interface on the underlying native object of a XPCWrappedNative.
 
 class XPCWrappedNativeTearOff final
@@ -1851,17 +1812,17 @@ public:
      */
     JSObject*
     GetFlatJSObjectPreserveColor() const {return mFlatJSObject;}
 
     XPCNativeSet*
     GetSet() const {return mSet;}
 
     void
-    SetSet(XPCNativeSet* set) {mSet = set;}
+    SetSet(already_AddRefed<XPCNativeSet> set) {mSet = set;}
 
     static XPCWrappedNative* Get(JSObject* obj) {
         MOZ_ASSERT(IS_WN_REFLECTOR(obj));
         return (XPCWrappedNative*)js::GetObjectPrivate(obj);
     }
 
 private:
     inline void
@@ -1926,26 +1887,19 @@ public:
 
     inline bool HasInterfaceNoQI(const nsIID& iid);
 
     XPCWrappedNativeTearOff* FindTearOff(XPCNativeInterface* aInterface,
                                          bool needJSObject = false,
                                          nsresult* pError = nullptr);
     XPCWrappedNativeTearOff* FindTearOff(const nsIID& iid);
 
-    void Mark() const
-    {
-        mSet->Mark();
-        if (HasProto()) GetProto()->Mark();
-    }
+    void Mark() const {}
 
     inline void TraceInside(JSTracer* trc) {
-        if (trc->isMarkingTracer()) {
-            mSet->Mark();
-        }
         if (HasProto())
             GetProto()->TraceSelf(trc);
         else
             GetScope()->TraceSelf(trc);
         if (mFlatJSObject && JS_IsGlobalObject(mFlatJSObject))
         {
             xpc::TraceXPCGlobal(trc, mFlatJSObject);
         }
@@ -1964,22 +1918,16 @@ public:
     }
 
     static void Trace(JSTracer* trc, JSObject* obj);
 
     void AutoTrace(JSTracer* trc) {
         TraceSelf(trc);
     }
 
-#ifdef DEBUG
-    void ASSERT_SetsNotMarked() const
-        {mSet->ASSERT_NotMarked();
-         if (HasProto()){GetProto()->ASSERT_SetNotMarked();}}
-#endif
-
     inline void SweepTearOffs();
 
     // Returns a string that shuld be free'd using JS_smprintf_free (or null).
     char* ToString(XPCWrappedNativeTearOff* to = nullptr) const;
 
     static void GatherProtoScriptableCreateInfo(nsIClassInfo* classInfo,
                                                 XPCNativeScriptableCreateInfo& sciProto);
 
@@ -1994,17 +1942,17 @@ protected:
 
     // This ctor is used if this object will have a proto.
     XPCWrappedNative(already_AddRefed<nsISupports>&& aIdentity,
                      XPCWrappedNativeProto* aProto);
 
     // This ctor is used if this object will NOT have a proto.
     XPCWrappedNative(already_AddRefed<nsISupports>&& aIdentity,
                      XPCWrappedNativeScope* aScope,
-                     XPCNativeSet* aSet);
+                     already_AddRefed<XPCNativeSet>&& aSet);
 
     virtual ~XPCWrappedNative();
     void Destroy();
 
     void UpdateScriptableInfo(XPCNativeScriptableInfo* si);
 
 private:
     enum {
@@ -2030,17 +1978,17 @@ public:
                                                                            XPCNativeScriptableCreateInfo& sciWrapper);
 
 private:
     union
     {
         XPCWrappedNativeScope* mMaybeScope;
         XPCWrappedNativeProto* mMaybeProto;
     };
-    XPCNativeSet* mSet;
+    RefPtr<XPCNativeSet> mSet;
     JS::TenuredHeap<JSObject*> mFlatJSObject;
     XPCNativeScriptableInfo* mScriptableInfo;
     XPCWrappedNativeTearOff mFirstTearOff;
 };
 
 /***************************************************************************
 ****************************************************************************
 *
@@ -2876,17 +2824,16 @@ class TypedAutoMarkingPtr : public AutoM
         if (mPtr)
             mPtr->Mark();
     }
 
   private:
     T* mPtr;
 };
 
-typedef TypedAutoMarkingPtr<XPCNativeSet> AutoMarkingNativeSetPtr;
 typedef TypedAutoMarkingPtr<XPCWrappedNative> AutoMarkingWrappedNativePtr;
 typedef TypedAutoMarkingPtr<XPCWrappedNativeTearOff> AutoMarkingWrappedNativeTearOffPtr;
 typedef TypedAutoMarkingPtr<XPCWrappedNativeProto> AutoMarkingWrappedNativeProtoPtr;
 
 /***************************************************************************/
 namespace xpc {
 // Allocates a string that grants all access ("AllAccess")
 char*
--- a/js/xpconnect/wrappers/WrapperFactory.cpp
+++ b/js/xpconnect/wrappers/WrapperFactory.cpp
@@ -308,22 +308,22 @@ WrapperFactory::PrepareForWrapping(JSCon
     // This could be a problem for chrome code that passes XPCOM objects
     // across compartments, because the effects of QI would disappear across
     // compartments.
     //
     // So whenever we pull an XPCWN across compartments in this manner, we
     // give the destination object the union of the two native sets. We try
     // to do this cleverly in the common case to avoid too much overhead.
     XPCWrappedNative* newwn = XPCWrappedNative::Get(obj);
-    XPCNativeSet* unionSet = XPCNativeSet::GetNewOrUsed(newwn->GetSet(),
-                                                        wn->GetSet(), false);
+    RefPtr<XPCNativeSet> unionSet = XPCNativeSet::GetNewOrUsed(newwn->GetSet(),
+                                                               wn->GetSet(), false);
     if (!unionSet) {
         return;
     }
-    newwn->SetSet(unionSet);
+    newwn->SetSet(unionSet.forget());
 
     retObj.set(waive ? WaiveXray(cx, obj) : obj);
 }
 
 #ifdef DEBUG
 static void
 DEBUG_CheckUnwrapSafety(HandleObject obj, const js::Wrapper* handler,
                         JSCompartment* origin, JSCompartment* target)
--- a/layout/reftests/bugs/reftest-stylo.list
+++ b/layout/reftests/bugs/reftest-stylo.list
@@ -1068,17 +1068,17 @@ skip-if((B2G&&browserIsRemote)||Mulet) =
 == 403656-1.html 403656-1.html
 == 403656-2.html 403656-2.html
 == 403656-3.html 403656-3.html
 fails skip == 403656-4.html 403656-4.html
 == 403656-5.html 403656-5.html
 #== 403657-1.html 403657-1.html
 # Fails depending on the fonts...
 == 403733-1.html 403733-1.html
-== 403962-1.xhtml 403962-1.xhtml
+skip == 403962-1.xhtml 403962-1.xhtml
 == 404030-1.html 404030-1.html
 == 404030-1-notref.html 404030-1-notref.html
 == 404030-1-notref2.html 404030-1-notref2.html
 fuzzy-if(skiaContent,2,4) == 404123-1.html 404123-1.html
 == 404123-2.html 404123-2.html
 == 404123-3.html 404123-3.html
 # may fail "randomly" on OS X, doesn't seem to be rendering usefully anyhow - bug 602469
 random-if(cocoaWidget) skip-if((B2G&&browserIsRemote)||Mulet) HTTP(..) == 404149-1.xul 404149-1.xul
--- a/layout/reftests/css-display/reftest-stylo.list
+++ b/layout/reftests/css-display/reftest-stylo.list
@@ -4,17 +4,17 @@
 
 fuzzy-if(Android,8,604) pref(layout.css.display-contents.enabled,true) == display-contents-acid.html display-contents-acid.html
 random pref(layout.css.display-contents.enabled,true) == display-contents-acid-dyn-1.html display-contents-acid-dyn-1.html
 random pref(layout.css.display-contents.enabled,true) == display-contents-acid-dyn-2.html display-contents-acid-dyn-2.html
 random pref(layout.css.display-contents.enabled,true) == display-contents-acid-dyn-3.html display-contents-acid-dyn-3.html
 pref(layout.css.display-contents.enabled,true) == display-contents-generated-content.html display-contents-generated-content.html
 pref(layout.css.display-contents.enabled,true) == display-contents-generated-content-2.html display-contents-generated-content-2.html
 pref(layout.css.display-contents.enabled,true) == display-contents-style-inheritance-1.html display-contents-style-inheritance-1.html
-pref(layout.css.display-contents.enabled,true) == display-contents-style-inheritance-1-stylechange.html display-contents-style-inheritance-1-stylechange.html
+skip pref(layout.css.display-contents.enabled,true) == display-contents-style-inheritance-1-stylechange.html display-contents-style-inheritance-1-stylechange.html
 skip pref(layout.css.display-contents.enabled,true) fuzzy-if(winWidget,12,100) == display-contents-style-inheritance-1-dom-mutations.html display-contents-style-inheritance-1-dom-mutations.html
 pref(layout.css.display-contents.enabled,true) == display-contents-tables.xhtml display-contents-tables.xhtml
 pref(layout.css.display-contents.enabled,true) == display-contents-tables-2.xhtml display-contents-tables-2.xhtml
 pref(layout.css.display-contents.enabled,true) == display-contents-tables-3.xhtml display-contents-tables-3.xhtml
 pref(layout.css.display-contents.enabled,true) == display-contents-visibility-hidden.html display-contents-visibility-hidden.html
 pref(layout.css.display-contents.enabled,true) == display-contents-visibility-hidden-2.html display-contents-visibility-hidden-2.html
 random pref(layout.css.display-contents.enabled,true) == display-contents-495385-2d.html display-contents-495385-2d.html
 skip-if(B2G||Mulet) fuzzy-if(Android,7,3935) pref(layout.css.display-contents.enabled,true) == display-contents-xbl.xhtml display-contents-xbl.xhtml
--- a/layout/reftests/css-grid/reftest-stylo.list
+++ b/layout/reftests/css-grid/reftest-stylo.list
@@ -35,20 +35,20 @@ skip == grid-abspos-items-010.html grid-
 skip == grid-abspos-items-015.html grid-abspos-items-015.html
 == grid-order-abspos-items-001.html grid-order-abspos-items-001.html
 == grid-order-placement-auto-001.html grid-order-placement-auto-001.html
 fuzzy-if(skiaContent,1,200) == grid-order-placement-definite-001.html grid-order-placement-definite-001.html
 skip-if(Android) == grid-placement-definite-implicit-001.html grid-placement-definite-implicit-001.html
 == grid-placement-definite-implicit-002.html grid-placement-definite-implicit-002.html
 skip-if(Android) fuzzy-if(winWidget,1,32) == grid-placement-auto-implicit-001.html grid-placement-auto-implicit-001.html
 == grid-placement-abspos-implicit-001.html grid-placement-abspos-implicit-001.html
-pref(layout.css.vertical-text.enabled,true) == rtl-grid-placement-definite-001.html rtl-grid-placement-definite-001.html
-pref(layout.css.vertical-text.enabled,true) == rtl-grid-placement-auto-row-sparse-001.html rtl-grid-placement-auto-row-sparse-001.html
-pref(layout.css.vertical-text.enabled,true) == vlr-grid-placement-auto-row-sparse-001.html vlr-grid-placement-auto-row-sparse-001.html
-pref(layout.css.vertical-text.enabled,true) == vrl-grid-placement-auto-row-sparse-001.html vrl-grid-placement-auto-row-sparse-001.html
+== rtl-grid-placement-definite-001.html rtl-grid-placement-definite-001.html
+== rtl-grid-placement-auto-row-sparse-001.html rtl-grid-placement-auto-row-sparse-001.html
+== vlr-grid-placement-auto-row-sparse-001.html vlr-grid-placement-auto-row-sparse-001.html
+== vrl-grid-placement-auto-row-sparse-001.html vrl-grid-placement-auto-row-sparse-001.html
 == grid-relpos-items-001.html grid-relpos-items-001.html
 == grid-item-sizing-percent-001.html grid-item-sizing-percent-001.html
 == grid-item-sizing-px-001.html grid-item-sizing-px-001.html
 == grid-item-dir-001.html grid-item-dir-001.html
 fuzzy-if(winWidget,70,130) fuzzy-if(cocoaWidget,85,180) == grid-col-max-sizing-max-content-001.html grid-col-max-sizing-max-content-001.html
 fuzzy-if(winWidget,70,130) fuzzy-if(cocoaWidget,85,180) == grid-col-max-sizing-max-content-002.html grid-col-max-sizing-max-content-002.html
 == grid-min-max-content-sizing-001.html grid-min-max-content-sizing-001.html
 == grid-min-max-content-sizing-002.html grid-min-max-content-sizing-002.html
--- a/layout/reftests/css-ruby/reftest-stylo.list
+++ b/layout/reftests/css-ruby/reftest-stylo.list
@@ -43,17 +43,17 @@ skip load nested-ruby-1.html
 == no-transform.html no-transform.html
 == relative-positioning-1.html relative-positioning-1.html
 == relative-positioning-2.html relative-positioning-2.html
 == ruby-align-1.html ruby-align-1.html
 == ruby-align-1a.html ruby-align-1a.html
 == ruby-align-2.html ruby-align-2.html
 == ruby-align-2a.html ruby-align-2a.html
 == ruby-position-horizontal.html ruby-position-horizontal.html
-pref(layout.css.vertical-text.enabled,true) == ruby-position-vertical-lr.html ruby-position-vertical-lr.html
-pref(layout.css.vertical-text.enabled,true) == ruby-position-vertical-rl.html ruby-position-vertical-rl.html
+== ruby-position-vertical-lr.html ruby-position-vertical-lr.html
+== ruby-position-vertical-rl.html ruby-position-vertical-rl.html
 == ruby-reflow-1-opaqueruby.html ruby-reflow-1-opaqueruby.html
 fuzzy-if(/^Windows\x20NT\x2010\.0/.test(http.oscpu),13,1) == ruby-reflow-1-transparentruby.html ruby-reflow-1-transparentruby.html
 == ruby-span-1.html ruby-span-1.html
 == ruby-whitespace-1.html ruby-whitespace-1.html
 == ruby-whitespace-2.html ruby-whitespace-2.html
 == bug1181890.html bug1181890.html
 == bug1181890.html bug1181890.html
--- a/layout/reftests/floats/reftest-stylo.list
+++ b/layout/reftests/floats/reftest-stylo.list
@@ -54,17 +54,17 @@ fails fuzzy-if(skiaContent,1,12000) == f
 == bfc-displace-4.html bfc-displace-4.html
 == bfc-shrink-1.html bfc-shrink-1.html
 
 # Testcases that involve vertical writing mode.
 #
 # XXX The default-preferences setting here can be removed after the
 #     pref has been made true by default for all channels (bug 1138384).
 
-default-preferences pref(layout.css.vertical-text.enabled,true)
+
 
 == float-in-rtl-vlr-1a.html float-in-rtl-vlr-1a.html
 == float-in-rtl-vlr-1b.html float-in-rtl-vlr-1b.html
 == float-in-rtl-vlr-1c.html float-in-rtl-vlr-1c.html
 == float-in-rtl-vlr-1d.html float-in-rtl-vlr-1d.html
 == float-in-rtl-vlr-2a.html float-in-rtl-vlr-2a.html
 == float-in-rtl-vlr-2b.html float-in-rtl-vlr-2b.html
 == float-in-rtl-vlr-2c.html float-in-rtl-vlr-2c.html
--- a/layout/reftests/forms/input/number/reftest-stylo.list
+++ b/layout/reftests/forms/input/number/reftest-stylo.list
@@ -9,18 +9,18 @@ skip skip-if(Android||B2G||Mulet) == not
 # Initial mulet triage: parity with B2G/B2G Desktop
 # only valid on Android/B2G where type=number looks the same as type=text
 skip-if(!Android&&!B2G&&!Mulet) == number-same-as-text-unthemed.html number-same-as-text-unthemed.html
 # Initial mulet triage: parity with B2G/B2G Desktop
 
 # should look the same as type=text, except for the spin box
 skip == number-similar-to-text-unthemed.html number-similar-to-text-unthemed.html
 skip == number-similar-to-text-unthemed-rtl.html number-similar-to-text-unthemed-rtl.html
-skip pref(layout.css.vertical-text.enabled,true) == number-similar-to-text-unthemed-vertical-lr.html number-similar-to-text-unthemed-vertical-lr.html
-skip pref(layout.css.vertical-text.enabled,true) == number-similar-to-text-unthemed-vertical-rl.html number-similar-to-text-unthemed-vertical-rl.html
+skip == number-similar-to-text-unthemed-vertical-lr.html number-similar-to-text-unthemed-vertical-lr.html
+skip == number-similar-to-text-unthemed-vertical-rl.html number-similar-to-text-unthemed-vertical-rl.html
 
 # dynamic type changes:
 fuzzy-if(/^Windows\x20NT\x205\.1/.test(http.oscpu),64,4) fuzzy-if(cocoaWidget,63,4) fuzzy-if(skiaContent,2,5) == to-number-from-other-type-unthemed-1.html to-number-from-other-type-unthemed-1.html
 # skip fuzzy-if(skiaContent,2,5) == from-number-to-other-type-unthemed-1.html from-number-to-other-type-unthemed-1.html
 
 # dynamic value changes:
 # skip fuzzy-if(skiaContent,2,13) == show-value.html show-value.html
 
--- a/layout/reftests/forms/input/range/reftest-stylo.list
+++ b/layout/reftests/forms/input/range/reftest-stylo.list
@@ -32,17 +32,17 @@ skip == reset-value.html reset-value.htm
 fails-if(B2G||Mulet||Android) == moz-range-progress-1.html moz-range-progress-1.html
 # Initial mulet triage: parity with B2G/B2G Desktop
 == moz-range-progress-2.html moz-range-progress-2.html
 == moz-range-progress-3.html moz-range-progress-3.html
 
 # Tests for block and inline orientation in combination with writing-mode
 # XXX Remove default-preferences setting here after bug 1138384 makes
 #     it the default for all channels
-default-preferences pref(layout.css.vertical-text.enabled,true)
+
 == range-orient-horizontal.html range-orient-horizontal.html
 == range-orient-horizontal.html range-orient-horizontal.html
 == range-orient-block.html range-orient-block.html
 == range-orient-inline.html range-orient-inline.html
 == range-vlr.html range-vlr.html
 == range-vlr-orient-block.html range-vlr-orient-block.html
 == range-vlr-orient-inline.html range-vlr-orient-inline.html
 == range-vlr-orient-horizontal.html range-vlr-orient-horizontal.html
--- a/layout/reftests/forms/meter/reftest-stylo.list
+++ b/layout/reftests/forms/meter/reftest-stylo.list
@@ -27,17 +27,17 @@ include default-style/reftest-stylo.list
 # Tests for bugs:
 == block-invalidate.html block-invalidate.html
 == in-cells.html in-cells.html
 == max-height.html max-height.html
 
 # Tests for block and inline orientation in combination with writing-mode
 # XXX Remove default-preferences setting here after bug 1138384 makes
 #     it the default for all channels
-default-preferences pref(layout.css.vertical-text.enabled,true)
+
 == meter-orient-vertical.html meter-orient-vertical.html
 == meter-orient-horizontal.html meter-orient-horizontal.html
 == meter-orient-block.html meter-orient-block.html
 == meter-orient-inline.html meter-orient-inline.html
 == meter-vlr.html meter-vlr.html
 == meter-vlr-orient-block.html meter-vlr-orient-block.html
 == meter-vlr-orient-inline.html meter-vlr-orient-inline.html
 == meter-vlr-orient-horizontal.html meter-vlr-orient-horizontal.html
--- a/layout/reftests/forms/progress/reftest-stylo.list
+++ b/layout/reftests/forms/progress/reftest-stylo.list
@@ -26,17 +26,17 @@ skip-if(B2G||Mulet) == bar-pseudo-elemen
 # Tests for bugs:
 == block-invalidate.html block-invalidate.html
 == in-cells.html in-cells.html
 == max-height.html max-height.html
 
 # Tests for block and inline orientation in combination with writing-mode
 # XXX Remove default-preferences setting here after bug 1138384 makes
 #     it the default for all channels
-default-preferences pref(layout.css.vertical-text.enabled,true)
+
 == progress-orient-horizontal.html progress-orient-horizontal.html
 skip == progress-orient-vertical.html progress-orient-vertical.html
 # only OS X currently has direction-dependent rendering here
 == progress-orient-block.html progress-orient-block.html
 == progress-orient-inline.html progress-orient-inline.html
 == progress-vlr.html progress-vlr.html
 == progress-vlr-orient-block.html progress-vlr-orient-block.html
 == progress-vlr-orient-inline.html progress-vlr-orient-inline.html
--- a/layout/reftests/svg/text/reftest-stylo.list
+++ b/layout/reftests/svg/text/reftest-stylo.list
@@ -127,17 +127,17 @@ fuzzy-if(/^Windows\x20NT\x2010\.0/.test(
 == textLength-4.svg textLength-4.svg
 == textLength-5.svg textLength-5.svg
 == textLength-6.svg textLength-6.svg
 
 # text-shadow
 == text-shadow.svg text-shadow.svg
 
 # vertical text
-pref(layout.css.vertical-text.enabled,true) == vertical-01.svg vertical-01.svg
+== vertical-01.svg vertical-01.svg
 
 # tests for ignoring various properties
 == ignore-border.svg ignore-border.svg
 == ignore-display.svg ignore-display.svg
 == ignore-float.svg ignore-float.svg
 == ignore-float-first-letter.svg ignore-float-first-letter.svg
 == ignore-position.svg ignore-position.svg
 == ignore-margin.svg ignore-margin.svg
--- a/layout/reftests/text-overflow/reftest-stylo.list
+++ b/layout/reftests/text-overflow/reftest-stylo.list
@@ -39,16 +39,16 @@ skip-if(B2G||Mulet) HTTP(..) == single-v
 # Initial mulet triage: parity with B2G/B2G Desktop
 fails skip-if(B2G||Mulet) fuzzy-if(gtkWidget,10,2) HTTP(..) == atomic-under-marker.html atomic-under-marker.html
 # Initial mulet triage: parity with B2G/B2G Desktop
 skip-if(Android||B2G||Mulet) fuzzy-if(asyncPan&&!layersGPUAccelerated,102,12352) HTTP(..) == xulscroll.html xulscroll.html
 # Initial mulet triage: parity with B2G/B2G Desktop
 HTTP(..) == combobox-zoom.html combobox-zoom.html
 
 # The vertical-text pref setting can be removed after bug 1138384 lands
-pref(layout.css.vertical-text.enabled,true) == vertical-decorations-1.html vertical-decorations-1.html
-pref(layout.css.vertical-text.enabled,true) == vertical-decorations-2.html vertical-decorations-2.html
-pref(layout.css.vertical-text.enabled,true) == vertical-decorations-1.html vertical-decorations-1.html
-pref(layout.css.vertical-text.enabled,true) == vertical-decorations-2.html vertical-decorations-2.html
-pref(layout.css.vertical-text.enabled,true) == vertical-decorations-3.html vertical-decorations-3.html
-pref(layout.css.vertical-text.enabled,true) == vertical-decorations-4.html vertical-decorations-4.html
-pref(layout.css.vertical-text.enabled,true) == vertical-decorations-3.html vertical-decorations-3.html
-pref(layout.css.vertical-text.enabled,true) == vertical-decorations-4.html vertical-decorations-4.html
+== vertical-decorations-1.html vertical-decorations-1.html
+== vertical-decorations-2.html vertical-decorations-2.html
+== vertical-decorations-1.html vertical-decorations-1.html
+== vertical-decorations-2.html vertical-decorations-2.html
+== vertical-decorations-3.html vertical-decorations-3.html
+== vertical-decorations-4.html vertical-decorations-4.html
+== vertical-decorations-3.html vertical-decorations-3.html
+== vertical-decorations-4.html vertical-decorations-4.html
--- a/layout/reftests/w3c-css/submitted/flexbox/reftest-stylo.list
+++ b/layout/reftests/w3c-css/submitted/flexbox/reftest-stylo.list
@@ -190,17 +190,17 @@ random == flexbox-mbp-horiz-002b.xhtml f
 == flexbox-whitespace-handling-002.xhtml flexbox-whitespace-handling-002.xhtml
 
 # Tests for flex containers with pseudo-elements
 == flexbox-with-pseudo-elements-001.html flexbox-with-pseudo-elements-001.html
 == flexbox-with-pseudo-elements-002.html flexbox-with-pseudo-elements-002.html
 == flexbox-with-pseudo-elements-003.html flexbox-with-pseudo-elements-003.html
 
 # Tests for combined influence of 'writing-mode' & 'direction' on flex axes
-test-pref(layout.css.vertical-text.enabled,true) == flexbox-writing-mode-001.html flexbox-writing-mode-001.html
-test-pref(layout.css.vertical-text.enabled,true) == flexbox-writing-mode-002.html flexbox-writing-mode-002.html
-test-pref(layout.css.vertical-text.enabled,true) == flexbox-writing-mode-003.html flexbox-writing-mode-003.html
-test-pref(layout.css.vertical-text.enabled,true) == flexbox-writing-mode-004.html flexbox-writing-mode-004.html
-test-pref(layout.css.vertical-text.enabled,true) == flexbox-writing-mode-005.html flexbox-writing-mode-005.html
-test-pref(layout.css.vertical-text.enabled,true) == flexbox-writing-mode-006.html flexbox-writing-mode-006.html
-test-pref(layout.css.vertical-text.enabled,true) == flexbox-writing-mode-007.html flexbox-writing-mode-007.html
-test-pref(layout.css.vertical-text.enabled,true) == flexbox-writing-mode-008.html flexbox-writing-mode-008.html
-test-pref(layout.css.vertical-text.enabled,true) == flexbox-writing-mode-009.html flexbox-writing-mode-009.html
+== flexbox-writing-mode-001.html flexbox-writing-mode-001.html
+== flexbox-writing-mode-002.html flexbox-writing-mode-002.html
+== flexbox-writing-mode-003.html flexbox-writing-mode-003.html
+== flexbox-writing-mode-004.html flexbox-writing-mode-004.html
+== flexbox-writing-mode-005.html flexbox-writing-mode-005.html
+== flexbox-writing-mode-006.html flexbox-writing-mode-006.html
+== flexbox-writing-mode-007.html flexbox-writing-mode-007.html
+== flexbox-writing-mode-008.html flexbox-writing-mode-008.html
+== flexbox-writing-mode-009.html flexbox-writing-mode-009.html
--- a/layout/reftests/w3c-css/submitted/text-decor-3/reftest-stylo.list
+++ b/layout/reftests/w3c-css/submitted/text-decor-3/reftest-stylo.list
@@ -1,10 +1,10 @@
 # DO NOT EDIT! This is a auto-generated temporary list for Stylo testing
-default-preferences pref(layout.css.vertical-text.enabled,true)
+
 
 == ruby-text-decoration-01.html ruby-text-decoration-01.html
 == text-decoration-propagation-01.html text-decoration-propagation-01.html
 
 # text-emphasis-style
 == text-emphasis-style-property-001.html text-emphasis-style-property-001.html
 fuzzy-if(gtkWidget,3,4) fuzzy-if(skiaContent,87,65) == text-emphasis-style-property-002.html text-emphasis-style-property-002.html
 skip-if(/^Windows\x20NT\x205\.1/.test(http.oscpu)) == text-emphasis-style-property-003.html text-emphasis-style-property-003.html
--- a/layout/reftests/writing-mode/abspos/reftest-stylo.list
+++ b/layout/reftests/writing-mode/abspos/reftest-stylo.list
@@ -1,13 +1,13 @@
 # DO NOT EDIT! This is a auto-generated temporary list for Stylo testing
 # This directory contains tests for position:absolute and vertical writing modes.
 # They require the vertical-text pref to be true, otherwise lots of them will fail.
 # (See bug 1079151 for the origin of these testcases by GĂ©rard Talbot.)
-default-preferences pref(layout.css.vertical-text.enabled,true)
+
 
 # All of these are fuzzy-if on skia content on OS X due to subpixel text positioning.
 fails fuzzy-if(cocoaWidget,15,5) fuzzy-if(d2d,102,164) fuzzy-if(skiaContent,255,248) == s71-abs-pos-non-replaced-vlr-003.xht s71-abs-pos-non-replaced-vlr-003.xht
 fails fuzzy-if(cocoaWidget,15,5) fuzzy-if(d2d,102,164) fuzzy-if(skiaContent,255,248) == s71-abs-pos-non-replaced-vlr-005.xht s71-abs-pos-non-replaced-vlr-005.xht
 fails fuzzy-if(cocoaWidget,15,5) fuzzy-if(d2d,102,164) fuzzy-if(skiaContent,255,248) == s71-abs-pos-non-replaced-vlr-007.xht s71-abs-pos-non-replaced-vlr-007.xht
 fails fuzzy-if(cocoaWidget,15,5) fuzzy-if(d2d,102,164) fuzzy-if(skiaContent,255,248) == s71-abs-pos-non-replaced-vlr-009.xht s71-abs-pos-non-replaced-vlr-009.xht
 fails fuzzy-if(cocoaWidget,15,5) fuzzy-if(d2d,102,164) fuzzy-if(skiaContent,255,248) == s71-abs-pos-non-replaced-vlr-011.xht s71-abs-pos-non-replaced-vlr-011.xht
 fails fuzzy-if(cocoaWidget,15,5) fuzzy-if(d2d,102,164) fuzzy-if(skiaContent,255,248) == s71-abs-pos-non-replaced-vlr-013.xht s71-abs-pos-non-replaced-vlr-013.xht
--- a/layout/reftests/writing-mode/reftest-stylo.list
+++ b/layout/reftests/writing-mode/reftest-stylo.list
@@ -1,12 +1,12 @@
 # DO NOT EDIT! This is a auto-generated temporary list for Stylo testing
 # This directory contains tests for vertical text and logical layout coordinates.
 # They require the vertical-text pref to be true, otherwise lots of them will fail.
-default-preferences pref(layout.css.vertical-text.enabled,true)
+
 
 HTTP(..) == 1079154-1-vertical-rl-columns.html 1079154-1-vertical-rl-columns.html
 == 1082844.html 1082844.html
 HTTP(..) == 1083748.html 1083748.html
 HTTP(..) == 1083848-1-inline-border.html 1083848-1-inline-border.html
 HTTP(..) == 1083848-2-inline-background.html 1083848-2-inline-background.html
 fails fuzzy-if(gtkWidget,255,2) fuzzy-if(winWidget||Android||B2G,4,8704) HTTP(..) == 1083848-3-inline-background-repeat.html 1083848-3-inline-background-repeat.html
 == 1083892-1.html 1083892-1.html
--- a/taskcluster/ci/spidermonkey/kind.yml
+++ b/taskcluster/ci/spidermonkey/kind.yml
@@ -44,27 +44,30 @@ jobs:
             using: spidermonkey-package
             spidermonkey-variant: plain
         run-on-projects:
             - integration
             - release
         when:
             files-changed:
                 - build/**
+                - config/**
                 - configure.py
                 - dom/bindings/**
                 - intl/icu/**
                 - js/moz.configure
                 - layout/tools/reftest/reftest/**
+                - Makefile.in
                 - media/webrtc/trunk/tools/gyp/**
                 - memory/**
                 - mfbt/**
                 - modules/fdlibm/**
                 - modules/zlib/src/**
                 - mozglue/**
+                - moz.build
                 - moz.configure
                 - nsprpub/**
                 - python/**
                 - taskcluster/moz.build
                 - testing/mozbase/**
                 - test.mozbuild
                 - toolkit/mozapps/installer/package-name.mk
                 - toolkit/mozapps/installer/upload-files.mk
--- a/testing/mozharness/scripts/get_apk.py
+++ b/testing/mozharness/scripts/get_apk.py
@@ -12,17 +12,16 @@ sys.path.insert(1, os.path.dirname(sys.p
 from mozharness.base.script import BaseScript
 from mozharness.base.python import VirtualenvMixin
 from mozharness.base.script import ScriptMixin
 
 
 class GetAPK(BaseScript, VirtualenvMixin):
     all_actions = [
         'create-virtualenv',
-        "test",
         'download-apk'
     ]
 
     default_actions = [
         'create-virtualenv',
         'test'
     ]
 
@@ -32,16 +31,28 @@ class GetAPK(BaseScript, VirtualenvMixin
             "help": "Specify build number (default 1)",
             "default": "1"
         }],
         [["--version"], {
             "dest": "version",
             "help": "Specify version number to download (e.g. 23.0b7)",
             "default": "None"
         }],
+        [["--latest-nightly"], {
+            "dest": "latest_nightly",
+            "help": "Download the latest nightly version",
+            "action": "store_true",
+            "default": False
+        }],
+        [["--latest-aurora"], {
+            "dest": "latest_aurora",
+            "help": "Download the latest aurora version",
+            "action": "store_true",
+            "default": False
+        }],
         [["--arch"], {
             "dest": "arch",
             "help": "Specify which architecture to get the apk for",
             "default": "all"
         }],
         [["--locale"], {
             "dest": "locale",
             "help": "Specify which locale to get the apk for",
@@ -52,25 +63,28 @@ class GetAPK(BaseScript, VirtualenvMixin
             "help": "Use this option to clean the download directory",
             "action": "store_true",
             "default": False
         }]
     ]
 
     arch_values = ["arm", "x86"]
     multi_api_archs = ["arm"]
-    multi_apis = ["api-15"] # v11 has been dropped in fx 46 (1155801)
+    multi_apis = ["api-15"]  # v11 has been dropped in fx 46 (1155801)
     # v9 has been dropped in fx 48 (1220184)
 
     download_dir = "apk-download"
 
     apk_ext = ".apk"
     checksums_ext = ".checksums"
     android_prefix = "android-"
 
+    base_url = "https://ftp.mozilla.org/pub/mobile"
+    json_version_url = "https://product-details.mozilla.org/1.0/firefox_versions.json"
+
     # Cleanup half downloaded files on Ctrl+C
     def signal_handler(self, signal, frame):
         print("You pressed Ctrl+C!")
         self.cleanup()
         sys.exit(1)
 
     def cleanup(self):
         ScriptMixin.rmtree(self, self.download_dir)
@@ -107,26 +121,30 @@ class GetAPK(BaseScript, VirtualenvMixin
 
     # Check the given values are correct
     def check_argument(self):
         if self.config["clean"]:
             self.cleanup()
         if self.config["version"] == "None":
             if self.config["clean"]:
                 sys.exit(0)
-            self.fatal("Version is required")
+        if self.config["version"] != "None" and (self.config["latest_nightly"] or self.config["latest_aurora"]):
+            self.fatal("Cannot set a version and --latest-nightly or --latest-aurora")
 
         if self.config["arch"] not in self.arch_values and not self.config["arch"] == "all":
             error = self.config["arch"] + " is not a valid arch.  " \
                                           "Try one of the following:"+os.linesep
             for arch in self.arch_values:
                 error += arch + os.linesep
             error += "Or don't use the --arch option to download all the archs"
             self.fatal(error)
 
+        if self.config["latest_nightly"] and self.config["latest_aurora"]:
+            self.fatal("Conflicting options. Cannot use --latest-nightly with --latest-aurora")
+
     # Checksum check the APK
     def check_apk(self, apk_file, checksum_file):
         self.info("The checksum for the APK is being checked....")
         checksum = ScriptMixin.read_from_file(self, checksum_file, False)
         checksum = re.sub("\s(.*)", "", checksum.splitlines()[0])
 
         apk_checksum = self.file_sha512sum(apk_file)
 
@@ -134,19 +152,22 @@ class GetAPK(BaseScript, VirtualenvMixin
             self.info("APK checksum check succeeded!")
             self.download_complete(apk_file, checksum_file)
         else:
             ScriptMixin.rmtree(self, self.download_dir)
             self.fatal("Downloading " + apk_file + " failed!")
 
     # Helper functions
     def generate_url(self, version, build, locale, api_suffix, arch_file):
-        return "https://ftp.mozilla.org/pub/mozilla.org/mobile/candidates/" + version + "-candidates/build" + build + \
-               "/" + self.android_prefix + api_suffix + "/" + locale + "/fennec-" + version + "." + locale + \
-               "." + self.android_prefix + arch_file
+        if self.config["latest_nightly"] or self.config["latest_aurora"]:
+            code = "central" if self.config["latest_nightly"] else "aurora"
+            return ("%s/nightly/latest-mozilla-%s-android-%s/fennec-%s.%s.android-%s") % (self.base_url, code, api_suffix, version, locale, arch_file)
+
+        return ("%s/candidates/%s-candidates/build%s/%s%s/%s/fennec-%s.%s.%s%s") % (self.base_url, version, build, self.android_prefix, api_suffix, locale, version, locale, self.android_prefix, arch_file)
+
 
     def get_api_suffix(self, arch):
         if arch in self.multi_api_archs:
             return self.multi_apis
         else:
             return [arch]
 
     def get_arch_file(self, arch):
@@ -173,33 +194,42 @@ class GetAPK(BaseScript, VirtualenvMixin
             if arch in self.multi_api_archs:
                 filename = common_filename + arch_file + "-" + api_suffix
             else:
                 filename = common_filename + arch_file
 
             filename_apk = os.path.join(self.download_dir, filename + self.apk_ext)
             filename_checksums = os.path.join(self.download_dir, filename + self.checksums_ext)
 
+            # Download the APK
             retry_config = {'attempts': 1, 'cleanup': self.download_error}
             ScriptMixin.download_file(self, apk_url, filename_apk, retry_config=retry_config)
 
+            # Download the checksum of the APK
             retry_config = {'attempts': 1, 'cleanup': self.download_error}
             ScriptMixin.download_file(self, checksum_url, filename_checksums, retry_config=retry_config)
 
             self.check_apk(filename_apk, filename_checksums)
 
+    def get_version_name(self):
+        if self.config["latest_nightly"] or self.config["latest_aurora"]:
+            json = self.load_json_url(self.json_version_url)
+            version_code = json['FIREFOX_NIGHTLY'] if self.config["latest_nightly"] else json['FIREFOX_AURORA']
+            return version_code
+        return self.config["version"]
+
     # Download all the archs if none is given
     def download_all(self, version, build, locale):
         for arch in self.arch_values:
             self.download(version, build, arch, locale)
 
     # Download apk initial action
     def download_apk(self):
         self.check_argument()
-        version = self.config["version"]
+        version = self.get_version_name()
         arch = self.config["arch"]
         build = str(self.config["build"])
         locale = self.config["locale"]
 
         self.info("Downloading version " + version + " build #" + build
                   + " for arch " + arch + " (locale " + locale + ")")
         if arch == "all":
             self.download_all(version, build, locale)
@@ -220,18 +250,19 @@ class GetAPK(BaseScript, VirtualenvMixin
 
         self.cleanup()
         if os.path.isdir(self.download_dir):
             self.fatal("cleanup test failed")
 
         url = self.generate_url("43.0", "2", "multi", "x86", "i386")
         correcturl = "https://ftp.mozilla.org/pub/mozilla.org/mobile/candidates/43.0-candidates/build2/"\
                      + self.android_prefix + "x86/multi/fennec-43.0.multi." + self.android_prefix + "i386"
+
         if not url == correcturl:
-            self.fatal("get_url test failed!")
+            self.fatal(("get_url test failed! %s != %s") % (url, correcturl))
 
         if not self.get_api_suffix(self.multi_api_archs[0]) == self.multi_apis:
             self.fatal("get_api_suffix test failed!")
 
         if not self.get_arch_file("x86") == "i386":
             self.fatal("get_arch_file test failed!")
 
         if not self.get_common_file_name("43.0", "multi") == "fennec-43.0.multi." + self.android_prefix:
--- a/testing/web-platform/meta/html/semantics/embedded-content/the-iframe-element/iframe-allowfullscreen.html.ini
+++ b/testing/web-platform/meta/html/semantics/embedded-content/the-iframe-element/iframe-allowfullscreen.html.ini
@@ -1,3 +1,4 @@
 [iframe-allow-fullscreen.html.ini]
   type: testharness
+  disabled: https://bugzilla.mozilla.org/show_bug.cgi?id=1282024
   prefs: [full-screen-api.unprefix.enabled:true]
--- a/testing/xpcshell/selftest.py
+++ b/testing/xpcshell/selftest.py
@@ -9,30 +9,30 @@ import mozunit
 import os
 import pprint
 import re
 import shutil
 import sys
 import tempfile
 import unittest
 
+from buildconfig import substs
 from StringIO import StringIO
 from mozlog import structured
 from mozbuild.base import MozbuildObject
 os.environ.pop('MOZ_OBJDIR', None)
 build_obj = MozbuildObject.from_environment()
 
 from runxpcshelltests import XPCShellTests
 
 mozinfo.find_and_update_from_json()
 
 objdir = build_obj.topobjdir.encode("utf-8")
 
 if mozinfo.isMac:
-  from buildconfig import substs
   xpcshellBin = os.path.join(objdir, "dist", substs['MOZ_MACBUNDLE_NAME'], "Contents", "MacOS", "xpcshell")
 else:
   xpcshellBin = os.path.join(objdir, "dist", "bin", "xpcshell")
   if sys.platform == "win32":
     xpcshellBin += ".exe"
 
 TEST_PASS_STRING = "TEST-PASS"
 TEST_FAIL_STRING = "TEST-UNEXPECTED-FAIL"
@@ -853,17 +853,19 @@ add_test({
         """
         Ensure a simple test with an uncaught rejection is reported.
         """
         self.writeFile("test_simple_uncaught_rejection.js", SIMPLE_UNCAUGHT_REJECTION_TEST)
         self.writeManifest(["test_simple_uncaught_rejection.js"])
 
         self.assertTestResult(False)
         self.assertInLog(TEST_FAIL_STRING)
-        self.assertInLog("test_simple_uncaught_rejection.js:3:3")
+        if not substs.get('RELEASE_BUILD'):
+          # async stacks are currently not enabled in release builds.
+          self.assertInLog("test_simple_uncaught_rejection.js:3:3")
         self.assertInLog("Test rejection.")
         self.assertEquals(1, self.x.testCount)
         self.assertEquals(0, self.x.passCount)
         self.assertEquals(1, self.x.failCount)
 
     def testUncaughtRejectionJSM(self):
         """
         Ensure a simple test with an uncaught rejection from Promise.jsm is reported.
--- a/toolkit/components/telemetry/Telemetry.cpp
+++ b/toolkit/components/telemetry/Telemetry.cpp
@@ -2343,16 +2343,23 @@ TelemetryImpl::SnapshotScalars(unsigned 
 
 NS_IMETHODIMP
 TelemetryImpl::ClearScalars()
 {
   TelemetryScalar::ClearScalars();
   return NS_OK;
 }
 
+NS_IMETHODIMP
+TelemetryImpl::FlushBatchedChildTelemetry()
+{
+  TelemetryHistogram::IPCTimerFired(nullptr, nullptr);
+  return NS_OK;
+}
+
 size_t
 TelemetryImpl::SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf)
 {
   size_t n = aMallocSizeOf(this);
 
   // Ignore the hashtables in mAddonMap; they are not significant.
   n += TelemetryHistogram::GetMapShallowSizesOfExcludingThis(aMallocSizeOf);
   n += TelemetryScalar::GetMapShallowSizesOfExcludingThis(aMallocSizeOf);
@@ -2789,16 +2796,28 @@ AccumulateCategorical(ID id, const nsCSt
 void
 AccumulateTimeDelta(ID aHistogram, TimeStamp start, TimeStamp end)
 {
   Accumulate(aHistogram,
              static_cast<uint32_t>((end - start).ToMilliseconds()));
 }
 
 void
+AccumulateChild(const nsTArray<Accumulation>& aAccumulations)
+{
+  TelemetryHistogram::AccumulateChild(aAccumulations);
+}
+
+void
+AccumulateChildKeyed(const nsTArray<KeyedAccumulation>& aAccumulations)
+{
+  TelemetryHistogram::AccumulateChildKeyed(aAccumulations);
+}
+
+void
 ClearHistogram(ID aId)
 {
   TelemetryHistogram::ClearHistogram(aId);
 }
 
 const char*
 GetHistogramName(ID id)
 {
--- a/toolkit/components/telemetry/Telemetry.h
+++ b/toolkit/components/telemetry/Telemetry.h
@@ -28,16 +28,19 @@
  *****************************************************************************/
 
 namespace mozilla {
 namespace HangMonitor {
   class HangAnnotations;
 } // namespace HangMonitor
 namespace Telemetry {
 
+struct Accumulation;
+struct KeyedAccumulation;
+
 enum TimerResolution {
   Millisecond,
   Microsecond
 };
 
 /**
  * Create and destroy the underlying base::StatisticsRecorder singleton.
  * Creation has to be done very early in the startup sequence.
@@ -121,16 +124,30 @@ void AccumulateCategorical(ID id, const 
  *
  * @param id - histogram id
  * @param start - start time
  * @param end - end time
  */
 void AccumulateTimeDelta(ID id, TimeStamp start, TimeStamp end = TimeStamp::Now());
 
 /**
+ * Accumulate child data into child histograms
+ *
+ * @param aAccumulations - accumulation actions to perform
+ */
+void AccumulateChild(const nsTArray<Accumulation>& aAccumulations);
+
+/**
+ * Accumulate child data into child keyed histograms
+ *
+ * @param aAccumulations - accumulation actions to perform
+ */
+void AccumulateChildKeyed(const nsTArray<KeyedAccumulation>& aAccumulations);
+
+/**
  * This clears the data for a histogram in TelemetryHistogramEnums.h.
  *
  * @param id - histogram id
  */
 void ClearHistogram(ID id);
 
 /**
  * Enable/disable recording for this histogram at runtime.
new file mode 100644
--- /dev/null
+++ b/toolkit/components/telemetry/TelemetryComms.h
@@ -0,0 +1,84 @@
+/* -*-  Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2; -*- */
+/* 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
+ */
+
+#ifndef Telemetry_Comms_h__
+#define Telemetry_Comms_h__
+
+#include "ipc/IPCMessageUtils.h"
+
+namespace mozilla {
+namespace Telemetry {
+
+enum ID : uint32_t;
+
+struct Accumulation
+{
+  mozilla::Telemetry::ID mId;
+  uint32_t mSample;
+};
+
+struct KeyedAccumulation
+{
+  mozilla::Telemetry::ID mId;
+  uint32_t mSample;
+  nsCString mKey;
+};
+
+} // namespace Telemetry
+} // namespace mozilla
+
+namespace IPC {
+
+template<>
+struct
+ParamTraits<mozilla::Telemetry::Accumulation>
+{
+  typedef mozilla::Telemetry::Accumulation paramType;
+
+  static void Write(Message* aMsg, const paramType& aParam)
+  {
+    aMsg->WriteUInt32(aParam.mId);
+    WriteParam(aMsg, aParam.mSample);
+  }
+
+  static bool Read(const Message* aMsg, PickleIterator* aIter, paramType* aResult)
+  {
+    if (!aMsg->ReadUInt32(aIter, reinterpret_cast<uint32_t*>(&(aResult->mId))) ||
+        !ReadParam(aMsg, aIter, &(aResult->mSample))) {
+      return false;
+    }
+
+    return true;
+  }
+};
+
+template<>
+struct
+ParamTraits<mozilla::Telemetry::KeyedAccumulation>
+{
+  typedef mozilla::Telemetry::KeyedAccumulation paramType;
+
+  static void Write(Message* aMsg, const paramType& aParam)
+  {
+    aMsg->WriteUInt32(aParam.mId);
+    WriteParam(aMsg, aParam.mSample);
+    WriteParam(aMsg, aParam.mKey);
+  }
+
+  static bool Read(const Message* aMsg, PickleIterator* aIter, paramType* aResult)
+  {
+    if (!aMsg->ReadUInt32(aIter, reinterpret_cast<uint32_t*>(&(aResult->mId))) ||
+        !ReadParam(aMsg, aIter, &(aResult->mSample)) ||
+        !ReadParam(aMsg, aIter, &(aResult->mKey))) {
+      return false;
+    }
+
+    return true;
+  }
+};
+
+} // namespace IPC
+
+#endif // Telemetry_Comms_h__
--- a/toolkit/components/telemetry/TelemetryHistogram.cpp
+++ b/toolkit/components/telemetry/TelemetryHistogram.cpp
@@ -9,33 +9,40 @@
 #include "js/GCAPI.h"
 #include "nsString.h"
 #include "nsTHashtable.h"
 #include "nsHashKeys.h"
 #include "nsBaseHashtable.h"
 #include "nsClassHashtable.h"
 #include "nsITelemetry.h"
 
+#include "mozilla/dom/ContentChild.h"
 #include "mozilla/dom/ToJSValue.h"
+#include "mozilla/Atomics.h"
 #include "mozilla/StartupTimeline.h"
 #include "mozilla/StaticMutex.h"
+#include "mozilla/StaticPtr.h"
+#include "mozilla/Unused.h"
 
 #include "TelemetryCommon.h"
 #include "TelemetryHistogram.h"
 
 #include "base/histogram.h"
 
 using base::Histogram;
 using base::StatisticsRecorder;
 using base::BooleanHistogram;
 using base::CountHistogram;
 using base::FlagHistogram;
 using base::LinearHistogram;
 using mozilla::StaticMutex;
 using mozilla::StaticMutexAutoLock;
+using mozilla::StaticAutoPtr;
+using mozilla::Telemetry::Accumulation;
+using mozilla::Telemetry::KeyedAccumulation;
 
 
 ////////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////////
 //
 // Naming: there are two kinds of functions in this file:
 //
 // * Functions named internal_*: these can only be reached via an
@@ -87,16 +94,17 @@ using mozilla::StaticMutexAutoLock;
 ////////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////////
 //
 // PRIVATE TYPES
 
 #define EXPIRED_ID "__expired__"
 #define SUBSESSION_HISTOGRAM_PREFIX "sub#"
 #define KEYED_HISTOGRAM_NAME_SEPARATOR "#"
+#define CHILD_HISTOGRAM_SUFFIX "#content"
 
 namespace {
 
 using mozilla::Telemetry::Common::AutoHashtable;
 using mozilla::Telemetry::Common::IsExpiredVersion;
 using mozilla::Telemetry::Common::CanRecordDataset;
 using mozilla::Telemetry::Common::IsInDataset;
 
@@ -180,16 +188,23 @@ bool gCorruptHistograms[mozilla::Telemet
 // This is for gHistograms, gHistogramStringTable
 #include "TelemetryHistogramData.inc"
 
 AddonMapType gAddonMap;
 
 // The singleton StatisticsRecorder object for this process.
 base::StatisticsRecorder* gStatisticsRecorder = nullptr;
 
+// For batching and sending child process accumulations to the parent
+nsITimer* gIPCTimer = nullptr;
+mozilla::Atomic<bool, mozilla::Relaxed> gIPCTimerArmed(false);
+mozilla::Atomic<bool, mozilla::Relaxed> gIPCTimerArming(false);
+StaticAutoPtr<nsTArray<Accumulation>> gAccumulations;
+StaticAutoPtr<nsTArray<KeyedAccumulation>> gKeyedAccumulations;
+
 } // namespace
 
 
 ////////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////////
 //
 // PRIVATE CONSTANTS
 
@@ -199,16 +214,27 @@ namespace {
 const mozilla::Telemetry::ID kRecordingInitiallyDisabledIDs[] = {
   mozilla::Telemetry::FX_REFRESH_DRIVER_SYNC_SCROLL_FRAME_DELAY_MS,
 
   // The array must not be empty. Leave these item here.
   mozilla::Telemetry::TELEMETRY_TEST_COUNT_INIT_NO_RECORD,
   mozilla::Telemetry::TELEMETRY_TEST_KEYED_COUNT_INIT_NO_RECORD
 };
 
+// Sending each remote accumulation immediately places undue strain on the
+// IPC subsystem. Batch the remote accumulations for a period of time before
+// sending them all at once. This value was chosen as a balance between data
+// timeliness and performance (see bug 1218576)
+const uint32_t kBatchTimeoutMs = 2000;
+
+// To stop growing unbounded in memory while waiting for kBatchTimeoutMs to
+// drain the g*Accumulations arrays, request an immediate flush if the arrays
+// manage to reach this high water mark of elements.
+const size_t kAccumulationsArrayHighWaterMark = 5 * 1024;
+
 } // namespace
 
 
 ////////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////////
 //
 // PRIVATE: Misc small helpers
 
@@ -306,16 +332,26 @@ HistogramInfo::label_id(const char* labe
       *labelId = i;
       return NS_OK;
     }
   }
 
   return NS_ERROR_FAILURE;
 }
 
+bool
+StringEndsWith(const std::string& name, const std::string& suffix)
+{
+  if (name.size() < suffix.size()) {
+    return false;
+  }
+
+  return name.compare(name.size() - suffix.size(), suffix.size(), suffix) == 0;
+}
+
 } // namespace
 
 
 ////////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////////
 //
 // PRIVATE: Histogram Get, Add, Clone, Clear functions
 
@@ -390,49 +426,70 @@ internal_HistogramGet(const char *name, 
     break;
   default:
     NS_ASSERTION(false, "Invalid histogram type");
     return NS_ERROR_INVALID_ARG;
   }
   return NS_OK;
 }
 
+CharPtrEntryType*
+internal_GetHistogramMapEntry(const char* name)
+{
+  nsDependentCString histogramName(name);
+  NS_NAMED_LITERAL_CSTRING(suffix, CHILD_HISTOGRAM_SUFFIX);
+  if (!StringEndsWith(histogramName, suffix)) {
+    return gHistogramMap.GetEntry(name);
+  }
+  auto root = Substring(histogramName, 0, histogramName.Length() - suffix.Length());
+  return gHistogramMap.GetEntry(PromiseFlatCString(root).get());
+}
+
 nsresult
 internal_GetHistogramEnumId(const char *name, mozilla::Telemetry::ID *id)
 {
   if (!gInitDone) {
     return NS_ERROR_FAILURE;
   }
 
-  CharPtrEntryType *entry = gHistogramMap.GetEntry(name);
+  CharPtrEntryType *entry = internal_GetHistogramMapEntry(name);
   if (!entry) {
     return NS_ERROR_INVALID_ARG;
   }
   *id = entry->mData;
   return NS_OK;
 }
 
 // O(1) histogram lookup by numeric id
 nsresult
-internal_GetHistogramByEnumId(mozilla::Telemetry::ID id, Histogram **ret)
+internal_GetHistogramByEnumId(mozilla::Telemetry::ID id, Histogram **ret,
+                              bool child = false)
 {
   static Histogram* knownHistograms[mozilla::Telemetry::HistogramCount] = {0};
-  Histogram *h = knownHistograms[id];
+  static Histogram* knownChildHistograms[mozilla::Telemetry::HistogramCount] = {0};
+  Histogram *h = child ? knownChildHistograms[id] : knownHistograms[id];
   if (h) {
     *ret = h;
     return NS_OK;
   }
 
   const HistogramInfo &p = gHistograms[id];
   if (p.keyed) {
     return NS_ERROR_FAILURE;
   }
 
-  nsresult rv = internal_HistogramGet(p.id(), p.expiration(), p.histogramType,
-                                      p.min, p.max, p.bucketCount, true, &h);
+  nsCString histogramName;
+  histogramName.Append(p.id());
+  if (child) {
+    histogramName.AppendLiteral(CHILD_HISTOGRAM_SUFFIX);
+  }
+
+  nsresult rv = internal_HistogramGet(histogramName.get(), p.expiration(),
+                                      p.histogramType, p.min, p.max,
+                                      p.bucketCount, true, &h);
   if (NS_FAILED(rv))
     return rv;
 
 #ifdef DEBUG
   // Check that the C++ Histogram code computes the same ranges as the
   // Python histogram code.
   if (!IsExpiredVersion(p.expiration())) {
     const struct bounds &b = gBucketLowerBoundIndex[id];
@@ -442,31 +499,37 @@ internal_GetHistogramByEnumId(mozilla::T
       for (int i = 0; i < b.length; ++i) {
         MOZ_ASSERT(gBucketLowerBounds[b.offset + i] == h->ranges(i),
                    "C++/Python bucket mismatch");
       }
     }
   }
 #endif
 
-  *ret = knownHistograms[id] = h;
+  if (child) {
+    *ret = knownChildHistograms[id] = h;
+  } else {
+    *ret = knownHistograms[id] = h;
+  }
   return NS_OK;
 }
 
 nsresult
 internal_GetHistogramByName(const nsACString &name, Histogram **ret)
 {
   mozilla::Telemetry::ID id;
   nsresult rv
     = internal_GetHistogramEnumId(PromiseFlatCString(name).get(), &id);
   if (NS_FAILED(rv)) {
     return rv;
   }
 
-  rv = internal_GetHistogramByEnumId(id, ret);
+  bool isChild = StringEndsWith(name,
+                                NS_LITERAL_CSTRING(CHILD_HISTOGRAM_SUFFIX));
+  rv = internal_GetHistogramByEnumId(id, ret, isChild);
   if (NS_FAILED(rv))
     return rv;
 
   return NS_OK;
 }
 
 /**
  * This clones a histogram |existing| with the id |existingId| to a
@@ -512,42 +575,53 @@ internal_CloneHistogram(const nsACString
   if (NS_FAILED(rv)) {
     return nullptr;
   }
 
   return internal_CloneHistogram(newName, existingId, *existing);
 }
 
 #if !defined(MOZ_WIDGET_GONK) && !defined(MOZ_WIDGET_ANDROID)
+
 Histogram*
 internal_GetSubsessionHistogram(Histogram& existing)
 {
   mozilla::Telemetry::ID id;
   nsresult rv
     = internal_GetHistogramEnumId(existing.histogram_name().c_str(), &id);
   if (NS_FAILED(rv) || gHistograms[id].keyed) {
     return nullptr;
   }
 
+  bool isChild = StringEndsWith(existing.histogram_name(),
+                                CHILD_HISTOGRAM_SUFFIX);
+
   static Histogram* subsession[mozilla::Telemetry::HistogramCount] = {};
-  if (subsession[id]) {
-    return subsession[id];
+  static Histogram* subsessionChild[mozilla::Telemetry::HistogramCount] = {};
+  Histogram* cached = isChild ? subsessionChild[id] : subsession[id];
+  if (cached) {
+    return cached;
   }
 
   NS_NAMED_LITERAL_CSTRING(prefix, SUBSESSION_HISTOGRAM_PREFIX);
   nsDependentCString existingName(gHistograms[id].id());
   if (StringBeginsWith(existingName, prefix)) {
     return nullptr;
   }
 
   nsCString subsessionName(prefix);
-  subsessionName.Append(existingName);
+  subsessionName.Append(existing.histogram_name().c_str());
 
-  subsession[id] = internal_CloneHistogram(subsessionName, id, existing);
-  return subsession[id];
+  Histogram* clone = internal_CloneHistogram(subsessionName, id, existing);
+  if (isChild) {
+    subsessionChild[id] = clone;
+  } else {
+    subsession[id] = clone;
+  }
+  return clone;
 }
 #endif
 
 nsresult
 internal_HistogramAdd(Histogram& histogram, int32_t value, uint32_t dataset)
 {
   // Check if we are allowed to record the data.
   bool canRecordDataset = CanRecordDataset(dataset,
@@ -586,36 +660,23 @@ internal_HistogramAdd(Histogram& histogr
       return NS_OK;
     }
     dataset = gHistograms[id].dataset;
   }
 
   return internal_HistogramAdd(histogram, value, dataset);
 }
 
-nsresult
-internal_HistogramAddCategorical(mozilla::Telemetry::ID id, const nsCString& label)
-{
-  uint32_t labelId = 0;
-  if (NS_FAILED(gHistograms[id].label_id(label.get(), &labelId))) {
-    return NS_ERROR_ILLEGAL_VALUE;
-  }
-
-  Histogram* h = nullptr;
-  nsresult rv = internal_GetHistogramByEnumId(id, &h);
-  if (NS_FAILED(rv)) {
-    return rv;
-  }
-
-  return internal_HistogramAdd(*h, labelId);
-}
-
 void
 internal_HistogramClear(Histogram& aHistogram, bool onlySubsession)
 {
+  MOZ_ASSERT(XRE_IsParentProcess());
+  if (!XRE_IsParentProcess()) {
+    return;
+  }
   if (!onlySubsession) {
     aHistogram.Clear();
   }
 
 #if !defined(MOZ_WIDGET_GONK) && !defined(MOZ_WIDGET_ANDROID)
   if (Histogram* subsession = internal_GetSubsessionHistogram(aHistogram)) {
     subsession->Clear();
   }
@@ -801,16 +862,18 @@ public:
                          bool subsession, bool clearSubsession);
 
   void SetRecordingEnabled(bool aEnabled) { mRecordingEnabled = aEnabled; };
   bool IsRecordingEnabled() const { return mRecordingEnabled; };
 
   nsresult Add(const nsCString& key, uint32_t aSample);
   void Clear(bool subsession);
 
+  nsresult GetEnumId(mozilla::Telemetry::ID& id);
+
 private:
   typedef nsBaseHashtableET<nsCStringHashKey, Histogram*> KeyedHistogramEntry;
   typedef AutoHashtable<KeyedHistogramEntry> KeyedHistogramMapType;
   KeyedHistogramMapType mHistogramMap;
 #if !defined(MOZ_WIDGET_GONK) && !defined(MOZ_WIDGET_ANDROID)
   KeyedHistogramMapType mSubsessionMap;
 #endif
 
@@ -943,16 +1006,20 @@ KeyedHistogram::Add(const nsCString& key
   subsession->Add(sample);
 #endif
   return NS_OK;
 }
 
 void
 KeyedHistogram::Clear(bool onlySubsession)
 {
+  MOZ_ASSERT(XRE_IsParentProcess());
+  if (!XRE_IsParentProcess()) {
+    return;
+  }
 #if !defined(MOZ_WIDGET_GONK) && !defined(MOZ_WIDGET_ANDROID)
   for (auto iter = mSubsessionMap.Iter(); !iter.Done(); iter.Next()) {
     iter.Get()->mData->Clear();
   }
   mSubsessionMap.Clear();
   if (onlySubsession) {
     return;
   }
@@ -1030,16 +1097,22 @@ KeyedHistogram::GetJSSnapshot(JSContext*
   if (subsession && clearSubsession) {
     Clear(true);
   }
 #endif
 
   return NS_OK;
 }
 
+nsresult
+KeyedHistogram::GetEnumId(mozilla::Telemetry::ID& id)
+{
+  return internal_GetHistogramEnumId(mName.get(), &id);
+}
+
 } // namespace
 
 
 ////////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////////
 //
 // PRIVATE: KeyedHistogram helpers
 
@@ -1058,16 +1131,329 @@ internal_GetKeyedHistogramById(const nsA
 }
 
 } // namespace
 
 
 ////////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////////
 //
+// PRIVATE: functions related to addon histograms
+
+namespace {
+
+// Compute the name to pass into Histogram for the addon histogram
+// 'name' from the addon 'id'.  We can't use 'name' directly because it
+// might conflict with other histograms in other addons or even with our
+// own.
+void
+internal_AddonHistogramName(const nsACString &id, const nsACString &name,
+                            nsACString &ret)
+{
+  ret.Append(id);
+  ret.Append(':');
+  ret.Append(name);
+}
+
+bool
+internal_CreateHistogramForAddon(const nsACString &name,
+                                 AddonHistogramInfo &info)
+{
+  Histogram *h;
+  nsresult rv = internal_HistogramGet(PromiseFlatCString(name).get(), "never",
+                                      info.histogramType, info.min, info.max,
+                                      info.bucketCount, true, &h);
+  if (NS_FAILED(rv)) {
+    return false;
+  }
+  // Don't let this histogram be reported via the normal means
+  // (e.g. Telemetry.registeredHistograms); we'll make it available in
+  // other ways.
+  h->ClearFlags(Histogram::kUmaTargetedHistogramFlag);
+  info.h = h;
+  return true;
+}
+
+bool
+internal_AddonHistogramReflector(AddonHistogramEntryType *entry,
+                                 JSContext *cx, JS::Handle<JSObject*> obj)
+{
+  AddonHistogramInfo &info = entry->mData;
+
+  // Never even accessed the histogram.
+  if (!info.h) {
+    // Have to force creation of HISTOGRAM_FLAG histograms.
+    if (info.histogramType != nsITelemetry::HISTOGRAM_FLAG)
+      return true;
+
+    if (!internal_CreateHistogramForAddon(entry->GetKey(), info)) {
+      return false;
+    }
+  }
+
+  if (internal_IsEmpty(info.h)) {
+    return true;
+  }
+
+  JS::Rooted<JSObject*> snapshot(cx, JS_NewPlainObject(cx));
+  if (!snapshot) {
+    // Just consider this to be skippable.
+    return true;
+  }
+  switch (internal_ReflectHistogramSnapshot(cx, snapshot, info.h)) {
+  case REFLECT_FAILURE:
+  case REFLECT_CORRUPT:
+    return false;
+  case REFLECT_OK:
+    const nsACString &histogramName = entry->GetKey();
+    if (!JS_DefineProperty(cx, obj, PromiseFlatCString(histogramName).get(),
+                           snapshot, JSPROP_ENUMERATE)) {
+      return false;
+    }
+    break;
+  }
+  return true;
+}
+
+bool
+internal_AddonReflector(AddonEntryType *entry, JSContext *cx,
+                        JS::Handle<JSObject*> obj)
+{
+  const nsACString &addonId = entry->GetKey();
+  JS::Rooted<JSObject*> subobj(cx, JS_NewPlainObject(cx));
+  if (!subobj) {
+    return false;
+  }
+
+  AddonHistogramMapType *map = entry->mData;
+  if (!(map->ReflectIntoJS(internal_AddonHistogramReflector, cx, subobj)
+        && JS_DefineProperty(cx, obj, PromiseFlatCString(addonId).get(),
+                             subobj, JSPROP_ENUMERATE))) {
+    return false;
+  }
+  return true;
+}
+
+} // namespace
+
+
+////////////////////////////////////////////////////////////////////////
+////////////////////////////////////////////////////////////////////////
+//
+// PRIVATE: thread-unsafe helpers for the external interface
+
+// This is a StaticMutex rather than a plain Mutex (1) so that
+// it gets initialised in a thread-safe manner the first time
+// it is used, and (2) because it is never de-initialised, and
+// a normal Mutex would show up as a leak in BloatView.  StaticMutex
+// also has the "OffTheBooks" property, so it won't show as a leak
+// in BloatView.
+static StaticMutex gTelemetryHistogramMutex;
+
+namespace {
+
+void
+internal_SetHistogramRecordingEnabled(mozilla::Telemetry::ID aID, bool aEnabled)
+{
+  if (!internal_IsHistogramEnumId(aID)) {
+    MOZ_ASSERT(false, "Telemetry::SetHistogramRecordingEnabled(...) must be used with an enum id");
+    return;
+  }
+
+  if (gHistograms[aID].keyed) {
+    const nsDependentCString id(gHistograms[aID].id());
+    KeyedHistogram* keyed = internal_GetKeyedHistogramById(id);
+    if (keyed) {
+      keyed->SetRecordingEnabled(aEnabled);
+      return;
+    }
+  } else {
+    Histogram *h;
+    nsresult rv = internal_GetHistogramByEnumId(aID, &h);
+    if (NS_SUCCEEDED(rv)) {
+      h->SetRecordingEnabled(aEnabled);
+      return;
+    }
+  }
+
+  MOZ_ASSERT(false, "Telemetry::SetHistogramRecordingEnabled(...) id not found");
+}
+
+void internal_armIPCTimerMainThread()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  gIPCTimerArming = false;
+  if (gIPCTimerArmed) {
+    return;
+  }
+  if (!gIPCTimer) {
+    CallCreateInstance(NS_TIMER_CONTRACTID, &gIPCTimer);
+  }
+  if (gIPCTimer) {
+    gIPCTimer->InitWithFuncCallback(TelemetryHistogram::IPCTimerFired,
+                                    nullptr, kBatchTimeoutMs,
+                                    nsITimer::TYPE_ONE_SHOT);
+    gIPCTimerArmed = true;
+  }
+}
+
+void internal_armIPCTimer()
+{
+  if (gIPCTimerArmed || gIPCTimerArming) {
+    return;
+  }
+  gIPCTimerArming = true;
+  if (NS_IsMainThread()) {
+    internal_armIPCTimerMainThread();
+  } else {
+    NS_DispatchToMainThread(NS_NewRunnableFunction([]() -> void {
+      StaticMutexAutoLock locker(gTelemetryHistogramMutex);
+      internal_armIPCTimerMainThread();
+    }));
+  }
+}
+
+bool
+internal_RemoteAccumulate(mozilla::Telemetry::ID aId, uint32_t aSample)
+{
+  if (XRE_IsParentProcess()) {
+    return false;
+  }
+  if (!gAccumulations) {
+    gAccumulations = new nsTArray<Accumulation>();
+  }
+  if (gAccumulations->Length() == kAccumulationsArrayHighWaterMark) {
+    NS_DispatchToMainThread(NS_NewRunnableFunction([]() -> void {
+      TelemetryHistogram::IPCTimerFired(nullptr, nullptr);
+    }));
+  }
+  gAccumulations->AppendElement(Accumulation{aId, aSample});
+  internal_armIPCTimer();
+  return true;
+}
+
+bool
+internal_RemoteAccumulate(mozilla::Telemetry::ID aId,
+                    const nsCString& aKey, uint32_t aSample)
+{
+  if (XRE_IsParentProcess()) {
+    return false;
+  }
+  if (!gKeyedAccumulations) {
+    gKeyedAccumulations = new nsTArray<KeyedAccumulation>();
+  }
+  if (gKeyedAccumulations->Length() == kAccumulationsArrayHighWaterMark) {
+    NS_DispatchToMainThread(NS_NewRunnableFunction([]() -> void {
+      TelemetryHistogram::IPCTimerFired(nullptr, nullptr);
+    }));
+  }
+  gKeyedAccumulations->AppendElement(KeyedAccumulation{aId, aSample, aKey});
+  internal_armIPCTimer();
+  return true;
+}
+
+void internal_Accumulate(mozilla::Telemetry::ID aHistogram, uint32_t aSample)
+{
+  bool isValid = internal_IsHistogramEnumId(aHistogram);
+  MOZ_ASSERT(isValid, "Accumulation using invalid id");
+  if (!internal_CanRecordBase() || !isValid ||
+      internal_RemoteAccumulate(aHistogram, aSample)) {
+    return;
+  }
+  Histogram *h;
+  nsresult rv = internal_GetHistogramByEnumId(aHistogram, &h);
+  if (NS_SUCCEEDED(rv)) {
+    internal_HistogramAdd(*h, aSample, gHistograms[aHistogram].dataset);
+  }
+}
+
+void
+internal_Accumulate(mozilla::Telemetry::ID aID,
+                    const nsCString& aKey, uint32_t aSample)
+{
+  bool isValid = internal_IsHistogramEnumId(aID);
+  MOZ_ASSERT(isValid, "Child keyed telemetry accumulation using invalid id");
+  if (!gInitDone || !internal_CanRecordBase() || !isValid ||
+      internal_RemoteAccumulate(aID, aKey, aSample)) {
+    return;
+  }
+  const HistogramInfo& th = gHistograms[aID];
+  KeyedHistogram* keyed
+     = internal_GetKeyedHistogramById(nsDependentCString(th.id()));
+  MOZ_ASSERT(keyed);
+  keyed->Add(aKey, aSample);
+}
+
+void
+internal_Accumulate(Histogram& aHistogram, uint32_t aSample)
+{
+  if (XRE_IsParentProcess()) {
+    internal_HistogramAdd(aHistogram, aSample);
+    return;
+  }
+
+  mozilla::Telemetry::ID id;
+  nsresult rv = internal_GetHistogramEnumId(aHistogram.histogram_name().c_str(), &id);
+  if (NS_SUCCEEDED(rv)) {
+    internal_RemoteAccumulate(id, aSample);
+  }
+}
+
+void
+internal_Accumulate(KeyedHistogram& aKeyed,
+                    const nsCString& aKey, uint32_t aSample)
+{
+  if (XRE_IsParentProcess()) {
+    aKeyed.Add(aKey, aSample);
+    return;
+  }
+
+  mozilla::Telemetry::ID id;
+  if (NS_SUCCEEDED(aKeyed.GetEnumId(id))) {
+    internal_RemoteAccumulate(id, aKey, aSample);
+  }
+}
+
+void
+internal_AccumulateChild(mozilla::Telemetry::ID aId, uint32_t aSample)
+{
+  if (!internal_CanRecordBase()) {
+    return;
+  }
+  Histogram* h;
+  nsresult rv = internal_GetHistogramByEnumId(aId, &h, true);
+  if (NS_SUCCEEDED(rv)) {
+    internal_HistogramAdd(*h, aSample, gHistograms[aId].dataset);
+  } else {
+    NS_WARNING("NS_FAILED GetHistogramByEnumId for CHILD");
+  }
+}
+
+void
+internal_AccumulateChildKeyed(mozilla::Telemetry::ID aId,
+                              const nsCString& aKey, uint32_t aSample)
+{
+  if (!gInitDone || !internal_CanRecordBase()) {
+    return;
+  }
+  const HistogramInfo& th = gHistograms[aId];
+  nsCString id;
+  id.Append(th.id());
+  id.AppendLiteral(CHILD_HISTOGRAM_SUFFIX);
+  KeyedHistogram* keyed = internal_GetKeyedHistogramById(id);
+  MOZ_ASSERT(keyed);
+  keyed->Add(aKey, aSample);
+}
+
+} // namespace
+
+
+////////////////////////////////////////////////////////////////////////
+////////////////////////////////////////////////////////////////////////
+//
 // PRIVATE: JSHistogram_* functions
 
 // NOTE: the functions in this section:
 //
 //   internal_JSHistogram_Add
 //   internal_JSHistogram_Snapshot
 //   internal_JSHistogram_Clear
 //   internal_JSHistogram_Dataset
@@ -1097,62 +1483,57 @@ internal_JSHistogram_Add(JSContext *cx, 
   Histogram::ClassType type = h->histogram_type();
 
   JS::CallArgs args = CallArgsFromVp(argc, vp);
 
   if (!internal_CanRecordBase()) {
     return true;
   }
 
-  // If we don't have an argument for the count histogram, assume an increment of 1.
-  // Otherwise, make sure to run some sanity checks on the argument.
+  uint32_t value = 0;
+  mozilla::Telemetry::ID id;
   if ((type == base::CountHistogram::COUNT_HISTOGRAM) && (args.length() == 0)) {
-    internal_HistogramAdd(*h, 1);
-    return true;
-  }
-
-  // For categorical histograms we allow passing a string argument that specifies the label.
-  mozilla::Telemetry::ID id;
-  if (type == base::LinearHistogram::LINEAR_HISTOGRAM &&
+    // If we don't have an argument for the count histogram, assume an increment of 1.
+    // Otherwise, make sure to run some sanity checks on the argument.
+    value = 1;
+  } else if (type == base::LinearHistogram::LINEAR_HISTOGRAM &&
       (args.length() > 0) && args[0].isString() &&
       NS_SUCCEEDED(internal_GetHistogramEnumId(h->histogram_name().c_str(), &id)) &&
       gHistograms[id].histogramType == nsITelemetry::HISTOGRAM_CATEGORICAL) {
+    // For categorical histograms we allow passing a string argument that specifies the label.
     nsAutoJSString label;
     if (!label.init(cx, args[0])) {
       JS_ReportError(cx, "Invalid string parameter");
       return false;
     }
 
-    nsresult rv = internal_HistogramAddCategorical(id, NS_ConvertUTF16toUTF8(label));
+    nsresult rv = gHistograms[id].label_id(NS_ConvertUTF16toUTF8(label).get(), &value);
     if (NS_FAILED(rv)) {
       JS_ReportError(cx, "Unknown label for categorical histogram");
       return false;
     }
-
-    return true;
-  }
+  } else {
+    // All other accumulations expect one numerical argument.
+    if (!args.length()) {
+      JS_ReportError(cx, "Expected one argument");
+      return false;
+    }
 
-  // All other accumulations expect one numerical argument.
-  int32_t value = 0;
-  if (!args.length()) {
-    JS_ReportError(cx, "Expected one argument");
-    return false;
+    if (!(args[0].isNumber() || args[0].isBoolean())) {
+      JS_ReportError(cx, "Not a number");
+      return false;
+    }
+
+    if (!JS::ToUint32(cx, args[0], &value)) {
+      JS_ReportError(cx, "Failed to convert argument");
+      return false;
+    }
   }
 
-  if (!(args[0].isNumber() || args[0].isBoolean())) {
-    JS_ReportError(cx, "Not a number");
-    return false;
-  }
-
-  if (!JS::ToInt32(cx, args[0], &value)) {
-    JS_ReportError(cx, "Failed to convert argument");
-    return false;
-  }
-
-  internal_HistogramAdd(*h, value);
+  internal_Accumulate(*h, value);
   return true;
 }
 
 bool
 internal_JSHistogram_Snapshot(JSContext *cx, unsigned argc, JS::Value *vp)
 {
   JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
   JSObject *obj = JS_THIS_OBJECT(cx, vp);
@@ -1390,17 +1771,17 @@ internal_JSKeyedHistogram_Add(JSContext 
       return false;
     }
 
     if (!JS::ToInt32(cx, args[1], &value)) {
       return false;
     }
   }
 
-  keyed->Add(NS_ConvertUTF16toUTF8(key), value);
+  internal_Accumulate(*keyed, NS_ConvertUTF16toUTF8(key), value);
   return true;
 }
 
 bool
 internal_JSKeyedHistogram_Keys(JSContext *cx, unsigned argc, JS::Value *vp)
 {
   JSObject *obj = JS_THIS_OBJECT(cx, vp);
   if (!obj) {
@@ -1543,191 +1924,18 @@ internal_WrapAndReturnKeyedHistogram(Key
 }
 
 } // namespace
 
 
 ////////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////////
 //
-// PRIVATE: functions related to addon histograms
-
-namespace {
-
-// Compute the name to pass into Histogram for the addon histogram
-// 'name' from the addon 'id'.  We can't use 'name' directly because it
-// might conflict with other histograms in other addons or even with our
-// own.
-void
-internal_AddonHistogramName(const nsACString &id, const nsACString &name,
-                            nsACString &ret)
-{
-  ret.Append(id);
-  ret.Append(':');
-  ret.Append(name);
-}
-
-bool
-internal_CreateHistogramForAddon(const nsACString &name,
-                                 AddonHistogramInfo &info)
-{
-  Histogram *h;
-  nsresult rv = internal_HistogramGet(PromiseFlatCString(name).get(), "never",
-                                      info.histogramType, info.min, info.max,
-                                      info.bucketCount, true, &h);
-  if (NS_FAILED(rv)) {
-    return false;
-  }
-  // Don't let this histogram be reported via the normal means
-  // (e.g. Telemetry.registeredHistograms); we'll make it available in
-  // other ways.
-  h->ClearFlags(Histogram::kUmaTargetedHistogramFlag);
-  info.h = h;
-  return true;
-}
-
-bool
-internal_AddonHistogramReflector(AddonHistogramEntryType *entry,
-                                 JSContext *cx, JS::Handle<JSObject*> obj)
-{
-  AddonHistogramInfo &info = entry->mData;
-
-  // Never even accessed the histogram.
-  if (!info.h) {
-    // Have to force creation of HISTOGRAM_FLAG histograms.
-    if (info.histogramType != nsITelemetry::HISTOGRAM_FLAG)
-      return true;
-
-    if (!internal_CreateHistogramForAddon(entry->GetKey(), info)) {
-      return false;
-    }
-  }
-
-  if (internal_IsEmpty(info.h)) {
-    return true;
-  }
-
-  JS::Rooted<JSObject*> snapshot(cx, JS_NewPlainObject(cx));
-  if (!snapshot) {
-    // Just consider this to be skippable.
-    return true;
-  }
-  switch (internal_ReflectHistogramSnapshot(cx, snapshot, info.h)) {
-  case REFLECT_FAILURE:
-  case REFLECT_CORRUPT:
-    return false;
-  case REFLECT_OK:
-    const nsACString &histogramName = entry->GetKey();
-    if (!JS_DefineProperty(cx, obj, PromiseFlatCString(histogramName).get(),
-                           snapshot, JSPROP_ENUMERATE)) {
-      return false;
-    }
-    break;
-  }
-  return true;
-}
-
-bool
-internal_AddonReflector(AddonEntryType *entry, JSContext *cx,
-                        JS::Handle<JSObject*> obj)
-{
-  const nsACString &addonId = entry->GetKey();
-  JS::Rooted<JSObject*> subobj(cx, JS_NewPlainObject(cx));
-  if (!subobj) {
-    return false;
-  }
-
-  AddonHistogramMapType *map = entry->mData;
-  if (!(map->ReflectIntoJS(internal_AddonHistogramReflector, cx, subobj)
-        && JS_DefineProperty(cx, obj, PromiseFlatCString(addonId).get(),
-                             subobj, JSPROP_ENUMERATE))) {
-    return false;
-  }
-  return true;
-}
-
-} // namespace
-
-
-////////////////////////////////////////////////////////////////////////
-////////////////////////////////////////////////////////////////////////
-//
-// PRIVATE: thread-unsafe helpers for the external interface
-
-namespace {
-
-void
-internal_SetHistogramRecordingEnabled(mozilla::Telemetry::ID aID, bool aEnabled)
-{
-  if (!internal_IsHistogramEnumId(aID)) {
-    MOZ_ASSERT(false, "Telemetry::SetHistogramRecordingEnabled(...) must be used with an enum id");
-    return;
-  }
-
-  if (gHistograms[aID].keyed) {
-    const nsDependentCString id(gHistograms[aID].id());
-    KeyedHistogram* keyed = internal_GetKeyedHistogramById(id);
-    if (keyed) {
-      keyed->SetRecordingEnabled(aEnabled);
-      return;
-    }
-  } else {
-    Histogram *h;
-    nsresult rv = internal_GetHistogramByEnumId(aID, &h);
-    if (NS_SUCCEEDED(rv)) {
-      h->SetRecordingEnabled(aEnabled);
-      return;
-    }
-  }
-
-  MOZ_ASSERT(false, "Telemetry::SetHistogramRecordingEnabled(...) id not found");
-}
-
-void internal_Accumulate(mozilla::Telemetry::ID aHistogram, uint32_t aSample)
-{
-  if (!internal_CanRecordBase()) {
-    return;
-  }
-  Histogram *h;
-  nsresult rv = internal_GetHistogramByEnumId(aHistogram, &h);
-  if (NS_SUCCEEDED(rv)) {
-    internal_HistogramAdd(*h, aSample, gHistograms[aHistogram].dataset);
-  }
-}
-
-void
-internal_Accumulate(mozilla::Telemetry::ID aID,
-                    const nsCString& aKey, uint32_t aSample)
-{
-  if (!gInitDone || !internal_CanRecordBase()) {
-    return;
-  }
-  const HistogramInfo& th = gHistograms[aID];
-  KeyedHistogram* keyed
-     = internal_GetKeyedHistogramById(nsDependentCString(th.id()));
-  MOZ_ASSERT(keyed);
-  keyed->Add(aKey, aSample);
-}
-
-} // namespace
-
-
-////////////////////////////////////////////////////////////////////////
-////////////////////////////////////////////////////////////////////////
-//
 // EXTERNALLY VISIBLE FUNCTIONS in namespace TelemetryHistogram::
 
-// This is a StaticMutex rather than a plain Mutex (1) so that
-// it gets initialised in a thread-safe manner the first time
-// it is used, and (2) because it is never de-initialised, and
-// a normal Mutex would show up as a leak in BloatView.  StaticMutex
-// also has the "OffTheBooks" property, so it won't show as a leak
-// in BloatView.
-static StaticMutex gTelemetryHistogramMutex;
-
 // All of these functions are actually in namespace TelemetryHistogram::,
 // but the ::TelemetryHistogram prefix is given explicitly.  This is
 // because it is critical to see which calls from these functions are
 // to another function in this interface.  Mis-identifying "inwards
 // calls" from "calls to another function in this interface" will lead
 // to deadlocking and/or races.  See comments at the top of the file
 // for further (important!) details.
 
@@ -1781,16 +1989,26 @@ void TelemetryHistogram::InitializeGloba
     if (!h.keyed) {
       continue;
     }
 
     const nsDependentCString id(h.id());
     const nsDependentCString expiration(h.expiration());
     gKeyedHistograms.Put(id, new KeyedHistogram(id, expiration, h.histogramType,
                                                 h.min, h.max, h.bucketCount, h.dataset));
+    if (XRE_IsParentProcess()) {
+      // We must create registered child keyed histograms as well or else the
+      // same code in TelemetrySession.jsm that fails without parent keyed
+      // histograms will fail without child keyed histograms.
+      nsCString childId(id);
+      childId.AppendLiteral(CHILD_HISTOGRAM_SUFFIX);
+      gKeyedHistograms.Put(childId,
+                           new KeyedHistogram(id, expiration, h.histogramType,
+                                              h.min, h.max, h.bucketCount, h.dataset));
+    }
   }
 
   // Some Telemetry histograms depend on the value of C++ constants and hardcode
   // their values in Histograms.json.
   // We add static asserts here for those values to match so that future changes
   // don't go unnoticed.
   // TODO: Compare explicitly with gHistograms[<histogram id>].bucketCount here
   // once we can make gHistograms constexpr (requires VS2015).
@@ -1811,16 +2029,21 @@ void TelemetryHistogram::InitializeGloba
 void TelemetryHistogram::DeInitializeGlobalState()
 {
   StaticMutexAutoLock locker(gTelemetryHistogramMutex);
   gCanRecordBase = false;
   gCanRecordExtended = false;
   gHistogramMap.Clear();
   gKeyedHistograms.Clear();
   gAddonMap.Clear();
+  gAccumulations = nullptr;
+  gKeyedAccumulations = nullptr;
+  if (gIPCTimer) {
+    NS_RELEASE(gIPCTimer);
+  }
   gInitDone = false;
 }
 
 #ifdef DEBUG
 bool TelemetryHistogram::GlobalStateHasBeenInitialized() {
   StaticMutexAutoLock locker(gTelemetryHistogramMutex);
   return gInitDone;
 }
@@ -1916,22 +2139,17 @@ TelemetryHistogram::Accumulate(const cha
   if (!internal_CanRecordBase()) {
     return;
   }
   mozilla::Telemetry::ID id;
   nsresult rv = internal_GetHistogramEnumId(name, &id);
   if (NS_FAILED(rv)) {
     return;
   }
-
-  Histogram *h;
-  rv = internal_GetHistogramByEnumId(id, &h);
-  if (NS_SUCCEEDED(rv)) {
-    internal_HistogramAdd(*h, sample, gHistograms[id].dataset);
-  }
+  internal_Accumulate(id, sample);
 }
 
 void
 TelemetryHistogram::Accumulate(const char* name,
                                const nsCString& key, uint32_t sample)
 {
   StaticMutexAutoLock locker(gTelemetryHistogramMutex);
   if (!internal_CanRecordBase()) {
@@ -1944,17 +2162,62 @@ TelemetryHistogram::Accumulate(const cha
   }
 }
 
 void
 TelemetryHistogram::AccumulateCategorical(mozilla::Telemetry::ID aId,
                                           const nsCString& label)
 {
   StaticMutexAutoLock locker(gTelemetryHistogramMutex);
-  internal_HistogramAddCategorical(aId, label);
+  if (!internal_CanRecordBase()) {
+    return;
+  }
+  uint32_t labelId = 0;
+  if (NS_FAILED(gHistograms[aId].label_id(label.get(), &labelId))) {
+    return;
+  }
+  internal_Accumulate(aId, labelId);
+}
+
+void
+TelemetryHistogram::AccumulateChild(const nsTArray<Accumulation>& aAccumulations)
+{
+  MOZ_ASSERT(XRE_IsParentProcess());
+  StaticMutexAutoLock locker(gTelemetryHistogramMutex);
+  if (!internal_CanRecordBase()) {
+    return;
+  }
+  for (uint32_t i = 0; i < aAccumulations.Length(); ++i) {
+    bool isValid = internal_IsHistogramEnumId(aAccumulations[i].mId);
+    MOZ_ASSERT(isValid);
+    if (!isValid) {
+      continue;
+    }
+    internal_AccumulateChild(aAccumulations[i].mId, aAccumulations[i].mSample);
+  }
+}
+
+void
+TelemetryHistogram::AccumulateChildKeyed(const nsTArray<KeyedAccumulation>& aAccumulations)
+{
+  MOZ_ASSERT(XRE_IsParentProcess());
+  StaticMutexAutoLock locker(gTelemetryHistogramMutex);
+  if (!internal_CanRecordBase()) {
+    return;
+  }
+  for (uint32_t i = 0; i < aAccumulations.Length(); ++i) {
+    bool isValid = internal_IsHistogramEnumId(aAccumulations[i].mId);
+    MOZ_ASSERT(isValid);
+    if (!isValid) {
+      continue;
+    }
+    internal_AccumulateChildKeyed(aAccumulations[i].mId,
+                                  aAccumulations[i].mKey,
+                                  aAccumulations[i].mSample);
+  }
 }
 
 void
 TelemetryHistogram::ClearHistogram(mozilla::Telemetry::ID aId)
 {
   StaticMutexAutoLock locker(gTelemetryHistogramMutex);
   if (!internal_CanRecordBase()) {
     return;
@@ -2053,16 +2316,18 @@ TelemetryHistogram::CreateHistogramSnaps
     }
     const uint32_t type = gHistograms[i].histogramType;
     if (type == nsITelemetry::HISTOGRAM_FLAG ||
         type == nsITelemetry::HISTOGRAM_COUNT) {
       Histogram *h;
       mozilla::DebugOnly<nsresult> rv
          = internal_GetHistogramByEnumId(mozilla::Telemetry::ID(i), &h);
       MOZ_ASSERT(NS_SUCCEEDED(rv));
+      rv = internal_GetHistogramByEnumId(mozilla::Telemetry::ID(i), &h, true);
+      MOZ_ASSERT(NS_SUCCEEDED(rv));
     }
   }
 
   StatisticsRecorder::Histograms hs;
   StatisticsRecorder::GetHistograms(&hs);
 
   // We identify corrupt histograms first, rather than interspersing it
   // in the loop below, to ensure that our corruption statistics don't
@@ -2316,8 +2581,48 @@ TelemetryHistogram::GetHistogramSizesofI
   StatisticsRecorder::GetHistograms(&hs);
   size_t n = 0;
   for (HistogramIterator it = hs.begin(); it != hs.end(); ++it) {
     Histogram *h = *it;
     n += h->SizeOfIncludingThis(aMallocSizeOf);
   }
   return n;
 }
+
+// This method takes the lock only to double-buffer the batched telemetry.
+// It releases the lock before calling out to IPC code which can (and does)
+// Accumulate (which would deadlock)
+//
+// To ensure we don't loop IPCTimerFired->AccumulateChild->arm timer, we don't
+// unset gIPCTimerArmed until the IPC completes
+//
+// This function must be called on the main thread, otherwise IPC will fail.
+void
+TelemetryHistogram::IPCTimerFired(nsITimer* aTimer, void* aClosure)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  nsTArray<Accumulation> accumulationsToSend;
+  nsTArray<KeyedAccumulation> keyedAccumulationsToSend;
+  {
+    StaticMutexAutoLock locker(gTelemetryHistogramMutex);
+    if (gAccumulations) {
+      accumulationsToSend.SwapElements(*gAccumulations);
+    }
+    if (gKeyedAccumulations) {
+      keyedAccumulationsToSend.SwapElements(*gKeyedAccumulations);
+    }
+  }
+
+  mozilla::dom::ContentChild* contentChild = mozilla::dom::ContentChild::GetSingleton();
+  mozilla::Unused << NS_WARN_IF(!contentChild);
+  if (contentChild) {
+    if (accumulationsToSend.Length()) {
+      mozilla::Unused <<
+        NS_WARN_IF(!contentChild->SendAccumulateChildHistogram(accumulationsToSend));
+    }
+    if (keyedAccumulationsToSend.Length()) {
+      mozilla::Unused <<
+        NS_WARN_IF(!contentChild->SendAccumulateChildKeyedHistogram(keyedAccumulationsToSend));
+    }
+  }
+
+  gIPCTimerArmed = false;
+}
--- a/toolkit/components/telemetry/TelemetryHistogram.h
+++ b/toolkit/components/telemetry/TelemetryHistogram.h
@@ -3,16 +3,18 @@
  * 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 TelemetryHistogram_h__
 #define TelemetryHistogram_h__
 
 #include "mozilla/TelemetryHistogramEnums.h"
 
+#include "mozilla/TelemetryComms.h"
+
 // This module is internal to Telemetry.  It encapsulates Telemetry's
 // histogram accumulation and storage logic.  It should only be used by
 // Telemetry.cpp.  These functions should not be used anywhere else.
 // For the public interface to Telemetry functionality, see Telemetry.h.
 
 namespace TelemetryHistogram {
 
 void CreateStatisticsRecorder();
@@ -37,16 +39,19 @@ nsresult SetHistogramRecordingEnabled(co
 void Accumulate(mozilla::Telemetry::ID aHistogram, uint32_t aSample);
 void Accumulate(mozilla::Telemetry::ID aID, const nsCString& aKey,
                                             uint32_t aSample);
 void Accumulate(const char* name, uint32_t sample);
 void Accumulate(const char* name, const nsCString& key, uint32_t sample);
 
 void AccumulateCategorical(mozilla::Telemetry::ID aId, const nsCString& aLabel);
 
+void AccumulateChild(const nsTArray<mozilla::Telemetry::Accumulation>& aAccumulations);
+void AccumulateChildKeyed(const nsTArray<mozilla::Telemetry::KeyedAccumulation>& aAccumulations);
+
 void
 ClearHistogram(mozilla::Telemetry::ID aId);
 
 nsresult
 GetHistogramById(const nsACString &name, JSContext *cx,
                  JS::MutableHandle<JS::Value> ret);
 
 nsresult
@@ -91,11 +96,13 @@ nsresult
 GetAddonHistogramSnapshots(JSContext *cx, JS::MutableHandle<JS::Value> ret);
 
 size_t
 GetMapShallowSizesOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf);
 
 size_t
 GetHistogramSizesofIncludingThis(mozilla::MallocSizeOf aMallocSizeOf);
 
+void
+IPCTimerFired(nsITimer* aTimer, void* aClosure);
 } // namespace TelemetryHistogram
 
 #endif // TelemetryHistogram_h__
--- a/toolkit/components/telemetry/TelemetrySession.jsm
+++ b/toolkit/components/telemetry/TelemetrySession.jsm
@@ -36,33 +36,37 @@ const REASON_ABORTED_SESSION = "aborted-
 const REASON_DAILY = "daily";
 const REASON_SAVED_SESSION = "saved-session";
 const REASON_GATHER_PAYLOAD = "gather-payload";
 const REASON_GATHER_SUBSESSION_PAYLOAD = "gather-subsession-payload";
 const REASON_TEST_PING = "test-ping";
 const REASON_ENVIRONMENT_CHANGE = "environment-change";
 const REASON_SHUTDOWN = "shutdown";
 
+const HISTOGRAM_SUFFIXES = {
+  PARENT: "",
+  CONTENT: "#content",
+}
+
 const ENVIRONMENT_CHANGE_LISTENER = "TelemetrySession::onEnvironmentChange";
 
 const MS_IN_ONE_HOUR  = 60 * 60 * 1000;
 const MIN_SUBSESSION_LENGTH_MS = Preferences.get("toolkit.telemetry.minSubsessionLength", 10 * 60) * 1000;
 
 const LOGGER_NAME = "Toolkit.Telemetry";
 const LOGGER_PREFIX = "TelemetrySession" + (Utils.isContentProcess ? "#content::" : "::");
 
 const PREF_BRANCH = "toolkit.telemetry.";
 const PREF_PREVIOUS_BUILDID = PREF_BRANCH + "previousBuildID";
 const PREF_FHR_UPLOAD_ENABLED = "datareporting.healthreport.uploadEnabled";
 const PREF_ASYNC_PLUGIN_INIT = "dom.ipc.plugins.asyncInit.enabled";
 const PREF_UNIFIED = PREF_BRANCH + "unified";
 
 
 const MESSAGE_TELEMETRY_PAYLOAD = "Telemetry:Payload";
-const MESSAGE_TELEMETRY_GET_CHILD_PAYLOAD = "Telemetry:GetChildPayload";
 const MESSAGE_TELEMETRY_THREAD_HANGS = "Telemetry:ChildThreadHangs";
 const MESSAGE_TELEMETRY_GET_CHILD_THREAD_HANGS = "Telemetry:GetChildThreadHangs";
 const MESSAGE_TELEMETRY_USS = "Telemetry:USS";
 const MESSAGE_TELEMETRY_GET_CHILD_USS = "Telemetry:GetChildUSS";
 
 const DATAREPORTING_DIRECTORY = "datareporting";
 const ABORTED_SESSION_FILE_NAME = "aborted-session-ping";
 
@@ -536,23 +540,16 @@ this.TelemetrySession = Object.freeze({
    * @param reason Optional, the reason to trigger the payload.
    * @param clearSubsession Optional, whether to clear subsession specific data.
    * @returns Object
    */
   getPayload: function(reason, clearSubsession = false) {
     return Impl.getPayload(reason, clearSubsession);
   },
   /**
-   * Asks the content processes to send their payloads.
-   * @returns Object
-   */
-  requestChildPayloads: function() {
-    return Impl.requestChildPayloads();
-  },
-  /**
    * Returns a promise that resolves to an array of thread hang stats from content processes, one entry per process.
    * The structure of each entry is identical to that of "threadHangStats" in nsITelemetry.
    * While thread hang stats are also part of the child payloads, this function is useful for cheaply getting this information,
    * which is useful for realtime hang monitoring.
    * Child processes that do not respond, or spawn/die during execution of this function are excluded from the result.
    * @returns Promise
    */
   getChildThreadHangs: function() {
@@ -879,33 +876,38 @@ var Impl = {
    * @return {Integer} A value from nsITelemetry.DATASET_*.
    */
   getDatasetType: function() {
     return Telemetry.canRecordExtended ? Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN
                                        : Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTOUT;
   },
 
   getHistograms: function getHistograms(subsession, clearSubsession) {
-    this._log.trace("getHistograms - subsession: " + subsession + ", clearSubsession: " + clearSubsession);
+    this._log.trace("getHistograms - subsession: " + subsession +
+                    ", clearSubsession: " + clearSubsession);
 
     let registered =
       Telemetry.registeredHistograms(this.getDatasetType(), []);
+    if (this._testing == false) {
+      // Omit telemetry test histograms outside of tests.
+      registered = registered.filter(n => !n.startsWith("TELEMETRY_TEST_"));
+    }
+    registered = registered.concat(registered.map(n => "STARTUP_" + n));
+
     let hls = subsession ? Telemetry.snapshotSubsessionHistograms(clearSubsession)
                          : Telemetry.histogramSnapshots;
     let ret = {};
 
     for (let name of registered) {
-      for (let n of [name, "STARTUP_" + name]) {
-        if (n in hls) {
-          // Omit telemetry test histograms outside of tests.
-          if (n.startsWith('TELEMETRY_TEST_') && this._testing == false) {
-            this._log.trace("getHistograms - Skipping test histogram: " + n);
-          } else {
-            ret[n] = this.packHistogram(hls[n]);
+      for (let suffix of Object.values(HISTOGRAM_SUFFIXES)) {
+        if (name + suffix in hls) {
+          if (!(suffix in ret)) {
+            ret[suffix] = {};
           }
+          ret[suffix][name] = this.packHistogram(hls[name + suffix]);
         }
       }
     }
 
     return ret;
   },
 
   getAddonHistograms: function getAddonHistograms() {
@@ -923,46 +925,51 @@ var Impl = {
       if (Object.keys(packedHistograms).length != 0)
         ret[addonName] = packedHistograms;
     }
 
     return ret;
   },
 
   getKeyedHistograms: function(subsession, clearSubsession) {
-    this._log.trace("getKeyedHistograms - subsession: " + subsession + ", clearSubsession: " + clearSubsession);
+    this._log.trace("getKeyedHistograms - subsession: " + subsession +
+                    ", clearSubsession: " + clearSubsession);
 
     let registered =
       Telemetry.registeredKeyedHistograms(this.getDatasetType(), []);
+    if (this._testing == false) {
+      // Omit telemetry test histograms outside of tests.
+      registered = registered.filter(id => !id.startsWith("TELEMETRY_TEST_"));
+    }
     let ret = {};
 
     for (let id of registered) {
-      // Omit telemetry test histograms outside of tests.
-      if (id.startsWith('TELEMETRY_TEST_') && this._testing == false) {
-        this._log.trace("getKeyedHistograms - Skipping test histogram: " + id);
-        continue;
-      }
-      let keyed = Telemetry.getKeyedHistogramById(id);
-      let snapshot = null;
-      if (subsession) {
-        snapshot = clearSubsession ? keyed.snapshotSubsessionAndClear()
-                                   : keyed.subsessionSnapshot();
-      } else {
-        snapshot = keyed.snapshot();
-      }
+      for (let suffix of Object.values(HISTOGRAM_SUFFIXES)) {
+        let keyed = Telemetry.getKeyedHistogramById(id + suffix);
+        let snapshot = null;
+        if (subsession) {
+          snapshot = clearSubsession ? keyed.snapshotSubsessionAndClear()
+                                     : keyed.subsessionSnapshot();
+        } else {
+          snapshot = keyed.snapshot();
+        }
 
-      let keys = Object.keys(snapshot);
-      if (keys.length == 0) {
-        // Skip empty keyed histogram.
-        continue;
-      }
+        let keys = Object.keys(snapshot);
+        if (keys.length == 0) {
+          // Skip empty keyed histogram.
+          continue;
+        }
 
-      ret[id] = {};
-      for (let key of keys) {
-        ret[id][key] = this.packHistogram(snapshot[key]);
+        if (!(suffix in ret)) {
+          ret[suffix] = {};
+        }
+        ret[suffix][id] = {};
+        for (let key of keys) {
+          ret[suffix][id][key] = this.packHistogram(snapshot[key]);
+        }
       }
     }
 
     return ret;
   },
 
   getScalars: function (subsession, clearSubsession) {
     this._log.trace("getScalars - subsession: " + subsession + ", clearSubsession: " + clearSubsession);
@@ -1238,53 +1245,58 @@ var Impl = {
   assemblePayloadWithMeasurements: function(simpleMeasurements, info, reason, clearSubsession) {
     const isSubsession = IS_UNIFIED_TELEMETRY && !this._isClassicReason(reason);
     clearSubsession = IS_UNIFIED_TELEMETRY && clearSubsession;
     this._log.trace("assemblePayloadWithMeasurements - reason: " + reason +
                     ", submitting subsession data: " + isSubsession);
 
     // This allows wrapping data retrieval calls in a try-catch block so that
     // failures don't break the rest of the ping assembly.
-    const protect = (fn) => {
+    const protect = (fn, defaultReturn = null) => {
       try {
         return fn();
       } catch (ex) {
         this._log.error("assemblePayloadWithMeasurements - caught exception", ex);
-        return null;
+        return defaultReturn;
       }
     };
 
     // Payload common to chrome and content processes.
     let payloadObj = {
       ver: PAYLOAD_VERSION,
       simpleMeasurements: simpleMeasurements,
-      histograms: protect(() => this.getHistograms(isSubsession, clearSubsession)),
-      keyedHistograms: protect(() => this.getKeyedHistograms(isSubsession, clearSubsession)),
     };
 
     // Add extended set measurements common to chrome & content processes
     if (Telemetry.canRecordExtended) {
       payloadObj.chromeHangs = protect(() => Telemetry.chromeHangs);
       payloadObj.threadHangStats = protect(() => this.getThreadHangStats(Telemetry.threadHangStats));
       payloadObj.log = protect(() => TelemetryLog.entries());
       payloadObj.webrtc = protect(() => Telemetry.webrtcStats);
     }
 
     if (Utils.isContentProcess) {
       return payloadObj;
     }
 
-    // Set the scalars for the parent process.
+    // Additional payload for chrome process.
+    let histograms = protect(() => this.getHistograms(isSubsession, clearSubsession), {});
+    let keyedHistograms = protect(() => this.getKeyedHistograms(isSubsession, clearSubsession), {});
+    payloadObj.histograms = histograms[HISTOGRAM_SUFFIXES.PARENT] || {};
+    payloadObj.keyedHistograms = keyedHistograms[HISTOGRAM_SUFFIXES.PARENT] || {};
     payloadObj.processes = {
       parent: {
         scalars: protect(() => this.getScalars(isSubsession, clearSubsession)),
-      }
+      },
+      content: {
+        histograms: histograms[HISTOGRAM_SUFFIXES.CONTENT],
+        keyedHistograms: keyedHistograms[HISTOGRAM_SUFFIXES.CONTENT],
+      },
     };
 
-    // Additional payload for chrome process.
     payloadObj.info = info;
 
     // Add extended set measurements for chrome process.
     if (Telemetry.canRecordExtended) {
       payloadObj.slowSQL = protect(() => Telemetry.slowSQL);
       payloadObj.fileIOReports = protect(() => Telemetry.fileIOReports);
       payloadObj.lateWrites = protect(() => Telemetry.lateWrites);
 
@@ -1516,17 +1528,16 @@ var Impl = {
     this._testing = testing;
 
     if (!Telemetry.canRecordBase) {
       this._log.trace("setupContentProcess - base recording is disabled, not initializing");
       return;
     }
 
     Services.obs.addObserver(this, "content-child-shutdown", false);
-    cpml.addMessageListener(MESSAGE_TELEMETRY_GET_CHILD_PAYLOAD, this);
     cpml.addMessageListener(MESSAGE_TELEMETRY_GET_CHILD_THREAD_HANGS, this);
     cpml.addMessageListener(MESSAGE_TELEMETRY_GET_CHILD_USS, this);
 
     this.gatherStartupHistograms();
 
     let delayedTask = new DeferredTask(function* () {
       this._initialized = true;
 
@@ -1554,42 +1565,28 @@ var Impl = {
     this._log.trace("receiveMessage - Message name " + message.name);
     switch (message.name) {
     case MESSAGE_TELEMETRY_PAYLOAD:
     {
       // In parent process, receive Telemetry payload from child
       let source = message.data.childUUID;
       delete message.data.childUUID;
 
-      for (let child of this._childTelemetry) {
-        if (child.source === source) {
-          // Update existing telemetry data.
-          child.payload = message.data;
-          return;
-        }
-      }
-      // Did not find existing child in this._childTelemetry.
       this._childTelemetry.push({
         source: source,
         payload: message.data,
       });
 
       if (this._childTelemetry.length == MAX_NUM_CONTENT_PAYLOADS + 1) {
         this._childTelemetry.shift();
         Telemetry.getHistogramById("TELEMETRY_DISCARDED_CONTENT_PINGS_COUNT").add();
       }
 
       break;
     }
-    case MESSAGE_TELEMETRY_GET_CHILD_PAYLOAD:
-    {
-      // In child process, send the requested Telemetry payload
-      this.sendContentProcessPing("saved-session");
-      break;
-    }
     case MESSAGE_TELEMETRY_THREAD_HANGS:
     {
       // Accumulate child thread hang stats from this child
       this._childThreadHangs.push(message.data);
 
       // Check if we've got data from all the children, accounting for child processes dying
       // if it happens before the last response is received and no new child processes are spawned at the exact same time
       // If that happens, we can resolve the promise earlier rather than having to wait for the timeout to expire
@@ -1759,21 +1756,16 @@ var Impl = {
     if (Object.keys(this._slowSQLStartup).length == 0) {
       this.gatherStartupHistograms();
       this._slowSQLStartup = Telemetry.slowSQL;
     }
     this.gatherMemory();
     return this.getSessionPayload(reason, clearSubsession);
   },
 
-  requestChildPayloads: function() {
-    this._log.trace("requestChildPayloads");
-    ppmm.broadcastAsyncMessage(MESSAGE_TELEMETRY_GET_CHILD_PAYLOAD, {});
-  },
-
   getChildThreadHangs: function getChildThreadHangs() {
     return new Promise((resolve) => {
       // Return immediately if there are no child processes to get stats from
       if (ppmm.childCount === 0) {
         resolve([]);
         return;
       }
 
@@ -1841,17 +1833,17 @@ var Impl = {
       this._log.trace("observe - " + aTopic + " notified.");
     }
 
     switch (aTopic) {
     case "content-child-shutdown":
       // content-child-shutdown is only registered for content processes.
       Services.obs.removeObserver(this, "content-child-shutdown");
       this.uninstall();
-
+      Telemetry.flushBatchedChildTelemetry();
       this.sendContentProcessPing(REASON_SAVED_SESSION);
       break;
     case TOPIC_CYCLE_COLLECTOR_BEGIN:
       let now = new Date();
       if (!gLastMemoryPoll
           || (TELEMETRY_INTERVAL <= now - gLastMemoryPoll)) {
         gLastMemoryPoll = now;
         this.gatherMemory();
--- a/toolkit/components/telemetry/moz.build
+++ b/toolkit/components/telemetry/moz.build
@@ -14,16 +14,17 @@ XPIDL_SOURCES += [
 
 XPIDL_MODULE = 'telemetry'
 
 EXPORTS.mozilla += [
     '!TelemetryHistogramEnums.h',
     '!TelemetryScalarEnums.h',
     'ProcessedStack.h',
     'Telemetry.h',
+    'TelemetryComms.h',
     'ThreadHangStats.h',
 ]
 
 SOURCES += [
     'Telemetry.cpp',
     'TelemetryCommon.cpp',
     'TelemetryHistogram.cpp',
     'TelemetryScalar.cpp',
--- a/toolkit/components/telemetry/nsITelemetry.idl
+++ b/toolkit/components/telemetry/nsITelemetry.idl
@@ -391,9 +391,15 @@ interface nsITelemetry : nsISupports
    */
   [implicit_jscontext, optional_argc]
   jsval snapshotScalars(in uint32_t aDataset, [optional] in boolean aClear);
 
   /**
    * Resets all the stored scalars. This is intended to be only used in tests.
    */
   void clearScalars();
+
+  /**
+   * Immediately sends any Telemetry batched on this process to the parent
+   * process. This is intended only to be used on process shutdown.
+   */
+  void flushBatchedChildTelemetry();
 };
--- a/toolkit/components/telemetry/tests/unit/test_ChildHistograms.js
+++ b/toolkit/components/telemetry/tests/unit/test_ChildHistograms.js
@@ -1,12 +1,13 @@
 
 Cu.import("resource://gre/modules/TelemetryController.jsm", this);
 Cu.import("resource://gre/modules/TelemetrySession.jsm", this);
 Cu.import("resource://gre/modules/PromiseUtils.jsm", this);
+Cu.import("resource://testing-common/ContentTaskUtils.jsm", this);
 
 const MESSAGE_TELEMETRY_PAYLOAD = "Telemetry:Payload";
 const MESSAGE_TELEMETRY_GET_CHILD_PAYLOAD = "Telemetry:GetChildPayload";
 const MESSAGE_CHILD_TEST_DONE = "ChildTest:Done";
 
 const PLATFORM_VERSION = "1.9.2";
 const APP_VERSION = "1";
 const APP_ID = "xpcshell@tests.mozilla.org";
@@ -14,38 +15,40 @@ const APP_NAME = "XPCShell";
 
 function run_child_test() {
   // Setup histograms with some fixed values.
   let flagHist = Telemetry.getHistogramById("TELEMETRY_TEST_FLAG");
   flagHist.add(1);
   let countHist = Telemetry.getHistogramById("TELEMETRY_TEST_COUNT");
   countHist.add();
   countHist.add();
+  let categHist = Telemetry.getHistogramById("TELEMETRY_TEST_CATEGORICAL");
+  categHist.add("Label2");
+  categHist.add("Label3");
 
   let flagKeyed = Telemetry.getKeyedHistogramById("TELEMETRY_TEST_KEYED_FLAG");
   flagKeyed.add("a", 1);
   flagKeyed.add("b", 1);
   let countKeyed = Telemetry.getKeyedHistogramById("TELEMETRY_TEST_KEYED_COUNT");
   countKeyed.add("a");
   countKeyed.add("b");
   countKeyed.add("b");
-
-  // Check payload values.
-  const payload = TelemetrySession.getPayload("test-ping");
-  check_histogram_values(payload);
 }
 
 function check_histogram_values(payload) {
   const hs = payload.histograms;
   Assert.ok("TELEMETRY_TEST_COUNT" in hs, "Should have count test histogram.");
   Assert.ok("TELEMETRY_TEST_FLAG" in hs, "Should have flag test histogram.");
+  Assert.ok("TELEMETRY_TEST_CATEGORICAL" in hs, "Should have categorical test histogram.");
   Assert.equal(hs["TELEMETRY_TEST_COUNT"].sum, 2,
                "Count test histogram should have the right value.");
   Assert.equal(hs["TELEMETRY_TEST_FLAG"].sum, 1,
                "Flag test histogram should have the right value.");
+  Assert.equal(hs["TELEMETRY_TEST_CATEGORICAL"].sum, 3,
+               "Categorical test histogram should have the right sum.");
 
   const kh = payload.keyedHistograms;
   Assert.ok("TELEMETRY_TEST_KEYED_COUNT" in kh, "Should have keyed count test histogram.");
   Assert.ok("TELEMETRY_TEST_KEYED_FLAG" in kh, "Should have keyed flag test histogram.");
   Assert.equal(kh["TELEMETRY_TEST_KEYED_COUNT"]["a"].sum, 1,
                "Keyed count test histogram should have the right value.");
   Assert.equal(kh["TELEMETRY_TEST_KEYED_COUNT"]["b"].sum, 2,
                "Keyed count test histogram should have the right value.");
@@ -56,18 +59,16 @@ function check_histogram_values(payload)
 }
 
 add_task(function*() {
   if (!runningInParent) {
     TelemetryController.testSetupContent();
     run_child_test();
     dump("... done with child test\n");
     do_send_remote_message(MESSAGE_CHILD_TEST_DONE);
-    dump("... waiting for child payload collection\n");
-    yield do_await_remote_message(MESSAGE_TELEMETRY_GET_CHILD_PAYLOAD);
     return;
   }
 
   // Setup.
   do_get_profile(true);
   loadAddonManager(APP_ID, APP_NAME, APP_VERSION, PLATFORM_VERSION);
   Services.prefs.setBoolPref(PREF_TELEMETRY_ENABLED, true);
   yield TelemetryController.testSetup();
@@ -75,25 +76,25 @@ add_task(function*() {
     // Make sure we don't generate unexpected pings due to pref changes.
     yield setEmptyPrefWatchlist();
   }
 
   // Run test in child, don't wait for it to finish.
   let childPromise = run_test_in_child("test_ChildHistograms.js");
   yield do_await_remote_message(MESSAGE_CHILD_TEST_DONE);
 
-  // Gather payload from child.
-  dump("... requesting child payloads\n");
-  let promiseMessage = do_await_remote_message(MESSAGE_TELEMETRY_PAYLOAD);
-  TelemetrySession.requestChildPayloads();
-  yield promiseMessage;
-  dump("... received child payload\n");
-
-  // Check child payload.
+  yield ContentTaskUtils.waitForCondition(() => {
+    let payload = TelemetrySession.getPayload("test-ping");
+    return payload &&
+           "processes" in payload &&
+           "content" in payload.processes &&
+           "histograms" in payload.processes.content &&
+           "TELEMETRY_TEST_COUNT" in payload.processes.content.histograms;
+  });
   const payload = TelemetrySession.getPayload("test-ping");
-  Assert.ok("childPayloads" in payload, "Should have child payloads.");
-  Assert.equal(payload.childPayloads.length, 1, "Should have received one child payload so far.");
-  Assert.ok("histograms" in payload.childPayloads[0], "Child payload should have histograms.");
-  Assert.ok("keyedHistograms" in payload.childPayloads[0], "Child payload should have keyed histograms.");
-  check_histogram_values(payload.childPayloads[0]);
+  Assert.ok("processes" in payload, "Should have processes section");
+  Assert.ok("content" in payload.processes, "Should have child process section");
+  Assert.ok("histograms" in payload.processes.content, "Child process section should have histograms.");
+  Assert.ok("keyedHistograms" in payload.processes.content, "Child process section should have keyed histograms.");
+  check_histogram_values(payload.processes.content);
 
   do_test_finished();
 });
--- a/toolkit/content/aboutTelemetry.css
+++ b/toolkit/content/aboutTelemetry.css
@@ -224,16 +224,24 @@ body[dir="rtl"] .copy-node {
   padding-inline-start: 10em;
   display: none;
 }
 
 .has-data.expanded .filter-ui {
   display: inline;
 }
 
+.processes-ui {
+  display: none;
+}
+
+.has-data.expanded .processes-ui {
+  display: initial;
+}
+
 .filter-blocked {
   display: none;
 }
 
 #raw-ping-data-section {
   width: 100%;
   height: 100%;
   background-color:-moz-Dialog;
@@ -252,8 +260,12 @@ body[dir="rtl"] .copy-node {
   padding: 5px 10px;
 }
 
 /* addon subsection style */
 .addon-caption {
   font-size: larger;
   margin: 5px 0;
 }
+
+.process-picker {
+  margin: 0 0.5em;
+}
--- a/toolkit/content/aboutTelemetry.js
+++ b/toolkit/content/aboutTelemetry.js
@@ -301,16 +301,20 @@ var PingPicker = {
     }, false);
 
     document.getElementById("newer-ping")
             .addEventListener("click", () => this._movePingIndex(-1), false);
     document.getElementById("older-ping")
             .addEventListener("click", () => this._movePingIndex(1), false);
     document.getElementById("choose-payload")
             .addEventListener("change", () => displayPingData(gPingData), false);
+    document.getElementById("histograms-processes")
+            .addEventListener("change", () => displayPingData(gPingData), false);
+    document.getElementById("keyed-histograms-processes")
+            .addEventListener("change", () => displayPingData(gPingData), false);
   },
 
   onPingSourceChanged: function() {
     this.update();
   },
 
   onPingDisplayChanged: function() {
     this.update();
@@ -1777,16 +1781,43 @@ function sortStartupMilestones(aSimpleMe
   let result = {};
   for (let key of sortedKeys) {
     result[key] = aSimpleMeasurements[key];
   }
 
   return result;
 }
 
+function renderProcessList(ping, selectEl) {
+  removeAllChildNodes(selectEl);
+  let option = document.createElement("option");
+  option.appendChild(document.createTextNode("parent"));
+  option.setAttribute("value", "");
+  option.selected = true;
+  selectEl.appendChild(option);
+
+  if (!("processes" in ping.payload)) {
+    selectEl.disabled = true;
+    return;
+  }
+  selectEl.disabled = false;
+
+  for (let process of Object.keys(ping.payload.processes)) {
+    // TODO: parent hgrams are on root payload, not in payload.processes.parent
+    // When/If that gets moved, you'll need to remove this:
+    if (process === "parent") {
+      continue;
+    }
+    option = document.createElement("option");
+    option.appendChild(document.createTextNode(process));
+    option.setAttribute("value", process);
+    selectEl.appendChild(option);
+  }
+}
+
 function renderPayloadList(ping) {
   // Rebuild the payload select with options:
   //   Parent Payload (selected)
   //   Child Payload 1..ping.payload.childPayloads.length
   let listEl = document.getElementById("choose-payload");
   removeAllChildNodes(listEl);
 
   let option = document.createElement("option");
@@ -1845,19 +1876,21 @@ function displayPingData(ping, updatePay
   // Render raw ping data.
   let pre = document.getElementById("raw-ping-data");
   pre.textContent = JSON.stringify(gPingData, null, 2);
 
   // Update the structured data rendering.
   const keysHeader = bundle.GetStringFromName("keysHeader");
   const valuesHeader = bundle.GetStringFromName("valuesHeader");
 
-  // Update the payload list
+  // Update the payload list and process lists
   if (updatePayloadList) {
     renderPayloadList(ping);
+    renderProcessList(ping, document.getElementById("histograms-processes"));
+    renderProcessList(ping, document.getElementById("keyed-histograms-processes"));
   }
 
   // Show general data.
   GeneralData.render(ping);
 
   // Show environment data.
   EnvironmentData.render(ping);
 
@@ -1924,18 +1957,28 @@ function displayPingData(ping, updatePay
   // Show scalar data.
   Scalars.render(payload);
 
   // Show histogram data
   let hgramDiv = document.getElementById("histograms");
   removeAllChildNodes(hgramDiv);
 
   let histograms = payload.histograms;
+
+  let hgramsSelect = document.getElementById("histograms-processes");
+  let hgramsOption = hgramsSelect.selectedOptions.item(0);
+  let hgramsProcess = hgramsOption.getAttribute("value");
+  if (hgramsProcess &&
+      "processes" in ping.payload &&
+      hgramsProcess in ping.payload.processes) {
+    histograms = ping.payload.processes[hgramsProcess].histograms;
+  }
+
   hasData = Object.keys(histograms).length > 0;
-  setHasData("histograms-section", hasData);
+  setHasData("histograms-section", hasData || hgramsSelect.options.length);
 
   if (hasData) {
     for (let [name, hgram] of Object.entries(histograms)) {
       Histogram.render(hgramDiv, name, hgram, {unpacked: true});
     }
 
     let filterBox = document.getElementById("histograms-filter");
     filterBox.addEventListener("input", Histogram.histogramFilterChanged, false);
@@ -1945,27 +1988,37 @@ function displayPingData(ping, updatePay
 
     setHasData("histograms-section", true);
   }
 
   // Show keyed histogram data
   let keyedDiv = document.getElementById("keyed-histograms");
   removeAllChildNodes(keyedDiv);
 
-  setHasData("keyed-histograms-section", false);
   let keyedHistograms = payload.keyedHistograms;
+
+  let keyedHgramsSelect = document.getElementById("keyed-histograms-processes");
+  let keyedHgramsOption = keyedHgramsSelect.selectedOptions.item(0);
+  let keyedHgramsProcess = keyedHgramsOption.getAttribute("value");
+  if (keyedHgramsProcess &&
+      "processes" in ping.payload &&
+      keyedHgramsProcess in ping.payload.processes) {
+    keyedHistograms = ping.payload.processes[keyedHgramsProcess].keyedHistograms;
+  }
+
+  setHasData("keyed-histograms-section", keyedHgramsSelect.options.length);
   if (keyedHistograms) {
     let hasData = false;
     for (let [id, keyed] of Object.entries(keyedHistograms)) {
       if (Object.keys(keyed).length > 0) {
         hasData = true;
         KeyedHistogram.render(keyedDiv, id, keyed, {unpacked: true});
       }
     }
-    setHasData("keyed-histograms-section", hasData);
+    setHasData("keyed-histograms-section", hasData || keyedHgramsSelect.options.length);
   }
 
   // Show addon histogram data
   let addonDiv = document.getElementById("addon-histograms");
   removeAllChildNodes(addonDiv);
 
   let addonHistogramsRendered = false;
   let addonData = payload.addonHistograms;
--- a/toolkit/content/aboutTelemetry.xhtml
+++ b/toolkit/content/aboutTelemetry.xhtml
@@ -147,25 +147,31 @@
       <section id="histograms-section" class="data-section">
         <input type="checkbox" class="statebox"/>
         <h1 class="section-name">&aboutTelemetry.histogramsSection;</h1>
         <span class="toggle-caption">&aboutTelemetry.toggle;</span>
         <span class="empty-caption">&aboutTelemetry.emptySection;</span>
         <span class="filter-ui">
           &aboutTelemetry.filterText; <input type="text" class="filter" id="histograms-filter" target_id="histograms"/>
         </span>
+        <div class="processes-ui">
+          <select id="histograms-processes" class="process-picker"></select>
+        </div>
         <div id="histograms" class="data">
         </div>
       </section>
 
       <section id="keyed-histograms-section" class="data-section">
         <input type="checkbox" class="statebox"/>
         <h1 class="section-name">&aboutTelemetry.keyedHistogramsSection;</h1>
         <span class="toggle-caption">&aboutTelemetry.toggle;</span>
         <span class="empty-caption">&aboutTelemetry.emptySection;</span>
+        <div class="processes-ui">
+          <select id="keyed-histograms-processes" class="process-picker"></select>
+        </div>
         <div id="keyed-histograms" class="data">
         </div>
       </section>
 
       <section id="simple-measurements-section" class="data-section">
         <input type="checkbox" class="statebox"/>
         <h1 class="section-name">&aboutTelemetry.simpleMeasurementsSection;</h1>
         <span class="toggle-caption">&aboutTelemetry.toggle;</span>
--- a/tools/lint/flake8.lint
+++ b/tools/lint/flake8.lint
@@ -45,19 +45,22 @@ The offset is of the form (lineno_offset
 to the lineoffset property of `ResultContainer`.
 """
 
 EXTENSIONS = ['.py', '.lint']
 results = []
 
 
 def process_line(line):
+    # Escape slashes otherwise JSON conversion will not work
+    line = line.replace('\\', '\\\\')
     try:
         res = json.loads(line)
     except ValueError:
+        print('Non JSON output from linter, will not be processed: {}'.format(line))
         return
 
     if 'code' in res:
         if res['code'].startswith('W'):
             res['level'] = 'warning'
 
         if res['code'] in LINE_OFFSETS:
             res['lineoffset'] = LINE_OFFSETS[res['code']]
--- a/widget/WidgetEventImpl.cpp
+++ b/widget/WidgetEventImpl.cpp
@@ -1,13 +1,14 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
+#include "gfxPrefs.h"
 #include "mozilla/BasicEvents.h"
 #include "mozilla/ContentEvents.h"
 #include "mozilla/InternalMutationEvent.h"
 #include "mozilla/MiscEvents.h"
 #include "mozilla/MouseEvents.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/TextEvents.h"
 #include "mozilla/TouchEvents.h"
@@ -407,45 +408,25 @@ WidgetInputEvent::AccelModifier()
   }
   return sAccelModifier;
 }
 
 /******************************************************************************
  * mozilla::WidgetWheelEvent (MouseEvents.h)
  ******************************************************************************/
 
-bool WidgetWheelEvent::sInitialized = false;
-bool WidgetWheelEvent::sIsSystemScrollSpeedOverrideEnabled = false;
-int32_t WidgetWheelEvent::sOverrideFactorX = 0;
-int32_t WidgetWheelEvent::sOverrideFactorY = 0;
-
-/* static */ void
-WidgetWheelEvent::Initialize()
-{
-  if (sInitialized) {
-    return;
-  }
-
-  Preferences::AddBoolVarCache(&sIsSystemScrollSpeedOverrideEnabled,
-    "mousewheel.system_scroll_override_on_root_content.enabled", false);
-  Preferences::AddIntVarCache(&sOverrideFactorX,
-    "mousewheel.system_scroll_override_on_root_content.horizontal.factor", 0);
-  Preferences::AddIntVarCache(&sOverrideFactorY,
-    "mousewheel.system_scroll_override_on_root_content.vertical.factor", 0);
-  sInitialized = true;
-}
-
 /* static */ double
 WidgetWheelEvent::ComputeOverriddenDelta(double aDelta, bool aIsForVertical)
 {
-  Initialize();
-  if (!sIsSystemScrollSpeedOverrideEnabled) {
+  if (!gfxPrefs::MouseWheelHasRootScrollDeltaOverride()) {
     return aDelta;
   }
-  int32_t intFactor = aIsForVertical ? sOverrideFactorY : sOverrideFactorX;
+  int32_t intFactor = aIsForVertical
+                      ? gfxPrefs::MouseWheelRootScrollVerticalFactor()
+                      : gfxPrefs::MouseWheelRootScrollHorizontalFactor();
   // Making the scroll speed slower doesn't make sense. So, ignore odd factor
   // which is less than 1.0.
   if (intFactor <= 100) {
     return aDelta;
   }
   double factor = static_cast<double>(intFactor) / 100;
   return aDelta * factor;
 }
--- a/widget/windows/nsWindow.cpp
+++ b/widget/windows/nsWindow.cpp
@@ -1091,17 +1091,17 @@ float nsWindow::GetDPI()
   return float(heightPx/heightInches);
 }
 
 double nsWindow::GetDefaultScaleInternal()
 {
   if (mDefaultScale <= 0.0) {
     mDefaultScale = WinUtils::LogToPhysFactor(mWnd);
   }
-  return WinUtils::LogToPhysFactor(mWnd);
+  return mDefaultScale;
 }
 
 int32_t nsWindow::LogToPhys(double aValue)
 {
   return WinUtils::LogToPhys(::MonitorFromWindow(mWnd,
                                                  MONITOR_DEFAULTTOPRIMARY),
                              aValue);
 }
@@ -2780,18 +2780,18 @@ NS_IMETHODIMP nsWindow::SetCursor(imgICo
   // Reject cursors greater than 128 pixels in either direction, to prevent
   // spoofing.
   // XXX ideally we should rescale. Also, we could modify the API to
   // allow trusted content to set larger cursors.
   if (width > 128 || height > 128)
     return NS_ERROR_NOT_AVAILABLE;
 
   HCURSOR cursor;
-  // No scaling
-  IntSize size(0, 0);
+  double scale = GetDefaultScale().scale;
+  IntSize size = RoundedToInt(Size(width * scale, height * scale));
   rv = nsWindowGfx::CreateIcon(aCursor, true, aHotspotX, aHotspotY, size, &cursor);
   NS_ENSURE_SUCCESS(rv, rv);
 
   mCursor = nsCursor(-1);
   ::SetCursor(cursor);
 
   NS_IF_RELEASE(sCursorImgContainer);
   sCursorImgContainer = aCursor;