merge mozilla-inbound to mozilla-central a=merge
authorCarsten "Tomcat" Book <cbook@mozilla.com>
Tue, 17 Mar 2015 11:36:52 +0100
changeset 234008 008b3f65a7e0471abb6887812ee56dd87cc528df
parent 233867 a194e80230901e7e3644c3c16b3356c5c3099316 (current diff)
parent 234007 6aaf86f2c5d85e358e01a4af8310e775c684f79d (diff)
child 234043 15e49729e473ded4ea41c16a6b7dab0143674703
child 234055 655bf846ef73fe592f6c87b61dc075348615b676
child 234073 c912d22bfe3deae3d34ef8d7710654c418df3362
push id28426
push usercbook@mozilla.com
push dateTue, 17 Mar 2015 10:46:54 +0000
treeherdermozilla-central@008b3f65a7e0 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone39.0a1
first release with
nightly linux32
008b3f65a7e0 / 39.0a1 / 20150317073344 / files
nightly linux64
008b3f65a7e0 / 39.0a1 / 20150317073344 / files
nightly mac
008b3f65a7e0 / 39.0a1 / 20150317073344 / files
nightly win32
008b3f65a7e0 / 39.0a1 / 20150317073344 / files
nightly win64
008b3f65a7e0 / 39.0a1 / 20150317073344 / files
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
releases
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
merge mozilla-inbound to mozilla-central a=merge
browser/app/profile/firefox.js
dom/media/MediaDecoderStateMachineScheduler.cpp
dom/media/MediaDecoderStateMachineScheduler.h
dom/tests/mochitest/fetch/test_fetch_basic_worker.html
dom/tests/mochitest/fetch/test_headers_mainthread.js
dom/tests/mochitest/fetch/worker_test_fetch_basic.js
dom/tests/mochitest/fetch/worker_test_fetch_basic_http.js
dom/tests/mochitest/fetch/worker_test_fetch_cors.js
dom/workers/test/fetch/mochitest.ini
dom/workers/test/fetch/test_interfaces.html
dom/workers/test/fetch/test_request.html
dom/workers/test/fetch/test_response.html
dom/workers/test/fetch/worker_interfaces.js
dom/workers/test/fetch/worker_test_request.js
dom/workers/test/fetch/worker_test_response.js
mobile/android/chrome/content/browser.js
toolkit/components/passwordmgr/moz.build
--- a/accessible/base/nsAccessibilityService.cpp
+++ b/accessible/base/nsAccessibilityService.cpp
@@ -131,89 +131,89 @@ MustBeAccessible(nsIContent* aContent, D
     return aDocument->IsDependentID(id);
 
   return false;
 }
 
 ////////////////////////////////////////////////////////////////////////////////
 // Accessible constructors
 
-Accessible*
+static Accessible*
 New_HTMLLink(nsIContent* aContent, Accessible* aContext)
 {
   // Only some roles truly enjoy life as HTMLLinkAccessibles, for details
   // see closed bug 494807.
   nsRoleMapEntry* roleMapEntry = aria::GetRoleMap(aContent);
   if (roleMapEntry && roleMapEntry->role != roles::NOTHING &&
       roleMapEntry->role != roles::LINK) {
     return new HyperTextAccessibleWrap(aContent, aContext->Document());
   }
 
   return new HTMLLinkAccessible(aContent, aContext->Document());
 }
 
-Accessible* New_HyperText(nsIContent* aContent, Accessible* aContext)
+static Accessible* New_HyperText(nsIContent* aContent, Accessible* aContext)
   { return new HyperTextAccessibleWrap(aContent, aContext->Document()); }
 
-Accessible* New_HTMLFigcaption(nsIContent* aContent, Accessible* aContext)
+static Accessible* New_HTMLFigcaption(nsIContent* aContent, Accessible* aContext)
   { return new HTMLFigcaptionAccessible(aContent, aContext->Document()); }
 
-Accessible* New_HTMLFigure(nsIContent* aContent, Accessible* aContext)
+static Accessible* New_HTMLFigure(nsIContent* aContent, Accessible* aContext)
   { return new HTMLFigureAccessible(aContent, aContext->Document()); }
 
-Accessible* New_HTMLLegend(nsIContent* aContent, Accessible* aContext)
+static Accessible* New_HTMLLegend(nsIContent* aContent, Accessible* aContext)
   { return new HTMLLegendAccessible(aContent, aContext->Document()); }
 
-Accessible* New_HTMLOption(nsIContent* aContent, Accessible* aContext)
+static Accessible* New_HTMLOption(nsIContent* aContent, Accessible* aContext)
   { return new HTMLSelectOptionAccessible(aContent, aContext->Document()); }
 
-Accessible* New_HTMLOptgroup(nsIContent* aContent, Accessible* aContext)
+static Accessible* New_HTMLOptgroup(nsIContent* aContent, Accessible* aContext)
   { return new HTMLSelectOptGroupAccessible(aContent, aContext->Document()); }
 
-Accessible* New_HTMLList(nsIContent* aContent, Accessible* aContext)
+static Accessible* New_HTMLList(nsIContent* aContent, Accessible* aContext)
   { return new HTMLListAccessible(aContent, aContext->Document()); }
 
-Accessible*
+static Accessible*
 New_HTMLListitem(nsIContent* aContent, Accessible* aContext)
 {
   // If list item is a child of accessible list then create an accessible for
   // it unconditionally by tag name. nsBlockFrame creates the list item
   // accessible for other elements styled as list items.
   if (aContext->IsList() && aContext->GetContent() == aContent->GetParent())
     return new HTMLLIAccessible(aContent, aContext->Document());
 
   return nullptr;
 }
 
-Accessible*
+static Accessible*
 New_HTMLDefinition(nsIContent* aContent, Accessible* aContext)
 {
   if (aContext->IsList())
     return new HyperTextAccessibleWrap(aContent, aContext->Document());
   return nullptr;
 }
 
-Accessible* New_HTMLLabel(nsIContent* aContent, Accessible* aContext)
+static Accessible* New_HTMLLabel(nsIContent* aContent, Accessible* aContext)
   { return new HTMLLabelAccessible(aContent, aContext->Document()); }
 
-Accessible* New_HTMLOutput(nsIContent* aContent, Accessible* aContext)
+static Accessible* New_HTMLOutput(nsIContent* aContent, Accessible* aContext)
   { return new HTMLOutputAccessible(aContent, aContext->Document()); }
 
-Accessible* New_HTMLProgress(nsIContent* aContent, Accessible* aContext)
+static Accessible* New_HTMLProgress(nsIContent* aContent, Accessible* aContext)
   { return new HTMLProgressMeterAccessible(aContent, aContext->Document()); }
 
-Accessible*
+static Accessible*
 New_HTMLTableHeaderCell(nsIContent* aContent, Accessible* aContext)
 {
   if (aContext->IsTableRow() && aContext->GetContent() == aContent->GetParent())
     return new HTMLTableHeaderCellAccessibleWrap(aContent, aContext->Document());
   return nullptr;
 }
 
-Accessible*
+static Accessible*
 New_HTMLTableHeaderCellIfScope(nsIContent* aContent, Accessible* aContext)
 {
   if (aContext->IsTableRow() && aContext->GetContent() == aContent->GetParent() &&
       aContent->HasAttr(kNameSpaceID_None, nsGkAtoms::scope))
     return new HTMLTableHeaderCellAccessibleWrap(aContent, aContext->Document());
   return nullptr;
 }
 
--- a/accessible/ipc/ProxyAccessible.h
+++ b/accessible/ipc/ProxyAccessible.h
@@ -37,16 +37,19 @@ public:
     MOZ_ASSERT(!mWrapper);
   }
 
   void AddChildAt(uint32_t aIdx, ProxyAccessible* aChild)
   { mChildren.InsertElementAt(aIdx, aChild); }
 
   uint32_t ChildrenCount() const { return mChildren.Length(); }
   ProxyAccessible* ChildAt(uint32_t aIdx) const { return mChildren[aIdx]; }
+
+  // XXX evaluate if this is fast enough.
+  size_t IndexInParent() const { return mParent->mChildren.IndexOf(this); }
   bool MustPruneChildren() const;
 
   void Shutdown();
 
   void SetChildDoc(DocAccessibleParent*);
 
   /**
    * Remove The given child.
--- a/accessible/jsat/Utils.jsm
+++ b/accessible/jsat/Utils.jsm
@@ -296,46 +296,46 @@ this.Utils = { // jshint ignore:line
   getVirtualCursor: function getVirtualCursor(aDocument) {
     let doc = (aDocument instanceof Ci.nsIAccessible) ? aDocument :
       this.AccRetrieval.getAccessibleFor(aDocument);
 
     return doc.QueryInterface(Ci.nsIAccessibleDocument).virtualCursor;
   },
 
   getContentResolution: function _getContentResolution(aAccessible) {
-    let resX = { value: 1 }, resY = { value: 1 };
+    let res = { value: 1 };
     aAccessible.document.window.QueryInterface(
       Ci.nsIInterfaceRequestor).getInterface(
-      Ci.nsIDOMWindowUtils).getResolution(resX, resY);
-    return [resX.value, resY.value];
+      Ci.nsIDOMWindowUtils).getResolution(res);
+    return res.value;
   },
 
   getBounds: function getBounds(aAccessible, aPreserveContentScale) {
     let objX = {}, objY = {}, objW = {}, objH = {};
     aAccessible.getBounds(objX, objY, objW, objH);
 
-    let [scaleX, scaleY] = aPreserveContentScale ? [1, 1] :
+    let scale = aPreserveContentScale ? 1 :
       this.getContentResolution(aAccessible);
 
     return new Rect(objX.value, objY.value, objW.value, objH.value).scale(
-      scaleX, scaleY);
+      scale, scale);
   },
 
   getTextBounds: function getTextBounds(aAccessible, aStart, aEnd,
                                         aPreserveContentScale) {
     let accText = aAccessible.QueryInterface(Ci.nsIAccessibleText);
     let objX = {}, objY = {}, objW = {}, objH = {};
     accText.getRangeExtents(aStart, aEnd, objX, objY, objW, objH,
       Ci.nsIAccessibleCoordinateType.COORDTYPE_SCREEN_RELATIVE);
 
-    let [scaleX, scaleY] = aPreserveContentScale ? [1, 1] :
+    let scale = aPreserveContentScale ? 1 :
       this.getContentResolution(aAccessible);
 
     return new Rect(objX.value, objY.value, objW.value, objH.value).scale(
-      scaleX, scaleY);
+      scale, scale);
   },
 
   /**
    * Get current display DPI.
    */
   get dpi() {
     delete this.dpi;
     this.dpi = this.winUtils.displayDPI;
--- a/accessible/tests/mochitest/common.js
+++ b/accessible/tests/mochitest/common.js
@@ -365,17 +365,18 @@ function testAccessibleTree(aAccOrElmOrI
     accTree = {
       role: nsIAccessibleRole[roleName],
       children: accTree[key]
     };
   }
 
   // Test accessible properties.
   for (var prop in accTree) {
-    var msg = "Wrong value of property '" + prop + "' for " + prettyName(acc) + ".";
+    var msg = "Wrong value of property '" + prop + "' for " +
+               prettyName(acc) + ".";
 
     switch (prop) {
     case "actions": {
       testActionNames(acc, accTree.actions);
       break;
     }
 
     case "attributes":
@@ -446,20 +447,39 @@ function testAccessibleTree(aAccOrElmOrI
     }
   }
 
   // Test children.
   if ("children" in accTree && accTree["children"] instanceof Array) {
     var children = acc.children;
     var childCount = children.length;
 
-    is(childCount, accTree.children.length,
-       "Different amount of expected children of " + prettyName(acc) + ".");
 
-    if (accTree.children.length == childCount) {
+    if (accTree.children.length != childCount) {
+      for (var i = 0; i < Math.max(accTree.children.length, childCount); i++) {
+        var accChild;
+        try {
+          accChild = children.queryElementAt(i, nsIAccessible);
+          if (!accTree.children[i]) {
+            ok(false, prettyName(acc) + " has an extra child at index " + i +
+              " : " + prettyName(accChild));
+          }
+          if (accChild.role !== accTree.children[i].role) {
+            ok(false, prettyName(accTree) + " and " + prettyName(acc) +
+              " have different children at index " + i + " : " +
+              prettyName(accTree.children[i]) + ", " + prettyName(accChild));
+          }
+          info("Matching " + prettyName(accTree) + " and " + prettyName(acc) +
+               " child at index " + i + " : " + prettyName(accChild));
+        } catch (e) {
+          ok(false, prettyName(accTree) + " has an extra child at index " + i +
+             " : " + prettyName(accTree.children[i]));
+        }
+      }
+    } else {
       if (aFlags & kSkipTreeFullCheck) {
         for (var i = 0; i < childCount; i++) {
           var child = children.queryElementAt(i, nsIAccessible);
           testAccessibleTree(child, accTree.children[i], aFlags);
         }
         return;
       }
 
@@ -532,17 +552,18 @@ function isAccessibleInCache(aNodeOrId)
  * @param  aNodeOrId  [in] the DOM node identifier for the defunct accessible
  */
 function testDefunctAccessible(aAcc, aNodeOrId)
 {
   if (aNodeOrId)
     ok(!isAccessible(aNodeOrId),
        "Accessible for " + aNodeOrId + " wasn't properly shut down!");
 
-  var msg = " doesn't fail for shut down accessible " + prettyName(aNodeOrId) + "!";
+  var msg = " doesn't fail for shut down accessible " +
+             prettyName(aNodeOrId) + "!";
 
   // firstChild
   var success = false;
   try {
     aAcc.firstChild;
   } catch (e) {
     success = (e.result == Components.results.NS_ERROR_FAILURE)
   }
@@ -715,16 +736,20 @@ function prettyName(aIdentifier)
     msg += "]";
 
     return msg;
   }
 
   if (aIdentifier instanceof nsIDOMNode)
     return "[ " + getNodePrettyName(aIdentifier) + " ]";
 
+  if (aIdentifier && typeof aIdentifier === "object" ) {
+    return JSON.stringify(aIdentifier);
+  }
+
   return " '" + aIdentifier + "' ";
 }
 
 /**
  * Shorten a long string if it exceeds MAX_TRIM_LENGTH.
  * @param aString the string to shorten.
  * @returns the shortened string.
  */
--- a/accessible/windows/ia2/ia2Accessible.cpp
+++ b/accessible/windows/ia2/ia2Accessible.cpp
@@ -11,24 +11,27 @@
 #include "AccessibleRole.h"
 #include "AccessibleStates.h"
 
 #include "Compatibility.h"
 #include "ia2AccessibleRelation.h"
 #include "IUnknownImpl.h"
 #include "nsCoreUtils.h"
 #include "nsIAccessibleTypes.h"
+#include "mozilla/a11y/PDocAccessible.h"
 #include "Relation.h"
 
 #include "nsIPersistentProperties2.h"
 #include "nsISimpleEnumerator.h"
 
 using namespace mozilla;
 using namespace mozilla::a11y;
 
+template<typename String> static void EscapeAttributeChars(String& aStr);
+
 ////////////////////////////////////////////////////////////////////////////////
 // ia2Accessible
 ////////////////////////////////////////////////////////////////////////////////
 
 STDMETHODIMP
 ia2Accessible::QueryInterface(REFIID iid, void** ppv)
 {
   if (!ppv)
@@ -60,16 +63,25 @@ ia2Accessible::get_nRelations(long* aNRe
   if (!aNRelations)
     return E_INVALIDARG;
   *aNRelations = 0;
 
   AccessibleWrap* acc = static_cast<AccessibleWrap*>(this);
   if (acc->IsDefunct())
     return CO_E_OBJNOTCONNECTED;
 
+  if (acc->IsProxy()) {
+    // XXX evaluate performance of collecting all relation targets.
+    nsTArray<RelationType> types;
+    nsTArray<nsTArray<ProxyAccessible*>> targetSets;
+    acc->Proxy()->Relations(&types, &targetSets);
+    *aNRelations = types.Length();
+    return S_OK;
+  }
+
   for (uint32_t idx = 0; idx < ArrayLength(sRelationTypePairs); idx++) {
     if (sRelationTypePairs[idx].second == IA2_RELATION_NULL)
       continue;
 
     Relation rel = acc->RelationByType(sRelationTypePairs[idx].first);
     if (rel.Next())
       (*aNRelations)++;
   }
@@ -79,24 +91,52 @@ ia2Accessible::get_nRelations(long* aNRe
 }
 
 STDMETHODIMP
 ia2Accessible::get_relation(long aRelationIndex,
                             IAccessibleRelation** aRelation)
 {
   A11Y_TRYBLOCK_BEGIN
 
-  if (!aRelation)
+  if (!aRelation || aRelationIndex < 0)
     return E_INVALIDARG;
   *aRelation = nullptr;
 
   AccessibleWrap* acc = static_cast<AccessibleWrap*>(this);
   if (acc->IsDefunct())
     return CO_E_OBJNOTCONNECTED;
 
+  if (acc->IsProxy()) {
+    nsTArray<RelationType> types;
+    nsTArray<nsTArray<ProxyAccessible*>> targetSets;
+    acc->Proxy()->Relations(&types, &targetSets);
+
+    size_t targetSetCount = targetSets.Length();
+    for (size_t i = 0; i < targetSetCount; i++) {
+      uint32_t relTypeIdx = static_cast<uint32_t>(types[i]);
+      MOZ_ASSERT(sRelationTypePairs[relTypeIdx].first == types[i]);
+      if (sRelationTypePairs[relTypeIdx].second == IA2_RELATION_NULL)
+        continue;
+
+      if (static_cast<size_t>(aRelationIndex) == i) {
+        nsTArray<nsRefPtr<Accessible>> targets;
+        size_t targetCount = targetSets[i].Length();
+        for (size_t j = 0; j < targetCount; j++)
+          targets.AppendElement(WrapperFor(targetSets[i][j]));
+
+        nsRefPtr<ia2AccessibleRelation> rel =
+          new ia2AccessibleRelation(types[i], Move(targets));
+        rel.forget(aRelation);
+        return S_OK;
+      }
+    }
+
+    return E_INVALIDARG;
+  }
+
   long relIdx = 0;
   for (uint32_t idx = 0; idx < ArrayLength(sRelationTypePairs); idx++) {
     if (sRelationTypePairs[idx].second == IA2_RELATION_NULL)
       continue;
 
     RelationType relationType = sRelationTypePairs[idx].first;
     Relation rel = acc->RelationByType(relationType);
     nsRefPtr<ia2AccessibleRelation> ia2Relation =
@@ -118,24 +158,52 @@ ia2Accessible::get_relation(long aRelati
 
 STDMETHODIMP
 ia2Accessible::get_relations(long aMaxRelations,
                              IAccessibleRelation** aRelation,
                              long *aNRelations)
 {
   A11Y_TRYBLOCK_BEGIN
 
-  if (!aRelation || !aNRelations)
+  if (!aRelation || !aNRelations || aMaxRelations <= 0)
     return E_INVALIDARG;
   *aNRelations = 0;
 
   AccessibleWrap* acc = static_cast<AccessibleWrap*>(this);
   if (acc->IsDefunct())
     return CO_E_OBJNOTCONNECTED;
 
+  if (acc->IsProxy()) {
+    nsTArray<RelationType> types;
+    nsTArray<nsTArray<ProxyAccessible*>> targetSets;
+    acc->Proxy()->Relations(&types, &targetSets);
+
+    size_t count = std::min(targetSets.Length(),
+                            static_cast<size_t>(aMaxRelations));
+    size_t i = 0;
+    while (i < count) {
+      uint32_t relTypeIdx = static_cast<uint32_t>(types[i]);
+      if (sRelationTypePairs[relTypeIdx].second == IA2_RELATION_NULL)
+        continue;
+
+      size_t targetCount = targetSets[i].Length();
+      nsTArray<nsRefPtr<Accessible>> targets(targetCount);
+      for (size_t j = 0; j < targetCount; j++)
+        targets.AppendElement(WrapperFor(targetSets[i][j]));
+
+      nsRefPtr<ia2AccessibleRelation> rel =
+        new ia2AccessibleRelation(types[i], Move(targets));
+      rel.forget(aRelation + i);
+      i++;
+    }
+
+    *aNRelations = i;
+    return S_OK;
+  }
+
   for (uint32_t idx = 0; idx < ArrayLength(sRelationTypePairs) &&
        *aNRelations < aMaxRelations; idx++) {
     if (sRelationTypePairs[idx].second == IA2_RELATION_NULL)
       continue;
 
     RelationType relationType = sRelationTypePairs[idx].first;
     Relation rel = acc->RelationByType(relationType);
     nsRefPtr<ia2AccessibleRelation> ia2Rel =
@@ -164,31 +232,41 @@ ia2Accessible::role(long* aRole)
     return CO_E_OBJNOTCONNECTED;
 
 #define ROLE(_geckoRole, stringRole, atkRole, macRole, \
              msaaRole, ia2Role, nameRule) \
   case roles::_geckoRole: \
     *aRole = ia2Role; \
     break;
 
-  a11y::role geckoRole = acc->Role();
+  a11y::role geckoRole;
+  if (acc->IsProxy())
+    geckoRole = acc->Proxy()->Role();
+  else
+    geckoRole = acc->Role();
   switch (geckoRole) {
 #include "RoleMap.h"
     default:
       MOZ_CRASH("Unknown role.");
   };
 
 #undef ROLE
 
   // Special case, if there is a ROLE_ROW inside of a ROLE_TREE_TABLE, then call
   // the IA2 role a ROLE_OUTLINEITEM.
-  if (geckoRole == roles::ROW) {
-    Accessible* xpParent = acc->Parent();
-    if (xpParent && xpParent->Role() == roles::TREE_TABLE)
+  if (acc->IsProxy()) {
+    if (geckoRole == roles::ROW && acc->Proxy()->Parent() &&
+        acc->Proxy()->Parent()->Role() == roles::TREE_TABLE)
       *aRole = ROLE_SYSTEM_OUTLINEITEM;
+  } else {
+    if (geckoRole == roles::ROW) {
+      Accessible* xpParent = acc->Parent();
+      if (xpParent && xpParent->Role() == roles::TREE_TABLE)
+        *aRole = ROLE_SYSTEM_OUTLINEITEM;
+    }
   }
 
   return S_OK;
 
   A11Y_TRYBLOCK_END
 }
 
 STDMETHODIMP
@@ -269,17 +347,26 @@ ia2Accessible::get_states(AccessibleStat
 
   if (!aStates)
     return E_INVALIDARG;
   *aStates = 0;
 
   // XXX: bug 344674 should come with better approach that we have here.
 
   AccessibleWrap* acc = static_cast<AccessibleWrap*>(this);
-  uint64_t state = acc->State();
+  if (acc->IsDefunct()) {
+    *aStates = IA2_STATE_DEFUNCT;
+    return CO_E_OBJNOTCONNECTED;
+  }
+
+  uint64_t state;
+  if (acc->IsProxy())
+    state = acc->Proxy()->State();
+  else
+    state = acc->State();
 
   if (state & states::INVALID)
     *aStates |= IA2_STATE_INVALID_ENTRY;
   if (state & states::REQUIRED)
     *aStates |= IA2_STATE_REQUIRED;
 
   // The following IA2 states are not supported by Gecko
   // IA2_STATE_ARMED
@@ -441,17 +528,21 @@ ia2Accessible::get_indexInParent(long* a
   if (!aIndexInParent)
     return E_INVALIDARG;
   *aIndexInParent = -1;
 
   AccessibleWrap* acc = static_cast<AccessibleWrap*>(this);
   if (acc->IsDefunct())
     return CO_E_OBJNOTCONNECTED;
 
-  *aIndexInParent = acc->IndexInParent();
+  if (acc->IsProxy())
+    *aIndexInParent = acc->Proxy()->IndexInParent();
+  else
+    *aIndexInParent = acc->IndexInParent();
+
   if (*aIndexInParent == -1)
     return S_FALSE;
 
   return S_OK;
 
   A11Y_TRYBLOCK_END
 }
 
@@ -516,18 +607,39 @@ ia2Accessible::get_attributes(BSTR* aAtt
   *aAttributes = nullptr;
 
   AccessibleWrap* acc = static_cast<AccessibleWrap*>(this);
   if (acc->IsDefunct())
     return CO_E_OBJNOTCONNECTED;
 
   // The format is name:value;name:value; with \ for escaping these
   // characters ":;=,\".
-  nsCOMPtr<nsIPersistentProperties> attributes = acc->Attributes();
-  return ConvertToIA2Attributes(attributes, aAttributes);
+  if (!acc->IsProxy()) {
+    nsCOMPtr<nsIPersistentProperties> attributes = acc->Attributes();
+    return ConvertToIA2Attributes(attributes, aAttributes);
+  }
+
+  nsTArray<Attribute> attrs;
+  acc->Proxy()->Attributes(&attrs);
+  nsString attrStr;
+  size_t attrCount = attrs.Length();
+  for (size_t i = 0; i < attrCount; i++) {
+    EscapeAttributeChars(attrs[i].Name());
+    EscapeAttributeChars(attrs[i].Value());
+    AppendUTF8toUTF16(attrs[i].Name(), attrStr);
+    attrStr.Append(':');
+    attrStr.Append(attrs[i].Value());
+    attrStr.Append(';');
+  }
+
+  if (attrStr.IsEmpty())
+    return S_FALSE;
+
+  *aAttributes = ::SysAllocStringLen(attrStr.get(), attrStr.Length());
+  return *aAttributes ? S_OK : E_OUTOFMEMORY;
 
   A11Y_TRYBLOCK_END
 }
 
 ////////////////////////////////////////////////////////////////////////////////
 // IAccessible2_2
 
 STDMETHODIMP
@@ -562,17 +674,17 @@ ia2Accessible::get_accessibleWithCaret(I
 STDMETHODIMP
 ia2Accessible::get_relationTargetsOfType(BSTR aType,
                                          long aMaxTargets,
                                          IUnknown*** aTargets,
                                          long* aNTargets)
 {
   A11Y_TRYBLOCK_BEGIN
 
-  if (!aTargets || !aNTargets)
+  if (!aTargets || !aNTargets || aMaxTargets < 0)
     return E_INVALIDARG;
   *aNTargets = 0;
 
   Maybe<RelationType> relationType;
   for (uint32_t idx = 0; idx < ArrayLength(sRelationTypePairs); idx++) {
     if (wcscmp(aType, sRelationTypePairs[idx].second) == 0) {
       relationType.emplace(sRelationTypePairs[idx].first);
       break;
@@ -580,23 +692,33 @@ ia2Accessible::get_relationTargetsOfType
   }
   if (!relationType)
     return E_INVALIDARG;
 
   AccessibleWrap* acc = static_cast<AccessibleWrap*>(this);
   if (acc->IsDefunct())
     return CO_E_OBJNOTCONNECTED;
 
-  Relation rel = acc->RelationByType(*relationType);
+  nsTArray<Accessible*> targets;
+  if (acc->IsProxy()) {
+    nsTArray<ProxyAccessible*> targetProxies =
+      acc->Proxy()->RelationByType(*relationType);
 
-  nsTArray<Accessible*> targets;
-  Accessible* target = nullptr;
-  while ((target = rel.Next()) &&
-         static_cast<long>(targets.Length()) <= aMaxTargets)
-    targets.AppendElement(target);
+    size_t targetCount = aMaxTargets;
+    if (targetProxies.Length() < targetCount)
+      targetCount = targetProxies.Length();
+    for (size_t i = 0; i < targetCount; i++)
+      targets.AppendElement(WrapperFor(targetProxies[i]));
+  } else {
+    Relation rel = acc->RelationByType(*relationType);
+    Accessible* target = nullptr;
+    while ((target = rel.Next()) &&
+           static_cast<long>(targets.Length()) <= aMaxTargets)
+      targets.AppendElement(target);
+  }
 
   *aNTargets = targets.Length();
   *aTargets = static_cast<IUnknown**>(
     ::CoTaskMemAlloc(sizeof(IUnknown*) * *aNTargets));
   if (!*aTargets)
     return E_OUTOFMEMORY;
 
   for (int32_t i = 0; i < *aNTargets; i++) {
@@ -608,16 +730,28 @@ ia2Accessible::get_relationTargetsOfType
   return S_OK;
 
   A11Y_TRYBLOCK_END
 }
 
 ////////////////////////////////////////////////////////////////////////////////
 // Helpers
 
+template<typename String>
+static inline void
+EscapeAttributeChars(String& aStr)
+{
+  int32_t offset = 0;
+  static const char kCharsToEscape[] = ":;=,\\";
+  while ((offset = aStr.FindCharInSet(kCharsToEscape, offset)) != kNotFound) {
+    aStr.Insert('\\', offset);
+    offset += 2;
+  }
+}
+
 HRESULT
 ia2Accessible::ConvertToIA2Attributes(nsIPersistentProperties* aAttributes,
                                       BSTR* aIA2Attributes)
 {
   *aIA2Attributes = nullptr;
 
   // The format is name:value;name:value; with \ for escaping these
   // characters ":;=,\".
@@ -627,46 +761,36 @@ ia2Accessible::ConvertToIA2Attributes(ns
 
   nsCOMPtr<nsISimpleEnumerator> propEnum;
   aAttributes->Enumerate(getter_AddRefs(propEnum));
   if (!propEnum)
     return E_FAIL;
 
   nsAutoString strAttrs;
 
-  const char kCharsToEscape[] = ":;=,\\";
-
   bool hasMore = false;
   while (NS_SUCCEEDED(propEnum->HasMoreElements(&hasMore)) && hasMore) {
     nsCOMPtr<nsISupports> propSupports;
     propEnum->GetNext(getter_AddRefs(propSupports));
 
     nsCOMPtr<nsIPropertyElement> propElem(do_QueryInterface(propSupports));
     if (!propElem)
       return E_FAIL;
 
     nsAutoCString name;
     if (NS_FAILED(propElem->GetKey(name)))
       return E_FAIL;
 
-    int32_t offset = 0;
-    while ((offset = name.FindCharInSet(kCharsToEscape, offset)) != kNotFound) {
-      name.Insert('\\', offset);
-      offset += 2;
-    }
+    EscapeAttributeChars(name);
 
     nsAutoString value;
     if (NS_FAILED(propElem->GetValue(value)))
       return E_FAIL;
 
-    offset = 0;
-    while ((offset = value.FindCharInSet(kCharsToEscape, offset)) != kNotFound) {
-      value.Insert('\\', offset);
-      offset += 2;
-    }
+    EscapeAttributeChars(value);
 
     AppendUTF8toUTF16(name, strAttrs);
     strAttrs.Append(':');
     strAttrs.Append(value);
     strAttrs.Append(';');
   }
 
   if (strAttrs.IsEmpty())
--- a/accessible/windows/ia2/ia2AccessibleRelation.h
+++ b/accessible/windows/ia2/ia2AccessibleRelation.h
@@ -19,16 +19,20 @@
 namespace mozilla {
 namespace a11y {
 
 class ia2AccessibleRelation MOZ_FINAL : public IAccessibleRelation
 {
 public:
   ia2AccessibleRelation(RelationType aType, Relation* aRel);
 
+  ia2AccessibleRelation(RelationType aType,
+                        nsTArray<nsRefPtr<Accessible>>&& aTargets) :
+    mType(aType), mTargets(Move(aTargets)) {}
+
   // IUnknown
   DECL_IUNKNOWN
 
   // IAccessibleRelation
   virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_relationType(
       /* [retval][out] */ BSTR *relationType);
 
   virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_localizedRelationType(
--- a/accessible/windows/ia2/moz.build
+++ b/accessible/windows/ia2/moz.build
@@ -47,8 +47,10 @@ LOCAL_INCLUDES += [
 FINAL_LIBRARY = 'xul'
 
 # The midl generated code include Windows headers which defines min and max
 # macros which conflicts with std::min/max.  Suppress the macros:
 if CONFIG['OS_ARCH'] == 'WINNT':
     DEFINES['NOMINMAX'] = True
 
 FAIL_ON_WARNINGS = True
+
+include('/ipc/chromium/chromium-config.mozbuild')
--- a/b2g/app/b2g.js
+++ b/b2g/app/b2g.js
@@ -689,16 +689,19 @@ pref("javascript.options.mem.gc_max_empt
 pref("ui.showHideScrollbars", 1);
 pref("ui.useOverlayScrollbars", 1);
 pref("ui.scrollbarFadeBeginDelay", 450);
 pref("ui.scrollbarFadeDuration", 200);
 
 // Scrollbar position follows the document `dir` attribute
 pref("layout.scrollbar.side", 1);
 
+// CSS Scroll Snapping
+pref("layout.css.scroll-snap.enabled", true);
+
 // Enable the ProcessPriorityManager, and give processes with no visible
 // documents a 1s grace period before they're eligible to be marked as
 // background. Background processes that are perceivable due to playing
 // media are given a longer grace period to accomodate changing tracks, etc.
 pref("dom.ipc.processPriorityManager.enabled", true);
 pref("dom.ipc.processPriorityManager.backgroundGracePeriodMS", 1000);
 pref("dom.ipc.processPriorityManager.backgroundPerceivableGracePeriodMS", 5000);
 pref("dom.ipc.processPriorityManager.temporaryPriorityLockMS", 5000);
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -1226,17 +1226,17 @@ pref("security.sandbox.windows.log.stack
 // This pref is discussed in bug 1083344, the naming is inspired from its Windows
 // counterpart, but on Mac it's an integer which means:
 // 0 -> "no sandbox"
 // 1 -> "an imperfect sandbox designed to allow firefox to run reasonably well"
 // 2 -> "an ideal sandbox which may break many things"
 // This setting is read when the content process is started. On Mac the content
 // process is killed when all windows are closed, so a change will take effect
 // when the 1st window is opened.
-pref("security.sandbox.content.level", 0);
+pref("security.sandbox.content.level", 1);
 #endif
 
 // This pref governs whether we attempt to work around problems caused by
 // plugins using OS calls to manipulate the cursor while running out-of-
 // process.  These workarounds all involve intercepting (hooking) certain
 // OS calls in the plugin process, then arranging to make certain OS calls
 // in the browser process.  Eventually plugins will be required to use the
 // NPAPI to manipulate the cursor, and these workarounds will be removed.
--- a/browser/base/content/test/general/browser.ini
+++ b/browser/base/content/test/general/browser.ini
@@ -177,17 +177,16 @@ skip-if = buildapp == 'mulet' || e10s # 
 skip-if = buildapp == 'mulet' || e10s # Bug 1056146 - zoom tests use FullZoomHelper and break in e10s
 [browser_bug455852.js]
 skip-if = e10s
 [browser_bug460146.js]
 skip-if = e10s # Bug 866413 - PageInfo doesn't work in e10s
 [browser_bug462289.js]
 skip-if = toolkit == "cocoa" || e10s # Bug 1102017 - middle-button mousedown on selected tab2 does not activate tab - Didn't expect [object XULElement], but got it
 [browser_bug462673.js]
-skip-if = e10s # Bug 1093404 - test expects sync window opening from content and is disappointed in that expectation
 [browser_bug477014.js]
 [browser_bug479408.js]
 skip-if = buildapp == 'mulet'
 [browser_bug481560.js]
 [browser_bug484315.js]
 skip-if = e10s
 [browser_bug491431.js]
 skip-if = buildapp == 'mulet'
--- a/browser/base/content/test/general/browser_bug462673.js
+++ b/browser/base/content/test/general/browser_bug462673.js
@@ -1,61 +1,36 @@
-var runs = [
-  function (win, tabbrowser, tab) {
-    is(tabbrowser.browsers.length, 2, "test_bug462673.html has opened a second tab");
-    is(tabbrowser.selectedTab, tab.nextSibling, "dependent tab is selected");
-    tabbrowser.removeTab(tab);
-    // Closing a tab will also close its parent chrome window, but async
-    executeSoon(function() {
-      ok(win.closed, "Window is closed");
-      testComplete(win);
-    });
-  },
-  function (win, tabbrowser, tab) {
-    var newTab = tabbrowser.addTab();
-    var newBrowser = newTab.linkedBrowser;
-    tabbrowser.removeTab(tab);
-    ok(!win.closed, "Window stays open");
-    if (!win.closed) {
-      is(tabbrowser.tabContainer.childElementCount, 1, "Window has one tab");
-      is(tabbrowser.browsers.length, 1, "Window has one browser");
-      is(tabbrowser.selectedTab, newTab, "Remaining tab is selected");
-      is(tabbrowser.selectedBrowser, newBrowser, "Browser for remaining tab is selected");
-      is(tabbrowser.mTabBox.selectedPanel, newBrowser.parentNode.parentNode.parentNode.parentNode, "Panel for remaining tab is selected");
-    }
-    testComplete(win);
-  }
-];
+add_task(function* () {
+  var win = openDialog(getBrowserURL(), "_blank", "chrome,all,dialog=no");
+  yield SimpleTest.promiseFocus(win);
+
+  let tab = win.gBrowser.tabContainer.firstChild;
+  yield promiseTabLoadEvent(tab, getRootDirectory(gTestPath) + "test_bug462673.html");
+
+  is(win.gBrowser.browsers.length, 2, "test_bug462673.html has opened a second tab");
+  is(win.gBrowser.selectedTab, tab.nextSibling, "dependent tab is selected");
+  win.gBrowser.removeTab(tab);
+
+  // Closing a tab will also close its parent chrome window, but async
+  yield promiseWindowWillBeClosed(win);
+});
 
-function test() {
-  waitForExplicitFinish();
-  runOneTest();
-}
+add_task(function* () {
+  var win = openDialog(getBrowserURL(), "_blank", "chrome,all,dialog=no");
+  yield SimpleTest.promiseFocus(win);
 
-function testComplete(win) {
-  win.close();
-  if (runs.length)
-    runOneTest();
-  else
-    finish();
-}
-
-function runOneTest() {
-  var win = openDialog(getBrowserURL(), "_blank", "chrome,all,dialog=no");
+  let tab = win.gBrowser.tabContainer.firstChild;
+  yield promiseTabLoadEvent(tab, getRootDirectory(gTestPath) + "test_bug462673.html");
 
-  win.addEventListener("load", function () {
-    win.removeEventListener("load", arguments.callee, false);
-
-    var tab = win.gBrowser.tabContainer.firstChild;
-    var browser = tab.linkedBrowser;
-
-    browser.addEventListener("load", function () {
-      browser.removeEventListener("load", arguments.callee, true);
+  var newTab = win.gBrowser.addTab();
+  var newBrowser = newTab.linkedBrowser;
+  win.gBrowser.removeTab(tab);
+  ok(!win.closed, "Window stays open");
+  if (!win.closed) {
+    is(win.gBrowser.tabContainer.childElementCount, 1, "Window has one tab");
+    is(win.gBrowser.browsers.length, 1, "Window has one browser");
+    is(win.gBrowser.selectedTab, newTab, "Remaining tab is selected");
+    is(win.gBrowser.selectedBrowser, newBrowser, "Browser for remaining tab is selected");
+    is(win.gBrowser.mTabBox.selectedPanel, newBrowser.parentNode.parentNode.parentNode.parentNode, "Panel for remaining tab is selected");
+  }
 
-      executeSoon(function () {
-        runs.shift()(win, win.gBrowser, tab);
-      });
-    }, true);
-
-    var rootDir = getRootDirectory(gTestPath);
-    browser.contentWindow.location = rootDir + "test_bug462673.html"
-  }, false);
-}
+  yield promiseWindowClosed(win);
+});
--- a/browser/devtools/debugger/test/browser_dbg_parser-09.js
+++ b/browser/devtools/debugger/test/browser_dbg_parser-09.js
@@ -29,262 +29,262 @@ function test() {
 
   // VariableDeclarator
 
   verify("var foo=()=>{}", e => e.type == "ArrowFunctionExpression", {
     name: "foo",
     chain: null,
     loc: [[1, 4], [1, 7]]
   });
-  verify("\nvar\nfoo\n=\n(\n)\n=>\n{\n}\n", e => e.type == "ArrowFunctionExpression", {
+  verify("\nvar\nfoo\n=\n(\n)=>\n{\n}\n", e => e.type == "ArrowFunctionExpression", {
     name: "foo",
     chain: null,
     loc: [[3, 0], [3, 3]]
   });
 
   // AssignmentExpression
 
   verify("foo=()=>{}", e => e.type == "ArrowFunctionExpression",
     { name: "foo", chain: [], loc: [[1, 0], [1, 3]] });
 
-  verify("\nfoo\n=\n(\n)\n=>\n{\n}\n", e => e.type == "ArrowFunctionExpression",
+  verify("\nfoo\n=\n(\n)=>\n{\n}\n", e => e.type == "ArrowFunctionExpression",
     { name: "foo", chain: [], loc: [[2, 0], [2, 3]] });
 
   verify("foo.bar=()=>{}", e => e.type == "ArrowFunctionExpression",
     { name: "bar", chain: ["foo"], loc: [[1, 0], [1, 7]] });
 
-  verify("\nfoo.bar\n=\n(\n)\n=>\n{\n}\n", e => e.type == "ArrowFunctionExpression",
+  verify("\nfoo.bar\n=\n(\n)=>\n{\n}\n", e => e.type == "ArrowFunctionExpression",
     { name: "bar", chain: ["foo"], loc: [[2, 0], [2, 7]] });
 
   verify("this.foo=()=>{}", e => e.type == "ArrowFunctionExpression",
     { name: "foo", chain: ["this"], loc: [[1, 0], [1, 8]] });
 
-  verify("\nthis.foo\n=\n(\n)\n=>\n{\n}\n", e => e.type == "ArrowFunctionExpression",
+  verify("\nthis.foo\n=\n(\n)=>\n{\n}\n", e => e.type == "ArrowFunctionExpression",
     { name: "foo", chain: ["this"], loc: [[2, 0], [2, 8]] });
 
   verify("this.foo.bar=()=>{}", e => e.type == "ArrowFunctionExpression",
     { name: "bar", chain: ["this", "foo"], loc: [[1, 0], [1, 12]] });
 
-  verify("\nthis.foo.bar\n=\n(\n)\n=>\n{\n}\n", e => e.type == "ArrowFunctionExpression",
+  verify("\nthis.foo.bar\n=\n(\n)=>\n{\n}\n", e => e.type == "ArrowFunctionExpression",
     { name: "bar", chain: ["this", "foo"], loc: [[2, 0], [2, 12]] });
 
   verify("foo.this.bar=()=>{}", e => e.type == "ArrowFunctionExpression",
     { name: "bar", chain: ["foo", "this"], loc: [[1, 0], [1, 12]] });
 
-  verify("\nfoo.this.bar\n=\n(\n)\n=>\n{\n}\n", e => e.type == "ArrowFunctionExpression",
+  verify("\nfoo.this.bar\n=\n(\n)=>\n{\n}\n", e => e.type == "ArrowFunctionExpression",
     { name: "bar", chain: ["foo", "this"], loc: [[2, 0], [2, 12]] });
 
   // ObjectExpression
 
   verify("({foo:()=>{}})", e => e.type == "ArrowFunctionExpression",
     { name: "foo", chain: [], loc: [[1, 2], [1, 5]] });
 
-  verify("(\n{\nfoo\n:\n(\n)\n=>\n{\n}\n}\n)", e => e.type == "ArrowFunctionExpression",
+  verify("(\n{\nfoo\n:\n(\n)=>\n{\n}\n}\n)", e => e.type == "ArrowFunctionExpression",
     { name: "foo", chain: [], loc: [[3, 0], [3, 3]] });
 
   verify("({foo:{bar:()=>{}}})", e => e.type == "ArrowFunctionExpression",
     { name: "bar", chain: ["foo"], loc: [[1, 7], [1, 10]] });
 
-  verify("(\n{\nfoo\n:\n{\nbar\n:\n(\n)\n=>\n{\n}\n}\n}\n)", e => e.type == "ArrowFunctionExpression",
+  verify("(\n{\nfoo\n:\n{\nbar\n:\n(\n)=>\n{\n}\n}\n}\n)", e => e.type == "ArrowFunctionExpression",
     { name: "bar", chain: ["foo"], loc: [[6, 0], [6, 3]] });
 
   // AssignmentExpression + ObjectExpression
 
   verify("foo={bar:()=>{}}", e => e.type == "ArrowFunctionExpression",
     { name: "bar", chain: ["foo"], loc: [[1, 5], [1, 8]] });
 
-  verify("\nfoo\n=\n{\nbar\n:\n(\n)\n=>\n{\n}\n}\n", e => e.type == "ArrowFunctionExpression",
+  verify("\nfoo\n=\n{\nbar\n:\n(\n)=>\n{\n}\n}\n", e => e.type == "ArrowFunctionExpression",
     { name: "bar", chain: ["foo"], loc: [[5, 0], [5, 3]] });
 
   verify("foo={bar:{baz:()=>{}}}", e => e.type == "ArrowFunctionExpression",
     { name: "baz", chain: ["foo", "bar"], loc: [[1, 10], [1, 13]] });
 
-  verify("\nfoo\n=\n{\nbar\n:\n{\nbaz\n:\n(\n)\n=>\n{\n}\n}\n}\n", e => e.type == "ArrowFunctionExpression",
+  verify("\nfoo\n=\n{\nbar\n:\n{\nbaz\n:\n(\n)=>\n{\n}\n}\n}\n", e => e.type == "ArrowFunctionExpression",
     { name: "baz", chain: ["foo", "bar"], loc: [[8, 0], [8, 3]] });
 
   verify("nested.foo={bar:()=>{}}", e => e.type == "ArrowFunctionExpression",
     { name: "bar", chain: ["nested", "foo"], loc: [[1, 12], [1, 15]] });
 
-  verify("\nnested.foo\n=\n{\nbar\n:\n(\n)\n=>\n{\n}\n}\n", e => e.type == "ArrowFunctionExpression",
+  verify("\nnested.foo\n=\n{\nbar\n:\n(\n)=>\n{\n}\n}\n", e => e.type == "ArrowFunctionExpression",
     { name: "bar", chain: ["nested", "foo"], loc: [[5, 0], [5, 3]] });
 
   verify("nested.foo={bar:{baz:()=>{}}}", e => e.type == "ArrowFunctionExpression",
     { name: "baz", chain: ["nested", "foo", "bar"], loc: [[1, 17], [1, 20]] });
 
-  verify("\nnested.foo\n=\n{\nbar\n:\n{\nbaz\n:\n(\n)\n=>\n{\n}\n}\n}\n", e => e.type == "ArrowFunctionExpression",
+  verify("\nnested.foo\n=\n{\nbar\n:\n{\nbaz\n:\n(\n)=>\n{\n}\n}\n}\n", e => e.type == "ArrowFunctionExpression",
     { name: "baz", chain: ["nested", "foo", "bar"], loc: [[8, 0], [8, 3]] });
 
   verify("this.foo={bar:()=>{}}", e => e.type == "ArrowFunctionExpression",
     { name: "bar", chain: ["this", "foo"], loc: [[1, 10], [1, 13]] });
 
-  verify("\nthis.foo\n=\n{\nbar\n:\n(\n)\n=>\n{\n}\n}\n", e => e.type == "ArrowFunctionExpression",
+  verify("\nthis.foo\n=\n{\nbar\n:\n(\n)=>\n{\n}\n}\n", e => e.type == "ArrowFunctionExpression",
     { name: "bar", chain: ["this", "foo"], loc: [[5, 0], [5, 3]] });
 
   verify("this.foo={bar:{baz:()=>{}}}", e => e.type == "ArrowFunctionExpression",
     { name: "baz", chain: ["this", "foo", "bar"], loc: [[1, 15], [1, 18]] });
 
-  verify("\nthis.foo\n=\n{\nbar\n:\n{\nbaz\n:\n(\n)\n=>\n{\n}\n}\n}\n", e => e.type == "ArrowFunctionExpression",
+  verify("\nthis.foo\n=\n{\nbar\n:\n{\nbaz\n:\n(\n)=>\n{\n}\n}\n}\n", e => e.type == "ArrowFunctionExpression",
     { name: "baz", chain: ["this", "foo", "bar"], loc: [[8, 0], [8, 3]] });
 
   verify("this.nested.foo={bar:()=>{}}", e => e.type == "ArrowFunctionExpression",
     { name: "bar", chain: ["this", "nested", "foo"], loc: [[1, 17], [1, 20]] });
 
-  verify("\nthis.nested.foo\n=\n{\nbar\n:\n(\n)\n=>\n{\n}\n}\n", e => e.type == "ArrowFunctionExpression",
+  verify("\nthis.nested.foo\n=\n{\nbar\n:\n(\n)=>\n{\n}\n}\n", e => e.type == "ArrowFunctionExpression",
     { name: "bar", chain: ["this", "nested", "foo"], loc: [[5, 0], [5, 3]] });
 
   verify("this.nested.foo={bar:{baz:()=>{}}}", e => e.type == "ArrowFunctionExpression",
     { name: "baz", chain: ["this", "nested", "foo", "bar"], loc: [[1, 22], [1, 25]] });
 
-  verify("\nthis.nested.foo\n=\n{\nbar\n:\n{\nbaz\n:\n(\n)\n=>\n{\n}\n}\n}\n", e => e.type == "ArrowFunctionExpression",
+  verify("\nthis.nested.foo\n=\n{\nbar\n:\n{\nbaz\n:\n(\n)=>\n{\n}\n}\n}\n", e => e.type == "ArrowFunctionExpression",
     { name: "baz", chain: ["this", "nested", "foo", "bar"], loc: [[8, 0], [8, 3]] });
 
   verify("nested.this.foo={bar:()=>{}}", e => e.type == "ArrowFunctionExpression",
     { name: "bar", chain: ["nested", "this", "foo"], loc: [[1, 17], [1, 20]] });
 
-  verify("\nnested.this.foo\n=\n{\nbar\n:\n(\n)\n=>\n{\n}\n}\n", e => e.type == "ArrowFunctionExpression",
+  verify("\nnested.this.foo\n=\n{\nbar\n:\n(\n)=>\n{\n}\n}\n", e => e.type == "ArrowFunctionExpression",
     { name: "bar", chain: ["nested", "this", "foo"], loc: [[5, 0], [5, 3]] });
 
   verify("nested.this.foo={bar:{baz:()=>{}}}", e => e.type == "ArrowFunctionExpression",
     { name: "baz", chain: ["nested", "this", "foo", "bar"], loc: [[1, 22], [1, 25]] });
 
-  verify("\nnested.this.foo\n=\n{\nbar\n:\n{\nbaz\n:\n(\n)\n=>\n{\n}\n}\n}\n", e => e.type == "ArrowFunctionExpression",
+  verify("\nnested.this.foo\n=\n{\nbar\n:\n{\nbaz\n:\n(\n)=>\n{\n}\n}\n}\n", e => e.type == "ArrowFunctionExpression",
     { name: "baz", chain: ["nested", "this", "foo", "bar"], loc: [[8, 0], [8, 3]] });
 
   // VariableDeclarator + AssignmentExpression + ObjectExpression
 
   verify("let foo={bar:()=>{}}", e => e.type == "ArrowFunctionExpression",
     { name: "bar", chain: ["foo"], loc: [[1, 9], [1, 12]] });
 
-  verify("\nlet\nfoo\n=\n{\nbar\n:\n(\n)\n=>\n{\n}\n}\n", e => e.type == "ArrowFunctionExpression",
+  verify("\nlet\nfoo\n=\n{\nbar\n:\n(\n)=>\n{\n}\n}\n", e => e.type == "ArrowFunctionExpression",
     { name: "bar", chain: ["foo"], loc: [[6, 0], [6, 3]] });
 
   verify("let foo={bar:{baz:()=>{}}}", e => e.type == "ArrowFunctionExpression",
     { name: "baz", chain: ["foo", "bar"], loc: [[1, 14], [1, 17]] });
 
-  verify("\nlet\nfoo\n=\n{\nbar\n:\n{\nbaz\n:\n(\n)\n=>\n{\n}\n}\n}\n", e => e.type == "ArrowFunctionExpression",
+  verify("\nlet\nfoo\n=\n{\nbar\n:\n{\nbaz\n:\n(\n)=>\n{\n}\n}\n}\n", e => e.type == "ArrowFunctionExpression",
     { name: "baz", chain: ["foo", "bar"], loc: [[9, 0], [9, 3]] });
 
   // New/CallExpression + AssignmentExpression + ObjectExpression
 
   verify("foo({bar:()=>{}})", e => e.type == "ArrowFunctionExpression",
     { name: "bar", chain: [], loc: [[1, 5], [1, 8]] });
 
-  verify("\nfoo\n(\n{\nbar\n:\n(\n)\n=>\n{\n}\n}\n)\n", e => e.type == "ArrowFunctionExpression",
+  verify("\nfoo\n(\n{\nbar\n:\n(\n)=>\n{\n}\n}\n)\n", e => e.type == "ArrowFunctionExpression",
     { name: "bar", chain: [], loc: [[5, 0], [5, 3]] });
 
   verify("foo({bar:{baz:()=>{}}})", e => e.type == "ArrowFunctionExpression",
     { name: "baz", chain: ["bar"], loc: [[1, 10], [1, 13]] });
 
-  verify("\nfoo\n(\n{\nbar\n:\n{\nbaz\n:\n(\n)\n=>\n{\n}\n}\n}\n)\n", e => e.type == "ArrowFunctionExpression",
+  verify("\nfoo\n(\n{\nbar\n:\n{\nbaz\n:\n(\n)=>\n{\n}\n}\n}\n)\n", e => e.type == "ArrowFunctionExpression",
     { name: "baz", chain: ["bar"], loc: [[8, 0], [8, 3]] });
 
   verify("nested.foo({bar:()=>{}})", e => e.type == "ArrowFunctionExpression",
     { name: "bar", chain: [], loc: [[1, 12], [1, 15]] });
 
-  verify("\nnested.foo\n(\n{\nbar\n:\n(\n)\n=>\n{\n}\n}\n)\n", e => e.type == "ArrowFunctionExpression",
+  verify("\nnested.foo\n(\n{\nbar\n:\n(\n)=>\n{\n}\n}\n)\n", e => e.type == "ArrowFunctionExpression",
     { name: "bar", chain: [], loc: [[5, 0], [5, 3]] });
 
   verify("nested.foo({bar:{baz:()=>{}}})", e => e.type == "ArrowFunctionExpression",
     { name: "baz", chain: ["bar"], loc: [[1, 17], [1, 20]] });
 
-  verify("\nnested.foo\n(\n{\nbar\n:\n{\nbaz\n:\n(\n)\n=>\n{\n}\n}\n}\n)\n", e => e.type == "ArrowFunctionExpression",
+  verify("\nnested.foo\n(\n{\nbar\n:\n{\nbaz\n:\n(\n)=>\n{\n}\n}\n}\n)\n", e => e.type == "ArrowFunctionExpression",
     { name: "baz", chain: ["bar"], loc: [[8, 0], [8, 3]] });
 
   verify("this.foo({bar:()=>{}})", e => e.type == "ArrowFunctionExpression",
     { name: "bar", chain: [], loc: [[1, 10], [1, 13]] });
 
-  verify("\nthis.foo\n(\n{\nbar\n:\n(\n)\n=>\n{\n}\n}\n)\n", e => e.type == "ArrowFunctionExpression",
+  verify("\nthis.foo\n(\n{\nbar\n:\n(\n)=>\n{\n}\n}\n)\n", e => e.type == "ArrowFunctionExpression",
     { name: "bar", chain: [], loc: [[5, 0], [5, 3]] });
 
   verify("this.foo({bar:{baz:()=>{}}})", e => e.type == "ArrowFunctionExpression",
     { name: "baz", chain: ["bar"], loc: [[1, 15], [1, 18]] });
 
-  verify("\nthis.foo\n(\n{\nbar\n:\n{\nbaz\n:\n(\n)\n=>\n{\n}\n}\n}\n)\n", e => e.type == "ArrowFunctionExpression",
+  verify("\nthis.foo\n(\n{\nbar\n:\n{\nbaz\n:\n(\n)=>\n{\n}\n}\n}\n)\n", e => e.type == "ArrowFunctionExpression",
     { name: "baz", chain: ["bar"], loc: [[8, 0], [8, 3]] });
 
   verify("this.nested.foo({bar:()=>{}})", e => e.type == "ArrowFunctionExpression",
     { name: "bar", chain: [], loc: [[1, 17], [1, 20]] });
 
-  verify("\nthis.nested.foo\n(\n{\nbar\n:\n(\n)\n=>\n{\n}\n}\n)\n", e => e.type == "ArrowFunctionExpression",
+  verify("\nthis.nested.foo\n(\n{\nbar\n:\n(\n)=>\n{\n}\n}\n)\n", e => e.type == "ArrowFunctionExpression",
     { name: "bar", chain: [], loc: [[5, 0], [5, 3]] });
 
   verify("this.nested.foo({bar:{baz:()=>{}}})", e => e.type == "ArrowFunctionExpression",
     { name: "baz", chain: ["bar"], loc: [[1, 22], [1, 25]] });
 
-  verify("\nthis.nested.foo\n(\n{\nbar\n:\n{\nbaz\n:\n(\n)\n=>\n{\n}\n}\n}\n)\n", e => e.type == "ArrowFunctionExpression",
+  verify("\nthis.nested.foo\n(\n{\nbar\n:\n{\nbaz\n:\n(\n)=>\n{\n}\n}\n}\n)\n", e => e.type == "ArrowFunctionExpression",
     { name: "baz", chain: ["bar"], loc: [[8, 0], [8, 3]] });
 
   verify("nested.this.foo({bar:()=>{}})", e => e.type == "ArrowFunctionExpression",
     { name: "bar", chain: [], loc: [[1, 17], [1, 20]] });
 
-  verify("\nnested.this.foo\n(\n{\nbar\n:\n(\n)\n=>\n{\n}\n}\n)\n", e => e.type == "ArrowFunctionExpression",
+  verify("\nnested.this.foo\n(\n{\nbar\n:\n(\n)=>\n{\n}\n}\n)\n", e => e.type == "ArrowFunctionExpression",
     { name: "bar", chain: [], loc: [[5, 0], [5, 3]] });
 
   verify("nested.this.foo({bar:{baz:()=>{}}})", e => e.type == "ArrowFunctionExpression",
     { name: "baz", chain: ["bar"], loc: [[1, 22], [1, 25]] });
 
-  verify("\nnested.this.foo\n(\n{\nbar\n:\n{\nbaz\n:\n(\n)\n=>\n{\n}\n}\n}\n)\n", e => e.type == "ArrowFunctionExpression",
+  verify("\nnested.this.foo\n(\n{\nbar\n:\n{\nbaz\n:\n(\n)=>\n{\n}\n}\n}\n)\n", e => e.type == "ArrowFunctionExpression",
     { name: "baz", chain: ["bar"], loc: [[8, 0], [8, 3]] });
 
   // New/CallExpression + VariableDeclarator + AssignmentExpression + ObjectExpression
 
   verify("let target=foo({bar:()=>{}})", e => e.type == "ArrowFunctionExpression",
     { name: "bar", chain: ["target"], loc: [[1, 16], [1, 19]] });
 
-  verify("\nlet\ntarget=\nfoo\n(\n{\nbar\n:\n(\n)\n=>\n{\n}\n}\n)\n", e => e.type == "ArrowFunctionExpression",
+  verify("\nlet\ntarget=\nfoo\n(\n{\nbar\n:\n(\n)=>\n{\n}\n}\n)\n", e => e.type == "ArrowFunctionExpression",
     { name: "bar", chain: ["target"], loc: [[7, 0], [7, 3]] });
 
   verify("let target=foo({bar:{baz:()=>{}}})", e => e.type == "ArrowFunctionExpression",
     { name: "baz", chain: ["target", "bar"], loc: [[1, 21], [1, 24]] });
 
-  verify("\nlet\ntarget=\nfoo\n(\n{\nbar\n:\n{\nbaz\n:\n(\n)\n=>\n{\n}\n}\n}\n)\n", e => e.type == "ArrowFunctionExpression",
+  verify("\nlet\ntarget=\nfoo\n(\n{\nbar\n:\n{\nbaz\n:\n(\n)=>\n{\n}\n}\n}\n)\n", e => e.type == "ArrowFunctionExpression",
     { name: "baz", chain: ["target", "bar"], loc: [[10, 0], [10, 3]] });
 
   verify("let target=nested.foo({bar:()=>{}})", e => e.type == "ArrowFunctionExpression",
     { name: "bar", chain: ["target"], loc: [[1, 23], [1, 26]] });
 
-  verify("\nlet\ntarget=\nnested.foo\n(\n{\nbar\n:\n(\n)\n=>\n{\n}\n}\n)\n", e => e.type == "ArrowFunctionExpression",
+  verify("\nlet\ntarget=\nnested.foo\n(\n{\nbar\n:\n(\n)=>\n{\n}\n}\n)\n", e => e.type == "ArrowFunctionExpression",
     { name: "bar", chain: ["target"], loc: [[7, 0], [7, 3]] });
 
   verify("let target=nested.foo({bar:{baz:()=>{}}})", e => e.type == "ArrowFunctionExpression",
     { name: "baz", chain: ["target", "bar"], loc: [[1, 28], [1, 31]] });
 
-  verify("\nlet\ntarget=\nnested.foo\n(\n{\nbar\n:\n{\nbaz\n:\n(\n)\n=>\n{\n}\n}\n}\n)\n", e => e.type == "ArrowFunctionExpression",
+  verify("\nlet\ntarget=\nnested.foo\n(\n{\nbar\n:\n{\nbaz\n:\n(\n)=>\n{\n}\n}\n}\n)\n", e => e.type == "ArrowFunctionExpression",
     { name: "baz", chain: ["target", "bar"], loc: [[10, 0], [10, 3]] });
 
   verify("let target=this.foo({bar:()=>{}})", e => e.type == "ArrowFunctionExpression",
     { name: "bar", chain: ["target"], loc: [[1, 21], [1, 24]] });
 
-  verify("\nlet\ntarget=\nthis.foo\n(\n{\nbar\n:\n(\n)\n=>\n{\n}\n}\n)\n", e => e.type == "ArrowFunctionExpression",
+  verify("\nlet\ntarget=\nthis.foo\n(\n{\nbar\n:\n(\n)=>\n{\n}\n}\n)\n", e => e.type == "ArrowFunctionExpression",
     { name: "bar", chain: ["target"], loc: [[7, 0], [7, 3]] });
 
   verify("let target=this.foo({bar:{baz:()=>{}}})", e => e.type == "ArrowFunctionExpression",
     { name: "baz", chain: ["target", "bar"], loc: [[1, 26], [1, 29]] });
 
-  verify("\nlet\ntarget=\nthis.foo\n(\n{\nbar\n:\n{\nbaz\n:\n(\n)\n=>\n{\n}\n}\n}\n)\n", e => e.type == "ArrowFunctionExpression",
+  verify("\nlet\ntarget=\nthis.foo\n(\n{\nbar\n:\n{\nbaz\n:\n(\n)=>\n{\n}\n}\n}\n)\n", e => e.type == "ArrowFunctionExpression",
     { name: "baz", chain: ["target", "bar"], loc: [[10, 0], [10, 3]] });
 
   verify("let target=this.nested.foo({bar:()=>{}})", e => e.type == "ArrowFunctionExpression",
     { name: "bar", chain: ["target"], loc: [[1, 28], [1, 31]] });
 
-  verify("\nlet\ntarget=\nthis.nested.foo\n(\n{\nbar\n:\n(\n)\n=>\n{\n}\n}\n)\n", e => e.type == "ArrowFunctionExpression",
+  verify("\nlet\ntarget=\nthis.nested.foo\n(\n{\nbar\n:\n(\n)=>\n{\n}\n}\n)\n", e => e.type == "ArrowFunctionExpression",
     { name: "bar", chain: ["target"], loc: [[7, 0], [7, 3]] });
 
   verify("let target=this.nested.foo({bar:{baz:()=>{}}})", e => e.type == "ArrowFunctionExpression",
     { name: "baz", chain: ["target", "bar"], loc: [[1, 33], [1, 36]] });
 
-  verify("\nlet\ntarget=\nthis.nested.foo\n(\n{\nbar\n:\n{\nbaz\n:\n(\n)\n=>\n{\n}\n}\n}\n)\n", e => e.type == "ArrowFunctionExpression",
+  verify("\nlet\ntarget=\nthis.nested.foo\n(\n{\nbar\n:\n{\nbaz\n:\n(\n)=>\n{\n}\n}\n}\n)\n", e => e.type == "ArrowFunctionExpression",
     { name: "baz", chain: ["target", "bar"], loc: [[10, 0], [10, 3]] });
 
   verify("let target=nested.this.foo({bar:()=>{}})", e => e.type == "ArrowFunctionExpression",
     { name: "bar", chain: ["target"], loc: [[1, 28], [1, 31]] });
 
-  verify("\nlet\ntarget=\nnested.this.foo\n(\n{\nbar\n:\n(\n)\n=>\n{\n}\n}\n)\n", e => e.type == "ArrowFunctionExpression",
+  verify("\nlet\ntarget=\nnested.this.foo\n(\n{\nbar\n:\n(\n)=>\n{\n}\n}\n)\n", e => e.type == "ArrowFunctionExpression",
     { name: "bar", chain: ["target"], loc: [[7, 0], [7, 3]] });
 
   verify("let target=nested.this.foo({bar:{baz:()=>{}}})", e => e.type == "ArrowFunctionExpression",
     { name: "baz", chain: ["target", "bar"], loc: [[1, 33], [1, 36]] });
 
-  verify("\nlet\ntarget=\nnested.this.foo\n(\n{\nbar\n:\n{\nbaz\n:\n(\n)\n=>\n{\n}\n}\n}\n)\n", e => e.type == "ArrowFunctionExpression",
+  verify("\nlet\ntarget=\nnested.this.foo\n(\n{\nbar\n:\n{\nbaz\n:\n(\n)=>\n{\n}\n}\n}\n)\n", e => e.type == "ArrowFunctionExpression",
     { name: "baz", chain: ["target", "bar"], loc: [[10, 0], [10, 3]] });
 
   finish();
 }
--- a/browser/installer/package-manifest.in
+++ b/browser/installer/package-manifest.in
@@ -732,16 +732,28 @@
 @RESPATH@/res/table-add-row-before-hover.gif
 @RESPATH@/res/table-add-row-before.gif
 @RESPATH@/res/table-remove-column-active.gif
 @RESPATH@/res/table-remove-column-hover.gif
 @RESPATH@/res/table-remove-column.gif
 @RESPATH@/res/table-remove-row-active.gif
 @RESPATH@/res/table-remove-row-hover.gif
 @RESPATH@/res/table-remove-row.gif
+@RESPATH@/res/text_caret.png
+@RESPATH@/res/text_caret@1.5x.png
+@RESPATH@/res/text_caret@2.25x.png
+@RESPATH@/res/text_caret@2x.png
+@RESPATH@/res/text_caret_tilt_left.png
+@RESPATH@/res/text_caret_tilt_left@1.5x.png
+@RESPATH@/res/text_caret_tilt_left@2.25x.png
+@RESPATH@/res/text_caret_tilt_left@2x.png
+@RESPATH@/res/text_caret_tilt_right.png
+@RESPATH@/res/text_caret_tilt_right@1.5x.png
+@RESPATH@/res/text_caret_tilt_right@2.25x.png
+@RESPATH@/res/text_caret_tilt_right@2x.png
 @RESPATH@/res/grabber.gif
 #ifdef XP_MACOSX
 @RESPATH@/res/cursors/*
 #endif
 @RESPATH@/res/fonts/*
 @RESPATH@/res/dtd/*
 @RESPATH@/res/html/*
 #if defined(XP_MACOSX) || defined(XP_WIN)
--- a/browser/metro/base/content/bindings/browser.xml
+++ b/browser/metro/base/content/bindings/browser.xml
@@ -334,20 +334,19 @@
       </method>
 
       <property name="scale">
         <getter><![CDATA[
           let cwu = this.contentDocument
                         .defaultView
                         .QueryInterface(Components.interfaces.nsIInterfaceRequestor)
                         .getInterface(Components.interfaces.nsIDOMWindowUtils);
-          let resx = {}, resy = {};
-          cwu.getResolution(resx, resy);
-          // Resolution set by the apzc and is symmetric.
-          return resx.value;
+          let res = {};
+          cwu.getResolution(res);
+          return res.value;
         ]]></getter>
       </property>
 
       <field name="_messageListenerLocal"><![CDATA[
         ({
           self: this,
           receiveMessage: function receiveMessage(aMessage) {
             let self = this.self;
--- a/dom/base/nsContentSink.cpp
+++ b/dom/base/nsContentSink.cpp
@@ -847,19 +847,25 @@ nsContentSink::PrefetchDNS(const nsAStri
     hostname = Substring(aHref, 2);
   }
   else {
     nsCOMPtr<nsIURI> uri;
     NS_NewURI(getter_AddRefs(uri), aHref);
     if (!uri) {
       return;
     }
-    nsAutoCString host;
-    uri->GetHost(host);
-    CopyUTF8toUTF16(host, hostname);
+    nsresult rv;
+    bool isLocalResource = false;
+    rv = NS_URIChainHasFlags(uri, nsIProtocolHandler::URI_IS_LOCAL_RESOURCE,
+                             &isLocalResource);
+    if (NS_SUCCEEDED(rv) && !isLocalResource) {
+      nsAutoCString host;
+      uri->GetHost(host);
+      CopyUTF8toUTF16(host, hostname);
+    }
   }
 
   if (!hostname.IsEmpty() && nsHTMLDNSPrefetch::IsAllowed(mDocument)) {
     nsHTMLDNSPrefetch::PrefetchLow(hostname);
   }
 }
 
 void
--- a/dom/base/nsContentUtils.cpp
+++ b/dom/base/nsContentUtils.cpp
@@ -6486,39 +6486,42 @@ bool
 nsContentUtils::IsPatternMatching(nsAString& aValue, nsAString& aPattern,
                                   nsIDocument* aDocument)
 {
   NS_ASSERTION(aDocument, "aDocument should be a valid pointer (not null)");
 
   AutoJSAPI jsapi;
   jsapi.Init();
   JSContext* cx = jsapi.cx();
+
+  // Failure to create or run the regexp results in the invalid pattern
+  // matching, but we can still report the error to the console.
+  jsapi.TakeOwnershipOfErrorReporting();
+
   // We can use the junk scope here, because we're just using it for
   // regexp evaluation, not actual script execution.
   JSAutoCompartment ac(cx, xpc::UnprivilegedJunkScope());
 
   // The pattern has to match the entire value.
   aPattern.Insert(NS_LITERAL_STRING("^(?:"), 0);
   aPattern.AppendLiteral(")$");
 
   JS::Rooted<JSObject*> re(cx,
     JS_NewUCRegExpObjectNoStatics(cx,
                                   static_cast<char16_t*>(aPattern.BeginWriting()),
                                   aPattern.Length(), 0));
   if (!re) {
-    JS_ClearPendingException(cx);
     return true;
   }
 
   JS::Rooted<JS::Value> rval(cx, JS::NullValue());
   size_t idx = 0;
   if (!JS_ExecuteRegExpNoStatics(cx, re,
                                  static_cast<char16_t*>(aValue.BeginWriting()),
                                  aValue.Length(), &idx, true, &rval)) {
-    JS_ClearPendingException(cx);
     return true;
   }
 
   return !rval.isNull();
 }
 
 // static
 nsresult
--- a/dom/base/nsDOMWindowUtils.cpp
+++ b/dom/base/nsDOMWindowUtils.cpp
@@ -485,75 +485,72 @@ nsDOMWindowUtils::SetDisplayPortBaseForE
   }
 
   nsLayoutUtils::SetDisplayPortBase(content, nsRect(aX, aY, aWidth, aHeight));
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
-nsDOMWindowUtils::SetResolution(float aXResolution, float aYResolution)
+nsDOMWindowUtils::SetResolution(float aResolution)
 {
   if (!nsContentUtils::IsCallerChrome()) {
     return NS_ERROR_DOM_SECURITY_ERR;
   }
 
   nsIPresShell* presShell = GetPresShell();
   if (!presShell) {
     return NS_ERROR_FAILURE;
   }
 
   nsIScrollableFrame* sf = presShell->GetRootScrollFrameAsScrollable();
   if (sf) {
-    sf->SetResolution(gfxSize(aXResolution, aYResolution));
-    presShell->SetResolution(aXResolution, aYResolution);
+    sf->SetResolution(aResolution);
+    presShell->SetResolution(aResolution);
   }
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
-nsDOMWindowUtils::SetResolutionAndScaleTo(float aXResolution, float aYResolution)
+nsDOMWindowUtils::SetResolutionAndScaleTo(float aResolution)
 {
   if (!nsContentUtils::IsCallerChrome()) {
     return NS_ERROR_DOM_SECURITY_ERR;
   }
 
   nsIPresShell* presShell = GetPresShell();
   if (!presShell) {
     return NS_ERROR_FAILURE;
   }
 
   nsIScrollableFrame* sf = presShell->GetRootScrollFrameAsScrollable();
   if (sf) {
-    sf->SetResolutionAndScaleTo(gfxSize(aXResolution, aYResolution));
-    presShell->SetResolutionAndScaleTo(aXResolution, aYResolution);
+    sf->SetResolutionAndScaleTo(aResolution);
+    presShell->SetResolutionAndScaleTo(aResolution);
   }
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
-nsDOMWindowUtils::GetResolution(float* aXResolution, float* aYResolution)
+nsDOMWindowUtils::GetResolution(float* aResolution)
 {
   MOZ_RELEASE_ASSERT(nsContentUtils::IsCallerChrome());
 
   nsIPresShell* presShell = GetPresShell();
   if (!presShell) {
     return NS_ERROR_FAILURE;
   }
 
   nsIScrollableFrame* sf = presShell->GetRootScrollFrameAsScrollable();
   if (sf) {
-    const gfxSize& res = sf->GetResolution();
-    *aXResolution = res.width;
-    *aYResolution = res.height;
+    *aResolution = sf->GetResolution();
   } else {
-    *aXResolution = presShell->GetXResolution();
-    *aYResolution = presShell->GetYResolution();
+    *aResolution = presShell->GetResolution();
   }
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsDOMWindowUtils::GetIsResolutionSet(bool* aIsResolutionSet) {
   MOZ_RELEASE_ASSERT(nsContentUtils::IsCallerChrome());
--- a/dom/base/nsDocument.cpp
+++ b/dom/base/nsDocument.cpp
@@ -12839,16 +12839,45 @@ nsIDocument::CreateHTMLElement(nsIAtom* 
   DebugOnly<nsresult> rv = NS_NewHTMLElement(getter_AddRefs(element),
                                              nodeInfo.forget(),
                                              mozilla::dom::NOT_FROM_PARSER);
 
   MOZ_ASSERT(NS_SUCCEEDED(rv), "NS_NewHTMLElement should never fail");
   return element.forget();
 }
 
+nsresult
+nsIDocument::GetId(nsAString& aId)
+{
+  if (mId.IsEmpty()) {
+    nsresult rv;
+    nsCOMPtr<nsIUUIDGenerator> uuidgen = do_GetService("@mozilla.org/uuid-generator;1", &rv);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
+    }
+
+    nsID id;
+    rv = uuidgen->GenerateUUIDInPlace(&id);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
+    }
+
+    // Build a string in {xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx} format
+    char buffer[NSID_LENGTH];
+    id.ToProvidedString(buffer);
+    NS_ConvertASCIItoUTF16 uuid(buffer);
+
+    // Remove {} and the null terminator
+    mId.Assign(Substring(uuid, 1, NSID_LENGTH - 3));
+  }
+
+  aId = mId;
+  return NS_OK;
+}
+
 bool
 MarkDocumentTreeToBeInSyncOperation(nsIDocument* aDoc, void* aData)
 {
   nsCOMArray<nsIDocument>* documents =
     static_cast<nsCOMArray<nsIDocument>*>(aData);
   if (aDoc) {
     aDoc->SetIsInSyncOperation(true);
     documents->AppendObject(aDoc);
--- a/dom/base/nsIDocument.h
+++ b/dom/base/nsIDocument.h
@@ -11,16 +11,18 @@
 #include "nsCRT.h"                       // for NS_DECL_AND_IMPL_ZEROING_OPERATOR_NEW
 #include "nsCompatibility.h"             // for member
 #include "nsCOMPtr.h"                    // for member
 #include "nsGkAtoms.h"                   // for static class members
 #include "nsIDocumentObserver.h"         // for typedef (nsUpdateType)
 #include "nsILoadGroup.h"                // for member (in nsCOMPtr)
 #include "nsINode.h"                     // for base class
 #include "nsIScriptGlobalObject.h"       // for member (in nsCOMPtr)
+#include "nsIServiceManager.h"
+#include "nsIUUIDGenerator.h"
 #include "nsPIDOMWindow.h"               // for use in inline functions
 #include "nsPropertyTable.h"             // for member
 #include "nsTHashtable.h"                // for member
 #include "mozilla/net/ReferrerPolicy.h"  // for member
 #include "nsWeakReference.h"
 #include "mozilla/dom/DocumentBinding.h"
 #include "mozilla/WeakPtr.h"
 #include "Units.h"
@@ -742,16 +744,18 @@ public:
   InsertAnonymousContent(mozilla::dom::Element& aElement,
                          mozilla::ErrorResult& aError);
   void RemoveAnonymousContent(mozilla::dom::AnonymousContent& aContent,
                               mozilla::ErrorResult& aError);
   nsTArray<nsRefPtr<mozilla::dom::AnonymousContent>>& GetAnonymousContents() {
     return mAnonymousContents;
   }
 
+  nsresult GetId(nsAString& aId);
+
 protected:
   virtual Element *GetRootElementInternal() const = 0;
 
 private:
   class SelectorCacheKey
   {
     public:
       explicit SelectorCacheKey(const nsAString& aString) : mKey(aString)
@@ -2793,16 +2797,17 @@ protected:
   uint32_t mSandboxFlags;
 
   nsCString mContentLanguage;
 
   // The channel that got passed to nsDocument::StartDocumentLoad(), if any.
   nsCOMPtr<nsIChannel> mChannel;
 private:
   nsCString mContentType;
+  nsString mId;
 protected:
 
   // The document's security info
   nsCOMPtr<nsISupports> mSecurityInfo;
 
   // The channel that failed to load and resulted in an error page.
   // This only applies to error pages. Might be null.
   nsCOMPtr<nsIChannel> mFailedChannel;
--- a/dom/bluetooth/bluedroid/BluetoothDaemonHelpers.cpp
+++ b/dom/bluetooth/bluedroid/BluetoothDaemonHelpers.cpp
@@ -1112,19 +1112,19 @@ PackPDU(const BluetoothAvrcpElementAttri
   }
 
   const NS_ConvertUTF16toUTF8 cstr(aIn.mValue);
 
   if (NS_WARN_IF(cstr.Length() == PR_UINT32_MAX)) {
     return NS_ERROR_ILLEGAL_VALUE; /* integer overflow detected */
   }
 
-  PRUint32 clen = cstr.Length() + 1; /* include \0 character */
+  uint32_t clen = cstr.Length() + 1; /* include \0 character */
 
-  rv = PackPDU(PackConversion<PRUint32, uint8_t>(clen), aPDU);
+  rv = PackPDU(PackConversion<uint32_t, uint8_t>(clen), aPDU);
   if (NS_FAILED(rv)) {
     return rv;
   }
 
   return PackPDU(
     PackArray<uint8_t>(reinterpret_cast<const uint8_t*>(cstr.get()), clen),
     aPDU);
 }
--- a/dom/bluetooth2/bluedroid/BluetoothDaemonHelpers.cpp
+++ b/dom/bluetooth2/bluedroid/BluetoothDaemonHelpers.cpp
@@ -1065,19 +1065,19 @@ PackPDU(const BluetoothAvrcpElementAttri
   }
 
   const NS_ConvertUTF16toUTF8 cstr(aIn.mValue);
 
   if (NS_WARN_IF(cstr.Length() == PR_UINT32_MAX)) {
     return NS_ERROR_ILLEGAL_VALUE; /* integer overflow detected */
   }
 
-  PRUint32 clen = cstr.Length() + 1; /* include \0 character */
+  uint32_t clen = cstr.Length() + 1; /* include \0 character */
 
-  rv = PackPDU(PackConversion<PRUint32, uint8_t>(clen), aPDU);
+  rv = PackPDU(PackConversion<uint32_t, uint8_t>(clen), aPDU);
   if (NS_FAILED(rv)) {
     return rv;
   }
 
   return PackPDU(
     PackArray<uint8_t>(reinterpret_cast<const uint8_t*>(cstr.get()), clen),
     aPDU);
 }
--- a/dom/cache/ActorChild.cpp
+++ b/dom/cache/ActorChild.cpp
@@ -45,16 +45,20 @@ ActorChild::GetFeature() const
 }
 
 bool
 ActorChild::FeatureNotified() const
 {
   return mFeature && mFeature->Notified();
 }
 
+ActorChild::ActorChild()
+{
+}
+
 ActorChild::~ActorChild()
 {
   MOZ_ASSERT(!mFeature);
 }
 
 } // namespace cache
 } // namespace dom
 } // namespace mozilla
--- a/dom/cache/ActorChild.h
+++ b/dom/cache/ActorChild.h
@@ -29,16 +29,17 @@ public:
 
   Feature*
   GetFeature() const;
 
   bool
   FeatureNotified() const;
 
 protected:
+  ActorChild();
   ~ActorChild();
 
 private:
   nsRefPtr<Feature> mFeature;
 };
 
 } // namespace cache
 } // namespace dom
--- a/dom/cache/test/mochitest/driver.js
+++ b/dom/cache/test/mochitest/driver.js
@@ -76,17 +76,18 @@ function runTests(testFile, order) {
       };
       document.body.appendChild(iframe);
     });
   }
 
   SimpleTest.waitForExplicitFinish();
 
   if (typeof order == "undefined") {
-    order = "both"; // both by default
+    order = "sequential"; // sequential by default, see bug 1143222.
+    // TODO: Make this "both".
   }
 
   ok(order == "parallel" || order == "sequential" || order == "both",
      "order argument should be valid");
 
   if (order == "both") {
     info("Running tests in both modes; first: sequential");
     return runTests(testFile, "sequential")
--- a/dom/cache/test/mochitest/mochitest.ini
+++ b/dom/cache/test/mochitest/mochitest.ini
@@ -9,11 +9,9 @@ support-files =
   driver.js
   serviceworker_driver.js
   test_cache_match_request.js
   test_cache_matchAll_request.js
 
 [test_cache.html]
 [test_cache_add.html]
 [test_cache_match_request.html]
-skip-if = true # bug 1143222
 [test_cache_matchAll_request.html]
-skip-if = true # bug 1143222
--- a/dom/cache/test/mochitest/test_cache_matchAll_request.js
+++ b/dom/cache/test/mochitest/test_cache_matchAll_request.js
@@ -1,19 +1,22 @@
-var request1 = new Request("//mochi.test:8888/?1&" + context);
+var request1 = new Request("//mochi.test:8888/?1&" + context + "#fragment");
 var request2 = new Request("//mochi.test:8888/?2&" + context);
 var request3 = new Request("//mochi.test:8888/?3&" + context);
+var requestWithAltQS = new Request("//mochi.test:8888/?queryString");
+var unknownRequest = new Request("//mochi.test:8888/non/existing/path?" + context);
 var response1, response3;
 var c;
 var response1Text, response3Text;
 var name = "matchAll-request" + context;
 
 function checkResponse(r, response, responseText) {
   ok(r !== response, "The objects should not be the same");
-  is(r.url, response.url, "The URLs should be the same");
+  is(r.url, response.url.replace("#fragment", ""),
+     "The URLs should be the same");
   is(r.status, response.status, "The status codes should be the same");
   is(r.type, response.type, "The response types should be the same");
   is(r.ok, response.ok, "Both responses should have succeeded");
   is(r.statusText, response.statusText,
      "Both responses should have the same status text");
   return r.text().then(function(text) {
     is(text, responseText, "The response body should be correct");
   });
@@ -25,69 +28,115 @@ fetch(new Request(request1)).then(functi
 }).then(function(text) {
   response1Text = text;
   return fetch(new Request(request3));
 }).then(function(r) {
   response3 = r;
   return response3.text();
 }).then(function(text) {
   response3Text = text;
-  return caches.open(name);
-}).then(function(cache) {
-  c = cache;
-  return c.add(request1);
-}).then(function() {
-  return c.add(request3);
-}).then(function() {
-  return c.matchAll(request1);
-}).then(function(r) {
-  is(r.length, 1, "Should only find 1 item");
-  return checkResponse(r[0], response1, response1Text);
-}).then(function() {
-  return c.matchAll(request3);
-}).then(function(r) {
-  is(r.length, 1, "Should only find 1 item");
-  return checkResponse(r[0], response3, response3Text);
-}).then(function() {
-  return c.matchAll();
-}).then(function(r) {
-  is(r.length, 2, "Should find 2 items");
-  return Promise.all([
-    checkResponse(r[0], response1, response1Text),
-    checkResponse(r[1], response3, response3Text)
-  ]);
-}).then(function() {
-  return c.matchAll({cacheName: name + "mambojambo"});
-}).catch(function(err) {
-  is(err.name, "NotFoundError", "Searching in the wrong cache should not succeed");
+  return testRequest(request1, request2, request3, unknownRequest,
+                     requestWithAltQS,
+                     request1.url.replace("#fragment", "#other"));
 }).then(function() {
-  return caches.delete(name);
-}).then(function(success) {
-  ok(success, "We should be able to delete the cache successfully");
-  // Make sure that the cache is still usable after deletion.
-  return c.matchAll(request1);
-}).then(function(r) {
-  is(r.length, 1, "Should only find 1 item");
-  return checkResponse(r[0], response1, response1Text);
-}).then(function() {
-  return c.matchAll(request3);
-}).then(function(r) {
-  is(r.length, 1, "Should only find 1 item");
-  return checkResponse(r[0], response3, response3Text);
-}).then(function() {
-  return c.matchAll();
-}).then(function(r) {
-  is(r.length, 2, "Should find 2 items");
-  return Promise.all([
-    checkResponse(r[0], response1, response1Text),
-    checkResponse(r[1], response3, response3Text)
-  ]);
-}).then(function() {
-  // Now, drop the cache, reopen and verify that we can't find the request any more.
-  c = null;
-  return caches.open(name);
-}).then(function(cache) {
-  return cache.matchAll();
-}).catch(function(err) {
-  is(err.name, "NotFoundError", "Searching in the cache after deletion should not succeed");
+  return testRequest(request1.url, request2.url, request3.url,
+                     unknownRequest.url, requestWithAltQS.url,
+                     request1.url.replace("#fragment", "#other"));
 }).then(function() {
   testDone();
 });
+
+// The request arguments can either be a URL string, or a Request object.
+function testRequest(request1, request2, request3, unknownRequest,
+                     requestWithAlternateQueryString,
+                     requestWithDifferentFragment) {
+  return caches.open(name).then(function(cache) {
+    c = cache;
+    return c.add(request1);
+  }).then(function() {
+    return c.add(request3);
+  }).then(function() {
+    return Promise.all(
+      ["HEAD", "POST", "PUT", "DELETE", "OPTIONS"]
+        .map(function(method) {
+          var r = new Request(request1, {method: method});
+          return c.add(r)
+            .then(function() {
+              ok(false, "Promise should be rejected");
+            }, function(err) {
+              is(err.name, "TypeError", "Adding a request with type '" + method + "' should fail");
+            });
+        })
+    );
+  }).then(function() {
+    return c.matchAll(request1);
+  }).then(function(r) {
+    is(r.length, 1, "Should only find 1 item");
+    return checkResponse(r[0], response1, response1Text);
+  }).then(function() {
+    return c.matchAll(requestWithDifferentFragment);
+  }).then(function(r) {
+    is(r.length, 1, "Should only find 1 item");
+    return checkResponse(r[0], response1, response1Text);
+  }).then(function() {
+    return c.matchAll(requestWithAlternateQueryString,
+                      {ignoreSearch: true, cacheName: name});
+  }).then(function(r) {
+    is(r.length, 2, "Should find 2 items");
+    return Promise.all([
+      checkResponse(r[0], response1, response1Text),
+      checkResponse(r[1], response3, response3Text)
+    ]);
+  }).then(function() {
+    return c.matchAll(request3);
+  }).then(function(r) {
+    is(r.length, 1, "Should only find 1 item");
+    return checkResponse(r[0], response3, response3Text);
+  }).then(function() {
+    return c.matchAll();
+  }).then(function(r) {
+    is(r.length, 2, "Should find 2 items");
+    return Promise.all([
+      checkResponse(r[0], response1, response1Text),
+      checkResponse(r[1], response3, response3Text)
+    ]);
+  }).then(function() {
+    return c.matchAll({cacheName: name + "mambojambo"});
+  }).then(function(r) {
+    is(r.length, 0, "Searching in the wrong cache should not succeed");
+  }).then(function() {
+    return c.matchAll(unknownRequest);
+  }).then(function(r) {
+    is(r.length, 0, "Searching for an unknown request should not succeed");
+    return c.matchAll(unknownRequest, {cacheName: name});
+  }).then(function(r) {
+    is(r.length, 0, "Searching for an unknown request should not succeed");
+    return caches.delete(name);
+  }).then(function(success) {
+    ok(success, "We should be able to delete the cache successfully");
+    // Make sure that the cache is still usable after deletion.
+    return c.matchAll(request1);
+  }).then(function(r) {
+    is(r.length, 1, "Should only find 1 item");
+    return checkResponse(r[0], response1, response1Text);
+  }).then(function() {
+    return c.matchAll(request3);
+  }).then(function(r) {
+    is(r.length, 1, "Should only find 1 item");
+    return checkResponse(r[0], response3, response3Text);
+  }).then(function() {
+    return c.matchAll();
+  }).then(function(r) {
+    is(r.length, 2, "Should find 2 items");
+    return Promise.all([
+      checkResponse(r[0], response1, response1Text),
+      checkResponse(r[1], response3, response3Text)
+    ]);
+  }).then(function() {
+    // Now, drop the cache, reopen and verify that we can't find the request any more.
+    c = null;
+    return caches.open(name);
+  }).then(function(cache) {
+    return cache.matchAll();
+  }).then(function(r) {
+    is(r.length, 0, "Searching in the cache after deletion should not succeed");
+  });
+}
--- a/dom/cache/test/mochitest/test_cache_match_request.js
+++ b/dom/cache/test/mochitest/test_cache_match_request.js
@@ -1,63 +1,110 @@
-var request = new Request("//mochi.test:8888/?" + context);
+var request = new Request("//mochi.test:8888/?" + context + "#fragment");
+var requestWithAltQS = new Request("//mochi.test:8888/?queryString");
+var unknownRequest = new Request("//mochi.test:8888/non/existing/path?" + context);
 var response;
 var c;
 var responseText;
 var name = "match-request" + context;
 
 function checkResponse(r) {
   ok(r !== response, "The objects should not be the same");
-  is(r.url, response.url, "The URLs should be the same");
+  is(r.url, response.url.replace("#fragment", ""),
+     "The URLs should be the same");
   is(r.status, response.status, "The status codes should be the same");
   is(r.type, response.type, "The response types should be the same");
   is(r.ok, response.ok, "Both responses should have succeeded");
   is(r.statusText, response.statusText,
      "Both responses should have the same status text");
   return r.text().then(function(text) {
     is(text, responseText, "The response body should be correct");
   });
 }
 
 fetch(new Request(request)).then(function(r) {
   response = r;
   return response.text();
 }).then(function(text) {
   responseText = text;
-  return caches.open(name);
-}).then(function(cache) {
-  c = cache;
-  return c.add(request);
-}).then(function() {
-  return c.match(request);
-}).then(function(r) {
-  return checkResponse(r);
-}).then(function() {
-  return caches.match(request);
-}).then(function(r) {
-  return checkResponse(r);
-}).then(function() {
-  return caches.match(request, {cacheName: name});
-}).then(function(r) {
-  return checkResponse(r);
+  return testRequest(request, unknownRequest, requestWithAltQS,
+                     request.url.replace("#fragment", "#other"));
 }).then(function() {
-  return caches.match(request, {cacheName: name + "mambojambo"});
-}).catch(function(err) {
-  is(err.name, "NotFoundError", "Searching in the wrong cache should not succeed");
-}).then(function() {
-  return caches.delete(name);
-}).then(function(success) {
-  ok(success, "We should be able to delete the cache successfully");
-  // Make sure that the cache is still usable after deletion.
-  return c.match(request);
-}).then(function(r) {
-  return checkResponse(r);
-}).then(function() {
-  // Now, drop the cache, reopen and verify that we can't find the request any more.
-  c = null;
-  return caches.open(name);
-}).then(function(cache) {
-  return cache.match(request);
-}).catch(function(err) {
-  is(err.name, "NotFoundError", "Searching in the cache after deletion should not succeed");
+  return testRequest(request.url, unknownRequest.url, requestWithAltQS.url,
+                     request.url.replace("#fragment", "#other"));
 }).then(function() {
   testDone();
 });
+
+// The request argument can either be a URL string, or a Request object.
+function testRequest(request, unknownRequest, requestWithAlternateQueryString,
+                     requestWithDifferentFragment) {
+  return caches.open(name).then(function(cache) {
+    c = cache;
+    return c.add(request);
+  }).then(function() {
+    return Promise.all(
+      ["HEAD", "POST", "PUT", "DELETE", "OPTIONS"]
+        .map(function(method) {
+          var r = new Request(request, {method: method});
+          return c.add(r)
+            .then(function() {
+              ok(false, "Promise should be rejected");
+            }, function(err) {
+              is(err.name, "TypeError", "Adding a request with type '" + method + "' should fail");
+            });
+        })
+    );
+  }).then(function() {
+    return c.match(request);
+  }).then(function(r) {
+    return checkResponse(r);
+  }).then(function() {
+    return caches.match(request);
+  }).then(function(r) {
+    return checkResponse(r);
+  }).then(function() {
+    return caches.match(requestWithDifferentFragment);
+  }).then(function(r) {
+    return checkResponse(r);
+  }).then(function() {
+    return caches.match(requestWithAlternateQueryString,
+                        {ignoreSearch: true, cacheName: name});
+  }).then(function(r) {
+    return checkResponse(r);
+  }).then(function() {
+    return caches.match(request, {cacheName: name});
+  }).then(function(r) {
+    return checkResponse(r);
+  }).then(function() {
+    return caches.match(request, {cacheName: name + "mambojambo"})
+      .then(function() {
+        ok(false, "Promise should be rejected");
+      }, function(err) {
+        is(err.name, "NotFoundError", "Searching in the wrong cache should not succeed");
+      });
+  }).then(function() {
+    return c.match(unknownRequest);
+  }).then(function(r) {
+    is(typeof r, "undefined", "Searching for an unknown request should not succeed");
+    return caches.match(unknownRequest);
+  }).then(function(r) {
+    is(typeof r, "undefined", "Searching for an unknown request should not succeed");
+    return caches.match(unknownRequest, {cacheName: name});
+  }).then(function(r) {
+    is(typeof r, "undefined", "Searching for an unknown request should not succeed");
+    return caches.delete(name);
+  }).then(function(success) {
+    ok(success, "We should be able to delete the cache successfully");
+    // Make sure that the cache is still usable after deletion.
+    return c.match(request);
+  }).then(function(r) {
+    return checkResponse(r);
+  }).then(function() {
+    // Now, drop the cache, reopen and verify that we can't find the request any more.
+    c = null;
+    return caches.open(name);
+  }).then(function(cache) {
+    return cache.match(request);
+  }).then(function(r) {
+    is(typeof r, "undefined", "Searching in the cache after deletion should not succeed");
+  });
+}
--- a/dom/canvas/WebGL2Context.cpp
+++ b/dom/canvas/WebGL2Context.cpp
@@ -76,17 +76,16 @@ static const gl::GLFeature kRequiredFeat
     gl::GLFeature::frag_depth,
     gl::GLFeature::framebuffer_blit,
     gl::GLFeature::framebuffer_multisample,
     gl::GLFeature::get_integer_indexed,
     gl::GLFeature::get_integer64_indexed,
     gl::GLFeature::gpu_shader4,
     gl::GLFeature::instanced_arrays,
     gl::GLFeature::instanced_non_arrays,
-    gl::GLFeature::invalidate_framebuffer,
     gl::GLFeature::map_buffer_range,
     gl::GLFeature::occlusion_query2,
     gl::GLFeature::packed_depth_stencil,
     gl::GLFeature::query_objects,
     gl::GLFeature::renderbuffer_color_float,
     gl::GLFeature::renderbuffer_color_half_float,
     gl::GLFeature::sRGB,
     gl::GLFeature::sampler_objects,
--- a/dom/canvas/WebGL2ContextFramebuffers.cpp
+++ b/dom/canvas/WebGL2ContextFramebuffers.cpp
@@ -393,16 +393,23 @@ WebGL2Context::InvalidateFramebuffer(GLe
     for (size_t i = 0; i < attachments.Length(); i++) {
         if (!ValidateFramebufferAttachment(fb, attachments[i],
                                            "invalidateFramebuffer"))
         {
             return;
         }
     }
 
+    // InvalidateFramebuffer is a hint to the driver. Should be OK to
+    // skip calls if not supported, for example by OSX 10.9 GL
+    // drivers.
+    static bool invalidateFBSupported = gl->IsSupported(gl::GLFeature::invalidate_framebuffer);
+    if (!invalidateFBSupported)
+        return;
+
     if (!fb && !isDefaultFB) {
         dom::Sequence<GLenum> tmpAttachments;
         TranslateDefaultAttachments(attachments, &tmpAttachments);
         gl->fInvalidateFramebuffer(target, tmpAttachments.Length(), tmpAttachments.Elements());
     } else {
         gl->fInvalidateFramebuffer(target, attachments.Length(), attachments.Elements());
     }
 }
@@ -440,16 +447,23 @@ WebGL2Context::InvalidateSubFramebuffer(
     for (size_t i = 0; i < attachments.Length(); i++) {
         if (!ValidateFramebufferAttachment(fb, attachments[i],
                                            "invalidateSubFramebuffer"))
         {
             return;
         }
     }
 
+    // InvalidateFramebuffer is a hint to the driver. Should be OK to
+    // skip calls if not supported, for example by OSX 10.9 GL
+    // drivers.
+    static bool invalidateFBSupported = gl->IsSupported(gl::GLFeature::invalidate_framebuffer);
+    if (!invalidateFBSupported)
+        return;
+
     if (!fb && !isDefaultFB) {
         dom::Sequence<GLenum> tmpAttachments;
         TranslateDefaultAttachments(attachments, &tmpAttachments);
         gl->fInvalidateSubFramebuffer(target, tmpAttachments.Length(), tmpAttachments.Elements(),
                                       x, y, width, height);
     } else {
         gl->fInvalidateSubFramebuffer(target, attachments.Length(), attachments.Elements(),
                                       x, y, width, height);
--- a/dom/canvas/WebGLContext.cpp
+++ b/dom/canvas/WebGLContext.cpp
@@ -916,16 +916,18 @@ WebGLContext::SetDimensions(int32_t sign
     mOptions.antialias = gl->Caps().antialias;
 
     MakeContextCurrent();
 
     gl->fViewport(0, 0, mWidth, mHeight);
     mViewportWidth = mWidth;
     mViewportHeight = mHeight;
 
+    gl->fScissor(0, 0, mWidth, mHeight);
+
     // Make sure that we clear this out, otherwise
     // we'll end up displaying random memory
     gl->fBindFramebuffer(LOCAL_GL_FRAMEBUFFER, 0);
 
     AssertCachedBindings();
     AssertCachedState();
 
     // Clear immediately, because we need to present the cleared initial
--- a/dom/canvas/WebGLFramebuffer.cpp
+++ b/dom/canvas/WebGLFramebuffer.cpp
@@ -259,19 +259,16 @@ IsValidFBORenderbufferStencilFormat(GLen
 {
     return internalFormat == LOCAL_GL_STENCIL_INDEX8;
 }
 
 bool
 WebGLContext::IsFormatValidForFB(GLenum sizedFormat) const
 {
     switch (sizedFormat) {
-    case LOCAL_GL_ALPHA8:
-    case LOCAL_GL_LUMINANCE8:
-    case LOCAL_GL_LUMINANCE8_ALPHA8:
     case LOCAL_GL_RGB8:
     case LOCAL_GL_RGBA8:
     case LOCAL_GL_RGB565:
     case LOCAL_GL_RGB5_A1:
     case LOCAL_GL_RGBA4:
         return true;
 
     case LOCAL_GL_SRGB8:
--- a/dom/canvas/test/_webgl-conformance.ini
+++ b/dom/canvas/test/_webgl-conformance.ini
@@ -1,15 +1,15 @@
 # This is a GENERATED FILE. Do not edit it directly.
 # Regenerated it by using `python generate-wrappers-and-manifest.py`.
 # Mark skipped tests in mochitest-errata.ini.
 # Mark failing tests in mochi-single.html.
 
 [DEFAULT]
-skip-if = e10s || os == 'b2g' || ((os == 'linux') && (buildapp == 'b2g')) || ((os == 'linux') && (buildapp == 'mulet')) # Bug 1136181 disabled on B2G Desktop and Mulet for intermittent failures
+skip-if = e10s || os == 'b2g' || ((os == 'linux') && (buildapp == 'b2g')) || ((os == 'linux') && (buildapp == 'mulet'))
 
 support-files = webgl-conformance/../webgl-mochitest/driver-info.js
                 webgl-conformance/always-fail.html
                 webgl-conformance/conformance/00_readme.txt
                 webgl-conformance/conformance/00_test_list.txt
                 webgl-conformance/conformance/LICENSE_CHROMIUM
                 webgl-conformance/conformance/attribs/00_test_list.txt
                 webgl-conformance/conformance/attribs/gl-enable-vertex-attrib.html
@@ -521,17 +521,16 @@ skip-if = (os == 'b2g')
 [webgl-conformance/_wrappers/test_conformance__extensions__ext-texture-filter-anisotropic.html]
 [webgl-conformance/_wrappers/test_conformance__extensions__oes-texture-float.html]
 fail-if = (os == 'linux')
 [webgl-conformance/_wrappers/test_conformance__extensions__oes-vertex-array-object.html]
 [webgl-conformance/_wrappers/test_conformance__extensions__webgl-debug-renderer-info.html]
 [webgl-conformance/_wrappers/test_conformance__extensions__webgl-debug-shaders.html]
 [webgl-conformance/_wrappers/test_conformance__extensions__webgl-compressed-texture-etc1.html]
 [webgl-conformance/_wrappers/test_conformance__extensions__webgl-compressed-texture-s3tc.html]
-fail-if = (os == 'mac' && os_version == '10.10')
 [webgl-conformance/_wrappers/test_conformance__extensions__ext-sRGB.html]
 [webgl-conformance/_wrappers/test_conformance__extensions__ext-shader-texture-lod.html]
 [webgl-conformance/_wrappers/test_conformance__glsl__functions__glsl-function.html]
 [webgl-conformance/_wrappers/test_conformance__glsl__functions__glsl-function-abs.html]
 [webgl-conformance/_wrappers/test_conformance__glsl__functions__glsl-function-acos.html]
 [webgl-conformance/_wrappers/test_conformance__glsl__functions__glsl-function-asin.html]
 [webgl-conformance/_wrappers/test_conformance__glsl__functions__glsl-function-atan.html]
 [webgl-conformance/_wrappers/test_conformance__glsl__functions__glsl-function-atan-xy.html]
--- a/dom/canvas/test/reftest/reftest.list
+++ b/dom/canvas/test/reftest/reftest.list
@@ -54,21 +54,21 @@ fuzzy(1,30000) fails-if(gtk2Widget&&brow
                                                                                                          == webgl-color-test.html?frame=1&__&________&premult&alpha  wrapper.html?colors-premult.png
                                                                                                          == webgl-color-test.html?frame=1&aa&________&premult&alpha  wrapper.html?colors-premult.png
                                                                                                          == webgl-color-test.html?frame=1&__&preserve&premult&alpha  wrapper.html?colors-premult.png
                                                                                                          == webgl-color-test.html?frame=1&aa&preserve&premult&alpha  wrapper.html?colors-premult.png
 
                                                                                                          == webgl-color-test.html?frame=6&__&________&_______&_____  wrapper.html?colors-no-alpha.png
                                                                                                          == webgl-color-test.html?frame=6&aa&________&_______&_____  wrapper.html?colors-no-alpha.png
                                                                                                          == webgl-color-test.html?frame=6&__&preserve&_______&_____  wrapper.html?colors-no-alpha.png
-fails-if(winWidget&&layersGPUAccelerated&&d2d)                                                           == webgl-color-test.html?frame=6&aa&preserve&_______&_____  wrapper.html?colors-no-alpha.png
+                                                                                                          == webgl-color-test.html?frame=6&aa&preserve&_______&_____  wrapper.html?colors-no-alpha.png
                                                                                                          == webgl-color-test.html?frame=6&__&________&premult&_____  wrapper.html?colors-no-alpha.png
                                                                                                          == webgl-color-test.html?frame=6&aa&________&premult&_____  wrapper.html?colors-no-alpha.png
                                                                                                          == webgl-color-test.html?frame=6&__&preserve&premult&_____  wrapper.html?colors-no-alpha.png
-fails-if(winWidget&&layersGPUAccelerated&&d2d)                                                           == webgl-color-test.html?frame=6&aa&preserve&premult&_____  wrapper.html?colors-no-alpha.png
+                                                                                                          == webgl-color-test.html?frame=6&aa&preserve&premult&_____  wrapper.html?colors-no-alpha.png
 fuzzy(1,30000) fails-if(gtk2Widget&&browserIsRemote) fails-if(winWidget&&layersGPUAccelerated&&!d2d)     == webgl-color-test.html?frame=6&__&________&_______&alpha  wrapper.html?colors-non-premult.png
 fuzzy(1,30000) fails-if(gtk2Widget&&browserIsRemote) fails-if(winWidget&&layersGPUAccelerated&&!d2d)     == webgl-color-test.html?frame=6&aa&________&_______&alpha  wrapper.html?colors-non-premult.png
 fuzzy(1,30000) fails-if(gtk2Widget&&browserIsRemote) fails-if(winWidget&&layersGPUAccelerated&&!d2d)     == webgl-color-test.html?frame=6&__&preserve&_______&alpha  wrapper.html?colors-non-premult.png
 fuzzy(1,30000) fails-if(gtk2Widget&&browserIsRemote) fails-if(winWidget&&(!layersGPUAccelerated||!d2d))  == webgl-color-test.html?frame=6&aa&preserve&_______&alpha  wrapper.html?colors-non-premult.png
                                                                                                          == webgl-color-test.html?frame=6&__&________&premult&alpha  wrapper.html?colors-premult.png
                                                                                                          == webgl-color-test.html?frame=6&aa&________&premult&alpha  wrapper.html?colors-premult.png
                                                                                                          == webgl-color-test.html?frame=6&__&preserve&premult&alpha  wrapper.html?colors-premult.png
                                                                                                          == webgl-color-test.html?frame=6&aa&preserve&premult&alpha  wrapper.html?colors-premult.png
@@ -88,21 +88,21 @@ fuzzy(1,30000) fails-if(gtk2Widget&&brow
                                                                                                         pref(webgl.force-layers-readback,true)  == webgl-color-test.html?frame=1&readback&__&________&premult&alpha  wrapper.html?colors-premult.png
                                                                                                         pref(webgl.force-layers-readback,true)  == webgl-color-test.html?frame=1&readback&aa&________&premult&alpha  wrapper.html?colors-premult.png
                                                                                                         pref(webgl.force-layers-readback,true)  == webgl-color-test.html?frame=1&readback&__&preserve&premult&alpha  wrapper.html?colors-premult.png
                                                                                                         pref(webgl.force-layers-readback,true)  == webgl-color-test.html?frame=1&readback&aa&preserve&premult&alpha  wrapper.html?colors-premult.png
 
                                                                                                         pref(webgl.force-layers-readback,true)  == webgl-color-test.html?frame=6&readback&__&________&_______&_____  wrapper.html?colors-no-alpha.png
                                                                                                         pref(webgl.force-layers-readback,true)  == webgl-color-test.html?frame=6&readback&aa&________&_______&_____  wrapper.html?colors-no-alpha.png
                                                                                                         pref(webgl.force-layers-readback,true)  == webgl-color-test.html?frame=6&readback&__&preserve&_______&_____  wrapper.html?colors-no-alpha.png
-fails-if(winWidget&&layersGPUAccelerated&&d2d)                                                          pref(webgl.force-layers-readback,true)  == webgl-color-test.html?frame=6&readback&aa&preserve&_______&_____  wrapper.html?colors-no-alpha.png
+                                                                                                         pref(webgl.force-layers-readback,true)  == webgl-color-test.html?frame=6&readback&aa&preserve&_______&_____  wrapper.html?colors-no-alpha.png
                                                                                                         pref(webgl.force-layers-readback,true)  == webgl-color-test.html?frame=6&readback&__&________&premult&_____  wrapper.html?colors-no-alpha.png
                                                                                                         pref(webgl.force-layers-readback,true)  == webgl-color-test.html?frame=6&readback&aa&________&premult&_____  wrapper.html?colors-no-alpha.png
                                                                                                         pref(webgl.force-layers-readback,true)  == webgl-color-test.html?frame=6&readback&__&preserve&premult&_____  wrapper.html?colors-no-alpha.png
-random-if(winWidget&&layersGPUAccelerated&&d2d)                                                         pref(webgl.force-layers-readback,true)  == webgl-color-test.html?frame=6&readback&aa&preserve&premult&_____  wrapper.html?colors-no-alpha.png
+                                                                                                        pref(webgl.force-layers-readback,true)  == webgl-color-test.html?frame=6&readback&aa&preserve&premult&_____  wrapper.html?colors-no-alpha.png
 fuzzy(1,30000) fails-if(gtk2Widget&&browserIsRemote) fails-if(winWidget&&layersGPUAccelerated&&!d2d)    pref(webgl.force-layers-readback,true)  == webgl-color-test.html?frame=6&readback&__&________&_______&alpha  wrapper.html?colors-non-premult.png
 fuzzy(1,30000) fails-if(gtk2Widget&&browserIsRemote) fails-if(winWidget&&layersGPUAccelerated&&!d2d)    pref(webgl.force-layers-readback,true)  == webgl-color-test.html?frame=6&readback&aa&________&_______&alpha  wrapper.html?colors-non-premult.png
 fuzzy(1,30000) fails-if(gtk2Widget&&browserIsRemote) fails-if(winWidget&&layersGPUAccelerated&&!d2d)    pref(webgl.force-layers-readback,true)  == webgl-color-test.html?frame=6&readback&__&preserve&_______&alpha  wrapper.html?colors-non-premult.png
 fuzzy(1,30000) fails-if(gtk2Widget&&browserIsRemote) fails-if(winWidget&&(!layersGPUAccelerated||!d2d)) pref(webgl.force-layers-readback,true)  == webgl-color-test.html?frame=6&readback&aa&preserve&_______&alpha  wrapper.html?colors-non-premult.png
                                                                                                         pref(webgl.force-layers-readback,true)  == webgl-color-test.html?frame=6&readback&__&________&premult&alpha  wrapper.html?colors-premult.png
                                                                                                         pref(webgl.force-layers-readback,true)  == webgl-color-test.html?frame=6&readback&aa&________&premult&alpha  wrapper.html?colors-premult.png
                                                                                                         pref(webgl.force-layers-readback,true)  == webgl-color-test.html?frame=6&readback&__&preserve&premult&alpha  wrapper.html?colors-premult.png
                                                                                                         pref(webgl.force-layers-readback,true)  == webgl-color-test.html?frame=6&readback&aa&preserve&premult&alpha  wrapper.html?colors-premult.png
--- a/dom/canvas/test/webgl-conformance/mochitest-errata.ini
+++ b/dom/canvas/test/webgl-conformance/mochitest-errata.ini
@@ -1,8 +1,12 @@
+# *** WARNING! ***
+# Modification to this file only take effect after running
+# generate-wrappers-and-manifest.py
+
 # See python/mozbuild/mozbuild/mozinfo.py for incoming data.
 
 [DEFAULT]
 # No e10s yet.
 # 'B2G Desktop Linux' fails to create WebGL contexts.
 # Also skip B2G for now, until we get a handle on the longer tail of emulator
 # bugs.
 # Bug 1136181 disabled on B2G Desktop and Mulet for intermittent failures
@@ -91,19 +95,16 @@ skip-if = (os == 'b2g')
 # Failures after enabling color_buffer_[half_]float.
 fail-if = (os == 'linux')
 
 ########################################################################
 # Mac
 [_wrappers/test_conformance__canvas__drawingbuffer-static-canvas-test.html]
 # Intermittent crash on OSX.
 skip-if = os == 'mac'
-[_wrappers/test_conformance__extensions__webgl-compressed-texture-s3tc.html]
-# Fails on OS X 10.10
-fail-if = (os == 'mac' && os_version == '10.10')
 [_wrappers/test_conformance__misc__object-deletion-behaviour.html]
 # Fails on OS X 10.10
 fail-if = (os == 'mac' && os_version == '10.10')
 [_wrappers/test_conformance__rendering__line-loop-tri-fan.html]
 # Fails on OS X 10.10
 fail-if = (os == 'mac' && os_version == '10.10')
 [_wrappers/test_conformance__rendering__point-size.html]
 # Fails on OS X 10.10
--- a/dom/events/MessageEvent.cpp
+++ b/dom/events/MessageEvent.cpp
@@ -8,16 +8,19 @@
 #include "mozilla/dom/MessagePort.h"
 #include "mozilla/dom/MessagePortBinding.h"
 #include "mozilla/dom/MessagePortList.h"
 
 #include "mozilla/HoldDropJSObjects.h"
 #include "jsapi.h"
 #include "nsGlobalWindow.h" // So we can assign an nsGlobalWindow* to mWindowSource
 
+#include "ServiceWorker.h"
+#include "ServiceWorkerClient.h"
+
 namespace mozilla {
 namespace dom {
 
 NS_IMPL_CYCLE_COLLECTION_CLASS(MessageEvent)
 
 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(MessageEvent, Event)
   tmp->mData.setUndefined();
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mWindowSource)
@@ -98,22 +101,24 @@ MessageEvent::GetLastEventId(nsAString& 
 NS_IMETHODIMP
 MessageEvent::GetSource(nsIDOMWindow** aSource)
 {
   NS_IF_ADDREF(*aSource = mWindowSource);
   return NS_OK;
 }
 
 void
-MessageEvent::GetSource(Nullable<OwningWindowProxyOrMessagePort>& aValue) const
+MessageEvent::GetSource(Nullable<OwningWindowProxyOrMessagePortOrClient>& aValue) const
 {
   if (mWindowSource) {
     aValue.SetValue().SetAsWindowProxy() = mWindowSource;
   } else if (mPortSource) {
     aValue.SetValue().SetAsMessagePort() = mPortSource;
+  } else if (mClientSource) {
+    aValue.SetValue().SetAsClient() = mClientSource;
   }
 }
 
 /* static */ already_AddRefed<MessageEvent>
 MessageEvent::Constructor(const GlobalObject& aGlobal,
                           const nsAString& aType,
                           const MessageEventInit& aParam,
                           ErrorResult& aRv)
@@ -201,16 +206,22 @@ MessageEvent::SetPorts(MessagePortList* 
 }
 
 void
 MessageEvent::SetSource(mozilla::dom::MessagePort* aPort)
 {
   mPortSource = aPort;
 }
 
+void
+MessageEvent::SetSource(mozilla::dom::workers::ServiceWorkerClient* aClient)
+{
+  mClientSource = aClient;
+}
+
 } // namespace dom
 } // namespace mozilla
 
 using namespace mozilla;
 using namespace mozilla::dom;
 
 nsresult
 NS_NewDOMMessageEvent(nsIDOMEvent** aInstancePtrResult,
--- a/dom/events/MessageEvent.h
+++ b/dom/events/MessageEvent.h
@@ -13,17 +13,23 @@
 
 namespace mozilla {
 namespace dom {
 
 struct MessageEventInit;
 class MessagePort;
 class MessagePortBase;
 class MessagePortList;
-class OwningWindowProxyOrMessagePort;
+class OwningWindowProxyOrMessagePortOrClient;
+
+namespace workers {
+
+class ServiceWorkerClient;
+
+}
 
 /**
  * Implements the MessageEvent event, used for cross-document messaging and
  * server-sent events.
  *
  * See http://www.whatwg.org/specs/web-apps/current-work/#messageevent for
  * further details.
  */
@@ -43,28 +49,30 @@ public:
   // Forward to base class
   NS_FORWARD_TO_EVENT
 
   virtual JSObject* WrapObjectInternal(JSContext* aCx) MOZ_OVERRIDE;
 
   void GetData(JSContext* aCx, JS::MutableHandle<JS::Value> aData,
                ErrorResult& aRv);
 
-  void GetSource(Nullable<OwningWindowProxyOrMessagePort>& aValue) const;
+  void GetSource(Nullable<OwningWindowProxyOrMessagePortOrClient>& aValue) const;
 
   MessagePortList* GetPorts()
   {
     return mPorts;
   }
 
   void SetPorts(MessagePortList* aPorts);
 
   // Non WebIDL methods
   void SetSource(mozilla::dom::MessagePort* aPort);
 
+  void SetSource(workers::ServiceWorkerClient* aClient);
+
   void SetSource(nsPIDOMWindow* aWindow)
   {
     mWindowSource = aWindow;
   }
 
   static already_AddRefed<MessageEvent>
   Constructor(const GlobalObject& aGlobal,
               const nsAString& aType,
@@ -81,15 +89,16 @@ protected:
   ~MessageEvent();
 
 private:
   JS::Heap<JS::Value> mData;
   nsString mOrigin;
   nsString mLastEventId;
   nsCOMPtr<nsIDOMWindow> mWindowSource;
   nsRefPtr<MessagePortBase> mPortSource;
+  nsRefPtr<workers::ServiceWorkerClient> mClientSource;
   nsRefPtr<MessagePortList> mPorts;
 };
 
 } // namespace dom
 } // namespace mozilla
 
 #endif // mozilla_dom_MessageEvent_h_
--- a/dom/interfaces/base/nsIDOMWindowUtils.idl
+++ b/dom/interfaces/base/nsIDOMWindowUtils.idl
@@ -45,17 +45,17 @@ interface nsIFile;
 interface nsIDOMClientRect;
 interface nsIURI;
 interface nsIDOMEventTarget;
 interface nsIRunnable;
 interface nsITranslationNodeList;
 interface nsIJSRAIIHelper;
 interface nsIContentPermissionRequest;
 
-[scriptable, uuid(b39cb73f-ff99-4744-9780-2c26f830c6f7)]
+[scriptable, uuid(dde97573-f4cf-45ce-bbb0-5af4e5f77440)]
 interface nsIDOMWindowUtils : nsISupports {
 
   /**
    * Image animation mode of the window. When this attribute's value
    * is changed, the implementation should set all images in the window
    * to the given value. That is, when set to kDontAnimMode, all images
    * will stop animating. The attribute's value must be one of the
    * animationMode values from imgIContainer.
@@ -200,38 +200,37 @@ interface nsIDOMWindowUtils : nsISupport
    * Get/set the resolution at which rescalable web content is drawn.
    *
    * Setting a new resolution does *not* trigger reflow.  This API is
    * entirely separate from textZoom and fullZoom; a resolution scale
    * can be applied together with both textZoom and fullZoom.
    *
    * The effect of this API is for gfx code to allocate more or fewer
    * pixels for rescalable content by a factor of |resolution| in
-   * either or both dimensions.  The scale at which the content is
-   * displayed does not change; if that is desired, use
-   * setResolutionAndScaleTo() instead.
+   * both dimensions.  The scale at which the content is displayed does
+   * not change; if that is desired, use setResolutionAndScaleTo() instead.
    *
    * The caller of this method must have chrome privileges.
    */
-  void setResolution(in float aXResolution, in float aYResolution);
+  void setResolution(in float aResolution);
 
-  void getResolution(out float aXResolution, out float aYResolution);
+  void getResolution(out float aResolution);
 
   /**
    * Similar to setResolution(), but also scales the content by the
    * amount of the resolution, so that it is displayed at a
    * correspondingly larger or smaller size, without the need for
    * the caller to set an additional transform.
    *
    * This can be used to implement a non-reflowing scale-zoom, e.g.
    * for pinch-zoom on mobile platforms.
    *
    * The caller of this method must have chrome privileges.
    */
-  void setResolutionAndScaleTo(in float aXResolution, in float aYResolution);
+  void setResolutionAndScaleTo(in float aResolution);
 
   /**
    * Whether the resolution has been set by the user.
    * This gives a way to check whether the provided resolution is the default
    * value or restored from a previous session.
    *
    * Can only be accessed with chrome privileges.
    */
--- a/dom/interfaces/base/nsIServiceWorkerManager.idl
+++ b/dom/interfaces/base/nsIServiceWorkerManager.idl
@@ -14,28 +14,28 @@ interface nsIURI;
 interface nsIServiceWorkerUnregisterCallback : nsISupports
 {
   // aState is true if the unregistration succeded.
   // It's false if this ServiceWorkerRegistration doesn't exist.
   [noscript] void UnregisterSucceeded(in bool aState);
   [noscript] void UnregisterFailed();
 };
 
-[builtinclass, uuid(464882c8-81c0-4620-b9c4-44c12085b65b)]
+[builtinclass, uuid(706c3e6b-c9d2-4857-893d-4b4845fec48f)]
 interface nsIServiceWorkerManager : nsISupports
 {
   /**
    * Registers a ServiceWorker with script loaded from `aScriptURI` to act as
    * the ServiceWorker for aScope.  Requires a valid entry settings object on
    * the stack. This means you must call this from content code 'within'
    * a window.
    *
    * Returns a Promise.
    */
-  nsISupports register(in nsIDOMWindow aWindow, in DOMString aScope, in DOMString aScriptURI);
+  nsISupports register(in nsIDOMWindow aWindow, in nsIURI aScope, in nsIURI aScriptURI);
 
   /**
    * Unregister an existing ServiceWorker registration for `aScope`.
    * It keeps aCallback alive until the operation is concluded.
    */
   void unregister(in nsIPrincipal aPrincipal,
                   in nsIServiceWorkerUnregisterCallback aCallback,
                   in DOMString aScope);
--- a/dom/ipc/TabChild.cpp
+++ b/dom/ipc/TabChild.cpp
@@ -385,17 +385,17 @@ TabChildBase::HandlePossibleViewportChan
   }
 
   metrics.SetCumulativeResolution(metrics.GetZoom()
                                 / metrics.GetDevPixelsPerCSSPixel()
                                 * ParentLayerToLayerScale(1));
   // This is the root layer, so the cumulative resolution is the same
   // as the resolution.
   metrics.SetPresShellResolution(metrics.GetCumulativeResolution().ToScaleFactor().scale);
-  utils->SetResolutionAndScaleTo(metrics.GetPresShellResolution(), metrics.GetPresShellResolution());
+  utils->SetResolutionAndScaleTo(metrics.GetPresShellResolution());
 
   CSSSize scrollPort = metrics.CalculateCompositedSizeInCssPixels();
   utils->SetScrollPositionClampingScrollPortSize(scrollPort.width, scrollPort.height);
 
   // The call to GetPageSize forces a resize event to content, so we need to
   // make sure that we have the right CSS viewport and
   // scrollPositionClampingScrollPortSize set up before that happens.
 
@@ -912,18 +912,17 @@ TabChild::Observe(nsISupports *aSubject,
         mContentDocumentIsDisplayed = true;
 
         // In some cases before-first-paint gets called before
         // RecvUpdateDimensions is called and therefore before we have an
         // mInnerSize value set. In such cases defer initializing the viewport
         // until we we get an inner size.
         if (HasValidInnerSize()) {
           InitializeRootMetrics();
-          utils->SetResolutionAndScaleTo(mLastRootMetrics.GetPresShellResolution(),
-                                         mLastRootMetrics.GetPresShellResolution());
+          utils->SetResolutionAndScaleTo(mLastRootMetrics.GetPresShellResolution());
           HandlePossibleViewportChange(mInnerSize);
         }
       }
     }
   }
 
   return NS_OK;
 }
@@ -2339,17 +2338,17 @@ TabChild::CancelTapTracking()
 float
 TabChild::GetPresShellResolution() const
 {
   nsCOMPtr<nsIDocument> document(GetDocument());
   nsIPresShell* shell = document->GetShell();
   if (!shell) {
     return 1.0f;
   }
-  return shell->GetXResolution();
+  return shell->GetResolution();
 }
 
 bool
 TabChild::RecvRealTouchEvent(const WidgetTouchEvent& aEvent,
                              const ScrollableLayerGuid& aGuid,
                              const uint64_t& aInputBlockId)
 {
   TABC_LOG("Receiving touch event of type %d\n", aEvent.message);
--- a/dom/ipc/TabParent.cpp
+++ b/dom/ipc/TabParent.cpp
@@ -231,18 +231,16 @@ private:
         }
         mFD = nullptr;
     }
 };
 
 namespace mozilla {
 namespace dom {
 
-TabParent* sEventCapturer;
-
 TabParent *TabParent::mIMETabParent = nullptr;
 TabParent::LayerToTabParentTable* TabParent::sLayerToTabParentTable = nullptr;
 
 NS_IMPL_ISUPPORTS(TabParent,
                   nsITabParent,
                   nsIAuthPromptProvider,
                   nsISecureBrowserUI,
                   nsISupportsWeakReference)
@@ -257,17 +255,16 @@ TabParent::TabParent(nsIContentParent* a
   , mIMESelectionFocus(0)
   , mWritingMode()
   , mIMEComposing(false)
   , mIMECompositionEnding(false)
   , mIMEEventCountAfterEnding(0)
   , mIMECompositionStart(0)
   , mIMESeqno(0)
   , mIMECompositionRectOffset(0)
-  , mEventCaptureDepth(0)
   , mRect(0, 0, 0, 0)
   , mDimensions(0, 0)
   , mOrientation(0)
   , mDPI(0)
   , mDefaultScale(0)
   , mShown(false)
   , mUpdatedDimensions(false)
   , mManager(aManager)
@@ -396,19 +393,16 @@ TabParent::Recv__delete__()
   }
 
   return true;
 }
 
 void
 TabParent::ActorDestroy(ActorDestroyReason why)
 {
-  if (sEventCapturer == this) {
-    sEventCapturer = nullptr;
-  }
   if (mIMETabParent == this) {
     mIMETabParent = nullptr;
   }
   nsRefPtr<nsFrameLoader> frameLoader = GetFrameLoader();
   nsCOMPtr<nsIObserverService> os = services::GetObserverService();
   nsRefPtr<nsFrameMessageManager> fmm;
   if (frameLoader) {
     fmm = frameLoader->GetFrameMessageManager();
@@ -1320,32 +1314,17 @@ bool TabParent::SendRealKeyEvent(WidgetK
 }
 
 bool TabParent::SendRealTouchEvent(WidgetTouchEvent& event)
 {
   if (mIsDestroyed) {
     return false;
   }
   if (event.message == NS_TOUCH_START) {
-    // Adjust the widget coordinates to be relative to our frame.
-    nsRefPtr<nsFrameLoader> frameLoader = GetFrameLoader();
-    if (!frameLoader) {
-      // No frame anymore?
-      sEventCapturer = nullptr;
-      return false;
-    }
-
     mChildProcessOffsetAtTouchStart = GetChildProcessOffset();
-
-    MOZ_ASSERT((!sEventCapturer && mEventCaptureDepth == 0) ||
-               (sEventCapturer == this && mEventCaptureDepth > 0));
-    // We want to capture all remaining touch events in this series
-    // for fast-path dispatch.
-    sEventCapturer = this;
-    ++mEventCaptureDepth;
   }
 
   // PresShell::HandleEventInternal adds touches on touch end/cancel.  This
   // confuses remote content and the panning and zooming logic into thinking
   // that the added touches are part of the touchend/cancel, when actually
   // they're not.
   if (event.message == NS_TOUCH_END || event.message == NS_TOUCH_CANCEL) {
     for (int i = event.touches.Length() - 1; i >= 0; i--) {
@@ -1368,51 +1347,16 @@ bool TabParent::SendRealTouchEvent(Widge
     event.touches[i]->mRefPoint += offset;
   }
 
   return (event.message == NS_TOUCH_MOVE) ?
     PBrowserParent::SendRealTouchMoveEvent(event, guid, blockId) :
     PBrowserParent::SendRealTouchEvent(event, guid, blockId);
 }
 
-/*static*/ TabParent*
-TabParent::GetEventCapturer()
-{
-  return sEventCapturer;
-}
-
-bool
-TabParent::TryCapture(const WidgetGUIEvent& aEvent)
-{
-  MOZ_ASSERT(sEventCapturer == this && mEventCaptureDepth > 0);
-
-  if (aEvent.mClass != eTouchEventClass) {
-    // Only capture of touch events is implemented, for now.
-    return false;
-  }
-
-  WidgetTouchEvent event(*aEvent.AsTouchEvent());
-
-  bool isTouchPointUp = (event.message == NS_TOUCH_END ||
-                         event.message == NS_TOUCH_CANCEL);
-  if (event.message == NS_TOUCH_START || isTouchPointUp) {
-    // Let the DOM see touch start/end events so that its touch-point
-    // state stays consistent.
-    if (isTouchPointUp && 0 == --mEventCaptureDepth) {
-      // All event series are un-captured, don't try to catch any
-      // more.
-      sEventCapturer = nullptr;
-    }
-    return false;
-  }
-
-  SendRealTouchEvent(event);
-  return true;
-}
-
 bool
 TabParent::RecvSyncMessage(const nsString& aMessage,
                            const ClonedMessageData& aData,
                            InfallibleTArray<CpowEntry>&& aCpows,
                            const IPC::Principal& aPrincipal,
                            InfallibleTArray<nsString>* aJSONRetVal)
 {
   // FIXME Permission check for TabParent in Content process
@@ -2545,21 +2489,16 @@ TabParent::GetLoadContext()
                                   mChromeFlags & nsIWebBrowserChrome::CHROME_PRIVATE_WINDOW,
                                   mChromeFlags & nsIWebBrowserChrome::CHROME_REMOTE_WINDOW,
                                   IsBrowserElement());
     mLoadContext = loadContext;
   }
   return loadContext.forget();
 }
 
-/* Be careful if you call this method while proceding a real touch event. For
- * example sending a touchstart during a real touchend may results into
- * a busted mEventCaptureDepth and following touch events may not do what you
- * expect.
- */
 NS_IMETHODIMP
 TabParent::InjectTouchEvent(const nsAString& aType,
                             uint32_t* aIdentifiers,
                             int32_t* aXs,
                             int32_t* aYs,
                             uint32_t* aRxs,
                             uint32_t* aRys,
                             float* aRotationAngles,
@@ -2609,21 +2548,16 @@ TabParent::InjectTouchEvent(const nsAStr
 
     // Consider all injected touch events as changedTouches. For more details
     // about the meaning of changedTouches for each event, see
     // https://developer.mozilla.org/docs/Web/API/TouchEvent.changedTouches
     t->mChanged = true;
     event.touches.AppendElement(t);
   }
 
-  if ((msg == NS_TOUCH_END || msg == NS_TOUCH_CANCEL) && sEventCapturer) {
-    WidgetGUIEvent* guiEvent = event.AsGUIEvent();
-    TryCapture(*guiEvent);
-  }
-
   SendRealTouchEvent(event);
   return NS_OK;
 }
 
 NS_IMETHODIMP
 TabParent::GetUseAsyncPanZoom(bool* useAsyncPanZoom)
 {
   *useAsyncPanZoom = gfxPrefs::AsyncPanZoomEnabled();
--- a/dom/ipc/TabParent.h
+++ b/dom/ipc/TabParent.h
@@ -97,40 +97,16 @@ public:
     void SetBrowserDOMWindow(nsIBrowserDOMWindow* aBrowserDOMWindow) {
         mBrowserDOMWindow = aBrowserDOMWindow;
     }
 
     already_AddRefed<nsILoadContext> GetLoadContext();
 
     nsIXULBrowserWindow* GetXULBrowserWindow();
 
-    /**
-     * Return the TabParent that has decided it wants to capture an
-     * event series for fast-path dispatch to its subprocess, if one
-     * has.
-     *
-     * DOM event dispatch and widget are free to ignore capture
-     * requests from TabParents; the end result wrt remote content is
-     * (must be) always the same, albeit usually slower without
-     * subprocess capturing.  This allows frontends/widget backends to
-     * "opt in" to faster cross-process dispatch.
-     */
-    static TabParent* GetEventCapturer();
-    /**
-     * If this is the current event capturer, give this a chance to
-     * capture the event.  If it was captured, return true, false
-     * otherwise.  Un-captured events should follow normal DOM
-     * dispatch; captured events should result in no further
-     * processing from the caller of TryCapture().
-     *
-     * It's an error to call TryCapture() if this isn't the event
-     * capturer.
-     */
-    bool TryCapture(const WidgetGUIEvent& aEvent);
-
     void Destroy();
 
     virtual bool RecvMoveFocus(const bool& aForward) MOZ_OVERRIDE;
     virtual bool RecvEvent(const RemoteDOMEvent& aEvent) MOZ_OVERRIDE;
     virtual bool RecvReplyKeyEvent(const WidgetKeyboardEvent& aEvent) MOZ_OVERRIDE;
     virtual bool RecvDispatchAfterKeyboardEvent(const WidgetKeyboardEvent& aEvent) MOZ_OVERRIDE;
     virtual bool RecvBrowserFrameOpenWindow(PBrowserParent* aOpener,
                                             const nsString& aURL,
@@ -435,19 +411,16 @@ protected:
     uint32_t mIMESeqno;
 
     uint32_t mIMECompositionRectOffset;
     InfallibleTArray<LayoutDeviceIntRect> mIMECompositionRects;
     uint32_t mIMECaretOffset;
     LayoutDeviceIntRect mIMECaretRect;
     LayoutDeviceIntRect mIMEEditorRect;
 
-    // The number of event series we're currently capturing.
-    int32_t mEventCaptureDepth;
-
     nsIntRect mRect;
     ScreenIntSize mDimensions;
     ScreenOrientation mOrientation;
     float mDPI;
     CSSToLayoutDeviceScale mDefaultScale;
     bool mShown;
     bool mUpdatedDimensions;
 
--- a/dom/media/MediaDecoder.cpp
+++ b/dom/media/MediaDecoder.cpp
@@ -186,38 +186,38 @@ void MediaDecoder::UpdateDormantState(bo
   if (prevDormant == mIsDormant) {
     // No update to dormant state
     return;
   }
 
   if (mIsDormant) {
     DECODER_LOG("UpdateDormantState() entering DORMANT state");
     // enter dormant state
-    nsCOMPtr<nsIRunnable> event =
+    RefPtr<nsRunnable> event =
       NS_NewRunnableMethodWithArg<bool>(
         mDecoderStateMachine,
         &MediaDecoderStateMachine::SetDormant,
         true);
-    mDecoderStateMachine->GetStateMachineThread()->Dispatch(event, NS_DISPATCH_NORMAL);
+    mDecoderStateMachine->TaskQueue()->Dispatch(event);
 
     if (IsEnded()) {
       mWasEndedWhenEnteredDormant = true;
     }
     mNextState = mPlayState;
     ChangeState(PLAY_STATE_LOADING);
   } else {
     DECODER_LOG("UpdateDormantState() leaving DORMANT state");
     // exit dormant state
     // trigger to state machine.
-    nsCOMPtr<nsIRunnable> event =
+    RefPtr<nsRunnable> event =
       NS_NewRunnableMethodWithArg<bool>(
         mDecoderStateMachine,
         &MediaDecoderStateMachine::SetDormant,
         false);
-    mDecoderStateMachine->GetStateMachineThread()->Dispatch(event, NS_DISPATCH_NORMAL);
+    mDecoderStateMachine->TaskQueue()->Dispatch(event);
   }
 }
 
 void MediaDecoder::DormantTimerExpired(nsITimer* aTimer, void* aClosure)
 {
   MOZ_ASSERT(aClosure);
   MediaDecoder* decoder = static_cast<MediaDecoder*>(aClosure);
   ReentrantMonitorAutoEnter mon(decoder->GetReentrantMonitor());
@@ -745,31 +745,31 @@ nsresult MediaDecoder::ScheduleStateMach
   MOZ_ASSERT(NS_IsMainThread());
   NS_ASSERTION(mDecoderStateMachine,
                "Must have state machine to start state machine thread");
   NS_ENSURE_STATE(mDecoderStateMachine);
 
   if (mShuttingDown)
     return NS_OK;
   ReentrantMonitorAutoEnter mon(GetReentrantMonitor());
-  return mDecoderStateMachine->ScheduleStateMachine();
+  mDecoderStateMachine->ScheduleStateMachine();
+  return NS_OK;
 }
 
 nsresult MediaDecoder::Play()
 {
   MOZ_ASSERT(NS_IsMainThread());
   ReentrantMonitorAutoEnter mon(GetReentrantMonitor());
   UpdateDormantState(false /* aDormantTimeout */, true /* aActivity */);
 
   NS_ASSERTION(mDecoderStateMachine != nullptr, "Should have state machine.");
   if (mPausedForPlaybackRateNull) {
     return NS_OK;
   }
-  nsresult res = ScheduleStateMachineThread();
-  NS_ENSURE_SUCCESS(res,res);
+  ScheduleStateMachineThread();
   if (IsEnded()) {
     return Seek(0, SeekTarget::PrevSyncPoint);
   } else if (mPlayState == PLAY_STATE_LOADING || mPlayState == PLAY_STATE_SEEKING) {
     mNextState = PLAY_STATE_PLAYING;
     return NS_OK;
   }
 
   ChangeState(PLAY_STATE_PLAYING);
@@ -1312,17 +1312,17 @@ void MediaDecoder::ApplyStateToStateMach
   GetReentrantMonitor().AssertCurrentThreadIn();
 
   if (mDecoderStateMachine) {
     switch (aState) {
       case PLAY_STATE_PLAYING:
         mDecoderStateMachine->Play();
         break;
       case PLAY_STATE_SEEKING:
-        mSeekRequest.Begin(ProxyMediaCall(mDecoderStateMachine->GetStateMachineThread(),
+        mSeekRequest.Begin(ProxyMediaCall(mDecoderStateMachine->TaskQueue(),
                                           mDecoderStateMachine.get(), __func__,
                                           &MediaDecoderStateMachine::Seek, mRequestedSeekTarget)
           ->RefableThen(NS_GetCurrentThread(), __func__, this,
                         &MediaDecoder::OnSeekResolved, &MediaDecoder::OnSeekRejected));
         mRequestedSeekTarget.Reset();
         break;
       default:
         // The state machine checks for things like PAUSED in RunStateMachine.
--- a/dom/media/MediaDecoderStateMachine.cpp
+++ b/dom/media/MediaDecoderStateMachine.cpp
@@ -8,17 +8,17 @@
 #include "windows.h"
 #include "mmsystem.h"
 #endif
 
 #include "mozilla/DebugOnly.h"
 #include <stdint.h>
 
 #include "MediaDecoderStateMachine.h"
-#include "MediaDecoderStateMachineScheduler.h"
+#include "MediaTimer.h"
 #include "AudioSink.h"
 #include "nsTArray.h"
 #include "MediaDecoder.h"
 #include "MediaDecoderReader.h"
 #include "mozilla/MathAlgorithms.h"
 #include "mozilla/mozalloc.h"
 #include "VideoUtils.h"
 #include "mozilla/dom/TimeRanges.h"
@@ -196,19 +196,19 @@ static const uint32_t MAX_VIDEO_QUEUE_SI
 
 static uint32_t sVideoQueueDefaultSize = MAX_VIDEO_QUEUE_SIZE;
 static uint32_t sVideoQueueHWAccelSize = MIN_VIDEO_QUEUE_SIZE;
 
 MediaDecoderStateMachine::MediaDecoderStateMachine(MediaDecoder* aDecoder,
                                                    MediaDecoderReader* aReader,
                                                    bool aRealTime) :
   mDecoder(aDecoder),
-  mScheduler(new MediaDecoderStateMachineScheduler(
-      aDecoder->GetReentrantMonitor(),
-      &MediaDecoderStateMachine::TimeoutExpired, this, aRealTime)),
+  mRealTime(aRealTime),
+  mDispatchedStateMachine(false),
+  mDelayedScheduler(this),
   mState(DECODER_STATE_DECODING_NONE),
   mPlayDuration(0),
   mStartTime(-1),
   mEndTime(-1),
   mDurationSet(false),
   mFragmentEndTime(-1),
   mReader(aReader),
   mCurrentFrameTime(0),
@@ -243,16 +243,21 @@ MediaDecoderStateMachine::MediaDecoderSt
   mDisabledHardwareAcceleration(false),
   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());
+
   static bool sPrefCacheInit = false;
   if (!sPrefCacheInit) {
     sPrefCacheInit = true;
     Preferences::AddUintVarCache(&sVideoQueueDefaultSize,
                                  "media.video-queue.default-size",
                                  MAX_VIDEO_QUEUE_SIZE);
     Preferences::AddUintVarCache(&sVideoQueueHWAccelSize,
                                  "media.video-queue.hw-accel-size",
@@ -382,17 +387,17 @@ static void WriteVideoToMediaStream(Medi
       aStream->MicrosecondsToStreamTimeRoundDown(aStartMicroseconds);
   aOutput->AppendFrame(image.forget(), duration, aIntrinsicSize);
 }
 
 void MediaDecoderStateMachine::SendStreamData()
 {
   MOZ_ASSERT(OnStateMachineThread(), "Should be on state machine thread");
   AssertCurrentThreadInMonitor();
-  MOZ_ASSERT(!mAudioSink, "Should've been stopped in CallRunStateMachine()");
+  MOZ_ASSERT(!mAudioSink, "Should've been stopped in RunStateMachine()");
 
   DecodedStreamData* stream = mDecoder->GetDecodedStream();
 
   bool finished =
       (!mInfo.HasAudio() || AudioQueue().IsFinished()) &&
       (!mInfo.HasVideo() || VideoQueue().IsFinished());
   if (mDecoder->IsSameOriginMedia()) {
     SourceMediaStream* mediaStream = stream->mStream;
@@ -400,26 +405,26 @@ void MediaDecoderStateMachine::SendStrea
 
     if (!stream->mStreamInitialized) {
       if (mInfo.HasAudio()) {
         TrackID audioTrackId = mInfo.mAudio.mTrackInfo.mOutputId;
         AudioSegment* audio = new AudioSegment();
         mediaStream->AddAudioTrack(audioTrackId, mInfo.mAudio.mRate, 0, audio,
                                    SourceMediaStream::ADDTRACK_QUEUED);
         stream->mStream->DispatchWhenNotEnoughBuffered(audioTrackId,
-            GetStateMachineThread(), GetWakeDecoderRunnable());
+            TaskQueue(), GetWakeDecoderRunnable());
         stream->mNextAudioTime = mStartTime + stream->mInitialTime;
       }
       if (mInfo.HasVideo()) {
         TrackID videoTrackId = mInfo.mVideo.mTrackInfo.mOutputId;
         VideoSegment* video = new VideoSegment();
         mediaStream->AddTrack(videoTrackId, 0, video,
                               SourceMediaStream::ADDTRACK_QUEUED);
         stream->mStream->DispatchWhenNotEnoughBuffered(videoTrackId,
-            GetStateMachineThread(), GetWakeDecoderRunnable());
+            TaskQueue(), GetWakeDecoderRunnable());
 
         // TODO: We can't initialize |mNextVideoTime| until |mStartTime|
         // is set. This is a good indication that DecodedStreamData is in
         // deep coupling with the state machine and we should move the class
         // into MediaDecoderStateMachine.
         stream->mNextVideoTime = mStartTime + stream->mInitialTime;
       }
       mediaStream->FinishAddTracks();
@@ -565,17 +570,17 @@ bool MediaDecoderStateMachine::HaveEnoug
 
   if (stream && stream->mStreamInitialized && !stream->mHaveSentFinishAudio) {
     MOZ_ASSERT(mInfo.HasAudio());
     TrackID audioTrackId = mInfo.mAudio.mTrackInfo.mOutputId;
     if (!stream->mStream->HaveEnoughBuffered(audioTrackId)) {
       return false;
     }
     stream->mStream->DispatchWhenNotEnoughBuffered(audioTrackId,
-        GetStateMachineThread(), GetWakeDecoderRunnable());
+        TaskQueue(), GetWakeDecoderRunnable());
   }
 
   return true;
 }
 
 bool MediaDecoderStateMachine::HaveEnoughDecodedVideo()
 {
   AssertCurrentThreadInMonitor();
@@ -588,17 +593,17 @@ bool MediaDecoderStateMachine::HaveEnoug
 
   if (stream && stream->mStreamInitialized && !stream->mHaveSentFinishVideo) {
     MOZ_ASSERT(mInfo.HasVideo());
     TrackID videoTrackId = mInfo.mVideo.mTrackInfo.mOutputId;
     if (!stream->mStream->HaveEnoughBuffered(videoTrackId)) {
       return false;
     }
     stream->mStream->DispatchWhenNotEnoughBuffered(videoTrackId,
-        GetStateMachineThread(), GetWakeDecoderRunnable());
+        TaskQueue(), GetWakeDecoderRunnable());
   }
 
   return true;
 }
 
 bool
 MediaDecoderStateMachine::NeedToDecodeVideo()
 {
@@ -850,17 +855,17 @@ MediaDecoderStateMachine::OnNotDecoded(M
 
   // If the decoder is waiting for data, we tell it to call us back when the
   // data arrives.
   if (aReason == MediaDecoderReader::WAITING_FOR_DATA) {
     MOZ_ASSERT(mReader->IsWaitForDataSupported(),
                "Readers that send WAITING_FOR_DATA need to implement WaitForData");
     WaitRequestRef(aType).Begin(ProxyMediaCall(DecodeTaskQueue(), mReader.get(), __func__,
                                                &MediaDecoderReader::WaitForData, aType)
-      ->RefableThen(mScheduler.get(), __func__, this,
+      ->RefableThen(TaskQueue(), __func__, this,
                     &MediaDecoderStateMachine::OnWaitForDataResolved,
                     &MediaDecoderStateMachine::OnWaitForDataRejected));
     return;
   }
 
   if (aReason == MediaDecoderReader::CANCELED) {
     DispatchDecodeTasksIfNeeded();
     return;
@@ -1079,17 +1084,17 @@ MediaDecoderStateMachine::CheckIfSeekCom
 
   SAMPLE_LOG("CheckIfSeekComplete() audioSeekComplete=%d videoSeekComplete=%d",
              audioSeekComplete, videoSeekComplete);
 
   if (audioSeekComplete && videoSeekComplete) {
     mDecodeToSeekTarget = false;
     RefPtr<nsIRunnable> task(
       NS_NewRunnableMethod(this, &MediaDecoderStateMachine::SeekCompleted));
-    nsresult rv = GetStateMachineThread()->Dispatch(task, NS_DISPATCH_NORMAL);
+    nsresult rv = TaskQueue()->Dispatch(task);
     if (NS_FAILED(rv)) {
       DecodeError();
     }
   }
 }
 
 bool
 MediaDecoderStateMachine::IsAudioDecoding()
@@ -1141,20 +1146,17 @@ nsresult MediaDecoderStateMachine::Init(
     return NS_ERROR_FAILURE;
   }
 
   MediaDecoderReader* cloneReader = nullptr;
   if (aCloneDonor) {
     cloneReader = aCloneDonor->mReader;
   }
 
-  nsresult rv = mScheduler->Init();
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  rv = mReader->Init(cloneReader);
+  nsresult rv = mReader->Init(cloneReader);
   NS_ENSURE_SUCCESS(rv, rv);
 
   return NS_OK;
 }
 
 void MediaDecoderStateMachine::StopPlayback()
 {
   MOZ_ASSERT(OnStateMachineThread());
@@ -1338,17 +1340,17 @@ int64_t MediaDecoderStateMachine::GetCur
                OnStateMachineThread() ||
                OnDecodeThread(),
                "Should be on main, decode, or state machine thread.");
 
   return mCurrentFrameTime;
 }
 
 bool MediaDecoderStateMachine::IsRealTime() const {
-  return mScheduler->IsRealTime();
+  return mRealTime;
 }
 
 int64_t MediaDecoderStateMachine::GetDuration()
 {
   AssertCurrentThreadInMonitor();
 
   if (mEndTime == -1 || mStartTime == -1)
     return -1;
@@ -1487,18 +1489,28 @@ void MediaDecoderStateMachine::SetDorman
                                        SeekTarget::Accurate,
                                        MediaDecoderEventVisibility::Suppressed);
       // XXXbholley - Nobody is listening to this promise. Do we need to pass it
       // back to MediaDecoder when we come out of dormant?
       nsRefPtr<MediaDecoder::SeekPromise> unused = mQueuedSeek.mPromise.Ensure(__func__);
     }
     mPendingSeek.RejectIfExists(__func__);
     mCurrentSeek.RejectIfExists(__func__);
-    ScheduleStateMachine();
     SetState(DECODER_STATE_DORMANT);
+    if (IsPlaying()) {
+      StopPlayback();
+    }
+    StopAudioThread();
+    FlushDecoding();
+    // Now that those threads are stopped, there's no possibility of
+    // mPendingWakeDecoder being needed again. Revoke it.
+    mPendingWakeDecoder = nullptr;
+    DebugOnly<nsresult> rv = DecodeTaskQueue()->Dispatch(
+    NS_NewRunnableMethod(mReader, &MediaDecoderReader::ReleaseMediaResources));
+    MOZ_ASSERT(NS_SUCCEEDED(rv));
     mDecoder->GetReentrantMonitor().NotifyAll();
   } else if ((aDormant != true) && (mState == DECODER_STATE_DORMANT)) {
     mDecodingFrozenAtStateDecoding = true;
     ScheduleStateMachine();
     mCurrentFrameTime = 0;
     SetState(DECODER_STATE_DECODING_NONE);
     mDecoder->GetReentrantMonitor().NotifyAll();
   }
@@ -1508,19 +1520,18 @@ void MediaDecoderStateMachine::Shutdown(
 {
   NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
 
   // Once we've entered the shutdown state here there's no going back.
   ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
 
   // Change state before issuing shutdown request to threads so those
   // threads can start exiting cleanly during the Shutdown call.
-  DECODER_LOG("Changed state to SHUTDOWN");
+  ScheduleStateMachine();
   SetState(DECODER_STATE_SHUTDOWN);
-  mScheduler->ScheduleAndShutdown();
   if (mAudioSink) {
     mAudioSink->PrepareToShutdown();
   }
   mDecoder->GetReentrantMonitor().NotifyAll();
 }
 
 void MediaDecoderStateMachine::StartDecoding()
 {
@@ -1769,17 +1780,17 @@ MediaDecoderStateMachine::EnqueueDecodeM
 nsresult
 MediaDecoderStateMachine::EnqueueDecodeFirstFrameTask()
 {
   AssertCurrentThreadInMonitor();
   MOZ_ASSERT(mState == DECODER_STATE_DECODING_FIRSTFRAME);
 
   RefPtr<nsIRunnable> task(
     NS_NewRunnableMethod(this, &MediaDecoderStateMachine::CallDecodeFirstFrame));
-  nsresult rv = GetStateMachineThread()->Dispatch(task, NS_DISPATCH_NORMAL);
+  nsresult rv = TaskQueue()->Dispatch(task);
   NS_ENSURE_SUCCESS(rv, rv);
   return NS_OK;
 }
 
 void
 MediaDecoderStateMachine::SetReaderIdle()
 {
   MOZ_ASSERT(OnDecodeThread());
@@ -1916,17 +1927,17 @@ MediaDecoderStateMachine::InitiateSeek()
 
   // Put a reset in the pipe before seek.
   ResetDecode();
 
   // Do the seek.
   mSeekRequest.Begin(ProxyMediaCall(DecodeTaskQueue(), mReader.get(), __func__,
                                     &MediaDecoderReader::Seek, mCurrentSeek.mTarget.mTime,
                                     GetEndTime())
-    ->RefableThen(mScheduler.get(), __func__, this,
+    ->RefableThen(TaskQueue(), __func__, this,
                   &MediaDecoderStateMachine::OnSeekCompleted,
                   &MediaDecoderStateMachine::OnSeekFailed));
 }
 
 nsresult
 MediaDecoderStateMachine::DispatchAudioDecodeTaskIfNeeded()
 {
   MOZ_ASSERT(OnStateMachineThread());
@@ -1964,17 +1975,17 @@ MediaDecoderStateMachine::EnsureAudioDec
     return NS_OK;
   }
 
   SAMPLE_LOG("Queueing audio task - queued=%i, decoder-queued=%o",
              AudioQueue().GetSize(), mReader->SizeOfAudioQueueInFrames());
 
   mAudioDataRequest.Begin(ProxyMediaCall(DecodeTaskQueue(), mReader.get(),
                                          __func__, &MediaDecoderReader::RequestAudioData)
-    ->RefableThen(mScheduler.get(), __func__, this,
+    ->RefableThen(TaskQueue(), __func__, this,
                   &MediaDecoderStateMachine::OnAudioDecoded,
                   &MediaDecoderStateMachine::OnAudioNotDecoded));
 
   return NS_OK;
 }
 
 nsresult
 MediaDecoderStateMachine::DispatchVideoDecodeTaskIfNeeded()
@@ -2024,17 +2035,17 @@ MediaDecoderStateMachine::EnsureVideoDec
 
   SAMPLE_LOG("Queueing video task - queued=%i, decoder-queued=%o, skip=%i, time=%lld",
              VideoQueue().GetSize(), mReader->SizeOfVideoQueueInFrames(), skipToNextKeyFrame,
              currentTime);
 
   mVideoDataRequest.Begin(ProxyMediaCall(DecodeTaskQueue(), mReader.get(), __func__,
                                          &MediaDecoderReader::RequestVideoData,
                                          skipToNextKeyFrame, currentTime)
-    ->RefableThen(mScheduler.get(), __func__, this,
+    ->RefableThen(TaskQueue(), __func__, this,
                   &MediaDecoderStateMachine::OnVideoDecoded,
                   &MediaDecoderStateMachine::OnVideoNotDecoded));
   return NS_OK;
 }
 
 nsresult
 MediaDecoderStateMachine::StartAudioThread()
 {
@@ -2162,19 +2173,19 @@ MediaDecoderStateMachine::DecodeError()
       DECODER_WARN("Failed to dispatch AcquireMonitorAndInvokeDecodeError");
     }
     return;
   }
 
   // Change state to shutdown before sending error report to MediaDecoder
   // and the HTMLMediaElement, so that our pipeline can start exiting
   // cleanly during the sync dispatch below.
-  DECODER_WARN("Decode error, changed state to SHUTDOWN due to error");
+  ScheduleStateMachine();
   SetState(DECODER_STATE_SHUTDOWN);
-  mScheduler->ScheduleAndShutdown();
+  DECODER_WARN("Decode error, changed state to SHUTDOWN due to error");
   mDecoder->GetReentrantMonitor().NotifyAll();
 
   // Dispatch the event to call DecodeError synchronously. This ensures
   // we're in shutdown state by the time we exit the decode thread.
   // If we just moved to shutdown state here on the decode thread, we may
   // cause the state machine to shutdown/free memory without closing its
   // media stream properly, and we'll get callbacks from the media stream
   // causing a crash.
@@ -2317,22 +2328,22 @@ MediaDecoderStateMachine::DecodeFirstFra
   MOZ_ASSERT(OnStateMachineThread());
   AssertCurrentThreadInMonitor();
   MOZ_ASSERT(mState == DECODER_STATE_DECODING_FIRSTFRAME);
   DECODER_LOG("DecodeFirstFrame started");
 
   if (HasAudio()) {
     RefPtr<nsIRunnable> decodeTask(
       NS_NewRunnableMethod(this, &MediaDecoderStateMachine::DispatchAudioDecodeTaskIfNeeded));
-    AudioQueue().AddPopListener(decodeTask, GetStateMachineThread());
+    AudioQueue().AddPopListener(decodeTask, TaskQueue());
   }
   if (HasVideo()) {
     RefPtr<nsIRunnable> decodeTask(
       NS_NewRunnableMethod(this, &MediaDecoderStateMachine::DispatchVideoDecodeTaskIfNeeded));
-    VideoQueue().AddPopListener(decodeTask, GetStateMachineThread());
+    VideoQueue().AddPopListener(decodeTask, TaskQueue());
   }
 
   if (IsRealTime()) {
     SetStartTime(0);
     nsresult res = FinishDecodeFirstFrame();
     NS_ENSURE_SUCCESS(res, res);
   } else if (mSentFirstFrameLoadedEvent) {
     // We're resuming from dormant state, so we don't need to request
@@ -2340,25 +2351,25 @@ MediaDecoderStateMachine::DecodeFirstFra
     // we have the start time from last time we loaded.
     SetStartTime(mStartTime);
     nsresult res = FinishDecodeFirstFrame();
     NS_ENSURE_SUCCESS(res, res);
   } else {
     if (HasAudio()) {
       mAudioDataRequest.Begin(ProxyMediaCall(DecodeTaskQueue(), mReader.get(),
                                              __func__, &MediaDecoderReader::RequestAudioData)
-        ->RefableThen(mScheduler.get(), __func__, this,
+        ->RefableThen(TaskQueue(), __func__, this,
                       &MediaDecoderStateMachine::OnAudioDecoded,
                       &MediaDecoderStateMachine::OnAudioNotDecoded));
     }
     if (HasVideo()) {
       mVideoDecodeStartTime = TimeStamp::Now();
       mVideoDataRequest.Begin(ProxyMediaCall(DecodeTaskQueue(), mReader.get(),
                                              __func__, &MediaDecoderReader::RequestVideoData, false, int64_t(0))
-        ->RefableThen(mScheduler.get(), __func__, this,
+        ->RefableThen(TaskQueue(), __func__, this,
                       &MediaDecoderStateMachine::OnVideoDecoded,
                       &MediaDecoderStateMachine::OnVideoNotDecoded));
     }
   }
 
   return NS_OK;
 }
 
@@ -2545,61 +2556,45 @@ MediaDecoderStateMachine::SeekCompleted(
     ReentrantMonitorAutoExit exitMon(mDecoder->GetReentrantMonitor());
     RenderVideoFrame(video, TimeStamp::Now());
     nsCOMPtr<nsIRunnable> event =
       NS_NewRunnableMethod(mDecoder, &MediaDecoder::Invalidate);
     NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL);
   }
 }
 
-// Runnable to dispose of the decoder and state machine on the main thread.
-class nsDecoderDisposeEvent : public nsRunnable {
+class DecoderDisposer
+{
 public:
-  nsDecoderDisposeEvent(already_AddRefed<MediaDecoder> aDecoder,
-                        already_AddRefed<MediaDecoderStateMachine> aStateMachine)
+  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(DecoderDisposer)
+  DecoderDisposer(MediaDecoder* aDecoder, MediaDecoderStateMachine* aStateMachine)
     : mDecoder(aDecoder), mStateMachine(aStateMachine) {}
-  NS_IMETHOD Run() {
-    NS_ASSERTION(NS_IsMainThread(), "Must be on main thread.");
+
+  void OnTaskQueueShutdown()
+  {
+    MOZ_ASSERT(NS_IsMainThread());
     MOZ_ASSERT(mStateMachine);
     MOZ_ASSERT(mDecoder);
     mStateMachine->BreakCycles();
     mDecoder->BreakCycles();
     mStateMachine = nullptr;
     mDecoder = nullptr;
-    return NS_OK;
   }
+
 private:
-  nsRefPtr<MediaDecoder> mDecoder;
-  nsRefPtr<MediaDecoderStateMachine> mStateMachine;
-};
-
-// Runnable which dispatches an event to the main thread to dispose of the
-// decoder and state machine. This runs on the state machine thread after
-// the state machine has shutdown, and all events for that state machine have
-// finished running.
-class nsDispatchDisposeEvent : public nsRunnable {
-public:
-  nsDispatchDisposeEvent(MediaDecoder* aDecoder,
-                         MediaDecoderStateMachine* aStateMachine)
-    : mDecoder(aDecoder), mStateMachine(aStateMachine) {}
-  NS_IMETHOD Run() {
-    NS_DispatchToMainThread(new nsDecoderDisposeEvent(mDecoder.forget(),
-                                                      mStateMachine.forget()));
-    return NS_OK;
-  }
-private:
+  virtual ~DecoderDisposer() {}
   nsRefPtr<MediaDecoder> mDecoder;
   nsRefPtr<MediaDecoderStateMachine> mStateMachine;
 };
 
 void
 MediaDecoderStateMachine::ShutdownReader()
 {
   MOZ_ASSERT(OnDecodeThread());
-  mReader->Shutdown()->Then(mScheduler.get(), __func__, this,
+  mReader->Shutdown()->Then(TaskQueue(), __func__, this,
                             &MediaDecoderStateMachine::FinishShutdown,
                             &MediaDecoderStateMachine::FinishShutdown);
 }
 
 void
 MediaDecoderStateMachine::FinishShutdown()
 {
   MOZ_ASSERT(OnStateMachineThread());
@@ -2625,25 +2620,37 @@ MediaDecoderStateMachine::FinishShutdown
   // hold the decoder monitor here. We also want to guarantee that the
   // state machine is destroyed on the main thread, and so the
   // event runner running this function (which holds a reference to the
   // state machine) needs to finish and be released in order to allow
   // that. So we dispatch an event to run after this event runner has
   // finished and released its monitor/references. That event then will
   // dispatch an event to the main thread to release the decoder and
   // state machine.
-  GetStateMachineThread()->Dispatch(
-    new nsDispatchDisposeEvent(mDecoder, this), NS_DISPATCH_NORMAL);
-
-  DECODER_LOG("Dispose Event Dispatched");
+  DECODER_LOG("Shutting down state machine task queue");
+  nsCOMPtr<nsIThread> mainThread;
+  NS_GetMainThread(getter_AddRefs(mainThread));
+  RefPtr<DecoderDisposer> disposer = new DecoderDisposer(mDecoder, this);
+  TaskQueue()->BeginShutdown()->Then(mainThread.get(), __func__, disposer.get(),
+                                     &DecoderDisposer::OnTaskQueueShutdown,
+                                     &DecoderDisposer::OnTaskQueueShutdown);
 }
 
 nsresult MediaDecoderStateMachine::RunStateMachine()
 {
-  AssertCurrentThreadInMonitor();
+  MOZ_ASSERT(OnStateMachineThread());
+  ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
+
+  mDelayedScheduler.Reset(); // Must happen on state machine thread.
+  mDispatchedStateMachine = false;
+
+  // If audio is being captured, stop the audio sink if it's running
+  if (mAudioCaptured) {
+    StopAudioThread();
+  }
 
   MediaResource* resource = mDecoder->GetResource();
   NS_ENSURE_TRUE(resource, NS_ERROR_NULL_POINTER);
 
   switch (mState) {
     case DECODER_STATE_SHUTDOWN: {
       mQueuedSeek.RejectIfExists(__func__);
       mPendingSeek.RejectIfExists(__func__);
@@ -2663,27 +2670,16 @@ nsresult MediaDecoderStateMachine::RunSt
       DebugOnly<nsresult> rv = DecodeTaskQueue()->Dispatch(task);
       MOZ_ASSERT(NS_SUCCEEDED(rv));
 
       DECODER_LOG("Shutdown started");
       return NS_OK;
     }
 
     case DECODER_STATE_DORMANT: {
-      if (IsPlaying()) {
-        StopPlayback();
-      }
-      StopAudioThread();
-      FlushDecoding();
-      // Now that those threads are stopped, there's no possibility of
-      // mPendingWakeDecoder being needed again. Revoke it.
-      mPendingWakeDecoder = nullptr;
-      DebugOnly<nsresult> rv = DecodeTaskQueue()->Dispatch(
-      NS_NewRunnableMethod(mReader, &MediaDecoderReader::ReleaseMediaResources));
-      MOZ_ASSERT(NS_SUCCEEDED(rv));
       return NS_OK;
     }
 
     case DECODER_STATE_WAIT_FOR_RESOURCES: {
       return NS_OK;
     }
 
     case DECODER_STATE_DECODING_NONE: {
@@ -2734,17 +2730,17 @@ nsresult MediaDecoderStateMachine::RunSt
               elapsed < TimeDuration::FromSeconds(mBufferingWait * mPlaybackRate) &&
               (mQuickBuffering ? HasLowDecodedData(mQuickBufferingLowDataThresholdUsecs)
                                : HasLowUndecodedData(mBufferingWait * USECS_PER_S)) &&
               mDecoder->IsExpectingMoreData())
         {
           DECODER_LOG("Buffering: wait %ds, timeout in %.3lfs %s",
                       mBufferingWait, mBufferingWait - elapsed.ToSeconds(),
                       (mQuickBuffering ? "(quick exit)" : ""));
-          ScheduleStateMachine(USECS_PER_S);
+          ScheduleStateMachineIn(USECS_PER_S);
           return NS_OK;
         }
       } else if (OutOfDecodedAudio() || OutOfDecodedVideo()) {
         MOZ_ASSERT(mReader->IsWaitForDataSupported(),
                    "Don't yet have a strategy for non-heuristic + non-WaitForData");
         DispatchDecodeTasksIfNeeded();
         MOZ_ASSERT_IF(!mMinimizePreroll && OutOfDecodedAudio(), mAudioDataRequest.Exists() || mAudioWaitRequest.Exists());
         MOZ_ASSERT_IF(!mMinimizePreroll && OutOfDecodedVideo(), mVideoDataRequest.Exists() || mVideoWaitRequest.Exists());
@@ -3058,17 +3054,17 @@ void MediaDecoderStateMachine::AdvanceFr
     if (shouldBuffer) {
       if (currentFrame) {
         VideoQueue().PushFront(currentFrame);
       }
       StartBuffering();
       // Don't go straight back to the state machine loop since that might
       // cause us to start decoding again and we could flip-flop between
       // decoding and quick-buffering.
-      ScheduleStateMachine(USECS_PER_S);
+      ScheduleStateMachineIn(USECS_PER_S);
       return;
     }
   }
 
   // We've got enough data to keep playing until at least the next frame.
   // Start playing now if need be.
   if ((mFragmentEndTime >= 0 && clock_time < mFragmentEndTime) || mFragmentEndTime < 0) {
     MaybeStartPlayback();
@@ -3120,17 +3116,22 @@ void MediaDecoderStateMachine::AdvanceFr
   }
 
   // If the number of audio/video frames queued has changed, either by
   // this function popping and playing a video frame, or by the audio
   // thread popping and playing an audio frame, we may need to update our
   // ready state. Post an update to do so.
   UpdateReadyState();
 
-  ScheduleStateMachine(remainingTime / mPlaybackRate);
+  int64_t delay = remainingTime / mPlaybackRate;
+  if (delay > 0) {
+    ScheduleStateMachineIn(delay);
+  } else {
+    ScheduleStateMachine();
+  }
 }
 
 nsresult
 MediaDecoderStateMachine::DropVideoUpToSeekTarget(VideoData* aSample)
 {
   nsRefPtr<VideoData> video(aSample);
   MOZ_ASSERT(video);
   DECODER_LOG("DropVideoUpToSeekTarget() frame [%lld, %lld] dup=%d",
@@ -3366,63 +3367,84 @@ void MediaDecoderStateMachine::SetPlaySt
   }
   if (!mPlayStartTime.IsNull()) {
     mAudioSink->StartPlayback();
   } else {
     mAudioSink->StopPlayback();
   }
 }
 
-nsresult MediaDecoderStateMachine::CallRunStateMachine()
-{
-  AssertCurrentThreadInMonitor();
-  NS_ASSERTION(OnStateMachineThread(), "Should be on state machine thread.");
-
-  // If audio is being captured, stop the audio sink if it's running
-  if (mAudioCaptured) {
-    StopAudioThread();
-  }
-
-  return RunStateMachine();
-}
-
-nsresult MediaDecoderStateMachine::TimeoutExpired(void* aClosure)
-{
-  MediaDecoderStateMachine* p = static_cast<MediaDecoderStateMachine*>(aClosure);
-  return p->CallRunStateMachine();
-}
-
 void MediaDecoderStateMachine::ScheduleStateMachineWithLockAndWakeDecoder() {
   ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
   DispatchAudioDecodeTaskIfNeeded();
   DispatchVideoDecodeTaskIfNeeded();
 }
 
-nsresult MediaDecoderStateMachine::ScheduleStateMachine(int64_t aUsecs) {
-  return mScheduler->Schedule(aUsecs);
+void
+MediaDecoderStateMachine::ScheduleStateMachine() {
+  AssertCurrentThreadInMonitor();
+  if (mState == DECODER_STATE_SHUTDOWN) {
+    NS_WARNING("Refusing to schedule shutdown state machine");
+    return;
+  }
+
+  if (mDispatchedStateMachine) {
+    return;
+  }
+  mDispatchedStateMachine = true;
+
+  RefPtr<nsIRunnable> task =
+    NS_NewRunnableMethod(this, &MediaDecoderStateMachine::RunStateMachine);
+  nsresult rv = TaskQueue()->Dispatch(task);
+  MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(rv));
+  (void) rv;
+}
+
+void
+MediaDecoderStateMachine::ScheduleStateMachineIn(int64_t aMicroseconds)
+{
+  AssertCurrentThreadInMonitor();
+  MOZ_ASSERT(OnStateMachineThread()); // mDelayedScheduler.Ensure() may Disconnect()
+                                      // the promise, which must happen on the state
+                                      // machine thread.
+  MOZ_ASSERT(aMicroseconds > 0);
+  if (mState == DECODER_STATE_SHUTDOWN) {
+    NS_WARNING("Refusing to schedule shutdown state machine");
+    return;
+  }
+
+  if (mDispatchedStateMachine) {
+    return;
+  }
+
+  // Real-time weirdness.
+  if (IsRealTime()) {
+    aMicroseconds = std::min(aMicroseconds, int64_t(40000));
+  }
+
+  TimeStamp now = TimeStamp::Now();
+  TimeStamp target = now + TimeDuration::FromMicroseconds(aMicroseconds);
+
+  SAMPLE_LOG("Scheduling state machine for %lf ms from now", (target - now).ToMilliseconds());
+  mDelayedScheduler.Ensure(target);
 }
 
 bool MediaDecoderStateMachine::OnDecodeThread() const
 {
   return !DecodeTaskQueue() || DecodeTaskQueue()->IsCurrentThreadIn();
 }
 
 bool MediaDecoderStateMachine::OnStateMachineThread() const
 {
-  return mScheduler->OnStateMachineThread();
-}
-
-nsIEventTarget* MediaDecoderStateMachine::GetStateMachineThread() const
-{
-  return mScheduler->GetStateMachineThread();
+  return TaskQueue()->IsCurrentThreadIn();
 }
 
 bool MediaDecoderStateMachine::IsStateMachineScheduled() const
 {
-  return mScheduler->IsScheduled();
+  return mDispatchedStateMachine || mDelayedScheduler.IsScheduled();
 }
 
 void MediaDecoderStateMachine::SetPlaybackRate(double aPlaybackRate)
 {
   NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
   NS_ASSERTION(aPlaybackRate != 0,
       "PlaybackRate == 0 should be handled before this function.");
   ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
--- a/dom/media/MediaDecoderStateMachine.h
+++ b/dom/media/MediaDecoderStateMachine.h
@@ -84,18 +84,18 @@ hardware (via AudioStream).
 
 #include "mozilla/Attributes.h"
 #include "nsThreadUtils.h"
 #include "MediaDecoder.h"
 #include "mozilla/ReentrantMonitor.h"
 #include "MediaDecoderReader.h"
 #include "MediaDecoderOwner.h"
 #include "MediaMetadataManager.h"
-#include "MediaDecoderStateMachineScheduler.h"
 #include "mozilla/RollingMean.h"
+#include "MediaTimer.h"
 
 class nsITimer;
 
 namespace mozilla {
 
 class AudioSegment;
 class VideoSegment;
 class MediaTaskQueue;
@@ -206,18 +206,18 @@ public:
   MediaDecoderOwner::NextFrameStatus GetNextFrameStatus();
 
   // Cause state transitions. These methods obtain the decoder monitor
   // to synchronise the change of state, and to notify other threads
   // that the state has changed.
   void Play()
   {
     MOZ_ASSERT(NS_IsMainThread());
-    nsRefPtr<nsRunnable> r = NS_NewRunnableMethod(this, &MediaDecoderStateMachine::PlayInternal);
-    GetStateMachineThread()->Dispatch(r, NS_DISPATCH_NORMAL);
+    RefPtr<nsRunnable> r = NS_NewRunnableMethod(this, &MediaDecoderStateMachine::PlayInternal);
+    TaskQueue()->Dispatch(r);
   }
 
 private:
   // The actual work for the above, which happens asynchronously on the state
   // machine thread.
   void PlayInternal();
 public:
 
@@ -306,31 +306,40 @@ public:
     if (mReader) {
       return mReader->SizeOfAudioQueueInBytes();
     }
     return 0;
   }
 
   void NotifyDataArrived(const char* aBuffer, uint32_t aLength, int64_t aOffset);
 
-  // Returns the shared state machine thread.
-  nsIEventTarget* GetStateMachineThread() const;
+  // Returns the state machine task queue.
+  MediaTaskQueue* TaskQueue() const { return mTaskQueue; }
 
   // Calls ScheduleStateMachine() after taking the decoder lock. Also
   // notifies the decoder thread in case it's waiting on the decoder lock.
   void ScheduleStateMachineWithLockAndWakeDecoder();
 
-  // Schedules the shared state machine thread to run the state machine
-  // in aUsecs microseconds from now, if it's not already scheduled to run
-  // earlier, in which case the request is discarded.
-  nsresult ScheduleStateMachine(int64_t aUsecs = 0);
+  // Schedules the shared state machine thread to run the state machine.
+  void ScheduleStateMachine();
+
+  // Invokes ScheduleStateMachine to run in |aMicroseconds| microseconds,
+  // unless it's already scheduled to run earlier, in which case the
+  // request is discarded.
+  void ScheduleStateMachineIn(int64_t aMicroseconds);
 
-  // Callback function registered with MediaDecoderStateMachineScheduler
-  // to run state machine cycles.
-  static nsresult TimeoutExpired(void* aClosure);
+  void OnDelayedSchedule()
+  {
+    MOZ_ASSERT(OnStateMachineThread());
+    ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
+    mDelayedScheduler.CompleteRequest();
+    ScheduleStateMachine();
+  }
+
+  void NotReached() { MOZ_DIAGNOSTIC_ASSERT(false); }
 
   // Set the media fragment end time. aEndTime is in microseconds.
   void SetFragmentEndTime(int64_t aEndTime);
 
   // Drop reference to decoder.  Only called during shutdown dance.
   void BreakCycles() {
     if (mReader) {
       mReader->BreakCycles();
@@ -684,19 +693,16 @@ protected:
   // The decoder monitor must be held.
   void CheckIfDecodeComplete();
 
   // Copy audio from an AudioData packet to aOutput. This may require
   // inserting silence depending on the timing of the audio packet.
   void SendStreamAudio(AudioData* aAudio, DecodedStreamData* aStream,
                        AudioSegment* aOutput);
 
-  // State machine thread run function. Defers to RunStateMachine().
-  nsresult CallRunStateMachine();
-
   // Performs one "cycle" of the state machine. Polls the state, and may send
   // a video frame to be displayed, and generally manages the decode. Called
   // periodically via timer to ensure the video stays in sync.
   nsresult RunStateMachine();
 
   bool IsStateMachineScheduled() const;
 
   // Returns true if we're not playing and the decode thread has filled its
@@ -740,19 +746,70 @@ protected:
   // dropped its reference to the decoder. This enables the state machine to
   // keep using the decoder's monitor until the state machine has finished
   // shutting down, without fear of the monitor being destroyed. After
   // shutting down, the state machine will then release this reference,
   // causing the decoder to be destroyed. This is accessed on the decode,
   // state machine, audio and main threads.
   nsRefPtr<MediaDecoder> mDecoder;
 
-  // Used to schedule state machine cycles. This should never outlive
-  // the life cycle of the state machine.
-  const nsRefPtr<MediaDecoderStateMachineScheduler> mScheduler;
+  // Task queue for running the state machine.
+  nsRefPtr<MediaTaskQueue> mTaskQueue;
+
+  // True is we are decoding a realtime stream, like a camera stream.
+  bool mRealTime;
+
+  // True if we've dispatched a task to run the state machine but the task has
+  // yet to run.
+  bool mDispatchedStateMachine;
+
+  // Class for managing delayed dispatches of the state machine.
+  class DelayedScheduler {
+  public:
+    explicit DelayedScheduler(MediaDecoderStateMachine* aSelf)
+      : mSelf(aSelf), mMediaTimer(new MediaTimer()) {}
+
+    bool IsScheduled() const { return !mTarget.IsNull(); }
+
+    void Reset()
+    {
+      MOZ_ASSERT(mSelf->OnStateMachineThread(),
+                 "Must be on state machine queue to disconnect");
+      if (IsScheduled()) {
+        mRequest.Disconnect();
+        mTarget = TimeStamp();
+      }
+    }
+
+    void Ensure(mozilla::TimeStamp& aTarget)
+    {
+      if (IsScheduled() && mTarget <= aTarget) {
+        return;
+      }
+      Reset();
+      mTarget = aTarget;
+      mRequest.Begin(mMediaTimer->WaitUntil(mTarget, __func__)->RefableThen(
+        mSelf->TaskQueue(), __func__, mSelf,
+        &MediaDecoderStateMachine::OnDelayedSchedule,
+        &MediaDecoderStateMachine::NotReached));
+    }
+
+    void CompleteRequest()
+    {
+      mRequest.Complete();
+      mTarget = TimeStamp();
+    }
+
+  private:
+    MediaDecoderStateMachine* mSelf;
+    nsRefPtr<MediaTimer> mMediaTimer;
+    MediaPromiseConsumerHolder<mozilla::MediaTimerPromise> mRequest;
+    TimeStamp mTarget;
+
+  } mDelayedScheduler;
 
   // Time at which the last video sample was requested. If it takes too long
   // before the sample arrives, we will increase the amount of audio we buffer.
   // This is necessary for legacy synchronous decoders to prevent underruns.
   TimeStamp mVideoDecodeStartTime;
 
   // Queue of audio frames. This queue is threadsafe, and is accessed from
   // the audio, decoder, state machine, and main threads.
@@ -950,28 +1007,28 @@ protected:
   // At the start of decoding we want to "preroll" the decode until we've
   // got a few frames decoded before we consider whether decode is falling
   // behind. Otherwise our "we're falling behind" logic will trigger
   // unneccessarily if we start playing as soon as the first sample is
   // decoded. These two fields store how many video frames and audio
   // samples we must consume before are considered to be finished prerolling.
   uint32_t AudioPrerollUsecs() const
   {
-    if (mScheduler->IsRealTime()) {
+    if (IsRealTime()) {
       return 0;
     }
 
     uint32_t result = mLowAudioThresholdUsecs * 2;
     MOZ_ASSERT(result <= mAmpleAudioThresholdUsecs, "Prerolling will never finish");
     return result;
   }
 
   uint32_t VideoPrerollFrames() const
   {
-    return mScheduler->IsRealTime() ? 0 : GetAmpleVideoFrames() / 2;
+    return IsRealTime() ? 0 : GetAmpleVideoFrames() / 2;
   }
 
   bool DonePrerollingAudio()
   {
     AssertCurrentThreadInMonitor();
     return !IsAudioDecoding() || GetDecodedAudioDuration() >= AudioPrerollUsecs() * mPlaybackRate;
   }
 
deleted file mode 100644
--- a/dom/media/MediaDecoderStateMachineScheduler.cpp
+++ /dev/null
@@ -1,194 +0,0 @@
-/* vim:set ts=2 sw=2 sts=2 et cindent: */
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-#include "MediaDecoderStateMachineScheduler.h"
-#include "SharedThreadPool.h"
-#include "mozilla/Preferences.h"
-#include "mozilla/ReentrantMonitor.h"
-#include "nsITimer.h"
-#include "nsComponentManagerUtils.h"
-#include "VideoUtils.h"
-
-namespace {
-class TimerEvent : public nsITimerCallback, public nsRunnable {
-  typedef mozilla::MediaDecoderStateMachineScheduler Scheduler;
-  NS_DECL_ISUPPORTS_INHERITED
-public:
-  TimerEvent(Scheduler* aScheduler, int aTimerId)
-    : mScheduler(aScheduler), mTimerId(aTimerId) {}
-
-  NS_IMETHOD Run() MOZ_OVERRIDE {
-    return mScheduler->TimeoutExpired(mTimerId);
-  }
-
-  NS_IMETHOD Notify(nsITimer* aTimer) MOZ_OVERRIDE {
-    return mScheduler->TimeoutExpired(mTimerId);
-  }
-private:
-  ~TimerEvent() {}
-  Scheduler* const mScheduler;
-  const int mTimerId;
-};
-
-NS_IMPL_ISUPPORTS_INHERITED(TimerEvent, nsRunnable, nsITimerCallback);
-} // anonymous namespace
-
-static already_AddRefed<nsIEventTarget>
-CreateStateMachineThread()
-{
-  using mozilla::SharedThreadPool;
-  using mozilla::RefPtr;
-  RefPtr<SharedThreadPool> threadPool(
-      SharedThreadPool::Get(NS_LITERAL_CSTRING("Media State Machine"), 1));
-  nsCOMPtr<nsIEventTarget> rv = threadPool.get();
-  return rv.forget();
-}
-
-namespace mozilla {
-
-MediaDecoderStateMachineScheduler::MediaDecoderStateMachineScheduler(
-    ReentrantMonitor& aMonitor,
-    nsresult (*aTimeoutCallback)(void*),
-    void* aClosure, bool aRealTime)
-  : mTimeoutCallback(aTimeoutCallback)
-  , mClosure(aClosure)
-  // Only enable realtime mode when "media.realtime_decoder.enabled" is true.
-  , mRealTime(aRealTime &&
-              Preferences::GetBool("media.realtime_decoder.enabled", false))
-  , mMonitor(aMonitor)
-  , mEventTarget(CreateStateMachineThread())
-  , mTimer(do_CreateInstance("@mozilla.org/timer;1"))
-  , mTimerId(0)
-  , mState(SCHEDULER_STATE_NONE)
-  , mInRunningStateMachine(false)
-{
-  MOZ_ASSERT(NS_IsMainThread());
-  MOZ_COUNT_CTOR(MediaDecoderStateMachineScheduler);
-}
-
-MediaDecoderStateMachineScheduler::~MediaDecoderStateMachineScheduler()
-{
-  MOZ_ASSERT(NS_IsMainThread());
-  MOZ_COUNT_DTOR(MediaDecoderStateMachineScheduler);
-}
-
-nsresult
-MediaDecoderStateMachineScheduler::Init()
-{
-  MOZ_ASSERT(NS_IsMainThread());
-  NS_ENSURE_TRUE(mEventTarget, NS_ERROR_FAILURE);
-  nsresult rv = mTimer->SetTarget(mEventTarget);
-  NS_ENSURE_SUCCESS(rv, rv);
-  return NS_OK;
-}
-
-nsresult
-MediaDecoderStateMachineScheduler::Schedule(int64_t aUsecs)
-{
-  mMonitor.AssertCurrentThreadIn();
-
-  if (NS_WARN_IF(mState == SCHEDULER_STATE_SHUTDOWN)) {
-    return NS_ERROR_FAILURE;
-  }
-
-  aUsecs = std::max<int64_t>(aUsecs, 0);
-
-  TimeStamp timeout = TimeStamp::Now() +
-    TimeDuration::FromMilliseconds(static_cast<double>(aUsecs) / USECS_PER_MS);
-
-  if (!mTimeout.IsNull() && timeout >= mTimeout) {
-    // We've already scheduled a timer set to expire at or before this time,
-    // or have an event dispatched to run the state machine.
-    return NS_OK;
-  }
-
-  uint32_t ms = static_cast<uint32_t>((aUsecs / USECS_PER_MS) & 0xFFFFFFFF);
-  if (IsRealTime() && ms > 40) {
-    ms = 40;
-  }
-
-  // Don't cancel the timer here for this function will be called from
-  // different threads.
-
-  nsresult rv = NS_ERROR_FAILURE;
-  nsRefPtr<TimerEvent> event = new TimerEvent(this, mTimerId+1);
-
-  if (ms == 0) {
-    // Dispatch a runnable to the state machine thread when delay is 0.
-    // It will has less latency than dispatching a runnable to the state
-    // machine thread which will then schedule a zero-delay timer.
-    rv = mEventTarget->Dispatch(event, NS_DISPATCH_NORMAL);
-  } else if (OnStateMachineThread()) {
-    rv = mTimer->InitWithCallback(event, ms, nsITimer::TYPE_ONE_SHOT);
-  } else {
-    MOZ_ASSERT(false, "non-zero delay timer should be only "
-                      "scheduled in state machine thread");
-  }
-
-  if (NS_SUCCEEDED(rv)) {
-    mTimeout = timeout;
-    ++mTimerId;
-  } else {
-    NS_WARNING("Failed to schedule state machine");
-  }
-
-  return rv;
-}
-
-nsresult
-MediaDecoderStateMachineScheduler::TimeoutExpired(int aTimerId)
-{
-  ReentrantMonitorAutoEnter mon(mMonitor);
-  MOZ_ASSERT(OnStateMachineThread());
-  MOZ_ASSERT(!mInRunningStateMachine,
-             "State machine cycles must run in sequence!");
-
-  mInRunningStateMachine = true;
-  // Only run state machine cycles when id matches.
-  nsresult rv = NS_OK;
-  if (mTimerId == aTimerId) {
-    ResetTimer();
-    rv = mTimeoutCallback(mClosure);
-  }
-  mInRunningStateMachine = false;
-
-  return rv;
-}
-
-void
-MediaDecoderStateMachineScheduler::ScheduleAndShutdown()
-{
-  mMonitor.AssertCurrentThreadIn();
-  // Schedule next cycle to handle SHUTDOWN in state machine thread.
-  Schedule();
-  // This must be set after calling Schedule()
-  // which does nothing in shutdown state.
-  mState = SCHEDULER_STATE_SHUTDOWN;
-}
-
-bool
-MediaDecoderStateMachineScheduler::OnStateMachineThread() const
-{
-  bool rv = false;
-  mEventTarget->IsOnCurrentThread(&rv);
-  return rv;
-}
-
-bool
-MediaDecoderStateMachineScheduler::IsScheduled() const
-{
-  mMonitor.AssertCurrentThreadIn();
-  return !mTimeout.IsNull();
-}
-
-void
-MediaDecoderStateMachineScheduler::ResetTimer()
-{
-  mMonitor.AssertCurrentThreadIn();
-  mTimer->Cancel();
-  mTimeout = TimeStamp();
-}
-
-} // namespace mozilla
deleted file mode 100644
--- a/dom/media/MediaDecoderStateMachineScheduler.h
+++ /dev/null
@@ -1,78 +0,0 @@
-/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
-/* vim:set ts=2 sw=2 sts=2 et cindent: */
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-#ifndef MediaDecoderStateMachineScheduler_h__
-#define MediaDecoderStateMachineScheduler_h__
-
-#include "nsCOMPtr.h"
-#include "mozilla/TimeStamp.h"
-#include "mozilla/DebugOnly.h"
-
-class nsITimer;
-class nsIEventTarget;
-
-namespace mozilla {
-
-class ReentrantMonitor;
-
-class MediaDecoderStateMachineScheduler {
-  enum State {
-    SCHEDULER_STATE_NONE,
-    SCHEDULER_STATE_SHUTDOWN
-  };
-public:
-  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(MediaDecoderStateMachineScheduler)
-  MediaDecoderStateMachineScheduler(ReentrantMonitor& aMonitor,
-                                    nsresult (*aTimeoutCallback)(void*),
-                                    void* aClosure, bool aRealTime);
-  nsresult Init();
-  nsresult Schedule(int64_t aUsecs = 0);
-  void ScheduleAndShutdown();
-  nsresult TimeoutExpired(int aTimerId);
-
-  bool OnStateMachineThread() const;
-  bool IsScheduled() const;
-
-  bool IsRealTime() const {
-    return mRealTime;
-  }
-
-  nsIEventTarget* GetStateMachineThread() const {
-    return mEventTarget;
-  }
-
-private:
-  ~MediaDecoderStateMachineScheduler();
-  void ResetTimer();
-
-  // Callback function provided by MediaDecoderStateMachine to run
-  // state machine cycles.
-  nsresult (*const mTimeoutCallback)(void*);
-  // Since StateMachineScheduler will never outlive the state machine,
-  // it is safe to keep a raw pointer only to avoid reference cycles.
-  void* const mClosure;
-  // True is we are decoding a realtime stream, like a camera stream
-  const bool mRealTime;
-  // Monitor of the decoder
-  ReentrantMonitor& mMonitor;
-  // State machine thread
-  const nsCOMPtr<nsIEventTarget> mEventTarget;
-  // Timer to schedule callbacks to run the state machine cycles.
-  nsCOMPtr<nsITimer> mTimer;
-  // Timestamp at which the next state machine cycle will run.
-  TimeStamp mTimeout;
-  // The id of timer tasks, timer callback will only run if id matches.
-  int mTimerId;
-  // No more state machine cycles in shutdown state.
-  State mState;
-
-  // Used to check if state machine cycles are running in sequence.
-  DebugOnly<bool> mInRunningStateMachine;
-};
-
-} // namespace mozilla
-
-#endif // MediaDecoderStateMachineScheduler_h__
--- a/dom/media/MediaManager.cpp
+++ b/dom/media/MediaManager.cpp
@@ -107,18 +107,18 @@ using dom::GetUserMediaRequest;
 using dom::Sequence;
 using dom::OwningBooleanOrMediaTrackConstraints;
 using dom::SupportedAudioConstraints;
 using dom::SupportedVideoConstraints;
 
 static bool
 HostInDomain(const nsCString &aHost, const nsCString &aPattern)
 {
-  PRInt32 patternOffset = 0;
-  PRInt32 hostOffset = 0;
+  int32_t patternOffset = 0;
+  int32_t hostOffset = 0;
 
   // Act on '*.' wildcard in the left-most position in a domain pattern.
   if (aPattern.Length() > 2 && aPattern[0] == '*' && aPattern[1] == '.') {
     patternOffset = 2;
 
     // Ignore the lowest level sub-domain for the hostname.
     hostOffset = aHost.FindChar('.') + 1;
 
--- a/dom/media/MediaPromise.cpp
+++ b/dom/media/MediaPromise.cpp
@@ -1,17 +1,16 @@
 /* -*- 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 "MediaPromise.h"
 
-#include "MediaDecoderStateMachineScheduler.h"
 #include "MediaTaskQueue.h"
 #include "nsThreadUtils.h"
 
 namespace mozilla {
 namespace detail {
 
 nsresult
 DispatchMediaPromiseRunnable(MediaTaskQueue* aTaskQueue, nsIRunnable* aRunnable)
@@ -20,35 +19,23 @@ DispatchMediaPromiseRunnable(MediaTaskQu
 }
 
 nsresult
 DispatchMediaPromiseRunnable(nsIEventTarget* aEventTarget, nsIRunnable* aRunnable)
 {
   return aEventTarget->Dispatch(aRunnable, NS_DISPATCH_NORMAL);
 }
 
-nsresult
-DispatchMediaPromiseRunnable(MediaDecoderStateMachineScheduler* aScheduler, nsIRunnable* aRunnable)
-{
-  return aScheduler->GetStateMachineThread()->Dispatch(aRunnable, NS_DISPATCH_NORMAL);
-}
-
 void
 AssertOnThread(MediaTaskQueue* aQueue)
 {
   MOZ_ASSERT(aQueue->IsCurrentThreadIn());
 }
 
 void AssertOnThread(nsIEventTarget* aTarget)
 {
   nsCOMPtr<nsIThread> targetThread = do_QueryInterface(aTarget);
   MOZ_ASSERT(targetThread, "Don't know how to deal with threadpools etc here");
   MOZ_ASSERT(NS_GetCurrentThread() == targetThread);
 }
 
-void
-AssertOnThread(MediaDecoderStateMachineScheduler* aScheduler)
-{
-  MOZ_ASSERT(aScheduler->OnStateMachineThread());
-}
-
 }
 } // namespace mozilla
--- a/dom/media/MediaPromise.h
+++ b/dom/media/MediaPromise.h
@@ -11,43 +11,41 @@
 
 #include "nsTArray.h"
 #include "nsThreadUtils.h"
 
 #include "mozilla/DebugOnly.h"
 #include "mozilla/Maybe.h"
 #include "mozilla/Mutex.h"
 #include "mozilla/Monitor.h"
+#include "mozilla/unused.h"
 
 /* Polyfill __func__ on MSVC for consumers to pass to the MediaPromise API. */
 #ifdef _MSC_VER
 #define __func__ __FUNCTION__
 #endif
 
 class nsIEventTarget;
 namespace mozilla {
 
 extern PRLogModuleInfo* gMediaPromiseLog;
 
 #define PROMISE_LOG(x, ...) \
   MOZ_ASSERT(gMediaPromiseLog); \
   PR_LOG(gMediaPromiseLog, PR_LOG_DEBUG, (x, ##__VA_ARGS__))
 
 class MediaTaskQueue;
-class MediaDecoderStateMachineScheduler;
 namespace detail {
 
 nsresult DispatchMediaPromiseRunnable(MediaTaskQueue* aQueue, nsIRunnable* aRunnable);
 nsresult DispatchMediaPromiseRunnable(nsIEventTarget* aTarget, nsIRunnable* aRunnable);
-nsresult DispatchMediaPromiseRunnable(MediaDecoderStateMachineScheduler* aScheduler, nsIRunnable* aRunnable);
 
 #ifdef DEBUG
 void AssertOnThread(MediaTaskQueue* aQueue);
 void AssertOnThread(nsIEventTarget* aTarget);
-void AssertOnThread(MediaDecoderStateMachineScheduler* aScheduler);
 #endif
 
 } // namespace detail
 
 /*
  * A promise manages an asynchronous request that may or may not be able to be
  * fulfilled immediately. When an API returns a promise, the consumer may attach
  * callbacks to be invoked (asynchronously, on a specified thread) when the
@@ -103,28 +101,21 @@ public:
     return Move(p);
   }
 
   class Consumer
   {
   public:
     NS_INLINE_DECL_THREADSAFE_REFCOUNTING(Consumer)
 
-    void Disconnect()
-    {
-      AssertOnDispatchThread();
-      MOZ_DIAGNOSTIC_ASSERT(!mComplete);
-      mDisconnected = true;
-    }
+    virtual void Disconnect() = 0;
 
-#ifdef DEBUG
-    virtual void AssertOnDispatchThread() = 0;
-#else
-    void AssertOnDispatchThread() {}
-#endif
+    // MSVC complains when an inner class (ThenValueBase::{Resolve,Reject}Runnable)
+    // tries to access an inherited protected member.
+    bool IsDisconnected() const { return mDisconnected; }
 
   protected:
     Consumer() : mComplete(false), mDisconnected(false) {}
     virtual ~Consumer() {}
 
     bool mComplete;
     bool mDisconnected;
   };
@@ -144,17 +135,17 @@ protected:
     {
     public:
       ResolveRunnable(ThenValueBase* aThenValue, ResolveValueType aResolveValue)
         : mThenValue(aThenValue)
         , mResolveValue(aResolveValue) {}
 
       ~ResolveRunnable()
       {
-        MOZ_ASSERT(!mThenValue);
+        MOZ_DIAGNOSTIC_ASSERT(!mThenValue || mThenValue->IsDisconnected());
       }
 
       NS_IMETHODIMP Run()
       {
         PROMISE_LOG("ResolveRunnable::Run() [this=%p]", this);
         mThenValue->DoResolve(mResolveValue);
         mThenValue = nullptr;
         return NS_OK;
@@ -169,17 +160,17 @@ protected:
     {
     public:
       RejectRunnable(ThenValueBase* aThenValue, RejectValueType aRejectValue)
         : mThenValue(aThenValue)
         , mRejectValue(aRejectValue) {}
 
       ~RejectRunnable()
       {
-        MOZ_ASSERT(!mThenValue);
+        MOZ_DIAGNOSTIC_ASSERT(!mThenValue || mThenValue->IsDisconnected());
       }
 
       NS_IMETHODIMP Run()
       {
         PROMISE_LOG("RejectRunnable::Run() [this=%p]", this);
         mThenValue->DoReject(mRejectValue);
         mThenValue = nullptr;
         return NS_OK;
@@ -247,71 +238,87 @@ protected:
       MOZ_ASSERT(!aPromise->IsPending());
       bool resolved = aPromise->mResolveValue.isSome();
       nsRefPtr<nsRunnable> runnable =
         resolved ? static_cast<nsRunnable*>(new (typename ThenValueBase::ResolveRunnable)(this, aPromise->mResolveValue.ref()))
                  : static_cast<nsRunnable*>(new (typename ThenValueBase::RejectRunnable)(this, aPromise->mRejectValue.ref()));
       PROMISE_LOG("%s Then() call made from %s [Runnable=%p, Promise=%p, ThenValue=%p]",
                   resolved ? "Resolving" : "Rejecting", ThenValueBase::mCallSite,
                   runnable.get(), aPromise, this);
-      DebugOnly<nsresult> rv = detail::DispatchMediaPromiseRunnable(mResponseTarget, runnable);
-      MOZ_ASSERT(NS_SUCCEEDED(rv));
+      nsresult rv = detail::DispatchMediaPromiseRunnable(mResponseTarget, runnable);
+      unused << rv;
+
+      // NB: mDisconnected is only supposed to be accessed on the dispatch
+      // thread. However, we require the consumer to have disconnected any
+      // oustanding promise requests _before_ initiating shutdown on the
+      // thread or task queue. So the only non-buggy scenario for dispatch
+      // failing involves the target thread being unable to manipulate the
+      // ThenValue (since it's been disconnected), so it's safe to read here.
+      MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(rv) || Consumer::mDisconnected);
     }
 
 #ifdef DEBUG
-  virtual void AssertOnDispatchThread() MOZ_OVERRIDE
+  void AssertOnDispatchThread()
   {
     detail::AssertOnThread(mResponseTarget);
   }
+#else
+  void AssertOnDispatchThread() {}
 #endif
 
+  virtual void Disconnect() MOZ_OVERRIDE
+  {
+    AssertOnDispatchThread();
+    MOZ_DIAGNOSTIC_ASSERT(!Consumer::mComplete);
+    Consumer::mDisconnected = true;
+
+    // If a Consumer has been disconnected, we don't guarantee that the
+    // resolve/reject runnable will be dispatched. Null out our refcounted
+    // this-value now so that it's released predictably on the dispatch thread.
+    mThisVal = nullptr;
+  }
+
   protected:
     virtual void DoResolve(ResolveValueType aResolveValue) MOZ_OVERRIDE
     {
       Consumer::mComplete = true;
       if (Consumer::mDisconnected) {
+        MOZ_ASSERT(!mThisVal);
         PROMISE_LOG("ThenValue::DoResolve disconnected - bailing out [this=%p]", this);
-        // Null these out for the same reasons described below.
-        mResponseTarget = nullptr;
-        mThisVal = nullptr;
         return;
       }
       InvokeCallbackMethod(mThisVal.get(), mResolveMethod, aResolveValue);
 
-      // Null these out after invoking the callback so that any references are
-      // released predictably on the target thread. Otherwise, they would be
+      // Null out mThisVal after invoking the callback so that any references are
+      // released predictably on the dispatch thread. Otherwise, it would be
       // released on whatever thread last drops its reference to the ThenValue,
       // which may or may not be ok.
-      mResponseTarget = nullptr;
       mThisVal = nullptr;
     }
 
     virtual void DoReject(RejectValueType aRejectValue) MOZ_OVERRIDE
     {
       Consumer::mComplete = true;
       if (Consumer::mDisconnected) {
+        MOZ_ASSERT(!mThisVal);
         PROMISE_LOG("ThenValue::DoReject disconnected - bailing out [this=%p]", this);
-        // Null these out for the same reasons described below.
-        mResponseTarget = nullptr;
-        mThisVal = nullptr;
         return;
       }
       InvokeCallbackMethod(mThisVal.get(), mRejectMethod, aRejectValue);
 
-      // Null these out after invoking the callback so that any references are
-      // released predictably on the target thread. Otherwise, they would be
+      // Null out mThisVal after invoking the callback so that any references are
+      // released predictably on the dispatch thread. Otherwise, it would be
       // released on whatever thread last drops its reference to the ThenValue,
       // which may or may not be ok.
-      mResponseTarget = nullptr;
       mThisVal = nullptr;
     }
 
   private:
-    nsRefPtr<TargetType> mResponseTarget;
-    nsRefPtr<ThisType> mThisVal;
+    nsRefPtr<TargetType> mResponseTarget; // May be released on any thread.
+    nsRefPtr<ThisType> mThisVal; // Only accessed and refcounted on dispatch thread.
     ResolveMethodType mResolveMethod;
     RejectMethodType mRejectMethod;
   };
 public:
 
   template<typename TargetType, typename ThisType,
            typename ResolveMethodType, typename RejectMethodType>
   already_AddRefed<Consumer> RefableThen(TargetType* aResponseTarget, const char* aCallSite, ThisType* aThisVal,
@@ -656,17 +663,17 @@ private:
 template<typename PromiseType, typename TargetType>
 static nsRefPtr<PromiseType>
 ProxyInternal(TargetType* aTarget, MethodCallBase<PromiseType>* aMethodCall, const char* aCallerName)
 {
   nsRefPtr<typename PromiseType::Private> p = new (typename PromiseType::Private)(aCallerName);
   nsRefPtr<ProxyRunnable<PromiseType>> r = new ProxyRunnable<PromiseType>(p, aMethodCall);
   nsresult rv = detail::DispatchMediaPromiseRunnable(aTarget, r);
   MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(rv));
-  (void) rv; // Avoid compilation failures in builds with MOZ_DIAGNOSTIC_ASSERT disabled.
+  unused << rv;
   return Move(p);
 }
 
 } // namespace detail
 
 template<typename PromiseType, typename TargetType, typename ThisType>
 static nsRefPtr<PromiseType>
 ProxyMediaCall(TargetType* aTarget, ThisType* aThisVal, const char* aCallerName,
--- a/dom/media/MediaQueue.h
+++ b/dom/media/MediaQueue.h
@@ -152,45 +152,45 @@ template <class T> class MediaQueue : pr
     return frames;
   }
 
   void ClearListeners() {
     ReentrantMonitorAutoEnter mon(mReentrantMonitor);
     mPopListeners.Clear();
   }
 
-  void AddPopListener(nsIRunnable* aRunnable, nsIEventTarget* aTarget) {
+  void AddPopListener(nsIRunnable* aRunnable, MediaTaskQueue* aTarget) {
     ReentrantMonitorAutoEnter mon(mReentrantMonitor);
     mPopListeners.AppendElement(Listener(aRunnable, aTarget));
   }
 
 private:
   mutable ReentrantMonitor mReentrantMonitor;
 
   struct Listener {
-    Listener(nsIRunnable* aRunnable, nsIEventTarget* aTarget)
+    Listener(nsIRunnable* aRunnable, MediaTaskQueue* aTarget)
       : mRunnable(aRunnable)
       , mTarget(aTarget)
     {
     }
     Listener(const Listener& aOther)
       : mRunnable(aOther.mRunnable)
       , mTarget(aOther.mTarget)
     {
     }
     RefPtr<nsIRunnable> mRunnable;
-    RefPtr<nsIEventTarget> mTarget;
+    RefPtr<MediaTaskQueue> mTarget;
   };
 
   nsTArray<Listener> mPopListeners;
 
   void NotifyPopListeners() {
     for (uint32_t i = 0; i < mPopListeners.Length(); i++) {
       Listener& l = mPopListeners[i];
-      l.mTarget->Dispatch(l.mRunnable, NS_DISPATCH_NORMAL);
+      l.mTarget->Dispatch(l.mRunnable);
     }
   }
 
   // True when we've decoded the last frame of data in the
   // bitstream for which we're queueing frame data.
   bool mEndOfStream;
 };
 
--- a/dom/media/MediaStreamGraph.cpp
+++ b/dom/media/MediaStreamGraph.cpp
@@ -273,17 +273,17 @@ MediaStreamGraphImpl::UpdateBufferSuffic
       data->mHaveEnough = track->GetEnd() >= desiredEnd;
       if (!data->mHaveEnough) {
         runnables.MoveElementsFrom(data->mDispatchWhenNotEnough);
       }
     }
   }
 
   for (uint32_t i = 0; i < runnables.Length(); ++i) {
-    runnables[i].mTarget->Dispatch(runnables[i].mRunnable, 0);
+    runnables[i].mTarget->Dispatch(runnables[i].mRunnable);
   }
 }
 
 StreamTime
 MediaStreamGraphImpl::GraphTimeToStreamTime(MediaStream* aStream,
                                             GraphTime aTime)
 {
   MOZ_ASSERT(aTime <= CurrentDriver()->StateComputedTime(),
@@ -1079,16 +1079,33 @@ SetImageToBlackPixel(PlanarYCbCrImage* a
   data.mYChannel = blackPixel;
   data.mCbChannel = blackPixel + 1;
   data.mCrChannel = blackPixel + 2;
   data.mYStride = data.mCbCrStride = 1;
   data.mPicSize = data.mYSize = data.mCbCrSize = IntSize(1, 1);
   aImage->SetData(data);
 }
 
+class VideoFrameContainerInvalidateRunnable : public nsRunnable {
+public:
+  explicit VideoFrameContainerInvalidateRunnable(VideoFrameContainer* aVideoFrameContainer)
+    : mVideoFrameContainer(aVideoFrameContainer)
+  {}
+  NS_IMETHOD Run()
+  {
+    MOZ_ASSERT(NS_IsMainThread());
+
+    mVideoFrameContainer->Invalidate();
+
+    return NS_OK;
+  }
+private:
+  nsRefPtr<VideoFrameContainer> mVideoFrameContainer;
+};
+
 void
 MediaStreamGraphImpl::PlayVideo(MediaStream* aStream)
 {
   MOZ_ASSERT(mRealtime, "Should only attempt to play video in realtime mode");
 
   if (aStream->mVideoOutputs.IsEmpty())
     return;
 
@@ -1142,17 +1159,17 @@ MediaStreamGraphImpl::PlayVideo(MediaStr
       output->SetCurrentFrame(frame->GetIntrinsicSize(), image,
                               targetTime);
     } else {
       output->SetCurrentFrame(frame->GetIntrinsicSize(), frame->GetImage(),
                               targetTime);
     }
 
     nsCOMPtr<nsIRunnable> event =
-      NS_NewRunnableMethod(output, &VideoFrameContainer::Invalidate);
+      new VideoFrameContainerInvalidateRunnable(output);
     DispatchToMainThreadAfterStreamStateUpdate(event.forget());
   }
   if (!aStream->mNotifiedFinished) {
     aStream->mLastPlayedVideoFrame = *frame;
   }
 }
 
 bool
@@ -2471,31 +2488,31 @@ SourceMediaStream::GetEndOfAppendedData(
     return track->mEndOfFlushedData + track->mData->GetDuration();
   }
   NS_ERROR("Track not found");
   return 0;
 }
 
 void
 SourceMediaStream::DispatchWhenNotEnoughBuffered(TrackID aID,
-    nsIEventTarget* aSignalThread, nsIRunnable* aSignalRunnable)
+    MediaTaskQueue* aSignalQueue, nsIRunnable* aSignalRunnable)
 {
   MutexAutoLock lock(mMutex);
   TrackData* data = FindDataForTrack(aID);
   if (!data) {
-    aSignalThread->Dispatch(aSignalRunnable, 0);
+    aSignalQueue->Dispatch(aSignalRunnable);
     return;
   }
 
   if (data->mHaveEnough) {
     if (data->mDispatchWhenNotEnough.IsEmpty()) {
-      data->mDispatchWhenNotEnough.AppendElement()->Init(aSignalThread, aSignalRunnable);
+      data->mDispatchWhenNotEnough.AppendElement()->Init(aSignalQueue, aSignalRunnable);
     }
   } else {
-    aSignalThread->Dispatch(aSignalRunnable, 0);
+    aSignalQueue->Dispatch(aSignalRunnable);
   }
 }
 
 void
 SourceMediaStream::EndTrack(TrackID aID)
 {
   MutexAutoLock lock(mMutex);
   TrackData *track = FindDataForTrack(aID);
--- a/dom/media/MediaStreamGraph.h
+++ b/dom/media/MediaStreamGraph.h
@@ -11,16 +11,17 @@
 #include "AudioStream.h"
 #include "nsTArray.h"
 #include "nsIRunnable.h"
 #include "StreamBuffer.h"
 #include "TimeVarying.h"
 #include "VideoFrameContainer.h"
 #include "VideoSegment.h"
 #include "MainThreadUtils.h"
+#include "MediaTaskQueue.h"
 #include "nsAutoRef.h"
 #include "GraphDriver.h"
 #include <speex/speex_resampler.h>
 #include "mozilla/dom/AudioChannelBinding.h"
 #include "DOMMediaStream.h"
 
 class nsIRunnable;
 
@@ -778,17 +779,17 @@ public:
   StreamTime GetEndOfAppendedData(TrackID aID);
   /**
    * Ensures that aSignalRunnable will be dispatched to aSignalThread
    * when we don't have enough buffered data in the track (which could be
    * immediately). Will dispatch the runnable immediately if the track
    * does not exist. No op if a runnable is already present for this track.
    */
   void DispatchWhenNotEnoughBuffered(TrackID aID,
-      nsIEventTarget* aSignalThread, nsIRunnable* aSignalRunnable);
+      MediaTaskQueue* aSignalQueue, nsIRunnable* aSignalRunnable);
   /**
    * Indicate that a track has ended. Do not do any more API calls
    * affecting this track.
    * Ignored if the track does not exist.
    */
   void EndTrack(TrackID aID);
   /**
    * Indicate that no tracks will be added starting before time aKnownTime.
@@ -842,24 +843,24 @@ public:
   void RegisterForAudioMixing();
 
   // XXX need a Reset API
 
   friend class MediaStreamGraphImpl;
 
 protected:
   struct ThreadAndRunnable {
-    void Init(nsIEventTarget* aTarget, nsIRunnable* aRunnable)
+    void Init(MediaTaskQueue* aTarget, nsIRunnable* aRunnable)
     {
       mTarget = aTarget;
       mRunnable = aRunnable;
     }
 
-    nsCOMPtr<nsIEventTarget> mTarget;
-    nsCOMPtr<nsIRunnable> mRunnable;
+    nsRefPtr<MediaTaskQueue> mTarget;
+    RefPtr<nsIRunnable> mRunnable;
   };
   enum TrackCommands {
     TRACK_CREATE = MediaStreamListener::TRACK_EVENT_CREATED,
     TRACK_END = MediaStreamListener::TRACK_EVENT_ENDED
   };
   /**
    * Data for each track that hasn't ended.
    */
new file mode 100644
--- /dev/null
+++ b/dom/media/MediaTimer.cpp
@@ -0,0 +1,165 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "MediaTimer.h"
+
+#include <math.h>
+
+#include "nsComponentManagerUtils.h"
+#include "nsThreadUtils.h"
+
+namespace mozilla {
+
+NS_IMPL_ADDREF(MediaTimer)
+NS_IMPL_RELEASE_WITH_DESTROY(MediaTimer, DispatchDestroy())
+
+MediaTimer::MediaTimer()
+  : mMonitor("MediaTimer Monitor")
+  , mTimer(do_CreateInstance("@mozilla.org/timer;1"))
+  , mUpdateScheduled(false)
+{
+  // Use the SharedThreadPool to create an nsIThreadPool with a maximum of one
+  // thread, which is equivalent to an nsIThread for our purposes.
+  RefPtr<SharedThreadPool> threadPool(
+    SharedThreadPool::Get(NS_LITERAL_CSTRING("MediaTimer"), 1));
+  mThread = threadPool.get();
+  mTimer->SetTarget(mThread);
+}
+
+void
+MediaTimer::DispatchDestroy()
+{
+  nsCOMPtr<nsIRunnable> task = NS_NewNonOwningRunnableMethod(this, &MediaTimer::Destroy);
+  nsresult rv = mThread->Dispatch(task, NS_DISPATCH_NORMAL);
+  MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(rv));
+  (void) rv;
+}
+
+void
+MediaTimer::Destroy()
+{
+  MOZ_ASSERT(OnMediaTimerThread());
+
+  // Reject any outstanding entries. There's no need to acquire the monitor
+  // here, because we're on the timer thread and all other references to us
+  // must be gone.
+  while (!mEntries.empty()) {
+    mEntries.top().mPromise->Reject(false, __func__);
+    mEntries.pop();
+  }
+
+  // Cancel the timer if necessary.
+  CancelTimerIfArmed();
+
+  delete this;
+}
+
+bool
+MediaTimer::OnMediaTimerThread()
+{
+  bool rv = false;
+  mThread->IsOnCurrentThread(&rv);
+  return rv;
+}
+
+nsRefPtr<MediaTimerPromise>
+MediaTimer::WaitUntil(const TimeStamp& aTimeStamp, const char* aCallSite)
+{
+  MonitorAutoLock mon(mMonitor);
+  Entry e(aTimeStamp, aCallSite);
+  nsRefPtr<MediaTimerPromise> p = e.mPromise.get();
+  mEntries.push(e);
+  ScheduleUpdate();
+  return p;
+}
+
+void
+MediaTimer::ScheduleUpdate()
+{
+  mMonitor.AssertCurrentThreadOwns();
+  if (mUpdateScheduled) {
+    return;
+  }
+  mUpdateScheduled = true;
+
+  nsCOMPtr<nsIRunnable> task = NS_NewRunnableMethod(this, &MediaTimer::Update);
+  nsresult rv = mThread->Dispatch(task, NS_DISPATCH_NORMAL);
+  MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(rv));
+  (void) rv;
+}
+
+void
+MediaTimer::Update()
+{
+  MonitorAutoLock mon(mMonitor);
+  UpdateLocked();
+}
+
+void
+MediaTimer::UpdateLocked()
+{
+  MOZ_ASSERT(OnMediaTimerThread());
+  mMonitor.AssertCurrentThreadOwns();
+  mUpdateScheduled = false;
+
+  // Resolve all the promises whose time is up.
+  TimeStamp now = TimeStamp::Now();
+  while (!mEntries.empty() && mEntries.top().mTimeStamp <= now) {
+    mEntries.top().mPromise->Resolve(true, __func__);
+    mEntries.pop();
+  }
+
+  // If we've got no more entries, cancel any pending timer and bail out.
+  if (mEntries.empty()) {
+    CancelTimerIfArmed();
+    return;
+  }
+
+  // We've got more entries - (re)arm the timer for the soonest one.
+  if (!TimerIsArmed() || mEntries.top().mTimeStamp < mCurrentTimerTarget) {
+    CancelTimerIfArmed();
+    ArmTimer(mEntries.top().mTimeStamp, now);
+  }
+}
+
+/*
+ * We use a callback function, rather than a callback method, to ensure that
+ * the nsITimer does not artifically keep the refcount of the MediaTimer above
+ * zero. When the MediaTimer is destroyed, it safely cancels the nsITimer so that
+ * we never fire against a dangling closure.
+ */
+
+/* static */ void
+MediaTimer::TimerCallback(nsITimer* aTimer, void* aClosure)
+{
+  static_cast<MediaTimer*>(aClosure)->TimerFired();
+}
+
+void
+MediaTimer::TimerFired()
+{
+  MonitorAutoLock mon(mMonitor);
+  MOZ_ASSERT(OnMediaTimerThread());
+  mCurrentTimerTarget = TimeStamp();
+  UpdateLocked();
+}
+
+void
+MediaTimer::ArmTimer(const TimeStamp& aTarget, const TimeStamp& aNow)
+{
+  MOZ_DIAGNOSTIC_ASSERT(!TimerIsArmed());
+  MOZ_DIAGNOSTIC_ASSERT(aTarget > aNow);
+
+  // XPCOM timer resolution is in milliseconds. It's important to never resolve
+  // a timer when mTarget might compare < now (even if very close), so round up.
+  unsigned long delay = std::ceil((aTarget - aNow).ToMilliseconds());
+  mCurrentTimerTarget = aTarget;
+  nsresult rv = mTimer->InitWithFuncCallback(&TimerCallback, this, delay, nsITimer::TYPE_ONE_SHOT);
+  MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(rv));
+  (void) rv;
+}
+
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/media/MediaTimer.h
@@ -0,0 +1,99 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#if !defined(MediaTimer_h_)
+#define MediaTimer_h_
+
+#include "MediaPromise.h"
+
+#include <queue>
+
+#include "nsITimer.h"
+#include "nsRefPtr.h"
+
+#include "mozilla/Monitor.h"
+#include "mozilla/TimeStamp.h"
+
+namespace mozilla {
+
+// This promise type is only exclusive because so far there isn't a reason for
+// it not to be. Feel free to change that.
+typedef MediaPromise<bool, bool, /* IsExclusive = */ true> MediaTimerPromise;
+
+// Timers only know how to fire at a given thread, which creates an impedence
+// mismatch with code that operates with MediaTaskQueues. This class solves
+// that mismatch with a dedicated (but shared) thread and a nice MediaPromise-y
+// interface.
+class MediaTimer
+{
+public:
+  MediaTimer();
+
+  // We use a release with a custom Destroy().
+  NS_IMETHOD_(MozExternalRefCountType) AddRef(void);
+  NS_IMETHOD_(MozExternalRefCountType) Release(void);
+
+  nsRefPtr<MediaTimerPromise> WaitUntil(const TimeStamp& aTimeStamp, const char* aCallSite);
+
+private:
+  virtual ~MediaTimer() { MOZ_ASSERT(OnMediaTimerThread()); }
+
+  void DispatchDestroy(); // Invoked by Release on an arbitrary thread.
+  void Destroy(); // Runs on the timer thread.
+
+  bool OnMediaTimerThread();
+  void ScheduleUpdate();
+  void Update();
+  void UpdateLocked();
+
+  static void TimerCallback(nsITimer* aTimer, void* aClosure);
+  void TimerFired();
+  void ArmTimer(const TimeStamp& aTarget, const TimeStamp& aNow);
+
+  bool TimerIsArmed()
+  {
+    return !mCurrentTimerTarget.IsNull();
+  }
+
+  void CancelTimerIfArmed()
+  {
+    MOZ_ASSERT(OnMediaTimerThread());
+    if (TimerIsArmed()) {
+      mTimer->Cancel();
+      mCurrentTimerTarget = TimeStamp();
+    }
+  }
+
+
+  struct Entry
+  {
+    TimeStamp mTimeStamp;
+    nsRefPtr<MediaTimerPromise::Private> mPromise;
+
+    explicit Entry(const TimeStamp& aTimeStamp, const char* aCallSite)
+      : mTimeStamp(aTimeStamp)
+      , mPromise(new MediaTimerPromise::Private(aCallSite))
+    {}
+
+    bool operator<(const Entry& aOther) const
+    {
+      return mTimeStamp < aOther.mTimeStamp;
+    }
+  };
+
+  ThreadSafeAutoRefCnt mRefCnt;
+  NS_DECL_OWNINGTHREAD
+  nsCOMPtr<nsIEventTarget> mThread;
+  std::priority_queue<Entry> mEntries;
+  Monitor mMonitor;
+  nsCOMPtr<nsITimer> mTimer;
+  TimeStamp mCurrentTimerTarget;
+  bool mUpdateScheduled;
+};
+
+} // namespace mozilla
+
+#endif
--- a/dom/media/fmp4/AVCCDecoderModule.cpp
+++ b/dom/media/fmp4/AVCCDecoderModule.cpp
@@ -131,17 +131,19 @@ AVCCMediaDataDecoder::Drain()
   }
   return mLastError;
 }
 
 nsresult
 AVCCMediaDataDecoder::Shutdown()
 {
   if (mDecoder) {
-    return mDecoder->Shutdown();
+    nsresult rv = mDecoder->Shutdown();
+    mDecoder = nullptr;
+    return rv;
   }
   return NS_OK;
 }
 
 bool
 AVCCMediaDataDecoder::IsWaitingMediaResources()
 {
   if (mDecoder) {
@@ -160,20 +162,17 @@ void
 AVCCMediaDataDecoder::AllocateMediaResources()
 {
   // Nothing to do, decoder will be allocated on the fly when required.
 }
 
 void
 AVCCMediaDataDecoder::ReleaseMediaResources()
 {
-  if (mDecoder) {
-    mDecoder->Shutdown();
-    mDecoder = nullptr;
-  }
+  Shutdown();
 }
 
 nsresult
 AVCCMediaDataDecoder::CreateDecoder()
 {
   if (!mp4_demuxer::AnnexB::HasSPS(mCurrentConfig.extra_data)) {
     // nothing found yet, will try again later
     return NS_ERROR_NOT_INITIALIZED;
@@ -275,17 +274,18 @@ already_AddRefed<MediaDataDecoder>
 AVCCDecoderModule::CreateVideoDecoder(const mp4_demuxer::VideoDecoderConfig& aConfig,
                                       layers::LayersBackend aLayersBackend,
                                       layers::ImageContainer* aImageContainer,
                                       FlushableMediaTaskQueue* aVideoTaskQueue,
                                       MediaDataDecoderCallback* aCallback)
 {
   nsRefPtr<MediaDataDecoder> decoder;
 
-  if (strcmp(aConfig.mime_type, "video/avc") ||
+  if ((strcmp(aConfig.mime_type, "video/avc") &&
+       strcmp(aConfig.mime_type, "video/mp4")) ||
       !mPDM->DecoderNeedsAVCC(aConfig)) {
     // There is no need for an AVCC wrapper for non-AVC content.
     decoder = mPDM->CreateVideoDecoder(aConfig,
                                        aLayersBackend,
                                        aImageContainer,
                                        aVideoTaskQueue,
                                        aCallback);
   } else {
--- a/dom/media/fmp4/MP4Reader.cpp
+++ b/dom/media/fmp4/MP4Reader.cpp
@@ -34,18 +34,18 @@ using mozilla::layers::LayersBackend;
 #ifdef PR_LOGGING
 PRLogModuleInfo* GetDemuxerLog() {
   static PRLogModuleInfo* log = nullptr;
   if (!log) {
     log = PR_NewLogModule("MP4Demuxer");
   }
   return log;
 }
-#define LOG(...) PR_LOG(GetDemuxerLog(), PR_LOG_DEBUG, (__VA_ARGS__))
-#define VLOG(...) PR_LOG(GetDemuxerLog(), PR_LOG_DEBUG+1, (__VA_ARGS__))
+#define LOG(arg, ...) PR_LOG(GetDemuxerLog(), PR_LOG_DEBUG, ("MP4Reader(%p)::%s: " arg, this, __func__, ##__VA_ARGS__))
+#define VLOG(arg, ...) PR_LOG(GetDemuxerLog(), PR_LOG_DEBUG, ("MP4Reader(%p)::%s: " arg, this, __func__, ##__VA_ARGS__))
 #else
 #define LOG(...)
 #define VLOG(...)
 #endif
 
 using namespace mp4_demuxer;
 
 namespace mozilla {
@@ -317,17 +317,17 @@ bool MP4Reader::IsWaitingOnCDMResource()
     if (!proxy) {
       // We're encrypted, we need a CDMProxy to decrypt file.
       return true;
     }
   }
   // We'll keep waiting if the CDM hasn't informed Gecko of its capabilities.
   {
     CDMCaps::AutoLock caps(proxy->Capabilites());
-    LOG("MP4Reader::IsWaitingMediaResources() capsKnown=%d", caps.AreCapsKnown());
+    LOG("capsKnown=%d", caps.AreCapsKnown());
     return !caps.AreCapsKnown();
   }
 #else
   return false;
 #endif
 }
 
 bool MP4Reader::IsWaitingMediaResources()
@@ -613,17 +613,17 @@ MP4Reader::ShouldSkip(bool aSkipToNextKe
   return nextKeyframe < aTimeThreshold;
 }
 
 nsRefPtr<MediaDecoderReader::VideoDataPromise>
 MP4Reader::RequestVideoData(bool aSkipToNextKeyframe,
                             int64_t aTimeThreshold)
 {
   MOZ_ASSERT(GetTaskQueue()->IsCurrentThreadIn());
-  VLOG("RequestVideoData skip=%d time=%lld", aSkipToNextKeyframe, aTimeThreshold);
+  VLOG("skip=%d time=%lld", aSkipToNextKeyframe, aTimeThreshold);
 
   if (mShutdown) {
     NS_WARNING("RequestVideoData on shutdown MP4Reader!");
     return VideoDataPromise::CreateAndReject(CANCELED, __func__);
   }
 
   MOZ_ASSERT(HasVideo() && mPlatform && mVideo.mDecoder);
 
@@ -649,17 +649,17 @@ MP4Reader::RequestVideoData(bool aSkipTo
 
   return p;
 }
 
 nsRefPtr<MediaDecoderReader::AudioDataPromise>
 MP4Reader::RequestAudioData()
 {
   MOZ_ASSERT(GetTaskQueue()->IsCurrentThreadIn());
-  VLOG("RequestAudioData");
+  VLOG("");
   if (mShutdown) {
     NS_WARNING("RequestAudioData on shutdown MP4Reader!");
     return AudioDataPromise::CreateAndReject(CANCELED, __func__);
   }
 
   MonitorAutoLock lock(mAudio.mMonitor);
   nsRefPtr<AudioDataPromise> p = mAudio.mPromise.Ensure(__func__);
   ScheduleUpdate(kAudio);
@@ -784,17 +784,17 @@ MP4Reader::ReturnOutput(MediaData* aData
     aData->mDiscontinuity = true;
   }
 
   if (aTrack == kAudio) {
     AudioData* audioData = static_cast<AudioData*>(aData);
 
     if (audioData->mChannels != mInfo.mAudio.mChannels ||
         audioData->mRate != mInfo.mAudio.mRate) {
-      LOG("MP4Reader::ReturnOutput change of sampling rate:%d->%d",
+      LOG("change of sampling rate:%d->%d",
           mInfo.mAudio.mRate, audioData->mRate);
       mInfo.mAudio.mRate = audioData->mRate;
       mInfo.mAudio.mChannels = audioData->mChannels;
     }
 
     mAudio.mPromise.Resolve(audioData, __func__);
   } else if (aTrack == kVideo) {
     mVideo.mPromise.Resolve(static_cast<VideoData*>(aData), __func__);
@@ -994,17 +994,17 @@ MP4Reader::SkipVideoDemuxToNextKeyFrame(
   }
 
   return true;
 }
 
 nsRefPtr<MediaDecoderReader::SeekPromise>
 MP4Reader::Seek(int64_t aTime, int64_t aEndTime)
 {
-  LOG("MP4Reader::Seek(%lld)", aTime);
+  LOG("aTime=(%lld)", aTime);
   MOZ_ASSERT(GetTaskQueue()->IsCurrentThreadIn());
   MonitorAutoLock mon(mDemuxerMonitor);
   if (!mDecoder->GetResource()->IsTransportSeekable() || !mDemuxer->CanSeek()) {
     VLOG("Seek() END (Unseekable)");
     return SeekPromise::CreateAndReject(NS_ERROR_FAILURE, __func__);
   }
 
   int64_t seekTime = aTime;
@@ -1014,17 +1014,17 @@ MP4Reader::Seek(int64_t aTime, int64_t a
     mQueuedVideoSample = PopSampleLocked(kVideo);
     if (mQueuedVideoSample) {
       seekTime = mQueuedVideoSample->mMp4Sample->composition_timestamp;
     }
   }
   if (mDemuxer->HasValidAudio()) {
     mAudio.mTrackDemuxer->Seek(seekTime);
   }
-  LOG("MP4Reader::Seek(%lld) exit", aTime);
+  LOG("aTime=%lld exit", aTime);
   return SeekPromise::CreateAndResolve(seekTime, __func__);
 }
 
 void
 MP4Reader::UpdateIndex()
 {
   if (!mIndexReady) {
     return;
@@ -1078,18 +1078,19 @@ bool MP4Reader::IsDormantNeeded()
 {
 #if defined(MP4_READER_DORMANT)
   return
 #if defined(MP4_READER_DORMANT_HEURISTIC)
         mDormantEnabled &&
 #endif
         mVideo.mDecoder &&
         mVideo.mDecoder->IsDormantNeeded();
+#else
+  return false;
 #endif
-  return false;
 }
 
 void MP4Reader::ReleaseMediaResources()
 {
   // Before freeing a video codec, all video buffers needed to be released
   // even from graphics pipeline.
   VideoFrameContainer* container = mDecoder->GetVideoFrameContainer();
   if (container) {
--- a/dom/media/fmp4/ffmpeg/FFmpegH264Decoder.cpp
+++ b/dom/media/fmp4/ffmpeg/FFmpegH264Decoder.cpp
@@ -284,17 +284,17 @@ FFmpegH264Decoder<LIBAV_VER>::Flush()
 FFmpegH264Decoder<LIBAV_VER>::~FFmpegH264Decoder()
 {
   MOZ_COUNT_DTOR(FFmpegH264Decoder);
 }
 
 AVCodecID
 FFmpegH264Decoder<LIBAV_VER>::GetCodecId(const char* aMimeType)
 {
-  if (!strcmp(aMimeType, "video/avc")) {
+  if (!strcmp(aMimeType, "video/avc") || !strcmp(aMimeType, "video/mp4")) {
     return AV_CODEC_ID_H264;
   }
 
   if (!strcmp(aMimeType, "video/x-vnd.on2.vp6")) {
     return AV_CODEC_ID_VP6F;
   }
 
   return AV_CODEC_ID_NONE;
--- a/dom/media/gmp/GMPChild.cpp
+++ b/dom/media/gmp/GMPChild.cpp
@@ -38,28 +38,43 @@ static const int MAX_VOUCHER_LENGTH = 50
 #define TARGET_SANDBOX_EXPORTS
 #include "mozilla/sandboxTarget.h"
 #elif defined(XP_MACOSX)
 #include "mozilla/Sandbox.h"
 #endif
 #endif
 
 namespace mozilla {
+
+#undef LOG
+#undef LOGD
+
+#ifdef PR_LOGGING
+extern PRLogModuleInfo* GetGMPLog();
+#define LOG(level, x, ...) PR_LOG(GetGMPLog(), (level), (x, ##__VA_ARGS__))
+#define LOGD(x, ...) LOG(PR_LOG_DEBUG, "GMPChild[pid=%d] " x, (int)base::GetCurrentProcId(), ##__VA_ARGS__)
+#else
+#define LOG(level, x, ...)
+#define LOGD(x, ...)
+#endif
+
 namespace gmp {
 
 GMPChild::GMPChild()
   : mAsyncShutdown(nullptr)
   , mGMPMessageLoop(MessageLoop::current())
   , mGMPLoader(nullptr)
 {
+  LOGD("GMPChild ctor");
   nsDebugImpl::SetMultiprocessMode("GMP");
 }
 
 GMPChild::~GMPChild()
 {
+  LOGD("GMPChild dtor");
 }
 
 static bool
 GetFileBase(const std::string& aPluginPath,
 #if defined(XP_MACOSX)
             nsCOMPtr<nsIFile>& aLibDirectory,
 #endif
             nsCOMPtr<nsIFile>& aFileBase,
@@ -252,32 +267,36 @@ GMPChild::CheckThread()
 
 bool
 GMPChild::Init(const std::string& aPluginPath,
                const std::string& aVoucherPath,
                base::ProcessHandle aParentProcessHandle,
                MessageLoop* aIOLoop,
                IPC::Channel* aChannel)
 {
-  if (!Open(aChannel, aParentProcessHandle, aIOLoop)) {
+  LOGD("%s pluginPath=%s", __FUNCTION__, aPluginPath.c_str());
+
+  if (NS_WARN_IF(!Open(aChannel, aParentProcessHandle, aIOLoop))) {
     return false;
   }
 
 #ifdef MOZ_CRASHREPORTER
   SendPCrashReporterConstructor(CrashReporter::CurrentThreadId());
 #endif
 
   mPluginPath = aPluginPath;
   mVoucherPath = aVoucherPath;
   return true;
 }
 
 bool
 GMPChild::RecvSetNodeId(const nsCString& aNodeId)
 {
+  LOGD("%s nodeId=%s", __FUNCTION__, aNodeId.Data());
+
   // Store the per origin salt for the node id. Note: we do this in a
   // separate message than RecvStartPlugin() so that the string is not
   // sitting in a string on the IPC code's call stack.
   mNodeId = std::string(aNodeId.BeginReading(), aNodeId.EndReading());
   return true;
 }
 
 GMPErr
@@ -388,16 +407,18 @@ GMPChild::GetLibPath(nsACString& aOutLib
   }
   return NS_SUCCEEDED(libFile->GetNativePath(aOutLibPath));
 #endif
 }
 
 bool
 GMPChild::RecvStartPlugin()
 {
+  LOGD("%s", __FUNCTION__);
+
 #if defined(XP_WIN)
   PreLoadLibraries(mPluginPath);
 #endif
   PreLoadPluginVoucher(mPluginPath);
   PreLoadSandboxVoucher();
 
   nsCString libPath;
   if (!GetLibPath(libPath)) {
@@ -442,16 +463,18 @@ MessageLoop*
 GMPChild::GMPMessageLoop()
 {
   return mGMPMessageLoop;
 }
 
 void
 GMPChild::ActorDestroy(ActorDestroyReason aWhy)
 {
+  LOGD("%s reason=%d", __FUNCTION__, aWhy);
+
   if (mGMPLoader) {
     mGMPLoader->Shutdown();
   }
   if (AbnormalShutdown == aWhy) {
     NS_WARNING("Abnormal shutdown of GMP process!");
     _exit(0);
   }
 
@@ -673,28 +696,31 @@ GMPChild::RecvCrashPluginNow()
 {
   MOZ_CRASH();
   return true;
 }
 
 bool
 GMPChild::RecvBeginAsyncShutdown()
 {
+  LOGD("%s AsyncShutdown=%d", __FUNCTION__, mAsyncShutdown!=nullptr);
+
   MOZ_ASSERT(mGMPMessageLoop == MessageLoop::current());
   if (mAsyncShutdown) {
     mAsyncShutdown->BeginShutdown();
   } else {
     ShutdownComplete();
   }
   return true;
 }
 
 void
 GMPChild::ShutdownComplete()
 {
+  LOGD("%s", __FUNCTION__);
   MOZ_ASSERT(mGMPMessageLoop == MessageLoop::current());
   SendAsyncShutdownComplete();
 }
 
 static bool
 GetPluginVoucherFile(const std::string& aPluginPath,
                      nsCOMPtr<nsIFile>& aOutVoucherFile)
 {
@@ -770,8 +796,11 @@ GMPChild::PreLoadSandboxVoucher()
   if (!stream) {
     NS_WARNING("PreLoadSandboxVoucher failed to read plugin voucher file!");
     return;
   }
 }
 
 } // namespace gmp
 } // namespace mozilla
+
+#undef LOG
+#undef LOGD
--- a/dom/media/mediasource/ContainerParser.cpp
+++ b/dom/media/mediasource/ContainerParser.cpp
@@ -288,17 +288,17 @@ public:
     bool initSegment = IsInitSegmentPresent(aData);
     if (initSegment) {
       mResource = new SourceBufferResource(NS_LITERAL_CSTRING("video/mp4"));
       mStream = new MP4Stream(mResource);
       // We use a timestampOffset of 0 for ContainerParser, and require
       // consumers of ParseStartAndEndTimestamps to add their timestamp offset
       // manually. This allows the ContainerParser to be shared across different
       // timestampOffsets.
-      mParser = new mp4_demuxer::MoofParser(mStream, 0, &mMonitor);
+      mParser = new mp4_demuxer::MoofParser(mStream, 0, /* aIsAudio = */ false, &mMonitor);
       mInitData = new LargeDataBuffer();
     } else if (!mStream || !mParser) {
       return false;
     }
 
     mResource->AppendData(aData);
     nsTArray<MediaByteRange> byteRanges;
     MediaByteRange mbr =
--- a/dom/media/moz.build
+++ b/dom/media/moz.build
@@ -97,26 +97,26 @@ EXPORTS += [
     'GraphDriver.h',
     'Latency.h',
     'MediaCache.h',
     'MediaData.h',
     'MediaDecoder.h',
     'MediaDecoderOwner.h',
     'MediaDecoderReader.h',
     'MediaDecoderStateMachine.h',
-    'MediaDecoderStateMachineScheduler.h',
     'MediaInfo.h',
     'MediaMetadataManager.h',
     'MediaPromise.h',
     'MediaQueue.h',
     'MediaRecorder.h',
     'MediaResource.h',
     'MediaSegment.h',
     'MediaStreamGraph.h',
     'MediaTaskQueue.h',
+    'MediaTimer.h',
     'MediaTrack.h',
     'MediaTrackList.h',
     'MP3FrameParser.h',
     'nsIDocumentActivity.h',
     'RtspMediaResource.h',
     'SelfRef.h',
     'SharedBuffer.h',
     'SharedThreadPool.h',
@@ -176,27 +176,27 @@ UNIFIED_SOURCES += [
     'GetUserMediaRequest.cpp',
     'GraphDriver.cpp',
     'Latency.cpp',
     'MediaCache.cpp',
     'MediaData.cpp',
     'MediaDecoder.cpp',
     'MediaDecoderReader.cpp',
     'MediaDecoderStateMachine.cpp',
-    'MediaDecoderStateMachineScheduler.cpp',
     'MediaDevices.cpp',
     'MediaManager.cpp',
     'MediaPromise.cpp',
     'MediaRecorder.cpp',
     'MediaResource.cpp',
     'MediaShutdownManager.cpp',
     'MediaStreamError.cpp',
     'MediaStreamGraph.cpp',
     'MediaStreamTrack.cpp',
     'MediaTaskQueue.cpp',
+    'MediaTimer.cpp',
     'MediaTrack.cpp',
     'MediaTrackList.cpp',
     'MP3FrameParser.cpp',
     'RTCIdentityProviderRegistrar.cpp',
     'RtspMediaResource.cpp',
     'SharedThreadPool.cpp',
     'StreamBuffer.cpp',
     'TextTrack.cpp',
--- a/dom/media/tests/mochitest/test_peerConnection_webAudio.html
+++ b/dom/media/tests/mochitest/test_peerConnection_webAudio.html
@@ -12,22 +12,34 @@ createHTML({
 });
 
 // This tests WebAudio as input to a PeerConnection and a PeerConnection as
 // input to WebAudio. This is done by piping a 700Hz oscillator through an
 // analyser on the input side, the PeerConnection, and an analyser on the
 // output side. We then sanity check the audio by comparing the frequency domain
 // data from both analysers.
 
+/*
+ * Use as callback to Array.reduce to get an object { value, index }
+ * that contains the largest element in the array.
+ */
+var maxWithIndex = function(a, b, i) {
+  if (b >= a.value) {
+    return { value: b, index: i };
+  } else {
+    return a;
+  }
+};
+
 runNetworkTest(function() {
   var test = new PeerConnectionTest();
 
   var audioContext = new AudioContext();
-  var inputAnalyser;
-  var outputAnalyser;
+  var inputAnalyser, outputAnalyser;
+  var inputData, outputData;
 
   test.setMediaConstraints([{audio: true}], []);
   test.chain.replace("PC_LOCAL_GUM", [
     function PC_LOCAL_WEBAUDIO_SOURCE(test) {
       var oscillator = audioContext.createOscillator();
       oscillator.type = 'sine';
       oscillator.frequency.value = 700;
       oscillator.start();
@@ -51,55 +63,48 @@ runNetworkTest(function() {
 
         source.connect(outputAnalyser);
         outputAnalyser.connect(dest);
         realAttachMedia(dest.stream, type, side);
       };
       return Promise.resolve();
   }]);
   test.chain.append([
-    function WAIT_FOR_CLEAN_AUDIO(test) {
+    function GET_INPUT_DATA(test) {
+      inputData = new Uint8Array(inputAnalyser.frequencyBinCount);
+      inputAnalyser.getByteFrequencyData(inputData);
+      return Promise.resolve();
+    },
+    function GET_OUTPUT_DATA(test) {
       // We've seen completely silent output with e10s, suggesting that the
-      // machine is overloaded. Here we wait for the media element on the
-      // output side to progress a bit after all previous steps finish to
-      // ensure we have healthy data to check.
-      var wait = function(elem, startTime, resolve) {
-        elem.ontimeupdate = function(ev) {
-          info("Waiting... current: " + elem.currentTime + ", start: " + startTime);
-          if (elem.currentTime - startTime < 0.5) {
+      // machine is overloaded. Here we wait for actual audio data on the
+      // output side before we proceed.
+      return new Promise(resolve => {
+        is(test.pcRemote.mediaCheckers.length, 1, "One media element on remote side");
+        var elem = test.pcRemote.mediaCheckers[0].element;
+        var data = new Uint8Array(outputAnalyser.frequencyBinCount);
+        elem.ontimeupdate = ev => {
+          outputAnalyser.getByteFrequencyData(data);
+          if (data.reduce(maxWithIndex, { value: -1, index: -1 }).value === 0) {
+            info("Waiting for output data... time: " + elem.currentTime);
             return;
           }
           elem.ontimeupdate = null;
+          outputData = data;
           resolve();
-        }
-      };
-      return Promise.all(test.pcRemote.mediaCheckers.map(function(checker) {
-        var elem = checker.element;
-        var startTime = elem.currentTime;
-        return new Promise((y, n) => wait(elem, startTime, y));
-      }));
+        };
+      });
     },
     function CHECK_AUDIO_FLOW(test) {
       // This is for sanity check only. We'll deem that the streams are working
       // if the global maxima in the frequency domain for both the input and
       // the output are within 10 (out of 1024) steps of each other.
 
-      var inputData = new Uint8Array(inputAnalyser.frequencyBinCount);
-      inputAnalyser.getByteFrequencyData(inputData);
-      var outputData = new Uint8Array(outputAnalyser.frequencyBinCount);
-      outputAnalyser.getByteFrequencyData(outputData);
       is(inputData.length, outputData.length, "Equally sized datasets");
 
-      var maxWithIndex = function(a, b, i) {
-        if (b >= a.value) {
-          return { value: b, index: i };
-        } else {
-          return a;
-        }
-      };
       var initialValue = { value: -1, index: -1 };
       var inputMax = inputData.reduce(maxWithIndex, initialValue);
       var outputMax = outputData.reduce(maxWithIndex, initialValue);
 
       // Mild paranoia
       isnot(inputMax, 0, "Input should have data");
       isnot(outputMax, 0, "Output should have data");
 
--- a/dom/network/UDPSocket.cpp
+++ b/dom/network/UDPSocket.cpp
@@ -15,16 +15,20 @@
 #include "nsComponentManagerUtils.h"
 #include "nsContentUtils.h"
 #include "nsINetAddr.h"
 #include "nsStringStream.h"
 
 namespace mozilla {
 namespace dom {
 
+NS_IMPL_ISUPPORTS(UDPSocket::ListenerProxy,
+                  nsIUDPSocketListener,
+                  nsIUDPSocketInternal)
+
 NS_IMPL_CYCLE_COLLECTION_CLASS(UDPSocket)
 
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(UDPSocket, DOMEventTargetHelper)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOpened)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mClosed)
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
 
 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(UDPSocket, DOMEventTargetHelper)
@@ -170,16 +174,21 @@ UDPSocket::CloseWithReason(nsresult aRea
       // reject openedPromise with AbortError if socket is closed without error
       nsresult openFailedReason = NS_FAILED(aReason) ? aReason : NS_ERROR_DOM_ABORT_ERR;
       mOpened->MaybeReject(openFailedReason);
     }
   }
 
   mReadyState = SocketReadyState::Closed;
 
+  if (mListenerProxy) {
+    mListenerProxy->Disconnect();
+    mListenerProxy = nullptr;
+  }
+
   if (mSocket) {
     mSocket->Close();
     mSocket = nullptr;
   }
 
   if (mSocketChild) {
     mSocketChild->Close();
     mSocketChild = nullptr;
@@ -425,17 +434,19 @@ UDPSocket::InitLocal(const nsAString& aL
 
   uint16_t localPort;
   rv = localAddr->GetPort(&localPort);
   if (NS_FAILED(rv)) {
     return rv;
   }
   mLocalPort.SetValue(localPort);
 
-  rv = mSocket->AsyncListen(this);
+  mListenerProxy = new ListenerProxy(this);
+
+  rv = mSocket->AsyncListen(mListenerProxy);
   if (NS_FAILED(rv)) {
     return rv;
   }
 
   mReadyState = SocketReadyState::Open;
   rv = DoPendingMcastCommand();
   if (NS_FAILED(rv)) {
     return rv;
@@ -453,17 +464,24 @@ UDPSocket::InitRemote(const nsAString& a
   nsresult rv;
 
   nsCOMPtr<nsIUDPSocketChild> sock =
     do_CreateInstance("@mozilla.org/udp-socket-child;1", &rv);
   if (NS_FAILED(rv)) {
     return rv;
   }
 
-  rv = sock->Bind(this, NS_ConvertUTF16toUTF8(aLocalAddress), aLocalPort, mAddressReuse, mLoopback);
+  mListenerProxy = new ListenerProxy(this);
+
+  rv = sock->Bind(mListenerProxy,
+                  NS_ConvertUTF16toUTF8(aLocalAddress),
+                  aLocalPort,
+                  mAddressReuse,
+                  mLoopback);
+
   if (NS_FAILED(rv)) {
     return rv;
   }
 
   mSocketChild = sock;
 
   return NS_OK;
 }
--- a/dom/network/UDPSocket.h
+++ b/dom/network/UDPSocket.h
@@ -123,16 +123,40 @@ public:
 
   bool
   Send(const StringOrBlobOrArrayBufferOrArrayBufferView& aData,
        const Optional<nsAString>& aRemoteAddress,
        const Optional<Nullable<uint16_t>>& aRemotePort,
        ErrorResult& aRv);
 
 private:
+  class ListenerProxy : public nsIUDPSocketListener
+                      , public nsIUDPSocketInternal
+  {
+  public:
+    NS_DECL_ISUPPORTS
+    NS_FORWARD_SAFE_NSIUDPSOCKETLISTENER(mSocket)
+    NS_FORWARD_SAFE_NSIUDPSOCKETINTERNAL(mSocket)
+
+    explicit ListenerProxy(UDPSocket* aSocket)
+      : mSocket(aSocket)
+    {
+    }
+
+    void Disconnect()
+    {
+      mSocket = nullptr;
+    }
+
+  private:
+    virtual ~ListenerProxy() {}
+
+    UDPSocket* mSocket;
+  };
+
   UDPSocket(nsPIDOMWindow* aOwner,
             const nsCString& aRemoteAddress,
             const Nullable<uint16_t>& aRemotePort);
 
   virtual ~UDPSocket();
 
   nsresult
   Init(const nsString& aLocalAddress,
@@ -171,16 +195,17 @@ private:
   bool mAddressReuse;
   bool mLoopback;
   SocketReadyState mReadyState;
   nsRefPtr<Promise> mOpened;
   nsRefPtr<Promise> mClosed;
 
   nsCOMPtr<nsIUDPSocket> mSocket;
   nsCOMPtr<nsIUDPSocketChild> mSocketChild;
+  nsRefPtr<ListenerProxy> mListenerProxy;
 
   struct MulticastCommand {
     enum CommandType { Join, Leave };
 
     MulticastCommand(CommandType aCommand, const nsAString& aAddress)
       : mCommand(aCommand), mAddress(aAddress)
     { }
 
--- a/dom/network/tests/mochitest.ini
+++ b/dom/network/tests/mochitest.ini
@@ -19,9 +19,8 @@ skip-if = true # Bug 958689
 skip-if = true # Bug 958689, bug 858005
 [test_networkstats_disabled.html]
 skip-if = toolkit != "gonk"
 [test_networkstats_enabled_no_perm.html]
 skip-if = true # Bug 958689
 [test_networkstats_enabled_perm.html]
 skip-if = toolkit != "gonk"
 [test_udpsocket.html]
-skip-if = toolkit != "gonk" || (toolkit == 'gonk' && debug) # Bug 1061174 for B2G debug
--- a/dom/network/tests/test_udpsocket.html
+++ b/dom/network/tests/test_udpsocket.html
@@ -141,25 +141,19 @@ function testSendBigArray(socket) {
     let byteReceived = 0;
     socket.addEventListener('message', function recv_callback(msg) {
       let byteBegin = byteReceived;
       byteReceived += msg.data.byteLength;
       is(msg.remotePort, socket.localPort, 'expected packet from ' + socket.localPort);
       ok(is_same_buffer(msg.data, BIG_TYPED_ARRAY.subarray(byteBegin, byteReceived)), 'expected same buffer data [' + byteBegin+ '-' + byteReceived + ']');
       if (byteReceived >= BIG_TYPED_ARRAY.length) {
         socket.removeEventListener('message', recv_callback);
-        clearTimeout(timeout);
         resolve(socket);
       }
     });
-
-    let timeout = setTimeout(function() {
-      ok(false, 'timeout for sending big array');
-      resolve(socket);
-    }, 5000);
   });
 }
 
 function testSendBigBlob(socket) {
   info('test for sending Big Blob');
 
   let blob = new Blob([BIG_TYPED_ARRAY]);
   socket.send(blob, '127.0.0.1', socket.localPort);
@@ -168,25 +162,19 @@ function testSendBigBlob(socket) {
     let byteReceived = 0;
     socket.addEventListener('message', function recv_callback(msg) {
       let byteBegin = byteReceived;
       byteReceived += msg.data.byteLength;
       is(msg.remotePort, socket.localPort, 'expected packet from ' + socket.localPort);
       ok(is_same_buffer(msg.data, BIG_TYPED_ARRAY.subarray(byteBegin, byteReceived)), 'expected same buffer data [' + byteBegin+ '-' + byteReceived + ']');
       if (byteReceived >= BIG_TYPED_ARRAY.length) {
         socket.removeEventListener('message', recv_callback);
-        clearTimeout(timeout);
         resolve(socket);
       }
     });
-
-    let timeout = setTimeout(function() {
-      ok(false, 'timeout for sending big blob');
-      resolve(socket);
-    }, 5000);
   });
 }
 
 function testUDPOptions(socket) {
   info('test for UDP init options');
 
   let remoteSocket = new UDPSocket({addressReuse: false,
                                     loopback: true,
--- a/dom/plugins/base/nsJSNPRuntime.cpp
+++ b/dom/plugins/base/nsJSNPRuntime.cpp
@@ -117,31 +117,36 @@ NPObjectIsOutOfProcessProxy(NPObject *ob
          obj->_class == PluginAsyncSurrogate::GetClass();
 }
 
 } // anonymous namespace
 
 // Helper class that reports any JS exceptions that were thrown while
 // the plugin executed JS.
 
-class AutoJSExceptionReporter
+class MOZ_STACK_CLASS AutoJSExceptionReporter
 {
 public:
-  explicit AutoJSExceptionReporter(JSContext* aCx)
-    : mCx(aCx)
+  AutoJSExceptionReporter(dom::AutoJSAPI& jsapi, nsJSObjWrapper* aWrapper)
+    : mJsapi(jsapi)
+    , mIsDestroyPending(aWrapper->mDestroyPending)
   {
+    jsapi.TakeOwnershipOfErrorReporting();
   }
 
   ~AutoJSExceptionReporter()
   {
-    JS_ReportPendingException(mCx);
+    if (mIsDestroyPending) {
+      mJsapi.ClearException();
+    }
   }
 
 protected:
-  JSContext *mCx;
+  dom::AutoJSAPI& mJsapi;
+  bool mIsDestroyPending;
 };
 
 
 NPClass nsJSObjWrapper::sJSObjWrapperNPClass =
   {
     NP_CLASS_STRUCT_VERSION,
     nsJSObjWrapper::NP_Allocate,
     nsJSObjWrapper::NP_Deallocate,
@@ -741,17 +746,17 @@ nsJSObjWrapper::NP_HasMethod(NPObject *n
 
     return false;
   }
 
   nsJSObjWrapper *npjsobj = (nsJSObjWrapper *)npobj;
 
   JSAutoCompartment ac(cx, npjsobj->mJSObj);
 
-  AutoJSExceptionReporter reporter(cx);
+  AutoJSExceptionReporter reporter(jsapi, npjsobj);
 
   JS::Rooted<JS::Value> v(cx);
   bool ok = GetProperty(cx, npjsobj->mJSObj, id, &v);
 
   return ok && !v.isPrimitive() &&
     ::JS_ObjectIsFunction(cx, v.toObjectOrNull());
 }
 
@@ -781,17 +786,17 @@ doInvoke(NPObject *npobj, NPIdentifier m
   VOID_TO_NPVARIANT(*result);
 
   nsJSObjWrapper *npjsobj = (nsJSObjWrapper *)npobj;
 
   JS::Rooted<JSObject*> jsobj(cx, npjsobj->mJSObj);
   JSAutoCompartment ac(cx, jsobj);
   JS::Rooted<JS::Value> fv(cx);
 
-  AutoJSExceptionReporter reporter(cx);
+  AutoJSExceptionReporter reporter(aes, npjsobj);
 
   if (method != NPIdentifier_VOID) {
     if (!GetProperty(cx, jsobj, method, &fv) ||
         ::JS_TypeOfValue(cx, fv) != JSTYPE_FUNCTION) {
       return false;
     }
   } else {
     fv.setObject(*jsobj);
@@ -866,17 +871,17 @@ nsJSObjWrapper::NP_HasProperty(NPObject 
                      "Null npobj in nsJSObjWrapper::NP_HasProperty!");
 
     return false;
   }
 
   nsJSObjWrapper *npjsobj = (nsJSObjWrapper *)npobj;
   bool found, ok = false;
 
-  AutoJSExceptionReporter reporter(cx);
+  AutoJSExceptionReporter reporter(jsapi, npjsobj);
   JS::Rooted<JSObject*> jsobj(cx, npjsobj->mJSObj);
   JSAutoCompartment ac(cx, jsobj);
 
   NS_ASSERTION(NPIdentifierIsInt(npid) || NPIdentifierIsString(npid),
                "id must be either string or int!\n");
   JS::Rooted<jsid> id(cx, NPIdentifierToJSId(npid));
   ok = ::JS_HasPropertyById(cx, jsobj, id, &found);
   return ok && found;
@@ -903,17 +908,17 @@ nsJSObjWrapper::NP_GetProperty(NPObject 
     ThrowJSException(cx,
                      "Null npobj in nsJSObjWrapper::NP_GetProperty!");
 
     return false;
   }
 
   nsJSObjWrapper *npjsobj = (nsJSObjWrapper *)npobj;
 
-  AutoJSExceptionReporter reporter(cx);
+  AutoJSExceptionReporter reporter(aes, npjsobj);
   JSAutoCompartment ac(cx, npjsobj->mJSObj);
 
   JS::Rooted<JS::Value> v(cx);
   return (GetProperty(cx, npjsobj->mJSObj, id, &v) &&
           JSValToNPVariant(npp, cx, v, result));
 }
 
 // static
@@ -938,17 +943,17 @@ nsJSObjWrapper::NP_SetProperty(NPObject 
                      "Null npobj in nsJSObjWrapper::NP_SetProperty!");
 
     return false;
   }
 
   nsJSObjWrapper *npjsobj = (nsJSObjWrapper *)npobj;
   bool ok = false;
 
-  AutoJSExceptionReporter reporter(cx);
+  AutoJSExceptionReporter reporter(aes, npjsobj);
   JS::Rooted<JSObject*> jsObj(cx, npjsobj->mJSObj);
   JSAutoCompartment ac(cx, jsObj);
 
   JS::Rooted<JS::Value> v(cx, NPVariantToJSVal(npp, cx, value));
 
   NS_ASSERTION(NPIdentifierIsInt(npid) || NPIdentifierIsString(npid),
                "id must be either string or int!\n");
   JS::Rooted<jsid> id(cx, NPIdentifierToJSId(npid));
@@ -972,17 +977,17 @@ nsJSObjWrapper::NP_RemoveProperty(NPObje
     ThrowJSException(cx,
                      "Null npobj in nsJSObjWrapper::NP_RemoveProperty!");
 
     return false;
   }
 
   nsJSObjWrapper *npjsobj = (nsJSObjWrapper *)npobj;
 
-  AutoJSExceptionReporter reporter(cx);
+  AutoJSExceptionReporter reporter(jsapi, npjsobj);
   JS::ObjectOpResult result;
   JS::Rooted<JSObject*> obj(cx, npjsobj->mJSObj);
   JSAutoCompartment ac(cx, obj);
 
   NS_ASSERTION(NPIdentifierIsInt(npid) || NPIdentifierIsString(npid),
                "id must be either string or int!\n");
   JS::Rooted<jsid> id(cx, NPIdentifierToJSId(npid));
   if (!::JS_DeletePropertyById(cx, obj, id, result))
@@ -1024,17 +1029,17 @@ nsJSObjWrapper::NP_Enumerate(NPObject *n
     ThrowJSException(cx,
                      "Null npobj in nsJSObjWrapper::NP_Enumerate!");
 
     return false;
   }
 
   nsJSObjWrapper *npjsobj = (nsJSObjWrapper *)npobj;
 
-  AutoJSExceptionReporter reporter(cx);
+  AutoJSExceptionReporter reporter(jsapi, npjsobj);
   JS::Rooted<JSObject*> jsobj(cx, npjsobj->mJSObj);
   JSAutoCompartment ac(cx, jsobj);
 
   JS::AutoIdArray ida(cx, JS_Enumerate(cx, jsobj));
   if (!ida) {
     return false;
   }
 
@@ -1115,16 +1120,27 @@ NPObject *
 nsJSObjWrapper::GetNewOrUsed(NPP npp, JSContext *cx, JS::Handle<JSObject*> obj)
 {
   if (!npp) {
     NS_ERROR("Null NPP passed to nsJSObjWrapper::GetNewOrUsed()!");
 
     return nullptr;
   }
 
+  // If we're running out-of-process and initializing asynchronously, and if
+  // the plugin has been asked to destroy itself during initialization,
+  // don't return any new NPObjects.
+  nsNPAPIPluginInstance* inst = static_cast<nsNPAPIPluginInstance*>(npp->ndata);
+  if (inst->GetPlugin()->GetLibrary()->IsOOP()) {
+    PluginAsyncSurrogate* surrogate = PluginAsyncSurrogate::Cast(npp);
+    if (surrogate && surrogate->IsDestroyPending()) {
+      return nullptr;
+    }
+  }
+
   if (!cx) {
     cx = GetJSContext(npp);
 
     if (!cx) {
       NS_ERROR("Unable to find a JSContext in nsJSObjWrapper::GetNewOrUsed()!");
 
       return nullptr;
     }
@@ -2024,16 +2040,33 @@ nsJSNPRuntime::OnPluginDestroy(NPP npp)
   AutoSafeJSContext cx;
   if (sNPObjWrappers.IsInitialized()) {
     NppAndCx nppcx = { npp, cx };
     PL_DHashTableEnumerate(&sNPObjWrappers,
                            NPObjWrapperPluginDestroyedCallback, &nppcx);
   }
 }
 
+// static
+void
+nsJSNPRuntime::OnPluginDestroyPending(NPP npp)
+{
+  if (sJSObjWrappersAccessible) {
+    // Prevent modification of sJSObjWrappers table if we go reentrant.
+    sJSObjWrappersAccessible = false;
+    for (JSObjWrapperTable::Enum e(sJSObjWrappers); !e.empty(); e.popFront()) {
+      nsJSObjWrapper *npobj = e.front().value();
+      MOZ_ASSERT(npobj->_class == &nsJSObjWrapper::sJSObjWrapperNPClass);
+      if (npobj->mNpp == npp) {
+        npobj->mDestroyPending = true;
+      }
+    }
+    sJSObjWrappersAccessible = true;
+  }
+}
 
 // Find the NPP for a NPObject.
 static NPP
 LookupNPP(NPObject *npobj)
 {
   if (npobj->_class == &nsJSObjWrapper::sJSObjWrapperNPClass) {
     nsJSObjWrapper* o = static_cast<nsJSObjWrapper*>(npobj);
     return o->mNpp;
@@ -2286,17 +2319,17 @@ nsJSObjWrapper::HasOwnProperty(NPObject 
                      "Null npobj in nsJSObjWrapper::NP_HasOwnProperty!");
 
     return false;
   }
 
   nsJSObjWrapper *npjsobj = (nsJSObjWrapper *)npobj;
   bool found, ok = false;
 
-  AutoJSExceptionReporter reporter(cx);
+  AutoJSExceptionReporter reporter(jsapi, npjsobj);
   JS::Rooted<JSObject*> jsobj(cx, npjsobj->mJSObj);
   JSAutoCompartment ac(cx, jsobj);
 
   NS_ASSERTION(NPIdentifierIsInt(npid) || NPIdentifierIsString(npid),
                "id must be either string or int!\n");
   JS::Rooted<jsid> id(cx, NPIdentifierToJSId(npid));
   ok = ::JS_AlreadyHasOwnPropertyById(cx, jsobj, id, &found);
   return ok && found;
--- a/dom/plugins/base/nsJSNPRuntime.h
+++ b/dom/plugins/base/nsJSNPRuntime.h
@@ -10,16 +10,17 @@
 #include "npapi.h"
 #include "npruntime.h"
 #include "pldhash.h"
 
 class nsJSNPRuntime
 {
 public:
   static void OnPluginDestroy(NPP npp);
+  static void OnPluginDestroyPending(NPP npp);
 };
 
 class nsJSObjWrapperKey
 {
 public:
   nsJSObjWrapperKey(JSObject *obj, NPP npp)
     : mJSObj(obj), mNpp(npp)
   {
@@ -36,16 +37,17 @@ public:
   const NPP mNpp;
 };
 
 class nsJSObjWrapper : public NPObject
 {
 public:
   JS::Heap<JSObject *> mJSObj;
   const NPP mNpp;
+  bool mDestroyPending;
 
   static NPObject *GetNewOrUsed(NPP npp, JSContext *cx,
                                 JS::Handle<JSObject*> obj);
   static bool HasOwnProperty(NPObject* npobj, NPIdentifier npid);
 
 protected:
   explicit nsJSObjWrapper(NPP npp);
   ~nsJSObjWrapper();
--- a/dom/plugins/base/nsPluginInstanceOwner.cpp
+++ b/dom/plugins/base/nsPluginInstanceOwner.cpp
@@ -225,18 +225,18 @@ nsPluginInstanceOwner::GetImageContainer
   // for what we do on other versions.
   if (AndroidBridge::Bridge()->GetAPIVersion() < 11)
     return nullptr;
 
   LayoutDeviceRect r = GetPluginRect();
 
   // NotifySize() causes Flash to do a bunch of stuff like ask for surfaces to render
   // into, set y-flip flags, etc, so we do this at the beginning.
-  gfxSize resolution = mPluginFrame->PresContext()->PresShell()->GetCumulativeResolution();
-  ScreenSize screenSize = (r * LayoutDeviceToScreenScale2D(resolution.width, resolution.height)).Size();
+  float resolution = mPluginFrame->PresContext()->PresShell()->GetCumulativeResolution();
+  ScreenSize screenSize = (r * LayoutDeviceToScreenScale(resolution)).Size();
   mInstance->NotifySize(nsIntSize(screenSize.width, screenSize.height));
 
   container = LayerManager::CreateImageContainer();
 
   // Try to get it as an EGLImage first.
   nsRefPtr<Image> img;
   AttachToContainerAsEGLImage(container, mInstance, r, &img);
   if (!img) {
@@ -1463,16 +1463,33 @@ nsPluginInstanceOwner::NotifyHostCreateW
   if (mPluginFrame) {
     mPluginFrame->InvalidateFrame();
   } else {
     CallSetWindow();
   }
 #endif
 }
 
+void
+nsPluginInstanceOwner::NotifyDestroyPending()
+{
+  if (!mInstance) {
+    return;
+  }
+  bool isOOP = false;
+  if (NS_FAILED(mInstance->GetIsOOP(&isOOP)) || !isOOP) {
+    return;
+  }
+  NPP npp = nullptr;
+  if (NS_FAILED(mInstance->GetNPP(&npp)) || !npp) {
+    return;
+  }
+  PluginAsyncSurrogate::NotifyDestroyPending(npp);
+}
+
 nsresult nsPluginInstanceOwner::DispatchFocusToPlugin(nsIDOMEvent* aFocusEvent)
 {
 #ifdef MOZ_WIDGET_ANDROID
   if (mInstance) {
     ANPEvent event;
     event.inSize = sizeof(ANPEvent);
     event.eventType = kLifecycle_ANPEventType;
 
--- a/dom/plugins/base/nsPluginInstanceOwner.h
+++ b/dom/plugins/base/nsPluginInstanceOwner.h
@@ -253,16 +253,17 @@ public:
   void ExitFullScreen();
 
   // Called from AndroidJNI when we removed the fullscreen view.
   static void ExitFullScreen(jobject view);
 #endif
 
   void NotifyHostAsyncInitFailed();
   void NotifyHostCreateWidget();
+  void NotifyDestroyPending();
 
 private:
   virtual ~nsPluginInstanceOwner();
 
   // return FALSE if LayerSurface dirty (newly created and don't have valid plugin content yet)
   bool IsUpToDate()
   {
     nsIntSize size;
--- a/dom/plugins/ipc/PluginAsyncSurrogate.cpp
+++ b/dom/plugins/ipc/PluginAsyncSurrogate.cpp
@@ -94,16 +94,17 @@ PluginAsyncSurrogate::PluginAsyncSurroga
   : mParent(aParent)
   , mInstance(nullptr)
   , mMode(0)
   , mWindow(nullptr)
   , mAcceptCalls(false)
   , mInstantiated(false)
   , mAsyncSetWindow(false)
   , mInitCancelled(false)
+  , mDestroyPending(false)
   , mAsyncCallsInFlight(0)
 {
   MOZ_ASSERT(aParent);
 }
 
 PluginAsyncSurrogate::~PluginAsyncSurrogate()
 {
 }
@@ -174,19 +175,37 @@ PluginAsyncSurrogate::NP_GetEntryPoints(
   aFuncs->event = &NPP_HandleEvent;
   aFuncs->destroystream = &NPP_DestroyStream;
   // We need to set these so that content code doesn't make assumptions
   // about these operations not being supported
   aFuncs->write = &PluginModuleParent::NPP_Write;
   aFuncs->asfile = &PluginModuleParent::NPP_StreamAsFile;
 }
 
+/* static */ void
+PluginAsyncSurrogate::NotifyDestroyPending(NPP aInstance)
+{
+  PluginAsyncSurrogate* surrogate = Cast(aInstance);
+  if (!surrogate) {
+    return;
+  }
+  surrogate->NotifyDestroyPending();
+}
+
+void
+PluginAsyncSurrogate::NotifyDestroyPending()
+{
+  mDestroyPending = true;
+  nsJSNPRuntime::OnPluginDestroyPending(mInstance);
+}
+
 NPError
 PluginAsyncSurrogate::NPP_Destroy(NPSavedData** aSave)
 {
+  NotifyDestroyPending();
   if (!WaitForInit()) {
     return NPERR_GENERIC_ERROR;
   }
   return PluginModuleParent::NPP_Destroy(mInstance, aSave);
 }
 
 NPError
 PluginAsyncSurrogate::NPP_GetValue(NPPVariable aVariable, void* aRetval)
@@ -412,17 +431,17 @@ PluginAsyncSurrogate::SetStreamType(NPSt
     return false;
   }
   return streamListener->SetStreamType(aStreamType);
 }
 
 void
 PluginAsyncSurrogate::OnInstanceCreated(PluginInstanceParent* aInstance)
 {
-  for (PRUint32 i = 0, len = mPendingNewStreamCalls.Length(); i < len; ++i) {
+  for (uint32_t i = 0, len = mPendingNewStreamCalls.Length(); i < len; ++i) {
     PendingNewStreamCall& curPendingCall = mPendingNewStreamCalls[i];
     uint16_t streamType = NP_NORMAL;
     NPError curError = aInstance->NPP_NewStream(
                     const_cast<char*>(NullableStringGet(curPendingCall.mType)),
                     curPendingCall.mStream, curPendingCall.mSeekable,
                     &streamType);
     if (curError != NPERR_NO_ERROR) {
       // If we failed here then the send failed and we need to clean up
--- a/dom/plugins/ipc/PluginAsyncSurrogate.h
+++ b/dom/plugins/ipc/PluginAsyncSurrogate.h
@@ -45,31 +45,34 @@ public:
   NPError NPP_DestroyStream(NPStream* aStream, NPReason aReason);
   void OnInstanceCreated(PluginInstanceParent* aInstance);
   static bool Create(PluginModuleParent* aParent, NPMIMEType aPluginType,
                      NPP aInstance, uint16_t aMode, int16_t aArgc,
                      char* aArgn[], char* aArgv[]);
   static const NPClass* GetClass() { return &sNPClass; }
   static void NP_GetEntryPoints(NPPluginFuncs* aFuncs);
   static PluginAsyncSurrogate* Cast(NPP aInstance);
+  static void NotifyDestroyPending(NPP aInstance);
+  void NotifyDestroyPending();
 
   virtual PluginAsyncSurrogate*
   GetAsyncSurrogate() { return this; }
 
   virtual PluginInstanceParent*
   GetInstance() { return nullptr; }
 
   NPP GetNPP() { return mInstance; }
 
   bool GetPropertyHelper(NPObject* aObject, NPIdentifier aName,
                          bool* aHasProperty, bool* aHasMethod,
                          NPVariant* aResult);
 
-  PluginModuleParent*
-  GetParent() { return mParent; }
+  PluginModuleParent* GetParent() { return mParent; }
+
+  bool IsDestroyPending() const { return mDestroyPending; }
 
   bool SetAcceptingCalls(bool aAccept)
   {
     bool prevState = mAcceptCalls;
     if (mInstantiated) {
       aAccept = true;
     }
     mAcceptCalls = aAccept;
@@ -146,16 +149,17 @@ private:
   NPWindow*                       mWindow;
   nsTArray<PendingNewStreamCall>  mPendingNewStreamCalls;
   UniquePtr<PluginDestructionGuard> mPluginDestructionGuard;
 
   bool      mAcceptCalls;
   bool      mInstantiated;
   bool      mAsyncSetWindow;
   bool      mInitCancelled;
+  bool      mDestroyPending;
   int32_t   mAsyncCallsInFlight;
 
   static const NPClass sNPClass;
 };
 
 struct AsyncNPObject : NPObject
 {
   explicit AsyncNPObject(PluginAsyncSurrogate* aSurrogate);
--- a/dom/promise/PromiseNativeHandler.h
+++ b/dom/promise/PromiseNativeHandler.h
@@ -3,16 +3,17 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef mozilla_dom_PromiseNativeHandler_h
 #define mozilla_dom_PromiseNativeHandler_h
 
 #include "nsISupports.h"
+#include "js/TypeDecls.h"
 
 namespace mozilla {
 namespace dom {
 
 /*
  * PromiseNativeHandler allows C++ to react to a Promise being rejected/resolved.
  * A PromiseNativeHandler can be appended to a Promise using
  * Promise::AppendNativeHandler().
--- a/dom/tests/mochitest/bugs/mochitest.ini
+++ b/dom/tests/mochitest/bugs/mochitest.ini
@@ -161,8 +161,9 @@ skip-if = toolkit == 'android' || e10s #
 [test_resize_move_windows.html]
 skip-if = buildapp == 'mulet' || buildapp == 'b2g' || toolkit == 'android' || e10s #Windows can't change size and position on Android # b2g(Windows can't change size and position on B2G) b2g-debug(Windows can't change size and position on B2G) b2g-desktop(Windows can't change size and position on B2G)
 [test_sizetocontent_clamp.html]
 skip-if = buildapp == 'mulet' || buildapp == 'b2g' || toolkit == 'android' || e10s #Windows can't change size on Android # b2g(Windows can't change size on B2G) b2g-debug(Windows can't change size on B2G) b2g-desktop(Windows can't change size on B2G)
 [test_toJSON.html]
 [test_window_bar.html]
 skip-if = buildapp == 'mulet' || buildapp == 'b2g' || toolkit == 'android' || e10s
 [test_bug1022869.html]
+[test_bug1112040.html]
new file mode 100644
--- /dev/null
+++ b/dom/tests/mochitest/bugs/test_bug1112040.html
@@ -0,0 +1,32 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1112040
+-->
+<head>
+  <title>Test for Bug 1112040</title>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+  <base href="http://mochi.test:8888/tests/dom/tests/mochitest/">
+  <meta charset="UTF-8">
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1112040">Mozilla Bug 1112040</a>
+<p id="display">
+</p>
+<div id="content">
+    <input id="i" type="text" pattern="^.**$">
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 1112040 **/
+SimpleTest.runTestExpectingConsoleMessages(
+    function() { $('i').value = "42"; },
+    [{ errorMessage: "SyntaxError: nothing to repeat" }]
+);
+
+</script>
+</pre>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/tests/mochitest/fetch/fetch_test_framework.js
@@ -0,0 +1,86 @@
+function testScript(script) {
+  function workerTest() {
+    return new Promise(function(resolve, reject) {
+      var worker = new Worker("worker_wrapper.js");
+      worker.onmessage = function(event) {
+        if (event.data.type == 'finish') {
+          resolve();
+        } else if (event.data.type == 'status') {
+          ok(event.data.status, "Worker fetch test: " + event.data.msg);
+        }
+      }
+      worker.onerror = function(event) {
+        reject("Worker error: " + event.message);
+      };
+
+      worker.postMessage({ "script": script });
+    });
+  }
+
+  function windowTest() {
+    return new Promise(function(resolve, reject) {
+      var scriptEl = document.createElement("script");
+      scriptEl.setAttribute("src", script);
+      scriptEl.onload = function() {
+        runTest().then(resolve, reject);
+      };
+      document.body.appendChild(scriptEl);
+    });
+  }
+
+  SimpleTest.waitForExplicitFinish();
+  // We have to run the window and worker tests sequentially since some tests
+  // set and compare cookies and running in parallel can lead to conflicting
+  // values.
+  windowTest()
+    .then(function() {
+      return workerTest();
+    })
+    .catch(function(e) {
+      ok(false, "Some test failed in " + script);
+      info(e);
+      info(e.message);
+      return Promise.resolve();
+    })
+    .then(function() {
+      SimpleTest.finish();
+    });
+}
+
+// Utilities
+// =========
+
+// Helper that uses FileReader or FileReaderSync based on context and returns
+// a Promise that resolves with the text or rejects with error.
+function readAsText(blob) {
+  if (typeof FileReader !== "undefined") {
+    return new Promise(function(resolve, reject) {
+      var fs = new FileReader();
+      fs.onload = function() {
+        resolve(fs.result);
+      }
+      fs.onerror = reject;
+      fs.readAsText(blob);
+    });
+  } else {
+    var fs = new FileReaderSync();
+    return Promise.resolve(fs.readAsText(blob));
+  }
+}
+
+function readAsArrayBuffer(blob) {
+  if (typeof FileReader !== "undefined") {
+    return new Promise(function(resolve, reject) {
+      var fs = new FileReader();
+      fs.onload = function() {
+        resolve(fs.result);
+      }
+      fs.onerror = reject;
+      fs.readAsArrayBuffer(blob);
+    });
+  } else {
+    var fs = new FileReaderSync();
+    return Promise.resolve(fs.readAsArrayBuffer(blob));
+  }
+}
+
--- a/dom/tests/mochitest/fetch/mochitest.ini
+++ b/dom/tests/mochitest/fetch/mochitest.ini
@@ -1,13 +1,18 @@
 [DEFAULT]
 support-files =
+  fetch_test_framework.js
+  test_fetch_basic.js
+  test_fetch_basic_http.js
+  test_fetch_cors.js
   test_headers_common.js
-  test_headers_mainthread.js
-  worker_test_fetch_basic.js
-  worker_test_fetch_basic_http.js
-  worker_test_fetch_cors.js
+  test_request.js
+  test_response.js
   worker_wrapper.js
 
 [test_headers.html]
+[test_headers_mainthread.html]
 [test_fetch_basic.html]
 [test_fetch_basic_http.html]
 [test_fetch_cors.html]
+[test_request.html]
+[test_response.html]
--- a/dom/tests/mochitest/fetch/test_fetch_basic.html
+++ b/dom/tests/mochitest/fetch/test_fetch_basic.html
@@ -8,46 +8,15 @@
   <title>Bug 1039846 - Test fetch() function in worker</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" style="display: none"></div>
 <pre id="test"></pre>
-<script type="text/javascript" src="worker_test_fetch_basic.js"> </script>
+<script type="text/javascript" src="fetch_test_framework.js"> </script>
 <script class="testbody" type="text/javascript">
-SimpleTest.waitForExplicitFinish();
-
-function testOnWorker(done) {
-  ok(true, "=== Start Worker Tests ===");
-  var worker = new Worker("worker_test_fetch_basic.js");
-  worker.onmessage = function(event) {
-    if (event.data.type == "finish") {
-      ok(true, "=== Finish Worker Tests ===");
-      done();
-    } else if (event.data.type == "status") {
-      ok(event.data.status, event.data.msg);
-    }
-  }
-
-  worker.onerror = function(event) {
-    ok(false, "Worker had an error: " + event.data);
-    ok(true, "=== Finish Worker Tests ===");
-    done();
-  };
-
-  worker.postMessage("start");
-}
-
-//
-// Driver
-//
-
-testOnWorker(function() {
-  SimpleTest.finish();
-});
+testScript("test_fetch_basic.js");
 </script>
-</script>
-</pre>
 </body>
 </html>
 
rename from dom/tests/mochitest/fetch/worker_test_fetch_basic.js
rename to dom/tests/mochitest/fetch/test_fetch_basic.js
--- a/dom/tests/mochitest/fetch/worker_test_fetch_basic.js
+++ b/dom/tests/mochitest/fetch/test_fetch_basic.js
@@ -1,22 +1,8 @@
-if (typeof ok !== "function") {
-  function ok(a, msg) {
-    dump("OK: " + !!a + "  =>  " + a + " " + msg + "\n");
-    postMessage({type: 'status', status: !!a, msg: a + ": " + msg });
-  }
-}
-
-if (typeof is !== "function") {
-  function is(a, b, msg) {
-    dump("IS: " + (a===b) + "  =>  " + a + " | " + b + " " + msg + "\n");
-    postMessage({type: 'status', status: a === b, msg: a + " === " + b + ": " + msg });
-  }
-}
-
 function testAboutURL() {
   var p1 = fetch('about:blank').then(function(res) {
     is(res.status, 200, "about:blank should load a valid Response");
     is(res.headers.get('content-type'), 'text/html;charset=utf-8',
        "about:blank content-type should be text/html;charset=utf-8");
     return res.text().then(function(v) {
       is(v, "", "about:blank body should be empty");
     });
@@ -67,29 +53,14 @@ function testSameOriginBlobURL() {
     is(parseInt(res.headers.get("content-length")), 16, "Content-Length should match Blob's size");
     return res.text().then(function(body) {
       is(body, "english sentence", "Blob fetch body should match");
     });
   });
 }
 
 function runTest() {
-  var done = function() {
-    if (typeof SimpleTest === "object") {
-      SimpleTest.finish();
-    } else {
-      postMessage({ type: 'finish' });
-    }
-  }
-
-  Promise.resolve()
+  return Promise.resolve()
     .then(testAboutURL)
     .then(testDataURL)
     .then(testSameOriginBlobURL)
     // Put more promise based tests here.
-    .then(done)
-    .catch(function(e) {
-      ok(false, "Some Response tests failed " + e);
-      done();
-    })
 }
-
-onmessage = runTest;
--- a/dom/tests/mochitest/fetch/test_fetch_basic_http.html
+++ b/dom/tests/mochitest/fetch/test_fetch_basic_http.html
@@ -8,46 +8,15 @@
   <title>Bug 1039846 - Test fetch() http fetching in worker</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" style="display: none"></div>
 <pre id="test"></pre>
-<script type="text/javascript" src="worker_test_fetch_basic.js"> </script>
+<script type="text/javascript" src="fetch_test_framework.js"> </script>
 <script class="testbody" type="text/javascript">
-SimpleTest.waitForExplicitFinish();
-
-function testOnWorker(done) {
-  ok(true, "=== Start Worker Tests ===");
-  var worker = new Worker("worker_test_fetch_basic_http.js");
-  worker.onmessage = function(event) {
-    if (event.data.type == "finish") {
-      ok(true, "=== Finish Worker Tests ===");
-      done();
-    } else if (event.data.type == "status") {
-      ok(event.data.status, event.data.msg);
-    }
-  }
-
-  worker.onerror = function(event) {
-    ok(false, "Worker had an error: " + event.message);
-    ok(true, "=== Finish Worker Tests ===");
-    done();
-  };
-
-  worker.postMessage("start");
-}
-
-//
-// Driver
-//
-
-testOnWorker(function() {
-  SimpleTest.finish();
-});
+testScript("test_fetch_basic_http.js");
 </script>
-</script>
-</pre>
 </body>
 </html>
 
rename from dom/tests/mochitest/fetch/worker_test_fetch_basic_http.js
rename to dom/tests/mochitest/fetch/test_fetch_basic_http.js
--- a/dom/tests/mochitest/fetch/worker_test_fetch_basic_http.js
+++ b/dom/tests/mochitest/fetch/test_fetch_basic_http.js
@@ -1,20 +1,8 @@
-if (typeof ok !== "function") {
-  function ok(a, msg) {
-    postMessage({type: 'status', status: !!a, msg: a + ": " + msg });
-  }
-}
-
-if (typeof is !== "function") {
-  function is(a, b, msg) {
-    postMessage({type: 'status', status: a === b, msg: a + " === " + b + ": " + msg });
-  }
-}
-
 var path = "/tests/dom/base/test/";
 
 var passFiles = [['file_XHR_pass1.xml', 'GET', 200, 'OK', 'text/xml'],
                  ['file_XHR_pass2.txt', 'GET', 200, 'OK', 'text/plain'],
                  ['file_XHR_pass3.txt', 'GET', 200, 'OK', 'text/plain'],
                  ];
 
 function testURL() {
@@ -128,45 +116,30 @@ function testResponses() {
   return Promise.all(fetches);
 }
 
 function testBlob() {
   return fetch(path + '/file_XHR_binary2.bin').then((r) => {
     ok(r.status, 200, "status should match");
     return r.blob().then((b) => {
       ok(b.size, 65536, "blob should have size 65536");
-      var frs = new FileReaderSync();
-      var buf = frs.readAsArrayBuffer(b);
-      var u8 = new Uint8Array(buf);
-      for (var i = 0; i < 65536; i++) {
-        if (u8[i] !== (i & 255)) {
-          break;
+      return readAsArrayBuffer(b).then(function(ab) {
+        var u8 = new Uint8Array(ab);
+        for (var i = 0; i < 65536; i++) {
+          if (u8[i] !== (i & 255)) {
+            break;
+          }
         }
-      }
-      is(i, 65536, "wrong value at offset " + i);
+        is(i, 65536, "wrong value at offset " + i);
+      });
     });
   });
 }
 
 function runTest() {
-  var done = function() {
-    if (typeof SimpleTest === "object") {
-      SimpleTest.finish();
-    } else {
-      postMessage({ type: 'finish' });
-    }
-  }
-
-  Promise.resolve()
+  return Promise.resolve()
     .then(testURL)
     .then(testURLFail)
     .then(testRequestGET)
     .then(testResponses)
     .then(testBlob)
     // Put more promise based tests here.
-    .then(done)
-    .catch(function(e) {
-      ok(false, "Some test failed " + e);
-      done();
-    });
 }
-
-onmessage = runTest;
deleted file mode 100644
--- a/dom/tests/mochitest/fetch/test_fetch_basic_worker.html
+++ /dev/null
@@ -1,44 +0,0 @@
-<!--
-  Any copyright is dedicated to the Public Domain.
-  http://creativecommons.org/publicdomain/zero/1.0/
--->
-<!DOCTYPE HTML>
-<html>
-<head>
-  <title>Bug 1039846 - Test fetch() function in worker</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" style="display: none"></div>
-<pre id="test"></pre>
-<script class="testbody" type="text/javascript">
-
-  function runTest() {
-    var worker = new Worker("worker_test_fetch_basic.js");
-    worker.onmessage = function(event) {
-
-      if (event.data.type == 'finish') {
-        SimpleTest.finish();
-      } else if (event.data.type == 'status') {
-        ok(event.data.status, event.data.msg);
-      }
-    }
-
-    worker.onerror = function(event) {
-      ok(false, "Worker had an error: " + event.message + " at " + event.lineno);
-      SimpleTest.finish();
-    };
-
-    worker.postMessage(true);
-  }
-
-  SimpleTest.waitForExplicitFinish();
-
-  runTest();
-</script>
-</pre>
-</body>
-</html>
-
--- a/dom/tests/mochitest/fetch/test_fetch_cors.html
+++ b/dom/tests/mochitest/fetch/test_fetch_cors.html
@@ -8,46 +8,15 @@
   <title>Bug 1039846 - Test fetch() CORS mode</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" style="display: none"></div>
 <pre id="test"></pre>
+<script type="text/javascript" src="fetch_test_framework.js"> </script>
 <script class="testbody" type="text/javascript">
-SimpleTest.waitForExplicitFinish();
-
-var worker;
-function testOnWorker(done) {
-  ok(true, "=== Start Worker Tests ===");
-  worker = new Worker("worker_test_fetch_cors.js");
-  worker.onmessage = function(event) {
-    if (event.data.type == "finish") {
-      ok(true, "=== Finish Worker Tests ===");
-      done();
-    } else if (event.data.type == "status") {
-      ok(event.data.status, event.data.msg);
-    }
-  }
-
-  worker.onerror = function(event) {
-    ok(false, "Worker had an error: " + event.message);
-    ok(true, "=== Finish Worker Tests ===");
-    done();
-  };
-
-  worker.postMessage("start");
-}
-
-//
-// Driver
-//
-
-testOnWorker(function() {
-  SimpleTest.finish();
-});
+testScript("test_fetch_cors.js");
 </script>
-</script>
-</pre>
 </body>
 </html>
 
rename from dom/tests/mochitest/fetch/worker_test_fetch_cors.js
rename to dom/tests/mochitest/fetch/test_fetch_cors.js
--- a/dom/tests/mochitest/fetch/worker_test_fetch_cors.js
+++ b/dom/tests/mochitest/fetch/test_fetch_cors.js
@@ -1,20 +1,8 @@
-if (typeof ok !== "function") {
-  function ok(a, msg) {
-    postMessage({type: 'status', status: !!a, msg: a + ": " + msg });
-  }
-}
-
-if (typeof is !== "function") {
-  function is(a, b, msg) {
-    postMessage({type: 'status', status: a === b, msg: a + " === " + b + ": " + msg });
-  }
-}
-
 var path = "/tests/dom/base/test/";
 
 function isOpaqueResponse(response) {
   return response.type == "opaque" && response.status === 0 && response.statusText === "";
 }
 
 function testModeSameOrigin() {
   // Fetch spec Section 4, step 4, "request's mode is same-origin".
@@ -1256,34 +1244,19 @@ function testRedirects() {
       });
     })(request, test));
   }
 
   return Promise.all(fetches);
 }
 
 function runTest() {
-  var done = function() {
-    if (typeof SimpleTest === "object") {
-      SimpleTest.finish();
-    } else {
-      postMessage({ type: 'finish' });
-    }
-  }
-
   testNoCorsCtor();
 
-  Promise.resolve()
+  return Promise.resolve()
     .then(testModeSameOrigin)
     .then(testModeNoCors)
     .then(testModeCors)
     .then(testSameOriginCredentials)
     .then(testCrossOriginCredentials)
     .then(testRedirects)
     // Put more promise based tests here.
-    .then(done)
-    .catch(function(e) {
-      ok(false, "Some test failed " + e);
-      done();
-    });
 }
-
-onmessage = runTest;
--- a/dom/tests/mochitest/fetch/test_headers.html
+++ b/dom/tests/mochitest/fetch/test_headers.html
@@ -3,61 +3,14 @@
 <!DOCTYPE HTML>
 <html>
 <head>
   <title>Test Fetch Headers - Basic</title>
   <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
 </head>
 <body>
+<script type="text/javascript" src="fetch_test_framework.js"> </script>
 <script class="testbody" type="text/javascript">
-SimpleTest.waitForExplicitFinish();
-
-function testOnWorker(done) {
-  ok(true, "=== Start Worker Headers Tests ===");
-  var worker = new Worker("worker_wrapper.js");
-  worker.onmessage = function(event) {
-    if (event.data.type == "finish") {
-      ok(true, "=== Finish Worker Headers Tests ===");
-      done();
-    } else if (event.data.type == "status") {
-      ok(event.data.status, event.data.msg);
-    }
-  }
-
-  worker.onerror = function(event) {
-    ok(false, "Worker had an error: " + event.data);
-    ok(true, "=== Finish Worker Headers Tests ===");
-    done();
-  };
-
-  worker.postMessage({ script: "test_headers_common.js" });
-}
-
-function testOnMainThread(done) {
-  ok(true, "=== Start Main Thread Headers Tests ===");
-  var commonScript = document.createElement("script");
-  commonScript.setAttribute("src", "test_headers_common.js");
-  commonScript.onload = function() {
-    var mainThreadScript = document.createElement("script");
-    mainThreadScript.setAttribute("src", "test_headers_mainthread.js");
-    mainThreadScript.onload = function() {
-      ok(true, "=== Finish Main Thread Headers Tests ===");
-      done();
-    }
-    document.head.appendChild(mainThreadScript);
-  };
-  document.head.appendChild(commonScript);
-}
-
-
-//
-// Driver
-//
-
-testOnMainThread(function() {
-  testOnWorker(function() {
-    SimpleTest.finish();
-  });
-});
+testScript("test_headers_common.js");
 </script>
 </body>
 </html>
--- a/dom/tests/mochitest/fetch/test_headers_common.js
+++ b/dom/tests/mochitest/fetch/test_headers_common.js
@@ -164,10 +164,13 @@ function TestFilledHeaders() {
   shouldThrow(function() {
     filled = new Headers([
       ["zxy"],
       ["uts", "321"]
     ]);
   }, TypeError, "Fill with non-tuple sequence should throw TypeError.");
 }
 
-TestEmptyHeaders();
-TestFilledHeaders();
+function runTest() {
+  TestEmptyHeaders();
+  TestFilledHeaders();
+  return Promise.resolve();
+}
new file mode 100644
--- /dev/null
+++ b/dom/tests/mochitest/fetch/test_headers_mainthread.html
@@ -0,0 +1,157 @@
+<!-- Any copyright is dedicated to the Public Domain.
+   - http://creativecommons.org/publicdomain/zero/1.0/ -->
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>Test Fetch Headers - Basic</title>
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<script type="text/javascript" src="test_headers_common.js"> </script>
+<script type="text/javascript">
+// Main thread specific tests because they need SpecialPowers.  Expects
+// test_headers_common.js to already be loaded.
+
+function TestRequestHeaders() {
+  is(typeof Headers, "function", "Headers global constructor exists.");
+  var headers = new Headers();
+  ok(headers, "Constructed empty Headers object");
+  SpecialPowers.wrap(headers).guard = "request";
+  TestCoreBehavior(headers, "foo");
+  var forbidden = [
+    "Accept-Charset",
+    "Accept-Encoding",
+    "Access-Control-Request-Headers",
+    "Access-Control-Request-Method",
+    "Connection",
+    "Content-Length",
+    "Cookie",
+    "Cookie2",
+    "Date",
+    "DNT",
+    "Expect",
+    "Host",
+    "Keep-Alive",
+    "Origin",
+    "Referer",
+    "TE",
+    "Trailer",
+    "Transfer-Encoding",
+    "Upgrade",
+    "User-Agent",
+    "Via",
+    "Proxy-Authorization",
+    "Proxy-blarg",
+    "Proxy-",
+    "Sec-foo",
+    "Sec-"
+  ];
+
+  for (var i = 0, n = forbidden.length; i < n; ++i) {
+    var name = forbidden[i];
+    headers.append(name, "hmm");
+    checkNotHas(headers, name, "Should not be able to append " + name + " to request headers");
+    headers.set(name, "hmm");
+    checkNotHas(headers, name, "Should not be able to set " + name + " on request headers");
+  }
+}
+
+function TestRequestNoCorsHeaders() {
+  is(typeof Headers, "function", "Headers global constructor exists.");
+  var headers = new Headers();
+  ok(headers, "Constructed empty Headers object");
+  SpecialPowers.wrap(headers).guard = "request-no-cors";
+
+  headers.append("foo", "bar");
+  checkNotHas(headers, "foo", "Should not be able to append arbitrary headers to request-no-cors headers.");
+  headers.set("foo", "bar");
+  checkNotHas(headers, "foo", "Should not be able to set arbitrary headers on request-no-cors headers.");
+
+  var simpleNames = [
+    "Accept",
+    "Accept-Language",
+    "Content-Language"
+  ];
+
+  var simpleContentTypes = [
+    "application/x-www-form-urlencoded",
+    "multipart/form-data",
+    "text/plain",
+    "application/x-www-form-urlencoded; charset=utf-8",
+    "multipart/form-data; charset=utf-8",
+    "text/plain; charset=utf-8"
+  ];
+
+  for (var i = 0, n = simpleNames.length; i < n; ++i) {
+    var name = simpleNames[i];
+    headers.append(name, "hmm");
+    checkHas(headers, name, "Should be able to append " + name + " to request-no-cors headers");
+    headers.set(name, "hmm");
+    checkHas(headers, name, "Should be able to set " + name + " on request-no-cors headers");
+  }
+
+  for (var i = 0, n = simpleContentTypes.length; i < n; ++i) {
+    var value = simpleContentTypes[i];
+    headers.append("Content-Type", value);
+    checkHas(headers, "Content-Type", "Should be able to append " + value + " Content-Type to request-no-cors headers");
+    headers.delete("Content-Type");
+    headers.set("Content-Type", value);
+    checkHas(headers, "Content-Type", "Should be able to set " + value + " Content-Type on request-no-cors headers");
+  }
+}
+
+function TestResponseHeaders() {
+  is(typeof Headers, "function", "Headers global constructor exists.");
+  var headers = new Headers();
+  ok(headers, "Constructed empty Headers object");
+  SpecialPowers.wrap(headers).guard = "response";
+  TestCoreBehavior(headers, "foo");
+  var forbidden = [
+    "Set-Cookie",
+    "Set-Cookie2"
+  ];
+
+  for (var i = 0, n = forbidden.length; i < n; ++i) {
+    var name = forbidden[i];
+    headers.append(name, "hmm");
+    checkNotHas(headers, name, "Should not be able to append " + name + " to response headers");
+    headers.set(name, "hmm");
+    checkNotHas(headers, name, "Should not be able to set " + name + " on response headers");
+  }
+}
+
+function TestImmutableHeaders() {
+  is(typeof Headers, "function", "Headers global constructor exists.");
+  var headers = new Headers();
+  ok(headers, "Constructed empty Headers object");
+  TestCoreBehavior(headers, "foo");
+  headers.append("foo", "atleastone");
+
+  SpecialPowers.wrap(headers).guard = "immutable";
+
+  shouldThrow(function() {
+    headers.append("foo", "wat");
+  }, TypeError, "Should not be able to append to immutable headers");
+
+  shouldThrow(function() {
+    headers.set("foo", "wat");
+  }, TypeError, "Should not be able to set immutable headers");
+
+  shouldThrow(function() {
+    headers.delete("foo");
+  }, TypeError, "Should not be able to delete immutable headers");
+
+  checkHas(headers, "foo", "Should be able to check immutable headers");
+  ok(headers.get("foo"), "Should be able to get immutable headers");
+  ok(headers.getAll("foo").length, "Should be able to get all immutable headers");
+}
+
+TestRequestHeaders();
+TestRequestNoCorsHeaders();
+TestResponseHeaders();
+TestImmutableHeaders();
+</script>
+</body>
+</html>
+
deleted file mode 100644
--- a/dom/tests/mochitest/fetch/test_headers_mainthread.js
+++ /dev/null
@@ -1,141 +0,0 @@
-// Main thread specific tests because they need SpecialPowers.  Expects
-// test_headers_common.js to already be loaded.
-
-function TestRequestHeaders() {
-  is(typeof Headers, "function", "Headers global constructor exists.");
-  var headers = new Headers();
-  ok(headers, "Constructed empty Headers object");
-  SpecialPowers.wrap(headers).guard = "request";
-  TestCoreBehavior(headers, "foo");
-  var forbidden = [
-    "Accept-Charset",
-    "Accept-Encoding",
-    "Access-Control-Request-Headers",
-    "Access-Control-Request-Method",
-    "Connection",
-    "Content-Length",
-    "Cookie",
-    "Cookie2",
-    "Date",
-    "DNT",
-    "Expect",
-    "Host",
-    "Keep-Alive",
-    "Origin",
-    "Referer",
-    "TE",
-    "Trailer",
-    "Transfer-Encoding",
-    "Upgrade",
-    "User-Agent",
-    "Via",
-    "Proxy-Authorization",
-    "Proxy-blarg",
-    "Proxy-",
-    "Sec-foo",
-    "Sec-"
-  ];
-
-  for (var i = 0, n = forbidden.length; i < n; ++i) {
-    var name = forbidden[i];
-    headers.append(name, "hmm");
-    checkNotHas(headers, name, "Should not be able to append " + name + " to request headers");
-    headers.set(name, "hmm");
-    checkNotHas(headers, name, "Should not be able to set " + name + " on request headers");
-  }
-}
-
-function TestRequestNoCorsHeaders() {
-  is(typeof Headers, "function", "Headers global constructor exists.");
-  var headers = new Headers();
-  ok(headers, "Constructed empty Headers object");
-  SpecialPowers.wrap(headers).guard = "request-no-cors";
-
-  headers.append("foo", "bar");
-  checkNotHas(headers, "foo", "Should not be able to append arbitrary headers to request-no-cors headers.");
-  headers.set("foo", "bar");
-  checkNotHas(headers, "foo", "Should not be able to set arbitrary headers on request-no-cors headers.");
-
-  var simpleNames = [
-    "Accept",
-    "Accept-Language",
-    "Content-Language"
-  ];
-
-  var simpleContentTypes = [
-    "application/x-www-form-urlencoded",
-    "multipart/form-data",
-    "text/plain",
-    "application/x-www-form-urlencoded; charset=utf-8",
-    "multipart/form-data; charset=utf-8",
-    "text/plain; charset=utf-8"
-  ];
-
-  for (var i = 0, n = simpleNames.length; i < n; ++i) {
-    var name = simpleNames[i];
-    headers.append(name, "hmm");
-    checkHas(headers, name, "Should be able to append " + name + " to request-no-cors headers");
-    headers.set(name, "hmm");
-    checkHas(headers, name, "Should be able to set " + name + " on request-no-cors headers");
-  }
-
-  for (var i = 0, n = simpleContentTypes.length; i < n; ++i) {
-    var value = simpleContentTypes[i];
-    headers.append("Content-Type", value);
-    checkHas(headers, "Content-Type", "Should be able to append " + value + " Content-Type to request-no-cors headers");
-    headers.delete("Content-Type");
-    headers.set("Content-Type", value);
-    checkHas(headers, "Content-Type", "Should be able to set " + value + " Content-Type on request-no-cors headers");
-  }
-}
-
-function TestResponseHeaders() {
-  is(typeof Headers, "function", "Headers global constructor exists.");
-  var headers = new Headers();
-  ok(headers, "Constructed empty Headers object");
-  SpecialPowers.wrap(headers).guard = "response";
-  TestCoreBehavior(headers, "foo");
-  var forbidden = [
-    "Set-Cookie",
-    "Set-Cookie2"
-  ];
-
-  for (var i = 0, n = forbidden.length; i < n; ++i) {
-    var name = forbidden[i];
-    headers.append(name, "hmm");
-    checkNotHas(headers, name, "Should not be able to append " + name + " to response headers");
-    headers.set(name, "hmm");
-    checkNotHas(headers, name, "Should not be able to set " + name + " on response headers");
-  }
-}
-
-function TestImmutableHeaders() {
-  is(typeof Headers, "function", "Headers global constructor exists.");
-  var headers = new Headers();
-  ok(headers, "Constructed empty Headers object");
-  TestCoreBehavior(headers, "foo");
-  headers.append("foo", "atleastone");
-
-  SpecialPowers.wrap(headers).guard = "immutable";
-
-  shouldThrow(function() {
-    headers.append("foo", "wat");
-  }, TypeError, "Should not be able to append to immutable headers");
-
-  shouldThrow(function() {
-    headers.set("foo", "wat");
-  }, TypeError, "Should not be able to set immutable headers");
-
-  shouldThrow(function() {
-    headers.delete("foo");
-  }, TypeError, "Should not be able to delete immutable headers");
-
-  checkHas(headers, "foo", "Should be able to check immutable headers");
-  ok(headers.get("foo"), "Should be able to get immutable headers");
-  ok(headers.getAll("foo").length, "Should be able to get all immutable headers");
-}
-
-TestRequestHeaders();
-TestRequestNoCorsHeaders();
-TestResponseHeaders();
-TestImmutableHeaders();
rename from dom/workers/test/fetch/test_request.html
rename to dom/tests/mochitest/fetch/test_request.html
--- a/dom/workers/test/fetch/test_request.html
+++ b/dom/tests/mochitest/fetch/test_request.html
@@ -8,37 +8,15 @@
   <title>Bug XXXXXX - Test Request object in worker</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" style="display: none"></div>
 <pre id="test"></pre>
+<script type="text/javascript" src="fetch_test_framework.js"> </script>
 <script class="testbody" type="text/javascript">
-
-  function checkEnabled() {
-    var worker = new Worker("worker_test_request.js");
-    worker.onmessage = function(event) {
-
-      if (event.data.type == 'finish') {
-        SimpleTest.finish();
-      } else if (event.data.type == 'status') {
-        ok(event.data.status, event.data.msg);
-      }
-    }
-
-    worker.onerror = function(event) {
-      ok(false, "Worker had an error: " + event.message + " at " + event.lineno);
-      SimpleTest.finish();
-    };
-
-    worker.postMessage(true);
-  }
-
-  SimpleTest.waitForExplicitFinish();
-
-  checkEnabled();
+testScript("test_request.js");
 </script>
-</pre>
 </body>
 </html>
 
rename from dom/workers/test/fetch/worker_test_request.js
rename to dom/tests/mochitest/fetch/test_request.js
--- a/dom/workers/test/fetch/worker_test_request.js
+++ b/dom/tests/mochitest/fetch/test_request.js
@@ -1,16 +1,8 @@
-function ok(a, msg) {
-  postMessage({type: 'status', status: !!a, msg: a + ": " + msg });
-}
-
-function is(a, b, msg) {
-  postMessage({type: 'status', status: a === b, msg: a + " === " + b + ": " + msg });
-}
-
 function testDefaultCtor() {
   var req = new Request("");
   is(req.method, "GET", "Default Request method is GET");
   ok(req.headers instanceof Headers, "Request should have non-null Headers object");
   is(req.url, self.location.href, "URL should be resolved with entry settings object's API base URL");
   is(req.context, "fetch", "Default context is fetch.");
   is(req.referrer, "about:client", "Default referrer is `client` which serializes to about:client.");
   is(req.mode, "cors", "Request mode for string input is cors");
@@ -266,18 +258,19 @@ function testBodyExtraction() {
   var text = "κόσμε";
   var newReq = function() { return new Request("", { method: 'post', body: text }); }
   return newReq().text().then(function(v) {
     ok(typeof v === "string", "Should resolve to string");
     is(text, v, "Extracted string should match");
   }).then(function() {
     return newReq().blob().then(function(v) {
       ok(v instanceof Blob, "Should resolve to Blob");
-      var fs = new FileReaderSync();
-      is(fs.readAsText(v), text, "Decoded Blob should match original");
+      return readAsText(v).then(function(result) {
+        is(result, text, "Decoded Blob should match original");
+      });
     });
   }).then(function() {
     return newReq().json().then(function(v) {
       ok(false, "Invalid json should reject");
     }, function(e) {
       ok(true, "Invalid json should reject");
     })
   }).then(function() {
@@ -306,31 +299,24 @@ function testModeCorsPreflightEnumValue(
       invalidExc = e;
     }
     var expectedMessage = invalidExc.message.replace(invalidMode, 'cors-with-forced-preflight');
     is(e.message, expectedMessage,
        "mode cors-with-forced-preflight should throw same error as invalid RequestMode strings.");
   }
 }
 
-onmessage = function() {
-  var done = function() { postMessage({ type: 'finish' }) }
-
+function runTest() {
   testDefaultCtor();
   testSimpleUrlParse();
   testUrlFragment();
   testMethod();
   testBug1109574();
   testModeCorsPreflightEnumValue();
 
-  Promise.resolve()
+  return Promise.resolve()
     .then(testBodyCreation)
     .then(testBodyUsed)
     .then(testBodyExtraction)
     .then(testUsedRequest)
     .then(testClone())
     // Put more promise based tests here.
-    .then(done)
-    .catch(function(e) {
-      ok(false, "Some Request tests failed " + e);
-      done();
-    })
 }
rename from dom/workers/test/fetch/test_response.html
rename to dom/tests/mochitest/fetch/test_response.html
--- a/dom/workers/test/fetch/test_response.html
+++ b/dom/tests/mochitest/fetch/test_response.html
@@ -8,37 +8,15 @@
   <title>Bug 1039846 - Test Response object in worker</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" style="display: none"></div>
 <pre id="test"></pre>
+<script type="text/javascript" src="fetch_test_framework.js"> </script>
 <script class="testbody" type="text/javascript">
-
-  function runTest() {
-    var worker = new Worker("worker_test_response.js");
-    worker.onmessage = function(event) {
-
-      if (event.data.type == 'finish') {
-        SimpleTest.finish();
-      } else if (event.data.type == 'status') {
-        ok(event.data.status, event.data.msg);
-      }
-    }
-
-    worker.onerror = function(event) {
-      ok(false, "Worker had an error: " + event.message + " at " + event.lineno);
-      SimpleTest.finish();
-    };
-
-    worker.postMessage(true);
-  }
-
-  SimpleTest.waitForExplicitFinish();
-
-  runTest();
+testScript("test_response.js");
 </script>
-</pre>
 </body>
 </html>
 
rename from dom/workers/test/fetch/worker_test_response.js
rename to dom/tests/mochitest/fetch/test_response.js
--- a/dom/workers/test/fetch/worker_test_response.js
+++ b/dom/tests/mochitest/fetch/test_response.js
@@ -1,18 +1,8 @@
-function ok(a, msg) {
-  dump("OK: " + !!a + "  =>  " + a + " " + msg + "\n");
-  postMessage({type: 'status', status: !!a, msg: a + ": " + msg });
-}
-
-function is(a, b, msg) {
-  dump("IS: " + (a===b) + "  =>  " + a + " | " + b + " " + msg + "\n");
-  postMessage({type: 'status', status: a === b, msg: a + " === " + b + ": " + msg });
-}
-
 function testDefaultCtor() {
   var res = new Response();
   is(res.type, "default", "Default Response type is default");
   ok(res.headers instanceof Headers, "Response should have non-null Headers object");
   is(res.url, "", "URL should be empty string");
   is(res.status, 200, "Default status is 200");
   is(res.statusText, "OK", "Default statusText is OK");
 }
@@ -207,47 +197,41 @@ function testBodyExtraction() {
   var text = "κόσμε";
   var newRes = function() { return new Response(text); }
   return newRes().text().then(function(v) {
     ok(typeof v === "string", "Should resolve to string");
     is(text, v, "Extracted string should match");
   }).then(function() {
     return newRes().blob().then(function(v) {
       ok(v instanceof Blob, "Should resolve to Blob");
-      var fs = new FileReaderSync();
-      is(fs.readAsText(v), text, "Decoded Blob should match original");
+      return readAsText(v).then(function(result) {
+        is(result, text, "Decoded Blob should match original");
+      });
     });
   }).then(function() {
     return newRes().json().then(function(v) {
       ok(false, "Invalid json should reject");
     }, function(e) {
       ok(true, "Invalid json should reject");
     })
   }).then(function() {
     return newRes().arrayBuffer().then(function(v) {
       ok(v instanceof ArrayBuffer, "Should resolve to ArrayBuffer");
       var dec = new TextDecoder();
       is(dec.decode(new Uint8Array(v)), text, "UTF-8 decoded ArrayBuffer should match original");
     });
   })
 }
 
-onmessage = function() {
-  var done = function() { postMessage({ type: 'finish' }) }
-
+function runTest() {
   testDefaultCtor();
   testError();
   testRedirect();
   testOk();
   testFinalURL();
 
-  Promise.resolve()
+  return Promise.resolve()
     .then(testBodyCreation)
     .then(testBodyUsed)
     .then(testBodyExtraction)
     .then(testClone)
     // Put more promise based tests here.
-    .then(done)
-    .catch(function(e) {
-      ok(false, "Some Response tests failed " + e);
-      done();
-    })
 }
--- a/dom/tests/mochitest/fetch/worker_wrapper.js
+++ b/dom/tests/mochitest/fetch/worker_wrapper.js
@@ -1,24 +1,29 @@
 function ok(a, msg) {
-  dump("OK: " + !!a + "  =>  " + a + " " + msg + "\n");
   postMessage({type: 'status', status: !!a, msg: a + ": " + msg });
 }
 
 function is(a, b, msg) {
-  dump("IS: " + (a===b) + "  =>  " + a + " | " + b + " " + msg + "\n");
   postMessage({type: 'status', status: a === b, msg: a + " === " + b + ": " + msg });
 }
 
 addEventListener('message', function workerWrapperOnMessage(e) {
   removeEventListener('message', workerWrapperOnMessage);
   var data = e.data;
+
+  var done = function() {
+    postMessage({ type: 'finish' });
+  }
+
   try {
     importScripts(data.script);
+    // runTest() is provided by the test.
+    runTest().then(done, done);
   } catch(e) {
     postMessage({
       type: 'status',
       status: false,
       msg: 'worker failed to import ' + data.script + "; error: " + e.message
     });
+    done();
   }
-  postMessage({ type: 'finish' });
 });
--- a/dom/webidl/Client.webidl
+++ b/dom/webidl/Client.webidl
@@ -5,16 +5,17 @@
  *
  * The origin of this IDL file is
  * http://slightlyoff.github.io/ServiceWorker/spec/service_worker/index.html
  *
  */
 
 [Exposed=ServiceWorker]
 interface Client {
+  readonly attribute DOMString id;
   readonly attribute USVString url;
 
   [Throws]
   void postMessage(any message, optional sequence<Transferable> transfer);
 };
 
 [Exposed=ServiceWorker]
 interface WindowClient : Client {
--- a/dom/webidl/MessageEvent.webidl
+++ b/dom/webidl/MessageEvent.webidl
@@ -26,19 +26,22 @@ interface MessageEvent : Event {
 
   /**
    * The last event ID string of the event source, for server-sent DOM events; this
    * value is the empty string for cross-origin messaging.
    */
   readonly attribute DOMString lastEventId;
 
   /**
-   * The window or the port which originated this event.
+   * The window, port or client which originated this event.
+   * FIXME(catalinb): Update this when the spec changes are implemented.
+   * https://www.w3.org/Bugs/Public/show_bug.cgi?id=28199
+   * https://bugzilla.mozilla.org/show_bug.cgi?id=1143717
    */
-  readonly attribute (WindowProxy or MessagePort)? source;
+  readonly attribute (WindowProxy or MessagePort or Client)? source;
 
   /**
    * Initializes this event with the given data, in a manner analogous to
    * the similarly-named method on the nsIDOMEvent interface, also setting the
    * data, origin, source, and lastEventId attributes of this appropriately.
    */
   readonly attribute MessagePortList? ports;
 };
--- a/dom/webidl/ServiceWorkerContainer.webidl
+++ b/dom/webidl/ServiceWorkerContainer.webidl
@@ -16,17 +16,17 @@ interface ServiceWorkerContainer : Event
   // and discussion at https://etherpad.mozilla.org/serviceworker07apr
   [Unforgeable] readonly attribute ServiceWorker? controller;
 
   [Throws]
   readonly attribute Promise<ServiceWorkerRegistration> ready;
 
   [Throws]
   Promise<ServiceWorkerRegistration> register(USVString scriptURL,
-                                              optional RegistrationOptionList options);
+                                              optional RegistrationOptions options);
 
   [Throws]
   Promise<ServiceWorkerRegistration> getRegistration(optional USVString documentURL = "");
 
   [Throws]
    Promise<sequence<ServiceWorkerRegistration>> getRegistrations();
 
   attribute EventHandler oncontrollerchange;
@@ -36,11 +36,11 @@ interface ServiceWorkerContainer : Event
 };
 
 // Testing only.
 partial interface ServiceWorkerContainer {
   [Throws,Pref="dom.serviceWorkers.testing.enabled"]
   DOMString getScopeForUrl(DOMString url);
 };
 
-dictionary RegistrationOptionList {
-  USVString scope = "/";
+dictionary RegistrationOptions {
+  USVString scope;
 };
--- a/dom/workers/ServiceWorker.cpp
+++ b/dom/workers/ServiceWorker.cpp
@@ -1,16 +1,17 @@
 /* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */
 /* 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 "ServiceWorker.h"
 
 #include "nsPIDOMWindow.h"
+#include "ServiceWorkerClient.h"
 #include "ServiceWorkerManager.h"
 #include "SharedWorker.h"
 #include "WorkerPrivate.h"
 
 #include "mozilla/Preferences.h"
 #include "mozilla/dom/Promise.h"
 #include "mozilla/dom/ServiceWorkerGlobalScopeBinding.h"
 
@@ -90,17 +91,22 @@ ServiceWorker::PostMessage(JSContext* aC
   WorkerPrivate* workerPrivate = GetWorkerPrivate();
   MOZ_ASSERT(workerPrivate);
 
   if (State() == ServiceWorkerState::Redundant) {
     aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
     return;
   }
 
-  workerPrivate->PostMessage(aCx, aMessage, aTransferable, aRv);
+  nsCOMPtr<nsPIDOMWindow> window = do_QueryInterface(GetParentObject());
+  nsCOMPtr<nsIDocument> doc = window->GetExtantDoc();
+  nsAutoPtr<ServiceWorkerClientInfo> clientInfo(new ServiceWorkerClientInfo(doc));
+
+  workerPrivate->PostMessageToServiceWorker(aCx, aMessage, aTransferable,
+                                            clientInfo, aRv);
 }
 
 WorkerPrivate*
 ServiceWorker::GetWorkerPrivate() const
 {
   // At some point in the future, this may be optimized to terminate a worker
   // that hasn't been used in a certain amount of time or when there is memory
   // pressure or similar.
--- a/dom/workers/ServiceWorkerClient.cpp
+++ b/dom/workers/ServiceWorkerClient.cpp
@@ -26,18 +26,23 @@ NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(
   NS_INTERFACE_MAP_ENTRY(nsISupports)
 NS_INTERFACE_MAP_END
 
 ServiceWorkerClientInfo::ServiceWorkerClientInfo(nsIDocument* aDoc)
 {
   MOZ_ASSERT(aDoc);
   MOZ_ASSERT(aDoc->GetWindow());
 
+  nsresult rv = aDoc->GetId(mClientId);
+  if (NS_FAILED(rv)) {
+    NS_WARNING("Failed to get the UUID of the document.");
+  }
+
   nsRefPtr<nsGlobalWindow> outerWindow = static_cast<nsGlobalWindow*>(aDoc->GetWindow());
-  mClientId = outerWindow->WindowID();
+  mWindowId = outerWindow->WindowID();
   aDoc->GetURL(mUrl);
   mVisibilityState = aDoc->VisibilityState();
 
   ErrorResult result;
   mFocused = aDoc->HasFocus(result);
   if (result.Failed()) {
     NS_WARNING("Failed to get focus information.");
   }
@@ -56,35 +61,35 @@ ServiceWorkerClient::WrapObject(JSContex
 {
   return ClientBinding::Wrap(aCx, this);
 }
 
 namespace {
 
 class ServiceWorkerClientPostMessageRunnable MOZ_FINAL : public nsRunnable
 {
-  uint64_t mId;
+  uint64_t mWindowId;
   JSAutoStructuredCloneBuffer mBuffer;
   nsTArray<nsCOMPtr<nsISupports>> mClonedObjects;
 
 public:
-  ServiceWorkerClientPostMessageRunnable(uint64_t aId,
+  ServiceWorkerClientPostMessageRunnable(uint64_t aWindowId,
                                          JSAutoStructuredCloneBuffer&& aData,
                                          nsTArray<nsCOMPtr<nsISupports>>& aClonedObjects)
-    : mId(aId),
+    : mWindowId(aWindowId),
       mBuffer(Move(aData))
   {
     mClonedObjects.SwapElements(aClonedObjects);
   }
 
   NS_IMETHOD
   Run()
   {
     AssertIsOnMainThread();
-    nsGlobalWindow* window = nsGlobalWindow::GetOuterWindowWithId(mId);
+    nsGlobalWindow* window = nsGlobalWindow::GetOuterWindowWithId(mWindowId);
     if (!window) {
       return NS_ERROR_FAILURE;
     }
 
     ErrorResult result;
     dom::Navigator* navigator = window->GetNavigator(result);
     if (NS_WARN_IF(result.Failed())) {
       return result.ErrorCode();
@@ -177,15 +182,15 @@ ServiceWorkerClient::PostMessage(JSConte
 
   JSAutoStructuredCloneBuffer buffer;
   if (!buffer.write(aCx, aMessage, transferable, callbacks, &clonedObjects)) {
     aRv.Throw(NS_ERROR_DOM_DATA_CLONE_ERR);
     return;
   }
 
   nsRefPtr<ServiceWorkerClientPostMessageRunnable> runnable =
-    new ServiceWorkerClientPostMessageRunnable(mId, Move(buffer), clonedObjects);
+    new ServiceWorkerClientPostMessageRunnable(mWindowId, Move(buffer), clonedObjects);
   nsresult rv = NS_DispatchToMainThread(runnable);
   if (NS_FAILED(rv)) {
     aRv.Throw(NS_ERROR_FAILURE);
   }
 }
 
--- a/dom/workers/ServiceWorkerClient.h
+++ b/dom/workers/ServiceWorkerClient.h
@@ -26,17 +26,18 @@ class ServiceWorkerClientInfo MOZ_FINAL
 {
   friend class ServiceWorkerClient;
   friend class ServiceWorkerWindowClient;
 
 public:
   explicit ServiceWorkerClientInfo(nsIDocument* aDoc);
 
 private:
-  uint64_t mClientId;
+  nsString mClientId;
+  uint64_t mWindowId;
   nsString mUrl;
 
   // Window Clients
   VisibilityState mVisibilityState;
   bool mFocused;
   FrameType mFrameType;
 };
 
@@ -46,27 +47,33 @@ class ServiceWorkerClient : public nsISu
 public:
   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
   NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(ServiceWorkerClient)
 
   ServiceWorkerClient(nsISupports* aOwner,
                       const ServiceWorkerClientInfo& aClientInfo)
     : mOwner(aOwner),
       mId(aClientInfo.mClientId),
+      mWindowId(aClientInfo.mWindowId),
       mUrl(aClientInfo.mUrl)
   {
     MOZ_ASSERT(aOwner);
   }
 
   nsISupports*
   GetParentObject() const
   {
     return mOwner;
   }
 
+  void GetId(nsString& aRetval) const
+  {
+    aRetval = mId;
+  }
+
   void
   GetUrl(nsAString& aUrl) const
   {
     aUrl.Assign(mUrl);
   }
 
   void
   PostMessage(JSContext* aCx, JS::Handle<JS::Value> aMessage,
@@ -76,17 +83,18 @@ public:
   JSObject* WrapObject(JSContext* aCx) MOZ_OVERRIDE;
 
 protected:
   virtual ~ServiceWorkerClient()
   { }
 
 private:
   nsCOMPtr<nsISupports> mOwner;
-  uint64_t mId;
+  nsString mId;
+  uint64_t mWindowId;
   nsString mUrl;
 };
 
 } // namespace workers
 } // namespace dom
 } // namespace mozilla
 
 #endif // mozilla_dom_workers_serviceworkerclient_h
--- a/dom/workers/ServiceWorkerContainer.cpp
+++ b/dom/workers/ServiceWorkerContainer.cpp
@@ -68,28 +68,59 @@ ServiceWorkerContainer::RemoveReadyPromi
 JSObject*
 ServiceWorkerContainer::WrapObject(JSContext* aCx)
 {
   return ServiceWorkerContainerBinding::Wrap(aCx, this);
 }
 
 already_AddRefed<Promise>
 ServiceWorkerContainer::Register(const nsAString& aScriptURL,
-                                 const RegistrationOptionList& aOptions,
+                                 const RegistrationOptions& aOptions,
                                  ErrorResult& aRv)
 {
   nsCOMPtr<nsISupports> promise;
 
   nsCOMPtr<nsIServiceWorkerManager> swm = mozilla::services::GetServiceWorkerManager();
   if (!swm) {
     aRv.Throw(NS_ERROR_FAILURE);
     return nullptr;
   }
 
-  aRv = swm->Register(GetOwner(), aOptions.mScope, aScriptURL, getter_AddRefs(promise));
+  nsCOMPtr<nsPIDOMWindow> window = GetOwner();
+  MOZ_ASSERT(window);
+
+  nsresult rv;
+  nsCOMPtr<nsIURI> scriptURI;
+  rv = NS_NewURI(getter_AddRefs(scriptURI), aScriptURL, nullptr,
+                 window->GetDocBaseURI());
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    aRv.ThrowTypeError(MSG_INVALID_URL, &aScriptURL);
+    return nullptr;
+  }
+
+  // In ServiceWorkerContainer.register() the scope argument is parsed against
+  // different base URLs depending on whether it was passed or not.
+  nsCOMPtr<nsIURI> scopeURI;
+
+  // Step 4. If none passed, parse against script's URL
+  if (!aOptions.mScope.WasPassed()) {
+    nsresult rv = NS_NewURI(getter_AddRefs(scopeURI), NS_LITERAL_CSTRING("./"),
+                            nullptr, scriptURI);
+    MOZ_ALWAYS_TRUE(NS_SUCCEEDED(rv));
+  } else {
+    // Step 5. Parse against entry settings object's base URL.
+    nsresult rv = NS_NewURI(getter_AddRefs(scopeURI), aOptions.mScope.Value(),
+                            nullptr, window->GetDocBaseURI());
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      aRv.ThrowTypeError(MSG_INVALID_URL, &aOptions.mScope.Value());
+      return nullptr;
+    }
+  }
+
+  aRv = swm->Register(window, scopeURI, scriptURI, getter_AddRefs(promise));
   if (aRv.Failed()) {
     return nullptr;
   }
 
   nsRefPtr<Promise> ret = static_cast<Promise*>(promise.get());
   MOZ_ASSERT(ret);
   return ret.forget();
 }
--- a/dom/workers/ServiceWorkerContainer.h
+++ b/dom/workers/ServiceWorkerContainer.h
@@ -10,17 +10,17 @@
 #include "mozilla/DOMEventTargetHelper.h"
 
 class nsPIDOMWindow;
 
 namespace mozilla {
 namespace dom {
 
 class Promise;
-struct RegistrationOptionList;
+struct RegistrationOptions;
 
 namespace workers {
 class ServiceWorker;
 }
 
 // Lightweight serviceWorker APIs collection.
 class ServiceWorkerContainer MOZ_FINAL : public DOMEventTargetHelper
 {
@@ -35,17 +35,17 @@ public:
 
   explicit ServiceWorkerContainer(nsPIDOMWindow* aWindow);
 
   virtual JSObject*
   WrapObject(JSContext* aCx) MOZ_OVERRIDE;
 
   already_AddRefed<Promise>
   Register(const nsAString& aScriptURL,
-           const RegistrationOptionList& aOptions,
+           const RegistrationOptions& aOptions,
            ErrorResult& aRv);
 
   already_AddRefed<workers::ServiceWorker>
   GetController();
 
   already_AddRefed<Promise>
   GetRegistration(const nsAString& aDocumentURL,
                   ErrorResult& aRv);
--- a/dom/workers/ServiceWorkerManager.cpp
+++ b/dom/workers/ServiceWorkerManager.cpp
@@ -437,16 +437,48 @@ public:
         NS_WARNING("Failed to dispatch ContinueUpdateRunnable to main thread.");
       }
     }
 
     return true;
   }
 };
 
+namespace {
+nsresult
+GetRequiredScopeStringPrefix(const nsACString& aScriptSpec, nsACString& aPrefix)
+{
+  nsCOMPtr<nsIURI> scriptURI;
+  nsresult rv = NS_NewURI(getter_AddRefs(scriptURI), aScriptSpec,
+                 nullptr, nullptr);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  rv = scriptURI->GetPrePath(aPrefix);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  nsCOMPtr<nsIURL> scriptURL(do_QueryInterface(scriptURI));
+  if (NS_WARN_IF(!scriptURL)) {
+    return rv;
+  }
+
+  nsAutoCString dir;
+  rv = scriptURL->GetDirectory(dir);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  aPrefix.Append(dir);
+  return NS_OK;
+}
+} // anonymous namespace
+
 class ServiceWorkerRegisterJob MOZ_FINAL : public ServiceWorkerJob,
                                            public nsIStreamLoaderObserver
 {
   friend class ContinueInstallTask;
 
   nsCString mScope;
   nsCString mScriptSpec;
   nsRefPtr<ServiceWorkerRegistrationInfo> mRegistration;
@@ -558,16 +590,30 @@ public:
     }
 
 
     // FIXME(nsm): "Extract mime type..."
     // FIXME(nsm): Byte match to aString.
     NS_WARNING("Byte wise check is disabled, just using new one");
     nsRefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
 
+    // FIXME: Bug 1130101 - Read max scope from Service-Worker-Allowed header.
+    nsAutoCString allowedPrefix;
+    rv = GetRequiredScopeStringPrefix(mRegistration->mScriptSpec, allowedPrefix);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      Fail(NS_ERROR_DOM_SECURITY_ERR);
+      return rv;
+    }
+
+    if (!StringBeginsWith(mRegistration->mScope, allowedPrefix)) {
+      NS_WARNING("By default a service worker's scope is restricted to at or below it's script's location.");
+      Fail(NS_ERROR_DOM_SECURITY_ERR);
+      return NS_ERROR_DOM_SECURITY_ERR;
+    }
+
     // We have to create a ServiceWorker here simply to ensure there are no
     // errors. Ideally we should just pass this worker on to ContinueInstall.
     MOZ_ASSERT(!swm->mSetOfScopesBeingUpdated.Contains(mRegistration->mScope));
     swm->mSetOfScopesBeingUpdated.Put(mRegistration->mScope, true);
     nsRefPtr<ServiceWorkerInfo> dummyInfo =
       new ServiceWorkerInfo(mRegistration, mRegistration->mScriptSpec);
     nsRefPtr<ServiceWorker> serviceWorker;
     rv = swm->CreateServiceWorker(mRegistration->mPrincipal,
@@ -587,17 +633,17 @@ public:
     nsRefPtr<CheckWorkerEvaluationAndContinueUpdateWorkerRunnable> r =
       new CheckWorkerEvaluationAndContinueUpdateWorkerRunnable(serviceWorker->GetWorkerPrivate(), handle);
     AutoJSAPI jsapi;
     jsapi.Init();
     bool ok = r->Dispatch(jsapi.cx());
     if (NS_WARN_IF(!ok)) {
       swm->mSetOfScopesBeingUpdated.Remove(mRegistration->mScope);
       Fail(NS_ERROR_DOM_ABORT_ERR);
-      return rv;
+      return NS_ERROR_FAILURE;
     }
 
     return NS_OK;
   }
 
   // Public so our error handling code can use it.
   void
   Fail(const ErrorEventInit& aError)
@@ -808,18 +854,18 @@ ContinueInstallTask::ContinueAfterWorker
   // queue, which captures the "atomic" behaviour we want.
   mJob->ContinueAfterInstallEvent(aSuccess, aActivateImmediately);
 }
 
 // If we return an error code here, the ServiceWorkerContainer will
 // automatically reject the Promise.
 NS_IMETHODIMP
 ServiceWorkerManager::Register(nsIDOMWindow* aWindow,
-                               const nsAString& aScope,
-                               const nsAString& aScriptURL,
+                               nsIURI* aScopeURI,
+                               nsIURI* aScriptURI,
                                nsISupports** aPromise)
 {
   AssertIsOnMainThread();
 
   // XXXnsm Don't allow chrome callers for now, we don't support chrome
   // ServiceWorkers.
   MOZ_ASSERT(!nsContentUtils::IsCallerChrome());
 
@@ -879,51 +925,39 @@ ServiceWorkerManager::Register(nsIDOMWin
       rv = documentURI->SchemeIs("https", &isHttps);
       if (NS_WARN_IF(NS_FAILED(rv)) || !isHttps) {
         NS_WARNING("ServiceWorker registration from insecure websites is not allowed.");
         return NS_ERROR_DOM_SECURITY_ERR;
       }
     }
   }
 
-  nsCOMPtr<nsIURI> scriptURI;
-  rv = NS_NewURI(getter_AddRefs(scriptURI), aScriptURL, nullptr, documentURI);
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    return rv;
-  }
-
   // Data URLs are not allowed.
   nsCOMPtr<nsIPrincipal> documentPrincipal = doc->NodePrincipal();
 
-  rv = documentPrincipal->CheckMayLoad(scriptURI, true /* report */,
+  rv = documentPrincipal->CheckMayLoad(aScriptURI, true /* report */,
                                        false /* allowIfInheritsPrincipal */);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return NS_ERROR_DOM_SECURITY_ERR;
   }
 
-  nsCOMPtr<nsIURI> scopeURI;
-  rv = NS_NewURI(getter_AddRefs(scopeURI), aScope, nullptr, documentURI);
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    return NS_ERROR_DOM_SECURITY_ERR;
-  }
-
-  rv = documentPrincipal->CheckMayLoad(scopeURI, true /* report */,
+  rv = documentPrincipal->CheckMayLoad(aScopeURI, true /* report */,
                                        false /* allowIfInheritsPrinciple */);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return NS_ERROR_DOM_SECURITY_ERR;
   }
 
   nsCString cleanedScope;
-  rv = scopeURI->GetSpecIgnoringRef(cleanedScope);
+  rv = aScopeURI->GetSpecIgnoringRef(cleanedScope);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return NS_ERROR_FAILURE;
   }
 
   nsAutoCString spec;
-  rv = scriptURI->GetSpec(spec);
+  rv = aScriptURI->GetSpec(spec);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   nsCOMPtr<nsIGlobalObject> sgo = do_QueryInterface(window);
   ErrorResult result;
   nsRefPtr<Promise> promise = Promise::Create(sgo, result);
   if (result.Failed()) {
--- a/dom/workers/WorkerPrivate.cpp
+++ b/dom/workers/WorkerPrivate.cpp
@@ -980,30 +980,39 @@ private:
 
 class MessageEventRunnable MOZ_FINAL : public WorkerRunnable
 {
   JSAutoStructuredCloneBuffer mBuffer;
   nsTArray<nsCOMPtr<nsISupports> > mClonedObjects;
   uint64_t mMessagePortSerial;
   bool mToMessagePort;
 
+  // This is only used for messages dispatched to a service worker.
+  nsAutoPtr<ServiceWorkerClientInfo> mEventSource;
+
 public:
   MessageEventRunnable(WorkerPrivate* aWorkerPrivate,
                        TargetAndBusyBehavior aBehavior,
                        JSAutoStructuredCloneBuffer&& aData,
                        nsTArray<nsCOMPtr<nsISupports> >& aClonedObjects,
                        bool aToMessagePort, uint64_t aMessagePortSerial)
   : WorkerRunnable(aWorkerPrivate, aBehavior)
   , mBuffer(Move(aData))
   , mMessagePortSerial(aMessagePortSerial)
   , mToMessagePort(aToMessagePort)
   {
     mClonedObjects.SwapElements(aClonedObjects);
   }
 
+  void
+  SetMessageSource(ServiceWorkerClientInfo* aSource)
+  {
+    mEventSource = aSource;
+  }
+
   bool
   DispatchDOMEvent(JSContext* aCx, WorkerPrivate* aWorkerPrivate,
                    DOMEventTargetHelper* aTarget, bool aIsMainThread)
   {
     // Release reference to objects that were AddRef'd for
     // cloning into worker when array goes out of scope.
     nsTArray<nsCOMPtr<nsISupports>> clonedObjects;
     clonedObjects.SwapElements(mClonedObjects);
@@ -1019,16 +1028,22 @@ public:
     nsresult rv =
       event->InitMessageEvent(NS_LITERAL_STRING("message"),
                               false /* non-bubbling */,
                               true /* cancelable */,
                               messageData,
                               EmptyString(),
                               EmptyString(),
                               nullptr);
+    if (mEventSource) {
+      nsRefPtr<ServiceWorkerClient> client =
+        new ServiceWorkerWindowClient(aTarget, *mEventSource);
+      event->SetSource(client);
+    }
+
     if (NS_FAILED(rv)) {
       xpc::Throw(aCx, rv);
       return false;
     }
 
     event->SetTrusted(true);
 
     nsCOMPtr<nsIDOMEvent> domEvent = do_QueryObject(event);
@@ -3084,19 +3099,20 @@ WorkerPrivateParent<Derived>::ForgetMain
   mMainThreadObjectsForgotten = true;
 }
 
 template <class Derived>
 void
 WorkerPrivateParent<Derived>::PostMessageInternal(
                                             JSContext* aCx,
                                             JS::Handle<JS::Value> aMessage,
-                                            const Optional<Sequence<JS::Value> >& aTransferable,
+                                            const Optional<Sequence<JS::Value>>& aTransferable,
                                             bool aToMessagePort,
                                             uint64_t aMessagePortSerial,
+                                            ServiceWorkerClientInfo* aClientInfo,
                                             ErrorResult& aRv)
 {
   AssertIsOnParentThread();
 
   {
     MutexAutoLock lock(mMutex);
     if (mParentStatus > Running) {
       return;
@@ -3150,34 +3166,49 @@ WorkerPrivateParent<Derived>::PostMessag
     return;
   }
 
   nsRefPtr<MessageEventRunnable> runnable =
     new MessageEventRunnable(ParentAsWorkerPrivate(),
                              WorkerRunnable::WorkerThreadModifyBusyCount,
                              Move(buffer), clonedObjects, aToMessagePort,
                              aMessagePortSerial);
+  runnable->SetMessageSource(aClientInfo);
+
   if (!runnable->Dispatch(aCx)) {
     aRv.Throw(NS_ERROR_FAILURE);
   }
 }
 
 template <class Derived>
 void
+WorkerPrivateParent<Derived>::PostMessageToServiceWorker(
+                             JSContext* aCx, JS::Handle<JS::Value> aMessage,
+                             const Optional<Sequence<JS::Value>>& aTransferable,
+                             nsAutoPtr<ServiceWorkerClientInfo>& aClientInfo,
+                             ErrorResult& aRv)
+{
+  AssertIsOnMainThread();
+  PostMessageInternal(aCx, aMessage, aTransferable, false, 0,
+                      aClientInfo.forget(), aRv);
+}
+
+template <class Derived>
+void
 WorkerPrivateParent<Derived>::PostMessageToMessagePort(
                              JSContext* aCx,
                              uint64_t aMessagePortSerial,
                              JS::Handle<JS::Value> aMessage,
                              const Optional<Sequence<JS::Value>>& aTransferable,
                              ErrorResult& aRv)
 {
   AssertIsOnMainThread();
 
   PostMessageInternal(aCx, aMessage, aTransferable, true, aMessagePortSerial,
-                      aRv);
+                      nullptr, aRv);
 }
 
 template <class Derived>
 bool
 WorkerPrivateParent<Derived>::DispatchMessageEventToMessagePort(
                                 JSContext* aCx, uint64_t aMessagePortSerial,
                                 JSAutoStructuredCloneBuffer&& aBuffer,
                                 nsTArray<nsCOMPtr<nsISupports>>& aClonedObjects)
--- a/dom/workers/WorkerPrivate.h
+++ b/dom/workers/WorkerPrivate.h
@@ -56,16 +56,17 @@ class PrincipalInfo;
 
 struct PRThread;
 
 BEGIN_WORKERS_NAMESPACE
 
 class AutoSyncLoopHolder;
 class MessagePort;
 class SharedWorker;
+class ServiceWorkerClientInfo;
 class WorkerControlRunnable;
 class WorkerDebugger;
 class WorkerDebuggerGlobalScope;
 class WorkerGlobalScope;
 class WorkerPrivate;
 class WorkerRunnable;
 class WorkerThread;
 
@@ -216,18 +217,19 @@ private:
   bool
   TerminatePrivate(JSContext* aCx)
   {
     return NotifyPrivate(aCx, Terminating);
   }
 
   void
   PostMessageInternal(JSContext* aCx, JS::Handle<JS::Value> aMessage,
-                      const Optional<Sequence<JS::Value> >& aTransferable,
+                      const Optional<Sequence<JS::Value>>& aTransferable,
                       bool aToMessagePort, uint64_t aMessagePortSerial,
+                      ServiceWorkerClientInfo* aClientInfo,
                       ErrorResult& aRv);
 
   nsresult
   DispatchPrivate(WorkerRunnable* aRunnable, nsIEventTarget* aSyncLoopTarget);
 
 public:
   virtual JSObject*
   WrapObject(JSContext* aCx) MOZ_OVERRIDE;
@@ -322,20 +324,26 @@ public:
   void
   ForgetMainThreadObjects(nsTArray<nsCOMPtr<nsISupports> >& aDoomed);
 
   void
   PostMessage(JSContext* aCx, JS::Handle<JS::Value> aMessage,
               const Optional<Sequence<JS::Value> >& aTransferable,
               ErrorResult& aRv)
   {
-    PostMessageInternal(aCx, aMessage, aTransferable, false, 0, aRv);
+    PostMessageInternal(aCx, aMessage, aTransferable, false, 0, nullptr, aRv);
   }
 
   void
+  PostMessageToServiceWorker(JSContext* aCx, JS::Handle<JS::Value> aMessage,
+                             const Optional<Sequence<JS::Value>>& aTransferable,
+                             nsAutoPtr<ServiceWorkerClientInfo>& aClientInfo,
+                             ErrorResult& aRv);
+
+  void
   PostMessageToMessagePort(JSContext* aCx,
                            uint64_t aMessagePortSerial,
                            JS::Handle<JS::Value> aMessage,
                            const Optional<Sequence<JS::Value> >& aTransferable,
                            ErrorResult& aRv);
 
   bool
   DispatchMessageEventToMessagePort(
--- a/dom/workers/moz.build
+++ b/dom/workers/moz.build
@@ -103,17 +103,16 @@ include('/ipc/chromium/chromium-config.m
 FINAL_LIBRARY = 'xul'
 
 TEST_DIRS += [
     'test/extensions/bootstrap',
     'test/extensions/traditional',
 ]
 
 MOCHITEST_MANIFESTS += [
-    'test/fetch/mochitest.ini',
     'test/mochitest.ini',
     'test/serviceworkers/mochitest.ini',
 ]
 
 MOCHITEST_CHROME_MANIFESTS += ['test/chrome.ini']
 
 XPCSHELL_TESTS_MANIFESTS += ['test/xpcshell/xpcshell.ini']
 
deleted file mode 100644
--- a/dom/workers/test/fetch/mochitest.ini
+++ /dev/null
@@ -1,9 +0,0 @@
-[DEFAULT]
-support-files =
-  worker_interfaces.js
-  worker_test_request.js
-  worker_test_response.js
-
-[test_interfaces.html]
-[test_request.html]
-[test_response.html]
deleted file mode 100644
--- a/dom/workers/test/fetch/test_interfaces.html
+++ /dev/null
@@ -1,44 +0,0 @@
-<!--
-  Any copyright is dedicated to the Public Domain.
-  http://creativecommons.org/publicdomain/zero/1.0/
--->
-<!DOCTYPE HTML>
-<html>
-<head>
-  <title>Bug 1017613 - Test fetch API interfaces</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" style="display: none"></div>
-<pre id="test"></pre>
-<script class="testbody" type="text/javascript">
-
-  function checkEnabled() {
-    var worker = new Worker("worker_interfaces.js");
-    worker.onmessage = function(event) {
-
-      if (event.data.type == 'finish') {
-        SimpleTest.finish();
-      } else if (event.data.type == 'status') {
-        ok(event.data.status, event.data.msg);
-      }
-    }
-
-    worker.onerror = function(event) {
-      ok(false, "Worker had an error: " + event.data);
-      SimpleTest.finish();
-    };
-
-    worker.postMessage(true);
-  }
-
-  SimpleTest.waitForExplicitFinish();
-
-  checkEnabled();
-</script>
-</pre>
-</body>
-</html>
-
deleted file mode 100644
--- a/dom/workers/test/fetch/worker_interfaces.js
+++ /dev/null
@@ -1,12 +0,0 @@
-function ok(a, msg) {
-  dump("OK: " + !!a + "  =>  " + a + " " + msg + "\n");
-  postMessage({type: 'status', status: !!a, msg: a + ": " + msg });
-}
-
-onmessage = function() {
-  ok(typeof Headers === "function", "Headers should be defined");
-  ok(typeof Request === "function", "Request should be defined");
-  ok(typeof Response === "function", "Response should be defined");
-  ok(typeof fetch === "function", "fetch() should be defined");
-  postMessage({ type: 'finish' });
-}
new file mode 100644
--- /dev/null
+++ b/dom/workers/test/serviceworkers/match_all_client/match_all_client_id.html
@@ -0,0 +1,31 @@
+<!--
+  Any copyright is dedicated to the Public Domain.
+  http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>Bug 1139425 - controlled page</title>
+<script class="testbody" type="text/javascript">
+  var testWindow = parent;
+  if (opener) {
+    testWindow = opener;
+  }
+
+  window.onload = function() {
+    navigator.serviceWorker.ready.then(function(swr) {
+        swr.active.postMessage("Start");
+    });
+  }
+
+  navigator.serviceWorker.onmessage = function(msg) {
+    // worker message;
+    testWindow.postMessage(msg.data, "*");
+    window.close();
+  };
+</script>
+
+</head>
+<body>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/workers/test/serviceworkers/match_all_client_id_worker.js
@@ -0,0 +1,28 @@
+onmessage = function(e) {
+  dump("MatchAllClientIdWorker:" + e.data + "\n");
+  var id = [];
+  var iterations = 5;
+  var counter = 0;
+
+  for (var i = 0; i < iterations; i++) {
+    self.clients.matchAll().then(function(res) {
+      if (!res.length) {
+        dump("ERROR: no clients are currently controlled.\n");
+      }
+
+      client = res[0];
+      id[counter] = client.id;
+      counter++;
+      if (counter >= iterations) {
+        var response = true;
+        for (var index = 1; index < iterations; index++) {
+          if (id[0] != id[index]) {
+            response = false;
+            break;
+          }
+        }
+        client.postMessage(response);
+      }
+    });
+  }
+}
--- a/dom/workers/test/serviceworkers/match_all_clients/match_all_controlled.html
+++ b/dom/workers/test/serviceworkers/match_all_clients/match_all_controlled.html
@@ -2,16 +2,17 @@
   Any copyright is dedicated to the Public Domain.
   http://creativecommons.org/publicdomain/zero/1.0/
 -->
 <!DOCTYPE HTML>
 <html>
 <head>
   <title>Bug 1058311 - controlled page</title>
 <script class="testbody" type="text/javascript">
+  var re = /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/;
   var frameType = "none";
   var testWindow = parent;
 
   if (parent != window) {
     frameType = "nested";
   } else if (opener) {
     frameType = "auxiliary";
     testWindow = opener;
@@ -33,16 +34,19 @@
       message: msg
     };
 
     testWindow.postMessage(response, "*");
   }
 
   navigator.serviceWorker.onmessage = function(msg) {
     // worker message;
+    result = re.test(msg.data.id);
+    postResult(result, "Client id test");
+
     result = msg.data.url == window.location;
     postResult(result, "Client url test");
 
     result = msg.data.visibilityState === document.visibilityState;
     postResult(result, "Client visibility test. expected=" +document.visibilityState);
 
     result = msg.data.focused === document.hasFocus();
     postResult(result, "Client focus test. expected=" + document.hasFocus());
--- a/dom/workers/test/serviceworkers/match_all_properties_worker.js
+++ b/dom/workers/test/serviceworkers/match_all_properties_worker.js
@@ -3,16 +3,17 @@ onmessage = function(e) {
   self.clients.matchAll().then(function(res) {
     if (!res.length) {
       dump("ERROR: no clients are currently controlled.\n");
     }
 
     for (i = 0; i < res.length; i++) {
       client = res[i];
       response = {
+        id: client.id,
         url: client.url,
         visibilityState: client.visibilityState,
         focused: client.focused,
         frameType: client.frameType
       };
       client.postMessage(response);
     }
   });
--- a/dom/workers/test/serviceworkers/mochitest.ini
+++ b/dom/workers/test/serviceworkers/mochitest.ini
@@ -34,27 +34,33 @@ support-files =
   fetch/https/clonedresponse/https_test.js
   match_all_properties_worker.js
   match_all_clients/match_all_controlled.html
   test_serviceworker_interfaces.js
   serviceworker_wrapper.js
   message_receiver.html
   close_test.js
   serviceworker_not_sharedworker.js
+  match_all_client/match_all_client_id.html
+  match_all_client_id_worker.js
+  source_message_posting_worker.js
+  scope/scope_worker.js
 
 [test_unregister.html]
 [test_installation_simple.html]
 [test_fetch_event.html]
 [test_https_fetch.html]
 [test_https_fetch_cloned_response.html]
 [test_match_all.html]
 [test_install_event.html]
 [test_navigator.html]
 [test_scopes.html]
 [test_controller.html]
 [test_workerUpdate.html]
 [test_workerUnregister.html]
 [test_post_message.html]
 [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]
new file mode 100644
--- /dev/null
+++ b/dom/workers/test/serviceworkers/scope/scope_worker.js
@@ -0,0 +1,2 @@
+// This worker is used to test if calling register() without a scope argument
+// leads to scope being relative to service worker script.
new file mode 100644
--- /dev/null
+++ b/dom/workers/test/serviceworkers/source_message_posting_worker.js
@@ -0,0 +1,12 @@
+onmessage = function(e) {
+  if (!e.source) {
+    dump("ERROR: message doesn't have a source.");
+  }
+
+  // The client should be a window client
+  if (e.source instanceof  WindowClient) {
+    e.source.postMessage(e.data);
+  } else {
+    e.source.postMessage("ERROR. source is not a window client.");
+  }
+};
new file mode 100644
--- /dev/null
+++ b/dom/workers/test/serviceworkers/test_match_all_client_id.html
@@ -0,0 +1,87 @@
+<!--
+  Any copyright is dedicated to the Public Domain.
+  http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>Bug 1058311 - Test matchAll client id </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" style="display: none"></div>
+<pre id="test"></pre>
+<script class="testbody" type="text/javascript">
+  var registration;
+  var clientURL = "match_all_client/match_all_client_id.html";
+  function start() {
+    return navigator.serviceWorker.register("match_all_client_id_worker.js",
+                                            { scope: "./match_all_client/" })
+      .then((swr) => registration = swr);
+  }
+
+  function unregister() {
+    return registration.unregister().then(function(result) {
+      ok(result, "Unregister should return true.");
+    });
+  }
+
+  function getMessageListener() {
+    return new Promise(function(res, rej) {
+      window.onmessage = function(e) {
+        ok(e.data, "Same client id for multiple calls.");
+
+        if (!e.data) {
+          rej();
+          return;
+        }
+
+        info("DONE from: " + e.source);
+        res();
+      }
+    });
+  }
+
+  function testNestedWindow() {
+    var p = getMessageListener();
+
+    var content = document.getElementById("content");
+    ok(content, "Parent exists.");
+
+    iframe = document.createElement("iframe");
+
+    content.appendChild(iframe);
+    iframe.setAttribute('src', clientURL);
+
+    return p.then(() => content.removeChild(iframe));
+  }
+
+  function testAuxiliaryWindow() {
+    var p = getMessageListener();
+    var w = window.open(clientURL);
+
+    return p.then(() => w.close());
+  }
+
+  function runTest() {
+    info(window.opener == undefined);
+    start()
+      .then(testAuxiliaryWindow)
+      .then(testNestedWindow)
+      .catch(function(e) {
+        ok(false, "Some test failed with error " + e);
+      }).then(SimpleTest.finish);
+  }
+
+  SimpleTest.waitForExplicitFinish();
+  SpecialPowers.pushPrefEnv({"set": [
+    ["dom.serviceWorkers.exemptFromPerDomainMax", true],
+    ["dom.serviceWorkers.enabled", true],
+    ["dom.serviceWorkers.testing.enabled", true]
+  ]}, runTest);
+</script>
+</pre>
+</body>
+</html>
--- a/dom/workers/test/serviceworkers/test_match_all_client_properties.html
+++ b/dom/workers/test/serviceworkers/test_match_all_client_properties.html
@@ -79,15 +79,16 @@
       .then(testNestedWindow)
       .catch(function(e) {
         ok(false, "Some test failed with error " + e);
       }).then(SimpleTest.finish);
   }
 
   SimpleTest.waitForExplicitFinish();
   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/dom/workers/test/serviceworkers/test_post_message_source.html
@@ -0,0 +1,65 @@
+<!--
+  Any copyright is dedicated to the Public Domain.
+  http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>Bug 1142015 - Test service worker post message source </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" style="display: none"></div>
+<pre id="test"></pre>
+<script class="testbody" type="text/javascript">
+  var magic_value = "MAGIC_VALUE_RANDOM";
+  var registration;
+  function start() {
+    return navigator.serviceWorker.register("source_message_posting_worker.js",
+                                            { scope: "./nonexistent_scope/" })
+      .then((swr) => registration = swr);
+  }
+
+  function unregister() {
+    return registration.unregister().then(function(result) {
+      ok(result, "Unregister should return true.");
+    });
+  }
+
+
+  function testPostMessage(swr) {
+    var p = new Promise(function(res, rej) {
+      navigator.serviceWorker.onmessage = function(e) {
+        ok(e.data === magic_value, "Worker posted the correct value.");
+        res();
+      }
+    });
+
+    ok(swr.installing, "Installing worker exists.");
+    swr.installing.postMessage(magic_value);
+    return p;
+  }
+
+
+  function runTest() {
+    start()
+      .then(testPostMessage)
+      .then(unregister)
+      .catch(function(e) {
+        ok(false, "Some test failed with error " + e);
+      }).then(SimpleTest.finish);
+  }
+
+  SimpleTest.waitForExplicitFinish();
+  SpecialPowers.pushPrefEnv({"set": [
+    ["dom.serviceWorkers.exemptFromPerDomainMax", true],
+    ["dom.serviceWorkers.enabled", true],
+    ["dom.serviceWorkers.testing.enabled", true]
+  ]}, runTest);
+</script>
+</pre>
+</body>
+</html>
+
--- a/dom/workers/test/serviceworkers/test_scopes.html
+++ b/dom/workers/test/serviceworkers/test_scopes.html
@@ -24,27 +24,48 @@
     [ "worker.js", "./star*" ], // '*' has no special meaning
   ];
 
   function registerWorkers() {
     var registerArray = [];
     scriptsAndScopes.forEach(function(item) {
       registerArray.push(navigator.serviceWorker.register(item[0], { scope: item[1] }));
     });
+
+    // Check register()'s step 4 which uses script's url with "./" as the scope if no scope is passed.
+    // The other tests already check step 5.
+    registerArray.push(navigator.serviceWorker.register("scope/scope_worker.js"));
+
+    // Check that SW cannot be registered for a scope "above" the script's location.
+    registerArray.push(new Promise(function(resolve, reject) {
+      navigator.serviceWorker.register("scope/scope_worker.js", { scope: "./" })
+        .then(function() {
+          ok(false, "registration scope has to be inside service worker script scope.");
+          reject();
+        }, function() {
+          ok(true, "registration scope has to be inside service worker script scope.");
+          resolve();
+        });
+    }));
     return Promise.all(registerArray);
   }
 
   function unregisterWorkers() {
     var unregisterArray = [];
     scriptsAndScopes.forEach(function(item) {
       var p = navigator.serviceWorker.getRegistration(item[1]);
       unregisterArray.push(p.then(function(reg) {
         return reg.unregister();
       }));
     });
+
+    unregisterArray.push(navigator.serviceWorker.getRegistration("scope/").then(function (reg) {
+      return reg.unregister();
+    }));
+
     return Promise.all(unregisterArray);
   }
 
   function testScopes() {
     return new Promise(function(resolve, reject) {
       var getScope = navigator.serviceWorker.getScopeForUrl.bind(navigator.serviceWorker);
       var base = new URL(".", document.baseURI);
 
@@ -62,16 +83,17 @@
       }
 
       ok(getScope(p("sub.html")) === p("sub"), "Scope should match");
       ok(getScope(p("sub/dir.html")) === p("sub/dir.html"), "Scope should match");
       ok(getScope(p("sub/dir")) === p("sub/dir"), "Scope should match");
       ok(getScope(p("sub/dir/foo")) === p("sub/dir/"), "Scope should match");
       ok(getScope(p("sub/dir/afoo")) === p("sub/dir/a"), "Scope should match");
       ok(getScope(p("star*wars")) === p("star*"), "Scope should match");
+      ok(getScope(p("scope/some_file.html")) === p("scope/"), "Scope should match");
       fail("index.html");
       fail("sua.html");
       fail("star/a.html");
       resolve();
     });
   }
 
   function runTest() {
--- a/gfx/2d/BaseSize.h
+++ b/gfx/2d/BaseSize.h
@@ -64,16 +64,19 @@ struct BaseSize {
   }
 
   Sub operator*(T aScale) const {
     return Sub(width * aScale, height * aScale);
   }
   Sub operator/(T aScale) const {
     return Sub(width / aScale, height / aScale);
   }
+  friend Sub operator*(T aScale, const Sub& aSize) {
+    return Sub(aScale * aSize.width, aScale * aSize.height);
+  }
   void Scale(T aXScale, T aYScale) {
     width *= aXScale;
     height *= aYScale;
   }
 
   Sub operator*(const Sub& aSize) const {
     return Sub(width * aSize.width, height * aSize.height);
   }
--- a/gfx/gl/GLContext.cpp
+++ b/gfx/gl/GLContext.cpp
@@ -1684,18 +1684,19 @@ GLContext::InitExtensions()
             MarkExtensionUnsupported(ANGLE_texture_compression_dxt5);
         }
 
 #ifdef XP_MACOSX
         // Bug 1009642: On OSX Mavericks (10.9), the driver for Intel HD
         // 3000 appears to be buggy WRT updating sub-images of S3TC
         // textures with glCompressedTexSubImage2D. Works on Intel HD 4000
         // and Intel HD 5000/Iris that I tested.
+        // Bug 1124996: Appears to be the same on OSX Yosemite (10.10)
         if (nsCocoaFeatures::OSXVersionMajor() == 10 &&
-            nsCocoaFeatures::OSXVersionMinor() == 9 &&
+            nsCocoaFeatures::OSXVersionMinor() >= 9 &&
             Renderer() == GLRenderer::IntelHD3000)
         {
             MarkExtensionUnsupported(EXT_texture_compression_s3tc);
         }
 #endif
     }
 
     if (shouldDumpExts) {
--- a/gfx/gl/GLScreenBuffer.cpp
+++ b/gfx/gl/GLScreenBuffer.cpp
@@ -398,40 +398,55 @@ GLScreenBuffer::Attach(SharedSurface* su
         surf->mAttachType == SharedSurf()->mAttachType &&
         size == Size())
     {
         // Same size, same type, ready for reuse!
         mRead->Attach(surf);
     } else {
         // Else something changed, so resize:
         UniquePtr<DrawBuffer> draw;
-        bool drawOk = CreateDraw(size, &draw);  // Can be null.
+        bool drawOk = true;
+
+        /* Don't change out the draw buffer unless we resize. In the
+         * preserveDrawingBuffer:true case, prior contents of the buffer must
+         * be retained. If we're using a draw buffer, it's an MSAA buffer, so
+         * even if we copy the previous frame into the (single-sampled) read
+         * buffer, if we need to re-resolve from draw to read (as triggered by
+         * drawing), we'll need the old MSAA content to still be in the draw
+         * buffer.
+         */
+        if (!mDraw || size != Size())
+            drawOk = CreateDraw(size, &draw);  // Can be null.
 
         UniquePtr<ReadBuffer> read = CreateRead(surf);
         bool readOk = !!read;
 
         if (!drawOk || !readOk) {
             surf->UnlockProd();
 
             return false;
         }
 
-        mDraw = Move(draw);
+        if (draw)
+            mDraw = Move(draw);
+
         mRead = Move(read);
     }
 
     // Check that we're all set up.
     MOZ_ASSERT(SharedSurf() == surf);
 
     // Update the ReadBuffer mode.
     if (mGL->IsSupported(gl::GLFeature::read_buffer)) {
         BindFB(0);
         mRead->SetReadBuffer(mUserReadBufferMode);
     }
 
+    RequireBlit();
+
     return true;
 }
 
 bool
 GLScreenBuffer::Swap(const gfx::IntSize& size)
 {
     RefPtr<ShSurfHandle> newBack = mFactory->NewShSurfHandle(size);
     if (!newBack)
@@ -445,17 +460,18 @@ GLScreenBuffer::Swap(const gfx::IntSize&
     mBack = newBack;
 
     if (mBack) {
         mBack->Surf()->ProducerAcquire();
     }
 
     if (ShouldPreserveBuffer() &&
         mFront &&
-        mBack)
+        mBack &&
+        !mDraw)
     {
         auto src  = mFront->Surf();
         auto dest = mBack->Surf();
 
         //uint32_t srcPixel = ReadPixel(src);
         //uint32_t destPixel = ReadPixel(dest);
         //printf_stderr("Before: src: 0x%08x, dest: 0x%08x\n", srcPixel, destPixel);
 
--- a/gfx/layers/apz/util/APZCCallbackHelper.cpp
+++ b/gfx/layers/apz/util/APZCCallbackHelper.cpp
@@ -203,17 +203,17 @@ APZCCallbackHelper::UpdateRootFrame(nsID
 
   nsIContent* content = nsLayoutUtils::FindContentFor(aMetrics.GetScrollId());
   ScrollFrame(content, aMetrics);
 
   // The pres shell resolution is updated by the the async zoom since the
   // last paint.
   float presShellResolution = aMetrics.GetPresShellResolution()
                             * aMetrics.GetAsyncZoom().scale;
-  aUtils->SetResolutionAndScaleTo(presShellResolution, presShellResolution);
+  aUtils->SetResolutionAndScaleTo(presShellResolution);
 
   SetDisplayPortMargins(aUtils, content, aMetrics);
 }
 
 void
 APZCCallbackHelper::UpdateSubFrame(nsIContent* aContent,
                                    FrameMetrics& aMetrics)
 {
@@ -410,33 +410,20 @@ APZCCallbackHelper::ApplyCallbackTransfo
     aEvent.touches[i]->mRefPoint = ApplyCallbackTransform(
         aEvent.touches[i]->mRefPoint, aGuid, aScale, aPresShellResolution);
   }
 }
 
 nsEventStatus
 APZCCallbackHelper::DispatchWidgetEvent(WidgetGUIEvent& aEvent)
 {
-  if (!aEvent.widget)
-    return nsEventStatus_eConsumeNoDefault;
-
-  // A nested process may be capturing events.
-  if (TabParent* capturer = TabParent::GetEventCapturer()) {
-    if (capturer->TryCapture(aEvent)) {
-      // Only touch events should be captured, and touch events from a parent
-      // process should not make it here. Capture for those is done elsewhere
-      // (for gonk, in nsWindow::DispatchTouchInputViaAPZ).
-      MOZ_ASSERT(!XRE_IsParentProcess());
-
-      return nsEventStatus_eConsumeNoDefault;
-    }
+  nsEventStatus status = nsEventStatus_eConsumeNoDefault;
+  if (aEvent.widget) {
+    aEvent.widget->DispatchEvent(&aEvent, status);
   }
-  nsEventStatus status;
-  NS_ENSURE_SUCCESS(aEvent.widget->DispatchEvent(&aEvent, status),
-                    nsEventStatus_eConsumeNoDefault);
   return status;
 }
 
 nsEventStatus
 APZCCallbackHelper::DispatchSynthesizedMouseEvent(uint32_t aMsg,
                                                   uint64_t aTime,
                                                   const LayoutDevicePoint& aRefPoint,
                                                   Modifiers aModifiers,
--- a/gfx/layers/apz/util/APZEventState.cpp
+++ b/gfx/layers/apz/util/APZEventState.cpp
@@ -80,16 +80,17 @@ APZEventState::APZEventState(nsIWidget* 
                              const nsRefPtr<ContentReceivedInputBlockCallback>& aCallback)
   : mWidget(nullptr)  // initialized in constructor body
   , mActiveElementManager(new ActiveElementManager())
   , mContentReceivedInputBlockCallback(aCallback)
   , mPendingTouchPreventedResponse(false)
   , mPendingTouchPreventedBlockId(0)
   , mEndTouchIsClick(false)
   , mTouchEndCancelled(false)
+  , mActiveAPZTransforms(0)
 {
   nsresult rv;
   mWidget = do_GetWeakReference(aWidget, &rv);
   MOZ_ASSERT(NS_SUCCEEDED(rv), "APZEventState constructed with a widget that"
       " does not support weak references. APZ will NOT work!");
 
   if (!sActiveDurationMsSet) {
     Preferences::AddIntVarCache(&sActiveDurationMs,
@@ -315,37 +316,39 @@ APZEventState::ProcessAPZStateChange(con
     if (sf) {
       sf->SetTransformingByAPZ(true);
     }
     nsIScrollbarMediator* scrollbarMediator = do_QueryFrame(sf);
     if (scrollbarMediator) {
       scrollbarMediator->ScrollbarActivityStarted();
     }
 
-    if (aDocument) {
+    if (aDocument && mActiveAPZTransforms == 0) {
       nsCOMPtr<nsIDocShell> docshell(aDocument->GetDocShell());
       if (docshell && sf) {
         nsDocShell* nsdocshell = static_cast<nsDocShell*>(docshell.get());
         nsdocshell->NotifyAsyncPanZoomStarted(sf->GetScrollPositionCSSPixels());
       }
     }
+    mActiveAPZTransforms++;
     break;
   }
   case APZStateChange::TransformEnd:
   {
+    mActiveAPZTransforms--;
     nsIScrollableFrame* sf = nsLayoutUtils::FindScrollableFrameFor(aViewId);
     if (sf) {
       sf->SetTransformingByAPZ(false);
     }
     nsIScrollbarMediator* scrollbarMediator = do_QueryFrame(sf);
     if (scrollbarMediator) {
       scrollbarMediator->ScrollbarActivityStopped();
     }
 
-    if (aDocument) {
+    if (aDocument && mActiveAPZTransforms == 0) {
       nsCOMPtr<nsIDocShell> docshell(aDocument->GetDocShell());
       if (docshell && sf) {
         nsDocShell* nsdocshell = static_cast<nsDocShell*>(docshell.get());
         nsdocshell->NotifyAsyncPanZoomStopped(sf->GetScrollPositionCSSPixels());
       }
     }
     break;
   }
--- a/gfx/layers/apz/util/APZEventState.h
+++ b/gfx/layers/apz/util/APZEventState.h
@@ -82,14 +82,15 @@ private:
   nsWeakPtr mWidget;
   nsRefPtr<ActiveElementManager> mActiveElementManager;
   nsRefPtr<ContentReceivedInputBlockCallback> mContentReceivedInputBlockCallback;
   bool mPendingTouchPreventedResponse;
   ScrollableLayerGuid mPendingTouchPreventedGuid;
   uint64_t mPendingTouchPreventedBlockId;
   bool mEndTouchIsClick;
   bool mTouchEndCancelled;
+  int mActiveAPZTransforms;
 };
 
 }
 }
 
 #endif /* mozilla_layers_APZEventState_h */
--- a/gfx/layers/basic/BasicCompositor.cpp
+++ b/gfx/layers/basic/BasicCompositor.cpp
@@ -12,18 +12,23 @@
 #include "gfx2DGlue.h"
 #include "mozilla/gfx/2D.h"
 #include "mozilla/gfx/Helpers.h"
 #include "gfxUtils.h"
 #include "YCbCrUtils.h"
 #include <algorithm>
 #include "ImageContainer.h"
 #include "gfxPrefs.h"
+#ifdef MOZ_ENABLE_SKIA
 #include "skia/SkCanvas.h"              // for SkCanvas
 #include "skia/SkBitmapDevice.h"        // for SkBitmapDevice
+#else
+#define PIXMAN_DONT_DEFINE_STDINT
+#include "pixman.h"                     // for pixman_f_transform, etc
+#endif
 
 namespace mozilla {
 using namespace mozilla::gfx;
 
 namespace layers {
 
 class DataTextureSourceBasic : public DataTextureSource
                              , public TextureSourceBasic
@@ -170,16 +175,17 @@ DrawSurfaceWithTextureCoords(DrawTarget 
   // Only use REPEAT if aTextureCoords is outside (0, 0, 1, 1).
   gfx::Rect unitRect(0, 0, 1, 1);
   ExtendMode mode = unitRect.Contains(aTextureCoords) ? ExtendMode::CLAMP : ExtendMode::REPEAT;
 
   FillRectWithMask(aDest, aDestRect, aSource, aFilter, DrawOptions(aOpacity),
                    mode, aMask, aMaskTransform, &matrix);
 }
 
+#ifdef MOZ_ENABLE_SKIA
 static SkMatrix
 Matrix3DToSkia(const gfx3DMatrix& aMatrix)
 {
   SkMatrix transform;
   transform.setAll(aMatrix._11,
                    aMatrix._21,
                    aMatrix._41,
                    aMatrix._12,
@@ -188,20 +194,20 @@ Matrix3DToSkia(const gfx3DMatrix& aMatri
                    aMatrix._14,
                    aMatrix._24,
                    aMatrix._44);
 
   return transform;
 }
 
 static void
-SkiaTransform(DataSourceSurface* aDest,
-              DataSourceSurface* aSource,
-              const gfx3DMatrix& aTransform,
-              const Point& aDestOffset)
+Transform(DataSourceSurface* aDest,
+          DataSourceSurface* aSource,
+          const gfx3DMatrix& aTransform,
+          const Point& aDestOffset)
 {
   if (aTransform.IsSingular()) {
     return;
   }
 
   IntSize destSize = aDest->GetSize();
   SkImageInfo destInfo = SkImageInfo::Make(destSize.width,
                                            destSize.height,
@@ -227,16 +233,88 @@ SkiaTransform(DataSourceSurface* aDest,
 
   SkPaint paint;
   paint.setXfermodeMode(SkXfermode::kSrc_Mode);
   paint.setAntiAlias(true);
   paint.setFilterLevel(SkPaint::kLow_FilterLevel);
   SkRect destRect = SkRect::MakeXYWH(0, 0, srcSize.width, srcSize.height);
   destCanvas.drawBitmapRectToRect(src, nullptr, destRect, &paint);
 }
+#else
+static pixman_transform
+Matrix3DToPixman(const gfx3DMatrix& aMatrix)
+{
+  pixman_f_transform transform;
+
+  transform.m[0][0] = aMatrix._11;
+  transform.m[0][1] = aMatrix._21;
+  transform.m[0][2] = aMatrix._41;
+  transform.m[1][0] = aMatrix._12;
+  transform.m[1][1] = aMatrix._22;
+  transform.m[1][2] = aMatrix._42;
+  transform.m[2][0] = aMatrix._14;
+  transform.m[2][1] = aMatrix._24;
+  transform.m[2][2] = aMatrix._44;
+
+  pixman_transform result;
+  pixman_transform_from_pixman_f_transform(&result, &transform);
+
+  return result;
+}
+
+static void
+Transform(DataSourceSurface* aDest,
+          DataSourceSurface* aSource,
+          const gfx3DMatrix& aTransform,
+          const Point& aDestOffset)
+{
+  IntSize destSize = aDest->GetSize();
+  pixman_image_t* dest = pixman_image_create_bits(PIXMAN_a8r8g8b8,
+                                                  destSize.width,
+                                                  destSize.height,
+                                                  (uint32_t*)aDest->GetData(),
+                                                  aDest->Stride());
+
+  IntSize srcSize = aSource->GetSize();
+  pixman_image_t* src = pixman_image_create_bits(PIXMAN_a8r8g8b8,
+                                                 srcSize.width,
+                                                 srcSize.height,
+                                                 (uint32_t*)aSource->GetData(),
+                                                 aSource->Stride());
+
+  NS_ABORT_IF_FALSE(src && dest, "Failed to create pixman images?");
+
+  pixman_transform pixTransform = Matrix3DToPixman(aTransform);
+  pixman_transform pixTransformInverted;
+
+  // If the transform is singular then nothing would be drawn anyway, return here
+  if (!pixman_transform_invert(&pixTransformInverted, &pixTransform)) {
+    pixman_image_unref(dest);
+    pixman_image_unref(src);
+    return;
+  }
+  pixman_image_set_transform(src, &pixTransformInverted);
+
+  pixman_image_composite32(PIXMAN_OP_SRC,
+                           src,
+                           nullptr,
+                           dest,
+                           aDestOffset.x,
+                           aDestOffset.y,
+                           0,
+                           0,
+                           0,
+                           0,
+                           destSize.width,
+                           destSize.height);
+
+  pixman_image_unref(dest);
+  pixman_image_unref(src);
+}
+#endif
 
 static inline IntRect
 RoundOut(Rect r)
 {
   r.RoundOut();
   return IntRect(r.x, r.y, r.width, r.height);
 }
 
@@ -366,22 +444,26 @@ BasicCompositor::DrawQuad(const gfx::Rec
   }
 
   if (!aTransform.Is2D()) {
     dest->Flush();
 
     RefPtr<SourceSurface> snapshot = dest->Snapshot();
     RefPtr<DataSourceSurface> source = snapshot->GetDataSurface();
     RefPtr<DataSourceSurface> temp =
-      Factory::CreateDataSourceSurface(RoundOut(transformBounds).Size(), SurfaceFormat::B8G8R8A8, true);
+      Factory::CreateDataSourceSurface(RoundOut(transformBounds).Size(), SurfaceFormat::B8G8R8A8
+#ifdef MOZ_ENABLE_SKIA
+        , true
+#endif
+        );
     if (NS_WARN_IF(!temp)) {
       return;
     }
 
-    SkiaTransform(temp, source, new3DTransform, transformBounds.TopLeft());
+    Transform(temp, source, new3DTransform, transformBounds.TopLeft());
 
     transformBounds.MoveTo(0, 0);
     buffer->DrawSurface(temp, transformBounds, transformBounds);
   }
 
   buffer->PopClip();
 }
 
--- a/gfx/layers/basic/BasicLayerManager.cpp
+++ b/gfx/layers/basic/BasicLayerManager.cpp
@@ -41,18 +41,23 @@
 #include "nsAutoPtr.h"                  // for nsRefPtr
 #include "nsCOMPtr.h"                   // for already_AddRefed
 #include "nsDebug.h"                    // for NS_ASSERTION, etc
 #include "nsISupportsImpl.h"            // for gfxContext::Release, etc
 #include "nsPoint.h"                    // for nsIntPoint
 #include "nsRect.h"                     // for nsIntRect
 #include "nsRegion.h"                   // for nsIntRegion, etc
 #include "nsTArray.h"                   // for nsAutoTArray
+#ifdef MOZ_ENABLE_SKIA
 #include "skia/SkCanvas.h"              // for SkCanvas
 #include "skia/SkBitmapDevice.h"        // for SkBitmapDevice
+#else
+#define PIXMAN_DONT_DEFINE_STDINT
+#include "pixman.h"                     // for pixman_f_transform, etc
+#endif
 class nsIWidget;
 
 namespace mozilla {
 namespace layers {
 
 using namespace mozilla::gfx;
 
 /**
@@ -601,16 +606,17 @@ void
 BasicLayerManager::SetRoot(Layer* aLayer)
 {
   NS_ASSERTION(aLayer, "Root can't be null");
   NS_ASSERTION(aLayer->Manager() == this, "Wrong manager");
   NS_ASSERTION(InConstruction(), "Only allowed in construction phase");
   mRoot = aLayer;
 }
 
+#ifdef MOZ_ENABLE_SKIA
 static SkMatrix
 BasicLayerManager_Matrix3DToSkia(const gfx3DMatrix& aMatrix)
 {
   SkMatrix transform;
   transform.setAll(aMatrix._11,
                    aMatrix._21,
                    aMatrix._41,
                    aMatrix._12,
@@ -619,20 +625,20 @@ BasicLayerManager_Matrix3DToSkia(const g
                    aMatrix._14,
                    aMatrix._24,
                    aMatrix._44);
 
   return transform;
 }
 
 static void
-SkiaTransform(const gfxImageSurface* aDest,
-              RefPtr<DataSourceSurface> aSrc,
-              const gfx3DMatrix& aTransform,
-              gfxPoint aDestOffset)
+Transform(const gfxImageSurface* aDest,
+          RefPtr<DataSourceSurface> aSrc,
+          const gfx3DMatrix& aTransform,
+          gfxPoint aDestOffset)
 {
   if (aTransform.IsSingular()) {
     return;
   }
 
   IntSize destSize = ToIntSize(aDest->GetSize());
   SkImageInfo destInfo = SkImageInfo::Make(destSize.width,
                                            destSize.height,
@@ -658,16 +664,88 @@ SkiaTransform(const gfxImageSurface* aDe
 
   SkPaint paint;
   paint.setXfermodeMode(SkXfermode::kSrc_Mode);
   paint.setAntiAlias(true);
   paint.setFilterLevel(SkPaint::kLow_FilterLevel);
   SkRect destRect = SkRect::MakeXYWH(0, 0, srcSize.width, srcSize.height);
   destCanvas.drawBitmapRectToRect(src, nullptr, destRect, &paint);
 }
+#else
+static pixman_transform
+BasicLayerManager_Matrix3DToPixman(const gfx3DMatrix& aMatrix)
+{
+  pixman_f_transform transform;
+
+  transform.m[0][0] = aMatrix._11;
+  transform.m[0][1] = aMatrix._21;
+  transform.m[0][2] = aMatrix._41;
+  transform.m[1][0] = aMatrix._12;
+  transform.m[1][1] = aMatrix._22;
+  transform.m[1][2] = aMatrix._42;
+  transform.m[2][0] = aMatrix._14;
+  transform.m[2][1] = aMatrix._24;
+  transform.m[2][2] = aMatrix._44;
+
+  pixman_transform result;
+  pixman_transform_from_pixman_f_transform(&result, &transform);
+
+  return result;
+}
+
+static void
+Transform(const gfxImageSurface* aDest,
+          RefPtr<DataSourceSurface> aSrc,
+          const gfx3DMatrix& aTransform,
+          gfxPoint aDestOffset)
+{
+  IntSize destSize = ToIntSize(aDest->GetSize());
+  pixman_image_t* dest = pixman_image_create_bits(aDest->Format() == gfxImageFormat::ARGB32 ? PIXMAN_a8r8g8b8 : PIXMAN_x8r8g8b8,
+                                                  destSize.width,
+                                                  destSize.height,
+                                                  (uint32_t*)aDest->Data(),
+                                                  aDest->Stride());
+
+  IntSize srcSize = aSrc->GetSize();
+  pixman_image_t* src = pixman_image_create_bits(aSrc->GetFormat() == SurfaceFormat::B8G8R8A8 ? PIXMAN_a8r8g8b8 : PIXMAN_x8r8g8b8,
+                                                 srcSize.width,
+                                                 srcSize.height,
+                                                 (uint32_t*)aSrc->GetData(),
+                                                 aSrc->Stride());
+
+  NS_ABORT_IF_FALSE(src && dest, "Failed to create pixman images?");
+
+  pixman_transform pixTransform = BasicLayerManager_Matrix3DToPixman(aTransform);
+  pixman_transform pixTransformInverted;
+
+  // If the transform is singular then nothing would be drawn anyway, return here
+  if (!pixman_transform_invert(&pixTransformInverted, &pixTransform)) {
+    pixman_image_unref(dest);
+    pixman_image_unref(src);
+    return;
+  }
+  pixman_image_set_transform(src, &pixTransformInverted);
+
+  pixman_image_composite32(PIXMAN_OP_SRC,
+                           src,
+                           nullptr,
+                           dest,
+                           aDestOffset.x,
+                           aDestOffset.y,
+                           0,
+                           0,
+                           0,
+                           0,
+                           destSize.width,
+                           destSize.height);
+
+  pixman_image_unref(dest);
+  pixman_image_unref(src);
+}
+#endif
 
 /**
  * Transform a surface using a gfx3DMatrix and blit to the destination if
  * it is efficient to do so.
  *
  * @param aSource       Source surface.
  * @param aDest         Desintation context.
  * @param aBounds       Area represented by aSource.
@@ -699,17 +777,17 @@ Transform3D(RefPtr<SourceSurface> aSourc
                                                                        aDestRect.height),
                                                             gfxImageFormat::ARGB32);
   gfxPoint offset = aDestRect.TopLeft();
 
   // Include a translation to the correct origin.
   gfx3DMatrix translation = gfx3DMatrix::Translation(aBounds.x, aBounds.y, 0);
 
   // Transform the content and offset it such that the content begins at the origin.
-  SkiaTransform(destImage, aSource->GetDataSurface(), translation * aTransform, offset);
+  Transform(destImage, aSource->GetDataSurface(), translation * aTransform, offset);
 
   // If we haven't actually drawn to aDest then return our temporary image so
   // that the caller can do this.
   return destImage.forget();
 }
 
 void
 BasicLayerManager::PaintSelfOrChildren(PaintLayerContext& aPaintContext,
--- a/gfx/thebes/VsyncSource.cpp
+++ b/gfx/thebes/VsyncSource.cpp
@@ -119,16 +119,20 @@ VsyncSource::Display::UpdateVsyncStatus(
     enableVsync = !mCompositorVsyncDispatchers.IsEmpty() || mRefreshTimerNeedsVsync;
   }
 
   if (enableVsync) {
     EnableVsync();
   } else {
     DisableVsync();
   }
+
+  if (IsVsyncEnabled() != enableVsync) {
+    NS_WARNING("Vsync status did not change.");
+  }
 }
 
 nsRefPtr<RefreshTimerVsyncDispatcher>
 VsyncSource::Display::GetRefreshTimerVsyncDispatcher()
 {
   return mRefreshTimerVsyncDispatcher;
 }
 
--- a/gfx/thebes/gfxPlatformMac.cpp
+++ b/gfx/thebes/gfxPlatformMac.cpp
@@ -464,28 +464,41 @@ public:
         return;
       }
 
       // Create a display link capable of being used with all active displays
       // TODO: See if we need to create an active DisplayLink for each monitor in multi-monitor
       // situations. According to the docs, it is compatible with all displays running on the computer
       // But if we have different monitors at different display rates, we may hit issues.
       if (CVDisplayLinkCreateWithActiveCGDisplays(&mDisplayLink) != kCVReturnSuccess) {
-        NS_WARNING("Could not create a display link, returning");
-        return;
+        NS_WARNING("Could not create a display link with all active displays. Falling back to main display\n");
+        CVDisplayLinkRelease(mDisplayLink);
+
+        // bug 1142708 - When coming back from sleep, there may be no active displays ready yet,
+        // even if listening for the kIOMessageSystemHasPoweredOn event from OS X sleep notifications.
+        // Active displays are those that are drawable.
+        // In these cases, default back to the main display to try to get a vsync event.
+        // The alternative would be to keep polling the CGActiveDisplayList for the displays to be ready.
+        if (CVDisplayLinkCreateWithCGDisplay(CGMainDisplayID(), &mDisplayLink) != kCVReturnSuccess) {
+          MOZ_CRASH("Could not create a CVDisplayLink with either active displays or the main display");
+        }
+        NS_WARNING("Using the CVDisplayLink from the main display\n");
       }
 
       if (CVDisplayLinkSetOutputCallback(mDisplayLink, &VsyncCallback, this) != kCVReturnSuccess) {
         NS_WARNING("Could not set displaylink output callback");
+        CVDisplayLinkRelease(mDisplayLink);
+        mDisplayLink = nullptr;
         return;
       }
 
       mPreviousTimestamp = TimeStamp::Now();
       if (CVDisplayLinkStart(mDisplayLink) != kCVReturnSuccess) {
         NS_WARNING("Could not activate the display link");
+        CVDisplayLinkRelease(mDisplayLink);
         mDisplayLink = nullptr;
       }
     }
 
     virtual void DisableVsync() MOZ_OVERRIDE
     {
       MOZ_ASSERT(NS_IsMainThread());
       if (!IsVsyncEnabled()) {
--- a/image/test/crashtests/crashtests.list
+++ b/image/test/crashtests/crashtests.list
@@ -41,9 +41,9 @@ load truncated-second-frame.png # bug 86
 # This icon's size is such that it leads to multiple writes to the PNG decoder
 # after we've gotten our size.
 load multiple-png-hassize.ico
 
 # Bug 856615
 # Asserts in the debug build
 load 856616.gif
 
-load 944353.jpg
+skip-if(AddressSanitizer) load 944353.jpg
--- a/js/public/Utility.h
+++ b/js/public/Utility.h
@@ -90,19 +90,32 @@ static MOZ_NEVER_INLINE void js_failedAl
     do \
     { \
         if (++OOM_counter > OOM_maxAllocations) { \
             JS_OOM_CALL_BP_FUNC();\
             return false; \
         } \
     } while (0)
 
+namespace js {
+namespace oom {
+static inline bool ShouldFailWithOOM()
+{
+    if (++OOM_counter > OOM_maxAllocations) {
+        JS_OOM_CALL_BP_FUNC();
+        return true;
+    }
+    return false;
+}
+}
+}
 # else
 #  define JS_OOM_POSSIBLY_FAIL() do {} while(0)
 #  define JS_OOM_POSSIBLY_FAIL_BOOL() do {} while(0)
+namespace js { namespace oom { static inline bool ShouldFailWithOOM() { return false; } } }
 # endif /* DEBUG || JS_OOM_BREAKPOINT */
 
 static inline void* js_malloc(size_t bytes)
 {
     JS_OOM_POSSIBLY_FAIL();
     return malloc(bytes);
 }
 
--- a/js/src/asmjs/AsmJSValidate.cpp
+++ b/js/src/asmjs/AsmJSValidate.cpp
@@ -5929,20 +5929,20 @@ CheckSimdOperationCall(FunctionCompiler 
       case AsmJSSimdOperation_abs:
         return CheckSimdUnary(f, call, opType, MSimdUnaryArith::abs, def, type);
       case AsmJSSimdOperation_neg:
         return CheckSimdUnary(f, call, opType, MSimdUnaryArith::neg, def, type);
       case AsmJSSimdOperation_not:
         return CheckSimdUnary(f, call, opType, MSimdUnaryArith::not_, def, type);
       case AsmJSSimdOperation_sqrt:
         return CheckSimdUnary(f, call, opType, MSimdUnaryArith::sqrt, def, type);
-      case AsmJSSimdOperation_reciprocal:
-        return CheckSimdUnary(f, call, opType, MSimdUnaryArith::reciprocal, def, type);
-      case AsmJSSimdOperation_reciprocalSqrt:
-        return CheckSimdUnary(f, call, opType, MSimdUnaryArith::reciprocalSqrt, def, type);
+      case AsmJSSimdOperation_reciprocalApproximation:
+        return CheckSimdUnary(f, call, opType, MSimdUnaryArith::reciprocalApproximation, def, type);
+      case AsmJSSimdOperation_reciprocalSqrtApproximation:
+        return CheckSimdUnary(f, call, opType, MSimdUnaryArith::reciprocalSqrtApproximation, def, type);
 
       case AsmJSSimdOperation_swizzle:
         return CheckSimdSwizzle(f, call, opType, def, type);
       case AsmJSSimdOperation_shuffle:
         return CheckSimdShuffle(f, call, opType, def, type);
 
       case AsmJSSimdOperation_load:
         return CheckSimdLoad(f, call, opType, 4, def, type);
--- a/js/src/builtin/SIMD.cpp
+++ b/js/src/builtin/SIMD.cpp
@@ -527,21 +527,21 @@ template<typename T>
 struct Neg {
     static inline T apply(T x) { return -1 * x; }
 };
 template<typename T>
 struct Not {
     static inline T apply(T x) { return ~x; }
 };
 template<typename T>
-struct Rec {
+struct RecApprox {
     static inline T apply(T x) { return 1 / x; }
 };
 template<typename T>
-struct RecSqrt {
+struct RecSqrtApprox {
     static inline T apply(T x) { return sqrt(1 / x); }
 };
 template<typename T>
 struct Sqrt {
     static inline T apply(T x) { return sqrt(x); }
 };
 
 // Binary SIMD operators
@@ -743,19 +743,19 @@ Swizzle(JSContext *cx, unsigned argc, Va
     typedef typename V::Elem Elem;
 
     CallArgs args = CallArgsFromVp(argc, vp);
     if (args.length() != (V::lanes + 1) || !IsVectorObject<V>(args[0]))
         return ErrorBadArgs(cx);
 
     uint32_t lanes[V::lanes];
     for (unsigned i = 0; i < V::lanes; i++) {
-        int32_t lane = -1;
-        if (!ToInt32(cx, args[i + 1], &lane))
-            return false;
+        if (!args[i + 1].isInt32())
+            return ErrorBadArgs(cx);
+        int32_t lane = args[i + 1].toInt32();
         if (lane < 0 || uint32_t(lane) >= V::lanes)
             return ErrorBadArgs(cx);
         lanes[i] = uint32_t(lane);
     }
 
     Elem *val = TypedObjectMemory<Elem *>(args[0]);
 
     Elem result[V::lanes];
@@ -772,19 +772,19 @@ Shuffle(JSContext *cx, unsigned argc, Va
     typedef typename V::Elem Elem;
 
     CallArgs args = CallArgsFromVp(argc, vp);
     if (args.length() != (V::lanes + 2) || !IsVectorObject<V>(args[0]) || !IsVectorObject<V>(args[1]))
         return ErrorBadArgs(cx);
 
     uint32_t lanes[V::lanes];
     for (unsigned i = 0; i < V::lanes; i++) {
-        int32_t lane = -1;
-        if (!ToInt32(cx, args[i + 2], &lane))
-            return false;
+        if (!args[i + 2].isInt32())
+            return ErrorBadArgs(cx);
+        int32_t lane = args[i + 2].toInt32();
         if (lane < 0 || uint32_t(lane) >= (2 * V::lanes))
             return ErrorBadArgs(cx);
         lanes[i] = uint32_t(lane);
     }
 
     Elem *lhs = TypedObjectMemory<Elem *>(args[0]);
     Elem *rhs = TypedObjectMemory<Elem *>(args[1]);
 
--- a/js/src/builtin/SIMD.h
+++ b/js/src/builtin/SIMD.h
@@ -24,18 +24,18 @@
   V(abs, (UnaryFunc<Float32x4, Abs, Float32x4>), 1, 0)                              \
   V(check, (UnaryFunc<Float32x4, Identity, Float32x4>), 1, 0)                       \
   V(fromFloat64x2, (FuncConvert<Float64x2, Float32x4> ), 1, 0)                      \
   V(fromFloat64x2Bits, (FuncConvertBits<Float64x2, Float32x4>), 1, 0)               \
   V(fromInt32x4, (FuncConvert<Int32x4, Float32x4> ), 1, 0)                          \
   V(fromInt32x4Bits, (FuncConvertBits<Int32x4, Float32x4>), 1, 0)                   \
   V(neg, (UnaryFunc<Float32x4, Neg, Float32x4>), 1, 0)                              \
   V(not, (CoercedUnaryFunc<Float32x4, Int32x4, Not, Float32x4>), 1, 0)              \
-  V(reciprocal, (UnaryFunc<Float32x4, Rec, Float32x4>), 1, 0)                       \
-  V(reciprocalSqrt, (UnaryFunc<Float32x4, RecSqrt, Float32x4>), 1, 0)               \
+  V(reciprocalApproximation, (UnaryFunc<Float32x4, RecApprox, Float32x4>), 1, 0)    \
+  V(reciprocalSqrtApproximation, (UnaryFunc<Float32x4, RecSqrtApprox, Float32x4>), 1, 0) \
   V(splat, (FuncSplat<Float32x4>), 1, 0)                                            \
   V(sqrt, (UnaryFunc<Float32x4, Sqrt, Float32x4>), 1, 0)
 
 #define FLOAT32X4_BINARY_FUNCTION_LIST(V)                                           \
   V(add, (BinaryFunc<Float32x4, Add, Float32x4>), 2, 0)                             \
   V(and, (CoercedBinaryFunc<Float32x4, Int32x4, And, Float32x4>), 2, 0)             \
   V(div, (BinaryFunc<Float32x4, Div, Float32x4>), 2, 0)                             \
   V(equal, (CompareFunc<Float32x4, Equal>), 2, 0)                                   \
@@ -83,18 +83,18 @@
 #define FLOAT64X2_UNARY_FUNCTION_LIST(V)                                            \
   V(abs, (UnaryFunc<Float64x2, Abs, Float64x2>), 1, 0)                              \
   V(check, (UnaryFunc<Float64x2, Identity, Float64x2>), 1, 0)                       \
   V(fromFloat32x4, (FuncConvert<Float32x4, Float64x2> ), 1, 0)                      \
   V(fromFloat32x4Bits, (FuncConvertBits<Float32x4, Float64x2>), 1, 0)               \
   V(fromInt32x4, (FuncConvert<Int32x4, Float64x2> ), 1, 0)                          \
   V(fromInt32x4Bits, (FuncConvertBits<Int32x4, Float64x2>), 1, 0)                   \
   V(neg, (UnaryFunc<Float64x2, Neg, Float64x2>), 1, 0)                              \
-  V(reciprocal, (UnaryFunc<Float64x2, Rec, Float64x2>), 1, 0)                       \
-  V(reciprocalSqrt, (UnaryFunc<Float64x2, RecSqrt, Float64x2>), 1, 0)               \
+  V(reciprocalApproximation, (UnaryFunc<Float64x2, RecApprox, Float64x2>), 1, 0)    \
+  V(reciprocalSqrtApproximation, (UnaryFunc<Float64x2, RecSqrtApprox, Float64x2>), 1, 0) \
   V(splat, (FuncSplat<Float64x2>), 1, 0)                                            \
   V(sqrt, (UnaryFunc<Float64x2, Sqrt, Float64x2>), 1, 0)
 
 #define FLOAT64X2_BINARY_FUNCTION_LIST(V)                                           \
   V(add, (BinaryFunc<Float64x2, Add, Float64x2>), 2, 0)                             \
   V(div, (BinaryFunc<Float64x2, Div, Float64x2>), 2, 0)                             \
   V(equal, (CompareFunc<Float64x2, Equal>), 2, 0)                                   \
   V(greaterThan, (CompareFunc<Float64x2, GreaterThan>), 2, 0)                       \
@@ -193,18 +193,18 @@
 #define FOREACH_INT32X4_SIMD_OP(_)   \
     CONVERSION_INT32X4_SIMD_OP(_)    \
     _(shiftLeftByScalar)             \
     _(shiftRightArithmeticByScalar)  \
     _(shiftRightLogicalByScalar)
 #define UNARY_ARITH_FLOAT32X4_SIMD_OP(_) \
     _(abs)                           \
     _(sqrt)                          \
-    _(reciprocal)                    \
-    _(reciprocalSqrt)
+    _(reciprocalApproximation)       \
+    _(reciprocalSqrtApproximation)
 #define BINARY_ARITH_FLOAT32X4_SIMD_OP(_) \
     _(div)                           \
     _(max)                           \
     _(min)                           \
     _(maxNum)                        \
     _(minNum)
 #define FOREACH_FLOAT32X4_SIMD_OP(_) \
     UNARY_ARITH_FLOAT32X4_SIMD_OP(_) \
--- a/js/src/ctypes/CTypes.cpp
+++ b/js/src/ctypes/CTypes.cpp
@@ -5336,16 +5336,21 @@ StructType::AddressOfField(JSContext* cx
     return false;
   }
 
   if (args.length() != 1) {
     JS_ReportError(cx, "addressOfField takes one argument");
     return false;
   }
 
+  if (!args[0].isString()) {
+    JS_ReportError(cx, "argument must be a string");
+    return false;
+  }
+
   JSFlatString *str = JS_FlattenString(cx, args[0].toString());
   if (!str)
     return false;
 
   const FieldInfo* field = LookupField(cx, typeObj, str);
   if (!field)
     return false;
 
--- a/js/src/doc/Debugger/Conventions.md
+++ b/js/src/doc/Debugger/Conventions.md
@@ -131,16 +131,22 @@ resumption value has one of the followin
 
 If a function that would normally return a resumption value to indicate
 how the debuggee should continue instead throws an exception, we never
 propagate such an exception to the debuggee; instead, we call the
 associated `Debugger` instance's `uncaughtExceptionHook` property, as
 described below.
 
 
+## Timestamps
+
+Timestamps are expressed in units of microseconds since the epoch (midnight,
+January 1st, 1970).
+
+
 ## The `Debugger.DebuggeeWouldRun` Exception
 
 Some debugger operations that appear to simply inspect the debuggee's state
 may actually cause debuggee code to run. For example, reading a variable
 might run a getter function on the global or on a `with` expression's
 operand; and getting an object's property descriptor will run a handler
 trap if the object is a proxy. To protect the debugger's integrity, only
 methods whose stated purpose is to run debuggee code can do so. These
--- a/js/src/doc/Debugger/Debugger.Memory.md
+++ b/js/src/doc/Debugger/Debugger.Memory.md
@@ -80,22 +80,114 @@ following accessor properties from its p
     [`Debugger.Object.prototype.allocationSite`][allocation-site] accessor
     property.
 
 <code id='max-alloc-log'>maxAllocationsLogLength</code>
 :   The maximum number of allocation sites to accumulate in the allocations log
     at a time. This accessor can be both fetched and stored to. Its default
     value is `5000`.
 
-<code id='allocationsLogOverflowed'>allocationsLogOverflowed</a>
+<code id='allocationsLogOverflowed'>allocationsLogOverflowed</code>
 :   Returns `true` if there have been more than
     [`maxAllocationsLogLength`][#max-alloc-log] allocations since the last time
     [`drainAllocationsLog`][#drain-alloc-log] was called and some data has been
     lost. Returns `false` otherwise.
 
+Debugger.Memory Handler Functions
+---------------------------------
+
+Similar to [`Debugger`'s handler functions][debugger], `Debugger.Memory`
+inherits accessor properties that store handler functions for SpiderMonkey to
+call when given events occur in debuggee code.
+
+Unlike `Debugger`'s hooks, `Debugger.Memory`'s handlers' return values are not
+significant, and are ignored. The handler functions receive the
+`Debugger.Memory`'s owning `Debugger` instance as their `this` value. The owning
+`Debugger`'s `uncaughtExceptionHandler` is still fired for errors thrown in
+`Debugger.Memory` hooks.
+
+On a new `Debugger.Memory` instance, each of these properties is initially
+`undefined`. Any value assigned to a debugging handler must be either a function
+or `undefined`; otherwise a `TypeError` is thrown.
+
+Handler functions run in the same thread in which the event occurred.
+They run in the compartment to which they belong, not in a debuggee
+compartment.
+
+<code>onGarbageCollection(<i>statistics</i>)</code>
+:   A garbage collection cycle spanning one or more debuggees has just been
+    completed.
+
+    The *statistics* parameter is an object containing information about the GC
+    cycle. It has the following properties:
+
+    `collections`
+    :   The `collections` property's value is an array. Because SpiderMonkey's
+        collector is incremental, a full collection cycle may consist of
+        multiple discrete collection slices with the JS mutator running
+        interleaved. For each collection slice that occurred, there is an entry
+        in the `collections` array with the following form:
+
+        <pre class='language-js'><code>
+        {
+          "startTimestamp": <i>timestamp</i>,
+          "endTimestamp": <i>timestamp</i>,
+        }
+        </code></pre>
+
+        Here the *timestamp* values are [timestamps][] of the GC slice's start
+        and end events.
+
+    `reason`
+    :   A very short string describing th reason why the collection was
+        triggered. Known values include the following:
+
+        * "API"
+        * "EAGER_ALLOC_TRIGGER"
+        * "DESTROY_RUNTIME"
+        * "DESTROY_CONTEXT"
+        * "LAST_DITCH"
+        * "TOO_MUCH_MALLOC"
+        * "ALLOC_TRIGGER"
+        * "DEBUG_GC"
+        * "COMPARTMENT_REVIVED"
+        * "RESET"
+        * "OUT_OF_NURSERY"
+        * "EVICT_NURSERY"
+        * "FULL_STORE_BUFFER"
+        * "SHARED_MEMORY_LIMIT"
+        * "PERIODIC_FULL_GC"
+        * "INCREMENTAL_TOO_SLOW"
+        * "DOM_WINDOW_UTILS"
+        * "COMPONENT_UTILS"
+        * "MEM_PRESSURE"
+        * "CC_WAITING"
+        * "CC_FORCED"
+        * "LOAD_END"
+        * "POST_COMPARTMENT"
+        * "PAGE_HIDE"
+        * "NSJSCONTEXT_DESTROY"
+        * "SET_NEW_DOCUMENT"
+        * "SET_DOC_SHELL"
+        * "DOM_UTILS"
+        * "DOM_IPC"
+        * "DOM_WORKER"
+        * "INTER_SLICE_GC"
+        * "REFRESH_FRAME"
+        * "FULL_GC_TIMER"
+        * "SHUTDOWN_CC"
+        * "FINISH_LARGE_EVALUATE"
+        * "USER_INACTIVE"
+
+    `nonincrementalReason`
+    :   If SpiderMonkey's collector determined it could not incrementally
+        collect garbage, and had to do a full GC all at once, this is a short
+        string describing the reason it determined the full GC was necessary.
+        Otherwise, `null` is returned.
+
 Function Properties of the `Debugger.Memory.prototype` Object
 -------------------------------------------------------------
 
 <code id='drain-alloc-log'>drainAllocationsLog()</code>
 :   When `trackingAllocationSites` is `true`, this method returns an array of
     recent `Object` allocations within the set of debuggees. *Recent* is
     defined as the `maxAllocationsLogLength` most recent `Object` allocations
     since the last call to `drainAllocationsLog`. Therefore, calling this
@@ -105,21 +197,20 @@ Function Properties of the `Debugger.Mem
 
     <pre class='language-js'><code>
     {
       "timestamp": <i>timestamp</i>,
       "frame": <i>allocationSite</i>
     }
     </code></pre>
 
-    Here <i>timestamp</i> is the timestamp of the event in units of
-    microseconds since the epoch and <i>allocationSite</i> is an
-    allocation site (as a [captured stack][saved-frame]).
-    <i>allocationSite</i> is `null` for objects allocated with no
-    JavaScript frames on the stack.
+    Here <i>timestamp</i> is the [timestamp][timestamps] of the allocation event and
+    <i>allocationSite</i> is an allocation site (as a
+    [captured stack][saved-frame]).  <i>allocationSite</i> is `null` for objects
+    allocated with no JavaScript frames on the stack.
 
     When `trackingAllocationSites` is `false`, `drainAllocationsLog()` throws an
     `Error`.
 
 <code id='take-census'>takeCensus()</code>
 :   Carry out a census of the debuggee compartments' contents. A *census* is a
     complete traversal of the graph of all reachable memory items belonging to a
     particular `Debugger`'s debuggees. The census produces a count of those
--- a/js/src/doc/Debugger/config.sh
+++ b/js/src/doc/Debugger/config.sh
@@ -6,16 +6,17 @@ base-url https://developer.mozilla.org/e
 markdown Debugger-API.md Debugger-API
   label 'debugger'                              "The Debugger API"
 
 markdown Conventions.md Debugger-API/Conventions
   label 'conventions'                           "Debugger API: General Conventions"
   label 'dbg code'      '#debuggee-code'        "Debugger API: General Conventions: Debuggee Code"
   label 'cv'            '#completion-values'    "Debugger API: General Conventions: Completion Values"
   label 'rv'            '#resumption-values'    "Debugger API: General Conventions: Resumption Values"
+  label 'timestamps'    '#timestamps'           "Debugger API: General Conventions: Timestamps"
   label 'wouldrun'      '#the-debugger.debuggeewouldrun-exception' "Debugger API: DebuggeeWouldRun"
 
 markdown Debugger.md Debugger-API/Debugger
   label 'debugger-object'                       "The Debugger object"
   label 'add'           '#addDebuggee'          "The Debugger object: addDebuggee"
 
 markdown Debugger.Environment.md Debugger-API/Debugger.Environment
   label 'environment'                           "Debugger.Environment"
--- a/js/src/frontend/Parser.cpp
+++ b/js/src/frontend/Parser.cpp
@@ -2551,17 +2551,17 @@ Parser<ParseHandler>::functionArgsAndBod
         fun->setIsExprClosure();
 #endif
     }
 
     Node body = functionBody(kind, bodyType);
     if (!body)
         return false;
 
-    if (kind != Method && kind != Lazy && 
+    if (kind != Method && kind != Lazy &&
         fun->name() && !checkStrictBinding(fun->name(), pn))
     {
         return false;
     }
 
     if (bodyType == StatementListBody) {
         bool matched;
         if (!tokenStream.matchToken(&matched, TOK_RC))
@@ -6410,16 +6410,25 @@ Parser<ParseHandler>::assignExpr(Invoked
       case TOK_LSHASSIGN:    kind = PNK_LSHASSIGN;    op = JSOP_LSH;    break;
       case TOK_RSHASSIGN:    kind = PNK_RSHASSIGN;    op = JSOP_RSH;    break;
       case TOK_URSHASSIGN:   kind = PNK_URSHASSIGN;   op = JSOP_URSH;   break;
       case TOK_MULASSIGN:    kind = PNK_MULASSIGN;    op = JSOP_MUL;    break;
       case TOK_DIVASSIGN:    kind = PNK_DIVASSIGN;    op = JSOP_DIV;    break;
       case TOK_MODASSIGN:    kind = PNK_MODASSIGN;    op = JSOP_MOD;    break;
 
       case TOK_ARROW: {
+        // A line terminator between ArrowParameters and the => should trigger a SyntaxError.
+        tokenStream.ungetToken();
+        TokenKind next;
+        if (!tokenStream.peekTokenSameLine(&next) || next != TOK_ARROW) {
+            report(ParseError, false, null(), JSMSG_UNEXPECTED_TOKEN,
+                   "expression", TokenKindToDesc(TOK_ARROW));
+            return null();
+        }
+
         tokenStream.seek(start);
         if (!abortIfSyntaxParser())
             return null();
 
         TokenKind ignored;
         if (!tokenStream.peekToken(&ignored))
             return null();
 
@@ -8446,17 +8455,17 @@ Parser<ParseHandler>::primaryExpr(TokenK
         if (!tokenStream.getToken(&next))
             return null();
         if (next != TOK_RP) {
             report(ParseError, false, null(), JSMSG_UNEXPECTED_TOKEN,
                    "closing parenthesis", TokenKindToDesc(next));
             return null();
         }
 
-        if (!tokenStream.peekToken(&next))
+        if (!tokenStream.peekTokenSameLine(&next))
             return null();
         if (next != TOK_ARROW) {
             report(ParseError, false, null(), JSMSG_UNEXPECTED_TOKEN,
                    "'=>' after argument list", TokenKindToDesc(next));
             return null();
         }
 
         tokenStream.ungetToken();  // put back right paren
--- a/js/src/gc/Allocator.cpp
+++ b/js/src/gc/Allocator.cpp
@@ -3,242 +3,231 @@
  * 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 "gc/Allocator.h"
 
 #include "jscntxt.h"
 
+#include "gc/GCInternals.h"
 #include "gc/GCTrace.h"
 #include "gc/Nursery.h"
 #include "jit/JitCompartment.h"
 #include "vm/Runtime.h"
 #include "vm/String.h"
 
 #include "jsobjinlines.h"
 
 using namespace js;
 using namespace gc;
 
-static inline bool
-ShouldNurseryAllocateObject(const Nursery &nursery, InitialHeap heap)
-{
-    return nursery.isEnabled() && heap != TenuredHeap;
-}
-
-/*
- * Attempt to allocate a new GC thing out of the nursery. If there is not enough
- * room in the nursery or there is an OOM, this method will return nullptr.
- */
-template <AllowGC allowGC>
-inline JSObject *
-TryNewNurseryObject(JSContext *cx, size_t thingSize, size_t nDynamicSlots, const Class *clasp)
+bool
+GCRuntime::gcIfNeededPerAllocation(JSContext *cx)
 {
-    MOZ_ASSERT(!IsAtomsCompartment(cx->compartment()));
-    JSRuntime *rt = cx->runtime();
-    Nursery &nursery = rt->gc.nursery;
-    JSObject *obj = nursery.allocateObject(cx, thingSize, nDynamicSlots, clasp);
-    if (obj)
-        return obj;
-
-    if (allowGC && !rt->mainThread.suppressGC) {
-        cx->minorGC(JS::gcreason::OUT_OF_NURSERY);
-
-        /* Exceeding gcMaxBytes while tenuring can disable the Nursery. */
-        if (nursery.isEnabled()) {
-            JSObject *obj = nursery.allocateObject(cx, thingSize, nDynamicSlots, clasp);
-            MOZ_ASSERT(obj);
-            return obj;
-        }
-    }
-    return nullptr;
-}
-
-static inline bool
-PossiblyFail()
-{
-    JS_OOM_POSSIBLY_FAIL_BOOL();
-    return true;
-}
-
-static inline bool
-GCIfNeeded(ExclusiveContext *cx)
-{
-    if (cx->isJSContext()) {
-        JSContext *ncx = cx->asJSContext();
-        JSRuntime *rt = ncx->runtime();
-
 #ifdef JS_GC_ZEAL
-        if (rt->gc.needZealousGC())
-            rt->gc.runDebugGC();
+    if (needZealousGC())
+        runDebugGC();
 #endif
 
-        // Invoking the interrupt callback can fail and we can't usefully
-        // handle that here. Just check in case we need to collect instead.
-        if (rt->hasPendingInterrupt())
-            rt->gc.gcIfRequested(ncx);
+    // Invoking the interrupt callback can fail and we can't usefully
+    // handle that here. Just check in case we need to collect instead.
+    if (rt->hasPendingInterrupt())
+        gcIfRequested(cx);
 
-        // If we have grown past our GC heap threshold while in the middle of
-        // an incremental GC, we're growing faster than we're GCing, so stop
-        // the world and do a full, non-incremental GC right now, if possible.
-        if (rt->gc.isIncrementalGCInProgress() &&
-            cx->zone()->usage.gcBytes() > cx->zone()->threshold.gcTriggerBytes())
-        {
-            PrepareZoneForGC(cx->zone());
-            AutoKeepAtoms keepAtoms(cx->perThreadData);
-            rt->gc.gc(GC_NORMAL, JS::gcreason::INCREMENTAL_TOO_SLOW);
-        }
+    // If we have grown past our GC heap threshold while in the middle of
+    // an incremental GC, we're growing faster than we're GCing, so stop
+    // the world and do a full, non-incremental GC right now, if possible.
+    if (isIncrementalGCInProgress() &&
+        cx->zone()->usage.gcBytes() > cx->zone()->threshold.gcTriggerBytes())
+    {
+        PrepareZoneForGC(cx->zone());
+        AutoKeepAtoms keepAtoms(cx->perThreadData);
+        gc(GC_NORMAL, JS::gcreason::INCREMENTAL_TOO_SLOW);
     }
 
     return true;
 }
 
 template <AllowGC allowGC>
-static inline bool
-CheckAllocatorState(ExclusiveContext *cx, AllocKind kind)
+bool
+GCRuntime::checkAllocatorState(JSContext *cx, AllocKind kind)
 {
     if (allowGC) {
-        if (!GCIfNeeded(cx))
+        if (!gcIfNeededPerAllocation(cx))
             return false;
     }
 
-    if (!cx->isJSContext())
-        return true;
-
-    JSContext *ncx = cx->asJSContext();
-    JSRuntime *rt = ncx->runtime();
 #if defined(JS_GC_ZEAL) || defined(DEBUG)
-    MOZ_ASSERT_IF(rt->isAtomsCompartment(ncx->compartment()),
+    MOZ_ASSERT_IF(rt->isAtomsCompartment(cx->compartment()),
                   kind == AllocKind::STRING ||
                   kind == AllocKind::FAT_INLINE_STRING ||
                   kind == AllocKind::SYMBOL ||
                   kind == AllocKind::JITCODE);
     MOZ_ASSERT(!rt->isHeapBusy());
-    MOZ_ASSERT(rt->gc.isAllocAllowed());
+    MOZ_ASSERT(isAllocAllowed());
 #endif
 
     // Crash if we perform a GC action when it is not safe.
     if (allowGC && !rt->mainThread.suppressGC)
         JS::AutoAssertOnGC::VerifyIsSafeToGC(rt);
 
     // For testing out of memory conditions
-    if (!PossiblyFail()) {
-        ReportOutOfMemory(ncx);
+    if (js::oom::ShouldFailWithOOM()) {
+        ReportOutOfMemory(cx);
         return false;
     }
 
     return true;
 }
 
 template <typename T>
-static inline void
-CheckIncrementalZoneState(ExclusiveContext *cx, T *t)
+/* static */ void
+GCRuntime::checkIncrementalZoneState(ExclusiveContext *cx, T *t)
 {
 #ifdef DEBUG
     if (!cx->isJSContext())
         return;
 
     Zone *zone = cx->asJSContext()->zone();
     MOZ_ASSERT_IF(t && zone->wasGCStarted() && (zone->isGCMarking() || zone->isGCSweeping()),
                   t->asTenured().arenaHeader()->allocatedDuringIncremental);
 #endif
 }
 
-/*
- * Allocate a new GC thing. After a successful allocation the caller must
- * fully initialize the thing before calling any function that can potentially
- * trigger GC. This will ensure that GC tracing never sees junk values stored
- * in the partially initialized thing.
- */
-
+// Allocate a new GC thing. After a successful allocation the caller must
+// fully initialize the thing before calling any function that can potentially
+// trigger GC. This will ensure that GC tracing never sees junk values stored
+// in the partially initialized thing.
 template <typename T, AllowGC allowGC /* = CanGC */>
 JSObject *
 js::Allocate(ExclusiveContext *cx, AllocKind kind, size_t nDynamicSlots, InitialHeap heap,
              const Class *clasp)
 {
     static_assert(mozilla::IsConvertible<T *, JSObject *>::value, "must be JSObject derived");
     MOZ_ASSERT(kind <= AllocKind::OBJECT_LAST);
     size_t thingSize = Arena::thingSize(kind);
 
     MOZ_ASSERT(thingSize == Arena::thingSize(kind));
     MOZ_ASSERT(thingSize >= sizeof(JSObject_Slots0));
     static_assert(sizeof(JSObject_Slots0) >= CellSize,
                   "All allocations must be at least the allocator-imposed minimum size.");
 
-    if (!CheckAllocatorState<allowGC>(cx, kind))
+    // Off-main-thread alloc cannot trigger GC or make runtime assertions.
+    if (!cx->isJSContext())
+        return GCRuntime::tryNewTenuredObject<NoGC>(cx, kind, thingSize, nDynamicSlots);
+
+    JSContext *ncx = cx->asJSContext();
+    JSRuntime *rt = ncx->runtime();
+    if (!rt->gc.checkAllocatorState<allowGC>(ncx, kind))
         return nullptr;
 
-    if (cx->isJSContext() &&
-        ShouldNurseryAllocateObject(cx->asJSContext()->nursery(), heap))
-    {
-        JSObject *obj = TryNewNurseryObject<allowGC>(cx->asJSContext(), thingSize, nDynamicSlots,
-                                                     clasp);
+    if (ncx->nursery().isEnabled() && heap != TenuredHeap) {
+        JSObject *obj = rt->gc.tryNewNurseryObject<allowGC>(ncx, thingSize, nDynamicSlots, clasp);
         if (obj)
             return obj;
 
         // Our most common non-jit allocation path is NoGC; thus, if we fail the
         // alloc and cannot GC, we *must* return nullptr here so that the caller
         // will do a CanGC allocation to clear the nursery. Failing to do so will
         // cause all allocations on this path to land in Tenured, and we will not
         // get the benefit of the nursery.
         if (!allowGC)
             return nullptr;
     }
 
+    return GCRuntime::tryNewTenuredObject<allowGC>(cx, kind, thingSize, nDynamicSlots);
+}
+template JSObject *js::Allocate<JSObject, NoGC>(ExclusiveContext *cx, gc::AllocKind kind,
+                                                size_t nDynamicSlots, gc::InitialHeap heap,
+                                                const Class *clasp);
+template JSObject *js::Allocate<JSObject, CanGC>(ExclusiveContext *cx, gc::AllocKind kind,
+                                                 size_t nDynamicSlots, gc::InitialHeap heap,
+                                                 const Class *clasp);
+
+// Attempt to allocate a new GC thing out of the nursery. If there is not enough
+// room in the nursery or there is an OOM, this method will return nullptr.
+template <AllowGC allowGC>
+JSObject *
+GCRuntime::tryNewNurseryObject(JSContext *cx, size_t thingSize, size_t nDynamicSlots, const Class *clasp)
+{
+    MOZ_ASSERT(!IsAtomsCompartment(cx->compartment()));
+    JSObject *obj = nursery.allocateObject(cx, thingSize, nDynamicSlots, clasp);
+    if (obj)
+        return obj;
+
+    if (allowGC && !rt->mainThread.suppressGC) {
+        minorGC(cx, JS::gcreason::OUT_OF_NURSERY);
+
+        // Exceeding gcMaxBytes while tenuring can disable the Nursery.
+        if (nursery.isEnabled()) {
+            JSObject *obj = nursery.allocateObject(cx, thingSize, nDynamicSlots, clasp);
+            MOZ_ASSERT(obj);
+            return obj;
+        }
+    }
+    return nullptr;
+}
+
+typedef mozilla::UniquePtr<HeapSlot, JS::FreePolicy> UniqueSlots;
+
+static inline UniqueSlots
+MakeSlotArray(ExclusiveContext *cx, size_t count)
+{
+    HeapSlot *slots = nullptr;
+    if (count) {
+        slots = cx->zone()->pod_malloc<HeapSlot>(count);
+        if (slots)
+            Debug_SetSlotRangeToCrashOnTouch(slots, count);
+    }
+    return UniqueSlots(slots);
+}
+
+template <AllowGC allowGC>
+JSObject *
+GCRuntime::tryNewTenuredObject(ExclusiveContext *cx, AllocKind kind, size_t thingSize,
+                               size_t nDynamicSlots)
+{
     HeapSlot *slots = nullptr;
     if (nDynamicSlots) {
         slots = cx->zone()->pod_malloc<HeapSlot>(nDynamicSlots);
         if (MOZ_UNLIKELY(!slots))
             return nullptr;
         Debug_SetSlotRangeToCrashOnTouch(slots, nDynamicSlots);
     }
 
-    JSObject *obj = reinterpret_cast<JSObject *>(cx->arenas()->allocateFromFreeList(kind, thingSize));
-    if (!obj)
-        obj = reinterpret_cast<JSObject *>(GCRuntime::refillFreeListFromAnyThread<allowGC>(cx, kind));
+    JSObject *obj = tryNewTenuredThing<JSObject, allowGC>(cx, kind, thingSize);
 
     if (obj)
         obj->setInitialSlotsMaybeNonNative(slots);
     else
         js_free(slots);
 
-    CheckIncrementalZoneState(cx, obj);
-    TraceTenuredAlloc(obj, kind);
     return obj;
 }
-template JSObject *js::Allocate<JSObject, NoGC>(ExclusiveContext *cx, gc::AllocKind kind,
-                                                size_t nDynamicSlots, gc::InitialHeap heap,
-                                                const Class *clasp);
-template JSObject *js::Allocate<JSObject, CanGC>(ExclusiveContext *cx, gc::AllocKind kind,
-                                                 size_t nDynamicSlots, gc::InitialHeap heap,
-                                                 const Class *clasp);
 
 template <typename T, AllowGC allowGC /* = CanGC */>
 T *
 js::Allocate(ExclusiveContext *cx)
 {
     static_assert(!mozilla::IsConvertible<T*, JSObject*>::value, "must not be JSObject derived");
     static_assert(sizeof(T) >= CellSize,
                   "All allocations must be at least the allocator-imposed minimum size.");
 
     AllocKind kind = MapTypeToFinalizeKind<T>::kind;
     size_t thingSize = sizeof(T);
-
     MOZ_ASSERT(thingSize == Arena::thingSize(kind));
-    if (!CheckAllocatorState<allowGC>(cx, kind))
-        return nullptr;
 
-    T *t = static_cast<T *>(cx->arenas()->allocateFromFreeList(kind, thingSize));
-    if (!t)
-        t = static_cast<T *>(GCRuntime::refillFreeListFromAnyThread<allowGC>(cx, kind));
+    if (cx->isJSContext()) {
+        JSContext *ncx = cx->asJSContext();
+        if (!ncx->runtime()->gc.checkAllocatorState<allowGC>(ncx, kind))
+            return nullptr;
+    }
 
-    CheckIncrementalZoneState(cx, t);
-    gc::TraceTenuredAlloc(t, kind);
-    return t;
+    return GCRuntime::tryNewTenuredThing<T, allowGC>(cx, kind, thingSize);
 }
 
 #define FOR_ALL_NON_OBJECT_GC_LAYOUTS(macro) \
     macro(JS::Symbol) \
     macro(JSExternalString) \
     macro(JSFatInlineString) \
     macro(JSScript) \
     macro(JSString) \
@@ -249,8 +238,200 @@ js::Allocate(ExclusiveContext *cx)
     macro(js::Shape) \
     macro(js::jit::JitCode)
 
 #define DECL_ALLOCATOR_INSTANCES(type) \
     template type *js::Allocate<type, NoGC>(ExclusiveContext *cx);\
     template type *js::Allocate<type, CanGC>(ExclusiveContext *cx);
 FOR_ALL_NON_OBJECT_GC_LAYOUTS(DECL_ALLOCATOR_INSTANCES)
 #undef DECL_ALLOCATOR_INSTANCES
+
+template <typename T, AllowGC allowGC>
+/* static */ T *
+GCRuntime::tryNewTenuredThing(ExclusiveContext *cx, AllocKind kind, size_t thingSize)
+{
+    T *t = reinterpret_cast<T *>(cx->arenas()->allocateFromFreeList(kind, thingSize));
+    if (!t)
+        t = reinterpret_cast<T *>(refillFreeListFromAnyThread<allowGC>(cx, kind));
+
+    checkIncrementalZoneState(cx, t);
+    TraceTenuredAlloc(t, kind);
+    return t;
+}
+
+template <AllowGC allowGC>
+/* static */ void *
+GCRuntime::refillFreeListFromAnyThread(ExclusiveContext *cx, AllocKind thingKind)
+{
+    MOZ_ASSERT(cx->arenas()->freeLists[thingKind].isEmpty());
+
+    if (cx->isJSContext())
+        return refillFreeListFromMainThread<allowGC>(cx->asJSContext(), thingKind);
+
+    return refillFreeListOffMainThread(cx, thingKind);
+}
+
+template <AllowGC allowGC>
+/* static */ void *
+GCRuntime::refillFreeListFromMainThread(JSContext *cx, AllocKind thingKind)
+{
+    JSRuntime *rt = cx->runtime();
+    MOZ_ASSERT(!rt->isHeapBusy(), "allocating while under GC");
+    MOZ_ASSERT_IF(allowGC, !rt->currentThreadHasExclusiveAccess());
+
+    // Try to allocate; synchronize with background GC threads if necessary.
+    void *thing = tryRefillFreeListFromMainThread(cx, thingKind);
+    if (MOZ_LIKELY(thing))
+        return thing;
+
+    // Perform a last-ditch GC to hopefully free up some memory.
+    {
+        // If we are doing a fallible allocation, percolate up the OOM
+        // instead of reporting it.
+        if (!allowGC)
+            return nullptr;
+
+        JS::PrepareForFullGC(rt);
+        AutoKeepAtoms keepAtoms(cx->perThreadData);
+        rt->gc.gc(GC_SHRINK, JS::gcreason::LAST_DITCH);
+    }
+
+    // Retry the allocation after the last-ditch GC.
+    thing = tryRefillFreeListFromMainThread(cx, thingKind);
+    if (thing)
+        return thing;
+
+    // We are really just totally out of memory.
+    MOZ_ASSERT(allowGC, "A fallible allocation must not report OOM on failure.");
+    ReportOutOfMemory(cx);
+    return nullptr;
+}
+
+/* static */ void *
+GCRuntime::tryRefillFreeListFromMainThread(JSContext *cx, AllocKind thingKind)
+{
+    ArenaLists *arenas = cx->arenas();
+    Zone *zone = cx->zone();
+
+    AutoMaybeStartBackgroundAllocation maybeStartBGAlloc;
+
+    void *thing = arenas->allocateFromArena(zone, thingKind, maybeStartBGAlloc);
+    if (MOZ_LIKELY(thing))
+        return thing;
+
+    // Even if allocateFromArena failed due to OOM, a background
+    // finalization or allocation task may be running freeing more memory
+    // or adding more available memory to our free pool; wait for them to
+    // finish, then try to allocate again in case they made more memory
+    // available.
+    cx->runtime()->gc.waitBackgroundSweepOrAllocEnd();
+
+    thing = arenas->allocateFromArena(zone, thingKind, maybeStartBGAlloc);
+    if (thing)
+        return thing;
+
+    return nullptr;
+}
+
+/* static */ void *
+GCRuntime::refillFreeListOffMainThread(ExclusiveContext *cx, AllocKind thingKind)
+{
+    ArenaLists *arenas = cx->arenas();
+    Zone *zone = cx->zone();
+    JSRuntime *rt = zone->runtimeFromAnyThread();
+
+    AutoMaybeStartBackgroundAllocation maybeStartBGAlloc;
+
+    // If we're off the main thread, we try to allocate once and return
+    // whatever value we get. We need to first ensure the main thread is not in
+    // a GC session.
+    AutoLockHelperThreadState lock;
+    while (rt->isHeapBusy())
+        HelperThreadState().wait(GlobalHelperThreadState::PRODUCER);
+
+    void *thing = arenas->allocateFromArena(zone, thingKind, maybeStartBGAlloc);
+    if (thing)
+        return thing;
+
+    ReportOutOfMemory(cx);
+    return nullptr;
+}
+
+TenuredCell *
+ArenaLists::allocateFromArena(JS::Zone *zone, AllocKind thingKind,
+                              AutoMaybeStartBackgroundAllocation &maybeStartBGAlloc)
+{
+    JSRuntime *rt = zone->runtimeFromAnyThread();
+    Maybe<AutoLockGC> maybeLock;
+
+    // See if we can proceed without taking the GC lock.
+    if (backgroundFinalizeState[thingKind] != BFS_DONE)
+        maybeLock.emplace(rt);
+
+    ArenaList &al = arenaLists[thingKind];
+    ArenaHeader *aheader = al.takeNextArena();
+    if (aheader) {
+        // Empty arenas should be immediately freed.
+        MOZ_ASSERT(!aheader->isEmpty());
+
+        return allocateFromArenaInner<HasFreeThings>(zone, aheader, thingKind);
+    }
+
+    // Parallel threads have their own ArenaLists, but chunks are shared;
+    // if we haven't already, take the GC lock now to avoid racing.
+    if (maybeLock.isNothing())
+        maybeLock.emplace(rt);
+
+    Chunk *chunk = rt->gc.pickChunk(maybeLock.ref(), maybeStartBGAlloc);
+    if (!chunk)
+        return nullptr;
+
+    // Although our chunk should definitely have enough space for another arena,
+    // there are other valid reasons why Chunk::allocateArena() may fail.
+    aheader = rt->gc.allocateArena(chunk, zone, thingKind, maybeLock.ref());
+    if (!aheader)
+        return nullptr;
+
+    MOZ_ASSERT(!maybeLock->wasUnlocked());
+    MOZ_ASSERT(al.isCursorAtEnd());
+    al.insertAtCursor(aheader);
+
+    return allocateFromArenaInner<IsEmpty>(zone, aheader, thingKind);
+}
+
+template <ArenaLists::ArenaAllocMode hasFreeThings>
+TenuredCell *
+ArenaLists::allocateFromArenaInner(JS::Zone *zone, ArenaHeader *aheader, AllocKind kind)
+{