Merge m-c to b2g-inbound. a=merge
authorRyan VanderMeulen <ryanvm@gmail.com>
Thu, 26 Mar 2015 14:48:20 -0400
changeset 266150 02755adcb1284dfcaa51135c365dc08dd123b6e8
parent 266149 bb826e1f6e3100d0833ff7abe4bec037b0bab360 (current diff)
parent 266141 d5a537c7c5582d7edcdf83a3705d888259c62a6e (diff)
child 266151 8169d9fae061e02d2d2eaa32962fecc2eb86892b
push id830
push userraliiev@mozilla.com
push dateFri, 19 Jun 2015 19:24:37 +0000
treeherdermozilla-release@932614382a68 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone39.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge m-c to b2g-inbound. a=merge
browser/devtools/framework/test/browser_toolbox_options_devedition.js
browser/devtools/webide/themes/prefs.css
--- a/accessible/atk/nsMaiInterfaceHypertext.cpp
+++ b/accessible/atk/nsMaiInterfaceHypertext.cpp
@@ -5,69 +5,91 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "InterfaceInitFuncs.h"
 
 #include "Accessible-inl.h"
 #include "HyperTextAccessible.h"
 #include "nsMai.h"
 #include "nsMaiHyperlink.h"
+#include "ProxyAccessible.h"
 #include "mozilla/Likely.h"
 
+
 using namespace mozilla::a11y;
 
 extern "C" {
 
 static AtkHyperlink*
 getLinkCB(AtkHypertext *aText, gint aLinkIndex)
 {
   AccessibleWrap* accWrap = GetAccessibleWrap(ATK_OBJECT(aText));
-  if (!accWrap)
-    return nullptr;
+  if (accWrap) {
+    HyperTextAccessible* hyperText = accWrap->AsHyperText();
+    NS_ENSURE_TRUE(hyperText, nullptr);
+
+    Accessible* hyperLink = hyperText->LinkAt(aLinkIndex);
+    if (!hyperLink) {
+      return nullptr;
+    }
 
-  HyperTextAccessible* hyperText = accWrap->AsHyperText();
-  NS_ENSURE_TRUE(hyperText, nullptr);
+    AtkObject* hyperLinkAtkObj = AccessibleWrap::GetAtkObject(hyperLink);
+    AccessibleWrap* accChild = GetAccessibleWrap(hyperLinkAtkObj);
+    NS_ENSURE_TRUE(accChild, nullptr);
 
-  Accessible* hyperLink = hyperText->LinkAt(aLinkIndex);
-  if (!hyperLink)
+    MaiHyperlink* maiHyperlink = accChild->GetMaiHyperlink();
+    NS_ENSURE_TRUE(maiHyperlink, nullptr);
+    return maiHyperlink->GetAtkHyperlink();
+  }
+
+  if (ProxyAccessible* proxy = GetProxy(ATK_OBJECT(aText))) {
+    ProxyAccessible* proxyLink = proxy->LinkAt(aLinkIndex);
+    if (proxyLink) {
+      NS_WARNING("IMPLEMENT ME! See bug 1146518.");
+      // We should somehow get from ProxyAccessible* to AtkHyperlink*.
+    }
     return nullptr;
+  }
 
-  AtkObject* hyperLinkAtkObj = AccessibleWrap::GetAtkObject(hyperLink);
-  AccessibleWrap* accChild = GetAccessibleWrap(hyperLinkAtkObj);
-  NS_ENSURE_TRUE(accChild, nullptr);
-
-  MaiHyperlink *maiHyperlink = accChild->GetMaiHyperlink();
-  NS_ENSURE_TRUE(maiHyperlink, nullptr);
-  return maiHyperlink->GetAtkHyperlink();
+  return nullptr;
 }
 
 static gint
 getLinkCountCB(AtkHypertext *aText)
 {
   AccessibleWrap* accWrap = GetAccessibleWrap(ATK_OBJECT(aText));
-  if (!accWrap)
-    return -1;
+  if (accWrap) {
+    HyperTextAccessible* hyperText = accWrap->AsHyperText();
+    NS_ENSURE_TRUE(hyperText, -1);
+    return hyperText->LinkCount();
+  }
 
-  HyperTextAccessible* hyperText = accWrap->AsHyperText();
-  NS_ENSURE_TRUE(hyperText, -1);
+  if (ProxyAccessible* proxy = GetProxy(ATK_OBJECT(aText))) {
+    return proxy->LinkCount();
+  }
 
-  return hyperText->LinkCount();
+  return -1;
 }
 
 static gint
 getLinkIndexCB(AtkHypertext *aText, gint aCharIndex)
 {
   AccessibleWrap* accWrap = GetAccessibleWrap(ATK_OBJECT(aText));
-  if (!accWrap)
-    return -1;
+  if (accWrap) {
+    HyperTextAccessible* hyperText = accWrap->AsHyperText();
+    NS_ENSURE_TRUE(hyperText, -1);
 
-  HyperTextAccessible* hyperText = accWrap->AsHyperText();
-  NS_ENSURE_TRUE(hyperText, -1);
+    return hyperText->LinkIndexAtOffset(aCharIndex);
+  }
 
-  return hyperText->LinkIndexAtOffset(aCharIndex);
+  if (ProxyAccessible* proxy = GetProxy(ATK_OBJECT(aText))) {
+    return proxy->LinkIndexAtOffset(aCharIndex);
+  }
+
+  return -1;
 }
 }
 
 void
 hypertextInterfaceInitCB(AtkHypertextIface* aIface)
 {
   NS_ASSERTION(aIface, "no interface!");
   if (MOZ_UNLIKELY(!aIface))
--- a/accessible/atk/nsMaiInterfaceSelection.cpp
+++ b/accessible/atk/nsMaiInterfaceSelection.cpp
@@ -4,100 +4,139 @@
  * 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 "InterfaceInitFuncs.h"
 
 #include "Accessible-inl.h"
 #include "AccessibleWrap.h"
 #include "nsMai.h"
+#include "ProxyAccessible.h"
 #include "mozilla/Likely.h"
 
 #include <atk/atk.h>
 
 using namespace mozilla::a11y;
 
 extern "C" {
 
 static gboolean
 addSelectionCB(AtkSelection *aSelection, gint i)
 {
   AccessibleWrap* accWrap = GetAccessibleWrap(ATK_OBJECT(aSelection));
-  if (!accWrap || !accWrap->IsSelect())
-    return FALSE;
+  if (accWrap && accWrap->IsSelect()) {
+    return accWrap->AddItemToSelection(i);
+  }
 
-  return accWrap->AddItemToSelection(i);
+  if (ProxyAccessible* proxy = GetProxy(ATK_OBJECT(aSelection))) {
+    return proxy->AddItemToSelection(i);
+  }
+
+  return FALSE;
 }
 
 static gboolean
 clearSelectionCB(AtkSelection *aSelection)
 {
   AccessibleWrap* accWrap = GetAccessibleWrap(ATK_OBJECT(aSelection));
-  if (!accWrap || !accWrap->IsSelect())
-    return FALSE;
+  if (accWrap && accWrap->IsSelect()) {
+    return accWrap->UnselectAll();
+  }
 
-  return accWrap->UnselectAll();
+  if (ProxyAccessible* proxy = GetProxy(ATK_OBJECT(aSelection))) {
+    return proxy->UnselectAll();
+  }
+
+  return FALSE;
 }
 
 static AtkObject*
 refSelectionCB(AtkSelection *aSelection, gint i)
 {
+  AtkObject* atkObj = nullptr;
   AccessibleWrap* accWrap = GetAccessibleWrap(ATK_OBJECT(aSelection));
-  if (!accWrap || !accWrap->IsSelect())
-    return nullptr;
+  if (accWrap && accWrap->IsSelect()) {
+    Accessible* selectedItem = accWrap->GetSelectedItem(i);
+    if (!selectedItem) {
+      return nullptr;
+    }
 
-  Accessible* selectedItem = accWrap->GetSelectedItem(i);
-  if (!selectedItem)
-    return nullptr;
+    atkObj = AccessibleWrap::GetAtkObject(selectedItem);
+  } else if (ProxyAccessible* proxy = GetProxy(ATK_OBJECT(aSelection))) {
+    ProxyAccessible* selectedItem = proxy->GetSelectedItem(i);
+    if (selectedItem) {
+      atkObj = GetWrapperFor(selectedItem);
+    }
+  }
 
-  AtkObject* atkObj = AccessibleWrap::GetAtkObject(selectedItem);
-  if (atkObj)
+  if (atkObj) {
     g_object_ref(atkObj);
+  }
 
   return atkObj;
 }
 
 static gint
 getSelectionCountCB(AtkSelection *aSelection)
 {
   AccessibleWrap* accWrap = GetAccessibleWrap(ATK_OBJECT(aSelection));
-  if (!accWrap || !accWrap->IsSelect())
-    return -1;
+  if (accWrap && accWrap->IsSelect()) {
+    return accWrap->SelectedItemCount();
+  }
 
-  return accWrap->SelectedItemCount();
+  if (ProxyAccessible* proxy = GetProxy(ATK_OBJECT(aSelection))) {
+    return proxy->SelectedItemCount();
+  }
+
+  return -1;
 }
 
 static gboolean
 isChildSelectedCB(AtkSelection *aSelection, gint i)
 {
   AccessibleWrap* accWrap = GetAccessibleWrap(ATK_OBJECT(aSelection));
-  if (!accWrap || !accWrap->IsSelect())
-    return FALSE;
+  if (accWrap && accWrap->IsSelect()) {
+    return accWrap->IsItemSelected(i);
+  }
 
-  return accWrap->IsItemSelected(i);
+  if (ProxyAccessible* proxy = GetProxy(ATK_OBJECT(aSelection))) {
+    return proxy->IsItemSelected(i);
+  }
+
+  return FALSE;
 }
 
 static gboolean
 removeSelectionCB(AtkSelection *aSelection, gint i)
 {
   AccessibleWrap* accWrap = GetAccessibleWrap(ATK_OBJECT(aSelection));
-  if (!accWrap || !accWrap->IsSelect())
-    return FALSE;
+  if (accWrap && accWrap->IsSelect()) {
+    return accWrap->RemoveItemFromSelection(i);
+  }
 
-  return accWrap->RemoveItemFromSelection(i);
+  if (ProxyAccessible* proxy = GetProxy(ATK_OBJECT(aSelection))) {
+    return proxy->RemoveItemFromSelection(i);
+  }
+
+  return FALSE;
 }
 
 static gboolean
 selectAllSelectionCB(AtkSelection *aSelection)
 {
   AccessibleWrap* accWrap = GetAccessibleWrap(ATK_OBJECT(aSelection));
-  if (!accWrap || !accWrap->IsSelect())
-    return FALSE;
+  if (accWrap && accWrap->IsSelect()) {
+    return accWrap->SelectAll();
+  }
 
-  return accWrap->SelectAll();
+  if (ProxyAccessible* proxy = GetProxy(ATK_OBJECT(aSelection))) {
+    return proxy->SelectAll();
+  }
+
+  return FALSE;
 }
 }
 
 void
 selectionInterfaceInitCB(AtkSelectionIface* aIface)
 {
   NS_ASSERTION(aIface, "Invalid aIface");
   if (MOZ_UNLIKELY(!aIface))
--- a/accessible/html/HTMLTableAccessible.cpp
+++ b/accessible/html/HTMLTableAccessible.cpp
@@ -309,44 +309,38 @@ HTMLTableHeaderCellAccessible::NativeRol
     case 0:
     case 1:
       return roles::COLUMNHEADER;
     case 2:
     case 3:
       return roles::ROWHEADER;
   }
 
-  // Assume it's columnheader if there are headers in siblings, otherwise
-  // rowheader.
-  // This should iterate the flattened tree
-  nsIContent* parentContent = mContent->GetParent();
-  if (!parentContent) {
-    NS_ERROR("Deattached content on alive accessible?");
+  TableAccessible* table = Table();
+  if (!table)
     return roles::NOTHING;
-  }
+
+  // If the cell next to this one is not a header cell then assume this cell is
+  // a row header for it.
+  uint32_t rowIdx = RowIdx(), colIdx = ColIdx();
+  Accessible* cell = table->CellAt(rowIdx, colIdx + ColExtent());
+  if (cell && !nsCoreUtils::IsHTMLTableHeader(cell->GetContent()))
+    return roles::ROWHEADER;
 
-  for (nsIContent* siblingContent = mContent->GetPreviousSibling(); siblingContent;
-       siblingContent = siblingContent->GetPreviousSibling()) {
-    if (siblingContent->IsElement()) {
-      return nsCoreUtils::IsHTMLTableHeader(siblingContent) ?
-        roles::COLUMNHEADER : roles::ROWHEADER;
-    }
-  }
+  // If the cell below this one is not a header cell then assume this cell is
+  // a column header for it.
+  uint32_t rowExtent = RowExtent();
+  cell = table->CellAt(rowIdx + rowExtent, colIdx);
+  if (cell && !nsCoreUtils::IsHTMLTableHeader(cell->GetContent()))
+    return roles::COLUMNHEADER;
 
-  for (nsIContent* siblingContent = mContent->GetNextSibling(); siblingContent;
-       siblingContent = siblingContent->GetNextSibling()) {
-    if (siblingContent->IsElement()) {
-      return nsCoreUtils::IsHTMLTableHeader(siblingContent) ?
-       roles::COLUMNHEADER : roles::ROWHEADER;
-    }
-  }
-
-  // No elements in siblings what means the table has one column only. Therefore
-  // it should be column header.
-  return roles::COLUMNHEADER;
+  // Otherwise if this cell is surrounded by header cells only then make a guess
+  // based on its cell spanning. In other words if it is row spanned then assume
+  // it's a row header, otherwise it's a column header.
+  return rowExtent > 1 ? roles::ROWHEADER : roles::COLUMNHEADER;
 }
 
 
 ////////////////////////////////////////////////////////////////////////////////
 // HTMLTableRowAccessible
 ////////////////////////////////////////////////////////////////////////////////
 
 NS_IMPL_ISUPPORTS_INHERITED0(HTMLTableRowAccessible, Accessible)
--- a/accessible/ipc/DocAccessibleChild.cpp
+++ b/accessible/ipc/DocAccessibleChild.cpp
@@ -6,16 +6,18 @@
 
 #include "DocAccessibleChild.h"
 
 #include "Accessible-inl.h"
 #include "ProxyAccessible.h"
 #include "Relation.h"
 #include "HyperTextAccessible-inl.h"
 #include "ImageAccessible.h"
+#include "TableAccessible.h"
+#include "TableCellAccessible.h"
 #include "nsIPersistentProperties2.h"
 #include "nsISimpleEnumerator.h"
 
 namespace mozilla {
 namespace a11y {
 
 static uint32_t
 InterfacesFor(Accessible* aAcc)
@@ -47,31 +49,58 @@ SerializeTree(Accessible* aRoot, nsTArra
 }
 
 Accessible*
 DocAccessibleChild::IdToAccessible(const uint64_t& aID) const
 {
   return mDoc->GetAccessibleByUniqueID(reinterpret_cast<void*>(aID));
 }
 
+Accessible*
+DocAccessibleChild::IdToAccessibleLink(const uint64_t& aID) const
+{
+  Accessible* acc = IdToAccessible(aID);
+  return acc && acc->IsLink() ? acc : nullptr;
+}
+
+Accessible*
+DocAccessibleChild::IdToAccessibleSelect(const uint64_t& aID) const
+{
+  Accessible* acc = IdToAccessible(aID);
+  return acc && acc->IsSelect() ? acc : nullptr;
+}
+
 HyperTextAccessible*
 DocAccessibleChild::IdToHyperTextAccessible(const uint64_t& aID) const
 {
   Accessible* acc = IdToAccessible(aID);
-  MOZ_ASSERT(!acc || acc->IsHyperText());
-  return acc ? acc->AsHyperText() : nullptr;
+  return acc && acc->IsHyperText() ? acc->AsHyperText() : nullptr;
 }
 
 ImageAccessible*
 DocAccessibleChild::IdToImageAccessible(const uint64_t& aID) const
 {
   Accessible* acc = IdToAccessible(aID);
   return (acc && acc->IsImage()) ? acc->AsImage() : nullptr;
 }
 
+TableCellAccessible*
+DocAccessibleChild::IdToTableCellAccessible(const uint64_t& aID) const
+{
+  Accessible* acc = IdToAccessible(aID);
+  return (acc && acc->IsTableCell()) ? acc->AsTableCell() : nullptr;
+}
+
+TableAccessible*
+DocAccessibleChild::IdToTableAccessible(const uint64_t& aID) const
+{
+  Accessible* acc = IdToAccessible(aID);
+  return (acc && acc->IsTable()) ? acc->AsTable() : nullptr;
+}
+
 void
 DocAccessibleChild::ShowEvent(AccShowEvent* aShowEvent)
 {
   Accessible* parent = aShowEvent->Parent();
   uint64_t parentID = parent->IsDoc() ? 0 : reinterpret_cast<uint64_t>(parent->UniqueID());
   uint32_t idxInParent = aShowEvent->GetAccessible()->IndexInParent();
   nsTArray<AccessibleData> shownTree;
   ShowEventData data(parentID, idxInParent, shownTree);
@@ -613,10 +642,793 @@ DocAccessibleChild::RecvImageSize(const 
   ImageAccessible* acc = IdToImageAccessible(aID);
   if (acc) {
     *aRetVal = acc->Size();
   }
 
   return true;
 }
 
+bool
+DocAccessibleChild::RecvStartOffset(const uint64_t& aID,
+                                    uint32_t* aRetVal,
+                                    bool* aOk)
+{
+  Accessible* acc = IdToAccessibleLink(aID);
+  if (acc) {
+    *aRetVal = acc->StartOffset();
+    *aOk = true;
+  } else {
+    *aRetVal = 0;
+    *aOk = false;
+  }
+
+  return true;
+}
+
+bool
+DocAccessibleChild::RecvEndOffset(const uint64_t& aID,
+                                  uint32_t* aRetVal,
+                                  bool* aOk)
+{
+  Accessible* acc = IdToAccessibleLink(aID);
+  if (acc) {
+    *aRetVal = acc->EndOffset();
+    *aOk = true;
+  } else {
+    *aRetVal = 0;
+    *aOk = false;
+  }
+
+  return true;
+}
+
+bool
+DocAccessibleChild::RecvIsLinkValid(const uint64_t& aID,
+                                    bool* aRetVal)
+{
+  Accessible* acc = IdToAccessibleLink(aID);
+  if (acc) {
+    *aRetVal = acc->IsLinkValid();
+  } else {
+    *aRetVal = false;
+  }
+
+  return true;
+}
+
+bool
+DocAccessibleChild::RecvAnchorCount(const uint64_t& aID,
+                                    uint32_t* aRetVal,
+                                    bool* aOk)
+{
+  Accessible* acc = IdToAccessibleLink(aID);
+  if (acc) {
+    *aRetVal = acc->AnchorCount();
+    *aOk = true;
+  } else {
+    *aRetVal = 0;
+    *aOk = false;
+  }
+
+  return true;
+}
+
+bool
+DocAccessibleChild::RecvAnchorURIAt(const uint64_t& aID,
+                                    const uint32_t& aIndex,
+                                    nsCString* aURI,
+                                    bool* aOk)
+{
+  Accessible* acc = IdToAccessibleLink(aID);
+  *aOk = false;
+  if (acc) {
+    nsCOMPtr<nsIURI> uri = acc->AnchorURIAt(aIndex);
+    if (uri) {
+      uri->GetSpec(*aURI);
+      *aOk = true;
+    }
+  }
+
+  return true;
+}
+
+bool
+DocAccessibleChild::RecvAnchorAt(const uint64_t& aID,
+                                 const uint32_t& aIndex,
+                                 uint64_t* aIDOfAnchor,
+                                 bool* aOk)
+{
+  *aIDOfAnchor = 0;
+  *aOk = false;
+  Accessible* acc = IdToAccessibleLink(aID);
+  if (acc) {
+    Accessible* anchor = acc->AnchorAt(aIndex);
+    if (anchor) {
+      *aIDOfAnchor = reinterpret_cast<uint64_t>(anchor->UniqueID());
+      *aOk = true;
+    }
+  }
+
+  return true;
+}
+
+bool
+DocAccessibleChild::RecvLinkCount(const uint64_t& aID,
+                                  uint32_t* aCount)
+{
+  HyperTextAccessible* acc = IdToHyperTextAccessible(aID);
+  *aCount = acc ? acc->LinkCount() : 0;
+  return true;
+}
+
+bool
+DocAccessibleChild::RecvLinkAt(const uint64_t& aID,
+                               const uint32_t& aIndex,
+                               uint64_t* aIDOfLink,
+                               bool* aOk)
+{
+  *aIDOfLink = 0;
+  *aOk = false;
+  HyperTextAccessible* acc = IdToHyperTextAccessible(aID);
+  if (acc) {
+    Accessible* link = acc->LinkAt(aIndex);
+    if (link) {
+      *aIDOfLink = reinterpret_cast<uint64_t>(link->UniqueID());
+      *aOk = true;
+    }
+  }
+
+  return true;
+}
+
+bool
+DocAccessibleChild::RecvLinkIndexOf(const uint64_t& aID,
+                                    const uint64_t& aLinkID,
+                                    int32_t* aIndex)
+{
+  HyperTextAccessible* acc = IdToHyperTextAccessible(aID);
+  Accessible* link = IdToAccessible(aLinkID);
+  *aIndex = -1;
+  if (acc && link) {
+    *aIndex = acc->LinkIndexOf(link);
+  }
+
+  return true;
+}
+
+bool
+DocAccessibleChild::RecvLinkIndexAtOffset(const uint64_t& aID,
+                                          const uint32_t& aOffset,
+                                          int32_t* aIndex)
+{
+  HyperTextAccessible* acc = IdToHyperTextAccessible(aID);
+  *aIndex = acc ? acc->LinkIndexAtOffset(aOffset) : -1;
+  return true;
+}
+
+bool
+DocAccessibleChild::RecvTableOfACell(const uint64_t& aID,
+                                     uint64_t* aTableID,
+                                     bool* aOk)
+{
+  *aTableID = 0;
+  *aOk = false;
+  TableCellAccessible* acc = IdToTableCellAccessible(aID);
+  if (acc) {
+    TableAccessible* table = acc->Table();
+    if (table) {
+      *aTableID = reinterpret_cast<uint64_t>(table->AsAccessible()->UniqueID());
+      *aOk = true;
+    }
+  }
+
+  return true;
+}
+
+bool
+DocAccessibleChild::RecvColIdx(const uint64_t& aID,
+                               uint32_t* aIndex)
+{
+  *aIndex = 0;
+  TableCellAccessible* acc = IdToTableCellAccessible(aID);
+  if (acc) {
+    *aIndex = acc->ColIdx();
+  }
+
+  return true;
+}
+
+bool
+DocAccessibleChild::RecvRowIdx(const uint64_t& aID,
+                               uint32_t* aIndex)
+{
+  *aIndex = 0;
+  TableCellAccessible* acc = IdToTableCellAccessible(aID);
+  if (acc) {
+    *aIndex = acc->RowIdx();
+  }
+
+  return true;
+}
+
+bool
+DocAccessibleChild::RecvColExtent(const uint64_t& aID,
+                                  uint32_t* aExtent)
+{
+  *aExtent = 0;
+  TableCellAccessible* acc = IdToTableCellAccessible(aID);
+  if (acc) {
+    *aExtent = acc->ColExtent();
+  }
+
+  return true;
+}
+
+bool
+DocAccessibleChild::RecvRowExtent(const uint64_t& aID,
+                                  uint32_t* aExtent)
+{
+  *aExtent = 0;
+  TableCellAccessible* acc = IdToTableCellAccessible(aID);
+  if (acc) {
+    *aExtent = acc->RowExtent();
+  }
+
+  return true;
+}
+
+bool
+DocAccessibleChild::RecvColHeaderCells(const uint64_t& aID,
+                                       nsTArray<uint64_t>* aCells)
+{
+  TableCellAccessible* acc = IdToTableCellAccessible(aID);
+  if (acc) {
+    nsAutoTArray<Accessible*, 10> headerCells;
+    acc->ColHeaderCells(&headerCells);
+    aCells->SetCapacity(headerCells.Length());
+    for (uint32_t i = 0; i < headerCells.Length(); ++i) {
+      aCells->AppendElement(
+        reinterpret_cast<uint64_t>(headerCells[i]->UniqueID()));
+    }
+  }
+
+  return true;
+}
+
+bool
+DocAccessibleChild::RecvRowHeaderCells(const uint64_t& aID,
+                                       nsTArray<uint64_t>* aCells)
+{
+  TableCellAccessible* acc = IdToTableCellAccessible(aID);
+  if (acc) {
+    nsAutoTArray<Accessible*, 10> headerCells;
+    acc->RowHeaderCells(&headerCells);
+    aCells->SetCapacity(headerCells.Length());
+    for (uint32_t i = 0; i < headerCells.Length(); ++i) {
+      aCells->AppendElement(
+        reinterpret_cast<uint64_t>(headerCells[i]->UniqueID()));
+    }
+  }
+
+  return true;
+}
+
+bool
+DocAccessibleChild::RecvIsCellSelected(const uint64_t& aID,
+                                       bool* aSelected)
+{
+  TableCellAccessible* acc = IdToTableCellAccessible(aID);
+  *aSelected = acc && acc->Selected();
+  return true;
+}
+
+bool
+DocAccessibleChild::RecvTableCaption(const uint64_t& aID,
+                                     uint64_t* aCaptionID,
+                                     bool* aOk)
+{
+  *aCaptionID = 0;
+  *aOk = false;
+  TableAccessible* acc = IdToTableAccessible(aID);
+  if (acc) {
+    Accessible* caption = acc->Caption();
+    if (caption) {
+      *aCaptionID = reinterpret_cast<uint64_t>(caption->UniqueID());
+      *aOk = true;
+    }
+  }
+
+  return true;
+}
+
+bool
+DocAccessibleChild::RecvTableSummary(const uint64_t& aID,
+                                     nsString* aSummary)
+{
+  TableAccessible* acc = IdToTableAccessible(aID);
+  if (acc) {
+    acc->Summary(*aSummary);
+  }
+
+  return true;
+}
+
+bool
+DocAccessibleChild::RecvTableColumnCount(const uint64_t& aID,
+                                         uint32_t* aColCount)
+{
+  *aColCount = 0;
+  TableAccessible* acc = IdToTableAccessible(aID);
+  if (acc) {
+    *aColCount = acc->ColCount();
+  }
+
+  return true;
+}
+
+bool
+DocAccessibleChild::RecvTableRowCount(const uint64_t& aID,
+                                      uint32_t* aRowCount)
+{
+  *aRowCount = 0;
+  TableAccessible* acc = IdToTableAccessible(aID);
+  if (acc) {
+    *aRowCount = acc->RowCount();
+  }
+
+  return true;
+}
+
+bool
+DocAccessibleChild::RecvTableCellAt(const uint64_t& aID,
+                                    const uint32_t& aRow,
+                                    const uint32_t& aCol,
+                                    uint64_t* aCellID,
+                                    bool* aOk)
+{
+  *aCellID = 0;
+  *aOk = false;
+  TableAccessible* acc = IdToTableAccessible(aID);
+  if (acc) {
+    Accessible* cell = acc->CellAt(aRow, aCol);
+    if (cell) {
+      *aCellID = reinterpret_cast<uint64_t>(cell->UniqueID());
+      *aOk = true;
+    }
+  }
+
+  return true;
+}
+
+bool
+DocAccessibleChild::RecvTableCellIndexAt(const uint64_t& aID,
+                                         const uint32_t& aRow,
+                                         const uint32_t& aCol,
+                                         int32_t* aIndex)
+{
+  *aIndex = -1;
+  TableAccessible* acc = IdToTableAccessible(aID);
+  if (acc) {
+    *aIndex = acc->CellIndexAt(aRow, aCol);
+  }
+
+  return true;
+}
+
+bool
+DocAccessibleChild::RecvTableColumnIndexAt(const uint64_t& aID,
+                                           const uint32_t& aCellIndex,
+                                           int32_t* aCol)
+{
+  *aCol = -1;
+  TableAccessible* acc = IdToTableAccessible(aID);
+  if (acc) {
+    *aCol = acc->ColIndexAt(aCellIndex);
+  }
+
+  return true;
+}
+
+bool
+DocAccessibleChild::RecvTableRowIndexAt(const uint64_t& aID,
+                                        const uint32_t& aCellIndex,
+                                        int32_t* aRow)
+{
+  *aRow = -1;
+  TableAccessible* acc = IdToTableAccessible(aID);
+  if (acc) {
+    *aRow = acc->RowIndexAt(aCellIndex);
+  }
+
+  return true;
+}
+
+bool
+DocAccessibleChild::RecvTableRowAndColumnIndicesAt(const uint64_t& aID,
+                                                  const uint32_t& aCellIndex,
+                                                  int32_t* aRow,
+                                                  int32_t* aCol)
+{
+  *aRow = -1;
+  *aCol = -1;
+  TableAccessible* acc = IdToTableAccessible(aID);
+  if (acc) {
+    acc->RowAndColIndicesAt(aCellIndex, aRow, aCol);
+  }
+
+  return true;
+}
+
+bool
+DocAccessibleChild::RecvTableColumnExtentAt(const uint64_t& aID,
+                                            const uint32_t& aRow,
+                                            const uint32_t& aCol,
+                                            uint32_t* aExtent)
+{
+  *aExtent = 0;
+  TableAccessible* acc = IdToTableAccessible(aID);
+  if (acc) {
+    *aExtent = acc->ColExtentAt(aRow, aCol);
+  }
+
+  return true;
+}
+
+bool
+DocAccessibleChild::RecvTableRowExtentAt(const uint64_t& aID,
+                                         const uint32_t& aRow,
+                                         const uint32_t& aCol,
+                                         uint32_t* aExtent)
+{
+  *aExtent = 0;
+  TableAccessible* acc = IdToTableAccessible(aID);
+  if (acc) {
+    *aExtent = acc->RowExtentAt(aRow, aCol);
+  }
+
+  return true;
+}
+
+bool
+DocAccessibleChild::RecvTableColumnDescription(const uint64_t& aID,
+                                               const uint32_t& aCol,
+                                               nsString* aDescription)
+{
+  TableAccessible* acc = IdToTableAccessible(aID);
+  if (acc) {
+    acc->ColDescription(aCol, *aDescription);
+  }
+
+  return true;
+}
+
+bool
+DocAccessibleChild::RecvTableRowDescription(const uint64_t& aID,
+                                            const uint32_t& aRow,
+                                            nsString* aDescription)
+{
+  TableAccessible* acc = IdToTableAccessible(aID);
+  if (acc) {
+    acc->RowDescription(aRow, *aDescription);
+  }
+
+  return true;
+}
+
+bool
+DocAccessibleChild::RecvTableColumnSelected(const uint64_t& aID,
+                                            const uint32_t& aCol,
+                                            bool* aSelected)
+{
+  *aSelected = false;
+  TableAccessible* acc = IdToTableAccessible(aID);
+  if (acc) {
+    *aSelected = acc->IsColSelected(aCol);
+  }
+
+  return true;
+}
+
+bool
+DocAccessibleChild::RecvTableRowSelected(const uint64_t& aID,
+                                         const uint32_t& aRow,
+                                         bool* aSelected)
+{
+  *aSelected = false;
+  TableAccessible* acc = IdToTableAccessible(aID);
+  if (acc) {
+    *aSelected = acc->IsRowSelected(aRow);
+  }
+
+  return true;
+}
+
+bool
+DocAccessibleChild::RecvTableCellSelected(const uint64_t& aID,
+                                          const uint32_t& aRow,
+                                          const uint32_t& aCol,
+                                          bool* aSelected)
+{
+  *aSelected = false;
+  TableAccessible* acc = IdToTableAccessible(aID);
+  if (acc) {
+    *aSelected = acc->IsCellSelected(aRow, aCol);
+  }
+
+  return true;
+}
+
+bool
+DocAccessibleChild::RecvTableSelectedCellCount(const uint64_t& aID,
+                                               uint32_t* aSelectedCells)
+{
+  *aSelectedCells = 0;
+  TableAccessible* acc = IdToTableAccessible(aID);
+  if (acc) {
+    *aSelectedCells = acc->SelectedCellCount();
+  }
+
+  return true;
+}
+
+bool
+DocAccessibleChild::RecvTableSelectedColumnCount(const uint64_t& aID,
+                                                 uint32_t* aSelectedColumns)
+{
+  *aSelectedColumns = 0;
+  TableAccessible* acc = IdToTableAccessible(aID);
+  if (acc) {
+    *aSelectedColumns = acc->SelectedColCount();
+  }
+
+  return true;
+}
+
+bool
+DocAccessibleChild::RecvTableSelectedRowCount(const uint64_t& aID,
+                                              uint32_t* aSelectedRows)
+{
+  *aSelectedRows = 0;
+  TableAccessible* acc = IdToTableAccessible(aID);
+  if (acc) {
+    *aSelectedRows = acc->SelectedRowCount();
+  }
+
+  return true;
+}
+
+bool
+DocAccessibleChild::RecvTableSelectedCells(const uint64_t& aID,
+                                           nsTArray<uint64_t>* aCellIDs)
+{
+  TableAccessible* acc = IdToTableAccessible(aID);
+  if (acc) {
+    nsAutoTArray<Accessible*, 30> cells;
+    acc->SelectedCells(&cells);
+    aCellIDs->SetCapacity(cells.Length());
+    for (uint32_t i = 0; i < cells.Length(); ++i) {
+      aCellIDs->AppendElement(
+        reinterpret_cast<uint64_t>(cells[i]->UniqueID()));
+    }
+  }
+
+  return true;
+}
+
+bool
+DocAccessibleChild::RecvTableSelectedCellIndices(const uint64_t& aID,
+                                                 nsTArray<uint32_t>* aCellIndices)
+{
+  TableAccessible* acc = IdToTableAccessible(aID);
+  if (acc) {
+    acc->SelectedCellIndices(aCellIndices);
+  }
+
+  return true;
+}
+
+bool
+DocAccessibleChild::RecvTableSelectedColumnIndices(const uint64_t& aID,
+                                                   nsTArray<uint32_t>* aColumnIndices)
+{
+  TableAccessible* acc = IdToTableAccessible(aID);
+  if (acc) {
+    acc->SelectedColIndices(aColumnIndices);
+  }
+
+  return true;
+}
+
+bool
+DocAccessibleChild::RecvTableSelectedRowIndices(const uint64_t& aID,
+                                                nsTArray<uint32_t>* aRowIndices)
+{
+  TableAccessible* acc = IdToTableAccessible(aID);
+  if (acc) {
+    acc->SelectedRowIndices(aRowIndices);
+  }
+
+  return true;
+}
+
+bool
+DocAccessibleChild::RecvTableSelectColumn(const uint64_t& aID,
+                                          const uint32_t& aCol)
+{
+  TableAccessible* acc = IdToTableAccessible(aID);
+  if (acc) {
+    acc->SelectCol(aCol);
+  }
+
+  return true;
+}
+
+bool
+DocAccessibleChild::RecvTableSelectRow(const uint64_t& aID,
+                                       const uint32_t& aRow)
+{
+  TableAccessible* acc = IdToTableAccessible(aID);
+  if (acc) {
+    acc->SelectRow(aRow);
+  }
+
+  return true;
+}
+
+bool
+DocAccessibleChild::RecvTableUnselectColumn(const uint64_t& aID,
+                                            const uint32_t& aCol)
+{
+  TableAccessible* acc = IdToTableAccessible(aID);
+  if (acc) {
+    acc->UnselectCol(aCol);
+  }
+
+  return true;
+}
+
+bool
+DocAccessibleChild::RecvTableUnselectRow(const uint64_t& aID,
+                                         const uint32_t& aRow)
+{
+  TableAccessible* acc = IdToTableAccessible(aID);
+  if (acc) {
+    acc->UnselectRow(aRow);
+  }
+
+  return true;
+}
+
+bool
+DocAccessibleChild::RecvTableIsProbablyForLayout(const uint64_t& aID,
+                                                 bool* aForLayout)
+{
+  *aForLayout = false;
+  TableAccessible* acc = IdToTableAccessible(aID);
+  if (acc) {
+    *aForLayout = acc->IsProbablyLayoutTable();
+  }
+
+  return true;
+}
+
+bool
+DocAccessibleChild::RecvSelectedItems(const uint64_t& aID,
+                                      nsTArray<uint64_t>* aSelectedItemIDs)
+{
+  Accessible* acc = IdToAccessibleSelect(aID);
+  if (acc) {
+    nsAutoTArray<Accessible*, 10> selectedItems;
+    acc->SelectedItems(&selectedItems);
+    aSelectedItemIDs->SetCapacity(selectedItems.Length());
+    for (size_t i = 0; i < selectedItems.Length(); ++i) {
+      aSelectedItemIDs->AppendElement(
+        reinterpret_cast<uint64_t>(selectedItems[i]->UniqueID()));
+    }
+  }
+
+  return true;
+}
+
+bool
+DocAccessibleChild::RecvSelectedItemCount(const uint64_t& aID,
+                                          uint32_t* aCount)
+{
+  *aCount = 0;
+  Accessible* acc = IdToAccessibleSelect(aID);
+  if (acc) {
+    *aCount = acc->SelectedItemCount();
+  }
+
+  return true;
+}
+
+bool
+DocAccessibleChild::RecvGetSelectedItem(const uint64_t& aID,
+                                        const uint32_t& aIndex,
+                                        uint64_t* aSelected,
+                                        bool* aOk)
+{
+  *aSelected = 0;
+  *aOk = false;
+  Accessible* acc = IdToAccessibleSelect(aID);
+  if (acc) {
+    Accessible* item = acc->GetSelectedItem(aIndex);
+    if (item) {
+      *aSelected = reinterpret_cast<uint64_t>(item->UniqueID());
+      *aOk = true;
+    }
+  }
+
+  return true;
+}
+
+bool
+DocAccessibleChild::RecvIsItemSelected(const uint64_t& aID,
+                                       const uint32_t& aIndex,
+                                       bool* aSelected)
+{
+  *aSelected = false;
+  Accessible* acc = IdToAccessibleSelect(aID);
+  if (acc) {
+    *aSelected = acc->IsItemSelected(aIndex);
+  }
+
+  return true;
+}
+
+bool
+DocAccessibleChild::RecvAddItemToSelection(const uint64_t& aID,
+                                           const uint32_t& aIndex,
+                                           bool* aSuccess)
+{
+  *aSuccess = false;
+  Accessible* acc = IdToAccessibleSelect(aID);
+  if (acc) {
+    *aSuccess = acc->AddItemToSelection(aIndex);
+  }
+
+  return true;
+}
+
+bool
+DocAccessibleChild::RecvRemoveItemFromSelection(const uint64_t& aID,
+                                                const uint32_t& aIndex,
+                                                bool* aSuccess)
+{
+  *aSuccess = false;
+  Accessible* acc = IdToAccessibleSelect(aID);
+  if (acc) {
+    *aSuccess = acc->RemoveItemFromSelection(aIndex);
+  }
+
+  return true;
+}
+
+bool
+DocAccessibleChild::RecvSelectAll(const uint64_t& aID,
+                                  bool* aSuccess)
+{
+  *aSuccess = false;
+  Accessible* acc = IdToAccessibleSelect(aID);
+  if (acc) {
+    *aSuccess = acc->SelectAll();
+  }
+
+  return true;
+}
+
+bool
+DocAccessibleChild::RecvUnselectAll(const uint64_t& aID,
+                                    bool* aSuccess)
+{
+  *aSuccess = false;
+  Accessible* acc = IdToAccessibleSelect(aID);
+  if (acc) {
+    *aSuccess = acc->UnselectAll();
+  }
+
+  return true;
+}
+
 }
 }
--- a/accessible/ipc/DocAccessibleChild.h
+++ b/accessible/ipc/DocAccessibleChild.h
@@ -11,17 +11,18 @@
 #include "mozilla/a11y/PDocAccessibleChild.h"
 #include "nsISupportsImpl.h"
 
 namespace mozilla {
 namespace a11y {
 class Accessible;
 class HyperTextAccessible;
 class ImageAccessible;
-
+class TableAccessible;
+class TableCellAccessible;
 class AccShowEvent;
 
   /*
    * These objects handle content side communication for an accessible document,
    * and their lifetime is the same as the document they represent.
    */
 class DocAccessibleChild : public PDocAccessibleChild
 {
@@ -184,21 +185,186 @@ public:
 
   virtual bool RecvImagePosition(const uint64_t& aID,
                                  const uint32_t& aCoordType,
                                  nsIntPoint* aRetVal) override;
 
   virtual bool RecvImageSize(const uint64_t& aID,
                              nsIntSize* aRetVal) override;
 
+  virtual bool RecvStartOffset(const uint64_t& aID,
+                               uint32_t* aRetVal,
+                               bool* aOk) override;
+  virtual bool RecvEndOffset(const uint64_t& aID,
+                             uint32_t* aRetVal,
+                             bool* aOk) override;
+  virtual bool RecvIsLinkValid(const uint64_t& aID,
+                               bool* aRetVal) override;
+  virtual bool RecvAnchorCount(const uint64_t& aID,
+                               uint32_t* aRetVal, bool* aOk) override;
+  virtual bool RecvAnchorURIAt(const uint64_t& aID,
+                               const uint32_t& aIndex,
+                               nsCString* aURI,
+                               bool* aOk) override;
+  virtual bool RecvAnchorAt(const uint64_t& aID,
+                            const uint32_t& aIndex,
+                            uint64_t* aIDOfAnchor,
+                            bool* aOk) override;
+
+  virtual bool RecvLinkCount(const uint64_t& aID,
+                             uint32_t* aCount) override;
+
+  virtual bool RecvLinkAt(const uint64_t& aID,
+                          const uint32_t& aIndex,
+                          uint64_t* aIDOfLink,
+                          bool* aOk) override;
+
+  virtual bool RecvLinkIndexOf(const uint64_t& aID,
+                               const uint64_t& aLinkID,
+                               int32_t* aIndex) override;
+
+  virtual bool RecvLinkIndexAtOffset(const uint64_t& aID,
+                                     const uint32_t& aOffset,
+                                     int32_t* aIndex) override;
+
+  virtual bool RecvTableOfACell(const uint64_t& aID,
+                                uint64_t* aTableID,
+                                bool* aOk) override;
+
+  virtual bool RecvColIdx(const uint64_t& aID, uint32_t* aIndex) override;
+
+  virtual bool RecvRowIdx(const uint64_t& aID, uint32_t* aIndex) override;
+
+  virtual bool RecvColExtent(const uint64_t& aID, uint32_t* aExtent) override;
+
+  virtual bool RecvRowExtent(const uint64_t& aID, uint32_t* aExtent) override;
+
+  virtual bool RecvColHeaderCells(const uint64_t& aID,
+                                  nsTArray<uint64_t>* aCells) override;
+
+  virtual bool RecvRowHeaderCells(const uint64_t& aID,
+                                  nsTArray<uint64_t>* aCells) override;
+
+  virtual bool RecvIsCellSelected(const uint64_t& aID,
+                                  bool* aSelected) override;
+
+  virtual bool RecvTableCaption(const uint64_t& aID,
+                                uint64_t* aCaptionID,
+                                bool* aOk) override;
+  virtual bool RecvTableSummary(const uint64_t& aID,
+                                nsString* aSummary) override;
+  virtual bool RecvTableColumnCount(const uint64_t& aID,
+                                    uint32_t* aColCount) override;
+  virtual bool RecvTableRowCount(const uint64_t& aID,
+                                 uint32_t* aRowCount) override;
+  virtual bool RecvTableCellAt(const uint64_t& aID,
+                               const uint32_t& aRow,
+                               const uint32_t& aCol,
+                               uint64_t* aCellID,
+                               bool* aOk) override;
+  virtual bool RecvTableCellIndexAt(const uint64_t& aID,
+                                    const uint32_t& aRow,
+                                    const uint32_t& aCol,
+                                    int32_t* aIndex) override;
+  virtual bool RecvTableColumnIndexAt(const uint64_t& aID,
+                                      const uint32_t& aCellIndex,
+                                      int32_t* aCol) override;
+  virtual bool RecvTableRowIndexAt(const uint64_t& aID,
+                                   const uint32_t& aCellIndex,
+                                   int32_t* aRow) override;
+  virtual bool RecvTableRowAndColumnIndicesAt(const uint64_t& aID,
+                                             const uint32_t& aCellIndex,
+                                             int32_t* aRow,
+                                             int32_t* aCol) override;
+  virtual bool RecvTableColumnExtentAt(const uint64_t& aID,
+                                       const uint32_t& aRow,
+                                       const uint32_t& aCol,
+                                       uint32_t* aExtent) override;
+  virtual bool RecvTableRowExtentAt(const uint64_t& aID,
+                                    const uint32_t& aRow,
+                                    const uint32_t& aCol,
+                                    uint32_t* aExtent) override;
+  virtual bool RecvTableColumnDescription(const uint64_t& aID,
+                                          const uint32_t& aCol,
+                                          nsString* aDescription) override;
+  virtual bool RecvTableRowDescription(const uint64_t& aID,
+                                       const uint32_t& aRow,
+                                       nsString* aDescription) override;
+  virtual bool RecvTableColumnSelected(const uint64_t& aID,
+                                       const uint32_t& aCol,
+                                       bool* aSelected) override;
+  virtual bool RecvTableRowSelected(const uint64_t& aID,
+                                    const uint32_t& aRow,
+                                    bool* aSelected) override;
+  virtual bool RecvTableCellSelected(const uint64_t& aID,
+                                     const uint32_t& aRow,
+                                     const uint32_t& aCol,
+                                     bool* aSelected) override;
+  virtual bool RecvTableSelectedCellCount(const uint64_t& aID,
+                                          uint32_t* aSelectedCells) override;
+  virtual bool RecvTableSelectedColumnCount(const uint64_t& aID,
+                                            uint32_t* aSelectedColumns) override;
+  virtual bool RecvTableSelectedRowCount(const uint64_t& aID,
+                                         uint32_t* aSelectedRows) override;
+  virtual bool RecvTableSelectedCells(const uint64_t& aID,
+                                      nsTArray<uint64_t>* aCellIDs) override;
+  virtual bool RecvTableSelectedCellIndices(const uint64_t& aID,
+                                            nsTArray<uint32_t>* aCellIndices) override;
+  virtual bool RecvTableSelectedColumnIndices(const uint64_t& aID,
+                                              nsTArray<uint32_t>* aColumnIndices) override;
+  virtual bool RecvTableSelectedRowIndices(const uint64_t& aID,
+                                           nsTArray<uint32_t>* aRowIndices) override;
+  virtual bool RecvTableSelectColumn(const uint64_t& aID,
+                                     const uint32_t& aCol) override;
+  virtual bool RecvTableSelectRow(const uint64_t& aID,
+                                  const uint32_t& aRow) override;
+  virtual bool RecvTableUnselectColumn(const uint64_t& aID,
+                                       const uint32_t& aCol) override;
+  virtual bool RecvTableUnselectRow(const uint64_t& aID,
+                                    const uint32_t& aRow) override;
+  virtual bool RecvTableIsProbablyForLayout(const uint64_t& aID,
+                                            bool* aForLayout) override;
+
+  virtual bool RecvSelectedItems(const uint64_t& aID,
+                                 nsTArray<uint64_t>* aSelectedItemIDs) override;
+
+  virtual bool RecvSelectedItemCount(const uint64_t& aID,
+                                     uint32_t* aCount) override;
+
+  virtual bool RecvGetSelectedItem(const uint64_t& aID,
+                                   const uint32_t& aIndex,
+                                   uint64_t* aSelected,
+                                   bool* aOk) override;
+
+  virtual bool RecvIsItemSelected(const uint64_t& aID,
+                                  const uint32_t& aIndex,
+                                  bool* aSelected) override;
+
+  virtual bool RecvAddItemToSelection(const uint64_t& aID,
+                                      const uint32_t& aIndex,
+                                      bool* aSuccess) override;
+
+  virtual bool RecvRemoveItemFromSelection(const uint64_t& aID,
+                                           const uint32_t& aIndex,
+                                           bool* aSuccess) override;
+
+  virtual bool RecvSelectAll(const uint64_t& aID,
+                             bool* aSuccess) override;
+
+  virtual bool RecvUnselectAll(const uint64_t& aID,
+                               bool* aSuccess) override;
 private:
 
   Accessible* IdToAccessible(const uint64_t& aID) const;
+  Accessible* IdToAccessibleLink(const uint64_t& aID) const;
+  Accessible* IdToAccessibleSelect(const uint64_t& aID) const;
   HyperTextAccessible* IdToHyperTextAccessible(const uint64_t& aID) const;
   ImageAccessible* IdToImageAccessible(const uint64_t& aID) const;
+  TableCellAccessible* IdToTableCellAccessible(const uint64_t& aID) const;
+  TableAccessible* IdToTableAccessible(const uint64_t& aID) const;
 
   bool PersistentPropertiesToArray(nsIPersistentProperties* aProps,
                                    nsTArray<Attribute>* aAttributes);
 
   DocAccessible* mDoc;
 };
 
 }
--- a/accessible/ipc/PDocAccessible.ipdl
+++ b/accessible/ipc/PDocAccessible.ipdl
@@ -127,12 +127,72 @@ child:
   prio(high) sync InsertText(uint64_t aID, nsString aText, int32_t aPosition);
   prio(high) sync CopyText(uint64_t aID, int32_t aStartPos, int32_t aEndPos);
   prio(high) sync CutText(uint64_t aID, int32_t aStartPos, int32_t aEndPos);
   prio(high) sync DeleteText(uint64_t aID, int32_t aStartPos, int32_t aEndPos);
   prio(high) sync PasteText(uint64_t aID, int32_t aPosition);
 
   prio(high) sync ImagePosition(uint64_t aID, uint32_t aCoordType) returns(nsIntPoint aRetVal);
   prio(high) sync ImageSize(uint64_t aID) returns(nsIntSize aRetVal);
+
+  prio(high) sync StartOffset(uint64_t aID) returns(uint32_t aRetVal, bool aOk);
+  prio(high) sync EndOffset(uint64_t aID) returns(uint32_t aRetVal, bool aOk);
+  prio(high) sync IsLinkValid(uint64_t aID) returns(bool aRetVal);
+  prio(high) sync AnchorCount(uint64_t aID) returns(uint32_t aRetVal, bool aOk);
+  prio(high) sync AnchorURIAt(uint64_t aID, uint32_t aIndex) returns(nsCString aURI, bool aOk);
+  prio(high) sync AnchorAt(uint64_t aID, uint32_t aIndex) returns(uint64_t aIDOfAnchor, bool aOk);
+
+  prio(high) sync LinkCount(uint64_t aID) returns(uint32_t aCount);
+  prio(high) sync LinkAt(uint64_t aID, uint32_t aIndex) returns(uint64_t aIDOfLink, bool aOk);
+  prio(high) sync LinkIndexOf(uint64_t aID, uint64_t aLinkID) returns(int32_t aIndex);
+  prio(high) sync LinkIndexAtOffset(uint64_t aID, uint32_t aOffset) returns(int32_t aIndex);
+
+  prio(high) sync TableOfACell(uint64_t aID) returns(uint64_t aTableID, bool aOk);
+  prio(high) sync ColIdx(uint64_t aID) returns(uint32_t aIndex);
+  prio(high) sync RowIdx(uint64_t aID) returns(uint32_t aIndex);
+  prio(high) sync ColExtent(uint64_t aID) returns(uint32_t aExtent);
+  prio(high) sync RowExtent(uint64_t aID) returns(uint32_t aExtent);
+  prio(high) sync ColHeaderCells(uint64_t aID) returns(uint64_t[] aCells);
+  prio(high) sync RowHeaderCells(uint64_t aID) returns(uint64_t[] aCells);
+  prio(high) sync IsCellSelected(uint64_t aID) returns(bool aSelected);
+
+  prio(high) sync TableCaption(uint64_t aID) returns(uint64_t aCaptionID, bool aOk);
+  prio(high) sync TableSummary(uint64_t aID) returns(nsString aSummary);
+  prio(high) sync TableColumnCount(uint64_t aID) returns(uint32_t aColCount);
+  prio(high) sync TableRowCount(uint64_t aID) returns(uint32_t aRowCount);
+  prio(high) sync TableCellAt(uint64_t aID, uint32_t aRow, uint32_t aCol) returns(uint64_t aCellID, bool aOk);
+  prio(high) sync TableCellIndexAt(uint64_t aID, uint32_t aRow, uint32_t aCol) returns(int32_t aIndex);
+  prio(high) sync TableColumnIndexAt(uint64_t aID, uint32_t aCellIndex) returns(int32_t aCol);
+  prio(high) sync TableRowIndexAt(uint64_t aID, uint32_t aCellIndex) returns(int32_t aRow);
+  prio(high) sync TableRowAndColumnIndicesAt(uint64_t aID, uint32_t aCellIndex) returns(int32_t aRow, int32_t aCol);
+  prio(high) sync TableColumnExtentAt(uint64_t aID, uint32_t aRow, uint32_t aCol) returns(uint32_t aExtent);
+  prio(high) sync TableRowExtentAt(uint64_t aID, uint32_t aRow, uint32_t aCol) returns(uint32_t aExtent);
+  prio(high) sync TableColumnDescription(uint64_t aID, uint32_t aCol) returns(nsString aDescription);
+  prio(high) sync TableRowDescription(uint64_t aID, uint32_t aRow) returns(nsString aDescription);
+  prio(high) sync TableColumnSelected(uint64_t aID, uint32_t aCol) returns(bool aSelected);
+  prio(high) sync TableRowSelected(uint64_t aID, uint32_t aRow) returns(bool aSelected);
+  prio(high) sync TableCellSelected(uint64_t aID, uint32_t aRow, uint32_t aCol) returns(bool aSelected);
+  prio(high) sync TableSelectedCellCount(uint64_t aID) returns(uint32_t aSelectedCells);
+  prio(high) sync TableSelectedColumnCount(uint64_t aID) returns(uint32_t aSelectedColumns);
+  prio(high) sync TableSelectedRowCount(uint64_t aID) returns(uint32_t aSelectedRows);
+  prio(high) sync TableSelectedCells(uint64_t aID) returns(uint64_t[] aCellIDs);
+  prio(high) sync TableSelectedCellIndices(uint64_t aID) returns(uint32_t[] aCellIndeces);
+  prio(high) sync TableSelectedColumnIndices(uint64_t aID) returns(uint32_t[] aColumnIndeces);
+  prio(high) sync TableSelectedRowIndices(uint64_t aID) returns(uint32_t[] aRowIndeces);
+  prio(high) sync TableSelectColumn(uint64_t aID, uint32_t aCol);
+  prio(high) sync TableSelectRow(uint64_t aID, uint32_t aRow);
+  prio(high) sync TableUnselectColumn(uint64_t aID, uint32_t aCol);
+  prio(high) sync TableUnselectRow(uint64_t aID, uint32_t aRow);
+  prio(high) sync TableIsProbablyForLayout(uint64_t aID) returns(bool aForLayout);
+
+  prio(high) sync SelectedItems(uint64_t aID) returns(uint64_t[] aSelectedItemIDs);
+  prio(high) sync SelectedItemCount(uint64_t aID) returns(uint32_t aCount);
+  prio(high) sync GetSelectedItem(uint64_t aID, uint32_t aIndex) returns(uint64_t aSelected, bool aOk);
+  prio(high) sync IsItemSelected(uint64_t aID, uint32_t aIndex) returns(bool aSelected);
+  prio(high) sync AddItemToSelection(uint64_t aID, uint32_t aIndex) returns(bool aSuccess);
+  prio(high) sync RemoveItemFromSelection(uint64_t aID, uint32_t aIndex) returns(bool aSuccess);
+  prio(high) sync SelectAll(uint64_t aID) returns(bool aSuccess);
+  prio(high) sync UnselectAll(uint64_t aID) returns(bool aSuccess);
+  
 };
 
 }
 }
--- a/accessible/ipc/ProxyAccessible.cpp
+++ b/accessible/ipc/ProxyAccessible.cpp
@@ -374,10 +374,430 @@ ProxyAccessible::ImagePosition(uint32_t 
 nsIntSize
 ProxyAccessible::ImageSize()
 {
   nsIntSize retVal;
   unused << mDoc->SendImageSize(mID, &retVal);
   return retVal;
 }
 
+uint32_t
+ProxyAccessible::StartOffset(bool* aOk)
+{
+  uint32_t retVal = 0;
+  unused << mDoc->SendStartOffset(mID, &retVal, aOk);
+  return retVal;
+}
+
+uint32_t
+ProxyAccessible::EndOffset(bool* aOk)
+{
+  uint32_t retVal = 0;
+  unused << mDoc->SendEndOffset(mID, &retVal, aOk);
+  return retVal;
+}
+
+bool
+ProxyAccessible::IsLinkValid()
+{
+  bool retVal = false;
+  unused << mDoc->SendIsLinkValid(mID, &retVal);
+  return retVal;
+}
+
+uint32_t
+ProxyAccessible::AnchorCount(bool* aOk)
+{
+  uint32_t retVal = 0;
+  unused << mDoc->SendAnchorCount(mID, &retVal, aOk);
+  return retVal;
+}
+
+void
+ProxyAccessible::AnchorURIAt(uint32_t aIndex, nsCString& aURI, bool* aOk)
+{
+  unused << mDoc->SendAnchorURIAt(mID, aIndex, &aURI, aOk);
+}
+
+ProxyAccessible*
+ProxyAccessible::AnchorAt(uint32_t aIndex)
+{
+  uint64_t id = 0;
+  bool ok = false;
+  unused << mDoc->SendAnchorAt(mID, aIndex, &id, &ok);
+  return ok ? mDoc->GetAccessible(id) : nullptr;
+}
+
+uint32_t
+ProxyAccessible::LinkCount()
+{
+  uint32_t retVal = 0;
+  unused << mDoc->SendLinkCount(mID, &retVal);
+  return retVal;
+}
+
+ProxyAccessible*
+ProxyAccessible::LinkAt(const uint32_t& aIndex)
+{
+  uint64_t linkID = 0;
+  bool ok = false;
+  unused << mDoc->SendLinkAt(mID, aIndex, &linkID, &ok);
+  return ok ? mDoc->GetAccessible(linkID) : nullptr;
+}
+
+int32_t
+ProxyAccessible::LinkIndexOf(ProxyAccessible* aLink)
+{
+  int32_t retVal = -1;
+  if (aLink) {
+    unused << mDoc->SendLinkIndexOf(mID, aLink->ID(), &retVal);
+  }
+
+  return retVal;
+}
+
+int32_t
+ProxyAccessible::LinkIndexAtOffset(uint32_t aOffset)
+{
+  int32_t retVal = -1;
+  unused << mDoc->SendLinkIndexAtOffset(mID, aOffset, &retVal);
+  return retVal;
+}
+
+ProxyAccessible*
+ProxyAccessible::TableOfACell()
+{
+  uint64_t tableID = 0;
+  bool ok = false;
+  unused << mDoc->SendTableOfACell(mID, &tableID, &ok);
+  return ok ? mDoc->GetAccessible(tableID) : nullptr;
+}
+
+uint32_t
+ProxyAccessible::ColIdx()
+{
+  uint32_t index = 0;
+  unused << mDoc->SendColIdx(mID, &index);
+  return index;
+}
+
+uint32_t
+ProxyAccessible::RowIdx()
+{
+  uint32_t index = 0;
+  unused << mDoc->SendRowIdx(mID, &index);
+  return index;
+}
+
+uint32_t
+ProxyAccessible::ColExtent()
+{
+  uint32_t extent = 0;
+  unused << mDoc->SendColExtent(mID, &extent);
+  return extent;
+}
+
+uint32_t
+ProxyAccessible::RowExtent()
+{
+  uint32_t extent = 0;
+  unused << mDoc->SendRowExtent(mID, &extent);
+  return extent;
+}
+
+void
+ProxyAccessible::ColHeaderCells(nsTArray<uint64_t>* aCells)
+{
+  unused << mDoc->SendColHeaderCells(mID, aCells);
+}
+
+void
+ProxyAccessible::RowHeaderCells(nsTArray<uint64_t>* aCells)
+{
+  unused << mDoc->SendRowHeaderCells(mID, aCells);
+}
+
+bool
+ProxyAccessible::IsCellSelected()
+{
+  bool selected = false;
+  unused << mDoc->SendIsCellSelected(mID, &selected);
+  return selected;
+}
+
+ProxyAccessible*
+ProxyAccessible::TableCaption()
+{
+  uint64_t captionID = 0;
+  bool ok = false;
+  unused << mDoc->SendTableCaption(mID, &captionID, &ok);
+  return ok ? mDoc->GetAccessible(captionID) : nullptr;
+}
+
+void
+ProxyAccessible::TableSummary(nsString& aSummary)
+{
+  unused << mDoc->SendTableSummary(mID, &aSummary);
+}
+
+uint32_t
+ProxyAccessible::TableColumnCount()
+{
+  uint32_t count = 0;
+  unused << mDoc->SendTableColumnCount(mID, &count);
+  return count;
+}
+
+uint32_t
+ProxyAccessible::TableRowCount()
+{
+  uint32_t count = 0;
+  unused << mDoc->SendTableRowCount(mID, &count);
+  return count;
+}
+
+ProxyAccessible*
+ProxyAccessible::TableCellAt(uint32_t aRow, uint32_t aCol)
+{
+  uint64_t cellID = 0;
+  bool ok = false;
+  unused << mDoc->SendTableCellAt(mID, aRow, aCol, &cellID, &ok);
+  return ok ? mDoc->GetAccessible(cellID) : nullptr;
+}
+
+int32_t
+ProxyAccessible::TableCellIndexAt(uint32_t aRow, uint32_t aCol)
+{
+  int32_t index = 0;
+  unused << mDoc->SendTableCellIndexAt(mID, aRow, aCol, &index);
+  return index;
+}
+
+int32_t
+ProxyAccessible::TableColumnIndexAt(uint32_t aCellIndex)
+{
+  int32_t index = 0;
+  unused << mDoc->SendTableColumnIndexAt(mID, aCellIndex, &index);
+  return index;
+}
+
+int32_t
+ProxyAccessible::TableRowIndexAt(uint32_t aCellIndex)
+{
+  int32_t index = 0;
+  unused << mDoc->SendTableRowIndexAt(mID, aCellIndex, &index);
+  return index;
+}
+
+void
+ProxyAccessible::TableRowAndColumnIndicesAt(uint32_t aCellIndex,
+                                            int32_t* aRow, int32_t* aCol)
+{
+  unused << mDoc->SendTableRowAndColumnIndicesAt(mID, aCellIndex, aRow, aCol);
+}
+
+uint32_t
+ProxyAccessible::TableColumnExtentAt(uint32_t aRow, uint32_t aCol)
+{
+  uint32_t extent = 0;
+  unused << mDoc->SendTableColumnExtentAt(mID, aRow, aCol, &extent);
+  return extent;
+}
+
+uint32_t
+ProxyAccessible::TableRowExtentAt(uint32_t aRow, uint32_t aCol)
+{
+  uint32_t extent = 0;
+  unused << mDoc->SendTableRowExtentAt(mID, aRow, aCol, &extent);
+  return extent;
+}
+
+void
+ProxyAccessible::TableColumnDescription(uint32_t aCol, nsString& aDescription)
+{
+  unused << mDoc->SendTableColumnDescription(mID, aCol, &aDescription);
+}
+
+void
+ProxyAccessible::TableRowDescription(uint32_t aRow, nsString& aDescription)
+{
+  unused << mDoc->SendTableRowDescription(mID, aRow, &aDescription);
+}
+
+bool
+ProxyAccessible::TableColumnSelected(uint32_t aCol)
+{
+  bool selected = false;
+  unused << mDoc->SendTableColumnSelected(mID, aCol, &selected);
+  return selected;
+}
+
+bool
+ProxyAccessible::TableRowSelected(uint32_t aRow)
+{
+  bool selected = false;
+  unused << mDoc->SendTableRowSelected(mID, aRow, &selected);
+  return selected;
+}
+
+bool
+ProxyAccessible::TableCellSelected(uint32_t aRow, uint32_t aCol)
+{
+  bool selected = false;
+  unused << mDoc->SendTableCellSelected(mID, aRow, aCol, &selected);
+  return selected;
+}
+
+uint32_t
+ProxyAccessible::TableSelectedCellCount()
+{
+  uint32_t count = 0;
+  unused << mDoc->SendTableSelectedCellCount(mID, &count);
+  return count;
+}
+
+uint32_t
+ProxyAccessible::TableSelectedColumnCount()
+{
+  uint32_t count = 0;
+  unused << mDoc->SendTableSelectedColumnCount(mID, &count);
+  return count;
+}
+
+uint32_t
+ProxyAccessible::TableSelectedRowCount()
+{
+  uint32_t count = 0;
+  unused << mDoc->SendTableSelectedRowCount(mID, &count);
+  return count;
+}
+
+void
+ProxyAccessible::TableSelectedCells(nsTArray<ProxyAccessible*>* aCellIDs)
+{
+  nsAutoTArray<uint64_t, 30> cellIDs;
+  unused << mDoc->SendTableSelectedCells(mID, &cellIDs);
+  aCellIDs->SetCapacity(cellIDs.Length());
+  for (uint32_t i = 0; i < cellIDs.Length(); ++i) {
+    aCellIDs->AppendElement(mDoc->GetAccessible(cellIDs[i]));
+  }
+}
+
+void
+ProxyAccessible::TableSelectedCellIndices(nsTArray<uint32_t>* aCellIndices)
+{
+  unused << mDoc->SendTableSelectedCellIndices(mID, aCellIndices);
+}
+
+void
+ProxyAccessible::TableSelectedColumnIndices(nsTArray<uint32_t>* aColumnIndices)
+{
+  unused << mDoc->SendTableSelectedColumnIndices(mID, aColumnIndices);
+}
+
+void
+ProxyAccessible::TableSelectedRowIndices(nsTArray<uint32_t>* aRowIndices)
+{
+  unused << mDoc->SendTableSelectedRowIndices(mID, aRowIndices);
+}
+
+void
+ProxyAccessible::TableSelectColumn(uint32_t aCol)
+{
+  unused << mDoc->SendTableSelectColumn(mID, aCol);
+}
+
+void
+ProxyAccessible::TableSelectRow(uint32_t aRow)
+{
+  unused << mDoc->SendTableSelectRow(mID, aRow);
+}
+
+void
+ProxyAccessible::TableUnselectColumn(uint32_t aCol)
+{
+  unused << mDoc->SendTableUnselectColumn(mID, aCol);
+}
+
+void
+ProxyAccessible::TableUnselectRow(uint32_t aRow)
+{
+  unused << mDoc->SendTableUnselectRow(mID, aRow);
+}
+
+bool
+ProxyAccessible::TableIsProbablyForLayout()
+{
+  bool forLayout = false;
+  unused << mDoc->SendTableIsProbablyForLayout(mID, &forLayout);
+  return forLayout;
+}
+
+void
+ProxyAccessible::SelectedItems(nsTArray<ProxyAccessible*>* aSelectedItems)
+{
+  nsAutoTArray<uint64_t, 10> itemIDs;
+  unused << mDoc->SendSelectedItems(mID, &itemIDs);
+  aSelectedItems->SetCapacity(itemIDs.Length());
+  for (size_t i = 0; i < itemIDs.Length(); ++i) {
+    aSelectedItems->AppendElement(mDoc->GetAccessible(itemIDs[i]));
+  }
+}
+
+uint32_t
+ProxyAccessible::SelectedItemCount()
+{
+  uint32_t count = 0;
+  unused << mDoc->SendSelectedItemCount(mID, &count);
+  return count;
+}
+
+ProxyAccessible*
+ProxyAccessible::GetSelectedItem(uint32_t aIndex)
+{
+  uint64_t selectedItemID = 0;
+  bool ok = false;
+  unused << mDoc->SendGetSelectedItem(mID, aIndex, &selectedItemID, &ok);
+  return ok ? mDoc->GetAccessible(selectedItemID) : nullptr;
+}
+
+bool
+ProxyAccessible::IsItemSelected(uint32_t aIndex)
+{
+  bool selected = false;
+  unused << mDoc->SendIsItemSelected(mID, aIndex, &selected);
+  return selected;
+}
+ 
+bool
+ProxyAccessible::AddItemToSelection(uint32_t aIndex)
+{
+  bool success = false;
+  unused << mDoc->SendAddItemToSelection(mID, aIndex, &success);
+  return success;
+}
+
+bool
+ProxyAccessible::RemoveItemFromSelection(uint32_t aIndex)
+{
+  bool success = false;
+  unused << mDoc->SendRemoveItemFromSelection(mID, aIndex, &success);
+  return success;
+}
+
+bool
+ProxyAccessible::SelectAll()
+{
+  bool success = false;
+  unused << mDoc->SendSelectAll(mID, &success);
+  return success;
+}
+
+bool
+ProxyAccessible::UnselectAll()
+{
+  bool success = false;
+  unused << mDoc->SendUnselectAll(mID, &success);
+  return success;
+}
+
 }
 }
--- a/accessible/ipc/ProxyAccessible.h
+++ b/accessible/ipc/ProxyAccessible.h
@@ -176,16 +176,91 @@ public:
   void DeleteText(int32_t aStartPos, int32_t aEndPos);
 
   void PasteText(int32_t aPosition);
 
   nsIntPoint ImagePosition(uint32_t aCoordType);
 
   nsIntSize ImageSize();
 
+  uint32_t StartOffset(bool* aOk);
+
+  uint32_t EndOffset(bool* aOk);
+
+  bool IsLinkValid();
+
+  uint32_t AnchorCount(bool* aOk);
+
+  void AnchorURIAt(uint32_t aIndex, nsCString& aURI, bool* aOk);
+
+  ProxyAccessible* AnchorAt(uint32_t aIndex);
+
+  uint32_t LinkCount();
+
+  ProxyAccessible* LinkAt(const uint32_t& aIndex);
+
+  int32_t LinkIndexOf(ProxyAccessible* aLink);
+
+  int32_t LinkIndexAtOffset(uint32_t aOffset);
+
+  ProxyAccessible* TableOfACell();
+
+  uint32_t ColIdx();
+
+  uint32_t RowIdx();
+
+  uint32_t ColExtent();
+
+  uint32_t RowExtent();
+
+  void ColHeaderCells(nsTArray<uint64_t>* aCells);
+
+  void RowHeaderCells(nsTArray<uint64_t>* aCells);
+
+  bool IsCellSelected();
+
+  ProxyAccessible* TableCaption();
+  void TableSummary(nsString& aSummary);
+  uint32_t TableColumnCount();
+  uint32_t TableRowCount();
+  ProxyAccessible* TableCellAt(uint32_t aRow, uint32_t aCol);
+  int32_t TableCellIndexAt(uint32_t aRow, uint32_t aCol);
+  int32_t TableColumnIndexAt(uint32_t aCellIndex);
+  int32_t TableRowIndexAt(uint32_t aCellIndex);
+  void TableRowAndColumnIndicesAt(uint32_t aCellIndex,
+                                  int32_t* aRow, int32_t* aCol);
+  uint32_t TableColumnExtentAt(uint32_t aRow, uint32_t aCol);
+  uint32_t TableRowExtentAt(uint32_t aRow, uint32_t aCol);
+  void TableColumnDescription(uint32_t aCol, nsString& aDescription);
+  void TableRowDescription(uint32_t aRow, nsString& aDescription);
+  bool TableColumnSelected(uint32_t aCol);
+  bool TableRowSelected(uint32_t aRow);
+  bool TableCellSelected(uint32_t aRow, uint32_t aCol);
+  uint32_t TableSelectedCellCount();
+  uint32_t TableSelectedColumnCount();
+  uint32_t TableSelectedRowCount();
+  void TableSelectedCells(nsTArray<ProxyAccessible*>* aCellIDs);
+  void TableSelectedCellIndices(nsTArray<uint32_t>* aCellIndices);
+  void TableSelectedColumnIndices(nsTArray<uint32_t>* aColumnIndices);
+  void TableSelectedRowIndices(nsTArray<uint32_t>* aRowIndices);
+  void TableSelectColumn(uint32_t aCol);
+  void TableSelectRow(uint32_t aRow);
+  void TableUnselectColumn(uint32_t aCol);
+  void TableUnselectRow(uint32_t aRow);
+  bool TableIsProbablyForLayout();
+
+  void SelectedItems(nsTArray<ProxyAccessible*>* aSelectedItems);
+  uint32_t SelectedItemCount();
+  ProxyAccessible* GetSelectedItem(uint32_t aIndex);
+  bool IsItemSelected(uint32_t aIndex);
+  bool AddItemToSelection(uint32_t aIndex);
+  bool RemoveItemFromSelection(uint32_t aIndex);
+  bool SelectAll();
+  bool UnselectAll();
+
   /**
    * Allow the platform to store a pointers worth of data on us.
    */
   uintptr_t GetWrapper() const { return mWrapper; }
   void SetWrapper(uintptr_t aWrapper) { mWrapper = aWrapper; }
 
   /*
    * Return the ID of the accessible being proxied.
--- a/accessible/tests/mochitest/table.js
+++ b/accessible/tests/mochitest/table.js
@@ -709,19 +709,19 @@ function testHeaderCells(aHeaderInfoMap)
        "Wrong number of row header cells for the cell " +
        prettyName(dataCellIdentifier));
 
     if (actualRowHeaderCellsCount == rowHeaderCellsCount) {
       for (var idx = 0; idx < rowHeaderCellsCount; idx++) {
         var rowHeaderCell = getAccessible(rowHeaderCells[idx]);
         var actualRowHeaderCell =
           actualRowHeaderCells.queryElementAt(idx, nsIAccessible);
-        ok(actualRowHeaderCell, rowHeaderCell,
-           "Wrong row header cell at index " + idx + " for the cell " +
-           prettyName(rowHeaderCells[idx]));
+        isObject(actualRowHeaderCell, rowHeaderCell,
+                 "Wrong row header cell at index " + idx + " for the cell " +
+                 dataCellIdentifier);
       }
     }
 
     // column header cells
     var colHeaderCells = aHeaderInfoMap[testIdx].columnHeaderCells;
     var colHeaderCellsCount = colHeaderCells.length;
     var actualColHeaderCells = dataCell.columnHeaderCells;
     var actualColHeaderCellsCount = actualColHeaderCells.length;
@@ -730,19 +730,19 @@ function testHeaderCells(aHeaderInfoMap)
        "Wrong number of column header cells for the cell " +
        prettyName(dataCellIdentifier));
 
     if (actualColHeaderCellsCount == colHeaderCellsCount) {
       for (var idx = 0; idx < colHeaderCellsCount; idx++) {
         var colHeaderCell = getAccessible(colHeaderCells[idx]);
         var actualColHeaderCell =
           actualColHeaderCells.queryElementAt(idx, nsIAccessible);
-        ok(actualColHeaderCell, colHeaderCell,
-           "Wrong column header cell at index " + idx + " for the cell " +
-           prettyName(colHeaderCells[idx]));
+        isObject(actualColHeaderCell, colHeaderCell,
+                 "Wrong column header cell at index " + idx + " for the cell " +
+                 dataCellIdentifier);
       }
     }
   }
 }
 
 ////////////////////////////////////////////////////////////////////////////////
 // private implementation
 
--- a/accessible/tests/mochitest/table/test_headers_table.html
+++ b/accessible/tests/mochitest/table/test_headers_table.html
@@ -157,141 +157,277 @@
       testHeaderCells(headerInfoMap);
 
       //////////////////////////////////////////////////////////////////////////
       // @scope="rowgroup" and @scope="row"
 
       headerInfoMap = [
         {
           cell: "t7_r1c1",
-          rowHeaderCells: [ "t7_Females", "t7_Mary" ],
+          rowHeaderCells: [ "t7_Mary", "t7_Females" ],
           columnHeaderCells: [ "t7_1km" ]
         },
         {
           cell: "t7_r1c2",
-          rowHeaderCells: [ "t7_Females", "t7_Mary" ],
+          rowHeaderCells: [ "t7_Mary", "t7_Females" ],
           columnHeaderCells: [ "t7_5km" ]
         },
         {
           cell: "t7_r1c3",
-          rowHeaderCells: [ "t7_Females", "t7_Mary" ],
+          rowHeaderCells: [ "t7_Mary", "t7_Females" ],
           columnHeaderCells: [ "t7_10km" ]
         },
         {
           cell: "t7_r2c1",
-          rowHeaderCells: [ "t7_Females", "t7_Betsy" ],
+          rowHeaderCells: [ "t7_Betsy", "t7_Females" ],
           columnHeaderCells: [ "t7_1km" ]
         },
         {
           cell: "t7_r2c2",
-          rowHeaderCells: [ "t7_Females", "t7_Betsy" ],
+          rowHeaderCells: [ "t7_Betsy", "t7_Females" ],
           columnHeaderCells: [ "t7_5km" ]
         },
         {
           cell: "t7_r2c3",
-          rowHeaderCells: [ "t7_Females", "t7_Betsy" ],
+          rowHeaderCells: [ "t7_Betsy", "t7_Females" ],
           columnHeaderCells: [ "t7_10km" ]
         },
         {
           cell: "t7_r3c1",
-          rowHeaderCells: [ "t7_Males", "t7_Matt" ],
+          rowHeaderCells: [ "t7_Matt", "t7_Males" ],
           columnHeaderCells: [ "t7_1km" ]
         },
         {
           cell: "t7_r3c2",
-          rowHeaderCells: [ "t7_Males", "t7_Matt" ],
+          rowHeaderCells: [ "t7_Matt", "t7_Males" ],
           columnHeaderCells: [ "t7_5km" ]
         },
         {
           cell: "t7_r3c3",
-          rowHeaderCells: [ "t7_Males", "t7_Matt" ],
+          rowHeaderCells: [ "t7_Matt", "t7_Males" ],
           columnHeaderCells: [ "t7_10km" ]
         },
         {
           cell: "t7_r4c1",
-          rowHeaderCells: [ "t7_Males", "t7_Todd" ],
+          rowHeaderCells: [ "t7_Todd", "t7_Males" ],
           columnHeaderCells: [ "t7_1km" ]
         },
         {
           cell: "t7_r4c2",
-          rowHeaderCells: [ "t7_Males", "t7_Todd" ],
+          rowHeaderCells: [ "t7_Todd", "t7_Males" ],
           columnHeaderCells: [ "t7_5km" ]
         },
         {
           cell: "t7_r4c3",
-          rowHeaderCells: [ "t7_Males", "t7_Todd" ],
+          rowHeaderCells: [ "t7_Todd", "t7_Males" ],
           columnHeaderCells: [ "t7_10km" ]
         }
       ];
 
       testHeaderCells(headerInfoMap);
 
       //////////////////////////////////////////////////////////////////////////
       // @scope="colgroup" and @scope="col"
 
       headerInfoMap = [
         {
           cell: "t8_r1c1",
           rowHeaderCells: [ "t8_1km" ],
-          columnHeaderCells: [ "t7_Females", "t7_Mary" ]
+          columnHeaderCells: [ "t8_Mary", "t8_Females" ]
         },
         {
           cell: "t8_r1c2",
-          rowHeaderCells: [ "t8_5km" ],
-          columnHeaderCells: [ "t8_Females", "t8_Mary" ]
+          rowHeaderCells: [ "t8_1km" ],
+          columnHeaderCells: [ "t8_Betsy", "t8_Females" ]
         },
         {
           cell: "t8_r1c3",
-          rowHeaderCells: [ "t8_10km" ],
-          columnHeaderCells: [ "t8_Females", "t8_Mary" ]
+          rowHeaderCells: [ "t8_1km" ],
+          columnHeaderCells: [ "t8_Matt", "t8_Males" ]
         },
         {
           cell: "t8_r1c4",
           rowHeaderCells: [ "t8_1km" ],
-          columnHeaderCells: [ "t8_Females", "t8_Betsy" ]
+          columnHeaderCells: [ "t8_Todd", "t8_Males" ]
         },
         {
           cell: "t8_r2c1",
           rowHeaderCells: [ "t8_5km" ],
-          columnHeaderCells: [ "t8_Females", "t8_Betsy" ]
+          columnHeaderCells: [ "t8_Mary", "t8_Females" ]
         },
         {
           cell: "t8_r2c2",
-          rowHeaderCells: [ "t8_10km" ],
-          columnHeaderCells: [ "t8_Females", "t8_Betsy" ]
+          rowHeaderCells: [ "t8_5km" ],
+          columnHeaderCells: [ "t8_Betsy", "t8_Females" ]
         },
         {
           cell: "t8_r2c3",
-          rowHeaderCells: [ "t8_1km" ],
-          columnHeaderCells: [ "t8_Males", "t8_Matt" ]
+          rowHeaderCells: [ "t8_5km" ],
+          columnHeaderCells: [ "t8_Matt", "t8_Males" ]
         },
         {
           cell: "t8_r2c4",
           rowHeaderCells: [ "t8_5km" ],
-          columnHeaderCells: [ "t8_Males", "t8_Matt" ]
+          columnHeaderCells: [ "t8_Todd", "t8_Males" ]
         },
         {
           cell: "t8_r3c1",
           rowHeaderCells: [ "t8_10km" ],
-          columnHeaderCells: [ "t8_Males", "t8_Matt" ]
+          columnHeaderCells: [ "t8_Mary", "t8_Females" ]
         },
         {
           cell: "t8_r3c2",
-          rowHeaderCells: [ "t8_1km" ],
-          columnHeaderCells: [ "t8_Males", "t8_Todd" ]
+          rowHeaderCells: [ "t8_10km" ],
+          columnHeaderCells: [ "t8_Betsy", "t8_Females" ]
         },
         {
           cell: "t8_r3c3",
-          rowHeaderCells: [ "t8_5km" ],
-          columnHeaderCells: [ "t8_Males", "t8_Todd" ]
+          rowHeaderCells: [ "t8_10km" ],
+          columnHeaderCells: [ "t8_Matt", "t8_Males" ]
         },
         {
           cell: "t8_r3c4",
           rowHeaderCells: [ "t8_10km" ],
-          columnHeaderCells: [ "t8_Males", "t8_Todd" ]
+          columnHeaderCells: [ "t8_Todd", "t8_Males" ]
+        }
+      ];
+
+      testHeaderCells(headerInfoMap);
+
+      //////////////////////////////////////////////////////////////////////////
+      // spanned table header cells (v1), @headers define header order
+
+      headerInfoMap = [
+        {
+          cell: "t9_r1c1",
+          rowHeaderCells: [ "t9_females", "t9_mary" ],
+          columnHeaderCells: [ "t9_1km" ]
+        },
+        {
+          cell: "t9_r1c2",
+          rowHeaderCells: [ "t9_females", "t9_mary" ],
+          columnHeaderCells: [ "t9_5km" ]
+        },
+        {
+          cell: "t9_r1c3",
+          rowHeaderCells: [ "t9_females", "t9_mary" ],
+          columnHeaderCells: [ "t9_10km" ]
+        },
+        {
+          cell: "t9_r2c1",
+          rowHeaderCells: [ "t9_females", "t9_betsy" ],
+          columnHeaderCells: [ "t9_1km" ]
+        },
+        {
+          cell: "t9_r2c2",
+          rowHeaderCells: [ "t9_females", "t9_betsy" ],
+          columnHeaderCells: [ "t9_5km" ]
+        },
+        {
+          cell: "t9_r2c3",
+          rowHeaderCells: [ "t9_females", "t9_betsy" ],
+          columnHeaderCells: [ "t9_10km" ]
+        },
+        {
+          cell: "t9_r3c1",
+          rowHeaderCells: [ "t9_males", "t9_matt" ],
+          columnHeaderCells: [ "t9_1km" ]
+        },
+        {
+          cell: "t9_r3c2",
+          rowHeaderCells: [ "t9_males", "t9_matt" ],
+          columnHeaderCells: [ "t9_5km" ]
+        },
+        {
+          cell: "t9_r3c3",
+          rowHeaderCells: [ "t9_males", "t9_matt" ],
+          columnHeaderCells: [ "t9_10km" ]
+        },
+        {
+          cell: "t9_r4c1",
+          rowHeaderCells: [ "t9_males", "t9_todd" ],
+          columnHeaderCells: [ "t9_1km" ]
+        },
+        {
+          cell: "t9_r4c2",
+          rowHeaderCells: [ "t9_males", "t9_todd" ],
+          columnHeaderCells: [ "t9_5km" ]
+        },
+        {
+          cell: "t9_r4c3",
+          rowHeaderCells: [ "t9_males", "t9_todd" ],
+          columnHeaderCells: [ "t9_10km" ]
+        }
+      ];
+
+      testHeaderCells(headerInfoMap);
+
+      //////////////////////////////////////////////////////////////////////////
+      // spanned table header cells (v2), @headers define header order
+
+      headerInfoMap = [
+        {
+          cell: "t10_r1c1",
+          rowHeaderCells: [ "t10_1km" ],
+          columnHeaderCells: [ "t10_females", "t10_mary" ]
+        },
+        {
+          cell: "t10_r1c2",
+          rowHeaderCells: [ "t10_1km" ],
+          columnHeaderCells: [ "t10_females", "t10_betsy" ]
+        },
+        {
+          cell: "t10_r1c3",
+          rowHeaderCells: [ "t10_1km" ],
+          columnHeaderCells: [ "t10_males", "t10_matt" ]
+        },
+        {
+          cell: "t10_r1c4",
+          rowHeaderCells: [ "t10_1km" ],
+          columnHeaderCells: [ "t10_males", "t10_todd" ]
+        },
+        {
+          cell: "t10_r2c1",
+          rowHeaderCells: [ "t10_5km" ],
+          columnHeaderCells: [ "t10_females", "t10_mary" ]
+        },
+        {
+          cell: "t10_r2c2",
+          rowHeaderCells: [ "t10_5km" ],
+          columnHeaderCells: [ "t10_females", "t10_betsy" ]
+        },
+        {
+          cell: "t10_r2c3",
+          rowHeaderCells: [ "t10_5km" ],
+          columnHeaderCells: [ "t10_males", "t10_matt" ]
+        },
+        {
+          cell: "t10_r2c4",
+          rowHeaderCells: [ "t10_5km" ],
+          columnHeaderCells: [ "t10_males", "t10_todd" ]
+        },
+        {
+          cell: "t10_r3c1",
+          rowHeaderCells: [ "t10_10km" ],
+          columnHeaderCells: [ "t10_females", "t10_mary" ]
+        },
+        {
+          cell: "t10_r3c2",
+          rowHeaderCells: [ "t10_10km" ],
+          columnHeaderCells: [ "t10_females", "t10_betsy" ]
+        },
+        {
+          cell: "t10_r3c3",
+          rowHeaderCells: [ "t10_10km" ],
+          columnHeaderCells: [ "t10_males", "t10_matt" ]
+        },
+        {
+          cell: "t10_r3c4",
+          rowHeaderCells: [ "t10_10km" ],
+          columnHeaderCells: [ "t10_males", "t10_todd" ]
         }
       ];
 
       testHeaderCells(headerInfoMap);
 
       SimpleTest.finish();
     }
 
@@ -493,10 +629,85 @@
         <td id="t8_r3c2">55:38</td>
         <td id="t8_r3c3">57:04</td>
         <td id="t8_r3c4">50:35</td>
       </tr>
 
     </tbody>
   </table>
 
+  <table id="table9" border="1">
+    <caption>
+      Example 1 (row group headers):
+    </caption>
+    <tr>
+      <td colspan="2"><span class="offscreen">empty</span></td>
+      <th id="t9_1km" width="40">1 km</th>
+      <th id="t9_5km" width="35">5 km</th>
+      <th id="t9_10km" width="42">10 km</th>
+    </tr>
+    <tr>
+      <th id="t9_females" width="56" rowspan="2">Females</th>
+      <th id="t9_mary" width="39">Mary</th>
+      <td id="t9_r1c1" headers="t9_females t9_mary t9_1km">8:32</td>
+      <td id="t9_r1c2" headers="t9_females t9_mary t9_5km">28:04</td>
+      <td id="t9_r1c3" headers="t9_females t9_mary t9_10km">1:01:16</td>
+    </tr>
+    <tr>
+      <th id="t9_betsy">Betsy</th>
+      <td id="t9_r2c1" headers="t9_females t9_betsy t9_1km">7:43</td>
+      <td id="t9_r2c2" headers="t9_females t9_betsy t9_5km">26:47</td>
+      <td id="t9_r2c3" headers="t9_females t9_betsy t9_10km">55:38</td>
+    </tr>
+    <tr>
+      <th id="t9_males" rowspan="2">Males</th>
+      <th id="t9_matt">Matt</th>
+      <td id="t9_r3c1" headers="t9_males t9_matt t9_1km">7:55</td>
+      <td id="t9_r3c2" headers="t9_males t9_matt t9_5km">27:29</td>
+      <td id="t9_r3c3" headers="t9_males t9_matt t9_10km">57:04</td>
+    </tr>
+    <tr>
+      <th id="t9_todd">Todd</th>
+      <td id="t9_r4c1" headers="t9_males t9_todd t9_1km">7:01</td>
+      <td id="t9_r4c2" headers="t9_males t9_todd t9_5km">24:21</td>
+      <td id="t9_r4c3" headers="t9_males t9_todd t9_10km">50:35</td>
+    </tr>
+  </table>
+
+  <table id="table10" border="1">
+    <caption>
+      Example 2 (column group headers):
+    </caption>
+    <tr>
+      <td rowspan="2"><span class="offscreen">empty</span></td>
+      <th colspan="2" id="t10_females">Females</th>
+      <th colspan="2" id="t10_males">Males</th>
+    </tr>
+    <tr>
+      <th width="40" id="t10_mary">Mary</th>
+      <th width="35" id="t10_betsy">Betsy</th>
+      <th width="42" id="t10_matt">Matt</th>
+      <th width="42" id="t10_todd">Todd</th>
+    </tr>
+    <tr>
+      <th width="39" id="t10_1km">1 km</th>
+      <td headers="t10_females t10_mary t10_1km" id="t10_r1c1">8:32</td>
+      <td headers="t10_females t10_betsy t10_1km" id="t10_r1c2">7:43</td>
+      <td headers="t10_males t10_matt t10_1km" id="t10_r1c3">7:55</td>
+      <td headers="t10_males t10_todd t10_1km" id="t10_r1c4">7:01</td>
+    </tr>
+    <tr>
+      <th id="t10_5km">5 km</th>
+      <td headers="t10_females t10_mary t10_5km" id="t10_r2c1">28:04</td>
+      <td headers="t10_females t10_betsy t10_5km" id="t10_r2c2">26:47</td>
+      <td headers="t10_males t10_matt t10_5km" id="t10_r2c3">27:29</td>
+      <td headers="t10_males t10_todd t10_5km" id="t10_r2c4">24:21</td>
+    </tr>
+    <tr>
+      <th id="t10_10km">10 km</th>
+      <td headers="t10_females t10_mary t10_10km" id="t10_r3c1">1:01:16</td>
+      <td headers="t10_females t10_betsy t10_10km" id="t10_r3c2">55:38</td>
+      <td headers="t10_males t10_matt t10_10km" id="t10_r3c3">57:04</td>
+      <td headers="t10_males t10_todd t10_10km" id="t10_r3c4">50:35</td>
+    </tr>
+  </table>
 </body>
 </html>
--- a/b2g/app/b2g.js
+++ b/b2g/app/b2g.js
@@ -991,16 +991,23 @@ pref("gfx.gralloc.fence-with-readpixels"
 // The url of the page used to display network error details.
 pref("b2g.neterror.url", "app://system.gaiamobile.org/net_error.html");
 
 // The origin used for the shared themes uri space.
 pref("b2g.theme.origin", "app://theme.gaiamobile.org");
 pref("dom.mozApps.themable", true);
 pref("dom.mozApps.selected_theme", "default_theme.gaiamobile.org");
 
+// Enable PAC generator for B2G.
+pref("network.proxy.pac_generator", true);
+
+// List of app origins to apply browsing traffic proxy setting, separated by
+// comma.  Specify '*' in the list to apply to all apps.
+pref("network.proxy.browsing.app_origins", "app://system.gaiamobile.org");
+
 // Enable Web Speech synthesis API
 pref("media.webspeech.synth.enabled", true);
 
 // Downloads API
 pref("dom.mozDownloads.enabled", true);
 pref("dom.downloads.max_retention_days", 7);
 
 // External Helper Application Handling
--- a/b2g/chrome/content/settings.js
+++ b/b2g/chrome/content/settings.js
@@ -25,16 +25,20 @@ XPCOMUtils.defineLazyGetter(this, "libcu
   return libcutils;
 });
 #endif
 
 XPCOMUtils.defineLazyServiceGetter(this, "uuidgen",
                                    "@mozilla.org/uuid-generator;1",
                                    "nsIUUIDGenerator");
 
+XPCOMUtils.defineLazyServiceGetter(this, "gPACGenerator",
+                                   "@mozilla.org/pac-generator;1",
+                                   "nsIPACGenerator");
+
 // Once Bug 731746 - Allow chrome JS object to implement nsIDOMEventTarget
 // is resolved this helper could be removed.
 var SettingsListener = {
   _callbacks: {},
 
   init: function sl_init() {
     if ('mozSettings' in navigator && navigator.mozSettings) {
       navigator.mozSettings.onsettingchange = this.onchange.bind(this);
@@ -477,16 +481,50 @@ SettingsListener.observe("theme.selected
   if (currentTheme != newTheme) {
     debug("New theme selected " + value);
     Services.prefs.setCharPref('dom.mozApps.selected_theme', newTheme);
     Services.prefs.savePrefFile(null);
     Services.obs.notifyObservers(null, 'app-theme-changed', newTheme);
   }
 });
 
+// =================== Proxy server ======================
+(function setupBrowsingProxySettings() {
+  function setPAC() {
+    let usePAC;
+    try {
+      usePAC = Services.prefs.getBoolPref('network.proxy.pac_generator');
+    } catch (ex) {}
+
+    if (usePAC) {
+      Services.prefs.setCharPref('network.proxy.autoconfig_url',
+                                 gPACGenerator.generate());
+      Services.prefs.setIntPref('network.proxy.type',
+                                Ci.nsIProtocolProxyService.PROXYCONFIG_PAC);
+    }
+  }
+
+  SettingsListener.observe('browser.proxy.enabled', false, function(value) {
+    Services.prefs.setBoolPref('network.proxy.browsing.enabled', value);
+    setPAC();
+  });
+
+  SettingsListener.observe('browser.proxy.host', '', function(value) {
+    Services.prefs.setCharPref('network.proxy.browsing.host', value);
+    setPAC();
+  });
+
+  SettingsListener.observe('browser.proxy.port', 0, function(value) {
+    Services.prefs.setIntPref('network.proxy.browsing.port', value);
+    setPAC();
+  });
+
+  setPAC();
+})();
+
 // =================== Various simple mapping  ======================
 let settingsToObserve = {
   'accessibility.screenreader_quicknav_modes': {
     prefName: 'accessibility.accessfu.quicknav_modes',
     resetToPref: true,
     defaultValue: ''
   },
   'accessibility.screenreader_quicknav_index': {
--- a/b2g/installer/package-manifest.in
+++ b/b2g/installer/package-manifest.in
@@ -668,16 +668,19 @@
 @RESPATH@/components/EngineeringModeAPI.js
 @RESPATH@/components/EngineeringModeService.js
 
 #ifdef MOZ_DEBUG
 @RESPATH@/components/TestInterfaceJS.js
 @RESPATH@/components/TestInterfaceJS.manifest
 #endif
 
+@RESPATH@/components/PACGenerator.js
+@RESPATH@/components/PACGenerator.manifest
+
 ; Modules
 @RESPATH@/modules/*
 
 ; Safe Browsing
 @RESPATH@/components/nsURLClassifier.manifest
 @RESPATH@/components/nsUrlClassifierHashCompleter.js
 @RESPATH@/components/nsUrlClassifierListManager.js
 @RESPATH@/components/nsUrlClassifierLib.js
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -1328,21 +1328,18 @@ pref("services.sync.prefs.sync.security.
 pref("services.sync.prefs.sync.security.tls.version.max", true);
 pref("services.sync.prefs.sync.signon.rememberSignons", true);
 pref("services.sync.prefs.sync.spellchecker.dictionary", true);
 pref("services.sync.prefs.sync.xpinstall.whitelist.required", true);
 #endif
 
 // Developer edition preferences
 #ifdef MOZ_DEV_EDITION
+pref("lightweightThemes.selectedThemeID", "firefox-devedition@mozilla.org");
 pref("browser.devedition.theme.enabled", true);
-pref("browser.devedition.theme.showCustomizeButton", true);
-#else
-pref("browser.devedition.theme.enabled", false);
-pref("browser.devedition.theme.showCustomizeButton", false);
 #endif
 
 // Developer edition promo preferences
 pref("devtools.devedition.promo.shown", false);
 pref("devtools.devedition.promo.url", "https://www.mozilla.org/firefox/developer/?utm_source=firefox-dev-tools&utm_medium=firefox-browser&utm_content=betadoorhanger");
 
 // Only potentially show in beta release
 #if MOZ_UPDATE_CHANNEL == beta
--- a/browser/base/content/browser-devedition.js
+++ b/browser/base/content/browser-devedition.js
@@ -2,56 +2,47 @@
 # 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/.
 
 /**
  * Listeners for the DevEdition theme.  This adds an extra stylesheet
  * to browser.xul if a pref is set and no other themes are applied.
  */
 let DevEdition = {
-  _prefName: "browser.devedition.theme.enabled",
-  _themePrefName: "general.skins.selectedSkin",
-  _lwThemePrefName: "lightweightThemes.selectedThemeID",
   _devtoolsThemePrefName: "devtools.theme",
-
   styleSheetLocation: "chrome://browser/skin/devedition.css",
   styleSheet: null,
 
-  init: function () {
-    this._updateDevtoolsThemeAttribute();
-    this._updateStyleSheetFromPrefs();
+  get isThemeCurrentlyApplied() {
+    let theme = LightweightThemeManager.currentTheme;
+    return theme && theme.id == "firefox-devedition@mozilla.org";
+  },
 
-    // Listen for changes to all prefs except for complete themes.
-    // No need for this since changing a complete theme requires a
-    // restart.
-    Services.prefs.addObserver(this._lwThemePrefName, this, false);
-    Services.prefs.addObserver(this._prefName, this, false);
+  init: function () {
     Services.prefs.addObserver(this._devtoolsThemePrefName, this, false);
     Services.obs.addObserver(this, "lightweight-theme-styling-update", false);
+    this._updateDevtoolsThemeAttribute();
+
+    if (this.isThemeCurrentlyApplied) {
+      this._toggleStyleSheet(true);
+    }
   },
 
   observe: function (subject, topic, data) {
     if (topic == "lightweight-theme-styling-update") {
       let newTheme = JSON.parse(data);
-      if (!newTheme) {
-        // A lightweight theme has been unapplied, so just re-read prefs.
-        this._updateStyleSheetFromPrefs();
+      if (newTheme && newTheme.id == "firefox-devedition@mozilla.org") {
+        this._toggleStyleSheet(true);
       } else {
-        // A lightweight theme has been applied, but the pref may not be
-        // set yet if this happened from customize menu or addons page.
         this._toggleStyleSheet(false);
       }
     }
 
-    if (topic == "nsPref:changed") {
-      if (data == this._devtoolsThemePrefName) {
-        this._updateDevtoolsThemeAttribute();
-      } else {
-        this._updateStyleSheetFromPrefs();
-      }
+    if (topic == "nsPref:changed" && data == this._devtoolsThemePrefName) {
+      this._updateDevtoolsThemeAttribute();
     }
   },
 
   _inferBrightness: function() {
     ToolbarIconColor.inferFromText();
     // Get an inverted full screen button if the dark theme is applied.
     if (this.styleSheet &&
         document.documentElement.getAttribute("devtoolstheme") == "dark") {
@@ -65,42 +56,23 @@ let DevEdition = {
     // Set an attribute on root element to make it possible
     // to change colors based on the selected devtools theme.
     let devtoolsTheme = Services.prefs.getCharPref(this._devtoolsThemePrefName);
     if (devtoolsTheme != "dark") {
       devtoolsTheme = "light";
     }
     document.documentElement.setAttribute("devtoolstheme", devtoolsTheme);
     this._inferBrightness();
-    this._updateStyleSheetFromPrefs();
-  },
-
-  _updateStyleSheetFromPrefs: function() {
-    let lightweightThemeSelected = false;
-    try {
-      lightweightThemeSelected = !!Services.prefs.getCharPref(this._lwThemePrefName);
-    } catch(e) {}
-
-    let defaultThemeSelected = false;
-    try {
-       defaultThemeSelected = Services.prefs.getCharPref(this._themePrefName) == "classic/1.0";
-    } catch(e) {}
-
-    let deveditionThemeEnabled = Services.prefs.getBoolPref(this._prefName) &&
-      !lightweightThemeSelected && defaultThemeSelected;
-
-    this._toggleStyleSheet(deveditionThemeEnabled);
   },
 
   handleEvent: function(e) {
     if (e.type === "load") {
       this.styleSheet.removeEventListener("load", this);
       gBrowser.tabContainer._positionPinnedTabs();
       this._inferBrightness();
-      Services.obs.notifyObservers(window, "devedition-theme-state-changed", true);
     }
   },
 
   _toggleStyleSheet: function(deveditionThemeEnabled) {
     if (deveditionThemeEnabled && !this.styleSheet) {
       let styleSheetAttr = `href="${this.styleSheetLocation}" type="text/css"`;
       this.styleSheet = document.createProcessingInstruction(
         'xml-stylesheet', styleSheetAttr);
@@ -109,23 +81,20 @@ let DevEdition = {
       // NB: we'll notify observers once the stylesheet has fully loaded, see
       // handleEvent above.
     } else if (!deveditionThemeEnabled && this.styleSheet) {
       this.styleSheet.removeEventListener("load", this);
       this.styleSheet.remove();
       this.styleSheet = null;
       gBrowser.tabContainer._positionPinnedTabs();
       this._inferBrightness();
-      Services.obs.notifyObservers(window, "devedition-theme-state-changed", false);
     }
   },
 
   uninit: function () {
-    Services.prefs.removeObserver(this._lwThemePrefName, this);
-    Services.prefs.removeObserver(this._prefName, this);
     Services.prefs.removeObserver(this._devtoolsThemePrefName, this);
     Services.obs.removeObserver(this, "lightweight-theme-styling-update", false);
     if (this.styleSheet) {
       this.styleSheet.removeEventListener("load", this);
     }
     this.styleSheet = null;
   }
 };
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -40,16 +40,18 @@ XPCOMUtils.defineLazyModuleGetter(this, 
 XPCOMUtils.defineLazyModuleGetter(this, "Log",
                                   "resource://gre/modules/Log.jsm");
 XPCOMUtils.defineLazyServiceGetter(this, "Favicons",
                                    "@mozilla.org/browser/favicon-service;1",
                                    "mozIAsyncFavicons");
 XPCOMUtils.defineLazyServiceGetter(this, "gDNSService",
                                    "@mozilla.org/network/dns-service;1",
                                    "nsIDNSService");
+XPCOMUtils.defineLazyModuleGetter(this, "LightweightThemeManager",
+                                  "resource://gre/modules/LightweightThemeManager.jsm");
 
 const nsIWebNavigation = Ci.nsIWebNavigation;
 
 var gLastBrowserCharset = null;
 var gProxyFavIcon = null;
 var gLastValidURLStr = "";
 var gInPrintPreviewMode = false;
 var gContextMenu = null; // nsContextMenu instance
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..e4e8dcaa3b3cd01a1287359902561166d7f942ee
GIT binary patch
literal 95
zc%17D@N?(olHy`uVBq!ia0vp^j3CU&3?x-=hn)ga%mF?juK#@*VoWXSL2@NQe!&b5
m&u*jvIb5DDjv*Cul9PaJHU?%h^O_Yv7K5j&pUXO@geCw~dlW4I
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..04cfba796dc6aa0bac6b3b368e929009bf01af52
GIT binary patch
literal 2402
zc$@)Z37z(dP)<h;3K|Lk000e1NJLTq001BW001Be1^@s6b9#F8000RoNkl<ZcwWVq
zS%@Xqea3%hsk(jpZrwfI(=*+(SW;^=WMtXnu|1GjMA;O_*vQ02#MrT{2N5R$+t^7S
zY)l@46L3htiUNL+vml9>ID{auY=ILjB72Yoqmj_8OZW6Pz4u+K>fTdl$@RTX!^lH2
z0Yg6c_@8?Ee&;(>9g6>N3GPt8_Ut8nWM<-#Sj!IuIAwEtzZ58hz`Njdq~DNIoVCuq
z&@uk`Z$Es%{agTk^|?!<cg;+E!H4jLq@7MR+ZjpMkQ<Bbc9BY<1jLb|Scs_B%9JXl
z^(fYVy|vwX^4~r_*W-VX<Zb%cv*%-m;!`iKJvcK_y0E#`_}Yc@3sdLby20fuOI)8{
zWodPjjZT;KM#A#)8aJ*ja_!=E&Y!!?&isu7-CpnOwXxD$pZUT1`|lXwZ(qK7V5(gB
zJAmJLVdaVTZvBT>-?()2%A1#2xjIjMZJq7yU0SUU&3d!{yh(FolXidIZnS8uZLqtx
z#<|xoa{0_zRyxUC+nAT1_}8m1|K*R?PW-P&@aQw=pSrr;`PAKqCZ23=ZGClZVTpFv
zkQvW-d6eqhQAWpWES<f`>Slw`>!FoIcn6&p(dZ}u!a0g`*?-U7B&SYtv>5Yl;dVDY
zpM3TscYpu34gABaTk_J)#wQk5c4{lvR=yfK9ZHolwQ7a^hiA!;&2dc@*uHR`)wMdd
zoe*+6uxmDvO5?V-vF#2a@8PYdGC9W4OK<Vxj^V766UplK3y*zo@waXZ(CX%QZSAzj
zXG$@>DCWE^a=kFl?*2pA{Znk^fvMbZ`RXdcIb;+emFmwJ6&FB71R)WP9Yn83?gXo^
zyvE|OU*MPuywu65SIgqs(#F%D``%*tX8;zqI<<X8E%I2ip7ij+1A$tsSj!9mqGuVc
z)$q>YjR6EWi_cO*nxGHNqNit&0kDpi(Ui`eW64WqBgt725yr=6&Svh@KLaqon;uD#
zq}?2RfB*m+sX+w#4zx1EzIPnt$on7Q_y<n$-UsdlA(3$b5j=it4f<wx6ScO4+TEgl
zd67})5Cld9bfjWE_mAEJ@E8AnQ*5-eKg&%HAq>J`Fo#WqAy8y<R<>F!FK@HkNU=c>
z@|=*RgkBflY7%rr5DJ1vq#g3b>r@vOSnWBEM3UeG*7?)704&#&U%9!{K7n%x?-7Bw
zc?eS(jX4}Fr)DRZ)qx9dT}F5ZKrn`=v5rhzkTe)V1Vom?;^JVvY8)<rabe`~=WiU|
z1GuzS|Cs3-5u8U1>JPWf{{}F%DkFH7FI?fxSI=Qh&H&n3gp~MV717#7NI}Rtc%`Wv
zKSp8U5=8+m>p%#c^@yzZ^d3MtQorQ$oZuY{8u+P)YYIZ}&RUVVN)b^WVQXs#=^X;#
zC$n#rbr4G9qasL!%rks1#g#_!AN(-y9*Kwo)Sc&GB)NKZ`PTt7Ks(8fdTa4$q#Ev8
zhCnIO=vcFVBtj(_E=|~{#W37i2q3IQIESvUfzk+@L$OSVM^K#xvQTDg_6XK`=3>o7
z<GC*qn4}~30E&^Gch<f~2|;iQp*+$Dy1krA+T+#rI#Wf-`uq*lpq+q_iXbF7hm;aK
zHjNvpP^jM=90f`d)i=<~S5WpLOkfD?JR`jXSse{~M-VA7Awxh*fgkM7<E>*V*4$Xz
zMHM2-V`Wru1e*_bEb|^BH-jdm%b{37n+)MC@#YOe2m~Pz<uN8bRDHmQfOdhBcclAg
zvOR!KlI0WS5h}$9XC2;qr1SK;2CWrwAz~BFk&`DV2#+%9AZ+Hq1cy(*heM}N4H}9}
zhDw@)W>lxiNZO!q=qS!O3IdwO5joG2u8=rh**gMb^8X0l-CNKFthK{Wtdz(Q7>P9~
zNu(upS*H5XFB5fIMB`&@ON|u*Atcqi<|sY)_hgkxdbMc^>sJ|>J%m60E}XR_G6L(V
zNI|=sQ;yUR_8J(kl)kA16e5kwb8O$vI(o(uE6GGLVkE$1tf|Hk(Y{H%)Qskqu-&9K
zzW^`41f4d8tUYK4ABc~gAkQ7bdPIL}=P3hOW<o^x_Fe<efBqNG-T%dZ*>KKIADpbR
z)lA9roJ@e$nlv?3Dn+tRMzfdW5d`lUD@4pJUZ(Zh8HCM|eS2LX>UIzkxc8kDMPMs4
zRJ@1W4;$`wUpw<h@0-8nF3^I1&U=O|?NTblSYybCO+er6P8R|k^(hB#oaey#S7@F&
zL;dyF5xGHHgYb@^G|n0Jojipqme4>q%`n{_HtV8#U3|Ts?&UHR(+4J>eDmDJKTIuh
zYId4F3<v=TqDUj1!1e+=QHA34ER{-`=6Vw+CB1lb5L`4V#e3#B_^y*Ag)&5v_3b7$
zGvorwN|7!var7OBHx}Ox@ZujobnWC9zw@mtmlmE_UkO;D03ZZH-%;N(Xo3$U#TssG
zlG<2;sg<zp253#~{s%CnDy^=es}+T$OQX?Y-&l!eD`hrzw2EaqLjMy(q1Y45{pRC8
z3a0mXyU{K=APn<iZ-%|g$vKZv8aFn9?>7=3IzosG*xZp=H>|#uF)?070#XRNI)=FL
z-AiA7@K0|Gu=K;{vg059gEx9RJD<#r5iUR&IP6WOB*A-xr|&>%q`+9Yh(ZvYCrvV9
zDba&hI6_E4C--zRgM>4Z@X^+PK4WeR0I>4XbMyB;^4XhaXXhi{<idr3AAZFK^#=lR
z9HV1}jx@mVCUlNo(nSeSfH(Q@xQxdb8>+?lTQY=CEdTk#$({U|#K#`{tIwuQ_w`O{
z3MPtiIz~p(&|0JVmP!pRB|?ay5g-NS$#Hr?;F}F%+xW`--+bm9K*HN3xxM6-fBe#S
zyK6uGe6^yNM|&xSZi2{Ce41d>lsxIXKTYyerzv^2i^o%_mJmr3wRjD=HUIs7`17}9
z-9ZBaC<C)dec%2EKmEJ9de_4y$tzYzSQQbJ0;xes#Grwn7$gu^D}lVT>^3euyYu2x
z-vO3^I^g*~N&{oSaiQbi9zXr)H>W@R<+BGLdunI$k*_r-KmH9r_1M#H>_cDKsD1EH
z=4%gs?#$S4{N6K#d*1i^z)4^Vi1|550;9k*a3Anq-~$NxAaEKu0n7qrKyioqFVKt_
UJkx%vwEzGB07*qoM6N<$f}8H2SpWb4
--- a/browser/base/content/tabbrowser.xml
+++ b/browser/base/content/tabbrowser.xml
@@ -4449,17 +4449,17 @@
       </method>
 
       <method name="_handleTabTelemetryStart">
         <parameter name="aTab"/>
         <parameter name="aURI"/>
         <body>
         <![CDATA[
           // Animation-smoothness telemetry/logging
-          if (Services.telemetry.canRecord || this._tabAnimationLoggingEnabled) {
+          if (Services.telemetry.canRecordExtended || this._tabAnimationLoggingEnabled) {
             if (aURI == "about:newtab" && (aTab._tPos == 1 || aTab._tPos == 2)) {
               // Indicate newtab page animation where other tabs are unaffected
               // (for which case, the 2nd or 3rd tabs are good representatives, even if not absolute)
               aTab._recordingTabOpenPlain = true;
             }
             aTab._recordingHandle = window.QueryInterface(Ci.nsIInterfaceRequestor)
                                           .getInterface(Ci.nsIDOMWindowUtils)
                                           .startFrameTimeRecording();
--- a/browser/base/content/test/general/browser.ini
+++ b/browser/base/content/test/general/browser.ini
@@ -404,16 +404,18 @@ support-files =
 [browser_selectTabAtIndex.js]
 [browser_ssl_error_reports.js]
 [browser_star_hsts.js]
 [browser_subframe_favicons_not_used.js]
 [browser_syncui.js]
 skip-if = e10s # Bug 1137087 - browser_tabopen_reflows.js fails if this was previously run with e10s
 [browser_tabDrop.js]
 skip-if = buildapp == 'mulet' || e10s
+[browser_tabReorder.js]
+skip-if = buildapp == 'mulet'
 [browser_tabMatchesInAwesomebar.js]
 [browser_tabMatchesInAwesomebar_perwindowpb.js]
 skip-if = e10s || os == 'linux' # Bug 1093373, bug 1104755
 [browser_tab_detach_restore.js]
 [browser_tab_drag_drop_perwindow.js]
 skip-if = buildapp == 'mulet'
 [browser_tab_dragdrop.js]
 skip-if = buildapp == 'mulet'
--- a/browser/base/content/test/general/browser_devedition.js
+++ b/browser/base/content/test/general/browser_devedition.js
@@ -1,72 +1,65 @@
 /*
  * Testing changes for Developer Edition theme.
  * A special stylesheet should be added to the browser.xul document
  * when browser.devedition.theme.enabled is set to true and no themes
  * are applied.
  */
 
-const PREF_DEVEDITION_THEME = "browser.devedition.theme.enabled";
-const PREF_LWTHEME = "lightweightThemes.selectedThemeID";
 const PREF_LWTHEME_USED_THEMES = "lightweightThemes.usedThemes";
 const PREF_DEVTOOLS_THEME = "devtools.theme";
 const {LightweightThemeManager} = Components.utils.import("resource://gre/modules/LightweightThemeManager.jsm", {});
 
+LightweightThemeManager.clearBuiltInThemes();
+LightweightThemeManager.addBuiltInTheme(dummyLightweightTheme("firefox-devedition@mozilla.org"));
+
 registerCleanupFunction(() => {
   // Set preferences back to their original values
   LightweightThemeManager.currentTheme = null;
-  Services.prefs.clearUserPref(PREF_DEVEDITION_THEME);
-  Services.prefs.clearUserPref(PREF_LWTHEME);
   Services.prefs.clearUserPref(PREF_DEVTOOLS_THEME);
   Services.prefs.clearUserPref(PREF_LWTHEME_USED_THEMES);
+
+  LightweightThemeManager.currentTheme = null;
+  LightweightThemeManager.clearBuiltInThemes();
 });
 
 add_task(function* startTests() {
   Services.prefs.setCharPref(PREF_DEVTOOLS_THEME, "dark");
 
-  info ("Setting browser.devedition.theme.enabled to false.");
-  Services.prefs.setBoolPref(PREF_DEVEDITION_THEME, false);
-  ok (!DevEdition.styleSheet, "There is no devedition style sheet when the pref is false.");
-
-  info ("Setting browser.devedition.theme.enabled to true.");
-  Services.prefs.setBoolPref(PREF_DEVEDITION_THEME, true);
-  ok (DevEdition.styleSheet, "There is a devedition stylesheet when no themes are applied and pref is set.");
+  info ("Setting the current theme to null");
+  LightweightThemeManager.currentTheme = null;
+  ok (!DevEdition.styleSheet, "There is no devedition style sheet when no lw theme is applied.");
 
   info ("Adding a lightweight theme.");
   LightweightThemeManager.currentTheme = dummyLightweightTheme("preview0");
   ok (!DevEdition.styleSheet, "The devedition stylesheet has been removed when a lightweight theme is applied.");
 
-  info ("Removing a lightweight theme.");
+  info ("Applying the devedition lightweight theme.");
   let onAttributeAdded = waitForBrightTitlebarAttribute();
-  LightweightThemeManager.currentTheme = null;
-  ok (DevEdition.styleSheet, "The devedition stylesheet has been added when a lightweight theme is removed.");
+  LightweightThemeManager.currentTheme = LightweightThemeManager.getUsedTheme("firefox-devedition@mozilla.org");
+  ok (DevEdition.styleSheet, "The devedition stylesheet has been added when the devedition lightweight theme is applied");
   yield onAttributeAdded;
-
   is (document.documentElement.getAttribute("brighttitlebarforeground"), "true",
      "The brighttitlebarforeground attribute is set on the window.");
 
-  info ("Setting browser.devedition.theme.enabled to false.");
-  Services.prefs.setBoolPref(PREF_DEVEDITION_THEME, false);
-  ok (!DevEdition.styleSheet, "The devedition stylesheet has been removed.");
+  info ("Unapplying all themes.");
+  LightweightThemeManager.currentTheme = null;
+  ok (!DevEdition.styleSheet, "There is no devedition style sheet when no lw theme is applied.");
 
-  ok (!document.documentElement.hasAttribute("brighttitlebarforeground"),
-     "The brighttitlebarforeground attribute is not set on the window after devedition.theme is false.");
+  info ("Applying the devedition lightweight theme.");
+  onAttributeAdded = waitForBrightTitlebarAttribute();
+  LightweightThemeManager.currentTheme = LightweightThemeManager.getUsedTheme("firefox-devedition@mozilla.org");
+  ok (DevEdition.styleSheet, "The devedition stylesheet has been added when the devedition lightweight theme is applied");
+  yield onAttributeAdded;
+  ok (document.documentElement.hasAttribute("brighttitlebarforeground"),
+     "The brighttitlebarforeground attribute is set on the window with dark devtools theme.");
 });
 
 add_task(function* testDevtoolsTheme() {
-  info ("Checking that Australis is shown when the light devtools theme is applied.");
-
-  let onAttributeAdded = waitForBrightTitlebarAttribute();
-  Services.prefs.setBoolPref(PREF_DEVEDITION_THEME, true);
-  ok (DevEdition.styleSheet, "The devedition stylesheet exists.");
-  yield onAttributeAdded;
-  ok (document.documentElement.hasAttribute("brighttitlebarforeground"),
-     "The brighttitlebarforeground attribute is set on the window with dark devtools theme.");
-
   info ("Checking stylesheet and :root attributes based on devtools theme.");
   Services.prefs.setCharPref(PREF_DEVTOOLS_THEME, "light");
   is (document.documentElement.getAttribute("devtoolstheme"), "light",
     "The documentElement has an attribute based on devtools theme.");
   ok (DevEdition.styleSheet, "The devedition stylesheet is still there with the light devtools theme.");
   ok (!document.documentElement.hasAttribute("brighttitlebarforeground"),
      "The brighttitlebarforeground attribute is not set on the window with light devtools theme.");
 
@@ -84,52 +77,43 @@ add_task(function* testDevtoolsTheme() {
   ok (!document.documentElement.hasAttribute("brighttitlebarforeground"),
      "The brighttitlebarforeground attribute is not set on the window with light devtools theme.");
 });
 
 function dummyLightweightTheme(id) {
   return {
     id: id,
     name: id,
-    headerURL: "resource:///chrome/browser/content/browser/defaultthemes/1.header.jpg",
-    iconURL: "resource:///chrome/browser/content/browser/defaultthemes/1.icon.jpg",
+    headerURL: "resource:///chrome/browser/content/browser/defaultthemes/devedition.header.png",
+    iconURL: "resource:///chrome/browser/content/browser/defaultthemes/devedition.icon.png",
     textcolor: "red",
     accentcolor: "blue"
   };
 }
 
 add_task(function* testLightweightThemePreview() {
-  info ("Turning the pref on, then previewing lightweight themes");
-  Services.prefs.setBoolPref(PREF_DEVEDITION_THEME, true);
+  info ("Setting devedition to current and the previewing others");
+  LightweightThemeManager.currentTheme = LightweightThemeManager.getUsedTheme("firefox-devedition@mozilla.org");
   ok (DevEdition.styleSheet, "The devedition stylesheet is enabled.");
   LightweightThemeManager.previewTheme(dummyLightweightTheme("preview0"));
   ok (!DevEdition.styleSheet, "The devedition stylesheet is not enabled after a lightweight theme preview.");
   LightweightThemeManager.resetPreview();
   LightweightThemeManager.previewTheme(dummyLightweightTheme("preview1"));
   ok (!DevEdition.styleSheet, "The devedition stylesheet is not enabled after a second lightweight theme preview.");
   LightweightThemeManager.resetPreview();
   ok (DevEdition.styleSheet, "The devedition stylesheet is enabled again after resetting the preview.");
+  LightweightThemeManager.currentTheme = null;
+  ok (!DevEdition.styleSheet, "The devedition stylesheet is gone after removing the current theme.");
 
-  info ("Turning the pref on, then previewing a theme, turning it off and resetting the preview");
-  Services.prefs.setBoolPref(PREF_DEVEDITION_THEME, true);
+  info ("Previewing the devedition theme");
+  LightweightThemeManager.previewTheme(LightweightThemeManager.getUsedTheme("firefox-devedition@mozilla.org"));
   ok (DevEdition.styleSheet, "The devedition stylesheet is enabled.");
   LightweightThemeManager.previewTheme(dummyLightweightTheme("preview2"));
-  ok (!DevEdition.styleSheet, "The devedition stylesheet is not enabled after a lightweight theme preview.");
-  Services.prefs.setBoolPref(PREF_DEVEDITION_THEME, false);
-  ok (!DevEdition.styleSheet, "The devedition stylesheet is not enabled after pref is turned off.");
   LightweightThemeManager.resetPreview();
-  ok (!DevEdition.styleSheet, "The devedition stylesheet is still disabled after resetting the preview.");
-
-  info ("Turning the pref on, then previewing the default theme, turning it off and resetting the preview");
-  Services.prefs.setBoolPref(PREF_DEVEDITION_THEME, true);
-  ok (DevEdition.styleSheet, "The devedition stylesheet is enabled.");
-  LightweightThemeManager.previewTheme(null);
-  ok (DevEdition.styleSheet, "The devedition stylesheet is still enabled after the default theme is applied.");
-  LightweightThemeManager.resetPreview();
-  ok (DevEdition.styleSheet, "The devedition stylesheet is still enabled after resetting the preview.");
+  ok (!DevEdition.styleSheet, "The devedition stylesheet is now disabled after resetting the preview.");
 });
 
 // Use a mutation observer to wait for the brighttitlebarforeground
 // attribute to change.  Using this instead of waiting for the load
 // event on the DevEdition styleSheet.
 function waitForBrightTitlebarAttribute() {
   return new Promise((resolve, reject) => {
     let mutationObserver = new MutationObserver(function (mutations) {
--- a/browser/base/content/test/general/browser_fxa_profile_channel.html
+++ b/browser/base/content/test/general/browser_fxa_profile_channel.html
@@ -6,17 +6,17 @@
 </head>
 <body>
 <script>
   window.onload = function(){
     var event = new window.CustomEvent("WebChannelMessageToChrome", {
       detail: {
         id: "account_updates",
         message: {
-          command: "profile:image:change",
+          command: "profile:change",
           data: {
             uid: "abc123",
           },
         },
       },
     });
 
     window.dispatchEvent(event);
--- a/browser/base/content/test/general/browser_fxa_profile_channel.js
+++ b/browser/base/content/test/general/browser_fxa_profile_channel.js
@@ -29,17 +29,17 @@ let gTests = [
 
           tabOpened = true;
         });
 
         let client = new FxAccountsProfileChannel({
           content_uri: HTTP_PATH,
         });
 
-        makeObserver(FxAccountsCommon.ONPROFILE_IMAGE_CHANGE_NOTIFICATION, function (subject, topic, data) {
+        makeObserver(FxAccountsCommon.ON_PROFILE_CHANGE_NOTIFICATION, function (subject, topic, data) {
           Assert.ok(tabOpened);
           Assert.equal(data, "abc123");
           resolve();
           gBrowser.removeCurrentTab();
         });
 
         gBrowser.selectedTab = gBrowser.addTab(properUrl);
       });
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/general/browser_tabReorder.js
@@ -0,0 +1,82 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+function test() {
+  let initialTabsLength = gBrowser.tabs.length;
+
+  let newTab1 = gBrowser.selectedTab = gBrowser.addTab("about:robots", {skipAnimation: true});
+  let newTab2 = gBrowser.selectedTab = gBrowser.addTab("about:about", {skipAnimation: true});
+  let newTab3 = gBrowser.selectedTab = gBrowser.addTab("about:config", {skipAnimation: true});
+  registerCleanupFunction(function () {
+    while (gBrowser.tabs.length > initialTabsLength) {
+      gBrowser.removeTab(gBrowser.tabs[initialTabsLength]);
+    }
+  });
+
+  is(gBrowser.tabs.length, initialTabsLength + 3, "new tabs are opened");
+  is(gBrowser.tabs[initialTabsLength], newTab1, "newTab1 position is correct");
+  is(gBrowser.tabs[initialTabsLength + 1], newTab2, "newTab2 position is correct");
+  is(gBrowser.tabs[initialTabsLength + 2], newTab3, "newTab3 position is correct");
+
+  let dataTransfer;
+  let trapDrag = function(event) {
+    dataTransfer = event.dataTransfer;
+  };
+  window.addEventListener("dragstart", trapDrag, true);
+  registerCleanupFunction(function () {
+    window.removeEventListener("dragstart", trapDrag, true);
+  });
+
+  let windowUtil = window.QueryInterface(Components.interfaces.nsIInterfaceRequestor).
+                          getInterface(Components.interfaces.nsIDOMWindowUtils);
+  let ds = Components.classes["@mozilla.org/widget/dragservice;1"].
+           getService(Components.interfaces.nsIDragService);
+
+  function dragAndDrop(tab1, tab2, copy) {
+    let ctrlKey = copy;
+    let altKey = copy;
+
+    let rect = tab1.getBoundingClientRect();
+    let x = rect.width / 2;
+    let y = rect.height / 2;
+    let diffX = 10;
+
+    ds.startDragSession();
+    try {
+      EventUtils.synthesizeMouse(tab1, x, y, { type: "mousedown" }, window);
+      EventUtils.synthesizeMouse(tab1, x + diffX, y, { type: "mousemove" }, window);
+
+      dataTransfer.dropEffect = copy ? "copy" : "move";
+
+      let event = window.document.createEvent("DragEvents");
+      event.initDragEvent("dragover", true, true, window, 0,
+                          tab2.boxObject.screenX + x + diffX,
+                          tab2.boxObject.screenY + y,
+                          x + diffX, y, ctrlKey, altKey, false, false, 0, null, dataTransfer);
+      windowUtil.dispatchDOMEventViaPresShell(tab2, event, true);
+
+      event = window.document.createEvent("DragEvents");
+      event.initDragEvent("drop", true, true, window, 0,
+                          tab2.boxObject.screenX + x + diffX,
+                          tab2.boxObject.screenY + y,
+                          x + diffX, y, ctrlKey, altKey, false, false, 0, null, dataTransfer);
+      windowUtil.dispatchDOMEventViaPresShell(tab2, event, true);
+
+      EventUtils.synthesizeMouse(tab2, x + diffX, y, { type: "mouseup" }, window);
+    } finally {
+      ds.endDragSession(true);
+    }
+  }
+
+  dragAndDrop(newTab1, newTab2, false);
+  is(gBrowser.tabs.length, initialTabsLength + 3, "tabs are still there");
+  is(gBrowser.tabs[initialTabsLength], newTab2, "newTab2 and newTab1 are swapped");
+  is(gBrowser.tabs[initialTabsLength + 1], newTab1, "newTab1 and newTab2 are swapped");
+  is(gBrowser.tabs[initialTabsLength + 2], newTab3, "newTab3 stays same place");
+
+  dragAndDrop(newTab2, newTab1, true);
+  is(gBrowser.tabs.length, initialTabsLength + 4, "a tab is duplicated");
+  is(gBrowser.tabs[initialTabsLength], newTab2, "newTab2 stays same place");
+  is(gBrowser.tabs[initialTabsLength + 1], newTab1, "newTab1 stays same place");
+  is(gBrowser.tabs[initialTabsLength + 3], newTab3, "a new tab is inserted before newTab3");
+}
--- a/browser/base/jar.mn
+++ b/browser/base/jar.mn
@@ -93,16 +93,18 @@ browser.jar:
         content/browser/defaultthemes/4.footer.png    (content/defaultthemes/4.footer.png)
         content/browser/defaultthemes/4.header.png    (content/defaultthemes/4.header.png)
         content/browser/defaultthemes/4.icon.png      (content/defaultthemes/4.icon.png)
         content/browser/defaultthemes/4.preview.png   (content/defaultthemes/4.preview.png)
         content/browser/defaultthemes/5.footer.png    (content/defaultthemes/5.footer.png)
         content/browser/defaultthemes/5.header.png    (content/defaultthemes/5.header.png)
         content/browser/defaultthemes/5.icon.jpg      (content/defaultthemes/5.icon.jpg)
         content/browser/defaultthemes/5.preview.jpg   (content/defaultthemes/5.preview.jpg)
+        content/browser/defaultthemes/devedition.header.png   (content/defaultthemes/devedition.header.png)
+        content/browser/defaultthemes/devedition.icon.png     (content/defaultthemes/devedition.icon.png)
         content/browser/newtab/newTab.xul             (content/newtab/newTab.xul)
 *       content/browser/newtab/newTab.js              (content/newtab/newTab.js)
         content/browser/newtab/newTab.css             (content/newtab/newTab.css)
 *       content/browser/pageinfo/pageInfo.xul         (content/pageinfo/pageInfo.xul)
         content/browser/pageinfo/pageInfo.js          (content/pageinfo/pageInfo.js)
         content/browser/pageinfo/pageInfo.css         (content/pageinfo/pageInfo.css)
         content/browser/pageinfo/pageInfo.xml         (content/pageinfo/pageInfo.xml)
         content/browser/pageinfo/feeds.js             (content/pageinfo/feeds.js)
--- a/browser/components/customizableui/CustomizableUI.jsm
+++ b/browser/components/customizableui/CustomizableUI.jsm
@@ -32,17 +32,16 @@ XPCOMUtils.defineLazyServiceGetter(this,
 const kNSXUL = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
 
 const kSpecialWidgetPfx = "customizableui-special-";
 
 const kPrefCustomizationState        = "browser.uiCustomization.state";
 const kPrefCustomizationAutoAdd      = "browser.uiCustomization.autoAdd";
 const kPrefCustomizationDebug        = "browser.uiCustomization.debug";
 const kPrefDrawInTitlebar            = "browser.tabs.drawInTitlebar";
-const kPrefDeveditionTheme           = "browser.devedition.theme.enabled";
 const kPrefWebIDEInNavbar            = "devtools.webide.widget.inNavbarByDefault";
 
 /**
  * The keys are the handlers that are fired when the event type (the value)
  * is fired on the subview. A widget that provides a subview has the option
  * of providing onViewShowing and onViewHiding event handlers.
  */
 const kSubviewEvents = [
@@ -2327,25 +2326,23 @@ let CustomizableUIInternal = {
     }
 
     gResetting = false;
   },
 
   _resetUIState: function() {
     try {
       gUIStateBeforeReset.drawInTitlebar = Services.prefs.getBoolPref(kPrefDrawInTitlebar);
-      gUIStateBeforeReset.deveditionTheme = Services.prefs.getBoolPref(kPrefDeveditionTheme);
       gUIStateBeforeReset.uiCustomizationState = Services.prefs.getCharPref(kPrefCustomizationState);
     } catch(e) { }
 
     this._resetExtraToolbars();
 
     Services.prefs.clearUserPref(kPrefCustomizationState);
     Services.prefs.clearUserPref(kPrefDrawInTitlebar);
-    Services.prefs.clearUserPref(kPrefDeveditionTheme);
     LOG("State reset");
 
     // Reset placements to make restoring default placements possible.
     gPlacements = new Map();
     gDirtyAreaCache = new Set();
     gSeenWidgets = new Set();
     // Clear the saved state to ensure that defaults will be used.
     gSavedState = null;
@@ -2397,33 +2394,30 @@ let CustomizableUIInternal = {
     }
   },
 
   /**
    * Undoes a previous reset, restoring the state of the UI to the state prior to the reset.
    */
   undoReset: function() {
     if (gUIStateBeforeReset.uiCustomizationState == null ||
-        gUIStateBeforeReset.drawInTitlebar == null ||
-        gUIStateBeforeReset.deveditionTheme == null) {
+        gUIStateBeforeReset.drawInTitlebar == null) {
       return;
     }
     gUndoResetting = true;
 
     let uiCustomizationState = gUIStateBeforeReset.uiCustomizationState;
     let drawInTitlebar = gUIStateBeforeReset.drawInTitlebar;
-    let deveditionTheme = gUIStateBeforeReset.deveditionTheme;
 
     // Need to clear the previous state before setting the prefs
     // because pref observers may check if there is a previous UI state.
     this._clearPreviousUIState();
 
     Services.prefs.setCharPref(kPrefCustomizationState, uiCustomizationState);
     Services.prefs.setBoolPref(kPrefDrawInTitlebar, drawInTitlebar);
-    Services.prefs.setBoolPref(kPrefDeveditionTheme, deveditionTheme);
     this.loadSavedState();
     // If the user just customizes toolbar/titlebar visibility, gSavedState will be null
     // and we don't need to do anything else here:
     if (gSavedState) {
       for (let areaId of Object.keys(gSavedState.placements)) {
         let placements = gSavedState.placements[areaId];
         gPlacements.set(areaId, placements);
       }
@@ -2591,20 +2585,16 @@ let CustomizableUIInternal = {
         }
       }
     }
 
     if (Services.prefs.prefHasUserValue(kPrefDrawInTitlebar)) {
       LOG(kPrefDrawInTitlebar + " pref is non-default");
       return false;
     }
-    if (Services.prefs.prefHasUserValue(kPrefDeveditionTheme)) {
-      LOG(kPrefDeveditionTheme + " pref is non-default");
-      return false;
-    }
 
     return true;
   },
 
   setToolbarVisibility: function(aToolbarId, aIsVisible) {
     // We only persist the attribute the first time.
     let isFirstChangedToolbar = true;
     for (let window of CustomizableUI.windows) {
@@ -3295,18 +3285,17 @@ this.CustomizableUI = {
   /**
    * Can the last Restore Defaults operation be undone.
    *
    * @return A boolean stating whether an undo of the
    *         Restore Defaults can be performed.
    */
   get canUndoReset() {
     return gUIStateBeforeReset.uiCustomizationState != null ||
-           gUIStateBeforeReset.drawInTitlebar != null ||
-           gUIStateBeforeReset.deveditionTheme != null;
+           gUIStateBeforeReset.drawInTitlebar != null;
   },
 
   /**
    * Get the placement of a widget. This is by far the best way to obtain
    * information about what the state of your widget is. The internals of
    * this call are cheap (no DOM necessary) and you will know where the user
    * has put your widget.
    *
--- a/browser/components/customizableui/CustomizeMode.jsm
+++ b/browser/components/customizableui/CustomizeMode.jsm
@@ -12,19 +12,16 @@ const kPrefCustomizationDebug = "browser
 const kPrefCustomizationAnimation = "browser.uiCustomization.disableAnimation";
 const kPaletteId = "customization-palette";
 const kAboutURI = "about:customizing";
 const kDragDataTypePrefix = "text/toolbarwrapper-id/";
 const kPlaceholderClass = "panel-customization-placeholder";
 const kSkipSourceNodePref = "browser.uiCustomization.skipSourceNodeCheck";
 const kToolbarVisibilityBtn = "customization-toolbar-visibility-button";
 const kDrawInTitlebarPref = "browser.tabs.drawInTitlebar";
-const kDeveditionThemePref = "browser.devedition.theme.enabled";
-const kDeveditionButtonPref = "browser.devedition.theme.showCustomizeButton";
-const kDeveditionChangedNotification = "devedition-theme-state-changed";
 const kMaxTransitionDurationMs = 2000;
 
 const kPanelItemContextMenu = "customizationPanelItemContextMenu";
 const kPaletteItemContextMenu = "customizationPaletteItemContextMenu";
 
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource:///modules/CustomizableUI.jsm");
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
@@ -62,27 +59,22 @@ function CustomizeMode(aWindow) {
   // user. Then there's the visible palette, which gets populated and displayed
   // to the user when in customizing mode.
   this.visiblePalette = this.document.getElementById(kPaletteId);
   this.paletteEmptyNotice = this.document.getElementById("customization-empty");
   this.paletteSpacer = this.document.getElementById("customization-spacer");
   this.tipPanel = this.document.getElementById("customization-tipPanel");
   if (Services.prefs.getCharPref("general.skins.selectedSkin") != "classic/1.0") {
     let lwthemeButton = this.document.getElementById("customization-lwtheme-button");
-    let deveditionButton = this.document.getElementById("customization-devedition-theme-button");
     lwthemeButton.setAttribute("hidden", "true");
-    deveditionButton.setAttribute("hidden", "true");
   }
 #ifdef CAN_DRAW_IN_TITLEBAR
   this._updateTitlebarButton();
   Services.prefs.addObserver(kDrawInTitlebarPref, this, false);
 #endif
-  this._updateDevEditionThemeButton();
-  Services.prefs.addObserver(kDeveditionButtonPref, this, false);
-  Services.obs.addObserver(this, kDeveditionChangedNotification, false);
   this.window.addEventListener("unload", this);
 };
 
 CustomizeMode.prototype = {
   _changed: false,
   _transitioning: false,
   window: null,
   document: null,
@@ -108,18 +100,16 @@ CustomizeMode.prototype = {
   get _handler() {
     return this.window.CustomizationHandler;
   },
 
   uninit: function() {
 #ifdef CAN_DRAW_IN_TITLEBAR
     Services.prefs.removeObserver(kDrawInTitlebarPref, this);
 #endif
-    Services.prefs.removeObserver(kDeveditionButtonPref, this);
-    Services.obs.removeObserver(this, kDeveditionChangedNotification);
   },
 
   toggle: function() {
     if (this._handler.isEnteringCustomizeMode || this._handler.isExitingCustomizeMode) {
       this._wantToBeInCustomizeMode = !this._wantToBeInCustomizeMode;
       return;
     }
     if (this._customizing) {
@@ -1502,23 +1492,16 @@ CustomizeMode.prototype = {
           aData = JSON.parse(aData);
           if (!aData) {
             this.removeLWTStyling();
           } else {
             this.updateLWTStyling(aData);
           }
         }
         break;
-      case kDeveditionChangedNotification:
-        if (aSubject == this.window) {
-          this._updateDevEditionThemeButton();
-          this._updateResetButton();
-          this._updateUndoResetButton();
-        }
-        break;
     }
   },
 
 #ifdef CAN_DRAW_IN_TITLEBAR
   _updateTitlebarButton: function() {
     let drawInTitlebar = true;
     try {
       drawInTitlebar = Services.prefs.getBoolPref(kDrawInTitlebarPref);
@@ -1533,54 +1516,16 @@ CustomizeMode.prototype = {
   },
 
   toggleTitlebar: function(aShouldShowTitlebar) {
     // Drawing in the titlebar means not showing the titlebar, hence the negation:
     Services.prefs.setBoolPref(kDrawInTitlebarPref, !aShouldShowTitlebar);
   },
 #endif
 
-  _updateDevEditionThemeButton: function() {
-    let button = this.document.getElementById("customization-devedition-theme-button");
-
-    let themeEnabled = !!this.window.DevEdition.styleSheet;
-    if (themeEnabled) {
-      button.setAttribute("checked", "true");
-    } else {
-      button.removeAttribute("checked");
-    }
-
-    let buttonVisible = Services.prefs.getBoolPref(kDeveditionButtonPref);
-    if (buttonVisible) {
-      button.removeAttribute("hidden");
-    } else {
-      button.setAttribute("hidden", "true");
-    }
-  },
-
-  toggleDevEditionTheme: function(shouldEnable) {
-    const DEFAULT_THEME_ID = "{972ce4c6-7e08-4474-a285-3208198ce6fd}";
-
-    Services.prefs.setBoolPref(kDeveditionThemePref, shouldEnable);
-
-    let currentLWT = LightweightThemeManager.currentTheme;
-    if (currentLWT && shouldEnable) {
-      this._lastLightweightTheme = currentLWT;
-      AddonManager.getAddonByID(DEFAULT_THEME_ID, function(aDefaultTheme) {
-        // Theoretically, this could race if people are /very/ quick in switching
-        // something else here, so doublecheck:
-        if (Services.prefs.getBoolPref(kDeveditionThemePref)) {
-          aDefaultTheme.userDisabled = false;
-        }
-      });
-    } else if (!currentLWT && !shouldEnable && this._lastLightweightTheme) {
-      LightweightThemeManager.currentTheme = this._lastLightweightTheme;
-    }
-  },
-
   _onDragStart: function(aEvent) {
     __dumpDragData(aEvent);
     let item = aEvent.target;
     while (item && item.localName != "toolbarpaletteitem") {
       if (item.localName == "toolbar") {
         return;
       }
       item = item.parentNode;
--- a/browser/components/customizableui/content/customizeMode.inc.xul
+++ b/browser/components/customizableui/content/customizeMode.inc.xul
@@ -48,23 +48,16 @@
                            label="&customizeMode.lwthemes.menuGetMore;"
                            accesskey="&customizeMode.lwthemes.menuGetMore.accessKey;"
                            tabindex="0"
                            oncommand="gCustomizeMode.getMoreThemes(event);"/>
           </hbox>
         </panel>
       </button>
 
-      <button id="customization-devedition-theme-button"
-              class="customizationmode-button"
-              hidden="true"
-              label="&customizeMode.deveditionTheme.label2;"
-              oncommand="gCustomizeMode.toggleDevEditionTheme(this.hasAttribute('checked'))"
-              type="checkbox" />
-
       <spacer id="customization-footer-spacer"/>
       <button id="customization-undo-reset-button"
               class="customizationmode-button"
               hidden="true"
               oncommand="gCustomizeMode.undoReset();"
               label="&undoCmd.label;"/>
       <button id="customization-reset-button"
               oncommand="gCustomizeMode.reset();"
--- a/browser/components/customizableui/test/browser_1007336_lwthemes_in_customize_mode.js
+++ b/browser/components/customizableui/test/browser_1007336_lwthemes_in_customize_mode.js
@@ -1,19 +1,21 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
   * License, v. 2.0. If a copy of the MPL was not distributed with this
   * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 const DEFAULT_THEME_ID = "{972ce4c6-7e08-4474-a285-3208198ce6fd}";
+const {LightweightThemeManager} = Components.utils.import("resource://gre/modules/LightweightThemeManager.jsm", {});
 
 add_task(function () {
   Services.prefs.clearUserPref("lightweightThemes.usedThemes");
   Services.prefs.clearUserPref("lightweightThemes.recommendedThemes");
+  LightweightThemeManager.clearBuiltInThemes();
 
   yield startCustomizing();
 
   let themesButton = document.getElementById("customization-lwtheme-button");
   let popup = document.getElementById("customization-lwtheme-menu");
 
   let popupShownPromise = popupShown(popup);
   EventUtils.synthesizeMouseAtCenter(themesButton, {});
--- a/browser/components/customizableui/test/browser_970511_undo_restore_default.js
+++ b/browser/components/customizableui/test/browser_970511_undo_restore_default.js
@@ -96,54 +96,12 @@ add_task(function() {
   is(Services.prefs.getBoolPref(prefName), !defaultValue, "Undo-reset goes back to previous pref value");
   is(undoResetButton.hidden, true, "Undo reset button should be hidden after undo-reset clicked");
 
   Services.prefs.clearUserPref(prefName);
   ok(CustomizableUI.inDefaultState, "In default state after pref cleared");
   is(undoResetButton.hidden, true, "Undo reset button should be hidden at end of test");
 });
 
-// Bug 1082108 - Restore Defaults should clear user pref for devedition theme
-add_task(function() {
-  let prefName = "browser.devedition.theme.enabled";
-  Services.prefs.setBoolPref("browser.devedition.theme.showCustomizeButton", true);
-  let defaultValue = Services.prefs.getBoolPref(prefName);
-  let restoreDefaultsButton = document.getElementById("customization-reset-button");
-  let deveditionThemeButton = document.getElementById("customization-devedition-theme-button");
-  let undoResetButton = document.getElementById("customization-undo-reset-button");
-  ok(CustomizableUI.inDefaultState, "Should be in default state at start of test");
-  ok(restoreDefaultsButton.disabled, "Restore defaults button should be disabled when in default state");
-  is(deveditionThemeButton.hasAttribute("checked"), defaultValue, "Devedition theme button should reflect pref value");
-  is(undoResetButton.hidden, true, "Undo reset button should be hidden at start of test");
-  Services.prefs.setBoolPref(prefName, !defaultValue);
-
-  yield waitForCondition(() => !restoreDefaultsButton.disabled);
-  ok(!restoreDefaultsButton.disabled, "Restore defaults button should be enabled when pref changed");
-  is(deveditionThemeButton.hasAttribute("checked"), !defaultValue, "Devedition theme button should reflect changed pref value");
-  ok(!CustomizableUI.inDefaultState, "With devedition theme flipped, no longer default");
-  is(undoResetButton.hidden, true, "Undo reset button should be hidden after pref change");
-
-  yield gCustomizeMode.reset();
-  ok(restoreDefaultsButton.disabled, "Restore defaults button should be disabled after reset");
-  is(deveditionThemeButton.hasAttribute("checked"), defaultValue, "devedition theme button should reflect default value after reset");
-  is(Services.prefs.getBoolPref(prefName), defaultValue, "Reset should reset devedition.theme.enabled");
-  ok(CustomizableUI.inDefaultState, "In default state after devedition theme reset");
-  is(undoResetButton.hidden, false, "Undo reset button should be visible after reset");
-  ok(!undoResetButton.disabled, "Undo reset button should be enabled after reset");
-
-  yield gCustomizeMode.undoReset();
-  ok(!restoreDefaultsButton.disabled, "Restore defaults button should be enabled after undo-reset");
-  is(deveditionThemeButton.hasAttribute("checked"), !defaultValue, "devedition theme button should reflect undo-reset value");
-  ok(!CustomizableUI.inDefaultState, "No longer in default state after undo");
-  is(Services.prefs.getBoolPref(prefName), !defaultValue, "Undo-reset goes back to previous pref value");
-  is(undoResetButton.hidden, true, "Undo reset button should be hidden after undo-reset clicked");
-
-  //XXXgijs this line should be removed once bug 1094509 lands
-  Services.prefs.clearUserPref("devtools.theme");
-  Services.prefs.clearUserPref(prefName);
-  ok(CustomizableUI.inDefaultState, "In default state after pref cleared");
-  is(undoResetButton.hidden, true, "Undo reset button should be hidden at end of test");
-});
-
 add_task(function asyncCleanup() {
   yield gCustomizeMode.reset();
   yield endCustomizing();
 });
--- a/browser/components/downloads/DownloadsCommon.jsm
+++ b/browser/components/downloads/DownloadsCommon.jsm
@@ -546,17 +546,18 @@ this.DownloadsCommon = {
    *
    * @return True to unblock the file, false to keep the user safe and
    *         cancel the operation.
    */
   confirmUnblockDownload: Task.async(function* (aType, aOwnerWindow) {
     let s = DownloadsCommon.strings;
     let title = s.unblockHeader;
     let buttonFlags = (Ci.nsIPrompt.BUTTON_TITLE_IS_STRING * Ci.nsIPrompt.BUTTON_POS_0) +
-                      (Ci.nsIPrompt.BUTTON_TITLE_IS_STRING * Ci.nsIPrompt.BUTTON_POS_1);
+                      (Ci.nsIPrompt.BUTTON_TITLE_IS_STRING * Ci.nsIPrompt.BUTTON_POS_1) +
+                      Ci.nsIPrompt.BUTTON_POS_1_DEFAULT;
     let type = "";
     let message = s.unblockTip;
     let okButton = s.unblockButtonContinue;
     let cancelButton = s.unblockButtonCancel;
 
     switch (aType) {
       case this.BLOCK_VERDICT_MALWARE:
         type = s.unblockTypeMalware;
@@ -587,19 +588,21 @@ this.DownloadsCommon = {
               // Change the dialog to use a warning icon.
               dialog.classList.add("alert-dialog");
             }
           }
         });
       }
     });
 
+    // The ordering of the ok/cancel buttons is used this way to allow "cancel"
+    // to have the same result as hitting the ESC or Close button (see bug 345067).
     let rv = Services.prompt.confirmEx(aOwnerWindow, title, message, buttonFlags,
-                                       cancelButton, okButton, null, null, {});
-    return (rv == 1);
+                                       okButton, cancelButton, null, null, {});
+    return (rv == 0);
   }),
 };
 
 /**
  * Returns true if we are executing on Windows Vista or a later version.
  */
 XPCOMUtils.defineLazyGetter(DownloadsCommon, "isWinVistaOrHigher", function () {
   let os = Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULRuntime).OS;
--- a/browser/components/downloads/test/browser/browser_confirm_unblock_download.js
+++ b/browser/components/downloads/test/browser/browser_confirm_unblock_download.js
@@ -27,20 +27,20 @@ function addDialogOpenObserver(buttonAct
           doc.getButton(buttonAction).click();
         }
       });
     }
   });
 }
 
 add_task(function* test_confirm_unblock_dialog_unblock() {
-  addDialogOpenObserver("cancel");
+  addDialogOpenObserver("accept");
   let result = yield DownloadsCommon.confirmUnblockDownload(DownloadsCommon.BLOCK_VERDICT_MALWARE,
                                                             window);
   ok(result, "Should return true when the user clicks on `Unblock` button.");
 });
 
 add_task(function* test_confirm_unblock_dialog_keep_safe() {
-  addDialogOpenObserver("accept");
+  addDialogOpenObserver("cancel");
   let result = yield DownloadsCommon.confirmUnblockDownload(DownloadsCommon.BLOCK_VERDICT_MALWARE,
                                                             window);
   ok(!result, "Should return false when the user clicks on `Keep me safe` button.");
 });
--- a/browser/components/loop/MozLoopService.jsm
+++ b/browser/components/loop/MozLoopService.jsm
@@ -889,18 +889,17 @@ let MozLoopServiceInternal = {
                   (context.callId? " call=" + context.callId : "") + " " + pair[1]; //)
             }
           }
 
           if (type == "iceconnectionstatechange") {
             switch(pc.iceConnectionState) {
               case "failed":
               case "disconnected":
-                if (Services.telemetry.canSend ||
-                    Services.prefs.getBoolPref("toolkit.telemetry.test")) {
+                if (Services.telemetry.canRecordExtended) {
                   this.stageForTelemetryUpload(window, pc);
                 }
                 break;
             }
           }
         };
 
         let pc_static = new window.mozRTCPeerConnectionStatic();
--- a/browser/components/loop/content/shared/js/activeRoomStore.js
+++ b/browser/components/loop/content/shared/js/activeRoomStore.js
@@ -177,20 +177,20 @@ loop.store.ActiveRoomStore = (function()
 
           // For the conversation window, we need to automatically
           // join the room.
           this.dispatchAction(new sharedActions.JoinRoom());
         }.bind(this));
     },
 
     /**
-     * Execute fetchServerData event action from the dispatcher. Although
-     * this is to fetch the server data - for rooms on the standalone client,
-     * we don't actually need to get any data. Therefore we just save the
-     * data that is given to us for when the user chooses to join the room.
+     * Execute fetchServerData event action from the dispatcher. For rooms
+     * we need to get the room context information from the server. We don't
+     * need other data until the user decides to join the room.
+     * This action is only used for the standalone UI.
      *
      * @param {sharedActions.FetchServerData} actionData
      */
     fetchServerData: function(actionData) {
       if (actionData.windowType !== "room") {
         // Nothing for us to do here, leave it to other stores.
         return;
       }
@@ -201,16 +201,28 @@ loop.store.ActiveRoomStore = (function()
         roomToken: actionData.token,
         roomState: ROOM_STATES.READY
       });
 
       this._mozLoop.rooms.on("update:" + actionData.roomToken,
         this._handleRoomUpdate.bind(this));
       this._mozLoop.rooms.on("delete:" + actionData.roomToken,
         this._handleRoomDelete.bind(this));
+
+      this._mozLoop.rooms.get(this._storeState.roomToken,
+        function(err, result) {
+          if (err) {
+            // XXX Bug 1110937 will want to handle the error results here
+            // e.g. room expired/invalid.
+            console.error("Failed to get room data:", err);
+            return;
+          }
+
+          this.dispatcher.dispatch(new sharedActions.UpdateRoomInfo(result));
+        }.bind(this));
     },
 
     /**
      * Handles the setupRoomInfo action. Sets up the initial room data and
      * sets the state to `READY`.
      *
      * @param {sharedActions.SetupRoomInfo} actionData
      */
@@ -336,31 +348,16 @@ loop.store.ActiveRoomStore = (function()
 
       // Only send media telemetry on one side of the call: the desktop side.
       actionData["sendTwoWayMediaTelemetry"] = this._isDesktop;
 
       this._sdkDriver.connectSession(actionData);
 
       this._mozLoop.addConversationContext(this._storeState.windowId,
                                            actionData.sessionId, "");
-
-      // If we haven't got a room name yet, go and get one. We typically
-      // need to do this in the case of the standalone window.
-      // XXX When bug 1103331 lands this can be moved to earlier.
-      if (!this._storeState.roomName) {
-        this._mozLoop.rooms.get(this._storeState.roomToken,
-          function(err, result) {
-            if (err) {
-              console.error("Failed to get room data:", err);
-              return;
-            }
-
-            this.dispatcher.dispatch(new sharedActions.UpdateRoomInfo(result));
-        }.bind(this));
-      }
     },
 
     /**
      * Handles recording when the sdk has connected to the servers.
      */
     connectedToSdkServers: function() {
       this.setStoreState({
         roomState: ROOM_STATES.SESSION_CONNECTED
--- a/browser/components/loop/standalone/content/js/standaloneAppStore.js
+++ b/browser/components/loop/standalone/content/js/standaloneAppStore.js
@@ -12,18 +12,17 @@ loop.store = loop.store || {};
  * the window data and store the window type.
  */
 loop.store.StandaloneAppStore = (function() {
   "use strict";
 
   var sharedActions = loop.shared.actions;
   var sharedUtils = loop.shared.utils;
 
-  var OLD_STYLE_CALL_REGEXP = /\#call\/(.*)/;
-  var NEW_STYLE_CALL_REGEXP = /\/c\/([\w\-]+)$/;
+  var CALL_REGEXP = /\/c\/([\w\-]+)$/;
   var ROOM_REGEXP = /\/([\w\-]+)$/;
 
   /**
    * Constructor
    *
    * @param {Object} options Options for the store. Should contain the dispatcher.
    */
   var StandaloneAppStore = function(options) {
@@ -75,19 +74,17 @@ loop.store.StandaloneAppStore = (functio
         var match = path.match(regexp);
         if (match && match[1]) {
           return match;
         }
         return null;
       }
 
       if (windowPath) {
-        // Is this a call url (the hash is a backwards-compatible url)?
-        match = extractId(windowPath, OLD_STYLE_CALL_REGEXP) ||
-                extractId(windowPath, NEW_STYLE_CALL_REGEXP);
+        match = extractId(windowPath, CALL_REGEXP);
 
         if (match) {
           windowType = "outgoing";
         } else {
           // Is this a room url?
           match = extractId(windowPath, ROOM_REGEXP);
 
           if (match) {
--- a/browser/components/loop/standalone/content/js/webapp.js
+++ b/browser/components/loop/standalone/content/js/webapp.js
@@ -1104,19 +1104,17 @@ loop.webapp = (function($, _, OT, mozL10
     // Set the 'lang' and 'dir' attributes to <html> when the page is translated
     document.documentElement.lang = mozL10n.language.code;
     document.documentElement.dir = mozL10n.language.direction;
     document.title = mozL10n.get("clientShortname2");
 
     var locationData = sharedUtils.locationData();
 
     dispatcher.dispatch(new sharedActions.ExtractTokenInfo({
-      // We pass the hash or the pathname - the hash was used for the original
-      // urls, the pathname for later ones.
-      windowPath: locationData.hash || locationData.pathname
+      windowPath: locationData.pathname
     }));
   }
 
   return {
     CallUrlExpiredView: CallUrlExpiredView,
     PendingConversationView: PendingConversationView,
     GumPromptConversationView: GumPromptConversationView,
     WaitingConversationView: WaitingConversationView,
--- a/browser/components/loop/standalone/content/js/webapp.jsx
+++ b/browser/components/loop/standalone/content/js/webapp.jsx
@@ -1104,19 +1104,17 @@ loop.webapp = (function($, _, OT, mozL10
     // Set the 'lang' and 'dir' attributes to <html> when the page is translated
     document.documentElement.lang = mozL10n.language.code;
     document.documentElement.dir = mozL10n.language.direction;
     document.title = mozL10n.get("clientShortname2");
 
     var locationData = sharedUtils.locationData();
 
     dispatcher.dispatch(new sharedActions.ExtractTokenInfo({
-      // We pass the hash or the pathname - the hash was used for the original
-      // urls, the pathname for later ones.
-      windowPath: locationData.hash || locationData.pathname
+      windowPath: locationData.pathname
     }));
   }
 
   return {
     CallUrlExpiredView: CallUrlExpiredView,
     PendingConversationView: PendingConversationView,
     GumPromptConversationView: GumPromptConversationView,
     WaitingConversationView: WaitingConversationView,
--- a/browser/components/loop/test/mochitest/browser_mozLoop_telemetry.js
+++ b/browser/components/loop/test/mochitest/browser_mozLoop_telemetry.js
@@ -7,20 +7,20 @@
 Components.utils.import("resource://gre/modules/Promise.jsm", this);
 
 add_task(loadLoopPanel);
 
 /**
  * Enable local telemetry recording for the duration of the tests.
  */
 add_task(function* test_initialize() {
-  let oldCanRecord = Services.telemetry.canRecord;
-  Services.telemetry.canRecord = true;
+  let oldCanRecord = Services.telemetry.canRecordExtended;
+  Services.telemetry.canRecordExtended = true;
   registerCleanupFunction(function () {
-    Services.telemetry.canRecord = oldCanRecord;
+    Services.telemetry.canRecordExtended = oldCanRecord;
   });
 });
 
 /**
  * Tests that enumerated bucket histograms exist and can be updated.
  */
 add_task(function* test_mozLoop_telemetryAdd_buckets() {
   let histogramId = "LOOP_TWO_WAY_MEDIA_CONN_LENGTH";
--- a/browser/components/loop/test/shared/activeRoomStore_test.js
+++ b/browser/components/loop/test/shared/activeRoomStore_test.js
@@ -315,33 +315,58 @@ describe("loop.store.ActiveRoomStore", f
           new sharedActions.RoomFailure({
             error: fakeError,
             failedJoinRequest: false
           }));
       });
   });
 
   describe("#fetchServerData", function() {
-    it("should save the token", function() {
-      store.fetchServerData(new sharedActions.FetchServerData({
+    var fetchServerAction;
+
+    beforeEach(function() {
+      fetchServerAction = new sharedActions.FetchServerData({
         windowType: "room",
         token: "fakeToken"
-      }));
+      });
+    });
+
+    it("should save the token", function() {
+      store.fetchServerData(fetchServerAction);
 
       expect(store.getStoreState().roomToken).eql("fakeToken");
     });
 
     it("should set the state to `READY`", function() {
-      store.fetchServerData(new sharedActions.FetchServerData({
-        windowType: "room",
-        token: "fakeToken"
-      }));
+      store.fetchServerData(fetchServerAction);
 
       expect(store.getStoreState().roomState).eql(ROOM_STATES.READY);
     });
+
+    it("should call mozLoop.rooms.get to get the room data", function() {
+      store.fetchServerData(fetchServerAction);
+
+      sinon.assert.calledOnce(fakeMozLoop.rooms.get);
+    });
+
+    it("should dispatch UpdateRoomInfo if mozLoop.rooms.get is successful", function() {
+      var roomDetails = {
+        roomName: "fakeName",
+        roomUrl: "http://invalid",
+        roomOwner: "gavin"
+      };
+
+      fakeMozLoop.rooms.get.callsArgWith(1, null, roomDetails);
+
+      store.fetchServerData(fetchServerAction);
+
+      sinon.assert.calledOnce(dispatcher.dispatch);
+      sinon.assert.calledWithExactly(dispatcher.dispatch,
+        new sharedActions.UpdateRoomInfo(roomDetails));
+    });
   });
 
   describe("#feedbackComplete", function() {
     it("should reset the room store state", function() {
       var initialState = store.getInitialStoreState();
       store.setStoreState({
         roomState: ROOM_STATES.ENDED,
         audioMuted: true,
@@ -573,44 +598,16 @@ describe("loop.store.ActiveRoomStore", f
 
       store.joinedRoom(actionData);
 
       sinon.assert.calledOnce(fakeMozLoop.addConversationContext);
       sinon.assert.calledWithExactly(fakeMozLoop.addConversationContext,
                                      "42", "15263748", "");
     });
 
-    it("should call mozLoop.rooms.get to get the room data if the roomName" +
-      "is not known", function() {
-        store.setStoreState({roomName: undefined});
-
-        store.joinedRoom(new sharedActions.JoinedRoom(fakeJoinedData));
-
-        sinon.assert.calledOnce(fakeMozLoop.rooms.get);
-      });
-
-    it("should dispatch UpdateRoomInfo if mozLoop.rooms.get is successful",
-      function() {
-        var roomDetails = {
-          roomName: "fakeName",
-          roomUrl: "http://invalid",
-          roomOwner: "gavin"
-        };
-
-        fakeMozLoop.rooms.get.callsArgWith(1, null, roomDetails);
-
-        store.setStoreState({roomName: undefined});
-
-        store.joinedRoom(new sharedActions.JoinedRoom(fakeJoinedData));
-
-        sinon.assert.calledOnce(dispatcher.dispatch);
-        sinon.assert.calledWithExactly(dispatcher.dispatch,
-          new sharedActions.UpdateRoomInfo(roomDetails));
-      });
-
     it("should call mozLoop.rooms.refreshMembership before the expiresTime",
       function() {
         store.joinedRoom(new sharedActions.JoinedRoom(fakeJoinedData));
 
         sandbox.clock.tick(fakeJoinedData.expires * 1000);
 
         sinon.assert.calledOnce(fakeMozLoop.rooms.refreshMembership);
         sinon.assert.calledWith(fakeMozLoop.rooms.refreshMembership,
--- a/browser/components/loop/test/standalone/standaloneAppStore_test.js
+++ b/browser/components/loop/test/standalone/standaloneAppStore_test.js
@@ -119,26 +119,17 @@ describe("loop.store.StandaloneAppStore"
         fakeSdk.checkSystemRequirements.returns(false);
 
         store.extractTokenInfo(
           new sharedActions.ExtractTokenInfo(fakeGetWindowData));
 
         expect(store.getStoreState().windowType).eql("unsupportedBrowser");
       });
 
-    it("should set windowType to `outgoing` for old style call hashes", function() {
-      fakeGetWindowData.windowPath = "#call/faketoken";
-
-      store.extractTokenInfo(
-        new sharedActions.ExtractTokenInfo(fakeGetWindowData));
-
-      expect(store.getStoreState().windowType).eql("outgoing");
-    });
-
-    it("should set windowType to `outgoing` for new style call paths", function() {
+    it("should set windowType to `outgoing` for call paths", function() {
       fakeGetWindowData.windowPath = "/c/fakecalltoken";
 
       store.extractTokenInfo(
         new sharedActions.ExtractTokenInfo(fakeGetWindowData));
 
       expect(store.getStoreState().windowType).eql("outgoing");
     });
 
@@ -155,30 +146,17 @@ describe("loop.store.StandaloneAppStore"
       fakeGetWindowData.windowPath = "/";
 
       store.extractTokenInfo(
         new sharedActions.ExtractTokenInfo(fakeGetWindowData));
 
       expect(store.getStoreState().windowType).eql("home");
     });
 
-    it("should set the loopToken on the conversation for old style call hashes",
-      function() {
-        fakeGetWindowData.windowPath = "#call/faketoken";
-
-        store.extractTokenInfo(
-          new sharedActions.ExtractTokenInfo(fakeGetWindowData));
-
-        sinon.assert.calledOnce(fakeConversation.set);
-        sinon.assert.calledWithExactly(fakeConversation.set, {
-          loopToken: "faketoken"
-        });
-      });
-
-    it("should set the loopToken on the conversation for new style call paths",
+    it("should set the loopToken on the conversation for call paths",
       function() {
         fakeGetWindowData.windowPath = "/c/fakecalltoken";
 
         store.extractTokenInfo(
           new sharedActions.ExtractTokenInfo(fakeGetWindowData));
 
         sinon.assert.calledOnce(fakeConversation.set);
         sinon.assert.calledWithExactly(fakeConversation.set, {
@@ -194,32 +172,17 @@ describe("loop.store.StandaloneAppStore"
           new sharedActions.ExtractTokenInfo(fakeGetWindowData));
 
         sinon.assert.calledOnce(fakeConversation.set);
         sinon.assert.calledWithExactly(fakeConversation.set, {
           loopToken: "fakeroomtoken"
         });
       });
 
-    it("should dispatch a SetupWindowData action for old style call hashes",
-      function() {
-        fakeGetWindowData.windowPath = "#call/faketoken";
-
-        store.extractTokenInfo(
-          new sharedActions.ExtractTokenInfo(fakeGetWindowData));
-
-        sinon.assert.calledOnce(dispatcher.dispatch);
-        sinon.assert.calledWithExactly(dispatcher.dispatch,
-          new sharedActions.FetchServerData({
-            windowType: "outgoing",
-            token: "faketoken"
-          }));
-      });
-
-    it("should set the loopToken on the conversation for new style call paths",
+    it("should set the loopToken on the conversation for call paths",
       function() {
         fakeGetWindowData.windowPath = "/c/fakecalltoken";
 
         store.extractTokenInfo(
           new sharedActions.ExtractTokenInfo(fakeGetWindowData));
 
         sinon.assert.calledOnce(dispatcher.dispatch);
         sinon.assert.calledWithExactly(dispatcher.dispatch,
--- a/browser/components/loop/test/standalone/webapp_test.js
+++ b/browser/components/loop/test/standalone/webapp_test.js
@@ -66,32 +66,17 @@ describe("loop.webapp", function() {
       sinon.assert.calledOnce(React.render);
       sinon.assert.calledWith(React.render,
         sinon.match(function(value) {
           return TestUtils.isCompositeComponentElement(value,
             loop.webapp.WebappRootView);
       }));
     });
 
-    it("should dispatch a ExtractTokenInfo action with the hash", function() {
-      sandbox.stub(loop.shared.utils, "locationData").returns({
-        hash: "#call/faketoken",
-        pathname: "invalid"
-      });
-
-      loop.webapp.init();
-
-      sinon.assert.calledOnce(loop.Dispatcher.prototype.dispatch);
-      sinon.assert.calledWithExactly(loop.Dispatcher.prototype.dispatch,
-        new sharedActions.ExtractTokenInfo({
-          windowPath: "#call/faketoken"
-        }));
-    });
-
-    it("should dispatch a ExtractTokenInfo action with the path if there is no hash",
+    it("should dispatch a ExtractTokenInfo action with the path",
       function() {
         sandbox.stub(loop.shared.utils, "locationData").returns({
           hash: "",
           pathname: "/c/faketoken"
         });
 
       loop.webapp.init();
 
--- a/browser/components/nsBrowserGlue.js
+++ b/browser/components/nsBrowserGlue.js
@@ -146,16 +146,19 @@ XPCOMUtils.defineLazyModuleGetter(this, 
                                   "resource://gre/modules/WebChannel.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "ReaderParent",
                                   "resource:///modules/ReaderParent.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "AddonWatcher",
                                   "resource://gre/modules/AddonWatcher.jsm");
 
+XPCOMUtils.defineLazyModuleGetter(this, "LightweightThemeManager",
+                                  "resource://gre/modules/LightweightThemeManager.jsm");
+
 const PREF_PLUGINS_NOTIFYUSER = "plugins.update.notifyUser";
 const PREF_PLUGINS_UPDATEURL  = "plugins.update.url";
 
 // Seconds of idle before trying to create a bookmarks backup.
 const BOOKMARKS_BACKUP_IDLE_TIME_SEC = 8 * 60;
 // Minimum interval between backups.  We try to not create more than one backup
 // per interval.
 const BOOKMARKS_BACKUP_MIN_INTERVAL_DAYS = 1;
@@ -710,16 +713,31 @@ BrowserGlue.prototype = {
     Services.prefs.addObserver(POLARIS_ENABLED, this, false);
 #endif
 
 #ifdef MOZ_DEBUG_UA
     UserAgentOverrides.init();
     DebugUserAgent.init();
 #endif
 
+#ifndef RELEASE_BUILD
+    let browserBundle = Services.strings.createBundle("chrome://browser/locale/browser.properties");
+    let brandBundle = Services.strings.createBundle("chrome://branding/locale/brand.properties");
+    let themeName = browserBundle.GetStringFromName("deveditionTheme.name");
+    let vendorShortName = brandBundle.GetStringFromName("vendorShortName");
+
+    LightweightThemeManager.addBuiltInTheme({
+      id: "firefox-devedition@mozilla.org",
+      name: themeName,
+      headerURL: "resource:///chrome/browser/content/browser/defaultthemes/devedition.header.png",
+      iconURL: "resource:///chrome/browser/content/browser/defaultthemes/devedition.icon.png",
+      author: vendorShortName,
+    });
+#endif
+
     Services.obs.notifyObservers(null, "browser-ui-startup-complete", "");
 
     AddonWatcher.init(this._notifySlowAddon);
   },
 
   _checkForOldBuildUpdates: function () {
     // check for update if our build is old
     if (Services.prefs.getBoolPref("app.update.enabled") &&
@@ -1638,17 +1656,17 @@ BrowserGlue.prototype = {
     var notifyBox = win.gBrowser.getNotificationBox();
     var notification = notifyBox.appendNotification(text, title, null,
                                                     notifyBox.PRIORITY_CRITICAL_MEDIUM,
                                                     buttons);
     notification.persistence = -1; // Until user closes it
   },
 
   _migrateUI: function BG__migrateUI() {
-    const UI_VERSION = 27;
+    const UI_VERSION = 28;
     const BROWSER_DOCURL = "chrome://browser/content/browser.xul";
     let currentUIVersion = 0;
     try {
       currentUIVersion = Services.prefs.getIntPref("browser.migration.version");
     } catch(ex) {}
     if (currentUIVersion >= UI_VERSION)
       return;
 
@@ -1957,16 +1975,52 @@ BrowserGlue.prototype = {
       // Fix up document color use:
       const kOldColorPref = "browser.display.use_document_colors";
       if (Services.prefs.prefHasUserValue(kOldColorPref) &&
           !Services.prefs.getBoolPref(kOldColorPref)) {
         Services.prefs.setIntPref("browser.display.document_color_use", 2);
       }
     }
 
+    if (currentUIVersion < 28) {
+      // Convert old devedition theme pref to lightweight theme storage
+      let lightweightThemeSelected = false;
+      let selectedThemeID = null;
+      try {
+        lightweightThemeSelected = Services.prefs.prefHasUserValue("lightweightThemes.selectedThemeID");
+        selectedThemeID = Services.prefs.getCharPref("lightweightThemes.selectedThemeID");
+      } catch(e) {}
+
+      let defaultThemeSelected = false;
+      try {
+         defaultThemeSelected = Services.prefs.getCharPref("general.skins.selectedSkin") == "classic/1.0";
+      } catch(e) {}
+
+      let deveditionThemeEnabled = false;
+      try {
+         deveditionThemeEnabled = Services.prefs.getBoolPref("browser.devedition.theme.enabled");
+      } catch(e) {}
+
+      // If we are on the devedition channel, the devedition theme is on by
+      // default.  But we need to handle the case where they didn't want it
+      // applied, and unapply the theme.
+      let userChoseToNotUseDeveditionTheme =
+        !deveditionThemeEnabled ||
+        !defaultThemeSelected ||
+        (lightweightThemeSelected && selectedThemeID != "firefox-devedition@mozilla.org");
+
+      if (userChoseToNotUseDeveditionTheme && selectedThemeID == "firefox-devedition@mozilla.org") {
+        Services.prefs.setCharPref("lightweightThemes.selectedThemeID", "");
+      }
+
+      // Not clearing browser.devedition.theme.enabled, to preserve user's pref
+      // if for some reason this function runs again (even though it shouldn't)
+      Services.prefs.clearUserPref("browser.devedition.showCustomizeButton");
+    }
+
     // Update the migration version.
     Services.prefs.setIntPref("browser.migration.version", UI_VERSION);
   },
 
   // ------------------------------
   // public nsIBrowserGlue members
   // ------------------------------
 
--- a/browser/devtools/framework/test/browser.ini
+++ b/browser/devtools/framework/test/browser.ini
@@ -18,17 +18,16 @@ support-files =
 [browser_target_remote.js]
 [browser_target_support.js]
 [browser_two_tabs.js]
 [browser_toolbox_dynamic_registration.js]
 [browser_toolbox_getpanelwhenready.js]
 [browser_toolbox_highlight.js]
 [browser_toolbox_hosts.js]
 [browser_toolbox_options.js]
-[browser_toolbox_options_devedition.js]
 [browser_toolbox_options_disable_buttons.js]
 [browser_toolbox_options_disable_cache-01.js]
 skip-if = e10s # Bug 1030318
 [browser_toolbox_options_disable_cache-02.js]
 skip-if = e10s # Bug 1030318
 [browser_toolbox_options_disable_js.js]
 skip-if = e10s # Bug 1030318
 # [browser_toolbox_raise.js] # Bug 962258
deleted file mode 100644
--- a/browser/devtools/framework/test/browser_toolbox_options_devedition.js
+++ /dev/null
@@ -1,59 +0,0 @@
-/* Any copyright is dedicated to the Public Domain.
- * http://creativecommons.org/publicdomain/zero/1.0/ */
-
-// Tests that changing preferences in the options panel updates the prefs
-// and toggles appropriate things in the toolbox.
-
-let doc = null, toolbox = null, panelWin = null;
-
-const PREF_ENABLED = "browser.devedition.theme.enabled";
-const PREF_SHOW = "browser.devedition.theme.showCustomizeButton";
-
-const URL = "data:text/html;charset=utf8,test for toggling dev edition browser theme toggling";
-
-add_task(function*() {
-  // Set preference to false by default so this could
-  // run in Developer Edition which has it on by default.
-  Services.prefs.setBoolPref(PREF_ENABLED, false);
-  Services.prefs.setBoolPref(PREF_SHOW, true);
-
-  let tab = yield addTab(URL);
-  let target = TargetFactory.forTab(tab);
-  toolbox = yield gDevTools.showToolbox(target);
-  let tool = yield toolbox.selectTool("options");
-  panelWin = tool.panelWin;
-
-  let checkbox = tool.panelDoc.getElementById("devtools-browser-theme");
-
-  ise(Services.prefs.getBoolPref(PREF_ENABLED), false, "Dev Theme pref off on start");
-
-  let themeStatus = yield clickAndWaitForThemeChange(checkbox, panelWin);
-  ise(themeStatus, true, "Theme has been toggled on.");
-
-  themeStatus = yield clickAndWaitForThemeChange(checkbox, panelWin);
-  ise(themeStatus, false, "Theme has been toggled off.");
-
-  yield cleanup();
-});
-
-function clickAndWaitForThemeChange (el, win) {
-  let deferred = promise.defer();
-  gDevTools.on("pref-changed", function handler (event, {pref}) {
-    if (pref === PREF_ENABLED) {
-      gDevTools.off("pref-changed", handler);
-      deferred.resolve(Services.prefs.getBoolPref(PREF_ENABLED));
-    }
-  });
-
-  EventUtils.synthesizeMouseAtCenter(el, {}, win);
-
-  return deferred.promise;
-}
-
-function* cleanup() {
-  yield toolbox.destroy();
-  gBrowser.removeCurrentTab();
-  Services.prefs.clearUserPref(PREF_ENABLED);
-  Services.prefs.clearUserPref(PREF_SHOW);
-  toolbox = doc = panelWin = null;
-}
--- a/browser/devtools/framework/toolbox-options.js
+++ b/browser/devtools/framework/toolbox-options.js
@@ -4,19 +4,16 @@
 
 "use strict";
 
 const {Cu, Cc, Ci} = require("chrome");
 const Services = require("Services");
 const promise = require("promise");
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "gDevTools", "resource:///modules/devtools/gDevTools.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "CustomizeMode", "resource:///modules/CustomizeMode.jsm");
-const kDeveditionChangedNotification = "devedition-theme-state-changed";
-const DEVEDITION_THEME_PREF = "browser.devedition.theme.enabled";
 
 exports.OptionsPanel = OptionsPanel;
 
 XPCOMUtils.defineLazyGetter(this, "l10n", function() {
   let bundle = Services.strings.createBundle("chrome://browser/locale/devtools/toolbox.properties");
   let l10n = function(aName, ...aArgs) {
     try {
       if (aArgs.length == 0) {
@@ -82,17 +79,16 @@ function OptionsPanel(iframeWindow, tool
   this._themeRegistered = this._themeRegistered.bind(this);
   this._themeUnregistered = this._themeUnregistered.bind(this);
   this._disableJSClicked = this._disableJSClicked.bind(this);
 
   this.disableJSNode = this.panelDoc.getElementById("devtools-disable-javascript");
 
   this._addListeners();
 
-  Services.obs.addObserver(this, kDeveditionChangedNotification, false);
   const EventEmitter = require("devtools/toolkit/event-emitter");
   EventEmitter.decorate(this);
 }
 
 OptionsPanel.prototype = {
 
   get target() {
     return this.toolbox.target;
@@ -107,17 +103,16 @@ OptionsPanel.prototype = {
     } else {
       targetPromise = promise.resolve(this.target);
     }
 
     return targetPromise.then(() => {
       this.setupToolsList();
       this.setupToolbarButtonsList();
       this.setupThemeList();
-      this.setupBrowserThemeButton();
       this.populatePreferences();
       this.updateDefaultTheme();
     }).then(() => {
       this.isReady = true;
       this.emit("ready");
       return this;
     }).then(null, function onError(aReason) {
       Cu.reportError("OptionsPanel open failed. " +
@@ -141,18 +136,16 @@ OptionsPanel.prototype = {
     if (data.pref === "devtools.cache.disabled") {
       let cacheDisabled = data.newValue;
       let cbx = this.panelDoc.getElementById("devtools-disable-cache");
 
       cbx.checked = cacheDisabled;
     }
     else if (data.pref === "devtools.theme") {
       this.updateCurrentTheme();
-    } else if (data.pref === "browser.devedition.theme.enabled") {
-      this.updateBrowserTheme();
     }
   },
 
   _themeRegistered: function(event, themeId) {
     this.setupThemeList();
   },
 
   _themeUnregistered: function(event, theme) {
@@ -278,62 +271,16 @@ OptionsPanel.prototype = {
     let themes = gDevTools.getThemeDefinitionArray();
     for (let theme of themes) {
       themeBox.appendChild(createThemeOption(theme));
     }
 
     this.updateCurrentTheme();
   },
 
-  /**
-   * Similar to `populatePrefs`, except we want more
-   * special rules for the browser theme button.
-   */
-  setupBrowserThemeButton: function() {
-    let checkbox = this.panelDoc.getElementById("devtools-browser-theme");
-
-    checkbox.addEventListener("command", function() {
-      setPrefAndEmit(DEVEDITION_THEME_PREF, this.checked);
-    }.bind(checkbox));
-
-    this.updateBrowserThemeButton();
-  },
-
-  /**
-   * Called on theme changed via observer of "devedition-theme-state-changed".
-   */
-  updateBrowserThemeButton: function() {
-    let checkbox = this.panelDoc.getElementById("devtools-browser-theme");
-
-    // Check if the dev edition style sheet is applied -- will not
-    // be applied when dev edition theme is disabled, or when there's
-    // a LWT applied.
-    if (this._isDevEditionThemeOn()) {
-      checkbox.setAttribute("checked", "true");
-    } else {
-      checkbox.removeAttribute("checked");
-    }
-
-    // Should the button be shown
-    if (GetPref("browser.devedition.theme.showCustomizeButton")) {
-      checkbox.removeAttribute("hidden");
-    } else {
-      checkbox.setAttribute("hidden", "true");
-    }
-  },
-
-  /**
-   * Called when clicking the browser theme button to enable/disable
-   * the dev edition browser theme.
-   */
-  updateBrowserTheme: function() {
-    let enabled = GetPref("browser.devedition.theme.enabled");
-    CustomizeMode.prototype.toggleDevEditionTheme.call(this, enabled);
-  },
-
   populatePreferences: function() {
     let prefCheckboxes = this.panelDoc.querySelectorAll("checkbox[data-pref]");
     for (let checkbox of prefCheckboxes) {
       checkbox.checked = GetPref(checkbox.getAttribute("data-pref"));
       checkbox.addEventListener("command", function() {
         setPrefAndEmit(this.getAttribute("data-pref"), this.checked);
       }.bind(checkbox));
     }
@@ -412,35 +359,16 @@ OptionsPanel.prototype = {
 
     let options = {
       "javascriptEnabled": !checked
     };
 
     this.target.activeTab.reconfigure(options);
   },
 
-  /**
-   * Returns a boolean indicating whether or not the dev edition
-   * browser theme is applied.
-   */
-  _isDevEditionThemeOn: function() {
-    let win = Services.wm.getMostRecentWindow("navigator:browser");
-    return !!(win && win.DevEdition.styleSheet);
-  },
-
-  /**
-   * Called on observer notification for "devedition-theme-state-changed"
-   * to possibly change the state of the dev edition button
-   */
-  observe: function(aSubject, aTopic, aData) {
-    if (aTopic === kDeveditionChangedNotification) {
-      this.updateBrowserThemeButton();
-    }
-  },
-
   destroy: function() {
     if (this.destroyPromise) {
       return this.destroyPromise;
     }
 
     let deferred = promise.defer();
 
     this.destroyPromise = deferred.promise;
@@ -455,18 +383,16 @@ OptionsPanel.prototype = {
       this.target.activeTab.reconfigure(options, () => {
         this.toolbox = null;
         deferred.resolve();
       }, true);
     }
 
     this.panelWin = this.panelDoc = this.disableJSNode = null;
 
-    Services.obs.removeObserver(this, kDeveditionChangedNotification);
-
     return deferred.promise;
   }
 };
 
 /* Set a pref and emit the pref-changed event if needed. */
 function setPrefAndEmit(prefName, newValue) {
   let data = {
     pref: prefName,
--- a/browser/devtools/framework/toolbox-options.xul
+++ b/browser/devtools/framework/toolbox-options.xul
@@ -30,19 +30,16 @@
       <vbox class="options-vertical-pane" flex="1">
         <label>&options.selectDevToolsTheme.label2;</label>
         <vbox id="theme-options" class="options-groupbox">
           <radiogroup id="devtools-theme-box"
                       class="options-groupbox"
                       data-pref="devtools.theme"
                       orient="horizontal">
           </radiogroup>
-          <checkbox id="devtools-browser-theme"
-                    label="&options.usedeveditiontheme.label;"
-                    tooltiptext="&options.usedeveditiontheme.tooltip;"/>
         </vbox>
         <label>&options.commonPrefs.label;</label>
         <vbox id="commonprefs-options" class="options-groupbox">
           <checkbox label="&options.enablePersistentLogs.label;"
                     tooltiptext="&options.enablePersistentLogs.tooltip;"
                     data-pref="devtools.webconsole.persistlog"/>
         </vbox>
         <label>&options.context.inspector;</label>
--- a/browser/devtools/framework/toolbox-process-window.js
+++ b/browser/devtools/framework/toolbox-process-window.js
@@ -47,17 +47,16 @@ let connect = Task.async(function*() {
     }
   });
 });
 
 // Certain options should be toggled since we can assume chrome debugging here
 function setPrefDefaults() {
   Services.prefs.setBoolPref("devtools.inspector.showUserAgentStyles", true);
   Services.prefs.setBoolPref("devtools.performance.ui.show-platform-data", true);
-  Services.prefs.setBoolPref("browser.devedition.theme.showCustomizeButton", false);
   Services.prefs.setBoolPref("devtools.inspector.showAllAnonymousContent", true);
   Services.prefs.setBoolPref("browser.dom.window.dump.enabled", true);
   Services.prefs.setBoolPref("devtools.command-button-frames.enabled", true);
 }
 
 window.addEventListener("load", function() {
   let cmdClose = document.getElementById("toolbox-cmd-close");
   cmdClose.addEventListener("command", onCloseCommand);
--- a/browser/devtools/markupview/test/browser_markupview_tag_edit_02.js
+++ b/browser/devtools/markupview/test/browser_markupview_tag_edit_02.js
@@ -11,35 +11,35 @@ const TEST_URL = "data:text/html,<div id
 add_task(function*() {
   info("Opening the inspector on the test page");
   let {toolbox, inspector} = yield addTab(TEST_URL).then(openInspector);
 
   info("Selecting the test node");
   yield selectNode("#test-div", inspector);
 
   info("Verify attributes, only ID should be there for now");
-  assertAttributes("#test-div", {
+  yield assertAttributes("#test-div", {
     id: "test-div"
   });
 
   info("Focus the ID attribute and change its content");
   let {editor} = yield getContainerForSelector("#test-div", inspector);
   let attr = editor.attrs["id"].querySelector(".editable");
   let mutated = inspector.once("markupmutation");
   setEditableFieldValue(attr,
     attr.textContent + ' class="newclass" style="color:green"', inspector);
   yield mutated;
 
   info("Verify attributes, should have ID, class and style");
-  assertAttributes("#test-div", {
+  yield assertAttributes("#test-div", {
     id: "test-div",
     class: "newclass",
     style: "color:green"
   });
 
   info("Trying to undo the change");
   yield undoChange(inspector);
-  assertAttributes("#test-div", {
+  yield assertAttributes("#test-div", {
     id: "test-div"
   });
 
   yield inspector.once("inspector-updated");
 });
--- a/browser/devtools/markupview/test/browser_markupview_tag_edit_06.js
+++ b/browser/devtools/markupview/test/browser_markupview_tag_edit_06.js
@@ -61,18 +61,18 @@ let TEST_DATA = [{
   text: "style=\"font-family: 'Lucida Grande', sans-serif; font-size: 75%;\"",
   expectedAttributes: {
     style: "font-family: 'Lucida Grande', sans-serif; font-size: 75%;"
   }
 }, {
   desc: "Object attribute names",
   text: "toString=\"true\" hasOwnProperty=\"false\"",
   expectedAttributes: {
-    toString: "true",
-    hasOwnProperty: "false"
+    tostring: "true",
+    hasownproperty: "false"
   }
 }, {
   desc: "Add event handlers",
   text: "onclick=\"javascript: throw new Error('wont fire');\" onload=\"alert('here');\"",
   expectedAttributes: {
     onclick: "javascript: throw new Error('wont fire');",
     onload: "alert('here');"
   }
--- a/browser/devtools/markupview/test/browser_markupview_tag_edit_08.js
+++ b/browser/devtools/markupview/test/browser_markupview_tag_edit_08.js
@@ -20,22 +20,21 @@ add_task(function*() {
   yield testEditingAttributeWithMixedQuotes(inspector);
 });
 
 function* testCollapsedLongAttribute(inspector) {
   info("Try to modify the collapsed long attribute, making sure it expands.");
 
   info("Adding test attributes to the node");
   let onMutated = inspector.once("markupmutation");
-  let node = getNode("#node24");
-  node.setAttribute("class", "");
-  node.setAttribute("data-long", LONG_ATTRIBUTE);
+  yield setNodeAttribute("#node24", "class", "");
+  yield setNodeAttribute("#node24", "data-long", LONG_ATTRIBUTE);
   yield onMutated;
 
-  assertAttributes("#node24", {
+  yield assertAttributes("#node24", {
     id: "node24",
     "class": "",
     "data-long": LONG_ATTRIBUTE
   });
 
   let {editor} = yield getContainerForSelector("#node24", inspector);
   let attr = editor.attrs["data-long"].querySelector(".editable");
 
@@ -47,28 +46,28 @@ function* testCollapsedLongAttribute(ins
   EventUtils.sendKey("escape", inspector.panelWin);
 
   setEditableFieldValue(attr, input.value + ' data-short="ABC"', inspector);
   yield inspector.once("markupmutation");
 
   let visibleAttrText = editor.attrs["data-long"].querySelector(".attr-value").textContent;
   is (visibleAttrText, LONG_ATTRIBUTE_COLLAPSED)
 
-  assertAttributes("#node24", {
+  yield assertAttributes("#node24", {
     id: "node24",
     class: "",
     'data-long': LONG_ATTRIBUTE,
     "data-short": "ABC"
   });
 }
 
 function* testModifyInlineStyleWithQuotes(inspector) {
   info("Modify inline style containing \"");
 
-  assertAttributes("#node26", {
+  yield assertAttributes("#node26", {
     id: "node26",
     style: 'background-image: url("moz-page-thumb://thumbnail?url=http%3A%2F%2Fwww.mozilla.org%2F");'
   });
 
   let onMutated = inspector.once("markupmutation");
   let {editor} = yield getContainerForSelector("#node26", inspector);
   let attr = editor.attrs["style"].querySelector(".editable");
 
@@ -85,26 +84,26 @@ function* testModifyInlineStyleWithQuote
 
   value = value.replace(/mozilla\.org/, "mozilla.com");
   input.value = value;
 
   EventUtils.sendKey("return", inspector.panelWin);
 
   yield onMutated;
 
-  assertAttributes("#node26", {
+  yield assertAttributes("#node26", {
     id: "node26",
     style: 'background-image: url("moz-page-thumb://thumbnail?url=http%3A%2F%2Fwww.mozilla.com%2F");'
   });
 }
 
 function* testEditingAttributeWithMixedQuotes(inspector) {
   info("Modify class containing \" and \'");
 
-  assertAttributes("#node27", {
+  yield assertAttributes("#node27", {
     "id": "node27",
     "class": 'Double " and single \''
   });
 
   let onMutated = inspector.once("markupmutation");
   let {editor} = yield getContainerForSelector("#node27", inspector);
   let attr = editor.attrs["class"].querySelector(".editable");
 
@@ -118,13 +117,13 @@ function* testEditingAttributeWithMixedQ
 
   value = value.replace(/Double/, "&quot;").replace(/single/, "'");
   input.value = value;
 
   EventUtils.sendKey("return", inspector.panelWin);
 
   yield onMutated;
 
-  assertAttributes("#node27", {
+  yield assertAttributes("#node27", {
     id: "node27",
     class: '" " and \' \''
   });
 }
--- a/browser/devtools/markupview/test/browser_markupview_tag_edit_09.js
+++ b/browser/devtools/markupview/test/browser_markupview_tag_edit_09.js
@@ -32,17 +32,17 @@ function* testWellformedMixedCase(inspec
   EventUtils.sendKey("return", inspector.panelWin);
 
   info("Editing the attribute value and waiting for the mutation event");
   let input = inplaceEditor(attr).input;
   input.value = "viewBox=\"0 0 1 1\"";
   EventUtils.sendKey("return", inspector.panelWin);
   yield onMutated;
 
-  assertAttributes("svg", {
+  yield assertAttributes("svg", {
     "viewBox": "0 0 1 1",
     "width": "200",
     "height": "200"
   });
 }
 
 function* testMalformedMixedCase(inspector) {
   info("Modifying a malformed, mixed-case attribute, " +
@@ -58,14 +58,14 @@ function* testMalformedMixedCase(inspect
   EventUtils.sendKey("return", inspector.panelWin);
 
   info("Editing the attribute value and waiting for the mutation event");
   let input = inplaceEditor(attr).input;
   input.value = "viewBox=\"<>\"";
   EventUtils.sendKey("return", inspector.panelWin);
   yield onMutated;
 
-  assertAttributes("svg", {
+  yield assertAttributes("svg", {
     "viewBox": "<>",
     "width": "200",
     "height": "200"
   });
 }
--- a/browser/devtools/markupview/test/head.js
+++ b/browser/devtools/markupview/test/head.js
@@ -205,16 +205,28 @@ function getNodeFront(selector, {walker}
  * @param {String} selector.
  * @return {Promise} a promise that resolves to the element's information.
  */
 function getNodeInfo(selector) {
   return executeInContent("devtools:test:getDomElementInfo", {selector});
 }
 
 /**
+ * Set the value of an attribute of a DOM element, identified by its selector.
+ * @param {String} selector.
+ * @param {String} attributeName.
+ * @param {String} attributeValue.
+ * @param {Promise} resolves when done.
+ */
+function setNodeAttribute(selector, attributeName, attributeValue) {
+  return executeInContent("devtools:test:setAttribute",
+                          {selector, attributeName, attributeValue});
+}
+
+/**
  * Highlight a node and set the inspector's current selection to the node or
  * the first match of the given css selector.
  * @param {String|DOMNode} nodeOrSelector
  * @param {InspectorPanel} inspector
  *        The instance of InspectorPanel currently loaded in the toolbox
  * @return a promise that resolves when the inspector is updated with the new
  * node
  */
@@ -357,35 +369,38 @@ let addNewAttributes = Task.async(functi
 
   info("Listening for the markupmutation event");
   let nodeMutated = inspector.once("markupmutation");
   setEditableFieldValue(container.editor.newAttr, text, inspector);
   yield nodeMutated;
 });
 
 /**
- * Checks that a node has the given attributes
+ * Checks that a node has the given attributes.
  *
- * @param {String} selector The node or node selector to check.
- * @param {Object} attrs An object containing the attributes to check.
+ * @param {String} selector The selector for the node to check.
+ * @param {Object} expected An object containing the attributes to check.
  *        e.g. {id: "id1", class: "someclass"}
  *
  * Note that node.getAttribute() returns attribute values provided by the HTML
  * parser. The parser only provides unescaped entities so &amp; will return &.
  */
-function assertAttributes(selector, attrs) {
-  let node = getNode(selector);
+let assertAttributes = Task.async(function*(selector, expected) {
+  let {attributes: actual} = yield getNodeInfo(selector);
 
-  is(node.attributes.length, Object.keys(attrs).length,
-    "Node has the correct number of attributes.");
-  for (let attr in attrs) {
-    is(node.getAttribute(attr), attrs[attr],
-      "Node has the correct " + attr + " attribute.");
+  is(actual.length, Object.keys(expected).length,
+    "The node " + selector + " has the expected number of attributes.");
+  for (let attr in expected) {
+    let foundAttr = actual.find(({name, value}) => name === attr);
+    let foundValue = foundAttr ? foundAttr.value : undefined;
+    ok(foundAttr, "The node " + selector + " has the attribute " + attr);
+    is(foundValue, expected[attr],
+      "The node " + selector + " has the correct " + attr + " attribute value");
   }
-}
+});
 
 /**
  * Undo the last markup-view action and wait for the corresponding mutation to
  * occur
  * @param {InspectorPanel} inspector The instance of InspectorPanel currently
  * loaded in the toolbox
  * @return a promise that resolves when the markup-mutation has been treated or
  * rejects if no undo action is possible
--- a/browser/devtools/markupview/test/helper_attributes_test_runner.js
+++ b/browser/devtools/markupview/test/helper_attributes_test_runner.js
@@ -53,28 +53,28 @@ function runAddAttributesTests(tests, no
  */
 function* runAddAttributesTest(test, selector, inspector) {
   let element = getNode(selector);
 
   info("Starting add-attribute test: " + test.desc);
   yield addNewAttributes(selector, test.text, inspector);
 
   info("Assert that the attribute(s) has/have been applied correctly");
-  assertAttributes(element, test.expectedAttributes);
+  yield assertAttributes(selector, test.expectedAttributes);
 
   if (test.validate) {
     let container = yield getContainerForSelector(selector, inspector);
     test.validate(element, container, inspector);
   }
 
   info("Undo the change");
   yield undoChange(inspector);
 
   info("Assert that the attribute(s) has/have been removed correctly");
-  assertAttributes(element, {});
+  yield assertAttributes(selector, {});
 }
 
 /**
  * Run a series of edit-attributes tests.
  * This function will iterate over the provided tests array and run each test.
  * Each test's goal is to locate a given element on the current test page, assert
  * its current attributes, then provide the name of one of them and a value to
  * be set into it, and then check if the new attributes are correct.
@@ -118,33 +118,33 @@ function runEditAttributesTests(tests, i
  */
 function* runEditAttributesTest(test, inspector) {
   info("Starting edit-attribute test: " + test.desc);
 
   info("Selecting the test node " + test.node);
   yield selectNode(test.node, inspector);
 
   info("Asserting that the node has the right attributes to start with");
-  assertAttributes(test.node, test.originalAttributes);
+  yield assertAttributes(test.node, test.originalAttributes);
 
   info("Editing attribute " + test.name + " with value " + test.value);
 
   let container = yield getContainerForSelector(test.node, inspector);
   ok(container && container.editor, "The markup-container for " + test.node +
     " was found");
 
   info("Listening for the markupmutation event");
   let nodeMutated = inspector.once("markupmutation");
   let attr = container.editor.attrs[test.name].querySelector(".editable");
   setEditableFieldValue(attr, test.value, inspector);
   yield nodeMutated;
 
   info("Asserting the new attributes after edition");
-  assertAttributes(test.node, test.expectedAttributes);
+  yield assertAttributes(test.node, test.expectedAttributes);
 
   info("Undo the change and assert that the attributes have been changed back");
   yield undoChange(inspector);
-  assertAttributes(test.node, test.originalAttributes);
+  yield assertAttributes(test.node, test.originalAttributes);
 
   info("Redo the change and assert that the attributes have been changed again");
   yield redoChange(inspector);
-  assertAttributes(test.node, test.expectedAttributes);
+  yield assertAttributes(test.node, test.expectedAttributes);
 }
--- a/browser/devtools/markupview/test/helper_outerhtml_test_runner.js
+++ b/browser/devtools/markupview/test/helper_outerhtml_test_runner.js
@@ -45,25 +45,26 @@ function* runEditOuterHTMLTest(test, ins
   let onUpdated = inspector.once("inspector-updated");
 
   info("Listen for reselectedonremoved and edit the outerHTML");
   let onReselected = inspector.markup.once("reselectedonremoved");
   yield inspector.markup.updateNodeOuterHTML(inspector.selection.nodeFront,
                                              test.newHTML, test.oldHTML);
   yield onReselected;
 
-  // Typically selectedNode will === pageNode, but if a new element has been injected in front
-  // of it, this will not be the case.  If this happens.
+  // Typically selectedNode will === pageNode, but if a new element has been
+  // injected in front of it, this will not be the case. If this happens.
   let selectedNodeFront = inspector.selection.nodeFront;
   let pageNodeFront = yield inspector.walker.querySelector(inspector.walker.rootNode, test.selector);
   let pageNode = getNode(test.selector);
 
   if (test.validate) {
     yield test.validate(pageNode, pageNodeFront, selectedNodeFront, inspector);
   } else {
     is(pageNodeFront, selectedNodeFront, "Original node (grabbed by selector) is selected");
-    is(pageNode.outerHTML, test.newHTML, "Outer HTML has been updated");
+    let {outerHTML} = yield getNodeInfo(test.selector);
+    is(outerHTML, test.newHTML, "Outer HTML has been updated");
   }
 
   // Wait for the inspector to be fully updated to avoid causing errors by
   // abruptly closing hanging requests when the test ends
   yield onUpdated;
 }
--- a/browser/devtools/shared/frame-script-utils.js
+++ b/browser/devtools/shared/frame-script-utils.js
@@ -138,30 +138,36 @@ addMessageListener("devtools:test:setSty
  * @param {Object} data
  * - {String} selector The CSS selector to get the node (can be a "super"
  *   selector).
  * @return {Object} data Null if selector didn't match any node, otherwise:
  * - {String} tagName.
  * - {String} namespaceURI.
  * - {Number} numChildren The number of children in the element.
  * - {Array} attributes An array of {name, value, namespaceURI} objects.
+ * - {String} outerHTML.
+ * - {String} innerHTML.
+ * - {String} textContent.
  */
 addMessageListener("devtools:test:getDomElementInfo", function(msg) {
   let {selector} = msg.data;
   let node = superQuerySelector(selector);
 
   let info = null;
   if (node) {
     info = {
       tagName: node.tagName,
       namespaceURI: node.namespaceURI,
       numChildren: node.children.length,
       attributes: [...node.attributes].map(({name, value, namespaceURI}) => {
         return {name, value, namespaceURI};
-      })
+      }),
+      outerHTML: node.outerHTML,
+      innerHTML: node.innerHTML,
+      textContent: node.textContent
     };
   }
 
   sendAsyncMessage("devtools:test:getDomElementInfo", info);
 });
 
 /**
  * Set a given attribute value on a node.
--- a/browser/devtools/styleinspector/test/browser_ruleview_eyedropper.js
+++ b/browser/devtools/styleinspector/test/browser_ruleview_eyedropper.js
@@ -1,19 +1,19 @@
 /* vim: set ts=2 et sw=2 tw=80: */
 /* Any copyright is dedicated to the Public Domain.
  http://creativecommons.org/publicdomain/zero/1.0/ */
 
 "use strict";
 
 // So we can test collecting telemetry on the eyedropper
-let oldCanRecord = Services.telemetry.canRecord;
-Services.telemetry.canRecord = true;
+let oldCanRecord = Services.telemetry.canRecordExtended;
+Services.telemetry.canRecordExtended = true;
 registerCleanupFunction(function () {
-  Services.telemetry.canRecord = oldCanRecord;
+  Services.telemetry.canRecordExtended = oldCanRecord;
 });
 const HISTOGRAM_ID = "DEVTOOLS_PICKER_EYEDROPPER_OPENED_BOOLEAN";
 const FLAG_HISTOGRAM_ID = "DEVTOOLS_PICKER_EYEDROPPER_OPENED_PER_USER_FLAG";
 const EXPECTED_TELEMETRY = {
   "DEVTOOLS_PICKER_EYEDROPPER_OPENED_BOOLEAN": 2,
   "DEVTOOLS_PICKER_EYEDROPPER_OPENED_PER_USER_FLAG": 1
 }
 
--- a/browser/devtools/webide/content/prefs.xhtml
+++ b/browser/devtools/webide/content/prefs.xhtml
@@ -8,72 +8,61 @@
   <!ENTITY % webideDTD SYSTEM "chrome://browser/locale/devtools/webide.dtd" >
   %webideDTD;
 ]>
 
 <html xmlns="http://www.w3.org/1999/xhtml">
   <head>
     <meta charset="utf8"/>
     <link rel="stylesheet" href="chrome://webide/skin/deck.css" type="text/css"/>
-    <link rel="stylesheet" href="chrome://webide/skin/prefs.css" type="text/css"/>
     <script type="application/javascript;version=1.8" src="chrome://webide/content/prefs.js"></script>
   </head>
   <body>
 
     <div id="controls">
       <a id="restore">&prefs_restore;</a>
       <a id="manageComponents">&prefs_manage_components;</a>
       <a id="close">&deck_close;</a>
     </div>
 
     <h1>&prefs_title;</h1>
 
     <h2>&prefs_general_title;</h2>
 
     <ul>
       <li>
+        <label title="&prefs_options_showeditor_tooltip;">
+          <input type="checkbox" data-pref="devtools.webide.showProjectEditor"/>
+          <span>&prefs_options_showeditor;</span>
+        </label>
+      </li>
+      <li>
         <label title="&prefs_options_rememberlastproject_tooltip;">
           <input type="checkbox" data-pref="devtools.webide.restoreLastProject"/>
           <span>&prefs_options_rememberlastproject;</span>
         </label>
       </li>
       <li>
         <label title="&prefs_options_autoconnectruntime_tooltip;">
           <input type="checkbox" data-pref="devtools.webide.autoConnectRuntime"/>
           <span>&prefs_options_autoconnectruntime;</span>
         </label>
       </li>
       <li>
-        <label title="&prefs_options_showeditor_tooltip;">
-          <input type="checkbox" data-pref="devtools.webide.showProjectEditor"/>
-          <span>&prefs_options_showeditor;</span>
-        </label>
-      </li>
-      <li>
         <label title="&prefs_options_templatesurl_tooltip;">
           <span>&prefs_options_templatesurl;</span>
           <input data-pref="devtools.webide.templatesURL"/>
         </label>
       </li>
     </ul>
 
     <h2>&prefs_editor_title;</h2>
 
     <ul>
       <li>
-        <label><span>&prefs_options_keybindings;</span>
-          <select data-pref="devtools.editor.keymap">
-            <option value="default">&prefs_options_keybindings_default;</option>
-            <option value="vim">Vim</option>
-            <option value="emacs">Emacs</option>
-            <option value="sublime">Sublime</option>
-          </select>
-        </label>
-      </li>
-      <li>
         <label><span>&prefs_options_tabsize;</span>
           <select data-pref="devtools.editor.tabsize">
             <option value="2">2</option>
             <option value="4">4</option>
             <option value="8">8</option>
           </select>
         </label>
       </li>
@@ -96,12 +85,22 @@
         </label>
       </li>
       <li>
         <label title="&prefs_options_autoclosebrackets_tooltip;">
           <input type="checkbox" data-pref="devtools.editor.autoclosebrackets"/>
           <span>&prefs_options_autoclosebrackets;</span>
         </label>
       </li>
+      <li>
+        <label><span>&prefs_options_keybindings;</span>
+          <select data-pref="devtools.editor.keymap">
+            <option value="default">&prefs_options_keybindings_default;</option>
+            <option value="vim">Vim</option>
+            <option value="emacs">Emacs</option>
+            <option value="sublime">Sublime</option>
+          </select>
+        </label>
+      </li>
     </ul>
 
   </body>
 </html>
--- a/browser/devtools/webide/content/webide.xul
+++ b/browser/devtools/webide/content/webide.xul
@@ -178,17 +178,17 @@
         <vbox id="runtime-panel-simulator"></vbox>
         <toolbarbutton class="panel-item" label="&runtimePanel_installsimulator;" id="runtime-panel-installsimulator" command="cmd_showAddons"/>
         <label class="panel-header">&runtimePanel_other;</label>
         <vbox id="runtime-panel-other"></vbox>
         <vbox flex="1" id="runtime-actions">
           <toolbarbutton class="panel-item" id="runtime-details" command="cmd_showRuntimeDetails"/>
           <toolbarbutton class="panel-item" id="runtime-permissions" command="cmd_showPermissionsTable"/>
           <toolbarbutton class="panel-item" id="runtime-preferences" command="cmd_showDevicePrefs"/>
-          <toolbarbutton class="panel-item" id="runtime-preferences" command="cmd_showSettings"/>
+          <toolbarbutton class="panel-item" id="runtime-settings" command="cmd_showSettings"/>
           <toolbarbutton class="panel-item" id="runtime-screenshot"  command="cmd_takeScreenshot"/>
           <toolbarbutton class="panel-item" id="runtime-disconnect"  command="cmd_disconnectRuntime"/>
         </vbox>
       </vbox>
     </panel>
 
   </popupset>
 
--- a/browser/devtools/webide/test/head.js
+++ b/browser/devtools/webide/test/head.js
@@ -140,17 +140,17 @@ function documentIsLoaded(doc) {
   }
   return deferred.promise;
 }
 
 function lazyIframeIsLoaded(iframe) {
   let deferred = promise.defer();
   iframe.addEventListener("load", function onLoad() {
     iframe.removeEventListener("load", onLoad, true);
-    deferred.resolve();
+    deferred.resolve(nextTick());
   }, true);
   return deferred.promise;
 }
 
 function addTab(aUrl, aWindow) {
   info("Adding tab: " + aUrl);
 
   let deferred = promise.defer();
--- a/browser/devtools/webide/test/test_device_permissions.html
+++ b/browser/devtools/webide/test/test_device_permissions.html
@@ -40,18 +40,16 @@
 
           win.Cmds.showPermissionsTable();
           is(deck.selectedPanel, permIframe, "permission iframe selected");
 
           yield nextTick();
 
           yield lazyIframeIsLoaded(permIframe);
 
-          yield nextTick();
-
           yield permIframe.contentWindow.getRawPermissionsTablePromise;
 
           doc = permIframe.contentWindow.document;
           trs = doc.querySelectorAll(".line");
           found = false;
           for (let tr of trs) {
             let [name,v1,v2,v3] = tr.querySelectorAll("td");
             if (name.textContent == "geolocation") {
--- a/browser/devtools/webide/test/test_device_preferences.html
+++ b/browser/devtools/webide/test/test_device_preferences.html
@@ -42,18 +42,16 @@
 
           win.Cmds.showDevicePrefs();
           is(deck.selectedPanel, prefIframe, "device preferences iframe selected");
 
           yield nextTick();
 
           yield lazyIframeIsLoaded(prefIframe);
 
-          yield nextTick();
-
           yield prefIframe.contentWindow.getAllPrefs;
 
           setDocument(prefIframe);
 
           let fields = doc.querySelectorAll(".editable");
 
           addNewField();
 
--- a/browser/devtools/webide/test/test_device_runtime.html
+++ b/browser/devtools/webide/test/test_device_runtime.html
@@ -40,18 +40,16 @@
 
           win.Cmds.showRuntimeDetails();
           is(deck.selectedPanel, detailsIframe, "info iframe selected");
 
           yield nextTick();
 
           yield lazyIframeIsLoaded(detailsIframe);
 
-          yield nextTick();
-
           yield detailsIframe.contentWindow.getDescriptionPromise;
 
           // device info and permissions content is checked in other tests
           // We just test one value to make sure we get something
 
           let doc = detailsIframe.contentWindow.document;
           let trs = doc.querySelectorAll("tr");
           let found = false;
--- a/browser/devtools/webide/test/test_device_settings.html
+++ b/browser/devtools/webide/test/test_device_settings.html
@@ -46,18 +46,16 @@
 
           win.Cmds.showSettings();
           is(deck.selectedPanel, settingIframe, "device settings iframe selected");
 
           yield nextTick();
 
           yield lazyIframeIsLoaded(settingIframe);
 
-          yield nextTick();
-
           yield settingIframe.contentWindow.getAllSettings;
 
           setDocument(settingIframe);
 
           let fields = doc.querySelectorAll(".editable");
 
           addNewField();
 
--- a/browser/devtools/webide/themes/deck.css
+++ b/browser/devtools/webide/themes/deck.css
@@ -52,8 +52,21 @@ th, td {
 
 th {
   min-width: 130px;
 }
 
 th:first-of-type, td:first-of-type {
   text-align: left;
 }
+
+li {
+  list-style: none;
+  padding: 2px;
+}
+
+li > label:hover {
+  background-color: rgba(0,0,0,0.02);
+}
+
+li > label > span {
+  display: inline-block;
+}
--- a/browser/devtools/webide/themes/jar.mn
+++ b/browser/devtools/webide/themes/jar.mn
@@ -6,16 +6,15 @@ webide.jar:
 % skin webide classic/1.0 %skin/
 * skin/webide.css              (webide.css)
   skin/icons.png               (icons.png)
   skin/details.css             (details.css)
   skin/newapp.css              (newapp.css)
   skin/throbber.svg            (throbber.svg)
   skin/deck.css                (deck.css)
   skin/addons.css              (addons.css)
-  skin/prefs.css               (prefs.css)
   skin/runtimedetails.css      (runtimedetails.css)
   skin/permissionstable.css    (permissionstable.css)
   skin/monitor.css             (monitor.css)
   skin/config-view.css         (config-view.css)
   skin/wifi-auth.css           (wifi-auth.css)
   skin/logs.css                (logs.css)
   skin/project-listing.css     (project-listing.css)
deleted file mode 100644
--- a/browser/devtools/webide/themes/prefs.css
+++ /dev/null
@@ -1,15 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-li {
-  list-style: none;
-}
-
-li > label:hover {
-  background-color: rgba(0,0,0,0.02);
-}
-
-li > label > span {
-  display: inline-block;
-}
--- a/browser/devtools/webide/themes/webide.css
+++ b/browser/devtools/webide/themes/webide.css
@@ -250,31 +250,33 @@ panel > .panel-arrowcontainer > .panel-a
 #runtime-panel-other {
   margin-bottom: 12px;
 }
 
 #runtime-details,
 #runtime-screenshot,
 #runtime-permissions,
 #runtime-preferences,
+#runtime-settings,
 #runtime-disconnect,
 #runtime-panel-nousbdevice,
 #runtime-panel-noadbhelper,
 #runtime-panel-installsimulator,
 .runtime-panel-item-usb,
 .runtime-panel-item-wifi,
 .runtime-panel-item-other,
 .runtime-panel-item-simulator {
   list-style-image: url("icons.png");
 }
 
 #runtime-details                { -moz-image-region: rect(156px,438px,182px,412px) }
 #runtime-screenshot             { -moz-image-region: rect(130px,438px,156px,412px) }
 #runtime-permissions            { -moz-image-region: rect(104px,438px,130px,412px) }
 #runtime-preferences            { -moz-image-region: rect(104px,462px,129px,438px) }
+#runtime-settings               { -moz-image-region: rect(104px,462px,129px,438px) }
 #runtime-disconnect             { -moz-image-region: rect(52px,438px,78px,412px) }
 #runtime-panel-nousbdevice      { -moz-image-region: rect(156px,438px,182px,412px) }
 #runtime-panel-noadbhelper      { -moz-image-region: rect(234px,438px,260px,412px) }
 #runtime-panel-installsimulator { -moz-image-region: rect(0px,438px,26px,412px) }
 .runtime-panel-item-usb         { -moz-image-region: rect(52px,438px,78px,412px) }
 .runtime-panel-item-wifi        { -moz-image-region: rect(208px,438px,234px,412px) }
 .runtime-panel-item-other       { -moz-image-region: rect(26px,438px,52px,412px) }
 .runtime-panel-item-simulator   { -moz-image-region: rect(0px,438px,26px,412px) }
--- a/browser/installer/package-manifest.in
+++ b/browser/installer/package-manifest.in
@@ -613,16 +613,19 @@
 @RESPATH@/components/MozKeyboard.js
 @RESPATH@/components/InputMethod.manifest
 
 #ifdef MOZ_DEBUG
 @RESPATH@/components/TestInterfaceJS.js
 @RESPATH@/components/TestInterfaceJS.manifest
 #endif
 
+@RESPATH@/components/PACGenerator.js
+@RESPATH@/components/PACGenerator.manifest
+
 ; Modules
 @RESPATH@/browser/modules/*
 @RESPATH@/modules/*
 
 ; Safe Browsing
 #ifdef MOZ_URL_CLASSIFIER
 @RESPATH@/components/nsURLClassifier.manifest
 @RESPATH@/components/nsUrlClassifierHashCompleter.js
--- a/browser/locales/en-US/chrome/browser/browser.dtd
+++ b/browser/locales/en-US/chrome/browser/browser.dtd
@@ -737,17 +737,16 @@ just addresses the organization to follo
 <!ENTITY customizeMode.titlebar "Title Bar">
 <!ENTITY customizeMode.lwthemes "Themes">
 <!ENTITY customizeMode.lwthemes.myThemes "My Themes">
 <!ENTITY customizeMode.lwthemes.recommended "Recommended">
 <!ENTITY customizeMode.lwthemes.menuManage "Manage">
 <!ENTITY customizeMode.lwthemes.menuManage.accessKey "M">
 <!ENTITY customizeMode.lwthemes.menuGetMore "Get More Themes">
 <!ENTITY customizeMode.lwthemes.menuGetMore.accessKey "G">
-<!ENTITY customizeMode.deveditionTheme.label2 "Use Developer Edition Theme">
 
 <!ENTITY social.chatBar.commandkey "c">
 <!ENTITY social.chatBar.label "Focus chats">
 <!ENTITY social.chatBar.accesskey "c">
 
 <!ENTITY social.markpageMenu.accesskey "P">
 <!ENTITY social.markpageMenu.label "Save Page To…">
 <!ENTITY social.marklinkMenu.accesskey "L">
--- a/browser/locales/en-US/chrome/browser/browser.properties
+++ b/browser/locales/en-US/chrome/browser/browser.properties
@@ -84,16 +84,20 @@ addonError-4=#1 could not be installed b
 # #1 is the add-on name, #3 is the application name, #4 is the application version
 addonLocalError-1=This add-on could not be installed because of a filesystem error.
 addonLocalError-2=This add-on could not be installed because it does not match the add-on #3 expected.
 addonLocalError-3=This add-on could not be installed because it appears to be corrupt.
 addonLocalError-4=#1 could not be installed because #3 cannot modify the needed file.
 addonErrorIncompatible=#1 could not be installed because it is not compatible with #3 #4.
 addonErrorBlocklisted=#1 could not be installed because it has a high risk of causing stability or security problems.
 
+# LOCALIZATION NOTE (deveditionTheme.name): This should be nearly the brand name for aurora.
+# See browser/branding/aurora/locales/*/brand.properties
+deveditionTheme.name=Developer Edition
+
 # LOCALIZATION NOTE (lwthemeInstallRequest.message): %S will be replaced with
 # the host name of the site.
 lwthemeInstallRequest.message=This site (%S) attempted to install a theme.
 lwthemeInstallRequest.allowButton=Allow
 lwthemeInstallRequest.allowButton.accesskey=a
 
 lwthemePostInstallNotification.message=A new theme has been installed.
 lwthemePostInstallNotification.undoButton=Undo
--- a/browser/modules/PluginContent.jsm
+++ b/browser/modules/PluginContent.jsm
@@ -91,19 +91,17 @@ PluginContent.prototype = {
   },
 
   onPageHide: function (event) {
     // Ignore events that aren't from the main document.
     if (!this.content || event.target != this.content.document) {
       return;
     }
 
-    if (Services.telemetry.canSend) {
-      this._finishRecordingFlashPluginTelemetry();
-    }
+    this._finishRecordingFlashPluginTelemetry();
     this.clearPluginDataCache();
   },
 
   getPluginUI: function (plugin, anonid) {
     return plugin.ownerDocument.
            getAnonymousElementByAttribute(plugin, "anonid", anonid);
   },
 
@@ -386,18 +384,17 @@ PluginContent.prototype = {
         shouldShowNotification = true;
         break;
 
       case "PluginInstantiated":
         shouldShowNotification = true;
         break;
     }
 
-    if (Services.telemetry.canSend && this._getPluginInfo(plugin).mimetype ===
-                                      "application/x-shockwave-flash") {
+    if (this._getPluginInfo(plugin).mimetype === "application/x-shockwave-flash") {
       this._recordFlashPluginTelemetry(eventType, plugin);
     }
 
     // Show the in-content UI if it's not too big. The crashed plugin handler already did this.
     if (eventType != "PluginCrashed") {
       let overlay = this.getPluginUI(plugin, "main");
       if (overlay != null) {
         this.setVisibility(plugin, overlay,
@@ -421,16 +418,20 @@ PluginContent.prototype = {
     }
 
     if (shouldShowNotification) {
       this._showClickToPlayNotification(plugin, false);
     }
   },
 
   _recordFlashPluginTelemetry: function (eventType, plugin) {
+    if (!Services.telemetry.canRecordExtended) {
+      return;
+    }
+
     if (!this.flashPluginStats) {
       this.flashPluginStats = {
         instancesCount: 0,
         plugins: new WeakSet()
       };
     }
 
     if (!this.flashPluginStats.plugins.has(plugin)) {
--- a/browser/themes/linux/devedition.css
+++ b/browser/themes/linux/devedition.css
@@ -66,8 +66,14 @@
 #new-tab-button:hover > .toolbarbutton-icon {
   border-color: transparent !important;
 }
 
 /* Prevent double border below tabs toolbar */
 #TabsToolbar:not([collapsed="true"]) + #nav-bar {
   border-top-width: 0 !important;
 }
+
+/* Prevent devedition foreground color from seeping into the sidebar-box (since
+ * its background colors aren't affected by the devedition theme) */
+#sidebar-box {
+  color: initial;
+}
--- a/docshell/base/nsDocShell.cpp
+++ b/docshell/base/nsDocShell.cpp
@@ -3234,59 +3234,59 @@ nsDocShell::AddWeakScrollObserver(nsIScr
 NS_IMETHODIMP
 nsDocShell::RemoveWeakScrollObserver(nsIScrollObserver* aObserver)
 {
   nsWeakPtr obs = do_GetWeakReference(aObserver);
   return mScrollObservers.RemoveElement(obs) ? NS_OK : NS_ERROR_FAILURE;
 }
 
 void
-nsDocShell::NotifyAsyncPanZoomStarted(const mozilla::CSSIntPoint aScrollPos)
+nsDocShell::NotifyAsyncPanZoomStarted()
 {
   nsTObserverArray<nsWeakPtr>::ForwardIterator iter(mScrollObservers);
   while (iter.HasMore()) {
     nsWeakPtr ref = iter.GetNext();
     nsCOMPtr<nsIScrollObserver> obs = do_QueryReferent(ref);
     if (obs) {
-      obs->AsyncPanZoomStarted(aScrollPos);
+      obs->AsyncPanZoomStarted();
     } else {
       mScrollObservers.RemoveElement(ref);
     }
   }
 
   // Also notify child docshell
   for (uint32_t i = 0; i < mChildList.Length(); ++i) {
     nsCOMPtr<nsIDocShell> kid = do_QueryInterface(ChildAt(i));
     if (kid) {
       nsDocShell* docShell = static_cast<nsDocShell*>(kid.get());
-      docShell->NotifyAsyncPanZoomStarted(aScrollPos);
+      docShell->NotifyAsyncPanZoomStarted();
     }
   }
 }
 
 void
-nsDocShell::NotifyAsyncPanZoomStopped(const mozilla::CSSIntPoint aScrollPos)
+nsDocShell::NotifyAsyncPanZoomStopped()
 {
   nsTObserverArray<nsWeakPtr>::ForwardIterator iter(mScrollObservers);
   while (iter.HasMore()) {
     nsWeakPtr ref = iter.GetNext();
     nsCOMPtr<nsIScrollObserver> obs = do_QueryReferent(ref);
     if (obs) {
-      obs->AsyncPanZoomStopped(aScrollPos);
+      obs->AsyncPanZoomStopped();
     } else {
       mScrollObservers.RemoveElement(ref);
     }
   }
 
   // Also notify child docshell
   for (uint32_t i = 0; i < mChildList.Length(); ++i) {
     nsCOMPtr<nsIDocShell> kid = do_QueryInterface(ChildAt(i));
     if (kid) {
       nsDocShell* docShell = static_cast<nsDocShell*>(kid.get());
-      docShell->NotifyAsyncPanZoomStopped(aScrollPos);
+      docShell->NotifyAsyncPanZoomStopped();
     }
   }
 }
 
 NS_IMETHODIMP
 nsDocShell::NotifyScrollObservers()
 {
   nsTObserverArray<nsWeakPtr>::ForwardIterator iter(mScrollObservers);
--- a/docshell/base/nsDocShell.h
+++ b/docshell/base/nsDocShell.h
@@ -257,20 +257,20 @@ public:
     FireOnLocationChange(this, nullptr, mCurrentURI,
                          LOCATION_CHANGE_SAME_DOCUMENT);
   }
 
   nsresult HistoryTransactionRemoved(int32_t aIndex);
 
   // Notify Scroll observers when an async panning/zooming transform
   // has started being applied
-  void NotifyAsyncPanZoomStarted(const mozilla::CSSIntPoint aScrollPos);
+  void NotifyAsyncPanZoomStarted();
   // Notify Scroll observers when an async panning/zooming transform
   // is no longer applied
-  void NotifyAsyncPanZoomStopped(const mozilla::CSSIntPoint aScrollPos);
+  void NotifyAsyncPanZoomStopped();
 
   // Add new profile timeline markers to this docShell. This will only add
   // markers if the docShell is currently recording profile timeline markers.
   // See nsIDocShell::recordProfileTimelineMarkers
   void AddProfileTimelineMarker(const char* aName,
                                 TracingMetadata aMetaData);
   void AddProfileTimelineMarker(mozilla::UniquePtr<TimelineMarker>& aMarker);
 
--- a/docshell/base/nsIScrollObserver.h
+++ b/docshell/base/nsIScrollObserver.h
@@ -6,37 +6,37 @@
 
 #ifndef nsIScrollObserver_h___
 #define nsIScrollObserver_h___
 
 #include "nsISupports.h"
 #include "Units.h"
 
 #define NS_ISCROLLOBSERVER_IID \
-  { 0x00bc10e3, 0xaa59, 0x4aa3, \
-    { 0x88, 0xe9, 0x43, 0x0a, 0x01, 0xa3, 0x88, 0x04 } }
+  { 0xaa5026eb, 0x2f88, 0x4026, \
+    { 0xa4, 0x6b, 0xf4, 0x59, 0x6b, 0x4e, 0xdf, 0x00 } }
 
 class nsIScrollObserver : public nsISupports
 {
 public:
   NS_DECLARE_STATIC_IID_ACCESSOR(NS_ISCROLLOBSERVER_IID)
 
   /**
    * Called when the scroll position of some element has changed.
    */
   virtual void ScrollPositionChanged() = 0;
 
   /**
    * Called when an async panning/zooming transform has started being applied
    * and passed the scroll offset
    */
-  virtual void AsyncPanZoomStarted(const mozilla::CSSIntPoint aScrollPos) {};
+  virtual void AsyncPanZoomStarted() {};
 
   /**
    * Called when an async panning/zooming transform is no longer applied
    * and passed the scroll offset
    */
-  virtual void AsyncPanZoomStopped(const mozilla::CSSIntPoint aScrollPos) {};
+  virtual void AsyncPanZoomStopped() {};
 };
 
 NS_DEFINE_STATIC_IID_ACCESSOR(nsIScrollObserver, NS_ISCROLLOBSERVER_IID)
 
 #endif /* nsIScrollObserver_h___ */
--- a/dom/base/WindowNamedPropertiesHandler.cpp
+++ b/dom/base/WindowNamedPropertiesHandler.cpp
@@ -154,17 +154,17 @@ WindowNamedPropertiesHandler::getOwnProp
   aDesc.setAttributes(JSPROP_ENUMERATE);
   return true;
 }
 
 bool
 WindowNamedPropertiesHandler::defineProperty(JSContext* aCx,
                                              JS::Handle<JSObject*> aProxy,
                                              JS::Handle<jsid> aId,
-                                             JS::MutableHandle<JSPropertyDescriptor> aDesc,
+                                             JS::Handle<JSPropertyDescriptor> aDesc,
                                              JS::ObjectOpResult &result) const
 {
   ErrorResult rv;
   rv.ThrowTypeError(MSG_DEFINEPROPERTY_ON_GSP);
   rv.ReportErrorWithMessage(aCx);
   return false;
 }
 
--- a/dom/base/WindowNamedPropertiesHandler.h
+++ b/dom/base/WindowNamedPropertiesHandler.h
@@ -23,17 +23,17 @@ public:
   getOwnPropDescriptor(JSContext* aCx, JS::Handle<JSObject*> aProxy,
                        JS::Handle<jsid> aId,
                        bool /* unused */,
                        JS::MutableHandle<JSPropertyDescriptor> aDesc)
                        const override;
   virtual bool
   defineProperty(JSContext* aCx, JS::Handle<JSObject*> aProxy,
                  JS::Handle<jsid> aId,
-                 JS::MutableHandle<JSPropertyDescriptor> aDesc,
+                 JS::Handle<JSPropertyDescriptor> aDesc,
                  JS::ObjectOpResult &result) const override;
   virtual bool
   ownPropNames(JSContext* aCx, JS::Handle<JSObject*> aProxy, unsigned flags,
                JS::AutoIdVector& aProps) const override;
   virtual bool
   delete_(JSContext* aCx, JS::Handle<JSObject*> aProxy, JS::Handle<jsid> aId,
           JS::ObjectOpResult &aResult) const override;
   virtual bool
--- a/dom/base/nsGlobalWindow.cpp
+++ b/dom/base/nsGlobalWindow.cpp
@@ -616,17 +616,17 @@ public:
   virtual bool getOwnPropertyDescriptor(JSContext* cx,
                                         JS::Handle<JSObject*> proxy,
                                         JS::Handle<jsid> id,
                                         JS::MutableHandle<JSPropertyDescriptor> desc)
                                         const override;
   virtual bool defineProperty(JSContext* cx,
                               JS::Handle<JSObject*> proxy,
                               JS::Handle<jsid> id,
-                              JS::MutableHandle<JSPropertyDescriptor> desc,
+                              JS::Handle<JSPropertyDescriptor> desc,
                               JS::ObjectOpResult &result) const override;
   virtual bool ownPropertyKeys(JSContext *cx,
                                JS::Handle<JSObject*> proxy,
                                JS::AutoIdVector &props) const override;
   virtual bool delete_(JSContext *cx, JS::Handle<JSObject*> proxy,
                        JS::Handle<jsid> id,
                        JS::ObjectOpResult &result) const override;
   virtual bool enumerate(JSContext *cx, JS::Handle<JSObject*> proxy,
@@ -777,17 +777,17 @@ nsOuterWindowProxy::getOwnPropertyDescri
 
   return js::Wrapper::getOwnPropertyDescriptor(cx, proxy, id, desc);
 }
 
 bool
 nsOuterWindowProxy::defineProperty(JSContext* cx,
                                    JS::Handle<JSObject*> proxy,
                                    JS::Handle<jsid> id,
-                                   JS::MutableHandle<JSPropertyDescriptor> desc,
+                                   JS::Handle<JSPropertyDescriptor> desc,
                                    JS::ObjectOpResult &result) const
 {
   int32_t index = GetArrayIndexFromId(cx, id);
   if (IsArrayIndex(index)) {
     // Spec says to Reject whether this is a supported index or not,
     // since we have no indexed setter or indexed creator.  It is up
     // to the caller to decide whether to throw a TypeError.
     return result.failCantDefineWindowElement();
--- a/dom/bindings/BindingUtils.cpp
+++ b/dom/bindings/BindingUtils.cpp
@@ -1434,17 +1434,17 @@ XrayResolveOwnProperty(JSContext* cx, JS
   }
 
   return true;
 }
 
 bool
 XrayDefineProperty(JSContext* cx, JS::Handle<JSObject*> wrapper,
                    JS::Handle<JSObject*> obj, JS::Handle<jsid> id,
-                   JS::MutableHandle<JSPropertyDescriptor> desc,
+                   JS::Handle<JSPropertyDescriptor> desc,
                    JS::ObjectOpResult &result, bool *defined)
 {
   if (!js::IsProxy(obj))
     return true;
 
   const DOMProxyHandler* handler = GetDOMProxyHandler(obj);
   return handler->defineProperty(cx, wrapper, id, desc, result, defined);
 }
--- a/dom/bindings/BindingUtils.h
+++ b/dom/bindings/BindingUtils.h
@@ -2461,17 +2461,17 @@ XrayResolveOwnProperty(JSContext* cx, JS
  * id and desc are the parameters for the property to be defined.
  * result is the out-parameter indicating success (read it only if
  *     this returns true and also sets *defined to true).
  * defined will be set to true if a property was set as a result of this call.
  */
 bool
 XrayDefineProperty(JSContext* cx, JS::Handle<JSObject*> wrapper,
                    JS::Handle<JSObject*> obj, JS::Handle<jsid> id,
-                   JS::MutableHandle<JSPropertyDescriptor> desc,
+                   JS::Handle<JSPropertyDescriptor> desc,
                    JS::ObjectOpResult &result,
                    bool *defined);
 
 /**
  * Add to props the property keys of all indexed or named properties of obj and
  * operations, attributes and constants of the interfaces for obj.
  *
  * wrapper is the Xray JS object.
--- a/dom/bindings/Codegen.py
+++ b/dom/bindings/Codegen.py
@@ -10241,17 +10241,17 @@ class CGDOMJSProxyHandler_getOwnPropDesc
 
 class CGDOMJSProxyHandler_defineProperty(ClassMethod):
     def __init__(self, descriptor):
         # The usual convention is to name the ObjectOpResult out-parameter
         # `result`, but that name is a bit overloaded around here.
         args = [Argument('JSContext*', 'cx'),
                 Argument('JS::Handle<JSObject*>', 'proxy'),
                 Argument('JS::Handle<jsid>', 'id'),
-                Argument('JS::MutableHandle<JSPropertyDescriptor>', 'desc'),
+                Argument('JS::Handle<JSPropertyDescriptor>', 'desc'),
                 Argument('JS::ObjectOpResult&', 'opresult'),
                 Argument('bool*', 'defined')]
         ClassMethod.__init__(self, "defineProperty", "bool", args, virtual=True, override=True, const=True)
         self.descriptor = descriptor
 
     def getBody(self):
         set = ""
 
--- a/dom/bindings/DOMJSProxyHandler.cpp
+++ b/dom/bindings/DOMJSProxyHandler.cpp
@@ -190,17 +190,17 @@ BaseDOMProxyHandler::getOwnPropertyDescr
                                               MutableHandle<JSPropertyDescriptor> desc) const
 {
   return getOwnPropDescriptor(cx, proxy, id, /* ignoreNamedProps = */ false,
                               desc);
 }
 
 bool
 DOMProxyHandler::defineProperty(JSContext* cx, JS::Handle<JSObject*> proxy, JS::Handle<jsid> id,
-                                MutableHandle<JSPropertyDescriptor> desc,
+                                Handle<JSPropertyDescriptor> desc,
                                 JS::ObjectOpResult &result, bool *defined) const
 {
   if (desc.hasGetterObject() && desc.setter() == JS_StrictPropertyStub) {
     return result.failGetterOnly();
   }
 
   if (xpc::WrapperFactory::IsXrayWrapper(proxy)) {
     return result.succeed();
@@ -247,17 +247,17 @@ DOMProxyHandler::set(JSContext *cx, Hand
     if (!js::GetObjectProto(cx, proxy, &proto)) {
       return false;
     }
     if (proto && !JS_GetPropertyDescriptorById(cx, proto, id, &desc)) {
       return false;
     }
   }
 
-  return js::SetPropertyIgnoringNamedGetter(cx, proxy, id, vp, receiver, &desc, result);
+  return js::SetPropertyIgnoringNamedGetter(cx, proxy, id, vp, receiver, desc, result);
 }
 
 bool
 DOMProxyHandler::delete_(JSContext* cx, JS::Handle<JSObject*> proxy,
                          JS::Handle<jsid> id, JS::ObjectOpResult &result) const
 {
   JS::Rooted<JSObject*> expando(cx);
   if (!xpc::WrapperFactory::IsXrayWrapper(proxy) && (expando = GetExpandoObject(proxy))) {
--- a/dom/bindings/DOMJSProxyHandler.h
+++ b/dom/bindings/DOMJSProxyHandler.h
@@ -100,24 +100,24 @@ protected:
 class DOMProxyHandler : public BaseDOMProxyHandler
 {
 public:
   MOZ_CONSTEXPR DOMProxyHandler()
     : BaseDOMProxyHandler(&family)
   {}
 
   bool defineProperty(JSContext* cx, JS::Handle<JSObject*> proxy, JS::Handle<jsid> id,
-                      JS::MutableHandle<JSPropertyDescriptor> desc,
+                      JS::Handle<JSPropertyDescriptor> desc,
                       JS::ObjectOpResult &result) const override
   {
     bool unused;
     return defineProperty(cx, proxy, id, desc, result, &unused);
   }
   virtual bool defineProperty(JSContext* cx, JS::Handle<JSObject*> proxy, JS::Handle<jsid> id,
-                              JS::MutableHandle<JSPropertyDescriptor> desc,
+                              JS::Handle<JSPropertyDescriptor> desc,
                               JS::ObjectOpResult &result, bool *defined) const;
   bool delete_(JSContext* cx, JS::Handle<JSObject*> proxy, JS::Handle<jsid> id,
                JS::ObjectOpResult &result) const override;
   bool preventExtensions(JSContext* cx, JS::Handle<JSObject*> proxy,
                          JS::ObjectOpResult& result) const override;
   bool isExtensible(JSContext *cx, JS::Handle<JSObject*> proxy, bool *extensible)
                     const override;
   bool has(JSContext* cx, JS::Handle<JSObject*> proxy, JS::Handle<jsid> id,
--- a/dom/browser-element/BrowserElementChildPreload.js
+++ b/dom/browser-element/BrowserElementChildPreload.js
@@ -570,18 +570,16 @@ BrowserElementChild.prototype = {
 
     sendAsyncMsg('metachange', meta);
   },
 
   _ScrollViewChangeHandler: function(e) {
     e.stopPropagation();
     let detail = {
       state: e.state,
-      scrollX: e.scrollX,
-      scrollY: e.scrollY,
     };
     sendAsyncMsg('scrollviewchange', detail);
   },
 
   _selectionStateChangedHandler: function(e) {
     e.stopPropagation();
 
     if (!this._isContentWindowCreated) {
--- a/dom/browser-element/BrowserElementParent.js
+++ b/dom/browser-element/BrowserElementParent.js
@@ -267,17 +267,18 @@ BrowserElementParent.prototype = {
         this._frameLoader.QueryInterface(Ci.nsIFrameLoader).ownerIsWidget) {
       cancelCallback();
       return;
     }
 
     /* username and password */
     let detail = {
       host:     authDetail.host,
-      realm:    authDetail.realm
+      realm:    authDetail.realm,
+      isProxy:  authDetail.isProxy
     };
 
     evt = this._createEvent('usernameandpasswordrequired', detail,
                             /* cancelable */ true);
     Cu.exportFunction(function(username, password) {
       if (callbackCalled)
         return;
       callbackCalled = true;
--- a/dom/browser-element/BrowserElementPromptService.jsm
+++ b/dom/browser-element/BrowserElementPromptService.jsm
@@ -378,27 +378,56 @@ BrowserElementAuthPrompt.prototype = {
   },
 
   _createAuthDetail: function(channel, authInfo) {
     let [hostname, httpRealm] = this._getAuthTarget(channel, authInfo);
     return {
       host:             hostname,
       realm:            httpRealm,
       username:         authInfo.username,
+      isProxy:          !!(authInfo.flags & Ci.nsIAuthInformation.AUTH_PROXY),
       isOnlyPassword:   !!(authInfo.flags & Ci.nsIAuthInformation.ONLY_PASSWORD)
     };
   },
 
+  // The code is taken from nsLoginManagerPrompter.js, with slight
+  // modification for parameter name consistency here.
   _getAuthTarget : function (channel, authInfo) {
-    let hostname = this._getFormattedHostname(channel.URI);
+    let hostname, realm;
+
+    // If our proxy is demanding authentication, don't use the
+    // channel's actual destination.
+    if (authInfo.flags & Ci.nsIAuthInformation.AUTH_PROXY) {
+      if (!(channel instanceof Ci.nsIProxiedChannel))
+        throw new Error("proxy auth needs nsIProxiedChannel");
+
+      let info = channel.proxyInfo;
+      if (!info)
+        throw new Error("proxy auth needs nsIProxyInfo");
+
+      // Proxies don't have a scheme, but we'll use "moz-proxy://"
+      // so that it's more obvious what the login is for.
+      var idnService = Cc["@mozilla.org/network/idn-service;1"].
+                       getService(Ci.nsIIDNService);
+      hostname = "moz-proxy://" +
+                  idnService.convertUTF8toACE(info.host) +
+                  ":" + info.port;
+      realm = authInfo.realm;
+      if (!realm)
+        realm = hostname;
+
+      return [hostname, realm];
+    }
+
+    hostname = this._getFormattedHostname(channel.URI);
 
     // If a HTTP WWW-Authenticate header specified a realm, that value
     // will be available here. If it wasn't set or wasn't HTTP, we'll use
     // the formatted hostname instead.
-    let realm = authInfo.realm;
+    realm = authInfo.realm;
     if (!realm)
       realm = hostname;
 
     return [hostname, realm];
   },
 
   _getFormattedHostname : function(uri) {
     let scheme = uri.scheme;
--- a/dom/browser-element/mochitest/browserElement_Auth.js
+++ b/dom/browser-element/mochitest/browserElement_Auth.js
@@ -61,28 +61,97 @@ function testHttpAuth(e) {
 
   // Will authenticate with correct password, prompt should not be
   // called again.
   iframe.addEventListener("mozbrowserusernameandpasswordrequired", testFail);
   iframe.addEventListener("mozbrowsertitlechange", function onTitleChange(e) {
     iframe.removeEventListener("mozbrowsertitlechange", onTitleChange);
     iframe.removeEventListener("mozbrowserusernameandpasswordrequired", testFail);
     is(e.detail, 'http auth success', 'expect authentication to succeed');
-    SimpleTest.executeSoon(testAuthJarNoInterfere);
+    SimpleTest.executeSoon(testProxyAuth);
   });
 
   is(e.detail.realm, 'http_realm', 'expected realm matches');
   is(e.detail.host, 'http://test', 'expected host matches');
+  is(e.detail.isProxy, false, 'expected isProxy is false');
   e.preventDefault();
 
   SimpleTest.executeSoon(function() {
     e.detail.authenticate("httpuser", "httppass");
   });
 }
 
+function testProxyAuth(e) {
+  // The testingSJS simulates the 407 proxy authentication required response
+  // for proxy server, which will trigger the browser element to send prompt
+  // event with proxy infomation.
+  var testingSJS = 'http://test/tests/dom/browser-element/mochitest/file_http_407_response.sjs';
+  var mozproxy;
+
+  function onUserNameAndPasswordRequired(e) {
+    iframe.removeEventListener("mozbrowserusernameandpasswordrequired",
+                               onUserNameAndPasswordRequired);
+    iframe.addEventListener("mozbrowsertitlechange", function onTitleChange(e) {
+      iframe.removeEventListener("mozbrowsertitlechange", onTitleChange);
+      iframe.removeEventListener("mozbrowserusernameandpasswordrequired", testFail);
+      is(e.detail, 'http auth success', 'expect authentication to succeed');
+      SimpleTest.executeSoon(testAuthJarNoInterfere);
+    });
+
+    is(e.detail.realm, 'http_realm', 'expected realm matches');
+    is(e.detail.host, mozproxy, 'expected host matches');
+    is(e.detail.isProxy, true, 'expected isProxy is true');
+    e.preventDefault();
+
+    SimpleTest.executeSoon(function() {
+      e.detail.authenticate("proxyuser", "proxypass");
+    });
+  }
+
+  // Resolve proxy information used by the test suite, we need it to validate
+  // whether the proxy information delivered with the prompt event is correct.
+  var resolveCallback = SpecialPowers.wrapCallbackObject({
+    QueryInterface: function (iid) {
+      const interfaces = [Ci.nsIProtocolProxyCallback, Ci.nsISupports];
+
+      if (!interfaces.some( function(v) { return iid.equals(v) } )) {
+        throw SpecialPowers.Cr.NS_ERROR_NO_INTERFACE;
+      }
+      return this;
+    },
+
+    onProxyAvailable: function (req, channel, pi, status) {
+      isnot(pi, null, 'expected proxy information available');
+      if (pi) {
+        mozproxy = "moz-proxy://" + pi.host + ":" + pi.port;
+      }
+      iframe.addEventListener("mozbrowserusernameandpasswordrequired",
+                              onUserNameAndPasswordRequired);
+
+      iframe.src = testingSJS;
+    }
+  });
+
+  var ioService = SpecialPowers.Cc["@mozilla.org/network/io-service;1"]
+                  .getService(SpecialPowers.Ci.nsIIOService);
+  var pps = SpecialPowers.Cc["@mozilla.org/network/protocol-proxy-service;1"]
+            .getService();
+  var systemPrincipal = SpecialPowers.Services.scriptSecurityManager
+                                     .getSystemPrincipal();
+  var channel = ioService.newChannel2(testingSJS,
+                                      null,
+                                      null,
+                                      null,
+                                      systemPrincipal,
+                                      null,
+                                      SpecialPowers.Ci.nsILoadInfo.SEC_NORMAL,
+                                      SpecialPowers.Ci.nsIContentPolicy.TYPE_OTHER);
+  pps.asyncResolve(channel, 0, resolveCallback);
+}
+
 function testAuthJarNoInterfere(e) {
   var authMgr = SpecialPowers.Cc['@mozilla.org/network/http-auth-manager;1']
     .getService(SpecialPowers.Ci.nsIHttpAuthManager);
   var secMan = SpecialPowers.Cc["@mozilla.org/scriptsecuritymanager;1"]
                .getService(SpecialPowers.Ci.nsIScriptSecurityManager);
   var ioService = SpecialPowers.Cc["@mozilla.org/network/io-service;1"]
                   .getService(SpecialPowers.Ci.nsIIOService);
   var uri = ioService.newURI("http://test/tests/dom/browser-element/mochitest/file_http_401_response.sjs", null, null);
new file mode 100644
--- /dev/null
+++ b/dom/browser-element/mochitest/file_http_407_response.sjs
@@ -0,0 +1,16 @@
+function handleRequest(request, response)
+{
+  var auth = "";
+  try {
+    auth = request.getHeader("Proxy-Authorization");
+  } catch(e) {}
+
+  if (auth == "Basic cHJveHl1c2VyOnByb3h5cGFzcw==") {
+    response.setStatusLine("1.1", 200, "OK");
+    response.write("<html><head><title>http auth success</title></head><html>");
+  } else {
+    response.setStatusLine("1.1", 407, "Proxy Authentication Required");
+    response.setHeader("Proxy-Authenticate", "Basic realm=\"http_realm\"");
+    response.write("<html><head><title>http auth failed</title></head><html>");
+  }
+}
--- a/dom/browser-element/mochitest/mochitest.ini
+++ b/dom/browser-element/mochitest/mochitest.ini
@@ -106,16 +106,17 @@ support-files =
   file_browserElement_XFrameOptionsSameOrigin.html
   file_bug709759.sjs
   file_bug741717.sjs
   file_download_bin.sjs
   file_empty.html
   file_empty_script.js
   file_focus.html
   file_http_401_response.sjs
+  file_http_407_response.sjs
   file_inputmethod.html
   file_post_request.html
   file_wyciwyg.html
   file_NestedFramesOuter_CopyPaste.html
 
 # Note: browserElementTestHelpers.js looks at the test's filename to determine
 # whether the test should be OOP.  "_oop_" signals OOP, "_inproc_" signals in
 # process.  Default is OOP.
--- a/dom/cache/Context.cpp
+++ b/dom/cache/Context.cpp
@@ -77,37 +77,46 @@ public:
                     Action* aQuotaIOThreadAction)
     : mContext(aContext)
     , mThreadsafeHandle(aContext->CreateThreadsafeHandle())
     , mManager(aManager)
     , mQuotaIOThreadAction(aQuotaIOThreadAction)
     , mInitiatingThread(NS_GetCurrentThread())
     , mResult(NS_OK)
     , mState(STATE_INIT)
+    , mCanceled(false)
     , mNeedsQuotaRelease(false)
   {
     MOZ_ASSERT(mContext);
     MOZ_ASSERT(mManager);
     MOZ_ASSERT(mInitiatingThread);
   }
 
   nsresult Dispatch()
   {
-    NS_ASSERT_OWNINGTHREAD(Action::Resolver);
+    NS_ASSERT_OWNINGTHREAD(QuotaInitRunnable);
     MOZ_ASSERT(mState == STATE_INIT);
 
     mState = STATE_CALL_WAIT_FOR_OPEN_ALLOWED;
     nsresult rv = NS_DispatchToMainThread(this, nsIThread::DISPATCH_NORMAL);
     if (NS_WARN_IF(NS_FAILED(rv))) {
       mState = STATE_COMPLETE;
       Clear();
     }
     return rv;
   }
 
+  void Cancel()
+  {
+    NS_ASSERT_OWNINGTHREAD(QuotaInitRunnable);
+    MOZ_ASSERT(!mCanceled);
+    mCanceled = true;
+    mQuotaIOThreadAction->CancelOnInitiatingThread();
+  }
+
 private:
   class SyncResolver final : public Action::Resolver
   {
   public:
     SyncResolver()
       : mResolved(false)
       , mResult(NS_OK)
     { }
@@ -147,32 +156,33 @@ private:
     STATE_ENSURE_ORIGIN_INITIALIZED,
     STATE_RUNNING,
     STATE_COMPLETING,
     STATE_COMPLETE
   };
 
   void Clear()
   {
-    NS_ASSERT_OWNINGTHREAD(Action::Resolver);
+    NS_ASSERT_OWNINGTHREAD(QuotaInitRunnable);
     MOZ_ASSERT(mContext);
     mContext = nullptr;
     mManager = nullptr;
     mQuotaIOThreadAction = nullptr;
   }
 
   nsRefPtr<Context> mContext;
   nsRefPtr<ThreadsafeHandle> mThreadsafeHandle;
   nsRefPtr<Manager> mManager;
   nsRefPtr<Action> mQuotaIOThreadAction;
   nsCOMPtr<nsIThread> mInitiatingThread;
   nsresult mResult;
   QuotaInfo mQuotaInfo;
   nsMainThreadPtrHandle<OfflineStorage> mOfflineStorage;
   State mState;
+  Atomic<bool> mCanceled;
   bool mNeedsQuotaRelease;
 
 public:
   NS_DECL_THREADSAFE_ISUPPORTS
   NS_DECL_NSIRUNNABLE
 };
 
 NS_IMPL_ISUPPORTS(mozilla::dom::cache::Context::QuotaInitRunnable, nsIRunnable);
@@ -218,16 +228,22 @@ Context::QuotaInitRunnable::Run()
 
   nsRefPtr<SyncResolver> resolver = new SyncResolver();
 
   switch(mState) {
     // -----------------------------------
     case STATE_CALL_WAIT_FOR_OPEN_ALLOWED:
     {
       MOZ_ASSERT(NS_IsMainThread());
+
+      if (mCanceled) {
+        resolver->Resolve(NS_ERROR_ABORT);
+        break;
+      }
+
       QuotaManager* qm = QuotaManager::GetOrCreate();
       if (!qm) {
         resolver->Resolve(NS_ERROR_FAILURE);
         break;
       }
 
       nsRefPtr<ManagerId> managerId = mManager->GetManagerId();
       nsCOMPtr<nsIPrincipal> principal = managerId->Principal();
@@ -261,16 +277,21 @@ Context::QuotaInitRunnable::Run()
     }
     // ------------------------------
     case STATE_WAIT_FOR_OPEN_ALLOWED:
     {
       MOZ_ASSERT(NS_IsMainThread());
 
       mNeedsQuotaRelease = true;
 
+      if (mCanceled) {
+        resolver->Resolve(NS_ERROR_ABORT);
+        break;
+      }
+
       QuotaManager* qm = QuotaManager::Get();
       MOZ_ASSERT(qm);
 
       nsRefPtr<OfflineStorage> offlineStorage =
         OfflineStorage::Register(mThreadsafeHandle, mQuotaInfo);
       mOfflineStorage = new nsMainThreadPtrHolder<OfflineStorage>(offlineStorage);
 
       mState = STATE_ENSURE_ORIGIN_INITIALIZED;
@@ -284,16 +305,21 @@ Context::QuotaInitRunnable::Run()
     // ----------------------------------
     case STATE_ENSURE_ORIGIN_INITIALIZED:
     {
       // Can't assert quota IO thread because its an idle thread that can get
       // recreated.  At least assert we're not on main thread or owning thread.
       MOZ_ASSERT(!NS_IsMainThread());
       MOZ_ASSERT(_mOwningThread.GetThread() != PR_GetCurrentThread());
 
+      if (mCanceled) {
+        resolver->Resolve(NS_ERROR_ABORT);
+        break;
+      }
+
       QuotaManager* qm = QuotaManager::Get();
       MOZ_ASSERT(qm);
       nsresult rv = qm->EnsureOriginIsInitialized(PERSISTENCE_TYPE_DEFAULT,
                                                   mQuotaInfo.mGroup,
                                                   mQuotaInfo.mOrigin,
                                                   mQuotaInfo.mIsApp,
                                                   getter_AddRefs(mQuotaInfo.mDir));
       if (NS_FAILED(rv)) {
@@ -313,17 +339,17 @@ Context::QuotaInitRunnable::Run()
       mQuotaIOThreadAction->RunOnTarget(resolver, mQuotaInfo);
       MOZ_ASSERT(resolver->Resolved());
 
       break;
     }
     // -------------------
     case STATE_COMPLETING:
     {
-      NS_ASSERT_OWNINGTHREAD(Action::Resolver);
+      NS_ASSERT_OWNINGTHREAD(QuotaInitRunnable);
       if (mQuotaIOThreadAction) {
         mQuotaIOThreadAction->CompleteOnInitiatingThread(mResult);
       }
       mContext->OnQuotaInit(mResult, mQuotaInfo, mOfflineStorage);
       mState = STATE_COMPLETE;
 
       if (mNeedsQuotaRelease) {
         // Unlock the quota dir if we locked it previously
@@ -380,39 +406,39 @@ public:
     MOZ_ASSERT(mTarget);
     MOZ_ASSERT(mAction);
     MOZ_ASSERT(mQuotaInfo.mDir);
     MOZ_ASSERT(mInitiatingThread);
   }
 
   nsresult Dispatch()
   {
-    NS_ASSERT_OWNINGTHREAD(Action::Resolver);
+    NS_ASSERT_OWNINGTHREAD(ActionRunnable);
     MOZ_ASSERT(mState == STATE_INIT);
 
     mState = STATE_RUN_ON_TARGET;
     nsresult rv = mTarget->Dispatch(this, nsIEventTarget::DISPATCH_NORMAL);
     if (NS_WARN_IF(NS_FAILED(rv))) {
       mState = STATE_COMPLETE;
       Clear();
     }
     return rv;
   }
 
   virtual bool
   MatchesCacheId(CacheId aCacheId) const override
   {
-    NS_ASSERT_OWNINGTHREAD(Action::Resolver);
+    NS_ASSERT_OWNINGTHREAD(ActionRunnable);
     return mAction->MatchesCacheId(aCacheId);
   }
 
   virtual void
   Cancel() override
   {
-    NS_ASSERT_OWNINGTHREAD(Action::Resolver);
+    NS_ASSERT_OWNINGTHREAD(ActionRunnable);
     mAction->CancelOnInitiatingThread();
   }
 
   virtual void Resolve(nsresult aRv) override
   {
     MOZ_ASSERT(mTarget == NS_GetCurrentThread());
     MOZ_ASSERT(mState == STATE_RUNNING);
 
@@ -442,17 +468,17 @@ private:
   {
     MOZ_ASSERT(mState == STATE_COMPLETE);
     MOZ_ASSERT(!mContext);
     MOZ_ASSERT(!mAction);
   }
 
   void Clear()
   {
-    NS_ASSERT_OWNINGTHREAD(Action::Resolver);
+    NS_ASSERT_OWNINGTHREAD(ActionRunnable);
     MOZ_ASSERT(mContext);
     MOZ_ASSERT(mAction);
     mContext->RemoveActivity(this);
     mContext = nullptr;
     mAction = nullptr;
   }
 
   enum State
@@ -557,17 +583,17 @@ Context::ActionRunnable::Run()
       // for this invariant violation.
       MOZ_ALWAYS_TRUE(NS_SUCCEEDED(
         mInitiatingThread->Dispatch(this, nsIThread::DISPATCH_NORMAL)));
       break;
     }
     // -------------------
     case STATE_COMPLETING:
     {
-      NS_ASSERT_OWNINGTHREAD(Action::Resolver);
+      NS_ASSERT_OWNINGTHREAD(ActionRunnable);
       mAction->CompleteOnInitiatingThread(mResult);
       mState = STATE_COMPLETE;
       // Explicitly cleanup here as the destructor could fire on any of
       // the threads we have bounced through.
       Clear();
       break;
     }
     // -----------------
@@ -674,19 +700,19 @@ Context::ThreadsafeHandle::ContextDestro
 }
 
 // static
 already_AddRefed<Context>
 Context::Create(Manager* aManager, Action* aQuotaIOThreadAction)
 {
   nsRefPtr<Context> context = new Context(aManager);
 
-  nsRefPtr<QuotaInitRunnable> runnable =
-    new QuotaInitRunnable(context, aManager, aQuotaIOThreadAction);
-  nsresult rv = runnable->Dispatch();
+  context->mInitRunnable = new QuotaInitRunnable(context, aManager,
+                                                 aQuotaIOThreadAction);
+  nsresult rv = context->mInitRunnable->Dispatch();
   if (NS_FAILED(rv)) {
     // Shutdown must be delayed until all Contexts are destroyed.  Shutdown
     // must also prevent any new Contexts from being constructed.  Crash
     // for this invariant violation.
     MOZ_CRASH("Failed to dispatch QuotaInitRunnable.");
   }
 
   return context.forget();
@@ -718,16 +744,22 @@ Context::Dispatch(nsIEventTarget* aTarge
   MOZ_ASSERT(STATE_CONTEXT_READY);
   DispatchAction(aTarget, aAction);
 }
 
 void
 Context::CancelAll()
 {
   NS_ASSERT_OWNINGTHREAD(Context);
+
+  if (mInitRunnable) {
+    MOZ_ASSERT(mState == STATE_CONTEXT_INIT);
+    mInitRunnable->Cancel();
+  }
+
   mState = STATE_CONTEXT_CANCELED;
   mPendingActions.Clear();
   {
     ActivityList::ForwardIterator iter(mActivityList);
     while (iter.HasMore()) {
       iter.GetNext()->Cancel();
     }
   }
@@ -802,16 +834,19 @@ Context::DispatchAction(nsIEventTarget* 
 }
 
 void
 Context::OnQuotaInit(nsresult aRv, const QuotaInfo& aQuotaInfo,
                      nsMainThreadPtrHandle<OfflineStorage>& aOfflineStorage)
 {
   NS_ASSERT_OWNINGTHREAD(Context);
 
+  MOZ_ASSERT(mInitRunnable);
+  mInitRunnable = nullptr;
+
   mQuotaInfo = aQuotaInfo;
 
   // Always save the offline storage to ensure QuotaManager does not shutdown
   // before the Context has gone away.
   MOZ_ASSERT(!mOfflineStorage);
   mOfflineStorage = aOfflineStorage;
 
   if (mState == STATE_CONTEXT_CANCELED || NS_FAILED(aRv)) {
--- a/dom/cache/Context.h
+++ b/dom/cache/Context.h
@@ -172,16 +172,17 @@ private:
                    nsMainThreadPtrHandle<OfflineStorage>& aOfflineStorage);
 
   already_AddRefed<ThreadsafeHandle>
   CreateThreadsafeHandle();
 
   nsRefPtr<Manager> mManager;
   State mState;
   QuotaInfo mQuotaInfo;
+  nsRefPtr<QuotaInitRunnable> mInitRunnable;
   nsTArray<PendingAction> mPendingActions;
 
   // Weak refs since activites must remove themselves from this list before
   // being destroyed by calling RemoveActivity().
   typedef nsTObserverArray<Activity*> ActivityList;
   ActivityList mActivityList;
 
   // The ThreadsafeHandle may have a strong ref back to us.  This creates
--- a/dom/cache/test/mochitest/test_cache_put.js
+++ b/dom/cache/test/mochitest/test_cache_put.js
@@ -10,17 +10,41 @@ Promise.all([fetch(url),
   is(undefined, result, 'Successful put() should resolve undefined');
   return cache.match(url);
 }).then(function(response) {
   ok(response, 'match() should find resppnse that was previously put()');
   ok(response.url.endsWith(url), 'matched response should match original url');
   return Promise.all([fetchResponse.text(),
                       response.text()]);
 }).then(function(results) {
-  // suppress large assert spam unless its relevent
+  // suppress large assert spam unless it's relevent
   if (results[0] !== results[1]) {
     is(results[0], results[1], 'stored response body should match original');
   }
+
+  // Now, try to overwrite the request with a different response object.
+  return cache.put(url, new Response("overwritten"));
+}).then(function() {
+  return cache.matchAll(url);
+}).then(function(result) {
+  is(result.length, 1, "Only one entry should exist");
+  return result[0].text();
+}).then(function(body) {
+  is(body, "overwritten", "The cache entry should be successfully overwritten");
+
+  // Now, try to write a URL with a fragment
+  return cache.put(url + "#fragment", new Response("more overwritten"));
+}).then(function() {
+  return cache.matchAll(url + "#differentFragment");
+}).then(function(result) {
+  is(result.length, 1, "Only one entry should exist");
+  return result[0].text();
+}).then(function(body) {
+  is(body, "more overwritten", "The cache entry should be successfully overwritten");
+
+  // TODO: Verify that trying to store a response with an error raises a TypeError
+  // when bug 1147178 is fixed.
+
   return caches.delete('putter' + context);
 }).then(function(deleted) {
   ok(deleted, "The cache should be deleted successfully");
   testDone();
 });
--- a/dom/canvas/WebGL2Context.h
+++ b/dom/canvas/WebGL2Context.h
@@ -117,26 +117,42 @@ public:
 private:
     void Uniform1uiv_base(WebGLUniformLocation* loc, size_t arrayLength, const GLuint* data);
     void Uniform2uiv_base(WebGLUniformLocation* loc, size_t arrayLength, const GLuint* data);
     void Uniform3uiv_base(WebGLUniformLocation* loc, size_t arrayLength, const GLuint* data);
     void Uniform4uiv_base(WebGLUniformLocation* loc, size_t arrayLength, const GLuint* data);
 
 public:
     void Uniform1uiv(WebGLUniformLocation* loc, const dom::Sequence<GLuint>& arr) {
-        Uniform1uiv_base(loc,arr.Length(), arr.Elements());
+        Uniform1uiv_base(loc, arr.Length(), arr.Elements());
     }
     void Uniform2uiv(WebGLUniformLocation* loc, const dom::Sequence<GLuint>& arr) {
-        Uniform2uiv_base(loc,arr.Length(), arr.Elements());
+        Uniform2uiv_base(loc, arr.Length(), arr.Elements());
     }
     void Uniform3uiv(WebGLUniformLocation* loc, const dom::Sequence<GLuint>& arr) {
-        Uniform3uiv_base(loc,arr.Length(), arr.Elements());
+        Uniform3uiv_base(loc, arr.Length(), arr.Elements());
     }
     void Uniform4uiv(WebGLUniformLocation* loc, const dom::Sequence<GLuint>& arr) {
-        Uniform4uiv_base(loc,arr.Length(), arr.Elements());
+        Uniform4uiv_base(loc, arr.Length(), arr.Elements());
+    }
+    void Uniform1uiv(WebGLUniformLocation* loc, const dom::Uint32Array& arr) {
+        arr.ComputeLengthAndData();
+        Uniform1uiv_base(loc, arr.Length(), arr.Data());
+    }
+    void Uniform2uiv(WebGLUniformLocation* loc, const dom::Uint32Array& arr) {
+        arr.ComputeLengthAndData();
+        Uniform2uiv_base(loc, arr.Length(), arr.Data());
+    }
+    void Uniform3uiv(WebGLUniformLocation* loc, const dom::Uint32Array& arr) {
+        arr.ComputeLengthAndData();
+        Uniform3uiv_base(loc, arr.Length(), arr.Data());
+    }
+    void Uniform4uiv(WebGLUniformLocation* loc, const dom::Uint32Array& arr) {
+        arr.ComputeLengthAndData();
+        Uniform4uiv_base(loc, arr.Length(), arr.Data());
     }
 
 private:
     void UniformMatrix2x3fv_base(WebGLUniformLocation* loc, bool transpose,
                                  size_t arrayLength, const GLfloat* data);
     void UniformMatrix3x2fv_base(WebGLUniformLocation* loc, bool transpose,
                                  size_t arrayLength, const GLfloat* data);
     void UniformMatrix2x4fv_base(WebGLUniformLocation* loc, bool transpose,
--- a/dom/events/EventStateManager.cpp
+++ b/dom/events/EventStateManager.cpp
@@ -2426,17 +2426,17 @@ EventStateManager::DoScrollText(nsIScrol
   ScrollbarStyles overflowStyle = aScrollableFrame->GetScrollbarStyles();
   if (overflowStyle.mHorizontal == NS_STYLE_OVERFLOW_HIDDEN) {
     actualDevPixelScrollAmount.x = 0;
   }
   if (overflowStyle.mVertical == NS_STYLE_OVERFLOW_HIDDEN) {
     actualDevPixelScrollAmount.y = 0;
   }
 
-  nsIScrollableFrame::ScrollSnapMode snapMode = nsIScrollableFrame::DISABLE_SNAP;
+  nsIScrollbarMediator::ScrollSnapMode snapMode = nsIScrollbarMediator::DISABLE_SNAP;
   nsIAtom* origin = nullptr;
   switch (aEvent->deltaMode) {
     case nsIDOMWheelEvent::DOM_DELTA_LINE:
       origin = nsGkAtoms::mouseWheel;
       snapMode = nsIScrollableFrame::ENABLE_SNAP;
       break;
     case nsIDOMWheelEvent::DOM_DELTA_PAGE:
       origin = nsGkAtoms::pages;
--- a/dom/ipc/TabChild.cpp
+++ b/dom/ipc/TabChild.cpp
@@ -2387,17 +2387,18 @@ TabChild::RecvRealTouchEvent(const Widge
   // Dispatch event to content (potentially a long-running operation)
   nsEventStatus status = APZCCallbackHelper::DispatchWidgetEvent(localEvent);
 
   if (!gfxPrefs::AsyncPanZoomEnabled()) {
     UpdateTapState(localEvent, status);
     return true;
   }
 
-  mAPZEventState->ProcessTouchEvent(localEvent, aGuid, aInputBlockId);
+  mAPZEventState->ProcessTouchEvent(localEvent, aGuid, aInputBlockId,
+                                    nsEventStatus_eIgnore);
   return true;
 }
 
 bool
 TabChild::RecvRealTouchMoveEvent(const WidgetTouchEvent& aEvent,
                                  const ScrollableLayerGuid& aGuid,
                                  const uint64_t& aInputBlockId)
 {
--- a/dom/manifest/ManifestProcessor.jsm
+++ b/dom/manifest/ManifestProcessor.jsm
@@ -1,360 +1,357 @@
 /* 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/.
- *
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
  * ManifestProcessor
  * Implementation of processing algorithms from:
  * http://www.w3.org/2008/webapps/manifest/
  *
  * Creates manifest processor that lets you process a JSON file
  * or individual parts of a manifest object. A manifest is just a
  * standard JS object that has been cleaned up.
  *
- *   .process({jsonText,manifestURL,docURL});
+ *   .process(jsonText, manifestURL, docURL);
  *
  * TODO: The constructor should accept the UA's supported orientations.
  * TODO: The constructor should accept the UA's supported display modes.
- * TODO: hook up developer tools to console. (1086997).
+ * TODO: hook up developer tools to issueDeveloperWarning (1086997).
  */
+/*globals Components*/
 /*exported EXPORTED_SYMBOLS */
-/*JSLint options in comment below: */
-/*globals Components, XPCOMUtils*/
 'use strict';
+
 this.EXPORTED_SYMBOLS = ['ManifestProcessor'];
-const imports = {};
 const {
   utils: Cu,
   classes: Cc,
   interfaces: Ci
 } = Components;
-Cu.import('resource://gre/modules/XPCOMUtils.jsm');
+const imports = {};
+Cu.import('resource://gre/modules/Services.jsm', imports);
 Cu.importGlobalProperties(['URL']);
-XPCOMUtils.defineLazyModuleGetter(imports, 'Services',
-  'resource://gre/modules/Services.jsm');
-imports.netutil = Cc['@mozilla.org/network/util;1'].getService(Ci.nsINetUtil);
-// Helper function extracts values from manifest members
-// and reports conformance violations.
-function extractValue({
-  objectName,
-  object,
-  property,
-  expectedType,
-  trim
-}, console) {
-  const value = object[property];
-  const isArray = Array.isArray(value);
-  // We need to special-case "array", as it's not a JS primitive.
-  const type = (isArray) ? 'array' : typeof value;
-  if (type !== expectedType) {
-    if (type !== 'undefined') {
-      let msg = `Expected the ${objectName}'s ${property} `;
-      msg += `member to a be a ${expectedType}.`;
-      console.log(msg);
-    }
-    return undefined;
-  }
-  // Trim string and returned undefined if the empty string.
-  const shouldTrim = expectedType === 'string' && value && trim;
-  if (shouldTrim) {
-    return value.trim() || undefined;
-  }
-  return value;
-}
-const displayModes = new Set(['fullscreen', 'standalone', 'minimal-ui',
+const securityManager = imports.Services.scriptSecurityManager;
+const netutil = Cc['@mozilla.org/network/util;1'].getService(Ci.nsINetUtil);
+const defaultDisplayMode = 'browser';
+const displayModes = new Set([
+  'fullscreen',
+  'standalone',
+  'minimal-ui',
   'browser'
 ]);
-const orientationTypes = new Set(['any', 'natural', 'landscape', 'portrait',
-  'portrait-primary', 'portrait-secondary', 'landscape-primary',
+const orientationTypes = new Set([
+  'any',
+  'natural',
+  'landscape',
+  'portrait',
+  'portrait-primary',
+  'portrait-secondary',
+  'landscape-primary',
   'landscape-secondary'
 ]);
-const {
-  ConsoleAPI
-} = Cu.import('resource://gre/modules/devtools/Console.jsm');
 
-class ManifestProcessor {
+this.ManifestProcessor = function ManifestProcessor() {};
+/**
+ * process method: processes json text into a clean manifest
+ * that conforms with the W3C specification.
+ * @param jsonText - the JSON string to be processd.
+ * @param manifestURL - the URL of the manifest, to resolve URLs.
+ * @param docURL - the URL of the owner doc, for security checks
+ */
+this.ManifestProcessor.prototype.process = function({
+  jsonText: jsonText,
+  manifestURL: manifestURL,
+  docLocation: docURL
+}) {
+  /*
+   * This helper function is used to extract values from manifest members.
+   * It also reports conformance violations.
+   */
+  function extractValue(obj) {
+    let value = obj.object[obj.property];
+    //we need to special-case "array", as it's not a JS primitive
+    const type = (Array.isArray(value)) ? 'array' : typeof value;
 
-  constructor() {}
-
-  static get defaultDisplayMode() {
-    return 'browser';
+    if (type !== obj.expectedType) {
+      if (type !== 'undefined') {
+        let msg = `Expected the ${obj.objectName}'s ${obj.property}`;
+        msg += `member to a be a ${obj.expectedType}.`;
+        issueDeveloperWarning(msg);
+      }
+      value = undefined;
+    }
+    return value;
   }
 
-  static get displayModes() {
-    return displayModes;
+  function issueDeveloperWarning(msg) {
+    //https://bugzilla.mozilla.org/show_bug.cgi?id=1086997
   }
 
-  static get orientationTypes() {
-    return orientationTypes;
+  function processNameMember(manifest) {
+    const obj = {
+      objectName: 'manifest',
+      object: manifest,
+      property: 'name',
+      expectedType: 'string'
+    };
+    let value = extractValue(obj);
+    return (value) ? value.trim() : value;
+  }
+
+  function processShortNameMember(manifest) {
+    const obj = {
+      objectName: 'manifest',
+      object: manifest,
+      property: 'short_name',
+      expectedType: 'string'
+    };
+    let value = extractValue(obj);
+    return (value) ? value.trim() : value;
   }
 
-  // process method: processes json text into a clean manifest
-  // that conforms with the W3C specification. Takes an object
-  // expecting the following dictionary items:
-  //  * jsonText: the JSON string to be processd.
-  //  * manifestURL: the URL of the manifest, to resolve URLs.
-  //  * docURL: the URL of the owner doc, for security checks.
-  process({
-    jsonText, manifestURL, docURL
-  }) {
-    const console = new ConsoleAPI({
-      prefix: 'Web Manifest: '
-    });
-    let rawManifest = {};
+  function processOrientationMember(manifest) {
+    const obj = {
+      objectName: 'manifest',
+      object: manifest,
+      property: 'orientation',
+      expectedType: 'string'
+    };
+    let value = extractValue(obj);
+    value = (value) ? value.trim() : undefined;
+    //The spec special-cases orientation to return the empty string
+    return (orientationTypes.has(value)) ? value : '';
+  }
+
+  function processDisplayMember(manifest) {
+    const obj = {
+      objectName: 'manifest',
+      object: manifest,
+      property: 'display',
+      expectedType: 'string'
+    };
+
+    let value = extractValue(obj);
+    value = (value) ? value.trim() : value;
+    return (displayModes.has(value)) ? value : defaultDisplayMode;
+  }
+
+  function processScopeMember(manifest, manifestURL, docURL, startURL) {
+    const spec = {
+        objectName: 'manifest',
+        object: manifest,
+        property: 'scope',
+        expectedType: 'string',
+        dontTrim: true
+      },
+      value = extractValue(spec);
+    let scopeURL;
     try {
-      rawManifest = JSON.parse(jsonText);
-    } catch (e) {}
-    if (typeof rawManifest !== 'object' || rawManifest === null) {
-      let msg = 'Manifest needs to be an object.';
-      console.warn(msg);
-      rawManifest = {};
-    }
-    const processedManifest = {
-      start_url: processStartURLMember(rawManifest, manifestURL, docURL),
-      display: processDisplayMember(rawManifest),
-      orientation: processOrientationMember(rawManifest),
-      name: processNameMember(rawManifest),
-      icons: IconsProcessor.process(rawManifest, manifestURL, console),
-      short_name: processShortNameMember(rawManifest),
-    };
-    processedManifest.scope = processScopeMember(rawManifest, manifestURL,
-      docURL, processedManifest.start_url);
-    return processedManifest;
-
-    function processNameMember(aManifest) {
-      const spec = {
-        objectName: 'manifest',
-        object: aManifest,
-        property: 'name',
-        expectedType: 'string',
-        trim: true
-      };
-      return extractValue(spec, console);
-    }
-
-    function processShortNameMember(aManifest) {
-      const spec = {
-        objectName: 'manifest',
-        object: aManifest,
-        property: 'short_name',
-        expectedType: 'string',
-        trim: true
-      };
-      return extractValue(spec, console);
+      scopeURL = new URL(value, manifestURL);
+    } catch (e) {
+      let msg = 'The URL of scope is invalid.';
+      issueDeveloperWarning(msg);
+      return undefined;
     }
 
-    function processOrientationMember(aManifest) {
-      const spec = {
-        objectName: 'manifest',
-        object: aManifest,
-        property: 'orientation',
-        expectedType: 'string',
-        trim: true
-      };
-      const value = extractValue(spec, console);
-      if (ManifestProcessor.orientationTypes.has(value)) {
-        return value;
-      }
-      // The spec special-cases orientation to return the empty string.
-      return '';
+    if (scopeURL.origin !== docURL.origin) {
+      let msg = 'Scope needs to be same-origin as Document.';
+      issueDeveloperWarning(msg);
+      return undefined;
     }
 
-    function processDisplayMember(aManifest) {
-      const spec = {
-        objectName: 'manifest',
-        object: aManifest,
-        property: 'display',
-        expectedType: 'string',
-        trim: true
-      };
-      const value = extractValue(spec, console);
-      if (ManifestProcessor.displayModes.has(value)) {
-        return value;
-      }
-      return ManifestProcessor.defaultDisplayMode;
+    //If start URL is not within scope of scope URL:
+    if (startURL && startURL.origin !== scopeURL.origin || !startURL.pathname.startsWith(scopeURL.pathname)) {
+      let msg = 'The start URL is outside the scope, so scope is invalid.';
+      issueDeveloperWarning(msg);
+      return undefined;
+    }
+    return scopeURL;
+  }
+
+  function processStartURLMember(manifest, manifestURL, docURL) {
+    const obj = {
+      objectName: 'manifest',
+      object: manifest,
+      property: 'start_url',
+      expectedType: 'string'
+    };
+
+    let value = extractValue(obj),
+      result = new URL(docURL),
+      targetURI = makeURI(result),
+      sameOrigin = false,
+      potentialResult,
+      referrerURI;
+
+    if (value === undefined || value === '') {
+      return result;
     }
 
-    function processScopeMember(aManifest, aManifestURL, aDocURL, aStartURL) {
-      const spec = {
-        objectName: 'manifest',
-        object: aManifest,
-        property: 'scope',
-        expectedType: 'string',
-        trim: false
-      };
-      const value = extractValue(spec, console);
-      let scopeURL;
-      try {
-        scopeURL = new URL(value, aManifestURL);
-      } catch (e) {
-        let msg = 'The URL of scope is invalid.';
-        console.warn(msg);
-        return undefined;
-      }
-      if (scopeURL.origin !== aDocURL.origin) {
-        let msg = 'Scope needs to be same-origin as Document.';
-        console.warn(msg);
-        return undefined;
-      }
-      // If start URL is not within scope of scope URL:
-      let isSameOrigin = aStartURL && aStartURL.origin !== scopeURL.origin;
-      if (isSameOrigin || !aStartURL.pathname.startsWith(scopeURL.pathname)) {
-        let msg =
-          'The start URL is outside the scope, so scope is invalid.';
-        console.warn(msg);
-        return undefined;
-      }
-      return scopeURL;
-    }
-
-    function processStartURLMember(aManifest, aManifestURL, aDocURL) {
-      const spec = {
-        objectName: 'manifest',
-        object: aManifest,
-        property: 'start_url',
-        expectedType: 'string',
-        trim: false
-      };
-      let result = new URL(aDocURL);
-      const value = extractValue(spec, console);
-      if (value === undefined || value === '') {
-        return result;
-      }
-      let potentialResult;
-      try {
-        potentialResult = new URL(value, aManifestURL);
-      } catch (e) {
-        console.warn('Invalid URL.');
-        return result;
-      }
-      if (potentialResult.origin !== aDocURL.origin) {
-        let msg = 'start_url must be same origin as document.';
-        console.warn(msg);
-      } else {
-        result = potentialResult;
-      }
+    try {
+      potentialResult = new URL(value, manifestURL);
+    } catch (e) {
+      issueDeveloperWarning('Invalid URL.');
       return result;
     }
-  }
-}
-this.ManifestProcessor = ManifestProcessor;
-
-class IconsProcessor {
+    referrerURI = makeURI(potentialResult);
+    try {
+      securityManager.checkSameOriginURI(referrerURI, targetURI, false);
+      sameOrigin = true;
+    } catch (e) {}
+    if (!sameOrigin) {
+      let msg = 'start_url must be same origin as document.';
+      issueDeveloperWarning(msg);
+    } else {
+      result = potentialResult;
+    }
+    return result;
 
-  constructor() {
-    throw new Error('Static use only.');
-  }
-
-  static get onlyDecimals() {
-    return /^\d+$/;
+    //Converts a URL to a Gecko URI
+    function makeURI(webURL) {
+      return imports.Services.io.newURI(webURL.toString(), null, null);
+    }
   }
 
-  static get anyRegEx() {
-    return new RegExp('any', 'i');
-  }
+  //Constants used by IconsProcessor
+  const onlyDecimals = /^\d+$/,
+    anyRegEx = new RegExp('any', 'i');
+
+  function IconsProcessor() {}
+  IconsProcessor.prototype.processIcons = function(manifest, baseURL) {
+    const obj = {
+        objectName: 'manifest',
+        object: manifest,
+        property: 'icons',
+        expectedType: 'array'
+      },
+      icons = [];
+    let value = extractValue(obj);
 
-  static process(aManifest, aBaseURL, console) {
-    const spec = {
-      objectName: 'manifest',
-      object: aManifest,
-      property: 'icons',
-      expectedType: 'array',
-      trim: false
-    };
-    const icons = [];
-    const value = extractValue(spec, console);
     if (Array.isArray(value)) {
-      // Filter out icons whose "src" is not useful.
-      value.filter(item => !!processSrcMember(item, aBaseURL))
-        .map(toIconObject)
-        .forEach(icon => icons.push(icon));
+      //filter out icons with no "src" or src is empty string
+      let processableIcons = value.filter(
+        icon => icon && Object.prototype.hasOwnProperty.call(icon, 'src') && icon.src !== ''
+      );
+      for (let potentialIcon of processableIcons) {
+        let src = processSrcMember(potentialIcon, baseURL)
+        if(src !== undefined){
+          let icon = {
+            src: src,
+            type: processTypeMember(potentialIcon),
+            sizes: processSizesMember(potentialIcon),
+            density: processDensityMember(potentialIcon)
+          };
+          icons.push(icon);
+        }
+      }
     }
     return icons;
 
-    function toIconObject(aIconData) {
-      return {
-        src: processSrcMember(aIconData, aBaseURL),
-        type: processTypeMember(aIconData),
-        sizes: processSizesMember(aIconData),
-        density: processDensityMember(aIconData)
-      };
+    function processTypeMember(icon) {
+      const charset = {},
+        hadCharset = {},
+        obj = {
+          objectName: 'icon',
+          object: icon,
+          property: 'type',
+          expectedType: 'string'
+        };
+      let value = extractValue(obj),
+        isParsable = (typeof value === 'string' && value.length > 0);
+      value = (isParsable) ? netutil.parseContentType(value.trim(), charset, hadCharset) : undefined;
+      return (value === '') ? undefined : value;
     }
 
-    function processTypeMember(aIcon) {
-      const charset = {};
-      const hadCharset = {};
-      const spec = {
-        objectName: 'icon',
-        object: aIcon,
-        property: 'type',
-        expectedType: 'string',
-        trim: true
-      };
-      let value = extractValue(spec, console);
-      if (value) {
-        value = imports.netutil.parseContentType(value, charset, hadCharset);
-      }
-      return value || undefined;
+    function processDensityMember(icon) {
+      const hasDensity = Object.prototype.hasOwnProperty.call(icon, 'density'),
+        rawValue = (hasDensity) ? icon.density : undefined,
+        value = parseFloat(rawValue),
+        result = (Number.isNaN(value) || value === +Infinity || value <= 0) ? 1.0 : value;
+      return result;
     }
 
-    function processDensityMember(aIcon) {
-      const value = parseFloat(aIcon.density);
-      const validNum = Number.isNaN(value) || value === +Infinity || value <=
-        0;
-      return (validNum) ? 1.0 : value;
-    }
-
-    function processSrcMember(aIcon, aBaseURL) {
-      const spec = {
-        objectName: 'icon',
-        object: aIcon,
-        property: 'src',
-        expectedType: 'string',
-        trim: false
-      };
-      const value = extractValue(spec, console);
+    function processSrcMember(icon, baseURL) {
+      const obj = {
+          objectName: 'icon',
+          object: icon,
+          property: 'src',
+          expectedType: 'string'
+        },
+        value = extractValue(obj);
       let url;
-      if (value && value.length) {
+      if (typeof value === 'string' && value.trim() !== '') {
         try {
-          url = new URL(value, aBaseURL);
+          url = new URL(value, baseURL);
         } catch (e) {}
       }
       return url;
     }
 
-    function processSizesMember(aIcon) {
+    function processSizesMember(icon) {
       const sizes = new Set(),
-        spec = {
+        obj = {
           objectName: 'icon',
-          object: aIcon,
+          object: icon,
           property: 'sizes',
-          expectedType: 'string',
-          trim: true
-        },
-        value = extractValue(spec, console);
+          expectedType: 'string'
+        };
+      let value = extractValue(obj);
+      value = (value) ? value.trim() : value;
       if (value) {
-        // Split on whitespace and filter out invalid values.
-        value.split(/\s+/)
-          .filter(isValidSizeValue)
-          .forEach(size => sizes.add(size));
+        //split on whitespace and filter out invalid values
+        let validSizes = value.split(/\s+/).filter(isValidSizeValue);
+        validSizes.forEach((size) => sizes.add(size));
       }
       return sizes;
-      // Implementation of HTML's link@size attribute checker.
-      function isValidSizeValue(aSize) {
-        const size = aSize.toLowerCase();
-        if (IconsProcessor.anyRegEx.test(aSize)) {
+
+      /*
+       * Implementation of HTML's link@size attribute checker
+       */
+      function isValidSizeValue(size) {
+        if (anyRegEx.test(size)) {
           return true;
         }
+        size = size.toLowerCase();
         if (!size.contains('x') || size.indexOf('x') !== size.lastIndexOf('x')) {
           return false;
         }
-        // Split left of x for width, after x for height.
-        const widthAndHeight = size.split('x');
-        const w = widthAndHeight.shift();
-        const h = widthAndHeight.join('x');
-        const validStarts = !w.startsWith('0') && !h.startsWith('0');
-        const validDecimals = IconsProcessor.onlyDecimals.test(w + h);
-        return (validStarts && validDecimals);
+
+        //split left of x for width, after x for height
+        const width = size.substring(0, size.indexOf('x'));
+        const height = size.substring(size.indexOf('x') + 1, size.length);
+        const isValid = !(height.startsWith('0') || width.startsWith('0') || !onlyDecimals.test(width + height));
+        return isValid;
       }
     }
+  };
+
+  function processIconsMember(manifest, manifestURL) {
+    const iconsProcessor = new IconsProcessor();
+    return iconsProcessor.processIcons(manifest, manifestURL);
   }
-}
+
+  //Processing starts here!
+  let manifest = {};
+
+  try {
+    manifest = JSON.parse(jsonText);
+    if (typeof manifest !== 'object' || manifest === null) {
+      let msg = 'Manifest needs to be an object.';
+      issueDeveloperWarning(msg);
+      manifest = {};
+    }
+  } catch (e) {
+    issueDeveloperWarning(e);
+  }
+
+  const processedManifest = {
+    start_url: processStartURLMember(manifest, manifestURL, docURL),
+    display: processDisplayMember(manifest),
+    orientation: processOrientationMember(manifest),
+    name: processNameMember(manifest),
+    icons: processIconsMember(manifest, manifestURL),
+    short_name: processShortNameMember(manifest)
+  };
+  processedManifest.scope = processScopeMember(manifest, manifestURL, docURL, processedManifest.start_url);
+  return processedManifest;
+};
\ No newline at end of file
--- a/dom/manifest/test/common.js
+++ b/dom/manifest/test/common.js
@@ -11,10 +11,10 @@ const bsp = SpecialPowers.Cu.import('res
   lineTerminators = '\u000D\u000A\u2028\u2029',
   whiteSpace = `${seperators}${lineTerminators}`,
   typeTests = [1, null, {},
     [], false
   ],
   data = {
     jsonText: '{}',
     manifestURL: manifestURL,
-    docURL: docLocation
+    docLocation: docLocation
   };
--- a/dom/media/MediaDecoderReader.cpp
+++ b/dom/media/MediaDecoderReader.cpp
@@ -351,20 +351,19 @@ MediaDecoderReader::RequestAudioData()
   return p;
 }
 
 MediaTaskQueue*
 MediaDecoderReader::EnsureTaskQueue()
 {
   if (!mTaskQueue) {
     MOZ_ASSERT(!mTaskQueueIsBorrowed);
-    RefPtr<SharedThreadPool> decodePool(GetMediaDecodeThreadPool());
-    NS_ENSURE_TRUE(decodePool, nullptr);
-
-    mTaskQueue = new MediaTaskQueue(decodePool.forget());
+    RefPtr<SharedThreadPool> pool(GetMediaThreadPool());
+    MOZ_DIAGNOSTIC_ASSERT(pool);
+    mTaskQueue = new MediaTaskQueue(pool.forget());
   }
 
   return mTaskQueue;
 }
 
 void
 MediaDecoderReader::BreakCycles()
 {
--- a/dom/media/MediaDecoderStateMachine.cpp
+++ b/dom/media/MediaDecoderStateMachine.cpp
@@ -244,19 +244,19 @@ MediaDecoderStateMachine::MediaDecoderSt
   mDecodingFrozenAtStateDecoding(false),
   mSentLoadedMetadataEvent(false),
   mSentFirstFrameLoadedEvent(false)
 {
   MOZ_COUNT_CTOR(MediaDecoderStateMachine);
   NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
 
   // Set up our task queue.
-  RefPtr<SharedThreadPool> threadPool(
-      SharedThreadPool::Get(NS_LITERAL_CSTRING("Media State Machine"), 1));
-  mTaskQueue = new MediaTaskQueue(threadPool.forget());
+  RefPtr<SharedThreadPool> pool(GetMediaThreadPool());
+  MOZ_DIAGNOSTIC_ASSERT(pool);
+  mTaskQueue = new MediaTaskQueue(pool.forget());
 
   static bool sPrefCacheInit = false;
   if (!sPrefCacheInit) {
     sPrefCacheInit = true;
     Preferences::AddUintVarCache(&sVideoQueueDefaultSize,
                                  "media.video-queue.default-size",
                                  MAX_VIDEO_QUEUE_SIZE);
     Preferences::AddUintVarCache(&sVideoQueueHWAccelSize,
@@ -1277,17 +1277,18 @@ static const char* const gMachineStateSt
   "WAIT_FOR_RESOURCES",
   "WAIT_FOR_CDM",
   "DECODING_FIRSTFRAME",
   "DORMANT",
   "DECODING",
   "SEEKING",
   "BUFFERING",
   "COMPLETED",
-  "SHUTDOWN"
+  "SHUTDOWN",
+  "ERROR"
 };
 
 void MediaDecoderStateMachine::SetState(State aState)
 {
   AssertCurrentThreadInMonitor();
   if (mState == aState) {
     return;
   }
--- a/dom/media/VideoUtils.cpp
+++ b/dom/media/VideoUtils.cpp
@@ -192,19 +192,19 @@ IsValidVideoRegion(const nsIntSize& aFra
     aPicture.width * aPicture.height <= MAX_VIDEO_WIDTH * MAX_VIDEO_HEIGHT &&
     aPicture.width * aPicture.height != 0 &&
     aDisplay.width <= PlanarYCbCrImage::MAX_DIMENSION &&
     aDisplay.height <= PlanarYCbCrImage::MAX_DIMENSION &&
     aDisplay.width * aDisplay.height <= MAX_VIDEO_WIDTH * MAX_VIDEO_HEIGHT &&
     aDisplay.width * aDisplay.height != 0;
 }
 
-TemporaryRef<SharedThreadPool> GetMediaDecodeThreadPool()
+TemporaryRef<SharedThreadPool> GetMediaThreadPool()
 {
-  return SharedThreadPool::Get(NS_LITERAL_CSTRING("Media Decode"),
+  return SharedThreadPool::Get(NS_LITERAL_CSTRING("Media Playback"),
                                Preferences::GetUint("media.num-decode-threads", 25));
 }
 
 bool
 ExtractH264CodecDetails(const nsAString& aCodec,
                         int16_t& aProfile,
                         int16_t& aLevel)
 {
@@ -296,27 +296,27 @@ GenerateRandomPathName(nsCString& aOutSa
 
   return NS_OK;
 }
 
 class CreateTaskQueueTask : public nsRunnable {
 public:
   NS_IMETHOD Run() {
     MOZ_ASSERT(NS_IsMainThread());
-    mTaskQueue = new MediaTaskQueue(GetMediaDecodeThreadPool());
+    mTaskQueue = new MediaTaskQueue(GetMediaThreadPool());
     return NS_OK;
   }
   nsRefPtr<MediaTaskQueue> mTaskQueue;
 };
 
 class CreateFlushableTaskQueueTask : public nsRunnable {
 public:
   NS_IMETHOD Run() {
     MOZ_ASSERT(NS_IsMainThread());
-    mTaskQueue = new FlushableMediaTaskQueue(GetMediaDecodeThreadPool());
+    mTaskQueue = new FlushableMediaTaskQueue(GetMediaThreadPool());
     return NS_OK;
   }
   nsRefPtr<FlushableMediaTaskQueue> mTaskQueue;
 };
 
 already_AddRefed<MediaTaskQueue>
 CreateMediaDecodeTaskQueue()
 {
--- a/dom/media/VideoUtils.h
+++ b/dom/media/VideoUtils.h
@@ -214,17 +214,17 @@ private:
   T& mVar;
   const T mValue;
 };
 
 class SharedThreadPool;
 
 // Returns the thread pool that is shared amongst all decoder state machines
 // for decoding streams.
-TemporaryRef<SharedThreadPool> GetMediaDecodeThreadPool();
+TemporaryRef<SharedThreadPool> GetMediaThreadPool();
 
 enum H264_PROFILE {
   H264_PROFILE_UNKNOWN                     = 0,
   H264_PROFILE_BASE                        = 0x42,
   H264_PROFILE_MAIN                        = 0x4D,
   H264_PROFILE_EXTENDED                    = 0x58,
   H264_PROFILE_HIGH                        = 0x64,
 };
--- a/dom/media/eme/CDMCaps.cpp
+++ b/dom/media/eme/CDMCaps.cpp
@@ -189,9 +189,20 @@ CDMCaps::AutoLock::GetKeyStatusesForSess
   for (size_t i = 0; i < mData.mKeyStatuses.Length(); i++) {
     const auto& key = mData.mKeyStatuses[i];
     if (key.mSessionId.Equals(aSessionId)) {
       aOutKeyStatuses.AppendElement(key);
     }
   }
 }
 
+void
+CDMCaps::AutoLock::GetSessionIdsForKeyId(const CencKeyId& aKeyId,
+                                         nsTArray<nsCString>& aOutSessionIds)
+{
+  for (const auto& keyStatus : mData.mKeyStatuses) {
+    if (keyStatus.mId == aKeyId) {
+      aOutSessionIds.AppendElement(NS_ConvertUTF16toUTF8(keyStatus.mSessionId));
+    }
+  }
+}
+
 } // namespace mozilla
--- a/dom/media/eme/CDMCaps.h
+++ b/dom/media/eme/CDMCaps.h
@@ -63,16 +63,19 @@ public:
 
     // Returns true if key status changed,
     // i.e. the key status changed from usable to expired.
     bool SetKeyStatus(const CencKeyId& aKeyId, const nsString& aSessionId, GMPMediaKeyStatus aStatus);
 
     void GetKeyStatusesForSession(const nsAString& aSessionId,
                                   nsTArray<KeyStatus>& aOutKeyStatuses);
 
+    void GetSessionIdsForKeyId(const CencKeyId& aKeyId,
+                               nsTArray<nsCString>& aOutSessionIds);
+
     // Sets the capabilities of the CDM. aCaps is the logical OR of the
     // GMP_EME_CAP_* flags from gmp-decryption.h.
     void SetCaps(uint64_t aCaps);
 
     bool CanDecryptAndDecodeAudio();
     bool CanDecryptAndDecodeVideo();
 
     bool CanDecryptAudio();
--- a/dom/media/eme/CDMProxy.cpp
+++ b/dom/media/eme/CDMProxy.cpp
@@ -129,17 +129,22 @@ CDMProxy::gmp_Init(nsAutoPtr<InitData> a
 void
 CDMProxy::OnCDMCreated(uint32_t aPromiseId)
 {
   MOZ_ASSERT(NS_IsMainThread());
   if (mKeys.IsNull()) {
     return;
   }
   MOZ_ASSERT(!GetNodeId().IsEmpty());
-  mKeys->OnCDMCreated(aPromiseId, GetNodeId());
+  if (mCDM) {
+    mKeys->OnCDMCreated(aPromiseId, GetNodeId(), mCDM->GetPluginId());
+  } else {
+    // No CDM? Just reject the promise.
+    mKeys->RejectPromise(aPromiseId, NS_ERROR_DOM_INVALID_STATE_ERR);
+  }
 }
 
 void
 CDMProxy::CreateSession(uint32_t aCreateSessionToken,
                         dom::SessionType aSessionType,
                         PromiseId aPromiseId,
                         const nsAString& aInitDataType,
                         nsTArray<uint8_t>& aInitData)
@@ -391,17 +396,19 @@ CDMProxy::OnSetSessionId(uint32_t aCreat
                          const nsAString& aSessionId)
 {
   MOZ_ASSERT(NS_IsMainThread());
   if (mKeys.IsNull()) {
     return;
   }
 
   nsRefPtr<dom::MediaKeySession> session(mKeys->GetPendingSession(aCreateSessionToken));
-  session->SetSessionId(aSessionId);
+  if (session) {
+    session->SetSessionId(aSessionId);
+  }
 }
 
 void
 CDMProxy::OnResolveLoadSessionPromise(uint32_t aPromiseId, bool aSuccess)
 {
   MOZ_ASSERT(NS_IsMainThread());
   if (mKeys.IsNull()) {
     return;
@@ -580,16 +587,24 @@ CDMProxy::gmp_Decrypted(uint32_t aId,
       mDecryptionJobs.RemoveElementAt(i);
       return;
     }
   }
   NS_WARNING("GMPDecryptorChild returned incorrect job ID");
 }
 
 void
+CDMProxy::GetSessionIdsForKeyId(const nsTArray<uint8_t>& aKeyId,
+                                nsTArray<nsCString>& aSessionIds)
+{
+  CDMCaps::AutoLock caps(Capabilites());
+  caps.GetSessionIdsForKeyId(aKeyId, aSessionIds);
+}
+
+void
 CDMProxy::Terminated()
 {
   MOZ_ASSERT(NS_IsMainThread());
   NS_WARNING("CDM terminated");
   if (!mKeys.IsNull()) {
     mKeys->Terminated();
   }
 }
--- a/dom/media/eme/CDMProxy.h
+++ b/dom/media/eme/CDMProxy.h
@@ -161,16 +161,19 @@ public:
                      GMPErr aResult,
                      const nsTArray<uint8_t>& aDecryptedData);
 
   CDMCaps& Capabilites();
 
   // Main thread only.
   void OnKeyStatusesChange(const nsAString& aSessionId);
 
+  void GetSessionIdsForKeyId(const nsTArray<uint8_t>& aKeyId,
+                             nsTArray<nsCString>& aSessionIds);
+
 #ifdef DEBUG
   bool IsOnGMPThread();
 #endif
 
 private:
 
   struct InitData {
     uint32_t mPromiseId;
--- a/dom/media/eme/MediaKeySession.cpp
+++ b/dom/media/eme/MediaKeySession.cpp
@@ -61,17 +61,17 @@ MediaKeySession::MediaKeySession(JSConte
     return;
   }
   mClosed = mKeys->MakePromise(aRv);
 }
 
 void MediaKeySession::SetSessionId(const nsAString& aSessionId)
 {
   EME_LOG("MediaKeySession[%p,'%s'] session Id set",
-          this, NS_ConvertUTF16toUTF8(mSessionId).get());
+          this, NS_ConvertUTF16toUTF8(aSessionId).get());
 
   if (NS_WARN_IF(!mSessionId.IsEmpty())) {
     return;
   }
   mSessionId = aSessionId;
   mKeys->OnSessionIdReady(this);
 }
 
--- a/dom/media/eme/MediaKeySystemAccess.cpp
+++ b/dom/media/eme/MediaKeySystemAccess.cpp
@@ -99,16 +99,20 @@ EnsureMinCDMVersion(mozIGeckoMediaPlugin
     return MediaKeySystemStatus::Available;
   }
 
   nsTArray<nsCString> tags;
   tags.AppendElement(NS_ConvertUTF16toUTF8(aKeySystem));
   nsAutoCString versionStr;
   if (NS_FAILED(aGMPService->GetPluginVersionForAPI(NS_LITERAL_CSTRING(GMP_API_DECRYPTOR),
                                                     &tags,
+                                                    versionStr)) &&
+      // XXX to be removed later in bug 1147692
+      NS_FAILED(aGMPService->GetPluginVersionForAPI(NS_LITERAL_CSTRING(GMP_API_DECRYPTOR_COMPAT),
+                                                    &tags,
                                                     versionStr))) {
     return MediaKeySystemStatus::Error;
   }
 
   nsresult rv;
   int32_t version = versionStr.ToInteger(&rv);
   if (NS_FAILED(rv) || version < 0 || aMinCdmVersion > version) {
     return MediaKeySystemStatus::Cdm_insufficient_version;
@@ -148,17 +152,21 @@ MediaKeySystemAccess::GetKeySystemStatus
     if (!IsVistaOrLater()) {
       return MediaKeySystemStatus::Cdm_not_supported;
     }
     if (!Preferences::GetBool("media.gmp-eme-adobe.enabled", false)) {
       return MediaKeySystemStatus::Cdm_disabled;
     }
     if (!HaveGMPFor(mps,
                     NS_ConvertUTF16toUTF8(aKeySystem),
-                    NS_LITERAL_CSTRING(GMP_API_DECRYPTOR))) {
+                    NS_LITERAL_CSTRING(GMP_API_DECRYPTOR)) &&
+        // XXX to be removed later in bug 1147692
+        !HaveGMPFor(mps,
+                    NS_ConvertUTF16toUTF8(aKeySystem),
+                    NS_LITERAL_CSTRING(GMP_API_DECRYPTOR_COMPAT))) {
       return MediaKeySystemStatus::Cdm_not_installed;
     }
     return EnsureMinCDMVersion(mps, aKeySystem, aMinCdmVersion);
   }
 #endif
 
   return MediaKeySystemStatus::Cdm_not_supported;
 }
@@ -192,24 +200,36 @@ IsPlayableWithGMP(mozIGeckoMediaPluginSe
   if (!MP4Decoder::CanHandleMediaType(mimeTypeUTF8,
                                       codecs,
                                       hasAAC,
                                       hasH264,
                                       hasMP3) ||
       hasMP3) {
     return false;
   }
-  return (!hasAAC || !HaveGMPFor(aGMPS,
-                                 NS_ConvertUTF16toUTF8(aKeySystem),
-                                 NS_LITERAL_CSTRING(GMP_API_DECRYPTOR),
-                                 NS_LITERAL_CSTRING("aac"))) &&
-         (!hasH264 || !HaveGMPFor(aGMPS,
-                                  NS_ConvertUTF16toUTF8(aKeySystem),
-                                  NS_LITERAL_CSTRING(GMP_API_DECRYPTOR),
-                                  NS_LITERAL_CSTRING("h264")));
+  return (!hasAAC ||
+          !(HaveGMPFor(aGMPS,
+                       NS_ConvertUTF16toUTF8(aKeySystem),
+                       NS_LITERAL_CSTRING(GMP_API_DECRYPTOR),
+                       NS_LITERAL_CSTRING("aac")) ||
+            // XXX remove later in bug 1147692
+            HaveGMPFor(aGMPS,
+                       NS_ConvertUTF16toUTF8(aKeySystem),
+                       NS_LITERAL_CSTRING(GMP_API_DECRYPTOR_COMPAT),
+                       NS_LITERAL_CSTRING("aac")))) &&
+         (!hasH264 ||
+          !(HaveGMPFor(aGMPS,
+                       NS_ConvertUTF16toUTF8(aKeySystem),
+                       NS_LITERAL_CSTRING(GMP_API_DECRYPTOR),
+                       NS_LITERAL_CSTRING("h264")) ||
+            // XXX remove later in bug 1147692
+            HaveGMPFor(aGMPS,
+                       NS_ConvertUTF16toUTF8(aKeySystem),
+                       NS_LITERAL_CSTRING(GMP_API_DECRYPTOR_COMPAT),
+                       NS_LITERAL_CSTRING("h264"))));
 #else
   return false;
 #endif
 }
 
 /* static */
 bool
 MediaKeySystemAccess::IsSupported(const nsAString& aKeySystem,
--- a/dom/media/eme/MediaKeys.cpp
+++ b/dom/media/eme/MediaKeys.cpp
@@ -1,37 +1,39 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
+#include "mozilla/dom/MediaKeys.h"
+#include "GMPService.h"
+#include "mozilla/EventDispatcher.h"
 #include "mozilla/dom/HTMLMediaElement.h"
-#include "mozilla/dom/MediaKeys.h"
 #include "mozilla/dom/MediaKeysBinding.h"
 #include "mozilla/dom/MediaKeyMessageEvent.h"
 #include "mozilla/dom/MediaKeyError.h"
 #include "mozilla/dom/MediaKeySession.h"
 #include "mozilla/dom/DOMException.h"
+#include "mozilla/dom/PluginCrashedEvent.h"
 #include "mozilla/dom/UnionTypes.h"
 #include "mozilla/CDMProxy.h"
 #include "mozilla/EMEUtils.h"
 #include "nsContentUtils.h"
 #include "nsIScriptObjectPrincipal.h"
 #include "mozilla/Preferences.h"
 #include "nsContentTypeParser.h"
 #ifdef MOZ_FMP4
 #include "MP4Decoder.h"
 #endif
 #ifdef XP_WIN
 #include "mozilla/WindowsVersion.h"
 #endif
 #include "nsContentCID.h"
 #include "nsServiceManagerUtils.h"
-#include "mozIGeckoMediaPluginService.h"
 #include "mozilla/dom/MediaKeySystemAccess.h"
 #include "nsPrintfCString.h"
 
 namespace mozilla {
 
 namespace dom {
 
 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(MediaKeys,
@@ -175,17 +177,17 @@ MediaKeys::MakePromise(ErrorResult& aRv)
 
 PromiseId
 MediaKeys::StorePromise(Promise* aPromise)
 {
   static uint32_t sEMEPromiseCount = 1;
   MOZ_ASSERT(aPromise);
   uint32_t id = sEMEPromiseCount++;
 
-  EME_LOG("MediaKeys::StorePromise() id=%d", id);
+  EME_LOG("MediaKeys[%p]::StorePromise() id=%d", this, id);
 
   // Keep MediaKeys alive for the lifetime of its promises. Any still-pending
   // promises are rejected in Shutdown().
   AddRef();
 
   mPromises.Put(id, aPromise);
   return id;
 }
@@ -201,17 +203,17 @@ MediaKeys::RetrievePromise(PromiseId aId
   mPromises.Remove(aId, getter_AddRefs(promise));
   Release();
   return promise.forget();
 }
 
 void
 MediaKeys::RejectPromise(PromiseId aId, nsresult aExceptionCode)
 {
-  EME_LOG("MediaKeys::RejectPromise(%d, 0x%x)", aId, aExceptionCode);
+  EME_LOG("MediaKeys[%p]::RejectPromise(%d, 0x%x)", this, aId, aExceptionCode);
 
   nsRefPtr<Promise> promise(RetrievePromise(aId));
   if (!promise) {
     return;
   }
   if (mPendingSessions.Contains(aId)) {
     // This promise could be a createSession or loadSession promise,
     // so we might have a pending session waiting to be resolved into
@@ -249,17 +251,17 @@ MediaKeys::OnSessionIdReady(MediaKeySess
     return;
   }
   mKeySessions.Put(aSession->GetSessionId(), aSession);
 }
 
 void
 MediaKeys::ResolvePromise(PromiseId aId)
 {
-  EME_LOG("MediaKeys::ResolvePromise(%d)", aId);
+  EME_LOG("MediaKeys[%p]::ResolvePromise(%d)", this, aId);
 
   nsRefPtr<Promise> promise(RetrievePromise(aId));
   if (!promise) {
     return;
   }
   if (mPendingSessions.Contains(aId)) {
     // We should only resolve LoadSession calls via this path,
     // not CreateSession() promises.
@@ -336,17 +338,18 @@ MediaKeys::Init(ErrorResult& aRv)
 
   if (!window) {
     promise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR);
     return promise.forget();
   }
   nsIDocument* doc = window->GetExtantDoc();
   const bool inPrivateBrowsing = nsContentUtils::IsInPrivateBrowsing(doc);
 
-  EME_LOG("MediaKeys::Create() (%s, %s), %s",
+  EME_LOG("MediaKeys[%p]::Create() (%s, %s), %s",
+          this,
           NS_ConvertUTF16toUTF8(origin).get(),
           NS_ConvertUTF16toUTF8(topLevelOrigin).get(),
           (inPrivateBrowsing ? "PrivateBrowsing" : "NonPrivateBrowsing"));
 
   // The CDMProxy's initialization is asynchronous. The MediaKeys is
   // refcounted, and its instance is returned to JS by promise once
   // it's been initialized. No external refs exist to the MediaKeys while
   // we're waiting for the promise to be resolved, so we must hold a
@@ -360,34 +363,128 @@ MediaKeys::Init(ErrorResult& aRv)
   mProxy->Init(mCreatePromiseId,
                origin,
                topLevelOrigin,
                inPrivateBrowsing);
 
   return promise.forget();
 }
 
+class CrashHandler : public gmp::GeckoMediaPluginService::PluginCrashCallback
+{
+public:
+  CrashHandler(const nsACString& aPluginId,
+               nsPIDOMWindow* aParentWindow,
+               nsIDocument* aDocument)
+    : gmp::GeckoMediaPluginService::PluginCrashCallback(aPluginId)
+    , mParentWindowWeakPtr(do_GetWeakReference(aParentWindow))
+    , mDocumentWeakPtr(do_GetWeakReference(aDocument))
+  {
+  }
+
+  virtual void Run(const nsACString& aPluginName, const nsAString& aPluginDumpId) override
+  {
+    PluginCrashedEventInit init;
+    init.mBubbles = true;
+    init.mCancelable = true;
+    init.mGmpPlugin = true;
+    init.mPluginDumpID = aPluginDumpId;
+    CopyUTF8toUTF16(aPluginName, init.mPluginName);
+    init.mSubmittedCrashReport = false;
+
+    // The following PluginCrashedEvent fields stay empty:
+    // init.mBrowserDumpID
+    // init.mPluginFilename
+    // TODO: Can/should we fill them?
+
+    nsCOMPtr<nsPIDOMWindow> parentWindow;
+    nsCOMPtr<nsIDocument> document;
+    if (!GetParentWindowAndDocumentIfValid(parentWindow, document)) {
+      return;
+    }
+
+    nsRefPtr<PluginCrashedEvent> event =
+      PluginCrashedEvent::Constructor(document, NS_LITERAL_STRING("PluginCrashed"), init);
+    event->SetTrusted(true);
+    event->GetInternalNSEvent()->mFlags.mOnlyChromeDispatch = true;
+
+    EventDispatcher::DispatchDOMEvent(parentWindow, nullptr, event, nullptr, nullptr);
+  }
+
+  virtual bool IsStillValid()
+  {
+    nsCOMPtr<nsPIDOMWindow> parentWindow;
+    nsCOMPtr<nsIDocument> document;
+    return GetParentWindowAndDocumentIfValid(parentWindow, document);
+  }
+
+private:
+  virtual ~CrashHandler()
+  { }
+
+  bool
+  GetParentWindowAndDocumentIfValid(nsCOMPtr<nsPIDOMWindow>& parentWindow,
+                                    nsCOMPtr<nsIDocument>& document)
+  {
+    parentWindow = do_QueryReferent(mParentWindowWeakPtr);
+    if (!parentWindow) {
+      return false;
+    }
+    document = do_QueryReferent(mDocumentWeakPtr);
+    if (!document) {
+      return false;
+    }
+    nsCOMPtr<nsIDocument> parentWindowDocument = parentWindow->GetExtantDoc();
+    if (!parentWindowDocument || document.get() != parentWindowDocument.get()) {
+      return false;
+    }
+    return true;
+  }
+
+  nsWeakPtr mParentWindowWeakPtr;
+  nsWeakPtr mDocumentWeakPtr;
+};
+
 void
-MediaKeys::OnCDMCreated(PromiseId aId, const nsACString& aNodeId)
+MediaKeys::OnCDMCreated(PromiseId aId, const nsACString& aNodeId, const nsACString& aPluginId)
 {
   nsRefPtr<Promise> promise(RetrievePromise(aId));
   if (!promise) {
     return;
   }
   mNodeId = aNodeId;
   nsRefPtr<MediaKeys> keys(this);
-  EME_LOG("MediaKeys::OnCDMCreated() resolve promise id=%d", aId);
+  EME_LOG("MediaKeys[%p]::OnCDMCreated() resolve promise id=%d", this, aId);
   promise->MaybeResolve(keys);
   if (mCreatePromiseId == aId) {
     Release();
   }
 
   MediaKeySystemAccess::NotifyObservers(mParent,
                                         mKeySystem,
                                         MediaKeySystemStatus::Cdm_created);
+
+  if (!aPluginId.IsEmpty()) {
+    // Prepare plugin crash reporter.
+    nsRefPtr<gmp::GeckoMediaPluginService> service =
+      gmp::GeckoMediaPluginService::GetGeckoMediaPluginService();
+    if (NS_WARN_IF(!service)) {
+      return;
+    }
+    if (NS_WARN_IF(!mParent)) {
+      return;
+    }
+    nsCOMPtr<nsIDocument> doc = mParent->GetExtantDoc();
+    if (NS_WARN_IF(!doc)) {
+      return;
+    }
+    service->AddPluginCrashCallback(new CrashHandler(aPluginId, mParent, doc));
+    EME_LOG("MediaKeys[%p]::OnCDMCreated() registered crash handler for pluginId '%s'",
+            this, aPluginId.Data());
+  }
 }
 
 already_AddRefed<MediaKeySession>
 MediaKeys::CreateSession(JSContext* aCx,
                          SessionType aSessionType,
                          ErrorResult& aRv)
 {
   EME_LOG("MediaKeys[%p] Creating session", this);
@@ -411,17 +508,17 @@ MediaKeys::CreateSession(JSContext* aCx,
 
 void
 MediaKeys::OnSessionLoaded(PromiseId aId, bool aSuccess)
 {
   nsRefPtr<Promise> promise(RetrievePromise(aId));
   if (!promise) {
     return;
   }
-  EME_LOG("MediaKeys::OnSessionLoaded() resolve promise id=%d", aId);
+  EME_LOG("MediaKeys[%p]::OnSessionLoaded() resolve promise id=%d", this, aId);
 
   promise->MaybeResolve(aSuccess);
 }
 
 void
 MediaKeys::OnSessionClosed(MediaKeySession* aSession)
 {
   nsAutoString id;
--- a/dom/media/eme/MediaKeys.h
+++ b/dom/media/eme/MediaKeys.h
@@ -38,17 +38,17 @@ typedef uint32_t PromiseId;
 // (ArrayBuffer or ArrayBufferView) IDL typed function argument.
 bool
 CopyArrayBufferViewOrArrayBufferData(const ArrayBufferViewOrArrayBuffer& aBufferOrView,
                                      nsTArray<uint8_t>& aOutData);
 
 // This class is used on the main thread only.
 // Note: it's addref/release is not (and can't be) thread safe!
 class MediaKeys final : public nsISupports,
-                            public nsWrapperCache
+                        public nsWrapperCache
 {
   ~MediaKeys();
 
 public:
   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
   NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(MediaKeys)
 
   MediaKeys(nsPIDOMWindow* aParentWindow, const nsAString& aKeySystem);
@@ -75,17 +75,18 @@ public:
 
   already_AddRefed<MediaKeySession> GetSession(const nsAString& aSessionId);
 
   // Removes and returns MediaKeySession from the set of sessions awaiting
   // their sessionId to be assigned.
   already_AddRefed<MediaKeySession> GetPendingSession(uint32_t aToken);
 
   // Called once a Init() operation succeeds.
-  void OnCDMCreated(PromiseId aId, const nsACString& aNodeId);
+  void OnCDMCreated(PromiseId aId,
+                    const nsACString& aNodeId, const nsACString& aPluginId);
 
   // Called once the CDM generates a sessionId while servicing a
   // MediaKeySession.generateRequest() or MediaKeySession.load() call,
   // once the sessionId of a MediaKeySession is known.
   void OnSessionIdReady(MediaKeySession* aSession);
 
   // Called once a LoadSession succeeds.
   void OnSessionLoaded(PromiseId aId, bool aSuccess);
--- a/dom/media/fmp4/MP4Reader.cpp
+++ b/dom/media/fmp4/MP4Reader.cpp
@@ -243,20 +243,20 @@ nsresult
 MP4Reader::Init(MediaDecoderReader* aCloneDonor)
 {
   MOZ_ASSERT(NS_IsMainThread(), "Must be on main thread.");
   PlatformDecoderModule::Init();
   mStream = new MP4Stream(mDecoder->GetResource());
 
   InitLayersBackendType();
 
-  mAudio.mTaskQueue = new FlushableMediaTaskQueue(GetMediaDecodeThreadPool());
+  mAudio.mTaskQueue = new FlushableMediaTaskQueue(GetMediaThreadPool());
   NS_ENSURE_TRUE(mAudio.mTaskQueue, NS_ERROR_FAILURE);
 
-  mVideo.mTaskQueue = new FlushableMediaTaskQueue(GetMediaDecodeThreadPool());
+  mVideo.mTaskQueue = new FlushableMediaTaskQueue(GetMediaThreadPool());
   NS_ENSURE_TRUE(mVideo.mTaskQueue, NS_ERROR_FAILURE);
 
   static bool sSetupPrefCache = false;
   if (!sSetupPrefCache) {
     sSetupPrefCache = true;
     Preferences::AddBoolVarCache(&sIsEMEEnabled, "media.eme.enabled", false);
     Preferences::AddBoolVarCache(&sDemuxSkipToNextKeyframe, "media.fmp4.demux-skip", true);
   }
@@ -744,16 +744,28 @@ MP4Reader::Update(TrackType aTrack)
     nsAutoPtr<MediaSample> sample(PopSample(aTrack));
 
     // Collect telemetry from h264 Annex B SPS.
     if (!mFoundSPSForTelemetry && sample && AnnexB::HasSPS(sample->mMp4Sample)) {
       nsRefPtr<ByteBuffer> extradata = AnnexB::ExtractExtraData(sample->mMp4Sample);
       mFoundSPSForTelemetry = AccumulateSPSTelemetry(extradata);
     }
 
+    if (sample && sample->mMp4Sample && sample->mMp4Sample->crypto.valid) {
+      CryptoSample& crypto = sample->mMp4Sample->crypto;
+      MOZ_ASSERT(crypto.session_ids.IsEmpty());
+
+      ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
+
+      nsRefPtr<CDMProxy> proxy = mDecoder->GetCDMProxy();
+      MOZ_ASSERT(proxy);
+
+      proxy->GetSessionIdsForKeyId(crypto.key, crypto.session_ids);
+    }
+
     if (sample) {
       decoder.mDecoder->Input(sample->mMp4Sample.forget());
       if (aTrack == kVideo) {
         a.mParsed++;
       }
     } else {
       {
         MonitorAutoLock lock(decoder.mMonitor);
--- a/dom/media/fmp4/SharedDecoderManager.cpp
+++ b/dom/media/fmp4/SharedDecoderManager.cpp
@@ -53,17 +53,17 @@ public:
       mManager->mActiveCallback->ReleaseMediaResources();
     }
   }
 
   SharedDecoderManager* mManager;
 };
 
 SharedDecoderManager::SharedDecoderManager()
-  : mTaskQueue(new FlushableMediaTaskQueue(GetMediaDecodeThreadPool()))
+  : mTaskQueue(new FlushableMediaTaskQueue(GetMediaThreadPool()))
   , mActiveProxy(nullptr)
   , mActiveCallback(nullptr)
   , mWaitForInternalDrain(false)
   , mMonitor("SharedDecoderProxy")
   , mDecoderReleasedResources(false)
 {
   MOZ_ASSERT(NS_IsMainThread()); // taskqueue must be created on main thread.
   mCallback = new SharedDecoderCallback(this);
--- a/dom/media/gmp-plugin/fake.info
+++ b/dom/media/gmp-plugin/fake.info
@@ -1,5 +1,5 @@
 Name: fake
 Description: Fake GMP Plugin
 Version: 1.0
-APIs: encode-video[h264], decode-video[h264], eme-decrypt-v6[fake]
+APIs: encode-video[h264], decode-video[h264], eme-decrypt-v7[fake]
 Libraries: dxva2.dll
--- a/dom/media/gmp/GMPChild.cpp
+++ b/dom/media/gmp/GMPChild.cpp
@@ -624,16 +624,22 @@ GMPChild::RecvPGMPVideoEncoderConstructo
 bool
 GMPChild::RecvPGMPDecryptorConstructor(PGMPDecryptorChild* aActor)
 {
   GMPDecryptorChild* child = static_cast<GMPDecryptorChild*>(aActor);
   GMPDecryptorHost* host = static_cast<GMPDecryptorHost*>(child);
 
   void* session = nullptr;
   GMPErr err = GetAPI(GMP_API_DECRYPTOR, host, &session);
+
+  if (err != GMPNoErr && !session) {
+    // XXX to remove in bug 1147692
+    err = GetAPI(GMP_API_DECRYPTOR_COMPAT, host, &session);
+  }
+
   if (err != GMPNoErr || !session) {
     return false;
   }
 
   child->Init(static_cast<GMPDecryptor*>(session));
 
   return true;
 }
--- a/dom/media/gmp/GMPDecryptorParent.cpp
+++ b/dom/media/gmp/GMPDecryptorParent.cpp
@@ -16,22 +16,29 @@ GMPDecryptorParent::GMPDecryptorParent(G
   , mShuttingDown(false)
   , mPlugin(aPlugin)
   , mCallback(nullptr)
 #ifdef DEBUG
   , mGMPThread(aPlugin->GMPThread())
 #endif
 {
   MOZ_ASSERT(mPlugin && mGMPThread);
+  mPluginId = aPlugin->GetPluginId();
 }
 
 GMPDecryptorParent::~GMPDecryptorParent()
 {
 }
 
+const nsACString&
+GMPDecryptorParent::GetPluginId() const
+{
+  return mPluginId;
+}
+
 nsresult
 GMPDecryptorParent::Init(GMPDecryptorProxyCallback* aCallback)
 {
   if (mIsOpen) {
     NS_WARNING("Trying to re-use an in-use GMP decrypter!");
     return NS_ERROR_FAILURE;
   }
   mCallback = aCallback;
@@ -135,17 +142,18 @@ GMPDecryptorParent::Decrypt(uint32_t aId
   }
 
   // Caller should ensure parameters passed in are valid.
   MOZ_ASSERT(!aBuffer.IsEmpty() && aCrypto.valid);
 
   GMPDecryptionData data(aCrypto.key,
                          aCrypto.iv,
                          aCrypto.plain_sizes,
-                         aCrypto.encrypted_sizes);
+                         aCrypto.encrypted_sizes,
+                         aCrypto.session_ids);
 
   unused << SendDecrypt(aId, aBuffer, data);
 }
 
 bool
 GMPDecryptorParent::RecvSetSessionId(const uint32_t& aCreateSessionId,
                                      const nsCString& aSessionId)
 {
--- a/dom/media/gmp/GMPDecryptorParent.h
+++ b/dom/media/gmp/GMPDecryptorParent.h
@@ -15,24 +15,27 @@ class CryptoSample;
 }
 
 namespace mozilla {
 namespace gmp {
 
 class GMPParent;
 
 class GMPDecryptorParent final : public GMPDecryptorProxy
-                                   , public PGMPDecryptorParent
+                               , public PGMPDecryptorParent
 {
 public:
   NS_INLINE_DECL_REFCOUNTING(GMPDecryptorParent)
 
   explicit GMPDecryptorParent(GMPParent *aPlugin);
 
   // GMPDecryptorProxy
+
+  virtual const nsACString& GetPluginId() const override;
+
   virtual nsresult Init(GMPDecryptorProxyCallback* aCallback) override;
 
   virtual void CreateSession(uint32_t aCreateSessionToken,
                              uint32_t aPromiseId,
                              const nsCString& aInitDataType,
                              const nsTArray<uint8_t>& aInitData,
                              GMPSessionType aSessionType) override;
 
@@ -102,16 +105,17 @@ private:
   virtual bool RecvSetCaps(const uint64_t& aCaps) override;
 
   virtual void ActorDestroy(ActorDestroyReason aWhy) override;
   virtual bool Recv__delete__() override;
 
   bool mIsOpen;
   bool mShuttingDown;
   nsRefPtr<GMPParent> mPlugin;
+  nsCString mPluginId;
   GMPDecryptorProxyCallback* mCallback;
 #ifdef DEBUG
   nsIThread* const mGMPThread;
 #endif
 };
 
 } // namespace gmp
 } // namespace mozilla
--- a/dom/media/gmp/GMPDecryptorProxy.h
+++ b/dom/media/gmp/GMPDecryptorProxy.h
@@ -54,16 +54,18 @@ public:
                          GMPErr aResult,
                          const nsTArray<uint8_t>& aDecryptedData) = 0;
 };
 
 class GMPDecryptorProxy {
 public:
   ~GMPDecryptorProxy() {}
 
+  virtual const nsACString& GetPluginId() const = 0;
+
   virtual nsresult Init(GMPDecryptorProxyCallback* aCallback) = 0;
 
   virtual void CreateSession(uint32_t aCreateSessionToken,
                              uint32_t aPromiseId,
                              const nsCString& aInitDataType,
                              const nsTArray<uint8_t>& aInitData,
                              GMPSessionType aSessionType) = 0;
 
--- a/dom/media/gmp/GMPEncryptedBufferDataImpl.cpp
+++ b/dom/media/gmp/GMPEncryptedBufferDataImpl.cpp
@@ -9,39 +9,42 @@
 namespace mozilla {
 namespace gmp {
 
 GMPEncryptedBufferDataImpl::GMPEncryptedBufferDataImpl(const CryptoSample& aCrypto)
   : mKeyId(aCrypto.key)
   , mIV(aCrypto.iv)
   , mClearBytes(aCrypto.plain_sizes)
   , mCipherBytes(aCrypto.encrypted_sizes)
+  , mSessionIdList(aCrypto.session_ids)
 {
 }
 
 GMPEncryptedBufferDataImpl::GMPEncryptedBufferDataImpl(const GMPDecryptionData& aData)
+  : mKeyId(aData.mKeyId())
+  , mIV(aData.mIV())
+  , mClearBytes(aData.mClearBytes())
+  , mCipherBytes(aData.mCipherBytes())
+  , mSessionIdList(aData.mSessionIds())
 {
-  mKeyId = aData.mKeyId();
-  mIV = aData.mIV();
-  mClearBytes = aData.mClearBytes();
-  mCipherBytes = aData.mCipherBytes();
   MOZ_ASSERT(mClearBytes.Length() == mCipherBytes.Length());
 }
 
 GMPEncryptedBufferDataImpl::~GMPEncryptedBufferDataImpl()
 {
 }
 
 void
 GMPEncryptedBufferDataImpl::RelinquishData(GMPDecryptionData& aData)
 {
   aData.mKeyId() = Move(mKeyId);
   aData.mIV() = Move(mIV);
   aData.mClearBytes() = Move(mClearBytes);
   aData.mCipherBytes() = Move(mCipherBytes);
+  mSessionIdList.RelinquishData(aData.mSessionIds());
 }
 
 const uint8_t*
 GMPEncryptedBufferDataImpl::KeyId() const
 {
   return mKeyId.Elements();
 }
 
@@ -70,19 +73,59 @@ GMPEncryptedBufferDataImpl::ClearBytes()
 }
 
 const uint32_t*
 GMPEncryptedBufferDataImpl::CipherBytes() const
 {
   return mCipherBytes.Elements();
 }
 
+const GMPStringList*
+GMPEncryptedBufferDataImpl::SessionIds() const
+{
+  return &mSessionIdList;
+}
+
 uint32_t
 GMPEncryptedBufferDataImpl::NumSubsamples() const
 {
   MOZ_ASSERT(mClearBytes.Length() == mCipherBytes.Length());
   // Return the min of the two, to ensure there's not chance of array index
   // out-of-bounds shenanigans.
   return std::min<uint32_t>(mClearBytes.Length(), mCipherBytes.Length());
 }
 
+GMPStringListImpl::GMPStringListImpl(const nsTArray<nsCString>& aStrings)
+  : mStrings(aStrings)
+{
+}
+
+const uint32_t
+GMPStringListImpl::Size() const
+{
+  return mStrings.Length();
+}
+
+void
+GMPStringListImpl::StringAt(uint32_t aIndex,
+                            const char** aOutString,
+                            uint32_t *aOutLength) const
+{
+  if (NS_WARN_IF(aIndex >= Size())) {
+    return;
+  }
+
+  *aOutString = mStrings[aIndex].BeginReading();
+  *aOutLength = mStrings[aIndex].Length();
+}
+
+void
+GMPStringListImpl::RelinquishData(nsTArray<nsCString>& aStrings)
+{
+  aStrings = Move(mStrings);
+}
+
+GMPStringListImpl::~GMPStringListImpl()
+{
+}
+
 } // namespace gmp
 } // namespace mozilla
--- a/dom/media/gmp/GMPEncryptedBufferDataImpl.h
+++ b/dom/media/gmp/GMPEncryptedBufferDataImpl.h
@@ -9,16 +9,30 @@
 #include "gmp-decryption.h"
 #include "mp4_demuxer/DecoderData.h"
 #include "nsTArray.h"
 #include "mozilla/gmp/GMPTypes.h"
 
 namespace mozilla {
 namespace gmp {
 
+class GMPStringListImpl : public GMPStringList
+{
+public:
+  explicit GMPStringListImpl(const nsTArray<nsCString>& aStrings);
+  virtual const uint32_t Size() const override;
+  virtual void StringAt(uint32_t aIndex,
+                        const char** aOutString, uint32_t *aOutLength) const override;
+  virtual ~GMPStringListImpl() override;
+  void RelinquishData(nsTArray<nsCString>& aStrings);
+
+private:
+  nsTArray<nsCString> mStrings;
+};
+
 class GMPEncryptedBufferDataImpl : public GMPEncryptedBufferMetadata {
 private:
   typedef mp4_demuxer::CryptoSample CryptoSample;
 public:
   explicit GMPEncryptedBufferDataImpl(const CryptoSample& aCrypto);
   explicit GMPEncryptedBufferDataImpl(const GMPDecryptionData& aData);
   virtual ~GMPEncryptedBufferDataImpl();
 
@@ -26,22 +40,25 @@ public:
 
   virtual const uint8_t* KeyId() const override;
   virtual uint32_t KeyIdSize() const override;
   virtual const uint8_t* IV() const override;
   virtual uint32_t IVSize() const override;
   virtual uint32_t NumSubsamples() const override;
   virtual const uint16_t* ClearBytes() const override;
   virtual const uint32_t* CipherBytes() const override;
+  virtual const GMPStringList* SessionIds() const override;
 
 private:
   nsTArray<uint8_t> mKeyId;
   nsTArray<uint8_t> mIV;
   nsTArray<uint16_t> mClearBytes;
   nsTArray<uint32_t> mCipherBytes;
+
+  GMPStringListImpl mSessionIdList;
 };
 
 class GMPBufferImpl : public GMPBuffer {
 public:
   GMPBufferImpl(uint32_t aId, const nsTArray<uint8_t>& aData)
     : mId(aId)
     , mData(aData)
   {
--- a/dom/media/gmp/GMPParent.cpp
+++ b/dom/media/gmp/GMPParent.cpp
@@ -55,16 +55,19 @@ GMPParent::GMPParent()
   , mAbnormalShutdownInProgress(false)
   , mAsyncShutdownRequired(false)
   , mAsyncShutdownInProgress(false)
 #ifdef PR_LOGGING
   , mChildPid(0)
 #endif
 {
   LOGD("GMPParent ctor");
+  // Use the parent address to identify it.
+  // We could use any unique-to-the-parent value.
+  mPluginId.AppendInt(reinterpret_cast<uint64_t>(this));
 }
 
 GMPParent::~GMPParent()
 {
   // Can't Close or Destroy the process here, since destruction is MainThread only
   MOZ_ASSERT(NS_IsMainThread());
   LOGD("GMPParent dtor");
 }
@@ -629,53 +632,60 @@ GMPParent::GetCrashID(nsString& aResult)
   }
 
   AnnotationTable notes(4);
   WriteExtraDataForMinidump(notes);
   nsCOMPtr<nsIFile> dumpFile;
   TakeMinidump(getter_AddRefs(dumpFile), nullptr);
   if (!dumpFile) {
     NS_WARNING("GMP crash without crash report");
+    aResult = mName;
+    aResult += '-';
+    AppendUTF8toUTF16(mVersion, aResult);
     return;
   }
   GetIDFromMinidump(dumpFile, aResult);
   cr->GenerateCrashReportForMinidump(dumpFile, &notes);
 }
 
 static void
-GMPNotifyObservers(nsAString& aData)
+GMPNotifyObservers(const nsACString& aPluginId, const nsACString& aPluginName, const nsAString& aPluginDumpId)
 {
   nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
   if (obs) {
-    nsString temp(aData);
-    obs->NotifyObservers(nullptr, "gmp-plugin-crash", temp.get());
+    nsString id;
+    AppendUTF8toUTF16(aPluginId, id);
+    id.Append(NS_LITERAL_STRING(" "));
+    AppendUTF8toUTF16(aPluginName, id);
+    id.Append(NS_LITERAL_STRING(" "));
+    id.Append(aPluginDumpId);
+    obs->NotifyObservers(nullptr, "gmp-plugin-crash", id.Data());
+  }
+
+  nsRefPtr<gmp::GeckoMediaPluginService> service =
+    gmp::GeckoMediaPluginService::GetGeckoMediaPluginService();
+  if (service) {
+    service->RunPluginCrashCallbacks(aPluginId, aPluginName, aPluginDumpId);
   }
 }
 #endif
 void
 GMPParent::ActorDestroy(ActorDestroyReason aWhy)
 {
   LOGD("%s: (%d)", __FUNCTION__, (int)aWhy);
 #ifdef MOZ_CRASHREPORTER
   if (AbnormalShutdown == aWhy) {
     Telemetry::Accumulate(Telemetry::SUBPROCESS_ABNORMAL_ABORT,
                           NS_LITERAL_CSTRING("gmplugin"), 1);
     nsString dumpID;
     GetCrashID(dumpID);
-    nsString id;
-    // use the parent address to identify it
-    // We could use any unique-to-the-parent value
-    id.AppendInt(reinterpret_cast<uint64_t>(this));
-    id.Append(NS_LITERAL_STRING(" "));
-    AppendUTF8toUTF16(mDisplayName, id);
-    id.Append(NS_LITERAL_STRING(" "));
-    id.Append(dumpID);
 
     // NotifyObservers is mainthread-only
-    NS_DispatchToMainThread(WrapRunnableNM(&GMPNotifyObservers, id),
+    NS_DispatchToMainThread(WrapRunnableNM(&GMPNotifyObservers,
+                                           mPluginId, mDisplayName, dumpID),
                             NS_DISPATCH_NORMAL);
   }
 #endif
   // warn us off trying to close again
   mState = GMPStateClosing;
   mAbnormalShutdownInProgress = true;
   CloseActive(false);
 
@@ -1008,16 +1018,22 @@ GMPParent::GetDisplayName() const
 }
 
 const nsCString&
 GMPParent::GetVersion() const
 {
   return mVersion;
 }
 
+const nsACString&
+GMPParent::GetPluginId() const
+{
+  return mPluginId;
+}
+
 bool
 GMPParent::RecvAsyncShutdownRequired()
 {
   LOGD("%s", __FUNCTION__);
   if (mAsyncShutdownRequired) {
     NS_WARNING("Received AsyncShutdownRequired message more than once!");
     return true;
   }
--- a/dom/media/gmp/GMPParent.h
+++ b/dom/media/gmp/GMPParent.h
@@ -50,17 +50,17 @@ public:
 enum GMPState {
   GMPStateNotLoaded,
   GMPStateLoaded,
   GMPStateUnloading,
   GMPStateClosing
 };
 
 class GMPParent final : public PGMPParent,
-                            public GMPSharedMem
+                        public GMPSharedMem
 {
 public:
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING_WITH_MAIN_THREAD_DESTRUCTION(GMPParent)
 
   GMPParent();
 
   nsresult Init(GeckoMediaPluginService *aService, nsIFile* aPluginDir);
   nsresult CloneFrom(const GMPParent* aOther);
@@ -112,16 +112,17 @@ public:
   // be shared across NodeIds.
 
   // Specifies that a GMP can only work with the specified NodeIds.
   void SetNodeId(const nsACString& aNodeId);
   const nsACString& GetNodeId() const { return mNodeId; }
 
   const nsCString& GetDisplayName() const;
   const nsCString& GetVersion() const;
+  const nsACString& GetPluginId() const;
 
   // Returns true if a plugin can be or is being used across multiple NodeIds.
   bool CanBeSharedCrossNodeIds() const;
 
   // A GMP can be used from a NodeId if it's already been set to work with
   // that NodeId, or if it's not been set to work with any NodeId and has
   // not yet been loaded (i.e. it's not shared across NodeIds).
   bool CanBeUsedFrom(const nsACString& aNodeId) const;
@@ -175,16 +176,17 @@ private:
   nsresult EnsureAsyncShutdownTimeoutSet();
 
   GMPState mState;
   nsCOMPtr<nsIFile> mDirectory; // plugin directory on disk
   nsString mName; // base name of plugin on disk, UTF-16 because used for paths
   nsCString mDisplayName; // name of plugin displayed to users
   nsCString mDescription; // description of plugin for display to users
   nsCString mVersion;
+  nsCString mPluginId;
   nsTArray<nsAutoPtr<GMPCapability>> mCapabilities;
   GMPProcessParent* mProcess;
   bool mDeleteProcessOnlyOnUnload;
   bool mAbnormalShutdownInProgress;
 
   nsTArray<nsRefPtr<GMPVideoDecoderParent>> mVideoDecoders;
   nsTArray<nsRefPtr<GMPVideoEncoderParent>> mVideoEncoders;
   nsTArray<nsRefPtr<GMPDecryptorParent>> mDecryptors;
--- a/dom/media/gmp/GMPService.cpp
+++ b/dom/media/gmp/GMPService.cpp
@@ -165,16 +165,75 @@ GeckoMediaPluginService::~GeckoMediaPlug
 
 int32_t
 GeckoMediaPluginService::AsyncShutdownTimeoutMs()
 {
   MOZ_ASSERT(sHaveSetTimeoutPrefCache);
   return sMaxAsyncShutdownWaitMs;
 }
 
+void
+GeckoMediaPluginService::RemoveObsoletePluginCrashCallbacks()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  for (size_t i = mPluginCrashCallbacks.Length(); i != 0; --i) {
+    nsRefPtr<PluginCrashCallback>& callback = mPluginCrashCallbacks[i - 1];
+    if (!callback->IsStillValid()) {
+      LOGD(("%s::%s - Removing obsolete callback for pluginId %s",
+            __CLASS__, __FUNCTION__,
+            PromiseFlatCString(callback->PluginId()).get()));
+      mPluginCrashCallbacks.RemoveElementAt(i - 1);
+    }
+  }
+}
+
+void
+GeckoMediaPluginService::AddPluginCrashCallback(
+  nsRefPtr<PluginCrashCallback> aPluginCrashCallback)
+{
+  RemoveObsoletePluginCrashCallbacks();
+  mPluginCrashCallbacks.AppendElement(aPluginCrashCallback);
+}
+
+void
+GeckoMediaPluginService::RemovePluginCrashCallbacks(const nsACString& aPluginId)
+{
+  RemoveObsoletePluginCrashCallbacks();
+  for (size_t i = mPluginCrashCallbacks.Length(); i != 0; --i) {
+    nsRefPtr<PluginCrashCallback>& callback = mPluginCrashCallbacks[i - 1];
+    if (callback->PluginId() == aPluginId) {
+      mPluginCrashCallbacks.RemoveElementAt(i - 1);
+    }
+  }
+}
+
+void
+GeckoMediaPluginService::RunPluginCrashCallbacks(const nsACString& aPluginId,
+                                                 const nsACString& aPluginName,
+                                                 const nsAString& aPluginDumpId)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  LOGD(("%s::%s(%s)", __CLASS__, __FUNCTION__, aPluginId.Data()));
+  for (size_t i = mPluginCrashCallbacks.Length(); i != 0; --i) {
+    nsRefPtr<PluginCrashCallback>& callback = mPluginCrashCallbacks[i - 1];
+    const nsACString& callbackPluginId = callback->PluginId();
+    if (!callback->IsStillValid()) {
+      LOGD(("%s::%s(%s) - Removing obsolete callback for pluginId %s",
+            __CLASS__, __FUNCTION__, aPluginId.Data(),
+            PromiseFlatCString(callback->PluginId()).get()));
+      mPluginCrashCallbacks.RemoveElementAt(i - 1);
+    } else if (callbackPluginId == aPluginId) {
+      LOGD(("%s::%s(%s) - Running #%u",
+          __CLASS__, __FUNCTION__, aPluginId.Data(), i - 1));
+      callback->Run(aPluginName, aPluginDumpId);
+      mPluginCrashCallbacks.RemoveElementAt(i - 1);
+    }
+  }
+}
+
 nsresult
 GeckoMediaPluginService::Init()
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   nsCOMPtr<nsIObserverService> obsService = mozilla::services::GetObserverService();
   MOZ_ASSERT(obsService);
   MOZ_ALWAYS_TRUE(NS_SUCCEEDED(obsService->AddObserver(this, "profile-change-teardown", false)));
@@ -538,16 +597,24 @@ GeckoMediaPluginService::GetGMPDecryptor
 
   if (mShuttingDownOnGMPThread) {
     return NS_ERROR_FAILURE;
   }
 
   nsRefPtr<GMPParent> gmp = SelectPluginForAPI(aNodeId,
                                                NS_LITERAL_CSTRING(GMP_API_DECRYPTOR),
                                                *aTags);
+
+  if (!gmp) {
+    // XXX to remove in bug 1147692
+    gmp = SelectPluginForAPI(aNodeId,
+                             NS_LITERAL_CSTRING(GMP_API_DECRYPTOR_COMPAT),
+                             *aTags);
+  }
+
   if (!gmp) {
     return NS_ERROR_FAILURE;
   }
 
   GMPDecryptorParent* ksp;
   nsresult rv = gmp->GetGMPDecryptor(&ksp);
   if (NS_FAILED(rv)) {
     return rv;
--- a/dom/media/gmp/GMPService.h
+++ b/dom/media/gmp/GMPService.h
@@ -26,17 +26,17 @@ template <class> struct already_AddRefed
 namespace mozilla {
 namespace gmp {
 
 class GMPParent;
 
 #define GMP_DEFAULT_ASYNC_SHUTDONW_TIMEOUT 3000
 
 class GeckoMediaPluginService final : public mozIGeckoMediaPluginService
-                                        , public nsIObserver
+                                    , public nsIObserver
 {
 public:
   static already_AddRefed<GeckoMediaPluginService> GetGeckoMediaPluginService();
 
   GeckoMediaPluginService();
   nsresult Init();
 
   NS_DECL_THREADSAFE_ISUPPORTS
@@ -44,16 +44,44 @@ public:
   NS_DECL_NSIOBSERVER
 
   void AsyncShutdownNeeded(GMPParent* aParent);
   void AsyncShutdownComplete(GMPParent* aParent);
   void AbortAsyncShutdown();
 
   int32_t AsyncShutdownTimeoutMs();
 
+  class PluginCrashCallback
+  {
+  public:
+    NS_INLINE_DECL_REFCOUNTING(PluginCrashCallback)
+
+    PluginCrashCallback(const nsACString& aPluginId)
+      : mPluginId(aPluginId)
+    {
+      MOZ_ASSERT(NS_IsMainThread());
+    }
+    const nsACString& PluginId() const { return mPluginId; }
+    virtual void Run(const nsACString& aPluginName, const nsAString& aPluginDumpId) = 0;
+    virtual bool IsStillValid() = 0; // False if callback has become useless.
+  protected:
+    virtual ~PluginCrashCallback()
+    {
+      MOZ_ASSERT(NS_IsMainThread());
+    }
+  private:
+    const nsCString mPluginId;
+  };
+  void RemoveObsoletePluginCrashCallbacks(); // Called from add/remove/run.
+  void AddPluginCrashCallback(nsRefPtr<PluginCrashCallback> aPluginCrashCallback);
+  void RemovePluginCrashCallbacks(const nsACString& aPluginId);
+  void RunPluginCrashCallbacks(const nsACString& aPluginId,
+                               const nsACString& aPluginName,
+                               const nsAString& aPluginDumpId);
+
 private:
   ~GeckoMediaPluginService();
 
   nsresult GMPDispatch(nsIRunnable* event, uint32_t flags = NS_DISPATCH_NORMAL);
 
   void ClearStorage();
 
   GMPParent* SelectPluginForAPI(const nsACString& aNodeId,
@@ -119,16 +147,18 @@ private:
   };
 
   Mutex mMutex; // Protects mGMPThread and mShuttingDown and mPlugins
   nsTArray<nsRefPtr<GMPParent>> mPlugins;
   nsCOMPtr<nsIThread> mGMPThread;
   bool mShuttingDown;
   bool mShuttingDownOnGMPThread;
 
+  nsTArray<nsRefPtr<PluginCrashCallback>> mPluginCrashCallbacks;
+
   // True if we've inspected MOZ_GMP_PATH on the GMP thread and loaded any
   // plugins found there into mPlugins.
   Atomic<bool> mScannedPluginOnDisk;
 
   template<typename T>
   class MainThreadOnly {
   public:
     MOZ_IMPLICIT MainThreadOnly(T aValue)
--- a/dom/media/gmp/GMPTypes.ipdlh
+++ b/dom/media/gmp/GMPTypes.ipdlh
@@ -9,16 +9,17 @@ using GMPAudioCodecType from "gmp-audio-
 namespace mozilla {
 namespace gmp {
 
 struct GMPDecryptionData {
   uint8_t[] mKeyId;
   uint8_t[] mIV;
   uint16_t[] mClearBytes;
   uint32_t[] mCipherBytes;
+  nsCString[] mSessionIds;
 };
 
 struct GMPVideoEncodedFrameData
 {
   uint32_t mEncodedWidth;
   uint32_t mEncodedHeight;
   uint64_t mTimestamp; // microseconds
   uint64_t mDuration; // microseconds
--- a/dom/media/gmp/gmp-api/gmp-decryption.h
+++ b/dom/media/gmp/gmp-api/gmp-decryption.h
@@ -14,16 +14,26 @@
 * limitations under the License.
 */
 
 #ifndef GMP_DECRYPTION_h_
 #define GMP_DECRYPTION_h_
 
 #include "gmp-platform.h"
 
+class GMPStringList {
+public:
+  virtual const uint32_t Size() const = 0;
+
+  virtual void StringAt(uint32_t aIndex,
+                        const char** aOutString, uint32_t* aOutLength) const = 0;
+
+  virtual ~GMPStringList() { }
+};
+
 class GMPEncryptedBufferMetadata {
 public:
   // Key ID to identify the decryption key.
   virtual const uint8_t* KeyId() const = 0;
 
   // Size (in bytes) of |KeyId()|.
   virtual uint32_t KeyIdSize() const = 0;
 
@@ -36,16 +46,20 @@ public:
   // Number of entries returned by ClearBytes() and CipherBytes().
   virtual uint32_t NumSubsamples() const = 0;
 
   virtual const uint16_t* ClearBytes() const = 0;
 
   virtual const uint32_t* CipherBytes() const = 0;
 
   virtual ~GMPEncryptedBufferMetadata() {}
+
+  // The set of MediaKeySession IDs associated with this decryption key in
+  // the current stream.
+  virtual const GMPStringList* SessionIds() const = 0;
 };
 
 class GMPBuffer {
 public:
   virtual uint32_t Id() const = 0;
   virtual uint8_t* Data() = 0;
   virtual uint32_t Size() const = 0;
   virtual void Resize(uint32_t aSize) = 0;
@@ -219,17 +233,20 @@ public:
 };
 
 enum GMPSessionType {
   kGMPTemporySession = 0,
   kGMPPersistentSession = 1,
   kGMPSessionInvalid = 2 // Must always be last.
 };
 
-#define GMP_API_DECRYPTOR "eme-decrypt-v6"
+#define GMP_API_DECRYPTOR "eme-decrypt-v7"
+
+// XXX remove in bug 1147692
+#define GMP_API_DECRYPTOR_COMPAT "eme-decrypt-v6"
 
 // API exposed by plugin library to manage decryption sessions.
 // When the Host requests this by calling GMPGetAPIFunc().
 //
 // API name macro: GMP_API_DECRYPTOR
 // Host API: GMPDecryptorHost
 class GMPDecryptor {
 public:
--- a/dom/media/mediasource/ContainerParser.cpp
+++ b/dom/media/mediasource/ContainerParser.cpp
@@ -333,21 +333,21 @@ public:
     }
     aStart = compositionRange.start;
     aEnd = compositionRange.end;
     MSE_DEBUG(MP4ContainerParser, "[%lld, %lld]",
               aStart, aEnd);
     return true;
   }
 
-  // Gaps of up to 20ms (marginally longer than a single frame at 60fps) are considered
+  // Gaps of up to 35ms (marginally longer than a single frame at 30fps) are considered
   // to be sequential frames.
   int64_t GetRoundingError()
   {
-    return 20000;
+    return 35000;
   }
 
 private:
   nsRefPtr<MP4Stream> mStream;
   nsAutoPtr<mp4_demuxer::MoofParser> mParser;
   Monitor mMonitor;
 };
 #endif
--- a/dom/media/mediasource/MediaSourceDecoder.cpp
+++ b/dom/media/mediasource/MediaSourceDecoder.cpp
@@ -300,16 +300,28 @@ MediaSourceDecoder::GetMozDebugReaderDat
 nsresult
 MediaSourceDecoder::SetCDMProxy(CDMProxy* aProxy)
 {
   nsresult rv = MediaDecoder::SetCDMProxy(aProxy);
   NS_ENSURE_SUCCESS(rv, rv);
   rv = mReader->SetCDMProxy(aProxy);
   NS_ENSURE_SUCCESS(rv, rv);
 
+  {
+    // The sub readers can't decrypt EME content until they have a CDMProxy,
+    // and the CDMProxy knows the capabilities of the CDM. The MediaSourceReader
+    // remains in "waiting for resources" state until then. We need to kick the
+    // reader out of waiting if the CDM gets added with known capabilities.
+    CDMCaps::AutoLock caps(aProxy->Capabilites());
+    if (!caps.AreCapsKnown()) {
+      nsCOMPtr<nsIRunnable> task(
+        NS_NewRunnableMethod(this, &MediaDecoder::NotifyWaitingForResourcesStatusChanged));
+      caps.CallOnMainThreadWhenCapsAvailable(task);
+    }
+  }
   return NS_OK;
 }
 #endif
 
 bool
 MediaSourceDecoder::IsActiveReader(MediaDecoderReader* aReader)
 {
   return mReader->IsActiveReader(aReader);
--- a/dom/media/mediasource/MediaSourceReader.cpp
+++ b/dom/media/mediasource/MediaSourceReader.cpp
@@ -92,23 +92,32 @@ MediaSourceReader::IsWaitingMediaResourc
 
 bool
 MediaSourceReader::IsWaitingOnCDMResource()
 {
 #ifdef MOZ_EME
   ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
   MOZ_ASSERT(!IsWaitingMediaResources());
 
-  for (auto& trackBuffer : mTrackBuffers) {
-    if (trackBuffer->IsWaitingOnCDMResource()) {
-      return true;
-    }
+  if (!mInfo.IsEncrypted()) {
+    return false;
   }
 
-  return mInfo.IsEncrypted() && !mCDMProxy;
+  // We'll need to wait on the CDMProxy being added, and it having received
+  // notification from the child GMP of its capabilities; whether it can
+  // decode, or whether we need to decode on our side.
+  if (!mCDMProxy) {
+    return true;
+  }
+
+  {
+    CDMCaps::AutoLock caps(mCDMProxy->Capabilites());
+    return !caps.AreCapsKnown();
+  }
+
 #else
   return false;
 #endif
 }
 
 size_t
 MediaSourceReader::SizeOfVideoQueueInFrames()
 {
--- a/dom/media/mediasource/TrackBuffer.cpp
+++ b/dom/media/mediasource/TrackBuffer.cpp
@@ -47,17 +47,17 @@ TrackBuffer::TrackBuffer(MediaSourceDeco
   , mLastStartTimestamp(0)
   , mLastTimestampOffset(0)
   , mAdjustedTimestamp(0)
   , mIsWaitingOnCDM(false)
   , mShutdown(false)
 {
   MOZ_COUNT_CTOR(TrackBuffer);
   mParser = ContainerParser::CreateForMIMEType(aType);
-  mTaskQueue = new MediaTaskQueue(GetMediaDecodeThreadPool());
+  mTaskQueue = new MediaTaskQueue(GetMediaThreadPool());
   aParentDecoder->AddTrackBuffer(this);
   mDecoderPerSegment = Preferences::GetBool("media.mediasource.decoder-per-segment", false);
   MSE_DEBUG("TrackBuffer created for parent decoder %p", aParentDecoder);
 }
 
 TrackBuffer::~TrackBuffer()
 {
   MOZ_COUNT_DTOR(TrackBuffer);
--- a/dom/media/test/crashtests/crashtests.list
+++ b/dom/media/test/crashtests/crashtests.list
@@ -73,9 +73,9 @@ load buffer-source-ended-1.html
 HTTP load media-element-source-seek-1.html
 load offline-buffer-source-ended-1.html
 load oscillator-ended-1.html
 load oscillator-ended-2.html
 load 1080986.html
 include ../../mediasource/test/crashtests/crashtests.list
 
 # This needs to run at the end to avoid leaking busted state into other tests.
-skip-if(winWidget||OSX==1010&&isDebugBuild) load 691096-1.html
+skip-if(B2G||winWidget||OSX==1010&&isDebugBuild) load 691096-1.html
--- a/dom/media/webspeech/recognition/test/mochitest.ini
+++ b/dom/media/webspeech/recognition/test/mochitest.ini
@@ -2,16 +2,17 @@
 support-files =
   head.js
   hello.ogg
   hello.ogg^headers^
   silence.ogg
   silence.ogg^headers^
 
 [test_abort.html]
+skip-if = toolkit == 'android' || toolkit == 'gonk' # bug 1037287
 [test_audio_capture_error.html]
 [test_call_start_from_end_handler.html]
 [test_nested_eventloop.html]
 skip-if = buildapp == 'mulet' || buildapp == 'b2g' || toolkit == 'android' || e10s # b2g(showmodaldialog)
 [test_preference_enable.html]
 [test_recognition_service_error.html]
 skip-if = buildapp == 'b2g' # b2g(timed out)
 [test_success_without_recognition_service.html]
--- a/dom/network/tests/test_udpsocket.html
+++ b/dom/network/tests/test_udpsocket.html
@@ -269,16 +269,17 @@ function testInvalidUDPOptions() {
 function testOpenFailed() {
   info('test for falied on open');
 
   //according to RFC5737, address block 192.0.2.0/24 should not be used in both local and public contexts
   let socket = new UDPSocket({localAddress: '192.0.2.0'});
 
   return socket.opened.then(function() {
     ok(false, 'should not resolve openedPromise while fail to bind socket');
+    socket.close();
   }).catch(function(reason) {
     is(reason.name, 'NetworkError', 'expected openedPromise to be rejected while fail to bind socket');
   });
 }
 
 function testSendBeforeOpen() {
   info('test for send before open');
 
@@ -286,17 +287,19 @@ function testSendBeforeOpen() {
 
   try {
     socket.send(HELLO_WORLD, '127.0.0.1', 9);
     ok(false, 'unexpect to send successfully');
   } catch (e) {
     ok(true, 'expected send fail before openedPromise is resolved');
   }
 
-  return socket.opened;
+  return socket.opened.then(function() {
+    socket.close();
+  });
 }
 
 function testCloseBeforeOpened() {
   info('test for close socket before opened');
 
   let socket = new UDPSocket();
   socket.opened.then(function() {
     ok(false, 'should not resolve openedPromise if it has already been closed');
--- a/dom/security/nsMixedContentBlocker.cpp
+++ b/dom/security/nsMixedContentBlocker.cpp
@@ -92,40 +92,43 @@ public:
        }
        rootDoc->SetHasMixedActiveContentLoaded(true);
 
       // Update the security UI in the tab with the allowed mixed active content
       nsCOMPtr<nsISecurityEventSink> eventSink = do_QueryInterface(docShell);
       if (eventSink) {
         // If mixed display content is loaded, make sure to include that in the state.
         if (rootDoc->GetHasMixedDisplayContentLoaded()) {
-          eventSink->OnSecurityChange(mContext, (nsIWebProgressListener::STATE_IS_BROKEN |
-          nsIWebProgressListener::STATE_LOADED_MIXED_ACTIVE_CONTENT |
-          nsIWebProgressListener::STATE_LOADED_MIXED_DISPLAY_CONTENT));
+          eventSink->OnSecurityChange(mContext,
+                                      (nsIWebProgressListener::STATE_IS_BROKEN |
+                                       nsIWebProgressListener::STATE_LOADED_MIXED_ACTIVE_CONTENT |
+                                       nsIWebProgressListener::STATE_LOADED_MIXED_DISPLAY_CONTENT));
         } else {
-          eventSink->OnSecurityChange(mContext, (nsIWebProgressListener::STATE_IS_BROKEN |
-          nsIWebProgressListener::STATE_LOADED_MIXED_ACTIVE_CONTENT));
+          eventSink->OnSecurityChange(mContext,
+                                      (nsIWebProgressListener::STATE_IS_BROKEN |
+                                       nsIWebProgressListener::STATE_LOADED_MIXED_ACTIVE_CONTENT));
         }
       }
 
     } else if (mType == eMixedDisplay) {
       // See if the pref will change here. If it will, only then do we need to call OnSecurityChange() to update the UI.
       if (rootDoc->GetHasMixedDisplayContentLoaded()) {
         return NS_OK;
       }
       rootDoc->SetHasMixedDisplayContentLoaded(true);
 
       // Update the security UI in the tab with the allowed mixed display content.
       nsCOMPtr<nsISecurityEventSink> eventSink = do_QueryInterface(docShell);
       if (eventSink) {
         // If mixed active content is loaded, make sure to include that in the state.
         if (rootDoc->GetHasMixedActiveContentLoaded()) {
-          eventSink->OnSecurityChange(mContext, (nsIWebProgressListener::STATE_IS_BROKEN |
-          nsIWebProgressListener::STATE_LOADED_MIXED_DISPLAY_CONTENT |
-          nsIWebProgressListener::STATE_LOADED_MIXED_ACTIVE_CONTENT));
+          eventSink->OnSecurityChange(mContext,
+                                      (nsIWebProgressListener::STATE_IS_BROKEN |
+                                       nsIWebProgressListener::STATE_LOADED_MIXED_DISPLAY_CONTENT |
+                                       nsIWebProgressListener::STATE_LOADED_MIXED_ACTIVE_CONTENT));
         } else {
           eventSink->OnSecurityChange(mContext, (nsIWebProgressListener::STATE_IS_BROKEN |
           nsIWebProgressListener::STATE_LOADED_MIXED_DISPLAY_CONTENT));
         }
       }
     }
 
     return NS_OK;
@@ -651,79 +654,102 @@ nsMixedContentBlocker::ShouldLoad(bool a
   }
   nsresult stateRV = securityUI->GetState(&State);
 
   // If the content is display content, and the pref says display content should be blocked, block it.
   if (sBlockMixedDisplay && classification == eMixedDisplay) {
     if (allowMixedContent) {
       LogMixedContentMessage(classification, aContentLocation, rootDoc, eUserOverride);
       *aDecision = nsIContentPolicy::ACCEPT;
-      rootDoc->SetHasMixedActiveContentLoaded(true);
-      if (!rootDoc->GetHasMixedDisplayContentLoaded() && NS_SUCCEEDED(stateRV)) {
-        rootDoc->SetHasMixedDisplayContentLoaded(true);
-        eventSink->OnSecurityChange(aRequestingContext, (State | nsIWebProgressListener::STATE_LOADED_MIXED_DISPLAY_CONTENT));
+      // See if mixed display content has already loaded on the page or if the state needs to be updated here.
+      // If mixed display hasn't loaded previously, then we need to call OnSecurityChange() to update the UI.
+      if (rootDoc->GetHasMixedDisplayContentLoaded()) {
+        return NS_OK;
+      }
+      rootDoc->SetHasMixedDisplayContentLoaded(true);
+
+      if (rootHasSecureConnection) {
+        if (rootDoc->GetHasMixedActiveContentLoaded()) {
+          // If mixed active content is loaded, make sure to include that in the state.
+          eventSink->OnSecurityChange(aRequestingContext,
+                                      (nsIWebProgressListener::STATE_IS_BROKEN |
+                                       nsIWebProgressListener::STATE_LOADED_MIXED_DISPLAY_CONTENT |
+                                       nsIWebProgressListener::STATE_LOADED_MIXED_ACTIVE_CONTENT));
+        } else {
+          eventSink->OnSecurityChange(aRequestingContext,
+                                      (nsIWebProgressListener::STATE_IS_BROKEN |
+                                       nsIWebProgressListener::STATE_LOADED_MIXED_DISPLAY_CONTENT));
+        }
+      } else {
+        // User has overriden the pref and the root is not https;
+        // mixed display content was allowed on an https subframe.
+        if (NS_SUCCEEDED(stateRV)) {
+          eventSink->OnSecurityChange(aRequestingContext, (State | nsIWebProgressListener::STATE_LOADED_MIXED_DISPLAY_CONTENT));
+         }
       }
     } else {
       *aDecision = nsIContentPolicy::REJECT_REQUEST;
       LogMixedContentMessage(classification, aContentLocation, rootDoc, eBlocked);
       if (!rootDoc->GetHasMixedDisplayContentBlocked() && NS_SUCCEEDED(stateRV)) {
         rootDoc->SetHasMixedDisplayContentBlocked(true);
         eventSink->OnSecurityChange(aRequestingContext, (State | nsIWebProgressListener::STATE_BLOCKED_MIXED_DISPLAY_CONTENT));
       }
     }
     return NS_OK;
 
   } else if (sBlockMixedScript && classification == eMixedScript) {
     // If the content is active content, and the pref says active content should be blocked, block it
     // unless the user has choosen to override the pref
     if (allowMixedContent) {
-       LogMixedContentMessage(classification, aContentLocation, rootDoc, eUserOverride);
-       *aDecision = nsIContentPolicy::ACCEPT;
-       // See if the pref will change here. If it will, only then do we need to call OnSecurityChange() to update the UI.
-       if (rootDoc->GetHasMixedActiveContentLoaded()) {
-         return NS_OK;
-       }
-       rootDoc->SetHasMixedActiveContentLoaded(true);
+      LogMixedContentMessage(classification, aContentLocation, rootDoc, eUserOverride);
+      *aDecision = nsIContentPolicy::ACCEPT;
+      // See if the state will change here. If it will, only then do we need to call OnSecurityChange() to update the UI.
+      if (rootDoc->GetHasMixedActiveContentLoaded()) {
+        return NS_OK;
+      }
+      rootDoc->SetHasMixedActiveContentLoaded(true);
 
-       if (rootHasSecureConnection) {
-         // User has decided to override the pref and the root is https, so change the Security State.
-         if (rootDoc->GetHasMixedDisplayContentLoaded()) {
-           // If mixed display content is loaded, make sure to include that in the state.
-           eventSink->OnSecurityChange(aRequestingContext, (nsIWebProgressListener::STATE_IS_BROKEN |
-           nsIWebProgressListener::STATE_LOADED_MIXED_ACTIVE_CONTENT |
-           nsIWebProgressListener::STATE_LOADED_MIXED_DISPLAY_CONTENT));
-         } else {
-           eventSink->OnSecurityChange(aRequestingContext, (nsIWebProgressListener::STATE_IS_BROKEN |
-           nsIWebProgressListener::STATE_LOADED_MIXED_ACTIVE_CONTENT));
-         }
-         return NS_OK;
-       } else {
-         // User has already overriden the pref and the root is not https;
-         // mixed content was allowed on an https subframe.
-         if (NS_SUCCEEDED(stateRV)) {
-           eventSink->OnSecurityChange(aRequestingContext, (State | nsIWebProgressListener::STATE_LOADED_MIXED_ACTIVE_CONTENT));
-         }
-         return NS_OK;
-       }
+      if (rootHasSecureConnection) {
+        // User has decided to override the pref and the root is https, so change the Security State.
+        if (rootDoc->GetHasMixedDisplayContentLoaded()) {
+          // If mixed display content is loaded, make sure to include that in the state.
+          eventSink->OnSecurityChange(aRequestingContext,
+                                      (nsIWebProgressListener::STATE_IS_BROKEN |
+                                       nsIWebProgressListener::STATE_LOADED_MIXED_ACTIVE_CONTENT |
+                                       nsIWebProgressListener::STATE_LOADED_MIXED_DISPLAY_CONTENT));
+        } else {
+          eventSink->OnSecurityChange(aRequestingContext,
+                                      (nsIWebProgressListener::STATE_IS_BROKEN |
+                                       nsIWebProgressListener::STATE_LOADED_MIXED_ACTIVE_CONTENT));
+        }
+        return NS_OK;
+      } else {
+        // User has already overriden the pref and the root is not https;
+        // mixed active content was allowed on an https subframe.
+        if (NS_SUCCEEDED(stateRV)) {
+          eventSink->OnSecurityChange(aRequestingContext, (State | nsIWebProgressListener::STATE_LOADED_MIXED_ACTIVE_CONTENT));
+        }
+        return NS_OK;
+      }
     } else {
-       //User has not overriden the pref by Disabling protection. Reject the request and update the security state.
-       *aDecision = nsIContentPolicy::REJECT_REQUEST;
-       LogMixedContentMessage(classification, aContentLocation, rootDoc, eBlocked);
-       // See if the pref will change here. If it will, only then do we need to call OnSecurityChange() to update the UI.
-       if (rootDoc->GetHasMixedActiveContentBlocked()) {
-         return NS_OK;
-       }
-       rootDoc->SetHasMixedActiveContentBlocked(true);
+      //User has not overriden the pref by Disabling protection. Reject the request and update the security state.
+      *aDecision = nsIContentPolicy::REJECT_REQUEST;
+      LogMixedContentMessage(classification, aContentLocation, rootDoc, eBlocked);
+      // See if the pref will change here. If it will, only then do we need to call OnSecurityChange() to update the UI.
+      if (rootDoc->GetHasMixedActiveContentBlocked()) {
+        return NS_OK;
+      }
+      rootDoc->SetHasMixedActiveContentBlocked(true);
 
-       // The user has not overriden the pref, so make sure they still have an option by calling eventSink
-       // which will invoke the doorhanger
-       if (NS_SUCCEEDED(stateRV)) {
-          eventSink->OnSecurityChange(aRequestingContext, (State | nsIWebProgressListener::STATE_BLOCKED_MIXED_ACTIVE_CONTENT));
-       }
-       return NS_OK;
+      // The user has not overriden the pref, so make sure they still have an option by calling eventSink
+      // which will invoke the doorhanger
+      if (NS_SUCCEEDED(stateRV)) {
+         eventSink->OnSecurityChange(aRequestingContext, (State | nsIWebProgressListener::STATE_BLOCKED_MIXED_ACTIVE_CONTENT));
+      }
+      return NS_OK;
     }
 
   } else {
     // The content is not blocked by the mixed content prefs.
 
     // Log a message that we are loading mixed content.
     LogMixedContentMessage(classification, aContentLocation, rootDoc, eUserOverride);
 
--- a/dom/system/gonk/NetworkService.js
+++ b/dom/system/gonk/NetworkService.js
@@ -18,31 +18,36 @@ const NETWORKSERVICE_CID = Components.ID
 const TOPIC_PREF_CHANGED             = "nsPref:changed";
 const TOPIC_XPCOM_SHUTDOWN           = "xpcom-shutdown";
 const PREF_NETWORK_DEBUG_ENABLED     = "network.debugging.enabled";
 
 XPCOMUtils.defineLazyServiceGetter(this, "gNetworkWorker",
                                    "@mozilla.org/network/worker;1",
                                    "nsINetworkWorker");
 
+XPCOMUtils.defineLazyServiceGetter(this, "gPACGenerator",
+                                   "@mozilla.org/pac-generator;1",
+                                   "nsIPACGenerator");
+
 // 1xx - Requested action is proceeding
 const NETD_COMMAND_PROCEEDING   = 100;
 // 2xx - Requested action has been successfully completed
 const NETD_COMMAND_OKAY         = 200;
 // 4xx - The command is accepted but the requested action didn't
 // take place.
 const NETD_COMMAND_FAIL         = 400;
 // 5xx - The command syntax or parameters error
 const NETD_COMMAND_ERROR        = 500;
 // 6xx - Unsolicited broadcasts
 const NETD_COMMAND_UNSOLICITED  = 600;
 
 const WIFI_CTRL_INTERFACE = "wl0.1";
 
-const MANUAL_PROXY_CONFIGURATION = 1;
+const PROXY_TYPE_MANUAL = Ci.nsIProtocolProxyService.PROXYCONFIG_MANUAL;
+const PROXY_TYPE_PAC = Ci.nsIProtocolProxyService.PROXYCONFIG_PAC;
 
 let debug;
 function updateDebug() {
   let debugPref = false; // set default value here.
   try {
     debugPref = debugPref || Services.prefs.getBoolPref(PREF_NETWORK_DEBUG_ENABLED);
   } catch (e) {}
 
@@ -518,40 +523,64 @@ NetworkService.prototype = {
         // Sets direct connection to internet.
         this.clearNetworkProxy();
 
         debug("No proxy support for " + network.name + " network interface.");
         return;
       }
 
       debug("Going to set proxy settings for " + network.name + " network interface.");
-      // Sets manual proxy configuration.
-      Services.prefs.setIntPref("network.proxy.type", MANUAL_PROXY_CONFIGURATION);
+
       // Do not use this proxy server for all protocols.
       Services.prefs.setBoolPref("network.proxy.share_proxy_settings", false);
       Services.prefs.setCharPref("network.proxy.http", network.httpProxyHost);
       Services.prefs.setCharPref("network.proxy.ssl", network.httpProxyHost);
       let port = network.httpProxyPort === 0 ? 8080 : network.httpProxyPort;
       Services.prefs.setIntPref("network.proxy.http_port", port);
       Services.prefs.setIntPref("network.proxy.ssl_port", port);
+
+      let usePAC;
+      try {
+        usePAC = Services.prefs.getBoolPref("network.proxy.pac_generator");
+      } catch (ex) {}
+
+      if (usePAC) {
+        Services.prefs.setCharPref("network.proxy.autoconfig_url",
+                                   gPACGenerator.generate());
+        Services.prefs.setIntPref("network.proxy.type", PROXY_TYPE_PAC);
+      } else {
+        Services.prefs.setIntPref("network.proxy.type", PROXY_TYPE_MANUAL);
+      }
     } catch(ex) {
         debug("Exception " + ex + ". Unable to set proxy setting for " +
                          network.name + " network interface.");
     }
   },
 
   clearNetworkProxy: function() {
     debug("Going to clear all network proxy.");
 
-    Services.prefs.clearUserPref("network.proxy.type");
     Services.prefs.clearUserPref("network.proxy.share_proxy_settings");
     Services.prefs.clearUserPref("network.proxy.http");
     Services.prefs.clearUserPref("network.proxy.http_port");
     Services.prefs.clearUserPref("network.proxy.ssl");
     Services.prefs.clearUserPref("network.proxy.ssl_port");
+
+    let usePAC;
+    try {
+      usePAC = Services.prefs.getBoolPref("network.proxy.pac_generator");
+    } catch (ex) {}
+
+    if (usePAC) {
+      Services.prefs.setCharPref("network.proxy.autoconfig_url",
+                                 gPACGenerator.generate());
+      Services.prefs.setIntPref("network.proxy.type", PROXY_TYPE_PAC);
+    } else {
+      Services.prefs.clearUserPref("network.proxy.type");
+    }
   },
 
   // Enable/Disable DHCP server.
   setDhcpServer: function(enabled, config, callback) {
     if (null === config) {
       config = {};
     }
 
--- a/dom/system/gonk/tests/marionette/test_data_connection_proxy.js
+++ b/dom/system/gonk/tests/marionette/test_data_connection_proxy.js
@@ -1,17 +1,18 @@
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
 MARIONETTE_TIMEOUT = 60000;
 MARIONETTE_HEAD_JS = "head.js";
 
 const HTTP_PROXY = "10.0.2.200";
 const HTTP_PROXY_PORT = "8080";
-const MANUAL_PROXY_CONFIGURATION = 1;
+const PROXY_TYPE_MANUAL = Ci.nsIProtocolProxyService.PROXYCONFIG_MANUAL;
+const PROXY_TYPE_PAC = Ci.nsIProtocolProxyService.PROXYCONFIG_PAC;
 
 // Test initial State
 function verifyInitialState() {
   log("= verifyInitialState =");
 
   // Data should be off before starting any test.
   return getSettings(SETTINGS_KEY_DATA_ENABLED)
     .then(value => {
@@ -33,30 +34,34 @@ function setTestApn() {
 }
 
 function waitForHttpProxyVerified(aShouldBeSet) {
   let TIME_OUT_VALUE = 20000;
 
   return new Promise(function(aResolve, aReject) {
     try {
       waitFor(aResolve, () => {
+        let usePAC = SpecialPowers.getBoolPref("network.proxy.pac_generator");
         let proxyType = SpecialPowers.getIntPref("network.proxy.type");
         let httpProxy = SpecialPowers.getCharPref("network.proxy.http");
         let sslProxy = SpecialPowers.getCharPref("network.proxy.ssl");
         let httpProxyPort = SpecialPowers.getIntPref("network.proxy.http_port");
         let sslProxyPort = SpecialPowers.getIntPref("network.proxy.ssl_port");
 
         if ((aShouldBeSet &&
-             proxyType == MANUAL_PROXY_CONFIGURATION &&
+             (usePAC ? proxyType == PROXY_TYPE_PAC :
+                       proxyType == PROXY_TYPE_MANUAL) &&
              httpProxy == HTTP_PROXY &&
              sslProxy == HTTP_PROXY &&
              httpProxyPort == HTTP_PROXY_PORT &&
              sslProxyPort == HTTP_PROXY_PORT) ||
-            (!aShouldBeSet && proxyType != MANUAL_PROXY_CONFIGURATION &&
-             !httpProxy && !sslProxy && !httpProxyPort && !sslProxyPort)) {
+             (!aShouldBeSet &&
+              (usePAC ? proxyType == PROXY_TYPE_PAC :
+                        proxyType != PROXY_TYPE_MANUAL) &&
+              !httpProxy && !sslProxy && !httpProxyPort && !sslProxyPort)) {
           return true;
         }
 
         return false;
       }, TIME_OUT_VALUE);
     } catch(aError) {
       // Timed out.
       aReject(aError);
--- a/dom/webidl/ScrollViewChangeEvent.webidl
+++ b/dom/webidl/ScrollViewChangeEvent.webidl
@@ -3,19 +3,15 @@
  * 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/.
  */
 
 enum ScrollState {"started", "stopped"};
 
 dictionary ScrollViewChangeEventInit : EventInit {
   ScrollState state = "started";
-  float scrollX = 0;
-  float scrollY = 0;
 };
 
 [Constructor(DOMString type, optional ScrollViewChangeEventInit eventInit),
  ChromeOnly]
 interface ScrollViewChangeEvent : Event {
   readonly attribute ScrollState state;
-  readonly attribute float scrollX;
-  readonly attribute float scrollY;
 };
--- a/dom/webidl/WebGL2RenderingContext.webidl
+++ b/dom/webidl/WebGL2RenderingContext.webidl
@@ -370,19 +370,23 @@ interface WebGL2RenderingContext : WebGL
     /* Programs and shaders */
     [WebGLHandlesContextLoss] GLint getFragDataLocation(WebGLProgram? program, DOMString name);
 
     /* Uniforms and attributes */
     void uniform1ui(WebGLUniformLocation? location, GLuint v0);
     void uniform2ui(WebGLUniformLocation? location, GLuint v0, GLuint v1);
     void uniform3ui(WebGLUniformLocation? location, GLuint v0, GLuint v1, GLuint v2);
     void uniform4ui(WebGLUniformLocation? location, GLuint v0, GLuint v1, GLuint v2, GLuint v3);
+    void uniform1uiv(WebGLUniformLocation? location, Uint32Array value);
     void uniform1uiv(WebGLUniformLocation? location, sequence<GLuint> value);
+    void uniform2uiv(WebGLUniformLocation? location, Uint32Array value);
     void uniform2uiv(WebGLUniformLocation? location, sequence<GLuint> value);
+    void uniform3uiv(WebGLUniformLocation? location, Uint32Array value);
     void uniform3uiv(WebGLUniformLocation? location, sequence<GLuint> value);
+    void uniform4uiv(WebGLUniformLocation? location, Uint32Array value);
     void uniform4uiv(WebGLUniformLocation? location, sequence<GLuint> value);
     void uniformMatrix2x3fv(WebGLUniformLocation? location, GLboolean transpose, Float32Array value);
     void uniformMatrix2x3fv(WebGLUniformLocation? location, GLboolean transpose, sequence<GLfloat> value);
     void uniformMatrix3x2fv(WebGLUniformLocation? location, GLboolean transpose, Float32Array value);
     void uniformMatrix3x2fv(WebGLUniformLocation? location, GLboolean transpose, sequence<GLfloat> value);
     void uniformMatrix2x4fv(WebGLUniformLocation? location, GLboolean transpose, Float32Array value);
     void uniformMatrix2x4fv(WebGLUniformLocation? location, GLboolean transpose, sequence<GLfloat> value);
     void uniformMatrix4x2fv(WebGLUniformLocation? location, GLboolean transpose, Float32Array value);
--- a/dom/workers/XMLHttpRequest.cpp
+++ b/dom/workers/XMLHttpRequest.cpp
@@ -965,20 +965,20 @@ Proxy::Teardown()
           new MainThreadStopSyncLoopRunnable(mWorkerPrivate,
                                              mSyncLoopTarget.forget(),
                                              false);
         if (!runnable->Dispatch(nullptr)) {
           NS_RUNTIMEABORT("We're going to hang at shutdown anyways.");
         }
       }
 
-      mWorkerPrivate = nullptr;
       mOutstandingSendCount = 0;
     }
 
+    mWorkerPrivate = nullptr;
     mXHRUpload = nullptr;
     mXHR = nullptr;
   }
 
   MOZ_ASSERT(!mWorkerPrivate);
   MOZ_ASSERT(!mSyncLoopTarget);
 }
 
new file mode 100644
--- /dev/null
+++ b/dom/workers/test/serviceworkers/fetch/context/context_test.js
@@ -0,0 +1,15 @@
+self.addEventListener("fetch", function(event) {
+  if (event.request.url.indexOf("index.html") >= 0 ||
+      event.request.url.indexOf("register.html") >= 0 ||
+      event.request.url.indexOf("unregister.html") >= 0) {
+    // Handle pass-through requests
+    event.respondWith(fetch(event.request));
+  } else if (event.request.url.indexOf("fetch.txt") >= 0) {
+    var body = event.request.context == "fetch" ?
+               "so fetch" : "so unfetch";
+    event.respondWith(new Response(body));
+  } else {
+    // Fail any request that we don't know about.
+    event.respondWith(Promise.reject());
+  }
+});
new file mode 100644
--- /dev/null
+++ b/dom/workers/test/serviceworkers/fetch/context/index.html
@@ -0,0 +1,27 @@
+<!DOCTYPE html>
+<script>
+  function ok(v, msg) {
+    window.parent.postMessage({status: "ok", result: !!v, message: msg}, "*");
+  }
+
+  function is(a, b, msg) {
+    ok(a === b, msg + ", expected '" + b + "', got '" + a + "'");
+  }
+
+  function finish() {
+    window.parent.postMessage({status: "done"}, "*");
+  }
+
+  function testFetch() {
+    return fetch("fetch.txt").then(function(r) {
+      return r.text();
+    }).then(function(body) {
+      is(body, "so fetch", "A fetch() Request should have the 'fetch' context");
+    });
+  }
+
+  testFetch()
+  .then(function() {
+    finish();
+  });
+</script>
new file mode 100644
--- /dev/null
+++ b/dom/workers/test/serviceworkers/fetch/context/register.html
@@ -0,0 +1,26 @@
+<!DOCTYPE html>
+<script>
+  function ok(v, msg) {
+    window.parent.postMessage({status: "ok", result: !!v, message: msg}, "*");
+  }
+
+  var isDone = false;
+  function done(reg) {
+    if (!isDone) {
+      ok(reg.waiting || reg.active, "Either active or waiting worker should be available.");
+      window.parent.postMessage({status: "registrationdone"}, "*");
+      isDone = true;
+    }
+  }
+
+  navigator.serviceWorker.register("context_test.js", {scope: "."})
+    .then(function(registration) {
+      if (registration.installing) {
+        registration.installing.onstatechange = function(e) {
+          done(registration);
+        };
+      } else {
+        done(registration);
+      }
+    });
+</script>
new file mode 100644
--- /dev/null
+++ b/dom/workers/test/serviceworkers/fetch/context/unregister.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<script>
+  navigator.serviceWorker.getRegistration(".").then(function(registration) {
+    registration.unregister().then(function(success) {
+      if (success) {
+        window.parent.postMessage({status: "unregistrationdone"}, "*");
+      }
+    });
+  });
+</script>
--- a/dom/workers/test/serviceworkers/mochitest.ini
+++ b/dom/workers/test/serviceworkers/mochitest.ini
@@ -21,16 +21,20 @@ support-files =
   match_all_advanced_worker.js
   worker_unregister.js
   worker_update.js
   message_posting_worker.js
   fetch/index.html
   fetch/fetch_worker_script.js
   fetch/fetch_tests.js
   fetch/deliver-gzip.sjs
+  fetch/context/index.html
+  fetch/context/register.html
+  fetch/context/unregister.html
+  fetch/context/context_test.js
   fetch/https/index.html
   fetch/https/register.html
   fetch/https/unregister.html
   fetch/https/https_test.js
   fetch/https/clonedresponse/index.html
   fetch/https/clonedresponse/register.html
   fetch/https/clonedresponse/unregister.html
   fetch/https/clonedresponse/https_test.js
@@ -69,8 +73,9 @@ support-files =
 [test_post_message_advanced.html]
 [test_post_message_source.html]
 [test_match_all_client_properties.html]
 [test_close.html]
 [test_serviceworker_interfaces.html]
 [test_serviceworker_not_sharedworker.html]
 [test_match_all_client_id.html]
 [test_sandbox_intercept.html]
+[test_request_context.html]
new file mode 100644
--- /dev/null
+++ b/dom/workers/test/serviceworkers/test_request_context.html
@@ -0,0 +1,50 @@
+<!--
+  Any copyright is dedicated to the Public Domain.
+  http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>Bug 1121157 - Test that Request objects passed to FetchEvent have the correct context</title>
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<p id="display"></p>
+<div id="content">
+<iframe></iframe>
+</div>
+<pre id="test"></pre>
+<script class="testbody" type="text/javascript">
+
+  var iframe;
+  function runTest() {
+    iframe = document.querySelector("iframe");
+    iframe.src = "/tests/dom/workers/test/serviceworkers/fetch/context/register.html";
+    window.onmessage = function(e) {
+      if (e.data.status == "ok") {
+        ok(e.data.result, e.data.message);
+      } else if (e.data.status == "registrationdone") {
+        iframe.src = "/tests/dom/workers/test/serviceworkers/fetch/context/index.html";
+      } else if (e.data.status == "done") {
+        iframe.src = "/tests/dom/workers/test/serviceworkers/fetch/context/unregister.html";
+      } else if (e.data.status == "unregistrationdone") {
+        window.onmessage = null;
+        ok(true, "Test finished successfully");
+        SimpleTest.finish();
+      }
+    };
+  }
+
+  SimpleTest.waitForExplicitFinish();
+  onload = function() {
+    SpecialPowers.pushPrefEnv({"set": [
+      ["dom.serviceWorkers.exemptFromPerDomainMax", true],
+      ["dom.serviceWorkers.enabled", true],
+      ["dom.serviceWorkers.testing.enabled", true],
+    ]}, runTest);
+  };
+</script>
+</pre>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/editor/libeditor/crashtests/1134545.html
@@ -0,0 +1,22 @@
+<!DOCTYPE html>
+<!-- saved from url=(0065)https://bug1134545.bugzilla.mozilla.org/attachment.cgi?id=8566418 -->
+<html><head><meta http-equiv="Content-Type" content="text/html; charset=windows-1252">
+<script>
+
+function boom()
+{
+    textNode = document.createTextNode(" ");
+    x.appendChild(textNode);
+    x.setAttribute('contenteditable', "true");
+    textNode.remove();
+    window.getSelection().selectAllChildren(textNode);
+    document.execCommand("increasefontsize", false, null);
+}
+
+</script>
+</head>
+<body onload="boom();">
+<div id="x" contenteditable="true"></div>
+
+
+</body></html>
\ No newline at end of file
--- a/editor/libeditor/crashtests/crashtests.list
+++ b/editor/libeditor/crashtests/crashtests.list
@@ -54,8 +54,9 @@ load 767169.html
 load 769967.xhtml
 load 768748.html
 load 768765.html
 needs-focus load 771749.html
 load 772282.html
 load 776323.html
 needs-focus load 793866.html
 load 1057677.html
+load 1134545.html
--- a/editor/libeditor/nsHTMLEditorStyle.cpp
+++ b/editor/libeditor/nsHTMLEditorStyle.cpp
@@ -1522,20 +1522,20 @@ nsHTMLEditor::RelativeFontChange( int32_
     } else {
       atom = nsGkAtoms::small;
     }
 
     // Let's see in what kind of element the selection is
     int32_t offset;
     nsCOMPtr<nsINode> selectedNode;
     GetStartNodeAndOffset(selection, getter_AddRefs(selectedNode), &offset);
-    NS_ENSURE_TRUE(selectedNode, NS_OK);
-    if (IsTextNode(selectedNode)) {
+    if (selectedNode && IsTextNode(selectedNode)) {
       selectedNode = selectedNode->GetParentNode();
     }
+    NS_ENSURE_TRUE(selectedNode, NS_OK);
     if (!CanContainTag(*selectedNode, *atom)) {
       return NS_OK;
     }
 
     // manipulating text attributes on a collapsed selection only sets state for the next text insertion
     mTypeInState->SetProp(atom, EmptyString(), EmptyString());
     return NS_OK;
   }
--- a/gfx/angle/src/common/blocklayout.cpp
+++ b/gfx/angle/src/common/blocklayout.cpp
@@ -28,16 +28,28 @@ BlockMemberInfo BlockLayoutEncoder::enco
 
     const BlockMemberInfo memberInfo(mCurrentOffset * BytesPerComponent, arrayStride * BytesPerComponent, matrixStride * BytesPerComponent, isRowMajorMatrix);
 
     advanceOffset(type, arraySize, isRowMajorMatrix, arrayStride, matrixStride);
 
     return memberInfo;
 }
 
+// static
+size_t BlockLayoutEncoder::getBlockRegister(const BlockMemberInfo &info)
+{
+    return (info.offset / BytesPerComponent) / ComponentsPerRegister;
+}
+
+// static
+size_t BlockLayoutEncoder::getBlockRegisterElement(const BlockMemberInfo &info)
+{
+    return (info.offset / BytesPerComponent) % ComponentsPerRegister;
+}
+
 void BlockLayoutEncoder::nextRegister()
 {
     mCurrentOffset = rx::roundUp<size_t>(mCurrentOffset, ComponentsPerRegister);
 }
 
 Std140BlockEncoder::Std140BlockEncoder()
 {
 }
--- a/gfx/angle/src/common/blocklayout.h
+++ b/gfx/angle/src/common/blocklayout.h
@@ -56,16 +56,19 @@ class BlockLayoutEncoder
     size_t getCurrentElement() const { return mCurrentOffset % ComponentsPerRegister; }
 
     virtual void enterAggregateType() = 0;
     virtual void exitAggregateType() = 0;
 
     static const size_t BytesPerComponent = 4u;
     static const unsigned int ComponentsPerRegister = 4u;
 
+    static size_t getBlockRegister(const BlockMemberInfo &info);
+    static size_t getBlockRegisterElement(const BlockMemberInfo &info);
+
   protected:
     size_t mCurrentOffset;
 
     void nextRegister();
 
     virtual void getBlockLayoutInfo(GLenum type, unsigned int arraySize, bool isRowMajorMatrix, int *arrayStrideOut, int *matrixStrideOut) = 0;
     virtual void advanceOffset(GLenum type, unsigned int arraySize, bool isRowMajorMatrix, int arrayStride, int matrixStride) = 0;
 };
--- a/gfx/angle/src/libGLESv2/renderer/d3d/ProgramD3D.cpp
+++ b/gfx/angle/src/libGLESv2/renderer/d3d/ProgramD3D.cpp
@@ -1389,40 +1389,38 @@ void ProgramD3D::defineUniform(GLenum sh
         // Arrays are treated as aggregate types
         if (uniform.isArray())
         {
             encoder->enterAggregateType();
         }
 
         gl::LinkedUniform *linkedUniform = getUniformByName(fullName);
 
+        // Advance the uniform offset, to track registers allocation for structs
+        sh::BlockMemberInfo blockInfo = encoder->encodeType(uniform.type, uniform.arraySize, false);
+
         if (!linkedUniform)
         {
             linkedUniform = new gl::LinkedUniform(uniform.type, uniform.precision, fullName, uniform.arraySize,
                                               -1, sh::BlockMemberInfo::getDefaultBlockInfo());
             ASSERT(linkedUniform);
-            linkedUniform->registerElement = encoder->getCurrentElement();
+            linkedUniform->registerElement = sh::HLSLBlockEncoder::getBlockRegisterElement(blockInfo);
             mUniforms.push_back(linkedUniform);
         }
 
-        ASSERT(linkedUniform->registerElement == encoder->getCurrentElement());
-
         if (shader == GL_FRAGMENT_SHADER)
         {
-            linkedUniform->psRegisterIndex = encoder->getCurrentRegister();
+            linkedUniform->psRegisterIndex = sh::HLSLBlockEncoder::getBlockRegister(blockInfo);
         }
         else if (shader == GL_VERTEX_SHADER)
         {
-            linkedUniform->vsRegisterIndex = encoder->getCurrentRegister();
+            linkedUniform->vsRegisterIndex = sh::HLSLBlockEncoder::getBlockRegister(blockInfo);
         }
         else UNREACHABLE();
 
-        // Advance the uniform offset, to track registers allocation for structs
-        encoder->encodeType(uniform.type, uniform.arraySize, false);
-
         // Arrays are treated as aggregate types
         if (uniform.isArray())
         {
             encoder->exitAggregateType();
         }
     }
 }
 
--- a/gfx/layers/FrameMetrics.h
+++ b/gfx/layers/FrameMetrics.h
@@ -676,16 +676,26 @@ private:
   // The value of GetLineScrollAmount(), for scroll frames.
   LayoutDeviceIntSize mLineScrollAmount;
 
   // The value of GetPageScrollAmount(), for scroll frames.
   LayoutDeviceIntSize mPageScrollAmount;
 
   // Whether or not the frame can be vertically scrolled with a mouse wheel.
   bool mAllowVerticalScrollWithWheel;
+
+  // WARNING!!!!
+  //
+  // When adding new fields to FrameMetrics, the following places should be
+  // updated to include them (as needed):
+  //    FrameMetrics::operator ==
+  //    AsyncPanZoomController::NotifyLayersUpdated
+  //    The ParamTraits specialization in GfxMessageUtils.h
+  //
+  // Please add new fields above this comment.
 };
 
 /**
  * This class allows us to uniquely identify a scrollable layer. The
  * mLayersId identifies the layer tree (corresponding to a child process
  * and/or tab) that the scrollable layer belongs to. The mPresShellId
  * is a temporal identifier (corresponding to the document loaded that
  * contains the scrollable layer, which may change over time). The
--- a/gfx/layers/apz/src/APZCTreeManager.cpp
+++ b/gfx/layers/apz/src/APZCTreeManager.cpp
@@ -898,17 +898,16 @@ APZCTreeManager::ProcessWheelEvent(Widge
   return status;
 }
 
 bool
 APZCTreeManager::WillHandleWheelEvent(WidgetWheelEvent* aEvent)
 {
   return EventStateManager::WheelEventIsScrollAction(aEvent) &&
          aEvent->deltaMode == nsIDOMWheelEvent::DOM_DELTA_LINE &&
-         !gfxPrefs::MouseWheelHasScrollDeltaOverride() &&
          !EventStateManager::WheelEventNeedsDeltaMultipliers(aEvent);
 }
 
 nsEventStatus
 APZCTreeManager::ReceiveInputEvent(WidgetInputEvent& aEvent,
                                    ScrollableLayerGuid* aOutTargetGuid,
                                    uint64_t* aOutInputBlockId)
 {
--- a/gfx/layers/apz/src/AsyncPanZoomController.cpp
+++ b/gfx/layers/apz/src/AsyncPanZoomController.cpp
@@ -1437,16 +1437,28 @@ AsyncPanZoomController::GetScrollWheelDe
       aOutDeltaX *= scrollAmount.width;
       aOutDeltaY *= scrollAmount.height;
       break;
     }
     default:
       MOZ_ASSERT_UNREACHABLE("unexpected scroll delta type");
   }
 
+  if (gfxPrefs::MouseWheelHasRootScrollDeltaOverride()) {
+    // Only apply delta multipliers if we're increasing the delta.
+    double hfactor = double(gfxPrefs::MouseWheelRootHScrollDeltaFactor()) / 100;
+    double vfactor = double(gfxPrefs::MouseWheelRootVScrollDeltaFactor()) / 100;
+    if (vfactor > 1.0) {
+      aOutDeltaX *= hfactor;
+    }
+    if (hfactor > 1.0) {
+      aOutDeltaY *= vfactor;
+    }
+  }
+
   LayoutDeviceIntSize pageScrollSize = mFrameMetrics.GetPageScrollAmount();
   if (Abs(aOutDeltaX) > pageScrollSize.width) {
     aOutDeltaX = (aOutDeltaX >= 0)
                  ? pageScrollSize.width
                  : -pageScrollSize.width;
   }
   if (Abs(aOutDeltaY) > pageScrollSize.height) {
     aOutDeltaY = (aOutDeltaY >= 0)
@@ -2850,16 +2862,18 @@ void AsyncPanZoomController::NotifyLayer
       mFrameMetrics.SetScrollableRect(aLayerMetrics.GetScrollableRect());
       needContentRepaint = true;
     }
     mFrameMetrics.mCompositionBounds = aLayerMetrics.mCompositionBounds;
     mFrameMetrics.SetRootCompositionSize(aLayerMetrics.GetRootCompositionSize());
     mFrameMetrics.SetPresShellResolution(aLayerMetrics.GetPresShellResolution());
     mFrameMetrics.SetCumulativeResolution(aLayerMetrics.GetCumulativeResolution());
     mFrameMetrics.SetHasScrollgrab(aLayerMetrics.GetHasScrollgrab());
+    mFrameMetrics.SetLineScrollAmount(aLayerMetrics.GetLineScrollAmount());
+    mFrameMetrics.SetPageScrollAmount(aLayerMetrics.GetPageScrollAmount());
 
     if (scrollOffsetUpdated) {
       APZC_LOG("%p updating scroll offset from %s to %s\n", this,
         ToString(mFrameMetrics.GetScrollOffset()).c_str(),
         ToString(aLayerMetrics.GetScrollOffset()).c_str());
 
       mFrameMetrics.CopyScrollInfoFrom(aLayerMetrics);
 
--- a/gfx/layers/apz/util/APZEventState.cpp
+++ b/gfx/layers/apz/util/APZEventState.cpp
@@ -231,36 +231,40 @@ APZEventState::ProcessLongTap(const nsCO
   }
 
   mContentReceivedInputBlockCallback->Run(aGuid, aInputBlockId, eventHandled);
 }
 
 void
 APZEventState::ProcessTouchEvent(const WidgetTouchEvent& aEvent,
                                  const ScrollableLayerGuid& aGuid,
-                                 uint64_t aInputBlockId)
+                                 uint64_t aInputBlockId,
+                                 nsEventStatus aApzResponse)
 {
   if (aEvent.message == NS_TOUCH_START && aEvent.touches.Length() > 0) {
     mActiveElementManager->SetTargetElement(aEvent.touches[0]->GetTarget());
   }
 
   bool isTouchPrevented = TouchManager::gPreventMouseEvents ||
       aEvent.mFlags.mMultipleActionsPrevented;
+  bool sentContentResponse = false;
   switch (aEvent.message) {
   case NS_TOUCH_START: {
     mTouchEndCancelled = false;
     if (mPendingTouchPreventedResponse) {
       // We can enter here if we get two TOUCH_STARTs in a row and didn't
       // respond to the first one. Respond to it now.
       mContentReceivedInputBlockCallback->Run(mPendingTouchPreventedGuid,
           mPendingTouchPreventedBlockId, false);
+      sentContentResponse = true;
       mPendingTouchPreventedResponse = false;
     }
     if (isTouchPrevented) {
       mContentReceivedInputBlockCallback->Run(aGuid, aInputBlockId, isTouchPrevented);
+      sentContentResponse = true;
     } else {
       mPendingTouchPreventedResponse = true;
       mPendingTouchPreventedGuid = aGuid;
       mPendingTouchPreventedBlockId = aInputBlockId;
     }
     break;
   }
 
@@ -269,23 +273,38 @@ APZEventState::ProcessTouchEvent(const W
       mTouchEndCancelled = true;
       mEndTouchIsClick = false;
     }
     // fall through
   case NS_TOUCH_CANCEL:
     mActiveElementManager->HandleTouchEndEvent(mEndTouchIsClick);
     // fall through
   case NS_TOUCH_MOVE: {
-    SendPendingTouchPreventedResponse(isTouchPrevented, aGuid);
+    sentContentResponse = SendPendingTouchPreventedResponse(isTouchPrevented, aGuid);
     break;
   }
 
   default:
     NS_WARNING("Unknown touch event type");
   }
+
+  if (sentContentResponse &&
+        aApzResponse == nsEventStatus_eConsumeDoDefault &&
+        gfxPrefs::PointerEventsEnabled()) {
+    WidgetTouchEvent cancelEvent(aEvent);
+    cancelEvent.message = NS_TOUCH_CANCEL;
+    cancelEvent.mFlags.mCancelable = false; // message != NS_TOUCH_CANCEL;
+    for (uint32_t i = 0; i < cancelEvent.touches.Length(); ++i) {
+      if (mozilla::dom::Touch* touch = cancelEvent.touches[i]) {
+        touch->convertToPointer = true;
+      }
+    }
+    nsEventStatus status;
+    cancelEvent.widget->DispatchEvent(&cancelEvent, status);
+  }
 }
 
 void
 APZEventState::ProcessWheelEvent(const WidgetWheelEvent& aEvent,
                                  const ScrollableLayerGuid& aGuid,
                                  uint64_t aInputBlockId)
 {
   mContentReceivedInputBlockCallback->Run(aGuid, aInputBlockId, aEvent.mFlags.mDefaultPrevented);
@@ -309,17 +328,17 @@ APZEventState::ProcessAPZStateChange(con
     if (scrollbarMediator) {
       scrollbarMediator->ScrollbarActivityStarted();
     }
 
     if (aDocument && mActiveAPZTransforms == 0) {
       nsCOMPtr<nsIDocShell> docshell(aDocument->GetDocShell());
       if (docshell && sf) {
         nsDocShell* nsdocshell = static_cast<nsDocShell*>(docshell.get());
-        nsdocshell->NotifyAsyncPanZoomStarted(sf->GetScrollPositionCSSPixels());
+        nsdocshell->NotifyAsyncPanZoomStarted();
       }
     }
     mActiveAPZTransforms++;
     break;
   }
   case APZStateChange::TransformEnd:
   {
     mActiveAPZTransforms--;
@@ -331,17 +350,17 @@ APZEventState::ProcessAPZStateChange(con
     if (scrollbarMediator) {
       scrollbarMediator->ScrollbarActivityStopped();
     }
 
     if (aDocument && mActiveAPZTransforms == 0) {
       nsCOMPtr<nsIDocShell> docshell(aDocument->GetDocShell());
       if (docshell && sf) {
         nsDocShell* nsdocshell = static_cast<nsDocShell*>(docshell.get());
-        nsdocshell->NotifyAsyncPanZoomStopped(sf->GetScrollPositionCSSPixels());
+        nsdocshell->NotifyAsyncPanZoomStopped();
       }
     }
     break;
   }
   case APZStateChange::StartTouch:
   {
     mActiveElementManager->HandleTouchStart(aArg);
     break;
@@ -359,26 +378,28 @@ APZEventState::ProcessAPZStateChange(con
   }
   default:
     // APZStateChange has a 'sentinel' value, and the compiler complains
     // if an enumerator is not handled and there is no 'default' case.
     break;
   }
 }
 
-void
+bool
 APZEventState::SendPendingTouchPreventedResponse(bool aPreventDefault,
                                                  const ScrollableLayerGuid& aGuid)
 {
   if (mPendingTouchPreventedResponse) {
     MOZ_ASSERT(aGuid == mPendingTouchPreventedGuid);
     mContentReceivedInputBlockCallback->Run(mPendingTouchPreventedGuid,
         mPendingTouchPreventedBlockId, aPreventDefault);
     mPendingTouchPreventedResponse = false;
+    return true;
   }
+  return false;
 }
 
 already_AddRefed<nsIWidget>
 APZEventState::GetWidget() const
 {
   nsCOMPtr<nsIWidget> result = do_QueryReferent(mWidget);
   return result.forget();
 }
--- a/gfx/layers/apz/util/APZEventState.h
+++ b/gfx/layers/apz/util/APZEventState.h
@@ -56,27 +56,28 @@ public:
   void ProcessLongTap(const nsCOMPtr<nsIDOMWindowUtils>& aUtils,
                       const CSSPoint& aPoint,
                       Modifiers aModifiers,
                       const ScrollableLayerGuid& aGuid,
                       uint64_t aInputBlockId,
                       float aPresShellResolution);
   void ProcessTouchEvent(const WidgetTouchEvent& aEvent,
                          const ScrollableLayerGuid& aGuid,
-                         uint64_t aInputBlockId);
+                         uint64_t aInputBlockId,
+                         nsEventStatus aApzResponse);
   void ProcessWheelEvent(const WidgetWheelEvent& aEvent,
                          const ScrollableLayerGuid& aGuid,
                          uint64_t aInputBlockId);
   void ProcessAPZStateChange(const nsCOMPtr<nsIDocument>& aDocument,
                              ViewID aViewId,
                              APZStateChange aChange,
                              int aArg);
 private:
   ~APZEventState();
-  void SendPendingTouchPreventedResponse(bool aPreventDefault,
+  bool SendPendingTouchPreventedResponse(bool aPreventDefault,
                                          const ScrollableLayerGuid& aGuid);
   already_AddRefed<nsIWidget> GetWidget() const;
 private:
   nsWeakPtr mWidget;
   nsRefPtr<ActiveElementManager> mActiveElementManager;
   nsRefPtr<ContentReceivedInputBlockCallback> mContentReceivedInputBlockCallback;
   bool mPendingTouchPreventedResponse;
   ScrollableLayerGuid mPendingTouchPreventedGuid;
--- a/gfx/layers/client/TextureClient.cpp
+++ b/gfx/layers/client/TextureClient.cpp
@@ -61,36 +61,55 @@
 
 namespace mozilla {
 namespace layers {
 
 using namespace mozilla::ipc;
 using namespace mozilla::gl;
 using namespace mozilla::gfx;
 
+struct ReleaseKeepAlive : public nsRunnable
+{
+  NS_IMETHOD Run()
+  {
+    mKeep = nullptr;
+    return NS_OK;
+  }
+
+  UniquePtr<KeepAlive> mKeep;
+};
+
 /**
  * TextureChild is the content-side incarnation of the PTexture IPDL actor.
  *
  * TextureChild is used to synchronize a texture client and its corresponding
  * TextureHost if needed (a TextureClient that is not shared with the compositor
  * does not have a TextureChild)
  *
  * During the deallocation phase, a TextureChild may hold its recently destroyed
  * TextureClient's data until the compositor side confirmed that it is safe to
  * deallocte or recycle the it.
  */
 class TextureChild final : public PTextureChild
 {
-  ~TextureChild() {}
+  ~TextureChild()
+  {
+    if (mKeep && mMainThreadOnly && !NS_IsMainThread()) {
+      nsRefPtr<ReleaseKeepAlive> release = new ReleaseKeepAlive();
+      release->mKeep = Move(mKeep);
+      NS_DispatchToMainThread(release);
+    }
+  }
 public:
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(TextureChild)
 
   TextureChild()
   : mForwarder(nullptr)
   , mTextureClient(nullptr)
+  , mMainThreadOnly(false)
   , mIPCOpen(false)
   {
   }
 
   bool Recv__delete__() override;
 
   bool RecvCompositorRecycle() override
   {
@@ -130,16 +149,17 @@ private:
     mIPCOpen = false;
     Release();
   }
 
   RefPtr<CompositableForwarder> mForwarder;
   RefPtr<TextureClient> mWaitForRecycle;
   TextureClient* mTextureClient;
   UniquePtr<KeepAlive> mKeep;
+  bool mMainThreadOnly;
   bool mIPCOpen;
 
   friend class TextureClient;
 };
 
 bool
 TextureChild::Recv__delete__()
 {
@@ -490,21 +510,22 @@ TextureClient::TextureClient(ISurfaceAll
 
 TextureClient::~TextureClient()
 {
   // All the destruction code that may lead to virtual method calls must
   // be in Finalize() which is called just before the destructor.
 }
 
 void
-TextureClient::KeepUntilFullDeallocation(UniquePtr<KeepAlive> aKeep)
+TextureClient::KeepUntilFullDeallocation(UniquePtr<KeepAlive> aKeep, bool aMainThreadOnly)
 {
   MOZ_ASSERT(mActor);
   MOZ_ASSERT(!mActor->mKeep);
   mActor->mKeep = Move(aKeep);
+  mActor->mMainThreadOnly = aMainThreadOnly;
 }
 
 void TextureClient::ForceRemove(bool sync)
 {
   if (mValid && mActor) {
     if (sync || GetFlags() & TextureFlags::DEALLOCATE_CLIENT) {
       MOZ_PERFORMANCE_WARNING("gfx", "TextureClient/Host pair requires synchronous deallocation");
       if (mActor->IPCOpen()) {
--- a/gfx/layers/client/TextureClient.h
+++ b/gfx/layers/client/TextureClient.h
@@ -398,17 +398,17 @@ public:
   bool IsAddedToCompositableClient() const { return mAddedToCompositableClient; }
 
   /**
    * kee the passed object alive until the IPDL actor is destroyed. This can
    * help avoid race conditions in some cases.
    * It's a temporary hack to ensure that DXGI textures don't get destroyed
    * between serialization and deserialization.
    */
-  void KeepUntilFullDeallocation(UniquePtr<KeepAlive> aKeep);
+  void KeepUntilFullDeallocation(UniquePtr<KeepAlive> aKeep, bool aMainThreadOnly = false);
 
   /**
    * Create and init the TextureChild/Parent IPDL actor pair.
    *
    * Should be called only once per TextureClient.
    */
   bool InitIPDLActor(CompositableForwarder* aForwarder);
 
--- a/gfx/layers/d3d11/CompositorD3D11.cpp
+++ b/gfx/layers/d3d11/CompositorD3D11.cpp
@@ -1340,26 +1340,30 @@ CompositorD3D11::CreateShaders()
   return true;
 }
 
 bool
 CompositorD3D11::UpdateConstantBuffers()
 {
   HRESULT hr;
   D3D11_MAPPED_SUBRESOURCE resource;
+  resource.pData = nullptr;
 
   hr = mContext->Map(mAttachments->mVSConstantBuffer, 0, D3D11_MAP_WRITE_DISCARD, 0, &resource);
-  if (Failed(hr)) {
+  if (Failed(hr) || !resource.pData) {
+    gfxCriticalError() << "Failed to map VSConstantBuffer. Result: " << hr;
     return false;
   }
   *(VertexShaderConstants*)resource.pData = mVSConstants;
   mContext->Unmap(mAttachments->mVSConstantBuffer, 0);
+  resource.pData = nullptr;
 
   hr = mContext->Map(mAttachments->mPSConstantBuffer, 0, D3D11_MAP_WRITE_DISCARD, 0, &resource);
-  if (Failed(hr)) {
+  if (Failed(hr) || !resource.pData) {
+    gfxCriticalError() << "Failed to map PSConstantBuffer. Result: " << hr;
     return false;
   }
   *(PixelShaderConstants*)resource.pData = mPSConstants;
   mContext->Unmap(mAttachments->mPSConstantBuffer, 0);
 
   ID3D11Buffer *buffer = mAttachments->mVSConstantBuffer;
 
   mContext->VSSetConstantBuffers(0, 1, &buffer);
--- a/gfx/layers/d3d11/TextureD3D11.cpp
+++ b/gfx/layers/d3d11/TextureD3D11.cpp
@@ -179,19 +179,19 @@ TextureClientD3D11::TextureClientD3D11(I
   , mNeedsClear(false)
   , mNeedsClearWhite(false)
 {}
 
 TextureClientD3D11::~TextureClientD3D11()
 {
   if (mActor) {
     if (mTexture) {
-      KeepUntilFullDeallocation(MakeUnique<TKeepAlive<ID3D10Texture2D>>(mTexture10));
+      KeepUntilFullDeallocation(MakeUnique<TKeepAlive<ID3D11Texture2D>>(mTexture));
     } else if (mTexture10) {
-      KeepUntilFullDeallocation(MakeUnique<TKeepAlive<ID3D11Texture2D>>(mTexture));
+      KeepUntilFullDeallocation(MakeUnique<TKeepAlive<ID3D10Texture2D>>(mTexture10));
     }
   }
 #ifdef DEBUG
   // An Azure DrawTarget needs to be locked when it gets nullptr'ed as this is
   // when it calls EndDraw. This EndDraw should not execute anything so it
   // shouldn't -really- need the lock but the debug layer chokes on this.
   if (mDrawTarget) {
     MOZ_ASSERT(!mIsLocked);
@@ -524,17 +524,17 @@ public:
 
 protected:
   RefPtr<IUnknown> mTextures[3];
 };
 
 DXGIYCbCrTextureClient::~DXGIYCbCrTextureClient()
 {
   if (mHoldRefs[0] && mActor) {
-    KeepUntilFullDeallocation(MakeUnique<YCbCrKeepAliveD3D11>(mHoldRefs));
+    KeepUntilFullDeallocation(MakeUnique<YCbCrKeepAliveD3D11>(mHoldRefs), true);
   }
   MOZ_COUNT_DTOR(DXGIYCbCrTextureClient);
 }
 
 bool
 DXGIYCbCrTextureClient::Lock(OpenMode)
 {
   MOZ_ASSERT(!mIsLocked);
--- a/gfx/layers/ipc/SharedBufferManagerParent.cpp
+++ b/gfx/layers/ipc/SharedBufferManagerParent.cpp
@@ -208,17 +208,22 @@ PSharedBufferManagerParent* SharedBuffer
 bool SharedBufferManagerParent::RecvAllocateGrallocBuffer(const IntSize& aSize, const uint32_t& aFormat, const uint32_t& aUsage, mozilla::layers::MaybeMagicGrallocBufferHandle* aHandle)
 {
 #ifdef MOZ_HAVE_SURFACEDESCRIPTORGRALLOC
 
   *aHandle = null_t();
 
   if (aFormat == 0 || aUsage == 0) {
     printf_stderr("SharedBufferManagerParent::RecvAllocateGrallocBuffer -- format and usage must be non-zero");
-    return true;
+    return false;
+  }
+
+  if (aSize.width <= 0 || aSize.height <= 0) {
+    printf_stderr("SharedBufferManagerParent::RecvAllocateGrallocBuffer -- requested gralloc buffer size is invalid");
+    return false;
   }
 
   // If the requested size is too big (i.e. exceeds the commonly used max GL texture size)
   // then we risk OOMing the parent process. It's better to just deny the allocation and
   // kill the child process, which is what the following code does.
   // TODO: actually use GL_MAX_TEXTURE_SIZE instead of hardcoding 4096
   if (aSize.width > 4096 || aSize.height > 4096) {
     printf_stderr("SharedBufferManagerParent::RecvAllocateGrallocBuffer -- requested gralloc buffer is too big.");
--- a/gfx/thebes/gfxHarfBuzzShaper.cpp
+++ b/gfx/thebes/gfxHarfBuzzShaper.cpp
@@ -350,25 +350,32 @@ gfxHarfBuzzShaper::GetGlyphVOrigin(hb_co
         bool emptyGlyf;
         const Glyf *glyf = FindGlyf(aGlyph, &emptyGlyf);
         if (glyf) {
             if (emptyGlyf) {
                 *aY = 0;
                 return;
             }
 
-            if (aGlyph >= uint32_t(mNumLongVMetrics)) {
-                aGlyph = mNumLongVMetrics - 1;
-            }
             const GlyphMetrics* metrics =
                 reinterpret_cast<const GlyphMetrics*>
                     (hb_blob_get_data(mVmtxTable, nullptr));
+            int16_t lsb;
+            if (aGlyph < hb_codepoint_t(mNumLongVMetrics)) {
+                // Glyph is covered by the first (advance & sidebearing) array
+                lsb = int16_t(metrics->metrics[aGlyph].lsb);
+            } else {
+                // Glyph is covered by the second (sidebearing-only) array
+                const AutoSwap_PRInt16* sidebearings =
+                    reinterpret_cast<const AutoSwap_PRInt16*>
+                        (&metrics->metrics[mNumLongVMetrics]);
+                lsb = int16_t(sidebearings[aGlyph - mNumLongVMetrics]);
+            }
             *aY = -FloatToFixed(mFont->FUnitsToDevUnitsFactor() *
-                                (int16_t(metrics->metrics[aGlyph].lsb) +
-                                 int16_t(glyf->yMax)));
+                                (lsb + int16_t(glyf->yMax)));
             return;
         } else {
             // XXX TODO: not a truetype font; need to get glyph extents
             // via some other API?
             // For now, fall through to default code below.
         }
     }
 
@@ -377,18 +384,21 @@ gfxHarfBuzzShaper::GetGlyphVOrigin(hb_co
     gfxFontEntry::AutoTable hheaTable(GetFont()->GetFontEntry(),
                                       TRUETYPE_TAG('h','h','e','a'));
     if (hheaTable) {
         uint32_t len;
         const MetricsHeader* hhea =
             reinterpret_cast<const MetricsHeader*>(hb_blob_get_data(hheaTable,
                                                                     &len));
         if (len >= sizeof(MetricsHeader)) {
-            *aY = -FloatToFixed(GetFont()->FUnitsToDevUnitsFactor() *
-                                int16_t(hhea->ascender));
+            // divide up the default advance we're using (1em) in proportion
+            // to ascender:descender from the hhea table
+            int16_t a = int16_t(hhea->ascender);
+            int16_t d = int16_t(hhea->descender);
+            *aY = -FloatToFixed(GetFont()->GetAdjustedSize() * a / (a - d));
             return;
         }
     }
 
     NS_NOTREACHED("we shouldn't be here!");
     *aY = -FloatToFixed(GetFont()->GetAdjustedSize() / 2);
 }
 
@@ -1266,21 +1276,32 @@ gfxHarfBuzzShaper::InitializeVertical()
     gfxFontEntry::AutoTable vheaTable(entry, TRUETYPE_TAG('v','h','e','a'));
     if (vheaTable) {
         uint32_t len;
         const MetricsHeader* vhea =
             reinterpret_cast<const MetricsHeader*>
             (hb_blob_get_data(vheaTable, &len));
         if (len >= sizeof(MetricsHeader)) {
             mNumLongVMetrics = vhea->numOfLongMetrics;
-            if (mNumLongVMetrics > 0 &&
+            gfxFontEntry::AutoTable
+                maxpTable(entry, TRUETYPE_TAG('m','a','x','p'));
+            int numGlyphs = -1; // invalid if we fail to read 'maxp'
+            if (maxpTable &&
+                hb_blob_get_length(maxpTable) >= sizeof(MaxpTableHeader)) {
+                const MaxpTableHeader* maxp =
+                    reinterpret_cast<const MaxpTableHeader*>
+                    (hb_blob_get_data(maxpTable, nullptr));
+                numGlyphs = uint16_t(maxp->numGlyphs);
+            }
+            if (mNumLongVMetrics > 0 && mNumLongVMetrics <= numGlyphs &&
                 int16_t(vhea->metricDataFormat) == 0) {
                 mVmtxTable = entry->GetFontTable(TRUETYPE_TAG('v','m','t','x'));
                 if (mVmtxTable && hb_blob_get_length(mVmtxTable) <
-                    mNumLongVMetrics * sizeof(LongMetric)) {
+                    mNumLongVMetrics * sizeof(LongMetric) +
+                    (numGlyphs - mNumLongVMetrics) * sizeof(int16_t)) {
                     // metrics table is not large enough for the claimed
                     // number of entries: invalid, do not use.
                     hb_blob_destroy(mVmtxTable);
                     mVmtxTable = nullptr;
                 }
             }
         }
     }
--- a/gfx/thebes/gfxPlatform.cpp
+++ b/gfx/thebes/gfxPlatform.cpp
@@ -2325,43 +2325,53 @@ gfxPlatform::IsInLayoutAsapMode()
   // There are 2 modes of ASAP mode.
   // 1 is that the refresh driver and compositor are in lock step
   // the second is that the compositor goes ASAP and the refresh driver
   // goes at whatever the configurated rate is. This only checks the version
   // talos uses, which is the refresh driver and compositor are in lockstep.
   return Preferences::GetInt("layout.frame_rate", -1) == 0;
 }
 
+static nsString
+DetectBadApzWheelInputPrefs()
+{
+  static const char *sBadMultiplierPrefs[] = {
+    "mousewheel.default.delta_multiplier_x",
+    "mousewheel.with_alt.delta_multiplier_x",
+    "mousewheel.with_control.delta_multiplier_x",
+    "mousewheel.with_meta.delta_multiplier_x",
+    "mousewheel.with_shift.delta_multiplier_x",
+    "mousewheel.with_win.delta_multiplier_x",
+    "mousewheel.with_alt.delta_multiplier_y",
+    "mousewheel.with_control.delta_multiplier_y",
+    "mousewheel.with_meta.delta_multiplier_y",
+    "mousewheel.with_shift.delta_multiplier_y",
+    "mousewheel.with_win.delta_multiplier_y",
+  };
+
+  nsString badPref;
+  for (size_t i = 0; i < MOZ_ARRAY_LENGTH(sBadMultiplierPrefs); i++) {
+    if (Preferences::GetInt(sBadMultiplierPrefs[i], 100) != 100) {
+      badPref.AssignASCII(sBadMultiplierPrefs[i]);
+      break;
+    }
+  }
+
+  return badPref;
+}
+
 void
 gfxPlatform::GetApzSupportInfo(mozilla::widget::InfoObject& aObj)
 {
+  if (!gfxPrefs::AsyncPanZoomEnabled()) {
+    return;
+  }
+
   if (SupportsApzWheelInput()) {
-    static const char *sBadPrefs[] = {
-      "mousewheel.system_scroll_override_on_root_content.enabled",
-      "mousewheel.default.delta_multiplier_x",
-      "mousewheel.with_alt.delta_multiplier_x",
-      "mousewheel.with_alt.delta_multiplier_x",
-      "mousewheel.with_control.delta_multiplier_x",
-      "mousewheel.with_meta.delta_multiplier_x",
-      "mousewheel.with_shift.delta_multiplier_x",
-      "mousewheel.with_win.delta_multiplier_x",
-      "mousewheel.with_alt.delta_multiplier_y",
-      "mousewheel.with_control.delta_multiplier_y",
-      "mousewheel.with_meta.delta_multiplier_y",
-      "mousewheel.with_shift.delta_multiplier_y",
-      "mousewheel.with_win.delta_multiplier_y",
-    };
-
-    nsString badPref;
-    for (size_t i = 0; i < MOZ_ARRAY_LENGTH(sBadPrefs); i++) {
-      if (Preferences::HasUserValue(sBadPrefs[i])) {
-        badPref.AssignASCII(sBadPrefs[i]);
-        break;
-      }
-    }
+    nsString badPref = DetectBadApzWheelInputPrefs();
 
     aObj.DefineProperty("ApzWheelInput", 1);
     if (badPref.Length()) {
       aObj.DefineProperty("ApzWheelInputWarning", badPref);
     }
   }
 
   if (SupportsApzTouchInput()) {
--- a/gfx/thebes/gfxPlatform.h
+++ b/gfx/thebes/gfxPlatform.h
@@ -162,17 +162,18 @@ GetBackendName(mozilla::gfx::BackendType
 enum class DeviceResetReason
 {
   OK = 0,
   HUNG,
   REMOVED,
   RESET,
   DRIVER_ERROR,
   INVALID_CALL,
-  OUT_OF_MEMORY
+  OUT_OF_MEMORY,
+  UNKNOWN
 };
 
 class gfxPlatform {
     friend class SRGBOverrideObserver;
 
 public:
     typedef mozilla::gfx::Color Color;
     typedef mozilla::gfx::DataSourceSurface DataSourceSurface;
--- a/gfx/thebes/gfxPrefs.h
+++ b/gfx/thebes/gfxPrefs.h
@@ -181,16 +181,17 @@ private:
   DECL_GFX_PREF(Live, "apz.x_skate_size_multiplier",           APZXSkateSizeMultiplier, float, 1.5f);
   DECL_GFX_PREF(Live, "apz.x_stationary_size_multiplier",      APZXStationarySizeMultiplier, float, 3.0f);
   DECL_GFX_PREF(Live, "apz.y_skate_size_multiplier",           APZYSkateSizeMultiplier, float, 2.5f);
   DECL_GFX_PREF(Live, "apz.y_stationary_size_multiplier",      APZYStationarySizeMultiplier, float, 3.5f);
   DECL_GFX_PREF(Live, "apz.zoom_animation_duration_ms",        APZZoomAnimationDuration, int32_t, 250);
 
   DECL_GFX_PREF(Once, "dom.vr.enabled",                        VREnabled, bool, false);
   DECL_GFX_PREF(Once, "dom.vr.add-test-devices",               VRAddTestDevices, int32_t, 1);
+  DECL_GFX_PREF(Live, "dom.w3c_pointer_events.enabled",        PointerEventsEnabled, bool, false);
 
   DECL_GFX_PREF(Once, "gfx.android.rgb16.force",               AndroidRGB16Force, bool, false);
 #if defined(ANDROID)
   DECL_GFX_PREF(Once, "gfx.apitrace.enabled",                  UseApitrace, bool, false);
 #endif
   DECL_GFX_PREF(Live, "gfx.canvas.auto_accelerate.min_calls",  CanvasAutoAccelerateMinCalls, int32_t, 4);
   DECL_GFX_PREF(Live, "gfx.canvas.auto_accelerate.min_frames", CanvasAutoAccelerateMinFrames, int32_t, 30);
   DECL_GFX_PREF(Live, "gfx.canvas.auto_accelerate.min_seconds", CanvasAutoAccelerateMinSeconds, float, 5.0f);
@@ -330,18 +331,22 @@ private:
   DECL_GFX_PREF(Live, "layout.event-regions.enabled",          LayoutEventRegionsEnabled, bool, false);
   DECL_GFX_PREF(Once, "layout.frame_rate",                     LayoutFrameRate, int32_t, -1);
   DECL_GFX_PREF(Once, "layout.paint_rects_separately",         LayoutPaintRectsSeparately, bool, true);
 
   // This and code dependent on it should be removed once containerless scrolling looks stable.
   DECL_GFX_PREF(Once, "layout.scroll.root-frame-containers",   LayoutUseContainersForRootFrames, bool, true);
 
   // This affects whether events will be routed through APZ or not.
-  DECL_GFX_PREF(Once, "mousewheel.system_scroll_override_on_root_content.enabled",
-                                                               MouseWheelHasScrollDeltaOverride, bool, false);
+  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",
+                                                               MouseWheelRootHScrollDeltaFactor, int32_t, 100);
+  DECL_GFX_PREF(Live, "mousewheel.system_scroll_override_on_root_content.vertical.factor",
+                                                               MouseWheelRootVScrollDeltaFactor, int32_t, 100);
   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/gfx/thebes/gfxWindowsPlatform.cpp
+++ b/gfx/thebes/gfxWindowsPlatform.cpp
@@ -407,16 +407,17 @@ public:
   }
 };
 
 NS_IMPL_ISUPPORTS(D3D9SharedTextureReporter, nsIMemoryReporter)
 
 gfxWindowsPlatform::gfxWindowsPlatform()
   : mD3D11DeviceInitialized(false)
   , mIsWARP(false)
+  , mCanInitMediaDevice(false)
 {
     mUseClearTypeForDownloadableFonts = UNINITIALIZED_VALUE;
     mUseClearTypeAlways = UNINITIALIZED_VALUE;
 
     mUsingGDIFonts = false;
 
     /* 
      * Initialize COM 
@@ -1137,60 +1138,68 @@ gfxWindowsPlatform::IsFontFormatSupporte
     if (aFormatFlags != 0) {
         return false;
     }
 
     // no format hint set, need to look at data
     return true;
 }
 
+static DeviceResetReason HResultToResetReason(HRESULT hr)
+{
+  switch (hr) {
+  case DXGI_ERROR_DEVICE_HUNG:
+    return DeviceResetReason::HUNG;
+  case DXGI_ERROR_DEVICE_REMOVED:
+    return DeviceResetReason::REMOVED;
+  case DXGI_ERROR_DEVICE_RESET:
+    return DeviceResetReason::RESET;
+  case DXGI_ERROR_DRIVER_INTERNAL_ERROR:
+    return DeviceResetReason::DRIVER_ERROR;
+  case DXGI_ERROR_INVALID_CALL:
+    return DeviceResetReason::INVALID_CALL;
+  case E_OUTOFMEMORY:
+    return DeviceResetReason::OUT_OF_MEMORY;
+  default:
+    MOZ_ASSERT(false);
+  }
+  return DeviceResetReason::UNKNOWN;
+}
+
 bool
 gfxWindowsPlatform::DidRenderingDeviceReset(DeviceResetReason* aResetReason)
 {
   if (aResetReason) {
     *aResetReason = DeviceResetReason::OK;
   }
 
   if (mD3D11Device) {
     HRESULT hr = mD3D11Device->GetDeviceRemovedReason();
     if (hr != S_OK) {
       if (aResetReason) {
-        switch (hr) {
-        case DXGI_ERROR_DEVICE_HUNG:
-          *aResetReason = DeviceResetReason::HUNG;
-          break;
-        case DXGI_ERROR_DEVICE_REMOVED:
-          *aResetReason = DeviceResetReason::REMOVED;
-          break;
-        case DXGI_ERROR_DEVICE_RESET:
-          *aResetReason = DeviceResetReason::RESET;
-          break;
-        case DXGI_ERROR_DRIVER_INTERNAL_ERROR:
-          *aResetReason = DeviceResetReason::DRIVER_ERROR;
-          break;
-        case DXGI_ERROR_INVALID_CALL:
-          *aResetReason = DeviceResetReason::INVALID_CALL;
-          break;
-        case E_OUTOFMEMORY:
-          *aResetReason = DeviceResetReason::OUT_OF_MEMORY;
-          break;
-        default:
-          MOZ_ASSERT(false);
-        }
+        *aResetReason = HResultToResetReason(hr);
       }
       return true;
     }
   }
   if (mD3D11ContentDevice) {
-    if (mD3D11ContentDevice->GetDeviceRemovedReason() != S_OK) {
+    HRESULT hr = mD3D11ContentDevice->GetDeviceRemovedReason();
+    if (hr != S_OK) {
+      if (aResetReason) {
+        *aResetReason = HResultToResetReason(hr);
+      }
       return true;
     }
   }
   if (GetD3D10Device()) {
-    if (GetD3D10Device()->GetDeviceRemovedReason() != S_OK) {
+    HRESULT hr = GetD3D10Device()->GetDeviceRemovedReason();
+    if (hr != S_OK) {
+      if (aResetReason) {
+        *aResetReason = HResultToResetReason(hr);
+      }
       return true;
     }
   }
   return false;
 }
 
 void
 gfxWindowsPlatform::GetPlatformCMSOutputProfile(void* &mem, size_t &mem_size)